Auto merge of #4578 - therealyingtong:zip212-impl, r=str4d

ZIP212 implementation

Closes #4557.
(description by @ebfull, taken from #4575)

* The `SaplingNote` structure has a new enum called `zip212Enabled`. This
  member is private and reflects whether the note was or is being created
  using the derivation method of ZIP 212 (i.e., `BeforeZip212` or `AfterZip212`).
* The `SaplingNotePlaintext` structure has a new unsigned char member
  `leadbyte`. This member is private and contains the leading byte of the
  plaintext (e.g. `0x01`, `0x02`).
* The serialization of `SaplingNotePlaintext` sets `zip212Enabled` to
  `BeforeZip212` iff the serialized note plaintext version is not `0x01`.
* The `r`/`rcm` fields have been removed and replaced with a private field
  `rseed`. `SaplingNote` and `SaplingNotePlaintext` now have a helper method
  `rcm()` which returns the `rcm` either by deriving it with `rseed`
  (if `zip212Enabled` is `AfterZip212`) or returning `rseed` by interpreting
  `rseed` as `rcm`.
* All the methods of obtaining a `SaplingNote` account for these changes:
  - The `SaplingNote` constructor that is used by e.g. the transaction builder,
    and internally samples random `rcm`, now takes a `zip212Enabled` argument
    to decide whether to sample `rcm` the "old" way or the "new" way.
  - The bare constructor for `SaplingNote` is removed.
  - The other constructor which takes the raw contents of the note is only used
    in tests or in `Note.cpp`, but now also takes a `zip212Enabled` argument.
  - The other way of obtaining a note, by calling `SaplingNotePlaintext::note()`,
    has been adjusted.
* The `SaplingNotePlaintext` class now has an `generate_or_derive_esk()` method
  that either samples a random `esk` or derives it using the local `rseed`
  depending on the value of `leadbyte`.
* The encryption routine is modified to consult `generate_or_derive_esk()` and
  provide it to the note encryption object.
* The note encryption objects now take an optional `esk` as input and otherwise
  sample a random `esk` internally. This API functionality is preserved to allow
  for testing.
* The `SaplingNotePlaintext` decryption routines are modified:
  - The out and enc decryption routines now check that `epk` is consistent with
    the derived `esk`.
  -  The out decryption routine for plaintexts also checks that `esk` is
    consistent with what is derived by the note.
* The miner and transaction builder consult the activation of Canopy when
  creating `SaplingNote`s.
* The consensus rules are modified so that shielded outputs (miner rewards)
  must have `v2` note plaintexts after Canopy has activated.
This commit is contained in:
Homu 2020-07-09 00:29:07 +00:00
commit 701adc38cb
29 changed files with 1140 additions and 488 deletions

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,7 +42,10 @@ 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 params = (*activations[ver])();
SaplingNote note(addr, 39393, zip_212_enabled[ver]);
auto cmu_opt = note.cmu(); auto cmu_opt = note.cmu();
if (!cmu_opt) { if (!cmu_opt) {
FAIL(); FAIL();
@ -55,6 +66,8 @@ TEST(noteencryption, NotePlaintext)
// Try to decrypt with incorrect commitment // Try to decrypt with incorrect commitment
ASSERT_FALSE(SaplingNotePlaintext::decrypt( ASSERT_FALSE(SaplingNotePlaintext::decrypt(
params,
1,
ct, ct,
ivk, ivk,
epk, epk,
@ -63,6 +76,8 @@ TEST(noteencryption, NotePlaintext)
// Try to decrypt with correct commitment // Try to decrypt with correct commitment
auto foo = SaplingNotePlaintext::decrypt( auto foo = SaplingNotePlaintext::decrypt(
params,
1,
ct, ct,
ivk, ivk,
epk, epk,
@ -78,7 +93,7 @@ TEST(noteencryption, NotePlaintext)
ASSERT_TRUE(bar.value() == pt.value()); ASSERT_TRUE(bar.value() == pt.value());
ASSERT_TRUE(bar.memo() == pt.memo()); ASSERT_TRUE(bar.memo() == pt.memo());
ASSERT_TRUE(bar.d == pt.d); ASSERT_TRUE(bar.d == pt.d);
ASSERT_TRUE(bar.rcm == pt.rcm); ASSERT_TRUE(bar.rcm() == pt.rcm());
auto foobar = bar.note(ivk); auto foobar = bar.note(ivk);
@ -91,7 +106,7 @@ TEST(noteencryption, NotePlaintext)
ASSERT_TRUE(note.value() == new_note.value()); ASSERT_TRUE(note.value() == new_note.value());
ASSERT_TRUE(note.d == new_note.d); ASSERT_TRUE(note.d == new_note.d);
ASSERT_TRUE(note.pk_d == new_note.pk_d); ASSERT_TRUE(note.pk_d == new_note.pk_d);
ASSERT_TRUE(note.r == new_note.r); ASSERT_TRUE(note.rcm() == new_note.rcm());
ASSERT_TRUE(note.cmu() == new_note.cmu()); ASSERT_TRUE(note.cmu() == new_note.cmu());
SaplingOutgoingPlaintext out_pt; SaplingOutgoingPlaintext out_pt;
@ -129,6 +144,8 @@ TEST(noteencryption, NotePlaintext)
// Test sender won't accept invalid commitments // Test sender won't accept invalid commitments
ASSERT_FALSE( ASSERT_FALSE(
SaplingNotePlaintext::decrypt( SaplingNotePlaintext::decrypt(
params,
1,
ct, ct,
epk, epk,
decrypted_out_ct_unwrapped.esk, decrypted_out_ct_unwrapped.esk,
@ -139,6 +156,8 @@ TEST(noteencryption, NotePlaintext)
// Test sender can decrypt the note ciphertext. // Test sender can decrypt the note ciphertext.
foo = SaplingNotePlaintext::decrypt( foo = SaplingNotePlaintext::decrypt(
params,
1,
ct, ct,
epk, epk,
decrypted_out_ct_unwrapped.esk, decrypted_out_ct_unwrapped.esk,
@ -155,10 +174,247 @@ TEST(noteencryption, NotePlaintext)
ASSERT_TRUE(bar.value() == pt.value()); ASSERT_TRUE(bar.value() == pt.value());
ASSERT_TRUE(bar.memo() == pt.memo()); ASSERT_TRUE(bar.memo() == pt.memo());
ASSERT_TRUE(bar.d == pt.d); ASSERT_TRUE(bar.d == pt.d);
ASSERT_TRUE(bar.rcm == pt.rcm); ASSERT_TRUE(bar.rcm() == pt.rcm());
(*deactivations[ver])();
}
} }
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

@ -920,19 +920,34 @@ bool ContextualCheckTransaction(
} }
// SaplingNotePlaintext::decrypt() checks note commitment validity. // SaplingNotePlaintext::decrypt() checks note commitment validity.
if (!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 (!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"),
REJECT_INVALID, REJECT_INVALID,
"bad-cb-output-desc-invalid-encct"); "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");
}
} }
} }
} }

View File

@ -157,7 +157,11 @@ public:
mtx.valueBalance = -value; mtx.valueBalance = -value;
uint256 ovk; 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 output = OutputDescriptionInfo(ovk, note, {{0xF6}});
auto ctx = librustzcash_sapling_proving_ctx_init(); auto ctx = librustzcash_sapling_proving_ctx_init();

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,7 +376,14 @@ TEST(WalletTests, SetSproutNoteAddrsInCWalletTx) {
} }
TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) { TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
auto consensusParams = RegtestActivateSapling(); 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};
for (int ver = 0; ver < zip_212_enabled.size(); ver++) {
auto consensusParams = (*activations[ver])();
TestWallet wallet; TestWallet wallet;
LOCK(wallet.cs_wallet); LOCK(wallet.cs_wallet);
@ -387,7 +394,7 @@ TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
auto ivk = fvk.in_viewing_key(); auto ivk = fvk.in_viewing_key();
auto pk = sk.DefaultAddress(); auto pk = sk.DefaultAddress();
libzcash::SaplingNote note(pk, 50000); libzcash::SaplingNote note(pk, 50000, zip_212_enabled[ver]);
auto cm = note.cmu().get(); auto cm = note.cmu().get();
SaplingMerkleTree tree; SaplingMerkleTree tree;
tree.append(cm); tree.append(cm);
@ -426,8 +433,8 @@ TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
EXPECT_EQ(nd.witnessHeight, wtx.mapSaplingNoteData[op].witnessHeight); EXPECT_EQ(nd.witnessHeight, wtx.mapSaplingNoteData[op].witnessHeight);
EXPECT_TRUE(witness == wtx.mapSaplingNoteData[op].witnesses.front()); EXPECT_TRUE(witness == wtx.mapSaplingNoteData[op].witnesses.front());
// Revert to default (*deactivations[ver])();
RegtestDeactivateSapling(); }
} }
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,7 +645,14 @@ 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);
std::vector<libzcash::Zip212Enabled> zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212};
const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy};
void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy};
for (int ver = 0; ver < zip_212_enabled.size(); ver++) {
auto consensusParams = (*activations[ver])();
TestWallet wallet; TestWallet wallet;
LOCK2(cs_main, wallet.cs_wallet); LOCK2(cs_main, wallet.cs_wallet);
@ -654,7 +668,7 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk)); ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk));
// Generate note A // Generate note A
libzcash::SaplingNote note(pk, 50000); libzcash::SaplingNote note(pk, 50000, zip_212_enabled[ver]);
auto cm = note.cmu().get(); auto cm = note.cmu().get();
SaplingMerkleTree saplingTree; SaplingMerkleTree saplingTree;
saplingTree.append(cm); saplingTree.append(cm);
@ -682,7 +696,7 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
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, 1).first;
ASSERT_TRUE(saplingNoteData.size() > 0); ASSERT_TRUE(saplingNoteData.size() > 0);
wtx.SetSaplingNoteData(saplingNoteData); wtx.SetSaplingNoteData(saplingNoteData);
wtx.SetMerkleBranch(block); wtx.SetMerkleBranch(block);
@ -698,6 +712,8 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
// Decrypt output note B // Decrypt output note B
auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt( auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt(
consensusParams,
wtx.nExpiryHeight,
wtx.vShieldedOutput[0].encCiphertext, wtx.vShieldedOutput[0].encCiphertext,
ivk, ivk,
wtx.vShieldedOutput[0].ephemeralKey, wtx.vShieldedOutput[0].ephemeralKey,
@ -751,8 +767,8 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
chainActive.SetTip(NULL); chainActive.SetTip(NULL);
mapBlockIndex.erase(blockHash); mapBlockIndex.erase(blockHash);
// Revert to default (*deactivations[ver])();
RegtestDeactivateSapling(); }
} }
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,7 +1021,14 @@ 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);
std::vector<libzcash::Zip212Enabled> zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212};
const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy};
void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy};
for (int ver = 0; ver < zip_212_enabled.size(); ver++) {
auto consensusParams = (*activations[ver])();
TestWallet wallet; TestWallet wallet;
LOCK2(cs_main, wallet.cs_wallet); LOCK2(cs_main, wallet.cs_wallet);
@ -1018,7 +1041,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
auto pk = sk.DefaultAddress(); auto pk = sk.DefaultAddress();
// Generate Sapling note A // Generate Sapling note A
libzcash::SaplingNote note(pk, 50000); libzcash::SaplingNote note(pk, 50000, zip_212_enabled[ver]);
auto cm = note.cmu().get(); auto cm = note.cmu().get();
SaplingMerkleTree saplingTree; SaplingMerkleTree saplingTree;
saplingTree.append(cm); saplingTree.append(cm);
@ -1048,7 +1071,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
EXPECT_TRUE(chainActive.Contains(&fakeIndex)); EXPECT_TRUE(chainActive.Contains(&fakeIndex));
EXPECT_EQ(0, chainActive.Height()); EXPECT_EQ(0, chainActive.Height());
auto saplingNoteData = wallet.FindMySaplingNotes(wtx).first; auto saplingNoteData = wallet.FindMySaplingNotes(wtx, 1).first;
ASSERT_TRUE(saplingNoteData.size() > 0); ASSERT_TRUE(saplingNoteData.size() > 0);
wtx.SetSaplingNoteData(saplingNoteData); wtx.SetSaplingNoteData(saplingNoteData);
wtx.SetMerkleBranch(block); wtx.SetMerkleBranch(block);
@ -1075,6 +1098,8 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
// Decrypt note B // Decrypt note B
auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt( auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt(
consensusParams,
wtx.nExpiryHeight,
wtx.vShieldedOutput[0].encCiphertext, wtx.vShieldedOutput[0].encCiphertext,
ivk, ivk,
wtx.vShieldedOutput[0].ephemeralKey, wtx.vShieldedOutput[0].ephemeralKey,
@ -1123,7 +1148,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
EXPECT_TRUE(chainActive.Contains(&fakeIndex2)); EXPECT_TRUE(chainActive.Contains(&fakeIndex2));
EXPECT_EQ(1, chainActive.Height()); EXPECT_EQ(1, chainActive.Height());
auto saplingNoteData2 = wallet.FindMySaplingNotes(wtx2).first; auto saplingNoteData2 = wallet.FindMySaplingNotes(wtx2, 2).first;
ASSERT_TRUE(saplingNoteData2.size() > 0); ASSERT_TRUE(saplingNoteData2.size() > 0);
wtx2.SetSaplingNoteData(saplingNoteData2); wtx2.SetSaplingNoteData(saplingNoteData2);
wtx2.SetMerkleBranch(block2); wtx2.SetMerkleBranch(block2);
@ -1141,8 +1166,8 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
mapBlockIndex.erase(blockHash); mapBlockIndex.erase(blockHash);
mapBlockIndex.erase(blockHash2); mapBlockIndex.erase(blockHash2);
// Revert to default (*deactivations[ver])();
RegtestDeactivateSapling(); }
} }
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);
// 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, // An item in mapSaplingNoteData must have already been successfully decrypted,
// otherwise the item would not exist in the first place. // otherwise the item would not exist in the first place.
assert(false); 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(false); assert(optNullifier != boost::none);
}
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;
} 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 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,55 @@ 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 +124,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 +152,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 +194,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 +217,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);