diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index c7773d0b5..05885d122 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -223,7 +223,7 @@ def initialize_chain(test_dir): print("initialize_chain: bitcoind started, waiting for RPC to come up") wait_for_bitcoind_start(bitcoind_processes[i], rpc_url(i), i) if os.getenv("PYTHON_DEBUG", ""): - print("initialize_chain: RPC succesfully started") + print("initialize_chain: RPC successfully started") rpcs = [] 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) wait_for_bitcoind_start(bitcoind_processes[i], url, i) 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) if COVERAGE_DIR: diff --git a/src/amqp/amqpnotificationinterface.cpp b/src/amqp/amqpnotificationinterface.cpp index 0dfae00b9..27f98f690 100644 --- a/src/amqp/amqpnotificationinterface.cpp +++ b/src/amqp/amqpnotificationinterface.cpp @@ -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::iterator i = notifiers.begin(); i != notifiers.end(); ) { AMQPAbstractNotifier *notifier = *i; diff --git a/src/amqp/amqpnotificationinterface.h b/src/amqp/amqpnotificationinterface.h index 77495a85c..ef0ac7f34 100644 --- a/src/amqp/amqpnotificationinterface.h +++ b/src/amqp/amqpnotificationinterface.h @@ -24,7 +24,7 @@ protected: void Shutdown(); // 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); private: diff --git a/src/consensus/consensus.h b/src/consensus/consensus.h index 847e5d53a..3452cd4b4 100644 --- a/src/consensus/consensus.h +++ b/src/consensus/consensus.h @@ -31,6 +31,8 @@ static const unsigned int MAX_TX_SIZE_AFTER_SAPLING = MAX_BLOCK_SIZE; static const int COINBASE_MATURITY = 100; /** The minimum value which is invalid for expiry height, used by CTransaction and CMutableTransaction */ 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() */ enum { diff --git a/src/gtest/test_checktransaction.cpp b/src/gtest/test_checktransaction.cpp index 0e0e639ff..4472a8efd 100644 --- a/src/gtest/test_checktransaction.cpp +++ b/src/gtest/test_checktransaction.cpp @@ -1134,7 +1134,7 @@ TEST(ChecktransactionTests, HeartwoodAcceptsShieldedCoinbase) { uint256 ovk; 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 ctx = librustzcash_sapling_proving_ctx_init(); @@ -1217,7 +1217,7 @@ TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) { uint256 ovk; 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}}); CMutableTransaction mtx = GetValidTransaction(); diff --git a/src/gtest/test_noteencryption.cpp b/src/gtest/test_noteencryption.cpp index d66580ac9..a9b19f184 100644 --- a/src/gtest/test_noteencryption.cpp +++ b/src/gtest/test_noteencryption.cpp @@ -10,6 +10,8 @@ #include "zcash/Address.hpp" #include "crypto/sha256.h" #include "librustzcash.h" +#include "consensus/params.h" +#include "utiltest.h" class TestNoteDecryption : public ZCNoteDecryption { public: @@ -20,8 +22,14 @@ public: } }; -TEST(noteencryption, NotePlaintext) +TEST(NoteEncryption, NotePlaintext) { + SelectParams(CBaseChainParams::REGTEST); + + std::vector zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212}; + const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy}; + void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy}; + using namespace libzcash; auto xsk = SaplingSpendingKey(uint256()).expanded_spending_key(); auto fvk = xsk.full_viewing_key(); @@ -34,131 +42,379 @@ TEST(noteencryption, NotePlaintext) memo[i] = (unsigned char) i; } - SaplingNote note(addr, 39393); - auto cmu_opt = note.cmu(); - if (!cmu_opt) { - FAIL(); - } - uint256 cmu = cmu_opt.get(); - SaplingNotePlaintext pt(note, memo); + for (int ver = 0; ver < zip_212_enabled.size(); ver++){ + auto params = (*activations[ver])(); - auto res = pt.encrypt(addr.pk_d); - if (!res) { - FAIL(); - } + SaplingNote note(addr, 39393, zip_212_enabled[ver]); + auto cmu_opt = note.cmu(); + 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 encryptor = enc.second; - auto epk = encryptor.get_epk(); + auto enc = res.get(); - // Try to decrypt with incorrect commitment - ASSERT_FALSE(SaplingNotePlaintext::decrypt( - ct, - ivk, - epk, - uint256() - )); + auto ct = enc.first; + auto encryptor = enc.second; + auto epk = encryptor.get_epk(); - // Try to decrypt with correct commitment - auto foo = SaplingNotePlaintext::decrypt( - ct, - ivk, - epk, - cmu - ); + // Try to decrypt with incorrect commitment + ASSERT_FALSE(SaplingNotePlaintext::decrypt( + params, + 1, + ct, + ivk, + epk, + uint256() + )); - if (!foo) { - FAIL(); - } + // Try to decrypt with correct commitment + auto foo = SaplingNotePlaintext::decrypt( + params, + 1, + ct, + ivk, + epk, + cmu + ); - auto bar = foo.get(); + if (!foo) { + 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); + auto bar = foo.get(); - 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) { - FAIL(); - } + auto foobar = bar.note(ivk); - auto new_note = foobar.get(); + if (!foobar) { + FAIL(); + } - ASSERT_TRUE(note.value() == new_note.value()); - 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()); + auto new_note = foobar.get(); - SaplingOutgoingPlaintext out_pt; - out_pt.pk_d = note.pk_d; - out_pt.esk = encryptor.get_esk(); + ASSERT_TRUE(note.value() == new_note.value()); + ASSERT_TRUE(note.d == new_note.d); + 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(); - auto cv = random_uint256(); - auto cm = random_uint256(); + SaplingOutgoingPlaintext out_pt; + out_pt.pk_d = note.pk_d; + out_pt.esk = encryptor.get_esk(); - auto out_ct = out_pt.encrypt( - ovk, - cv, - cm, - encryptor - ); + auto ovk = random_uint256(); + auto cv = random_uint256(); + auto cm = random_uint256(); - auto decrypted_out_ct = out_pt.decrypt( - out_ct, - ovk, - cv, - cm, - encryptor.get_epk() - ); + auto out_ct = out_pt.encrypt( + ovk, + cv, + cm, + encryptor + ); - if (!decrypted_out_ct) { - FAIL(); - } + auto decrypted_out_ct = out_pt.decrypt( + 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); - ASSERT_TRUE(decrypted_out_ct_unwrapped.esk == out_pt.esk); + auto decrypted_out_ct_unwrapped = decrypted_out_ct.get(); - // Test sender won't accept invalid commitments - ASSERT_FALSE( - SaplingNotePlaintext::decrypt( + ASSERT_TRUE(decrypted_out_ct_unwrapped.pk_d == out_pt.pk_d); + ASSERT_TRUE(decrypted_out_ct_unwrapped.esk == out_pt.esk); + + // 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, epk, decrypted_out_ct_unwrapped.esk, decrypted_out_ct_unwrapped.pk_d, - uint256() - ) - ); + cmu + ); - // Test sender can decrypt the note ciphertext. - foo = SaplingNotePlaintext::decrypt( - ct, - epk, - decrypted_out_ct_unwrapped.esk, - decrypted_out_ct_unwrapped.pk_d, - cmu - ); + if (!foo) { + FAIL(); + } - if (!foo) { - FAIL(); + 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()); + + (*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 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 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 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; @@ -182,11 +438,14 @@ TEST(noteencryption, SaplingApi) small_message[i] = (unsigned char) i; } + uint256 esk; + librustzcash_sapling_generate_r(esk.begin()); + // 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 - auto enc = *SaplingNoteEncryption::FromDiversifier(pk_1.d); + auto enc = *SaplingNoteEncryption::FromDiversifier(pk_1.d, esk); auto ciphertext_1 = *enc.encrypt_to_recipient( pk_1.pk_d, message @@ -208,7 +467,7 @@ TEST(noteencryption, SaplingApi) ); // Encrypt to pk_2 - enc = *SaplingNoteEncryption::FromDiversifier(pk_2.d); + enc = *SaplingNoteEncryption::FromDiversifier(pk_2.d, esk); auto ciphertext_2 = *enc.encrypt_to_recipient( pk_2.pk_d, message @@ -226,7 +485,7 @@ TEST(noteencryption, SaplingApi) // 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( 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 pk_enc = ZCNoteEncryption::generate_pubkey(sk_enc); @@ -446,7 +705,7 @@ uint256 test_prf( return ret; } -TEST(noteencryption, PrfAddr) +TEST(NoteEncryption, PrfAddr) { for (size_t i = 0; i < 100; i++) { 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++) { 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++) { 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); } -TEST(noteencryption, PrfRho) +TEST(NoteEncryption, PrfRho) { for (size_t i = 0; i < 100; i++) { uint252 phi = libzcash::random_uint252(); @@ -523,7 +782,7 @@ TEST(noteencryption, PrfRho) 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); } diff --git a/src/gtest/test_sapling_note.cpp b/src/gtest/test_sapling_note.cpp index b6846217a..ca1fece7c 100644 --- a/src/gtest/test_sapling_note.cpp +++ b/src/gtest/test_sapling_note.cpp @@ -44,7 +44,7 @@ TEST(SaplingNote, TestVectors) uint256 nf(v_nf); // 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); // Test nullifier @@ -57,16 +57,16 @@ TEST(SaplingNote, Random) { // Test creating random notes using the same spending key auto address = SaplingSpendingKey::random().default_address(); - SaplingNote note1(address, GetRand(MAX_MONEY)); - SaplingNote note2(address, GetRand(MAX_MONEY)); + SaplingNote note1(address, GetRand(MAX_MONEY), Zip212Enabled::BeforeZip212); + SaplingNote note2(address, GetRand(MAX_MONEY), Zip212Enabled::BeforeZip212); ASSERT_EQ(note1.d, note2.d); ASSERT_EQ(note1.pk_d, note2.pk_d); 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 - 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.pk_d, note3.pk_d); } diff --git a/src/gtest/test_transaction_builder.cpp b/src/gtest/test_transaction_builder.cpp index d97d0287e..253b3f7ff 100644 --- a/src/gtest/test_transaction_builder.cpp +++ b/src/gtest/test_transaction_builder.cpp @@ -1,5 +1,6 @@ #include "chainparams.h" #include "consensus/params.h" +#include "consensus/consensus.h" #include "consensus/validation.h" #include "key_io.h" #include "main.h" @@ -482,7 +483,7 @@ TEST(TransactionBuilder, CheckSaplingTxVersion) } // Cannot add Sapling spends to a non-Sapling transaction - libzcash::SaplingNote note(pk, 50000); + libzcash::SaplingNote note(pk, 50000, libzcash::Zip212Enabled::BeforeZip212); SaplingMerkleTree tree; try { builder.AddSaplingSpend(expsk, note, uint256(), tree.witness()); diff --git a/src/main.cpp b/src/main.cpp index 2c4fa4e66..bad3fd3a5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -920,19 +920,34 @@ bool ContextualCheckTransaction( } // SaplingNotePlaintext::decrypt() checks note commitment validity. - if (!SaplingNotePlaintext::decrypt( + + auto encPlaintext = SaplingNotePlaintext::decrypt( + chainparams.GetConsensus(), + nHeight, output.encCiphertext, output.ephemeralKey, outPlaintext->esk, outPlaintext->pk_d, - output.cmu) - ) { + output.cmu); + if (!encPlaintext) { return state.DoS( DOS_LEVEL_BLOCK, error("CheckTransaction(): coinbase output description has invalid encCiphertext"), REJECT_INVALID, "bad-cb-output-desc-invalid-encct"); } + + // 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"); + } } } } diff --git a/src/miner.cpp b/src/miner.cpp index 89f52175a..638082040 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -157,7 +157,11 @@ public: mtx.valueBalance = -value; uint256 ovk; - auto note = libzcash::SaplingNote(pa, value); + libzcash::Zip212Enabled zip_212_enabled = libzcash::Zip212Enabled::BeforeZip212; + if (Params().GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_CANOPY)) { + zip_212_enabled = libzcash::Zip212Enabled::AfterZip212; + } + auto note = libzcash::SaplingNote(pa, value, zip_212_enabled); auto output = OutputDescriptionInfo(ovk, note, {{0xF6}}); auto ctx = librustzcash_sapling_proving_ctx_init(); diff --git a/src/transaction_builder.cpp b/src/transaction_builder.cpp index 811db2740..6236d4e9d 100644 --- a/src/transaction_builder.cpp +++ b/src/transaction_builder.cpp @@ -9,6 +9,7 @@ #include "rpc/protocol.h" #include "script/sign.h" #include "utilmoneystr.h" +#include "zcash/Note.hpp" #include #include @@ -43,11 +44,12 @@ boost::optional OutputDescriptionInfo::Build(void* ctx) { std::vector addressBytes(ss.begin(), ss.end()); OutputDescription odesc; + uint256 rcm = this->note.rcm(); if (!librustzcash_sapling_output_proof( ctx, encryptor.get_esk().begin(), addressBytes.data(), - this->note.r.begin(), + rcm.begin(), this->note.value(), odesc.cv.begin(), odesc.zkproof.begin())) { @@ -161,7 +163,13 @@ void TransactionBuilder::AddSaplingOutput( 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); mtx.valueBalance -= value; } @@ -324,12 +332,13 @@ TransactionBuilderResult TransactionBuilder::Build() std::vector witness(ss.begin(), ss.end()); SpendDescription sdesc; + uint256 rcm = spend.note.rcm(); if (!librustzcash_sapling_spend_proof( ctx, spend.expsk.full_viewing_key().ak.begin(), spend.expsk.nsk.begin(), spend.note.d.data(), - spend.note.r.begin(), + rcm.begin(), spend.alpha.begin(), spend.note.value(), spend.anchor.begin(), diff --git a/src/utiltest.cpp b/src/utiltest.cpp index bcb47abeb..24ac06a59 100644 --- a/src/utiltest.cpp +++ b/src/utiltest.cpp @@ -264,6 +264,10 @@ const Consensus::Params& RegtestActivateCanopy(bool updatePow, int canopyActivat return Params().GetConsensus(); } +const Consensus::Params& RegtestActivateCanopy() { + return RegtestActivateCanopy(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); +} + void RegtestDeactivateCanopy() { UpdateRegtestPow(0, 0, uint256S("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f")); 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) { // Generate dummy Sapling note - libzcash::SaplingNote note(pa, value); + libzcash::SaplingNote note(pa, value, libzcash::Zip212Enabled::BeforeZip212); uint256 cm = note.cmu().get(); SaplingMerkleTree tree; tree.append(cm); diff --git a/src/utiltest.h b/src/utiltest.h index 9166f499c..d22f48af9 100644 --- a/src/utiltest.h +++ b/src/utiltest.h @@ -54,6 +54,7 @@ const Consensus::Params& RegtestActivateHeartwood(bool updatePow, int heartwoodA void RegtestDeactivateHeartwood(); const Consensus::Params& RegtestActivateCanopy(bool updatePow, int canopyActivationHeight = Consensus::NetworkUpgrade::ALWAYS_ACTIVE); +const Consensus::Params& RegtestActivateCanopy(); void RegtestDeactivateCanopy(); diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index 49773aee8..db9f8e47e 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -25,7 +25,7 @@ CMainSignals& GetMainSignals() void RegisterValidationInterface(CValidationInterface* pwalletIn) { 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.UpdatedTransaction.connect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1)); 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.UpdatedTransaction.disconnect(boost::bind(&CValidationInterface::UpdatedTransaction, 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)); } @@ -65,8 +65,8 @@ void UnregisterAllValidationInterfaces() { g_signals.UpdatedBlockTip.disconnect_all_slots(); } -void SyncWithWallets(const CTransaction &tx, const CBlock *pblock) { - g_signals.SyncTransaction(tx, pblock); +void SyncWithWallets(const CTransaction &tx, const CBlock *pblock, const int nHeight) { + g_signals.SyncTransaction(tx, pblock, nHeight); } struct CachedBlockData { @@ -187,7 +187,7 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip) // Let wallets know transactions went from 1-confirmed to // 0-confirmed or conflicted: for (const CTransaction &tx : block.vtx) { - SyncWithWallets(tx, NULL); + SyncWithWallets(tx, NULL, pindexLastTip->nHeight); } // Update cached incremental witnesses GetMainSignals().ChainTip(pindexLastTip, &block, boost::none); @@ -214,11 +214,11 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip) // Tell wallet about transactions that went from mempool // to conflicted: for (const CTransaction &tx : blockData.txConflicted) { - SyncWithWallets(tx, NULL); + SyncWithWallets(tx, NULL, blockData.pindex->nHeight + 1); } // ... and about transactions that got confirmed: for (const CTransaction &tx : block.vtx) { - SyncWithWallets(tx, &block); + SyncWithWallets(tx, &block, blockData.pindex->nHeight); } // Update cached incremental witnesses GetMainSignals().ChainTip(blockData.pindex, &block, blockData.oldTrees); @@ -230,7 +230,7 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip) // Notify transactions in the mempool for (auto tx : recentlyAdded.first) { try { - SyncWithWallets(tx, NULL); + SyncWithWallets(tx, NULL, pindexLastTip->nHeight + 1); } catch (const boost::thread_interrupted&) { throw; } catch (const std::exception& e) { diff --git a/src/validationinterface.h b/src/validationinterface.h index 8dfdd6eb4..f1694a313 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -33,7 +33,7 @@ void UnregisterAllValidationInterfaces(); class CValidationInterface { protected: 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 ChainTip(const CBlockIndex *pindex, const CBlock *pblock, boost::optional> added) {} virtual void SetBestChain(const CBlockLocator &locator) {} @@ -52,7 +52,7 @@ struct CMainSignals { /** Notifies listeners of updated block chain tip */ boost::signals2::signal UpdatedBlockTip; /** Notifies listeners of updated transaction data (transaction, and optionally the block it is found in. */ - boost::signals2::signal SyncTransaction; + boost::signals2::signal SyncTransaction; /** Notifies listeners of an erased transaction (currently disabled, requires transaction replacement). */ boost::signals2::signal EraseTransaction; /** Notifies listeners of an updated transaction without new data (for now: a coinbase potentially becoming visible). */ diff --git a/src/wallet/gtest/test_wallet.cpp b/src/wallet/gtest/test_wallet.cpp index 51f233c7d..a0a032140 100644 --- a/src/wallet/gtest/test_wallet.cpp +++ b/src/wallet/gtest/test_wallet.cpp @@ -376,58 +376,65 @@ TEST(WalletTests, SetSproutNoteAddrsInCWalletTx) { } TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) { - auto consensusParams = RegtestActivateSapling(); + SelectParams(CBaseChainParams::REGTEST); - TestWallet wallet; - LOCK(wallet.cs_wallet); + std::vector zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212}; + const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy}; + void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy}; - auto sk = GetTestMasterSaplingSpendingKey(); - auto expsk = sk.expsk; - auto fvk = expsk.full_viewing_key(); - auto ivk = fvk.in_viewing_key(); - auto pk = sk.DefaultAddress(); + for (int ver = 0; ver < zip_212_enabled.size(); ver++) { + auto consensusParams = (*activations[ver])(); - libzcash::SaplingNote note(pk, 50000); - auto cm = note.cmu().get(); - SaplingMerkleTree tree; - tree.append(cm); - auto anchor = tree.root(); - auto witness = tree.witness(); + TestWallet wallet; + LOCK(wallet.cs_wallet); - auto nf = note.nullifier(fvk, witness.position()); - ASSERT_TRUE(nf); - uint256 nullifier = nf.get(); + auto sk = GetTestMasterSaplingSpendingKey(); + auto expsk = sk.expsk; + auto fvk = expsk.full_viewing_key(); + auto ivk = fvk.in_viewing_key(); + auto pk = sk.DefaultAddress(); - auto builder = TransactionBuilder(consensusParams, 1); - builder.AddSaplingSpend(expsk, note, anchor, witness); - builder.AddSaplingOutput(fvk.ovk, pk, 50000, {}); - builder.SetFee(0); - auto tx = builder.Build().GetTxOrThrow(); + libzcash::SaplingNote note(pk, 50000, zip_212_enabled[ver]); + auto cm = note.cmu().get(); + SaplingMerkleTree tree; + tree.append(cm); + 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()); - mapSaplingNoteData_t noteData; + auto builder = TransactionBuilder(consensusParams, 1); + 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}; - SaplingNoteData nd; - nd.nullifier = nullifier; - nd.ivk = ivk; - nd.witnesses.push_front(witness); - nd.witnessHeight = 123; - noteData.insert(std::make_pair(op, nd)); + CWalletTx wtx {&wallet, tx}; - wtx.SetSaplingNoteData(noteData); - EXPECT_EQ(noteData, wtx.mapSaplingNoteData); + EXPECT_EQ(0, wtx.mapSaplingNoteData.size()); + mapSaplingNoteData_t noteData; - // 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()); + SaplingOutPoint op {wtx.GetHash(), 0}; + SaplingNoteData nd; + nd.nullifier = nullifier; + nd.ivk = ivk; + nd.witnesses.push_front(witness); + nd.witnessHeight = 123; + noteData.insert(std::make_pair(op, nd)); - // Revert to default - RegtestDeactivateSapling(); + wtx.SetSaplingNoteData(noteData); + 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) { @@ -534,13 +541,13 @@ TEST(WalletTests, FindMySaplingNotes) { // No Sapling notes can be found in tx which does not belong to the wallet CWalletTx wtx {&wallet, tx}; ASSERT_FALSE(wallet.HaveSaplingSpendingKey(extfvk)); - auto noteMap = wallet.FindMySaplingNotes(wtx).first; + auto noteMap = wallet.FindMySaplingNotes(wtx, 1).first; EXPECT_EQ(0, noteMap.size()); // Add spending key to wallet, so Sapling notes can be found ASSERT_TRUE(wallet.AddSaplingZKey(sk)); ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk)); - noteMap = wallet.FindMySaplingNotes(wtx).first; + noteMap = wallet.FindMySaplingNotes(wtx, 1).first; EXPECT_EQ(2, noteMap.size()); // 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 TEST(WalletTests, GetConflictedSaplingNotes) { - auto consensusParams = RegtestActivateSapling(); + SelectParams(CBaseChainParams::REGTEST); - TestWallet wallet; - LOCK2(cs_main, wallet.cs_wallet); + std::vector zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212}; + const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy}; + void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy}; - // Generate Sapling address - auto sk = GetTestMasterSaplingSpendingKey(); - auto expsk = sk.expsk; - auto extfvk = sk.ToXFVK(); - auto ivk = extfvk.fvk.in_viewing_key(); - auto pk = sk.DefaultAddress(); + for (int ver = 0; ver < zip_212_enabled.size(); ver++) { + auto consensusParams = (*activations[ver])(); - ASSERT_TRUE(wallet.AddSaplingZKey(sk)); - ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk)); + TestWallet wallet; + LOCK2(cs_main, wallet.cs_wallet); - // Generate note A - libzcash::SaplingNote note(pk, 50000); - auto cm = note.cmu().get(); - SaplingMerkleTree saplingTree; - saplingTree.append(cm); - auto anchor = saplingTree.root(); - auto witness = saplingTree.witness(); + // Generate Sapling address + auto sk = GetTestMasterSaplingSpendingKey(); + auto expsk = sk.expsk; + auto extfvk = sk.ToXFVK(); + auto ivk = extfvk.fvk.in_viewing_key(); + auto pk = sk.DefaultAddress(); - // Generate tx to create output note B - auto builder = TransactionBuilder(consensusParams, 1); - builder.AddSaplingSpend(expsk, note, anchor, witness); - builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 35000, {}); - auto tx = builder.Build().GetTxOrThrow(); - CWalletTx wtx {&wallet, tx}; + ASSERT_TRUE(wallet.AddSaplingZKey(sk)); + ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk)); - // Fake-mine the transaction - EXPECT_EQ(-1, chainActive.Height()); - 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()); + // Generate note A + libzcash::SaplingNote note(pk, 50000, zip_212_enabled[ver]); + auto cm = note.cmu().get(); + SaplingMerkleTree saplingTree; + saplingTree.append(cm); + auto anchor = saplingTree.root(); + auto witness = saplingTree.witness(); - // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe - auto saplingNoteData = wallet.FindMySaplingNotes(wtx).first; - ASSERT_TRUE(saplingNoteData.size() > 0); - wtx.SetSaplingNoteData(saplingNoteData); - wtx.SetMerkleBranch(block); - wallet.AddToWallet(wtx, true, NULL); + // Generate tx to create output note B + auto builder = TransactionBuilder(consensusParams, 1); + builder.AddSaplingSpend(expsk, note, anchor, witness); + builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 35000, {}); + auto tx = builder.Build().GetTxOrThrow(); + CWalletTx wtx {&wallet, tx}; - // Simulate receiving new block and ChainTip signal - wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree); - wallet.UpdateSaplingNullifierNoteMapForBlock(&block); + // Fake-mine the transaction + EXPECT_EQ(-1, chainActive.Height()); + 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 - uint256 hash = wtx.GetHash(); - wtx = wallet.mapWallet[hash]; + // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe + auto saplingNoteData = wallet.FindMySaplingNotes(wtx, 1).first; + ASSERT_TRUE(saplingNoteData.size() > 0); + wtx.SetSaplingNoteData(saplingNoteData); + wtx.SetMerkleBranch(block); + wallet.AddToWallet(wtx, true, NULL); - // Decrypt output note B - auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt( - wtx.vShieldedOutput[0].encCiphertext, - ivk, - wtx.vShieldedOutput[0].ephemeralKey, - wtx.vShieldedOutput[0].cmu); - ASSERT_EQ(static_cast(maybe_pt), true); - auto maybe_note = maybe_pt.get().note(ivk); - ASSERT_EQ(static_cast(maybe_note), true); - auto note2 = maybe_note.get(); + // Simulate receiving new block and ChainTip signal + wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree); + wallet.UpdateSaplingNullifierNoteMapForBlock(&block); - SaplingOutPoint sop0(wtx.GetHash(), 0); - auto spend_note_witness = wtx.mapSaplingNoteData[sop0].witnesses.front(); - auto maybe_nf = note2.nullifier(extfvk.fvk, spend_note_witness.position()); - ASSERT_EQ(static_cast(maybe_nf), true); - auto nullifier2 = maybe_nf.get(); + // Retrieve the updated wtx from wallet + uint256 hash = wtx.GetHash(); + wtx = wallet.mapWallet[hash]; - 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(maybe_pt), true); + auto maybe_note = maybe_pt.get().note(ivk); + ASSERT_EQ(static_cast(maybe_note), true); + auto note2 = maybe_note.get(); - // Create transaction to spend note B - 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(); + SaplingOutPoint sop0(wtx.GetHash(), 0); + auto spend_note_witness = wtx.mapSaplingNoteData[sop0].witnesses.front(); + auto maybe_nf = note2.nullifier(extfvk.fvk, spend_note_witness.position()); + ASSERT_EQ(static_cast(maybe_nf), true); + auto nullifier2 = maybe_nf.get(); - // Create conflicting transaction which also spends note B - 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(); + anchor = saplingTree.root(); - CWalletTx wtx2 {&wallet, tx2}; - CWalletTx wtx3 {&wallet, tx3}; + // Create transaction to spend note B + 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(); - auto hash3 = wtx3.GetHash(); + // Create conflicting transaction which also spends note B + 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) - EXPECT_EQ(0, wallet.GetConflicts(hash2).size()); - EXPECT_EQ(0, wallet.GetConflicts(hash3).size()); + CWalletTx wtx2 {&wallet, tx2}; + CWalletTx wtx3 {&wallet, tx3}; - // No conflicts for one spend - wallet.AddToWallet(wtx2, true, NULL); - EXPECT_EQ(0, wallet.GetConflicts(hash2).size()); + auto hash2 = wtx2.GetHash(); + auto hash3 = wtx3.GetHash(); - // Conflicts for two spends - wallet.AddToWallet(wtx3, true, NULL); - auto c3 = wallet.GetConflicts(hash2); - EXPECT_EQ(2, c3.size()); - EXPECT_EQ(std::set({hash2, hash3}), c3); + // No conflicts for no spends (wtx is currently the only transaction in the wallet) + EXPECT_EQ(0, wallet.GetConflicts(hash2).size()); + EXPECT_EQ(0, wallet.GetConflicts(hash3).size()); - // Tear down - chainActive.SetTip(NULL); - mapBlockIndex.erase(blockHash); + // No conflicts for one spend + wallet.AddToWallet(wtx2, true, NULL); + EXPECT_EQ(0, wallet.GetConflicts(hash2).size()); - // Revert to default - RegtestDeactivateSapling(); + // Conflicts for two spends + wallet.AddToWallet(wtx3, true, NULL); + auto c3 = wallet.GetConflicts(hash2); + EXPECT_EQ(2, c3.size()); + EXPECT_EQ(std::set({hash2, hash3}), c3); + + // Tear down + chainActive.SetTip(NULL); + mapBlockIndex.erase(blockHash); + + (*deactivations[ver])(); + } } TEST(WalletTests, SproutNullifierIsSpent) { @@ -928,7 +944,7 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) { // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe wtx.SetMerkleBranch(block); - auto saplingNoteData = wallet.FindMySaplingNotes(wtx).first; + auto saplingNoteData = wallet.FindMySaplingNotes(wtx, chainActive.Height()).first; ASSERT_TRUE(saplingNoteData.size() > 0); wtx.SetSaplingNoteData(saplingNoteData); 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. TEST(WalletTests, SpentSaplingNoteIsFromMe) { - auto consensusParams = RegtestActivateSapling(); + SelectParams(CBaseChainParams::REGTEST); - TestWallet wallet; - LOCK2(cs_main, wallet.cs_wallet); + std::vector zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212}; + const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy}; + void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy}; - // Generate Sapling address - auto sk = GetTestMasterSaplingSpendingKey(); - auto expsk = sk.expsk; - auto extfvk = sk.ToXFVK(); - auto ivk = extfvk.fvk.in_viewing_key(); - auto pk = sk.DefaultAddress(); + for (int ver = 0; ver < zip_212_enabled.size(); ver++) { + auto consensusParams = (*activations[ver])(); - // Generate Sapling note A - libzcash::SaplingNote note(pk, 50000); - auto cm = note.cmu().get(); - SaplingMerkleTree saplingTree; - saplingTree.append(cm); - auto anchor = saplingTree.root(); - auto witness = saplingTree.witness(); + TestWallet wallet; + LOCK2(cs_main, wallet.cs_wallet); - // Generate transaction, which sends funds to note B - auto builder = TransactionBuilder(consensusParams, 1); - builder.AddSaplingSpend(expsk, note, anchor, witness); - builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 25000, {}); - auto tx = builder.Build().GetTxOrThrow(); + // Generate Sapling address + auto sk = GetTestMasterSaplingSpendingKey(); + auto expsk = sk.expsk; + auto extfvk = sk.ToXFVK(); + auto ivk = extfvk.fvk.in_viewing_key(); + auto pk = sk.DefaultAddress(); - CWalletTx wtx {&wallet, tx}; - ASSERT_TRUE(wallet.AddSaplingZKey(sk)); - ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk)); + // Generate Sapling note A + libzcash::SaplingNote note(pk, 50000, zip_212_enabled[ver]); + auto cm = note.cmu().get(); + SaplingMerkleTree saplingTree; + saplingTree.append(cm); + auto anchor = saplingTree.root(); + auto witness = saplingTree.witness(); - // Fake-mine the transaction - EXPECT_EQ(-1, chainActive.Height()); - 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()); + // Generate transaction, which sends funds to note B + auto builder = TransactionBuilder(consensusParams, 1); + builder.AddSaplingSpend(expsk, note, anchor, witness); + builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 25000, {}); + auto tx = builder.Build().GetTxOrThrow(); - auto saplingNoteData = wallet.FindMySaplingNotes(wtx).first; - ASSERT_TRUE(saplingNoteData.size() > 0); - wtx.SetSaplingNoteData(saplingNoteData); - wtx.SetMerkleBranch(block); - wallet.AddToWallet(wtx, true, NULL); + CWalletTx wtx {&wallet, tx}; + ASSERT_TRUE(wallet.AddSaplingZKey(sk)); + ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk)); - // Simulate receiving new block and ChainTip signal. - // This triggers calculation of nullifiers for notes belonging to this wallet - // in the output descriptions of wtx. - wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree); - wallet.UpdateSaplingNullifierNoteMapForBlock(&block); + // Fake-mine the transaction + EXPECT_EQ(-1, chainActive.Height()); + 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 - wtx = wallet.mapWallet[wtx.GetHash()]; + auto saplingNoteData = wallet.FindMySaplingNotes(wtx, 1).first; + 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 - // is no mapping from nullifier to notedata stored in mapSaplingNullifiersToNotes. - // Therefore the wallet does not know the tx belongs to the wallet. - EXPECT_FALSE(wallet.IsFromMe(wtx)); + // Simulate receiving new block and ChainTip signal. + // This triggers calculation of nullifiers for notes belonging to this wallet + // in the output descriptions of wtx. + wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree); + wallet.UpdateSaplingNullifierNoteMapForBlock(&block); - // Manually compute the nullifier and check map entry does not exist - auto nf = note.nullifier(extfvk.fvk, witness.position()); - ASSERT_TRUE(nf); - ASSERT_FALSE(wallet.mapSaplingNullifiersToNotes.count(nf.get())); + // Retrieve the updated wtx from wallet + wtx = wallet.mapWallet[wtx.GetHash()]; - // Decrypt note B - auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt( - wtx.vShieldedOutput[0].encCiphertext, - ivk, - wtx.vShieldedOutput[0].ephemeralKey, - wtx.vShieldedOutput[0].cmu); - ASSERT_EQ(static_cast(maybe_pt), true); - auto maybe_note = maybe_pt.get().note(ivk); - ASSERT_EQ(static_cast(maybe_note), true); - auto note2 = maybe_note.get(); + // The test wallet never received the fake note which is being spent, so there + // is no mapping from nullifier to notedata stored in mapSaplingNullifiersToNotes. + // Therefore the wallet does not know the tx belongs to the wallet. + EXPECT_FALSE(wallet.IsFromMe(wtx)); - // Get witness to retrieve position of note B we want to spend - SaplingOutPoint sop0(wtx.GetHash(), 0); - auto spend_note_witness = wtx.mapSaplingNoteData[sop0].witnesses.front(); - auto maybe_nf = note2.nullifier(extfvk.fvk, spend_note_witness.position()); - ASSERT_EQ(static_cast(maybe_nf), true); - auto nullifier2 = maybe_nf.get(); + // Manually compute the nullifier and check map entry does not exist + auto nf = note.nullifier(extfvk.fvk, witness.position()); + ASSERT_TRUE(nf); + ASSERT_FALSE(wallet.mapSaplingNullifiersToNotes.count(nf.get())); - // 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(); + // Decrypt 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(maybe_pt), true); + auto maybe_note = maybe_pt.get().note(ivk); + ASSERT_EQ(static_cast(maybe_note), true); + auto note2 = maybe_note.get(); - // Create transaction to spend note B - auto builder2 = TransactionBuilder(consensusParams, 2); - builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness); - builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 12500, {}); - auto tx2 = builder2.Build().GetTxOrThrow(); - EXPECT_EQ(tx2.vin.size(), 0); - 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); + // Get witness to retrieve position of note B we want to spend + SaplingOutPoint sop0(wtx.GetHash(), 0); + auto spend_note_witness = wtx.mapSaplingNoteData[sop0].witnesses.front(); + auto maybe_nf = note2.nullifier(extfvk.fvk, spend_note_witness.position()); + ASSERT_EQ(static_cast(maybe_nf), true); + auto nullifier2 = maybe_nf.get(); - 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 - 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()); + // Create transaction to spend note B + auto builder2 = TransactionBuilder(consensusParams, 2); + builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness); + builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 12500, {}); + auto tx2 = builder2.Build().GetTxOrThrow(); + EXPECT_EQ(tx2.vin.size(), 0); + 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); - auto saplingNoteData2 = wallet.FindMySaplingNotes(wtx2).first; - ASSERT_TRUE(saplingNoteData2.size() > 0); - wtx2.SetSaplingNoteData(saplingNoteData2); - wtx2.SetMerkleBranch(block2); - wallet.AddToWallet(wtx2, true, NULL); + CWalletTx wtx2 {&wallet, tx2}; - // Verify note B is spent. AddToWallet invokes AddToSpends which updates mapTxSaplingNullifiers - EXPECT_TRUE(wallet.IsSaplingSpent(nullifier2)); + // Fake-mine this tx into the next block + 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. - EXPECT_TRUE(wallet.IsFromMe(wtx2)); - ASSERT_TRUE(wallet.mapSaplingNullifiersToNotes.count(nullifier2)); + auto saplingNoteData2 = wallet.FindMySaplingNotes(wtx2, 2).first; + ASSERT_TRUE(saplingNoteData2.size() > 0); + wtx2.SetSaplingNoteData(saplingNoteData2); + wtx2.SetMerkleBranch(block2); + wallet.AddToWallet(wtx2, true, NULL); - // Tear down - chainActive.SetTip(NULL); - mapBlockIndex.erase(blockHash); - mapBlockIndex.erase(blockHash2); + // Verify note B is spent. AddToWallet invokes AddToSpends which updates mapTxSaplingNullifiers + EXPECT_TRUE(wallet.IsSaplingSpent(nullifier2)); - // Revert to default - RegtestDeactivateSapling(); + // Verify note B belongs to wallet. + 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) { @@ -1843,7 +1868,7 @@ TEST(WalletTests, UpdatedSaplingNoteData) { EXPECT_EQ(0, chainActive.Height()); // 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 wtx.SetSaplingNoteData(saplingNoteData); wtx.SetMerkleBranch(block); @@ -1861,7 +1886,7 @@ TEST(WalletTests, UpdatedSaplingNoteData) { ASSERT_TRUE(wallet.AddSaplingZKey(sk2)); ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk2)); CWalletTx wtx2 = wtx; - auto saplingNoteData2 = wallet.FindMySaplingNotes(wtx2).first; + auto saplingNoteData2 = wallet.FindMySaplingNotes(wtx2, chainActive.Height()).first; ASSERT_TRUE(saplingNoteData2.size() == 2); wtx2.SetSaplingNoteData(saplingNoteData2); @@ -1990,7 +2015,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) { EXPECT_EQ(0, chainActive.Height()); // Simulate SyncTransaction which calls AddToWalletIfInvolvingMe - auto saplingNoteData = wallet.FindMySaplingNotes(wtx).first; + auto saplingNoteData = wallet.FindMySaplingNotes(wtx, chainActive.Height()).first; ASSERT_TRUE(saplingNoteData.size() > 0); wtx.SetSaplingNoteData(saplingNoteData); wtx.SetMerkleBranch(block); @@ -2005,8 +2030,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) { wtx = wallet.mapWallet[hash]; // Prepare to spend the note that was just created - auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt( - tx1.vShieldedOutput[0].encCiphertext, ivk, tx1.vShieldedOutput[0].ephemeralKey, tx1.vShieldedOutput[0].cmu); + auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt(consensusParams, fakeIndex.nHeight, tx1.vShieldedOutput[0].encCiphertext, ivk, tx1.vShieldedOutput[0].ephemeralKey, tx1.vShieldedOutput[0].cmu); ASSERT_EQ(static_cast(maybe_pt), true); auto maybe_note = maybe_pt.get().note(ivk); ASSERT_EQ(static_cast(maybe_note), true); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index ae38c4b0a..622a65ed1 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5,6 +5,7 @@ #include "amount.h" #include "consensus/upgrades.h" +#include "consensus/params.h" #include "core_io.h" #include "experimental_features.h" #include "init.h" @@ -3775,7 +3776,9 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp) auto op = res->second; 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 pa = decrypted.second; @@ -3803,14 +3806,16 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp) SaplingPaymentAddress pa; 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) { notePt = decrypted->first; pa = decrypted->second; isOutgoing = false; } else { // Try recovering the output - auto recovered = wtx.RecoverSaplingNote(op, ovks); + auto recovered = wtx.RecoverSaplingNoteWithoutLeadByteCheck(op, ovks); if (recovered) { notePt = recovered->first; pa = recovered->second; diff --git a/src/wallet/test/rpc_wallet_tests.cpp b/src/wallet/test/rpc_wallet_tests.cpp index 621d7314b..0b2ad86b6 100644 --- a/src/wallet/test/rpc_wallet_tests.cpp +++ b/src/wallet/test/rpc_wallet_tests.cpp @@ -1837,7 +1837,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_mergetoaddress_parameters) std::vector sproutNoteInputs = {MergeToAddressInputSproutNote{JSOutPoint(), SproutNote(), 0, SproutSpendingKey()}}; std::vector saplingNoteInputs = - {MergeToAddressInputSaplingNote{SaplingOutPoint(), SaplingNote(), 0, SaplingExpandedSpendingKey()}}; + {MergeToAddressInputSaplingNote{SaplingOutPoint(), SaplingNote({}, uint256(), 0, uint256(), Zip212Enabled::BeforeZip212), 0, SaplingExpandedSpendingKey()}}; // Sprout and Sapling inputs -> throw try { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a1c7bb5c0..6e20ae6d4 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1495,21 +1495,25 @@ void CWallet::UpdateSaplingNullifierNoteMapWithTx(CWalletTx& wtx) { uint64_t position = nd.witnesses.front().position(); auto extfvk = mapSaplingFullViewingKeys.at(nd.ivk); OutputDescription output = wtx.vShieldedOutput[op.n]; - auto optPlaintext = SaplingNotePlaintext::decrypt(output.encCiphertext, nd.ivk, output.ephemeralKey, output.cmu); - if (!optPlaintext) { - // An item in mapSaplingNoteData must have already been successfully decrypted, - // otherwise the item would not exist in the first place. - assert(false); - } + + 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 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); - if (!optNote) { - assert(false); - } + assert(optNote != boost::none); + 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? - assert(false); - } + // This should not happen. If it does, maybe the position has been corrupted or miscalculated? + assert(optNullifier != boost::none); uint256 nullifier = optNullifier.get(); mapSaplingNullifiersToNotes[nullifier] = op; 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 * 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); bool fExisted = mapWallet.count(tx.GetHash()) != 0; if (fExisted && !fUpdate) return false; auto sproutNoteData = FindMySproutNotes(tx); - auto saplingNoteDataAndAddressesToAdd = FindMySaplingNotes(tx); + auto saplingNoteDataAndAddressesToAdd = FindMySaplingNotes(tx, nHeight); auto saplingNoteData = saplingNoteDataAndAddressesToAdd.first; auto addressesToAdd = saplingNoteDataAndAddressesToAdd.second; for (const auto &addressToAdd : addressesToAdd) { @@ -1748,10 +1752,10 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl 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); - if (!AddToWalletIfInvolvingMe(tx, pblock, true)) + if (!AddToWalletIfInvolvingMe(tx, pblock, nHeight, true)) return; // Not one of ours 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 * already have been cached in CWalletTx.mapSaplingNoteData. */ -std::pair CWallet::FindMySaplingNotes(const CTransaction &tx) const +std::pair CWallet::FindMySaplingNotes(const CTransaction &tx, int height) const { LOCK(cs_KeyStore); uint256 hash = tx.GetHash(); @@ -1901,7 +1905,8 @@ std::pair CWallet::FindMySap const OutputDescription output = tx.vShieldedOutput[i]; for (auto it = mapSaplingFullViewingKeys.begin(); it != mapSaplingFullViewingKeys.end(); ++it) { 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) { continue; } @@ -2300,7 +2305,7 @@ std::pair CWalletTx::DecryptSproutNot boost::optional> CWalletTx::DecryptSaplingNote(SaplingOutPoint op) const + SaplingPaymentAddress>> CWalletTx::DecryptSaplingNote(const Consensus::Params& params, int height, SaplingOutPoint op) const { // Check whether we can decrypt this SaplingOutPoint if (this->mapSaplingNoteData.count(op) == 0) { @@ -2311,11 +2316,46 @@ boost::optionalmapSaplingNoteData.at(op); auto maybe_pt = SaplingNotePlaintext::decrypt( + params, + height, output.encCiphertext, nd.ivk, output.ephemeralKey, output.cmu); - assert(static_cast(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> 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 maybe_pa = nd.ivk.address(notePt.d); @@ -2327,8 +2367,7 @@ boost::optional> CWalletTx::RecoverSaplingNote( - SaplingOutPoint op, std::set& ovks) const + SaplingPaymentAddress>> CWalletTx::RecoverSaplingNote(const Consensus::Params& params, int height, SaplingOutPoint op, std::set& ovks) const { auto output = this->vShieldedOutput[op.n]; @@ -2340,10 +2379,13 @@ boost::optionalesk, @@ -2359,6 +2401,46 @@ boost::optional> CWalletTx::RecoverSaplingNoteWithoutLeadByteCheck(SaplingOutPoint op, std::set& 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(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 n = nTimeSmart; @@ -2654,7 +2736,7 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) ReadBlockFromDisk(block, pindex, Params().GetConsensus()); BOOST_FOREACH(CTransaction& tx, block.vtx) { - if (AddToWalletIfInvolvingMe(tx, &block, fUpdate)) { + if (AddToWalletIfInvolvingMe(tx, &block, pindex->nHeight, fUpdate)) { myTxHashes.push_back(tx.GetHash()); ret++; } @@ -4975,14 +5057,13 @@ void CWallet::GetFilteredNotes( SaplingOutPoint op = pair.first; SaplingNoteData nd = pair.second; - auto maybe_pt = SaplingNotePlaintext::decrypt( - wtx.vShieldedOutput[op.n].encCiphertext, - nd.ivk, - wtx.vShieldedOutput[op.n].ephemeralKey, - wtx.vShieldedOutput[op.n].cmu); - assert(static_cast(maybe_pt)); - auto notePt = maybe_pt.get(); + auto optDeserialized = SaplingNotePlaintext::attempt_sapling_enc_decryption_deserialization(wtx.vShieldedOutput[op.n].encCiphertext, nd.ivk, wtx.vShieldedOutput[op.n].ephemeralKey); + // 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); assert(static_cast(maybe_pa)); auto pa = maybe_pa.get(); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index c673dfcea..c41116119 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -566,11 +566,17 @@ public: JSOutPoint jsop) const; boost::optional> DecryptSaplingNote(SaplingOutPoint op) const; + libzcash::SaplingPaymentAddress>> DecryptSaplingNote(const Consensus::Params& params, int height, SaplingOutPoint op) const; boost::optional> RecoverSaplingNote( + libzcash::SaplingPaymentAddress>> DecryptSaplingNoteWithoutLeadByteCheck(SaplingOutPoint op) const; + boost::optional> RecoverSaplingNote(const Consensus::Params& params, int height, SaplingOutPoint op, std::set& ovks) const; + boost::optional> RecoverSaplingNoteWithoutLeadByteCheck(SaplingOutPoint op, std::set& ovks) const; //! filter decides which addresses will count towards the debit CAmount GetDebit(const isminefilter& filter) const; @@ -1156,8 +1162,8 @@ public: void UpdateSaplingNullifierNoteMapWithTx(CWalletTx& wtx); void UpdateSaplingNullifierNoteMapForBlock(const CBlock* pblock); bool AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb); - void SyncTransaction(const CTransaction& tx, const CBlock* pblock); - bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate); + void SyncTransaction(const CTransaction& tx, const CBlock* pblock, const int nHeight); + bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, const int nHeight, bool fUpdate); void EraseFromWallet(const uint256 &hash); void WitnessNoteCommitment( std::vector commitments, @@ -1221,7 +1227,7 @@ public: const uint256& hSig, uint8_t n) const; mapSproutNoteData_t FindMySproutNotes(const CTransaction& tx) const; - std::pair FindMySaplingNotes(const CTransaction& tx) const; + std::pair FindMySaplingNotes(const CTransaction& tx, int height) const; bool IsSproutNullifierFromMe(const uint256& nullifier) const; bool IsSaplingNullifierFromMe(const uint256& nullifier) const; diff --git a/src/zcash/Note.cpp b/src/zcash/Note.cpp index 5f8b439fb..23320c07a 100644 --- a/src/zcash/Note.cpp +++ b/src/zcash/Note.cpp @@ -1,6 +1,7 @@ #include "Note.hpp" #include "prf.h" #include "crypto/sha256.h" +#include "consensus/consensus.h" #include "random.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. -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; 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 boost::optional SaplingNote::cmu() const { uint256 result; + uint256 rcm_tmp = rcm(); if (!librustzcash_sapling_compute_cm( d.data(), pk_d.begin(), value(), - r.begin(), + rcm_tmp.begin(), result.begin() )) { @@ -71,11 +83,12 @@ boost::optional SaplingNote::nullifier(const SaplingFullViewingKey& vk, auto nk = vk.nk; uint256 result; + uint256 rcm_tmp = rcm(); if (!librustzcash_sapling_compute_nf( d.data(), pk_d.begin(), value(), - r.begin(), + rcm_tmp.begin(), ak.begin(), nk.begin(), position, @@ -145,7 +158,12 @@ SaplingNotePlaintext::SaplingNotePlaintext( std::array memo) : BaseNotePlaintext(note, memo) { 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 SaplingNotePlaintext::note(const SaplingIncomingVie { auto addr = ivk.address(d); 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 { return boost::none; } @@ -176,12 +199,9 @@ boost::optional SaplingOutgoingPlaintext::decrypt( try { CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); ss << pt.get(); - SaplingOutgoingPlaintext ret; ss >> ret; - assert(ss.size() == 0); - return ret; } catch (const boost::thread_interrupted&) { throw; @@ -191,14 +211,39 @@ boost::optional SaplingOutgoingPlaintext::decrypt( } boost::optional SaplingNotePlaintext::decrypt( + const Consensus::Params& params, + int height, const SaplingEncCiphertext &ciphertext, const uint256 &ivk, const uint256 &epk, const uint256 &cmu ) { - auto pt = AttemptSaplingEncDecryption(ciphertext, ivk, epk); - if (!pt) { + auto ret = attempt_sapling_enc_decryption_deserialization(ciphertext, ivk, epk); + + 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::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; } @@ -206,26 +251,36 @@ boost::optional SaplingNotePlaintext::decrypt( SaplingNotePlaintext ret; try { CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); - ss << pt.get(); + ss << encPlaintext.get(); ss >> ret; assert(ss.size() == 0); + return ret; } catch (const boost::thread_interrupted&) { throw; } catch (...) { return boost::none; } +} +boost::optional SaplingNotePlaintext::plaintext_checks_without_height( + const SaplingNotePlaintext &plaintext, + const uint256 &ivk, + const uint256 &epk, + const uint256 &cmu +) +{ 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; } uint256 cmu_expected; + uint256 rcm = plaintext.rcm(); if (!librustzcash_sapling_compute_cm( - ret.d.data(), + plaintext.d.data(), pk_d.begin(), - ret.value(), - ret.rcm.begin(), + plaintext.value(), + rcm.begin(), cmu_expected.begin() )) { @@ -236,10 +291,25 @@ boost::optional SaplingNotePlaintext::decrypt( 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::decrypt( + const Consensus::Params& params, + int height, const SaplingEncCiphertext &ciphertext, const uint256 &epk, const uint256 &esk, @@ -247,30 +317,74 @@ boost::optional SaplingNotePlaintext::decrypt( const uint256 &cmu ) { - auto pt = AttemptSaplingEncDecryption(ciphertext, epk, esk, pk_d); - if (!pt) { + auto ret = attempt_sapling_enc_decryption_deserialization(ciphertext, epk, esk, pk_d); + + if (!ret) { 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::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 SaplingNotePlaintext ret; try { CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); - ss << pt.get(); + ss << encPlaintext.get(); ss >> ret; assert(ss.size() == 0); + return ret; } catch (const boost::thread_interrupted&) { throw; } catch (...) { return boost::none; } +} + +boost::optional 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 rcm = plaintext.rcm(); if (!librustzcash_sapling_compute_cm( - ret.d.data(), + plaintext.d.data(), pk_d.begin(), - ret.value(), - ret.rcm.begin(), + plaintext.value(), + rcm.begin(), cmu_expected.begin() )) { @@ -281,13 +395,21 @@ boost::optional SaplingNotePlaintext::decrypt( 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 SaplingNotePlaintext::encrypt(const uint256& pk_d) const { // Get the encryptor - auto sne = SaplingNoteEncryption::FromDiversifier(d); + auto sne = SaplingNoteEncryption::FromDiversifier(d, generate_or_derive_esk()); if (!sne) { return boost::none; } @@ -325,3 +447,30 @@ SaplingOutCiphertext SaplingOutgoingPlaintext::encrypt( 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; + } +} diff --git a/src/zcash/Note.hpp b/src/zcash/Note.hpp index dee17bfe4..0e4cc1ccb 100644 --- a/src/zcash/Note.hpp +++ b/src/zcash/Note.hpp @@ -5,6 +5,8 @@ #include "Zcash.h" #include "Address.hpp" #include "NoteEncryption.hpp" +#include "consensus/params.h" +#include "consensus/consensus.h" #include #include @@ -40,24 +42,55 @@ public: 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 { +private: + uint256 rseed; + friend class SaplingNotePlaintext; + Zip212Enabled zip_212_enabled; public: diversifier_t d; uint256 pk_d; - uint256 r; - SaplingNote(diversifier_t d, uint256 pk_d, uint64_t value, uint256 r) - : BaseNote(value), d(d), pk_d(pk_d), r(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), rseed(rseed), zip_212_enabled(zip_212_enabled) {} - SaplingNote() {}; - - SaplingNote(const SaplingPaymentAddress &address, uint64_t value); + SaplingNote(const SaplingPaymentAddress &address, uint64_t value, Zip212Enabled zip_212_enabled); virtual ~SaplingNote() {}; boost::optional cmu() const; boost::optional nullifier(const SaplingFullViewingKey &vk, const uint64_t position) const; + uint256 rcm() const; + + Zip212Enabled get_zip_212_enabled() const { + return zip_212_enabled; + } }; class BaseNotePlaintext { @@ -91,10 +124,10 @@ public: template inline void SerializationOp(Stream& s, Operation ser_action) { - unsigned char leadingByte = 0x00; - READWRITE(leadingByte); + unsigned char leadbyte = 0x00; + READWRITE(leadbyte); - if (leadingByte != 0x00) { + if (leadbyte != 0x00) { throw std::ios_base::failure("lead byte of SproutNotePlaintext is not recognized"); } @@ -119,22 +152,41 @@ public: typedef std::pair SaplingNotePlaintextEncryptionResult; class SaplingNotePlaintext : public BaseNotePlaintext { +private: + uint256 rseed; + unsigned char leadbyte; public: diversifier_t d; - uint256 rcm; SaplingNotePlaintext() {} SaplingNotePlaintext(const SaplingNote& note, std::array memo); static boost::optional decrypt( + const Consensus::Params& params, + int height, const SaplingEncCiphertext &ciphertext, const uint256 &ivk, const uint256 &epk, const uint256 &cmu ); + static boost::optional plaintext_checks_without_height( + const SaplingNotePlaintext &plaintext, + const uint256 &ivk, + const uint256 &epk, + const uint256 &cmu + ); + + static boost::optional attempt_sapling_enc_decryption_deserialization( + const SaplingEncCiphertext &ciphertext, + const uint256 &ivk, + const uint256 &epk + ); + static boost::optional decrypt( + const Consensus::Params& params, + int height, const SaplingEncCiphertext &ciphertext, const uint256 &epk, const uint256 &esk, @@ -142,6 +194,21 @@ public: const uint256 &cmu ); + static boost::optional plaintext_checks_without_height( + const SaplingNotePlaintext &plaintext, + const uint256 &epk, + const uint256 &esk, + const uint256 &pk_d, + const uint256 &cmu + ); + + static boost::optional attempt_sapling_enc_decryption_deserialization( + const SaplingEncCiphertext &ciphertext, + const uint256 &epk, + const uint256 &esk, + const uint256 &pk_d + ); + boost::optional note(const SaplingIncomingViewingKey& ivk) const; virtual ~SaplingNotePlaintext() {} @@ -150,20 +217,25 @@ public: template inline void SerializationOp(Stream& s, Operation ser_action) { - unsigned char leadingByte = 0x01; - READWRITE(leadingByte); + READWRITE(leadbyte); - if (leadingByte != 0x01) { + if (leadbyte != 0x01 && leadbyte != 0x02) { throw std::ios_base::failure("lead byte of SaplingNotePlaintext is not recognized"); } READWRITE(d); // 11 bytes READWRITE(value_); // 8 bytes - READWRITE(rcm); // 32 bytes + READWRITE(rseed); // 32 bytes READWRITE(memo_); // 512 bytes } boost::optional encrypt(const uint256& pk_d) const; + + uint256 rcm() const; + uint256 generate_or_derive_esk() const; + unsigned char get_leadbyte() const { + return leadbyte; + } }; class SaplingOutgoingPlaintext diff --git a/src/zcash/NoteEncryption.cpp b/src/zcash/NoteEncryption.cpp index 63e073265..dee211cba 100644 --- a/src/zcash/NoteEncryption.cpp +++ b/src/zcash/NoteEncryption.cpp @@ -101,12 +101,12 @@ void KDF(unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE], namespace libzcash { -boost::optional SaplingNoteEncryption::FromDiversifier(diversifier_t d) { +boost::optional SaplingNoteEncryption::FromDiversifier( + diversifier_t d, + uint256 esk +) +{ uint256 epk; - uint256 esk; - - // Pick random esk - librustzcash_sapling_generate_r(esk.begin()); // Compute epk given the diversifier if (!librustzcash_sapling_ka_derivepublic(d.begin(), esk.begin(), epk.begin())) { diff --git a/src/zcash/NoteEncryption.hpp b/src/zcash/NoteEncryption.hpp index f6e692028..308d81dc1 100644 --- a/src/zcash/NoteEncryption.hpp +++ b/src/zcash/NoteEncryption.hpp @@ -42,7 +42,7 @@ protected: public: - static boost::optional FromDiversifier(diversifier_t d); + static boost::optional FromDiversifier(diversifier_t d, uint256 esk); boost::optional encrypt_to_recipient( const uint256 &pk_d, diff --git a/src/zcash/prf.cpp b/src/zcash/prf.cpp index 2491de83e..1aa8142d8 100644 --- a/src/zcash/prf.cpp +++ b/src/zcash/prf.cpp @@ -24,6 +24,22 @@ std::array PRF_expand(const uint256& sk, unsigned char t) 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 ask; diff --git a/src/zcash/prf.h b/src/zcash/prf.h index f666cfa23..b9256769a 100644 --- a/src/zcash/prf.h +++ b/src/zcash/prf.h @@ -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_nsk(const uint256& sk); uint256 PRF_ovk(const uint256& sk); +uint256 PRF_rcm(const uint256& rseed); +uint256 PRF_esk(const uint256& rseed); std::array default_diversifier(const uint256& sk); diff --git a/src/zcbenchmarks.cpp b/src/zcbenchmarks.cpp index cd728eceb..d66404814 100644 --- a/src/zcbenchmarks.cpp +++ b/src/zcbenchmarks.cpp @@ -307,7 +307,7 @@ double benchmark_try_decrypt_sapling_notes(size_t nKeys) struct timeval tv_start; timer_start(tv_start); - auto noteDataMapAndAddressesToAdd = wallet.FindMySaplingNotes(tx); + auto noteDataMapAndAddressesToAdd = wallet.FindMySaplingNotes(tx, 1); assert(noteDataMapAndAddressesToAdd.first.empty()); return timer_stop(tv_start); } @@ -594,7 +594,7 @@ double benchmark_create_sapling_spend() auto sk = libzcash::SaplingSpendingKey::random(); auto expsk = sk.expanded_spending_key(); auto address = sk.default_address(); - SaplingNote note(address, GetRand(MAX_MONEY)); + SaplingNote note(address, GetRand(MAX_MONEY), libzcash::Zip212Enabled::BeforeZip212); SaplingMerkleTree tree; auto maybe_cmu = note.cmu(); tree.append(maybe_cmu.get()); @@ -618,12 +618,13 @@ double benchmark_create_sapling_spend() timer_start(tv_start); SpendDescription sdesc; + uint256 rcm = note.rcm(); bool result = librustzcash_sapling_spend_proof( ctx, expsk.full_viewing_key().ak.begin(), expsk.nsk.begin(), note.d.data(), - note.r.begin(), + rcm.begin(), alpha.begin(), note.value(), anchor.begin(), @@ -646,7 +647,7 @@ double benchmark_create_sapling_output() auto address = sk.default_address(); std::array memo; - SaplingNote note(address, GetRand(MAX_MONEY)); + SaplingNote note(address, GetRand(MAX_MONEY), libzcash::Zip212Enabled::BeforeZip212); libzcash::SaplingNotePlaintext notePlaintext(note, memo); auto res = notePlaintext.encrypt(note.pk_d); @@ -667,11 +668,12 @@ double benchmark_create_sapling_output() timer_start(tv_start); OutputDescription odesc; + uint256 rcm = note.rcm(); bool result = librustzcash_sapling_output_proof( ctx, encryptor.get_esk().begin(), addressBytes.data(), - note.r.begin(), + rcm.begin(), note.value(), odesc.cv.begin(), odesc.zkproof.begin()); diff --git a/src/zmq/zmqnotificationinterface.cpp b/src/zmq/zmqnotificationinterface.cpp index e427e6b97..22c3b3901 100644 --- a/src/zmq/zmqnotificationinterface.cpp +++ b/src/zmq/zmqnotificationinterface.cpp @@ -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::iterator i = notifiers.begin(); i!=notifiers.end(); ) { diff --git a/src/zmq/zmqnotificationinterface.h b/src/zmq/zmqnotificationinterface.h index 1dc7211d5..fedff60ef 100644 --- a/src/zmq/zmqnotificationinterface.h +++ b/src/zmq/zmqnotificationinterface.h @@ -25,7 +25,7 @@ protected: void Shutdown(); // 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 BlockChecked(const CBlock& block, const CValidationState& state);