Derive random HD seeds from ZIP-339 seed phrases.

Add support for dump of legacy HD seeds & persistence of mnemonic-based seeds.
This commit is contained in:
Kris Nuttycombe 2021-08-19 15:32:24 -06:00
parent d6fc064018
commit 5677649af1
16 changed files with 561 additions and 204 deletions

View File

@ -16,35 +16,37 @@
TEST(KeystoreTests, StoreAndRetrieveHDSeed) {
CBasicKeyStore keyStore;
HDSeed seedOut;
// When we haven't set a seed, we shouldn't get one
EXPECT_FALSE(keyStore.HaveHDSeed());
EXPECT_FALSE(keyStore.GetHDSeed(seedOut));
EXPECT_FALSE(keyStore.HaveMnemonicSeed());
auto seedOut = keyStore.GetMnemonicSeed();
EXPECT_FALSE(seedOut.has_value());
// Generate a random seed
auto seed = HDSeed::Random();
auto seed = MnemonicSeed::Random();
// We should be able to set and retrieve the seed
ASSERT_TRUE(keyStore.SetHDSeed(seed));
EXPECT_TRUE(keyStore.HaveHDSeed());
ASSERT_TRUE(keyStore.GetHDSeed(seedOut));
EXPECT_EQ(seed, seedOut);
ASSERT_TRUE(keyStore.SetMnemonicSeed(seed));
EXPECT_TRUE(keyStore.HaveMnemonicSeed());
seedOut = keyStore.GetMnemonicSeed();
ASSERT_TRUE(seedOut.has_value());
EXPECT_EQ(seed, seedOut.value());
// Generate another random seed
auto seed2 = HDSeed::Random();
auto seed2 = MnemonicSeed::Random();
EXPECT_NE(seed, seed2);
// We should not be able to set and retrieve a different seed
EXPECT_FALSE(keyStore.SetHDSeed(seed2));
ASSERT_TRUE(keyStore.GetHDSeed(seedOut));
EXPECT_EQ(seed, seedOut);
EXPECT_FALSE(keyStore.SetMnemonicSeed(seed2));
seedOut = keyStore.GetMnemonicSeed();
ASSERT_TRUE(seedOut.has_value());
EXPECT_EQ(seed, seedOut.value());
}
TEST(KeystoreTests, SaplingKeys) {
// ["sk, ask, nsk, ovk, ak, nk, ivk, default_d, default_pk_d, note_v, note_r, note_cm, note_pos, note_nf"],
UniValue sapling_keys = read_json(MAKE_STRING(json_tests::sapling_key_components));
// Skipping over comments in sapling_key_components.json file
for (size_t i = 2; i < 12; i++) {
uint256 skSeed, ask, nsk, ovk, ak, nk, ivk;
@ -55,40 +57,40 @@ TEST(KeystoreTests, SaplingKeys) {
ak.SetHex(sapling_keys[i][4].getValStr());
nk.SetHex(sapling_keys[i][5].getValStr());
ivk.SetHex(sapling_keys[i][6].getValStr());
libzcash::diversifier_t default_d;
std::copy_n(ParseHex(sapling_keys[i][7].getValStr()).begin(), 11, default_d.begin());
uint256 default_pk_d;
default_pk_d.SetHex(sapling_keys[i][8].getValStr());
auto sk = libzcash::SaplingSpendingKey(skSeed);
// Check that expanded spending key from primitives and from sk are the same
auto exp_sk_2 = libzcash::SaplingExpandedSpendingKey(ask, nsk, ovk);
auto exp_sk = sk.expanded_spending_key();
EXPECT_EQ(exp_sk, exp_sk_2);
// Check that full viewing key derived from sk and expanded sk are the same
auto full_viewing_key = sk.full_viewing_key();
EXPECT_EQ(full_viewing_key, exp_sk.full_viewing_key());
// Check that full viewing key from primitives and from sk are the same
auto full_viewing_key_2 = libzcash::SaplingFullViewingKey(ak, nk, ovk);
EXPECT_EQ(full_viewing_key, full_viewing_key_2);
// Check that incoming viewing key from primitives and from sk are the same
auto in_viewing_key = full_viewing_key.in_viewing_key();
auto in_viewing_key_2 = libzcash::SaplingIncomingViewingKey(ivk);
EXPECT_EQ(in_viewing_key, in_viewing_key_2);
// Check that the default address from primitives and from sk method are the same
auto default_addr = sk.default_address();
auto addrOpt2 = in_viewing_key.address(default_d);
EXPECT_TRUE(addrOpt2);
auto default_addr_2 = addrOpt2.value();
EXPECT_EQ(default_addr, default_addr_2);
auto default_addr_3 = libzcash::SaplingPaymentAddress(default_d, default_pk_d);
EXPECT_EQ(default_addr_2, default_addr_3);
EXPECT_EQ(default_addr, default_addr_3);
@ -285,20 +287,22 @@ TEST(KeystoreTests, StoreAndRetrieveHDSeedInEncryptedStore) {
TestCCryptoKeyStore keyStore;
CKeyingMaterial vMasterKey(32, 0);
GetRandBytes(vMasterKey.data(), 32);
HDSeed seedOut;
// 1) Test adding a seed to an unencrypted key store, then encrypting it
auto seed = HDSeed::Random();
EXPECT_FALSE(keyStore.HaveHDSeed());
EXPECT_FALSE(keyStore.GetHDSeed(seedOut));
auto seed = MnemonicSeed::Random();
EXPECT_FALSE(keyStore.HaveMnemonicSeed());
auto seedOut = keyStore.GetMnemonicSeed();
EXPECT_FALSE(seedOut.has_value());
ASSERT_TRUE(keyStore.SetHDSeed(seed));
EXPECT_TRUE(keyStore.HaveHDSeed());
ASSERT_TRUE(keyStore.GetHDSeed(seedOut));
EXPECT_EQ(seed, seedOut);
ASSERT_TRUE(keyStore.SetMnemonicSeed(seed));
EXPECT_TRUE(keyStore.HaveMnemonicSeed());
seedOut = keyStore.GetMnemonicSeed();
ASSERT_TRUE(seedOut.has_value());
EXPECT_EQ(seed, seedOut.value());
ASSERT_TRUE(keyStore.EncryptKeys(vMasterKey));
EXPECT_FALSE(keyStore.GetHDSeed(seedOut));
seedOut = keyStore.GetMnemonicSeed();
EXPECT_FALSE(seedOut.has_value());
// Unlocking with a random key should fail
CKeyingMaterial vRandomKey(32, 0);
@ -312,15 +316,17 @@ TEST(KeystoreTests, StoreAndRetrieveHDSeedInEncryptedStore) {
// Unlocking with vMasterKey should succeed
ASSERT_TRUE(keyStore.Unlock(vMasterKey));
ASSERT_TRUE(keyStore.GetHDSeed(seedOut));
EXPECT_EQ(seed, seedOut);
seedOut = keyStore.GetMnemonicSeed();
ASSERT_TRUE(seedOut.has_value());
EXPECT_EQ(seed, seedOut.value());
// 2) Test replacing the seed in an already-encrypted key store fails
auto seed2 = HDSeed::Random();
EXPECT_FALSE(keyStore.SetHDSeed(seed2));
EXPECT_TRUE(keyStore.HaveHDSeed());
ASSERT_TRUE(keyStore.GetHDSeed(seedOut));
EXPECT_EQ(seed, seedOut);
auto seed2 = MnemonicSeed::Random();
EXPECT_FALSE(keyStore.SetMnemonicSeed(seed2));
EXPECT_TRUE(keyStore.HaveMnemonicSeed());
seedOut = keyStore.GetMnemonicSeed();
ASSERT_TRUE(seedOut.has_value());
EXPECT_EQ(seed, seedOut.value());
// 3) Test adding a new seed to an already-encrypted key store
TestCCryptoKeyStore keyStore2;
@ -331,14 +337,16 @@ TEST(KeystoreTests, StoreAndRetrieveHDSeedInEncryptedStore) {
ASSERT_TRUE(keyStore2.EncryptKeys(vMasterKey));
ASSERT_TRUE(keyStore2.Unlock(vMasterKey));
EXPECT_FALSE(keyStore2.HaveHDSeed());
EXPECT_FALSE(keyStore2.GetHDSeed(seedOut));
EXPECT_FALSE(keyStore2.HaveMnemonicSeed());
seedOut = keyStore2.GetMnemonicSeed();
EXPECT_FALSE(seedOut.has_value());
auto seed3 = HDSeed::Random();
ASSERT_TRUE(keyStore2.SetHDSeed(seed3));
EXPECT_TRUE(keyStore2.HaveHDSeed());
ASSERT_TRUE(keyStore2.GetHDSeed(seedOut));
EXPECT_EQ(seed3, seedOut);
auto seed3 = MnemonicSeed::Random();
ASSERT_TRUE(keyStore2.SetMnemonicSeed(seed3));
EXPECT_TRUE(keyStore2.HaveMnemonicSeed());
seedOut = keyStore2.GetMnemonicSeed();
ASSERT_TRUE(seedOut.has_value());
EXPECT_EQ(seed3, seedOut.value());
}
TEST(KeystoreTests, StoreAndRetrieveSpendingKeyInEncryptedStore) {

View File

@ -11,6 +11,9 @@ TEST(ZIP32, TestVectors) {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
HDSeed seed(rawSeed);
// TODO: Regenerate test vectors using a BIP-44-derived seed.
// std::string mnemonic("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art");
// HDSeed seed(English, mnemonic);
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
EXPECT_EQ(m.depth, 0);

View File

@ -30,33 +30,50 @@ bool CBasicKeyStore::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) con
return true;
}
bool CBasicKeyStore::SetHDSeed(const HDSeed& seed)
bool CBasicKeyStore::SetMnemonicSeed(const MnemonicSeed& seed)
{
LOCK(cs_KeyStore);
if (!hdSeed.IsNull()) {
if (mnemonicSeed.has_value()) {
// Don't allow an existing seed to be changed. We can maybe relax this
// restriction later once we have worked out the UX implications.
return false;
}
hdSeed = seed;
mnemonicSeed = seed;
return true;
}
bool CBasicKeyStore::HaveHDSeed() const
bool CBasicKeyStore::HaveMnemonicSeed() const
{
LOCK(cs_KeyStore);
return !hdSeed.IsNull();
return mnemonicSeed.has_value();
}
bool CBasicKeyStore::GetHDSeed(HDSeed& seedOut) const
std::optional<MnemonicSeed> CBasicKeyStore::GetMnemonicSeed() const
{
LOCK(cs_KeyStore);
if (hdSeed.IsNull()) {
return mnemonicSeed;
}
bool CBasicKeyStore::SetLegacyHDSeed(const HDSeed& seed)
{
if (legacySeed.has_value()) {
// Don't allow an existing seed to be changed.
return false;
} else {
seedOut = hdSeed;
return true;
}
legacySeed = seed;
return true;
}
bool CBasicKeyStore::HaveLegacyHDSeed() const
{
LOCK(cs_KeyStore);
return legacySeed.has_value();
}
std::optional<HDSeed> CBasicKeyStore::GetLegacyHDSeed() const
{
LOCK(cs_KeyStore);
return legacySeed;
}
bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
@ -151,7 +168,7 @@ bool CBasicKeyStore::AddSproutSpendingKey(const libzcash::SproutSpendingKey &sk)
return true;
}
//! Sapling
//! Sapling
bool CBasicKeyStore::AddSaplingSpendingKey(
const libzcash::SaplingExtendedSpendingKey &sk)
{
@ -187,7 +204,7 @@ bool CBasicKeyStore::AddSaplingFullViewingKey(
return CBasicKeyStore::AddSaplingIncomingViewingKey(ivk, extfvk.DefaultAddress());
}
// This function updates the wallet's internal address->ivk map.
// This function updates the wallet's internal address->ivk map.
// If we add an address that is already in the map, the map will
// remain unchanged as each address only has one ivk.
bool CBasicKeyStore::AddSaplingIncomingViewingKey(
@ -265,7 +282,7 @@ bool CBasicKeyStore::GetSaplingIncomingViewingKey(const libzcash::SaplingPayment
return false;
}
bool CBasicKeyStore::GetSaplingExtendedSpendingKey(const libzcash::SaplingPaymentAddress &addr,
bool CBasicKeyStore::GetSaplingExtendedSpendingKey(const libzcash::SaplingPaymentAddress &addr,
libzcash::SaplingExtendedSpendingKey &extskOut) const {
libzcash::SaplingIncomingViewingKey ivk;
libzcash::SaplingExtendedFullViewingKey extfvk;

View File

@ -25,11 +25,16 @@ protected:
public:
virtual ~CKeyStore() {}
//! Set the HD seed for this keystore
virtual bool SetHDSeed(const HDSeed& seed) =0;
virtual bool HaveHDSeed() const =0;
//! Get the HD seed for this keystore
virtual bool GetHDSeed(HDSeed& seedOut) const =0;
//! Set the mnemonic HD seed for this keystore
virtual bool SetMnemonicSeed(const MnemonicSeed& seed) =0;
virtual bool HaveMnemonicSeed() const =0;
//! Get the mnemonic HD seed for this keystore
virtual std::optional<MnemonicSeed> GetMnemonicSeed() const =0;
//! Set the legacy HD seed for this keystore
virtual bool SetLegacyHDSeed(const HDSeed& seed) =0;
//! Get the legacy HD seed for this keystore
virtual std::optional<HDSeed> GetLegacyHDSeed() const =0;
//! Add a key to the store.
virtual bool AddKeyPubKey(const CKey &key, const CPubKey &pubkey) =0;
@ -59,10 +64,10 @@ public:
virtual bool HaveSproutSpendingKey(const libzcash::SproutPaymentAddress &address) const =0;
virtual bool GetSproutSpendingKey(const libzcash::SproutPaymentAddress &address, libzcash::SproutSpendingKey& skOut) const =0;
virtual void GetSproutPaymentAddresses(std::set<libzcash::SproutPaymentAddress> &setAddress) const =0;
//! Add a Sapling spending key to the store.
virtual bool AddSaplingSpendingKey(const libzcash::SaplingExtendedSpendingKey &sk) =0;
//! Check whether a Sapling spending key corresponding to a given Sapling viewing key is present in the store.
virtual bool HaveSaplingSpendingKey(
const libzcash::SaplingExtendedFullViewingKey &extfvk) const =0;
@ -74,16 +79,16 @@ public:
virtual bool AddSaplingFullViewingKey(const libzcash::SaplingExtendedFullViewingKey &extfvk) =0;
virtual bool HaveSaplingFullViewingKey(const libzcash::SaplingIncomingViewingKey &ivk) const =0;
virtual bool GetSaplingFullViewingKey(
const libzcash::SaplingIncomingViewingKey &ivk,
const libzcash::SaplingIncomingViewingKey &ivk,
libzcash::SaplingExtendedFullViewingKey& extfvkOut) const =0;
//! Sapling incoming viewing keys
//! Sapling incoming viewing keys
virtual bool AddSaplingIncomingViewingKey(
const libzcash::SaplingIncomingViewingKey &ivk,
const libzcash::SaplingPaymentAddress &addr) =0;
virtual bool HaveSaplingIncomingViewingKey(const libzcash::SaplingPaymentAddress &addr) const =0;
virtual bool GetSaplingIncomingViewingKey(
const libzcash::SaplingPaymentAddress &addr,
const libzcash::SaplingPaymentAddress &addr,
libzcash::SaplingIncomingViewingKey& ivkOut) const =0;
virtual void GetSaplingPaymentAddresses(std::set<libzcash::SaplingPaymentAddress> &setAddress) const =0;
@ -112,14 +117,15 @@ typedef std::map<
typedef std::map<
libzcash::SaplingIncomingViewingKey,
libzcash::SaplingExtendedFullViewingKey> SaplingFullViewingKeyMap;
// Only maps from default addresses to ivk, may need to be reworked when adding diversified addresses.
// Only maps from default addresses to ivk, may need to be reworked when adding diversified addresses.
typedef std::map<libzcash::SaplingPaymentAddress, libzcash::SaplingIncomingViewingKey> SaplingIncomingViewingKeyMap;
/** Basic key store, that keeps keys in an address->secret map */
class CBasicKeyStore : public CKeyStore
{
protected:
HDSeed hdSeed;
std::optional<MnemonicSeed> mnemonicSeed;
std::optional<HDSeed> legacySeed;
KeyMap mapKeys;
WatchKeyMap mapWatchKeys;
ScriptMap mapScripts;
@ -133,9 +139,13 @@ protected:
SaplingIncomingViewingKeyMap mapSaplingIncomingViewingKeys;
public:
bool SetHDSeed(const HDSeed& seed);
bool HaveHDSeed() const;
bool GetHDSeed(HDSeed& seedOut) const;
bool SetMnemonicSeed(const MnemonicSeed& seed);
bool HaveMnemonicSeed() const;
std::optional<MnemonicSeed> GetMnemonicSeed() const;
bool SetLegacyHDSeed(const HDSeed& seed);
bool HaveLegacyHDSeed() const;
std::optional<HDSeed> GetLegacyHDSeed() const;
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const;
@ -235,7 +245,7 @@ public:
}
}
//! Sapling
//! Sapling
bool AddSaplingSpendingKey(const libzcash::SaplingExtendedSpendingKey &sk);
bool HaveSaplingSpendingKey(const libzcash::SaplingExtendedFullViewingKey &extfvk) const
{
@ -274,13 +284,13 @@ public:
const libzcash::SaplingPaymentAddress &addr);
virtual bool HaveSaplingIncomingViewingKey(const libzcash::SaplingPaymentAddress &addr) const;
virtual bool GetSaplingIncomingViewingKey(
const libzcash::SaplingPaymentAddress &addr,
const libzcash::SaplingPaymentAddress &addr,
libzcash::SaplingIncomingViewingKey& ivkOut) const;
bool GetSaplingExtendedSpendingKey(
const libzcash::SaplingPaymentAddress &addr,
const libzcash::SaplingPaymentAddress &addr,
libzcash::SaplingExtendedSpendingKey &extskOut) const;
void GetSaplingPaymentAddresses(std::set<libzcash::SaplingPaymentAddress> &setAddress) const
{
setAddress.clear();
@ -307,7 +317,7 @@ typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMate
typedef std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char> > > CryptedKeyMap;
typedef std::map<libzcash::SproutPaymentAddress, std::vector<unsigned char> > CryptedSproutSpendingKeyMap;
//! Sapling
//! Sapling
typedef std::map<libzcash::SaplingExtendedFullViewingKey, std::vector<unsigned char> > CryptedSaplingSpendingKeyMap;
#endif // BITCOIN_KEYSTORE_H

View File

@ -316,8 +316,8 @@ void RegtestDeactivateNU5() {
}
libzcash::SaplingExtendedSpendingKey GetTestMasterSaplingSpendingKey() {
std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
HDSeed seed(rawSeed);
std::string mnemonic("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art");
MnemonicSeed seed(English, mnemonic);
return libzcash::SaplingExtendedSpendingKey::Master(seed);
}

View File

@ -130,21 +130,56 @@ static bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<u
return cKeyCrypter.Decrypt(vchCiphertext, *((CKeyingMaterial*)&vchPlaintext));
}
static bool DecryptHDSeed(
static CKeyingMaterial EncryptMnemonicSeed(const MnemonicSeed& seed)
{
CSecureDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << seed;
CKeyingMaterial vchSecret(ss.begin(), ss.end());
return vchSecret;
}
static std::optional<MnemonicSeed> DecryptMnemonicSeed(
const CKeyingMaterial& vMasterKey,
const std::vector<unsigned char>& vchCryptedSecret,
const uint256& seedFp,
HDSeed& seed)
const uint256& seedFp)
{
CKeyingMaterial vchSecret;
// Use seed's fingerprint as IV
// TODO: Handle IV properly when we make encryption a supported feature
if(!DecryptSecret(vMasterKey, vchCryptedSecret, seedFp, vchSecret))
return false;
if(DecryptSecret(vMasterKey, vchCryptedSecret, seedFp, vchSecret)) {
CSecureDataStream ss(vchSecret, SER_NETWORK, PROTOCOL_VERSION);
auto seed = MnemonicSeed::Read(ss);
if (seed.Fingerprint() == seedFp) {
return seed;
} else {
return std::nullopt;
}
} else {
return std::nullopt;
}
}
seed = HDSeed(vchSecret);
return seed.Fingerprint() == seedFp;
static std::optional<HDSeed> DecryptLegacyHDSeed(
const CKeyingMaterial& vMasterKey,
const std::vector<unsigned char>& vchCryptedSecret,
const uint256& seedFp)
{
CKeyingMaterial vchSecret;
// Use seed's fingerprint as IV
// TODO: Handle IV properly when we make encryption a supported feature
if(DecryptSecret(vMasterKey, vchCryptedSecret, seedFp, vchSecret)) {
auto seed = HDSeed(vchSecret);
if (seed.Fingerprint() == seedFp) {
return seed;
} else {
return std::nullopt;
}
} else {
return std::nullopt;
}
}
static bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key)
@ -227,9 +262,22 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
bool keyPass = false;
bool keyFail = false;
if (!cryptedHDSeed.first.IsNull()) {
HDSeed seed;
if (!DecryptHDSeed(vMasterKeyIn, cryptedHDSeed.second, cryptedHDSeed.first, seed))
if (!cryptedMnemonicSeed.first.IsNull()) {
// Check that we can successfully decrypt the mnemonic seed, if present
auto seed = DecryptMnemonicSeed(vMasterKeyIn, cryptedMnemonicSeed.second, cryptedMnemonicSeed.first);
if (!seed.has_value())
{
keyFail = true;
} else {
keyPass = true;
}
}
if (cryptedLegacySeed.has_value()) {
// Check that we can successfully decrypt the legacy seed, if present
auto seed = DecryptLegacyHDSeed(vMasterKeyIn,
cryptedLegacySeed.value().second,
cryptedLegacySeed.value().first);
if (!seed.has_value())
{
keyFail = true;
} else {
@ -295,71 +343,146 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
return true;
}
bool CCryptoKeyStore::SetHDSeed(const HDSeed& seed)
//
// Mnemonic HD seeds
//
bool CCryptoKeyStore::SetMnemonicSeed(const MnemonicSeed& seed)
{
{
LOCK(cs_KeyStore);
if (!fUseCrypto) {
return CBasicKeyStore::SetHDSeed(seed);
return CBasicKeyStore::SetMnemonicSeed(seed);
}
if (IsLocked())
return false;
std::vector<unsigned char> vchCryptedSecret;
// Use seed's fingerprint as IV
// TODO: Handle this properly when we make encryption a supported feature
auto seedFp = seed.Fingerprint();
if (!EncryptSecret(vMasterKey, seed.RawSeed(), seedFp, vchCryptedSecret))
CKeyingMaterial vchSecret = EncryptMnemonicSeed(seed);
std::vector<unsigned char> vchCryptedSecret;
if (!EncryptSecret(vMasterKey, vchSecret, seedFp, vchCryptedSecret))
return false;
// This will call into CWallet to store the crypted seed to disk
if (!SetCryptedHDSeed(seedFp, vchCryptedSecret))
if (!SetCryptedMnemonicSeed(seedFp, vchCryptedSecret))
return false;
}
return true;
}
bool CCryptoKeyStore::SetCryptedHDSeed(
bool CCryptoKeyStore::SetCryptedMnemonicSeed(
const uint256& seedFp,
const std::vector<unsigned char>& vchCryptedSecret)
{
LOCK(cs_KeyStore);
if (!fUseCrypto) {
if (!fUseCrypto || !cryptedMnemonicSeed.first.IsNull()) {
// Require encryption & don't allow an existing seed to be changed.
return false;
} else {
cryptedMnemonicSeed = std::make_pair(seedFp, vchCryptedSecret);
return true;
}
}
if (!cryptedHDSeed.first.IsNull()) {
// Don't allow an existing seed to be changed. We can maybe relax this
// restriction later once we have worked out the UX implications.
return false;
bool CCryptoKeyStore::HaveMnemonicSeed() const
{
LOCK(cs_KeyStore);
if (fUseCrypto) {
return !cryptedMnemonicSeed.second.empty();
} else {
return CBasicKeyStore::HaveMnemonicSeed();
}
}
cryptedHDSeed = std::make_pair(seedFp, vchCryptedSecret);
std::optional<MnemonicSeed> CCryptoKeyStore::GetMnemonicSeed() const
{
LOCK(cs_KeyStore);
if (fUseCrypto) {
if (cryptedMnemonicSeed.second.empty()) {
return std::nullopt;
} else {
return DecryptMnemonicSeed(vMasterKey, cryptedMnemonicSeed.second, cryptedMnemonicSeed.first);
}
} else {
return CBasicKeyStore::GetMnemonicSeed();
}
}
//
// Legacy HD seeds
//
bool CCryptoKeyStore::SetLegacyHDSeed(const HDSeed& seed)
{
{
LOCK(cs_KeyStore);
if (!fUseCrypto) {
return CBasicKeyStore::SetLegacyHDSeed(seed);
}
if (IsLocked())
return false;
// Use seed's fingerprint as IV
// TODO: Handle this properly when we make encryption a supported feature
auto seedFp = seed.Fingerprint();
std::vector<unsigned char> vchCryptedSecret;
if (!EncryptSecret(vMasterKey, seed.RawSeed(), seedFp, vchCryptedSecret))
return false;
// This will call into CWallet to store the crypted seed to disk
if (!SetCryptedLegacyHDSeed(seedFp, vchCryptedSecret))
return false;
}
return true;
}
bool CCryptoKeyStore::HaveHDSeed() const
bool CCryptoKeyStore::SetCryptedLegacyHDSeed(
const uint256& seedFp,
const std::vector<unsigned char>& vchCryptedSecret)
{
LOCK(cs_KeyStore);
if (!fUseCrypto)
return CBasicKeyStore::HaveHDSeed();
return !cryptedHDSeed.second.empty();
}
bool CCryptoKeyStore::GetHDSeed(HDSeed& seedOut) const
{
LOCK(cs_KeyStore);
if (!fUseCrypto)
return CBasicKeyStore::GetHDSeed(seedOut);
if (cryptedHDSeed.second.empty())
if (!fUseCrypto || cryptedLegacySeed.has_value()) {
// Require encryption & don't allow an existing seed to be changed.
return false;
return DecryptHDSeed(vMasterKey, cryptedHDSeed.second, cryptedHDSeed.first, seedOut);
} else {
cryptedLegacySeed = std::make_pair(seedFp, vchCryptedSecret);
return true;
}
}
bool CCryptoKeyStore::HaveLegacyHDSeed() const
{
LOCK(cs_KeyStore);
if (fUseCrypto) {
return cryptedLegacySeed.has_value();
} else {
return CBasicKeyStore::HaveLegacyHDSeed();
}
}
std::optional<HDSeed> CCryptoKeyStore::GetLegacyHDSeed() const {
LOCK(cs_KeyStore);
if (fUseCrypto) {
if (cryptedLegacySeed.has_value()) {
const auto cryptedPair = cryptedLegacySeed.value();
return DecryptLegacyHDSeed(vMasterKey, cryptedPair.second, cryptedPair.first);
} else {
return std::nullopt;
}
} else {
return CBasicKeyStore::GetLegacyHDSeed();
}
}
//
// Transparent public keys
//
bool CCryptoKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
{
LOCK(cs_KeyStore);
@ -420,6 +543,10 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co
return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);
}
//
// Sprout & Sapling keys
//
bool CCryptoKeyStore::AddSproutSpendingKey(const libzcash::SproutSpendingKey &sk)
{
LOCK(cs_KeyStore);
@ -536,21 +663,38 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
return false;
fUseCrypto = true;
if (!hdSeed.IsNull()) {
if (mnemonicSeed.has_value()) {
{
std::vector<unsigned char> vchCryptedSecret;
// Use seed's fingerprint as IV
// TODO: Handle this properly when we make encryption a supported feature
auto seedFp = hdSeed.Fingerprint();
if (!EncryptSecret(vMasterKeyIn, hdSeed.RawSeed(), seedFp, vchCryptedSecret)) {
auto seedFp = mnemonicSeed.value().Fingerprint();
auto seedData = EncryptMnemonicSeed(mnemonicSeed.value());
if (!EncryptSecret(vMasterKeyIn, seedData, seedFp, vchCryptedSecret)) {
return false;
}
// This will call into CWallet to store the crypted seed to disk
if (!SetCryptedHDSeed(seedFp, vchCryptedSecret)) {
if (!SetCryptedMnemonicSeed(seedFp, vchCryptedSecret)) {
return false;
}
}
hdSeed = HDSeed();
mnemonicSeed = std::nullopt;
}
if (legacySeed.has_value()) {
{
std::vector<unsigned char> vchCryptedSecret;
// Use seed's fingerprint as IV
// TODO: Handle this properly when we make encryption a supported feature
auto seedFp = legacySeed.value().Fingerprint();
if (!EncryptSecret(vMasterKeyIn, legacySeed.value().RawSeed(), seedFp, vchCryptedSecret)) {
return false;
}
// This will call into CWallet to store the crypted seed to disk
if (!SetCryptedLegacyHDSeed(seedFp, vchCryptedSecret)) {
return false;
}
}
legacySeed = std::nullopt;
}
for (KeyMap::value_type& mKey : mapKeys)
{

View File

@ -131,7 +131,8 @@ public:
class CCryptoKeyStore : public CBasicKeyStore
{
private:
std::pair<uint256, std::vector<unsigned char>> cryptedHDSeed;
std::pair<uint256, std::vector<unsigned char>> cryptedMnemonicSeed;
std::optional<std::pair<uint256, std::vector<unsigned char>>> cryptedLegacySeed;
CryptedKeyMap mapCryptedKeys;
CryptedSproutSpendingKeyMap mapCryptedSproutSpendingKeys;
CryptedSaplingSpendingKeyMap mapCryptedSaplingSpendingKeys;
@ -172,10 +173,15 @@ public:
bool Lock();
virtual bool SetCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char> &vchCryptedSecret);
bool SetHDSeed(const HDSeed& seed);
bool HaveHDSeed() const;
bool GetHDSeed(HDSeed& seedOut) const;
bool SetMnemonicSeed(const MnemonicSeed& seed);
virtual bool SetCryptedMnemonicSeed(const uint256& seedFp, const std::vector<unsigned char> &vchCryptedSecret);
bool HaveMnemonicSeed() const;
std::optional<MnemonicSeed> GetMnemonicSeed() const;
bool SetLegacyHDSeed(const HDSeed& seed);
virtual bool SetCryptedLegacyHDSeed(const uint256& seedFp, const std::vector<unsigned char> &vchCryptedSecret);
bool HaveLegacyHDSeed() const;
std::optional<HDSeed> GetLegacyHDSeed() const;
virtual bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
@ -230,7 +236,7 @@ public:
mi++;
}
}
//! Sapling
//! Sapling
virtual bool AddCryptedSaplingSpendingKey(
const libzcash::SaplingExtendedFullViewingKey &extfvk,
const std::vector<unsigned char> &vchCryptedSecret);

View File

@ -32,9 +32,9 @@ TEST(WalletZkeysTest, StoreAndLoadSaplingZkeys) {
EXPECT_ANY_THROW(wallet.GenerateNewSaplingZKey());
// Load the all-zeroes seed
CKeyingMaterial rawSeed(32, 0);
HDSeed seed(rawSeed);
wallet.LoadHDSeed(seed);
std::string mnemonic("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art");
MnemonicSeed seed(English, mnemonic);
wallet.LoadMnemonicSeed(seed);
// Now this call succeeds
auto address = wallet.GenerateNewSaplingZKey();
@ -428,7 +428,7 @@ TEST(wallet_zkeys_tests, WriteCryptedSaplingZkeyDirectToDb) {
// No default CPubKey set
ASSERT_TRUE(fFirstRun);
ASSERT_FALSE(wallet.HaveHDSeed());
ASSERT_FALSE(wallet.HaveMnemonicSeed());
wallet.GenerateNewSeed();
// wallet should be empty
@ -477,7 +477,7 @@ TEST(wallet_zkeys_tests, WriteCryptedSaplingZkeyDirectToDb) {
// Confirm it's not the same as the other wallet
ASSERT_TRUE(&wallet != &wallet2);
ASSERT_TRUE(wallet2.HaveHDSeed());
ASSERT_TRUE(wallet2.HaveMnemonicSeed());
// wallet should have three addresses
wallet2.GetSaplingPaymentAddresses(addrs);

View File

@ -67,7 +67,7 @@ std::string DecodeDumpString(const std::string &str) {
for (unsigned int pos = 0; pos < str.length(); pos++) {
unsigned char c = str[pos];
if (c == '%' && pos+2 < str.length()) {
c = (((str[pos+1]>>6)*9+((str[pos+1]-'0')&15)) << 4) |
c = (((str[pos+1]>>6)*9+((str[pos+1]-'0')&15)) << 4) |
((str[pos+2]>>6)*9+((str[pos+2]-'0')&15));
pos += 2;
}
@ -80,7 +80,7 @@ UniValue importprivkey(const UniValue& params, bool fHelp)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() < 1 || params.size() > 3)
throw runtime_error(
"importprivkey \"zcashprivkey\" ( \"label\" rescan )\n"
@ -189,7 +189,7 @@ UniValue importaddress(const UniValue& params, bool fHelp)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() < 1 || params.size() > 4)
throw runtime_error(
"importaddress \"address\" ( \"label\" rescan p2sh )\n"
@ -338,7 +338,7 @@ UniValue importwallet(const UniValue& params, bool fHelp)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() != 1)
throw runtime_error(
"importwallet \"filename\"\n"
@ -476,7 +476,7 @@ UniValue dumpprivkey(const UniValue& params, bool fHelp)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() != 1)
throw runtime_error(
"dumpprivkey \"t-addr\"\n"
@ -520,7 +520,7 @@ UniValue z_exportwallet(const UniValue& params, bool fHelp)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() != 1)
throw runtime_error(
"z_exportwallet \"filename\"\n"
@ -609,13 +609,27 @@ UniValue dumpwallet_impl(const UniValue& params, bool fDumpZKeys)
file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime()));
file << strprintf("# * Best block at time of backup was %i (%s),\n", chainActive.Height(), chainActive.Tip()->GetBlockHash().ToString());
file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime()));
{
HDSeed hdSeed;
pwalletMain->GetHDSeed(hdSeed);
auto rawSeed = hdSeed.RawSeed();
file << strprintf("# HDSeed=%s fingerprint=%s", HexStr(rawSeed.begin(), rawSeed.end()), hdSeed.Fingerprint().GetHex());
file << "\n";
std::optional<MnemonicSeed> hdSeed = pwalletMain->GetMnemonicSeed();
if (hdSeed.has_value()) {
auto mSeed = hdSeed.value();
file << strprintf(
"# Recovery Phrase=\"%s\" \n# language=%s \n# fingerprint=%s\n",
mSeed.GetMnemonic(),
MnemonicSeed::LanguageName(mSeed.GetLanguage()),
mSeed.Fingerprint().GetHex()
);
}
std::optional<HDSeed> legacySeed = pwalletMain->GetLegacyHDSeed();
if (legacySeed.has_value()) {
auto rawSeed = legacySeed.value().RawSeed();
file << strprintf("# Legacy HDSeed=%s fingerprint=%s\n",
HexStr(rawSeed.begin(), rawSeed.end()),
legacySeed.value().Fingerprint().GetHex()
);
}
file << "\n";
for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
const CKeyID &keyid = it->second;
@ -769,10 +783,10 @@ UniValue z_importkey(const UniValue& params, bool fHelp)
if (addResult == KeyNotAdded) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet");
}
// whenever a key is imported, we need to scan the whole chain
pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value'
// We want to scan for transactions and notes
if (fRescan) {
pwalletMain->ScanForWalletTransactions(chainActive[nRescanHeight], true);

View File

@ -790,7 +790,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_getnewaddress) {
using namespace libzcash;
UniValue addr;
if (!pwalletMain->HaveHDSeed()) {
if (!pwalletMain->HaveMnemonicSeed()) {
pwalletMain->GenerateNewSeed();
}
@ -1449,7 +1449,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling)
LOCK2(cs_main, pwalletMain->cs_wallet);
if (!pwalletMain->HaveHDSeed()) {
if (!pwalletMain->HaveMnemonicSeed()) {
pwalletMain->GenerateNewSeed();
}
@ -1523,11 +1523,11 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling)
// We should be able to decrypt the outCiphertext with the ovk
// generated for transparent addresses
HDSeed seed;
BOOST_ASSERT(pwalletMain->GetHDSeed(seed));
std::optional<MnemonicSeed> seed = pwalletMain->GetMnemonicSeed();
BOOST_ASSERT(seed.has_value());
BOOST_CHECK(AttemptSaplingOutDecryption(
tx.vShieldedOutput[0].outCiphertext,
ovkForShieldingFromTaddr(seed),
ovkForShieldingFromTaddr(seed.value()),
tx.vShieldedOutput[0].cv,
tx.vShieldedOutput[0].cmu,
tx.vShieldedOutput[0].ephemeralKey));
@ -1608,7 +1608,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_sapzkeys)
UniValue retValue;
int n = 100;
if(!pwalletMain->HaveHDSeed())
if(!pwalletMain->HaveMnemonicSeed())
{
pwalletMain->GenerateNewSeed();
}
@ -1657,7 +1657,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_sapzkeys)
// Verify the key has been added
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_listaddresses"));
arr = retValue.get_array();
BOOST_CHECK(arr.size() == n+1);
BOOST_CHECK_EQUAL(arr.size(), n+1);
// We can't simulate over RPC the wallet closing and being reloaded
// but there are tests for this in gtest.

View File

@ -120,11 +120,11 @@ SaplingPaymentAddress CWallet::GenerateNewSaplingZKey()
CKeyMetadata metadata(nCreationTime);
// Try to get the seed
HDSeed seed;
if (!GetHDSeed(seed))
auto seed = GetMnemonicSeed();
if (!seed.has_value())
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): HD seed not found");
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed.value());
// We use a fixed keypath scheme of m/32'/coin_type'/account'
// Derive m/32'
@ -526,7 +526,7 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase)
if (CCryptoKeyStore::Unlock(vMasterKey)) {
// Now that the wallet is decrypted, ensure we have an HD seed.
// https://github.com/zcash/zcash/issues/3607
if (!this->HaveHDSeed()) {
if (!this->HaveMnemonicSeed()) {
this->GenerateNewSeed();
}
return true;
@ -2216,16 +2216,16 @@ bool CWallet::IsHDFullyEnabled() const
return false;
}
void CWallet::GenerateNewSeed()
void CWallet::GenerateNewSeed(Language language)
{
LOCK(cs_wallet);
auto seed = HDSeed::Random(HD_WALLET_SEED_LENGTH);
auto seed = MnemonicSeed::Random(language, HD_WALLET_SEED_LENGTH);
int64_t nCreationTime = GetTime();
// If the wallet is encrypted and locked, this will fail.
if (!SetHDSeed(seed))
if (!SetMnemonicSeed(seed))
throw std::runtime_error(std::string(__func__) + ": SetHDSeed failed");
// store the key creation time together with
@ -2238,9 +2238,9 @@ void CWallet::GenerateNewSeed()
SetHDChain(newHdChain, false);
}
bool CWallet::SetHDSeed(const HDSeed& seed)
bool CWallet::SetMnemonicSeed(const MnemonicSeed& seed)
{
if (!CCryptoKeyStore::SetHDSeed(seed)) {
if (!CCryptoKeyStore::SetMnemonicSeed(seed)) {
return false;
}
@ -2252,15 +2252,15 @@ bool CWallet::SetHDSeed(const HDSeed& seed)
LOCK(cs_wallet);
CWalletDB(strWalletFile).WriteNetworkInfo(networkIdString);
if (!IsCrypted()) {
return CWalletDB(strWalletFile).WriteHDSeed(seed);
return CWalletDB(strWalletFile).WriteMnemonicSeed(seed);
}
}
return true;
}
bool CWallet::SetCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char> &vchCryptedSecret)
bool CWallet::SetCryptedMnemonicSeed(const uint256& seedFp, const std::vector<unsigned char> &vchCryptedSecret)
{
if (!CCryptoKeyStore::SetCryptedHDSeed(seedFp, vchCryptedSecret)) {
if (!CCryptoKeyStore::SetCryptedMnemonicSeed(seedFp, vchCryptedSecret)) {
return false;
}
@ -2271,19 +2271,19 @@ bool CWallet::SetCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned
{
LOCK(cs_wallet);
if (pwalletdbEncryption)
return pwalletdbEncryption->WriteCryptedHDSeed(seedFp, vchCryptedSecret);
return pwalletdbEncryption->WriteCryptedMnemonicSeed(seedFp, vchCryptedSecret);
else
return CWalletDB(strWalletFile).WriteCryptedHDSeed(seedFp, vchCryptedSecret);
return CWalletDB(strWalletFile).WriteCryptedMnemonicSeed(seedFp, vchCryptedSecret);
}
return false;
}
HDSeed CWallet::GetHDSeedForRPC() const {
HDSeed seed;
if (!pwalletMain->GetHDSeed(seed)) {
auto seed = pwalletMain->GetMnemonicSeed();
if (!seed.has_value()) {
throw JSONRPCError(RPC_WALLET_ERROR, "HD seed not found");
}
return seed;
return seed.value();
}
void CWallet::SetHDChain(const CHDChain& chain, bool memonly)
@ -2307,14 +2307,24 @@ uint32_t CWallet::BIP44CoinType() {
}
bool CWallet::LoadHDSeed(const HDSeed& seed)
bool CWallet::LoadMnemonicSeed(const MnemonicSeed& seed)
{
return CBasicKeyStore::SetHDSeed(seed);
return CBasicKeyStore::SetMnemonicSeed(seed);
}
bool CWallet::LoadCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char>& seed)
bool CWallet::LoadLegacyHDSeed(const HDSeed& seed)
{
return CCryptoKeyStore::SetCryptedHDSeed(seedFp, seed);
return CBasicKeyStore::SetLegacyHDSeed(seed);
}
bool CWallet::LoadCryptedMnemonicSeed(const uint256& seedFp, const std::vector<unsigned char>& seed)
{
return CCryptoKeyStore::SetCryptedMnemonicSeed(seedFp, seed);
}
bool CWallet::LoadCryptedLegacyHDSeed(const uint256& seedFp, const std::vector<unsigned char>& seed)
{
return CCryptoKeyStore::SetCryptedLegacyHDSeed(seedFp, seed);
}
void CWalletTx::SetSproutNoteData(mapSproutNoteData_t &noteData)
@ -4781,7 +4791,7 @@ bool CWallet::InitLoadWallet(const CChainParams& params, bool clearWitnessCaches
walletInstance->SetMaxVersion(nMaxVersion);
}
if (!walletInstance->HaveHDSeed())
if (!walletInstance->HaveMnemonicSeed())
{
// We can't set the new HD seed until the wallet is decrypted.
// https://github.com/zcash/zcash/issues/3607

View File

@ -1284,10 +1284,20 @@ public:
Sets the seed's version based on the current wallet version (so the
caller must ensure the current wallet version is correct before calling
this function). */
void GenerateNewSeed();
void GenerateNewSeed(Language language = English);
bool SetHDSeed(const HDSeed& seed);
bool SetCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char> &vchCryptedSecret);
bool SetMnemonicSeed(const MnemonicSeed& seed);
bool SetCryptedMnemonicSeed(const uint256& seedFp, const std::vector<unsigned char> &vchCryptedSecret);
/* Set the current HD seed, without saving it to disk (used by LoadWallet) */
bool LoadMnemonicSeed(const MnemonicSeed& seed);
/* Set the legacy HD seed, without saving it to disk (used by LoadWallet) */
bool LoadLegacyHDSeed(const HDSeed& seed);
/* Set the current encrypted HD seed, without saving it to disk (used by LoadWallet) */
bool LoadCryptedMnemonicSeed(const uint256& seedFp, const std::vector<unsigned char>& seed);
/* Set the legacy encrypted HD seed, without saving it to disk (used by LoadWallet) */
bool LoadCryptedLegacyHDSeed(const uint256& seedFp, const std::vector<unsigned char>& seed);
/* Returns the wallet's HD seed or throw JSONRPCError(...) */
HDSeed GetHDSeedForRPC() const;
@ -1299,11 +1309,6 @@ public:
bool CheckNetworkInfo(std::pair<std::string, std::string> networkInfo);
uint32_t BIP44CoinType();
/* Set the current HD seed, without saving it to disk (used by LoadWallet) */
bool LoadHDSeed(const HDSeed& key);
/* Set the current encrypted HD seed, without saving it to disk (used by LoadWallet) */
bool LoadCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char>& seed);
/* Find notes filtered by payment address, min depth, ability to spend */
void GetFilteredNotes(std::vector<SproutNoteEntry>& sproutEntries,
std::vector<SaplingNoteEntry>& saplingEntries,

View File

@ -685,6 +685,37 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
{
ssValue >> pwallet->nWitnessCacheSize;
}
else if (strType == "mnemonicseed")
{
uint256 seedFp;
ssKey >> seedFp;
auto seed = MnemonicSeed::Read(ssValue);
if (seed.Fingerprint() != seedFp)
{
strErr = "Error reading wallet database: HDSeed corrupt";
return false;
}
if (!pwallet->LoadMnemonicSeed(seed))
{
strErr = "Error reading wallet database: LoadHDSeed failed";
return false;
}
}
else if (strType == "chdmnemonicseed")
{
uint256 seedFp;
vector<unsigned char> vchCryptedSecret;
ssKey >> seedFp;
ssValue >> vchCryptedSecret;
if (!pwallet->LoadCryptedMnemonicSeed(seedFp, vchCryptedSecret))
{
strErr = "Error reading wallet database: LoadCryptedMnemonicSeed failed";
return false;
}
wss.fIsEncrypted = true;
}
else if (strType == "hdseed")
{
uint256 seedFp;
@ -699,7 +730,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
return false;
}
if (!pwallet->LoadHDSeed(seed))
if (!pwallet->LoadLegacyHDSeed(seed))
{
strErr = "Error reading wallet database: LoadHDSeed failed";
return false;
@ -711,7 +742,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
vector<unsigned char> vchCryptedSecret;
ssKey >> seedFp;
ssValue >> vchCryptedSecret;
if (!pwallet->LoadCryptedHDSeed(seedFp, vchCryptedSecret))
if (!pwallet->LoadCryptedLegacyHDSeed(seedFp, vchCryptedSecret))
{
strErr = "Error reading wallet database: LoadCryptedHDSeed failed";
return false;
@ -744,6 +775,7 @@ static bool IsKeyType(string strType)
{
return (strType== "key" || strType == "wkey" ||
strType == "hdseed" || strType == "chdseed" ||
strType == "mnemonicseed" || strType == "chdmnemonicseed" ||
strType == "zkey" || strType == "czkey" ||
strType == "sapzkey" || strType == "csapzkey" ||
strType == "vkey" || strType == "sapextfvk" ||
@ -1160,18 +1192,19 @@ bool CWalletDB::WriteNetworkInfo(const std::string& networkId)
return Write(std::string("networkinfo"), networkInfo);
}
bool CWalletDB::WriteHDSeed(const HDSeed& seed)
bool CWalletDB::WriteMnemonicSeed(const MnemonicSeed& seed)
{
nWalletDBUpdateCounter++;
return Write(std::make_pair(std::string("hdseed"), seed.Fingerprint()), seed.RawSeed());
return Write(std::make_pair(std::string("mnemonicseed"), seed.Fingerprint()), seed);
}
bool CWalletDB::WriteCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char>& vchCryptedSecret)
bool CWalletDB::WriteCryptedMnemonicSeed(const uint256& seedFp, const std::vector<unsigned char>& vchCryptedSecret)
{
nWalletDBUpdateCounter++;
return Write(std::make_pair(std::string("chdseed"), seedFp), vchCryptedSecret);
return Write(std::make_pair(std::string("chdmnemonicseed"), seedFp), vchCryptedSecret);
}
bool CWalletDB::WriteHDChain(const CHDChain& chain)
{
nWalletDBUpdateCounter++;

View File

@ -48,7 +48,7 @@ public:
static const int VERSION_HD_BASE = 1;
static const int CURRENT_VERSION = VERSION_HD_BASE;
int nVersion;
uint256 seedFp;
uint256 seedFp; //NU5: Deprecate???
int64_t nCreateTime; // 0 means unknown
uint32_t saplingAccountCounter;
@ -170,10 +170,13 @@ public:
static bool Recover(CDBEnv& dbenv, const std::string& filename);
bool WriteNetworkInfo(const std::string& networkId);
bool WriteHDSeed(const HDSeed& seed);
bool WriteCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char>& vchCryptedSecret);
bool WriteMnemonicSeed(const MnemonicSeed& seed);
bool WriteCryptedMnemonicSeed(const uint256& seedFp, const std::vector<unsigned char>& vchCryptedSecret);
//! write the hdchain model (external chain child index counter)
bool WriteHDChain(const CHDChain& chain);
std::optional<HDSeed> ReadLegacyHDSeed();
std::optional<std::pair<uint256, std::vector<unsigned char>>> ReadLegacyCryptedHDSeed();
/// Write spending key to wallet database, where key is payment address and value is spending key.
bool WriteZKey(const libzcash::SproutPaymentAddress& addr, const libzcash::SproutSpendingKey& key, const CKeyMetadata &keyMeta);

View File

@ -19,12 +19,21 @@ 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'};
HDSeed HDSeed::Random(size_t len)
MnemonicSeed MnemonicSeed::Random(Language language, size_t len)
{
assert(len >= 32);
RawHDSeed rawSeed(len, 0);
GetRandBytes(rawSeed.data(), len);
return HDSeed(rawSeed);
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::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;
}
}
uint256 HDSeed::Fingerprint() const

View File

@ -9,6 +9,7 @@
#include "support/allocators/secure.h"
#include "uint256.h"
#include "zcash/address/sapling.hpp"
#include "rust/zip339.h"
#include <optional>
#include <string>
@ -18,27 +19,121 @@ const uint32_t ZIP32_HARDENED_KEY_LIMIT = 0x80000000;
const size_t ZIP32_XFVK_SIZE = 169;
const size_t ZIP32_XSK_SIZE = 169;
class CWalletDB;
typedef std::vector<unsigned char, secure_allocator<unsigned char>> RawHDSeed;
class HDSeed {
private:
protected:
RawHDSeed seed;
public:
HDSeed() {}
public:
//
HDSeed(RawHDSeed& seedIn) : seed(seedIn) {}
static HDSeed Random(size_t len = 32);
bool IsNull() const { return seed.empty(); };
template <typename Stream>
static HDSeed ReadLegacy(Stream& stream) {
RawHDSeed rawSeed;
stream >> rawSeed;
HDSeed seed(rawSeed);
return seed;
}
uint256 Fingerprint() const;
RawHDSeed RawSeed() const { return seed; }
};
friend bool operator==(const HDSeed& a, const HDSeed& b)
class MnemonicSeed: public HDSeed {
private:
Language language;
std::string mnemonic;
MnemonicSeed() {}
void Init() {
unsigned char buf[64];
zip339_phrase_to_seed(language, mnemonic.c_str(), &buf);
seed.assign(buf, std::end(buf));
}
public:
MnemonicSeed(Language languageIn, std::string& mnemonicIn): language(languageIn), mnemonic(mnemonicIn) {
unsigned char buf[64];
zip339_phrase_to_seed(languageIn, mnemonicIn.c_str(), &buf);
seed.assign(buf, std::end(buf));
}
static MnemonicSeed Random(Language language = English, size_t len = 32);
static std::string LanguageName(Language language) {
switch (language) {
case English:
return "English";
case SimplifiedChinese:
return "Simplified Chinese";
case TraditionalChinese:
return "Traditional Chinese";
case Czech:
return "Czech";
case French:
return "French";
case Italian:
return "Italian";
case Japanese:
return "Japanese";
case Korean:
return "Korean";
case Portuguese:
return "Portuguese";
case Spanish:
return "Spanish";
default:
return "INVALID";
}
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
if (ser_action.ForRead()) {
uint32_t language0;
READWRITE(language0);
READWRITE(mnemonic);
language = (Language) language0;
Init();
} else {
uint32_t language0 = (uint32_t) language;
READWRITE(language0);
READWRITE(mnemonic);
}
}
template <typename Stream>
static MnemonicSeed Read(Stream& stream) {
MnemonicSeed seed;
stream >> seed;
return seed;
}
const Language GetLanguage() const {
return language;
}
const std::string GetMnemonic() const {
return mnemonic;
}
friend bool operator==(const MnemonicSeed& a, const MnemonicSeed& b)
{
return a.seed == b.seed;
}
friend bool operator!=(const HDSeed& a, const HDSeed& b)
friend bool operator!=(const MnemonicSeed& a, const MnemonicSeed& b)
{
return !(a == b);
}