Add a first-class type for transparent full viewing keys.

This commit is contained in:
Kris Nuttycombe 2022-01-26 19:32:43 -07:00
parent c21ffff790
commit bdcb12e445
17 changed files with 439 additions and 179 deletions

53
Cargo.lock generated
View File

@ -624,6 +624,17 @@ dependencies = [
"ahash",
]
[[package]]
name = "hdwallet"
version = "0.3.0"
source = "git+https://github.com/nuttycom/hdwallet?rev=576683b9f2865f1118c309017ff36e01f84420c9#576683b9f2865f1118c309017ff36e01f84420c9"
dependencies = [
"lazy_static",
"rand_core 0.6.3",
"ring",
"secp256k1",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
@ -1404,6 +1415,32 @@ version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "ripemd160"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251"
dependencies = [
"block-buffer",
"digest",
"opaque-debug",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
@ -1493,6 +1530,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "static_assertions"
version = "1.1.0"
@ -1715,6 +1758,12 @@ dependencies = [
"subtle",
]
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "version_check"
version = "0.9.3"
@ -1890,12 +1939,14 @@ dependencies = [
"blake2b_simd 1.0.0",
"blake2s_simd 1.0.0",
"bls12_381",
"bs58",
"byteorder",
"chacha20poly1305",
"equihash",
"ff",
"fpe",
"group",
"hdwallet",
"hex",
"incrementalmerkletree",
"jubjub",
@ -1905,6 +1956,8 @@ dependencies = [
"orchard",
"rand",
"rand_core 0.6.3",
"ripemd160",
"secp256k1",
"sha2",
"subtle",
"zcash_encoding",

View File

@ -47,7 +47,7 @@ tracing-appender = "0.2"
zcash_address = "0.0"
zcash_history = "0.2"
zcash_note_encryption = "0.1"
zcash_primitives = "0.5"
zcash_primitives = { version = "0.5", features = ["transparent-inputs"] }
zcash_proofs = "0.5"
ed25519-zebra = "3"
zeroize = "1.4.2"
@ -71,6 +71,7 @@ panic = 'abort'
codegen-units = 1
[patch.crates-io]
hdwallet = { git = "https://github.com/nuttycom/hdwallet", rev = "576683b9f2865f1118c309017ff36e01f84420c9" }
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "a01290869a4f0c4e05bdcca550795ea0e76e8550" }
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "a01290869a4f0c4e05bdcca550795ea0e76e8550" }
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "a01290869a4f0c4e05bdcca550795ea0e76e8550" }

View File

@ -114,7 +114,7 @@ LIBZCASH_H = \
zcash/IncrementalMerkleTree.hpp \
zcash/NoteEncryption.hpp \
zcash/Address.hpp \
zcash/address/bip44.h \
zcash/address/transparent.h \
zcash/address/mnemonic.h \
zcash/address/orchard.h \
zcash/address/sapling.hpp \
@ -547,7 +547,7 @@ libzcash_a_SOURCES = \
zcash/IncrementalMerkleTree.cpp \
zcash/NoteEncryption.cpp \
zcash/Address.cpp \
zcash/address/bip44.cpp \
zcash/address/transparent.cpp \
zcash/address/mnemonic.cpp \
zcash/address/orchard.cpp \
zcash/address/sapling.cpp \

View File

@ -98,6 +98,16 @@ bool CPubKey::Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChi
return true;
}
std::optional<CChainablePubKey> CChainablePubKey::Derive(unsigned int nChild) const {
CPubKey pubkeyChild;
ChainCode ccChild;
if (pubkey.Derive(pubkeyChild, ccChild, nChild, chaincode)) {
return CChainablePubKey::FromParts(ccChild, pubkeyChild);
} else {
return std::nullopt;
}
}
void CExtPubKey::Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const {
code[0] = nDepth;
memcpy(code+1, vchFingerprint, 4);

View File

@ -221,6 +221,8 @@ public:
return pubkey;
}
std::optional<CChainablePubKey> Derive(unsigned int nChild) const;
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
@ -272,6 +274,10 @@ struct CExtPubKey {
void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);
bool Derive(CExtPubKey& out, unsigned int nChild) const;
std::optional<CChainablePubKey> ToChainablePubKey() const {
return CChainablePubKey::FromParts(chaincode, pubkey);
}
void Serialize(CSizeComputer& s) const
{
// Optimized implementation for ::GetSerializeSize that avoids copying.

View File

@ -3,6 +3,7 @@ use std::ffi::{CStr, CString};
use tracing::error;
use zcash_address::unified::{Container, Encoding, Fvk, Ufvk};
use zcash_primitives::legacy::keys::AccountPubKey;
use crate::address_ffi::network_from_cstr;
@ -165,3 +166,29 @@ pub extern "C" fn unified_full_viewing_key_from_components(
}
}
}
#[no_mangle]
pub extern "C" fn transparent_key_ovks(
t_key: *const [u8; 65],
internal_ovk_ret: *mut [u8; 32],
external_ovk_ret: *mut [u8; 32],
) -> bool {
let key_bytes = unsafe { t_key.as_ref() }.expect("Transparent FVK pointer may not be null.");
let internal_ovk = unsafe { &mut *internal_ovk_ret };
let external_ovk = unsafe { &mut *external_ovk_ret };
match AccountPubKey::deserialize(key_bytes) {
Ok(epubkey) => {
let (internal, external) = epubkey.ovks_for_shielding();
*internal_ovk = internal.as_bytes();
*external_ovk = external.as_bytes();
true
}
Err(e) => {
error!(
"An error occurred parsing the transparent full viewing key: {:?}",
e
);
false
}
}
}

