diff --git a/.gitignore b/.gitignore index fcced07..081a1e4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ rust/target/ # These are build by-products that `make xcframework` produces products/ +Products/ # These are SPM generated files .swiftpm diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e90998..ea0ac13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,109 @@ -# 0.0.3 -- [#13] Migrate to `zcash/librustzcash` revision with NU5 awareness (#20) -This enables mobile wallets to send transactions after NU5 activation. \ No newline at end of file +# Unreleased + +Unified spending keys are now used in all places where spending authority +is required, both for performing spends of shielded funds and for shielding +transparent funds. Unified spending keys are represented as opaque arrays +of bytes, and FFI methods are provided to permit derivation of viewing keys +from the binary unified spending key representation. + +IMPORTANT NOTE: the binary representation of a unified spending key may be +cached, but may become invalid and require re-derivation from seed to use as +input to any of the relevant APIs in the future, in the case that the +representation of the spending key changes or new types of spending authority +are recognized. Spending keys give irrevocable spend authority over +a specific account. Clients that choose to store the binary representation +of unified spending keys locally on device, should handle them with the +same level of care and secure storage policies as the wallet seed itself. + +## Added +- `zcashlc_create_account` provides new account creation functionality. + This is now the preferred API for the creation of new spend authorities + within the wallet; `zcashlc_init_accounts_table_with_keys` remains available + but should only be used if it is necessary to add multiple accounts at once, + such as when restoring a wallet from seed where multiple accounts had been + previously derived. + +Key derivation API: +- `zcashlc_derive_spending_key` +- `zcashlc_spending_key_to_full_viewing_key` + +Address retrieval, derivation, and verification API: +- `zcashlc_get_current_address` +- `zcashlc_get_next_available_address` +- `zcashlc_get_sapling_receiver_for_unified_address` +- `zcashlc_get_transparent_receiver_for_unified_address` +- `zcashlc_is_valid_unified_address` +- `zcashlc_is_valid_unified_full_viewing_key` +- `zcashlc_list_transparent_receivers` +- `zcashlc_get_typecodes_for_unified_address_receivers` +- `zcashlc_free_typecodes` +- `zcashlc_get_address_metadata` +Balance API: +- `zcashlc_get_verified_transparent_balance_for_account` +- `zcashlc_get_total_transparent_balance_for_account` + +New memo access API: +- `zcashlc_get_received_memo` +- `zcashlc_get_sent_memo` + +## Changed +- `zcashlc_create_to_address` now has been changed as follows: + - it no longer takes the string encoding of a Sapling extended spending key + as spend authority; instead, it takes the binary encoded form of a unified + spending key as returned by `zcashlc_create_account` or + `zcashlc_derive_spending_key`. See the note above. + - it now takes the minimum number of confirmations used to filter notes to + spend as an argument. + - the memo argument is now passed as a potentially-null pointer to an + `[u8; 512]` instead of a C string. +- `zcashlc_shield_funds` has been changed as follows: + - it no longer takes the transparent spending key for a single P2PKH address + as spend authority; instead, it takes the binary encoded form of a unified + spending key as returned by `zcashlc_create_account` + or `zcashlc_derive_spending_key`. See the note above. + - the memo argument is now passed as a potentially-null pointer to an + `[u8; 512]` instead of a C string. + - it no longer takes a destination address; instead, the internal shielding + address is automatically derived from the account ID. +- Various changes have been made to correctly implement ZIP 316: + - `FFIUnifiedViewingKey` now stores an account ID and the encoding of a + ZIP 316 Unified Full Viewing Key. + - `zcashlc_init_accounts_table_with_keys` now takes a slice of ZIP 316 UFVKs. +- `zcashlc_put_utxo` no longer has an `address_str` argument (the address is + instead inferred from the script). +- `zcashlc_get_verified_balance` now takes the minimum number of confirmations + used to filter received notes as an argument. +- `zcashlc_get_verified_transparent_balance` now takes the minimum number of + confirmations used to filter received notes as an argument. +- `zcashlc_get_total_transparent_balance` now returns a balance that includes + all UTXOs including those only in the mempool (i.e. those with 0 + confirmations). + +## Removed + +The following spending key derivation APIs have been removed and replaced by +`zcashlc_derive_spending_key`: +- `zcashlc_derive_extended_spending_key` +- `zcashlc_derive_transparent_private_key_from_seed` +- `zcashlc_derive_transparent_account_private_key_from_seed` + +The following viewing key APIs have been removed and replaced by +`zcashlc_spending_key_to_full_viewing_key`: +- `zcashlc_derive_extended_full_viewing_key` +- `zcashlc_derive_shielded_address_from_viewing_key` +- `zcashlc_derive_unified_viewing_keys_from_seed` + +The following address derivation APIs have been removed in favor of +`zcashlc_get_current_address` and `zcashlc_get_next_available_address`: +- `zcashlc_get_address` +- `zcashlc_derive_shielded_address_from_seed` +- `zcashlc_derive_transparent_address_from_secret_key` +- `zcashlc_derive_transparent_address_from_seed` +- `zcashlc_derive_transparent_address_from_public_key` + +- `zcashlc_init_accounts_table` has been removed in favor of + `zcashlc_create_account` + +# 0.0.3 +- [#13] Migrate to `zcash/librustzcash` revision with NU5 awareness (#20) + This enables mobile wallets to send transactions after NU5 activation. diff --git a/README.md b/README.md index f6a435b..b7c1d3f 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,17 @@ Depending on what state the intermediate build products might be in, you may fir 2. Update `s.version` in `libzcashlc.podspec` to the new release version. 3. (assuming you have the `pod` command) `pod trunk push libzcashlc.podspec` + +## License + +Licensed under MIT license ([LICENSE](LICENSE) or http://opensource.org/licenses/MIT). + +Downstream code forks should note that 'libzcashlc' (and thus XCFramework) +depends on the 'orchard' crate, which is licensed under the [Bootstrap Open +Source License](https://github.com/zcash/orchard/blob/main/LICENSE-BOSL). A +license exception is provided allowing some derived works that are linked or +combined with the 'orchard' crate to be copied or distributed under the original +licenses (in this case MIT), provided that the included portions of the +'orchard' code remain subject to BOSL. See +https://github.com/zcash/orchard/blob/main/COPYING for details of which derived +works can make use of this exception. diff --git a/libzcashlc.podspec b/libzcashlc.podspec index 1314074..80c0989 100644 --- a/libzcashlc.podspec +++ b/libzcashlc.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'libzcashlc' - s.version = '0.0.3' + s.version = '0.1.0-beta.3' s.summary = 'Rust core for Zcash clients' s.homepage = 'https://github.com/zcash-hackworks/zcash-light-client-ffi' s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 3ca19df..5cb61e0 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.15.2" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a2e47a1fbe209ee101dd6d61285226744c6c8d3c21c8dc878ba6cb9f467f3a" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ "gimli", ] @@ -18,42 +18,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "aes" -version = "0.6.0" +name = "aead" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" +checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" dependencies = [ - "aes-soft", - "aesni", - "cipher", + "crypto-common", + "generic-array", ] [[package]] -name = "aes-soft" -version = "0.6.4" +name = "aes" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ - "cipher", + "cfg-if", + "cipher 0.3.0", + "cpufeatures", "opaque-debug", ] [[package]] -name = "aesni" -version = "0.10.0" +name = "ahash" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "cipher", - "opaque-debug", + "getrandom", + "once_cell", + "version_check", ] -[[package]] -name = "ahash" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" - [[package]] name = "ansi_term" version = "0.12.1" @@ -63,6 +59,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "anyhow" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" + [[package]] name = "arrayref" version = "0.3.6" @@ -71,9 +73,9 @@ checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" [[package]] name = "arrayvec" -version = "0.5.2" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "atty" @@ -94,13 +96,13 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.59" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", - "cfg-if 1.0.0", + "cfg-if", "libc", "miniz_oxide", "object", @@ -109,15 +111,9 @@ dependencies = [ [[package]] name = "base-x" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc19a4937b4fbd3fe3379793130e42060d10627a360f2127802b10b87e7baf74" - -[[package]] -name = "base58" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" [[package]] name = "base64" @@ -125,6 +121,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64ct" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" + [[package]] name = "bech32" version = "0.8.1" @@ -133,58 +135,62 @@ checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" [[package]] name = "bellman" -version = "0.8.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7089887635778eabf0038a166f586eee5413fb85c8fa6c9a754914f0f644f49f" +checksum = "a4dd656ef4fdf7debb6d87d4dd92642fcbcdb78cbf6600c13e25c87e4d1a3807" dependencies = [ - "bitvec 0.18.5", + "bitvec", "blake2s_simd", "byteorder", - "crossbeam", + "crossbeam-channel", "ff", - "futures", - "futures-cpupool", "group", + "lazy_static", + "log", "num_cpus", "pairing", - "rand_core 0.5.1", + "rand_core", + "rayon", "subtle", ] [[package]] -name = "bitflags" -version = "1.3.2" +name = "bip0039" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "d0830ae4cc96b0617cc912970c2b17e89456fecbf55e8eed53a956f37ab50c41" +dependencies = [ + "hmac", + "pbkdf2", + "rand", + "sha2", + "unicode-normalization", + "zeroize", +] [[package]] -name = "bitvec" -version = "0.18.5" +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98fcd36dda4e17b7d7abc64cb549bf0201f4ab71e00700c798ca7e62ed3761fa" -dependencies = [ - "funty", - "radium 0.3.0", - "wyz", -] +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" -version = "0.19.6" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55f93d0ef3363c364d5976646a38f04cf67cfe1d4c8d160cdea02cab2c116b33" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", - "radium 0.5.3", + "radium", "tap", "wyz", ] [[package]] name = "blake2b_simd" -version = "0.5.11" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" dependencies = [ "arrayref", "arrayvec", @@ -193,9 +199,9 @@ dependencies = [ [[package]] name = "blake2s_simd" -version = "0.5.11" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e461a7034e85b211a4acb57ee2e6730b32912b06c08cc242243c39fc21ae6a2" +checksum = "db539cc2b5f6003621f1cd9ef92d7ded8ea5232c7de0f9faa2de251cd98730d4" dependencies = [ "arrayref", "arrayvec", @@ -211,14 +217,23 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + [[package]] name = "block-modes" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" dependencies = [ "block-padding", - "cipher", + "cipher 0.3.0", ] [[package]] @@ -229,15 +244,14 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "bls12_381" -version = "0.3.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4caf0101205582491f772d60a6fcb6bcec19963e68209cb631851eeadb01421f" +checksum = "62250ece575fa9b22068b3a8d59586f01d426dd7785522efd97632959e71c986" dependencies = [ - "bitvec 0.18.5", "ff", "group", "pairing", - "rand_core 0.5.1", + "rand_core", "subtle", ] @@ -252,9 +266,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" [[package]] name = "byteorder" @@ -262,6 +276,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + [[package]] name = "cbindgen" version = "0.14.3" @@ -269,7 +289,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e03a705df2e735cc5486f104a48e25a8f72ae06eaea5b7753a81270ed00859" dependencies = [ "clap", - "heck", + "heck 0.3.3", "log", "proc-macro2", "quote", @@ -288,25 +308,54 @@ checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" [[package]] name = "cfg-if" -version = "0.1.10" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "cfg-if" -version = "1.0.0" +name = "chacha20" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "c7fc89c7c5b9e7a02dfe45cd2367bae382f9ed31c61ca8debe5f827c420a2f08" +dependencies = [ + "cfg-if", + "cipher 0.4.3", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher 0.4.3", + "poly1305", + "zeroize", +] [[package]] name = "cipher" -version = "0.2.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ "generic-array", ] +[[package]] +name = "cipher" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clap" version = "2.34.0" @@ -336,98 +385,89 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] -[[package]] -name = "crossbeam" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" -dependencies = [ - "cfg-if 0.1.10", - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - [[package]] name = "crossbeam-channel" -version = "0.4.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ + "cfg-if", "crossbeam-utils", - "maybe-uninit", ] [[package]] name = "crossbeam-deque" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20ff29ded3204c5106278a81a38f4b482636ed4fa1e6cfbeef193291beb29ed" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ + "cfg-if", "crossbeam-epoch", "crossbeam-utils", - "maybe-uninit", ] [[package]] name = "crossbeam-epoch" -version = "0.8.2" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ "autocfg", - "cfg-if 0.1.10", + "cfg-if", "crossbeam-utils", - "lazy_static", - "maybe-uninit", "memoffset", "scopeguard", ] [[package]] -name = "crossbeam-queue" -version = "0.2.3" +name = "crossbeam-utils" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ - "cfg-if 0.1.10", - "crossbeam-utils", - "maybe-uninit", + "cfg-if", ] [[package]] -name = "crossbeam-utils" -version = "0.7.2" +name = "crunchy" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "autocfg", - "cfg-if 0.1.10", - "lazy_static", + "generic-array", + "typenum", ] [[package]] -name = "crypto_api" -version = "0.2.2" +name = "crypto-mac" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f855e87e75a4799e18b8529178adcde6fd4f97c1449ff4821e747ff728bb102" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] [[package]] -name = "crypto_api_chachapoly" -version = "0.4.3" +name = "daggy" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930b6a026ce9d358a17f9c9046c55d90b14bb847f36b6ebb6b19365d4feffb8" +checksum = "e2099ef075418d7b252af69583c831cde749af9423c2a212dea8895e8ea78841" dependencies = [ - "crypto_api", + "petgraph 0.4.13", ] [[package]] @@ -439,11 +479,21 @@ dependencies = [ "generic-array", ] +[[package]] +name = "digest" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +dependencies = [ + "block-buffer 0.10.3", + "crypto-common", +] + [[package]] name = "directories" -version = "3.0.2" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e69600ff1703123957937708eb27f7a564e48885c537782722ed0ba3189ce1d7" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" dependencies = [ "dirs-sys", ] @@ -465,15 +515,29 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + [[package]] name = "equihash" -version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=0a1ed9b8acf336bdc20ba49083c66f114ecf6877#0a1ed9b8acf336bdc20ba49083c66f114ecf6877" +version = "0.2.0" +source = "git+https://github.com/zcash/librustzcash.git?rev=6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d#6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d" dependencies = [ "blake2b_simd", "byteorder", ] +[[package]] +name = "f4jumble" +version = "0.1.0" +source = "git+https://github.com/zcash/librustzcash.git?rev=6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d#6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d" +dependencies = [ + "blake2b_simd", +] + [[package]] name = "failure" version = "0.1.8" @@ -510,21 +574,21 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] [[package]] name = "ff" -version = "0.8.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01646e077d4ebda82b73f1bca002ea1e91561a77df2431a9e79729bcc31950ef" +checksum = "df689201f395c6b90dfe87127685f8dbfc083a5e779e613575d8bd7314300c3e" dependencies = [ - "bitvec 0.18.5", - "rand_core 0.5.1", + "bitvec", + "rand_core", "subtle", ] @@ -539,14 +603,27 @@ dependencies = [ "libc", ] +[[package]] +name = "fixedbitset" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "fpe" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25080721bbcd2cd4d765b7d607ea350425fa087ce53cd3e31afcacdab850352" +checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" dependencies = [ - "aes", "block-modes", + "cipher 0.3.0", + "libm", "num-bigint", "num-integer", "num-traits", @@ -554,31 +631,15 @@ dependencies = [ [[package]] name = "funty" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" - -[[package]] -name = "futures" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" - -[[package]] -name = "futures-cpupool" -version = "0.1.8" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" -dependencies = [ - "futures", - "num_cpus", -] +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -586,70 +647,98 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.16" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "wasi", ] [[package]] -name = "getrandom" -version = "0.2.6" +name = "gimli" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "ff", + "memuse", + "rand_core", + "subtle", ] [[package]] -name = "gimli" -version = "0.24.0" +name = "halo2_gadgets" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" +checksum = "85e10bf9924da1754e443641c9e7f9f00483749f8fb837fde696ef6ed6e2f079" +dependencies = [ + "arrayvec", + "bitvec", + "ff", + "group", + "halo2_proofs", + "lazy_static", + "pasta_curves", + "rand", + "subtle", + "uint", +] [[package]] -name = "group" -version = "0.8.0" +name = "halo2_proofs" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc11f9f5fbf1943b48ae7c2bf6846e7d827a512d1be4f23af708f5ca5d01dde1" +checksum = "cff771b9a2445cd2545c9ef26d863c290fbb44ae440c825a20eb7156f67a949a" dependencies = [ - "byteorder", + "blake2b_simd", "ff", - "rand_core 0.5.1", - "subtle", + "group", + "pasta_curves", + "rand_core", + "rayon", + "tracing", ] [[package]] name = "hashbrown" -version = "0.9.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashlink" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d99cf782f0dc4372d26846bec3de7804ceb5df083c2d4462c0b8d2330e894fa8" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" dependencies = [ - "hashbrown", + "hashbrown 0.11.2", ] [[package]] name = "hdwallet" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2033326faddc94885c26775db24683819bf3ae9461e016c45df18b910bdeedd7" +checksum = "9cd89bf343be18dbe1e505100e48168bbd084760e842a8fed0317d2361470193" dependencies = [ "lazy_static", - "rand_core 0.6.3", + "rand_core", "ring", "secp256k1", ] @@ -663,6 +752,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -678,41 +773,88 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "incrementalmerkletree" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "068c5bdd31006d55536655cf1eb0d22d84d28de7c725b419480fd5d005c83216" +dependencies = [ + "serde", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", ] [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "js-sys" -version = "0.3.57" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] [[package]] name = "jubjub" -version = "0.5.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "620638af3b80d23f4df0cae21e3cc9809ac8826767f345066f010bcea66a2c55" +checksum = "a575df5f985fe1cd5b2b05664ff6accfc46559032b954529fd225a2168d27b0f" dependencies = [ - "bitvec 0.18.5", + "bitvec", "bls12_381", "ff", "group", - "rand_core 0.5.1", + "rand_core", "subtle", ] @@ -723,29 +865,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] -name = "lexical-core" -version = "0.7.6" +name = "libc" +version = "0.2.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" -dependencies = [ - "arrayvec", - "bitflags", - "cfg-if 1.0.0", - "ryu", - "static_assertions", -] +checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" [[package]] -name = "libc" -version = "0.2.126" +name = "libm" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" [[package]] name = "libsqlite3-sys" -version = "0.20.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d31059f22935e6c31830db5249ba2b7ecd54fd73a9909286f0a67aa55c2fbd" +checksum = "290b64917f8b0cb885d9de0f9959fe1f775d7fa12f1da2db9001c1c8ab60f89d" dependencies = [ "cc", "pkg-config", @@ -754,17 +889,17 @@ dependencies = [ [[package]] name = "libzcashlc" -version = "0.0.8" +version = "0.1.0-beta.3" dependencies = [ - "base58", "cbindgen", "failure", "ffi_helpers", - "hdwallet", "hex", - "num-bigint", + "memuse", + "schemer", "secp256k1", - "time", + "secrecy", + "zcash_address", "zcash_client_backend", "zcash_client_sqlite", "zcash_primitives", @@ -777,58 +912,75 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - [[package]] name = "memchr" -version = "2.3.4" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.5.6" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] +[[package]] +name = "memuse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2145869435ace5ea6ea3d35f59be559317ec9a0d04e1812d5f185a87b6d36f1a" +dependencies = [ + "nonempty", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" dependencies = [ "adler", - "autocfg", ] +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + [[package]] name = "nom" -version = "6.2.1" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c5c51b9083a3c620fa67a2a635d1ce7d95b897e957d6b28ff9a5da960a103a6" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" dependencies = [ - "bitvec 0.19.6", - "funty", - "lexical-core", "memchr", - "version_check", + "minimal-lexical", ] +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + [[package]] name = "num-bigint" -version = "0.3.3" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ "autocfg", "num-integer", @@ -866,15 +1018,18 @@ dependencies = [ [[package]] name = "object" -version = "0.24.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] [[package]] name = "once_cell" -version = "1.12.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -883,20 +1038,108 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] -name = "pairing" -version = "0.18.0" +name = "orchard" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f06b263206a75a7d96ca75d46a3e9ca8eaf7ab7feea209749bb8b818d22f427" +dependencies = [ + "aes", + "bitvec", + "blake2b_simd", + "ff", + "fpe", + "group", + "halo2_gadgets", + "halo2_proofs", + "hex", + "incrementalmerkletree", + "lazy_static", + "memuse", + "nonempty", + "pasta_curves", + "rand", + "reddsa", + "serde", + "subtle", + "tracing", + "zcash_note_encryption 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pairing" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" +dependencies = [ + "group", +] + +[[package]] +name = "password-hash" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pasta_curves" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc65faf8e7313b4b1fbaa9f7ca917a0eed499a9663be71477f87993604341d8" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "lazy_static", + "rand", + "static_assertions", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" +dependencies = [ + "crypto-mac", + "password-hash", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "petgraph" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3659d1ee90221741f65dd128d9998311b0e40c5d3c23a62445938214abce4f" +dependencies = [ + "fixedbitset 0.1.9", +] + +[[package]] +name = "petgraph" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f702cdbee9e0a6272452c20dec82465bc821116598b4eeb63e9a71a69dbf7fd" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ - "ff", - "group", + "fixedbitset 0.4.2", + "indexmap", ] [[package]] -name = "percent-encoding" -version = "2.1.0" +name = "pin-project-lite" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pkg-config" @@ -904,12 +1147,33 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "prettyplease" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c142c0e46b57171fe0c528bee8c5b7569e80f0c17e377cd0e30ea57dbc11bb51" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -918,111 +1182,173 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" dependencies = [ "unicode-ident", ] [[package]] -name = "protobuf" -version = "2.27.1" +name = "prost" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf7e6d18738ecd0902d30d1ad232c9125985a3422929b16c65517b38adc14f96" +checksum = "399c3c31cdec40583bb68f0b18403400d01ec4289c383aa047560439952c4dd7" +dependencies = [ + "bytes", + "prost-derive", +] [[package]] -name = "protobuf-codegen" -version = "2.27.1" +name = "prost-build" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aec1632b7c8f2e620343439a7dfd1f3c47b18906c4be58982079911482b5d707" +checksum = "7f835c582e6bd972ba8347313300219fed5bfa52caf175298d860b61ff6069bb" dependencies = [ - "protobuf", + "bytes", + "heck 0.4.0", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph 0.6.2", + "prost", + "prost-types", + "regex", + "tempfile", + "which", ] [[package]] -name = "protobuf-codegen-pure" -version = "2.27.1" +name = "prost-derive" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f8122fdb18e55190c796b088a16bdb70cd7acdcd48f7a8b796b58c62e532cc6" +checksum = "7345d5f0e08c0536d7ac7229952590239e77abf0a0100a1b1d890add6ea96364" dependencies = [ - "protobuf", - "protobuf-codegen", + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "quote" -version = "1.0.18" +name = "prost-types" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "4dfaa718ad76a44b3415e6c4d53b17c8f99160dcb3a99b10470fce8ad43f6e3e" dependencies = [ - "proc-macro2", + "bytes", + "prost", ] [[package]] -name = "radium" -version = "0.3.0" +name = "quote" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] [[package]] name = "radium" -version = "0.5.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.7.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "getrandom 0.1.16", "libc", "rand_chacha", - "rand_core 0.5.1", - "rand_hc", + "rand_core", ] [[package]] name = "rand_chacha" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.5.1", + "rand_core", ] [[package]] name = "rand_core" -version = "0.5.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.1.16", + "getrandom", ] [[package]] -name = "rand_core" -version = "0.6.3" +name = "rayon" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] [[package]] -name = "rand_hc" -version = "0.2.0" +name = "rayon-core" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "reddsa" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cc8038c8b7e481bdf688d0585d4897ed0e9e0cee10aa365dde51238c20e4182" +dependencies = [ + "blake2b_simd", + "byteorder", + "group", + "jubjub", + "pasta_curves", + "rand_core", + "serde", + "thiserror", + "zeroize", +] + +[[package]] +name = "redjubjub" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "6039ff156887caf92df308cbaccdc058c9d3155a913da046add6e48c4cdbd91d" dependencies = [ - "rand_core 0.5.1", + "blake2b_simd", + "byteorder", + "digest 0.9.0", + "jubjub", + "rand_core", + "serde", + "thiserror", + "zeroize", ] [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -1033,11 +1359,26 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.6", + "getrandom", "redox_syscall", "thiserror", ] +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -1063,21 +1404,19 @@ dependencies = [ ] [[package]] -name = "ripemd160" -version = "0.9.1" +name = "ripemd" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "block-buffer", - "digest", - "opaque-debug", + "digest 0.10.5", ] [[package]] name = "rusqlite" -version = "0.24.2" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38ee71cbab2c827ec0ac24e76f82eca723cee92c509a65f67dee393c25112" +checksum = "5c4b1eaf239b47034fb450ee9cdedd7d0226571689d8823030c4b6c2cb407152" dependencies = [ "bitflags", "fallible-iterator", @@ -1106,9 +1445,30 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "schemer" +version = "0.2.0" +source = "git+https://github.com/aschampion/schemer.git?rev=6726b60f43f72c6e24a18d31be0ec7d42829e5e1#6726b60f43f72c6e24a18d31be0ec7d42829e5e1" +dependencies = [ + "daggy", + "log", + "thiserror", + "uuid", +] + +[[package]] +name = "schemer-rusqlite" +version = "0.2.0" +source = "git+https://github.com/aschampion/schemer.git?rev=6726b60f43f72c6e24a18d31be0ec7d42829e5e1#6726b60f43f72c6e24a18d31be0ec7d42829e5e1" +dependencies = [ + "rusqlite", + "schemer", + "uuid", +] [[package]] name = "scopeguard" @@ -1118,22 +1478,31 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "secp256k1" -version = "0.19.0" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6179428c22c73ac0fbb7b5579a56353ce78ba29759b3b8575183336ea74cdfb" +checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260" dependencies = [ "secp256k1-sys", ] [[package]] name = "secp256k1-sys" -version = "0.3.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11553d210db090930f4432bea123b31f70bbf693ace14504ea2a35e796c28dd2" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" dependencies = [ "cc", ] +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + [[package]] name = "semver" version = "0.9.0" @@ -1151,18 +1520,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.137" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -1171,9 +1540,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074" dependencies = [ "itoa", "ryu", @@ -1201,18 +1570,18 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ - "block-buffer", - "cfg-if 1.0.0", + "block-buffer 0.9.0", + "cfg-if", "cpufeatures", - "digest", + "digest 0.9.0", "opaque-debug", ] [[package]] name = "smallvec" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "spin" @@ -1298,9 +1667,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.95" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" dependencies = [ "proc-macro2", "quote", @@ -1331,7 +1700,7 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "fastrand", "libc", "redox_syscall", @@ -1350,18 +1719,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -1406,6 +1775,21 @@ dependencies = [ "syn", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + [[package]] name = "toml" version = "0.5.9" @@ -1415,35 +1799,111 @@ dependencies = [ "serde", ] +[[package]] +name = "tonic-build" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c6fd7c2581e36d63388a9e04c350c21beb7a8b059580b2e93993c526899ddc" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + [[package]] name = "typenum" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +[[package]] +name = "uint" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" -version = "0.2.3" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "universal-hash" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +dependencies = [ + "crypto-common", + "subtle", +] [[package]] name = "untrusted" @@ -1451,6 +1911,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "uuid" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1471,35 +1937,29 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -1508,9 +1968,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1518,9 +1978,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -1531,20 +1991,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "web-sys" -version = "0.3.57" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "which" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1569,116 +2040,179 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "wyz" -version = "0.2.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + +[[package]] +name = "zcash_address" +version = "0.2.0" +source = "git+https://github.com/zcash/librustzcash.git?rev=6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d#6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d" +dependencies = [ + "bech32", + "bs58", + "f4jumble", + "zcash_encoding", +] [[package]] name = "zcash_client_backend" version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=0a1ed9b8acf336bdc20ba49083c66f114ecf6877#0a1ed9b8acf336bdc20ba49083c66f114ecf6877" +source = "git+https://github.com/zcash/librustzcash.git?rev=6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d#6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d" dependencies = [ "base64", "bech32", "bls12_381", "bs58", - "ff", + "byteorder", + "crossbeam-channel", "group", "hdwallet", - "hex", - "jubjub", - "log", + "memuse", "nom", + "orchard", "percent-encoding", - "protobuf", - "protobuf-codegen-pure", - "rand_core 0.5.1", - "ripemd160", - "secp256k1", - "sha2", + "prost", + "rayon", + "secrecy", "subtle", "time", - "zcash_note_encryption", + "tonic-build", + "tracing", + "which", + "zcash_address", + "zcash_encoding", + "zcash_note_encryption 0.2.0 (git+https://github.com/zcash/librustzcash.git?rev=6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d)", "zcash_primitives", ] [[package]] name = "zcash_client_sqlite" version = "0.3.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=0a1ed9b8acf336bdc20ba49083c66f114ecf6877#0a1ed9b8acf336bdc20ba49083c66f114ecf6877" +source = "git+https://github.com/zcash/librustzcash.git?rev=6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d#6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d" dependencies = [ - "bech32", "bs58", - "ff", "group", + "hdwallet", "jubjub", - "protobuf", - "rand_core 0.5.1", + "prost", "rusqlite", - "secp256k1", + "schemer", + "schemer-rusqlite", + "secrecy", "time", + "uuid", "zcash_client_backend", "zcash_primitives", ] [[package]] -name = "zcash_note_encryption" -version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=0a1ed9b8acf336bdc20ba49083c66f114ecf6877#0a1ed9b8acf336bdc20ba49083c66f114ecf6877" +name = "zcash_encoding" +version = "0.2.0" +source = "git+https://github.com/zcash/librustzcash.git?rev=6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d#6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d" dependencies = [ - "blake2b_simd", "byteorder", - "crypto_api_chachapoly", - "ff", - "group", - "rand_core 0.5.1", + "nonempty", +] + +[[package]] +name = "zcash_note_encryption" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2be9c12532389fd03786b7068fb7936c17fade23b48f584707bdc5f79f3ec867" +dependencies = [ + "chacha20", + "chacha20poly1305", + "cipher 0.4.3", + "rand_core", + "subtle", +] + +[[package]] +name = "zcash_note_encryption" +version = "0.2.0" +source = "git+https://github.com/zcash/librustzcash.git?rev=6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d#6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d" +dependencies = [ + "chacha20", + "chacha20poly1305", + "cipher 0.4.3", + "rand_core", "subtle", ] [[package]] name = "zcash_primitives" -version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=0a1ed9b8acf336bdc20ba49083c66f114ecf6877#0a1ed9b8acf336bdc20ba49083c66f114ecf6877" +version = "0.8.1" +source = "git+https://github.com/zcash/librustzcash.git?rev=6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d#6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d" dependencies = [ "aes", - "bitvec 0.18.5", + "bip0039", + "bitvec", "blake2b_simd", "blake2s_simd", "bls12_381", "byteorder", - "crypto_api_chachapoly", "equihash", "ff", "fpe", - "funty", "group", + "hdwallet", "hex", + "incrementalmerkletree", "jubjub", "lazy_static", - "log", + "memuse", + "nonempty", + "orchard", "rand", - "rand_core 0.5.1", - "ripemd160", + "rand_core", + "ripemd", "secp256k1", "sha2", "subtle", - "zcash_note_encryption", + "zcash_address", + "zcash_encoding", + "zcash_note_encryption 0.2.0 (git+https://github.com/zcash/librustzcash.git?rev=6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d)", ] [[package]] name = "zcash_proofs" -version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=0a1ed9b8acf336bdc20ba49083c66f114ecf6877#0a1ed9b8acf336bdc20ba49083c66f114ecf6877" +version = "0.8.0" +source = "git+https://github.com/zcash/librustzcash.git?rev=6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d#6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d" dependencies = [ "bellman", "blake2b_simd", "bls12_381", - "byteorder", "directories", - "ff", "group", "jubjub", "lazy_static", - "rand_core 0.5.1", + "rand_core", + "redjubjub", + "tracing", "zcash_primitives", ] + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 586502d..ad64461 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "libzcashlc" -version = "0.0.8" -authors = ["Jack Grigg ", - "Francisco Gindre ", +version = "0.1.0-beta.3" +authors = [ + "Jack Grigg ", + "Francisco Gindre ", ] +license = "MIT" edition = "2018" build = "build.rs" @@ -11,27 +13,16 @@ build = "build.rs" failure = "0.1" ffi_helpers = "0.2" hex = "0.4" -zcash_client_backend = "0.5" -zcash_client_sqlite = "0.3" -zcash_primitives = "0.5" -zcash_proofs = "0.5" +memuse = "0.2.1" +schemer = "0.2" +secp256k1 = "0.21" +secrecy = "0.8" -#### Temporary additions: #################################### -#bitvec = "0.22" -base58 = "0.1.0" -#sha2 = "0.9" -#bs58 = { version = "0.3", features = ["check"] } -hdwallet = "=0.3.0" -#ripemd160 = "0.9" -secp256k1 = "0.19" -time = "0.2" -num-bigint = "0.3.3" -############################################################## - -#[dependencies.zcash_proofs] -#version = "0.5" -#default-features = false -#features = ["local-prover"] +zcash_address = { version = "0.2" } +zcash_client_backend = { version = "0.5", features = ["transparent-inputs", "unstable"] } +zcash_client_sqlite = { version = "0.3", features = ["transparent-inputs", "unstable"] } +zcash_primitives = "0.8.1" +zcash_proofs = "0.8" [build-dependencies] cbindgen = "0.14" @@ -44,15 +35,17 @@ crate-type = ["staticlib"] [profile.release] lto = true -# Revision corresponds to the partially-complete auto-shielding version, with the NU5 -# consensus branch ID and activation heights, and v5 transaction parsing, backported. -# https://github.com/zcash/librustzcash/pull/555 -# https://github.com/zcash/librustzcash/pull/558 +# Revision corresponds to zcash_primitives 0.6.0 plus the NU5 mainnet activation height. +# We also need zcash_client_backend and zcash_client_sqlite, which haven't been published +# with NU5 updates yet. [patch.crates-io] -zcash_client_backend = { git = 'https://github.com/zcash/librustzcash.git', rev='0a1ed9b8acf336bdc20ba49083c66f114ecf6877' } -zcash_client_sqlite = { git = 'https://github.com/zcash/librustzcash.git', rev='0a1ed9b8acf336bdc20ba49083c66f114ecf6877' } -zcash_primitives = { git = 'https://github.com/zcash/librustzcash.git', rev='0a1ed9b8acf336bdc20ba49083c66f114ecf6877' } -zcash_proofs = { git = 'https://github.com/zcash/librustzcash.git', rev='0a1ed9b8acf336bdc20ba49083c66f114ecf6877' } +zcash_address = { git = 'https://github.com/zcash/librustzcash.git', rev='6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d' } +zcash_client_backend = { git = 'https://github.com/zcash/librustzcash.git', rev='6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d' } +zcash_client_sqlite = { git = 'https://github.com/zcash/librustzcash.git', rev='6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d' } +zcash_primitives = { git = 'https://github.com/zcash/librustzcash.git', rev='6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d' } +zcash_proofs = { git = 'https://github.com/zcash/librustzcash.git', rev='6047c1d0030b2a40abd35cbc7ba15bd2bd63fc8d' } +schemer = { git = "https://github.com/aschampion/schemer.git", rev = "6726b60f43f72c6e24a18d31be0ec7d42829e5e1" } +schemer-rusqlite = { git = "https://github.com/aschampion/schemer.git", rev = "6726b60f43f72c6e24a18d31be0ec7d42829e5e1" } [features] mainnet = ["zcash_client_sqlite/mainnet"] diff --git a/rust/src/lib.rs b/rust/src/lib.rs index aff39de..9533169 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,52 +1,55 @@ use failure::format_err; use ffi_helpers::panic::catch_panic; +use schemer::MigratorError; +use secrecy::Secret; + +use std::collections::HashMap; +use std::convert::{TryFrom, TryInto}; use std::ffi::{CStr, CString, OsStr}; +use std::mem::ManuallyDrop; use std::os::raw::c_char; use std::os::unix::ffi::OsStrExt; use std::path::Path; use std::slice; -use std::str::FromStr; + +use zcash_address::{ + self, + unified::{self, Container, Encoding}, + ConversionError, ToAddress, TryFromAddress, ZcashAddress, +}; use zcash_client_backend::{ - address::RecipientAddress, + address::{RecipientAddress, UnifiedAddress}, data_api::{ chain::{scan_cached_blocks, validate_chain}, error::Error, - wallet::{create_spend_to_address, decrypt_and_store_transaction, shield_funds}, - WalletRead, - }, - encoding::{ - decode_extended_full_viewing_key, decode_extended_spending_key, - encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address, - AddressCodec, - }, - keys::{ - derive_public_key_from_seed, derive_secret_key_from_seed, - derive_transparent_address_from_public_key, derive_transparent_address_from_secret_key, - spending_key, Wif, + wallet::{ + create_spend_to_address, decrypt_and_store_transaction, shield_transparent_funds, + }, + WalletRead, WalletWrite, }, - wallet::{AccountId, OvkPolicy, WalletTransparentOutput}, + encoding::{decode_extended_full_viewing_key, decode_extended_spending_key, AddressCodec}, + keys::{DecodingError, Era, UnifiedFullViewingKey, UnifiedSpendingKey}, + wallet::{OvkPolicy, WalletTransparentOutput}, }; +#[allow(deprecated)] +use zcash_client_sqlite::wallet::get_rewind_height; use zcash_client_sqlite::{ error::SqliteClientError, - wallet::{ - delete_utxos_above, get_rewind_height, - init::{init_accounts_table, init_blocks_table, init_wallet_db}, - put_received_transparent_utxo, rewind_to_height, - }, + wallet::init::{init_accounts_table, init_blocks_table, init_wallet_db, WalletMigrationError}, BlockDb, NoteId, WalletDb, }; use zcash_primitives::consensus::Network::{MainNetwork, TestNetwork}; use zcash_primitives::{ block::BlockHash, consensus::{BlockHeight, BranchId, Network, Parameters}, - legacy::TransparentAddress, + legacy::{self, TransparentAddress}, memo::{Memo, MemoBytes}, - transaction::{components::Amount, components::OutPoint, Transaction}, - zip32::ExtendedFullViewingKey, + transaction::{ + components::{Amount, OutPoint, TxOut}, + Transaction, + }, + zip32::AccountId, }; - -use secp256k1::key::{PublicKey, SecretKey}; -use std::convert::{TryFrom, TryInto}; use zcash_proofs::prover::LocalTxProver; const ANCHOR_OFFSET: u32 = 10; @@ -68,22 +71,44 @@ where } } -fn wallet_db( +/// Helper method for construcing a WalletDb value from path data provided over the FFI. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +unsafe fn wallet_db( db_data: *const u8, db_data_len: usize, network: Network, ) -> Result, failure::Error> { - let db_data = Path::new(OsStr::from_bytes(unsafe { - slice::from_raw_parts(db_data, db_data_len) - })); + let db_data = Path::new(OsStr::from_bytes(slice::from_raw_parts( + db_data, + db_data_len, + ))); WalletDb::for_path(db_data, network) .map_err(|e| format_err!("Error opening wallet database connection: {}", e)) } -fn block_db(cache_db: *const u8, cache_db_len: usize) -> Result { - let cache_db = Path::new(OsStr::from_bytes(unsafe { - slice::from_raw_parts(cache_db, cache_db_len) - })); +/// Helper method for construcing a BlockDb value from path data provided over the FFI. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +unsafe fn block_db(cache_db: *const u8, cache_db_len: usize) -> Result { + let cache_db = Path::new(OsStr::from_bytes(slice::from_raw_parts( + cache_db, + cache_db_len, + ))); BlockDb::for_path(cache_db) .map_err(|e| format_err!("Error opening block source database connection: {}", e)) } @@ -95,6 +120,14 @@ pub extern "C" fn zcashlc_last_error_length() -> i32 { } /// Copies the last error message into the provided allocated buffer. +/// +/// # Safety +/// +/// - `buf` must be non-null and valid for reads for `length` bytes, and it must have an alignment +/// of `1`. +/// - The memory referenced by `buf` must not be mutated for the duration of the function call. +/// - The total size `length` must be no larger than `isize::MAX`. See the safety documentation of +/// pointer::offset. #[no_mangle] pub unsafe extern "C" fn zcashlc_error_message_utf8(buf: *mut c_char, length: i32) -> i32 { ffi_helpers::error_handling::error_message_utf8(buf, length) @@ -106,430 +139,390 @@ pub extern "C" fn zcashlc_clear_last_error() { ffi_helpers::error_handling::clear_last_error() } -/// Sets up the internal structure of the data database. -#[no_mangle] -pub extern "C" fn zcashlc_init_data_database( - db_data: *const u8, - db_data_len: usize, - network_id: u32, -) -> i32 { - let res = catch_panic(|| { - let network = parse_network(network_id)?; - let db_data = Path::new(OsStr::from_bytes(unsafe { - slice::from_raw_parts(db_data, db_data_len) - })); - - WalletDb::for_path(db_data, network) - .map(|db| init_wallet_db(&db)) - .map(|_| 1) - .map_err(|e| format_err!("Error while initializing data DB: {}", e)) - }); - unwrap_exc_or_null(res) -} - -/// Initialises the data database with the given number of accounts using the given seed. +/// Sets up the internal structure of the data database. The value for `seed` may be provided as a +/// null pointer if the caller wishes to attempt migrations without providing the wallet's seed +/// value. +/// +/// Returns 0 if successful, 1 if the seed must be provided in order to execute the requested +/// migrations, or -1 otherwise. /// -/// Returns the ExtendedSpendingKeys for the accounts. The caller should store these -/// securely for use while spending. +/// # Safety /// -/// Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it. +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `seed` must be non-null and valid for reads for `seed_len` bytes, and it must have an +/// alignment of `1`. +/// - The memory referenced by `seed` must not be mutated for the duration of the function call. +/// - The total size `seed_len` must be no larger than `isize::MAX`. See the safety documentation +/// of pointer::offset. #[no_mangle] -pub extern "C" fn zcashlc_init_accounts_table( +pub extern "C" fn zcashlc_init_data_database( db_data: *const u8, db_data_len: usize, seed: *const u8, seed_len: usize, - accounts: i32, - capacity_ret: *mut usize, network_id: u32, -) -> *mut *mut c_char { +) -> i32 { let res = catch_panic(|| { let network = parse_network(network_id)?; - let db_data = wallet_db(db_data, db_data_len, network)?; - let seed = unsafe { slice::from_raw_parts(seed, seed_len) }; - let accounts = if accounts >= 0 { - accounts as u32 + let mut db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; + + let seed = if seed.is_null() { + None } else { - return Err(format_err!("accounts argument must be positive")); + Some(Secret::new( + (unsafe { slice::from_raw_parts(seed, seed_len) }).to_vec(), + )) }; - let extsks: Vec<_> = (0..accounts) - .map(|account| spending_key(&seed, network.coin_type(), AccountId(account))) - .collect(); - let extfvks: Vec<_> = extsks.iter().map(ExtendedFullViewingKey::from).collect(); - - let t_addreses: Vec<_> = (0..accounts) - .map(|account| { - let tsk = - derive_secret_key_from_seed(&network, &seed, AccountId(account), 0).unwrap(); - derive_transparent_address_from_secret_key(&tsk) - }) - .collect(); - - init_accounts_table(&db_data, &extfvks, &t_addreses) - .map(|_| { - // Return the ExtendedSpendingKeys for the created accounts. - let mut v: Vec<_> = extsks - .iter() - .map(|extsk| { - let encoded = encode_extended_spending_key( - network.hrp_sapling_extended_spending_key(), - extsk, - ); - CString::new(encoded).unwrap().into_raw() - }) - .collect(); - assert!(v.len() == accounts as usize); - unsafe { *capacity_ret.as_mut().unwrap() = v.capacity() }; - let p = v.as_mut_ptr(); - std::mem::forget(v); - return p; - }) - .map_err(|e| format_err!("Error while initializing accounts: {}", e)) + match init_wallet_db(&mut db_data, seed) { + Ok(_) => Ok(0), + Err(MigratorError::Adapter(WalletMigrationError::SeedRequired)) => Ok(1), + Err(e) => Err(format_err!("Error while initializing data DB: {}", e)), + } }); - unwrap_exc_or_null(res) + unwrap_exc_or(res, -1) } -/// 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_accounts_table_with_keys( - db_data: *const u8, - db_data_len: usize, - uvks: *mut FFIUVKBoxedSlice, - network_id: u32, -) -> bool { - let res = catch_panic(|| { - let network = parse_network(network_id)?; - let db_data = wallet_db(db_data, db_data_len, network)?; - - let s: Box = unsafe { Box::from_raw(uvks) }; - - let slice: &mut [FFIUnifiedViewingKey] = unsafe { slice::from_raw_parts_mut(s.ptr, s.len) }; - - let mut extfvks: Vec = Vec::new(); - let mut t_addreses: Vec = Vec::new(); - - for u in slice.into_iter() { - let vkstr = unsafe { CStr::from_ptr(u.extfvk).to_str().unwrap() }; - let extfvk = decode_extended_full_viewing_key( - network.hrp_sapling_extended_full_viewing_key(), - &vkstr, - ) - .unwrap() - .unwrap(); - extfvks.push(extfvk); - - let extpub_str = unsafe { CStr::from_ptr(u.extpub).to_str().unwrap() }; - let pubkey = PublicKey::from_str(&extpub_str).unwrap(); - let t_addr = derive_transparent_address_from_public_key(&pubkey); +/// A struct that contains an account identifier along with a pointer to the binary encoding +/// of an associated key. +/// +/// # Safety +/// +/// - `encoding` must be non-null and must point to an array of `encoding_len` bytes. +#[repr(C)] +pub struct FFIBinaryKey { + account_id: u32, + encoding: *mut u8, + encoding_len: usize, +} - t_addreses.push(t_addr); +impl FFIBinaryKey { + fn new(account_id: AccountId, key_bytes: Vec) -> Self { + let mut raw_key_bytes = ManuallyDrop::new(key_bytes.into_boxed_slice()); + FFIBinaryKey { + account_id: account_id.into(), + encoding: raw_key_bytes.as_mut_ptr(), + encoding_len: raw_key_bytes.len(), } + } +} - match init_accounts_table(&db_data, &extfvks, &t_addreses) { - Ok(()) => Ok(true), - Err(e) => Err(format_err!("Error while initializing accounts: {}", e)), - } - }); - unwrap_exc_or(res, false) +/// Frees a FFIBinaryKey value +/// +/// # Safety +/// +/// - `ptr` must be non-null and must point to a struct having the layout of [`FFIBinaryKey`]. +/// See the safety documentation of [`FFIBinaryKey`]. +#[no_mangle] +pub unsafe extern "C" fn zcashlc_free_binary_key(ptr: *mut FFIBinaryKey) { + if !ptr.is_null() { + let key: Box = Box::from_raw(ptr); + let key_slice: &mut [u8] = slice::from_raw_parts_mut(key.encoding, key.encoding_len); + drop(Box::from_raw(key_slice)); + } } -/// 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. +/// Adds the next available account-level spend authority, given the current set of [ZIP 316] +/// account identifiers known, to the wallet database. +/// +/// Returns the newly created [ZIP 316] account identifier, along with the binary encoding of the +/// [`UnifiedSpendingKey`] for the newly created account. The caller should manage the memory of +/// (and store) the returned spending keys in a secure fashion. +/// +/// If `seed` was imported from a backup and this method is being used to restore a +/// previous wallet state, you should use this method to add all of the desired +/// accounts before scanning the chain from the seed's birthday height. +/// +/// By convention, wallets should only allow a new account to be generated after funds +/// have been received by the currently available account (in order to enable +/// automated account recovery). +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `seed` must be non-null and valid for reads for `seed_len` bytes, and it must have an +/// alignment of `1`. +/// - The memory referenced by `seed` must not be mutated for the duration of the function call. +/// - The total size `seed_len` must be no larger than `isize::MAX`. See the safety documentation +/// of pointer::offset. +/// - Call [`zcashlc_free_binary_key`] to free the memory associated with the returned pointer when +/// you are finished using it. /// -/// Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it. +/// [ZIP 316]: https://zips.z.cash/zip-0316 #[no_mangle] -pub unsafe extern "C" fn zcashlc_derive_extended_spending_keys( +pub extern "C" fn zcashlc_create_account( + db_data: *const u8, + db_data_len: usize, seed: *const u8, seed_len: usize, - accounts: i32, - capacity_ret: *mut usize, network_id: u32, -) -> *mut *mut c_char { +) -> *mut FFIBinaryKey { let res = catch_panic(|| { let network = parse_network(network_id)?; - let seed = slice::from_raw_parts(seed, seed_len); - let accounts = if accounts > 0 { - accounts as u32 - } else { - return Err(format_err!("accounts argument must be greater than zero")); - }; - - let extsks: Vec<_> = (0..accounts) - .map(|account| spending_key(&seed, network.coin_type(), AccountId(account))) - .collect(); - - // Return the ExtendedSpendingKeys for the created accounts. - let mut v: Vec<_> = extsks - .iter() - .map(|extsk| { - let encoded = encode_extended_spending_key( - network.hrp_sapling_extended_spending_key(), - extsk, - ); - CString::new(encoded).unwrap().into_raw() + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; + let seed = Secret::new((unsafe { slice::from_raw_parts(seed, seed_len) }).to_vec()); + + let mut db_ops = db_data.get_update_ops()?; + db_ops + .create_account(&seed) + .map(|(account, usk)| { + let encoded = usk.to_bytes(Era::Orchard); + Box::into_raw(Box::new(FFIBinaryKey::new(account, encoded))) }) - .collect(); - assert!(v.len() == accounts as usize); - *capacity_ret.as_mut().unwrap() = v.capacity(); - let p = v.as_mut_ptr(); - std::mem::forget(v); - Ok(p) + .map_err(|e| format_err!("Error while initializing accounts: {}", e)) }); unwrap_exc_or_null(res) } +/// A struct that contains an account identifier along with a pointer to the string encoding +/// of an associated key. +/// +/// # Safety +/// +/// - `encoding` must be non-null and must point to a null-terminated UTF-8 string. #[repr(C)] -pub struct FFIUnifiedViewingKey { - extfvk: *const c_char, - extpub: *const c_char, +pub struct FFIEncodedKey { + account_id: u32, + encoding: *mut c_char, } +/// A struct that contains a pointer to, and length information for, a heap-allocated +/// slice of [`FFIEncodedKey`] values. +/// +/// # Safety +/// +/// - `ptr` must be non-null and must be valid for reads for `len * mem::size_of::()` +/// many bytes, and it must be properly aligned. This means in particular: +/// - The entire memory range pointed to by `ptr` must be contained within a single allocated +/// object. Slices can never span across multiple allocated objects. +/// - `ptr` must be non-null and aligned even for zero-length slices. +/// - `ptr` must point to `len` consecutive properly initialized values of type +/// [`FFIEncodedKey`]. +/// - The total size `len * mem::size_of::()` of the slice pointed to +/// by `ptr` must be no larger than isize::MAX. See the safety documentation of pointer::offset. +/// - See the safety documentation of [`FFIEncodedKey`] #[repr(C)] -pub struct FFIUVKBoxedSlice { - ptr: *mut FFIUnifiedViewingKey, +pub struct FFIEncodedKeys { + ptr: *mut FFIEncodedKey, len: usize, // number of elems } -fn unified_viewing_key_new( - extfvk: &ExtendedFullViewingKey, - extpub: &PublicKey, - network: Network, -) -> FFIUnifiedViewingKey { - let encoded_extfvk = - encode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), extfvk); - let encoded_pubkey = hex::encode(&extpub.serialize()); - - FFIUnifiedViewingKey { - extfvk: CString::new(encoded_extfvk).unwrap().into_raw(), - extpub: CString::new(encoded_pubkey).unwrap().into_raw(), +impl FFIEncodedKeys { + pub fn ptr_from_vec(v: Vec) -> *mut Self { + // Going from Vec<_> to Box<[_]> just drops the (extra) `capacity` + let boxed_slice: Box<[FFIEncodedKey]> = v.into_boxed_slice(); + let len = boxed_slice.len(); + let fat_ptr: *mut [FFIEncodedKey] = Box::into_raw(boxed_slice); + // It is guaranteed to be possible to obtain a raw pointer to the start + // of a slice by casting the pointer-to-slice, as documented e.g. at + // . + // TODO: replace with `as_mut_ptr()` when that is stable. + let slim_ptr: *mut FFIEncodedKey = fat_ptr as _; + Box::into_raw(Box::new(FFIEncodedKeys { ptr: slim_ptr, len })) } } -fn uvk_vec_to_ffi(v: Vec) -> *mut FFIUVKBoxedSlice { - // Going from Vec<_> to Box<[_]> just drops the (extra) `capacity` - let boxed_slice: Box<[FFIUnifiedViewingKey]> = v.into_boxed_slice(); - let len = boxed_slice.len(); - let fat_ptr: *mut [FFIUnifiedViewingKey] = Box::into_raw(boxed_slice); - let slim_ptr: *mut FFIUnifiedViewingKey = fat_ptr as _; - Box::into_raw(Box::new(FFIUVKBoxedSlice { ptr: slim_ptr, len })) -} - +/// Frees an array of FFIEncodedKeys values as allocated by `zcashlc_derive_unified_viewing_keys_from_seed` +/// +/// # Safety +/// +/// - `ptr` must be non-null and must point to a struct having the layout of [`FFIEncodedKeys`]. +/// See the safety documentation of [`FFIEncodedKeys`]. #[no_mangle] -pub unsafe extern "C" fn zcashlc_free_uvk_array(uvks: *mut FFIUVKBoxedSlice) { - if uvks.is_null() { - return; - } - let s: Box = Box::from_raw(uvks); +pub unsafe extern "C" fn zcashlc_free_keys(ptr: *mut FFIEncodedKeys) { + if !ptr.is_null() { + let s: Box = Box::from_raw(ptr); - let slice: &mut [FFIUnifiedViewingKey] = slice::from_raw_parts_mut(s.ptr, s.len); - drop(Box::from_raw(slice)); - drop(s); + let slice: &mut [FFIEncodedKey] = slice::from_raw_parts_mut(s.ptr, s.len); + for k in slice.into_iter() { + zcashlc_string_free(k.encoding) + } + drop(s); + } } +/// Initialises the data database with the given set of unified full viewing keys. This +/// should only be used in special cases for implementing wallet recovery; prefer +/// `zcashlc_create_account` for normal account creation purposes. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `ufvks` must be non-null and valid for reads for `ufvks_len * sizeof(FFIEncodedKey)` bytes. +/// It must point to an array of `FFIEncodedKey` values. +/// - The memory referenced by `ufvks` must not be mutated for the duration of the function call. +/// - The total size `ufvks_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. #[no_mangle] -pub unsafe extern "C" fn zcashlc_derive_unified_viewing_keys_from_seed( - seed: *const u8, - seed_len: usize, - accounts: i32, +pub extern "C" fn zcashlc_init_accounts_table_with_keys( + db_data: *const u8, + db_data_len: usize, + ufvks_ptr: *mut FFIEncodedKey, + ufvks_len: usize, network_id: u32, -) -> *mut FFIUVKBoxedSlice { +) -> bool { let res = catch_panic(|| { let network = parse_network(network_id)?; - let seed = slice::from_raw_parts(seed, seed_len); - let accounts = if accounts > 0 { - accounts as u32 - } else { - return Err(format_err!("accounts argument must be greater than zero")); - }; - - let uvks: Vec<_> = (0..accounts) - .map(|account| { - let extfvk = ExtendedFullViewingKey::from(&spending_key( - &seed, - network.coin_type(), - AccountId(account), - )); - let extpub = - derive_public_key_from_seed(&network, &seed, AccountId(account), 0).unwrap(); - unified_viewing_key_new(&extfvk, &extpub, network) + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; + + let encoded_keys: &mut [FFIEncodedKey] = + unsafe { slice::from_raw_parts_mut(ufvks_ptr, ufvks_len) }; + let ufvks: HashMap = encoded_keys + .iter_mut() + .map(|u| { + let ufvkstr = unsafe { CStr::from_ptr(u.encoding).to_str().unwrap() }; + UnifiedFullViewingKey::decode(&network, ufvkstr) + .map(|ufvk| (AccountId::from(u.account_id), ufvk)) }) - .collect(); - Ok(uvk_vec_to_ffi(uvks)) + .collect::, _>>() + .map_err(|e| format_err!("Error decoding unified full viewing keys: {:?}", e))?; + + match init_accounts_table(&db_data, &ufvks) { + Ok(()) => Ok(true), + Err(e) => Err(format_err!("Error while initializing accounts: {}", e)), + } }); - unwrap_exc_or_null(res) + unwrap_exc_or(res, false) } -/// 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 +/// Derives and returns a unified spending key from the given seed for the given account ID. /// -/// Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it. +/// Returns the binary encoding of the spending key. The caller should manage the memory of (and +/// store, if necessary) the returned spending key in a secure fashion. +/// +/// # Safety +/// +/// - `seed` must be non-null and valid for reads for `seed_len` bytes, and it must have an +/// alignment of `1`. +/// - The memory referenced by `seed` must not be mutated for the duration of the function call. +/// - The total size `seed_len` must be no larger than `isize::MAX`. See the safety documentation +/// of pointer::offset. +/// - Call `zcashlc_free_binary_key` to free the memory associated with the returned pointer when +/// you are finished using it. #[no_mangle] -pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_keys( +pub unsafe extern "C" fn zcashlc_derive_spending_key( seed: *const u8, seed_len: usize, - accounts: i32, - capacity_ret: *mut usize, + account: i32, network_id: u32, -) -> *mut *mut c_char { +) -> *mut FFIBinaryKey { let res = catch_panic(|| { let network = parse_network(network_id)?; let seed = slice::from_raw_parts(seed, seed_len); - let accounts = if accounts > 0 { - accounts as u32 + let account = if account >= 0 { + account as u32 } else { - return Err(format_err!("accounts argument must be greater than zero")); + return Err(format_err!("account ID argument must be nonnegative")); }; - let extsks: Vec<_> = (0..accounts) - .map(|account| { - ExtendedFullViewingKey::from(&spending_key( - &seed, - network.coin_type(), - AccountId(account), - )) - }) - .collect(); - - // Return the ExtendedSpendingKeys for the created accounts. - let mut v: Vec<_> = extsks - .iter() - .map(|extsk| { - let encoded = encode_extended_full_viewing_key( - network.hrp_sapling_extended_full_viewing_key(), - extsk, - ); - CString::new(encoded).unwrap().into_raw() + let account = AccountId::from(account); + UnifiedSpendingKey::from_seed(&network, seed, account) + .map_err(|e| format_err!("error generating unified spending key from seed: {:?}", e)) + .map(move |usk| { + let encoded = usk.to_bytes(Era::Orchard); + Box::into_raw(Box::new(FFIBinaryKey::new(account, encoded))) }) - .collect(); - assert!(v.len() == accounts as usize); - *capacity_ret.as_mut().unwrap() = v.capacity(); - let p = v.as_mut_ptr(); - std::mem::forget(v); - Ok(p) - }); - 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, - network_id: u32, -) -> *mut c_char { - let res = catch_panic(|| { - let network = parse_network(network_id)?; - 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, network.coin_type(), AccountId(account_index)) - .default_address() - .unwrap() - .1; - let address_str = encode_payment_address(network.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_transparent_address_from_public_key( - pubkey: *const c_char, - network_id: u32, -) -> *mut c_char { - let res = catch_panic(|| { - let network = parse_network(network_id)?; - let public_key_str = CStr::from_ptr(pubkey).to_str()?; - let pk = PublicKey::from_str(&public_key_str)?; - let taddr = derive_transparent_address_from_public_key(&pk).encode(&network); - - Ok(CString::new(taddr).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, - network_id: u32, -) -> *mut c_char { - let res = catch_panic(|| { - let network = parse_network(network_id)?; - let extfvk_string = CStr::from_ptr(extfvk).to_str()?; - let extfvk = match decode_extended_full_viewing_key( - network.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(network.hrp_sapling_payment_address(), &address); - Ok(CString::new(address_str).unwrap().into_raw()) - }); - unwrap_exc_or_null(res) +/// A private utility function to reduce duplication across functions that take an USK +/// across the FFI. `usk_ptr` should point to an array of `usk_len` bytes containing +/// a unified spending key encoded as returned from the `zcashlc_create_account` or +/// `zcashlc_derive_spending_key` functions. Callers should reproduce the following +/// safety documentation. +/// +/// # Safety +/// +/// - `usk_ptr` must be non-null and must point to an array of `usk_len` bytes. +/// - The memory referenced by `usk_ptr` must not be mutated for the duration of the function call. +/// - The total size `usk_len` must be no larger than `isize::MAX`. See the safety documentation +/// of pointer::offset. +unsafe fn decode_usk( + usk_ptr: *const u8, + usk_len: usize, +) -> Result { + let usk_bytes = unsafe { slice::from_raw_parts(usk_ptr, usk_len) }; + + // The remainder of the function is safe. + UnifiedSpendingKey::from_bytes(Era::Orchard, usk_bytes).map_err(|e| match e { + DecodingError::EraMismatch(era) => format_err!( + "Spending key was from era {:?}, but {:?} was expected.", + era, + Era::Orchard + ), + e => format_err!( + "An error occurred decoding the provided unified spending key: {:?}", + e + ), + }) } -/// derives a shielded address from the given extended full viewing key. -/// call zcashlc_string_free with the returned pointer when done using it +/// Obtains the unified full viewing key for the given binary-encoded unified spending key +/// and returns the resulting encoded UFVK string. `usk_ptr` should point to an array of `usk_len` +/// bytes containing a unified spending key encoded as returned from the `zcashlc_create_account` +/// or `zcashlc_derive_spending_key` functions. +/// +/// # Safety +/// +/// - `usk_ptr` must be non-null and must point to an array of `usk_len` bytes. +/// - The memory referenced by `usk_ptr` must not be mutated for the duration of the function call. +/// - The total size `usk_len` must be no larger than `isize::MAX`. See the safety documentation +/// of pointer::offset. +/// - Call [`zcashlc_string_free`] to free the memory associated with the returned pointer +/// when you are done using it. #[no_mangle] -pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_key( - extsk: *const c_char, +pub extern "C" fn zcashlc_spending_key_to_full_viewing_key( + usk_ptr: *const u8, + usk_len: usize, network_id: u32, ) -> *mut c_char { let res = catch_panic(|| { let network = parse_network(network_id)?; - let extsk = CStr::from_ptr(extsk).to_str()?; - let extfvk = match decode_extended_spending_key( - network.hrp_sapling_extended_spending_key(), - &extsk, - ) { - Ok(Some(extsk)) => ExtendedFullViewingKey::from(&extsk), - Ok(None) => { - return Err(format_err!("Deriving viewing key from spending key returned no results. Encoding was valid but type was incorrect.")); - } - Err(e) => { - return Err(format_err!( - "Error while deriving viewing key from spending key: {}", - e - )); - } - }; - - let encoded = encode_extended_full_viewing_key( - network.hrp_sapling_extended_full_viewing_key(), - &extfvk, - ); - - Ok(CString::new(encoded).unwrap().into_raw()) + unsafe { decode_usk(usk_ptr, usk_len) }.map(|usk| { + let ufvk = usk.to_unified_full_viewing_key(); + CString::new(ufvk.encode(&network)).unwrap().into_raw() + }) }); unwrap_exc_or_null(res) } -/// Initialises the data database with the given block. +/// Initialises the data database with the given block metadata. /// /// This enables a newly-created database to be immediately-usable, without needing to /// synchronise historic blocks. +/// +/// The string represented by `sapling_tree_hex` should contain the encoded byte representation +/// of a Sapling commitment tree. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `hash_hex` must be non-null and must point to a null-terminated UTF-8 string. +/// - The memory referenced by `hash_hex` must not be mutated for the duration of the function call. +/// - `sapling_tree_hex` must be non-null and must point to a null-terminated UTF-8 string. +/// - The memory referenced by `sapling_tree_hex` must not be mutated for the duration of the +/// function call. #[no_mangle] pub extern "C" fn zcashlc_init_blocks_table( db_data: *const u8, @@ -542,7 +535,7 @@ pub extern "C" fn zcashlc_init_blocks_table( ) -> i32 { let res = catch_panic(|| { let network = parse_network(network_id)?; - let db_data = wallet_db(db_data, db_data_len, network)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; let hash = { let mut hash = hex::decode(unsafe { CStr::from_ptr(hash_hex) }.to_str()?).unwrap(); hash.reverse(); @@ -565,11 +558,20 @@ pub extern "C" fn zcashlc_init_blocks_table( unwrap_exc_or_null(res) } -/// Returns the address for the account. +/// Returns the most-recently-generated unified payment address for the specified account. /// -/// Call `zcashlc_string_free` on the returned pointer when you are finished with it. +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - Call [`zcashlc_string_free`] to free the memory associated with the returned pointer +/// when done using it. #[no_mangle] -pub extern "C" fn zcashlc_get_address( +pub extern "C" fn zcashlc_get_current_address( db_data: *const u8, db_data_len: usize, account: i32, @@ -577,20 +579,19 @@ pub extern "C" fn zcashlc_get_address( ) -> *mut c_char { let res = catch_panic(|| { let network = parse_network(network_id)?; - let db_data = wallet_db(db_data, db_data_len, network)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; let account = if account >= 0 { account as u32 } else { return Err(format_err!("accounts argument must be positive")); }; - let account = AccountId(account); + let account = AccountId::from(account); - match (&db_data).get_address(account) { - Ok(Some(addr)) => { - let addr_str = encode_payment_address(network.hrp_sapling_payment_address(), &addr); - let c_str_addr = CString::new(addr_str).unwrap(); - Ok(c_str_addr.into_raw()) + match db_data.get_current_address(account) { + Ok(Some(ua)) => { + let address_str = ua.encode(&network); + Ok(CString::new(address_str).unwrap().into_raw()) } Ok(None) => Err(format_err!( "No payment address was available for account {:?}", @@ -602,78 +603,549 @@ pub extern "C" fn zcashlc_get_address( unwrap_exc_or_null(res) } -/// Returns true when the address is valid and shielded. -/// Returns false in any other case -/// Errors when the provided address belongs to another network +/// Returns a newly-generated unified payment address for the specified account, with the next +/// available diversifier. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - Call [`zcashlc_string_free`] to free the memory associated with the returned pointer +/// when done using it. #[no_mangle] -pub unsafe extern "C" fn zcashlc_is_valid_shielded_address( +pub extern "C" fn zcashlc_get_next_available_address( + db_data: *const u8, + db_data_len: usize, + account: i32, + network_id: u32, +) -> *mut c_char { + let res = catch_panic(|| { + let network = parse_network(network_id)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; + let mut db_ops = db_data.get_update_ops()?; + let account = if account >= 0 { + account as u32 + } else { + return Err(format_err!("Account id must be nonnegative.")); + }; + + let account = AccountId::from(account); + + match db_ops.get_next_available_address(account) { + Ok(Some(ua)) => { + let address_str = ua.encode(&network); + Ok(CString::new(address_str).unwrap().into_raw()) + } + Ok(None) => Err(format_err!( + "No payment address was available for account {:?}", + account + )), + Err(e) => Err(format_err!("Error while fetching address: {}", e)), + } + }); + unwrap_exc_or_null(res) +} + +/// Returns a list of the transparent receivers for the diversified unified addresses that have +/// been allocated for the provided account. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - Call [`zcashlc_free_keys`] to free the memory associated with the returned pointer +/// when done using it. +#[no_mangle] +pub extern "C" fn zcashlc_list_transparent_receivers( + db_data: *const u8, + db_data_len: usize, + account_id: i32, + network_id: u32, +) -> *mut FFIEncodedKeys { + let res = catch_panic(|| { + let network = parse_network(network_id)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; + let account_id = if account_id >= 0 { + account_id as u32 + } else { + return Err(format_err!("Account id must be nonnegative.")); + }; + + let account = AccountId::from(account_id); + match db_data.get_transparent_receivers(account) { + Ok(receivers) => { + let keys = receivers + .iter() + .map(|(receiver, _)| { + let address_str = receiver.encode(&network); + FFIEncodedKey { + account_id, + encoding: CString::new(address_str).unwrap().into_raw(), + } + }) + .collect::>(); + + Ok(FFIEncodedKeys::ptr_from_vec(keys)) + } + Err(e) => Err(format_err!("Error while fetching address: {}", e)), + } + }); + unwrap_exc_or_null(res) +} + +/// Extracts the typecodes of the receivers within the given Unified Address. +/// +/// Returns a pointer to a slice of typecodes. `len_ret` is set to the length of the +/// slice. +/// +/// See the following sections of ZIP 316 for details on how to interpret typecodes: +/// - [List of known typecodes](https://zips.z.cash/zip-0316#encoding-of-unified-addresses) +/// - [Adding new types](https://zips.z.cash/zip-0316#adding-new-types) +/// - [Metadata Items](https://zips.z.cash/zip-0316#metadata-items) +/// +/// # Safety +/// +/// - `ua` must be non-null and must point to a null-terminated UTF-8 string containing an +/// encoded Unified Address. +/// - Call [`zcashlc_free_typecodes`] to free the memory associated with the returned +/// pointer when done using it. +#[no_mangle] +pub extern "C" fn zcashlc_get_typecodes_for_unified_address_receivers( + ua: *const c_char, + len_ret: *mut usize, +) -> *mut u32 { + let res = catch_panic(|| { + let ua_str = unsafe { CStr::from_ptr(ua).to_str()? }; + + let (_, ua) = unified::Address::decode(ua_str) + .map_err(|e| format_err!("Invalid Unified Address: {}", e))?; + + let typecodes = ua + .items() + .into_iter() + .map(|receiver| match receiver { + unified::Receiver::P2pkh(_) => unified::Typecode::P2pkh, + unified::Receiver::P2sh(_) => unified::Typecode::P2sh, + unified::Receiver::Sapling(_) => unified::Typecode::Sapling, + unified::Receiver::Orchard(_) => unified::Typecode::Orchard, + unified::Receiver::Unknown { typecode, .. } => unified::Typecode::Unknown(typecode), + }) + .map(u32::from) + .collect::>(); + + let mut typecodes = ManuallyDrop::new(typecodes.into_boxed_slice()); + let (ptr, len) = (typecodes.as_mut_ptr(), typecodes.len()); + + unsafe { *len_ret = len }; + Ok(ptr) + }); + unwrap_exc_or_null(res) +} + +/// Frees a list of typecodes previously obtained from the FFI. +/// +/// # Safety +/// +/// - `data` and `len` must have been obtained from +/// [`zcashlc_get_typecodes_for_unified_address_receivers`]. +#[no_mangle] +pub unsafe extern "C" fn zcashlc_free_typecodes(data: *mut u32, len: usize) { + if !data.is_null() { + let s = Box::from_raw(slice::from_raw_parts_mut(data, len)); + drop(s); + } +} + +struct UnifiedAddressParser(UnifiedAddress); + +impl zcash_address::TryFromRawAddress for UnifiedAddressParser { + type Error = failure::Error; + + fn try_from_raw_unified( + data: zcash_address::unified::Address, + ) -> Result> { + data.try_into() + .map(UnifiedAddressParser) + .map_err(|e| format_err!("Invalid Unified Address: {}", e).into()) + } +} + +/// Returns the transparent receiver within the given Unified Address, if any. +/// +/// # Safety +/// +/// - `ua` must be non-null and must point to a null-terminated UTF-8 string. +/// - Call [`zcashlc_string_free`] to free the memory associated with the returned pointer +/// when done using it. +#[no_mangle] +pub extern "C" fn zcashlc_get_transparent_receiver_for_unified_address( + ua: *const c_char, +) -> *mut c_char { + let res = catch_panic(|| { + let ua_str = unsafe { CStr::from_ptr(ua).to_str()? }; + + let (network, ua) = match ZcashAddress::try_from_encoded(ua_str) { + Ok(addr) => addr + .convert::<(_, UnifiedAddressParser)>() + .map_err(|e| format_err!("Not a Unified Address: {}", e)), + Err(e) => return Err(format_err!("Invalid Zcash address: {}", e)), + }?; + + if let Some(taddr) = ua.0.transparent() { + let taddr = match taddr { + TransparentAddress::PublicKey(data) => { + ZcashAddress::from_transparent_p2pkh(network, *data) + } + TransparentAddress::Script(data) => { + ZcashAddress::from_transparent_p2sh(network, *data) + } + }; + + Ok(CString::new(taddr.encode())?.into_raw()) + } else { + Err(format_err!( + "Unified Address doesn't contain a transparent receiver" + )) + } + }); + unwrap_exc_or_null(res) +} + +/// Returns the Sapling receiver within the given Unified Address, if any. +/// +/// # Safety +/// +/// - `ua` must be non-null and must point to a null-terminated UTF-8 string. +/// - Call [`zcashlc_string_free`] to free the memory associated with the returned pointer +/// when done using it. +#[no_mangle] +pub extern "C" fn zcashlc_get_sapling_receiver_for_unified_address( + ua: *const c_char, +) -> *mut c_char { + let res = catch_panic(|| { + let ua_str = unsafe { CStr::from_ptr(ua).to_str()? }; + + let (network, ua) = match ZcashAddress::try_from_encoded(ua_str) { + Ok(addr) => addr + .convert::<(_, UnifiedAddressParser)>() + .map_err(|e| format_err!("Not a Unified Address: {}", e)), + Err(e) => return Err(format_err!("Invalid Zcash address: {}", e)), + }?; + + if let Some(addr) = ua.0.sapling() { + Ok( + CString::new(ZcashAddress::from_sapling(network, addr.to_bytes()).encode())? + .into_raw(), + ) + } else { + Err(format_err!( + "Unified Address doesn't contain a Sapling receiver" + )) + } + }); + unwrap_exc_or_null(res) +} + +/// Returns true when the provided address decodes to a valid Sapling payment address for the +/// specified network, false in any other case. +/// +/// # Safety +/// +/// - `address` must be non-null and must point to a null-terminated UTF-8 string. +/// - The memory referenced by `address` must not be mutated for the duration of the function call. +#[no_mangle] +pub extern "C" fn zcashlc_is_valid_shielded_address( address: *const c_char, network_id: u32, ) -> bool { let res = catch_panic(|| { let network = parse_network(network_id)?; - let addr = CStr::from_ptr(address).to_str()?; - Ok(is_valid_shielded_address(&addr, &network)) + let addr = unsafe { CStr::from_ptr(address).to_str()? }; + Ok(is_valid_shielded_address(addr, &network)) }); unwrap_exc_or(res, false) } fn is_valid_shielded_address(address: &str, network: &Network) -> bool { - match RecipientAddress::decode(network, &address) { + match RecipientAddress::decode(network, address) { Some(addr) => match addr { RecipientAddress::Shielded(_) => true, - RecipientAddress::Transparent(_) => false, + RecipientAddress::Transparent(_) | RecipientAddress::Unified(_) => false, }, None => false, } } -/// Returns true when the address is valid and transparent. -/// Returns false in any other case +enum AddressType { + Sprout, + P2pkh, + P2sh, + Sapling, + Unified, +} + +struct AddressMetadata { + network: zcash_address::Network, + addr_type: AddressType, +} + +#[derive(Debug)] +enum Void {} + +impl TryFromAddress for AddressMetadata { + /// This instance produces no errors. + type Error = Void; + + fn try_from_sprout( + network: zcash_address::Network, + _data: [u8; 64], + ) -> Result> { + Ok(AddressMetadata { + network, + addr_type: AddressType::Sprout, + }) + } + + fn try_from_sapling( + network: zcash_address::Network, + _data: [u8; 43], + ) -> Result> { + Ok(AddressMetadata { + network, + addr_type: AddressType::Sapling, + }) + } + + fn try_from_unified( + network: zcash_address::Network, + _data: unified::Address, + ) -> Result> { + Ok(AddressMetadata { + network, + addr_type: AddressType::Unified, + }) + } + + fn try_from_transparent_p2pkh( + network: zcash_address::Network, + _data: [u8; 20], + ) -> Result> { + Ok(AddressMetadata { + network, + addr_type: AddressType::P2pkh, + }) + } + + fn try_from_transparent_p2sh( + network: zcash_address::Network, + _data: [u8; 20], + ) -> Result> { + Ok(AddressMetadata { + network, + addr_type: AddressType::P2sh, + }) + } +} + +/// Returns the network type and address kind for the given address string, +/// if the address is a valid Zcash address. +/// +/// Address kind codes are as follows: +/// * p2pkh: 0 +/// * p2sh: 1 +/// * sapling: 2 +/// * unified: 3 +/// +/// # Safety +/// +/// - `address` must be non-null and must point to a null-terminated UTF-8 string. +/// - The memory referenced by `address` must not be mutated for the duration of the function call. +#[no_mangle] +pub extern "C" fn zcashlc_get_address_metadata( + address: *const c_char, + network_id_ret: *mut u32, + addr_kind_ret: *mut u32, +) -> bool { + let res = catch_panic(|| { + let addr = unsafe { CStr::from_ptr(address).to_str()? }; + let zaddr = ZcashAddress::try_from_encoded(addr)?; + + // The following .unwrap is safe because address type detection + // cannot fail for valid ZcashAddress values. + let addr_meta: AddressMetadata = zaddr.convert().unwrap(); + unsafe { + *network_id_ret = match addr_meta.network { + zcash_address::Network::Main => 1, + zcash_address::Network::Test => 0, + zcash_address::Network::Regtest => { + return Err(format_err!("Regtest addresses are not supported.")); + } + }; + + *addr_kind_ret = match addr_meta.addr_type { + AddressType::P2pkh => 0, + AddressType::P2sh => 1, + AddressType::Sapling => 2, + AddressType::Unified => 3, + AddressType::Sprout => { + return Err(format_err!("Sprout addresses are not supported.")); + } + }; + } + + Ok(true) + }); + unwrap_exc_or(res, false) +} + +/// Returns true when the address is a valid transparent payment address for the specified network, +/// false in any other case. +/// +/// # Safety +/// +/// - `address` must be non-null and must point to a null-terminated UTF-8 string. +/// - The memory referenced by `address` must not be mutated for the duration of the function call. #[no_mangle] -pub unsafe extern "C" fn zcashlc_is_valid_transparent_address( +pub extern "C" fn zcashlc_is_valid_transparent_address( address: *const c_char, network_id: u32, ) -> bool { let res = catch_panic(|| { let network = parse_network(network_id)?; - let addr = CStr::from_ptr(address).to_str()?; - Ok(is_valid_transparent_address(&addr, &network)) + let addr = unsafe { CStr::from_ptr(address).to_str()? }; + Ok(is_valid_transparent_address(addr, &network)) }); unwrap_exc_or(res, false) } -/// returns whether the given viewing key is valid or not + +fn is_valid_transparent_address(address: &str, network: &Network) -> bool { + match RecipientAddress::decode(network, address) { + Some(addr) => match addr { + RecipientAddress::Shielded(_) | RecipientAddress::Unified(_) => false, + RecipientAddress::Transparent(_) => true, + }, + None => false, + } +} + +/// Returns true when the provided key decodes to a valid Sapling extended spending key for the +/// specified network, false in any other case. +/// +/// # Safety +/// +/// - `extsk` must be non-null and must point to a null-terminated UTF-8 string. +/// - The memory referenced by `extsk` must not be mutated for the duration of the function call. #[no_mangle] -pub unsafe extern "C" fn zcashlc_is_valid_viewing_key(key: *const c_char, network_id: u32) -> bool { +pub extern "C" fn zcashlc_is_valid_sapling_extended_spending_key( + extsk: *const c_char, + network_id: u32, +) -> bool { let res = catch_panic(|| { let network = parse_network(network_id)?; - let vkstr = CStr::from_ptr(key).to_str()?; + let extsk = unsafe { CStr::from_ptr(extsk).to_str()? }; - match decode_extended_full_viewing_key( - &network.hrp_sapling_extended_full_viewing_key(), - &vkstr, - ) { - Ok(s) => match s { - None => Ok(false), - _ => Ok(true), - }, - Err(_) => Ok(false), - } + Ok( + decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), extsk) + .is_ok(), + ) }); unwrap_exc_or(res, false) } -fn is_valid_transparent_address(address: &str, network: &Network) -> bool { - match RecipientAddress::decode(network, &address) { +/// Returns true when the provided key decodes to a valid Sapling extended full viewing key for the +/// specified network, false in any other case. +/// +/// # Safety +/// +/// - `key` must be non-null and must point to a null-terminated UTF-8 string. +/// - The memory referenced by `key` must not be mutated for the duration of the function call. +#[no_mangle] +pub extern "C" fn zcashlc_is_valid_viewing_key(key: *const c_char, network_id: u32) -> bool { + let res = + catch_panic(|| { + let network = parse_network(network_id)?; + let vkstr = unsafe { CStr::from_ptr(key).to_str()? }; + + Ok(decode_extended_full_viewing_key( + network.hrp_sapling_extended_full_viewing_key(), + vkstr, + ) + .is_ok()) + }); + unwrap_exc_or(res, false) +} + +/// Returns true when the provided key decodes to a valid unified full viewing key for the +/// specified network, false in any other case. +/// +/// # Safety +/// +/// - `ufvk` must be non-null and must point to a null-terminated UTF-8 string. +/// - The memory referenced by `ufvk` must not be mutated for the duration of the +/// function call. +#[no_mangle] +pub extern "C" fn zcashlc_is_valid_unified_full_viewing_key( + ufvk: *const c_char, + network_id: u32, +) -> bool { + let res = catch_panic(|| { + let network = parse_network(network_id)?; + let ufvkstr = unsafe { CStr::from_ptr(ufvk).to_str()? }; + + Ok(UnifiedFullViewingKey::decode(&network, ufvkstr).is_ok()) + }); + unwrap_exc_or(res, false) +} + +/// Returns true when the provided key decodes to a valid unified address for the +/// specified network, false in any other case. +/// +/// # Safety +/// +/// - `address` must be non-null and must point to a null-terminated UTF-8 string. +/// - The memory referenced by `address` must not be mutated for the duration of the +/// function call. +#[no_mangle] +pub extern "C" fn zcashlc_is_valid_unified_address( + address: *const c_char, + network_id: u32, +) -> bool { + let res = catch_panic(|| { + let network = parse_network(network_id)?; + let addr = unsafe { CStr::from_ptr(address).to_str()? }; + Ok(is_valid_unified_address(addr, &network)) + }); + unwrap_exc_or(res, false) +} + +fn is_valid_unified_address(address: &str, network: &Network) -> bool { + match RecipientAddress::decode(network, address) { Some(addr) => match addr { - RecipientAddress::Shielded(_) => false, - RecipientAddress::Transparent(_) => true, + RecipientAddress::Unified(_) => true, + RecipientAddress::Shielded(_) | RecipientAddress::Transparent(_) => false, }, None => false, } } -/// Returns the balance for the account, including all unspent notes that we know about. +/// Returns the balance for the specified account, including all unspent notes that we know about. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. #[no_mangle] pub extern "C" fn zcashlc_get_balance( db_data: *const u8, @@ -683,20 +1155,20 @@ pub extern "C" fn zcashlc_get_balance( ) -> i64 { let res = catch_panic(|| { let network = parse_network(network_id)?; - let db_data = wallet_db(db_data, db_data_len, network)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; if account >= 0 { let (_, max_height) = (&db_data) .block_height_extrema() .map_err(|e| format_err!("Error while fetching max block height: {}", e)) .and_then(|opt| { - opt.ok_or(format_err!( - "No blockchain information available; scan required." - )) + opt.ok_or_else(|| { + format_err!("No blockchain information available; scan required.") + }) })?; (&db_data) - .get_balance_at(AccountId(account as u32), max_height) + .get_balance_at(AccountId::from(account as u32), max_height) .map(|b| b.into()) .map_err(|e| format_err!("Error while fetching balance: {}", e)) } else { @@ -707,29 +1179,39 @@ pub extern "C" fn zcashlc_get_balance( } /// Returns the verified balance for the account, which ignores notes that have been -/// received too recently and are not yet deemed spendable. +/// received too recently and are not yet deemed spendable according to `min_confirmations`. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. #[no_mangle] pub extern "C" fn zcashlc_get_verified_balance( db_data: *const u8, db_data_len: usize, account: i32, network_id: u32, + min_confirmations: u32, ) -> i64 { let res = catch_panic(|| { let network = parse_network(network_id)?; - let db_data = wallet_db(db_data, db_data_len, network)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; if account >= 0 { (&db_data) - .get_target_and_anchor_heights() + .get_target_and_anchor_heights(min_confirmations) .map_err(|e| format_err!("Error while fetching anchor height: {}", e)) .and_then(|opt_anchor| { opt_anchor .map(|(_, a)| a) - .ok_or(format_err!("Anchor height not available; scan required.")) + .ok_or_else(|| format_err!("Anchor height not available; scan required.")) }) .and_then(|anchor| { (&db_data) - .get_balance_at(AccountId(account as u32), anchor) + .get_balance_at(AccountId::from(account as u32), anchor) .map_err(|e| format_err!("Error while fetching verified balance: {}", e)) }) .map(|amount| amount.into()) @@ -740,46 +1222,143 @@ pub extern "C" fn zcashlc_get_verified_balance( unwrap_exc_or(res, -1) } -/// Returns the verified transparent balance for the address, which ignores utxos that have been -/// received too recently and are not yet deemed spendable. +/// Returns the verified transparent balance for `address`, which ignores utxos that have been +/// received too recently and are not yet deemed spendable according to `min_confirmations`. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `address` must be non-null and must point to a null-terminated UTF-8 string. +/// - The memory referenced by `address` must not be mutated for the duration of the function call. #[no_mangle] pub extern "C" fn zcashlc_get_verified_transparent_balance( db_data: *const u8, db_data_len: usize, address: *const c_char, network_id: u32, + min_confirmations: u32, ) -> i64 { let res = catch_panic(|| { let network = parse_network(network_id)?; - let db_data = wallet_db(db_data, db_data_len, network)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; let addr = unsafe { CStr::from_ptr(address).to_str()? }; - let taddr = TransparentAddress::decode(&network, &addr).unwrap(); + let taddr = TransparentAddress::decode(&network, addr).unwrap(); let amount = (&db_data) - .get_target_and_anchor_heights() + .get_target_and_anchor_heights(min_confirmations) .map_err(|e| format_err!("Error while fetching anchor height: {}", e)) .and_then(|opt_anchor| { opt_anchor - .map(|(h, _)| h) - .ok_or(format_err!("height not available; scan required.")) + .map(|(_, a)| a) + .ok_or_else(|| format_err!("height not available; scan required.")) }) .and_then(|anchor| { (&db_data) - .get_unspent_transparent_utxos(&taddr, anchor) + .get_unspent_transparent_outputs(&taddr, anchor) .map_err(|e| { format_err!("Error while fetching verified transparent balance: {}", e) }) })? .iter() - .map(|utxo| utxo.value) - .sum::(); + .map(|utxo| utxo.txout().value) + .sum::>() + .ok_or_else(|| format_err!("Balance overflowed MAX_MONEY."))?; + + Ok(amount.into()) + }); + unwrap_exc_or(res, -1) +} + +/// Returns the verified transparent balance for `account`, which ignores utxos that have been +/// received too recently and are not yet deemed spendable according to `min_confirmations`. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `address` must be non-null and must point to a null-terminated UTF-8 string. +/// - The memory referenced by `address` must not be mutated for the duration of the function call. +#[no_mangle] +pub extern "C" fn zcashlc_get_verified_transparent_balance_for_account( + db_data: *const u8, + db_data_len: usize, + network_id: u32, + account: i32, + min_confirmations: u32, +) -> i64 { + let res = catch_panic(|| { + let network = parse_network(network_id)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; + let account = if account >= 0 { + AccountId::from(account as u32) + } else { + return Err(format_err!("account argument must be positive")); + }; + let amount = (&db_data) + .get_target_and_anchor_heights(min_confirmations) + .map_err(|e| format_err!("Error while fetching anchor height: {}", e)) + .and_then(|opt_anchor| { + opt_anchor + .map(|(_, a)| a) + .ok_or_else(|| format_err!("height not available; scan required.")) + }) + .and_then(|anchor| { + db_data + .get_transparent_receivers(account) + .map_err(|e| { + format_err!( + "Error while fetching transparent receivers for {:?}: {}", + account, + e + ) + }) + .and_then(|receivers| { + receivers + .iter() + .map(|(taddr, _)| { + db_data + .get_unspent_transparent_outputs(&taddr, anchor) + .map_err(|e| { + format_err!( + "Error while fetching verified transparent balance: {}", + e + ) + }) + }) + .collect::, _>>() + }) + })? + .iter() + .flatten() + .map(|utxo| utxo.txout().value) + .sum::>() + .ok_or_else(|| format_err!("Balance overflowed MAX_MONEY."))?; Ok(amount.into()) }); unwrap_exc_or(res, -1) } -/// Returns the verified transparent balance for the address, which ignores utxos that have been -/// received too recently and are not yet deemed spendable. +/// Returns the balance for `address`, including all UTXOs that we know about. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `address` must be non-null and must point to a null-terminated UTF-8 string. +/// - The memory referenced by `address` must not be mutated for the duration of the function call. #[no_mangle] pub extern "C" fn zcashlc_get_total_transparent_balance( db_data: *const u8, @@ -789,27 +1368,84 @@ pub extern "C" fn zcashlc_get_total_transparent_balance( ) -> i64 { let res = catch_panic(|| { let network = parse_network(network_id)?; - let db_data = wallet_db(db_data, db_data_len, network)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; let addr = unsafe { CStr::from_ptr(address).to_str()? }; - let taddr = TransparentAddress::decode(&network, &addr).unwrap(); + let taddr = TransparentAddress::decode(&network, addr).unwrap(); let amount = (&db_data) - .get_target_and_anchor_heights() + .get_target_and_anchor_heights(0u32) .map_err(|e| format_err!("Error while fetching anchor height: {}", e)) .and_then(|opt_anchor| { opt_anchor - .map(|(h, _)| h) - .ok_or(format_err!("height not available; scan required.")) + .map(|(_, a)| a) + .ok_or_else(|| format_err!("height not available; scan required.")) }) .and_then(|anchor| { (&db_data) - .get_unspent_transparent_utxos(&taddr, anchor) + .get_unspent_transparent_outputs(&taddr, anchor) .map_err(|e| { format_err!("Error while fetching total transparent balance: {}", e) }) })? .iter() - .map(|utxo| utxo.value) - .sum::(); + .map(|utxo| utxo.txout().value) + .sum::>() + .ok_or_else(|| format_err!("Balance overflowed MAX_MONEY."))?; + + Ok(amount.into()) + }); + unwrap_exc_or(res, -1) +} + +/// Returns the balance for `account`, including all UTXOs that we know about. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `address` must be non-null and must point to a null-terminated UTF-8 string. +/// - The memory referenced by `address` must not be mutated for the duration of the function call. +#[no_mangle] +pub extern "C" fn zcashlc_get_total_transparent_balance_for_account( + db_data: *const u8, + db_data_len: usize, + network_id: u32, + account: i32, +) -> i64 { + let res = catch_panic(|| { + let network = parse_network(network_id)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; + let account = if account >= 0 { + AccountId::from(account as u32) + } else { + return Err(format_err!("account argument must be positive")); + }; + let amount = (&db_data) + .get_target_and_anchor_heights(0u32) + .map_err(|e| format_err!("Error while fetching anchor height: {}", e)) + .and_then(|opt_anchor| { + opt_anchor + .map(|(_, a)| a) + .ok_or_else(|| format_err!("height not available; scan required.")) + }) + .and_then(|anchor| { + db_data + .get_transparent_balances(account, anchor) + .map_err(|e| { + format_err!( + "Error while fetching transparent balances for {:?}: {}", + account, + e + ) + }) + })? + .iter() + .map(|(_, value)| value) + .sum::>() + .ok_or_else(|| format_err!("Balance overflowed MAX_MONEY."))?; Ok(amount.into()) }); @@ -821,7 +1457,16 @@ pub extern "C" fn zcashlc_get_total_transparent_balance( /// The note is identified by its row index in the `received_notes` table within the data /// database. /// -/// Call `zcashlc_string_free` on the returned pointer when you are finished with it. +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - Call [`zcashlc_string_free`] to free the memory associated with the returned pointer +/// when done using it. #[no_mangle] pub extern "C" fn zcashlc_get_received_memo_as_utf8( db_data: *const u8, @@ -831,7 +1476,7 @@ pub extern "C" fn zcashlc_get_received_memo_as_utf8( ) -> *mut c_char { let res = catch_panic(|| { let network = parse_network(network_id)?; - let db_data = wallet_db(db_data, db_data_len, network)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; let memo = (&db_data) .get_memo(NoteId::ReceivedNoteId(id_note)) @@ -847,12 +1492,87 @@ pub extern "C" fn zcashlc_get_received_memo_as_utf8( unwrap_exc_or_null(res) } +/// Returns the memo for a received note by copying the corresponding bytes to the received +/// pointer in `memo_bytes_ret`. +/// +/// The note is identified by its row index in the `received_notes` table within the data +/// database. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `memo_bytes_ret` must be non-null and must point to an allocated 512-byte region of memory. +#[no_mangle] +pub extern "C" fn zcashlc_get_received_memo( + db_data: *const u8, + db_data_len: usize, + id_note: i64, + memo_bytes_ret: *mut u8, + network_id: u32, +) -> bool { + zcashlc_get_memo( + db_data, + db_data_len, + NoteId::ReceivedNoteId(id_note), + memo_bytes_ret, + network_id, + ) +} + +/// Returns the memo for a note by copying the corresponding bytes to the received +/// pointer in `memo_bytes_ret`. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `memo_bytes_ret` must be non-null and must point to an allocated 512-byte region of memory. +fn zcashlc_get_memo( + db_data: *const u8, + db_data_len: usize, + note_id: NoteId, + memo_bytes_ret: *mut u8, + network_id: u32, +) -> bool { + let res = catch_panic(|| { + let network = parse_network(network_id)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; + + let memo_bytes = (&db_data) + .get_memo(note_id) + .map_err(|e| format_err!("An error occurred retrieving the memo, {}", e)) + .map(|memo| memo.encode())?; + + unsafe { memo_bytes_ret.copy_from(memo_bytes.as_slice().as_ptr(), 512) }; + Ok(true) + }); + unwrap_exc_or(res, false) +} + /// Returns the memo for a sent note, if it is known and a valid UTF-8 string. /// /// The note is identified by its row index in the `sent_notes` table within the data /// database. /// -/// Call `zcashlc_string_free` on the returned pointer when you are finished with it. +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - Call [`zcashlc_string_free`] to free the memory associated with the returned pointer +/// when done using it. #[no_mangle] pub extern "C" fn zcashlc_get_sent_memo_as_utf8( db_data: *const u8, @@ -862,7 +1582,7 @@ pub extern "C" fn zcashlc_get_sent_memo_as_utf8( ) -> *mut c_char { let res = catch_panic(|| { let network = parse_network(network_id)?; - let db_data = wallet_db(db_data, db_data_len, network)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; let memo = (&db_data) .get_memo(NoteId::SentNoteId(id_note)) @@ -878,6 +1598,38 @@ pub extern "C" fn zcashlc_get_sent_memo_as_utf8( unwrap_exc_or_null(res) } +/// Returns the memo for a sent note, by copying the corresponding bytes to the received +/// pointer in `memo_bytes_ret`. +/// +/// The note is identified by its row index in the `sent_notes` table within the data +/// database. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `memo_bytes_ret` must be non-null and must point to an allocated 512-byte region of memory. +#[no_mangle] +pub extern "C" fn zcashlc_get_sent_memo( + db_data: *const u8, + db_data_len: usize, + id_note: i64, + memo_bytes_ret: *mut u8, + network_id: u32, +) -> bool { + zcashlc_get_memo( + db_data, + db_data_len, + NoteId::SentNoteId(id_note), + memo_bytes_ret, + network_id, + ) +} + /// Checks that the scanned blocks in the data database, when combined with the recent /// `CompactBlock`s in the cache database, form a valid chain. /// @@ -894,6 +1646,21 @@ pub extern "C" fn zcashlc_get_sent_memo_as_utf8( /// - `0` if there was an error during validation unrelated to chain validity. /// /// This function does not mutate either of the databases. +/// +/// # Safety +/// +/// - `db_cache` must be non-null and valid for reads for `db_cache_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_cache` must not be mutated for the duration of the function call. +/// - The total size `db_cache_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. #[no_mangle] pub extern "C" fn zcashlc_validate_combined_chain( db_cache: *const u8, @@ -904,8 +1671,8 @@ pub extern "C" fn zcashlc_validate_combined_chain( ) -> i32 { let res = catch_panic(|| { let network = parse_network(network_id)?; - let block_db = block_db(db_cache, db_cache_len)?; - let db_data = wallet_db(db_data, db_data_len, network)?; + let block_db = unsafe { block_db(db_cache, db_cache_len)? }; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; let validate_from = (&db_data) .get_max_height_hash() @@ -928,6 +1695,18 @@ pub extern "C" fn zcashlc_validate_combined_chain( }); unwrap_exc_or_null(res) } + +/// Returns the most recent block height to which it is possible to reset the state +/// of the data database. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. #[no_mangle] pub extern "C" fn zcashlc_get_nearest_rewind_height( db_data: *const u8, @@ -935,12 +1714,13 @@ pub extern "C" fn zcashlc_get_nearest_rewind_height( height: i32, network_id: u32, ) -> i32 { + #[allow(deprecated)] let res = catch_panic(|| { if height < 100 { Ok(height) } else { let network = parse_network(network_id)?; - let db_data = wallet_db(db_data, db_data_len, network)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; let height = BlockHeight::try_from(height)?; match get_rewind_height(&db_data) { Ok(Some(best_height)) => { @@ -965,10 +1745,20 @@ pub extern "C" fn zcashlc_get_nearest_rewind_height( }); unwrap_exc_or(res, -1) } + /// Rewinds the data database to the given height. /// /// If the requested height is greater than or equal to the height of the last scanned /// block, this function does nothing. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. #[no_mangle] pub extern "C" fn zcashlc_rewind_to_height( db_data: *const u8, @@ -978,10 +1768,12 @@ pub extern "C" fn zcashlc_rewind_to_height( ) -> bool { let res = catch_panic(|| { let network = parse_network(network_id)?; - let db_data = wallet_db(db_data, db_data_len, network)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; + let mut db_data = db_data.get_update_ops()?; let height = BlockHeight::try_from(height)?; - rewind_to_height(&db_data, height) + db_data + .rewind_to_height(height) .map(|_| true) .map_err(|e| format_err!("Error while rewinding data DB to height {}: {}", height, e)) }); @@ -1002,6 +1794,21 @@ pub extern "C" fn zcashlc_rewind_to_height( /// /// Scanned blocks are required to be height-sequential. If a block is missing from the /// cache, an error will be signalled. +/// +/// # Safety +/// +/// - `db_cache` must be non-null and valid for reads for `db_cache_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_cache` must not be mutated for the duration of the function call. +/// - The total size `db_cache_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. #[no_mangle] pub extern "C" fn zcashlc_scan_blocks( db_cache: *const u8, @@ -1013,10 +1820,10 @@ pub extern "C" fn zcashlc_scan_blocks( ) -> i32 { let res = catch_panic(|| { let network = parse_network(network_id)?; - let block_db = block_db(db_cache, db_cache_len)?; - let db_read = wallet_db(db_data, db_data_len, network)?; + let block_db = unsafe { block_db(db_cache, db_cache_len)? }; + let db_read = unsafe { wallet_db(db_data, db_data_len, network)? }; let mut db_data = db_read.get_update_ops()?; - let limit = if scan_limit <= 0 { + let limit = if scan_limit == 0 { None } else { Some(scan_limit) @@ -1029,11 +1836,30 @@ pub extern "C" fn zcashlc_scan_blocks( unwrap_exc_or_null(res) } +/// Inserts a UTXO into the wallet database. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `txid_bytes` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. +/// - The memory referenced by `txid_bytes_len` must not be mutated for the duration of the function call. +/// - The total size `txid_bytes_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `script_bytes` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. +/// - The memory referenced by `script_bytes_len` must not be mutated for the duration of the function call. +/// - The total size `script_bytes_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. #[no_mangle] pub extern "C" fn zcashlc_put_utxo( db_data: *const u8, db_data_len: usize, - address_str: *const c_char, txid_bytes: *const u8, txid_bytes_len: usize, index: i32, @@ -1045,27 +1871,31 @@ pub extern "C" fn zcashlc_put_utxo( ) -> bool { let res = catch_panic(|| { let network = parse_network(network_id)?; - let db_data = wallet_db(db_data, db_data_len, network)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; let mut db_data = db_data.get_update_ops()?; - let addr = unsafe { CStr::from_ptr(address_str).to_str()? }; let txid_bytes = unsafe { slice::from_raw_parts(txid_bytes, txid_bytes_len) }; let mut txid = [0u8; 32]; - txid.copy_from_slice(&txid_bytes); + txid.copy_from_slice(txid_bytes); let script_bytes = unsafe { slice::from_raw_parts(script_bytes, script_bytes_len) }; - let script = script_bytes.to_vec(); - - let address = TransparentAddress::decode(&network, &addr).unwrap(); + let script_pubkey = legacy::Script(script_bytes.to_vec()); - let output = WalletTransparentOutput { - address: address, - outpoint: OutPoint::new(txid, index as u32), - script: script, - value: Amount::from_i64(value).unwrap(), - height: BlockHeight::from(height as u32), - }; - match put_received_transparent_utxo(&mut db_data, &output) { + let output = WalletTransparentOutput::from_parts( + OutPoint::new(txid, index as u32), + TxOut { + value: Amount::from_i64(value).unwrap(), + script_pubkey, + }, + BlockHeight::from(height as u32), + ) + .ok_or_else(|| { + format_err!( + "{:?} is not a valid P2PKH or P2SH script_pubkey", + script_bytes + ) + })?; + match db_data.put_received_transparent_utxo(&output) { Ok(_) => Ok(true), Err(e) => Err(format_err!("Error while inserting UTXO: {}", e)), } @@ -1073,29 +1903,21 @@ pub extern "C" fn zcashlc_put_utxo( unwrap_exc_or(res, false) } -#[no_mangle] -pub unsafe extern "C" fn zcashlc_clear_utxos( - db_data: *const u8, - db_data_len: usize, - taddress: *const c_char, - above_height: i32, - network_id: u32, -) -> i32 { - let res = catch_panic(|| { - let network = parse_network(network_id)?; - let db_data = wallet_db(db_data, db_data_len, network)?; - let mut db_data = db_data.get_update_ops()?; - let addr = CStr::from_ptr(taddress).to_str()?; - let taddress = TransparentAddress::decode(&network, &addr).unwrap(); - let height = BlockHeight::from(above_height as u32); - match delete_utxos_above(&mut db_data, &taddress, height) { - Ok(rows) => Ok(rows as i32), - Err(e) => Err(format_err!("Error while clearing UTXOs: {}", e)), - } - }); - unwrap_exc_or(res, -1) -} - +/// Decrypts whatever parts of the specified transaction it can and stores them in db_data. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `tx` must be non-null and valid for reads for `tx_len` bytes, and it must have an +/// alignment of `1`. +/// - The memory referenced by `tx` must not be mutated for the duration of the function call. +/// - The total size `tx_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. #[no_mangle] pub extern "C" fn zcashlc_decrypt_and_store_transaction( db_data: *const u8, @@ -1107,12 +1929,19 @@ pub extern "C" fn zcashlc_decrypt_and_store_transaction( ) -> i32 { let res = catch_panic(|| { let network = parse_network(network_id)?; - let db_read = wallet_db(db_data, db_data_len, network)?; + let db_read = unsafe { wallet_db(db_data, db_data_len, network)? }; let mut db_data = db_read.get_update_ops()?; let tx_bytes = unsafe { slice::from_raw_parts(tx, tx_len) }; - let tx = Transaction::read(&tx_bytes[..])?; - match decrypt_and_store_transaction(&network, &mut db_data, tx_bytes, &tx) { + // The consensus branch ID passed in here does not matter: + // - v4 and below cache it internally, but all we do with this transaction while + // it is in memory is decryption and serialization, neither of which use the + // consensus branch ID. + // - v5 and above transactions ignore the argument, and parse the correct value + // from their encoding. + let tx = Transaction::read(tx_bytes, BranchId::Sapling)?; + + match decrypt_and_store_transaction(&network, &mut db_data, &tx) { Ok(()) => Ok(1), Err(e) => Err(format_err!("Error while decrypting transaction: {}", e)), } @@ -1128,38 +1957,62 @@ pub extern "C" fn zcashlc_decrypt_and_store_transaction( /// /// Do not call this multiple times in parallel, or you will generate transactions that /// double-spend the same notes. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `usk_ptr` must be non-null and must point to an array of `usk_len` bytes containing a unified +/// spending key encoded as returned from the `zcashlc_create_account` or +/// `zcashlc_derive_spending_key` functions. +/// - The memory referenced by `usk_ptr` must not be mutated for the duration of the function call. +/// - The total size `usk_len` must be no larger than `isize::MAX`. See the safety documentation +/// of pointer::offset. +/// - `to` must be non-null and must point to a null-terminated UTF-8 string. +/// - `memo` must either be null (indicating an empty memo or a transparent recipient) or point to a +/// 512-byte array. +/// - `spend_params` must be non-null and valid for reads for `spend_params_len` bytes, and it must have an +/// alignment of `1`. Its contents must be the Sapling spend proving parameters. +/// - The memory referenced by `spend_params` must not be mutated for the duration of the function call. +/// - The total size `spend_params_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `output_params` must be non-null and valid for reads for `output_params_len` bytes, and it must have an +/// alignment of `1`. Its contents must be the Sapling output proving parameters. +/// - The memory referenced by `output_params` must not be mutated for the duration of the function call. +/// - The total size `output_params_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. #[no_mangle] pub extern "C" fn zcashlc_create_to_address( db_data: *const u8, db_data_len: usize, - account: i32, - extsk: *const c_char, + usk_ptr: *const u8, + usk_len: usize, to: *const c_char, value: i64, - memo: *const c_char, + memo: *const u8, spend_params: *const u8, spend_params_len: usize, output_params: *const u8, output_params_len: usize, network_id: u32, + min_confirmations: u32, ) -> i64 { let res = catch_panic(|| { let network = parse_network(network_id)?; - let db_read = wallet_db(db_data, db_data_len, network)?; + let db_read = unsafe { wallet_db(db_data, db_data_len, network)? }; let mut db_data = db_read.get_update_ops()?; - let account = if account >= 0 { - account as u32 - } else { - return Err(format_err!("account argument must be positive")); - }; - let extsk = unsafe { CStr::from_ptr(extsk) }.to_str()?; + + let usk = unsafe { decode_usk(usk_ptr, usk_len) }?; let to = unsafe { CStr::from_ptr(to) }.to_str()?; let value = Amount::from_i64(value).map_err(|()| format_err!("Invalid amount, out of range"))?; if value.is_negative() { return Err(format_err!("Amount is negative")); } - let memo = unsafe { CStr::from_ptr(memo) }.to_str()?; let spend_params = Path::new(OsStr::from_bytes(unsafe { slice::from_raw_parts(spend_params, spend_params_len) })); @@ -1167,33 +2020,29 @@ pub extern "C" fn zcashlc_create_to_address( slice::from_raw_parts(output_params, output_params_len) })); - let extsk = - match decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &extsk) - { - Ok(Some(extsk)) => extsk, - Ok(None) => { - return Err(format_err!("ExtendedSpendingKey is for the wrong network")); - } - Err(e) => { - return Err(format_err!("Invalid ExtendedSpendingKey: {}", e)); - } - }; - - let to = match RecipientAddress::decode(&network, &to) { - Some(to) => to, - None => { - return Err(format_err!("PaymentAddress is for the wrong network")); - } - }; + let to = RecipientAddress::decode(&network, to) + .ok_or_else(|| format_err!("PaymentAddress is for the wrong network"))?; - // TODO: consider warning in this case somehow, rather than swallowing this error let memo = match to { - RecipientAddress::Shielded(_) => { - let memo_value = Memo::from_str(&memo).map_err(|_| format_err!("Invalid memo"))?; - Some(MemoBytes::from(&memo_value)) + RecipientAddress::Shielded(_) | RecipientAddress::Unified(_) => { + if memo.is_null() { + Ok(None) + } else { + MemoBytes::from_bytes(unsafe { slice::from_raw_parts(memo, 512) }) + .map(Some) + .map_err(|e| format_err!("Invalid MemoBytes {}", e)) + } } - RecipientAddress::Transparent(_) => None, - }; + RecipientAddress::Transparent(_) => { + if memo.is_null() { + Ok(None) + } else { + Err(format_err!( + "Memos are not permitted when sending to transparent recipients." + )) + } + } + }?; let prover = LocalTxProver::new(spend_params, output_params); @@ -1201,12 +2050,12 @@ pub extern "C" fn zcashlc_create_to_address( &mut db_data, &network, prover, - AccountId(account), - &extsk, + &usk, &to, value, memo, OvkPolicy::Sender, + min_confirmations, ) .map_err(|e| format_err!("Error while sending funds: {}", e)) }); @@ -1225,124 +2074,53 @@ pub extern "C" fn zcashlc_branch_id_for_height(height: i32, network_id: u32) -> } /// Frees strings returned by other zcashlc functions. -#[no_mangle] -pub extern "C" fn zcashlc_string_free(s: *mut c_char) { - unsafe { - if s.is_null() { - return; - } - CString::from_raw(s) - }; -} - -/// Frees vectors of strings returned by other zcashlc functions. -#[no_mangle] -pub extern "C" fn zcashlc_vec_string_free(v: *mut *mut c_char, len: usize, capacity: usize) { - unsafe { - if v.is_null() { - return; - } - assert!(len <= capacity); - let v = Vec::from_raw_parts(v, len, capacity); - v.into_iter().map(|s| CString::from_raw(s)).for_each(drop); - }; -} - -/// TEST TEST 123 TEST /// +/// # Safety /// - -/// Derives a transparent private key from seed -#[no_mangle] -pub unsafe extern "C" fn zcashlc_derive_transparent_private_key_from_seed( - seed: *const u8, - seed_len: usize, - account: i32, - index: i32, - network_id: u32, -) -> *mut c_char { - let res = catch_panic(|| { - let network = parse_network(network_id)?; - let seed = slice::from_raw_parts(seed, seed_len); - let account = if account >= 0 { - account as u32 - } else { - return Err(format_err!("account argument must be positive")); - }; - - let index = if index >= 0 { - index as u32 - } else { - return Err(format_err!("index argument must be positive")); - }; - let sk = derive_secret_key_from_seed(&network, &seed, AccountId(account), index).unwrap(); - let sk_wif = Wif::from_secret_key(&sk, true); - - Ok(CString::new(sk_wif.0.to_string()).unwrap().into_raw()) - }); - unwrap_exc_or_null(res) -} - -/// 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, - account: i32, - index: i32, - network_id: u32, -) -> *mut c_char { - let res = catch_panic(|| { - let seed = slice::from_raw_parts(seed, seed_len); - let network = parse_network(network_id)?; - let account = if account >= 0 { - account as u32 - } else { - return Err(format_err!("account argument must be positive")); - }; - - let index = if index >= 0 { - index as u32 - } else { - return Err(format_err!("index argument must be positive")); - }; - let sk = derive_secret_key_from_seed(&network, &seed, AccountId(account), index); - let taddr = derive_transparent_address_from_secret_key(&sk.unwrap()).encode(&network); - - Ok(CString::new(taddr).unwrap().into_raw()) - }); - unwrap_exc_or_null(res) -} - -/// Derives a transparent address from the given secret key enconded as a WIF string +/// - `s` should be a non-null pointer returned as a string by another zcashlc function. #[no_mangle] -pub unsafe extern "C" fn zcashlc_derive_transparent_address_from_secret_key( - tsk: *const c_char, - network_id: u32, -) -> *mut c_char { - let res = catch_panic(|| { - let network = parse_network(network_id)?; - let tsk_wif = CStr::from_ptr(tsk).to_str()?; - - let sk: SecretKey = (&Wif(tsk_wif.to_string())) - .try_into() - .expect("invalid private key WIF"); - - // derive the corresponding t-address - let taddr = derive_transparent_address_from_secret_key(&sk).encode(&network); - Ok(CString::new(taddr).unwrap().into_raw()) - }); - unwrap_exc_or_null(res) +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub unsafe extern "C" fn zcashlc_string_free(s: *mut c_char) { + if !s.is_null() { + let s = CString::from_raw(s); + drop(s); + } } +/// Shield transparent UTXOs by sending them to an address associated with the specified Sapling +/// spending key. +/// +/// # Safety +/// +/// - `db_data` must be non-null and valid for reads for `db_data_len` bytes, and it must have an +/// alignment of `1`. Its contents must be a string representing a valid system path in the +/// operating system's preferred representation. +/// - The memory referenced by `db_data` must not be mutated for the duration of the function call. +/// - The total size `db_data_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `usk_ptr` must be non-null and must point to an array of `usk_len` bytes containing a unified +/// spending key encoded as returned from the `zcashlc_create_account` or +/// `zcashlc_derive_spending_key` functions. +/// - The memory referenced by `usk_ptr` must not be mutated for the duration of the function call. +/// - The total size `usk_len` must be no larger than `isize::MAX`. See the safety documentation +/// - `memo` must either be null (indicating an empty memo) or point to a 512-byte array. +/// - `spend_params` must be non-null and valid for reads for `spend_params_len` bytes, and it must have an +/// alignment of `1`. Its contents must be the Sapling spend proving parameters. +/// - The memory referenced by `spend_params` must not be mutated for the duration of the function call. +/// - The total size `spend_params_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. +/// - `output_params` must be non-null and valid for reads for `output_params_len` bytes, and it must have an +/// alignment of `1`. Its contents must be the Sapling output proving parameters. +/// - The memory referenced by `output_params` must not be mutated for the duration of the function call. +/// - The total size `output_params_len` must be no larger than `isize::MAX`. See the safety +/// documentation of pointer::offset. #[no_mangle] pub extern "C" fn zcashlc_shield_funds( db_data: *const u8, db_data_len: usize, - account: i32, - tsk: *const c_char, - extsk: *const c_char, - memo: *const c_char, + usk_ptr: *const u8, + usk_len: usize, + memo: *const u8, spend_params: *const u8, spend_params_len: usize, output_params: *const u8, @@ -1351,19 +2129,20 @@ pub extern "C" fn zcashlc_shield_funds( ) -> i64 { let res = catch_panic(|| { let network = parse_network(network_id)?; - let db_data = wallet_db(db_data, db_data_len, network)?; + let db_data = unsafe { wallet_db(db_data, db_data_len, network)? }; let mut update_ops = (&db_data) .get_update_ops() .map_err(|e| format_err!("Could not obtain a writable database connection: {}", e))?; - let account = if account >= 0 { - account as u32 + let usk = unsafe { decode_usk(usk_ptr, usk_len) }?; + + let memo_bytes = if memo.is_null() { + MemoBytes::empty() } else { - return Err(format_err!("account argument must be positive")); + MemoBytes::from_bytes(unsafe { slice::from_raw_parts(memo, 512) }) + .map_err(|e| format_err!("Invalid MemoBytes {}", e))? }; - let tsk_wif = unsafe { CStr::from_ptr(tsk) }.to_str()?; - let extsk = unsafe { CStr::from_ptr(extsk) }.to_str()?; - let memo = unsafe { CStr::from_ptr(memo) }.to_str()?; + let spend_params = Path::new(OsStr::from_bytes(unsafe { slice::from_raw_parts(spend_params, spend_params_len) })); @@ -1371,31 +2150,39 @@ pub extern "C" fn zcashlc_shield_funds( slice::from_raw_parts(output_params, output_params_len) })); - //grab secret private key for t-funds - let sk: SecretKey = (&Wif(tsk_wif.to_string())).try_into()?; + let account = db_data + .get_account_for_ufvk(&usk.to_unified_full_viewing_key())? + .ok_or_else(|| format_err!("Spending key not recognized."))?; - let extsk = - match decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &extsk) - { - Ok(Some(extsk)) => extsk, - Ok(None) => { - return Err(format_err!("ExtendedSpendingKey is for the wrong network")); - } - Err(e) => { - return Err(format_err!("Invalid ExtendedSpendingKey: {}", e)); - } - }; + let taddrs: Vec = db_data + .get_target_and_anchor_heights(0u32) + .map_err(|e| format_err!("Error while fetching anchor height: {}", e)) + .and_then(|opt_anchor| { + opt_anchor + .map(|(_, a)| a) + .ok_or_else(|| format_err!("height not available; scan required.")) + }) + .and_then(|anchor| { + db_data + .get_transparent_balances(account, anchor) + .map_err(|e| { + format_err!( + "Error while fetching transparent balances for {:?}: {}", + account, + e + ) + }) + })? + .keys() + .cloned() + .collect(); - let memo = Memo::from_str(&memo).map_err(|_| format_err!("Invalid memo"))?; - let memo_bytes = MemoBytes::from(memo); - // shield_funds(&db_cache, &db_data, account, &tsk, &extsk, &memo, &spend_params, &output_params) - shield_funds( + shield_transparent_funds( &mut update_ops, &network, LocalTxProver::new(spend_params, output_params), - AccountId(account), - &sk, - &extsk, + &usk, + &taddrs, &memo_bytes, ANCHOR_OFFSET, )