Merge pull request #5395 from nuttycom/feature/wallet_orchard-unified_addrs
Add unified addresses to the zcashd wallet.
This commit is contained in:
commit
1d7a29ea56
|
@ -86,7 +86,7 @@ public:
|
||||||
CMainParams() {
|
CMainParams() {
|
||||||
keyConstants.strNetworkID = "main";
|
keyConstants.strNetworkID = "main";
|
||||||
strCurrencyUnits = "ZEC";
|
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.fCoinbaseMustBeShielded = true;
|
||||||
consensus.nSubsidySlowStartInterval = 20000;
|
consensus.nSubsidySlowStartInterval = 20000;
|
||||||
consensus.nPreBlossomSubsidyHalvingInterval = Consensus::PRE_BLOSSOM_HALVING_INTERVAL;
|
consensus.nPreBlossomSubsidyHalvingInterval = Consensus::PRE_BLOSSOM_HALVING_INTERVAL;
|
||||||
|
@ -370,7 +370,7 @@ public:
|
||||||
CTestNetParams() {
|
CTestNetParams() {
|
||||||
keyConstants.strNetworkID = "test";
|
keyConstants.strNetworkID = "test";
|
||||||
strCurrencyUnits = "TAZ";
|
strCurrencyUnits = "TAZ";
|
||||||
bip44CoinType = 1;
|
keyConstants.bip44CoinType = 1;
|
||||||
consensus.fCoinbaseMustBeShielded = true;
|
consensus.fCoinbaseMustBeShielded = true;
|
||||||
consensus.nSubsidySlowStartInterval = 20000;
|
consensus.nSubsidySlowStartInterval = 20000;
|
||||||
consensus.nPreBlossomSubsidyHalvingInterval = Consensus::PRE_BLOSSOM_HALVING_INTERVAL;
|
consensus.nPreBlossomSubsidyHalvingInterval = Consensus::PRE_BLOSSOM_HALVING_INTERVAL;
|
||||||
|
@ -621,7 +621,7 @@ public:
|
||||||
CRegTestParams() {
|
CRegTestParams() {
|
||||||
keyConstants.strNetworkID = "regtest";
|
keyConstants.strNetworkID = "regtest";
|
||||||
strCurrencyUnits = "REG";
|
strCurrencyUnits = "REG";
|
||||||
bip44CoinType = 1;
|
keyConstants.bip44CoinType = 1;
|
||||||
consensus.fCoinbaseMustBeShielded = false;
|
consensus.fCoinbaseMustBeShielded = false;
|
||||||
consensus.nSubsidySlowStartInterval = 0;
|
consensus.nSubsidySlowStartInterval = 0;
|
||||||
consensus.nPreBlossomSubsidyHalvingInterval = Consensus::PRE_BLOSSOM_REGTEST_HALVING_INTERVAL;
|
consensus.nPreBlossomSubsidyHalvingInterval = Consensus::PRE_BLOSSOM_REGTEST_HALVING_INTERVAL;
|
||||||
|
|
|
@ -32,17 +32,6 @@ struct CCheckpointData {
|
||||||
double fTransactionsPerDay;
|
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
|
* CChainParams defines various tweakable parameters of a given instance of the
|
||||||
* Bitcoin system. There are three: the main network on which people trade goods
|
* Bitcoin system. There are three: the main network on which people trade goods
|
||||||
|
@ -73,14 +62,19 @@ public:
|
||||||
bool RequireStandard() const { return fRequireStandard; }
|
bool RequireStandard() const { return fRequireStandard; }
|
||||||
int64_t PruneAfterHeight() const { return nPruneAfterHeight; }
|
int64_t PruneAfterHeight() const { return nPruneAfterHeight; }
|
||||||
std::string CurrencyUnits() const { return strCurrencyUnits; }
|
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 */
|
/** Make miner stop after a block is found. In RPC, don't return until nGenProcLimit blocks are generated */
|
||||||
bool MineBlocksOnDemand() const { return fMineBlocksOnDemand; }
|
bool MineBlocksOnDemand() const { return fMineBlocksOnDemand; }
|
||||||
/** In the future use NetworkIDString() for RPC fields */
|
/** In the future use NetworkIDString() for RPC fields */
|
||||||
bool TestnetToBeDeprecatedFieldRPC() const { return fTestnetToBeDeprecatedFieldRPC; }
|
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; }
|
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 {
|
const std::vector<unsigned char>& Base58Prefix(Base58Type type) const {
|
||||||
return keyConstants.Base58Prefix(type);
|
return keyConstants.Base58Prefix(type);
|
||||||
}
|
}
|
||||||
|
@ -107,7 +101,6 @@ protected:
|
||||||
std::vector<CDNSSeedData> vSeeds;
|
std::vector<CDNSSeedData> vSeeds;
|
||||||
CBaseKeyConstants keyConstants;
|
CBaseKeyConstants keyConstants;
|
||||||
std::string strCurrencyUnits;
|
std::string strCurrencyUnits;
|
||||||
uint32_t bip44CoinType;
|
|
||||||
CBlock genesis;
|
CBlock genesis;
|
||||||
std::vector<SeedSpec6> vFixedSeeds;
|
std::vector<SeedSpec6> vFixedSeeds;
|
||||||
bool fMiningRequiresPeers = false;
|
bool fMiningRequiresPeers = false;
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
|
|
||||||
#define MAKE_STRING(x) std::string((x), (x)+sizeof(x))
|
#define MAKE_STRING(x) std::string((x), (x)+sizeof(x))
|
||||||
|
|
||||||
|
using namespace libzcash;
|
||||||
|
|
||||||
const uint32_t SLIP44_TESTNET_TYPE = 1;
|
const uint32_t SLIP44_TESTNET_TYPE = 1;
|
||||||
|
|
||||||
TEST(KeystoreTests, StoreAndRetrieveMnemonicSeed) {
|
TEST(KeystoreTests, StoreAndRetrieveMnemonicSeed) {
|
||||||
|
@ -531,4 +533,60 @@ TEST(KeystoreTests, StoreAndRetrieveSpendingKeyInEncryptedStore) {
|
||||||
ASSERT_EQ(1, addrs.count(addr));
|
ASSERT_EQ(1, addrs.count(addr));
|
||||||
ASSERT_EQ(1, addrs.count(addr2));
|
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
|
#endif
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#define ZCASH_KEY_CONSTANTS_H
|
#define ZCASH_KEY_CONSTANTS_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class KeyConstants
|
class KeyConstants
|
||||||
{
|
{
|
||||||
|
@ -35,8 +36,22 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual std::string NetworkIDString() const =0;
|
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::vector<unsigned char>& Base58Prefix(Base58Type type) const =0;
|
||||||
virtual const std::string& Bech32HRP(Bech32Type 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
|
#endif // ZCASH_KEY_CONSTANTS_H
|
||||||
|
|
108
src/keystore.cpp
108
src/keystore.cpp
|
@ -287,3 +287,111 @@ bool CBasicKeyStore::GetSaplingExtendedSpendingKey(const libzcash::SaplingPaymen
|
||||||
GetSaplingFullViewingKey(ivk, extfvk) &&
|
GetSaplingFullViewingKey(ivk, extfvk) &&
|
||||||
GetSaplingSpendingKey(extfvk, extskOut);
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "script/standard.h"
|
#include "script/standard.h"
|
||||||
#include "sync.h"
|
#include "sync.h"
|
||||||
#include "zcash/address/mnemonic.h"
|
#include "zcash/address/mnemonic.h"
|
||||||
|
#include "zcash/address/unified.h"
|
||||||
#include "zcash/Address.hpp"
|
#include "zcash/Address.hpp"
|
||||||
#include "zcash/NoteEncryption.hpp"
|
#include "zcash/NoteEncryption.hpp"
|
||||||
|
|
||||||
|
@ -100,6 +101,35 @@ public:
|
||||||
virtual bool GetSproutViewingKey(
|
virtual bool GetSproutViewingKey(
|
||||||
const libzcash::SproutPaymentAddress &address,
|
const libzcash::SproutPaymentAddress &address,
|
||||||
libzcash::SproutViewingKey& vkOut) const =0;
|
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;
|
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.
|
// Only maps from default addresses to ivk, may need to be reworked when adding diversified addresses.
|
||||||
typedef std::map<libzcash::SaplingPaymentAddress, libzcash::SaplingIncomingViewingKey> SaplingIncomingViewingKeyMap;
|
typedef std::map<libzcash::SaplingPaymentAddress, libzcash::SaplingIncomingViewingKey> SaplingIncomingViewingKeyMap;
|
||||||
|
|
||||||
|
class FindUFVKId;
|
||||||
|
|
||||||
/** Basic key store, that keeps keys in an address->secret map */
|
/** Basic key store, that keeps keys in an address->secret map */
|
||||||
class CBasicKeyStore : public CKeyStore
|
class CBasicKeyStore : public CKeyStore
|
||||||
{
|
{
|
||||||
|
@ -142,6 +174,13 @@ protected:
|
||||||
SaplingFullViewingKeyMap mapSaplingFullViewingKeys;
|
SaplingFullViewingKeyMap mapSaplingFullViewingKeys;
|
||||||
SaplingIncomingViewingKeyMap mapSaplingIncomingViewingKeys;
|
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:
|
public:
|
||||||
bool SetMnemonicSeed(const MnemonicSeed& seed);
|
bool SetMnemonicSeed(const MnemonicSeed& seed);
|
||||||
bool HaveMnemonicSeed() const;
|
bool HaveMnemonicSeed() const;
|
||||||
|
@ -314,6 +353,22 @@ public:
|
||||||
virtual bool GetSproutViewingKey(
|
virtual bool GetSproutViewingKey(
|
||||||
const libzcash::SproutPaymentAddress &address,
|
const libzcash::SproutPaymentAddress &address,
|
||||||
libzcash::SproutViewingKey& vkOut) const;
|
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;
|
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
|
//! Sapling
|
||||||
typedef std::map<libzcash::SaplingExtendedFullViewingKey, std::vector<unsigned char> > CryptedSaplingSpendingKeyMap;
|
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
|
#endif // BITCOIN_KEYSTORE_H
|
||||||
|
|
|
@ -353,6 +353,22 @@ extern "C" {
|
||||||
unsigned char *addr_ret
|
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
|
/// Fills the provided buffer with random bytes. This is intended to
|
||||||
/// be a cryptographically secure RNG; it uses Rust's `OsRng`, which
|
/// be a cryptographically secure RNG; it uses Rust's `OsRng`, which
|
||||||
/// is implemented in terms of the `getrandom` crate. The first call
|
/// is implemented in terms of the `getrandom` crate. The first call
|
||||||
|
|
|
@ -47,6 +47,7 @@ use zcash_primitives::{
|
||||||
constants::{CRH_IVK_PERSONALIZATION, PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR},
|
constants::{CRH_IVK_PERSONALIZATION, PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR},
|
||||||
merkle_tree::MerklePath,
|
merkle_tree::MerklePath,
|
||||||
sapling::{
|
sapling::{
|
||||||
|
self,
|
||||||
keys::FullViewingKey,
|
keys::FullViewingKey,
|
||||||
note_encryption::sapling_ka_agree,
|
note_encryption::sapling_ka_agree,
|
||||||
redjubjub::{self, Signature},
|
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]
|
#[no_mangle]
|
||||||
pub extern "C" fn librustzcash_getrandom(buf: *mut u8, buf_len: usize) {
|
pub extern "C" fn librustzcash_getrandom(buf: *mut u8, buf_len: usize) {
|
||||||
let buf = unsafe { slice::from_raw_parts_mut(buf, buf_len) };
|
let buf = unsafe { slice::from_raw_parts_mut(buf, buf_len) };
|
||||||
|
|
|
@ -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
|
|
@ -20,6 +20,7 @@
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
using ::testing::Return;
|
using ::testing::Return;
|
||||||
|
using namespace libzcash;
|
||||||
|
|
||||||
ACTION(ThrowLogicError) {
|
ACTION(ThrowLogicError) {
|
||||||
throw std::logic_error("Boom");
|
throw std::logic_error("Boom");
|
||||||
|
@ -181,12 +182,12 @@ TEST(WalletTests, SproutNoteDataSerialisation) {
|
||||||
EXPECT_EQ(noteData[jsoutpt].witnesses, noteData2[jsoutpt].witnesses);
|
EXPECT_EQ(noteData[jsoutpt].witnesses, noteData2[jsoutpt].witnesses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST(WalletTests, FindUnspentSproutNotes) {
|
TEST(WalletTests, FindUnspentSproutNotes) {
|
||||||
auto consensusParams = RegtestActivateSapling();
|
SelectParams(CBaseChainParams::TESTNET);
|
||||||
|
|
||||||
CWallet wallet(Params());
|
CWallet wallet(Params());
|
||||||
LOCK2(cs_main, wallet.cs_wallet);
|
LOCK2(cs_main, wallet.cs_wallet);
|
||||||
|
|
||||||
auto sk = libzcash::SproutSpendingKey::random();
|
auto sk = libzcash::SproutSpendingKey::random();
|
||||||
wallet.AddSproutSpendingKey(sk);
|
wallet.AddSproutSpendingKey(sk);
|
||||||
|
|
||||||
|
@ -354,9 +355,6 @@ TEST(WalletTests, FindUnspentSproutNotes) {
|
||||||
mapBlockIndex.erase(blockHash);
|
mapBlockIndex.erase(blockHash);
|
||||||
mapBlockIndex.erase(blockHash2);
|
mapBlockIndex.erase(blockHash2);
|
||||||
mapBlockIndex.erase(blockHash3);
|
mapBlockIndex.erase(blockHash3);
|
||||||
|
|
||||||
// Revert to default
|
|
||||||
RegtestDeactivateSapling();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -377,8 +375,6 @@ TEST(WalletTests, SetSproutNoteAddrsInCWalletTx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
|
TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
|
||||||
SelectParams(CBaseChainParams::REGTEST);
|
|
||||||
|
|
||||||
std::vector<libzcash::Zip212Enabled> zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212};
|
std::vector<libzcash::Zip212Enabled> zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212};
|
||||||
const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy};
|
const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy};
|
||||||
void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy};
|
void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy};
|
||||||
|
@ -468,6 +464,7 @@ TEST(WalletTests, SetInvalidSaplingNoteDataInCWalletTx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, CheckSproutNoteCommitmentAgainstNotePlaintext) {
|
TEST(WalletTests, CheckSproutNoteCommitmentAgainstNotePlaintext) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
CWallet wallet(Params());
|
CWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
|
@ -492,6 +489,7 @@ TEST(WalletTests, CheckSproutNoteCommitmentAgainstNotePlaintext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, GetSproutNoteNullifier) {
|
TEST(WalletTests, GetSproutNoteNullifier) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
CWallet wallet(Params());
|
CWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
|
@ -527,7 +525,6 @@ TEST(WalletTests, GetSproutNoteNullifier) {
|
||||||
|
|
||||||
TEST(WalletTests, FindMySaplingNotes) {
|
TEST(WalletTests, FindMySaplingNotes) {
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
|
@ -562,6 +559,7 @@ TEST(WalletTests, FindMySaplingNotes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, FindMySproutNotes) {
|
TEST(WalletTests, FindMySproutNotes) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
CWallet wallet(Params());
|
CWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
|
@ -588,8 +586,10 @@ TEST(WalletTests, FindMySproutNotes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, FindMySproutNotesInEncryptedWallet) {
|
TEST(WalletTests, FindMySproutNotesInEncryptedWallet) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
uint256 r {GetRandHash()};
|
uint256 r {GetRandHash()};
|
||||||
CKeyingMaterial vMasterKey (r.begin(), r.end());
|
CKeyingMaterial vMasterKey (r.begin(), r.end());
|
||||||
|
|
||||||
|
@ -619,6 +619,7 @@ TEST(WalletTests, FindMySproutNotesInEncryptedWallet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, GetConflictedSproutNotes) {
|
TEST(WalletTests, GetConflictedSproutNotes) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
CWallet wallet(Params());
|
CWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
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
|
// Generate note A and spend to create note B, from which we spend to create two conflicting transactions
|
||||||
TEST(WalletTests, GetConflictedSaplingNotes) {
|
TEST(WalletTests, GetConflictedSaplingNotes) {
|
||||||
SelectParams(CBaseChainParams::REGTEST);
|
|
||||||
|
|
||||||
std::vector<libzcash::Zip212Enabled> zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212};
|
std::vector<libzcash::Zip212Enabled> zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212};
|
||||||
const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy};
|
const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy};
|
||||||
void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy};
|
void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy};
|
||||||
|
@ -779,6 +778,7 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, SproutNullifierIsSpent) {
|
TEST(WalletTests, SproutNullifierIsSpent) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
CWallet wallet(Params());
|
CWallet wallet(Params());
|
||||||
LOCK2(cs_main, wallet.cs_wallet);
|
LOCK2(cs_main, wallet.cs_wallet);
|
||||||
|
|
||||||
|
@ -821,7 +821,6 @@ TEST(WalletTests, SproutNullifierIsSpent) {
|
||||||
|
|
||||||
TEST(WalletTests, SaplingNullifierIsSpent) {
|
TEST(WalletTests, SaplingNullifierIsSpent) {
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK2(cs_main, wallet.cs_wallet);
|
LOCK2(cs_main, wallet.cs_wallet);
|
||||||
|
|
||||||
|
@ -878,6 +877,7 @@ TEST(WalletTests, SaplingNullifierIsSpent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, NavigateFromSproutNullifierToNote) {
|
TEST(WalletTests, NavigateFromSproutNullifierToNote) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
CWallet wallet(Params());
|
CWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
|
@ -906,7 +906,6 @@ TEST(WalletTests, NavigateFromSproutNullifierToNote) {
|
||||||
|
|
||||||
TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
|
TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK2(cs_main, wallet.cs_wallet);
|
LOCK2(cs_main, wallet.cs_wallet);
|
||||||
|
|
||||||
|
@ -998,6 +997,7 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, SpentSproutNoteIsFromMe) {
|
TEST(WalletTests, SpentSproutNoteIsFromMe) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
CWallet wallet(Params());
|
CWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
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.
|
// Create note A, spend A to create note B, spend and verify note B is from me.
|
||||||
TEST(WalletTests, SpentSaplingNoteIsFromMe) {
|
TEST(WalletTests, SpentSaplingNoteIsFromMe) {
|
||||||
SelectParams(CBaseChainParams::REGTEST);
|
|
||||||
|
|
||||||
std::vector<libzcash::Zip212Enabled> zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212};
|
std::vector<libzcash::Zip212Enabled> zip_212_enabled = {libzcash::Zip212Enabled::BeforeZip212, libzcash::Zip212Enabled::AfterZip212};
|
||||||
const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy};
|
const Consensus::Params& (*activations [])() = {RegtestActivateSapling, RegtestActivateCanopy};
|
||||||
void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy};
|
void (*deactivations [])() = {RegtestDeactivateSapling, RegtestDeactivateCanopy};
|
||||||
|
@ -1238,8 +1236,10 @@ TEST(WalletTests, CachedWitnessesEmptyChain) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, CachedWitnessesChainTip) {
|
TEST(WalletTests, CachedWitnessesChainTip) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
std::pair<uint256, uint256> anchors1;
|
std::pair<uint256, uint256> anchors1;
|
||||||
CBlock block1;
|
CBlock block1;
|
||||||
SproutMerkleTree sproutTree;
|
SproutMerkleTree sproutTree;
|
||||||
|
@ -1341,8 +1341,10 @@ TEST(WalletTests, CachedWitnessesChainTip) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, CachedWitnessesDecrementFirst) {
|
TEST(WalletTests, CachedWitnessesDecrementFirst) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
SproutMerkleTree sproutTree;
|
SproutMerkleTree sproutTree;
|
||||||
SaplingMerkleTree saplingTree;
|
SaplingMerkleTree saplingTree;
|
||||||
|
|
||||||
|
@ -1422,8 +1424,10 @@ TEST(WalletTests, CachedWitnessesDecrementFirst) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, CachedWitnessesCleanIndex) {
|
TEST(WalletTests, CachedWitnessesCleanIndex) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
std::vector<CBlock> blocks;
|
std::vector<CBlock> blocks;
|
||||||
std::vector<CBlockIndex> indices;
|
std::vector<CBlockIndex> indices;
|
||||||
std::vector<JSOutPoint> sproutNotes;
|
std::vector<JSOutPoint> sproutNotes;
|
||||||
|
@ -1510,6 +1514,7 @@ TEST(WalletTests, CachedWitnessesCleanIndex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, ClearNoteWitnessCache) {
|
TEST(WalletTests, ClearNoteWitnessCache) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
|
@ -1577,8 +1582,10 @@ TEST(WalletTests, ClearNoteWitnessCache) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, WriteWitnessCache) {
|
TEST(WalletTests, WriteWitnessCache) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
MockWalletDB walletdb;
|
MockWalletDB walletdb;
|
||||||
CBlockLocator loc;
|
CBlockLocator loc;
|
||||||
|
|
||||||
|
@ -1664,9 +1671,9 @@ TEST(WalletTests, WriteWitnessCache) {
|
||||||
|
|
||||||
TEST(WalletTests, SetBestChainIgnoresTxsWithoutShieldedData) {
|
TEST(WalletTests, SetBestChainIgnoresTxsWithoutShieldedData) {
|
||||||
SelectParams(CBaseChainParams::REGTEST);
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
|
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
MockWalletDB walletdb;
|
MockWalletDB walletdb;
|
||||||
CBlockLocator loc;
|
CBlockLocator loc;
|
||||||
|
|
||||||
|
@ -1746,8 +1753,10 @@ TEST(WalletTests, SetBestChainIgnoresTxsWithoutShieldedData) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, UpdateSproutNullifierNoteMap) {
|
TEST(WalletTests, UpdateSproutNullifierNoteMap) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
uint256 r {GetRandHash()};
|
uint256 r {GetRandHash()};
|
||||||
CKeyingMaterial vMasterKey (r.begin(), r.end());
|
CKeyingMaterial vMasterKey (r.begin(), r.end());
|
||||||
|
|
||||||
|
@ -1782,6 +1791,7 @@ TEST(WalletTests, UpdateSproutNullifierNoteMap) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, UpdatedSproutNoteData) {
|
TEST(WalletTests, UpdatedSproutNoteData) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
|
@ -1831,7 +1841,6 @@ TEST(WalletTests, UpdatedSproutNoteData) {
|
||||||
|
|
||||||
TEST(WalletTests, UpdatedSaplingNoteData) {
|
TEST(WalletTests, UpdatedSaplingNoteData) {
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK2(cs_main, wallet.cs_wallet);
|
LOCK2(cs_main, wallet.cs_wallet);
|
||||||
|
|
||||||
|
@ -1941,6 +1950,7 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, MarkAffectedSproutTransactionsDirty) {
|
TEST(WalletTests, MarkAffectedSproutTransactionsDirty) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
|
@ -1974,7 +1984,6 @@ TEST(WalletTests, MarkAffectedSproutTransactionsDirty) {
|
||||||
|
|
||||||
TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
|
TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK2(cs_main, wallet.cs_wallet);
|
LOCK2(cs_main, wallet.cs_wallet);
|
||||||
|
|
||||||
|
@ -2084,6 +2093,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, SproutNoteLocking) {
|
TEST(WalletTests, SproutNoteLocking) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
|
@ -2118,8 +2128,10 @@ TEST(WalletTests, SproutNoteLocking) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(WalletTests, SaplingNoteLocking) {
|
TEST(WalletTests, SaplingNoteLocking) {
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
TestWallet wallet(Params());
|
TestWallet wallet(Params());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
SaplingOutPoint sop1 {uint256(), 1};
|
SaplingOutPoint sop1 {uint256(), 1};
|
||||||
SaplingOutPoint sop2 {uint256(), 2};
|
SaplingOutPoint sop2 {uint256(), 2};
|
||||||
|
|
||||||
|
@ -2149,3 +2161,74 @@ TEST(WalletTests, SaplingNoteLocking) {
|
||||||
EXPECT_FALSE(wallet.IsLockedNote(sop1));
|
EXPECT_FALSE(wallet.IsLockedNote(sop1));
|
||||||
EXPECT_FALSE(wallet.IsLockedNote(sop2));
|
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();
|
||||||
|
}
|
||||||
|
|
|
@ -870,7 +870,7 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp)
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid viewing key");
|
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);
|
UniValue result(UniValue::VOBJ);
|
||||||
const string strAddress = keyIO.EncodePaymentAddress(addrInfo.second);
|
const string strAddress = keyIO.EncodePaymentAddress(addrInfo.second);
|
||||||
result.pushKV("type", addrInfo.first);
|
result.pushKV("type", addrInfo.first);
|
||||||
|
|
|
@ -172,7 +172,6 @@ std::pair<SaplingExtendedSpendingKey, bool> CWallet::GenerateLegacySaplingZKey(u
|
||||||
} else {
|
} else {
|
||||||
return std::make_pair(xsk.first, false);
|
return std::make_pair(xsk.first, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add spending key to keystore
|
// Add spending key to keystore
|
||||||
|
@ -281,27 +280,14 @@ CPubKey CWallet::GenerateNewKey()
|
||||||
BIP44CoinType(),
|
BIP44CoinType(),
|
||||||
ZCASH_LEGACY_ACCOUNT).value();
|
ZCASH_LEGACY_ACCOUNT).value();
|
||||||
|
|
||||||
std::optional<std::pair<CExtKey, HDKeyPath>> extKey = std::nullopt;
|
std::optional<std::pair<CKey, HDKeyPath>> extKey = std::nullopt;
|
||||||
do {
|
do {
|
||||||
extKey = accountChains.DeriveExternal(hdChain.GetLegacyTKeyCounter());
|
extKey = accountChains.DeriveExternal(hdChain.GetLegacyTKeyCounter());
|
||||||
hdChain.IncrementLegacyTKeyCounter();
|
hdChain.IncrementLegacyTKeyCounter();
|
||||||
// if we did not successfully generate a key, try again.
|
// if we did not successfully generate a key, try again.
|
||||||
} while (!extKey.has_value());
|
} while (!extKey.has_value());
|
||||||
|
|
||||||
CKey secret = extKey.value().first.key;
|
auto pubkey = AddTransparentSecretKey(seed.Fingerprint(), extKey.value());
|
||||||
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");
|
|
||||||
|
|
||||||
// Update the persisted chain information
|
// Update the persisted chain information
|
||||||
if (fFileBacked && !CWalletDB(strWalletFile).WriteMnemonicHDChain(hdChain)) {
|
if (fFileBacked && !CWalletDB(strWalletFile).WriteMnemonicHDChain(hdChain)) {
|
||||||
|
@ -311,7 +297,31 @@ CPubKey CWallet::GenerateNewKey()
|
||||||
return pubkey;
|
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
|
AssertLockHeld(cs_wallet); // mapKeyMetadata
|
||||||
if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey))
|
if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey))
|
||||||
|
@ -328,11 +338,13 @@ bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
|
||||||
|
|
||||||
if (!fFileBacked)
|
if (!fFileBacked)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!IsCrypted()) {
|
if (!IsCrypted()) {
|
||||||
return CWalletDB(strWalletFile).WriteKey(pubkey,
|
return CWalletDB(strWalletFile).WriteKey(pubkey,
|
||||||
secret.GetPrivKey(),
|
secret.GetPrivKey(),
|
||||||
mapKeyMetadata[pubkey.GetID()]);
|
mapKeyMetadata[pubkey.GetID()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,16 +419,18 @@ bool CWallet::AddCryptedSaplingSpendingKey(const libzcash::SaplingExtendedFullVi
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ZcashdUnifiedSpendingKey CWallet::GenerateNewUnifiedSpendingKey() {
|
std::pair<ZcashdUnifiedSpendingKey, libzcash::AccountId> CWallet::GenerateNewUnifiedSpendingKey() {
|
||||||
AssertLockHeld(cs_wallet);
|
AssertLockHeld(cs_wallet);
|
||||||
|
|
||||||
if (!mnemonicHDChain.has_value()) {
|
if (!mnemonicHDChain.has_value()) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"CWallet::GenerateNewUnifiedSpendingKey(): Wallet is missing mnemonic seed metadata.");
|
"CWallet::GenerateNewUnifiedSpendingKey(): Wallet is missing mnemonic seed metadata.");
|
||||||
}
|
}
|
||||||
|
|
||||||
CHDChain& hdChain = mnemonicHDChain.value();
|
CHDChain& hdChain = mnemonicHDChain.value();
|
||||||
while (true) {
|
while (true) {
|
||||||
auto usk = GenerateUnifiedSpendingKeyForAccount(hdChain.GetAccountCounter());
|
auto accountId = hdChain.GetAccountCounter();
|
||||||
|
auto usk = GenerateUnifiedSpendingKeyForAccount(accountId);
|
||||||
hdChain.IncrementAccountCounter();
|
hdChain.IncrementAccountCounter();
|
||||||
|
|
||||||
if (usk.has_value()) {
|
if (usk.has_value()) {
|
||||||
|
@ -426,12 +440,15 @@ ZcashdUnifiedSpendingKey CWallet::GenerateNewUnifiedSpendingKey() {
|
||||||
"CWallet::GenerateNewUnifiedSpendingKey(): Writing HD chain model failed");
|
"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();
|
auto seed = GetMnemonicSeed();
|
||||||
if (!seed.has_value()) {
|
if (!seed.has_value()) {
|
||||||
throw std::runtime_error(std::string(__func__) + ": Wallet has no mnemonic HD seed.");
|
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);
|
auto usk = ZcashdUnifiedSpendingKey::ForAccount(seed.value(), BIP44CoinType(), accountId);
|
||||||
if (usk.has_value()) {
|
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 {
|
} else {
|
||||||
return std::nullopt;
|
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)
|
void CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta)
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs_wallet); // mapKeyMetadata
|
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
|
// Shielded key and address generalizations
|
||||||
//
|
//
|
||||||
|
@ -5542,7 +5827,7 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedS
|
||||||
if (m_wallet->HaveSaplingSpendingKey(extfvk)) {
|
if (m_wallet->HaveSaplingSpendingKey(extfvk)) {
|
||||||
return KeyAlreadyExists;
|
return KeyAlreadyExists;
|
||||||
} else {
|
} else {
|
||||||
if (!m_wallet-> AddSaplingZKey(sk)) {
|
if (!m_wallet->AddSaplingZKey(sk)) {
|
||||||
return KeyNotAdded;
|
return KeyNotAdded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5556,7 +5841,7 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedS
|
||||||
if (hdKeypath.has_value()) {
|
if (hdKeypath.has_value()) {
|
||||||
m_wallet->mapSaplingZKeyMetadata[ivk].hdKeypath = hdKeypath.value();
|
m_wallet->mapSaplingZKeyMetadata[ivk].hdKeypath = hdKeypath.value();
|
||||||
}
|
}
|
||||||
if (seedFpStr) {
|
if (seedFpStr.has_value()) {
|
||||||
uint256 seedFp;
|
uint256 seedFp;
|
||||||
seedFp.SetHex(seedFpStr.value());
|
seedFp.SetHex(seedFpStr.value());
|
||||||
m_wallet->mapSaplingZKeyMetadata[ivk].seedFp = seedFp;
|
m_wallet->mapSaplingZKeyMetadata[ivk].seedFp = seedFp;
|
||||||
|
@ -5569,3 +5854,69 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedS
|
||||||
KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::InvalidEncoding& no) const {
|
KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::InvalidEncoding& no) const {
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key");
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -402,6 +402,20 @@ public:
|
||||||
bool AcceptToMemoryPool(bool fLimitFree=true, bool fRejectAbsurdFee=true);
|
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.
|
* 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.
|
* It includes any unrecorded transactions needed to link it back to the block chain.
|
||||||
|
@ -625,9 +639,6 @@ public:
|
||||||
std::set<uint256> GetConflicts() const;
|
std::set<uint256> GetConflicts() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class COutput
|
class COutput
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -644,9 +655,6 @@ public:
|
||||||
std::string ToString() const;
|
std::string ToString() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** Private key that includes an expiration date in case it never gets used. */
|
/** Private key that includes an expiration date in case it never gets used. */
|
||||||
class CWalletKey
|
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,
|
* 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.
|
* and provides the ability to create new transactions.
|
||||||
|
@ -702,8 +784,13 @@ private:
|
||||||
int nSetChainUpdates;
|
int nSetChainUpdates;
|
||||||
bool fBroadcastTransactions;
|
bool fBroadcastTransactions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map from a protocol-specific transaction output identifier to
|
||||||
|
* a txid.
|
||||||
|
*/
|
||||||
template <class T>
|
template <class T>
|
||||||
using TxSpendMap = std::multimap<T, uint256>;
|
using TxSpendMap = std::multimap<T, uint256>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to keep track of spent outpoints, and
|
* Used to keep track of spent outpoints, and
|
||||||
* detect and report conflicts (double-spends or
|
* detect and report conflicts (double-spends or
|
||||||
|
@ -711,6 +798,7 @@ private:
|
||||||
*/
|
*/
|
||||||
typedef TxSpendMap<COutPoint> TxSpends;
|
typedef TxSpendMap<COutPoint> TxSpends;
|
||||||
TxSpends mapTxSpends;
|
TxSpends mapTxSpends;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to keep track of spent Notes, and
|
* Used to keep track of spent Notes, and
|
||||||
* detect and report conflicts (double-spends).
|
* detect and report conflicts (double-spends).
|
||||||
|
@ -803,6 +891,11 @@ private:
|
||||||
void SyncMetaData(std::pair<typename TxSpendMap<T>::iterator, typename TxSpendMap<T>::iterator>);
|
void SyncMetaData(std::pair<typename TxSpendMap<T>::iterator, typename TxSpendMap<T>::iterator>);
|
||||||
void ChainTipAdded(const CBlockIndex *pindex, const CBlock *pblock, SproutMerkleTree sproutTree, SaplingMerkleTree saplingTree);
|
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:
|
protected:
|
||||||
bool UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx);
|
bool UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx);
|
||||||
void MarkAffectedTransactionsDirty(const CTransaction& tx);
|
void MarkAffectedTransactionsDirty(const CTransaction& tx);
|
||||||
|
@ -828,8 +921,11 @@ public:
|
||||||
|
|
||||||
std::set<int64_t> setKeyPool;
|
std::set<int64_t> setKeyPool;
|
||||||
std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
|
std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
|
||||||
|
|
||||||
std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapSproutZKeyMetadata;
|
std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapSproutZKeyMetadata;
|
||||||
std::map<libzcash::SaplingIncomingViewingKey, CKeyMetadata> mapSaplingZKeyMetadata;
|
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;
|
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
|
||||||
MasterKeyMap mapMasterKeys;
|
MasterKeyMap mapMasterKeys;
|
||||||
|
@ -1074,7 +1170,8 @@ public:
|
||||||
//! full viewing key to disk. Inside CCryptoKeyStore and CBasicKeyStore,
|
//! full viewing key to disk. Inside CCryptoKeyStore and CBasicKeyStore,
|
||||||
//! CBasicKeyStore::AddSaplingFullViewingKey is called directly when adding a
|
//! CBasicKeyStore::AddSaplingFullViewingKey is called directly when adding a
|
||||||
//! full viewing key to the keystore, to avoid this override.
|
//! full viewing key to the keystore, to avoid this override.
|
||||||
bool AddSaplingFullViewingKey(const libzcash::SaplingExtendedFullViewingKey &extfvk);
|
bool AddSaplingFullViewingKey(
|
||||||
|
const libzcash::SaplingExtendedFullViewingKey &extfvk);
|
||||||
bool AddSaplingIncomingViewingKey(
|
bool AddSaplingIncomingViewingKey(
|
||||||
const libzcash::SaplingIncomingViewingKey &ivk,
|
const libzcash::SaplingIncomingViewingKey &ivk,
|
||||||
const libzcash::SaplingPaymentAddress &addr);
|
const libzcash::SaplingPaymentAddress &addr);
|
||||||
|
@ -1096,11 +1193,43 @@ public:
|
||||||
bool LoadCryptedSaplingZKey(const libzcash::SaplingExtendedFullViewingKey &extfvk,
|
bool LoadCryptedSaplingZKey(const libzcash::SaplingExtendedFullViewingKey &extfvk,
|
||||||
const std::vector<unsigned char> &vchCryptedSecret);
|
const std::vector<unsigned char> &vchCryptedSecret);
|
||||||
|
|
||||||
/**
|
//
|
||||||
* Unified keys & addresses
|
// Unified keys & addresses
|
||||||
*/
|
//
|
||||||
libzcash::ZcashdUnifiedSpendingKey GenerateNewUnifiedSpendingKey();
|
|
||||||
std::optional<libzcash::ZcashdUnifiedSpendingKey> GenerateUnifiedSpendingKeyForAccount(libzcash::AccountId accountId);
|
//! 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
|
* Increment the next transaction order id
|
||||||
|
@ -1512,5 +1641,18 @@ public:
|
||||||
KeyAddResult operator()(const libzcash::InvalidEncoding& no) const;
|
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
|
#endif // BITCOIN_WALLET_WALLET_H
|
||||||
|
|
|
@ -169,6 +169,7 @@ bool CWalletDB::WriteZKey(const libzcash::SproutPaymentAddress& addr, const libz
|
||||||
// pair is: tuple_key("zkey", paymentaddress) --> secretkey
|
// pair is: tuple_key("zkey", paymentaddress) --> secretkey
|
||||||
return Write(std::make_pair(std::string("zkey"), addr), key, false);
|
return Write(std::make_pair(std::string("zkey"), addr), key, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWalletDB::WriteSaplingZKey(const libzcash::SaplingIncomingViewingKey &ivk,
|
bool CWalletDB::WriteSaplingZKey(const libzcash::SaplingIncomingViewingKey &ivk,
|
||||||
const libzcash::SaplingExtendedSpendingKey &key,
|
const libzcash::SaplingExtendedSpendingKey &key,
|
||||||
const CKeyMetadata &keyMeta)
|
const CKeyMetadata &keyMeta)
|
||||||
|
@ -216,6 +217,33 @@ bool CWalletDB::EraseSaplingExtendedFullViewingKey(
|
||||||
return Erase(std::make_pair(std::string("sapextfvk"), extfvk));
|
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)
|
bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript)
|
||||||
{
|
{
|
||||||
nWalletDBUpdateCounter++;
|
nWalletDBUpdateCounter++;
|
||||||
|
@ -632,6 +660,62 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||||
{
|
{
|
||||||
ssValue >> pwallet->vchDefaultKey;
|
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")
|
else if (strType == "pool")
|
||||||
{
|
{
|
||||||
int64_t nIndex;
|
int64_t nIndex;
|
||||||
|
|
|
@ -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 */
|
/** Access to the wallet database */
|
||||||
class CWalletDB : public CDB
|
class CWalletDB : public CDB
|
||||||
{
|
{
|
||||||
|
@ -249,6 +384,12 @@ public:
|
||||||
bool WriteSaplingExtendedFullViewingKey(const libzcash::SaplingExtendedFullViewingKey &extfvk);
|
bool WriteSaplingExtendedFullViewingKey(const libzcash::SaplingExtendedFullViewingKey &extfvk);
|
||||||
bool EraseSaplingExtendedFullViewingKey(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 void IncrementUpdateCounter();
|
||||||
static unsigned int GetUpdateCounter();
|
static unsigned int GetUpdateCounter();
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -12,6 +12,10 @@ const uint8_t ZCASH_UA_TYPECODE_SAPLING = 0x02;
|
||||||
|
|
||||||
namespace libzcash {
|
namespace libzcash {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Unified Addresses
|
||||||
|
//
|
||||||
|
|
||||||
std::vector<const Receiver*> UnifiedAddress::GetSorted() const {
|
std::vector<const Receiver*> UnifiedAddress::GetSorted() const {
|
||||||
std::vector<const libzcash::Receiver*> sorted;
|
std::vector<const libzcash::Receiver*> sorted;
|
||||||
for (const auto& receiver : receivers) {
|
for (const auto& receiver : receivers) {
|
||||||
|
@ -39,6 +43,37 @@ bool UnifiedAddress::AddReceiver(Receiver receiver) {
|
||||||
return true;
|
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 {
|
std::pair<std::string, PaymentAddress> AddressInfoFromSpendingKey::operator()(const SproutSpendingKey &sk) const {
|
||||||
return std::make_pair("sprout", sk.address());
|
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 {
|
std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(const UnifiedFullViewingKey &ufvk) const {
|
||||||
return std::make_pair(
|
return std::make_pair(
|
||||||
"unified",
|
"unified",
|
||||||
ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(ufvk)
|
ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(keyConstants, ufvk)
|
||||||
.FindAddress(diversifier_index_t(0))
|
.FindAddress(diversifier_index_t(0))
|
||||||
|
.value() //safe because we're searching from 0
|
||||||
.first
|
.first
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -189,6 +225,10 @@ std::set<libzcash::RawAddress> GetRawAddresses::operator()(
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Unified full viewing keys
|
||||||
|
//
|
||||||
|
|
||||||
std::optional<libzcash::UnifiedFullViewingKey> libzcash::UnifiedFullViewingKey::Decode(
|
std::optional<libzcash::UnifiedFullViewingKey> libzcash::UnifiedFullViewingKey::Decode(
|
||||||
const std::string& str,
|
const std::string& str,
|
||||||
const KeyConstants& keyConstants) {
|
const KeyConstants& keyConstants) {
|
||||||
|
@ -280,3 +320,11 @@ libzcash::UnifiedFullViewingKey libzcash::UnifiedFullViewingKey::FromZcashdUFVK(
|
||||||
}
|
}
|
||||||
return result.value();
|
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());
|
||||||
|
}
|
||||||
|
|
|
@ -3,8 +3,10 @@
|
||||||
|
|
||||||
#include "key_constants.h"
|
#include "key_constants.h"
|
||||||
#include "pubkey.h"
|
#include "pubkey.h"
|
||||||
|
#include "key_constants.h"
|
||||||
#include "script/script.h"
|
#include "script/script.h"
|
||||||
#include "uint256.h"
|
#include "uint256.h"
|
||||||
|
#include "util/match.h"
|
||||||
#include "zcash/address/orchard.hpp"
|
#include "zcash/address/orchard.hpp"
|
||||||
#include "zcash/address/sapling.hpp"
|
#include "zcash/address/sapling.hpp"
|
||||||
#include "zcash/address/sprout.hpp"
|
#include "zcash/address/sprout.hpp"
|
||||||
|
@ -14,6 +16,9 @@
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include <rust/unified_keys.h>
|
#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 {
|
namespace libzcash {
|
||||||
|
|
||||||
/** Protocol addresses that can receive funds in a transaction. */
|
/** Protocol addresses that can receive funds in a transaction. */
|
||||||
|
@ -112,6 +117,26 @@ public:
|
||||||
|
|
||||||
const std::vector<Receiver>& GetReceiversAsParsed() const { return receivers; }
|
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 {
|
ReceiverIterator begin() const {
|
||||||
return ReceiverIterator(GetSorted(), 0);
|
return ReceiverIterator(GetSorted(), 0);
|
||||||
}
|
}
|
||||||
|
@ -122,9 +147,18 @@ public:
|
||||||
return receivers.size();
|
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) {
|
friend inline bool operator==(const UnifiedAddress& a, const UnifiedAddress& b) {
|
||||||
return a.receivers == b.receivers;
|
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) {
|
friend inline bool operator<(const UnifiedAddress& a, const UnifiedAddress& b) {
|
||||||
return a.receivers < b.receivers;
|
return a.receivers < b.receivers;
|
||||||
}
|
}
|
||||||
|
@ -147,9 +181,10 @@ private:
|
||||||
|
|
||||||
friend class UnifiedFullViewingKeyBuilder;
|
friend class UnifiedFullViewingKeyBuilder;
|
||||||
public:
|
public:
|
||||||
static std::optional<UnifiedFullViewingKey> Decode(
|
UnifiedFullViewingKey(UnifiedFullViewingKey&& key) : inner(std::move(key.inner)) {}
|
||||||
const std::string& str,
|
|
||||||
const KeyConstants& keyConstants);
|
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
|
* This method should only be used for serialization of unified full
|
||||||
|
@ -161,16 +196,17 @@ public:
|
||||||
*/
|
*/
|
||||||
static UnifiedFullViewingKey FromZcashdUFVK(const ZcashdUnifiedFullViewingKey&);
|
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::string Encode(const KeyConstants& keyConstants) const;
|
||||||
|
|
||||||
std::optional<SaplingDiversifiableFullViewingKey> GetSaplingKey() const;
|
std::optional<SaplingDiversifiableFullViewingKey> GetSaplingKey() const;
|
||||||
|
|
||||||
std::optional<CChainablePubKey> GetTransparentKey() const;
|
std::optional<CChainablePubKey> GetTransparentKey() const;
|
||||||
|
|
||||||
UnifiedFullViewingKey(UnifiedFullViewingKey&& key) : inner(std::move(key.inner)) {}
|
UFVKId GetKeyID(const KeyConstants& keyConstants) const;
|
||||||
|
|
||||||
UnifiedFullViewingKey(const UnifiedFullViewingKey& key) :
|
|
||||||
inner(unified_full_viewing_key_clone(key.inner.get()), unified_full_viewing_key_free) {}
|
|
||||||
|
|
||||||
UnifiedFullViewingKey& operator=(UnifiedFullViewingKey&& key)
|
UnifiedFullViewingKey& operator=(UnifiedFullViewingKey&& key)
|
||||||
{
|
{
|
||||||
|
@ -228,7 +264,11 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
class AddressInfoFromViewingKey {
|
class AddressInfoFromViewingKey {
|
||||||
|
private:
|
||||||
|
const KeyConstants& keyConstants;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
AddressInfoFromViewingKey(const KeyConstants& keyConstants): keyConstants(keyConstants) {}
|
||||||
std::pair<std::string, PaymentAddress> operator()(const SproutViewingKey&) const;
|
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 struct SaplingExtendedFullViewingKey&) const;
|
||||||
std::pair<std::string, PaymentAddress> operator()(const UnifiedFullViewingKey&) const;
|
std::pair<std::string, PaymentAddress> operator()(const UnifiedFullViewingKey&) const;
|
||||||
|
|
|
@ -4,6 +4,10 @@
|
||||||
|
|
||||||
#include "bip44.h"
|
#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) {
|
std::optional<std::pair<CExtKey, HDKeyPath>> libzcash::DeriveBip44TransparentAccountKey(const HDSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId) {
|
||||||
auto rawSeed = seed.RawSeed();
|
auto rawSeed = seed.RawSeed();
|
||||||
auto m = CExtKey::Master(rawSeed.data(), rawSeed.size());
|
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);
|
auto result = m_44h_cth.value().Derive(accountId | HARDENED_KEY_LIMIT);
|
||||||
if (!result.has_value()) return std::nullopt;
|
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);
|
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());
|
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);
|
auto childKey = external.Derive(addrIndex);
|
||||||
if (!childKey.has_value()) return std::nullopt;
|
if (!childKey.has_value()) return std::nullopt;
|
||||||
|
|
||||||
auto hdKeypath = "m/44'/"
|
auto hdKeypath = Bip44TransparentAccountKeyPath(bip44CoinType, accountId) + "/0/" + std::to_string(addrIndex);
|
||||||
+ std::to_string(bip44CoinType) + "'/"
|
|
||||||
+ std::to_string(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);
|
auto childKey = internal.Derive(addrIndex);
|
||||||
if (!childKey.has_value()) return std::nullopt;
|
if (!childKey.has_value()) return std::nullopt;
|
||||||
|
|
||||||
auto hdKeypath = "m/44'/"
|
auto hdKeypath = Bip44TransparentAccountKeyPath(bip44CoinType, accountId) + "/1/" + std::to_string(addrIndex);
|
||||||
+ std::to_string(bip44CoinType) + "'/"
|
|
||||||
+ std::to_string(accountId) + "'/"
|
|
||||||
+ "1/"
|
|
||||||
+ std::to_string(addrIndex);
|
|
||||||
|
|
||||||
return std::make_pair(childKey.value(), hdKeypath);
|
return std::make_pair(childKey.value().key, hdKeypath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,12 @@
|
||||||
|
|
||||||
namespace libzcash {
|
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);
|
std::optional<std::pair<CExtKey, HDKeyPath>> DeriveBip44TransparentAccountKey(const HDSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId);
|
||||||
|
|
||||||
class Bip44AccountChains {
|
class Bip44AccountChains {
|
||||||
|
@ -27,8 +33,19 @@ public:
|
||||||
uint32_t bip44CoinType,
|
uint32_t bip44CoinType,
|
||||||
libzcash::AccountId accountId);
|
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
|
} //namespace libzcash
|
||||||
|
|
|
@ -12,9 +12,27 @@ using namespace libzcash;
|
||||||
// Unified Keys
|
// 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;
|
ZcashdUnifiedSpendingKey usk;
|
||||||
usk.accountId = accountId;
|
|
||||||
|
|
||||||
auto transparentKey = DeriveBip44TransparentAccountKey(seed, bip44CoinType, accountId);
|
auto transparentKey = DeriveBip44TransparentAccountKey(seed, bip44CoinType, accountId);
|
||||||
if (!transparentKey.has_value()) return std::nullopt;
|
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);
|
auto saplingKey = SaplingExtendedSpendingKey::ForAccount(seed, bip44CoinType, accountId);
|
||||||
usk.saplingKey = saplingKey.first;
|
usk.saplingKey = saplingKey.first;
|
||||||
|
|
||||||
return std::make_pair(usk, saplingKey.second);
|
return usk;
|
||||||
}
|
}
|
||||||
|
|
||||||
ZcashdUnifiedFullViewingKey ZcashdUnifiedSpendingKey::ToFullViewingKey() const {
|
UnifiedFullViewingKey ZcashdUnifiedSpendingKey::ToFullViewingKey() const {
|
||||||
ZcashdUnifiedFullViewingKey ufvk;
|
UnifiedFullViewingKeyBuilder builder;
|
||||||
|
|
||||||
if (transparentKey.has_value()) {
|
auto extPubKey = transparentKey.Neuter();
|
||||||
auto extPubKey = transparentKey.value().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?
|
// This call to .value() is safe as ZcashdUnifiedSpendingKey values are always
|
||||||
// Is that already guaranteed?
|
// constructed to contain all required components.
|
||||||
ufvk.transparentKey = CChainablePubKey::FromParts(extPubKey.chaincode, extPubKey.pubkey).value();
|
return builder.build().value();
|
||||||
}
|
|
||||||
|
|
||||||
if (saplingKey.has_value()) {
|
|
||||||
ufvk.saplingKey = saplingKey.value().ToXFVK();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ufvk;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ZcashdUnifiedFullViewingKey ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(
|
ZcashdUnifiedFullViewingKey ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(
|
||||||
|
const KeyConstants& keyConstants,
|
||||||
const UnifiedFullViewingKey& ufvk) {
|
const UnifiedFullViewingKey& ufvk) {
|
||||||
ZcashdUnifiedFullViewingKey result;
|
ZcashdUnifiedFullViewingKey result;
|
||||||
|
result.keyId = ufvk.GetKeyID(keyConstants);
|
||||||
|
|
||||||
auto transparentKey = ufvk.GetTransparentKey();
|
auto transparentKey = ufvk.GetTransparentKey();
|
||||||
if (transparentKey.has_value()) {
|
if (transparentKey.has_value()) {
|
||||||
|
@ -61,10 +75,16 @@ ZcashdUnifiedFullViewingKey ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingK
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<UnifiedAddress> ZcashdUnifiedFullViewingKey::Address(diversifier_index_t j) const {
|
std::optional<UnifiedAddress> ZcashdUnifiedFullViewingKey::Address(
|
||||||
UnifiedAddress ua;
|
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);
|
auto saplingAddress = saplingKey.value().Address(j);
|
||||||
if (saplingAddress.has_value()) {
|
if (saplingAddress.has_value()) {
|
||||||
ua.AddReceiver(saplingAddress.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();
|
const auto& tkey = transparentKey.value();
|
||||||
auto childIndex = j.ToTransparentChildIndex();
|
auto childIndex = j.ToTransparentChildIndex();
|
||||||
if (!childIndex.has_value()) return std::nullopt;
|
if (!childIndex.has_value()) return std::nullopt;
|
||||||
|
@ -96,13 +116,21 @@ std::optional<UnifiedAddress> ZcashdUnifiedFullViewingKey::Address(diversifier_i
|
||||||
return ua;
|
return ua;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<UnifiedAddress, diversifier_index_t> ZcashdUnifiedFullViewingKey::FindAddress(diversifier_index_t j) const {
|
std::optional<std::pair<UnifiedAddress, diversifier_index_t>> ZcashdUnifiedFullViewingKey::FindAddress(
|
||||||
auto addr = Address(j);
|
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()) {
|
while (!addr.has_value()) {
|
||||||
if (!j.increment())
|
if (!j0.increment() || (hasTransparent && !j0.ToTransparentChildIndex().has_value()))
|
||||||
throw std::runtime_error(std::string(__func__) + ": diversifier index overflow.");;
|
return std::nullopt;
|
||||||
addr = Address(j);
|
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});
|
||||||
|
}
|
||||||
|
|
|
@ -5,18 +5,47 @@
|
||||||
#ifndef ZCASH_ZCASH_ADDRESS_UNIFIED_H
|
#ifndef ZCASH_ZCASH_ADDRESS_UNIFIED_H
|
||||||
#define ZCASH_ZCASH_ADDRESS_UNIFIED_H
|
#define ZCASH_ZCASH_ADDRESS_UNIFIED_H
|
||||||
|
|
||||||
#include "zip32.h"
|
|
||||||
#include "bip44.h"
|
#include "bip44.h"
|
||||||
|
#include "key_constants.h"
|
||||||
|
#include "zip32.h"
|
||||||
|
|
||||||
namespace libzcash {
|
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 ZcashdUnifiedSpendingKey;
|
||||||
class ZcashdUnifiedFullViewingKey;
|
|
||||||
|
|
||||||
// prototypes for the classes handling ZIP-316 encoding (in Address.hpp)
|
// prototypes for the classes handling ZIP-316 encoding (in Address.hpp)
|
||||||
class UnifiedAddress;
|
class UnifiedAddress;
|
||||||
class UnifiedFullViewingKey;
|
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
|
* 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
|
* set of receiver types that are supported by zcashd. This type does not
|
||||||
|
@ -25,6 +54,7 @@ class UnifiedFullViewingKey;
|
||||||
*/
|
*/
|
||||||
class ZcashdUnifiedFullViewingKey {
|
class ZcashdUnifiedFullViewingKey {
|
||||||
private:
|
private:
|
||||||
|
UFVKId keyId;
|
||||||
std::optional<CChainablePubKey> transparentKey;
|
std::optional<CChainablePubKey> transparentKey;
|
||||||
std::optional<SaplingDiversifiableFullViewingKey> saplingKey;
|
std::optional<SaplingDiversifiableFullViewingKey> saplingKey;
|
||||||
|
|
||||||
|
@ -33,9 +63,16 @@ private:
|
||||||
friend class ZcashdUnifiedSpendingKey;
|
friend class ZcashdUnifiedSpendingKey;
|
||||||
public:
|
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 {
|
const std::optional<CChainablePubKey>& GetTransparentKey() const {
|
||||||
return transparentKey;
|
return transparentKey;
|
||||||
|
@ -45,9 +82,44 @@ public:
|
||||||
return saplingKey;
|
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 {
|
class ZcashdUnifiedSpendingKey {
|
||||||
private:
|
private:
|
||||||
libzcash::AccountId accountId;
|
CExtKey transparentKey;
|
||||||
std::optional<CExtKey> transparentKey;
|
SaplingExtendedSpendingKey saplingKey;
|
||||||
std::optional<SaplingExtendedSpendingKey> saplingKey;
|
|
||||||
|
|
||||||
ZcashdUnifiedSpendingKey() {}
|
ZcashdUnifiedSpendingKey() {}
|
||||||
public:
|
public:
|
||||||
static std::optional<std::pair<ZcashdUnifiedSpendingKey, HDKeyPath>> ForAccount(
|
static std::optional<ZcashdUnifiedSpendingKey> ForAccount(
|
||||||
const HDSeed& seed,
|
const HDSeed& seed,
|
||||||
uint32_t bip44CoinType,
|
uint32_t bip44CoinType,
|
||||||
libzcash::AccountId accountId);
|
libzcash::AccountId accountId);
|
||||||
|
|
||||||
const std::optional<CExtKey>& GetTransparentKey() const {
|
const CExtKey& GetTransparentKey() const {
|
||||||
return transparentKey;
|
return transparentKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::optional<SaplingExtendedSpendingKey>& GetSaplingExtendedSpendingKey() const {
|
const SaplingExtendedSpendingKey& GetSaplingExtendedSpendingKey() const {
|
||||||
return saplingKey;
|
return saplingKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
ZcashdUnifiedFullViewingKey ToFullViewingKey() const;
|
UnifiedFullViewingKey ToFullViewingKey() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} //namespace libzcash
|
} //namespace libzcash
|
||||||
|
|
|
@ -19,8 +19,6 @@ const unsigned char ZCASH_HD_SEED_FP_PERSONAL[BLAKE2bPersonalBytes] =
|
||||||
const unsigned char ZCASH_TADDR_OVK_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'};
|
{'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
|
uint256 HDSeed::Fingerprint() const
|
||||||
{
|
{
|
||||||
CBLAKE2bWriter h(SER_GETHASH, 0, ZCASH_HD_SEED_FP_PERSONAL);
|
CBLAKE2bWriter h(SER_GETHASH, 0, ZCASH_HD_SEED_FP_PERSONAL);
|
||||||
|
@ -48,12 +46,12 @@ uint256 ovkForShieldingFromTaddr(HDSeed& seed) {
|
||||||
|
|
||||||
namespace libzcash {
|
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
|
// ensure that the diversifier index is small enough for a t-addr
|
||||||
if (MAX_TRANSPARENT_CHILD_IDX < *this) {
|
if (MAX_TRANSPARENT_CHILD_IDX < *this) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
} else {
|
} 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
|
// Derive account key at the given account index
|
||||||
auto xsk = m_32h_cth.Derive(accountId | HARDENED_KEY_LIMIT);
|
auto xsk = m_32h_cth.Derive(accountId | HARDENED_KEY_LIMIT);
|
||||||
|
|
||||||
// Create new metadata
|
return std::make_pair(xsk, libzcash::Zip32AccountKeyPath(bip44CoinType, accountId));
|
||||||
auto hdKeypath = "m/32'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(accountId) + "'";
|
|
||||||
|
|
||||||
return std::make_pair(xsk, hdKeypath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<SaplingExtendedSpendingKey, HDKeyPath> SaplingExtendedSpendingKey::Legacy(const HDSeed& seed, uint32_t bip44CoinType, uint32_t addressIndex) {
|
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
|
// Derive key at the specified address index
|
||||||
auto xsk = m_32h_cth_l.Derive(addressIndex | HARDENED_KEY_LIMIT);
|
auto xsk = m_32h_cth_l.Derive(addressIndex | HARDENED_KEY_LIMIT);
|
||||||
|
|
||||||
// Create new metadata
|
return std::make_pair(xsk, libzcash::Zip32AccountKeyPath(bip44CoinType, ZCASH_LEGACY_ACCOUNT, addressIndex));
|
||||||
auto hdKeypath = "m/32'/"
|
|
||||||
+ std::to_string(bip44CoinType) + "'/"
|
|
||||||
+ std::to_string(ZCASH_LEGACY_ACCOUNT) + "'/"
|
|
||||||
+ std::to_string(addressIndex) + "'";
|
|
||||||
|
|
||||||
return std::make_pair(xsk, hdKeypath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SaplingExtendedFullViewingKey SaplingExtendedSpendingKey::ToXFVK() const
|
SaplingExtendedFullViewingKey SaplingExtendedSpendingKey::ToXFVK() const
|
||||||
|
@ -219,6 +208,17 @@ SaplingExtendedFullViewingKey SaplingExtendedSpendingKey::ToXFVK() const
|
||||||
return ret;
|
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::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::regex pattern("m/" + std::to_string(purpose) + "'/" + std::to_string(coinType) + "'/([0-9]+)'.*");
|
||||||
std::smatch matches;
|
std::smatch matches;
|
||||||
|
|
|
@ -62,6 +62,7 @@ uint256 ovkForShieldingFromTaddr(HDSeed& seed);
|
||||||
|
|
||||||
namespace libzcash {
|
namespace libzcash {
|
||||||
|
|
||||||
|
typedef uint256 SeedFingerprint;
|
||||||
typedef uint32_t AccountId;
|
typedef uint32_t AccountId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -102,7 +103,16 @@ public:
|
||||||
return false; //overflow
|
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) {
|
friend bool operator<(const diversifier_index_t& a, const diversifier_index_t& b) {
|
||||||
for (int i = 10; i >= 0; i--) {
|
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 {
|
class SaplingDiversifiableFullViewingKey {
|
||||||
public:
|
public:
|
||||||
libzcash::SaplingFullViewingKey fvk;
|
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
|
} //namespace libzcash
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue