Add Orchard components to unified address

Co-authored-by: Jack Grigg <jack@z.cash>
This commit is contained in:
Kris Nuttycombe 2022-02-18 17:18:40 -06:00 committed by Jack Grigg
parent 3e3ee1bf3b
commit 1a1522a4f1
15 changed files with 205 additions and 18 deletions

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());

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

@ -403,6 +403,12 @@ 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 {
// TODO: Implement once we have Orchard in UFVKs
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);

View File

@ -398,6 +398,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

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() };

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

@ -6555,6 +6555,10 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedS
// UFVKForReceiver :: (CWallet&, Receiver) -> std::optional<ZcashdUnifiedFullViewingKey>
std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()(const libzcash::OrchardRawAddress& orchardAddr) const {
// TODO: Implement once we have Orchard in UFVKs
return std::nullopt;
}
std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(saplingAddr);
if (ufvkPair.has_value()) {
@ -6594,6 +6598,12 @@ std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()
// UnifiedAddressForReceiver :: (CWallet&, Receiver) -> std::optional<UnifiedAddress>
std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(
const libzcash::OrchardRawAddress& orchardAddr) const {
// TODO: Implement once we have Orchard in UFVKs
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

@ -1931,6 +1931,7 @@ 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;
@ -1945,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
{

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.
@ -283,6 +288,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

@ -31,6 +31,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 +56,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

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,