diff --git a/.cargo/config.offline b/.cargo/config.offline index d441af308..bf526dc97 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 2b3d8d6b2..56892ec02 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 2c38a3184..ecd6ee944 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 ccb7fbaff..d0578f990 100644 --- a/src/gtest/test_keys.cpp +++ b/src/gtest/test_keys.cpp @@ -293,14 +293,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()); @@ -313,5 +312,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/keystore.cpp b/src/keystore.cpp index 5fe4b9514..ba72c42bd 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -312,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 @@ -329,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; } @@ -390,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(); @@ -405,7 +426,15 @@ std::optional CBasicKeyStore::GetUFVKIdForViewingKey(const lib std::optional>> FindUFVKId::operator()(const libzcash::OrchardRawAddress& orchardAddr) const { - // TODO: Implement once we have Orchard in UFVKs + 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; } diff --git a/src/keystore.h b/src/keystore.h index a2edfa321..925cc3a3b 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -185,6 +185,7 @@ protected: std::map> mapP2PKHUnified; std::map> mapP2SHUnified; std::map mapSaplingKeyUnified; + std::map mapOrchardKeyUnified; std::map mapUnifiedFullViewingKeys; friend class FindUFVKId; diff --git a/src/rust/include/rust/orchard/keys.h b/src/rust/include/rust/orchard/keys.h index 16ebdbe76..d765586a5 100644 --- a/src/rust/include/rust/orchard/keys.h +++ b/src/rust/include/rust/orchard/keys.h @@ -97,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. * @@ -179,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. */ diff --git a/src/rust/include/rust/unified_keys.h b/src/rust/include/rust/unified_keys.h index df649e910..d485f3fae 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/orchard_keys_ffi.rs b/src/rust/src/orchard_keys_ffi.rs index 5eaa9b458..f5675ad8c 100644 --- a/src/rust/src/orchard_keys_ffi.rs +++ b/src/rust/src/orchard_keys_ffi.rs @@ -145,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, @@ -250,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 bc239072b..a676f956e 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/wallet.cpp b/src/wallet/wallet.cpp index ef9e6080a..474c0eae8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -6556,8 +6556,18 @@ 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; + 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); @@ -6600,7 +6610,30 @@ std::optional UFVKForReceiver::operator() std::optional UnifiedAddressForReceiver::operator()( const libzcash::OrchardRawAddress& orchardAddr) const { - // TODO: Implement once we have Orchard in UFVKs + 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; } diff --git a/src/zcash/Address.cpp b/src/zcash/Address.cpp index c20194dfb..644c983e1 100644 --- a/src/zcash/Address.cpp +++ b/src/zcash/Address.cpp @@ -192,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())) { @@ -232,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; @@ -252,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 8ddb251a5..4a58fdf8c 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -176,6 +176,8 @@ public: std::string Encode(const KeyConstants& keyConstants) const; + std::optional GetOrchardKey() const; + std::optional GetSaplingKey() const; std::optional GetTransparentKey() const; @@ -190,6 +192,9 @@ public: if (GetSaplingKey().has_value()) { result.insert(ReceiverType::Sapling); } + if (GetOrchardKey().has_value()) { + result.insert(ReceiverType::Orchard); + } return result; } @@ -214,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; }; diff --git a/src/zcash/address/orchard.cpp b/src/zcash/address/orchard.cpp index ada6103a2..bbcf39143 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 a1c72391c..a79e57f3a 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; } @@ -113,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 DecryptDiversifier(const OrchardRawAddress& addr) const; + OrchardIncomingViewingKey& operator=(OrchardIncomingViewingKey&& key) { if (this != &key) { @@ -184,6 +192,8 @@ public: OrchardIncomingViewingKey ToIncomingViewingKey() const; + OrchardIncomingViewingKey ToInternalIncomingViewingKey() const; + OrchardFullViewingKey& operator=(OrchardFullViewingKey&& key) { if (this != &key) { diff --git a/src/zcash/address/unified.cpp b/src/zcash/address/unified.cpp index 0f92693c3..ca9a8f9b6 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(); } @@ -69,6 +69,11 @@ ZcashdUnifiedFullViewingKey ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingK result.saplingKey = saplingKey.value(); } + auto orchardKey = ufvk.GetOrchardKey(); + if (orchardKey.has_value()) { + result.orchardKey = orchardKey.value(); + } + return result; } @@ -85,6 +90,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 +193,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 a1beaf8cd..f388013bb 100644 --- a/src/zcash/address/unified.h +++ b/src/zcash/address/unified.h @@ -141,6 +141,7 @@ private: UFVKId keyId; std::optional transparentKey; std::optional saplingKey; + std::optional orchardKey; ZcashdUnifiedFullViewingKey() {} @@ -169,6 +170,10 @@ public: 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.