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:
commit
701adc38cb
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
21
src/main.cpp
21
src/main.cpp
|
@ -920,19 +920,34 @@ bool ContextualCheckTransaction(
|
|||
}
|
||||
|
||||
// SaplingNotePlaintext::decrypt() checks note commitment validity.
|
||||
if (!SaplingNotePlaintext::decrypt(
|
||||
|
||||
auto encPlaintext = SaplingNotePlaintext::decrypt(
|
||||
chainparams.GetConsensus(),
|
||||
nHeight,
|
||||
output.encCiphertext,
|
||||
output.ephemeralKey,
|
||||
outPlaintext->esk,
|
||||
outPlaintext->pk_d,
|
||||
output.cmu)
|
||||
) {
|
||||
output.cmu);
|
||||
if (!encPlaintext) {
|
||||
return state.DoS(
|
||||
DOS_LEVEL_BLOCK,
|
||||
error("CheckTransaction(): coinbase output description has invalid encCiphertext"),
|
||||
REJECT_INVALID,
|
||||
"bad-cb-output-desc-invalid-encct");
|
||||
}
|
||||
|
||||
// ZIP 212: Check that the note plaintexts use the v2 note plaintext
|
||||
// version.
|
||||
// This check compels miners to switch to the new plaintext version
|
||||
// and overrides the grace period in plaintext_version_is_valid()
|
||||
if (canopyActive != (encPlaintext->get_leadbyte() == 0x02)) {
|
||||
return state.DoS(
|
||||
DOS_LEVEL_BLOCK,
|
||||
error("CheckTransaction(): coinbase output description has invalid note plaintext version"),
|
||||
REJECT_INVALID,
|
||||
"bad-cb-output-desc-invalid-note-plaintext-version");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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). */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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(); )
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue