diff --git a/src/Makefile.am b/src/Makefile.am index aa3213ff6..bca93e6ce 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -107,9 +107,12 @@ LIBZCASH_H = \ zcash/IncrementalMerkleTree.hpp \ zcash/NoteEncryption.hpp \ zcash/Address.hpp \ + zcash/address/bip44.h \ + zcash/address/mnemonic.h \ + zcash/address/orchard.h \ zcash/address/sapling.hpp \ zcash/address/sprout.hpp \ - zcash/address/orchard.h \ + zcash/address/unified.h \ zcash/address/zip32.h \ zcash/History.hpp \ zcash/JoinSplit.hpp \ @@ -536,9 +539,12 @@ libzcash_a_SOURCES = \ zcash/IncrementalMerkleTree.cpp \ zcash/NoteEncryption.cpp \ zcash/Address.cpp \ + zcash/address/bip44.cpp \ + zcash/address/mnemonic.cpp \ + zcash/address/orchard.cpp \ zcash/address/sapling.cpp \ zcash/address/sprout.cpp \ - zcash/address/orchard.cpp \ + zcash/address/unified.cpp \ zcash/address/zip32.cpp \ zcash/History.cpp \ zcash/JoinSplit.cpp \ diff --git a/src/gtest/test_keys.cpp b/src/gtest/test_keys.cpp index 4ff275128..d86c959f6 100644 --- a/src/gtest/test_keys.cpp +++ b/src/gtest/test_keys.cpp @@ -78,11 +78,11 @@ namespace libzcash { return tfm::format("Sapling(%s)", HexStr(ss.begin(), ss.end())); } - std::string operator()(const P2SHAddress &p2sh) const { + std::string operator()(const CScriptID &p2sh) const { return tfm::format("P2SH(%s)", p2sh.GetHex()); } - std::string operator()(const P2PKHAddress &p2pkh) const { + std::string operator()(const CKeyID &p2pkh) const { return tfm::format("P2PKH(%s)", p2pkh.GetHex()); } @@ -140,11 +140,11 @@ TEST(Keys, EncodeAndDecodeUnified) ua.AddReceiver(r); } if (!test[1].isNull()) { - libzcash::P2SHAddress r(ParseHex(test[1].get_str())); + CScriptID r(ParseHex(test[1].get_str())); ua.AddReceiver(r); } if (!test[0].isNull()) { - libzcash::P2PKHAddress r(ParseHex(test[0].get_str())); + CKeyID r(ParseHex(test[0].get_str())); ua.AddReceiver(r); } diff --git a/src/key_io.cpp b/src/key_io.cpp index db83bea04..e22243f9c 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -56,8 +56,8 @@ public: DataLenForReceiver() {} size_t operator()(const libzcash::SaplingPaymentAddress &zaddr) const { return 43; } - size_t operator()(const libzcash::P2SHAddress &p2sh) const { return 20; } - size_t operator()(const libzcash::P2PKHAddress &p2pkh) const { return 20; } + size_t operator()(const CScriptID &p2sh) const { return 20; } + size_t operator()(const CKeyID &p2pkh) const { return 20; } size_t operator()(const libzcash::UnknownReceiver &unknown) const { return unknown.data.size(); } }; @@ -82,11 +82,11 @@ public: memcpy(data, ss.data(), ss.size()); } - void operator()(const libzcash::P2SHAddress &p2sh) const { + void operator()(const CScriptID &p2sh) const { memcpy(data, p2sh.begin(), p2sh.size()); } - void operator()(const libzcash::P2PKHAddress &p2pkh) const { + void operator()(const CKeyID &p2pkh) const { memcpy(data, p2pkh.begin(), p2pkh.size()); } @@ -421,7 +421,7 @@ static bool AddP2SHReceiver(void* ua, const unsigned char* raw) reinterpret_cast(raw + 20), SER_NETWORK, PROTOCOL_VERSION); - libzcash::P2SHAddress receiver; + CScriptID receiver; ss >> receiver; return reinterpret_cast(ua)->AddReceiver(receiver); } @@ -436,7 +436,7 @@ static bool AddP2PKHReceiver(void* ua, const unsigned char* raw) reinterpret_cast(raw + 20), SER_NETWORK, PROTOCOL_VERSION); - libzcash::P2PKHAddress receiver; + CKeyID receiver; ss >> receiver; return reinterpret_cast(ua)->AddReceiver(receiver); } diff --git a/src/keystore.h b/src/keystore.h index 78bf5c400..c2e40b399 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -11,6 +11,7 @@ #include "script/script.h" #include "script/standard.h" #include "sync.h" +#include "zcash/address/mnemonic.h" #include "zcash/Address.hpp" #include "zcash/NoteEncryption.hpp" diff --git a/src/pubkey.h b/src/pubkey.h index 4cdfbef38..94ff547c4 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -22,6 +22,7 @@ class CKeyID : public uint160 public: CKeyID() : uint160() {} CKeyID(const uint160& in) : uint160(in) {} + explicit CKeyID(const std::vector& vch) : uint160(vch) {} }; typedef uint256 ChainCode; diff --git a/src/script/script.h b/src/script/script.h index e67119197..b20227caf 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -625,6 +625,16 @@ public: } }; +/** A reference to a CScript: the Hash160 of its serialization */ +class CScriptID : public uint160 +{ +public: + CScriptID() : uint160() {} + explicit CScriptID(const CScript& in); + CScriptID(const uint160& in) : uint160(in) {} + explicit CScriptID(const std::vector& vch) : uint160(vch) {} +}; + class CReserveScript { public: diff --git a/src/script/standard.h b/src/script/standard.h index 09a23368f..920ac165d 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -18,15 +18,6 @@ static const bool DEFAULT_ACCEPT_DATACARRIER = true; class CKeyID; class CScript; -/** A reference to a CScript: the Hash160 of its serialization (see script.h) */ -class CScriptID : public uint160 -{ -public: - CScriptID() : uint160() {} - explicit CScriptID(const CScript& in); - CScriptID(const uint160& in) : uint160(in) {} -}; - /** * Default setting for nMaxDatacarrierBytes. 80 bytes of data, +1 for OP_RETURN, * +2 for the pushdata opcodes. diff --git a/src/wallet/gtest/test_orchard_zkeys.cpp b/src/wallet/gtest/test_orchard_zkeys.cpp index bdf84f66c..3afd22f0a 100644 --- a/src/wallet/gtest/test_orchard_zkeys.cpp +++ b/src/wallet/gtest/test_orchard_zkeys.cpp @@ -4,6 +4,7 @@ #include +#include "zcash/address/mnemonic.h" #include "zcash/address/orchard.hpp" TEST(OrchardZkeysTest, IVKSerializationRoundtrip) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 3bb5c55d1..6e1ffa2fa 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -23,6 +23,9 @@ #include "wallet/crypter.h" #include "wallet/walletdb.h" #include "wallet/rpcwallet.h" +#include "zcash/address/bip44.h" +#include "zcash/address/unified.h" +#include "zcash/address/mnemonic.h" #include "zcash/Address.hpp" #include "zcash/Note.hpp" #include "base58.h" diff --git a/src/zcash/Address.cpp b/src/zcash/Address.cpp index 9b5340cb1..568b3bbff 100644 --- a/src/zcash/Address.cpp +++ b/src/zcash/Address.cpp @@ -24,8 +24,8 @@ bool UnifiedAddress::AddReceiver(Receiver receiver) { auto t = std::visit(TypecodeForReceiver(), r); if ( (t == typecode) || - (std::holds_alternative(r) && std::holds_alternative(receiver)) || - (std::holds_alternative(r) && std::holds_alternative(receiver)) + (std::holds_alternative(r) && std::holds_alternative(receiver)) || + (std::holds_alternative(r) && std::holds_alternative(receiver)) ) { return false; } @@ -76,13 +76,13 @@ uint32_t TypecodeForReceiver::operator()( } uint32_t TypecodeForReceiver::operator()( - const libzcash::P2SHAddress &p2sh) const + const CScriptID &p2sh) const { return ZCASH_UA_TYPECODE_P2SH; } uint32_t TypecodeForReceiver::operator()( - const libzcash::P2PKHAddress &p2sh) const + const CKeyID &p2sh) const { return ZCASH_UA_TYPECODE_P2PKH; } @@ -100,13 +100,13 @@ std::optional ReceiverToRawAddress::operator()( } std::optional ReceiverToRawAddress::operator()( - const libzcash::P2SHAddress &p2sh) const + const CScriptID &p2sh) const { return std::nullopt; } std::optional ReceiverToRawAddress::operator()( - const libzcash::P2PKHAddress &p2sh) const + const CKeyID &p2sh) const { return std::nullopt; } diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index e41272a90..09e6144a4 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -2,6 +2,8 @@ #define ZC_ADDRESS_H_ #include "uint256.h" +#include "pubkey.h" +#include "script/script.h" #include "zcash/address/orchard.hpp" #include "zcash/address/sapling.hpp" #include "zcash/address/sprout.hpp" @@ -10,17 +12,6 @@ #include namespace libzcash { -// We use new classes here instead of CKeyID and CScriptID to prevent a cyclic dependency. -class P2PKHAddress: public uint160 { -public: - P2PKHAddress() {} - explicit P2PKHAddress(const std::vector& vch) : uint160(vch) {} -}; -class P2SHAddress: public uint160 { -public: - P2SHAddress() {} - explicit P2SHAddress(const std::vector& vch) : uint160(vch) {} -}; /** Protocol addresses that can receive funds in a transaction. */ typedef std::variant RawAddress; @@ -60,8 +51,8 @@ public: */ typedef std::variant< SaplingPaymentAddress, - P2SHAddress, - P2PKHAddress, + CScriptID, + CKeyID, UnknownReceiver> Receiver; struct ReceiverIterator { @@ -178,8 +169,8 @@ public: TypecodeForReceiver() {} uint32_t operator()(const libzcash::SaplingPaymentAddress &zaddr) const; - uint32_t operator()(const libzcash::P2SHAddress &p2sh) const; - uint32_t operator()(const libzcash::P2PKHAddress &p2pkh) const; + uint32_t operator()(const CScriptID &p2sh) const; + uint32_t operator()(const CKeyID &p2pkh) const; uint32_t operator()(const libzcash::UnknownReceiver &p2pkh) const; }; @@ -191,8 +182,8 @@ public: ReceiverToRawAddress() {} std::optional operator()(const libzcash::SaplingPaymentAddress &zaddr) const; - std::optional operator()(const libzcash::P2SHAddress &p2sh) const; - std::optional operator()(const libzcash::P2PKHAddress &p2pkh) const; + std::optional operator()(const CScriptID &p2sh) const; + std::optional operator()(const CKeyID &p2pkh) const; std::optional operator()(const libzcash::UnknownReceiver &p2pkh) const; }; diff --git a/src/zcash/address/bip44.cpp b/src/zcash/address/bip44.cpp new file mode 100644 index 000000000..f3e8b69e8 --- /dev/null +++ b/src/zcash/address/bip44.cpp @@ -0,0 +1,70 @@ +// Copyright (c) 2021 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +#include "bip44.h" + +std::optional> libzcash::DeriveBip44TransparentAccountKey(const HDSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId) { + auto rawSeed = seed.RawSeed(); + auto m = CExtKey::Master(rawSeed.data(), rawSeed.size()); + + // We use a fixed keypath scheme of m/44'/coin_type'/account' + // Derive m/44' + auto m_44h = m.Derive(44 | HARDENED_KEY_LIMIT); + if (!m_44h.has_value()) return std::nullopt; + + // Derive m/44'/coin_type' + auto m_44h_cth = m_44h.value().Derive(bip44CoinType | HARDENED_KEY_LIMIT); + if (!m_44h_cth.has_value()) return std::nullopt; + + // Derive m/44'/coin_type'/account_id' + auto result = m_44h_cth.value().Derive(accountId | HARDENED_KEY_LIMIT); + if (!result.has_value()) return std::nullopt; + + auto hdKeypath = "m/44'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(accountId) + "'"; + + return std::make_pair(result.value(), hdKeypath); +} + +std::optional libzcash::Bip44AccountChains::ForAccount( + const HDSeed& seed, + uint32_t bip44CoinType, + libzcash::AccountId accountId) { + auto accountKeyOpt = DeriveBip44TransparentAccountKey(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 Bip44AccountChains(seed.Fingerprint(), bip44CoinType, accountId, external.value(), internal.value()); +} + +std::optional> libzcash::Bip44AccountChains::DeriveExternal(uint32_t addrIndex) { + auto childKey = external.Derive(addrIndex); + if (!childKey.has_value()) return std::nullopt; + + auto hdKeypath = "m/44'/" + + std::to_string(bip44CoinType) + "'/" + + std::to_string(accountId) + "'/" + + "0/" + + std::to_string(addrIndex); + + return std::make_pair(childKey.value(), hdKeypath); +} + +std::optional> libzcash::Bip44AccountChains::DeriveInternal(uint32_t addrIndex) { + auto childKey = internal.Derive(addrIndex); + if (!childKey.has_value()) return std::nullopt; + + auto hdKeypath = "m/44'/" + + std::to_string(bip44CoinType) + "'/" + + std::to_string(accountId) + "'/" + + "1/" + + std::to_string(addrIndex); + + return std::make_pair(childKey.value(), hdKeypath); +} + diff --git a/src/zcash/address/bip44.h b/src/zcash/address/bip44.h new file mode 100644 index 000000000..4a6bc15c9 --- /dev/null +++ b/src/zcash/address/bip44.h @@ -0,0 +1,36 @@ +// Copyright (c) 2021 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +#ifndef ZCASH_ZCASH_ADDRESS_BIP44_H +#define ZCASH_ZCASH_ADDRESS_BIP44_H + +#include "zip32.h" + +namespace libzcash { + +std::optional> DeriveBip44TransparentAccountKey(const HDSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId); + +class Bip44AccountChains { +private: + uint256 seedFp; + libzcash::AccountId accountId; + uint32_t bip44CoinType; + CExtKey external; + CExtKey internal; + + Bip44AccountChains(uint256 seedFpIn, uint32_t bip44CoinTypeIn, libzcash::AccountId accountIdIn, CExtKey externalIn, CExtKey internalIn): + seedFp(seedFpIn), accountId(accountIdIn), bip44CoinType(bip44CoinTypeIn), external(externalIn), internal(internalIn) {} +public: + static std::optional ForAccount( + const HDSeed& mnemonic, + uint32_t bip44CoinType, + libzcash::AccountId accountId); + + std::optional> DeriveExternal(uint32_t addrIndex); + std::optional> DeriveInternal(uint32_t addrIndex); +}; + +} //namespace libzcash + +#endif // ZCASH_ZCASH_ADDRESS_BIP44_H diff --git a/src/zcash/address/mnemonic.cpp b/src/zcash/address/mnemonic.cpp new file mode 100644 index 000000000..97ac24389 --- /dev/null +++ b/src/zcash/address/mnemonic.cpp @@ -0,0 +1,37 @@ +// Copyright (c) 2018-2021 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +#include "random.h" + +#include "mnemonic.h" + +#include "bip44.h" +#include "unified.h" + +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); + SecureString mnemonic(phrase); + zip339_free_phrase(phrase); + MnemonicSeed seed(language, mnemonic); + + // 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() && + Bip44AccountChains::ForAccount(seed, bip44CoinType, ZCASH_LEGACY_ACCOUNT).has_value()) { + return seed; + } + } +} + diff --git a/src/zcash/address/mnemonic.h b/src/zcash/address/mnemonic.h new file mode 100644 index 000000000..db9f6a517 --- /dev/null +++ b/src/zcash/address/mnemonic.h @@ -0,0 +1,107 @@ +// Copyright (c) 2021 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +#ifndef ZCASH_ZCASH_ADDRESS_MNEMONIC_H +#define ZCASH_ZCASH_ADDRESS_MNEMONIC_H + +#include "zip32.h" + +class MnemonicSeed: public HDSeed { +private: + Language language; + SecureString mnemonic; + + MnemonicSeed() {} + + void SetSeedFromMnemonic() { + seed.resize(64); + zip339_phrase_to_seed(language, mnemonic.c_str(), (uint8_t (*)[64])seed.data()); + } + +public: + MnemonicSeed(Language languageIn, SecureString mnemonicIn): language(languageIn), mnemonic(mnemonicIn) { + SetSeedFromMnemonic(); + } + + /** + * Randomly generate a new mnemonic seed. A SLIP-44 coin type is required to make it possible + * to check that the generated seed can produce valid transparent and unified addresses at account + * numbers 0x7FFFFFFF and 0x00 respectively. + */ + static MnemonicSeed Random(uint32_t bip44CoinType, Language language = English, size_t entropyLen = 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 + inline void SerializationOp(Stream& s, Operation ser_action) { + if (ser_action.ForRead()) { + uint32_t language0; + + READWRITE(language0); + READWRITE(mnemonic); + language = (Language) language0; + SetSeedFromMnemonic(); + } else { + uint32_t language0 = (uint32_t) language; + + READWRITE(language0); + READWRITE(mnemonic); + } + } + + template + static MnemonicSeed Read(Stream& stream) { + MnemonicSeed seed; + stream >> seed; + return seed; + } + + const Language GetLanguage() const { + return language; + } + + const SecureString& GetMnemonic() const { + return mnemonic; + } + + friend bool operator==(const MnemonicSeed& a, const MnemonicSeed& b) + { + return a.seed == b.seed; + } + + friend bool operator!=(const MnemonicSeed& a, const MnemonicSeed& b) + { + return !(a == b); + } +}; + +#endif // ZCASH_ZCASH_ADDRESS_MNEMONIC_H + diff --git a/src/zcash/address/unified.cpp b/src/zcash/address/unified.cpp new file mode 100644 index 000000000..d72a8686e --- /dev/null +++ b/src/zcash/address/unified.cpp @@ -0,0 +1,73 @@ +// Copyright (c) 2021 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +#include "unified.h" +#include "bip44.h" + +using namespace libzcash; + +// +// Unified Keys +// + +std::optional> ZcashdUnifiedSpendingKey::ForAccount(const HDSeed& seed, uint32_t bip44CoinType, AccountId accountId) { + ZcashdUnifiedSpendingKey usk; + usk.accountId = accountId; + + auto transparentKey = DeriveBip44TransparentAccountKey(seed, bip44CoinType, accountId); + if (!transparentKey.has_value()) return std::nullopt; + usk.transparentKey = transparentKey.value().first; + + auto saplingKey = SaplingExtendedSpendingKey::ForAccount(seed, bip44CoinType, accountId); + usk.saplingKey = saplingKey.first; + + return std::make_pair(usk, saplingKey.second); +} + +ZcashdUnifiedFullViewingKey ZcashdUnifiedSpendingKey::ToFullViewingKey() const { + ZcashdUnifiedFullViewingKey ufvk; + + if (transparentKey.has_value()) { + ufvk.transparentKey = transparentKey.value().Neuter(); + } + + if (saplingKey.has_value()) { + ufvk.saplingKey = saplingKey.value().ToXFVK(); + } + + return ufvk; +} + +std::optional ZcashdUnifiedFullViewingKey::Address(diversifier_index_t j) const { + UnifiedAddress ua; + + if (saplingKey.has_value()) { + auto saplingAddress = saplingKey.value().Address(j); + if (saplingAddress.has_value()) { + ua.AddReceiver(saplingAddress.value()); + } else { + return std::nullopt; + } + } + + if (transparentKey.has_value()) { + auto childIndex = j.ToTransparentChildIndex(); + if (!childIndex.has_value()) return std::nullopt; + + CExtPubKey externalKey; + if (!transparentKey.value().Derive(externalKey, 0)) { + return std::nullopt; + } + + CExtPubKey childKey; + if (externalKey.Derive(childKey, childIndex.value())) { + ua.AddReceiver(childKey.pubkey.GetID()); + } else { + return std::nullopt; + } + } + + return ua; +} + diff --git a/src/zcash/address/unified.h b/src/zcash/address/unified.h new file mode 100644 index 000000000..be87a8d6b --- /dev/null +++ b/src/zcash/address/unified.h @@ -0,0 +1,74 @@ +// Copyright (c) 2021 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +#ifndef ZCASH_ZCASH_ADDRESS_UNIFIED_H +#define ZCASH_ZCASH_ADDRESS_UNIFIED_H + +#include "zip32.h" +#include "bip44.h" +#include "zcash/Address.hpp" + +namespace libzcash { + +class ZcashdUnifiedSpendingKey; +class ZcashdUnifiedFullViewingKey; + +class ZcashdUnifiedFullViewingKey { +private: + std::optional transparentKey; + std::optional saplingKey; + + ZcashdUnifiedFullViewingKey() {} + + friend class ZcashdUnifiedSpendingKey; +public: + const std::optional& GetTransparentKey() const { + return transparentKey; + } + + const std::optional& GetSaplingExtendedFullViewingKey() const { + return saplingKey; + } + + std::optional Address(diversifier_index_t j) const; + + std::pair FindAddress(diversifier_index_t j) const { + auto addr = Address(j); + while (!addr.has_value()) { + if (!j.increment()) + throw std::runtime_error(std::string(__func__) + ": diversifier index overflow.");; + addr = Address(j); + } + return std::make_pair(addr.value(), j); + } +}; + +class ZcashdUnifiedSpendingKey { +private: + libzcash::AccountId accountId; + std::optional transparentKey; + std::optional saplingKey; + + ZcashdUnifiedSpendingKey() {} +public: + static std::optional> ForAccount( + const HDSeed& seed, + uint32_t bip44CoinType, + libzcash::AccountId accountId); + + const std::optional& GetTransparentKey() const { + return transparentKey; + } + + const std::optional& GetSaplingExtendedSpendingKey() const { + return saplingKey; + } + + ZcashdUnifiedFullViewingKey ToFullViewingKey() const; +}; + +} //namespace libzcash + +#endif // ZCASH_ZCASH_ADDRESS_UNIFIED_H + diff --git a/src/zcash/address/zip32.cpp b/src/zcash/address/zip32.cpp index f88311fde..fc558edef 100644 --- a/src/zcash/address/zip32.cpp +++ b/src/zcash/address/zip32.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018 The Zcash developers +// Copyright (c) 2018-2021 The Zcash developers // Distributed under the MIT software license, see the accompanying // file COPYING or https://www.opensource.org/licenses/mit-license.php . @@ -21,30 +21,6 @@ const unsigned char ZCASH_TADDR_OVK_PERSONAL[BLAKE2bPersonalBytes] = const libzcash::diversifier_index_t MAX_TRANSPARENT_CHILD_IDX(0x7FFFFFFF); -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); - SecureString mnemonic(phrase); - zip339_free_phrase(phrase); - MnemonicSeed seed(language, mnemonic); - - // 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 (libzcash::ZcashdUnifiedSpendingKey::ForAccount(seed, bip44CoinType, 0).has_value() && - libzcash::Bip44AccountChains::ForAccount(seed, bip44CoinType, ZCASH_LEGACY_ACCOUNT).has_value()) { - return seed; - } - } -} - uint256 HDSeed::Fingerprint() const { CBLAKE2bWriter h(SER_GETHASH, 0, ZCASH_HD_SEED_FP_PERSONAL); @@ -81,74 +57,6 @@ std::optional diversifier_index_t::ToTransparentChildIndex() const } } -// -// Transparent -// - -std::optional> DeriveBip44TransparentAccountKey(const MnemonicSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId) { - auto rawSeed = seed.RawSeed(); - auto m = CExtKey::Master(rawSeed.data(), rawSeed.size()); - - // We use a fixed keypath scheme of m/44'/coin_type'/account' - // Derive m/44' - auto m_44h = m.Derive(44 | HARDENED_KEY_LIMIT); - if (!m_44h.has_value()) return std::nullopt; - - // Derive m/44'/coin_type' - auto m_44h_cth = m_44h.value().Derive(bip44CoinType | HARDENED_KEY_LIMIT); - if (!m_44h_cth.has_value()) return std::nullopt; - - // Derive m/44'/coin_type'/account_id' - auto result = m_44h_cth.value().Derive(accountId | HARDENED_KEY_LIMIT); - if (!result.has_value()) return std::nullopt; - - auto hdKeypath = "m/44'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(accountId) + "'"; - - return std::make_pair(result.value(), hdKeypath); -} - -std::optional Bip44AccountChains::ForAccount( - const MnemonicSeed& seed, - uint32_t bip44CoinType, - libzcash::AccountId accountId) { - auto accountKeyOpt = DeriveBip44TransparentAccountKey(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 Bip44AccountChains(seed.Fingerprint(), bip44CoinType, accountId, external.value(), internal.value()); -} - -std::optional> Bip44AccountChains::DeriveExternal(uint32_t addrIndex) { - auto childKey = external.Derive(addrIndex); - if (!childKey.has_value()) return std::nullopt; - - auto hdKeypath = "m/44'/" - + std::to_string(bip44CoinType) + "'/" - + std::to_string(accountId) + "'/" - + "0/" - + std::to_string(addrIndex); - - return std::make_pair(childKey.value(), hdKeypath); -} - -std::optional> Bip44AccountChains::DeriveInternal(uint32_t addrIndex) { - auto childKey = internal.Derive(addrIndex); - if (!childKey.has_value()) return std::nullopt; - - auto hdKeypath = "m/44'/" - + std::to_string(bip44CoinType) + "'/" - + std::to_string(accountId) + "'/" - + "1/" - + std::to_string(addrIndex); - - return std::make_pair(childKey.value(), hdKeypath); -} - // // Sapling // @@ -251,7 +159,7 @@ SaplingExtendedSpendingKey SaplingExtendedSpendingKey::Derive(uint32_t i) const return xsk_i; } -std::pair SaplingExtendedSpendingKey::ForAccount(const MnemonicSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId) { +std::pair SaplingExtendedSpendingKey::ForAccount(const HDSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId) { auto m = Master(seed); // We use a fixed keypath scheme of m/32'/coin_type'/account' @@ -269,7 +177,7 @@ std::pair SaplingExtendedSpendingKey::For return std::make_pair(xsk, hdKeypath); } -std::pair SaplingExtendedSpendingKey::Legacy(const MnemonicSeed& seed, uint32_t bip44CoinType, uint32_t addressIndex) { +std::pair SaplingExtendedSpendingKey::Legacy(const HDSeed& seed, uint32_t bip44CoinType, uint32_t addressIndex) { auto m = Master(seed); // We use a fixed keypath scheme of m/32'/coin_type'/0x7FFFFFFF'/addressIndex' @@ -309,70 +217,6 @@ SaplingExtendedFullViewingKey SaplingExtendedSpendingKey::ToXFVK() const return ret; } -// -// Unified -// - -std::optional> ZcashdUnifiedSpendingKey::ForAccount(const MnemonicSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId) { - ZcashdUnifiedSpendingKey usk; - usk.accountId = accountId; - - auto transparentKey = DeriveBip44TransparentAccountKey(seed, bip44CoinType, accountId); - if (!transparentKey.has_value()) return std::nullopt; - usk.transparentKey = transparentKey.value().first; - - auto saplingKey = SaplingExtendedSpendingKey::ForAccount(seed, bip44CoinType, accountId); - usk.saplingKey = saplingKey.first; - - return std::make_pair(usk, saplingKey.second); -} - -ZcashdUnifiedFullViewingKey ZcashdUnifiedSpendingKey::ToFullViewingKey() const { - ZcashdUnifiedFullViewingKey ufvk; - - if (transparentKey.has_value()) { - ufvk.transparentKey = transparentKey.value().Neuter(); - } - - if (saplingKey.has_value()) { - ufvk.saplingKey = saplingKey.value().ToXFVK(); - } - - return ufvk; -} - -std::optional ZcashdUnifiedFullViewingKey::Address(diversifier_index_t j) const { - ZcashdUnifiedAddress ua; - - if (transparentKey.has_value()) { - auto childIndex = j.ToTransparentChildIndex(); - if (!childIndex.has_value()) return std::nullopt; - - CExtPubKey externalKey; - if (!transparentKey.value().Derive(externalKey, 0)) { - return std::nullopt; - } - - CExtPubKey childKey; - if (externalKey.Derive(childKey, childIndex.value())) { - ua.transparentAddress = childKey.pubkey.GetID(); - } 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 ParseHDKeypathAccount(uint32_t purpose, uint32_t coinType, const std::string& keyPath) { std::regex pattern("m/" + std::to_string(purpose) + "'/" + std::to_string(coinType) + "'/([0-9]+)'.*"); std::smatch matches; diff --git a/src/zcash/address/zip32.h b/src/zcash/address/zip32.h index 9da16b71e..7f9cf4cc9 100644 --- a/src/zcash/address/zip32.h +++ b/src/zcash/address/zip32.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018 The Zcash developers +// Copyright (c) 2018-2021 The Zcash developers // Distributed under the MIT software license, see the accompanying // file COPYING or https://www.opensource.org/licenses/mit-license.php . @@ -22,13 +22,6 @@ const uint32_t HARDENED_KEY_LIMIT = 0x80000000; const size_t ZIP32_XFVK_SIZE = 169; const size_t ZIP32_XSK_SIZE = 169; -/** - * The account identifier used for HD derivation of - * transparent and Sapling addresses via the legacy - * `getnewaddress` and `z_getnewaddress` code paths, - */ -const uint32_t ZCASH_LEGACY_ACCOUNT = HARDENED_KEY_LIMIT - 1; - typedef std::vector> RawHDSeed; typedef std::string HDKeyPath; @@ -64,102 +57,6 @@ public: }; -class MnemonicSeed: public HDSeed { -private: - Language language; - SecureString mnemonic; - - MnemonicSeed() {} - - void SetSeedFromMnemonic() { - seed.resize(64); - zip339_phrase_to_seed(language, mnemonic.c_str(), (uint8_t (*)[64])seed.data()); - } - -public: - MnemonicSeed(Language languageIn, SecureString mnemonicIn): language(languageIn), mnemonic(mnemonicIn) { - SetSeedFromMnemonic(); - } - - /** - * Randomly generate a new mnemonic seed. A SLIP-44 coin type is required to make it possible - * to check that the generated seed can produce valid transparent and unified addresses at account - * numbers 0x7FFFFFFF and 0x00 respectively. - */ - static MnemonicSeed Random(uint32_t bip44CoinType, Language language = English, size_t entropyLen = 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 - inline void SerializationOp(Stream& s, Operation ser_action) { - if (ser_action.ForRead()) { - uint32_t language0; - - READWRITE(language0); - READWRITE(mnemonic); - language = (Language) language0; - SetSeedFromMnemonic(); - } else { - uint32_t language0 = (uint32_t) language; - - READWRITE(language0); - READWRITE(mnemonic); - } - } - - template - static MnemonicSeed Read(Stream& stream) { - MnemonicSeed seed; - stream >> seed; - return seed; - } - - const Language GetLanguage() const { - return language; - } - - const SecureString& GetMnemonic() const { - return mnemonic; - } - - friend bool operator==(const MnemonicSeed& a, const MnemonicSeed& b) - { - return a.seed == b.seed; - } - - friend bool operator!=(const MnemonicSeed& a, const MnemonicSeed& b) - { - return !(a == b); - } -}; - // This is not part of ZIP 32, but is here because it's linked to the HD seed. uint256 ovkForShieldingFromTaddr(HDSeed& seed); @@ -167,6 +64,13 @@ namespace libzcash { typedef uint32_t AccountId; +/** + * The account identifier used for HD derivation of + * transparent and Sapling addresses via the legacy + * `getnewaddress` and `z_getnewaddress` code paths, + */ +const AccountId ZCASH_LEGACY_ACCOUNT = HARDENED_KEY_LIMIT - 1; + /** * 88-bit diversifier index. This would ideally derive from base_uint * but those values must have bit widths that are multiples of 32. @@ -292,8 +196,8 @@ struct SaplingExtendedSpendingKey { } static SaplingExtendedSpendingKey Master(const HDSeed& seed); - static std::pair ForAccount(const MnemonicSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId); - static std::pair Legacy(const MnemonicSeed& seed, uint32_t bip44CoinType, uint32_t addressIndex); + static std::pair ForAccount(const HDSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId); + static std::pair Legacy(const HDSeed& seed, uint32_t bip44CoinType, uint32_t addressIndex); SaplingExtendedSpendingKey Derive(uint32_t i) const; @@ -311,104 +215,8 @@ struct SaplingExtendedSpendingKey { } }; -class ZcashdUnifiedSpendingKey; -class ZcashdUnifiedFullViewingKey; - -class ZcashdUnifiedAddress { -private: - diversifier_index_t diversifier_index; - std::optional transparentAddress; - std::optional saplingAddress; - - friend class ZcashdUnifiedFullViewingKey; - - ZcashdUnifiedAddress() {} -public: - const std::optional& GetTransparentAddress() const { - return transparentAddress; - } - - const std::optional& GetSaplingPaymentAddress() const { - return saplingAddress; - } -}; - -class ZcashdUnifiedFullViewingKey { -private: - std::optional transparentKey; - std::optional saplingKey; - - ZcashdUnifiedFullViewingKey() {} - - friend class ZcashdUnifiedSpendingKey; -public: - const std::optional& GetTransparentKey() const { - return transparentKey; - } - - const std::optional& GetSaplingExtendedFullViewingKey() const { - return saplingKey; - } - - std::optional Address(diversifier_index_t j) const; - - std::pair FindAddress(diversifier_index_t j) const { - auto addr = Address(j); - while (!addr.has_value()) { - if (!j.increment()) - throw std::runtime_error(std::string(__func__) + ": diversifier index overflow.");; - addr = Address(j); - } - return std::make_pair(addr.value(), j); - } -}; - -class ZcashdUnifiedSpendingKey { -private: - libzcash::AccountId accountId; - std::optional transparentKey; - std::optional saplingKey; - - ZcashdUnifiedSpendingKey() {} -public: - static std::optional> ForAccount( - const MnemonicSeed& mnemonic, - uint32_t bip44CoinType, - libzcash::AccountId accountId); - - const std::optional& GetTransparentKey() const { - return transparentKey; - } - - const std::optional& GetSaplingExtendedSpendingKey() const { - return saplingKey; - } - - ZcashdUnifiedFullViewingKey ToFullViewingKey() const; -}; - std::optional ParseHDKeypathAccount(uint32_t purpose, uint32_t coinType, const std::string& keyPath); -class Bip44AccountChains { -private: - uint256 seedFp; - libzcash::AccountId accountId; - uint32_t bip44CoinType; - CExtKey external; - CExtKey internal; - - Bip44AccountChains(uint256 seedFpIn, uint32_t bip44CoinTypeIn, libzcash::AccountId accountIdIn, CExtKey externalIn, CExtKey internalIn): - seedFp(seedFpIn), accountId(accountIdIn), bip44CoinType(bip44CoinTypeIn), external(externalIn), internal(internalIn) {} -public: - static std::optional ForAccount( - const MnemonicSeed& mnemonic, - uint32_t bip44CoinType, - libzcash::AccountId accountId); - - std::optional> DeriveExternal(uint32_t addrIndex); - std::optional> DeriveInternal(uint32_t addrIndex); -}; - -} +} //namespace libzcash #endif // ZCASH_ZCASH_ADDRESS_ZIP32_H