Remove `DerivationTool` APIs for deriving transparent keys and addresses

Users should use the UnifiedAddress etc. APIs instead.
This commit is contained in:
Jack Grigg 2022-11-04 16:51:23 +13:00 committed by Carter Jernigan
parent 5e82f8cf07
commit 5ad0efcbea
9 changed files with 13 additions and 305 deletions

View File

@ -20,8 +20,6 @@ Change Log
- `cash.z.ecc.android.sdk.tool`: - `cash.z.ecc.android.sdk.tool`:
- `DerivationTool.deriveUnifiedSpendingKey` - `DerivationTool.deriveUnifiedSpendingKey`
- `DerivationTool.deriveUnifiedFullViewingKey` - `DerivationTool.deriveUnifiedFullViewingKey`
- `DerivationTool.deriveTransparentAccountPrivateKey`
- `DerivationTool.deriveTransparentAddressFromAccountPrivateKey`
- `DerivationTool.deriveUnifiedAddress` - `DerivationTool.deriveUnifiedAddress`
- `DerivationTool.deriveUnifiedFullViewingKeys` - `DerivationTool.deriveUnifiedFullViewingKeys`
- `DerivationTool.validateUnifiedFullViewingKey` - `DerivationTool.validateUnifiedFullViewingKey`
@ -60,18 +58,13 @@ Change Log
public key, and not the extended public key as intended. This made it incompatible public key, and not the extended public key as intended. This made it incompatible
with ZIP 316. with ZIP 316.
- `cash.z.ecc.android.sdk.tool`: - `cash.z.ecc.android.sdk.tool`:
- `DerivationTool.deriveSpendingKeys` (use - `DerivationTool.deriveSpendingKeys` (use `DerivationTool.deriveUnifiedSpendingKey` instead).
`DerivationTool.deriveUnifiedSpendingKey` instead). - `DerivationTool.deriveViewingKey` (use `DerivationTool.deriveUnifiedFullViewingKey` instead).
- `DerivationTool.deriveViewingKey` (use - `DerivationTool.deriveTransparentAddress` (use `Synchronizer.getLegacyTransparentAddress` instead).
- `DerivationTool.deriveUnifiedFullViewingKey` instead). - `DerivationTool.deriveTransparentAddressFromPrivateKey` (use `Synchronizer.getLegacyTransparentAddress` instead).
- `DerivationTool.deriveTransparentAddressFromPrivateKey` (use - `DerivationTool.deriveTransparentAddressFromPublicKey` (use `Synchronizer.getLegacyTransparentAddress` instead).
`DerivationTool.deriveTransparentAddressFromAccountPrivateKey` instead). - `DerivationTool.deriveTransparentSecretKey` (use `DerivationTool.deriveUnifiedSpendingKey` instead).
- `DerivationTool.deriveTransparentSecretKey` (use
`DerivationTool.deriveTransparentAccountPrivateKey` instead).
- `DerivationTool.deriveShieldedAddress` - `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
Sapling FVK on the Kotlin side (unlike the previous `UnifiedViewingKey`).
- `DerivationTool.deriveUnifiedViewingKeys` - `DerivationTool.deriveUnifiedViewingKeys`
- `DerivationTool.validateUnifiedViewingKey` - `DerivationTool.validateUnifiedViewingKey`

View File

@ -65,8 +65,6 @@ class TestWallet(
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed() private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
private val shieldedSpendingKey = private val shieldedSpendingKey =
runBlocking { DerivationTool.deriveUnifiedSpendingKey(seed, network = network, Account.DEFAULT) } runBlocking { DerivationTool.deriveUnifiedSpendingKey(seed, network = network, Account.DEFAULT) }
private val transparentAccountPrivateKey =
runBlocking { DerivationTool.deriveTransparentAccountPrivateKey(seed, network = network, Account.DEFAULT) }
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking( val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(
context, context,
network, network,
@ -81,9 +79,9 @@ class TestWallet(
val available get() = synchronizer.saplingBalances.value?.available val available get() = synchronizer.saplingBalances.value?.available
val unifiedAddress = val unifiedAddress =
runBlocking { DerivationTool.deriveUnifiedAddress(seed, network = network, Account.DEFAULT) } runBlocking { synchronizer.getCurrentAddress(Account.DEFAULT) }
val transparentAddress = val transparentAddress =
runBlocking { DerivationTool.deriveTransparentAddress(seed, network = network, Account.DEFAULT) } runBlocking { synchronizer.getLegacyTransparentAddress(Account.DEFAULT) }
val birthdayHeight get() = synchronizer.latestBirthdayHeight val birthdayHeight get() = synchronizer.latestBirthdayHeight
val networkName get() = synchronizer.network.networkName val networkName get() = synchronizer.network.networkName
@ -139,7 +137,7 @@ class TestWallet(
twig("FOUND utxo balance of total: ${walletBalance.total} available: ${walletBalance.available}") twig("FOUND utxo balance of total: ${walletBalance.total} available: ${walletBalance.available}")
if (walletBalance.available.value > 0L) { if (walletBalance.available.value > 0L) {
synchronizer.shieldFunds(shieldedSpendingKey, transparentAccountPrivateKey) synchronizer.shieldFunds(shieldedSpendingKey)
.onCompletion { twig("done shielding funds") } .onCompletion { twig("done shielding funds") }
.catch { twig("Failed with $it") } .catch { twig("Failed with $it") }
.collect() .collect()

View File

@ -24,7 +24,6 @@ import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.TransactionOverview import cash.z.ecc.android.sdk.model.TransactionOverview
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.model.defaultForNetwork import cash.z.ecc.android.sdk.model.defaultForNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@ -173,14 +172,9 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
resetInBackground() resetInBackground()
val seed = Mnemonics.MnemonicCode(sharedViewModel.seedPhrase.value).toSeed()
viewLifecycleOwner.lifecycleScope.launchWhenStarted { viewLifecycleOwner.lifecycleScope.launchWhenStarted {
binding.inputAddress.setText( binding.inputAddress.setText(
DerivationTool.deriveTransparentAddress( synchronizer.getLegacyTransparentAddress(Account.DEFAULT)
seed,
ZcashNetwork.fromResources(requireApplicationContext()),
Account.DEFAULT
)
) )
} }
} }

View File

@ -7,7 +7,6 @@ import cash.z.ecc.android.sdk.annotation.MaintainedTest
import cash.z.ecc.android.sdk.annotation.TestPurpose import cash.z.ecc.android.sdk.annotation.TestPurpose
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -22,29 +21,6 @@ import org.junit.runners.Parameterized
@SmallTest @SmallTest
class TransparentTest(val expected: Expected, val network: ZcashNetwork) { class TransparentTest(val expected: Expected, val network: ZcashNetwork) {
@Test
fun deriveTransparentAccountPrivateKeyTest() = runBlocking {
assertEquals(
expected.tAccountPrivKey,
DerivationTool.deriveTransparentAccountPrivateKey(
SEED,
network = network,
Account.DEFAULT
)
)
}
@Test
fun deriveTransparentAddressTest() = runBlocking {
assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddress(SEED, network = network, Account.DEFAULT))
}
@Test
fun deriveTransparentAddressFromAccountPrivateKeyTest() = runBlocking {
val pk = DerivationTool.deriveTransparentAccountPrivateKey(SEED, network = network, Account.DEFAULT)
assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromAccountPrivateKey(pk, network = network))
}
@Test @Test
fun deriveUnifiedFullViewingKeysFromSeedTest() = runBlocking { fun deriveUnifiedFullViewingKeysFromSeedTest() = runBlocking {
val ufvks = DerivationTool.deriveUnifiedFullViewingKeys(SEED, network = network) val ufvks = DerivationTool.deriveUnifiedFullViewingKeys(SEED, network = network)

View File

@ -65,8 +65,6 @@ class TestWallet(
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed() private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
private val spendingKey = private val spendingKey =
runBlocking { DerivationTool.deriveUnifiedSpendingKey(seed, network = network, Account.DEFAULT) } runBlocking { DerivationTool.deriveUnifiedSpendingKey(seed, network = network, Account.DEFAULT) }
private val transparentAccountPrivateKey =
runBlocking { DerivationTool.deriveTransparentAccountPrivateKey(seed, network = network, Account.DEFAULT) }
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking( val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(
context, context,
network, network,
@ -79,9 +77,9 @@ class TestWallet(
val available get() = synchronizer.saplingBalances.value?.available val available get() = synchronizer.saplingBalances.value?.available
val unifiedAddress = val unifiedAddress =
runBlocking { DerivationTool.deriveUnifiedAddress(seed, network = network, Account.DEFAULT) } runBlocking { synchronizer.getCurrentAddress(Account.DEFAULT) }
val transparentAddress = val transparentAddress =
runBlocking { DerivationTool.deriveTransparentAddress(seed, network = network, Account.DEFAULT) } runBlocking { synchronizer.getLegacyTransparentAddress(Account.DEFAULT) }
val birthdayHeight get() = synchronizer.latestBirthdayHeight val birthdayHeight get() = synchronizer.latestBirthdayHeight
val networkName get() = synchronizer.network.networkName val networkName get() = synchronizer.network.networkName
@ -141,7 +139,7 @@ class TestWallet(
twig("FOUND utxo balance of total: ${walletBalance.total} available: ${walletBalance.available}") twig("FOUND utxo balance of total: ${walletBalance.total} available: ${walletBalance.available}")
if (walletBalance.available.value > 0L) { if (walletBalance.available.value > 0L) {
synchronizer.shieldFunds(spendingKey, transparentAccountPrivateKey) synchronizer.shieldFunds(spendingKey)
.onCompletion { twig("done shielding funds") } .onCompletion { twig("done shielding funds") }
.catch { twig("Failed with $it") } .catch { twig("Failed with $it") }
.collect() .collect()

View File

@ -114,30 +114,6 @@ internal interface RustBackendWelding {
account: Account account: Account
): UnifiedSpendingKey ): UnifiedSpendingKey
suspend fun deriveTransparentAddress(
seed: ByteArray,
network: ZcashNetwork,
account: Account,
index: Int = 0
): String
suspend fun deriveTransparentAddressFromPublicKey(
publicKey: String,
network: ZcashNetwork
): String
suspend fun deriveTransparentAddressFromAccountPrivateKey(
privateKey: String,
network: ZcashNetwork,
index: Int = 0
): String
suspend fun deriveTransparentAccountPrivateKey(
seed: ByteArray,
network: ZcashNetwork,
account: Account
): String
suspend fun deriveUnifiedFullViewingKey( suspend fun deriveUnifiedFullViewingKey(
usk: UnifiedSpendingKey, usk: UnifiedSpendingKey,
network: ZcashNetwork network: ZcashNetwork

View File

@ -97,41 +97,6 @@ class DerivationTool {
deriveUnifiedAddressFromViewingKey(viewingKey, networkId = network.id) deriveUnifiedAddressFromViewingKey(viewingKey, networkId = network.id)
} }
// WIP probably shouldn't be used just yet. Why?
// - because we need the private key associated with this seed and this function doesn't return it.
// - the underlying implementation needs to be split out into a few lower-level calls
override suspend fun deriveTransparentAddress(
seed: ByteArray,
network: ZcashNetwork,
account: Account,
index: Int
): String = withRustBackendLoaded {
deriveTransparentAddressFromSeed(seed, account.value, index, networkId = network.id)
}
override suspend fun deriveTransparentAddressFromPublicKey(
publicKey: String,
network: ZcashNetwork
): String = withRustBackendLoaded {
deriveTransparentAddressFromPubKey(pk = publicKey, networkId = network.id)
}
override suspend fun deriveTransparentAddressFromAccountPrivateKey(
privateKey: String,
network: ZcashNetwork,
index: Int
): String = withRustBackendLoaded {
deriveTransparentAddressFromAccountPrivKey(sk = privateKey, index = index, networkId = network.id)
}
override suspend fun deriveTransparentAccountPrivateKey(
seed: ByteArray,
network: ZcashNetwork,
account: Account
): String = withRustBackendLoaded {
deriveTransparentAccountPrivKeyFromSeed(seed, account.value, networkId = network.id)
}
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
fun validateUnifiedFullViewingKey(viewingKey: UnifiedFullViewingKey, networkId: Int = ZcashNetwork.Mainnet.id) { fun validateUnifiedFullViewingKey(viewingKey: UnifiedFullViewingKey, networkId: Int = ZcashNetwork.Mainnet.id) {
// TODO [#654] https://github.com/zcash/zcash-android-wallet-sdk/issues/654 // TODO [#654] https://github.com/zcash/zcash-android-wallet-sdk/issues/654
@ -177,26 +142,5 @@ class DerivationTool {
@JvmStatic @JvmStatic
private external fun deriveUnifiedAddressFromViewingKey(key: String, networkId: Int): String private external fun deriveUnifiedAddressFromViewingKey(key: String, networkId: Int): String
@JvmStatic
private external fun deriveTransparentAddressFromSeed(
seed: ByteArray,
account: Int,
index: Int,
networkId: Int
): String
@JvmStatic
private external fun deriveTransparentAddressFromPubKey(pk: String, networkId: Int): String
@JvmStatic
private external fun deriveTransparentAddressFromAccountPrivKey(sk: String, index: Int, networkId: Int): String
@JvmStatic
private external fun deriveTransparentAccountPrivKeyFromSeed(
seed: ByteArray,
account: Int,
networkId: Int
): String
} }
} }

View File

@ -6,11 +6,9 @@ use std::convert::{TryFrom, TryInto};
use std::panic; use std::panic;
use std::path::Path; use std::path::Path;
use std::ptr; use std::ptr;
use std::str::FromStr;
use android_logger::Config; use android_logger::Config;
use failure::format_err; use failure::format_err;
use hdwallet::traits::{Deserialize, Serialize};
use jni::objects::{JObject, JValue}; use jni::objects::{JObject, JValue};
use jni::{ use jni::{
objects::{JClass, JString}, objects::{JClass, JString},
@ -19,7 +17,6 @@ use jni::{
}; };
use log::Level; use log::Level;
use schemer::MigratorError; use schemer::MigratorError;
use secp256k1::PublicKey;
use secrecy::{ExposeSecret, SecretVec}; use secrecy::{ExposeSecret, SecretVec};
use zcash_address::{ToAddress, ZcashAddress}; use zcash_address::{ToAddress, ZcashAddress};
use zcash_client_backend::keys::{DecodingError, UnifiedSpendingKey}; use zcash_client_backend::keys::{DecodingError, UnifiedSpendingKey};
@ -45,8 +42,6 @@ use zcash_client_sqlite::{
BlockDb, NoteId, WalletDb, BlockDb, NoteId, WalletDb,
}; };
use zcash_primitives::consensus::Network::{MainNetwork, TestNetwork}; use zcash_primitives::consensus::Network::{MainNetwork, TestNetwork};
#[allow(deprecated)]
use zcash_primitives::legacy::keys::{pubkey_to_address, AccountPrivKey};
use zcash_primitives::{ use zcash_primitives::{
block::BlockHash, block::BlockHash,
consensus::{BlockHeight, BranchId, Network, Parameters}, consensus::{BlockHeight, BranchId, Network, Parameters},
@ -1019,139 +1014,6 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_putUtxo(
unwrap_exc_or(&env, res, JNI_FALSE) unwrap_exc_or(&env, res, JNI_FALSE)
} }
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveTransparentAccountPrivKeyFromSeed(
env: JNIEnv<'_>,
_: JClass<'_>,
seed: jbyteArray,
account: 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 {
AccountId::from(account as u32)
} else {
return Err(format_err!("account argument must be nonnegative"));
};
// Derive the USK to ensure it exists, and fetch its transparent component.
let usk = UnifiedSpendingKey::from_seed(&network, &seed, account)
.map_err(|e| format_err!("error generating unified spending key from seed: {:?}", e))?;
// Derive the corresponding BIP 32 extended privkey.
let xprv = utils::p2pkh_xprv(&network, &seed, account)
.expect("USK derivation should ensure this exists");
// Verify that we did derive the same privkey.
assert_eq!(
usk.transparent().to_account_pubkey().serialize(),
AccountPrivKey::from_extended_privkey(xprv.extended_key.clone())
.to_account_pubkey()
.serialize(),
);
// Encode using the BIP 32 xprv serialization format.
let xprv_str: String = xprv.serialize();
let output = env
.new_string(xprv_str)
.expect("Couldn't create Java string for private key!");
Ok(output.into_raw())
});
unwrap_exc_or(&env, res, ptr::null_mut())
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveTransparentAddressFromSeed(
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
} else {
return Err(format_err!("account argument must be nonnegative"));
};
let index = if index >= 0 {
index as u32
} else {
return Err(format_err!("index argument must be nonnegative"));
};
let tfvk = UnifiedSpendingKey::from_seed(&network, &seed, AccountId::from(account))
.map_err(|e| format_err!("error generating unified spending key from seed: {:?}", e))
.map(|usk| usk.transparent().to_account_pubkey())?;
let taddr = match utils::p2pkh_addr(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 for taddr!");
Ok(output.into_raw())
});
unwrap_exc_or(&env, res, ptr::null_mut())
}
#[no_mangle]
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 index = if index >= 0 {
index as u32
} else {
return Err(format_err!("index argument must be nonnegative"));
};
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 = AccountPrivKey::from_extended_privkey(xprv.extended_key).to_account_pubkey();
let taddr = match utils::p2pkh_addr(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!");
Ok(output.into_raw())
});
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<'_>,
network_id: jint,
) -> jstring {
#[allow(deprecated)]
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let public_key_str = utils::java_string_to_rust(&env, public_key);
let pk = PublicKey::from_str(&public_key_str)?;
let taddr = pubkey_to_address(&pk).encode(&network);
let output = env.new_string(taddr).expect("Couldn't create Java string!");
Ok(output.into_raw())
});
unwrap_exc_or(&env, res, ptr::null_mut())
}
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_decryptAndStoreTransaction( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_decryptAndStoreTransaction(
env: JNIEnv<'_>, env: JNIEnv<'_>,

View File

@ -1,4 +1,3 @@
use hdwallet::{ExtendedPrivKey, KeyChain};
use jni::{ use jni::{
descriptors::Desc, descriptors::Desc,
errors::Result as JNIResult, errors::Result as JNIResult,
@ -6,14 +5,6 @@ use jni::{
sys::{jobjectArray, jsize}, sys::{jobjectArray, jsize},
JNIEnv, JNIEnv,
}; };
use zcash_primitives::{
consensus,
legacy::{
keys::{AccountPubKey, IncomingViewingKey},
TransparentAddress,
},
zip32::AccountId,
};
use std::ops::Deref; use std::ops::Deref;
@ -87,27 +78,3 @@ where
// } // }
// outer // 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(), u32::from(account)).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_addr(
tfvk: AccountPubKey,
index: u32,
) -> Result<TransparentAddress, hdwallet::error::Error> {
tfvk.derive_external_ivk()
.and_then(|tivk| tivk.derive_address(index))
}