From 640f31463fc3b37d076d0c8fabce4b7ec3697e40 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Sat, 4 Dec 2021 11:00:47 -0700 Subject: [PATCH 1/9] Update to ufvk zcash_address build. --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 10 +++++----- src/rust/src/address_ffi.rs | 9 ++++++--- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee0140140..a2119f065 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -521,7 +521,7 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "equihash" version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=0ec7f97c976d55e1a194a37b27f247e8887fca1d#0ec7f97c976d55e1a194a37b27f247e8887fca1d" +source = "git+https://github.com/zcash/librustzcash.git?rev=69c3b4b5e143bb349cca76c677b7cd0a8556b28f#69c3b4b5e143bb349cca76c677b7cd0a8556b28f" dependencies = [ "blake2b_simd", "byteorder", @@ -530,7 +530,7 @@ dependencies = [ [[package]] name = "f4jumble" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=0ec7f97c976d55e1a194a37b27f247e8887fca1d#0ec7f97c976d55e1a194a37b27f247e8887fca1d" +source = "git+https://github.com/zcash/librustzcash.git?rev=69c3b4b5e143bb349cca76c677b7cd0a8556b28f#69c3b4b5e143bb349cca76c677b7cd0a8556b28f" dependencies = [ "blake2b_simd", ] @@ -1885,7 +1885,7 @@ dependencies = [ [[package]] name = "zcash_address" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=0ec7f97c976d55e1a194a37b27f247e8887fca1d#0ec7f97c976d55e1a194a37b27f247e8887fca1d" +source = "git+https://github.com/zcash/librustzcash.git?rev=69c3b4b5e143bb349cca76c677b7cd0a8556b28f#69c3b4b5e143bb349cca76c677b7cd0a8556b28f" dependencies = [ "bech32", "blake2b_simd", @@ -1897,7 +1897,7 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=0ec7f97c976d55e1a194a37b27f247e8887fca1d#0ec7f97c976d55e1a194a37b27f247e8887fca1d" +source = "git+https://github.com/zcash/librustzcash.git?rev=69c3b4b5e143bb349cca76c677b7cd0a8556b28f#69c3b4b5e143bb349cca76c677b7cd0a8556b28f" dependencies = [ "byteorder", "nonempty", @@ -1906,7 +1906,7 @@ dependencies = [ [[package]] name = "zcash_history" version = "0.2.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=0ec7f97c976d55e1a194a37b27f247e8887fca1d#0ec7f97c976d55e1a194a37b27f247e8887fca1d" +source = "git+https://github.com/zcash/librustzcash.git?rev=69c3b4b5e143bb349cca76c677b7cd0a8556b28f#69c3b4b5e143bb349cca76c677b7cd0a8556b28f" dependencies = [ "bigint", "blake2b_simd", @@ -1916,7 +1916,7 @@ dependencies = [ [[package]] name = "zcash_note_encryption" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=0ec7f97c976d55e1a194a37b27f247e8887fca1d#0ec7f97c976d55e1a194a37b27f247e8887fca1d" +source = "git+https://github.com/zcash/librustzcash.git?rev=69c3b4b5e143bb349cca76c677b7cd0a8556b28f#69c3b4b5e143bb349cca76c677b7cd0a8556b28f" dependencies = [ "blake2b_simd", "byteorder", @@ -1931,7 +1931,7 @@ dependencies = [ [[package]] name = "zcash_primitives" version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=0ec7f97c976d55e1a194a37b27f247e8887fca1d#0ec7f97c976d55e1a194a37b27f247e8887fca1d" +source = "git+https://github.com/zcash/librustzcash.git?rev=69c3b4b5e143bb349cca76c677b7cd0a8556b28f#69c3b4b5e143bb349cca76c677b7cd0a8556b28f" dependencies = [ "aes", "bip0039", @@ -1965,7 +1965,7 @@ dependencies = [ [[package]] name = "zcash_proofs" version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=0ec7f97c976d55e1a194a37b27f247e8887fca1d#0ec7f97c976d55e1a194a37b27f247e8887fca1d" +source = "git+https://github.com/zcash/librustzcash.git?rev=69c3b4b5e143bb349cca76c677b7cd0a8556b28f#69c3b4b5e143bb349cca76c677b7cd0a8556b28f" dependencies = [ "bellman", "blake2b_simd", diff --git a/Cargo.toml b/Cargo.toml index b104fd3df..9e7113993 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,8 +73,8 @@ codegen-units = 1 ed25519-zebra = { git = "https://github.com/ZcashFoundation/ed25519-zebra.git", rev = "d3512400227a362d08367088ffaa9bd4142a69c7" } incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree", rev = "b7bd6246122a6e9ace8edb51553fbf5228906cbb" } orchard = { git = "https://github.com/zcash/orchard.git", rev = "68b790c7dadade049f44ad4aafa0ff71a3a10e91" } -zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "0ec7f97c976d55e1a194a37b27f247e8887fca1d" } -zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "0ec7f97c976d55e1a194a37b27f247e8887fca1d" } -zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "0ec7f97c976d55e1a194a37b27f247e8887fca1d" } -zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "0ec7f97c976d55e1a194a37b27f247e8887fca1d" } -zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "0ec7f97c976d55e1a194a37b27f247e8887fca1d" } +zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "69c3b4b5e143bb349cca76c677b7cd0a8556b28f" } +zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "69c3b4b5e143bb349cca76c677b7cd0a8556b28f" } +zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "69c3b4b5e143bb349cca76c677b7cd0a8556b28f" } +zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "69c3b4b5e143bb349cca76c677b7cd0a8556b28f" } +zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "69c3b4b5e143bb349cca76c677b7cd0a8556b28f" } diff --git a/src/rust/src/address_ffi.rs b/src/rust/src/address_ffi.rs index a77488743..bab50741e 100644 --- a/src/rust/src/address_ffi.rs +++ b/src/rust/src/address_ffi.rs @@ -5,7 +5,10 @@ use std::{ }; use libc::{c_char, c_void}; -use zcash_address::{unified, FromAddress, Network, ToAddress, ZcashAddress}; +use zcash_address::{ + unified::{self, Container, Encoding}, + FromAddress, Network, ToAddress, ZcashAddress, +}; use zcash_primitives::sapling; pub type UnifiedAddressObj = NonNull; @@ -69,7 +72,7 @@ impl UnifiedAddressHelper { } self.ua - .receivers() + .items() .into_iter() .map(|receiver| match receiver { unified::Receiver::Orchard(data) => { @@ -209,7 +212,7 @@ pub extern "C" fn zcash_address_serialize_unified( } }; - let ua: unified::Address = match receivers.try_into() { + let ua = match unified::Address::try_from_items(receivers) { Ok(ua) => ua, Err(e) => { tracing::error!("{}", e); From 4257abd32839aad9c771f679ca2f129c4a12699f Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 30 Nov 2021 20:35:26 -0700 Subject: [PATCH 2/9] Adds SaplingDiversifiableFullViewingKey Adds SaplingDiversifiableFullViewingKey as a superclass of SaplingExtendedFullViewingKey, and reduces the Sapling component of UnifiedFullViewingKey to the new intermediate type. This permits deserialization from the data that will be present in the serialized form of the Sapling component of a UFVK. --- src/rust/include/librustzcash.h | 10 ++++--- src/rust/src/rustzcash.rs | 27 +++++++++-------- src/wallet/test/rpc_wallet_tests.cpp | 2 +- src/zcash/address/unified.h | 4 +-- src/zcash/address/zip32.cpp | 28 +++++++++--------- src/zcash/address/zip32.h | 43 ++++++++++++++++------------ 6 files changed, 63 insertions(+), 51 deletions(-) diff --git a/src/rust/include/librustzcash.h b/src/rust/include/librustzcash.h index 94d49535f..048c70ef5 100644 --- a/src/rust/include/librustzcash.h +++ b/src/rust/include/librustzcash.h @@ -317,8 +317,9 @@ extern "C" { * - addr_ret: [c_uchar; 43] array to which the returned address will be written, * if the specified diversifier index `j` produces a valid address. */ - bool librustzcash_zip32_xfvk_address( - const unsigned char *xfvk, + bool librustzcash_zip32_sapling_address( + const unsigned char *fvk, + const unsigned char *dk, const unsigned char *j, unsigned char *addr_ret ); @@ -338,8 +339,9 @@ extern "C" { * returned address was found * - addr_ret: [c_uchar; 43] array to which the returned address will be written */ - bool librustzcash_zip32_find_xfvk_address( - const unsigned char *xfvk, + bool librustzcash_zip32_find_sapling_address( + const unsigned char *fvk, + const unsigned char *dk, const unsigned char *j, unsigned char *j_ret, unsigned char *addr_ret diff --git a/src/rust/src/rustzcash.rs b/src/rust/src/rustzcash.rs index 352000889..ed62203a5 100644 --- a/src/rust/src/rustzcash.rs +++ b/src/rust/src/rustzcash.rs @@ -46,14 +46,15 @@ use zcash_primitives::{ block::equihash, constants::{CRH_IVK_PERSONALIZATION, PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR}, merkle_tree::MerklePath, - sapling::{merkle_hash, spend_sig}, sapling::{ + keys::FullViewingKey, note_encryption::sapling_ka_agree, redjubjub::{self, Signature}, Diversifier, Note, PaymentAddress, ProofGenerationKey, Rseed, ViewingKey, }, + sapling::{merkle_hash, spend_sig}, transaction::components::Amount, - zip32, + zip32::{self, sapling_address, sapling_find_address}, }; use zcash_proofs::{ circuit::sapling::TREE_DEPTH as SAPLING_TREE_DEPTH, @@ -1080,16 +1081,17 @@ pub extern "C" fn librustzcash_zip32_xfvk_derive( /// Derive a PaymentAddress from an ExtendedFullViewingKey. #[no_mangle] -pub extern "C" fn librustzcash_zip32_xfvk_address( - xfvk: *const [c_uchar; 169], +pub extern "C" fn librustzcash_zip32_sapling_address( + fvk: *const [c_uchar; 96], + dk: *const [c_uchar; 32], j: *const [c_uchar; 11], addr_ret: *mut [c_uchar; 43], ) -> bool { - let xfvk = zip32::ExtendedFullViewingKey::read(&unsafe { *xfvk }[..]) - .expect("valid ExtendedFullViewingKey"); + let fvk = FullViewingKey::read(&unsafe { *fvk }[..]).expect("valid Sapling FullViewingKey"); + let dk = zip32::DiversifierKey(unsafe { *dk }); let j = zip32::DiversifierIndex(unsafe { *j }); - match xfvk.address(j) { + match sapling_address(&fvk, &dk, j) { Some(addr) => { let addr_ret = unsafe { &mut *addr_ret }; addr_ret.copy_from_slice(&addr.to_bytes()); @@ -1102,17 +1104,18 @@ pub extern "C" fn librustzcash_zip32_xfvk_address( /// Derive a PaymentAddress from an ExtendedFullViewingKey. #[no_mangle] -pub extern "C" fn librustzcash_zip32_find_xfvk_address( - xfvk: *const [c_uchar; 169], +pub extern "C" fn librustzcash_zip32_find_sapling_address( + fvk: *const [c_uchar; 96], + dk: *const [c_uchar; 32], j: *const [c_uchar; 11], j_ret: *mut [c_uchar; 11], addr_ret: *mut [c_uchar; 43], ) -> bool { - let xfvk = zip32::ExtendedFullViewingKey::read(&unsafe { *xfvk }[..]) - .expect("valid ExtendedFullViewingKey"); + let fvk = FullViewingKey::read(&unsafe { *fvk }[..]).expect("valid Sapling FullViewingKey"); + let dk = zip32::DiversifierKey(unsafe { *dk }); let j = zip32::DiversifierIndex(unsafe { *j }); - match xfvk.find_address(j) { + match sapling_find_address(&fvk, &dk, j) { Some((j, addr)) => { let j_ret = unsafe { &mut *j_ret }; let addr_ret = unsafe { &mut *addr_ret }; diff --git a/src/wallet/test/rpc_wallet_tests.cpp b/src/wallet/test/rpc_wallet_tests.cpp index 7b91c2167..0528ab0e6 100644 --- a/src/wallet/test/rpc_wallet_tests.cpp +++ b/src/wallet/test/rpc_wallet_tests.cpp @@ -80,7 +80,7 @@ static SaplingPaymentAddress DefaultSaplingAddress(CWallet* pwallet) { return usk.value() .ToFullViewingKey() - .GetSaplingExtendedFullViewingKey().value() + .GetSaplingKey().value() .FindAddress(libzcash::diversifier_index_t(0)).first; } diff --git a/src/zcash/address/unified.h b/src/zcash/address/unified.h index be87a8d6b..68de27920 100644 --- a/src/zcash/address/unified.h +++ b/src/zcash/address/unified.h @@ -17,7 +17,7 @@ class ZcashdUnifiedFullViewingKey; class ZcashdUnifiedFullViewingKey { private: std::optional transparentKey; - std::optional saplingKey; + std::optional saplingKey; ZcashdUnifiedFullViewingKey() {} @@ -27,7 +27,7 @@ public: return transparentKey; } - const std::optional& GetSaplingExtendedFullViewingKey() const { + const std::optional& GetSaplingKey() const { return saplingKey; } diff --git a/src/zcash/address/zip32.cpp b/src/zcash/address/zip32.cpp index fc558edef..c0512a43d 100644 --- a/src/zcash/address/zip32.cpp +++ b/src/zcash/address/zip32.cpp @@ -83,15 +83,16 @@ std::optional SaplingExtendedFullViewingKey::Deri } std::optional - SaplingExtendedFullViewingKey::Address(diversifier_index_t j) const + SaplingDiversifiableFullViewingKey::Address(diversifier_index_t j) const { - CDataStream ss_xfvk(SER_NETWORK, PROTOCOL_VERSION); - ss_xfvk << *this; - CSerializeData xfvk_bytes(ss_xfvk.begin(), ss_xfvk.end()); + CDataStream ss_fvk(SER_NETWORK, PROTOCOL_VERSION); + ss_fvk << fvk; + CSerializeData fvk_bytes(ss_fvk.begin(), ss_fvk.end()); CSerializeData addr_bytes(libzcash::SerializedSaplingPaymentAddressSize); - if (librustzcash_zip32_xfvk_address( - reinterpret_cast(xfvk_bytes.data()), + if (librustzcash_zip32_sapling_address( + reinterpret_cast(fvk_bytes.data()), + dk.begin(), j.begin(), reinterpret_cast(addr_bytes.data()))) { CDataStream ss_addr(addr_bytes, SER_NETWORK, PROTOCOL_VERSION); @@ -103,17 +104,18 @@ std::optional } } -libzcash::SaplingPaymentAddress SaplingExtendedFullViewingKey::DefaultAddress() const +libzcash::SaplingPaymentAddress SaplingDiversifiableFullViewingKey::DefaultAddress() const { - CDataStream ss_xfvk(SER_NETWORK, PROTOCOL_VERSION); - ss_xfvk << *this; - CSerializeData xfvk_bytes(ss_xfvk.begin(), ss_xfvk.end()); + CDataStream ss_fvk(SER_NETWORK, PROTOCOL_VERSION); + ss_fvk << fvk; + CSerializeData fvk_bytes(ss_fvk.begin(), ss_fvk.end()); diversifier_index_t j_default; diversifier_index_t j_ret; CSerializeData addr_bytes_ret(libzcash::SerializedSaplingPaymentAddressSize); - if (librustzcash_zip32_find_xfvk_address( - reinterpret_cast(xfvk_bytes.data()), + if (librustzcash_zip32_find_sapling_address( + reinterpret_cast(fvk_bytes.data()), + dk.begin(), j_default.begin(), j_ret.begin(), reinterpret_cast(addr_bytes_ret.data()))) { CDataStream ss_addr(addr_bytes_ret, SER_NETWORK, PROTOCOL_VERSION); @@ -122,7 +124,7 @@ libzcash::SaplingPaymentAddress SaplingExtendedFullViewingKey::DefaultAddress() return addr; } else { // If we can't obtain a default address, we are *very* unlucky... - throw std::runtime_error("SaplingExtendedFullViewingKey::DefaultAddress(): No valid diversifiers out of 2^88!"); + throw std::runtime_error("SaplingDiversifiableFullViewingKey::DefaultAddress(): No valid diversifiers out of 2^88!"); } } diff --git a/src/zcash/address/zip32.h b/src/zcash/address/zip32.h index 7f9cf4cc9..608918e68 100644 --- a/src/zcash/address/zip32.h +++ b/src/zcash/address/zip32.h @@ -117,28 +117,11 @@ public: } }; -struct SaplingExtendedFullViewingKey { - uint8_t depth; - uint32_t parentFVKTag; - uint32_t childIndex; - uint256 chaincode; +class SaplingDiversifiableFullViewingKey { +public: libzcash::SaplingFullViewingKey fvk; uint256 dk; - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(depth); - READWRITE(parentFVKTag); - READWRITE(childIndex); - READWRITE(chaincode); - READWRITE(fvk); - READWRITE(dk); - } - - std::optional Derive(uint32_t i) const; - // Attempts to construct a valid payment address with diversifier index // `j`; returns std::nullopt if `j` does not result in a valid diversifier // given this xfvk. @@ -158,6 +141,28 @@ struct SaplingExtendedFullViewingKey { } libzcash::SaplingPaymentAddress DefaultAddress() const; +}; + +class SaplingExtendedFullViewingKey: public SaplingDiversifiableFullViewingKey { +public: + uint8_t depth; + uint32_t parentFVKTag; + uint32_t childIndex; + uint256 chaincode; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(depth); + READWRITE(parentFVKTag); + READWRITE(childIndex); + READWRITE(chaincode); + READWRITE(fvk); + READWRITE(dk); + } + + std::optional Derive(uint32_t i) const; friend inline bool operator==(const SaplingExtendedFullViewingKey& a, const SaplingExtendedFullViewingKey& b) { return ( From c78887a14870ef4a66bc10ae994931e6b0095dba Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 1 Dec 2021 19:09:16 -0700 Subject: [PATCH 3/9] Add Rust FFI components for unified full viewing keys. --- src/rust/include/rust/unified_keys.h | 102 +++++++++++++++++++++ src/rust/src/address_ffi.rs | 2 +- src/rust/src/rustzcash.rs | 1 + src/rust/src/unified_keys_ffi.rs | 132 +++++++++++++++++++++++++++ 4 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 src/rust/include/rust/unified_keys.h create mode 100644 src/rust/src/unified_keys_ffi.rs diff --git a/src/rust/include/rust/unified_keys.h b/src/rust/include/rust/unified_keys.h new file mode 100644 index 000000000..10ea8ea01 --- /dev/null +++ b/src/rust/include/rust/unified_keys.h @@ -0,0 +1,102 @@ +// Copyright (c) 2021 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +#ifndef ZCASH_RUST_INCLUDE_RUST_UNIFIED_KEYS_H +#define ZCASH_RUST_INCLUDE_RUST_UNIFIED_KEYS_H + +#ifdef __cplusplus +extern "C" { +#endif + +// +// Unified full viewing keys +// + +/** + * Void pointer type representing a reference to a Rust-allocated + * unified::Ufvk value + */ +struct UnifiedFullViewingKeyPtr; +typedef struct UnifiedFullViewingKeyPtr UnifiedFullViewingKeyPtr; + +/** + * Free the memory allocated for the given address::unified::Ufvk; + */ +void unified_full_viewing_key_free(UnifiedFullViewingKeyPtr* ptr); + +/** + * Clones the given Unified full viewing key and returns + * a pointer to the newly created value. Both the original + * one's memory and the newly allocated one need to be freed + * independently. + */ +UnifiedFullViewingKeyPtr* unified_full_viewing_key_clone( + const UnifiedFullViewingKeyPtr* ptr); + +/** + * Parses a unified full viewing key from the given string. + * + * - If the key does not parse correctly, or the network for which the + * key was encoded does not match the specified network the returned + * pointer will be null. + */ +UnifiedFullViewingKeyPtr* unified_full_viewing_key_parse( + const char* network, + const char* str); + +/** + * Serializes a unified full viewing key and returns its string representation. + */ +char* unified_full_viewing_key_serialize( + const char* network, + const UnifiedFullViewingKeyPtr* full_viewing_key); + +/** + * Reads the transparent component of a unified viewing key. + * + * `tkeyout` must be of length 65. + * + * Returns `true` if the UFVK contained a transparent component, + * `false` otherwise. The bytes of the transparent key will be + * copied to tkeyout + */ +bool unified_full_viewing_key_read_transparent( + const UnifiedFullViewingKeyPtr* full_viewing_key, + unsigned char *tkeyout); + +/** + * Reads the Sapling component of a unified viewing key. + * + * `skeyout` must be of length 128. + * + * Returns `true` if the UFVK contained a Sapling component, + * false otherwise. + */ +bool unified_full_viewing_key_read_sapling( + const UnifiedFullViewingKeyPtr* full_viewing_key, + unsigned char *skeyout); + +/** + * Sets the Sapling component of a unified viewing key. + * + * `t_key` must be of length 65 and must be the concatenated + * bytes of the serialized `(ChainCode, CPubKey)` pair. + * + * `sapling_key` must be of length 128 and must be the concatenated + * bytes of the serialized `(SaplingFullViewingKey, DiversifierKey)` + * pair. + * + * Returns the newly allocated UFVK if the operation succeeds, + * or the null pointer otherwise. + */ +UnifiedFullViewingKeyPtr* unified_full_viewing_key_from_components( + const unsigned char *t_key, + const unsigned char *sapling_key); + +#ifdef __cplusplus +} +#endif + +#endif // ZCASH_RUST_INCLUDE_RUST_UNIFIED_KEYS_H + diff --git a/src/rust/src/address_ffi.rs b/src/rust/src/address_ffi.rs index bab50741e..808af7f54 100644 --- a/src/rust/src/address_ffi.rs +++ b/src/rust/src/address_ffi.rs @@ -26,7 +26,7 @@ pub type GetReceiverLenCb = pub type GetReceiverDataCb = unsafe extern "C" fn(ua: Option, index: usize, data: *mut u8, length: usize); -fn network_from_cstr(network: *const c_char) -> Option { +pub(crate) fn network_from_cstr(network: *const c_char) -> Option { match unsafe { CStr::from_ptr(network) }.to_str().unwrap() { "main" => Some(Network::Main), "test" => Some(Network::Test), diff --git a/src/rust/src/rustzcash.rs b/src/rust/src/rustzcash.rs index ed62203a5..86f11f4e5 100644 --- a/src/rust/src/rustzcash.rs +++ b/src/rust/src/rustzcash.rs @@ -75,6 +75,7 @@ mod incremental_merkle_tree_ffi; mod orchard_ffi; mod orchard_keys_ffi; mod transaction_ffi; +mod unified_keys_ffi; mod zip339_ffi; mod test_harness_ffi; diff --git a/src/rust/src/unified_keys_ffi.rs b/src/rust/src/unified_keys_ffi.rs new file mode 100644 index 000000000..eeca01c18 --- /dev/null +++ b/src/rust/src/unified_keys_ffi.rs @@ -0,0 +1,132 @@ +use libc::c_char; +use std::ffi::{CStr, CString}; +use tracing::error; + +use zcash_address::unified::{Container, Encoding, Fvk, Ufvk}; + +use crate::address_ffi::network_from_cstr; + +#[no_mangle] +pub extern "C" fn unified_full_viewing_key_free(key: *mut Ufvk) { + if !key.is_null() { + drop(unsafe { Box::from_raw(key) }); + } +} + +#[no_mangle] +pub extern "C" fn unified_full_viewing_key_clone(key: *const Ufvk) -> *mut Ufvk { + unsafe { key.as_ref() } + .map(|key| Box::into_raw(Box::new(key.clone()))) + .unwrap_or(std::ptr::null_mut()) +} + +#[no_mangle] +pub extern "C" fn unified_full_viewing_key_parse( + network: *const c_char, + encoded: *const c_char, +) -> *mut Ufvk { + let network = match network_from_cstr(network) { + Some(n) => n, + None => return std::ptr::null_mut(), + }; + + match unsafe { CStr::from_ptr(encoded) }.to_str() { + Ok(encoded) => match Ufvk::decode(encoded) { + Ok((parsed_network, fvk)) => { + if parsed_network == network { + Box::into_raw(Box::new(fvk)) + } else { + error!( + "Key was encoded for a different network than what was requested: {:?}", + parsed_network + ); + std::ptr::null_mut() + } + } + Err(e) => { + 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() + } + } +} + +#[no_mangle] +pub extern "C" fn unified_full_viewing_key_serialize( + network: *const c_char, + key: *const Ufvk, +) -> *mut c_char { + let key = unsafe { key.as_ref() }.expect("Unified full viewing key pointer may not be null."); + match network_from_cstr(network) { + Some(n) => CString::new(key.encode(&n)).unwrap().into_raw(), + None => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub extern "C" fn unified_full_viewing_key_read_transparent( + key: *const Ufvk, + out: *mut [u8; 65], +) -> 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::P2pkh(data) = r { + *out = *data; + return true; + } + } + + false +} + +#[no_mangle] +pub extern "C" fn unified_full_viewing_key_read_sapling( + key: *const Ufvk, + out: *mut [u8; 128], +) -> 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::Sapling(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], +) -> *mut Ufvk { + let t_key = unsafe { t_key.as_ref() }; + let sapling_key = unsafe { sapling_key.as_ref() }; + + let mut items = vec![]; + if let Some(t_bytes) = t_key { + items.push(Fvk::P2pkh(*t_bytes)); + } + if let Some(sapling_bytes) = sapling_key { + items.push(Fvk::Sapling(*sapling_bytes)); + } + + match Ufvk::try_from_items(items) { + Ok(ufvk) => Box::into_raw(Box::new(ufvk)), + Err(e) => { + error!( + "An error occurred constructing the unified full viewing key: {:?}", + e + ); + std::ptr::null_mut() + } + } +} From 217c56811d1f7c4e38caa2cd7fe46bac66215d98 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 29 Nov 2021 16:40:29 -0700 Subject: [PATCH 4/9] Add UnifiedFullViewingKey type. This type is backed by the `zcash_address` implementaion of unified full viewing keys. It is intended for serialization and parsing of UFVKs, and provides conversion functions that allow for construction to and from ZcashdUnifiedFullViewingKey values. --- src/key_constants.h | 2 + src/key_io.cpp | 11 +++ src/pubkey.cpp | 8 +++ src/pubkey.h | 49 +++++++++++++ src/rust/include/rust/unified_keys.h | 8 +-- src/rust/src/address_ffi.rs | 2 +- src/rust/src/unified_keys_ffi.rs | 9 ++- src/wallet/wallet.cpp | 4 ++ src/wallet/wallet.h | 1 + src/zcash/Address.cpp | 104 +++++++++++++++++++++++++++ src/zcash/Address.hpp | 83 +++++++++++++++++++-- src/zcash/address/unified.cpp | 47 ++++++++++-- src/zcash/address/unified.h | 21 +++--- src/zcash/address/zip32.h | 20 ++++++ 14 files changed, 339 insertions(+), 30 deletions(-) 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 { From 22cd218e831c508108f7155cd3d716dfdc592481 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 6 Dec 2021 11:56:32 -0700 Subject: [PATCH 5/9] Apply suggestions from code review Co-authored-by: Daira Hopwood --- src/rust/include/librustzcash.h | 6 ++++-- src/rust/include/rust/unified_keys.h | 24 ++++++++++++++++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/rust/include/librustzcash.h b/src/rust/include/librustzcash.h index 048c70ef5..7b538bcb3 100644 --- a/src/rust/include/librustzcash.h +++ b/src/rust/include/librustzcash.h @@ -311,7 +311,8 @@ extern "C" { * if no valid address can be derived for the specified diversifier index. * * Arguments: - * - xfvk: [c_uchar; 169] the serialized form of a Sapling extended full viewing key + * - xfvk: [c_uchar; 96] the serialized form of a Sapling full viewing key + * - dk: [c_uchar; 32] the byte representation of a Sapling diversifier key * - j: [c_uchar; 11] the 88-bit diversifier address at which to start searching, * encoded in little-endian order * - addr_ret: [c_uchar; 43] array to which the returned address will be written, @@ -332,7 +333,8 @@ extern "C" { * in which case this function will return `false`. * * Arguments: - * - xfvk: [c_uchar; 169] the serialized form of a Sapling extended full viewing key + * - xfvk: [c_uchar; 96] the serialized form of a Sapling full viewing key + * - dk: [c_uchar; 32] the byte representation of a Sapling diversifier key * - j: [c_uchar; 11] the 88-bit diversifier address at which to start searching, * encoded in little-endian order * - j_ret: [c_uchar; 11] array that will store the diversifier index at which the diff --git a/src/rust/include/rust/unified_keys.h b/src/rust/include/rust/unified_keys.h index 6d993bd47..56360b55d 100644 --- a/src/rust/include/rust/unified_keys.h +++ b/src/rust/include/rust/unified_keys.h @@ -47,6 +47,9 @@ UnifiedFullViewingKeyPtr* unified_full_viewing_key_parse( /** * Serializes a unified full viewing key and returns its string representation. + * + * The returned string's memory must be freed by the caller using + * `zcash_address_string_free`. */ char* unified_full_viewing_key_serialize( const char* network, @@ -57,9 +60,9 @@ char* unified_full_viewing_key_serialize( * * `tkeyout` must be of length 65. * - * Returns `true` if the UFVK contained a transparent component, - * `false` otherwise. The bytes of the transparent key will be - * copied to tkeyout + * Returns `true` if the UFVK contained a transparent component, `false` + * otherwise. If this returns `true`, the transparent key will be copied to + * `tkeyout` as the byte representation of the `(ChainCode, CPubKey)` pair */ bool unified_full_viewing_key_read_transparent( const UnifiedFullViewingKeyPtr* full_viewing_key, @@ -71,24 +74,29 @@ bool unified_full_viewing_key_read_transparent( * `skeyout` must be of length 128. * * Returns `true` if the UFVK contained a Sapling component, - * false otherwise. + * `false` otherwise. The bytes of the `(ak, nk, ovk, dk)` fields + * of the viewing key, in the encoding given by `EncodeExtFVKParts` + * defined in ZIP 32, will be copied to `skeyout` if `true` is returned. */ bool unified_full_viewing_key_read_sapling( const UnifiedFullViewingKeyPtr* full_viewing_key, unsigned char* skeyout); /** - * Sets the Sapling component of a unified viewing key. + * Constructs a unified full viewing key from the binary encodings + * of its constituent parts * * `t_key` must be of length 65 and must be the concatenated * bytes of the serialized `(ChainCode, CPubKey)` pair. * * `sapling_key` must be of length 128 and must be the concatenated * bytes of the serialized `(SaplingFullViewingKey, DiversifierKey)` - * pair. + * pair in the encoding given by `EncodeExtFVKParts` defined in + * ZIP 32. * - * Returns the newly allocated UFVK if the operation succeeds, - * or the null pointer otherwise. + * Returns a pointer to newly allocated UFVK if the operation succeeds, + * or the null pointer otherwise. The pointer returned by this function + * must be freed by the caller with `unified_full_viewing_key_free`. */ UnifiedFullViewingKeyPtr* unified_full_viewing_key_from_components( const unsigned char* t_key, From 8ae8ddd7d10dcd8e179e7e6124ac35f488efaaed Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 8 Dec 2021 12:05:33 -0700 Subject: [PATCH 6/9] Add tests for ufvk roundtrip serialization. --- src/Makefile.test.include | 1 + src/gtest/test_keys.cpp | 80 +++++++++++++++++++- src/rust/src/unified_keys_ffi.rs | 9 ++- src/test/data/unified_full_viewing_keys.json | 14 ++++ 4 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 src/test/data/unified_full_viewing_keys.json diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 8f86ec3ba..a21b7e403 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -44,6 +44,7 @@ JSON_TEST_FILES = \ test/data/merkle_commitments_sapling.json \ test/data/sapling_key_components.json \ test/data/unified_addrs.json \ + test/data/unified_full_viewing_keys.json \ test/data/zip0244.json RAW_TEST_FILES = test/data/alertTests.raw diff --git a/src/gtest/test_keys.cpp b/src/gtest/test_keys.cpp index d86c959f6..7927973a8 100644 --- a/src/gtest/test_keys.cpp +++ b/src/gtest/test_keys.cpp @@ -10,6 +10,7 @@ #include "json_test_vectors.h" #include "test/data/unified_addrs.json.h" +#include "test/data/unified_full_viewing_keys.json.h" TEST(Keys, EncodeAndDecodeSapling) { @@ -102,7 +103,7 @@ namespace libzcash { } } -TEST(Keys, EncodeAndDecodeUnified) +TEST(Keys, EncodeAndDecodeUnifiedAddresses) { SelectParams(CBaseChainParams::MAIN); KeyIO keyIO(Params()); @@ -131,8 +132,7 @@ TEST(Keys, EncodeAndDecodeUnified) if (!test[2].isNull()) { auto data = ParseHex(test[2].get_str()); CDataStream ss( - reinterpret_cast(data.data()), - reinterpret_cast(data.data() + data.size()), + data, SER_NETWORK, PROTOCOL_VERSION); libzcash::SaplingPaymentAddress r; @@ -164,3 +164,77 @@ TEST(Keys, EncodeAndDecodeUnified) } } } + +TEST(Keys, EncodeAndDecodeUnifiedFullViewingKeys) +{ + SelectParams(CBaseChainParams::MAIN); + KeyIO keyIO(Params()); + + UniValue ua_tests = read_json(MAKE_STRING(json_tests::unified_full_viewing_keys)); + + for (size_t idx = 0; idx < ua_tests.size(); idx++) { + UniValue test = ua_tests[idx]; + std::string strTest = test.write(); + if (test.size() < 1) // Allow for extra stuff (useful for comments) + { + FAIL() << "Bad test: " << strTest; + continue; + } + if (test.size() == 1) continue; // comment + + //try { + libzcash::UnifiedFullViewingKeyBuilder builder; + // ["p2pkh_key_bytes, sapling_key_bytes, orchard_key_bytes, unknown_key_bytes, unified_key"] + // These were added to the UA in preference order by the Python test vectors. + if (!test[0].isNull()) { + auto data = ParseHex(test[0].get_str()); + ASSERT_EQ(data.size(), 65); + CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION); + auto decoded = CChainablePubKey::Read(ss); + ASSERT_TRUE(builder.AddTransparentKey(decoded)); + } + if (!test[1].isNull()) { + auto data = ParseHex(test[1].get_str()); + ASSERT_EQ(data.size(), 128); + CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION); + 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[3].get_str()); + // libzcash::UnknownReceiver r(0x03, data); + // ASSERT_TRUE(builder.AddUnknownItem(r)); + // } + // if (!test[4].isNull()) { + // auto data = ParseHex(test[4].get_str()); + // libzcash::UnknownReceiver r(test[3].get_int(), data); + // ASSERT_TRUE(builder.AddUnknownItem(r)); + // } + + auto built = builder.build(); + ASSERT_TRUE(built.has_value()); + + auto keystrBytes = ParseHex(test[5].get_str()); + std::string keystr(keystrBytes.begin(), keystrBytes.end()); + + auto decoded = libzcash::UnifiedFullViewingKey::Decode(keystr, Params()); + ASSERT_TRUE(decoded.has_value()); + + EXPECT_EQ(decoded.value().GetTransparentKey(), built.value().GetTransparentKey()); + EXPECT_EQ(decoded.value().GetSaplingKey(), built.value().GetSaplingKey()); + //} catch (const std::exception& ex) { + // FAIL() << "Bad test, couldn't deserialize data: " << strTest << ": " << ex.what(); + //} catch (...) { + // FAIL() << "Bad test, couldn't deserialize data: " << strTest; + //} + } +} diff --git a/src/rust/src/unified_keys_ffi.rs b/src/rust/src/unified_keys_ffi.rs index b7ecfc383..8037fc2ea 100644 --- a/src/rust/src/unified_keys_ffi.rs +++ b/src/rust/src/unified_keys_ffi.rs @@ -33,8 +33,7 @@ pub extern "C" fn unified_full_viewing_key_parse( }; 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)) @@ -50,7 +49,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() @@ -125,6 +124,10 @@ pub extern "C" fn unified_full_viewing_key_from_components( match Ufvk::try_from_items(items) { Ok(ufvk) => Box::into_raw(Box::new(ufvk)), Err(e) => { + println!( + "An error occurred constructing the unified full viewing key: {:?}", + e + ); error!( "An error occurred constructing the unified full viewing key: {:?}", e diff --git a/src/test/data/unified_full_viewing_keys.json b/src/test/data/unified_full_viewing_keys.json new file mode 100644 index 000000000..99c6990a0 --- /dev/null +++ b/src/test/data/unified_full_viewing_keys.json @@ -0,0 +1,14 @@ +[ + ["From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/unified_full_viewing_keys.py"], + ["t_key_bytes, sapling_fvk_bytes, orchard_fvk_bytes, unknown_fvk_typecode, unknown_fvk_bytes, unified_fvk"], + [null, "cfb835e7c05c80c2a15a58702bc529a44e1a815ef79124f23709214cf0167ac4e6340b493dca8e4bee114259dc35edc4c296ffd53869885531d1bdb27008bbcd6fec092ad5c4d1f68819f41ae447db96df4a5f110018f47060916ec54884f1cc27a0d4c0bca90984cdf39fb4cc61ceee78ddaa2a45af871f49f04e98b02fb16b", null, 65535, null, "757669657731747878783339707833736a676478796c6d6636666876706e6878667966717376756e3863737330723678717830726b3974767a3076727a74756a74683474716e7534367877657035367279396a643537687972726c36757467657a356a717232716466737a79787265686b64686774757964376d78756e6e6133327732356d396b7771387163687377673476686632796d736b376c6e7637786a3864356b347a7272343370756e746b6d666b396e346a636b66763237677063376e747765726c6c756439307a346c356c71786a68366333356b376135786c6d663563726467677537366c307572657475787333386839"], + [null, "04da0d94cb0a6397067a81a88ef422e56678e0ba232eb4dd6b05b98bc5e3461cd4a52b366a7df1f3a871854bfe1492711dc5130b35441748caa2742959279ce31e2b3604995d4ccedc4618ad16cdd2c0d42a6d36fb3a0610054cedef30beca20d187f32ce02f8ca357b575e705cda7ef8e1c68b9110381bd3958e0659a084205", "9bf5e479a60cfa3893a2a7693488b3fa016402851263e63c48540b3daff13814cd71a2ab1f78c561c768a3641dcf7a7d6b473393a629841c81f93afa87689035a119c9f7dac6fb9e7cf8fd6aa40f5f7f9e75b161d5f7269d28a4211ab84a8408", 65532, null, "75766965773135797678377477766a376a726a67766d6e6c797474683632396e7861746c7837686e79727a707a3468337a74776a79786c7733793770657a737671397276643470726779326474726a6c736d6c326c76366c6a7575306a7274766b6d6d366c6a32793768706c70686b383978656b66636565687a637a6777773237756538763272376e3077727339636b78376134756c6365766b777830363077667139613379357932746d337132777163756c796d3232396668387474386a6d376c683863367639707a67613274396e6c78673630377478353673303775757177337673336d78663676716b68353071636a33677678347664367a367379356e3039387565636c396a707968737178333934677337323539786b33796c376764326e333370767371707979377064393734347271786e73343073327a323963796b3435766a763765756b713976777a3879306163383539707a7166386b6d6c6b767678723930716b326e30376c6e7264393061363467376b6472336366356a637665726c7173667134366e6e71777137386b68"], + ["546c1fe01f7e9c8e36d6a5e29d4e30a73594bf5098421c69378af1e40f64e12503f4e4887b87079d7b7647f01a93a73c7a7d517840575c3246710adbdd752ed2d6", "fc02759ff2853b5e9e15842bef22c76023d43db7c265f120ed175713195f1240f63aee02ec23f146e9e25d25605fbae5472cdedc3b4c31c66b76fe9e6e47eeca79d5fb6a84d152820daaf89e99551d068d99cdf9be065007bc25f245ea62631d17b3bc77f62f35bd4205e6f682b1f9e824ecea53e271b80ff6bc79ef68a20ab5", "c36559ed79d1fecbe1de93d57698e915802f0ccd097adb4e448caef8c48d6c1e439482a1e5636131cba024f891b3bb1dfe333773415eeecf7a42e37dbd934e3a97d92dc90afa0ec3e96eeed9238dfcb6da23799cff14d477368d9ddc330afe39", 65532, null, "757669657731796e727935646c356c676b6733656a61326b386d777779646661357a363465726674657936383365776561326e3068333436636134326e6d7561347170636b63787565707073366e326d656a6c6b64363930333772766d7a61343677306c6d3035676a7070686566756a7a6d6734663876366d7130746b676766376667727138307a6d6d6b756467737739397465687363396d6a65773966367238366b6463646a7a666e66773065326b6d713567646b6866617175797167676b64616d68727a726e6a756663777739386e6577647a7479726d353775757037673338383730703932763776787a727377336c6366797a37616e326473616637757733663672716b32747a6a396c66356371376378346a77767667776a3078787064723433786a6d63646b366b6c6668773379663672617168383676676c61706a636d6b6879776a67366b687161777561667077716a6b776a75756c6a646e756b68646c6b6e726c3878307674727075727335636b7a357734683030397674657165646478786e63666e736c6e6830687074723665766566366e7a393268723838766c616a79366b6378366d306a67613267706b7773736b347277773373766d6e61683437656b64687430713964647467666e7a6736736d6b656c66336a723834707473706c376e37646575397a306d65616b756b336b3867707768703532"], + [null, null, "13a3e9649004641d3fc4da1cdefc4c5c6c457b0aee6e668d1d57a58bc48b0e1501a9e2572d16a3cb72c7b539582a46d8f67823da02235d41d49c12788bceeb334500fef8adfc1bcbaa0eede5b48ea7d3e9ec2a774986aac43050c50aa24f1d0f", 65533, null, "757669657731686d3672366e6434747463707137786e326a307367796a336d63726e6633336b796b3574797271666c70663774327266336a6c6530713839616a6870327764746164397533646e76356b6c746171767576647a6c7770397679726378666535676b763078646a6578326e68707839336e65367876307770386130676c6e36746a6b76796b7174686166726e686e6b6633676138656a766676717636386735633974386a75656e7a6833396766776a64637238786d7476677937656d7465"], + [null, null, "33ca395730a107b148032a999349f6dc9983daab9d01ffb1180b56ba7038221d1bf174c534ae1adb7a61a30ad0e3b36c52cf90957491ed84f90b5bd42c76833ef0cf897fe579461bc8db0b37e2da35766f2ee00712416c87f0a679fc55d4791f", 65531, null, "7576696577316478386a3774756b7473686678737930336167706a68633878326d716137343338353861343274736c327335786b78647a706534617664677363647978366163676c35736c656a3978616e33307479616735707333706a6a7375677075307276343237656b6c336c78616c6738637378726568667670717635306672617772687868716b38766a387975353270336875333872786c3772726e6c766a663374776b7467336b67323667656765636d30786873726d673671667036327563"], + ["9db6990ed83dd64af3597c04323ea51b0052ad8084a8b9da948d320dadd64f540215761b6d5155ba66df44f224e914e4a8bb6f2fee6820a865ac9fbfb327c11507", null, "a71cccc7d3f4fd389ba45bc4dc4e9b3d91d1fdc70eb15ab92aced578409e272f15181f6ae4b8412caf1044dab7749111cdba8c43a4ffad1de39bb22d2dd150077e9343b886179115512836623a664e3125d2faf44a1308e3dbd525da73e46523", 65534, null, "75766965773166326d6d6a783867786e376a716a796a397872776c6b32656d61617770393561326e716a3366373574766b37737a766d6430686e6d7a6d79637732336773343870646a37676d326c36637573763464733935666d656d723972723966787838333370776a63336c7a353330376c787a7632366630757132756b70776167717730786c7a6a746838386476776536616a3339686d6e7661307561716b6a656c73373268386563757539753273636c30756c373239687a397437793537656b617978777375326638366164306c3536727237653374353378736333787333347075766167667564787734766a377039336c346377303232747238706b746567716d386e32737835687061747a3776357130636d3865767032356d77776b6767657568677366737a357078"], + ["1ebd931de518856878f73476f21a482ec9378365c8f7393c94e2885315eb4671021c3e1aa400191c4609ba93df32612f46dfa68aebcfb80778b7fb59bb563e0a7a", null, "3d301cbf5f38ad466992b6466c3377d9f314e6e0e456cd8879f9f9a2db838232c3c801b93141a1ddc29f1079a9b3b46126ed23aeabc4007912798cadf9cfc00e800540cf11186e253036ae5a53f875c50a4a7940021523a92d8b359c140acb31", 65534, null, "7576696577317237386b6434393237373270396371646165336a396a6a647572637a76357a75633632706b366b3371756674776339396735666c7a6366356470737a676b6d6574307a6533726d38746c33356d63647a686564616a74386e7a397877377676656c673275397137756a373876736372666d383466703937633770753867326e78376a3476346c3966633530637671756b396d37673536306d386e3468707972726e6c6a39726b377537726d657578303265723876727a727167326136767535766b786d6b66753368326834666b30777a336c337735657173676c393374777037616d6d3936726b6b30356d6e326b7970677a3863636e6c3778786e3973616b35787a6c65617072736c3630653573323870346e796875356b6a32776b35726c6676766e3434396d35"], + ["26c1039586a7afcf4a0d9c731e985d99589c8bb838e8aaf745533ed9e8ae3a1c02ca6a53e13c134c1fb70013b1c3c7f59788b28df32fac5c220f565bd926dca633", null, "ffdffc7112cf550135fa5476272b24289fcabc5401b16db739d1df8f4493f1143c3cc90b837152144de475182209b0c169559f4a91fb788403b3b7642cc99c0d519abc658bfae98e12c9878d9e16c22937cc8182ebac54f15e307a2e639da239", 65535, null, "75766965773139706d666b617678647639777a6867766670653070753678727366373967616e613832396c3277667537713833336e63667a71657732656c637579643674757139683770656472397871763068713461637768396e647332776436677474357633776530777964336374736463336866676b7968637779776d753733323076666b30636b6e70326e65706a64686d756b3375746365613267796d78306630327a6d6c61386a706137656c6d7768796b7434637371333739717a356133386579346b79353077373064353330797a6a6d686c7473637a32673832766a7479616b65737366323776397177736b79713734786c6a63333530663767793036306a686b6c673871727671746535656c6d637374336e6b30766b6d6c6e6d6d7437367337657363656a707263"], + [null, "deb352b3dfeb09d42a96e77ce38e8a6bd00a1052e565215e1325e0712703368c8dbc0cd5eb82701bbd5903e60d488ef8f8944940ca7d87efbbd6c1eacf9ede8019a3b8fde82c151e11321e54a49aaf6de8b9d6515816a5104fb6ea54ed33873dc4a6ba5a80c1ee0f78378db0052388372aecdde7f53fb35320bec33763ddaa51", "37e5d0367502f509f94f077449cbd07473b9d2b630a5b4f8cefa689543d27028b01e4b736cc5620dc82093b4077b12fab582f225f3a5e58a76921cdad17dd70d458aee1ef78af37e9aadfffbd95db1046ad5b9f29ea4297050cadf9825721632", 65533, "857deecc40a98d5f2935395ee4762dd21afdbb5d47fa9a6dd984d567db2857b927b7fae2db587105415d4642789d38f50b8dbcc129cab3d17d19f3355bcf73cecb8cb8a5da01307152f13936a270572670dc82d39026c6cb4cd4b0f7f5aa2a4f5a", "757669657731756d6b78787175716e7377307a73717a6175327235376d3863706a75363576787865636d6437686c726d677367326c63653932396c6a3563776c636e65707973636577306371353577646d657072767476337a707665356465727976716d726c7035796770346c733264716d796764636738636d6132763878656e6e7a637575326132736c6c7938646c646479757a717573677064736e7a61717933747332736c70746b6d386a737567766a333938766e336b7472643975757167336b3376737a33687a7a6361736c3765646e676868366c78707477746e6468377961616665796c6372353870686b66636635307a6b64666d6b36736133366e36646a687536636e68347278663530766b79396a767663663075707a346479713876367465687732673661616b737730747873336530646b3973763336613371797874356d68636373366768387265336736757633327a3370737879767133617930776e7332616b756666376a333833677130757036706d6e3467366c763937666a646d73397a336735777261383338786668657a776361363574796174333078637933326a76357279356e37613363306d3036713763616e71736e72366133673330716b387265616b717a716d64366b656338667663336d7a6a6e6d39653564376564686a6c36357a67636c743272756e6a34337a6d6d73327665636b7176366e3338716477303863736d7378746b643068796666366a6e3266377732376434726a7168326c7a637638386d30383763393839"], + ["d715406f2fdd2afa733f5f641c8c21862a1bafce2609d9eecfa158cfb5cd79f803ff3f19851764670df1660c9cb56918abc2a0691eb8b89e705eb8e65b0f4e0dcb", "068d407022db8e5dd0730882bd54851ca0797a00dff60e358d28a04df20793838ba1618a6871da256cdbf7d8ad2ce81d9ce01df64765bc7c14ac74e7ed60129090fcb061b3e672a742aa0f5db728265947355b6375f3d2226b25129dced2e0991d02f739d2d822df5d41edc122b2330916ba36ca09e80cf07f99be4a45fce8e7", "d0e5967a78a4ba305dc8c47ad6ea668d2e88e11575d2ec1f082c740b5fdc050ddb57fa080007c5d868dd8670904b76a82959e8482795ab3ba4cbdc7bb37b1822d2f3ccda83c005fe5c571dbec5074f159f345ad2f60edf8364e4ddf983f91235", 65533, "ad03bc0cda4a44946c00e1b1a1df0e5b87b5bece477a709649e950060591394812951e1fe3895b8cc3d14d2cf6556df6ed4b4ddd3d9a69f53357d7767f4f5ccbdbc596631277f8fecd08cb056b95e3025b9792fff7f244fc716269b926d62e95", "757669657731666b6e393734747468686363306b71796d726a7467637370646c7170613768346e356e7439386b76773761326a676a686174706a713478656a76673879646e747074706e327a6c64707471386371716e646a6c67336c6b786b66776a7635726e39767638636a3666783567307a387a66746e6470726b7073387937656d34737a79327679386a70336832653678386b64717170656e356865387272326466797072767763713563756e7639396c67306a6b397233796c777064617a723779757a357938756a773071366e333939756d643236366a37396a7371776d6c7967767533776e666b797a3335647964687861706c63327578713572613237396e77396e73776379326837676873673930376d70733279686578666876323576776c343461383833376a63777336766365776a7434347a357333336e71756c396d35737a6d6e6179346476763364777568377a796871687732376d753777376a736b30756e6b73787363617a3372616d7973386579667a7a6475636e6765657764357738707433687478666a66786b657a65757130326d6c6333646b636163716b736c716761356b33656c3064746775796771686a3976793572646b70746d7274633432776c3336667676346b6d6b66793735677377776b72796e3835656e6e32396d75396a6c35326e7730717a7967747a366879347a653535756e707071647133346371767237766374777132657a676c716432666b79676161657072746c66376b65707277763871307571383865657771746a756b6c6a3973713933323675717a76786b727837773772716e7a68633361777138367938796a3371386868633472356a6e3864637a7766613934763367307572716173706334727132636878767a3772776e6872683332386168326c6168707776656134747771"] +] From 6cccc4ad3c3d8952c00e9a25d4aa0e45c00a25f6 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 10 Dec 2021 12:29:33 -0700 Subject: [PATCH 7/9] Apply suggestions from code review Co-authored-by: Daira Hopwood --- src/gtest/test_keys.cpp | 81 +++++++++++----------------- src/rust/include/rust/unified_keys.h | 6 ++- src/rust/src/unified_keys_ffi.rs | 5 +- 3 files changed, 39 insertions(+), 53 deletions(-) diff --git a/src/gtest/test_keys.cpp b/src/gtest/test_keys.cpp index 7927973a8..d2144e18c 100644 --- a/src/gtest/test_keys.cpp +++ b/src/gtest/test_keys.cpp @@ -182,59 +182,42 @@ TEST(Keys, EncodeAndDecodeUnifiedFullViewingKeys) } if (test.size() == 1) continue; // comment - //try { - libzcash::UnifiedFullViewingKeyBuilder builder; - // ["p2pkh_key_bytes, sapling_key_bytes, orchard_key_bytes, unknown_key_bytes, unified_key"] - // These were added to the UA in preference order by the Python test vectors. - if (!test[0].isNull()) { - auto data = ParseHex(test[0].get_str()); - ASSERT_EQ(data.size(), 65); - CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION); - auto decoded = CChainablePubKey::Read(ss); - ASSERT_TRUE(builder.AddTransparentKey(decoded)); - } - if (!test[1].isNull()) { - auto data = ParseHex(test[1].get_str()); - ASSERT_EQ(data.size(), 128); - CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION); - auto key = libzcash::SaplingDiversifiableFullViewingKey::Read(ss); - ASSERT_TRUE(builder.AddSaplingKey(key)); - } + libzcash::UnifiedFullViewingKeyBuilder builder; + // ["p2pkh_key_bytes, sapling_key_bytes, orchard_key_bytes, unknown_key_bytes, unified_key"] + // These were added to the UA in preference order by the Python test vectors. + if (!test[0].isNull()) { + auto data = ParseHex(test[0].get_str()); + ASSERT_EQ(data.size(), 65); + CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION); + auto decoded = CChainablePubKey::Read(ss); + ASSERT_TRUE(builder.AddTransparentKey(decoded)); + } + if (!test[1].isNull()) { + auto data = ParseHex(test[1].get_str()); + ASSERT_EQ(data.size(), 128); + CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION); + 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; + // 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[3].get_str()); - // libzcash::UnknownReceiver r(0x03, data); - // ASSERT_TRUE(builder.AddUnknownItem(r)); - // } - // if (!test[4].isNull()) { - // auto data = ParseHex(test[4].get_str()); - // libzcash::UnknownReceiver r(test[3].get_int(), data); - // ASSERT_TRUE(builder.AddUnknownItem(r)); - // } + auto built = builder.build(); + ASSERT_TRUE(built.has_value()); - auto built = builder.build(); - ASSERT_TRUE(built.has_value()); + auto keystrBytes = ParseHex(test[5].get_str()); + std::string keystr(keystrBytes.begin(), keystrBytes.end()); - auto keystrBytes = ParseHex(test[5].get_str()); - std::string keystr(keystrBytes.begin(), keystrBytes.end()); + auto decoded = libzcash::UnifiedFullViewingKey::Decode(keystr, Params()); + ASSERT_TRUE(decoded.has_value()); - auto decoded = libzcash::UnifiedFullViewingKey::Decode(keystr, Params()); - ASSERT_TRUE(decoded.has_value()); - - EXPECT_EQ(decoded.value().GetTransparentKey(), built.value().GetTransparentKey()); - EXPECT_EQ(decoded.value().GetSaplingKey(), built.value().GetSaplingKey()); - //} catch (const std::exception& ex) { - // FAIL() << "Bad test, couldn't deserialize data: " << strTest << ": " << ex.what(); - //} catch (...) { - // FAIL() << "Bad test, couldn't deserialize data: " << strTest; - //} + EXPECT_EQ(decoded.value().GetTransparentKey(), built.value().GetTransparentKey()); + EXPECT_EQ(decoded.value().GetSaplingKey(), built.value().GetSaplingKey()); } } diff --git a/src/rust/include/rust/unified_keys.h b/src/rust/include/rust/unified_keys.h index 56360b55d..f9c837bbf 100644 --- a/src/rust/include/rust/unified_keys.h +++ b/src/rust/include/rust/unified_keys.h @@ -62,7 +62,8 @@ char* unified_full_viewing_key_serialize( * * Returns `true` if the UFVK contained a transparent component, `false` * otherwise. If this returns `true`, the transparent key will be copied to - * `tkeyout` as the byte representation of the `(ChainCode, CPubKey)` pair + * `tkeyout` as the byte representation of the `(ChainCode, CPubKey)` pair. + * If it returns `false` then `tkeyout` will be unchanged. */ bool unified_full_viewing_key_read_transparent( const UnifiedFullViewingKeyPtr* full_viewing_key, @@ -77,6 +78,7 @@ bool unified_full_viewing_key_read_transparent( * `false` otherwise. The bytes of the `(ak, nk, ovk, dk)` fields * of the viewing key, in the encoding given by `EncodeExtFVKParts` * defined in ZIP 32, will be copied to `skeyout` if `true` is returned. + * If `false` is returned then `skeyout` will be unchanged. */ bool unified_full_viewing_key_read_sapling( const UnifiedFullViewingKeyPtr* full_viewing_key, @@ -94,7 +96,7 @@ bool unified_full_viewing_key_read_sapling( * pair in the encoding given by `EncodeExtFVKParts` defined in * ZIP 32. * - * Returns a pointer to newly allocated UFVK if the operation succeeds, + * Returns a pointer to newly allocated UFVK if the operation succeeds, * or the null pointer otherwise. The pointer returned by this function * must be freed by the caller with `unified_full_viewing_key_free`. */ diff --git a/src/rust/src/unified_keys_ffi.rs b/src/rust/src/unified_keys_ffi.rs index 8037fc2ea..9857e6b3c 100644 --- a/src/rust/src/unified_keys_ffi.rs +++ b/src/rust/src/unified_keys_ffi.rs @@ -39,8 +39,9 @@ pub extern "C" fn unified_full_viewing_key_parse( Box::into_raw(Box::new(fvk)) } else { error!( - "Key was encoded for a different network than what was requested: {:?}", - parsed_network + "Key was encoded for a different network ({:?}) than what was requested ({:?})", + parsed_network, + network, ); std::ptr::null_mut() } From d1890ebd241ffadd808bacf521a4e55ccc2bb348 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 13 Dec 2021 21:00:05 -0700 Subject: [PATCH 8/9] Apply suggestions from code review Co-authored-by: str4d --- src/pubkey.h | 6 ++--- src/rust/include/librustzcash.h | 40 ++++++++++++++++++--------------- src/zcash/Address.hpp | 10 +++++++++ src/zcash/address/unified.h | 12 ++++++++++ 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/src/pubkey.h b/src/pubkey.h index 04739eaea..1d66e091a 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -227,13 +227,13 @@ public: inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(chaincode); if (ser_action.ForRead()) { - std::array pubkeyBytes; + std::array pubkeyBytes; READWRITE(pubkeyBytes); pubkey = CPubKey(pubkeyBytes.begin(), pubkeyBytes.end()); assert(pubkey.IsCompressed()); } else { - assert(pubkey.size() == 33); - std::array pubkeyBytes; + assert(pubkey.size() == CPubKey::COMPRESSED_PUBLIC_KEY_SIZE); + std::array pubkeyBytes; std::copy(pubkey.begin(), pubkey.end(), pubkeyBytes.begin()); READWRITE(pubkeyBytes); } diff --git a/src/rust/include/librustzcash.h b/src/rust/include/librustzcash.h index 7b538bcb3..c55073cff 100644 --- a/src/rust/include/librustzcash.h +++ b/src/rust/include/librustzcash.h @@ -307,16 +307,18 @@ extern "C" { ); /** - * Derive a PaymentAddress from an ExtendedFullViewingKey. Returns 'false' - * if no valid address can be derived for the specified diversifier index. + * Derive a PaymentAddress from a (SaplingFullViewingKey, DiversifierKey) + * pair. Returns 'false' if no valid address can be derived for the + * specified diversifier index. * * Arguments: - * - xfvk: [c_uchar; 96] the serialized form of a Sapling full viewing key + * - fvk: [c_uchar; 96] the serialized form of a Sapling full viewing key * - dk: [c_uchar; 32] the byte representation of a Sapling diversifier key - * - j: [c_uchar; 11] the 88-bit diversifier address at which to start searching, - * encoded in little-endian order - * - addr_ret: [c_uchar; 43] array to which the returned address will be written, - * if the specified diversifier index `j` produces a valid address. + * - j: [c_uchar; 11] the 88-bit diversifier address at which to start + * searching, encoded in little-endian order + * - addr_ret: [c_uchar; 43] array to which the returned address will be + * written, if the specified diversifier index `j` produces a valid + * address. */ bool librustzcash_zip32_sapling_address( const unsigned char *fvk, @@ -326,20 +328,22 @@ extern "C" { ); /** - * Derive a PaymentAddress from an ExtendedFullViewingKey by searching the - * space of valid diversifiers starting at diversifier index `j`. This will - * always return a valid address along with the diversifier index that produced - * the address unless no addresses can be derived at any diversifier index >= `j`, - * in which case this function will return `false`. + * Derive a PaymentAddress from a (SaplingFullViewingKey, DiversifierKey) + * pair by searching the space of valid diversifiers starting at + * diversifier index `j`. This will always return a valid address along + * with the diversifier index that produced the address unless no addresses + * can be derived at any diversifier index >= `j`, in which case this + * function will return `false`. * * Arguments: - * - xfvk: [c_uchar; 96] the serialized form of a Sapling full viewing key + * - fvk: [c_uchar; 96] the serialized form of a Sapling full viewing key * - dk: [c_uchar; 32] the byte representation of a Sapling diversifier key - * - j: [c_uchar; 11] the 88-bit diversifier address at which to start searching, - * encoded in little-endian order - * - j_ret: [c_uchar; 11] array that will store the diversifier index at which the - * returned address was found - * - addr_ret: [c_uchar; 43] array to which the returned address will be written + * - j: [c_uchar; 11] the 88-bit diversifier address at which to start + * searching, encoded in little-endian order + * - j_ret: [c_uchar; 11] array that will store the diversifier index at + * which the returned address was found + * - addr_ret: [c_uchar; 43] array to which the returned address will be + * written */ bool librustzcash_zip32_find_sapling_address( const unsigned char *fvk, diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index 8db8b03d2..b6a63c8db 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -151,6 +151,14 @@ public: const std::string& str, const KeyConstants& keyConstants); + /** + * This method should only be used for serialization of unified full + * viewing keys that have been generated internally from unified spending + * keys by Zcashd. It is not suitable for use in any case where the + * ZcashdUnifiedFullViewingKey value may have been produced by a + * potentially-lossy conversion from a UnifiedFullViewingKey value that + * originated outside of zcashd. + */ static UnifiedFullViewingKey FromZcashdUFVK(const ZcashdUnifiedFullViewingKey&); std::string Encode(const KeyConstants& keyConstants) const; @@ -159,6 +167,8 @@ public: std::optional GetTransparentKey() const; + UnifiedFullViewingKey(UnifiedFullViewingKey&& key) : inner(std::move(key.inner)) {} + UnifiedFullViewingKey(const UnifiedFullViewingKey& key) : inner(unified_full_viewing_key_clone(key.inner.get()), unified_full_viewing_key_free) {} diff --git a/src/zcash/address/unified.h b/src/zcash/address/unified.h index f65b4b13d..9742eb2fa 100644 --- a/src/zcash/address/unified.h +++ b/src/zcash/address/unified.h @@ -17,6 +17,12 @@ class ZcashdUnifiedFullViewingKey; class UnifiedAddress; class UnifiedFullViewingKey; +/** + * An internal-only type for unified full viewing keys that represents only the + * set of receiver types that are supported by zcashd. This type does not + * support round-trip serialization to and from the UnifiedFullViewingKey type, + * which should be used in most cases. + */ class ZcashdUnifiedFullViewingKey { private: std::optional transparentKey; @@ -26,6 +32,9 @@ private: friend class ZcashdUnifiedSpendingKey; public: + /** + * This constructor is lossy, and does not support round-trip transformations. + */ static ZcashdUnifiedFullViewingKey FromUnifiedFullViewingKey(const UnifiedFullViewingKey& ufvk); const std::optional& GetTransparentKey() const { @@ -41,6 +50,9 @@ public: std::pair FindAddress(diversifier_index_t j) const; }; +/** + * The type of unified spending keys supported by zcashd. + */ class ZcashdUnifiedSpendingKey { private: libzcash::AccountId accountId; From 1e18410b5549f58555a992275127ba432e11c97d Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 14 Dec 2021 13:02:27 -0700 Subject: [PATCH 9/9] Apply suggestions from code review Co-authored-by: str4d Co-authored-by: Daira Hopwood --- src/gtest/test_keys.cpp | 2 +- src/rust/include/librustzcash.h | 6 +++--- src/rust/include/rust/unified_keys.h | 15 +++++++-------- src/rust/src/unified_keys_ffi.rs | 22 +++++++--------------- 4 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/gtest/test_keys.cpp b/src/gtest/test_keys.cpp index d2144e18c..a9736930a 100644 --- a/src/gtest/test_keys.cpp +++ b/src/gtest/test_keys.cpp @@ -183,7 +183,7 @@ TEST(Keys, EncodeAndDecodeUnifiedFullViewingKeys) if (test.size() == 1) continue; // comment libzcash::UnifiedFullViewingKeyBuilder builder; - // ["p2pkh_key_bytes, sapling_key_bytes, orchard_key_bytes, unknown_key_bytes, unified_key"] + // ["t_key_bytes, sapling_fvk_bytes, orchard_fvk_bytes, unknown_fvk_typecode, unknown_fvk_bytes, unified_fvk"] // These were added to the UA in preference order by the Python test vectors. if (!test[0].isNull()) { auto data = ParseHex(test[0].get_str()); diff --git a/src/rust/include/librustzcash.h b/src/rust/include/librustzcash.h index c55073cff..ae5dc4b84 100644 --- a/src/rust/include/librustzcash.h +++ b/src/rust/include/librustzcash.h @@ -314,8 +314,8 @@ extern "C" { * Arguments: * - fvk: [c_uchar; 96] the serialized form of a Sapling full viewing key * - dk: [c_uchar; 32] the byte representation of a Sapling diversifier key - * - j: [c_uchar; 11] the 88-bit diversifier address at which to start - * searching, encoded in little-endian order + * - j: [c_uchar; 11] the 88-bit diversifier index, encoded in little-endian + * order * - addr_ret: [c_uchar; 43] array to which the returned address will be * written, if the specified diversifier index `j` produces a valid * address. @@ -338,7 +338,7 @@ extern "C" { * Arguments: * - fvk: [c_uchar; 96] the serialized form of a Sapling full viewing key * - dk: [c_uchar; 32] the byte representation of a Sapling diversifier key - * - j: [c_uchar; 11] the 88-bit diversifier address at which to start + * - j: [c_uchar; 11] the 88-bit diversifier index at which to start * searching, encoded in little-endian order * - j_ret: [c_uchar; 11] array that will store the diversifier index at * which the returned address was found diff --git a/src/rust/include/rust/unified_keys.h b/src/rust/include/rust/unified_keys.h index f9c837bbf..f3929c573 100644 --- a/src/rust/include/rust/unified_keys.h +++ b/src/rust/include/rust/unified_keys.h @@ -56,7 +56,7 @@ char* unified_full_viewing_key_serialize( const UnifiedFullViewingKeyPtr* full_viewing_key); /** - * Reads the transparent component of a unified viewing key. + * Reads the transparent component of a unified full viewing key. * * `tkeyout` must be of length 65. * @@ -70,7 +70,7 @@ bool unified_full_viewing_key_read_transparent( unsigned char* tkeyout); /** - * Reads the Sapling component of a unified viewing key. + * Reads the Sapling component of a unified full viewing key. * * `skeyout` must be of length 128. * @@ -86,15 +86,14 @@ bool unified_full_viewing_key_read_sapling( /** * Constructs a unified full viewing key from the binary encodings - * of its constituent parts + * of its constituent parts. * - * `t_key` must be of length 65 and must be the concatenated + * If `t_key` is not `null`, it must be of length 65 and must be the concatenated * bytes of the serialized `(ChainCode, CPubKey)` pair. * - * `sapling_key` must be of length 128 and must be the concatenated - * bytes of the serialized `(SaplingFullViewingKey, DiversifierKey)` - * pair in the encoding given by `EncodeExtFVKParts` defined in - * ZIP 32. + * If `sapling_key` is not `null`, it must be of length 128 and must be the concatenated + * bytes of the `(ak, nk, ovk, dk)` fields in the encoding given by + * `EncodeExtFVKParts` defined in ZIP 32. * * Returns a pointer to newly allocated UFVK if the operation succeeds, * or the null pointer otherwise. The pointer returned by this function diff --git a/src/rust/src/unified_keys_ffi.rs b/src/rust/src/unified_keys_ffi.rs index 9857e6b3c..a2adabd82 100644 --- a/src/rust/src/unified_keys_ffi.rs +++ b/src/rust/src/unified_keys_ffi.rs @@ -34,17 +34,13 @@ pub extern "C" fn unified_full_viewing_key_parse( match unsafe { CStr::from_ptr(encoded) }.to_str() { Ok(encoded) => match Ufvk::decode(encoded) { - Ok((parsed_network, fvk)) => { - if parsed_network == network { - Box::into_raw(Box::new(fvk)) - } else { - error!( - "Key was encoded for a different network ({:?}) than what was requested ({:?})", - parsed_network, - network, - ); - std::ptr::null_mut() - } + Ok((parsed_network, fvk)) if parsed_network == network => Box::into_raw(Box::new(fvk)), + Ok((parsed_network, _)) => { + error!( + "Key was encoded for a different network ({:?}) than what was requested ({:?})", + parsed_network, network, + ); + std::ptr::null_mut() } Err(e) => { error!("Failure decoding unified full viewing key: {}", e); @@ -125,10 +121,6 @@ pub extern "C" fn unified_full_viewing_key_from_components( match Ufvk::try_from_items(items) { Ok(ufvk) => Box::into_raw(Box::new(ufvk)), Err(e) => { - println!( - "An error occurred constructing the unified full viewing key: {:?}", - e - ); error!( "An error occurred constructing the unified full viewing key: {:?}", e