diff --git a/src/gtest/test_keys.cpp b/src/gtest/test_keys.cpp index 9d3a9a596..ccb7fbaff 100644 --- a/src/gtest/test_keys.cpp +++ b/src/gtest/test_keys.cpp @@ -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()); diff --git a/src/key_io.cpp b/src/key_io.cpp index b1c5daf95..e7ea20adf 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -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 DecodeAny( return std::nullopt; } +static bool AddOrchardReceiver(void* ua, OrchardRawAddressPtr* ptr) +{ + return reinterpret_cast(ua)->AddReceiver( + libzcash::OrchardRawAddress::KeyIoOnlyFromReceiver(ptr)); +} + /** * `raw` MUST be 43 bytes. */ @@ -492,6 +506,7 @@ std::optional KeyIO::DecodePaymentAddress(const std::s str.c_str(), keyConstants.NetworkIDString().c_str(), &ua, + AddOrchardReceiver, AddSaplingReceiver, AddP2SHReceiver, AddP2PKHReceiver, diff --git a/src/keystore.cpp b/src/keystore.cpp index 38b88cdf8..5fe4b9514 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -403,6 +403,12 @@ std::optional CBasicKeyStore::GetUFVKIdForViewingKey(const lib return result; } +std::optional>> +FindUFVKId::operator()(const libzcash::OrchardRawAddress& orchardAddr) const { + // TODO: Implement once we have Orchard in UFVKs + return std::nullopt; +} + std::optional>> FindUFVKId::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const { const auto saplingIvk = keystore.mapSaplingIncomingViewingKeys.find(saplingAddr); diff --git a/src/keystore.h b/src/keystore.h index 2c293f0b6..a2edfa321 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -398,6 +398,8 @@ private: public: FindUFVKId(const CBasicKeyStore& keystore): keystore(keystore) {} + std::optional>> + operator()(const libzcash::OrchardRawAddress& orchardAddr) const; std::optional>> operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const; std::optional>> diff --git a/src/rust/include/rust/address.h b/src/rust/include/rust/address.h index 677349763..92636c8aa 100644 --- a/src/rust/include/rust/address.h +++ b/src/rust/include/rust/address.h @@ -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, diff --git a/src/rust/include/rust/orchard/keys.h b/src/rust/include/rust/orchard/keys.h index a1a7842a6..16ebdbe76 100644 --- a/src/rust/include/rust/orchard/keys.h +++ b/src/rust/include/rust/orchard/keys.h @@ -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 diff --git a/src/rust/src/address_ffi.rs b/src/rust/src/address_ffi.rs index 5b0b7ba26..83118a373 100644 --- a/src/rust/src/address_ffi.rs +++ b/src/rust/src/address_ffi.rs @@ -12,6 +12,8 @@ use zcash_address::{ use zcash_primitives::sapling; pub type UnifiedAddressObj = NonNull; +pub type AddOrchardReceiverCb = + unsafe extern "C" fn(ua: Option, orchard: *const orchard::Address) -> bool; pub type AddReceiverCb = unsafe extern "C" fn(ua: Option, 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, + orchard_cb: Option, sapling_cb: Option, p2sh_cb: Option, p2pkh_cb: Option, @@ -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, + orchard_cb: Option, sapling_cb: Option, p2sh_cb: Option, p2pkh_cb: Option, @@ -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]; diff --git a/src/rust/src/orchard_keys_ffi.rs b/src/rust/src/orchard_keys_ffi.rs index 93c21c1fe..5eaa9b458 100644 --- a/src/rust/src/orchard_keys_ffi.rs +++ b/src/rust/src/orchard_keys_ffi.rs @@ -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, + read_cb: Option, +) -> *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, + write_cb: Option, +) -> 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() }; diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 3dd252b22..7fb09d4d2 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -340,6 +340,9 @@ uint256 AsyncRPCOperation_sendmany::main_impl() { case ReceiverType::Sapling: allowedChangeTypes_.insert(libzcash::ChangeType::Sapling); break; + case ReceiverType::Orchard: + // TODO + break; } } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 845cb1a97..ef9e6080a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -6555,6 +6555,10 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedS // UFVKForReceiver :: (CWallet&, Receiver) -> std::optional +std::optional UFVKForReceiver::operator()(const libzcash::OrchardRawAddress& orchardAddr) const { + // TODO: Implement once we have Orchard in UFVKs + return std::nullopt; +} std::optional UFVKForReceiver::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const { auto ufvkPair = wallet.GetUFVKMetadataForReceiver(saplingAddr); if (ufvkPair.has_value()) { @@ -6594,6 +6598,12 @@ std::optional UFVKForReceiver::operator() // UnifiedAddressForReceiver :: (CWallet&, Receiver) -> std::optional +std::optional UnifiedAddressForReceiver::operator()( + const libzcash::OrchardRawAddress& orchardAddr) const { + // TODO: Implement once we have Orchard in UFVKs + return std::nullopt; +} + std::optional UnifiedAddressForReceiver::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const { auto ufvkPair = wallet.GetUFVKMetadataForReceiver(saplingAddr); if (ufvkPair.has_value()) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 191407f95..3b297098b 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1931,6 +1931,7 @@ private: public: UFVKForReceiver(const CWallet& wallet): wallet(wallet) {} + std::optional operator()(const libzcash::OrchardRawAddress& orchardAddr) const; std::optional operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const; std::optional operator()(const CScriptID& scriptId) const; std::optional operator()(const CKeyID& keyId) const; @@ -1945,6 +1946,7 @@ private: public: UnifiedAddressForReceiver(const CWallet& wallet): wallet(wallet) {} + std::optional operator()(const libzcash::OrchardRawAddress& orchardAddr) const; std::optional operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const; std::optional operator()(const CScriptID& scriptId) const; std::optional operator()(const CKeyID& keyId) const; diff --git a/src/zcash/Address.cpp b/src/zcash/Address.cpp index bef1ca439..c20194dfb 100644 --- a/src/zcash/Address.cpp +++ b/src/zcash/Address.cpp @@ -73,12 +73,23 @@ std::optional UnifiedAddress::GetSaplingReceiver() const return std::nullopt; } +std::optional UnifiedAddress::GetOrchardReceiver() const { + for (const auto& r : receivers) { + if (std::holds_alternative(r)) { + return std::get(r); + } + } + + return std::nullopt; +} + std::optional UnifiedAddress::GetPreferredRecipientAddress() const { // Return the first receiver type we recognize; receivers are sorted in // order from most-preferred to least. std::optional 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 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 AddressInfoFromViewingKey::operator()(con } // namespace libzcash +uint32_t TypecodeForReceiver::operator()( + const libzcash::OrchardRawAddress &zaddr) const +{ + return static_cast(libzcash::ReceiverType::Orchard); +} + uint32_t TypecodeForReceiver::operator()( const libzcash::SaplingPaymentAddress &zaddr) const { diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index c9ed001c3..8ddb251a5 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -84,6 +84,9 @@ public: std::set 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 GetSaplingReceiver() const; + std::optional 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; diff --git a/src/zcash/address/orchard.hpp b/src/zcash/address/orchard.hpp index 0e0233fa3..a1c72391c 100644 --- a/src/zcash/address/orchard.hpp +++ b/src/zcash/address/orchard.hpp @@ -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 + void Serialize(Stream& s) const { + RustStream rs(s); + if (!orchard_raw_address_serialize(inner.get(), &rs, RustStream::write_callback)) { + throw std::ios_base::failure("Failed to serialize Orchard raw address to bytes"); + } + } + + template + void Unserialize(Stream& s) { + RustStream rs(s); + OrchardRawAddressPtr* addr = orchard_raw_address_parse(&rs, RustStream::read_callback); + if (addr == nullptr) { + throw std::ios_base::failure("Failed to parse Orchard raw address bytes"); + } + inner.reset(addr); + } + + template + static OrchardRawAddress Read(Stream& stream) { + OrchardRawAddress key; + stream >> key; + return key; + } }; class OrchardIncomingViewingKey diff --git a/src/zcash/address/unified.h b/src/zcash/address/unified.h index c66df239d..a1beaf8cd 100644 --- a/src/zcash/address/unified.h +++ b/src/zcash/address/unified.h @@ -8,6 +8,7 @@ #include "transparent.h" #include "key_constants.h" #include "script/script.h" +#include "zcash/address/orchard.hpp" #include "zip32.h" #include @@ -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,