From d420e2efabaf74f0dae6e534719f24b7bc6cb408 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 11 Mar 2022 11:34:16 -0700 Subject: [PATCH 1/2] Derive the new mnemonic seed from the legacy HD seed, if one is available. Fixes #5572 Co-authored-by: Daira Hopwood --- src/wallet/wallet.cpp | 5 +++- src/zcash/address/mnemonic.cpp | 43 ++++++++++++++++++++++++++++------ src/zcash/address/mnemonic.h | 4 ++++ 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 6a8a79a25..24416c2a5 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3123,7 +3123,10 @@ void CWallet::GenerateNewSeed(Language language) { LOCK(cs_wallet); - auto seed = MnemonicSeed::Random(BIP44CoinType(), language, WALLET_MNEMONIC_ENTROPY_LENGTH); + auto legacySeed = GetLegacyHDSeed(); + auto seed = legacySeed.has_value() ? + MnemonicSeed::FromLegacySeed(legacySeed.value(), BIP44CoinType(), language) : + MnemonicSeed::Random(BIP44CoinType(), language, WALLET_MNEMONIC_ENTROPY_LENGTH); int64_t nCreationTime = GetTime(); diff --git a/src/zcash/address/mnemonic.cpp b/src/zcash/address/mnemonic.cpp index 45b922fb4..541a5a646 100644 --- a/src/zcash/address/mnemonic.cpp +++ b/src/zcash/address/mnemonic.cpp @@ -9,13 +9,8 @@ using namespace libzcash; -MnemonicSeed MnemonicSeed::Random(uint32_t bip44CoinType, Language language, size_t entropyLen) -{ - assert(entropyLen >= 32); - while (true) { // loop until we find usable entropy - RawHDSeed entropy(entropyLen, 0); - GetRandBytes(entropy.data(), entropyLen); - const char* phrase = zip339_entropy_to_phrase(language, entropy.data(), entropyLen); +std::optional MnemonicSeed::FromEntropy(const RawHDSeed& entropy, uint32_t bip44CoinType, Language language) { + const char* phrase = zip339_entropy_to_phrase(language, entropy.data(), entropy.size()); SecureString mnemonic(phrase); zip339_free_phrase(phrase); @@ -33,7 +28,41 @@ MnemonicSeed MnemonicSeed::Random(uint32_t bip44CoinType, Language language, siz if (ZcashdUnifiedSpendingKey::ForAccount(seed, bip44CoinType, 0).has_value() && transparent::AccountKey::ForAccount(seed, bip44CoinType, ZCASH_LEGACY_ACCOUNT).has_value()) { return seed; + } else { + return std::nullopt; + } +} + +MnemonicSeed MnemonicSeed::Random(uint32_t bip44CoinType, Language language, size_t entropyLen) +{ + assert(entropyLen >= 32); + while (true) { // loop until we find usable entropy + RawHDSeed entropy(entropyLen, 0); + GetRandBytes(entropy.data(), entropyLen); + + auto seed = MnemonicSeed::FromEntropy(entropy, bip44CoinType, language); + if (seed.has_value()) { + return seed.value(); } } } +MnemonicSeed MnemonicSeed::FromLegacySeed(const HDSeed& legacySeed, uint32_t bip44CoinType, Language language) +{ + auto rawSeed = legacySeed.RawSeed(); + if (rawSeed.size() != 32) { + throw std::runtime_error("Mnemonic seed derivation is only supported for 32-byte legacy seeds."); + } + + for (int nonce = 0; nonce < 256; nonce++) { + auto seed = MnemonicSeed::FromEntropy(rawSeed, bip44CoinType, language); + if (seed.has_value()) { + return seed.value(); + } else { + rawSeed[0]++; + } + } + + throw std::runtime_error("Failed to find a valid mnemonic seed that could be derived from the legacy seed."); +} + diff --git a/src/zcash/address/mnemonic.h b/src/zcash/address/mnemonic.h index 57980f68d..e93fd6fe4 100644 --- a/src/zcash/address/mnemonic.h +++ b/src/zcash/address/mnemonic.h @@ -37,6 +37,10 @@ public: */ static MnemonicSeed Random(uint32_t bip44CoinType, Language language = English, size_t entropyLen = 32); + static MnemonicSeed FromLegacySeed(const HDSeed& legacySeed, uint32_t bip44CoinType, Language language = English); + + static std::optional FromEntropy(const RawHDSeed& entropy, uint32_t bip44CoinType, Language language = English); + static std::string LanguageName(Language language) { switch (language) { case English: From e335ff2325a892a11767f0aab22e90e1a985e01e Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 11 Mar 2022 11:36:03 -0700 Subject: [PATCH 2/2] Fix indentation. --- src/zcash/address/mnemonic.cpp | 38 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/zcash/address/mnemonic.cpp b/src/zcash/address/mnemonic.cpp index 541a5a646..9321975b5 100644 --- a/src/zcash/address/mnemonic.cpp +++ b/src/zcash/address/mnemonic.cpp @@ -10,27 +10,27 @@ using namespace libzcash; std::optional MnemonicSeed::FromEntropy(const RawHDSeed& entropy, uint32_t bip44CoinType, Language language) { - const char* phrase = zip339_entropy_to_phrase(language, entropy.data(), entropy.size()); - SecureString mnemonic(phrase); - zip339_free_phrase(phrase); + const char* phrase = zip339_entropy_to_phrase(language, entropy.data(), entropy.size()); + SecureString mnemonic(phrase); + zip339_free_phrase(phrase); - // The phrase returned from zip339_entropy_to_phrase should always be a - // valid UTF-8 string; this `.value()` unwrap will correctly throw a - // `std::bad_optional_access` exception if that invariant does not hold. - auto seed = MnemonicSeed::ForPhrase(language, mnemonic).value(); + // The phrase returned from zip339_entropy_to_phrase should always be a + // valid UTF-8 string; this `.value()` unwrap will correctly throw a + // `std::bad_optional_access` exception if that invariant does not hold. + auto seed = MnemonicSeed::ForPhrase(language, mnemonic).value(); - // Verify that the seed data is valid entropy for unified spending keys at - // account 0 and at both the public & private chain levels for account 0x7FFFFFFF. - // It is not necessary to check for a valid diversified Sapling address at - // account 0x7FFFFFFF because derivation via the legacy path can simply search - // for a valid diversifier; unlike in the unified spending key case, diversifier - // indices don't need to line up with anything. - if (ZcashdUnifiedSpendingKey::ForAccount(seed, bip44CoinType, 0).has_value() && - transparent::AccountKey::ForAccount(seed, bip44CoinType, ZCASH_LEGACY_ACCOUNT).has_value()) { - return seed; - } else { - return std::nullopt; - } + // Verify that the seed data is valid entropy for unified spending keys at + // account 0 and at both the public & private chain levels for account 0x7FFFFFFF. + // It is not necessary to check for a valid diversified Sapling address at + // account 0x7FFFFFFF because derivation via the legacy path can simply search + // for a valid diversifier; unlike in the unified spending key case, diversifier + // indices don't need to line up with anything. + if (ZcashdUnifiedSpendingKey::ForAccount(seed, bip44CoinType, 0).has_value() && + transparent::AccountKey::ForAccount(seed, bip44CoinType, ZCASH_LEGACY_ACCOUNT).has_value()) { + return seed; + } else { + return std::nullopt; + } } MnemonicSeed MnemonicSeed::Random(uint32_t bip44CoinType, Language language, size_t entropyLen)