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")
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:

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(); ) {
AMQPAbstractNotifier *notifier = *i;

View File

@ -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:

View File

@ -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 {

View File

@ -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();

View File

@ -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<libzcash::Zip212Enabled> 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<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;
@ -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);
}

View File

@ -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);
}

View File

@ -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());

View File

@ -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");
}
}
}
}

View File

@ -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();

View File

@ -9,6 +9,7 @@
#include "rpc/protocol.h"
#include "script/sign.h"
#include "utilmoneystr.h"
#include "zcash/Note.hpp"
#include <boost/variant.hpp>
#include <librustzcash.h>
@ -43,11 +44,12 @@ boost::optional<OutputDescription> OutputDescriptionInfo::Build(void* ctx) {
std::vector<unsigned char> 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<unsigned char> 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(),

View File

@ -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);

View File

@ -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();

View File

@ -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) {

View File

@ -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<std::pair<SproutMerkleTree, SaplingMerkleTree>> added) {}
virtual void SetBestChain(const CBlockLocator &locator) {}
@ -52,7 +52,7 @@ struct CMainSignals {
/** Notifies listeners of updated block chain tip */
boost::signals2::signal<void (const CBlockIndex *)> UpdatedBlockTip;
/** 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). */
boost::signals2::signal<void (const uint256 &)> EraseTransaction;
/** Notifies listeners of an updated transaction without new data (for now: a coinbase potentially becoming visible). */

View File

@ -376,58 +376,65 @@ TEST(WalletTests, SetSproutNoteAddrsInCWalletTx) {
}
TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
auto consensusParams = RegtestActivateSapling();
SelectParams(CBaseChainParams::REGTEST);
TestWallet wallet;
LOCK(wallet.cs_wallet);
std::vector<libzcash::Zip212Enabled> 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<libzcash::Zip212Enabled> 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<bool>(maybe_pt), true);
auto maybe_note = maybe_pt.get().note(ivk);
ASSERT_EQ(static_cast<bool>(maybe_note), true);
auto note2 = maybe_note.get();
// 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<bool>(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<bool>(maybe_pt), true);
auto maybe_note = maybe_pt.get().note(ivk);
ASSERT_EQ(static_cast<bool>(maybe_note), true);
auto note2 = maybe_note.get();
// Create transaction to spend note B
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<bool>(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<uint256>({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<uint256>({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<libzcash::Zip212Enabled> 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<bool>(maybe_pt), true);
auto maybe_note = maybe_pt.get().note(ivk);
ASSERT_EQ(static_cast<bool>(maybe_note), true);
auto note2 = maybe_note.get();
// 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<bool>(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<bool>(maybe_pt), true);
auto maybe_note = maybe_pt.get().note(ivk);
ASSERT_EQ(static_cast<bool>(maybe_note), true);
auto note2 = maybe_note.get();
// Create transaction to spend note B
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<bool>(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<bool>(maybe_pt), true);
auto maybe_note = maybe_pt.get().note(ivk);
ASSERT_EQ(static_cast<bool>(maybe_note), true);

View File

@ -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;

View File

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

View File

@ -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<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);
uint256 hash = tx.GetHash();
@ -1901,7 +1905,8 @@ std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> 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<SproutNotePlaintext, SproutPaymentAddress> CWalletTx::DecryptSproutNot
boost::optional<std::pair<
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
if (this->mapSaplingNoteData.count(op) == 0) {
@ -2311,11 +2316,46 @@ boost::optional<std::pair<
auto nd = this->mapSaplingNoteData.at(op);
auto maybe_pt = SaplingNotePlaintext::decrypt(
params,
height,
output.encCiphertext,
nd.ivk,
output.ephemeralKey,
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 maybe_pa = nd.ivk.address(notePt.d);
@ -2327,8 +2367,7 @@ boost::optional<std::pair<
boost::optional<std::pair<
SaplingNotePlaintext,
SaplingPaymentAddress>> CWalletTx::RecoverSaplingNote(
SaplingOutPoint op, std::set<uint256>& ovks) const
SaplingPaymentAddress>> CWalletTx::RecoverSaplingNote(const Consensus::Params& params, int height, SaplingOutPoint op, std::set<uint256>& ovks) const
{
auto output = this->vShieldedOutput[op.n];
@ -2340,10 +2379,13 @@ boost::optional<std::pair<
output.cmu,
output.ephemeralKey);
if (!outPt) {
// Try decrypting with the next ovk
continue;
}
auto maybe_pt = SaplingNotePlaintext::decrypt(
params,
height,
output.encCiphertext,
output.ephemeralKey,
outPt->esk,
@ -2359,6 +2401,46 @@ boost::optional<std::pair<
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 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<bool>(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<bool>(maybe_pa));
auto pa = maybe_pa.get();

View File

@ -566,11 +566,17 @@ public:
JSOutPoint jsop) const;
boost::optional<std::pair<
libzcash::SaplingNotePlaintext,
libzcash::SaplingPaymentAddress>> DecryptSaplingNote(SaplingOutPoint op) const;
libzcash::SaplingPaymentAddress>> DecryptSaplingNote(const Consensus::Params& params, int height, SaplingOutPoint op) const;
boost::optional<std::pair<
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;
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
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<uint256> commitments,
@ -1221,7 +1227,7 @@ public:
const uint256& hSig,
uint8_t n) 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 IsSaplingNullifierFromMe(const uint256& nullifier) const;

View File

@ -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<uint256> 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<uint256> 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<unsigned char, ZC_MEMO_SIZE> 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<SaplingNote> 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> 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> SaplingOutgoingPlaintext::decrypt(
}
boost::optional<SaplingNotePlaintext> 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> 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> 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> 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> 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> SaplingNotePlaintext::decrypt(
const Consensus::Params& params,
int height,
const SaplingEncCiphertext &ciphertext,
const uint256 &epk,
const uint256 &esk,
@ -247,30 +317,74 @@ boost::optional<SaplingNotePlaintext> 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> 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> 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> 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<SaplingNotePlaintextEncryptionResult> 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;
}
}

View File

@ -5,6 +5,8 @@
#include "Zcash.h"
#include "Address.hpp"
#include "NoteEncryption.hpp"
#include "consensus/params.h"
#include "consensus/consensus.h"
#include <array>
#include <boost/optional.hpp>
@ -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<uint256> cmu() 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 {
@ -91,10 +124,10 @@ public:
template <typename Stream, typename Operation>
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<SaplingEncCiphertext, SaplingNoteEncryption> SaplingNotePlaintextEncryptionResult;
class SaplingNotePlaintext : public BaseNotePlaintext {
private:
uint256 rseed;
unsigned char leadbyte;
public:
diversifier_t d;
uint256 rcm;
SaplingNotePlaintext() {}
SaplingNotePlaintext(const SaplingNote& note, std::array<unsigned char, ZC_MEMO_SIZE> memo);
static boost::optional<SaplingNotePlaintext> decrypt(
const Consensus::Params& params,
int height,
const SaplingEncCiphertext &ciphertext,
const uint256 &ivk,
const uint256 &epk,
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(
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<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;
virtual ~SaplingNotePlaintext() {}
@ -150,20 +217,25 @@ public:
template <typename Stream, typename Operation>
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<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

View File

@ -101,12 +101,12 @@ void KDF(unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE],
namespace libzcash {
boost::optional<SaplingNoteEncryption> SaplingNoteEncryption::FromDiversifier(diversifier_t d) {
boost::optional<SaplingNoteEncryption> 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())) {

View File

@ -42,7 +42,7 @@ protected:
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(
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;
}
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;

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_nsk(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);

View File

@ -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<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);
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());

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(); )
{

View File

@ -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);