Use the binary USK format for transaction creation
Co-authored-by: Carter Jernigan <git@carterjernigan.com>
This commit is contained in:
parent
3b826f8f6a
commit
88bbd0afcb
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -13,6 +13,8 @@ Change Log
|
||||||
- `FirstClassByteArray`
|
- `FirstClassByteArray`
|
||||||
- `UnifiedSpendingKey`
|
- `UnifiedSpendingKey`
|
||||||
- `cash.z.ecc.android.sdk.tool`:
|
- `cash.z.ecc.android.sdk.tool`:
|
||||||
|
- `DerivationTool.deriveUnifiedSpendingKey`
|
||||||
|
- `DerivationTool.deriveUnifiedFullViewingKey`
|
||||||
- `DerivationTool.deriveTransparentAccountPrivateKey`
|
- `DerivationTool.deriveTransparentAccountPrivateKey`
|
||||||
- `DerivationTool.deriveTransparentAddressFromAccountPrivateKey`
|
- `DerivationTool.deriveTransparentAddressFromAccountPrivateKey`
|
||||||
- `DerivationTool.deriveUnifiedAddress`
|
- `DerivationTool.deriveUnifiedAddress`
|
||||||
|
@ -36,8 +38,11 @@ Change Log
|
||||||
- `Synchronizer.Companion.new` now takes a `seed` argument. A non-null value should be
|
- `Synchronizer.Companion.new` now takes a `seed` argument. A non-null value should be
|
||||||
provided if `Synchronizer.Companion.new` throws an error that a database migration
|
provided if `Synchronizer.Companion.new` throws an error that a database migration
|
||||||
requires the wallet seed.
|
requires the wallet seed.
|
||||||
- `Synchronizer.shieldFunds` now takes a transparent account private key (representing
|
- `Synchronizer.sendToAddress` now takes a `UnifiedSpendingKey` instead of an encoded
|
||||||
all transparent secret keys within an account) instead of a transparent secret key.
|
Sapling extended spending key, and the `fromAccountIndex` argument is now implicit in
|
||||||
|
the `UnifiedSpendingKey`.
|
||||||
|
- `Synchronizer.shieldFunds` now takes a `UnifiedSpendingKey` instead of separately
|
||||||
|
encoded Sapling and transparent keys.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- `cash.z.ecc.android.sdk`:
|
- `cash.z.ecc.android.sdk`:
|
||||||
|
@ -49,6 +54,10 @@ 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.deriveUnifiedSpendingKey` instead).
|
||||||
|
- `DerivationTool.deriveViewingKey` (use
|
||||||
|
- `DerivationTool.deriveUnifiedFullViewingKey` instead).
|
||||||
- `DerivationTool.deriveTransparentAddressFromPrivateKey` (use
|
- `DerivationTool.deriveTransparentAddressFromPrivateKey` (use
|
||||||
`DerivationTool.deriveTransparentAddressFromAccountPrivateKey` instead).
|
`DerivationTool.deriveTransparentAddressFromAccountPrivateKey` instead).
|
||||||
- `DerivationTool.deriveTransparentSecretKey` (use
|
- `DerivationTool.deriveTransparentSecretKey` (use
|
||||||
|
|
|
@ -43,14 +43,14 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
|
||||||
// demonstrate deriving spending keys for five accounts but only take the first one
|
// demonstrate deriving spending keys for five accounts but only take the first one
|
||||||
lifecycleScope.launchWhenStarted {
|
lifecycleScope.launchWhenStarted {
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
val spendingKey = DerivationTool.deriveSpendingKeys(
|
val spendingKey = DerivationTool.deriveUnifiedSpendingKey(
|
||||||
seed,
|
seed,
|
||||||
ZcashNetwork.fromResources(requireApplicationContext()),
|
ZcashNetwork.fromResources(requireApplicationContext()),
|
||||||
5
|
5
|
||||||
).first()
|
)
|
||||||
|
|
||||||
// derive the key that allows you to view but not spend transactions
|
// derive the key that allows you to view but not spend transactions
|
||||||
val viewingKey = DerivationTool.deriveViewingKey(
|
val viewingKey = DerivationTool.deriveUnifiedFullViewingKey(
|
||||||
spendingKey,
|
spendingKey,
|
||||||
ZcashNetwork.fromResources(requireApplicationContext())
|
ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
)
|
)
|
||||||
|
|
|
@ -30,6 +30,7 @@ import cash.z.ecc.android.sdk.ext.toZecString
|
||||||
import cash.z.ecc.android.sdk.internal.Twig
|
import cash.z.ecc.android.sdk.internal.Twig
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
|
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.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
|
||||||
|
@ -53,7 +54,7 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
||||||
|
|
||||||
// in a normal app, this would be stored securely with the trusted execution environment (TEE)
|
// in a normal app, this would be stored securely with the trusted execution environment (TEE)
|
||||||
// but since this is a demo, we'll derive it on the fly
|
// but since this is a demo, we'll derive it on the fly
|
||||||
private lateinit var spendingKey: String
|
private lateinit var spendingKey: UnifiedSpendingKey
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the required values that would normally live outside the demo but are repeated
|
* Initialize the required values that would normally live outside the demo but are repeated
|
||||||
|
@ -76,7 +77,7 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
||||||
birthday = null
|
birthday = null
|
||||||
)
|
)
|
||||||
spendingKey = runBlocking {
|
spendingKey = runBlocking {
|
||||||
DerivationTool.deriveSpendingKeys(seed, ZcashNetwork.fromResources(requireApplicationContext())).first()
|
DerivationTool.deriveUnifiedSpendingKey(seed, ZcashNetwork.fromResources(requireApplicationContext()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -661,17 +661,16 @@ class SdkSynchronizer internal constructor(
|
||||||
processor.getTransparentAddress(accountId)
|
processor.getTransparentAddress(accountId)
|
||||||
|
|
||||||
override fun sendToAddress(
|
override fun sendToAddress(
|
||||||
spendingKey: String,
|
usk: UnifiedSpendingKey,
|
||||||
amount: Zatoshi,
|
amount: Zatoshi,
|
||||||
toAddress: String,
|
toAddress: String,
|
||||||
memo: String,
|
memo: String,
|
||||||
fromAccountIndex: Int
|
|
||||||
): Flow<PendingTransaction> = flow {
|
): Flow<PendingTransaction> = flow {
|
||||||
twig("Initializing pending transaction")
|
twig("Initializing pending transaction")
|
||||||
// Emit the placeholder transaction, then switch to monitoring the database
|
// Emit the placeholder transaction, then switch to monitoring the database
|
||||||
txManager.initSpend(amount, toAddress, memo, fromAccountIndex).let { placeHolderTx ->
|
txManager.initSpend(amount, toAddress, memo, usk.account).let { placeHolderTx ->
|
||||||
emit(placeHolderTx)
|
emit(placeHolderTx)
|
||||||
txManager.encode(spendingKey, placeHolderTx).let { encodedTx ->
|
txManager.encode(usk, placeHolderTx).let { encodedTx ->
|
||||||
// only submit if it wasn't cancelled. Otherwise cleanup, immediately for best UX.
|
// only submit if it wasn't cancelled. Otherwise cleanup, immediately for best UX.
|
||||||
if (encodedTx.isCancelled()) {
|
if (encodedTx.isCancelled()) {
|
||||||
twig("[cleanup] this tx has been cancelled so we will cleanup instead of submitting")
|
twig("[cleanup] this tx has been cancelled so we will cleanup instead of submitting")
|
||||||
|
@ -691,20 +690,20 @@ class SdkSynchronizer internal constructor(
|
||||||
}.distinctUntilChanged()
|
}.distinctUntilChanged()
|
||||||
|
|
||||||
override fun shieldFunds(
|
override fun shieldFunds(
|
||||||
spendingKey: String,
|
usk: UnifiedSpendingKey,
|
||||||
transparentAccountPrivateKey: String,
|
|
||||||
memo: String
|
memo: String
|
||||||
): Flow<PendingTransaction> = flow {
|
): Flow<PendingTransaction> = flow {
|
||||||
twig("Initializing shielding transaction")
|
twig("Initializing shielding transaction")
|
||||||
val tAddr =
|
// TODO(str4d): This only shields funds from the current UA's transparent receiver. Fix this once we start
|
||||||
DerivationTool.deriveTransparentAddressFromAccountPrivateKey(transparentAccountPrivateKey, network)
|
// rolling UAs.
|
||||||
|
val tAddr = processor.getTransparentAddress(usk.account)
|
||||||
val tBalance = processor.getUtxoCacheBalance(tAddr)
|
val tBalance = processor.getUtxoCacheBalance(tAddr)
|
||||||
val zAddr = getCurrentAddress(0)
|
val zAddr = getCurrentAddress(usk.account)
|
||||||
|
|
||||||
// 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, usk.account).let { placeHolderTx ->
|
||||||
emit(placeHolderTx)
|
emit(placeHolderTx)
|
||||||
txManager.encode(spendingKey, transparentAccountPrivateKey, placeHolderTx).let { encodedTx ->
|
txManager.encode("", usk, placeHolderTx).let { encodedTx ->
|
||||||
// only submit if it wasn't cancelled. Otherwise cleanup, immediately for best UX.
|
// only submit if it wasn't cancelled. Otherwise cleanup, immediately for best UX.
|
||||||
if (encodedTx.isCancelled()) {
|
if (encodedTx.isCancelled()) {
|
||||||
twig("[cleanup] this shielding tx has been cancelled so we will cleanup instead of submitting")
|
twig("[cleanup] this shielding tx has been cancelled so we will cleanup instead of submitting")
|
||||||
|
|
|
@ -225,11 +225,10 @@ interface Synchronizer {
|
||||||
/**
|
/**
|
||||||
* Sends zatoshi.
|
* Sends zatoshi.
|
||||||
*
|
*
|
||||||
* @param spendingKey the key associated with the notes that will be spent.
|
* @param usk the unified spending key associated with the notes that will be spent.
|
||||||
* @param zatoshi the amount of zatoshi to send.
|
* @param zatoshi the amount of zatoshi to send.
|
||||||
* @param toAddress the recipient's address.
|
* @param toAddress the recipient's address.
|
||||||
* @param memo the optional memo to include as part of the transaction.
|
* @param memo the optional memo to include as part of the transaction.
|
||||||
* @param fromAccountIndex the optional account id to use. By default, the first account is used.
|
|
||||||
*
|
*
|
||||||
* @return a flow of PendingTransaction objects representing changes to the state of the
|
* @return a flow of PendingTransaction objects representing changes to the state of the
|
||||||
* transaction. Any time the state changes a new instance will be emitted by this flow. This is
|
* transaction. Any time the state changes a new instance will be emitted by this flow. This is
|
||||||
|
@ -237,16 +236,14 @@ interface Synchronizer {
|
||||||
* for any wallet that wants to ignore this return value.
|
* for any wallet that wants to ignore this return value.
|
||||||
*/
|
*/
|
||||||
fun sendToAddress(
|
fun sendToAddress(
|
||||||
spendingKey: String,
|
usk: UnifiedSpendingKey,
|
||||||
amount: Zatoshi,
|
amount: Zatoshi,
|
||||||
toAddress: String,
|
toAddress: String,
|
||||||
memo: String = "",
|
memo: String = "",
|
||||||
fromAccountIndex: Int = 0
|
|
||||||
): Flow<PendingTransaction>
|
): Flow<PendingTransaction>
|
||||||
|
|
||||||
fun shieldFunds(
|
fun shieldFunds(
|
||||||
spendingKey: String,
|
usk: UnifiedSpendingKey,
|
||||||
transparentAccountPrivateKey: String,
|
|
||||||
memo: String = ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX
|
memo: String = ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX
|
||||||
): Flow<PendingTransaction>
|
): Flow<PendingTransaction>
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import cash.z.ecc.android.sdk.internal.db.PendingTransactionDb
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletService
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
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.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
|
@ -108,21 +109,23 @@ class PersistentTransactionManager(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun encode(
|
override suspend fun encode(
|
||||||
spendingKey: String,
|
usk: UnifiedSpendingKey,
|
||||||
pendingTx: PendingTransaction
|
pendingTx: PendingTransaction
|
||||||
): PendingTransaction = withContext(Dispatchers.IO) {
|
): PendingTransaction = withContext(Dispatchers.IO) {
|
||||||
twig("managing the creation of a transaction")
|
twig("managing the creation of a transaction")
|
||||||
var tx = pendingTx as PendingTransactionEntity
|
var tx = pendingTx as PendingTransactionEntity
|
||||||
|
if (tx.accountIndex != usk.account) {
|
||||||
|
throw java.lang.IllegalArgumentException("usk is not for the same account as pendingTx")
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("TooGenericExceptionCaught")
|
@Suppress("TooGenericExceptionCaught")
|
||||||
try {
|
try {
|
||||||
twig("beginning to encode transaction with : $encoder")
|
twig("beginning to encode transaction with : $encoder")
|
||||||
val encodedTx = encoder.createTransaction(
|
val encodedTx = encoder.createTransaction(
|
||||||
spendingKey,
|
usk,
|
||||||
tx.valueZatoshi,
|
tx.valueZatoshi,
|
||||||
tx.toAddress,
|
tx.toAddress,
|
||||||
tx.memo,
|
tx.memo,
|
||||||
tx.accountIndex
|
|
||||||
)
|
)
|
||||||
twig("successfully encoded transaction!")
|
twig("successfully encoded transaction!")
|
||||||
safeUpdate("updating transaction encoding", -1) {
|
safeUpdate("updating transaction encoding", -1) {
|
||||||
|
@ -149,7 +152,7 @@ class PersistentTransactionManager(
|
||||||
// spendingKey is removed. Figure out where these methods need to be renamed, and do so.
|
// spendingKey is removed. Figure out where these methods need to be renamed, and do so.
|
||||||
override suspend fun encode(
|
override suspend fun encode(
|
||||||
spendingKey: String, // TODO(str4d): Remove this argument.
|
spendingKey: String, // TODO(str4d): Remove this argument.
|
||||||
transparentAccountPrivateKey: String,
|
usk: UnifiedSpendingKey,
|
||||||
pendingTx: PendingTransaction
|
pendingTx: PendingTransaction
|
||||||
): PendingTransaction {
|
): PendingTransaction {
|
||||||
twig("managing the creation of a shielding transaction")
|
twig("managing the creation of a shielding transaction")
|
||||||
|
@ -158,7 +161,7 @@ class PersistentTransactionManager(
|
||||||
try {
|
try {
|
||||||
twig("beginning to encode shielding transaction with : $encoder")
|
twig("beginning to encode shielding transaction with : $encoder")
|
||||||
val encodedTx = encoder.createShieldingTransaction(
|
val encodedTx = encoder.createShieldingTransaction(
|
||||||
transparentAccountPrivateKey,
|
usk,
|
||||||
tx.memo
|
tx.memo
|
||||||
)
|
)
|
||||||
twig("successfully encoded shielding transaction!")
|
twig("successfully encoded shielding transaction!")
|
||||||
|
|
|
@ -1,41 +1,37 @@
|
||||||
package cash.z.ecc.android.sdk.internal.transaction
|
package cash.z.ecc.android.sdk.internal.transaction
|
||||||
|
|
||||||
import cash.z.ecc.android.sdk.db.entity.EncodedTransaction
|
import cash.z.ecc.android.sdk.db.entity.EncodedTransaction
|
||||||
|
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
|
||||||
interface TransactionEncoder {
|
interface TransactionEncoder {
|
||||||
// TODO(str4d): Migrate to binary USK format.
|
|
||||||
/**
|
/**
|
||||||
* Creates a transaction, throwing an exception whenever things are missing. When the provided
|
* Creates a transaction, throwing an exception whenever things are missing. When the provided
|
||||||
* wallet implementation doesn't throw an exception, we wrap the issue into a descriptive
|
* wallet implementation doesn't throw an exception, we wrap the issue into a descriptive
|
||||||
* exception ourselves (rather than using double-bangs for things).
|
* exception ourselves (rather than using double-bangs for things).
|
||||||
*
|
*
|
||||||
* @param spendingKey the key associated with the notes that will be spent.
|
* @param usk the unified spending key associated with the notes that will be spent.
|
||||||
* @param amount the amount of zatoshi to send.
|
* @param amount the amount of zatoshi to send.
|
||||||
* @param toAddress the recipient's address.
|
* @param toAddress the recipient's address.
|
||||||
* @param memo the optional memo to include as part of the transaction.
|
* @param memo the optional memo to include as part of the transaction.
|
||||||
* @param fromAccountIndex the optional account id to use. By default, the 1st account is used.
|
|
||||||
*
|
*
|
||||||
* @return the successfully encoded transaction or an exception
|
* @return the successfully encoded transaction or an exception
|
||||||
*/
|
*/
|
||||||
suspend fun createTransaction(
|
suspend fun createTransaction(
|
||||||
spendingKey: String,
|
usk: UnifiedSpendingKey,
|
||||||
amount: Zatoshi,
|
amount: Zatoshi,
|
||||||
toAddress: String,
|
toAddress: String,
|
||||||
memo: ByteArray? = byteArrayOf(),
|
memo: ByteArray? = byteArrayOf(),
|
||||||
fromAccountIndex: Int = 0
|
|
||||||
): EncodedTransaction
|
): EncodedTransaction
|
||||||
|
|
||||||
// TODO(str4d): Migrate to binary USK format.
|
|
||||||
// TODO(str4d): Enable this to shield funds for other accounts.
|
|
||||||
/**
|
/**
|
||||||
* Creates a transaction that shields any transparent funds sent to account 0.
|
* Creates a transaction that shields any transparent funds sent to the given usk's account.
|
||||||
*
|
*
|
||||||
* @param transparentAccountPrivateKey the transparent account private key for account 0.
|
* @param usk the unified spending key associated with the transparent funds that will be shielded.
|
||||||
* @param memo the optional memo to include as part of the transaction.
|
* @param memo the optional memo to include as part of the transaction.
|
||||||
*/
|
*/
|
||||||
suspend fun createShieldingTransaction(
|
suspend fun createShieldingTransaction(
|
||||||
transparentAccountPrivateKey: String,
|
usk: UnifiedSpendingKey,
|
||||||
memo: ByteArray? = byteArrayOf()
|
memo: ByteArray? = byteArrayOf()
|
||||||
): EncodedTransaction
|
): EncodedTransaction
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package cash.z.ecc.android.sdk.internal.transaction
|
||||||
|
|
||||||
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
|
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
|
||||||
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.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@ -34,17 +35,17 @@ interface OutboundTransactionManager {
|
||||||
* Encode the pending transaction using the given spending key. This is a local operation that
|
* Encode the pending transaction using the given spending key. This is a local operation that
|
||||||
* produces a raw transaction to submit to lightwalletd.
|
* produces a raw transaction to submit to lightwalletd.
|
||||||
*
|
*
|
||||||
* @param spendingKey the spendingKey to use for constructing the transaction.
|
* @param usk the unified spending key to use for constructing the transaction.
|
||||||
* @param pendingTx the transaction information created by [initSpend] that will be used to
|
* @param pendingTx the transaction information created by [initSpend] that will be used to
|
||||||
* construct a transaction.
|
* construct a transaction.
|
||||||
*
|
*
|
||||||
* @return the resulting pending transaction whose ID can be used to monitor for changes.
|
* @return the resulting pending transaction whose ID can be used to monitor for changes.
|
||||||
*/
|
*/
|
||||||
suspend fun encode(spendingKey: String, pendingTx: PendingTransaction): PendingTransaction
|
suspend fun encode(usk: UnifiedSpendingKey, pendingTx: PendingTransaction): PendingTransaction
|
||||||
|
|
||||||
suspend fun encode(
|
suspend fun encode(
|
||||||
spendingKey: String,
|
spendingKey: String,
|
||||||
transparentAccountPrivateKey: String,
|
usk: UnifiedSpendingKey,
|
||||||
pendingTx: PendingTransaction
|
pendingTx: PendingTransaction
|
||||||
): PendingTransaction
|
): PendingTransaction
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import cash.z.ecc.android.sdk.internal.twig
|
||||||
import cash.z.ecc.android.sdk.internal.twigTask
|
import cash.z.ecc.android.sdk.internal.twigTask
|
||||||
import cash.z.ecc.android.sdk.jni.RustBackend
|
import cash.z.ecc.android.sdk.jni.RustBackend
|
||||||
import cash.z.ecc.android.sdk.jni.RustBackendWelding
|
import cash.z.ecc.android.sdk.jni.RustBackendWelding
|
||||||
|
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,31 +30,29 @@ internal class WalletTransactionEncoder(
|
||||||
* wallet implementation doesn't throw an exception, we wrap the issue into a descriptive
|
* wallet implementation doesn't throw an exception, we wrap the issue into a descriptive
|
||||||
* exception ourselves (rather than using double-bangs for things).
|
* exception ourselves (rather than using double-bangs for things).
|
||||||
*
|
*
|
||||||
* @param spendingKey the key associated with the notes that will be spent.
|
* @param usk the unified spending key associated with the notes that will be spent.
|
||||||
* @param amount the amount of zatoshi to send.
|
* @param amount the amount of zatoshi to send.
|
||||||
* @param toAddress the recipient's address.
|
* @param toAddress the recipient's address.
|
||||||
* @param memo the optional memo to include as part of the transaction.
|
* @param memo the optional memo to include as part of the transaction.
|
||||||
* @param fromAccountIndex the optional account id to use. By default, the 1st account is used.
|
|
||||||
*
|
*
|
||||||
* @return the successfully encoded transaction or an exception
|
* @return the successfully encoded transaction or an exception
|
||||||
*/
|
*/
|
||||||
override suspend fun createTransaction(
|
override suspend fun createTransaction(
|
||||||
spendingKey: String,
|
usk: UnifiedSpendingKey,
|
||||||
amount: Zatoshi,
|
amount: Zatoshi,
|
||||||
toAddress: String,
|
toAddress: String,
|
||||||
memo: ByteArray?,
|
memo: ByteArray?,
|
||||||
fromAccountIndex: Int
|
|
||||||
): EncodedTransaction {
|
): EncodedTransaction {
|
||||||
val transactionId = createSpend(spendingKey, amount, toAddress, memo)
|
val transactionId = createSpend(usk, amount, toAddress, memo)
|
||||||
return repository.findEncodedTransactionById(transactionId)
|
return repository.findEncodedTransactionById(transactionId)
|
||||||
?: throw TransactionEncoderException.TransactionNotFoundException(transactionId)
|
?: throw TransactionEncoderException.TransactionNotFoundException(transactionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun createShieldingTransaction(
|
override suspend fun createShieldingTransaction(
|
||||||
transparentAccountPrivateKey: String,
|
usk: UnifiedSpendingKey,
|
||||||
memo: ByteArray?
|
memo: ByteArray?
|
||||||
): EncodedTransaction {
|
): EncodedTransaction {
|
||||||
val transactionId = createShieldingSpend(transparentAccountPrivateKey, memo)
|
val transactionId = createShieldingSpend(usk, memo)
|
||||||
return repository.findEncodedTransactionById(transactionId)
|
return repository.findEncodedTransactionById(transactionId)
|
||||||
?: throw TransactionEncoderException.TransactionNotFoundException(transactionId)
|
?: throw TransactionEncoderException.TransactionNotFoundException(transactionId)
|
||||||
}
|
}
|
||||||
|
@ -103,21 +102,19 @@ internal class WalletTransactionEncoder(
|
||||||
* Does the proofs and processing required to create a transaction to spend funds and inserts
|
* Does the proofs and processing required to create a transaction to spend funds and inserts
|
||||||
* the result in the database. On average, this call takes over 10 seconds.
|
* the result in the database. On average, this call takes over 10 seconds.
|
||||||
*
|
*
|
||||||
* @param spendingKey the key associated with the notes that will be spent.
|
* @param usk the unified spending key associated with the notes that will be spent.
|
||||||
* @param amount the amount of zatoshi to send.
|
* @param amount the amount of zatoshi to send.
|
||||||
* @param toAddress the recipient's address.
|
* @param toAddress the recipient's address.
|
||||||
* @param memo the optional memo to include as part of the transaction.
|
* @param memo the optional memo to include as part of the transaction.
|
||||||
* @param fromAccountIndex the optional account id to use. By default, the 1st account is used.
|
|
||||||
*
|
*
|
||||||
* @return the row id in the transactions table that contains the spend transaction or -1 if it
|
* @return the row id in the transactions table that contains the spend transaction or -1 if it
|
||||||
* failed.
|
* failed.
|
||||||
*/
|
*/
|
||||||
private suspend fun createSpend(
|
private suspend fun createSpend(
|
||||||
spendingKey: String,
|
usk: UnifiedSpendingKey,
|
||||||
amount: Zatoshi,
|
amount: Zatoshi,
|
||||||
toAddress: String,
|
toAddress: String,
|
||||||
memo: ByteArray? = byteArrayOf(),
|
memo: ByteArray? = byteArrayOf(),
|
||||||
fromAccountIndex: Int = 0
|
|
||||||
): Long {
|
): Long {
|
||||||
return twigTask(
|
return twigTask(
|
||||||
"creating transaction to spend $amount zatoshi to" +
|
"creating transaction to spend $amount zatoshi to" +
|
||||||
|
@ -128,8 +125,7 @@ internal class WalletTransactionEncoder(
|
||||||
SaplingParamTool.ensureParams((rustBackend as RustBackend).pathParamsDir)
|
SaplingParamTool.ensureParams((rustBackend as RustBackend).pathParamsDir)
|
||||||
twig("params exist! attempting to send...")
|
twig("params exist! attempting to send...")
|
||||||
rustBackend.createToAddress(
|
rustBackend.createToAddress(
|
||||||
fromAccountIndex,
|
usk,
|
||||||
spendingKey,
|
|
||||||
toAddress,
|
toAddress,
|
||||||
amount.value,
|
amount.value,
|
||||||
memo
|
memo
|
||||||
|
@ -144,7 +140,7 @@ internal class WalletTransactionEncoder(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun createShieldingSpend(
|
private suspend fun createShieldingSpend(
|
||||||
transparentAccountPrivateKey: String,
|
usk: UnifiedSpendingKey,
|
||||||
memo: ByteArray? = byteArrayOf()
|
memo: ByteArray? = byteArrayOf()
|
||||||
): Long {
|
): Long {
|
||||||
return twigTask("creating transaction to shield all UTXOs") {
|
return twigTask("creating transaction to shield all UTXOs") {
|
||||||
|
@ -153,7 +149,7 @@ internal class WalletTransactionEncoder(
|
||||||
SaplingParamTool.ensureParams((rustBackend as RustBackend).pathParamsDir)
|
SaplingParamTool.ensureParams((rustBackend as RustBackend).pathParamsDir)
|
||||||
twig("params exist! attempting to shield...")
|
twig("params exist! attempting to shield...")
|
||||||
rustBackend.shieldToAddress(
|
rustBackend.shieldToAddress(
|
||||||
transparentAccountPrivateKey,
|
usk,
|
||||||
memo
|
memo
|
||||||
)
|
)
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
|
|
|
@ -225,16 +225,15 @@ internal class RustBackend private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun createToAddress(
|
override suspend fun createToAddress(
|
||||||
account: Int,
|
usk: UnifiedSpendingKey,
|
||||||
extsk: String,
|
|
||||||
to: String,
|
to: String,
|
||||||
value: Long,
|
value: Long,
|
||||||
memo: ByteArray?
|
memo: ByteArray?
|
||||||
): Long = withContext(SdkDispatchers.DATABASE_IO) {
|
): Long = withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
createToAddress(
|
createToAddress(
|
||||||
dataDbFile.absolutePath,
|
dataDbFile.absolutePath,
|
||||||
account,
|
usk.account,
|
||||||
extsk,
|
usk.bytes.byteArray,
|
||||||
to,
|
to,
|
||||||
value,
|
value,
|
||||||
memo ?: ByteArray(0),
|
memo ?: ByteArray(0),
|
||||||
|
@ -245,15 +244,15 @@ internal class RustBackend private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun shieldToAddress(
|
override suspend fun shieldToAddress(
|
||||||
xprv: String,
|
usk: UnifiedSpendingKey,
|
||||||
memo: ByteArray?
|
memo: ByteArray?
|
||||||
): Long {
|
): Long {
|
||||||
twig("TMP: shieldToAddress with db path: $dataDbFile, ${memo?.size}")
|
twig("TMP: shieldToAddress with db path: $dataDbFile, ${memo?.size}")
|
||||||
return withContext(SdkDispatchers.DATABASE_IO) {
|
return withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
shieldToAddress(
|
shieldToAddress(
|
||||||
dataDbFile.absolutePath,
|
dataDbFile.absolutePath,
|
||||||
0,
|
usk.account,
|
||||||
xprv,
|
usk.bytes.byteArray,
|
||||||
memo ?: ByteArray(0),
|
memo ?: ByteArray(0),
|
||||||
"$pathParamsDir/$SPEND_PARAM_FILE_NAME",
|
"$pathParamsDir/$SPEND_PARAM_FILE_NAME",
|
||||||
"$pathParamsDir/$OUTPUT_PARAM_FILE_NAME",
|
"$pathParamsDir/$OUTPUT_PARAM_FILE_NAME",
|
||||||
|
@ -429,6 +428,12 @@ internal class RustBackend private constructor(
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun getSaplingReceiverForUnifiedAddress(ua: String): String?
|
private external fun getSaplingReceiverForUnifiedAddress(ua: String): String?
|
||||||
|
|
||||||
|
internal fun validateUnifiedSpendingKey(bytes: ByteArray) =
|
||||||
|
isValidSpendingKey(bytes)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
private external fun isValidSpendingKey(bytes: ByteArray): Boolean
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun isValidShieldedAddress(addr: String, networkId: Int): Boolean
|
private external fun isValidShieldedAddress(addr: String, networkId: Int): Boolean
|
||||||
|
|
||||||
|
@ -510,7 +515,7 @@ internal class RustBackend private constructor(
|
||||||
private external fun createToAddress(
|
private external fun createToAddress(
|
||||||
dbDataPath: String,
|
dbDataPath: String,
|
||||||
account: Int,
|
account: Int,
|
||||||
extsk: String,
|
usk: ByteArray,
|
||||||
to: String,
|
to: String,
|
||||||
value: Long,
|
value: Long,
|
||||||
memo: ByteArray,
|
memo: ByteArray,
|
||||||
|
@ -524,7 +529,7 @@ internal class RustBackend private constructor(
|
||||||
private external fun shieldToAddress(
|
private external fun shieldToAddress(
|
||||||
dbDataPath: String,
|
dbDataPath: String,
|
||||||
account: Int,
|
account: Int,
|
||||||
xprv: String,
|
usk: ByteArray,
|
||||||
memo: ByteArray,
|
memo: ByteArray,
|
||||||
spendParamsPath: String,
|
spendParamsPath: String,
|
||||||
outputParamsPath: String,
|
outputParamsPath: String,
|
||||||
|
|
|
@ -21,15 +21,14 @@ internal interface RustBackendWelding {
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
suspend fun createToAddress(
|
suspend fun createToAddress(
|
||||||
account: Int,
|
usk: UnifiedSpendingKey,
|
||||||
extsk: String,
|
|
||||||
to: String,
|
to: String,
|
||||||
value: Long,
|
value: Long,
|
||||||
memo: ByteArray? = byteArrayOf()
|
memo: ByteArray? = byteArrayOf()
|
||||||
): Long
|
): Long
|
||||||
|
|
||||||
suspend fun shieldToAddress(
|
suspend fun shieldToAddress(
|
||||||
xprv: String,
|
usk: UnifiedSpendingKey,
|
||||||
memo: ByteArray? = byteArrayOf()
|
memo: ByteArray? = byteArrayOf()
|
||||||
): Long
|
): Long
|
||||||
|
|
||||||
|
@ -110,11 +109,11 @@ internal interface RustBackendWelding {
|
||||||
accountIndex: Int = 0
|
accountIndex: Int = 0
|
||||||
): String
|
): String
|
||||||
|
|
||||||
suspend fun deriveSpendingKeys(
|
suspend fun deriveUnifiedSpendingKey(
|
||||||
seed: ByteArray,
|
seed: ByteArray,
|
||||||
network: ZcashNetwork,
|
network: ZcashNetwork,
|
||||||
numberOfAccounts: Int = 1
|
account: Int = 0
|
||||||
): Array<String>
|
): UnifiedSpendingKey
|
||||||
|
|
||||||
suspend fun deriveTransparentAddress(
|
suspend fun deriveTransparentAddress(
|
||||||
seed: ByteArray,
|
seed: ByteArray,
|
||||||
|
@ -140,10 +139,10 @@ internal interface RustBackendWelding {
|
||||||
account: Int = 0
|
account: Int = 0
|
||||||
): String
|
): String
|
||||||
|
|
||||||
suspend fun deriveViewingKey(
|
suspend fun deriveUnifiedFullViewingKey(
|
||||||
spendingKey: String,
|
usk: UnifiedSpendingKey,
|
||||||
network: ZcashNetwork
|
network: ZcashNetwork
|
||||||
): String
|
): UnifiedFullViewingKey
|
||||||
|
|
||||||
suspend fun deriveUnifiedFullViewingKeys(
|
suspend fun deriveUnifiedFullViewingKeys(
|
||||||
seed: ByteArray,
|
seed: ByteArray,
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package cash.z.ecc.android.sdk.model
|
package cash.z.ecc.android.sdk.model
|
||||||
|
|
||||||
|
import cash.z.ecc.android.sdk.jni.RustBackend
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [ZIP 316](https://zips.z.cash/zip-0316) Unified Spending Key.
|
* A [ZIP 316](https://zips.z.cash/zip-0316) Unified Spending Key.
|
||||||
*
|
*
|
||||||
|
@ -29,4 +31,17 @@ data class UnifiedSpendingKey internal constructor(
|
||||||
override fun toString() = "UnifiedSpendingKey(account=$account)"
|
override fun toString() = "UnifiedSpendingKey(account=$account)"
|
||||||
|
|
||||||
fun copyBytes() = bytes.byteArray.copyOf()
|
fun copyBytes() = bytes.byteArray.copyOf()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
suspend fun new(account: Int, bytes: ByteArray): Result<UnifiedSpendingKey> {
|
||||||
|
val bytesCopy = bytes.copyOf()
|
||||||
|
RustBackend.rustLibraryLoader.load()
|
||||||
|
return Result.runCatching {
|
||||||
|
// We can ignore the Boolean returned from this, because if an error
|
||||||
|
// occurs the Rust side will throw.
|
||||||
|
RustBackend.validateUnifiedSpendingKey(bytesCopy)
|
||||||
|
return success(UnifiedSpendingKey(account, FirstClassByteArray(bytesCopy)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.RustBackend
|
||||||
import cash.z.ecc.android.sdk.jni.RustBackendWelding
|
import cash.z.ecc.android.sdk.jni.RustBackendWelding
|
||||||
|
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||||
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.type.UnifiedFullViewingKey
|
import cash.z.ecc.android.sdk.type.UnifiedFullViewingKey
|
||||||
|
|
||||||
|
@ -32,34 +33,39 @@ class DerivationTool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a spending key, return the associated viewing key.
|
* Given a unified spending key, return the associated unified full viewing key.
|
||||||
*
|
*
|
||||||
* @param spendingKey the key from which to derive the viewing key.
|
* @param usk the key from which to derive the viewing key.
|
||||||
*
|
*
|
||||||
* @return the viewing key that corresponds to the spending key.
|
* @return a unified full viewing key.
|
||||||
*/
|
*/
|
||||||
override suspend fun deriveViewingKey(
|
override suspend fun deriveUnifiedFullViewingKey(
|
||||||
spendingKey: String,
|
usk: UnifiedSpendingKey,
|
||||||
network: ZcashNetwork
|
network: ZcashNetwork
|
||||||
): String = withRustBackendLoaded {
|
): UnifiedFullViewingKey = withRustBackendLoaded {
|
||||||
deriveExtendedFullViewingKey(spendingKey, networkId = network.id)
|
UnifiedFullViewingKey(
|
||||||
|
deriveUnifiedFullViewingKey(usk.bytes.byteArray, networkId = network.id)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a seed and a number of accounts, return the associated spending keys.
|
* Derives and returns a unified spending key from the given seed for the given account ID.
|
||||||
|
*
|
||||||
|
* Returns the newly created [ZIP 316] account identifier, along with the binary encoding
|
||||||
|
* of the [`UnifiedSpendingKey`] for the newly created account. The caller should store
|
||||||
|
* the returned spending key in a secure fashion.
|
||||||
*
|
*
|
||||||
* @param seed the seed from which to derive spending keys.
|
* @param seed the seed from which to derive spending keys.
|
||||||
* @param numberOfAccounts the number of accounts to use. Multiple accounts are not fully
|
* @param account the account to derive.
|
||||||
* supported so the default value of 1 is recommended.
|
|
||||||
*
|
*
|
||||||
* @return the spending keys that correspond to the seed, formatted as Strings.
|
* @return the unified spending key for the account.
|
||||||
*/
|
*/
|
||||||
override suspend fun deriveSpendingKeys(
|
override suspend fun deriveUnifiedSpendingKey(
|
||||||
seed: ByteArray,
|
seed: ByteArray,
|
||||||
network: ZcashNetwork,
|
network: ZcashNetwork,
|
||||||
numberOfAccounts: Int
|
account: Int
|
||||||
): Array<String> = withRustBackendLoaded {
|
): UnifiedSpendingKey = withRustBackendLoaded {
|
||||||
deriveExtendedSpendingKeys(seed, numberOfAccounts, networkId = network.id)
|
deriveSpendingKey(seed, account, networkId = network.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -146,11 +152,11 @@ class DerivationTool {
|
||||||
//
|
//
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun deriveExtendedSpendingKeys(
|
private external fun deriveSpendingKey(
|
||||||
seed: ByteArray,
|
seed: ByteArray,
|
||||||
numberOfAccounts: Int,
|
account: Int,
|
||||||
networkId: Int
|
networkId: Int
|
||||||
): Array<String>
|
): UnifiedSpendingKey
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun deriveUnifiedFullViewingKeysFromSeed(
|
private external fun deriveUnifiedFullViewingKeysFromSeed(
|
||||||
|
@ -160,7 +166,7 @@ class DerivationTool {
|
||||||
): Array<String>
|
): Array<String>
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun deriveExtendedFullViewingKey(spendingKey: String, networkId: Int): String
|
private external fun deriveUnifiedFullViewingKey(usk: ByteArray, networkId: Int): String
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun deriveUnifiedAddressFromSeed(
|
private external fun deriveUnifiedAddressFromSeed(
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package cash.z.ecc.android.sdk.type
|
package cash.z.ecc.android.sdk.type
|
||||||
|
|
||||||
|
import cash.z.ecc.android.sdk.model.FirstClassByteArray
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [ZIP 316] Unified Full Viewing Key, corresponding to a single wallet account.
|
* A [ZIP 316] Unified Full Viewing Key, corresponding to a single wallet account.
|
||||||
*
|
*
|
||||||
|
|
|
@ -20,9 +20,9 @@ use jni::{
|
||||||
use log::Level;
|
use log::Level;
|
||||||
use schemer::MigratorError;
|
use schemer::MigratorError;
|
||||||
use secp256k1::PublicKey;
|
use secp256k1::PublicKey;
|
||||||
use secrecy::SecretVec;
|
use secrecy::{ExposeSecret, SecretVec};
|
||||||
use zcash_address::{ToAddress, ZcashAddress};
|
use zcash_address::{ToAddress, ZcashAddress};
|
||||||
use zcash_client_backend::keys::UnifiedSpendingKey;
|
use zcash_client_backend::keys::{DecodingError, UnifiedSpendingKey};
|
||||||
use zcash_client_backend::{
|
use zcash_client_backend::{
|
||||||
address::{RecipientAddress, UnifiedAddress},
|
address::{RecipientAddress, UnifiedAddress},
|
||||||
data_api::{
|
data_api::{
|
||||||
|
@ -33,11 +33,8 @@ use zcash_client_backend::{
|
||||||
},
|
},
|
||||||
WalletRead, WalletReadTransparent, WalletWrite, WalletWriteTransparent,
|
WalletRead, WalletReadTransparent, WalletWrite, WalletWriteTransparent,
|
||||||
},
|
},
|
||||||
encoding::{
|
encoding::AddressCodec,
|
||||||
decode_extended_spending_key, encode_extended_full_viewing_key,
|
keys::{Era, UnifiedFullViewingKey},
|
||||||
encode_extended_spending_key, AddressCodec,
|
|
||||||
},
|
|
||||||
keys::{sapling, Era, UnifiedFullViewingKey},
|
|
||||||
wallet::{OvkPolicy, WalletTransparentOutput},
|
wallet::{OvkPolicy, WalletTransparentOutput},
|
||||||
};
|
};
|
||||||
use zcash_client_sqlite::wallet::init::WalletMigrationError;
|
use zcash_client_sqlite::wallet::init::WalletMigrationError;
|
||||||
|
@ -60,7 +57,7 @@ use zcash_primitives::{
|
||||||
components::{Amount, OutPoint, TxOut},
|
components::{Amount, OutPoint, TxOut},
|
||||||
Transaction,
|
Transaction,
|
||||||
},
|
},
|
||||||
zip32::{AccountId, DiversifierIndex, ExtendedFullViewingKey},
|
zip32::{AccountId, DiversifierIndex},
|
||||||
};
|
};
|
||||||
use zcash_proofs::prover::LocalTxProver;
|
use zcash_proofs::prover::LocalTxProver;
|
||||||
|
|
||||||
|
@ -147,6 +144,40 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initDataDb(
|
||||||
unwrap_exc_or(&env, res, -1)
|
unwrap_exc_or(&env, res, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn encode_usk(
|
||||||
|
env: &JNIEnv<'_>,
|
||||||
|
account: AccountId,
|
||||||
|
usk: UnifiedSpendingKey,
|
||||||
|
) -> Result<jobject, failure::Error> {
|
||||||
|
let encoded = SecretVec::new(usk.to_bytes(Era::Orchard));
|
||||||
|
let output = env.new_object(
|
||||||
|
"cash/z/ecc/android/sdk/model/UnifiedSpendingKey",
|
||||||
|
"(I[B)V",
|
||||||
|
&[
|
||||||
|
JValue::Int(u32::from(account) as i32),
|
||||||
|
JValue::Object(env.byte_array_from_slice(encoded.expose_secret())?.into()),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
Ok(output.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_usk(env: &JNIEnv<'_>, usk: jbyteArray) -> Result<UnifiedSpendingKey, failure::Error> {
|
||||||
|
let usk_bytes = SecretVec::new(env.convert_byte_array(usk).unwrap());
|
||||||
|
|
||||||
|
// The remainder of the function is safe.
|
||||||
|
UnifiedSpendingKey::from_bytes(Era::Orchard, usk_bytes.expose_secret()).map_err(|e| match e {
|
||||||
|
DecodingError::EraMismatch(era) => format_err!(
|
||||||
|
"Spending key was from era {:?}, but {:?} was expected.",
|
||||||
|
era,
|
||||||
|
Era::Orchard
|
||||||
|
),
|
||||||
|
e => format_err!(
|
||||||
|
"An error occurred decoding the provided unified spending key: {:?}",
|
||||||
|
e
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds the next available account-level spend authority, given the current set of
|
/// Adds the next available account-level spend authority, given the current set of
|
||||||
/// [ZIP 316] account identifiers known, to the wallet database.
|
/// [ZIP 316] account identifiers known, to the wallet database.
|
||||||
///
|
///
|
||||||
|
@ -181,16 +212,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createAccou
|
||||||
.create_account(&seed)
|
.create_account(&seed)
|
||||||
.map_err(|e| format_err!("Error while initializing accounts: {}", e))?;
|
.map_err(|e| format_err!("Error while initializing accounts: {}", e))?;
|
||||||
|
|
||||||
let encoded = usk.to_bytes(Era::Orchard);
|
encode_usk(&env, account, usk)
|
||||||
let output = env.new_object(
|
|
||||||
"cash/z/ecc/android/sdk/model/UnifiedSpendingKey",
|
|
||||||
"(I[B)V",
|
|
||||||
&[
|
|
||||||
JValue::Int(u32::from(account) as i32),
|
|
||||||
JValue::Object(env.byte_array_from_slice(&encoded)?.into()),
|
|
||||||
],
|
|
||||||
)?;
|
|
||||||
Ok(output.into_inner())
|
|
||||||
});
|
});
|
||||||
unwrap_exc_or(&env, res, ptr::null_mut())
|
unwrap_exc_or(&env, res, ptr::null_mut())
|
||||||
}
|
}
|
||||||
|
@ -241,41 +263,32 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initAccount
|
||||||
unwrap_exc_or(&env, res, JNI_FALSE)
|
unwrap_exc_or(&env, res, JNI_FALSE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Derives and returns a unified spending key from the given seed for the given account ID.
|
||||||
|
///
|
||||||
|
/// Returns the newly created [ZIP 316] account identifier, along with the binary encoding
|
||||||
|
/// of the [`UnifiedSpendingKey`] for the newly created account. The caller should store
|
||||||
|
/// the returned spending key in a secure fashion.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveExtendedSpendingKeys(
|
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveSpendingKey(
|
||||||
env: JNIEnv<'_>,
|
env: JNIEnv<'_>,
|
||||||
_: JClass<'_>,
|
_: JClass<'_>,
|
||||||
seed: jbyteArray,
|
seed: jbyteArray,
|
||||||
accounts: jint,
|
account: jint,
|
||||||
network_id: jint,
|
network_id: jint,
|
||||||
) -> jobjectArray {
|
) -> jobject {
|
||||||
let res = panic::catch_unwind(|| {
|
let res = panic::catch_unwind(|| {
|
||||||
let network = parse_network(network_id as u32)?;
|
let network = parse_network(network_id as u32)?;
|
||||||
let seed = env.convert_byte_array(seed).unwrap();
|
let seed = SecretVec::new(env.convert_byte_array(seed).unwrap());
|
||||||
let accounts = if accounts > 0 {
|
let account = if account >= 0 {
|
||||||
accounts as u32
|
AccountId::from(account as u32)
|
||||||
} else {
|
} else {
|
||||||
return Err(format_err!("accounts argument must be greater than zero"));
|
return Err(format_err!("accounts argument must be greater than zero"));
|
||||||
};
|
};
|
||||||
|
|
||||||
let extsks: Vec<_> = (0..accounts)
|
let usk = UnifiedSpendingKey::from_seed(&network, seed.expose_secret(), account)
|
||||||
.map(|account| {
|
.map_err(|e| format_err!("error generating unified spending key from seed: {:?}", e))?;
|
||||||
sapling::spending_key(&seed, network.coin_type(), AccountId::from(account))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(utils::rust_vec_to_java(
|
encode_usk(&env, account, usk)
|
||||||
&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(""),
|
|
||||||
))
|
|
||||||
});
|
});
|
||||||
unwrap_exc_or(&env, res, ptr::null_mut())
|
unwrap_exc_or(&env, res, ptr::null_mut())
|
||||||
}
|
}
|
||||||
|
@ -386,33 +399,20 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveU
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveExtendedFullViewingKey(
|
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveUnifiedFullViewingKey(
|
||||||
env: JNIEnv<'_>,
|
env: JNIEnv<'_>,
|
||||||
_: JClass<'_>,
|
_: JClass<'_>,
|
||||||
extsk_string: JString<'_>,
|
usk: jbyteArray,
|
||||||
network_id: jint,
|
network_id: jint,
|
||||||
) -> jobjectArray {
|
) -> jstring {
|
||||||
let res = panic::catch_unwind(|| {
|
let res = panic::catch_unwind(|| {
|
||||||
|
let usk = decode_usk(&env, usk)?;
|
||||||
let network = parse_network(network_id as u32)?;
|
let network = parse_network(network_id as u32)?;
|
||||||
let extsk_string = utils::java_string_to_rust(&env, extsk_string);
|
|
||||||
let extfvk = match decode_extended_spending_key(
|
let ufvk = usk.to_unified_full_viewing_key();
|
||||||
network.hrp_sapling_extended_spending_key(),
|
|
||||||
&extsk_string,
|
|
||||||
) {
|
|
||||||
Ok(extsk) => ExtendedFullViewingKey::from(&extsk),
|
|
||||||
Err(e) => {
|
|
||||||
return Err(format_err!(
|
|
||||||
"Error while deriving viewing key from spending key: {}",
|
|
||||||
e
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let output = env
|
let output = env
|
||||||
.new_string(encode_extended_full_viewing_key(
|
.new_string(ufvk.encode(&network))
|
||||||
network.hrp_sapling_extended_full_viewing_key(),
|
|
||||||
&extfvk,
|
|
||||||
))
|
|
||||||
.expect("Couldn't create Java string!");
|
.expect("Couldn't create Java string!");
|
||||||
|
|
||||||
Ok(output.into_inner())
|
Ok(output.into_inner())
|
||||||
|
@ -576,6 +576,19 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getSaplingR
|
||||||
unwrap_exc_or(&env, res, ptr::null_mut())
|
unwrap_exc_or(&env, res, ptr::null_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidSpendingKey(
|
||||||
|
env: JNIEnv<'_>,
|
||||||
|
_: JClass<'_>,
|
||||||
|
usk: jbyteArray,
|
||||||
|
) -> jboolean {
|
||||||
|
let res = panic::catch_unwind(|| {
|
||||||
|
let _usk = decode_usk(&env, usk)?;
|
||||||
|
Ok(JNI_TRUE)
|
||||||
|
});
|
||||||
|
unwrap_exc_or(&env, res, JNI_FALSE)
|
||||||
|
}
|
||||||
|
|
||||||
#[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<'_>,
|
||||||
|
@ -1222,7 +1235,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createToAdd
|
||||||
_: JClass<'_>,
|
_: JClass<'_>,
|
||||||
db_data: JString<'_>,
|
db_data: JString<'_>,
|
||||||
account: jint,
|
account: jint,
|
||||||
extsk: JString<'_>,
|
usk: jbyteArray,
|
||||||
to: JString<'_>,
|
to: JString<'_>,
|
||||||
value: jlong,
|
value: jlong,
|
||||||
memo: jbyteArray,
|
memo: jbyteArray,
|
||||||
|
@ -1239,7 +1252,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createToAdd
|
||||||
} else {
|
} else {
|
||||||
return Err(format_err!("account argument must be nonnegative"));
|
return Err(format_err!("account argument must be nonnegative"));
|
||||||
};
|
};
|
||||||
let extsk = utils::java_string_to_rust(&env, extsk);
|
let usk = decode_usk(&env, usk)?;
|
||||||
let to = utils::java_string_to_rust(&env, to);
|
let to = utils::java_string_to_rust(&env, to);
|
||||||
let value =
|
let value =
|
||||||
Amount::from_i64(value).map_err(|()| format_err!("Invalid amount, out of range"))?;
|
Amount::from_i64(value).map_err(|()| format_err!("Invalid amount, out of range"))?;
|
||||||
|
@ -1250,15 +1263,6 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createToAdd
|
||||||
let spend_params = utils::java_string_to_rust(&env, spend_params);
|
let spend_params = utils::java_string_to_rust(&env, spend_params);
|
||||||
let output_params = utils::java_string_to_rust(&env, output_params);
|
let output_params = utils::java_string_to_rust(&env, output_params);
|
||||||
|
|
||||||
let extsk =
|
|
||||||
match decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &extsk)
|
|
||||||
{
|
|
||||||
Ok(extsk) => extsk,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(format_err!("Invalid ExtendedSpendingKey: {}", e));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let to = match RecipientAddress::decode(&network, &to) {
|
let to = match RecipientAddress::decode(&network, &to) {
|
||||||
Some(to) => to,
|
Some(to) => to,
|
||||||
None => {
|
None => {
|
||||||
|
@ -1284,7 +1288,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createToAdd
|
||||||
&network,
|
&network,
|
||||||
prover,
|
prover,
|
||||||
AccountId::from(account),
|
AccountId::from(account),
|
||||||
&extsk,
|
usk.sapling(),
|
||||||
&to,
|
&to,
|
||||||
value,
|
value,
|
||||||
memo,
|
memo,
|
||||||
|
@ -1302,7 +1306,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_shieldToAdd
|
||||||
_: JClass<'_>,
|
_: JClass<'_>,
|
||||||
db_data: JString<'_>,
|
db_data: JString<'_>,
|
||||||
account: jint,
|
account: jint,
|
||||||
xprv: JString<'_>,
|
usk: jbyteArray,
|
||||||
memo: jbyteArray,
|
memo: jbyteArray,
|
||||||
spend_params: JString<'_>,
|
spend_params: JString<'_>,
|
||||||
output_params: JString<'_>,
|
output_params: JString<'_>,
|
||||||
|
@ -1312,25 +1316,16 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_shieldToAdd
|
||||||
let network = parse_network(network_id as u32)?;
|
let network = parse_network(network_id as u32)?;
|
||||||
let db_data = wallet_db(&env, network, db_data)?;
|
let db_data = wallet_db(&env, network, db_data)?;
|
||||||
let mut db_data = db_data.get_update_ops()?;
|
let mut db_data = db_data.get_update_ops()?;
|
||||||
let account = if account == 0 {
|
let account = if account >= 0 {
|
||||||
account as u32
|
account as u32
|
||||||
} else {
|
} else {
|
||||||
return Err(format_err!(
|
return Err(format_err!("account argument must be nonnegative"));
|
||||||
"account argument {} must be nonnegative",
|
|
||||||
account
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
let xprv_str = utils::java_string_to_rust(&env, xprv);
|
let usk = decode_usk(&env, usk)?;
|
||||||
let memo_bytes = env.convert_byte_array(memo).unwrap();
|
let memo_bytes = env.convert_byte_array(memo).unwrap();
|
||||||
let spend_params = utils::java_string_to_rust(&env, spend_params);
|
let spend_params = utils::java_string_to_rust(&env, spend_params);
|
||||||
let output_params = utils::java_string_to_rust(&env, output_params);
|
let output_params = utils::java_string_to_rust(&env, output_params);
|
||||||
|
|
||||||
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 = AccountPrivKey::from_extended_privkey(xprv.extended_key);
|
|
||||||
|
|
||||||
let memo = Memo::from_bytes(&memo_bytes).unwrap();
|
let memo = Memo::from_bytes(&memo_bytes).unwrap();
|
||||||
|
|
||||||
let prover = LocalTxProver::new(Path::new(&spend_params), Path::new(&output_params));
|
let prover = LocalTxProver::new(Path::new(&spend_params), Path::new(&output_params));
|
||||||
|
@ -1339,7 +1334,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_shieldToAdd
|
||||||
&mut db_data,
|
&mut db_data,
|
||||||
&network,
|
&network,
|
||||||
prover,
|
prover,
|
||||||
&sk,
|
usk.transparent(),
|
||||||
AccountId::from(account),
|
AccountId::from(account),
|
||||||
&MemoBytes::from(&memo),
|
&MemoBytes::from(&memo),
|
||||||
0,
|
0,
|
||||||
|
|
Loading…
Reference in New Issue