diff --git a/.cargo/config.offline b/.cargo/config.offline index d441af30845..bf526dc976a 100644 --- a/.cargo/config.offline +++ b/.cargo/config.offline @@ -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"] diff --git a/Cargo.lock b/Cargo.lock index 2b3d8d6b2fe..56892ec0247 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 2c38a3184f7..ecd6ee944b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/src/gtest/test_keys.cpp b/src/gtest/test_keys.cpp index 9d3a9a59621..6d68cdb5751 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()); @@ -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()); } } diff --git a/src/gtest/test_keystore.cpp b/src/gtest/test_keystore.cpp index 6d739165f02..c99b504d675 100644 --- a/src/gtest/test_keystore.cpp +++ b/src/gtest/test_keystore.cpp @@ -557,17 +557,22 @@ TEST(KeystoreTests, StoreAndRetrieveUFVK) { EXPECT_EQ(keyStore.GetUnifiedFullViewingKey(ufvkid).value(), zufvk); auto addrPair = std::get>(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()); diff --git a/src/key_io.cpp b/src/key_io.cpp index b1c5daf95e5..e7ea20adfcc 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 6ba3d57cbfd..b8f67f97b08 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -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 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 CBasicKeyStore::GetUFVKIdForViewingKey(const lib return result; } +std::optional>> +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>> 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>> FindUFVKId::operator()(const CScriptID& scriptId) const { diff --git a/src/keystore.h b/src/keystore.h index 2c293f0b620..925cc3a3b70 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -185,6 +185,7 @@ class CBasicKeyStore : public CKeyStore std::map> mapP2PKHUnified; std::map> mapP2SHUnified; std::map mapSaplingKeyUnified; + std::map mapOrchardKeyUnified; std::map mapUnifiedFullViewingKeys; friend class FindUFVKId; @@ -398,6 +399,8 @@ class FindUFVKId { 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 6773497632d..92636c8aaac 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 a1a7842a652..06a54321927 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 @@ -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. * diff --git a/src/rust/include/rust/unified_keys.h b/src/rust/include/rust/unified_keys.h index df649e910dd..d485f3fae11 100644 --- a/src/rust/include/rust/unified_keys.h +++ b/src/rust/include/rust/unified_keys.h @@ -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 diff --git a/src/rust/src/address_ffi.rs b/src/rust/src/address_ffi.rs index 5b0b7ba2657..83118a373f3 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 93c21c1fecc..f5675ad8c54 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() }; @@ -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, diff --git a/src/rust/src/unified_keys_ffi.rs b/src/rust/src/unified_keys_ffi.rs index bc239072b9d..a676f956e7f 100644 --- a/src/rust/src/unified_keys_ffi.rs +++ b/src/rust/src/unified_keys_ffi.rs @@ -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)), diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 3dd252b2222..7fb09d4d220 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/gtest/test_orchard_zkeys.cpp b/src/wallet/gtest/test_orchard_zkeys.cpp index 3afd22f0a36..d8e910269b6 100644 --- a/src/wallet/gtest/test_orchard_zkeys.cpp +++ b/src/wallet/gtest/test_orchard_zkeys.cpp @@ -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); -} diff --git a/src/wallet/gtest/test_wallet.cpp b/src/wallet/gtest/test_wallet.cpp index 36a0696a7cd..dc0123cac96 100644 --- a/src/wallet/gtest/test_wallet.cpp +++ b/src/wallet/gtest/test_wallet.cpp @@ -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>(&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()); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 90f89d9b7b0..aa99bb0cbff 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -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); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d2e0435d5a9..5aca42d7e88 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -477,7 +477,7 @@ libzcash::transparent::AccountKey CWallet::GetLegacyAccountKey() const { } -std::pair CWallet::GenerateNewUnifiedSpendingKey() { +std::pair CWallet::GenerateNewUnifiedSpendingKey() { AssertLockHeld(cs_wallet); if (!mnemonicHDChain.has_value()) { @@ -488,17 +488,17 @@ std::pair 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 ); // 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 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 GetViewingKeyForPaymentAddress::operator()( { return std::nullopt; } + +// GetViewingKeyForPaymentAddress visitor + std::optional GetViewingKeyForPaymentAddress::operator()( const libzcash::SproutPaymentAddress &zaddr) const { @@ -6551,6 +6556,20 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedS // UFVKForReceiver :: (CWallet&, Receiver) -> std::optional +std::optional 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 UFVKForReceiver::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const { auto ufvkPair = wallet.GetUFVKMetadataForReceiver(saplingAddr); if (ufvkPair.has_value()) { @@ -6590,6 +6609,35 @@ std::optional UFVKForReceiver::operator() // UnifiedAddressForReceiver :: (CWallet&, Receiver) -> std::optional +std::optional 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>(&addr); + if (addrPtr != nullptr) { + return addrPtr->first; + } + } + } + } + } + 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 95853eb8f73..bbe1279e21d 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1480,7 +1480,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface //! Generate the unified spending key from the wallet's mnemonic seed //! for the next unused account identifier. - std::pair + std::pair GenerateNewUnifiedSpendingKey(); //! Generate the unified spending key for the specified ZIP-32/BIP-44 @@ -1816,6 +1816,7 @@ class CReserveKey : public CReserveScript // Shielded key and address generalizations // +// PaymentAddressBelongsToWallet visitor :: (CWallet&, PaymentAddress) -> bool class PaymentAddressBelongsToWallet { private: @@ -1830,6 +1831,7 @@ class PaymentAddressBelongsToWallet bool operator()(const libzcash::UnifiedAddress &uaddr) const; }; +// GetViewingKeyForPaymentAddress visitor :: (CWallet&, PaymentAddress) -> std::optional 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 @@ class AddViewingKeyToWallet KeyAddResult operator()(const libzcash::UnifiedFullViewingKey &sk) const; }; +// AddSpendingKeyToWallet visitor :: +// (CWallet&, Consensus::Params, ..., ViewingKey) -> KeyAddResult class AddSpendingKeyToWallet { private: @@ -1917,6 +1923,7 @@ class AddSpendingKeyToWallet KeyAddResult operator()(const libzcash::SaplingExtendedSpendingKey &sk) const; }; +// UFVKForReceiver :: (CWallet&, Receiver) -> std::optional class UFVKForReceiver { private: const CWallet& wallet; @@ -1924,12 +1931,14 @@ class UFVKForReceiver { 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; std::optional operator()(const libzcash::UnknownReceiver& receiver) const; }; +// UnifiedAddressForReceiver :: (CWallet&, Receiver) -> std::optional class UnifiedAddressForReceiver { private: const CWallet& wallet; @@ -1937,6 +1946,7 @@ class UnifiedAddressForReceiver { 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 bef1ca4391b..644c983e107 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 { @@ -174,6 +192,16 @@ std::string libzcash::UnifiedFullViewingKey::Encode(const KeyConstants& keyConst return res; } +std::optional libzcash::UnifiedFullViewingKey::GetOrchardKey() const { + std::vector 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::UnifiedFullViewingKey::GetSaplingKey() const { std::vector 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 ss_bytes(ss.begin(), ss.end()); + orchard_bytes = ss_bytes; + return true; +} + std::optional 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()) { diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index c9ed001c3ce..4a58fdf8c90 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -84,6 +84,9 @@ class UnifiedAddress { 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 @@ class UnifiedAddress { std::optional GetSaplingReceiver() const; + std::optional GetOrchardReceiver() const; + /** * Return the most-preferred receiver from among the receiver types * that we recognize. @@ -171,6 +176,8 @@ class UnifiedFullViewingKey { std::string Encode(const KeyConstants& keyConstants) const; + std::optional GetOrchardKey() const; + std::optional GetSaplingKey() const; std::optional GetTransparentKey() const; @@ -185,6 +192,9 @@ class UnifiedFullViewingKey { 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> t_bytes; std::optional> sapling_bytes; + std::optional> 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 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; diff --git a/src/zcash/address/orchard.cpp b/src/zcash/address/orchard.cpp index ada6103a2c7..bbcf391439c 100644 --- a/src/zcash/address/orchard.cpp +++ b/src/zcash/address/orchard.cpp @@ -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 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, diff --git a/src/zcash/address/orchard.hpp b/src/zcash/address/orchard.hpp index 0e0233fa31c..d8932244126 100644 --- a/src/zcash/address/orchard.hpp +++ b/src/zcash/address/orchard.hpp @@ -9,6 +9,8 @@ #include "zcash/address/zip32.h" #include +#include + class OrchardWallet; namespace orchard { class Builder; } @@ -31,6 +33,10 @@ class OrchardRawAddress 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 @@ class OrchardRawAddress 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 @@ -80,6 +115,12 @@ class OrchardIncomingViewingKey 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 DecryptDiversifier(const OrchardRawAddress& addr) const; + OrchardIncomingViewingKey& operator=(OrchardIncomingViewingKey&& key) { if (this != &key) { @@ -151,6 +192,8 @@ class OrchardFullViewingKey OrchardIncomingViewingKey ToIncomingViewingKey() const; + OrchardIncomingViewingKey ToInternalIncomingViewingKey() const; + OrchardFullViewingKey& operator=(OrchardFullViewingKey&& key) { if (this != &key) { @@ -237,31 +280,6 @@ class OrchardSpendingKey } return *this; } - - template - void Serialize(Stream& s) const { - RustStream rs(s); - if (!orchard_spending_key_serialize(inner.get(), &rs, RustStream::write_callback)) { - throw std::ios_base::failure("Failed to serialize Orchard spending key"); - } - } - - template - void Unserialize(Stream& s) { - RustStream rs(s); - OrchardSpendingKeyPtr* key = orchard_spending_key_parse(&rs, RustStream::read_callback); - if (key == nullptr) { - throw std::ios_base::failure("Failed to parse Orchard spending key"); - } - inner.reset(key); - } - - template - static OrchardSpendingKey Read(Stream& stream) { - OrchardSpendingKey key; - stream >> key; - return key; - } }; } // namespace libzcash diff --git a/src/zcash/address/unified.cpp b/src/zcash/address/unified.cpp index 0f92693c31e..93dd1b34f6f 100644 --- a/src/zcash/address/unified.cpp +++ b/src/zcash/address/unified.cpp @@ -16,7 +16,7 @@ using namespace libzcash; bool libzcash::HasShielded(const std::set& 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::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. diff --git a/src/zcash/address/unified.h b/src/zcash/address/unified.h index c66df239d48..de5227bc0ae 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 @@ class UnknownReceiver { * variants by `operator<` is equivalent to sorting by preference. */ typedef std::variant< + OrchardRawAddress, SaplingPaymentAddress, CScriptID, CKeyID, @@ -139,6 +141,7 @@ class ZcashdUnifiedFullViewingKey { UFVKId keyId; std::optional transparentKey; std::optional saplingKey; + std::optional orchardKey; ZcashdUnifiedFullViewingKey() {} @@ -167,6 +170,10 @@ class ZcashdUnifiedFullViewingKey { return saplingKey; } + const std::optional& 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 ForAccount( const HDSeed& seed, @@ -255,10 +264,14 @@ class ZcashdUnifiedSpendingKey { return transparentKey; } - const SaplingExtendedSpendingKey& GetSaplingExtendedSpendingKey() const { + const SaplingExtendedSpendingKey& GetSaplingKey() const { return saplingKey; } + const OrchardSpendingKey& GetOrchardKey() const { + return orchardKey; + } + UnifiedFullViewingKey ToFullViewingKey() const; }; diff --git a/src/zcash/address/zip32.cpp b/src/zcash/address/zip32.cpp index 9c7a9a56c78..026dfc389f1 100644 --- a/src/zcash/address/zip32.cpp +++ b/src/zcash/address/zip32.cpp @@ -295,4 +295,4 @@ bool IsInternalKeyPath(uint32_t purpose, uint32_t coinType, const std::string& k } } -}; +} //namespace libzcash