Skip to content

Commit

Permalink
Add ZIP 32 Arbitrary Key derivation to DerivationTool
Browse files Browse the repository at this point in the history
  • Loading branch information
str4d committed Oct 22, 2024
1 parent f75c10a commit fdd3fb5
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions backend-lib/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,37 @@ interface Derivation {
numberOfAccounts: Int
): Array<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.
*
* @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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
}
}
59 changes: 59 additions & 0 deletions backend-lib/src/main/rust/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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};

Expand Down Expand Up @@ -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
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit fdd3fb5

Please sign in to comment.