Merge remote-tracking branch 'upstream/master' into zip-207

This commit is contained in:
Kris Nuttycombe 2020-07-08 21:43:38 -06:00
commit e456d87cbf
30 changed files with 1165 additions and 505 deletions

View File

@ -40,11 +40,11 @@ class BlockchainTest(BitcoinTestFramework):
node = self.nodes[0] node = self.nodes[0]
res = node.gettxoutsetinfo() res = node.gettxoutsetinfo()
assert_equal(res['total_amount'], decimal.Decimal('2181.25000000')) # 150*12.5 + 49*6.25 assert_equal(res['total_amount'], decimal.Decimal('2143.75000000')) # 144*12.5 + 55*6.25
assert_equal(res['transactions'], 200) assert_equal(res['transactions'], 200)
assert_equal(res['height'], 200) assert_equal(res['height'], 200)
assert_equal(res['txouts'], 349) # 150*2 + 49 assert_equal(res['txouts'], 343) # 144*2 + 55
assert_equal(res['bytes_serialized'], 14951), # 32*199 + 48*90 + 49*60 + 27*49 assert_equal(res['bytes_serialized'], 14819), # 32*199 + 48*90 + 49*54 + 27*55
assert_equal(len(res['bestblock']), 64) assert_equal(len(res['bestblock']), 64)
assert_equal(len(res['hash_serialized']), 64) assert_equal(len(res['hash_serialized']), 64)

View File

@ -223,7 +223,7 @@ def initialize_chain(test_dir):
print("initialize_chain: bitcoind started, waiting for RPC to come up") print("initialize_chain: bitcoind started, waiting for RPC to come up")
wait_for_bitcoind_start(bitcoind_processes[i], rpc_url(i), i) wait_for_bitcoind_start(bitcoind_processes[i], rpc_url(i), i)
if os.getenv("PYTHON_DEBUG", ""): if os.getenv("PYTHON_DEBUG", ""):
print("initialize_chain: RPC succesfully started") print("initialize_chain: RPC successfully started")
rpcs = [] rpcs = []
for i in range(4): for i in range(4):
@ -313,7 +313,7 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=
url = rpc_url(i, rpchost) url = rpc_url(i, rpchost)
wait_for_bitcoind_start(bitcoind_processes[i], url, i) wait_for_bitcoind_start(bitcoind_processes[i], url, i)
if os.getenv("PYTHON_DEBUG", ""): if os.getenv("PYTHON_DEBUG", ""):
print("start_node: RPC succesfully started") print("start_node: RPC successfully started")
proxy = get_rpc_proxy(url, i, timeout=timewait) proxy = get_rpc_proxy(url, i, timeout=timewait)
if COVERAGE_DIR: if COVERAGE_DIR:

View File

