Merge pull request #5569 from str4d/feature/wallet_orchard-ua_integration

Integrate Orchard into Unified Address types
This commit is contained in:
Kris Nuttycombe 2022-02-23 11:20:49 -07:00 committed by GitHub
commit 49065bee59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 560 additions and 122 deletions

View File

@ -8,7 +8,7 @@ replace-with = "vendored-sources"
[source."https://github.com/zcash/orchard.git"]
git = "https://github.com/zcash/orchard.git"
rev = "4dc1ae059a59ee911134cb3e731c7be627a71d4d"
rev = "3b8d07f7b64b2329622089ac9698e4cce97e2f14"
replace-with = "vendored-sources"
[source."https://github.com/nuttycom/hdwallet.git"]

2
Cargo.lock generated
View File

@ -1090,7 +1090,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "orchard"
version = "0.1.0-beta.1"
source = "git+https://github.com/zcash/orchard.git?rev=4dc1ae059a59ee911134cb3e731c7be627a71d4d#4dc1ae059a59ee911134cb3e731c7be627a71d4d"
source = "git+https://github.com/zcash/orchard.git?rev=3b8d07f7b64b2329622089ac9698e4cce97e2f14#3b8d07f7b64b2329622089ac9698e4cce97e2f14"
dependencies = [
"aes",
"arrayvec 0.7.2",

View File

@ -72,7 +72,7 @@ codegen-units = 1
[patch.crates-io]
hdwallet = { git = "https://github.com/nuttycom/hdwallet", rev = "576683b9f2865f1118c309017ff36e01f84420c9" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "4dc1ae059a59ee911134cb3e731c7be627a71d4d" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "3b8d07f7b64b2329622089ac9698e4cce97e2f14" }
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "3d935a94e75786a67c3ea4992d7c372af203086f" }
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "3d935a94e75786a67c3ea4992d7c372af203086f" }
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "3d935a94e75786a67c3ea4992d7c372af203086f" }

View File

@ -73,6 +73,12 @@ namespace libzcash {
public:
ReceiverToString() {}
std::string operator()(const OrchardRawAddress &zaddr) const {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << zaddr;
return tfm::format("Orchard(%s)", HexStr(ss.begin(), ss.end()));
}
std::string operator()(const SaplingPaymentAddress &zaddr) const {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << zaddr;
@ -126,8 +132,11 @@ TEST(Keys, EncodeAndDecodeUnifiedAddresses)
// These were added to the UA in preference order by the Python test vectors.
if (!test[3].isNull()) {
auto data = ParseHex(test[3].get_str());
libzcash::UnknownReceiver r(0x03, data);
ua.AddReceiver(r);
CDataStream ss(
data,
SER_NETWORK,
PROTOCOL_VERSION);
ua.AddReceiver(libzcash::OrchardRawAddress::Read(ss));
}
if (!test[2].isNull()) {
auto data = ParseHex(test[2].get_str());
@ -186,6 +195,16 @@ TEST(Keys, DeriveUnifiedFullViewingKeys)
if (test.size() == 1) continue; // comment
try {
// [
// t_key_bytes,
// sapling_fvk_bytes,
// orchard_fvk_bytes,
// unknown_fvk_typecode,
// unknown_fvk_bytes,
// unified_fvk,
// root_seed,
// account,
// ],
auto seed_hex = test[6].get_str();
auto seed_data = ParseHex(seed_hex);
RawHDSeed raw_seed(seed_data.begin(), seed_data.end());
@ -235,6 +254,24 @@ TEST(Keys, DeriveUnifiedFullViewingKeys)
auto key = libzcash::SaplingDiversifiableFullViewingKey::Read(ss);
EXPECT_EQ(key, saplingKey);
}
if (!test[2].isNull()) {
auto expectedHex = test[2].get_str();
// Ensure that the serialized Orchard fvk matches the test data.
auto orchardKey = ufvk.GetOrchardKey().value();
CDataStream ssEncode(SER_NETWORK, PROTOCOL_VERSION);
ssEncode << orchardKey;
EXPECT_EQ(ssEncode.size(), 96);
auto skeyHex = HexStr(ssEncode.begin(), ssEncode.end());
EXPECT_EQ(expectedHex, skeyHex);
// Ensure that parsing the test data derives the correct dfvk
auto data = ParseHex(expectedHex);
ASSERT_EQ(data.size(), 96);
CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION);
auto key = libzcash::OrchardFullViewingKey::Read(ss);
EXPECT_EQ(key, orchardKey);
}
// Enable the following after Orchard keys are supported.
//{
// auto fvk_data = ParseHex(test[5].get_str());
@ -284,14 +321,13 @@ TEST(Keys, EncodeAndDecodeUnifiedFullViewingKeys)
auto key = libzcash::SaplingDiversifiableFullViewingKey::Read(ss);
ASSERT_TRUE(builder.AddSaplingKey(key));
}
// Orchard keys and unknown items are not yet supported; instead,
// we just test that we're able to parse the unified key string
// and that the constituent items match the elements; if no Sapling
// key is present then UFVK construction would fail because it might
// presume the UFVK to be transparent-only.
if (test[1].isNull())
continue;
if (!test[2].isNull()) {
auto data = ParseHex(test[2].get_str());
ASSERT_EQ(data.size(), 96);
CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION);
auto key = libzcash::OrchardFullViewingKey::Read(ss);
ASSERT_TRUE(builder.AddOrchardKey(key));
}
auto built = builder.build();
ASSERT_TRUE(built.has_value());
@ -304,5 +340,6 @@ TEST(Keys, EncodeAndDecodeUnifiedFullViewingKeys)
EXPECT_EQ(decoded.value().GetTransparentKey(), built.value().GetTransparentKey());
EXPECT_EQ(decoded.value().GetSaplingKey(), built.value().GetSaplingKey());
EXPECT_EQ(decoded.value().GetOrchardKey(), built.value().GetOrchardKey());
}
}

View File

