From f75c10a884e5b5feb5971a0efdd0ffb339e15edf Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 23 Oct 2024 09:50:45 +1300 Subject: [PATCH 1/3] rust: Reorder FFI methods to group by Kotlin class --- backend-lib/src/main/rust/lib.rs | 340 ++++++++++++++++--------------- 1 file changed, 172 insertions(+), 168 deletions(-) diff --git a/backend-lib/src/main/rust/lib.rs b/backend-lib/src/main/rust/lib.rs index 0482aa84..aca35ccf 100644 --- a/backend-lib/src/main/rust/lib.rs +++ b/backend-lib/src/main/rust/lib.rs @@ -404,174 +404,6 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isSeedRel unwrap_exc_or(&mut env, res, JNI_FALSE) } -/// Derives and returns a unified spending key from the given seed for the given account ID. -/// -/// Returns the newly created [ZIP 316] account identifier, along with the binary encoding -/// of the [`UnifiedSpendingKey`] for the newly created account. The caller should store -/// the returned spending key in a secure fashion. -#[unsafe(no_mangle)] -pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveSpendingKey< - 'local, ->( - mut env: JNIEnv<'local>, - _: JClass<'local>, - seed: JByteArray<'local>, - account: jint, - network_id: jint, -) -> jobject { - let res = catch_unwind(&mut env, |env| { - let _span = tracing::info_span!("RustDerivationTool.deriveSpendingKey").entered(); - let network = parse_network(network_id as u32)?; - let seed = SecretVec::new(env.convert_byte_array(seed).unwrap()); - let account = account_id_from_jint(account)?; - - let usk = UnifiedSpendingKey::from_seed(&network, seed.expose_secret(), account) - .map_err(|e| anyhow!("error generating unified spending key from seed: {:?}", e))?; - - Ok(encode_usk(env, account, usk)?.into_raw()) - }); - unwrap_exc_or(&mut env, res, ptr::null_mut()) -} - -#[unsafe(no_mangle)] -pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedFullViewingKeysFromSeed< - 'local, ->( - mut env: JNIEnv<'local>, - _: JClass<'local>, - seed: JByteArray<'local>, - accounts: jint, - network_id: jint, -) -> jobjectArray { - let res = catch_unwind(&mut env, |env| { - let _span = tracing::info_span!("RustDerivationTool.deriveUnifiedFullViewingKeysFromSeed") - .entered(); - let network = parse_network(network_id as u32)?; - let seed = env.convert_byte_array(seed).unwrap(); - let accounts = if accounts > 0 { - accounts as u32 - } else { - return Err(anyhow!("accounts argument must be greater than zero")); - }; - - let ufvks: Vec<_> = (0..accounts) - .map(|account| { - let account_id = zip32::AccountId::try_from(account) - .map_err(|_| anyhow!("Invalid account ID"))?; - UnifiedSpendingKey::from_seed(&network, &seed, account_id) - .map_err(|e| { - anyhow!("error generating unified spending key from seed: {:?}", e) - }) - .map(|usk| usk.to_unified_full_viewing_key().encode(&network)) - }) - .collect::>()?; - - Ok(utils::rust_vec_to_java( - env, - ufvks, - "java/lang/String", - |env, ufvk| env.new_string(ufvk), - |env| env.new_string(""), - )? - .into_raw()) - }); - unwrap_exc_or(&mut env, res, ptr::null_mut()) -} - -#[unsafe(no_mangle)] -pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedAddressFromSeed< - 'local, ->( - mut env: JNIEnv<'local>, - _: JClass<'local>, - seed: JByteArray<'local>, - account_index: jint, - network_id: jint, -) -> jstring { - let res = panic::catch_unwind(|| { - let _span = - tracing::info_span!("RustDerivationTool.deriveUnifiedAddressFromSeed").entered(); - let network = parse_network(network_id as u32)?; - let seed = env.convert_byte_array(seed).unwrap(); - let account_id = account_id_from_jint(account_index)?; - - let ufvk = UnifiedSpendingKey::from_seed(&network, &seed, account_id) - .map_err(|e| anyhow!("error generating unified spending key from seed: {:?}", e)) - .map(|usk| usk.to_unified_full_viewing_key())?; - - let (ua, _) = ufvk - .find_address(DiversifierIndex::new(), DEFAULT_ADDRESS_REQUEST) - .expect("At least one Unified Address should be derivable"); - let address_str = ua.encode(&network); - let output = env - .new_string(address_str) - .expect("Couldn't create Java string!"); - Ok(output.into_raw()) - }); - unwrap_exc_or(&mut env, res, ptr::null_mut()) -} - -#[unsafe(no_mangle)] -pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedAddressFromViewingKey< - 'local, ->( - mut env: JNIEnv<'local>, - _: JClass<'local>, - ufvk_string: JString<'local>, - network_id: jint, -) -> jstring { - let res = catch_unwind(&mut env, |env| { - let _span = - tracing::info_span!("RustDerivationTool.deriveUnifiedAddressFromViewingKey").entered(); - let network = parse_network(network_id as u32)?; - let ufvk_string = utils::java_string_to_rust(env, &ufvk_string); - let ufvk = match UnifiedFullViewingKey::decode(&network, &ufvk_string) { - Ok(ufvk) => ufvk, - Err(e) => { - return Err(anyhow!( - "Error while deriving viewing key from string input: {}", - e, - )); - } - }; - - // Derive the default Unified Address (containing the default Sapling payment - // address that older SDKs used). - let (ua, _) = ufvk.default_address(DEFAULT_ADDRESS_REQUEST)?; - let address_str = ua.encode(&network); - let output = env - .new_string(address_str) - .expect("Couldn't create Java string!"); - Ok(output.into_raw()) - }); - unwrap_exc_or(&mut env, res, ptr::null_mut()) -} - -#[unsafe(no_mangle)] -pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedFullViewingKey< - 'local, ->( - mut env: JNIEnv<'local>, - _: JClass<'local>, - usk: JByteArray<'local>, - network_id: jint, -) -> jstring { - let res = panic::catch_unwind(|| { - let _span = tracing::info_span!("RustDerivationTool.deriveUnifiedFullViewingKey").entered(); - let usk = decode_usk(&env, usk)?; - let network = parse_network(network_id as u32)?; - - let ufvk = usk.to_unified_full_viewing_key(); - - let output = env - .new_string(ufvk.encode(&network)) - .expect("Couldn't create Java string!"); - - Ok(output.into_raw()) - }); - unwrap_exc_or(&mut env, res, ptr::null_mut()) -} - #[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getCurrentAddress<'local>( mut env: JNIEnv<'local>, @@ -2003,6 +1835,178 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_branchIdF unwrap_exc_or(&mut env, res, -1) } +// +// Derivation tool +// + +/// Derives and returns a unified spending key from the given seed for the given account ID. +/// +/// Returns the newly created [ZIP 316] account identifier, along with the binary encoding +/// of the [`UnifiedSpendingKey`] for the newly created account. The caller should store +/// the returned spending key in a secure fashion. +#[unsafe(no_mangle)] +pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveSpendingKey< + 'local, +>( + mut env: JNIEnv<'local>, + _: JClass<'local>, + seed: JByteArray<'local>, + account: jint, + network_id: jint, +) -> jobject { + let res = catch_unwind(&mut env, |env| { + let _span = tracing::info_span!("RustDerivationTool.deriveSpendingKey").entered(); + let network = parse_network(network_id as u32)?; + let seed = SecretVec::new(env.convert_byte_array(seed).unwrap()); + let account = account_id_from_jint(account)?; + + let usk = UnifiedSpendingKey::from_seed(&network, seed.expose_secret(), account) + .map_err(|e| anyhow!("error generating unified spending key from seed: {:?}", e))?; + + Ok(encode_usk(env, account, usk)?.into_raw()) + }); + unwrap_exc_or(&mut env, res, ptr::null_mut()) +} + +#[unsafe(no_mangle)] +pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedFullViewingKeysFromSeed< + 'local, +>( + mut env: JNIEnv<'local>, + _: JClass<'local>, + seed: JByteArray<'local>, + accounts: jint, + network_id: jint, +) -> jobjectArray { + let res = catch_unwind(&mut env, |env| { + let _span = tracing::info_span!("RustDerivationTool.deriveUnifiedFullViewingKeysFromSeed") + .entered(); + let network = parse_network(network_id as u32)?; + let seed = env.convert_byte_array(seed).unwrap(); + let accounts = if accounts > 0 { + accounts as u32 + } else { + return Err(anyhow!("accounts argument must be greater than zero")); + }; + + let ufvks: Vec<_> = (0..accounts) + .map(|account| { + let account_id = zip32::AccountId::try_from(account) + .map_err(|_| anyhow!("Invalid account ID"))?; + UnifiedSpendingKey::from_seed(&network, &seed, account_id) + .map_err(|e| { + anyhow!("error generating unified spending key from seed: {:?}", e) + }) + .map(|usk| usk.to_unified_full_viewing_key().encode(&network)) + }) + .collect::>()?; + + Ok(utils::rust_vec_to_java( + env, + ufvks, + "java/lang/String", + |env, ufvk| env.new_string(ufvk), + |env| env.new_string(""), + )? + .into_raw()) + }); + unwrap_exc_or(&mut env, res, ptr::null_mut()) +} + +#[unsafe(no_mangle)] +pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedAddressFromSeed< + 'local, +>( + mut env: JNIEnv<'local>, + _: JClass<'local>, + seed: JByteArray<'local>, + account_index: jint, + network_id: jint, +) -> jstring { + let res = panic::catch_unwind(|| { + let _span = + tracing::info_span!("RustDerivationTool.deriveUnifiedAddressFromSeed").entered(); + let network = parse_network(network_id as u32)?; + let seed = env.convert_byte_array(seed).unwrap(); + let account_id = account_id_from_jint(account_index)?; + + let ufvk = UnifiedSpendingKey::from_seed(&network, &seed, account_id) + .map_err(|e| anyhow!("error generating unified spending key from seed: {:?}", e)) + .map(|usk| usk.to_unified_full_viewing_key())?; + + let (ua, _) = ufvk + .find_address(DiversifierIndex::new(), DEFAULT_ADDRESS_REQUEST) + .expect("At least one Unified Address should be derivable"); + let address_str = ua.encode(&network); + let output = env + .new_string(address_str) + .expect("Couldn't create Java string!"); + Ok(output.into_raw()) + }); + unwrap_exc_or(&mut env, res, ptr::null_mut()) +} + +#[unsafe(no_mangle)] +pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedAddressFromViewingKey< + 'local, +>( + mut env: JNIEnv<'local>, + _: JClass<'local>, + ufvk_string: JString<'local>, + network_id: jint, +) -> jstring { + let res = catch_unwind(&mut env, |env| { + let _span = + tracing::info_span!("RustDerivationTool.deriveUnifiedAddressFromViewingKey").entered(); + let network = parse_network(network_id as u32)?; + let ufvk_string = utils::java_string_to_rust(env, &ufvk_string); + let ufvk = match UnifiedFullViewingKey::decode(&network, &ufvk_string) { + Ok(ufvk) => ufvk, + Err(e) => { + return Err(anyhow!( + "Error while deriving viewing key from string input: {}", + e, + )); + } + }; + + // Derive the default Unified Address (containing the default Sapling payment + // address that older SDKs used). + let (ua, _) = ufvk.default_address(DEFAULT_ADDRESS_REQUEST)?; + let address_str = ua.encode(&network); + let output = env + .new_string(address_str) + .expect("Couldn't create Java string!"); + Ok(output.into_raw()) + }); + unwrap_exc_or(&mut env, res, ptr::null_mut()) +} + +#[unsafe(no_mangle)] +pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedFullViewingKey< + 'local, +>( + mut env: JNIEnv<'local>, + _: JClass<'local>, + usk: JByteArray<'local>, + network_id: jint, +) -> jstring { + let res = panic::catch_unwind(|| { + let _span = tracing::info_span!("RustDerivationTool.deriveUnifiedFullViewingKey").entered(); + let usk = decode_usk(&env, usk)?; + let network = parse_network(network_id as u32)?; + + let ufvk = usk.to_unified_full_viewing_key(); + + let output = env + .new_string(ufvk.encode(&network)) + .expect("Couldn't create Java string!"); + + Ok(output.into_raw()) + }); + unwrap_exc_or(&mut env, res, ptr::null_mut()) +} + // // Tor support // From fdd3fb507ac1bbfb072d90f37f3b76f63a451134 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 23 Oct 2024 12:52:03 +1300 Subject: [PATCH 2/3] Add ZIP 32 Arbitrary Key derivation to `DerivationTool` --- CHANGELOG.md | 4 ++ backend-lib/Cargo.lock | 10 ++-- backend-lib/Cargo.toml | 1 + .../z/ecc/android/sdk/internal/Derivation.kt | 31 ++++++++++ .../sdk/internal/jni/RustDerivationTool.kt | 26 ++++++++ backend-lib/src/main/rust/lib.rs | 59 +++++++++++++++++++ .../android/sdk/internal/DerivationToolExt.kt | 12 ++++ .../internal/TypesafeDerivationToolImpl.kt | 12 ++++ .../z/ecc/android/sdk/tool/DerivationTool.kt | 35 +++++++++++ 9 files changed, 186 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78efc4e4..10850ced 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 +- `DerivationTool.deriveArbitraryWalletKey` +- `DerivationTool.deriveArbitraryAccountKey` + ## [2.2.5] - 2024-10-22 ### Added diff --git a/backend-lib/Cargo.lock b/backend-lib/Cargo.lock index 3cd7b711..c708ca4e 100644 --- a/backend-lib/Cargo.lock +++ b/backend-lib/Cargo.lock @@ -5019,6 +5019,7 @@ dependencies = [ "zcash_client_sqlite", "zcash_primitives", "zcash_proofs", + "zip32", ] [[package]] @@ -5257,9 +5258,9 @@ dependencies = [ [[package]] name = "zcash_spec" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a3bf58b673cb3dacd8ae09ba345998923a197ab0da70d6239d8e8838949e9b" +checksum = "9cede95491c2191d3e278cab76e097a44b17fde8d6ca0d4e3a22cf4807b2d857" dependencies = [ "blake2b_simd", ] @@ -5327,13 +5328,14 @@ dependencies = [ [[package]] name = "zip32" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4226d0aee9c9407c27064dfeec9d7b281c917de3374e1e5a2e2cfad9e09de19e" +checksum = "92022ac1e47c7b78f9cee29efac8a1a546e189506f3bb5ad46d525be7c519bf6" dependencies = [ "blake2b_simd", "memuse", "subtle", + "zcash_spec", ] [[package]] diff --git a/backend-lib/Cargo.toml b/backend-lib/Cargo.toml index dcb5c421..d1b34d2c 100644 --- a/backend-lib/Cargo.toml +++ b/backend-lib/Cargo.toml @@ -19,6 +19,7 @@ zcash_client_backend = { version = "0.14", features = ["orchard", "tor", "transp zcash_client_sqlite = { version = "0.12.2", features = ["orchard", "transparent-inputs", "unstable"] } zcash_primitives = "0.19" zcash_proofs = "0.19" +zip32 = "0.1.2" # Infrastructure prost = "0.13" diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Derivation.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Derivation.kt index 3f61d159..229649e1 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Derivation.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Derivation.kt @@ -38,6 +38,37 @@ interface Derivation { numberOfAccounts: Int ): Array + /** + * Derives a ZIP 32 Arbitrary Key from the given seed at the "wallet level", i.e. + * directly from the seed with no ZIP 32 path applied. + * + * The resulting key will be the same across all networks (Zcash mainnet, Zcash + * testnet, OtherCoin mainnet, and so on). You can think of it as a context-specific + * seed fingerprint that can be used as (static) key material. + * + * @param contextString a globally-unique non-empty sequence of at most 252 bytes that + * identifies the desired context. + * @return an array of 32 bytes. + */ + fun deriveArbitraryWalletKey( + contextString: ByteArray, + seed: ByteArray + ): ByteArray + + /** + * Derives a ZIP 32 Arbitrary Key from the given seed at the account level. + * + * @param contextString a globally-unique non-empty sequence of at most 252 bytes that + * identifies the desired context. + * @return an array of 32 bytes. + */ + fun deriveArbitraryAccountKey( + contextString: ByteArray, + seed: ByteArray, + networkId: Int, + accountIndex: Int + ): ByteArray + companion object { const val DEFAULT_NUMBER_OF_ACCOUNTS = 1 } diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustDerivationTool.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustDerivationTool.kt index be2143bf..dbb5589a 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustDerivationTool.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustDerivationTool.kt @@ -40,6 +40,18 @@ class RustDerivationTool private constructor() : Derivation { networkId: Int ): String = deriveUnifiedAddressFromViewingKey(viewingKey, networkId = networkId) + override fun deriveArbitraryWalletKey( + contextString: ByteArray, + seed: ByteArray + ): ByteArray = deriveArbitraryWalletKeyFromSeed(contextString, seed) + + override fun deriveArbitraryAccountKey( + contextString: ByteArray, + seed: ByteArray, + networkId: Int, + accountIndex: Int + ): ByteArray = deriveArbitraryAccountKeyFromSeed(contextString, seed, accountIndex = accountIndex, networkId = networkId) + companion object { suspend fun new(): Derivation { RustBackend.loadLibrary() @@ -79,5 +91,19 @@ class RustDerivationTool private constructor() : Derivation { key: String, networkId: Int ): String + + @JvmStatic + private external fun deriveArbitraryWalletKeyFromSeed( + contextString: ByteArray, + seed: ByteArray + ): ByteArray + + @JvmStatic + private external fun deriveArbitraryAccountKeyFromSeed( + contextString: ByteArray, + seed: ByteArray, + accountIndex: Int, + networkId: Int + ): ByteArray } } diff --git a/backend-lib/src/main/rust/lib.rs b/backend-lib/src/main/rust/lib.rs index aca35ccf..bd755526 100644 --- a/backend-lib/src/main/rust/lib.rs +++ b/backend-lib/src/main/rust/lib.rs @@ -47,6 +47,7 @@ use zcash_client_sqlite::{ wallet::init::{init_wallet_db, WalletMigrationError}, AccountId, FsBlockDb, WalletDb, }; +use zcash_primitives::consensus::NetworkConstants; use zcash_primitives::{ block::BlockHash, consensus::{ @@ -65,6 +66,7 @@ use zcash_primitives::{ zip32::{self, DiversifierIndex}, }; use zcash_proofs::prover::LocalTxProver; +use zip32::ChildIndex; use crate::utils::{catch_unwind, exception::unwrap_exc_or}; @@ -2007,6 +2009,63 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_de unwrap_exc_or(&mut env, res, ptr::null_mut()) } +#[unsafe(no_mangle)] +pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveArbitraryWalletKeyFromSeed< + 'local, +>( + mut env: JNIEnv<'local>, + _: JClass<'local>, + context_string: JByteArray<'local>, + seed: JByteArray<'local>, +) -> jbyteArray { + let res = panic::catch_unwind(|| { + let _span = + tracing::info_span!("RustDerivationTool.deriveArbitraryWalletKeyFromSeed").entered(); + let context_string = env.convert_byte_array(context_string)?; + let seed = SecretVec::new(env.convert_byte_array(seed)?); + + let key = + zip32::arbitrary::SecretKey::from_path(&context_string, seed.expose_secret(), &[]); + + Ok(utils::rust_bytes_to_java(&env, key.data())?.into_raw()) + }); + unwrap_exc_or(&mut env, res, ptr::null_mut()) +} + +#[unsafe(no_mangle)] +pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveArbitraryAccountKeyFromSeed< + 'local, +>( + mut env: JNIEnv<'local>, + _: JClass<'local>, + context_string: JByteArray<'local>, + seed: JByteArray<'local>, + account: jint, + network_id: jint, +) -> jbyteArray { + let res = panic::catch_unwind(|| { + let _span = + tracing::info_span!("RustDerivationTool.deriveArbitraryAccountKeyFromSeed").entered(); + let network = parse_network(network_id as u32)?; + let context_string = env.convert_byte_array(context_string)?; + let seed = SecretVec::new(env.convert_byte_array(seed)?); + let account = account_id_from_jint(account)?; + + let key = zip32::arbitrary::SecretKey::from_path( + &context_string, + seed.expose_secret(), + &[ + ChildIndex::hardened(32), + ChildIndex::hardened(network.coin_type()), + ChildIndex::hardened(account.into()), + ], + ); + + Ok(utils::rust_bytes_to_java(&env, key.data())?.into_raw()) + }); + unwrap_exc_or(&mut env, res, ptr::null_mut()) +} + // // Tor support // diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/DerivationToolExt.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/DerivationToolExt.kt index 799cad5e..d5691ea0 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/DerivationToolExt.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/DerivationToolExt.kt @@ -47,3 +47,15 @@ fun Derivation.deriveUnifiedFullViewingKeysTypesafe( network.id, numberOfAccounts ).map { UnifiedFullViewingKey(it) } + +fun Derivation.deriveArbitraryWalletKeyTypesafe( + contextString: ByteArray, + seed: ByteArray +): ByteArray = deriveArbitraryWalletKey(contextString, seed) + +fun Derivation.deriveArbitraryAccountKeyTypesafe( + contextString: ByteArray, + seed: ByteArray, + network: ZcashNetwork, + account: Account +): ByteArray = deriveArbitraryAccountKey(contextString, seed, network.id, account.value) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeDerivationToolImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeDerivationToolImpl.kt index bdf340af..3356c0aa 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeDerivationToolImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeDerivationToolImpl.kt @@ -34,4 +34,16 @@ internal class TypesafeDerivationToolImpl(private val derivation: Derivation) : viewingKey: String, network: ZcashNetwork, ): String = derivation.deriveUnifiedAddress(viewingKey, network) + + override suspend fun deriveArbitraryWalletKey( + contextString: ByteArray, + seed: ByteArray + ): ByteArray = derivation.deriveArbitraryWalletKeyTypesafe(contextString, seed) + + override suspend fun deriveArbitraryAccountKey( + contextString: ByteArray, + seed: ByteArray, + network: ZcashNetwork, + account: Account + ): ByteArray = derivation.deriveArbitraryAccountKeyTypesafe(contextString, seed, network, account) } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt index a1d7ef32..a21eeaa4 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt @@ -82,6 +82,41 @@ interface DerivationTool { network: ZcashNetwork ): String + /** + * Derives a [ZIP 32 Arbitrary Key] from the given seed at the "wallet level", i.e. + * directly from the seed with no ZIP 32 path applied. + * + * The resulting key will be the same across all networks (Zcash mainnet, Zcash + * testnet, OtherCoin mainnet, and so on). You can think of it as a context-specific + * seed fingerprint that can be used as (static) key material. + * + * [ZIP 32 Arbitrary Key]: https://zips.z.cash/zip-0032#specification-arbitrary-key-derivation + * + * @param contextString a globally-unique non-empty sequence of at most 252 bytes that + * identifies the desired context. + * @return an array of 32 bytes. + */ + suspend fun deriveArbitraryWalletKey( + contextString: ByteArray, + seed: ByteArray + ): ByteArray + + /** + * Derives a [ZIP 32 Arbitrary Key] from the given seed at the account level. + * + * [ZIP 32 Arbitrary Key]: https://zips.z.cash/zip-0032#specification-arbitrary-key-derivation + * + * @param contextString a globally-unique non-empty sequence of at most 252 bytes that + * identifies the desired context. + * @return an array of 32 bytes. + */ + suspend fun deriveArbitraryAccountKey( + contextString: ByteArray, + seed: ByteArray, + network: ZcashNetwork, + account: Account + ): ByteArray + companion object { const val DEFAULT_NUMBER_OF_ACCOUNTS = Derivation.DEFAULT_NUMBER_OF_ACCOUNTS From 968afa46654620269462099d5fc64fb48f6c4724 Mon Sep 17 00:00:00 2001 From: Honza Date: Wed, 23 Oct 2024 14:40:44 +0200 Subject: [PATCH 3/3] Fix Ktlint warning --- .../z/ecc/android/sdk/internal/jni/RustDerivationTool.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustDerivationTool.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustDerivationTool.kt index dbb5589a..371c3553 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustDerivationTool.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustDerivationTool.kt @@ -50,7 +50,13 @@ class RustDerivationTool private constructor() : Derivation { seed: ByteArray, networkId: Int, accountIndex: Int - ): ByteArray = deriveArbitraryAccountKeyFromSeed(contextString, seed, accountIndex = accountIndex, networkId = networkId) + ): ByteArray = + deriveArbitraryAccountKeyFromSeed( + contextString = contextString, + seed = seed, + accountIndex = accountIndex, + networkId = networkId + ) companion object { suspend fun new(): Derivation {