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);
}
{
auto addr = sk.DefaultAddress();
auto addr = sk.ToXFVK().DefaultAddress();
std::string addr_string = keyIO.EncodePaymentAddress(addr);
EXPECT_EQ(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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 */
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());
}
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");

View File

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

View File

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

View File

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