New: Explicitly declare network when needed.

It is important to be very explicit about the network and not make any assumptions for ease of use because that resulted in numerous bugs while transitioning away from the old two library setup.

squash explicit network
This commit is contained in:
Kevin Gorham 2021-04-09 21:43:07 -04:00
parent 9dd9eef9ee
commit af190e786b
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
18 changed files with 562 additions and 271 deletions

View File

@ -27,12 +27,14 @@ import org.mockito.Spy
@RunWith(AndroidJUnit4::class)
class ChangeServiceTest : ScopedTest() {
val network = ZcashNetwork.Mainnet
@Mock
lateinit var mockBlockStore: CompactBlockStore
var mockCloseable: AutoCloseable? = null
@Spy
val service = LightWalletGrpcService(context, ZcashSdk.DEFAULT_LIGHTWALLETD_HOST)
val service = LightWalletGrpcService(context, network)
lateinit var downloader: CompactBlockDownloader
lateinit var otherService: LightWalletService
@ -105,7 +107,7 @@ class ChangeServiceTest : ScopedTest() {
@Test
fun testSwitchToTestnetFails() = runBlocking {
var caughtException: Throwable? = null
downloader.changeService(LightWalletGrpcService(context, "testnet.lightwalletd.com", 9067)) {
downloader.changeService(LightWalletGrpcService(context, ZcashNetwork.Testnet)) {
caughtException = it
}
assertNotNull("Using an invalid host should generate an exception.", caughtException)

View File

@ -17,34 +17,41 @@ import org.junit.runner.RunWith
class TransparentTest {
lateinit var expected: Expected
lateinit var network: ZcashNetwork
@Before
fun setup() {
expected = if (BuildConfig.FLAVOR == "zcashtestnet") ExpectedTestnet else ExpectedMainnet
if (BuildConfig.FLAVOR == "zcashtestnet") {
expected = ExpectedTestnet
network = ZcashNetwork.Testnet
} else {
expected = ExpectedMainnet
network = ZcashNetwork.Mainnet
}
}
@Test
fun deriveTransparentSecretKeyTest() {
assertEquals(expected.tskCompressed, DerivationTool.deriveTransparentSecretKey(SEED))
assertEquals(expected.tskCompressed, DerivationTool.deriveTransparentSecretKey(SEED, network = network))
}
@Test
fun deriveTransparentAddressTest() {
assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddress(SEED))
assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddress(SEED, network = network))
}
@Test
fun deriveTransparentAddressFromSecretKeyTest() {
assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromPrivateKey(expected.tskCompressed))
assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromPrivateKey(expected.tskCompressed, network = network))
}
@Test
fun deriveUnifiedViewingKeysFromSeedTest() {
val uvks = DerivationTool.deriveUnifiedViewingKeys(SEED)
val uvks = DerivationTool.deriveUnifiedViewingKeys(SEED, network = network)
assertEquals(1, uvks.size)
val uvk = uvks.first()
assertEquals(expected.zAddr, DerivationTool.deriveShieldedAddress(uvk.extfvk))
assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromPublicKey(uvk.extpub))
assertEquals(expected.zAddr, DerivationTool.deriveShieldedAddress(uvk.extfvk, network = network))
assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromPublicKey(uvk.extpub, network = network))
}
// @Test

View File

@ -56,17 +56,17 @@ class ShieldFundsSample {
)
private val context = InstrumentationRegistry.getInstrumentation().context
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
private val shieldedSpendingKey = DerivationTool.deriveSpendingKeys(seed)[0]
private val transparentSecretKey = DerivationTool.deriveTransparentSecretKey(seed)
private val shieldedAddress = DerivationTool.deriveShieldedAddress(seed)
private val shieldedSpendingKey = DerivationTool.deriveSpendingKeys(seed, Testnet)[0]
private val transparentSecretKey = DerivationTool.deriveTransparentSecretKey(seed, Testnet)
private val shieldedAddress = DerivationTool.deriveShieldedAddress(seed, Testnet)
// t1b9Y6PESSGavavgge3ruTtX9X83817V29s
private val transparentAddress = DerivationTool.deriveTransparentAddress(seed)
private val transparentAddress = DerivationTool.deriveTransparentAddress(seed, Testnet)
private val config = Initializer.Config {
it.setSeed(seed)
it.setSeed(seed, Testnet)
it.setBirthdayHeight(startHeight, false)
it.server("lightwalletd.electriccoin.co", 9067)
it.setNetwork(Testnet, host)
}
val synchronizer = Synchronizer(Initializer(context, config))

View File

