[#461] Adopt a Type-Safe Keys and Addresses API

This PR creates data types for Addresses and Keys so that they are
not represented by Strings anymore. This avoids mistakenly use
the wrong keys because they are all alike for the type system.

New Protocols:
=============

StringEncoded -> Protocol that makes a type can be expressed in an
string-encoded fashion either for UI or Interchange purposes.

Undescribable -> A protocol that implements methods that override default
decriptions used by debuggers, loggers and event trackers to avoid types
conforming to it to be leaked to logs.

Deleted Protocols:
==================

UnifiedFullViewingKey --> turned into a struct.
UnifiedAddress --> turned into a struct

new Error Type:
================

````
enum KeyEncodingError: Error {
    case invalidEncoding
}
````

This error is thrown when an Address or Key type (addresses are public
keys in the end) can be decoded from their String representation,
typically upon initialization from a User input.

New Types:
=========

SaplingExtendedSpendingKey -> Type for Sapling Extended Full Viewing Keys
this type will be replaced with Unified Spending Keys soon.

SaplingExtendedFullViewingKey -> Extended Full Viewing Key for Sapling.
Maintains existing funcionality. Will be probably deprecated in favor of
UFVK.

TransparentAccountPrivKey -> Private key for transparent account. Used
only for shielding operations. Note: this will probably be deprecated soon.

UnifiedFullViewingKey -> Replaces the protocol that had the same name.

TransparentAddress -> Replaces a type alias with a struct

SaplingAddress --> Represents a Sapling receiver address. Comonly called zAddress. This address corresponds to the Zcash Sapling shielded pool.
Although this it is fully functional, we encourage developers to
choose `UnifiedAddress` before Sapling or Transparent ones.

UnifiedAddress -> Represents a UA. String-encodable and Equatable. Use of
UAs must be favored instead of individual receivers for different pools.
This type can't be decomposed into their Receiver types yet.

Recipient -> This represents all valid receiver types to be used as
inputs for outgoing transactions.

````
public enum Recipient: Equatable, StringEncoded {
    case transparent(TransparentAddress)
    case sapling(SaplingAddress)
    case unified(UnifiedAddress)
````

The wrapped concrete receiver is a valid receiver type.

Deleted Type Aliases:
=====================

The following aliases were deleted and turned into types
````
public typealias TransparentAddress = String
public typealias SaplingShieldedAddress = String

````

Changes to Derivation Tool
==========================

DerivationTool has been changed to accomodate this new types and
remove Strings whenever possible.

Changes to Synchronizer and CompactBlockProcessor
=================================================
Accordingly these to components have been modified to accept the
new types intead of strings when possible.

Changes to Demo App
===================
The demo App has been patch to compile and work with the new types.
Developers must consider that the use (and abuse) of forced_try and
forced unwrapping is a "license" that maintainers are using for the
sake of brevity. We consider that clients of this SDK do know how to
handle Errors and Optional and it is not the objective of the demo
code to show good practices on those matters.

Closes #461
This commit is contained in:
Francisco Gindre 2022-08-20 19:10:22 -03:00
parent 905ee401d1
commit ed87a2781c
41 changed files with 1033 additions and 678 deletions

View File

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@ -15,7 +15,6 @@
0D756A94236C761E009B041B /* GetAddressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D756A93236C761E009B041B /* GetAddressViewController.swift */; };
0D76121926B1D66D001CA417 /* Testnet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D76121826B1D66D001CA417 /* Testnet.swift */; };
0D7A4A83236CCD88001F4DD8 /* SyncBlocksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7A4A82236CCD88001F4DD8 /* SyncBlocksViewController.swift */; };
0D7C85E523AD5A9B006878FC /* SampleStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7C85E423AD5A9B006878FC /* SampleStorage.swift */; };
0D907F162322CC5900D641FE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D907F152322CC5900D641FE /* AppDelegate.swift */; };
0D907F182322CC5900D641FE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D907F172322CC5900D641FE /* ViewController.swift */; };
0D907F1B2322CC5900D641FE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0D907F192322CC5900D641FE /* Main.storyboard */; };
@ -35,7 +34,6 @@
0DA1C4C727D11D9500E5006E /* SaplingParametersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D6CE8BC252E3C4A0005D707 /* SaplingParametersViewController.swift */; };
0DA1C4C827D11D9500E5006E /* GetUTXOsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1BE47E2581937100F78BE3 /* GetUTXOsViewController.swift */; };
0DA1C4C927D11D9500E5006E /* DerivationToolViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1BE4502581585C00F78BE3 /* DerivationToolViewController.swift */; };
0DA1C4CA27D11D9500E5006E /* SampleStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7C85E423AD5A9B006878FC /* SampleStorage.swift */; };
0DA1C4CB27D11D9500E5006E /* SampleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D49A18B241698A800CC0649 /* SampleLogger.swift */; };
0DA1C4CC27D11D9500E5006E /* TransactionsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA58B952397F2CB004596EA /* TransactionsDataSource.swift */; };
0DA1C4CD27D11D9500E5006E /* PaginatedTransactionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF53E6623A438F100D7249C /* PaginatedTransactionsViewController.swift */; };
@ -83,7 +81,6 @@
0D76121526B1D5ED001CA417 /* Mainnet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mainnet.swift; sourceTree = "<group>"; };
0D76121826B1D66D001CA417 /* Testnet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Testnet.swift; sourceTree = "<group>"; };
0D7A4A82236CCD88001F4DD8 /* SyncBlocksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncBlocksViewController.swift; sourceTree = "<group>"; };
0D7C85E423AD5A9B006878FC /* SampleStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleStorage.swift; sourceTree = "<group>"; };
0D907F122322CC5900D641FE /* ZcashLightClientSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ZcashLightClientSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
0D907F152322CC5900D641FE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
0D907F172322CC5900D641FE /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
@ -248,7 +245,6 @@
0D907F212322CC5B00D641FE /* Info.plist */,
F94912622790D7C4004BB3DE /* ZcashLightClientSample-Mainnet-Info.plist */,
0DDFB33D236B844900AED892 /* DemoAppConfig.swift */,
0D7C85E423AD5A9B006878FC /* SampleStorage.swift */,
0D49A18B241698A800CC0649 /* SampleLogger.swift */,
);
path = ZcashLightClientSample;
@ -498,6 +494,7 @@
};
0DCB58A4237B5B580040096C /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -534,7 +531,6 @@
0D6CE8BD252E3C4A0005D707 /* SaplingParametersViewController.swift in Sources */,
0D1BE47F2581937100F78BE3 /* GetUTXOsViewController.swift in Sources */,
0D1BE4512581585C00F78BE3 /* DerivationToolViewController.swift in Sources */,
0D7C85E523AD5A9B006878FC /* SampleStorage.swift in Sources */,
0D49A18C241698A800CC0649 /* SampleLogger.swift in Sources */,
0DA58B962397F2CB004596EA /* TransactionsDataSource.swift in Sources */,
0DF53E6723A438F100D7249C /* PaginatedTransactionsViewController.swift in Sources */,
@ -567,7 +563,6 @@
0DA1C4C727D11D9500E5006E /* SaplingParametersViewController.swift in Sources */,
0DA1C4C827D11D9500E5006E /* GetUTXOsViewController.swift in Sources */,
0DA1C4C927D11D9500E5006E /* DerivationToolViewController.swift in Sources */,
0DA1C4CA27D11D9500E5006E /* SampleStorage.swift in Sources */,
0DA1C4CB27D11D9500E5006E /* SampleLogger.swift in Sources */,
0DA1C4CC27D11D9500E5006E /* TransactionsDataSource.swift in Sources */,
0DA1C4CD27D11D9500E5006E /* PaginatedTransactionsViewController.swift in Sources */,

View File

@ -138,10 +138,10 @@
},
{
"package": "libzcashlc",
"repositoryURL": "https://github.com/nuttycom/zcash-light-client-ffi.git",
"repositoryURL": "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state": {
"branch": "bin/librustzcash_0_7-migrations",
"revision": "dbf7ac51a7de0bbc2c1088973236d68ceb752361",
"branch": "bin/librustzcash_0_7",
"revision": "823f864a7952073fb9718cf75610691756e34d59",
"version": null
}
}

View File

@ -50,10 +50,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
)
_ = try! wallet.initialize(with: DemoAppConfig.seed)
var storage = SampleStorage.shared
storage!.seed = Data(DemoAppConfig.seed)
storage!.privateKey = try! DerivationTool(networkType: kZcashNetwork.networkType)
.deriveSpendingKeys(seed: DemoAppConfig.seed, numberOfAccounts: 1)[0]
self.wallet = wallet
return wallet
}
@ -141,10 +138,6 @@ extension AppDelegate {
} catch {
loggerProxy.error("error clearing data db: \(error)")
}
var storage = SampleStorage.shared
storage!.seed = nil
storage!.privateKey = nil
}
}

View File

@ -13,8 +13,6 @@ class DerivationToolViewController: UIViewController {
enum DerivationErrors: Error {
case couldNotDeriveSpendingKeys(underlyingError: Error)
case couldNotDeriveViewingKeys(underlyingError: Error)
case couldNotDeriveShieldedAddress(underlyingError: Error)
case couldNotDeriveTransparentAddress(underlyingError: Error)
case unknown
}
@ -113,15 +111,15 @@ class DerivationToolViewController: UIViewController {
throw DerivationErrors.couldNotDeriveViewingKeys(underlyingError: DerivationErrors.unknown)
}
let unifiedAddress = try derivationTool.deriveUnifiedAddress(viewingKey: viewingKey.encoding)
let unifiedAddress = try derivationTool.deriveUnifiedAddress(from: viewingKey)
let transparentAddress = try derivationTool.deriveTransparentAddress(seed: seedBytes)
updateLabels(
spendingKey: spendingKey,
viewingKey: viewingKey.encoding,
shieldedAddress: unifiedAddress,
transaparentAddress: transparentAddress
spendingKey: spendingKey.stringEncoded,
viewingKey: viewingKey.stringEncoded,
shieldedAddress: unifiedAddress.stringEncoded,
transaparentAddress: transparentAddress.stringEncoded
)
}

View File

