Merge pull request #5396 from nuttycom/feature/wallet_orchard-unified_addrs_prereq

Prerequisites for adding unified addresses to the wallet.
This commit is contained in:
Kris Nuttycombe 2021-11-30 18:24:50 -07:00 committed by GitHub
commit c908a3b059
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 459 additions and 406 deletions

View File

@ -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 \

View File

@ -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);
}

View File

@ -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<const char*>(raw + 20),
SER_NETWORK,
PROTOCOL_VERSION);
libzcash::P2SHAddress receiver;
CScriptID receiver;
ss >> receiver;
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
}
@ -436,7 +436,7 @@ static bool AddP2PKHReceiver(void* ua, const unsigned char* raw)
reinterpret_cast<const char*>(raw + 20),
SER_NETWORK,
PROTOCOL_VERSION);
libzcash::P2PKHAddress receiver;
CKeyID receiver;
ss >> receiver;
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
}

View File

@ -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"

View File

@ -22,6 +22,7 @@ class CKeyID : public uint160
public:
CKeyID() : uint160() {}
CKeyID(const uint160& in) : uint160(in) {}
explicit CKeyID(const std::vector<unsigned char>& vch) : uint160(vch) {}
};
typedef uint256 ChainCode;

View File

@ -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<unsigned char>& vch) : uint160(vch) {}
};
class CReserveScript
{
public:

View File

@ -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.

View File

@ -4,6 +4,7 @@
#include <gtest/gtest.h>
#include "zcash/address/mnemonic.h"
#include "zcash/address/orchard.hpp"
TEST(OrchardZkeysTest, IVKSerializationRoundtrip) {

View File

@ -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"

View File

@ -24,8 +24,8 @@ bool UnifiedAddress::AddReceiver(Receiver receiver) {
auto t = std::visit(TypecodeForReceiver(), r);
if (
(t == typecode) ||
(std::holds_alternative<P2PKHAddress>(r) && std::holds_alternative<P2SHAddress>(receiver)) ||
(std::holds_alternative<P2SHAddress>(r) && std::holds_alternative<P2PKHAddress>(receiver))
(std::holds_alternative<CKeyID>(r) && std::holds_alternative<CScriptID>(receiver)) ||
(std::holds_alternative<CScriptID>(r) && std::holds_alternative<CKeyID>(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<libzcash::RawAddress> ReceiverToRawAddress::operator()(
}
std::optional<libzcash::RawAddress> ReceiverToRawAddress::operator()(
const libzcash::P2SHAddress &p2sh) const
const CScriptID &p2sh) const
{
return std::nullopt;
}
std::optional<libzcash::RawAddress> ReceiverToRawAddress::operator()(
const libzcash::P2PKHAddress &p2sh) const
const CKeyID &p2sh) const
{
return std::nullopt;
}

View File

@ -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 <variant>
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<unsigned char>& vch) : uint160(vch) {}
};
class P2SHAddress: public uint160 {
public:
P2SHAddress() {}
explicit P2SHAddress(const std::vector<unsigned char>& vch) : uint160(vch) {}
};
/** Protocol addresses that can receive funds in a transaction. */
typedef std::variant<SproutPaymentAddress, SaplingPaymentAddress> 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<libzcash::RawAddress> operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
std::optional<libzcash::RawAddress> operator()(const libzcash::P2SHAddress &p2sh) const;
std::optional<libzcash::RawAddress> operator()(const libzcash::P2PKHAddress &p2pkh) const;
std::optional<libzcash::RawAddress> operator()(const CScriptID &p2sh) const;
std::optional<libzcash::RawAddress> operator()(const CKeyID &p2pkh) const;
std::optional<libzcash::RawAddress> operator()(const libzcash::UnknownReceiver &p2pkh) const;
};

View File

@ -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<std::pair<CExtKey, HDKeyPath>> 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> 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<std::pair<CExtKey, HDKeyPath>> 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<std::pair<CExtKey, HDKeyPath>> 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);
}

36
src/zcash/address/bip44.h Normal file
View File

@ -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<std::pair<CExtKey, HDKeyPath>> 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<Bip44AccountChains> ForAccount(
const HDSeed& mnemonic,
uint32_t bip44CoinType,
libzcash::AccountId accountId);
std::optional<std::pair<CExtKey, HDKeyPath>> DeriveExternal(uint32_t addrIndex);
std::optional<std::pair<CExtKey, HDKeyPath>> DeriveInternal(uint32_t addrIndex);
};
} //namespace libzcash
#endif // ZCASH_ZCASH_ADDRESS_BIP44_H

View File

@ -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;
}
}
}

View File

@ -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 <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;
SetSeedFromMnemonic();
} 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 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

View File

@ -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<std::pair<ZcashdUnifiedSpendingKey, HDKeyPath>> 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<UnifiedAddress> 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;
}

View File

