Add `SdkSynchronizer.createAccount()` method

It is currently hidden from the public API, implemented for testing
purposes.
This commit is contained in:
Jack Grigg 2022-09-27 14:01:53 +01:00 committed by Carter Jernigan
parent c6fd783317
commit f69cacb9e6
11 changed files with 184 additions and 18 deletions

View File

@ -6,6 +6,9 @@ Change Log
### Added
- `cash.z.ecc.android.sdk`:
- `Synchronizer.isValidUnifiedAddr`
- `cash.z.ecc.android.sdk.model`:
- `FirstClassByteArray`
- `UnifiedSpendingKey`
- `cash.z.ecc.android.sdk.tool`:
- `DerivationTool.deriveTransparentAccountPrivateKey`
- `DerivationTool.deriveTransparentAddressFromAccountPrivateKey`

20
sdk-lib/Cargo.lock generated
View File

@ -591,7 +591,7 @@ dependencies = [
[[package]]
name = "equihash"
version = "0.2.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=6cb0d212195a32b7456c866bcd367fc98967b668#6cb0d212195a32b7456c866bcd367fc98967b668"
source = "git+https://github.com/zcash/librustzcash.git?rev=774ffadf5a0120a74d70d281974d079ccd58c600#774ffadf5a0120a74d70d281974d079ccd58c600"
dependencies = [
"blake2b_simd",
"byteorder",
@ -609,7 +609,7 @@ dependencies = [
[[package]]
name = "f4jumble"
version = "0.1.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=6cb0d212195a32b7456c866bcd367fc98967b668#6cb0d212195a32b7456c866bcd367fc98967b668"
source = "git+https://github.com/zcash/librustzcash.git?rev=774ffadf5a0120a74d70d281974d079ccd58c600#774ffadf5a0120a74d70d281974d079ccd58c600"
dependencies = [
"blake2b_simd",
]
@ -2037,7 +2037,7 @@ dependencies = [
[[package]]
name = "zcash_address"
version = "0.1.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=6cb0d212195a32b7456c866bcd367fc98967b668#6cb0d212195a32b7456c866bcd367fc98967b668"
source = "git+https://github.com/zcash/librustzcash.git?rev=774ffadf5a0120a74d70d281974d079ccd58c600#774ffadf5a0120a74d70d281974d079ccd58c600"
dependencies = [
"bech32",
"bs58",
@ -2048,12 +2048,13 @@ dependencies = [
[[package]]
name = "zcash_client_backend"
version = "0.5.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=6cb0d212195a32b7456c866bcd367fc98967b668#6cb0d212195a32b7456c866bcd367fc98967b668"
source = "git+https://github.com/zcash/librustzcash.git?rev=774ffadf5a0120a74d70d281974d079ccd58c600#774ffadf5a0120a74d70d281974d079ccd58c600"
dependencies = [
"base64",
"bech32",
"bls12_381",
"bs58",
"byteorder",
"crossbeam-channel",
"ff",
"group",
@ -2061,6 +2062,7 @@ dependencies = [
"hex",
"jubjub",
"log",
"memuse",
"nom",
"orchard",
"percent-encoding",
@ -2084,7 +2086,7 @@ dependencies = [
[[package]]
name = "zcash_client_sqlite"
version = "0.3.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=6cb0d212195a32b7456c866bcd367fc98967b668#6cb0d212195a32b7456c866bcd367fc98967b668"
source = "git+https://github.com/zcash/librustzcash.git?rev=774ffadf5a0120a74d70d281974d079ccd58c600#774ffadf5a0120a74d70d281974d079ccd58c600"
dependencies = [
"bech32",
"bs58",
@ -2108,7 +2110,7 @@ dependencies = [
[[package]]
name = "zcash_encoding"
version = "0.1.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=6cb0d212195a32b7456c866bcd367fc98967b668#6cb0d212195a32b7456c866bcd367fc98967b668"
source = "git+https://github.com/zcash/librustzcash.git?rev=774ffadf5a0120a74d70d281974d079ccd58c600#774ffadf5a0120a74d70d281974d079ccd58c600"
dependencies = [
"byteorder",
"nonempty",
@ -2117,7 +2119,7 @@ dependencies = [
[[package]]
name = "zcash_note_encryption"
version = "0.1.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=6cb0d212195a32b7456c866bcd367fc98967b668#6cb0d212195a32b7456c866bcd367fc98967b668"
source = "git+https://github.com/zcash/librustzcash.git?rev=774ffadf5a0120a74d70d281974d079ccd58c600#774ffadf5a0120a74d70d281974d079ccd58c600"
dependencies = [
"chacha20 0.9.0",
"chacha20poly1305 0.10.1",
@ -2130,7 +2132,7 @@ dependencies = [
[[package]]
name = "zcash_primitives"
version = "0.7.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=6cb0d212195a32b7456c866bcd367fc98967b668#6cb0d212195a32b7456c866bcd367fc98967b668"
source = "git+https://github.com/zcash/librustzcash.git?rev=774ffadf5a0120a74d70d281974d079ccd58c600#774ffadf5a0120a74d70d281974d079ccd58c600"
dependencies = [
"aes",
"bip0039",
@ -2167,7 +2169,7 @@ dependencies = [
[[package]]
name = "zcash_proofs"
version = "0.7.1"
source = "git+https://github.com/zcash/librustzcash.git?rev=6cb0d212195a32b7456c866bcd367fc98967b668#6cb0d212195a32b7456c866bcd367fc98967b668"
source = "git+https://github.com/zcash/librustzcash.git?rev=774ffadf5a0120a74d70d281974d079ccd58c600#774ffadf5a0120a74d70d281974d079ccd58c600"
dependencies = [
"bellman",
"blake2b_simd",

View File

@ -21,8 +21,8 @@ log-panics = "2.0.0"
schemer = "0.2"
secp256k1 = "0.21"
secrecy = "0.8"
zcash_client_backend = { version = "0.5", features = ["transparent-inputs"] }
zcash_client_sqlite = { version = "0.3", features = ["transparent-inputs"] }
zcash_client_backend = { version = "0.5", features = ["transparent-inputs", "unstable"] }
zcash_client_sqlite = { version = "0.3", features = ["transparent-inputs", "unstable"] }
zcash_primitives = "0.7"
zcash_proofs = "0.7"
@ -30,11 +30,11 @@ zcash_proofs = "0.7"
[patch.crates-io]
group = { git = "https://github.com/zkcrypto/group.git", rev = "a7f3ceb2373e9fe536996f7b4d55c797f3e667f0" }
orchard = { git = 'https://github.com/zcash/orchard.git', rev='f206b3f5d4e31bba75d03d9d03d5fa25825a9384' }
zcash_client_backend = { git = 'https://github.com/zcash/librustzcash.git', rev='6cb0d212195a32b7456c866bcd367fc98967b668' }
zcash_client_sqlite = { git = 'https://github.com/zcash/librustzcash.git', rev='6cb0d212195a32b7456c866bcd367fc98967b668' }
zcash_note_encryption = { git = 'https://github.com/zcash/librustzcash.git', rev='6cb0d212195a32b7456c866bcd367fc98967b668' }
zcash_primitives = { git = 'https://github.com/zcash/librustzcash.git', rev='6cb0d212195a32b7456c866bcd367fc98967b668' }
zcash_proofs = { git = 'https://github.com/zcash/librustzcash.git', rev='6cb0d212195a32b7456c866bcd367fc98967b668' }
zcash_client_backend = { git = 'https://github.com/zcash/librustzcash.git', rev='774ffadf5a0120a74d70d281974d079ccd58c600' }
zcash_client_sqlite = { git = 'https://github.com/zcash/librustzcash.git', rev='774ffadf5a0120a74d70d281974d079ccd58c600' }
zcash_note_encryption = { git = 'https://github.com/zcash/librustzcash.git', rev='774ffadf5a0120a74d70d281974d079ccd58c600' }
zcash_primitives = { git = 'https://github.com/zcash/librustzcash.git', rev='774ffadf5a0120a74d70d281974d079ccd58c600' }
zcash_proofs = { git = 'https://github.com/zcash/librustzcash.git', rev='774ffadf5a0120a74d70d281974d079ccd58c600' }
## Uncomment this to test librustzcash changes locally
#[patch.crates-io]

View File

@ -49,6 +49,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.model.BlockHeight
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork
@ -622,6 +623,14 @@ class SdkSynchronizer internal constructor(
}
}
//
// Account management
//
// Not ready to be a public API; internal for testing only
internal suspend fun createAccount(seed: ByteArray): UnifiedSpendingKey =
processor.createAccount(seed)
//
// Send / Receive
//

View File

@ -5,6 +5,7 @@ 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.BlockHeight
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork
@ -159,6 +160,33 @@ interface Synchronizer {
// Operations
//
/**
* Adds the next available account-level spend authority, given the current set of
* [ZIP 316](https://zips.z.cash/zip-0316) account identifiers known, to the wallet
* database.
*
* The caller should store the byte encoding of the returned spending key in a secure
* fashion. This encoding **MUST NOT** be exposed to users. It is an internal encoding
* that is inherently unstable, and only intended to be passed between the SDK and the
* storage backend. The caller **MUST NOT** allow this encoding to be exported or
* imported.
*
* If `seed` was imported from a backup and this method is being used to restore a
* previous wallet state, you should use this method to add all of the desired
* accounts before scanning the chain from the seed's birthday height.
*
* By convention, wallets should only allow a new account to be generated after funds
* have been received by the currently-available account (in order to enable
* automated account recovery).
*
* @param seed the wallet's seed phrase.
*
* @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
/**
* Gets the shielded address for the given account. This is syntactic sugar for
* [getShieldedAddress] because we use z-addrs by default.

View File

@ -41,6 +41,7 @@ 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.BlockHeight
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.wallet.sdk.rpc.Service
import io.grpc.StatusRuntimeException
@ -1016,6 +1017,11 @@ class CompactBlockProcessor internal constructor(
suspend fun getLastScannedHeight() =
repository.lastScannedHeight()
// TODO(str4d): CompactBlockProcessor is the wrong place for this, but it's where all the other APIs that need
// access to the RustBackend live. This should be refactored.
internal suspend fun createAccount(seed: ByteArray): UnifiedSpendingKey =
rustBackend.createAccount(seed)
/**
* Get address corresponding to the given account for this wallet.
*

View File

@ -7,6 +7,7 @@ import cash.z.ecc.android.sdk.internal.ext.deleteSuspend
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork
@ -52,6 +53,16 @@ internal class RustBackend private constructor(
)
}
override suspend fun createAccount(seed: ByteArray): UnifiedSpendingKey {
return withContext(SdkDispatchers.DATABASE_IO) {
createAccount(
dataDbFile.absolutePath,
seed,
networkId = network.id
)
}
}
override suspend fun initAccountsTable(vararg keys: UnifiedFullViewingKey): Boolean {
val ufvks = Array(keys.size) { keys[it].encoding }
@ -405,6 +416,9 @@ internal class RustBackend private constructor(
networkId: Int
): Boolean
@JvmStatic
private external fun createAccount(dbDataPath: String, seed: ByteArray, networkId: Int): UnifiedSpendingKey
@JvmStatic
private external fun getCurrentAddress(
dbDataPath: String,

View File

@ -2,6 +2,7 @@ package cash.z.ecc.android.sdk.jni
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork
@ -42,6 +43,8 @@ internal interface RustBackendWelding {
suspend fun initDataDb(seed: ByteArray?): Int
suspend fun createAccount(seed: ByteArray): UnifiedSpendingKey
fun isValidShieldedAddr(addr: String): Boolean
fun isValidTransparentAddr(addr: String): Boolean

View File

@ -0,0 +1,16 @@
package cash.z.ecc.android.sdk.model
class FirstClassByteArray(val byteArray: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as FirstClassByteArray
if (!byteArray.contentEquals(other.byteArray)) return false
return true
}
override fun hashCode() = byteArray.contentHashCode()
}

View File

@ -0,0 +1,32 @@
package cash.z.ecc.android.sdk.model
/**
* A [ZIP 316](https://zips.z.cash/zip-0316) Unified Spending Key.
*
* This is the spend authority for an account under the wallet's seed.
*
* An instance of this class contains all of the per-pool spending keys that could be
* derived at the time of its creation. As such, it is not suitable for long-term storage,
* export/import, or backup purposes.
*/
data class UnifiedSpendingKey internal constructor(
/**
* A [ZIP 316](https://zips.z.cash/zip-0316) account identifier.
*/
val account: Int,
/**
* The binary encoding of the [ZIP 316](https://zips.z.cash/zip-0316) Unified Spending
* Key for [account].
*
* This encoding **MUST NOT** be exposed to users. It is an internal encoding that is
* inherently unstable, and only intended to be passed between the SDK and the storage
* backend. Wallets **MUST NOT** allow this encoding to be exported or imported.
*/
internal val bytes: FirstClassByteArray
) {
// Override to prevent leaking key to logs
override fun toString() = "UnifiedSpendingKey(account=$account)"
fun copyBytes() = bytes.byteArray.copyOf()
}

View File

@ -11,9 +11,10 @@ use std::str::FromStr;
use android_logger::Config;
use failure::format_err;
use hdwallet::traits::{Deserialize, Serialize};
use jni::objects::JValue;
use jni::{
objects::{JClass, JString},
sys::{jboolean, jbyteArray, jint, jlong, jobjectArray, jstring, JNI_FALSE, JNI_TRUE},
sys::{jboolean, jbyteArray, jint, jlong, jobject, jobjectArray, jstring, JNI_FALSE, JNI_TRUE},
JNIEnv,
};
use log::Level;
@ -35,7 +36,7 @@ use zcash_client_backend::{
decode_extended_spending_key, encode_extended_full_viewing_key,
encode_extended_spending_key, AddressCodec,
},
keys::{sapling, UnifiedFullViewingKey},
keys::{sapling, Era, UnifiedFullViewingKey},
wallet::{OvkPolicy, WalletTransparentOutput},
};
use zcash_client_sqlite::wallet::init::WalletMigrationError;
@ -145,6 +146,58 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initDataDb(
unwrap_exc_or(&env, res, -1)
}
/// Adds the next available account-level spend authority, given the current set of
/// [ZIP 316] account identifiers known, to the wallet database.
///
/// Returns the newly created [ZIP 316] account identifier, along with the binary encoding
/// of the [`UnifiedSpendingKey`] for the newly created account. The caller should store
/// the returned spending key in a secure fashion.
///
/// If `seed` was imported from a backup and this method is being used to restore a
/// previous wallet state, you should use this method to add all of the desired
/// accounts before scanning the chain from the seed's birthday height.
///
/// By convention, wallets should only allow a new account to be generated after funds
/// have been received by the currently-available account (in order to enable
/// automated account recovery).
///
/// [ZIP 316]: https://zips.z.cash/zip-0316
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createAccount(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
seed: jbyteArray,
network_id: jint,
) -> jobject {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let db_data = wallet_db(&env, network, db_data)?;
let seed = SecretVec::new(env.convert_byte_array(seed).unwrap());
let mut db_ops = db_data.get_update_ops()?;
let (account, usk) = db_ops
.create_account(&seed)
.map_err(|e| format_err!("Error while initializing accounts: {}", e))?;
let encoded = usk.to_bytes(Era::Orchard);
let output = env.new_object(
"cash/z/ecc/android/sdk/model/UnifiedSpendingKey",
"(I[B)V",
&[
JValue::Int(u32::from(account) as i32),
JValue::Object(env.byte_array_from_slice(&encoded)?.into()),
],
)?;
Ok(output.into_inner())
});
unwrap_exc_or(&env, res, ptr::null_mut())
}
/// Initialises the data database with the given set of unified full viewing keys.
///
/// This should only be used in special cases for implementing wallet recovery; prefer
/// `RustBackend.createAccount` for normal account creation purposes.
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initAccountsTableWithKeys(
env: JNIEnv<'_>,