Add unified full viewing keys and Zcash-internal unified addresses.

This commit is contained in:
Kris Nuttycombe 2021-09-24 14:24:41 -06:00
parent 83dee7e886
commit f6ad523ccb
4 changed files with 156 additions and 7 deletions

View File

@ -108,6 +108,16 @@ class blob88 : public base_blob<88> {
public:
blob88() {}
blob88(const base_blob<88>& b) : base_blob<88>(b) {}
blob88(uint64_t i): base_blob<88>() {
data[0] = (uint8_t) i;
data[1] = (uint8_t) (i >> 8);
data[2] = (uint8_t) (i >> 16);
data[3] = (uint8_t) (i >> 24);
data[4] = (uint8_t) (i >> 32);
data[5] = (uint8_t) (i >> 40);
data[6] = (uint8_t) (i >> 48);
data[7] = (uint8_t) (i >> 56);
}
explicit blob88(const std::vector<unsigned char>& vch) : base_blob<88>(vch) {}
std::optional<blob88> increment() const {
@ -123,6 +133,17 @@ public:
return std::nullopt;
}
// treat as little-endian for numeric comparison
bool less_than_le(const blob88& other) const {
for (int i = 10; i >= 0; i--) {
if (data[i] < other.data[i]) {
return true;
}
}
return false;
}
};
/** 160-bit opaque blob.

View File

@ -75,6 +75,15 @@ static UniValue ValueFromString(const std::string &str)
return value;
}
static SaplingPaymentAddress DefaultSaplingAddress(CWallet* pwallet) {
auto usk = pwallet->GetUnifiedSpendingKeyForAccount(0);
return usk.value()
.ToFullViewingKey()
.GetSaplingExtendedFullViewingKey().value()
.FindAddress(libzcash::diversifier_index_t(0)).first;
}
BOOST_FIXTURE_TEST_SUITE(rpc_wallet_tests, WalletTestingSetup)
BOOST_AUTO_TEST_CASE(rpc_addmultisig)
@ -788,12 +797,15 @@ void CheckHaveAddr(const libzcash::PaymentAddress& addr) {
BOOST_AUTO_TEST_CASE(rpc_wallet_z_getnewaddress) {
using namespace libzcash;
UniValue addr;
if (!pwalletMain->HaveMnemonicSeed()) {
pwalletMain->GenerateNewSeed();
if (!pwalletMain->HaveLegacyHDSeed()) {
// fake a legacy seed by creating a separate mnemonic seed
auto seed = MnemonicSeed::Random(1);
pwalletMain->LoadLegacyHDSeed(seed);
}
UniValue addr;
KeyIO keyIO(Params());
// No parameter defaults to sapling address
addr = CallRPC("z_getnewaddress");
@ -1459,7 +1471,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling)
// add keys manually
auto taddr = pwalletMain->GenerateNewKey().GetID();
std::string taddr1 = keyIO.EncodeDestination(taddr);
auto pa = pwalletMain->GenerateNewLegacySaplingZKey().value();
auto pa = DefaultSaplingAddress(pwalletMain);
std::string zaddr1 = keyIO.EncodePaymentAddress(pa);
const Consensus::Params& consensusParams = Params().GetConsensus();
@ -1549,6 +1561,13 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling)
BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_zkeys)
{
LOCK2(cs_main, pwalletMain->cs_wallet);
if (!pwalletMain->HaveLegacyHDSeed()) {
// fake a legacy seed by creating a separate mnemonic seed
auto seed = MnemonicSeed::Random(1);
pwalletMain->LoadLegacyHDSeed(seed);
}
UniValue retValue;
int n = 100;
@ -1605,6 +1624,13 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_zkeys)
BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_sapzkeys)
{
LOCK2(cs_main, pwalletMain->cs_wallet);
if (!pwalletMain->HaveLegacyHDSeed()) {
// fake a legacy seed by creating a separate mnemonic seed
auto seed = MnemonicSeed::Random(1);
pwalletMain->LoadLegacyHDSeed(seed);
}
UniValue retValue;
int n = 100;
@ -1668,6 +1694,12 @@ BOOST_AUTO_TEST_CASE(rpc_z_listunspent_parameters)
{
SelectParams(CBaseChainParams::TESTNET);
if (!pwalletMain->HaveLegacyHDSeed()) {
// fake a legacy seed by creating a separate mnemonic seed
auto seed = MnemonicSeed::Random(1);
pwalletMain->LoadLegacyHDSeed(seed);
}
LOCK2(cs_main, pwalletMain->cs_wallet);
UniValue retValue;

View File

@ -19,6 +19,8 @@ const unsigned char ZCASH_HD_SEED_FP_PERSONAL[BLAKE2bPersonalBytes] =
const unsigned char ZCASH_TADDR_OVK_PERSONAL[BLAKE2bPersonalBytes] =
{'Z', 'c', 'T', 'a', 'd', 'd', 'r', 'T', 'o', 'S', 'a', 'p', 'l', 'i', 'n', 'g'};
const libzcash::diversifier_index_t MAX_TRANSPARENT_CHILD_IDX(0x40000000);
MnemonicSeed MnemonicSeed::Random(uint32_t bip44CoinType, Language language, size_t entropyLen)
{
assert(entropyLen >= 32);
@ -216,10 +218,11 @@ std::optional<CExtKey> DeriveZip32TransparentSpendingKey(const HDSeed& seed, uin
std::optional<std::pair<UnifiedSpendingKey, CKeyMetadata>> UnifiedSpendingKey::Derive(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId) {
UnifiedSpendingKey usk;
usk.accountId = accountId;
auto transparentKey = DeriveZip32TransparentSpendingKey(seed, bip44CoinType, accountId);
if (!transparentKey.has_value()) return std::nullopt;
usk.p2pkhKey = transparentKey.value();
usk.transparentKey = transparentKey.value();
auto saplingKey = SaplingExtendedSpendingKey::ForAccount(seed, bip44CoinType, accountId);
usk.saplingKey = saplingKey.first;
@ -227,6 +230,51 @@ std::optional<std::pair<UnifiedSpendingKey, CKeyMetadata>> UnifiedSpendingKey::D
return std::make_pair(usk, saplingKey.second);
}
UnifiedFullViewingKey UnifiedSpendingKey::ToFullViewingKey() const {
UnifiedFullViewingKey ufvk;
if (transparentKey.has_value()) {
ufvk.transparentKey = transparentKey.value().Neuter();
}
if (saplingKey.has_value()) {
ufvk.saplingKey = saplingKey.value().ToXFVK();
}
return ufvk;
}
std::optional<ZcashdUnifiedAddress> UnifiedFullViewingKey::Address(diversifier_index_t j) const {
ZcashdUnifiedAddress ua;
if (transparentKey.has_value()) {
if (MAX_TRANSPARENT_CHILD_IDX.less_than_le(j)) return std::nullopt;
CExtPubKey changeKey;
if (!transparentKey.value().Derive(changeKey, 0)) {
return std::nullopt;
}
CExtPubKey childKey;
unsigned int childIndex = (unsigned int) j.GetUint64(0);
if (changeKey.Derive(childKey, childIndex)) {
ua.transparentKey = childKey.pubkey;
} else {
return std::nullopt;
}
}
if (saplingKey.has_value()) {
auto saplingAddress = saplingKey.value().Address(j);
if (saplingAddress.has_value()) {
ua.saplingAddress = saplingAddress.value();
} else {
return std::nullopt;
}
}
return ua;
}
std::optional<unsigned long> ParseZip32KeypathAccount(const std::string& keyPath) {
std::regex pattern("m/32'/[0-9]+'/([0-9]+)'");
std::smatch matches;

View File

@ -281,19 +281,67 @@ struct SaplingExtendedSpendingKey {
}
};
class UnifiedSpendingKey;
class UnifiedFullViewingKey;
class ZcashdUnifiedAddress {
private:
diversifier_index_t diversifier_index;
std::optional<CPubKey> transparentKey; //TODO: should this just be the public key hash?
std::optional<SaplingPaymentAddress> saplingAddress;
friend class UnifiedFullViewingKey;
ZcashdUnifiedAddress() {}
public:
const std::optional<CPubKey>& GetTransparentKey() const {
return transparentKey;
}
const std::optional<SaplingPaymentAddress>& GetSaplingPaymentAddress() const {
return saplingAddress;
}
};
class UnifiedFullViewingKey {
private:
std::optional<CExtPubKey> transparentKey;
std::optional<SaplingExtendedFullViewingKey> saplingKey;
UnifiedFullViewingKey() {}
friend class UnifiedSpendingKey;
public:
const std::optional<CExtPubKey>& GetTransparentKey() const {
return transparentKey;
}
const std::optional<SaplingExtendedFullViewingKey>& GetSaplingExtendedFullViewingKey() const {
return saplingKey;
}
std::optional<ZcashdUnifiedAddress> Address(diversifier_index_t j) const;
};
class UnifiedSpendingKey {
private:
uint32_t accountId;
std::optional<CExtKey> p2pkhKey;
std::optional<CExtKey> transparentKey;
std::optional<SaplingExtendedSpendingKey> saplingKey;
UnifiedSpendingKey() {}
public:
static std::optional<std::pair<UnifiedSpendingKey, CKeyMetadata>> Derive(const HDSeed& seed, uint32_t bip44CoinType, uint32_t accountId);
const std::optional<SaplingExtendedSpendingKey>& GetSaplingExtendedSpendingKey() {
const std::optional<CExtKey>& GetTransparentKey() const {
return transparentKey;
}
const std::optional<SaplingExtendedSpendingKey>& GetSaplingExtendedSpendingKey() const {
return saplingKey;
}
UnifiedFullViewingKey ToFullViewingKey() const;
};
std::optional<unsigned long> ParseZip32KeypathAccount(const std::string& keyPath);