Replace `Synchronizer.getAddress()` with `Synchronizer.getCurrentAddress()`

The SDK now exposes UAs primarily, with additional methods for obtaining
corresponding Sapling and transparent addresses for legacy compatibility.

The `Account` DAO is also fixed to use the migrated `accounts` table format.

The demo app now shows the current UA and the legacy Sapling and transparent
addresses.

Closes zcash/zcash-android-wallet-sdk#677.
This commit is contained in:
Carter Jernigan 2022-10-06 13:40:49 -04:00 committed by Carter Jernigan
parent f69cacb9e6
commit 597cc43886
15 changed files with 192 additions and 69 deletions

View File

@ -5,6 +5,9 @@ Change Log
### Added ### Added
- `cash.z.ecc.android.sdk`: - `cash.z.ecc.android.sdk`:
- `Synchronizer.getCurrentAddress`
- `Synchronizer.getLegacySaplingAddress`
- `Synchronizer.getLegacyTransparentAddress`
- `Synchronizer.isValidUnifiedAddr` - `Synchronizer.isValidUnifiedAddr`
- `cash.z.ecc.android.sdk.model`: - `cash.z.ecc.android.sdk.model`:
- `FirstClassByteArray` - `FirstClassByteArray`
@ -37,6 +40,10 @@ Change Log
all transparent secret keys within an account) instead of a transparent secret key. all transparent secret keys within an account) instead of a transparent secret key.
### Removed ### Removed
- `cash.z.ecc.android.sdk`:
- `Synchronizer.getAddress` (use `Synchronizer.getCurrentAddress` instead).
- `Synchronizer.getShieldedAddress` (use `Synchronizer.getLegacySaplingAddress` instead).
- `Synchronizer.getTransparentAddress` (use `Synchronizer.getLegacyTransparentAddress` instead).
- `cash.z.ecc.android.sdk.type.UnifiedViewingKey` - `cash.z.ecc.android.sdk.type.UnifiedViewingKey`
- This type had a bug where the `extpub` field actually was storing a plain transparent - This type had a bug where the `extpub` field actually was storing a plain transparent
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

View File

@ -76,7 +76,7 @@ class SampleCodeTest {
// /////////////////////////////////////////////////// // ///////////////////////////////////////////////////
// Get Address // Get Address
@Test fun getAddress() = runBlocking { @Test fun getAddress() = runBlocking {
val address = synchronizer.getAddress() val address = synchronizer.getCurrentAddress()
assertFalse(address.isBlank()) assertFalse(address.isBlank())
log("Address: $address") log("Address: $address")
} }

View File

@ -5,11 +5,15 @@ import android.view.LayoutInflater
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.bip39.Mnemonics import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetAddressBinding import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetAddressBinding
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
import cash.z.ecc.android.sdk.demoapp.util.fromResources import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
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.tool.DerivationTool import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.UnifiedFullViewingKey import cash.z.ecc.android.sdk.type.UnifiedFullViewingKey
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -21,6 +25,7 @@ import kotlinx.coroutines.runBlocking
*/ */
class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() { class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
private lateinit var synchronizer: Synchronizer
private lateinit var viewingKey: UnifiedFullViewingKey private lateinit var viewingKey: UnifiedFullViewingKey
private lateinit var seed: ByteArray private lateinit var seed: ByteArray
@ -36,28 +41,46 @@ class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
// have the seed stored // have the seed stored
seed = Mnemonics.MnemonicCode(seedPhrase).toSeed() seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
// the derivation tool can be used for generating keys and addresses // converting seed into viewingKey
viewingKey = runBlocking { viewingKey = runBlocking {
DerivationTool.deriveUnifiedFullViewingKeys( DerivationTool.deriveUnifiedFullViewingKeys(
seed, seed,
ZcashNetwork.fromResources(requireApplicationContext()) ZcashNetwork.fromResources(requireApplicationContext())
).first() ).first()
} }
}
private fun displayAddress() { // using the ViewingKey to initialize
// a full fledged app would just get the address from the synchronizer runBlocking {
viewLifecycleOwner.lifecycleScope.launchWhenStarted { Initializer.new(requireApplicationContext(), null) {
val uaddress = DerivationTool.deriveUnifiedAddress( val network = ZcashNetwork.fromResources(requireApplicationContext())
seed, it.newWallet(
ZcashNetwork.fromResources(requireApplicationContext()) viewingKey,
) network = network,
binding.textInfo.text = "address:\n$uaddress" lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
)
}
}.let { initializer ->
synchronizer = Synchronizer.newBlocking(initializer)
} }
} }
// TODO [#677]: Show an example with the synchronizer private fun displayAddress() {
// TODO [#677]: https://github.com/zcash/zcash-android-wallet-sdk/issues/677 viewLifecycleOwner.lifecycleScope.launchWhenStarted {
val uaddress = synchronizer.getCurrentAddress()
val sapling = synchronizer.getLegacySaplingAddress()
val transparent = synchronizer.getLegacyTransparentAddress()
binding.textInfo.text = """
Unified Address:
$uaddress
Legacy Sapling:
$sapling
Legacy transparent:
$transparent
""".trimIndent()
}
}
// //
// Android Lifecycle overrides // Android Lifecycle overrides