@ -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<CExtPubKey> transparentKey;
std::optional<SaplingExtendedFullViewingKey> saplingKey;
ZcashdUnifiedFullViewingKey() {}
friend class ZcashdUnifiedSpendingKey;
public:
const std::optional<CExtPubKey>& GetTransparentKey() const {
return transparentKey;
}
const std::optional<SaplingExtendedFullViewingKey>& GetSaplingExtendedFullViewingKey() const {
return saplingKey;
}
std::optional<UnifiedAddress> Address(diversifier_index_t j) const;
std::pair<UnifiedAddress, diversifier_index_t> 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<CExtKey> transparentKey;
std::optional<SaplingExtendedSpendingKey> saplingKey;
ZcashdUnifiedSpendingKey() {}
public:
static std::optional<std::pair<ZcashdUnifiedSpendingKey, HDKeyPath>> ForAccount(
const HDSeed& seed,
uint32_t bip44CoinType,
libzcash::AccountId accountId);
const std::optional<CExtKey>& GetTransparentKey() const {
return transparentKey;
}
const std::optional<SaplingExtendedSpendingKey>& GetSaplingExtendedSpendingKey() const {
return saplingKey;
}
ZcashdUnifiedFullViewingKey ToFullViewingKey() const;
};
} //namespace libzcash
#endif // ZCASH_ZCASH_ADDRESS_UNIFIED_H

View File

@ -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<unsigned int> diversifier_index_t::ToTransparentChildIndex() const
}
}
//
// Transparent
//
std::optional<std::pair<CExtKey, HDKeyPath>> 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> 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<std::pair<CExtKey, HDKeyPath>> 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<std::pair<CExtKey, HDKeyPath>> 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, HDKeyPath> SaplingExtendedSpendingKey::ForAccount(const MnemonicSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId) {
std::pair<SaplingExtendedSpendingKey, HDKeyPath> 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, HDKeyPath> SaplingExtendedSpendingKey::For
return std::make_pair(xsk, hdKeypath);
}
std::pair<SaplingExtendedSpendingKey, HDKeyPath> SaplingExtendedSpendingKey::Legacy(const MnemonicSeed& seed, uint32_t bip44CoinType, uint32_t addressIndex) {
std::pair<SaplingExtendedSpendingKey, HDKeyPath> 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<std::pair<ZcashdUnifiedSpendingKey, HDKeyPath>> 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<ZcashdUnifiedAddress> 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<unsigned long> 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;

View File

@ -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<unsigned char, secure_allocator<unsigned char>> 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 <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;
SetSeedFromMnemonic();
} 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 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<SaplingExtendedSpendingKey, HDKeyPath> ForAccount(const MnemonicSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId);
static std::pair<SaplingExtendedSpendingKey, HDKeyPath> Legacy(const MnemonicSeed& seed, uint32_t bip44CoinType, uint32_t addressIndex);
static std::pair<SaplingExtendedSpendingKey, HDKeyPath> ForAccount(const HDSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId);
static std::pair<SaplingExtendedSpendingKey, HDKeyPath> 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<CKeyID> transparentAddress;
std::optional<SaplingPaymentAddress> saplingAddress;
friend class ZcashdUnifiedFullViewingKey;
ZcashdUnifiedAddress() {}
public:
const std::optional<CKeyID>& GetTransparentAddress() const {
return transparentAddress;
}
const std::optional<SaplingPaymentAddress>& GetSaplingPaymentAddress() const {
return saplingAddress;
}
};
class ZcashdUnifiedFullViewingKey {
private:
std::optional<CExtPubKey> transparentKey;
std::optional<SaplingExtendedFullViewingKey> saplingKey;
ZcashdUnifiedFullViewingKey() {}
friend class ZcashdUnifiedSpendingKey;
public:
const std::optional<CExtPubKey>& GetTransparentKey() const {
return transparentKey;
}
const std::optional<SaplingExtendedFullViewingKey>& GetSaplingExtendedFullViewingKey() const {
return saplingKey;
}
std::optional<ZcashdUnifiedAddress> Address(diversifier_index_t j) const;
std::pair<ZcashdUnifiedAddress, diversifier_index_t> 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<CExtKey> transparentKey;
std::optional<SaplingExtendedSpendingKey> saplingKey;
ZcashdUnifiedSpendingKey() {}
public:
static std::optional<std::pair<ZcashdUnifiedSpendingKey, HDKeyPath>> ForAccount(
const MnemonicSeed& mnemonic,
uint32_t bip44CoinType,
libzcash::AccountId accountId);
const std::optional<CExtKey>& GetTransparentKey() const {
return transparentKey;
}
const std::optional<SaplingExtendedSpendingKey>& GetSaplingExtendedSpendingKey() const {
return saplingKey;
}
ZcashdUnifiedFullViewingKey ToFullViewingKey() const;
};
std::optional<unsigned long> 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<Bip44AccountChains> ForAccount(
const MnemonicSeed& mnemonic,
uint32_t bip44CoinType,
libzcash::AccountId accountId);
std::optional<std::pair<CExtKey, HDKeyPath>> DeriveExternal(uint32_t addrIndex);
std::optional<std::pair<CExtKey, HDKeyPath>> DeriveInternal(uint32_t addrIndex);
};
}
} //namespace libzcash
#endif // ZCASH_ZCASH_ADDRESS_ZIP32_H