Merge pull request #202 from zcash/enhancements

Enhancements
This commit is contained in:
Francisco Gindre 2020-10-16 15:14:55 -03:00 committed by GitHub
commit 5d84755615
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1443 additions and 240 deletions

103
Cargo.lock generated
View File

@ -111,6 +111,12 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1"
[[package]]
name = "base58"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83"
[[package]]
name = "base64"
version = "0.12.3"
@ -295,9 +301,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.60"
version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c"
checksum = "8dae9c4b8fedcae85592ba623c4fd08cfdab3e3b72d6df780c6ead964a69bfff"
[[package]]
name = "cfg-if"
@ -571,6 +577,18 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "hdwallet"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35b15cc3c181a2aace485d56c784568a4ae6e34322287a2499549d4cda7af3e1"
dependencies = [
"lazy_static",
"rand",
"ring",
"secp256k1",
]
[[package]]
name = "heck"
version = "0.3.1"
@ -601,6 +619,15 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
[[package]]
name = "js-sys"
version = "0.3.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "jubjub"
version = "0.5.1"
@ -642,10 +669,16 @@ dependencies = [
name = "libzcashlc"
version = "0.0.5"
dependencies = [
"base58",
"bs58",
"cbindgen",
"failure",
"ffi_helpers",
"hdwallet",
"hex",
"ripemd160",
"secp256k1",
"sha2 0.9.1",
"zcash_client_backend",
"zcash_client_sqlite",
"zcash_primitives",
@ -869,6 +902,32 @@ dependencies = [
"winapi",
]
[[package]]
name = "ring"
version = "0.16.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ba5a8ec64ee89a76c98c549af81ff14813df09c3e6dc4766c3856da48597a0c"
dependencies = [
"cc",
"lazy_static",
"libc",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "ripemd160"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251"
dependencies = [
"block-buffer 0.9.0",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
[[package]]
name = "rusqlite"
version = "0.24.1"
@ -918,6 +977,24 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "secp256k1"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2932dc07acd2066ff2e3921a4419606b220ba6cd03a9935123856cc534877056"
dependencies = [
"secp256k1-sys",
]
[[package]]
name = "secp256k1-sys"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ab2c26f0d3552a0f12e639ae8a64afc2e3db9c52fe32f5fc6c289d38519f220"
dependencies = [
"cc",
]
[[package]]
name = "semver"
version = "0.9.0"
@ -1001,6 +1078,12 @@ version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "standback"
version = "0.2.11"
@ -1188,6 +1271,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "vcpkg"
version = "0.2.10"
@ -1266,6 +1355,16 @@ version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307"
[[package]]
name = "web-sys"
version = "0.3.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"

View File

@ -15,6 +15,15 @@ zcash_client_backend = "0.4"
zcash_client_sqlite = "0.2"
zcash_primitives = "0.4"
#### Temporary additions: ####################################
base58 = "0.1.0"
sha2 = "0.9"
bs58 = { version = "0.3", features = ["check"] }
hdwallet = "0.2.2"
ripemd160 = "0.9"
secp256k1 = "0.17.2"
##############################################################
[dependencies.zcash_proofs]
version = "0.4"
default-features = false

View File

@ -18,7 +18,6 @@ target 'ZcashLightClientSample' do
end
target 'ZcashLightClientSampleUITests' do
use_frameworks!
inherit! :search_paths
# Pods for testing
end

View File

@ -66,10 +66,10 @@ PODS:
- SwiftNIOFoundationCompat (< 3, >= 2.22.0)
- SwiftNIOTLS (< 3, >= 2.22.0)
- SwiftProtobuf (1.12.0)
- ZcashLightClientKit (0.6.6):
- ZcashLightClientKit (0.7.0):
- gRPC-Swift (= 1.0.0-alpha.19)
- SQLite.swift (~> 0.12.2)
- ZcashLightClientKit/Tests (0.6.6):
- ZcashLightClientKit/Tests (0.7.0):
- gRPC-Swift (= 1.0.0-alpha.19)
- SQLite.swift (~> 0.12.2)
@ -145,8 +145,8 @@ SPEC CHECKSUMS:
SwiftNIOTLS: 46bb3a0ff37d6b52ae6baf5207ec3cd411da327c
SwiftNIOTransportServices: 801923921fbecdcde1e1c1ff38e812167d01ead1
SwiftProtobuf: 4ef85479c18ca85b5482b343df9c319c62bda699
ZcashLightClientKit: aa2bfedb1cd48385d35113672b521a4bb47fbbf4
ZcashLightClientKit: 6a12b800a373f3330d4bc177480e56b499c23a8f
PODFILE CHECKSUM: db5f48346d4a54b2e0a33adb8890ffba9289ec63
PODFILE CHECKSUM: 7d5095283dc02470f40ab06564d94076ba16d570
COCOAPODS: 1.9.3

View File

@ -7,10 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
0D2343EE238C91B900606F71 /* sapling-output.params in Resources */ = {isa = PBXBuildFile; fileRef = 0D2343EC238C91B900606F71 /* sapling-output.params */; };
0D2343EF238C91B900606F71 /* sapling-spend.params in Resources */ = {isa = PBXBuildFile; fileRef = 0D2343ED238C91B900606F71 /* sapling-spend.params */; };
0D49A18C241698A800CC0649 /* SampleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D49A18B241698A800CC0649 /* SampleLogger.swift */; };
0D4EBA312396CFD70041B507 /* SendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D4EBA302396CFD70041B507 /* SendViewController.swift */; };
0D6CE8BD252E3C4A0005D707 /* SaplingParametersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D6CE8BC252E3C4A0005D707 /* SaplingParametersViewController.swift */; };
0D756A94236C761E009B041B /* GetAddressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D756A93236C761E009B041B /* GetAddressViewController.swift */; };
0D7A4A83236CCD88001F4DD8 /* SyncBlocksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7A4A82236CCD88001F4DD8 /* SyncBlocksViewController.swift */; };
0D7C85E523AD5A9B006878FC /* SampleStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7C85E423AD5A9B006878FC /* SampleStorage.swift */; };
@ -30,8 +29,6 @@
0D8BB46223B1DA0700D5E2A1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0D907F1E2322CC5B00D641FE /* LaunchScreen.storyboard */; };
0D8BB46323B1DA0700D5E2A1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0D907F1C2322CC5B00D641FE /* Assets.xcassets */; };
0D8BB46423B1DA0700D5E2A1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0D907F192322CC5900D641FE /* Main.storyboard */; };
0D8BB46623B1DA0700D5E2A1 /* sapling-output.params in Resources */ = {isa = PBXBuildFile; fileRef = 0D2343EC238C91B900606F71 /* sapling-output.params */; };
0D8BB46723B1DA0700D5E2A1 /* sapling-spend.params in Resources */ = {isa = PBXBuildFile; fileRef = 0D2343ED238C91B900606F71 /* sapling-spend.params */; };
0D907F162322CC5900D641FE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D907F152322CC5900D641FE /* AppDelegate.swift */; };
0D907F182322CC5900D641FE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D907F172322CC5900D641FE /* ViewController.swift */; };
0D907F1B2322CC5900D641FE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0D907F192322CC5900D641FE /* Main.storyboard */; };
@ -46,9 +43,9 @@
0DDFB33C236B743000AED892 /* LatestHeightViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDFB33B236B743000AED892 /* LatestHeightViewController.swift */; };
0DDFB33E236B844900AED892 /* DemoAppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDFB33D236B844900AED892 /* DemoAppConfig.swift */; };
0DF53E6723A438F100D7249C /* PaginatedTransactionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF53E6623A438F100D7249C /* PaginatedTransactionsViewController.swift */; };
4B7E6D953A7CC9204FDE510C /* Pods_ZcashLightClientSample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6CB06C3C92417F3584991C0B /* Pods_ZcashLightClientSample.framework */; };
6355F11E6389A7F4FD9F21F7 /* Pods_ZcashLightClientSample_Mainnet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49540782BAF7E579947E23C4 /* Pods_ZcashLightClientSample_Mainnet.framework */; };
742DCA7AFF9FBEA217FD3EED /* Pods_ZcashLightClientSampleUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33F67E6BA5A27016288E77EB /* Pods_ZcashLightClientSampleUITests.framework */; };
A337DEF9CF408DD9F8C200DD /* Pods_ZcashLightClientSample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CCCFED26DDACC4FBD997470C /* Pods_ZcashLightClientSample.framework */; };
A459F9A413C2AB70A02FE894 /* Pods_ZcashLightClientSampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C89C3AE14EA6993FECC643BC /* Pods_ZcashLightClientSampleTests.framework */; };
/* End PBXBuildFile section */
@ -70,10 +67,9 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
0D2343EC238C91B900606F71 /* sapling-output.params */ = {isa = PBXFileReference; lastKnownFileType = file; name = "sapling-output.params"; path = "../../../ZcashLightClientKitTests/sapling-output.params"; sourceTree = "<group>"; };
0D2343ED238C91B900606F71 /* sapling-spend.params */ = {isa = PBXFileReference; lastKnownFileType = file; name = "sapling-spend.params"; path = "../../../ZcashLightClientKitTests/sapling-spend.params"; 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>"; };
0D6CE8BC252E3C4A0005D707 /* SaplingParametersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaplingParametersViewController.swift; sourceTree = "<group>"; };
0D756A93236C761E009B041B /* GetAddressViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetAddressViewController.swift; sourceTree = "<group>"; };
0D7A4A82236CCD88001F4DD8 /* SyncBlocksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncBlocksViewController.swift; sourceTree = "<group>"; };
0D7C85E423AD5A9B006878FC /* SampleStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleStorage.swift; sourceTree = "<group>"; };
@ -105,12 +101,12 @@
47991618469ABD2A7FE69E73 /* Pods-ZcashLightClientSample-Mainnet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSample-Mainnet.release.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSample-Mainnet/Pods-ZcashLightClientSample-Mainnet.release.xcconfig"; sourceTree = "<group>"; };
49540782BAF7E579947E23C4 /* Pods_ZcashLightClientSample_Mainnet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ZcashLightClientSample_Mainnet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
501B4B56030ED65B9C170CE2 /* Pods-ZcashLightClientSampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSampleTests.debug.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSampleTests/Pods-ZcashLightClientSampleTests.debug.xcconfig"; sourceTree = "<group>"; };
6CB06C3C92417F3584991C0B /* Pods_ZcashLightClientSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ZcashLightClientSample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
81B2AC726EA5CC3FEF521F84 /* Pods-ZcashLightClientSampleUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSampleUITests.debug.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSampleUITests/Pods-ZcashLightClientSampleUITests.debug.xcconfig"; sourceTree = "<group>"; };
90F55027AEE5406E35644223 /* Pods-ZcashLightClientSample-Mainnet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSample-Mainnet.debug.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSample-Mainnet/Pods-ZcashLightClientSample-Mainnet.debug.xcconfig"; sourceTree = "<group>"; };
93F435FD5C95BEE3BC7BBC28 /* Pods-ZcashLightClientSample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSample.debug.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSample/Pods-ZcashLightClientSample.debug.xcconfig"; sourceTree = "<group>"; };
BEA142AE2F3C339269CBA120 /* Pods-ZcashLightClientSampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSampleTests.release.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSampleTests/Pods-ZcashLightClientSampleTests.release.xcconfig"; sourceTree = "<group>"; };
C89C3AE14EA6993FECC643BC /* Pods_ZcashLightClientSampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ZcashLightClientSampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CCCFED26DDACC4FBD997470C /* Pods_ZcashLightClientSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ZcashLightClientSample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -126,7 +122,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4B7E6D953A7CC9204FDE510C /* Pods_ZcashLightClientSample.framework in Frameworks */,
A337DEF9CF408DD9F8C200DD /* Pods_ZcashLightClientSample.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -157,6 +153,14 @@
path = Send;
sourceTree = "<group>";
};
0D6CE8BB252E3C1A0005D707 /* Sapling Parameters */ = {
isa = PBXGroup;
children = (
0D6CE8BC252E3C4A0005D707 /* SaplingParametersViewController.swift */,
);
path = "Sapling Parameters";
sourceTree = "<group>";
};
0D756A92236C75FE009B041B /* Get Address */ = {
isa = PBXGroup;
children = (
@ -200,13 +204,12 @@
0D907F142322CC5900D641FE /* ZcashLightClientSample */ = {
isa = PBXGroup;
children = (
0D6CE8BB252E3C1A0005D707 /* Sapling Parameters */,
0DBF8F9323A80F0E0010B85F /* Transaction Detail */,
0DF53E6523A438BA00D7249C /* Paginated Transactions */,
0DA58B922397DDBC004596EA /* List Transactions */,
0D4EBA2F2396CFBE0041B507 /* Send */,
0DCD3DC5238D888B00DD3EC4 /* Get Balance */,
0D2343EC238C91B900606F71 /* sapling-output.params */,
0D2343ED238C91B900606F71 /* sapling-spend.params */,
0D7A4A81236CCCDB001F4DD8 /* Sync Blocks */,
0D756A92236C75FE009B041B /* Get Address */,
0DDFB33A236B733700AED892 /* Latest Block Height */,
@ -303,7 +306,7 @@
C89C3AE14EA6993FECC643BC /* Pods_ZcashLightClientSampleTests.framework */,
33F67E6BA5A27016288E77EB /* Pods_ZcashLightClientSampleUITests.framework */,
49540782BAF7E579947E23C4 /* Pods_ZcashLightClientSample_Mainnet.framework */,
6CB06C3C92417F3584991C0B /* Pods_ZcashLightClientSample.framework */,
CCCFED26DDACC4FBD997470C /* Pods_ZcashLightClientSample.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -340,7 +343,7 @@
0D907F0E2322CC5900D641FE /* Sources */,
0D907F0F2322CC5900D641FE /* Frameworks */,
0D907F102322CC5900D641FE /* Resources */,
45EC872B9ED4F87BF9B92B1C /* [CP] Embed Pods Frameworks */,
AD38BD608A7D4F64E720F342 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -441,8 +444,6 @@
0D8BB46223B1DA0700D5E2A1 /* LaunchScreen.storyboard in Resources */,
0D8BB46323B1DA0700D5E2A1 /* Assets.xcassets in Resources */,
0D8BB46423B1DA0700D5E2A1 /* Main.storyboard in Resources */,
0D8BB46623B1DA0700D5E2A1 /* sapling-output.params in Resources */,
0D8BB46723B1DA0700D5E2A1 /* sapling-spend.params in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -453,8 +454,6 @@
0D907F202322CC5B00D641FE /* LaunchScreen.storyboard in Resources */,
0D907F1D2322CC5B00D641FE /* Assets.xcassets in Resources */,
0D907F1B2322CC5900D641FE /* Main.storyboard in Resources */,
0D2343EE238C91B900606F71 /* sapling-output.params in Resources */,
0D2343EF238C91B900606F71 /* sapling-spend.params in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -553,23 +552,6 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
45EC872B9ED4F87BF9B92B1C /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ZcashLightClientSample/Pods-ZcashLightClientSample-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ZcashLightClientSample/Pods-ZcashLightClientSample-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ZcashLightClientSample/Pods-ZcashLightClientSample-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
A18B8A4D55F1DF8B808ABB67 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -631,6 +613,23 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
AD38BD608A7D4F64E720F342 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ZcashLightClientSample/Pods-ZcashLightClientSample-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ZcashLightClientSample/Pods-ZcashLightClientSample-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ZcashLightClientSample/Pods-ZcashLightClientSample-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -668,6 +667,7 @@
0D7A4A83236CCD88001F4DD8 /* SyncBlocksViewController.swift in Sources */,
0DDFB33E236B844900AED892 /* DemoAppConfig.swift in Sources */,
0D907F162322CC5900D641FE /* AppDelegate.swift in Sources */,
0D6CE8BD252E3C4A0005D707 /* SaplingParametersViewController.swift in Sources */,
0D7C85E523AD5A9B006878FC /* SampleStorage.swift in Sources */,
0D49A18C241698A800CC0649 /* SampleLogger.swift in Sources */,
0DA58B962397F2CB004596EA /* TransactionsDataSource.swift in Sources */,

View File

@ -40,12 +40,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
spendParamsURL: try! __spendParamsURL(),
outputParamsURL: try! __outputParamsURL(),
loggerProxy: loggerProxy)
let addresses = try! wallet.initialize(seedProvider: DemoAppConfig(),
walletBirthdayHeight: BlockHeight(DemoAppConfig.birthdayHeight)) // Init or DIE
try! wallet.initialize(viewingKeys: try DerivationTool.default.deriveViewingKeys(seed: DemoAppConfig.seed, numberOfAccounts: 1),
walletBirthday: BlockHeight(DemoAppConfig.birthdayHeight)) // Init or DIE
var storage = SampleStorage.shared
storage!.seed = Data(DemoAppConfig().seed())
storage!.privateKey = addresses?[0]
storage!.seed = Data(try! DemoAppConfig().seed())
storage!.privateKey = try! DerivationTool.default.deriveSpendingKeys(seed: DemoAppConfig.seed, numberOfAccounts: 1)[0]
self.wallet = wallet
return wallet
}
@ -132,12 +132,6 @@ extension AppDelegate {
}
}
extension DemoAppConfig: SeedProvider {
func seed() -> [UInt8] {
DemoAppConfig.seed
}
}
extension Initializer {
static var shared: Initializer {
AppDelegate.shared.sharedWallet // AppDelegate or DIE.
@ -167,14 +161,15 @@ func __pendingDbURL() throws -> URL {
}
func __spendParamsURL() throws -> URL {
Bundle.main.url(forResource: "sapling-spend", withExtension: ".params")!
try __documentsDirectory().appendingPathComponent("sapling-spend.params")
}
func __outputParamsURL() throws -> URL {
Bundle.main.url(forResource: "sapling-output", withExtension: ".params")!
try __documentsDirectory().appendingPathComponent("sapling-output.params")
}
public extension NotificationBubble {
static func sucessOptions(animation: NotificationBubble.Animation) -> [NotificationBubble.Style] {
return [ NotificationBubble.Style.animation(animation),

View File

@ -1,9 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097" 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="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Ewq-Xy-xHb">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -14,7 +16,7 @@
<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="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<sections>
<tableViewSection id="HAA-1f-le3">
<cells>
@ -238,6 +240,26 @@
<segue destination="6wc-2b-HvC" kind="show" identifier="Paginated" id="tXC-6F-YcU"/>
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="XCC-pZ-eel" style="IBUITableViewCellStyleDefault" id="dgB-c9-cv9">
<rect key="frame" x="0.0" y="506.5" width="414" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="dgB-c9-cv9" id="C46-42-3yU">
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Sapling Parameters" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="XCC-pZ-eel">
<rect key="frame" x="20" y="0.0" width="374" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="Nsd-EZ-dgy" kind="show" id="Xvb-pF-KgB"/>
</connections>
</tableViewCell>
</cells>
</tableViewSection>
</sections>
@ -250,7 +272,7 @@
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="u9B-ne-NE7" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="vYi-by-eJM">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" id="vYi-by-eJM">
<rect key="frame" x="0.0" y="0.0" width="46" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Button"/>
@ -268,7 +290,7 @@
<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">
<rect key="frame" x="0.0" y="88" width="414" height="774"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="TransactionCell" textLabel="U7p-Y7-W1b" detailTextLabel="ngx-lY-C9z" style="IBUITableViewCellStyleSubtitle" id="0Hj-GS-f83">
<rect key="frame" x="0.0" y="28" width="414" height="55.5"/>
@ -278,7 +300,7 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="U7p-Y7-W1b">
<rect key="frame" x="20" y="10" width="33.5" height="20.5"/>
<rect key="frame" x="20" y="10" width="33" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
@ -300,7 +322,8 @@
</prototypes>
</tableView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="uPE-9c-zlh"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="uPE-9c-zlh" firstAttribute="bottom" secondItem="7kA-7f-dpe" secondAttribute="bottom" id="NTg-7T-3FT"/>
<constraint firstItem="7kA-7f-dpe" firstAttribute="centerX" secondItem="uPE-9c-zlh" secondAttribute="centerX" id="WgJ-Jn-EWn"/>
@ -309,7 +332,6 @@
<constraint firstItem="uPE-9c-zlh" firstAttribute="trailing" secondItem="7kA-7f-dpe" secondAttribute="trailing" id="np6-8l-i7Y"/>
<constraint firstItem="7kA-7f-dpe" firstAttribute="top" secondItem="uPE-9c-zlh" secondAttribute="top" id="vWa-3a-llZ"/>
</constraints>
<viewLayoutGuide key="safeArea" id="uPE-9c-zlh"/>
</view>
<navigationItem key="navigationItem" id="Dpm-6S-CDh"/>
<connections>
@ -327,7 +349,7 @@
<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="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="TransactionCell" textLabel="ZgQ-vD-hBa" detailTextLabel="hDe-Pg-Ygb" style="IBUITableViewCellStyleSubtitle" id="GMs-Mo-bHf">
<rect key="frame" x="0.0" y="28" width="414" height="55.5"/>
@ -337,7 +359,7 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ZgQ-vD-hBa">
<rect key="frame" x="20" y="10" width="33.5" height="20.5"/>
<rect key="frame" x="20" y="10" width="33" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
@ -375,7 +397,7 @@
<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="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<sections>
<tableViewSection id="Imd-sb-3mE">
<cells>
@ -387,7 +409,7 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="id:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="MpE-wV-n7T">
<rect key="frame" x="20" y="12" width="19" height="20.5"/>
<rect key="frame" x="20" y="12" width="18.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
@ -411,14 +433,14 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Mined Height" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Lz1-6q-dQ6">
<rect key="frame" x="20" y="12" width="102.5" height="20.5"/>
<rect key="frame" x="20" y="12" width="102" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Height" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="7dw-1w-ESS">
<rect key="frame" x="343" y="12" width="51" height="20.5"/>
<rect key="frame" x="343.5" y="12" width="50.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
@ -442,7 +464,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="date" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3it-Im-6mo">
<rect key="frame" x="359.5" y="12" width="34.5" height="20.5"/>
<rect key="frame" x="360" y="12" width="34" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
@ -459,14 +481,14 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="expiry height" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="dPr-pp-SCj">
<rect key="frame" x="20" y="12" width="99" height="20.5"/>
<rect key="frame" x="20" y="12" width="98.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="expires at" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="K4H-bj-ib8">
<rect key="frame" x="319.5" y="12" width="74.5" height="20.5"/>
<rect key="frame" x="320" y="12" width="74" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
@ -483,7 +505,7 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="zatoshi" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Wfw-cd-nlP">
<rect key="frame" x="20" y="12" width="55.5" height="20.5"/>
<rect key="frame" x="20" y="12" width="55" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
@ -557,7 +579,7 @@
<rect key="frame" x="20" y="143" width="374" height="32"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="Balance:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AFU-aL-aZ0">
<rect key="frame" x="0.0" y="0.0" width="254" height="32"/>
<rect key="frame" x="0.0" y="0.0" width="255.5" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="32" id="c5n-hg-sIV"/>
</constraints>
@ -566,7 +588,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Zec 0.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8RU-tm-VOE">
<rect key="frame" x="286" y="0.0" width="88" height="32"/>
<rect key="frame" x="287.5" y="0.0" width="86.5" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="32" id="KuU-va-m1a"/>
</constraints>
@ -585,20 +607,20 @@
<constraint firstAttribute="height" constant="28" id="LJY-DX-BrR"/>
</constraints>
<fontDescription key="fontDescription" type="italicSystem" pointSize="23"/>
<color key="textColor" cocoaTouchSystemColor="scrollViewTexturedBackgroundColor"/>
<color key="textColor" systemColor="scrollViewTexturedBackgroundColor"/>
<nil key="highlightedColor"/>
</label>
<stackView opaque="NO" contentMode="scaleToFill" spacing="19" translatesAutoresizingMaskIntoConstraints="NO" id="q7P-t6-O88">
<rect key="frame" x="19" y="252" width="375" height="34"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Amount:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0JH-Y9-k10">
<rect key="frame" x="0.0" y="0.0" width="95.5" height="34"/>
<rect key="frame" x="0.0" y="0.0" width="95" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="26"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="0.0" textAlignment="natural" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="QPl-ho-Vb5">
<rect key="frame" x="114.5" y="0.0" width="260.5" height="34"/>
<rect key="frame" x="114" y="0.0" width="261" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="decimalPad" returnKeyType="next" enablesReturnKeyAutomatically="YES" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no"/>
<connections>
@ -614,13 +636,13 @@
<rect key="frame" x="20" y="353" width="374" height="34"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="to:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uPw-SX-zmP">
<rect key="frame" x="0.0" y="0.0" width="29" height="34"/>
<rect key="frame" x="0.0" y="0.0" width="28" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="26"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="zAddress" textAlignment="natural" minimumFontSize="17" clearButtonMode="always" translatesAutoresizingMaskIntoConstraints="NO" id="pMR-dt-N4H">
<rect key="frame" x="50" y="0.0" width="324" height="34"/>
<rect key="frame" x="49" y="0.0" width="325" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="alphabet" returnKeyType="done" enablesReturnKeyAutomatically="YES" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no"/>
<connections>
@ -630,16 +652,16 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="06w-ih-1vw">
<rect key="frame" x="249" y="311" width="145" height="31"/>
<rect key="frame" x="249.5" y="311" width="144.5" height="31"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Max funds" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qUm-uc-jqe">
<rect key="frame" x="0.0" y="0.0" width="80" height="31"/>
<rect key="frame" x="0.0" y="0.0" width="79.5" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="xns-dw-RD9">
<rect key="frame" x="96" y="0.0" width="51" height="31"/>
<rect key="frame" x="95.5" y="0.0" width="51" height="31"/>
<connections>
<action selector="maxFundsValueChanged:" destination="6mH-Rv-HBn" eventType="valueChanged" id="Fbp-vd-U5h"/>
</connections>
@ -650,15 +672,15 @@
<rect key="frame" x="21" y="183" width="373" height="32"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="verified" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rBT-Aw-Qe1">
<rect key="frame" x="0.0" y="0.0" width="61" height="32"/>
<rect key="frame" x="0.0" y="0.0" width="60.5" height="32"/>
<fontDescription key="fontDescription" type="italicSystem" pointSize="18"/>
<color key="textColor" cocoaTouchSystemColor="scrollViewTexturedBackgroundColor"/>
<color key="textColor" systemColor="scrollViewTexturedBackgroundColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="Zec 0.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="io7-QX-p59">
<rect key="frame" x="93" y="0.0" width="280" height="32"/>
<rect key="frame" x="92.5" y="0.0" width="280.5" height="32"/>
<fontDescription key="fontDescription" type="italicSystem" pointSize="18"/>
<color key="textColor" cocoaTouchSystemColor="scrollViewTexturedBackgroundColor"/>
<color key="textColor" systemColor="scrollViewTexturedBackgroundColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
@ -672,7 +694,7 @@
<constraint firstAttribute="height" constant="141" id="J6k-T9-MPS"/>
</constraints>
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string>
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
<color key="textColor" systemColor="labelColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" enablesReturnKeyAutomatically="YES" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no"/>
<connections>
@ -685,10 +707,10 @@
<constraint firstAttribute="height" constant="21" id="0Yw-Vi-MSw"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="systemGray2Color" red="0.68235294120000001" green="0.68235294120000001" blue="0.69803921570000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" systemColor="systemGray2Color"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Kti-hs-oWv">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Kti-hs-oWv">
<rect key="frame" x="172" y="620" width="70" height="49"/>
<constraints>
<constraint firstAttribute="height" constant="49" id="7G1-ut-7KK"/>
@ -700,7 +722,8 @@
</connections>
</button>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="jlC-eJ-vHy"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="mCk-Iy-qub" firstAttribute="leading" secondItem="jlC-eJ-vHy" secondAttribute="leading" constant="20" id="58F-y0-DKl"/>
<constraint firstItem="06w-ih-1vw" firstAttribute="leading" secondItem="jlC-eJ-vHy" secondAttribute="leading" priority="250" constant="249" id="5hm-3u-gqf"/>
@ -729,7 +752,6 @@
<constraint firstItem="BEY-nq-CrN" firstAttribute="top" secondItem="mCk-Iy-qub" secondAttribute="bottom" constant="26" id="ob2-RM-fjc"/>
<constraint firstItem="8Pq-pM-WWH" firstAttribute="top" secondItem="TpC-jA-EZ0" secondAttribute="bottom" constant="8" id="r1c-df-j7W"/>
</constraints>
<viewLayoutGuide key="safeArea" id="jlC-eJ-vHy"/>
</view>
<navigationItem key="navigationItem" title="Send Funds" id="BMZ-tq-ThY"/>
<connections>
@ -762,7 +784,7 @@
<rect key="frame" x="20" y="88" width="374" height="774"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="characterWrap" numberOfLines="0" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="X0E-Ba-xxX">
<rect key="frame" x="166" y="0.0" width="42" height="387"/>
<rect key="frame" x="166.5" y="0.0" width="41.5" height="387"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@ -780,14 +802,14 @@
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="jVy-LO-XKe"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstAttribute="trailingMargin" secondItem="TGE-Nt-Y1I" secondAttribute="trailing" id="MdM-tW-ZXD"/>
<constraint firstItem="jVy-LO-XKe" firstAttribute="bottom" secondItem="TGE-Nt-Y1I" secondAttribute="bottom" id="aeY-PS-JRQ"/>
<constraint firstItem="TGE-Nt-Y1I" firstAttribute="top" secondItem="5WU-Pu-1e9" secondAttribute="topMargin" id="dp4-xM-chZ"/>
<constraint firstItem="TGE-Nt-Y1I" firstAttribute="leading" secondItem="5WU-Pu-1e9" secondAttribute="leadingMargin" id="p2L-c4-Chf"/>
</constraints>
<viewLayoutGuide key="safeArea" id="jVy-LO-XKe"/>
</view>
<navigationItem key="navigationItem" title="Get Address" largeTitleDisplayMode="always" id="Uvy-EM-bSo"/>
<connections>
@ -799,6 +821,81 @@
</objects>
<point key="canvasLocation" x="2317" y="-1829"/>
</scene>
<!--Sapling Parameters View Controller-->
<scene sceneID="A7L-pt-VGf">
<objects>
<viewController id="Nsd-EZ-dgy" customClass="SaplingParametersViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="cqf-Dv-Buo">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalCentering" translatesAutoresizingMaskIntoConstraints="NO" id="mvO-Hh-h9H">
<rect key="frame" x="0.0" y="88" width="414" height="774"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Spend Parameter path:" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="d2f-dy-eY1">
<rect key="frame" x="0.0" y="0.0" width="414" height="23"/>
<fontDescription key="fontDescription" type="system" pointSize="19"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="path" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="leL-2C-dpj">
<rect key="frame" x="0.0" y="151" width="414" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Output Parameter path:" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ou4-Wo-H6t">
<rect key="frame" x="0.0" y="299" width="414" height="23"/>
<fontDescription key="fontDescription" type="system" pointSize="19"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="path" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0HT-EL-sbN">
<rect key="frame" x="0.0" y="450" width="414" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="zSx-wA-Nuf">
<rect key="frame" x="0.0" y="594.5" width="414" height="30"/>
<state key="normal" title="Download"/>
<connections>
<action selector="download:" destination="Nsd-EZ-dgy" eventType="touchUpInside" id="KRw-e7-LtM"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5ix-Pz-cnR">
<rect key="frame" x="0.0" y="744" width="414" height="30"/>
<state key="normal" title="Delete">
<color key="titleColor" systemColor="systemRedColor"/>
</state>
<connections>
<action selector="deleteFiles:" destination="Nsd-EZ-dgy" eventType="touchUpInside" id="QEQ-ou-Z3F"/>
</connections>
</button>
</subviews>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="37y-Pc-bYm"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="37y-Pc-bYm" firstAttribute="bottom" secondItem="mvO-Hh-h9H" secondAttribute="bottom" id="T9S-l6-MNr"/>
<constraint firstItem="mvO-Hh-h9H" firstAttribute="leading" secondItem="37y-Pc-bYm" secondAttribute="leading" id="aF3-Vr-xzJ"/>
<constraint firstItem="37y-Pc-bYm" firstAttribute="trailing" secondItem="mvO-Hh-h9H" secondAttribute="trailing" id="k8T-hf-CJM"/>
<constraint firstItem="mvO-Hh-h9H" firstAttribute="top" secondItem="37y-Pc-bYm" secondAttribute="top" id="yZ2-S9-t24"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="zEi-Kz-Ejw"/>
<connections>
<outlet property="deleteButton" destination="5ix-Pz-cnR" id="on6-U7-Tb1"/>
<outlet property="downloadButton" destination="zSx-wA-Nuf" id="wbq-LF-0W8"/>
<outlet property="outputPath" destination="0HT-EL-sbN" id="Pb1-d0-pkT"/>
<outlet property="spendPath" destination="leL-2C-dpj" id="rch-Co-nFz"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="vQP-aT-BeF" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="889" y="983"/>
</scene>
<!--Sync Blocks-->
<scene sceneID="mqi-cb-0xH">
<objects>
@ -812,7 +909,7 @@
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TdB-z4-xLJ">
<rect key="frame" x="0.0" y="0.0" width="398" height="128"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstAttribute="height" constant="128" id="enj-s3-Sct"/>
</constraints>
@ -821,15 +918,15 @@
<rect key="frame" x="0.0" y="152" width="398" height="112.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" text="Status:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3t3-Tr-UlI">
<rect key="frame" x="0.0" y="37" width="97" height="38.5"/>
<rect key="frame" x="0.0" y="37" width="95.5" height="38.5"/>
<fontDescription key="fontDescription" type="system" pointSize="32"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Jj9-7r-s2Y">
<rect key="frame" x="110" y="42.5" width="288" height="27.5"/>
<rect key="frame" x="108.5" y="42.5" width="289.5" height="27.5"/>
<fontDescription key="fontDescription" type="italicSystem" pointSize="23"/>
<color key="textColor" cocoaTouchSystemColor="scrollViewTexturedBackgroundColor"/>
<color key="textColor" systemColor="scrollViewTexturedBackgroundColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
@ -852,7 +949,7 @@
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="G5M-gm-1ux">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="G5M-gm-1ux">
<rect key="frame" x="0.0" y="444.5" width="398" height="45"/>
<fontDescription key="fontDescription" type="system" pointSize="27"/>
<state key="normal" title="Start"/>
@ -862,7 +959,7 @@
</button>
<view contentMode="scaleToFill" verticalHuggingPriority="750" verticalCompressionResistancePriority="737" translatesAutoresizingMaskIntoConstraints="NO" id="fB9-xh-4fl" userLabel="Trailing View">
<rect key="frame" x="0.0" y="513.5" width="398" height="260.5"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" priority="750" constant="200" id="Bpt-XM-IZA"/>
</constraints>
@ -883,14 +980,14 @@
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="K0A-3E-XB9"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="K0A-3E-XB9" firstAttribute="bottom" secondItem="dp8-P5-xEk" secondAttribute="bottom" id="Eqf-9d-bM8"/>
<constraint firstItem="dp8-P5-xEk" firstAttribute="leading" secondItem="K0A-3E-XB9" secondAttribute="leading" constant="8" id="Mkg-Rm-u7R"/>
<constraint firstItem="K0A-3E-XB9" firstAttribute="trailing" secondItem="dp8-P5-xEk" secondAttribute="trailing" constant="8" id="VM8-iL-n3m"/>
<constraint firstItem="dp8-P5-xEk" firstAttribute="top" secondItem="K0A-3E-XB9" secondAttribute="top" id="iOn-jh-hj0"/>
</constraints>
<viewLayoutGuide key="safeArea" id="K0A-3E-XB9"/>
</view>
<navigationItem key="navigationItem" title="Sync Blocks" id="Acf-Sj-aA6"/>
<connections>
@ -928,7 +1025,8 @@
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="fg7-R9-l4X"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="0Hj-Jx-J3i" firstAttribute="centerY" secondItem="tie-BL-1Ip" secondAttribute="centerY" id="6HS-U8-JHx"/>
<constraint firstItem="WKf-j9-SjN" firstAttribute="centerX" secondItem="0Hj-Jx-J3i" secondAttribute="centerX" id="UOE-Jf-r6N"/>
@ -937,7 +1035,6 @@
<constraint firstItem="0Hj-Jx-J3i" firstAttribute="leading" secondItem="fg7-R9-l4X" secondAttribute="leading" id="kAe-yR-Nr8"/>
<constraint firstItem="0Hj-Jx-J3i" firstAttribute="centerX" secondItem="tie-BL-1Ip" secondAttribute="centerX" id="ruf-Mw-pLN"/>
</constraints>
<viewLayoutGuide key="safeArea" id="fg7-R9-l4X"/>
</view>
<connections>
<outlet property="blockHeightLabel" destination="0Hj-Jx-J3i" id="P2U-am-RMw"/>
@ -1011,14 +1108,14 @@
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="5sH-IW-gs5"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="5sH-IW-gs5" firstAttribute="bottom" secondItem="Cep-qY-yP1" secondAttribute="bottom" id="1wA-22-MWW"/>
<constraint firstItem="5sH-IW-gs5" firstAttribute="trailing" secondItem="Cep-qY-yP1" secondAttribute="trailing" id="5rX-6x-hht"/>
<constraint firstItem="Cep-qY-yP1" firstAttribute="leading" secondItem="5sH-IW-gs5" secondAttribute="leading" id="6mk-cr-RT1"/>
<constraint firstItem="Cep-qY-yP1" firstAttribute="top" secondItem="5sH-IW-gs5" secondAttribute="top" id="MhI-qH-RAz"/>
</constraints>
<viewLayoutGuide key="safeArea" id="5sH-IW-gs5"/>
</view>
<navigationItem key="navigationItem" id="HmT-B1-R92"/>
<connections>
@ -1035,4 +1132,30 @@
<segue reference="oxP-eV-1Z2"/>
<segue reference="snP-Bc-obL"/>
</inferredMetricsTieBreakers>
<resources>
<systemColor name="labelColor">
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="scrollViewTexturedBackgroundColor">
<color red="0.43529411764705878" green="0.44313725490196082" blue="0.47450980392156861" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="scrollViewTexturedBackgroundColor">
<color red="0.43529411764705878" green="0.44313725490196082" blue="0.47450980392156861" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="scrollViewTexturedBackgroundColor">
<color red="0.43529411764705878" green="0.44313725490196082" blue="0.47450980392156861" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="scrollViewTexturedBackgroundColor">
<color red="0.43529411764705878" green="0.44313725490196082" blue="0.47450980392156861" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemGray2Color">
<color red="0.68235294117647061" green="0.68235294117647061" blue="0.69803921568627447" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemRedColor">
<color red="1" green="0.23137254901960785" blue="0.18823529411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>

View File

@ -10,7 +10,7 @@ import Foundation
import ZcashLightClientKit
import MnemonicSwift
struct DemoAppConfig {
static var host = ZcashSDK.isMainnet ? "lightwalletd.electriccoin.co" : "localhost"
static var host = ZcashSDK.isMainnet ? "localhost" : "localhost"
static var port: Int = 9067
static var birthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 663174 : 620_000
static var network = ZcashSDK.isMainnet ? ZcashNetwork.mainNet : ZcashNetwork.testNet

View File

@ -0,0 +1,98 @@
//
// SaplingParametersViewController.swift
// ZcashLightClientSample
//
// Created by Francisco Gindre on 10/7/20.
// Copyright © 2020 Electric Coin Company. All rights reserved.
//
import UIKit
import ZcashLightClientKit
class SaplingParametersViewController: UIViewController {
@IBOutlet weak var outputPath: UILabel!
@IBOutlet weak var spendPath: UILabel!
@IBOutlet weak var downloadButton: UIButton!
@IBOutlet weak var deleteButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
let spendParamPath = try! __spendParamsURL().path
let outputParamPath = try! __outputParamsURL().path
// Do any additional setup after loading the view.
self.spendPath.text = spendParamPath
self.outputPath.text = outputParamPath
self.updateColor()
self.spendPath.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(spendPathTapped(_:))))
self.outputPath.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(outputPathTapped(_:))))
self.outputPath.isUserInteractionEnabled = true
self.spendPath.isUserInteractionEnabled = true
self.updateButtons()
}
func updateButtons() {
let spendParamPath = try! __spendParamsURL().path
let outputParamPath = try! __outputParamsURL().path
self.downloadButton.isHidden = fileExists(outputParamPath) && fileExists(spendParamPath)
self.deleteButton.isHidden = !(fileExists(outputParamPath) || fileExists(spendParamPath))
}
func updateColor() {
let spendParamPath = try! __spendParamsURL().path
let outputParamPath = try! __outputParamsURL().path
self.spendPath.textColor = fileExists(spendParamPath) ? UIColor.green : UIColor.red
self.outputPath.textColor = fileExists(outputParamPath) ? UIColor.green : UIColor.red
}
@objc func spendPathTapped(_ gesture: UIGestureRecognizer) {
loggerProxy.event("copied to clipboard:\(self.spendPath.text ?? "")")
UIPasteboard.general.string = self.spendPath.text
let alert = UIAlertController(title: "", message: "Path 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)
}
@objc func outputPathTapped(_ gesture: UIGestureRecognizer) {
loggerProxy.event("copied to clipboard:\(self.outputPath.text ?? "")")
UIPasteboard.general.string = self.outputPath.text
let alert = UIAlertController(title: "", message: "Path 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 download(_ sender: Any) {
let outputParameter = try! __outputParamsURL()
let spendParameter = try! __spendParamsURL()
SaplingParameterDownloader.downloadParamsIfnotPresent(spendURL: spendParameter, outputURL: outputParameter) { (result) in
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
switch result {
case .success(let urls):
self.spendPath.text = urls.spend.path
self.outputPath.text = urls.output.path
self.updateColor()
self.updateButtons()
case .failure(let error):
self.showError(error)
}
}
}
}
func fileExists(_ path: String) -> Bool {
(try? FileManager.default.attributesOfItem(atPath: path)) != nil
}
func showError(_ error: Error) {
let alert = UIAlertController(title: "Download Failed", message: "\(error)", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
@IBAction func deleteFiles(_ sender: Any) {
let spendParamURL = try! __spendParamsURL()
let outputParamURL = try! __outputParamsURL()
try? FileManager.default.removeItem(at: spendParamURL)
try? FileManager.default.removeItem(at: outputParamURL)
self.updateColor()
self.updateButtons()
}
}

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'ZcashLightClientKit'
s.version = '0.6.6'
s.version = '0.7.0'
s.summary = 'Zcash Light Client wallet SDK for iOS'
s.description = <<-DESC

View File

@ -121,4 +121,18 @@ public protocol ConfirmedTransactionEntity: MinedTransactionEntity, SignedTransa
expiration height for this transaction
*/
var expiryHeight: BlockHeight? { get set }
}
public extension ConfirmedTransactionEntity {
var isOutbound: Bool {
self.toAddress != nil
}
var isInbound: Bool {
self.toAddress == nil
}
var blockTimeInMilliseconds: Double {
self.blockTimeInSeconds * 1000
}
}

View File

@ -17,6 +17,7 @@ public enum InitializerError: Error {
case dataDbInitFailed
case accountInitFailed
case falseStart
case invalidViewingKey(key: String)
}
/**
@ -50,13 +51,15 @@ public struct LightWalletEndpoint {
public class Initializer {
private(set) var rustBackend: ZcashRustBackendWelding.Type
private(set) var alias: String
private(set) var endpoint: LightWalletEndpoint
private var lowerBoundHeight: BlockHeight
private(set) var cacheDbURL: URL
private(set) var dataDbURL: URL
private(set) var pendingDbURL: URL
private(set) var spendParamsURL: URL
private(set) var outputParamsURL: URL
private var walletBirthday: WalletBirthday?
private(set) var lightWalletService: LightWalletService
private(set) var transactionRepository: TransactionRepository
private(set) var downloader: CompactBlockDownloader
@ -78,6 +81,7 @@ public class Initializer {
endpoint: LightWalletEndpoint,
spendParamsURL: URL,
outputParamsURL: URL,
alias: String = "",
loggerProxy: Logger? = nil) {
let storage = CompactBlockStorage(url: cacheDbURL, readonly: false)
@ -90,11 +94,13 @@ public class Initializer {
cacheDbURL: cacheDbURL,
dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
endpoint: endpoint,
service: lwdService,
repository: TransactionRepositoryBuilder.build(dataDbURL: dataDbURL),
downloader: CompactBlockDownloader(service: lwdService, storage: storage),
spendParamsURL: spendParamsURL,
outputParamsURL: outputParamsURL,
alias: alias,
loggerProxy: loggerProxy
)
}
@ -107,12 +113,15 @@ public class Initializer {
cacheDbURL: URL,
dataDbURL: URL,
pendingDbURL: URL,
endpoint: LightWalletEndpoint,
service: LightWalletService,
repository: TransactionRepository,
downloader: CompactBlockDownloader,
spendParamsURL: URL,
outputParamsURL: URL,
alias: String = "",
loggerProxy: Logger? = nil
) {
logger = loggerProxy
self.rustBackend = rustBackend
@ -120,8 +129,10 @@ public class Initializer {
self.cacheDbURL = cacheDbURL
self.dataDbURL = dataDbURL
self.pendingDbURL = pendingDbURL
self.endpoint = endpoint
self.spendParamsURL = spendParamsURL
self.outputParamsURL = outputParamsURL
self.alias = alias
self.lightWalletService = service
self.transactionRepository = repository
self.downloader = downloader
@ -139,12 +150,18 @@ public class Initializer {
do not already exist). These files can be given a prefix for scenarios where multiple wallets
operate in one app--for instance, when sweeping funds from another wallet seed.
- Parameters:
- seedProvider: the seed to use for initializing this wallet.
- walletBirthdayHeight: the height corresponding to when the wallet seed was created. If null, this signals that the wallet is being born.
- numberOfAccounts: the number of accounts to create from this seed.
- viewingKeys: Extended Full Viewing Keys to initialize the DBs with
*/
public func initialize(seedProvider: SeedProvider, walletBirthdayHeight: BlockHeight, numberOfAccounts: Int = 1) throws -> [String]? {
public func initialize(viewingKeys: [String], walletBirthday: BlockHeight) throws {
let derivationTool = DerivationTool()
for vk in viewingKeys {
do {
try derivationTool.validateViewingKey(viewingKey: vk)
} catch {
throw InitializerError.invalidViewingKey(key: vk)
}
}
do {
try rustBackend.initDataDb(dbData: dataDbURL)
@ -154,10 +171,7 @@ public class Initializer {
throw InitializerError.dataDbInitFailed
}
self.walletBirthday = WalletBirthday.birthday(with: walletBirthdayHeight)
guard let birthday = self.walletBirthday else {
throw InitializerError.falseStart
}
let birthday = WalletBirthday.birthday(with: walletBirthday)
do {
try rustBackend.initBlocksTable(dbData: dataDbURL, height: Int32(birthday.height), hash: birthday.hash, time: birthday.time, saplingTree: birthday.tree)
@ -167,17 +181,24 @@ public class Initializer {
throw InitializerError.dataDbInitFailed
}
let lastDownloaded = (try? downloader.storage.latestHeight()) ?? self.walletBirthday?.height ?? ZcashSDK.SAPLING_ACTIVATION_HEIGHT
let lastDownloaded = (try? downloader.storage.latestHeight()) ?? birthday.height
// resume from last downloaded block
lowerBoundHeight = max(birthday.height, lastDownloaded)
self.processor = CompactBlockProcessorBuilder.buildProcessor(configuration: CompactBlockProcessor.Configuration(cacheDb: cacheDbURL, dataDb: dataDbURL, walletBirthday: walletBirthday?.height ?? self.lowerBoundHeight), downloader: self.downloader, transactionRepository: transactionRepository, backend: rustBackend)
let config = CompactBlockProcessor.Configuration(cacheDb: cacheDbURL,
dataDb: dataDbURL,
walletBirthday: birthday.height)
guard let accounts = rustBackend.initAccountsTable(dbData: dataDbURL, seed: seedProvider.seed(), accounts: Int32(numberOfAccounts)) else {
self.processor = CompactBlockProcessorBuilder.buildProcessor(configuration: config,
downloader: self.downloader,
transactionRepository: transactionRepository,
backend: rustBackend)
guard try rustBackend.initAccountsTable(dbData: dataDbURL, exfvks: viewingKeys) else {
throw rustBackend.lastError() ?? InitializerError.accountInitFailed
}
return accounts
}
/**
@ -225,11 +246,69 @@ public class Initializer {
public func blockProcessor() -> CompactBlockProcessor? {
self.processor
}
func isSpendParameterPresent() -> Bool {
FileManager.default.isReadableFile(atPath: self.spendParamsURL.path)
}
func isOutputParameterPresent() -> Bool {
FileManager.default.isReadableFile(atPath: self.outputParamsURL.path)
}
func downloadParametersIfNeeded(result: @escaping (Result<Bool,Error>) -> Void) {
let spendParameterPresent = isSpendParameterPresent()
let outputParameterPresent = isOutputParameterPresent()
if spendParameterPresent && outputParameterPresent {
result(.success(true))
return
}
let outputURL = self.outputParamsURL
let spendURL = self.spendParamsURL
if !outputParameterPresent {
SaplingParameterDownloader.downloadOutputParameter(outputURL) { outputResult in
switch outputResult {
case .failure(let e):
result(.failure(e))
case .success:
guard !spendParameterPresent else {
result(.success(false))
return
}
SaplingParameterDownloader.downloadSpendParameter(spendURL) { (spendResult) in
switch spendResult {
case .failure(let e):
result(.failure(e))
case .success:
result(.success(false))
}
}
}
}
} else if !spendParameterPresent {
SaplingParameterDownloader.downloadSpendParameter(spendURL) { (spendResult) in
switch spendResult {
case .failure(let e):
result(.failure(e))
case .success:
result(.success(false))
}
}
}
}
}
class CompactBlockProcessorBuilder {
static func buildProcessor(configuration: CompactBlockProcessor.Configuration, downloader: CompactBlockDownloader, transactionRepository: TransactionRepository, backend: ZcashRustBackendWelding.Type) -> CompactBlockProcessor {
return CompactBlockProcessor(downloader: downloader, backend: backend, config: configuration, repository: transactionRepository)
static func buildProcessor(configuration: CompactBlockProcessor.Configuration,
downloader: CompactBlockDownloader,
transactionRepository: TransactionRepository,
backend: ZcashRustBackendWelding.Type) -> CompactBlockProcessor {
return CompactBlockProcessor(downloader: downloader,
backend: backend,
config: configuration,
repository: transactionRepository)
}
}

View File

@ -1,15 +0,0 @@
//
// SeedProvider.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 13/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
/**
Describes an interface for an entity that provides seed bytes
*/
public protocol SeedProvider {
func seed() -> [UInt8]
}

View File

@ -90,6 +90,28 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return extsks
}
static func initAccountsTable(dbData: URL, exfvks: [String]) throws -> Bool {
let dbData = dbData.osStr()
let viewingKeys = exfvks.map { UnsafePointer(strdup($0)) }
guard exfvks.count > 0 else {
throw RustWeldingError.malformedStringInput
}
let res = zcashlc_init_accounts_table_with_keys(dbData.0, dbData.1, viewingKeys, UInt(viewingKeys.count));
viewingKeys.compactMap({UnsafeMutablePointer(mutating: $0)}).forEach({ free($0) })
guard res else {
if let error = lastError() {
throw error
}
return false
}
return res
}
static func initBlocksTable(dbData: URL, height: Int32, hash: String, time: UInt32, saplingTree: String) throws {
let dbData = dbData.osStr()
@ -208,9 +230,9 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return derived
}
static func deriveExtendedFullViewingKeys(seed: String, accounts: Int32) throws -> [String]? {
static func deriveExtendedFullViewingKeys(seed: [UInt8], accounts: Int32) throws -> [String]? {
var capacity = UInt(0);
guard let extsksCStr = zcashlc_derive_extended_full_viewing_keys(seed, UInt(seed.lengthOfBytes(using: .utf8)), accounts, &capacity) else {
guard let extsksCStr = zcashlc_derive_extended_full_viewing_keys(seed, UInt(seed.count), accounts, &capacity) else {
if let error = lastError() {
throw error
}
@ -225,9 +247,9 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return extsks
}
static func deriveExtendedSpendingKeys(seed: String, accounts: Int32) throws -> [String]? {
static func deriveExtendedSpendingKeys(seed: [UInt8], accounts: Int32) throws -> [String]? {
var capacity = UInt(0);
guard let extsksCStr = zcashlc_derive_extended_spending_keys(seed, UInt(seed.lengthOfBytes(using: .utf8)), accounts, &capacity) else {
guard let extsksCStr = zcashlc_derive_extended_spending_keys(seed, UInt(seed.count), accounts, &capacity) else {
if let error = lastError() {
throw error
}
@ -242,6 +264,52 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return extsks
}
static func deriveShieldedAddressFromSeed(seed: [UInt8], accountIndex: Int32) throws -> String? {
guard let zaddrCStr = zcashlc_derive_shielded_address_from_seed(seed, UInt(seed.count), accountIndex) else {
if let error = lastError() {
throw error
}
return nil
}
let zAddr = String(validatingUTF8: zaddrCStr)
zcashlc_string_free(zaddrCStr)
return zAddr
}
static func deriveShieldedAddressFromViewingKey(_ extfvk: String) throws -> String? {
guard !extfvk.containsCStringNullBytesBeforeStringEnding() else {
throw RustWeldingError.malformedStringInput
}
guard let zaddrCStr = zcashlc_derive_shielded_address_from_viewing_key([CChar](extfvk.utf8CString)) else {
if let error = lastError() {
throw error
}
return nil
}
let zAddr = String(validatingUTF8: zaddrCStr)
zcashlc_string_free(zaddrCStr)
return zAddr
}
static func deriveTransparentAddressFromSeed(seed: [UInt8]) throws -> String? {
guard let tAddrCStr = zcashlc_derive_transparent_address_from_seed(seed, UInt(seed.count)) else {
if let error = lastError() {
throw error
}
return nil
}
let tAddr = String(validatingUTF8: tAddrCStr)
return tAddr
}
static func consensusBranchIdFor(height: Int32) throws -> Int32 {
let branchId = zcashlc_branch_id_for_height(height)

View File

@ -49,7 +49,7 @@ public protocol ZcashRustBackendWelding {
static func isValidTransparentAddress(_ address: String) throws -> Bool
/**
initialize the blocks table from a given checkpoint (birthday)
initialize the accounts table from a given seed and a number of accounts
- Parameters:
- dbData: location of the data db
- seed: byte array of the zip32 seed
@ -57,6 +57,16 @@ public protocol ZcashRustBackendWelding {
*/
static func initAccountsTable(dbData: URL, seed: [UInt8], accounts: Int32) -> [String]?
/**
initialize the accounts table from a given seed and a number of accounts
- Parameters:
- dbData: location of the data db
- exfvks: byte array of the zip32 seed
- Returns: a boolean indicating if the database was initialized or an error
*/
static func initAccountsTable(dbData: URL, exfvks: [String]) throws -> Bool
/**
initialize the blocks table from a given checkpoint (birthday)
- Parameters:
@ -190,7 +200,7 @@ public protocol ZcashRustBackendWelding {
- Returns: an array containing the derived keys
- Throws: RustBackendError if fatal error occurs
*/
static func deriveExtendedFullViewingKeys(seed: String, accounts: Int32) throws -> [String]?
static func deriveExtendedFullViewingKeys(seed: [UInt8], accounts: Int32) throws -> [String]?
/**
Derives a set of full viewing keys from a seed
@ -199,7 +209,32 @@ public protocol ZcashRustBackendWelding {
- Returns: an array containing the spending keys
- Throws: RustBackendError if fatal error occurs
*/
static func deriveExtendedSpendingKeys(seed: String, accounts: Int32) throws -> [String]?
static func deriveExtendedSpendingKeys(seed: [UInt8], accounts: Int32) throws -> [String]?
/**
Derives a shielded address from a seed
- Parameter seed: an array of bytes of the seed
- Parameter accountIndex: the index of the account you want the address for
- Returns: an optional String containing the Shielded address
- Throws: RustBackendError if fatal error occurs
*/
static func deriveShieldedAddressFromSeed(seed: [UInt8], accountIndex: Int32) throws -> String?
/**
Derives a shielded address from an Extended Full Viewing Key
- Parameter extfvk: a string containing the extended full viewing key
- Returns: an optional String containing the Shielded address
- Throws: RustBackendError if fatal error occurs
*/
static func deriveShieldedAddressFromViewingKey(_ extfvk: String) throws -> String?
/**
Derives a shielded address from an Extended Full Viewing Key
- Parameter seed: an array of bytes of the seed
- Returns: an optional String containing the transparent address
- Throws: RustBackendError if fatal error occurs
*/
static func deriveTransparentAddressFromSeed(seed: [UInt8]) throws -> String?
/**
Gets the consensus branch id for the given height

View File

@ -229,13 +229,20 @@ public extension WalletBirthday {
time: 1599946198,
tree: "01d9e6147caab719ae68cb20d976c78437634e2c999ef3a09c6ba35086d443703d00120001019d135be7b1db088c68bd76703ec2b45066bb1761619745362e61dcf55f644601d0c8f296479a73722c2e2a260ab7017b9a9e6d084b651289cfe6d3c7a00ff54e01f853ab39dbfc81e2aabefd231d3374ff794028168c725ad465e61205692fef4b014f13b6e4475cbd004b4d95aa8205ed7338224e13627ecbb19afd1937dcbc0818000185b4f1ddee3199cd1f7913b223c01c4623cd9d0e1b47df4e36aaca7717b6331f011d6c8ec914cc312ef0962d52240308b22a647f4cbd2d7c2fd420ad5fbcae5619011e43cbb05b8efc885531367e5f611fe7ce7514131be892cce3adad02e151f72b01f0d7e0d589c7e5f8fff0bdd5037aeb5d5d818d413262758c9915ded705e40f70000101d26ff60e77e23fb86a52da565c22d76f81df7f25d543ec0e58a0d692d4be2700000110b2bfd32a99e0b982a41a6dbaebf783bdb9d6af795f5f20056ce7317d15ce1101f1c57245fff8dbc2d3efe5a0953eafdedeb06e18a3ad4f1e4042ee76623f803200011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
)
default:
return WalletBirthday(
case 980000 ..< 998500:
return WalletBirthday(
height: 980000,
hash: "00000000005de60d31b653cdf1637f1bad62af844c6c51f38557a4e8bb74e2d7",
time: 1600700163,
tree: "0184330bda72e9596256847a9597d7d9476aa3d69d9dad2149314751e708da206601cd8a1a61df1e80514cd2c0a7faac8b8d7ce27d6d96bb63cb7c61c1f33e7c654312014098788b75f26108d93f0429202ffffb6cf9ffffd7278383e4d8ad2af642264600011c035ed934a11c1b48e24b6be9b2d483e7747dd082f06abf75f9a092b34cd33c01be00394a99bd33304fc4343cd928857ae7c09c176452f40d9815f9ff3ba3865d013bd7218072e1588aea0568198d37e0d83ba75f155c863355974c4eb864de0103019ed27335bc5452e320ec22a30cfe61508929016157ff2a555181a9a0623e725801328208bea2c5c83487effab780cdb36b4b82e6e7290b06d98817a160f3d79d2800019ed6779a1724a107807baf4dda9481fb940f50d85db701dda43a0989c2d62535000001d1e806194dbe171d4ad1ef8c73c1a469130caced0e24b04b8acef91c42be7a56000107771e04f7d6371bfda40ef9e04419a25c6563dcd359c85bd501de28c3c7f3250110b2bfd32a99e0b982a41a6dbaebf783bdb9d6af795f5f20056ce7317d15ce1101f1c57245fff8dbc2d3efe5a0953eafdedeb06e18a3ad4f1e4042ee76623f803200011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
)
default:
return WalletBirthday(
height: 998500,
hash: "00000000014cf0915c4f105140e846c62ca6f2e321f4f717cf6762a35c6e8eb4",
time: 1602093677,
tree: "01b86ad8964b68a9a316a3038af14ac9e557a6eed614ee5d337c9fdb8887e9dc6001b71fbc815cc014a7f68648fcad5b9b72920b3d21cce406b5f593a0eab6d91f1212000001aede58c0641825b7531a8b296b9fdc7393090b87588893e8dd5160bd59b2e16300000000014fa30e43b641cf67c84cef81083b81bec3bd677f55def5c7bd948298e64690560134ac55ded091faef2ae8a1043704dfec41b1a95c4cedf1818574752bc40f2033000103ee02ae59c6688dcaadf1c4ff95e7b1a902837e4989a4c4994dce7dac6ecb20014ff8c0fe6bce02ac4ad684996bfa931d61c724015d797642819361d611ebd61201c7ae83949d9502b0eff10618124d335f046e4aae52c19ccad5567feceb342a5200000001b7fc5791e3650729b7e1e38ee8c4ea9da612a07b4bf412cefaffbab7ac74c547011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
)
}
}
}

View File

@ -21,6 +21,7 @@ public enum SynchronizerError: Error {
case networkTimeout
case uncategorized(underlyingError: Error)
case criticalError
case parameterMissing(underlyingError: Error)
}
/**
@ -104,6 +105,21 @@ public protocol Synchronizer {
*/
func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository
/**
gets the latest downloaded height from the compact block cache
*/
func latestDownloadedHeight() throws -> BlockHeight
/**
Gets the latest block height from the provided Lightwallet endpoint
*/
func latestHeight(result: @escaping (Result<BlockHeight, Error>) -> Void)
/**
Gets the latest block height from the provided Lightwallet endpoint
Blocking
*/
func latestHeight() throws -> BlockHeight
}
/**

View File

@ -0,0 +1,207 @@
//
// DerivationTool.swift
// Pods
//
// Created by Francisco Gindre on 10/8/20.
//
import Foundation
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 deriveViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [String]
/**
Given a spending key, return the associated viewing key.
- Parameter spendingKey: the key from which to derive the viewing key.
- Returns: the viewing key that corresponds to the spending key.
*/
func deriveViewingKey(spendingKey: String) throws -> String
/**
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
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 -> [String]
/**
Given a seed and account index, return the associated 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 deriveShieldedAddress(seed: [UInt8], accountIndex: Int) throws -> String
/**
Given a viewing key string, return the associated address.
- Parameter viewingKey: the viewing key to use for deriving the address. The viewing key is tied to
a specific account so no account index is required.
- Returns: the address that corresponds to the viewing key.
*/
func deriveShieldedAddress(viewingKey: String) throws -> String
// WIP probably shouldn't be used just yet. Why?
// - because we need the private key associated with this seed and this function doesn't return it.
// - the underlying implementation needs to be split out into a few lower-level calls
func deriveTransparentAddress(seed: [UInt8]) throws -> String
func validateViewingKey(viewingKey: String) throws
}
public enum KeyDerivationErrors: Error {
case derivationError(underlyingError: Error)
case unableToDerive
case invalidInput
}
public class DerivationTool: KeyDeriving {
var rustwelding: ZcashRustBackendWelding.Type = ZcashRustBackend.self
public static let `default` = DerivationTool()
/**
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.
*/
public func deriveViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [String] {
guard numberOfAccounts > 0, let numberOfAccounts = Int32(exactly: numberOfAccounts) else {
throw KeyDerivationErrors.invalidInput
}
do {
guard let keys = try rustwelding.deriveExtendedFullViewingKeys(seed: seed, accounts: numberOfAccounts) else {
throw KeyDerivationErrors.unableToDerive
}
return keys
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/**
Given a spending key, return the associated viewing key.
- 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: String) throws -> String {
do {
guard let key = try rustwelding.deriveExtendedFullViewingKey(spendingKey) else {
throw KeyDerivationErrors.unableToDerive
}
return key
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/**
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
supported so the default value of 1 is recommended.
- Returns: the spending keys that correspond to the seed, formatted as Strings.
*/
public func deriveSpendingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [String] {
guard numberOfAccounts > 0, let numberOfAccounts = Int32(exactly: numberOfAccounts) else {
throw KeyDerivationErrors.invalidInput
}
do {
guard let keys = try rustwelding.deriveExtendedSpendingKeys(seed: seed, accounts: numberOfAccounts) else {
throw KeyDerivationErrors.unableToDerive
}
return keys
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/**
Given a seed and account index, return the associated 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 deriveShieldedAddress(seed: [UInt8], accountIndex: Int) throws -> String {
guard accountIndex >= 0, let accountIndex = Int32(exactly: accountIndex) else {
throw KeyDerivationErrors.invalidInput
}
do {
guard let address = try rustwelding.deriveShieldedAddressFromSeed(seed: seed, accountIndex: accountIndex) else {
throw KeyDerivationErrors.unableToDerive
}
return address
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/**
Given a viewing key string, return the associated 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 deriveShieldedAddress(viewingKey: String) throws -> String {
do {
guard let zaddr = try rustwelding.deriveShieldedAddressFromViewingKey(viewingKey) else {
throw KeyDerivationErrors.unableToDerive
}
return zaddr
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
// WIP probably shouldn't be used just yet. Why?
// - because we need the private key associated with this seed and this function doesn't return it.
// - the underlying implementation needs to be split out into a few lower-level calls
public func deriveTransparentAddress(seed: [UInt8]) throws -> String {
do {
guard let zaddr = try rustwelding.deriveTransparentAddressFromSeed(seed: seed) else {
throw KeyDerivationErrors.unableToDerive
}
return zaddr
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
public func validateViewingKey(viewingKey: String) throws {
// TODO
}
}

View File

@ -392,12 +392,6 @@ public class SDKSynchronizer: Synchronizer {
@objc func applicationWillResignActive(_ notification: Notification) {
registerBackgroundActivity()
LoggerProxy.debug("applicationWillResignActive")
// do {
//
// try stop()
// } catch {
// LoggerProxy.debug("stop failed with error: \(error)")
// }
}
@objc func applicationWillTerminate(_ notification: Notification) {
@ -408,6 +402,20 @@ public class SDKSynchronizer: Synchronizer {
public func sendToAddress(spendingKey: String, zatoshi: Int64, toAddress: String, memo: String?, from accountIndex: Int, resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void) {
initializer.downloadParametersIfNeeded { (downloadResult) in
DispatchQueue.main.async { [weak self] in
switch downloadResult {
case .success:
self?.createToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: toAddress, memo: memo, from: accountIndex, resultBlock: resultBlock)
case .failure(let error):
resultBlock(.failure(SynchronizerError.parameterMissing(underlyingError: error)))
}
}
}
}
func createToAddress(spendingKey: String, zatoshi: Int64, toAddress: String, memo: String?, from accountIndex: Int, resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void) {
do {
let spend = try transactionManager.initSpend(zatoshi: Int(zatoshi), toAddress: toAddress, memo: memo, from: accountIndex)
@ -435,7 +443,6 @@ public class SDKSynchronizer: Synchronizer {
resultBlock(.failure(error))
}
}
public func getAddress(accountIndex: Int) -> String {
initializer.getAddress(index: accountIndex) ?? ""
}
@ -464,6 +471,18 @@ public class SDKSynchronizer: Synchronizer {
PagedTransactionRepositoryBuilder.build(initializer: initializer, kind: .all)
}
public func latestDownloadedHeight() throws -> BlockHeight {
try initializer.downloader.lastDownloadedBlockHeight()
}
public func latestHeight(result: @escaping (Result<BlockHeight, Error>) -> Void) {
initializer.downloader.latestBlockHeight(result: result)
}
public func latestHeight() throws -> BlockHeight {
try initializer.downloader.latestBlockHeight()
}
// MARK: notify state
private func notify(progress: Float, height: BlockHeight) {
NotificationCenter.default.post(name: Notification.Name.synchronizerProgressUpdated, object: self, userInfo: [

View File

@ -0,0 +1,119 @@
//
// SaplingParameterDownloader.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/7/20.
//
import Foundation
/**
Helper class to handle the download of Sapling parameters
*/
public class SaplingParameterDownloader {
public enum Errors: Error {
case invalidURL(url: String)
case failed(error: Error)
}
/**
Download a Spend parameter from default host and stores it at given URL
- Parameters:
- at: The destination URL for the download
- result: block to handle the download success or error
*/
public static func downloadSpendParameter(_ at: URL, result: @escaping (Result<URL, Error>) -> Void) {
guard let url = URL(string: spendParamsURLString) else {
result(.failure(Errors.invalidURL(url: spendParamsURLString)))
return
}
downloadFileWithRequest(URLRequest(url: url), at: at, result: result)
}
/**
Download an Output parameter from default host and stores it at given URL
- Parameters:
- at: The destination URL for the download
- result: block to handle the download success or error
*/
public static func downloadOutputParameter(_ at: URL, result: @escaping (Result<URL, Error>) -> Void) {
guard let url = URL(string: outputParamsURLString) else {
result(.failure(Errors.invalidURL(url: outputParamsURLString)))
return
}
downloadFileWithRequest(URLRequest(url: url), at: at, result: result)
}
private static func downloadFileWithRequest(_ request: URLRequest, at destination: URL, result: @escaping (Result<URL,Error>) -> Void) {
let task = URLSession.shared.downloadTask(with: request) { (url, _, error) in
if let e = error {
result(.failure(Errors.failed(error: e)))
return
} else if let localUrl = url {
do {
try FileManager.default.moveItem(at: localUrl, to: destination)
result(.success(destination))
} catch {
result(.failure(error))
}
}
}
task.resume()
}
/**
Downloads the parameters if not present and provides the resulting URLs for both parameters
- Parameters:
- spendURL: URL to check whether the parameter is already downloaded
- outputURL: URL to check whether the parameter is already downloaded
- result: block to handle success or error
*/
public static func downloadParamsIfnotPresent(spendURL: URL, outputURL: URL, result: @escaping (Result<(spend: URL, output: URL),Error>) -> Void) {
ensureSpendParameter(at: spendURL) { (spendResult) in
switch spendResult {
case .success(let spendResultURL):
ensureOutputParameter(at: outputURL) { (outputResult) in
switch outputResult {
case .success(let outputResultURL):
result(.success((spendResultURL,outputResultURL)))
case .failure(let outputResultError):
result(.failure(Errors.failed(error: outputResultError)))
}
}
case .failure(let spendResultError):
result(.failure(Errors.failed(error: spendResultError)))
}
}
}
static func ensureSpendParameter(at url: URL, result: @escaping (Result<URL,Error>) -> Void) {
if isFilePresent(url: url) {
DispatchQueue.global().async {
result(.success(url))
}
} else {
downloadSpendParameter(url, result: result)
}
}
static func ensureOutputParameter(at url: URL, result: @escaping (Result<URL,Error>) -> Void) {
if isFilePresent(url: url) {
DispatchQueue.global().async {
result(.success(url))
}
} else {
downloadOutputParameter(url, result: result)
}
}
static func isFilePresent(url: URL) -> Bool {
(try? FileManager.default.attributesOfItem(atPath: url.path)) != nil
}
static var spendParamsURLString: String {
return ZcashSDK.CLOUD_PARAM_DIR_URL + ZcashSDK.SPEND_PARAM_FILE_NAME
}
static var outputParamsURLString: String {
return ZcashSDK.CLOUD_PARAM_DIR_URL + ZcashSDK.OUTPUT_PARAM_FILE_NAME
}
}

View File

@ -38,18 +38,56 @@ int32_t zcashlc_decrypt_and_store_transaction(const uint8_t *db_data,
const uint8_t *tx,
uintptr_t tx_len);
/**
* derives a shielded address from the given extended full viewing key.
* call zcashlc_string_free with the returned pointer when done using it
*/
char *zcashlc_derive_extended_full_viewing_key(const char *extsk);
/**
* Derives Extended Full Viewing Keys from the given seed into 'accounts' number of accounts.
* Returns the Extended Full Viewing Keys for the accounts. The caller should store these
* securely
*
* Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it.
*/
char **zcashlc_derive_extended_full_viewing_keys(const uint8_t *seed,
uintptr_t seed_len,
int32_t accounts,
uintptr_t *capacity_ret);
/**
* Derives Extended Spending Keys from the given seed into 'accounts' number of accounts.
* Returns the ExtendedSpendingKeys for the accounts. The caller should store these
* securely for use while spending.
*
* Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it.
*/
char **zcashlc_derive_extended_spending_keys(const uint8_t *seed,
uintptr_t seed_len,
int32_t accounts,
uintptr_t *capacity_ret);
/**
* derives a shielded address from the given seed.
* call zcashlc_string_free with the returned pointer when done using it
*/
char *zcashlc_derive_shielded_address_from_seed(const uint8_t *seed,
uintptr_t seed_len,
int32_t account_index);
/**
* derives a shielded address from the given viewing key.
* call zcashlc_string_free with the returned pointer when done using it
*/
char *zcashlc_derive_shielded_address_from_viewing_key(const char *extfvk);
/**
* TEST TEST 123 TEST
* Derives a transparent address from the given seed
*/
char *zcashlc_derive_transparent_address_from_seed(const uint8_t *seed, uintptr_t seed_len);
/**
* Copies the last error message into the provided allocated buffer.
*/
@ -112,6 +150,15 @@ char **zcashlc_init_accounts_table(const uint8_t *db_data,
int32_t accounts,
uintptr_t *capacity_ret);
/**
* Initialises the data database with the given extended full viewing keys
* Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it.
*/
bool zcashlc_init_accounts_table_with_keys(const uint8_t *db_data,
uintptr_t db_data_len,
const char *const *extfvks,
uintptr_t extfvks_len);
/**
* Initialises the data database with the given block.
*

View File

@ -688,7 +688,6 @@ class AdvancedReOrgTests: XCTestCase {
sleep(1)
let initialTotalBalance = coordinator.synchronizer.initializer.getBalance()
let initialVerifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let sendExpectation = XCTestExpectation(description: "send expectation")
var p: PendingTransactionEntity? = nil

View File

@ -0,0 +1,70 @@
//
// DerivationToolTests.swift
// ZcashLightClientKit-Unit-Tests
//
// Created by Francisco Gindre on 10/9/20.
//
import XCTest
import ZcashLightClientKit
class DerivationToolTests: XCTestCase {
var seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" //TODO: Parameterize this from environment?
var seedData: Data = Data(base64Encoded: "9VDVOZZZOWWHpZtq1Ebridp3Qeux5C+HwiRR0g7Oi7HgnMs8Gfln83+/Q1NnvClcaSwM4ADFL1uZHxypEWlWXg==")!
let testRecipientAddress = "zs1vp7kvlqr4n9gpehztr76lcn6skkss9p8keqs3nv8avkdtjrcctrvmk9a7u494kluv756jeee5k0" //TODO: Parameterize this from environment
let expectedSpendingKey = "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv"
let expectedViewingKey = "zxviews1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkysswfhjk79n8l99f2grd26dqg6dy3jcmxsaypxfsu6ara6vsk3x8l544uaksstx9zre879mdg7s9a7zurrx6pf5qg2n323js2s3zlu8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszcq7kwxy"
let expectedTransparentAddress = "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz"
func testDeriveViewingKeysFromSeed() throws {
let accounts: Int = 1
let seedBytes = [UInt8](seedData)
let viewingKeys = try DerivationTool.default.deriveViewingKeys(seed: seedBytes, numberOfAccounts: accounts)
XCTAssertEqual(viewingKeys.count, accounts, "the number of viewing keys have to match the number of account requested to derive")
guard let viewingKey = viewingKeys.first else {
XCTFail("no viewing key generated")
return
}
XCTAssertEqual(expectedViewingKey, viewingKey)
}
func testDeriveViewingKeyFromSpendingKeys() throws {
XCTAssertEqual(expectedViewingKey, try DerivationTool.default.deriveViewingKey(spendingKey: expectedSpendingKey))
}
func testDeriveSpendingKeysFromSeed() throws {
let accounts: Int = 1
let seedBytes = [UInt8](seedData)
let spendingKeys = try DerivationTool.default.deriveSpendingKeys(seed: seedBytes, numberOfAccounts: accounts)
XCTAssertEqual(spendingKeys.count, accounts, "the number of viewing keys have to match the number of account requested to derive")
guard let spendingKey = spendingKeys.first else {
XCTFail("no viewing key generated")
return
}
XCTAssertEqual(expectedSpendingKey, spendingKey)
}
func testDeriveShieldedAddressFromSeed() throws {
let seedBytes = [UInt8](seedData)
let shieldedAddress = try DerivationTool.default.deriveShieldedAddress(seed: seedBytes, accountIndex: 0)
XCTAssertEqual(shieldedAddress, testRecipientAddress)
}
func testDeriveShieldedAddressFromViewingKey() throws {
XCTAssertEqual(try DerivationTool.default.deriveShieldedAddress(viewingKey: expectedViewingKey), testRecipientAddress)
}
func testDeriveTransparentAddressFromSeed() throws {
XCTAssertEqual(try DerivationTool.default.deriveTransparentAddress(seed: [UInt8](seedData)), expectedTransparentAddress)
}
}

View File

@ -29,7 +29,12 @@ class OutboundTransactionManagerTests: XCTestCase {
try! pendingRespository.createrTableIfNeeded()
initializer = Initializer(cacheDbURL: cacheDbHandle.readWriteDb, dataDbURL: dataDbHandle.readWriteDb, pendingDbURL: try! TestDbBuilder.pendingTransactionsDbURL(), endpoint: LightWalletEndpointBuilder.default, spendParamsURL: try! __spendParamsURL(), outputParamsURL: try! __outputParamsURL())
initializer = Initializer(cacheDbURL: cacheDbHandle.readWriteDb,
dataDbURL: dataDbHandle.readWriteDb,
pendingDbURL: try! TestDbBuilder.pendingTransactionsDbURL(),
endpoint: LightWalletEndpointBuilder.default,
spendParamsURL: try! __spendParamsURL(),
outputParamsURL: try! __outputParamsURL())
encoder = WalletTransactionEncoder(initializer: initializer)
transactionManager = PersistentTransactionManager(encoder: encoder, service: MockLightWalletService(latestBlockHeight: 620999), repository: pendingRespository)

View File

@ -78,8 +78,6 @@ class PendingTransactionUpdatesTest: XCTestCase {
wait(for: [firstSyncExpectation], timeout: 5)
sleep(1)
let initialTotalBalance = coordinator.synchronizer.initializer.getBalance()
let initialVerifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let sendExpectation = XCTestExpectation(description: "send expectation")
var p: PendingTransactionEntity? = nil

View File

@ -63,6 +63,7 @@ class TestCoordinator {
cacheDbURL: databases.cacheDB,
dataDbURL: databases.dataDB,
pendingDbURL: databases.pendingDB,
endpoint: LightWalletEndpointBuilder.default,
service: self.service,
repository: TransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: databases.dataDB.absoluteString)),
downloader: downloader,
@ -78,7 +79,7 @@ class TestCoordinator {
}
func stop() throws {
try synchronizer.stop()
synchronizer.stop()
self.completionHandler = nil
self.errorHandler = nil
@ -216,6 +217,7 @@ class TestSynchronizerBuilder {
cacheDbURL: URL,
dataDbURL: URL,
pendingDbURL: URL,
endpoint: LightWalletEndpoint,
service: LightWalletService,
repository: TransactionRepository,
downloader: CompactBlockDownloader,
@ -232,6 +234,7 @@ class TestSynchronizerBuilder {
cacheDbURL: cacheDbURL,
dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
endpoint: endpoint,
service: service,
repository: repository,
downloader: downloader,
@ -239,19 +242,10 @@ class TestSynchronizerBuilder {
outputParamsURL: outputParamsURL,
loggerProxy: loggerProxy
)
let credentials = try initializer.initialize(seedProvider: StubSeedProvider(bytes: seedBytes), walletBirthdayHeight: walletBirthday.height)
try initializer.initialize(viewingKeys: try DerivationTool().deriveViewingKeys(seed: seedBytes, numberOfAccounts: 1), walletBirthday: walletBirthday.height)
let credentials = try DerivationTool().deriveSpendingKeys(seed: seedBytes, numberOfAccounts: 1)
return (credentials, try SDKSynchronizer(initializer: initializer)
)
}
}
class StubSeedProvider: SeedProvider {
let bytes: [UInt8]
init(bytes: [UInt8]) {
self.bytes = bytes
}
func seed() -> [UInt8] {
self.bytes
}
}

View File

@ -31,9 +31,15 @@ class WalletTests: XCTestCase {
func testWalletInitialization() {
let wallet = Initializer(cacheDbURL: cacheData, dataDbURL: dbData, pendingDbURL: try! TestDbBuilder.pendingTransactionsDbURL(), endpoint: LightWalletEndpointBuilder.default, spendParamsURL: try! __spendParamsURL(), outputParamsURL: try! __outputParamsURL())
let wallet = Initializer(cacheDbURL: cacheData,
dataDbURL: dbData,
pendingDbURL: try! TestDbBuilder.pendingTransactionsDbURL(),
endpoint: LightWalletEndpointBuilder.default,
spendParamsURL: try! __spendParamsURL(),
outputParamsURL: try! __outputParamsURL()
)
XCTAssertNoThrow(try wallet.initialize(seedProvider: SampleSeedProvider(), walletBirthdayHeight: ZcashSDK.SAPLING_ACTIVATION_HEIGHT))
XCTAssertNoThrow(try wallet.initialize(viewingKeys: ["zxviewtestsapling1qwxyzvdmqqqqpqy3knx32fpja779wzg76kmglgguvr74g773f3aw3gy37rar6y9d37knvskz6thnea55s05cz3a7q38835hq4w58yevn763cn2wf7k2mpj247ynxpt9qm0nn39slkz5dk572hxr43pxqtg5kz3pqcj8z8uhz0l2vx8gxe90uf4pgw7ks23f0hz2hm47k9ym42cmns3tenhxzlyur2nvx68h4fmk9nrs44ymcqz434zsuxpvhklrjzn00gc43fdghn5szc5x2w"], walletBirthday: 663194))
// fileExists actually sucks, so attempting to delete the file and checking what happens is far better :)
XCTAssertNoThrow( try FileManager.default.removeItem(at: dbData!) )
@ -42,12 +48,6 @@ class WalletTests: XCTestCase {
}
}
struct SampleSeedProvider: SeedProvider {
func seed() -> [UInt8] {
Array("testreferencealicetestreferencealice".utf8)
}
}
struct WalletBirthdayProvider {
static var testBirthday: WalletBirthday {
WalletBirthday()

View File

@ -28,7 +28,12 @@ class WalletTransactionEncoderTests: XCTestCase {
queue.maxConcurrentOperationCount = 1
queue.qualityOfService = .userInitiated
initializer = Initializer(cacheDbURL: cacheDbHandle.readWriteDb, dataDbURL: dataDbHandle.readWriteDb, pendingDbURL: try! TestDbBuilder.pendingTransactionsDbURL(), endpoint: LightWalletEndpointBuilder.default, spendParamsURL: try! __spendParamsURL(), outputParamsURL: try! __outputParamsURL())
initializer = Initializer(cacheDbURL: cacheDbHandle.readWriteDb,
dataDbURL: dataDbHandle.readWriteDb,
pendingDbURL: try! TestDbBuilder.pendingTransactionsDbURL(),
endpoint: LightWalletEndpointBuilder.default,
spendParamsURL: try! __spendParamsURL(),
outputParamsURL: try! __outputParamsURL())
repository = TransactionSQLDAO(dbProvider: dataDbHandle.connectionProvider(readwrite: false))
transactionEncoder = WalletTransactionEncoder(initializer: initializer)

View File

@ -37,8 +37,20 @@ class ZcashRustBackendTests: XCTestCase {
}
func testInitWithViewingKeys() throws {
let viewingKey = "zxviews1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkysswfhjk79n8l99f2grd26dqg6dy3jcmxsaypxfsu6ara6vsk3x8l544uaksstx9zre879mdg7s9a7zurrx6pf5qg2n323js2s3zlu8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszcq7kwxy"
XCTAssertNoThrow(try ZcashRustBackend.initDataDb(dbData: dbData!))
let _ = try ZcashRustBackend.initAccountsTable(dbData: dbData!, exfvks: [viewingKey])
XCTAssertNil(ZcashRustBackend.getLastError())
XCTAssertEqual(ZcashRustBackend.getAddress(dbData: dbData!, account: 0),"zs1vp7kvlqr4n9gpehztr76lcn6skkss9p8keqs3nv8avkdtjrcctrvmk9a7u494kluv756jeee5k0")
}
func testDeriveExtendedSpendingKeys() {
let seed = "testreferencealicetestreferencealice"
let seed = Array("testreferencealicetestreferencealice".utf8)
var spendingKeys: [String]? = nil
XCTAssertNoThrow(try { spendingKeys = try ZcashRustBackend.deriveExtendedSpendingKeys(seed: seed, accounts: 1) }())
@ -49,7 +61,7 @@ class ZcashRustBackendTests: XCTestCase {
}
func testDeriveExtendedFullViewingKeys() {
let seed = "testreferencealicetestreferencealice"
let seed = Array("testreferencealicetestreferencealice".utf8)
var fullViewingKeys: [String]? = nil
XCTAssertNoThrow(try { fullViewingKeys = try ZcashRustBackend.deriveExtendedFullViewingKeys(seed: seed, accounts: 1) }())
@ -59,7 +71,7 @@ class ZcashRustBackendTests: XCTestCase {
}
func testDeriveExtendedFullViewingKey() {
let seed = "testreferencealicetestreferencealice"
let seed = Array("testreferencealicetestreferencealice".utf8)
var fullViewingKey: String? = nil

View File

@ -204,14 +204,3 @@ internal final class DarksideStreamerClient: GRPCClient, DarksideStreamerClientP
}
// Provides conformance to `GRPCPayload` for request and response messages
extension DarksideMetaState: GRPCProtobufPayload {}
//extension Empty: GRPCProtobufPayload {}
extension DarksideBlock: GRPCProtobufPayload {}
extension DarksideBlocksURL: GRPCProtobufPayload {}
extension DarksideEmptyBlocks: GRPCProtobufPayload {}
//extension RawTransaction: GRPCProtobufPayload {}
extension DarksideTransactionsURL: GRPCProtobufPayload {}
extension DarksideHeight: GRPCProtobufPayload {}

View File

@ -77,6 +77,30 @@ extension LightWalletServiceMockResponse {
}
class MockRustBackend: ZcashRustBackendWelding {
static func initAccountsTable(dbData: URL, exfvks: [String]) throws -> Bool {
false
}
static func deriveTransparentAddressFromSeed(seed: [UInt8]) throws -> String? {
nil
}
static func deriveExtendedFullViewingKeys(seed: [UInt8], accounts: Int32) throws -> [String]? {
nil
}
static func deriveExtendedSpendingKeys(seed: [UInt8], accounts: Int32) throws -> [String]? {
nil
}
static func deriveShieldedAddressFromSeed(seed: [UInt8], accountIndex: Int32) throws -> String? {
nil
}
static func deriveShieldedAddressFromViewingKey(_ extfvk: String) throws -> String? {
nil
}
static func consensusBranchIdFor(height: Int32) throws -> Int32 {
-1

View File

@ -67,11 +67,11 @@ func __dataDbURL() throws -> URL {
}
func __spendParamsURL() throws -> URL {
URL(string: Bundle.testBundle.url(forResource: "sapling-spend", withExtension: "params")!.path)!
try __documentsDirectory().appendingPathComponent("sapling-spend.params")
}
func __outputParamsURL() throws -> URL {
URL(string: Bundle.testBundle.url(forResource: "sapling-output", withExtension: "params")!.path)!
try __documentsDirectory().appendingPathComponent("sapling-output.params")
}
func copyParametersToDocuments() throws -> (spend: URL, output: URL) {
@ -112,7 +112,7 @@ extension Bundle {
}
class TestSeed: SeedProvider {
class TestSeed {
/**
test account: "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"

View File

@ -8,8 +8,8 @@ use std::slice;
use std::str::FromStr;
use zcash_client_backend::{
encoding::{
decode_extended_spending_key, encode_extended_full_viewing_key,
encode_extended_spending_key,
decode_extended_full_viewing_key, decode_extended_spending_key,
encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address,
},
keys::spending_key,
};
@ -43,14 +43,29 @@ use zcash_proofs::prover::LocalTxProver;
#[cfg(feature = "mainnet")]
use zcash_client_backend::constants::mainnet::{
COIN_TYPE, HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, HRP_SAPLING_EXTENDED_SPENDING_KEY,
HRP_SAPLING_PAYMENT_ADDRESS,
};
#[cfg(not(feature = "mainnet"))]
use zcash_client_backend::constants::testnet::{
COIN_TYPE, HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, HRP_SAPLING_EXTENDED_SPENDING_KEY,
HRP_SAPLING_PAYMENT_ADDRESS,
};
use std::convert::TryFrom;
// /////////////////////////////////////////////////////////////////////////////////////////////////
// Temporary Imports
use base58::ToBase58;
use sha2::{Digest, Sha256};
// use zcash_primitives::legacy::TransparentAddress;
use hdwallet::{ExtendedPrivKey, KeyIndex};
use secp256k1::{PublicKey, Secp256k1};
use zcash_client_backend::constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX;
// use crate::extended_key::{key_index::KeyIndex, ExtendedPrivKey, ExtendedPubKey, KeySeed};
// /////////////////////////////////////////////////////////////////////////////////////////////////
fn unwrap_exc_or<T>(exc: Result<T, ()>, def: T) -> T {
match exc {
Ok(value) => value,
@ -160,39 +175,49 @@ pub extern "C" fn zcashlc_init_accounts_table(
unwrap_exc_or_null(res)
}
/// Initialises the data database with the given block.
///
/// This enables a newly-created database to be immediately-usable, without needing to
/// synchronise historic blocks.
/// Initialises the data database with the given extended full viewing keys
/// Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it.
#[no_mangle]
pub extern "C" fn zcashlc_init_blocks_table(
pub extern "C" fn zcashlc_init_accounts_table_with_keys(
db_data: *const u8,
db_data_len: usize,
height: i32,
hash_hex: *const c_char,
time: u32,
sapling_tree_hex: *const c_char,
) -> i32 {
extfvks: *const *const c_char,
extfvks_len: usize,
) -> bool {
let res = catch_panic(|| {
let db_data = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(db_data, db_data_len)
}));
let hash = {
let mut hash = hex::decode(unsafe { CStr::from_ptr(hash_hex) }.to_str()?).unwrap();
hash.reverse();
BlockHash::from_slice(&hash)
};
let sapling_tree =
hex::decode(unsafe { CStr::from_ptr(sapling_tree_hex) }.to_str()?).unwrap();
match init_blocks_table(&db_data, height, hash, time, &sapling_tree) {
Ok(()) => Ok(1),
Err(e) => Err(format_err!("Error while initializing blocks table: {}", e)),
let extfvks = unsafe { std::slice::from_raw_parts(extfvks, extfvks_len)
.into_iter()
.map(|s| CStr::from_ptr(*s).to_str().unwrap())
.map( |vkstr|
decode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, &vkstr)
.unwrap()
.unwrap()
).collect::<Vec<_>>() };
match init_accounts_table(&db_data,&extfvks) {
Ok(()) => Ok(true),
Err(e) => match e.kind() {
ErrorKind::TableNotEmpty => {
// Ignore this error.
Ok(true)
}
_ => return Err(format_err!("Error while initializing accounts: {}", e)),
},
}
});
unwrap_exc_or_null(res)
unwrap_exc_or(res, false)
}
/// Derives Extended Spending Keys from the given seed into 'accounts' number of accounts.
/// Returns the ExtendedSpendingKeys for the accounts. The caller should store these
/// securely for use while spending.
///
/// Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it.
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_extended_spending_keys(
seed: *const u8,
@ -229,7 +254,11 @@ pub unsafe extern "C" fn zcashlc_derive_extended_spending_keys(
});
unwrap_exc_or_null(res)
}
/// Derives Extended Full Viewing Keys from the given seed into 'accounts' number of accounts.
/// Returns the Extended Full Viewing Keys for the accounts. The caller should store these
/// securely
///
/// Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it.
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_keys(
seed: *const u8,
@ -266,7 +295,63 @@ pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_keys(
});
unwrap_exc_or_null(res)
}
/// derives a shielded address from the given seed.
/// call zcashlc_string_free with the returned pointer when done using it
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_shielded_address_from_seed(
seed: *const u8,
seed_len: usize,
account_index: i32,
) -> *mut c_char {
let res = catch_panic(|| {
let seed = slice::from_raw_parts(seed, seed_len);
let account_index = if account_index >= 0 {
account_index as u32
} else {
return Err(format_err!("accounts argument must be greater than zero"));
};
let address = spending_key(&seed, COIN_TYPE, account_index)
.default_address()
.unwrap()
.1;
let address_str = encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &address);
Ok(CString::new(address_str).unwrap().into_raw())
});
unwrap_exc_or_null(res)
}
/// derives a shielded address from the given viewing key.
/// call zcashlc_string_free with the returned pointer when done using it
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_shielded_address_from_viewing_key(
extfvk: *const c_char,
) -> *mut c_char {
let res = catch_panic(|| {
let extfvk_string = CStr::from_ptr(extfvk).to_str()?;
let extfvk = match decode_extended_full_viewing_key(
HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
&extfvk_string,
) {
Ok(Some(extfvk)) => extfvk,
Ok(None) => {
return Err(format_err!("Failed to parse viewing key string in order to derive the address. Deriving a viewing key from the string returned no results. Encoding was valid but type was incorrect."));
}
Err(e) => {
return Err(format_err!(
"Error while deriving viewing key from string input: {}",
e
));
}
};
let address = extfvk.default_address().unwrap().1;
let address_str = encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &address);
Ok(CString::new(address_str).unwrap().into_raw())
});
unwrap_exc_or_null(res)
}
/// derives a shielded address from the given extended full viewing key.
/// call zcashlc_string_free with the returned pointer when done using it
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_key(
extsk: *const c_char,
@ -294,6 +379,39 @@ pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_key(
unwrap_exc_or_null(res)
}
/// Initialises the data database with the given block.
///
/// This enables a newly-created database to be immediately-usable, without needing to
/// synchronise historic blocks.
#[no_mangle]
pub extern "C" fn zcashlc_init_blocks_table(
db_data: *const u8,
db_data_len: usize,
height: i32,
hash_hex: *const c_char,
time: u32,
sapling_tree_hex: *const c_char,
) -> i32 {
let res = catch_panic(|| {
let db_data = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(db_data, db_data_len)
}));
let hash = {
let mut hash = hex::decode(unsafe { CStr::from_ptr(hash_hex) }.to_str()?).unwrap();
hash.reverse();
BlockHash::from_slice(&hash)
};
let sapling_tree =
hex::decode(unsafe { CStr::from_ptr(sapling_tree_hex) }.to_str()?).unwrap();
match init_blocks_table(&db_data, height, hash, time, &sapling_tree) {
Ok(()) => Ok(1),
Err(e) => Err(format_err!("Error while initializing blocks table: {}", e)),
}
});
unwrap_exc_or_null(res)
}
/// Returns the address for the account.
///
/// Call `zcashlc_string_free` on the returned pointer when you are finished with it.
@ -324,27 +442,6 @@ pub extern "C" fn zcashlc_get_address(
unwrap_exc_or_null(res)
}
/// Returns the balance for the account, including all unspent notes that we know about.
#[no_mangle]
pub extern "C" fn zcashlc_get_balance(db_data: *const u8, db_data_len: usize, account: i32) -> i64 {
let res = catch_panic(|| {
let db_data = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(db_data, db_data_len)
}));
let account = if account >= 0 {
account as u32
} else {
return Err(format_err!("account argument must be positive"));
};
match get_balance(&db_data, account) {
Ok(balance) => Ok(balance.into()),
Err(e) => Err(format_err!("Error while fetching balance: {}", e)),
}
});
unwrap_exc_or(res, -1)
}
/// Returns true when the address is valid and shielded.
/// Returns false in any other case
/// Errors when the provided address belongs to another network
@ -382,6 +479,27 @@ pub unsafe extern "C" fn zcashlc_is_valid_transparent_address(address: *const c_
unwrap_exc_or(res, false)
}
/// Returns the balance for the account, including all unspent notes that we know about.
#[no_mangle]
pub extern "C" fn zcashlc_get_balance(db_data: *const u8, db_data_len: usize, account: i32) -> i64 {
let res = catch_panic(|| {
let db_data = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(db_data, db_data_len)
}));
let account = if account >= 0 {
account as u32
} else {
return Err(format_err!("account argument must be positive"));
};
match get_balance(&db_data, account) {
Ok(balance) => Ok(balance.into()),
Err(e) => Err(format_err!("Error while fetching balance: {}", e)),
}
});
unwrap_exc_or(res, -1)
}
/// Returns the verified balance for the account, which ignores notes that have been
/// received too recently and are not yet deemed spendable.
#[no_mangle]
@ -715,3 +833,73 @@ pub extern "C" fn zcashlc_vec_string_free(v: *mut *mut c_char, len: usize, capac
v.into_iter().map(|s| CString::from_raw(s)).for_each(drop);
};
}
/// TEST TEST 123 TEST
/// Derives a transparent address from the given seed
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_transparent_address_from_seed(
seed: *const u8,
seed_len: usize,
) -> *mut c_char {
let res = catch_panic(|| {
let seed = slice::from_raw_parts(seed, seed_len);
// modified from: https://github.com/adityapk00/zecwallet-light-cli/blob/master/lib/src/lightwallet.rs
let ext_t_key = ExtendedPrivKey::with_seed(&seed).unwrap();
let address_sk = ext_t_key
.derive_private_key(KeyIndex::hardened_from_normalize_index(44).unwrap())
.unwrap()
.derive_private_key(KeyIndex::hardened_from_normalize_index(COIN_TYPE).unwrap())
.unwrap()
.derive_private_key(KeyIndex::hardened_from_normalize_index(0).unwrap())
.unwrap()
.derive_private_key(KeyIndex::Normal(0))
.unwrap()
.derive_private_key(KeyIndex::Normal(0))
.unwrap()
.private_key;
let secp = Secp256k1::new();
let pk = PublicKey::from_secret_key(&secp, &address_sk);
let mut hash160 = ripemd160::Ripemd160::new();
hash160.update(Sha256::digest(&pk.serialize()[..].to_vec()));
let address_string = hash160
.finalize()
.to_base58check(&B58_PUBKEY_ADDRESS_PREFIX, &[]);
Ok(CString::new(address_string).unwrap().into_raw())
});
unwrap_exc_or_null(res)
}
//
// Helper code from: https://github.com/adityapk00/zecwallet-light-cli/blob/master/lib/src/lightwallet.rs
//
/// A trait for converting a [u8] to base58 encoded string.
pub trait ToBase58Check {
/// Converts a value of `self` to a base58 value, returning the owned string.
/// The version is a coin-specific prefix that is added.
/// The suffix is any bytes that we want to add at the end (like the "iscompressed" flag for
/// Secret key encoding)
fn to_base58check(&self, version: &[u8], suffix: &[u8]) -> String;
}
impl ToBase58Check for [u8] {
fn to_base58check(&self, version: &[u8], suffix: &[u8]) -> String {
let mut payload: Vec<u8> = Vec::new();
payload.extend_from_slice(version);
payload.extend_from_slice(self);
payload.extend_from_slice(suffix);
let checksum = double_sha256(&payload);
payload.append(&mut checksum[..4].to_vec());
payload.to_base58()
}
}
pub fn double_sha256(payload: &[u8]) -> Vec<u8> {
let h1 = Sha256::digest(&payload);
let h2 = Sha256::digest(&h1);
h2.to_vec()
}