From 04293f4f759fa67512a3e966e9ca3d608aaab416 Mon Sep 17 00:00:00 2001 From: Kevin Gorham Date: Thu, 1 Apr 2021 02:23:41 -0400 Subject: [PATCH] New: Add UnifiedViewingKey concept. A unified viewing keys serves as a grouping of keys that are all related to the same account but do not have spend authority. This is most important when initializing the database for scanning. --- .../z/ecc/android/sdk/jni/TransparentTest.kt | 41 ++++-- .../cash/z/ecc/android/sdk/Initializer.kt | 42 ++++--- .../cash/z/ecc/android/sdk/SdkSynchronizer.kt | 2 +- .../cash/z/ecc/android/sdk/jni/RustBackend.kt | 28 +++-- .../ecc/android/sdk/jni/RustBackendWelding.kt | 11 +- .../z/ecc/android/sdk/tool/DerivationTool.kt | 26 ++-- .../z/ecc/android/sdk/type/WalletTypes.kt | 12 ++ src/main/rust/lib.rs | 118 ++++++++---------- src/main/rust/utils.rs | 29 +++++ 9 files changed, 197 insertions(+), 112 deletions(-) diff --git a/src/androidTest/java/cash/z/ecc/android/sdk/jni/TransparentTest.kt b/src/androidTest/java/cash/z/ecc/android/sdk/jni/TransparentTest.kt index dfb048ca..5f732123 100644 --- a/src/androidTest/java/cash/z/ecc/android/sdk/jni/TransparentTest.kt +++ b/src/androidTest/java/cash/z/ecc/android/sdk/jni/TransparentTest.kt @@ -5,6 +5,7 @@ import cash.z.ecc.android.bip39.Mnemonics.MnemonicCode import cash.z.ecc.android.bip39.toSeed import cash.z.ecc.android.sdk.ext.TroubleshootingTwig import cash.z.ecc.android.sdk.ext.Twig +import cash.z.ecc.android.sdk.test.BuildConfig import cash.z.ecc.android.sdk.tool.DerivationTool import org.junit.Assert.assertEquals import org.junit.Before @@ -15,23 +16,35 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class TransparentTest { + lateinit var expected: Expected + @Before fun setup() { + expected = if (BuildConfig.FLAVOR == "zcashtestnet") ExpectedTestnet else ExpectedMainnet } @Test fun deriveTransparentSecretKeyTest() { - assertEquals(Expected.tskCompressed, DerivationTool.deriveTransparentSecretKey(SEED)) + assertEquals(expected.tskCompressed, DerivationTool.deriveTransparentSecretKey(SEED)) } @Test fun deriveTransparentAddressTest() { - assertEquals(Expected.tAddr, DerivationTool.deriveTransparentAddress(SEED)) + assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddress(SEED)) } @Test fun deriveTransparentAddressFromSecretKeyTest() { - assertEquals(Expected.tAddr, DerivationTool.deriveTransparentAddress(Expected.tskCompressed)) + assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromPrivateKey(expected.tskCompressed)) + } + + @Test + fun deriveUnifiedViewingKeysFromSeedTest() { + val uvks = DerivationTool.deriveUnifiedViewingKeys(SEED) + assertEquals(1, uvks.size) + val uvk = uvks.first() + assertEquals(expected.zAddr, DerivationTool.deriveShieldedAddress(uvk.extfvk)) + assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromPublicKey(uvk.extpub)) } // @Test @@ -49,11 +62,18 @@ class TransparentTest { val MNEMONIC = MnemonicCode(PHRASE) val SEED = MNEMONIC.toSeed() - object Expected { - val tAddr = "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4" + object ExpectedMainnet : Expected { + override val tAddr = "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4" + override val zAddr = "zs1yc4sgtfwwzz6xfsy2xsradzr6m4aypgxhfw2vcn3hatrh5ryqsr08sgpemlg39vdh9kfupx20py" + override val tskCompressed = "L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2" + override val tpk = "03b1d7fb28d17c125b504d06b1530097e0a3c76ada184237e3bc0925041230a5af" + } - // private key in compressed Wallet Import Format (WIF) - val tskCompressed = "L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2" + object ExpectedTestnet : Expected { + override val tAddr = "tm9v3KTsjXK8XWSqiwFjic6Vda6eHY9Mjjq" + override val zAddr = "ztestsapling1wn3tw9w5rs55x5yl586gtk72e8hcfdq8zsnjzcu8p7ghm8lrx54axc74mvm335q7lmy3g0sqje6" + override val tskCompressed = "KzVugoXxR7AtTMdR5sdJtHxCNvMzQ4H196k7ATv4nnjoummsRC9G" + override val tpk = "03b1d7fb28d17c125b504d06b1530097e0a3c76ada184237e3bc0925041230a5af" } @BeforeClass @@ -62,4 +82,11 @@ class TransparentTest { Twig.plant(TroubleshootingTwig(formatter = { "@TWIG $it" })) } } + + interface Expected { + val tAddr: String + val zAddr: String + val tskCompressed: String + val tpk: String + } } diff --git a/src/main/java/cash/z/ecc/android/sdk/Initializer.kt b/src/main/java/cash/z/ecc/android/sdk/Initializer.kt index 126f9b50..f487ece7 100644 --- a/src/main/java/cash/z/ecc/android/sdk/Initializer.kt +++ b/src/main/java/cash/z/ecc/android/sdk/Initializer.kt @@ -9,6 +9,7 @@ import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.sdk.jni.RustBackend import cash.z.ecc.android.sdk.tool.DerivationTool import cash.z.ecc.android.sdk.tool.WalletBirthdayTool +import cash.z.ecc.android.sdk.type.UnifiedViewingKey import cash.z.ecc.android.sdk.type.WalletBirthday import java.io.File @@ -21,7 +22,7 @@ class Initializer constructor(appContext: Context, config: Config) : SdkSynchron override val alias: String override val host: String override val port: Int - val viewingKeys: List + val viewingKeys: List val birthday: WalletBirthday /** @@ -62,7 +63,7 @@ class Initializer constructor(appContext: Context, config: Config) : SdkSynchron private fun initMissingDatabases( birthday: WalletBirthday, - vararg viewingKeys: String + vararg viewingKeys: UnifiedViewingKey ) { maybeCreateDataDb() maybeInitBlocksTable(birthday) @@ -101,9 +102,10 @@ class Initializer constructor(appContext: Context, config: Config) : SdkSynchron /** * Initialize the accounts table with the given viewing keys, if needed. */ - private fun maybeInitAccountsTable(vararg viewingKeys: String) { + private fun maybeInitAccountsTable(vararg viewingKeys: UnifiedViewingKey) { tryWarn( - "Warning: did not initialize the accounts table. It probably was already initialized." + "Warning: did not initialize the accounts table. It probably was already initialized.", + unlessContains = "constraint failed" ) { rustBackend.initAccountsTable(*viewingKeys) accountsCreated = true @@ -133,7 +135,7 @@ class Initializer constructor(appContext: Context, config: Config) : SdkSynchron } class Config private constructor ( - val viewingKeys: MutableList = mutableListOf(), + val viewingKeys: MutableList = mutableListOf(), var alias: String = ZcashSdk.DEFAULT_ALIAS, var host: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST, var port: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT, @@ -207,10 +209,10 @@ class Initializer constructor(appContext: Context, config: Config) : SdkSynchron * is not currently well supported. Consider it an alpha-preview feature that might work but * probably has serious bugs. */ - fun setViewingKeys(vararg extendedFullViewingKeys: String): Config = apply { + fun setViewingKeys(vararg unifiedViewingKeys: UnifiedViewingKey): Config = apply { viewingKeys.apply { clear() - addAll(extendedFullViewingKeys) + addAll(unifiedViewingKeys) } } @@ -219,8 +221,8 @@ class Initializer constructor(appContext: Context, config: Config) : SdkSynchron * is not currently well supported. Consider it an alpha-preview feature that might work but * probably has serious bugs. */ - fun addViewingKey(extendedFullViewingKey: String): Config = apply { - viewingKeys.add(extendedFullViewingKey) + fun addViewingKey(unifiedFullViewingKey: UnifiedViewingKey): Config = apply { + viewingKeys.add(unifiedFullViewingKey) } // @@ -238,7 +240,7 @@ class Initializer constructor(appContext: Context, config: Config) : SdkSynchron } fun importWallet( - viewingKey: String, + viewingKey: UnifiedViewingKey, birthdayHeight: Int? = null, host: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST, port: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT @@ -259,7 +261,7 @@ class Initializer constructor(appContext: Context, config: Config) : SdkSynchron } fun newWallet( - viewingKey: String, + viewingKey: UnifiedViewingKey, host: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST, port: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT ): Config = apply { @@ -273,7 +275,7 @@ class Initializer constructor(appContext: Context, config: Config) : SdkSynchron * calling `setViewingKeys` with the keys that match this seed. */ fun setSeed(seed: ByteArray, numberOfAccounts: Int = 1): Config = apply { - setViewingKeys(*DerivationTool.deriveViewingKeys(seed, numberOfAccounts)) + setViewingKeys(*DerivationTool.deriveUnifiedViewingKeys(seed, numberOfAccounts)) } // @@ -303,11 +305,11 @@ class Initializer constructor(appContext: Context, config: Config) : SdkSynchron private fun validateViewingKeys() { require(viewingKeys.isNotEmpty()) { - "Viewing keys are required. Ensure that the viewing keys or seed have been set" + - " on this Initializer." + "Unified Viewing keys are required. Ensure that the unified viewing keys or seed" + + " have been set on this Initializer." } viewingKeys.forEach { - DerivationTool.validateViewingKey(it) + DerivationTool.validateUnifiedViewingKey(it) } } } @@ -397,13 +399,15 @@ internal fun validateAlias(alias: String) { ) { "ERROR: Invalid alias ($alias). For security, the alias must be shorter than 100 " + "characters and only contain letters, digits or underscores and start with a letter; " + - "ideally, it would also differentiate across mainnet and testnet but that is not " + - "enforced." + "ideally, it would also differentiate across mainnet and testnet but that is not " + + "enforced." } // TODO: consider exposing this as a proper warning that can be received by apps, since most apps won't use logging if (alias.toLowerCase().contains(BuildConfig.FLAVOR.toLowerCase())) { - twig("WARNING: alias does not contain the build flavor but it probably should to help" + - " prevent testnet data from contaminating mainnet data.") + twig( + "WARNING: alias does not contain the build flavor but it probably should to help" + + " prevent testnet data from contaminating mainnet data." + ) } } diff --git a/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt b/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt index f10b2c1a..5031beb0 100644 --- a/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt +++ b/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt @@ -603,7 +603,7 @@ class SdkSynchronizer internal constructor( memo: String ): Flow = flow { twig("Initializing shielding transaction") - val tAddr = DerivationTool.deriveTransparentAddress(transparentSecretKey) + val tAddr = DerivationTool.deriveTransparentAddressFromPrivateKey(transparentSecretKey) val tBalance = processor.getUtxoCacheBalance(tAddr) val zAddr = getAddress(0) diff --git a/src/main/java/cash/z/ecc/android/sdk/jni/RustBackend.kt b/src/main/java/cash/z/ecc/android/sdk/jni/RustBackend.kt index c3925fd4..b3d97e2b 100644 --- a/src/main/java/cash/z/ecc/android/sdk/jni/RustBackend.kt +++ b/src/main/java/cash/z/ecc/android/sdk/jni/RustBackend.kt @@ -4,6 +4,8 @@ import cash.z.ecc.android.sdk.exception.BirthdayException import cash.z.ecc.android.sdk.ext.ZcashSdk.OUTPUT_PARAM_FILE_NAME import cash.z.ecc.android.sdk.ext.ZcashSdk.SPEND_PARAM_FILE_NAME import cash.z.ecc.android.sdk.ext.twig +import cash.z.ecc.android.sdk.tool.DerivationTool +import cash.z.ecc.android.sdk.type.UnifiedViewingKey import cash.z.ecc.android.sdk.type.WalletBalance import java.io.File @@ -47,13 +49,24 @@ class RustBackend private constructor() : RustBackendWelding { override fun initDataDb() = initDataDb(pathDataDb) - override fun initAccountsTable(vararg extfvks: String) = - initAccountsTableWithKeys(pathDataDb, extfvks) + override fun initAccountsTable(vararg keys: UnifiedViewingKey): Boolean { + val extfvks = Array(keys.size) { "" } + val extpubs = Array(keys.size) { "" } + keys.forEachIndexed { i, key -> + extfvks[i] = key.extfvk + extpubs[i] = key.extpub + } + return initAccountsTableWithKeys(pathDataDb, extfvks, extpubs) + } override fun initAccountsTable( seed: ByteArray, numberOfAccounts: Int - ) = initAccountsTable(pathDataDb, seed, numberOfAccounts) + ): Array { + return DerivationTool.deriveUnifiedViewingKeys(seed, numberOfAccounts).apply { + initAccountsTable(*this) + } + } override fun initBlocksTable( height: Int, @@ -230,15 +243,10 @@ class RustBackend private constructor() : RustBackendWelding { @JvmStatic private external fun initDataDb(dbDataPath: String): Boolean - @JvmStatic private external fun initAccountsTable( - dbDataPath: String, - seed: ByteArray, - accounts: Int - ): Array - @JvmStatic private external fun initAccountsTableWithKeys( dbDataPath: String, - extfvk: Array + extfvk: Array, + extpub: Array, ): Boolean @JvmStatic private external fun initBlocksTable( diff --git a/src/main/java/cash/z/ecc/android/sdk/jni/RustBackendWelding.kt b/src/main/java/cash/z/ecc/android/sdk/jni/RustBackendWelding.kt index 8de3e99b..7fb32365 100644 --- a/src/main/java/cash/z/ecc/android/sdk/jni/RustBackendWelding.kt +++ b/src/main/java/cash/z/ecc/android/sdk/jni/RustBackendWelding.kt @@ -1,6 +1,7 @@ package cash.z.ecc.android.sdk.jni import cash.z.ecc.android.sdk.ext.ZcashSdk +import cash.z.ecc.android.sdk.type.UnifiedViewingKey import cash.z.ecc.android.sdk.type.WalletBalance /** @@ -28,9 +29,9 @@ interface RustBackendWelding { fun decryptAndStoreTransaction(tx: ByteArray) - fun initAccountsTable(seed: ByteArray, numberOfAccounts: Int): Array + fun initAccountsTable(seed: ByteArray, numberOfAccounts: Int): Array - fun initAccountsTable(vararg extfvks: String): Boolean + fun initAccountsTable(vararg keys: UnifiedViewingKey): Boolean fun initBlocksTable(height: Int, hash: String, time: Long, saplingTree: String): Boolean @@ -85,12 +86,14 @@ interface RustBackendWelding { fun deriveTransparentAddress(seed: ByteArray, account: Int = 0, index: Int = 0): String - fun deriveTransparentAddress(transparentSecretKey: String): String + fun deriveTransparentAddressFromPublicKey(publicKey: String): String + + fun deriveTransparentAddressFromPrivateKey(privateKey: String): String fun deriveTransparentSecretKey(seed: ByteArray, account: Int = 0, index: Int = 0): String fun deriveViewingKey(spendingKey: String): String - fun deriveViewingKeys(seed: ByteArray, numberOfAccounts: Int = 1): Array + fun deriveUnifiedViewingKeys(seed: ByteArray, numberOfAccounts: Int = 1): Array } } diff --git a/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt b/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt index 51d23877..b691be01 100644 --- a/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt +++ b/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt @@ -2,6 +2,7 @@ package cash.z.ecc.android.sdk.tool import cash.z.ecc.android.sdk.jni.RustBackend import cash.z.ecc.android.sdk.jni.RustBackendWelding +import cash.z.ecc.android.sdk.type.UnifiedViewingKey class DerivationTool { @@ -16,9 +17,11 @@ class DerivationTool { * * @return the viewing keys that correspond to the seed, formatted as Strings. */ - override fun deriveViewingKeys(seed: ByteArray, numberOfAccounts: Int): Array = + override fun deriveUnifiedViewingKeys(seed: ByteArray, numberOfAccounts: Int): Array = withRustBackendLoaded { - deriveExtendedFullViewingKeys(seed, numberOfAccounts) + deriveUnifiedViewingKeysFromSeed(seed, numberOfAccounts).map { + UnifiedViewingKey(it[0], it[1]) + }.toTypedArray() } /** @@ -79,15 +82,19 @@ class DerivationTool { deriveTransparentAddressFromSeed(seed, account, index) } - override fun deriveTransparentAddress(transparentSecretKey: String): String = withRustBackendLoaded { - deriveTransparentAddressFromSecretKey(transparentSecretKey) + override fun deriveTransparentAddressFromPublicKey(transparentPublicKey: String): String = withRustBackendLoaded { + deriveTransparentAddressFromPubKey(transparentPublicKey) + } + + override fun deriveTransparentAddressFromPrivateKey(transparentPrivateKey: String): String = withRustBackendLoaded { + deriveTransparentAddressFromPrivKey(transparentPrivateKey) } override fun deriveTransparentSecretKey(seed: ByteArray, account: Int, index: Int): String = withRustBackendLoaded { deriveTransparentSecretKeyFromSeed(seed, account, index) } - fun validateViewingKey(viewingKey: String) { + fun validateUnifiedViewingKey(viewingKey: UnifiedViewingKey) { // TODO } @@ -112,10 +119,10 @@ class DerivationTool { ): Array @JvmStatic - private external fun deriveExtendedFullViewingKeys( + private external fun deriveUnifiedViewingKeysFromSeed( seed: ByteArray, numberOfAccounts: Int - ): Array + ): Array> @JvmStatic private external fun deriveExtendedFullViewingKey(spendingKey: String): String @@ -133,7 +140,10 @@ class DerivationTool { private external fun deriveTransparentAddressFromSeed(seed: ByteArray, account: Int, index: Int): String @JvmStatic - private external fun deriveTransparentAddressFromSecretKey(tsk: String): String + private external fun deriveTransparentAddressFromPubKey(pk: String): String + + @JvmStatic + private external fun deriveTransparentAddressFromPrivKey(sk: String): String @JvmStatic private external fun deriveTransparentSecretKeyFromSeed(seed: ByteArray, account: Int, index: Int): String diff --git a/src/main/java/cash/z/ecc/android/sdk/type/WalletTypes.kt b/src/main/java/cash/z/ecc/android/sdk/type/WalletTypes.kt index b5169448..dfbeaf10 100644 --- a/src/main/java/cash/z/ecc/android/sdk/type/WalletTypes.kt +++ b/src/main/java/cash/z/ecc/android/sdk/type/WalletTypes.kt @@ -30,3 +30,15 @@ data class WalletBirthday( val tree: String = "" ) +/** + * A grouping of keys that correspond to a single wallet account but do not have spend authority. + * + * @param extfvk the extended full viewing key which provides the ability to see inbound and + * outbound shielded transactions. It can also be used to derive a z-addr. + * @param extpub the extended public key which provides the ability to see transparent + * transactions. It can also be used to derive a t-addr. + */ +data class UnifiedViewingKey( + val extfvk: String = "", + val extpub: String = "" +) \ No newline at end of file diff --git a/src/main/rust/lib.rs b/src/main/rust/lib.rs index 70e6b059..20e367b0 100644 --- a/src/main/rust/lib.rs +++ b/src/main/rust/lib.rs @@ -5,6 +5,7 @@ use std::convert::{TryFrom, TryInto}; use std::panic; use std::path::Path; use std::ptr; +use std::str::FromStr; use android_logger::Config; use failure::format_err; @@ -20,7 +21,7 @@ use zcash_client_backend::{ chain::{scan_cached_blocks, validate_chain}, error::Error, wallet::{create_spend_to_address, decrypt_and_store_transaction}, - WalletRead, WalletWrite, + WalletRead, }, encoding::{ AddressCodec, decode_extended_full_viewing_key, @@ -28,7 +29,8 @@ use zcash_client_backend::{ encode_payment_address, }, keys::{ - derive_secret_key_from_seed, derive_transparent_address_from_secret_key, + derive_secret_key_from_seed, derive_public_key_from_seed, + derive_transparent_address_from_public_key, derive_transparent_address_from_secret_key, spending_key, Wif, }, wallet::{AccountId, OvkPolicy, WalletTransparentOutput}, @@ -61,10 +63,9 @@ use zcash_primitives::consensus::{MAIN_NETWORK, MainNetwork}; #[cfg(not(feature = "mainnet"))] use zcash_primitives::consensus::{TEST_NETWORK, TestNetwork}; use zcash_proofs::prover::LocalTxProver; -use secp256k1::key::SecretKey; +use secp256k1::key::{SecretKey, PublicKey}; use crate::utils::exception::unwrap_exc_or; -use zcash_client_sqlite::wallet::get_unspent_transparent_utxos; mod utils; @@ -127,56 +128,13 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initDataDb( unwrap_exc_or(&env, res, JNI_FALSE) } -#[no_mangle] -pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initAccountsTable( - env: JNIEnv<'_>, - _: JClass<'_>, - db_data: JString<'_>, - seed: jbyteArray, - accounts: jint, -) -> jobjectArray { - let res = panic::catch_unwind(|| { - let db_data = wallet_db(&env, NETWORK, db_data)?; - let seed = env.convert_byte_array(seed).unwrap(); - let accounts = if accounts >= 0 { - accounts as u32 - } else { - return Err(format_err!("accounts argument must be positive")); - }; - - let extsks: Vec<_> = (0..accounts) - .map(|account| spending_key(&seed, NETWORK.coin_type(), account)) - .collect(); - let extfvks: Vec<_> = extsks.iter().map(ExtendedFullViewingKey::from).collect(); - - init_accounts_table(&db_data, &extfvks) - .map(|_| { - // Return the ExtendedSpendingKeys for the created accounts - utils::rust_vec_to_java( - &env, - extsks, - "java/lang/String", - |env, extsk| { - env.new_string(encode_extended_spending_key( - NETWORK.hrp_sapling_extended_spending_key(), - &extsk, - )) - }, - |env| env.new_string(""), - ) - }) - .map_err(|e| format_err!("Error while initializing accounts: {}", e)) - }); - - unwrap_exc_or(&env, res, ptr::null_mut()) -} - #[no_mangle] pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initAccountsTableWithKeys( env: JNIEnv<'_>, _: JClass<'_>, db_data: JString<'_>, extfvks_arr: jobjectArray, + extpubs_arr: jobjectArray, ) -> jboolean { let res = panic::catch_unwind(|| { let db_data = wallet_db(&env, NETWORK, db_data)?; @@ -195,7 +153,14 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initAccount }) .collect::>(); - match init_accounts_table(&db_data, &extfvks) { + let taddrs:Vec<_> = (0..count) + .map(|i| env.get_object_array_element(extpubs_arr, i)) + .map(|jstr| utils::java_string_to_rust(&env, jstr.unwrap().into())) + .map(|extpub_str| PublicKey::from_str(&extpub_str).unwrap()) + .map(|pk| derive_transparent_address_from_public_key(pk)) + .collect(); + + match init_accounts_table(&db_data, &extfvks, &taddrs) { Ok(()) => Ok(JNI_TRUE), Err(e) => Err(format_err!("Error while initializing accounts: {}", e)), } @@ -239,7 +204,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveE } #[no_mangle] -pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveExtendedFullViewingKeys( +pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveUnifiedViewingKeysFromSeed( env: JNIEnv<'_>, _: JClass<'_>, seed: jbyteArray, @@ -255,19 +220,26 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveE let extfvks: Vec<_> = (0..accounts) .map(|account| { - ExtendedFullViewingKey::from(&spending_key(&seed, NETWORK.coin_type(), account)) + encode_extended_full_viewing_key( + NETWORK.hrp_sapling_extended_full_viewing_key(), + &ExtendedFullViewingKey::from(&spending_key(&seed, NETWORK.coin_type(), account)) + ) }) .collect(); - Ok(utils::rust_vec_to_java( + let extpubs: Vec<_> = (0..accounts) + .map(|account| { + let pk = derive_public_key_from_seed(&NETWORK, &seed, AccountId(account), 0).unwrap(); + hex::encode(&pk.serialize()) + }) + .collect(); + + Ok(utils::rust_vec_to_java_2d( &env, extfvks, - "java/lang/String", - |env, extfvk| { - env.new_string(encode_extended_full_viewing_key( - NETWORK.hrp_sapling_extended_full_viewing_key(), - &extfvk, - )) + extpubs, + |env, extfvkstr| { + env.new_string(extfvkstr) }, |env| env.new_string(""), )) @@ -704,9 +676,6 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_rewindToHei ) -> jboolean { let res = panic::catch_unwind(|| { let db_data = wallet_db(&env, NETWORK, db_data)?; - let mut update_ops = (&db_data) - .get_update_ops() - .map_err(|e| format_err!("Could not obtain a writable database connection: {}", e))?; let height = BlockHeight::try_from(height)?; rewind_to_height(&db_data, height) @@ -889,7 +858,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveT } #[no_mangle] -pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveTransparentAddressFromSecretKey( +pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveTransparentAddressFromPrivKey( env: JNIEnv<'_>, _: JClass<'_>, secret_key: JString<'_>, @@ -910,6 +879,29 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveT unwrap_exc_or(&env, res, ptr::null_mut()) } + +#[no_mangle] +pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveTransparentAddressFromPubKey( + env: JNIEnv<'_>, + _: JClass<'_>, + public_key: JString<'_>, +) -> jstring { + let res = panic::catch_unwind(|| { + let public_key_str = utils::java_string_to_rust(&env, public_key); + let pk = PublicKey::from_str(&public_key_str)?; + let taddr = + derive_transparent_address_from_public_key(pk) + .encode(&NETWORK); + + let output = env + .new_string(taddr) + .expect("Couldn't create Java string!"); + + Ok(output.into_inner()) + }); + unwrap_exc_or(&env, res, ptr::null_mut()) +} + #[no_mangle] pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_decryptAndStoreTransaction( env: JNIEnv<'_>, @@ -984,7 +976,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createToAdd } }; - let memo = Memo::from_bytes(&memo_bytes).map_err(|_| format_err!("Invalid memo"))?;; + let memo = Memo::from_bytes(&memo_bytes).map_err(|_| format_err!("Invalid memo"))?; let prover = LocalTxProver::new(Path::new(&spend_params), Path::new(&output_params)); diff --git a/src/main/rust/utils.rs b/src/main/rust/utils.rs index b65be95e..6c03c91f 100644 --- a/src/main/rust/utils.rs +++ b/src/main/rust/utils.rs @@ -39,3 +39,32 @@ where } jret } + +// 2D array +pub(crate) fn rust_vec_to_java_2d<'a, T, V, F, G>( + env: &JNIEnv<'a>, + data1: Vec, + data2: Vec, + element_map: F, + empty_element: G, +) -> jobjectArray +where + V: Deref>, + F: Fn(&JNIEnv<'a>, T) -> JNIResult, + G: Fn(&JNIEnv<'a>) -> JNIResult, +{ + let jempty = empty_element(env).expect("Couldn't create Java string!"); + let outer = env + .new_object_array(data1.len() as jsize, "[Ljava/lang/String;", *jni::objects::JObject::null()) + .expect("Couldn't create Java array of string arrays!"); + + for (i, (elem1, elem2)) in data1.into_iter().zip(data2.into_iter()).enumerate() { + let inner = env.new_object_array(2 as jsize, "java/lang/String", *jempty).expect("Couldn't create Java array!"); + let jelem1 = element_map(env, elem1).expect("Couldn't map element to Java!"); + let jelem2 = element_map(env, elem2).expect("Couldn't map element to Java!"); + env.set_object_array_element(inner, 0 as jsize, *jelem1).expect("Couldn't set Java array element!"); + env.set_object_array_element(inner, 1 as jsize, *jelem2).expect("Couldn't set Java array element!"); + env.set_object_array_element(outer, i as jsize, inner).expect("Couldn't set Java array element!"); + } + outer +}