[#525] Derive transparent account privkey instead of transparent secret key

We need the transparent account privkey in order to be able to derive
the secret key for any leaf transparent address under the ZIP 316
diversified address tree.
This commit is contained in:
Jack Grigg 2022-05-19 01:04:30 +00:00 committed by Jack Grigg
parent f7c9bad367
commit cfdd3640a9
17 changed files with 260 additions and 88 deletions

View File

@ -8,6 +8,8 @@ Change Log
Key as specified in [ZIP 316](https://zips.z.cash/zip-0316#encoding-of-unified-full-incoming-viewing-keys).
- TODO: Actually encode per ZIP 316.
- `cash.z.ecc.android.sdk.tool`:
- `DerivationTool.deriveTransparentAccountPrivateKey`
- `DerivationTool.deriveTransparentAddressFromAccountPrivateKey`
- `DerivationTool.deriveUnifiedAddress`
- `DerivationTool.deriveUnifiedFullViewingKeys`
- `DerivationTool.validateUnifiedFullViewingKey`
@ -21,6 +23,9 @@ Change Log
- `Initializer.Config.importWallet`
- `Initializer.Config.newWallet`
- `Initializer.Config.setViewingKeys`
- `cash.z.ecc.android.sdk`:
- `Synchronizer.shieldFunds` now takes a transparent account private key (representing
all transparent secret keys within an account) instead of a transparent secret key.
### Removed
- `cash.z.ecc.android.sdk.type.UnifiedViewingKey`
@ -28,6 +33,10 @@ Change Log
public key, and not the extended public key as intended. This made it incompatible
with ZIP 316.
- `cash.z.ecc.android.sdk.tool`:
- `DerivationTool.deriveTransparentAddressFromPrivateKey` (use
`DerivationTool.deriveTransparentAddressFromAccountPrivateKey` instead).
- `DerivationTool.deriveTransparentSecretKey` (use
`DerivationTool.deriveTransparentAccountPrivateKey` instead).
- `DerivationTool.deriveShieldedAddress`
- TODO: Do we still need to be able to derive Sapling shielded addresses for legacy
support? Currently removed because `UnifiedFullViewingKey` doesn't expose the

View File

@ -63,8 +63,8 @@ class TestWallet(
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
private val shieldedSpendingKey =
runBlocking { DerivationTool.deriveSpendingKeys(seed, network = network)[0] }
private val transparentSecretKey =
runBlocking { DerivationTool.deriveTransparentSecretKey(seed, network = network) }
private val transparentAccountPrivateKey =
runBlocking { DerivationTool.deriveTransparentAccountPrivateKey(seed, network = network) }
val initializer = runBlocking {
Initializer.new(context) { config ->
runBlocking { config.importWallet(seed, startHeight, network, endpoint, alias = alias) }
@ -133,7 +133,7 @@ class TestWallet(
twig("FOUND utxo balance of total: ${walletBalance.total} available: ${walletBalance.available}")
if (walletBalance.available.value > 0L) {
synchronizer.shieldFunds(shieldedSpendingKey, transparentSecretKey)
synchronizer.shieldFunds(shieldedSpendingKey, transparentAccountPrivateKey)
.onCompletion { twig("done shielding funds") }
.catch { twig("Failed with $it") }
.collect()

109
sdk-lib/Cargo.lock generated
View File

@ -35,7 +35,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072"
dependencies = [
"cipher",
"opaque-debug",
"opaque-debug 0.3.0",
]
[[package]]
@ -45,7 +45,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce"
dependencies = [
"cipher",
"opaque-debug",
"opaque-debug 0.3.0",
]
[[package]]
@ -120,6 +120,12 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc19a4937b4fbd3fe3379793130e42060d10627a360f2127802b10b87e7baf74"
[[package]]
name = "base58"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83"
[[package]]
name = "base64"
version = "0.13.0"
@ -203,13 +209,25 @@ dependencies = [
"constant_time_eq",
]
[[package]]
name = "block-buffer"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
dependencies = [
"block-padding 0.1.5",
"byte-tools",
"byteorder",
"generic-array 0.12.4",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array",
"generic-array 0.14.5",
]
[[package]]
@ -218,10 +236,19 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0"
dependencies = [
"block-padding",
"block-padding 0.2.1",
"cipher",
]
[[package]]
name = "block-padding"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
dependencies = [
"byte-tools",
]
[[package]]
name = "block-padding"
version = "0.2.1"
@ -257,6 +284,12 @@ version = "3.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
[[package]]
name = "byte-tools"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
[[package]]
name = "byteorder"
version = "1.4.3"
@ -299,7 +332,7 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801"
dependencies = [
"generic-array",
"generic-array 0.14.5",
]
[[package]]
@ -420,13 +453,22 @@ dependencies = [
"crypto_api",
]
[[package]]
name = "digest"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
dependencies = [
"generic-array 0.12.4",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
"generic-array 0.14.5",
]
[[package]]
@ -563,6 +605,15 @@ dependencies = [
"num_cpus",
]
[[package]]
name = "generic-array"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.14.5"
@ -643,6 +694,18 @@ dependencies = [
"secp256k1",
]
[[package]]
name = "hdwallet-bitcoin"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "969c513e03167e65d4bb59f5c51ec3820210975044ad7f218ab801fc169760fa"
dependencies = [
"base58",
"hdwallet",
"hex",
"ripemd160 0.8.0",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
@ -857,6 +920,12 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
[[package]]
name = "opaque-debug"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "opaque-debug"
version = "0.3.0"
@ -1051,15 +1120,26 @@ dependencies = [
"winapi",
]
[[package]]
name = "ripemd160"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad5112e0dbbb87577bfbc56c42450235e3012ce336e29c5befd7807bd626da4a"
dependencies = [
"block-buffer 0.7.3",
"digest 0.8.1",
"opaque-debug 0.2.3",
]
[[package]]
name = "ripemd160"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251"
dependencies = [
"block-buffer",
"digest",
"opaque-debug",
"block-buffer 0.9.0",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
[[package]]
@ -1196,11 +1276,11 @@ version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
dependencies = [
"block-buffer",
"block-buffer 0.9.0",
"cfg-if 1.0.0",
"cpufeatures",
"digest",
"opaque-debug",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
[[package]]
@ -1539,6 +1619,7 @@ dependencies = [
"android_logger",
"failure",
"hdwallet",
"hdwallet-bitcoin",
"hex",
"jni",
"log",
@ -1570,7 +1651,7 @@ dependencies = [
"protobuf",
"protobuf-codegen-pure",
"rand_core 0.5.1",
"ripemd160",
"ripemd160 0.9.1",
"secp256k1",
"sha2",
"subtle",
@ -1635,7 +1716,7 @@ dependencies = [
"log",
"rand",
"rand_core 0.5.1",
"ripemd160",
"ripemd160 0.9.1",
"secp256k1",
"sha2",
"subtle",

View File

@ -13,6 +13,7 @@ edition = "2018"
android_logger = "0.9"
failure = "0.1"
hdwallet = "=0.3.0"
hdwallet-bitcoin = "0.3"
hex = "0.4"
jni = { version = "0.17", default-features = false }
log = "0.4"

View File

@ -22,8 +22,8 @@ import org.junit.runners.Parameterized
class TransparentTest(val expected: Expected, val network: ZcashNetwork) {
@Test
fun deriveTransparentSecretKeyTest() = runBlocking {
assertEquals(expected.tskCompressed, DerivationTool.deriveTransparentSecretKey(SEED, network = network))
fun deriveTransparentAccountPrivateKeyTest() = runBlocking {
assertEquals(expected.tskCompressed, DerivationTool.deriveTransparentAccountPrivateKey(SEED, network = network))
}
@Test
@ -32,9 +32,9 @@ class TransparentTest(val expected: Expected, val network: ZcashNetwork) {
}
@Test
fun deriveTransparentAddressFromSecretKeyTest() = runBlocking {
val pk = DerivationTool.deriveTransparentSecretKey(SEED, network = network)
assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromPrivateKey(pk, network = network))
fun deriveTransparentAddressFromAccountPrivateKeyTest() = runBlocking {
val pk = DerivationTool.deriveTransparentAccountPrivateKey(SEED, network = network)
assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromAccountPrivateKey(pk, network = network))
}
@Test

View File

@ -162,7 +162,7 @@ class TransparentRestoreSample {
// private val context = InstrumentationRegistry.getInstrumentation().context
// private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
// private val shieldedSpendingKey = DerivationTool.deriveSpendingKeys(seed, Testnet)[0]
// private val transparentSecretKey = DerivationTool.deriveTransparentSecretKey(seed, Testnet)
// private val transparentAccountPrivateKey = DerivationTool.deriveTransparentAccountPrivateKey(seed, Testnet)
// private val host = "lightwalletd.testnet.electriccoin.co"
// private val initializer = Initializer(context) { config ->
// config.importWallet(seed, startHeight)
@ -219,7 +219,7 @@ class TransparentRestoreSample {
// twig("FOUND utxo balance of total: ${walletBalance.totalZatoshi} available: ${walletBalance.availableZatoshi}")
//
// if (walletBalance.availableZatoshi > 0L) {
// synchronizer.shieldFunds(shieldedSpendingKey, transparentSecretKey)
// synchronizer.shieldFunds(shieldedSpendingKey, transparentAccountPrivateKey)
// .onCompletion { twig("done shielding funds") }
// .catch { twig("Failed with $it") }
// .collect()

View File

@ -63,8 +63,8 @@ class TestWallet(
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
private val shieldedSpendingKey =
runBlocking { DerivationTool.deriveSpendingKeys(seed, network = network)[0] }
private val transparentSecretKey =
runBlocking { DerivationTool.deriveTransparentSecretKey(seed, network = network) }
private val transparentAccountPrivateKey =
runBlocking { DerivationTool.deriveTransparentAccountPrivateKey(seed, network = network) }
val initializer = runBlocking {
Initializer.new(context) { config ->
runBlocking { config.importWallet(seed, startHeight, network, endpoint, alias = alias) }
@ -133,7 +133,7 @@ class TestWallet(
twig("FOUND utxo balance of total: ${walletBalance.total} available: ${walletBalance.available}")
if (walletBalance.available.value > 0L) {
synchronizer.shieldFunds(shieldedSpendingKey, transparentSecretKey)
synchronizer.shieldFunds(shieldedSpendingKey, transparentAccountPrivateKey)
.onCompletion { twig("done shielding funds") }
.catch { twig("Failed with $it") }
.collect()

View File

@ -656,19 +656,19 @@ class SdkSynchronizer internal constructor(
override fun shieldFunds(
spendingKey: String,
transparentSecretKey: String,
transparentAccountPrivateKey: String,
memo: String
): Flow<PendingTransaction> = flow {
twig("Initializing shielding transaction")
val tAddr =
DerivationTool.deriveTransparentAddressFromPrivateKey(transparentSecretKey, network)
DerivationTool.deriveTransparentAddressFromAccountPrivateKey(transparentAccountPrivateKey, network)
val tBalance = processor.getUtxoCacheBalance(tAddr)
val zAddr = getAddress(0)
// Emit the placeholder transaction, then switch to monitoring the database
txManager.initSpend(tBalance.available, zAddr, memo, 0).let { placeHolderTx ->
emit(placeHolderTx)
txManager.encode(spendingKey, transparentSecretKey, placeHolderTx).let { encodedTx ->
txManager.encode(spendingKey, transparentAccountPrivateKey, placeHolderTx).let { encodedTx ->
// only submit if it wasn't cancelled. Otherwise cleanup, immediately for best UX.
if (encodedTx.isCancelled()) {
twig("[cleanup] this shielding tx has been cancelled so we will cleanup instead of submitting")

View File

@ -213,7 +213,7 @@ interface Synchronizer {
fun shieldFunds(
spendingKey: String,
transparentSecretKey: String,
transparentAccountPrivateKey: String,
memo: String = ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX
): Flow<PendingTransaction>

View File

@ -144,7 +144,7 @@ class PersistentTransactionManager(
override suspend fun encode(
spendingKey: String,
transparentSecretKey: String,
transparentAccountPrivateKey: String,
pendingTx: PendingTransaction
): PendingTransaction {
twig("managing the creation of a shielding transaction")
@ -153,7 +153,7 @@ class PersistentTransactionManager(
twig("beginning to encode shielding transaction with : $encoder")
val encodedTx = encoder.createShieldingTransaction(
spendingKey,
transparentSecretKey,
transparentAccountPrivateKey,
tx.memo
)
twig("successfully encoded shielding transaction!")

View File

@ -43,7 +43,7 @@ interface OutboundTransactionManager {
suspend fun encode(
spendingKey: String,
transparentSecretKey: String,
transparentAccountPrivateKey: String,
pendingTx: PendingTransaction
): PendingTransaction

View File

@ -51,10 +51,10 @@ internal class WalletTransactionEncoder(
override suspend fun createShieldingTransaction(
spendingKey: String,
transparentSecretKey: String,
transparentAccountPrivateKey: String,
memo: ByteArray?
): EncodedTransaction {
val transactionId = createShieldingSpend(spendingKey, transparentSecretKey, memo)
val transactionId = createShieldingSpend(spendingKey, transparentAccountPrivateKey, memo)
return repository.findEncodedTransactionById(transactionId)
?: throw TransactionEncoderException.TransactionNotFoundException(transactionId)
}
@ -136,7 +136,7 @@ internal class WalletTransactionEncoder(
private suspend fun createShieldingSpend(
spendingKey: String,
transparentSecretKey: String,
transparentAccountPrivateKey: String,
memo: ByteArray? = byteArrayOf()
): Long {
return twigTask("creating transaction to shield all UTXOs") {
@ -145,7 +145,7 @@ internal class WalletTransactionEncoder(
twig("params exist! attempting to shield...")
rustBackend.shieldToAddress(
spendingKey,
transparentSecretKey,
transparentAccountPrivateKey,
memo
)
} catch (t: Throwable) {

View File

@ -234,7 +234,7 @@ internal class RustBackend private constructor(
override suspend fun shieldToAddress(
extsk: String,
tsk: String,
xprv: String,
memo: ByteArray?
): Long {
twig("TMP: shieldToAddress with db path: $pathDataDb, ${memo?.size}")
@ -243,7 +243,7 @@ internal class RustBackend private constructor(
pathDataDb,
0,
extsk,
tsk,
xprv,
memo ?: ByteArray(0),
"$pathParamsDir/$SPEND_PARAM_FILE_NAME",
"$pathParamsDir/$OUTPUT_PARAM_FILE_NAME",
@ -491,11 +491,12 @@ internal class RustBackend private constructor(
): Long
@JvmStatic
@Suppress("LongParameterList")
private external fun shieldToAddress(
dbDataPath: String,
account: Int,
extsk: String,
tsk: String,
xprv: String,
memo: ByteArray,
spendParamsPath: String,
outputParamsPath: String,

View File

@ -117,16 +117,16 @@ internal interface RustBackendWelding {
network: ZcashNetwork
): String
suspend fun deriveTransparentAddressFromPrivateKey(
suspend fun deriveTransparentAddressFromAccountPrivateKey(
privateKey: String,
network: ZcashNetwork
network: ZcashNetwork,
index: Int = 0
): String
suspend fun deriveTransparentSecretKey(
suspend fun deriveTransparentAccountPrivateKey(
seed: ByteArray,
network: ZcashNetwork,
account: Int = 0,
index: Int = 0
account: Int = 0
): String
suspend fun deriveViewingKey(

View File

@ -87,12 +87,12 @@ class DerivationTool {
deriveTransparentAddressFromPubKey(transparentPublicKey, networkId = network.id)
}
override suspend fun deriveTransparentAddressFromPrivateKey(transparentPrivateKey: String, network: ZcashNetwork): String = withRustBackendLoaded {
deriveTransparentAddressFromPrivKey(transparentPrivateKey, networkId = network.id)
override suspend fun deriveTransparentAddressFromAccountPrivateKey(transparentPrivateKey: String, network: ZcashNetwork, index: Int): String = withRustBackendLoaded {
deriveTransparentAddressFromAccountPrivKey(transparentPrivateKey, index, networkId = network.id)
}
override suspend fun deriveTransparentSecretKey(seed: ByteArray, network: ZcashNetwork, account: Int, index: Int): String = withRustBackendLoaded {
deriveTransparentSecretKeyFromSeed(seed, account, index, networkId = network.id)
override suspend fun deriveTransparentAccountPrivateKey(seed: ByteArray, network: ZcashNetwork, account: Int): String = withRustBackendLoaded {
deriveTransparentAccountPrivKeyFromSeed(seed, account, networkId = network.id)
}
@Suppress("UnusedPrivateMember")
@ -148,9 +148,9 @@ class DerivationTool {
private external fun deriveTransparentAddressFromPubKey(pk: String, networkId: Int): String
@JvmStatic
private external fun deriveTransparentAddressFromPrivKey(sk: String, networkId: Int): String
private external fun deriveTransparentAddressFromAccountPrivKey(sk: String, index: Int, networkId: Int): String
@JvmStatic
private external fun deriveTransparentSecretKeyFromSeed(seed: ByteArray, account: Int, index: Int, networkId: Int): String
private external fun deriveTransparentAccountPrivKeyFromSeed(seed: ByteArray, account: Int, networkId: Int): String
}
}

View File

@ -9,13 +9,14 @@ use std::str::FromStr;
use android_logger::Config;
use failure::format_err;
use hdwallet::traits::{Deserialize, Serialize};
use jni::{
objects::{JClass, JString},
sys::{jboolean, jbyteArray, jint, jlong, jobjectArray, jstring, JNI_FALSE, JNI_TRUE},
JNIEnv,
};
use log::Level;
use secp256k1::key::{PublicKey, SecretKey};
use secp256k1::key::PublicKey;
use zcash_client_backend::data_api::wallet::{shield_funds, ANCHOR_OFFSET};
use zcash_client_backend::{
address::RecipientAddress,
@ -31,7 +32,7 @@ use zcash_client_backend::{
},
keys::{
derive_secret_key_from_seed, derive_transparent_address_from_public_key,
derive_transparent_address_from_secret_key, spending_key, Wif,
derive_transparent_address_from_secret_key, spending_key,
},
wallet::{AccountId, OvkPolicy, WalletTransparentOutput},
};
@ -869,31 +870,30 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_scanBlockBa
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveTransparentSecretKeyFromSeed(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveTransparentAccountPrivKeyFromSeed(
env: JNIEnv<'_>,
_: JClass<'_>,
seed: jbyteArray,
account: jint,
index: jint,
network_id: jint,
) -> jstring {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let seed = env.convert_byte_array(seed).unwrap();
let account = if account >= 0 {
account as u32
AccountId(account as u32)
} else {
return Err(format_err!("account argument must be positive"));
};
let index = if index >= 0 {
index as u32
} else {
return Err(format_err!("index argument must be positive"));
// Derive the BIP 32 extended privkey.
let xprv = match utils::p2pkh_xprv(&network, &seed, account) {
Ok(xprv) => xprv,
Err(e) => return Err(format_err!("Invalid transparent account privkey: {:?}", e)),
};
let sk = derive_secret_key_from_seed(&network, &seed, AccountId(account), index).unwrap();
let sk_wif = Wif::from_secret_key(&sk, true);
// Encode using the BIP 32 xprv serialization format.
let xprv_str: String = xprv.serialize();
let output = env
.new_string(sk_wif.0)
.new_string(xprv_str)
.expect("Couldn't create Java string for private key!");
Ok(output.into_inner())
});
@ -934,17 +934,31 @@ 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_deriveTransparentAddressFromPrivKey(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveTransparentAddressFromAccountPrivKey(
env: JNIEnv<'_>,
_: JClass<'_>,
secret_key: JString<'_>,
index: jint,
network_id: jint,
) -> jstring {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let tsk_wif = utils::java_string_to_rust(&env, secret_key);
let sk: SecretKey = (&Wif(tsk_wif)).try_into().expect("invalid private key WIF");
let taddr = derive_transparent_address_from_secret_key(&sk).encode(&network);
let index = if index >= 0 {
index as u32
} else {
return Err(format_err!("index argument must be positive"));
};
let xprv_str = utils::java_string_to_rust(&env, secret_key);
let xprv = match hdwallet_bitcoin::PrivKey::deserialize(xprv_str) {
Ok(xprv) => xprv,
Err(e) => return Err(format_err!("Invalid transparent extended privkey: {:?}", e)),
};
let tfvk = hdwallet::ExtendedPubKey::from_private_key(&xprv.extended_key);
let taddr = match utils::p2pkh_addr_with_u32_index(tfvk, index) {
Ok(taddr) => taddr,
Err(e) => return Err(format_err!("Couldn't derive transparent address: {:?}", e)),
};
let taddr = taddr.encode(&network);
let output = env.new_string(taddr).expect("Couldn't create Java string!");
@ -1087,7 +1101,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_shieldToAdd
db_data: JString<'_>,
account: jint,
extsk: JString<'_>,
tsk: JString<'_>,
xprv: JString<'_>,
memo: jbyteArray,
spend_params: JString<'_>,
output_params: JString<'_>,
@ -1103,7 +1117,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_shieldToAdd
return Err(format_err!("account argument {} must be positive", account));
};
let extsk = utils::java_string_to_rust(&env, extsk);
let tsk_wif = utils::java_string_to_rust(&env, tsk);
let xprv_str = utils::java_string_to_rust(&env, xprv);
let memo_bytes = env.convert_byte_array(memo).unwrap();
let spend_params = utils::java_string_to_rust(&env, spend_params);
let output_params = utils::java_string_to_rust(&env, output_params);
@ -1119,7 +1133,19 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_shieldToAdd
}
};
let sk: SecretKey = (&Wif(tsk_wif)).try_into().expect("invalid private key WIF");
let xprv = match hdwallet_bitcoin::PrivKey::deserialize(xprv_str) {
Ok(xprv) => xprv,
Err(e) => return Err(format_err!("Invalid transparent extended privkey: {:?}", e)),
};
let sk = match utils::p2pkh_secret_key(xprv.extended_key, 0) {
Ok(sk) => sk,
Err(e) => {
return Err(format_err!(
"Transparent extended privkey can't derive spending key 0: {:?}",
e
))
}
};
let memo = Memo::from_bytes(&memo_bytes).unwrap();

View File

@ -1,6 +1,6 @@
use hdwallet::{
traits::{Deserialize, Serialize},
ExtendedPrivKey, ExtendedPubKey, KeyIndex,
ExtendedPrivKey, ExtendedPubKey, KeyChain, KeyIndex,
};
use jni::{
descriptors::Desc,
@ -9,6 +9,7 @@ use jni::{
sys::{jobjectArray, jsize},
JNIEnv,
};
use secp256k1::SecretKey;
use zcash_client_backend::{keys::derive_transparent_address_from_public_key, wallet::AccountId};
use zcash_primitives::{
consensus,
@ -90,6 +91,22 @@ where
// outer
//}
pub(crate) fn p2pkh_xprv<P: consensus::Parameters>(
params: &P,
seed: &[u8],
account: AccountId,
) -> Result<hdwallet_bitcoin::PrivKey, hdwallet::error::Error> {
let master_key = ExtendedPrivKey::with_seed(&seed)?;
let key_chain = hdwallet::DefaultKeyChain::new(master_key);
let chain_path = format!("m/44H/{}H/{}H", params.coin_type(), account.0).into();
let (extended_key, derivation) = key_chain.derive_private_key(chain_path)?;
Ok(hdwallet_bitcoin::PrivKey {
network: hdwallet_bitcoin::Network::MainNet,
derivation,
extended_key,
})
}
pub(crate) fn p2pkh_full_viewing_key<P: consensus::Parameters>(
params: &P,
seed: &[u8],
@ -106,16 +123,30 @@ pub(crate) fn p2pkh_full_viewing_key<P: consensus::Parameters>(
pub(crate) fn p2pkh_addr(
fvk: ExtendedPubKey,
index: DiversifierIndex,
) -> Result<TransparentAddress, hdwallet::error::Error> {
p2pkh_addr_with_u32_index(fvk, u32::from_le_bytes(index.0[..4].try_into().unwrap()))
}
pub(crate) fn p2pkh_addr_with_u32_index(
fvk: ExtendedPubKey,
index: u32,
) -> Result<TransparentAddress, hdwallet::error::Error> {
let pubkey = fvk
.derive_public_key(KeyIndex::Normal(0))?
.derive_public_key(KeyIndex::Normal(u32::from_le_bytes(
index.0[..4].try_into().unwrap(),
)))?
.derive_public_key(KeyIndex::Normal(index))?
.public_key;
Ok(derive_transparent_address_from_public_key(&pubkey))
}
pub(crate) fn p2pkh_secret_key(
tsk: ExtendedPrivKey,
index: u32,
) -> Result<SecretKey, hdwallet::error::Error> {
tsk.derive_private_key(KeyIndex::Normal(0))?
.derive_private_key(KeyIndex::Normal(index))
.map(|k| k.private_key)
}
/// This is temporary, and will be replaced by `zcash_address::unified::Ufvk`.
pub(crate) fn fake_ufvk_encode(p2pkh: &ExtendedPubKey, sapling: &ExtendedFullViewingKey) -> String {
let mut ufvk = p2pkh.serialize();
@ -148,16 +179,39 @@ pub(crate) fn fake_ua_encode(p2pkh: &TransparentAddress, sapling: &PaymentAddres
}
// This is temporary, and will be replaced by `zcash_address::unified::Address`.
//pub(crate) fn fake_ua_decode(encoding: &str) -> Option<(TransparentAddress, PaymentAddress)> {
// encoding
// .strip_prefix("DONOTUSEUA")
// .and_then(|data| hex::decode(data).ok())
// .and_then(|data| {
// PaymentAddress::from_bytes(&data[20..].try_into().unwrap()).map(|pa| {
// (
// TransparentAddress::PublicKey(data[..20].try_into().unwrap()),
// pa,
// )
// })
// })
//}
// pub(crate) fn fake_ua_decode(encoding: &str) -> Option<(TransparentAddress, PaymentAddress)> {
// encoding
// .strip_prefix("DONOTUSEUA")
// .and_then(|data| hex::decode(data).ok())
// .and_then(|data| {
// PaymentAddress::from_bytes(&data[20..].try_into().unwrap()).map(|pa| {
// (
// TransparentAddress::PublicKey(data[..20].try_into().unwrap()),
// pa,
// )
// })
// })
// }
#[cfg(test)]
mod tests {
use hdwallet::ExtendedPubKey;
use zcash_client_backend::wallet::AccountId;
use zcash_primitives::consensus::MAIN_NETWORK;
use super::{p2pkh_full_viewing_key, p2pkh_xprv};
#[test]
fn test_transparent_xprv() {
let params = MAIN_NETWORK;
let seed = [0; 32];
let account = AccountId(0);
assert_eq!(
ExtendedPubKey::from_private_key(
&p2pkh_xprv(&params, &seed, account).unwrap().extended_key
),
p2pkh_full_viewing_key(&params, &seed, account).unwrap(),
);
}
}