diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e2971a..04d00c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added +- `zcashlc_derive_address_ufvk` +- `zcashlc_derive_address_uivk` + ## 0.11.0 - 2024-11-15 ### Added diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 80eb100..03562dc 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1232,7 +1232,7 @@ dependencies = [ [[package]] name = "equihash" version = "0.2.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cc2dfbf7bf1f40a4d9d1aeb731537568bab61164#cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" +source = "git+https://github.com/zcash/librustzcash.git?rev=67fe5f8c8851b17c4129c9f3a590dde00d59f667#67fe5f8c8851b17c4129c9f3a590dde00d59f667" dependencies = [ "blake2b_simd", "byteorder", @@ -1268,7 +1268,7 @@ dependencies = [ [[package]] name = "f4jumble" version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cc2dfbf7bf1f40a4d9d1aeb731537568bab61164#cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" +source = "git+https://github.com/zcash/librustzcash.git?rev=67fe5f8c8851b17c4129c9f3a590dde00d59f667#67fe5f8c8851b17c4129c9f3a590dde00d59f667" dependencies = [ "blake2b_simd", ] @@ -1542,6 +1542,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getset" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f636605b743120a8d32ed92fc27b6cde1a769f8f936c065151eb66f88ded513c" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "gimli" version = "0.31.1" @@ -2062,6 +2074,7 @@ dependencies = [ "zcash_address", "zcash_client_backend", "zcash_client_sqlite", + "zcash_note_encryption", "zcash_primitives", "zcash_proofs", "zcash_protocol", @@ -2392,14 +2405,14 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "orchard" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f18e997fa121de5c73e95cdc7e8512ae43b7de38904aeea5e5713cc48f3c0ba" +source = "git+https://github.com/zcash/orchard.git?rev=bcd08e1d23e70c42a338f3e3f79d6f4c0c219805#bcd08e1d23e70c42a338f3e3f79d6f4c0c219805" dependencies = [ "aes", "bitvec", "blake2b_simd", "ff", "fpe", + "getset", "group", "halo2_gadgets", "halo2_proofs", @@ -2738,6 +2751,28 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "proc-macro2" version = "1.0.89" @@ -3210,8 +3245,7 @@ dependencies = [ [[package]] name = "sapling-crypto" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfff8cfce16aeb38da50b8e2ed33c9018f30552beff2210c266662a021b17f38" +source = "git+https://github.com/zcash/sapling-crypto.git?rev=29cff9683cdf2f0c522ff3224081dfb4fbc80248#29cff9683cdf2f0c522ff3224081dfb4fbc80248" dependencies = [ "aes", "bellman", @@ -3223,6 +3257,7 @@ dependencies = [ "document-features", "ff", "fpe", + "getset", "group", "hex", "incrementalmerkletree", @@ -5283,7 +5318,7 @@ dependencies = [ [[package]] name = "zcash_address" version = "0.6.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cc2dfbf7bf1f40a4d9d1aeb731537568bab61164#cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" +source = "git+https://github.com/zcash/librustzcash.git?rev=67fe5f8c8851b17c4129c9f3a590dde00d59f667#67fe5f8c8851b17c4129c9f3a590dde00d59f667" dependencies = [ "bech32", "bs58", @@ -5295,7 +5330,7 @@ dependencies = [ [[package]] name = "zcash_client_backend" version = "0.15.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cc2dfbf7bf1f40a4d9d1aeb731537568bab61164#cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" +source = "git+https://github.com/zcash/librustzcash.git?rev=67fe5f8c8851b17c4129c9f3a590dde00d59f667#67fe5f8c8851b17c4129c9f3a590dde00d59f667" dependencies = [ "arti-client", "base64", @@ -5354,7 +5389,7 @@ dependencies = [ [[package]] name = "zcash_client_sqlite" version = "0.13.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cc2dfbf7bf1f40a4d9d1aeb731537568bab61164#cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" +source = "git+https://github.com/zcash/librustzcash.git?rev=67fe5f8c8851b17c4129c9f3a590dde00d59f667#67fe5f8c8851b17c4129c9f3a590dde00d59f667" dependencies = [ "bip32", "bs58", @@ -5391,7 +5426,7 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.2.1" -source = "git+https://github.com/zcash/librustzcash.git?rev=cc2dfbf7bf1f40a4d9d1aeb731537568bab61164#cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" +source = "git+https://github.com/zcash/librustzcash.git?rev=67fe5f8c8851b17c4129c9f3a590dde00d59f667#67fe5f8c8851b17c4129c9f3a590dde00d59f667" dependencies = [ "byteorder", "nonempty", @@ -5400,7 +5435,7 @@ dependencies = [ [[package]] name = "zcash_keys" version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cc2dfbf7bf1f40a4d9d1aeb731537568bab61164#cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" +source = "git+https://github.com/zcash/librustzcash.git?rev=67fe5f8c8851b17c4129c9f3a590dde00d59f667#67fe5f8c8851b17c4129c9f3a590dde00d59f667" dependencies = [ "bech32", "bip32", @@ -5427,9 +5462,9 @@ dependencies = [ [[package]] name = "zcash_note_encryption" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b4580cd6cee12e44421dac43169be8d23791650816bdb34e6ddfa70ac89c1c5" +checksum = "77efec759c3798b6e4d829fcc762070d9b229b0f13338c40bf993b7b609c2272" dependencies = [ "chacha20", "chacha20poly1305", @@ -5441,7 +5476,7 @@ dependencies = [ [[package]] name = "zcash_primitives" version = "0.20.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cc2dfbf7bf1f40a4d9d1aeb731537568bab61164#cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" +source = "git+https://github.com/zcash/librustzcash.git?rev=67fe5f8c8851b17c4129c9f3a590dde00d59f667#67fe5f8c8851b17c4129c9f3a590dde00d59f667" dependencies = [ "aes", "bip32", @@ -5452,6 +5487,7 @@ dependencies = [ "equihash", "ff", "fpe", + "getset", "group", "hex", "incrementalmerkletree", @@ -5479,7 +5515,7 @@ dependencies = [ [[package]] name = "zcash_proofs" version = "0.20.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cc2dfbf7bf1f40a4d9d1aeb731537568bab61164#cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" +source = "git+https://github.com/zcash/librustzcash.git?rev=67fe5f8c8851b17c4129c9f3a590dde00d59f667#67fe5f8c8851b17c4129c9f3a590dde00d59f667" dependencies = [ "bellman", "blake2b_simd", @@ -5501,7 +5537,7 @@ dependencies = [ [[package]] name = "zcash_protocol" version = "0.4.1" -source = "git+https://github.com/zcash/librustzcash.git?rev=cc2dfbf7bf1f40a4d9d1aeb731537568bab61164#cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" +source = "git+https://github.com/zcash/librustzcash.git?rev=67fe5f8c8851b17c4129c9f3a590dde00d59f667#67fe5f8c8851b17c4129c9f3a590dde00d59f667" dependencies = [ "document-features", "memuse", @@ -5572,7 +5608,7 @@ dependencies = [ [[package]] name = "zip321" version = "0.2.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cc2dfbf7bf1f40a4d9d1aeb731537568bab61164#cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" +source = "git+https://github.com/zcash/librustzcash.git?rev=67fe5f8c8851b17c4129c9f3a590dde00d59f667#67fe5f8c8851b17c4129c9f3a590dde00d59f667" dependencies = [ "base64", "nom", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index fb5db48..ede694e 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -19,6 +19,7 @@ sapling = { package = "sapling-crypto", version = "0.3", default-features = fals zcash_address = { version = "0.6" } zcash_client_backend = { version = "0.15", features = ["orchard", "tor", "transparent-inputs", "unstable"] } zcash_client_sqlite = { version = "0.13", features = ["orchard", "transparent-inputs", "unstable"] } +zcash_note_encryption = "0.4.1" zcash_primitives = "0.20" zcash_proofs = "0.20" zcash_protocol = "0.4" @@ -65,10 +66,12 @@ crate-type = ["staticlib"] lto = true [patch.crates-io] -zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" } -zcash_client_backend = { git = "https://github.com/zcash/librustzcash.git", rev = "cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" } -zcash_client_sqlite = { git = "https://github.com/zcash/librustzcash.git", rev = "cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" } -zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" } -zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" } -zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" } -zcash_protocol = { git = "https://github.com/zcash/librustzcash.git", rev = "cc2dfbf7bf1f40a4d9d1aeb731537568bab61164" } +zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "67fe5f8c8851b17c4129c9f3a590dde00d59f667" } +zcash_client_backend = { git = "https://github.com/zcash/librustzcash.git", rev = "67fe5f8c8851b17c4129c9f3a590dde00d59f667" } +zcash_client_sqlite = { git = "https://github.com/zcash/librustzcash.git", rev = "67fe5f8c8851b17c4129c9f3a590dde00d59f667" } +zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "67fe5f8c8851b17c4129c9f3a590dde00d59f667" } +zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "67fe5f8c8851b17c4129c9f3a590dde00d59f667" } +zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "67fe5f8c8851b17c4129c9f3a590dde00d59f667" } +zcash_protocol = { git = "https://github.com/zcash/librustzcash.git", rev = "67fe5f8c8851b17c4129c9f3a590dde00d59f667" } +orchard = { git = "https://github.com/zcash/orchard.git", rev = "bcd08e1d23e70c42a338f3e3f79d6f4c0c219805" } +sapling-crypto = { git = "https://github.com/zcash/sapling-crypto.git", rev = "29cff9683cdf2f0c522ff3224081dfb4fbc80248" } diff --git a/rust/src/derivation.rs b/rust/src/derivation.rs index fff2d16..4407e57 100644 --- a/rust/src/derivation.rs +++ b/rust/src/derivation.rs @@ -7,8 +7,9 @@ use std::ffi::{CStr, CString}; use std::mem::ManuallyDrop; use std::os::raw::c_char; use std::slice; -use zcash_primitives::consensus::NetworkConstants; -use zip32::{arbitrary, ChildIndex}; +use zcash_client_backend::keys::UnifiedIncomingViewingKey; +use zcash_primitives::consensus::{Network, NetworkConstants}; +use zip32::{arbitrary, ChildIndex, DiversifierIndex}; use zcash_address::{ unified::{self, Container, Encoding}, @@ -22,7 +23,8 @@ use zcash_client_backend::{ use zcash_primitives::legacy::TransparentAddress; use crate::{ - decode_usk, free_ptr_from_vec, parse_network, unwrap_exc_or, unwrap_exc_or_null, FfiBoxedSlice, + decode_usk, free_ptr_from_vec, parse_network, unwrap_exc_or, unwrap_exc_or_null, + zcashlc_string_free, FfiBoxedSlice, }; enum AddressType { @@ -313,13 +315,13 @@ pub unsafe extern "C" fn zcashlc_is_valid_unified_full_viewing_key( pub unsafe extern "C" fn zcashlc_derive_spending_key( seed: *const u8, seed_len: usize, - account: i32, + hd_account_index: i32, network_id: u32, ) -> *mut FfiBoxedSlice { let res = catch_panic(|| { let network = parse_network(network_id)?; let seed = unsafe { slice::from_raw_parts(seed, seed_len) }; - let account = zip32_account_index(account)?; + let account = zip32_account_index(hd_account_index)?; UnifiedSpendingKey::from_seed(&network, seed, account) .map_err(|e| anyhow!("error generating unified spending key from seed: {:?}", e)) @@ -374,6 +376,130 @@ impl zcash_address::TryFromRawAddress for UnifiedAddressParser { } } +/// A struct that contains a Zcash unified address, along with the diversifier index used to +/// generate that address. +#[repr(C)] +pub struct FfiAddress { + address: *mut c_char, + diversifier_index_bytes: [u8; 11], +} + +impl FfiAddress { + fn new( + network: &Network, + address: UnifiedAddress, + diversifier_index: DiversifierIndex, + ) -> Self { + let address_str = address.encode(network); + Self { + address: CString::new(address_str).unwrap().into_raw(), + diversifier_index_bytes: *diversifier_index.as_bytes(), + } + } +} + +/// Frees a FfiAddress value +/// +/// # Safety +/// +/// - `ptr` must be non-null and must point to a struct having the layout of [`FfiAddress`]. +#[no_mangle] +pub unsafe extern "C" fn zcashlc_free_ffi_address(ptr: *mut FfiAddress) { + if !ptr.is_null() { + let ffi_address: Box = unsafe { Box::from_raw(ptr) }; + if !(ffi_address.address.is_null()) { + unsafe { zcashlc_string_free(ffi_address.address) } + } + drop(ffi_address); + } +} + +/// Derives a unified address address for the provided UFVK, along with the diversifier at which it +/// was derived; this may not be equal to the provided diversifier index if no valid Sapling +/// address could be derived at that index. If the `diversifier_index_bytes` parameter is null, the +/// default address for the UFVK is returned. +/// +/// # Safety +/// +/// - `ufvk` must be non-null and must point to a null-terminated UTF-8 string. +/// - `diversifier_index_bytes must either be null or be valid for reads for 11 bytes and have an +/// alignment of `1`. +/// - Call [`zcashlc_free_ffi_address`] to free the memory associated with the returned pointer +/// when done using it. +#[no_mangle] +pub unsafe extern "C" fn zcashlc_derive_address_from_ufvk( + network_id: u32, + ufvk: *const c_char, + diversifier_index_bytes: *const u8, +) -> *mut FfiAddress { + let res = catch_panic(|| { + let network = parse_network(network_id)?; + let ufvk_str = unsafe { CStr::from_ptr(ufvk).to_str()? }; + let ufvk = UnifiedFullViewingKey::decode(&network, ufvk_str).map_err(|e| { + anyhow!( + "Value \"{}\" did not decode as a valid UFVK: {}", + ufvk_str, + e + ) + })?; + + let (ua, di) = if diversifier_index_bytes.is_null() { + ufvk.default_address(None) + } else { + let j = DiversifierIndex::from(<[u8; 11]>::try_from(unsafe { + slice::from_raw_parts(diversifier_index_bytes, 11) + })?); + ufvk.find_address(j, None) + }?; + + Ok(Box::into_raw(Box::new(FfiAddress::new(&network, ua, di)))) + }); + unwrap_exc_or_null(res) +} + +/// Derives a unified address address for the provided UIVK, along with the diversifier at which it +/// was derived; this may not be equal to the provided diversifier index if no valid Sapling +/// address could be derived at that index. If the `diversifier_index_bytes` parameter is null, the +/// default address for the UIVK is returned. +/// +/// # Safety +/// +/// - `uivk` must be non-null and must point to a null-terminated UTF-8 string. +/// - `diversifier_index_bytes must either be null or be valid for reads for 11 bytes and have an +/// alignment of `1`. +/// - Call [`zcashlc_string_free`] to free the memory associated with the returned pointer +/// when done using it. +#[no_mangle] +pub unsafe extern "C" fn zcashlc_derive_address_from_uivk( + network_id: u32, + uivk: *const c_char, + diversifier_index_bytes: *const u8, +) -> *mut FfiAddress { + let res = catch_panic(|| { + let network = parse_network(network_id)?; + let uivk_str = unsafe { CStr::from_ptr(uivk).to_str()? }; + let uivk = UnifiedIncomingViewingKey::decode(&network, uivk_str).map_err(|e| { + anyhow!( + "Value \"{}\" did not decode as a valid UIVK: {}", + uivk_str, + e + ) + })?; + + let (ua, di) = if diversifier_index_bytes.is_null() { + uivk.default_address(None) + } else { + let j = DiversifierIndex::from(<[u8; 11]>::try_from(unsafe { + slice::from_raw_parts(diversifier_index_bytes, 11) + })?); + uivk.find_address(j, None) + }?; + + Ok(Box::into_raw(Box::new(FfiAddress::new(&network, ua, di)))) + }); + unwrap_exc_or_null(res) +} + /// Returns the transparent receiver within the given Unified Address, if any. /// /// # Safety diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 153e740..1ea6b34 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -40,7 +40,7 @@ use zcash_client_backend::{ }, encoding::AddressCodec, fees::DustOutputPolicy, - keys::{DecodingError, Era, UnifiedAddressRequest, UnifiedSpendingKey}, + keys::{DecodingError, Era, UnifiedSpendingKey}, proto::{proposal::Proposal, service::TreeState}, tor::http::cryptex, wallet::{NoteId, OvkPolicy, WalletTransparentOutput}, @@ -953,8 +953,7 @@ pub unsafe extern "C" fn zcashlc_get_next_available_address( let mut db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; let account_uuid = account_uuid_from_bytes(account_uuid_bytes)?; - let request = UnifiedAddressRequest::new(true, true, true).expect("have shielded receiver"); - match db_data.get_next_available_address(account_uuid, request) { + match db_data.get_next_available_address(account_uuid, None) { Ok(Some(ua)) => { let address_str = ua.encode(&network); Ok(CString::new(address_str).unwrap().into_raw()) @@ -1751,7 +1750,7 @@ impl FfiAccountBalance { account_uuid: account_uuid.expose_uuid().into_bytes(), sapling_balance: FfiBalance::new(balance.sapling_balance()), orchard_balance: FfiBalance::new(balance.orchard_balance()), - unshielded: ZatBalance::from(balance.unshielded()).into(), + unshielded: ZatBalance::from(balance.unshielded_balance().total()).into(), } } }