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.
This commit is contained in:
Kevin Gorham 2021-04-01 02:23:41 -04:00
parent ac9f803d5b
commit 04293f4f75
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
9 changed files with 197 additions and 112 deletions

View File

@ -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
}
}

View File

@ -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<String>
val viewingKeys: List<UnifiedViewingKey>
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<String> = mutableListOf(),
val viewingKeys: MutableList<UnifiedViewingKey> = 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."
)
}
}

View File

@ -603,7 +603,7 @@ class SdkSynchronizer internal constructor(
memo: String
): Flow<PendingTransaction> = flow {
twig("Initializing shielding transaction")
val tAddr = DerivationTool.deriveTransparentAddress(transparentSecretKey)
val tAddr = DerivationTool.deriveTransparentAddressFromPrivateKey(transparentSecretKey)
val tBalance = processor.getUtxoCacheBalance(tAddr)
val zAddr = getAddress(0)

View File

@ -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<UnifiedViewingKey> {
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<String>
@JvmStatic private external fun initAccountsTableWithKeys(
dbDataPath: String,
extfvk: Array<out String>
extfvk: Array<out String>,
extpub: Array<out String>,
): Boolean
@JvmStatic private external fun initBlocksTable(

View File

@ -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<String>
fun initAccountsTable(seed: ByteArray, numberOfAccounts: Int): Array<UnifiedViewingKey>
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<String>
fun deriveUnifiedViewingKeys(seed: ByteArray, numberOfAccounts: Int = 1): Array<UnifiedViewingKey>
}
}

View File

@ -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<String> =
override fun deriveUnifiedViewingKeys(seed: ByteArray, numberOfAccounts: Int): Array<UnifiedViewingKey> =
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<String>
@JvmStatic
private external fun deriveExtendedFullViewingKeys(
private external fun deriveUnifiedViewingKeysFromSeed(
seed: ByteArray,
numberOfAccounts: Int
): Array<String>
): Array<Array<String>>
@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

View File

@ -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 = ""
)

View File

@ -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::<Vec<_>>();
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));

View File

@ -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<T>,
data2: Vec<T>,
element_map: F,
empty_element: G,
) -> jobjectArray
where
V: Deref<Target = JObject<'a>>,
F: Fn(&JNIEnv<'a>, T) -> JNIResult<V>,
G: Fn(&JNIEnv<'a>) -> JNIResult<V>,
{
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
}