Skip to content

Commit

Permalink
Add derivation methods for ZIP 325 private use metadata keys
Browse files Browse the repository at this point in the history
Co-authored-by: Daira-Emma Hopwood <[email protected]>
  • Loading branch information
str4d and daira committed Feb 19, 2025
1 parent c78afc7 commit fe9b9a6
Show file tree
Hide file tree
Showing 12 changed files with 416 additions and 29 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- `AccountMetadataKey`
- `DerivationTool.deriveAccountMetadataKey`
- `DerivationTool.derivePrivateUseMetadataKey`
- `Synchronizer.getTransactionsByMemoSubstring()` has been added
- `Synchronizer.redactPcztForSigner`
- `Synchronizer.pcztRequiresSaplingProofs`
Expand Down
34 changes: 16 additions & 18 deletions backend-lib/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 11 additions & 9 deletions backend-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,19 @@ xz2 = { version = "0.1", features = ["static"] }
#zcash_protocol = { git = "https://github.com/zcash/librustzcash", branch = "main" }

[patch.crates-io]
orchard = { git = "https://github.com/zcash/orchard.git", rev = "b1c22c07300db22239235d16dab096e23369948f" }
pczt = { git = "https://github.com/zcash/librustzcash", rev = "043286755f36e6e201b22c4b683398ac365b8bfc" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "9c59e6c5d04248d3548bca1ce61767e469d946c2" }
pczt = { git = "https://github.com/zcash/librustzcash", rev = "a84f0644e7b2c1ba5635591bde869abcb2921582" }
redjubjub = { git = "https://github.com/ZcashFoundation/redjubjub", rev = "eae848c5c14d9c795d000dd9f4c4762d1aee7ee1" }
sapling = { package = "sapling-crypto", git = "https://github.com/zcash/sapling-crypto.git", rev = "6ca338532912adcd82369220faeea31aab4720c5" }
transparent = { package = "zcash_transparent", git = "https://github.com/zcash/librustzcash", rev = "043286755f36e6e201b22c4b683398ac365b8bfc" }
zcash_address = { git = "https://github.com/zcash/librustzcash", rev = "043286755f36e6e201b22c4b683398ac365b8bfc" }
zcash_client_backend = { git = "https://github.com/zcash/librustzcash", rev = "043286755f36e6e201b22c4b683398ac365b8bfc" }
zcash_client_sqlite = { git = "https://github.com/zcash/librustzcash", rev = "043286755f36e6e201b22c4b683398ac365b8bfc" }
zcash_primitives = { git = "https://github.com/zcash/librustzcash", rev = "043286755f36e6e201b22c4b683398ac365b8bfc" }
zcash_proofs = { git = "https://github.com/zcash/librustzcash", rev = "043286755f36e6e201b22c4b683398ac365b8bfc" }
zcash_protocol = { git = "https://github.com/zcash/librustzcash", rev = "043286755f36e6e201b22c4b683398ac365b8bfc" }
transparent = { package = "zcash_transparent", git = "https://github.com/zcash/librustzcash", rev = "a84f0644e7b2c1ba5635591bde869abcb2921582" }
zcash_address = { git = "https://github.com/zcash/librustzcash", rev = "a84f0644e7b2c1ba5635591bde869abcb2921582" }
zcash_client_backend = { git = "https://github.com/zcash/librustzcash", rev = "a84f0644e7b2c1ba5635591bde869abcb2921582" }
zcash_client_sqlite = { git = "https://github.com/zcash/librustzcash", rev = "a84f0644e7b2c1ba5635591bde869abcb2921582" }
zcash_primitives = { git = "https://github.com/zcash/librustzcash", rev = "a84f0644e7b2c1ba5635591bde869abcb2921582" }
zcash_proofs = { git = "https://github.com/zcash/librustzcash", rev = "a84f0644e7b2c1ba5635591bde869abcb2921582" }
zcash_protocol = { git = "https://github.com/zcash/librustzcash", rev = "a84f0644e7b2c1ba5635591bde869abcb2921582" }
zcash_spec = { git = "https://github.com/daira/zcash_spec.git", rev = "295ed474426a8c2954a534b0bd72b4f4bf0696d9" }
zip32 = { git = "https://github.com/daira/zip32.git", rev = "fa7805dd7d4c9d37756a0ab1d31f55e97aa57eb4" }

[lib]
name = "zcashwalletsdk"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package cash.z.ecc.android.sdk.internal

import cash.z.ecc.android.sdk.internal.model.JniMetadataKey
import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey

interface Derivation {
Expand Down Expand Up @@ -38,6 +39,44 @@ interface Derivation {
numberOfAccounts: Int
): Array<String>

/**
* Derives a ZIP 325 Account Metadata Key from the given seed.
*
* @return an account metadata key.
*/
fun deriveAccountMetadataKey(
seed: ByteArray,
networkId: Int,
accountIndex: Long
): JniMetadataKey

/**
* Derives a metadata key for private use from a ZIP 325 Account Metadata Key.
*
* If `ufvk` is non-null, this method will return one metadata key for every FVK item
* contained within the UFVK, in preference order. As UFVKs may in general change over
* time (due to the inclusion of new higher-preference FVK items, or removal of older
* deprecated FVK items), private usage of these keys should always follow preference
* order:
* - For encryption-like private usage, the first key in the array should always be
* used, and all other keys ignored.
* - For decryption-like private usage, each key in the array should be tried in turn
* until metadata can be recovered, and then the metadata should be re-encrypted
* under the first key.
*
* @param ufvk the external UFVK for which a metadata key is required, or `null` if the
* metadata key is "inherent" (for the same account as the Account Metadata Key).
* @param privateUseSubject a globally unique non-empty sequence of at most 252 bytes that
* identifies the desired private-use context.
* @return an array of 32-byte metadata keys in preference order.
*/
fun derivePrivateUseMetadataKey(
accountMetadataKey: JniMetadataKey,
ufvk: String?,
networkId: Int,
privateUseSubject: ByteArray
): Array<ByteArray>

/**
* Derives a ZIP 32 Arbitrary Key from the given seed at the "wallet level", i.e.
* directly from the seed with no ZIP 32 path applied.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,13 @@ const val JNI_ACCOUNT_UUID_BYTES_SIZE = 16
* The number of bytes in the seed fingerprint parameter. It's used e.g. in [JniAccount.seedFingerprint]
*/
const val JNI_ACCOUNT_SEED_FP_BYTES_SIZE = 32

/**
* The number of bytes in an HD-derived ZIP 32 key. It's used e.g. in [JniMetadataKey.sk]
*/
const val JNI_METADATA_KEY_SK_SIZE = 32

/**
* The number of bytes in a chain code. It's used e.g. in [JniMetadataKey.chainCode]
*/
const val JNI_METADATA_KEY_CHAIN_CODE_SIZE = 32
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cash.z.ecc.android.sdk.internal.jni

import cash.z.ecc.android.sdk.internal.Derivation
import cash.z.ecc.android.sdk.internal.model.JniMetadataKey
import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey

class RustDerivationTool private constructor() : Derivation {
Expand Down Expand Up @@ -40,6 +41,26 @@ class RustDerivationTool private constructor() : Derivation {
networkId: Int
): String = deriveUnifiedAddressFromViewingKey(viewingKey, networkId = networkId)

override fun deriveAccountMetadataKey(
seed: ByteArray,
networkId: Int,
accountIndex: Long
): JniMetadataKey = deriveAccountMetadataKeyFromSeed(seed, accountIndex, networkId)

override fun derivePrivateUseMetadataKey(
accountMetadataKey: JniMetadataKey,
ufvk: String?,
networkId: Int,
privateUseSubject: ByteArray
): Array<ByteArray> =
derivePrivateUseMetadataKey(
accountMetadataKey_sk = accountMetadataKey.sk,
accountMetadataKey_c = accountMetadataKey.chainCode,
ufvk,
privateUseSubject,
networkId
)

override fun deriveArbitraryWalletKey(
contextString: ByteArray,
seed: ByteArray
Expand Down Expand Up @@ -98,6 +119,22 @@ class RustDerivationTool private constructor() : Derivation {
networkId: Int
): String

private external fun deriveAccountMetadataKeyFromSeed(
seed: ByteArray,
accountIndex: Long,
networkId: Int
): JniMetadataKey

@Suppress("FunctionParameterNaming")
@JvmStatic
private external fun derivePrivateUseMetadataKey(
accountMetadataKey_sk: ByteArray,
accountMetadataKey_c: ByteArray,
ufvk: String?,
privateUseSubject: ByteArray,
networkId: Int
): Array<ByteArray>

@JvmStatic
private external fun deriveArbitraryWalletKeyFromSeed(
contextString: ByteArray,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package cash.z.ecc.android.sdk.internal.model

import androidx.annotation.Keep
import cash.z.ecc.android.sdk.internal.jni.JNI_METADATA_KEY_CHAIN_CODE_SIZE
import cash.z.ecc.android.sdk.internal.jni.JNI_METADATA_KEY_SK_SIZE

/**
* Serves as cross layer (Kotlin, Rust) communication class.
*
* @param sk the ZIP 32 key required to derive child keys.
* @param chainCode The ZIP 32 chain code required to derive child keys.
*
* @throws IllegalArgumentException if the values are inconsistent.
*/
@Keep
class JniMetadataKey(
val sk: ByteArray,
val chainCode: ByteArray,
) {
init {
require(sk.size == JNI_METADATA_KEY_SK_SIZE) {
"Account UUID must be 32 bytes"
}

require(chainCode.size == JNI_METADATA_KEY_CHAIN_CODE_SIZE) {
"Seed fingerprint must be 32 bytes"
}
}
}
Loading

0 comments on commit fe9b9a6

Please sign in to comment.