Merge pull request #5395 from nuttycom/feature/wallet_orchard-unified_addrs

Add unified addresses to the zcashd wallet.
This commit is contained in:
str4d 2022-01-05 02:30:30 +00:00 committed by GitHub
commit 1d7a29ea56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1478 additions and 155 deletions

View File

@ -86,7 +86,7 @@ public:
CMainParams() {
keyConstants.strNetworkID = "main";
strCurrencyUnits = "ZEC";
bip44CoinType = 133; // As registered in https://github.com/satoshilabs/slips/blob/master/slip-0044.md
keyConstants.bip44CoinType = 133; // As registered in https://github.com/satoshilabs/slips/blob/master/slip-0044.md
consensus.fCoinbaseMustBeShielded = true;
consensus.nSubsidySlowStartInterval = 20000;
consensus.nPreBlossomSubsidyHalvingInterval = Consensus::PRE_BLOSSOM_HALVING_INTERVAL;
@ -370,7 +370,7 @@ public:
CTestNetParams() {
keyConstants.strNetworkID = "test";
strCurrencyUnits = "TAZ";
bip44CoinType = 1;
keyConstants.bip44CoinType = 1;
consensus.fCoinbaseMustBeShielded = true;
consensus.nSubsidySlowStartInterval = 20000;
consensus.nPreBlossomSubsidyHalvingInterval = Consensus::PRE_BLOSSOM_HALVING_INTERVAL;
@ -621,7 +621,7 @@ public:
CRegTestParams() {
keyConstants.strNetworkID = "regtest";
strCurrencyUnits = "REG";
bip44CoinType = 1;
keyConstants.bip44CoinType = 1;
consensus.fCoinbaseMustBeShielded = false;
consensus.nSubsidySlowStartInterval = 0;
consensus.nPreBlossomSubsidyHalvingInterval = Consensus::PRE_BLOSSOM_REGTEST_HALVING_INTERVAL;

View File

@ -32,17 +32,6 @@ struct CCheckpointData {
double fTransactionsPerDay;
};
class CBaseKeyConstants : public KeyConstants {
public:
std::string NetworkIDString() const { return strNetworkID; }
const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; }
const std::string& Bech32HRP(Bech32Type type) const { return bech32HRPs[type]; }
std::string strNetworkID;
std::vector<unsigned char> base58Prefixes[KeyConstants::MAX_BASE58_TYPES];
std::string bech32HRPs[KeyConstants::MAX_BECH32_TYPES];
};
/**
* CChainParams defines various tweakable parameters of a given instance of the
* Bitcoin system. There are three: the main network on which people trade goods
@ -73,14 +62,19 @@ public:
bool RequireStandard() const { return fRequireStandard; }
int64_t PruneAfterHeight() const { return nPruneAfterHeight; }
std::string CurrencyUnits() const { return strCurrencyUnits; }
uint32_t BIP44CoinType() const { return bip44CoinType; }
/** Make miner stop after a block is found. In RPC, don't return until nGenProcLimit blocks are generated */
bool MineBlocksOnDemand() const { return fMineBlocksOnDemand; }
/** In the future use NetworkIDString() for RPC fields */
bool TestnetToBeDeprecatedFieldRPC() const { return fTestnetToBeDeprecatedFieldRPC; }
/** Return the BIP70 network string (main, test or regtest) */
std::string NetworkIDString() const { return keyConstants.NetworkIDString(); }
const std::vector<CDNSSeedData>& DNSSeeds() const { return vSeeds; }
/** Return the BIP70 network string (main, test or regtest) */
std::string NetworkIDString() const {
return keyConstants.NetworkIDString();
}
/** Return the BIP44 coin type for addresses created by the zcashd embedded wallet. */
uint32_t BIP44CoinType() const {
return keyConstants.BIP44CoinType();
}
const std::vector<unsigned char>& Base58Prefix(Base58Type type) const {
return keyConstants.Base58Prefix(type);
}
@ -107,7 +101,6 @@ protected:
std::vector<CDNSSeedData> vSeeds;
CBaseKeyConstants keyConstants;
std::string strCurrencyUnits;
uint32_t bip44CoinType;
CBlock genesis;
std::vector<SeedSpec6> vFixedSeeds;
bool fMiningRequiresPeers = false;

View File

@ -14,6 +14,8 @@
#define MAKE_STRING(x) std::string((x), (x)+sizeof(x))
using namespace libzcash;
const uint32_t SLIP44_TESTNET_TYPE = 1;
TEST(KeystoreTests, StoreAndRetrieveMnemonicSeed) {
@ -531,4 +533,60 @@ TEST(KeystoreTests, StoreAndRetrieveSpendingKeyInEncryptedStore) {
ASSERT_EQ(1, addrs.count(addr));
ASSERT_EQ(1, addrs.count(addr2));
}
TEST(KeystoreTests, StoreAndRetrieveUFVK) {
SelectParams(CBaseChainParams::TESTNET);
CBasicKeyStore keyStore;
auto seed = MnemonicSeed::Random(SLIP44_TESTNET_TYPE);
auto usk = ZcashdUnifiedSpendingKey::ForAccount(seed, SLIP44_TESTNET_TYPE, 0);
EXPECT_TRUE(usk.has_value());
auto ufvk = usk.value().ToFullViewingKey();
auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), ufvk);
auto ufvkid = zufvk.GetKeyID();
EXPECT_FALSE(keyStore.GetUnifiedFullViewingKey(ufvkid).has_value());
EXPECT_TRUE(keyStore.AddUnifiedFullViewingKey(zufvk));
EXPECT_EQ(keyStore.GetUnifiedFullViewingKey(ufvkid).value(), zufvk);
auto addrPair = zufvk.FindAddress(diversifier_index_t(0), {ReceiverType::Sapling}).value();
EXPECT_TRUE(addrPair.first.GetSaplingReceiver().has_value());
auto saplingReceiver = addrPair.first.GetSaplingReceiver().value();
auto ufvkmeta = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
EXPECT_FALSE(ufvkmeta.has_value());
auto saplingIvk = zufvk.GetSaplingKey().value().fvk.in_viewing_key();
keyStore.AddSaplingIncomingViewingKey(saplingIvk, saplingReceiver);
ufvkmeta = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
EXPECT_TRUE(ufvkmeta.has_value());
EXPECT_EQ(ufvkmeta.value().first, ufvkid);
EXPECT_FALSE(ufvkmeta.value().second.has_value());
}
TEST(KeystoreTests, AddTransparentReceiverForUnifiedAddress) {
SelectParams(CBaseChainParams::TESTNET);
CBasicKeyStore keyStore;
auto seed = MnemonicSeed::Random(SLIP44_TESTNET_TYPE);
auto usk = ZcashdUnifiedSpendingKey::ForAccount(seed, SLIP44_TESTNET_TYPE, 0);
EXPECT_TRUE(usk.has_value());
auto ufvk = usk.value().ToFullViewingKey();
auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), ufvk);
auto ufvkid = zufvk.GetKeyID();
auto addrPair = zufvk.FindAddress(diversifier_index_t(0), {ReceiverType::P2PKH, ReceiverType::Sapling}).value();
EXPECT_TRUE(addrPair.first.GetP2PKHReceiver().has_value());
auto ufvkmeta = keyStore.GetUFVKMetadataForReceiver(addrPair.first.GetP2PKHReceiver().value());
EXPECT_FALSE(ufvkmeta.has_value());
keyStore.AddTransparentReceiverForUnifiedAddress(ufvkid, addrPair.second, addrPair.first);
ufvkmeta = keyStore.GetUFVKMetadataForReceiver(addrPair.first.GetP2PKHReceiver().value());
EXPECT_TRUE(ufvkmeta.has_value());
EXPECT_EQ(ufvkmeta.value().first, ufvkid);
}
#endif

View File

@ -6,6 +6,7 @@
#define ZCASH_KEY_CONSTANTS_H
#include <string>
#include <vector>
class KeyConstants
{
@ -35,8 +36,22 @@ public:
};
virtual std::string NetworkIDString() const =0;
virtual uint32_t BIP44CoinType() const =0;
virtual const std::vector<unsigned char>& Base58Prefix(Base58Type type) const =0;
virtual const std::string& Bech32HRP(Bech32Type type) const =0;
};
class CBaseKeyConstants : public KeyConstants {
public:
std::string strNetworkID;
uint32_t bip44CoinType;
std::vector<unsigned char> base58Prefixes[KeyConstants::MAX_BASE58_TYPES];
std::string bech32HRPs[KeyConstants::MAX_BECH32_TYPES];
std::string NetworkIDString() const { return strNetworkID; }
uint32_t BIP44CoinType() const { return bip44CoinType; }
const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; }
const std::string& Bech32HRP(Bech32Type type) const { return bech32HRPs[type]; }
};
#endif // ZCASH_KEY_CONSTANTS_H

View File

@ -287,3 +287,111 @@ bool CBasicKeyStore::GetSaplingExtendedSpendingKey(const libzcash::SaplingPaymen
GetSaplingFullViewingKey(ivk, extfvk) &&
GetSaplingSpendingKey(extfvk, extskOut);
}
//
// Unified Keys
//
bool CBasicKeyStore::AddUnifiedFullViewingKey(
const libzcash::ZcashdUnifiedFullViewingKey &ufvk)
{
LOCK(cs_KeyStore);
// Add the Sapling component of the UFVK to the wallet.
auto saplingKey = ufvk.GetSaplingKey();
if (saplingKey.has_value()) {
auto ivk = saplingKey.value().fvk.in_viewing_key();
mapSaplingKeyUnified.insert(std::make_pair(ivk, ufvk.GetKeyID()));
}
// We can't reasonably add the transparent component here, because
// of the way that transparent addresses are generated from the
// P2PKH part of the unified address. Instead, whenever a new
// unified address is generated, the keys associated with the
// transparent part of the address must be added to the keystore.
// Add the UFVK by key identifier.
mapUnifiedFullViewingKeys.insert({ufvk.GetKeyID(), ufvk});
return true;
}
bool CBasicKeyStore::AddTransparentReceiverForUnifiedAddress(
const libzcash::UFVKId& keyId,
const libzcash::diversifier_index_t& diversifierIndex,
const libzcash::UnifiedAddress& ua)
{
LOCK(cs_KeyStore);
// It's only necessary to add p2pkh and p2sh components of
// the UA; all other lookups of the associated UFVK will be
// made via the protocol-specific viewing key that is used
// to trial-decrypt a transaction.
auto addrEntry = std::make_pair(keyId, diversifierIndex);
auto p2pkhReceiver = ua.GetP2PKHReceiver();
if (p2pkhReceiver.has_value()) {
mapP2PKHUnified.insert(std::make_pair(p2pkhReceiver.value(), addrEntry));
}
auto p2shReceiver = ua.GetP2SHReceiver();
if (p2shReceiver.has_value()) {
mapP2SHUnified.insert(std::make_pair(p2shReceiver.value(), addrEntry));
}
return true;
}
std::optional<libzcash::ZcashdUnifiedFullViewingKey> CBasicKeyStore::GetUnifiedFullViewingKey(
const libzcash::UFVKId& keyId) const
{
auto mi = mapUnifiedFullViewingKeys.find(keyId);
if (mi != mapUnifiedFullViewingKeys.end()) {
return mi->second;
} else {
return std::nullopt;
}
}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
CBasicKeyStore::GetUFVKMetadataForReceiver(const libzcash::Receiver& receiver) const
{
return std::visit(FindUFVKId(*this), receiver);
}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
FindUFVKId::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
const auto saplingIvk = keystore.mapSaplingIncomingViewingKeys.find(saplingAddr);
if (saplingIvk != keystore.mapSaplingIncomingViewingKeys.end()) {
const auto ufvkId = keystore.mapSaplingKeyUnified.find(saplingIvk->second);
if (ufvkId != keystore.mapSaplingKeyUnified.end()) {
return std::make_pair(ufvkId->second, std::nullopt);
} else {
return std::nullopt;
}
} else {
return std::nullopt;
}
}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
FindUFVKId::operator()(const CScriptID& scriptId) const {
const auto metadata = keystore.mapP2SHUnified.find(scriptId);
if (metadata != keystore.mapP2SHUnified.end()) {
return metadata->second;
} else {
return std::nullopt;
}
}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
FindUFVKId::operator()(const CKeyID& keyId) const {
const auto metadata = keystore.mapP2PKHUnified.find(keyId);
if (metadata != keystore.mapP2PKHUnified.end()) {
return metadata->second;
} else {
return std::nullopt;
}
}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
FindUFVKId::operator()(const libzcash::UnknownReceiver& receiver) const {
return std::nullopt;
}

