From f6ad523ccbc77c88730f4c5ff113a64538e0ce9b Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 24 Sep 2021 14:24:41 -0600 Subject: [PATCH] Add unified full viewing keys and Zcash-internal unified addresses. --- src/uint256.h | 21 +++++++++++ src/wallet/test/rpc_wallet_tests.cpp | 40 ++++++++++++++++++--- src/zcash/address/zip32.cpp | 50 +++++++++++++++++++++++++- src/zcash/address/zip32.h | 52 ++++++++++++++++++++++++++-- 4 files changed, 156 insertions(+), 7 deletions(-) diff --git a/src/uint256.h b/src/uint256.h index 60746304d..76a7917ce 100644 --- a/src/uint256.h +++ b/src/uint256.h @@ -108,6 +108,16 @@ class blob88 : public base_blob<88> { public: blob88() {} blob88(const base_blob<88>& b) : base_blob<88>(b) {} + blob88(uint64_t i): base_blob<88>() { + data[0] = (uint8_t) i; + data[1] = (uint8_t) (i >> 8); + data[2] = (uint8_t) (i >> 16); + data[3] = (uint8_t) (i >> 24); + data[4] = (uint8_t) (i >> 32); + data[5] = (uint8_t) (i >> 40); + data[6] = (uint8_t) (i >> 48); + data[7] = (uint8_t) (i >> 56); + } explicit blob88(const std::vector& vch) : base_blob<88>(vch) {} std::optional increment() const { @@ -123,6 +133,17 @@ public: return std::nullopt; } + + // treat as little-endian for numeric comparison + bool less_than_le(const blob88& other) const { + for (int i = 10; i >= 0; i--) { + if (data[i] < other.data[i]) { + return true; + } + } + + return false; + } }; /** 160-bit opaque blob. diff --git a/src/wallet/test/rpc_wallet_tests.cpp b/src/wallet/test/rpc_wallet_tests.cpp index 771e558f1..49e84c47b 100644 --- a/src/wallet/test/rpc_wallet_tests.cpp +++ b/src/wallet/test/rpc_wallet_tests.cpp @@ -75,6 +75,15 @@ static UniValue ValueFromString(const std::string &str) return value; } +static SaplingPaymentAddress DefaultSaplingAddress(CWallet* pwallet) { + auto usk = pwallet->GetUnifiedSpendingKeyForAccount(0); + + return usk.value() + .ToFullViewingKey() + .GetSaplingExtendedFullViewingKey().value() + .FindAddress(libzcash::diversifier_index_t(0)).first; +} + BOOST_FIXTURE_TEST_SUITE(rpc_wallet_tests, WalletTestingSetup) BOOST_AUTO_TEST_CASE(rpc_addmultisig) @@ -788,12 +797,15 @@ void CheckHaveAddr(const libzcash::PaymentAddress& addr) { BOOST_AUTO_TEST_CASE(rpc_wallet_z_getnewaddress) { using namespace libzcash; - UniValue addr; - if (!pwalletMain->HaveMnemonicSeed()) { - pwalletMain->GenerateNewSeed(); + if (!pwalletMain->HaveLegacyHDSeed()) { + // fake a legacy seed by creating a separate mnemonic seed + auto seed = MnemonicSeed::Random(1); + pwalletMain->LoadLegacyHDSeed(seed); } + UniValue addr; + KeyIO keyIO(Params()); // No parameter defaults to sapling address addr = CallRPC("z_getnewaddress"); @@ -1459,7 +1471,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling) // add keys manually auto taddr = pwalletMain->GenerateNewKey().GetID(); std::string taddr1 = keyIO.EncodeDestination(taddr); - auto pa = pwalletMain->GenerateNewLegacySaplingZKey().value(); + auto pa = DefaultSaplingAddress(pwalletMain); std::string zaddr1 = keyIO.EncodePaymentAddress(pa); const Consensus::Params& consensusParams = Params().GetConsensus(); @@ -1549,6 +1561,13 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling) BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_zkeys) { LOCK2(cs_main, pwalletMain->cs_wallet); + + if (!pwalletMain->HaveLegacyHDSeed()) { + // fake a legacy seed by creating a separate mnemonic seed + auto seed = MnemonicSeed::Random(1); + pwalletMain->LoadLegacyHDSeed(seed); + } + UniValue retValue; int n = 100; @@ -1605,6 +1624,13 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_zkeys) BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_sapzkeys) { LOCK2(cs_main, pwalletMain->cs_wallet); + + if (!pwalletMain->HaveLegacyHDSeed()) { + // fake a legacy seed by creating a separate mnemonic seed + auto seed = MnemonicSeed::Random(1); + pwalletMain->LoadLegacyHDSeed(seed); + } + UniValue retValue; int n = 100; @@ -1668,6 +1694,12 @@ BOOST_AUTO_TEST_CASE(rpc_z_listunspent_parameters) { SelectParams(CBaseChainParams::TESTNET); + if (!pwalletMain->HaveLegacyHDSeed()) { + // fake a legacy seed by creating a separate mnemonic seed + auto seed = MnemonicSeed::Random(1); + pwalletMain->LoadLegacyHDSeed(seed); + } + LOCK2(cs_main, pwalletMain->cs_wallet); UniValue retValue; diff --git a/src/zcash/address/zip32.cpp b/src/zcash/address/zip32.cpp index 12f5a8548..01a7dd325 100644 --- a/src/zcash/address/zip32.cpp +++ b/src/zcash/address/zip32.cpp @@ -19,6 +19,8 @@ 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'}; +const libzcash::diversifier_index_t MAX_TRANSPARENT_CHILD_IDX(0x40000000); + MnemonicSeed MnemonicSeed::Random(uint32_t bip44CoinType, Language language, size_t entropyLen) { assert(entropyLen >= 32); @@ -216,10 +218,11 @@ std::optional DeriveZip32TransparentSpendingKey(const HDSeed& seed, uin std::optional> UnifiedSpendingKey::Derive(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId) { UnifiedSpendingKey usk; + usk.accountId = accountId; auto transparentKey = DeriveZip32TransparentSpendingKey(seed, bip44CoinType, accountId); if (!transparentKey.has_value()) return std::nullopt; - usk.p2pkhKey = transparentKey.value(); + usk.transparentKey = transparentKey.value(); auto saplingKey = SaplingExtendedSpendingKey::ForAccount(seed, bip44CoinType, accountId); usk.saplingKey = saplingKey.first; @@ -227,6 +230,51 @@ std::optional> UnifiedSpendingKey::D return std::make_pair(usk, saplingKey.second); } +UnifiedFullViewingKey UnifiedSpendingKey::ToFullViewingKey() const { + UnifiedFullViewingKey ufvk; + + if (transparentKey.has_value()) { + ufvk.transparentKey = transparentKey.value().Neuter(); + } + + if (saplingKey.has_value()) { + ufvk.saplingKey = saplingKey.value().ToXFVK(); + } + + return ufvk; +} + +std::optional UnifiedFullViewingKey::Address(diversifier_index_t j) const { + ZcashdUnifiedAddress ua; + + if (transparentKey.has_value()) { + if (MAX_TRANSPARENT_CHILD_IDX.less_than_le(j)) return std::nullopt; + CExtPubKey changeKey; + if (!transparentKey.value().Derive(changeKey, 0)) { + return std::nullopt; + } + + CExtPubKey childKey; + unsigned int childIndex = (unsigned int) j.GetUint64(0); + if (changeKey.Derive(childKey, childIndex)) { + ua.transparentKey = childKey.pubkey; + } else { + return std::nullopt; + } + } + + if (saplingKey.has_value()) { + auto saplingAddress = saplingKey.value().Address(j); + if (saplingAddress.has_value()) { + ua.saplingAddress = saplingAddress.value(); + } else { + return std::nullopt; + } + } + + return ua; +} + std::optional ParseZip32KeypathAccount(const std::string& keyPath) { std::regex pattern("m/32'/[0-9]+'/([0-9]+)'"); std::smatch matches; diff --git a/src/zcash/address/zip32.h b/src/zcash/address/zip32.h index 478f67c13..46eff708d 100644 --- a/src/zcash/address/zip32.h +++ b/src/zcash/address/zip32.h @@ -281,19 +281,67 @@ struct SaplingExtendedSpendingKey { } }; +class UnifiedSpendingKey; +class UnifiedFullViewingKey; + +class ZcashdUnifiedAddress { +private: + diversifier_index_t diversifier_index; + std::optional transparentKey; //TODO: should this just be the public key hash? + std::optional saplingAddress; + + friend class UnifiedFullViewingKey; + + ZcashdUnifiedAddress() {} +public: + const std::optional& GetTransparentKey() const { + return transparentKey; + } + + const std::optional& GetSaplingPaymentAddress() const { + return saplingAddress; + } +}; + +class UnifiedFullViewingKey { +private: + std::optional transparentKey; + std::optional saplingKey; + + UnifiedFullViewingKey() {} + + friend class UnifiedSpendingKey; +public: + const std::optional& GetTransparentKey() const { + return transparentKey; + } + + const std::optional& GetSaplingExtendedFullViewingKey() const { + return saplingKey; + } + + std::optional Address(diversifier_index_t j) const; +}; + class UnifiedSpendingKey { private: uint32_t accountId; - std::optional p2pkhKey; + std::optional transparentKey; std::optional saplingKey; UnifiedSpendingKey() {} public: static std::optional> Derive(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId); - const std::optional& GetSaplingExtendedSpendingKey() { + const std::optional& GetTransparentKey() const { + return transparentKey; + } + + const std::optional& GetSaplingExtendedSpendingKey() const { return saplingKey; } + + UnifiedFullViewingKey ToFullViewingKey() const; }; std::optional ParseZip32KeypathAccount(const std::string& keyPath);