@ -2,6 +2,7 @@ package cash.z.ecc.android.sdk.util
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
@ -35,7 +36,7 @@ class AddressGeneratorUtil {
.map { seedPhrase ->
mnemonics.toSeed(seedPhrase.toCharArray())
}.map { seed ->
DerivationTool.deriveShieldedAddress(seed)
DerivationTool.deriveShieldedAddress(seed, ZcashNetwork.Mainnet)
}.collect { address ->
println("xrxrx2\t$address")
assertTrue(address.startsWith("zs1"))

View File

@ -8,6 +8,7 @@ import cash.z.ecc.android.sdk.ext.Twig
import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
import cash.z.ecc.android.sdk.type.WalletBirthday
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
@ -26,8 +27,7 @@ import java.io.IOException
@ExperimentalCoroutinesApi
class BalancePrinterUtil {
private val host = "lightd-main.zecwallet.co"
private val port = 443
private val network = ZcashNetwork.Mainnet
private val downloadBatchSize = 9_000
private val birthdayHeight = 523240
@ -52,7 +52,7 @@ class BalancePrinterUtil {
fun setup() {
Twig.plant(TroubleshootingTwig())
cacheBlocks()
birthday = WalletBirthdayTool.loadNearest(context, birthdayHeight)
birthday = WalletBirthdayTool.loadNearest(context, network, birthdayHeight)
}
private fun cacheBlocks() = runBlocking {
@ -80,7 +80,7 @@ class BalancePrinterUtil {
// TODO: clear the dataDb but leave the cacheDb
val initializer = Initializer(context) { config ->
config.importWallet(seed, birthdayHeight)
config.server(host, port)
config.setNetwork(network)
config.alias = alias
}
/*

View File

@ -2,9 +2,9 @@ package cash.z.ecc.android.sdk.util
import android.content.Context
import cash.z.ecc.android.sdk.R
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.sdk.service.LightWalletGrpcService
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.wallet.sdk.rpc.Darkside
import cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL
import cash.z.wallet.sdk.rpc.DarksideStreamerGrpc
@ -23,7 +23,7 @@ class DarksideApi(
constructor(
appContext: Context,
host: String,
port: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT,
port: Int = ZcashNetwork.Mainnet.defaultPort,
usePlainText: Boolean = appContext.resources.getBoolean(
R.bool.lightwalletd_allow_very_insecure_connections
)
@ -42,8 +42,8 @@ class DarksideApi(
fun reset(
saplingActivationHeight: Int = 419200,
branchId: String = "2bb40e60", // Blossom,
chainName: String = "darkside"
branchId: String = "e9ff75a6", // Canopy,
chainName: String = "darkside${ZcashNetwork.Mainnet.networkName}"
) = apply {
twig("resetting darksidewalletd with saplingActivation=$saplingActivationHeight branchId=$branchId chainName=$chainName")
Darkside.DarksideMetaState.newBuilder()

View File

@ -8,6 +8,7 @@ import cash.z.ecc.android.sdk.ext.Twig
import cash.z.ecc.android.sdk.ext.seedPhrase
import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.ZcashNetwork
import io.grpc.StatusRuntimeException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@ -21,8 +22,7 @@ import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
class DarksideTestCoordinator(val host: String = "127.0.0.1", val testName: String = "DarksideTestCoordinator") {
private val port = 9067
class DarksideTestCoordinator(val host: String = "127.0.0.1", val testName: String = "DarksideTestCoordinator", val network: ZcashNetwork = ZcashNetwork.Mainnet, val port: Int = 9067) {
private val birthdayHeight = 663150
private val targetHeight = 663250
private val seedPhrase =
@ -38,7 +38,7 @@ class DarksideTestCoordinator(val host: String = "127.0.0.1", val testName: Stri
// var initializer = Initializer(context, Initializer.Builder(host, port, testName))
lateinit var synchronizer: SdkSynchronizer
val spendingKey: String get() = DerivationTool.deriveSpendingKeys(SimpleMnemonics().toSeed(seedPhrase.toCharArray()))[0]
val spendingKey: String get() = DerivationTool.deriveSpendingKeys(SimpleMnemonics().toSeed(seedPhrase.toCharArray()), network)[0]
//
// High-level APIs
@ -75,7 +75,8 @@ class DarksideTestCoordinator(val host: String = "127.0.0.1", val testName: Stri
fun initiate() {
twig("*************** INITIALIZING TEST COORDINATOR (ONLY ONCE) ***********************")
val initializer = Initializer(context) { config ->
config.seedPhrase(seedPhrase)
config.setNetwork(network, host, port)
config.seedPhrase(seedPhrase, network)
config.setBirthdayHeight(birthdayHeight)
config.alias = testName
}

View File

@ -3,16 +3,17 @@ package cash.z.ecc.android.sdk.util
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.sdk.ext.TroubleshootingTwig
import cash.z.ecc.android.sdk.ext.Twig
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.sdk.service.LightWalletGrpcService
import cash.z.ecc.android.sdk.type.ZcashNetwork
import org.junit.Ignore
import org.junit.Test
class TransactionCounterUtil {
private val network = ZcashNetwork.Mainnet
private val context = InstrumentationRegistry.getInstrumentation().context
private val service = LightWalletGrpcService(context, ZcashSdk.DEFAULT_LIGHTWALLETD_HOST, ZcashSdk.DEFAULT_LIGHTWALLETD_PORT)
private val service = LightWalletGrpcService(context, network)
init {
Twig.plant(TroubleshootingTwig())

View File

@ -19,6 +19,7 @@ import java.io.File
class Initializer constructor(appContext: Context, config: Config) : SdkSynchronizer.SdkInitializer {
override val context = appContext.applicationContext
override val rustBackend: RustBackend
override val network: ZcashNetwork
override val alias: String
override val host: String
override val port: Int
@ -35,28 +36,30 @@ class Initializer constructor(appContext: Context, config: Config) : SdkSynchron
init {
config.validate()
network = config.network
val heightToUse = config.birthdayHeight
?: (if (config.defaultToOldestHeight == true) SAPLING_ACTIVATION_HEIGHT else null)
val loadedBirthday = WalletBirthdayTool.loadNearest(context, heightToUse)
val loadedBirthday = WalletBirthdayTool.loadNearest(context, network, heightToUse)
birthday = loadedBirthday
viewingKeys = config.viewingKeys
alias = config.alias
host = config.host
port = config.port
rustBackend = initRustBackend(birthday)
rustBackend = initRustBackend(network, birthday)
// TODO: get rid of this by first answering the question: why is this necessary?
initMissingDatabases(birthday, *viewingKeys.toTypedArray())
}
constructor(appContext: Context, block: (Config) -> Unit) : this(appContext, Config(block))
fun erase() = erase(context, alias)
fun erase() = erase(context, network, alias)
private fun initRustBackend(birthday: WalletBirthday): RustBackend {
private fun initRustBackend(network: ZcashNetwork, birthday: WalletBirthday): RustBackend {
return RustBackend.init(
cacheDbPath(context, alias),
dataDbPath(context, alias),
cacheDbPath(context, network, alias),
dataDbPath(context, network, alias),
"${context.cacheDir.absolutePath}/params",
network,
birthday.height
)
}
@ -137,12 +140,19 @@ class Initializer constructor(appContext: Context, config: Config) : SdkSynchron
class Config private constructor (
val viewingKeys: MutableList<UnifiedViewingKey> = mutableListOf(),
var alias: String = ZcashSdk.DEFAULT_ALIAS,
var host: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST,
var port: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT,
) {
var birthdayHeight: Int? = null
private set
lateinit var network: ZcashNetwork
private set
lateinit var host: String
private set
var port: Int = ZcashNetwork.Mainnet.defaultPort
private set
/**
* Determines the default behavior for null birthdays. When null, nothing has been specified
* so a null birthdayHeight value is an error. When false, null birthdays will be replaced
@ -229,53 +239,112 @@ class Initializer constructor(appContext: Context, config: Config) : SdkSynchron
// Convenience functions
//
fun server(host: String, port: Int): Config = apply {
/**
* Set the server and the network property at the same time to prevent them from getting out
* of sync. Ultimately, this determines which host a synchronizer will use in order to
* connect to lightwalletd. In most cases, the default host is sufficient but an override
* can be provided. The host cannot be changed without explicitly setting the network.
*
* @param network the Zcash network to use. Either testnet or mainnet.
* @param host the lightwalletd host to use.
* @param port the lightwalletd port to use.
*/
fun setNetwork(
network: ZcashNetwork,
host: String = network.defaultHost,
port: Int = network.defaultPort
): Config = apply {
this.network = network
this.host = host
this.port = port
}
fun importWallet(seed: ByteArray, birthdayHeight: Int? = null): Config = apply {
setSeed(seed)
importedWalletBirthday(birthdayHeight)
}
/**
* Import a wallet using the first viewing key derived from the given seed.
*/
fun importWallet(
seed: ByteArray,
birthdayHeight: Int? = null,
network: ZcashNetwork,
host: String = network.defaultHost,
port: Int = network.defaultPort,
alias: String = ZcashSdk.DEFAULT_ALIAS
): Config =
importWallet(
DerivationTool.deriveUnifiedViewingKeys(seed, network = network)[0],
birthdayHeight,
network,
host,
port,
alias
)
/**
* Default function for importing a wallet.
*/
fun importWallet(
viewingKey: UnifiedViewingKey,
birthdayHeight: Int? = null,
host: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST,
port: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT
network: ZcashNetwork,
host: String = network.defaultHost,
port: Int = network.defaultPort,
alias: String = ZcashSdk.DEFAULT_ALIAS
): Config = apply {
setViewingKeys(viewingKey)
server(host, port)
setNetwork(network, host, port)
importedWalletBirthday(birthdayHeight)
this.alias = alias
}
/**
* Create a new wallet using the first viewing key derived from the given seed.
*/
fun newWallet(
seed: ByteArray,
host: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST,
port: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT
): Config = apply {
setSeed(seed)
server(host, port)
newWalletBirthday()
}
network: ZcashNetwork,
host: String = network.defaultHost,
port: Int = network.defaultPort,
alias: String = ZcashSdk.DEFAULT_ALIAS
): Config = newWallet(
DerivationTool.deriveUnifiedViewingKeys(seed, network)[0],
network,
host,
port,
alias
)
/**
* Default function for creating a new wallet.
*/
fun newWallet(
viewingKey: UnifiedViewingKey,
host: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST,
port: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT
network: ZcashNetwork,
host: String = network.defaultHost,
port: Int = network.defaultPort,
alias: String = ZcashSdk.DEFAULT_ALIAS
): Config = apply {
setViewingKeys(viewingKey)
server(host, port)
setNetwork(network, host, port)
newWalletBirthday()
this.alias = alias
}
/**
* Convenience method for setting thew viewingKeys from a given seed. This is the same as
* calling `setViewingKeys` with the keys that match this seed.
*/
fun setSeed(seed: ByteArray, numberOfAccounts: Int = 1): Config = apply {
setViewingKeys(*DerivationTool.deriveUnifiedViewingKeys(seed, numberOfAccounts))
fun setSeed(seed: ByteArray, network: ZcashNetwork, numberOfAccounts: Int = 1): Config = apply {
setViewingKeys(*DerivationTool.deriveUnifiedViewingKeys(seed, network, numberOfAccounts))
}
/**
* Sets the network from a network id, throwing an exception if the id is not recognized.
*
* @param networkId the ID of the network corresponding to the [ZcashNetwork] enum.
* Typically, it is 0 for testnet and 1 for mainnet.
*/
fun setNetworkId(networkId: Int): Config = apply {
network = ZcashNetwork.from(networkId)
}
//
@ -299,7 +368,7 @@ class Initializer constructor(appContext: Context, config: Config) : SdkSynchron
(birthdayHeight ?: SAPLING_ACTIVATION_HEIGHT)
< SAPLING_ACTIVATION_HEIGHT
) {
throw InitializerException.InvalidBirthdayHeightException(birthdayHeight)
throw InitializerException.InvalidBirthdayHeightException(birthdayHeight, network)
}
}
@ -324,13 +393,17 @@ class Initializer constructor(appContext: Context, config: Config) : SdkSynchron
* managed separately by the wallet.
*
* @param appContext the application context.
* @param network the network associated with the data to be erased.
* @param alias the alias used to create the local data.
*
* @return true when associated files were found. False most likely indicates that the wrong
* alias was provided.
* @return true when one of the associated files was found. False most likely indicates
* that the wrong alias was provided.
*/
override fun erase(appContext: Context, alias: String) =
delete(cacheDbPath(appContext, alias)) || delete(dataDbPath(appContext, alias))
override fun erase(appContext: Context, network: ZcashNetwork, alias: String): Boolean {
val cacheDeleted = deleteDb(cacheDbPath(appContext, network, alias))
val dataDeleted = deleteDb(dataDbPath(appContext, network, alias))
return dataDeleted || cacheDeleted
}
//
// Path Helpers
@ -340,25 +413,29 @@ class Initializer constructor(appContext: Context, config: Config) : SdkSynchron
* Returns the path to the cache database that would correspond to the given alias.
*
* @param appContext the application context
* @param network the network associated with the data in the cache database.
* @param alias the alias to convert into a database path
*/
internal fun cacheDbPath(appContext: Context, alias: String): String =
aliasToPath(appContext, alias, ZcashSdk.DB_CACHE_NAME)
internal fun cacheDbPath(appContext: Context, network: ZcashNetwork, alias: String): String =
aliasToPath(appContext, network, alias, ZcashSdk.DB_CACHE_NAME)
/**
* Returns the path to the data database that would correspond to the given alias.
* @param appContext the application context
* * @param network the network associated with the data in the database.
* @param alias the alias to convert into a database path
*/
internal fun dataDbPath(appContext: Context, alias: String): String =
aliasToPath(appContext, alias, ZcashSdk.DB_DATA_NAME)
internal fun dataDbPath(appContext: Context, network: ZcashNetwork, alias: String): String =
aliasToPath(appContext, network, alias, ZcashSdk.DB_DATA_NAME)
private fun aliasToPath(appContext: Context, alias: String, dbFileName: String): String {
private fun aliasToPath(appContext: Context, network: ZcashNetwork, alias: String, dbFileName: String): String {
val parentDir: String =
appContext.getDatabasePath("unused.db").parentFile?.absolutePath
?: throw InitializerException.DatabasePathException
val prefix = if (alias.endsWith('_')) alias else "${alias}_"
return File(parentDir, "$prefix$dbFileName").absolutePath
return File(parentDir, "$prefix${network.networkName}_$dbFileName").absolutePath
}
}
/**
@ -398,16 +475,6 @@ internal fun validateAlias(alias: String) {
alias.all { it.isLetterOrDigit() || it == '_' }
) {
"ERROR: Invalid alias ($alias). For security, the alias must be shorter than 100 " +
"characters and only contain letters, digits or underscores and start with a letter; " +
"ideally, it would also differentiate across mainnet and testnet but that is not " +
"enforced."
}
// TODO: consider exposing this as a proper warning that can be received by apps, since most apps won't use logging
if (alias.toLowerCase().contains(BuildConfig.FLAVOR.toLowerCase())) {
twig(
"WARNING: alias does not contain the build flavor but it probably should to help" +
" prevent testnet data from contaminating mainnet data."
)
"characters and only contain letters, digits or underscores and start with a letter."
}
}

View File

@ -53,6 +53,7 @@ import cash.z.ecc.android.sdk.type.AddressType.Shielded
import cash.z.ecc.android.sdk.type.AddressType.Transparent
import cash.z.ecc.android.sdk.type.ConsensusMatchType
import cash.z.ecc.android.sdk.type.WalletBalance
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.wallet.sdk.rpc.Service
import io.grpc.ManagedChannel
import kotlinx.coroutines.CoroutineExceptionHandler
@ -146,6 +147,8 @@ class SdkSynchronizer internal constructor(
// Status
//
override val network: ZcashNetwork get() = processor.network
/**
* Indicates the status of this Synchronizer. This implementation basically simplifies the
* status of the processor to focus only on the high level states that matter most. Whenever the
@ -608,7 +611,7 @@ class SdkSynchronizer internal constructor(
memo: String
): Flow<PendingTransaction> = flow {
twig("Initializing shielding transaction")
val tAddr = DerivationTool.deriveTransparentAddressFromPrivateKey(transparentSecretKey)
val tAddr = DerivationTool.deriveTransparentAddressFromPrivateKey(transparentSecretKey, network)
val tBalance = processor.getUtxoCacheBalance(tAddr)
val zAddr = getAddress(0)
@ -677,6 +680,7 @@ class SdkSynchronizer internal constructor(
interface SdkInitializer {
val context: Context
val rustBackend: RustBackend
val network: ZcashNetwork
val host: String
val port: Int
val alias: String
@ -687,12 +691,14 @@ class SdkSynchronizer internal constructor(
* Erase content related to this SDK.
*
* @param appContext the application context.
* @param network the network corresponding to the data being erased. Data is segmented by
* network in order to prevent contamination.
* @param alias identifier for SDK content. It is possible for multiple synchronizers to
* exist with different aliases.
*
* @return true when content was found for the given alias. False otherwise.
*/
fun erase(appContext: Context, alias: String = ZcashSdk.DEFAULT_ALIAS): Boolean
fun erase(appContext: Context, network: ZcashNetwork, alias: String = ZcashSdk.DEFAULT_ALIAS): Boolean
}
}

View File

@ -8,6 +8,7 @@ import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.type.AddressType
import cash.z.ecc.android.sdk.type.ConsensusMatchType
import cash.z.ecc.android.sdk.type.WalletBalance
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.wallet.sdk.rpc.Service
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@ -58,6 +59,11 @@ interface Synchronizer {
/* Status */
/**
* The network to which this synchronizer is connected and from which it is processing blocks.
*/
val network: ZcashNetwork
/**
* A flow of values representing the [Status] of this Synchronizer. As the status changes, a new
* value will be emitted by this flow.
@ -264,7 +270,7 @@ interface Synchronizer {
*/
suspend fun changeServer(
host: String,
port: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT,
port: Int = network.defaultPort,
errorHandler: (Throwable) -> Unit = { throw it }
)

View File

@ -7,6 +7,7 @@ import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
import cash.z.ecc.android.sdk.type.WalletBalance
import cash.z.ecc.android.sdk.type.ZcashNetwork
import java.io.File
/**
@ -28,6 +29,8 @@ class RustBackend private constructor() : RustBackendWelding {
lateinit var pathParamsDir: String
internal set
override lateinit var network: ZcashNetwork
internal var birthdayHeight: Int = -1
get() = if (field != -1) field else throw BirthdayException.UninitializedBirthdayException
private set
@ -47,7 +50,7 @@ class RustBackend private constructor() : RustBackendWelding {
// Wrapper Functions
//
override fun initDataDb() = initDataDb(pathDataDb)
override fun initDataDb() = initDataDb(pathDataDb, networkId = network.id)
override fun initAccountsTable(vararg keys: UnifiedViewingKey): Boolean {
val extfvks = Array(keys.size) { "" }
@ -56,14 +59,14 @@ class RustBackend private constructor() : RustBackendWelding {
extfvks[i] = key.extfvk
extpubs[i] = key.extpub
}
return initAccountsTableWithKeys(pathDataDb, extfvks, extpubs)
return initAccountsTableWithKeys(pathDataDb, extfvks, extpubs, networkId = network.id)
}
override fun initAccountsTable(
seed: ByteArray,
numberOfAccounts: Int
): Array<UnifiedViewingKey> {
return DerivationTool.deriveUnifiedViewingKeys(seed, numberOfAccounts).apply {
return DerivationTool.deriveUnifiedViewingKeys(seed, network, numberOfAccounts).apply {
initAccountsTable(*this)
}
}
@ -74,37 +77,38 @@ class RustBackend private constructor() : RustBackendWelding {
time: Long,
saplingTree: String
): Boolean {
return initBlocksTable(pathDataDb, height, hash, time, saplingTree)
return initBlocksTable(pathDataDb, height, hash, time, saplingTree, networkId = network.id)
}
override fun getShieldedAddress(account: Int) = getShieldedAddress(pathDataDb, account)
override fun getShieldedAddress(account: Int) = getShieldedAddress(pathDataDb, account, networkId = network.id)
override fun getTransparentAddress(account: Int, index: Int): String {
throw NotImplementedError("TODO: implement this at the zcash_client_sqlite level. But for now, use DerivationTool, instead to derive addresses from seeds")
}
override fun getBalance(account: Int) = getBalance(pathDataDb, account)
override fun getBalance(account: Int) = getBalance(pathDataDb, account, networkId = network.id)
override fun getVerifiedBalance(account: Int) = getVerifiedBalance(pathDataDb, account)
override fun getVerifiedBalance(account: Int) = getVerifiedBalance(pathDataDb, account, networkId = network.id)
override fun getReceivedMemoAsUtf8(idNote: Long) =
getReceivedMemoAsUtf8(pathDataDb, idNote)
getReceivedMemoAsUtf8(pathDataDb, idNote, networkId = network.id)
override fun getSentMemoAsUtf8(idNote: Long) = getSentMemoAsUtf8(pathDataDb, idNote)
override fun getSentMemoAsUtf8(idNote: Long) = getSentMemoAsUtf8(pathDataDb, idNote, networkId = network.id)
override fun validateCombinedChain() = validateCombinedChain(pathCacheDb, pathDataDb)
override fun validateCombinedChain() = validateCombinedChain(pathCacheDb, pathDataDb, networkId = network.id,)
override fun rewindToHeight(height: Int) = rewindToHeight(pathDataDb, height)
override fun rewindToHeight(height: Int) = rewindToHeight(pathDataDb, height, networkId = network.id)
override fun scanBlocks(limit: Int): Boolean {
return if (limit > 0) {
scanBlockBatch(pathCacheDb, pathDataDb, limit)
scanBlockBatch(pathCacheDb, pathDataDb, limit, networkId = network.id)
} else {
scanBlocks(pathCacheDb, pathDataDb)
scanBlocks(pathCacheDb, pathDataDb, networkId = network.id)
}
}
override fun decryptAndStoreTransaction(tx: ByteArray) = decryptAndStoreTransaction(pathDataDb, tx)
override fun decryptAndStoreTransaction(tx: ByteArray) = decryptAndStoreTransaction(pathDataDb, tx, networkId = network.id)
override fun createToAddress(
consensusBranchId: Long,
@ -122,7 +126,8 @@ class RustBackend private constructor() : RustBackendWelding {
value,
memo ?: ByteArray(0),
"$pathParamsDir/$SPEND_PARAM_FILE_NAME",
"$pathParamsDir/$OUTPUT_PARAM_FILE_NAME"
"$pathParamsDir/$OUTPUT_PARAM_FILE_NAME",
networkId = network.id,
)
override fun shieldToAddress(
@ -138,7 +143,8 @@ class RustBackend private constructor() : RustBackendWelding {
tsk,
memo ?: ByteArray(0),
"$pathParamsDir/$SPEND_PARAM_FILE_NAME",
"$pathParamsDir/$OUTPUT_PARAM_FILE_NAME"
"$pathParamsDir/$OUTPUT_PARAM_FILE_NAME",
networkId = network.id,
)
}
@ -149,24 +155,24 @@ class RustBackend private constructor() : RustBackendWelding {
script: ByteArray,
value: Long,
height: Int
): Boolean = putUtxo(pathDataDb, tAddress, txId, index, script, value, height)
): Boolean = putUtxo(pathDataDb, tAddress, txId, index, script, value, height, networkId = network.id)
override fun clearUtxos(
tAddress: String,
aboveHeight: Int,
): Boolean = clearUtxos(pathDataDb, tAddress, aboveHeight)
): Boolean = clearUtxos(pathDataDb, tAddress, aboveHeight, networkId = network.id)
override fun getDownloadedUtxoBalance(address: String): WalletBalance {
val verified = getVerifiedTransparentBalance(pathDataDb, address)
val total = getTotalTransparentBalance(pathDataDb, address)
val verified = getVerifiedTransparentBalance(pathDataDb, address, networkId = network.id)
val total = getTotalTransparentBalance(pathDataDb, address, networkId = network.id)
return WalletBalance(total, verified)
}
override fun isValidShieldedAddr(addr: String) = isValidShieldedAddress(addr)
override fun isValidShieldedAddr(addr: String) = isValidShieldedAddress(addr, networkId = network.id)
override fun isValidTransparentAddr(addr: String) = isValidTransparentAddress(addr)
override fun isValidTransparentAddr(addr: String) = isValidTransparentAddress(addr, networkId = network.id)
override fun getBranchIdForHeight(height: Int): Long = branchIdForHeight(height)
override fun getBranchIdForHeight(height: Int): Long = branchIdForHeight(height, networkId = network.id)
// /**
// * This is a proof-of-concept for doing Local RPC, where we are effectively using the JNI
@ -201,12 +207,14 @@ class RustBackend private constructor() : RustBackendWelding {
cacheDbPath: String,
dataDbPath: String,
paramsPath: String,
zcashNetwork: ZcashNetwork,
birthdayHeight: Int? = null
): RustBackend {
return RustBackend().apply {
pathCacheDb = cacheDbPath
pathDataDb = dataDbPath
pathParamsDir = paramsPath
network = zcashNetwork
if (birthdayHeight != null) {
this.birthdayHeight = birthdayHeight
}
@ -241,12 +249,13 @@ class RustBackend private constructor() : RustBackendWelding {
// External Functions
//
@JvmStatic private external fun initDataDb(dbDataPath: String): Boolean
@JvmStatic private external fun initDataDb(dbDataPath: String, networkId: Int): Boolean
@JvmStatic private external fun initAccountsTableWithKeys(
dbDataPath: String,
extfvk: Array<out String>,
extpub: Array<out String>,
networkId: Int,
): Boolean
@JvmStatic private external fun initBlocksTable(
@ -254,34 +263,82 @@ class RustBackend private constructor() : RustBackendWelding {
height: Int,
hash: String,
time: Long,
saplingTree: String
saplingTree: String,
networkId: Int,
): Boolean
@JvmStatic private external fun getShieldedAddress(dbDataPath: String, account: Int): String
// TODO: implement this in the zcash_client_sqlite layer. For now, use DerivationTool, instead.
// @JvmStatic private external fun getTransparentAddress(dbDataPath: String, account: Int): String
@JvmStatic
private external fun getShieldedAddress(
dbDataPath: String,
account: Int,
networkId: Int,
): String
@JvmStatic private external fun isValidShieldedAddress(addr: String): Boolean
@JvmStatic
private external fun isValidShieldedAddress(addr: String, networkId: Int): Boolean
@JvmStatic private external fun isValidTransparentAddress(addr: String): Boolean
@JvmStatic
private external fun isValidTransparentAddress(addr: String, networkId: Int): Boolean
@JvmStatic private external fun getBalance(dbDataPath: String, account: Int): Long
@JvmStatic
private external fun getBalance(dbDataPath: String, account: Int, networkId: Int): Long
@JvmStatic private external fun getVerifiedBalance(dbDataPath: String, account: Int): Long
@JvmStatic
private external fun getVerifiedBalance(
dbDataPath: String,
account: Int,
networkId: Int,
): Long
@JvmStatic private external fun getReceivedMemoAsUtf8(dbDataPath: String, idNote: Long): String
@JvmStatic
private external fun getReceivedMemoAsUtf8(
dbDataPath: String,
idNote: Long,
networkId: Int,
): String
@JvmStatic private external fun getSentMemoAsUtf8(dbDataPath: String, idNote: Long): String
@JvmStatic
private external fun getSentMemoAsUtf8(
dbDataPath: String,
dNote: Long,
networkId: Int,
): String
@JvmStatic private external fun validateCombinedChain(dbCachePath: String, dbDataPath: String): Int
@JvmStatic
private external fun validateCombinedChain(
dbCachePath: String,
dbDataPath: String,
networkId: Int,
): Int
@JvmStatic private external fun rewindToHeight(dbDataPath: String, height: Int): Boolean
@JvmStatic
private external fun rewindToHeight(
dbDataPath: String,
height: Int,
networkId: Int,
): Boolean
@JvmStatic private external fun scanBlocks(dbCachePath: String, dbDataPath: String): Boolean
@JvmStatic
private external fun scanBlocks(
dbCachePath: String,
dbDataPath: String,
networkId: Int,
): Boolean
@JvmStatic private external fun scanBlockBatch(dbCachePath: String, dbDataPath: String, limit: Int): Boolean
@JvmStatic
private external fun scanBlockBatch(
dbCachePath: String,
dbDataPath: String,
limit: Int,
networkId: Int,
): Boolean
@JvmStatic private external fun decryptAndStoreTransaction(dbDataPath: String, tx: ByteArray)
@JvmStatic
private external fun decryptAndStoreTransaction(
dbDataPath: String,
tx: ByteArray,
networkId: Int,
)
@JvmStatic private external fun createToAddress(
dbDataPath: String,
@ -292,7 +349,8 @@ class RustBackend private constructor() : RustBackendWelding {
value: Long,
memo: ByteArray,
spendParamsPath: String,
outputParamsPath: String
outputParamsPath: String,
networkId: Int,
): Long
@JvmStatic private external fun shieldToAddress(
@ -302,12 +360,13 @@ class RustBackend private constructor() : RustBackendWelding {
tsk: String,
memo: ByteArray,
spendParamsPath: String,
outputParamsPath: String
outputParamsPath: String,
networkId: Int,
): Long
@JvmStatic private external fun initLogs()
@JvmStatic private external fun branchIdForHeight(height: Int): Long
@JvmStatic private external fun branchIdForHeight(height: Int, networkId: Int): Long
@JvmStatic private external fun putUtxo(
dbDataPath: String,
@ -316,23 +375,27 @@ class RustBackend private constructor() : RustBackendWelding {
index: Int,
script: ByteArray,
value: Long,
height: Int
height: Int,
networkId: Int,
): Boolean
@JvmStatic private external fun clearUtxos(
dbDataPath: String,
tAddress: String,
aboveHeight: Int,
networkId: Int,
): Boolean
@JvmStatic private external fun getVerifiedTransparentBalance(
pathDataDb: String,
taddr: String
taddr: String,
networkId: Int,
): Long
@JvmStatic private external fun getTotalTransparentBalance(
pathDataDb: String,
taddr: String
taddr: String,
networkId: Int,
): Long
}
}

View File

@ -1,8 +1,8 @@
package cash.z.ecc.android.sdk.jni
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
import cash.z.ecc.android.sdk.type.WalletBalance
import cash.z.ecc.android.sdk.type.ZcashNetwork
/**
* Contract defining the exposed capabilities of the Rust backend.
@ -12,6 +12,8 @@ import cash.z.ecc.android.sdk.type.WalletBalance
*/
interface RustBackendWelding {
val network: ZcashNetwork
fun createToAddress(
consensusBranchId: Long,
account: Int,
@ -78,22 +80,56 @@ interface RustBackendWelding {
// Implemented by `DerivationTool`
interface Derivation {
fun deriveShieldedAddress(viewingKey: String): String
fun deriveShieldedAddress(
viewingKey: String,
network: ZcashNetwork
): String
fun deriveShieldedAddress(seed: ByteArray, accountIndex: Int = 0): String
fun deriveShieldedAddress(
seed: ByteArray,
network: ZcashNetwork,
accountIndex: Int = 0,
): String
fun deriveSpendingKeys(seed: ByteArray, numberOfAccounts: Int = 1): Array<String>
fun deriveSpendingKeys(
seed: ByteArray,
network: ZcashNetwork,
numberOfAccounts: Int = 1,
): Array<String>
fun deriveTransparentAddress(seed: ByteArray, account: Int = 0, index: Int = 0): String
fun deriveTransparentAddress(
seed: ByteArray,
network: ZcashNetwork,
account: Int = 0,
index: Int = 0,
): String
fun deriveTransparentAddressFromPublicKey(publicKey: String): String
fun deriveTransparentAddressFromPublicKey(
publicKey: String,
network: ZcashNetwork
): String
fun deriveTransparentAddressFromPrivateKey(privateKey: String): String
fun deriveTransparentAddressFromPrivateKey(
privateKey: String,
network: ZcashNetwork
): String
fun deriveTransparentSecretKey(seed: ByteArray, account: Int = 0, index: Int = 0): String
fun deriveTransparentSecretKey(
seed: ByteArray,
network: ZcashNetwork,
account: Int = 0,
index: Int = 0,
): String
fun deriveViewingKey(spendingKey: String): String
fun deriveViewingKey(
spendingKey: String,
network: ZcashNetwork
): String
fun deriveUnifiedViewingKeys(seed: ByteArray, numberOfAccounts: Int = 1): Array<UnifiedViewingKey>
fun deriveUnifiedViewingKeys(
seed: ByteArray,
network: ZcashNetwork,
numberOfAccounts: Int = 1,
): Array<UnifiedViewingKey>
}
}

View File

@ -4,8 +4,8 @@ import android.content.Context
import cash.z.ecc.android.sdk.R
import cash.z.ecc.android.sdk.annotation.OpenForTesting
import cash.z.ecc.android.sdk.exception.LightWalletException
import cash.z.ecc.android.sdk.ext.ZcashSdk.DEFAULT_LIGHTWALLETD_PORT
import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.wallet.sdk.rpc.CompactFormats
import cash.z.wallet.sdk.rpc.CompactTxStreamerGrpc
import cash.z.wallet.sdk.rpc.Service
@ -34,6 +34,13 @@ class LightWalletGrpcService private constructor(
lateinit var connectionInfo: ConnectionInfo
constructor(
appContext: Context,
network: ZcashNetwork,
usePlaintext: Boolean =
appContext.resources.getBoolean(R.bool.lightwalletd_allow_very_insecure_connections)
) : this(appContext, network.defaultHost, network.defaultPort, usePlaintext)
/**
* Construct an instance that corresponds to the given host and port.
*
@ -47,7 +54,7 @@ class LightWalletGrpcService private constructor(
constructor(
appContext: Context,
host: String,
port: Int = DEFAULT_LIGHTWALLETD_PORT,
port: Int = ZcashNetwork.Mainnet.defaultPort,
usePlaintext: Boolean =
appContext.resources.getBoolean(R.bool.lightwalletd_allow_very_insecure_connections)
) : this(createDefaultChannel(appContext, host, port, usePlaintext)) {

View File

@ -3,6 +3,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.type.UnifiedViewingKey
import cash.z.ecc.android.sdk.type.ZcashNetwork
class DerivationTool {
@ -17,9 +18,9 @@ class DerivationTool {
*
* @return the viewing keys that correspond to the seed, formatted as Strings.
*/
override fun deriveUnifiedViewingKeys(seed: ByteArray, numberOfAccounts: Int): Array<UnifiedViewingKey> =
override fun deriveUnifiedViewingKeys(seed: ByteArray, network: ZcashNetwork, numberOfAccounts: Int): Array<UnifiedViewingKey> =
withRustBackendLoaded {
deriveUnifiedViewingKeysFromSeed(seed, numberOfAccounts).map {
deriveUnifiedViewingKeysFromSeed(seed, numberOfAccounts, networkId = network.id).map {
UnifiedViewingKey(it[0], it[1])
}.toTypedArray()
}
@ -31,8 +32,8 @@ class DerivationTool {
*
* @return the viewing key that corresponds to the spending key.
*/
override fun deriveViewingKey(spendingKey: String): String = withRustBackendLoaded {
deriveExtendedFullViewingKey(spendingKey)
override fun deriveViewingKey(spendingKey: String, network: ZcashNetwork): String = withRustBackendLoaded {
deriveExtendedFullViewingKey(spendingKey, networkId = network.id)
}
/**
@ -44,9 +45,9 @@ class DerivationTool {
*
* @return the spending keys that correspond to the seed, formatted as Strings.
*/
override fun deriveSpendingKeys(seed: ByteArray, numberOfAccounts: Int): Array<String> =
override fun deriveSpendingKeys(seed: ByteArray, network: ZcashNetwork, numberOfAccounts: Int): Array<String> =
withRustBackendLoaded {
deriveExtendedSpendingKeys(seed, numberOfAccounts)
deriveExtendedSpendingKeys(seed, numberOfAccounts, networkId = network.id)
}
/**
@ -58,9 +59,9 @@ class DerivationTool {
*
* @return the address that corresponds to the seed and account index.
*/
override fun deriveShieldedAddress(seed: ByteArray, accountIndex: Int): String =
override fun deriveShieldedAddress(seed: ByteArray, network: ZcashNetwork, accountIndex: Int): String =
withRustBackendLoaded {
deriveShieldedAddressFromSeed(seed, accountIndex)
deriveShieldedAddressFromSeed(seed, accountIndex, networkId = network.id)
}
/**
@ -71,30 +72,30 @@ class DerivationTool {
*
* @return the address that corresponds to the viewing key.
*/
override fun deriveShieldedAddress(viewingKey: String): String = withRustBackendLoaded {
deriveShieldedAddressFromViewingKey(viewingKey)
override fun deriveShieldedAddress(viewingKey: String, network: ZcashNetwork): String = withRustBackendLoaded {
deriveShieldedAddressFromViewingKey(viewingKey, networkId = network.id)
}
// WIP probably shouldn't be used just yet. Why?
// - because we need the private key associated with this seed and this function doesn't return it.
// - the underlying implementation needs to be split out into a few lower-level calls
override fun deriveTransparentAddress(seed: ByteArray, account: Int, index: Int): String = withRustBackendLoaded {
deriveTransparentAddressFromSeed(seed, account, index)
override fun deriveTransparentAddress(seed: ByteArray, network: ZcashNetwork, account: Int, index: Int): String = withRustBackendLoaded {
deriveTransparentAddressFromSeed(seed, account, index, networkId = network.id)
}
override fun deriveTransparentAddressFromPublicKey(transparentPublicKey: String): String = withRustBackendLoaded {
deriveTransparentAddressFromPubKey(transparentPublicKey)
override fun deriveTransparentAddressFromPublicKey(transparentPublicKey: String, network: ZcashNetwork): String = withRustBackendLoaded {
deriveTransparentAddressFromPubKey(transparentPublicKey, networkId = network.id)
}
override fun deriveTransparentAddressFromPrivateKey(transparentPrivateKey: String): String = withRustBackendLoaded {
deriveTransparentAddressFromPrivKey(transparentPrivateKey)
override fun deriveTransparentAddressFromPrivateKey(transparentPrivateKey: String, network: ZcashNetwork): String = withRustBackendLoaded {
deriveTransparentAddressFromPrivKey(transparentPrivateKey, networkId = network.id)
}
override fun deriveTransparentSecretKey(seed: ByteArray, account: Int, index: Int): String = withRustBackendLoaded {
deriveTransparentSecretKeyFromSeed(seed, account, index)
override fun deriveTransparentSecretKey(seed: ByteArray, network: ZcashNetwork, account: Int, index: Int): String = withRustBackendLoaded {
deriveTransparentSecretKeyFromSeed(seed, account, index, networkId = network.id)
}
fun validateUnifiedViewingKey(viewingKey: UnifiedViewingKey) {
fun validateUnifiedViewingKey(viewingKey: UnifiedViewingKey, networkId: Int = ZcashNetwork.Mainnet.id) {
// TODO
}
@ -115,37 +116,40 @@ class DerivationTool {
@JvmStatic
private external fun deriveExtendedSpendingKeys(
seed: ByteArray,
numberOfAccounts: Int
numberOfAccounts: Int,
networkId: Int,
): Array<String>
@JvmStatic
private external fun deriveUnifiedViewingKeysFromSeed(
seed: ByteArray,
numberOfAccounts: Int
numberOfAccounts: Int,
networkId: Int,
): Array<Array<String>>
@JvmStatic
private external fun deriveExtendedFullViewingKey(spendingKey: String): String
private external fun deriveExtendedFullViewingKey(spendingKey: String, networkId: Int): String
@JvmStatic
private external fun deriveShieldedAddressFromSeed(
seed: ByteArray,
accountIndex: Int
accountIndex: Int,
networkId: Int,
): String
@JvmStatic
private external fun deriveShieldedAddressFromViewingKey(key: String): String
private external fun deriveShieldedAddressFromViewingKey(key: String, networkId: Int): String
@JvmStatic
private external fun deriveTransparentAddressFromSeed(seed: ByteArray, account: Int, index: Int): String
private external fun deriveTransparentAddressFromSeed(seed: ByteArray, account: Int, index: Int, networkId: Int): String
@JvmStatic
private external fun deriveTransparentAddressFromPubKey(pk: String): String
private external fun deriveTransparentAddressFromPubKey(pk: String, networkId: Int): String
@JvmStatic
private external fun deriveTransparentAddressFromPrivKey(sk: String): String
private external fun deriveTransparentAddressFromPrivKey(sk: String, networkId: Int): String
@JvmStatic
private external fun deriveTransparentSecretKeyFromSeed(seed: ByteArray, account: Int, index: Int): String
private external fun deriveTransparentSecretKeyFromSeed(seed: ByteArray, account: Int, index: Int, networkId: Int): String
}
}

View File

@ -2,13 +2,14 @@ package cash.z.ecc.android.sdk.tool
import android.content.Context
import cash.z.ecc.android.sdk.exception.BirthdayException
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.sdk.type.WalletBirthday
import cash.z.ecc.android.sdk.type.ZcashNetwork
import com.google.gson.Gson
import com.google.gson.stream.JsonReader
import java.io.InputStreamReader
import java.util.Arrays
import java.util.Locale
/**
* Tool for loading checkpoints for the wallet, based on the height at which the wallet was born.
@ -22,33 +23,27 @@ class WalletBirthdayTool(appContext: Context) {
* Load the nearest checkpoint to the given birthday height. If null is given, then this
* will load the most recent checkpoint available.
*/
fun loadNearest(birthdayHeight: Int? = null): WalletBirthday {
return loadBirthdayFromAssets(context, birthdayHeight)
fun loadNearest(network: ZcashNetwork, birthdayHeight: Int? = null): WalletBirthday {
return loadBirthdayFromAssets(context, network, birthdayHeight)
}
companion object {
/**
* Directory within the assets folder where birthday data
* (i.e. sapling trees for a given height) can be found.
*/
private const val BIRTHDAY_DIRECTORY = "zcash/saplingtree"
/**
* Load the nearest checkpoint to the given birthday height. If null is given, then this
* will load the most recent checkpoint available.
*/
fun loadNearest(context: Context, birthdayHeight: Int? = null): WalletBirthday {
fun loadNearest(context: Context, network: ZcashNetwork, birthdayHeight: Int? = null): WalletBirthday {
// TODO: potentially pull from shared preferences first
return loadBirthdayFromAssets(context, birthdayHeight)
return loadBirthdayFromAssets(context, network, birthdayHeight)
}
/**
* Useful for when an exact checkpoint is needed, like for SAPLING_ACTIVATION_HEIGHT. In
* most cases, loading the nearest checkpoint is preferred for privacy reasons.
*/
fun loadExact(context: Context, birthdayHeight: Int) =
loadNearest(context, birthdayHeight).also {
fun loadExact(context: Context, network: ZcashNetwork, birthdayHeight: Int) =
loadNearest(context, network, birthdayHeight).also {
if (it.height != birthdayHeight)
throw BirthdayException.ExactBirthdayNotFoundException(
birthdayHeight,
@ -56,6 +51,14 @@ class WalletBirthdayTool(appContext: Context) {
)
}
/**
* Returns the directory within the assets folder where birthday data
* (i.e. sapling trees for a given height) can be found.
*/
private fun birthdayDirectory(network: ZcashNetwork): String {
return "saplingtree/${network.networkName.toLowerCase(Locale.US)}"
}
/**
* Load the given birthday file from the assets of the given context. When no height is
* specified, we default to the file with the greatest name.
@ -68,21 +71,23 @@ class WalletBirthdayTool(appContext: Context) {
*/
private fun loadBirthdayFromAssets(
context: Context,
network: ZcashNetwork,
birthdayHeight: Int? = null
): WalletBirthday {
twig("loading birthday from assets: $birthdayHeight")
val directory = birthdayDirectory(network)
val treeFiles =
context.assets.list(BIRTHDAY_DIRECTORY)?.apply {
context.assets.list(directory)?.apply {
sortByDescending { fileName ->
try {
fileName.split('.').first().toInt()
} catch (t: Throwable) {
ZcashSdk.SAPLING_ACTIVATION_HEIGHT
network.saplingActivationHeight
}
}
}
if (treeFiles.isNullOrEmpty()) throw BirthdayException.MissingBirthdayFilesException(
BIRTHDAY_DIRECTORY
directory
)
twig("found ${treeFiles.size} sapling tree checkpoints: ${Arrays.toString(treeFiles)}")
val file: String
@ -94,18 +99,18 @@ class WalletBirthdayTool(appContext: Context) {
}
} catch (t: Throwable) {
throw BirthdayException.BirthdayFileNotFoundException(
BIRTHDAY_DIRECTORY,
directory,
birthdayHeight
)
}
try {
val reader = JsonReader(
InputStreamReader(context.assets.open("$BIRTHDAY_DIRECTORY/$file"))
InputStreamReader(context.assets.open("$directory/$file"))
)
return Gson().fromJson(reader, WalletBirthday::class.java)
} catch (t: Throwable) {
throw BirthdayException.MalformattedBirthdayFilesException(
BIRTHDAY_DIRECTORY,
directory,
treeFiles[0]
)
}

View File

@ -53,3 +53,12 @@ interface UnifiedAddress {
val rawShieldedAddress: String
val rawTransparentAddress: String
}
enum class ZcashNetwork(val id: Int, val networkName: String, val saplingActivationHeight: Int, val defaultHost: String, val defaultPort: Int) {
Testnet(0, "testnet", 280_000, "testnet.lightwalletd.com", 9067),
Mainnet(1, "mainnet", 419_200, "mainnet.lightwalletd.com", 9067);
companion object {
fun from(id: Int) = values().first { it.id == id }
}
}

View File

@ -15,6 +15,7 @@ use jni::{
sys::{jboolean, jbyteArray, jint, jlong, JNI_FALSE, JNI_TRUE, jobjectArray, jstring},
};
use log::Level;
use secp256k1::key::{PublicKey, SecretKey};
use zcash_client_backend::{
address::RecipientAddress,
data_api::{
@ -29,41 +30,37 @@ use zcash_client_backend::{
encode_payment_address,
},
keys::{
derive_secret_key_from_seed, derive_public_key_from_seed,
derive_public_key_from_seed, derive_secret_key_from_seed,
derive_transparent_address_from_public_key, derive_transparent_address_from_secret_key,
spending_key, Wif,
},
wallet::{AccountId, OvkPolicy, WalletTransparentOutput},
};
use zcash_client_backend::data_api::wallet::{shield_funds, ANCHOR_OFFSET};
use zcash_client_backend::data_api::wallet::{ANCHOR_OFFSET, shield_funds};
use zcash_client_sqlite::{
BlockDB,
error::SqliteClientError,
NoteId,
wallet::{delete_utxos_above, put_received_transparent_utxo},
wallet::init::{init_accounts_table, init_blocks_table, init_wallet_db},
wallet::{put_received_transparent_utxo, delete_utxos_above},
wallet::rewind_to_height,
WalletDB,
};
use zcash_primitives::{
block::BlockHash,
consensus::{BlockHeight, BranchId, Parameters},
consensus::{BlockHeight, BranchId, Network, Parameters},
legacy::TransparentAddress,
memo::{
Memo, MemoBytes
},
transaction::{
components::{Amount, OutPoint},
Transaction
},
zip32::ExtendedFullViewingKey,
memo::{
Memo, MemoBytes
}
zip32::ExtendedFullViewingKey
};
#[cfg(feature = "mainnet")]
use zcash_primitives::consensus::{MAIN_NETWORK, MainNetwork};
#[cfg(not(feature = "mainnet"))]
use zcash_primitives::consensus::{TEST_NETWORK, TestNetwork};
use zcash_primitives::consensus::Network::{MainNetwork, TestNetwork};
use zcash_proofs::prover::LocalTxProver;
use secp256k1::key::{SecretKey, PublicKey};
use crate::utils::exception::unwrap_exc_or;
@ -79,12 +76,6 @@ fn print_debug_state() {
debug!("Release enabled (congrats, this is NOT a debug build).");
}
#[cfg(feature = "mainnet")]
pub const NETWORK: MainNetwork = MAIN_NETWORK;
#[cfg(not(feature = "mainnet"))]
pub const NETWORK: TestNetwork = TEST_NETWORK;
fn wallet_db<P: Parameters>(env: &JNIEnv<'_>, params: P, db_data: JString<'_>) -> Result<WalletDB<P>, failure::Error> {
WalletDB::for_path(utils::java_string_to_rust(&env, db_data), params)
.map_err(|e| format_err!("Error opening wallet database connection: {}", e))
@ -117,10 +108,12 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initDataDb(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
network_id: jint,
) -> jboolean {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let db_path = utils::java_string_to_rust(&env, db_data);
WalletDB::for_path(db_path, NETWORK)
WalletDB::for_path(db_path, network)
.and_then(|db| init_wallet_db(&db))
.map(|()| JNI_TRUE)
.map_err(|e| format_err!("Error while initializing data DB: {}", e))
@ -135,9 +128,11 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initAccount
db_data: JString<'_>,
extfvks_arr: jobjectArray,
extpubs_arr: jobjectArray,
network_id: jint,
) -> jboolean {
let res = panic::catch_unwind(|| {
let db_data = wallet_db(&env, NETWORK, db_data)?;
let network = parse_network(network_id as u32)?;
let db_data = wallet_db(&env, network, db_data)?;
// TODO: avoid all this unwrapping and also surface errors, better
let count = env.get_array_length(extfvks_arr).unwrap();
let extfvks = (0..count)
@ -145,13 +140,21 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initAccount
.map(|jstr| utils::java_string_to_rust(&env, jstr.unwrap().into()))
.map(|vkstr| {
decode_extended_full_viewing_key(
NETWORK.hrp_sapling_extended_full_viewing_key(),
network.hrp_sapling_extended_full_viewing_key(),
&vkstr,
)
.unwrap()
.unwrap()
.map_err(|err| format_err!("Invalid bech32: {}", err))
.and_then(|extfvk|
extfvk.ok_or_else(|| {
let (network_name, other) = if network == TestNetwork {
("testnet", "mainnet")
} else {
("mainnet", "testnet")
};
format_err!("Error: Wrong network! Unable to decode viewing key for {}. Check whether this is a key for {}.", network_name, other)
}))
})
.collect::<Vec<_>>();
.collect::<Result<Vec<_>, _>>()?;
let taddrs:Vec<_> = (0..count)
.map(|i| env.get_object_array_element(extpubs_arr, i))
@ -174,8 +177,10 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveE
_: JClass<'_>,
seed: jbyteArray,
accounts: jint,
network_id: jint,
) -> jobjectArray {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let seed = env.convert_byte_array(seed).unwrap();
let accounts = if accounts > 0 {
accounts as u32
@ -184,7 +189,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveE
};
let extsks: Vec<_> = (0..accounts)
.map(|account| spending_key(&seed, NETWORK.coin_type(), AccountId(account)))
.map(|account| spending_key(&seed, network.coin_type(), AccountId(account)))
.collect();
Ok(utils::rust_vec_to_java(
@ -193,7 +198,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveE
"java/lang/String",
|env, extsk| {
env.new_string(encode_extended_spending_key(
NETWORK.hrp_sapling_extended_spending_key(),
network.hrp_sapling_extended_spending_key(),
&extsk,
))
},
@ -209,8 +214,10 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveU
_: JClass<'_>,
seed: jbyteArray,
accounts: jint,
network_id: jint,
) -> jobjectArray {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let seed = env.convert_byte_array(seed).unwrap();
let accounts = if accounts > 0 {
accounts as u32
@ -221,15 +228,15 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveU
let extfvks: Vec<_> = (0..accounts)
.map(|account| {
encode_extended_full_viewing_key(
NETWORK.hrp_sapling_extended_full_viewing_key(),
&ExtendedFullViewingKey::from(&spending_key(&seed, NETWORK.coin_type(), AccountId(account)))
network.hrp_sapling_extended_full_viewing_key(),
&ExtendedFullViewingKey::from(&spending_key(&seed, network.coin_type(), AccountId(account)))
)
})
.collect();
let extpubs: Vec<_> = (0..accounts)
.map(|account| {
let pk = derive_public_key_from_seed(&NETWORK, &seed, AccountId(account), 0).unwrap();
let pk = derive_public_key_from_seed(&network, &seed, AccountId(account), 0).unwrap();
hex::encode(&pk.serialize())
})
.collect();
@ -253,8 +260,10 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveS
_: JClass<'_>,
seed: jbyteArray,
account_index: jint,
network_id: jint,
) -> jstring {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let seed = env.convert_byte_array(seed).unwrap();
let account_index = if account_index >= 0 {
account_index as u32
@ -262,11 +271,11 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveS
return Err(format_err!("accountIndex argument must be positive"));
};
let address = spending_key(&seed, NETWORK.coin_type(), AccountId(account_index))
let address = spending_key(&seed, network.coin_type(), AccountId(account_index))
.default_address()
.unwrap()
.1;
let address_str = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &address);
let address_str = encode_payment_address(network.hrp_sapling_payment_address(), &address);
let output = env
.new_string(address_str)
.expect("Couldn't create Java string!");
@ -280,11 +289,13 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveS
env: JNIEnv<'_>,
_: JClass<'_>,
extfvk_string: JString<'_>,
network_id: jint,
) -> jstring {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let extfvk_string = utils::java_string_to_rust(&env, extfvk_string);
let extfvk = match decode_extended_full_viewing_key(
NETWORK.hrp_sapling_extended_full_viewing_key(),
network.hrp_sapling_extended_full_viewing_key(),
&extfvk_string,
) {
Ok(Some(extfvk)) => extfvk,
@ -300,7 +311,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveS
};
let address = extfvk.default_address().unwrap().1;
let address_str = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &address);
let address_str = encode_payment_address(network.hrp_sapling_payment_address(), &address);
let output = env
.new_string(address_str)
.expect("Couldn't create Java string!");
@ -314,11 +325,13 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveE
env: JNIEnv<'_>,
_: JClass<'_>,
extsk_string: JString<'_>,
network_id: jint,
) -> jobjectArray {
let res = panic::catch_unwind(|| {
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(
NETWORK.hrp_sapling_extended_spending_key(),
network.hrp_sapling_extended_spending_key(),
&extsk_string,
) {
Ok(Some(extsk)) => ExtendedFullViewingKey::from(&extsk),
@ -335,7 +348,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveE
let output = env
.new_string(encode_extended_full_viewing_key(
NETWORK.hrp_sapling_extended_full_viewing_key(),
network.hrp_sapling_extended_full_viewing_key(),
&extfvk,
))
.expect("Couldn't create Java string!");
@ -354,9 +367,11 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initBlocksT
hash_string: JString<'_>,
time: jlong,
sapling_tree_string: JString<'_>,
network_id: jint,
) -> jboolean {
let res = panic::catch_unwind(|| {
let db_data = wallet_db(&env, NETWORK, db_data)?;
let network = parse_network(network_id as u32)?;
let db_data = wallet_db(&env, network, db_data)?;
let hash = {
let mut hash = hex::decode(utils::java_string_to_rust(&env, hash_string)).unwrap();
hash.reverse();
@ -385,14 +400,16 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getShielded
_: JClass<'_>,
db_data: JString<'_>,
account: jint,
network_id: jint,
) -> jstring {
let res = panic::catch_unwind(|| {
let db_data = wallet_db(&env, NETWORK, db_data)?;
let network = parse_network(network_id as u32)?;
let db_data = wallet_db(&env, network, db_data)?;
let account = AccountId(account.try_into()?);
match (&db_data).get_address(account) {
Ok(Some(addr)) => {
let addr_str = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &addr);
let addr_str = encode_payment_address(network.hrp_sapling_payment_address(), &addr);
let output = env
.new_string(addr_str)
.expect("Couldn't create Java string!");
@ -414,11 +431,13 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidShie
env: JNIEnv<'_>,
_: JClass<'_>,
addr: JString<'_>,
network_id: jint,
) -> jboolean {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let addr = utils::java_string_to_rust(&env, addr);
match RecipientAddress::decode(&NETWORK, &addr) {
match RecipientAddress::decode(&network, &addr) {
Some(addr) => match addr {
RecipientAddress::Shielded(_) => Ok(JNI_TRUE),
RecipientAddress::Transparent(_) => Ok(JNI_FALSE),
@ -434,11 +453,13 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidTran
env: JNIEnv<'_>,
_: JClass<'_>,
addr: JString<'_>,
network_id: jint,
) -> jboolean {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let addr = utils::java_string_to_rust(&env, addr);
match RecipientAddress::decode(&NETWORK, &addr) {
match RecipientAddress::decode(&network, &addr) {
Some(addr) => match addr {
RecipientAddress::Shielded(_) => Ok(JNI_FALSE),
RecipientAddress::Transparent(_) => Ok(JNI_TRUE),
@ -454,11 +475,13 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getBalance(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
account: jint,
accountj: jint,
network_id: jint,
) -> jlong {
let res = panic::catch_unwind(|| {
let db_data = wallet_db(&env, NETWORK, db_data)?;
let account = AccountId(account.try_into()?);
let network = parse_network(network_id as u32)?;
let db_data = wallet_db(&env, network, db_data)?;
let account = AccountId(accountj.try_into()?);
(&db_data)
.get_target_and_anchor_heights()
@ -484,11 +507,13 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getVerified
_: JClass<'_>,
db_data: JString<'_>,
address: JString<'_>,
network_id: jint,
) -> jlong {
let res = panic::catch_unwind(|| {
let db_data = wallet_db(&env, NETWORK, db_data)?;
let network = parse_network(network_id as u32)?;
let db_data = wallet_db(&env, network, db_data)?;
let addr = utils::java_string_to_rust(&env, address);
let taddr = TransparentAddress::decode(&NETWORK, &addr).unwrap();
let taddr = TransparentAddress::decode(&network, &addr).unwrap();
let amount = (&db_data)
.get_target_and_anchor_heights()
@ -519,11 +544,13 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getTotalTra
_: JClass<'_>,
db_data: JString<'_>,
address: JString<'_>,
network_id: jint,
) -> jlong {
let res = panic::catch_unwind(|| {
let db_data = wallet_db(&env, NETWORK, db_data)?;
let network = parse_network(network_id as u32)?;
let db_data = wallet_db(&env, network, db_data)?;
let addr = utils::java_string_to_rust(&env, address);
let taddr = TransparentAddress::decode(&NETWORK, &addr).unwrap();
let taddr = TransparentAddress::decode(&network, &addr).unwrap();
let amount = (&db_data)
.get_target_and_anchor_heights()
@ -554,9 +581,11 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getVerified
_: JClass<'_>,
db_data: JString<'_>,
account: jint,
network_id: jint,
) -> jlong {
let res = panic::catch_unwind(|| {
let db_data = wallet_db(&env, NETWORK, db_data)?;
let network = parse_network(network_id as u32)?;
let db_data = wallet_db(&env, network, db_data)?;
let account = AccountId(account.try_into()?);
(&db_data)
@ -584,9 +613,11 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getReceived
_: JClass<'_>,
db_data: JString<'_>,
id_note: jlong,
network_id: jint,
) -> jstring {
let res = panic::catch_unwind(|| {
let db_data = wallet_db(&env, NETWORK, db_data)?;
let network = parse_network(network_id as u32)?;
let db_data = wallet_db(&env, network, db_data)?;
let memo = (&db_data)
.get_memo(NoteId::ReceivedNoteId(id_note))
@ -611,9 +642,11 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getSentMemo
_: JClass<'_>,
db_data: JString<'_>,
id_note: jlong,
network_id: jint,
) -> jstring {
let res = panic::catch_unwind(|| {
let db_data = wallet_db(&env, NETWORK, db_data)?;
let network = parse_network(network_id as u32)?;
let db_data = wallet_db(&env, network, db_data)?;
let memo = (&db_data)
.get_memo(NoteId::SentNoteId(id_note))
@ -639,16 +672,18 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_validateCom
_: JClass<'_>,
db_cache: JString<'_>,
db_data: JString<'_>,
network_id: jint,
) -> jint {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let block_db = block_db(&env, db_cache)?;
let db_data = wallet_db(&env, NETWORK, db_data)?;
let db_data = wallet_db(&env, network, db_data)?;
let validate_from = (&db_data)
.get_max_height_hash()
.map_err(|e| format_err!("Error while validating chain: {}", e))?;
let val_res = validate_chain(&NETWORK, &block_db, validate_from);
let val_res = validate_chain(&network, &block_db, validate_from);
if let Err(e) = val_res {
match e {
@ -673,9 +708,11 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_rewindToHei
_: JClass<'_>,
db_data: JString<'_>,
height: jint,
network_id: jint,
) -> jboolean {
let res = panic::catch_unwind(|| {
let db_data = wallet_db(&env, NETWORK, db_data)?;
let network = parse_network(network_id as u32)?;
let db_data = wallet_db(&env, network, db_data)?;
let height = BlockHeight::try_from(height)?;
rewind_to_height(&db_data, height)
@ -692,13 +729,15 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_scanBlocks(
_: JClass<'_>,
db_cache: JString<'_>,
db_data: JString<'_>,
network_id: jint,
) -> jboolean {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let db_cache = block_db(&env, db_cache)?;
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()?;
match scan_cached_blocks(&NETWORK, &db_cache, &mut db_data, None) {
match scan_cached_blocks(&network, &db_cache, &mut db_data, None) {
Ok(()) => Ok(JNI_TRUE),
Err(e) => Err(format_err!("Error while scanning blocks: {}", e)),
}
@ -717,19 +756,21 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_putUtxo(
script: jbyteArray,
value: jlong,
height: jint,
network_id: jint,
) -> jboolean {
// debug!("For height {} found consensus branch {:?}", height, branch);
debug!("preparing to store UTXO in db_data");
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let txid_bytes = env.convert_byte_array(txid_bytes).unwrap();
let mut txid = [0u8; 32];
txid.copy_from_slice(&txid_bytes);
let script = env.convert_byte_array(script).unwrap();
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 addr = utils::java_string_to_rust(&env, address);
let address = TransparentAddress::decode(&NETWORK, &addr).unwrap();
let address = TransparentAddress::decode(&network, &addr).unwrap();
let output = WalletTransparentOutput {
address: address,
@ -756,19 +797,21 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_clearUtxos(
db_data: JString<'_>,
taddress: JString<'_>,
above_height: jint,
network_id: jint,
) -> jint {
let res = panic::catch_unwind(|| {
let db_data = wallet_db(&env, NETWORK, db_data)?;
let mut db_data = db_data.get_update_ops()?;
let addr = utils::java_string_to_rust(&env, taddress);
let taddress = TransparentAddress::decode(&NETWORK, &addr).unwrap();
let height = BlockHeight::from(above_height as u32);
let network = parse_network(network_id as u32)?;
let db_data = wallet_db(&env, network, db_data)?;
let mut db_data = db_data.get_update_ops()?;
let addr = utils::java_string_to_rust(&env, taddress);
let taddress = TransparentAddress::decode(&network, &addr).unwrap();
let height = BlockHeight::from(above_height as u32);
debug!("clearing UTXOs that were found above height: {}", above_height);
match delete_utxos_above(&mut db_data, &taddress, height) {
Ok(rows) => Ok(rows as i32),
Err(e) => Err(format_err!("Error while clearing UTXOs: {}", e)),
}
debug!("clearing UTXOs that were found above height: {}", above_height);
match delete_utxos_above(&mut db_data, &taddress, height) {
Ok(rows) => Ok(rows as i32),
Err(e) => Err(format_err!("Error while clearing UTXOs: {}", e)),
}
});
unwrap_exc_or(&env, res, -1)
}
@ -781,13 +824,15 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_scanBlockBa
db_cache: JString<'_>,
db_data: JString<'_>,
limit: jint,
network_id: jint,
) -> jboolean {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let db_cache = block_db(&env, db_cache)?;
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()?;
match scan_cached_blocks(&NETWORK, &db_cache, &mut db_data, Some(limit as u32)) {
match scan_cached_blocks(&network, &db_cache, &mut db_data, Some(limit as u32)) {
Ok(()) => Ok(JNI_TRUE),
Err(e) => Err(format_err!("Error while scanning blocks: {}", e)),
}
@ -802,8 +847,10 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveT
seed: jbyteArray,
account: jint,
index: jint,
network_id: jint,
) -> jstring {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let seed = env.convert_byte_array(seed).unwrap();
let account = if account >= 0 {
account as u32
@ -815,7 +862,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveT
} else {
return Err(format_err!("index argument must be positive"));
};
let sk = derive_secret_key_from_seed(&NETWORK, &seed, AccountId(account), index).unwrap();
let sk = derive_secret_key_from_seed(&network, &seed, AccountId(account), index).unwrap();
let sk_wif = Wif::from_secret_key(&sk, true);
let output = env
.new_string(sk_wif.0)
@ -832,8 +879,10 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveT
seed: jbyteArray,
account: jint,
index: jint,
network_id: jint,
) -> jstring {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let seed = env.convert_byte_array(seed).unwrap();
let account = if account >= 0 {
account as u32
@ -845,9 +894,9 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveT
} else {
return Err(format_err!("index argument must be positive"));
};
let sk = derive_secret_key_from_seed(&NETWORK, &seed, AccountId(account), index);
let sk = derive_secret_key_from_seed(&network, &seed, AccountId(account), index);
let taddr = derive_transparent_address_from_secret_key(&sk.unwrap())
.encode(&NETWORK);
.encode(&network);
let output = env
.new_string(taddr)
@ -862,13 +911,15 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveT
env: JNIEnv<'_>,
_: JClass<'_>,
secret_key: JString<'_>,
network_id: jint,
) -> jstring {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let tsk_wif = utils::java_string_to_rust(&env, secret_key);
let sk:SecretKey = (&Wif(tsk_wif)).try_into().expect("invalid private key WIF");
let taddr =
derive_transparent_address_from_secret_key(&sk)
.encode(&NETWORK);
.encode(&network);
let output = env
.new_string(taddr)
@ -885,13 +936,15 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveT
env: JNIEnv<'_>,
_: JClass<'_>,
public_key: JString<'_>,
network_id: jint,
) -> jstring {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let public_key_str = utils::java_string_to_rust(&env, public_key);
let pk = PublicKey::from_str(&public_key_str)?;
let taddr =
derive_transparent_address_from_public_key(&pk)
.encode(&NETWORK);
.encode(&network);
let output = env
.new_string(taddr)
@ -908,14 +961,16 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_decryptAndS
_: JClass<'_>,
db_data: JString<'_>,
tx: jbyteArray,
network_id: jint,
) -> jboolean {
let res = panic::catch_unwind(|| {
let db_data = wallet_db(&env, NETWORK, db_data)?;
let network = parse_network(network_id as u32)?;
let db_data = wallet_db(&env, network, db_data)?;
let mut db_data = db_data.get_update_ops()?;
let tx_bytes = env.convert_byte_array(tx).unwrap();
let tx = Transaction::read(&tx_bytes[..])?;
match decrypt_and_store_transaction(&NETWORK, &mut db_data, &tx) {
match decrypt_and_store_transaction(&network, &mut db_data, &tx) {
Ok(()) => Ok(JNI_TRUE),
Err(e) => Err(format_err!("Error while decrypting transaction: {}", e)),
}
@ -937,9 +992,11 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createToAdd
memo: jbyteArray,
spend_params: JString<'_>,
output_params: JString<'_>,
network_id: jint,
) -> jlong {
let res = panic::catch_unwind(|| {
let db_data = wallet_db(&env, NETWORK, db_data)?;
let network = parse_network(network_id as u32)?;
let db_data = wallet_db(&env, network, db_data)?;
let mut db_data = db_data.get_update_ops()?;
let account = if account >= 0 {
account as u32
@ -958,7 +1015,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createToAdd
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)
match decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &extsk)
{
Ok(Some(extsk)) => extsk,
Ok(None) => {
@ -969,7 +1026,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createToAdd
}
};
let to = match RecipientAddress::decode(&NETWORK, &to) {
let to = match RecipientAddress::decode(&network, &to) {
Some(to) => to,
None => {
return Err(format_err!("Address is for the wrong network"));
@ -990,7 +1047,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createToAdd
// let branch = if
create_spend_to_address(
&mut db_data,
&NETWORK,
&network,
prover,
AccountId(account),
&extsk,
@ -1015,9 +1072,11 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_shieldToAdd
memo: jbyteArray,
spend_params: JString<'_>,
output_params: JString<'_>,
network_id: jint,
) -> jlong {
let res = panic::catch_unwind(|| {
let db_data = wallet_db(&env, NETWORK, db_data)?;
let network = parse_network(network_id as u32)?;
let db_data = wallet_db(&env, network, db_data)?;
let mut db_data = db_data.get_update_ops()?;
let account = if account == 0 {
account as u32
@ -1030,7 +1089,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_shieldToAdd
let spend_params = utils::java_string_to_rust(&env, spend_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)
match decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &extsk)
{
Ok(Some(extsk)) => extsk,
Ok(None) => {
@ -1049,7 +1108,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_shieldToAdd
shield_funds(
&mut db_data,
&NETWORK,
&network,
prover,
AccountId(account),
&sk,
@ -1067,12 +1126,29 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_branchIdFor
env: JNIEnv<'_>,
_: JClass<'_>,
height: jint,
network_id: jint,
) -> jint {
let res = panic::catch_unwind(|| {
let branch: BranchId = BranchId::for_height(&NETWORK, BlockHeight::from(height as u32));
let network = parse_network(network_id as u32)?;
let branch: BranchId = BranchId::for_height(&network, BlockHeight::from(height as u32));
let branch_id: u32 = u32::from(branch);
debug!("For height {} found consensus branch {:?}", height, branch);
Ok(branch_id as i32)
});
unwrap_exc_or(&env, res, -1)
}
//
// Utility functions
//
fn parse_network(value: u32) -> Result<Network, failure::Error> {
match value {
0 => Ok(TestNetwork),
1 => Ok(MainNetwork),
_ => Err(format_err!("Invalid network type: {}. Expected either 0 or 1 for Testnet or Mainnet, respectively.", value))
}
}