View File

@ -276,26 +276,29 @@ CPubKey CWallet::GenerateNewKey()
// All mnemonic seeds are checked at construction to ensure that we can obtain
// a valid spending key for the account ZCASH_LEGACY_ACCOUNT;
// therefore, the `value()` call here is safe.
Bip44AccountChains accountChains = Bip44AccountChains::ForAccount(
transparent::AccountKey accountKey = transparent::AccountKey::ForAccount(
seed,
BIP44CoinType(),
ZCASH_LEGACY_ACCOUNT).value();
std::optional<std::pair<CKey, HDKeyPath>> extKey = std::nullopt;
std::optional<CPubKey> pubkey = std::nullopt;
do {
extKey = accountChains.DeriveExternal(hdChain.GetLegacyTKeyCounter());
auto index = hdChain.GetLegacyTKeyCounter();
auto key = accountKey.DeriveExternalSpendingKey(index);
hdChain.IncrementLegacyTKeyCounter();
if (key.has_value()) {
auto keyPath = transparent::AccountKey::KeyPath(BIP44CoinType(), ZCASH_LEGACY_ACCOUNT, true, index);
pubkey = AddTransparentSecretKey(seed.Fingerprint(), std::make_pair(key.value(), keyPath));
}
// if we did not successfully generate a key, try again.
} while (!extKey.has_value());
auto pubkey = AddTransparentSecretKey(seed.Fingerprint(), extKey.value());
} while (!pubkey.has_value());
// Update the persisted chain information
if (fFileBacked && !CWalletDB(strWalletFile).WriteMnemonicHDChain(hdChain)) {
throw std::runtime_error("CWallet::GenerateNewKey(): Writing HD chain model failed");
}
return pubkey;
return pubkey.value();
}
CPubKey CWallet::AddTransparentSecretKey(
@ -479,14 +482,9 @@ std::optional<libzcash::ZcashdUnifiedSpendingKey>
// LoadUnifiedAccountMetadata().
mapUfvkAddressMetadata.insert({ufvkid, UFVKAddressMetadata(accountId)});
// Add Transparent component to the wallet
AddTransparentSecretKey(
skmeta.GetSeedFingerprint(),
std::make_pair(
usk.value().GetTransparentKey().key,
libzcash::Bip44TransparentAccountKeyPath(BIP44CoinType(), accountId)
)
);
// We do not explicitly add any transparent component to the keystore;
// the secret keys that we need to store are the child spending keys
// that are produced whenever we create a transparent address.
// Add Sapling component to the wallet
auto saplingEsk = usk.value().GetSaplingExtendedSpendingKey();
@ -658,9 +656,21 @@ UAGenerationResult CWallet::GenerateUnifiedAddress(
// 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);
auto accountKey = transparent::AccountKey::ForAccount(seed, BIP44CoinType(), accountId).value();
// this .value is known to be safe from the earlier check
auto childIndex = diversifierIndex.ToTransparentChildIndex().value();
auto key = accountKey.DeriveExternalSpendingKey(childIndex).value();
AddTransparentSecretKey(
seed.Fingerprint(),
std::make_pair(
key,
transparent::AccountKey::KeyPath(BIP44CoinType(), accountId, true, childIndex)
)
);
// We do not add the change address for the transparent key, because
// we do not send transparent change when using unified accounts.
}
return std::make_pair(address.value(), diversifierIndex);
@ -1555,7 +1565,7 @@ std::optional<RecipientAddress> CWallet::GenerateChangeAddressForAccount(
if (t == libzcash::ChangeType::Transparent && accountId == ZCASH_LEGACY_ACCOUNT) {
return GenerateNewKey().GetID();
} else {
return ufvk.value().GetChangeAddress(changeOptions);
return ufvk.value().GetChangeAddress(SaplingChangeRequest());
}
}
}

