Add Account type

This commit is contained in:
Carter Jernigan 2022-10-06 13:44:34 -04:00 committed by Carter Jernigan
parent 88bbd0afcb
commit 4c2fdeeb73
27 changed files with 155 additions and 152 deletions

View File

@ -10,6 +10,7 @@ Change Log
- `Synchronizer.getLegacyTransparentAddress`
- `Synchronizer.isValidUnifiedAddr`
- `cash.z.ecc.android.sdk.model`:
- `Account`
- `FirstClassByteArray`
- `UnifiedSpendingKey`
- `cash.z.ecc.android.sdk.tool`:
@ -43,6 +44,7 @@ Change Log
the `UnifiedSpendingKey`.
- `Synchronizer.shieldFunds` now takes a `UnifiedSpendingKey` instead of separately
encoded Sapling and transparent keys.
- `Synchronizer` methods that previously took an `Int` for account index now take an `Account` object
### Removed
- `cash.z.ecc.android.sdk`:

View File

@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk.darkside.test
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.Darkside
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
@ -214,8 +215,8 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
assertTrue("invalid total balance. Expected a minimum of $total but found ${balance?.total}", total <= balance?.total?.value!!)
}
}
suspend fun validateBalance(available: Long = -1, total: Long = -1, accountIndex: Int = 0) {
val balance = synchronizer.processor.getBalanceInfo(accountIndex)
suspend fun validateBalance(available: Long = -1, total: Long = -1, account: Account) {
val balance = synchronizer.processor.getBalanceInfo(account)
if (available > 0) {
assertEquals("invalid available balance", available, balance.available)
}

View File

@ -9,6 +9,7 @@ import cash.z.ecc.android.sdk.db.entity.isPending
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.Darkside
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
@ -63,9 +64,9 @@ class TestWallet(
private val context = InstrumentationRegistry.getInstrumentation().context
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
private val shieldedSpendingKey =
runBlocking { DerivationTool.deriveSpendingKeys(seed, network = network)[0] }
runBlocking { DerivationTool.deriveUnifiedSpendingKey(seed, network = network, Account.DEFAULT) }
private val transparentAccountPrivateKey =
runBlocking { DerivationTool.deriveTransparentAccountPrivateKey(seed, network = network) }
runBlocking { DerivationTool.deriveTransparentAccountPrivateKey(seed, network = network, Account.DEFAULT) }
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(
context,
network,
@ -80,9 +81,9 @@ class TestWallet(
val available get() = synchronizer.saplingBalances.value?.available
val unifiedAddress =
runBlocking { DerivationTool.deriveUnifiedAddress(seed, network = network) }
runBlocking { DerivationTool.deriveUnifiedAddress(seed, network = network, Account.DEFAULT) }
val transparentAddress =
runBlocking { DerivationTool.deriveTransparentAddress(seed, network = network) }
runBlocking { DerivationTool.deriveTransparentAddress(seed, network = network, Account.DEFAULT) }
val birthdayHeight get() = synchronizer.latestBirthdayHeight
val networkName get() = synchronizer.network.networkName
@ -112,9 +113,9 @@ class TestWallet(
return this
}
suspend fun send(address: String = transparentAddress, memo: String = "", amount: Zatoshi = Zatoshi(500L), fromAccountIndex: Int = 0): TestWallet {
suspend fun send(address: String = transparentAddress, memo: String = "", amount: Zatoshi = Zatoshi(500L)): TestWallet {
Twig.sprout("$alias sending")
synchronizer.sendToAddress(shieldedSpendingKey, amount, address, memo, fromAccountIndex)
synchronizer.sendToAddress(shieldedSpendingKey, amount, address, memo)
.takeWhile { it.isPending(null) }
.collect {
twig("Updated transaction: $it")

View File

@ -10,6 +10,7 @@ import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.Mainnet
@ -61,19 +62,6 @@ class SampleCodeTest {
// log(entropy.asSeedPhrase())
}
// ///////////////////////////////////////////////////
// Derive Extended Spending Key
@Test fun deriveSpendingKey() {
val spendingKeys = runBlocking {
DerivationTool.deriveSpendingKeys(
seed,
ZcashNetwork.Mainnet
)
}
assertEquals(1, spendingKeys.size)
log("Spending Key: ${spendingKeys[0]}")
}
// ///////////////////////////////////////////////////
// Get Address
@Test fun getAddress() = runBlocking {
@ -143,7 +131,7 @@ class SampleCodeTest {
val amount = 0.123.convertZecToZatoshi()
val address = "ztestsapling1tklsjr0wyw0d58f3p7wufvrj2cyfv6q6caumyueadq8qvqt8lda6v6tpx474rfru9y6u75u7qnw"
val memo = "Test Transaction"
val spendingKey = DerivationTool.deriveSpendingKeys(seed, ZcashNetwork.Mainnet)[0]
val spendingKey = DerivationTool.deriveUnifiedSpendingKey(seed, ZcashNetwork.Mainnet, Account.DEFAULT)
val transactionFlow = synchronizer.sendToAddress(spendingKey, amount, address, memo)
transactionFlow.collect {
log("pending transaction updated $it")

View File

@ -11,6 +11,7 @@ import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetPrivateKeyBinding
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.launch
@ -46,7 +47,7 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
val spendingKey = DerivationTool.deriveUnifiedSpendingKey(
seed,
ZcashNetwork.fromResources(requireApplicationContext()),
5
Account(5)
)
// derive the key that allows you to view but not spend transactions

View File

@ -17,6 +17,7 @@ import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.ext.collectWith
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.model.defaultForNetwork
@ -53,7 +54,8 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
address = runBlocking {
DerivationTool.deriveUnifiedAddress(
seed,
ZcashNetwork.fromResources(requireApplicationContext())
ZcashNetwork.fromResources(requireApplicationContext()),
Account.DEFAULT
)
}
synchronizer = Synchronizer.newBlocking(

View File

@ -19,6 +19,7 @@ import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.demoapp.util.mainActivity
import cash.z.ecc.android.sdk.ext.collectWith
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.ZcashNetwork
@ -176,7 +177,8 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
binding.inputAddress.setText(
DerivationTool.deriveTransparentAddress(
seed,
ZcashNetwork.fromResources(requireApplicationContext())
ZcashNetwork.fromResources(requireApplicationContext()),
Account.DEFAULT
)
)
}

View File

@ -29,6 +29,7 @@ import cash.z.ecc.android.sdk.ext.convertZecToZatoshi
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.model.Account
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance
@ -77,7 +78,11 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
birthday = null
)
spendingKey = runBlocking {
DerivationTool.deriveUnifiedSpendingKey(seed, ZcashNetwork.fromResources(requireApplicationContext()))
DerivationTool.deriveUnifiedSpendingKey(
seed,
ZcashNetwork.fromResources(requireApplicationContext()),
Account.DEFAULT
)
}
}

View File

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 7,
"identityHash": "8e6d7ff0e82352e1fa54e951a5006ba9",
"identityHash": "70ef5c6e545e393e2a67bc81143e82e6",
"entities": [
{
"tableName": "transactions",
@ -226,7 +226,7 @@
},
{
"tableName": "accounts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account` INTEGER, `extfvk` TEXT NOT NULL, `address` TEXT NOT NULL, `transparent_address` TEXT NOT NULL, PRIMARY KEY(`account`))",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account` INTEGER, `ufvk` TEXT, PRIMARY KEY(`account`))",
"fields": [
{
"fieldPath": "account",
@ -235,22 +235,10 @@
"notNull": false
},
{
"fieldPath": "extendedFullViewingKey",
"columnName": "extfvk",
"fieldPath": "unifiedFullViewingKey",
"columnName": "ufvk",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "address",
"columnName": "address",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "transparentAddress",
"columnName": "transparent_address",
"affinity": "TEXT",
"notNull": true
"notNull": false
}
],
"primaryKey": {
@ -264,7 +252,7 @@
},
{
"tableName": "sent_notes",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id_note` INTEGER, `tx` INTEGER NOT NULL, `output_index` INTEGER NOT NULL, `from_account` INTEGER NOT NULL, `address` TEXT NOT NULL, `value` INTEGER NOT NULL, `memo` BLOB, PRIMARY KEY(`id_note`), FOREIGN KEY(`tx`) REFERENCES `transactions`(`id_tx`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`from_account`) REFERENCES `accounts`(`account`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id_note` INTEGER, `tx` INTEGER NOT NULL, `output_pool` INTEGER NOT NULL, `output_index` INTEGER NOT NULL, `from_account` INTEGER NOT NULL, `address` TEXT NOT NULL, `value` INTEGER NOT NULL, `memo` BLOB, PRIMARY KEY(`id_note`), FOREIGN KEY(`tx`) REFERENCES `transactions`(`id_tx`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`from_account`) REFERENCES `accounts`(`account`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "id",
@ -278,6 +266,12 @@
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "outputPool",
"columnName": "output_pool",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "outputIndex",
"columnName": "output_index",
@ -419,7 +413,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8e6d7ff0e82352e1fa54e951a5006ba9')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '70ef5c6e545e393e2a67bc81143e82e6')"
]
}
}

View File

@ -11,6 +11,7 @@ import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.Zatoshi
@ -97,7 +98,7 @@ class TestnetIntegrationTest : ScopedTest() {
}
private suspend fun sendFunds(): Boolean {
val spendingKey = DerivationTool.deriveSpendingKeys(seed, synchronizer.network)[0]
val spendingKey = DerivationTool.deriveUnifiedSpendingKey(seed, synchronizer.network, Account.DEFAULT)
log("sending to address")
synchronizer.sendToAddress(
spendingKey,

View File

@ -10,19 +10,14 @@ import cash.z.ecc.android.sdk.db.entity.isCancelled
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.service.LightWalletService
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.test.ScopedTest
import cash.z.ecc.fixture.DatabaseNameFixture
import cash.z.ecc.fixture.DatabasePathFixture
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.stub
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.BeforeClass
@ -72,7 +67,7 @@ class PersistentTransactionManagerTest : ScopedTest() {
MockitoAnnotations.openMocks(this)
mockEncoder.stub {
onBlocking {
createTransaction(any(), any(), any(), any(), any())
createTransaction(any(), any(), any(), any())
}.thenAnswer {
runBlocking {
delay(200)
@ -82,33 +77,9 @@ class PersistentTransactionManagerTest : ScopedTest() {
}
}
@Test
fun testCancellation_RaceCondition() = runBlocking {
val tx = manager.initSpend(Zatoshi(1234), "taddr", "memo-good", 0)
val txFlow = manager.monitorById(tx.id)
// encode TX
testScope.launch {
twig("ENCODE: start"); manager.encode("fookey", tx); twig("ENCODE: end")
}
// then cancel it before it is done encoding
testScope.launch {
delay(100)
twig("CANCEL: start"); manager.cancel(tx.id); twig("CANCEL: end")
}
txFlow.drop(2).onEach {
twig("found tx: $it")
assertTrue(it.isCancelled(), "Expected the encoded tx to be cancelled but it wasn't")
twig("found it to be successfully cancelled")
testScope.cancel()
}.launchIn(testScope).join()
}
@Test
fun testCancel() = runBlocking {
var tx = manager.initSpend(Zatoshi(1234), "a", "b", 0)
var tx = manager.initSpend(Zatoshi(1234), "a", "b", Account.DEFAULT)
assertFalse(tx.isCancelled())
manager.cancel(tx.id)
tx = manager.findById(tx.id)!!
@ -117,7 +88,7 @@ class PersistentTransactionManagerTest : ScopedTest() {
@Test
fun testAbort() = runBlocking {
var tx: PendingTransaction? = manager.initSpend(Zatoshi(1234), "a", "b", 0)
var tx: PendingTransaction? = manager.initSpend(Zatoshi(1234), "a", "b", Account.DEFAULT)
assertNotNull(tx)
manager.abort(tx)
tx = manager.findById(tx.id)

View File

@ -7,6 +7,7 @@ import cash.z.ecc.android.sdk.annotation.MaintainedTest
import cash.z.ecc.android.sdk.annotation.TestPurpose
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
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.tool.DerivationTool
import kotlinx.coroutines.runBlocking
@ -23,17 +24,25 @@ class TransparentTest(val expected: Expected, val network: ZcashNetwork) {
@Test
fun deriveTransparentAccountPrivateKeyTest() = runBlocking {
assertEquals(expected.tAccountPrivKey, DerivationTool.deriveTransparentAccountPrivateKey(SEED, network = network))
assertEquals(
expected.tAccountPrivKey,
DerivationTool.deriveTransparentAccountPrivateKey(
SEED,
network =
network,
Account.DEFAULT
)
)
}
@Test
fun deriveTransparentAddressTest() = runBlocking {
assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddress(SEED, network = network))
assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddress(SEED, network = network, Account.DEFAULT))
}
@Test
fun deriveTransparentAddressFromAccountPrivateKeyTest() = runBlocking {
val pk = DerivationTool.deriveTransparentAccountPrivateKey(SEED, network = network)
val pk = DerivationTool.deriveTransparentAccountPrivateKey(SEED, network = network, Account.DEFAULT)
assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromAccountPrivateKey(pk, network = network))
}

View File

@ -1,6 +1,7 @@
package cash.z.ecc.android.sdk.util
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -36,7 +37,7 @@ class AddressGeneratorUtil {
.map { seedPhrase ->
mnemonics.toSeed(seedPhrase.toCharArray())
}.map { seed ->
DerivationTool.deriveUnifiedAddress(seed, ZcashNetwork.Mainnet)
DerivationTool.deriveUnifiedAddress(seed, ZcashNetwork.Mainnet, Account.DEFAULT)
}.collect { address ->
println("xrxrx2\t$address")
assertTrue(address.startsWith("u1"))

View File

@ -9,6 +9,7 @@ import cash.z.ecc.android.sdk.db.entity.isPending
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.Testnet
@ -62,10 +63,10 @@ class TestWallet(
private val context = InstrumentationRegistry.getInstrumentation().context
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
private val shieldedSpendingKey =
runBlocking { DerivationTool.deriveSpendingKeys(seed, network = network)[0] }
private val spendingKey =
runBlocking { DerivationTool.deriveUnifiedSpendingKey(seed, network = network, Account.DEFAULT) }
private val transparentAccountPrivateKey =
runBlocking { DerivationTool.deriveTransparentAccountPrivateKey(seed, network = network) }
runBlocking { DerivationTool.deriveTransparentAccountPrivateKey(seed, network = network, Account.DEFAULT) }
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(
context,
network,
@ -78,9 +79,9 @@ class TestWallet(
val available get() = synchronizer.saplingBalances.value?.available
val unifiedAddress =
runBlocking { DerivationTool.deriveUnifiedAddress(seed, network = network) }
runBlocking { DerivationTool.deriveUnifiedAddress(seed, network = network, Account.DEFAULT) }
val transparentAddress =
runBlocking { DerivationTool.deriveTransparentAddress(seed, network = network) }
runBlocking { DerivationTool.deriveTransparentAddress(seed, network = network, Account.DEFAULT) }
val birthdayHeight get() = synchronizer.latestBirthdayHeight
val networkName get() = synchronizer.network.networkName
@ -113,11 +114,10 @@ class TestWallet(
suspend fun send(
address: String = transparentAddress,
memo: String = "",
amount: Zatoshi = Zatoshi(500L),
fromAccountIndex: Int = 0
amount: Zatoshi = Zatoshi(500L)
): TestWallet {
Twig.sprout("$alias sending")
synchronizer.sendToAddress(shieldedSpendingKey, amount, address, memo, fromAccountIndex)
synchronizer.sendToAddress(spendingKey, amount, address, memo)
.takeWhile { it.isPending(null) }
.collect {
twig("Updated transaction: $it")
@ -141,7 +141,7 @@ class TestWallet(
twig("FOUND utxo balance of total: ${walletBalance.total} available: ${walletBalance.available}")
if (walletBalance.available.value > 0L) {
synchronizer.shieldFunds(shieldedSpendingKey, transparentAccountPrivateKey)
synchronizer.shieldFunds(spendingKey, transparentAccountPrivateKey)
.onCompletion { twig("done shielding funds") }
.catch { twig("Failed with $it") }
.collect()

View File

@ -51,6 +51,7 @@ import cash.z.ecc.android.sdk.internal.transaction.WalletTransactionEncoder
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.internal.twigTask
import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
@ -379,7 +380,7 @@ class SdkSynchronizer internal constructor(
suspend fun refreshSaplingBalance() {
twig("refreshing sapling balance")
_saplingBalances.value = processor.getBalanceInfo()
_saplingBalances.value = processor.getBalanceInfo(Account.DEFAULT)
}
suspend fun refreshTransparentBalance() {
@ -645,26 +646,26 @@ class SdkSynchronizer internal constructor(
/**
* Returns the current Unified Address for this account.
*/
override suspend fun getCurrentAddress(accountId: Int): String =
processor.getCurrentAddress(accountId)
override suspend fun getCurrentAddress(account: Account): String =
processor.getCurrentAddress(account)
/**
* Returns the legacy Sapling address corresponding to the current Unified Address for this account.
*/
override suspend fun getLegacySaplingAddress(accountId: Int): String =
processor.getLegacySaplingAddress(accountId)
override suspend fun getLegacySaplingAddress(account: Account): String =
processor.getLegacySaplingAddress(account)
/**
* Returns the legacy transparent address corresponding to the current Unified Address for this account.
*/
override suspend fun getLegacyTransparentAddress(accountId: Int): String =
processor.getTransparentAddress(accountId)
override suspend fun getLegacyTransparentAddress(account: Account): String =
processor.getTransparentAddress(account)
override fun sendToAddress(
usk: UnifiedSpendingKey,
amount: Zatoshi,
toAddress: String,
memo: String,
memo: String
): Flow<PendingTransaction> = flow {
twig("Initializing pending transaction")
// Emit the placeholder transaction, then switch to monitoring the database

View File

@ -6,6 +6,7 @@ import cash.z.ecc.android.sdk.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
@ -189,8 +190,8 @@ interface Synchronizer {
* @return the newly created ZIP 316 account identifier, along with the binary
* encoding of the `UnifiedSpendingKey` for the newly created account.
*/
// This is not yet ready to be a public API
// suspend fun createAccount(seed: ByteArray): UnifiedSpendingKey
// This is not yet ready to be a public API
// suspend fun createAccount(seed: ByteArray): UnifiedSpendingKey
/**
* Gets the current unified address for the given account.
@ -200,27 +201,27 @@ interface Synchronizer {
*
* @return the current unified address for the given account.
*/
suspend fun getCurrentAddress(accountId: Int = 0): String
suspend fun getCurrentAddress(account: Account = Account.DEFAULT): String
/**
* 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 account the optional accountId whose address is of interest. By default, the first
* account is used.
*
* @return a legacy Sapling address for the given account.
*/
suspend fun getLegacySaplingAddress(accountId: Int = 0): String
suspend fun getLegacySaplingAddress(account: Account = Account.DEFAULT): String
/**
* 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 account the optional accountId whose address is of interest. By default, the first
* account is used.
*
* @return a legacy transparent address for the given account.
*/
suspend fun getLegacyTransparentAddress(accountId: Int = 0): String
suspend fun getLegacyTransparentAddress(account: Account = Account.DEFAULT): String
/**
* Sends zatoshi.
@ -239,7 +240,7 @@ interface Synchronizer {
usk: UnifiedSpendingKey,
amount: Zatoshi,
toAddress: String,
memo: String = "",
memo: String = ""
): Flow<PendingTransaction>
fun shieldFunds(

View File

@ -40,6 +40,7 @@ import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.internal.twigTask
import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.jni.RustBackendWelding
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance
@ -1027,17 +1028,17 @@ class CompactBlockProcessor internal constructor(
*
* @return the current unified address of this account.
*/
suspend fun getCurrentAddress(accountId: Int = 0) =
rustBackend.getCurrentAddress(accountId)
suspend fun getCurrentAddress(account: Account) =
rustBackend.getCurrentAddress(account.value)
/**
* 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) =
suspend fun getLegacySaplingAddress(account: Account) =
rustBackend.getSaplingReceiver(
rustBackend.getCurrentAddress(accountId)
rustBackend.getCurrentAddress(account.value)
)
?: throw InitializerException.MissingAddressException("legacy Sapling")
@ -1046,26 +1047,26 @@ class CompactBlockProcessor internal constructor(
*
* @return a transparent address.
*/
suspend fun getTransparentAddress(accountId: Int = 0) =
suspend fun getTransparentAddress(account: Account) =
rustBackend.getTransparentReceiver(
rustBackend.getCurrentAddress(accountId)
rustBackend.getCurrentAddress(account.value)
)
?: throw InitializerException.MissingAddressException("legacy transparent")
/**
* Calculates the latest balance info. Defaults to the first account.
*
* @param accountIndex the account to check for balance info.
* @param account the account to check for balance info.
*
* @return an instance of WalletBalance containing information about available and total funds.
*/
suspend fun getBalanceInfo(accountIndex: Int = 0): WalletBalance =
suspend fun getBalanceInfo(account: Account): WalletBalance =
twigTask("checking balance info", -1) {
@Suppress("TooGenericExceptionCaught")
try {
val balanceTotal = rustBackend.getBalance(accountIndex)
val balanceTotal = rustBackend.getBalance(account.value)
twig("found total balance: $balanceTotal")
val balanceAvailable = rustBackend.getVerifiedBalance(accountIndex)
val balanceAvailable = rustBackend.getVerifiedBalance(account.value)
twig("found available balance: $balanceAvailable")
WalletBalance(balanceTotal, balanceAvailable)
} catch (t: Throwable) {

View File

@ -12,6 +12,7 @@ import cash.z.ecc.android.sdk.internal.db.PendingTransactionDao
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.twig
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.Zatoshi
@ -76,14 +77,14 @@ class PersistentTransactionManager(
zatoshi: Zatoshi,
toAddress: String,
memo: String,
fromAccountIndex: Int
account: Account
): PendingTransaction = withContext(Dispatchers.IO) {
twig("constructing a placeholder transaction")
var tx = PendingTransactionEntity(
value = zatoshi.value,
toAddress = toAddress,
memo = memo.toByteArray(),
accountIndex = fromAccountIndex
accountIndex = account.value
)
@Suppress("TooGenericExceptionCaught")
try {
@ -114,7 +115,7 @@ class PersistentTransactionManager(
): PendingTransaction = withContext(Dispatchers.IO) {
twig("managing the creation of a transaction")
var tx = pendingTx as PendingTransactionEntity
if (tx.accountIndex != usk.account) {
if (tx.accountIndex != usk.account.value) {
throw java.lang.IllegalArgumentException("usk is not for the same account as pendingTx")
}
@ -125,7 +126,7 @@ class PersistentTransactionManager(
usk,
tx.valueZatoshi,
tx.toAddress,
tx.memo,
tx.memo
)
twig("successfully encoded transaction!")
safeUpdate("updating transaction encoding", -1) {

View File

@ -21,7 +21,7 @@ interface TransactionEncoder {
usk: UnifiedSpendingKey,
amount: Zatoshi,
toAddress: String,
memo: ByteArray? = byteArrayOf(),
memo: ByteArray? = byteArrayOf()
): EncodedTransaction
/**

View File

@ -1,6 +1,7 @@
package cash.z.ecc.android.sdk.internal.transaction
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.Zatoshi
@ -20,7 +21,7 @@ interface OutboundTransactionManager {
* @param zatoshi the amount to spend.
* @param toAddress the address to which funds will be sent.
* @param memo the optionally blank memo associated with this transaction.
* @param fromAccountIndex the account from which to spend funds.
* @param account the account from which to spend funds.
*
* @return the associated pending transaction whose ID can be used to monitor for changes.
*/
@ -28,7 +29,7 @@ interface OutboundTransactionManager {
zatoshi: Zatoshi,
toAddress: String,
memo: String,
fromAccountIndex: Int
account: Account
): PendingTransaction
/**

View File

@ -41,7 +41,7 @@ internal class WalletTransactionEncoder(
usk: UnifiedSpendingKey,
amount: Zatoshi,
toAddress: String,
memo: ByteArray?,
memo: ByteArray?
): EncodedTransaction {
val transactionId = createSpend(usk, amount, toAddress, memo)
return repository.findEncodedTransactionById(transactionId)
@ -114,7 +114,7 @@ internal class WalletTransactionEncoder(
usk: UnifiedSpendingKey,
amount: Zatoshi,
toAddress: String,
memo: ByteArray? = byteArrayOf(),
memo: ByteArray? = byteArrayOf()
): Long {
return twigTask(
"creating transaction to spend $amount zatoshi to" +

View File

@ -232,7 +232,7 @@ internal class RustBackend private constructor(
): Long = withContext(SdkDispatchers.DATABASE_IO) {
createToAddress(
dataDbFile.absolutePath,
usk.account,
usk.account.value,
usk.bytes.byteArray,
to,
value,
@ -251,7 +251,7 @@ internal class RustBackend private constructor(
return withContext(SdkDispatchers.DATABASE_IO) {
shieldToAddress(
dataDbFile.absolutePath,
usk.account,
usk.account.value,
usk.bytes.byteArray,
memo ?: ByteArray(0),
"$pathParamsDir/$SPEND_PARAM_FILE_NAME",

View File

@ -1,6 +1,7 @@
package cash.z.ecc.android.sdk.jni
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance
@ -106,19 +107,19 @@ internal interface RustBackendWelding {
suspend fun deriveUnifiedAddress(
seed: ByteArray,
network: ZcashNetwork,
accountIndex: Int = 0
account: Account
): String
suspend fun deriveUnifiedSpendingKey(
seed: ByteArray,
network: ZcashNetwork,
account: Int = 0
account: Account
): UnifiedSpendingKey
suspend fun deriveTransparentAddress(
seed: ByteArray,
network: ZcashNetwork,
account: Int = 0,
account: Account,
index: Int = 0
): String
@ -136,7 +137,7 @@ internal interface RustBackendWelding {
suspend fun deriveTransparentAccountPrivateKey(
seed: ByteArray,
network: ZcashNetwork,
account: Int = 0
account: Account
): String
suspend fun deriveUnifiedFullViewingKey(

View File

@ -0,0 +1,16 @@
package cash.z.ecc.android.sdk.model
/**
* A [ZIP 316](https://zips.z.cash/zip-0316) account identifier.
*
* @param value A 0-based account index. Must be >= 0.
*/
data class Account(val value: Int) {
init {
require(value >= 0) { "Account index must be >= 0 but actually is $value" }
}
companion object {
val DEFAULT = Account(0)
}
}

View File

@ -12,10 +12,7 @@ import cash.z.ecc.android.sdk.jni.RustBackend
* export/import, or backup purposes.
*/
data class UnifiedSpendingKey internal constructor(
/**
* A [ZIP 316](https://zips.z.cash/zip-0316) account identifier.
*/
val account: Int,
val account: Account,
/**
* The binary encoding of the [ZIP 316](https://zips.z.cash/zip-0316) Unified Spending
@ -33,14 +30,22 @@ data class UnifiedSpendingKey internal constructor(
fun copyBytes() = bytes.byteArray.copyOf()
companion object {
suspend fun new(account: Int, bytes: ByteArray): Result<UnifiedSpendingKey> {
/**
* This method may fail if the [bytes] no longer represent a valid key. A key could become invalid due to
* network upgrades or other internal changes. If a non-successful result is returned, clients are expected
* to use [DerivationTool.deriveUnifiedSpendingKey] to regenerate the key from the seed.
*
* @return A validated UnifiedSpendingKey.
*/
suspend fun new(account: Account, bytes: ByteArray): Result<UnifiedSpendingKey> {
val bytesCopy = bytes.copyOf()
RustBackend.rustLibraryLoader.load()
return Result.runCatching {
return 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)))
UnifiedSpendingKey(account, FirstClassByteArray(bytesCopy))
}
}
}

View File

@ -2,6 +2,7 @@ package cash.z.ecc.android.sdk.tool
import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.jni.RustBackendWelding
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.type.UnifiedFullViewingKey
@ -63,23 +64,22 @@ class DerivationTool {
override suspend fun deriveUnifiedSpendingKey(
seed: ByteArray,
network: ZcashNetwork,
account: Int
account: Account
): UnifiedSpendingKey = withRustBackendLoaded {
deriveSpendingKey(seed, account, networkId = network.id)
deriveSpendingKey(seed, account.value, networkId = network.id)
}
/**
* Given a seed and account index, return the associated Unified Address.
*
* @param seed the seed from which to derive the address.
* @param accountIndex the index of the account to use for deriving the address. Multiple
* accounts are not fully supported so the default value of 1 is recommended.
* @param accountIndex the index of the account to use for deriving the address.
*
* @return the address that corresponds to the seed and account index.
*/
override suspend fun deriveUnifiedAddress(seed: ByteArray, network: ZcashNetwork, accountIndex: Int): String =
override suspend fun deriveUnifiedAddress(seed: ByteArray, network: ZcashNetwork, account: Account): String =
withRustBackendLoaded {
deriveUnifiedAddressFromSeed(seed, accountIndex, networkId = network.id)
deriveUnifiedAddressFromSeed(seed, account.value, networkId = network.id)
}
/**
@ -103,10 +103,10 @@ class DerivationTool {
override suspend fun deriveTransparentAddress(
seed: ByteArray,
network: ZcashNetwork,
account: Int,
account: Account,
index: Int
): String = withRustBackendLoaded {
deriveTransparentAddressFromSeed(seed, account, index, networkId = network.id)
deriveTransparentAddressFromSeed(seed, account.value, index, networkId = network.id)
}
override suspend fun deriveTransparentAddressFromPublicKey(
@ -127,9 +127,9 @@ class DerivationTool {
override suspend fun deriveTransparentAccountPrivateKey(
seed: ByteArray,
network: ZcashNetwork,
account: Int
account: Account
): String = withRustBackendLoaded {
deriveTransparentAccountPrivKeyFromSeed(seed, account, networkId = network.id)
deriveTransparentAccountPrivKeyFromSeed(seed, account.value, networkId = network.id)
}
@Suppress("UNUSED_PARAMETER")

View File

@ -1,7 +1,5 @@
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.
*