diff --git a/Cargo.lock b/Cargo.lock index 87c3e988..94c9bf17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,14 +173,14 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9496f0c1d1afb7a2af4338bbe1d969cddfead41d87a9fb3aaa6d0bbc7af648" +checksum = "c943a505c17b494638a38a9af129067f760c9c06794b9f57d499266909be8e72" dependencies = [ "async-trait", "axum-core", "bitflags", - "bytes 1.1.0", + "bytes 1.2.0", "futures-util", "http", "http-body", @@ -207,7 +207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4f44a0e6200e9d11a1cdc989e4b358f6e3d354fbf48478f345a17f4e43f8635" dependencies = [ "async-trait", - "bytes 1.1.0", + "bytes 1.2.0", "futures-util", "http", "http-body", @@ -441,9 +441,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" [[package]] name = "cc" @@ -591,9 +591,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -601,9 +601,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", @@ -612,9 +612,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" +checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", @@ -626,9 +626,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" dependencies = [ "cfg-if 1.0.0", "once_cell 1.13.0", @@ -642,9 +642,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-common" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ccfd8c0ee4cce11e45b3fd6f9d5e69e0cc62912aa6a0cb1bf4617b0eba5a12f" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array 0.14.5", "typenum", @@ -781,13 +781,21 @@ checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" [[package]] name = "equihash" -version = "0.1.0" -source = "git+https://github.com/adityapk00/librustzcash?rev=7183acd2fe12ebf201cae5b871166e356273c481#7183acd2fe12ebf201cae5b871166e356273c481" +version = "0.2.0" +source = "git+https://github.com/zcash/librustzcash?rev=09567fc280463f5797a77f1d764205be8ad8e64a#09567fc280463f5797a77f1d764205be8ad8e64a" dependencies = [ "blake2b_simd", "byteorder", ] +[[package]] +name = "f4jumble" +version = "0.1.0" +source = "git+https://github.com/zcash/librustzcash?rev=09567fc280463f5797a77f1d764205be8ad8e64a#09567fc280463f5797a77f1d764205be8ad8e64a" +dependencies = [ + "blake2b_simd", +] + [[package]] name = "failure" version = "0.1.8" @@ -818,9 +826,9 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[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", ] @@ -1018,9 +1026,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" [[package]] name = "group" @@ -1040,7 +1048,7 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.0", "fnv", "futures-core", "futures-sink", @@ -1055,9 +1063,9 @@ dependencies = [ [[package]] name = "halo2_gadgets" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13f3914f58cc4af5e4fe83d48b02d582be18976bc7e96c3151aa2bf1c98e9f60" +checksum = "85e10bf9924da1754e443641c9e7f9f00483749f8fb837fde696ef6ed6e2f079" dependencies = [ "arrayvec", "bitvec", @@ -1073,9 +1081,9 @@ dependencies = [ [[package]] name = "halo2_proofs" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e925780549adee8364c7f2b685c753f6f3df23bde520c67416e93bf615933760" +checksum = "cff771b9a2445cd2545c9ef26d863c290fbb44ae440c825a20eb7156f67a949a" dependencies = [ "blake2b_simd", "ff", @@ -1083,6 +1091,7 @@ dependencies = [ "pasta_curves", "rand_core 0.6.3", "rayon", + "tracing", ] [[package]] @@ -1097,9 +1106,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hdwallet" @@ -1176,7 +1185,7 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.0", "fnv", "itoa", ] @@ -1187,7 +1196,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.0", "http", "pin-project-lite", ] @@ -1231,7 +1240,7 @@ version = "0.14.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.0", "futures-channel", "futures-core", "futures-util", @@ -1277,7 +1286,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg 1.1.0", - "hashbrown 0.12.2", + "hashbrown 0.12.3", ] [[package]] @@ -1315,9 +1324,9 @@ checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" [[package]] name = "js-sys" -version = "0.3.58" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" dependencies = [ "wasm-bindgen", ] @@ -1652,9 +1661,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "orchard" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f918076e191a68d55c5517a16e075ecfe58fc63ed112408263f3d6194597bfcf" +checksum = "7619db7f917afd9b1139044c595fab1b6166de2db62317794b5f5e34a2104ae1" dependencies = [ "aes", "bitvec", @@ -1674,7 +1683,8 @@ dependencies = [ "reddsa", "serde", "subtle 2.4.1", - "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing", + "zcash_note_encryption", ] [[package]] @@ -1745,7 +1755,7 @@ checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.13", + "redox_syscall 0.2.15", "smallvec 1.9.0", "windows-sys", ] @@ -1903,9 +1913,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b" dependencies = [ "unicode-ident", ] @@ -1916,7 +1926,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.0", "prost-derive", ] @@ -1926,7 +1936,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae5a4388762d5815a9fc0dea33c56b021cdc8dde0c55e0c9ca57197254b0cab" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.0", "cfg-if 1.0.0", "cmake", "heck", @@ -1961,7 +1971,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.0", "prost", ] @@ -2251,6 +2261,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "redjubjub" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6039ff156887caf92df308cbaccdc058c9d3155a913da046add6e48c4cdbd91d" +dependencies = [ + "blake2b_simd", + "byteorder", + "digest 0.9.0", + "jubjub", + "rand_core 0.6.3", + "serde", + "thiserror", + "zeroize", +] + [[package]] name = "redox_syscall" version = "0.1.57" @@ -2259,9 +2285,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "534cfe58d6a18cc17120fbf4635d53d14691c1fe4d951064df9bd326178d7d5a" dependencies = [ "bitflags", ] @@ -2273,7 +2299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom 0.2.7", - "redox_syscall 0.2.13", + "redox_syscall 0.2.15", "thiserror", ] @@ -2549,9 +2575,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.139" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" +checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" dependencies = [ "serde_derive", ] @@ -2578,9 +2604,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.139" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb" +checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" dependencies = [ "proc-macro2", "quote", @@ -2600,9 +2626,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec0091e1f5aa338283ce049bd9dfefd55e1f168ac233e85c1ffe0038fb48cbe" +checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" dependencies = [ "indexmap", "ryu", @@ -2677,9 +2703,12 @@ checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" [[package]] name = "slab" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg 1.1.0", +] [[package]] name = "smallvec" @@ -2860,7 +2889,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand", "libc", - "redox_syscall 0.2.13", + "redox_syscall 0.2.15", "remove_dir_all", "winapi", ] @@ -2912,7 +2941,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fdfe0627923f7411a43ec9ec9c39c3a9b4151be313e0922042581fb6c9b717f" dependencies = [ "libc", - "redox_syscall 0.2.13", + "redox_syscall 0.2.15", "winapi", ] @@ -3016,12 +3045,12 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57aec3cfa4c296db7255446efb4928a6be304b431a806216105542a67b6ca82e" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" dependencies = [ "autocfg 1.1.0", - "bytes 1.1.0", + "bytes 1.2.0", "libc", "memchr", "mio", @@ -3084,7 +3113,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.0", "futures-core", "futures-sink", "pin-project-lite", @@ -3102,7 +3131,7 @@ dependencies = [ "async-trait", "axum", "base64", - "bytes 1.1.0", + "bytes 1.2.0", "futures-core", "futures-util", "h2", @@ -3167,7 +3196,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" dependencies = [ "bitflags", - "bytes 1.1.0", + "bytes 1.2.0", "futures-core", "futures-util", "http", @@ -3275,9 +3304,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" [[package]] name = "unicode-normalization" @@ -3396,9 +3425,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.81" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -3406,13 +3435,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell 1.13.0", "proc-macro2", "quote", "syn", @@ -3421,9 +3450,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.81" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3431,9 +3460,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" dependencies = [ "proc-macro2", "quote", @@ -3444,15 +3473,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" [[package]] name = "web-sys" -version = "0.3.58" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -3590,10 +3619,21 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "zcash_address" +version = "0.1.0" +source = "git+https://github.com/zcash/librustzcash?rev=09567fc280463f5797a77f1d764205be8ad8e64a#09567fc280463f5797a77f1d764205be8ad8e64a" +dependencies = [ + "bech32", + "bs58", + "f4jumble", + "zcash_encoding", +] + [[package]] name = "zcash_client_backend" version = "0.5.0" -source = "git+https://github.com/adityapk00/librustzcash?rev=7183acd2fe12ebf201cae5b871166e356273c481#7183acd2fe12ebf201cae5b871166e356273c481" +source = "git+https://github.com/zcash/librustzcash?rev=09567fc280463f5797a77f1d764205be8ad8e64a#09567fc280463f5797a77f1d764205be8ad8e64a" dependencies = [ "base64", "bech32", @@ -3605,20 +3645,22 @@ dependencies = [ "jubjub", "log", "nom", + "orchard", "percent-encoding", "protobuf", "protobuf-codegen-pure", "rand_core 0.6.3", "subtle 2.4.1", "time 0.2.27", - "zcash_note_encryption 0.1.0 (git+https://github.com/adityapk00/librustzcash?rev=7183acd2fe12ebf201cae5b871166e356273c481)", + "zcash_address", + "zcash_note_encryption", "zcash_primitives", ] [[package]] name = "zcash_encoding" version = "0.1.0" -source = "git+https://github.com/adityapk00/librustzcash?rev=7183acd2fe12ebf201cae5b871166e356273c481#7183acd2fe12ebf201cae5b871166e356273c481" +source = "git+https://github.com/zcash/librustzcash?rev=09567fc280463f5797a77f1d764205be8ad8e64a#09567fc280463f5797a77f1d764205be8ad8e64a" dependencies = [ "byteorder", "nonempty", @@ -3627,19 +3669,7 @@ dependencies = [ [[package]] name = "zcash_note_encryption" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33f84ae538f05a8ac74c82527f06b77045ed9553a0871d9db036166a4c344e3a" -dependencies = [ - "chacha20", - "chacha20poly1305", - "rand_core 0.6.3", - "subtle 2.4.1", -] - -[[package]] -name = "zcash_note_encryption" -version = "0.1.0" -source = "git+https://github.com/adityapk00/librustzcash?rev=7183acd2fe12ebf201cae5b871166e356273c481#7183acd2fe12ebf201cae5b871166e356273c481" +source = "git+https://github.com/zcash/librustzcash?rev=09567fc280463f5797a77f1d764205be8ad8e64a#09567fc280463f5797a77f1d764205be8ad8e64a" dependencies = [ "chacha20", "chacha20poly1305", @@ -3649,8 +3679,8 @@ dependencies = [ [[package]] name = "zcash_primitives" -version = "0.6.0" -source = "git+https://github.com/adityapk00/librustzcash?rev=7183acd2fe12ebf201cae5b871166e356273c481#7183acd2fe12ebf201cae5b871166e356273c481" +version = "0.7.0" +source = "git+https://github.com/zcash/librustzcash?rev=09567fc280463f5797a77f1d764205be8ad8e64a#09567fc280463f5797a77f1d764205be8ad8e64a" dependencies = [ "aes", "bip0039", @@ -3680,13 +3710,14 @@ dependencies = [ "sha2 0.9.9", "subtle 2.4.1", "zcash_encoding", - "zcash_note_encryption 0.1.0 (git+https://github.com/adityapk00/librustzcash?rev=7183acd2fe12ebf201cae5b871166e356273c481)", + "zcash_note_encryption", ] [[package]] name = "zcash_proofs" -version = "0.6.0" -source = "git+https://github.com/adityapk00/librustzcash?rev=7183acd2fe12ebf201cae5b871166e356273c481#7183acd2fe12ebf201cae5b871166e356273c481" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98bf5f6af051dd929263f279b21b9c04c1f30116c70f3c190de2566677f245ef" dependencies = [ "bellman", "blake2b_simd", @@ -3698,12 +3729,14 @@ dependencies = [ "jubjub", "lazy_static", "rand_core 0.6.3", + "redjubjub", + "tracing", "zcash_primitives", ] [[package]] name = "zecwallet-cli" -version = "1.7.20" +version = "1.8.0-beta1" dependencies = [ "byteorder", "clap", @@ -3738,6 +3771,7 @@ dependencies = [ "lazy_static", "log", "log4rs 1.1.1", + "orchard", "pairing", "portpicker", "prost", @@ -3756,18 +3790,19 @@ dependencies = [ "tonic", "tonic-build", "webpki-roots", + "zcash_address", "zcash_client_backend", "zcash_encoding", - "zcash_note_encryption 0.1.0 (git+https://github.com/adityapk00/librustzcash?rev=7183acd2fe12ebf201cae5b871166e356273c481)", + "zcash_note_encryption", "zcash_primitives", "zcash_proofs", ] [[package]] name = "zeroize" -version = "1.5.6" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20b578acffd8516a6c3f2a1bdefc1ec37e547bb4e0fb8b6b01a4cafc886b4442" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" dependencies = [ "zeroize_derive", ] @@ -3783,3 +3818,8 @@ dependencies = [ "syn", "synstructure", ] + +[[patch.unused]] +name = "zcash_proofs" +version = "0.7.0" +source = "git+https://github.com/zcash/librustzcash?rev=09567fc280463f5797a77f1d764205be8ad8e64a#09567fc280463f5797a77f1d764205be8ad8e64a" diff --git a/Cargo.toml b/Cargo.toml index 1e7b3f07..c99d9643 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,12 @@ members = [ ] [profile.release] -debug = false \ No newline at end of file +debug = false + +[patch.crates-io] +zcash_address = { git = "https://github.com/zcash/librustzcash", rev = "09567fc280463f5797a77f1d764205be8ad8e64a"} +zcash_primitives = { git = "https://github.com/zcash/librustzcash", rev = "09567fc280463f5797a77f1d764205be8ad8e64a"} +zcash_client_backend = { git = "https://github.com/zcash/librustzcash", rev = "09567fc280463f5797a77f1d764205be8ad8e64a"} +zcash_note_encryption = { git = "https://github.com/zcash/librustzcash", rev = "09567fc280463f5797a77f1d764205be8ad8e64a"} +zcash_encoding = { git = "https://github.com/zcash/librustzcash", rev = "09567fc280463f5797a77f1d764205be8ad8e64a"} +zcash_proofs = { git = "https://github.com/zcash/librustzcash", rev = "09567fc280463f5797a77f1d764205be8ad8e64a"} \ No newline at end of file diff --git a/cli/Cargo.toml b/cli/Cargo.toml index be3f1457..f2ce5994 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zecwallet-cli" -version = "1.7.20" +version = "1.8.0-beta1" edition = "2018" [dependencies] diff --git a/cli/src/version.rs b/cli/src/version.rs index dd1cb8db..3d53afc2 100644 --- a/cli/src/version.rs +++ b/cli/src/version.rs @@ -1 +1 @@ -pub const VERSION: &str = "1.7.20"; +pub const VERSION: &str = "1.8.0-beta1"; diff --git a/lib/Cargo.toml b/lib/Cargo.toml index a2a29ee6..4aab567b 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -48,12 +48,20 @@ group = "0.12" rust-embed = { version = "6.3.0", features = ["debug-embed"] } -zcash_primitives = { git = "https://github.com/adityapk00/librustzcash", rev = "7183acd2fe12ebf201cae5b871166e356273c481", features = ["transparent-inputs"] } -# zcash_primitives = { path = "../../librustzcash/zcash_primitives", features = ["transparent-inputs"] } -zcash_client_backend = { git = "https://github.com/adityapk00/librustzcash", rev = "7183acd2fe12ebf201cae5b871166e356273c481"} -zcash_proofs = { git = "https://github.com/adityapk00/librustzcash", rev = "7183acd2fe12ebf201cae5b871166e356273c481", features = ["multicore"]} -zcash_encoding = { git = "https://github.com/adityapk00/librustzcash", rev = "7183acd2fe12ebf201cae5b871166e356273c481"} -zcash_note_encryption = { git = "https://github.com/adityapk00/librustzcash", rev = "7183acd2fe12ebf201cae5b871166e356273c481", features = ["pre-zip-212"]} +orchard = "0.2.0" +zcash_address = "0.1.0" +zcash_primitives = { version = "0.7.0", features = ["transparent-inputs"] } +zcash_client_backend = "0.5.0" +zcash_proofs = { version = "0.7.1", features = ["multicore"] } +zcash_encoding = "0.1.0" +zcash_note_encryption = { version = "0.1.0", features = ["pre-zip-212"] } + + +#zcash_primitives = { git = "https://github.com/adityapk00/librustzcash", rev = "7183acd2fe12ebf201cae5b871166e356273c481", features = ["transparent-inputs"] } +#zcash_client_backend = { git = "https://github.com/adityapk00/librustzcash", rev = "7183acd2fe12ebf201cae5b871166e356273c481"} +#zcash_proofs = { git = "https://github.com/adityapk00/librustzcash", rev = "7183acd2fe12ebf201cae5b871166e356273c481", features = ["multicore"]} +#zcash_encoding = { git = "https://github.com/adityapk00/librustzcash", rev = "7183acd2fe12ebf201cae5b871166e356273c481"} +#zcash_note_encryption = { git = "https://github.com/adityapk00/librustzcash", rev = "7183acd2fe12ebf201cae5b871166e356273c481", features = ["pre-zip-212"]} [dev-dependencies] portpicker = "0.1.1" diff --git a/lib/proto/compact_formats.proto b/lib/proto/compact_formats.proto index bf51ebb3..f2129f2c 100644 --- a/lib/proto/compact_formats.proto +++ b/lib/proto/compact_formats.proto @@ -37,20 +37,30 @@ message CompactTx { // valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut)) uint32 fee = 3; - repeated CompactSpend spends = 4; // inputs - repeated CompactOutput outputs = 5; // outputs + repeated CompactSaplingSpend spends = 4; // inputs + repeated CompactSaplingOutput outputs = 5; // outputs + repeated CompactOrchardAction actions = 6; } -// CompactSpend is a Sapling Spend Description as described in 7.3 of the Zcash +// CompactSaplingSpend is a Sapling Spend Description as described in 7.3 of the Zcash // protocol specification. -message CompactSpend { +message CompactSaplingSpend { bytes nf = 1; // nullifier (see the Zcash protocol specification) } // output is a Sapling Output Description as described in section 7.4 of the // Zcash protocol spec. Total size is 948. -message CompactOutput { +message CompactSaplingOutput { bytes cmu = 1; // note commitment u-coordinate bytes epk = 2; // ephemeral public key - bytes ciphertext = 3; // ciphertext and zkproof + bytes ciphertext = 3; // first 52 bytes of ciphertext +} + +// https://github.com/zcash/zips/blob/main/zip-0225.rst#orchard-action-description-orchardaction +// (but not all fields are needed) +message CompactOrchardAction { + bytes nullifier = 1; // [32] The nullifier of the input note + bytes cmx = 2; // [32] The x-coordinate of the note commitment for the output note + bytes ephemeralKey = 3; // [32] An encoding of an ephemeral Pallas public key + bytes ciphertext = 4; // [52] The note plaintext component of the encCiphertext field } diff --git a/lib/proto/service.proto b/lib/proto/service.proto index da253bfe..d03b70fa 100644 --- a/lib/proto/service.proto +++ b/lib/proto/service.proto @@ -32,7 +32,8 @@ message TxFilter { } // RawTransaction contains the complete transaction data. It also optionally includes -// the block height in which the transaction was included. +// the block height in which the transaction was included, or, when returned +// by GetMempoolStream(), the latest block height. message RawTransaction { bytes data = 1; // exact data returned by Zcash 'getrawtransaction' uint64 height = 2; // height that the transaction was mined (or -1) @@ -109,11 +110,12 @@ message Exclude { // The TreeState is derived from the Zcash z_gettreestate rpc. message TreeState { - string network = 1; // "main" or "test" - uint64 height = 2; - string hash = 3; // block id - uint32 time = 4; // Unix epoch time when the block was mined - string tree = 5; // sapling commitment tree state + string network = 1; // "main" or "test" + uint64 height = 2; // block height + string hash = 3; // block id + uint32 time = 4; // Unix epoch time when the block was mined + string tree = 5; // sapling commitment tree state + string orchardTree = 6; // orchard commitment tree state } // Results are sorted by height, which makes it easy to issue another @@ -163,7 +165,7 @@ service CompactTxStreamer { rpc GetBlock(BlockID) returns (CompactBlock) {} // Return a list of consecutive compact blocks rpc GetBlockRange(BlockRange) returns (stream CompactBlock) {} - + // Get the historical and current prices rpc GetZECPrice(PriceRequest) returns (PriceResponse) {} rpc GetCurrentZECPrice(Empty) returns (PriceResponse) {} @@ -178,7 +180,7 @@ service CompactTxStreamer { // Legacy API that is used as a fallback for t-Address support, if the server is running the old version (lwdv2) rpc GetAddressTxids(TransparentAddressBlockFilter) returns (stream RawTransaction) {} - + rpc GetTaddressBalance(AddressList) returns (Balance) {} rpc GetTaddressBalanceStream(stream Address) returns (Balance) {} @@ -193,6 +195,8 @@ service CompactTxStreamer { // in the exclude list that don't exist in the mempool are ignored. rpc GetMempoolTx(Exclude) returns (stream CompactTx) {} + // Return a stream of current Mempool transactions. This will keep the output stream open while + // there are mempool transactions. It will close the returned stream when a new block is mined. rpc GetMempoolStream(Empty) returns (stream RawTransaction) {} // GetTreeState returns the note commitment tree state corresponding to the given block. diff --git a/lib/src/blaze/fetch_full_tx.rs b/lib/src/blaze/fetch_full_tx.rs index 23e9e71c..fbd12cdb 100644 --- a/lib/src/blaze/fetch_full_tx.rs +++ b/lib/src/blaze/fetch_full_tx.rs @@ -9,6 +9,9 @@ use crate::{ use futures::{stream::FuturesUnordered, StreamExt}; use log::info; +use orchard::note_encryption::OrchardDomain; +use zcash_note_encryption::try_note_decryption; + use std::{ collections::HashSet, convert::{TryFrom, TryInto}, @@ -291,9 +294,9 @@ impl FetchFullTxns

{ .collect(); let extfvks = Arc::new(keys.read().await.get_all_extfvks()); - let ivks: Vec<_> = extfvks.iter().map(|k| k.fvk.vk.ivk()).collect(); + let s_ivks: Vec<_> = extfvks.iter().map(|k| k.fvk.vk.ivk()).collect(); - // Step 4: Scan shielded sapling outputs to see if anyone of them is us, and if it is, extract the memo. Note that if this + // Step 4a: Scan shielded sapling outputs to see if anyone of them is us, and if it is, extract the memo. Note that if this // is invoked by a transparent transaction, and we have not seen this Tx from the trial_decryptions processor, the Note // might not exist, and the memo updating might be a No-Op. That's Ok, the memo will get updated when this Tx is scanned // a second time by the Full Tx Fetcher @@ -302,7 +305,7 @@ impl FetchFullTxns

{ if let Some(s_bundle) = tx.sapling_bundle() { for output in s_bundle.shielded_outputs.iter() { // Search all of our keys - for (i, ivk) in ivks.iter().enumerate() { + for (i, ivk) in s_ivks.iter().enumerate() { let (note, to, memo_bytes) = match try_sapling_note_decryption(&config.get_params(), height, &ivk, output) { Some(ret) => ret, @@ -322,7 +325,7 @@ impl FetchFullTxns

{ } let memo = memo_bytes.clone().try_into().unwrap_or(Memo::Future(memo_bytes)); - wallet_txns.write().await.add_memo_to_note(&tx.txid(), note, memo); + wallet_txns.write().await.add_memo_to_s_note(&tx.txid(), note, memo); } // Also scan the output to see if it can be decoded with our OutgoingViewKey @@ -371,6 +374,34 @@ impl FetchFullTxns

{ } } + // Step 4b: Scan the orchard part of the bundle to see if there are any memos + let o_ivks = keys.read().await.get_all_orchard_ivks(); + if let Some(o_bundle) = tx.orchard_bundle() { + // let orchard_actions = o_bundle + // .actions() + // .into_iter() + // .map(|oa| (OrchardDomain::for_action(oa), oa)) + // .collect::>(); + + // let decrypts = try_note_decryption(o_ivks.as_ref(), orchard_actions.as_ref()); + for oa in o_bundle.actions() { + for (ivk_num, ivk) in o_ivks.iter().enumerate() { + if let Some((note, _address, memo_bytes)) = + try_note_decryption(&OrchardDomain::for_action(oa), ivk, oa) + { + if let Ok(memo) = Memo::from_bytes(&memo_bytes) { + wallet_txns.write().await.add_memo_to_o_note( + &tx.txid(), + &keys.read().await.okeys[ivk_num].fvk(), + note, + memo, + ); + } + } + } + } + } + // Step 5. Process t-address outputs // If this Tx in outgoing, i.e., we recieved sent some money in this Tx, then we need to grab all transparent outputs // that don't belong to us as the outgoing metadata diff --git a/lib/src/blaze/test_utils.rs b/lib/src/blaze/test_utils.rs index d1ede718..665c9474 100644 --- a/lib/src/blaze/test_utils.rs +++ b/lib/src/blaze/test_utils.rs @@ -1,7 +1,7 @@ use std::{convert::TryInto, sync::Arc}; use crate::{ - compact_formats::{CompactBlock, CompactOutput, CompactSpend, CompactTx}, + compact_formats::{CompactBlock, CompactSaplingOutput, CompactSaplingSpend, CompactTx}, lightclient::{ faketx::{clone_transactiondata, new_transactiondata}, test_server::TestServerData, @@ -138,12 +138,12 @@ impl FakeTransaction { let enc_ciphertext = encryptor.encrypt_note_plaintext(); // Create a fake CompactBlock containing the note - let mut cout = CompactOutput::default(); + let mut cout = CompactSaplingOutput::default(); cout.cmu = cmu; cout.epk = epk; cout.ciphertext = enc_ciphertext[..52].to_vec(); - let mut sapling_bundle = if self.td.sapling_bundle.is_some() { + let mut sapling_bundle = if self.td.sapling_bundle().is_some() { self.td.sapling_bundle().unwrap().clone() } else { sapling::Bundle { @@ -156,7 +156,17 @@ impl FakeTransaction { } }; sapling_bundle.shielded_outputs.push(od); - self.td.sapling_bundle = Some(sapling_bundle); + + self.td = TransactionData::from_parts( + self.td.version(), + self.td.consensus_branch_id(), + self.td.lock_time(), + self.td.expiry_height(), + self.td.transparent_bundle().cloned(), + self.td.sprout_bundle().cloned(), + Some(sapling_bundle), + self.td.orchard_bundle().cloned(), + ); self.ctx.outputs.push(cout); @@ -166,7 +176,7 @@ impl FakeTransaction { pub fn add_tx_spending(&mut self, nf: &Nullifier, value: u64, ovk: &OutgoingViewingKey, to: &PaymentAddress) { let _ = self.add_sapling_output(value, Some(ovk.clone()), to); - let mut cs = CompactSpend::default(); + let mut cs = CompactSaplingSpend::default(); cs.nf = nf.to_vec(); self.ctx.spends.push(cs); @@ -206,7 +216,17 @@ impl FakeTransaction { value: Amount::from_u64(value).unwrap(), script_pubkey: TransparentAddress::PublicKey(taddr_bytes.try_into().unwrap()).script(), }); - self.td.transparent_bundle = Some(t_bundle); + + self.td = TransactionData::from_parts( + self.td.version(), + self.td.consensus_branch_id(), + self.td.lock_time(), + self.td.expiry_height(), + Some(t_bundle), + self.td.sprout_bundle().cloned(), + self.td.sapling_bundle().cloned(), + self.td.orchard_bundle().cloned(), + ); self.taddrs_involved.push(taddr) } @@ -228,7 +248,17 @@ impl FakeTransaction { script_sig: Script { 0: vec![] }, sequence: 0, }); - self.td.transparent_bundle = Some(t_bundle); + + self.td = TransactionData::from_parts( + self.td.version(), + self.td.consensus_branch_id(), + self.td.lock_time(), + self.td.expiry_height(), + Some(t_bundle), + self.td.sprout_bundle().cloned(), + self.td.sapling_bundle().cloned(), + self.td.orchard_bundle().cloned(), + ); self.taddrs_involved.push(taddr); } @@ -289,7 +319,7 @@ impl FakeCompactBlock { }; // Create a fake CompactBlock containing the note - let mut cout = CompactOutput::default(); + let mut cout = CompactSaplingOutput::default(); cout.cmu = note.cmu().to_bytes().to_vec(); cout.epk = [0u8; 32].to_vec(); cout.ciphertext = [0u8; 52].to_vec(); @@ -349,7 +379,7 @@ impl FakeCompactBlockList { if let Some(s_bundle) = tx.sapling_bundle() { for out in &s_bundle.shielded_outputs { - let mut cout = CompactOutput::default(); + let mut cout = CompactSaplingOutput::default(); cout.cmu = out.cmu.to_repr().to_vec(); cout.epk = out.ephemeral_key.0.to_vec(); cout.ciphertext = out.enc_ciphertext[..52].to_vec(); @@ -358,7 +388,7 @@ impl FakeCompactBlockList { } for spend in &s_bundle.shielded_spends { - let mut cs = CompactSpend::default(); + let mut cs = CompactSaplingSpend::default(); cs.nf = spend.nullifier.to_vec(); ctx.spends.push(cs); diff --git a/lib/src/blaze/trial_decryptions.rs b/lib/src/blaze/trial_decryptions.rs index 3eaa683d..3e913da4 100644 --- a/lib/src/blaze/trial_decryptions.rs +++ b/lib/src/blaze/trial_decryptions.rs @@ -4,6 +4,9 @@ use crate::{ }; use futures::{stream::FuturesUnordered, StreamExt}; use log::info; +use orchard::{keys::IncomingViewingKey, note_encryption::OrchardDomain}; +use prost::Message; +use std::convert::TryFrom; use zcash_note_encryption::batch::try_compact_note_decryption; use std::sync::Arc; @@ -18,7 +21,7 @@ use tokio::{ use zcash_primitives::{ consensus::{self, BlockHeight}, - sapling::{note_encryption::SaplingDomain, Nullifier, SaplingIvk}, + sapling::{self, note_encryption::SaplingDomain, SaplingIvk}, transaction::{Transaction, TxId}, }; @@ -37,7 +40,7 @@ impl TrialDecryptions

{ pub async fn start( &self, bsync_data: Arc>, - detected_txid_sender: Sender<(TxId, Nullifier, BlockHeight, Option)>, + detected_txid_sender: Sender<(TxId, Option, BlockHeight, Option)>, fulltx_fetcher: UnboundedSender<(TxId, oneshot::Sender>)>, ) -> (JoinHandle>, Sender) { //info!("Starting trial decrptions processor"); @@ -52,7 +55,7 @@ impl TrialDecryptions

{ let mut workers = FuturesUnordered::new(); let mut cbs = vec![]; - let ivks = Arc::new( + let s_ivks = Arc::new( keys.read() .await .zkeys @@ -61,13 +64,16 @@ impl TrialDecryptions

{ .collect::>(), ); + let o_ivks = Arc::new(keys.read().await.get_all_orchard_ivks()); + while let Some(cb) = rx.recv().await { //println!("trial_witness recieved {:?}", cb.height); cbs.push(cb); if cbs.len() >= 50 { let keys = keys.clone(); - let ivks = ivks.clone(); + let s_ivks = s_ivks.clone(); + let o_ivks = o_ivks.clone(); let wallet_txns = wallet_txns.clone(); let bsync_data = bsync_data.clone(); let detected_txid_sender = detected_txid_sender.clone(); @@ -76,7 +82,8 @@ impl TrialDecryptions

{ cbs.split_off(0), keys, bsync_data, - ivks, + s_ivks, + o_ivks, wallet_txns, detected_txid_sender, fulltx_fetcher.clone(), @@ -88,7 +95,8 @@ impl TrialDecryptions

{ cbs, keys, bsync_data, - ivks, + s_ivks, + o_ivks, wallet_txns, detected_txid_sender, fulltx_fetcher, @@ -113,9 +121,10 @@ impl TrialDecryptions

{ cbs: Vec, keys: Arc>>, bsync_data: Arc>, - ivks: Arc>, + s_ivks: Arc>, + o_ivks: Arc>, wallet_txns: Arc>, - detected_txid_sender: Sender<(TxId, Nullifier, BlockHeight, Option)>, + detected_txid_sender: Sender<(TxId, Option, BlockHeight, Option)>, fulltx_fetcher: UnboundedSender<(TxId, oneshot::Sender>)>, ) -> Result<(), String> { // println!("Starting batch at {}", temp_start); @@ -132,71 +141,152 @@ impl TrialDecryptions

{ for (tx_num, ctx) in cb.vtx.into_iter().enumerate() { let tokio_handle = Handle::current(); - let outputs_total = ctx.outputs.len(); let ctx_hash = ctx.hash; - - // Collect Outputs - let outputs = ctx - .outputs - .into_iter() - .map(|o| (SaplingDomain::for_height(params.clone(), height), o)) - .collect::>(); - - let decrypts = try_compact_note_decryption(ivks.as_ref(), outputs.as_ref()); - let mut wallet_tx = false; - for (dec_num, maybe_decrypted) in decrypts.into_iter().enumerate() { - if let Some((note, to)) = maybe_decrypted { - wallet_tx = true; - let ctx_hash = ctx_hash.clone(); - let output_num = dec_num % outputs_total; - let ivk_num = dec_num / outputs_total; + { + // Orchard + let actions_total = ctx.actions.len(); + + let orchard_actions = ctx + .actions + .into_iter() + .map(|coa| { + ( + OrchardDomain::for_nullifier( + orchard::note::Nullifier::from_bytes( + <&[u8; 32]>::try_from(&coa.nullifier[..]).unwrap(), + ) + .unwrap(), + ), + coa, + ) + }) + .collect::>(); + + // if orchard_actions.len() > 0 { + // println!( + // "Trial decrypting {} actions with {} o_ivks for txid {}", + // orchard_actions.len(), + // o_ivks.len(), + // WalletTx::new_txid(&ctx_hash) + // ); + // } + + let decrypts = try_compact_note_decryption(o_ivks.as_ref(), orchard_actions.as_ref()); + for (dec_num, maybe_decrypted) in decrypts.into_iter().enumerate() { + if let Some((note, to)) = maybe_decrypted { + // println!( + // "An orchard note was decrypted! {}:{:?}", + // note.value().inner(), + // note.recipient() + // ); + wallet_tx = true; + + let mut action_bytes = vec![]; + orchard_actions + .get(dec_num) + .unwrap() + .1 + .encode(&mut action_bytes) + .unwrap(); - let keys = keys.clone(); - let bsync_data = bsync_data.clone(); - let wallet_txns = wallet_txns.clone(); - let detected_txid_sender = detected_txid_sender.clone(); - let timestamp = cb.time as u64; + let ctx_hash = ctx_hash.clone(); + let output_num = dec_num % actions_total; + let ivk_num = dec_num / actions_total; - workers.push(tokio_handle.spawn(async move { let keys = keys.read().await; - let extfvk = keys.zkeys[ivk_num].extfvk(); - let have_spending_key = keys.have_spending_key(extfvk); - let uri = bsync_data.read().await.uri().clone(); - - // Get the witness for the note - let witness = bsync_data - .read() - .await - .block_data - .get_note_witness(uri, height, tx_num, output_num) - .await?; + let detected_txid_sender = detected_txid_sender.clone(); + let timestamp = cb.time as u64; + let fvk = keys.okeys[ivk_num].fvk(); + let have_spending_key = keys.have_orchard_spending_key(fvk); let txid = WalletTx::new_txid(&ctx_hash); - let nullifier = note.nf(&extfvk.fvk.vk, witness.position() as u64); - - wallet_txns.write().await.add_new_note( - txid.clone(), + wallet_txns.write().await.add_new_orchard_note( + txid, height, false, timestamp, + action_bytes, note, - to, - &extfvk, + fvk, have_spending_key, - witness, ); - info!("Trial decrypt Detected txid {}", &txid); - detected_txid_sender - .send((txid, nullifier, height, Some(output_num as u32))) + .send((txid, None, height, Some(output_num as u32))) .await .unwrap(); + } + } + } - Ok::<_, String>(()) - })); + { + // Sapling + let outputs_total = ctx.outputs.len(); + + let outputs = ctx + .outputs + .into_iter() + .map(|o| (SaplingDomain::for_height(params.clone(), height), o)) + .collect::>(); + + // Batch decryption for sapling + let decrypts = try_compact_note_decryption(s_ivks.as_ref(), outputs.as_ref()); + + for (dec_num, maybe_decrypted) in decrypts.into_iter().enumerate() { + if let Some((note, to)) = maybe_decrypted { + wallet_tx = true; + + let ctx_hash = ctx_hash.clone(); + let output_num = dec_num % outputs_total; + let ivk_num = dec_num / outputs_total; + + let keys = keys.clone(); + let bsync_data = bsync_data.clone(); + let wallet_txns = wallet_txns.clone(); + let detected_txid_sender = detected_txid_sender.clone(); + let timestamp = cb.time as u64; + + workers.push(tokio_handle.spawn(async move { + let keys = keys.read().await; + let extfvk = keys.zkeys[ivk_num].extfvk(); + let have_spending_key = keys.have_sapling_spending_key(extfvk); + let uri = bsync_data.read().await.uri().clone(); + + // Get the witness for the note + let witness = bsync_data + .read() + .await + .block_data + .get_note_witness(uri, height, tx_num, output_num) + .await?; + + let txid = WalletTx::new_txid(&ctx_hash); + let nullifier = note.nf(&extfvk.fvk.vk, witness.position() as u64); + + wallet_txns.write().await.add_new_sapling_note( + txid.clone(), + height, + false, + timestamp, + note, + to, + &extfvk, + have_spending_key, + witness, + ); + + info!("Trial decrypt Detected txid {}", &txid); + + detected_txid_sender + .send((txid, Some(nullifier), height, Some(output_num as u32))) + .await + .unwrap(); + + Ok::<_, String>(()) + })); + } } } diff --git a/lib/src/blaze/update_notes.rs b/lib/src/blaze/update_notes.rs index 61778533..f4d0cae0 100644 --- a/lib/src/blaze/update_notes.rs +++ b/lib/src/blaze/update_notes.rs @@ -81,17 +81,17 @@ impl UpdateNotes { pub async fn start( &self, bsync_data: Arc>, - fetch_full_sender: UnboundedSender<(TxId, BlockHeight)>, + scan_full_tx_sender: UnboundedSender<(TxId, BlockHeight)>, ) -> ( JoinHandle>, oneshot::Sender, - Sender<(TxId, Nullifier, BlockHeight, Option)>, + Sender<(TxId, Option, BlockHeight, Option)>, ) { //info!("Starting Note Update processing"); let download_memos = bsync_data.read().await.wallet_options.download_memos; // Create a new channel where we'll be notified of TxIds that are to be processed - let (tx, mut rx) = channel::<(TxId, Nullifier, BlockHeight, Option)>(4); + let (tx, mut rx) = channel::<(TxId, Option, BlockHeight, Option)>(4); // Aside from the incoming Txns, we also need to update the notes that are currently in the wallet let wallet_txns = self.wallet_txns.clone(); @@ -109,7 +109,7 @@ impl UpdateNotes { let notes = wallet_txns.read().await.get_notes_for_updating(earliest_block - 1); for (txid, nf) in notes { tx_existing - .send((txid, nf, BlockHeight::from(earliest_block as u32), None)) + .send((txid, Some(nf), BlockHeight::from(earliest_block as u32), None)) .await .map_err(|e| format!("Error sending note for updating: {}", e))?; } @@ -126,55 +126,59 @@ impl UpdateNotes { while let Some((txid, nf, at_height, output_num)) = rx.recv().await { let bsync_data = bsync_data.clone(); let wallet_txns = wallet_txns.clone(); - let fetch_full_sender = fetch_full_sender.clone(); + let fetch_full_sender = scan_full_tx_sender.clone(); workers.push(tokio::spawn(async move { // If this nullifier was spent at a future height, fetch the TxId at the height and process it - if let Some(spent_height) = bsync_data - .read() - .await - .block_data - .is_nf_spent(nf, at_height.into()) - .await - { - //info!("Note was spent, just add it as spent for TxId {}", txid); - let (ctx, ts) = bsync_data + if nf.is_some() { + let nf = nf.unwrap(); + if let Some(spent_height) = bsync_data .read() .await .block_data - .get_ctx_for_nf_at_height(&nf, spent_height) - .await; - - let spent_txid = WalletTx::new_txid(&ctx.hash); - let spent_at_height = BlockHeight::from_u32(spent_height as u32); - - // Mark this note as being spent - let value = - wallet_txns - .write() + .is_nf_spent(nf, at_height.into()) + .await + { + //info!("Note was spent, just add it as spent for TxId {}", txid); + let (ctx, ts) = bsync_data + .read() .await - .mark_txid_nf_spent(txid, &nf, &spent_txid, spent_at_height); - - // Record the future tx, the one that has spent the nullifiers recieved in this Tx in the wallet - wallet_txns.write().await.add_new_spent( - spent_txid, - spent_at_height, - false, - ts, - nf, - value, - txid, - ); - - // Send the future Tx to be fetched too, in case it has only spent nullifiers and not recieved any change - if download_memos != MemoDownloadOption::NoMemos { - fetch_full_sender.send((spent_txid, spent_at_height)).unwrap(); + .block_data + .get_ctx_for_nf_at_height(&nf, spent_height) + .await; + + let spent_txid = WalletTx::new_txid(&ctx.hash); + let spent_at_height = BlockHeight::from_u32(spent_height as u32); + + // Mark this note as being spent + let value = + wallet_txns + .write() + .await + .mark_txid_nf_spent(txid, &nf, &spent_txid, spent_at_height); + + // Record the future tx, the one that has spent the nullifiers recieved in this Tx in the wallet + wallet_txns.write().await.add_new_spent( + spent_txid, + spent_at_height, + false, + ts, + nf, + value, + txid, + ); + + // TODO: We probably don't need this, since it is being fetched again outside this `if` + // Send the future Tx to be fetched too, in case it has only spent nullifiers and not recieved any change + if download_memos != MemoDownloadOption::NoMemos { + fetch_full_sender.send((spent_txid, spent_at_height)).unwrap(); + } + } else { + //info!("Note was NOT spent, update its witnesses for TxId {}", txid); + + // If this note's nullifier was not spent, then we need to update the witnesses for this. + Self::update_witnesses(bsync_data.clone(), wallet_txns.clone(), txid, nf, output_num).await; } - } else { - //info!("Note was NOT spent, update its witnesses for TxId {}", txid); - - // If this note's nullifier was not spent, then we need to update the witnesses for this. - Self::update_witnesses(bsync_data.clone(), wallet_txns.clone(), txid, nf, output_num).await; } // Send it off to get the full transaction if this is a new Tx, that is, it has an output_num diff --git a/lib/src/commands.rs b/lib/src/commands.rs index 87ce2c2b..b0129d0c 100644 --- a/lib/src/commands.rs +++ b/lib/src/commands.rs @@ -1191,7 +1191,7 @@ impl Command

for NewAddress let mut h = vec![]; h.push("Create a new address in this wallet"); h.push("Usage:"); - h.push("new [z | t]"); + h.push("new [u | z | t]"); h.push(""); h.push("Example:"); h.push("To create a new z address:"); diff --git a/lib/src/compact_formats.rs b/lib/src/compact_formats.rs index 5bb0f078..8c781968 100644 --- a/lib/src/compact_formats.rs +++ b/lib/src/compact_formats.rs @@ -1,5 +1,6 @@ use ff::PrimeField; use group::GroupEncoding; +use orchard::note_encryption::OrchardDomain; use std::convert::TryInto; use std::convert::TryFrom; @@ -70,12 +71,12 @@ impl CompactBlock { } } -impl CompactOutput { +impl CompactSaplingOutput { /// Returns the note commitment for this output. /// - /// A convenience method that parses [`CompactOutput.cmu`]. + /// A convenience method that parses [`CompactSaplingOutput.cmu`]. /// - /// [`CompactOutput.cmu`]: #structfield.cmu + /// [`CompactSaplingOutput.cmu`]: #structfield.cmu pub fn cmu(&self) -> Result { let mut repr = [0; 32]; repr.as_mut().copy_from_slice(&self.cmu[..]); @@ -89,9 +90,9 @@ impl CompactOutput { /// Returns the ephemeral public key for this output. /// - /// A convenience method that parses [`CompactOutput.epk`]. + /// A convenience method that parses [`CompactSaplingOutput.epk`]. /// - /// [`CompactOutput.epk`]: #structfield.epk + /// [`CompactSaplingOutput.epk`]: #structfield.epk pub fn epk(&self) -> Result { let p = jubjub::ExtendedPoint::from_bytes(&self.epk[..].try_into().map_err(|_| ())?); if p.is_some().into() { @@ -102,7 +103,7 @@ impl CompactOutput { } } -impl ShieldedOutput, 52_usize> for CompactOutput { +impl ShieldedOutput, 52_usize> for CompactSaplingOutput { fn ephemeral_key(&self) -> EphemeralKeyBytes { EphemeralKeyBytes(*vec_to_array(&self.epk)) } @@ -117,3 +118,17 @@ impl ShieldedOutput, 52_usize> for CompactOutput fn vec_to_array<'a, T, const N: usize>(vec: &'a Vec) -> &'a [T; N] { <&[T; N]>::try_from(&vec[..]).unwrap() } + +impl ShieldedOutput for CompactOrchardAction { + fn ephemeral_key(&self) -> EphemeralKeyBytes { + EphemeralKeyBytes::from(*vec_to_array(&self.ephemeral_key)) + } + + fn cmstar_bytes(&self) -> [u8; 32] { + *vec_to_array(&self.cmx) + } + + fn enc_ciphertext(&self) -> &[u8; 52] { + vec_to_array(&self.ciphertext) + } +} diff --git a/lib/src/lightclient.rs b/lib/src/lightclient.rs index 7614de1e..57b205b1 100644 --- a/lib/src/lightclient.rs +++ b/lib/src/lightclient.rs @@ -81,7 +81,7 @@ impl LightClient

{ } let l = LightClient { - wallet: LightWallet::new(config.clone(), seed_phrase, height, 1)?, + wallet: LightWallet::new(config.clone(), seed_phrase, height, 1, 1)?, config: config.clone(), mempool_monitor: std::sync::RwLock::new(None), bsync_data: Arc::new(RwLock::new(BlazeSyncData::new(&config))), @@ -209,10 +209,15 @@ impl LightClient

{ }; } - fn new_wallet(config: &LightClientConfig

, latest_block: u64, num_zaddrs: u32) -> io::Result { + fn new_wallet( + config: &LightClientConfig

, + latest_block: u64, + num_zaddrs: u32, + num_oaddrs: u32, + ) -> io::Result { Runtime::new().unwrap().block_on(async move { let l = LightClient { - wallet: LightWallet::new(config.clone(), None, latest_block, num_zaddrs)?, + wallet: LightWallet::new(config.clone(), None, latest_block, num_zaddrs, num_oaddrs)?, config: config.clone(), mempool_monitor: std::sync::RwLock::new(None), sync_lock: Mutex::new(()), @@ -246,7 +251,7 @@ impl LightClient

{ } } - Self::new_wallet(config, latest_block, 1) + Self::new_wallet(config, latest_block, 1, 1) } pub fn new_from_phrase( @@ -268,7 +273,7 @@ impl LightClient

{ let lr = if seed_phrase.starts_with(config.hrp_sapling_private_key()) || seed_phrase.starts_with(config.hrp_sapling_viewing_key()) { - let lc = Self::new_wallet(config, birthday, 0)?; + let lc = Self::new_wallet(config, birthday, 0, 0)?; Runtime::new().unwrap().block_on(async move { lc.do_import_key(seed_phrase, birthday) .await @@ -281,7 +286,7 @@ impl LightClient

{ } else { Runtime::new().unwrap().block_on(async move { let l = LightClient { - wallet: LightWallet::new(config.clone(), Some(seed_phrase), birthday, 1)?, + wallet: LightWallet::new(config.clone(), Some(seed_phrase), birthday, 1, 1)?, config: config.clone(), mempool_monitor: std::sync::RwLock::new(None), sync_lock: Mutex::new(()), @@ -420,6 +425,9 @@ impl LightClient

{ } pub async fn do_address(&self) -> JsonValue { + // Collect UAs + let uas = self.wallet.keys().read().await.get_all_uaddresses(); + // Collect z addresses let z_addresses = self.wallet.keys().read().await.get_all_zaddresses(); @@ -427,6 +435,7 @@ impl LightClient

{ let t_addresses = self.wallet.keys().read().await.get_all_taddrs(); object! { + "ua_addresses" => uas, "z_addresses" => z_addresses, "t_addresses" => t_addresses, } @@ -648,15 +657,54 @@ impl LightClient

{ .into_iter() .collect(); + // Collect orchard notes + self.wallet.txns.read().await.current.iter() + .flat_map( |(txid, wtx)| { + let spendable_address = spendable_address.clone(); + wtx.o_notes.iter().filter_map(move |nd| + if !all_notes && nd.spent.is_some() { + None + } else { + let address = LightWallet::

::orchard_ua_address (&self.config, &nd.note_address); + let spendable = spendable_address.contains(&address) && + wtx.block <= anchor_height && nd.spent.is_none() && nd.unconfirmed_spent.is_none(); + + let created_block:u32 = wtx.block.into(); + Some(object!{ + "created_in_block" => created_block, + "datetime" => wtx.datetime, + "created_in_txid" => format!("{}", txid), + "value" => nd.note_value, + "unconfirmed" => wtx.unconfirmed, + "is_change" => nd.is_change, + "address" => address, + "spendable" => spendable, + "spent" => nd.spent.map(|(spent_txid, _)| format!("{}", spent_txid)), + "spent_at_height" => nd.spent.map(|(_, h)| h), + "unconfirmed_spent" => nd.unconfirmed_spent.map(|(spent_txid, _)| format!("{}", spent_txid)), + }) + } + ) + }) + .for_each( |note| { + if note["spent"].is_null() && note["unconfirmed_spent"].is_null() { + unspent_notes.push(note); + } else if !note["spent"].is_null() { + spent_notes.push(note); + } else { + pending_notes.push(note); + } + }); + // Collect Sapling notes self.wallet.txns.read().await.current.iter() .flat_map( |(txid, wtx)| { let spendable_address = spendable_address.clone(); - wtx.notes.iter().filter_map(move |nd| + wtx.s_notes.iter().filter_map(move |nd| if !all_notes && nd.spent.is_some() { None } else { - let address = LightWallet::

::note_address(self.config.hrp_sapling_address(), nd); + let address = LightWallet::

::sapling_note_address(self.config.hrp_sapling_address(), nd); let spendable = address.is_some() && spendable_address.contains(&address.clone().unwrap()) && wtx.block <= anchor_height && nd.spent.is_none() && nd.unconfirmed_spent.is_none(); @@ -803,7 +851,7 @@ impl LightClient

{ // If money was spent, create a transaction. For this, we'll subtract // all the change notes + Utxos let total_change = v - .notes + .s_notes .iter() .filter(|nd| nd.is_change) .map(|nd| nd.note.value) @@ -845,7 +893,7 @@ impl LightClient

{ } // For each sapling note that is not a change, add a Tx. - txns.extend(v.notes.iter().filter(|nd| !nd.is_change).enumerate().map(|(i, nd)| { + txns.extend(v.s_notes.iter().filter(|nd| !nd.is_change).enumerate().map(|(i, nd)| { let block_height: u32 = v.block.into(); let mut o = object! { "block_height" => block_height, @@ -855,7 +903,39 @@ impl LightClient

{ "txid" => format!("{}", v.txid), "amount" => nd.note.value as i64, "zec_price" => v.zec_price.map(|p| (p * 100.0).round() / 100.0), - "address" => LightWallet::

::note_address(self.config.hrp_sapling_address(), nd), + "address" => LightWallet::

::sapling_note_address(self.config.hrp_sapling_address(), nd), + "memo" => LightWallet::

::memo_str(nd.memo.clone()) + }; + + if include_memo_hex { + o.insert( + "memohex", + match &nd.memo { + Some(m) => { + let memo_bytes: MemoBytes = m.into(); + hex::encode(memo_bytes.as_slice()) + } + _ => "".to_string(), + }, + ) + .unwrap(); + } + + return o; + })); + + // For each orchard note that is not a change, add a Tx + txns.extend(v.o_notes.iter().filter(|nd| !nd.is_change).enumerate().map(|(i, nd)| { + let block_height: u32 = v.block.into(); + let mut o = object! { + "block_height" => block_height, + "unconfirmed" => v.unconfirmed, + "datetime" => v.datetime, + "position" => i, + "txid" => format!("{}", v.txid), + "amount" => nd.note_value, + "zec_price" => v.zec_price.map(|p| (p * 100.0).round() / 100.0), + "address" => LightWallet::

::orchard_ua_address(&self.config, &nd.note_address), "memo" => LightWallet::

::memo_str(nd.memo.clone()) }; @@ -917,6 +997,7 @@ impl LightClient

{ let new_address = { let addr = match addr_type { + "u" => self.wallet.keys().write().await.add_oaddr(), "z" => self.wallet.keys().write().await.add_zaddr(), "t" => self.wallet.keys().write().await.add_taddr(), _ => { @@ -1389,15 +1470,14 @@ impl LightClient

{ // The processor to fetch the full transactions, and decode the memos and the outgoing metadata let fetch_full_tx_processor = FetchFullTxns::new(&self.config, self.wallet.keys(), self.wallet.txns()); - let (fetch_full_txns_handle, fetch_full_txn_tx, fetch_taddr_txns_tx) = fetch_full_tx_processor + let (fetch_full_txns_handle, scan_full_txn_tx, fetch_taddr_txns_tx) = fetch_full_tx_processor .start(fulltx_fetcher_tx.clone(), bsync_data.clone()) .await; // The processor to process Transactions detected by the trial decryptions processor let update_notes_processor = UpdateNotes::new(self.wallet.txns()); - let (update_notes_handle, blocks_done_tx, detected_txns_tx) = update_notes_processor - .start(bsync_data.clone(), fetch_full_txn_tx) - .await; + let (update_notes_handle, blocks_done_tx, detected_txns_tx) = + update_notes_processor.start(bsync_data.clone(), scan_full_txn_tx).await; // Do Trial decryptions of all the sapling outputs, and pass on the successful ones to the update_notes processor let trial_decryptions_processor = TrialDecryptions::new(self.wallet.keys(), self.wallet.txns()); diff --git a/lib/src/lightclient/lightclient_config.rs b/lib/src/lightclient/lightclient_config.rs index deac9861..c4bdfae1 100644 --- a/lib/src/lightclient/lightclient_config.rs +++ b/lib/src/lightclient/lightclient_config.rs @@ -15,6 +15,7 @@ use log4rs::{ Config, }; use tokio::runtime::Runtime; +use zcash_address::Network; use zcash_primitives::{ consensus::{self, BlockHeight, NetworkUpgrade, Parameters}, constants::{self}, @@ -150,6 +151,11 @@ impl LightClientConfig

{ self.params.clone() } + pub fn get_network(&self) -> &Network { + // TODO + return &Network::Main; + } + /// Build the Logging config pub fn get_log_config(&self) -> io::Result { let window_size = 3; // log0, log1, log2 diff --git a/lib/src/lightclient/tests.rs b/lib/src/lightclient/tests.rs index d1216343..9294f28f 100644 --- a/lib/src/lightclient/tests.rs +++ b/lib/src/lightclient/tests.rs @@ -27,14 +27,14 @@ use zcash_primitives::sapling::Node; use zcash_primitives::sapling::{Note, Rseed, ValueCommitment}; use zcash_primitives::transaction::components::amount::DEFAULT_FEE; use zcash_primitives::transaction::components::{OutputDescription, GROTH_PROOF_SIZE}; -use zcash_primitives::transaction::Transaction; +use zcash_primitives::transaction::{Transaction, TransactionData}; use zcash_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; use crate::blaze::fetch_full_tx::FetchFullTxns; use crate::blaze::test_utils::{FakeCompactBlockList, FakeTransaction}; use crate::compact_formats::compact_tx_streamer_client::CompactTxStreamerClient; -use crate::compact_formats::{CompactOutput, CompactTx, Empty}; +use crate::compact_formats::{CompactSaplingOutput, CompactTx, Empty}; use crate::lightclient::faketx::new_transactiondata; use crate::lightclient::test_server::{create_test_server, mine_pending_blocks, mine_random_blocks}; use crate::lightclient::LightClient; @@ -378,13 +378,13 @@ async fn multiple_incoming_same_tx() { let enc_ciphertext = encryptor.encrypt_note_plaintext(); // Create a fake CompactBlock containing the note - let mut cout = CompactOutput::default(); + let mut cout = CompactSaplingOutput::default(); cout.cmu = cmu; cout.epk = epk; cout.ciphertext = enc_ciphertext[..52].to_vec(); ctx.outputs.push(cout); - let mut sapling_bundle = if td.sapling_bundle.is_some() { + let mut sapling_bundle = if td.sapling_bundle().is_some() { td.sapling_bundle().unwrap().clone() } else { sapling::Bundle { @@ -398,7 +398,17 @@ async fn multiple_incoming_same_tx() { }; sapling_bundle.shielded_outputs.push(od); - td.sapling_bundle = Some(sapling_bundle); + + td = TransactionData::from_parts( + td.version(), + td.consensus_branch_id(), + td.lock_time(), + td.expiry_height(), + td.transparent_bundle().cloned(), + td.sprout_bundle().cloned(), + Some(sapling_bundle), + td.orchard_bundle().cloned(), + ); } // td.binding_sig = Signature::read(&vec![0u8; 64][..]).ok(); @@ -951,7 +961,7 @@ async fn aborted_resync() { &hex::decode(sent_txid.clone()).unwrap().into_iter().rev().collect(), )) .unwrap() - .notes + .s_notes .get(0) .unwrap() .witnesses @@ -979,7 +989,7 @@ async fn aborted_resync() { &hex::decode(sent_txid).unwrap().into_iter().rev().collect(), )) .unwrap() - .notes + .s_notes .get(0) .unwrap() .witnesses @@ -1191,7 +1201,7 @@ async fn witness_clearing() { .current .get(&tx.txid()) .unwrap() - .notes + .s_notes .get(0) .unwrap() .witnesses @@ -1211,7 +1221,7 @@ async fn witness_clearing() { .current .get(&tx.txid()) .unwrap() - .notes + .s_notes .get(0) .unwrap() .witnesses @@ -1228,7 +1238,7 @@ async fn witness_clearing() { .current .get(&tx.txid()) .unwrap() - .notes + .s_notes .get(0) .unwrap() .witnesses @@ -1245,7 +1255,7 @@ async fn witness_clearing() { .current .get(&tx.txid()) .unwrap() - .notes + .s_notes .get(0) .unwrap() .witnesses diff --git a/lib/src/lightwallet.rs b/lib/src/lightwallet.rs index 0ec2b426..bd6912d3 100644 --- a/lib/src/lightwallet.rs +++ b/lib/src/lightwallet.rs @@ -12,6 +12,7 @@ use crate::{ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use futures::Future; use log::{error, info, warn}; +use orchard::Address; use std::sync::mpsc; use std::{ cmp, @@ -21,14 +22,16 @@ use std::{ time::SystemTime, }; use tokio::sync::RwLock; +use zcash_address::unified::Receiver; +use zcash_address::unified::{Address as UnifiedAddress, Encoding}; use zcash_client_backend::{ address, encoding::{decode_extended_full_viewing_key, decode_extended_spending_key, encode_payment_address}, }; use zcash_encoding::{Optional, Vector}; -use zcash_primitives::consensus; use zcash_primitives::memo::MemoBytes; use zcash_primitives::sapling::prover::TxProver; +use zcash_primitives::{consensus, transaction}; use zcash_primitives::{ consensus::BlockHeight, legacy::Script, @@ -53,6 +56,7 @@ pub(crate) mod keys; pub(crate) mod message; pub(crate) mod utils; pub(crate) mod wallet_txns; +mod walletokey; pub(crate) mod wallettkey; mod walletzkey; @@ -183,8 +187,10 @@ impl LightWallet

{ seed_phrase: Option, height: u64, num_zaddrs: u32, + num_oaddrs: u32, ) -> io::Result { - let keys = Keys::new(&config, seed_phrase, num_zaddrs).map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + let keys = Keys::new(&config, seed_phrase, num_zaddrs, num_oaddrs) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; Ok(Self { keys: Arc::new(RwLock::new(keys)), @@ -272,7 +278,7 @@ impl LightWallet

{ let spendable_keys: Vec<_> = keys .get_all_extfvks() .into_iter() - .filter(|extfvk| keys.have_spending_key(extfvk)) + .filter(|extfvk| keys.have_sapling_spending_key(extfvk)) .collect(); txns.adjust_spendable_status(spendable_keys); @@ -353,7 +359,7 @@ impl LightWallet

{ pub async fn set_witness_block_heights(&mut self) { let top_height = self.last_scanned_height().await; self.txns.write().await.current.iter_mut().for_each(|(_, wtx)| { - wtx.notes.iter_mut().for_each(|nd| { + wtx.s_notes.iter_mut().for_each(|nd| { nd.witnesses.top_height = top_height; }); }); @@ -378,13 +384,19 @@ impl LightWallet

{ self.blocks.read().await.iter().map(|b| b.clone()).collect() } - pub fn note_address(hrp: &str, note: &SaplingNoteData) -> Option { + pub fn sapling_note_address(hrp: &str, note: &SaplingNoteData) -> Option { match note.extfvk.fvk.vk.to_payment_address(note.diversifier) { Some(pa) => Some(encode_payment_address(hrp, &pa)), None => None, } } + pub fn orchard_ua_address(config: &LightClientConfig

, address: &Address) -> String { + let orchard_container = Receiver::Orchard(address.to_raw_address_bytes()); + let unified_address = UnifiedAddress::try_from_items(vec![orchard_container]).unwrap(); + unified_address.encode(config.get_network()) + } + pub async fn set_download_memo(&self, value: MemoDownloadOption) { self.wallet_options.write().await.download_memos = value; } @@ -678,7 +690,7 @@ impl LightWallet

{ .current .values() .map(|tx| { - tx.notes + tx.s_notes .iter() .filter(|nd| match addr.as_ref() { Some(a) => { @@ -736,12 +748,12 @@ impl LightWallet

{ .current .values() .map(|tx| { - tx.notes + tx.s_notes .iter() .filter(|nd| nd.spent.is_none() && nd.unconfirmed_spent.is_none()) .filter(|nd| { // Check to see if we have this note's spending key. - keys.have_spending_key(&nd.extfvk) + keys.have_sapling_spending_key(&nd.extfvk) }) .filter(|nd| match addr.clone() { Some(a) => { @@ -776,7 +788,7 @@ impl LightWallet

{ .values() .map(|tx| { if tx.block <= BlockHeight::from_u32(anchor_height) { - tx.notes + tx.s_notes .iter() .filter(|nd| nd.spent.is_none() && nd.unconfirmed_spent.is_none()) .filter(|nd| match addr.as_ref() { @@ -809,12 +821,12 @@ impl LightWallet

{ .values() .map(|tx| { if tx.block <= BlockHeight::from_u32(anchor_height) { - tx.notes + tx.s_notes .iter() .filter(|nd| nd.spent.is_none() && nd.unconfirmed_spent.is_none()) .filter(|nd| { // Check to see if we have this note's spending key and witnesses - keys.have_spending_key(&nd.extfvk) && nd.witnesses.len() > 0 + keys.have_sapling_spending_key(&nd.extfvk) && nd.witnesses.len() > 0 }) .filter(|nd| match addr.as_ref() { Some(a) => { @@ -879,7 +891,7 @@ impl LightWallet

{ .current .values() .flat_map(|wtx| { - wtx.notes.iter().map(|n| { + wtx.s_notes.iter().map(|n| { let (_, pa) = n.extfvk.default_address(); let zaddr = encode_payment_address(self.config.hrp_sapling_address(), &pa); zaddrs.iter().position(|za| *za == zaddr).unwrap_or(zaddrs.len()) @@ -935,7 +947,7 @@ impl LightWallet

{ // Go over all the sapling notes that might need updating self.txns.write().await.current.values_mut().for_each(|wtx| { - wtx.notes + wtx.s_notes .iter_mut() .filter(|nd| nd.spent.is_some() && nd.spent.unwrap().1 == 0) .for_each(|nd| { @@ -994,7 +1006,7 @@ impl LightWallet

{ .await .current .iter() - .flat_map(|(txid, tx)| tx.notes.iter().map(move |note| (*txid, note))) + .flat_map(|(txid, tx)| tx.s_notes.iter().map(move |note| (*txid, note))) .filter(|(_, note)| note.note.value > 0) .filter_map(|(txid, note)| { // Filter out notes that are already spent @@ -1227,6 +1239,7 @@ impl LightWallet

{ builder.add_sapling_output(Some(ovk), to.clone(), value, encoded_memo) } address::RecipientAddress::Transparent(to) => builder.add_transparent_output(&to, value), + address::RecipientAddress::Unified(_) => Err(transaction::builder::Error::NoChangeAddress), } { let e = format!("Error adding output: {:?}", e); error!("{}", e); @@ -1297,7 +1310,7 @@ impl LightWallet

{ .current .get_mut(&selected.txid) .unwrap() - .notes + .s_notes .iter_mut() .find(|nd| nd.nullifier == selected.nullifier) .unwrap(); @@ -1401,7 +1414,7 @@ mod test { assert_eq!( incw_to_string(¬es[0].witness), incw_to_string( - lc.wallet.txns.read().await.current.get(&tx.txid()).unwrap().notes[0] + lc.wallet.txns.read().await.current.get(&tx.txid()).unwrap().s_notes[0] .witnesses .last() .unwrap() @@ -1425,7 +1438,7 @@ mod test { assert_eq!( incw_to_string(¬es[0].witness), incw_to_string( - lc.wallet.txns.read().await.current.get(&tx.txid()).unwrap().notes[0] + lc.wallet.txns.read().await.current.get(&tx.txid()).unwrap().s_notes[0] .witnesses .get_from_last(1) .unwrap() @@ -1443,7 +1456,7 @@ mod test { assert_eq!( incw_to_string(¬es[0].witness), incw_to_string( - lc.wallet.txns.read().await.current.get(&tx.txid()).unwrap().notes[0] + lc.wallet.txns.read().await.current.get(&tx.txid()).unwrap().s_notes[0] .witnesses .get_from_last(9) .unwrap() @@ -1527,7 +1540,7 @@ mod test { assert_eq!( incw_to_string(¬es[0].witness), incw_to_string( - lc.wallet.txns.read().await.current.get(&tx.txid()).unwrap().notes[0] + lc.wallet.txns.read().await.current.get(&tx.txid()).unwrap().s_notes[0] .witnesses .last() .unwrap() diff --git a/lib/src/lightwallet/data.rs b/lib/src/lightwallet/data.rs index 305c60c7..90c6dbc7 100644 --- a/lib/src/lightwallet/data.rs +++ b/lib/src/lightwallet/data.rs @@ -1,11 +1,14 @@ use crate::compact_formats::CompactBlock; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use orchard::keys::FullViewingKey; +use orchard::Address; use prost::Message; use std::convert::TryFrom; use std::io::{self, Read, Write}; use std::usize; use zcash_encoding::{Optional, Vector}; use zcash_primitives::memo::MemoBytes; +use zcash_primitives::sapling; use crate::blaze::fixed_size_buffer::FixedSizeBuffer; use zcash_primitives::{consensus::BlockHeight, zip32::ExtendedSpendingKey}; @@ -13,7 +16,7 @@ use zcash_primitives::{ memo::Memo, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::Node, - sapling::{Diversifier, Note, Nullifier, Rseed}, + sapling::{Diversifier, Rseed}, transaction::{components::OutPoint, TxId}, zip32::ExtendedFullViewingKey, }; @@ -174,17 +177,153 @@ impl WitnessCache { // } } +pub struct OrchardNoteData { + pub(super) fvk: FullViewingKey, + + pub note_address: Address, + pub note_value: u64, + pub note_nullifier: orchard::note::Nullifier, + + // Info needed to recreate note + pub action_bytes: Vec, + + pub spent: Option<(TxId, u32)>, // If this note was confirmed spent + + // If this note was spent in a send, but has not yet been confirmed. + // Contains the txid and height at which it was broadcast + pub unconfirmed_spent: Option<(TxId, u32)>, + pub memo: Option, + pub is_change: bool, + + // If the spending key is available in the wallet (i.e., whether to keep witness up-to-date) + pub have_spending_key: bool, +} + +impl OrchardNoteData { + fn serialized_version() -> u64 { + 22 + } + + // Reading a note also needs the corresponding address to read from. + pub fn read(mut reader: R) -> io::Result { + let version = reader.read_u64::()?; + assert!(version <= Self::serialized_version()); + + let fvk = FullViewingKey::read(&mut reader)?; + + // Read the parts of the note + // Raw address bytes is 43 + let mut address_bytes = [0u8; 43]; + reader.read_exact(&mut address_bytes)?; + let note_address = Address::from_raw_address_bytes(&address_bytes).unwrap(); + let note_value = reader.read_u64::()?; + let mut rho_bytes = [0u8; 32]; + reader.read_exact(&mut rho_bytes)?; + let note_nullifier = orchard::note::Nullifier::from_bytes(&rho_bytes).unwrap(); + + // Read the action bytes + let action_bytes = Vector::read(&mut reader, |r| r.read_u8())?; + + let spent = Optional::read(&mut reader, |r| { + let mut txid_bytes = [0u8; 32]; + r.read_exact(&mut txid_bytes)?; + let height = r.read_u32::()?; + Ok((TxId::from_bytes(txid_bytes), height)) + })?; + + let unconfirmed_spent = Optional::read(&mut reader, |r| { + let mut txbytes = [0u8; 32]; + r.read_exact(&mut txbytes)?; + + let height = r.read_u32::()?; + Ok((TxId::from_bytes(txbytes), height)) + })?; + + let memo = Optional::read(&mut reader, |r| { + let mut memo_bytes = [0u8; 512]; + r.read_exact(&mut memo_bytes)?; + + // Attempt to read memo, first as text, else as arbitrary 512 bytes + match MemoBytes::from_bytes(&memo_bytes) { + Ok(mb) => match Memo::try_from(mb.clone()) { + Ok(m) => Ok(m), + Err(_) => Ok(Memo::Future(mb)), + }, + Err(e) => Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("Couldn't create memo: {}", e), + )), + } + })?; + + let is_change: bool = reader.read_u8()? > 0; + + let have_spending_key = reader.read_u8()? > 0; + + Ok(OrchardNoteData { + fvk, + note_address, + note_value, + note_nullifier, + action_bytes, + spent, + unconfirmed_spent, + memo, + is_change, + have_spending_key, + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + // Write a version number first, so we can later upgrade this if needed. + writer.write_u64::(Self::serialized_version())?; + + self.fvk.write(&mut writer)?; + + // Write the components of the note + writer.write_all(&self.note_address.to_raw_address_bytes())?; + writer.write_u64::(self.note_value)?; + writer.write_all(&self.note_nullifier.to_bytes())?; + + // Write the action bytes + Vector::write(&mut writer, &self.action_bytes, |w, b| w.write_u8(*b))?; + + Optional::write(&mut writer, self.spent, |w, (txid, h)| { + w.write_all(txid.as_ref())?; + w.write_u32::(h) + })?; + + Optional::write(&mut writer, self.unconfirmed_spent, |w, (txid, height)| { + w.write_all(txid.as_ref())?; + w.write_u32::(height) + })?; + + Optional::write(&mut writer, self.memo.as_ref(), |w, m| { + w.write_all(m.encode().as_array()) + })?; + + writer.write_u8(if self.is_change { 1 } else { 0 })?; + + writer.write_u8(if self.have_spending_key { 1 } else { 0 })?; + + // Note that we don't write the unconfirmed_spent field, because if the wallet is restarted, + // we don't want to be beholden to any expired txns + + Ok(()) + } +} + pub struct SaplingNoteData { // Technically, this should be recoverable from the account number, // but we're going to refactor this in the future, so I'll write it again here. pub(super) extfvk: ExtendedFullViewingKey, pub diversifier: Diversifier, - pub note: Note, + pub note: sapling::Note, // Witnesses for the last 100 blocks. witnesses.last() is the latest witness pub(crate) witnesses: WitnessCache, - pub(super) nullifier: Nullifier, + pub(super) nullifier: sapling::Nullifier, pub spent: Option<(TxId, u32)>, // If this note was confirmed spent // If this note was spent in a send, but has not yet been confirmed. @@ -290,7 +429,7 @@ impl SaplingNoteData { let mut nullifier = [0u8; 32]; reader.read_exact(&mut nullifier)?; - let nullifier = Nullifier(nullifier); + let nullifier = sapling::Nullifier(nullifier); // Note that this is only the spent field, we ignore the unconfirmed_spent field. // The reason is that unconfirmed spents are only in memory, and we need to get the actual value of spent @@ -587,10 +726,16 @@ pub struct WalletTx { pub txid: TxId, // List of all nullifiers spent in this Tx. These nullifiers belong to the wallet. - pub spent_nullifiers: Vec, + pub s_spent_nullifiers: Vec, + + // List of all orchard nullifiers spent in this Tx. + pub o_spent_nullifiers: Vec, // List of all notes received in this tx. Some of these might be change notes. - pub notes: Vec, + pub s_notes: Vec, + + // List of all orchard notes recieved in this tx. Some of these might be change. + pub o_notes: Vec, // List of all Utxos received in this Tx. Some of these might be change notes pub utxos: Vec, @@ -613,7 +758,7 @@ pub struct WalletTx { impl WalletTx { pub fn serialized_version() -> u64 { - return 21; + return 22; } pub fn new_txid(txid: &Vec) -> TxId { @@ -643,8 +788,10 @@ impl WalletTx { unconfirmed, datetime, txid: txid.clone(), - spent_nullifiers: vec![], - notes: vec![], + o_spent_nullifiers: vec![], + s_spent_nullifiers: vec![], + s_notes: vec![], + o_notes: vec![], utxos: vec![], total_transparent_value_spent: 0, total_sapling_value_spent: 0, @@ -672,7 +819,7 @@ impl WalletTx { let txid = TxId::from_bytes(txid_bytes); - let notes = Vector::read(&mut reader, |r| SaplingNoteData::read(r))?; + let s_notes = Vector::read(&mut reader, |r| SaplingNoteData::read(r))?; let utxos = Vector::read(&mut reader, |r| Utxo::read(r))?; let total_sapling_value_spent = reader.read_u64::()?; @@ -689,13 +836,29 @@ impl WalletTx { Optional::read(&mut reader, |r| r.read_f64::())? }; - let spent_nullifiers = if version <= 5 { + let s_spent_nullifiers = if version <= 5 { vec![] } else { Vector::read(&mut reader, |r| { let mut n = [0u8; 32]; r.read_exact(&mut n)?; - Ok(Nullifier(n)) + Ok(sapling::Nullifier(n)) + })? + }; + + let o_notes = if version <= 21 { + vec![] + } else { + Vector::read(&mut reader, |r| OrchardNoteData::read(r))? + }; + + let o_spent_nullifiers = if version <= 21 { + vec![] + } else { + Vector::read(&mut reader, |r| { + let mut rho_bytes = [0u8; 32]; + r.read_exact(&mut rho_bytes)?; + Ok(orchard::note::Nullifier::from_bytes(&rho_bytes).unwrap()) })? }; @@ -704,9 +867,11 @@ impl WalletTx { unconfirmed, datetime, txid, - notes, + s_notes, + o_notes, utxos, - spent_nullifiers, + s_spent_nullifiers, + o_spent_nullifiers, total_sapling_value_spent, total_transparent_value_spent, outgoing_metadata, @@ -727,7 +892,7 @@ impl WalletTx { writer.write_all(self.txid.as_ref())?; - Vector::write(&mut writer, &self.notes, |w, nd| nd.write(w))?; + Vector::write(&mut writer, &self.s_notes, |w, nd| nd.write(w))?; Vector::write(&mut writer, &self.utxos, |w, u| u.write(w))?; writer.write_u64::(self.total_sapling_value_spent)?; @@ -740,7 +905,11 @@ impl WalletTx { Optional::write(&mut writer, self.zec_price, |w, p| w.write_f64::(p))?; - Vector::write(&mut writer, &self.spent_nullifiers, |w, n| w.write_all(&n.0))?; + Vector::write(&mut writer, &self.s_spent_nullifiers, |w, n| w.write_all(&n.0))?; + + Vector::write(&mut writer, &self.o_notes, |w, n| n.write(w))?; + + Vector::write(&mut writer, &self.o_spent_nullifiers, |w, n| w.write_all(&n.to_bytes()))?; Ok(()) } @@ -748,9 +917,9 @@ impl WalletTx { pub struct SpendableNote { pub txid: TxId, - pub nullifier: Nullifier, + pub nullifier: sapling::Nullifier, pub diversifier: Diversifier, - pub note: Note, + pub note: sapling::Note, pub witness: IncrementalWitness, pub extsk: ExtendedSpendingKey, } diff --git a/lib/src/lightwallet/keys.rs b/lib/src/lightwallet/keys.rs index 5e6ba36d..c8829aa4 100644 --- a/lib/src/lightwallet/keys.rs +++ b/lib/src/lightwallet/keys.rs @@ -6,10 +6,12 @@ use std::{ use base58::{FromBase58, ToBase58}; use bip39::{Language, Mnemonic, Seed}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use orchard::keys::{FullViewingKey, IncomingViewingKey, Scope}; use rand::{rngs::OsRng, Rng}; use ripemd160::Digest; use sha2::Sha256; use sodiumoxide::crypto::secretbox; +use zcash_address::unified::Encoding; use zcash_client_backend::{ address, encoding::{encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address}, @@ -28,6 +30,7 @@ use crate::{ }; use super::{ + walletokey::WalletOKey, wallettkey::{WalletTKey, WalletTKeyType}, walletzkey::{WalletZKey, WalletZKeyType}, }; @@ -116,11 +119,15 @@ pub struct Keys

{ // Transparent keys. If the wallet is locked, then the secret keys will be encrypted, // but the addresses will be present. This Vec contains both wallet and imported tkeys pub(crate) tkeys: Vec, + + // Unified address (Orchard) keys actually in this wallet. + // If wallet is locked, only viewing keys are present. + pub(crate) okeys: Vec, } impl Keys

{ pub fn serialized_version() -> u64 { - return 21; + return 22; } #[cfg(test)] @@ -135,10 +142,16 @@ impl Keys

{ seed: [0u8; 32], zkeys: vec![], tkeys: vec![], + okeys: vec![], } } - pub fn new(config: &LightClientConfig

, seed_phrase: Option, num_zaddrs: u32) -> Result { + pub fn new( + config: &LightClientConfig

, + seed_phrase: Option, + num_zaddrs: u32, + num_oaddrs: u32, + ) -> Result { let mut seed_bytes = [0u8; 32]; if seed_phrase.is_none() { @@ -165,12 +178,23 @@ impl Keys

{ // Derive only the first sk and address let tpk = WalletTKey::new_hdkey(config, 0, &bip39_seed.as_bytes()); + // Sapling keys let mut zkeys = vec![]; for hdkey_num in 0..num_zaddrs { let (extsk, _, _) = Self::get_zaddr_from_bip39seed(&config, &bip39_seed.as_bytes(), hdkey_num); zkeys.push(WalletZKey::new_hdkey(hdkey_num, extsk)); } + // Orchard keys + let mut okeys = vec![]; + for hdkey_num in 0..num_oaddrs { + let spending_key = + orchard::keys::SpendingKey::from_zip32_seed(&bip39_seed.as_bytes(), config.get_coin_type(), hdkey_num) + .unwrap(); + + okeys.push(WalletOKey::new_hdkey(hdkey_num, spending_key)); + } + Ok(Self { config: config.clone(), encrypted: false, @@ -180,6 +204,7 @@ impl Keys

{ seed: seed_bytes, zkeys, tkeys: vec![tpk], + okeys, }) } @@ -305,6 +330,7 @@ impl Keys

{ seed: seed_bytes, zkeys, tkeys, + okeys: vec![], }) } @@ -329,6 +355,12 @@ impl Keys

{ let mut seed_bytes = [0u8; 32]; reader.read_exact(&mut seed_bytes)?; + let okeys = if version <= 21 { + vec![] + } else { + Vector::read(&mut reader, |r| WalletOKey::read(r))? + }; + let zkeys = Vector::read(&mut reader, |r| WalletZKey::read(r))?; let tkeys = if version <= 20 { @@ -351,7 +383,7 @@ impl Keys

{ Vector::read(&mut reader, |r| WalletTKey::read(r))? }; - Ok(Self { + let keys = Self { config: config.clone(), encrypted, unlocked: !encrypted, @@ -360,7 +392,12 @@ impl Keys

{ seed: seed_bytes, zkeys, tkeys, - }) + okeys, + }; + + // If there are no okeys, derive the first one. + + Ok(keys) } pub fn write(&self, mut writer: W) -> io::Result<()> { @@ -382,7 +419,10 @@ impl Keys

{ // Flush after writing the seed, so in case of a disaster, we can still recover the seed. writer.flush()?; - // Write all the wallet's keys + // Write all orchard keys + Vector::write(&mut writer, &self.okeys, |w, ok| ok.write(w))?; + + // Write all the wallet's zkeys Vector::write(&mut writer, &self.zkeys, |w, zk| zk.write(w))?; // Write the transparent private keys @@ -406,10 +446,27 @@ impl Keys

{ .to_string() } + pub fn get_all_orchard_fvks(&self) -> Vec { + self.okeys.iter().map(|ok| ok.fvk().clone()).collect::>() + } + + pub fn get_all_orchard_ivks(&self) -> Vec { + self.okeys + .iter() + .map(|ok| ok.fvk().to_ivk(Scope::External)) + .collect::>() + } pub fn get_all_extfvks(&self) -> Vec { self.zkeys.iter().map(|zk| zk.extfvk.clone()).collect() } + pub fn get_all_uaddresses(&self) -> Vec { + self.okeys + .iter() + .map(|ok| ok.unified_address.encode(self.config.get_network())) + .collect() + } + pub fn get_all_zaddresses(&self) -> Vec { self.zkeys .iter() @@ -429,7 +486,7 @@ impl Keys

{ self.tkeys.iter().map(|tk| tk.address.clone()).collect::>() } - pub fn have_spending_key(&self, extfvk: &ExtendedFullViewingKey) -> bool { + pub fn have_sapling_spending_key(&self, extfvk: &ExtendedFullViewingKey) -> bool { self.zkeys .iter() .find(|zk| zk.extfvk == *extfvk) @@ -437,6 +494,14 @@ impl Keys

{ .unwrap_or(false) } + pub fn have_orchard_spending_key(&self, fvk: &FullViewingKey) -> bool { + self.okeys + .iter() + .find(|ok| ok.fvk == *fvk) + .map(|ok| ok.have_spending_key()) + .unwrap_or(false) + } + pub fn get_extsk_for_extfvk(&self, extfvk: &ExtendedFullViewingKey) -> Option { self.zkeys .iter() @@ -518,6 +583,35 @@ impl Keys

{ } } + /// Adds a new unified address to the wallet. This will derive a new address from the seed + /// at the next position and add it to the wallet. + /// NOTE: This does NOT rescan + pub fn add_oaddr(&mut self) -> String { + if !self.unlocked { + return "Error: Can't add key while wallet is locked".to_string(); + } + + // Find the highest pos we have + let pos = self + .zkeys + .iter() + .filter(|zk| zk.hdkey_num.is_some()) + .max_by(|zk1, zk2| zk1.hdkey_num.unwrap().cmp(&zk2.hdkey_num.unwrap())) + .map_or(0, |zk| zk.hdkey_num.unwrap() + 1); + + let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&self.seed, Language::English).unwrap(), ""); + + let spending_key = + orchard::keys::SpendingKey::from_zip32_seed(&bip39_seed.as_bytes(), self.config.get_coin_type(), pos) + .unwrap(); + let newkey = WalletOKey::new_hdkey(pos, spending_key); + let ua = newkey.unified_address.encode(&self.config.get_network()); + + self.okeys.push(newkey); + + return ua; + } + /// Adds a new z address to the wallet. This will derive a new address from the seed /// at the next position and add it to the wallet. /// NOTE: This does NOT rescan diff --git a/lib/src/lightwallet/wallet_txns.rs b/lib/src/lightwallet/wallet_txns.rs index cfbd6f23..9d14a537 100644 --- a/lib/src/lightwallet/wallet_txns.rs +++ b/lib/src/lightwallet/wallet_txns.rs @@ -5,6 +5,7 @@ use std::{ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use log::{error, info}; +use orchard::keys::FullViewingKey; use zcash_encoding::Vector; use zcash_primitives::{ consensus::BlockHeight, @@ -18,7 +19,7 @@ use zcash_primitives::{ use crate::lightclient::lightclient_config::MAX_REORG; -use super::data::{OutgoingTxMetadata, SaplingNoteData, Utxo, WalletTx, WitnessCache}; +use super::data::{OrchardNoteData, OutgoingTxMetadata, SaplingNoteData, Utxo, WalletTx, WitnessCache}; /// List of all transactions in a wallet. /// Note that the parent is expected to hold a RwLock, so we will assume that all accesses to @@ -126,7 +127,7 @@ impl WalletTxns { pub fn adjust_spendable_status(&mut self, spendable_keys: Vec) { self.current.values_mut().for_each(|tx| { - tx.notes.iter_mut().for_each(|nd| { + tx.s_notes.iter_mut().for_each(|nd| { nd.have_spending_key = spendable_keys.contains(&nd.extfvk); if !nd.have_spending_key { nd.witnesses.clear(); @@ -144,7 +145,7 @@ impl WalletTxns { // were spent in any of the txids that were removed self.current.values_mut().for_each(|wtx| { // Update notes to rollback any spent notes - wtx.notes.iter_mut().for_each(|nd| { + wtx.s_notes.iter_mut().for_each(|nd| { // Mark note as unspent if the txid being removed spent it. if nd.spent.is_some() && txids_to_remove.contains(&nd.spent.unwrap().0) { nd.spent = None; @@ -193,7 +194,7 @@ impl WalletTxns { for tx in self.current.values_mut() { // We only want to trim the witness for "existing" notes, i.e., notes that were created before the block that is being removed if tx.block < reorg_height { - for nd in tx.notes.iter_mut() { + for nd in tx.s_notes.iter_mut() { // The latest witness is at the last() position, so just pop() it. // We should be checking if there is a witness at all, but if there is none, it is an // empty vector, for which pop() is a no-op. @@ -215,7 +216,7 @@ impl WalletTxns { .filter(|(_, wtx)| !wtx.unconfirmed) // Update only confirmed notes .flat_map(|(txid, wtx)| { // Fetch notes that are before the before_block. - wtx.notes.iter().filter_map(move |snd| { + wtx.s_notes.iter().filter_map(move |snd| { if wtx.block <= before_block && snd.have_spending_key && snd.witnesses.len() > 0 @@ -241,7 +242,7 @@ impl WalletTxns { self.current .iter() .flat_map(|(_, wtx)| { - wtx.notes + wtx.s_notes .iter() .filter(|nd| nd.spent.is_none()) .map(move |nd| (nd.nullifier.clone(), nd.note.value, wtx.txid.clone())) @@ -251,7 +252,7 @@ impl WalletTxns { pub(crate) fn get_note_witness(&self, txid: &TxId, nullifier: &Nullifier) -> Option<(WitnessCache, BlockHeight)> { self.current.get(txid).map(|wtx| { - wtx.notes + wtx.s_notes .iter() .find(|nd| nd.nullifier == *nullifier) .map(|nd| (nd.witnesses.clone(), wtx.block)) @@ -262,7 +263,7 @@ impl WalletTxns { self.current .get_mut(txid) .unwrap() - .notes + .s_notes .iter_mut() .find(|nd| nd.nullifier == *nullifier) .unwrap() @@ -273,7 +274,7 @@ impl WalletTxns { let cutoff = (latest_height.saturating_sub(MAX_REORG as u64)) as u32; self.current.iter_mut().for_each(|(_, wtx)| { - wtx.notes + wtx.s_notes .iter_mut() .filter(|n| !n.witnesses.is_empty() && n.spent.is_some() && n.spent.unwrap().1 < cutoff) .for_each(|n| n.witnesses.clear()); @@ -309,7 +310,7 @@ impl WalletTxns { .current .get_mut(&txid) .unwrap() - .notes + .s_notes .iter_mut() .find(|n| n.nullifier == *nullifier) .unwrap(); @@ -324,7 +325,7 @@ impl WalletTxns { pub fn check_notes_mark_change(&mut self, txid: &TxId) { if self.total_funds_spent_in(txid) > 0 { self.current.get_mut(txid).map(|wtx| { - wtx.notes.iter_mut().for_each(|n| { + wtx.s_notes.iter_mut().for_each(|n| { n.is_change = true; }) }); @@ -379,8 +380,8 @@ impl WalletTxns { // Mark the height correctly, in case this was previously a mempool or unconfirmed tx. wtx.block = height; - if wtx.spent_nullifiers.iter().find(|nf| **nf == nullifier).is_none() { - wtx.spent_nullifiers.push(nullifier); + if wtx.s_spent_nullifiers.iter().find(|nf| **nf == nullifier).is_none() { + wtx.s_spent_nullifiers.push(nullifier); wtx.total_sapling_value_spent += value; } } @@ -392,7 +393,7 @@ impl WalletTxns { if !unconfirmed { let wtx = self.current.get_mut(&source_txid).expect("Txid should be present"); - wtx.notes.iter_mut().find(|n| n.nullifier == nullifier).map(|nd| { + wtx.s_notes.iter_mut().find(|n| n.nullifier == nullifier).map(|nd| { // Record the spent height nd.spent = Some((txid, height.into())); }); @@ -498,7 +499,7 @@ impl WalletTxns { // Update the block height, in case this was a mempool or unconfirmed tx. wtx.block = height; - match wtx.notes.iter_mut().find(|n| n.note == note) { + match wtx.s_notes.iter_mut().find(|n| n.note == note) { None => { let nd = SaplingNoteData { extfvk: extfvk.clone(), @@ -513,13 +514,62 @@ impl WalletTxns { have_spending_key: false, }; - wtx.notes.push(nd); + wtx.s_notes.push(nd); } Some(_) => {} } } - pub fn add_new_note( + pub fn add_new_orchard_note( + &mut self, + txid: TxId, + height: BlockHeight, + unconfirmed: bool, + timestamp: u64, + action_bytes: Vec, + note: orchard::Note, + fvk: &FullViewingKey, + have_spending_key: bool, + ) { + // Check if this is a change note + let is_change = self.total_funds_spent_in(&txid) > 0; + + let wtx = self.get_or_create_tx(&txid, BlockHeight::from(height), unconfirmed, timestamp); + // Update the block height, in case this was a mempool or unconfirmed tx. + wtx.block = height; + + let note_nullifier = note.nullifier(fvk); + + match wtx.o_notes.iter_mut().find(|n| n.note_nullifier == note_nullifier) { + None => { + let nd = OrchardNoteData { + fvk: fvk.clone(), + note_address: note.recipient(), + note_value: note.value().inner(), + action_bytes, + note_nullifier, + spent: None, + unconfirmed_spent: None, + memo: None, + is_change, + have_spending_key, + }; + + wtx.o_notes.push(nd); + + // TODO: Remove pending notes for this tx. + } + Some(_) => { + // If this note already exists, then just reset the witnesses, because we'll start scanning the witnesses + // again after this. + // This is likely to happen if the previous wallet wasn't synced properly or was aborted in the middle of a sync, + // and has some dangling witnesses + println!("Orchard note already exists in wallet!"); + } + } + } + + pub fn add_new_sapling_note( &mut self, txid: TxId, height: BlockHeight, @@ -545,7 +595,7 @@ impl WalletTxns { WitnessCache::empty() }; - match wtx.notes.iter_mut().find(|n| n.nullifier == nullifier) { + match wtx.s_notes.iter_mut().find(|n| n.nullifier == nullifier) { None => { let nd = SaplingNoteData { extfvk: extfvk.clone(), @@ -560,10 +610,10 @@ impl WalletTxns { have_spending_key, }; - wtx.notes.push(nd); + wtx.s_notes.push(nd); // Also remove any pending notes. - wtx.notes.retain(|n| n.nullifier.0 != [0u8; 32]); + wtx.s_notes.retain(|n| n.nullifier.0 != [0u8; 32]); } Some(n) => { // If this note already exists, then just reset the witnesses, because we'll start scanning the witnesses @@ -575,16 +625,29 @@ impl WalletTxns { } } - // Update the memo for a note if it already exists. If the note doesn't exist, then nothing happens. - pub fn add_memo_to_note(&mut self, txid: &TxId, note: Note, memo: Memo) { + // Update the memo for a sapling note if it already exists. If the note doesn't exist, then nothing happens. + pub fn add_memo_to_s_note(&mut self, txid: &TxId, note: Note, memo: Memo) { self.current.get_mut(txid).map(|wtx| { - wtx.notes + wtx.s_notes .iter_mut() .find(|n| n.note == note) .map(|n| n.memo = Some(memo)); }); } + // Update the memo for a orchard note if it already exists. The note has to already exist. + pub fn add_memo_to_o_note(&mut self, txid: &TxId, fvk: &FullViewingKey, note: orchard::Note, memo: Memo) { + // println!("Adding memo to orchard note"); + let note_nullifier = note.nullifier(fvk); + + self.current.get_mut(txid).map(|wtx| { + wtx.o_notes + .iter_mut() + .find(|n| n.note_nullifier == note_nullifier) + .map(|n| n.memo = Some(memo)); + }); + } + pub fn add_outgoing_metadata(&mut self, txid: &TxId, outgoing_metadata: Vec) { if let Some(wtx) = self.current.get_mut(txid) { // This is n^2 search, but this is likely very small struct, limited by the protocol, so... diff --git a/lib/src/lightwallet/walletokey.rs b/lib/src/lightwallet/walletokey.rs new file mode 100644 index 00000000..efed862a --- /dev/null +++ b/lib/src/lightwallet/walletokey.rs @@ -0,0 +1,199 @@ +use std::io::{self, ErrorKind, Read, Write}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use orchard::keys::{FullViewingKey, Scope, SpendingKey}; +use zcash_address::unified::{Address as UnifiedAddress, Encoding, Receiver}; +use zcash_encoding::{Optional, Vector}; + +#[derive(PartialEq, Debug, Clone)] +pub(crate) enum WalletOKeyType { + HdKey = 0, + ImportedSpendingKey = 1, + ImportedFullViewKey = 2, +} + +// A struct that holds orchard private keys or view keys +#[derive(Clone, Debug)] +pub struct WalletOKey { + locked: bool, + + pub(crate) keytype: WalletOKeyType, + pub(crate) sk: Option, + pub(crate) fvk: FullViewingKey, + pub(crate) unified_address: UnifiedAddress, + + // If this is a HD key, what is the key number + pub(crate) hdkey_num: Option, + + // If locked, the encrypted private key is stored here + enc_key: Option>, + nonce: Option>, +} + +impl WalletOKey { + pub fn new_hdkey(hdkey_num: u32, spending_key: SpendingKey) -> Self { + let fvk = FullViewingKey::from(&spending_key); + let address = fvk.address_at(0u64, Scope::External); + let orchard_container = Receiver::Orchard(address.to_raw_address_bytes()); + let unified_address = UnifiedAddress::try_from_items(vec![orchard_container]).unwrap(); + + WalletOKey { + keytype: WalletOKeyType::HdKey, + sk: Some(spending_key), + fvk, + locked: false, + unified_address, + hdkey_num: Some(hdkey_num), + enc_key: None, + nonce: None, + } + } + + pub fn new_locked_hdkey(hdkey_num: u32, fvk: FullViewingKey) -> Self { + let address = fvk.address_at(0u64, Scope::External); + let orchard_container = Receiver::Orchard(address.to_raw_address_bytes()); + let unified_address = UnifiedAddress::try_from_items(vec![orchard_container]).unwrap(); + + WalletOKey { + keytype: WalletOKeyType::HdKey, + sk: None, + fvk, + locked: true, + unified_address, + hdkey_num: Some(hdkey_num), + enc_key: None, + nonce: None, + } + } + + pub fn new_imported_sk(sk: SpendingKey) -> Self { + let fvk = FullViewingKey::from(&sk); + let address = fvk.address_at(0u64, Scope::External); + let orchard_container = Receiver::Orchard(address.to_raw_address_bytes()); + let unified_address = UnifiedAddress::try_from_items(vec![orchard_container]).unwrap(); + + Self { + keytype: WalletOKeyType::ImportedSpendingKey, + sk: Some(sk), + fvk, + locked: false, + unified_address, + hdkey_num: None, + enc_key: None, + nonce: None, + } + } + + pub fn new_imported_fullviewkey(fvk: FullViewingKey) -> Self { + let address = fvk.address_at(0u64, Scope::External); + let orchard_container = Receiver::Orchard(address.to_raw_address_bytes()); + let unified_address = UnifiedAddress::try_from_items(vec![orchard_container]).unwrap(); + + WalletOKey { + keytype: WalletOKeyType::ImportedFullViewKey, + sk: None, + fvk, + locked: false, + unified_address, + hdkey_num: None, + enc_key: None, + nonce: None, + } + } + + pub fn have_spending_key(&self) -> bool { + self.sk.is_some() || self.enc_key.is_some() || self.hdkey_num.is_some() + } + + pub fn fvk(&self) -> &'_ FullViewingKey { + &self.fvk + } + + fn serialized_version() -> u8 { + return 1; + } + + pub fn read(mut inp: R) -> io::Result { + let version = inp.read_u8()?; + assert!(version <= Self::serialized_version()); + + let keytype = match inp.read_u32::()? { + 0 => Ok(WalletOKeyType::HdKey), + 1 => Ok(WalletOKeyType::ImportedSpendingKey), + 2 => Ok(WalletOKeyType::ImportedFullViewKey), + n => Err(io::Error::new( + ErrorKind::InvalidInput, + format!("Unknown okey type {}", n), + )), + }?; + + let locked = inp.read_u8()? > 0; + + // HDKey num + let hdkey_num = Optional::read(&mut inp, |r| r.read_u32::())?; + + // FVK + let fvk = FullViewingKey::read(&mut inp)?; + + // SK (Read as 32 bytes) + let sk = Optional::read(&mut inp, |r| { + let mut bytes = [0u8; 32]; + r.read_exact(&mut bytes)?; + Ok(SpendingKey::from_bytes(bytes).unwrap()) + })?; + + // Derive unified address + let address = fvk.address_at(0u64, Scope::External); + let orchard_container = Receiver::Orchard(address.to_raw_address_bytes()); + let unified_address = UnifiedAddress::try_from_items(vec![orchard_container]).unwrap(); + + // enc_key + let enc_key = Optional::read(&mut inp, |r| Vector::read(r, |r| r.read_u8()))?; + + // None + let nonce = Optional::read(&mut inp, |r| Vector::read(r, |r| r.read_u8()))?; + + Ok(WalletOKey { + locked, + keytype, + sk, + fvk, + unified_address, + hdkey_num, + enc_key, + nonce, + }) + } + + pub fn write(&self, mut out: W) -> io::Result<()> { + out.write_u8(Self::serialized_version())?; + + out.write_u32::(self.keytype.clone() as u32)?; + + out.write_u8(self.locked as u8)?; + + // HDKey num + Optional::write(&mut out, self.hdkey_num, |o, n| o.write_u32::(n))?; + + // Note that the Unified address is not written, it is derived from the FVK/SK on reading. + + // FVK + FullViewingKey::write(&self.fvk, &mut out)?; + + // SK (written as just bytes) + Optional::write(&mut out, self.sk.as_ref(), |w, sk| { + // SK is 32 bytes + w.write_all(sk.to_bytes()) + })?; + + // Write enc_key + Optional::write(&mut out, self.enc_key.as_ref(), |o, v| { + Vector::write(o, &v[..], |o, n| o.write_u8(*n)) + })?; + + // Write nonce + Optional::write(&mut out, self.nonce.as_ref(), |o, v| { + Vector::write(o, &v[..], |o, n| o.write_u8(*n)) + }) + } +}