View File

@ -12,6 +12,7 @@
#include "script/standard.h"
#include "sync.h"
#include "zcash/address/mnemonic.h"
#include "zcash/address/unified.h"
#include "zcash/Address.hpp"
#include "zcash/NoteEncryption.hpp"
@ -100,6 +101,35 @@ public:
virtual bool GetSproutViewingKey(
const libzcash::SproutPaymentAddress &address,
libzcash::SproutViewingKey& vkOut) const =0;
//! Unified addresses and keys
virtual bool AddUnifiedFullViewingKey(
const libzcash::ZcashdUnifiedFullViewingKey &ufvk
) = 0;
/**
* Add the transparent component of the unified address, if any,
* to the keystore to make it possible to identify the unified
* full viewing key from which a transparent address was derived.
* It is not necessary for implementations to add shielded address
* components to the keystore because those will be automatically
* reconstructed when scanning the chain with a shielded incoming
* viewing key upon discovery of the address as having received
* funds.
*/
virtual bool AddTransparentReceiverForUnifiedAddress(
const libzcash::UFVKId& keyId,
const libzcash::diversifier_index_t& diversifierIndex,
const libzcash::UnifiedAddress& ua) = 0;
virtual std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUnifiedFullViewingKey(
const libzcash::UFVKId& keyId
) const = 0;
virtual std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
GetUFVKMetadataForReceiver(
const libzcash::Receiver& receiver
) const = 0;
};
typedef std::map<CKeyID, CKey> KeyMap;
@ -121,6 +151,8 @@ typedef std::map<
// Only maps from default addresses to ivk, may need to be reworked when adding diversified addresses.
typedef std::map<libzcash::SaplingPaymentAddress, libzcash::SaplingIncomingViewingKey> SaplingIncomingViewingKeyMap;
class FindUFVKId;
/** Basic key store, that keeps keys in an address->secret map */
class CBasicKeyStore : public CKeyStore
{
@ -142,6 +174,13 @@ protected:
SaplingFullViewingKeyMap mapSaplingFullViewingKeys;
SaplingIncomingViewingKeyMap mapSaplingIncomingViewingKeys;
// Unified key support
std::map<CKeyID, std::pair<libzcash::UFVKId, libzcash::diversifier_index_t>> mapP2PKHUnified;
std::map<CScriptID, std::pair<libzcash::UFVKId, libzcash::diversifier_index_t>> mapP2SHUnified;
std::map<libzcash::SaplingIncomingViewingKey, libzcash::UFVKId> mapSaplingKeyUnified;
std::map<libzcash::UFVKId, libzcash::ZcashdUnifiedFullViewingKey> mapUnifiedFullViewingKeys;
friend class FindUFVKId;
public:
bool SetMnemonicSeed(const MnemonicSeed& seed);
bool HaveMnemonicSeed() const;
@ -314,6 +353,22 @@ public:
virtual bool GetSproutViewingKey(
const libzcash::SproutPaymentAddress &address,
libzcash::SproutViewingKey& vkOut) const;
virtual bool AddUnifiedFullViewingKey(
const libzcash::ZcashdUnifiedFullViewingKey &ufvk);
virtual bool AddTransparentReceiverForUnifiedAddress(
const libzcash::UFVKId& keyId,
const libzcash::diversifier_index_t& diversifierIndex,
const libzcash::UnifiedAddress& ua);
virtual std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUnifiedFullViewingKey(
const libzcash::UFVKId& keyId) const;
virtual std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
GetUFVKMetadataForReceiver(
const libzcash::Receiver& receiver
) const;
};
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial;
@ -323,4 +378,21 @@ typedef std::map<libzcash::SproutPaymentAddress, std::vector<unsigned char> > Cr
//! Sapling
typedef std::map<libzcash::SaplingExtendedFullViewingKey, std::vector<unsigned char> > CryptedSaplingSpendingKeyMap;
class FindUFVKId {
private:
const CBasicKeyStore& keystore;
public:
FindUFVKId(const CBasicKeyStore& keystore): keystore(keystore) {}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const;
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
operator()(const CScriptID& scriptId) const;
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
operator()(const CKeyID& keyId) const;
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
operator()(const libzcash::UnknownReceiver& receiver) const;
};
#endif // BITCOIN_KEYSTORE_H

View File

@ -353,6 +353,22 @@ extern "C" {
unsigned char *addr_ret
);
/**
* Decrypts a Sapling diversifier using the specified diversifier key
* to obtain the diversifier index `j` at which the diversifier was
* derived.
*
* Arguments:
* - dk: [c_uchar; 32] the byte representation of a Sapling diversifier key
* - addr: [c_uchar; 11] the bytes of the diversifier
* - j_ret: [c_uchar; 11] array that will store the resulting diversifier index
*/
void librustzcash_sapling_diversifier_index(
const unsigned char *dk,
const unsigned char *d,
unsigned char *j_ret
);
/// Fills the provided buffer with random bytes. This is intended to
/// be a cryptographically secure RNG; it uses Rust's `OsRng`, which
/// is implemented in terms of the `getrandom` crate. The first call

View File