1
sdk-lib/Cargo.lock generated
View File

@ -2028,6 +2028,7 @@ dependencies = [
"schemer", "schemer",
"secp256k1", "secp256k1",
"secrecy", "secrecy",
"zcash_address",
"zcash_client_backend", "zcash_client_backend",
"zcash_client_sqlite", "zcash_client_sqlite",
"zcash_primitives", "zcash_primitives",

View File

@ -21,6 +21,7 @@ log-panics = "2.0.0"
schemer = "0.2" schemer = "0.2"
secp256k1 = "0.21" secp256k1 = "0.21"
secrecy = "0.8" secrecy = "0.8"
zcash_address = "0.1"
zcash_client_backend = { version = "0.5", features = ["transparent-inputs", "unstable"] } zcash_client_backend = { version = "0.5", features = ["transparent-inputs", "unstable"] }
zcash_client_sqlite = { version = "0.3", features = ["transparent-inputs", "unstable"] } zcash_client_sqlite = { version = "0.3", features = ["transparent-inputs", "unstable"] }
zcash_primitives = "0.7" zcash_primitives = "0.7"
@ -30,6 +31,7 @@ zcash_proofs = "0.7"
[patch.crates-io] [patch.crates-io]
group = { git = "https://github.com/zkcrypto/group.git", rev = "a7f3ceb2373e9fe536996f7b4d55c797f3e667f0" } group = { git = "https://github.com/zkcrypto/group.git", rev = "a7f3ceb2373e9fe536996f7b4d55c797f3e667f0" }
orchard = { git = 'https://github.com/zcash/orchard.git', rev='f206b3f5d4e31bba75d03d9d03d5fa25825a9384' } orchard = { git = 'https://github.com/zcash/orchard.git', rev='f206b3f5d4e31bba75d03d9d03d5fa25825a9384' }
zcash_address = { git = 'https://github.com/zcash/librustzcash.git', rev='774ffadf5a0120a74d70d281974d079ccd58c600' }
zcash_client_backend = { git = 'https://github.com/zcash/librustzcash.git', rev='774ffadf5a0120a74d70d281974d079ccd58c600' } zcash_client_backend = { git = 'https://github.com/zcash/librustzcash.git', rev='774ffadf5a0120a74d70d281974d079ccd58c600' }
zcash_client_sqlite = { git = 'https://github.com/zcash/librustzcash.git', rev='774ffadf5a0120a74d70d281974d079ccd58c600' } zcash_client_sqlite = { git = 'https://github.com/zcash/librustzcash.git', rev='774ffadf5a0120a74d70d281974d079ccd58c600' }
zcash_note_encryption = { git = 'https://github.com/zcash/librustzcash.git', rev='774ffadf5a0120a74d70d281974d079ccd58c600' } zcash_note_encryption = { git = 'https://github.com/zcash/librustzcash.git', rev='774ffadf5a0120a74d70d281974d079ccd58c600' }
@ -38,6 +40,7 @@ zcash_proofs = { git = 'https://github.com/zcash/librustzcash.git', rev='774ffad
## Uncomment this to test librustzcash changes locally ## Uncomment this to test librustzcash changes locally
#[patch.crates-io] #[patch.crates-io]
#zcash_address = { path = '../../clones/librustzcash/components/zcash_address' }
#zcash_client_backend = { path = '../../clones/librustzcash/zcash_client_backend' } #zcash_client_backend = { path = '../../clones/librustzcash/zcash_client_backend' }
#zcash_client_sqlite = { path = '../../clones/librustzcash/zcash_client_sqlite' } #zcash_client_sqlite = { path = '../../clones/librustzcash/zcash_client_sqlite' }
#zcash_primitives = { path = '../../clones/librustzcash/zcash_primitives' } #zcash_primitives = { path = '../../clones/librustzcash/zcash_primitives' }
@ -45,6 +48,7 @@ zcash_proofs = { git = 'https://github.com/zcash/librustzcash.git', rev='774ffad
## Uncomment this to test someone else's librustzcash changes in a branch ## Uncomment this to test someone else's librustzcash changes in a branch
#[patch.crates-io] #[patch.crates-io]
#zcash_address = { git = "https://github.com/zcash/librustzcash", branch = "branch-name" }
#zcash_client_backend = { git = "https://github.com/zcash/librustzcash", branch = "branch-name" } #zcash_client_backend = { git = "https://github.com/zcash/librustzcash", branch = "branch-name" }
#zcash_client_sqlite = { git = "https://github.com/zcash/librustzcash", branch = "branch-name" } #zcash_client_sqlite = { git = "https://github.com/zcash/librustzcash", branch = "branch-name" }
#zcash_primitives = { git = "https://github.com/zcash/librustzcash", branch = "branch-name" } #zcash_primitives = { git = "https://github.com/zcash/librustzcash", branch = "branch-name" }

View File

@ -357,7 +357,7 @@ class SdkSynchronizer internal constructor(
suspend fun refreshUtxos() { suspend fun refreshUtxos() {
twig("refreshing utxos", -1) twig("refreshing utxos", -1)
refreshUtxos(getTransparentAddress()) refreshUtxos(getLegacyTransparentAddress())
} }
/** /**
@ -379,7 +379,7 @@ class SdkSynchronizer internal constructor(
suspend fun refreshTransparentBalance() { suspend fun refreshTransparentBalance() {
twig("refreshing transparent balance") twig("refreshing transparent balance")
_transparentBalances.value = processor.getUtxoCacheBalance(getTransparentAddress()) _transparentBalances.value = processor.getUtxoCacheBalance(getLegacyTransparentAddress())
} }
suspend fun isValidAddress(address: String): Boolean { suspend fun isValidAddress(address: String): Boolean {
@ -637,20 +637,22 @@ class SdkSynchronizer internal constructor(
override suspend fun cancelSpend(pendingId: Long) = txManager.cancel(pendingId) override suspend fun cancelSpend(pendingId: Long) = txManager.cancel(pendingId)
// TODO(str4d): Rename this to getCurrentAddress (and remove/add in changelog).
/** /**
* Returns the current Unified Address for this account. * Returns the current Unified Address for this account.
*/ */
override suspend fun getAddress(accountId: Int): String = getShieldedAddress(accountId) override suspend fun getCurrentAddress(accountId: Int): String =
processor.getCurrentAddress(accountId)
override suspend fun getShieldedAddress(accountId: Int): String = /**
processor.getShieldedAddress(accountId) * Returns the legacy Sapling address corresponding to the current Unified Address for this account.
*/
override suspend fun getLegacySaplingAddress(accountId: Int): String =
processor.getLegacySaplingAddress(accountId)
// TODO(str4d): Change this to do the right thing.
/** /**
* Returns the legacy transparent address corresponding to the current Unified Address for this account. * Returns the legacy transparent address corresponding to the current Unified Address for this account.
*/ */
override suspend fun getTransparentAddress(accountId: Int): String = override suspend fun getLegacyTransparentAddress(accountId: Int): String =
processor.getTransparentAddress(accountId) processor.getTransparentAddress(accountId)
override fun sendToAddress( override fun sendToAddress(
@ -692,7 +694,7 @@ class SdkSynchronizer internal constructor(
val tAddr = val tAddr =
DerivationTool.deriveTransparentAddressFromAccountPrivateKey(transparentAccountPrivateKey, network) DerivationTool.deriveTransparentAddressFromAccountPrivateKey(transparentAccountPrivateKey, network)
val tBalance = processor.getUtxoCacheBalance(tAddr) val tBalance = processor.getUtxoCacheBalance(tAddr)
val zAddr = getAddress(0) val zAddr = getCurrentAddress(0)
// Emit the placeholder transaction, then switch to monitoring the database // Emit the placeholder transaction, then switch to monitoring the database
txManager.initSpend(tBalance.available, zAddr, memo, 0).let { placeHolderTx -> txManager.initSpend(tBalance.available, zAddr, memo, 0).let { placeHolderTx ->

View File

@ -5,7 +5,6 @@ import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
import cash.z.ecc.android.sdk.db.entity.PendingTransaction import cash.z.ecc.android.sdk.db.entity.PendingTransaction
import cash.z.ecc.android.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
@ -188,35 +187,34 @@ interface Synchronizer {
// suspend fun createAccount(seed: ByteArray): UnifiedSpendingKey // suspend fun createAccount(seed: ByteArray): UnifiedSpendingKey
/** /**
* Gets the shielded address for the given account. This is syntactic sugar for * Gets the current unified address for the given account.
* [getShieldedAddress] because we use z-addrs by default.
* *
* @param accountId the optional accountId whose address is of interest. By default, the first * @param accountId the optional accountId whose address is of interest. By default, the first
* account is used. * account is used.
* *
* @return the shielded address for the given account. * @return the current unified address for the given account.
*/ */
suspend fun getAddress(accountId: Int = 0) = getShieldedAddress(accountId) suspend fun getCurrentAddress(accountId: Int = 0): String
/** /**
* Gets the shielded address for the given account. * Gets the legacy Sapling address corresponding to the current unified address for the given account.
* *
* @param accountId the optional accountId whose address is of interest. By default, the first * @param accountId the optional accountId whose address is of interest. By default, the first
* account is used. * account is used.
* *
* @return the shielded address for the given account. * @return a legacy Sapling address for the given account.
*/ */
suspend fun getShieldedAddress(accountId: Int = 0): String suspend fun getLegacySaplingAddress(accountId: Int = 0): String
/** /**
* Gets the transparent address for the given account. * Gets the legacy transparent address corresponding to the current unified address for the given account.
* *
* @param accountId the optional accountId whose address is of interest. By default, the first * @param accountId the optional accountId whose address is of interest. By default, the first
* account is used. * account is used.
* *
* @return the address for the given account. * @return a legacy transparent address for the given account.
*/ */
suspend fun getTransparentAddress(accountId: Int = 0): String suspend fun getLegacyTransparentAddress(accountId: Int = 0): String
/** /**
* Sends zatoshi. * Sends zatoshi.

View File

@ -1023,17 +1023,34 @@ class CompactBlockProcessor internal constructor(
rustBackend.createAccount(seed) rustBackend.createAccount(seed)
/** /**
* Get address corresponding to the given account for this wallet. * Get the current unified address for the given wallet account.
* *
* @return the address of this wallet. * @return the current unified address of this account.
*/ */
suspend fun getShieldedAddress(accountId: Int = 0) = suspend fun getCurrentAddress(accountId: Int = 0) =
repository.getAccount(accountId)?.rawShieldedAddress rustBackend.getCurrentAddress(accountId)
?: throw InitializerException.MissingAddressException("shielded")
/**
* Get the legacy Sapling address corresponding to the current unified address for the given wallet account.
*
* @return a Sapling address.
*/
suspend fun getLegacySaplingAddress(accountId: Int = 0) =
rustBackend.getSaplingReceiver(
rustBackend.getCurrentAddress(accountId)
)
?: throw InitializerException.MissingAddressException("legacy Sapling")
/**
* Get the legacy transparent address corresponding to the current unified address for the given wallet account.
*
* @return a transparent address.
*/
suspend fun getTransparentAddress(accountId: Int = 0) = suspend fun getTransparentAddress(accountId: Int = 0) =
repository.getAccount(accountId)?.rawTransparentAddress rustBackend.getTransparentReceiver(
?: throw InitializerException.MissingAddressException("transparent") rustBackend.getCurrentAddress(accountId)
)
?: throw InitializerException.MissingAddressException("legacy transparent")
/** /**
* Calculates the latest balance info. Defaults to the first account. * Calculates the latest balance info. Defaults to the first account.

View File

@ -12,10 +12,5 @@ data class Account(
val account: Int? = 0, val account: Int? = 0,
@ColumnInfo(name = "ufvk") @ColumnInfo(name = "ufvk")
val unifiedFullViewingKey: String? = "", val unifiedFullViewingKey: String? = ""
val address: String? = "",
@ColumnInfo(name = "transparent_address")
val transparentAddress: String? = ""
) )

View File

@ -231,17 +231,6 @@ interface SentDao {
interface AccountDao { interface AccountDao {
@Query("SELECT COUNT(account) FROM accounts") @Query("SELECT COUNT(account) FROM accounts")
suspend fun count(): Int suspend fun count(): Int
@Query(
"""
SELECT account AS accountId,
transparent_address AS rawTransparentAddress,
address AS rawShieldedAddress
FROM accounts
WHERE account = :id
"""
)
suspend fun findAccountById(id: Int): UnifiedAddressAccount?
} }
/** /**

View File

@ -94,8 +94,6 @@ internal class PagedTransactionRepository private constructor(
override suspend fun count() = transactions.count() override suspend fun count() = transactions.count()
override suspend fun getAccount(accountId: Int) = accounts.findAccountById(accountId)
override suspend fun getAccountCount() = accounts.count() override suspend fun getAccountCount() = accounts.count()
/** /**

View File

@ -85,8 +85,6 @@ interface TransactionRepository {
suspend fun count(): Int suspend fun count(): Int
suspend fun getAccount(accountId: Int): UnifiedAddressAccount?
suspend fun getAccountCount(): Int suspend fun getAccountCount(): Int
// //

View File

@ -109,12 +109,9 @@ internal class RustBackend private constructor(
) )
} }
override suspend fun getTransparentAddress(account: Int, index: Int): String { override fun getTransparentReceiver(ua: String) = getTransparentReceiverForUnifiedAddress(ua)
throw NotImplementedError(
"TODO: implement this at the zcash_client_sqlite level. But for now, use " + override fun getSaplingReceiver(ua: String) = getSaplingReceiverForUnifiedAddress(ua)
"DerivationTool, instead to derive addresses from seeds"
)
}
override suspend fun getBalance(account: Int): Zatoshi { override suspend fun getBalance(account: Int): Zatoshi {
val longValue = withContext(SdkDispatchers.DATABASE_IO) { val longValue = withContext(SdkDispatchers.DATABASE_IO) {
@ -426,6 +423,12 @@ internal class RustBackend private constructor(
networkId: Int networkId: Int
): String ): String
@JvmStatic
private external fun getTransparentReceiverForUnifiedAddress(ua: String): String?
@JvmStatic
private external fun getSaplingReceiverForUnifiedAddress(ua: String): String?
@JvmStatic @JvmStatic
private external fun isValidShieldedAddress(addr: String, networkId: Int): Boolean private external fun isValidShieldedAddress(addr: String, networkId: Int): Boolean

View File

@ -53,7 +53,9 @@ internal interface RustBackendWelding {
suspend fun getCurrentAddress(account: Int = 0): String suspend fun getCurrentAddress(account: Int = 0): String
suspend fun getTransparentAddress(account: Int = 0, index: Int = 0): String fun getTransparentReceiver(ua: String): String?
fun getSaplingReceiver(ua: String): String?
suspend fun getBalance(account: Int = 0): Zatoshi suspend fun getBalance(account: Int = 0): Zatoshi

View File

@ -21,9 +21,10 @@ use log::Level;
use schemer::MigratorError; use schemer::MigratorError;
use secp256k1::PublicKey; use secp256k1::PublicKey;
use secrecy::SecretVec; use secrecy::SecretVec;
use zcash_address::{ToAddress, ZcashAddress};
use zcash_client_backend::keys::UnifiedSpendingKey; use zcash_client_backend::keys::UnifiedSpendingKey;
use zcash_client_backend::{ use zcash_client_backend::{
address::RecipientAddress, address::{RecipientAddress, UnifiedAddress},
data_api::{ data_api::{
chain::{scan_cached_blocks, validate_chain}, chain::{scan_cached_blocks, validate_chain},
error::Error, error::Error,
@ -490,6 +491,91 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getCurrentA
unwrap_exc_or(&env, res, ptr::null_mut()) unwrap_exc_or(&env, res, ptr::null_mut())
} }
struct UnifiedAddressParser(UnifiedAddress);
impl zcash_address::TryFromRawAddress for UnifiedAddressParser {
type Error = failure::Error;
fn try_from_raw_unified(
data: zcash_address::unified::Address,
) -> Result<Self, zcash_address::ConversionError<Self::Error>> {
data.try_into()
.map(UnifiedAddressParser)
.map_err(|e| format_err!("Invalid Unified Address: {}", e).into())
}
}
/// Returns the transparent receiver within the given Unified Address, if any.
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getTransparentReceiverForUnifiedAddress(
env: JNIEnv<'_>,
_: JClass<'_>,
ua: JString<'_>,
) -> jstring {
let res = panic::catch_unwind(|| {
let ua_str = utils::java_string_to_rust(&env, ua);
let (network, ua) = match ZcashAddress::try_from_encoded(&ua_str) {
Ok(addr) => addr
.convert::<(_, UnifiedAddressParser)>()
.map_err(|e| format_err!("Not a Unified Address: {}", e)),
Err(e) => return Err(format_err!("Invalid Zcash address: {}", e)),
}?;
if let Some(taddr) = ua.0.transparent() {
let taddr = match taddr {
TransparentAddress::PublicKey(data) => {
ZcashAddress::from_transparent_p2pkh(network, *data)
}
TransparentAddress::Script(data) => {
ZcashAddress::from_transparent_p2sh(network, *data)
}
};
let output = env
.new_string(taddr.encode())
.expect("Couldn't create Java string!");
Ok(output.into_inner())
} else {
Err(format_err!(
"Unified Address doesn't contain a transparent receiver"
))
}
});
unwrap_exc_or(&env, res, ptr::null_mut())
}
/// Returns the Sapling receiver within the given Unified Address, if any.
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getSaplingReceiverForUnifiedAddress(
env: JNIEnv<'_>,
_: JClass<'_>,
ua: JString<'_>,
) -> jstring {
let res = panic::catch_unwind(|| {
let ua_str = utils::java_string_to_rust(&env, ua);
let (network, ua) = match ZcashAddress::try_from_encoded(&ua_str) {
Ok(addr) => addr
.convert::<(_, UnifiedAddressParser)>()
.map_err(|e| format_err!("Not a Unified Address: {}", e)),
Err(e) => return Err(format_err!("Invalid Zcash address: {}", e)),
}?;
if let Some(addr) = ua.0.sapling() {
let output = env
.new_string(ZcashAddress::from_sapling(network, addr.to_bytes()).encode())
.expect("Couldn't create Java string!");
Ok(output.into_inner())
} else {
Err(format_err!(
"Unified Address doesn't contain a Sapling receiver"
))
}
});
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_isValidShieldedAddress( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidShieldedAddress(
env: JNIEnv<'_>, env: JNIEnv<'_>,