Use UnifiedSpendingKey for shielding and Spending (#535)

[#534] Use UnifiedSpendingKey for shielding and Spending

This commit implements the use of Unified Spending Keys for shielding
and spending as well as rolling Unified Addresses.

Users should obtain addresses by rolling them from the SDK.
USKs replace Sapling Extended Spending keys and TransparentAccountPrivKeys
when shielding or spending

Closes #534

Co-authored-by: Kris Nuttycombe <kris@nutty.land>

* Fix rebase issues

* PR Suggestion. Make `lastError` an Optional

* Fix test `testReOrgRemovesOutboundTxAndIsNeverMined`

Co-authored-by: Kris Nuttycombe <kris@nutty.land>
This commit is contained in:
Francisco Gindre 2022-10-02 19:11:17 -07:00 committed by GitHub
parent 10a884ba60
commit 7806b5114f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 2242 additions and 2758 deletions

View File

@ -7,7 +7,6 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
0D1BE4512581585C00F78BE3 /* DerivationToolViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1BE4502581585C00F78BE3 /* DerivationToolViewController.swift */; };
0D1BE47F2581937100F78BE3 /* GetUTXOsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1BE47E2581937100F78BE3 /* GetUTXOsViewController.swift */; }; 0D1BE47F2581937100F78BE3 /* GetUTXOsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1BE47E2581937100F78BE3 /* GetUTXOsViewController.swift */; };
0D49A18C241698A800CC0649 /* SampleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D49A18B241698A800CC0649 /* SampleLogger.swift */; }; 0D49A18C241698A800CC0649 /* SampleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D49A18B241698A800CC0649 /* SampleLogger.swift */; };
0D4EBA312396CFD70041B507 /* SendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D4EBA302396CFD70041B507 /* SendViewController.swift */; }; 0D4EBA312396CFD70041B507 /* SendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D4EBA302396CFD70041B507 /* SendViewController.swift */; };
@ -33,7 +32,6 @@
0DA1C4C627D11D9500E5006E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D907F152322CC5900D641FE /* AppDelegate.swift */; }; 0DA1C4C627D11D9500E5006E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D907F152322CC5900D641FE /* AppDelegate.swift */; };
0DA1C4C727D11D9500E5006E /* SaplingParametersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D6CE8BC252E3C4A0005D707 /* SaplingParametersViewController.swift */; }; 0DA1C4C727D11D9500E5006E /* SaplingParametersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D6CE8BC252E3C4A0005D707 /* SaplingParametersViewController.swift */; };
0DA1C4C827D11D9500E5006E /* GetUTXOsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1BE47E2581937100F78BE3 /* GetUTXOsViewController.swift */; }; 0DA1C4C827D11D9500E5006E /* GetUTXOsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1BE47E2581937100F78BE3 /* GetUTXOsViewController.swift */; };
0DA1C4C927D11D9500E5006E /* DerivationToolViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1BE4502581585C00F78BE3 /* DerivationToolViewController.swift */; };
0DA1C4CB27D11D9500E5006E /* SampleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D49A18B241698A800CC0649 /* SampleLogger.swift */; }; 0DA1C4CB27D11D9500E5006E /* SampleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D49A18B241698A800CC0649 /* SampleLogger.swift */; };
0DA1C4CC27D11D9500E5006E /* TransactionsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA58B952397F2CB004596EA /* TransactionsDataSource.swift */; }; 0DA1C4CC27D11D9500E5006E /* TransactionsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA58B952397F2CB004596EA /* TransactionsDataSource.swift */; };
0DA1C4CD27D11D9500E5006E /* PaginatedTransactionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF53E6623A438F100D7249C /* PaginatedTransactionsViewController.swift */; }; 0DA1C4CD27D11D9500E5006E /* PaginatedTransactionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF53E6623A438F100D7249C /* PaginatedTransactionsViewController.swift */; };
@ -72,7 +70,6 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
0D1BE4502581585C00F78BE3 /* DerivationToolViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DerivationToolViewController.swift; sourceTree = "<group>"; };
0D1BE47E2581937100F78BE3 /* GetUTXOsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetUTXOsViewController.swift; sourceTree = "<group>"; }; 0D1BE47E2581937100F78BE3 /* GetUTXOsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetUTXOsViewController.swift; sourceTree = "<group>"; };
0D49A18B241698A800CC0649 /* SampleLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleLogger.swift; sourceTree = "<group>"; }; 0D49A18B241698A800CC0649 /* SampleLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleLogger.swift; sourceTree = "<group>"; };
0D4EBA302396CFD70041B507 /* SendViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendViewController.swift; sourceTree = "<group>"; }; 0D4EBA302396CFD70041B507 /* SendViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendViewController.swift; sourceTree = "<group>"; };
@ -143,14 +140,6 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
0D1BE44F2581583D00F78BE3 /* Derivation Tool */ = {
isa = PBXGroup;
children = (
0D1BE4502581585C00F78BE3 /* DerivationToolViewController.swift */,
);
path = "Derivation Tool";
sourceTree = "<group>";
};
0D1BE47D2581933C00F78BE3 /* Get UTXOs */ = { 0D1BE47D2581933C00F78BE3 /* Get UTXOs */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -227,7 +216,6 @@
children = ( children = (
0D76121426B1D5D7001CA417 /* Constants */, 0D76121426B1D5D7001CA417 /* Constants */,
0D1BE47D2581933C00F78BE3 /* Get UTXOs */, 0D1BE47D2581933C00F78BE3 /* Get UTXOs */,
0D1BE44F2581583D00F78BE3 /* Derivation Tool */,
0D6CE8BB252E3C1A0005D707 /* Sapling Parameters */, 0D6CE8BB252E3C1A0005D707 /* Sapling Parameters */,
0DBF8F9323A80F0E0010B85F /* Transaction Detail */, 0DBF8F9323A80F0E0010B85F /* Transaction Detail */,
0DF53E6523A438BA00D7249C /* Paginated Transactions */, 0DF53E6523A438BA00D7249C /* Paginated Transactions */,
@ -530,7 +518,6 @@
0D907F162322CC5900D641FE /* AppDelegate.swift in Sources */, 0D907F162322CC5900D641FE /* AppDelegate.swift in Sources */,
0D6CE8BD252E3C4A0005D707 /* SaplingParametersViewController.swift in Sources */, 0D6CE8BD252E3C4A0005D707 /* SaplingParametersViewController.swift in Sources */,
0D1BE47F2581937100F78BE3 /* GetUTXOsViewController.swift in Sources */, 0D1BE47F2581937100F78BE3 /* GetUTXOsViewController.swift in Sources */,
0D1BE4512581585C00F78BE3 /* DerivationToolViewController.swift in Sources */,
0D49A18C241698A800CC0649 /* SampleLogger.swift in Sources */, 0D49A18C241698A800CC0649 /* SampleLogger.swift in Sources */,
0DA58B962397F2CB004596EA /* TransactionsDataSource.swift in Sources */, 0DA58B962397F2CB004596EA /* TransactionsDataSource.swift in Sources */,
0DF53E6723A438F100D7249C /* PaginatedTransactionsViewController.swift in Sources */, 0DF53E6723A438F100D7249C /* PaginatedTransactionsViewController.swift in Sources */,
@ -562,7 +549,6 @@
0DA1C4C627D11D9500E5006E /* AppDelegate.swift in Sources */, 0DA1C4C627D11D9500E5006E /* AppDelegate.swift in Sources */,
0DA1C4C727D11D9500E5006E /* SaplingParametersViewController.swift in Sources */, 0DA1C4C727D11D9500E5006E /* SaplingParametersViewController.swift in Sources */,
0DA1C4C827D11D9500E5006E /* GetUTXOsViewController.swift in Sources */, 0DA1C4C827D11D9500E5006E /* GetUTXOsViewController.swift in Sources */,
0DA1C4C927D11D9500E5006E /* DerivationToolViewController.swift in Sources */,
0DA1C4CB27D11D9500E5006E /* SampleLogger.swift in Sources */, 0DA1C4CB27D11D9500E5006E /* SampleLogger.swift in Sources */,
0DA1C4CC27D11D9500E5006E /* TransactionsDataSource.swift in Sources */, 0DA1C4CC27D11D9500E5006E /* TransactionsDataSource.swift in Sources */,
0DA1C4CD27D11D9500E5006E /* PaginatedTransactionsViewController.swift in Sources */, 0DA1C4CD27D11D9500E5006E /* PaginatedTransactionsViewController.swift in Sources */,

View File

@ -1,151 +1,149 @@
{ {
"object": { "pins" : [
"pins": [ {
{ "identity" : "grpc-swift",
"package": "grpc-swift", "kind" : "remoteSourceControl",
"repositoryURL": "https://github.com/grpc/grpc-swift.git", "location" : "https://github.com/grpc/grpc-swift.git",
"state": { "state" : {
"branch": null, "revision" : "466cc881f1760ed8c0e685900ed62dab7846a571",
"revision": "466cc881f1760ed8c0e685900ed62dab7846a571", "version" : "1.8.0"
"version": "1.8.0"
}
},
{
"package": "KRActivityIndicatorView",
"repositoryURL": "https://github.com/krimpedance/KRActivityIndicatorView.git",
"state": {
"branch": null,
"revision": "bcb0e841d6de0cd343a32bd5056580a56d06c0bc",
"version": "3.0.7"
}
},
{
"package": "KRProgressHUD",
"repositoryURL": "https://github.com/krimpedance/KRProgressHUD.git",
"state": {
"branch": null,
"revision": "265142816d8f8ea93840accaf4ac7c49998e77c2",
"version": "3.4.7"
}
},
{
"package": "MnemonicSwift",
"repositoryURL": "https://github.com/zcash-hackworks/MnemonicSwift.git",
"state": {
"branch": null,
"revision": "716a2c32ac2bbd8a1499ac834077df42b75edc85",
"version": "2.2.4"
}
},
{
"package": "NotificationBubbles",
"repositoryURL": "https://github.com/pacu/NotificationBubbles.git",
"state": {
"branch": null,
"revision": "ae6d47f3a415c9eec5daa8e04d040c0e68654f00",
"version": "1.0.0"
}
},
{
"package": "PaginatedTableView",
"repositoryURL": "https://github.com/dh-ecc/PaginatedTableView",
"state": {
"branch": "master",
"revision": "a3fd9f079d6c9ac3095ee3ef2369a68c29ba04db",
"version": null
}
},
{
"package": "SQLite.swift",
"repositoryURL": "https://github.com/stephencelis/SQLite.swift.git",
"state": {
"branch": null,
"revision": "60a65015f6402b7c34b9a924f755ca0a73afeeaa",
"version": "0.13.1"
}
},
{
"package": "swift-crypto",
"repositoryURL": "https://github.com/apple/swift-crypto.git",
"state": {
"branch": null,
"revision": "d9825fa541df64b1a7b182178d61b9a82730d01f",
"version": "2.1.0"
}
},
{
"package": "swift-log",
"repositoryURL": "https://github.com/apple/swift-log.git",
"state": {
"branch": null,
"revision": "5d66f7ba25daf4f94100e7022febf3c75e37a6c7",
"version": "1.4.2"
}
},
{
"package": "swift-nio",
"repositoryURL": "https://github.com/apple/swift-nio.git",
"state": {
"branch": null,
"revision": "51c3fc2e4a0fcdf4a25089b288dd65b73df1b0ef",
"version": "2.37.0"
}
},
{
"package": "swift-nio-extras",
"repositoryURL": "https://github.com/apple/swift-nio-extras.git",
"state": {
"branch": null,
"revision": "f73ca5ee9c6806800243f1ac415fcf82de9a4c91",
"version": "1.10.2"
}
},
{
"package": "swift-nio-http2",
"repositoryURL": "https://github.com/apple/swift-nio-http2.git",
"state": {
"branch": null,
"revision": "108ac15087ea9b79abb6f6742699cf31de0e8772",
"version": "1.22.0"
}
},
{
"package": "swift-nio-ssl",
"repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
"state": {
"branch": null,
"revision": "52a486ff6de9bc3e26bf634c5413c41c5fa89ca5",
"version": "2.17.2"
}
},
{
"package": "swift-nio-transport-services",
"repositoryURL": "https://github.com/apple/swift-nio-transport-services.git",
"state": {
"branch": null,
"revision": "8ab824b140d0ebcd87e9149266ddc353e3705a3e",
"version": "1.11.4"
}
},
{
"package": "SwiftProtobuf",
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
"state": {
"branch": null,
"revision": "e1499bc69b9040b29184f7f2996f7bab467c1639",
"version": "1.19.0"
}
},
{
"package": "libzcashlc",
"repositoryURL": "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state": {
"branch": "bin/librustzcash_0_7",
"revision": "823f864a7952073fb9718cf75610691756e34d59",
"version": null
}
} }
] },
}, {
"version": 1 "identity" : "kractivityindicatorview",
"kind" : "remoteSourceControl",
"location" : "https://github.com/krimpedance/KRActivityIndicatorView.git",
"state" : {
"revision" : "bcb0e841d6de0cd343a32bd5056580a56d06c0bc",
"version" : "3.0.7"
}
},
{
"identity" : "krprogresshud",
"kind" : "remoteSourceControl",
"location" : "https://github.com/krimpedance/KRProgressHUD.git",
"state" : {
"revision" : "265142816d8f8ea93840accaf4ac7c49998e77c2",
"version" : "3.4.7"
}
},
{
"identity" : "mnemonicswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/MnemonicSwift.git",
"state" : {
"revision" : "716a2c32ac2bbd8a1499ac834077df42b75edc85",
"version" : "2.2.4"
}
},
{
"identity" : "notificationbubbles",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pacu/NotificationBubbles.git",
"state" : {
"revision" : "ae6d47f3a415c9eec5daa8e04d040c0e68654f00",
"version" : "1.0.0"
}
},
{
"identity" : "paginatedtableview",
"kind" : "remoteSourceControl",
"location" : "https://github.com/dh-ecc/PaginatedTableView",
"state" : {
"branch" : "master",
"revision" : "a3fd9f079d6c9ac3095ee3ef2369a68c29ba04db"
}
},
{
"identity" : "sqlite.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/stephencelis/SQLite.swift.git",
"state" : {
"revision" : "60a65015f6402b7c34b9a924f755ca0a73afeeaa",
"version" : "0.13.1"
}
},
{
"identity" : "swift-crypto",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-crypto.git",
"state" : {
"revision" : "d9825fa541df64b1a7b182178d61b9a82730d01f",
"version" : "2.1.0"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "5d66f7ba25daf4f94100e7022febf3c75e37a6c7",
"version" : "1.4.2"
}
},
{
"identity" : "swift-nio",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "51c3fc2e4a0fcdf4a25089b288dd65b73df1b0ef",
"version" : "2.37.0"
}
},
{
"identity" : "swift-nio-extras",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-extras.git",
"state" : {
"revision" : "f73ca5ee9c6806800243f1ac415fcf82de9a4c91",
"version" : "1.10.2"
}
},
{
"identity" : "swift-nio-http2",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-http2.git",
"state" : {
"revision" : "108ac15087ea9b79abb6f6742699cf31de0e8772",
"version" : "1.22.0"
}
},
{
"identity" : "swift-nio-ssl",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-ssl.git",
"state" : {
"revision" : "52a486ff6de9bc3e26bf634c5413c41c5fa89ca5",
"version" : "2.17.2"
}
},
{
"identity" : "swift-nio-transport-services",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-transport-services.git",
"state" : {
"revision" : "8ab824b140d0ebcd87e9149266ddc353e3705a3e",
"version" : "1.11.4"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "e1499bc69b9040b29184f7f2996f7bab467c1639",
"version" : "1.19.0"
}
},
{
"identity" : "zcash-light-client-ffi",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state" : {
"branch" : "bin/librustzcash_0_7",
"revision" : "14d5b977a076775447a0630387596c21cacbd09a"
}
}
],
"version" : 2
} }

View File

@ -33,8 +33,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
if let wallet = wallet { if let wallet = wallet {
return wallet return wallet
} else { } else {
let unifiedFullViewingKeys = try! DerivationTool(networkType: kZcashNetwork.networkType) let ufvk = try! DerivationTool(networkType: kZcashNetwork.networkType)
.deriveUnifiedFullViewingKeysFromSeed(DemoAppConfig.seed, numberOfAccounts: 1) .deriveUnifiedSpendingKey(seed: DemoAppConfig.seed, accountIndex: 0)
.deriveFullViewingKey()
let wallet = Initializer( let wallet = Initializer(
cacheDbURL: try! cacheDbURLHelper(), cacheDbURL: try! cacheDbURLHelper(),
@ -44,7 +45,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
network: kZcashNetwork, network: kZcashNetwork,
spendParamsURL: try! spendParamsURLHelper(), spendParamsURL: try! spendParamsURLHelper(),
outputParamsURL: try! outputParamsURLHelper(), outputParamsURL: try! outputParamsURLHelper(),
viewingKeys: unifiedFullViewingKeys, viewingKeys: [ufvk],
walletBirthday: DemoAppConfig.birthdayHeight, walletBirthday: DemoAppConfig.birthdayHeight,
loggerProxy: loggerProxy loggerProxy: loggerProxy
) )

View File

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21208.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Ewq-Xy-xHb"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Ewq-Xy-xHb">
<device id="retina6_0" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21191"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -12,7 +14,7 @@
<objects> <objects>
<tableViewController storyboardIdentifier="MainTableViewController" id="SI9-Nk-8cq" customClass="MainTableViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController"> <tableViewController storyboardIdentifier="MainTableViewController" id="SI9-Nk-8cq" customClass="MainTableViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="EVy-1I-VtG"> <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="EVy-1I-VtG">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<sections> <sections>
@ -274,19 +276,16 @@
</label> </label>
</subviews> </subviews>
</tableViewCellContentView> </tableViewCellContentView>
<connections>
<segue destination="urD-um-X0E" kind="show" id="5XW-nP-b85"/>
</connections>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="qHq-xq-jFS" style="IBUITableViewCellStyleDefault" id="XHY-aU-r1N"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="qHq-xq-jFS" style="IBUITableViewCellStyleDefault" id="XHY-aU-r1N">
<rect key="frame" x="0.0" y="617.66668319702148" width="600" height="43.5"/> <rect key="frame" x="0.0" y="617.66668319702148" width="600" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="XHY-aU-r1N" id="fbk-CU-wgr"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="XHY-aU-r1N" id="fbk-CU-wgr">
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/> <rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" ambiguous="YES" insetsLayoutMarginsFromSafeArea="NO" text="Get UTXOs For transparent Address" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="qHq-xq-jFS"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" ambiguous="YES" insetsLayoutMarginsFromSafeArea="NO" text="Get UTXOs For transparent Address" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="qHq-xq-jFS">
<rect key="frame" x="8" y="0.0" width="398" height="43.5"/> <rect key="frame" x="8" y="0.0" width="374" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
@ -323,7 +322,7 @@
<objects> <objects>
<viewController id="6wc-2b-HvC" customClass="PaginatedTransactionsViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController"> <viewController id="6wc-2b-HvC" customClass="PaginatedTransactionsViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="7UD-Cl-PlM"> <view key="view" contentMode="scaleToFill" id="7UD-Cl-PlM">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="7kA-7f-dpe" customClass="PaginatedTableView" customModule="PaginatedTableView"> <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="7kA-7f-dpe" customClass="PaginatedTableView" customModule="PaginatedTableView">
@ -385,7 +384,7 @@
<objects> <objects>
<tableViewController id="Ya7-uX-3Bq" customClass="TransactionsTableViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController"> <tableViewController id="Ya7-uX-3Bq" customClass="TransactionsTableViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="Btf-Ps-SgQ"> <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="Btf-Ps-SgQ">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<prototypes> <prototypes>
@ -433,7 +432,7 @@
<objects> <objects>
<tableViewController id="E2F-Pe-9WA" customClass="TransactionDetailViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController"> <tableViewController id="E2F-Pe-9WA" customClass="TransactionDetailViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="GZf-SE-Mi3"> <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="GZf-SE-Mi3">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<sections> <sections>
@ -610,7 +609,7 @@
<objects> <objects>
<viewController title="Send Funds" id="6mH-Rv-HBn" customClass="SendViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController"> <viewController title="Send Funds" id="6mH-Rv-HBn" customClass="SendViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="mzn-Gc-de9"> <view key="view" contentMode="scaleToFill" id="mzn-Gc-de9">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" spacing="32" translatesAutoresizingMaskIntoConstraints="NO" id="TpC-jA-EZ0"> <stackView opaque="NO" contentMode="scaleToFill" spacing="32" translatesAutoresizingMaskIntoConstraints="NO" id="TpC-jA-EZ0">
@ -815,7 +814,7 @@
<objects> <objects>
<viewController title="Get Addreses" id="Rx9-1X-8Gi" customClass="GetAddressViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController"> <viewController title="Get Addreses" id="Rx9-1X-8Gi" customClass="GetAddressViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="5WU-Pu-1e9"> <view key="view" contentMode="scaleToFill" id="5WU-Pu-1e9">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="TGE-Nt-Y1I"> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="TGE-Nt-Y1I">
@ -857,7 +856,7 @@
</view> </view>
<navigationItem key="navigationItem" title="Get Address" largeTitleDisplayMode="always" id="Uvy-EM-bSo"/> <navigationItem key="navigationItem" title="Get Address" largeTitleDisplayMode="always" id="Uvy-EM-bSo"/>
<connections> <connections>
<outlet property="spendingKeyLabel" destination="hl0-9u-TsZ" id="afF-Yq-bty"/> <outlet property="saplingAddress" destination="hl0-9u-TsZ" id="afF-Yq-bty"/>
<outlet property="tAddressLabel" destination="ntO-Ig-rst" id="6ER-bQ-FVH"/> <outlet property="tAddressLabel" destination="ntO-Ig-rst" id="6ER-bQ-FVH"/>
<outlet property="unifiedAddressLabel" destination="X0E-Ba-xxX" id="9es-sw-gO5"/> <outlet property="unifiedAddressLabel" destination="X0E-Ba-xxX" id="9es-sw-gO5"/>
</connections> </connections>
@ -871,7 +870,7 @@
<objects> <objects>
<viewController id="Nsd-EZ-dgy" customClass="SaplingParametersViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController"> <viewController id="Nsd-EZ-dgy" customClass="SaplingParametersViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="cqf-Dv-Buo"> <view key="view" contentMode="scaleToFill" id="cqf-Dv-Buo">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalCentering" translatesAutoresizingMaskIntoConstraints="NO" id="mvO-Hh-h9H"> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalCentering" translatesAutoresizingMaskIntoConstraints="NO" id="mvO-Hh-h9H">
@ -946,7 +945,7 @@
<objects> <objects>
<viewController id="eja-yc-RHW" customClass="SyncBlocksViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController"> <viewController id="eja-yc-RHW" customClass="SyncBlocksViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="3Ob-Wz-DM3"> <view key="view" contentMode="scaleToFill" id="3Ob-Wz-DM3">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="24" translatesAutoresizingMaskIntoConstraints="NO" id="dp8-P5-xEk"> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="24" translatesAutoresizingMaskIntoConstraints="NO" id="dp8-P5-xEk">
@ -1051,7 +1050,7 @@
<objects> <objects>
<viewController id="tq2-zN-roj" customClass="LatestHeightViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController"> <viewController id="tq2-zN-roj" customClass="LatestHeightViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="tie-BL-1Ip"> <view key="view" contentMode="scaleToFill" id="tie-BL-1Ip">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0Hj-Jx-J3i"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0Hj-Jx-J3i">
@ -1094,7 +1093,7 @@
<objects> <objects>
<navigationController id="Ewq-Xy-xHb" sceneMemberID="viewController"> <navigationController id="Ewq-Xy-xHb" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="AlK-mv-yla"> <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="AlK-mv-yla">
<rect key="frame" x="0.0" y="44" width="414" height="44"/> <rect key="frame" x="0.0" y="0.0" width="600" height="50"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</navigationBar> </navigationBar>
<connections> <connections>
@ -1110,7 +1109,7 @@
<objects> <objects>
<viewController id="r9r-pi-Z4o" customClass="GetBalanceViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController"> <viewController id="r9r-pi-Z4o" customClass="GetBalanceViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="n2N-jn-4DV"> <view key="view" contentMode="scaleToFill" id="n2N-jn-4DV">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="Cep-qY-yP1"> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="Cep-qY-yP1">
@ -1172,217 +1171,12 @@
</objects> </objects>
<point key="canvasLocation" x="1830" y="1623"/> <point key="canvasLocation" x="1830" y="1623"/>
</scene> </scene>
<!--Derivation Tool View Controller-->
<scene sceneID="tSJ-ZN-BDT">
<objects>
<viewController id="urD-um-X0E" customClass="DerivationToolViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="bAZ-rg-kmi">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0JM-NT-N87">
<rect key="frame" x="0.0" y="44" width="600" height="556"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sgq-Qs-o8o">
<rect key="frame" x="0.0" y="0.0" width="600" height="556"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="18" translatesAutoresizingMaskIntoConstraints="NO" id="lJM-ik-oa2">
<rect key="frame" x="93" y="-77.666666666666686" width="414" height="711.33333333333348"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Type your seed phrase" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GdL-W0-t1l">
<rect key="frame" x="120.33333333333333" y="0.0" width="173.33333333333337" height="40"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="RTm-h4-DMc"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" text="enter a seed phrase" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="C59-0R-RxT">
<rect key="frame" x="16" y="58" width="382" height="160"/>
<color key="backgroundColor" systemColor="systemGray4Color"/>
<constraints>
<constraint firstAttribute="height" constant="160" id="a2K-H3-i61"/>
</constraints>
<color key="textColor" systemColor="labelColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences" enablesReturnKeyAutomatically="YES"/>
<connections>
<outlet property="delegate" destination="urD-um-X0E" id="GJK-9Q-yzM"/>
</connections>
</textView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="valid or invalid addresss" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nDc-QP-WuU">
<rect key="frame" x="144.33333333333334" y="236" width="125.66666666666666" height="24"/>
<constraints>
<constraint firstAttribute="height" constant="24" id="Cr0-L9-uab"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Yn0-gU-62P">
<rect key="frame" x="88" y="278" width="238" height="48"/>
<constraints>
<constraint firstAttribute="height" constant="48" id="T0a-BW-X9M"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<state key="normal" title="Derive Keys and Addresses"/>
<connections>
<action selector="deriveButtonTapped:" destination="urD-um-X0E" eventType="touchUpInside" id="psH-2C-cT6"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Shielded Address" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iKP-Wh-tZo">
<rect key="frame" x="140.66666666666666" y="344" width="132.99999999999997" height="40"/>
<gestureRecognizers/>
<constraints>
<constraint firstAttribute="height" constant="40" id="teC-Db-nKa"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" contentMode="left" horizontalHuggingPriority="251" text="this is your zAddress" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="s7h-kh-3VS">
<rect key="frame" x="128.33333333333337" y="402" width="157.66666666666663" height="20.333333333333314"/>
<gestureRecognizers/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<connections>
<outletCollection property="gestureRecognizers" destination="WdA-pD-38N" appends="YES" id="keb-ur-rAo"/>
</connections>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Transparent Address" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FtJ-j8-xGc">
<rect key="frame" x="128" y="440.33333333333337" width="158" height="40"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="wYY-yg-8Ab"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="this is your tAddress" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PHc-2R-paD">
<rect key="frame" x="129.66666666666669" y="498.33333333333337" width="154.66666666666669" height="20.333333333333371"/>
<gestureRecognizers/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<connections>
<outletCollection property="gestureRecognizers" destination="Wfp-JW-CWb" appends="YES" id="LsS-BP-Aow"/>
</connections>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Extended Full viewing Key" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mCg-81-0lp">
<rect key="frame" x="108.33333333333333" y="536.66666666666663" width="197.33333333333337" height="40"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="ceE-X1-a4Z"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="this is Extended Full viewing Key" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ckD-nG-axZ">
<rect key="frame" x="84" y="594.66666666666663" width="246" height="20.333333333333371"/>
<gestureRecognizers/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<connections>
<outletCollection property="gestureRecognizers" destination="mpR-7E-CQR" appends="YES" id="ghJ-vz-Omm"/>
</connections>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Spending Key" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="BU6-q8-Q1L">
<rect key="frame" x="154.66666666666666" y="633" width="104.99999999999997" height="40"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="owC-eB-pNf"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="This is your Spending Key" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="P0V-dh-PuR">
<rect key="frame" x="109" y="691" width="196" height="20.333333333333371"/>
<gestureRecognizers/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<connections>
<outletCollection property="gestureRecognizers" destination="pKS-94-Kus" appends="YES" id="4ah-N9-ImG"/>
</connections>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="HzG-VV-dlO"/>
<constraints>
<constraint firstItem="HzG-VV-dlO" firstAttribute="trailing" secondItem="C59-0R-RxT" secondAttribute="trailing" constant="16" id="Oyd-ke-rdl"/>
<constraint firstItem="C59-0R-RxT" firstAttribute="leading" secondItem="HzG-VV-dlO" secondAttribute="leading" constant="16" id="QYw-hv-FDJ"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="lJM-ik-oa2" firstAttribute="centerX" secondItem="sgq-Qs-o8o" secondAttribute="centerX" id="eLx-Qp-3Tu"/>
<constraint firstItem="lJM-ik-oa2" firstAttribute="centerY" secondItem="sgq-Qs-o8o" secondAttribute="centerY" id="fhI-j2-4Iv"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="sgq-Qs-o8o" firstAttribute="bottom" secondItem="0JM-NT-N87" secondAttribute="bottom" id="1B1-Wq-1sP"/>
<constraint firstItem="sgq-Qs-o8o" firstAttribute="top" secondItem="0JM-NT-N87" secondAttribute="top" id="PXs-CQ-jqU"/>
<constraint firstItem="sgq-Qs-o8o" firstAttribute="centerX" secondItem="0JM-NT-N87" secondAttribute="centerX" id="cf0-Ew-OFX"/>
<constraint firstItem="sgq-Qs-o8o" firstAttribute="leading" secondItem="0JM-NT-N87" secondAttribute="leading" id="iry-BX-4gc"/>
<constraint firstItem="sgq-Qs-o8o" firstAttribute="centerY" secondItem="0JM-NT-N87" secondAttribute="centerY" id="t8r-Ec-Ywh"/>
<constraint firstItem="sgq-Qs-o8o" firstAttribute="trailing" secondItem="0JM-NT-N87" secondAttribute="trailing" id="zfK-HW-dLA"/>
</constraints>
</scrollView>
</subviews>
<viewLayoutGuide key="safeArea" id="ofo-Ei-0uk"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="0JM-NT-N87" firstAttribute="bottom" secondItem="ofo-Ei-0uk" secondAttribute="bottom" id="1MB-Ep-koz"/>
<constraint firstItem="0JM-NT-N87" firstAttribute="top" secondItem="ofo-Ei-0uk" secondAttribute="top" id="5mI-HM-G7U"/>
<constraint firstItem="0JM-NT-N87" firstAttribute="leading" secondItem="ofo-Ei-0uk" secondAttribute="leading" id="c6q-tW-et9"/>
<constraint firstItem="0JM-NT-N87" firstAttribute="trailing" secondItem="ofo-Ei-0uk" secondAttribute="trailing" id="iDA-8Z-7El"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="vRQ-Ab-vha"/>
<connections>
<outlet property="deriveButton" destination="Yn0-gU-62P" id="JjO-iM-iYT"/>
<outlet property="extendedFullViewingKeyLabel" destination="ckD-nG-axZ" id="5R6-3U-lIY"/>
<outlet property="seedTextLabel" destination="nDc-QP-WuU" id="Dz7-3V-D4c"/>
<outlet property="seedTextView" destination="C59-0R-RxT" id="qgj-cx-r7p"/>
<outlet property="shieldedAddressLabel" destination="s7h-kh-3VS" id="i1g-QC-ZxR"/>
<outlet property="spendingKeyLabel" destination="P0V-dh-PuR" id="nvH-b2-e7F"/>
<outlet property="transparentAddressLabel" destination="PHc-2R-paD" id="rVu-Cp-Unt"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="CFW-FJ-RIU" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
<tapGestureRecognizer id="WdA-pD-38N">
<connections>
<action selector="zAddressTapped:" destination="urD-um-X0E" id="16h-CN-BzF"/>
</connections>
</tapGestureRecognizer>
<tapGestureRecognizer id="Wfp-JW-CWb">
<connections>
<action selector="tAddressTapped:" destination="urD-um-X0E" id="5gE-DU-6cP"/>
</connections>
</tapGestureRecognizer>
<tapGestureRecognizer id="mpR-7E-CQR">
<connections>
<action selector="viewingKeyTapped:" destination="urD-um-X0E" id="sEe-26-GP2"/>
</connections>
</tapGestureRecognizer>
<tapGestureRecognizer id="pKS-94-Kus">
<connections>
<action selector="spendingKeyTapped:" destination="urD-um-X0E" id="8U5-Nd-9pO"/>
</connections>
</tapGestureRecognizer>
</objects>
<point key="canvasLocation" x="-10.144927536231885" y="684.375"/>
</scene>
<!--Get UTXOs for tAddr--> <!--Get UTXOs for tAddr-->
<scene sceneID="lqr-tV-bOy"> <scene sceneID="lqr-tV-bOy">
<objects> <objects>
<viewController title="Get UTXOs for tAddr" id="Ugl-B2-O3O" customClass="GetUTXOsViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController"> <viewController title="Get UTXOs for tAddr" id="Ugl-B2-O3O" customClass="GetUTXOsViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="FUM-Ak-cpK"> <view key="view" contentMode="scaleToFill" id="FUM-Ak-cpK">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="40" translatesAutoresizingMaskIntoConstraints="NO" id="Dcv-Hc-vFg"> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="40" translatesAutoresizingMaskIntoConstraints="NO" id="Dcv-Hc-vFg">
@ -1435,7 +1229,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pcE-rS-DWU"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pcE-rS-DWU">
<rect key="frame" x="93" y="388" width="414" height="168"/> <rect key="frame" x="105" y="388" width="390" height="168"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view> </view>
</subviews> </subviews>
@ -1494,9 +1288,6 @@
<systemColor name="systemGray2Color"> <systemColor name="systemGray2Color">
<color red="0.68235294117647061" green="0.68235294117647061" blue="0.69803921568627447" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color red="0.68235294117647061" green="0.68235294117647061" blue="0.69803921568627447" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor> </systemColor>
<systemColor name="systemGray4Color">
<color red="0.81960784313725488" green="0.81960784313725488" blue="0.83921568627450982" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemRedColor"> <systemColor name="systemRedColor">
<color red="1" green="0.23137254901960785" blue="0.18823529411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color red="1" green="0.23137254901960785" blue="0.18823529411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor> </systemColor>

View File

@ -1,184 +0,0 @@
//
// DerivationToolViewController.swift
// ZcashLightClientSample
//
// Created by Francisco Gindre on 12/9/20.
// Copyright © 2020 Electric Coin Company. All rights reserved.
//
import UIKit
import ZcashLightClientKit
import MnemonicSwift
class DerivationToolViewController: UIViewController {
enum DerivationErrors: Error {
case couldNotDeriveSpendingKeys(underlyingError: Error)
case couldNotDeriveViewingKeys(underlyingError: Error)
case unknown
}
@IBOutlet weak var seedTextView: UITextView!
@IBOutlet weak var seedTextLabel: UILabel!
@IBOutlet weak var shieldedAddressLabel: UILabel!
@IBOutlet weak var transparentAddressLabel: UILabel!
@IBOutlet weak var spendingKeyLabel: UILabel!
@IBOutlet weak var extendedFullViewingKeyLabel: UILabel!
@IBOutlet weak var deriveButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
deriveButton.isEnabled = isValidSeed(seedTextView.text)
updateValidationUI()
}
@IBAction func spendingKeyTapped(_ gesture: UIGestureRecognizer) {
loggerProxy.event("spending key copied to clipboard")
UIPasteboard.general.string = self.spendingKeyLabel.text
let alert = UIAlertController(
title: "",
message: "Spending Key Copied to clipboard",
preferredStyle: UIAlertController.Style.alert
)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
@IBAction func viewingKeyTapped(_ gesture: UIGestureRecognizer) {
loggerProxy.event("extended full viewing key copied to clipboard")
UIPasteboard.general.string = self.extendedFullViewingKeyLabel.text
let alert = UIAlertController(
title: "",
message: "extended full viewing key copied to clipboard",
preferredStyle: UIAlertController.Style.alert
)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
@IBAction func zAddressTapped(_ gesture: UIGestureRecognizer) {
loggerProxy.event("zAddress copied to clipboard")
UIPasteboard.general.string = self.shieldedAddressLabel.text
let alert = UIAlertController(
title: "",
message: "zAddress Copied to clipboard",
preferredStyle: UIAlertController.Style.alert
)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
@IBAction func tAddressTapped(_ gesture: UIGestureRecognizer) {
loggerProxy.event("tAddress copied to clipboard")
UIPasteboard.general.string = self.transparentAddressLabel.text
let alert = UIAlertController(
title: "",
message: "tAddress Copied to clipboard",
preferredStyle: UIAlertController.Style.alert
)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
@IBAction func deriveButtonTapped(_ sender: Any) {
do {
try deriveFrom(seedPhrase: seedTextView.text)
} catch {
fail(error)
clearLabels()
}
}
func deriveFrom(seedPhrase: String) throws {
let seedBytes = try Mnemonic.deterministicSeedBytes(from: seedPhrase)
let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType)
guard let spendingKey = try derivationTool.deriveSpendingKeys(seed: seedBytes, numberOfAccounts: 1).first else {
throw DerivationErrors.couldNotDeriveSpendingKeys(underlyingError: DerivationErrors.unknown)
}
guard let viewingKey = try derivationTool.deriveUnifiedFullViewingKeysFromSeed(seedBytes, numberOfAccounts: 1).first else {
throw DerivationErrors.couldNotDeriveViewingKeys(underlyingError: DerivationErrors.unknown)
}
let unifiedAddress = try derivationTool.deriveUnifiedAddress(from: viewingKey)
let transparentAddress = try derivationTool.deriveTransparentAddress(seed: seedBytes)
updateLabels(
spendingKey: spendingKey.stringEncoded,
viewingKey: viewingKey.stringEncoded,
shieldedAddress: unifiedAddress.stringEncoded,
transaparentAddress: transparentAddress.stringEncoded
)
}
func updateLabels(
spendingKey: String = "",
viewingKey: String = "",
shieldedAddress: String = "",
transaparentAddress: String = ""
) {
spendingKeyLabel.text = spendingKey
extendedFullViewingKeyLabel.text = viewingKey
shieldedAddressLabel.text = shieldedAddress
transparentAddressLabel.text = transaparentAddress
}
func clearLabels() {
updateLabels()
}
func fail(_ error: Error) {
let alert = UIAlertController(title: "Error", message: "\(error)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
func setValidSeed() {
seedTextLabel.text = "This is a valid seed phrase"
seedTextLabel.textColor = UIColor.systemGreen
}
func setInvalidSeed() {
seedTextLabel.text = "Invalid seed phrase"
seedTextLabel.textColor = UIColor.red
}
func isValidSeed(_ seed: String) -> Bool {
do {
try Mnemonic.validate(mnemonic: seed)
} catch {
return false
}
return true
}
func updateValidationUI() {
guard isValidSeed(seedTextView.text) else {
setInvalidSeed()
return
}
setValidSeed()
}
}
extension DerivationToolViewController: UITextViewDelegate {
func textViewDidEndEditing(_ textView: UITextView) {
updateValidationUI()
}
func textViewDidChange(_ textView: UITextView) {
deriveButton.isEnabled = isValidSeed(textView.text)
}
}

View File

@ -9,26 +9,28 @@
import UIKit import UIKit
import ZcashLightClientKit import ZcashLightClientKit
class GetAddressViewController: UIViewController { class GetAddressViewController: UIViewController {
@IBOutlet weak var unifiedAddressLabel: UILabel! @IBOutlet weak var unifiedAddressLabel: UILabel! // This is your Unified Address
@IBOutlet weak var tAddressLabel: UILabel! @IBOutlet weak var tAddressLabel: UILabel! // this is the transparent receiver of your UA above
@IBOutlet weak var spendingKeyLabel: UILabel! // THIS SHOULD BE SUPER SECRET!!!!! @IBOutlet weak var saplingAddress: UILabel! // this is the sapling receiver of your UA above
// THIS SHOULD NEVER BE STORED IN MEMORY
// swiftlint:disable:next implicitly_unwrapped_optional
var spendingKey: SaplingExtendedSpendingKey!
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType)
// swiftlint:disable:next force_try force_unwrapping let synchronizer = SDKSynchronizer.shared
self.spendingKey = try! derivationTool.deriveSpendingKeys(seed: DemoAppConfig.seed, numberOfAccounts: 1).first!
guard let uAddress = synchronizer.getUnifiedAddress(accountIndex: 0) else {
unifiedAddressLabel.text = "could not derive UA"
tAddressLabel.text = "could not derive tAddress"
saplingAddress.text = "could not derive zAddress"
return
}
// you can either try to extract receivers from the UA itself or request the Synchronizer to do it for you. Certain UAs might not contain all the receivers you expect.
unifiedAddressLabel.text = uAddress.stringEncoded
tAddressLabel.text = uAddress.transparentReceiver()?.stringEncoded ?? "could not extract transparent receiver from UA"
saplingAddress.text = uAddress.saplingReceiver()?.stringEncoded ?? "could not extract sapling receiver from UA"
// Do any additional setup after loading the view.
// 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( unifiedAddressLabel.addGestureRecognizer(
UITapGestureRecognizer( UITapGestureRecognizer(
target: self, target: self,
@ -44,23 +46,23 @@ class GetAddressViewController: UIViewController {
action: #selector(tAddressTapped(_:)) action: #selector(tAddressTapped(_:))
) )
) )
spendingKeyLabel.addGestureRecognizer( saplingAddress.addGestureRecognizer(
UITapGestureRecognizer( UITapGestureRecognizer(
target: self, target: self,
action: #selector(spendingKeyTapped(_:)) action: #selector(spendingKeyTapped(_:))
) )
) )
spendingKeyLabel.isUserInteractionEnabled = true saplingAddress.isUserInteractionEnabled = true
loggerProxy.info("Address: \(String(describing: Initializer.shared.getAddress()))") loggerProxy.info("Address: \(String(describing: uAddress))")
} }
@IBAction func spendingKeyTapped(_ gesture: UIGestureRecognizer) { @IBAction func spendingKeyTapped(_ gesture: UIGestureRecognizer) {
loggerProxy.event("copied to clipboard") loggerProxy.event("copied to clipboard")
UIPasteboard.general.string = self.spendingKey.stringEncoded UIPasteboard.general.string = self.saplingAddress.text
let alert = UIAlertController( let alert = UIAlertController(
title: "", title: "",
message: "Spending Key Copied to clipboard", message: "Sapling Address Copied to clipboard",
preferredStyle: UIAlertController.Style.alert preferredStyle: UIAlertController.Style.alert
) )
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil)) alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
@ -84,8 +86,8 @@ class GetAddressViewController: UIViewController {
@IBAction func tAddressTapped(_ gesture: UIGestureRecognizer) { @IBAction func tAddressTapped(_ gesture: UIGestureRecognizer) {
loggerProxy.event("copied to clipboard") loggerProxy.event("copied to clipboard")
UIPasteboard.general.string = try? DerivationTool(networkType: kZcashNetwork.networkType)
.deriveTransparentAddress(seed: DemoAppConfig.seed).stringEncoded UIPasteboard.general.string = tAddressLabel.text
let alert = UIAlertController( let alert = UIAlertController(
title: "", title: "",

View File

@ -23,14 +23,13 @@ class GetUTXOsViewController: UIViewController {
} }
func updateUI() { func updateUI() {
// swiftlint:disable:next force_try let synchronizer = SDKSynchronizer.shared
let tAddress = try! DerivationTool(networkType: kZcashNetwork.networkType) let tAddress = synchronizer.getTransparentAddress(accountIndex: 0)?.stringEncoded ?? "no t-address found"
.deriveTransparentAddress(seed: DemoAppConfig.seed)
self.transparentAddressLabel.text = tAddress.stringEncoded self.transparentAddressLabel.text = tAddress
// swiftlint:disable:next force_try // swiftlint:disable:next force_try
let balance = try! AppDelegate.shared.sharedSynchronizer.getTransparentBalance(accountIndex: 0) let balance = try! synchronizer.getTransparentBalance(accountIndex: 0)
self.totalBalanceLabel.text = NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: balance.total.amount)) self.totalBalanceLabel.text = NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: balance.total.amount))
self.verifiedBalanceLabel.text = NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: balance.verified.amount)) self.verifiedBalanceLabel.text = NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: balance.verified.amount))
@ -38,18 +37,16 @@ class GetUTXOsViewController: UIViewController {
@IBAction func shieldFunds(_ sender: Any) { @IBAction func shieldFunds(_ sender: Any) {
do { do {
let seed = DemoAppConfig.seed
let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType) let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType)
let transparentSecretKey = try derivationTool.deriveTransparentAccountPrivateKey(seed: seed, account: 0) let usk = try derivationTool.deriveUnifiedSpendingKey(seed: DemoAppConfig.seed, accountIndex: 0)
KRProgressHUD.showMessage("🛡 Shielding 🛡") KRProgressHUD.showMessage("🛡 Shielding 🛡")
Task { @MainActor in Task { @MainActor in
let transaction = try await AppDelegate.shared.sharedSynchronizer.shieldFunds( let transaction = try await AppDelegate.shared.sharedSynchronizer.shieldFunds(
transparentAccountPrivateKey: transparentSecretKey, transparentAccountPrivateKey: transparentSecretKey,
memo: "shielding is fun!", memo: try Memo(string: "shielding is fun!")
from: 0
) )
KRProgressHUD.dismiss() KRProgressHUD.dismiss()
self.messageLabel.text = "funds shielded \(transaction)" self.messageLabel.text = "funds shielded \(transaction)"

View File

@ -9,6 +9,7 @@
import UIKit import UIKit
import ZcashLightClientKit import ZcashLightClientKit
import KRProgressHUD import KRProgressHUD
class SendViewController: UIViewController { class SendViewController: UIViewController {
@IBOutlet weak var addressLabel: UILabel! @IBOutlet weak var addressLabel: UILabel!
@IBOutlet weak var amountLabel: UILabel! @IBOutlet weak var amountLabel: UILabel!
@ -213,8 +214,15 @@ class SendViewController: UIViewController {
loggerProxy.warn("WARNING: Form is invalid") loggerProxy.warn("WARNING: Form is invalid")
return return
} }
// swiftlint:disable:next line_length force_try
guard let spendingKey = try! DerivationTool(networkType: kZcashNetwork.networkType).deriveSpendingKeys(seed: DemoAppConfig.seed, numberOfAccounts: 1).first else { guard let spendingKey = try? DerivationTool(
networkType: kZcashNetwork.networkType
)
.deriveUnifiedSpendingKey(
seed: DemoAppConfig.seed,
accountIndex: 0
)
else {
loggerProxy.error("NO SPENDING KEY") loggerProxy.error("NO SPENDING KEY")
return return
} }
@ -226,9 +234,9 @@ class SendViewController: UIViewController {
let pendingTransaction = try await synchronizer.sendToAddress( let pendingTransaction = try await synchronizer.sendToAddress(
spendingKey: spendingKey, spendingKey: spendingKey,
zatoshi: zec, zatoshi: zec,
toAddress: recipient, // swiftlint:disable:next force_try
memo: !self.memoField.text.isEmpty ? self.memoField.text : nil, toAddress: try! Recipient(recipient, network: kZcashNetwork.networkType),
from: 0 memo: try! self.memoField.text.asMemo()
) )
KRProgressHUD.dismiss() KRProgressHUD.dismiss()
loggerProxy.info("transaction created: \(pendingTransaction)") loggerProxy.info("transaction created: \(pendingTransaction)")
@ -364,3 +372,14 @@ extension SDKSynchronizer {
} }
} }
} }
extension Optional where Wrapped == String {
func asMemo() throws -> Memo {
switch self {
case .some(let string):
return try Memo(string: string)
case .none:
return .empty
}
}
}

View File

@ -1,97 +1,95 @@
{ {
"object": { "pins" : [
"pins": [ {
{ "identity" : "grpc-swift",
"package": "grpc-swift", "kind" : "remoteSourceControl",
"repositoryURL": "https://github.com/grpc/grpc-swift.git", "location" : "https://github.com/grpc/grpc-swift.git",
"state": { "state" : {
"branch": null, "revision" : "4c63368b7462305903507e8acebd77264c0fb695",
"revision": "4c63368b7462305903507e8acebd77264c0fb695", "version" : "1.8.2"
"version": "1.8.2"
}
},
{
"package": "SQLite.swift",
"repositoryURL": "https://github.com/stephencelis/SQLite.swift.git",
"state": {
"branch": null,
"revision": "4d543d811ee644fa4cc4bfa0be996b4dd6ba0f54",
"version": "0.13.3"
}
},
{
"package": "swift-log",
"repositoryURL": "https://github.com/apple/swift-log.git",
"state": {
"branch": null,
"revision": "5d66f7ba25daf4f94100e7022febf3c75e37a6c7",
"version": "1.4.2"
}
},
{
"package": "swift-nio",
"repositoryURL": "https://github.com/apple/swift-nio.git",
"state": {
"branch": null,
"revision": "124119f0bb12384cef35aa041d7c3a686108722d",
"version": "2.40.0"
}
},
{
"package": "swift-nio-extras",
"repositoryURL": "https://github.com/apple/swift-nio-extras.git",
"state": {
"branch": null,
"revision": "a75e92bde3683241c15df3dd905b7a6dcac4d551",
"version": "1.12.1"
}
},
{
"package": "swift-nio-http2",
"repositoryURL": "https://github.com/apple/swift-nio-http2.git",
"state": {
"branch": null,
"revision": "108ac15087ea9b79abb6f6742699cf31de0e8772",
"version": "1.22.0"
}
},
{
"package": "swift-nio-ssl",
"repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
"state": {
"branch": null,
"revision": "42436a25ff32c390465567f5c089a9a8ce8d7baf",
"version": "2.20.0"
}
},
{
"package": "swift-nio-transport-services",
"repositoryURL": "https://github.com/apple/swift-nio-transport-services.git",
"state": {
"branch": null,
"revision": "2cb54f91ddafc90832c5fa247faf5798d0a7c204",
"version": "1.13.0"
}
},
{
"package": "SwiftProtobuf",
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
"state": {
"branch": null,
"revision": "e1499bc69b9040b29184f7f2996f7bab467c1639",
"version": "1.19.0"
}
},
{
"package": "libzcashlc",
"repositoryURL": "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state": {
"branch": "bin/librustzcash_0_7",
"revision": "fcf3d4b652b67d6a9b9bdc8e5565eb8dd4b46145",
"version": null
}
} }
] },
}, {
"version": 1 "identity" : "sqlite.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/stephencelis/SQLite.swift.git",
"state" : {
"revision" : "4d543d811ee644fa4cc4bfa0be996b4dd6ba0f54",
"version" : "0.13.3"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "5d66f7ba25daf4f94100e7022febf3c75e37a6c7",
"version" : "1.4.2"
}
},
{
"identity" : "swift-nio",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "124119f0bb12384cef35aa041d7c3a686108722d",
"version" : "2.40.0"
}
},
{
"identity" : "swift-nio-extras",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-extras.git",
"state" : {
"revision" : "a75e92bde3683241c15df3dd905b7a6dcac4d551",
"version" : "1.12.1"
}
},
{
"identity" : "swift-nio-http2",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-http2.git",
"state" : {
"revision" : "108ac15087ea9b79abb6f6742699cf31de0e8772",
"version" : "1.22.0"
}
},
{
"identity" : "swift-nio-ssl",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-ssl.git",
"state" : {
"revision" : "42436a25ff32c390465567f5c089a9a8ce8d7baf",
"version" : "2.20.0"
}
},
{
"identity" : "swift-nio-transport-services",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-transport-services.git",
"state" : {
"revision" : "2cb54f91ddafc90832c5fa247faf5798d0a7c204",
"version" : "1.13.0"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "e1499bc69b9040b29184f7f2996f7bab467c1639",
"version" : "1.19.0"
}
},
{
"identity" : "zcash-light-client-ffi",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state" : {
"branch" : "bin/librustzcash_0_7",
"revision" : "e8fbb84c1bec44af9dbef7e27c85f25e8f51a5af"
}
}
],
"version" : 2
} }

View File

@ -16,7 +16,7 @@ let package = Package(
dependencies: [ dependencies: [
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.8.0"), .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(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", branch: "bin/librustzcash_0_7"), .package(name:"libzcashlc", url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", branch: "bin/librustzcash_0_7")
], ],
targets: [ targets: [
.target( .target(

View File

@ -212,7 +212,6 @@ extension CompactBlockDownloader: CompactBlockDownloading {
throw CompactBlockDownloadError.generalError(error: error) throw CompactBlockDownloadError.generalError(error: error)
} }
} }
func rewind(to height: BlockHeight) throws { func rewind(to height: BlockHeight) throws {
try self.storage.rewind(to: height) try self.storage.rewind(to: height)

View File

@ -41,11 +41,11 @@ extension CompactBlockProcessor {
} }
guard rustBackend.decryptAndStoreTransaction(dbData: config.dataDb, txBytes: rawBytes, minedHeight: Int32(minedHeight), networkType: config.network.networkType) else { guard rustBackend.decryptAndStoreTransaction(dbData: config.dataDb, txBytes: rawBytes, minedHeight: Int32(minedHeight), networkType: config.network.networkType) else {
if let rustError = rustBackend.lastError() { throw EnhancementError.decryptError(
throw EnhancementError.decryptError(error: rustError) error: rustBackend.lastError() ?? .genericError(message: "`decryptAndStoreTransaction` failed. No message available")
} )
throw EnhancementError.unknownError
} }
guard let confirmedTx = try transactionRepository.findConfirmedTransactionBy(rawId: transaction.transactionId) else { guard let confirmedTx = try transactionRepository.findConfirmedTransactionBy(rawId: transaction.transactionId) else {
throw EnhancementError.txIdNotFound(txId: transaction.transactionId) throw EnhancementError.txIdNotFound(txId: transaction.transactionId)
} }

View File

@ -536,7 +536,6 @@ public class CompactBlockProcessor {
Stops the CompactBlockProcessor Stops the CompactBlockProcessor
Note: retry count is reset Note: retry count is reset
- Parameter cancelTasks: cancel the pending tasks. Defaults to true
*/ */
public func stop() { public func stop() {
self.backoffTimer?.invalidate() self.backoffTimer?.invalidate()
@ -557,7 +556,11 @@ public class CompactBlockProcessor {
let lastDownloaded = try downloader.lastDownloadedBlockHeight() let lastDownloaded = try downloader.lastDownloadedBlockHeight()
let height = Int32(height ?? lastDownloaded) let height = Int32(height ?? lastDownloaded)
let nearestHeight = rustBackend.getNearestRewindHeight(dbData: config.dataDb, height: height, networkType: self.config.network.networkType) let nearestHeight = rustBackend.getNearestRewindHeight(
dbData: config.dataDb,
height: height,
networkType: self.config.network.networkType
)
guard nearestHeight > 0 else { guard nearestHeight > 0 else {
let error = rustBackend.lastError() ?? RustWeldingError.genericError( let error = rustBackend.lastError() ?? RustWeldingError.genericError(
@ -1013,52 +1016,51 @@ extension CompactBlockProcessor.State: Equatable {
} }
} }
// Transparent stuff
extension CompactBlockProcessor { extension CompactBlockProcessor {
public func utxoCacheBalance(tAddress: String) throws -> WalletBalance { public func getUnifiedAddress(accountIndex: Int) -> UnifiedAddress? {
try rustBackend.downloadedUtxoBalance(dbData: config.dataDb, address: tAddress, networkType: config.network.networkType) try? rustBackend.getCurrentAddress(
} dbData: config.dataDb,
} account: Int32(accountIndex),
networkType: config.network.networkType
extension CompactBlockProcessor { )
public func getUnifiedAddres(accountIndex: Int) -> UnifiedAddress? {
// 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 try? UnifiedAddress(encoding: address, network: self.config.network.networkType)
} }
public func getSaplingAddress(accountIndex: Int) -> SaplingAddress? { public func getSaplingAddress(accountIndex: Int) -> SaplingAddress? {
guard let zAddress = try? accountRepository.findBy(account: accountIndex)?.address else { getUnifiedAddress(accountIndex: accountIndex)?.saplingReceiver()
return nil
}
return try? SaplingAddress(encoding: zAddress, network: self.config.network.networkType)
} }
public func getTransparentAddress(accountIndex: Int) -> TransparentAddress? { public func getTransparentAddress(accountIndex: Int) -> TransparentAddress? {
guard let tAddress = try? accountRepository.findBy(account: accountIndex)?.transparentAddress else { return nil } getUnifiedAddress(accountIndex: accountIndex)?.transparentReceiver()
return TransparentAddress(validatedEncoding: tAddress)
} }
public func getTransparentBalance(accountIndex: Int) throws -> WalletBalance { public func getTransparentBalance(accountIndex: Int) throws -> WalletBalance {
guard let tAddress = try? accountRepository.findBy(account: accountIndex)?.transparentAddress else { guard accountIndex >= 0 else {
throw CompactBlockProcessorError.invalidAccount throw CompactBlockProcessorError.invalidAccount
} }
return try utxoCacheBalance(tAddress: tAddress)
return WalletBalance(
verified: Zatoshi(
try rustBackend.getVerifiedTransparentBalance(
dbData: config.dataDb,
account: Int32(accountIndex),
networkType: config.network.networkType)
),
total: Zatoshi(
try rustBackend.getTransparentBalance(
dbData: config.dataDb,
account: Int32(accountIndex),
networkType: config.network.networkType
)
)
)
} }
} }
extension CompactBlockProcessor { extension CompactBlockProcessor {
func refreshUTXOs(tAddress: String, startHeight: BlockHeight) async throws -> RefreshedUTXOs { func refreshUTXOs(tAddress: TransparentAddress, startHeight: BlockHeight) async throws -> RefreshedUTXOs {
let dataDb = self.config.dataDb let dataDb = self.config.dataDb
let stream: AsyncThrowingStream<UnspentTransactionOutputEntity, Error> = downloader.fetchUnspentTransactionOutputs(tAddress: tAddress, startHeight: startHeight) let stream: AsyncThrowingStream<UnspentTransactionOutputEntity, Error> = downloader.fetchUnspentTransactionOutputs(tAddress: tAddress.stringEncoded, startHeight: startHeight)
var utxos: [UnspentTransactionOutputEntity] = [] var utxos: [UnspentTransactionOutputEntity] = []
do { do {

View File

@ -17,9 +17,19 @@ extension CompactBlockProcessor {
try Task.checkCancellation() try Task.checkCancellation()
setState(.fetching) setState(.fetching)
do { do {
let tAddresses = try accountRepository.getAll().map({ $0.transparentAddress }) let tAddresses = try accountRepository.getAll()
.map { $0.account }
.map {
try rustBackend.listTransparentReceivers(
dbData: config.dataDb,
account: Int32($0),
networkType: config.network.networkType
)
}
.flatMap({ $0 })
do { do {
for tAddress in tAddresses { for tAddress in tAddresses {
guard try rustBackend.clearUtxos( guard try rustBackend.clearUtxos(
@ -28,19 +38,19 @@ extension CompactBlockProcessor {
sinceHeight: config.walletBirthday - 1, sinceHeight: config.walletBirthday - 1,
networkType: config.network.networkType networkType: config.network.networkType
) >= 0 else { ) >= 0 else {
throw rustBackend.lastError() ?? RustWeldingError.genericError(message: "attempted to clear utxos but -1 was returned") throw rustBackend.lastError() ?? .genericError(message: "clearUtxos failed. no error message available")
} }
} }
} catch { } catch {
throw FetchUTXOError.clearingFailed(error) throw FetchUTXOError.clearingFailed(error)
} }
var utxos: [UnspentTransactionOutputEntity] = [] var utxos: [UnspentTransactionOutputEntity] = []
let stream: AsyncThrowingStream<UnspentTransactionOutputEntity, Error> = downloader.fetchUnspentTransactionOutputs(tAddresses: tAddresses, startHeight: config.walletBirthday) let stream: AsyncThrowingStream<UnspentTransactionOutputEntity, Error> = downloader.fetchUnspentTransactionOutputs(tAddresses: tAddresses.map { $0.stringEncoded }, startHeight: config.walletBirthday)
for try await transaction in stream { for try await transaction in stream {
utxos.append(transaction) utxos.append(transaction)
} }
var refreshed: [UnspentTransactionOutputEntity] = [] var refreshed: [UnspentTransactionOutputEntity] = []
var skipped: [UnspentTransactionOutputEntity] = [] var skipped: [UnspentTransactionOutputEntity] = []
@ -62,13 +72,13 @@ extension CompactBlockProcessor {
} }
let result = (inserted: refreshed, skipped: skipped) let result = (inserted: refreshed, skipped: skipped)
NotificationCenter.default.post( NotificationCenter.default.post(
name: .blockProcessorStoredUTXOs, name: .blockProcessorStoredUTXOs,
object: self, object: self,
userInfo: [CompactBlockProcessorNotificationKey.refreshedUTXOs: result] userInfo: [CompactBlockProcessorNotificationKey.refreshedUTXOs: result]
) )
if Task.isCancelled { if Task.isCancelled {
LoggerProxy.debug("Warning: fetchUnspentTxOutputs on range \(range) cancelled") LoggerProxy.debug("Warning: fetchUnspentTxOutputs on range \(range) cancelled")
} else { } else {

View File

@ -10,41 +10,28 @@ import Foundation
protocol AccountEntity { protocol AccountEntity {
var account: Int { get set } var account: Int { get set }
var ufvk: String { get set } var ufvk: String { get set }
var address: String { get set }
var transparentAddress: String { get set }
} }
struct Account: AccountEntity, Encodable, Decodable { struct Account: AccountEntity, Encodable, Decodable {
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case account case account
case ufvk case ufvk
case address
case transparentAddress = "transparent_address"
} }
var account: Int var account: Int
var ufvk: String var ufvk: String
var address: String
var transparentAddress: String
} }
extension Account: Hashable { extension Account: Hashable {
func hash(into hasher: inout Hasher) { func hash(into hasher: inout Hasher) {
hasher.combine(account) hasher.combine(account)
hasher.combine(ufvk) hasher.combine(ufvk)
hasher.combine(address)
hasher.combine(transparentAddress)
} }
static func == (lhs: Self, rhs: Self) -> Bool { static func == (lhs: Self, rhs: Self) -> Bool {
guard guard
lhs.account == rhs.account, lhs.account == rhs.account,
lhs.ufvk == rhs.ufvk, lhs.ufvk == rhs.ufvk
lhs.address == rhs.address,
lhs.transparentAddress == rhs.transparentAddress
else { return false } else { return false }
return true return true
@ -54,7 +41,6 @@ extension Account: Hashable {
protocol AccountRepository { protocol AccountRepository {
func getAll() throws -> [AccountEntity] func getAll() throws -> [AccountEntity]
func findBy(account: Int) throws -> AccountEntity? func findBy(account: Int) throws -> AccountEntity?
func findBy(address: String) throws -> AccountEntity?
func update(_ account: AccountEntity) throws func update(_ account: AccountEntity) throws
} }
@ -63,9 +49,7 @@ import SQLite
class AccountSQDAO: AccountRepository { class AccountSQDAO: AccountRepository {
enum TableColums { enum TableColums {
static let account = Expression<Int>("account") static let account = Expression<Int>("account")
static let extfvk = Expression<String>("extfvk") static let extfvk = Expression<String>("ufvk")
static let address = Expression<String>("address")
static let transparentAddress = Expression<String>("transparent_address")
} }
let table = Table("accounts") let table = Table("accounts")
@ -89,11 +73,6 @@ class AccountSQDAO: AccountRepository {
return try dbProvider.connection().prepare(query).map({ try $0.decode() as Account }).first return try dbProvider.connection().prepare(query).map({ try $0.decode() as Account }).first
} }
func findBy(address: String) throws -> AccountEntity? {
let query = table.filter(TableColums.address == address).limit(1)
return try dbProvider.connection().prepare(query).map({ try $0.decode() as Account }).first
}
func update(_ account: AccountEntity) throws { func update(_ account: AccountEntity) throws {
guard let acc = account as? Account else { guard let acc = account as? Account else {
throw StorageError.updateFailed throw StorageError.updateFailed
@ -149,21 +128,7 @@ class CachingAccountDao: AccountRepository {
return acc return acc
} }
func findBy(address: String) throws -> AccountEntity? {
if !cache.isEmpty, let account = cache.values.first(where: { $0.address == address }) {
return account
}
guard let account = try dao.findBy(address: address) else {
return nil
}
cache[account.account] = account
return account
}
func update(_ account: AccountEntity) throws { func update(_ account: AccountEntity) throws {
try dao.update(account) try dao.update(account)
} }

View File

@ -179,21 +179,20 @@ public class Initializer {
self.walletBirthday = walletBirthday self.walletBirthday = walletBirthday
self.network = network self.network = network
} }
/**
Initialize the wallet with the given seed and return the related private keys for each
account specified or null if the wallet was previously initialized and block data exists on
disk. When this method returns null, that signals that the wallet will need to retrieve the
private keys from its own secure storage. In other words, the private keys are only given out
once for each set of database files. Subsequent calls to [initialize] will only load the Rust
library and return null.
'compactBlockCache.db' and 'transactionData.db' files are created by this function (if they
do not already exist). These files can be given a prefix for scenarios where multiple wallets
- Parameters: /// Initialize the wallet. The ZIP-32 seed bytes can optionally be passed to perform
- viewingKeys: Extended Full Viewing Keys to initialize the DBs with /// database migrations. most of the times the seed won't be needed. If they do and are
*/ /// not provided this will fail with `InitializationResult.seedRequired`. It could
/// be the case that this method is invoked by a wallet that does not contain the seed phrase
/// and is view-only, or by a wallet that does have the seed but the process does not have the
/// consent of the OS to fetch the keys from the secure storage, like on background tasks.
///
/// 'cache.db' and 'data.db' files are created by this function (if they
/// do not already exist). These files can be given a prefix for scenarios where multiple wallets
///
/// - Parameter seed: ZIP-32 Seed bytes for the wallet that will be initialized
/// - Throws: `InitializerError.dataDbInitFailed` if the creation of the dataDb fails
/// `InitializerError.accountInitFailed` if the account table can't be initialized.
public func initialize(with seed: [UInt8]?) throws -> InitializationResult { public func initialize(with seed: [UInt8]?) throws -> InitializationResult {
do { do {
try storage.createTable() try storage.createTable()
@ -245,26 +244,9 @@ public class Initializer {
} catch { } catch {
throw rustBackend.lastError() ?? InitializerError.accountInitFailed throw rustBackend.lastError() ?? InitializerError.accountInitFailed
} }
let migrationManager = MigrationManager(
cacheDbConnection: SimpleConnectionProvider(path: cacheDbURL.path),
pendingDbConnection: SimpleConnectionProvider(path: pendingDbURL.path),
networkType: self.network.networkType
)
try migrationManager.performMigration(ufvks: viewingKeys)
return .success return .success
} }
/**
get address from the given account index
- Parameter account: the index of the account
*/
public func getAddress(index account: Int = 0) -> String? {
try? accountRepository.findBy(account: account)?.address
}
/// get (unverified) balance from the given account index /// get (unverified) balance from the given account index
/// - Parameter account: the index of the account /// - Parameter account: the index of the account

View File

@ -14,6 +14,12 @@ public protocol StringEncoded {
var stringEncoded: String { get } var stringEncoded: String { get }
} }
public struct UnifiedSpendingKey: Equatable, Undescribable {
private(set) var network: NetworkType
var bytes: [UInt8]
public private(set) var account: UInt32
}
/// Sapling Extended Spending Key /// Sapling Extended Spending Key
public struct SaplingExtendedSpendingKey: Equatable, StringEncoded, Undescribable { public struct SaplingExtendedSpendingKey: Equatable, StringEncoded, Undescribable {
var encoding: String var encoding: String
@ -165,7 +171,6 @@ public struct UnifiedAddress: Equatable, StringEncoded {
} }
} }
var network: NetworkType
var encoding: String var encoding: String
public var stringEncoded: String { encoding } public var stringEncoded: String { encoding }
@ -181,7 +186,6 @@ public struct UnifiedAddress: Equatable, StringEncoded {
throw KeyEncodingError.invalidEncoding throw KeyEncodingError.invalidEncoding
} }
self.network = network
self.encoding = encoding self.encoding = encoding
} }
@ -190,7 +194,7 @@ public struct UnifiedAddress: Equatable, StringEncoded {
/// couldn't be extracted /// couldn't be extracted
public func availableReceiverTypecodes() throws -> [UnifiedAddress.ReceiverTypecodes] { public func availableReceiverTypecodes() throws -> [UnifiedAddress.ReceiverTypecodes] {
do { do {
return try DerivationTool(networkType: network).receiverTypecodesFromUnifiedAddress(self) return try DerivationTool.receiverTypecodesFromUnifiedAddress(self)
} catch { } catch {
throw Errors.couldNotExtractTypecodes throw Errors.couldNotExtractTypecodes
} }
@ -232,7 +236,6 @@ public enum Recipient: Equatable, StringEncoded {
} }
} }
public struct WalletBalance: Equatable { public struct WalletBalance: Equatable {
public var verified: Zatoshi public var verified: Zatoshi
public var total: Zatoshi public var total: Zatoshi

File diff suppressed because it is too large Load Diff

View File

@ -16,100 +16,318 @@ enum RustWeldingError: Error {
case malformedStringInput case malformedStringInput
case noConsensusBranchId(height: Int32) case noConsensusBranchId(height: Int32)
case unableToDeriveKeys case unableToDeriveKeys
case invalidInput(message: String)
case invalidRewind(suggestedHeight: Int32)
} }
enum ZcashRustBackendWeldingConstants { enum ZcashRustBackendWeldingConstants {
static let validChain: Int32 = -1 static let validChain: Int32 = -1
} }
/// Enumeration of potential return states for database initialization. If `seedRequired`
/** /// is returned, the caller must re-attempt initialization providing the seed
Enumeration of potential return states for database initialization. If `seedRequired` is returned, the caller must
re-attempt initialization providing the seed
*/
public enum DbInitResult { public enum DbInitResult {
case success case success
case seedRequired case seedRequired
} }
protocol ZcashRustBackendWelding { protocol ZcashRustBackendWelding {
/** /// clears the cached utxos for the given address from the specified height on for the
gets the latest error if available. Clear the existing error /// provided addresses. This will clear all UTXOs for the address from the database.
*/ /// if there are unspent funds, the balance will be zero after clearing up UTXOs,
/// needing to put them back again to restore the balance (if they weren't spent)
/// - Parameters:
/// - dbData: location of the data db file
/// - address: the address of the UTXO
/// - sinceheight: clear the UXTOs from that address on
/// - networkType: network type of this key
/// - Returns: the amount of UTXOs cleared or -1 on error
static func clearUtxos(
dbData: URL,
address: TransparentAddress,
sinceHeight: BlockHeight,
networkType: NetworkType
) throws -> Int32
/// Adds the next available account-level spend authority, given the current set of [ZIP 316]
/// account identifiers known, to the wallet database.
///
/// Returns the newly created [ZIP 316] account identifier, along with the binary encoding of the
/// [`UnifiedSpendingKey`] for the newly created account. The caller should manage the memory of
/// (and store) the returned spending keys in a secure fashion.
///
/// If `seed` was imported from a backup and this method is being used to restore a
/// previous wallet state, you should use this method to add all of the desired
/// accounts before scanning the chain from the seed's birthday height.
///
/// By convention, wallets should only allow a new account to be generated after funds
/// have been received by the currently-available account (in order to enable
/// automated account recovery).
/// - Parameters:
/// - dbData: location of the data db
/// - seed: byte array of the zip32 seed
/// - networkType: network type of this key
/// - Returns: The `UnifiedSpendingKey` structs for the number of accounts created
///
static func createAccount(
dbData: URL,
seed: [UInt8],
networkType: NetworkType
) throws -> UnifiedSpendingKey
/// Creates a transaction to the given address from the given account
/// - Parameter dbData: URL for the Data DB
/// - Parameter usk: `UnifiedSpendingKey` for the account that controls the funds to be spent.
/// - Parameter to: recipient address
/// - Parameter value: transaction amount in Zatoshi
/// - Parameter memo: the `Memo` for this transaction
/// - Parameter spendParamsPath: path escaped String for the filesystem locations where the spend parameters are located
/// - Parameter outputParamsPath: path escaped String for the filesystem locations where the output parameters are located
/// - Parameter networkType: network type of this key
static func createToAddress(
dbData: URL,
usk: UnifiedSpendingKey,
to address: String,
value: Int64,
memo: MemoBytes,
spendParamsPath: String,
outputParamsPath: String,
networkType: NetworkType
) -> Int64 // swiftlint:disable function_parameter_count
/// Scans a transaction for any information that can be decrypted by the accounts in the
/// wallet, and saves it to the wallet.
///
/// - Parameters:
/// - dbData: location of the data db file
/// - tx: the transaction to decrypt
/// - minedHeight: height on which this transaction was mined. this is used to fetch the consensus branch ID.
/// - networkType: network type of this key
/// returns false if fails to decrypt.
static func decryptAndStoreTransaction(
dbData: URL,
txBytes: [UInt8],
minedHeight: Int32,
networkType: NetworkType
) -> Bool
/// Derives and returns a unified spending key from the given seed for the given account ID.
/// Returns the binary encoding of the spending key. The caller should manage the memory of (and store, if necessary) the returned spending key in a secure fashion.
/// - Parameter seed: a Byte Array with the seed
/// - Parameter accountIndex:account index that the key can spend from
/// - Parameter networkType: network type of this key
/// - Throws `.unableToDerive` when there's an error
static func deriveUnifiedSpendingKey(
from seed: [UInt8],
accountIndex: Int32,
networkType: NetworkType
) throws -> UnifiedSpendingKey
/// get the (unverified) balance from the given account
/// - Parameters:
/// - dbData: location of the data db
/// - account: index of the given account
/// - networkType: network type of this key
static func getBalance(
dbData: URL,
account: Int32,
networkType: NetworkType
) -> Int64
/// Returns the most-recently-generated unified payment address for the specified account.
/// - Parameters:
/// - dbData: location of the data db
/// - account: index of the given account
/// - networkType: network type of this key
static func getCurrentAddress(
dbData: URL,
account: Int32,
networkType: NetworkType
) throws -> UnifiedAddress
/// Wallets might need to be rewound because of a reorg, or by user request.
/// There are times where the wallet could get out of sync for many reasons and
/// users might be asked to rescan their wallets in order to fix that. This function
/// returns the nearest height where a rewind is possible. Currently pruning gets rid
/// of sapling witnesses older than 100 blocks. So in order to reconstruct the witness
/// tree that allows to spend notes from the given wallet the rewind can't be more than
/// 100 blocks or back to the oldest unspent note that this wallet contains.
/// - Parameters:
/// - dbData: location of the data db file
/// - height: height you would like to rewind to.
/// - networkType: network type of this key]
/// - Returns: the blockheight of the nearest rewind height.
///
static func getNearestRewindHeight(
dbData: URL,
height: Int32,
networkType: NetworkType
) -> Int32
/// Returns a newly-generated unified payment address for the specified account, with the next available diversifier.
/// - Parameters:
/// - dbData: location of the data db
/// - account: index of the given account
/// - networkType: network type of this key
static func getNextAvailableAddress(
dbData: URL,
account: Int32,
networkType: NetworkType
) throws -> UnifiedAddress
/// get received memo from note
/// - Parameters:
/// - dbData: location of the data db file
/// - idNote: note_id of note where the memo is located
/// - networkType: network type of this key
@available(*, deprecated, message: "This function will be deprecated soon. Use `getReceivedMemo(dbData:idNote:networkType)` instead")
static func getReceivedMemoAsUTF8(
dbData: URL,
idNote: Int64,
networkType: NetworkType
) -> String?
/// get received memo from note
/// - Parameters:
/// - dbData: location of the data db file
/// - idNote: note_id of note where the memo is located
/// - networkType: network type of this key
static func getReceivedMemo(
dbData: URL,
idNote: Int64,
networkType: NetworkType
) -> Memo?
/// Returns the Sapling receiver within the given Unified Address, if any.
/// - Parameter uAddr: a `UnifiedAddress`
/// - Returns a `SaplingAddress` if any
/// - Throws `receiverNotFound` when the receiver is not found. `invalidUnifiedAddress` if the UA provided is not valid
static func getSaplingReceiver(for uAddr: UnifiedAddress) throws -> SaplingAddress?
/// get sent memo from note
/// - Parameters:
/// - dbData: location of the data db file
/// - idNote: note_id of note where the memo is located
/// - networkType: network type of this key
@available(*, deprecated, message: "This function will be deprecated soon. Use `getSentMemo(dbData:idNote:networkType)` instead")
static func getSentMemoAsUTF8(
dbData: URL,
idNote: Int64,
networkType: NetworkType
) -> String?
/// get sent memo from note
/// - Parameters:
/// - dbData: location of the data db file
/// - idNote: note_id of note where the memo is located
/// - networkType: network type of this key
/// - Returns: a `Memo` if any
static func getSentMemo(
dbData: URL,
idNote: Int64,
networkType: NetworkType
) -> Memo?
/// Get the verified cached transparent balance for the given address
/// - Parameters:
/// - dbData: location of the data db file
/// - account; the account index to query
/// - networkType: network type of this key
static func getTransparentBalance(
dbData: URL,
account: Int32,
networkType: NetworkType
) throws -> Int64
/// Returns the transparent receiver within the given Unified Address, if any.
// - Parameter uAddr: a `UnifiedAddress`
/// - Returns a `TransparentAddress` if any
/// - Throws `receiverNotFound` when the receiver is not found. `invalidUnifiedAddress` if the UA provided is not valid
static func getTransparentReceiver(for uAddr: UnifiedAddress) throws -> TransparentAddress?
/// gets the latest error if available. Clear the existing error
/// - Returns a `RustWeldingError` if exists
static func lastError() -> RustWeldingError? static func lastError() -> RustWeldingError?
/** /// gets the latest error message from librustzcash. Does not clear existing error
gets the latest error message from librustzcash. Does not clear existing error
*/
static func getLastError() -> String? static func getLastError() -> String?
/** /// initialize the accounts table from a set of unified full viewing keys
initializes the data db /// - Note: this function should only be used when restoring an existing seed phrase.
- Parameter dbData: location of the data db sql file /// when creating a new wallet, use `createAccount()` instead
*/ /// - Parameter dbData: location of the data db
static func initDataDb(dbData: URL, seed: [UInt8]?, networkType: NetworkType) throws -> DbInitResult /// - Parameter ufvks: an array of UnifiedFullViewingKeys
/// - Parameter networkType: network type of this key
/** static func initAccountsTable(
- Returns: true when the address is valid. Returns false in any other case dbData: URL,
- Throws: Error when the provided address belongs to another network ufvks: [UnifiedFullViewingKey],
*/ networkType: NetworkType
static func isValidSaplingAddress(_ address: String, networkType: NetworkType) throws -> Bool ) 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. /// initializes the data db. This will performs any migrations needed on the sqlite file
/// - Returns: true when the address is valid and transparent. false in any other case /// provided. Some migrations might need that callers provide the seed bytes.
/// - Parameter dbData: location of the data db sql file
/// - Parameter seed: ZIP-32 compliant seed bytes for this wallet
/// - Parameter networkType: network type of this key
/// - Returns: `DbInitResult.success` if the dataDb was initialized successfully
/// or `DbInitResult.seedRequired` if the operation requires the seed to be passed
/// in order to be completed successfully.
static func initDataDb(
dbData: URL,
seed: [UInt8]?,
networkType: NetworkType
) throws -> DbInitResult
/// Validates the if the given string is a valid Sapling Address
/// - Parameter address: UTF-8 encoded String to validate
/// - Parameter networkType: network type of this key
/// - Returns: true when the address is valid. Returns false in any other case
/// - Throws: Error when the provided address belongs to another network /// - Throws: Error when the provided address belongs to another network
static func isValidUnifiedAddress(_ address: String, networkType: NetworkType) throws -> Bool static func isValidSaplingAddress(_ address: String, networkType: NetworkType) -> Bool
/**
- 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 isValidSaplingExtendedFullViewingKey(_ key: String, networkType: NetworkType) throws -> Bool
/// Validates the if the given string is a valid Sapling Extended Full Viewing Key
/// - Parameter key: UTF-8 encoded String to validate
/// - Parameter networkType: network type of this key
/// - 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 isValidSaplingExtendedFullViewingKey(_ key: String, networkType: NetworkType) -> Bool
/// Validates the if the given string is a valid Sapling Extended Spending Key
/// - Returns: `true` when the Sapling Extended Spending Key is valid, false in any other case. /// - 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 /// - Throws: Error when the key is semantically valid but it belongs to another network
/// - parameter key: String encoded Extendeed Spending Key /// - parameter key: String encoded Extendeed Spending Key
/// - parameter networkType: `NetworkType` signaling testnet or mainnet /// - parameter networkType: `NetworkType` signaling testnet or mainnet
static func isValidSaplingExtendedSpendingKey(_ key: String, networkType: NetworkType) throws -> Bool static func isValidSaplingExtendedSpendingKey(_ key: String, networkType: NetworkType) -> Bool
/** /// Validates the if the given string is a valid Transparent Address
- Returns: true when the encoded string is a valid UFVK. false in any other case /// - Parameter address: UTF-8 encoded String to validate
- Throws: Error when there's another problem not related to validity of the string in question /// - Parameter networkType: network type of this key
*/ /// - Returns: true when the address is valid and transparent. false in any other case
static func isValidUnifiedFullViewingKey(_ ufvk: String, networkType: NetworkType) throws -> Bool /// - Throws: Error when the provided address belongs to another network
static func isValidTransparentAddress(_ address: String, networkType: NetworkType) -> Bool
/** /// validates whether a string encoded address is a valid Unified Address.
initialize the accounts table from a given seed and a number of accounts /// - Parameter address: UTF-8 encoded String to validate
- Parameters: /// - Parameter networkType: network type of this key
- dbData: location of the data db /// - Returns: true when the address is valid and transparent. false in any other case
- seed: byte array of the zip32 seed /// - Throws: Error when the provided address belongs to another network
- accounts: how many accounts you want to have static func isValidUnifiedAddress(_ address: String, networkType: NetworkType) -> Bool
*/
static func initAccountsTable(dbData: URL, seed: [UInt8], accounts: Int32, networkType: NetworkType) -> [SaplingExtendedSpendingKey]?
/**
initialize the accounts table from a set of unified full viewing keys
- Parameters:
- dbData: location of the data db
- ufvks: an array of UnifiedFullViewingKeys
*/
static func initAccountsTable(dbData: URL, ufvks: [UnifiedFullViewingKey], networkType: NetworkType) throws -> Bool
/** /// verifies that the given string-encoded `UnifiedFullViewingKey` is valid.
initialize the blocks table from a given checkpoint (birthday) /// - Parameter ufvk: UTF-8 encoded String to validate
- Parameters: /// - Parameter networkType: network type of this key
- dbData: location of the data db /// - Returns: true when the encoded string is a valid UFVK. false in any other case
- height: represents the block height of the given checkpoint /// - Throws: Error when there's another problem not related to validity of the string in question
- hash: hash of the merkle tree static func isValidUnifiedFullViewingKey(_ ufvk: String, networkType: NetworkType) -> Bool
- time: in milliseconds from reference
- saplingTree: hash of the sapling tree /// initialize the blocks table from a given checkpoint (heigh, hash, time, saplingTree and networkType)
*/ /// - Parameters:
// swiftlint:disable function_parameter_count /// - dbData: location of the data db
/// - height: represents the block height of the given checkpoint
/// - hash: hash of the merkle tree
/// - time: in milliseconds from reference
/// - saplingTree: hash of the sapling tree
/// - networkType: `NetworkType` signaling testnet or mainnet
static func initBlocksTable( static func initBlocksTable(
dbData: URL, dbData: URL,
height: Int32, height: Int32,
@ -117,142 +335,113 @@ protocol ZcashRustBackendWelding {
time: UInt32, time: UInt32,
saplingTree: String, saplingTree: String,
networkType: NetworkType networkType: NetworkType
) throws ) throws // swiftlint:disable function_parameter_count
/** /// Returns a list of the transparent receivers for the diversified unified addresses that have
gets the address from data db from the given account /// been allocated for the provided account.
- Parameters: /// - Parameters:
- dbData: location of the data db /// - dbData: location of the data db
- account: index of the given account /// - account: index of the given account
- Returns: an optional string with the address if found /// - networkType: the network type
*/ static func listTransparentReceivers(
static func getAddress(dbData: URL, account: Int32, networkType: NetworkType) -> String? dbData: URL,
account: Int32,
networkType: NetworkType
) throws -> [TransparentAddress]
/** /// get the verified balance from the given account
get the (unverified) balance from the given account /// - Parameters:
- Parameters: /// - dbData: location of the data db
- dbData: location of the data db /// - account: index of the given account
- account: index of the given account /// - networkType: the network type
*/ static func getVerifiedBalance(
static func getBalance(dbData: URL, account: Int32, networkType: NetworkType) -> Int64 dbData: URL,
account: Int32,
networkType: NetworkType
) -> Int64
/**
get the verified balance from the given account /// Get the verified cached transparent balance for the given account
- Parameters: /// - Parameters:
- dbData: location of the data db /// - dbData: location of the data db
- account: index of the given account /// - account: account index to query the balance for.
*/ /// - networkType: the network type
static func getVerifiedBalance(dbData: URL, account: Int32, networkType: NetworkType) -> Int64 static func getVerifiedTransparentBalance(
dbData: URL,
account: Int32,
networkType: NetworkType
) throws -> Int64
/// Checks that the scanned blocks in the data database, when combined with the recent
/// `CompactBlock`s in the cache database, form a valid chain.
/// This function is built on the core assumption that the information provided in the
/// cache database is more likely to be accurate than the previously-scanned information.
/// This follows from the design (and trust) assumption that the `lightwalletd` server
/// provides accurate block information as of the time it was requested.
/// - Parameters:
/// - dbCache: location of the cache db file
/// - dbData: location of the data db file
/// - networkType: the network type
/// - Returns:
/// - `-1` if the combined chain is valid.
/// - `upper_bound` if the combined chain is invalid.
/// - `upper_bound` is the height of the highest invalid block (on the assumption that the highest block in the cache database is correct).
/// - `0` if there was an error during validation unrelated to chain validity.
/// - Important: This function does not mutate either of the databases.
static func validateCombinedChain(
dbCache: URL,
dbData: URL,
networkType: NetworkType
) -> Int32
/// Resets the state of the database to only contain block and transaction information up to the given height. clears up all derived data as well
/// - Parameters:
/// - dbData: location of the data db file
/// - height: height to rewind to. DON'T PASS ARBITRARY HEIGHT. Use getNearestRewindHeight when unsure
/// - networkType: the network type
static func rewindToHeight(
dbData: URL,
height: Int32,
networkType: NetworkType
) -> Bool
/**
Get the verified cached transparent balance for the given address
*/
static func getVerifiedTransparentBalance(dbData: URL, address: String, networkType: NetworkType) throws -> Int64
/**
Get the verified cached transparent balance for the given address
*/
static func getTransparentBalance(dbData: URL, address: String, networkType: NetworkType) throws -> Int64
/** /// Scans new blocks added to the cache for any transactions received by the tracked
get received memo from note /// accounts.
- Parameters: /// This function pays attention only to cached blocks with heights greater than the
- dbData: location of the data db file /// highest scanned block in `db_data`. Cached blocks with lower heights are not verified
- idNote: note_id of note where the memo is located /// against previously-scanned blocks. In particular, this function **assumes** that the
*/ /// caller is handling rollbacks.
@available(*, deprecated, message: "This function will be deprecated soon. Use `getReceivedMemo(dbData:idNote:networkType)` instead") /// For brand-new light client databases, this function starts scanning from the Sapling
static func getReceivedMemoAsUTF8(dbData: URL, idNote: Int64, networkType: NetworkType) -> String? /// activation height. This height can be fast-forwarded to a more recent block by calling
/// [`initBlocksTable`] before this function.
/// Scanned blocks are required to be height-sequential. If a block is missing from the
/// cache, an error will be signalled.
///
/// - Parameters:
/// - dbCache: location of the compact block cache db
/// - dbData: location of the data db file
/// - limit: scan up to limit blocks. pass 0 to set no limit.
/// - networkType: the network type
/// returns false if fails to scan.
static func scanBlocks(
dbCache: URL,
dbData: URL,
limit: UInt32,
networkType: NetworkType
) -> Bool
/**
get received memo from note
- Parameters:
- dbData: location of the data db file
- idNote: note_id of note where the memo is located
*/
static func getReceivedMemo(dbData: URL, idNote: Int64, networkType: NetworkType) -> Memo?
/**
get sent memo from note
- Parameters:
- dbData: location of the data db file
- idNote: note_id of note where the memo is located
*/
@available(*, deprecated, message: "This function will be deprecated soon. Use `getSentMemo(dbData:idNote:networkType)` instead")
static func getSentMemoAsUTF8(dbData: URL, idNote: Int64, networkType: NetworkType) -> String?
/** /// puts a UTXO into the data db database
get sent memo from note /// - Parameters:
- Parameters: /// - dbData: location of the data db file
- dbData: location of the data db file /// - txid: the txid bytes for the UTXO
- idNote: note_id of note where the memo is located /// - index: the index of the UTXO
*/ /// - script: the script of the UTXO
static func getSentMemo(dbData: URL, idNote: Int64, networkType: NetworkType) -> Memo? /// - value: the value of the UTXO
/// - height: the mined height for the UTXO
/** /// - networkType: the network type
Checks that the scanned blocks in the data database, when combined with the recent /// - Returns: true if the operation succeded or false otherwise
`CompactBlock`s in the cache database, form a valid chain.
This function is built on the core assumption that the information provided in the
cache database is more likely to be accurate than the previously-scanned information.
This follows from the design (and trust) assumption that the `lightwalletd` server
provides accurate block information as of the time it was requested.
- Returns:
* `-1` if the combined chain is valid.
* `upper_bound` if the combined chain is invalid.
* `upper_bound` is the height of the highest invalid block (on the assumption that the highest block in the cache database is correct).
* `0` if there was an error during validation unrelated to chain validity.
- Important: This function does not mutate either of the databases.
*/
static func validateCombinedChain(dbCache: URL, dbData: URL, networkType: NetworkType) -> Int32
/**
Returns the nearest height where a rewind is possible. Currently prunning gets rid of sapling witnesses older
than 100 blocks. So in order to reconstruct the witness tree that allows to spend notes from the given wallet
the rewind can't be more than 100 block or back to the oldest unspent note that this wallet contains.
- Parameters:
- dbData: location of the data db file
- height: height you would like to rewind to.
*/
static func getNearestRewindHeight(dbData: URL, height: Int32, networkType: NetworkType) -> Int32
/**
rewinds the compact block storage to the given height. clears up all derived data as well
- Parameters:
- dbData: location of the data db file
- height: height to rewind to. DON'T PASS ARBITRARY HEIGHT. Use getNearestRewindHeight when unsure
*/
static func rewindToHeight(dbData: URL, height: Int32, networkType: NetworkType) -> Bool
/**
Scans new blocks added to the cache for any transactions received by the tracked
accounts.
This function pays attention only to cached blocks with heights greater than the
highest scanned block in `db_data`. Cached blocks with lower heights are not verified
against previously-scanned blocks. In particular, this function **assumes** that the
caller is handling rollbacks.
For brand-new light client databases, this function starts scanning from the Sapling
activation height. This height can be fast-forwarded to a more recent block by calling
[`zcashlc_init_blocks_table`] before this function.
Scanned blocks are required to be height-sequential. If a block is missing from the
cache, an error will be signalled.
- Parameters:
- dbCache: location of the compact block cache db
- dbData: location of the data db file
- limit: scan up to limit blocks. pass 0 to set no limit.
returns false if fails to scan.
*/
static func scanBlocks(dbCache: URL, dbData: URL, limit: UInt32, networkType: NetworkType) -> Bool
/**
puts a UTXO into the data db database
- Parameters:
- dbData: location of the data db file
- txid: the txid bytes for the UTXO
- index: the index of the UTXO
- value: the value of the UTXO
- height: the mined height for the UTXO
- Returns: true if the operation succeded or false otherwise
*/
static func putUnspentTransparentOutput( static func putUnspentTransparentOutput(
dbData: URL, dbData: URL,
txid: [UInt8], txid: [UInt8],
@ -262,176 +451,48 @@ protocol ZcashRustBackendWelding {
height: BlockHeight, height: BlockHeight,
networkType: NetworkType networkType: NetworkType
) throws -> Bool ) throws -> Bool
/**
clears the cached utxos for the given address from the specified height on
- Parameters:
- dbData: location of the data db file
- address: the address of the UTXO
- sinceheight: clear the UXTOs from that address on
- Returns: the amount of UTXOs cleared or -1 on error
*/
static func clearUtxos(dbData: URL, address: String, sinceHeight: BlockHeight, networkType: NetworkType) throws -> Int32
/** /// Creates a transaction to shield all found UTXOs in cache db for the account the provided `UnifiedSpendingKey` has spend authority for.
Gets the balance of the previously downloaded UTXOs /// - Parameter dbCache: URL for the Cache DB
- Parameters: /// - Parameter dbData: URL for the Data DB
- dbData: location of the data db file /// - Parameter usk: `UnifiedSpendingKey` that spend transparent funds and where the funds will be shielded to.
- address: the address of the UTXO /// - Parameter memo: the `Memo` for this transaction
- Returns: the wallet balance containing verified and total balance. /// - Parameter spendParamsPath: path escaped String for the filesystem locations where the spend parameters are located
- Throws: Rustwelding Error if something fails /// - Parameter outputParamsPath: path escaped String for the filesystem locations where the output parameters are located
*/ /// - Parameter networkType: the network type
static func downloadedUtxoBalance(dbData: URL, address: String, networkType: NetworkType) throws -> WalletBalance
/**
Scans a transaction for any information that can be decrypted by the accounts in the
wallet, and saves it to the wallet.
- Parameters:
- dbData: location of the data db file
- tx: the transaction to decrypt
- minedHeight: height on which this transaction was mined. this is used to fetch the consensus branch ID.
returns false if fails to decrypt.
*/
static func decryptAndStoreTransaction(
dbData: URL,
txBytes: [UInt8],
minedHeight: Int32,
networkType: NetworkType
) -> Bool
/**
Creates a transaction to the given address from the given account
- Parameters:
- dbData: URL for the Data DB
- account: the account index that will originate the transaction
- extsk: extended spending key string
- to: recipient address
- value: transaction amount in Zatoshi
- 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
*/
// swiftlint:disable function_parameter_count
static func createToAddress(
dbData: URL,
account: Int32,
extsk: String,
to address: String,
value: Int64,
memo: MemoBytes,
spendParamsPath: String,
outputParamsPath: String,
networkType: NetworkType
) -> Int64
/**
Creates a transaction to shield all found UTXOs in cache db.
- Parameters:
- 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 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
*/
// swiftlint:disable function_parameter_count
static func shieldFunds( static func shieldFunds(
dbCache: URL, dbCache: URL,
dbData: URL, dbData: URL,
account: Int32, usk: UnifiedSpendingKey,
xprv: String,
memo: MemoBytes, memo: MemoBytes,
spendParamsPath: String, spendParamsPath: String,
outputParamsPath: String, outputParamsPath: String,
networkType: NetworkType networkType: NetworkType
) -> Int64 ) -> Int64 // swiftlint:disable function_parameter_count
/**
Derives a full viewing key from a seed
- Parameter spendingKey: a string containing the spending key
- Returns: the derived key
- Throws: RustBackendError if fatal error occurs
*/
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
- Parameter accounts: the number of accounts you want to derive from this seed
- Returns: an array containing the derived keys
- Throws: RustBackendError if fatal error occurs
*/
static func deriveSaplingExtendedFullViewingKeys(seed: [UInt8], accounts: Int32, networkType: NetworkType) throws -> [SaplingExtendedFullViewingKey]?
/**
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 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 Unified Address
- Throws: RustBackendError if fatal error occurs
*/
static func deriveUnifiedAddressFromSeed(seed: [UInt8], accountIndex: Int32, networkType: NetworkType) throws -> String?
/**
Derives a unified address from a Unified Full Viewing Key
- Parameter ufvk: a string containing the extended full viewing key
- Returns: an optional String containing the Shielded address
- Throws: RustBackendError if fatal error occurs
*/
static func deriveUnifiedAddressFromViewingKey(_ ufvk: String, networkType: NetworkType) throws -> String?
/// 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?
/**
Derives a transparent account private key from Seed
- Parameter seed: an array of bytes containing the seed
- Returns: an optional String containing the transparent secret (private) key
*/
static func deriveTransparentAccountPrivateKeyFromSeed(seed: [UInt8], account: Int, networkType: NetworkType) throws -> String?
/**
Derives a transparent address from a secret key
- Parameter tsk: a hex string containing the Secret Key
- Returns: an optional String containing the transparent address.
*/
static func deriveTransparentAddressFromAccountPrivateKey(_ tsk: String, index: Int, networkType: NetworkType) throws -> String?
/**
Derives a tranparent address from a public key
- Parameter pubkey: public key represented as a string
*/
static func derivedTransparentAddressFromPublicKey(_ pubkey: String, networkType: NetworkType) throws -> String
static func deriveUnifiedFullViewingKeyFromSeed(_ seed: [UInt8], numberOfAccounts: Int32, networkType: NetworkType) throws -> [UnifiedFullViewingKey]
/// Obtains the available receiver typecodes for the given String encoded Unified Address /// Obtains the available receiver typecodes for the given String encoded Unified Address
/// - Parameter address: public key represented as a String /// - Parameter address: public key represented as a String
/// - Returns the `[UInt32]` that compose the given UA /// - Returns the `[UInt32]` that compose the given UA
/// - Throws `RustWeldingError.malformedStringInput` when the UA is either invalid or malformed /// - Throws `RustWeldingError.invalidInput(message: String)` when the UA is either invalid or malformed
static func receiverTypecodesOnUnifiedAddress(_ address: String) throws -> [UInt32] static func receiverTypecodesOnUnifiedAddress(_ address: String) throws -> [UInt32]
/**
Gets the consensus branch id for the given height /// Gets the consensus branch id for the given height
- Parameter height: the height you what to know the branch id for /// - Parameter height: the height you what to know the branch id for
*/ /// - Parameter networkType: the network type
static func consensusBranchIdFor(height: Int32, networkType: NetworkType) throws -> Int32 static func consensusBranchIdFor(
height: Int32,
networkType: NetworkType
) throws -> Int32
/// Derives a `UnifiedFullViewingKey` from a `UnifiedSpendingKey`
/// - Parameter spendingKey: the `UnifiedSpendingKey` to derive from
/// - Parameter networkType: the network type
/// - Throws: `RustWeldingError.unableToDeriveKeys` if the SDK couldn't derive the UFVK.
/// - Returns: the derived `UnifiedFullViewingKey`
static func deriveUnifiedFullViewingKey(
from spendingKey: UnifiedSpendingKey,
networkType: NetworkType
) throws -> UnifiedFullViewingKey
} }

View File

@ -106,27 +106,24 @@ public protocol Synchronizer {
func getTransparentAddress(accountIndex: Int) -> TransparentAddress? func getTransparentAddress(accountIndex: Int) -> TransparentAddress?
/// Sends zatoshi. /// Sends zatoshi.
/// - Parameter spendingKey: the key that allows spends to occur. /// - Parameter spendingKey: the `UnifiedSpendingKey` that allows spends to occur.
/// - Parameter zatoshi: the amount to send in Zatoshi. /// - Parameter zatoshi: the amount to send in Zatoshi.
/// - Parameter toAddress: the recipient's address. /// - Parameter toAddress: the recipient's address.
/// - Parameter memo: the memo to include as part of the transaction. /// - Parameter memo: the memo to include as part of the transaction.
/// - Parameter accountIndex: the optional account id to use. By default, the first account is used. // swiftlint:disable:next function_parameter_count
func sendToAddress( func sendToAddress(
spendingKey: SaplingExtendedSpendingKey, spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi, zatoshi: Zatoshi,
toAddress: Recipient, toAddress: Recipient,
memo: Memo, memo: Memo
from accountIndex: Int
) async throws -> PendingTransactionEntity ) async throws -> PendingTransactionEntity
/// Shields transparent funds from the given private key into the best shielded pool of the given account. /// Shields transparent funds from the given private key into the best shielded pool of the account associated to the given `UnifiedSpendingKey`.
/// - Parameter transparentAccountPrivateKey: the key that allows to spend transparent funds /// - Parameter spendingKey: the `UnifiedSpendingKey` that allows to spend transparent funds
/// - Parameter memo: the optional memo to include as part of the transaction. /// - 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( func shieldFunds(
transparentAccountPrivateKey: TransparentAccountPrivKey, spendingKey: UnifiedSpendingKey,
memo: Memo, memo: Memo
from accountIndex: Int
) async throws -> PendingTransactionEntity ) async throws -> PendingTransactionEntity
/// Attempts to cancel a transaction that is about to be sent. Typically, cancellation is only /// Attempts to cancel a transaction that is about to be sent. Typically, cancellation is only
@ -150,7 +147,6 @@ public protocol Synchronizer {
/// A repository serving transactions in a paginated manner /// A repository serving transactions in a paginated manner
/// - Parameter kind: Transaction Kind expected from this PaginatedTransactionRepository /// - Parameter kind: Transaction Kind expected from this PaginatedTransactionRepository
func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository
/// Returns a list of confirmed transactions that preceed the given transaction with a limit count. /// Returns a list of confirmed transactions that preceed the given transaction with a limit count.
/// - Parameters: /// - Parameters:
@ -165,14 +161,13 @@ public protocol Synchronizer {
/// Returns the latest block height from the provided Lightwallet endpoint /// Returns the latest block height from the provided Lightwallet endpoint
func latestHeight(result: @escaping (Result<BlockHeight, Error>) -> Void) func latestHeight(result: @escaping (Result<BlockHeight, Error>) -> Void)
/// Returns the latest block height from the provided Lightwallet endpoint /// Returns the latest block height from the provided Lightwallet endpoint
/// Blocking /// Blocking
func latestHeight() throws -> BlockHeight func latestHeight() throws -> BlockHeight
/// Returns the latests UTXOs for the given address from the specified height on /// Returns the latests UTXOs for the given address from the specified height on
func refreshUTXOs(address: String, from height: BlockHeight) async throws -> RefreshedUTXOs func refreshUTXOs(address: TransparentAddress, from height: BlockHeight) async throws -> RefreshedUTXOs
/// Returns the last stored transparent balance /// Returns the last stored transparent balance
func getTransparentBalance(accountIndex: Int) throws -> WalletBalance func getTransparentBalance(accountIndex: Int) throws -> WalletBalance
@ -191,7 +186,7 @@ public protocol Synchronizer {
/// Returns the shielded verified balance (anchor is 10 blocks back) /// Returns the shielded verified balance (anchor is 10 blocks back)
func getShieldedVerifiedBalance(accountIndex: Int) -> Zatoshi func getShieldedVerifiedBalance(accountIndex: Int) -> Zatoshi
/// Stops the synchronizer and rescans the known blocks with the current keys. /// Stops the synchronizer and rescans the known blocks with the current keys.
/// - Parameter policy: the rewind policy /// - Parameter policy: the rewind policy

View File

@ -458,11 +458,10 @@ public class SDKSynchronizer: Synchronizer {
// MARK: Synchronizer methods // MARK: Synchronizer methods
public func sendToAddress( public func sendToAddress(
spendingKey: SaplingExtendedSpendingKey, spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi, zatoshi: Zatoshi,
toAddress: Recipient, toAddress: Recipient,
memo: Memo, memo: Memo
from accountIndex: Int
) async throws -> PendingTransactionEntity { ) async throws -> PendingTransactionEntity {
do { do {
try await initializer.downloadParametersIfNeeded() try await initializer.downloadParametersIfNeeded()
@ -474,23 +473,19 @@ public class SDKSynchronizer: Synchronizer {
spendingKey: spendingKey, spendingKey: spendingKey,
zatoshi: zatoshi, zatoshi: zatoshi,
toAddress: toAddress.stringEncoded, toAddress: toAddress.stringEncoded,
memo: memo, memo: memo
from: accountIndex
) )
} }
public func shieldFunds( public func shieldFunds(
transparentAccountPrivateKey: TransparentAccountPrivKey, spendingKey: UnifiedSpendingKey,
memo: Memo, memo: Memo
from accountIndex: Int
) async throws -> PendingTransactionEntity { ) async throws -> PendingTransactionEntity {
// let's see if there are funds to shield // let's see if there are funds to shield
let derivationTool = DerivationTool(networkType: self.network.networkType) let accountIndex = Int(spendingKey.account)
do { do {
let tAddr = try derivationTool.deriveTransparentAddressFromAccountPrivateKey(transparentAccountPrivateKey, index: 0)
let tBalance = try utxoRepository.balance(address: tAddr.stringEncoded, latestHeight: self.latestDownloadedHeight()) let tBalance = try self.getTransparentBalance(accountIndex: accountIndex)
// 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 { guard tBalance.verified >= self.network.constants.defaultFee(for: self.latestScannedHeight) else {
@ -507,7 +502,7 @@ public class SDKSynchronizer: Synchronizer {
// TODO: Task will be removed when this method is changed to async, issue 487, https://github.com/zcash/ZcashLightClientKit/issues/487 // TODO: Task will be removed when this method is changed to async, issue 487, https://github.com/zcash/ZcashLightClientKit/issues/487
let transaction = try await transactionManager.encodeShieldingTransaction( let transaction = try await transactionManager.encodeShieldingTransaction(
xprv: transparentAccountPrivateKey, spendingKey: spendingKey,
pendingTransaction: shieldingSpend pendingTransaction: shieldingSpend
) )
@ -518,18 +513,17 @@ public class SDKSynchronizer: Synchronizer {
} }
func createToAddress( func createToAddress(
spendingKey: SaplingExtendedSpendingKey, spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi, zatoshi: Zatoshi,
toAddress: String, toAddress: String,
memo: Memo, memo: Memo
from accountIndex: Int
) async throws -> PendingTransactionEntity { ) async throws -> PendingTransactionEntity {
do { do {
let spend = try transactionManager.initSpend( let spend = try transactionManager.initSpend(
zatoshi: zatoshi, zatoshi: zatoshi,
toAddress: toAddress, toAddress: toAddress,
memo: memo.asMemoBytes(), memo: memo.asMemoBytes(),
from: accountIndex from: Int(spendingKey.account)
) )
let transaction = try await transactionManager.encode( let transaction = try await transactionManager.encode(
@ -596,7 +590,10 @@ public class SDKSynchronizer: Synchronizer {
return return
} }
initializer.lightWalletService.fetchUTXOs(for: address, height: network.constants.saplingActivationHeight) { [weak self] fetchResult in initializer.lightWalletService.fetchUTXOs(
for: address,
height: network.constants.saplingActivationHeight
) { [weak self] fetchResult in
guard let self = self else { return } guard let self = self else { return }
switch fetchResult { switch fetchResult {
case .success(let utxos): case .success(let utxos):
@ -613,7 +610,7 @@ public class SDKSynchronizer: Synchronizer {
} }
} }
public func refreshUTXOs(address: String, from height: BlockHeight) async throws -> RefreshedUTXOs { public func refreshUTXOs(address: TransparentAddress, from height: BlockHeight) async throws -> RefreshedUTXOs {
try await blockProcessor.refreshUTXOs(tAddress: address, startHeight: height) try await blockProcessor.refreshUTXOs(tAddress: address, startHeight: height)
} }
@available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead") @available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead")
@ -639,28 +636,19 @@ public class SDKSynchronizer: Synchronizer {
} }
public func getUnifiedAddress(accountIndex: Int) -> UnifiedAddress? { public func getUnifiedAddress(accountIndex: Int) -> UnifiedAddress? {
blockProcessor.getUnifiedAddres(accountIndex: accountIndex) blockProcessor.getUnifiedAddress(accountIndex: accountIndex)
} }
public func getTransparentAddress(accountIndex: Int) -> TransparentAddress? { public func getTransparentAddress(accountIndex: Int) -> TransparentAddress? {
blockProcessor.getTransparentAddress(accountIndex: accountIndex) blockProcessor.getTransparentAddress(accountIndex: accountIndex)
} }
/// Returns the last stored transparent balance
public func getTransparentBalance(accountIndex: Int) throws -> WalletBalance { public func getTransparentBalance(accountIndex: Int) throws -> WalletBalance {
try blockProcessor.getTransparentBalance(accountIndex: accountIndex) try blockProcessor.getTransparentBalance(accountIndex: accountIndex)
} }
/**
Returns the last stored transparent balance
*/
public func getTransparentBalance(address: String) throws -> WalletBalance {
do {
return try self.blockProcessor.utxoCacheBalance(tAddress: address)
} catch {
throw SynchronizerError.uncategorized(underlyingError: error)
}
}
public func rewind(_ policy: RewindPolicy) throws { public func rewind(_ policy: RewindPolicy) throws {
self.stop() self.stop()

View File

@ -20,76 +20,28 @@ public protocol KeyValidation {
} }
public protocol KeyDeriving { public protocol KeyDeriving {
/**
Given a seed and a number of accounts, return the associated viewing keys.
- Parameter seed: the seed from which to derive viewing keys.
- Parameter numberOfAccounts: the number of accounts to use. Multiple accounts are not fully
supported so the default value of 1 is recommended.
- Returns: the viewing keys that correspond to the seed, formatted as Strings.
*/
func deriveUnifiedFullViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [UnifiedFullViewingKey]
/** /// Given the seed bytes tand the account index, return the UnifiedSpendingKey
Given a spending key, return the associated viewing key. /// - Parameter seed: `[Uint8]` seed bytes
/// - Parameter accountNumber: `Int` with the account number
- Parameter spendingKey: the key from which to derive the viewing key. /// - Throws `.unableToDerive` if there's a problem deriving this key
/// - Returns a `UnifiedSpendingKey`
- Returns: the viewing key that corresponds to the spending key. func deriveUnifiedSpendingKey(seed: [UInt8], accountIndex: Int) throws -> UnifiedSpendingKey
*/
func deriveViewingKey(spendingKey: SaplingExtendedSpendingKey) throws -> SaplingExtendedFullViewingKey
/** /// Extracts the `SaplingAddress` from the given `UnifiedAddress`
Given a seed and a number of accounts, return the associated spending keys. /// - Parameter address: the `UnifiedAddress`
/// - Throws `KeyDerivationErrors.receiverNotFound` if the receiver is not present
- Parameter seed: the seed from which to derive spending keys. static func saplingReceiver(from unifiedAddress: UnifiedAddress) throws -> SaplingAddress?
- Parameter numberOfAccounts: the number of accounts to use. Multiple accounts are not fully
supported so the default value of 1 is recommended.
- Returns: the spending keys that correspond to the seed, formatted as Strings.
*/
func deriveSpendingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [SaplingExtendedSpendingKey]
/**
Given a seed and account index, return the associated unified address.
- Parameter seed: the seed from which to derive the address.
- Parameter accountIndex: the index of the account to use for deriving the address. Multiple
accounts are not fully supported so the default value of 1 is recommended.
- Returns: the address that corresponds to the seed and account index.
*/
func deriveUnifiedAddress(seed: [UInt8], accountIndex: Int) throws -> UnifiedAddress
/// Given a unified full viewing key string, return the associated unified address. /// Extracts the `TransparentAddress` from the given `UnifiedAddress`
/// /// - Parameter address: the `UnifiedAddress`
/// - Parameter ufvk: the viewing key to use for deriving the address. The viewing key is tied to /// - Throws `KeyDerivationErrors.receiverNotFound` if the receiver is not present
/// a specific account so no account index is required. static func transparentReceiver(from unifiedAddress: UnifiedAddress) throws -> TransparentAddress?
///
/// - 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 -> TransparentAddress
/**
Derives the account private key to spend transparent funds from a specific seed and account
*/
func deriveTransparentAccountPrivateKey(seed: [UInt8], account: Int) throws -> TransparentAccountPrivKey
/**
Derives a transparent address from the given transparent account private key
*/
func deriveTransparentAddressFromAccountPrivateKey(_ xprv: TransparentAccountPrivKey, index: Int) throws -> TransparentAddress
/// Extracts the `UnifiedAddress.ReceiverTypecodes` from the given `UnifiedAddress` /// Extracts the `UnifiedAddress.ReceiverTypecodes` from the given `UnifiedAddress`
/// - Parameter address: the `UnifiedAddress` /// - Parameter address: the `UnifiedAddress`
/// - Throws /// - Throws
func receiverTypecodesFromUnifiedAddress(_ address: UnifiedAddress) throws -> [UnifiedAddress.ReceiverTypecodes] static func receiverTypecodesFromUnifiedAddress(_ address: UnifiedAddress) throws -> [UnifiedAddress.ReceiverTypecodes]
} }
public enum KeyDerivationErrors: Error { public enum KeyDerivationErrors: Error {
@ -97,10 +49,11 @@ public enum KeyDerivationErrors: Error {
case unableToDerive case unableToDerive
case invalidInput case invalidInput
case invalidUnifiedAddress case invalidUnifiedAddress
case receiverNotFound
} }
public class DerivationTool: KeyDeriving { public class DerivationTool: KeyDeriving {
var rustwelding: ZcashRustBackendWelding.Type = ZcashRustBackend.self static var rustwelding: ZcashRustBackendWelding.Type = ZcashRustBackend.self
var networkType: NetworkType var networkType: NetworkType
@ -108,195 +61,47 @@ public class DerivationTool: KeyDeriving {
self.networkType = networkType self.networkType = networkType
} }
/** public static func saplingReceiver(from unifiedAddress: UnifiedAddress) throws -> SaplingAddress? {
Given a seed and a number of accounts, return the associated viewing keys. try rustwelding.getSaplingReceiver(for: unifiedAddress)
- Parameter seed: the seed from which to derive viewing keys.
- Parameter numberOfAccounts: the number of accounts to use. Multiple accounts are not fully
supported so the default value of 1 is recommended.
- Returns: the viewing keys that correspond to the seed, formatted as Strings.
*/
public func deriveUnifiedFullViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [UnifiedFullViewingKey] {
guard numberOfAccounts > 0, let numberOfAccounts = Int32(exactly: numberOfAccounts) else {
throw KeyDerivationErrors.invalidInput
}
do {
let ufvks = try rustwelding.deriveUnifiedFullViewingKeyFromSeed(seed, numberOfAccounts: numberOfAccounts, networkType: networkType)
var keys: [UnifiedFullViewingKey] = []
for ufvk in ufvks {
keys.append(ufvk)
}
return keys
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
} }
/** public static func transparentReceiver(from unifiedAddress: UnifiedAddress) throws -> TransparentAddress? {
Given a spending key, return the associated viewing key. try rustwelding.getTransparentReceiver(for: unifiedAddress)
- Parameter spendingKey: the key from which to derive the viewing key.
- Returns: the viewing key that corresponds to the spending key.
*/
public func deriveViewingKey(spendingKey: SaplingExtendedSpendingKey) throws -> SaplingExtendedFullViewingKey {
do {
guard let key = try rustwelding.deriveSaplingExtendedFullViewingKey(spendingKey, networkType: networkType) else {
throw KeyDerivationErrors.unableToDerive
}
return key
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
} }
public func deriveUnifiedFullViewingKeysFromSeed(_ seed: [UInt8], numberOfAccounts: Int) throws -> [UnifiedFullViewingKey] { /// Given a spending key, return the associated viewing key.
guard numberOfAccounts > 0, let numberOfAccounts = Int32(exactly: numberOfAccounts) else { /// - Parameter spendingKey: the `UnifiedSpendingKey` from which to derive the `UnifiedFullViewingKey` from.
throw KeyDerivationErrors.invalidInput /// - Returns: the viewing key that corresponds to the spending key.
} public func deriveUnifiedFullViewingKey(from spendingKey: UnifiedSpendingKey) throws -> UnifiedFullViewingKey {
do { try DerivationTool.rustwelding.deriveUnifiedFullViewingKey(from: spendingKey, networkType: self.networkType)
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.
Given a seed and a number of accounts, return the associated spending keys. /// - Parameter seed: the seed from which to derive spending keys.
/// - Parameter numberOfAccounts: the number of accounts to use. Multiple accounts are not fully
- Parameter seed: the seed from which to derive spending keys. /// supported so the default value of 1 is recommended.
- Parameter numberOfAccounts: the number of accounts to use. Multiple accounts are not fully /// - Returns: the spending keys that correspond to the seed, formatted as Strings.
supported so the default value of 1 is recommended. public func deriveUnifiedSpendingKey(seed: [UInt8], accountIndex: Int) throws -> UnifiedSpendingKey {
- Returns: the spending keys that correspond to the seed, formatted as Strings.
*/
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.deriveSaplingExtendedSpendingKeys(seed: seed, accounts: numberOfAccounts, networkType: networkType) else {
throw KeyDerivationErrors.unableToDerive
}
return keys
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/**
Given a seed and account index, return the associated unified address.
- Parameter seed: the seed from which to derive the address.
- Parameter accountIndex: the index of the account to use for deriving the address. Multiple
accounts are not fully supported so the default value of 1 is recommended.
- Returns: the address that corresponds to the seed and account index.
*/
public func deriveUnifiedAddress(seed: [UInt8], accountIndex: Int) throws -> UnifiedAddress {
guard accountIndex >= 0, let accountIndex = Int32(exactly: accountIndex) else { guard accountIndex >= 0, let accountIndex = Int32(exactly: accountIndex) else {
throw KeyDerivationErrors.invalidInput throw KeyDerivationErrors.invalidInput
} }
do { do {
guard let address = try rustwelding.deriveUnifiedAddressFromSeed(seed: seed, accountIndex: accountIndex, networkType: networkType) else { return try DerivationTool.rustwelding.deriveUnifiedSpendingKey(
throw KeyDerivationErrors.unableToDerive from: seed,
} accountIndex: accountIndex,
return UnifiedAddress(validatedEncoding: address, network: networkType) networkType: self.networkType
} catch { )
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/**
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.
*/
public func deriveUnifiedAddress(from ufvk: UnifiedFullViewingKey) throws -> UnifiedAddress {
do {
guard let stringEncodedUA = try rustwelding.deriveUnifiedAddressFromViewingKey(ufvk.stringEncoded, networkType: networkType) else {
throw KeyDerivationErrors.unableToDerive
}
return UnifiedAddress(validatedEncoding: stringEncodedUA, network: networkType)
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
public func receiverTypecodesFromUnifiedAddress(_ address: UnifiedAddress) throws -> [UnifiedAddress.ReceiverTypecodes] {
do {
return try rustwelding.receiverTypecodesOnUnifiedAddress(address.stringEncoded)
.map({ UnifiedAddress.ReceiverTypecodes(typecode: $0) })
} catch {
throw KeyDerivationErrors.invalidUnifiedAddress
}
}
public func deriveTransparentAddress(seed: [UInt8], account: Int = 0, index: Int = 0) throws -> TransparentAddress {
do {
guard let taddr = try rustwelding.deriveTransparentAddressFromSeed(
seed: seed,
account: account,
index: index,
networkType: networkType
) else {
throw KeyDerivationErrors.unableToDerive
}
return TransparentAddress(validatedEncoding: taddr)
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/// derives a Unified Address from a Unified Full Viewing Key
public func deriveUnifiedAddressFromUnifiedFullViewingKey(_ ufvk: UnifiedFullViewingKey) throws -> UnifiedAddress {
do {
return try deriveUnifiedAddress(from: ufvk)
} catch { } catch {
throw KeyDerivationErrors.unableToDerive throw KeyDerivationErrors.unableToDerive
} }
} }
/// Derives the transparent funds account private key from the given seed public static func receiverTypecodesFromUnifiedAddress(_ address: UnifiedAddress) throws -> [UnifiedAddress.ReceiverTypecodes] {
/// - 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 { do {
guard let seedKey = try rustwelding.deriveTransparentAccountPrivateKeyFromSeed( return try DerivationTool.rustwelding.receiverTypecodesOnUnifiedAddress(address.stringEncoded)
seed: seed, .map({ UnifiedAddress.ReceiverTypecodes(typecode: $0) })
account: account,
networkType: networkType
) else {
throw KeyDerivationErrors.unableToDerive
}
return TransparentAccountPrivKey(encoding: seedKey)
} catch { } catch {
throw KeyDerivationErrors.derivationError(underlyingError: error) throw KeyDerivationErrors.invalidUnifiedAddress
}
}
/// 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)
} }
} }
} }
@ -304,7 +109,7 @@ public class DerivationTool: KeyDeriving {
extension DerivationTool: KeyValidation { extension DerivationTool: KeyValidation {
public func isValidUnifiedAddress(_ unifiedAddress: String) throws -> Bool { public func isValidUnifiedAddress(_ unifiedAddress: String) throws -> Bool {
do { do {
return try rustwelding.isValidUnifiedAddress(unifiedAddress, networkType: networkType) return try DerivationTool.rustwelding.isValidUnifiedAddress(unifiedAddress, networkType: networkType)
} catch { } catch {
throw KeyDerivationErrors.derivationError(underlyingError: error) throw KeyDerivationErrors.derivationError(underlyingError: error)
} }
@ -312,7 +117,7 @@ extension DerivationTool: KeyValidation {
public func isValidExtendedViewingKey(_ extvk: String) throws -> Bool { public func isValidExtendedViewingKey(_ extvk: String) throws -> Bool {
do { do {
return try rustwelding.isValidSaplingExtendedFullViewingKey(extvk, networkType: networkType) return try DerivationTool.rustwelding.isValidSaplingExtendedFullViewingKey(extvk, networkType: networkType)
} catch { } catch {
throw KeyDerivationErrors.derivationError(underlyingError: error) throw KeyDerivationErrors.derivationError(underlyingError: error)
} }
@ -320,7 +125,7 @@ extension DerivationTool: KeyValidation {
public func isValidTransparentAddress(_ tAddress: String) throws -> Bool { public func isValidTransparentAddress(_ tAddress: String) throws -> Bool {
do { do {
return try rustwelding.isValidTransparentAddress(tAddress, networkType: networkType) return try DerivationTool.rustwelding.isValidTransparentAddress(tAddress, networkType: networkType)
} catch { } catch {
throw KeyDerivationErrors.derivationError(underlyingError: error) throw KeyDerivationErrors.derivationError(underlyingError: error)
} }
@ -328,7 +133,7 @@ extension DerivationTool: KeyValidation {
public func isValidSaplingAddress(_ zAddress: String) throws -> Bool { public func isValidSaplingAddress(_ zAddress: String) throws -> Bool {
do { do {
return try rustwelding.isValidSaplingAddress(zAddress, networkType: networkType) return try DerivationTool.rustwelding.isValidSaplingAddress(zAddress, networkType: networkType)
} catch { } catch {
throw KeyDerivationErrors.derivationError(underlyingError: error) throw KeyDerivationErrors.derivationError(underlyingError: error)
} }
@ -336,7 +141,7 @@ extension DerivationTool: KeyValidation {
public func isValidSaplingExtendedSpendingKey(_ extsk: String) throws -> Bool { public func isValidSaplingExtendedSpendingKey(_ extsk: String) throws -> Bool {
do { do {
return try rustwelding.isValidSaplingExtendedSpendingKey(extsk, networkType: networkType) return try DerivationTool.rustwelding.isValidSaplingExtendedSpendingKey(extsk, networkType: networkType)
} catch { } catch {
throw KeyDerivationErrors.derivationError(underlyingError: error) throw KeyDerivationErrors.derivationError(underlyingError: error)
} }
@ -369,9 +174,8 @@ extension UnifiedAddress {
/// already validated by another function. only for internal use. Unless you are /// already validated by another function. only for internal use. Unless you are
/// constructing an address from a primitive function of the FFI, you probably /// constructing an address from a primitive function of the FFI, you probably
/// shouldn't be using this.. /// shouldn't be using this..
init(validatedEncoding: String, network: NetworkType) { init(validatedEncoding: String) {
self.encoding = validatedEncoding self.encoding = validatedEncoding
self.network = network
} }
} }
@ -407,3 +211,27 @@ extension SaplingExtendedSpendingKey {
self.encoding = validatedEncoding self.encoding = validatedEncoding
} }
} }
public extension UnifiedSpendingKey {
func map<T>(_ transform: (UnifiedSpendingKey) throws -> T) rethrows -> T {
try transform(self)
}
func deriveFullViewingKey() throws -> UnifiedFullViewingKey {
try DerivationTool(networkType: self.network).deriveUnifiedFullViewingKey(from: self)
}
}
public extension UnifiedAddress {
/// Extracts the sapling receiver from this UA if available
/// - Returns: an `Optional<SaplingAddress>`
func saplingReceiver() -> SaplingAddress? {
try? DerivationTool.saplingReceiver(from: self)
}
/// Extracts the transparent receiver from this UA if available
/// - Returns: an `Optional<TransparentAddress>`
func transparentReceiver() -> TransparentAddress? {
try? DerivationTool.transparentReceiver(from: self)
}
}

View File

@ -65,12 +65,12 @@ class PersistentTransactionManager: OutboundTransactionManager {
} }
func encodeShieldingTransaction( func encodeShieldingTransaction(
xprv: TransparentAccountPrivKey, spendingKey: UnifiedSpendingKey,
pendingTransaction: PendingTransactionEntity pendingTransaction: PendingTransactionEntity
) async throws -> PendingTransactionEntity { ) async throws -> PendingTransactionEntity {
do { do {
let encodedTransaction = try self.encoder.createShieldingTransaction( let encodedTransaction = try self.encoder.createShieldingTransaction(
tAccountPrivateKey: xprv, spendingKey: spendingKey,
memoBytes: try pendingTransaction.memo.intoMemoBytes(), memoBytes: try pendingTransaction.memo.intoMemoBytes(),
from: pendingTransaction.accountIndex from: pendingTransaction.accountIndex
) )
@ -98,7 +98,7 @@ class PersistentTransactionManager: OutboundTransactionManager {
} }
func encode( func encode(
spendingKey: SaplingExtendedSpendingKey, spendingKey: UnifiedSpendingKey,
pendingTransaction: PendingTransactionEntity pendingTransaction: PendingTransactionEntity
) async throws -> PendingTransactionEntity { ) async throws -> PendingTransactionEntity {
do { do {

View File

@ -25,15 +25,16 @@ protocol TransactionEncoder {
/// Blocking /// Blocking
/// ///
/// - Parameters: /// - Parameters:
/// - Parameter spendingKey: a `SaplingExtendedSpendingKey` containing the spending key /// - Parameter spendingKey: a `UnifiedSpendingKey` containing the spending key
/// - Parameter zatoshi: the amount to send in `Zatoshi` /// - Parameter zatoshi: the amount to send in `Zatoshi`
/// - Parameter to: string containing the recipient address /// - Parameter to: string containing the recipient address
/// - Parameter memoBytes: MemoBytes for this transaction /// - Parameter memoBytes: MemoBytes for this transaction
/// - Parameter accountIndex: index of the account that will be used to send the funds /// - Parameter accountIndex: index of the account that will be used to send the funds
/// ///
/// - Throws: a TransactionEncoderError /// - Throws: a TransactionEncoderError
///
func createTransaction( func createTransaction(
spendingKey: SaplingExtendedSpendingKey, spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi, zatoshi: Zatoshi,
to address: String, to address: String,
memoBytes: MemoBytes, memoBytes: MemoBytes,
@ -55,7 +56,7 @@ protocol TransactionEncoder {
/// - Parameter result: a non escaping closure that receives a Result containing either an EncodedTransaction or a /// TransactionEncoderError /// - Parameter result: a non escaping closure that receives a Result containing either an EncodedTransaction or a /// TransactionEncoderError
// swiftlint:disable:next function_parameter_count // swiftlint:disable:next function_parameter_count
func createTransaction( func createTransaction(
spendingKey: SaplingExtendedSpendingKey, spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi, zatoshi: Zatoshi,
to address: String, to address: String,
memoBytes: MemoBytes, memoBytes: MemoBytes,
@ -68,13 +69,13 @@ protocol TransactionEncoder {
Blocking Blocking
- Parameters: - Parameters:
- Parameter tAccountPrivateKey: transparent account private key to spend the UTXOs - Parameter spendingKey: `UnifiedSpendingKey` to spend the UTXOs
- Parameter memoBytes: containing the memo (optional) - Parameter memoBytes: containing the memo (optional)
- Parameter accountIndex: index of the account that will be used to send the funds - Parameter accountIndex: index of the account that will be used to send the funds
- Throws: a TransactionEncoderError - Throws: a TransactionEncoderError
*/ */
func createShieldingTransaction( func createShieldingTransaction(
tAccountPrivateKey: TransparentAccountPrivKey, spendingKey: UnifiedSpendingKey,
memoBytes: MemoBytes, memoBytes: MemoBytes,
from accountIndex: Int from accountIndex: Int
) throws -> EncodedTransaction ) throws -> EncodedTransaction

View File

@ -22,12 +22,12 @@ protocol OutboundTransactionManager {
) throws -> PendingTransactionEntity ) throws -> PendingTransactionEntity
func encodeShieldingTransaction( func encodeShieldingTransaction(
xprv: TransparentAccountPrivKey, spendingKey: UnifiedSpendingKey,
pendingTransaction: PendingTransactionEntity pendingTransaction: PendingTransactionEntity
) async throws -> PendingTransactionEntity ) async throws -> PendingTransactionEntity
func encode( func encode(
spendingKey: SaplingExtendedSpendingKey, spendingKey: UnifiedSpendingKey,
pendingTransaction: PendingTransactionEntity pendingTransaction: PendingTransactionEntity
) async throws -> PendingTransactionEntity ) async throws -> PendingTransactionEntity

View File

@ -50,7 +50,7 @@ class WalletTransactionEncoder: TransactionEncoder {
} }
func createTransaction( func createTransaction(
spendingKey: SaplingExtendedSpendingKey, spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi, zatoshi: Zatoshi,
to address: String, to address: String,
memoBytes: MemoBytes, memoBytes: MemoBytes,
@ -80,7 +80,7 @@ class WalletTransactionEncoder: TransactionEncoder {
// swiftlint:disable:next function_parameter_count // swiftlint:disable:next function_parameter_count
func createTransaction( func createTransaction(
spendingKey: SaplingExtendedSpendingKey, spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi, zatoshi: Zatoshi,
to address: String, to address: String,
memoBytes: MemoBytes, memoBytes: MemoBytes,
@ -108,7 +108,7 @@ class WalletTransactionEncoder: TransactionEncoder {
} }
func createSpend( func createSpend(
spendingKey: SaplingExtendedSpendingKey, spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi, zatoshi: Zatoshi,
to address: String, to address: String,
memoBytes: MemoBytes, memoBytes: MemoBytes,
@ -120,8 +120,7 @@ class WalletTransactionEncoder: TransactionEncoder {
let txId = rustBackend.createToAddress( let txId = rustBackend.createToAddress(
dbData: self.dataDbURL, dbData: self.dataDbURL,
account: Int32(accountIndex), usk: spendingKey,
extsk: spendingKey.stringEncoded,
to: address, to: address,
value: zatoshi.amount, value: zatoshi.amount,
memo: memoBytes, memo: memoBytes,
@ -138,12 +137,12 @@ class WalletTransactionEncoder: TransactionEncoder {
} }
func createShieldingTransaction( func createShieldingTransaction(
tAccountPrivateKey: TransparentAccountPrivKey, spendingKey: UnifiedSpendingKey,
memoBytes: MemoBytes, memoBytes: MemoBytes,
from accountIndex: Int from accountIndex: Int
) throws -> EncodedTransaction { ) throws -> EncodedTransaction {
let txId = try createShieldingSpend( let txId = try createShieldingSpend(
xprv: tAccountPrivateKey.encoding, spendingKey: spendingKey,
memo: memoBytes, memo: memoBytes,
accountIndex: accountIndex accountIndex: accountIndex
) )
@ -162,7 +161,7 @@ class WalletTransactionEncoder: TransactionEncoder {
} }
} }
func createShieldingSpend(xprv: String, memo: MemoBytes, accountIndex: Int) throws -> Int { func createShieldingSpend(spendingKey: UnifiedSpendingKey, memo: MemoBytes, accountIndex: Int) throws -> Int {
guard ensureParams(spend: self.spendParamsURL, output: self.spendParamsURL) else { guard ensureParams(spend: self.spendParamsURL, output: self.spendParamsURL) else {
throw TransactionEncoderError.missingParams throw TransactionEncoderError.missingParams
} }
@ -170,8 +169,7 @@ class WalletTransactionEncoder: TransactionEncoder {
let txId = rustBackend.shieldFunds( let txId = rustBackend.shieldFunds(
dbCache: self.cacheDbURL, dbCache: self.cacheDbURL,
dbData: self.dataDbURL, dbData: self.dataDbURL,
account: Int32(accountIndex), usk: spendingKey,
xprv: xprv,
memo: memo, memo: memo,
spendParamsPath: self.spendParamsURL.path, spendParamsPath: self.spendParamsURL.path,
outputParamsPath: self.outputParamsURL.path, outputParamsPath: self.outputParamsURL.path,

View File

@ -310,8 +310,7 @@ class AdvancedReOrgTests: XCTestCase {
spendingKey: coordinator.spendingKeys!.first!, spendingKey: coordinator.spendingKeys!.first!,
zatoshi: sendAmount, zatoshi: sendAmount,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: try Memo(string: "test transaction"), memo: try Memo(string: "test transaction")
from: 0
) )
pendingEntity = pendingTx pendingEntity = pendingTx
sendExpectation.fulfill() sendExpectation.fulfill()
@ -733,8 +732,8 @@ class AdvancedReOrgTests: XCTestCase {
spendingKey: self.coordinator.spendingKeys!.first!, spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: Zatoshi(20000), zatoshi: Zatoshi(20000),
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: try Memo(string: "this is a test"), memo: try Memo(string: "this is a test")
from: 0) )
pendingEntity = pendingTx pendingEntity = pendingTx
sendExpectation.fulfill() sendExpectation.fulfill()
} catch { } catch {
@ -1095,15 +1094,16 @@ class AdvancedReOrgTests: XCTestCase {
try await withCheckedThrowingContinuation { continuation in try await withCheckedThrowingContinuation { continuation in
do { do {
try coordinator.sync(completion: { synchronizer in try coordinator.sync(completion: { synchronizer in
firstSyncExpectation.fulfill()
continuation.resume() continuation.resume()
firstSyncExpectation.fulfill()
}, error: self.handleError) }, error: self.handleError)
} catch { } catch {
continuation.resume(throwing: error) continuation.resume(throwing: error)
} }
} }
wait(for: [firstSyncExpectation], timeout: 5) wait(for: [firstSyncExpectation], timeout: 10)
sleep(1) sleep(1)
let initialTotalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance() let initialTotalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
@ -1119,8 +1119,8 @@ class AdvancedReOrgTests: XCTestCase {
spendingKey: self.coordinator.spendingKeys!.first!, spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: Zatoshi(20000), zatoshi: Zatoshi(20000),
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: try! Memo(string: "this is a test"), memo: try! Memo(string: "this is a test")
from: 0) )
pendingEntity = pendingTx pendingEntity = pendingTx
sendExpectation.fulfill() sendExpectation.fulfill()
} catch { } catch {

View File

@ -91,8 +91,8 @@ class BalanceTests: XCTestCase {
spendingKey: spendingKey, spendingKey: spendingKey,
zatoshi: maxBalance, zatoshi: maxBalance,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: try Memo(string: "this is a test"), memo: try Memo(string: "this is a test")
from: 0) )
pendingTx = transaction pendingTx = transaction
self.sentTransactionExpectation.fulfill() self.sentTransactionExpectation.fulfill()
} catch { } catch {
@ -249,8 +249,8 @@ class BalanceTests: XCTestCase {
spendingKey: spendingKey, spendingKey: spendingKey,
zatoshi: maxBalanceMinusOne, zatoshi: maxBalanceMinusOne,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: try Memo(string: "\(self.description) \(Date().description)"), memo: try Memo(string: "\(self.description) \(Date().description)")
from: 0) )
pendingTx = transaction pendingTx = transaction
self.sentTransactionExpectation.fulfill() self.sentTransactionExpectation.fulfill()
} catch { } catch {
@ -406,8 +406,8 @@ class BalanceTests: XCTestCase {
spendingKey: spendingKey, spendingKey: spendingKey,
zatoshi: maxBalanceMinusOne, zatoshi: maxBalanceMinusOne,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: try Memo(string: "test send \(self.description) \(Date().description)"), memo: try Memo(string: "test send \(self.description) \(Date().description)")
from: 0) )
pendingTx = transaction pendingTx = transaction
self.sentTransactionExpectation.fulfill() self.sentTransactionExpectation.fulfill()
} catch { } catch {
@ -566,8 +566,8 @@ class BalanceTests: XCTestCase {
spendingKey: spendingKey, spendingKey: spendingKey,
zatoshi: sendAmount, zatoshi: sendAmount,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: try Memo(string: "this is a test"), memo: try Memo(string: "this is a test")
from: 0) )
pendingTx = transaction pendingTx = transaction
self.sentTransactionExpectation.fulfill() self.sentTransactionExpectation.fulfill()
} catch { } catch {
@ -749,8 +749,8 @@ class BalanceTests: XCTestCase {
spendingKey: spendingKey, spendingKey: spendingKey,
zatoshi: sendAmount, zatoshi: sendAmount,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: try Memo(string: "test send \(self.description) \(Date().description)"), memo: try Memo(string: "test send \(self.description) \(Date().description)")
from: 0) )
pendingTx = transaction pendingTx = transaction
self.sentTransactionExpectation.fulfill() self.sentTransactionExpectation.fulfill()
} catch { } catch {
@ -915,8 +915,8 @@ class BalanceTests: XCTestCase {
spendingKey: spendingKeys, spendingKey: spendingKeys,
zatoshi: sendAmount, zatoshi: sendAmount,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: memo, memo: memo
from: 0) )
pendingTx = transaction pendingTx = transaction
sendExpectation.fulfill() sendExpectation.fulfill()
} catch { } catch {
@ -1090,8 +1090,8 @@ class BalanceTests: XCTestCase {
spendingKey: spendingKey, spendingKey: spendingKey,
zatoshi: sendAmount, zatoshi: sendAmount,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: try Memo(string: "test send \(self.description)"), memo: try Memo(string: "test send \(self.description)")
from: 0) )
pendingTx = pending pendingTx = pending
} catch { } catch {
// balance should be the same as before sending if transaction failed // balance should be the same as before sending if transaction failed

View File

@ -90,8 +90,8 @@ class NetworkUpgradeTests: XCTestCase {
spendingKey: self.coordinator.spendingKeys!.first!, spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: spendAmount, zatoshi: spendAmount,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: try Memo(string: "this is a test"), memo: try Memo(string: "this is a test")
from: 0) )
pendingEntity = pendingTx pendingEntity = pendingTx
sendExpectation.fulfill() sendExpectation.fulfill()
} catch { } catch {
@ -189,8 +189,8 @@ class NetworkUpgradeTests: XCTestCase {
spendingKey: self.coordinator.spendingKeys!.first!, spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: spendAmount, zatoshi: spendAmount,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: try Memo(string: "this is a test"), memo: try Memo(string: "this is a test")
from: 0) )
pendingEntity = pendingTx pendingEntity = pendingTx
sendExpectation.fulfill() sendExpectation.fulfill()
} catch { } catch {
@ -268,8 +268,8 @@ class NetworkUpgradeTests: XCTestCase {
spendingKey: self.coordinator.spendingKeys!.first!, spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: spendAmount, zatoshi: spendAmount,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: try Memo(string: "this is a test"), memo: try Memo(string: "this is a test")
from: 0) )
pendingEntity = pendingTx pendingEntity = pendingTx
sendExpectation.fulfill() sendExpectation.fulfill()
} catch { } catch {
@ -379,8 +379,8 @@ class NetworkUpgradeTests: XCTestCase {
spendingKey: self.coordinator.spendingKeys!.first!, spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: spendAmount, zatoshi: spendAmount,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: try Memo(string: "this is a test"), memo: try Memo(string: "this is a test")
from: 0) )
pendingEntity = pendingTx pendingEntity = pendingTx
sendExpectation.fulfill() sendExpectation.fulfill()
} catch { } catch {
@ -508,8 +508,8 @@ class NetworkUpgradeTests: XCTestCase {
spendingKey: self.coordinator.spendingKeys!.first!, spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: spendAmount, zatoshi: spendAmount,
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: try Memo(string: "this is a test"), memo: try Memo(string: "this is a test")
from: 0) )
pendingEntity = pendingTx pendingEntity = pendingTx
sendExpectation.fulfill() sendExpectation.fulfill()
} catch { } catch {

View File

@ -105,8 +105,8 @@ class PendingTransactionUpdatesTest: XCTestCase {
spendingKey: self.coordinator.spendingKeys!.first!, spendingKey: self.coordinator.spendingKeys!.first!,
zatoshi: Zatoshi(20000), zatoshi: Zatoshi(20000),
toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType),
memo: try Memo(string: "this is a test"), memo: try Memo(string: "this is a test")
from: 0) )
pendingEntity = pendingTx pendingEntity = pendingTx
sendExpectation.fulfill() sendExpectation.fulfill()
} catch { } catch {

View File

@ -181,8 +181,8 @@ class RewindRescanTests: XCTestCase {
spendingKey: coordinator.spendingKey, spendingKey: coordinator.spendingKey,
zatoshi: Zatoshi(1000), zatoshi: Zatoshi(1000),
toAddress: testRecipientAddress, toAddress: testRecipientAddress,
memo: .empty, memo: .empty
from: 0) )
XCTAssertEqual(Zatoshi(1000), pendingTx.value) XCTAssertEqual(Zatoshi(1000), pendingTx.value)
} catch { } catch {
XCTFail("sending fail: \(error)") XCTFail("sending fail: \(error)")
@ -284,8 +284,8 @@ class RewindRescanTests: XCTestCase {
spendingKey: spendingKey, spendingKey: spendingKey,
zatoshi: maxBalance, zatoshi: maxBalance,
toAddress: testRecipientAddress, toAddress: testRecipientAddress,
memo: try Memo(string: "test send \(self.description) \(Date().description)"), memo: try Memo(string: "test send \(self.description) \(Date().description)")
from: 0) )
pendingTx = transaction pendingTx = transaction
self.sentTransactionExpectation.fulfill() self.sentTransactionExpectation.fulfill()
} catch { } catch {

View File

@ -207,14 +207,6 @@ class ShieldFundsTests: XCTestCase {
// 9. shield the funds // 9. shield the funds
let shieldFundsExpectation = XCTestExpectation(description: "shield funds") let shieldFundsExpectation = XCTestExpectation(description: "shield funds")
let transparentAccountPrivateKey = try DerivationTool(
networkType: network.networkType
)
.deriveTransparentAccountPrivateKey(
seed: TestSeed().seed(),
account: 0
)
shouldContinue = false shouldContinue = false
var shieldingPendingTx: PendingTransactionEntity? var shieldingPendingTx: PendingTransactionEntity?
@ -222,9 +214,9 @@ class ShieldFundsTests: XCTestCase {
// shield the funds // shield the funds
do { do {
let pendingTx = try await coordinator.synchronizer.shieldFunds( let pendingTx = try await coordinator.synchronizer.shieldFunds(
transparentAccountPrivateKey: transparentAccountPrivateKey, spendingKey: coordinator.spendingKey,
memo: try Memo(string: "shield funds"), memo: try Memo(string: "shield funds")
from: 0) )
shouldContinue = true shouldContinue = true
XCTAssertEqual(pendingTx.value, Zatoshi(10000)) XCTAssertEqual(pendingTx.value, Zatoshi(10000))
shieldingPendingTx = pendingTx shieldingPendingTx = pendingTx

View File

@ -62,8 +62,24 @@ class TransactionEnhancementTests: XCTestCase {
try? FileManager.default.removeItem(at: processorConfig.cacheDb) try? FileManager.default.removeItem(at: processorConfig.cacheDb)
try? FileManager.default.removeItem(at: processorConfig.dataDb) try? FileManager.default.removeItem(at: processorConfig.dataDb)
_ = rustBackend.initAccountsTable(dbData: processorConfig.dataDb, seed: TestSeed().seed(), accounts: 1, networkType: network.networkType) let ufvks = [
try DerivationTool(networkType: network.networkType)
.deriveUnifiedSpendingKey(seed: TestSeed().seed(), accountIndex: 0)
.map{
try DerivationTool(networkType: network.networkType)
.deriveUnifiedFullViewingKey(from: $0)
}
]
guard try rustBackend.initAccountsTable(
dbData: processorConfig.dataDb,
ufvks: ufvks,
networkType: network.networkType
) else {
XCTFail("Failed to init accounts table error: " + String(describing: rustBackend.getLastError()))
return
}
let dbInit = try rustBackend.initDataDb(dbData: processorConfig.dataDb, seed: nil, networkType: network.networkType) let dbInit = try rustBackend.initDataDb(dbData: processorConfig.dataDb, seed: nil, networkType: network.networkType)

View File

@ -109,8 +109,8 @@ class Z2TReceiveTests: XCTestCase {
spendingKey: coordinator.spendingKeys!.first!, spendingKey: coordinator.spendingKeys!.first!,
zatoshi: sendAmount, zatoshi: sendAmount,
toAddress: try! Recipient(testRecipientAddress, network: self.network.networkType), toAddress: try! Recipient(testRecipientAddress, network: self.network.networkType),
memo: try Memo(string: "test transaction"), memo: try Memo(string: "test transaction")
from: 0) )
pendingEntity = pending pendingEntity = pending
sendExpectation.fulfill() sendExpectation.fulfill()
} catch { } catch {

View File

@ -109,6 +109,8 @@ class BlockScanTests: XCTestCase {
} }
func testScanValidateDownload() async throws { func testScanValidateDownload() async throws {
let seed = "testreferencealicetestreferencealice"
logger = SampleLogger(logLevel: .debug) logger = SampleLogger(logLevel: .debug)
NotificationCenter.default.addObserver( NotificationCenter.default.addObserver(
@ -123,10 +125,18 @@ class BlockScanTests: XCTestCase {
return return
} }
let uvks = try DerivationTool(networkType: .testnet).deriveUnifiedFullViewingKeys(seed: TestSeed().seed(), numberOfAccounts: 1) let derivationTool = DerivationTool(networkType: .testnet)
let ufvk = try derivationTool
.deriveUnifiedSpendingKey(seed: Array(seed.utf8), accountIndex: 0)
.map { try derivationTool.deriveUnifiedFullViewingKey(from: $0) }
guard try self.rustWelding.initAccountsTable(dbData: self.dataDbURL, ufvks: uvks, networkType: network.networkType) else {
XCTFail("failed to init account table") guard try self.rustWelding.initAccountsTable(
dbData: self.dataDbURL,
ufvks: [ufvk],
networkType: network.networkType
) else {
XCTFail("failed to init account table. error: \(self.rustWelding.getLastError() ?? "no error found")")
return return
} }

View File

@ -35,7 +35,7 @@ class ZcashRustBackendTests: XCTestCase {
dataDbHandle.dispose() dataDbHandle.dispose()
} }
func testInitWithShortSeedAndFail() { func testInitWithShortSeedAndFail() throws {
let seed = "testreferencealice" let seed = "testreferencealice"
var dbInit: DbInitResult! var dbInit: DbInitResult!
@ -46,58 +46,10 @@ class ZcashRustBackendTests: XCTestCase {
return return
} }
_ = ZcashRustBackend.initAccountsTable(dbData: dbData!, seed: Array(seed.utf8), accounts: 1, networkType: networkType) XCTAssertThrowsError(try ZcashRustBackend.createAccount(dbData: dbData!, seed: Array(seed.utf8), networkType: networkType))
XCTAssertNotNil(ZcashRustBackend.getLastError())
} }
func testDeriveExtendedSpendingKeys() { func testInitAndScanBlocks() throws {
let seed = Array("testreferencealicetestreferencealice".utf8)
var spendingKeys: [SaplingExtendedSpendingKey]?
XCTAssertNoThrow(try { spendingKeys = try ZcashRustBackend.deriveSaplingExtendedSpendingKeys(seed: seed, accounts: 1, networkType: networkType) }())
XCTAssertNotNil(spendingKeys)
XCTAssertEqual(spendingKeys?.count, 1)
}
func testDeriveExtendedFullViewingKeys() {
let seed = Array("testreferencealicetestreferencealice".utf8)
var fullViewingKeys: [SaplingExtendedFullViewingKey]?
XCTAssertNoThrow(
try {
fullViewingKeys = try ZcashRustBackend.deriveSaplingExtendedFullViewingKeys(
seed: seed,
accounts: 2,
networkType: networkType
)
}()
)
XCTAssertNotNil(fullViewingKeys)
XCTAssertEqual(fullViewingKeys?.count, 2)
}
func testDeriveExtendedFullViewingKey() {
let seed = Array("testreferencealicetestreferencealice".utf8)
var fullViewingKey: SaplingExtendedFullViewingKey?
var spendingKeys: [SaplingExtendedSpendingKey]?
XCTAssertNoThrow(try { spendingKeys = try ZcashRustBackend.deriveSaplingExtendedSpendingKeys(seed: seed, accounts: 1, networkType: networkType) }())
XCTAssertNotNil(spendingKeys)
guard let spendingKey = spendingKeys?.first else {
XCTFail("no spending key generated")
return
}
XCTAssertNoThrow(try { fullViewingKey = try ZcashRustBackend.deriveSaplingExtendedFullViewingKey(spendingKey, networkType: networkType) }())
XCTAssertNotNil(fullViewingKey)
}
func testInitAndScanBlocks() {
guard let cacheDb = TestDbBuilder.prePopulatedCacheDbURL() else { guard let cacheDb = TestDbBuilder.prePopulatedCacheDbURL() else {
XCTFail("pre populated Db not present") XCTFail("pre populated Db not present")
return return
@ -105,21 +57,36 @@ class ZcashRustBackendTests: XCTestCase {
let seed = "testreferencealicetestreferencealice" let seed = "testreferencealicetestreferencealice"
var dbInit: DbInitResult! var dbInit: DbInitResult!
XCTAssertNoThrow(try { dbInit = try ZcashRustBackend.initDataDb(dbData: self.dbData!, seed: nil, networkType: self.networkType) }()) XCTAssertNoThrow(try { dbInit = try ZcashRustBackend.initDataDb(dbData: self.dbData!, seed: Array(seed.utf8), networkType: self.networkType) }())
guard case .success = dbInit else { guard case .success = dbInit else {
XCTFail("Failed to initDataDb. Expected `.success` got: \(String(describing: dbInit))") XCTFail("Failed to initDataDb. Expected `.success` got: \(String(describing: dbInit))")
return return
} }
XCTAssertEqual(ZcashRustBackend.getLastError(), nil)
let ufvks = [
try DerivationTool(networkType: networkType).deriveUnifiedSpendingKey(seed: Array(seed.utf8), accountIndex: 0)
.deriveFullViewingKey()
]
guard try ZcashRustBackend.initAccountsTable(dbData: dbData!, ufvks: ufvks, networkType: networkType) else {
XCTFail("failed with error: \(String(describing: ZcashRustBackend.lastError()))")
return
}
XCTAssertNotNil(
try ZcashRustBackend.createAccount(
dbData: dbData!,
seed: Array(seed.utf8),
networkType: networkType
)
)
XCTAssertEqual(ZcashRustBackend.getLastError(), nil) XCTAssertEqual(ZcashRustBackend.getLastError(), nil)
XCTAssertNotNil(ZcashRustBackend.initAccountsTable(dbData: dbData!, seed: Array(seed.utf8), accounts: 1, networkType: networkType)) let addr = try ZcashRustBackend.getCurrentAddress(dbData: dbData!, account: 0, networkType: networkType)
XCTAssertEqual(ZcashRustBackend.getLastError(), nil) XCTAssertEqual(ZcashRustBackend.getLastError(), nil)
XCTAssertEqual(addr.saplingReceiver()?.stringEncoded, Optional("ztestsapling12k9m98wmpjts2m56wc60qzhgsfvlpxcwah268xk5yz4h942sd58jy3jamqyxjwums6hw7kfa4cc"))
let addr = ZcashRustBackend.getAddress(dbData: dbData!, account: 0, networkType: networkType)
XCTAssertEqual(ZcashRustBackend.getLastError(), nil)
XCTAssertEqual(addr, Optional("ztestsapling12k9m98wmpjts2m56wc60qzhgsfvlpxcwah268xk5yz4h942sd58jy3jamqyxjwums6hw7kfa4cc"))
XCTAssertTrue(ZcashRustBackend.scanBlocks(dbCache: cacheDb, dbData: dbData, networkType: networkType)) XCTAssertTrue(ZcashRustBackend.scanBlocks(dbCache: cacheDb, dbData: dbData, networkType: networkType))
} }
@ -199,4 +166,74 @@ class ZcashRustBackendTests: XCTestCase {
XCTFail("Failed as invalid") XCTFail("Failed as invalid")
} }
} }
func testListTransparentReceivers() throws {
let testVector = [TestVector](TestVector.testVectors![0 ... 2])
let network = NetworkType.mainnet
let tempDBs = TemporaryDbBuilder.build()
let seed = testVector[0].root_seed!
let ufvk = try DerivationTool(networkType: network).deriveUnifiedSpendingKey(seed: seed, accountIndex: Int(testVector[0].account)).deriveFullViewingKey()
XCTAssertEqual(
try ZcashRustBackend.initDataDb(
dbData: tempDBs.dataDB,
seed: seed,
networkType: network
),
.success
)
// XCTAssertTrue(
// try ZcashRustBackend.initAccountsTable(
// dbData: tempDBs.dataDB,
// ufvks: [ufvk],
// networkType: network
// )
// )
XCTAssertNoThrow(
try ZcashRustBackend.createAccount(
dbData: tempDBs.dataDB,
seed: seed,
networkType: .mainnet
)
)
let expectedReceivers = testVector.map {
UnifiedAddress(validatedEncoding: $0.unified_addr!)
}
.compactMap({ $0.transparentReceiver() })
guard expectedReceivers.count >= 2 else {
XCTFail("not enough transparent receivers")
return
}
for _ in [0 ... 2] {
XCTAssertNoThrow(
try ZcashRustBackend.getCurrentAddress(
dbData: tempDBs.dataDB,
account: 0,
networkType: network
)
)
XCTAssertNoThrow(
try ZcashRustBackend.getNextAvailableAddress(
dbData: tempDBs.dataDB,
account: 0,
networkType: network
)
)
}
XCTAssertEqual(
expectedReceivers,
try ZcashRustBackend.listTransparentReceivers(
dbData: tempDBs.dataDB,
account: 0,
networkType: network
)
)
}
} }

View File

@ -11,10 +11,15 @@ import XCTest
class DerivationToolMainnetTests: XCTestCase { 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 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==")! var seedData: Data = Data(base64Encoded: "9VDVOZZZOWWHpZtq1Ebridp3Qeux5C+HwiRR0g7Oi7HgnMs8Gfln83+/Q1NnvClcaSwM4ADFL1uZHxypEWlWXg==")!
let testRecipientAddress = UnifiedAddress(validatedEncoding: "u1l9f0l4348negsncgr9pxd9d3qaxagmqv3lnexcplmufpq7muffvfaue6ksevfvd7wrz7xrvn95rc5zjtn7ugkmgh5rnxswmcj30y0pw52pn0zjvy38rn2esfgve64rj5pcmazxgpyuj", network: .mainnet) //TODO: Parameterize this from environment
let testRecipientAddress = UnifiedAddress(validatedEncoding: "u1l9f0l4348negsncgr9pxd9d3qaxagmqv3lnexcplmufpq7muffvfaue6ksevfvd7wrz7xrvn95rc5zjtn7ugkmgh5rnxswmcj30y0pw52pn0zjvy38rn2esfgve64rj5pcmazxgpyuj") //TODO: Parameterize this from environment
let expectedSpendingKey = SaplingExtendedSpendingKey(validatedEncoding: "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv")
let expectedSpendingKey = UnifiedSpendingKey(
network: .mainnet,
bytes: Data(base64Encoded: "tNDWwgMg8Tkw4YrTsuDcwFRcaL4E6xllYD/72MAFAWl0ozX9q+ICqQOUcMGPAAAAgGofH2hCmQcNq7zShy1FFKYcENBO+X4tO3z8AlMahG6xOZQS96NqNozvVSf/ZffZxqWY0U8z2mwcJF04DKv/ZQRVzmOebCbHjT1q3PR40S8qy6jNFMmiKUUCprPLexpgB1ziepyEZ9FXROg3qYIwsmhZn3jFyDQ1/00oCXO3K65bln5489aKWhnXnmo/2qoFcmntX15GRdBtUw50Wj6+iAsAQBgnnRntCLIa/wXB4KsvlPe21H9bTk24s27gb5/tIXOZNug65274BKRqcMVddG9ISBGT85GYg0BmOBVSIPt8ZvQ=")!.bytes,
account: 0
)
let expectedViewingKey = UnifiedFullViewingKey(validatedEncoding: "uview17fme6ux853km45g9ep07djpfzeydxxgm22xpmr7arzxyutlusalgpqlx7suga4ahzywfuwz4jclm00u7g8u65qvvdt45kttnfunvschssg3h3g06txs9ja32vx3xa8dej3unnatgzjvd0vumk37t8es3ludldrtse3q6226ws7eq4q0ywz78nudwpepgdn7jmxz8yvp7k6gxkeynkam0f8aqf9qpeaej55zhkw39x7epayhndul0j4xjttdxxlnwcd09nr8svyx8j0zng0w6scx3m5unpkaqxcm3hslhlfg4caz7r8d4xy9wm7klkg79w7j0uyzec5s3yje20eg946r6rmkf532nfydu26s8q9ua7mwxw2j2ag7hfcuu652gw6uta03vlm05zju3a9rwc4h367kqzfqrcz35pdwdk2a7yqnk850un3ujxcvve45ueajgvtr6dj4ufszgqwdy0aedgmkalx2p7qed2suarwkr35dl0c8dnqp3", account: 0) let expectedViewingKey = UnifiedFullViewingKey(validatedEncoding: "uview17fme6ux853km45g9ep07djpfzeydxxgm22xpmr7arzxyutlusalgpqlx7suga4ahzywfuwz4jclm00u7g8u65qvvdt45kttnfunvschssg3h3g06txs9ja32vx3xa8dej3unnatgzjvd0vumk37t8es3ludldrtse3q6226ws7eq4q0ywz78nudwpepgdn7jmxz8yvp7k6gxkeynkam0f8aqf9qpeaej55zhkw39x7epayhndul0j4xjttdxxlnwcd09nr8svyx8j0zng0w6scx3m5unpkaqxcm3hslhlfg4caz7r8d4xy9wm7klkg79w7j0uyzec5s3yje20eg946r6rmkf532nfydu26s8q9ua7mwxw2j2ag7hfcuu652gw6uta03vlm05zju3a9rwc4h367kqzfqrcz35pdwdk2a7yqnk850un3ujxcvve45ueajgvtr6dj4ufszgqwdy0aedgmkalx2p7qed2suarwkr35dl0c8dnqp3", account: 0)
let expectedSaplingExtendedViewingKey = SaplingExtendedFullViewingKey(validatedEncoding: "zxviews1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkysswfhjk79n8l99f2grd26dqg6dy3jcmxsaypxfsu6ara6vsk3x8l544uaksstx9zre879mdg7s9a7zurrx6pf5qg2n323js2s3zlu8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszcq7kwxy") let expectedSaplingExtendedViewingKey = SaplingExtendedFullViewingKey(validatedEncoding: "zxviews1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkysswfhjk79n8l99f2grd26dqg6dy3jcmxsaypxfsu6ara6vsk3x8l544uaksstx9zre879mdg7s9a7zurrx6pf5qg2n323js2s3zlu8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszcq7kwxy")
@ -23,52 +28,43 @@ class DerivationToolMainnetTests: XCTestCase {
let derivationTool = DerivationTool(networkType: NetworkType.mainnet) let derivationTool = DerivationTool(networkType: NetworkType.mainnet)
let expectedTransparentAddress = TransparentAddress(validatedEncoding: "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz") let expectedTransparentAddress = TransparentAddress(validatedEncoding: "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz")
func testDeriveViewingKeysFromSeed() throws { func testDeriveViewingKeysFromSeed() throws {
let accounts: Int = 1
let seedBytes = [UInt8](seedData) 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") let spendingKey = try derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: 0)
let viewingKey = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
guard let viewingKey = viewingKeys.first else {
XCTFail("no viewing key generated")
return
}
XCTAssertEqual(expectedViewingKey, viewingKey) XCTAssertEqual(expectedViewingKey, viewingKey)
} }
func testDeriveViewingKeyFromSpendingKeys() throws { func testDeriveViewingKeyFromSpendingKeys() throws {
XCTAssertEqual(expectedSaplingExtendedViewingKey, try derivationTool.deriveViewingKey(spendingKey: expectedSpendingKey)) XCTAssertEqual(
expectedViewingKey,
try derivationTool.deriveUnifiedFullViewingKey(from: expectedSpendingKey)
)
} }
func testDeriveSpendingKeysFromSeed() throws { func testDeriveSpendingKeysFromSeed() throws {
let accounts: Int = 1
let seedBytes = [UInt8](seedData) let seedBytes = [UInt8](seedData)
let spendingKeys = try derivationTool.deriveSpendingKeys(seed: seedBytes, numberOfAccounts: accounts) let spendingKey = try derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: 0)
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) XCTAssertEqual(expectedSpendingKey, spendingKey)
} }
func testDeriveUnifiedAddressFromSeed() throws { func testDeriveUnifiedSpendingKeyFromSeed() throws {
let account = 0
let seedBytes = [UInt8](seedData) let seedBytes = [UInt8](seedData)
let unifiedAddress = try derivationTool.deriveUnifiedAddress(seed: seedBytes, accountIndex: 0) XCTAssertNoThrow(try derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: account))
XCTAssertEqual(unifiedAddress, testRecipientAddress)
} }
func testDeriveUnifiedAddressFromViewingKey() throws { func testGetTransparentAddressFromUA() throws {
XCTAssertEqual(try derivationTool.deriveUnifiedAddress(from: expectedViewingKey), testRecipientAddress) XCTAssertEqual(
} try DerivationTool.transparentReceiver(from: testRecipientAddress),
expectedTransparentAddress
func testDeriveTransparentAddressFromSeed() throws { )
XCTAssertEqual(try derivationTool.deriveTransparentAddress(seed: [UInt8](seedData)), expectedTransparentAddress)
} }
func testIsValidViewingKey() throws { func testIsValidViewingKey() throws {
@ -77,24 +73,25 @@ class DerivationToolMainnetTests: XCTestCase {
XCTAssertFalse(try derivationTool.isValidExtendedViewingKey("zxviews1q0dm7hkzky5skvnd9ldwj2u8fz2ry94s5q8p9lyp3j96yckudmp087d2jr2rnfuvjp7f56v78vpe658vljjddj7s645q399jd7")) XCTAssertFalse(try derivationTool.isValidExtendedViewingKey("zxviews1q0dm7hkzky5skvnd9ldwj2u8fz2ry94s5q8p9lyp3j96yckudmp087d2jr2rnfuvjp7f56v78vpe658vljjddj7s645q399jd7"))
} }
func testDeriveTransparentAccountPrivateKeyFromSeed() throws {
XCTAssertEqual(try derivationTool.deriveTransparentAccountPrivateKey(seed: [UInt8](seedData)), TransparentAccountPrivKey(encoding: "xprv9yCTU6giJ1qZ1DLC5rc7KMzwY9s8rSRXYqmoAKffAExpUVUKLhcdvN9ERdxjEW8tQq4pxerLKZE3WcNUKZCeX19rVTxpV2msTyNMNiFT3Nw"))
}
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 { func testDeriveQuiteALotOfUnifiedKeysFromSeed() throws {
let unifiedKeys = try derivationTool.deriveUnifiedFullViewingKeysFromSeed([UInt8](seedData), numberOfAccounts: 10) let numberOfAccounts: Int = 10
XCTAssertEqual(unifiedKeys.count, 10) let ufvks = try (0 ..< numberOfAccounts)
.map({
XCTAssertEqual(unifiedKeys[0].account, 0) try derivationTool.deriveUnifiedSpendingKey(
XCTAssertEqual(unifiedKeys[0], expectedViewingKey) seed: [UInt8](seedData),
accountIndex: $0
)
})
.map {
try derivationTool.deriveUnifiedFullViewingKey(
from: $0
)
}
XCTAssertEqual(ufvks.count, numberOfAccounts)
XCTAssertEqual(ufvks[0].account, 0)
XCTAssertEqual(ufvks[0], expectedViewingKey)
} }
func testShouldFailOnInvalidChecksumAddresses() throws { func testShouldFailOnInvalidChecksumAddresses() throws {
@ -102,10 +99,6 @@ class DerivationToolMainnetTests: XCTestCase {
XCTAssertFalse(try derivationTool.isValidTransparentAddress(testAddress)) XCTAssertFalse(try derivationTool.isValidTransparentAddress(testAddress))
} }
func testSpendingKeyValidation() throws {
XCTAssertTrue(try derivationTool.isValidSaplingExtendedSpendingKey(expectedSpendingKey.stringEncoded))
}
func testSpendingKeyValidationFailsOnInvalidKey() throws { func testSpendingKeyValidationFailsOnInvalidKey() throws {
let wrongSpendingKey = "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vvZzZzZz" let wrongSpendingKey = "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vvZzZzZz"

View File

@ -12,105 +12,103 @@ import XCTest
class DerivationToolTestnetTests: XCTestCase { 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 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==")! var seedData: Data = Data(base64Encoded: "9VDVOZZZOWWHpZtq1Ebridp3Qeux5C+HwiRR0g7Oi7HgnMs8Gfln83+/Q1NnvClcaSwM4ADFL1uZHxypEWlWXg==")!
let testRecipientAddress = UnifiedAddress(validatedEncoding: "utest1uqmec4a2njqz2z2rwppchsd06qe7a0jh4jmsqr0yy99m9er9646zlxunf3v8qr0hncgv86e8a62vxy0qa32qzetmj8s57yudmyx9zav6f52nurclsqjkqtjtpz6vg679p6wkczpl2wu", network: .testnet) //TODO: Parameterize this from environment let testRecipientAddress = UnifiedAddress(validatedEncoding: "utest1uqmec4a2njqz2z2rwppchsd06qe7a0jh4jmsqr0yy99m9er9646zlxunf3v8qr0hncgv86e8a62vxy0qa32qzetmj8s57yudmyx9zav6f52nurclsqjkqtjtpz6vg679p6wkczpl2wu") //TODO: Parameterize this from environment
let expectedSpendingKey = SaplingExtendedSpendingKey(validatedEncoding: "secret-extended-key-test1qdxykmuaqqqqpqqg3x5c02p4rhw0rtszr8ln4xl7g6wg6qzsqgn445qsu3cq4vd6lk8xce3d4jw7s8ln5yjp6fqv2g0nzue2hc0kv5t004vklvlenncscq9flwh5vf5qnv0hnync72n7gjn70u47765v3kyrxytx50g730svvmhhlazn5rj8mshh470fkrmzg4xarhrqlygg8f486307ujhndwhsw2h7ddzf89k3534aeu0ypz2tjgrzlcqtat380vhe8awm03f58cqe49swv") let expectedSpendingKey = UnifiedSpendingKey(
network: .testnet,
bytes: Data(base64Encoded: "tNDWwgMg6xrWm9j+A47jcwGN5dh/XkFK6GKTy5TYam5Y8UF6o+kCqQNMS2+dAAAAgAiJqYeoNR3c8a4CGf86m/5GnI0AUAInWtAQ5HAKsbr9jmxmLayd6B/zoSQdJAxSHzFzKr4fZlFvfVlvs/mc8QwAqfuvRiaAmx95knjyp+RKfn8r72qMjYgzEWaj0ei+DGbvf/RToOR9wvevnpsPYkVN0dxg+RCDpqfUX+5K82uvByr+a0STltGka9zx5AiUuSBi/gC+rid7L5P123xTQ+AAQAJO8vbUxpLCW2IvT1HEYhBOtKJDvC1Wp+wmBUmTmhG1aw/JybD+N5IY6PgiY2fiU43KI7tW9HZAlQTKitT+9m8=")!.bytes,
account: 0
)
let expectedViewingKey = UnifiedFullViewingKey(validatedEncoding: "uviewtest12tkgzhaevmw78us4xj2cx6ehxjgpp5da2qwrjqvytztejqfjdmy3e6nryqggtwrjum5cefuuuky8rscuw5dynmjec2tx3kkupqexw4va879pf874kvp6r8kjeza26gysxllaqwl67hm9u0jjke06zc93asrpw4wmy3g0lr9r5cy9pz49q2g7y7wm2pls5akmzhuvqr7khftk93aa2kpvwp7n3sjtmef28mxg3n2rpctsjlgsrhc29g6r23qc0u4tzd8rz8vqq4j7jxummdts8zx0jatzw4l2tl7r3egxhlw587rtkjx0y6dvw4hf4vjprn0qv3hs0sulmavk84ajeewn7argyerpr4essqvgfd0d24jpz6phxlasnd58qazh9d3yc6ad3hc5atp0pkvlq053zga65gscp0pv2plhqj9y2tcmx43thw5g4v8z3unytkc2dhyttuhmnlh5dyz4rmhgfkc96tp8z8rpfe35whjvky0jagz5n7qx", account: 0) let expectedViewingKey = UnifiedFullViewingKey(validatedEncoding: "uviewtest12tkgzhaevmw78us4xj2cx6ehxjgpp5da2qwrjqvytztejqfjdmy3e6nryqggtwrjum5cefuuuky8rscuw5dynmjec2tx3kkupqexw4va879pf874kvp6r8kjeza26gysxllaqwl67hm9u0jjke06zc93asrpw4wmy3g0lr9r5cy9pz49q2g7y7wm2pls5akmzhuvqr7khftk93aa2kpvwp7n3sjtmef28mxg3n2rpctsjlgsrhc29g6r23qc0u4tzd8rz8vqq4j7jxummdts8zx0jatzw4l2tl7r3egxhlw587rtkjx0y6dvw4hf4vjprn0qv3hs0sulmavk84ajeewn7argyerpr4essqvgfd0d24jpz6phxlasnd58qazh9d3yc6ad3hc5atp0pkvlq053zga65gscp0pv2plhqj9y2tcmx43thw5g4v8z3unytkc2dhyttuhmnlh5dyz4rmhgfkc96tp8z8rpfe35whjvky0jagz5n7qx", account: 0)
let expectedSaplingExtendedViewingKey = SaplingExtendedFullViewingKey(validatedEncoding: "zxviewtestsapling1qdxykmuaqqqqpqqg3x5c02p4rhw0rtszr8ln4xl7g6wg6qzsqgn445qsu3cq4vd6l5smlqrckkl2x5rnrauzc4gp665q3zyw0qf2sfdsx5wpp832htfavqk72uchuuvq2dpmgk8jfaza5t5l56u66fpx0sr8ewp9s3wj2txavmhhlazn5rj8mshh470fkrmzg4xarhrqlygg8f486307ujhndwhsw2h7ddzf89k3534aeu0ypz2tjgrzlcqtat380vhe8awm03f58cqgegsaj") let expectedSaplingExtendedViewingKey = SaplingExtendedFullViewingKey(validatedEncoding: "zxviewtestsapling1qdxykmuaqqqqpqqg3x5c02p4rhw0rtszr8ln4xl7g6wg6qzsqgn445qsu3cq4vd6l5smlqrckkl2x5rnrauzc4gp665q3zyw0qf2sfdsx5wpp832htfavqk72uchuuvq2dpmgk8jfaza5t5l56u66fpx0sr8ewp9s3wj2txavmhhlazn5rj8mshh470fkrmzg4xarhrqlygg8f486307ujhndwhsw2h7ddzf89k3534aeu0ypz2tjgrzlcqtat380vhe8awm03f58cqgegsaj")
let expectedSaplingAddress = SaplingAddress(validatedEncoding: "ztestsapling1475xtm56czrzmleqzzlu4cxvjjfsy2p6rv78q07232cpsx5ee52k0mn5jyndq09mampkgvrxnwg") let expectedSaplingAddress = SaplingAddress(validatedEncoding: "ztestsapling1475xtm56czrzmleqzzlu4cxvjjfsy2p6rv78q07232cpsx5ee52k0mn5jyndq09mampkgvrxnwg")
let derivationTool = DerivationTool(networkType: NetworkType.testnet) let derivationTool = DerivationTool(networkType: NetworkType.testnet)
let expectedTransparentAddress = TransparentAddress(validatedEncoding: "tmXuTnE11JojToagTqxXUn6KvdxDE3iLKbp") let expectedTransparentAddress = TransparentAddress(validatedEncoding: "tmXuTnE11JojToagTqxXUn6KvdxDE3iLKbp")
func testDeriveViewingKeysFromSeed() throws { func testDeriveViewingKeysFromSeed() throws {
let accounts: Int = 1
let seedBytes = [UInt8](seedData) 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") let spendingKey = try derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: 0)
let viewingKey = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
guard let viewingKey = viewingKeys.first else {
XCTFail("no viewing key generated")
return
}
XCTAssertEqual(expectedViewingKey, viewingKey) XCTAssertEqual(expectedViewingKey, viewingKey)
} }
func testDeriveViewingKeyFromSpendingKeys() throws { func testDeriveViewingKeyFromSpendingKeys() throws {
XCTAssertEqual(expectedSaplingExtendedViewingKey, try derivationTool.deriveViewingKey(spendingKey: expectedSpendingKey)) // XCTAssertEqual(
// expectedViewingKey,
// try derivationTool.deriveUnifierFullViewingKey(from: expectedSpendingKey)
// )
} }
func testDeriveSpendingKeysFromSeed() throws { func testDeriveSpendingKeysFromSeed() throws {
let accounts: Int = 1
let seedBytes = [UInt8](seedData) let seedBytes = [UInt8](seedData)
let spendingKeys = try derivationTool.deriveSpendingKeys(seed: seedBytes, numberOfAccounts: accounts) let spendingKey = try derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: 0)
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) XCTAssertEqual(expectedSpendingKey, spendingKey)
} }
func testDeriveUnifiedAddressFromSeed() throws { func testDeriveUnifiedSpendingKeyFromSeed() throws {
let account = 0
let seedBytes = [UInt8](seedData) let seedBytes = [UInt8](seedData)
let unifiedAddress = try derivationTool.deriveUnifiedAddress(seed: seedBytes, accountIndex: 0) XCTAssertNoThrow(try derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: account))
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 { func testGetTransparentAddressFromUA() throws {
XCTAssertTrue(try derivationTool.isValidSaplingExtendedSpendingKey(expectedSpendingKey.stringEncoded)) XCTAssertEqual(
try DerivationTool.transparentReceiver(from: testRecipientAddress),
expectedTransparentAddress
)
}
func testIsValidViewingKey() throws {
XCTAssertTrue(try derivationTool.isValidExtendedViewingKey("zxviewtestsapling1qdxykmuaqqqqpqqg3x5c02p4rhw0rtszr8ln4xl7g6wg6qzsqgn445qsu3cq4vd6l5smlqrckkl2x5rnrauzc4gp665q3zyw0qf2sfdsx5wpp832htfavqk72uchuuvq2dpmgk8jfaza5t5l56u66fpx0sr8ewp9s3wj2txavmhhlazn5rj8mshh470fkrmzg4xarhrqlygg8f486307ujhndwhsw2h7ddzf89k3534aeu0ypz2tjgrzlcqtat380vhe8awm03f58cqgegsaj"))
XCTAssertFalse(try derivationTool.isValidExtendedViewingKey("zxviews1q0dm7hkzky5skvnd9ldwj2u8fz2ry94s5q8p9lyp3j96yckudmp087d2jr2rnfuvjp7f56v78vpe658vljjddj7s645q399jd7"))
}
func testDeriveQuiteALotOfUnifiedKeysFromSeed() throws {
let numberOfAccounts: Int = 10
let ufvks = try (0 ..< numberOfAccounts)
.map({
try derivationTool.deriveUnifiedSpendingKey(
seed: [UInt8](seedData),
accountIndex: $0
)
})
.map {
try derivationTool.deriveUnifiedFullViewingKey(
from: $0
)
}
XCTAssertEqual(ufvks.count, numberOfAccounts)
XCTAssertEqual(ufvks[0].account, 0)
XCTAssertEqual(ufvks[0], expectedViewingKey)
}
func testShouldFailOnInvalidChecksumAddresses() throws {
let testAddress = "t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1"
XCTAssertFalse(try derivationTool.isValidTransparentAddress(testAddress))
} }
func testSpendingKeyValidationFailsOnInvalidKey() throws { func testSpendingKeyValidationFailsOnInvalidKey() throws {
let wrongSpendingKey = "secret-extended-key-test1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vvZzZzZz" let wrongSpendingKey = "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vvZzZzZz"
XCTAssertFalse(try derivationTool.isValidSaplingExtendedSpendingKey(wrongSpendingKey)) XCTAssertFalse(try derivationTool.isValidSaplingExtendedSpendingKey(wrongSpendingKey))
} }
// TODO: Address encoding does not catch this test https://github.com/zcash/ZcashLightClientKit/issues/509 // TODO: Address encoding does not catch this test https://github.com/zcash/ZcashLightClientKit/issues/509
// func testSpendingKeyValidationThrowsWhenWrongNetwork() throws { // func testSpendingKeyValidationThrowsWhenWrongNetwork() throws {
// XCTAssertThrowsError(try derivationTool.isValidExtendedSpendingKey("secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv")) // XCTAssertThrowsError(try derivationTool.isValidExtendedSpendingKey("secret-extended-key-test1qdxykmuaqqqqpqqg3x5c02p4rhw0rtszr8ln4xl7g6wg6qzsqgn445qsu3cq4vd6lk8xce3d4jw7s8ln5yjp6fqv2g0nzue2hc0kv5t004vklvlenncscq9flwh5vf5qnv0hnync72n7gjn70u47765v3kyrxytx50g730svvmhhlazn5rj8mshh470fkrmzg4xarhrqlygg8f486307ujhndwhsw2h7ddzf89k3534aeu0ypz2tjgrzlcqtat380vhe8awm03f58cqe49swv"))
// } // }
} }

View File

@ -57,10 +57,10 @@ class NullBytesTests: XCTestCase {
} }
switch rustError { switch rustError {
case .malformedStringInput: case .invalidInput:
XCTAssertTrue(true) XCTAssertTrue(true)
default: default:
XCTFail("expected \(RustWeldingError.malformedStringInput) and got \(rustError)") XCTFail("expected \(RustWeldingError.invalidInput) and got \(rustError)")
} }
} }
@ -81,39 +81,41 @@ class NullBytesTests: XCTestCase {
} }
switch rustError { switch rustError {
case .malformedStringInput: case .invalidInput:
XCTAssertTrue(true) XCTAssertTrue(true)
default: default:
XCTFail("expected \(RustWeldingError.malformedStringInput) and got \(rustError)") XCTFail("expected \(RustWeldingError.invalidInput) and got \(rustError)")
} }
} }
} }
func testderiveExtendedFullViewingKeyWithNullBytes() throws { func testderiveExtendedFullViewingKeyWithNullBytes() throws {
// swiftlint:disable:next line_length
let wrongSpendingKeys = SaplingExtendedSpendingKey(validatedEncoding: "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mq\0uy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv") // this spending key corresponds to the "demo app reference seed"
// swiftlint:disable:next line_length // TODO: fix
let goodSpendingKeys = SaplingExtendedSpendingKey(validatedEncoding: "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv") // // swiftlint:disable:next line_length
// let wrongSpendingKeys = SaplingExtendedSpendingKey(validatedEncoding: "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mq\0uy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv") // this spending key corresponds to the "demo app reference seed"
XCTAssertThrowsError( //
try ZcashRustBackend.deriveSaplingExtendedFullViewingKey(wrongSpendingKeys, networkType: networkType), // // swiftlint:disable:next line_length
"Should have thrown an error but didn't! this is dangerous!" // let goodSpendingKeys = SaplingExtendedSpendingKey(validatedEncoding: "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv")
) { error in //
guard let rustError = error as? RustWeldingError else { // XCTAssertThrowsError(
XCTFail("Expected RustWeldingError") // try ZcashRustBackend.deriveSaplingExtendedFullViewingKey(wrongSpendingKeys, networkType: networkType),
return // "Should have thrown an error but didn't! this is dangerous!"
} // ) { error in
// guard let rustError = error as? RustWeldingError else {
switch rustError { // XCTFail("Expected RustWeldingError")
case .malformedStringInput: // return
XCTAssertTrue(true) // }
default: //
XCTFail("expected \(RustWeldingError.malformedStringInput) and got \(rustError)") // switch rustError {
} // case .malformedStringInput:
} // XCTAssertTrue(true)
// default:
XCTAssertNoThrow(try ZcashRustBackend.deriveSaplingExtendedFullViewingKey(goodSpendingKeys, networkType: networkType)) // XCTFail("expected \(RustWeldingError.malformedStringInput) and got \(rustError)")
// }
// }
//
// XCTAssertNoThrow(try ZcashRustBackend.deriveSaplingExtendedFullViewingKey(goodSpendingKeys, networkType: networkType))
} }
func testCheckNullBytes() throws { func testCheckNullBytes() throws {

View File

@ -15,7 +15,7 @@ final class RecipientTests: XCTestCase {
let transparentString = "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz" let transparentString = "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz"
func testUnifiedRecipient() throws { func testUnifiedRecipient() throws {
let expectedUnifiedAddress = UnifiedAddress(validatedEncoding: uaString, network: .mainnet) let expectedUnifiedAddress = UnifiedAddress(validatedEncoding: uaString)
XCTAssertEqual(try Recipient(uaString, network: .mainnet), .unified(expectedUnifiedAddress)) XCTAssertEqual(try Recipient(uaString, network: .mainnet), .unified(expectedUnifiedAddress))
} }

View File

@ -1,220 +1,73 @@
////
//// UnifiedTypecodesTests.swift
//// OfflineTests
////
//// Created by Francisco Gindre on 9/14/22.
////
// //
// UnifiedTypecodesTests.swift //import XCTest
// OfflineTests //@testable import ZcashLightClientKit
//final class UnifiedTypecodesTests: XCTestCase {
// //
// Created by Francisco Gindre on 9/14/22. // func testVectorBuilding() throws {
// guard let testVectors = TestVector.hexStringtestVectors else {
// XCTFail("fail to construct vectors")
// return
// }
// //
// XCTAssertEqual(testVectors.count, 60)
import XCTest // }
@testable import ZcashLightClientKit //
final class UnifiedTypecodesTests: XCTestCase { // func testUnifiedAddressHasTransparentSaplingReceiversBackend() throws {
// guard let testVectors = TestVector.testVectors,
func testVectorBuilding() throws { // let firstVector = testVectors.first,
guard let testVectors = UnifiedAddress.testVectors else { // let uAddress = firstVector.unified_addr else {
XCTFail("fail to construct vectors") // XCTFail("fail to construct vectors")
return // return
} // }
//
XCTAssertEqual(testVectors.count, 20) //
} // let address = UnifiedAddress(validatedEncoding: uAddress)
//
func testUnifiedAddressHasTransparentSaplingReceiversBackend() throws { // let typecodes = try ZcashRustBackend.receiverTypecodesOnUnifiedAddress(address.stringEncoded)
guard let testVectors = UnifiedAddress.testVectors, //
let firstVector = testVectors.first, // XCTAssertEqual(typecodes, [0x03, 0, 65533])
let seed = firstVector.root_seed else { //
XCTFail("fail to construct vectors") // }
return //
} // func testUnifiedAddressHasTransparentSaplingReceivers() throws {
// guard let testVectors = TestVector.testVectors,
// let firstVector = testVectors.first,
let address = try DerivationTool(networkType: .mainnet).deriveUnifiedAddress(seed: seed, accountIndex: Int(firstVector.account)) // let uAddress = firstVector.unified_addr else {
// XCTFail("fail to construct vectors")
let typecodes = try ZcashRustBackend.receiverTypecodesOnUnifiedAddress(address.stringEncoded) // return
// }
XCTAssertEqual(Set(typecodes), Set([0x00, 0x02])) //
//
} // let address = UnifiedAddress(validatedEncoding: uAddress, network: .mainnet)
//
func testUnifiedAddressHasTransparentSaplingReceivers() throws { // let typecodes = try DerivationTool(networkType: .mainnet).receiverTypecodesFromUnifiedAddress(address)
guard let testVectors = UnifiedAddress.testVectors, //
let firstVector = testVectors.first, // XCTAssertEqual(
let seed = firstVector.root_seed else { // Set<UnifiedAddress.ReceiverTypecodes>(typecodes),
XCTFail("fail to construct vectors") // Set([
return // .unknown(65533),
} // .orchard,
// .p2pkh
let tool = DerivationTool(networkType: .mainnet) // ])
let address = try tool.deriveUnifiedAddress(seed: seed, accountIndex: Int(firstVector.account)) // )
// }
let typecodes = try tool.receiverTypecodesFromUnifiedAddress(address) //
// func testReceiverTypecodes() {
XCTAssertEqual( // XCTAssertEqual(UnifiedAddress.ReceiverTypecodes(typecode: 0x00), .p2pkh)
Set<UnifiedAddress.ReceiverTypecodes>(typecodes), // XCTAssertEqual(UnifiedAddress.ReceiverTypecodes(typecode: 0x01), .p2sh)
Set([UnifiedAddress.ReceiverTypecodes.p2pkh, .sapling]) // XCTAssertEqual(UnifiedAddress.ReceiverTypecodes(typecode: 0x02), .sapling)
) // XCTAssertEqual(UnifiedAddress.ReceiverTypecodes(typecode: 0x03), .orchard)
} // XCTAssertEqual(UnifiedAddress.ReceiverTypecodes(typecode: 0x0F), .unknown(0x0F))
// }
func testReceiverTypecodes() { //
XCTAssertEqual(UnifiedAddress.ReceiverTypecodes(typecode: 0x00), .p2pkh) // func testExtractTypecode() throws {
XCTAssertEqual(UnifiedAddress.ReceiverTypecodes(typecode: 0x01), .p2sh) // let ua = UnifiedAddress(validatedEncoding: "u1l9f0l4348negsncgr9pxd9d3qaxagmqv3lnexcplmufpq7muffvfaue6ksevfvd7wrz7xrvn95rc5zjtn7ugkmgh5rnxswmcj30y0pw52pn0zjvy38rn2esfgve64rj5pcmazxgpyuj", network: .mainnet)
XCTAssertEqual(UnifiedAddress.ReceiverTypecodes(typecode: 0x02), .sapling) // XCTAssertEqual(try ua.availableReceiverTypecodes(), [.sapling, .p2pkh])
XCTAssertEqual(UnifiedAddress.ReceiverTypecodes(typecode: 0x03), .orchard) // }
XCTAssertEqual(UnifiedAddress.ReceiverTypecodes(typecode: 0x0F), .unknown(0x0F)) //}
}
func testExtractTypecode() throws {
let ua = UnifiedAddress(validatedEncoding: "u1l9f0l4348negsncgr9pxd9d3qaxagmqv3lnexcplmufpq7muffvfaue6ksevfvd7wrz7xrvn95rc5zjtn7ugkmgh5rnxswmcj30y0pw52pn0zjvy38rn2esfgve64rj5pcmazxgpyuj", network: .mainnet)
XCTAssertEqual(try ua.availableReceiverTypecodes(), [.sapling, .p2pkh])
}
}
extension UnifiedAddress {
/// Test vectors for unified addresses
/// Original file can be found here https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/test-vectors/json/unified_address.json
/// Note: These vectors can't be used as-is since we are missing derivation functions that take a DiversifiedIndex
static var testVectors: [TestVector]? {
var vectors = [TestVector]()
for rawVector in UnifiedAddress.testVector.dropFirst(2) {
guard let vector = TestVector(from: rawVector) else {
return nil
}
vectors.append(vector)
}
return vectors
}
static let testVector: [[Any?]] =
[
["From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/unified_address.py"],
["p2pkh_bytes, p2sh_bytes, sapling_raw_addr, orchard_raw_addr, unknown_typecode, unknown_bytes, unified_addr, root_seed, account, diversifier_index"],
["e6cabf813929132d772d04b03ae85223d03b9be8", nil, nil, "d4714ee761d1ae823b6972152e20957fefa3f6e3129ea4dfb0a9e98703a63dab929589d6dc51c970f935b3", 65533, "f6ee6921481cdd86b3cc4318d9614fc820905d042bb1ef9ca3f24988c7b3534201cfb1cd8dbf69b8250c18ef41294ca97993db546c1fe0", "753179793677386e336a6d6a73676a39777663656e7238723570366833387679636c686d71307767396b7a70786c7534367a387636346b3567737a72387966777a346a7672796c76766733673633337a30326c756b38356e6d73636b366432736578336e3564376b6e3638687a7a3574763475647439703673793770676c6565756c76676c767832363237646666353771396665703577676478386d3065737832386d307a767578706d7779617a74336a756e3272707177386e75366a326663657167686b353563656436366a73366b366a786e387932787475653866337061716a726b3871366e70746e6e", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 0, 0],
["7bec9de217c04f7ce1a86f1fb458aa881c8f39e4", nil, nil, "d8e5ecb4e005c28718e61a5c336a4f369e771ccdb3363f4f7a04b02a966901a4c05da662d5fd75678f7fb4", 65530, nil, "75317a35677538783364766b7677636d726a30716b3568727839706361646c3536683834663777647970366e7635337233643563636365646563686d77393835746765357733633272353639716137326c676775753578727178683739616a7a63376b716d65733230706b747a71726a6c707835367168676d716d3536686e39777432686379787064616d616b", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 1, 0],
["aa6d43480fd9d91375ce6c4a020706361bd296de", nil, "88533c398a49c2513dc85162bf220abaf47dc983f14e908ddaaa7322dba16531bc62efe750fe575c8d149b", nil, 65530, nil, "7531343367706a3772643934766d39356d7a73757537746a74716161677934706d6678386c6b77656d70786a7463777a33357a746361383530796e6c7a323932307477617a6171703270367168787878337a357178616b6e73716372676c7578716a337070757367776635757963686c61677938376b376874613768773965793336776d7930367065776c6470", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 2, 0],
[nil, "a8d7551db5fd9313e8c7203d996af7d477083756", "52fd6aedefbf401633c2e4532515ebcf95bcc2b4b8e4d676dfad7e17925c6dfb8671e52544dc2ca075e261", nil, 65534, nil, "753178797970646a307a7978637466666b6878796d766a6e6b376e383371666c376e7365356c3071726b346e3266376465376c3733727a79787970347463727975356d6b7875617a6c646e633279306479747a7567797a79636739373034616a66786173376b63757761776d706877776e383839743938743735376579716667346a766566746b687672337167", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 3, 0],
[nil, "f44ab023752cb5b406ed8985e18130ab33362697", nil, "165082de84f2ad7204426ffafd6b6c7de9cab6d25c13846a1786715268c415948db788f4a5e0daa03d699e", 65533, nil, "7531706a336c72656d6e7175737368393878667161336a66647077303872726b35377330346b6c32366865707a7133746a72736e78653574367371716567653976716d776c63366c786373746e6333306e3575357232776b6b7a687039367a3564306a797530716137746b686378366663386a35396b616b387a35636570363261716d61336d36343566683863", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 4, 0],
[nil, nil, nil, "ea9df83fbee07d6f7895ebb2ea41ec7c4ba682b863e069b4a438e31c9571c83126c305d75456412aeaef1b", 65531, nil, "753132787567643930666c726b646b6575336e6c6e6e337565736b793533707175356d323479366170786d38386d34387637333734636c7335367a7039336e61796c617864636866307161796678747267653034376d393533717a3376326772346c74737232736b3372", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 5, 0],
[nil, nil, nil, "3c40246912b6efefab9a55244ac2c174e1a9f8c0bc0fd526933963c6ecb9b84ec8b0f6b40dc858fa23c72b", 65530, nil, "75317370757467353667736a763233637435346d7277646c616e7a7665716337747a73356d78786e616135636465676d303368673778363661797079647336356d39327674397561786c3637327375687063367a3768747776657079686b727066757376617a71756539", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 6, 0],
[nil, "defa3d5a57efc2e1e9b01a035587d5fb1a38e01d", nil, "cc099cc214e56b1192c7b5b17e958c3413e27fefd553380700aca81b24b2918cac951a1a68017fac525a18", 65535, nil, "75317667736b636d3939783567687561757668337978713777747037756e366130793663617964736e6e33357032647577707773356873367079676a6877703738326a716e65727a6c6878773370343971666d713237383339716a7472667976686b377964393877396e3064366a6e7336756834666333687364663736366b6e74716e6c6a646b64353667636e", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 7, 0],
[nil, nil, nil, "5f09a9807a56323b263b05df368dc28391b21a64a0e1b40f9a6803b7e68f3905923f35cb01f119b223f493", 65530, nil, "75316378636379656d6d3038747964776d743968703273356e6638776a766c757575366c32653861396a666c6c647861736e7a6b6438667665727170636a30786e767261637a71673235356377356e767936783977727566666d703975657a727a72376763783535396b", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 8, 0],
[nil, "10acd20b183e31d49f25c9a138f49b1a537edcf0", "9b60ae3d302248b349d601567e3d7795bfb334ea1fd1a7e71402169ebbe14bd2ceaa244ccd6e5aa2245613", "e340636542ece1c81285ed4eab448adbb5a8c0f4d386eeff337e88e6915f6c3ec1b6ea835a88d56612d2bd", 65531, nil, "75317a656b68686d686b353478356365356333367274376e63323735676570376e6176326e73783473683061666c6c75703976726835687338367a38736b6a746436646e736c7667736d6174743068386832343763676e666b73646c776c39786d617275797570666c743064716673637830647979656d3266616139776571653378616b397736656672353437636a3832397232746e7974613032687866647873646a6d76397a72356b746b70323066706378656164686672683032616b346136686e7876357336377267717272766670646a7435", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 9, 0],
[nil, "af9db6990ed83dd64af3597c04323ea51b0052ad", nil, "cdf7fed0d0822fd849cffb20a4d5ee701ad8141e66d81ddfabf87875117c05092240603c546b8dc187cd8c", 65532, nil, "753165353471636e30746570796c33307a7a326672677a37713461366d736e326530326e7076326e6666736433683532336d747838643232616a7666767371757235736a7a3876666e6d77327973363730387170386b6139306a3561343330757938763833616c6a63306330357a6a7535347879356e7677336d66686b376e7737366b6b7964796c713466656c", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 10, 0],
[nil, nil, nil, "24fd59f32b2d39dde66e46c39206a31bc04fa5c6847976ea6bbd3163ee14f58f584acc131479ea558d3f84", 65530, nil, "75317a38777372686d66366d3967766136766c33737a636b303670393730783577686d36336a666a3266726d6d63396e39756d34796373387975746a37673833387672676832306c667879353279306832367474386e6776643267796370797176396b793032716b6373", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 11, 0],
[nil, nil, "78d85bd0db639043377987cdd814c6390016964b684016faf1ad4f166c5f72399a5e8d469ec6beb873d55d", nil, 65535, nil, "75317861686a333570376d7639756c6b3337327333766465687172663438753077646633786c3772787a7270653461307468753864306d396d7961617078376b35767836747a357074636a76637675346472667137753771777a6d667565336b74387376736333736535", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 12, 0],
["33a6dd87b4d872a4895d345761e4ec423b77928d", nil, nil, "5178924f7067eac261044ca27ba3cf52f798486973af0795e61587aa1b1ecad333dc520497edc61df88980", 65533, "91e00c7a1d48af046827591e9733a97fa6b679f3dc601d008285edcbdae69ce8fc1be4aac00ff2711ebd931de518856878f73476f21a482ec9378365c8f7393c94e2885315eb4671098b79535e790fe53e29fef2b3766697ac32b4f473f468a008e72389fc03880d780cb07fcfaabe3f1a84b27db59a4a153d882d2b2103596555ed9494c6ac893c49723833ec8926c1", "7531687970706c733364776d616c783373756c746b72397564763237376679716a6478307378716c746638676a6e777976343968743575327270336c6c767632756e796d7330383675616a6b6638393837636175616a7136383670356638687276393474616336663078796637796d7a3636747279366b7936726179336d6a633567786661683030637370766b3564676d67736e3737663274336775763270307861366b6c6138717479376d6b6e6b6d337a68303932306c77733633326166743071686b3532363579736c337067323237747866373461736d7075656e326c746533616a6330667a376b34736878797a656d6e7035773770336b746c6874643030366d6b61787979306d746637646a73646175397a666b657332616e387661687a6737647173677938326330707830396d39683061657a736e7936786c66706767667268656d7661786a3578747871356a6e67763076306167726c3073757079676639636574656a35323779727a7a6574386471747164616771", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 13, 0],
["a56c057ef71dab58aa90e47025695c5faaea5123", nil, "a75a6de421d2ad1ee8f4b25e398adda9c0aaa6ab1f2518981a9ddb1de6a3957d77842332d6289dbe94e832", "b208c9235c8d40e49b76100b2d010f3783f12c66e7d3beb117b2c96321b7f6562adb4efc144e39d909e728", 65533, nil, "7531646670723876647335683361756e79657a7a7877726d38756461353273743837733876726c676732746730357430713070783336686368783974676b786b6c77747370753332786a6135617271336b7470326e387a613470773779776a30676d68713372776539353072386b3973756e736a76773734743538716c3333347065673464766b616c6b746d6e676e716b7077723332353837653779747932376e6d673636747371377976723779343639776570366b7077346a3530786e6c6d78306a78786737766c6735796c6671387566657664", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 14, 0],
[nil, nil, nil, "9e5445d6cd3cb9f98b0df1062bda47adffd5a66c0c2c483c8bf15c3176d755914a3576496b5c35fee28a88", 65531, nil, "75316a676c686a326d617936646674777a39753271796e786a717a6e75743637343768617375306d646d6c63303266636173756178756764797a776a326c38346d6a3966677a6a3779306b396663706a373336736c6d6a38676b37377567386c6c61766367326c666d6d", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 15, 0],
["b02aec10f6fa02a08667bf9b924c3d0574a1334f", nil, nil, "2598d84dffb34f5908b90732490f3881399150d4c694fce9bf30d1560b2c56f09829fe123b9add20e5d71c", 65534, nil, "7531397163617a647761793438707566366a77616a78307732386d307871756d746d6e6435677974796c6c6e79676867396c76393978356d3872387439673566396a307a30786e34787a6d6e7866747a3772746633756164786b79367178706e6b7438666b66686c78386b63396d6e72646c6e7874733536786378656a7a6472776c65787a7637377876797634", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 16, 0],
[nil, nil, "d3a803803feee7a032a24adfaa8f6a94cecb9671c1333d0d5d1a3d79d82bc310727c665364d71022559c50", "7c98b8f613f9ff02746bea2a167cfd1bd3a1862af9631bf61d9d604e0824e2cb8467a1e549db87a76e7a8a", 65535, nil, "75316136346c303971727378756c666a7a6e6d366b326735333575737968746166386564363076346a726a6d6b77766b757834743770647963336e6b7a7265666467746e77383432306c6a3873686d30356a6139667878676e68726139326e6873713536677838633270757a33666b6b676e726b7166357975716664746637743672616e343767646366357676646661637a7766337575793466797368336d7a7538686435746b6c30356d76726765396e38", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 17, 0],
["26c061d67beb8bad48c6b4774a156551e30e4fe2", nil, nil, "a80405d5568ab8ab8f8546163d951ab297fd5e6f43e7fcebcb664feacfab5afd80aaf7f354c07a9901788c", 65535, nil, "7531787a757764386163686667776d336577793976326d6a3537373268726b6e6d6578777a6339346d7a6133356d78363863656e767877727a3973396670306e39767a753872756a357a71666d6d376c65387775366c363275346c6d30376e75717865656d383733677838366a766e776c70787379636c397576366b786b72686d30726c677037307830357366", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 18, 0],
[nil, nil, "8660070e3757ff6507060791fd694f6a631b8495a2b74ffa39236cf653caea5575b86af3200b010e513bab", "63b7b706d991169986aee56133f0a50b2a0c8225fba6dae95176007b1f023a1e97c1aa366e99bf970fda82", 65534, nil, "7531766736326d676a64646e6c763577366c646b793278653063387465746d633832747539766c7a7a6b75796e783439666e75716a76786a743564676e33636d3874356e38357a6371356c6a727467377a6d77686b3730683672646d636c6637736378786e67756b35666c76663261707037367875393037636d6a796c787673656e3235786539763776336b727378613975793076326a6a7133376b6834796d6c61666e3870657671616c716134646d3637", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 19, 5]
]
struct TestVector {
enum Indices: Int, CaseIterable {
case p2pkh_bytes = 0
case p2sh_bytes
case sapling_raw_addr
case orchard_raw_addr
case unknown_typecode
case unknown_bytes
case unified_addr
case root_seed
case account
case diversifier_index
}
var p2pkh_bytes: [UInt8]?
var p2sh_bytes: [UInt8]?
var sapling_raw_addr: [UInt8]?
var orchard_raw_addr: [UInt8]?
var unknown_typecode: UInt32 = UInt32.max
var unknown_bytes: [UInt8]?
var unified_addr: [UInt8]?
var root_seed: [UInt8]?
var account: UInt32 = 0
var diversifier_index: UInt32 = 0
static func optionalByteArrayKeyPath(from index: Indices) -> WritableKeyPath<TestVector, [UInt8]?>? {
switch index {
case .p2pkh_bytes:
return \Self.p2pkh_bytes
case .p2sh_bytes:
return \Self.p2sh_bytes
case .sapling_raw_addr:
return \Self.sapling_raw_addr
case .orchard_raw_addr:
return \Self.orchard_raw_addr
case .unknown_bytes:
return \Self.unknown_bytes
case .unified_addr:
return \Self.unified_addr
case .root_seed:
return \Self.root_seed
default:
return nil
}
}
static func uintKeyPath(from index: Indices) -> WritableKeyPath<TestVector, UInt32>? {
switch index {
case .unknown_typecode:
return \Self.unknown_typecode
case .account:
return \Self.account
case .diversifier_index:
return \Self.diversifier_index
default:
return nil
}
}
init?(from rawVector: [Any?]) {
guard rawVector.count == Indices.diversifier_index.rawValue + 1 else { return nil }
for varIndex in Indices.allCases {
switch varIndex {
case .p2pkh_bytes,
.p2sh_bytes,
.sapling_raw_addr,
.orchard_raw_addr,
.unknown_bytes,
.unified_addr,
.root_seed:
guard let keyPath = Self.optionalByteArrayKeyPath(from: varIndex) else {
return nil
}
if rawVector[varIndex.rawValue] == nil {
self[keyPath: keyPath] = nil
break
}
guard let hexString = rawVector[varIndex.rawValue] as? String,
let data = Data(fromHexEncodedString: hexString) else { return nil }
self[keyPath: keyPath] = data.bytes
case .unknown_typecode,
.account,
.diversifier_index:
guard rawVector[varIndex.rawValue] != nil else { return nil }
guard let keyPath = Self.uintKeyPath(from: varIndex) else { return nil }
guard let optionalValue = rawVector[varIndex.rawValue],
let intValue = optionalValue as? Int,
let uintValue = UInt32(exactly: intValue) else {
return nil
}
self[keyPath: keyPath] = uintValue
}
}
}
}
}

View File

@ -35,7 +35,8 @@ class WalletTests: XCTestCase {
func testWalletInitialization() throws { func testWalletInitialization() throws {
let derivationTool = DerivationTool(networkType: network.networkType) let derivationTool = DerivationTool(networkType: network.networkType)
let ufvk = try derivationTool.deriveUnifiedFullViewingKeysFromSeed(seedData.bytes, numberOfAccounts: 1) let ufvk = try derivationTool.deriveUnifiedSpendingKey(seed: seedData.bytes, accountIndex: 0)
.map( { try derivationTool.deriveUnifiedFullViewingKey(from: $0) })
let wallet = Initializer( let wallet = Initializer(
cacheDbURL: try __cacheDbURL(), cacheDbURL: try __cacheDbURL(),
dataDbURL: try __dataDbURL(), dataDbURL: try __dataDbURL(),
@ -44,7 +45,7 @@ class WalletTests: XCTestCase {
network: network, network: network,
spendParamsURL: try __spendParamsURL(), spendParamsURL: try __spendParamsURL(),
outputParamsURL: try __outputParamsURL(), outputParamsURL: try __outputParamsURL(),
viewingKeys: ufvk, viewingKeys: [ufvk],
walletBirthday: 663194 walletBirthday: 663194
) )

View File

@ -81,10 +81,58 @@ extension LightWalletServiceMockResponse {
} }
class MockRustBackend: ZcashRustBackendWelding { class MockRustBackend: ZcashRustBackendWelding {
static func receiverTypecodesOnUnifiedAddress(_ ua: String) throws -> [UInt32] { static func clearUtxos(dbData: URL, address: ZcashLightClientKit.TransparentAddress, sinceHeight: ZcashLightClientKit.BlockHeight, networkType: ZcashLightClientKit.NetworkType) throws -> Int32 {
0
}
static func getTransparentBalance(dbData: URL, account: Int32, networkType: ZcashLightClientKit.NetworkType) throws -> Int64 {
0
}
static func getVerifiedTransparentBalance(dbData: URL, account: Int32, networkType: ZcashLightClientKit.NetworkType) throws -> Int64 {
0
}
static func listTransparentReceivers(dbData: URL, account: Int32, networkType: ZcashLightClientKit.NetworkType) throws -> [ZcashLightClientKit.TransparentAddress] {
[] []
} }
static func deriveUnifiedFullViewingKey(from spendingKey: ZcashLightClientKit.UnifiedSpendingKey, networkType: ZcashLightClientKit.NetworkType) throws -> ZcashLightClientKit.UnifiedFullViewingKey {
throw KeyDerivationErrors.unableToDerive
}
static func deriveUnifiedSpendingKey(from seed: [UInt8], accountIndex: Int32, networkType: ZcashLightClientKit.NetworkType) throws -> ZcashLightClientKit.UnifiedSpendingKey {
throw KeyDerivationErrors.unableToDerive
}
static func getCurrentAddress(dbData: URL, account: Int32, networkType: ZcashLightClientKit.NetworkType) throws -> ZcashLightClientKit.UnifiedAddress {
throw KeyDerivationErrors.unableToDerive
}
static func getNextAvailableAddress(dbData: URL, account: Int32, networkType: ZcashLightClientKit.NetworkType) throws -> ZcashLightClientKit.UnifiedAddress {
throw KeyDerivationErrors.unableToDerive
}
static func getSaplingReceiver(for uAddr: ZcashLightClientKit.UnifiedAddress) throws -> ZcashLightClientKit.SaplingAddress? {
throw KeyDerivationErrors.unableToDerive
}
static func getTransparentReceiver(for uAddr: ZcashLightClientKit.UnifiedAddress) throws -> ZcashLightClientKit.TransparentAddress? {
throw KeyDerivationErrors.unableToDerive
}
static func shieldFunds(dbCache: URL, dbData: URL, usk: ZcashLightClientKit.UnifiedSpendingKey, memo: ZcashLightClientKit.MemoBytes, spendParamsPath: String, outputParamsPath: String, networkType: ZcashLightClientKit.NetworkType) -> Int64 {
-1
}
static func receiverTypecodesOnUnifiedAddress(_ address: String) throws -> [UInt32] {
throw KeyDerivationErrors.receiverNotFound
}
static func createAccount(dbData: URL, seed: [UInt8], networkType: ZcashLightClientKit.NetworkType) throws -> ZcashLightClientKit.UnifiedSpendingKey {
throw KeyDerivationErrors.unableToDerive
}
static func getReceivedMemo(dbData: URL, idNote: Int64, networkType: ZcashLightClientKit.NetworkType) -> ZcashLightClientKit.Memo? { static func getReceivedMemo(dbData: URL, idNote: Int64, networkType: ZcashLightClientKit.NetworkType) -> ZcashLightClientKit.Memo? {
nil nil
} }
@ -93,15 +141,10 @@ class MockRustBackend: ZcashRustBackendWelding {
nil nil
} }
static func createToAddress(dbData: URL, account: Int32, extsk: String, to address: String, value: Int64, memo: ZcashLightClientKit.MemoBytes, spendParamsPath: String, outputParamsPath: String, networkType: ZcashLightClientKit.NetworkType) -> Int64 { static func createToAddress(dbData: URL, usk: ZcashLightClientKit.UnifiedSpendingKey, to address: String, value: Int64, memo: ZcashLightClientKit.MemoBytes, spendParamsPath: String, outputParamsPath: String, networkType: ZcashLightClientKit.NetworkType) -> Int64 {
-1 -1
} }
static func shieldFunds(dbCache: URL, dbData: URL, account: Int32, xprv: String, memo: ZcashLightClientKit.MemoBytes, spendParamsPath: String, outputParamsPath: String, networkType: ZcashLightClientKit.NetworkType) -> Int64 {
-1
}
static func initDataDb(dbData: URL, seed: [UInt8]?, networkType: ZcashLightClientKit.NetworkType) throws -> ZcashLightClientKit.DbInitResult { static func initDataDb(dbData: URL, seed: [UInt8]?, networkType: ZcashLightClientKit.NetworkType) throws -> ZcashLightClientKit.DbInitResult {
.seedRequired .seedRequired
} }
@ -110,7 +153,7 @@ class MockRustBackend: ZcashRustBackendWelding {
throw RustWeldingError.unableToDeriveKeys throw RustWeldingError.unableToDeriveKeys
} }
static func isValidSaplingExtendedSpendingKey(_ key: String, networkType: ZcashLightClientKit.NetworkType) throws -> Bool { static func isValidSaplingExtendedSpendingKey(_ key: String, networkType: ZcashLightClientKit.NetworkType) -> Bool {
false false
} }
@ -118,7 +161,7 @@ class MockRustBackend: ZcashRustBackendWelding {
nil nil
} }
static func isValidUnifiedAddress(_ address: String, networkType: ZcashLightClientKit.NetworkType) throws -> Bool { static func isValidUnifiedAddress(_ address: String, networkType: ZcashLightClientKit.NetworkType) -> Bool {
false false
} }
@ -130,10 +173,6 @@ class MockRustBackend: ZcashRustBackendWelding {
public func deriveViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [UnifiedFullViewingKey] { public func deriveViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [UnifiedFullViewingKey] {
[] []
} }
static func clearUtxos(dbData: URL, address: String, sinceHeight: BlockHeight, networkType: NetworkType) throws -> Int32 {
-1
}
static func getNearestRewindHeight(dbData: URL, height: Int32, networkType: NetworkType) -> Int32 { static func getNearestRewindHeight(dbData: URL, height: Int32, networkType: NetworkType) -> Int32 {
-1 -1
@ -147,14 +186,6 @@ class MockRustBackend: ZcashRustBackendWelding {
false false
} }
static func getVerifiedTransparentBalance(dbData: URL, address: String, networkType: NetworkType) throws -> Int64 {
-1
}
static func getTransparentBalance(dbData: URL, address: String, networkType: NetworkType) throws -> Int64 {
-1
}
static func putUnspentTransparentOutput( static func putUnspentTransparentOutput(
dbData: URL, dbData: URL,
txid: [UInt8], txid: [UInt8],
@ -184,72 +215,27 @@ class MockRustBackend: ZcashRustBackendWelding {
) -> Int64 { ) -> Int64 {
-1 -1
} }
static func shieldFunds( static func deriveTransparentAddressFromSeed(seed: [UInt8], account: Int, index: Int, networkType: NetworkType) throws -> TransparentAddress {
dbCache: URL,
dbData: URL,
account: Int32,
xprv: String,
memo: String?,
spendParamsPath: String,
outputParamsPath: String,
networkType: NetworkType
) -> Int64 {
-1
}
static func deriveTransparentAddressFromSeed(seed: [UInt8], account: Int, index: Int, networkType: NetworkType) throws -> String? {
throw KeyDerivationErrors.unableToDerive throw KeyDerivationErrors.unableToDerive
} }
static func deriveTransparentAccountPrivateKeyFromSeed(seed: [UInt8], account: Int, networkType: NetworkType) throws -> String? {
throw KeyDerivationErrors.unableToDerive
}
static func deriveTransparentAddressFromAccountPrivateKey(_ xprv: String, index: Int, networkType: NetworkType) throws -> String? {
throw KeyDerivationErrors.unableToDerive
}
static func derivedTransparentAddressFromPublicKey(_ pubkey: String, networkType: NetworkType) throws -> String {
throw KeyDerivationErrors.unableToDerive
}
static func deriveUnifiedFullViewingKeyFromSeed(_ seed: [UInt8], numberOfAccounts: Int32, networkType: NetworkType) throws -> [UnifiedFullViewingKey] { static func deriveUnifiedFullViewingKeyFromSeed(_ seed: [UInt8], numberOfAccounts: Int32, networkType: NetworkType) throws -> [UnifiedFullViewingKey] {
throw KeyDerivationErrors.unableToDerive throw KeyDerivationErrors.unableToDerive
} }
static func isValidSaplingExtendedFullViewingKey(_ key: String, networkType: NetworkType) throws -> Bool { static func isValidSaplingExtendedFullViewingKey(_ key: String, networkType: NetworkType) -> Bool {
false false
} }
static func isValidUnifiedFullViewingKey(_ ufvk: String, networkType: NetworkType) throws -> Bool { static func isValidUnifiedFullViewingKey(_ ufvk: String, networkType: NetworkType) -> Bool {
false false
} }
static func deriveTransparentPrivateKeyFromSeed(seed: [UInt8], networkType: NetworkType) throws -> String? {
nil
}
static func initAccountsTable(dbData: URL, exfvks: [String], networkType: NetworkType) throws -> Bool {
false
}
static func deriveTransparentAddressFromSeed(seed: [UInt8], networkType: NetworkType) throws -> String? {
nil
}
static func deriveSaplingExtendedSpendingKeys(seed: [UInt8], accounts: Int32, networkType: NetworkType) throws -> [SaplingExtendedSpendingKey]? { static func deriveSaplingExtendedSpendingKeys(seed: [UInt8], accounts: Int32, networkType: NetworkType) throws -> [SaplingExtendedSpendingKey]? {
nil nil
} }
static func deriveUnifiedAddressFromSeed(seed: [UInt8], accountIndex: Int32, networkType: NetworkType) throws -> String? {
nil
}
static func deriveUnifiedAddressFromViewingKey(_ ufvk: String, networkType: NetworkType) throws -> String? {
nil
}
static func consensusBranchIdFor(height: Int32, networkType: NetworkType) throws -> Int32 { static func consensusBranchIdFor(height: Int32, networkType: NetworkType) throws -> Int32 {
guard let consensus = consensusBranchID else { guard let consensus = consensusBranchID else {
return try rustBackend.consensusBranchIdFor(height: height, networkType: networkType) return try rustBackend.consensusBranchIdFor(height: height, networkType: networkType)
@ -285,11 +271,11 @@ class MockRustBackend: ZcashRustBackendWelding {
mockLastError ?? rustBackend.getLastError() mockLastError ?? rustBackend.getLastError()
} }
static func isValidSaplingAddress(_ address: String, networkType: NetworkType) throws -> Bool { static func isValidSaplingAddress(_ address: String, networkType: NetworkType) -> Bool {
true true
} }
static func isValidTransparentAddress(_ address: String, networkType: NetworkType) throws -> Bool { static func isValidTransparentAddress(_ address: String, networkType: NetworkType) -> Bool {
true true
} }
@ -298,11 +284,7 @@ class MockRustBackend: ZcashRustBackendWelding {
_ = try rustBackend.initDataDb(dbData: dbData, seed: nil, networkType: networkType) _ = try rustBackend.initDataDb(dbData: dbData, seed: nil, networkType: networkType)
} }
} }
static func initAccountsTable(dbData: URL, seed: [UInt8], accounts: Int32, networkType: NetworkType) -> [SaplingExtendedSpendingKey]? {
mockAccounts ?? rustBackend.initAccountsTable(dbData: dbData, seed: seed, accounts: accounts, networkType: networkType)
}
static func initBlocksTable( static func initBlocksTable(
dbData: URL, dbData: URL,
height: Int32, height: Int32,
@ -323,10 +305,6 @@ class MockRustBackend: ZcashRustBackendWelding {
} }
} }
static func getAddress(dbData: URL, account: Int32, networkType: NetworkType) -> String? {
mockAddresses?[Int(account)] ?? rustBackend.getAddress(dbData: dbData, account: account, networkType: networkType)
}
static func getBalance(dbData: URL, account: Int32, networkType: NetworkType) -> Int64 { static func getBalance(dbData: URL, account: Int32, networkType: NetworkType) -> Int64 {
mockBalance ?? rustBackend.getBalance(dbData: dbData, account: account, networkType: networkType) mockBalance ?? rustBackend.getBalance(dbData: dbData, account: account, networkType: networkType)
} }

View File

@ -37,12 +37,12 @@ class TestCoordinator {
var completionHandler: ((SDKSynchronizer) throws -> Void)? var completionHandler: ((SDKSynchronizer) throws -> Void)?
var errorHandler: ((Error?) -> Void)? var errorHandler: ((Error?) -> Void)?
var spendingKey: SaplingExtendedSpendingKey var spendingKey: UnifiedSpendingKey
var birthday: BlockHeight var birthday: BlockHeight
var channelProvider: ChannelProvider var channelProvider: ChannelProvider
var synchronizer: SDKSynchronizer var synchronizer: SDKSynchronizer
var service: DarksideWalletService var service: DarksideWalletService
var spendingKeys: [SaplingExtendedSpendingKey]? var spendingKeys: [UnifiedSpendingKey]?
var databases: TemporaryTestDatabases var databases: TemporaryTestDatabases
let network: ZcashNetwork let network: ZcashNetwork
convenience init( convenience init(
@ -53,28 +53,13 @@ class TestCoordinator {
) throws { ) throws {
let derivationTool = DerivationTool(networkType: network.networkType) let derivationTool = DerivationTool(networkType: network.networkType)
guard let spendingKey = try derivationTool.deriveUnifiedSpendingKey(
let spendingKey = try derivationTool seed: TestSeed().seed(),
.deriveSpendingKeys( accountIndex: 0
seed: TestSeed().seed(), )
numberOfAccounts: 1
) let ufvk = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
.first
else {
throw CoordinatorError.builderError
}
guard
let ufvk = try derivationTool
.deriveUnifiedFullViewingKeysFromSeed(
TestSeed().seed(),
numberOfAccounts: 1
)
.first
else {
throw CoordinatorError.builderError
}
try self.init( try self.init(
spendingKey: spendingKey, spendingKey: spendingKey,
unifiedFullViewingKey: ufvk, unifiedFullViewingKey: ufvk,
@ -85,7 +70,7 @@ class TestCoordinator {
} }
required init( required init(
spendingKey: SaplingExtendedSpendingKey, spendingKey: UnifiedSpendingKey,
unifiedFullViewingKey: UnifiedFullViewingKey, unifiedFullViewingKey: UnifiedFullViewingKey,
walletBirthday: BlockHeight, walletBirthday: BlockHeight,
channelProvider: ChannelProvider, channelProvider: ChannelProvider,
@ -176,12 +161,12 @@ class TestCoordinator {
self.errorHandler?(notification.userInfo?[SDKSynchronizer.NotificationKeys.error] as? Error) self.errorHandler?(notification.userInfo?[SDKSynchronizer.NotificationKeys.error] as? Error)
} }
@objc func synchronizerSynced(_ notification: Notification) { @objc func synchronizerSynced(_ notification: Notification) throws {
if case .stopped = self.synchronizer.status { if case .stopped = self.synchronizer.status {
LoggerProxy.debug("WARNING: notification received after synchronizer was stopped") LoggerProxy.debug("WARNING: notification received after synchronizer was stopped")
return return
} }
try? self.completionHandler?(self.synchronizer) try self.completionHandler?(self.synchronizer)
} }
@objc func synchronizerDisconnected(_ notification: Notification) { @objc func synchronizerDisconnected(_ notification: Notification) {
@ -288,13 +273,13 @@ enum TestSynchronizerBuilder {
storage: CompactBlockStorage, storage: CompactBlockStorage,
spendParamsURL: URL, spendParamsURL: URL,
outputParamsURL: URL, outputParamsURL: URL,
spendingKey: SaplingExtendedSpendingKey, spendingKey: UnifiedSpendingKey,
unifiedFullViewingKey: UnifiedFullViewingKey, unifiedFullViewingKey: UnifiedFullViewingKey,
walletBirthday: BlockHeight, walletBirthday: BlockHeight,
network: ZcashNetwork, network: ZcashNetwork,
seed: [UInt8]? = nil, seed: [UInt8]? = nil,
loggerProxy: Logger? = nil loggerProxy: Logger? = nil
) throws -> (spendingKeys: [SaplingExtendedSpendingKey]?, synchronizer: SDKSynchronizer) { ) throws -> (spendingKeys: [UnifiedSpendingKey]?, synchronizer: SDKSynchronizer) {
let initializer = Initializer( let initializer = Initializer(
cacheDbURL: cacheDbURL, cacheDbURL: cacheDbURL,
dataDbURL: dataDbURL, dataDbURL: dataDbURL,
@ -334,21 +319,13 @@ enum TestSynchronizerBuilder {
walletBirthday: BlockHeight, walletBirthday: BlockHeight,
network: ZcashNetwork, network: ZcashNetwork,
loggerProxy: Logger? = nil loggerProxy: Logger? = nil
) throws -> (spendingKeys: [SaplingExtendedSpendingKey]?, synchronizer: SDKSynchronizer) { ) throws -> (spendingKeys: [UnifiedSpendingKey]?, synchronizer: SDKSynchronizer) {
guard let spendingKey = try DerivationTool(networkType: network.networkType)
let spendingKey = try DerivationTool(networkType: network.networkType) .deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: 0)
.deriveSpendingKeys(seed: seedBytes, numberOfAccounts: 1)
.first
else {
throw TestCoordinator.CoordinatorError.builderError
}
guard let uvk = try DerivationTool(networkType: network.networkType) let uvk = try DerivationTool(networkType: network.networkType)
.deriveUnifiedFullViewingKeysFromSeed(seedBytes, numberOfAccounts: 1) .deriveUnifiedFullViewingKey(from: spendingKey)
.first
else {
throw TestCoordinator.CoordinatorError.builderError
}
return try build( return try build(
rustBackend: rustBackend, rustBackend: rustBackend,

View File

@ -0,0 +1,240 @@
//
// File.swift
//
//
// Created by Francisco Gindre on 9/26/22.
//
import Foundation
@testable import ZcashLightClientKit
/// Test vectors for unified addresses
/// Original file can be found here https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/test-vectors/json/unified_address.json
/// Note: These vectors can't be used as-is since we are missing derivation functions that take a DiversifiedIndex
struct TestVector {
enum Indices: Int, CaseIterable {
case p2pkh_bytes = 0
case p2sh_bytes
case sapling_raw_addr
case orchard_raw_addr
case unknown_typecode
case unknown_bytes
case unified_addr
case root_seed
case account
case diversifier_index
}
var p2pkh_bytes: [UInt8]?
var p2sh_bytes: [UInt8]?
var sapling_raw_addr: [UInt8]?
var orchard_raw_addr: [UInt8]?
var unknown_typecode: UInt32?
var unknown_bytes: [UInt8]?
var unified_addr: String?
var root_seed: [UInt8]?
var account: UInt32 = 0
var diversifier_index: UInt32 = 0
static func optionalByteArrayKeyPath(from index: Indices) -> WritableKeyPath<TestVector, [UInt8]?>? {
switch index {
case .p2pkh_bytes:
return \Self.p2pkh_bytes
case .p2sh_bytes:
return \Self.p2sh_bytes
case .sapling_raw_addr:
return \Self.sapling_raw_addr
case .orchard_raw_addr:
return \Self.orchard_raw_addr
case .unknown_bytes:
return \Self.unknown_bytes
case .root_seed:
return \Self.root_seed
default:
return nil
}
}
static func stringKeyPath(from index: Indices) -> WritableKeyPath<TestVector, String?>? {
switch index {
case .unified_addr:
return \Self.unified_addr
default:
return nil
}
}
static func uintKeyPath(from index: Indices) -> WritableKeyPath<TestVector, UInt32>? {
switch index {
case .account:
return \Self.account
case .diversifier_index:
return \Self.diversifier_index
default:
return nil
}
}
static var testVectors: [TestVector]? {
var vectors = [TestVector]()
for rawVector in testVector.dropFirst(2) {
guard let vector = TestVector(from: rawVector) else {
return nil
}
vectors.append(vector)
}
return vectors
}
init?(from rawVector: [Any?]) {
guard rawVector.count == Indices.diversifier_index.rawValue + 1 else { return nil }
for varIndex in Indices.allCases {
switch varIndex {
case .p2pkh_bytes,
.p2sh_bytes,
.sapling_raw_addr,
.orchard_raw_addr,
.unknown_bytes,
.root_seed:
guard let keyPath = Self.optionalByteArrayKeyPath(from: varIndex) else {
return nil
}
if rawVector[varIndex.rawValue] == nil {
self[keyPath: keyPath] = nil
break
}
guard let hexString = rawVector[varIndex.rawValue] as? String,
let data = hexString.hexadecimal else {
return nil
}
self[keyPath: keyPath] = data.bytes
case .account,
.diversifier_index:
guard rawVector[varIndex.rawValue] != nil else { return nil }
guard let keyPath = Self.uintKeyPath(from: varIndex) else { return nil }
guard let optionalValue = rawVector[varIndex.rawValue],
let intValue = optionalValue as? Int,
let uintValue = UInt32(exactly: intValue) else {
return nil
}
self[keyPath: keyPath] = uintValue
case .unified_addr:
guard rawVector[varIndex.rawValue] != nil else { return nil }
guard let keyPath = Self.stringKeyPath(from: varIndex) else { return nil }
guard let optionalValue = rawVector[varIndex.rawValue],
let stringValue = optionalValue as? String else {
return nil
}
self[keyPath: keyPath] = stringValue
case .unknown_typecode:
self.unknown_typecode = rawVector[varIndex.rawValue] as? UInt32
}
}
}
}
extension String {
/// Create `Data` from hexadecimal string representation
///
/// This creates a `Data` object from hex string. Note, if the string has any spaces or non-hex characters (e.g. starts with '<' and with a '>'), those are ignored and only hex characters are processed.
///
/// - returns: Data represented by this hexadecimal string.
///
/// source: https://stackoverflow.com/questions/26501276/converting-hex-string-to-nsdata-in-swift
var hexadecimal: Data? {
var data = Data(capacity: count / 2)
let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
regex.enumerateMatches(in: self, range: NSRange(startIndex..., in: self)) { match, _, _ in
let byteString = (self as NSString).substring(with: match!.range)
let num = UInt8(byteString, radix: 16)!
data.append(num)
}
guard data.count > 0 else { return nil }
return data
}
}
let testVector: [[Any?]] =
[
["From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/unified_address.py"],
["p2pkh_bytes, p2sh_bytes, sapling_raw_addr, orchard_raw_addr, unknown_typecode, unknown_bytes, unified_addr, root_seed, account, diversifier_index"],
["e6cabf813929132d772d04b03ae85223d03b9be8", nil, "d8ef8293d26de832e7193f296ba1922d90f122c6135bc231eebd91efdb03b1a8606771cd4fd6480574d43e", "d4714ee761d1ae823b6972152e20957fefa3f6e3129ea4dfb0a9e98703a63dab929589d6dc51c970f935b3", nil, nil, "u1jttnjvd97hja2fvajcgfmu6w2mcffuzlgfmvepjy8qdhkjmv4pf90dqgzcse23t95yvxaaysh8fu0d6wwjw2x7pxvzgx5zhxfn2j33mgspxlxysdhqafush2awsz4huts5mqz7mffzl09697my7dgl7c48eggfe072aj2u8jd7ktl0vzf36j65cye9ff0erc5n2mgqkjygwnq004a3y", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 0, 0],
["314f9bb5a18af9c9f6eeb035cfee3acb111facd9", nil, "435b0bbc95b5b7d52531a3944f2b85603ee22aaf850963bc156eb561edf2cbe7cf0e770e393ae5d7049026", "a9e372a4c8950cf34ae29ccc664564b56cc3030025065fc24da4c00f785e8d1b9531e3ef83a66a4edc3816", nil, nil, "u1ee82x2telrxdw72p4ke636wu2c2p09qrq7mhzm5kccjymh2033avaeh3ttt5s89ez0a3yrgwaec7glvgkyn7xl8x962dza5s6c4q9aspe655d4c3er883dleqdhgxnaxhex2j3z265mly7pf0kuv69mhyh30pm5vnsnq2lwn8hga24a0y52l9ydvg6hda5w23fes6hyasknhumwd8nu", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 0, 3],
["3ca74c80ef1d1853423ae2891cd5d0ecbcfde137", nil, "69a25a38699708e5f6e76e54e6a7a2ab84dcf288df0d1f2563670168d6c44ace0ef11155c60d5c225e9dec", "7e243f6141643f81e1e93fda73cf2f64f68de487785370f3021d1940f34bc3ed13a9d9a03baa78e4a43b97", nil, nil, "u1uqlu50zy5q6dtkqjfq92a8fxt3yg9gv6yrj48rt9l0jlxpgdxvddgefct6femf73yv05umnxcf848jr4r8ue37g036h64vk5cwel6sml50w0jeah8fg6awcuj0q7f9udr6agq9j76dza8j3urh6zq7p8t4gppgxsqr2nv3fgrrqv4dhx6hye7h2fz5ppq9m4v332x7767jzg75qe0kp", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 0, 4],
[nil, nil, "9f6e0bf90a18fc0b9b83ae9f23ad4358648638482b5def8975635b66fd8a708335f9235a3186ec0f033f84", nil, nil, nil, "u1z9vyk0d0h2k2jwuuk2gfvh5p65qsagkwcgqm6lvh8ratkzjau7stq5snlnkl0eutr687f3wcyn8a0m3n3462c0e4t4cs7m3lvumj2ddm", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 1, 3],
[nil, nil, "e1adf156a07d56bcac91bdb2f7bb3ea7c44569dcfee54273c09e8065807b6823faa94a77219554d0f6e017", nil, nil, nil, "u188xrtf88khrjcd8d4487qvjk3getm6xmex0pvjket5qen35tlzhpmgadkcyccz8q0kp3sxzy4ldgn5n0cnll34s47ymvw9cqfunh53wq", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 1, 7],
[nil, nil, "60ba572f8e379312d86897025decdd64b4b95e2c4afa9d13726b8cc393edb4988c51b976028f890f108bd2", nil, nil, nil, "u1jn6752r2s080uhf8grhdnnkfzrgfcyv3tu45f03rzcgheqpmxj5wuvf0v274l0k5ehvc763epv6qxnsv6t04vcn66h0sa2c8nsnyy2f3", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 1, 8],
["aa6d43480fd9d91375ce6c4a020706361bd296de", nil, nil, "953f3c78d103c32b60559299462ebb27348964b892acad10482fe502c99f0d524959ba7be4f188e3a27138", 65532, "cd8dbf69b8250c18ef41294ca97993db546c1fe01f7e9c8e36d6a5e29d4e30a73594bf5098421c69378af1e40f64e125946f62c2fa7b2fecbcb64b6968912a6381ce3dc166d56a1d62f5a8d7551db5fd9313e8c7203d996af7d477083756d59af80d06a745f44ab023752cb5b406ed8985e18130ab33362697b0e4e4c763ccb8f676495c222f7fba1e31defa3d5a57efc2e1e9b01a035587d5fb1a38e01d94903d3c3e0ad3360c1d3710acd20b183e31d49f25c9a138f49b1a537edcf04be34a9851a7af9db6990ed83dd64af3597c04323ea51b0052ad8084a8b9da948d320dadd64f5431e61ddf658d24ae67c22c8d1309131fc00fe7f2357342", "u1uwdt59245hh0y324zds9s4knmw3q450qgm4rhmyzq6x62uxerpwtsnmqlp25w7w79c9y9qez6j072krjvlvg9805a4hj5mya9k8h35kauj7cmdg3hqnmvqeaa0sfgg95qpd6k7mrnfxpp2zl0kq7z90wyr8ptmgdnq3swem4wken5pwr0vdgkvylzfzwcr2mesef9j9pd9frudzgdm392uvlh3nhe3vvlwkd4nu3tm03gzllae9dn0n0csupewz95z96xnauljwyc3crw34d2zftcumd0uadz0rlj24ax4svvm6y5ga9j363k4a996dh3h8dz769gekf3jadx5kwwf4eckfvkwn7lf04rctx7kql08956xz036jyrl2pphlwjn9xyprfs04ug3p0z92znkv7yf53hn0w63elnkwjl5u934lck6f5q8l7wnrlcly62utckyew808judy8lnuzrzfmd9r952v64p6g0g88s8n5w2wgshp4l58vssndy3rltlhw9y4r9agykflzrw0dp0w2yajnucnx2rd", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 2, 0],
["707aae9a13445677cf7d7cb394b9a883f4104018", nil, nil, "88391e4e038ca9e15cc38624a64552a99f07be0715ae2db4474234e946e1b6c6b6676e5cf7b25d90593e8c", 65532, "76d38d47f1e191e00c7a1d48af046827591e9733a97fa6b679f3dc601d008285edcbdae69ce8fc1be4aac00ff2711ebd931de518856878f73476f21a482ec9378365c8f7393c94e2885315eb4671098b79535e790fe53e29fef2b3766697ac32b4f473f468a008e72389fc03880d780cb07fcfaabe3f1a84b27db59a4a153d882d2b2103596555ed9494c6ac893c49723833ec8926c1039586a7afcf4a0d9c731e985d99589c8bb838e8aaf745533ed9e8ae3a1cd074a51a20da8aba18d1dbebbc862ded42435e92476930d069896cff30eb414f727b89e001afa2fb8dc3436d75a4a6f26572504b192232ecb9f0c02411e52596bc5e90457e7459", "u1c8qg4struvf8gm0gulzg25z30uxjxxcj2cmcdl5jyamqjmgdk85flag2lw7kjdptzaqkv04kjypyvga4vdmedw7y298h7e8qhp7qkdc53qmnt4k77cym7gnyhsz5e82cgkvfnl3sghndsnjcfnrj2w2n9x7p949x5mmxjkhpvs92zyyvf0zx28d093t629sl332aqgpv4qp9m6ytaqr6av6wy6l0le2n20z6ck6akd7276ql8cv7thzqd8nc2uyz9pedqywj90nmlymhkqk8pzjvauqfmnly2h6ezgwwv4utu4njj66y85ysamkm3g35wjrqkytad343uysst406pyr6y2asa6t9pzznpzf363hqxv8u34sccaafrrqpklrq7ejpv6nawvm425hxt0np3x7nwq4cdvjwcqghrq5c7pc4lddxlgdgv3ujlm5s5d6ss8f98hk99vkqw27jl2clnpz382htyccncdhp40c4xvm48glm3sr2nakfqm0twckzcuwenq96768yu04wprp3w6ayhsf8k9cshpj", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 2, 1],
["21e2c228dd8bad8816a407c18ad7987d1a4f0336", nil, nil, "251dcb7e5a988146d9cb52db9d7f81de1eb8ef58ee08eb4ec4810a23be8f4337790a99bfba8e364aa25b8d", 65532, "39ffedbd12863ce71a02af117d417adb3d15cc54dcb1fce467500c6b8fb86b12b56da9c382857deecc40a98d5f2935395ee4762dd21afdbb5d47fa9a6dd984d567db2857b927b7fae2db587105415d4642789d38f50b8dbcc129cab3d17d19f3355bcf73cecb8cb8a5da01307152f13936a270572670dc82d39026c6cb4cd4b0f7f5aa2a4f5a5341ec5dd715406f2fdd2afa733f5f641c8c21862a1bafce2609d9eecfa158cfb5cd79f88008e315dc7d8388e76c1782fd2795d18a763624c25fa959cc97489ce75745824b77868c53239cfbdf73caec65604037314faaceb56218c6bd30f8374ac13386793f21a9fb80ad03bc0cda4a44946c00e1", "u1q3vrqfexveqh2772en0ynkwstt62sqlyvkqzn8kr2w4hnpqy07d7l6qxzwwuuv3k040c5t6haqkak60km9l3c7hzd6y5ycednjep3c7puknhtlmp24adyduh02zq8kw3mqm4vhzkzc84tmsv9h2rpp6jf4xqpw2md4jrmrmuklw3zv50ajr2gxkgk6hxq2szymsmykvzpvwz8rt7zekzqakurrmrvnezmvu6h5c6gupp275qc6lrl57rg9pwvg7dza37c3ft569gnzhjz7f9ujvm8799nrc7nr2udgzcaphyxmrpzrzhzqpdqy28cqzdsay467fjtexyn24mqlzjdqwsylj3sqaylfy0hu8ezpt3q4xlssq7m6c0c0706w3mf7ed9nd20209q0rmxj75rya62gxcu5r2nss8vtdq8kuk93t4dvrte9r587zy37ty998njtmwk7s5vzeg4ugq07ea0ffdzjpf2zy84ccwauzkrq36z5vm72qwptvn3k47kurxxs0ltvrq262d0hfrmfp9ecpys7eewqy", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 2, 2],
["235fd68bee8f799114c9e7cb8ce3c01f5e442a58", nil, "52fd6aedefbf401633c2e4532515ebcf95bcc2b4b8e4d676dfad7e17925c6dfb8671e52544dc2ca075e261", nil, 65532, "8cc3d14d2cf6556df6ed4b4ddd3d9a69f53357d7767f4f5ccbdbc596631277f8fecd08cb056b95e3025b9792fff7f244fc716269b926d62e9596fa825c6bf21aff9e68625a192440ea06828123", "u1v8dtwgkrza584fj3jv7xvwm0paf3fqelt972s9kr3t808tj35gfa7lsnfpzjqgedkcc7kefrtek5nhpv6q6ekykua0eusve549crkm9yhjzrq3yqqk5wve684sjwazkztk4v4nupwgl4y6xyakysdy2p68msjm5tla8hsadaw4tmjrjzmwlm6u4c8z0fjaxew6l52qfl4dt5hqwz0wx82maek5wys7k7a0rjmh03ykgjsaphmz8qz62dpgvwl4t7pfzk4xqlvy6pk", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 3, 0],
["cd7c11d41e1f9db7c3fac7b01fac2d81a20a0fb0", nil, "78ba60804b822cc7e970b11a96b5bccbda556a7c26f0b082cfcd9a68e2690017771c4bdcf8f8bad3c8591f", nil, 65532, "d97884806f15fa08da52754a1095e3ff1abd5ce4fddfccfc3a6128aef784a64610a89d1a7099216d0814d3a2d452431c32d411ac1cce82ad0229407bbc48985675e3f874a4533f1d63a84dfa3e", "u1n6trq9cqde5vywu6zl4u6ydhg4k39nh3ymtvecnvzlv0crt906t97uxmxdqjxcf8uqratws4l0g6q4mjr34u77pmcle4224n35tq9ge7hjra8j2e88ggveqfmw9mdrrgcftu0p5hjmjf89fzehug6hng3zyktmmk0y9jyespq76s2l3sh539vk2v8h8aykqa2k6mmpmkza4ngpds7l9h2vf9nhfkhmjlysc0nsrq3y0qvfma8g3ad8efxwy8hjsgjyy2lts6muf3a", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 3, 3],
["a257c2f326ba0eccee803c2867e91779cf93ef19", nil, "508942b74eac2ab8e9868b06c95b30a6b5a56c35478b3b05460fc9e63217c22259ef84c23c0f29f877be4f", nil, 65532, "0f460fe2f57e34fbc75423c3737f5b2a0615f5722db041a3ef66fa483afd3c2e19e59444a64add6df1d963f5dd5b5010d3d025f0287c4cf19c75f33d51ddddba5d657b43ee8da645443814cc73", "u1q26wd9e3vgsepgw9748tvsktlq0jqvfr59ul4fya0tn56sss7yv5emkq348yr7hawdazngph0elatc56t6gz72lhjh8dcfrfkt6kamsf664w7h354rksgar90xt5h7axypgej6rudw8c4t0nchklst05hamddf67r5gkzcsqqz6a8ugjtl0ur0rlywfe8pmqju97s42tslaaygj0mnyu3xk3g2x6e2jezyjfwpqxfysjf0q8a5xcex26qpdwzsyc3smpw5cahgjc3", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 3, 5],
["7ae2a0db6781b6db28ebeccbac16f39ed3e5e4cb", nil, nil, "165082de84f2ad7204426ffafd6b6c7de9cab6d25c13846a1786715268c415948db788f4a5e0daa03d699e", 65530, "86a2658e0a07a05ac5b950051cd24c47a88d13d659ba2a46ca1830816d09cd7646f76f716abec5de07fe9b523410806ea6f288f8736c23357c85f45791e1708029d9824d90704607f387a03e49bf9836574431345a7877efaa8a08e73081ef8d62cb780ab6883a50a0d470190dfba10a857f82842d3825b3d6da0573d316eb160dc0b716c48fbd467f75b780149ae8808f4e68f50c0536acddf6f1aeab016b6bc1ec144b4e553acfd670f77e755fc88e0677e31ba459b44e307768958fe3789d41c2b1ff434cb30e15914f01bc6bc2307b488d2556d7b7380ea4ffd712f6b02fe806b94569cd4059f396bf29b99d0a40e5e1711ca944f72d43", "u1qtww2cneawwdu5v9rqt4dm9kwn80s2nztcwcpeqz8jagey04qnate7r8j6zu9thm97ldtn4qdl36vlpn8lvy3ljk8s37kr9pgkpqlmlla9vhh9j6wucqwham9zddrzu5jl9g8jav3rdx560ft6cr5c7j2p4gqp88z6y4zmrhszssnss839zfzkn34h5ttrtg6tuw4qdf272lhqns2faylkykz2yed239tehndaruw4v93rw04yc435eke3m7lczed935zhtasdptyjw0rd5ulhdg02rnqhltk43s3d2jgd30xla8qda550tlkmtg2d5kz3amvtldmzl7w778we2zq8ynfny4v6g6us6exvdcc4qsgq3esw3fvev5tycpyhk4srvj4u2haw4m3z37hpaan5y2k4pl53hdnd2je8ejcv5ka3a34c5xu7zxd3nydf4as5wsl7dpzn6zjqkl4dcs6l3cyzl5n6z77pn7d043d5egu4tcrg2suh9l0rwwz8enrqqw2c70q50n3lt3rgl2m3cqevdj0srv", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 4, 0],
["f45861cfbb22c49f51c40a3cde5c66c0d95bc413", nil, nil, "c906109b51e2b37bf8b67761bfa917dc5059c357b7dc8107672b66189a0d15bc496d84ef9114c68c99c911", 65530, "6a102fca4b97693da0b086fe9d2e7162470d02e0f05d4bec9512bfb3f38327296efaa74328b118c27402c70c3a90b49ad4bbc68e37c0aa7d9b3fe17799d73b841e751713a02943905aae0803fd69442eb7681ec2a05600054e92eed555028f21b6a155268a2dd6640a69301a52a38d4d9f9f957ae35af7167118141ce4c9be0a6a492fe79f1581a155fa3a2b9dafd82e650b386ad3a08cb6b83131ac300b0846354a7eef9c410e4b62c47c5426907dfc6685c5c99b7141ac626ab4761fd3f41e728e1a28f89db89ffdeca364dd2f0f0739f0534556483199c71f189341ac9b78a269164206a0ea1ce73bfb2a942e7370b247c046f8e75ef8e3", "u1qxhqghcyrv0vunc0qgdqlm45ftmahunlu8jwstmqmrptgam2ddw7nz3dcwmc93pzjqn3vk24acxtzdvytpnr4hlukkmnmhe063342u24wr0l2td6zdgwe06cfun4u2sa0ml00pmzdz3fhv079yz3jwdzq26hj8dywv0ml90xv74mljuvdc3apv8vkp03xlskc2rcrrfz4c6jazjjschses08xpxd088yl8e2aw0gg7arlfwletk4ruwam5qvmwz5drhe70kmwd38637y0zh2q7ayl6hunrgvqyyc42x28ewk7tt23lul4wjkl5g0w2aqz8p3jy62zk2e5vemd5sxeyudcfq3u3a5lhahl230adlx00z00tnx5kuwmgk7mf6ylylep76q9j6642fqg3y2svckg0nwxgadmtyyp8pwgltd7rdv9wefzwd2rywpu9h33gwq4s07txgjwwadmgs27n8m93ndr8vljxr74xr4n8ghveumzh3wnya7etkx8a49dyuzm0xvugsh5ejk8qggm63xv5jc0le5", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 4, 1],
["2456cac075428d24707af7de2fc610c833831bdb", nil, nil, "7cd065b0ab297fb7fd701291d03589031fe3aadf1177902e5bcb65b5ba0aa2a0b73f09734f0b867b29763d", 65530, "f8bd821cf577491864e20e6d08fd2e32b555c92c661f19588b72a89599710a88061253ca285b6304b37da2b5294f5cb354a894322848ccbdc7c2545b7da568afac87ffa005c312241c2d57f4b45d6419f0d2e2c5af33ae243785b325cdab95404fc7aed70525cddb41872cfcc214b13232edc78609753dbff930eb0dc156612b9cb434bc4b693392deb87c530435312edcedc6a961133338d786c4a3e103f60110a16b1337129704bf4754ff6ba9fbe65951e610620f71cda8fc877625f2c5bb04cbe1228b1e886f4050afd8fe94e97d2e9e85c6bb748c0042d3249abb1342bb0eebf62058bf3de080d94611a3750915b5dc6c0b3899d41222", "u15g2qv6nj2h46z9wkqxvj584lqejspg8rv4a4djpzdll9mz3fh36c54qhwgpgdrh03s6ehzdpx87pfqvcd7hwl2m5clfl9n6fcfj07pztsdttdxy5sfxx0dnrug38yders826wh5x64fq2sac2q40g6sk65a22wt0fmg8pvjhrel98f4jtxf4yg2l2jkr0aw0lng2hhwwzme7yzm5k5sxeys83hwwxmlum04z374za20zr2ghhty5ada5qpv5ry9ht6amrg2mfn8vf8emr84ng66zf2e48ymdfc7hxdcvmaly7j7kg5g27a42vgrvs5w7m996xmx4vtv924nlzcekpjgkfskn677muv6u0t8uqmv0q2hzdgvk6p0j39zm2u6vyct2sf6vnq24crgy3zfdg5v2tmwv5vtyuq8sdu7xjn9yuae4gtfs0ug3ryhujq43azve9t9zmm6pccxswxkwfexvzpmn8xrfltdfmgex68y6wrd3qwd0sgp0p8wxf6eyup2ydff2ytxq4ehngt35zeh6vv5u4t3r", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 4, 2],
[nil, nil, nil, "ea9df83fbee07d6f7895ebb2ea41ec7c4ba682b863e069b4a438e31c9571c83126c305d75456412aeaef1b", nil, nil, "u12xugd90flrkdkeu3nlnn3uesky53pqu5m24y6apxm88m48v7374cls56zp93naylaxdchf0qayfxtrge047m953qz3v2gr4ltsr2sk3r", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 5, 0],
[nil, nil, nil, "fd3e7eccdb1a91f2c4498bb7eb61cba83eca499cfde9c5ce3e3241873bad2e423abe91dece0a6930e8901d", nil, nil, "u1kffwpljstudp6rmdmdtfc8h408yf983qyrnl2pfsymapqsrsgznp2qnh57mzlzdcmecdgd82repec6zfjw874w5frxrw73na6sj3m90t", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 5, 1],
[nil, nil, nil, "5ef3c8b2bf2a8b0e60a6254f312229b4124d4787e7dada5d81e16b51211707871bede32811a35f4094ae8b", nil, nil, "u178uk6ed50uezamsthlls26qxwpj0hlv2nd7l4wl586x7y9zpzvmnjgwrx7f9j260ny5f3ewxxasun8zklw4schlmh6aw0c5epuuc75tg", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 5, 2],
["7ddfe9c9b7d08ed6bda0bbc2be4030e04679d59a", nil, "1cc9bcb1a50880e4efb08e6e5a49305d358d575a746a51fe0db5a96b7eb39bd20744dae185061819fb7967", nil, nil, nil, "u16u53z44ydps7d5kmx7wyt3cf67z8rpllvmfgcsj3ra0uaxwqm033s3xfczfl3l78h9d6th6p2de8t0dqkad4u50k3gsalvwlwrwkvf3px7eyzgsvz83sfdhg79pfgxhgnl0hz03vx36", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 6, 0],
["150a17d8f88608900ae24ca27046fbfa082940b1", nil, "ac093a82a7f4a5ab66bcc994bbfc5b3f5f945f4499c5d8987f6404ceb4a91c46320b3618c318d80281b285", nil, nil, nil, "u1vl86hhk6wtueylzh72tksuh5pscamttzzkq7f3vnq6stg4u63zwmwndjul9jfu8g8ns8hu075f09cvc48j456048rtcwueka866v7dx3088sth7c59wd3jvh0pfc22s9vtkjyknhtgv", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 6, 1],
["133341e730a55e80e5f7a0bbf468663835f7e0c8", nil, "7198a7b9bf9099809a63bccbd56af56744ea2857ac8d12892ad58d82fd5b0cce71ea7a25816007c34491ec", nil, nil, nil, "u1uhuw9lsxpw72ejrsgeh8rrztu7cm7h3qjyawyrgcekgn0f277msnlyqgysu5986ew3z8a2g0lmvcf36dqq7wguy7gvnteprz4qn8qkwwfq2pxeyukyn3v4tl65ptkjzul8lpv32lxly", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 6, 2],
[nil, nil, nil, "cc099cc214e56b1192c7b5b17e958c3413e27fefd553380700aca81b24b2918cac951a1a68017fac525a18", 65530, "942bfc9f4fd6ebb6b4cdd4da2bca26fac4578e9f543405acc7d86ff59158bd0cba3aef6f4a8472d144d99f8b8d1dedaa9077d4f01d4bb27bbe31d88fbefac3dcd4797563a26b1d61fcd9a464ab21ed550fe6fa09695ba0b2f10eea6468cc6e20a66f826e3d14c5006f0563887f5e1289be1b2004caca8d3f34d6e84bf59c1e04619a7c23a996941d889e4622a9b9b1d59d5e319094318cd4", "u1643kh4y642rxtsqa2pvmjnkch8ex0z3u5j8upczd4vgl8wqh0whpyu5cssjuyp4pf2g8mvz7phpuaj8zakha7qtzw6ec9hpczwa5n5zu88ylfxhysq3xpxtfemprkw2j4w78n6q3x4h2ty35cfgyzk7aduzazav4rg4y08du34kr5kdtg22plumma5c2xz3tzksr9et8wax0g647xrrmqqq29ryx29fet7xhrj44zz2qwfvmth0hcrvwrynd7m73tdf9sq9fkfm8g850y4l37ycw7eet48y0p03vwhzdmhrcnag670ctvfehsfs4kjtpssr6ayrvjuje2ftmgzyhgpgwgn2q6m9uv0", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 7, 0],
[nil, nil, nil, "3c025e3e717e185a77c29ef372b6e7c3b17c459256074fef6e6cbe321cf9e0886a82598867b979de1a7da1", 65530, "05ba27b7e2c084762d31453ec4549a4d97729d033460fcf89d6494f2ffd789e98082ea5ce9534b3acd60fe49e37e4f666931677319ed89f85588741b3128901a93bd78e4be0225a9e2692c77c969ed0176bdf9555948cbd5a332d045de6ba6bf4490adfe7444cd467a09075417fcc0062e49f008c51ad4227439c1b4476ccd8e97862dab7be1e8d399c05ef27c6e22ee273e15786e394c8f", "u12h7pa43zagukq40ndvujxsw2t3ekgzt8xfk4tkdn0u5mckfgg8nuxxwjf8xlwehgsg2zy2hesc5dm2mm2xy20rxkcw89fm37wz73xgp5amm9ccpczasmq4qhp2gnjgj4yxxpxsxtrjpgvssguau0tc7pdyjm6hh0843zk9cmmu60c5ckuaj7p585q330pd4dukpmseu9havskyhda6zcd58kc4dfpkpl24gd4y86z88kgmka6nx3y5d8ndmvwyw2sl3gcedrfrwphxm56q5dkg2amccs84egjrhnlfz2sanvydpn2y808u4m2mrh3vu4xcccg6v6y2uv957ja3r060cewkjs25ydd5", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 7, 1],
[nil, nil, nil, "cb1c888234bd7f9e6bdbc2178bd32c2fb0451384027975e83f71a98871a290f3bf43c94be686c77b12edb3", 65530, "1be31682a30147963ac8da8d41d804258426a3f70289b8ad19d8de13be4eebe3bd4c8a6f55d6e0c373d456851879f5fbc282db9e134806bff71e11bc33ab75dd6ca067fb73a043b646a7cf39cab4928386786d2f24141ee120fdc34d6764eafc66880ee0204f53cc1167ed20b43a52dea3ca7cff8ef35cd8e6d7c111a68ef44bcd0c1513ad47ca61c659cc5d325b440f6b9f59aff66879bb", "u1xsvuwuzm254u2rycpckqsv8hv592jktdf9cgadxp4j82mvhvh2w6k4d9lrdp260gm7lkspekskgakujyunywqv3g0kzd72e0pwmdg28ykfhjnfvr2j066cd92yjk5xjka00dmsf0kax5zdcgyknu2j7s84x7aeue2x97705ydq7s00cuntwft5cv3ejzd4whp4awcr9vsuhfjrqlnkjyk9v6n89jg57umf57fnace7z2ysdjqudcfwwjwp2999h3jpypa0c5da4hh9t48p7gxd6a5cm4xkwmddmspmxazm89s7p4fa425n0wrlu6gnf9xy4v89wt03wwtcs7jmz3zfz9nk3s9ldymk", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 7, 2],
[nil, nil, nil, "5f09a9807a56323b263b05df368dc28391b21a64a0e1b40f9a6803b7e68f3905923f35cb01f119b223f493", nil, nil, "u1cxccyemm08tydwmt9hp2s5nf8wjvluuu6l2e8a9jflldxasnzkd8fverqpcj0xnvraczqg255cw5nvy6x9wruffmp9uezrzr7gcx559k", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 8, 0],
[nil, nil, nil, "21006cfbb3db4f4bb63111ef63f7f80056f31b344d06aca5b7fa0740c660c8b2dc3bd234f4c18ae9eaf811", nil, nil, "u1e38h9rl45mryslydy7g4sjh4wqfql54ay6mk0hj78pay5p3esnz3kmk7sqfyjgpla5yrwa9h6tqy9y6earefuhq69n92sgq8que6mlvk", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 8, 1],
[nil, nil, nil, "04915d2bebce11111ce195226cde8440263c50204b2272ac8a96b38dbd70db8969ec9b6c87cd15d9d76512", nil, nil, "u1clspfaz7dswwrkl0vlp4gfjmprmsvdjyq0xewwldt8rfw5yy0meqqscczn74j3n6rpgucpk99mpf7gfr87yw99tum7pz796kpyjk97ml", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 8, 2],
["6b94c815d9cd2ac0cca4ff8bee0cd5175afb0d72", nil, nil, "e340636542ece1c81285ed4eab448adbb5a8c0f4d386eeff337e88e6915f6c3ec1b6ea835a88d56612d2bd", nil, nil, "u153s0peymew62a299k3nw437h57nyqgw28art0q7z5wa7lfp62w3czu2njsc4lma48kqlp889fh854nmhlpjyzwffmrdc0guj082jqef9knsvuwjc4mkypf0nqgydqsu5wzcy77255ev", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 9, 0],
["53a89cd8e8b635cd4c4b39d4df0a035d045c50d4", nil, nil, "3fadf8edb20a3301e8260aa311f4cbd54d7d6a76baac88c244b0b121c6dc22a8bcce15898e267829fc1e01", nil, nil, "u1fdsfklmddxv2uq6ypsz09vf28vyldv4qen2spj0lrzdl0jv38pdk7a6ju3806zdaghwaway62nvuhv98mwrfd4tdn7tu22eq4ma7emq5gajn7tuy7dxradjkzeeust0x7h7pvcfyhus", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 9, 1],
["972085f9e6a02aabb3c420f0e4e7c8c3267a00cb", nil, nil, "987fd74a2256c596a66f83eaff7bb026286e972be56d3b50e3459747dfba53ffa0f24732b4aa6cd437a317", nil, nil, "u1uh2cdr9mqewwzujfwnnf49mczesvmprjnqrk6yjqfvwshlquq07p73y43tpdha04fjdz6x6plneqz5rj3ka2tfr83768kglsn5a53u278sm9nfjx42ev2feht0e2tsczedw2z49m8uh", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 9, 2],
[nil, nil, nil, "cdf7fed0d0822fd849cffb20a4d5ee701ad8141e66d81ddfabf87875117c05092240603c546b8dc187cd8c", nil, nil, "u1sj55qey22hefwyz6mnc3ldz7fcnkg9536uk5e98rwznvvald27r2vzmeupya5u696l5j0w6f5fdxkf54yvyhy0xze2u2wa2zdv2g67u2", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 10, 0],
[nil, nil, nil, "a6aa4fd8937382812c9e5b333f95bb440e290f2e2dc6b9978298d88c277b85f7dbb4ab9d9788479b75ce04", nil, nil, "u16d6remztx7ezmgfdvcglv3w44cjdmjapf0jz6wgldj49p2a7269gf5gzpun597mv50d3h4pgetgznawjdjvaeuhqgx6qzxk09qq48qqf", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 10, 1],
[nil, nil, nil, "e4e01051b99c08506834971f80dadec44a4da13ecdcba617f77fc48d25324f57cb1d4d7424705d573cd682", nil, nil, "u1wyge9pna2zs3qwc27uvu6l0cf8sxtrsr2lpfkdz8g6gxhxlvy48xalk5xapwawrwer8vc4weha25cxd5vumzj9yyhxrhzuzkz5nw5u5d", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 10, 2],
[nil, nil, "0ba97e749afc9322db91f262fd8d2872f05f09de246b1f9068abfd25f5165da1a05115c6f4784d2b922a3d", nil, nil, nil, "u1e25fvhxh3hcqd9424am3pf20fvvvzjt4445lpk03f40d3xxjc6ppj5wn9lt0ru3zvnpt5q5fkhw0npgnu9z2en9l65xe4fqdxq7t4x5r", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 11, 0],
[nil, nil, "4f9e8b832971ce3bd99a2a1bd545fc258921fb51abcf8d2c00fcca7e9d2888fd60ffa31716786f1bcd4226", nil, nil, nil, "u1cnzmjsp3nl542nj73ezrvtll9ss7gmegjyuz65q7dn8wlyj99lxxzvy807t9700pesc5554a2egnx2pet6s99gllt2dzcsqn8sxvdwcr", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 11, 6],
[nil, nil, "314da30f32e4899d14d64212f70a10cfb60f541da1bdf72323654eefb75f1bdf4fccced2a4aae90e54e7e5", nil, nil, nil, "u1l9d7naej4k7pkm6f89rqvy9uc5q5rv70e78nyjtyt7st4hgyn9rj9we49amdjmg44s0uxmvyc6tq407phcexvwayc5jhts4trvs5uzpe", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 11, 7],
[nil, "4686e569d58d99c1383597fad81193c4c1b16e6a", nil, "6ed96d65379d5ece656901f5cb20cf554ce18600d4a1edcf6812f4459d7ff73cf2b88cd8476b75e8c08d28", nil, nil, "u149cwhzqdfvattu3dj40anwhge8z4slczq3wyf8m7qqp5uvwdsd5c3d3m55jltnzp0twl34p20mcxkuygvkwdnffuq7vlvl0l2mwlc502czdcfzwndznkqev2p703es0t82aujtdzcgk", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 12, 0],
[nil, "4686e569d58d99c1383597fad81193c4c1b16e6a", nil, "b6f481042a780462ffa96f81e1288978e5f05c791587de7e957729bcac6eb95892532b0fe13e9c7eef6a24", nil, nil, "u103cga223qevwyyq9wcdj34ngg2dmh30r7cwcgykjtyuu4kvm6gdye0dh2rkj8g7evq5pauzl8z68jrwn3wqjhcn6tkh058587u92tr6ufh8l4sfpzdmde32x0q28rty6e3pa2fkz5cv", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 12, 1],
[nil, "4686e569d58d99c1383597fad81193c4c1b16e6a", nil, "a8e557a58a1908eb8a1bb078b77a95c032fe0a0069ce8c89d3e7705a48d2c08f7b604e5af0218d8cc9c8b8", nil, nil, "u1encagheqh9gl7n7862g0z73fr2nsxzh75zjyyqsuynm03s7pat5se3e0xfpxy8mye9hknymfylzr03mm2h9z4wdpu37fzf4cfmprq6gs2x6xnzwjqtswh39tv0xt9953pam76jh7jxw", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 12, 2],
["33a6dd87b4d872a4895d345761e4ec423b77928d", nil, nil, "5178924f7067eac261044ca27ba3cf52f798486973af0795e61587aa1b1ecad333dc520497edc61df88980", 65535, "952fbfee76af61668190bd52ed490e677b515d014384af07219c7c0ee7fc7bfc79f325644e4df4c0d7db08e9f0bd024943c705abff8994bfa605cf", "u105s6j4gpnqtcwpnxl328kea8gf5jrvfw6s4vkjqz0pxnv4963d6l670hhyw5ugl3ydcsm9c3jzhdpn8v23j5p7n9uhn0sp774azjvzx69z9r2s0axl2pflhqdavtwcdrk3jzps9q35j720exual42phm3qyw5kxvdk77p8ug68w5dmuvr5lueu9dk02u84z96ecnc0ztax5atv9qcsedtafa4lex8jrdv5y77v5gtuk5p2pa", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 13, 0],
["eb1f66b5cd522c4f6a8e4bb81f3687c95c3939ec", nil, nil, "907639193311a847366c1a43ebaadd935a53180fd3e1219c07c8205f45077bc1768abdcf2425a4a13c4aba", 65535, "bc7ed746a7d3f7c37d9e8bdc433b7d79e08a12f738a8f0dbddfef2f2657ef3e47d1b0fd11e6a13311fb799c79c641d9da43b33e7ad012e28255398", "u1g50gam98jf3xvc8k0d7tcp2wkaxsz9qcmrn6nh3sjqypqcj0mjemdq7mcu5j0mjqduvxj37apk339zh7u3s8ckpkjvqalajeju4ht2w7xnsgy3max0n52dk4nknfvkxy9de9h37fa5rvljm7t0t5033x4pwhklkhgjradmknuvpe0e0z3urxh7cuj8l49tccc3760f8q9nsdv0ezp22vpvldqz9tu4rqjv62thc0ssrqxakn", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 13, 1],
["a9e21d18d3b8abaebcc89e7b6ab02def8ee7954e", nil, nil, "2809ddfc7db70c660a6c3fc7560c7add1c7889d9b277cb92d14cb40d2de00aae31670b753a42bdcdc3c220", 65535, "789262275f1175be8462c01491c4d842406d0ec4282c9526174a09878fe8fdde33a29604e5e5e7b2a025d6650b97dbb52befb59b1d30a57433b0a3", "u1285z7wyv8apqxky4hmagy9pu467t0qjhn8tsffjkwu0cduhzmwukwg5702j0mrmgepmekcj5tzd9e6dcyw79xeuhu3jxlaq5p7smeerugn8xddw5l32qstklpvw8f94mapx3hge6tcyukpxa9sdd2at85sftn5097snusewkcs2hz0m3m4chneym59k4krrmaer3wchlwzfvdxw87tsahdv5xjppks5dlm7m3f4ppykud0uy", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 13, 2],
["a56c057ef71dab58aa90e47025695c5faaea5123", nil, nil, "b208c9235c8d40e49b76100b2d010f3783f12c66e7d3beb117b2c96321b7f6562adb4efc144e39d909e728", nil, nil, "u1m83339wch8a7z0u78cnz37hcmc98q2lv8nmgmppnanvay5u59m4qym30fyphvnq36evezky5j9fa0eg9gxgfv5nr230uuggvltp6y59t5jxcdlq9q873j35n06zphugwchgwgeaq884", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 14, 0],
["d1dbe863479e28f55ad9dd1817d57f253886f310", nil, nil, "332f451dc6f7da17fe5ff4077d3d5db79a036e712df558853d4a854ac4f6e51474cf75f38fa97c22b4cf09", nil, nil, "u1w4qq0ycdlcnat2nkhqcq30wa4shnat0avgcf487t7etge34x8dxqah35drfsh5cmynqumnhdn3h0hhv0d738mskln0hlehph924l83wcju8qjk79pn4q29dp6kgcfvka0mcw6a4vm4k", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 14, 1],
["0c2af13bc82480607ea4e52412029bbbce6bb060", nil, nil, "3b68c29b4a138b289fea8b6795e64759a7cd7c0aaf4bb98ed3079959b0bba9b761704b6cfc1465ad74bb05", nil, nil, "u1aujw5lwjlajnysfmjyewn0c2qeann0x4l34axhxg0nd8pvlzglzg00c32jq0urgdc7cam05fte554lqn8wsecpc4pgm8nk5c546egpjwuw262mhgck5dxfrfwevpga2m66u9qskynum", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 14, 2],
["228e677b0b428bdbfa4fcaee0714c81963760585", nil, "eee19641bc6b802f353eb793f728b17a277ef0358696a24a7122bc56537b229647f3810d27ce45227c6f39", nil, 65535, "c447586f69173446d8e48bf84cbc000a807899973eb93c5e819aad669413f8387933ad1584aa35e43f4ecd1e2d0407c0b1b89920ffdfdb9bea51ac95b557af71b89f903f5d9848f14fcbeb1837570f544d6359eb23faf38a0822da36ce426c4a2fbeffeb0a8a2e297a9d19ba15024590e332", "u1u5kkc8lggdf4dgg9t7qg8vfpd06zvpcetq34rw55yg329kqrclj99atez0hn7jtg0cw5qh7jxewees0ynpv2hlkhtcpgsjjm22zrh8hn8j7uddvrwt07dwqfwh7mqkft326ft4y038s9y77dentqeynw2g6smh7s0ceuah6lylvxj07ex474u2lsh44m59gep2wywskwt54wx299jsr0h7rn9z6vhp59sxf5ey8qczv5ck6d0czkupvua6kcl6h6n3ssqvcfueh46xwr07clhexdjax9l45turlwxd9s8xc5c3nczmd2uyseut2zrra3kg6sucdj", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 15, 0],
["55a6349abd9eadcdbbcd337c71f09723b241beee", nil, "50ca46f825f7f423007aa4147169b529f07f1c8ed634fafc8145a4813177dd1257ee8d8fc5f44e9b564f6a", nil, 65535, "9d9fa9261f9938a4032dd34606c9cf9f3dd33e576f05cd1dd6811c6298757d77d9e810abdb226afcaa4346a6560f8932b3181fd355d5d391976183f8d99388839632d6354f666d09d3e5629ea19737388613d38a34fd0f6e50ee5a0cc9677177f50028c141378187bd2819403fc534f80076", "u1pcqreshyawlmmskegkjvulf8xk6h97nxckx88aj6m88qu2zeucf4kywfsz43fjrce6w4w5jv5e28n430z2jlj9vfv5yrs5cudx3qv8p7d9jmx5vhayk9a8gyl49yfjp46t6c7d8vh5jhuu7llfkzt2gswkvt2368cpjc0u8u2sgawtgzu07y55raunrx5n48v3tuzpwv8273tnrunh40yu3gk98zhg0pmjcjg8c3tf3qmnfr8zm93eynplk4jx7ypev84pl0yxhk5ckq8zr5gv267skrmcpqgngpnrn43hl9awe7qwvzhds7zg6qtfp9fggpnmvc", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 15, 2],
["f7b2c94ce3ca21526b6b472d3d5611fa98bec498", nil, "c412c8ff78f28d9b3391f4ab15d06acf46ac052821ee096a51524813f2adf9a4065cc6c45feba2c052df9e", nil, 65535, "e9380cb4964d3b6b45819d3b8e9caf54f051852d671bf8c1ffde2d1510756418cb4810936aa57e6965d6fb656a760b7f19adf96c173488552193b147ee58858033dac7cd0eb204c06490bbdedf5f7571acb2ebe76acef3f2a01ee987486dfe6c3f0a5e234c127258f97a28fb5d164a8176be", "u1dffmg4dp98h39xcvv4ghgaz76fmu50pezp8uf7xpyysj3ye8j9e07r8sdhge4yjk6ssl94qna5lwc6sjpdzk43tvf2gpxlzkymktqy9az77rjavvawy57xu5tyaevekq3esd5g23ew6gpt9srj9fqxusx4u5qnvwl4f3y39v2ryld8plc34x9l2y73m6wzk72gjepe536wf92q4065taed6cmcgkvr65rzv268qwhmwk5z77fkcseac3ewsmuxa4suzkzv0383yaykyj9a6v2xxg24p7e5k8paxadd59xs330t9nm6geuu2jps6ejh69asqak3x0", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 15, 3],
[nil, nil, nil, "2598d84dffb34f5908b90732490f3881399150d4c694fce9bf30d1560b2c56f09829fe123b9add20e5d71c", nil, nil, "u12jl7f8zw52z4lqvy6rdfc5q4zlz0gr50uq87zgwr6nvcu8pfkpnajx8uyj8utlsyekj7uafehuxn3tg8ufczgk8xwzj2fz0fjvh9fr8p", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 16, 0],
[nil, nil, nil, "c1150ae8529e667015c462f91fb26e9124095aebd6e72fca95a2fe17ae53e8cb101eda84d9fb4d336ee103", nil, nil, "u1xxndf9jxq3k60r2walgtza0e67nqvuwuhc3va3trhhgkkgynnwzjrwthktt68w2es9vwehgx2rx0sqt65ru087vutdh2v52ypcu8ylkh", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 16, 1],
[nil, nil, nil, "e961944a708a15c9c62734c34510bb5e2cd740abdeb488e4142b5d402b0295bec67922f1e71ab7fbd0a2ae", nil, nil, "u14ke36gmjx6xj370ntw3h3ruyl2ad57d8ma5zvwd22ygjfsycmgr6ft4pjm7l2gw3xzglz4lrh9v9mzgaj08vsghqaw062j5uvvxlwc54", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 16, 2],
[nil, nil, "d3a803803feee7a032a24adfaa8f6a94cecb9671c1333d0d5d1a3d79d82bc310727c665364d71022559c50", "7c98b8f613f9ff02746bea2a167cfd1bd3a1862af9631bf61d9d604e0824e2cb8467a1e549db87a76e7a8a", nil, nil, "u1a64l09qrsxulfjznm6k2g535usyhtaf8ed60v4jrjmkwvkux4t7pdyc3nkzrefdgtnw8420lj8shm05ja9fxxgnhra92nhsq56gx8c2puz3fkkgnrkqf5yuqfdtf7t6ran47gdcf5vvdfaczwf3uuy4fysh3mzu8hd5tkl05mvrge9n8", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 17, 0],
[nil, nil, "25c25d58c50533dfb55d29f9a8864f58f02ea4fed44369352c43538cdf9545b905bb2ef0961bd2daf25883", "8a1bff2a9d921e1153b3cb264bc05185a9811de911d53467935434d6537d306752d02054fe5a170464259d", nil, nil, "u1f2mk4p04nnn9rmxqs4elkmqd42l0gxzfeurrn2zdedrjutx6fh0hgxz8avdlq45uv5achkjguvpyay329sk2raxkswspfy9z4m9a2yn8mkla3qpjpe3d9slczyj3ulf3pdvf2yrswayxj9ausn7d2zze850ctvkrltfjvvsjwgnkues4", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 17, 1],
[nil, nil, "54b7fc0c85d378f375be48218a85424bb9e7a304830e9eb7255a12a09c961cca1f629b867e13242ed90d92", "14adca6f616abcbe5bc850cc617dcf999517a9a790292fec6bc0761eaa790333e7d06d016de05bca7c6712", nil, nil, "u1cha4ke52axxa37d3y5yjcdkpxjtgqvckww9zaecaq96c59y2hzm3hqsu5pktakkjhcl2r28ltdx95lfvq7y9wd5w4w0hfdrhgy0tkczephu30py7qyp7ftth92e68hhkjepj32xm2fp538p37nssx867g8t5apu84unc39ywxyltjhyq", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 17, 2],
["26c061d67beb8bad48c6b4774a156551e30e4fe2", nil, "bd51d90654e25613b5a4839dda2b9524ef4b12e7dbdccf3d4af150b06d95758afeb029227396094faf914a", nil, nil, nil, "u1p2nrze8u4geddlwn0dnppywuux58u4f24uqkfy6qns2hx2x3rm670q40ksp84ke2j3dfk0jn04gr3w4jp6yl69w48mxy795p98xfp5mvw7tkdp2utzm8fxjwm0u7fm79sx0c5h78ghx", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 18, 0],
["42874a6933bef7334702243232380781df8a71c5", nil, "4cea9549426d764dbd1b80c229e230a3bf3ca821b97c9847488afe37341634df27c3a634c2253436d98612", nil, nil, nil, "u1ufx6z3ck7ylkuph78pjpjgjflxh6dvvjx5aq7v8mwt4r3dlk4eyxqnk9tenck573vngsq555afelws0t5gm2rtuvswagsasff4naa4pkwnflcaxctxcvgy3m558mh7cgcgn26gq9a69", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 18, 5],
["c7ce0d050e10a82486bfab0a16537829dc6cf018", nil, "02221bc1820ca2b1940e532f00042367ee96acfe9f83476661a1ca4131e5d7485f4a49e0992a8917380f8b", nil, nil, nil, "u15quy62m78qxdl4yw5dzvt89vs0d6euwsa4mj4csxhyp0t7q40tggrlfyl3vt85caa53mg3xyz64xutxuzg7mmkyqydqypta2c6g2g9kgk3nu75jrnk6xj7eprpu5l36v2esykxfz0wh", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 18, 7],
[nil, nil, "8660070e3757ff6507060791fd694f6a631b8495a2b74ffa39236cf653caea5575b86af3200b010e513bab", "63b7b706d991169986aee56133f0a50b2a0c8225fba6dae95176007b1f023a1e97c1aa366e99bf970fda82", nil, nil, "u1vg62mgjddnlv5w6ldky2xe0c8tetmc82tu9vlzzkuynx49fnuqjvxjt5dgn3cm8t5n85zcq5ljrtg7zmwhk70h6rdmclf7scxxnguk5flvf2app76xu907cmjylxvsen25xe9v7v3krsxa9uy0v2jjq37kh4ymlafn8pevqalqa4dm67", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 19, 5],
[nil, nil, "6d75a1a948a4e730db3b4b816dbc7d80b4eb1bc68de9ac87b0cd1f1b3e6068e677888e105ac727c0d14b49", "52692e9402a8c14a225ec9dcf75a5588392e4da69e416f124e32168ff483144973d7ac8c23e4277fde5cb0", nil, nil, "u1z8m2p8pzzsxdek3x784l5xkd56u68wvv708pn475tkvultp7h6u66r8kxf9kttyahqnpdhvg9rfrmudxsf5le0c9mjmk942jm0yr78epqnwptzjuawgdhgy60zus2l62403hk5nlzhqlet85atyg88xxrwwj8dmvx98cy47g7s7qdsq7", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 19, 11],
[nil, nil, "38b14b44ed6f4a3ae8c5c3923e5770b786f9b41d46c65a149b13910f4a0a64e83bb9bc98e80d9576fbf76e", "4b42498b76509868e9e74d75357ba19be8e9d55d024adf4e06c756c830e74a46df5bceeaeee595a6ed333e", nil, nil, "u1yuv2ls5k0xaym2lc4je6mf3djvmkyqdrxhcue25d4243udr0e97pyzhs9u3yl9q5de24tzxxdl2wfy4vvdw3wmu78sggdjq8xrshdztxnk3p6lqe4hk952fnq7qxggyxmnvtlkl3pns09zysunxu288jme6qlvjlhh5ry0fqyg5rs00y", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 19, 15]
]

View File

@ -1,6 +1,82 @@
# Unified Spending Keys
## Changes to Demo APP
The demo application now uses the SDKSynchronizer to create addresses and
shield funds.
`DerivationToolViewController` was removed. See `DerivationTool` unit tests
for sample code.
`GetAddressViewController` now derives transparent and sapling addresses
from Unified Address
`SendViewController` uses Unified Spending Key and type-safe `Memo`
## Changes To SDK
### `CompactBlockProcessor`
`public func getUnifiedAddress(accountIndex: Int) -> UnifiedAddress?`
`public func getSaplingAddress(accountIndex: Int) -> SaplingAddress?` derived from UA
`public func getTransparentAddress(accountIndex: Int) -> TransparentAddress?`
is derived from UA
`public func getTransparentBalance(accountIndex: Int) throws -> WalletBalance` now
fetches from account exclusively
`func refreshUTXOs(tAddress: TransparentAddress, startHeight: BlockHeight) async throws -> RefreshedUTXOs`
uses `TransparentAddress`
### Initializer
Migration of DataDB and CacheDB are delegated to `librustzcash`
removed `public func getAddress(index account: Int = 0) -> String`
### Wallet Types
`UnifiedSpendingKey` to represent Unified Spending Keys. This is a binary
encoded not meant to be stored or backed up. This only serves the purpuse
of letting clients use the least priviledge keys at all times for every
operation.
### Synchronizer
`sendToAddress` and `shieldFunds` now take a `UnifiedSpendingKey` instead
of the respective spending and transparent private keys.
`refreshUTXOs` uses `TransparentAddress`
### KeyDeriving protocol
Addresses should be obtained from the `Synchronizer` by using the `get_address` functions
Transparent and Sapling receivers should be obtained by extracting the receivers of a UA
````Swift
public extension UnifiedAddress {
/// Extracts the sapling receiver from this UA if available
/// - Returns: an `Optional<SaplingAddress>`
func saplingReceiver() -> SaplingAddress? {
try? DerivationTool.saplingReceiver(from: self)
}
/// Extracts the transparent receiver from this UA if available
/// - Returns: an `Optional<TransparentAddress>`
func transparentReceiver() -> TransparentAddress? {
try? DerivationTool.transparentReceiver(from: self)
}
````
**Removed**
`func deriveUnifiedFullViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [UnifiedFullViewingKey]`
`func deriveViewingKey(spendingKey: SaplingExtendedSpendingKey) throws -> SaplingExtendedFullViewingKey`
`func deriveSpendingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [SaplingExtendedSpendingKey]`
`func deriveUnifiedAddress(from ufvk: UnifiedFullViewingKey) throws -> UnifiedAddress`
`func deriveTransparentAddress(seed: [UInt8], account: Int, index: Int) throws -> TransparentAddress`
`func deriveTransparentAccountPrivateKey(seed: [UInt8], account: Int) throws -> TransparentAccountPrivKey`
`func deriveTransparentAddressFromAccountPrivateKey(_ xprv: TransparentAccountPrivKey, index: Int) throws -> TransparentAddress`
**Added**
`static func saplingReceiver(from unifiedAddress: UnifiedAddress) throws -> SaplingAddress?`
`static func transparentReceiver(from unifiedAddress: UnifiedAddress) throws -> TransparentAddress?`
`static func receiverTypecodesFromUnifiedAddress(_ address: UnifiedAddress) throws -> [UnifiedAddress.ReceiverTypecodes]`
`func deriveUnifiedSpendingKey(seed: [UInt8], accountIndex: Int) throws -> UnifiedSpendingKey`
`public func deriveUnifiedFullViewingKey(from spendingKey: UnifiedSpendingKey) throws -> UnifiedFullViewingKey`
# 0.16.10-beta # 0.16.10-beta
- [#532] [0.16.x-beta] Download does not stop correctly - [#532] [0.16.x-beta] Download does not stop correctly
Issue Reported: Issue Reported:
When the synchronizer is stopped, the processor does not cancel When the synchronizer is stopped, the processor does not cancel
@ -20,6 +96,7 @@ Mainnet
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1807500.json Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1807500.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1810000.json Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1810000.json
```` ````
# 0.16.9-beta # 0.16.9-beta
Checkpoints added: Checkpoints added:
Mainnet Mainnet