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:
parent
8883ae8b9e
commit
83dee7e886
|
@ -48,7 +48,7 @@ TEST(Keys, EncodeAndDecodeSapling)
|
|||
EXPECT_EQ(extfvk, extfvk2);
|
||||
}
|
||||
{
|
||||
auto addr = sk.DefaultAddress();
|
||||
auto addr = sk.ToXFVK().DefaultAddress();
|
||||
|
||||
std::string addr_string = keyIO.EncodePaymentAddress(addr);
|
||||
EXPECT_EQ(
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
#define MAKE_STRING(x) std::string((x), (x)+sizeof(x))
|
||||
|
||||
const uint32_t BIP44_TESTNET_TYPE = 1;
|
||||
|
||||
TEST(KeystoreTests, StoreAndRetrieveHDSeed) {
|
||||
CBasicKeyStore keyStore;
|
||||
|
||||
|
@ -23,7 +25,7 @@ TEST(KeystoreTests, StoreAndRetrieveHDSeed) {
|
|||
EXPECT_FALSE(seedOut.has_value());
|
||||
|
||||
// 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
|
||||
ASSERT_TRUE(keyStore.SetMnemonicSeed(seed));
|
||||
|
@ -33,7 +35,7 @@ TEST(KeystoreTests, StoreAndRetrieveHDSeed) {
|
|||
EXPECT_EQ(seed, seedOut.value());
|
||||
|
||||
// Generate another random seed
|
||||
auto seed2 = MnemonicSeed::Random();
|
||||
auto seed2 = MnemonicSeed::Random(BIP44_TESTNET_TYPE);
|
||||
EXPECT_NE(seed, seed2);
|
||||
|
||||
// We should not be able to set and retrieve a different seed
|
||||
|
@ -203,7 +205,7 @@ TEST(KeystoreTests, StoreAndRetrieveSaplingSpendingKey) {
|
|||
auto sk = GetTestMasterSaplingSpendingKey();
|
||||
auto extfvk = sk.ToXFVK();
|
||||
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
|
||||
EXPECT_FALSE(keyStore.HaveSaplingSpendingKey(extfvk));
|
||||
|
@ -237,7 +239,7 @@ TEST(KeystoreTests, StoreAndRetrieveSaplingFullViewingKey) {
|
|||
auto sk = GetTestMasterSaplingSpendingKey();
|
||||
auto extfvk = sk.ToXFVK();
|
||||
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
|
||||
EXPECT_FALSE(keyStore.HaveSaplingFullViewingKey(ivk));
|
||||
|
@ -289,7 +291,7 @@ TEST(KeystoreTests, StoreAndRetrieveHDSeedInEncryptedStore) {
|
|||
GetRandBytes(vMasterKey.data(), 32);
|
||||
|
||||
// 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());
|
||||
auto seedOut = keyStore.GetMnemonicSeed();
|
||||
EXPECT_FALSE(seedOut.has_value());
|
||||
|
@ -321,7 +323,7 @@ TEST(KeystoreTests, StoreAndRetrieveHDSeedInEncryptedStore) {
|
|||
EXPECT_EQ(seed, seedOut.value());
|
||||
|
||||
// 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_TRUE(keyStore.HaveMnemonicSeed());
|
||||
seedOut = keyStore.GetMnemonicSeed();
|
||||
|
@ -341,7 +343,7 @@ TEST(KeystoreTests, StoreAndRetrieveHDSeedInEncryptedStore) {
|
|||
seedOut = keyStore2.GetMnemonicSeed();
|
||||
EXPECT_FALSE(seedOut.has_value());
|
||||
|
||||
auto seed3 = MnemonicSeed::Random();
|
||||
auto seed3 = MnemonicSeed::Random(BIP44_TESTNET_TYPE);
|
||||
ASSERT_TRUE(keyStore2.SetMnemonicSeed(seed3));
|
||||
EXPECT_TRUE(keyStore2.HaveMnemonicSeed());
|
||||
seedOut = keyStore2.GetMnemonicSeed();
|
||||
|
|
24
src/key.cpp
24
src/key.cpp
|
@ -274,23 +274,31 @@ bool CKey::Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const
|
|||
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;
|
||||
CKeyID id = key.GetPubKey().GetID();
|
||||
memcpy(&out.vchFingerprint[0], &id, 4);
|
||||
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'};
|
||||
std::vector<unsigned char, secure_allocator<unsigned char>> vout(64);
|
||||
CHMAC_SHA512(hashkey, sizeof(hashkey)).Write(seed, nSeedLen).Finalize(vout.data());
|
||||
key.Set(vout.data(), vout.data() + 32, true);
|
||||
memcpy(chaincode.begin(), vout.data() + 32, 32);
|
||||
nDepth = 0;
|
||||
nChild = 0;
|
||||
memset(vchFingerprint, 0, sizeof(vchFingerprint));
|
||||
xk.key.Set(vout.data(), vout.data() + 32, true);
|
||||
memcpy(xk.chaincode.begin(), vout.data() + 32, 32);
|
||||
xk.nDepth = 0;
|
||||
xk.nChild = 0;
|
||||
memset(xk.vchFingerprint, 0, sizeof(xk.vchFingerprint));
|
||||
|
||||
return xk;
|
||||
}
|
||||
|
||||
CExtPubKey CExtKey::Neuter() const {
|
||||
|
|
13
src/key.h
13
src/key.h
|
@ -15,6 +15,12 @@
|
|||
#include <stdexcept>
|
||||
#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
|
||||
|
@ -62,6 +68,8 @@ public:
|
|||
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)
|
||||
{
|
||||
return a.fCompressed == b.fCompressed &&
|
||||
|
@ -160,11 +168,12 @@ struct CExtKey {
|
|||
a.key == b.key;
|
||||
}
|
||||
|
||||
static CExtKey Master(const unsigned char* seed, unsigned int nSeedLen);
|
||||
|
||||
void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const;
|
||||
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;
|
||||
void SetMaster(const unsigned char* seed, unsigned int nSeedLen);
|
||||
template <typename Stream>
|
||||
void Serialize(Stream& s) const
|
||||
{
|
||||
|
|
|
@ -80,10 +80,8 @@ TestVector test2 =
|
|||
|
||||
void RunTest(const TestVector &test) {
|
||||
std::vector<unsigned char> seed = ParseHex(test.strHexMaster);
|
||||
CExtKey key;
|
||||
CExtPubKey pubkey;
|
||||
key.SetMaster(&seed[0], seed.size());
|
||||
pubkey = key.Neuter();
|
||||
CExtKey key = CExtKey::Master(&seed[0], seed.size());
|
||||
CExtPubKey pubkey = key.Neuter();
|
||||
KeyIO keyIO(Params());
|
||||
for (const TestDerivation &derive : test.vDerive) {
|
||||
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
|
||||
|
||||
// Derive new keys
|
||||
CExtKey keyNew;
|
||||
BOOST_CHECK(key.Derive(keyNew, derive.nChild));
|
||||
CExtPubKey pubkeyNew = keyNew.Neuter();
|
||||
auto keyNew = key.Derive(derive.nChild);
|
||||
BOOST_CHECK(keyNew.has_value());
|
||||
CExtPubKey pubkeyNew = keyNew.value().Neuter();
|
||||
if (!(derive.nChild & 0x80000000)) {
|
||||
// Compare with public derivation
|
||||
CExtPubKey pubkeyNew2;
|
||||
BOOST_CHECK(pubkey.Derive(pubkeyNew2, derive.nChild));
|
||||
BOOST_CHECK(pubkeyNew == pubkeyNew2);
|
||||
}
|
||||
key = keyNew;
|
||||
key = keyNew.value();
|
||||
pubkey = pubkeyNew;
|
||||
|
||||
CDataStream ssPub(SER_DISK, CLIENT_VERSION);
|
||||
|
@ -116,7 +114,7 @@ void RunTest(const TestVector &test) {
|
|||
BOOST_CHECK(ssPub.size() == 75);
|
||||
|
||||
CDataStream ssPriv(SER_DISK, CLIENT_VERSION);
|
||||
ssPriv << keyNew;
|
||||
ssPriv << keyNew.value();
|
||||
BOOST_CHECK(ssPriv.size() == 75);
|
||||
|
||||
CExtPubKey pubCheck;
|
||||
|
|
|
@ -247,7 +247,7 @@ BOOST_AUTO_TEST_CASE(zs_address_test)
|
|||
BOOST_CHECK(sk == sk2);
|
||||
}
|
||||
{
|
||||
auto addr = sk.DefaultAddress();
|
||||
auto addr = sk.ToXFVK().DefaultAddress();
|
||||
|
||||
std::string addr_string = keyIO.EncodePaymentAddress(addr);
|
||||
BOOST_CHECK(addr_string.compare(0, 15, Params().Bech32HRP(CChainParams::SAPLING_PAYMENT_ADDRESS)) == 0);
|
||||
|
|
|
@ -346,7 +346,7 @@ CWalletTx GetValidSaplingReceive(const Consensus::Params& consensusParams,
|
|||
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
|
||||
// To zaddr
|
||||
auto fvk = sk.expsk.full_viewing_key();
|
||||
auto pa = sk.DefaultAddress();
|
||||
auto pa = sk.ToXFVK().DefaultAddress();
|
||||
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keyStore);
|
||||
builder.SetFee(0);
|
||||
|
|
|
@ -203,12 +203,15 @@ libzcash::SaplingPaymentAddress AsyncRPCOperation_saplingmigration::getMigration
|
|||
}
|
||||
|
||||
// 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()) {
|
||||
return xsk.value().DefaultAddress();
|
||||
return xsk.value().ToXFVK().DefaultAddress();
|
||||
} else {
|
||||
// the wallet already has a key at account 0; what is the
|
||||
// correct behavior here?
|
||||
// This error will only occur if Sapling address generation has been disbled for USKs from this
|
||||
// wallet.
|
||||
throw std::runtime_error(std::string(__func__) + ": No Sapling address generated for account 0.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -393,7 +393,7 @@ TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
|
|||
auto expsk = sk.expsk;
|
||||
auto fvk = expsk.full_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]);
|
||||
auto cm = note.cmu().value();
|
||||
|
@ -535,7 +535,7 @@ TEST(WalletTests, FindMySaplingNotes) {
|
|||
auto sk = GetTestMasterSaplingSpendingKey();
|
||||
auto expsk = sk.expsk;
|
||||
auto extfvk = sk.ToXFVK();
|
||||
auto pa = sk.DefaultAddress();
|
||||
auto pa = extfvk.DefaultAddress();
|
||||
|
||||
auto testNote = GetTestSaplingNote(pa, 50000);
|
||||
|
||||
|
@ -669,7 +669,7 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
|
|||
auto expsk = sk.expsk;
|
||||
auto extfvk = sk.ToXFVK();
|
||||
auto ivk = extfvk.fvk.in_viewing_key();
|
||||
auto pk = sk.DefaultAddress();
|
||||
auto pk = extfvk.DefaultAddress();
|
||||
|
||||
ASSERT_TRUE(wallet.AddSaplingZKey(sk));
|
||||
ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk));
|
||||
|
@ -829,7 +829,7 @@ TEST(WalletTests, SaplingNullifierIsSpent) {
|
|||
auto sk = GetTestMasterSaplingSpendingKey();
|
||||
auto expsk = sk.expsk;
|
||||
auto extfvk = sk.ToXFVK();
|
||||
auto pa = sk.DefaultAddress();
|
||||
auto pa = extfvk.DefaultAddress();
|
||||
|
||||
auto testNote = GetTestSaplingNote(pa, 50000);
|
||||
|
||||
|
@ -914,7 +914,7 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
|
|||
auto sk = GetTestMasterSaplingSpendingKey();
|
||||
auto expsk = sk.expsk;
|
||||
auto extfvk = sk.ToXFVK();
|
||||
auto pa = sk.DefaultAddress();
|
||||
auto pa = extfvk.DefaultAddress();
|
||||
|
||||
auto testNote = GetTestSaplingNote(pa, 50000);
|
||||
|
||||
|
@ -1045,7 +1045,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
|
|||
auto expsk = sk.expsk;
|
||||
auto extfvk = sk.ToXFVK();
|
||||
auto ivk = extfvk.fvk.in_viewing_key();
|
||||
auto pk = sk.DefaultAddress();
|
||||
auto pk = extfvk.DefaultAddress();
|
||||
|
||||
// Generate Sapling note A
|
||||
libzcash::SaplingNote note(pk, 50000, zip_212_enabled[ver]);
|
||||
|
@ -1841,13 +1841,13 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
|
|||
auto sk = m.Derive(0);
|
||||
auto expsk = sk.expsk;
|
||||
auto extfvk = sk.ToXFVK();
|
||||
auto pa = sk.DefaultAddress();
|
||||
auto pa = extfvk.DefaultAddress();
|
||||
|
||||
// Generate dummy recipient Sapling address
|
||||
auto sk2 = m.Derive(1);
|
||||
auto expsk2 = sk2.expsk;
|
||||
auto extfvk2 = sk2.ToXFVK();
|
||||
auto pa2 = sk2.DefaultAddress();
|
||||
auto pa2 = extfvk2.DefaultAddress();
|
||||
|
||||
auto testNote = GetTestSaplingNote(pa, 50000);
|
||||
|
||||
|
@ -1983,7 +1983,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
|
|||
auto expsk = sk.expsk;
|
||||
auto extfvk = sk.ToXFVK();
|
||||
auto ivk = extfvk.fvk.in_viewing_key();
|
||||
auto pk = sk.DefaultAddress();
|
||||
auto pk = extfvk.DefaultAddress();
|
||||
|
||||
ASSERT_TRUE(wallet.AddSaplingZKey(sk));
|
||||
ASSERT_TRUE(wallet.HaveSaplingSpendingKey(extfvk));
|
||||
|
|
|
@ -64,7 +64,7 @@ TEST(WalletZkeysTest, StoreAndLoadSaplingZkeys) {
|
|||
wallet.GetSaplingPaymentAddresses(addrs);
|
||||
EXPECT_EQ(2, addrs.size());
|
||||
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
|
||||
// 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();
|
||||
|
||||
// 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));
|
||||
|
||||
// manually add a diversified address
|
||||
|
@ -81,7 +81,7 @@ TEST(WalletZkeysTest, StoreAndLoadSaplingZkeys) {
|
|||
EXPECT_TRUE(wallet.AddSaplingIncomingViewingKey(ivk, dpa));
|
||||
|
||||
// verify wallet did add it
|
||||
EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(sk.DefaultAddress()));
|
||||
EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(sk.ToXFVK().DefaultAddress()));
|
||||
EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(dpa));
|
||||
|
||||
// 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
|
||||
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_TRUE(wallet.LoadSaplingPaymentAddress(dpa2, ivk2));
|
||||
EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(dpa2));
|
||||
|
@ -501,8 +501,8 @@ TEST(wallet_zkeys_tests, WriteCryptedSaplingZkeyDirectToDb) {
|
|||
wallet2.Unlock(strWalletPass);
|
||||
|
||||
EXPECT_TRUE(wallet2.GetSaplingExtendedSpendingKey(address, keyOut));
|
||||
ASSERT_EQ(address, keyOut.DefaultAddress());
|
||||
ASSERT_EQ(address, keyOut.ToXFVK().DefaultAddress());
|
||||
|
||||
EXPECT_TRUE(wallet2.GetSaplingExtendedSpendingKey(address2, keyOut));
|
||||
ASSERT_EQ(address2, keyOut.DefaultAddress());
|
||||
ASSERT_EQ(address2, keyOut.ToXFVK().DefaultAddress());
|
||||
}
|
||||
|
|
|
@ -665,7 +665,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importexport)
|
|||
|
||||
// create a random Sapling key locally; split between IVKs and spending keys.
|
||||
auto testSaplingSpendingKey = m.Derive(i);
|
||||
auto testSaplingPaymentAddress = testSaplingSpendingKey.DefaultAddress();
|
||||
auto testSaplingPaymentAddress = testSaplingSpendingKey.ToXFVK().DefaultAddress();
|
||||
if (i % 2 == 0) {
|
||||
std::string testSaplingAddr = keyIO.EncodePaymentAddress(testSaplingPaymentAddress);
|
||||
std::string testSaplingKey = keyIO.EncodeSpendingKey(testSaplingSpendingKey);
|
||||
|
|
|
@ -118,9 +118,9 @@ libzcash::SproutPaymentAddress CWallet::GenerateNewSproutZKey()
|
|||
// is not present. When using legacy HD seeds, the account index is determined
|
||||
// by trial of legacyHDChain.GetAccountCounter(); for unified addresses this must use
|
||||
// valued derived from legacyHDChain.unifiedAccountCounter
|
||||
std::optional<SaplingPaymentAddress> CWallet::GenerateNewLegacySaplingZKey()
|
||||
{
|
||||
// Try to get the seed
|
||||
std::optional<SaplingPaymentAddress> CWallet::GenerateNewLegacySaplingZKey() {
|
||||
AssertLockHeld(cs_wallet);
|
||||
|
||||
auto seedOpt = GetLegacyHDSeed();
|
||||
if (seedOpt.has_value()) {
|
||||
auto seed = seedOpt.value();
|
||||
|
@ -128,18 +128,33 @@ std::optional<SaplingPaymentAddress> CWallet::GenerateNewLegacySaplingZKey()
|
|||
legacyHDChain = CHDChain(seed.Fingerprint(), GetTime());
|
||||
}
|
||||
CHDChain& hdChain = legacyHDChain.value();
|
||||
|
||||
// loop until we find an unused account id
|
||||
while (true) {
|
||||
auto xsk = GenerateNewSaplingZKey(seed, hdChain.GetAccountCounter());
|
||||
if (!xsk.has_value()) {
|
||||
hdChain.IncrementAccountCounter();
|
||||
auto xsk = libzcash::SaplingExtendedSpendingKey::ForAccount(
|
||||
seed,
|
||||
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;
|
||||
} else {
|
||||
// Update the chain model in the database
|
||||
// Update the persisted chain information
|
||||
if (fFileBacked && !CWalletDB(strWalletFile).WriteLegacyHDChain(hdChain)) {
|
||||
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 {
|
||||
|
@ -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
|
||||
bool CWallet::AddSaplingZKey(const libzcash::SaplingExtendedSpendingKey &sk)
|
||||
{
|
||||
|
@ -394,6 +367,37 @@ bool CWallet::AddCryptedSaplingSpendingKey(const libzcash::SaplingExtendedFullVi
|
|||
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)
|
||||
{
|
||||
AssertLockHeld(cs_wallet); // mapKeyMetadata
|
||||
|
@ -2252,7 +2256,7 @@ void CWallet::GenerateNewSeed(Language language)
|
|||
{
|
||||
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();
|
||||
|
||||
|
@ -5468,7 +5472,7 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedS
|
|||
KeyIO keyIO(Params());
|
||||
{
|
||||
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
|
||||
if (m_wallet->HaveSaplingSpendingKey(extfvk)) {
|
||||
|
|
|
@ -829,6 +829,7 @@ public:
|
|||
std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
|
||||
std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapSproutZKeyMetadata;
|
||||
std::map<libzcash::SaplingIncomingViewingKey, CKeyMetadata> mapSaplingZKeyMetadata;
|
||||
//std::map<libzcash::UnifiedIncomingViewingKey, CKeyMetadata> mapUnifiedKeyMetadata;
|
||||
|
||||
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
|
||||
MasterKeyMap mapMasterKeys;
|
||||
|
@ -1100,6 +1101,12 @@ public:
|
|||
bool LoadCryptedSaplingZKey(const libzcash::SaplingExtendedFullViewingKey &extfvk,
|
||||
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
|
||||
* @return next transaction order id
|
||||
|
|
|
@ -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 */
|
||||
class CWalletDB : public CDB
|
||||
{
|
||||
|
|
|
@ -39,7 +39,7 @@ std::pair<std::string, PaymentAddress> AddressInfoFromSpendingKey::operator()(co
|
|||
return std::make_pair("sprout", sk.address());
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
return std::make_pair("sprout", sk.address());
|
||||
}
|
||||
std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(const SaplingExtendedFullViewingKey &sk) const {
|
||||
return std::make_pair("sapling", sk.DefaultAddress());
|
||||
std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(const SaplingExtendedFullViewingKey &xfvk) const {
|
||||
return std::make_pair("sapling", xfvk.DefaultAddress());
|
||||
}
|
||||
std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(const InvalidEncoding&) const {
|
||||
throw std::invalid_argument("Cannot derive default address from invalid viewing key");
|
||||
|
|
|
@ -19,20 +19,23 @@ const unsigned char ZCASH_HD_SEED_FP_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'};
|
||||
|
||||
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) {
|
||||
std::vector<unsigned char> entropy(len, 0);
|
||||
GetRandBytes(entropy.data(), len);
|
||||
const char* phrase = zip339_entropy_to_phrase(language, entropy.data(), len);
|
||||
std::vector<unsigned char> entropy(entropyLen, 0);
|
||||
GetRandBytes(entropy.data(), entropyLen);
|
||||
const char* phrase = zip339_entropy_to_phrase(language, entropy.data(), entropyLen);
|
||||
std::string mnemonic(phrase);
|
||||
zip339_free_phrase(phrase);
|
||||
MnemonicSeed seed(language, mnemonic);
|
||||
|
||||
// TODO: check for the validity of the Sapling spending key at account 0 for this seed.
|
||||
|
||||
return seed;
|
||||
// Verify that the seed data is valid entropy for unified spending keys at
|
||||
// account 0 and account 0x7FFFFFFE
|
||||
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_ret;
|
||||
CSerializeData addr_bytes(libzcash::SerializedSaplingPaymentAddressSize);
|
||||
CSerializeData addr_bytes_ret(libzcash::SerializedSaplingPaymentAddressSize);
|
||||
if (librustzcash_zip32_find_xfvk_address(
|
||||
reinterpret_cast<unsigned char*>(xfvk_bytes.data()),
|
||||
j_default.begin(), j_ret.begin(),
|
||||
reinterpret_cast<unsigned char*>(addr_bytes.data()))) {
|
||||
CDataStream ss_addr(addr_bytes, SER_NETWORK, PROTOCOL_VERSION);
|
||||
reinterpret_cast<unsigned char*>(addr_bytes_ret.data()))) {
|
||||
CDataStream ss_addr(addr_bytes_ret, SER_NETWORK, PROTOCOL_VERSION);
|
||||
libzcash::SaplingPaymentAddress addr;
|
||||
ss_addr >> addr;
|
||||
return addr;
|
||||
|
@ -161,6 +164,27 @@ SaplingExtendedSpendingKey SaplingExtendedSpendingKey::Derive(uint32_t i) const
|
|||
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 ret;
|
||||
|
@ -173,9 +197,34 @@ SaplingExtendedFullViewingKey SaplingExtendedSpendingKey::ToXFVK() const
|
|||
return ret;
|
||||
}
|
||||
|
||||
libzcash::SaplingPaymentAddress SaplingExtendedSpendingKey::DefaultAddress() const
|
||||
{
|
||||
return ToXFVK().DefaultAddress();
|
||||
std::optional<CExtKey> DeriveZip32TransparentSpendingKey(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId) {
|
||||
auto rawSeed = seed.RawSeed();
|
||||
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) {
|
||||
|
@ -188,4 +237,4 @@ std::optional<unsigned long> ParseZip32KeypathAccount(const std::string& keyPath
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
|
|
@ -6,8 +6,10 @@
|
|||
#define ZCASH_ZCASH_ADDRESS_ZIP32_H
|
||||
|
||||
#include "serialize.h"
|
||||
#include "key.h"
|
||||
#include "support/allocators/secure.h"
|
||||
#include "uint256.h"
|
||||
#include "utiltime.h"
|
||||
#include "zcash/address/sapling.hpp"
|
||||
#include "rust/zip339.h"
|
||||
|
||||
|
@ -65,7 +67,12 @@ public:
|
|||
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) {
|
||||
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.
|
||||
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 {
|
||||
|
||||
typedef blob88 diversifier_index_t;
|
||||
|
@ -212,13 +264,12 @@ struct SaplingExtendedSpendingKey {
|
|||
}
|
||||
|
||||
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;
|
||||
|
||||
SaplingExtendedFullViewingKey ToXFVK() const;
|
||||
|
||||
libzcash::SaplingPaymentAddress DefaultAddress() const;
|
||||
|
||||
friend bool operator==(const SaplingExtendedSpendingKey& a, const SaplingExtendedSpendingKey& b)
|
||||
{
|
||||
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<CExtKey> DeriveZip32TransparentSpendingKey(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId);
|
||||
|
||||
}
|
||||
|
||||
#endif // ZCASH_ZCASH_ADDRESS_ZIP32_H
|
||||
|
|
|
@ -375,7 +375,7 @@ CWalletTx CreateSaplingTxWithNoteData(const Consensus::Params& consensusParams,
|
|||
CBasicKeyStore& keyStore,
|
||||
const libzcash::SaplingExtendedSpendingKey &sk) {
|
||||
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 nullifier = testNote.note.nullifier(fvk, testNote.tree.witness().position()).value();
|
||||
|
||||
|
|
Loading…
Reference in New Issue