@ -47,6 +47,7 @@ use zcash_primitives::{
constants::{CRH_IVK_PERSONALIZATION, PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR},
merkle_tree::MerklePath,
sapling::{
self,
keys::FullViewingKey,
note_encryption::sapling_ka_agree,
redjubjub::{self, Signature},
@ -1130,6 +1131,20 @@ pub extern "C" fn librustzcash_zip32_find_sapling_address(
}
}
#[no_mangle]
pub extern "C" fn librustzcash_sapling_diversifier_index(
dk: *const [c_uchar; 32],
d: *const [c_uchar; 11],
j_ret: *mut [c_uchar; 11],
) {
let dk = zip32::DiversifierKey(unsafe { *dk });
let diversifier = sapling::Diversifier(unsafe { *d });
let j_ret = unsafe { &mut *j_ret };
let j = dk.diversifier_index(&diversifier);
j_ret.copy_from_slice(&j.0);
}
#[no_mangle]
pub extern "C" fn librustzcash_getrandom(buf: *mut u8, buf_len: usize) {
let buf = unsafe { slice::from_raw_parts_mut(buf, buf_len) };

24
src/util/match.h Normal file
View File

@ -0,0 +1,24 @@
// 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_UTIL_MATCH_H
#define ZCASH_UTIL_MATCH_H
// Helper for using `std::visit` with Rust-style match syntax.
//
// This can be used in place of defining an explicit visitor. `std::visit` requires an
// exhaustive match; to emulate Rust's catch-all binding, use an `(auto arg)` template
// operator().
//
// Care must be taken that implicit conversions are handled correctly. For instance, a
// `(double arg)` operator() *will also* bind to `int` and `long`, if there isn't an
// earlier satisfying match.
//
// This corresponds to visitor example #4 in the `std::visit` documentation:
// https://en.cppreference.com/w/cpp/utility/variant/visit
template<class... Ts> struct match : Ts... { using Ts::operator()...; };
// explicit deduction guide (not needed as of C++20)
template<class... Ts> match(Ts...) -> match<Ts...>;
#endif // ZCASH_UTIL_MATCH_H

View File

@ -20,6 +20,7 @@
#include <optional>
using ::testing::Return;
using namespace libzcash;
ACTION(ThrowLogicError) {
throw std::logic_error("Boom");
@ -181,12 +182,12 @@ TEST(WalletTests, SproutNoteDataSerialisation) {
EXPECT_EQ(noteData[jsoutpt].witnesses, noteData2[jsoutpt].witnesses);
}
TEST(WalletTests, FindUnspentSproutNotes) {
auto consensusParams = RegtestActivateSapling();
SelectParams(CBaseChainParams::TESTNET);
CWallet wallet(Params());
LOCK2(cs_main, wallet.cs_wallet);
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk);
@ -354,9 +355,6 @@ TEST(WalletTests, FindUnspentSproutNotes) {
mapBlockIndex.erase(blockHash);
mapBlockIndex.erase(blockHash2);
mapBlockIndex.erase(blockHash3);
// Revert to default
RegtestDeactivateSapling();
}
@ -377,8 +375,6 @@ TEST(WalletTests, SetSproutNoteAddrsInCWalletTx) {
}
TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
SelectParams(CBaseChainParams::REGTEST);
std::vector<libzcash::Zip212Enabled> zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212};
const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy};
void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy};
@ -468,6 +464,7 @@ TEST(WalletTests, SetInvalidSaplingNoteDataInCWalletTx) {
}
TEST(WalletTests, CheckSproutNoteCommitmentAgainstNotePlaintext) {
SelectParams(CBaseChainParams::REGTEST);
CWallet wallet(Params());
LOCK(wallet.cs_wallet);
@ -492,6 +489,7 @@ TEST(WalletTests, CheckSproutNoteCommitmentAgainstNotePlaintext) {
}
TEST(WalletTests, GetSproutNoteNullifier) {
SelectParams(CBaseChainParams::REGTEST);
CWallet wallet(Params());
LOCK(wallet.cs_wallet);
@ -527,7 +525,6 @@ TEST(WalletTests, GetSproutNoteNullifier) {
TEST(WalletTests, FindMySaplingNotes) {
auto consensusParams = RegtestActivateSapling();
TestWallet wallet(Params());
LOCK(wallet.cs_wallet);
@ -562,6 +559,7 @@ TEST(WalletTests, FindMySaplingNotes) {
}
TEST(WalletTests, FindMySproutNotes) {
SelectParams(CBaseChainParams::REGTEST);
CWallet wallet(Params());
LOCK(wallet.cs_wallet);
@ -588,8 +586,10 @@ TEST(WalletTests, FindMySproutNotes) {
}
TEST(WalletTests, FindMySproutNotesInEncryptedWallet) {
SelectParams(CBaseChainParams::REGTEST);
TestWallet wallet(Params());
LOCK(wallet.cs_wallet);
uint256 r {GetRandHash()};
CKeyingMaterial vMasterKey (r.begin(), r.end());
@ -619,6 +619,7 @@ TEST(WalletTests, FindMySproutNotesInEncryptedWallet) {
}
TEST(WalletTests, GetConflictedSproutNotes) {
SelectParams(CBaseChainParams::REGTEST);
CWallet wallet(Params());
LOCK(wallet.cs_wallet);
@ -652,8 +653,6 @@ TEST(WalletTests, GetConflictedSproutNotes) {
// Generate note A and spend to create note B, from which we spend to create two conflicting transactions
TEST(WalletTests, GetConflictedSaplingNotes) {
SelectParams(CBaseChainParams::REGTEST);
std::vector<libzcash::Zip212Enabled> zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212};
const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy};
void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy};
@ -779,6 +778,7 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
}
TEST(WalletTests, SproutNullifierIsSpent) {
SelectParams(CBaseChainParams::REGTEST);
CWallet wallet(Params());
LOCK2(cs_main, wallet.cs_wallet);
@ -821,7 +821,6 @@ TEST(WalletTests, SproutNullifierIsSpent) {
TEST(WalletTests, SaplingNullifierIsSpent) {
auto consensusParams = RegtestActivateSapling();
TestWallet wallet(Params());
LOCK2(cs_main, wallet.cs_wallet);
@ -878,6 +877,7 @@ TEST(WalletTests, SaplingNullifierIsSpent) {
}
TEST(WalletTests, NavigateFromSproutNullifierToNote) {
SelectParams(CBaseChainParams::REGTEST);
CWallet wallet(Params());
LOCK(wallet.cs_wallet);
@ -906,7 +906,6 @@ TEST(WalletTests, NavigateFromSproutNullifierToNote) {
TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
auto consensusParams = RegtestActivateSapling();
TestWallet wallet(Params());
LOCK2(cs_main, wallet.cs_wallet);
@ -998,6 +997,7 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
}
TEST(WalletTests, SpentSproutNoteIsFromMe) {
SelectParams(CBaseChainParams::REGTEST);
CWallet wallet(Params());
LOCK(wallet.cs_wallet);
@ -1028,8 +1028,6 @@ TEST(WalletTests, SpentSproutNoteIsFromMe) {
// Create note A, spend A to create note B, spend and verify note B is from me.
TEST(WalletTests, SpentSaplingNoteIsFromMe) {
SelectParams(CBaseChainParams::REGTEST);
std::vector<libzcash::Zip212Enabled> zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212};
const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy};
void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy};
@ -1238,8 +1236,10 @@ TEST(WalletTests, CachedWitnessesEmptyChain) {
}
TEST(WalletTests, CachedWitnessesChainTip) {
SelectParams(CBaseChainParams::REGTEST);
TestWallet wallet(Params());
LOCK(wallet.cs_wallet);
std::pair<uint256, uint256> anchors1;
CBlock block1;
SproutMerkleTree sproutTree;
@ -1341,8 +1341,10 @@ TEST(WalletTests, CachedWitnessesChainTip) {
}
TEST(WalletTests, CachedWitnessesDecrementFirst) {
SelectParams(CBaseChainParams::REGTEST);
TestWallet wallet(Params());
LOCK(wallet.cs_wallet);
SproutMerkleTree sproutTree;
SaplingMerkleTree saplingTree;
@ -1422,8 +1424,10 @@ TEST(WalletTests, CachedWitnessesDecrementFirst) {
}
TEST(WalletTests, CachedWitnessesCleanIndex) {
SelectParams(CBaseChainParams::REGTEST);
TestWallet wallet(Params());
LOCK(wallet.cs_wallet);
std::vector<CBlock> blocks;
std::vector<CBlockIndex> indices;
std::vector<JSOutPoint> sproutNotes;
@ -1510,6 +1514,7 @@ TEST(WalletTests, CachedWitnessesCleanIndex) {
}
TEST(WalletTests, ClearNoteWitnessCache) {
SelectParams(CBaseChainParams::REGTEST);
TestWallet wallet(Params());
LOCK(wallet.cs_wallet);
@ -1577,8 +1582,10 @@ TEST(WalletTests, ClearNoteWitnessCache) {
}
TEST(WalletTests, WriteWitnessCache) {
SelectParams(CBaseChainParams::REGTEST);
TestWallet wallet(Params());
LOCK(wallet.cs_wallet);
MockWalletDB walletdb;
CBlockLocator loc;
@ -1664,9 +1671,9 @@ TEST(WalletTests, WriteWitnessCache) {
TEST(WalletTests, SetBestChainIgnoresTxsWithoutShieldedData) {
SelectParams(CBaseChainParams::REGTEST);
TestWallet wallet(Params());
LOCK(wallet.cs_wallet);
MockWalletDB walletdb;
CBlockLocator loc;
@ -1746,8 +1753,10 @@ TEST(WalletTests, SetBestChainIgnoresTxsWithoutShieldedData) {
}
TEST(WalletTests, UpdateSproutNullifierNoteMap) {
SelectParams(CBaseChainParams::REGTEST);
TestWallet wallet(Params());
LOCK(wallet.cs_wallet);
uint256 r {GetRandHash()};
CKeyingMaterial vMasterKey (r.begin(), r.end());
@ -1782,6 +1791,7 @@ TEST(WalletTests, UpdateSproutNullifierNoteMap) {
}
TEST(WalletTests, UpdatedSproutNoteData) {
SelectParams(CBaseChainParams::REGTEST);
TestWallet wallet(Params());
LOCK(wallet.cs_wallet);
@ -1831,7 +1841,6 @@ TEST(WalletTests, UpdatedSproutNoteData) {
TEST(WalletTests, UpdatedSaplingNoteData) {
auto consensusParams = RegtestActivateSapling();
TestWallet wallet(Params());
LOCK2(cs_main, wallet.cs_wallet);
@ -1941,6 +1950,7 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
}
TEST(WalletTests, MarkAffectedSproutTransactionsDirty) {
SelectParams(CBaseChainParams::REGTEST);
TestWallet wallet(Params());
LOCK(wallet.cs_wallet);
@ -1974,7 +1984,6 @@ TEST(WalletTests, MarkAffectedSproutTransactionsDirty) {
TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
auto consensusParams = RegtestActivateSapling();
TestWallet wallet(Params());
LOCK2(cs_main, wallet.cs_wallet);
@ -2084,6 +2093,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
}
TEST(WalletTests, SproutNoteLocking) {
SelectParams(CBaseChainParams::REGTEST);
TestWallet wallet(Params());
LOCK(wallet.cs_wallet);
@ -2118,8 +2128,10 @@ TEST(WalletTests, SproutNoteLocking) {
}
TEST(WalletTests, SaplingNoteLocking) {
SelectParams(CBaseChainParams::REGTEST);
TestWallet wallet(Params());
LOCK(wallet.cs_wallet);
SaplingOutPoint sop1 {uint256(), 1};
SaplingOutPoint sop2 {uint256(), 2};
@ -2149,3 +2161,74 @@ TEST(WalletTests, SaplingNoteLocking) {
EXPECT_FALSE(wallet.IsLockedNote(sop1));
EXPECT_FALSE(wallet.IsLockedNote(sop2));
}
TEST(WalletTests, GenerateUnifiedAddress) {
(void) RegtestActivateSapling();
TestWallet wallet(Params());
UAGenerationResult uaResult = wallet.GenerateUnifiedAddress(0, {ReceiverType::P2PKH, ReceiverType::Sapling});
// If the wallet does not have a mnemonic seed available, it is
// treated as if the wallet is encrypted.
EXPECT_FALSE(wallet.IsCrypted());
EXPECT_FALSE(wallet.GetMnemonicSeed().has_value());
UAGenerationResult expected = AddressGenerationError::WalletEncrypted;
EXPECT_EQ(uaResult, expected);
wallet.GenerateNewSeed();
EXPECT_FALSE(wallet.IsCrypted());
EXPECT_TRUE(wallet.GetMnemonicSeed().has_value());
// If the user has not generated a unified spending key,
// we cannot create an address for the account corresponding
// to that spending key.
uaResult = wallet.GenerateUnifiedAddress(0, {ReceiverType::P2PKH, ReceiverType::Sapling});
expected = AddressGenerationError::NoSuchAccount;
EXPECT_EQ(uaResult, expected);
// Create an account, then generate an address for that account.
auto skpair = wallet.GenerateNewUnifiedSpendingKey();
uaResult = wallet.GenerateUnifiedAddress(skpair.second, {ReceiverType::P2PKH, ReceiverType::Sapling});
auto ua = std::get_if<std::pair<libzcash::UnifiedAddress, libzcash::diversifier_index_t>>(&uaResult);
EXPECT_NE(ua, nullptr);
EXPECT_TRUE(ua->first.GetSaplingReceiver().has_value());
auto ufvk = skpair.first.ToFullViewingKey();
EXPECT_EQ(
ua->first.GetSaplingReceiver(),
ufvk.GetSaplingKey().value().Address(ua->second));
auto u4r = wallet.GetUnifiedForReceiver(ua->first.GetSaplingReceiver().value());
EXPECT_EQ(u4r, ua->first);
// Explicitly trigger the invalid transparent child index failure
uaResult = wallet.GenerateUnifiedAddress(
0,
{ReceiverType::P2PKH, ReceiverType::Sapling},
MAX_TRANSPARENT_CHILD_IDX.succ().value());
expected = AddressGenerationError::InvalidTransparentChildIndex;
EXPECT_EQ(uaResult, expected);
// Attempt to generate a UA at the maximum transparent child index. This might fail
// due to this index being invalid for Sapling; if so, it will return an error that
// the diversifier index is out of range. If it succeeds, we'll attempt to generate
// the next available diversifier, and this should always fail
uaResult = wallet.GenerateUnifiedAddress(
0,
{ReceiverType::P2PKH, ReceiverType::Sapling},
MAX_TRANSPARENT_CHILD_IDX);
ua = std::get_if<std::pair<libzcash::UnifiedAddress, libzcash::diversifier_index_t>>(&uaResult);
if (ua == nullptr) {
expected = AddressGenerationError::NoAddressForDiversifier;
EXPECT_EQ(uaResult, expected);
} else {
// the previous generation attempt succeeded, so this one should definitely fail.
uaResult = wallet.GenerateUnifiedAddress(0, {ReceiverType::P2PKH, ReceiverType::Sapling});
expected = AddressGenerationError::DiversifierSpaceExhausted;
EXPECT_EQ(uaResult, expected);
}
// Revert to default
RegtestDeactivateSapling();
}

View File

@ -870,7 +870,7 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid viewing key");
}
auto addrInfo = std::visit(libzcash::AddressInfoFromViewingKey{}, viewingkey);
auto addrInfo = std::visit(libzcash::AddressInfoFromViewingKey(Params()), viewingkey);
UniValue result(UniValue::VOBJ);
const string strAddress = keyIO.EncodePaymentAddress(addrInfo.second);
result.pushKV("type", addrInfo.first);

View File

@ -172,7 +172,6 @@ std::pair<SaplingExtendedSpendingKey, bool> CWallet::GenerateLegacySaplingZKey(u
} else {
return std::make_pair(xsk.first, false);
}
}
// Add spending key to keystore
@ -281,27 +280,14 @@ CPubKey CWallet::GenerateNewKey()
BIP44CoinType(),
ZCASH_LEGACY_ACCOUNT).value();
std::optional<std::pair<CExtKey, HDKeyPath>> extKey = std::nullopt;
std::optional<std::pair<CKey, HDKeyPath>> extKey = std::nullopt;
do {
extKey = accountChains.DeriveExternal(hdChain.GetLegacyTKeyCounter());
hdChain.IncrementLegacyTKeyCounter();
// if we did not successfully generate a key, try again.
} while (!extKey.has_value());
CKey secret = extKey.value().first.key;
CPubKey pubkey = secret.GetPubKey();
assert(secret.VerifyPubKey(pubkey));
// Create new metadata
CKeyMetadata keyMeta(GetTime());
keyMeta.hdKeypath = extKey.value().second;
keyMeta.seedFp = seed.Fingerprint();
mapKeyMetadata[pubkey.GetID()] = keyMeta;
if (nTimeFirstKey == 0 || keyMeta.nCreateTime < nTimeFirstKey)
nTimeFirstKey = keyMeta.nCreateTime;
if (!AddKeyPubKey(secret, pubkey))
throw std::runtime_error("CWallet::GenerateNewKey(): AddKey failed");
auto pubkey = AddTransparentSecretKey(seed.Fingerprint(), extKey.value());
// Update the persisted chain information
if (fFileBacked && !CWalletDB(strWalletFile).WriteMnemonicHDChain(hdChain)) {
@ -311,7 +297,31 @@ CPubKey CWallet::GenerateNewKey()
return pubkey;
}
bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
CPubKey CWallet::AddTransparentSecretKey(
const uint256& seedFingerprint,
const std::pair<CKey, HDKeyPath>& extSecret)
{
CKey secret = extSecret.first;
CPubKey pubkey = secret.GetPubKey();
assert(secret.VerifyPubKey(pubkey));
// Create new metadata
CKeyMetadata keyMeta(GetTime());
keyMeta.hdKeypath = extSecret.second;
keyMeta.seedFp = seedFingerprint;
mapKeyMetadata[pubkey.GetID()] = keyMeta;
if (nTimeFirstKey == 0 || keyMeta.nCreateTime < nTimeFirstKey)
nTimeFirstKey = keyMeta.nCreateTime;
if (!AddKeyPubKey(secret, pubkey))
throw std::runtime_error("CWallet::GenerateNewKey(): AddKeyPubKey failed");
return pubkey;
}
bool CWallet::AddKeyPubKey(
const CKey& secret,
const CPubKey &pubkey)
{
AssertLockHeld(cs_wallet); // mapKeyMetadata
if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey))
@ -328,11 +338,13 @@ bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
if (!fFileBacked)
return true;
if (!IsCrypted()) {
return CWalletDB(strWalletFile).WriteKey(pubkey,
secret.GetPrivKey(),
mapKeyMetadata[pubkey.GetID()]);
}
return true;
}
@ -407,16 +419,18 @@ bool CWallet::AddCryptedSaplingSpendingKey(const libzcash::SaplingExtendedFullVi
return false;
}
ZcashdUnifiedSpendingKey CWallet::GenerateNewUnifiedSpendingKey() {
std::pair<ZcashdUnifiedSpendingKey, libzcash::AccountId> CWallet::GenerateNewUnifiedSpendingKey() {
AssertLockHeld(cs_wallet);
if (!mnemonicHDChain.has_value()) {
throw std::runtime_error(
"CWallet::GenerateNewUnifiedSpendingKey(): Wallet is missing mnemonic seed metadata.");
}
CHDChain& hdChain = mnemonicHDChain.value();
while (true) {
auto usk = GenerateUnifiedSpendingKeyForAccount(hdChain.GetAccountCounter());
auto accountId = hdChain.GetAccountCounter();
auto usk = GenerateUnifiedSpendingKeyForAccount(accountId);
hdChain.IncrementAccountCounter();
if (usk.has_value()) {
@ -426,12 +440,15 @@ ZcashdUnifiedSpendingKey CWallet::GenerateNewUnifiedSpendingKey() {
"CWallet::GenerateNewUnifiedSpendingKey(): Writing HD chain model failed");
}
return usk.value();
return std::make_pair(usk.value(), accountId);
}
}
}
std::optional<libzcash::ZcashdUnifiedSpendingKey> CWallet::GenerateUnifiedSpendingKeyForAccount(libzcash::AccountId accountId) {
std::optional<libzcash::ZcashdUnifiedSpendingKey>
CWallet::GenerateUnifiedSpendingKeyForAccount(libzcash::AccountId accountId) {
AssertLockHeld(cs_wallet); // mapUnifiedAccountKeys
auto seed = GetMnemonicSeed();
if (!seed.has_value()) {
throw std::runtime_error(std::string(__func__) + ": Wallet has no mnemonic HD seed.");
@ -439,14 +456,278 @@ std::optional<libzcash::ZcashdUnifiedSpendingKey> CWallet::GenerateUnifiedSpendi
auto usk = ZcashdUnifiedSpendingKey::ForAccount(seed.value(), BIP44CoinType(), accountId);
if (usk.has_value()) {
// TODO: Save the unified full viewing key & metadata to the wallet
auto ufvk = usk.value().ToFullViewingKey();
auto ufvkid = ufvk.GetKeyID(Params());
return usk.value().first;
ZcashdUnifiedAccountMetadata skmeta(seed.value().Fingerprint(), BIP44CoinType(), accountId, ufvkid);
// We don't store the spending key directly; instead, we store each of
// the spending key's components, in order to not violate invariants
// with respect to the encryption of the wallet. We store each
// component in the appropriate wallet subsystem, and store the
// metadata that can be used to re-derive the spending key along with
// the fingerprint of the associated full viewing key.
auto metaKey = std::make_pair(skmeta.GetSeedFingerprint(), skmeta.GetAccountId());
mapUnifiedAccountKeys.insert({metaKey, skmeta.GetKeyID()});
// Add Transparent component to the wallet
AddTransparentSecretKey(
skmeta.GetSeedFingerprint(),
std::make_pair(
usk.value().GetTransparentKey().key,
libzcash::Bip44TransparentAccountKeyPath(BIP44CoinType(), accountId)
)
);
// Add Sapling component to the wallet
auto saplingEsk = usk.value().GetSaplingExtendedSpendingKey();
auto saplingKeyPath = libzcash::Zip32AccountKeyPath(BIP44CoinType(), accountId);
auto addSpendingKey = AddSpendingKeyToWallet(
this, Params().GetConsensus(), GetTime(),
saplingKeyPath, skmeta.GetSeedFingerprint().GetHex(), true);
if (addSpendingKey(saplingEsk) == KeyNotAdded) {
// If adding the Sapling key to the wallet failed, abort the process.
throw std::runtime_error("CWalletDB::GenerateUnifiedSpendingKeyForAccount(): Unable to add Sapling key component to the wallet.");
}
auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), ufvk);
if (!CCryptoKeyStore::AddUnifiedFullViewingKey(zufvk)) {
throw std::runtime_error("CWalletDB::GenerateUnifiedSpendingKeyForAccount(): Failed to add UFVK to the keystore.");
}
if (fFileBacked) {
auto walletdb = CWalletDB(strWalletFile);
if (!( walletdb.WriteUnifiedFullViewingKey(ufvk) &&
walletdb.WriteUnifiedAccountMetadata(skmeta)
)) {
throw std::runtime_error("CWalletDB::GenerateUnifiedSpendingKeyForAccount(): walletdb write failed.");
}
}
return usk;
} else {
return std::nullopt;
}
}
bool CWallet::AddUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &ufvk)
{
AssertLockHeld(cs_wallet);
auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), ufvk);
auto keyId = ufvk.GetKeyID(Params());
if (!CCryptoKeyStore::AddUnifiedFullViewingKey(zufvk)) {
return false;
}
if (!fFileBacked) {
return true;
}
return CWalletDB(strWalletFile).WriteUnifiedFullViewingKey(ufvk);
}
std::optional<ZcashdUnifiedFullViewingKey> CWallet::GetUnifiedFullViewingKeyByAccount(libzcash::AccountId accountId) {
if (!mnemonicHDChain.has_value()) {
throw std::runtime_error(
"CWallet::GetUnifiedFullViewingKeyByAccount(): Wallet is missing mnemonic seed metadata.");
}
auto seedfp = mnemonicHDChain.value().GetSeedFingerprint();
auto entry = mapUnifiedAccountKeys.find(std::make_pair(seedfp, accountId));
if (entry != mapUnifiedAccountKeys.end()) {
return CCryptoKeyStore::GetUnifiedFullViewingKey(entry->second);
} else {
return std::nullopt;
}
}
UAGenerationResult CWallet::GenerateUnifiedAddress(
const libzcash::AccountId& accountId,
const std::set<libzcash::ReceiverType>& receiverTypes,
std::optional<libzcash::diversifier_index_t> j)
{
bool searchDiversifiers = !j.has_value();
if (!libzcash::HasShielded(receiverTypes)) {
return AddressGenerationError::InvalidReceiverTypes;
}
// The wallet must be unlocked in order to generate new transparent UA
// receivers, because we need to be able to add the secret key for the
// external child address at the diversifier index to the wallet's
// transparent backend in order to be able to detect transactions as
// ours rather than considering them as watch-only.
bool hasTransparent = receiverTypes.find(ReceiverType::P2PKH) != receiverTypes.end();
if (hasTransparent) {
// A preemptive check to ensure that the user has not specified an
// invalid transparent child index. If we search from a valid transparent
// child index into invalid child index space, later checks will return
// this error as `AddressGenerationError::DiversifierSpaceExhausted`
if (j.has_value() && !j.value().ToTransparentChildIndex().has_value()) {
return AddressGenerationError::InvalidTransparentChildIndex;
}
if (IsCrypted() || !GetMnemonicSeed().has_value()) {
return AddressGenerationError::WalletEncrypted;
}
}
auto ufvk = GetUnifiedFullViewingKeyByAccount(accountId);
if (ufvk.has_value()) {
auto ufvkid = ufvk.value().GetKeyID();
// Check whether an address has already been generated for any provided
// diversifier index. Return that address, or set the diversifier index
// at which we'll begin searching for the next available diversified
// address.
auto metadata = mapUfvkAddressMetadata.find(ufvkid);
if (metadata != mapUfvkAddressMetadata.end()) {
if (j.has_value()) {
auto receivers = metadata->second.GetReceivers(j.value());
if (receivers.has_value()) {
// Ensure that the set of receiver types being requested is
// the same as the set of receiver types that was previously
// generated. If they match, simply return that address.
if (receivers.value() == receiverTypes) {
return std::make_pair(ufvk.value().Address(j.value(), receiverTypes).value(), j.value());
} else {
return AddressGenerationError::ExistingAddressMismatch;
}
}
} else {
// Set the diversifier index to one greater than the last used
// diversifier
j = metadata->second.GetNextDiversifierIndex();
if (!j.has_value()) {
return AddressGenerationError::DiversifierSpaceExhausted;
}
}
} else {
// Begin searching from the zero diversifier index if we haven't
// yet generated an address from the specified UFVK and no
// diversifier index has been specified.
if (!j.has_value()) {
j = libzcash::diversifier_index_t(0);
}
}
// Find a working diversifier and construct the associated address.
// At this point, we know that `j` will contain a value. Optimistically
// attempt to find an address at that diversifier; we'll search if we
// don't find one.
auto diversifierIndex = j.value();
auto address = ufvk.value().Address(diversifierIndex, receiverTypes);
if (!address.has_value() && searchDiversifiers) {
auto found = ufvk.value().FindAddress(j.value(), receiverTypes);
if (found.has_value()) {
address = found.value().first;
diversifierIndex = found.value().second;
} else {
return AddressGenerationError::DiversifierSpaceExhausted;
}
}
// Now that we're done searching, check that we actually got an address
if (!address.has_value()) {
return AddressGenerationError::NoAddressForDiversifier;
}
assert(mapUfvkAddressMetadata[ufvkid].SetReceivers(diversifierIndex, receiverTypes));
// Writing this data is handled by `CWalletDB::WriteUnifiedAddressMetadata` below.
assert(CCryptoKeyStore::AddTransparentReceiverForUnifiedAddress(ufvkid, diversifierIndex, address.value()));
// Save the metadata for the generated address so that we can re-derive
// it in the future.
ZcashdUnifiedAddressMetadata addrmeta(ufvkid, diversifierIndex, receiverTypes);
if (fFileBacked && !CWalletDB(strWalletFile).WriteUnifiedAddressMetadata(addrmeta)) {
throw std::runtime_error(
"CWallet::AddUnifiedAddress(): Writing unified address metadata failed");
}
if (hasTransparent) {
// Regenerate the secret key for the transparent address and add it to
// the wallet.
auto seed = GetMnemonicSeed().value();
auto b44 = libzcash::Bip44AccountChains::ForAccount(seed, BIP44CoinType(), accountId).value();
auto key = b44.DeriveExternal(diversifierIndex.ToTransparentChildIndex().value()).value();
AddTransparentSecretKey(seed.Fingerprint(), key);
}
return std::make_pair(address.value(), diversifierIndex);
} else {
return AddressGenerationError::NoSuchAccount;
}
}
// NOTE: There is an important relationship between `LoadUnifiedFullViewingKey`
// and `LoadUnifiedAddressMetadata`. In all cases, by the end of the
// wallet-loading process, `mapUfvkAddressMetadata` needs to be fully populated
// for each unified full viewing key. However, we cannot guarantee in what
// order UFVKs and unified address and account metadata are loaded from disk.
// Therefore:
// * When we load a unified full viewing key from disk, we repopulate
// the cache of generated addresses by regenerating the address for each
// (diversifierIndex, {receiverType...}) pair in `mapUfvkAddressMetadata`
// that we have loaded so far.
// * When we load a unified address metadata record from disk:
// - if we have not yet loaded the unified full viewing key, simply add
// an entry `mapUfvkAddressMetadata` so that we can restore the address
// once the UFVK has been loaded.
// - if we have already loaded the unified full viewing key from disk,
// regenerate the address from its metadata and add it to the keystore
//
// While this is somewhat complicated, it has the benefit of making each
// piece of unified full viewing key and address metadata write-once in
// the wallet database; we never need to update ufvk or address metadata
// once written.
bool CWallet::LoadUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &key)
{
auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), key);
auto metadata = mapUfvkAddressMetadata.find(zufvk.GetKeyID());
if (metadata != mapUfvkAddressMetadata.end()) {
// restore unified addresses that have been previously generated to the
// keystore
for (const auto &[j, receiverTypes] : metadata->second.GetKnownReceiverSetsByDiversifierIndex()) {
auto addr = zufvk.Address(j, receiverTypes).value();
if (!CCryptoKeyStore::AddTransparentReceiverForUnifiedAddress(zufvk.GetKeyID(), j, addr)) {
return false;
}
}
}
return CCryptoKeyStore::AddUnifiedFullViewingKey(zufvk);
}
bool CWallet::LoadUnifiedAccountMetadata(const ZcashdUnifiedAccountMetadata &skmeta)
{
AssertLockHeld(cs_wallet); // mapUnifiedAccountKeys
auto metaKey = std::make_pair(skmeta.GetSeedFingerprint(), skmeta.GetAccountId());
mapUnifiedAccountKeys.insert({metaKey, skmeta.GetKeyID()});
return mapUfvkAddressMetadata[skmeta.GetKeyID()].SetAccountId(skmeta.GetAccountId());
}
bool CWallet::LoadUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata &addrmeta)
{
AssertLockHeld(cs_wallet);
if (!mapUfvkAddressMetadata[addrmeta.GetKeyID()].SetReceivers(
addrmeta.GetDiversifierIndex(),
addrmeta.GetReceiverTypes())) {
return false;
}
auto ufvk = GetUnifiedFullViewingKey(addrmeta.GetKeyID());
if (ufvk.has_value()) {
// Regenerate the unified address and add it to the keystore.
auto j = addrmeta.GetDiversifierIndex();
auto addr = ufvk.value().Address(j, addrmeta.GetReceiverTypes()).value();
return CCryptoKeyStore::AddTransparentReceiverForUnifiedAddress(addrmeta.GetKeyID(), j, addr);
}
return true;
}
void CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta)
{
AssertLockHeld(cs_wallet); // mapKeyMetadata
@ -5299,6 +5580,10 @@ void CWallet::GetFilteredNotes(
}
std::optional<UnifiedAddress> CWallet::GetUnifiedForReceiver(const Receiver& receiver) {
return std::visit(LookupUnifiedAddress(*this), receiver);
}
//
// Shielded key and address generalizations
//
@ -5542,7 +5827,7 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedS
if (m_wallet->HaveSaplingSpendingKey(extfvk)) {
return KeyAlreadyExists;
} else {
if (!m_wallet-> AddSaplingZKey(sk)) {
if (!m_wallet->AddSaplingZKey(sk)) {
return KeyNotAdded;
}
@ -5556,7 +5841,7 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedS
if (hdKeypath.has_value()) {
m_wallet->mapSaplingZKeyMetadata[ivk].hdKeypath = hdKeypath.value();
}
if (seedFpStr) {
if (seedFpStr.has_value()) {
uint256 seedFp;
seedFp.SetHex(seedFpStr.value());
m_wallet->mapSaplingZKeyMetadata[ivk].seedFp = seedFp;
@ -5569,3 +5854,69 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedS
KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::InvalidEncoding& no) const {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key");
}
std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(saplingAddr);
if (ufvkPair.has_value()) {
auto ufvkid = ufvkPair.value().first;
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
if (!(ufvk.has_value() && ufvk.value().GetSaplingKey().has_value())) {
throw std::runtime_error("CWallet::LookupUnifiedAddress(): UFVK has no Sapling key part.");
}
diversifier_index_t j;
// If the wallet is missing metadata at this UFVK id, it is probably
// corrupt and the node should shut down.
const auto& metadata = wallet.mapUfvkAddressMetadata.at(ufvkid);
librustzcash_sapling_diversifier_index(
ufvk.value().GetSaplingKey().value().dk.begin(),
saplingAddr.d.begin(),
j.begin());
auto receivers = metadata.GetReceivers(j);
if (receivers.has_value()) {
return ufvk.value().Address(j, receivers.value());
} else {
// If we don't know the receiver types at which the address was originally
// generated, we can't reconstruct the address.
return std::nullopt;
}
} else {
return std::nullopt;
}
}
std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const CScriptID& scriptId) const {
return std::nullopt;
}
std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const CKeyID& keyId) const {
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(keyId);
if (ufvkPair.has_value()) {
auto ufvkid = ufvkPair.value().first;
// transparent address UFVK metadata is always accompanied by the child
// index at which the address was produced
assert(ufvkPair.value().second.has_value());
diversifier_index_t j = ufvkPair.value().second.value();
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
if (!(ufvk.has_value() && ufvk.value().GetTransparentKey().has_value())) {
throw std::runtime_error("CWallet::LookupUnifiedAddress(): UFVK has no P2PKH key part.");
}
// If the wallet is missing metadata at this UFVK id, it is probably
// corrupt and the node should shut down.
const auto& metadata = wallet.mapUfvkAddressMetadata.at(ufvkid);
// Find the set of receivers at the diversifier index. If we do not
// know the receiver types for the address produced at this
// diversifier, we cannot reconstruct the address.
auto receivers = metadata.GetReceivers(j);
if (receivers.has_value()) {
return ufvk.value().Address(j, receivers.value());
} else {
return std::nullopt;
}
} else {
return std::nullopt;
}
}
std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const libzcash::UnknownReceiver& receiver) const {
return std::nullopt;
}

View File

@ -402,6 +402,20 @@ public:
bool AcceptToMemoryPool(bool fLimitFree=true, bool fRejectAbsurdFee=true);
};
enum class AddressGenerationError {
NoSuchAccount,
InvalidReceiverTypes,
ExistingAddressMismatch,
NoAddressForDiversifier,
DiversifierSpaceExhausted,
WalletEncrypted,
InvalidTransparentChildIndex
};
typedef std::variant<
std::pair<libzcash::UnifiedAddress, libzcash::diversifier_index_t>,
AddressGenerationError> UAGenerationResult;
/**
* A transaction with a bunch of additional info that only the owner cares about.
* It includes any unrecorded transactions needed to link it back to the block chain.
@ -625,9 +639,6 @@ public:
std::set<uint256> GetConflicts() const;
};
class COutput
{
public:
@ -644,9 +655,6 @@ public:
std::string ToString() const;
};
/** Private key that includes an expiration date in case it never gets used. */
class CWalletKey
{
@ -674,6 +682,80 @@ public:
}
};
class UFVKAddressMetadata
{
private:
// The account ID may be absent for imported UFVKs, and also may temporarily
// be absent when this data structure is in a partially-reconstructed state
// during the wallet load process.
std::optional<libzcash::AccountId> accountId;
std::map<libzcash::diversifier_index_t, std::set<libzcash::ReceiverType>> addressReceivers;
public:
UFVKAddressMetadata() {}
UFVKAddressMetadata(libzcash::AccountId accountId): accountId(accountId) {}
/**
* Return all currently known diversifier indices for which addresses
* have been generated, each accompanied by the associated set of receiver
* types that were used when generating that address.
*/
const std::map<libzcash::diversifier_index_t, std::set<libzcash::ReceiverType>>& GetKnownReceiverSetsByDiversifierIndex() const {
return addressReceivers;
}
std::optional<std::set<libzcash::ReceiverType>> GetReceivers(
const libzcash::diversifier_index_t& j) const {
auto receivers = addressReceivers.find(j);
if (receivers != addressReceivers.end()) {
return receivers->second;
} else {
return std::nullopt;
}
}
/**
* Add the specified set of receivers at the provided diversifier index.
*
* Returns `true` if this is a new entry or if the operation would not
* alter the existing set of receiver types at this index, `false`
* otherwise.
*/
bool SetReceivers(
const libzcash::diversifier_index_t& j,
const std::set<libzcash::ReceiverType>& receivers) {
const auto [it, success] = addressReceivers.insert(std::make_pair(j, receivers));
if (success) {
return true;
} else {
return it->second == receivers;
}
}
bool SetAccountId(libzcash::AccountId accountIdIn) {
if (accountId.has_value()) {
return (accountIdIn == accountId.value());
} else {
accountId = accountIdIn;
return true;
}
}
/**
* Search the for the maximum diversifier that has already been used to
* generate a new address, and return the next diversifier. Returns the
* zero diversifier index if no addresses have yet been generated,
* and returns std::nullopt if the increment operation would cause an
* overflow.
*/
std::optional<libzcash::diversifier_index_t> GetNextDiversifierIndex() {
if (addressReceivers.empty()) {
return libzcash::diversifier_index_t(0);
} else {
return addressReceivers.rbegin()->first.succ();
}
}
};
/**
* A CWallet is an extension of a keystore, which also maintains a set of transactions and balances,
* and provides the ability to create new transactions.
@ -702,8 +784,13 @@ private:
int nSetChainUpdates;
bool fBroadcastTransactions;
/**
* A map from a protocol-specific transaction output identifier to
* a txid.
*/
template <class T>
using TxSpendMap = std::multimap<T, uint256>;
/**
* Used to keep track of spent outpoints, and
* detect and report conflicts (double-spends or
@ -711,6 +798,7 @@ private:
*/
typedef TxSpendMap<COutPoint> TxSpends;
TxSpends mapTxSpends;
/**
* Used to keep track of spent Notes, and
* detect and report conflicts (double-spends).
@ -803,6 +891,11 @@ private:
void SyncMetaData(std::pair<typename TxSpendMap<T>::iterator, typename TxSpendMap<T>::iterator>);
void ChainTipAdded(const CBlockIndex *pindex, const CBlock *pblock, SproutMerkleTree sproutTree, SaplingMerkleTree saplingTree);
/* Add a transparent secret key to the wallet. Internal use only. */
CPubKey AddTransparentSecretKey(
const uint256& seedFingerprint,
const std::pair<CKey, HDKeyPath>& extSecret);
protected:
bool UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx);
void MarkAffectedTransactionsDirty(const CTransaction& tx);
@ -828,8 +921,11 @@ public:
std::set<int64_t> setKeyPool;
std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapSproutZKeyMetadata;
std::map<libzcash::SaplingIncomingViewingKey, CKeyMetadata> mapSaplingZKeyMetadata;
std::map<std::pair<libzcash::SeedFingerprint, libzcash::AccountId>, libzcash::UFVKId> mapUnifiedAccountKeys;
std::map<libzcash::UFVKId, UFVKAddressMetadata> mapUfvkAddressMetadata;
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
MasterKeyMap mapMasterKeys;
@ -1074,7 +1170,8 @@ public:
//! full viewing key to disk. Inside CCryptoKeyStore and CBasicKeyStore,
//! CBasicKeyStore::AddSaplingFullViewingKey is called directly when adding a
//! full viewing key to the keystore, to avoid this override.
bool AddSaplingFullViewingKey(const libzcash::SaplingExtendedFullViewingKey &extfvk);
bool AddSaplingFullViewingKey(
const libzcash::SaplingExtendedFullViewingKey &extfvk);
bool AddSaplingIncomingViewingKey(
const libzcash::SaplingIncomingViewingKey &ivk,
const libzcash::SaplingPaymentAddress &addr);
@ -1096,11 +1193,43 @@ public:
bool LoadCryptedSaplingZKey(const libzcash::SaplingExtendedFullViewingKey &extfvk,
const std::vector<unsigned char> &vchCryptedSecret);
/**
* Unified keys & addresses
*/
libzcash::ZcashdUnifiedSpendingKey GenerateNewUnifiedSpendingKey();
std::optional<libzcash::ZcashdUnifiedSpendingKey> GenerateUnifiedSpendingKeyForAccount(libzcash::AccountId accountId);
//
// Unified keys & addresses
//
//! Generate the unified spending key from the wallet's mnemonic seed
//! for the next unused account identifier.
std::pair<libzcash::ZcashdUnifiedSpendingKey, libzcash::AccountId>
GenerateNewUnifiedSpendingKey();
//! Generate the unified spending key for the specified ZIP-32/BIP-44
//! account identifier from the wallet's mnemonic seed, or returns
//! std::nullopt if the account identifier does not produce a valid
//! spending key for all receiver types.
std::optional<libzcash::ZcashdUnifiedSpendingKey>
GenerateUnifiedSpendingKeyForAccount(libzcash::AccountId accountId);
//! Retrieves the UFVK derived from the wallet's mnemonic seed for the specified account.
std::optional<libzcash::ZcashdUnifiedFullViewingKey>
GetUnifiedFullViewingKeyByAccount(libzcash::AccountId account);
//! Generate a new unified address for the specified account, diversifier, and
//! set of receiver types.
//!
//! If no diversifier index is provided, the next unused diversifier index
//! will be selected.
UAGenerationResult GenerateUnifiedAddress(
const libzcash::AccountId& accountId,
const std::set<libzcash::ReceiverType>& receivers,
std::optional<libzcash::diversifier_index_t> j = std::nullopt);
bool AddUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &ufvk);
bool LoadUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &ufvk);
bool LoadUnifiedAccountMetadata(const ZcashdUnifiedAccountMetadata &skmeta);
bool LoadUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata &addrmeta);
std::optional<libzcash::UnifiedAddress> GetUnifiedForReceiver(const libzcash::Receiver& receiver);
/**
* Increment the next transaction order id
@ -1512,5 +1641,18 @@ public:
KeyAddResult operator()(const libzcash::InvalidEncoding& no) const;
};
class LookupUnifiedAddress {
private:
const CWallet& wallet;
public:
LookupUnifiedAddress(const CWallet& wallet): wallet(wallet) {}
std::optional<libzcash::UnifiedAddress> operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const;
std::optional<libzcash::UnifiedAddress> operator()(const CScriptID& scriptId) const;
std::optional<libzcash::UnifiedAddress> operator()(const CKeyID& keyId) const;
std::optional<libzcash::UnifiedAddress> operator()(const libzcash::UnknownReceiver& receiver) const;
};
#endif // BITCOIN_WALLET_WALLET_H

View File

@ -169,6 +169,7 @@ bool CWalletDB::WriteZKey(const libzcash::SproutPaymentAddress& addr, const libz
// pair is: tuple_key("zkey", paymentaddress) --> secretkey
return Write(std::make_pair(std::string("zkey"), addr), key, false);
}
bool CWalletDB::WriteSaplingZKey(const libzcash::SaplingIncomingViewingKey &ivk,
const libzcash::SaplingExtendedSpendingKey &key,
const CKeyMetadata &keyMeta)
@ -216,6 +217,33 @@ bool CWalletDB::EraseSaplingExtendedFullViewingKey(
return Erase(std::make_pair(std::string("sapextfvk"), extfvk));
}
//
// Unified address & key storage
//
bool CWalletDB::WriteUnifiedAccountMetadata(const ZcashdUnifiedAccountMetadata& keymeta)
{
nWalletDBUpdateCounter++;
return Write(std::make_pair(std::string("unifiedaccount"), keymeta), 0x00);
}
bool CWalletDB::WriteUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey& ufvk)
{
nWalletDBUpdateCounter++;
auto ufvkId = ufvk.GetKeyID(Params());
return Write(std::make_pair(std::string("unifiedfvk"), ufvkId), ufvk.Encode(Params()));
}
bool CWalletDB::WriteUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata& addrmeta)
{
nWalletDBUpdateCounter++;
return Write(std::make_pair(std::string("unifiedaddrmeta"), addrmeta), 0x00);
}
//
//
//
bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript)
{
nWalletDBUpdateCounter++;
@ -632,6 +660,62 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
{
ssValue >> pwallet->vchDefaultKey;
}
else if (strType == "unifiedfvk")
{
libzcash::UFVKId fp;
ssKey >> fp;
std::string ufvkenc;
ssValue >> ufvkenc;
auto ufvkopt = libzcash::UnifiedFullViewingKey::Decode(ufvkenc, Params());
if (ufvkopt.has_value()) {
auto ufvk = ufvkopt.value();
if (fp != ufvk.GetKeyID(Params())) {
strErr = "Error reading wallet database: key fingerprint did not match decoded key";
return false;
}
if (!pwallet->LoadUnifiedFullViewingKey(ufvk)) {
strErr = "Error reading wallet database: LoadUnifiedFullViewingKey failed.";
return false;
}
} else {
strErr = "Error reading wallet database: failed to decode unified full viewing key.";
return false;
}
}
else if (strType == "unifiedaccount")
{
auto acct = ZcashdUnifiedAccountMetadata::Read(ssKey);
uint8_t value;
ssValue >> value;
if (value != 0x00) {
strErr = "Error reading wallet database: invalid unified account metadata.";
return false;
}
if (!pwallet->LoadUnifiedAccountMetadata(acct)) {
strErr = "Error reading wallet database: account ID mismatch for unified spending key.";
return false;
};
}
else if (strType == "unifiedaddrmeta")
{
auto keymeta = ZcashdUnifiedAddressMetadata::Read(ssKey);
uint8_t value;
ssValue >> value;
if (value != 0x00) {
strErr = "Error reading wallet database: invalid unified address metadata.";
return false;
}
if (!pwallet->LoadUnifiedAddressMetadata(keymeta)) {
strErr = "Error reading wallet database: cannot reproduce previously generated unified address.";
return false;
}
}
else if (strType == "pool")
{
int64_t nIndex;

View File

@ -172,6 +172,141 @@ public:
}
};
class ZcashdUnifiedAccountMetadata {
private:
libzcash::SeedFingerprint seedFp;
uint32_t bip44CoinType;
libzcash::AccountId accountId;
libzcash::UFVKId ufvkId;
ZcashdUnifiedAccountMetadata() {}
public:
ZcashdUnifiedAccountMetadata(
libzcash::SeedFingerprint seedFp,
uint32_t bip44CoinType,
libzcash::AccountId accountId,
libzcash::UFVKId ufvkId):
seedFp(seedFp), bip44CoinType(bip44CoinType), accountId(accountId), ufvkId(ufvkId) {}
/** Returns the fingerprint of the HD seed used to generate this key. */
const libzcash::SeedFingerprint& GetSeedFingerprint() const {
return seedFp;
}
/** Returns the ZIP 32 account id for which this key was generated. */
uint32_t GetBip44CoinType() const {
return bip44CoinType;
}
/** Returns the ZIP 32 account id for which this key was generated. */
libzcash::AccountId GetAccountId() const {
return accountId;
}
/** Returns the fingerprint of the ufvk this key was generated. */
const libzcash::UFVKId& GetKeyID() const {
return ufvkId;
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(seedFp);
READWRITE(bip44CoinType);
READWRITE(accountId);
READWRITE(ufvkId);
}
template <typename Stream>
static ZcashdUnifiedAccountMetadata Read(Stream& stream) {
ZcashdUnifiedAccountMetadata meta;
stream >> meta;
return meta;
}
};
class ZcashdUnifiedAddressMetadata;
// Serialization wrapper for reading and writing ReceiverType
// in CompactSize format.
class ReceiverTypeSer {
private:
libzcash::ReceiverType t;
friend class ZcashdUnifiedAddressMetadata;
public:
ReceiverTypeSer() {} // for serialization only
ReceiverTypeSer(libzcash::ReceiverType t): t(t) {}
template<typename Stream>
void Serialize(Stream &s) const {
WriteCompactSize<Stream>(s, (uint64_t) t);
}
template<typename Stream>
void Unserialize(Stream& s) {
t = (libzcash::ReceiverType) ReadCompactSize<Stream>(s);
}
};
class ZcashdUnifiedAddressMetadata {
private:
libzcash::UFVKId ufvkId;
libzcash::diversifier_index_t diversifierIndex;
std::set<libzcash::ReceiverType> receiverTypes;
ZcashdUnifiedAddressMetadata() {}
public:
ZcashdUnifiedAddressMetadata(
libzcash::UFVKId ufvkId,
libzcash::diversifier_index_t diversifierIndex,
std::set<libzcash::ReceiverType> receiverTypes):
ufvkId(ufvkId), diversifierIndex(diversifierIndex), receiverTypes(receiverTypes) {}
libzcash::UFVKId GetKeyID() const {
return ufvkId;
}
libzcash::diversifier_index_t GetDiversifierIndex() const {
return diversifierIndex;
}
const std::set<libzcash::ReceiverType>& GetReceiverTypes() const {
return receiverTypes;
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(ufvkId);
READWRITE(diversifierIndex);
if (ser_action.ForRead()) {
std::vector<ReceiverTypeSer> serReceiverTypes;
READWRITE(serReceiverTypes);
receiverTypes.clear();
for (ReceiverTypeSer r : serReceiverTypes)
receiverTypes.insert(r.t);
} else {
std::vector<ReceiverTypeSer> serReceiverTypes;
for (libzcash::ReceiverType r : receiverTypes)
serReceiverTypes.push_back(ReceiverTypeSer(r));
READWRITE(serReceiverTypes);
}
}
template <typename Stream>
static ZcashdUnifiedAddressMetadata Read(Stream& stream) {
ZcashdUnifiedAddressMetadata meta;
stream >> meta;
return meta;
}
friend inline bool operator==(const ZcashdUnifiedAddressMetadata& a, const ZcashdUnifiedAddressMetadata& b) {
return
a.ufvkId == b.ufvkId &&
a.diversifierIndex == b.diversifierIndex &&
a.receiverTypes == b.receiverTypes;
}
};
/** Access to the wallet database */
class CWalletDB : public CDB
{
@ -249,6 +384,12 @@ public:
bool WriteSaplingExtendedFullViewingKey(const libzcash::SaplingExtendedFullViewingKey &extfvk);
bool EraseSaplingExtendedFullViewingKey(const libzcash::SaplingExtendedFullViewingKey &extfvk);
/// Unified key support.
bool WriteUnifiedAccountMetadata(const ZcashdUnifiedAccountMetadata& keymeta);
bool WriteUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey& ufvk);
bool WriteUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata& addrmeta);
static void IncrementUpdateCounter();
static unsigned int GetUpdateCounter();
private:

View File

@ -12,6 +12,10 @@ const uint8_t ZCASH_UA_TYPECODE_SAPLING = 0x02;
namespace libzcash {
//
// Unified Addresses
//
std::vector<const Receiver*> UnifiedAddress::GetSorted() const {
std::vector<const libzcash::Receiver*> sorted;
for (const auto& receiver : receivers) {
@ -39,6 +43,37 @@ bool UnifiedAddress::AddReceiver(Receiver receiver) {
return true;
}
std::optional<CKeyID> UnifiedAddress::GetP2PKHReceiver() const {
for (const auto& r : receivers) {
if (std::holds_alternative<CKeyID>(r)) {
return std::get<CKeyID>(r);
}
}
return std::nullopt;
}
std::optional<CScriptID> UnifiedAddress::GetP2SHReceiver() const {
for (const auto& r : receivers) {
if (std::holds_alternative<CScriptID>(r)) {
return std::get<CScriptID>(r);
}
}
return std::nullopt;
}
std::optional<SaplingPaymentAddress> UnifiedAddress::GetSaplingReceiver() const {
for (const auto& r : receivers) {
if (std::holds_alternative<SaplingPaymentAddress>(r)) {
return std::get<SaplingPaymentAddress>(r);
}
}
return std::nullopt;
}
std::pair<std::string, PaymentAddress> AddressInfoFromSpendingKey::operator()(const SproutSpendingKey &sk) const {
return std::make_pair("sprout", sk.address());
}
@ -58,8 +93,9 @@ std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(con
std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(const UnifiedFullViewingKey &ufvk) const {
return std::make_pair(
"unified",
ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(ufvk)
ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(keyConstants, ufvk)
.FindAddress(diversifier_index_t(0))
.value() //safe because we're searching from 0
.first
);
}
@ -189,6 +225,10 @@ std::set<libzcash::RawAddress> GetRawAddresses::operator()(
return ret;
}
//
// Unified full viewing keys
//
std::optional<libzcash::UnifiedFullViewingKey> libzcash::UnifiedFullViewingKey::Decode(
const std::string& str,
const KeyConstants& keyConstants) {
@ -280,3 +320,11 @@ libzcash::UnifiedFullViewingKey libzcash::UnifiedFullViewingKey::FromZcashdUFVK(
}
return result.value();
}
libzcash::UFVKId libzcash::UnifiedFullViewingKey::GetKeyID(const KeyConstants& keyConstants) const {
// The ID of a ufvk is the blake2b hash of the serialized form of the
// ufvk with the receivers sorted in order of descending receiver type.
CBLAKE2bWriter h(SER_GETHASH, 0, ZCASH_UFVK_ID_PERSONAL);
h << Encode(keyConstants);
return libzcash::UFVKId(h.GetHash());
}

View File

@ -3,8 +3,10 @@
#include "key_constants.h"
#include "pubkey.h"
#include "key_constants.h"
#include "script/script.h"
#include "uint256.h"
#include "util/match.h"
#include "zcash/address/orchard.hpp"
#include "zcash/address/sapling.hpp"
#include "zcash/address/sprout.hpp"
@ -14,6 +16,9 @@
#include <variant>
#include <rust/unified_keys.h>
const unsigned char ZCASH_UFVK_ID_PERSONAL[BLAKE2bPersonalBytes] =
{'Z', 'c', 'a', 's', 'h', '_', 'U', 'F', 'V', 'K', '_', 'I', 'd', '_', 'F', 'P'};
namespace libzcash {
/** Protocol addresses that can receive funds in a transaction. */
@ -112,6 +117,26 @@ public:
const std::vector<Receiver>& GetReceiversAsParsed() const { return receivers; }
std::set<ReceiverType> GetKnownReceiverTypes() const {
std::set<ReceiverType> result;
for (const auto& receiver : receivers) {
std::visit(match {
[&](const libzcash::SaplingPaymentAddress &zaddr) {
result.insert(ReceiverType::Sapling);
},
[&](const CScriptID &zaddr) {
result.insert(ReceiverType::P2SH);
},
[&](const CKeyID &zaddr) {
result.insert(ReceiverType::P2PKH);
},
[&](const libzcash::UnknownReceiver &uaddr) {
}
}, receiver);
}
return result;
}
ReceiverIterator begin() const {
return ReceiverIterator(GetSorted(), 0);
}
@ -122,9 +147,18 @@ public:
return receivers.size();
}
std::optional<CKeyID> GetP2PKHReceiver() const;
std::optional<CScriptID> GetP2SHReceiver() const;
std::optional<SaplingPaymentAddress> GetSaplingReceiver() const;
friend inline bool operator==(const UnifiedAddress& a, const UnifiedAddress& b) {
return a.receivers == b.receivers;
}
friend inline bool operator!=(const UnifiedAddress& a, const UnifiedAddress& b) {
return a.receivers != b.receivers;
}
friend inline bool operator<(const UnifiedAddress& a, const UnifiedAddress& b) {
return a.receivers < b.receivers;
}
@ -147,9 +181,10 @@ private:
friend class UnifiedFullViewingKeyBuilder;
public:
static std::optional<UnifiedFullViewingKey> Decode(
const std::string& str,
const KeyConstants& keyConstants);
UnifiedFullViewingKey(UnifiedFullViewingKey&& key) : inner(std::move(key.inner)) {}
UnifiedFullViewingKey(const UnifiedFullViewingKey& key) :
inner(unified_full_viewing_key_clone(key.inner.get()), unified_full_viewing_key_free) {}
/**
* This method should only be used for serialization of unified full
@ -161,16 +196,17 @@ public:
*/
static UnifiedFullViewingKey FromZcashdUFVK(const ZcashdUnifiedFullViewingKey&);
static std::optional<UnifiedFullViewingKey> Decode(
const std::string& str,
const KeyConstants& keyConstants);
std::string Encode(const KeyConstants& keyConstants) const;
std::optional<SaplingDiversifiableFullViewingKey> GetSaplingKey() const;
std::optional<CChainablePubKey> GetTransparentKey() const;
UnifiedFullViewingKey(UnifiedFullViewingKey&& key) : inner(std::move(key.inner)) {}
UnifiedFullViewingKey(const UnifiedFullViewingKey& key) :
inner(unified_full_viewing_key_clone(key.inner.get()), unified_full_viewing_key_free) {}
UFVKId GetKeyID(const KeyConstants& keyConstants) const;
UnifiedFullViewingKey& operator=(UnifiedFullViewingKey&& key)
{
@ -228,7 +264,11 @@ public:
};
class AddressInfoFromViewingKey {
private:
const KeyConstants& keyConstants;
public:
AddressInfoFromViewingKey(const KeyConstants& keyConstants): keyConstants(keyConstants) {}
std::pair<std::string, PaymentAddress> operator()(const SproutViewingKey&) const;
std::pair<std::string, PaymentAddress> operator()(const struct SaplingExtendedFullViewingKey&) const;
std::pair<std::string, PaymentAddress> operator()(const UnifiedFullViewingKey&) const;

View File

@ -4,6 +4,10 @@
#include "bip44.h"
HDKeyPath libzcash::Bip44TransparentAccountKeyPath(uint32_t bip44CoinType, libzcash::AccountId accountId) {
return "m/44'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(accountId) + "'";
}
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());
@ -21,7 +25,7 @@ std::optional<std::pair<CExtKey, HDKeyPath>> libzcash::DeriveBip44TransparentAcc
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) + "'";
auto hdKeypath = libzcash::Bip44TransparentAccountKeyPath(bip44CoinType, accountId);
return std::make_pair(result.value(), hdKeypath);
}
@ -42,29 +46,21 @@ std::optional<libzcash::Bip44AccountChains> libzcash::Bip44AccountChains::ForAcc
return Bip44AccountChains(seed.Fingerprint(), bip44CoinType, accountId, external.value(), internal.value());
}
std::optional<std::pair<CExtKey, HDKeyPath>> libzcash::Bip44AccountChains::DeriveExternal(uint32_t addrIndex) {
std::optional<std::pair<CKey, 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);
auto hdKeypath = Bip44TransparentAccountKeyPath(bip44CoinType, accountId) + "/0/" + std::to_string(addrIndex);
return std::make_pair(childKey.value(), hdKeypath);
return std::make_pair(childKey.value().key, hdKeypath);
}
std::optional<std::pair<CExtKey, HDKeyPath>> libzcash::Bip44AccountChains::DeriveInternal(uint32_t addrIndex) {
std::optional<std::pair<CKey, 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);
auto hdKeypath = Bip44TransparentAccountKeyPath(bip44CoinType, accountId) + "/1/" + std::to_string(addrIndex);
return std::make_pair(childKey.value(), hdKeypath);
return std::make_pair(childKey.value().key, hdKeypath);
}

