diff --git a/src/key_constants.h b/src/key_constants.h index aa999dd58..70b8f87e9 100644 --- a/src/key_constants.h +++ b/src/key_constants.h @@ -5,6 +5,8 @@ #ifndef ZCASH_KEY_CONSTANTS_H #define ZCASH_KEY_CONSTANTS_H +#include + class KeyConstants { public: diff --git a/src/key_io.cpp b/src/key_io.cpp index e22243f9c..8d3a2b8d5 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -190,6 +190,10 @@ public: return ret; } + std::string operator()(const libzcash::UnifiedFullViewingKey& ufvk) const { + return ufvk.Encode(keyConstants); + } + std::string operator()(const libzcash::InvalidEncoding& no) const { return {}; } }; @@ -485,6 +489,13 @@ std::string KeyIO::EncodeViewingKey(const libzcash::ViewingKey& vk) libzcash::ViewingKey KeyIO::DecodeViewingKey(const std::string& str) { + // Try parsing as a Unified full viewing key + auto ufvk = libzcash::UnifiedFullViewingKey::Decode(str, keyConstants); + if (ufvk.has_value()) { + return ufvk.value(); + } + + // Fall back on trying Sprout or Sapling. return DecodeAny( diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 32808a999..58a23873b 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -134,6 +134,14 @@ bool CExtPubKey::Derive(CExtPubKey &out, unsigned int _nChild) const { return (!secp256k1_ecdsa_signature_normalize(secp256k1_context_verify, NULL, &sig)); } +/* static */ std::optional CChainablePubKey::FromParts(ChainCode chaincode, CPubKey pubkey) { + if (pubkey.IsCompressed()) { + return CChainablePubKey(chaincode, pubkey); + } else { + return std::nullopt; + } +} + /* static */ int ECCVerifyHandle::refcount = 0; ECCVerifyHandle::ECCVerifyHandle() diff --git a/src/pubkey.h b/src/pubkey.h index 94ff547c4..04739eaea 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -203,6 +203,55 @@ public: bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const; }; +class CChainablePubKey { +private: + ChainCode chaincode; + CPubKey pubkey; + + CChainablePubKey() {} + CChainablePubKey(ChainCode chaincode, CPubKey pubkey): chaincode(chaincode), pubkey(pubkey) {} +public: + static std::optional FromParts(ChainCode chaincode, CPubKey pubkey); + + const ChainCode& GetChainCode() const { + return chaincode; + } + + const CPubKey& GetPubKey() const { + return pubkey; + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(chaincode); + if (ser_action.ForRead()) { + std::array pubkeyBytes; + READWRITE(pubkeyBytes); + pubkey = CPubKey(pubkeyBytes.begin(), pubkeyBytes.end()); + assert(pubkey.IsCompressed()); + } else { + assert(pubkey.size() == 33); + std::array pubkeyBytes; + std::copy(pubkey.begin(), pubkey.end(), pubkeyBytes.begin()); + READWRITE(pubkeyBytes); + } + } + + template + static CChainablePubKey Read(Stream& stream) { + CChainablePubKey key; + stream >> key; + return key; + } + + friend bool operator==(const CChainablePubKey &a, const CChainablePubKey &b) + { + return a.chaincode == b.chaincode && a.pubkey == b.pubkey; + } +}; + struct CExtPubKey { unsigned char nDepth; unsigned char vchFingerprint[4]; diff --git a/src/rust/include/rust/unified_keys.h b/src/rust/include/rust/unified_keys.h index 10ea8ea01..6d993bd47 100644 --- a/src/rust/include/rust/unified_keys.h +++ b/src/rust/include/rust/unified_keys.h @@ -63,7 +63,7 @@ char* unified_full_viewing_key_serialize( */ bool unified_full_viewing_key_read_transparent( const UnifiedFullViewingKeyPtr* full_viewing_key, - unsigned char *tkeyout); + unsigned char* tkeyout); /** * Reads the Sapling component of a unified viewing key. @@ -75,7 +75,7 @@ bool unified_full_viewing_key_read_transparent( */ bool unified_full_viewing_key_read_sapling( const UnifiedFullViewingKeyPtr* full_viewing_key, - unsigned char *skeyout); + unsigned char* skeyout); /** * Sets the Sapling component of a unified viewing key. @@ -91,8 +91,8 @@ bool unified_full_viewing_key_read_sapling( * or the null pointer otherwise. */ UnifiedFullViewingKeyPtr* unified_full_viewing_key_from_components( - const unsigned char *t_key, - const unsigned char *sapling_key); + const unsigned char* t_key, + const unsigned char* sapling_key); #ifdef __cplusplus } diff --git a/src/rust/src/address_ffi.rs b/src/rust/src/address_ffi.rs index 808af7f54..a670d1815 100644 --- a/src/rust/src/address_ffi.rs +++ b/src/rust/src/address_ffi.rs @@ -212,7 +212,7 @@ pub extern "C" fn zcash_address_serialize_unified( } }; - let ua = match unified::Address::try_from_items(receivers) { + let ua = match unified::Address::try_from_items_preserving_order(receivers) { Ok(ua) => ua, Err(e) => { tracing::error!("{}", e); diff --git a/src/rust/src/unified_keys_ffi.rs b/src/rust/src/unified_keys_ffi.rs index eeca01c18..b7ecfc383 100644 --- a/src/rust/src/unified_keys_ffi.rs +++ b/src/rust/src/unified_keys_ffi.rs @@ -27,11 +27,14 @@ pub extern "C" fn unified_full_viewing_key_parse( ) -> *mut Ufvk { let network = match network_from_cstr(network) { Some(n) => n, - None => return std::ptr::null_mut(), + None => { + return std::ptr::null_mut(); + } }; match unsafe { CStr::from_ptr(encoded) }.to_str() { - Ok(encoded) => match Ufvk::decode(encoded) { + Ok(encoded) => { + match Ufvk::decode(encoded) { Ok((parsed_network, fvk)) => { if parsed_network == network { Box::into_raw(Box::new(fvk)) @@ -47,7 +50,7 @@ pub extern "C" fn unified_full_viewing_key_parse( error!("Failure decoding unified full viewing key: {}", e); std::ptr::null_mut() } - }, + }}, Err(e) => { error!("Failure reading bytes of unified full viewing key: {}", e); std::ptr::null_mut() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 91cd9ed9b..f1238644d 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5506,6 +5506,10 @@ KeyAddResult AddViewingKeyToWallet::operator()(const libzcash::SaplingExtendedFu } } +KeyAddResult AddViewingKeyToWallet::operator()(const libzcash::UnifiedFullViewingKey& no) const { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unified full viewing key import is not yet supported."); +} + KeyAddResult AddViewingKeyToWallet::operator()(const libzcash::InvalidEncoding& no) const { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid viewing key"); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 6e1ffa2fa..2beb7d18c 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1481,6 +1481,7 @@ public: KeyAddResult operator()(const libzcash::SproutViewingKey &sk) const; KeyAddResult operator()(const libzcash::SaplingExtendedFullViewingKey &sk) const; + KeyAddResult operator()(const libzcash::UnifiedFullViewingKey &sk) const; KeyAddResult operator()(const libzcash::InvalidEncoding& no) const; }; diff --git a/src/zcash/Address.cpp b/src/zcash/Address.cpp index 568b3bbff..3481ae08d 100644 --- a/src/zcash/Address.cpp +++ b/src/zcash/Address.cpp @@ -1,6 +1,10 @@ #include "Address.hpp" +#include "zcash/address/unified.h" +#include "utilstrencodings.h" #include +#include +#include const uint8_t ZCASH_UA_TYPECODE_P2PKH = 0x00; const uint8_t ZCASH_UA_TYPECODE_P2SH = 0x01; @@ -51,6 +55,14 @@ std::pair AddressInfoFromViewingKey::operator()(con std::pair AddressInfoFromViewingKey::operator()(const SaplingExtendedFullViewingKey &xfvk) const { return std::make_pair("sapling", xfvk.DefaultAddress()); } +std::pair AddressInfoFromViewingKey::operator()(const UnifiedFullViewingKey &ufvk) const { + return std::make_pair( + "unified", + ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(ufvk) + .FindAddress(diversifier_index_t(0)) + .first + ); +} std::pair AddressInfoFromViewingKey::operator()(const InvalidEncoding&) const { throw std::invalid_argument("Cannot derive default address from invalid viewing key"); } @@ -176,3 +188,95 @@ std::set GetRawAddresses::operator()( } return ret; } + +std::optional libzcash::UnifiedFullViewingKey::Decode( + const std::string& str, + const KeyConstants& keyConstants) { + UnifiedFullViewingKeyPtr* ptr = unified_full_viewing_key_parse( + keyConstants.NetworkIDString().c_str(), + str.c_str()); + if (ptr == nullptr) { + return std::nullopt; + } else { + return UnifiedFullViewingKey(ptr); + } +} + +std::string libzcash::UnifiedFullViewingKey::Encode(const KeyConstants& keyConstants) const { + auto encoded = unified_full_viewing_key_serialize( + keyConstants.NetworkIDString().c_str(), + inner.get()); + // Copy the C-string into C++. + std::string res(encoded); + // Free the C-string. + zcash_address_string_free(encoded); + return res; +} + +std::optional libzcash::UnifiedFullViewingKey::GetSaplingKey() const { + std::vector buffer(128); + if (unified_full_viewing_key_read_sapling(inner.get(), buffer.data())) { + CDataStream ss(buffer, SER_NETWORK, PROTOCOL_VERSION); + return SaplingDiversifiableFullViewingKey::Read(ss); + } else { + return std::nullopt; + } +} + +std::optional libzcash::UnifiedFullViewingKey::GetTransparentKey() const { + std::vector buffer(65); + if (unified_full_viewing_key_read_transparent(inner.get(), buffer.data())) { + CDataStream ss(buffer, SER_NETWORK, PROTOCOL_VERSION); + return CChainablePubKey::Read(ss); + } else { + return std::nullopt; + } +} + +bool libzcash::UnifiedFullViewingKeyBuilder::AddTransparentKey(const CChainablePubKey& key) { + if (t_bytes.has_value()) return false; + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << key; + assert(ss.size() == 65); + std::vector ss_bytes(ss.begin(), ss.end()); + t_bytes = ss_bytes; + return true; +} + +bool libzcash::UnifiedFullViewingKeyBuilder::AddSaplingKey(const SaplingDiversifiableFullViewingKey& key) { + if (sapling_bytes.has_value()) return false; + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << key; + assert(ss.size() == 128); + std::vector ss_bytes(ss.begin(), ss.end()); + sapling_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); + + if (ptr == nullptr) { + return std::nullopt; + } else { + return UnifiedFullViewingKey(ptr); + } +} + +libzcash::UnifiedFullViewingKey libzcash::UnifiedFullViewingKey::FromZcashdUFVK(const ZcashdUnifiedFullViewingKey& key) { + UnifiedFullViewingKeyBuilder builder; + if (key.GetTransparentKey().has_value()) { + builder.AddTransparentKey(key.GetTransparentKey().value()); + } + if (key.GetSaplingKey().has_value()) { + builder.AddSaplingKey(key.GetSaplingKey().value()); + } + + auto result = builder.build(); + if (!result.has_value()) { + throw std::invalid_argument("Cannot convert from invalid viewing key."); + } + return result.value(); +} diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index 09e6144a4..8db8b03d2 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -1,15 +1,18 @@ #ifndef ZC_ADDRESS_H_ #define ZC_ADDRESS_H_ -#include "uint256.h" +#include "key_constants.h" #include "pubkey.h" #include "script/script.h" +#include "uint256.h" #include "zcash/address/orchard.hpp" #include "zcash/address/sapling.hpp" #include "zcash/address/sprout.hpp" +#include "zcash/address/unified.h" #include "zcash/address/zip32.h" #include +#include namespace libzcash { @@ -127,14 +130,85 @@ public: } }; +class UnifiedFullViewingKeyBuilder; + +/** + * Wrapper for a zcash_address::unified::Ufvk. + */ +class UnifiedFullViewingKey { +private: + std::unique_ptr inner; + + UnifiedFullViewingKey() : + inner(nullptr, unified_full_viewing_key_free) {} + + UnifiedFullViewingKey(UnifiedFullViewingKeyPtr* ptr) : + inner(ptr, unified_full_viewing_key_free) {} + + friend class UnifiedFullViewingKeyBuilder; +public: + static std::optional Decode( + const std::string& str, + const KeyConstants& keyConstants); + + static UnifiedFullViewingKey FromZcashdUFVK(const ZcashdUnifiedFullViewingKey&); + + std::string Encode(const KeyConstants& keyConstants) const; + + std::optional GetSaplingKey() const; + + std::optional GetTransparentKey() const; + + UnifiedFullViewingKey(const UnifiedFullViewingKey& key) : + inner(unified_full_viewing_key_clone(key.inner.get()), unified_full_viewing_key_free) {} + + UnifiedFullViewingKey& operator=(UnifiedFullViewingKey&& key) + { + if (this != &key) { + inner = std::move(key.inner); + } + return *this; + } + + UnifiedFullViewingKey& operator=(const UnifiedFullViewingKey& key) + { + if (this != &key) { + inner.reset(unified_full_viewing_key_clone(key.inner.get())); + } + return *this; + } +}; + +class UnifiedFullViewingKeyBuilder { +private: + std::optional> t_bytes; + std::optional> sapling_bytes; +public: + UnifiedFullViewingKeyBuilder(): t_bytes(std::nullopt), sapling_bytes(std::nullopt) {} + + bool AddTransparentKey(const CChainablePubKey&); + bool AddSaplingKey(const SaplingDiversifiableFullViewingKey&); + + std::optional build() const; +}; + /** Addresses that can appear in a string encoding. */ typedef std::variant< InvalidEncoding, SproutPaymentAddress, SaplingPaymentAddress, UnifiedAddress> PaymentAddress; -typedef std::variant ViewingKey; -typedef std::variant SpendingKey; +/** Viewing keys that can have a string encoding. */ +typedef std::variant< + InvalidEncoding, + SproutViewingKey, + SaplingExtendedFullViewingKey, + UnifiedFullViewingKey> ViewingKey; +/** Spending keys that can have a string encoding. */ +typedef std::variant< + InvalidEncoding, + SproutSpendingKey, + SaplingExtendedSpendingKey> SpendingKey; class AddressInfoFromSpendingKey { public: @@ -147,10 +221,11 @@ class AddressInfoFromViewingKey { public: std::pair operator()(const SproutViewingKey&) const; std::pair operator()(const struct SaplingExtendedFullViewingKey&) const; + std::pair operator()(const UnifiedFullViewingKey&) const; std::pair operator()(const InvalidEncoding&) const; }; -} +} //namespace libzcash /** Check whether a PaymentAddress is not an InvalidEncoding. */ bool IsValidPaymentAddress(const libzcash::PaymentAddress& zaddr); diff --git a/src/zcash/address/unified.cpp b/src/zcash/address/unified.cpp index d72a8686e..56d2c1fc8 100644 --- a/src/zcash/address/unified.cpp +++ b/src/zcash/address/unified.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://www.opensource.org/licenses/mit-license.php . +#include "zcash/Address.hpp" #include "unified.h" #include "bip44.h" @@ -29,7 +30,11 @@ ZcashdUnifiedFullViewingKey ZcashdUnifiedSpendingKey::ToFullViewingKey() const { ZcashdUnifiedFullViewingKey ufvk; if (transparentKey.has_value()) { - ufvk.transparentKey = transparentKey.value().Neuter(); + auto extPubKey = transparentKey.value().Neuter(); + + // TODO: how to ensure that the pubkey is in its compressed representation? + // Is that already guaranteed? + ufvk.transparentKey = CChainablePubKey::FromParts(extPubKey.chaincode, extPubKey.pubkey).value(); } if (saplingKey.has_value()) { @@ -39,6 +44,23 @@ ZcashdUnifiedFullViewingKey ZcashdUnifiedSpendingKey::ToFullViewingKey() const { return ufvk; } +ZcashdUnifiedFullViewingKey ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey( + const UnifiedFullViewingKey& ufvk) { + ZcashdUnifiedFullViewingKey result; + + auto transparentKey = ufvk.GetTransparentKey(); + if (transparentKey.has_value()) { + result.transparentKey = transparentKey.value(); + } + + auto saplingKey = ufvk.GetSaplingKey(); + if (saplingKey.has_value()) { + result.saplingKey = saplingKey.value(); + } + + return result; +} + std::optional ZcashdUnifiedFullViewingKey::Address(diversifier_index_t j) const { UnifiedAddress ua; @@ -52,17 +74,20 @@ std::optional ZcashdUnifiedFullViewingKey::Address(diversifier_i } if (transparentKey.has_value()) { + const auto& tkey = transparentKey.value(); auto childIndex = j.ToTransparentChildIndex(); if (!childIndex.has_value()) return std::nullopt; - CExtPubKey externalKey; - if (!transparentKey.value().Derive(externalKey, 0)) { + CPubKey externalKey; + ChainCode externalChainCode; + if (!tkey.GetPubKey().Derive(externalKey, externalChainCode, 0, tkey.GetChainCode())) { return std::nullopt; } - CExtPubKey childKey; - if (externalKey.Derive(childKey, childIndex.value())) { - ua.AddReceiver(childKey.pubkey.GetID()); + CPubKey childKey; + ChainCode childChainCode; + if (externalKey.Derive(childKey, childChainCode, childIndex.value(), externalChainCode)) { + ua.AddReceiver(childKey.GetID()); } else { return std::nullopt; } @@ -71,3 +96,13 @@ std::optional ZcashdUnifiedFullViewingKey::Address(diversifier_i return ua; } +std::pair ZcashdUnifiedFullViewingKey::FindAddress(diversifier_index_t j) const { + auto addr = Address(j); + while (!addr.has_value()) { + if (!j.increment()) + throw std::runtime_error(std::string(__func__) + ": diversifier index overflow.");; + addr = Address(j); + } + return std::make_pair(addr.value(), j); +} + diff --git a/src/zcash/address/unified.h b/src/zcash/address/unified.h index 68de27920..f65b4b13d 100644 --- a/src/zcash/address/unified.h +++ b/src/zcash/address/unified.h @@ -7,23 +7,28 @@ #include "zip32.h" #include "bip44.h" -#include "zcash/Address.hpp" namespace libzcash { class ZcashdUnifiedSpendingKey; class ZcashdUnifiedFullViewingKey; +// prototypes for the classes handling ZIP-316 encoding (in Address.hpp) +class UnifiedAddress; +class UnifiedFullViewingKey; + class ZcashdUnifiedFullViewingKey { private: - std::optional transparentKey; + std::optional transparentKey; std::optional saplingKey; ZcashdUnifiedFullViewingKey() {} friend class ZcashdUnifiedSpendingKey; public: - const std::optional& GetTransparentKey() const { + static ZcashdUnifiedFullViewingKey FromUnifiedFullViewingKey(const UnifiedFullViewingKey& ufvk); + + const std::optional& GetTransparentKey() const { return transparentKey; } @@ -33,15 +38,7 @@ public: std::optional Address(diversifier_index_t j) const; - std::pair FindAddress(diversifier_index_t j) const { - auto addr = Address(j); - while (!addr.has_value()) { - if (!j.increment()) - throw std::runtime_error(std::string(__func__) + ": diversifier index overflow.");; - addr = Address(j); - } - return std::make_pair(addr.value(), j); - } + std::pair FindAddress(diversifier_index_t j) const; }; class ZcashdUnifiedSpendingKey { diff --git a/src/zcash/address/zip32.h b/src/zcash/address/zip32.h index 608918e68..2a18d1af4 100644 --- a/src/zcash/address/zip32.h +++ b/src/zcash/address/zip32.h @@ -141,6 +141,26 @@ public: } libzcash::SaplingPaymentAddress DefaultAddress() const; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(fvk); + READWRITE(dk); + } + + template + static SaplingDiversifiableFullViewingKey Read(Stream& stream) { + SaplingDiversifiableFullViewingKey key; + stream >> key; + return key; + } + + friend inline bool operator==(const SaplingDiversifiableFullViewingKey& a, const SaplingDiversifiableFullViewingKey& b) { + return (a.fvk == b.fvk && a.dk == b.dk); + } + }; class SaplingExtendedFullViewingKey: public SaplingDiversifiableFullViewingKey {