Adds basic unified spending key derivation.

Also, begin pruning Sapling key derivation down to the
minimal amount required to support the legacy Sapling
key derivation process.
This commit is contained in:
Kris Nuttycombe 2021-09-21 11:17:33 -06:00
parent 8883ae8b9e
commit 83dee7e886
18 changed files with 271 additions and 166 deletions

View File

@ -48,7 +48,7 @@ TEST(Keys, EncodeAndDecodeSapling)
EXPECT_EQ(extfvk, extfvk2); EXPECT_EQ(extfvk, extfvk2);
} }
{ {
auto addr = sk.DefaultAddress(); auto addr = sk.ToXFVK().DefaultAddress();
std::string addr_string = keyIO.EncodePaymentAddress(addr); std::string addr_string = keyIO.EncodePaymentAddress(addr);
EXPECT_EQ( EXPECT_EQ(

View File

@ -14,6 +14,8 @@
#define MAKE_STRING(x) std::string((x), (x)+sizeof(x)) #define MAKE_STRING(x) std::string((x), (x)+sizeof(x))
const uint32_t BIP44_TESTNET_TYPE = 1;
TEST(KeystoreTests, StoreAndRetrieveHDSeed) { TEST(KeystoreTests, StoreAndRetrieveHDSeed) {
CBasicKeyStore keyStore; CBasicKeyStore keyStore;
@ -23,7 +25,7 @@ TEST(KeystoreTests, StoreAndRetrieveHDSeed) {
EXPECT_FALSE(seedOut.has_value()); EXPECT_FALSE(seedOut.has_value());
// Generate a random seed // Generate a random seed
auto seed = MnemonicSeed::Random(); auto seed = MnemonicSeed::Random(BIP44_TESTNET_TYPE);
// We should be able to set and retrieve the seed // We should be able to set and retrieve the seed
ASSERT_TRUE(keyStore.SetMnemonicSeed(seed)); ASSERT_TRUE(keyStore.SetMnemonicSeed(seed));
@ -33,7 +35,7 @@ TEST(KeystoreTests, StoreAndRetrieveHDSeed) {
EXPECT_EQ(seed, seedOut.value()); EXPECT_EQ(seed, seedOut.value());
// Generate another random seed // Generate another random seed
auto seed2 = MnemonicSeed::Random(); auto seed2 = MnemonicSeed::Random(BIP44_TESTNET_TYPE);
EXPECT_NE(seed, seed2); EXPECT_NE(seed, seed2);
// We should not be able to set and retrieve a different seed // We should not be able to set and retrieve a different seed
@ -203,7 +205,7 @@ TEST(KeystoreTests, StoreAndRetrieveSaplingSpendingKey) {
auto sk = GetTestMasterSaplingSpendingKey(); auto sk = GetTestMasterSaplingSpendingKey();
auto extfvk = sk.ToXFVK(); auto extfvk = sk.ToXFVK();
auto ivk = extfvk.fvk.in_viewing_key(); auto ivk = extfvk.fvk.in_viewing_key();
auto addr = sk.DefaultAddress(); auto addr = sk.ToXFVK().DefaultAddress();
// Sanity-check: we can't get a key we haven't added // Sanity-check: we can't get a key we haven't added
EXPECT_FALSE(keyStore.HaveSaplingSpendingKey(extfvk)); EXPECT_FALSE(keyStore.HaveSaplingSpendingKey(extfvk));
@ -237,7 +239,7 @@ TEST(KeystoreTests, StoreAndRetrieveSaplingFullViewingKey) {
auto sk = GetTestMasterSaplingSpendingKey(); auto sk = GetTestMasterSaplingSpendingKey();
auto extfvk = sk.ToXFVK(); auto extfvk = sk.ToXFVK();
auto ivk = extfvk.fvk.in_viewing_key(); auto ivk = extfvk.fvk.in_viewing_key();
auto addr = sk.DefaultAddress(); auto addr = sk.ToXFVK().DefaultAddress();
// Sanity-check: we can't get a full viewing key we haven't added // Sanity-check: we can't get a full viewing key we haven't added
EXPECT_FALSE(keyStore.HaveSaplingFullViewingKey(ivk)); EXPECT_FALSE(keyStore.HaveSaplingFullViewingKey(ivk));
@ -289,7 +291,7 @@ TEST(KeystoreTests, StoreAndRetrieveHDSeedInEncryptedStore) {
GetRandBytes(vMasterKey.data(), 32); GetRandBytes(vMasterKey.data(), 32);
// 1) Test adding a seed to an unencrypted key store, then encrypting it // 1) Test adding a seed to an unencrypted key store, then encrypting it
auto seed = MnemonicSeed::Random(); auto seed = MnemonicSeed::Random(BIP44_TESTNET_TYPE);
EXPECT_FALSE(keyStore.HaveMnemonicSeed()); EXPECT_FALSE(keyStore.HaveMnemonicSeed());
auto seedOut = keyStore.GetMnemonicSeed(); auto seedOut = keyStore.GetMnemonicSeed();
EXPECT_FALSE(seedOut.has_value()); EXPECT_FALSE(seedOut.has_value());
@ -321,7 +323,7 @@ TEST(KeystoreTests, StoreAndRetrieveHDSeedInEncryptedStore) {
EXPECT_EQ(seed, seedOut.value()); EXPECT_EQ(seed, seedOut.value());
// 2) Test replacing the seed in an already-encrypted key store fails // 2) Test replacing the seed in an already-encrypted key store fails
auto seed2 = MnemonicSeed::Random(); auto seed2 = MnemonicSeed::Random(BIP44_TESTNET_TYPE);
EXPECT_FALSE(keyStore.SetMnemonicSeed(seed2)); EXPECT_FALSE(keyStore.SetMnemonicSeed(seed2));
EXPECT_TRUE(keyStore.HaveMnemonicSeed()); EXPECT_TRUE(keyStore.HaveMnemonicSeed());
seedOut = keyStore.GetMnemonicSeed(); seedOut = keyStore.GetMnemonicSeed();
@ -341,7 +343,7 @@ TEST(KeystoreTests, StoreAndRetrieveHDSeedInEncryptedStore) {
seedOut = keyStore2.GetMnemonicSeed(); seedOut = keyStore2.GetMnemonicSeed();
EXPECT_FALSE(seedOut.has_value()); EXPECT_FALSE(seedOut.has_value());
auto seed3 = MnemonicSeed::Random(); auto seed3 = MnemonicSeed::Random(BIP44_TESTNET_TYPE);
ASSERT_TRUE(keyStore2.SetMnemonicSeed(seed3)); ASSERT_TRUE(keyStore2.SetMnemonicSeed(seed3));
EXPECT_TRUE(keyStore2.HaveMnemonicSeed()); EXPECT_TRUE(keyStore2.HaveMnemonicSeed());
seedOut = keyStore2.GetMnemonicSeed(); seedOut = keyStore2.GetMnemonicSeed();

View File

@ -274,23 +274,31 @@ bool CKey::Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const
return ret; return ret;
} }
bool CExtKey::Derive(CExtKey &out, unsigned int _nChild) const { std::optional<CExtKey> CExtKey::Derive(unsigned int _nChild) const {
CExtKey out;
out.nDepth = nDepth + 1; out.nDepth = nDepth + 1;
CKeyID id = key.GetPubKey().GetID(); CKeyID id = key.GetPubKey().GetID();
memcpy(&out.vchFingerprint[0], &id, 4); memcpy(&out.vchFingerprint[0], &id, 4);
out.nChild = _nChild; out.nChild = _nChild;
return key.Derive(out.key, out.chaincode, _nChild, chaincode); if (key.Derive(out.key, out.chaincode, _nChild, chaincode)) {
return out;
} else {
return std::nullopt;
}
} }
void CExtKey::SetMaster(const unsigned char *seed, unsigned int nSeedLen) { CExtKey CExtKey::Master(const unsigned char *seed, unsigned int nSeedLen) {
CExtKey xk;
static const unsigned char hashkey[] = {'B','i','t','c','o','i','n',' ','s','e','e','d'}; static const unsigned char hashkey[] = {'B','i','t','c','o','i','n',' ','s','e','e','d'};
std::vector<unsigned char, secure_allocator<unsigned char>> vout(64); std::vector<unsigned char, secure_allocator<unsigned char>> vout(64);
CHMAC_SHA512(hashkey, sizeof(hashkey)).Write(seed, nSeedLen).Finalize(vout.data()); CHMAC_SHA512(hashkey, sizeof(hashkey)).Write(seed, nSeedLen).Finalize(vout.data());
key.Set(vout.data(), vout.data() + 32, true); xk.key.Set(vout.data(), vout.data() + 32, true);
memcpy(chaincode.begin(), vout.data() + 32, 32); memcpy(xk.chaincode.begin(), vout.data() + 32, 32);
nDepth = 0; xk.nDepth = 0;
nChild = 0; xk.nChild = 0;
memset(vchFingerprint, 0, sizeof(vchFingerprint)); memset(xk.vchFingerprint, 0, sizeof(xk.vchFingerprint));
return xk;
} }
CExtPubKey CExtKey::Neuter() const { CExtPubKey CExtKey::Neuter() const {

View File

@ -15,6 +15,12 @@
#include <stdexcept> #include <stdexcept>
#include <vector> #include <vector>
/**
* The account identifier used for HD derivation of the transparent
* p2pkh public key from which all child transparent addresses are
* derived in accordance with ZIP-316.
*/
const uint32_t ZCASH_LEGACY_TRANSPARENT_ACCOUNT = 0x7FFFFFFE;
/** /**
* secure_allocator is defined in allocators.h * secure_allocator is defined in allocators.h
@ -62,6 +68,8 @@ public:
keydata.resize(32); keydata.resize(32);
} }
static std::optional<CKey> FromEntropy(std::vector<unsigned char, secure_allocator<unsigned char>> keydata);
friend bool operator==(const CKey& a, const CKey& b) friend bool operator==(const CKey& a, const CKey& b)
{ {
return a.fCompressed == b.fCompressed && return a.fCompressed == b.fCompressed &&
@ -160,11 +168,12 @@ struct CExtKey {
a.key == b.key; a.key == b.key;
} }
static CExtKey Master(const unsigned char* seed, unsigned int nSeedLen);
void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const; void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const;
void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]); void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);
bool Derive(CExtKey& out, unsigned int nChild) const; std::optional<CExtKey> Derive(unsigned int nChild) const;
CExtPubKey Neuter() const; CExtPubKey Neuter() const;
void SetMaster(const unsigned char* seed, unsigned int nSeedLen);
template <typename Stream> template <typename Stream>
void Serialize(Stream& s) const void Serialize(Stream& s) const
{ {

View File

@ -80,10 +80,8 @@ TestVector test2 =
void RunTest(const TestVector &test) { void RunTest(const TestVector &test) {
std::vector<unsigned char> seed = ParseHex(test.strHexMaster); std::vector<unsigned char> seed = ParseHex(test.strHexMaster);
CExtKey key; CExtKey key = CExtKey::Master(&seed[0], seed.size());
CExtPubKey pubkey; CExtPubKey pubkey = key.Neuter();
key.SetMaster(&seed[0], seed.size());
pubkey = key.Neuter();
KeyIO keyIO(Params()); KeyIO keyIO(Params());
for (const TestDerivation &derive : test.vDerive) { for (const TestDerivation &derive : test.vDerive) {
unsigned char data[74]; unsigned char data[74];
@ -99,16 +97,16 @@ void RunTest(const TestVector &test) {
BOOST_CHECK(keyIO.DecodeExtPubKey(derive.pub) == pubkey); //ensure a base58 decoded pubkey also matches BOOST_CHECK(keyIO.DecodeExtPubKey(derive.pub) == pubkey); //ensure a base58 decoded pubkey also matches
// Derive new keys // Derive new keys
CExtKey keyNew; auto keyNew = key.Derive(derive.nChild);
BOOST_CHECK(key.Derive(keyNew, derive.nChild)); BOOST_CHECK(keyNew.has_value());
CExtPubKey pubkeyNew = keyNew.Neuter(); CExtPubKey pubkeyNew = keyNew.value().Neuter();
if (!(derive.nChild & 0x80000000)) { if (!(derive.nChild & 0x80000000)) {
// Compare with public derivation // Compare with public derivation
CExtPubKey pubkeyNew2; CExtPubKey pubkeyNew2;
BOOST_CHECK(pubkey.Derive(pubkeyNew2, derive.nChild)); BOOST_CHECK(pubkey.Derive(pubkeyNew2, derive.nChild));
BOOST_CHECK(pubkeyNew == pubkeyNew2); BOOST_CHECK(pubkeyNew == pubkeyNew2);
} }
key = keyNew; key = keyNew.value();
pubkey = pubkeyNew; pubkey = pubkeyNew;
CDataStream ssPub(SER_DISK, CLIENT_VERSION); CDataStream ssPub(SER_DISK, CLIENT_VERSION);
@ -116,7 +114,7 @@ void RunTest(const TestVector &test) {
BOOST_CHECK(ssPub.size() == 75); BOOST_CHECK(ssPub.size() == 75);
CDataStream ssPriv(SER_DISK, CLIENT_VERSION); CDataStream ssPriv(SER_DISK, CLIENT_VERSION);
ssPriv << keyNew; ssPriv << keyNew.value();
BOOST_CHECK(ssPriv.size() == 75); BOOST_CHECK(ssPriv.size() == 75);
CExtPubKey pubCheck; CExtPubKey pubCheck;

View File

@ -247,7 +247,7 @@ BOOST_AUTO_TEST_CASE(zs_address_test)
BOOST_CHECK(sk == sk2); BOOST_CHECK(sk == sk2);
} }
{ {
auto addr = sk.DefaultAddress(); auto addr = sk.ToXFVK().DefaultAddress();
std::string addr_string = keyIO.EncodePaymentAddress(addr); std::string addr_string = keyIO.EncodePaymentAddress(addr);
BOOST_CHECK(addr_string.compare(0, 15, Params().Bech32HRP(CChainParams::SAPLING_PAYMENT_ADDRESS)) == 0); BOOST_CHECK(addr_string.compare(0, 15, Params().Bech32HRP(CChainParams::SAPLING_PAYMENT_ADDRESS)) == 0);

View File

@ -346,7 +346,7 @@ CWalletTx GetValidSaplingReceive(const Consensus::Params& consensusParams,
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID()); auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
// To zaddr // To zaddr
auto fvk = sk.expsk.full_viewing_key(); auto fvk = sk.expsk.full_viewing_key();
auto pa = sk.DefaultAddress(); auto pa = sk.ToXFVK().DefaultAddress();
auto builder = TransactionBuilder(consensusParams, 1, &keyStore); auto builder = TransactionBuilder(consensusParams, 1, &keyStore);
builder.SetFee(0); builder.SetFee(0);

View File

@ -203,12 +203,15 @@ libzcash::SaplingPaymentAddress AsyncRPCOperation_saplingmigration::getMigration
} }
// TODO: use UVK-based derivation here instead. // TODO: use UVK-based derivation here instead.
auto xsk = pwalletMain->GenerateNewSaplingZKey(seed, 0); auto usk = pwalletMain->GetUnifiedSpendingKeyForAccount(0);
assert(usk.has_value()); // mnemonic seeds are currently always generated to have valid USKs at account 0
auto xsk = usk.value().GetSaplingExtendedSpendingKey();
if (xsk.has_value()) { if (xsk.has_value()) {
return xsk.value().DefaultAddress(); return xsk.value().ToXFVK().DefaultAddress();
} else { } else {
// the wallet already has a key at account 0; what is the // This error will only occur if Sapling address generation has been disbled for USKs from this
// correct behavior here? // wallet.
throw std::runtime_error(std::string(__func__) + ": No Sapling address generated for account 0.");
} }
} }

View File

@ -393,7 +393,7 @@ TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
auto expsk = sk.expsk; auto expsk = sk.expsk;
auto fvk = expsk.full_viewing_key(); auto fvk = expsk.full_viewing_key();
auto ivk = fvk.in_viewing_key(); auto ivk = fvk.in_viewing_key();
auto pk = sk.DefaultAddress(); auto pk = sk.ToXFVK().DefaultAddress();
libzcash::SaplingNote note(pk, 50000, zip_212_enabled[ver]); libzcash::SaplingNote note(pk, 50000, zip_212_enabled[ver]);
auto cm = note.cmu().value(); auto cm = note.cmu().value();
@ -535,7 +535,7 @@ TEST(WalletTests, FindMySaplingNotes) {
auto sk = GetTestMasterSaplingSpendingKey(); auto sk = GetTestMasterSaplingSpendingKey();
auto expsk = sk.expsk; auto expsk = sk.expsk;
auto extfvk = sk.ToXFVK(); auto extfvk = sk.ToXFVK();
auto pa = sk.DefaultAddress(); auto pa = extfvk.DefaultAddress();
auto testNote = GetTestSaplingNote(pa, 50000); auto testNote = GetTestSaplingNote(pa, 50000);
@ -669,7 +669,7 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
auto expsk = sk.expsk; auto expsk = sk.expsk;
auto extfvk = sk.ToXFVK(); auto extfvk = sk.ToXFVK();
auto ivk = extfvk.fvk.in_viewing_key(); auto ivk = extfvk.fvk.in_viewing_key();
auto pk = sk.DefaultAddress(); auto pk = extfvk.DefaultAddress();
ASSERT_TRUE(wallet.AddSaplingZKey(sk)); ASSERT_TRUE(wallet.AddSaplingZKey(sk));
ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk)); ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk));
@ -829,7 +829,7 @@ TEST(WalletTests, SaplingNullifierIsSpent) {
auto sk = GetTestMasterSaplingSpendingKey(); auto sk = GetTestMasterSaplingSpendingKey();
auto expsk = sk.expsk; auto expsk = sk.expsk;
auto extfvk = sk.ToXFVK(); auto extfvk = sk.ToXFVK();
auto pa = sk.DefaultAddress(); auto pa = extfvk.DefaultAddress();
auto testNote = GetTestSaplingNote(pa, 50000); auto testNote = GetTestSaplingNote(pa, 50000);
@ -914,7 +914,7 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
auto sk = GetTestMasterSaplingSpendingKey(); auto sk = GetTestMasterSaplingSpendingKey();
auto expsk = sk.expsk; auto expsk = sk.expsk;
auto extfvk = sk.ToXFVK(); auto extfvk = sk.ToXFVK();
auto pa = sk.DefaultAddress(); auto pa = extfvk.DefaultAddress();
auto testNote = GetTestSaplingNote(pa, 50000); auto testNote = GetTestSaplingNote(pa, 50000);
@ -1045,7 +1045,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
auto expsk = sk.expsk; auto expsk = sk.expsk;
auto extfvk = sk.ToXFVK(); auto extfvk = sk.ToXFVK();
auto ivk = extfvk.fvk.in_viewing_key(); auto ivk = extfvk.fvk.in_viewing_key();
auto pk = sk.DefaultAddress(); auto pk = extfvk.DefaultAddress();
// Generate Sapling note A // Generate Sapling note A
libzcash::SaplingNote note(pk, 50000, zip_212_enabled[ver]); libzcash::SaplingNote note(pk, 50000, zip_212_enabled[ver]);
@ -1841,13 +1841,13 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
auto sk = m.Derive(0); auto sk = m.Derive(0);
auto expsk = sk.expsk; auto expsk = sk.expsk;
auto extfvk = sk.ToXFVK(); auto extfvk = sk.ToXFVK();
auto pa = sk.DefaultAddress(); auto pa = extfvk.DefaultAddress();
// Generate dummy recipient Sapling address // Generate dummy recipient Sapling address
auto sk2 = m.Derive(1); auto sk2 = m.Derive(1);
auto expsk2 = sk2.expsk; auto expsk2 = sk2.expsk;
auto extfvk2 = sk2.ToXFVK(); auto extfvk2 = sk2.ToXFVK();
auto pa2 = sk2.DefaultAddress(); auto pa2 = extfvk2.DefaultAddress();
auto testNote = GetTestSaplingNote(pa, 50000); auto testNote = GetTestSaplingNote(pa, 50000);
@ -1983,7 +1983,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
auto expsk = sk.expsk; auto expsk = sk.expsk;
auto extfvk = sk.ToXFVK(); auto extfvk = sk.ToXFVK();
auto ivk = extfvk.fvk.in_viewing_key(); auto ivk = extfvk.fvk.in_viewing_key();
auto pk = sk.DefaultAddress(); auto pk = extfvk.DefaultAddress();
ASSERT_TRUE(wallet.AddSaplingZKey(sk)); ASSERT_TRUE(wallet.AddSaplingZKey(sk));
ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk)); ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk));

View File

@ -64,7 +64,7 @@ TEST(WalletZkeysTest, StoreAndLoadSaplingZkeys) {
wallet.GetSaplingPaymentAddresses(addrs); wallet.GetSaplingPaymentAddresses(addrs);
EXPECT_EQ(2, addrs.size()); EXPECT_EQ(2, addrs.size());
EXPECT_EQ(1, addrs.count(address)); EXPECT_EQ(1, addrs.count(address));
EXPECT_EQ(1, addrs.count(sk.DefaultAddress())); EXPECT_EQ(1, addrs.count(sk.ToXFVK().DefaultAddress()));
// Generate a diversified address different to the default // Generate a diversified address different to the default
// If we can't get an early diversified address, we are very unlucky // If we can't get an early diversified address, we are very unlucky
@ -73,7 +73,7 @@ TEST(WalletZkeysTest, StoreAndLoadSaplingZkeys) {
auto dpa = sk.ToXFVK().Address(diversifier).value(); auto dpa = sk.ToXFVK().Address(diversifier).value();
// verify wallet only has the default address // verify wallet only has the default address
EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(sk.DefaultAddress())); EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(sk.ToXFVK().DefaultAddress()));
EXPECT_FALSE(wallet.HaveSaplingIncomingViewingKey(dpa)); EXPECT_FALSE(wallet.HaveSaplingIncomingViewingKey(dpa));
// manually add a diversified address // manually add a diversified address
@ -81,7 +81,7 @@ TEST(WalletZkeysTest, StoreAndLoadSaplingZkeys) {
EXPECT_TRUE(wallet.AddSaplingIncomingViewingKey(ivk, dpa)); EXPECT_TRUE(wallet.AddSaplingIncomingViewingKey(ivk, dpa));
// verify wallet did add it // verify wallet did add it
EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(sk.DefaultAddress())); EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(sk.ToXFVK().DefaultAddress()));
EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(dpa)); EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(dpa));
// Load a third key into the wallet // Load a third key into the wallet
@ -99,7 +99,7 @@ TEST(WalletZkeysTest, StoreAndLoadSaplingZkeys) {
// Load a diversified address for the third key into the wallet // Load a diversified address for the third key into the wallet
auto dpa2 = sk2.ToXFVK().Address(diversifier).value(); auto dpa2 = sk2.ToXFVK().Address(diversifier).value();
EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(sk2.DefaultAddress())); EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(sk2.ToXFVK().DefaultAddress()));
EXPECT_FALSE(wallet.HaveSaplingIncomingViewingKey(dpa2)); EXPECT_FALSE(wallet.HaveSaplingIncomingViewingKey(dpa2));
EXPECT_TRUE(wallet.LoadSaplingPaymentAddress(dpa2, ivk2)); EXPECT_TRUE(wallet.LoadSaplingPaymentAddress(dpa2, ivk2));
EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(dpa2)); EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(dpa2));
@ -501,8 +501,8 @@ TEST(wallet_zkeys_tests, WriteCryptedSaplingZkeyDirectToDb) {
wallet2.Unlock(strWalletPass); wallet2.Unlock(strWalletPass);
EXPECT_TRUE(wallet2.GetSaplingExtendedSpendingKey(address, keyOut)); EXPECT_TRUE(wallet2.GetSaplingExtendedSpendingKey(address, keyOut));
ASSERT_EQ(address, keyOut.DefaultAddress()); ASSERT_EQ(address, keyOut.ToXFVK().DefaultAddress());
EXPECT_TRUE(wallet2.GetSaplingExtendedSpendingKey(address2, keyOut)); EXPECT_TRUE(wallet2.GetSaplingExtendedSpendingKey(address2, keyOut));
ASSERT_EQ(address2, keyOut.DefaultAddress()); ASSERT_EQ(address2, keyOut.ToXFVK().DefaultAddress());
} }

View File

@ -665,7 +665,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importexport)
// create a random Sapling key locally; split between IVKs and spending keys. // create a random Sapling key locally; split between IVKs and spending keys.
auto testSaplingSpendingKey = m.Derive(i); auto testSaplingSpendingKey = m.Derive(i);
auto testSaplingPaymentAddress = testSaplingSpendingKey.DefaultAddress(); auto testSaplingPaymentAddress = testSaplingSpendingKey.ToXFVK().DefaultAddress();
if (i % 2 == 0) { if (i % 2 == 0) {
std::string testSaplingAddr = keyIO.EncodePaymentAddress(testSaplingPaymentAddress); std::string testSaplingAddr = keyIO.EncodePaymentAddress(testSaplingPaymentAddress);
std::string testSaplingKey = keyIO.EncodeSpendingKey(testSaplingSpendingKey); std::string testSaplingKey = keyIO.EncodeSpendingKey(testSaplingSpendingKey);

View File

@ -118,9 +118,9 @@ libzcash::SproutPaymentAddress CWallet::GenerateNewSproutZKey()
// is not present. When using legacy HD seeds, the account index is determined // is not present. When using legacy HD seeds, the account index is determined
// by trial of legacyHDChain.GetAccountCounter(); for unified addresses this must use // by trial of legacyHDChain.GetAccountCounter(); for unified addresses this must use
// valued derived from legacyHDChain.unifiedAccountCounter // valued derived from legacyHDChain.unifiedAccountCounter
std::optional<SaplingPaymentAddress> CWallet::GenerateNewLegacySaplingZKey() std::optional<SaplingPaymentAddress> CWallet::GenerateNewLegacySaplingZKey() {
{ AssertLockHeld(cs_wallet);
// Try to get the seed
auto seedOpt = GetLegacyHDSeed(); auto seedOpt = GetLegacyHDSeed();
if (seedOpt.has_value()) { if (seedOpt.has_value()) {
auto seed = seedOpt.value(); auto seed = seedOpt.value();
@ -128,18 +128,33 @@ std::optional<SaplingPaymentAddress> CWallet::GenerateNewLegacySaplingZKey()
legacyHDChain = CHDChain(seed.Fingerprint(), GetTime()); legacyHDChain = CHDChain(seed.Fingerprint(), GetTime());
} }
CHDChain& hdChain = legacyHDChain.value(); CHDChain& hdChain = legacyHDChain.value();
// loop until we find an unused account id
while (true) { while (true) {
auto xsk = GenerateNewSaplingZKey(seed, hdChain.GetAccountCounter()); auto xsk = libzcash::SaplingExtendedSpendingKey::ForAccount(
if (!xsk.has_value()) { seed,
hdChain.IncrementAccountCounter(); BIP44CoinType(),
hdChain.GetAccountCounter());
// advance the account counter so that the next time we need to generate
// a key we're pointing at a free index.
hdChain.IncrementAccountCounter();
if (HaveSaplingSpendingKey(xsk.first.ToXFVK())) {
// try the next account ID
continue; continue;
} else { } else {
// Update the chain model in the database // Update the persisted chain information
if (fFileBacked && !CWalletDB(strWalletFile).WriteLegacyHDChain(hdChain)) { if (fFileBacked && !CWalletDB(strWalletFile).WriteLegacyHDChain(hdChain)) {
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): Writing HD chain model failed"); throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): Writing HD chain model failed");
} }
return xsk.value().ToXFVK().DefaultAddress(); auto ivk = xsk.first.expsk.full_viewing_key().in_viewing_key();
mapSaplingZKeyMetadata[ivk] = xsk.second;
if (!AddSaplingZKey(xsk.first)) {
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): AddSaplingZKey failed");
}
return xsk.first.ToXFVK().DefaultAddress();
} }
} }
} else { } else {
@ -147,48 +162,6 @@ std::optional<SaplingPaymentAddress> CWallet::GenerateNewLegacySaplingZKey()
} }
} }
// Generate a new Sapling spending key and return its public payment address
//
// NU5: This must take both the account number and the HD seed to be used.
// Generation of unified accounts must allow the user to specify a set of protocol
// types that they wish to include in their unified addresses.
std::optional<libzcash::SaplingExtendedSpendingKey> CWallet::GenerateNewSaplingZKey(
const HDSeed& seed,
uint32_t accountId) {
AssertLockHeld(cs_wallet);
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
uint32_t bip44CoinType = BIP44CoinType();
// We use a fixed keypath scheme of m/32'/coin_type'/account'
// Derive m/32'
auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT);
// Derive m/32'/coin_type'
auto m_32h_cth = m_32h.Derive(bip44CoinType | ZIP32_HARDENED_KEY_LIMIT);
// Derive account key at next index, skip keys already known to the wallet
libzcash::SaplingExtendedSpendingKey xsk = m_32h_cth.Derive(accountId | ZIP32_HARDENED_KEY_LIMIT);
if (HaveSaplingSpendingKey(xsk.ToXFVK())) {
return std::nullopt;
} else {
// Create new metadata
int64_t nCreationTime = GetTime();
CKeyMetadata metadata(nCreationTime);
metadata.hdKeypath = "m/32'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(accountId) + "'";
metadata.seedFp = seed.Fingerprint();
auto ivk = xsk.expsk.full_viewing_key().in_viewing_key();
mapSaplingZKeyMetadata[ivk] = metadata;
if (!AddSaplingZKey(xsk)) {
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): AddSaplingZKey failed");
}
return xsk;
}
}
// Add spending key to keystore // Add spending key to keystore
bool CWallet::AddSaplingZKey(const libzcash::SaplingExtendedSpendingKey &sk) bool CWallet::AddSaplingZKey(const libzcash::SaplingExtendedSpendingKey &sk)
{ {
@ -394,6 +367,37 @@ bool CWallet::AddCryptedSaplingSpendingKey(const libzcash::SaplingExtendedFullVi
return false; return false;
} }
UnifiedSpendingKey CWallet::GenerateNewUnifiedSpendingKey() {
AssertLockHeld(cs_wallet);
auto seed = GetMnemonicSeed();
if (!seed.has_value()) {
throw std::runtime_error(std::string(__func__) + ": Wallet has no mnemonic HD seed. Please upgrade this wallet.");
}
auto hdChain = GetMnemonicHDChain().value();
while (true) {
auto usk = UnifiedSpendingKey::Derive(seed.value(), BIP44CoinType(), hdChain.GetAccountCounter());
hdChain.IncrementAccountCounter();
if (usk.has_value()) {
// Update the persisted chain information
if (fFileBacked && !CWalletDB(strWalletFile).WriteMnemonicHDChain(hdChain)) {
throw std::runtime_error("CWallet::GenerateNewUnifiedSpendingKey(): Writing HD chain model failed");
}
// TODO: Save the unified full viewing key to the wallet metadata
return usk.value().first;
}
}
}
std::optional<libzcash::UnifiedSpendingKey> CWallet::GetUnifiedSpendingKeyForAccount(uint32_t accountId) {
//TODO
return std::nullopt;
}
void CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta) void CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta)
{ {
AssertLockHeld(cs_wallet); // mapKeyMetadata AssertLockHeld(cs_wallet); // mapKeyMetadata
@ -2252,7 +2256,7 @@ void CWallet::GenerateNewSeed(Language language)
{ {
LOCK(cs_wallet); LOCK(cs_wallet);
auto seed = MnemonicSeed::Random(language, HD_WALLET_SEED_LENGTH); auto seed = MnemonicSeed::Random(BIP44CoinType(), language, HD_WALLET_SEED_LENGTH);
int64_t nCreationTime = GetTime(); int64_t nCreationTime = GetTime();
@ -5468,7 +5472,7 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedS
KeyIO keyIO(Params()); KeyIO keyIO(Params());
{ {
if (log){ if (log){
LogPrint("zrpc", "Importing zaddr %s...\n", keyIO.EncodePaymentAddress(sk.DefaultAddress())); LogPrint("zrpc", "Importing zaddr %s...\n", keyIO.EncodePaymentAddress(sk.ToXFVK().DefaultAddress()));
} }
// Don't throw error in case a key is already there // Don't throw error in case a key is already there
if (m_wallet->HaveSaplingSpendingKey(extfvk)) { if (m_wallet->HaveSaplingSpendingKey(extfvk)) {

View File

@ -829,6 +829,7 @@ public:
std::map<CKeyID, CKeyMetadata> mapKeyMetadata; std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapSproutZKeyMetadata; std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapSproutZKeyMetadata;
std::map<libzcash::SaplingIncomingViewingKey, CKeyMetadata> mapSaplingZKeyMetadata; std::map<libzcash::SaplingIncomingViewingKey, CKeyMetadata> mapSaplingZKeyMetadata;
//std::map<libzcash::UnifiedIncomingViewingKey, CKeyMetadata> mapUnifiedKeyMetadata;
typedef std::map<unsigned int, CMasterKey> MasterKeyMap; typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
MasterKeyMap mapMasterKeys; MasterKeyMap mapMasterKeys;
@ -1100,6 +1101,12 @@ public:
bool LoadCryptedSaplingZKey(const libzcash::SaplingExtendedFullViewingKey &extfvk, bool LoadCryptedSaplingZKey(const libzcash::SaplingExtendedFullViewingKey &extfvk,
const std::vector<unsigned char> &vchCryptedSecret); const std::vector<unsigned char> &vchCryptedSecret);
/**
* Unified keys & addresses
*/
libzcash::UnifiedSpendingKey GenerateNewUnifiedSpendingKey();
std::optional<libzcash::UnifiedSpendingKey> GetUnifiedSpendingKeyForAccount(uint32_t accountId);
/** /**
* Increment the next transaction order id * Increment the next transaction order id
* @return next transaction order id * @return next transaction order id

View File

@ -96,49 +96,6 @@ public:
} }
}; };
class CKeyMetadata
{
public:
static const int VERSION_BASIC=1;
static const int VERSION_WITH_HDDATA=10;
static const int CURRENT_VERSION=VERSION_WITH_HDDATA;
int nVersion;
int64_t nCreateTime; // 0 means unknown
std::string hdKeypath; //optional HD/zip32 keypath
uint256 seedFp;
CKeyMetadata()
{
SetNull();
}
CKeyMetadata(int64_t nCreateTime_)
{
SetNull();
nCreateTime = nCreateTime_;
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(this->nVersion);
READWRITE(nCreateTime);
if (this->nVersion >= VERSION_WITH_HDDATA)
{
READWRITE(hdKeypath);
READWRITE(seedFp);
}
}
void SetNull()
{
nVersion = CKeyMetadata::CURRENT_VERSION;
nCreateTime = 0;
hdKeypath.clear();
seedFp.SetNull();
}
};
/** Access to the wallet database */ /** Access to the wallet database */
class CWalletDB : public CDB class CWalletDB : public CDB
{ {

View File

@ -39,7 +39,7 @@ std::pair<std::string, PaymentAddress> AddressInfoFromSpendingKey::operator()(co
return std::make_pair("sprout", sk.address()); return std::make_pair("sprout", sk.address());
} }
std::pair<std::string, PaymentAddress> AddressInfoFromSpendingKey::operator()(const SaplingExtendedSpendingKey &sk) const { std::pair<std::string, PaymentAddress> AddressInfoFromSpendingKey::operator()(const SaplingExtendedSpendingKey &sk) const {
return std::make_pair("sapling", sk.DefaultAddress()); return std::make_pair("sapling", sk.ToXFVK().DefaultAddress());
} }
std::pair<std::string, PaymentAddress> AddressInfoFromSpendingKey::operator()(const InvalidEncoding&) const { std::pair<std::string, PaymentAddress> AddressInfoFromSpendingKey::operator()(const InvalidEncoding&) const {
throw std::invalid_argument("Cannot derive default address from invalid spending key"); throw std::invalid_argument("Cannot derive default address from invalid spending key");
@ -48,8 +48,8 @@ std::pair<std::string, PaymentAddress> AddressInfoFromSpendingKey::operator()(co
std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(const SproutViewingKey &sk) const { std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(const SproutViewingKey &sk) const {
return std::make_pair("sprout", sk.address()); return std::make_pair("sprout", sk.address());
} }
std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(const SaplingExtendedFullViewingKey &sk) const { std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(const SaplingExtendedFullViewingKey &xfvk) const {
return std::make_pair("sapling", sk.DefaultAddress()); return std::make_pair("sapling", xfvk.DefaultAddress());
} }
std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(const InvalidEncoding&) const { std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(const InvalidEncoding&) const {
throw std::invalid_argument("Cannot derive default address from invalid viewing key"); throw std::invalid_argument("Cannot derive default address from invalid viewing key");

View File

@ -19,20 +19,23 @@ const unsigned char ZCASH_HD_SEED_FP_PERSONAL[BLAKE2bPersonalBytes] =
const unsigned char ZCASH_TADDR_OVK_PERSONAL[BLAKE2bPersonalBytes] = const unsigned char ZCASH_TADDR_OVK_PERSONAL[BLAKE2bPersonalBytes] =
{'Z', 'c', 'T', 'a', 'd', 'd', 'r', 'T', 'o', 'S', 'a', 'p', 'l', 'i', 'n', 'g'}; {'Z', 'c', 'T', 'a', 'd', 'd', 'r', 'T', 'o', 'S', 'a', 'p', 'l', 'i', 'n', 'g'};
MnemonicSeed MnemonicSeed::Random(Language language, size_t len) MnemonicSeed MnemonicSeed::Random(uint32_t bip44CoinType, Language language, size_t entropyLen)
{ {
assert(len >= 32); assert(entropyLen >= 32);
while (true) { while (true) {
std::vector<unsigned char> entropy(len, 0); std::vector<unsigned char> entropy(entropyLen, 0);
GetRandBytes(entropy.data(), len); GetRandBytes(entropy.data(), entropyLen);
const char* phrase = zip339_entropy_to_phrase(language, entropy.data(), len); const char* phrase = zip339_entropy_to_phrase(language, entropy.data(), entropyLen);
std::string mnemonic(phrase); std::string mnemonic(phrase);
zip339_free_phrase(phrase); zip339_free_phrase(phrase);
MnemonicSeed seed(language, mnemonic); MnemonicSeed seed(language, mnemonic);
// TODO: check for the validity of the Sapling spending key at account 0 for this seed. // Verify that the seed data is valid entropy for unified spending keys at
// account 0 and account 0x7FFFFFFE
return seed; if (libzcash::UnifiedSpendingKey::Derive(seed, bip44CoinType, 0).has_value() &&
libzcash::DeriveZip32TransparentSpendingKey(seed, bip44CoinType, ZCASH_LEGACY_TRANSPARENT_ACCOUNT).has_value()) {
return seed;
}
} }
} }
@ -113,12 +116,12 @@ libzcash::SaplingPaymentAddress SaplingExtendedFullViewingKey::DefaultAddress()
diversifier_index_t j_default; diversifier_index_t j_default;
diversifier_index_t j_ret; diversifier_index_t j_ret;
CSerializeData addr_bytes(libzcash::SerializedSaplingPaymentAddressSize); CSerializeData addr_bytes_ret(libzcash::SerializedSaplingPaymentAddressSize);
if (librustzcash_zip32_find_xfvk_address( if (librustzcash_zip32_find_xfvk_address(
reinterpret_cast<unsigned char*>(xfvk_bytes.data()), reinterpret_cast<unsigned char*>(xfvk_bytes.data()),
j_default.begin(), j_ret.begin(), j_default.begin(), j_ret.begin(),
reinterpret_cast<unsigned char*>(addr_bytes.data()))) { reinterpret_cast<unsigned char*>(addr_bytes_ret.data()))) {
CDataStream ss_addr(addr_bytes, SER_NETWORK, PROTOCOL_VERSION); CDataStream ss_addr(addr_bytes_ret, SER_NETWORK, PROTOCOL_VERSION);
libzcash::SaplingPaymentAddress addr; libzcash::SaplingPaymentAddress addr;
ss_addr >> addr; ss_addr >> addr;
return addr; return addr;
@ -161,6 +164,27 @@ SaplingExtendedSpendingKey SaplingExtendedSpendingKey::Derive(uint32_t i) const
return xsk_i; return xsk_i;
} }
std::pair<SaplingExtendedSpendingKey, CKeyMetadata> SaplingExtendedSpendingKey::ForAccount(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId) {
auto m = Master(seed);
// We use a fixed keypath scheme of m/32'/coin_type'/account'
// Derive m/32'
auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT);
// Derive m/32'/coin_type'
auto m_32h_cth = m_32h.Derive(bip44CoinType | ZIP32_HARDENED_KEY_LIMIT);
// Derive account key at next index, skip keys already known to the wallet
auto xsk = m_32h_cth.Derive(accountId | ZIP32_HARDENED_KEY_LIMIT);
// Create new metadata
int64_t nCreationTime = GetTime();
CKeyMetadata metadata(nCreationTime);
metadata.hdKeypath = "m/32'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(accountId) + "'";
metadata.seedFp = seed.Fingerprint();
return std::make_pair(xsk, metadata);
}
SaplingExtendedFullViewingKey SaplingExtendedSpendingKey::ToXFVK() const SaplingExtendedFullViewingKey SaplingExtendedSpendingKey::ToXFVK() const
{ {
SaplingExtendedFullViewingKey ret; SaplingExtendedFullViewingKey ret;
@ -173,9 +197,34 @@ SaplingExtendedFullViewingKey SaplingExtendedSpendingKey::ToXFVK() const
return ret; return ret;
} }
libzcash::SaplingPaymentAddress SaplingExtendedSpendingKey::DefaultAddress() const std::optional<CExtKey> DeriveZip32TransparentSpendingKey(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId) {
{ auto rawSeed = seed.RawSeed();
return ToXFVK().DefaultAddress(); auto m = CExtKey::Master(rawSeed.data(), rawSeed.size());
// We use a fixed keypath scheme of m/32'/coin_type'/account'
// Derive m/32'
auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT);
if (!m_32h.has_value()) return std::nullopt;
// Derive m/32'/coin_type'
auto m_32h_cth = m_32h.value().Derive(bip44CoinType | ZIP32_HARDENED_KEY_LIMIT);
if (!m_32h_cth.has_value()) return std::nullopt;
// Derive m/32'/coin_type'/account_id'
return m_32h_cth.value().Derive(accountId | ZIP32_HARDENED_KEY_LIMIT);
}
std::optional<std::pair<UnifiedSpendingKey, CKeyMetadata>> UnifiedSpendingKey::Derive(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId) {
UnifiedSpendingKey usk;
auto transparentKey = DeriveZip32TransparentSpendingKey(seed, bip44CoinType, accountId);
if (!transparentKey.has_value()) return std::nullopt;
usk.p2pkhKey = transparentKey.value();
auto saplingKey = SaplingExtendedSpendingKey::ForAccount(seed, bip44CoinType, accountId);
usk.saplingKey = saplingKey.first;
return std::make_pair(usk, saplingKey.second);
} }
std::optional<unsigned long> ParseZip32KeypathAccount(const std::string& keyPath) { std::optional<unsigned long> ParseZip32KeypathAccount(const std::string& keyPath) {
@ -188,4 +237,4 @@ std::optional<unsigned long> ParseZip32KeypathAccount(const std::string& keyPath
} }
} }
} };

View File

@ -6,8 +6,10 @@
#define ZCASH_ZCASH_ADDRESS_ZIP32_H #define ZCASH_ZCASH_ADDRESS_ZIP32_H
#include "serialize.h" #include "serialize.h"
#include "key.h"
#include "support/allocators/secure.h" #include "support/allocators/secure.h"
#include "uint256.h" #include "uint256.h"
#include "utiltime.h"
#include "zcash/address/sapling.hpp" #include "zcash/address/sapling.hpp"
#include "rust/zip339.h" #include "rust/zip339.h"
@ -65,7 +67,12 @@ public:
seed.assign(buf, std::end(buf)); seed.assign(buf, std::end(buf));
} }
static MnemonicSeed Random(Language language = English, size_t len = 32); /**
* Randomly generate a new mnemonic seed. A SLIP-44 coin type is required to make it possible
* to check that the generated seed can produce valid transparent and unified addresses at account
* numbers 0x7FFFFFFE and 0x0 respectively.
*/
static MnemonicSeed Random(uint32_t bip44CoinType, Language language = English, size_t entropyLen = 32);
static std::string LanguageName(Language language) { static std::string LanguageName(Language language) {
switch (language) { switch (language) {
@ -142,6 +149,51 @@ public:
// This is not part of ZIP 32, but is here because it's linked to the HD seed. // This is not part of ZIP 32, but is here because it's linked to the HD seed.
uint256 ovkForShieldingFromTaddr(HDSeed& seed); uint256 ovkForShieldingFromTaddr(HDSeed& seed);
// Key derivation metadata
class CKeyMetadata
{
public:
static const int VERSION_BASIC=1;
static const int VERSION_WITH_HDDATA=10;
static const int CURRENT_VERSION=VERSION_WITH_HDDATA;
int nVersion;
int64_t nCreateTime; // 0 means unknown
std::string hdKeypath; //optional HD/zip32 keypath
uint256 seedFp;
CKeyMetadata()
{
SetNull();
}
CKeyMetadata(int64_t nCreateTime_)
{
SetNull();
nCreateTime = nCreateTime_;
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(this->nVersion);
READWRITE(nCreateTime);
if (this->nVersion >= VERSION_WITH_HDDATA)
{
READWRITE(hdKeypath);
READWRITE(seedFp);
}
}
void SetNull()
{
nVersion = CKeyMetadata::CURRENT_VERSION;
nCreateTime = 0;
hdKeypath.clear();
seedFp.SetNull();
}
};
namespace libzcash { namespace libzcash {
typedef blob88 diversifier_index_t; typedef blob88 diversifier_index_t;
@ -212,13 +264,12 @@ struct SaplingExtendedSpendingKey {
} }
static SaplingExtendedSpendingKey Master(const HDSeed& seed); static SaplingExtendedSpendingKey Master(const HDSeed& seed);
static std::pair<SaplingExtendedSpendingKey, CKeyMetadata> ForAccount(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId);
SaplingExtendedSpendingKey Derive(uint32_t i) const; SaplingExtendedSpendingKey Derive(uint32_t i) const;
SaplingExtendedFullViewingKey ToXFVK() const; SaplingExtendedFullViewingKey ToXFVK() const;
libzcash::SaplingPaymentAddress DefaultAddress() const;
friend bool operator==(const SaplingExtendedSpendingKey& a, const SaplingExtendedSpendingKey& b) friend bool operator==(const SaplingExtendedSpendingKey& a, const SaplingExtendedSpendingKey& b)
{ {
return a.depth == b.depth && return a.depth == b.depth &&
@ -230,8 +281,25 @@ struct SaplingExtendedSpendingKey {
} }
}; };
class UnifiedSpendingKey {
private:
uint32_t accountId;
std::optional<CExtKey> p2pkhKey;
std::optional<SaplingExtendedSpendingKey> saplingKey;
UnifiedSpendingKey() {}
public:
static std::optional<std::pair<UnifiedSpendingKey, CKeyMetadata>> Derive(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId);
const std::optional<SaplingExtendedSpendingKey>& GetSaplingExtendedSpendingKey() {
return saplingKey;
}
};
std::optional<unsigned long> ParseZip32KeypathAccount(const std::string& keyPath); std::optional<unsigned long> ParseZip32KeypathAccount(const std::string& keyPath);
std::optional<CExtKey> DeriveZip32TransparentSpendingKey(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId);
} }
#endif // ZCASH_ZCASH_ADDRESS_ZIP32_H #endif // ZCASH_ZCASH_ADDRESS_ZIP32_H

View File

@ -375,7 +375,7 @@ CWalletTx CreateSaplingTxWithNoteData(const Consensus::Params& consensusParams,
CBasicKeyStore& keyStore, CBasicKeyStore& keyStore,
const libzcash::SaplingExtendedSpendingKey &sk) { const libzcash::SaplingExtendedSpendingKey &sk) {
auto wtx = GetValidSaplingReceive(consensusParams, keyStore, sk, 10); auto wtx = GetValidSaplingReceive(consensusParams, keyStore, sk, 10);
auto testNote = GetTestSaplingNote(sk.DefaultAddress(), 10); auto testNote = GetTestSaplingNote(sk.ToXFVK().DefaultAddress(), 10);
auto fvk = sk.expsk.full_viewing_key(); auto fvk = sk.expsk.full_viewing_key();
auto nullifier = testNote.note.nullifier(fvk, testNote.tree.witness().position()).value(); auto nullifier = testNote.note.nullifier(fvk, testNote.tree.witness().position()).value();