@ -122,7 +122,7 @@ void AMQPNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindex)
} }
} }
void AMQPNotificationInterface::SyncTransaction(const CTransaction &tx, const CBlock *pblock) void AMQPNotificationInterface::SyncTransaction(const CTransaction &tx, const CBlock *pblock, const int nHeight)
{ {
for (std::list<AMQPAbstractNotifier*>::iterator i = notifiers.begin(); i != notifiers.end(); ) { for (std::list<AMQPAbstractNotifier*>::iterator i = notifiers.begin(); i != notifiers.end(); ) {
AMQPAbstractNotifier *notifier = *i; AMQPAbstractNotifier *notifier = *i;

View File

@ -24,7 +24,7 @@ protected:
void Shutdown(); void Shutdown();
// CValidationInterface // CValidationInterface
void SyncTransaction(const CTransaction &tx, const CBlock *pblock); void SyncTransaction(const CTransaction &tx, const CBlock *pblock, const int nHeight);
void UpdatedBlockTip(const CBlockIndex *pindex); void UpdatedBlockTip(const CBlockIndex *pindex);
private: private:

View File

@ -31,6 +31,8 @@ static const unsigned int MAX_TX_SIZE_AFTER_SAPLING = MAX_BLOCK_SIZE;
static const int COINBASE_MATURITY = 100; static const int COINBASE_MATURITY = 100;
/** The minimum value which is invalid for expiry height, used by CTransaction and CMutableTransaction */ /** The minimum value which is invalid for expiry height, used by CTransaction and CMutableTransaction */
static constexpr uint32_t TX_EXPIRY_HEIGHT_THRESHOLD = 500000000; static constexpr uint32_t TX_EXPIRY_HEIGHT_THRESHOLD = 500000000;
/** The number of blocks after Canopy activation after which v1 plaintexts will be rejected */
static const unsigned int ZIP212_GRACE_PERIOD = 32256;
/** Flags for LockTime() */ /** Flags for LockTime() */
enum { enum {

View File

@ -1134,7 +1134,7 @@ TEST(ChecktransactionTests, HeartwoodAcceptsShieldedCoinbase) {
uint256 ovk; uint256 ovk;
auto note = libzcash::SaplingNote( auto note = libzcash::SaplingNote(
libzcash::SaplingSpendingKey::random().default_address(), CAmount(123456)); libzcash::SaplingSpendingKey::random().default_address(), CAmount(123456), libzcash::Zip212Enabled::BeforeZip212);
auto output = OutputDescriptionInfo(ovk, note, {{0xF6}}); auto output = OutputDescriptionInfo(ovk, note, {{0xF6}});
auto ctx = librustzcash_sapling_proving_ctx_init(); auto ctx = librustzcash_sapling_proving_ctx_init();
@ -1217,7 +1217,7 @@ TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {
uint256 ovk; uint256 ovk;
auto note = libzcash::SaplingNote( auto note = libzcash::SaplingNote(
libzcash::SaplingSpendingKey::random().default_address(), CAmount(123456)); libzcash::SaplingSpendingKey::random().default_address(), CAmount(123456), libzcash::Zip212Enabled::BeforeZip212);
auto output = OutputDescriptionInfo(ovk, note, {{0xF6}}); auto output = OutputDescriptionInfo(ovk, note, {{0xF6}});
CMutableTransaction mtx = GetValidTransaction(); CMutableTransaction mtx = GetValidTransaction();

View File

@ -10,6 +10,8 @@
#include "zcash/Address.hpp" #include "zcash/Address.hpp"
#include "crypto/sha256.h" #include "crypto/sha256.h"
#include "librustzcash.h" #include "librustzcash.h"
#include "consensus/params.h"
#include "utiltest.h"
class TestNoteDecryption : public ZCNoteDecryption { class TestNoteDecryption : public ZCNoteDecryption {
public: public:
@ -20,8 +22,14 @@ public:
} }
}; };
TEST(noteencryption, NotePlaintext) TEST(NoteEncryption, NotePlaintext)
{ {
SelectParams(CBaseChainParams::REGTEST);
std::vector<libzcash::Zip212Enabled> zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212};
const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy};
void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy};
using namespace libzcash; using namespace libzcash;
auto xsk = SaplingSpendingKey(uint256()).expanded_spending_key(); auto xsk = SaplingSpendingKey(uint256()).expanded_spending_key();
auto fvk = xsk.full_viewing_key(); auto fvk = xsk.full_viewing_key();
@ -34,131 +42,379 @@ TEST(noteencryption, NotePlaintext)
memo[i] = (unsigned char) i; memo[i] = (unsigned char) i;
} }
SaplingNote note(addr, 39393); for (int ver = 0; ver < zip_212_enabled.size(); ver++){
auto cmu_opt = note.cmu(); auto params = (*activations[ver])();
if (!cmu_opt) {
FAIL();
}
uint256 cmu = cmu_opt.get();
SaplingNotePlaintext pt(note, memo);
auto res = pt.encrypt(addr.pk_d); SaplingNote note(addr, 39393, zip_212_enabled[ver]);
if (!res) { auto cmu_opt = note.cmu();
FAIL(); if (!cmu_opt) {
} FAIL();
}
uint256 cmu = cmu_opt.get();
SaplingNotePlaintext pt(note, memo);
auto enc = res.get(); auto res = pt.encrypt(addr.pk_d);
if (!res) {
FAIL();
}
auto ct = enc.first; auto enc = res.get();
auto encryptor = enc.second;
auto epk = encryptor.get_epk();
// Try to decrypt with incorrect commitment auto ct = enc.first;
ASSERT_FALSE(SaplingNotePlaintext::decrypt( auto encryptor = enc.second;
ct, auto epk = encryptor.get_epk();
ivk,
epk,
uint256()
));
// Try to decrypt with correct commitment // Try to decrypt with incorrect commitment
auto foo = SaplingNotePlaintext::decrypt( ASSERT_FALSE(SaplingNotePlaintext::decrypt(
ct, params,
ivk, 1,
epk, ct,
cmu ivk,
); epk,
uint256()
));
if (!foo) { // Try to decrypt with correct commitment
FAIL(); auto foo = SaplingNotePlaintext::decrypt(
} params,
1,
ct,
ivk,
epk,
cmu
);
auto bar = foo.get(); if (!foo) {
FAIL();
}
ASSERT_TRUE(bar.value() == pt.value()); auto bar = foo.get();
ASSERT_TRUE(bar.memo() == pt.memo());
ASSERT_TRUE(bar.d == pt.d);
ASSERT_TRUE(bar.rcm == pt.rcm);
auto foobar = bar.note(ivk); ASSERT_TRUE(bar.value() == pt.value());
ASSERT_TRUE(bar.memo() == pt.memo());
ASSERT_TRUE(bar.d == pt.d);
ASSERT_TRUE(bar.rcm() == pt.rcm());
if (!foobar) { auto foobar = bar.note(ivk);
FAIL();
}
auto new_note = foobar.get(); if (!foobar) {
FAIL();
}
ASSERT_TRUE(note.value() == new_note.value()); auto new_note = foobar.get();
ASSERT_TRUE(note.d == new_note.d);
ASSERT_TRUE(note.pk_d == new_note.pk_d);
ASSERT_TRUE(note.r == new_note.r);
ASSERT_TRUE(note.cmu() == new_note.cmu());
SaplingOutgoingPlaintext out_pt; ASSERT_TRUE(note.value() == new_note.value());
out_pt.pk_d = note.pk_d; ASSERT_TRUE(note.d == new_note.d);
out_pt.esk = encryptor.get_esk(); ASSERT_TRUE(note.pk_d == new_note.pk_d);
ASSERT_TRUE(note.rcm() == new_note.rcm());
ASSERT_TRUE(note.cmu() == new_note.cmu());
auto ovk = random_uint256(); SaplingOutgoingPlaintext out_pt;
auto cv = random_uint256(); out_pt.pk_d = note.pk_d;
auto cm = random_uint256(); out_pt.esk = encryptor.get_esk();
auto out_ct = out_pt.encrypt( auto ovk = random_uint256();
ovk, auto cv = random_uint256();
cv, auto cm = random_uint256();
cm,
encryptor
);
auto decrypted_out_ct = out_pt.decrypt( auto out_ct = out_pt.encrypt(
out_ct, ovk,
ovk, cv,
cv, cm,
cm, encryptor
encryptor.get_epk() );
);
if (!decrypted_out_ct) { auto decrypted_out_ct = out_pt.decrypt(
FAIL(); out_ct,
} ovk,
cv,
cm,
encryptor.get_epk()
);
auto decrypted_out_ct_unwrapped = decrypted_out_ct.get(); if (!decrypted_out_ct) {
FAIL();
}
ASSERT_TRUE(decrypted_out_ct_unwrapped.pk_d == out_pt.pk_d); auto decrypted_out_ct_unwrapped = decrypted_out_ct.get();
ASSERT_TRUE(decrypted_out_ct_unwrapped.esk == out_pt.esk);
// Test sender won't accept invalid commitments ASSERT_TRUE(decrypted_out_ct_unwrapped.pk_d == out_pt.pk_d);
ASSERT_FALSE( ASSERT_TRUE(decrypted_out_ct_unwrapped.esk == out_pt.esk);
SaplingNotePlaintext::decrypt(
// Test sender won't accept invalid commitments
ASSERT_FALSE(
SaplingNotePlaintext::decrypt(
params,
1,
ct,
epk,
decrypted_out_ct_unwrapped.esk,
decrypted_out_ct_unwrapped.pk_d,
uint256()
)
);
// Test sender can decrypt the note ciphertext.
foo = SaplingNotePlaintext::decrypt(
params,
1,
ct, ct,
epk, epk,
decrypted_out_ct_unwrapped.esk, decrypted_out_ct_unwrapped.esk,
decrypted_out_ct_unwrapped.pk_d, decrypted_out_ct_unwrapped.pk_d,
uint256() cmu
) );
);
// Test sender can decrypt the note ciphertext. if (!foo) {
foo = SaplingNotePlaintext::decrypt( FAIL();
ct, }
epk,
decrypted_out_ct_unwrapped.esk,
decrypted_out_ct_unwrapped.pk_d,
cmu
);
if (!foo) { bar = foo.get();
FAIL();
ASSERT_TRUE(bar.value() == pt.value());
ASSERT_TRUE(bar.memo() == pt.memo());
ASSERT_TRUE(bar.d == pt.d);
ASSERT_TRUE(bar.rcm() == pt.rcm());
(*deactivations[ver])();
} }
bar = foo.get();
ASSERT_TRUE(bar.value() == pt.value());
ASSERT_TRUE(bar.memo() == pt.memo());
ASSERT_TRUE(bar.d == pt.d);
ASSERT_TRUE(bar.rcm == pt.rcm);
} }
TEST(noteencryption, SaplingApi) TEST(NoteEncryption, RejectsInvalidNoteZip212Enabled)
{
SelectParams(CBaseChainParams::REGTEST);
int overwinterActivationHeight = 5;
int saplingActivationHeight = 30;
int canopyActivationHeight = 70;
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, overwinterActivationHeight);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, saplingActivationHeight);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_CANOPY, canopyActivationHeight);
auto params = Params().GetConsensus();
using namespace libzcash;
auto xsk = SaplingSpendingKey(uint256()).expanded_spending_key();
auto fvk = xsk.full_viewing_key();
auto ivk = fvk.in_viewing_key();
SaplingPaymentAddress addr = *ivk.address({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
std::array<unsigned char, ZC_MEMO_SIZE> memo;
for (size_t i = 0; i < ZC_MEMO_SIZE; i++) {
// Fill the message with dummy data
memo[i] = (unsigned char) i;
}
{
// non-0x01 received before Canopy activation height
SaplingNote note(addr, 39393, Zip212Enabled::AfterZip212);
auto cmu_opt = note.cmu();
if (!cmu_opt) {
FAIL();
}
uint256 cmu = cmu_opt.get();
SaplingNotePlaintext pt(note, memo);
auto res = pt.encrypt(addr.pk_d);
if (!res) {
FAIL();
}
auto enc = res.get();
auto ct = enc.first;
auto encryptor = enc.second;
auto epk = encryptor.get_epk();
ASSERT_FALSE(SaplingNotePlaintext::decrypt(
params,
canopyActivationHeight - 1,
ct,
ivk,
epk,
cmu
));
}
{
// non-0x02 received past (Canopy activation height + grace period)
SaplingNote note(addr, 39393, Zip212Enabled::BeforeZip212);
auto cmu_opt = note.cmu();
if (!cmu_opt) {
FAIL();
}
uint256 cmu = cmu_opt.get();
SaplingNotePlaintext pt(note, memo);
auto res = pt.encrypt(addr.pk_d);
if (!res) {
FAIL();
}
auto enc = res.get();
auto ct = enc.first;
auto encryptor = enc.second;
auto epk = encryptor.get_epk();
ASSERT_FALSE(SaplingNotePlaintext::decrypt(
params,
canopyActivationHeight + ZIP212_GRACE_PERIOD,
ct,
ivk,
epk,
cmu
));
}
// Revert to test default
RegtestDeactivateCanopy();
RegtestDeactivateHeartwood();
RegtestDeactivateSapling();
}
TEST(NoteEncryption, AcceptsValidNoteZip212Enabled)
{
SelectParams(CBaseChainParams::REGTEST);
int overwinterActivationHeight = 5;
int saplingActivationHeight = 30;
int canopyActivationHeight = 70;
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, overwinterActivationHeight);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, saplingActivationHeight);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_CANOPY, canopyActivationHeight);
auto params = Params().GetConsensus();
using namespace libzcash;
auto xsk = SaplingSpendingKey(uint256()).expanded_spending_key();
auto fvk = xsk.full_viewing_key();
auto ivk = fvk.in_viewing_key();
SaplingPaymentAddress addr = *ivk.address({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
std::array<unsigned char, ZC_MEMO_SIZE> memo;
for (size_t i = 0; i < ZC_MEMO_SIZE; i++) {
// Fill the message with dummy data
memo[i] = (unsigned char) i;
}
{
// 0x01 received before Canopy activation height
SaplingNote note(addr, 39393, Zip212Enabled::BeforeZip212);
auto cmu_opt = note.cmu();
if (!cmu_opt) {
FAIL();
}
uint256 cmu = cmu_opt.get();
SaplingNotePlaintext pt(note, memo);
auto res = pt.encrypt(addr.pk_d);
if (!res) {
FAIL();
}
auto enc = res.get();
auto ct = enc.first;
auto encryptor = enc.second;
auto epk = encryptor.get_epk();
auto plaintext = SaplingNotePlaintext::decrypt(
params,
canopyActivationHeight - 1,
ct,
ivk,
epk,
cmu
);
if (!plaintext) {
FAIL();
}
}
{
// {0x01,0x02} received after Canopy activation and before grace period has elapsed
std::vector<libzcash::Zip212Enabled> zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212};
int height1 = canopyActivationHeight;
int height2 = canopyActivationHeight + (ZIP212_GRACE_PERIOD) - 1;
int heights[] = {height1, height2};
for (int i = 0; i < zip_212_enabled.size(); i++) {
for (int j = 0; j < sizeof(heights) / sizeof(int); j++) {
SaplingNote note(addr, 39393, zip_212_enabled[i]);
auto cmu_opt = note.cmu();
if (!cmu_opt) {
FAIL();
}
uint256 cmu = cmu_opt.get();
SaplingNotePlaintext pt(note, memo);
auto res = pt.encrypt(addr.pk_d);
if (!res) {
FAIL();
}
auto enc = res.get();
auto ct = enc.first;
auto encryptor = enc.second;
auto epk = encryptor.get_epk();
auto plaintext = SaplingNotePlaintext::decrypt(
params,
heights[j],
ct,
ivk,
epk,
cmu
);
if (!plaintext) {
FAIL();
}
}
}
}
{
// 0x02 received past (Canopy activation height + grace period)
SaplingNote note(addr, 39393, Zip212Enabled::AfterZip212);
auto cmu_opt = note.cmu();
if (!cmu_opt) {
FAIL();
}
uint256 cmu = cmu_opt.get();
SaplingNotePlaintext pt(note, memo);
auto res = pt.encrypt(addr.pk_d);
if (!res) {
FAIL();
}
auto enc = res.get();
auto ct = enc.first;
auto encryptor = enc.second;
auto epk = encryptor.get_epk();
auto plaintext = SaplingNotePlaintext::decrypt(
params,
canopyActivationHeight + ZIP212_GRACE_PERIOD,
ct,
ivk,
epk,
cmu
);
if (!plaintext) {
FAIL();
}
}
// Revert to test default
RegtestDeactivateCanopy();
RegtestDeactivateHeartwood();
RegtestDeactivateSapling();
}
TEST(NoteEncryption, SaplingApi)
{ {
using namespace libzcash; using namespace libzcash;
@ -182,11 +438,14 @@ TEST(noteencryption, SaplingApi)
small_message[i] = (unsigned char) i; small_message[i] = (unsigned char) i;
} }
uint256 esk;
librustzcash_sapling_generate_r(esk.begin());
// Invalid diversifier // Invalid diversifier
ASSERT_EQ(boost::none, SaplingNoteEncryption::FromDiversifier({1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})); ASSERT_EQ(boost::none, SaplingNoteEncryption::FromDiversifier({1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, esk));
// Encrypt to pk_1 // Encrypt to pk_1
auto enc = *SaplingNoteEncryption::FromDiversifier(pk_1.d); auto enc = *SaplingNoteEncryption::FromDiversifier(pk_1.d, esk);
auto ciphertext_1 = *enc.encrypt_to_recipient( auto ciphertext_1 = *enc.encrypt_to_recipient(
pk_1.pk_d, pk_1.pk_d,
message message
@ -208,7 +467,7 @@ TEST(noteencryption, SaplingApi)
); );
// Encrypt to pk_2 // Encrypt to pk_2
enc = *SaplingNoteEncryption::FromDiversifier(pk_2.d); enc = *SaplingNoteEncryption::FromDiversifier(pk_2.d, esk);
auto ciphertext_2 = *enc.encrypt_to_recipient( auto ciphertext_2 = *enc.encrypt_to_recipient(
pk_2.pk_d, pk_2.pk_d,
message message
@ -226,7 +485,7 @@ TEST(noteencryption, SaplingApi)
// Test nonce-reuse resistance of API // Test nonce-reuse resistance of API
{ {
auto tmp_enc = *SaplingNoteEncryption::FromDiversifier(pk_1.d); auto tmp_enc = *SaplingNoteEncryption::FromDiversifier(pk_1.d, esk);
tmp_enc.encrypt_to_recipient( tmp_enc.encrypt_to_recipient(
pk_1.pk_d, pk_1.pk_d,
@ -341,7 +600,7 @@ TEST(noteencryption, SaplingApi)
)); ));
} }
TEST(noteencryption, api) TEST(NoteEncryption, api)
{ {
uint256 sk_enc = ZCNoteEncryption::generate_privkey(uint252(uint256S("21035d60bc1983e37950ce4803418a8fb33ea68d5b937ca382ecbae7564d6a07"))); uint256 sk_enc = ZCNoteEncryption::generate_privkey(uint252(uint256S("21035d60bc1983e37950ce4803418a8fb33ea68d5b937ca382ecbae7564d6a07")));
uint256 pk_enc = ZCNoteEncryption::generate_pubkey(sk_enc); uint256 pk_enc = ZCNoteEncryption::generate_pubkey(sk_enc);
@ -446,7 +705,7 @@ uint256 test_prf(
return ret; return ret;
} }
TEST(noteencryption, PrfAddr) TEST(NoteEncryption, PrfAddr)
{ {
for (size_t i = 0; i < 100; i++) { for (size_t i = 0; i < 100; i++) {
uint252 a_sk = libzcash::random_uint252(); uint252 a_sk = libzcash::random_uint252();
@ -466,7 +725,7 @@ TEST(noteencryption, PrfAddr)
} }
} }
TEST(noteencryption, PrfNf) TEST(NoteEncryption, PrfNf)
{ {
for (size_t i = 0; i < 100; i++) { for (size_t i = 0; i < 100; i++) {
uint252 a_sk = libzcash::random_uint252(); uint252 a_sk = libzcash::random_uint252();
@ -477,7 +736,7 @@ TEST(noteencryption, PrfNf)
} }
} }
TEST(noteencryption, PrfPk) TEST(NoteEncryption, PrfPk)
{ {
for (size_t i = 0; i < 100; i++) { for (size_t i = 0; i < 100; i++) {
uint252 a_sk = libzcash::random_uint252(); uint252 a_sk = libzcash::random_uint252();
@ -500,7 +759,7 @@ TEST(noteencryption, PrfPk)
ASSERT_THROW(PRF_pk(dummy_a, 2, dummy_b), std::domain_error); ASSERT_THROW(PRF_pk(dummy_a, 2, dummy_b), std::domain_error);
} }
TEST(noteencryption, PrfRho) TEST(NoteEncryption, PrfRho)
{ {
for (size_t i = 0; i < 100; i++) { for (size_t i = 0; i < 100; i++) {
uint252 phi = libzcash::random_uint252(); uint252 phi = libzcash::random_uint252();
@ -523,7 +782,7 @@ TEST(noteencryption, PrfRho)
ASSERT_THROW(PRF_rho(dummy_a, 2, dummy_b), std::domain_error); ASSERT_THROW(PRF_rho(dummy_a, 2, dummy_b), std::domain_error);
} }
TEST(noteencryption, uint252) TEST(NoteEncryption, uint252)
{ {
ASSERT_THROW(uint252(uint256S("f6da8716682d600f74fc16bd0187faad6a26b4aa4c24d5c055b216d94516847e")), std::domain_error); ASSERT_THROW(uint252(uint256S("f6da8716682d600f74fc16bd0187faad6a26b4aa4c24d5c055b216d94516847e")), std::domain_error);
} }

View File

@ -44,7 +44,7 @@ TEST(SaplingNote, TestVectors)
uint256 nf(v_nf); uint256 nf(v_nf);
// Test commitment // Test commitment
SaplingNote note = SaplingNote(diversifier, pk_d, v, r); SaplingNote note = SaplingNote(diversifier, pk_d, v, r, Zip212Enabled::BeforeZip212);
ASSERT_EQ(note.cmu().get(), cm); ASSERT_EQ(note.cmu().get(), cm);
// Test nullifier // Test nullifier
@ -57,16 +57,16 @@ TEST(SaplingNote, Random)
{ {
// Test creating random notes using the same spending key // Test creating random notes using the same spending key
auto address = SaplingSpendingKey::random().default_address(); auto address = SaplingSpendingKey::random().default_address();
SaplingNote note1(address, GetRand(MAX_MONEY)); SaplingNote note1(address, GetRand(MAX_MONEY), Zip212Enabled::BeforeZip212);
SaplingNote note2(address, GetRand(MAX_MONEY)); SaplingNote note2(address, GetRand(MAX_MONEY), Zip212Enabled::BeforeZip212);
ASSERT_EQ(note1.d, note2.d); ASSERT_EQ(note1.d, note2.d);
ASSERT_EQ(note1.pk_d, note2.pk_d); ASSERT_EQ(note1.pk_d, note2.pk_d);
ASSERT_NE(note1.value(), note2.value()); ASSERT_NE(note1.value(), note2.value());
ASSERT_NE(note1.r, note2.r); ASSERT_NE(note1.rcm(), note2.rcm());
// Test diversifier and pk_d are not the same for different spending keys // Test diversifier and pk_d are not the same for different spending keys
SaplingNote note3(SaplingSpendingKey::random().default_address(), GetRand(MAX_MONEY)); SaplingNote note3(SaplingSpendingKey::random().default_address(), GetRand(MAX_MONEY), Zip212Enabled::BeforeZip212);
ASSERT_NE(note1.d, note3.d); ASSERT_NE(note1.d, note3.d);
ASSERT_NE(note1.pk_d, note3.pk_d); ASSERT_NE(note1.pk_d, note3.pk_d);
} }

View File

@ -1,5 +1,6 @@
#include "chainparams.h" #include "chainparams.h"
#include "consensus/params.h" #include "consensus/params.h"
#include "consensus/consensus.h"
#include "consensus/validation.h" #include "consensus/validation.h"
#include "key_io.h" #include "key_io.h"
#include "main.h" #include "main.h"
@ -482,7 +483,7 @@ TEST(TransactionBuilder, CheckSaplingTxVersion)
} }
// Cannot add Sapling spends to a non-Sapling transaction // Cannot add Sapling spends to a non-Sapling transaction
libzcash::SaplingNote note(pk, 50000); libzcash::SaplingNote note(pk, 50000, libzcash::Zip212Enabled::BeforeZip212);
SaplingMerkleTree tree; SaplingMerkleTree tree;
try { try {
builder.AddSaplingSpend(expsk, note, uint256(), tree.witness()); builder.AddSaplingSpend(expsk, note, uint256(), tree.witness());

View File

@ -936,14 +936,16 @@ bool ContextualCheckTransaction(
} }
// SaplingNotePlaintext::decrypt() checks note commitment validity. // SaplingNotePlaintext::decrypt() checks note commitment validity.
auto notePlaintext = SaplingNotePlaintext::decrypt( auto encPlaintext = SaplingNotePlaintext::decrypt(
chainparams.GetConsensus(),
nHeight,
output.encCiphertext, output.encCiphertext,
output.ephemeralKey, output.ephemeralKey,
outPlaintext->esk, outPlaintext->esk,
outPlaintext->pk_d, outPlaintext->pk_d,
output.cmu); output.cmu);
if (!notePlaintext) { if (!encPlaintext) {
return state.DoS( return state.DoS(
DOS_LEVEL_BLOCK, DOS_LEVEL_BLOCK,
error("CheckTransaction(): coinbase output description has invalid encCiphertext"), error("CheckTransaction(): coinbase output description has invalid encCiphertext"),
@ -951,23 +953,41 @@ bool ContextualCheckTransaction(
"bad-cb-output-desc-invalid-encct"); "bad-cb-output-desc-invalid-encct");
} }
// Detect any ZIP 207 shielded funding streams. // ZIP 207: detect shielded funding stream elements
if (canopyActive) { if (canopyActive) {
libzcash::SaplingPaymentAddress zaddr(notePlaintext->d, outPlaintext->pk_d); libzcash::SaplingPaymentAddress zaddr(encPlaintext->d, outPlaintext->pk_d);
for (auto it = fundingStreamElements.begin(); it != fundingStreamElements.end(); ++it) { for (auto it = fundingStreamElements.begin(); it != fundingStreamElements.end(); ++it) {
const libzcash::SaplingPaymentAddress* streamAddr = boost::get<libzcash::SaplingPaymentAddress>(&(it->first)); const libzcash::SaplingPaymentAddress* streamAddr = boost::get<libzcash::SaplingPaymentAddress>(&(it->first));
if (streamAddr && zaddr == *streamAddr && notePlaintext->value() == it->second) { if (streamAddr && zaddr == *streamAddr && encPlaintext->value() == it->second) {
fundingStreamElements.erase(it); fundingStreamElements.erase(it);
break; break;
} }
} }
} }
// ZIP 212: Check that the note plaintexts use the v2 note plaintext
// version.
// This check compels miners to switch to the new plaintext version
// and overrides the grace period in plaintext_version_is_valid()
if (canopyActive != (encPlaintext->get_leadbyte() == 0x02)) {
return state.DoS(
DOS_LEVEL_BLOCK,
error("CheckTransaction(): coinbase output description has invalid note plaintext version"),
REJECT_INVALID,
"bad-cb-output-desc-invalid-note-plaintext-version");
}
} }
} }
} }
// Rules that apply to Canopy or later: // Rules that apply to Canopy or later:
if (canopyActive) { if (canopyActive) {
for (const JSDescription& joinsplit : tx.vJoinSplit) {
if (joinsplit.vpub_old > 0) {
return state.DoS(DOS_LEVEL_BLOCK, error("ContextualCheckTransaction(): joinsplit.vpub_old nonzero"), REJECT_INVALID, "bad-txns-vpub_old-nonzero");
}
}
if (tx.IsCoinBase()) { if (tx.IsCoinBase()) {
// Detect transparent funding streams. // Detect transparent funding streams.
for (const CTxOut& output : tx.vout) { for (const CTxOut& output : tx.vout) {
@ -988,15 +1008,6 @@ bool ContextualCheckTransaction(
} }
} }
// Rules that apply to Canopy or later:
if (canopyActive) {
for (const JSDescription& joinsplit : tx.vJoinSplit) {
if (joinsplit.vpub_old > 0) {
return state.DoS(DOS_LEVEL_BLOCK, error("ContextualCheckTransaction(): joinsplit.vpub_old nonzero"), REJECT_INVALID, "bad-txns-vpub_old-nonzero");
}
}
}
auto consensusBranchId = CurrentEpochBranchId(nHeight, chainparams.GetConsensus()); auto consensusBranchId = CurrentEpochBranchId(nHeight, chainparams.GetConsensus());
auto prevConsensusBranchId = PrevEpochBranchId(consensusBranchId, chainparams.GetConsensus()); auto prevConsensusBranchId = PrevEpochBranchId(consensusBranchId, chainparams.GetConsensus());
uint256 dataToBeSigned; uint256 dataToBeSigned;

View File

@ -22,6 +22,7 @@
#include "main.h" #include "main.h"
#include "metrics.h" #include "metrics.h"
#include "net.h" #include "net.h"
#include "zcash/Note.hpp"
#include "policy/policy.h" #include "policy/policy.h"
#include "pow.h" #include "pow.h"
#include "primitives/transaction.h" #include "primitives/transaction.h"
@ -126,12 +127,17 @@ private:
CMutableTransaction &mtx; CMutableTransaction &mtx;
void* ctx; void* ctx;
const CAmount fundingStreamValue; const CAmount fundingStreamValue;
const libzcash::Zip212Enabled zip212Enabled;
public: public:
AddFundingStreamValueToTx(CMutableTransaction &mtx, void* ctx, const CAmount fundingStreamValue): mtx(mtx), ctx(ctx), fundingStreamValue(fundingStreamValue) {} AddFundingStreamValueToTx(
CMutableTransaction &mtx,
void* ctx,
const CAmount fundingStreamValue,
const libzcash::Zip212Enabled zip212Enabled): mtx(mtx), ctx(ctx), fundingStreamValue(fundingStreamValue), zip212Enabled(zip212Enabled) {}
bool operator()(const libzcash::SaplingPaymentAddress& pa) const { bool operator()(const libzcash::SaplingPaymentAddress& pa) const {
uint256 ovk; uint256 ovk;
auto note = libzcash::SaplingNote(pa, fundingStreamValue); auto note = libzcash::SaplingNote(pa, fundingStreamValue, zip212Enabled);
auto output = OutputDescriptionInfo(ovk, note, NO_MEMO); auto output = OutputDescriptionInfo(ovk, note, NO_MEMO);
auto odesc = output.Build(ctx); auto odesc = output.Build(ctx);
@ -166,6 +172,14 @@ public:
const int nHeight, const int nHeight,
const CAmount nFees) : mtx(mtx), chainparams(chainparams), nHeight(nHeight), nFees(nFees) {} const CAmount nFees) : mtx(mtx), chainparams(chainparams), nHeight(nHeight), nFees(nFees) {}
const libzcash::Zip212Enabled GetZip212Flag() const {
if (chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_CANOPY)) {
return libzcash::Zip212Enabled::AfterZip212;
} else {
return libzcash::Zip212Enabled::BeforeZip212;
}
}
CAmount SetFoundersRewardAndGetMinerValue(void* ctx) const { CAmount SetFoundersRewardAndGetMinerValue(void* ctx) const {
auto miner_reward = GetBlockSubsidy(nHeight, chainparams.GetConsensus()); auto miner_reward = GetBlockSubsidy(nHeight, chainparams.GetConsensus());
@ -178,7 +192,7 @@ public:
for (Consensus::FundingStreamElement fselem : fundingStreamElements) { for (Consensus::FundingStreamElement fselem : fundingStreamElements) {
miner_reward -= fselem.second; miner_reward -= fselem.second;
bool added = boost::apply_visitor(AddFundingStreamValueToTx(mtx, ctx, fselem.second), fselem.first); bool added = boost::apply_visitor(AddFundingStreamValueToTx(mtx, ctx, fselem.second, GetZip212Flag()), fselem.first);
if (!added) { if (!added) {
librustzcash_sapling_proving_ctx_free(ctx); librustzcash_sapling_proving_ctx_free(ctx);
throw new std::runtime_error("Failed to add funding stream output."); throw new std::runtime_error("Failed to add funding stream output.");
@ -235,7 +249,8 @@ public:
mtx.valueBalance -= miner_reward; mtx.valueBalance -= miner_reward;
uint256 ovk; uint256 ovk;
auto note = libzcash::SaplingNote(pa, miner_reward);
auto note = libzcash::SaplingNote(pa, miner_reward, GetZip212Flag());
auto output = OutputDescriptionInfo(ovk, note, NO_MEMO); auto output = OutputDescriptionInfo(ovk, note, NO_MEMO);
auto odesc = output.Build(ctx); auto odesc = output.Build(ctx);

View File

@ -9,6 +9,7 @@
#include "rpc/protocol.h" #include "rpc/protocol.h"
#include "script/sign.h" #include "script/sign.h"
#include "utilmoneystr.h" #include "utilmoneystr.h"
#include "zcash/Note.hpp"
#include <boost/variant.hpp> #include <boost/variant.hpp>
#include <librustzcash.h> #include <librustzcash.h>
@ -43,11 +44,12 @@ boost::optional<OutputDescription> OutputDescriptionInfo::Build(void* ctx) {
std::vector<unsigned char> addressBytes(ss.begin(), ss.end()); std::vector<unsigned char> addressBytes(ss.begin(), ss.end());
OutputDescription odesc; OutputDescription odesc;
uint256 rcm = this->note.rcm();
if (!librustzcash_sapling_output_proof( if (!librustzcash_sapling_output_proof(
ctx, ctx,
encryptor.get_esk().begin(), encryptor.get_esk().begin(),
addressBytes.data(), addressBytes.data(),
this->note.r.begin(), rcm.begin(),
this->note.value(), this->note.value(),
odesc.cv.begin(), odesc.cv.begin(),
odesc.zkproof.begin())) { odesc.zkproof.begin())) {
@ -161,7 +163,13 @@ void TransactionBuilder::AddSaplingOutput(
throw std::runtime_error("TransactionBuilder cannot add Sapling output to pre-Sapling transaction"); throw std::runtime_error("TransactionBuilder cannot add Sapling output to pre-Sapling transaction");
} }
auto note = libzcash::SaplingNote(to, value); libzcash::Zip212Enabled zip_212_enabled = libzcash::Zip212Enabled::BeforeZip212;
// We use nHeight = chainActive.Height() + 1 since the output will be included in the next block
if (Params().GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_CANOPY)) {
zip_212_enabled = libzcash::Zip212Enabled::AfterZip212;
}
auto note = libzcash::SaplingNote(to, value, zip_212_enabled);
outputs.emplace_back(ovk, note, memo); outputs.emplace_back(ovk, note, memo);
mtx.valueBalance -= value; mtx.valueBalance -= value;
} }
@ -324,12 +332,13 @@ TransactionBuilderResult TransactionBuilder::Build()
std::vector<unsigned char> witness(ss.begin(), ss.end()); std::vector<unsigned char> witness(ss.begin(), ss.end());
SpendDescription sdesc; SpendDescription sdesc;
uint256 rcm = spend.note.rcm();
if (!librustzcash_sapling_spend_proof( if (!librustzcash_sapling_spend_proof(
ctx, ctx,
spend.expsk.full_viewing_key().ak.begin(), spend.expsk.full_viewing_key().ak.begin(),
spend.expsk.nsk.begin(), spend.expsk.nsk.begin(),
spend.note.d.data(), spend.note.d.data(),
spend.note.r.begin(), rcm.begin(),
spend.alpha.begin(), spend.alpha.begin(),
spend.note.value(), spend.note.value(),
spend.anchor.begin(), spend.anchor.begin(),

View File

@ -264,6 +264,10 @@ const Consensus::Params& RegtestActivateCanopy(bool updatePow, int canopyActivat
return Params().GetConsensus(); return Params().GetConsensus();
} }
const Consensus::Params& RegtestActivateCanopy() {
return RegtestActivateCanopy(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
}
void RegtestDeactivateCanopy() { void RegtestDeactivateCanopy() {
UpdateRegtestPow(0, 0, uint256S("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f")); UpdateRegtestPow(0, 0, uint256S("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"));
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_CANOPY, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); UpdateNetworkUpgradeParameters(Consensus::UPGRADE_CANOPY, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
@ -289,7 +293,7 @@ CKey AddTestCKeyToKeyStore(CBasicKeyStore& keyStore) {
TestSaplingNote GetTestSaplingNote(const libzcash::SaplingPaymentAddress& pa, CAmount value) { TestSaplingNote GetTestSaplingNote(const libzcash::SaplingPaymentAddress& pa, CAmount value) {
// Generate dummy Sapling note // Generate dummy Sapling note
libzcash::SaplingNote note(pa, value); libzcash::SaplingNote note(pa, value, libzcash::Zip212Enabled::BeforeZip212);
uint256 cm = note.cmu().get(); uint256 cm = note.cmu().get();
SaplingMerkleTree tree; SaplingMerkleTree tree;
tree.append(cm); tree.append(cm);

View File

@ -54,6 +54,7 @@ const Consensus::Params& RegtestActivateHeartwood(bool updatePow, int heartwoodA
void RegtestDeactivateHeartwood(); void RegtestDeactivateHeartwood();
const Consensus::Params& RegtestActivateCanopy(bool updatePow, int canopyActivationHeight = Consensus::NetworkUpgrade::ALWAYS_ACTIVE); const Consensus::Params& RegtestActivateCanopy(bool updatePow, int canopyActivationHeight = Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
const Consensus::Params& RegtestActivateCanopy();
void RegtestDeactivateCanopy(); void RegtestDeactivateCanopy();

View File

@ -25,7 +25,7 @@ CMainSignals& GetMainSignals()
void RegisterValidationInterface(CValidationInterface* pwalletIn) { void RegisterValidationInterface(CValidationInterface* pwalletIn) {
g_signals.UpdatedBlockTip.connect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1)); g_signals.UpdatedBlockTip.connect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1));
g_signals.SyncTransaction.connect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2)); g_signals.SyncTransaction.connect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2, _3));
g_signals.EraseTransaction.connect(boost::bind(&CValidationInterface::EraseFromWallet, pwalletIn, _1)); g_signals.EraseTransaction.connect(boost::bind(&CValidationInterface::EraseFromWallet, pwalletIn, _1));
g_signals.UpdatedTransaction.connect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1)); g_signals.UpdatedTransaction.connect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1));
g_signals.ChainTip.connect(boost::bind(&CValidationInterface::ChainTip, pwalletIn, _1, _2, _3)); g_signals.ChainTip.connect(boost::bind(&CValidationInterface::ChainTip, pwalletIn, _1, _2, _3));
@ -47,7 +47,7 @@ void UnregisterValidationInterface(CValidationInterface* pwalletIn) {
g_signals.SetBestChain.disconnect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1)); g_signals.SetBestChain.disconnect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
g_signals.UpdatedTransaction.disconnect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1)); g_signals.UpdatedTransaction.disconnect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1));
g_signals.EraseTransaction.disconnect(boost::bind(&CValidationInterface::EraseFromWallet, pwalletIn, _1)); g_signals.EraseTransaction.disconnect(boost::bind(&CValidationInterface::EraseFromWallet, pwalletIn, _1));
g_signals.SyncTransaction.disconnect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2)); g_signals.SyncTransaction.disconnect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2, _3));
g_signals.UpdatedBlockTip.disconnect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1)); g_signals.UpdatedBlockTip.disconnect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1));
} }
@ -65,8 +65,8 @@ void UnregisterAllValidationInterfaces() {
g_signals.UpdatedBlockTip.disconnect_all_slots(); g_signals.UpdatedBlockTip.disconnect_all_slots();
} }
void SyncWithWallets(const CTransaction &tx, const CBlock *pblock) { void SyncWithWallets(const CTransaction &tx, const CBlock *pblock, const int nHeight) {
g_signals.SyncTransaction(tx, pblock); g_signals.SyncTransaction(tx, pblock, nHeight);
} }
struct CachedBlockData { struct CachedBlockData {
@ -187,7 +187,7 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip)
// Let wallets know transactions went from 1-confirmed to // Let wallets know transactions went from 1-confirmed to
// 0-confirmed or conflicted: // 0-confirmed or conflicted:
for (const CTransaction &tx : block.vtx) { for (const CTransaction &tx : block.vtx) {
SyncWithWallets(tx, NULL); SyncWithWallets(tx, NULL, pindexLastTip->nHeight);
} }
// Update cached incremental witnesses // Update cached incremental witnesses
GetMainSignals().ChainTip(pindexLastTip, &block, boost::none); GetMainSignals().ChainTip(pindexLastTip, &block, boost::none);
@ -214,11 +214,11 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip)
// Tell wallet about transactions that went from mempool // Tell wallet about transactions that went from mempool
// to conflicted: // to conflicted:
for (const CTransaction &tx : blockData.txConflicted) { for (const CTransaction &tx : blockData.txConflicted) {
SyncWithWallets(tx, NULL); SyncWithWallets(tx, NULL, blockData.pindex->nHeight + 1);
} }
// ... and about transactions that got confirmed: // ... and about transactions that got confirmed:
for (const CTransaction &tx : block.vtx) { for (const CTransaction &tx : block.vtx) {
SyncWithWallets(tx, &block); SyncWithWallets(tx, &block, blockData.pindex->nHeight);
} }
// Update cached incremental witnesses // Update cached incremental witnesses
GetMainSignals().ChainTip(blockData.pindex, &block, blockData.oldTrees); GetMainSignals().ChainTip(blockData.pindex, &block, blockData.oldTrees);
@ -230,7 +230,7 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip)
// Notify transactions in the mempool // Notify transactions in the mempool
for (auto tx : recentlyAdded.first) { for (auto tx : recentlyAdded.first) {
try { try {
SyncWithWallets(tx, NULL); SyncWithWallets(tx, NULL, pindexLastTip->nHeight + 1);
} catch (const boost::thread_interrupted&) { } catch (const boost::thread_interrupted&) {
throw; throw;
} catch (const std::exception& e) { } catch (const std::exception& e) {

View File

@ -33,7 +33,7 @@ void UnregisterAllValidationInterfaces();
class CValidationInterface { class CValidationInterface {
protected: protected:
virtual void UpdatedBlockTip(const CBlockIndex *pindex) {} virtual void UpdatedBlockTip(const CBlockIndex *pindex) {}
virtual void SyncTransaction(const CTransaction &tx, const CBlock *pblock) {} virtual void SyncTransaction(const CTransaction &tx, const CBlock *pblock, const int nHeight) {}
virtual void EraseFromWallet(const uint256 &hash) {} virtual void EraseFromWallet(const uint256 &hash) {}
virtual void ChainTip(const CBlockIndex *pindex, const CBlock *pblock, boost::optional<std::pair<SproutMerkleTree, SaplingMerkleTree>> added) {} virtual void ChainTip(const CBlockIndex *pindex, const CBlock *pblock, boost::optional<std::pair<SproutMerkleTree, SaplingMerkleTree>> added) {}
virtual void SetBestChain(const CBlockLocator &locator) {} virtual void SetBestChain(const CBlockLocator &locator) {}
@ -52,7 +52,7 @@ struct CMainSignals {
/** Notifies listeners of updated block chain tip */ /** Notifies listeners of updated block chain tip */
boost::signals2::signal<void (const CBlockIndex *)> UpdatedBlockTip; boost::signals2::signal<void (const CBlockIndex *)> UpdatedBlockTip;
/** Notifies listeners of updated transaction data (transaction, and optionally the block it is found in. */ /** Notifies listeners of updated transaction data (transaction, and optionally the block it is found in. */
boost::signals2::signal<void (const CTransaction &, const CBlock *)> SyncTransaction; boost::signals2::signal<void (const CTransaction &, const CBlock *, const int nHeight)> SyncTransaction;
/** Notifies listeners of an erased transaction (currently disabled, requires transaction replacement). */ /** Notifies listeners of an erased transaction (currently disabled, requires transaction replacement). */
boost::signals2::signal<void (const uint256 &)> EraseTransaction; boost::signals2::signal<void (const uint256 &)> EraseTransaction;
/** Notifies listeners of an updated transaction without new data (for now: a coinbase potentially becoming visible). */ /** Notifies listeners of an updated transaction without new data (for now: a coinbase potentially becoming visible). */

View File

@ -376,58 +376,65 @@ TEST(WalletTests, SetSproutNoteAddrsInCWalletTx) {
} }
TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) { TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
auto consensusParams = RegtestActivateSapling(); SelectParams(CBaseChainParams::REGTEST);
TestWallet wallet; std::vector<libzcash::Zip212Enabled> zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212};
LOCK(wallet.cs_wallet); const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy};
void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy};
auto sk = GetTestMasterSaplingSpendingKey(); for (int ver = 0; ver < zip_212_enabled.size(); ver++) {
auto expsk = sk.expsk; auto consensusParams = (*activations[ver])();
auto fvk = expsk.full_viewing_key();
auto ivk = fvk.in_viewing_key();
auto pk = sk.DefaultAddress();
libzcash::SaplingNote note(pk, 50000); TestWallet wallet;
auto cm = note.cmu().get(); LOCK(wallet.cs_wallet);
SaplingMerkleTree tree;
tree.append(cm);
auto anchor = tree.root();
auto witness = tree.witness();
auto nf = note.nullifier(fvk, witness.position()); auto sk = GetTestMasterSaplingSpendingKey();
ASSERT_TRUE(nf); auto expsk = sk.expsk;
uint256 nullifier = nf.get(); auto fvk = expsk.full_viewing_key();
auto ivk = fvk.in_viewing_key();
auto pk = sk.DefaultAddress();
auto builder = TransactionBuilder(consensusParams, 1); libzcash::SaplingNote note(pk, 50000, zip_212_enabled[ver]);
builder.AddSaplingSpend(expsk, note, anchor, witness); auto cm = note.cmu().get();
builder.AddSaplingOutput(fvk.ovk, pk, 50000, {}); SaplingMerkleTree tree;
builder.SetFee(0); tree.append(cm);
auto tx = builder.Build().GetTxOrThrow(); auto anchor = tree.root();
auto witness = tree.witness();
CWalletTx wtx {&wallet, tx}; auto nf = note.nullifier(fvk, witness.position());
ASSERT_TRUE(nf);
uint256 nullifier = nf.get();
EXPECT_EQ(0, wtx.mapSaplingNoteData.size()); auto builder = TransactionBuilder(consensusParams, 1);
mapSaplingNoteData_t noteData; builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(fvk.ovk, pk, 50000, {});
builder.SetFee(0);
auto tx = builder.Build().GetTxOrThrow();
SaplingOutPoint op {wtx.GetHash(), 0}; CWalletTx wtx {&wallet, tx};
SaplingNoteData nd;
nd.nullifier = nullifier;
nd.ivk = ivk;
nd.witnesses.push_front(witness);
nd.witnessHeight = 123;
noteData.insert(std::make_pair(op, nd));
wtx.SetSaplingNoteData(noteData); EXPECT_EQ(0, wtx.mapSaplingNoteData.size());
EXPECT_EQ(noteData, wtx.mapSaplingNoteData); mapSaplingNoteData_t noteData;
// Test individual fields in case equality operator is defined/changed. SaplingOutPoint op {wtx.GetHash(), 0};
EXPECT_EQ(ivk, wtx.mapSaplingNoteData[op].ivk); SaplingNoteData nd;
EXPECT_EQ(nullifier, wtx.mapSaplingNoteData[op].nullifier); nd.nullifier = nullifier;
EXPECT_EQ(nd.witnessHeight, wtx.mapSaplingNoteData[op].witnessHeight); nd.ivk = ivk;
EXPECT_TRUE(witness == wtx.mapSaplingNoteData[op].witnesses.front()); nd.witnesses.push_front(witness);
nd.witnessHeight = 123;
noteData.insert(std::make_pair(op, nd));
// Revert to default wtx.SetSaplingNoteData(noteData);
RegtestDeactivateSapling(); EXPECT_EQ(noteData, wtx.mapSaplingNoteData);
// Test individual fields in case equality operator is defined/changed.
EXPECT_EQ(ivk, wtx.mapSaplingNoteData[op].ivk);
EXPECT_EQ(nullifier, wtx.mapSaplingNoteData[op].nullifier);
EXPECT_EQ(nd.witnessHeight, wtx.mapSaplingNoteData[op].witnessHeight);
EXPECT_TRUE(witness == wtx.mapSaplingNoteData[op].witnesses.front());
(*deactivations[ver])();
}
} }
TEST(WalletTests, SetSproutInvalidNoteAddrsInCWalletTx) { TEST(WalletTests, SetSproutInvalidNoteAddrsInCWalletTx) {
@ -534,13 +541,13 @@ TEST(WalletTests, FindMySaplingNotes) {
// No Sapling notes can be found in tx which does not belong to the wallet // No Sapling notes can be found in tx which does not belong to the wallet
CWalletTx wtx {&wallet, tx}; CWalletTx wtx {&wallet, tx};
ASSERT_FALSE(wallet.HaveSaplingSpendingKey(extfvk)); ASSERT_FALSE(wallet.HaveSaplingSpendingKey(extfvk));
auto noteMap = wallet.FindMySaplingNotes(wtx).first; auto noteMap = wallet.FindMySaplingNotes(wtx, 1).first;
EXPECT_EQ(0, noteMap.size()); EXPECT_EQ(0, noteMap.size());
// Add spending key to wallet, so Sapling notes can be found // Add spending key to wallet, so Sapling notes can be found
ASSERT_TRUE(wallet.AddSaplingZKey(sk)); ASSERT_TRUE(wallet.AddSaplingZKey(sk));
ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk)); ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk));
noteMap = wallet.FindMySaplingNotes(wtx).first; noteMap = wallet.FindMySaplingNotes(wtx, 1).first;
EXPECT_EQ(2, noteMap.size()); EXPECT_EQ(2, noteMap.size());
// Revert to default // Revert to default
@ -638,121 +645,130 @@ TEST(WalletTests, GetConflictedSproutNotes) {
// Generate note A and spend to create note B, from which we spend to create two conflicting transactions // Generate note A and spend to create note B, from which we spend to create two conflicting transactions
TEST(WalletTests, GetConflictedSaplingNotes) { TEST(WalletTests, GetConflictedSaplingNotes) {
auto consensusParams = RegtestActivateSapling(); SelectParams(CBaseChainParams::REGTEST);
TestWallet wallet; std::vector<libzcash::Zip212Enabled> zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212};
LOCK2(cs_main, wallet.cs_wallet); const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy};
void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy};
// Generate Sapling address for (int ver = 0; ver < zip_212_enabled.size(); ver++) {
auto sk = GetTestMasterSaplingSpendingKey(); auto consensusParams = (*activations[ver])();
auto expsk = sk.expsk;
auto extfvk = sk.ToXFVK();
auto ivk = extfvk.fvk.in_viewing_key();
auto pk = sk.DefaultAddress();
ASSERT_TRUE(wallet.AddSaplingZKey(sk)); TestWallet wallet;
ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk)); LOCK2(cs_main, wallet.cs_wallet);
// Generate note A // Generate Sapling address
libzcash::SaplingNote note(pk, 50000); auto sk = GetTestMasterSaplingSpendingKey();
auto cm = note.cmu().get(); auto expsk = sk.expsk;
SaplingMerkleTree saplingTree; auto extfvk = sk.ToXFVK();
saplingTree.append(cm); auto ivk = extfvk.fvk.in_viewing_key();
auto anchor = saplingTree.root(); auto pk = sk.DefaultAddress();
auto witness = saplingTree.witness();
// Generate tx to create output note B ASSERT_TRUE(wallet.AddSaplingZKey(sk));
auto builder = TransactionBuilder(consensusParams, 1); ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk));
builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 35000, {});
auto tx = builder.Build().GetTxOrThrow();
CWalletTx wtx {&wallet, tx};
// Fake-mine the transaction // Generate note A
EXPECT_EQ(-1, chainActive.Height()); libzcash::SaplingNote note(pk, 50000, zip_212_enabled[ver]);
SproutMerkleTree sproutTree; auto cm = note.cmu().get();
CBlock block; SaplingMerkleTree saplingTree;
block.vtx.push_back(wtx); saplingTree.append(cm);
block.hashMerkleRoot = block.BuildMerkleTree(); auto anchor = saplingTree.root();
auto blockHash = block.GetHash(); auto witness = saplingTree.witness();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
chainActive.SetTip(&fakeIndex);
EXPECT_TRUE(chainActive.Contains(&fakeIndex));
EXPECT_EQ(0, chainActive.Height());
// Simulate SyncTransaction which calls AddToWalletIfInvolvingMe // Generate tx to create output note B
auto saplingNoteData = wallet.FindMySaplingNotes(wtx).first; auto builder = TransactionBuilder(consensusParams, 1);
ASSERT_TRUE(saplingNoteData.size() > 0); builder.AddSaplingSpend(expsk, note, anchor, witness);
wtx.SetSaplingNoteData(saplingNoteData); builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 35000, {});
wtx.SetMerkleBranch(block); auto tx = builder.Build().GetTxOrThrow();
wallet.AddToWallet(wtx, true, NULL); CWalletTx wtx {&wallet, tx};
// Simulate receiving new block and ChainTip signal // Fake-mine the transaction
wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree); EXPECT_EQ(-1, chainActive.Height());
wallet.UpdateSaplingNullifierNoteMapForBlock(&block); SproutMerkleTree sproutTree;
CBlock block;
block.vtx.push_back(wtx);
block.hashMerkleRoot = block.BuildMerkleTree();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
chainActive.SetTip(&fakeIndex);
EXPECT_TRUE(chainActive.Contains(&fakeIndex));
EXPECT_EQ(0, chainActive.Height());
// Retrieve the updated wtx from wallet // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe
uint256 hash = wtx.GetHash(); auto saplingNoteData = wallet.FindMySaplingNotes(wtx, 1).first;
wtx = wallet.mapWallet[hash]; ASSERT_TRUE(saplingNoteData.size() > 0);
wtx.SetSaplingNoteData(saplingNoteData);
wtx.SetMerkleBranch(block);
wallet.AddToWallet(wtx, true, NULL);
// Decrypt output note B // Simulate receiving new block and ChainTip signal
auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt( wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree);
wtx.vShieldedOutput[0].encCiphertext, wallet.UpdateSaplingNullifierNoteMapForBlock(&block);
ivk,
wtx.vShieldedOutput[0].ephemeralKey,
wtx.vShieldedOutput[0].cmu);
ASSERT_EQ(static_cast<bool>(maybe_pt), true);
auto maybe_note = maybe_pt.get().note(ivk);
ASSERT_EQ(static_cast<bool>(maybe_note), true);
auto note2 = maybe_note.get();
SaplingOutPoint sop0(wtx.GetHash(), 0); // Retrieve the updated wtx from wallet
auto spend_note_witness = wtx.mapSaplingNoteData[sop0].witnesses.front(); uint256 hash = wtx.GetHash();
auto maybe_nf = note2.nullifier(extfvk.fvk, spend_note_witness.position()); wtx = wallet.mapWallet[hash];
ASSERT_EQ(static_cast<bool>(maybe_nf), true);
auto nullifier2 = maybe_nf.get();
anchor = saplingTree.root(); // Decrypt output note B
auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt(
consensusParams,
wtx.nExpiryHeight,
wtx.vShieldedOutput[0].encCiphertext,
ivk,
wtx.vShieldedOutput[0].ephemeralKey,
wtx.vShieldedOutput[0].cmu);
ASSERT_EQ(static_cast<bool>(maybe_pt), true);
auto maybe_note = maybe_pt.get().note(ivk);
ASSERT_EQ(static_cast<bool>(maybe_note), true);
auto note2 = maybe_note.get();
// Create transaction to spend note B SaplingOutPoint sop0(wtx.GetHash(), 0);
auto builder2 = TransactionBuilder(consensusParams, 2); auto spend_note_witness = wtx.mapSaplingNoteData[sop0].witnesses.front();
builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness); auto maybe_nf = note2.nullifier(extfvk.fvk, spend_note_witness.position());
builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 20000, {}); ASSERT_EQ(static_cast<bool>(maybe_nf), true);
auto tx2 = builder2.Build().GetTxOrThrow(); auto nullifier2 = maybe_nf.get();
// Create conflicting transaction which also spends note B anchor = saplingTree.root();
auto builder3 = TransactionBuilder(consensusParams, 2);
builder3.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
builder3.AddSaplingOutput(extfvk.fvk.ovk, pk, 19999, {});
auto tx3 = builder3.Build().GetTxOrThrow();
CWalletTx wtx2 {&wallet, tx2}; // Create transaction to spend note B
CWalletTx wtx3 {&wallet, tx3}; auto builder2 = TransactionBuilder(consensusParams, 2);
builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 20000, {});
auto tx2 = builder2.Build().GetTxOrThrow();
auto hash2 = wtx2.GetHash(); // Create conflicting transaction which also spends note B
auto hash3 = wtx3.GetHash(); auto builder3 = TransactionBuilder(consensusParams, 2);
builder3.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
builder3.AddSaplingOutput(extfvk.fvk.ovk, pk, 19999, {});
auto tx3 = builder3.Build().GetTxOrThrow();
// No conflicts for no spends (wtx is currently the only transaction in the wallet) CWalletTx wtx2 {&wallet, tx2};
EXPECT_EQ(0, wallet.GetConflicts(hash2).size()); CWalletTx wtx3 {&wallet, tx3};
EXPECT_EQ(0, wallet.GetConflicts(hash3).size());
// No conflicts for one spend auto hash2 = wtx2.GetHash();
wallet.AddToWallet(wtx2, true, NULL); auto hash3 = wtx3.GetHash();
EXPECT_EQ(0, wallet.GetConflicts(hash2).size());
// Conflicts for two spends // No conflicts for no spends (wtx is currently the only transaction in the wallet)
wallet.AddToWallet(wtx3, true, NULL); EXPECT_EQ(0, wallet.GetConflicts(hash2).size());
auto c3 = wallet.GetConflicts(hash2); EXPECT_EQ(0, wallet.GetConflicts(hash3).size());
EXPECT_EQ(2, c3.size());
EXPECT_EQ(std::set<uint256>({hash2, hash3}), c3);
// Tear down // No conflicts for one spend
chainActive.SetTip(NULL); wallet.AddToWallet(wtx2, true, NULL);
mapBlockIndex.erase(blockHash); EXPECT_EQ(0, wallet.GetConflicts(hash2).size());
// Revert to default // Conflicts for two spends
RegtestDeactivateSapling(); wallet.AddToWallet(wtx3, true, NULL);
auto c3 = wallet.GetConflicts(hash2);
EXPECT_EQ(2, c3.size());
EXPECT_EQ(std::set<uint256>({hash2, hash3}), c3);
// Tear down
chainActive.SetTip(NULL);
mapBlockIndex.erase(blockHash);
(*deactivations[ver])();
}
} }
TEST(WalletTests, SproutNullifierIsSpent) { TEST(WalletTests, SproutNullifierIsSpent) {
@ -928,7 +944,7 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
// Simulate SyncTransaction which calls AddToWalletIfInvolvingMe // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe
wtx.SetMerkleBranch(block); wtx.SetMerkleBranch(block);
auto saplingNoteData = wallet.FindMySaplingNotes(wtx).first; auto saplingNoteData = wallet.FindMySaplingNotes(wtx, chainActive.Height()).first;
ASSERT_TRUE(saplingNoteData.size() > 0); ASSERT_TRUE(saplingNoteData.size() > 0);
wtx.SetSaplingNoteData(saplingNoteData); wtx.SetSaplingNoteData(saplingNoteData);
wallet.AddToWallet(wtx, true, NULL); wallet.AddToWallet(wtx, true, NULL);
@ -1005,144 +1021,153 @@ TEST(WalletTests, SpentSproutNoteIsFromMe) {
// Create note A, spend A to create note B, spend and verify note B is from me. // Create note A, spend A to create note B, spend and verify note B is from me.
TEST(WalletTests, SpentSaplingNoteIsFromMe) { TEST(WalletTests, SpentSaplingNoteIsFromMe) {
auto consensusParams = RegtestActivateSapling(); SelectParams(CBaseChainParams::REGTEST);
TestWallet wallet; std::vector<libzcash::Zip212Enabled> zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212};
LOCK2(cs_main, wallet.cs_wallet); const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy};
void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy};
// Generate Sapling address for (int ver = 0; ver < zip_212_enabled.size(); ver++) {
auto sk = GetTestMasterSaplingSpendingKey(); auto consensusParams = (*activations[ver])();
auto expsk = sk.expsk;
auto extfvk = sk.ToXFVK();
auto ivk = extfvk.fvk.in_viewing_key();
auto pk = sk.DefaultAddress();
// Generate Sapling note A TestWallet wallet;
libzcash::SaplingNote note(pk, 50000); LOCK2(cs_main, wallet.cs_wallet);
auto cm = note.cmu().get();
SaplingMerkleTree saplingTree;
saplingTree.append(cm);
auto anchor = saplingTree.root();
auto witness = saplingTree.witness();
// Generate transaction, which sends funds to note B // Generate Sapling address
auto builder = TransactionBuilder(consensusParams, 1); auto sk = GetTestMasterSaplingSpendingKey();
builder.AddSaplingSpend(expsk, note, anchor, witness); auto expsk = sk.expsk;
builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 25000, {}); auto extfvk = sk.ToXFVK();
auto tx = builder.Build().GetTxOrThrow(); auto ivk = extfvk.fvk.in_viewing_key();
auto pk = sk.DefaultAddress();
CWalletTx wtx {&wallet, tx}; // Generate Sapling note A
ASSERT_TRUE(wallet.AddSaplingZKey(sk)); libzcash::SaplingNote note(pk, 50000, zip_212_enabled[ver]);
ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk)); auto cm = note.cmu().get();
SaplingMerkleTree saplingTree;
saplingTree.append(cm);
auto anchor = saplingTree.root();
auto witness = saplingTree.witness();
// Fake-mine the transaction // Generate transaction, which sends funds to note B
EXPECT_EQ(-1, chainActive.Height()); auto builder = TransactionBuilder(consensusParams, 1);
SproutMerkleTree sproutTree; builder.AddSaplingSpend(expsk, note, anchor, witness);
CBlock block; builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 25000, {});
block.vtx.push_back(wtx); auto tx = builder.Build().GetTxOrThrow();
block.hashMerkleRoot = block.BuildMerkleTree();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
chainActive.SetTip(&fakeIndex);
EXPECT_TRUE(chainActive.Contains(&fakeIndex));
EXPECT_EQ(0, chainActive.Height());
auto saplingNoteData = wallet.FindMySaplingNotes(wtx).first; CWalletTx wtx {&wallet, tx};
ASSERT_TRUE(saplingNoteData.size() > 0); ASSERT_TRUE(wallet.AddSaplingZKey(sk));
wtx.SetSaplingNoteData(saplingNoteData); ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk));
wtx.SetMerkleBranch(block);
wallet.AddToWallet(wtx, true, NULL);
// Simulate receiving new block and ChainTip signal. // Fake-mine the transaction
// This triggers calculation of nullifiers for notes belonging to this wallet EXPECT_EQ(-1, chainActive.Height());
// in the output descriptions of wtx. SproutMerkleTree sproutTree;
wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree); CBlock block;
wallet.UpdateSaplingNullifierNoteMapForBlock(&block); block.vtx.push_back(wtx);
block.hashMerkleRoot = block.BuildMerkleTree();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
chainActive.SetTip(&fakeIndex);
EXPECT_TRUE(chainActive.Contains(&fakeIndex));
EXPECT_EQ(0, chainActive.Height());
// Retrieve the updated wtx from wallet auto saplingNoteData = wallet.FindMySaplingNotes(wtx, 1).first;
wtx = wallet.mapWallet[wtx.GetHash()]; ASSERT_TRUE(saplingNoteData.size() > 0);
wtx.SetSaplingNoteData(saplingNoteData);
wtx.SetMerkleBranch(block);
wallet.AddToWallet(wtx, true, NULL);
// The test wallet never received the fake note which is being spent, so there // Simulate receiving new block and ChainTip signal.
// is no mapping from nullifier to notedata stored in mapSaplingNullifiersToNotes. // This triggers calculation of nullifiers for notes belonging to this wallet
// Therefore the wallet does not know the tx belongs to the wallet. // in the output descriptions of wtx.
EXPECT_FALSE(wallet.IsFromMe(wtx)); wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree);
wallet.UpdateSaplingNullifierNoteMapForBlock(&block);
// Manually compute the nullifier and check map entry does not exist // Retrieve the updated wtx from wallet
auto nf = note.nullifier(extfvk.fvk, witness.position()); wtx = wallet.mapWallet[wtx.GetHash()];
ASSERT_TRUE(nf);
ASSERT_FALSE(wallet.mapSaplingNullifiersToNotes.count(nf.get()));
// Decrypt note B // The test wallet never received the fake note which is being spent, so there
auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt( // is no mapping from nullifier to notedata stored in mapSaplingNullifiersToNotes.
wtx.vShieldedOutput[0].encCiphertext, // Therefore the wallet does not know the tx belongs to the wallet.
ivk, EXPECT_FALSE(wallet.IsFromMe(wtx));
wtx.vShieldedOutput[0].ephemeralKey,
wtx.vShieldedOutput[0].cmu);
ASSERT_EQ(static_cast<bool>(maybe_pt), true);
auto maybe_note = maybe_pt.get().note(ivk);
ASSERT_EQ(static_cast<bool>(maybe_note), true);
auto note2 = maybe_note.get();
// Get witness to retrieve position of note B we want to spend // Manually compute the nullifier and check map entry does not exist
SaplingOutPoint sop0(wtx.GetHash(), 0); auto nf = note.nullifier(extfvk.fvk, witness.position());
auto spend_note_witness = wtx.mapSaplingNoteData[sop0].witnesses.front(); ASSERT_TRUE(nf);
auto maybe_nf = note2.nullifier(extfvk.fvk, spend_note_witness.position()); ASSERT_FALSE(wallet.mapSaplingNullifiersToNotes.count(nf.get()));
ASSERT_EQ(static_cast<bool>(maybe_nf), true);
auto nullifier2 = maybe_nf.get();
// NOTE: Not updating the anchor results in a core dump. Shouldn't builder just return error? // Decrypt note B
// *** Error in `./zcash-gtest': double free or corruption (out): 0x00007ffd8755d990 *** auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt(
anchor = saplingTree.root(); consensusParams,
wtx.nExpiryHeight,
wtx.vShieldedOutput[0].encCiphertext,
ivk,
wtx.vShieldedOutput[0].ephemeralKey,
wtx.vShieldedOutput[0].cmu);
ASSERT_EQ(static_cast<bool>(maybe_pt), true);
auto maybe_note = maybe_pt.get().note(ivk);
ASSERT_EQ(static_cast<bool>(maybe_note), true);
auto note2 = maybe_note.get();
// Create transaction to spend note B // Get witness to retrieve position of note B we want to spend
auto builder2 = TransactionBuilder(consensusParams, 2); SaplingOutPoint sop0(wtx.GetHash(), 0);
builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness); auto spend_note_witness = wtx.mapSaplingNoteData[sop0].witnesses.front();
builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 12500, {}); auto maybe_nf = note2.nullifier(extfvk.fvk, spend_note_witness.position());
auto tx2 = builder2.Build().GetTxOrThrow(); ASSERT_EQ(static_cast<bool>(maybe_nf), true);
EXPECT_EQ(tx2.vin.size(), 0); auto nullifier2 = maybe_nf.get();
EXPECT_EQ(tx2.vout.size(), 0);
EXPECT_EQ(tx2.vJoinSplit.size(), 0);
EXPECT_EQ(tx2.vShieldedSpend.size(), 1);
EXPECT_EQ(tx2.vShieldedOutput.size(), 2);
EXPECT_EQ(tx2.valueBalance, 10000);
CWalletTx wtx2 {&wallet, tx2}; // NOTE: Not updating the anchor results in a core dump. Shouldn't builder just return error?
// *** Error in `./zcash-gtest': double free or corruption (out): 0x00007ffd8755d990 ***
anchor = saplingTree.root();
// Fake-mine this tx into the next block // Create transaction to spend note B
EXPECT_EQ(0, chainActive.Height()); auto builder2 = TransactionBuilder(consensusParams, 2);
CBlock block2; builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
block2.vtx.push_back(wtx2); builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 12500, {});
block2.hashMerkleRoot = block2.BuildMerkleTree(); auto tx2 = builder2.Build().GetTxOrThrow();
block2.hashPrevBlock = blockHash; EXPECT_EQ(tx2.vin.size(), 0);
auto blockHash2 = block2.GetHash(); EXPECT_EQ(tx2.vout.size(), 0);
CBlockIndex fakeIndex2 {block2}; EXPECT_EQ(tx2.vJoinSplit.size(), 0);
mapBlockIndex.insert(std::make_pair(blockHash2, &fakeIndex2)); EXPECT_EQ(tx2.vShieldedSpend.size(), 1);
fakeIndex2.nHeight = 1; EXPECT_EQ(tx2.vShieldedOutput.size(), 2);
chainActive.SetTip(&fakeIndex2); EXPECT_EQ(tx2.valueBalance, 10000);
EXPECT_TRUE(chainActive.Contains(&fakeIndex2));
EXPECT_EQ(1, chainActive.Height());
auto saplingNoteData2 = wallet.FindMySaplingNotes(wtx2).first; CWalletTx wtx2 {&wallet, tx2};
ASSERT_TRUE(saplingNoteData2.size() > 0);
wtx2.SetSaplingNoteData(saplingNoteData2);
wtx2.SetMerkleBranch(block2);
wallet.AddToWallet(wtx2, true, NULL);
// Verify note B is spent. AddToWallet invokes AddToSpends which updates mapTxSaplingNullifiers // Fake-mine this tx into the next block
EXPECT_TRUE(wallet.IsSaplingSpent(nullifier2)); EXPECT_EQ(0, chainActive.Height());
CBlock block2;
block2.vtx.push_back(wtx2);
block2.hashMerkleRoot = block2.BuildMerkleTree();
block2.hashPrevBlock = blockHash;
auto blockHash2 = block2.GetHash();
CBlockIndex fakeIndex2 {block2};
mapBlockIndex.insert(std::make_pair(blockHash2, &fakeIndex2));
fakeIndex2.nHeight = 1;
chainActive.SetTip(&fakeIndex2);
EXPECT_TRUE(chainActive.Contains(&fakeIndex2));
EXPECT_EQ(1, chainActive.Height());
// Verify note B belongs to wallet. auto saplingNoteData2 = wallet.FindMySaplingNotes(wtx2, 2).first;
EXPECT_TRUE(wallet.IsFromMe(wtx2)); ASSERT_TRUE(saplingNoteData2.size() > 0);
ASSERT_TRUE(wallet.mapSaplingNullifiersToNotes.count(nullifier2)); wtx2.SetSaplingNoteData(saplingNoteData2);
wtx2.SetMerkleBranch(block2);
wallet.AddToWallet(wtx2, true, NULL);
// Tear down // Verify note B is spent. AddToWallet invokes AddToSpends which updates mapTxSaplingNullifiers
chainActive.SetTip(NULL); EXPECT_TRUE(wallet.IsSaplingSpent(nullifier2));
mapBlockIndex.erase(blockHash);
mapBlockIndex.erase(blockHash2);
// Revert to default // Verify note B belongs to wallet.
RegtestDeactivateSapling(); EXPECT_TRUE(wallet.IsFromMe(wtx2));
ASSERT_TRUE(wallet.mapSaplingNullifiersToNotes.count(nullifier2));
// Tear down
chainActive.SetTip(NULL);
mapBlockIndex.erase(blockHash);
mapBlockIndex.erase(blockHash2);
(*deactivations[ver])();
}
} }
TEST(WalletTests, CachedWitnessesEmptyChain) { TEST(WalletTests, CachedWitnessesEmptyChain) {
@ -1843,7 +1868,7 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
EXPECT_EQ(0, chainActive.Height()); EXPECT_EQ(0, chainActive.Height());
// Simulate SyncTransaction which calls AddToWalletIfInvolvingMe // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe
auto saplingNoteData = wallet.FindMySaplingNotes(wtx).first; auto saplingNoteData = wallet.FindMySaplingNotes(wtx, chainActive.Height()).first;
ASSERT_TRUE(saplingNoteData.size() == 1); // wallet only has key for change output ASSERT_TRUE(saplingNoteData.size() == 1); // wallet only has key for change output
wtx.SetSaplingNoteData(saplingNoteData); wtx.SetSaplingNoteData(saplingNoteData);
wtx.SetMerkleBranch(block); wtx.SetMerkleBranch(block);
@ -1861,7 +1886,7 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
ASSERT_TRUE(wallet.AddSaplingZKey(sk2)); ASSERT_TRUE(wallet.AddSaplingZKey(sk2));
ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk2)); ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk2));
CWalletTx wtx2 = wtx; CWalletTx wtx2 = wtx;
auto saplingNoteData2 = wallet.FindMySaplingNotes(wtx2).first; auto saplingNoteData2 = wallet.FindMySaplingNotes(wtx2, chainActive.Height()).first;
ASSERT_TRUE(saplingNoteData2.size() == 2); ASSERT_TRUE(saplingNoteData2.size() == 2);
wtx2.SetSaplingNoteData(saplingNoteData2); wtx2.SetSaplingNoteData(saplingNoteData2);
@ -1990,7 +2015,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
EXPECT_EQ(0, chainActive.Height()); EXPECT_EQ(0, chainActive.Height());
// Simulate SyncTransaction which calls AddToWalletIfInvolvingMe // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe
auto saplingNoteData = wallet.FindMySaplingNotes(wtx).first; auto saplingNoteData = wallet.FindMySaplingNotes(wtx, chainActive.Height()).first;
ASSERT_TRUE(saplingNoteData.size() > 0); ASSERT_TRUE(saplingNoteData.size() > 0);
wtx.SetSaplingNoteData(saplingNoteData); wtx.SetSaplingNoteData(saplingNoteData);
wtx.SetMerkleBranch(block); wtx.SetMerkleBranch(block);
@ -2005,8 +2030,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
wtx = wallet.mapWallet[hash]; wtx = wallet.mapWallet[hash];
// Prepare to spend the note that was just created // Prepare to spend the note that was just created
auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt( auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt(consensusParams, fakeIndex.nHeight, tx1.vShieldedOutput[0].encCiphertext, ivk, tx1.vShieldedOutput[0].ephemeralKey, tx1.vShieldedOutput[0].cmu);
tx1.vShieldedOutput[0].encCiphertext, ivk, tx1.vShieldedOutput[0].ephemeralKey, tx1.vShieldedOutput[0].cmu);
ASSERT_EQ(static_cast<bool>(maybe_pt), true); ASSERT_EQ(static_cast<bool>(maybe_pt), true);
auto maybe_note = maybe_pt.get().note(ivk); auto maybe_note = maybe_pt.get().note(ivk);
ASSERT_EQ(static_cast<bool>(maybe_note), true); ASSERT_EQ(static_cast<bool>(maybe_note), true);

View File

@ -5,6 +5,7 @@
#include "amount.h" #include "amount.h"
#include "consensus/upgrades.h" #include "consensus/upgrades.h"
#include "consensus/params.h"
#include "core_io.h" #include "core_io.h"
#include "experimental_features.h" #include "experimental_features.h"
#include "init.h" #include "init.h"
@ -3775,7 +3776,9 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
auto op = res->second; auto op = res->second;
auto wtxPrev = pwalletMain->mapWallet.at(op.hash); auto wtxPrev = pwalletMain->mapWallet.at(op.hash);
auto decrypted = wtxPrev.DecryptSaplingNote(op).get(); // We don't need to check the leadbyte here: if wtx exists in
// the wallet, it must have already passed the leadbyte check
auto decrypted = wtxPrev.DecryptSaplingNoteWithoutLeadByteCheck(op).get();
auto notePt = decrypted.first; auto notePt = decrypted.first;
auto pa = decrypted.second; auto pa = decrypted.second;
@ -3803,14 +3806,16 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
SaplingPaymentAddress pa; SaplingPaymentAddress pa;
bool isOutgoing; bool isOutgoing;
auto decrypted = wtx.DecryptSaplingNote(op); // We don't need to check the leadbyte here: if wtx exists in
// the wallet, it must have already passed the leadbyte check
auto decrypted = wtx.DecryptSaplingNoteWithoutLeadByteCheck(op);
if (decrypted) { if (decrypted) {
notePt = decrypted->first; notePt = decrypted->first;
pa = decrypted->second; pa = decrypted->second;
isOutgoing = false; isOutgoing = false;
} else { } else {
// Try recovering the output // Try recovering the output
auto recovered = wtx.RecoverSaplingNote(op, ovks); auto recovered = wtx.RecoverSaplingNoteWithoutLeadByteCheck(op, ovks);
if (recovered) { if (recovered) {
notePt = recovered->first; notePt = recovered->first;
pa = recovered->second; pa = recovered->second;

View File

@ -1837,7 +1837,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_mergetoaddress_parameters)
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs = std::vector<MergeToAddressInputSproutNote> sproutNoteInputs =
{MergeToAddressInputSproutNote{JSOutPoint(), SproutNote(), 0, SproutSpendingKey()}}; {MergeToAddressInputSproutNote{JSOutPoint(), SproutNote(), 0, SproutSpendingKey()}};
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs = std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs =
{MergeToAddressInputSaplingNote{SaplingOutPoint(), SaplingNote(), 0, SaplingExpandedSpendingKey()}}; {MergeToAddressInputSaplingNote{SaplingOutPoint(), SaplingNote({}, uint256(), 0, uint256(), Zip212Enabled::BeforeZip212), 0, SaplingExpandedSpendingKey()}};
// Sprout and Sapling inputs -> throw // Sprout and Sapling inputs -> throw
try { try {

View File

@ -1495,21 +1495,25 @@ void CWallet::UpdateSaplingNullifierNoteMapWithTx(CWalletTx& wtx) {
uint64_t position = nd.witnesses.front().position(); uint64_t position = nd.witnesses.front().position();
auto extfvk = mapSaplingFullViewingKeys.at(nd.ivk); auto extfvk = mapSaplingFullViewingKeys.at(nd.ivk);
OutputDescription output = wtx.vShieldedOutput[op.n]; OutputDescription output = wtx.vShieldedOutput[op.n];
auto optPlaintext = SaplingNotePlaintext::decrypt(output.encCiphertext, nd.ivk, output.ephemeralKey, output.cmu);
if (!optPlaintext) { auto optDeserialized = SaplingNotePlaintext::attempt_sapling_enc_decryption_deserialization(output.encCiphertext, nd.ivk, output.ephemeralKey);
// An item in mapSaplingNoteData must have already been successfully decrypted,
// otherwise the item would not exist in the first place. // The transaction would not have entered the wallet unless
assert(false); // its plaintext had been successfully decrypted previously.
} assert(optDeserialized != boost::none);
auto optPlaintext = SaplingNotePlaintext::plaintext_checks_without_height(*optDeserialized, nd.ivk, output.ephemeralKey, output.cmu);
// An item in mapSaplingNoteData must have already been successfully decrypted,
// otherwise the item would not exist in the first place.
assert(optPlaintext != boost::none);
auto optNote = optPlaintext.get().note(nd.ivk); auto optNote = optPlaintext.get().note(nd.ivk);
if (!optNote) { assert(optNote != boost::none);
assert(false);
}
auto optNullifier = optNote.get().nullifier(extfvk.fvk, position); auto optNullifier = optNote.get().nullifier(extfvk.fvk, position);
if (!optNullifier) { // This should not happen. If it does, maybe the position has been corrupted or miscalculated?
// This should not happen. If it does, maybe the position has been corrupted or miscalculated? assert(optNullifier != boost::none);
assert(false);
}
uint256 nullifier = optNullifier.get(); uint256 nullifier = optNullifier.get();
mapSaplingNullifiersToNotes[nullifier] = op; mapSaplingNullifiersToNotes[nullifier] = op;
item.second.nullifier = nullifier; item.second.nullifier = nullifier;
@ -1707,14 +1711,14 @@ bool CWallet::UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx)
* updated; instead, the transaction being in the mempool or conflicted is determined on * updated; instead, the transaction being in the mempool or conflicted is determined on
* the fly in CMerkleTx::GetDepthInMainChain(). * the fly in CMerkleTx::GetDepthInMainChain().
*/ */
bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate) bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, const int nHeight, bool fUpdate)
{ {
{ {
AssertLockHeld(cs_wallet); AssertLockHeld(cs_wallet);
bool fExisted = mapWallet.count(tx.GetHash()) != 0; bool fExisted = mapWallet.count(tx.GetHash()) != 0;
if (fExisted && !fUpdate) return false; if (fExisted && !fUpdate) return false;
auto sproutNoteData = FindMySproutNotes(tx); auto sproutNoteData = FindMySproutNotes(tx);
auto saplingNoteDataAndAddressesToAdd = FindMySaplingNotes(tx); auto saplingNoteDataAndAddressesToAdd = FindMySaplingNotes(tx, nHeight);
auto saplingNoteData = saplingNoteDataAndAddressesToAdd.first; auto saplingNoteData = saplingNoteDataAndAddressesToAdd.first;
auto addressesToAdd = saplingNoteDataAndAddressesToAdd.second; auto addressesToAdd = saplingNoteDataAndAddressesToAdd.second;
for (const auto &addressToAdd : addressesToAdd) { for (const auto &addressToAdd : addressesToAdd) {
@ -1748,10 +1752,10 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
return false; return false;
} }
void CWallet::SyncTransaction(const CTransaction& tx, const CBlock* pblock) void CWallet::SyncTransaction(const CTransaction& tx, const CBlock* pblock, const int nHeight)
{ {
LOCK(cs_wallet); LOCK(cs_wallet);
if (!AddToWalletIfInvolvingMe(tx, pblock, true)) if (!AddToWalletIfInvolvingMe(tx, pblock, nHeight, true))
return; // Not one of ours return; // Not one of ours
MarkAffectedTransactionsDirty(tx); MarkAffectedTransactionsDirty(tx);
@ -1888,7 +1892,7 @@ mapSproutNoteData_t CWallet::FindMySproutNotes(const CTransaction &tx) const
* the result of FindMySaplingNotes (for the addresses available at the time) will * the result of FindMySaplingNotes (for the addresses available at the time) will
* already have been cached in CWalletTx.mapSaplingNoteData. * already have been cached in CWalletTx.mapSaplingNoteData.
*/ */
std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> CWallet::FindMySaplingNotes(const CTransaction &tx) const std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> CWallet::FindMySaplingNotes(const CTransaction &tx, int height) const
{ {
LOCK(cs_KeyStore); LOCK(cs_KeyStore);
uint256 hash = tx.GetHash(); uint256 hash = tx.GetHash();
@ -1901,7 +1905,8 @@ std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> CWallet::FindMySap
const OutputDescription output = tx.vShieldedOutput[i]; const OutputDescription output = tx.vShieldedOutput[i];
for (auto it = mapSaplingFullViewingKeys.begin(); it != mapSaplingFullViewingKeys.end(); ++it) { for (auto it = mapSaplingFullViewingKeys.begin(); it != mapSaplingFullViewingKeys.end(); ++it) {
SaplingIncomingViewingKey ivk = it->first; SaplingIncomingViewingKey ivk = it->first;
auto result = SaplingNotePlaintext::decrypt(output.encCiphertext, ivk, output.ephemeralKey, output.cmu);
auto result = SaplingNotePlaintext::decrypt(Params().GetConsensus(), height, output.encCiphertext, ivk, output.ephemeralKey, output.cmu);
if (!result) { if (!result) {
continue; continue;
} }
@ -2300,7 +2305,7 @@ std::pair<SproutNotePlaintext, SproutPaymentAddress> CWalletTx::DecryptSproutNot
boost::optional<std::pair< boost::optional<std::pair<
SaplingNotePlaintext, SaplingNotePlaintext,
SaplingPaymentAddress>> CWalletTx::DecryptSaplingNote(SaplingOutPoint op) const SaplingPaymentAddress>> CWalletTx::DecryptSaplingNote(const Consensus::Params& params, int height, SaplingOutPoint op) const
{ {
// Check whether we can decrypt this SaplingOutPoint // Check whether we can decrypt this SaplingOutPoint
if (this->mapSaplingNoteData.count(op) == 0) { if (this->mapSaplingNoteData.count(op) == 0) {
@ -2311,11 +2316,46 @@ boost::optional<std::pair<
auto nd = this->mapSaplingNoteData.at(op); auto nd = this->mapSaplingNoteData.at(op);
auto maybe_pt = SaplingNotePlaintext::decrypt( auto maybe_pt = SaplingNotePlaintext::decrypt(
params,
height,
output.encCiphertext, output.encCiphertext,
nd.ivk, nd.ivk,
output.ephemeralKey, output.ephemeralKey,
output.cmu); output.cmu);
assert(static_cast<bool>(maybe_pt)); assert(maybe_pt != boost::none);
auto notePt = maybe_pt.get();
auto maybe_pa = nd.ivk.address(notePt.d);
assert(maybe_pa != boost::none);
auto pa = maybe_pa.get();
return std::make_pair(notePt, pa);
}
boost::optional<std::pair<
SaplingNotePlaintext,
SaplingPaymentAddress>> CWalletTx::DecryptSaplingNoteWithoutLeadByteCheck(SaplingOutPoint op) const
{
// Check whether we can decrypt this SaplingOutPoint
if (this->mapSaplingNoteData.count(op) == 0) {
return boost::none;
}
auto output = this->vShieldedOutput[op.n];
auto nd = this->mapSaplingNoteData.at(op);
auto optDeserialized = SaplingNotePlaintext::attempt_sapling_enc_decryption_deserialization(output.encCiphertext, nd.ivk, output.ephemeralKey);
// The transaction would not have entered the wallet unless
// its plaintext had been successfully decrypted previously.
assert(optDeserialized != boost::none);
auto maybe_pt = SaplingNotePlaintext::plaintext_checks_without_height(
*optDeserialized,
nd.ivk,
output.ephemeralKey,
output.cmu);
assert(maybe_pt != boost::none);
auto notePt = maybe_pt.get(); auto notePt = maybe_pt.get();
auto maybe_pa = nd.ivk.address(notePt.d); auto maybe_pa = nd.ivk.address(notePt.d);
@ -2327,8 +2367,7 @@ boost::optional<std::pair<
boost::optional<std::pair< boost::optional<std::pair<
SaplingNotePlaintext, SaplingNotePlaintext,
SaplingPaymentAddress>> CWalletTx::RecoverSaplingNote( SaplingPaymentAddress>> CWalletTx::RecoverSaplingNote(const Consensus::Params& params, int height, SaplingOutPoint op, std::set<uint256>& ovks) const
SaplingOutPoint op, std::set<uint256>& ovks) const
{ {
auto output = this->vShieldedOutput[op.n]; auto output = this->vShieldedOutput[op.n];
@ -2340,10 +2379,13 @@ boost::optional<std::pair<
output.cmu, output.cmu,
output.ephemeralKey); output.ephemeralKey);
if (!outPt) { if (!outPt) {
// Try decrypting with the next ovk
continue; continue;
} }
auto maybe_pt = SaplingNotePlaintext::decrypt( auto maybe_pt = SaplingNotePlaintext::decrypt(
params,
height,
output.encCiphertext, output.encCiphertext,
output.ephemeralKey, output.ephemeralKey,
outPt->esk, outPt->esk,
@ -2359,6 +2401,46 @@ boost::optional<std::pair<
return boost::none; return boost::none;
} }
boost::optional<std::pair<
SaplingNotePlaintext,
SaplingPaymentAddress>> CWalletTx::RecoverSaplingNoteWithoutLeadByteCheck(SaplingOutPoint op, std::set<uint256>& ovks) const
{
auto output = this->vShieldedOutput[op.n];
for (auto ovk : ovks) {
auto outPt = SaplingOutgoingPlaintext::decrypt(
output.outCiphertext,
ovk,
output.cv,
output.cmu,
output.ephemeralKey);
if (!outPt) {
// Try decrypting with the next ovk
continue;
}
auto optDeserialized = SaplingNotePlaintext::attempt_sapling_enc_decryption_deserialization(output.encCiphertext, output.ephemeralKey, outPt->esk, outPt->pk_d);
// The transaction would not have entered the wallet unless
// its plaintext had been successfully decrypted previously.
assert(optDeserialized != boost::none);
auto maybe_pt = SaplingNotePlaintext::plaintext_checks_without_height(
*optDeserialized,
output.ephemeralKey,
outPt->esk,
outPt->pk_d,
output.cmu);
assert(static_cast<bool>(maybe_pt));
auto notePt = maybe_pt.get();
return std::make_pair(notePt, SaplingPaymentAddress(notePt.d, outPt->pk_d));
}
// Couldn't recover with any of the provided OutgoingViewingKeys
return boost::none;
}
int64_t CWalletTx::GetTxTime() const int64_t CWalletTx::GetTxTime() const
{ {
int64_t n = nTimeSmart; int64_t n = nTimeSmart;
@ -2654,7 +2736,7 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
ReadBlockFromDisk(block, pindex, Params().GetConsensus()); ReadBlockFromDisk(block, pindex, Params().GetConsensus());
BOOST_FOREACH(CTransaction& tx, block.vtx) BOOST_FOREACH(CTransaction& tx, block.vtx)
{ {
if (AddToWalletIfInvolvingMe(tx, &block, fUpdate)) { if (AddToWalletIfInvolvingMe(tx, &block, pindex->nHeight, fUpdate)) {
myTxHashes.push_back(tx.GetHash()); myTxHashes.push_back(tx.GetHash());
ret++; ret++;
} }
@ -4975,14 +5057,13 @@ void CWallet::GetFilteredNotes(
SaplingOutPoint op = pair.first; SaplingOutPoint op = pair.first;
SaplingNoteData nd = pair.second; SaplingNoteData nd = pair.second;
auto maybe_pt = SaplingNotePlaintext::decrypt( auto optDeserialized = SaplingNotePlaintext::attempt_sapling_enc_decryption_deserialization(wtx.vShieldedOutput[op.n].encCiphertext, nd.ivk, wtx.vShieldedOutput[op.n].ephemeralKey);
wtx.vShieldedOutput[op.n].encCiphertext,
nd.ivk,
wtx.vShieldedOutput[op.n].ephemeralKey,
wtx.vShieldedOutput[op.n].cmu);
assert(static_cast<bool>(maybe_pt));
auto notePt = maybe_pt.get();
// The transaction would not have entered the wallet unless
// its plaintext had been successfully decrypted previously.
assert(optDeserialized != boost::none);
auto notePt = optDeserialized.get();
auto maybe_pa = nd.ivk.address(notePt.d); auto maybe_pa = nd.ivk.address(notePt.d);
assert(static_cast<bool>(maybe_pa)); assert(static_cast<bool>(maybe_pa));
auto pa = maybe_pa.get(); auto pa = maybe_pa.get();

View File

@ -566,11 +566,17 @@ public:
JSOutPoint jsop) const; JSOutPoint jsop) const;
boost::optional<std::pair< boost::optional<std::pair<
libzcash::SaplingNotePlaintext, libzcash::SaplingNotePlaintext,
libzcash::SaplingPaymentAddress>> DecryptSaplingNote(SaplingOutPoint op) const; libzcash::SaplingPaymentAddress>> DecryptSaplingNote(const Consensus::Params& params, int height, SaplingOutPoint op) const;
boost::optional<std::pair< boost::optional<std::pair<
libzcash::SaplingNotePlaintext, libzcash::SaplingNotePlaintext,
libzcash::SaplingPaymentAddress>> RecoverSaplingNote( libzcash::SaplingPaymentAddress>> DecryptSaplingNoteWithoutLeadByteCheck(SaplingOutPoint op) const;
boost::optional<std::pair<
libzcash::SaplingNotePlaintext,
libzcash::SaplingPaymentAddress>> RecoverSaplingNote(const Consensus::Params& params, int height,
SaplingOutPoint op, std::set<uint256>& ovks) const; SaplingOutPoint op, std::set<uint256>& ovks) const;
boost::optional<std::pair<
libzcash::SaplingNotePlaintext,
libzcash::SaplingPaymentAddress>> RecoverSaplingNoteWithoutLeadByteCheck(SaplingOutPoint op, std::set<uint256>& ovks) const;
//! filter decides which addresses will count towards the debit //! filter decides which addresses will count towards the debit
CAmount GetDebit(const isminefilter& filter) const; CAmount GetDebit(const isminefilter& filter) const;
@ -1156,8 +1162,8 @@ public:
void UpdateSaplingNullifierNoteMapWithTx(CWalletTx& wtx); void UpdateSaplingNullifierNoteMapWithTx(CWalletTx& wtx);
void UpdateSaplingNullifierNoteMapForBlock(const CBlock* pblock); void UpdateSaplingNullifierNoteMapForBlock(const CBlock* pblock);
bool AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb); bool AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb);
void SyncTransaction(const CTransaction& tx, const CBlock* pblock); void SyncTransaction(const CTransaction& tx, const CBlock* pblock, const int nHeight);
bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate); bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, const int nHeight, bool fUpdate);
void EraseFromWallet(const uint256 &hash); void EraseFromWallet(const uint256 &hash);
void WitnessNoteCommitment( void WitnessNoteCommitment(
std::vector<uint256> commitments, std::vector<uint256> commitments,
@ -1221,7 +1227,7 @@ public:
const uint256& hSig, const uint256& hSig,
uint8_t n) const; uint8_t n) const;
mapSproutNoteData_t FindMySproutNotes(const CTransaction& tx) const; mapSproutNoteData_t FindMySproutNotes(const CTransaction& tx) const;
std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> FindMySaplingNotes(const CTransaction& tx) const; std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> FindMySaplingNotes(const CTransaction& tx, int height) const;
bool IsSproutNullifierFromMe(const uint256& nullifier) const; bool IsSproutNullifierFromMe(const uint256& nullifier) const;
bool IsSaplingNullifierFromMe(const uint256& nullifier) const; bool IsSaplingNullifierFromMe(const uint256& nullifier) const;

View File

@ -1,6 +1,7 @@
#include "Note.hpp" #include "Note.hpp"
#include "prf.h" #include "prf.h"
#include "crypto/sha256.h" #include "crypto/sha256.h"
#include "consensus/consensus.h"
#include "random.h" #include "random.h"
#include "version.h" #include "version.h"
@ -41,20 +42,31 @@ uint256 SproutNote::nullifier(const SproutSpendingKey& a_sk) const {
} }
// Construct and populate Sapling note for a given payment address and value. // Construct and populate Sapling note for a given payment address and value.
SaplingNote::SaplingNote(const SaplingPaymentAddress& address, const uint64_t value) : BaseNote(value) { SaplingNote::SaplingNote(
const SaplingPaymentAddress& address,
const uint64_t value,
Zip212Enabled zip212Enabled
) : BaseNote(value) {
d = address.d; d = address.d;
pk_d = address.pk_d; pk_d = address.pk_d;
librustzcash_sapling_generate_r(r.begin()); zip_212_enabled = zip212Enabled;
if (zip_212_enabled == Zip212Enabled::AfterZip212) {
// Per ZIP 212, the rseed field is 32 random bytes.
rseed = random_uint256();
} else {
librustzcash_sapling_generate_r(rseed.begin());
}
} }
// Call librustzcash to compute the commitment // Call librustzcash to compute the commitment
boost::optional<uint256> SaplingNote::cmu() const { boost::optional<uint256> SaplingNote::cmu() const {
uint256 result; uint256 result;
uint256 rcm_tmp = rcm();
if (!librustzcash_sapling_compute_cm( if (!librustzcash_sapling_compute_cm(
d.data(), d.data(),
pk_d.begin(), pk_d.begin(),
value(), value(),
r.begin(), rcm_tmp.begin(),
result.begin() result.begin()
)) ))
{ {
@ -71,11 +83,12 @@ boost::optional<uint256> SaplingNote::nullifier(const SaplingFullViewingKey& vk,
auto nk = vk.nk; auto nk = vk.nk;
uint256 result; uint256 result;
uint256 rcm_tmp = rcm();
if (!librustzcash_sapling_compute_nf( if (!librustzcash_sapling_compute_nf(
d.data(), d.data(),
pk_d.begin(), pk_d.begin(),
value(), value(),
r.begin(), rcm_tmp.begin(),
ak.begin(), ak.begin(),
nk.begin(), nk.begin(),
position, position,
@ -145,7 +158,12 @@ SaplingNotePlaintext::SaplingNotePlaintext(
std::array<unsigned char, ZC_MEMO_SIZE> memo) : BaseNotePlaintext(note, memo) std::array<unsigned char, ZC_MEMO_SIZE> memo) : BaseNotePlaintext(note, memo)
{ {
d = note.d; d = note.d;
rcm = note.r; rseed = note.rseed;
if (note.get_zip_212_enabled() == libzcash::Zip212Enabled::AfterZip212) {
leadbyte = 0x02;
} else {
leadbyte = 0x01;
}
} }
@ -153,7 +171,12 @@ boost::optional<SaplingNote> SaplingNotePlaintext::note(const SaplingIncomingVie
{ {
auto addr = ivk.address(d); auto addr = ivk.address(d);
if (addr) { if (addr) {
return SaplingNote(d, addr.get().pk_d, value_, rcm); Zip212Enabled zip_212_enabled = Zip212Enabled::BeforeZip212;
if (leadbyte != 0x01) {
zip_212_enabled = Zip212Enabled::AfterZip212;
};
auto tmp = SaplingNote(d, addr.get().pk_d, value_, rseed, zip_212_enabled);
return tmp;
} else { } else {
return boost::none; return boost::none;
} }
@ -176,12 +199,9 @@ boost::optional<SaplingOutgoingPlaintext> SaplingOutgoingPlaintext::decrypt(
try { try {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << pt.get(); ss << pt.get();
SaplingOutgoingPlaintext ret; SaplingOutgoingPlaintext ret;
ss >> ret; ss >> ret;
assert(ss.size() == 0); assert(ss.size() == 0);
return ret; return ret;
} catch (const boost::thread_interrupted&) { } catch (const boost::thread_interrupted&) {
throw; throw;
@ -191,14 +211,39 @@ boost::optional<SaplingOutgoingPlaintext> SaplingOutgoingPlaintext::decrypt(
} }
boost::optional<SaplingNotePlaintext> SaplingNotePlaintext::decrypt( boost::optional<SaplingNotePlaintext> SaplingNotePlaintext::decrypt(
const Consensus::Params& params,
int height,
const SaplingEncCiphertext &ciphertext, const SaplingEncCiphertext &ciphertext,
const uint256 &ivk, const uint256 &ivk,
const uint256 &epk, const uint256 &epk,
const uint256 &cmu const uint256 &cmu
) )
{ {
auto pt = AttemptSaplingEncDecryption(ciphertext, ivk, epk); auto ret = attempt_sapling_enc_decryption_deserialization(ciphertext, ivk, epk);
if (!pt) {
if (!ret) {
return boost::none;
} else {
const SaplingNotePlaintext plaintext = *ret;
// Check leadbyte is allowed at block height
if (!plaintext_version_is_valid(params, height, plaintext.get_leadbyte())) {
return boost::none;
}
return plaintext_checks_without_height(plaintext, ivk, epk, cmu);
}
}
boost::optional<SaplingNotePlaintext> SaplingNotePlaintext::attempt_sapling_enc_decryption_deserialization(
const SaplingEncCiphertext &ciphertext,
const uint256 &ivk,
const uint256 &epk
)
{
auto encPlaintext = AttemptSaplingEncDecryption(ciphertext, ivk, epk);
if (!encPlaintext) {
return boost::none; return boost::none;
} }
@ -206,26 +251,36 @@ boost::optional<SaplingNotePlaintext> SaplingNotePlaintext::decrypt(
SaplingNotePlaintext ret; SaplingNotePlaintext ret;
try { try {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << pt.get(); ss << encPlaintext.get();
ss >> ret; ss >> ret;
assert(ss.size() == 0); assert(ss.size() == 0);
return ret;
} catch (const boost::thread_interrupted&) { } catch (const boost::thread_interrupted&) {
throw; throw;
} catch (...) { } catch (...) {
return boost::none; return boost::none;
} }
}
boost::optional<SaplingNotePlaintext> SaplingNotePlaintext::plaintext_checks_without_height(
const SaplingNotePlaintext &plaintext,
const uint256 &ivk,
const uint256 &epk,
const uint256 &cmu
)
{
uint256 pk_d; uint256 pk_d;
if (!librustzcash_ivk_to_pkd(ivk.begin(), ret.d.data(), pk_d.begin())) { if (!librustzcash_ivk_to_pkd(ivk.begin(), plaintext.d.data(), pk_d.begin())) {
return boost::none; return boost::none;
} }
uint256 cmu_expected; uint256 cmu_expected;
uint256 rcm = plaintext.rcm();
if (!librustzcash_sapling_compute_cm( if (!librustzcash_sapling_compute_cm(
ret.d.data(), plaintext.d.data(),
pk_d.begin(), pk_d.begin(),
ret.value(), plaintext.value(),
ret.rcm.begin(), rcm.begin(),
cmu_expected.begin() cmu_expected.begin()
)) ))
{ {
@ -236,10 +291,25 @@ boost::optional<SaplingNotePlaintext> SaplingNotePlaintext::decrypt(
return boost::none; return boost::none;
} }
return ret; if (plaintext.get_leadbyte() != 0x01) {
// ZIP 212: Check that epk is consistent to guard against linkability
// attacks without relying on the soundness of the SNARK.
uint256 expected_epk;
uint256 esk = plaintext.generate_or_derive_esk();
if (!librustzcash_sapling_ka_derivepublic(plaintext.d.data(), esk.begin(), expected_epk.begin())) {
return boost::none;
}
if (expected_epk != epk) {
return boost::none;
}
}
return plaintext;
} }
boost::optional<SaplingNotePlaintext> SaplingNotePlaintext::decrypt( boost::optional<SaplingNotePlaintext> SaplingNotePlaintext::decrypt(
const Consensus::Params& params,
int height,
const SaplingEncCiphertext &ciphertext, const SaplingEncCiphertext &ciphertext,
const uint256 &epk, const uint256 &epk,
const uint256 &esk, const uint256 &esk,
@ -247,30 +317,74 @@ boost::optional<SaplingNotePlaintext> SaplingNotePlaintext::decrypt(
const uint256 &cmu const uint256 &cmu
) )
{ {
auto pt = AttemptSaplingEncDecryption(ciphertext, epk, esk, pk_d); auto ret = attempt_sapling_enc_decryption_deserialization(ciphertext, epk, esk, pk_d);
if (!pt) {
if (!ret) {
return boost::none; return boost::none;
} else {
SaplingNotePlaintext plaintext = *ret;
// Check leadbyte is allowed at block height
if (!plaintext_version_is_valid(params, height, plaintext.get_leadbyte())) {
return boost::none;
}
return plaintext_checks_without_height(plaintext, epk, esk, pk_d, cmu);
} }
}
boost::optional<SaplingNotePlaintext> SaplingNotePlaintext::attempt_sapling_enc_decryption_deserialization(
const SaplingEncCiphertext &ciphertext,
const uint256 &epk,
const uint256 &esk,
const uint256 &pk_d
)
{
auto encPlaintext = AttemptSaplingEncDecryption(ciphertext, epk, esk, pk_d);
if (!encPlaintext) {
return boost::none;
};
// Deserialize from the plaintext // Deserialize from the plaintext
SaplingNotePlaintext ret; SaplingNotePlaintext ret;
try { try {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << pt.get(); ss << encPlaintext.get();
ss >> ret; ss >> ret;
assert(ss.size() == 0); assert(ss.size() == 0);
return ret;
} catch (const boost::thread_interrupted&) { } catch (const boost::thread_interrupted&) {
throw; throw;
} catch (...) { } catch (...) {
return boost::none; return boost::none;
} }
}
boost::optional<SaplingNotePlaintext> SaplingNotePlaintext::plaintext_checks_without_height(
const SaplingNotePlaintext &plaintext,
const uint256 &epk,
const uint256 &esk,
const uint256 &pk_d,
const uint256 &cmu
)
{
// Check that epk is consistent with esk
uint256 expected_epk;
if (!librustzcash_sapling_ka_derivepublic(plaintext.d.data(), esk.begin(), expected_epk.begin())) {
return boost::none;
}
if (expected_epk != epk) {
return boost::none;
}
uint256 cmu_expected; uint256 cmu_expected;
uint256 rcm = plaintext.rcm();
if (!librustzcash_sapling_compute_cm( if (!librustzcash_sapling_compute_cm(
ret.d.data(), plaintext.d.data(),
pk_d.begin(), pk_d.begin(),
ret.value(), plaintext.value(),
ret.rcm.begin(), rcm.begin(),
cmu_expected.begin() cmu_expected.begin()
)) ))
{ {
@ -281,13 +395,21 @@ boost::optional<SaplingNotePlaintext> SaplingNotePlaintext::decrypt(
return boost::none; return boost::none;
} }
return ret; if (plaintext.get_leadbyte() != 0x01) {
// ZIP 212: Additionally check that the esk provided to this function
// is consistent with the esk we can derive
if (esk != plaintext.generate_or_derive_esk()) {
return boost::none;
}
}
return plaintext;
} }
boost::optional<SaplingNotePlaintextEncryptionResult> SaplingNotePlaintext::encrypt(const uint256& pk_d) const boost::optional<SaplingNotePlaintextEncryptionResult> SaplingNotePlaintext::encrypt(const uint256& pk_d) const
{ {
// Get the encryptor // Get the encryptor
auto sne = SaplingNoteEncryption::FromDiversifier(d); auto sne = SaplingNoteEncryption::FromDiversifier(d, generate_or_derive_esk());
if (!sne) { if (!sne) {
return boost::none; return boost::none;
} }
@ -325,3 +447,30 @@ SaplingOutCiphertext SaplingOutgoingPlaintext::encrypt(
return enc.encrypt_to_ourselves(ovk, cv, cm, pt); return enc.encrypt_to_ourselves(ovk, cv, cm, pt);
} }
uint256 SaplingNotePlaintext::rcm() const {
if (leadbyte != 0x01) {
return PRF_rcm(rseed);
} else {
return rseed;
}
}
uint256 SaplingNote::rcm() const {
if (SaplingNote::get_zip_212_enabled() == libzcash::Zip212Enabled::AfterZip212) {
return PRF_rcm(rseed);
} else {
return rseed;
}
}
uint256 SaplingNotePlaintext::generate_or_derive_esk() const {
if (leadbyte != 0x01) {
return PRF_esk(rseed);
} else {
uint256 esk;
// Pick random esk
librustzcash_sapling_generate_r(esk.begin());
return esk;
}
}

View File

@ -5,6 +5,8 @@
#include "Zcash.h" #include "Zcash.h"
#include "Address.hpp" #include "Address.hpp"
#include "NoteEncryption.hpp" #include "NoteEncryption.hpp"
#include "consensus/params.h"
#include "consensus/consensus.h"
#include <array> #include <array>
#include <boost/optional.hpp> #include <boost/optional.hpp>
@ -40,24 +42,56 @@ public:
uint256 nullifier(const SproutSpendingKey& a_sk) const; uint256 nullifier(const SproutSpendingKey& a_sk) const;
}; };
inline bool plaintext_version_is_valid(const Consensus::Params& params, int height, unsigned char leadbyte) {
int canopyActivationHeight = params.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight;
if (height < canopyActivationHeight && leadbyte != 0x01) {
// non-0x01 received before Canopy activation height
return false;
}
if (height >= canopyActivationHeight
&& height < canopyActivationHeight + ZIP212_GRACE_PERIOD
&& leadbyte != 0x01
&& leadbyte != 0x02)
{
// non-{0x01,0x02} received after Canopy activation and before grace period has elapsed
return false;
}
if (height >= canopyActivationHeight + ZIP212_GRACE_PERIOD && leadbyte != 0x02) {
// non-0x02 received past (Canopy activation height + grace period)
return false;
}
return true;
};
enum class Zip212Enabled {
BeforeZip212,
AfterZip212
};
class SaplingNote : public BaseNote { class SaplingNote : public BaseNote {
private:
uint256 rseed;
friend class SaplingNotePlaintext;
Zip212Enabled zip_212_enabled;
public: public:
diversifier_t d; diversifier_t d;
uint256 pk_d; uint256 pk_d;
uint256 r;
SaplingNote(diversifier_t d, uint256 pk_d, uint64_t value, uint256 r) SaplingNote(diversifier_t d, uint256 pk_d, uint64_t value, uint256 rseed, Zip212Enabled zip_212_enabled)
: BaseNote(value), d(d), pk_d(pk_d), r(r) {} : BaseNote(value), d(d), pk_d(pk_d), rseed(rseed), zip_212_enabled(zip_212_enabled) {}
SaplingNote() {}; SaplingNote(const SaplingPaymentAddress &address, uint64_t value, Zip212Enabled zip_212_enabled);
SaplingNote(const SaplingPaymentAddress &address, uint64_t value);
virtual ~SaplingNote() {}; virtual ~SaplingNote() {};
boost::optional<uint256> cmu() const; boost::optional<uint256> cmu() const;
boost::optional<uint256> nullifier(const SaplingFullViewingKey &vk, const uint64_t position) const; boost::optional<uint256> nullifier(const SaplingFullViewingKey &vk, const uint64_t position) const;
uint256 rcm() const;
Zip212Enabled get_zip_212_enabled() const {
return zip_212_enabled;
}
}; };
class BaseNotePlaintext { class BaseNotePlaintext {
@ -91,10 +125,10 @@ public:
template <typename Stream, typename Operation> template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) { inline void SerializationOp(Stream& s, Operation ser_action) {
unsigned char leadingByte = 0x00; unsigned char leadbyte = 0x00;
READWRITE(leadingByte); READWRITE(leadbyte);
if (leadingByte != 0x00) { if (leadbyte != 0x00) {
throw std::ios_base::failure("lead byte of SproutNotePlaintext is not recognized"); throw std::ios_base::failure("lead byte of SproutNotePlaintext is not recognized");
} }
@ -119,22 +153,41 @@ public:
typedef std::pair<SaplingEncCiphertext, SaplingNoteEncryption> SaplingNotePlaintextEncryptionResult; typedef std::pair<SaplingEncCiphertext, SaplingNoteEncryption> SaplingNotePlaintextEncryptionResult;
class SaplingNotePlaintext : public BaseNotePlaintext { class SaplingNotePlaintext : public BaseNotePlaintext {
private:
uint256 rseed;
unsigned char leadbyte;
public: public:
diversifier_t d; diversifier_t d;
uint256 rcm;
SaplingNotePlaintext() {} SaplingNotePlaintext() {}
SaplingNotePlaintext(const SaplingNote& note, std::array<unsigned char, ZC_MEMO_SIZE> memo); SaplingNotePlaintext(const SaplingNote& note, std::array<unsigned char, ZC_MEMO_SIZE> memo);
static boost::optional<SaplingNotePlaintext> decrypt( static boost::optional<SaplingNotePlaintext> decrypt(
const Consensus::Params& params,
int height,
const SaplingEncCiphertext &ciphertext, const SaplingEncCiphertext &ciphertext,
const uint256 &ivk, const uint256 &ivk,
const uint256 &epk, const uint256 &epk,
const uint256 &cmu const uint256 &cmu
); );
static boost::optional<SaplingNotePlaintext> plaintext_checks_without_height(
const SaplingNotePlaintext &plaintext,
const uint256 &ivk,
const uint256 &epk,
const uint256 &cmu
);
static boost::optional<SaplingNotePlaintext> attempt_sapling_enc_decryption_deserialization(
const SaplingEncCiphertext &ciphertext,
const uint256 &ivk,
const uint256 &epk
);
static boost::optional<SaplingNotePlaintext> decrypt( static boost::optional<SaplingNotePlaintext> decrypt(
const Consensus::Params& params,
int height,
const SaplingEncCiphertext &ciphertext, const SaplingEncCiphertext &ciphertext,
const uint256 &epk, const uint256 &epk,
const uint256 &esk, const uint256 &esk,
@ -142,6 +195,21 @@ public:
const uint256 &cmu const uint256 &cmu
); );
static boost::optional<SaplingNotePlaintext> plaintext_checks_without_height(
const SaplingNotePlaintext &plaintext,
const uint256 &epk,
const uint256 &esk,
const uint256 &pk_d,
const uint256 &cmu
);
static boost::optional<SaplingNotePlaintext> attempt_sapling_enc_decryption_deserialization(
const SaplingEncCiphertext &ciphertext,
const uint256 &epk,
const uint256 &esk,
const uint256 &pk_d
);
boost::optional<SaplingNote> note(const SaplingIncomingViewingKey& ivk) const; boost::optional<SaplingNote> note(const SaplingIncomingViewingKey& ivk) const;
virtual ~SaplingNotePlaintext() {} virtual ~SaplingNotePlaintext() {}
@ -150,20 +218,25 @@ public:
template <typename Stream, typename Operation> template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) { inline void SerializationOp(Stream& s, Operation ser_action) {
unsigned char leadingByte = 0x01; READWRITE(leadbyte);
READWRITE(leadingByte);
if (leadingByte != 0x01) { if (leadbyte != 0x01 && leadbyte != 0x02) {
throw std::ios_base::failure("lead byte of SaplingNotePlaintext is not recognized"); throw std::ios_base::failure("lead byte of SaplingNotePlaintext is not recognized");
} }
READWRITE(d); // 11 bytes READWRITE(d); // 11 bytes
READWRITE(value_); // 8 bytes READWRITE(value_); // 8 bytes
READWRITE(rcm); // 32 bytes READWRITE(rseed); // 32 bytes
READWRITE(memo_); // 512 bytes READWRITE(memo_); // 512 bytes
} }
boost::optional<SaplingNotePlaintextEncryptionResult> encrypt(const uint256& pk_d) const; boost::optional<SaplingNotePlaintextEncryptionResult> encrypt(const uint256& pk_d) const;
uint256 rcm() const;
uint256 generate_or_derive_esk() const;
unsigned char get_leadbyte() const {
return leadbyte;
}
}; };
class SaplingOutgoingPlaintext class SaplingOutgoingPlaintext

View File

@ -101,12 +101,12 @@ void KDF(unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE],
namespace libzcash { namespace libzcash {
boost::optional<SaplingNoteEncryption> SaplingNoteEncryption::FromDiversifier(diversifier_t d) { boost::optional<SaplingNoteEncryption> SaplingNoteEncryption::FromDiversifier(
diversifier_t d,
uint256 esk
)
{
uint256 epk; uint256 epk;
uint256 esk;
// Pick random esk
librustzcash_sapling_generate_r(esk.begin());
// Compute epk given the diversifier // Compute epk given the diversifier
if (!librustzcash_sapling_ka_derivepublic(d.begin(), esk.begin(), epk.begin())) { if (!librustzcash_sapling_ka_derivepublic(d.begin(), esk.begin(), epk.begin())) {

View File

@ -42,7 +42,7 @@ protected:
public: public:
static boost::optional<SaplingNoteEncryption> FromDiversifier(diversifier_t d); static boost::optional<SaplingNoteEncryption> FromDiversifier(diversifier_t d, uint256 esk);
boost::optional<SaplingEncCiphertext> encrypt_to_recipient( boost::optional<SaplingEncCiphertext> encrypt_to_recipient(
const uint256 &pk_d, const uint256 &pk_d,

View File

@ -24,6 +24,22 @@ std::array<unsigned char, 64> PRF_expand(const uint256& sk, unsigned char t)
return res; return res;
} }
uint256 PRF_rcm(const uint256& rseed)
{
uint256 rcm;
auto tmp = PRF_expand(rseed, 4);
librustzcash_to_scalar(tmp.data(), rcm.begin());
return rcm;
}
uint256 PRF_esk(const uint256& rseed)
{
uint256 esk;
auto tmp = PRF_expand(rseed, 5);
librustzcash_to_scalar(tmp.data(), esk.begin());
return esk;
}
uint256 PRF_ask(const uint256& sk) uint256 PRF_ask(const uint256& sk)
{ {
uint256 ask; uint256 ask;

View File

@ -22,6 +22,8 @@ uint256 PRF_rho(const uint252& phi, size_t i0, const uint256& h_sig);
uint256 PRF_ask(const uint256& sk); uint256 PRF_ask(const uint256& sk);
uint256 PRF_nsk(const uint256& sk); uint256 PRF_nsk(const uint256& sk);
uint256 PRF_ovk(const uint256& sk); uint256 PRF_ovk(const uint256& sk);
uint256 PRF_rcm(const uint256& rseed);
uint256 PRF_esk(const uint256& rseed);
std::array<unsigned char, 11> default_diversifier(const uint256& sk); std::array<unsigned char, 11> default_diversifier(const uint256& sk);

View File

@ -307,7 +307,7 @@ double benchmark_try_decrypt_sapling_notes(size_t nKeys)
struct timeval tv_start; struct timeval tv_start;
timer_start(tv_start); timer_start(tv_start);
auto noteDataMapAndAddressesToAdd = wallet.FindMySaplingNotes(tx); auto noteDataMapAndAddressesToAdd = wallet.FindMySaplingNotes(tx, 1);
assert(noteDataMapAndAddressesToAdd.first.empty()); assert(noteDataMapAndAddressesToAdd.first.empty());
return timer_stop(tv_start); return timer_stop(tv_start);
} }
@ -594,7 +594,7 @@ double benchmark_create_sapling_spend()
auto sk = libzcash::SaplingSpendingKey::random(); auto sk = libzcash::SaplingSpendingKey::random();
auto expsk = sk.expanded_spending_key(); auto expsk = sk.expanded_spending_key();
auto address = sk.default_address(); auto address = sk.default_address();
SaplingNote note(address, GetRand(MAX_MONEY)); SaplingNote note(address, GetRand(MAX_MONEY), libzcash::Zip212Enabled::BeforeZip212);
SaplingMerkleTree tree; SaplingMerkleTree tree;
auto maybe_cmu = note.cmu(); auto maybe_cmu = note.cmu();
tree.append(maybe_cmu.get()); tree.append(maybe_cmu.get());
@ -618,12 +618,13 @@ double benchmark_create_sapling_spend()
timer_start(tv_start); timer_start(tv_start);
SpendDescription sdesc; SpendDescription sdesc;
uint256 rcm = note.rcm();
bool result = librustzcash_sapling_spend_proof( bool result = librustzcash_sapling_spend_proof(
ctx, ctx,
expsk.full_viewing_key().ak.begin(), expsk.full_viewing_key().ak.begin(),
expsk.nsk.begin(), expsk.nsk.begin(),
note.d.data(), note.d.data(),
note.r.begin(), rcm.begin(),
alpha.begin(), alpha.begin(),
note.value(), note.value(),
anchor.begin(), anchor.begin(),
@ -646,7 +647,7 @@ double benchmark_create_sapling_output()
auto address = sk.default_address(); auto address = sk.default_address();
std::array<unsigned char, ZC_MEMO_SIZE> memo; std::array<unsigned char, ZC_MEMO_SIZE> memo;
SaplingNote note(address, GetRand(MAX_MONEY)); SaplingNote note(address, GetRand(MAX_MONEY), libzcash::Zip212Enabled::BeforeZip212);
libzcash::SaplingNotePlaintext notePlaintext(note, memo); libzcash::SaplingNotePlaintext notePlaintext(note, memo);
auto res = notePlaintext.encrypt(note.pk_d); auto res = notePlaintext.encrypt(note.pk_d);
@ -667,11 +668,12 @@ double benchmark_create_sapling_output()
timer_start(tv_start); timer_start(tv_start);
OutputDescription odesc; OutputDescription odesc;
uint256 rcm = note.rcm();
bool result = librustzcash_sapling_output_proof( bool result = librustzcash_sapling_output_proof(
ctx, ctx,
encryptor.get_esk().begin(), encryptor.get_esk().begin(),
addressBytes.data(), addressBytes.data(),
note.r.begin(), rcm.begin(),
note.value(), note.value(),
odesc.cv.begin(), odesc.cv.begin(),
odesc.zkproof.begin()); odesc.zkproof.begin());

View File

@ -163,7 +163,7 @@ void CZMQNotificationInterface::BlockChecked(const CBlock& block, const CValidat
} }
} }
void CZMQNotificationInterface::SyncTransaction(const CTransaction &tx, const CBlock *pblock) void CZMQNotificationInterface::SyncTransaction(const CTransaction &tx, const CBlock *pblock, const int nHeight)
{ {
for (std::list<CZMQAbstractNotifier*>::iterator i = notifiers.begin(); i!=notifiers.end(); ) for (std::list<CZMQAbstractNotifier*>::iterator i = notifiers.begin(); i!=notifiers.end(); )
{ {

View File

@ -25,7 +25,7 @@ protected:
void Shutdown(); void Shutdown();
// CValidationInterface // CValidationInterface
void SyncTransaction(const CTransaction &tx, const CBlock *pblock); void SyncTransaction(const CTransaction &tx, const CBlock *pblock, const int nHeight);
void UpdatedBlockTip(const CBlockIndex *pindex); void UpdatedBlockTip(const CBlockIndex *pindex);
void BlockChecked(const CBlock& block, const CValidationState& state); void BlockChecked(const CBlock& block, const CValidationState& state);