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:
parent
d6fc064018
commit
5677649af1
|
@ -16,29 +16,31 @@
|
|||
|
||||
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) {
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
@ -119,7 +124,8 @@ typedef std::map<libzcash::SaplingPaymentAddress, libzcash::SaplingIncomingViewi
|
|||
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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 ¬eData)
|
||||
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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++;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue