diff --git a/src/bench/verification.cpp b/src/bench/verification.cpp index ba7aea1c2..19c0b27d9 100644 --- a/src/bench/verification.cpp +++ b/src/bench/verification.cpp @@ -25,8 +25,7 @@ static void ECDSA(benchmark::State& state) mtx.nVersion = SAPLING_TX_VERSION; mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID; - CKey key; - key.MakeNewKey(false); + CKey key = CKey::TestOnlyRandomKey(false); CBasicKeyStore keystore; keystore.AddKeyPubKey(key, key.GetPubKey()); CKeyID hash = key.GetPubKey().GetID(); diff --git a/src/init.cpp b/src/init.cpp index ab58e98aa..65415a5a0 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -684,7 +684,7 @@ void ThreadImport(std::vector vImportFiles, const CChainParams& chainp */ bool InitSanityCheck(void) { - if(!ECC_InitSanityCheck()) { + if(!CKey::ECC_InitSanityCheck()) { InitError("Elliptic curve cryptography sanity check failure. Aborting."); return false; } diff --git a/src/key.cpp b/src/key.cpp index ff52e5eea..75dbcd33c 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -156,12 +156,14 @@ bool CKey::Check(const unsigned char *vch) { return secp256k1_ec_seckey_verify(secp256k1_context_sign, vch); } -void CKey::MakeNewKey(bool fCompressedIn) { +CKey CKey::TestOnlyRandomKey(bool fCompressedIn) { + CKey key; do { - GetRandBytes(keydata.data(), keydata.size()); - } while (!Check(keydata.data())); - fValid = true; - fCompressed = fCompressedIn; + GetRandBytes(key.keydata.data(), key.keydata.size()); + } while (!Check(key.keydata.data())); + key.fValid = true; + key.fCompressed = fCompressedIn; + return key; } bool CKey::SetPrivKey(const CPrivKey &privkey, bool fCompressedIn) { @@ -330,9 +332,8 @@ void CExtKey::Decode(const unsigned char code[BIP32_EXTKEY_SIZE]) { key.Set(code+42, code+BIP32_EXTKEY_SIZE, true); } -bool ECC_InitSanityCheck() { - CKey key; - key.MakeNewKey(true); +bool CKey::ECC_InitSanityCheck() { + CKey key = CKey::TestOnlyRandomKey(true); CPubKey pubkey = key.GetPubKey(); return key.VerifyPubKey(pubkey); } diff --git a/src/key.h b/src/key.h index 2a9ee32c5..4f48f3bbd 100644 --- a/src/key.h +++ b/src/key.h @@ -68,7 +68,17 @@ public: keydata.resize(32); } - static std::optional FromEntropy(std::vector> keydata); + /** + * Construct a random key. This is used only for internal sanity checks; + * all keys that actually control live funds should be derived from the + * wallet's mnemonic seed. + */ + static CKey TestOnlyRandomKey(bool fCompressedIn); + + static std::optional FromEntropy(std::vector> keydata, bool fComporessedIn); + + /** Check that required EC support is available at runtime. */ + static bool ECC_InitSanityCheck(); friend bool operator==(const CKey& a, const CKey& b) { @@ -106,9 +116,6 @@ public: //! Initialize from a CPrivKey (serialized OpenSSL-format private key data). bool SetPrivKey(const CPrivKey& vchPrivKey, bool fCompressed); - //! Generate a new private key using a cryptographic PRNG. - void MakeNewKey(bool fCompressed); - /** * Convert the private key to a CPrivKey (serialized OpenSSL-format private key data). * This is expensive. @@ -199,7 +206,5 @@ void ECC_Start(void); /** Deinitialize the elliptic curve support. No-op if ECC_Start wasn't called first. */ void ECC_Stop(void); -/** Check that required EC support is available at runtime. */ -bool ECC_InitSanityCheck(void); #endif // BITCOIN_KEY_H diff --git a/src/test/DoS_tests.cpp b/src/test/DoS_tests.cpp index b006c2160..ff13b80a9 100644 --- a/src/test/DoS_tests.cpp +++ b/src/test/DoS_tests.cpp @@ -126,8 +126,7 @@ BOOST_DATA_TEST_CASE(DoS_mapOrphans, boost::unit_test::data::xrange(static_cast< { uint32_t consensusBranchId = NetworkUpgradeInfo[sample].nBranchId; - CKey key; - key.MakeNewKey(true); + CKey key = CKey::TestOnlyRandomKey(true); CBasicKeyStore keystore; keystore.AddKey(key); diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp index 2aabbb3d2..2986a8359 100644 --- a/src/test/multisig_tests.cpp +++ b/src/test/multisig_tests.cpp @@ -49,7 +49,7 @@ BOOST_DATA_TEST_CASE(multisig_verify, boost::unit_test::data::xrange(static_cast CKey key[4]; CAmount amount = 0; for (int i = 0; i < 4; i++) - key[i].MakeNewKey(true); + key[i] = CKey::TestOnlyRandomKey(true); CScript a_and_b; a_and_b << OP_2 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_2 << OP_CHECKMULTISIG; @@ -149,7 +149,7 @@ BOOST_AUTO_TEST_CASE(multisig_IsStandard) { CKey key[4]; for (int i = 0; i < 4; i++) - key[i].MakeNewKey(true); + key[i] = CKey::TestOnlyRandomKey(true); txnouttype whichType; @@ -191,7 +191,7 @@ BOOST_DATA_TEST_CASE(multisig_Sign, boost::unit_test::data::xrange(static_cast(Conse CKey key[4]; for (int i = 0; i < 4; i++) { - key[i].MakeNewKey(true); + key[i] = CKey::TestOnlyRandomKey(true); keystore.AddKey(key[i]); } @@ -170,7 +170,7 @@ BOOST_DATA_TEST_CASE(set, boost::unit_test::data::xrange(static_cast(Consen std::vector keys; for (int i = 0; i < 4; i++) { - key[i].MakeNewKey(true); + key[i] = CKey::TestOnlyRandomKey(true); keystore.AddKey(key[i]); keys.push_back(key[i].GetPubKey()); } @@ -282,7 +282,7 @@ BOOST_DATA_TEST_CASE(AreInputsStandard, boost::unit_test::data::xrange(static_ca vector keys; for (int i = 0; i < 6; i++) { - key[i].MakeNewKey(true); + key[i] = CKey::TestOnlyRandomKey(true); keystore.AddKey(key[i]); } for (int i = 0; i < 3; i++) diff --git a/src/test/script_standard_tests.cpp b/src/test/script_standard_tests.cpp index 0196b4b5b..2a4ee1443 100644 --- a/src/test/script_standard_tests.cpp +++ b/src/test/script_standard_tests.cpp @@ -22,7 +22,7 @@ BOOST_AUTO_TEST_CASE(script_standard_Solver_success) CKey keys[3]; CPubKey pubkeys[3]; for (int i = 0; i < 3; i++) { - keys[i].MakeNewKey(true); + keys[i] = CKey::TestOnlyRandomKey(true); pubkeys[i] = keys[i].GetPubKey(); } @@ -103,9 +103,8 @@ BOOST_AUTO_TEST_CASE(script_standard_Solver_success) BOOST_AUTO_TEST_CASE(script_standard_Solver_failure) { - CKey key; + CKey key = CKey::TestOnlyRandomKey(true); CPubKey pubkey; - key.MakeNewKey(true); pubkey = key.GetPubKey(); CScript s; @@ -165,9 +164,8 @@ BOOST_AUTO_TEST_CASE(script_standard_Solver_failure) BOOST_AUTO_TEST_CASE(script_standard_ExtractDestination) { - CKey key; + CKey key = CKey::TestOnlyRandomKey(true); CPubKey pubkey; - key.MakeNewKey(true); pubkey = key.GetPubKey(); CScript s; @@ -221,7 +219,7 @@ BOOST_AUTO_TEST_CASE(script_standard_ExtractDestinations) CKey keys[3]; CPubKey pubkeys[3]; for (int i = 0; i < 3; i++) { - keys[i].MakeNewKey(true); + keys[i] = CKey::TestOnlyRandomKey(true); pubkeys[i] = keys[i].GetPubKey(); } @@ -297,7 +295,7 @@ BOOST_AUTO_TEST_CASE(script_standard_GetScriptFor_) CKey keys[3]; CPubKey pubkeys[3]; for (int i = 0; i < 3; i++) { - keys[i].MakeNewKey(true); + keys[i] = CKey::TestOnlyRandomKey(true); pubkeys[i] = keys[i].GetPubKey(); } @@ -343,12 +341,11 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) CKey keys[2]; CPubKey pubkeys[2]; for (int i = 0; i < 2; i++) { - keys[i].MakeNewKey(true); + keys[i] = CKey::TestOnlyRandomKey(true); pubkeys[i] = keys[i].GetPubKey(); } - CKey uncompressedKey; - uncompressedKey.MakeNewKey(false); + CKey uncompressedKey = CKey::TestOnlyRandomKey(false); CPubKey uncompressedPubkey = uncompressedKey.GetPubKey(); CScript scriptPubKey; diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 6ed81ade6..5ce70486a 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -701,10 +701,9 @@ BOOST_DATA_TEST_CASE(script_CHECKMULTISIG12, boost::unit_test::data::xrange(stat uint32_t consensusBranchId = NetworkUpgradeInfo[sample].nBranchId; ScriptError err; - CKey key1, key2, key3; - key1.MakeNewKey(true); - key2.MakeNewKey(false); - key3.MakeNewKey(true); + CKey key1 = CKey::TestOnlyRandomKey(true); + CKey key2 = CKey::TestOnlyRandomKey(false); + CKey key3 = CKey::TestOnlyRandomKey(true); CScript scriptPubKey12; scriptPubKey12 << OP_1 << ToByteVector(key1.GetPubKey()) << ToByteVector(key2.GetPubKey()) << OP_2 << OP_CHECKMULTISIG; @@ -734,11 +733,10 @@ BOOST_DATA_TEST_CASE(script_CHECKMULTISIG23, boost::unit_test::data::xrange(stat uint32_t consensusBranchId = NetworkUpgradeInfo[sample].nBranchId; ScriptError err; - CKey key1, key2, key3, key4; - key1.MakeNewKey(true); - key2.MakeNewKey(false); - key3.MakeNewKey(true); - key4.MakeNewKey(false); + CKey key1 = CKey::TestOnlyRandomKey(true); + CKey key2 = CKey::TestOnlyRandomKey(false); + CKey key3 = CKey::TestOnlyRandomKey(true); + CKey key4 = CKey::TestOnlyRandomKey(false); CScript scriptPubKey23; scriptPubKey23 << OP_2 << ToByteVector(key1.GetPubKey()) << ToByteVector(key2.GetPubKey()) << ToByteVector(key3.GetPubKey()) << OP_3 << OP_CHECKMULTISIG; @@ -812,8 +810,7 @@ BOOST_DATA_TEST_CASE(script_combineSigs, boost::unit_test::data::xrange(static_c vector pubkeys; for (int i = 0; i < 3; i++) { - CKey key; - key.MakeNewKey(i%2 == 1); + CKey key = CKey::TestOnlyRandomKey(i%2 == 1); keys.push_back(key); pubkeys.push_back(key.GetPubKey()); keystore.AddKey(key); diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp index b4c6aee80..58b88e686 100644 --- a/src/test/sigopcount_tests.cpp +++ b/src/test/sigopcount_tests.cpp @@ -47,8 +47,7 @@ BOOST_AUTO_TEST_CASE(GetSigOpCount) std::vector keys; for (int i = 0; i < 3; i++) { - CKey k; - k.MakeNewKey(true); + CKey k = CKey::TestOnlyRandomKey(true); keys.push_back(k.GetPubKey()); } CScript s2 = GetScriptForMultisig(1, keys); diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index df5d0a06e..f3360fe19 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -130,7 +130,7 @@ TestingSetup::~TestingSetup() TestChain100Setup::TestChain100Setup() : TestingSetup(CBaseChainParams::REGTEST) { // Generate a 100-block chain: - coinbaseKey.MakeNewKey(true); + coinbaseKey = CKey::TestOnlyRandomKey(true); CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG; for (int i = 0; i < COINBASE_MATURITY; i++) { diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index dfedf1be2..2c2758498 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -270,7 +270,7 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) CKey key[4]; for (int i = 0; i < 4; i++) { - key[i].MakeNewKey(i % 2); + key[i] = CKey::TestOnlyRandomKey(i % 2); keystoreRet.AddKey(key[i]); } @@ -639,8 +639,7 @@ BOOST_AUTO_TEST_CASE(test_big_overwinter_transaction) { mtx.nVersion = OVERWINTER_TX_VERSION; mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID; - CKey key; - key.MakeNewKey(false); + CKey key = CKey::TestOnlyRandomKey(false); CBasicKeyStore keystore; keystore.AddKeyPubKey(key, key.GetPubKey()); CKeyID hash = key.GetPubKey().GetID(); @@ -731,8 +730,7 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) t.vin[0].scriptSig << std::vector(65, 0); t.vout.resize(1); t.vout[0].nValue = 90*CENT; - CKey key; - key.MakeNewKey(true); + CKey key = CKey::TestOnlyRandomKey(true); t.vout[0].scriptPubKey = GetScriptForDestination(key.GetPubKey().GetID()); string reason; @@ -826,8 +824,7 @@ BOOST_AUTO_TEST_CASE(test_IsStandardV2) t.vin[0].scriptSig << std::vector(65, 0); t.vout.resize(1); t.vout[0].nValue = 90*CENT; - CKey key; - key.MakeNewKey(true); + CKey key = CKey::TestOnlyRandomKey(true); t.vout[0].scriptPubKey = GetScriptForDestination(key.GetPubKey().GetID()); string reason; diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 155afd700..4a2228e35 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -865,15 +865,26 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptCoinbase, TxValues& txVa return t_inputs_.size() > 0; } +/** + * Compute a dust threshold based upon a standard p2pkh txout. + */ +CAmount DefaultDustThreshold(const CFeeRate& minRelayTxFee) { + // Use the all-zeros seed, we're only constructing a key in order + // to get a txout from which we can obtain the dust threshold. + // Master key generation results in a compressed key. + RawHDSeed seed(64, 0x00); + CKey secret = CExtKey::Master(seed.data(), 64).key; + CScript scriptPubKey = GetScriptForDestination(secret.GetPubKey().GetID()); + CTxOut txout(CAmount(1), scriptPubKey); + return txout.GetDustThreshold(minRelayTxFee); +} + bool AsyncRPCOperation_sendmany::load_inputs(TxValues& txValues) { // If from address is a taddr, select UTXOs to spend CAmount selectedUTXOAmount = 0; + // Get dust threshold - CKey secret; - secret.MakeNewKey(true); - CScript scriptPubKey = GetScriptForDestination(secret.GetPubKey().GetID()); - CTxOut out(CAmount(1), scriptPubKey); - CAmount dustThreshold = out.GetDustThreshold(minRelayTxFee); + CAmount dustThreshold = DefaultDustThreshold(minRelayTxFee); CAmount dustChange = -1; std::vector selectedTInputs; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 519313305..c9a7a2169 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -167,10 +167,10 @@ UniValue getnewaddress(const UniValue& params, bool fHelp) pwalletMain->TopUpKeyPool(); // Generate a new key that is added to wallet - CPubKey newKey; - if (!pwalletMain->GetKeyFromPool(newKey)) + std::optional newKey = pwalletMain->GetKeyFromPool(); + if (!newKey.has_value()) throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); - CKeyID keyID = newKey.GetID(); + CKeyID keyID = newKey.value().GetID(); std::string dummy_account; pwalletMain->SetAddressBook(keyID, dummy_account, "receive"); diff --git a/src/wallet/test/rpc_wallet_tests.cpp b/src/wallet/test/rpc_wallet_tests.cpp index 49e84c47b..7d7a17144 100644 --- a/src/wallet/test/rpc_wallet_tests.cpp +++ b/src/wallet/test/rpc_wallet_tests.cpp @@ -134,7 +134,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) LOCK2(cs_main, pwalletMain->cs_wallet); - CPubKey demoPubkey = pwalletMain->GenerateNewKey(); + CPubKey demoPubkey = pwalletMain->GenerateNewKey().value(); CTxDestination demoAddress(CTxDestination(demoPubkey.GetID())); UniValue retValue; string strPurpose = "receive"; @@ -143,7 +143,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) pwalletMain->SetAddressBook(demoPubkey.GetID(), "", strPurpose); }); - CPubKey setaccountDemoPubkey = pwalletMain->GenerateNewKey(); + CPubKey setaccountDemoPubkey = pwalletMain->GenerateNewKey().value(); CTxDestination setaccountDemoAddress(CTxDestination(setaccountDemoPubkey.GetID())); /********************************* @@ -1469,7 +1469,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling) KeyIO keyIO(Params()); // add keys manually - auto taddr = pwalletMain->GenerateNewKey().GetID(); + auto taddr = pwalletMain->GenerateNewKey().value().GetID(); std::string taddr1 = keyIO.EncodeDestination(taddr); auto pa = DefaultSaplingAddress(pwalletMain); std::string zaddr1 = keyIO.EncodePaymentAddress(pa); @@ -2173,7 +2173,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_mergetoaddress_internals) void TestWTxStatus(const Consensus::Params consensusParams, const int delta) { auto AddTrx = [&consensusParams]() { - auto taddr = pwalletMain->GenerateNewKey().GetID(); + auto taddr = pwalletMain->GenerateNewKey().value().GetID(); CMutableTransaction mtx = CreateNewContextualCMutableTransaction(consensusParams, 1); CScript scriptPubKey = CScript() << OP_DUP << OP_HASH160 << ToByteVector(taddr) << OP_EQUALVERIFY << OP_CHECKSIG; mtx.vout.push_back(CTxOut(5 * COIN, scriptPubKey)); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 81f7fd33d..a02a6aa25 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -245,30 +245,50 @@ bool CWallet::AddSproutZKey(const libzcash::SproutSpendingKey &key) return true; } -CPubKey CWallet::GenerateNewKey() +std::optional CWallet::GenerateNewKey() { AssertLockHeld(cs_wallet); // mapKeyMetadata - bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets + auto seedOpt = GetMnemonicSeed(); + CHDChain& hdChain = mnemonicHDChain.value(); + if (seedOpt.has_value()) { + // All mnemonic seeds are checked at construction to ensure that we can obtain + // a valid spending key for the account ZCASH_LEGACY_TRANSPARENT_ACCOUNT; + // therefore, the `value()` call here is safe. + BIP32AccountChains accountChains = BIP32AccountChains::ForAccount( + seedOpt.value(), + BIP44CoinType(), + ZCASH_LEGACY_TRANSPARENT_ACCOUNT).value(); - CKey secret; - secret.MakeNewKey(fCompressed); + while (true) { + auto extKey = accountChains.DeriveExternal(hdChain.GetLegacyTKeyCounter()); + hdChain.IncrementLegacyTKeyCounter(); - // Compressed public keys were introduced in version 0.6.0 - if (fCompressed) - SetMinVersion(FEATURE_COMPRPUBKEY); + // if we did not successfully generate a key, try again. + if (extKey.has_value()) { + CKey secret = extKey.value().first.key; + CPubKey pubkey = secret.GetPubKey(); + assert(secret.VerifyPubKey(pubkey)); - CPubKey pubkey = secret.GetPubKey(); - assert(secret.VerifyPubKey(pubkey)); + // Create new metadata + const CKeyMetadata& keyMeta = extKey.value().second; + mapKeyMetadata[pubkey.GetID()] = keyMeta; + if (!nTimeFirstKey || keyMeta.nCreateTime < nTimeFirstKey) + nTimeFirstKey = keyMeta.nCreateTime; - // Create new metadata - int64_t nCreationTime = GetTime(); - mapKeyMetadata[pubkey.GetID()] = CKeyMetadata(nCreationTime); - if (!nTimeFirstKey || nCreationTime < nTimeFirstKey) - nTimeFirstKey = nCreationTime; + if (!AddKeyPubKey(secret, pubkey)) + throw std::runtime_error("CWallet::GenerateNewKey(): AddKey failed"); - if (!AddKeyPubKey(secret, pubkey)) - throw std::runtime_error("CWallet::GenerateNewKey(): AddKey failed"); - return pubkey; + // Update the persisted chain information + if (fFileBacked && !CWalletDB(strWalletFile).WriteMnemonicHDChain(hdChain)) { + throw std::runtime_error("CWallet::GenerateNewKey(): Writing HD chain model failed"); + } + + return pubkey; + } + } + } else { + return std::nullopt; + } } bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) @@ -375,9 +395,9 @@ UnifiedSpendingKey CWallet::GenerateNewUnifiedSpendingKey() { throw std::runtime_error(std::string(__func__) + ": Wallet has no mnemonic HD seed. Please upgrade this wallet."); } - auto hdChain = GetMnemonicHDChain().value(); + CHDChain& hdChain = mnemonicHDChain.value(); while (true) { - auto usk = UnifiedSpendingKey::Derive(seed.value(), BIP44CoinType(), hdChain.GetAccountCounter()); + auto usk = UnifiedSpendingKey::ForAccount(seed.value(), BIP44CoinType(), hdChain.GetAccountCounter()); hdChain.IncrementAccountCounter(); if (usk.has_value()) { @@ -397,7 +417,7 @@ std::optional CWallet::GetUnifiedSpendingKeyForAcc auto seed = GetMnemonicSeed(); assert(seed.has_value()); // TODO: is there any reason to cache and not re-derive this every time? - auto usk = UnifiedSpendingKey::Derive(seed.value(), BIP44CoinType(), accountId); + auto usk = UnifiedSpendingKey::ForAccount(seed.value(), BIP44CoinType(), accountId); if (usk.has_value()) { return usk.value().first; } else { @@ -4185,7 +4205,10 @@ bool CWallet::NewKeyPool() for (int i = 0; i < nKeys; i++) { int64_t nIndex = i+1; - walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey())); + auto key = GenerateNewKey(); + if (!key.has_value()) + return false; // should have been caught by the `IsLocked` call. + walletdb.WritePool(nIndex, CKeyPool(key.value())); setKeyPool.insert(nIndex); } LogPrintf("CWallet::NewKeyPool wrote %d new keys\n", nKeys); @@ -4215,7 +4238,8 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) int64_t nEnd = 1; if (!setKeyPool.empty()) nEnd = *(--setKeyPool.end()) + 1; - if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey()))) + auto newKey = GenerateNewKey(); + if (!newKey.has_value() || !walletdb.WritePool(nEnd, CKeyPool(newKey.value()))) throw runtime_error("TopUpKeyPool(): writing generated key failed"); setKeyPool.insert(nEnd); LogPrintf("keypool added key %d, size=%u\n", nEnd, setKeyPool.size()); @@ -4272,7 +4296,7 @@ void CWallet::ReturnKey(int64_t nIndex) LogPrintf("keypool return %d\n", nIndex); } -bool CWallet::GetKeyFromPool(CPubKey& result) +std::optional CWallet::GetKeyFromPool() { int64_t nIndex = 0; CKeyPool keypool; @@ -4281,14 +4305,12 @@ bool CWallet::GetKeyFromPool(CPubKey& result) ReserveKeyFromKeyPool(nIndex, keypool); if (nIndex == -1) { - if (IsLocked()) return false; - result = GenerateNewKey(); - return true; + if (IsLocked()) return std::nullopt; + return GenerateNewKey(); } KeepKey(nIndex); - result = keypool.vchPubKey; + return keypool.vchPubKey; } - return true; } int64_t CWallet::GetOldestKeyPoolTime() @@ -4858,9 +4880,9 @@ bool CWallet::InitLoadWallet(const CChainParams& params, bool clearWitnessCaches if (fFirstRun) { // Create new keyUser and set as default key - CPubKey newDefaultKey; - if (walletInstance->GetKeyFromPool(newDefaultKey)) { - walletInstance->SetDefaultKey(newDefaultKey); + std::optional newDefaultKey = walletInstance->GetKeyFromPool(); + if (newDefaultKey.has_value()) { + walletInstance->SetDefaultKey(newDefaultKey.value()); if (!walletInstance->SetAddressBook(walletInstance->vchDefaultKey.GetID(), "", "receive")) return UIError(_("Cannot write default address") += "\n"); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 6948325dc..35e13bcaf 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -993,7 +993,7 @@ public: * keystore implementation * Generate a new key */ - CPubKey GenerateNewKey(); + std::optional GenerateNewKey(); //! Adds a key to the store, and saves it to disk. bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); //! Adds a key to the store, without saving it to disk (used by LoadWallet) @@ -1161,7 +1161,7 @@ public: void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool); void KeepKey(int64_t nIndex); void ReturnKey(int64_t nIndex); - bool GetKeyFromPool(CPubKey &key); + std::optional GetKeyFromPool(); int64_t GetOldestKeyPoolTime(); void GetAllReserveKeys(std::set& setAddress) const; @@ -1318,7 +1318,7 @@ public: /* Set the metadata for the mnemonic HD seed (chain child index counters) */ void SetMnemonicHDChain(const CHDChain& chain, bool memonly); - const std::optional GetMnemonicHDChain() const { return mnemonicHDChain; } + const std::optional& GetMnemonicHDChain() const { return mnemonicHDChain; } /* Set the metadata for the legacy HD seed (chain child index counters) */ void SetLegacyHDChain(const CHDChain& chain, bool memonly); diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index a3abf37bc..aef6789cd 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -49,6 +49,7 @@ private: uint256 seedFp; int64_t nCreateTime; // 0 means unknown uint32_t accountCounter; + uint32_t legacyTKeyCounter; CHDChain() { SetNull(); } @@ -58,12 +59,13 @@ private: seedFp.SetNull(); nCreateTime = 0; accountCounter = 0; + legacyTKeyCounter = 0; } public: static const int VERSION_HD_BASE = 1; static const int CURRENT_VERSION = VERSION_HD_BASE; - CHDChain(uint256 seedFpIn, int64_t nCreateTimeIn): nVersion(CHDChain::CURRENT_VERSION), seedFp(seedFpIn), nCreateTime(nCreateTimeIn), accountCounter(0) {} + CHDChain(uint256 seedFpIn, int64_t nCreateTimeIn): nVersion(CHDChain::CURRENT_VERSION), seedFp(seedFpIn), nCreateTime(nCreateTimeIn), accountCounter(0), legacyTKeyCounter(0) {} ADD_SERIALIZE_METHODS; @@ -74,6 +76,7 @@ public: READWRITE(seedFp); READWRITE(nCreateTime); READWRITE(accountCounter); + READWRITE(legacyTKeyCounter); } template @@ -91,8 +94,16 @@ public: return accountCounter; } - uint32_t IncrementAccountCounter() { - return ++accountCounter; + void IncrementAccountCounter() { + accountCounter += 1; + } + + uint32_t GetLegacyTKeyCounter() { + return legacyTKeyCounter; + } + + void IncrementLegacyTKeyCounter() { + legacyTKeyCounter += 1; } }; diff --git a/src/zcash/address/zip32.cpp b/src/zcash/address/zip32.cpp index 01a7dd325..fe09bc183 100644 --- a/src/zcash/address/zip32.cpp +++ b/src/zcash/address/zip32.cpp @@ -24,7 +24,7 @@ const libzcash::diversifier_index_t MAX_TRANSPARENT_CHILD_IDX(0x40000000); MnemonicSeed MnemonicSeed::Random(uint32_t bip44CoinType, Language language, size_t entropyLen) { assert(entropyLen >= 32); - while (true) { + while (true) { // loop until we find usable entropy std::vector entropy(entropyLen, 0); GetRandBytes(entropy.data(), entropyLen); const char* phrase = zip339_entropy_to_phrase(language, entropy.data(), entropyLen); @@ -33,9 +33,9 @@ MnemonicSeed MnemonicSeed::Random(uint32_t bip44CoinType, Language language, siz MnemonicSeed seed(language, mnemonic); // Verify that the seed data is valid entropy for unified spending keys at - // account 0 and account 0x7FFFFFFE - if (libzcash::UnifiedSpendingKey::Derive(seed, bip44CoinType, 0).has_value() && - libzcash::DeriveZip32TransparentSpendingKey(seed, bip44CoinType, ZCASH_LEGACY_TRANSPARENT_ACCOUNT).has_value()) { + // account 0 and at both the public & private chain levels for account 0x7FFFFFFE + if (libzcash::UnifiedSpendingKey::ForAccount(seed, bip44CoinType, 0).has_value() && + libzcash::BIP32AccountChains::ForAccount(seed, bip44CoinType, ZCASH_LEGACY_TRANSPARENT_ACCOUNT).has_value()) { return seed; } } @@ -68,6 +68,87 @@ uint256 ovkForShieldingFromTaddr(HDSeed& seed) { namespace libzcash { +// +// Transparent +// + +std::optional> DeriveZip32TransparentAccountKey(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId) { + auto rawSeed = seed.RawSeed(); + auto m = CExtKey::Master(rawSeed.data(), rawSeed.size()); + + // We use a fixed keypath scheme of m/32'/coin_type'/account' + // Derive m/32' + auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT); + if (!m_32h.has_value()) return std::nullopt; + + // Derive m/32'/coin_type' + auto m_32h_cth = m_32h.value().Derive(bip44CoinType | ZIP32_HARDENED_KEY_LIMIT); + if (!m_32h_cth.has_value()) return std::nullopt; + + // Derive m/32'/coin_type'/account_id' + auto result = m_32h_cth.value().Derive(accountId | ZIP32_HARDENED_KEY_LIMIT); + if (!result.has_value()) return std::nullopt; + + int64_t nCreationTime = GetTime(); + auto keyMeta = CKeyMetadata(nCreationTime); + keyMeta.hdKeypath = "m/32'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(accountId) + "'"; + keyMeta.seedFp = seed.Fingerprint(); + + return std::make_pair(result.value(), keyMeta); +} + +std::optional BIP32AccountChains::ForAccount( + const HDSeed& seed, + uint32_t bip44CoinType, + uint32_t accountId) { + auto accountKeyOpt = DeriveZip32TransparentAccountKey(seed, bip44CoinType, accountId); + if (!accountKeyOpt.has_value()) return std::nullopt; + + auto accountKey = accountKeyOpt.value(); + auto external = accountKey.first.Derive(0); + auto internal = accountKey.first.Derive(1); + + if (!(external.has_value() && internal.has_value())) return std::nullopt; + + return BIP32AccountChains(seed.Fingerprint(), bip44CoinType, accountId, external.value(), internal.value()); +} + +std::optional> BIP32AccountChains::DeriveExternal(uint32_t addrIndex) { + auto childKey = external.Derive(addrIndex); + if (!childKey.has_value()) return std::nullopt; + + int64_t nCreationTime = GetTime(); + auto keyMeta = CKeyMetadata(nCreationTime); + keyMeta.hdKeypath = "m/32'/" + + std::to_string(bip44CoinType) + "'/" + + std::to_string(accountId) + "'/" + + "0/" + + std::to_string(addrIndex); + keyMeta.seedFp = seedFp; + + return std::make_pair(childKey.value(), keyMeta); +} + +std::optional> BIP32AccountChains::DeriveInternal(uint32_t addrIndex) { + auto childKey = internal.Derive(addrIndex); + if (!childKey.has_value()) return std::nullopt; + + int64_t nCreationTime = GetTime(); + auto keyMeta = CKeyMetadata(nCreationTime); + keyMeta.hdKeypath = "m/32'/" + + std::to_string(bip44CoinType) + "'/" + + std::to_string(accountId) + "'/" + + "1/" + + std::to_string(addrIndex); + keyMeta.seedFp = seedFp; + + return std::make_pair(childKey.value(), keyMeta); +} + +// +// Sapling +// + std::optional SaplingExtendedFullViewingKey::Derive(uint32_t i) const { CDataStream ss_p(SER_NETWORK, PROTOCOL_VERSION); @@ -180,11 +261,11 @@ std::pair SaplingExtendedSpendingKey:: // Create new metadata int64_t nCreationTime = GetTime(); - CKeyMetadata metadata(nCreationTime); - metadata.hdKeypath = "m/32'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(accountId) + "'"; - metadata.seedFp = seed.Fingerprint(); + CKeyMetadata keyMeta(nCreationTime); + keyMeta.hdKeypath = "m/32'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(accountId) + "'"; + keyMeta.seedFp = seed.Fingerprint(); - return std::make_pair(xsk, metadata); + return std::make_pair(xsk, keyMeta); } SaplingExtendedFullViewingKey SaplingExtendedSpendingKey::ToXFVK() const @@ -199,30 +280,17 @@ SaplingExtendedFullViewingKey SaplingExtendedSpendingKey::ToXFVK() const return ret; } -std::optional DeriveZip32TransparentSpendingKey(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId) { - auto rawSeed = seed.RawSeed(); - auto m = CExtKey::Master(rawSeed.data(), rawSeed.size()); +// +// Unified +// - // We use a fixed keypath scheme of m/32'/coin_type'/account' - // Derive m/32' - auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT); - if (!m_32h.has_value()) return std::nullopt; - - // Derive m/32'/coin_type' - auto m_32h_cth = m_32h.value().Derive(bip44CoinType | ZIP32_HARDENED_KEY_LIMIT); - if (!m_32h_cth.has_value()) return std::nullopt; - - // Derive m/32'/coin_type'/account_id' - return m_32h_cth.value().Derive(accountId | ZIP32_HARDENED_KEY_LIMIT); -} - -std::optional> UnifiedSpendingKey::Derive(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId) { +std::optional> UnifiedSpendingKey::ForAccount(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId) { UnifiedSpendingKey usk; usk.accountId = accountId; - auto transparentKey = DeriveZip32TransparentSpendingKey(seed, bip44CoinType, accountId); + auto transparentKey = DeriveZip32TransparentAccountKey(seed, bip44CoinType, accountId); if (!transparentKey.has_value()) return std::nullopt; - usk.transparentKey = transparentKey.value(); + usk.transparentKey = transparentKey.value().first; auto saplingKey = SaplingExtendedSpendingKey::ForAccount(seed, bip44CoinType, accountId); usk.saplingKey = saplingKey.first; @@ -248,7 +316,9 @@ std::optional UnifiedFullViewingKey::Address(diversifier_i ZcashdUnifiedAddress ua; if (transparentKey.has_value()) { + // ensure that the diversifier index is small enough for a t-addr if (MAX_TRANSPARENT_CHILD_IDX.less_than_le(j)) return std::nullopt; + CExtPubKey changeKey; if (!transparentKey.value().Derive(changeKey, 0)) { return std::nullopt; diff --git a/src/zcash/address/zip32.h b/src/zcash/address/zip32.h index 3e019e49e..cc7eb9f6e 100644 --- a/src/zcash/address/zip32.h +++ b/src/zcash/address/zip32.h @@ -351,7 +351,10 @@ private: UnifiedSpendingKey() {} public: - static std::optional> Derive(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId); + static std::optional> ForAccount( + const HDSeed& seed, + uint32_t bip44CoinType, + uint32_t accountId); const std::optional& GetTransparentKey() const { return transparentKey; @@ -366,7 +369,30 @@ public: std::optional ParseZip32KeypathAccount(const std::string& keyPath); -std::optional DeriveZip32TransparentSpendingKey(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId); +std::optional> DeriveZip32TransparentMasterKey( + const HDSeed& seed, + uint32_t bip44CoinType, + uint32_t accountId); + +class BIP32AccountChains { +private: + uint256 seedFp; + uint32_t accountId; + uint32_t bip44CoinType; + CExtKey external; + CExtKey internal; + + BIP32AccountChains(uint256 seedFpIn, uint32_t bip44CoinTypeIn, uint32_t accountIdIn, CExtKey externalIn, CExtKey internalIn): + seedFp(seedFpIn), accountId(accountIdIn), bip44CoinType(bip44CoinTypeIn), external(externalIn), internal(internalIn) {} +public: + static std::optional ForAccount( + const HDSeed& seed, + uint32_t bip44CoinType, + uint32_t accountId); + + std::optional> DeriveExternal(uint32_t addrIndex); + std::optional> DeriveInternal(uint32_t addrIndex); +}; } diff --git a/src/zcbenchmarks.cpp b/src/zcbenchmarks.cpp index b5299fc5e..27ab37765 100644 --- a/src/zcbenchmarks.cpp +++ b/src/zcbenchmarks.cpp @@ -212,8 +212,7 @@ double benchmark_verify_equihash() double benchmark_large_tx(size_t nInputs) { // Create priv/pub key - CKey priv; - priv.MakeNewKey(false); + CKey priv = CKey::TestOnlyRandomKey(true); auto pub = priv.GetPubKey(); CBasicKeyStore tempKeystore; tempKeystore.AddKey(priv);