View File

@ -23,7 +23,6 @@
#include "wallet/crypter.h"
#include "wallet/walletdb.h"
#include "wallet/rpcwallet.h"
#include "zcash/address/bip44.h"
#include "zcash/address/unified.h"
#include "zcash/address/mnemonic.h"
#include "zcash/Address.hpp"

View File

@ -195,10 +195,10 @@ std::optional<CChainablePubKey> libzcash::UnifiedFullViewingKey::GetTransparentK
}
}
bool libzcash::UnifiedFullViewingKeyBuilder::AddTransparentKey(const CChainablePubKey& key) {
bool libzcash::UnifiedFullViewingKeyBuilder::AddTransparentKey(const transparent::AccountPubKey& key) {
if (t_bytes.has_value()) return false;
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << key;
ss << key.GetPubKey();
assert(ss.size() == 65);
std::vector<uint8_t> ss_bytes(ss.begin(), ss.end());
t_bytes = ss_bytes;

View File

@ -201,7 +201,7 @@ private:
public:
UnifiedFullViewingKeyBuilder(): t_bytes(std::nullopt), sapling_bytes(std::nullopt) {}
bool AddTransparentKey(const CChainablePubKey&);
bool AddTransparentKey(const transparent::AccountPubKey&);
bool AddSaplingKey(const SaplingDiversifiableFullViewingKey&);
std::optional<UnifiedFullViewingKey> build() const;

View File

@ -1,66 +0,0 @@
// Copyright (c) 2021 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
#include "bip44.h"
HDKeyPath libzcash::Bip44TransparentAccountKeyPath(uint32_t bip44CoinType, libzcash::AccountId accountId) {
return "m/44'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(accountId) + "'";
}
std::optional<std::pair<CExtKey, HDKeyPath>> libzcash::DeriveBip44TransparentAccountKey(const HDSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId) {
auto rawSeed = seed.RawSeed();
auto m = CExtKey::Master(rawSeed.data(), rawSeed.size());
// We use a fixed keypath scheme of m/44'/coin_type'/account'
// Derive m/44'
auto m_44h = m.Derive(44 | HARDENED_KEY_LIMIT);
if (!m_44h.has_value()) return std::nullopt;
// Derive m/44'/coin_type'
auto m_44h_cth = m_44h.value().Derive(bip44CoinType | HARDENED_KEY_LIMIT);
if (!m_44h_cth.has_value()) return std::nullopt;
// Derive m/44'/coin_type'/account_id'
auto result = m_44h_cth.value().Derive(accountId | HARDENED_KEY_LIMIT);
if (!result.has_value()) return std::nullopt;
auto hdKeypath = libzcash::Bip44TransparentAccountKeyPath(bip44CoinType, accountId);
return std::make_pair(result.value(), hdKeypath);
}
std::optional<libzcash::Bip44AccountChains> libzcash::Bip44AccountChains::ForAccount(
const HDSeed& seed,
uint32_t bip44CoinType,
libzcash::AccountId accountId) {
auto accountKeyOpt = DeriveBip44TransparentAccountKey(seed, bip44CoinType, accountId);
if (!accountKeyOpt.has_value()) return std::nullopt;
auto accountKey = accountKeyOpt.value();
auto external = accountKey.first.Derive(0);
auto internal = accountKey.first.Derive(1);
if (!(external.has_value() && internal.has_value())) return std::nullopt;
return Bip44AccountChains(seed.Fingerprint(), bip44CoinType, accountId, external.value(), internal.value());
}
std::optional<std::pair<CKey, HDKeyPath>> libzcash::Bip44AccountChains::DeriveExternal(uint32_t addrIndex) {
auto childKey = external.Derive(addrIndex);
if (!childKey.has_value()) return std::nullopt;
auto hdKeypath = Bip44TransparentAccountKeyPath(bip44CoinType, accountId) + "/0/" + std::to_string(addrIndex);
return std::make_pair(childKey.value().key, hdKeypath);
}
std::optional<std::pair<CKey, HDKeyPath>> libzcash::Bip44AccountChains::DeriveInternal(uint32_t addrIndex) {
auto childKey = internal.Derive(addrIndex);
if (!childKey.has_value()) return std::nullopt;
auto hdKeypath = Bip44TransparentAccountKeyPath(bip44CoinType, accountId) + "/1/" + std::to_string(addrIndex);
return std::make_pair(childKey.value().key, hdKeypath);
}

View File

@ -1,53 +0,0 @@
// Copyright (c) 2021 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
#ifndef ZCASH_ZCASH_ADDRESS_BIP44_H
#define ZCASH_ZCASH_ADDRESS_BIP44_H
#include "zip32.h"
namespace libzcash {
HDKeyPath Bip44TransparentAccountKeyPath(uint32_t bip44CoinType, libzcash::AccountId accountId);
/**
* Derive a transparent extended key in compressed format for the specified
* seed, bip44 coin type, and account ID.
*/
std::optional<std::pair<CExtKey, HDKeyPath>> DeriveBip44TransparentAccountKey(const HDSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId);
class Bip44AccountChains {
private:
uint256 seedFp;
libzcash::AccountId accountId;
uint32_t bip44CoinType;
CExtKey external;
CExtKey internal;
Bip44AccountChains(uint256 seedFpIn, uint32_t bip44CoinTypeIn, libzcash::AccountId accountIdIn, CExtKey externalIn, CExtKey internalIn):
seedFp(seedFpIn), accountId(accountIdIn), bip44CoinType(bip44CoinTypeIn), external(externalIn), internal(internalIn) {}
public:
static std::optional<Bip44AccountChains> ForAccount(
const HDSeed& mnemonic,
uint32_t bip44CoinType,
libzcash::AccountId accountId);
/**
* 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
#endif // ZCASH_ZCASH_ADDRESS_BIP44_H

View File

@ -5,8 +5,6 @@
#include "random.h"
#include "mnemonic.h"
#include "bip44.h"
#include "unified.h"
using namespace libzcash;
@ -33,7 +31,7 @@ MnemonicSeed MnemonicSeed::Random(uint32_t bip44CoinType, Language language, siz
// for a valid diversifier; unlike in the unified spending key case, diversifier
// indices don't need to line up with anything.
if (ZcashdUnifiedSpendingKey::ForAccount(seed, bip44CoinType, 0).has_value() &&
Bip44AccountChains::ForAccount(seed, bip44CoinType, ZCASH_LEGACY_ACCOUNT).has_value()) {
transparent::AccountKey::ForAccount(seed, bip44CoinType, ZCASH_LEGACY_ACCOUNT).has_value()) {
return seed;
}
}

View File

@ -0,0 +1,101 @@
// Copyright (c) 2021 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
#include "transparent.h"
namespace libzcash {
namespace transparent {
std::optional<CPubKey> AccountPubKey::DeriveExternal(uint32_t addrIndex) const {
auto externalKey = pubkey.Derive(0);
if (!externalKey.has_value()) return std::nullopt;
auto childKey = externalKey.value().Derive(addrIndex);
if (!childKey.has_value()) return std::nullopt;
return childKey.value().GetPubKey();
}
std::optional<CPubKey> AccountPubKey::DeriveInternal(uint32_t addrIndex) const {
auto internalKey = pubkey.Derive(1);
if (!internalKey.has_value()) return std::nullopt;
auto childKey = internalKey.value().Derive(addrIndex);
if (!childKey.has_value()) return std::nullopt;
return childKey.value().GetPubKey();
}
std::optional<CKeyID> AccountPubKey::GetChangeAddress(const diversifier_index_t& j) const {
auto childIndex = j.ToTransparentChildIndex();
if (!childIndex.has_value()) return std::nullopt;
auto changeKey = this->DeriveInternal(childIndex.value());
if (!changeKey.has_value()) return std::nullopt;
return changeKey.value().GetID();
}
std::optional<std::pair<CKeyID, diversifier_index_t>> AccountPubKey::FindChangeAddress(diversifier_index_t j) const {
while (true) {
auto childIndex = j.ToTransparentChildIndex();
if (!childIndex.has_value()) return std::nullopt;
auto changeKey = this->DeriveInternal(childIndex.value());
if (changeKey.has_value()) {
return std::make_pair(changeKey.value().GetID(), j);
} else {
j.increment();
}
}
return std::nullopt;
}
std::optional<AccountKey> AccountKey::ForAccount(
const HDSeed& seed,
uint32_t bip44CoinType,
AccountId accountId) {
auto rawSeed = seed.RawSeed();
auto m = CExtKey::Master(rawSeed.data(), rawSeed.size());
// We use a fixed keypath scheme of m/44'/coin_type'/account'
// Derive m/44'
auto m_44h = m.Derive(44 | HARDENED_KEY_LIMIT);
if (!m_44h.has_value()) return std::nullopt;
// Derive m/44'/coin_type'
auto m_44h_cth = m_44h.value().Derive(bip44CoinType | HARDENED_KEY_LIMIT);
if (!m_44h_cth.has_value()) return std::nullopt;
// Derive m/44'/coin_type'/account_id'
auto accountKeyOpt = m_44h_cth.value().Derive(accountId | HARDENED_KEY_LIMIT);
if (!accountKeyOpt.has_value()) return std::nullopt;
auto accountKey = accountKeyOpt.value();
auto external = accountKey.Derive(0);
auto internal = accountKey.Derive(1);
if (!(external.has_value() && internal.has_value())) return std::nullopt;
return AccountKey(accountKey, external.value(), internal.value());
}
std::optional<CKey> AccountKey::DeriveExternalSpendingKey(uint32_t addrIndex) const {
auto childKey = external.Derive(addrIndex);
if (!childKey.has_value()) return std::nullopt;
return childKey.value().key;
}
std::optional<CKey> AccountKey::DeriveInternalSpendingKey(uint32_t addrIndex) const {
auto childKey = internal.Derive(addrIndex);
if (!childKey.has_value()) return std::nullopt;
return childKey.value().key;
}
AccountPubKey AccountKey::ToAccountPubKey() const {
// The .value() call is safe here because we never derive
// non-compressed public keys.
return accountKey.Neuter().ToChainablePubKey().value();
}
} //namespace transparent
} //namespace libzcash

View File

@ -0,0 +1,106 @@
// Copyright (c) 2021 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
#ifndef ZCASH_ZCASH_ADDRESS_TRANSPARENT_H
#define ZCASH_ZCASH_ADDRESS_TRANSPARENT_H
#include "zip32.h"
namespace libzcash {
namespace transparent {
class AccountPubKey {
private:
CChainablePubKey pubkey;
public:
AccountPubKey(CChainablePubKey pubkey): pubkey(pubkey) {};
const CChainablePubKey& GetPubKey() const {
return pubkey;
}
std::optional<CPubKey> DeriveExternal(uint32_t addrIndex) const;
std::optional<CPubKey> DeriveInternal(uint32_t addrIndex) const;
/**
* Return the transparent change address for this key a the given diversifier
* index, if that index is valid for a transparent address and if key
* derivation is successful.
*/
std::optional<CKeyID> GetChangeAddress(const diversifier_index_t& j) const;
/**
* Search the space of valid child indices starting at the given index, and
* return the change address corresponding to the first index at which a
* valid address is derived, along with that index. This will always return
* a valid address unless the space of valid transparent child indices is
* exhausted or the provided diversifier index exceeds the maximum allowed
* non-hardened transparent child index.
*/
std::optional<std::pair<CKeyID, diversifier_index_t>> FindChangeAddress(diversifier_index_t j) const;
friend bool operator==(const AccountPubKey& a, const AccountPubKey& b)
{
return a.pubkey == b.pubkey;
}
};
class AccountKey {
private:
CExtKey accountKey;
CExtKey external;
CExtKey internal;
AccountKey(CExtKey accountKeyIn, CExtKey externalIn, CExtKey internalIn):
accountKey(accountKeyIn), external(externalIn), internal(internalIn) {}
public:
static std::optional<AccountKey> ForAccount(
const HDSeed& mnemonic,
uint32_t bip44CoinType,
libzcash::AccountId accountId);
static HDKeyPath KeyPath(uint32_t bip44CoinType, libzcash::AccountId accountId) {
return
"m/44'/" +
std::to_string(bip44CoinType) + "'/" +
std::to_string(accountId) + "'";
}
static HDKeyPath KeyPath(uint32_t bip44CoinType, libzcash::AccountId accountId, bool external, uint32_t childIndex) {
return
AccountKey::KeyPath(bip44CoinType, accountId) + "/" +
(external ? "0/" : "1/") +
std::to_string(childIndex);
}
/**
* Generate the key corresponding to the specified index at the "external child"
* level of the TRANSPARENT path for the account.
*/
std::optional<CKey> DeriveExternalSpendingKey(uint32_t addrIndex) const;
/**
* Generate the key corresponding to the specified index at the "internal child"
* level of the TRANSPARENT 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<CKey> DeriveInternalSpendingKey(uint32_t addrIndex = 0) const;
/**
* Return the public key associated with this spending key.
*/
AccountPubKey ToAccountPubKey() const;
friend bool operator==(const AccountKey& a, const AccountKey& b)
{
return a.accountKey == b.accountKey;
}
};
} //namespace transparent
} //namespace libzcash
#endif // ZCASH_ZCASH_ADDRESS_TRANSPARENT_H

View File

@ -4,7 +4,6 @@
#include "zcash/Address.hpp"
#include "unified.h"
#include "bip44.h"
using namespace libzcash;
@ -32,23 +31,19 @@ std::optional<ZcashdUnifiedSpendingKey> ZcashdUnifiedSpendingKey::ForAccount(
const HDSeed& seed,
uint32_t bip44CoinType,
AccountId accountId) {
ZcashdUnifiedSpendingKey usk;
auto transparentKey = DeriveBip44TransparentAccountKey(seed, bip44CoinType, accountId);
auto transparentKey = transparent::AccountKey::ForAccount(seed, bip44CoinType, accountId);
if (!transparentKey.has_value()) return std::nullopt;
usk.transparentKey = transparentKey.value().first;
auto saplingKey = SaplingExtendedSpendingKey::ForAccount(seed, bip44CoinType, accountId);
usk.saplingKey = saplingKey.first;
return usk;
return ZcashdUnifiedSpendingKey(transparentKey.value(), saplingKey.first);
}
UnifiedFullViewingKey ZcashdUnifiedSpendingKey::ToFullViewingKey() const {
UnifiedFullViewingKeyBuilder builder;
auto extPubKey = transparentKey.Neuter();
builder.AddTransparentKey(CChainablePubKey::FromParts(extPubKey.chaincode, extPubKey.pubkey).value());
builder.AddTransparentKey(transparentKey.ToAccountPubKey());
builder.AddSaplingKey(saplingKey.ToXFVK());
// This call to .value() is safe as ZcashdUnifiedSpendingKey values are always
@ -95,22 +90,14 @@ std::optional<UnifiedAddress> ZcashdUnifiedFullViewingKey::Address(
if (transparentKey.has_value() && receiverTypes.count(ReceiverType::P2PKH) > 0) {
const auto& tkey = transparentKey.value();
auto childIndex = j.ToTransparentChildIndex();
if (!childIndex.has_value()) return std::nullopt;
CPubKey externalKey;
ChainCode externalChainCode;
if (!tkey.GetPubKey().Derive(externalKey, externalChainCode, 0, tkey.GetChainCode())) {
return std::nullopt;
}
auto externalPubkey = tkey.DeriveExternal(childIndex.value());
if (!externalPubkey.has_value()) return std::nullopt;
CPubKey childKey;
ChainCode childChainCode;
if (externalKey.Derive(childKey, childChainCode, childIndex.value(), externalChainCode)) {
ua.AddReceiver(childKey.GetID());
} else {
return std::nullopt;
}
ua.AddReceiver(externalPubkey.value().GetID());
}
return ua;
@ -135,6 +122,57 @@ std::optional<std::pair<UnifiedAddress, diversifier_index_t>> ZcashdUnifiedFullV
return FindAddress(j, {ReceiverType::P2PKH, ReceiverType::Sapling});
}
RecipientAddress ZcashdUnifiedFullViewingKey::GetChangeAddress(const std::set<ChangeType>& changeOptions) const {
throw std::runtime_error("TODO");
std::optional<RecipientAddress> ZcashdUnifiedFullViewingKey::GetChangeAddress(const ChangeRequest& req) const {
std::optional<RecipientAddress> addr;
std::visit(match {
[&](const TransparentChangeRequest& req) {
auto changeKey = this->GetTransparentChangeAddress(req.GetIndex());
if (changeKey.has_value()) {
addr = changeKey.value();
}
},
[&](const SaplingChangeRequest& req) {
std::optional<RecipientAddress> addr;
// currently true by construction, as a UFVK must have a shielded component
if (saplingKey.has_value()) {
addr = saplingKey.value().GetChangeAddress();
}
}
}, req);
return addr;
}
std::optional<CKeyID> ZcashdUnifiedFullViewingKey::GetTransparentChangeAddress(const diversifier_index_t& j) const {
if (transparentKey.has_value()) {
auto childIndex = j.ToTransparentChildIndex();
if (!childIndex.has_value()) return std::nullopt;
auto changeKey = transparentKey.value().DeriveInternal(childIndex.value());
if (!changeKey.has_value()) return std::nullopt;
return changeKey.value().GetID();
} else {
return std::nullopt;
}
}
//std::optional<std::pair<uint256, uint256>> ZcashdUnifiedFullViewingKey::GetTransparentOVKsForShielding() const {
// if (transparentKey.has_value()) {
// CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
// ss << transparentKey.value().GetPubKey();
// assert(ss.size() == 65);
// CSerializeData ss_bytes(ss.begin(), ss.end());
//
// uint256 internalOVK;
// uint256 externalOVK;
//
// assert(transparent_key_ovks(
// reinterpret_cast<unsigned char*>(ss_bytes.data()),
// internalOVK.begin(),
// externalOVK.begin()));
//
// return std::make_pair(internalOVK, externalOVK);
// } else {
// return std::nullopt;
// }
//}

View File

@ -5,7 +5,7 @@
#ifndef ZCASH_ZCASH_ADDRESS_UNIFIED_H
#define ZCASH_ZCASH_ADDRESS_UNIFIED_H
#include "bip44.h"
#include "transparent.h"
#include "key_constants.h"
#include "script/script.h"
#include "zip32.h"
@ -26,14 +26,32 @@ typedef std::variant<
libzcash::SaplingPaymentAddress> RecipientAddress;
/**
* An enumeration of the types of change that a transaction may
* produce.
* An enumeration of the types of change that a transaction may produce. It is
* sorted in descending preference order, so that when iterating over a set of
* change types the most-preferred type is selected first.
*/
enum class ChangeType {
Sapling,
Transparent,
};
class TransparentChangeRequest {
private:
const diversifier_index_t& index;
public:
TransparentChangeRequest(const diversifier_index_t& indexIn): index(indexIn) {}
const diversifier_index_t& GetIndex() const {
return index;
}
};
class SaplingChangeRequest {};
typedef std::variant<
TransparentChangeRequest,
SaplingChangeRequest> ChangeRequest;
/**
* Test whether the specified list of receiver types contains a
* shielded receiver type
@ -105,7 +123,7 @@ public:
class ZcashdUnifiedFullViewingKey {
private:
UFVKId keyId;
std::optional<CChainablePubKey> transparentKey;
std::optional<transparent::AccountPubKey> transparentKey;
std::optional<SaplingDiversifiableFullViewingKey> saplingKey;
ZcashdUnifiedFullViewingKey() {}
@ -124,7 +142,10 @@ public:
return keyId;
}
const std::optional<CChainablePubKey>& GetTransparentKey() const {
/**
* Return the transparent key at the account level;
*/
const std::optional<transparent::AccountPubKey>& GetTransparentKey() const {
return transparentKey;
}
@ -172,7 +193,14 @@ public:
* If the provided set is empty, return the change address
* corresponding to the most-preferred pool.
*/
RecipientAddress GetChangeAddress(const std::set<ChangeType>& changeOptions) const;
std::optional<RecipientAddress> GetChangeAddress(const ChangeRequest& req) const;
/**
* Return the transparent change address for this UFVK a the given diversifier
* index, if the UFVK has a transparent component and it is possible to derive
* an address at this index.
*/
std::optional<CKeyID> GetTransparentChangeAddress(const diversifier_index_t& j) const;
friend bool operator==(const ZcashdUnifiedFullViewingKey& a, const ZcashdUnifiedFullViewingKey& b)
{
@ -185,17 +213,19 @@ public:
*/
class ZcashdUnifiedSpendingKey {
private:
CExtKey transparentKey;
transparent::AccountKey transparentKey;
SaplingExtendedSpendingKey saplingKey;
ZcashdUnifiedSpendingKey() {}
ZcashdUnifiedSpendingKey(
transparent::AccountKey tkey,
SaplingExtendedSpendingKey skey): transparentKey(tkey), saplingKey(skey) {}
public:
static std::optional<ZcashdUnifiedSpendingKey> ForAccount(
const HDSeed& seed,
uint32_t bip44CoinType,
libzcash::AccountId accountId);
const CExtKey& GetTransparentKey() const {
const transparent::AccountKey& GetTransparentKey() const {
return transparentKey;
}