@ -557,17 +557,22 @@ TEST(KeystoreTests, StoreAndRetrieveUFVK) {
EXPECT_EQ(keyStore.GetUnifiedFullViewingKey(ufvkid).value(), zufvk);
auto addrPair = std::get<std::pair<UnifiedAddress, diversifier_index_t>>(zufvk.FindAddress(diversifier_index_t(0), {ReceiverType::Sapling}));
EXPECT_TRUE(addrPair.first.GetSaplingReceiver().has_value());
auto saplingReceiver = addrPair.first.GetSaplingReceiver().value();
auto ufvkmeta = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
EXPECT_FALSE(ufvkmeta.has_value());
// We detect this even though we haven't added the Sapling address, because
// we trial-decrypt diversifiers (which also means we learn the index).
auto ufvkmetaUnadded = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
EXPECT_TRUE(ufvkmetaUnadded.has_value());
EXPECT_EQ(ufvkmetaUnadded.value().first, ufvkid);
EXPECT_EQ(ufvkmetaUnadded.value().second.value(), addrPair.second);
// Adding the Sapling addr -> ivk map entry causes us to find the same UFVK,
// but as we're no longer trial-decrypting we don't learn the index.
auto saplingIvk = zufvk.GetSaplingKey().value().ToIncomingViewingKey();
keyStore.AddSaplingPaymentAddress(saplingIvk, saplingReceiver);
ufvkmeta = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
auto ufvkmeta = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
EXPECT_TRUE(ufvkmeta.has_value());
EXPECT_EQ(ufvkmeta.value().first, ufvkid);
EXPECT_FALSE(ufvkmeta.value().second.has_value());

View File

@ -56,6 +56,7 @@ class DataLenForReceiver {
public:
DataLenForReceiver() {}
size_t operator()(const libzcash::OrchardRawAddress &zaddr) const { return 43; }
size_t operator()(const libzcash::SaplingPaymentAddress &zaddr) const { return 43; }
size_t operator()(const CScriptID &p2sh) const { return 20; }
size_t operator()(const CKeyID &p2pkh) const { return 20; }
@ -76,6 +77,13 @@ class CopyDataForReceiver {
public:
CopyDataForReceiver(unsigned char* data, size_t length) : data(data), length(length) {}
void operator()(const libzcash::OrchardRawAddress &zaddr) const {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << zaddr;
assert(length == ss.size());
memcpy(data, ss.data(), ss.size());
}
void operator()(const libzcash::SaplingPaymentAddress &zaddr) const {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << zaddr;
@ -433,6 +441,12 @@ std::optional<T1> DecodeAny(
return std::nullopt;
}
static bool AddOrchardReceiver(void* ua, OrchardRawAddressPtr* ptr)
{
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(
libzcash::OrchardRawAddress::KeyIoOnlyFromReceiver(ptr));
}
/**
* `raw` MUST be 43 bytes.
*/
@ -492,6 +506,7 @@ std::optional<libzcash::PaymentAddress> KeyIO::DecodePaymentAddress(const std::s
str.c_str(),
keyConstants.NetworkIDString().c_str(),
&ua,
AddOrchardReceiver,
AddSaplingReceiver,
AddP2SHReceiver,
AddP2PKHReceiver,

View File

@ -252,6 +252,10 @@ bool CBasicKeyStore::GetSproutViewingKey(
return false;
}
//
// Sapling Keys
//
bool CBasicKeyStore::GetSaplingFullViewingKey(
const libzcash::SaplingIncomingViewingKey &ivk,
libzcash::SaplingExtendedFullViewingKey &extfvkOut) const
@ -308,14 +312,26 @@ bool CBasicKeyStore::AddUnifiedFullViewingKey(
{
LOCK(cs_KeyStore);
auto ufvkId = ufvk.GetKeyID();
// Add the Orchard component of the UFVK to the wallet.
auto orchardKey = ufvk.GetOrchardKey();
if (orchardKey.has_value()) {
auto ivk = orchardKey.value().ToIncomingViewingKey();
mapOrchardKeyUnified.insert(std::make_pair(ivk, ufvkId));
auto ivkInternal = orchardKey.value().ToInternalIncomingViewingKey();
mapOrchardKeyUnified.insert(std::make_pair(ivkInternal, ufvkId));
}
// Add the Sapling component of the UFVK to the wallet.
auto saplingKey = ufvk.GetSaplingKey();
if (saplingKey.has_value()) {
auto ivk = saplingKey.value().ToIncomingViewingKey();
mapSaplingKeyUnified.insert(std::make_pair(ivk, ufvk.GetKeyID()));
mapSaplingKeyUnified.insert(std::make_pair(ivk, ufvkId));
auto changeIvk = saplingKey.value().GetChangeIVK();
mapSaplingKeyUnified.insert(std::make_pair(changeIvk, ufvk.GetKeyID()));
mapSaplingKeyUnified.insert(std::make_pair(changeIvk, ufvkId));
}
// We can't reasonably add the transparent component here, because
@ -325,7 +341,7 @@ bool CBasicKeyStore::AddUnifiedFullViewingKey(
// transparent part of the address must be added to the keystore.
// Add the UFVK by key identifier.
mapUnifiedFullViewingKeys.insert({ufvk.GetKeyID(), ufvk});
mapUnifiedFullViewingKeys.insert({ufvkId, ufvk});
return true;
}
@ -386,6 +402,15 @@ std::optional<libzcash::UFVKId> CBasicKeyStore::GetUFVKIdForViewingKey(const lib
}
},
[&](const libzcash::UnifiedFullViewingKey& ufvk) {
const auto orchardFvk = ufvk.GetOrchardKey();
if (orchardFvk.has_value()) {
const auto orchardIvk = orchardFvk.value().ToIncomingViewingKey();
const auto ufvkId = mapOrchardKeyUnified.find(orchardIvk);
if (ufvkId != mapOrchardKeyUnified.end()) {
result = ufvkId->second;
return;
}
}
const auto saplingDfvk = ufvk.GetSaplingKey();
if (saplingDfvk.has_value()) {
const auto saplingIvk = saplingDfvk.value().ToIncomingViewingKey();
@ -399,19 +424,54 @@ std::optional<libzcash::UFVKId> CBasicKeyStore::GetUFVKIdForViewingKey(const lib
return result;
}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
FindUFVKId::operator()(const libzcash::OrchardRawAddress& orchardAddr) const {
for (const auto& [k, v] : keystore.mapUnifiedFullViewingKeys) {
auto fvk = v.GetOrchardKey();
if (fvk.has_value()) {
auto d_idx = fvk.value().ToIncomingViewingKey().DecryptDiversifier(orchardAddr);
if (d_idx.has_value()) {
return std::make_pair(k, d_idx);
}
}
}
return std::nullopt;
}
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()) {
// We have either generated this as a receiver via `z_getaddressforaccount` or a
// legacy Sapling address via `z_getnewaddress`, or we have previously detected
// this via trial-decryption of a note.
const auto ufvkId = keystore.mapSaplingKeyUnified.find(saplingIvk->second);
if (ufvkId != keystore.mapSaplingKeyUnified.end()) {
return std::make_pair(ufvkId->second, std::nullopt);
} else {
// If we have the addr -> ivk map entry but not the ivk -> UFVK map entry,
// then this is definitely a legacy Sapling address.
return std::nullopt;
}
} else {
return std::nullopt;
}
// We haven't generated this receiver via `z_getaddressforaccount` (or this is a
// recovery from a backed-up mnemonic which doesn't store receiver types selected by
// users). Trial-decrypt the diversifier of the Sapling address with every UFVK in the
// wallet, to check directly if it belongs to any of them.
for (const auto& [k, v] : keystore.mapUnifiedFullViewingKeys) {
auto dfvk = v.GetSaplingKey();
if (dfvk.has_value()) {
auto d_idx = dfvk.value().DecryptDiversifier(saplingAddr.d);
auto derived_addr = dfvk.value().Address(d_idx);
if (derived_addr.has_value() && derived_addr.value() == saplingAddr) {
return std::make_pair(k, d_idx);
}
}
}
// We definitely don't know of any UFVK linked to this Sapling address.
return std::nullopt;
}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
FindUFVKId::operator()(const CScriptID& scriptId) const {

View File

@ -185,6 +185,7 @@ protected:
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::OrchardIncomingViewingKey, libzcash::UFVKId> mapOrchardKeyUnified;
std::map<libzcash::UFVKId, libzcash::ZcashdUnifiedFullViewingKey> mapUnifiedFullViewingKeys;
friend class FindUFVKId;
@ -398,6 +399,8 @@ private:
public:
FindUFVKId(const CBasicKeyStore& keystore): keystore(keystore) {}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
operator()(const libzcash::OrchardRawAddress& orchardAddr) const;
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>>>

View File

@ -5,10 +5,13 @@
#ifndef ZCASH_RUST_INCLUDE_RUST_ADDRESS_H
#define ZCASH_RUST_INCLUDE_RUST_ADDRESS_H
#include "rust/orchard/keys.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef bool (*orchard_receiver_t)(void* ua, OrchardRawAddressPtr* addr);
typedef bool (*raw_to_receiver_t)(void* ua, const unsigned char* raw);
typedef bool (*unknown_receiver_t)(
void* ua,
@ -24,6 +27,7 @@ bool zcash_address_parse_unified(
const char* str,
const char* network,
void* ua,
orchard_receiver_t orchard_cb,
raw_to_receiver_t sapling_cb,
raw_to_receiver_t p2sh_cb,
raw_to_receiver_t p2pkh_cb,

View File

@ -32,6 +32,34 @@ OrchardRawAddressPtr* orchard_address_clone(
*/
void orchard_address_free(OrchardRawAddressPtr* ptr);
/**
* Parses Orchard raw address bytes from the given stream.
*
* - If the key does not parse correctly, the returned pointer will be null.
*/
OrchardRawAddressPtr* orchard_raw_address_parse(
void* stream,
read_callback_t read_cb);
/**
* Serializes Orchard raw address bytes to the given stream.
*
* This will return `false` and leave the stream unmodified if
* `raw_address == nullptr`;
*/
bool orchard_raw_address_serialize(
const OrchardRawAddressPtr* raw_address,
void* stream,
write_callback_t write_cb);
/**
* Implements the "equal" operation for comparing two Orchard addresses.
*/
bool orchard_address_eq(
const OrchardRawAddressPtr* k0,
const OrchardRawAddressPtr* k1);
/**
* Implements the "less than" operation `k0 < k1` for comparing two Orchard
* addresses. This is a comparison of the raw bytes, only useful for cases
@ -69,6 +97,18 @@ OrchardRawAddressPtr* orchard_incoming_viewing_key_to_address(
const OrchardIncomingViewingKeyPtr* incoming_viewing_key,
const unsigned char* j);
/**
* Decrypts the diversifier component of an Orchard raw address with the
* specified IVK, and verifies that the address was derived from that IVK.
*
* Returns `false` and leaves the `j_ret` parameter unmodified if the address
* was not derived from the specified IVK.
*/
bool orchard_incoming_viewing_key_decrypt_diversifier(
const OrchardIncomingViewingKeyPtr* incoming_viewing_key,
const OrchardRawAddressPtr* addr,
uint8_t *j_ret);
/**
* Parses an Orchard incoming viewing key from the given stream.
*
@ -151,6 +191,12 @@ bool orchard_full_viewing_key_serialize(
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_incoming_viewing_key(
const OrchardFullViewingKeyPtr* key);
/**
* Returns the internal incoming viewing key for the specified full viewing key.
*/
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_internal_incoming_viewing_key(
const OrchardFullViewingKeyPtr* key);
/**
* Implements equality testing between full viewing keys.
*/
@ -189,26 +235,6 @@ OrchardSpendingKeyPtr* orchard_spending_key_clone(
*/
void orchard_spending_key_free(OrchardSpendingKeyPtr* ptr);
/**
* Parses an Orchard spending key from the given stream.
*
* - If the key does not parse correctly, the returned pointer will be null.
*/
OrchardSpendingKeyPtr* orchard_spending_key_parse(
void* stream,
read_callback_t read_cb);
/**
* Serializes an Orchard spending key to the given stream.
*
* This will return `false` and leave the stream unmodified if
* `spending_key == nullptr`.
*/
bool orchard_spending_key_serialize(
const OrchardSpendingKeyPtr* spending_key,
void* stream,
write_callback_t write_cb);
/**
* Returns the full viewing key for the specified spending key.
*

View File

@ -84,6 +84,22 @@ bool unified_full_viewing_key_read_sapling(
const UnifiedFullViewingKeyPtr* full_viewing_key,
unsigned char* skeyout);
/**
* Reads the Orchard component of a unified full viewing key.
*
* `skeyout` must be of length 96.
*
* Returns `true` if the UFVK contained an Orchard component, `false` otherwise.
* The bytes of the Orchard Raw Full Viewing Key, in the encoding given in
* section 5.6.4.4 of the Zcash Protocol Specification, will be copied to
* `skeyout` if `true` is returned.
*
* If `false` is returned then `skeyout` will be unchanged.
*/
bool unified_full_viewing_key_read_orchard(
const UnifiedFullViewingKeyPtr* full_viewing_key,
unsigned char* skeyout);
/**
* Constructs a unified full viewing key from the binary encodings
* of its constituent parts.
@ -101,7 +117,8 @@ bool unified_full_viewing_key_read_sapling(
*/
UnifiedFullViewingKeyPtr* unified_full_viewing_key_from_components(
const unsigned char* t_key,
const unsigned char* sapling_key);
const unsigned char* sapling_key,
const unsigned char* orchard_key);
/**
* Derive the internal and external OVKs for the binary encoding

View File

@ -12,6 +12,8 @@ use zcash_address::{
use zcash_primitives::sapling;
pub type UnifiedAddressObj = NonNull<c_void>;
pub type AddOrchardReceiverCb =
unsafe extern "C" fn(ua: Option<UnifiedAddressObj>, orchard: *const orchard::Address) -> bool;
pub type AddReceiverCb =
unsafe extern "C" fn(ua: Option<UnifiedAddressObj>, raw: *const u8) -> bool;
pub type UnknownReceiverCb = unsafe extern "C" fn(
@ -53,10 +55,12 @@ impl FromAddress for UnifiedAddressHelper {
}
impl UnifiedAddressHelper {
#[allow(clippy::too_many_arguments)]
fn into_cpp(
self,
network: Network,
ua_obj: Option<UnifiedAddressObj>,
orchard_cb: Option<AddOrchardReceiverCb>,
sapling_cb: Option<AddReceiverCb>,
p2sh_cb: Option<AddReceiverCb>,
p2pkh_cb: Option<AddReceiverCb>,
@ -79,16 +83,13 @@ impl UnifiedAddressHelper {
// ZIP 316: Consumers MUST reject Unified Addresses/Viewing Keys in
// which any constituent Item does not meet the validation
// requirements of its encoding.
if orchard::Address::from_raw_address_bytes(&data)
.is_none()
.into()
{
let addr = orchard::Address::from_raw_address_bytes(&data);
if addr.is_none().into() {
tracing::error!("Unified Address contains invalid Orchard receiver");
false
} else {
unsafe {
// TODO: Replace with Orchard support.
(unknown_cb.unwrap())(ua_obj, 0x03, data.as_ptr(), data.len())
(orchard_cb.unwrap())(ua_obj, Box::into_raw(Box::new(addr.unwrap())))
}
}
}
@ -122,6 +123,7 @@ pub extern "C" fn zcash_address_parse_unified(
encoded: *const c_char,
network: *const c_char,
ua_obj: Option<UnifiedAddressObj>,
orchard_cb: Option<AddOrchardReceiverCb>,
sapling_cb: Option<AddReceiverCb>,
p2sh_cb: Option<AddReceiverCb>,
p2pkh_cb: Option<AddReceiverCb>,
@ -149,7 +151,9 @@ pub extern "C" fn zcash_address_parse_unified(
}
};
ua.into_cpp(network, ua_obj, sapling_cb, p2sh_cb, p2pkh_cb, unknown_cb)
ua.into_cpp(
network, ua_obj, orchard_cb, sapling_cb, p2sh_cb, p2pkh_cb, unknown_cb,
)
}
#[no_mangle]
@ -171,14 +175,9 @@ pub extern "C" fn zcash_address_serialize_unified(
Ok(
match unsafe { (typecode_cb.unwrap())(ua_obj, i) }.try_into()? {
unified::Typecode::Orchard => {
// TODO: Replace with Orchard support.
let data_len = unsafe { (receiver_len_cb.unwrap())(ua_obj, i) };
let mut data = vec![0; data_len];
unsafe { (receiver_cb.unwrap())(ua_obj, i, data.as_mut_ptr(), data_len) };
unified::Receiver::Unknown {
typecode: 0x03,
data,
}
let mut data = [0; 43];
unsafe { (receiver_cb.unwrap())(ua_obj, i, data.as_mut_ptr(), data.len()) };
unified::Receiver::Orchard(data)
}
unified::Typecode::Sapling => {
let mut data = [0; 43];

View File

@ -28,6 +28,56 @@ pub extern "C" fn orchard_address_free(addr: *mut Address) {
}
}
#[no_mangle]
pub extern "C" fn orchard_raw_address_parse(
stream: Option<StreamObj>,
read_cb: Option<ReadCb>,
) -> *mut Address {
let mut reader = CppStreamReader::from_raw_parts(stream, read_cb.unwrap());
let mut buf = [0u8; 43];
match reader.read_exact(&mut buf) {
Err(e) => {
error!("Stream failure reading bytes of Orchard raw address: {}", e);
std::ptr::null_mut()
}
Ok(()) => {
let read = Address::from_raw_address_bytes(&buf);
if read.is_some().into() {
Box::into_raw(Box::new(read.unwrap()))
} else {
error!("Failed to parse Orchard raw address.");
std::ptr::null_mut()
}
}
}
}
#[no_mangle]
pub extern "C" fn orchard_raw_address_serialize(
key: *const Address,
stream: Option<StreamObj>,
write_cb: Option<WriteCb>,
) -> bool {
let key = unsafe { key.as_ref() }.expect("Orchard raw address pointer may not be null.");
let mut writer = CppStreamWriter::from_raw_parts(stream, write_cb.unwrap());
match writer.write_all(&key.to_raw_address_bytes()) {
Ok(()) => true,
Err(e) => {
error!("Stream failure writing Orchard raw address: {}", e);
false
}
}
}
#[no_mangle]
pub extern "C" fn orchard_address_eq(a0: *const Address, a1: *const Address) -> bool {
let a0 = unsafe { a0.as_ref() };
let a1 = unsafe { a1.as_ref() };
a0 == a1
}
#[no_mangle]
pub extern "C" fn orchard_address_lt(a0: *const Address, a1: *const Address) -> bool {
let a0 = unsafe { a0.as_ref() };
@ -95,6 +145,26 @@ pub extern "C" fn orchard_incoming_viewing_key_to_address(
Box::into_raw(Box::new(key.address_at(diversifier_index)))
}
#[no_mangle]
pub extern "C" fn orchard_incoming_viewing_key_decrypt_diversifier(
key: *const IncomingViewingKey,
addr: *const Address,
j_ret: *mut [u8; 11],
) -> bool {
let key =
unsafe { key.as_ref() }.expect("Orchard incoming viewing key pointer may not be null.");
let addr = unsafe { addr.as_ref() }.expect("Orchard raw address pointer may not be null.");
let j_ret = unsafe { j_ret.as_mut() }.expect("j_ret may not be null.");
match key.diversifier_index(addr) {
Some(j) => {
j_ret.copy_from_slice(j.to_bytes());
true
}
None => false,
}
}
#[no_mangle]
pub extern "C" fn orchard_incoming_viewing_key_serialize(
key: *const IncomingViewingKey,
@ -200,6 +270,18 @@ pub extern "C" fn orchard_full_viewing_key_to_incoming_viewing_key(
.unwrap_or(std::ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn orchard_full_viewing_key_to_internal_incoming_viewing_key(
fvk: *const FullViewingKey,
) -> *mut IncomingViewingKey {
unsafe { fvk.as_ref() }
.map(|fvk| {
let internal_fvk = fvk.derive_internal();
Box::into_raw(Box::new(IncomingViewingKey::from(&internal_fvk)))
})
.unwrap_or(std::ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn orchard_full_viewing_key_eq(
k0: *const FullViewingKey,

View File

@ -139,13 +139,33 @@ pub extern "C" fn unified_full_viewing_key_read_sapling(
false
}
#[no_mangle]
pub extern "C" fn unified_full_viewing_key_read_orchard(
key: *const Ufvk,
out: *mut [u8; 96],
) -> bool {
let key = unsafe { key.as_ref() }.expect("Unified full viewing key pointer may not be null.");
let out = unsafe { &mut *out };
for r in &key.items() {
if let Fvk::Orchard(data) = r {
*out = *data;
return true;
}
}
false
}
#[no_mangle]
pub extern "C" fn unified_full_viewing_key_from_components(
t_key: *const [u8; 65],
sapling_key: *const [u8; 128],
orchard_key: *const [u8; 96],
) -> *mut Ufvk {
let t_key = unsafe { t_key.as_ref() };
let sapling_key = unsafe { sapling_key.as_ref() };
let orchard_key = unsafe { orchard_key.as_ref() };
let mut items = vec![];
if let Some(t_bytes) = t_key {
@ -154,6 +174,9 @@ pub extern "C" fn unified_full_viewing_key_from_components(
if let Some(sapling_bytes) = sapling_key {
items.push(Fvk::Sapling(*sapling_bytes));
}
if let Some(orchard_bytes) = orchard_key {
items.push(Fvk::Orchard(*orchard_bytes));
}
match Ufvk::try_from_items(items) {
Ok(ufvk) => Box::into_raw(Box::new(ufvk)),

View File

@ -340,6 +340,9 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
case ReceiverType::Sapling:
allowedChangeTypes_.insert(libzcash::ChangeType::Sapling);
break;
case ReceiverType::Orchard:
// TODO
break;
}
}

View File

@ -33,19 +33,3 @@ TEST(OrchardZkeysTest, FVKSerializationRoundtrip) {
ASSERT_EQ(fvk, fvk0);
}
TEST(OrchardZkeysTest, SKSerializationRoundtrip) {
auto seed = MnemonicSeed::Random(1); //testnet coin type
auto sk = libzcash::OrchardSpendingKey::ForAccount(seed, 1, 0);
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << sk;
std::string skStr = ss.str();
auto sk0 = libzcash::OrchardSpendingKey::Read(ss);
CDataStream ss0(SER_NETWORK, PROTOCOL_VERSION);
ss0 << sk0;
std::string sk0Str = ss0.str();
ASSERT_EQ(skStr, sk0Str);
}

View File

@ -2200,14 +2200,15 @@ TEST(WalletTests, GenerateUnifiedAddress) {
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 ufvkpair = wallet.GenerateNewUnifiedSpendingKey();
auto ufvk = ufvkpair.first;
auto account = ufvkpair.second;
uaResult = wallet.GenerateUnifiedAddress(account, {ReceiverType::P2PKH, ReceiverType::Sapling});
auto ua = std::get_if<std::pair<libzcash::UnifiedAddress, libzcash::diversifier_index_t>>(&uaResult);
EXPECT_NE(ua, nullptr);
auto uaSaplingReceiver = ua->first.GetSaplingReceiver();
EXPECT_TRUE(uaSaplingReceiver.has_value());
auto ufvk = skpair.first.ToFullViewingKey();
EXPECT_EQ(uaSaplingReceiver.value(), ufvk.GetSaplingKey().value().Address(ua->second));
auto u4r = wallet.FindUnifiedAddressByReceiver(uaSaplingReceiver.value());

View File

@ -3057,8 +3057,8 @@ UniValue z_getnewaccount(const UniValue& params, bool fHelp)
EnsureWalletIsUnlocked();
// Generate the new account.
auto skNew = pwalletMain->GenerateNewUnifiedSpendingKey();
const auto& account = skNew.second;
auto ufvkNew = pwalletMain->GenerateNewUnifiedSpendingKey();
const auto& account = ufvkNew.second;
UniValue result(UniValue::VOBJ);
result.pushKV("account", (uint64_t)account);

View File

@ -477,7 +477,7 @@ libzcash::transparent::AccountKey CWallet::GetLegacyAccountKey() const {
}
std::pair<ZcashdUnifiedSpendingKey, libzcash::AccountId> CWallet::GenerateNewUnifiedSpendingKey() {
std::pair<UnifiedFullViewingKey, libzcash::AccountId> CWallet::GenerateNewUnifiedSpendingKey() {
AssertLockHeld(cs_wallet);
if (!mnemonicHDChain.has_value()) {
@ -488,17 +488,17 @@ std::pair<ZcashdUnifiedSpendingKey, libzcash::AccountId> CWallet::GenerateNewUni
CHDChain& hdChain = mnemonicHDChain.value();
while (true) {
auto accountId = hdChain.GetAccountCounter();
auto usk = GenerateUnifiedSpendingKeyForAccount(accountId);
auto generated = GenerateUnifiedSpendingKeyForAccount(accountId);
hdChain.IncrementAccountCounter();
if (usk.has_value()) {
if (generated.has_value()) {
// Update the persisted chain information
if (fFileBacked && !CWalletDB(strWalletFile).WriteMnemonicHDChain(hdChain)) {
throw std::runtime_error(
"CWallet::GenerateNewUnifiedSpendingKey(): Writing HD chain model failed");
}
return std::make_pair(usk.value(), accountId);
return std::make_pair(generated.value().ToFullViewingKey(), accountId);
}
}
}
@ -554,7 +554,7 @@ std::optional<libzcash::ZcashdUnifiedSpendingKey>
);
// Add the Sapling spending key to the wallet
auto saplingEsk = usk.value().GetSaplingExtendedSpendingKey();
auto saplingEsk = usk.value().GetSaplingKey();
if (addSaplingKey(saplingEsk) == KeyNotAdded) {
// If adding the Sapling key to the wallet failed, abort the process.
throw std::runtime_error("CWalletDB::GenerateUnifiedSpendingKeyForAccount(): Unable to add Sapling spending key to the wallet.");
@ -576,7 +576,8 @@ std::optional<libzcash::ZcashdUnifiedSpendingKey>
throw std::runtime_error("CWallet::GenerateUnifiedSpendingKeyForAccount(): Failed to add Sapling change address to the wallet.");
};
// TODO ORCHARD: Add Orchard component to the wallet
// Add Orchard spending key to the wallet
orchardWallet.AddSpendingKey(usk.value().GetOrchardKey());
auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), ufvk);
if (!CCryptoKeyStore::AddUnifiedFullViewingKey(zufvk)) {
@ -725,8 +726,9 @@ WalletUAGenerationResult CWallet::GenerateUnifiedAddress(
assert(mapUfvkAddressMetadata[ufvkid].SetReceivers(address.second, receiverTypes));
if (hasTransparent) {
// We must construct and add the transparent spending key associated
// with the external transparent child address to the transparent
// keystore.
// with the external and internal transparent child addresses to the
// transparent keystore. This call to `value` will succeed because
// this key must have been previously generated.
auto usk = GenerateUnifiedSpendingKeyForAccount(accountId).value();
auto accountKey = usk.GetTransparentKey();
// this .value is known to be safe from the earlier check
@ -6425,6 +6427,9 @@ std::optional<libzcash::ViewingKey> GetViewingKeyForPaymentAddress::operator()(
{
return std::nullopt;
}
// GetViewingKeyForPaymentAddress visitor
std::optional<libzcash::ViewingKey> GetViewingKeyForPaymentAddress::operator()(
const libzcash::SproutPaymentAddress &zaddr) const
{
@ -6551,6 +6556,20 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedS
// UFVKForReceiver :: (CWallet&, Receiver) -> std::optional<ZcashdUnifiedFullViewingKey>
std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()(const libzcash::OrchardRawAddress& orchardAddr) const {
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(orchardAddr);
if (ufvkPair.has_value()) {
auto ufvkid = ufvkPair.value().first;
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
// If we have UFVK metadata, `GetUnifiedFullViewingKey` should always
// return a non-nullopt value, and since we obtained that metadata by
// lookup from an Orchard address, it should have a Orchard key component.
assert(ufvk.has_value() && ufvk.value().GetOrchardKey().has_value());
return ufvk.value();
} else {
return std::nullopt;
}
}
std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(saplingAddr);
if (ufvkPair.has_value()) {
@ -6590,6 +6609,35 @@ std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()
// UnifiedAddressForReceiver :: (CWallet&, Receiver) -> std::optional<UnifiedAddress>
std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(
const libzcash::OrchardRawAddress& orchardAddr) const {
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(orchardAddr);
if (ufvkPair.has_value()) {
auto ufvkid = ufvkPair.value().first;
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
assert(ufvk.has_value());
// 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);
auto orchardKey = ufvk.value().GetOrchardKey();
if (orchardKey.has_value()) {
auto j = orchardKey.value().ToIncomingViewingKey().DecryptDiversifier(orchardAddr);
if (j.has_value()) {
auto receivers = metadata.GetReceivers(j.value());
if (receivers.has_value()) {
auto addr = ufvk.value().Address(j.value(), receivers.value());
auto addrPtr = std::get_if<std::pair<UnifiedAddress, diversifier_index_t>>(&addr);
if (addrPtr != nullptr) {
return addrPtr->first;
}
}
}
}
}
return std::nullopt;
}
std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(saplingAddr);
if (ufvkPair.has_value()) {

View File

@ -1480,7 +1480,7 @@ public:
//! Generate the unified spending key from the wallet's mnemonic seed
//! for the next unused account identifier.
std::pair<libzcash::ZcashdUnifiedSpendingKey, libzcash::AccountId>
std::pair<libzcash::UnifiedFullViewingKey, libzcash::AccountId>
GenerateNewUnifiedSpendingKey();
//! Generate the unified spending key for the specified ZIP-32/BIP-44
@ -1816,6 +1816,7 @@ public:
// Shielded key and address generalizations
//
// PaymentAddressBelongsToWallet visitor :: (CWallet&, PaymentAddress) -> bool
class PaymentAddressBelongsToWallet
{
private:
@ -1830,6 +1831,7 @@ public:
bool operator()(const libzcash::UnifiedAddress &uaddr) const;
};
// GetViewingKeyForPaymentAddress visitor :: (CWallet&, PaymentAddress) -> std::optional<ViewingKey>
class GetViewingKeyForPaymentAddress
{
private:
@ -1853,6 +1855,7 @@ enum class PaymentAddressSource {
AddressNotFound,
};
// GetSourceForPaymentAddress visitor :: (CWallet&, PaymentAddress) -> PaymentAddressSource
class GetSourceForPaymentAddress
{
private:
@ -1876,6 +1879,7 @@ enum KeyAddResult {
KeyNotAdded,
};
// AddViewingKeyToWallet visitor :: (CWallet&, ViewingKey) -> KeyAddResult
class AddViewingKeyToWallet
{
private:
@ -1889,6 +1893,8 @@ public:
KeyAddResult operator()(const libzcash::UnifiedFullViewingKey &sk) const;
};
// AddSpendingKeyToWallet visitor ::
// (CWallet&, Consensus::Params, ..., ViewingKey) -> KeyAddResult
class AddSpendingKeyToWallet
{
private:
@ -1917,6 +1923,7 @@ public:
KeyAddResult operator()(const libzcash::SaplingExtendedSpendingKey &sk) const;
};
// UFVKForReceiver :: (CWallet&, Receiver) -> std::optional<ZcashdUnifiedFullViewingKey>
class UFVKForReceiver {
private:
const CWallet& wallet;
@ -1924,12 +1931,14 @@ private:
public:
UFVKForReceiver(const CWallet& wallet): wallet(wallet) {}
std::optional<libzcash::ZcashdUnifiedFullViewingKey> operator()(const libzcash::OrchardRawAddress& orchardAddr) const;
std::optional<libzcash::ZcashdUnifiedFullViewingKey> operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const;
std::optional<libzcash::ZcashdUnifiedFullViewingKey> operator()(const CScriptID& scriptId) const;
std::optional<libzcash::ZcashdUnifiedFullViewingKey> operator()(const CKeyID& keyId) const;
std::optional<libzcash::ZcashdUnifiedFullViewingKey> operator()(const libzcash::UnknownReceiver& receiver) const;
};
// UnifiedAddressForReceiver :: (CWallet&, Receiver) -> std::optional<UnifiedAddress>
class UnifiedAddressForReceiver {
private:
const CWallet& wallet;
@ -1937,6 +1946,7 @@ private:
public:
UnifiedAddressForReceiver(const CWallet& wallet): wallet(wallet) {}
std::optional<libzcash::UnifiedAddress> operator()(const libzcash::OrchardRawAddress& orchardAddr) const;
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;

View File

@ -73,12 +73,23 @@ std::optional<SaplingPaymentAddress> UnifiedAddress::GetSaplingReceiver() const
return std::nullopt;
}
std::optional<OrchardRawAddress> UnifiedAddress::GetOrchardReceiver() const {
for (const auto& r : receivers) {
if (std::holds_alternative<OrchardRawAddress>(r)) {
return std::get<OrchardRawAddress>(r);
}
}
return std::nullopt;
}
std::optional<RecipientAddress> UnifiedAddress::GetPreferredRecipientAddress() const {
// Return the first receiver type we recognize; receivers are sorted in
// order from most-preferred to least.
std::optional<RecipientAddress> result;
for (const auto& receiver : *this) {
std::visit(match {
[&](const OrchardRawAddress& addr) { /* TODO: Return once we enable Orchard as recipient */ },
[&](const SaplingPaymentAddress& addr) { result = addr; },
[&](const CScriptID& addr) { result = addr; },
[&](const CKeyID& addr) { result = addr; },
@ -94,6 +105,7 @@ std::optional<RecipientAddress> UnifiedAddress::GetPreferredRecipientAddress() c
bool HasKnownReceiverType(const Receiver& receiver) {
return std::visit(match {
[](const OrchardRawAddress& addr) { return true; },
[](const SaplingPaymentAddress& addr) { return true; },
[](const CScriptID& addr) { return true; },
[](const CKeyID& addr) { return true; },
@ -125,6 +137,12 @@ std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(con
} // namespace libzcash
uint32_t TypecodeForReceiver::operator()(
const libzcash::OrchardRawAddress &zaddr) const
{
return static_cast<uint32_t>(libzcash::ReceiverType::Orchard);
}
uint32_t TypecodeForReceiver::operator()(
const libzcash::SaplingPaymentAddress &zaddr) const
{
@ -174,6 +192,16 @@ std::string libzcash::UnifiedFullViewingKey::Encode(const KeyConstants& keyConst
return res;
}
std::optional<libzcash::OrchardFullViewingKey> libzcash::UnifiedFullViewingKey::GetOrchardKey() const {
std::vector<uint8_t> buffer(96);
if (unified_full_viewing_key_read_orchard(inner.get(), buffer.data())) {
CDataStream ss(buffer, SER_NETWORK, PROTOCOL_VERSION);
return OrchardFullViewingKey::Read(ss);
} else {
return std::nullopt;
}
}
std::optional<libzcash::SaplingDiversifiableFullViewingKey> libzcash::UnifiedFullViewingKey::GetSaplingKey() const {
std::vector<uint8_t> buffer(128);
if (unified_full_viewing_key_read_sapling(inner.get(), buffer.data())) {
@ -214,10 +242,21 @@ bool libzcash::UnifiedFullViewingKeyBuilder::AddSaplingKey(const SaplingDiversif
return true;
}
bool libzcash::UnifiedFullViewingKeyBuilder::AddOrchardKey(const OrchardFullViewingKey& key) {
if (orchard_bytes.has_value()) return false;
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << key;
assert(ss.size() == 96);
std::vector<uint8_t> ss_bytes(ss.begin(), ss.end());
orchard_bytes = ss_bytes;
return true;
}
std::optional<libzcash::UnifiedFullViewingKey> libzcash::UnifiedFullViewingKeyBuilder::build() const {
UnifiedFullViewingKeyPtr* ptr = unified_full_viewing_key_from_components(
t_bytes.has_value() ? t_bytes.value().data() : nullptr,
sapling_bytes.has_value() ? sapling_bytes.value().data() : nullptr);
sapling_bytes.has_value() ? sapling_bytes.value().data() : nullptr,
orchard_bytes.has_value() ? orchard_bytes.value().data() : nullptr);
if (ptr == nullptr) {
return std::nullopt;
@ -234,6 +273,9 @@ libzcash::UnifiedFullViewingKey libzcash::UnifiedFullViewingKey::FromZcashdUFVK(
if (key.GetSaplingKey().has_value()) {
builder.AddSaplingKey(key.GetSaplingKey().value());
}
if (key.GetOrchardKey().has_value()) {
builder.AddOrchardKey(key.GetOrchardKey().value());
}
auto result = builder.build();
if (!result.has_value()) {

View File

@ -84,6 +84,9 @@ public:
std::set<ReceiverType> result;
for (const auto& receiver : receivers) {
std::visit(match {
[&](const libzcash::OrchardRawAddress &zaddr) {
result.insert(ReceiverType::Orchard);
},
[&](const libzcash::SaplingPaymentAddress &zaddr) {
result.insert(ReceiverType::Sapling);
},
@ -116,6 +119,8 @@ public:
std::optional<SaplingPaymentAddress> GetSaplingReceiver() const;
std::optional<OrchardRawAddress> GetOrchardReceiver() const;
/**
* Return the most-preferred receiver from among the receiver types
* that we recognize.
@ -171,6 +176,8 @@ public:
std::string Encode(const KeyConstants& keyConstants) const;
std::optional<OrchardFullViewingKey> GetOrchardKey() const;
std::optional<SaplingDiversifiableFullViewingKey> GetSaplingKey() const;
std::optional<transparent::AccountPubKey> GetTransparentKey() const;
@ -185,6 +192,9 @@ public:
if (GetSaplingKey().has_value()) {
result.insert(ReceiverType::Sapling);
}
if (GetOrchardKey().has_value()) {
result.insert(ReceiverType::Orchard);
}
return result;
}
@ -209,11 +219,16 @@ class UnifiedFullViewingKeyBuilder {
private:
std::optional<std::vector<uint8_t>> t_bytes;
std::optional<std::vector<uint8_t>> sapling_bytes;
std::optional<std::vector<uint8_t>> orchard_bytes;
public:
UnifiedFullViewingKeyBuilder(): t_bytes(std::nullopt), sapling_bytes(std::nullopt) {}
UnifiedFullViewingKeyBuilder():
t_bytes(std::nullopt),
sapling_bytes(std::nullopt),
orchard_bytes(std::nullopt) {}
bool AddTransparentKey(const transparent::AccountPubKey&);
bool AddSaplingKey(const SaplingDiversifiableFullViewingKey&);
bool AddOrchardKey(const OrchardFullViewingKey&);
std::optional<UnifiedFullViewingKey> build() const;
};
@ -283,6 +298,7 @@ class TypecodeForReceiver {
public:
TypecodeForReceiver() {}
uint32_t operator()(const libzcash::OrchardRawAddress &zaddr) const;
uint32_t operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
uint32_t operator()(const CScriptID &p2sh) const;
uint32_t operator()(const CKeyID &p2pkh) const;

View File

@ -10,10 +10,23 @@ OrchardRawAddress OrchardIncomingViewingKey::Address(const diversifier_index_t&
return OrchardRawAddress(orchard_incoming_viewing_key_to_address(inner.get(), j.begin()));
}
std::optional<diversifier_index_t> OrchardIncomingViewingKey::DecryptDiversifier(const OrchardRawAddress& addr) const {
diversifier_index_t j_ret;
if (orchard_incoming_viewing_key_decrypt_diversifier(inner.get(), addr.inner.get(), j_ret.begin())) {
return j_ret;
} else {
return std::nullopt;
}
}
OrchardIncomingViewingKey OrchardFullViewingKey::ToIncomingViewingKey() const {
return OrchardIncomingViewingKey(orchard_full_viewing_key_to_incoming_viewing_key(inner.get()));
}
OrchardIncomingViewingKey OrchardFullViewingKey::ToInternalIncomingViewingKey() const {
return OrchardIncomingViewingKey(orchard_full_viewing_key_to_incoming_viewing_key(inner.get()));
}
OrchardSpendingKey OrchardSpendingKey::ForAccount(
const HDSeed& seed,
uint32_t bip44CoinType,

View File

@ -9,6 +9,8 @@
#include "zcash/address/zip32.h"
#include <rust/orchard/keys.h>
#include <optional>
class OrchardWallet;
namespace orchard { class Builder; }
@ -31,6 +33,10 @@ private:
friend class ::OrchardWallet;
friend class ::orchard::Builder;
public:
static OrchardRawAddress KeyIoOnlyFromReceiver(OrchardRawAddressPtr* ptr) {
return OrchardRawAddress(ptr);
}
OrchardRawAddress(OrchardRawAddress&& key) : inner(std::move(key.inner)) {}
OrchardRawAddress(const OrchardRawAddress& key) :
@ -52,9 +58,38 @@ public:
return *this;
}
friend bool operator==(const OrchardRawAddress& c1, const OrchardRawAddress& c2) {
return orchard_address_eq(c1.inner.get(), c2.inner.get());
}
friend bool operator<(const OrchardRawAddress& c1, const OrchardRawAddress& c2) {
return orchard_address_lt(c1.inner.get(), c2.inner.get());
}
template<typename Stream>
void Serialize(Stream& s) const {
RustStream rs(s);
if (!orchard_raw_address_serialize(inner.get(), &rs, RustStream<Stream>::write_callback)) {
throw std::ios_base::failure("Failed to serialize Orchard raw address to bytes");
}
}
template<typename Stream>
void Unserialize(Stream& s) {
RustStream rs(s);
OrchardRawAddressPtr* addr = orchard_raw_address_parse(&rs, RustStream<Stream>::read_callback);
if (addr == nullptr) {
throw std::ios_base::failure("Failed to parse Orchard raw address bytes");
}
inner.reset(addr);
}
template<typename Stream>
static OrchardRawAddress Read(Stream& stream) {
OrchardRawAddress key;
stream >> key;
return key;
}
};
class OrchardIncomingViewingKey
@ -80,6 +115,12 @@ public:
OrchardRawAddress Address(const diversifier_index_t& j) const;
/**
* Decrypts the diversifier for the given raw address, and returns it if that
* address was derived from this IVK; otherwise returns std::nullopt;
*/
std::optional<diversifier_index_t> DecryptDiversifier(const OrchardRawAddress& addr) const;
OrchardIncomingViewingKey& operator=(OrchardIncomingViewingKey&& key)
{
if (this != &key) {
@ -151,6 +192,8 @@ public:
OrchardIncomingViewingKey ToIncomingViewingKey() const;
OrchardIncomingViewingKey ToInternalIncomingViewingKey() const;
OrchardFullViewingKey& operator=(OrchardFullViewingKey&& key)
{
if (this != &key) {
@ -237,31 +280,6 @@ public:
}
return *this;
}
template<typename Stream>
void Serialize(Stream& s) const {
RustStream rs(s);
if (!orchard_spending_key_serialize(inner.get(), &rs, RustStream<Stream>::write_callback)) {
throw std::ios_base::failure("Failed to serialize Orchard spending key");
}
}
template<typename Stream>
void Unserialize(Stream& s) {
RustStream rs(s);
OrchardSpendingKeyPtr* key = orchard_spending_key_parse(&rs, RustStream<Stream>::read_callback);
if (key == nullptr) {
throw std::ios_base::failure("Failed to parse Orchard spending key");
}
inner.reset(key);
}
template<typename Stream>
static OrchardSpendingKey Read(Stream& stream) {
OrchardSpendingKey key;
stream >> key;
return key;
}
};
} // namespace libzcash

View File

@ -16,7 +16,7 @@ using namespace libzcash;
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 r == ReceiverType::Sapling || r == ReceiverType::Orchard;
};
return std::find_if(receiverTypes.begin(), receiverTypes.end(), has_shielded) != receiverTypes.end();
}
@ -39,7 +39,9 @@ std::optional<ZcashdUnifiedSpendingKey> ZcashdUnifiedSpendingKey::ForAccount(
auto saplingKey = SaplingExtendedSpendingKey::ForAccount(seed, bip44CoinType, accountId);
return ZcashdUnifiedSpendingKey(transparentKey.value(), saplingKey.first);
auto orchardKey = OrchardSpendingKey::ForAccount(seed, bip44CoinType, accountId);
return ZcashdUnifiedSpendingKey(transparentKey.value(), saplingKey.first, orchardKey);
}
UnifiedFullViewingKey ZcashdUnifiedSpendingKey::ToFullViewingKey() const {
@ -47,6 +49,7 @@ UnifiedFullViewingKey ZcashdUnifiedSpendingKey::ToFullViewingKey() const {
builder.AddTransparentKey(transparentKey.ToAccountPubKey());
builder.AddSaplingKey(saplingKey.ToXFVK());
builder.AddOrchardKey(orchardKey.ToFullViewingKey());
// This call to .value() is safe as ZcashdUnifiedSpendingKey values are always
// constructed to contain all required components.
@ -69,6 +72,11 @@ ZcashdUnifiedFullViewingKey ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingK
result.saplingKey = saplingKey.value();
}
auto orchardKey = ufvk.GetOrchardKey();
if (orchardKey.has_value()) {
result.orchardKey = orchardKey.value();
}
return result;
}
@ -85,6 +93,14 @@ UnifiedAddressGenerationResult ZcashdUnifiedFullViewingKey::Address(
}
UnifiedAddress ua;
if (receiverTypes.count(ReceiverType::Orchard) > 0) {
if (orchardKey.has_value()) {
ua.AddReceiver(orchardKey.value().ToIncomingViewingKey().Address(j));
} else {
return UnifiedAddressGenerationError::ReceiverTypeNotAvailable;
}
}
if (receiverTypes.count(ReceiverType::Sapling) > 0) {
if (saplingKey.has_value()) {
auto saplingAddress = saplingKey.value().Address(j);
@ -180,6 +196,9 @@ UnifiedFullViewingKey ZcashdUnifiedFullViewingKey::ToFullViewingKey() const {
if (saplingKey.has_value()) {
builder.AddSaplingKey(saplingKey.value());
}
if (orchardKey.has_value()) {
builder.AddOrchardKey(orchardKey.value());
}
// This call to .value() is safe as ZcashdUnifiedFullViewingKey values are always
// constructed to contain all required components.

View File

@ -8,6 +8,7 @@
#include "transparent.h"
#include "key_constants.h"
#include "script/script.h"
#include "zcash/address/orchard.hpp"
#include "zip32.h"
#include <variant>
@ -23,7 +24,7 @@ enum class ReceiverType: uint32_t {
P2PKH = 0x00,
P2SH = 0x01,
Sapling = 0x02,
//Orchard = 0x03
Orchard = 0x03
};
enum class UnifiedAddressGenerationError {
@ -113,6 +114,7 @@ public:
* variants by `operator<` is equivalent to sorting by preference.
*/
typedef std::variant<
OrchardRawAddress,
SaplingPaymentAddress,
CScriptID,
CKeyID,
@ -139,6 +141,7 @@ private:
UFVKId keyId;
std::optional<transparent::AccountPubKey> transparentKey;
std::optional<SaplingDiversifiableFullViewingKey> saplingKey;
std::optional<OrchardFullViewingKey> orchardKey;
ZcashdUnifiedFullViewingKey() {}
@ -167,6 +170,10 @@ public:
return saplingKey;
}
const std::optional<OrchardFullViewingKey>& GetOrchardKey() const {
return orchardKey;
}
/**
* Creates a new unified address having the specified receiver types, at the specified
* diversifier index, unless the diversifer index would generate an invalid receiver.
@ -241,10 +248,12 @@ class ZcashdUnifiedSpendingKey {
private:
transparent::AccountKey transparentKey;
SaplingExtendedSpendingKey saplingKey;
OrchardSpendingKey orchardKey;
ZcashdUnifiedSpendingKey(
transparent::AccountKey tkey,
SaplingExtendedSpendingKey skey): transparentKey(tkey), saplingKey(skey) {}
SaplingExtendedSpendingKey skey,
OrchardSpendingKey okey): transparentKey(tkey), saplingKey(skey), orchardKey(okey) {}
public:
static std::optional<ZcashdUnifiedSpendingKey> ForAccount(
const HDSeed& seed,
@ -255,10 +264,14 @@ public:
return transparentKey;
}
const SaplingExtendedSpendingKey& GetSaplingExtendedSpendingKey() const {
const SaplingExtendedSpendingKey& GetSaplingKey() const {
return saplingKey;
}
const OrchardSpendingKey& GetOrchardKey() const {
return orchardKey;
}
UnifiedFullViewingKey ToFullViewingKey() const;
};

View File

@ -295,4 +295,4 @@ bool IsInternalKeyPath(uint32_t purpose, uint32_t coinType, const std::string& k
}
}
};
} //namespace libzcash