@ -12,15 +12,23 @@ class GetAddressViewController: UIViewController {
@IBOutlet weak var unifiedAddressLabel: UILabel!
@IBOutlet weak var tAddressLabel: UILabel!
@IBOutlet weak var spendingKeyLabel: UILabel! // THIS SHOULD BE SUPER SECRET!!!!!
// THIS SHOULD NEVER BE STORED IN MEMORY
// swiftlint:disable:next implicitly_unwrapped_optional
var spendingKey: SaplingExtendedSpendingKey!
override func viewDidLoad() {
super.viewDidLoad()
let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType)
// swiftlint:disable:next force_try force_unwrapping
self.spendingKey = try! derivationTool.deriveSpendingKeys(seed: DemoAppConfig.seed, numberOfAccounts: 1).first!
// Do any additional setup after loading the view.
unifiedAddressLabel.text = (try? derivationTool.deriveUnifiedAddress(seed: DemoAppConfig.seed, accountIndex: 0)) ?? "No Addresses found"
tAddressLabel.text = (try? derivationTool.deriveTransparentAddress(seed: DemoAppConfig.seed)) ?? "could not derive t-address"
spendingKeyLabel.text = SampleStorage.shared.privateKey ?? "No Spending Key found"
// swiftlint:disable:next line_length
unifiedAddressLabel.text = (try? derivationTool.deriveUnifiedAddress(seed: DemoAppConfig.seed, accountIndex: 0))?.stringEncoded ?? "No Addresses found"
tAddressLabel.text = (try? derivationTool.deriveTransparentAddress(seed: DemoAppConfig.seed))?.stringEncoded ?? "could not derive t-address"
spendingKeyLabel.text = self.spendingKey.stringEncoded
unifiedAddressLabel.addGestureRecognizer(
UITapGestureRecognizer(
target: self,
@ -44,20 +52,12 @@ class GetAddressViewController: UIViewController {
)
spendingKeyLabel.isUserInteractionEnabled = true
loggerProxy.info("Address: \(String(describing: Initializer.shared.getAddress()))")
// NOTE: NEVER LOG YOUR PRIVATE KEYS IN REAL LIFE
// swiftlint:disable:next print_function_usage
print("Spending Key: \(SampleStorage.shared.privateKey ?? "No Spending Key found")")
}
@IBAction func spendingKeyTapped(_ gesture: UIGestureRecognizer) {
guard let key = SampleStorage.shared.privateKey else {
loggerProxy.warn("nothing to copy")
return
}
loggerProxy.event("copied to clipboard")
UIPasteboard.general.string = key
UIPasteboard.general.string = self.spendingKey.stringEncoded
let alert = UIAlertController(
title: "",
message: "Spending Key Copied to clipboard",
@ -85,7 +85,7 @@ class GetAddressViewController: UIViewController {
@IBAction func tAddressTapped(_ gesture: UIGestureRecognizer) {
loggerProxy.event("copied to clipboard")
UIPasteboard.general.string = try? DerivationTool(networkType: kZcashNetwork.networkType)
.deriveTransparentAddress(seed: DemoAppConfig.seed)
.deriveTransparentAddress(seed: DemoAppConfig.seed).stringEncoded
let alert = UIAlertController(
title: "",

View File

@ -27,7 +27,7 @@ class GetUTXOsViewController: UIViewController {
let tAddress = try! DerivationTool(networkType: kZcashNetwork.networkType)
.deriveTransparentAddress(seed: DemoAppConfig.seed)
self.transparentAddressLabel.text = tAddress
self.transparentAddressLabel.text = tAddress.stringEncoded
// swiftlint:disable:next force_try
let balance = try! AppDelegate.shared.sharedSynchronizer.getTransparentBalance(accountIndex: 0)
@ -40,14 +40,12 @@ class GetUTXOsViewController: UIViewController {
do {
let seed = DemoAppConfig.seed
let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType)
// swiftlint:disable:next force_unwrapping
let spendingKey = try derivationTool.deriveSpendingKeys(seed: seed, numberOfAccounts: 1).first!
let transparentSecretKey = try derivationTool.deriveTransparentAccountPrivateKey(seed: seed, account: 0)
KRProgressHUD.showMessage("🛡 Shielding 🛡")
AppDelegate.shared.sharedSynchronizer.shieldFunds(
spendingKey: spendingKey,
transparentAccountPrivateKey: transparentSecretKey,
memo: "shielding is fun!",
from: 0,

View File

@ -1,53 +0,0 @@
//
// SampleStorage.swift
// ZcashLightClientSample
//
// Created by Francisco Gindre on 12/20/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
protocol WalletStorage {
var seed: Data? { get set }
var privateKey: String? { get set }
}
/**
DO NOT STORE YOUR KEYS ON USER DEFAULTS!!!
USE KEYCHAIN OR ANY OTHER SECURE STORAGE MECHANISM OF YOUR CHOICE
USE AT YOUR OWN RISK
*/
class SampleStorage: WalletStorage {
// swiftlint:disable:next implicitly_unwrapped_optional
static var shared: WalletStorage! {
_shared
}
private static var _shared = SampleStorage()
private let keySeed = "cash.z.wallet.sdk.demoapp.SEED"
private let keyPK = "cash.z.wallet.sdk.demoapp.PK"
var seed: Data? {
get {
// swiftlint:disable:next force_cast
UserDefaults.standard.value(forKey: keySeed) as! Data?
}
set {
UserDefaults.standard.set(newValue, forKey: keySeed)
}
}
var privateKey: String? {
get {
// swiftlint:disable:next force_cast
UserDefaults.standard.value(forKey: keyPK) as! String?
}
set {
UserDefaults.standard.set(newValue, forKey: keyPK)
}
}
private init() {}
}

View File

@ -150,7 +150,7 @@ class SendViewController: UIViewController {
guard let addr = self.addressTextField.text else {
return false
}
return wallet.isValidShieldedAddress(addr) || wallet.isValidTransparentAddress(addr)
return wallet.isValidSaplingAddress(addr) || wallet.isValidTransparentAddress(addr)
}
@IBAction func maxFundsValueChanged(_ sender: Any) {
@ -206,18 +206,19 @@ class SendViewController: UIViewController {
loggerProxy.warn("WARNING: Form is invalid")
return
}
guard let address = SampleStorage.shared.privateKey else {
loggerProxy.error("NO ADDRESS")
// swiftlint:disable:next line_length force_try
guard let spendingKey = try! DerivationTool(networkType: kZcashNetwork.networkType).deriveSpendingKeys(seed: DemoAppConfig.seed, numberOfAccounts: 1).first else {
loggerProxy.error("NO SPENDING KEY")
return
}
KRProgressHUD.show()
synchronizer.sendToAddress(
spendingKey: address,
spendingKey: spendingKey,
zatoshi: zec,
toAddress: recipient,
// swiftlint:disable:next force_try
toAddress: try! Recipient(recipient, network: kZcashNetwork.networkType),
memo: !self.memoField.text.isEmpty ? self.memoField.text : nil,
from: 0
) { [weak self] result in

View File

@ -84,10 +84,10 @@
},
{
"package": "libzcashlc",
"repositoryURL": "https://github.com/zcash-hackworks/zcash-light-client-ffi.git",
"repositoryURL": "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state": {
"branch": "librustzcash_0_7_build_test",
"revision": "5a48541c1da48eb082defaf0fa127657f4f99094",
"branch": "bin/librustzcash_0_7",
"revision": "823f864a7952073fb9718cf75610691756e34d59",
"version": null
}
}

View File

@ -5,7 +5,7 @@ let package = Package(
name: "ZcashLightClientKit",
platforms: [
.iOS(.v13),
.macOS(.v10_12)
.macOS(.v10_15)
],
products: [
.library(
@ -16,7 +16,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.8.0"),
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.0"),
.package(name:"libzcashlc", url: "https://github.com/zcash-hackworks/zcash-light-client-ffi.git", branch: "librustzcash_0_7_build_test"),
.package(name:"libzcashlc", url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", branch: "bin/librustzcash_0_7"),
],
targets: [
.target(

View File

@ -1260,18 +1260,27 @@ extension CompactBlockProcessor {
extension CompactBlockProcessor {
public func getUnifiedAddres(accountIndex: Int) -> UnifiedAddress? {
guard let account = try? accountRepository.findBy(account: accountIndex) else {
// TODO: perform migrations on the account table to accommodate Unified Address or UFVK to to derive from.
guard let address = try? accountRepository.findBy(account: accountIndex)?.address else {
return nil
}
return UnifiedAddressShim(account: account)
return try? UnifiedAddress(encoding: address, network: self.config.network.networkType)
}
public func getShieldedAddress(accountIndex: Int) -> SaplingShieldedAddress? {
try? accountRepository.findBy(account: accountIndex)?.address
public func getSaplingAddress(accountIndex: Int) -> SaplingAddress? {
guard let zAddress = try? accountRepository.findBy(account: accountIndex)?.address else {
return nil
}
return try? SaplingAddress(encoding: zAddress, network: self.config.network.networkType)
}
public func getTransparentAddress(accountIndex: Int) -> TransparentAddress? {
try? accountRepository.findBy(account: accountIndex)?.transparentAddress
guard let tAddress = try? accountRepository.findBy(account: accountIndex)?.transparentAddress else { return nil }
return TransparentAddress(validatedEncoding: tAddress)
}
public func getTransparentBalance(accountIndex: Int) throws -> WalletBalance {
@ -1282,20 +1291,6 @@ extension CompactBlockProcessor {
}
}
private struct UnifiedAddressShim {
let account: AccountEntity
}
extension UnifiedAddressShim: UnifiedAddress {
var encoding: String {
account.transparentAddress
}
var zAddress: SaplingShieldedAddress {
account.address
}
}
extension CompactBlockProcessor {
func refreshUTXOs(tAddress: String, startHeight: BlockHeight, result: @escaping (Result<RefreshedUTXOs, Error>) -> Void) {
let dataDb = self.config.dataDb

View File

@ -31,17 +31,6 @@ struct Account: AccountEntity, Encodable, Decodable {
var transparentAddress: String
}
extension Account: UnifiedAddress {
var encoding: String {
get {
address
}
set {
address = newValue
}
}
}
extension Account: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(account)

View File

@ -0,0 +1,23 @@
//
// undescribable.swift
//
//
// Created by Francisco Gindre on 8/30/22.
//
public protocol Undescribable: CustomStringConvertible, CustomDebugStringConvertible, CustomLeafReflectable {}
extension Undescribable {
public var description: String {
return "--redacted--"
}
public var debugDescription: String {
return "--redacted--"
}
public var customMirror: Mirror {
return Mirror(reflecting: "--redacted--")
}
}

View File

@ -308,10 +308,10 @@ public class Initializer {
}
/**
checks if the provided address is a valid shielded zAddress
checks if the provided address is a valid sapling address
*/
public func isValidShieldedAddress(_ address: String) -> Bool {
(try? rustBackend.isValidShieldedAddress(address, networkType: network.networkType)) ?? false
public func isValidSaplingAddress(_ address: String) -> Bool {
(try? rustBackend.isValidSaplingAddress(address, networkType: network.networkType)) ?? false
}
/**

View File

@ -5,21 +5,194 @@
// Created by Francisco Gindre on 4/6/21.
//
enum KeyEncodingError: Error {
case invalidEncoding
}
/// Something that can be encoded as a String
public protocol StringEncoded {
var stringEncoded: String { get }
}
/// Sapling Extended Spending Key
public struct SaplingExtendedSpendingKey: Equatable, StringEncoded, Undescribable {
var encoding: String
public var stringEncoded: String {
encoding
}
/// Initializes a new Sapling Extended Full Viewing Key from the provided string encoding
/// - Parameters:
/// - parameter encoding: String encoding of ExtSK
/// - parameter network: `NetworkType` corresponding to the encoding (Mainnet or Testnet)
/// - Throws: `KeyEncodingError.invalidEncoding`when the provided encoding is
/// found to be invalid
public init(encoding: String, network: NetworkType) throws {
guard let valid = try? DerivationTool(networkType: network).isValidSaplingExtendedSpendingKey(encoding), valid else {
throw KeyEncodingError.invalidEncoding
}
self.encoding = encoding
}
}
/// A Transparent Account Private Key
public struct TransparentAccountPrivKey: Equatable, Undescribable {
var encoding: String
}
/**
A ZIP 316 Unified Full Viewing Key.
*/
public protocol UnifiedFullViewingKey {
var account: UInt32 { get set }
var encoding: String { get set }
public struct UnifiedFullViewingKey: Equatable, StringEncoded, Undescribable {
var encoding: String
public var account: UInt32
public var stringEncoded: String { encoding }
/// Initializes a new UnifiedFullViewingKey (UFVK) from the provided string encoding
/// - Parameters:
/// - parameter encoding: String encoding of unified full viewing key
/// - parameter account: account number of the given UFVK
/// - parameter network: `NetworkType` corresponding to the encoding (Mainnet or Testnet)
/// - Throws: `KeyEncodingError.invalidEncoding`when the provided encoding is
/// found to be invalid
public init(encoding: String, account: UInt32, network: NetworkType) throws {
guard let valid = try? DerivationTool(networkType: network).isValidExtendedViewingKey(encoding), valid else {
throw KeyEncodingError.invalidEncoding
}
self.encoding = encoding
self.account = account
}
}
public typealias TransparentAddress = String
public typealias SaplingShieldedAddress = String
public struct SaplingExtendedFullViewingKey: Equatable, StringEncoded, Undescribable {
var encoding: String
public var stringEncoded: String {
encoding
}
public protocol UnifiedAddress {
var encoding: String { get }
/// Initializes a new Extended Full Viewing key (EFVK) for Sapling from the provided string encoding
/// - Parameters:
/// - parameter encoding: String encoding of Sapling extended full viewing key
/// - parameter network: `NetworkType` corresponding to the encoding (Mainnet or Testnet)
/// - Throws: `KeyEncodingError.invalidEncoding`when the provided encoding is
/// found to be invalid
public init(encoding: String, network: NetworkType) throws {
guard let valid = try? DerivationTool(networkType: network).isValidExtendedViewingKey(encoding), valid else {
throw KeyEncodingError.invalidEncoding
}
self.encoding = encoding
}
}
/// A Transparent Address that can be encoded as a String
///
/// Transactions sent to this address are totally visible in the public
/// ledger. See "Multiple transaction types" in https://z.cash/technology/
public struct TransparentAddress: Equatable, StringEncoded {
var encoding: String
public var stringEncoded: String { encoding }
/// Initializes a new TransparentAddress (t-address) from the provided string encoding
///
/// - parameter encoding: String encoding of the t-address
/// - parameter network: `NetworkType` corresponding to the encoding (Mainnet or Testnet)
/// - Throws: `KeyEncodingError.invalidEncoding`when the provided encoding is
/// found to be invalid
public init(encoding: String, network: NetworkType) throws {
guard let valid = try? DerivationTool(networkType: network).isValidTransparentAddress(encoding), valid else {
throw KeyEncodingError.invalidEncoding
}
self.encoding = encoding
}
}
/// Represents a Sapling receiver address. Comonly called zAddress.
/// This address corresponds to the Zcash Sapling shielded pool.
/// Although this it is fully functional, we encourage developers to
/// choose `UnifiedAddress` before Sapling or Transparent ones.
public struct SaplingAddress: Equatable, StringEncoded {
var encoding: String
public var stringEncoded: String { encoding }
/// Initializes a new Sapling shielded address (z-address) from the provided string encoding
///
/// - parameter encoding: String encoding of the z-address
/// - parameter network: `NetworkType` corresponding to the encoding (Mainnet or Testnet)
///
/// - Throws: `KeyEncodingError.invalidEncoding`when the provided encoding is
/// found to be invalid
public init(encoding: String, network: NetworkType) throws {
guard let valid = try? DerivationTool(networkType: network).isValidSaplingAddress(encoding), valid else {
throw KeyEncodingError.invalidEncoding
}
self.encoding = encoding
}
}
public struct UnifiedAddress: Equatable, StringEncoded {
var encoding: String
public var stringEncoded: String { encoding }
/// Initializes a new Unified Address (UA) from the provided string encoding
/// - Parameters:
/// - parameter encoding: String encoding of the UA
/// - parameter network: `NetworkType` corresponding to the encoding (Mainnet or Testnet)
/// - Throws: `KeyEncodingError.invalidEncoding`when the provided encoding is
/// found to be invalid
public init(encoding: String, network: NetworkType) throws {
guard let valid = try? DerivationTool(networkType: network).isValidUnifiedAddress(encoding), valid else {
throw KeyEncodingError.invalidEncoding
}
self.encoding = encoding
}
}
/// Represents a valid recipient of Zcash
public enum Recipient: Equatable, StringEncoded {
case transparent(TransparentAddress)
case sapling(SaplingAddress)
case unified(UnifiedAddress)
public var stringEncoded: String {
switch self {
case .transparent(let tAddr):
return tAddr.stringEncoded
case .sapling(let zAddr):
return zAddr.stringEncoded
case .unified(let uAddr):
return uAddr.stringEncoded
}
}
/// Initializes a `Recipient` with string encoded Zcash address
/// - Parameter string: a string encoded Zcash address
/// - Parameter network: the `ZcashNetwork.NetworkType` of the recipient
/// - Throws: `KeyEncodingError.invalidEncoding` if the received string-encoded address
/// can't be initialized as a valid Zcash Address.
public init(_ string: String, network: NetworkType) throws {
if let unified = try? UnifiedAddress(encoding: string, network: network) {
self = .unified(unified)
} else if let sapling = try? SaplingAddress(encoding: string, network: network) {
self = .sapling(sapling)
} else if let transparent = try? TransparentAddress(encoding: string, network: network) {
self = .transparent(transparent)
} else {
throw KeyEncodingError.invalidEncoding
}
}
}
public struct WalletBalance: Equatable {
public var verified: Zatoshi
public var total: Zatoshi

View File

@ -10,6 +10,9 @@ import Foundation
import libzcashlc
class ZcashRustBackend: ZcashRustBackendWelding {
static let minimumConfirmations: UInt32 = 10
static func lastError() -> RustWeldingError? {
guard let message = getLastError() else { return nil }
@ -55,7 +58,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
}
}
static func isValidShieldedAddress(_ address: String, networkType: NetworkType) throws -> Bool {
static func isValidSaplingAddress(_ address: String, networkType: NetworkType) throws -> Bool {
guard !address.containsCStringNullBytesBeforeStringEnding() else {
return false
}
@ -85,7 +88,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return true
}
static func isValidExtendedFullViewingKey(_ key: String, networkType: NetworkType) throws -> Bool {
static func isValidSaplingExtendedFullViewingKey(_ key: String, networkType: NetworkType) throws -> Bool {
guard !key.containsCStringNullBytesBeforeStringEnding() else {
return false
}
@ -100,6 +103,35 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return true
}
static func isValidSaplingExtendedSpendingKey(_ key: String, networkType: NetworkType) throws -> Bool {
guard !key.containsCStringNullBytesBeforeStringEnding() else {
return false
}
guard zcashlc_is_valid_sapling_extended_spending_key(key, networkType.networkId) else {
if let error = lastError() {
throw error
}
return false
}
return true
}
static func isValidUnifiedAddress(_ address: String, networkType: NetworkType) throws -> Bool {
guard !address.containsCStringNullBytesBeforeStringEnding() else {
return false
}
guard zcashlc_is_valid_unified_address([CChar](address.utf8CString), networkType.networkId) else {
if let error = lastError() {
throw error
}
return false
}
return true
}
static func isValidUnifiedFullViewingKey(_ key: String, networkType: NetworkType) throws -> Bool {
guard !key.containsCStringNullBytesBeforeStringEnding() else {
return false
@ -115,27 +147,39 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return true
}
static func initAccountsTable(dbData: URL, seed: [UInt8], accounts: Int32, networkType: NetworkType) -> [String]? {
static func initAccountsTable(dbData: URL, seed: [UInt8], accounts: Int32, networkType: NetworkType) -> [SaplingExtendedSpendingKey]? {
let dbData = dbData.osStr()
var capacity = UInt(0)
let extsksCStr = zcashlc_init_accounts_table(dbData.0, dbData.1, seed, UInt(seed.count), accounts, &capacity, networkType.networkId)
if extsksCStr == nil {
guard let ffiEncodingKeysPtr = zcashlc_init_accounts_table(
dbData.0,
dbData.1,
seed,
UInt(seed.count),
accounts,
networkType.networkId
) else {
return nil
}
let extsks = UnsafeBufferPointer(start: extsksCStr, count: Int(accounts)).compactMap({ cStr -> String? in
guard let str = cStr else { return nil }
return String(cString: str)
let extsks = UnsafeBufferPointer(
start: ffiEncodingKeysPtr.pointee.ptr,
count: Int(ffiEncodingKeysPtr.pointee.len)
).compactMap({ encodedKey -> SaplingExtendedSpendingKey in
SaplingExtendedSpendingKey(validatedEncoding: String(cString: encodedKey.encoding))
})
zcashlc_vec_string_free(extsksCStr, UInt(accounts), capacity)
zcashlc_free_keys(ffiEncodingKeysPtr)
return extsks
}
static func initAccountsTable(dbData: URL, ufvks: [UnifiedFullViewingKey], networkType: NetworkType) throws -> Bool {
static func initAccountsTable(
dbData: URL,
ufvks: [UnifiedFullViewingKey],
networkType: NetworkType
) throws -> Bool {
let dbData = dbData.osStr()
var ffiUfvks: [FFIUnifiedViewingKey] = []
var ffiUfvks: [FFIEncodedKey] = []
for ufvk in ufvks {
guard !ufvk.encoding.containsCStringNullBytesBeforeStringEnding() else {
throw RustWeldingError.malformedStringInput
@ -149,15 +193,23 @@ class ZcashRustBackend: ZcashRustBackendWelding {
let ufvkPtr = UnsafeMutablePointer<CChar>.allocate(capacity: ufvkCStr.count)
ufvkPtr.initialize(from: ufvkCStr, count: ufvkCStr.count)
ffiUfvks.append(FFIUnifiedViewingKey(account_id: ufvk.account, encoding: ufvkPtr))
ffiUfvks.append(
FFIEncodedKey(account_id: ufvk.account, encoding: ufvkPtr)
)
}
var result = false
ffiUfvks.withContiguousMutableStorageIfAvailable { pointer in
let slice = UnsafeMutablePointer<FFIUVKBoxedSlice>.allocate(capacity: 1)
slice.initialize(to: FFIUVKBoxedSlice(ptr: pointer.baseAddress, len: UInt(pointer.count)))
let slice = UnsafeMutablePointer<FFIEncodedKeys>.allocate(capacity: 1)
slice.initialize(
to: FFIEncodedKeys(
ptr: pointer.baseAddress,
len: UInt(pointer.count)
)
)
result = zcashlc_init_accounts_table_with_keys(dbData.0, dbData.1, slice, networkType.networkId)
slice.deinitialize(count: 1)
@ -224,7 +276,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
static func getVerifiedBalance(dbData: URL, account: Int32, networkType: NetworkType) -> Int64 {
let dbData = dbData.osStr()
return zcashlc_get_verified_balance(dbData.0, dbData.1, account, networkType.networkId)
return zcashlc_get_verified_balance(dbData.0, dbData.1, account, networkType.networkId, minimumConfirmations)
}
static func getVerifiedTransparentBalance(dbData: URL, address: String, networkType: NetworkType) throws -> Int64 {
@ -238,7 +290,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
let dbData = dbData.osStr()
return zcashlc_get_verified_transparent_balance(dbData.0, dbData.1, [CChar](address.utf8CString), networkType.networkId)
return zcashlc_get_verified_transparent_balance(dbData.0, dbData.1, [CChar](address.utf8CString), networkType.networkId, minimumConfirmations)
}
static func getTransparentBalance(dbData: URL, address: String, networkType: NetworkType) throws -> Int64 {
@ -388,7 +440,8 @@ class ZcashRustBackend: ZcashRustBackendWelding {
UInt(spendParamsPath.lengthOfBytes(using: .utf8)),
outputParamsPath,
UInt(outputParamsPath.lengthOfBytes(using: .utf8)),
networkType.networkId
networkType.networkId,
minimumConfirmations
)
}
@ -397,7 +450,6 @@ class ZcashRustBackend: ZcashRustBackendWelding {
dbData: URL,
account: Int32,
xprv: String,
extsk: String,
memo: String?,
spendParamsPath: String,
outputParamsPath: String,
@ -411,7 +463,6 @@ class ZcashRustBackend: ZcashRustBackendWelding {
dbData.1,
account,
[CChar](xprv.utf8CString),
[CChar](extsk.utf8CString),
[CChar](memoBytes.utf8CString),
spendParamsPath,
UInt(spendParamsPath.lengthOfBytes(using: .utf8)),
@ -421,14 +472,14 @@ class ZcashRustBackend: ZcashRustBackendWelding {
)
}
static func deriveExtendedFullViewingKey(_ spendingKey: String, networkType: NetworkType) throws -> String? {
guard !spendingKey.containsCStringNullBytesBeforeStringEnding() else {
static func deriveSaplingExtendedFullViewingKey(_ spendingKey: SaplingExtendedSpendingKey, networkType: NetworkType) throws -> SaplingExtendedFullViewingKey? {
guard !spendingKey.stringEncoded.containsCStringNullBytesBeforeStringEnding() else {
throw RustWeldingError.malformedStringInput
}
guard
let extsk = zcashlc_derive_extended_full_viewing_key(
[CChar](spendingKey.utf8CString),
let extfvk = zcashlc_derive_extended_full_viewing_key(
[CChar](spendingKey.stringEncoded.utf8CString),
networkType.networkId
)
else {
@ -437,21 +488,23 @@ class ZcashRustBackend: ZcashRustBackendWelding {
}
return nil
}
let derived = String(validatingUTF8: extsk)
zcashlc_string_free(extsk)
return derived
defer { zcashlc_string_free(extfvk) }
guard let derived = String(validatingUTF8: extfvk) else {
return nil
}
return SaplingExtendedFullViewingKey(validatedEncoding: derived)
}
static func deriveExtendedFullViewingKeys(seed: [UInt8], accounts: Int32, networkType: NetworkType) throws -> [String]? {
var capacity = UInt(0)
static func deriveSaplingExtendedFullViewingKeys(seed: [UInt8], accounts: Int32, networkType: NetworkType) throws -> [SaplingExtendedFullViewingKey]? {
guard
let extsksCStr = zcashlc_derive_extended_full_viewing_keys(
let ffiEncodedKeysPtr = zcashlc_derive_extended_full_viewing_keys(
seed,
UInt(seed.count),
accounts,
&capacity,
networkType.networkId
)
else {
@ -460,24 +513,27 @@ class ZcashRustBackend: ZcashRustBackendWelding {
}
return nil
}
defer {
zcashlc_free_keys(ffiEncodedKeysPtr)
}
let extsks = UnsafeBufferPointer(start: extsksCStr, count: Int(accounts)).compactMap { cStr -> String? in
guard let str = cStr else { return nil }
return String(cString: str)
let extfvks = UnsafeBufferPointer(
start: ffiEncodedKeysPtr.pointee.ptr,
count: Int(ffiEncodedKeysPtr.pointee.len)
).compactMap { encodedKey -> SaplingExtendedFullViewingKey in
SaplingExtendedFullViewingKey(validatedEncoding: String(cString: encodedKey.encoding))
}
zcashlc_vec_string_free(extsksCStr, UInt(accounts), capacity)
return extsks
return extfvks
}
static func deriveExtendedSpendingKeys(seed: [UInt8], accounts: Int32, networkType: NetworkType) throws -> [String]? {
var capacity = UInt(0)
static func deriveSaplingExtendedSpendingKeys(seed: [UInt8], accounts: Int32, networkType: NetworkType) throws -> [SaplingExtendedSpendingKey]? {
guard
let extsksCStr = zcashlc_derive_extended_spending_keys(
let ffiEncodedKeysPtr = zcashlc_derive_extended_spending_keys(
seed,
UInt(seed.count),
accounts,
&capacity,
networkType.networkId
)
else {
@ -486,13 +542,13 @@ class ZcashRustBackend: ZcashRustBackendWelding {
}
return nil
}
let extsks = UnsafeBufferPointer(start: extsksCStr, count: Int(accounts)).compactMap { cStr -> String? in
guard let str = cStr else { return nil }
return String(cString: str)
defer { zcashlc_free_keys(ffiEncodedKeysPtr) }
let extsks = UnsafeBufferPointer(start: ffiEncodedKeysPtr.pointee.ptr, count: Int(ffiEncodedKeysPtr.pointee.len)).compactMap {
SaplingExtendedSpendingKey(validatedEncoding: String(cString: $0.encoding))
}
zcashlc_vec_string_free(extsksCStr, UInt(accounts), capacity)
return extsks
}
@ -503,7 +559,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
networkType: NetworkType
) throws -> [UnifiedFullViewingKey] {
guard
let ufvksStruct = zcashlc_derive_unified_full_viewing_keys_from_seed(
let ffiEncodedKeysPtr = zcashlc_derive_unified_full_viewing_keys_from_seed(
seed,
UInt(seed.count),
Int32(numberOfAccounts),
@ -515,10 +571,12 @@ class ZcashRustBackend: ZcashRustBackendWelding {
}
throw RustWeldingError.unableToDeriveKeys
}
let ufvksSize = ufvksStruct.pointee.len
guard let ufvksArrayPointer = ufvksStruct.pointee.ptr, ufvksSize > 0 else {
defer { zcashlc_free_keys(ffiEncodedKeysPtr) }
let ufvksSize = ffiEncodedKeysPtr.pointee.len
guard let ufvksArrayPointer = ffiEncodedKeysPtr.pointee.ptr, ufvksSize > 0 else {
throw RustWeldingError.unableToDeriveKeys
}
@ -531,10 +589,9 @@ class ZcashRustBackend: ZcashRustBackendWelding {
throw RustWeldingError.unableToDeriveKeys
}
ufvks.append(UFVK(account: UInt32(item), encoding: encoding))
ufvks.append(UnifiedFullViewingKey(validatedEncoding: encoding, account: UInt32(item)))
}
zcashlc_free_uvk_array(ufvksStruct)
return ufvks
}
@ -698,7 +755,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
}
}
private struct UFVK: UnifiedFullViewingKey {
private struct UFVK {
var account: UInt32
var encoding: String
}
@ -728,3 +785,11 @@ extension String {
self.utf8CString.firstIndex(of: 0) != (self.utf8CString.count - 1)
}
}
fileprivate extension UnifiedFullViewingKey {
init(ufvk: UFVK) {
self.account = ufvk.account
self.encoding = ufvk.encoding
}
}

View File

@ -8,7 +8,7 @@
import Foundation
public enum RustWeldingError: Error {
enum RustWeldingError: Error {
case genericError(message: String)
case dataDbInitFailed(message: String)
case dataDbNotEmpty
@ -18,7 +18,7 @@ public enum RustWeldingError: Error {
case unableToDeriveKeys
}
public enum ZcashRustBackendWeldingConstants {
enum ZcashRustBackendWeldingConstants {
static let validChain: Int32 = -1
}
@ -31,7 +31,7 @@ public enum DbInitResult {
case seedRequired
}
public protocol ZcashRustBackendWelding {
protocol ZcashRustBackendWelding {
/**
gets the latest error if available. Clear the existing error
*/
@ -49,25 +49,36 @@ public protocol ZcashRustBackendWelding {
static func initDataDb(dbData: URL, seed: [UInt8]?, networkType: NetworkType) throws -> DbInitResult
/**
- Returns: true when the address is valid and shielded. Returns false in any other case
- Returns: true when the address is valid. Returns false in any other case
- Throws: Error when the provided address belongs to another network
*/
static func isValidShieldedAddress(_ address: String, networkType: NetworkType) throws -> Bool
static func isValidSaplingAddress(_ address: String, networkType: NetworkType) throws -> Bool
/**
- Returns: true when the address is valid and transparent. false in any other case
- Throws: Error when the provided address belongs to another network
*/
static func isValidTransparentAddress(_ address: String, networkType: NetworkType) throws -> Bool
/// validates whether a string encoded address is a valid Unified Address.
/// - Returns: true when the address is valid and transparent. false in any other case
/// - Throws: Error when the provided address belongs to another network
static func isValidUnifiedAddress(_ address: String, networkType: NetworkType) throws -> Bool
/**
- Returns: true when the address is valid and transparent. false in any other case
- Returns: `true` when the Sapling Extended Full Viewing Key is valid. `false` in any other case
- Throws: Error when there's another problem not related to validity of the string in question
*/
static func isValidExtendedFullViewingKey(_ key: String, networkType: NetworkType) throws -> Bool
static func isValidSaplingExtendedFullViewingKey(_ key: String, networkType: NetworkType) throws -> Bool
/// - Returns: `true` when the Sapling Extended Spending Key is valid, false in any other case.
/// - Throws: Error when the key is semantically valid but it belongs to another network
/// - parameter key: String encoded Extendeed Spending Key
/// - parameter networkType: `NetworkType` signaling testnet or mainnet
static func isValidSaplingExtendedSpendingKey(_ key: String, networkType: NetworkType) throws -> Bool
/**
- Returns: true when the address is valid and a UFVK. false in any other case
- Returns: true when the encoded string is a valid UFVK. false in any other case
- Throws: Error when there's another problem not related to validity of the string in question
*/
static func isValidUnifiedFullViewingKey(_ ufvk: String, networkType: NetworkType) throws -> Bool
@ -79,7 +90,7 @@ public protocol ZcashRustBackendWelding {
- seed: byte array of the zip32 seed
- accounts: how many accounts you want to have
*/
static func initAccountsTable(dbData: URL, seed: [UInt8], accounts: Int32, networkType: NetworkType) -> [String]?
static func initAccountsTable(dbData: URL, seed: [UInt8], accounts: Int32, networkType: NetworkType) -> [SaplingExtendedSpendingKey]?
/**
initialize the accounts table from a set of unified full viewing keys
@ -302,8 +313,7 @@ public protocol ZcashRustBackendWelding {
- dbCache: URL for the Cache DB
- dbData: URL for the Data DB
- account: the account index that will originate the transaction
- xprv: transparent account private key for the shielded funds.
- extsk: extended spending key string
- xprv: transparent account private key for the transparent funds that will be shielded.
- memo: the memo string for this transaction
- spendParamsPath: path escaped String for the filesystem locations where the spend parameters are located
- outputParamsPath: path escaped String for the filesystem locations where the output parameters are located
@ -314,7 +324,6 @@ public protocol ZcashRustBackendWelding {
dbData: URL,
account: Int32,
xprv: String,
extsk: String,
memo: String?,
spendParamsPath: String,
outputParamsPath: String,
@ -327,8 +336,8 @@ public protocol ZcashRustBackendWelding {
- Returns: the derived key
- Throws: RustBackendError if fatal error occurs
*/
static func deriveExtendedFullViewingKey(_ spendingKey: String, networkType: NetworkType) throws -> String?
static func deriveSaplingExtendedFullViewingKey(_ spendingKey: SaplingExtendedSpendingKey, networkType: NetworkType) throws -> SaplingExtendedFullViewingKey?
/**
Derives a set of full viewing keys from a seed
- Parameter spendingKey: a string containing the spending key
@ -336,22 +345,22 @@ public protocol ZcashRustBackendWelding {
- Returns: an array containing the derived keys
- Throws: RustBackendError if fatal error occurs
*/
static func deriveExtendedFullViewingKeys(seed: [UInt8], accounts: Int32, networkType: NetworkType) throws -> [String]?
static func deriveSaplingExtendedFullViewingKeys(seed: [UInt8], accounts: Int32, networkType: NetworkType) throws -> [SaplingExtendedFullViewingKey]?
/**
Derives a set of full viewing keys from a seed
Derives a set of Extended Spending Keys from a seed
- Parameter seed: a string containing the seed
- Parameter accounts: the number of accounts you want to derive from this seed
- Returns: an array containing the spending keys
- Throws: RustBackendError if fatal error occurs
*/
static func deriveExtendedSpendingKeys(seed: [UInt8], accounts: Int32, networkType: NetworkType) throws -> [String]?
static func deriveSaplingExtendedSpendingKeys(seed: [UInt8], accounts: Int32, networkType: NetworkType) throws -> [SaplingExtendedSpendingKey]?
/**
Derives a unified address from a seed
- Parameter seed: an array of bytes of the seed
- Parameter accountIndex: the index of the account you want the address for
- Returns: an optional String containing the Shielded address
- Returns: an optional String containing Unified Address
- Throws: RustBackendError if fatal error occurs
*/
static func deriveUnifiedAddressFromSeed(seed: [UInt8], accountIndex: Int32, networkType: NetworkType) throws -> String?
@ -363,13 +372,14 @@ public protocol ZcashRustBackendWelding {
- Throws: RustBackendError if fatal error occurs
*/
static func deriveUnifiedAddressFromViewingKey(_ ufvk: String, networkType: NetworkType) throws -> String?
/**
Derives a shielded address from an Extended Full Viewing Key
- Parameter seed: an array of bytes of the seed
- Returns: an optional String containing the transparent address
- Throws: RustBackendError if fatal error occurs
*/
/// Derives a transparent address from seed bytes
/// - Parameter seed: an array of bytes of the seed
/// - Parameter account: account number
/// - Parameter index: diversifier index
/// - Returns: an optional String containing the transparent address
/// - Throws: RustBackendError if fatal error occurs
static func deriveTransparentAddressFromSeed(seed: [UInt8], account: Int, index: Int, networkType: NetworkType) throws -> String?
/**

View File

@ -95,7 +95,7 @@ public protocol Synchronizer {
/// Gets the sapling shielded address for the given account.
/// - Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used.
/// - Returns the address or nil if account index is incorrect
func getShieldedAddress(accountIndex: Int) -> SaplingShieldedAddress?
func getSaplingAddress(accountIndex: Int) -> SaplingAddress?
/// Gets the unified address for the given account.
@ -134,25 +134,23 @@ public protocol Synchronizer {
/// - Parameter accountIndex: the optional account id to use. By default, the first account is used.
// swiftlint:disable:next function_parameter_count
func sendToAddress(
spendingKey: String,
spendingKey: SaplingExtendedSpendingKey,
zatoshi: Zatoshi,
toAddress: String,
toAddress: Recipient,
memo: String?,
from accountIndex: Int,
resultBlock: @escaping (_ result: Result<PendingTransactionEntity, Error>) -> Void
)
/// Sends zatoshi.
/// - Parameter spendingKey: the key that allows spends to occur.
/// Shields transparent funds from the given private key into the best shielded pool of the given account.
/// - Parameter transparentAccountPrivateKey: the key that allows to spend transparent funds
/// - Parameter memo: the optional memo to include as part of the transaction.
/// - Parameter accountIndex: the optional account id that will be used to shield your funds to. By default, the first account is used.
func shieldFunds(
spendingKey: String,
transparentAccountPrivateKey: String,
transparentAccountPrivateKey: TransparentAccountPrivKey,
memo: String?,
from accountIndex: Int,
resultBlock: @escaping (_ result: Result<PendingTransactionEntity, Error>) -> Void
resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void
)
/// Attempts to cancel a transaction that is about to be sent. Typically, cancellation is only
@ -160,7 +158,6 @@ public protocol Synchronizer {
/// - Parameter transaction: the transaction to cancel.
/// - Returns: true when the cancellation request was successful. False when it is too late.
func cancelSpend(transaction: PendingTransactionEntity) -> Bool
/// all outbound pending transactions that have been sent but are awaiting confirmations
var pendingTransactions: [PendingTransactionEntity] { get }
@ -174,7 +171,6 @@ public protocol Synchronizer {
/// all transactions related to receiving funds
var receivedTransactions: [ConfirmedTransactionEntity] { get }
/// A repository serving transactions in a paginated manner
/// - Parameter kind: Transaction Kind expected from this PaginatedTransactionRepository
func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository
@ -203,7 +199,7 @@ public protocol Synchronizer {
/// Returns the latests UTXOs for the given address from the specified height on
func refreshUTXOs(address: String, from height: BlockHeight, result: @escaping (Result<RefreshedUTXOs, Error>) -> Void)
/// Returns the last stored unshielded balance
/// Returns the last stored transparent balance
func getTransparentBalance(accountIndex: Int) throws -> WalletBalance

View File

@ -74,9 +74,10 @@ public extension Notification.Name {
static let synchronizerConnectionStateChanged = Notification.Name("SynchronizerConnectionStateChanged")
}
/// Synchronizer implementation for UIKit and iOS 12+
/// Synchronizer implementation for UIKit and iOS 13+
// swiftlint:disable type_body_length
public class SDKSynchronizer: Synchronizer {
public struct SynchronizerState {
public var shieldedBalance: WalletBalance
public var transparentBalance: WalletBalance
@ -466,21 +467,25 @@ public class SDKSynchronizer: Synchronizer {
from accountIndex: Int,
resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void
) {
sendToAddress(
spendingKey: spendingKey,
zatoshi: Zatoshi(zatoshi),
toAddress: toAddress,
memo: memo,
from: accountIndex,
resultBlock: resultBlock
)
do {
sendToAddress(
spendingKey: try SaplingExtendedSpendingKey(encoding: spendingKey, network: network.networkType),
zatoshi: Zatoshi(zatoshi),
toAddress: try Recipient(toAddress, network: network.networkType),
memo: memo,
from: accountIndex,
resultBlock: resultBlock
)
} catch {
resultBlock(.failure(SynchronizerError.invalidAccount))
}
}
// swiftlint:disable:next function_parameter_count
public func sendToAddress(
spendingKey: String,
spendingKey: SaplingExtendedSpendingKey,
zatoshi: Zatoshi,
toAddress: String,
toAddress: Recipient,
memo: String?,
from accountIndex: Int,
resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void
@ -492,7 +497,7 @@ public class SDKSynchronizer: Synchronizer {
self?.createToAddress(
spendingKey: spendingKey,
zatoshi: zatoshi,
toAddress: toAddress,
toAddress: toAddress.stringEncoded,
memo: memo,
from: accountIndex,
resultBlock: resultBlock
@ -505,8 +510,7 @@ public class SDKSynchronizer: Synchronizer {
}
public func shieldFunds(
spendingKey: String,
transparentAccountPrivateKey: String,
transparentAccountPrivateKey: TransparentAccountPrivKey,
memo: String?,
from accountIndex: Int,
resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void
@ -516,20 +520,20 @@ public class SDKSynchronizer: Synchronizer {
do {
let tAddr = try derivationTool.deriveTransparentAddressFromAccountPrivateKey(transparentAccountPrivateKey, index: 0) // TODO: FIX
let tBalance = try utxoRepository.balance(address: tAddr, latestHeight: self.latestDownloadedHeight())
let tBalance = try utxoRepository.balance(address: tAddr.stringEncoded, latestHeight: self.latestDownloadedHeight())
// Verify that at least there are funds for the fee. Ideally this logic will be improved by the shielding wallet.
// Verify that at least there are funds for the fee. Ideally this logic will be improved by the shielding wallet.
guard tBalance.verified >= self.network.constants.defaultFee(for: self.latestScannedHeight) else {
resultBlock(.failure(ShieldFundsError.insuficientTransparentFunds))
return
}
let viewingKey = try derivationTool.deriveViewingKey(spendingKey: spendingKey)
let uAddr = try derivationTool.deriveUnifiedAddress(viewingKey: viewingKey)
// TODO: this should be a UA
let zAddr = "fasdfasdfa"
let shieldingSpend = try transactionManager.initSpend(zatoshi: tBalance.verified, toAddress: uAddr, memo: memo, from: 0)
let shieldingSpend = try transactionManager.initSpend(zatoshi: tBalance.verified, toAddress: zAddr, memo: memo, from: 0)
transactionManager.encodeShieldingTransaction(
spendingKey: spendingKey,
xprv: transparentAccountPrivateKey,
pendingTransaction: shieldingSpend
) { [weak self] result in
@ -560,7 +564,7 @@ public class SDKSynchronizer: Synchronizer {
// swiftlint:disable:next function_parameter_count
func createToAddress(
spendingKey: String,
spendingKey: SaplingExtendedSpendingKey,
zatoshi: Zatoshi,
toAddress: String,
memo: String?,
@ -683,8 +687,8 @@ public class SDKSynchronizer: Synchronizer {
initializer.getVerifiedBalance(account: accountIndex)
}
public func getShieldedAddress(accountIndex: Int) -> SaplingShieldedAddress? {
blockProcessor.getShieldedAddress(accountIndex: accountIndex)
public func getSaplingAddress(accountIndex: Int) -> SaplingAddress? {
blockProcessor.getSaplingAddress(accountIndex: accountIndex)
}
public func getUnifiedAddress(accountIndex: Int) -> UnifiedAddress? {
@ -700,7 +704,7 @@ public class SDKSynchronizer: Synchronizer {
}
/**
Returns the last stored unshielded balance
Returns the last stored transparent balance
*/
public func getTransparentBalance(address: String) throws -> WalletBalance {
do {

View File

@ -12,7 +12,11 @@ public protocol KeyValidation {
func isValidTransparentAddress(_ tAddress: String) throws -> Bool
func isValidShieldedAddress(_ zAddress: String) throws -> Bool
func isValidSaplingAddress(_ zAddress: String) throws -> Bool
func isValidSaplingExtendedSpendingKey(_ extsk: String) throws -> Bool
func isValidUnifiedAddress(_ unifiedAddress: String) throws -> Bool
}
public protocol KeyDeriving {
@ -25,7 +29,7 @@ public protocol KeyDeriving {
- Returns: the viewing keys that correspond to the seed, formatted as Strings.
*/
func deriveViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [String]
func deriveUnifiedFullViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [UnifiedFullViewingKey]
/**
Given a spending key, return the associated viewing key.
@ -34,7 +38,7 @@ public protocol KeyDeriving {
- Returns: the viewing key that corresponds to the spending key.
*/
func deriveViewingKey(spendingKey: String) throws -> String
func deriveViewingKey(spendingKey: SaplingExtendedSpendingKey) throws -> SaplingExtendedFullViewingKey
/**
Given a seed and a number of accounts, return the associated spending keys.
@ -45,7 +49,7 @@ public protocol KeyDeriving {
- Returns: the spending keys that correspond to the seed, formatted as Strings.
*/
func deriveSpendingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [String]
func deriveSpendingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [SaplingExtendedSpendingKey]
/**
Given a seed and account index, return the associated unified address.
@ -56,45 +60,31 @@ public protocol KeyDeriving {
- Returns: the address that corresponds to the seed and account index.
*/
func deriveUnifiedAddress(seed: [UInt8], accountIndex: Int) throws -> String
func deriveUnifiedAddress(seed: [UInt8], accountIndex: Int) throws -> UnifiedAddress
/**
Given a unified viewing key string, return the associated unified address.
- Parameter viewingKey: the viewing key to use for deriving the address. The viewing key is tied to
a specific account so no account index is required.
- Returns: the address that corresponds to the viewing key.
*/
func deriveUnifiedAddress(viewingKey: String) throws -> String
/// Given a unified full viewing key string, return the associated unified address.
///
/// - Parameter ufvk: the viewing key to use for deriving the address. The viewing key is tied to
/// a specific account so no account index is required.
///
/// - Returns: the address that corresponds to the viewing key.
func deriveUnifiedAddress(from ufvk: UnifiedFullViewingKey) throws -> UnifiedAddress
/**
Derives a transparent address from seedbytes, specifying account and index
*/
func deriveTransparentAddress(seed: [UInt8], account: Int, index: Int) throws -> String
func deriveTransparentAddress(seed: [UInt8], account: Int, index: Int) throws -> TransparentAddress
/**
Derives the account private key to spend transparent funds from a specific seed and account
*/
func deriveTransparentAccountPrivateKey(seed: [UInt8], account: Int) throws -> String
func deriveTransparentAccountPrivateKey(seed: [UInt8], account: Int) throws -> TransparentAccountPrivKey
/**
Derives a transparent address from the given transparent account private key
*/
func deriveTransparentAddressFromAccountPrivateKey(_ xprv: String, index: Int) throws -> String
func deriveTransparentAddressFromPublicKey(_ pubkey: String) throws -> String
/**
derives unified full viewing keys from seedbytes, specifying a number of accounts
- Returns an array of unified viewing key tuples.
*/
func deriveUnifiedFullViewingKeysFromSeed(_ seed: [UInt8], numberOfAccounts: Int) throws -> [UnifiedFullViewingKey]
/**
derives a Unified Address from a Unified Full Viewing Key
*/
func deriveUnifiedAddressFromUnifiedFullViewingKey(_ ufvk: UnifiedFullViewingKey) throws -> UnifiedAddress
func deriveTransparentAddressFromAccountPrivateKey(_ xprv: TransparentAccountPrivKey, index: Int) throws -> TransparentAddress
}
public enum KeyDerivationErrors: Error {
@ -104,6 +94,7 @@ public enum KeyDerivationErrors: Error {
}
public class DerivationTool: KeyDeriving {
var rustwelding: ZcashRustBackendWelding.Type = ZcashRustBackend.self
var networkType: NetworkType
@ -121,7 +112,7 @@ public class DerivationTool: KeyDeriving {
- Returns: the viewing keys that correspond to the seed, formatted as Strings.
*/
public func deriveViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [String] {
public func deriveUnifiedFullViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [UnifiedFullViewingKey] {
guard numberOfAccounts > 0, let numberOfAccounts = Int32(exactly: numberOfAccounts) else {
throw KeyDerivationErrors.invalidInput
}
@ -129,16 +120,16 @@ public class DerivationTool: KeyDeriving {
do {
let ufvks = try rustwelding.deriveUnifiedFullViewingKeyFromSeed(seed, numberOfAccounts: numberOfAccounts, networkType: networkType)
var keys: [String] = []
var keys: [UnifiedFullViewingKey] = []
for ufvk in ufvks {
keys.append(ufvk.encoding)
keys.append(ufvk)
}
return keys
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/**
Given a spending key, return the associated viewing key.
@ -146,9 +137,9 @@ public class DerivationTool: KeyDeriving {
- Returns: the viewing key that corresponds to the spending key.
*/
public func deriveViewingKey(spendingKey: String) throws -> String {
public func deriveViewingKey(spendingKey: SaplingExtendedSpendingKey) throws -> SaplingExtendedFullViewingKey {
do {
guard let key = try rustwelding.deriveExtendedFullViewingKey(spendingKey, networkType: networkType) else {
guard let key = try rustwelding.deriveSaplingExtendedFullViewingKey(spendingKey, networkType: networkType) else {
throw KeyDerivationErrors.unableToDerive
}
return key
@ -156,6 +147,17 @@ public class DerivationTool: KeyDeriving {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
public func deriveUnifiedFullViewingKeysFromSeed(_ seed: [UInt8], numberOfAccounts: Int) throws -> [UnifiedFullViewingKey] {
guard numberOfAccounts > 0, let numberOfAccounts = Int32(exactly: numberOfAccounts) else {
throw KeyDerivationErrors.invalidInput
}
do {
return try rustwelding.deriveUnifiedFullViewingKeyFromSeed(seed, numberOfAccounts: numberOfAccounts, networkType: networkType)
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/**
Given a seed and a number of accounts, return the associated spending keys.
@ -166,12 +168,12 @@ public class DerivationTool: KeyDeriving {
- Returns: the spending keys that correspond to the seed, formatted as Strings.
*/
public func deriveSpendingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [String] {
public func deriveSpendingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [SaplingExtendedSpendingKey] {
guard numberOfAccounts > 0, let numberOfAccounts = Int32(exactly: numberOfAccounts) else {
throw KeyDerivationErrors.invalidInput
}
do {
guard let keys = try rustwelding.deriveExtendedSpendingKeys(seed: seed, accounts: numberOfAccounts, networkType: networkType) else {
guard let keys = try rustwelding.deriveSaplingExtendedSpendingKeys(seed: seed, accounts: numberOfAccounts, networkType: networkType) else {
throw KeyDerivationErrors.unableToDerive
}
return keys
@ -189,7 +191,7 @@ public class DerivationTool: KeyDeriving {
- Returns: the address that corresponds to the seed and account index.
*/
public func deriveUnifiedAddress(seed: [UInt8], accountIndex: Int) throws -> String {
public func deriveUnifiedAddress(seed: [UInt8], accountIndex: Int) throws -> UnifiedAddress {
guard accountIndex >= 0, let accountIndex = Int32(exactly: accountIndex) else {
throw KeyDerivationErrors.invalidInput
}
@ -198,7 +200,7 @@ public class DerivationTool: KeyDeriving {
guard let address = try rustwelding.deriveUnifiedAddressFromSeed(seed: seed, accountIndex: accountIndex, networkType: networkType) else {
throw KeyDerivationErrors.unableToDerive
}
return address
return UnifiedAddress(validatedEncoding: address)
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
@ -212,20 +214,20 @@ public class DerivationTool: KeyDeriving {
- Returns: the address that corresponds to the viewing key.
*/
public func deriveUnifiedAddress(viewingKey: String) throws -> String {
public func deriveUnifiedAddress(from ufvk: UnifiedFullViewingKey) throws -> UnifiedAddress {
do {
guard let zaddr = try rustwelding.deriveUnifiedAddressFromViewingKey(viewingKey, networkType: networkType) else {
guard let stringEncodedUA = try rustwelding.deriveUnifiedAddressFromViewingKey(ufvk.stringEncoded, networkType: networkType) else {
throw KeyDerivationErrors.unableToDerive
}
return zaddr
return UnifiedAddress(validatedEncoding: stringEncodedUA)
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
public func deriveTransparentAddress(seed: [UInt8], account: Int = 0, index: Int = 0) throws -> String {
public func deriveTransparentAddress(seed: [UInt8], account: Int = 0, index: Int = 0) throws -> TransparentAddress {
do {
guard let zaddr = try rustwelding.deriveTransparentAddressFromSeed(
guard let taddr = try rustwelding.deriveTransparentAddressFromSeed(
seed: seed,
account: account,
index: index,
@ -233,54 +235,27 @@ public class DerivationTool: KeyDeriving {
) else {
throw KeyDerivationErrors.unableToDerive
}
return zaddr
return TransparentAddress(validatedEncoding: taddr)
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
public func deriveUnifiedFullViewingKeysFromSeed(_ seed: [UInt8], numberOfAccounts: Int) throws -> [UnifiedFullViewingKey] {
guard numberOfAccounts > 0, let numberOfAccounts = Int32(exactly: numberOfAccounts) else {
throw KeyDerivationErrors.invalidInput
}
do {
return try rustwelding.deriveUnifiedFullViewingKeyFromSeed(seed, numberOfAccounts: numberOfAccounts, networkType: networkType)
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/**
derives a Unified Address from a Unified Full Viewing Key
*/
/// derives a Unified Address from a Unified Full Viewing Key
public func deriveUnifiedAddressFromUnifiedFullViewingKey(_ ufvk: UnifiedFullViewingKey) throws -> UnifiedAddress {
do {
let encoding = try deriveUnifiedAddress(viewingKey: ufvk.encoding)
return ConcreteUnifiedAddress(encoding: encoding)
return try deriveUnifiedAddress(from: ufvk)
} catch {
throw KeyDerivationErrors.unableToDerive
}
}
public func deriveTransparentAddressFromPublicKey(_ pubkey: String) throws -> String {
guard !pubkey.isEmpty else {
throw KeyDerivationErrors.invalidInput
}
do {
return try rustwelding.derivedTransparentAddressFromPublicKey(pubkey, networkType: networkType)
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/**
Derives the transparent funds account private key from the given seed
- Throws:
- KeyDerivationErrors.derivationError with the underlying error when it fails
- KeyDerivationErrors.unableToDerive when there's an unknown error
*/
public func deriveTransparentAccountPrivateKey(seed: [UInt8], account: Int = 0) throws -> String {
/// Derives the transparent funds account private key from the given seed
/// - Throws:
/// - KeyDerivationErrors.derivationError with the underlying error when it fails
/// - KeyDerivationErrors.unableToDerive when there's an unknown error
public func deriveTransparentAccountPrivateKey(seed: [UInt8], account: Int = 0) throws -> TransparentAccountPrivKey {
do {
guard let seedKey = try rustwelding.deriveTransparentAccountPrivateKeyFromSeed(
seed: seed,
@ -289,7 +264,23 @@ public class DerivationTool: KeyDeriving {
) else {
throw KeyDerivationErrors.unableToDerive
}
return seedKey
return TransparentAccountPrivKey(encoding: seedKey)
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/// Derives the transparent address from an account private key
/// - Throws:
/// - KeyDerivationErrors.derivationError with the underlying error when it fails
/// - KeyDerivationErrors.unableToDerive when there's an unknown error
public func deriveTransparentAddressFromAccountPrivateKey(_ xprv: TransparentAccountPrivKey, index: Int) throws -> TransparentAddress {
do {
guard let tAddr = try rustwelding.deriveTransparentAddressFromAccountPrivateKey(xprv.encoding, index: index, networkType: networkType) else {
throw KeyDerivationErrors.unableToDerive
}
return TransparentAddress(validatedEncoding: tAddr)
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
@ -297,9 +288,17 @@ public class DerivationTool: KeyDeriving {
}
extension DerivationTool: KeyValidation {
public func isValidUnifiedAddress(_ unifiedAddress: String) throws -> Bool {
do {
return try rustwelding.isValidUnifiedAddress(unifiedAddress, networkType: networkType)
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
public func isValidExtendedViewingKey(_ extvk: String) throws -> Bool {
do {
return try rustwelding.isValidExtendedFullViewingKey(extvk, networkType: networkType)
return try rustwelding.isValidSaplingExtendedFullViewingKey(extvk, networkType: networkType)
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
@ -313,32 +312,83 @@ extension DerivationTool: KeyValidation {
}
}
public func isValidShieldedAddress(_ zAddress: String) throws -> Bool {
public func isValidSaplingAddress(_ zAddress: String) throws -> Bool {
do {
return try rustwelding.isValidShieldedAddress(zAddress, networkType: networkType)
return try rustwelding.isValidSaplingAddress(zAddress, networkType: networkType)
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/**
Derives the transparent address from an account private key
- Throws:
- KeyDerivationErrors.derivationError with the underlying error when it fails
- KeyDerivationErrors.unableToDerive when there's an unknown error
*/
public func deriveTransparentAddressFromAccountPrivateKey(_ xprv: String, index: Int) throws -> String {
public func isValidSaplingExtendedSpendingKey(_ extsk: String) throws -> Bool {
do {
guard let tAddr = try rustwelding.deriveTransparentAddressFromAccountPrivateKey(xprv, index: index, networkType: networkType) else {
throw KeyDerivationErrors.unableToDerive
}
return tAddr
return try rustwelding.isValidSaplingExtendedSpendingKey(extsk, networkType: networkType)
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
}
private struct ConcreteUnifiedAddress: UnifiedAddress {
var encoding: String
extension TransparentAddress {
/// This constructor is for internal use for Strings encodings that are assumed to be
/// already validated by another function. only for internal use. Unless you are
/// constructing an address from a primitive function of the FFI, you probably
/// shouldn't be using this.
init(validatedEncoding: String) {
self.encoding = validatedEncoding
}
}
extension SaplingAddress {
/// This constructor is for internal use for Strings encodings that are assumed to be
/// already validated by another function. only for internal use. Unless you are
/// constructing an address from a primitive function of the FFI, you probably
/// shouldn't be using this.
init(validatedEncoding: String) {
self.encoding = validatedEncoding
}
}
extension UnifiedAddress {
/// This constructor is for internal use for Strings encodings that are assumed to be
/// already validated by another function. only for internal use. Unless you are
/// constructing an address from a primitive function of the FFI, you probably
/// shouldn't be using this..
init(validatedEncoding: String) {
self.encoding = validatedEncoding
}
}
extension UnifiedFullViewingKey {
/// This constructor is for internal use for Strings encodings that are assumed to be
/// already validated by another function. only for internal use. Unless you are
/// constructing an address from a primitive function of the FFI, you probably
/// shouldn't be using this.
init(validatedEncoding: String, account: UInt32) {
self.encoding = validatedEncoding
self.account = account
}
}
extension SaplingExtendedFullViewingKey {
/// This constructor is for internal use for Strings encodings that are assumed to be
/// already validated by another function. only for internal use. Unless you are
/// constructing an address from a primitive function of the FFI, you probably
/// shouldn't be using this.
init(validatedEncoding: String) {
self.encoding = validatedEncoding
}
}
extension SaplingExtendedSpendingKey {
/// This constructor is for internal use for Strings encodings that are assumed to be
/// already validated by another function. only for internal use. Unless you are
/// constructing an address from a primitive function of the FFI, you probably
/// shouldn't be using this.
init(validatedEncoding: String) {
self.encoding = validatedEncoding
}
}

View File

@ -65,52 +65,15 @@ class PersistentTransactionManager: OutboundTransactionManager {
}
func encodeShieldingTransaction(
spendingKey: String,
xprv: String,
xprv: TransparentAccountPrivKey,
pendingTransaction: PendingTransactionEntity,
result: @escaping (Result<PendingTransactionEntity, Error>) -> Void
) {
queue.async { [weak self] in
guard let self = self else { return }
let derivationTool = DerivationTool(networkType: self.network)
guard
let viewingKey = try? derivationTool.deriveViewingKey(spendingKey: spendingKey),
let uAddr = try? derivationTool.deriveUnifiedAddress(viewingKey: viewingKey)
else {
result(
.failure(
TransactionManagerError.shieldingEncodingFailed(
pendingTransaction,
reason: "There was an error Deriving your keys"
)
)
)
return
}
guard pendingTransaction.toAddress == uAddr else {
result(
.failure(
TransactionManagerError.shieldingEncodingFailed(
pendingTransaction,
reason: """
the recipient address does not match your
derived shielded address. Shielding transactions
addresses must match the ones derived from your keys.
This is a serious error. We are not letting you encode
this shielding transaction because it can lead to loss
of funds
"""
)
)
)
return
}
do {
let encodedTransaction = try self.encoder.createShieldingTransaction(
spendingKey: spendingKey,
tAccountPrivateKey: xprv,
memo: pendingTransaction.memo?.asZcashTransactionMemo(),
from: pendingTransaction.accountIndex
@ -140,7 +103,7 @@ class PersistentTransactionManager: OutboundTransactionManager {
}
func encode(
spendingKey: String,
spendingKey: SaplingExtendedSpendingKey,
pendingTransaction: PendingTransactionEntity,
result: @escaping (Result<PendingTransactionEntity, Error>) -> Void
) {
@ -149,7 +112,7 @@ class PersistentTransactionManager: OutboundTransactionManager {
do {
let encodedTransaction = try self.encoder.createTransaction(
spendingKey: spendingKey,
zatoshi: pendingTransaction.intValue,
zatoshi: pendingTransaction.value,
to: pendingTransaction.toAddress,
memo: pendingTransaction.memo?.asZcashTransactionMemo(),
from: pendingTransaction.accountIndex
@ -266,10 +229,6 @@ class PersistentTransactionManager: OutboundTransactionManager {
.forEach { try self.repository.update($0) }
}
func monitorChanges(byId: Int, observer: Any) {
// TODO: Implement this
}
func cancel(pendingTransaction: PendingTransactionEntity) -> Bool {
guard let id = pendingTransaction.id else { return false }

View File

@ -18,47 +18,45 @@ public enum TransactionEncoderError: Error {
}
protocol TransactionEncoder {
/**
Creates a transaction, throwing an exception whenever things are missing. When the provided wallet implementation
doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using
double-bangs for things).
Blocking
- Parameters:
- Parameter spendingKey: a string containing the spending key
- Parameter zatoshi: the amount to send in zatoshis
- Parameter to: string containing the recipient address
- Parameter memo: string containing the memo (optional)
- Parameter accountIndex: index of the account that will be used to send the funds
- Throws: a TransactionEncoderError
*/
/// Creates a transaction, throwing an exception whenever things are missing. When the provided wallet implementation
/// doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using
/// double-bangs for things).
/// Blocking
///
/// - Parameters:
/// - Parameter spendingKey: a `SaplingExtendedSpendingKey` containing the spending key
/// - Parameter zatoshi: the amount to send in `Zatoshi`
/// - Parameter to: string containing the recipient address
/// - Parameter memo: string containing the memo (optional)
/// - Parameter accountIndex: index of the account that will be used to send the funds
///
/// - Throws: a TransactionEncoderError
func createTransaction(
spendingKey: String,
zatoshi: Int,
spendingKey: SaplingExtendedSpendingKey,
zatoshi: Zatoshi,
to address: String,
memo: String?,
from accountIndex: Int
) throws -> EncodedTransaction
/**
Creates a transaction, throwing an exception whenever things are missing. When the provided wallet implementation
doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using
double-bangs for things).
Non-blocking
- Parameters:
- Parameter spendingKey: a string containing the spending key
- Parameter zatoshi: the amount to send in zatoshis
- Parameter to: string containing the recipient address
- Parameter memo: string containing the memo (optional)
- Parameter accountIndex: index of the account that will be used to send the funds
- Parameter result: a non escaping closure that receives a Result containing either an EncodedTransaction or a TransactionEncoderError
*/
/// Creates a transaction, throwing an exception whenever things are missing. When the provided wallet implementation
/// doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using
/// double-bangs for things).
/// Non-blocking
///
/// - Parameters:
/// - Parameter spendingKey: a `SaplingExtendedSpendingKey` containing the spending key
/// - Parameter zatoshi: the amount to send in `Zatoshi`
/// - Parameter to: string containing the recipient address
/// - Parameter memo: string containing the memo (optional)
/// - Parameter accountIndex: index of the account that will be used to send the funds
/// - Parameter result: a non escaping closure that receives a Result containing either an EncodedTransaction or a /// TransactionEncoderError
// swiftlint:disable:next function_parameter_count
func createTransaction(
spendingKey: String,
zatoshi: Int,
spendingKey: SaplingExtendedSpendingKey,
zatoshi: Zatoshi,
to address: String,
memo: String?,
from accountIndex: Int,
@ -70,7 +68,6 @@ protocol TransactionEncoder {
Blocking
- Parameters:
- Parameter spendingKey: a string containing the spending key
- Parameter tAccountPrivateKey: transparent account private key to spend the UTXOs
- Parameter memo: string containing the memo (optional)
- Parameter accountIndex: index of the account that will be used to send the funds
@ -78,34 +75,11 @@ protocol TransactionEncoder {
- Throws: a TransactionEncoderError
*/
func createShieldingTransaction(
spendingKey: String,
tAccountPrivateKey: String,
tAccountPrivateKey: TransparentAccountPrivKey,
memo: String?,
from accountIndex: Int
) throws -> EncodedTransaction
/**
Creates a transaction that will attempt to shield transparent funds that are present on the cacheDB .throwing an exception whenever things are missing. When the provided wallet implementation doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using double-bangs for things).
Non-Blocking
- Parameters:
- Parameter spendingKey: a string containing the spending key
- Parameter tAccountPrivateKey: transparent account private key to spend the UTXOs
- Parameter memo: string containing the memo (optional)
- Parameter accountIndex: index of the account that will be used to send the funds
- Returns: a TransactionEncoderResultBlock
*/
func createShieldingTransaction(
spendingKey: String,
tAccountPrivateKey: String,
memo: String?,
from accountIndex: Int,
result: @escaping TransactionEncoderResultBlock
)
/**
Fetch the Transaction Entity from the encoded representation
- Parameter encodedTransaction: The encoded transaction to expand

View File

@ -14,17 +14,34 @@ transactions through to completion.
*/
protocol OutboundTransactionManager {
func initSpend(zatoshi: Zatoshi, toAddress: String, memo: String?, from accountIndex: Int) throws -> PendingTransactionEntity
func initSpend(
zatoshi: Zatoshi,
toAddress: String,
memo: String?,
from accountIndex: Int
) throws -> PendingTransactionEntity
func encodeShieldingTransaction(spendingKey: String, xprv: String, pendingTransaction: PendingTransactionEntity, result: @escaping (Result<PendingTransactionEntity, Error>) -> Void)
func encodeShieldingTransaction(
xprv: TransparentAccountPrivKey,
pendingTransaction: PendingTransactionEntity,
result: @escaping (Result<PendingTransactionEntity, Error>) -> Void
)
func encode(spendingKey: String, pendingTransaction: PendingTransactionEntity, result: @escaping (Result<PendingTransactionEntity, Error>) -> Void)
func encode(
spendingKey: SaplingExtendedSpendingKey,
pendingTransaction: PendingTransactionEntity,
result: @escaping (Result<PendingTransactionEntity, Error>
) -> Void)
func submit(pendingTransaction: PendingTransactionEntity, result: @escaping (Result<PendingTransactionEntity, Error>) -> Void)
func submit(
pendingTransaction: PendingTransactionEntity,
result: @escaping (Result<PendingTransactionEntity, Error>) -> Void
)
func applyMinedHeight(pendingTransaction: PendingTransactionEntity, minedHeight: BlockHeight) throws -> PendingTransactionEntity
func monitorChanges(byId: Int, observer: Any) // check this out. code smell
func applyMinedHeight(
pendingTransaction: PendingTransactionEntity,
minedHeight: BlockHeight
) throws -> PendingTransactionEntity
/**
Attempts to Cancel a transaction. Returns true if successful

View File

@ -50,8 +50,8 @@ class WalletTransactionEncoder: TransactionEncoder {
}
func createTransaction(
spendingKey: String,
zatoshi: Int,
spendingKey: SaplingExtendedSpendingKey,
zatoshi: Zatoshi,
to address: String,
memo: String?,
from accountIndex: Int
@ -80,8 +80,8 @@ class WalletTransactionEncoder: TransactionEncoder {
// swiftlint:disable:next function_parameter_count
func createTransaction(
spendingKey: String,
zatoshi: Int,
spendingKey: SaplingExtendedSpendingKey,
zatoshi: Zatoshi,
to address: String,
memo: String?,
from accountIndex: Int,
@ -107,7 +107,13 @@ class WalletTransactionEncoder: TransactionEncoder {
}
}
func createSpend(spendingKey: String, zatoshi: Int, to address: String, memo: String?, from accountIndex: Int) throws -> Int {
func createSpend(
spendingKey: SaplingExtendedSpendingKey,
zatoshi: Zatoshi,
to address: String,
memo: String?,
from accountIndex: Int
) throws -> Int {
guard ensureParams(spend: self.spendParamsURL, output: self.spendParamsURL) else {
throw TransactionEncoderError.missingParams
}
@ -115,9 +121,9 @@ class WalletTransactionEncoder: TransactionEncoder {
let txId = rustBackend.createToAddress(
dbData: self.dataDbURL,
account: Int32(accountIndex),
extsk: spendingKey,
extsk: spendingKey.stringEncoded,
to: address,
value: Int64(zatoshi),
value: zatoshi.amount,
memo: memo,
spendParamsPath: self.spendParamsURL.path,
outputParamsPath: self.outputParamsURL.path,
@ -132,14 +138,12 @@ class WalletTransactionEncoder: TransactionEncoder {
}
func createShieldingTransaction(
spendingKey: String,
tAccountPrivateKey: String,
tAccountPrivateKey: TransparentAccountPrivKey,
memo: String?,
from accountIndex: Int
) throws -> EncodedTransaction {
let txId = try createShieldingSpend(
spendingKey: spendingKey,
xprv: tAccountPrivateKey,
xprv: tAccountPrivateKey.encoding,
memo: memo,
accountIndex: accountIndex
)
@ -158,19 +162,7 @@ class WalletTransactionEncoder: TransactionEncoder {
}
}
func createShieldingTransaction(
spendingKey: String,
tAccountPrivateKey: String,
memo: String?,
from accountIndex: Int,
result: @escaping TransactionEncoderResultBlock
) {
queue.async {
result(.failure(RustWeldingError.genericError(message: "not implemented")))
}
}
func createShieldingSpend(spendingKey: String, xprv: String, memo: String?, accountIndex: Int) throws -> Int {
func createShieldingSpend(xprv: String, memo: String?, accountIndex: Int) throws -> Int {
guard ensureParams(spend: self.spendParamsURL, output: self.spendParamsURL) else {
throw TransactionEncoderError.missingParams
}
@ -180,7 +172,6 @@ class WalletTransactionEncoder: TransactionEncoder {
dbData: self.dataDbURL,
account: Int32(accountIndex),
xprv: xprv,
extsk: spendingKey,
memo: memo,
spendParamsPath: self.spendParamsURL.path,
outputParamsPath: self.outputParamsURL.path,

View File

@ -302,7 +302,7 @@ class AdvancedReOrgTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: coordinator.spendingKeys!.first!,
zatoshi: sendAmount,
toAddress: testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: "test transaction",
from: 0
) { result in
@ -709,7 +709,7 @@ class AdvancedReOrgTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: Zatoshi(20000),
toAddress: self.testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: "this is a test",
from: 0,
resultBlock: { result in
@ -1069,7 +1069,7 @@ class AdvancedReOrgTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: Zatoshi(20000),
toAddress: self.testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: "this is a test",
from: 0,
resultBlock: { result in

View File

@ -82,7 +82,7 @@ class BalanceTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: spendingKey,
zatoshi: maxBalance,
toAddress: testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: "test send \(self.description) \(Date().description)",
from: 0,
resultBlock: { result in
@ -226,7 +226,7 @@ class BalanceTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: spendingKey,
zatoshi: maxBalanceMinusOne,
toAddress: testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: "test send \(self.description) \(Date().description)",
from: 0,
resultBlock: { result in
@ -367,7 +367,7 @@ class BalanceTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: spendingKey,
zatoshi: maxBalanceMinusOne,
toAddress: testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: "test send \(self.description) \(Date().description)",
from: 0,
resultBlock: { result in
@ -510,7 +510,7 @@ class BalanceTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: spendingKey,
zatoshi: sendAmount,
toAddress: testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: "test send \(self.description) \(Date().description)",
from: 0,
resultBlock: { result in
@ -681,7 +681,7 @@ class BalanceTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: spendingKey,
zatoshi: sendAmount,
toAddress: testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: "test send \(self.description) \(Date().description)",
from: 0,
resultBlock: { result in
@ -836,7 +836,7 @@ class BalanceTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: spendingKeys,
zatoshi: sendAmount,
toAddress: testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: memo,
from: 0,
resultBlock: { sendResult in
@ -1008,7 +1008,7 @@ class BalanceTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: spendingKey,
zatoshi: sendAmount,
toAddress: testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: "test send \(self.description)",
from: 0,
resultBlock: { result in

View File

@ -82,7 +82,7 @@ class NetworkUpgradeTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: spendAmount,
toAddress: self.testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: "this is a test",
from: 0,
resultBlock: { result in
@ -171,7 +171,7 @@ class NetworkUpgradeTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: spendAmount,
toAddress: self.testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: "this is a test",
from: 0,
resultBlock: { result in
@ -240,7 +240,7 @@ class NetworkUpgradeTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: spendAmount,
toAddress: self.testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: "this is a test",
from: 0,
resultBlock: { result in
@ -341,7 +341,7 @@ class NetworkUpgradeTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: spendAmount,
toAddress: self.testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: "this is a test",
from: 0,
resultBlock: { result in
@ -453,7 +453,7 @@ class NetworkUpgradeTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: spendAmount,
toAddress: self.testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: "this is a test",
from: 0,
resultBlock: { result in

View File

@ -97,7 +97,7 @@ class PendingTransactionUpdatesTest: XCTestCase {
// swiftlint:disable:next force_unwrapping
spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: Zatoshi(20000),
toAddress: self.testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: "this is a test",
from: 0,
resultBlock: { result in

View File

@ -169,7 +169,7 @@ class RewindRescanTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: coordinator.spendingKey,
zatoshi: Zatoshi(1000),
toAddress: testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: nil,
from: 0
) { result in
@ -270,7 +270,7 @@ class RewindRescanTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: spendingKey,
zatoshi: maxBalance,
toAddress: testRecipientAddress,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: "test send \(self.description) \(Date().description)",
from: 0
) { result in

View File

@ -84,7 +84,7 @@ class ShieldFundsTests: XCTestCase {
///
func testShieldFunds() throws {
// 1. load the dataset
try coordinator.service.useDataset(from: "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/shielding-dataset/shield-funds/1631000.txt")
try coordinator.service.useDataset(from: "https://github.com/zcash-hackworks/darksidewalletd-test-data/blob/master/shield-funds/1631000.txt")
try coordinator.stageBlockCreate(height: birthday + 1, count: 200, nonce: 0)
@ -209,7 +209,6 @@ class ShieldFundsTests: XCTestCase {
// shield the funds
coordinator.synchronizer.shieldFunds(
spendingKey: coordinator.spendingKey,
transparentAccountPrivateKey: transparentAccountPrivateKey,
memo: "shield funds",
from: 0

View File

@ -104,7 +104,7 @@ class Z2TReceiveTests: XCTestCase {
coordinator.synchronizer.sendToAddress(
spendingKey: coordinator.spendingKeys!.first!,
zatoshi: sendAmount,
toAddress: testRecipientAddress,
toAddress: try! Recipient(testRecipientAddress, network: self.network.networkType),
memo: "test transaction",
from: 0
) { result in

View File

@ -19,9 +19,9 @@ class BlockScanOperationTests: XCTestCase {
var cacheDbURL: URL!
var dataDbURL: URL!
var ufvk = UFVFakeKey(
account: 0,
encoding: "uviewtest1q48t999peecrfkq7ykcxckfkjt77w3lckk5mptlrtuy7xltjnzg8fm5434cxe9p9838ljs24yv83rluhk33ew098dkarapzyj4vk5kfxp5zn2jp3ww74jwd48r05aqjvgqxzx3nqn6zfqh3cmwdtmz0mc5624tvdza55q7mguxrehwcy4y0uktcpp4tkpex4qhazddux4yt6hr0sc9fkqmfr5tyz6ldd7yrq93tyj7446u4kst3vhmd40uga636p56hr0hjfdhgp07qyh90kmsl3qnmld6c8h7u06vekkjywmxv07mqzz9muwcl6weczrn5vf3p27uc9ufrumdp64zdzulzvc373wx3gl0yntntujhcsjhrwk9xwyjpvyuf0s8q3mgjs7uy3pg960w40dthpngcnauhgg9xq8cdcyfkq7ctnngqg4nkp5eh9knd4ckwjyd9czdd240lumul96r2fuerlvjeha6cyn9ftm7gr6xqjmq0zy6tv" // swiftlint:disable:this line_length
var ufvk = UnifiedFullViewingKey(
validatedEncoding: "uviewtest1q48t999peecrfkq7ykcxckfkjt77w3lckk5mptlrtuy7xltjnzg8fm5434cxe9p9838ljs24yv83rluhk33ew098dkarapzyj4vk5kfxp5zn2jp3ww74jwd48r05aqjvgqxzx3nqn6zfqh3cmwdtmz0mc5624tvdza55q7mguxrehwcy4y0uktcpp4tkpex4qhazddux4yt6hr0sc9fkqmfr5tyz6ldd7yrq93tyj7446u4kst3vhmd40uga636p56hr0hjfdhgp07qyh90kmsl3qnmld6c8h7u06vekkjywmxv07mqzz9muwcl6weczrn5vf3p27uc9ufrumdp64zdzulzvc373wx3gl0yntntujhcsjhrwk9xwyjpvyuf0s8q3mgjs7uy3pg960w40dthpngcnauhgg9xq8cdcyfkq7ctnngqg4nkp5eh9knd4ckwjyd9czdd240lumul96r2fuerlvjeha6cyn9ftm7gr6xqjmq0zy6tv", // swiftlint:disable:this line_length
account: 0
)
var walletBirthDay = Checkpoint.birthday(
@ -268,8 +268,3 @@ extension BlockScanOperationTests: CompactBlockProgressDelegate {
func progressUpdated(_ progress: CompactBlockProgress) {
}
}
struct UFVFakeKey: UnifiedFullViewingKey {
var account: UInt32
var encoding: String
}

View File

@ -53,50 +53,48 @@ class ZcashRustBackendTests: XCTestCase {
func testDeriveExtendedSpendingKeys() {
let seed = Array("testreferencealicetestreferencealice".utf8)
var spendingKeys: [String]?
XCTAssertNoThrow(try { spendingKeys = try ZcashRustBackend.deriveExtendedSpendingKeys(seed: seed, accounts: 1, networkType: networkType) }())
var spendingKeys: [SaplingExtendedSpendingKey]?
XCTAssertNoThrow(try { spendingKeys = try ZcashRustBackend.deriveSaplingExtendedSpendingKeys(seed: seed, accounts: 1, networkType: networkType) }())
XCTAssertNotNil(spendingKeys)
XCTAssertFalse(spendingKeys?.first?.isEmpty ?? true)
XCTAssertEqual(spendingKeys?.count, 1)
}
func testDeriveExtendedFullViewingKeys() {
let seed = Array("testreferencealicetestreferencealice".utf8)
var fullViewingKeys: [String]?
var fullViewingKeys: [SaplingExtendedFullViewingKey]?
XCTAssertNoThrow(
try {
fullViewingKeys = try ZcashRustBackend.deriveExtendedFullViewingKeys(
fullViewingKeys = try ZcashRustBackend.deriveSaplingExtendedFullViewingKeys(
seed: seed,
accounts: 1,
accounts: 2,
networkType: networkType
)
}()
)
XCTAssertNotNil(fullViewingKeys)
XCTAssertFalse(fullViewingKeys?.first?.isEmpty ?? true)
XCTAssertEqual(fullViewingKeys?.count, 2)
}
func testDeriveExtendedFullViewingKey() {
let seed = Array("testreferencealicetestreferencealice".utf8)
var fullViewingKey: String?
var fullViewingKey: SaplingExtendedFullViewingKey?
var spendingKeys: [String]?
XCTAssertNoThrow(try { spendingKeys = try ZcashRustBackend.deriveExtendedSpendingKeys(seed: seed, accounts: 1, networkType: networkType) }())
var spendingKeys: [SaplingExtendedSpendingKey]?
XCTAssertNoThrow(try { spendingKeys = try ZcashRustBackend.deriveSaplingExtendedSpendingKeys(seed: seed, accounts: 1, networkType: networkType) }())
XCTAssertNotNil(spendingKeys)
XCTAssertFalse(spendingKeys?.first?.isEmpty ?? true)
guard let spendingKey = spendingKeys?.first else {
XCTFail("no spending key generated")
return
}
XCTAssertNoThrow(try { fullViewingKey = try ZcashRustBackend.deriveExtendedFullViewingKey(spendingKey, networkType: networkType) }())
XCTAssertNoThrow(try { fullViewingKey = try ZcashRustBackend.deriveSaplingExtendedFullViewingKey(spendingKey, networkType: networkType) }())
XCTAssertNotNil(fullViewingKey)
XCTAssertFalse(fullViewingKey?.isEmpty ?? true)
}
func testInitAndScanBlocks() {
@ -164,12 +162,12 @@ class ZcashRustBackendTests: XCTestCase {
}
}
func testIsValidShieldedAddressTrue() {
func testIsValidSaplingAddressTrue() {
var isValid: Bool?
XCTAssertNoThrow(
try {
isValid = try ZcashRustBackend.isValidShieldedAddress(
isValid = try ZcashRustBackend.isValidSaplingAddress(
"ztestsapling12k9m98wmpjts2m56wc60qzhgsfvlpxcwah268xk5yz4h942sd58jy3jamqyxjwums6hw7kfa4cc",
networkType: networkType
)
@ -183,12 +181,12 @@ class ZcashRustBackendTests: XCTestCase {
}
}
func testIsValidShieldedAddressFalse() {
func testIsValidSaplingAddressFalse() {
var isValid: Bool?
XCTAssertNoThrow(
try {
isValid = try ZcashRustBackend.isValidShieldedAddress(
isValid = try ZcashRustBackend.isValidSaplingAddress(
"tmSwpioc7reeoNrYB9SKpWkurJz3yEj3ee7",
networkType: networkType
)

View File

@ -1,98 +0,0 @@
//
// DerivatioToolTestnetTests.swift
// ZcashLightClientKit-Unit-DerivationToolTests
//
// Created by Francisco Gindre on 7/26/21.
//
// swift-format-ignore-file
import XCTest
@testable import ZcashLightClientKit
class DerivatioToolTestnetTests: XCTestCase {
var seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" //TODO: Parameterize this from environment?
var seedData: Data = Data(base64Encoded: "9VDVOZZZOWWHpZtq1Ebridp3Qeux5C+HwiRR0g7Oi7HgnMs8Gfln83+/Q1NnvClcaSwM4ADFL1uZHxypEWlWXg==")!
let testRecipientAddress = "utest1uqmec4a2njqz2z2rwppchsd06qe7a0jh4jmsqr0yy99m9er9646zlxunf3v8qr0hncgv86e8a62vxy0qa32qzetmj8s57yudmyx9zav6f52nurclsqjkqtjtpz6vg679p6wkczpl2wu" //TODO: Parameterize this from environment
let expectedSpendingKey = "secret-extended-key-test1qdxykmuaqqqqpqqg3x5c02p4rhw0rtszr8ln4xl7g6wg6qzsqgn445qsu3cq4vd6lk8xce3d4jw7s8ln5yjp6fqv2g0nzue2hc0kv5t004vklvlenncscq9flwh5vf5qnv0hnync72n7gjn70u47765v3kyrxytx50g730svvmhhlazn5rj8mshh470fkrmzg4xarhrqlygg8f486307ujhndwhsw2h7ddzf89k3534aeu0ypz2tjgrzlcqtat380vhe8awm03f58cqe49swv"
let expectedViewingKey = "uviewtest16dqd5q7zd3xfxlcm2jm5k95zd92ed3qcm5jr9uq6yl5y2h6vuwfpxlnndckv5hpwsajgvq26xgdcdns8mqclecl0zh4sph45t4ncfnhcsus0k6sashfknsp9ltxrxlf096ljkwmp7psh3z2vmcd3rcc72qaujsl2y23ajexhr7qza29u9k95frs8qqgvy83rgymt7mmw09a02a5ytjpa502tshlsgl2j736jlfuzt27gezlajrs2tw9c99uqxrj5sx942vdr7w6yk2ltz96yq7n96fd9nr48c59dh9znqtwtm0nt9tmt7vzwhdwzt00tgp57mn85hpe6w00upmjv52kct9y"
let expectedSaplingExtendedViewingKey = "zxviewtestsapling1qdxykmuaqqqqpqqg3x5c02p4rhw0rtszr8ln4xl7g6wg6qzsqgn445qsu3cq4vd6l5smlqrckkl2x5rnrauzc4gp665q3zyw0qf2sfdsx5wpp832htfavqk72uchuuvq2dpmgk8jfaza5t5l56u66fpx0sr8ewp9s3wj2txavmhhlazn5rj8mshh470fkrmzg4xarhrqlygg8f486307ujhndwhsw2h7ddzf89k3534aeu0ypz2tjgrzlcqtat380vhe8awm03f58cqgegsaj"
let derivationTool = DerivationTool(networkType: NetworkType.testnet)
let expectedTransparentAddress = "tmXuTnE11JojToagTqxXUn6KvdxDE3iLKbp"
func testDeriveViewingKeysFromSeed() throws {
let accounts: Int = 1
let seedBytes = [UInt8](seedData)
let viewingKeys = try derivationTool.deriveViewingKeys(seed: seedBytes, numberOfAccounts: accounts)
XCTAssertEqual(viewingKeys.count, accounts, "the number of viewing keys have to match the number of account requested to derive")
guard let viewingKey = viewingKeys.first else {
XCTFail("no viewing key generated")
return
}
XCTAssertEqual(expectedViewingKey, viewingKey)
}
func testDeriveViewingKeyFromSpendingKeys() throws {
XCTAssertEqual(expectedSaplingExtendedViewingKey, try derivationTool.deriveViewingKey(spendingKey: expectedSpendingKey))
}
func testDeriveSpendingKeysFromSeed() throws {
let accounts: Int = 1
let seedBytes = [UInt8](seedData)
let spendingKeys = try derivationTool.deriveSpendingKeys(seed: seedBytes, numberOfAccounts: accounts)
XCTAssertEqual(spendingKeys.count, accounts, "the number of viewing keys have to match the number of account requested to derive")
guard let spendingKey = spendingKeys.first else {
XCTFail("no viewing key generated")
return
}
XCTAssertEqual(expectedSpendingKey, spendingKey)
}
func testDeriveUnifiedAddressFromSeed() throws {
let seedBytes = [UInt8](seedData)
let shieldedAddress = try derivationTool.deriveUnifiedAddress(seed: seedBytes, accountIndex: 0)
XCTAssertEqual(shieldedAddress, testRecipientAddress)
}
func testDeriveUnifiedAddressFromViewingKey() throws {
XCTAssertEqual(try derivationTool.deriveUnifiedAddress(viewingKey: expectedViewingKey), testRecipientAddress)
}
func testDeriveTransparentAddressFromSeed() throws {
XCTAssertEqual(try derivationTool.deriveTransparentAddress(seed: [UInt8](seedData)), expectedTransparentAddress)
}
func testIsValidViewingKey() throws {
XCTAssertTrue(try derivationTool.isValidExtendedViewingKey(self.expectedSaplingExtendedViewingKey))
XCTAssertFalse(try derivationTool.isValidExtendedViewingKey("zxviews1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkysswfhjk79n8l99f2grd26dqg6dy3jcmxsaypxfsu6ara6vsk3x8l544uaksstx9zre879mdg7s9a7zurrx6pf5qg2n323js2s3zlu8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszcq7kwxy"))
}
func testDeriveTransparentAccountPrivateKeyFromSeed() throws {
XCTAssertEqual(try derivationTool.deriveTransparentAccountPrivateKey(seed: [UInt8](seedData)), "xprv9yURYog8Ds8XB36PVzPadbVaCPwVm4CZVMejW9bPPTqBCY8oLssPbe1MhJhPzSbVeg7cWZtuXxuUy2urADuAJUaN27c5f9nErx68SQokG1b")
}
func testDeriveUnifiedKeysFromSeed() throws {
let unifiedKeys = try derivationTool.deriveUnifiedFullViewingKeysFromSeed([UInt8](seedData), numberOfAccounts: 1)
XCTAssertEqual(unifiedKeys.count, 1)
XCTAssertEqual(unifiedKeys[0].account, 0)
XCTAssertEqual(unifiedKeys[0].encoding, expectedViewingKey)
}
func testDeriveQuiteALotOfUnifiedKeysFromSeed() throws {
let unifiedKeys = try derivationTool.deriveUnifiedFullViewingKeysFromSeed([UInt8](seedData), numberOfAccounts: 10)
XCTAssertEqual(unifiedKeys.count, 10)
XCTAssertEqual(unifiedKeys[0].account, 0)
XCTAssertEqual(unifiedKeys[0].encoding, expectedViewingKey)
}
}

View File

@ -6,24 +6,26 @@
//
//swiftlint:disable force_unwrapping
import XCTest
import ZcashLightClientKit
@testable import ZcashLightClientKit
class DerivationToolMainnetTests: XCTestCase {
var seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" //TODO: Parameterize this from environment?
var seedData: Data = Data(base64Encoded: "9VDVOZZZOWWHpZtq1Ebridp3Qeux5C+HwiRR0g7Oi7HgnMs8Gfln83+/Q1NnvClcaSwM4ADFL1uZHxypEWlWXg==")!
let testRecipientAddress = "u1l9f0l4348negsncgr9pxd9d3qaxagmqv3lnexcplmufpq7muffvfaue6ksevfvd7wrz7xrvn95rc5zjtn7ugkmgh5rnxswmcj30y0pw52pn0zjvy38rn2esfgve64rj5pcmazxgpyuj" //TODO: Parameterize this from environment
let testRecipientAddress = UnifiedAddress(validatedEncoding: "u1l9f0l4348negsncgr9pxd9d3qaxagmqv3lnexcplmufpq7muffvfaue6ksevfvd7wrz7xrvn95rc5zjtn7ugkmgh5rnxswmcj30y0pw52pn0zjvy38rn2esfgve64rj5pcmazxgpyuj") //TODO: Parameterize this from environment
let expectedSpendingKey = "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv"
let expectedSpendingKey = SaplingExtendedSpendingKey(validatedEncoding: "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv")
let expectedViewingKey = "uview1jpddskrm73gpgrsx00y4dryapkhjlzrm5wfdcue77a26u3e7u28qu0xfsgzwt72rs60rjnwujr93al6sxchste78p8vvrlperlvladfwkyryakdutykdcqgqn9dfn9my6k3aka5ej78leksj6aptqs9yzcysszwzwr6zmrcqycxxlg87ten6ers6urmxthe3pvvh07ga7t4uz92a5y0jgej94a7u9q3nezjqj4zm634x2wc2d8d39nu74jew79phf9u025p82d8qshq0pnzcjcnke0g72gva28qsx0wvtad7qjwld5khgudwlxmx24av2mq4k5k9zypheeppcpnujc9rqpm"
let expectedSaplingExtendedViewingKey = "zxviews1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkysswfhjk79n8l99f2grd26dqg6dy3jcmxsaypxfsu6ara6vsk3x8l544uaksstx9zre879mdg7s9a7zurrx6pf5qg2n323js2s3zlu8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszcq7kwxy"
let expectedViewingKey = UnifiedFullViewingKey(validatedEncoding: "uview17fme6ux853km45g9ep07djpfzeydxxgm22xpmr7arzxyutlusalgpqlx7suga4ahzywfuwz4jclm00u7g8u65qvvdt45kttnfunvschssg3h3g06txs9ja32vx3xa8dej3unnatgzjvd0vumk37t8es3ludldrtse3q6226ws7eq4q0ywz78nudwpepgdn7jmxz8yvp7k6gxkeynkam0f8aqf9qpeaej55zhkw39x7epayhndul0j4xjttdxxlnwcd09nr8svyx8j0zng0w6scx3m5unpkaqxcm3hslhlfg4caz7r8d4xy9wm7klkg79w7j0uyzec5s3yje20eg946r6rmkf532nfydu26s8q9ua7mwxw2j2ag7hfcuu652gw6uta03vlm05zju3a9rwc4h367kqzfqrcz35pdwdk2a7yqnk850un3ujxcvve45ueajgvtr6dj4ufszgqwdy0aedgmkalx2p7qed2suarwkr35dl0c8dnqp3", account: 0)
let expectedSaplingExtendedViewingKey = SaplingExtendedFullViewingKey(validatedEncoding: "zxviews1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkysswfhjk79n8l99f2grd26dqg6dy3jcmxsaypxfsu6ara6vsk3x8l544uaksstx9zre879mdg7s9a7zurrx6pf5qg2n323js2s3zlu8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszcq7kwxy")
let expectedSaplingAddress = SaplingAddress(validatedEncoding: "zs1vp7kvlqr4n9gpehztr76lcn6skkss9p8keqs3nv8avkdtjrcctrvmk9a7u494kluv756jeee5k0")
let derivationTool = DerivationTool(networkType: NetworkType.mainnet)
let expectedTransparentAddress = "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz"
let expectedTransparentAddress = TransparentAddress(validatedEncoding: "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz")
func testDeriveViewingKeysFromSeed() throws {
let accounts: Int = 1
let seedBytes = [UInt8](seedData)
let viewingKeys = try derivationTool.deriveViewingKeys(seed: seedBytes, numberOfAccounts: accounts)
let viewingKeys = try derivationTool.deriveUnifiedFullViewingKeys(seed: seedBytes, numberOfAccounts: accounts)
XCTAssertEqual(viewingKeys.count, accounts, "the number of viewing keys have to match the number of account requested to derive")
@ -57,12 +59,12 @@ class DerivationToolMainnetTests: XCTestCase {
func testDeriveUnifiedAddressFromSeed() throws {
let seedBytes = [UInt8](seedData)
let shieldedAddress = try derivationTool.deriveUnifiedAddress(seed: seedBytes, accountIndex: 0)
XCTAssertEqual(shieldedAddress, testRecipientAddress)
let unifiedAddress = try derivationTool.deriveUnifiedAddress(seed: seedBytes, accountIndex: 0)
XCTAssertEqual(unifiedAddress, testRecipientAddress)
}
func testDeriveUnifiedAddressFromViewingKey() throws {
XCTAssertEqual(try derivationTool.deriveUnifiedAddress(viewingKey: expectedViewingKey), testRecipientAddress)
XCTAssertEqual(try derivationTool.deriveUnifiedAddress(from: expectedViewingKey), testRecipientAddress)
}
func testDeriveTransparentAddressFromSeed() throws {
@ -76,7 +78,7 @@ class DerivationToolMainnetTests: XCTestCase {
}
func testDeriveTransparentAccountPrivateKeyFromSeed() throws {
XCTAssertEqual(try derivationTool.deriveTransparentAccountPrivateKey(seed: [UInt8](seedData)), "xprv9yCTU6giJ1qZ1DLC5rc7KMzwY9s8rSRXYqmoAKffAExpUVUKLhcdvN9ERdxjEW8tQq4pxerLKZE3WcNUKZCeX19rVTxpV2msTyNMNiFT3Nw")
XCTAssertEqual(try derivationTool.deriveTransparentAccountPrivateKey(seed: [UInt8](seedData)), TransparentAccountPrivKey(encoding: "xprv9yCTU6giJ1qZ1DLC5rc7KMzwY9s8rSRXYqmoAKffAExpUVUKLhcdvN9ERdxjEW8tQq4pxerLKZE3WcNUKZCeX19rVTxpV2msTyNMNiFT3Nw"))
}
func testDeriveUnifiedKeysFromSeed() throws {
@ -84,7 +86,7 @@ class DerivationToolMainnetTests: XCTestCase {
XCTAssertEqual(unifiedKeys.count, 1)
XCTAssertEqual(unifiedKeys[0].account, 0)
XCTAssertEqual(unifiedKeys[0].encoding, expectedViewingKey)
XCTAssertEqual(unifiedKeys[0], expectedViewingKey)
}
func testDeriveQuiteALotOfUnifiedKeysFromSeed() throws {
@ -92,11 +94,25 @@ class DerivationToolMainnetTests: XCTestCase {
XCTAssertEqual(unifiedKeys.count, 10)
XCTAssertEqual(unifiedKeys[0].account, 0)
XCTAssertEqual(unifiedKeys[0].encoding, expectedViewingKey)
XCTAssertEqual(unifiedKeys[0], expectedViewingKey)
}
func testShouldFailOnInvalidChecksumAddresses() throws {
let testAddress = "t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1"
XCTAssertFalse(try derivationTool.isValidTransparentAddress(testAddress))
}
func testSpendingKeyValidation() throws {
XCTAssertTrue(try derivationTool.isValidSaplingExtendedSpendingKey(expectedSpendingKey.stringEncoded))
}
func testSpendingKeyValidationFailsOnInvalidKey() throws {
let wrongSpendingKey = "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vvZzZzZz"
XCTAssertFalse(try derivationTool.isValidSaplingExtendedSpendingKey(wrongSpendingKey))
}
// TODO: Address encoding does not catch this test https://github.com/zcash/ZcashLightClientKit/issues/509
// func testSpendingKeyValidationThrowsWhenWrongNetwork() throws {
// XCTAssertThrowsError(try derivationTool.isValidExtendedSpendingKey("secret-extended-key-test1qdxykmuaqqqqpqqg3x5c02p4rhw0rtszr8ln4xl7g6wg6qzsqgn445qsu3cq4vd6lk8xce3d4jw7s8ln5yjp6fqv2g0nzue2hc0kv5t004vklvlenncscq9flwh5vf5qnv0hnync72n7gjn70u47765v3kyrxytx50g730svvmhhlazn5rj8mshh470fkrmzg4xarhrqlygg8f486307ujhndwhsw2h7ddzf89k3534aeu0ypz2tjgrzlcqtat380vhe8awm03f58cqe49swv"))
// }
}

View File

@ -0,0 +1,116 @@
//
// DerivatioToolTestnetTests.swift
// ZcashLightClientKit-Unit-DerivationToolTests
//
// Created by Francisco Gindre on 7/26/21.
//
// swift-format-ignore-file
import XCTest
@testable import ZcashLightClientKit
class DerivationToolTestnetTests: XCTestCase {
var seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" //TODO: Parameterize this from environment?
var seedData: Data = Data(base64Encoded: "9VDVOZZZOWWHpZtq1Ebridp3Qeux5C+HwiRR0g7Oi7HgnMs8Gfln83+/Q1NnvClcaSwM4ADFL1uZHxypEWlWXg==")!
let testRecipientAddress = UnifiedAddress(validatedEncoding: "utest1uqmec4a2njqz2z2rwppchsd06qe7a0jh4jmsqr0yy99m9er9646zlxunf3v8qr0hncgv86e8a62vxy0qa32qzetmj8s57yudmyx9zav6f52nurclsqjkqtjtpz6vg679p6wkczpl2wu") //TODO: Parameterize this from environment
let expectedSpendingKey = SaplingExtendedSpendingKey(validatedEncoding: "secret-extended-key-test1qdxykmuaqqqqpqqg3x5c02p4rhw0rtszr8ln4xl7g6wg6qzsqgn445qsu3cq4vd6lk8xce3d4jw7s8ln5yjp6fqv2g0nzue2hc0kv5t004vklvlenncscq9flwh5vf5qnv0hnync72n7gjn70u47765v3kyrxytx50g730svvmhhlazn5rj8mshh470fkrmzg4xarhrqlygg8f486307ujhndwhsw2h7ddzf89k3534aeu0ypz2tjgrzlcqtat380vhe8awm03f58cqe49swv")
let expectedViewingKey = UnifiedFullViewingKey(validatedEncoding: "uviewtest12tkgzhaevmw78us4xj2cx6ehxjgpp5da2qwrjqvytztejqfjdmy3e6nryqggtwrjum5cefuuuky8rscuw5dynmjec2tx3kkupqexw4va879pf874kvp6r8kjeza26gysxllaqwl67hm9u0jjke06zc93asrpw4wmy3g0lr9r5cy9pz49q2g7y7wm2pls5akmzhuvqr7khftk93aa2kpvwp7n3sjtmef28mxg3n2rpctsjlgsrhc29g6r23qc0u4tzd8rz8vqq4j7jxummdts8zx0jatzw4l2tl7r3egxhlw587rtkjx0y6dvw4hf4vjprn0qv3hs0sulmavk84ajeewn7argyerpr4essqvgfd0d24jpz6phxlasnd58qazh9d3yc6ad3hc5atp0pkvlq053zga65gscp0pv2plhqj9y2tcmx43thw5g4v8z3unytkc2dhyttuhmnlh5dyz4rmhgfkc96tp8z8rpfe35whjvky0jagz5n7qx", account: 0)
let expectedSaplingExtendedViewingKey = SaplingExtendedFullViewingKey(validatedEncoding: "zxviewtestsapling1qdxykmuaqqqqpqqg3x5c02p4rhw0rtszr8ln4xl7g6wg6qzsqgn445qsu3cq4vd6l5smlqrckkl2x5rnrauzc4gp665q3zyw0qf2sfdsx5wpp832htfavqk72uchuuvq2dpmgk8jfaza5t5l56u66fpx0sr8ewp9s3wj2txavmhhlazn5rj8mshh470fkrmzg4xarhrqlygg8f486307ujhndwhsw2h7ddzf89k3534aeu0ypz2tjgrzlcqtat380vhe8awm03f58cqgegsaj")
let expectedSaplingAddress = SaplingAddress(validatedEncoding: "ztestsapling1475xtm56czrzmleqzzlu4cxvjjfsy2p6rv78q07232cpsx5ee52k0mn5jyndq09mampkgvrxnwg")
let derivationTool = DerivationTool(networkType: NetworkType.testnet)
let expectedTransparentAddress = TransparentAddress(validatedEncoding: "tmXuTnE11JojToagTqxXUn6KvdxDE3iLKbp")
func testDeriveViewingKeysFromSeed() throws {
let accounts: Int = 1
let seedBytes = [UInt8](seedData)
let viewingKeys = try derivationTool.deriveUnifiedFullViewingKeys(seed: seedBytes, numberOfAccounts: accounts)
XCTAssertEqual(viewingKeys.count, accounts, "the number of viewing keys have to match the number of account requested to derive")
guard let viewingKey = viewingKeys.first else {
XCTFail("no viewing key generated")
return
}
XCTAssertEqual(expectedViewingKey, viewingKey)
}
func testDeriveViewingKeyFromSpendingKeys() throws {
XCTAssertEqual(expectedSaplingExtendedViewingKey, try derivationTool.deriveViewingKey(spendingKey: expectedSpendingKey))
}
func testDeriveSpendingKeysFromSeed() throws {
let accounts: Int = 1
let seedBytes = [UInt8](seedData)
let spendingKeys = try derivationTool.deriveSpendingKeys(seed: seedBytes, numberOfAccounts: accounts)
XCTAssertEqual(spendingKeys.count, accounts, "the number of viewing keys have to match the number of account requested to derive")
guard let spendingKey = spendingKeys.first else {
XCTFail("no viewing key generated")
return
}
XCTAssertEqual(expectedSpendingKey, spendingKey)
}
func testDeriveUnifiedAddressFromSeed() throws {
let seedBytes = [UInt8](seedData)
let unifiedAddress = try derivationTool.deriveUnifiedAddress(seed: seedBytes, accountIndex: 0)
XCTAssertEqual(unifiedAddress, testRecipientAddress)
}
func testDeriveUnifiedAddressFromViewingKey() throws {
XCTAssertEqual(try derivationTool.deriveUnifiedAddress(from: expectedViewingKey), testRecipientAddress)
}
func testDeriveTransparentAddressFromSeed() throws {
XCTAssertEqual(try derivationTool.deriveTransparentAddress(seed: [UInt8](seedData)), expectedTransparentAddress)
}
func testIsValidViewingKey() throws {
XCTAssertTrue(try derivationTool.isValidExtendedViewingKey(self.expectedSaplingExtendedViewingKey.stringEncoded))
XCTAssertFalse(try derivationTool.isValidExtendedViewingKey("zxviews1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkysswfhjk79n8l99f2grd26dqg6dy3jcmxsaypxfsu6ara6vsk3x8l544uaksstx9zre879mdg7s9a7zurrx6pf5qg2n323js2s3zlu8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszcq7kwxy"))
}
func testDeriveTransparentAccountPrivateKeyFromSeed() throws {
XCTAssertEqual(try derivationTool.deriveTransparentAccountPrivateKey(seed: [UInt8](seedData)), TransparentAccountPrivKey(encoding: "xprv9yURYog8Ds8XB36PVzPadbVaCPwVm4CZVMejW9bPPTqBCY8oLssPbe1MhJhPzSbVeg7cWZtuXxuUy2urADuAJUaN27c5f9nErx68SQokG1b"))
}
func testDeriveUnifiedKeysFromSeed() throws {
let unifiedKeys = try derivationTool.deriveUnifiedFullViewingKeysFromSeed([UInt8](seedData), numberOfAccounts: 1)
XCTAssertEqual(unifiedKeys.count, 1)
XCTAssertEqual(unifiedKeys[0].account, 0)
XCTAssertEqual(unifiedKeys[0], expectedViewingKey)
}
func testDeriveQuiteALotOfUnifiedKeysFromSeed() throws {
let unifiedKeys = try derivationTool.deriveUnifiedFullViewingKeysFromSeed([UInt8](seedData), numberOfAccounts: 10)
XCTAssertEqual(unifiedKeys.count, 10)
XCTAssertEqual(unifiedKeys[0].account, 0)
XCTAssertEqual(unifiedKeys[0], expectedViewingKey)
}
func testSpendingKeyValidation() throws {
XCTAssertTrue(try derivationTool.isValidSaplingExtendedSpendingKey(expectedSpendingKey.stringEncoded))
}
func testSpendingKeyValidationFailsOnInvalidKey() throws {
let wrongSpendingKey = "secret-extended-key-test1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vvZzZzZz"
XCTAssertFalse(try derivationTool.isValidSaplingExtendedSpendingKey(wrongSpendingKey))
}
// TODO: Address encoding does not catch this test https://github.com/zcash/ZcashLightClientKit/issues/509
// func testSpendingKeyValidationThrowsWhenWrongNetwork() throws {
// XCTAssertThrowsError(try derivationTool.isValidExtendedSpendingKey("secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv"))
// }
}

View File

@ -17,7 +17,7 @@ class NullBytesTests: XCTestCase {
let validZaddr = "zs1gqtfu59z20s9t20mxlxj86zpw6p69l0ev98uxrmlykf2nchj2dw8ny5e0l22kwmld2afc37gkfp"
let zAddrWithNullBytes = "\(validZaddr)\0something else that makes the address invalid"
XCTAssertFalse(try ZcashRustBackend.isValidShieldedAddress(zAddrWithNullBytes, networkType: networkType))
XCTAssertFalse(try ZcashRustBackend.isValidSaplingAddress(zAddrWithNullBytes, networkType: networkType))
}
func testTaddrNullBytes() throws {
@ -91,13 +91,13 @@ class NullBytesTests: XCTestCase {
func testderiveExtendedFullViewingKeyWithNullBytes() throws {
// swiftlint:disable:next line_length
let wrongSpendingKeys = "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mq\0uy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv" // this spending key corresponds to the "demo app reference seed"
let wrongSpendingKeys = SaplingExtendedSpendingKey(validatedEncoding: "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mq\0uy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv") // this spending key corresponds to the "demo app reference seed"
// swiftlint:disable:next line_length
let goodSpendingKeys = "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv"
let goodSpendingKeys = SaplingExtendedSpendingKey(validatedEncoding: "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv")
XCTAssertThrowsError(
try ZcashRustBackend.deriveExtendedFullViewingKey(wrongSpendingKeys, networkType: networkType),
try ZcashRustBackend.deriveSaplingExtendedFullViewingKey(wrongSpendingKeys, networkType: networkType),
"Should have thrown an error but didn't! this is dangerous!"
) { error in
guard let rustError = error as? RustWeldingError else {
@ -113,7 +113,7 @@ class NullBytesTests: XCTestCase {
}
}
XCTAssertNoThrow(try ZcashRustBackend.deriveExtendedFullViewingKey(goodSpendingKeys, networkType: networkType))
XCTAssertNoThrow(try ZcashRustBackend.deriveSaplingExtendedFullViewingKey(goodSpendingKeys, networkType: networkType))
}
func testCheckNullBytes() throws {

View File

@ -0,0 +1,34 @@
//
// RecipientTests.swift
// OfflineTests
//
// Created by Francisco Gindre on 8/31/22.
//
import XCTest
@testable import ZcashLightClientKit
final class RecipientTests: XCTestCase {
let uaString = "u1l9f0l4348negsncgr9pxd9d3qaxagmqv3lnexcplmufpq7muffvfaue6ksevfvd7wrz7xrvn95rc5zjtn7ugkmgh5rnxswmcj30y0pw52pn0zjvy38rn2esfgve64rj5pcmazxgpyuj"
let saplingString = "zs1vp7kvlqr4n9gpehztr76lcn6skkss9p8keqs3nv8avkdtjrcctrvmk9a7u494kluv756jeee5k0"
let transparentString = "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz"
func testUnifiedRecipient() throws {
let expectedUnifiedAddress = UnifiedAddress(validatedEncoding: uaString)
XCTAssertEqual(try Recipient(uaString, network: .mainnet), .unified(expectedUnifiedAddress))
}
func testSaplingRecipient() throws {
let expectedSaplingAddress = SaplingAddress(validatedEncoding: saplingString)
XCTAssertEqual(try Recipient(saplingString, network: .mainnet), .sapling(expectedSaplingAddress))
}
func testTransparentRecipient() throws {
let expectedTransparentAddress = TransparentAddress(validatedEncoding: transparentString)
XCTAssertEqual(try Recipient(transparentString, network: .mainnet), .transparent(expectedTransparentAddress))
}
}

View File

@ -0,0 +1,96 @@
//
// UndescribableTests.swift
//
//
// Created by Francisco Gindre on 8/30/22.
//
import XCTest
@testable import ZcashLightClientKit
struct SomeStructure: Undescribable {
var info: String
}
enum SomeError: Error {
case sensitiveThingFailed(SomeStructure)
}
struct EnclosingStruct {
var someStructure: SomeStructure
}
final class UndescribableTests: XCTestCase {
func testDescriptionIsRedacted() throws {
let info = "important info"
let someStructure = SomeStructure(info: info)
let description = String(describing: someStructure)
XCTAssertFalse(description.contains(info))
XCTAssertEqual(description, "--redacted--")
}
func testDumpIsRedacted() {
let info = "important info"
let someStructure = SomeStructure(info: info)
var stream = ""
dump(someStructure, to: &stream, indent: 0)
XCTAssertFalse(stream.contains(info))
XCTAssertEqual(stream, "- --redacted--\n")
}
func testPrintIsRedacted() {
let info = "important info"
let someStructure = SomeStructure(info: info)
var stream = ""
print(someStructure, to: &stream)
XCTAssertFalse(stream.contains(info))
XCTAssertEqual(stream, "--redacted--\n")
}
func testMirroringIsRedacted() {
let info = "important info"
let someStructure = SomeStructure(info: info)
var s = ""
debugPrint(someStructure, to: &s)
XCTAssertFalse(s.contains(info))
XCTAssertEqual(s, "--redacted--\n")
}
func testLocalizedErrorIsRedacted() {
let info = "importantInfo"
let description = "\(SomeError.sensitiveThingFailed(SomeStructure(info: info)))"
XCTAssertFalse(description.contains(info))
XCTAssertEqual(description, "sensitiveThingFailed(--redacted--)")
}
func testNestedStructuresCantDescribeUndescribable() {
let info = "important info"
let nested = EnclosingStruct(someStructure: SomeStructure(info: info))
var dumpStream = ""
dump(nested, to: &dumpStream)
XCTAssertFalse(dumpStream.contains(info))
var debugStream = ""
debugPrint(nested, to: &debugStream)
XCTAssertFalse(debugStream.contains(info))
var printStream = ""
print(nested, to: &printStream)
XCTAssertFalse(printStream.contains(info))
XCTAssertFalse(String(describing: nested).contains(info))
}
func testSpendingKeyCantBeDescribed() {
let key = SaplingExtendedFullViewingKey(validatedEncoding: "zxviewtestsapling1qdxykmuaqqqqpqqg3x5c02p4rhw0rtszr8ln4xl7g6wg6qzsqgn445qsu3cq4vd6l5smlqrckkl2x5rnrauzc4gp665q3zyw0qf2sfdsx5wpp832htfavqk72uchuuvq2dpmgk8jfaza5t5l56u66fpx0sr8ewp9s3wj2txavmhhlazn5rj8mshh470fkrmzg4xarhrqlygg8f486307ujhndwhsw2h7ddzf89k3534aeu0ypz2tjgrzlcqtat380vhe8awm03f58cqgegsaj")
var s = ""
debugPrint(key, to: &s)
XCTAssertEqual(s, "--redacted--\n")
}
}

View File

@ -77,10 +77,36 @@ extension LightWalletServiceMockResponse {
}
class MockRustBackend: ZcashRustBackendWelding {
static func initDataDb(dbData: URL, seed: [UInt8]?, networkType: ZcashLightClientKit.NetworkType) throws -> ZcashLightClientKit.DbInitResult {
.seedRequired
}
static func deriveSaplingAddressFromViewingKey(_ extfvk: ZcashLightClientKit.SaplingExtendedFullViewingKey, networkType: ZcashLightClientKit.NetworkType) throws -> ZcashLightClientKit.SaplingAddress {
throw RustWeldingError.unableToDeriveKeys
}
static func isValidSaplingExtendedSpendingKey(_ key: String, networkType: ZcashLightClientKit.NetworkType) throws -> Bool {
false
}
static func deriveSaplingExtendedFullViewingKeys(seed: [UInt8], accounts: Int32, networkType: ZcashLightClientKit.NetworkType) throws -> [ZcashLightClientKit.SaplingExtendedFullViewingKey]? {
nil
}
static func isValidUnifiedAddress(_ address: String, networkType: ZcashLightClientKit.NetworkType) throws -> Bool {
false
}
static func deriveSaplingExtendedFullViewingKey(_ spendingKey: SaplingExtendedSpendingKey, networkType: ZcashLightClientKit.NetworkType) throws -> ZcashLightClientKit.SaplingExtendedFullViewingKey? {
nil
}
public func deriveViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [UnifiedFullViewingKey] {
[]
}
static func clearUtxos(dbData: URL, address: String, sinceHeight: BlockHeight, networkType: NetworkType) throws -> Int32 {
-1
}
@ -140,7 +166,6 @@ class MockRustBackend: ZcashRustBackendWelding {
dbData: URL,
account: Int32,
xprv: String,
extsk: String,
memo: String?,
spendParamsPath: String,
outputParamsPath: String,
@ -169,7 +194,7 @@ class MockRustBackend: ZcashRustBackendWelding {
throw KeyDerivationErrors.unableToDerive
}
static func isValidExtendedFullViewingKey(_ key: String, networkType: NetworkType) throws -> Bool {
static func isValidSaplingExtendedFullViewingKey(_ key: String, networkType: NetworkType) throws -> Bool {
false
}
@ -189,11 +214,7 @@ class MockRustBackend: ZcashRustBackendWelding {
nil
}
static func deriveExtendedFullViewingKeys(seed: [UInt8], accounts: Int32, networkType: NetworkType) throws -> [String]? {
nil
}
static func deriveExtendedSpendingKeys(seed: [UInt8], accounts: Int32, networkType: NetworkType) throws -> [String]? {
static func deriveSaplingExtendedSpendingKeys(seed: [UInt8], accounts: Int32, networkType: NetworkType) throws -> [SaplingExtendedSpendingKey]? {
nil
}
@ -217,7 +238,7 @@ class MockRustBackend: ZcashRustBackendWelding {
static var mockAcounts = false
static var mockError: RustWeldingError?
static var mockLastError: String?
static var mockAccounts: [String]?
static var mockAccounts: [SaplingExtendedSpendingKey]?
static var mockAddresses: [String]?
static var mockBalance: Int64?
static var mockVerifiedBalance: Int64?
@ -240,7 +261,7 @@ class MockRustBackend: ZcashRustBackendWelding {
mockLastError ?? rustBackend.getLastError()
}
static func isValidShieldedAddress(_ address: String, networkType: NetworkType) throws -> Bool {
static func isValidSaplingAddress(_ address: String, networkType: NetworkType) throws -> Bool {
true
}
@ -254,7 +275,7 @@ class MockRustBackend: ZcashRustBackendWelding {
}
}
static func initAccountsTable(dbData: URL, seed: [UInt8], accounts: Int32, networkType: NetworkType) -> [String]? {
static func initAccountsTable(dbData: URL, seed: [UInt8], accounts: Int32, networkType: NetworkType) -> [SaplingExtendedSpendingKey]? {
mockAccounts ?? rustBackend.initAccountsTable(dbData: dbData, seed: seed, accounts: accounts, networkType: networkType)
}

View File

@ -37,12 +37,12 @@ class TestCoordinator {
var completionHandler: ((SDKSynchronizer) -> Void)?
var errorHandler: ((Error?) -> Void)?
var spendingKey: String
var spendingKey: SaplingExtendedSpendingKey
var birthday: BlockHeight
var channelProvider: ChannelProvider
var synchronizer: SDKSynchronizer
var service: DarksideWalletService
var spendingKeys: [String]?
var spendingKeys: [SaplingExtendedSpendingKey]?
var databases: TemporaryTestDatabases
let network: ZcashNetwork
convenience init(
@ -85,7 +85,7 @@ class TestCoordinator {
}
required init(
spendingKey: String,
spendingKey: SaplingExtendedSpendingKey,
unifiedFullViewingKey: UnifiedFullViewingKey,
walletBirthday: BlockHeight,
channelProvider: ChannelProvider,
@ -288,13 +288,13 @@ enum TestSynchronizerBuilder {
storage: CompactBlockStorage,
spendParamsURL: URL,
outputParamsURL: URL,
spendingKey: String,
spendingKey: SaplingExtendedSpendingKey,
unifiedFullViewingKey: UnifiedFullViewingKey,
walletBirthday: BlockHeight,
network: ZcashNetwork,
seed: [UInt8]? = nil,
loggerProxy: Logger? = nil
) throws -> (spendingKeys: [String]?, synchronizer: SDKSynchronizer) {
) throws -> (spendingKeys: [SaplingExtendedSpendingKey]?, synchronizer: SDKSynchronizer) {
let initializer = Initializer(
cacheDbURL: cacheDbURL,
dataDbURL: dataDbURL,
@ -334,7 +334,7 @@ enum TestSynchronizerBuilder {
walletBirthday: BlockHeight,
network: ZcashNetwork,
loggerProxy: Logger? = nil
) throws -> (spendingKeys: [String]?, synchronizer: SDKSynchronizer) {
) throws -> (spendingKeys: [SaplingExtendedSpendingKey]?, synchronizer: SDKSynchronizer) {
guard
let spendingKey = try DerivationTool(networkType: network.networkType)
.deriveSpendingKeys(seed: seedBytes, numberOfAccounts: 1)