Skip to content

Commit

Permalink
Add ZIP 32 Arbitrary Key derivation to DerivationTool
Browse files Browse the repository at this point in the history
  • Loading branch information
str4d committed Oct 23, 2024
1 parent bbdef9d commit 63a5a2d
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

# Unreleased

## Added
- `DerivationTool.deriveArbitraryWalletKey`
- `DerivationTool.deriveArbitraryAccountKey`

# 2.2.6 - 2024-10-22

## Fixed
Expand Down
12 changes: 12 additions & 0 deletions Sources/ZcashLightClientKit/Error/ZcashError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,14 @@ public enum ZcashError: Equatable, Error {
/// sourcery: code="ZRUST0064"
/// ZRUST0064
case rustTransactionDataRequests(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.deriveArbitraryWalletKey
/// - `rustError` contains error generated by the rust layer.
/// ZRUST0065
case rustDeriveArbitraryWalletKey(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.deriveArbitraryAccountKey
/// - `rustError` contains error generated by the rust layer.
/// ZRUST0066
case rustDeriveArbitraryAccountKey(_ rustError: String)
/// SQLite query failed when fetching all accounts from the database.
/// - `sqliteError` is error produced by SQLite library.
/// ZADAO0001
Expand Down Expand Up @@ -730,6 +738,8 @@ public enum ZcashError: Equatable, Error {
case .rustTorClientInit: return "Error from rust layer when calling TorClient.init"
case .rustTorClientGet: return "Error from rust layer when calling TorClient.get"
case .rustTransactionDataRequests: return "Error from rust layer when calling ZcashRustBackend.transactionDataRequests"
case .rustDeriveArbitraryWalletKey: return "Error from rust layer when calling ZcashRustBackend.deriveArbitraryWalletKey"
case .rustDeriveArbitraryAccountKey: return "Error from rust layer when calling ZcashRustBackend.deriveArbitraryAccountKey"
case .accountDAOGetAll: return "SQLite query failed when fetching all accounts from the database."
case .accountDAOGetAllCantDecode: return "Fetched accounts from SQLite but can't decode them."
case .accountDAOFindBy: return "SQLite query failed when seaching for accounts in the database."
Expand Down Expand Up @@ -915,6 +925,8 @@ public enum ZcashError: Equatable, Error {
case .rustTorClientInit: return .rustTorClientInit
case .rustTorClientGet: return .rustTorClientGet
case .rustTransactionDataRequests: return .rustTransactionDataRequests
case .rustDeriveArbitraryWalletKey: return .rustDeriveArbitraryWalletKey
case .rustDeriveArbitraryAccountKey: return .rustDeriveArbitraryAccountKey
case .accountDAOGetAll: return .accountDAOGetAll
case .accountDAOGetAllCantDecode: return .accountDAOGetAllCantDecode
case .accountDAOFindBy: return .accountDAOFindBy
Expand Down
4 changes: 4 additions & 0 deletions Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ public enum ZcashErrorCode: String {
case rustTorClientGet = "ZRUST0063"
/// Error from rust layer when calling ZcashRustBackend.transactionDataRequests
case rustTransactionDataRequests = "ZRUST0064"
/// Error from rust layer when calling ZcashRustBackend.deriveArbitraryWalletKey
case rustDeriveArbitraryWalletKey = "ZRUST0065"
/// Error from rust layer when calling ZcashRustBackend.deriveArbitraryAccountKey
case rustDeriveArbitraryAccountKey = "ZRUST0066"
/// SQLite query failed when fetching all accounts from the database.
case accountDAOGetAll = "ZADAO0001"
/// Fetched accounts from SQLite but can't decode them.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,14 @@ enum ZcashErrorDefinition {
/// - `rustError` contains error generated by the rust layer.
/// sourcery: code="ZRUST0064"
case rustTransactionDataRequests(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.deriveArbitraryWalletKey
/// - `rustError` contains error generated by the rust layer.
// sourcery: code="ZRUST0065"
case rustDeriveArbitraryWalletKey(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.deriveArbitraryAccountKey
/// - `rustError` contains error generated by the rust layer.
// sourcery: code="ZRUST0066"
case rustDeriveArbitraryAccountKey(_ rustError: String)

// MARK: - Account DAO

Expand Down
57 changes: 57 additions & 0 deletions Sources/ZcashLightClientKit/Rust/ZcashKeyDerivationBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,63 @@ struct ZcashKeyDerivationBackend: ZcashKeyDerivationBackendWelding {
return TransparentAddress(validatedEncoding: transparentReceiverStr)
}

static func deriveArbitraryWalletKey(
contextString: [UInt8],
from seed: [UInt8]
) throws -> [UInt8] {
let boxedSlicePtr = contextString.withUnsafeBufferPointer { contextStringBufferPtr in
seed.withUnsafeBufferPointer { seedBufferPtr in
return zcashlc_derive_arbitrary_wallet_key(
contextStringBufferPtr.baseAddress,
UInt(contextString.count),
seedBufferPtr.baseAddress,
UInt(seed.count)
)
}
}

defer { zcashlc_free_boxed_slice(boxedSlicePtr) }

guard let key = boxedSlicePtr?.pointee else {
throw ZcashError.rustDeriveArbitraryWalletKey(
ZcashKeyDerivationBackend.lastErrorMessage(fallback: "`deriveArbitraryWalletKey` failed with unknown error"))
}

return key.ptr.toByteArray(
length: Int(key.len)
)
}

func deriveArbitraryAccountKey(
contextString: [UInt8],
from seed: [UInt8],
accountIndex: Int32
) throws -> [UInt8] {
let boxedSlicePtr = contextString.withUnsafeBufferPointer { contextStringBufferPtr in
seed.withUnsafeBufferPointer { seedBufferPtr in
return zcashlc_derive_arbitrary_account_key(
contextStringBufferPtr.baseAddress,
UInt(contextString.count),
seedBufferPtr.baseAddress,
UInt(seed.count),
accountIndex,
networkType.networkId
)
}
}

defer { zcashlc_free_boxed_slice(boxedSlicePtr) }

guard let key = boxedSlicePtr?.pointee else {
throw ZcashError.rustDeriveArbitraryAccountKey(
ZcashKeyDerivationBackend.lastErrorMessage(fallback: "`deriveArbitraryAccountKey` failed with unknown error"))
}

return key.ptr.toByteArray(
length: Int(key.len)
)
}

// MARK: Error Handling

private static func lastErrorMessage(fallback: String) -> String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,38 @@ protocol ZcashKeyDerivationBackendWelding {
/// - `rustGetTransparentReceiverInvalidAddress` if failed to get transparent receiver for unified address.
/// - `rustGetTransparentReceiverInvalidReceiver` if generated transparent receiver is invalid.
func getTransparentReceiver(for uAddr: UnifiedAddress) throws -> TransparentAddress

/// Derives and returns 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.
///
/// The resulting key will be the same across all networks (Zcash mainnet, Zcash testnet,
/// OtherCoin mainnet, and so on). You can think of it as a context-specific seed fingerprint
/// that can be used as (static) key material.
///
/// - Parameter contextString: a globally-unique non-empty sequence of at most 252 bytes that identifies the desired context.
/// - Parameter seed: `[Uint8]` seed bytes
/// - Parameter accountNumber: `Int` with the account number
/// - Throws:
/// - `derivationToolInvalidAccount` if the `accountIndex` is invalid.
/// - some `ZcashError.rust*` error if the derivation fails.
/// - Returns a `[Uint8]`
static func deriveArbitraryWalletKey(
contextString: [UInt8],
from seed: [UInt8]
) throws -> [UInt8]

/// Derives and returns a ZIP 32 Arbitrary Key from the given seed at the account level.
///
/// - Parameter contextString: a globally-unique non-empty sequence of at most 252 bytes that identifies the desired context.
/// - Parameter seed: `[Uint8]` seed bytes
/// - Parameter accountNumber: `Int32` with the account number
/// - Throws:
/// - `derivationToolInvalidAccount` if the `accountIndex` is invalid.
/// - some `ZcashError.rust*` error if the derivation fails.
/// - Returns a `[Uint8]`
func deriveArbitraryAccountKey(
contextString: [UInt8],
from seed: [UInt8],
accountIndex: Int32
) throws -> [UInt8]
}
59 changes: 59 additions & 0 deletions Sources/ZcashLightClientKit/Tool/DerivationTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,33 @@ public protocol KeyDeriving {
func receiverTypecodesFromUnifiedAddress(_ address: UnifiedAddress) throws -> [UnifiedAddress.ReceiverTypecodes]

static func getAddressMetadata(_ addr: String) -> AddressMetadata?

/// Derives and returns 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.
///
/// The resulting key will be the same across all networks (Zcash mainnet, Zcash testnet,
/// OtherCoin mainnet, and so on). You can think of it as a context-specific seed fingerprint
/// that can be used as (static) key material.
///
/// - Parameter contextString: a globally-unique non-empty sequence of at most 252 bytes that identifies the desired context.
/// - Parameter seed: `[Uint8]` seed bytes
/// - Parameter accountNumber: `Int` with the account number
/// - Throws:
/// - `derivationToolInvalidAccount` if the `accountIndex` is invalid.
/// - some `ZcashError.rust*` error if the derivation fails.
/// - Returns a `[Uint8]`
static func deriveArbitraryWalletKey(contextString: [UInt8], seed: [UInt8]) throws -> [UInt8]

/// Derives and returns a ZIP 32 Arbitrary Key from the given seed at the account level.
///
/// - Parameter contextString: a globally-unique non-empty sequence of at most 252 bytes that identifies the desired context.
/// - Parameter seed: `[Uint8]` seed bytes
/// - Parameter accountNumber: `Int` with the account number
/// - Throws:
/// - `derivationToolInvalidAccount` if the `accountIndex` is invalid.
/// - some `ZcashError.rust*` error if the derivation fails.
/// - Returns a `[Uint8]`
func deriveArbitraryAccountKey(contextString: [UInt8], seed: [UInt8], accountIndex: Int) throws -> [UInt8]
}

public class DerivationTool: KeyDeriving {
Expand Down Expand Up @@ -90,6 +117,38 @@ public class DerivationTool: KeyDeriving {
return try backend.receiverTypecodesOnUnifiedAddress(address.stringEncoded)
.map { UnifiedAddress.ReceiverTypecodes(typecode: $0) }
}

/// Derives and returns 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.
///
/// The resulting key will be the same across all networks (Zcash mainnet, Zcash testnet,
/// OtherCoin mainnet, and so on). You can think of it as a context-specific seed fingerprint
/// that can be used as (static) key material.
///
/// - Parameter contextString: a globally-unique non-empty sequence of at most 252 bytes that identifies the desired context.
/// - Parameter seed: `[Uint8]` seed bytes
/// - Parameter accountNumber: `Int` with the account number
/// - Throws:
/// - `derivationToolInvalidAccount` if the `accountIndex` is invalid.
/// - some `ZcashError.rust*` error if the derivation fails.
/// - Returns a `[Uint8]`
public static func deriveArbitraryWalletKey(contextString: [UInt8], seed: [UInt8]) throws -> [UInt8] {
return try ZcashKeyDerivationBackend.deriveArbitraryWalletKey(contextString: contextString, from: seed)
}

/// Derives and returns a ZIP 32 Arbitrary Key from the given seed at the account level.
///
/// - Parameter contextString: a globally-unique non-empty sequence of at most 252 bytes that identifies the desired context.
/// - Parameter seed: `[Uint8]` seed bytes
/// - Parameter accountNumber: `Int` with the account number
/// - Throws:
/// - `derivationToolInvalidAccount` if the `accountIndex` is invalid.
/// - some `ZcashError.rust*` error if the derivation fails.
/// - Returns a `[Uint8]`
public func deriveArbitraryAccountKey(contextString: [UInt8], seed: [UInt8], accountIndex: Int) throws -> [UInt8] {
guard accountIndex >= 0, let accountIndex = Int32(exactly: accountIndex) else { throw ZcashError.derivationToolInvalidAccount }
return try backend.deriveArbitraryAccountKey(contextString: contextString, from: seed, accountIndex: accountIndex)
}
}

public struct AddressMetadata {
Expand Down

0 comments on commit 63a5a2d

Please sign in to comment.