View File

@ -9,6 +9,12 @@
namespace libzcash {
HDKeyPath Bip44TransparentAccountKeyPath(uint32_t bip44CoinType, libzcash::AccountId accountId);
/**
* Derive a transparent extended key in compressed format for the specified
* seed, bip44 coin type, and account ID.
*/
std::optional<std::pair<CExtKey, HDKeyPath>> DeriveBip44TransparentAccountKey(const HDSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId);
class Bip44AccountChains {
@ -27,8 +33,19 @@ public:
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);
/**
* Generate the key corresponding to the specified index at the "external child"
* level of the BIP44 path for the account.
*/
std::optional<std::pair<CKey, HDKeyPath>> DeriveExternal(uint32_t addrIndex);
/**
* Generate the key corresponding to the specified index at the "internal child"
* level of the BIP44 path for the account. This should probably only usually be
* used at address index 0, but ordinarily it won't need to be used at all since
* all change should be shielded by default.
*/
std::optional<std::pair<CKey, HDKeyPath>> DeriveInternal(uint32_t addrIndex = 0);
};
} //namespace libzcash

View File

@ -12,9 +12,27 @@ using namespace libzcash;
// Unified Keys
//
std::optional<std::pair<ZcashdUnifiedSpendingKey, HDKeyPath>> ZcashdUnifiedSpendingKey::ForAccount(const HDSeed& seed, uint32_t bip44CoinType, AccountId accountId) {
bool libzcash::HasShielded(const std::set<ReceiverType>& receiverTypes) {
auto has_shielded = [](ReceiverType r) {
// TODO: update this as support for new shielded protocols is added.
return r == ReceiverType::Sapling;
};
return std::find_if(receiverTypes.begin(), receiverTypes.end(), has_shielded) != receiverTypes.end();
}
bool libzcash::HasTransparent(const std::set<ReceiverType>& receiverTypes) {
auto has_transparent = [](ReceiverType r) {
// TODO: update this as support for new transparent protocols is added.
return r == ReceiverType::P2PKH || r == ReceiverType::P2SH;
};
return std::find_if(receiverTypes.begin(), receiverTypes.end(), has_transparent) != receiverTypes.end();
}
std::optional<ZcashdUnifiedSpendingKey> 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;
@ -23,30 +41,26 @@ std::optional<std::pair<ZcashdUnifiedSpendingKey, HDKeyPath>> ZcashdUnifiedSpend
auto saplingKey = SaplingExtendedSpendingKey::ForAccount(seed, bip44CoinType, accountId);
usk.saplingKey = saplingKey.first;
return std::make_pair(usk, saplingKey.second);
return usk;
}
ZcashdUnifiedFullViewingKey ZcashdUnifiedSpendingKey::ToFullViewingKey() const {
ZcashdUnifiedFullViewingKey ufvk;
UnifiedFullViewingKey ZcashdUnifiedSpendingKey::ToFullViewingKey() const {
UnifiedFullViewingKeyBuilder builder;
if (transparentKey.has_value()) {
auto extPubKey = transparentKey.value().Neuter();
auto extPubKey = transparentKey.Neuter();
builder.AddTransparentKey(CChainablePubKey::FromParts(extPubKey.chaincode, extPubKey.pubkey).value());
builder.AddSaplingKey(saplingKey.ToXFVK());
// TODO: how to ensure that the pubkey is in its compressed representation?
// Is that already guaranteed?
ufvk.transparentKey = CChainablePubKey::FromParts(extPubKey.chaincode, extPubKey.pubkey).value();
}
if (saplingKey.has_value()) {
ufvk.saplingKey = saplingKey.value().ToXFVK();
}
return ufvk;
// This call to .value() is safe as ZcashdUnifiedSpendingKey values are always
// constructed to contain all required components.
return builder.build().value();
}
ZcashdUnifiedFullViewingKey ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(
const KeyConstants& keyConstants,
const UnifiedFullViewingKey& ufvk) {
ZcashdUnifiedFullViewingKey result;
result.keyId = ufvk.GetKeyID(keyConstants);
auto transparentKey = ufvk.GetTransparentKey();
if (transparentKey.has_value()) {
@ -61,10 +75,16 @@ ZcashdUnifiedFullViewingKey ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingK
return result;
}
std::optional<UnifiedAddress> ZcashdUnifiedFullViewingKey::Address(diversifier_index_t j) const {
UnifiedAddress ua;
std::optional<UnifiedAddress> ZcashdUnifiedFullViewingKey::Address(
const diversifier_index_t& j,
const std::set<ReceiverType>& receiverTypes) const
{
if (!HasShielded(receiverTypes)) {
throw std::runtime_error("Unified addresses must include a shielded receiver.");
}
if (saplingKey.has_value()) {
UnifiedAddress ua;
if (saplingKey.has_value() && receiverTypes.count(ReceiverType::Sapling) > 0) {
auto saplingAddress = saplingKey.value().Address(j);
if (saplingAddress.has_value()) {
ua.AddReceiver(saplingAddress.value());
@ -73,7 +93,7 @@ std::optional<UnifiedAddress> ZcashdUnifiedFullViewingKey::Address(diversifier_i
}
}
if (transparentKey.has_value()) {
if (transparentKey.has_value() && receiverTypes.count(ReceiverType::P2PKH) > 0) {
const auto& tkey = transparentKey.value();
auto childIndex = j.ToTransparentChildIndex();
if (!childIndex.has_value()) return std::nullopt;
@ -96,13 +116,21 @@ std::optional<UnifiedAddress> ZcashdUnifiedFullViewingKey::Address(diversifier_i
return ua;
}
std::pair<UnifiedAddress, diversifier_index_t> ZcashdUnifiedFullViewingKey::FindAddress(diversifier_index_t j) const {
auto addr = Address(j);
std::optional<std::pair<UnifiedAddress, diversifier_index_t>> ZcashdUnifiedFullViewingKey::FindAddress(
const diversifier_index_t& j,
const std::set<ReceiverType>& receiverTypes) const {
diversifier_index_t j0(j);
bool hasTransparent = HasTransparent(receiverTypes);
auto addr = Address(j0, receiverTypes);
while (!addr.has_value()) {
if (!j.increment())
throw std::runtime_error(std::string(__func__) + ": diversifier index overflow.");;
addr = Address(j);
if (!j0.increment() || (hasTransparent && !j0.ToTransparentChildIndex().has_value()))
return std::nullopt;
addr = Address(j0, receiverTypes);
}
return std::make_pair(addr.value(), j);
return std::make_pair(addr.value(), j0);
}
std::optional<std::pair<UnifiedAddress, diversifier_index_t>> ZcashdUnifiedFullViewingKey::FindAddress(
const diversifier_index_t& j) const {
return FindAddress(j, {ReceiverType::P2PKH, ReceiverType::Sapling});
}

View File

@ -5,18 +5,47 @@
#ifndef ZCASH_ZCASH_ADDRESS_UNIFIED_H
#define ZCASH_ZCASH_ADDRESS_UNIFIED_H
#include "zip32.h"
#include "bip44.h"
#include "key_constants.h"
#include "zip32.h"
namespace libzcash {
enum class ReceiverType: uint32_t {
P2PKH = 0x00,
P2SH = 0x01,
Sapling = 0x02,
//Orchard = 0x03
};
/**
* Test whether the specified list of receiver types contains a
* shielded receiver type
*/
bool HasShielded(const std::set<ReceiverType>& receiverTypes);
/**
* Test whether the specified list of receiver types contains a
* shielded receiver type
*/
bool HasTransparent(const std::set<ReceiverType>& receiverTypes);
class ZcashdUnifiedSpendingKey;
class ZcashdUnifiedFullViewingKey;
// prototypes for the classes handling ZIP-316 encoding (in Address.hpp)
class UnifiedAddress;
class UnifiedFullViewingKey;
/**
* An internal identifier for a unified full viewing key, derived as a
* blake2b hash of the serialized form of the UFVK.
*/
class UFVKId: public uint256 {
public:
UFVKId() : uint256() {}
UFVKId(const uint256& in) : uint256(in) {}
};
/**
* An internal-only type for unified full viewing keys that represents only the
* set of receiver types that are supported by zcashd. This type does not
@ -25,6 +54,7 @@ class UnifiedFullViewingKey;
*/
class ZcashdUnifiedFullViewingKey {
private:
UFVKId keyId;
std::optional<CChainablePubKey> transparentKey;
std::optional<SaplingDiversifiableFullViewingKey> saplingKey;
@ -33,9 +63,16 @@ private:
friend class ZcashdUnifiedSpendingKey;
public:
/**
* This constructor is lossy, and does not support round-trip transformations.
* This constructor is lossy; it ignores unknown receiver types
* and therefore does not support round-trip transformations.
*/
static ZcashdUnifiedFullViewingKey FromUnifiedFullViewingKey(const UnifiedFullViewingKey& ufvk);
static ZcashdUnifiedFullViewingKey FromUnifiedFullViewingKey(
const KeyConstants& keyConstants,
const UnifiedFullViewingKey& ufvk);
const UFVKId& GetKeyID() const {
return keyId;
}
const std::optional<CChainablePubKey>& GetTransparentKey() const {
return transparentKey;
@ -45,9 +82,44 @@ public:
return saplingKey;
}
std::optional<UnifiedAddress> Address(diversifier_index_t j) const;
/**
* Creates a new unified address having the specified receiver types, at the specified
* diversifier index, unless the diversifer index would generate an invalid receiver.
* Returns `std::nullopt` if the diversifier index does not produce a valid receiver
* for one or more of the specified receiver types; under this circumstance, the caller
* should usually try successive diversifier indices until the operation returns a
* non-null value.
*
* This method will throw if `receiverTypes` does not include a shielded receiver type.
*/
std::optional<UnifiedAddress> Address(
const diversifier_index_t& j,
const std::set<ReceiverType>& receiverTypes) const;
std::pair<UnifiedAddress, diversifier_index_t> FindAddress(diversifier_index_t j) const;
/**
* Find the smallest diversifier index >= `j` such that it generates a valid
* unified address according to the conditions specified in the documentation
* for the `Address` method above, and returns the newly created address along
* with the diversifier index used to produce it. Returns `std::nullopt` if the
* diversifier space is exhausted, or if the set of receiver types contains a
* transparent receiver and the diversifier exceeds the maximum transparent
* child index.
*
* This method will throw if `receiverTypes` does not include a shielded receiver type.
*/
std::optional<std::pair<UnifiedAddress, diversifier_index_t>> FindAddress(
const diversifier_index_t& j,
const std::set<ReceiverType>& receiverTypes) const;
/**
* Find the next available address that contains all supported receiver types.
*/
std::optional<std::pair<UnifiedAddress, diversifier_index_t>> FindAddress(const diversifier_index_t& j) const;
friend bool operator==(const ZcashdUnifiedFullViewingKey& a, const ZcashdUnifiedFullViewingKey& b)
{
return a.transparentKey == b.transparentKey && a.saplingKey == b.saplingKey;
}
};
/**
@ -55,26 +127,25 @@ public:
*/
class ZcashdUnifiedSpendingKey {
private:
libzcash::AccountId accountId;
std::optional<CExtKey> transparentKey;
std::optional<SaplingExtendedSpendingKey> saplingKey;
CExtKey transparentKey;
SaplingExtendedSpendingKey saplingKey;
ZcashdUnifiedSpendingKey() {}
public:
static std::optional<std::pair<ZcashdUnifiedSpendingKey, HDKeyPath>> ForAccount(
static std::optional<ZcashdUnifiedSpendingKey> ForAccount(
const HDSeed& seed,
uint32_t bip44CoinType,
libzcash::AccountId accountId);
const std::optional<CExtKey>& GetTransparentKey() const {
const CExtKey& GetTransparentKey() const {
return transparentKey;
}
const std::optional<SaplingExtendedSpendingKey>& GetSaplingExtendedSpendingKey() const {
const SaplingExtendedSpendingKey& GetSaplingExtendedSpendingKey() const {
return saplingKey;
}
ZcashdUnifiedFullViewingKey ToFullViewingKey() const;
UnifiedFullViewingKey ToFullViewingKey() const;
};
} //namespace libzcash

View File

@ -19,8 +19,6 @@ 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(0x7FFFFFFF);
uint256 HDSeed::Fingerprint() const
{
CBLAKE2bWriter h(SER_GETHASH, 0, ZCASH_HD_SEED_FP_PERSONAL);
@ -48,12 +46,12 @@ uint256 ovkForShieldingFromTaddr(HDSeed& seed) {
namespace libzcash {
std::optional<unsigned int> diversifier_index_t::ToTransparentChildIndex() const {
std::optional<uint32_t> diversifier_index_t::ToTransparentChildIndex() const {
// ensure that the diversifier index is small enough for a t-addr
if (MAX_TRANSPARENT_CHILD_IDX < *this) {
return std::nullopt;
} else {
return (unsigned int) GetUint64(0);
return (uint32_t) GetUint64(0);
}
}
@ -173,10 +171,7 @@ std::pair<SaplingExtendedSpendingKey, HDKeyPath> SaplingExtendedSpendingKey::For
// Derive account key at the given account index
auto xsk = m_32h_cth.Derive(accountId | HARDENED_KEY_LIMIT);
// Create new metadata
auto hdKeypath = "m/32'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(accountId) + "'";
return std::make_pair(xsk, hdKeypath);
return std::make_pair(xsk, libzcash::Zip32AccountKeyPath(bip44CoinType, accountId));
}
std::pair<SaplingExtendedSpendingKey, HDKeyPath> SaplingExtendedSpendingKey::Legacy(const HDSeed& seed, uint32_t bip44CoinType, uint32_t addressIndex) {
@ -198,13 +193,7 @@ std::pair<SaplingExtendedSpendingKey, HDKeyPath> SaplingExtendedSpendingKey::Leg
// Derive key at the specified address index
auto xsk = m_32h_cth_l.Derive(addressIndex | HARDENED_KEY_LIMIT);
// Create new metadata
auto hdKeypath = "m/32'/"
+ std::to_string(bip44CoinType) + "'/"
+ std::to_string(ZCASH_LEGACY_ACCOUNT) + "'/"
+ std::to_string(addressIndex) + "'";
return std::make_pair(xsk, hdKeypath);
return std::make_pair(xsk, libzcash::Zip32AccountKeyPath(bip44CoinType, ZCASH_LEGACY_ACCOUNT, addressIndex));
}
SaplingExtendedFullViewingKey SaplingExtendedSpendingKey::ToXFVK() const
@ -219,6 +208,17 @@ SaplingExtendedFullViewingKey SaplingExtendedSpendingKey::ToXFVK() const
return ret;
}
HDKeyPath Zip32AccountKeyPath(
uint32_t bip44CoinType,
libzcash::AccountId accountId,
std::optional<uint32_t> legacyAddressIndex) {
HDKeyPath addrSuffix = "";
if (legacyAddressIndex.has_value()) {
addrSuffix = "/" + std::to_string(legacyAddressIndex.value()) + "'";
}
return "m/32'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(accountId) + "'" + addrSuffix;
}
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

@ -62,6 +62,7 @@ uint256 ovkForShieldingFromTaddr(HDSeed& seed);
namespace libzcash {
typedef uint256 SeedFingerprint;
typedef uint32_t AccountId;
/**
@ -102,7 +103,16 @@ public:
return false; //overflow
}
std::optional<unsigned int> ToTransparentChildIndex() const;
std::optional<diversifier_index_t> succ() const {
diversifier_index_t next(*this);
if (next.increment()) {
return next;
} else {
return std::nullopt;
}
}
std::optional<uint32_t> ToTransparentChildIndex() const;
friend bool operator<(const diversifier_index_t& a, const diversifier_index_t& b) {
for (int i = 10; i >= 0; i--) {
@ -117,6 +127,9 @@ public:
}
};
// The maximum allowed transparent child index according to BIP-44
const libzcash::diversifier_index_t MAX_TRANSPARENT_CHILD_IDX(0x7FFFFFFF);
class SaplingDiversifiableFullViewingKey {
public:
libzcash::SaplingFullViewingKey fvk;
@ -240,7 +253,15 @@ struct SaplingExtendedSpendingKey {
}
};
std::optional<unsigned long> ParseHDKeypathAccount(uint32_t purpose, uint32_t coinType, const std::string& keyPath);
HDKeyPath Zip32AccountKeyPath(
uint32_t bip44CoinType,
libzcash::AccountId accountId,
std::optional<uint32_t> legacyAddressIndex = std::nullopt);
std::optional<unsigned long> ParseHDKeypathAccount(
uint32_t purpose,
uint32_t coinType,
const std::string& keyPath);
} //namespace libzcash