From 79a3ad93f5487e628058d4d3774d4a3cf3525f85 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 3 Apr 2025 16:39:46 -0600 Subject: [PATCH 01/15] Update to zcash/librustzcash@10caf455e3f52744b5392af226a408b05721f70f --- Cargo.lock | 36 ++++---- Cargo.toml | 33 +++++--- supply-chain/config.toml | 45 +++++----- supply-chain/imports.lock | 21 +++++ zallet/Cargo.toml | 3 + zallet/src/components/database/connection.rs | 13 ++- .../methods/get_address_for_account.rs | 83 ++++++++++++------- .../json_rpc/methods/list_addresses.rs | 57 ++++++++----- .../json_rpc/methods/list_unspent.rs | 4 +- .../json_rpc/methods/view_transaction.rs | 5 +- .../json_rpc/methods/z_send_many.rs | 6 +- zallet/src/components/sync.rs | 2 +- 12 files changed, 202 insertions(+), 106 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f428366..e34c5fa5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1496,7 +1496,7 @@ dependencies = [ [[package]] name = "equihash" version = "0.2.2" -source = "git+https://github.com/zcash/librustzcash.git?rev=8be259c579762f1b0f569453a20c0d0dbeae6c07#8be259c579762f1b0f569453a20c0d0dbeae6c07" +source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ "blake2b_simd", "core2 0.3.3", @@ -1532,7 +1532,7 @@ dependencies = [ [[package]] name = "f4jumble" version = "0.1.1" -source = "git+https://github.com/zcash/librustzcash.git?rev=8be259c579762f1b0f569453a20c0d0dbeae6c07#8be259c579762f1b0f569453a20c0d0dbeae6c07" +source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ "blake2b_simd", ] @@ -5999,7 +5999,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -6528,6 +6528,7 @@ dependencies = [ "schemars", "schemerz", "schemerz-rusqlite", + "secp256k1", "secrecy 0.8.0", "serde", "serde_json", @@ -6567,7 +6568,7 @@ dependencies = [ [[package]] name = "zcash_address" version = "0.9.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=8be259c579762f1b0f569453a20c0d0dbeae6c07#8be259c579762f1b0f569453a20c0d0dbeae6c07" +source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ "bech32 0.11.0", "bs58", @@ -6580,7 +6581,7 @@ dependencies = [ [[package]] name = "zcash_client_backend" version = "0.19.1" -source = "git+https://github.com/zcash/librustzcash.git?rev=8be259c579762f1b0f569453a20c0d0dbeae6c07#8be259c579762f1b0f569453a20c0d0dbeae6c07" +source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ "async-trait", "base64 0.22.1", @@ -6606,6 +6607,7 @@ dependencies = [ "rand_core 0.6.4", "rayon", "sapling-crypto", + "secp256k1", "secrecy 0.8.0", "shardtree", "subtle", @@ -6629,7 +6631,7 @@ dependencies = [ [[package]] name = "zcash_client_sqlite" version = "0.17.3" -source = "git+https://github.com/zcash/librustzcash.git?rev=8be259c579762f1b0f569453a20c0d0dbeae6c07#8be259c579762f1b0f569453a20c0d0dbeae6c07" +source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ "bip32", "bitflags 2.9.0", @@ -6637,6 +6639,7 @@ dependencies = [ "byteorder", "document-features", "group", + "hex", "incrementalmerkletree", "jubjub", "maybe-rayon", @@ -6651,6 +6654,7 @@ dependencies = [ "sapling-crypto", "schemerz", "schemerz-rusqlite", + "secp256k1", "secrecy 0.8.0", "shardtree", "static_assertions", @@ -6671,7 +6675,7 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.3.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=8be259c579762f1b0f569453a20c0d0dbeae6c07#8be259c579762f1b0f569453a20c0d0dbeae6c07" +source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ "core2 0.3.3", "nonempty", @@ -6680,7 +6684,7 @@ dependencies = [ [[package]] name = "zcash_history" version = "0.4.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=8be259c579762f1b0f569453a20c0d0dbeae6c07#8be259c579762f1b0f569453a20c0d0dbeae6c07" +source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ "blake2b_simd", "byteorder", @@ -6690,7 +6694,7 @@ dependencies = [ [[package]] name = "zcash_keys" version = "0.10.1" -source = "git+https://github.com/zcash/librustzcash.git?rev=8be259c579762f1b0f569453a20c0d0dbeae6c07#8be259c579762f1b0f569453a20c0d0dbeae6c07" +source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ "bech32 0.11.0", "bip32", @@ -6705,6 +6709,7 @@ dependencies = [ "orchard", "rand_core 0.6.4", "sapling-crypto", + "secp256k1", "secrecy 0.8.0", "subtle", "tracing", @@ -6712,6 +6717,7 @@ dependencies = [ "zcash_encoding", "zcash_protocol", "zcash_transparent", + "zeroize", "zip32", ] @@ -6730,8 +6736,8 @@ dependencies = [ [[package]] name = "zcash_primitives" -version = "0.24.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=8be259c579762f1b0f569453a20c0d0dbeae6c07#8be259c579762f1b0f569453a20c0d0dbeae6c07" +version = "0.24.1" +source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ "bip32", "blake2b_simd", @@ -6772,7 +6778,7 @@ dependencies = [ [[package]] name = "zcash_proofs" version = "0.24.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=8be259c579762f1b0f569453a20c0d0dbeae6c07#8be259c579762f1b0f569453a20c0d0dbeae6c07" +source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ "bellman", "blake2b_simd", @@ -6795,7 +6801,7 @@ dependencies = [ [[package]] name = "zcash_protocol" version = "0.6.1" -source = "git+https://github.com/zcash/librustzcash.git?rev=8be259c579762f1b0f569453a20c0d0dbeae6c07#8be259c579762f1b0f569453a20c0d0dbeae6c07" +source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ "core2 0.3.3", "document-features", @@ -6833,7 +6839,7 @@ dependencies = [ [[package]] name = "zcash_transparent" version = "0.4.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=8be259c579762f1b0f569453a20c0d0dbeae6c07#8be259c579762f1b0f569453a20c0d0dbeae6c07" +source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ "bip32", "blake2b_simd", @@ -7213,7 +7219,7 @@ dependencies = [ [[package]] name = "zip321" version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=8be259c579762f1b0f569453a20c0d0dbeae6c07#8be259c579762f1b0f569453a20c0d0dbeae6c07" +source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ "base64 0.22.1", "nom", diff --git a/Cargo.toml b/Cargo.toml index c365f1a6..9efd09af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,9 +85,10 @@ zcash_protocol = "0.6" orchard = "0.11" sapling = { package = "sapling-crypto", version = "0.5" } transparent = { package = "zcash_transparent", version = "0.4" } -zcash_keys = "0.10" +zcash_keys = { version = "0.10", features = ["transparent-inputs", "sapling", "orchard", "transparent-key-encoding"] } zcash_primitives = "0.24" zcash_proofs = "0.24" +secp256k1 = "0.29" # Zcash chain state zaino-fetch = "0.1" @@ -101,7 +102,7 @@ zebra-state = "2.0" deadpool = "0.12" deadpool-sqlite = "0.9" deadpool-sync = "0.1" -incrementalmerkletree = "0.8" +incrementalmerkletree = "0.8.2" rusqlite = { version = "0.32", features = ["time"] } schemerz = "0.2" schemerz-rusqlite = "0.320.0" @@ -113,6 +114,12 @@ zcash_client_backend = "0.19" zcash_client_sqlite = "0.17" zcash_note_encryption = "0.4" zip32 = "0.2" +bip32 = "0.2" + +# Zcashd wallet migration +zewif = { version = "0.1" } +zewif-zcashd = { version = "0.1" } +anyhow = "1.0" # lightwalletd (temporary) tonic = "0.13" @@ -123,17 +130,17 @@ age = { git = "https://github.com/str4d/rage.git", rev = "84dc1e9f641994388f107c abscissa_core = { git = "https://github.com/iqlusioninc/abscissa.git", rev = "fdb60678fb3a883decf63d6d3ebc512abd20406f" } abscissa_tokio = { git = "https://github.com/iqlusioninc/abscissa.git", rev = "fdb60678fb3a883decf63d6d3ebc512abd20406f" } -equihash = { git = "https://github.com/zcash/librustzcash.git", rev = "8be259c579762f1b0f569453a20c0d0dbeae6c07" } -transparent = { package = "zcash_transparent", git = "https://github.com/zcash/librustzcash.git", rev = "8be259c579762f1b0f569453a20c0d0dbeae6c07" } -zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "8be259c579762f1b0f569453a20c0d0dbeae6c07" } -zcash_client_backend = { git = "https://github.com/zcash/librustzcash.git", rev = "8be259c579762f1b0f569453a20c0d0dbeae6c07" } -zcash_client_sqlite = { git = "https://github.com/zcash/librustzcash.git", rev = "8be259c579762f1b0f569453a20c0d0dbeae6c07" } -zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "8be259c579762f1b0f569453a20c0d0dbeae6c07" } -zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "8be259c579762f1b0f569453a20c0d0dbeae6c07" } -zcash_keys = { git = "https://github.com/zcash/librustzcash.git", rev = "8be259c579762f1b0f569453a20c0d0dbeae6c07" } -zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "8be259c579762f1b0f569453a20c0d0dbeae6c07" } -zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "8be259c579762f1b0f569453a20c0d0dbeae6c07" } -zcash_protocol = { git = "https://github.com/zcash/librustzcash.git", rev = "8be259c579762f1b0f569453a20c0d0dbeae6c07" } +equihash = { git = "https://github.com/zcash/librustzcash.git", rev = "10caf455e3f52744b5392af226a408b05721f70f" } +transparent = { package = "zcash_transparent", git = "https://github.com/zcash/librustzcash.git", rev = "10caf455e3f52744b5392af226a408b05721f70f" } +zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "10caf455e3f52744b5392af226a408b05721f70f" } +zcash_client_backend = { git = "https://github.com/zcash/librustzcash.git", rev = "10caf455e3f52744b5392af226a408b05721f70f" } +zcash_client_sqlite = { git = "https://github.com/zcash/librustzcash.git", rev = "10caf455e3f52744b5392af226a408b05721f70f" } +zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "10caf455e3f52744b5392af226a408b05721f70f" } +zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "10caf455e3f52744b5392af226a408b05721f70f" } +zcash_keys = { git = "https://github.com/zcash/librustzcash.git", rev = "10caf455e3f52744b5392af226a408b05721f70f" } +zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "10caf455e3f52744b5392af226a408b05721f70f" } +zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "10caf455e3f52744b5392af226a408b05721f70f" } +zcash_protocol = { git = "https://github.com/zcash/librustzcash.git", rev = "10caf455e3f52744b5392af226a408b05721f70f" } zaino-fetch = { git = "https://github.com/Electric-Coin-Company/zaino.git", rev = "1004733f3303b1c48b6df6db610c54401554683c" } zaino-proto = { git = "https://github.com/Electric-Coin-Company/zaino.git", rev = "1004733f3303b1c48b6df6db610c54401554683c" } diff --git a/supply-chain/config.toml b/supply-chain/config.toml index fe136250..b6b458fb 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -22,6 +22,15 @@ url = "https://raw.githubusercontent.com/mozilla/supply-chain/main/audits.toml" [imports.zcash] url = "https://raw.githubusercontent.com/zcash/rust-ecosystem/main/supply-chain/audits.toml" +[policy.abscissa_core] +audit-as-crates-io = true + +[policy.abscissa_derive] +audit-as-crates-io = true + +[policy.abscissa_tokio] +audit-as-crates-io = true + [policy.age] audit-as-crates-io = true @@ -104,15 +113,15 @@ audit-as-crates-io = true audit-as-crates-io = true [[exemptions.abscissa_core]] -version = "0.8.1" +version = "0.8.2@git:fdb60678fb3a883decf63d6d3ebc512abd20406f" criteria = "safe-to-deploy" [[exemptions.abscissa_derive]] -version = "0.8.0" +version = "0.8.2@git:fdb60678fb3a883decf63d6d3ebc512abd20406f" criteria = "safe-to-deploy" [[exemptions.abscissa_tokio]] -version = "0.8.0" +version = "0.8.0@git:fdb60678fb3a883decf63d6d3ebc512abd20406f" criteria = "safe-to-deploy" [[exemptions.addr2line]] @@ -143,10 +152,6 @@ criteria = "safe-to-deploy" version = "1.1.3" criteria = "safe-to-deploy" -[[exemptions.allocator-api2]] -version = "0.2.21" -criteria = "safe-to-deploy" - [[exemptions.anstream]] version = "0.6.18" criteria = "safe-to-deploy" @@ -552,7 +557,7 @@ version = "0.7.1" criteria = "safe-to-deploy" [[exemptions.equihash]] -version = "0.2.2@git:8be259c579762f1b0f569453a20c0d0dbeae6c07" +version = "0.2.2@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" [[exemptions.eyre]] @@ -560,7 +565,7 @@ version = "0.6.12" criteria = "safe-to-deploy" [[exemptions.f4jumble]] -version = "0.1.1@git:8be259c579762f1b0f569453a20c0d0dbeae6c07" +version = "0.1.1@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" [[exemptions.fallible-iterator]] @@ -1784,43 +1789,43 @@ version = "0.1.2@git:1004733f3303b1c48b6df6db610c54401554683c" criteria = "safe-to-deploy" [[exemptions.zcash_address]] -version = "0.9.0@git:8be259c579762f1b0f569453a20c0d0dbeae6c07" +version = "0.9.0@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" [[exemptions.zcash_client_backend]] -version = "0.19.1@git:8be259c579762f1b0f569453a20c0d0dbeae6c07" +version = "0.19.1@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" [[exemptions.zcash_client_sqlite]] -version = "0.17.3@git:8be259c579762f1b0f569453a20c0d0dbeae6c07" +version = "0.17.3@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" [[exemptions.zcash_encoding]] -version = "0.3.0@git:8be259c579762f1b0f569453a20c0d0dbeae6c07" +version = "0.3.0@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" [[exemptions.zcash_history]] -version = "0.4.0@git:8be259c579762f1b0f569453a20c0d0dbeae6c07" +version = "0.4.0@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" [[exemptions.zcash_keys]] -version = "0.10.1@git:8be259c579762f1b0f569453a20c0d0dbeae6c07" +version = "0.10.1@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" [[exemptions.zcash_primitives]] -version = "0.24.0@git:8be259c579762f1b0f569453a20c0d0dbeae6c07" +version = "0.24.1@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" [[exemptions.zcash_proofs]] -version = "0.24.0@git:8be259c579762f1b0f569453a20c0d0dbeae6c07" +version = "0.24.0@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" [[exemptions.zcash_protocol]] -version = "0.6.1@git:8be259c579762f1b0f569453a20c0d0dbeae6c07" +version = "0.6.1@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" [[exemptions.zcash_transparent]] -version = "0.4.0@git:8be259c579762f1b0f569453a20c0d0dbeae6c07" +version = "0.4.0@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" [[exemptions.zebra-chain]] @@ -1868,5 +1873,5 @@ version = "0.8.24" criteria = "safe-to-deploy" [[exemptions.zip321]] -version = "0.5.0@git:8be259c579762f1b0f569453a20c0d0dbeae6c07" +version = "0.5.0@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 357129b1..c3c7531e 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -368,6 +368,15 @@ criteria = "safe-to-deploy" version = "2.0.0" notes = "Fork of the original `adler` crate, zero unsfae code, works in `no_std`, does what it says on th tin." +[[audits.bytecode-alliance.audits.allocator-api2]] +who = "Chris Fallin " +criteria = "safe-to-deploy" +delta = "0.2.18 -> 0.2.20" +notes = """ +The changes appear to be reasonable updates from Rust's stdlib imported into +`allocator-api2`'s copy of this code. +""" + [[audits.bytecode-alliance.audits.anyhow]] who = "Pat Hickey " criteria = "safe-to-deploy" @@ -2133,6 +2142,18 @@ end = "2024-06-16" notes = "Maintained by Henri Sivonen who works at Mozilla." aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.allocator-api2]] +who = "Nicolas Silva " +criteria = "safe-to-deploy" +version = "0.2.18" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.allocator-api2]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "0.2.20 -> 0.2.21" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.android-tzdata]] who = "Mark Hammond " criteria = "safe-to-deploy" diff --git a/zallet/Cargo.toml b/zallet/Cargo.toml index 3e854c0e..c4c4531e 100644 --- a/zallet/Cargo.toml +++ b/zallet/Cargo.toml @@ -106,6 +106,7 @@ sapling.workspace = true schemars.workspace = true schemerz.workspace = true schemerz-rusqlite.workspace = true +secp256k1.workspace = true secrecy.workspace = true serde.workspace = true serde_json.workspace = true @@ -131,10 +132,12 @@ zcash_client_backend = { workspace = true, features = [ "orchard", "sync", "transparent-inputs", + "transparent-key-import", ] } zcash_client_sqlite = { workspace = true, features = [ "orchard", "transparent-inputs", + "transparent-key-import", ] } zcash_keys.workspace = true zcash_note_encryption.workspace = true diff --git a/zallet/src/components/database/connection.rs b/zallet/src/components/database/connection.rs index 358fc704..73e4197f 100644 --- a/zallet/src/components/database/connection.rs +++ b/zallet/src/components/database/connection.rs @@ -307,8 +307,11 @@ impl WalletRead for DbConnection { &self, account: Self::AccountId, include_change: bool, + include_standalone: bool, ) -> Result>, Self::Error> { - self.with(|db_data| db_data.get_transparent_receivers(account, include_change)) + self.with(|db_data| { + db_data.get_transparent_receivers(account, include_change, include_standalone) + }) } fn get_transparent_balances( @@ -474,6 +477,14 @@ impl WalletWrite for DbConnection { }) } + fn import_standalone_transparent_pubkey( + &mut self, + account: Self::AccountId, + pubkey: secp256k1::PublicKey, + ) -> Result<(), Self::Error> { + self.with_mut(|mut db_data| db_data.import_standalone_transparent_pubkey(account, pubkey)) + } + fn get_next_available_address( &mut self, account: Self::AccountId, diff --git a/zallet/src/components/json_rpc/methods/get_address_for_account.rs b/zallet/src/components/json_rpc/methods/get_address_for_account.rs index 9eda58fd..eaeab132 100644 --- a/zallet/src/components/json_rpc/methods/get_address_for_account.rs +++ b/zallet/src/components/json_rpc/methods/get_address_for_account.rs @@ -165,42 +165,63 @@ pub(crate) async fn call( }) } +fn map_address_generation_error( + e: AddressGenerationError, + account: &JsonValue, +) -> ErrorObjectOwned { + match e { + AddressGenerationError::InvalidTransparentChildIndex(diversifier_index) => { + LegacyCode::Wallet.with_message(format!( + "Error: diversifier index {} cannot generate an address with a transparent receiver.", + u128::from(diversifier_index), + )) + } + AddressGenerationError::InvalidSaplingDiversifierIndex(diversifier_index) => { + LegacyCode::Wallet.with_message(format!( + "Error: diversifier index {} cannot generate an address with a Sapling receiver.", + u128::from(diversifier_index), + )) + } + AddressGenerationError::DiversifierSpaceExhausted => LegacyCode::Wallet.with_static( + "Error: ran out of diversifier indices. Generate a new account with z_getnewaccount" + ), + AddressGenerationError::ReceiverTypeNotSupported(typecode) => match typecode { + unified::Typecode::P2sh => LegacyCode::Wallet.with_static( + "Error: P2SH addresses can not be created using this RPC method.", + ), + _ => LegacyCode::Wallet.with_message(format!( + "Error: receiver type {typecode:?} is not supported.", + )) + } + AddressGenerationError::KeyNotAvailable(typecode) => { + LegacyCode::Wallet.with_message(format!( + "Error: account {account} cannot generate a receiver component with type {typecode:?}.", + )) + } + AddressGenerationError::ShieldedReceiverRequired => { + LegacyCode::Wallet.with_static( + "Error: cannot generate an address containing no shielded receivers." + ) + } + AddressGenerationError::UnsupportedTransparentKeyScope(s) => { + LegacyCode::Wallet.with_message(format!( + "Error: Address generation is not supported for transparent key scope {s:?}", + )) + } + AddressGenerationError::Bip32DerivationError(e) => { + LegacyCode::Wallet.with_message(format!( + "An error occurred in BIP 32 address derivation: {e}" + )) + } + } +} + fn map_sqlite_error(e: SqliteClientError, account: &JsonValue) -> ErrorObjectOwned { match e { SqliteClientError::TransparentDerivation(error) => LegacyCode::Wallet.with_message(format!( "Error: failed to derive a transparent component: {error}", )), - SqliteClientError::AddressGeneration(e) => match e { - AddressGenerationError::InvalidTransparentChildIndex(diversifier_index) => { - LegacyCode::Wallet.with_message(format!( - "Error: diversifier index {} cannot generate an address with a transparent receiver.", - u128::from(diversifier_index), - )) - } - AddressGenerationError::InvalidSaplingDiversifierIndex(diversifier_index) => { - LegacyCode::Wallet.with_message(format!( - "Error: diversifier index {} cannot generate an address with a Sapling receiver.", - u128::from(diversifier_index), - )) - } - AddressGenerationError::DiversifierSpaceExhausted => LegacyCode::Wallet.with_static( - "Error: ran out of diversifier indices. Generate a new account with z_getnewaccount" - ), - AddressGenerationError::ReceiverTypeNotSupported(typecode) => match typecode { - unified::Typecode::P2sh => LegacyCode::Wallet.with_static( - "Error: P2SH addresses can not be created using this RPC method.", - ), - _ => LegacyCode::Wallet.with_message(format!( - "Error: receiver type {typecode:?} is not supported.", - )) - }, - AddressGenerationError::KeyNotAvailable(typecode) => LegacyCode::Wallet.with_message(format!( - "Error: account {account} cannot generate a receiver component with type {typecode:?}.", - )), - AddressGenerationError::ShieldedReceiverRequired => LegacyCode::Wallet.with_static( - "Error: cannot generate an address containing no shielded receivers." - ), - }, + SqliteClientError::AddressGeneration(e) => map_address_generation_error(e, account), SqliteClientError::AccountUnknown => LegacyCode::Wallet.with_message(format!( "Error: account {account} has not been generated by z_getnewaccount." )), diff --git a/zallet/src/components/json_rpc/methods/list_addresses.rs b/zallet/src/components/json_rpc/methods/list_addresses.rs index fbc3fe15..8249c39d 100644 --- a/zallet/src/components/json_rpc/methods/list_addresses.rs +++ b/zallet/src/components/json_rpc/methods/list_addresses.rs @@ -158,20 +158,20 @@ pub(crate) fn call(wallet: &DbConnection) -> Response { let addr = address_info.address(); match addr { Address::Transparent(_) | Address::Tex(_) => { - match address_info.transparent_key_scope() { - Some(TransparentKeyScope::EXTERNAL) => { + match address_info.source().transparent_key_scope() { + Some(&TransparentKeyScope::EXTERNAL) => { transparent_addresses.push(addr.encode(wallet.params())); } - Some(TransparentKeyScope::INTERNAL) => { + Some(&TransparentKeyScope::INTERNAL) => { transparent_change_addresses.push(addr.encode(wallet.params())); } - Some(TransparentKeyScope::EPHEMERAL) => { + Some(&TransparentKeyScope::EPHEMERAL) => { transparent_ephemeral_addresses.push(addr.encode(wallet.params())); } _ => { error!( "Unexpected {:?} for address {}", - address_info.transparent_key_scope(), + address_info.source().transparent_key_scope(), addr.encode(wallet.params()), ); return Err(RpcErrorCode::InternalError.into()); @@ -179,21 +179,38 @@ pub(crate) fn call(wallet: &DbConnection) -> Response { } } Address::Sapling(_) => sapling_addresses.push(addr.encode(wallet.params())), - Address::Unified(addr) => unified_addresses.push(UnifiedAddress { - diversifier_index: address_info.diversifier_index().into(), - receiver_types: addr - .receiver_types() - .into_iter() - .map(|r| match r { - unified::Typecode::P2pkh => "p2pkh".into(), - unified::Typecode::P2sh => "p2sh".into(), - unified::Typecode::Sapling => "sapling".into(), - unified::Typecode::Orchard => "orchard".into(), - unified::Typecode::Unknown(typecode) => format!("unknown({typecode})"), - }) - .collect(), - address: addr.encode(wallet.params()), - }), + Address::Unified(addr) => { + let address = addr.encode(wallet.params()); + unified_addresses.push(UnifiedAddress { + diversifier_index: match address_info.source() { + zcash_client_backend::data_api::AddressSource::Derived { + diversifier_index, + .. + } => diversifier_index.into(), + zcash_client_backend::data_api::AddressSource::Standalone => { + error!( + "Unified address {} lacks HD derivation information.", + address + ); + return Err(RpcErrorCode::InternalError.into()); + } + }, + receiver_types: addr + .receiver_types() + .into_iter() + .map(|r| match r { + unified::Typecode::P2pkh => "p2pkh".into(), + unified::Typecode::P2sh => "p2sh".into(), + unified::Typecode::Sapling => "sapling".into(), + unified::Typecode::Orchard => "orchard".into(), + unified::Typecode::Unknown(typecode) => { + format!("unknown({typecode})") + } + }) + .collect(), + address, + }) + } } } diff --git a/zallet/src/components/json_rpc/methods/list_unspent.rs b/zallet/src/components/json_rpc/methods/list_unspent.rs index 00ec43c0..30b37e0e 100644 --- a/zallet/src/components/json_rpc/methods/list_unspent.rs +++ b/zallet/src/components/json_rpc/methods/list_unspent.rs @@ -214,7 +214,7 @@ pub(crate) fn call( let is_watch_only = !matches!(account.purpose(), AccountPurpose::Spending { .. }); let utxos = wallet - .get_transparent_receivers(account_id, true) + .get_transparent_receivers(account_id, true, true) .map_err(|e| { RpcError::owned( LegacyCode::Database.into(), @@ -250,7 +250,7 @@ pub(crate) fn call( Some(format!("{e}")), ) })? - .is_some_and(|m| m.scope() == TransparentKeyScope::INTERNAL); + .is_some_and(|m| m.scope() == Some(TransparentKeyScope::INTERNAL)); unspent_outputs.push(UnspentOutput { txid: utxo.outpoint().txid().to_string(), diff --git a/zallet/src/components/json_rpc/methods/view_transaction.rs b/zallet/src/components/json_rpc/methods/view_transaction.rs index f24de09b..978d8656 100644 --- a/zallet/src/components/json_rpc/methods/view_transaction.rs +++ b/zallet/src/components/json_rpc/methods/view_transaction.rs @@ -497,7 +497,10 @@ pub(crate) async fn call( account_uuid, Some(address.encode(wallet.params())), wallet_scope.is_none(), - matches!(wallet_scope, Some(TransparentKeyScope::INTERNAL)), + // The outer `Some` indicates that we have address metadata; the inner + // `Option` is `None` for addresses associated with imported transparent + // spending keys. + wallet_scope == Some(Some(TransparentKeyScope::INTERNAL)), ) } }; diff --git a/zallet/src/components/json_rpc/methods/z_send_many.rs b/zallet/src/components/json_rpc/methods/z_send_many.rs index c41cf837..962e590e 100644 --- a/zallet/src/components/json_rpc/methods/z_send_many.rs +++ b/zallet/src/components/json_rpc/methods/z_send_many.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::convert::Infallible; use std::num::NonZeroU32; @@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use zaino_state::FetchServiceSubscriber; use zcash_address::{ZcashAddress, unified}; +use zcash_client_backend::data_api::wallet::SpendingKeys; use zcash_client_backend::{ data_api::{ Account, @@ -390,7 +391,8 @@ async fn run( ¶ms, &prover, &prover, - &usk, + // TODO: Look up spending keys for imported transparent addresses used in the proposal. + &SpendingKeys::new(usk, HashMap::new()), OvkPolicy::Sender, &proposal, ) diff --git a/zallet/src/components/sync.rs b/zallet/src/components/sync.rs index 31cb2185..6ab96a7f 100644 --- a/zallet/src/components/sync.rs +++ b/zallet/src/components/sync.rs @@ -432,7 +432,7 @@ async fn poll_transparent( let addresses = db_data .get_account_ids()? .into_iter() - .map(|account| db_data.get_transparent_receivers(account, true)) + .map(|account| db_data.get_transparent_receivers(account, true, true)) .collect::, _>>()? .into_iter() .flat_map(|m| m.into_keys().map(|addr| addr.encode(params))) From 9d1d0adef46c1b9c07e0a46a4fff6cabd200931e Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 12 Sep 2025 12:48:17 -0600 Subject: [PATCH 02/15] Include network information with database connections. --- zallet/src/components/database.rs | 4 ++-- zallet/src/components/database/connection.rs | 11 +++++++---- .../json_rpc/methods/view_transaction.rs | 6 +++--- zallet/src/components/keystore.rs | 19 ++++++++++--------- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/zallet/src/components/database.rs b/zallet/src/components/database.rs index af967392..aed35513 100644 --- a/zallet/src/components/database.rs +++ b/zallet/src/components/database.rs @@ -70,7 +70,7 @@ impl Database { // any changes (including migrations, some of which make use of the network // params), to avoid leaving the database in an inconsistent state. We can // assume the presence of this table, as it's added by the initial migrations. - handle.with_raw(|conn| { + handle.with_raw(|conn, _| { let wallet_network_type = conn .query_row( "SELECT network_type FROM ext_zallet_db_wallet_metadata", @@ -127,7 +127,7 @@ impl Database { // an easy way to detect whether any migrations actually ran, so we check whether // the most recent entry matches the current version tuple, and only record an // entry if it doesn't. - handle.with_raw_mut(|conn| { + handle.with_raw_mut(|conn, _| { #[allow(clippy::const_is_empty)] let (git_revision, clean) = (!crate::build::COMMIT_HASH.is_empty()) .then_some((crate::build::COMMIT_HASH, crate::build::GIT_CLEAN)) diff --git a/zallet/src/components/database/connection.rs b/zallet/src/components/database/connection.rs index 73e4197f..0683bd80 100644 --- a/zallet/src/components/database/connection.rs +++ b/zallet/src/components/database/connection.rs @@ -128,17 +128,20 @@ impl DbConnection { }) } - pub(crate) fn with_raw(&self, f: impl FnOnce(&rusqlite::Connection) -> T) -> T { + pub(crate) fn with_raw(&self, f: impl FnOnce(&rusqlite::Connection, &Network) -> T) -> T { tokio::task::block_in_place(|| { let _guard = self.lock.read().unwrap(); - f(self.inner.lock().unwrap().as_ref()) + f(self.inner.lock().unwrap().as_ref(), &self.params) }) } - pub(crate) fn with_raw_mut(&self, f: impl FnOnce(&mut rusqlite::Connection) -> T) -> T { + pub(crate) fn with_raw_mut( + &self, + f: impl FnOnce(&mut rusqlite::Connection, &Network) -> T, + ) -> T { tokio::task::block_in_place(|| { let _guard = self.lock.write().unwrap(); - f(self.inner.lock().unwrap().as_mut()) + f(self.inner.lock().unwrap().as_mut(), &self.params) }) } } diff --git a/zallet/src/components/json_rpc/methods/view_transaction.rs b/zallet/src/components/json_rpc/methods/view_transaction.rs index 978d8656..a4326736 100644 --- a/zallet/src/components/json_rpc/methods/view_transaction.rs +++ b/zallet/src/components/json_rpc/methods/view_transaction.rs @@ -351,7 +351,7 @@ pub(crate) async fn call( }; wallet - .with_raw(|conn| { + .with_raw(|conn, _| { conn.query_row( &format!( "SELECT txid, {output_prefix}_index, accounts.uuid, address, value @@ -391,7 +391,7 @@ pub(crate) async fn call( fallback_addr: impl FnOnce() -> Option, ) -> RpcResult> { Ok(wallet - .with_raw(|conn| { + .with_raw(|conn, _| { conn.query_row( "SELECT to_address FROM sent_notes @@ -797,7 +797,7 @@ pub(crate) async fn call( .map_err(|e| LegacyCode::Database.with_message(format!("Failed to compute fee: {e}")))?; #[cfg(zallet_build = "wallet")] - let accounts = wallet.with_raw(|conn| { + let accounts = wallet.with_raw(|conn, _| { let mut stmt = conn .prepare( "SELECT account_uuid, account_balance_delta diff --git a/zallet/src/components/keystore.rs b/zallet/src/components/keystore.rs index fb603e38..8c60cbf2 100644 --- a/zallet/src/components/keystore.rs +++ b/zallet/src/components/keystore.rs @@ -125,6 +125,7 @@ use tokio::{ }; use zip32::fingerprint::SeedFingerprint; +use crate::network::Network; use crate::{ config::ZalletConfig, error::{Error, ErrorKind}, @@ -364,14 +365,14 @@ impl KeyStore { async fn with_db( &self, - f: impl FnOnce(&rusqlite::Connection) -> Result, + f: impl FnOnce(&rusqlite::Connection, &Network) -> Result, ) -> Result { self.db.handle().await?.with_raw(f) } async fn with_db_mut( &self, - f: impl FnOnce(&mut rusqlite::Connection) -> Result, + f: impl FnOnce(&mut rusqlite::Connection, &Network) -> Result, ) -> Result { self.db.handle().await?.with_raw_mut(f) } @@ -394,7 +395,7 @@ impl KeyStore { let now = ::time::OffsetDateTime::now_utc(); - self.with_db_mut(|conn| { + self.with_db_mut(|conn, _| { let mut stmt = conn .prepare( "INSERT INTO ext_zallet_keystore_age_recipients @@ -419,7 +420,7 @@ impl KeyStore { /// Fetches the age recipients for this wallet from the database. async fn recipients(&self) -> Result>, Error> { - self.with_db(|conn| { + self.with_db(|conn, _| { let mut stmt = conn .prepare( "SELECT recipient @@ -452,7 +453,7 @@ impl KeyStore { /// Lists the fingerprint of every seed available in the keystore. pub(crate) async fn list_seed_fingerprints(&self) -> Result, Error> { - self.with_db(|conn| { + self.with_db(|conn, _| { let mut stmt = conn .prepare( "SELECT hd_seed_fingerprint @@ -475,7 +476,7 @@ impl KeyStore { pub(crate) async fn list_legacy_seed_fingerprints( &self, ) -> Result, Error> { - self.with_db(|conn| { + self.with_db(|conn, _| { let mut stmt = conn .prepare( "SELECT hd_seed_fingerprint @@ -511,7 +512,7 @@ impl KeyStore { let encrypted_mnemonic = encrypt_string(&recipients, mnemonic.expose_secret()) .map_err(|e| ErrorKind::Generic.context(e))?; - self.with_db_mut(|conn| { + self.with_db_mut(|conn, _| { conn.execute( "INSERT INTO ext_zallet_keystore_mnemonics VALUES (:hd_seed_fingerprint, :encrypted_mnemonic) @@ -542,7 +543,7 @@ impl KeyStore { let encrypted_legacy_seed = encrypt_legacy_seed_bytes(&recipients, legacy_seed) .map_err(|e| ErrorKind::Generic.context(e))?; - self.with_db_mut(|conn| { + self.with_db_mut(|conn, _| { conn.execute( "INSERT INTO ext_zallet_keystore_legacy_seeds VALUES (:hd_seed_fingerprint, :encrypted_legacy_seed) @@ -569,7 +570,7 @@ impl KeyStore { } let encrypted_mnemonic = self - .with_db(|conn| { + .with_db(|conn, _| { Ok(conn .query_row( "SELECT encrypted_mnemonic From d50ac7faa30a120e7b0219a4fdeee8ec2c3447a2 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 3 Apr 2025 16:39:46 -0600 Subject: [PATCH 03/15] Add `migrate_zcashd_wallet` command. This adds support for importing the secret key material from a `zcashd` `wallet.dat` file into `zallet`. It does not at present support import of wallet history; a wallet initialized using this command will perform recovery via scanning the chain. --- Cargo.lock | 1186 +++++++++++++++-- Cargo.toml | 3 + supply-chain/audits.toml | 6 +- supply-chain/config.toml | 250 +++- supply-chain/imports.lock | 84 ++ zallet/Cargo.toml | 9 +- zallet/i18n/en-US/zallet.ftl | 37 + zallet/src/cli.rs | 29 +- zallet/src/commands.rs | 2 + zallet/src/commands/generate_mnemonic.rs | 5 +- zallet/src/commands/import_mnemonic.rs | 4 +- zallet/src/commands/migrate_zcash_conf.rs | 6 +- zallet/src/commands/migrate_zcashd_wallet.rs | 654 +++++++++ zallet/src/components/database/connection.rs | 2 +- zallet/src/components/database/tests.rs | 2 + .../json_rpc/methods/view_transaction.rs | 4 + zallet/src/components/keystore.rs | 99 +- zallet/src/components/keystore/db.rs | 40 +- .../keystore/db/migrations/initial_setup.rs | 8 + zallet/src/lib.rs | 1 + zallet/src/rosetta.rs | 69 + .../tests/cmd/migrate_zcash_conf_mainnet.toml | 2 +- .../tests/cmd/migrate_zcash_conf_regtest.toml | 2 +- .../tests/cmd/migrate_zcash_conf_testnet.toml | 2 +- 24 files changed, 2367 insertions(+), 139 deletions(-) create mode 100644 zallet/src/commands/migrate_zcashd_wallet.rs create mode 100644 zallet/src/rosetta.rs diff --git a/Cargo.lock b/Cargo.lock index e34c5fa5..af276f36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -242,6 +242,18 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -417,6 +429,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.21.7" @@ -444,6 +462,122 @@ dependencies = [ "serde", ] +[[package]] +name = "bc-components" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ec457b709c34405b86c5f63f5d1bb706715383f6e1082de2a1e02f88b27fd40" +dependencies = [ + "anyhow", + "bc-crypto", + "bc-rand", + "bc-tags", + "bc-ur", + "dcbor", + "hex", + "miniz_oxide 0.7.4", + "pqcrypto-mldsa", + "pqcrypto-mlkem", + "pqcrypto-traits", + "rand_core 0.6.4", + "ssh-agent-client-rs", + "ssh-key", + "sskr", + "url", + "zeroize", +] + +[[package]] +name = "bc-crypto" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8647a24a5edf0ed9ea01f5258e7d092465c6f9b0508b32166c4dccbe1c291c9c" +dependencies = [ + "anyhow", + "argon2", + "bc-rand", + "chacha20poly1305", + "crc32fast", + "ed25519-dalek", + "hex", + "hkdf", + "hmac 0.12.1", + "pbkdf2", + "rand 0.8.5", + "scrypt", + "secp256k1 0.30.0", + "sha2 0.10.8", + "thiserror 1.0.69", + "x25519-dalek", +] + +[[package]] +name = "bc-envelope" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ff0ae3d610f7016ca7d6702f06c0dbfb63780ecb59b81497cfc7f4b134327b" +dependencies = [ + "anyhow", + "bc-components", + "bc-crypto", + "bc-rand", + "bc-ur", + "bytes", + "dcbor", + "hex", + "itertools 0.11.0", + "known-values", + "paste", + "ssh-key", + "thiserror 1.0.69", +] + +[[package]] +name = "bc-rand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd5f55d05b06624b5b3c2aa7d7e1c6ab06a41e186b66fef68cfd2ad08149ba1" +dependencies = [ + "getrandom 0.2.15", + "lazy_static", + "num-traits 0.2.19", + "rand 0.8.5", + "rand_core 0.6.4", + "rand_xoshiro", +] + +[[package]] +name = "bc-shamir" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b6bdb46e87c24147d929cd78a93316ac118284825babeec47becd00395ee9eb" +dependencies = [ + "bc-crypto", + "bc-rand", + "thiserror 1.0.69", +] + +[[package]] +name = "bc-tags" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfd44331570ae0d61d92f7d62408b215f6833da9c8d464eb09706a079363ce4" +dependencies = [ + "dcbor", + "paste", +] + +[[package]] +name = "bc-ur" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c65e33cfacc82d2e6ef819ac08e73a429de526a4b019135fddbd2e14477ec2b2" +dependencies = [ + "dcbor", + "thiserror 1.0.69", + "ur", +] + [[package]] name = "bech32" version = "0.9.1" @@ -540,6 +674,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bip32" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db40d3dfbeab4e031d78c844642fa0caa0b0db11ce1607ac9d2986dff1405c69" +dependencies = [ + "bs58", + "hmac 0.12.1", + "rand_core 0.6.4", + "ripemd 0.1.3", + "secp256k1 0.27.0", + "sha2 0.10.8", + "subtle", + "zeroize", +] + [[package]] name = "bip32" version = "0.6.0-pre.1" @@ -550,12 +700,43 @@ dependencies = [ "hmac 0.13.0-pre.4", "rand_core 0.6.4", "ripemd 0.2.0-pre.4", - "secp256k1", + "secp256k1 0.29.1", "sha2 0.11.0-pre.4", "subtle", "zeroize", ] +[[package]] +name = "bitcoin-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" + +[[package]] +name = "bitcoin-private" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" + +[[package]] +name = "bitcoin_hashes" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501" +dependencies = [ + "bitcoin-private", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -658,6 +839,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "bridgetree" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef977c7f8e75aa81fc589064c121ab8d32448b7939d34d58df479aa93e65ea5" +dependencies = [ + "incrementalmerkletree 0.7.1", +] + [[package]] name = "bs58" version = "0.5.1" @@ -1072,6 +1262,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.4.2" @@ -1121,6 +1326,18 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -1128,6 +1345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -1226,6 +1444,21 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "dcbor" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402f083d3c2daef249d4c06ef827c8b743ab88536f73dae52201f4de5abb0354" +dependencies = [ + "anyhow", + "chrono", + "half", + "hex", + "paste", + "thiserror 1.0.69", + "unicode-normalization", +] + [[package]] name = "deadpool" version = "0.12.2" @@ -1335,6 +1568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid", "crypto-common 0.1.6", "subtle", ] @@ -1382,6 +1616,12 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + [[package]] name = "document-features" version = "0.2.11" @@ -1417,6 +1657,22 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "dsa" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689" +dependencies = [ + "digest 0.10.7", + "num-bigint-dig", + "num-traits 0.2.19", + "pkcs8", + "rfc6979", + "sha2 0.10.8", + "signature", + "zeroize", +] + [[package]] name = "dunce" version = "1.0.5" @@ -1429,6 +1685,20 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -1440,6 +1710,21 @@ dependencies = [ "signature", ] +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2 0.10.8", + "subtle", + "zeroize", +] + [[package]] name = "ed25519-zebra" version = "4.0.3" @@ -1462,6 +1747,25 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encode_unicode" version = "1.0.0" @@ -1529,6 +1833,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "f4jumble" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d42773cb15447644d170be20231a3268600e0c4cea8987d013b93ac973d3cf7" +dependencies = [ + "blake2b_simd", +] + [[package]] name = "f4jumble" version = "0.1.1" @@ -1822,6 +2135,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1930,6 +2244,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "halo2_gadgets" version = "0.3.1" @@ -2055,6 +2379,15 @@ dependencies = [ "serde", ] +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec", +] + [[package]] name = "hex-literal" version = "0.4.1" @@ -2512,6 +2845,15 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "incrementalmerkletree" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216c71634ac6f6ed13c2102d64354c0a04dcbdc30e31692c5972d3974d8b6d97" +dependencies = [ + "either", +] + [[package]] name = "incrementalmerkletree" version = "0.8.2" @@ -2558,6 +2900,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "interprocess" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d" +dependencies = [ + "doctest-file", + "libc", + "recvmsg", + "widestring", + "windows-sys 0.52.0", +] + [[package]] name = "intl-memoizer" version = "0.5.2" @@ -2612,6 +2967,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.1" @@ -2802,6 +3166,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "known-values" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c3c5085ed0cb90e914af0906adca5fd87dd4af28ab6dc254edca97eee23a73" +dependencies = [ + "bc-components", + "dcbor", + "paste", +] + [[package]] name = "lazy-regex" version = "3.4.1" @@ -3076,6 +3451,26 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minicbor" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7005aaf257a59ff4de471a9d5538ec868a21586534fff7f85dd97d4043a6139" +dependencies = [ + "minicbor-derive", +] + +[[package]] +name = "minicbor-derive" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1154809406efdb7982841adb6311b3d095b46f78342dd646736122fe6b19e267" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3145,6 +3540,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + [[package]] name = "nonempty" version = "0.11.0" @@ -3177,6 +3578,23 @@ dependencies = [ "num-traits 0.2.19", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits 0.2.19", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -3192,6 +3610,17 @@ dependencies = [ "num-traits 0.2.19", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits 0.2.19", +] + [[package]] name = "num-traits" version = "0.1.43" @@ -3297,6 +3726,41 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "orchard" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f4cf75baf85bbd6f15eb919b7e70afdc4a311eef0a3e8a053e65542fe2b58e" +dependencies = [ + "aes", + "bitvec", + "blake2b_simd", + "core2 0.3.3", + "ff", + "fpe", + "getset", + "group", + "halo2_gadgets", + "halo2_poseidon", + "halo2_proofs", + "hex", + "incrementalmerkletree 0.7.1", + "lazy_static", + "memuse", + "nonempty 0.7.0", + "pasta_curves", + "rand 0.8.5", + "reddsa", + "serde", + "sinsemilla", + "subtle", + "tracing", + "visibility", + "zcash_note_encryption", + "zcash_spec 0.1.2", + "zip32 0.1.3", +] + [[package]] name = "orchard" version = "0.11.0" @@ -3315,10 +3779,10 @@ dependencies = [ "halo2_poseidon", "halo2_proofs", "hex", - "incrementalmerkletree", + "incrementalmerkletree 0.8.2", "lazy_static", "memuse", - "nonempty", + "nonempty 0.11.0", "pasta_curves", "rand 0.8.5", "reddsa", @@ -3328,8 +3792,8 @@ dependencies = [ "tracing", "visibility", "zcash_note_encryption", - "zcash_spec", - "zip32", + "zcash_spec 0.2.1", + "zip32 0.2.0", ] [[package]] @@ -3364,6 +3828,44 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.8", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.8", +] + +[[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core 0.6.4", + "sha2 0.10.8", +] + [[package]] name = "pairing" version = "0.23.0" @@ -3450,6 +3952,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pbkdf2" version = "0.12.2" @@ -3461,6 +3969,15 @@ dependencies = [ "password-hash", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -3565,6 +4082,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -3613,6 +4141,51 @@ dependencies = [ "zerocopy 0.8.24", ] +[[package]] +name = "pqcrypto-internals" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a326caf27cbf2ac291ca7fd56300497ba9e76a8cc6a7d95b7a18b57f22b61d" +dependencies = [ + "cc", + "dunce", + "getrandom 0.3.2", + "libc", +] + +[[package]] +name = "pqcrypto-mldsa" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f812cd126a2582599478a434fea75937b4b05d234c64a49e0cea129e130528" +dependencies = [ + "cc", + "glob", + "libc", + "paste", + "pqcrypto-internals", + "pqcrypto-traits", +] + +[[package]] +name = "pqcrypto-mlkem" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb14d207f3749e8a59a026c22ceaa72d70fff931cfbf4c8d9b08f3fc56dc6e60" +dependencies = [ + "cc", + "glob", + "libc", + "pqcrypto-internals", + "pqcrypto-traits", +] + +[[package]] +name = "pqcrypto-traits" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e851c7654eed9e68d7d27164c454961a616cf8c203d500607ef22c737b51bb" + [[package]] name = "prettyplease" version = "0.2.32" @@ -3623,6 +4196,15 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -3962,6 +4544,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rayon" version = "1.10.0" @@ -3982,6 +4573,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "recvmsg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" + [[package]] name = "reddsa" version = "0.5.1" @@ -4000,6 +4597,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "redjubjub" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a60db2c3bc9c6fd1e8631fee75abc008841d27144be744951d6b9b75f9b569c" +dependencies = [ + "rand_core 0.6.4", + "reddsa", + "serde", + "thiserror 1.0.69", + "zeroize", +] + [[package]] name = "redjubjub" version = "0.8.0" @@ -4142,6 +4752,16 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -4216,6 +4836,27 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rsa" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +dependencies = [ + "const-oid", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits 0.2.19", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "sha2 0.10.8", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rtoolbox" version = "0.0.2" @@ -4421,6 +5062,38 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "sapling-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfff8cfce16aeb38da50b8e2ed33c9018f30552beff2210c266662a021b17f38" +dependencies = [ + "aes", + "bellman", + "bitvec", + "blake2b_simd", + "blake2s_simd", + "bls12_381", + "byteorder", + "document-features", + "ff", + "fpe", + "group", + "hex", + "incrementalmerkletree 0.7.1", + "jubjub", + "lazy_static", + "memuse", + "rand 0.8.5", + "rand_core 0.6.4", + "redjubjub 0.7.0", + "subtle", + "tracing", + "zcash_note_encryption", + "zcash_spec 0.1.2", + "zip32 0.1.3", +] + [[package]] name = "sapling-crypto" version = "0.5.0" @@ -4440,18 +5113,18 @@ dependencies = [ "getset", "group", "hex", - "incrementalmerkletree", + "incrementalmerkletree 0.8.2", "jubjub", "lazy_static", "memuse", "rand 0.8.5", "rand_core 0.6.4", - "redjubjub", + "redjubjub 0.8.0", "subtle", "tracing", "zcash_note_encryption", - "zcash_spec", - "zip32", + "zcash_spec 0.2.1", + "zip32 0.2.0", ] [[package]] @@ -4520,16 +5193,59 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +dependencies = [ + "secp256k1-sys 0.8.2", +] + [[package]] name = "secp256k1" version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "secp256k1-sys", + "secp256k1-sys 0.10.1", "serde", ] +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes 0.14.0", + "rand 0.8.5", + "secp256k1-sys 0.10.1", +] + +[[package]] +name = "secp256k1-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4473013577ec77b4ee3668179ef1186df3146e2cf2d927bd200974c6fe60fd99" +dependencies = [ + "cc", +] + [[package]] name = "secp256k1-sys" version = "0.10.1" @@ -4760,7 +5476,7 @@ checksum = "637e95dcd06bc1bb3f86ed9db1e1832a70125f32daae071ef37dcb7701b7d4fe" dependencies = [ "bitflags 2.9.0", "either", - "incrementalmerkletree", + "incrementalmerkletree 0.8.2", "tracing", ] @@ -4785,6 +5501,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ + "digest 0.10.7", "rand_core 0.6.4", ] @@ -4899,6 +5616,76 @@ dependencies = [ "der", ] +[[package]] +name = "ssh-agent-client-rs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc3c4dd567de6556169a17ce106042cac86a5f5fe7ec9c89c924b57c47707b73" +dependencies = [ + "bytes", + "interprocess", + "signature", + "ssh-encoding", + "ssh-key", + "thiserror 2.0.12", +] + +[[package]] +name = "ssh-cipher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" +dependencies = [ + "cipher", + "ssh-encoding", +] + +[[package]] +name = "ssh-encoding" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15" +dependencies = [ + "base64ct", + "pem-rfc7468", + "sha2 0.10.8", +] + +[[package]] +name = "ssh-key" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3" +dependencies = [ + "dsa", + "ed25519-dalek", + "num-bigint-dig", + "p256", + "p384", + "p521", + "rand_core 0.6.4", + "rsa", + "sec1", + "sha1", + "sha2 0.10.8", + "signature", + "ssh-cipher", + "ssh-encoding", + "subtle", + "zeroize", +] + +[[package]] +name = "sskr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac11f99ef5cbbbc90a1eeeb4425d53af1e3ef9ff279102c009f643be2058e25" +dependencies = [ + "bc-rand", + "bc-shamir", + "thiserror 1.0.69", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -5659,6 +6446,19 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ur" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010f24a953db5d22d0010969ca3bbf40b3857b89f47c0f7be0da4c2d7ded0760" +dependencies = [ + "bitcoin_hashes 0.12.0", + "crc", + "minicbor", + "phf", + "rand_xoshiro", +] + [[package]] name = "url" version = "2.5.4" @@ -5977,6 +6777,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "widestring" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" + [[package]] name = "winapi" version = "0.3.9" @@ -6415,7 +7221,7 @@ dependencies = [ "tracing", "url", "zaino-proto", - "zcash_protocol", + "zcash_protocol 0.6.1", "zebra-chain", "zebra-rpc", "zebra-state", @@ -6456,7 +7262,7 @@ dependencies = [ "lazy-regex", "lmdb", "lmdb-sys", - "nonempty", + "nonempty 0.11.0", "primitive-types 0.13.1", "prost", "reqwest", @@ -6473,10 +7279,10 @@ dependencies = [ "whoami", "zaino-fetch", "zaino-proto", - "zcash_address", - "zcash_keys", - "zcash_primitives", - "zcash_protocol", + "zcash_address 0.9.0", + "zcash_keys 0.10.1", + "zcash_primitives 0.24.1", + "zcash_protocol 0.6.1", "zcash_transparent", "zebra-chain", "zebra-rpc", @@ -6490,6 +7296,7 @@ dependencies = [ "abscissa_core", "abscissa_tokio", "age", + "anyhow", "async-trait", "bip0039", "clap", @@ -6509,13 +7316,13 @@ dependencies = [ "hyper", "i18n-embed", "i18n-embed-fl", - "incrementalmerkletree", + "incrementalmerkletree 0.8.2", "jsonrpsee", "jsonrpsee-http-client", "known-folders", "nix", "once_cell", - "orchard", + "orchard 0.11.0", "phf", "quote", "rand 0.8.5", @@ -6524,11 +7331,11 @@ dependencies = [ "rusqlite", "rust-embed", "rust_decimal", - "sapling-crypto", + "sapling-crypto 0.5.0", "schemars", "schemerz", "schemerz-rusqlite", - "secp256k1", + "secp256k1 0.29.1", "secrecy 0.8.0", "serde", "serde_json", @@ -6550,19 +7357,35 @@ dependencies = [ "zaino-fetch", "zaino-proto", "zaino-state", - "zcash_address", + "zcash_address 0.9.0", "zcash_client_backend", "zcash_client_sqlite", - "zcash_keys", + "zcash_keys 0.10.1", "zcash_note_encryption", - "zcash_primitives", + "zcash_primitives 0.24.1", "zcash_proofs", - "zcash_protocol", + "zcash_protocol 0.6.1", "zcash_transparent", "zebra-chain", "zebra-rpc", "zebra-state", - "zip32", + "zewif", + "zewif-zcashd", + "zip32 0.2.0", +] + +[[package]] +name = "zcash_address" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32b380113014b136aec579ea1c07fef747a818b9ac97d91daa0ec3b7a642bc0" +dependencies = [ + "bech32 0.11.0", + "bs58", + "core2 0.3.3", + "f4jumble 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zcash_encoding 0.2.2", + "zcash_protocol 0.4.3", ] [[package]] @@ -6573,9 +7396,9 @@ dependencies = [ "bech32 0.11.0", "bs58", "core2 0.3.3", - "f4jumble", - "zcash_encoding", - "zcash_protocol", + "f4jumble 0.1.1 (git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f)", + "zcash_encoding 0.3.0", + "zcash_protocol 0.6.1", ] [[package]] @@ -6586,7 +7409,7 @@ dependencies = [ "async-trait", "base64 0.22.1", "bech32 0.11.0", - "bip32", + "bip32 0.6.0-pre.1", "bls12_381", "bs58", "byteorder", @@ -6597,17 +7420,17 @@ dependencies = [ "group", "hex", "hyper-util", - "incrementalmerkletree", + "incrementalmerkletree 0.8.2", "memuse", - "nonempty", - "orchard", + "nonempty 0.11.0", + "orchard 0.11.0", "pasta_curves", "percent-encoding", "prost", "rand_core 0.6.4", "rayon", - "sapling-crypto", - "secp256k1", + "sapling-crypto 0.5.0", + "secp256k1 0.29.1", "secrecy 0.8.0", "shardtree", "subtle", @@ -6617,14 +7440,14 @@ dependencies = [ "tonic-build 0.13.0", "tracing", "which 7.0.2", - "zcash_address", - "zcash_encoding", - "zcash_keys", + "zcash_address 0.9.0", + "zcash_encoding 0.3.0", + "zcash_keys 0.10.1", "zcash_note_encryption", - "zcash_primitives", - "zcash_protocol", + "zcash_primitives 0.24.1", + "zcash_protocol 0.6.1", "zcash_transparent", - "zip32", + "zip32 0.2.0", "zip321", ] @@ -6633,28 +7456,28 @@ name = "zcash_client_sqlite" version = "0.17.3" source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ - "bip32", + "bip32 0.6.0-pre.1", "bitflags 2.9.0", "bs58", "byteorder", "document-features", "group", "hex", - "incrementalmerkletree", + "incrementalmerkletree 0.8.2", "jubjub", "maybe-rayon", - "nonempty", - "orchard", + "nonempty 0.11.0", + "orchard 0.11.0", "prost", "rand 0.8.5", "rand_core 0.6.4", "rand_distr", "regex", "rusqlite", - "sapling-crypto", + "sapling-crypto 0.5.0", "schemerz", "schemerz-rusqlite", - "secp256k1", + "secp256k1 0.29.1", "secrecy 0.8.0", "shardtree", "static_assertions", @@ -6662,14 +7485,24 @@ dependencies = [ "time", "tracing", "uuid", - "zcash_address", + "zcash_address 0.9.0", "zcash_client_backend", - "zcash_encoding", - "zcash_keys", - "zcash_primitives", - "zcash_protocol", + "zcash_encoding 0.3.0", + "zcash_keys 0.10.1", + "zcash_primitives 0.24.1", + "zcash_protocol 0.6.1", "zcash_transparent", - "zip32", + "zip32 0.2.0", +] + +[[package]] +name = "zcash_encoding" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3654116ae23ab67dd1f849b01f8821a8a156f884807ff665eac109bf28306c4d" +dependencies = [ + "core2 0.3.3", + "nonempty 0.7.0", ] [[package]] @@ -6678,7 +7511,7 @@ version = "0.3.0" source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ "core2 0.3.3", - "nonempty", + "nonempty 0.11.0", ] [[package]] @@ -6691,34 +7524,65 @@ dependencies = [ "primitive-types 0.12.2", ] +[[package]] +name = "zcash_keys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca7dfe792eb5b7d13bdcf78575116d968a5b66ffaa9a66e8673071d47d25e540" +dependencies = [ + "bech32 0.9.1", + "bip32 0.5.3", + "blake2b_simd", + "bls12_381", + "bs58", + "document-features", + "group", + "memuse", + "nonempty 0.7.0", + "orchard 0.10.2", + "rand_core 0.6.4", + "sapling-crypto 0.3.0", + "secrecy 0.8.0", + "subtle", + "tracing", + "zcash_address 0.6.3", + "zcash_encoding 0.2.2", + "zcash_primitives 0.19.1", + "zcash_protocol 0.4.3", + "zip32 0.1.3", +] + [[package]] name = "zcash_keys" version = "0.10.1" source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ "bech32 0.11.0", - "bip32", + "bip0039", + "bip32 0.6.0-pre.1", "blake2b_simd", "bls12_381", "bs58", + "byteorder", "core2 0.3.3", "document-features", "group", "memuse", - "nonempty", - "orchard", + "nonempty 0.11.0", + "orchard 0.11.0", "rand_core 0.6.4", - "sapling-crypto", - "secp256k1", + "regex", + "sapling-crypto 0.5.0", + "secp256k1 0.29.1", "secrecy 0.8.0", "subtle", "tracing", - "zcash_address", - "zcash_encoding", - "zcash_protocol", + "zcash_address 0.9.0", + "zcash_encoding 0.3.0", + "zcash_protocol 0.6.1", "zcash_transparent", "zeroize", - "zip32", + "zip32 0.2.0", ] [[package]] @@ -6734,12 +7598,51 @@ dependencies = [ "subtle", ] +[[package]] +name = "zcash_primitives" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d66e2f114bf81094bc5bf74860048ea1ae3911c424567cb9cb4d0cbb71ec7a" +dependencies = [ + "aes", + "bip32 0.5.3", + "blake2b_simd", + "bs58", + "byteorder", + "document-features", + "equihash", + "ff", + "fpe", + "group", + "hex", + "incrementalmerkletree 0.7.1", + "jubjub", + "memuse", + "nonempty 0.7.0", + "orchard 0.10.2", + "rand 0.8.5", + "rand_core 0.6.4", + "redjubjub 0.7.0", + "ripemd 0.1.3", + "sapling-crypto 0.3.0", + "secp256k1 0.27.0", + "sha2 0.10.8", + "subtle", + "tracing", + "zcash_address 0.6.3", + "zcash_encoding 0.2.2", + "zcash_note_encryption", + "zcash_protocol 0.4.3", + "zcash_spec 0.1.2", + "zip32 0.1.3", +] + [[package]] name = "zcash_primitives" version = "0.24.1" source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ - "bip32", + "bip32 0.6.0-pre.1", "blake2b_simd", "block-buffer 0.11.0-rc.3", "bs58", @@ -6752,27 +7655,27 @@ dependencies = [ "getset", "group", "hex", - "incrementalmerkletree", + "incrementalmerkletree 0.8.2", "jubjub", "memuse", - "nonempty", - "orchard", + "nonempty 0.11.0", + "orchard 0.11.0", "rand 0.8.5", "rand_core 0.6.4", - "redjubjub", + "redjubjub 0.8.0", "ripemd 0.1.3", - "sapling-crypto", - "secp256k1", + "sapling-crypto 0.5.0", + "secp256k1 0.29.1", "sha2 0.10.8", "subtle", "tracing", - "zcash_address", - "zcash_encoding", + "zcash_address 0.9.0", + "zcash_encoding 0.3.0", "zcash_note_encryption", - "zcash_protocol", - "zcash_spec", + "zcash_protocol 0.6.1", + "zcash_spec 0.2.1", "zcash_transparent", - "zip32", + "zip32 0.2.0", ] [[package]] @@ -6790,12 +7693,24 @@ dependencies = [ "known-folders", "lazy_static", "rand_core 0.6.4", - "redjubjub", - "sapling-crypto", + "redjubjub 0.8.0", + "sapling-crypto 0.5.0", "tracing", "wagyu-zcash-parameters", "xdg", - "zcash_primitives", + "zcash_primitives 0.24.1", +] + +[[package]] +name = "zcash_protocol" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cb36b15b5a1be70b30c32ce40372dead6561df8a467e297f96b892873a63a2" +dependencies = [ + "core2 0.3.3", + "document-features", + "hex", + "memuse", ] [[package]] @@ -6820,13 +7735,22 @@ dependencies = [ "cc", "enum_primitive", "ripemd 0.1.3", - "secp256k1", + "secp256k1 0.29.1", "sha-1", "sha2 0.10.8", "thiserror 2.0.12", "tracing", ] +[[package]] +name = "zcash_spec" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cede95491c2191d3e278cab76e097a44b17fde8d6ca0d4e3a22cf4807b2d857" +dependencies = [ + "blake2b_simd", +] + [[package]] name = "zcash_spec" version = "0.2.1" @@ -6841,7 +7765,7 @@ name = "zcash_transparent" version = "0.4.0" source = "git+https://github.com/zcash/librustzcash.git?rev=10caf455e3f52744b5392af226a408b05721f70f#10caf455e3f52744b5392af226a408b05721f70f" dependencies = [ - "bip32", + "bip32 0.6.0-pre.1", "blake2b_simd", "bs58", "core2 0.3.3", @@ -6849,14 +7773,14 @@ dependencies = [ "getset", "hex", "ripemd 0.1.3", - "secp256k1", + "secp256k1 0.29.1", "sha2 0.10.8", "subtle", - "zcash_address", - "zcash_encoding", - "zcash_protocol", - "zcash_spec", - "zip32", + "zcash_address 0.9.0", + "zcash_encoding 0.3.0", + "zcash_protocol 0.6.1", + "zcash_spec 0.2.1", + "zip32 0.2.0", ] [[package]] @@ -6882,20 +7806,20 @@ dependencies = [ "halo2_proofs", "hex", "humantime", - "incrementalmerkletree", + "incrementalmerkletree 0.8.2", "itertools 0.14.0", "jubjub", "lazy_static", "num-integer", - "orchard", + "orchard 0.11.0", "primitive-types 0.12.2", "rand_core 0.6.4", "rayon", "reddsa", - "redjubjub", + "redjubjub 0.8.0", "ripemd 0.1.3", - "sapling-crypto", - "secp256k1", + "sapling-crypto 0.5.0", + "secp256k1 0.29.1", "serde", "serde-big-array", "serde_json", @@ -6909,12 +7833,12 @@ dependencies = [ "tracing", "uint 0.10.0", "x25519-dalek", - "zcash_address", - "zcash_encoding", + "zcash_address 0.9.0", + "zcash_encoding 0.3.0", "zcash_history", "zcash_note_encryption", - "zcash_primitives", - "zcash_protocol", + "zcash_primitives 0.24.1", + "zcash_protocol 0.6.1", "zcash_transparent", ] @@ -6936,10 +7860,10 @@ dependencies = [ "metrics", "mset", "once_cell", - "orchard", + "orchard 0.11.0", "rand 0.8.5", "rayon", - "sapling-crypto", + "sapling-crypto 0.5.0", "serde", "thiserror 2.0.12", "tokio", @@ -7042,10 +7966,10 @@ dependencies = [ "tower 0.4.13", "tracing", "which 8.0.0", - "zcash_address", - "zcash_keys", - "zcash_primitives", - "zcash_protocol", + "zcash_address 0.9.0", + "zcash_keys 0.10.1", + "zcash_primitives 0.24.1", + "zcash_protocol 0.6.1", "zcash_transparent", "zebra-chain", "zebra-consensus", @@ -7062,7 +7986,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a76a2e972e414caa3635b8c2d21f20c21a71c69f76b37bf7419d97ed0c2277e7" dependencies = [ "thiserror 2.0.12", - "zcash_primitives", + "zcash_primitives 0.24.1", "zcash_script", "zebra-chain", ] @@ -7204,6 +8128,58 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "zewif" +version = "0.1.0" +source = "git+https://github.com/BlockchainCommons/zewif.git?rev=8f005a6f5a189815dc08821f99cfe7e8af4e7f62#8f005a6f5a189815dc08821f99cfe7e8af4e7f62" +dependencies = [ + "anyhow", + "bc-components", + "bc-crypto", + "bc-envelope", + "chrono", + "dcbor", + "hex", +] + +[[package]] +name = "zewif-zcashd" +version = "0.1.0" +source = "git+https://github.com/BlockchainCommons/zewif-zcashd.git?rev=74cd5bc2a66a8ce63534c2c1c9ff2dedb2f9d9fd#74cd5bc2a66a8ce63534c2c1c9ff2dedb2f9d9fd" +dependencies = [ + "anyhow", + "bitflags 2.9.0", + "bridgetree", + "byteorder", + "chrono", + "hex", + "incrementalmerkletree 0.7.1", + "orchard 0.10.2", + "ripemd 0.1.3", + "sapling-crypto 0.3.0", + "sha2 0.10.8", + "uuid", + "zcash_address 0.6.3", + "zcash_encoding 0.2.2", + "zcash_keys 0.4.1", + "zcash_primitives 0.19.1", + "zcash_protocol 0.4.3", + "zewif", + "zip32 0.1.3", +] + +[[package]] +name = "zip32" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9943793abf9060b68e1889012dafbd5523ab5b125c0fcc24802d69182f2ac9" +dependencies = [ + "blake2b_simd", + "memuse", + "subtle", + "zcash_spec 0.1.2", +] + [[package]] name = "zip32" version = "0.2.0" @@ -7213,7 +8189,7 @@ dependencies = [ "blake2b_simd", "memuse", "subtle", - "zcash_spec", + "zcash_spec 0.2.1", ] [[package]] @@ -7224,6 +8200,6 @@ dependencies = [ "base64 0.22.1", "nom", "percent-encoding", - "zcash_address", - "zcash_protocol", + "zcash_address 0.9.0", + "zcash_protocol 0.6.1", ] diff --git a/Cargo.toml b/Cargo.toml index 9efd09af..83aa1b7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,6 +142,9 @@ zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "1 zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "10caf455e3f52744b5392af226a408b05721f70f" } zcash_protocol = { git = "https://github.com/zcash/librustzcash.git", rev = "10caf455e3f52744b5392af226a408b05721f70f" } +zewif = { git = "https://github.com/BlockchainCommons/zewif.git", rev = "8f005a6f5a189815dc08821f99cfe7e8af4e7f62" } +zewif-zcashd = { git = "https://github.com/BlockchainCommons/zewif-zcashd.git", rev = "74cd5bc2a66a8ce63534c2c1c9ff2dedb2f9d9fd" } + zaino-fetch = { git = "https://github.com/Electric-Coin-Company/zaino.git", rev = "1004733f3303b1c48b6df6db610c54401554683c" } zaino-proto = { git = "https://github.com/Electric-Coin-Company/zaino.git", rev = "1004733f3303b1c48b6df6db610c54401554683c" } zaino-state = { git = "https://github.com/Electric-Coin-Company/zaino.git", rev = "1004733f3303b1c48b6df6db610c54401554683c" } diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 192639ae..ae0e4435 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -248,7 +248,7 @@ importable = false [[trusted.equihash]] criteria = "safe-to-deploy" -user-id = 6289 +user-id = 6289 # Jack Grigg (str4d) start = "2020-06-26" end = "2026-03-22" @@ -260,7 +260,7 @@ end = "2026-03-04" [[trusted.f4jumble]] criteria = "safe-to-deploy" -user-id = 6289 +user-id = 6289 # Jack Grigg (str4d) start = "2021-09-22" end = "2026-03-04" @@ -416,7 +416,7 @@ end = "2026-03-04" [[trusted.zcash_history]] criteria = "safe-to-deploy" -user-id = 6289 +user-id = 6289 # Jack Grigg (str4d) start = "2024-03-01" end = "2026-04-08" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index b6b458fb..a93b34ed 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -176,6 +176,10 @@ criteria = "safe-to-deploy" version = "1.7.1" criteria = "safe-to-deploy" +[[exemptions.argon2]] +version = "0.5.3" +criteria = "safe-to-deploy" + [[exemptions.async-stream]] version = "0.3.6" criteria = "safe-to-deploy" @@ -216,10 +220,42 @@ criteria = "safe-to-deploy" version = "0.3.71" criteria = "safe-to-deploy" +[[exemptions.base16ct]] +version = "0.2.0" +criteria = "safe-to-deploy" + [[exemptions.base64ct]] version = "1.7.3" criteria = "safe-to-deploy" +[[exemptions.bc-components]] +version = "0.21.2" +criteria = "safe-to-deploy" + +[[exemptions.bc-crypto]] +version = "0.9.0" +criteria = "safe-to-deploy" + +[[exemptions.bc-envelope]] +version = "0.28.1" +criteria = "safe-to-deploy" + +[[exemptions.bc-rand]] +version = "0.4.0" +criteria = "safe-to-deploy" + +[[exemptions.bc-shamir]] +version = "0.8.0" +criteria = "safe-to-deploy" + +[[exemptions.bc-tags]] +version = "0.2.0" +criteria = "safe-to-deploy" + +[[exemptions.bc-ur]] +version = "0.9.0" +criteria = "safe-to-deploy" + [[exemptions.bech32]] version = "0.8.1" criteria = "safe-to-deploy" @@ -248,10 +284,30 @@ criteria = "safe-to-deploy" version = "0.12.0" criteria = "safe-to-deploy" +[[exemptions.bip32]] +version = "0.5.3" +criteria = "safe-to-deploy" + [[exemptions.bip32]] version = "0.6.0-pre.1" criteria = "safe-to-deploy" +[[exemptions.bitcoin-io]] +version = "0.1.3" +criteria = "safe-to-deploy" + +[[exemptions.bitcoin-private]] +version = "0.1.0" +criteria = "safe-to-deploy" + +[[exemptions.bitcoin_hashes]] +version = "0.12.0" +criteria = "safe-to-deploy" + +[[exemptions.bitcoin_hashes]] +version = "0.14.0" +criteria = "safe-to-deploy" + [[exemptions.bitflags-serde-legacy]] version = "0.1.1" criteria = "safe-to-deploy" @@ -284,6 +340,10 @@ criteria = "safe-to-deploy" version = "0.8.0" criteria = "safe-to-deploy" +[[exemptions.bridgetree]] +version = "0.6.0" +criteria = "safe-to-deploy" + [[exemptions.bs58]] version = "0.5.1" criteria = "safe-to-deploy" @@ -420,6 +480,14 @@ criteria = "safe-to-deploy" version = "0.2.17" criteria = "safe-to-deploy" +[[exemptions.crc]] +version = "3.3.0" +criteria = "safe-to-deploy" + +[[exemptions.crc-catalog]] +version = "2.4.0" +criteria = "safe-to-deploy" + [[exemptions.crossbeam-channel]] version = "0.5.14" criteria = "safe-to-deploy" @@ -436,6 +504,10 @@ criteria = "safe-to-deploy" version = "0.8.20" criteria = "safe-to-deploy" +[[exemptions.crypto-bigint]] +version = "0.5.5" +criteria = "safe-to-deploy" + [[exemptions.crypto-common]] version = "0.2.0-rc.1" criteria = "safe-to-deploy" @@ -468,6 +540,10 @@ criteria = "safe-to-deploy" version = "6.1.0" criteria = "safe-to-deploy" +[[exemptions.dcbor]] +version = "0.19.1" +criteria = "safe-to-deploy" + [[exemptions.deadpool]] version = "0.12.2" criteria = "safe-to-deploy" @@ -516,6 +592,10 @@ criteria = "safe-to-deploy" version = "0.4.1" criteria = "safe-to-deploy" +[[exemptions.doctest-file]] +version = "1.0.0" +criteria = "safe-to-deploy" + [[exemptions.documented]] version = "0.3.0" criteria = "safe-to-deploy" @@ -524,22 +604,38 @@ criteria = "safe-to-deploy" version = "0.9.1" criteria = "safe-to-deploy" +[[exemptions.dsa]] +version = "0.6.3" +criteria = "safe-to-deploy" + [[exemptions.dunce]] version = "1.0.5" -criteria = "safe-to-run" +criteria = "safe-to-deploy" [[exemptions.dyn-clone]] version = "1.0.19" criteria = "safe-to-deploy" +[[exemptions.ecdsa]] +version = "0.16.9" +criteria = "safe-to-deploy" + [[exemptions.ed25519]] version = "2.2.1" criteria = "safe-to-deploy" +[[exemptions.ed25519-dalek]] +version = "2.2.0" +criteria = "safe-to-deploy" + [[exemptions.ed25519-zebra]] version = "3.0.0" criteria = "safe-to-deploy" +[[exemptions.elliptic-curve]] +version = "0.13.8" +criteria = "safe-to-deploy" + [[exemptions.encode_unicode]] version = "1.0.0" criteria = "safe-to-deploy" @@ -672,6 +768,10 @@ criteria = "safe-to-deploy" version = "0.4.7" criteria = "safe-to-deploy" +[[exemptions.half]] +version = "2.6.0" +criteria = "safe-to-deploy" + [[exemptions.halo2_gadgets]] version = "0.3.1" criteria = "safe-to-deploy" @@ -708,6 +808,10 @@ criteria = "safe-to-deploy" version = "0.5.0" criteria = "safe-to-deploy" +[[exemptions.hex-conservative]] +version = "0.2.1" +criteria = "safe-to-deploy" + [[exemptions.hex-literal]] version = "0.4.1" criteria = "safe-to-deploy" @@ -812,6 +916,10 @@ criteria = "safe-to-deploy" version = "1.9.3" criteria = "safe-to-deploy" +[[exemptions.interprocess]] +version = "2.2.3" +criteria = "safe-to-deploy" + [[exemptions.intl-memoizer]] version = "0.5.2" criteria = "safe-to-deploy" @@ -840,6 +948,10 @@ criteria = "safe-to-deploy" version = "0.10.3" criteria = "safe-to-deploy" +[[exemptions.itertools]] +version = "0.11.0" +criteria = "safe-to-deploy" + [[exemptions.itertools]] version = "0.13.0" criteria = "safe-to-deploy" @@ -880,6 +992,10 @@ criteria = "safe-to-deploy" version = "0.10.0" criteria = "safe-to-deploy" +[[exemptions.known-values]] +version = "0.4.0" +criteria = "safe-to-deploy" + [[exemptions.lazy-regex]] version = "3.4.1" criteria = "safe-to-deploy" @@ -976,6 +1092,14 @@ criteria = "safe-to-deploy" version = "0.3.17" criteria = "safe-to-deploy" +[[exemptions.minicbor]] +version = "0.19.1" +criteria = "safe-to-deploy" + +[[exemptions.minicbor-derive]] +version = "0.13.0" +criteria = "safe-to-deploy" + [[exemptions.minimal-lexical]] version = "0.2.1" criteria = "safe-to-deploy" @@ -1000,10 +1124,18 @@ criteria = "safe-to-deploy" version = "0.15.0" criteria = "safe-to-deploy" +[[exemptions.nonempty]] +version = "0.7.0" +criteria = "safe-to-deploy" + [[exemptions.num-bigint]] version = "0.4.6" criteria = "safe-to-deploy" +[[exemptions.num-bigint-dig]] +version = "0.8.4" +criteria = "safe-to-deploy" + [[exemptions.num-traits]] version = "0.1.43" criteria = "safe-to-deploy" @@ -1052,6 +1184,18 @@ criteria = "safe-to-run" version = "3.5.0" criteria = "safe-to-deploy" +[[exemptions.p256]] +version = "0.13.2" +criteria = "safe-to-deploy" + +[[exemptions.p384]] +version = "0.13.1" +criteria = "safe-to-deploy" + +[[exemptions.p521]] +version = "0.13.3" +criteria = "safe-to-deploy" + [[exemptions.pairing]] version = "0.23.0" criteria = "safe-to-deploy" @@ -1080,10 +1224,18 @@ criteria = "safe-to-deploy" version = "0.5.1" criteria = "safe-to-deploy" +[[exemptions.paste]] +version = "1.0.10" +criteria = "safe-to-deploy" + [[exemptions.pbkdf2]] version = "0.12.2" criteria = "safe-to-deploy" +[[exemptions.pem-rfc7468]] +version = "0.7.0" +criteria = "safe-to-deploy" + [[exemptions.petgraph]] version = "0.7.1" criteria = "safe-to-deploy" @@ -1116,6 +1268,10 @@ criteria = "safe-to-deploy" version = "0.6.0" criteria = "safe-to-deploy" +[[exemptions.pkcs1]] +version = "0.7.5" +criteria = "safe-to-deploy" + [[exemptions.pkcs8]] version = "0.10.2" criteria = "safe-to-deploy" @@ -1136,10 +1292,30 @@ criteria = "safe-to-deploy" version = "0.2.21" criteria = "safe-to-deploy" +[[exemptions.pqcrypto-internals]] +version = "0.2.11" +criteria = "safe-to-deploy" + +[[exemptions.pqcrypto-mldsa]] +version = "0.1.2" +criteria = "safe-to-deploy" + +[[exemptions.pqcrypto-mlkem]] +version = "0.1.1" +criteria = "safe-to-deploy" + +[[exemptions.pqcrypto-traits]] +version = "0.3.5" +criteria = "safe-to-deploy" + [[exemptions.prettyplease]] version = "0.2.32" criteria = "safe-to-deploy" +[[exemptions.primeorder]] +version = "0.13.6" +criteria = "safe-to-deploy" + [[exemptions.primitive-types]] version = "0.12.2" criteria = "safe-to-deploy" @@ -1228,6 +1404,14 @@ criteria = "safe-to-deploy" version = "0.2.0" criteria = "safe-to-deploy" +[[exemptions.rand_xoshiro]] +version = "0.6.0" +criteria = "safe-to-deploy" + +[[exemptions.recvmsg]] +version = "1.0.0" +criteria = "safe-to-deploy" + [[exemptions.reddsa]] version = "0.5.1" criteria = "safe-to-deploy" @@ -1268,6 +1452,10 @@ criteria = "safe-to-deploy" version = "0.12.15" criteria = "safe-to-deploy" +[[exemptions.rfc6979]] +version = "0.4.0" +criteria = "safe-to-deploy" + [[exemptions.ring]] version = "0.17.14" criteria = "safe-to-deploy" @@ -1300,6 +1488,10 @@ criteria = "safe-to-deploy" version = "7.3.1" criteria = "safe-to-deploy" +[[exemptions.rsa]] +version = "0.9.8" +criteria = "safe-to-deploy" + [[exemptions.rtoolbox]] version = "0.0.2" criteria = "safe-to-deploy" @@ -1384,10 +1576,26 @@ criteria = "safe-to-deploy" version = "0.11.0" criteria = "safe-to-deploy" +[[exemptions.sec1]] +version = "0.7.3" +criteria = "safe-to-deploy" + +[[exemptions.secp256k1]] +version = "0.26.0" +criteria = "safe-to-deploy" + [[exemptions.secp256k1]] version = "0.29.1" criteria = "safe-to-deploy" +[[exemptions.secp256k1]] +version = "0.30.0" +criteria = "safe-to-deploy" + +[[exemptions.secp256k1-sys]] +version = "0.8.2" +criteria = "safe-to-deploy" + [[exemptions.secp256k1-sys]] version = "0.10.1" criteria = "safe-to-deploy" @@ -1492,6 +1700,26 @@ criteria = "safe-to-deploy" version = "0.7.3" criteria = "safe-to-deploy" +[[exemptions.ssh-agent-client-rs]] +version = "1.1.1" +criteria = "safe-to-deploy" + +[[exemptions.ssh-cipher]] +version = "0.2.0" +criteria = "safe-to-deploy" + +[[exemptions.ssh-encoding]] +version = "0.2.0" +criteria = "safe-to-deploy" + +[[exemptions.ssh-key]] +version = "0.6.7" +criteria = "safe-to-deploy" + +[[exemptions.sskr]] +version = "0.8.0" +criteria = "safe-to-deploy" + [[exemptions.syn]] version = "1.0.109" criteria = "safe-to-deploy" @@ -1648,6 +1876,10 @@ criteria = "safe-to-deploy" version = "0.9.0" criteria = "safe-to-deploy" +[[exemptions.ur]] +version = "0.4.1" +criteria = "safe-to-deploy" + [[exemptions.uuid]] version = "1.16.0" criteria = "safe-to-deploy" @@ -1728,6 +1960,10 @@ criteria = "safe-to-deploy" version = "1.6.0" criteria = "safe-to-deploy" +[[exemptions.widestring]] +version = "1.2.0" +criteria = "safe-to-deploy" + [[exemptions.winapi]] version = "0.3.9" criteria = "safe-to-deploy" @@ -1800,6 +2036,10 @@ criteria = "safe-to-deploy" version = "0.17.3@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" +[[exemptions.zcash_encoding]] +version = "0.2.2" +criteria = "safe-to-deploy" + [[exemptions.zcash_encoding]] version = "0.3.0@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" @@ -1824,6 +2064,10 @@ criteria = "safe-to-deploy" version = "0.6.1@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" +[[exemptions.zcash_spec]] +version = "0.1.2" +criteria = "safe-to-deploy" + [[exemptions.zcash_transparent]] version = "0.4.0@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" @@ -1872,6 +2116,10 @@ criteria = "safe-to-deploy" version = "0.8.24" criteria = "safe-to-deploy" +[[exemptions.zip32]] +version = "0.1.3" +criteria = "safe-to-deploy" + [[exemptions.zip321]] version = "0.5.0@git:10caf455e3f52744b5392af226a408b05721f70f" criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index c3c7531e..68f42bd6 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -15,6 +15,20 @@ user-id = 3788 user-login = "emilio" user-name = "Emilio Cobos Álvarez" +[[publisher.f4jumble]] +version = "0.1.1" +when = "2024-12-13" +user-id = 6289 +user-login = "str4d" +user-name = "Jack Grigg" + +[[publisher.incrementalmerkletree]] +version = "0.7.1" +when = "2024-12-16" +user-id = 169181 +user-login = "nuttycom" +user-name = "Kris Nuttycombe" + [[publisher.incrementalmerkletree]] version = "0.8.2" when = "2025-02-01" @@ -22,6 +36,13 @@ user-id = 169181 user-login = "nuttycom" user-name = "Kris Nuttycombe" +[[publisher.orchard]] +version = "0.10.2" +when = "2025-05-08" +user-id = 169181 +user-login = "nuttycom" +user-name = "Kris Nuttycombe" + [[publisher.orchard]] version = "0.11.0" when = "2025-02-21" @@ -29,6 +50,13 @@ user-id = 169181 user-login = "nuttycom" user-name = "Kris Nuttycombe" +[[publisher.sapling-crypto]] +version = "0.3.0" +when = "2024-10-02" +user-id = 169181 +user-login = "nuttycom" +user-name = "Kris Nuttycombe" + [[publisher.sapling-crypto]] version = "0.5.0" when = "2025-02-21" @@ -322,6 +350,34 @@ when = "2025-02-05" user-id = 73222 user-login = "wasmtime-publish" +[[publisher.zcash_address]] +version = "0.6.3" +when = "2025-05-07" +user-id = 169181 +user-login = "nuttycom" +user-name = "Kris Nuttycombe" + +[[publisher.zcash_keys]] +version = "0.4.1" +when = "2025-05-09" +user-id = 169181 +user-login = "nuttycom" +user-name = "Kris Nuttycombe" + +[[publisher.zcash_primitives]] +version = "0.19.1" +when = "2025-05-09" +user-id = 169181 +user-login = "nuttycom" +user-name = "Kris Nuttycombe" + +[[publisher.zcash_protocol]] +version = "0.4.3" +when = "2024-12-17" +user-id = 169181 +user-login = "nuttycom" +user-name = "Kris Nuttycombe" + [[publisher.zcash_script]] version = "0.3.2" when = "2025-06-24" @@ -1138,6 +1194,12 @@ version = "0.1.46" notes = "Contains no unsafe" aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" +[[audits.google.audits.num-iter]] +who = "George Burgess IV " +criteria = "safe-to-deploy" +version = "0.1.43" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + [[audits.google.audits.num-traits]] who = "Manish Goregaokar " criteria = "safe-to-deploy" @@ -2016,6 +2078,16 @@ who = "David Cook " criteria = "safe-to-deploy" version = "0.12.1" +[[audits.isrg.audits.num-iter]] +who = "David Cook " +criteria = "safe-to-deploy" +delta = "0.1.43 -> 0.1.44" + +[[audits.isrg.audits.num-iter]] +who = "David Cook " +criteria = "safe-to-deploy" +delta = "0.1.44 -> 0.1.45" + [[audits.isrg.audits.once_cell]] who = "J.C. Jones " criteria = "safe-to-deploy" @@ -2659,6 +2731,12 @@ criteria = "safe-to-deploy" version = "0.2.0" aggregated-from = "https://raw.githubusercontent.com/mozilla/cargo-vet/main/supply-chain/audits.toml" +[[audits.mozilla.audits.paste]] +who = "Jan-Erik Rediger " +criteria = "safe-to-deploy" +delta = "1.0.10 -> 1.0.15" +aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml" + [[audits.mozilla.audits.percent-encoding]] who = "Valentin Gosu " criteria = "safe-to-deploy" @@ -3448,6 +3526,12 @@ delta = "0.4.0 -> 0.4.1" notes = "Changes to `Command` usage are to add support for `RUSTC_WRAPPER`." aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" +[[audits.zcash.audits.secp256k1]] +who = "Jack Grigg " +criteria = "safe-to-deploy" +delta = "0.26.0 -> 0.27.0" +aggregated-from = "https://raw.githubusercontent.com/zcash/librustzcash/main/supply-chain/audits.toml" + [[audits.zcash.audits.sharded-slab]] who = "Jack Grigg " criteria = "safe-to-deploy" diff --git a/zallet/Cargo.toml b/zallet/Cargo.toml index c4c4531e..25ecadbb 100644 --- a/zallet/Cargo.toml +++ b/zallet/Cargo.toml @@ -91,7 +91,7 @@ http-body-util.workspace = true hyper.workspace = true i18n-embed = { workspace = true, features = ["desktop-requester"] } i18n-embed-fl.workspace = true -incrementalmerkletree.workspace = true +incrementalmerkletree = { workspace = true, features = ["legacy-api"] } jsonrpsee = { workspace = true, features = ["macros", "server"] } known-folders.workspace = true nix = { workspace = true, features = ["signal"] } @@ -133,13 +133,15 @@ zcash_client_backend = { workspace = true, features = [ "sync", "transparent-inputs", "transparent-key-import", + "zcashd-compat", ] } zcash_client_sqlite = { workspace = true, features = [ "orchard", "transparent-inputs", "transparent-key-import", + "zcashd-compat", ] } -zcash_keys.workspace = true +zcash_keys = { workspace = true, features = ["zcashd-compat", "unstable"] } zcash_note_encryption.workspace = true zcash_primitives.workspace = true zcash_proofs = { workspace = true, features = ["bundled-prover"] } @@ -148,6 +150,9 @@ zebra-chain.workspace = true zebra-rpc.workspace = true zebra-state.workspace = true zip32.workspace = true +zewif.workspace = true +zewif-zcashd.workspace = true +anyhow.workspace = true console-subscriber = { workspace = true, optional = true } jsonrpsee-http-client = { workspace = true, optional = true } diff --git a/zallet/i18n/en-US/zallet.ftl b/zallet/i18n/en-US/zallet.ftl index 1be9febd..b7698d94 100644 --- a/zallet/i18n/en-US/zallet.ftl +++ b/zallet/i18n/en-US/zallet.ftl @@ -112,6 +112,43 @@ err-migrate-unknown-zcashd-option = Unknown {-zcashd} option '{$option}' err-failed-seed-fingerprinting = Zallet was unable to import invalid seed data, likely due to the seed having an invalid length. +err-migrate-wallet-bdb-parse = + An error occurred in parsing the {-zcashd} wallet file at '{$path}': '{$err}' +err-migrate-wallet-db-dump = + An error occurred in extracting wallet data from '{$path}': '{$err}' +err-migrate-wallet-seed-absent = + The {-zcashd} wallet file did not contain HD seed information. Wallets from + prior to the Sapling network upgrade are not supported by this migration + tool. +err-migrate-wallet-invalid-mnemonic = + The {-zcashd} wallet file contained invalid mnemonic seed phrase data and + may be corrupt: '{$err}' +err-migrate-wallet-key-decoding= + The {-zcashd} wallet file contained invalid mnemonic transparent secret key + data and may be corrupt: '{$err}' +err-migrate-wallet-key-data= + The {-zcashd} wallet file contained invalid key data and may be corrupt: + '{$err}' +err-migrate-wallet-network-mismatch = + The {-zcashd} wallet being imported is for the '{$wallet_network}' network, + but this {-zallet} instance is configured for '{$zallet_network}' +err-migrate-wallet-regtest = + Migration of regtest wallets is not yet supported. +err-migrate-wallet-storage = + An database error occurred in wallet migration. This is indicative of a + programming error; please report the following error to (TBD): '{$err}' +err-migrate-wallet-invalid-chain-data = + Invalid chain data was encountered in wallet migration. This is indicative of a + programming error; please report the following error to (TBD): '{$err}' +err-migrate-wallet-key-decoding = + An error occurred decoding key material: '{$err}'. +err-migrate-wallet-tx-fetch = + An error occurred fetching transaction data: '{$err}'. +err-migrate-wallet-data-parse= + An error occurred parsing zcashd wallet data: '{$err}'. +err-migrate-wallet-invalid-account-id = + Error encountered in wallet migration: '{$account_id}' is not a valid ZIP + 32 account identifier. err-ux-A = Did {-zallet} not do what you expected? Could the error be more useful? err-ux-B = Tell us diff --git a/zallet/src/cli.rs b/zallet/src/cli.rs index 46e2030c..b0b72668 100644 --- a/zallet/src/cli.rs +++ b/zallet/src/cli.rs @@ -54,6 +54,10 @@ pub(crate) enum ZalletCmd { #[cfg(zallet_build = "wallet")] MigrateZcashConf(MigrateZcashConfCmd), + /// Add the keys and transactions of a zcashd wallet.dat file to the wallet database. + #[cfg(zallet_build = "wallet")] + MigrateZcashdWallet(MigrateZcashdWalletCmd), + /// Initialize wallet encryption. #[cfg(zallet_build = "wallet")] InitWalletEncryption(InitWalletEncryptionCmd), @@ -107,13 +111,13 @@ pub(crate) struct ExampleConfigCmd { pub(crate) struct MigrateZcashConfCmd { /// Specify `zcashd` configuration file. /// - /// Relative paths will be prefixed by `datadir` location. + /// Relative paths will be prefixed by `zcashd_datadir` location. #[arg(long, default_value = "zcash.conf")] pub(crate) conf: PathBuf, /// Specify `zcashd` data directory (this path cannot use '~'). #[arg(long)] - pub(crate) datadir: Option, + pub(crate) zcashd_datadir: Option, /// Allow a migration when warnings are present. #[arg(long)] @@ -135,6 +139,27 @@ pub(crate) struct MigrateZcashConfCmd { pub(crate) this_is_alpha_code_and_you_will_need_to_redo_the_migration_later: bool, } +/// `migrate-zcashd-wallet` subcommand +#[cfg(zallet_build = "wallet")] +#[derive(Debug, Parser)] +#[cfg_attr(outside_buildscript, derive(Command))] +pub(crate) struct MigrateZcashdWalletCmd { + /// Specify location of the `zcashd` `wallet.dat` file. + /// + /// Relative paths will be prefixed by `zcashd_datadir` location. + #[arg(long, default_value = "wallet.dat")] + pub(crate) path: PathBuf, + + /// Specify `zcashd` data directory (this path cannot use '~'). + #[arg(long)] + pub(crate) zcashd_datadir: Option, + + /// Allow a migration when warnings are present. If set to `false`, any warning will be treated + /// as an error and cause the migration to abort. + #[arg(long)] + pub(crate) allow_warnings: bool, +} + /// `init-wallet-encryption` subcommand #[cfg(zallet_build = "wallet")] #[derive(Debug, Parser)] diff --git a/zallet/src/commands.rs b/zallet/src/commands.rs index 80d70cac..5136e435 100644 --- a/zallet/src/commands.rs +++ b/zallet/src/commands.rs @@ -32,6 +32,8 @@ mod import_mnemonic; mod init_wallet_encryption; #[cfg(zallet_build = "wallet")] mod migrate_zcash_conf; +#[cfg(zallet_build = "wallet")] +mod migrate_zcashd_wallet; #[cfg(feature = "rpc-cli")] pub(crate) mod rpc_cli; diff --git a/zallet/src/commands/generate_mnemonic.rs b/zallet/src/commands/generate_mnemonic.rs index 94772661..66a5e8b4 100644 --- a/zallet/src/commands/generate_mnemonic.rs +++ b/zallet/src/commands/generate_mnemonic.rs @@ -1,7 +1,6 @@ use abscissa_core::Runnable; use bip0039::{Count, English, Mnemonic}; use rand::{RngCore, rngs::OsRng}; -use secrecy::SecretString; use crate::{ cli::GenerateMnemonicCmd, @@ -30,9 +29,7 @@ impl AsyncRunnable for GenerateMnemonicCmd { let mnemonic = Mnemonic::::from_entropy(entropy) .expect("valid entropy length won't fail to generate the mnemonic"); - keystore - .encrypt_and_store_mnemonic(&SecretString::new(mnemonic.into_phrase())) - .await?; + keystore.encrypt_and_store_mnemonic(mnemonic).await?; Ok(()) } diff --git a/zallet/src/commands/import_mnemonic.rs b/zallet/src/commands/import_mnemonic.rs index 506d89d1..6aa2f726 100644 --- a/zallet/src/commands/import_mnemonic.rs +++ b/zallet/src/commands/import_mnemonic.rs @@ -26,9 +26,7 @@ impl AsyncRunnable for ImportMnemonicCmd { let mnemonic = Mnemonic::::from_phrase(phrase.expose_secret()) .map_err(|e| ErrorKind::Generic.context(e))?; - let seedfp = keystore - .encrypt_and_store_mnemonic(&SecretString::new(mnemonic.into_phrase())) - .await?; + let seedfp = keystore.encrypt_and_store_mnemonic(mnemonic).await?; println!("Seed fingerprint: {seedfp}"); diff --git a/zallet/src/commands/migrate_zcash_conf.rs b/zallet/src/commands/migrate_zcash_conf.rs index 3be9a912..959e3fea 100644 --- a/zallet/src/commands/migrate_zcash_conf.rs +++ b/zallet/src/commands/migrate_zcash_conf.rs @@ -22,10 +22,10 @@ use crate::{ impl AsyncRunnable for MigrateZcashConfCmd { async fn run(&self) -> Result<(), Error> { let conf = if self.conf.is_relative() { - if let Some(datadir) = self.datadir.as_ref() { + if let Some(datadir) = self.zcashd_datadir.as_ref() { datadir.join(&self.conf) } else { - default_data_dir() + zcashd_default_data_dir() .ok_or(ErrorKind::Generic)? .join(&self.conf) } @@ -178,7 +178,7 @@ impl Runnable for MigrateZcashConfCmd { } } -fn default_data_dir() -> Option { +pub(crate) fn zcashd_default_data_dir() -> Option { #[cfg(windows)] { use known_folders::{KnownFolder, get_known_folder_path}; diff --git a/zallet/src/commands/migrate_zcashd_wallet.rs b/zallet/src/commands/migrate_zcashd_wallet.rs new file mode 100644 index 00000000..8a3b028f --- /dev/null +++ b/zallet/src/commands/migrate_zcashd_wallet.rs @@ -0,0 +1,654 @@ +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + +use abscissa_core::{Runnable, Shutdown}; + +use bip0039::{English, Mnemonic}; +use secp256k1::PublicKey; +use secrecy::SecretVec; +use shardtree::error::ShardTreeError; +use zaino_proto::proto::service::TxFilter; +use zaino_state::{FetchServiceError, LightWalletIndexer}; +use zcash_client_backend::data_api::{ + Account as _, AccountBirthday, AccountPurpose, WalletRead, WalletWrite as _, Zip32Derivation, +}; +use zcash_client_sqlite::error::SqliteClientError; +use zcash_keys::keys::{ + DerivationError, UnifiedFullViewingKey, + zcashd::{PathParseError, ZcashdHdDerivation}, +}; +use zcash_protocol::consensus::{BlockHeight, NetworkType, Parameters}; +use zewif_zcashd::{BDBDump, ZcashdDump, ZcashdParser, ZcashdWallet}; +use zip32::{AccountId, fingerprint::SeedFingerprint}; + +use crate::{ + cli::MigrateZcashdWalletCmd, + components::{chain_view::ChainView, database::Database, keystore::KeyStore}, + error::{Error, ErrorKind}, + fl, + prelude::*, + rosetta::to_chainstate, +}; + +use super::migrate_zcash_conf; + +pub const ZCASHD_LEGACY_ACCOUNT: AccountId = AccountId::const_from_u32(0x7FFFFFFF); + +#[derive(Debug)] +pub(crate) enum ZewifError { + BdbDump, + ZcashdDump, +} + +#[derive(Debug)] +#[allow(dead_code)] +pub(crate) enum MigrateError { + Wrapped(Error), + Zewif { + error_type: ZewifError, + wallet_path: PathBuf, + error: anyhow::Error, + }, + SeedNotAvailable, + MnemonicInvalid(bip0039::Error), + KeyError(secp256k1::Error), + NetworkMismatch { + wallet_network: zewif::Network, + db_network: NetworkType, + }, + NetworkNotSupported(NetworkType), + Database(SqliteClientError), + Tree(ShardTreeError), + Io(std::io::Error), + Fetch(Box), + KeyDerivation(DerivationError), + HdPath(PathParseError), + AccountIdInvalid(u32), +} + +impl From for Error { + fn from(value: MigrateError) -> Self { + match value { + MigrateError::Wrapped(e) => e, + MigrateError::Zewif { + error_type, + wallet_path, + error, + } => Error::from(match error_type { + ZewifError::BdbDump => ErrorKind::Generic.context(fl!( + "err-migrate-wallet-bdb-parse", + path = wallet_path.to_str(), + err = error.to_string() + )), + ZewifError::ZcashdDump => ErrorKind::Generic.context(fl!( + "err-migrate-wallet-db-dump", + path = wallet_path.to_str(), + err = error.to_string() + )), + }), + MigrateError::SeedNotAvailable => { + Error::from(ErrorKind::Generic.context(fl!("err-migrate-wallet-seed-absent"))) + } + + MigrateError::MnemonicInvalid(error) => Error::from(ErrorKind::Generic.context(fl!( + "err-migrate-wallet-invalid-mnemonic", + err = error.to_string() + ))), + MigrateError::KeyError(error) => Error::from(ErrorKind::Generic.context(fl!( + "err-migrate-wallet-key-decoding", + err = error.to_string() + ))), + MigrateError::NetworkMismatch { + wallet_network, + db_network, + } => Error::from(ErrorKind::Generic.context(fl!( + "err-migrate-wallet-network-mismatch", + wallet_network = String::from(wallet_network), + zallet_network = match db_network { + NetworkType::Main => "main", + NetworkType::Test => "test", + NetworkType::Regtest => "regtest", + } + ))), + MigrateError::NetworkNotSupported(_) => { + Error::from(ErrorKind::Generic.context(fl!("err-migrate-wallet-regtest"))) + } + MigrateError::Database(sqlite_client_error) => { + Error::from(ErrorKind::Generic.context(fl!( + "err-migrate-wallet-storage", + err = sqlite_client_error.to_string() + ))) + } + MigrateError::Tree(e) => Error::from( + ErrorKind::Generic + .context(fl!("err-migrate-wallet-data-parse", err = e.to_string())), + ), + MigrateError::Io(e) => Error::from( + ErrorKind::Generic + .context(fl!("err-migrate-wallet-data-parse", err = e.to_string())), + ), + MigrateError::Fetch(e) => Error::from( + ErrorKind::Generic.context(fl!("err-migrate-wallet-tx-fetch", err = e.to_string())), + ), + MigrateError::KeyDerivation(e) => Error::from( + ErrorKind::Generic.context(fl!("err-migrate-wallet-key-data", err = e.to_string())), + ), + MigrateError::HdPath(err) => Error::from(ErrorKind::Generic.context(fl!( + "err-migrate-wallet-data-parse", + err = format!("{:?}", err) + ))), + MigrateError::AccountIdInvalid(id) => Error::from(ErrorKind::Generic.context(fl!( + "err-migrate-wallet-invalid-account-id", + account_id = id + ))), + } + } +} + +impl From> for MigrateError { + fn from(e: ShardTreeError) -> Self { + Self::Tree(e) + } +} + +impl From for MigrateError { + fn from(e: SqliteClientError) -> Self { + Self::Database(e) + } +} + +impl From for MigrateError { + fn from(value: bip0039::Error) -> Self { + Self::MnemonicInvalid(value) + } +} + +impl From for MigrateError { + fn from(value: Error) -> Self { + MigrateError::Wrapped(value) + } +} + +impl From> for MigrateError { + fn from(value: abscissa_core::error::Context) -> Self { + MigrateError::Wrapped(value.into()) + } +} + +impl From for MigrateError { + fn from(value: std::io::Error) -> Self { + MigrateError::Io(value) + } +} + +impl From for MigrateError { + fn from(value: FetchServiceError) -> Self { + MigrateError::Fetch(Box::new(value)) + } +} + +impl From for MigrateError { + fn from(value: DerivationError) -> Self { + MigrateError::KeyDerivation(value) + } +} + +impl From for MigrateError { + fn from(value: PathParseError) -> Self { + MigrateError::HdPath(value) + } +} + +impl From for MigrateError { + fn from(value: secp256k1::Error) -> Self { + MigrateError::KeyError(value) + } +} + +impl MigrateZcashdWalletCmd { + fn dump_wallet(path: &Path, allow_warnings: bool) -> Result { + let db_dump = BDBDump::from_file(path).map_err(|e| MigrateError::Zewif { + error_type: ZewifError::BdbDump, + wallet_path: path.to_path_buf(), + error: e, + })?; + + let zcashd_dump = ZcashdDump::from_bdb_dump(&db_dump, allow_warnings).map_err(|e| { + MigrateError::Zewif { + error_type: ZewifError::ZcashdDump, + wallet_path: path.to_path_buf(), + error: e, + } + })?; + + let (zcashd_wallet, _unparsed_keys) = + ZcashdParser::parse_dump(&zcashd_dump, !allow_warnings).map_err(|e| { + MigrateError::Zewif { + error_type: ZewifError::ZcashdDump, + wallet_path: path.to_path_buf(), + error: e, + } + })?; + + Ok(zcashd_wallet) + } + + async fn chain_tip( + chain: &C, + ) -> Result, MigrateError> + where + MigrateError: From, + { + let tip_height = chain.get_latest_block().await?.height; + let chain_tip = if tip_height == 0 { + None + } else { + // TODO: this error should go away when we have a better chain data API + Some(BlockHeight::try_from(tip_height).map_err(|e| { + ErrorKind::Generic.context(fl!( + "err-migrate-wallet-invalid-chain-data", + err = e.to_string() + )) + })?) + }; + + Ok(chain_tip) + } + + async fn get_birthday( + chain: &C, + birthday_height: BlockHeight, + recover_until: Option, + ) -> Result + where + MigrateError: From, + { + // Fetch the tree state corresponding to the last block prior to the wallet's + // birthday height. + let chain_state = to_chainstate( + chain + .get_tree_state(zaino_proto::proto::service::BlockId { + height: u64::from(birthday_height.saturating_sub(1)), + hash: vec![], + }) + .await?, + )?; + + Ok(AccountBirthday::from_parts(chain_state, recover_until)) + } + + fn check_network( + zewif_network: zewif::Network, + network_type: NetworkType, + ) -> Result { + match (zewif_network, network_type) { + (zewif::Network::Main, NetworkType::Main) => Ok(()), + (zewif::Network::Test, NetworkType::Test) => Ok(()), + (zewif::Network::Regtest, NetworkType::Regtest) => Ok(()), + (wallet_network, db_network) => Err(MigrateError::NetworkMismatch { + wallet_network, + db_network, + }), + }?; + + Ok(network_type) + } + + fn parse_mnemonic(mnemonic: &str) -> Result, bip0039::Error> { + (!mnemonic.is_empty()) + .then(|| Mnemonic::::from_phrase(mnemonic)) + .transpose() + } + + async fn migrate_zcashd_wallet( + db: Database, + keystore: KeyStore, + chain_view: ChainView, + wallet: ZcashdWallet, + ) -> Result<(), MigrateError> { + let mut db_data = db.handle().await?; + let network = Self::check_network(wallet.network(), db_data.params().network_type())?; + + // Obtain information about the current state of the chain, so that we can set the recovery + // height properly. + let chain_subscriber = chain_view.subscribe().await?.inner(); + let chain_tip = Self::chain_tip(&chain_subscriber).await?; + let sapling_activation = db_data + .params() + .activation_height(zcash_protocol::consensus::NetworkUpgrade::Sapling) + .expect("Sapling activation height is defined."); + + // Collect an index from txid to block height for all transactions known to the wallet that + // appear in the main chain.. + let mut tx_heights = HashMap::new(); + for (txid, _) in wallet.transactions().iter() { + let tx_filter = TxFilter { + hash: txid.as_ref().to_vec(), + ..Default::default() + }; + #[allow(unused_must_use)] + match chain_subscriber.get_transaction(tx_filter).await { + Ok(raw_tx) => { + let tx_height = + BlockHeight::from(u32::try_from(raw_tx.height).map_err(|e| { + // TODO: this error should go away when we have a better chain data API + ErrorKind::Generic.context(fl!( + "err-migrate-wallet-invalid-chain-data", + err = e.to_string() + )) + })?); + tx_heights.insert(txid, tx_height); + } + Err(FetchServiceError::TonicStatusError(status)) + if (status.code() as isize) == (tonic::Code::NotFound as isize) => + { + // Ignore any transactions that are not in the main chain. + } + other => { + // FIXME: we should be able to propagate this error, but at present Zaino is + // returning all sorts of errors as 500s. + dbg!(other); + } + } + } + + // Since zcashd scans in linear order, we can reliably choose the earliest wallet + // transaction's mined height as the birthday height, so long as it is in the "stable" + // range. We don't have a good source of individual per-account birthday information at + // this point; once we've imported all of the transaction data into the wallet then we'll + // be able to choose per-account birthdays without difficulty. + let wallet_birthday = Self::get_birthday( + &chain_subscriber, + // Fall back to the chain tip height, and then Sapling activation as a last resort. If + // we have a birthday height, max() that with sapling activation; that will be the + // minimum possible wallet birthday that is relevant to future recovery scenarios. + tx_heights + .values() + .min() + .copied() + .or(chain_tip) + .map_or(sapling_activation, |h| std::cmp::max(h, sapling_activation)), + chain_tip, + ) + .await?; + + let mnemonic_seed_data = match Self::parse_mnemonic(wallet.bip39_mnemonic().mnemonic())? { + Some(m) => Some(( + SecretVec::new(m.to_seed("").to_vec()), + keystore.encrypt_and_store_mnemonic(m).await?, + )), + None => None, + }; + + let legacy_transparent_account_uuid = if let Some((seed, _)) = mnemonic_seed_data.as_ref() { + // Always create the zero-indexed unified account + db_data.import_account_hd( + &format!( + "zcashd imported unified account {}", + u32::from(AccountId::ZERO), + ), + seed, + AccountId::ZERO, + &wallet_birthday, + Some("zcashd_mnemonic"), + )?; + + // If there are any legacy transparent keys, also create the legacy account. + if !wallet.keys().is_empty() { + let (account, _) = db_data.import_account_hd( + &format!( + "zcashd post-v4.7.0 legacy transparent account {}", + u32::from(ZCASHD_LEGACY_ACCOUNT), + ), + seed, + ZCASHD_LEGACY_ACCOUNT, + &wallet_birthday, + Some("zcashd_mnemonic"), + )?; + + Some(account.id()) + } else { + None + } + } else { + None + }; + + let mnemonic_seed_fp = mnemonic_seed_data.as_ref().map(|(_, fp)| *fp); + + let legacy_seed_data = match wallet.legacy_hd_seed() { + Some(d) => Some(( + SecretVec::new(d.seed_data().to_vec()), + keystore + .encrypt_and_store_legacy_seed(&SecretVec::new(d.seed_data().to_vec())) + .await?, + )), + None => None, + }; + let legacy_transparent_account_uuid = + match (legacy_transparent_account_uuid, legacy_seed_data.as_ref()) { + (Some(uuid), _) => { + // We already had a mnemonic seed and have created the mnemonic-based legacy + // account, so we don't need to do anything. + Some(uuid) + } + (None, Some((seed, _))) if !wallet.keys().is_empty() => { + // In this case, we have the legacy seed, but no mnemonic seed was ever derived + // from it, so this is a pre-v4.7.0 wallet. We construct the mnemonic in the same + // fashion as zcashd, by using the legacy seed as entropy in the generation of the + // mnemonic seed, and then import that seed and the associated legacy account so + // that we have an account to act as the "bucket of funds" for the transparent keys + // derived from system randomness. + let mnemonic = zcash_keys::keys::zcashd::derive_mnemonic(seed) + .ok_or(ErrorKind::Generic.context(fl!("err-failed-seed-fingerprinting")))?; + + let seed = SecretVec::new(mnemonic.to_seed("").to_vec()); + keystore.encrypt_and_store_mnemonic(mnemonic).await?; + let (account, _) = db_data.import_account_hd( + &format!( + "zcashd post-v4.7.0 legacy transparent account {}", + u32::from(ZCASHD_LEGACY_ACCOUNT), + ), + &seed, + ZCASHD_LEGACY_ACCOUNT, + &wallet_birthday, + Some("zcashd_mnemonic"), + )?; + + Some(account.id()) + } + _ => None, + }; + + let legacy_seed_fp = legacy_seed_data.map(|(_, fp)| fp); + + // Add unified accounts. The only source of unified accounts in zcashd is derivation from + // the mnemonic seed. + for (_, account) in wallet.unified_accounts().account_metadata.iter() { + // The only way that a unified account could be created in zcashd was + // to be derived from the mnemonic seed, so we can safely unwrap here. + let (seed, seed_fp) = mnemonic_seed_data + .as_ref() + .expect("mnemonic seed should be present"); + + assert_eq!( + SeedFingerprint::from_bytes(*account.seed_fingerprint().as_bytes()), + *seed_fp + ); + + let zip32_account_id = AccountId::try_from(account.zip32_account_id()) + .map_err(|_| MigrateError::AccountIdInvalid(account.zip32_account_id()))?; + + // FIXME: this `if` is a workaround for the lack of ability to query by seed + // fingerprint & zip32 account ID. + if db_data + .get_derived_account(&Zip32Derivation::new(*seed_fp, zip32_account_id, None))? + .is_none() + { + db_data.import_account_hd( + &format!( + "zcashd imported unified account {}", + account.zip32_account_id() + ), + seed, + zip32_account_id, + &wallet_birthday, + Some("zcashd_mnemonic"), + )?; + } + } + + // Sapling keys may originate from: + // * The legacy HD seed, under a standard ZIP 32 key path + // * The mnemonic HD seed, under a standard ZIP 32 key path + // * The mnemonic HD seed, under the "legacy" account with an additional hardened path element + // * Zcashd Sapling spending key import + for (idx, key) in wallet.sapling_keys().keypairs().enumerate() { + // `zewif_zcashd` parses to an earlier version of the `sapling` types, so we + // must roundtrip through the byte representation into the version we need. + let extsk = sapling::zip32::ExtendedSpendingKey::from_bytes(&key.extsk().to_bytes()) + .map_err(|_| ()) //work around missing Debug impl + .expect("Sapling extsk encoding is stable across sapling-crypto versions"); + #[allow(deprecated)] + let extfvk = extsk.to_extended_full_viewing_key(); + let ufvk = + UnifiedFullViewingKey::from_sapling_extended_full_viewing_key(extfvk.clone())?; + + let key_seed_fp = key + .metadata() + .seed_fp() + .map(|seed_fp_bytes| SeedFingerprint::from_bytes(*seed_fp_bytes.as_bytes())); + + let derivation = key + .metadata() + .hd_keypath() + .map(|keypath| ZcashdHdDerivation::parse_hd_path(&network, keypath)) + .transpose()? + .zip(key_seed_fp) + .map(|(derivation, key_seed_fp)| match derivation { + ZcashdHdDerivation::Zip32 { account_id } => { + Zip32Derivation::new(key_seed_fp, account_id, None) + } + ZcashdHdDerivation::Post470LegacySapling { address_index } => { + Zip32Derivation::new( + key_seed_fp, + ZCASHD_LEGACY_ACCOUNT, + Some(address_index), + ) + } + }); + + // If the key is not associated with either of the seeds, treat it as a standalone + // imported key + if key_seed_fp != mnemonic_seed_fp && key_seed_fp != legacy_seed_fp { + keystore + .encrypt_and_store_standalone_sapling_key(&extsk) + .await?; + } + + // FIXME: this `if` is a workaround for the lack of ability to query by seed fingerprint + // & zip32 account ID. + let account_exists = match key_seed_fp.as_ref() { + Some(fp) => db_data + .get_derived_account(&Zip32Derivation::new( + *fp, + ZCASHD_LEGACY_ACCOUNT, + derivation.as_ref().and_then(|d| d.legacy_address_index()), + ))? + .is_some(), + None => db_data.get_account_for_ufvk(&ufvk)?.is_some(), + }; + + if !account_exists { + db_data.import_account_ufvk( + &format!("zcashd legacy sapling {}", idx), + &ufvk, + &wallet_birthday, + AccountPurpose::Spending { derivation }, + Some("zcashd_legacy"), + )?; + } + } + + // TODO: Move this into zewif-zcashd once we're out of dependency version hell. + fn convert_key( + key: &zewif_zcashd::zcashd_wallet::transparent::KeyPair, + ) -> Result { + // Check the encoding of the pubkey + let _ = PublicKey::from_slice(key.pubkey().as_slice())?; + let compressed = key.pubkey().is_compressed(); + + let key = zcash_keys::keys::transparent::Key::der_decode( + &SecretVec::new(key.privkey().data().to_vec()), + compressed, + ) + .map_err(|_| { + ErrorKind::Generic.context(fl!( + "err-migrate-wallet-key-decoding", + err = "failed DER decoding" + )) + })?; + + Ok(key) + } + + for key in wallet.keys().keypairs() { + let key = convert_key(key)?; + keystore + .encrypt_and_store_standalone_transparent_key(&key) + .await?; + + db_data.import_standalone_transparent_pubkey( + legacy_transparent_account_uuid.ok_or(MigrateError::SeedNotAvailable)?, + key.pubkey(), + )?; + } + + Ok(()) + } + + async fn start(&self) -> Result<(), Error> { + let config = APP.config(); + + // Start monitoring the chain. + let (chain_view, _chain_indexer_task_handle) = ChainView::new(&config).await?; + let db = Database::open(&config).await?; + let keystore = KeyStore::new(&config, db.clone())?; + + let wallet_path = if self.path.is_relative() { + if let Some(datadir) = self.zcashd_datadir.as_ref() { + datadir.join(&self.path) + } else { + migrate_zcash_conf::zcashd_default_data_dir() + .ok_or(ErrorKind::Generic)? + .join(&self.path) + } + } else { + self.path.to_path_buf() + }; + + let wallet = Self::dump_wallet(&wallet_path, self.allow_warnings)?; + + Self::migrate_zcashd_wallet(db, keystore, chain_view, wallet).await?; + + Ok(()) + } +} + +impl Runnable for MigrateZcashdWalletCmd { + fn run(&self) { + match abscissa_tokio::run(&APP, self.start()) { + Ok(Ok(())) => (), + Ok(Err(e)) => { + eprintln!("{}", e); + APP.shutdown_with_exitcode(Shutdown::Forced, 1); + } + Err(e) => { + eprintln!("{}", e); + APP.shutdown_with_exitcode(Shutdown::Forced, 1); + } + } + } +} diff --git a/zallet/src/components/database/connection.rs b/zallet/src/components/database/connection.rs index 0683bd80..56de93f0 100644 --- a/zallet/src/components/database/connection.rs +++ b/zallet/src/components/database/connection.rs @@ -98,7 +98,7 @@ impl DbConnection { &self.params } - fn with( + pub(crate) fn with( &self, f: impl FnOnce(WalletDb<&rusqlite::Connection, Network, SystemClock, OsRng>) -> T, ) -> T { diff --git a/zallet/src/components/database/tests.rs b/zallet/src/components/database/tests.rs index 8a255a49..f2460584 100644 --- a/zallet/src/components/database/tests.rs +++ b/zallet/src/components/database/tests.rs @@ -57,6 +57,8 @@ fn verify_schema() { keystore::db::TABLE_LEGACY_SEEDS, #[cfg(zallet_build = "wallet")] keystore::db::TABLE_MNEMONICS, + keystore::db::TABLE_STANDALONE_SAPLING_KEYS, + keystore::db::TABLE_STANDALONE_TRANSPARENT_KEYS, ], ); diff --git a/zallet/src/components/json_rpc/methods/view_transaction.rs b/zallet/src/components/json_rpc/methods/view_transaction.rs index a4326736..928e9d7d 100644 --- a/zallet/src/components/json_rpc/methods/view_transaction.rs +++ b/zallet/src/components/json_rpc/methods/view_transaction.rs @@ -475,6 +475,10 @@ pub(crate) async fn call( }); } + let account_ids = wallet.get_account_ids().map_err(|e| { + LegacyCode::Database + .with_message(format!("Failed to retrieve transparent account IDs: {e}")) + })?; // Transparent outputs for (output, idx) in bundle.vout.iter().zip(0..) { let (account_uuid, address, outgoing, wallet_internal) = diff --git a/zallet/src/components/keystore.rs b/zallet/src/components/keystore.rs index 8c60cbf2..cebac5aa 100644 --- a/zallet/src/components/keystore.rs +++ b/zallet/src/components/keystore.rs @@ -117,6 +117,7 @@ use std::time::{Duration, SystemTime}; use bip0039::{English, Mnemonic}; use rusqlite::named_params; +use sapling::zip32::{DiversifiableFullViewingKey, ExtendedSpendingKey}; use secrecy::{ExposeSecret, SecretString, SecretVec, Zeroize}; use tokio::{ sync::{Mutex, RwLock}, @@ -497,18 +498,15 @@ impl KeyStore { pub(crate) async fn encrypt_and_store_mnemonic( &self, - mnemonic: &SecretString, + mnemonic: Mnemonic, ) -> Result { let recipients = self.recipients().await?; - let seed_bytes = SecretVec::new( - Mnemonic::::from_phrase(mnemonic.expose_secret()) - .map_err(|e| ErrorKind::Generic.context(e))? - .to_seed("") - .to_vec(), - ); + let seed_bytes = SecretVec::new(mnemonic.to_seed("").to_vec()); let seed_fp = SeedFingerprint::from_seed(seed_bytes.expose_secret()).expect("valid length"); + // Take ownership of the memory of the mnemonic to ensure it will be correctly zeroized on drop + let mnemonic = SecretString::new(mnemonic.into_phrase()); let encrypted_mnemonic = encrypt_string(&recipients, mnemonic.expose_secret()) .map_err(|e| ErrorKind::Generic.context(e))?; @@ -561,6 +559,62 @@ impl KeyStore { Ok(legacy_seed_fp) } + pub(crate) async fn encrypt_and_store_standalone_sapling_key( + &self, + sapling_key: &ExtendedSpendingKey, + ) -> Result { + let recipients = self.recipients().await?; + + let dfvk = sapling_key.to_diversifiable_full_viewing_key(); + let encrypted_sapling_extsk = encrypt_standalone_sapling_key(&recipients, sapling_key) + .map_err(|e| ErrorKind::Generic.context(e))?; + + self.with_db_mut(|conn, _| { + conn.execute( + "INSERT INTO ext_zallet_keystore_standalone_sapling_keys + VALUES (:dfvk, :encrypted_sapling_extsk) + ON CONFLICT (dfvk) DO NOTHING ", + named_params! { + ":dfvk": &dfvk.to_bytes(), + ":encrypted_sapling_extsk": encrypted_sapling_extsk, + }, + ) + .map_err(|e| ErrorKind::Generic.context(e))?; + Ok(()) + }) + .await?; + + Ok(dfvk) + } + + pub(crate) async fn encrypt_and_store_standalone_transparent_key( + &self, + key: &zcash_keys::keys::transparent::Key, + ) -> Result<(), Error> { + let recipients = self.recipients().await?; + + let encrypted_transparent_key = + encrypt_standalone_transparent_privkey(&recipients, key.secret()) + .map_err(|e| ErrorKind::Generic.context(e))?; + + self.with_db_mut(|conn, _| { + conn.execute( + "INSERT INTO ext_zallet_keystore_standalone_transparent_keys + VALUES (:pubkey, :encrypted_key_bytes) + ON CONFLICT (pubkey) DO NOTHING ", + named_params! { + ":pubkey": &key.pubkey().serialize(), + ":encrypted_key_bytes": encrypted_transparent_key, + }, + ) + .map_err(|e| ErrorKind::Generic.context(e))?; + Ok(()) + }) + .await?; + + Ok(()) + } + /// Decrypts the mnemonic phrase corresponding to the given seed fingerprint. async fn decrypt_mnemonic(&self, seed_fp: &SeedFingerprint) -> Result { // Acquire a read lock on the identities for decryption. @@ -620,20 +674,43 @@ fn encrypt_string( Ok(ciphertext) } -fn encrypt_legacy_seed_bytes( +fn encrypt_secret( recipients: &[Box], - seed: &SecretVec, + secret: &SecretVec, ) -> Result, age::EncryptError> { let encryptor = age::Encryptor::with_recipients(recipients.iter().map(|r| r.as_ref() as _))?; - let mut ciphertext = Vec::with_capacity(seed.expose_secret().len()); + let mut ciphertext = Vec::with_capacity(secret.expose_secret().len()); let mut writer = encryptor.wrap_output(&mut ciphertext)?; - writer.write_all(seed.expose_secret())?; + writer.write_all(secret.expose_secret())?; writer.finish()?; Ok(ciphertext) } +fn encrypt_legacy_seed_bytes( + recipients: &[Box], + seed: &SecretVec, +) -> Result, age::EncryptError> { + encrypt_secret(recipients, seed) +} + +fn encrypt_standalone_sapling_key( + recipients: &[Box], + key: &ExtendedSpendingKey, +) -> Result, age::EncryptError> { + let secret = SecretVec::new(key.to_bytes().to_vec()); + encrypt_secret(recipients, &secret) +} + +fn encrypt_standalone_transparent_privkey( + recipients: &[Box], + key: &secp256k1::SecretKey, +) -> Result, age::EncryptError> { + let secret = SecretVec::new(key.secret_bytes().to_vec()); + encrypt_secret(recipients, &secret) +} + fn decrypt_string( identities: &[Box], ciphertext: &[u8], diff --git a/zallet/src/components/keystore/db.rs b/zallet/src/components/keystore/db.rs index d2e2ecc0..1cee8372 100644 --- a/zallet/src/components/keystore/db.rs +++ b/zallet/src/components/keystore/db.rs @@ -53,7 +53,7 @@ CREATE TABLE ext_zallet_keystore_mnemonics ( ) "#; -/// Stores encrypted raw HD seeds. +/// Stores encrypted raw HD seeds. These are likely to only be produced via `zcashd` wallet import. /// /// ### Columns /// @@ -70,3 +70,41 @@ CREATE TABLE ext_zallet_keystore_legacy_seeds ( encrypted_legacy_seed BLOB NOT NULL ) "#; + +/// Stores encrypted standalone Sapling spending keys. +/// +/// ### Columns +/// +/// - `dfvk` is the [`DiversifiableFullViewingKey`] derived from the spending key. +/// - `encrypted_sapling_extsk` is a [ZIP 32]-encoded [`ExtendedFullViewingKey`] in an +/// [age encrypted file]. +/// +/// [ZIP 32]: https://zips.z.cash/zip-0032 +/// [`DiversifiableFullViewingKey`]: sapling::zip32::DiversifiableFullViewingKey +/// [`ExtendedFullViewingKey`]: sapling::zip32::ExtendedFullViewingKey +/// [age encrypted file]: https://c2sp.org/age#encrypted-file-format +pub(crate) const TABLE_STANDALONE_SAPLING_KEYS: &str = r#" +CREATE TABLE ext_zallet_keystore_standalone_sapling_keys ( + dfvk BLOB NOT NULL UNIQUE, + encrypted_sapling_extsk BLOB NOT NULL +) +"#; + +/// Stores encrypted standalone transparent secret keys. +/// +/// ### Columns +/// +/// - `pubkey` is the [`PublicKey`] derived from the spending key. +/// - `encrypted_transparent_privkey` is a [`SecretKey`] serialized in its compressed form in an +/// [age encrypted file]. +/// +/// [ZIP 32]: https://zips.z.cash/zip-0032 +/// [`Publickey`]: secp256k1::PublicKey +/// [`SecretKey`]: secp256k1::SecretKey +/// [age encrypted file]: https://c2sp.org/age#encrypted-file-format +pub(crate) const TABLE_STANDALONE_TRANSPARENT_KEYS: &str = r#" +CREATE TABLE ext_zallet_keystore_standalone_transparent_keys ( + pubkey BLOB NOT NULL UNIQUE, + encrypted_transparent_privkey BLOB NOT NULL +) +"#; diff --git a/zallet/src/components/keystore/db/migrations/initial_setup.rs b/zallet/src/components/keystore/db/migrations/initial_setup.rs index d4f46cfa..dc1bf3c6 100644 --- a/zallet/src/components/keystore/db/migrations/initial_setup.rs +++ b/zallet/src/components/keystore/db/migrations/initial_setup.rs @@ -38,6 +38,14 @@ impl RusqliteMigration for Migration { CREATE TABLE ext_zallet_keystore_legacy_seeds ( hd_seed_fingerprint BLOB NOT NULL UNIQUE, encrypted_legacy_seed BLOB NOT NULL + ); + CREATE TABLE ext_zallet_keystore_standalone_sapling_keys ( + dfvk BLOB NOT NULL UNIQUE, + encrypted_sapling_extsk BLOB NOT NULL + ); + CREATE TABLE ext_zallet_keystore_standalone_transparent_keys ( + pubkey BLOB NOT NULL UNIQUE, + encrypted_transparent_privkey BLOB NOT NULL );", )?; Ok(()) diff --git a/zallet/src/lib.rs b/zallet/src/lib.rs index aaf13179..9f7a706e 100644 --- a/zallet/src/lib.rs +++ b/zallet/src/lib.rs @@ -25,6 +25,7 @@ mod error; mod i18n; pub mod network; mod prelude; +mod rosetta; mod task; // Needed for the `Component` derive to work. diff --git a/zallet/src/rosetta.rs b/zallet/src/rosetta.rs new file mode 100644 index 00000000..19f6ab61 --- /dev/null +++ b/zallet/src/rosetta.rs @@ -0,0 +1,69 @@ +//! Conversion utilities for adapting between `zebra`, `zaino`, and `zcash_client_backend` types. + +use incrementalmerkletree::frontier::CommitmentTree; +use orchard::tree::MerkleHashOrchard; +use sapling::Node; +use std::io; +use zcash_client_backend::data_api::chain::ChainState; +use zcash_primitives::{block::BlockHash, merkle_tree::read_commitment_tree}; + +pub(crate) fn to_chainstate( + ts: zaino_proto::proto::service::TreeState, +) -> Result { + let mut hash_bytes = hex::decode(&ts.hash).map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("Block hash is not valid hex: {:?}", e), + ) + })?; + // Zcashd hex strings for block hashes are byte-reversed. + hash_bytes.reverse(); + + Ok(ChainState::new( + ts.height + .try_into() + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid block height"))?, + BlockHash::try_from_slice(&hash_bytes).ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidData, "Invalid block hash length.") + })?, + sapling_tree(&ts.sapling_tree)?.to_frontier(), + orchard_tree(&ts.orchard_tree)?.to_frontier(), + )) +} + +/// Deserializes and returns the Sapling note commitment tree field of the tree state. +pub(crate) fn sapling_tree( + sapling_tree_str: &str, +) -> io::Result> { + if sapling_tree_str.is_empty() { + Ok(CommitmentTree::empty()) + } else { + let sapling_tree_bytes = hex::decode(sapling_tree_str).map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("Hex decoding of Sapling tree bytes failed: {:?}", e), + ) + })?; + read_commitment_tree::( + &sapling_tree_bytes[..], + ) + } +} + +pub fn orchard_tree( + orchard_tree_str: &str, +) -> io::Result> { + if orchard_tree_str.is_empty() { + Ok(CommitmentTree::empty()) + } else { + let orchard_tree_bytes = hex::decode(orchard_tree_str).map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("Hex decoding of Orchard tree bytes failed: {:?}", e), + ) + })?; + read_commitment_tree::( + &orchard_tree_bytes[..], + ) + } +} diff --git a/zallet/tests/cmd/migrate_zcash_conf_mainnet.toml b/zallet/tests/cmd/migrate_zcash_conf_mainnet.toml index 6e0ad0e1..81a68420 100644 --- a/zallet/tests/cmd/migrate_zcash_conf_mainnet.toml +++ b/zallet/tests/cmd/migrate_zcash_conf_mainnet.toml @@ -1,5 +1,5 @@ bin.name = "zallet" -args = "migrate-zcash-conf --datadir ./ -o - --this-is-alpha-code-and-you-will-need-to-redo-the-migration-later" +args = "migrate-zcash-conf --zcashd-datadir ./ -o - --this-is-alpha-code-and-you-will-need-to-redo-the-migration-later" stdin = "" stdout = """ # Zallet configuration file diff --git a/zallet/tests/cmd/migrate_zcash_conf_regtest.toml b/zallet/tests/cmd/migrate_zcash_conf_regtest.toml index f24ae072..941ac75f 100644 --- a/zallet/tests/cmd/migrate_zcash_conf_regtest.toml +++ b/zallet/tests/cmd/migrate_zcash_conf_regtest.toml @@ -1,5 +1,5 @@ bin.name = "zallet" -args = "migrate-zcash-conf --datadir ./ -o - --this-is-alpha-code-and-you-will-need-to-redo-the-migration-later" +args = "migrate-zcash-conf --zcashd-datadir ./ -o - --this-is-alpha-code-and-you-will-need-to-redo-the-migration-later" stdin = "" stdout = """ # Zallet configuration file diff --git a/zallet/tests/cmd/migrate_zcash_conf_testnet.toml b/zallet/tests/cmd/migrate_zcash_conf_testnet.toml index aa7d3ac9..c18445f5 100644 --- a/zallet/tests/cmd/migrate_zcash_conf_testnet.toml +++ b/zallet/tests/cmd/migrate_zcash_conf_testnet.toml @@ -1,5 +1,5 @@ bin.name = "zallet" -args = "migrate-zcash-conf --datadir ./ -o - --this-is-alpha-code-and-you-will-need-to-redo-the-migration-later" +args = "migrate-zcash-conf --zcashd-datadir ./ -o - --this-is-alpha-code-and-you-will-need-to-redo-the-migration-later" stdin = "" stdout = """ # Zallet configuration file From 0eac2d48b92aaac4f2ed36a30bf0cbd1175db019 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 30 May 2025 12:23:50 -0600 Subject: [PATCH 04/15] Skip coinbase transactions in transparent tree traversal. --- zallet/src/components/sync.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zallet/src/components/sync.rs b/zallet/src/components/sync.rs index 6ab96a7f..01ee4f6f 100644 --- a/zallet/src/components/sync.rs +++ b/zallet/src/components/sync.rs @@ -493,6 +493,10 @@ async fn data_requests( for request in requests { match request { TransactionDataRequest::GetStatus(txid) => { + if txid.is_null() { + continue; + } + info!("Getting status of {txid}"); let status = match chain.get_raw_transaction(txid.to_string(), Some(1)).await { // TODO: Zaino should have a Rust API for fetching tx details, @@ -521,6 +525,10 @@ async fn data_requests( db_data.set_transaction_status(txid, status)?; } TransactionDataRequest::Enhancement(txid) => { + if txid.is_null() { + continue; + } + info!("Enhancing {txid}"); let tx = match chain.get_raw_transaction(txid.to_string(), Some(1)).await { // TODO: Zaino should have a Rust API for fetching tx details, From 11d48e81c139052d3792863e003d39d467a4e79f Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 11 Sep 2025 19:56:14 -0600 Subject: [PATCH 05/15] rpc (z_sendmany): Allow spending of funds held by imported transparent privkeys. --- .../json_rpc/methods/z_send_many.rs | 82 ++++++++++++------- zallet/src/components/keystore.rs | 65 +++++++++++++++ 2 files changed, 116 insertions(+), 31 deletions(-) diff --git a/zallet/src/components/json_rpc/methods/z_send_many.rs b/zallet/src/components/json_rpc/methods/z_send_many.rs index 962e590e..33262e59 100644 --- a/zallet/src/components/json_rpc/methods/z_send_many.rs +++ b/zallet/src/components/json_rpc/methods/z_send_many.rs @@ -161,10 +161,6 @@ pub(crate) async fn call( get_account_for_address(wallet.as_ref(), &address) } }?; - let derivation = account.source().key_derivation().ok_or_else(|| { - LegacyCode::InvalidAddressOrKey - .with_static("Invalid from address, no payment source found for address.") - })?; let privacy_policy = match privacy_policy.as_deref() { Some("LegacyCompat") => Err(LegacyCode::InvalidParameter @@ -191,24 +187,6 @@ pub(crate) async fn call( } }; - // Fetch spending key last, to avoid a keystore decryption if unnecessary. - let seed = keystore - .decrypt_seed(derivation.seed_fingerprint()) - .await - .map_err(|e| match e.kind() { - // TODO: Improve internal error types. - crate::error::ErrorKind::Generic if e.to_string() == "Wallet is locked" => { - LegacyCode::WalletUnlockNeeded.with_message(e.to_string()) - } - _ => LegacyCode::Database.with_message(e.to_string()), - })?; - let usk = UnifiedSpendingKey::from_seed( - wallet.params(), - seed.expose_secret(), - derivation.account_index(), - ) - .map_err(|e| LegacyCode::InvalidAddressOrKey.with_message(e.to_string()))?; - Ok(( Some(ContextInfo::new( "z_sendmany", @@ -220,12 +198,12 @@ pub(crate) async fn call( )), run( wallet, + keystore, chain, - account.id(), + account, request, confirmations_policy, privacy_policy, - usk, ), )) } @@ -239,15 +217,14 @@ pub(crate) async fn call( /// 2. #1360 Note selection is not optimal. /// 3. #1277 Spendable notes are not locked, so an operation running in parallel /// could also try to use them. -async fn run( +async fn run>( mut wallet: DbHandle, + keystore: KeyStore, chain: FetchServiceSubscriber, - spend_from_account: AccountUuid, + spend_from_account: A, request: TransactionRequest, confirmations_policy: ConfirmationsPolicy, privacy_policy: PrivacyPolicy, - // TODO: Support legacy transparent pool of funds. https://github.com/zcash/wallet/issues/138 - usk: UnifiedSpendingKey, ) -> RpcResult { let params = *wallet.params(); @@ -329,7 +306,7 @@ async fn run( let proposal = propose_transfer::<_, _, _, _, Infallible>( wallet.as_mut(), ¶ms, - spend_from_account, + spend_from_account.id(), &input_selector, &change_strategy, request, @@ -385,14 +362,57 @@ async fn run( let prover = LocalTxProver::bundled(); + let derivation = spend_from_account + .source() + .key_derivation() + .ok_or_else(|| { + LegacyCode::InvalidAddressOrKey + .with_static("Invalid from address, no payment source found for address.") + })?; + // Fetch spending key last, to avoid a keystore decryption if unnecessary. + let seed = keystore + .decrypt_seed(derivation.seed_fingerprint()) + .await + .map_err(|e| match e.kind() { + // TODO: Improve internal error types. + crate::error::ErrorKind::Generic if e.to_string() == "Wallet is locked" => { + LegacyCode::WalletUnlockNeeded.with_message(e.to_string()) + } + _ => LegacyCode::Database.with_message(e.to_string()), + })?; + let usk = UnifiedSpendingKey::from_seed( + wallet.params(), + seed.expose_secret(), + derivation.account_index(), + ) + .map_err(|e| LegacyCode::InvalidAddressOrKey.with_message(e.to_string()))?; + + let mut standalone_keys = HashMap::new(); + for step in proposal.steps() { + for input in step.transparent_inputs() { + if let Some(address) = input.txout().script_pubkey().address() { + let secret_key = keystore + .decrypt_standalone_transparent_key(&address) + .await + .map_err(|e| match e.kind() { + // TODO: Improve internal error types. + crate::error::ErrorKind::Generic if e.to_string() == "Wallet is locked" => { + LegacyCode::WalletUnlockNeeded.with_message(e.to_string()) + } + _ => LegacyCode::Database.with_message(e.to_string()), + })?; + standalone_keys.insert(address, secret_key); + } + } + } + let (wallet, txids) = crate::spawn_blocking!("z_sendmany prover", move || { create_proposed_transactions::<_, _, Infallible, _, Infallible, _>( wallet.as_mut(), ¶ms, &prover, &prover, - // TODO: Look up spending keys for imported transparent addresses used in the proposal. - &SpendingKeys::new(usk, HashMap::new()), + &SpendingKeys::new(usk, standalone_keys), OvkPolicy::Sender, &proposal, ) diff --git a/zallet/src/components/keystore.rs b/zallet/src/components/keystore.rs index cebac5aa..1b4fd2b6 100644 --- a/zallet/src/components/keystore.rs +++ b/zallet/src/components/keystore.rs @@ -124,6 +124,8 @@ use tokio::{ task::JoinHandle, time, }; +use transparent::address::TransparentAddress; +use zcash_keys::address::Address; use zip32::fingerprint::SeedFingerprint; use crate::network::Network; @@ -658,6 +660,41 @@ impl KeyStore { Ok(seed) } + + pub(crate) async fn decrypt_standalone_transparent_key( + &self, + address: &TransparentAddress, + ) -> Result { + // Acquire a read lock on the identities for decryption. + let identities = self.identities.read().await; + if identities.is_empty() { + return Err(ErrorKind::Generic.context("Wallet is locked").into()); + } + + let encrypted_key_bytes = self + .with_db(|conn, network| { + let addr_str = Address::Transparent(*address).encode(network); + let encrypted_key_bytes = conn + .query_row( + "SELECT encrypted_key_bytes + FROM ext_zallet_keystore_standalone_transparent_keys ztk + JOIN addresses a ON ztk.pubkey = a.imported_transparent_receiver_pubkey + WHERE a.cached_transparent_receiver_address = :address", + named_params! { + ":address": addr_str, + }, + |row| row.get::<_, Vec>("encrypted_key_bytes"), + ) + .map_err(|e| ErrorKind::Generic.context(e))?; + Ok(encrypted_key_bytes) + }) + .await?; + + let secret_key = + decrypt_standalone_transparent_privkey(&identities, &encrypted_key_bytes[..])?; + + Ok(secret_key) + } } fn encrypt_string( @@ -735,3 +772,31 @@ fn decrypt_string( Ok(mnemonic) } + +fn decrypt_standalone_transparent_privkey( + identities: &[Box], + ciphertext: &[u8], +) -> Result { + let decryptor = age::Decryptor::new(ciphertext).map_err(|e| ErrorKind::Generic.context(e))?; + + // The plaintext is always shorter than the ciphertext. Over-allocating the initial + // string ensures that no internal re-allocations occur that might leave plaintext + // bytes strewn around the heap. + let mut buf = Vec::with_capacity(ciphertext.len()); + let res = decryptor + .decrypt(identities.iter().map(|i| i.as_ref() as _)) + .map_err(|e| ErrorKind::Generic.context(e))? + .read(&mut buf); + + // We intentionally do not use `?` on the decryption expression because doing so in + // the case of a partial failure could result in part of the secret data being read + // into `buf`, which would not then be properly zeroized. Instead, we take ownership + // of the buffer in construction of a `SecretVec` to ensure that the memory is + // zeroed out when we raise the error on the following line. + let buf_secret = SecretVec::new(buf); + res.map_err(|e| ErrorKind::Generic.context(e))?; + let secret_key = secp256k1::SecretKey::from_slice(buf_secret.expose_secret()) + .map_err(|e| ErrorKind::Generic.context(e))?; + + Ok(secret_key) +} From 908526cf9e351df53c704671ea06bd79194f548f Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 12 Sep 2025 11:54:29 -0600 Subject: [PATCH 06/15] migrate_zcashd_wallet: Decrypt and store wallet transactions as part of import. --- zallet/src/cli.rs | 6 +++ zallet/src/commands/migrate_zcashd_wallet.rs | 43 ++++++++++++++++---- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/zallet/src/cli.rs b/zallet/src/cli.rs index b0b72668..1e21e0e2 100644 --- a/zallet/src/cli.rs +++ b/zallet/src/cli.rs @@ -154,6 +154,12 @@ pub(crate) struct MigrateZcashdWalletCmd { #[arg(long)] pub(crate) zcashd_datadir: Option, + /// Buffer wallet transactions in-memory in the process of performing the wallet restore. For + /// very active wallets, this might exceed the available memory on your machine, so enable this + /// with caution. + #[arg(long)] + pub(crate) buffer_wallet_transactions: bool, + /// Allow a migration when warnings are present. If set to `false`, any warning will be treated /// as an error and cause the migration to abort. #[arg(long)] diff --git a/zallet/src/commands/migrate_zcashd_wallet.rs b/zallet/src/commands/migrate_zcashd_wallet.rs index 8a3b028f..64d12d78 100644 --- a/zallet/src/commands/migrate_zcashd_wallet.rs +++ b/zallet/src/commands/migrate_zcashd_wallet.rs @@ -13,13 +13,15 @@ use zaino_proto::proto::service::TxFilter; use zaino_state::{FetchServiceError, LightWalletIndexer}; use zcash_client_backend::data_api::{ Account as _, AccountBirthday, AccountPurpose, WalletRead, WalletWrite as _, Zip32Derivation, + wallet::decrypt_and_store_transaction, }; use zcash_client_sqlite::error::SqliteClientError; use zcash_keys::keys::{ DerivationError, UnifiedFullViewingKey, zcashd::{PathParseError, ZcashdHdDerivation}, }; -use zcash_protocol::consensus::{BlockHeight, NetworkType, Parameters}; +use zcash_primitives::transaction::Transaction; +use zcash_protocol::consensus::{BlockHeight, BranchId, NetworkType, Parameters}; use zewif_zcashd::{BDBDump, ZcashdDump, ZcashdParser, ZcashdWallet}; use zip32::{AccountId, fingerprint::SeedFingerprint}; @@ -91,7 +93,6 @@ impl From for Error { MigrateError::SeedNotAvailable => { Error::from(ErrorKind::Generic.context(fl!("err-migrate-wallet-seed-absent"))) } - MigrateError::MnemonicInvalid(error) => Error::from(ErrorKind::Generic.context(fl!( "err-migrate-wallet-invalid-mnemonic", err = error.to_string() @@ -307,21 +308,22 @@ impl MigrateZcashdWalletCmd { keystore: KeyStore, chain_view: ChainView, wallet: ZcashdWallet, + buffer_wallet_transactions: bool, ) -> Result<(), MigrateError> { let mut db_data = db.handle().await?; - let network = Self::check_network(wallet.network(), db_data.params().network_type())?; + let network_params = *db_data.params(); + Self::check_network(wallet.network(), network_params.network_type())?; // Obtain information about the current state of the chain, so that we can set the recovery // height properly. let chain_subscriber = chain_view.subscribe().await?.inner(); let chain_tip = Self::chain_tip(&chain_subscriber).await?; - let sapling_activation = db_data - .params() + let sapling_activation = network_params .activation_height(zcash_protocol::consensus::NetworkUpgrade::Sapling) .expect("Sapling activation height is defined."); // Collect an index from txid to block height for all transactions known to the wallet that - // appear in the main chain.. + // appear in the main chain. let mut tx_heights = HashMap::new(); for (txid, _) in wallet.transactions().iter() { let tx_filter = TxFilter { @@ -339,7 +341,10 @@ impl MigrateZcashdWalletCmd { err = e.to_string() )) })?); - tx_heights.insert(txid, tx_height); + tx_heights.insert( + txid, + (tx_height, buffer_wallet_transactions.then_some(raw_tx)), + ); } Err(FetchServiceError::TonicStatusError(status)) if (status.code() as isize) == (tonic::Code::NotFound as isize) => @@ -366,6 +371,7 @@ impl MigrateZcashdWalletCmd { // minimum possible wallet birthday that is relevant to future recovery scenarios. tx_heights .values() + .map(|(h, _)| h) .min() .copied() .or(chain_tip) @@ -524,7 +530,7 @@ impl MigrateZcashdWalletCmd { let derivation = key .metadata() .hd_keypath() - .map(|keypath| ZcashdHdDerivation::parse_hd_path(&network, keypath)) + .map(|keypath| ZcashdHdDerivation::parse_hd_path(&network_params, keypath)) .transpose()? .zip(key_seed_fp) .map(|(derivation, key_seed_fp)| match derivation { @@ -606,6 +612,18 @@ impl MigrateZcashdWalletCmd { )?; } + // Since we've retrieved the raw transaction data anyway, preemptively store it for faster + // access to balance & to set priorities in the scan queue. + for (h, raw_tx) in tx_heights.values() { + let branch_id = BranchId::for_height(&network_params, *h); + if let Some(raw_tx) = raw_tx { + let tx = Transaction::read(&raw_tx.data[..], branch_id)?; + db_data.with_mut(|mut db| { + decrypt_and_store_transaction(&network_params, &mut db, &tx, Some(*h)) + })?; + } + } + Ok(()) } @@ -631,7 +649,14 @@ impl MigrateZcashdWalletCmd { let wallet = Self::dump_wallet(&wallet_path, self.allow_warnings)?; - Self::migrate_zcashd_wallet(db, keystore, chain_view, wallet).await?; + Self::migrate_zcashd_wallet( + db, + keystore, + chain_view, + wallet, + self.buffer_wallet_transactions, + ) + .await?; Ok(()) } From ffc5c37a0da26f2603d7ed23704b93b5bf0ebaeb Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 12 Sep 2025 13:46:51 -0600 Subject: [PATCH 07/15] trivial: Move errors to bottom of `migrate_zcashd_wallet` --- zallet/src/commands/migrate_zcashd_wallet.rs | 340 +++++++++---------- 1 file changed, 170 insertions(+), 170 deletions(-) diff --git a/zallet/src/commands/migrate_zcashd_wallet.rs b/zallet/src/commands/migrate_zcashd_wallet.rs index 64d12d78..b2eaf72d 100644 --- a/zallet/src/commands/migrate_zcashd_wallet.rs +++ b/zallet/src/commands/migrate_zcashd_wallet.rs @@ -38,176 +38,6 @@ use super::migrate_zcash_conf; pub const ZCASHD_LEGACY_ACCOUNT: AccountId = AccountId::const_from_u32(0x7FFFFFFF); -#[derive(Debug)] -pub(crate) enum ZewifError { - BdbDump, - ZcashdDump, -} - -#[derive(Debug)] -#[allow(dead_code)] -pub(crate) enum MigrateError { - Wrapped(Error), - Zewif { - error_type: ZewifError, - wallet_path: PathBuf, - error: anyhow::Error, - }, - SeedNotAvailable, - MnemonicInvalid(bip0039::Error), - KeyError(secp256k1::Error), - NetworkMismatch { - wallet_network: zewif::Network, - db_network: NetworkType, - }, - NetworkNotSupported(NetworkType), - Database(SqliteClientError), - Tree(ShardTreeError), - Io(std::io::Error), - Fetch(Box), - KeyDerivation(DerivationError), - HdPath(PathParseError), - AccountIdInvalid(u32), -} - -impl From for Error { - fn from(value: MigrateError) -> Self { - match value { - MigrateError::Wrapped(e) => e, - MigrateError::Zewif { - error_type, - wallet_path, - error, - } => Error::from(match error_type { - ZewifError::BdbDump => ErrorKind::Generic.context(fl!( - "err-migrate-wallet-bdb-parse", - path = wallet_path.to_str(), - err = error.to_string() - )), - ZewifError::ZcashdDump => ErrorKind::Generic.context(fl!( - "err-migrate-wallet-db-dump", - path = wallet_path.to_str(), - err = error.to_string() - )), - }), - MigrateError::SeedNotAvailable => { - Error::from(ErrorKind::Generic.context(fl!("err-migrate-wallet-seed-absent"))) - } - MigrateError::MnemonicInvalid(error) => Error::from(ErrorKind::Generic.context(fl!( - "err-migrate-wallet-invalid-mnemonic", - err = error.to_string() - ))), - MigrateError::KeyError(error) => Error::from(ErrorKind::Generic.context(fl!( - "err-migrate-wallet-key-decoding", - err = error.to_string() - ))), - MigrateError::NetworkMismatch { - wallet_network, - db_network, - } => Error::from(ErrorKind::Generic.context(fl!( - "err-migrate-wallet-network-mismatch", - wallet_network = String::from(wallet_network), - zallet_network = match db_network { - NetworkType::Main => "main", - NetworkType::Test => "test", - NetworkType::Regtest => "regtest", - } - ))), - MigrateError::NetworkNotSupported(_) => { - Error::from(ErrorKind::Generic.context(fl!("err-migrate-wallet-regtest"))) - } - MigrateError::Database(sqlite_client_error) => { - Error::from(ErrorKind::Generic.context(fl!( - "err-migrate-wallet-storage", - err = sqlite_client_error.to_string() - ))) - } - MigrateError::Tree(e) => Error::from( - ErrorKind::Generic - .context(fl!("err-migrate-wallet-data-parse", err = e.to_string())), - ), - MigrateError::Io(e) => Error::from( - ErrorKind::Generic - .context(fl!("err-migrate-wallet-data-parse", err = e.to_string())), - ), - MigrateError::Fetch(e) => Error::from( - ErrorKind::Generic.context(fl!("err-migrate-wallet-tx-fetch", err = e.to_string())), - ), - MigrateError::KeyDerivation(e) => Error::from( - ErrorKind::Generic.context(fl!("err-migrate-wallet-key-data", err = e.to_string())), - ), - MigrateError::HdPath(err) => Error::from(ErrorKind::Generic.context(fl!( - "err-migrate-wallet-data-parse", - err = format!("{:?}", err) - ))), - MigrateError::AccountIdInvalid(id) => Error::from(ErrorKind::Generic.context(fl!( - "err-migrate-wallet-invalid-account-id", - account_id = id - ))), - } - } -} - -impl From> for MigrateError { - fn from(e: ShardTreeError) -> Self { - Self::Tree(e) - } -} - -impl From for MigrateError { - fn from(e: SqliteClientError) -> Self { - Self::Database(e) - } -} - -impl From for MigrateError { - fn from(value: bip0039::Error) -> Self { - Self::MnemonicInvalid(value) - } -} - -impl From for MigrateError { - fn from(value: Error) -> Self { - MigrateError::Wrapped(value) - } -} - -impl From> for MigrateError { - fn from(value: abscissa_core::error::Context) -> Self { - MigrateError::Wrapped(value.into()) - } -} - -impl From for MigrateError { - fn from(value: std::io::Error) -> Self { - MigrateError::Io(value) - } -} - -impl From for MigrateError { - fn from(value: FetchServiceError) -> Self { - MigrateError::Fetch(Box::new(value)) - } -} - -impl From for MigrateError { - fn from(value: DerivationError) -> Self { - MigrateError::KeyDerivation(value) - } -} - -impl From for MigrateError { - fn from(value: PathParseError) -> Self { - MigrateError::HdPath(value) - } -} - -impl From for MigrateError { - fn from(value: secp256k1::Error) -> Self { - MigrateError::KeyError(value) - } -} - impl MigrateZcashdWalletCmd { fn dump_wallet(path: &Path, allow_warnings: bool) -> Result { let db_dump = BDBDump::from_file(path).map_err(|e| MigrateError::Zewif { @@ -677,3 +507,173 @@ impl Runnable for MigrateZcashdWalletCmd { } } } + +#[derive(Debug)] +pub(crate) enum ZewifError { + BdbDump, + ZcashdDump, +} + +#[derive(Debug)] +#[allow(dead_code)] +pub(crate) enum MigrateError { + Wrapped(Error), + Zewif { + error_type: ZewifError, + wallet_path: PathBuf, + error: anyhow::Error, + }, + SeedNotAvailable, + MnemonicInvalid(bip0039::Error), + KeyError(secp256k1::Error), + NetworkMismatch { + wallet_network: zewif::Network, + db_network: NetworkType, + }, + NetworkNotSupported(NetworkType), + Database(SqliteClientError), + Tree(ShardTreeError), + Io(std::io::Error), + Fetch(Box), + KeyDerivation(DerivationError), + HdPath(PathParseError), + AccountIdInvalid(u32), +} + +impl From for Error { + fn from(value: MigrateError) -> Self { + match value { + MigrateError::Wrapped(e) => e, + MigrateError::Zewif { + error_type, + wallet_path, + error, + } => Error::from(match error_type { + ZewifError::BdbDump => ErrorKind::Generic.context(fl!( + "err-migrate-wallet-bdb-parse", + path = wallet_path.to_str(), + err = error.to_string() + )), + ZewifError::ZcashdDump => ErrorKind::Generic.context(fl!( + "err-migrate-wallet-db-dump", + path = wallet_path.to_str(), + err = error.to_string() + )), + }), + MigrateError::SeedNotAvailable => { + Error::from(ErrorKind::Generic.context(fl!("err-migrate-wallet-seed-absent"))) + } + MigrateError::MnemonicInvalid(error) => Error::from(ErrorKind::Generic.context(fl!( + "err-migrate-wallet-invalid-mnemonic", + err = error.to_string() + ))), + MigrateError::KeyError(error) => Error::from(ErrorKind::Generic.context(fl!( + "err-migrate-wallet-key-decoding", + err = error.to_string() + ))), + MigrateError::NetworkMismatch { + wallet_network, + db_network, + } => Error::from(ErrorKind::Generic.context(fl!( + "err-migrate-wallet-network-mismatch", + wallet_network = String::from(wallet_network), + zallet_network = match db_network { + NetworkType::Main => "main", + NetworkType::Test => "test", + NetworkType::Regtest => "regtest", + } + ))), + MigrateError::NetworkNotSupported(_) => { + Error::from(ErrorKind::Generic.context(fl!("err-migrate-wallet-regtest"))) + } + MigrateError::Database(sqlite_client_error) => { + Error::from(ErrorKind::Generic.context(fl!( + "err-migrate-wallet-storage", + err = sqlite_client_error.to_string() + ))) + } + MigrateError::Tree(e) => Error::from( + ErrorKind::Generic + .context(fl!("err-migrate-wallet-data-parse", err = e.to_string())), + ), + MigrateError::Io(e) => Error::from( + ErrorKind::Generic + .context(fl!("err-migrate-wallet-data-parse", err = e.to_string())), + ), + MigrateError::Fetch(e) => Error::from( + ErrorKind::Generic.context(fl!("err-migrate-wallet-tx-fetch", err = e.to_string())), + ), + MigrateError::KeyDerivation(e) => Error::from( + ErrorKind::Generic.context(fl!("err-migrate-wallet-key-data", err = e.to_string())), + ), + MigrateError::HdPath(err) => Error::from(ErrorKind::Generic.context(fl!( + "err-migrate-wallet-data-parse", + err = format!("{:?}", err) + ))), + MigrateError::AccountIdInvalid(id) => Error::from(ErrorKind::Generic.context(fl!( + "err-migrate-wallet-invalid-account-id", + account_id = id + ))), + } + } +} + +impl From> for MigrateError { + fn from(e: ShardTreeError) -> Self { + Self::Tree(e) + } +} + +impl From for MigrateError { + fn from(e: SqliteClientError) -> Self { + Self::Database(e) + } +} + +impl From for MigrateError { + fn from(value: bip0039::Error) -> Self { + Self::MnemonicInvalid(value) + } +} + +impl From for MigrateError { + fn from(value: Error) -> Self { + MigrateError::Wrapped(value) + } +} + +impl From> for MigrateError { + fn from(value: abscissa_core::error::Context) -> Self { + MigrateError::Wrapped(value.into()) + } +} + +impl From for MigrateError { + fn from(value: std::io::Error) -> Self { + MigrateError::Io(value) + } +} + +impl From for MigrateError { + fn from(value: FetchServiceError) -> Self { + MigrateError::Fetch(Box::new(value)) + } +} + +impl From for MigrateError { + fn from(value: DerivationError) -> Self { + MigrateError::KeyDerivation(value) + } +} + +impl From for MigrateError { + fn from(value: PathParseError) -> Self { + MigrateError::HdPath(value) + } +} + +impl From for MigrateError { + fn from(value: secp256k1::Error) -> Self { + MigrateError::KeyError(value) + } +} From d348cc9f1ad4663e53cbb8f3bd5da4d7af1b27b5 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 12 Sep 2025 13:35:22 -0600 Subject: [PATCH 08/15] Apply suggestions from code review Co-authored-by: Jack Grigg --- zallet/src/commands/migrate_zcashd_wallet.rs | 121 ++++++++----------- zallet/src/components/database/tests.rs | 2 + zallet/src/components/keystore.rs | 2 +- 3 files changed, 56 insertions(+), 69 deletions(-) diff --git a/zallet/src/commands/migrate_zcashd_wallet.rs b/zallet/src/commands/migrate_zcashd_wallet.rs index b2eaf72d..71fdc733 100644 --- a/zallet/src/commands/migrate_zcashd_wallet.rs +++ b/zallet/src/commands/migrate_zcashd_wallet.rs @@ -3,7 +3,7 @@ use std::{ path::{Path, PathBuf}, }; -use abscissa_core::{Runnable, Shutdown}; +use abscissa_core::Runnable; use bip0039::{English, Mnemonic}; use secp256k1::PublicKey; @@ -34,9 +34,54 @@ use crate::{ rosetta::to_chainstate, }; -use super::migrate_zcash_conf; +use super::{AsyncRunnable, migrate_zcash_conf}; +/// The ZIP 32 account identifier of the zcashd account used for maintaining legacy `getnewaddress` +/// and `z_getnewaddress` semantics after the zcashd v4.7.0 upgrade to support using +/// mnemonic-sourced HD derivation for all addresses in the wallet. pub const ZCASHD_LEGACY_ACCOUNT: AccountId = AccountId::const_from_u32(0x7FFFFFFF); +/// A source string to identify an account as being derived from the randomly generated binary HD +/// seed used for Sapling key generation prior to the zcashd v4.7.0 upgrade. +pub const ZCASHD_LEGACY_SOURCE: &str = "zcashd_legacy"; +/// A source string to identify an account as being derived from the mnemonic HD seed used for +/// key derivation after the zcashd v4.7.0 upgrade. +pub const ZCASHD_MNEMONIC_SOURCE: &str = "zcashd_mnemonic"; + +impl AsyncRunnable for MigrateZcashdWalletCmd { + async fn run(&self) -> Result<(), Error> { + let config = APP.config(); + + // Start monitoring the chain. + let (chain_view, _chain_indexer_task_handle) = ChainView::new(&config).await?; + let db = Database::open(&config).await?; + let keystore = KeyStore::new(&config, db.clone())?; + + let wallet_path = if self.path.is_relative() { + if let Some(datadir) = self.zcashd_datadir.as_ref() { + datadir.join(&self.path) + } else { + migrate_zcash_conf::zcashd_default_data_dir() + .ok_or(ErrorKind::Generic)? + .join(&self.path) + } + } else { + self.path.to_path_buf() + }; + + let wallet = Self::dump_wallet(&wallet_path, self.allow_warnings)?; + + Self::migrate_zcashd_wallet( + db, + keystore, + chain_view, + wallet, + self.buffer_wallet_transactions, + ) + .await?; + + Ok(()) + } +} impl MigrateZcashdWalletCmd { fn dump_wallet(path: &Path, allow_warnings: bool) -> Result { @@ -219,19 +264,7 @@ impl MigrateZcashdWalletCmd { }; let legacy_transparent_account_uuid = if let Some((seed, _)) = mnemonic_seed_data.as_ref() { - // Always create the zero-indexed unified account - db_data.import_account_hd( - &format!( - "zcashd imported unified account {}", - u32::from(AccountId::ZERO), - ), - seed, - AccountId::ZERO, - &wallet_birthday, - Some("zcashd_mnemonic"), - )?; - - // If there are any legacy transparent keys, also create the legacy account. + // If there are any legacy transparent keys, create the legacy account. if !wallet.keys().is_empty() { let (account, _) = db_data.import_account_hd( &format!( @@ -241,7 +274,7 @@ impl MigrateZcashdWalletCmd { seed, ZCASHD_LEGACY_ACCOUNT, &wallet_birthday, - Some("zcashd_mnemonic"), + Some(ZCASHD_MNEMONIC_SOURCE), )?; Some(account.id()) @@ -290,7 +323,7 @@ impl MigrateZcashdWalletCmd { &seed, ZCASHD_LEGACY_ACCOUNT, &wallet_birthday, - Some("zcashd_mnemonic"), + Some(ZCASHD_MNEMONIC_SOURCE), )?; Some(account.id()) @@ -317,8 +350,6 @@ impl MigrateZcashdWalletCmd { let zip32_account_id = AccountId::try_from(account.zip32_account_id()) .map_err(|_| MigrateError::AccountIdInvalid(account.zip32_account_id()))?; - // FIXME: this `if` is a workaround for the lack of ability to query by seed - // fingerprint & zip32 account ID. if db_data .get_derived_account(&Zip32Derivation::new(*seed_fp, zip32_account_id, None))? .is_none() @@ -331,7 +362,7 @@ impl MigrateZcashdWalletCmd { seed, zip32_account_id, &wallet_birthday, - Some("zcashd_mnemonic"), + Some(ZCASHD_MNEMONIC_SOURCE), )?; } } @@ -384,8 +415,6 @@ impl MigrateZcashdWalletCmd { .await?; } - // FIXME: this `if` is a workaround for the lack of ability to query by seed fingerprint - // & zip32 account ID. let account_exists = match key_seed_fp.as_ref() { Some(fp) => db_data .get_derived_account(&Zip32Derivation::new( @@ -403,7 +432,7 @@ impl MigrateZcashdWalletCmd { &ufvk, &wallet_birthday, AccountPurpose::Spending { derivation }, - Some("zcashd_legacy"), + Some(ZCASHD_LEGACY_SOURCE), )?; } } @@ -456,55 +485,11 @@ impl MigrateZcashdWalletCmd { Ok(()) } - - async fn start(&self) -> Result<(), Error> { - let config = APP.config(); - - // Start monitoring the chain. - let (chain_view, _chain_indexer_task_handle) = ChainView::new(&config).await?; - let db = Database::open(&config).await?; - let keystore = KeyStore::new(&config, db.clone())?; - - let wallet_path = if self.path.is_relative() { - if let Some(datadir) = self.zcashd_datadir.as_ref() { - datadir.join(&self.path) - } else { - migrate_zcash_conf::zcashd_default_data_dir() - .ok_or(ErrorKind::Generic)? - .join(&self.path) - } - } else { - self.path.to_path_buf() - }; - - let wallet = Self::dump_wallet(&wallet_path, self.allow_warnings)?; - - Self::migrate_zcashd_wallet( - db, - keystore, - chain_view, - wallet, - self.buffer_wallet_transactions, - ) - .await?; - - Ok(()) - } } impl Runnable for MigrateZcashdWalletCmd { fn run(&self) { - match abscissa_tokio::run(&APP, self.start()) { - Ok(Ok(())) => (), - Ok(Err(e)) => { - eprintln!("{}", e); - APP.shutdown_with_exitcode(Shutdown::Forced, 1); - } - Err(e) => { - eprintln!("{}", e); - APP.shutdown_with_exitcode(Shutdown::Forced, 1); - } - } + self.run_on_runtime(); } } diff --git a/zallet/src/components/database/tests.rs b/zallet/src/components/database/tests.rs index f2460584..216e75ef 100644 --- a/zallet/src/components/database/tests.rs +++ b/zallet/src/components/database/tests.rs @@ -57,7 +57,9 @@ fn verify_schema() { keystore::db::TABLE_LEGACY_SEEDS, #[cfg(zallet_build = "wallet")] keystore::db::TABLE_MNEMONICS, + #[cfg(zallet_build = "wallet")] keystore::db::TABLE_STANDALONE_SAPLING_KEYS, + #[cfg(zallet_build = "wallet")] keystore::db::TABLE_STANDALONE_TRANSPARENT_KEYS, ], ); diff --git a/zallet/src/components/keystore.rs b/zallet/src/components/keystore.rs index 1b4fd2b6..576a0f17 100644 --- a/zallet/src/components/keystore.rs +++ b/zallet/src/components/keystore.rs @@ -786,7 +786,7 @@ fn decrypt_standalone_transparent_privkey( let res = decryptor .decrypt(identities.iter().map(|i| i.as_ref() as _)) .map_err(|e| ErrorKind::Generic.context(e))? - .read(&mut buf); + .read_to_end(&mut buf); // We intentionally do not use `?` on the decryption expression because doing so in // the case of a partial failure could result in part of the secret data being read From c013767be6cbbcfe2e1dc27b0758fdf5b4e8cdbf Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 12 Sep 2025 15:11:04 -0600 Subject: [PATCH 09/15] rpc (z_sendmany): Build transaction proposal synchronously. The primary purpose of the asynchronous operation of `z_sendmany` is to avoid long proving times in a synchronous call. We don't have any reason to believe that proposal construction should be a long-running process, and it's better to report errors to the user synchronously rather than async, so this change moves all of proposal construction into the synchronous phase of the RPC call. --- .../json_rpc/methods/z_send_many.rs | 98 +++++++++---------- 1 file changed, 47 insertions(+), 51 deletions(-) diff --git a/zallet/src/components/json_rpc/methods/z_send_many.rs b/zallet/src/components/json_rpc/methods/z_send_many.rs index 33262e59..79003ce2 100644 --- a/zallet/src/components/json_rpc/methods/z_send_many.rs +++ b/zallet/src/components/json_rpc/methods/z_send_many.rs @@ -11,6 +11,7 @@ use serde_json::json; use zaino_state::FetchServiceSubscriber; use zcash_address::{ZcashAddress, unified}; use zcash_client_backend::data_api::wallet::SpendingKeys; +use zcash_client_backend::proposal::Proposal; use zcash_client_backend::{ data_api::{ Account, @@ -23,7 +24,7 @@ use zcash_client_backend::{ wallet::OvkPolicy, zip321::{Payment, TransactionRequest}, }; -use zcash_client_sqlite::AccountUuid; +use zcash_client_sqlite::ReceivedNoteId; use zcash_keys::{address::Address, keys::UnifiedSpendingKey}; use zcash_proofs::prover::LocalTxProver; use zcash_protocol::{ @@ -80,7 +81,7 @@ pub(super) const PARAM_PRIVACY_POLICY_DESC: &str = #[allow(clippy::too_many_arguments)] pub(crate) async fn call( - wallet: DbHandle, + mut wallet: DbHandle, keystore: KeyStore, chain: FetchServiceSubscriber, fromaddress: String, @@ -187,45 +188,6 @@ pub(crate) async fn call( } }; - Ok(( - Some(ContextInfo::new( - "z_sendmany", - json!({ - "fromaddress": fromaddress, - "amounts": amounts, - "minconf": minconf - }), - )), - run( - wallet, - keystore, - chain, - account, - request, - confirmations_policy, - privacy_policy, - ), - )) -} - -/// Construct and send the transaction, returning the resulting txid. -/// Errors in transaction construction will throw. -/// -/// Notes: -/// 1. #1159 Currently there is no limit set on the number of elements, which could -/// make the tx too large. -/// 2. #1360 Note selection is not optimal. -/// 3. #1277 Spendable notes are not locked, so an operation running in parallel -/// could also try to use them. -async fn run>( - mut wallet: DbHandle, - keystore: KeyStore, - chain: FetchServiceSubscriber, - spend_from_account: A, - request: TransactionRequest, - confirmations_policy: ConfirmationsPolicy, - privacy_policy: PrivacyPolicy, -) -> RpcResult { let params = *wallet.params(); // TODO: Fetch the real maximums within the account so we can detect correctly. @@ -296,6 +258,7 @@ async fn run>( DustOutputPolicy::default(), APP.config().note_management.split_policy(), ); + // TODO: Once `zcash_client_backend` supports spending transparent coins arbitrarily, // consider using the privacy policy here to avoid selecting incompatible funds. This // would match what `zcashd` did more closely (though we might instead decide to let @@ -306,7 +269,7 @@ async fn run>( let proposal = propose_transfer::<_, _, _, _, Infallible>( wallet.as_mut(), ¶ms, - spend_from_account.id(), + account.id(), &input_selector, &change_strategy, request, @@ -360,15 +323,11 @@ async fn run>( } } - let prover = LocalTxProver::bundled(); + let derivation = account.source().key_derivation().ok_or_else(|| { + LegacyCode::InvalidAddressOrKey + .with_static("Invalid from address, no payment source found for address.") + })?; - let derivation = spend_from_account - .source() - .key_derivation() - .ok_or_else(|| { - LegacyCode::InvalidAddressOrKey - .with_static("Invalid from address, no payment source found for address.") - })?; // Fetch spending key last, to avoid a keystore decryption if unnecessary. let seed = keystore .decrypt_seed(derivation.seed_fingerprint()) @@ -406,13 +365,50 @@ async fn run>( } } + // TODO: verify that the proposal satisfies the requested privacy policy + + Ok(( + Some(ContextInfo::new( + "z_sendmany", + json!({ + "fromaddress": fromaddress, + "amounts": amounts, + "minconf": minconf + }), + )), + run( + wallet, + chain, + proposal, + SpendingKeys::new(usk, standalone_keys), + ), + )) +} + +/// Construct and send the transaction, returning the resulting txid. +/// Errors in transaction construction will throw. +/// +/// Notes: +/// 1. #1159 Currently there is no limit set on the number of elements, which could +/// make the tx too large. +/// 2. #1360 Note selection is not optimal. +/// 3. #1277 Spendable notes are not locked, so an operation running in parallel +/// could also try to use them. +async fn run( + mut wallet: DbHandle, + chain: FetchServiceSubscriber, + proposal: Proposal, + spending_keys: SpendingKeys, +) -> RpcResult { + let prover = LocalTxProver::bundled(); let (wallet, txids) = crate::spawn_blocking!("z_sendmany prover", move || { + let params = *wallet.params(); create_proposed_transactions::<_, _, Infallible, _, Infallible, _>( wallet.as_mut(), ¶ms, &prover, &prover, - &SpendingKeys::new(usk, standalone_keys), + &spending_keys, OvkPolicy::Sender, &proposal, ) From 0db54354d9cc4287adc1be3af7c454fd2b3bf6e3 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 12 Sep 2025 16:42:51 -0600 Subject: [PATCH 10/15] Use `zcash` forks of `zewif` crates & fix license exemptions. --- Cargo.lock | 78 ++++++++++++++++++++---------------------------------- Cargo.toml | 4 +-- deny.toml | 16 ++++++++++- 3 files changed, 45 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af276f36..f26f4df0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -464,9 +464,9 @@ dependencies = [ [[package]] name = "bc-components" -version = "0.21.2" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec457b709c34405b86c5f63f5d1bb706715383f6e1082de2a1e02f88b27fd40" +checksum = "6077faa2e784ba4d60340f87d210c9c88bffd61384e33fb212895f0a912eb775" dependencies = [ "anyhow", "bc-crypto", @@ -513,9 +513,9 @@ dependencies = [ [[package]] name = "bc-envelope" -version = "0.28.1" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ff0ae3d610f7016ca7d6702f06c0dbfb63780ecb59b81497cfc7f4b134327b" +checksum = "8a405ac045fd75c54b31fca2df85c38b73b0824bce893faa77154dbefb47dbad" dependencies = [ "anyhow", "bc-components", @@ -559,9 +559,9 @@ dependencies = [ [[package]] name = "bc-tags" -version = "0.2.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbfd44331570ae0d61d92f7d62408b215f6833da9c8d464eb09706a079363ce4" +checksum = "f849b1705d71e8e1f744f47e66fdff09191b0c4bc06df5477855f155294407b5" dependencies = [ "dcbor", "paste", @@ -569,9 +569,9 @@ dependencies = [ [[package]] name = "bc-ur" -version = "0.9.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c65e33cfacc82d2e6ef819ac08e73a429de526a4b019135fddbd2e14477ec2b2" +checksum = "c66cbdfcce5175fc68c809990927653535d9b7c28efc9bc301583867a2c2ffde" dependencies = [ "dcbor", "thiserror 1.0.69", @@ -1288,9 +1288,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -1446,9 +1446,9 @@ dependencies = [ [[package]] name = "dcbor" -version = "0.19.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402f083d3c2daef249d4c06ef827c8b743ab88536f73dae52201f4de5abb0354" +checksum = "c9ee71342cca725c77c9fc7cb2e043ab41b130f36d53826b31cc6053a2899bf1" dependencies = [ "anyhow", "chrono", @@ -3168,9 +3168,9 @@ dependencies = [ [[package]] name = "known-values" -version = "0.4.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c3c5085ed0cb90e914af0906adca5fd87dd4af28ab6dc254edca97eee23a73" +checksum = "80d0b88b8b600c157b67d764cc28b56d90dc47b1a18ea8844d99e9773b107cba" dependencies = [ "bc-components", "dcbor", @@ -3394,11 +3394,11 @@ dependencies = [ [[package]] name = "matchers" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata 0.1.10", + "regex-automata", ] [[package]] @@ -3560,12 +3560,11 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ - "overload", - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -3816,12 +3815,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "owo-colors" version = "3.5.0" @@ -4671,17 +4664,8 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] @@ -4692,15 +4676,9 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.5" @@ -6279,14 +6257,14 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex", + "regex-automata", "sharded-slab", "smallvec", "thread_local", @@ -8131,7 +8109,7 @@ dependencies = [ [[package]] name = "zewif" version = "0.1.0" -source = "git+https://github.com/BlockchainCommons/zewif.git?rev=8f005a6f5a189815dc08821f99cfe7e8af4e7f62#8f005a6f5a189815dc08821f99cfe7e8af4e7f62" +source = "git+https://github.com/zcash/zewif.git?rev=f84f80612813ba00a0a8a9a5f060bd217fa981cc#f84f80612813ba00a0a8a9a5f060bd217fa981cc" dependencies = [ "anyhow", "bc-components", @@ -8145,7 +8123,7 @@ dependencies = [ [[package]] name = "zewif-zcashd" version = "0.1.0" -source = "git+https://github.com/BlockchainCommons/zewif-zcashd.git?rev=74cd5bc2a66a8ce63534c2c1c9ff2dedb2f9d9fd#74cd5bc2a66a8ce63534c2c1c9ff2dedb2f9d9fd" +source = "git+https://github.com/zcash/zewif-zcashd.git?rev=02a98d6236e24819904e084180da9ba0f5c9b5d0#02a98d6236e24819904e084180da9ba0f5c9b5d0" dependencies = [ "anyhow", "bitflags 2.9.0", diff --git a/Cargo.toml b/Cargo.toml index 83aa1b7d..bc86f03a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,8 +142,8 @@ zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "1 zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "10caf455e3f52744b5392af226a408b05721f70f" } zcash_protocol = { git = "https://github.com/zcash/librustzcash.git", rev = "10caf455e3f52744b5392af226a408b05721f70f" } -zewif = { git = "https://github.com/BlockchainCommons/zewif.git", rev = "8f005a6f5a189815dc08821f99cfe7e8af4e7f62" } -zewif-zcashd = { git = "https://github.com/BlockchainCommons/zewif-zcashd.git", rev = "74cd5bc2a66a8ce63534c2c1c9ff2dedb2f9d9fd" } +zewif = { git = "https://github.com/zcash/zewif.git", rev = "f84f80612813ba00a0a8a9a5f060bd217fa981cc" } +zewif-zcashd = { git = "https://github.com/zcash/zewif-zcashd.git", rev = "02a98d6236e24819904e084180da9ba0f5c9b5d0" } zaino-fetch = { git = "https://github.com/Electric-Coin-Company/zaino.git", rev = "1004733f3303b1c48b6df6db610c54401554683c" } zaino-proto = { git = "https://github.com/Electric-Coin-Company/zaino.git", rev = "1004733f3303b1c48b6df6db610c54401554683c" } diff --git a/deny.toml b/deny.toml index ea26c15d..6b32d6a0 100644 --- a/deny.toml +++ b/deny.toml @@ -20,11 +20,22 @@ allow = [ ] exceptions = [ { name = "arrayref", allow = ["BSD-2-Clause"] }, + { name = "bc-components", allow = ["BSD-2-Clause-Patent"] }, + { name = "bc-crypto", allow = ["BSD-2-Clause-Patent"] }, + { name = "bc-envelope", allow = ["BSD-2-Clause-Patent"] }, + { name = "bc-rand", allow = ["BSD-2-Clause-Patent"] }, + { name = "bc-shamir", allow = ["BSD-2-Clause-Patent"] }, + { name = "bc-tags", allow = ["BSD-2-Clause-Patent"] }, + { name = "bc-ur", allow = ["BSD-2-Clause-Patent"] }, { name = "bindgen", allow = ["BSD-3-Clause"] }, + { name = "bitcoin-private", allow = ["CC0-1.0"] }, + { name = "bitcoin_hashes", allow = ["CC0-1.0"] }, { name = "const_format", allow = ["Zlib"] }, { name = "const_format_proc_macros", allow = ["Zlib"] }, { name = "curve25519-dalek", allow = ["BSD-3-Clause"] }, { name = "dcbor", allow = ["BSD-2-Clause-Patent"] }, + { name = "doctest-file", allow = ["0BSD"] }, + { name = "ed25519-dalek", allow = ["BSD-3-Clause"] }, { name = "human_bytes", allow = ["BSD-2-Clause"] }, { name = "icu_collections", allow = ["Unicode-3.0"] }, { name = "icu_locid", allow = ["Unicode-3.0"] }, @@ -36,15 +47,18 @@ exceptions = [ { name = "icu_properties_data", allow = ["Unicode-3.0"] }, { name = "icu_provider", allow = ["Unicode-3.0"] }, { name = "icu_provider_macros", allow = ["Unicode-3.0"] }, + { name = "known-values", allow = ["BSD-2-Clause-Patent"] }, { name = "libloading", allow = ["ISC"] }, { name = "litemap", allow = ["Unicode-3.0"] }, { name = "matchit", allow = ["BSD-3-Clause"] }, - # Copyleft license. Temporary exception until Zebra stops depending on `dirs`. + { name = "minicbor", allow = ["BlueOak-1.0.0"] }, { name = "option-ext", allow = ["MPL-2.0"] }, + { name = "recvmsg", allow = ["0BSD"] }, { name = "ring", allow = ["ISC"] }, { name = "rustls-webpki", allow = ["ISC"] }, { name = "secp256k1", allow = ["CC0-1.0"] }, { name = "secp256k1-sys", allow = ["CC0-1.0"] }, + { name = "sskr", allow = ["BSD-2-Clause-Patent"] }, { name = "subtle", allow = ["BSD-3-Clause"] }, { name = "tinystr", allow = ["Unicode-3.0"] }, { name = "unicode-ident", allow = ["Unicode-3.0"] }, From cb94bf3a9ee8a1f69d784f578924aa4ca3fad2dc Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 12 Sep 2025 17:09:57 -0600 Subject: [PATCH 11/15] Put zcashd import and transparent key import functionality behind feature flags. --- zallet/Cargo.toml | 24 +++++-- zallet/src/cli.rs | 4 ++ zallet/src/commands.rs | 2 + zallet/src/components/database/connection.rs | 1 + .../json_rpc/methods/list_addresses.rs | 1 + .../json_rpc/methods/z_send_many.rs | 46 +++++++----- zallet/src/components/keystore.rs | 72 +++++++++++-------- zallet/src/lib.rs | 4 +- 8 files changed, 99 insertions(+), 55 deletions(-) diff --git a/zallet/Cargo.toml b/zallet/Cargo.toml index 25ecadbb..1bf459bd 100644 --- a/zallet/Cargo.toml +++ b/zallet/Cargo.toml @@ -132,14 +132,10 @@ zcash_client_backend = { workspace = true, features = [ "orchard", "sync", "transparent-inputs", - "transparent-key-import", - "zcashd-compat", ] } zcash_client_sqlite = { workspace = true, features = [ "orchard", "transparent-inputs", - "transparent-key-import", - "zcashd-compat", ] } zcash_keys = { workspace = true, features = ["zcashd-compat", "unstable"] } zcash_note_encryption.workspace = true @@ -150,8 +146,8 @@ zebra-chain.workspace = true zebra-rpc.workspace = true zebra-state.workspace = true zip32.workspace = true -zewif.workspace = true -zewif-zcashd.workspace = true +zewif = { workspace = true, optional = true } +zewif-zcashd = { workspace = true, optional = true } anyhow.workspace = true console-subscriber = { workspace = true, optional = true } @@ -191,6 +187,22 @@ rpc-cli = ["jsonrpsee/async-client", "dep:jsonrpsee-http-client"] ## https://github.com/tokio-rs/console/blob/main/console-subscriber/README.md#enabling-tokio-instrumentation tokio-console = ["dep:console-subscriber", "tokio/tracing"] +## Allows `zallet` to provide transparent key import functionality, and to work +## with a wallet imported via the `migrate_zcashd_wallet` command. +transparent-key-import = [ + "zcash_client_backend/transparent-key-import", + "zcash_client_sqlite/transparent-key-import", +] + +## Allows `zallet` to import zcashd wallets. +zcashd-import = [ + "transparent-key-import", + "dep:zewif", + "dep:zewif-zcashd", + "zcash_client_backend/zcashd-compat", + "zcash_client_sqlite/zcashd-compat", +] + [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(outside_buildscript)', diff --git a/zallet/src/cli.rs b/zallet/src/cli.rs index 1e21e0e2..5654f289 100644 --- a/zallet/src/cli.rs +++ b/zallet/src/cli.rs @@ -52,10 +52,12 @@ pub(crate) enum ZalletCmd { /// Generate a `zallet.toml` config from an existing `zcash.conf` file. #[cfg(zallet_build = "wallet")] + #[cfg(feature = "zcashd-import")] MigrateZcashConf(MigrateZcashConfCmd), /// Add the keys and transactions of a zcashd wallet.dat file to the wallet database. #[cfg(zallet_build = "wallet")] + #[cfg(feature = "zcashd-import")] MigrateZcashdWallet(MigrateZcashdWalletCmd), /// Initialize wallet encryption. @@ -106,6 +108,7 @@ pub(crate) struct ExampleConfigCmd { /// `migrate-zcash-conf` subcommand #[cfg(zallet_build = "wallet")] +#[cfg(feature = "zcashd-import")] #[derive(Debug, Parser)] #[cfg_attr(outside_buildscript, derive(Command))] pub(crate) struct MigrateZcashConfCmd { @@ -141,6 +144,7 @@ pub(crate) struct MigrateZcashConfCmd { /// `migrate-zcashd-wallet` subcommand #[cfg(zallet_build = "wallet")] +#[cfg(feature = "zcashd-import")] #[derive(Debug, Parser)] #[cfg_attr(outside_buildscript, derive(Command))] pub(crate) struct MigrateZcashdWalletCmd { diff --git a/zallet/src/commands.rs b/zallet/src/commands.rs index 5136e435..c49362f3 100644 --- a/zallet/src/commands.rs +++ b/zallet/src/commands.rs @@ -31,8 +31,10 @@ mod import_mnemonic; #[cfg(zallet_build = "wallet")] mod init_wallet_encryption; #[cfg(zallet_build = "wallet")] +#[cfg(feature = "zcashd-import")] mod migrate_zcash_conf; #[cfg(zallet_build = "wallet")] +#[cfg(feature = "zcashd-import")] mod migrate_zcashd_wallet; #[cfg(feature = "rpc-cli")] diff --git a/zallet/src/components/database/connection.rs b/zallet/src/components/database/connection.rs index 56de93f0..6c63e8a2 100644 --- a/zallet/src/components/database/connection.rs +++ b/zallet/src/components/database/connection.rs @@ -480,6 +480,7 @@ impl WalletWrite for DbConnection { }) } + #[cfg(feature = "zcashd-import")] fn import_standalone_transparent_pubkey( &mut self, account: Self::AccountId, diff --git a/zallet/src/components/json_rpc/methods/list_addresses.rs b/zallet/src/components/json_rpc/methods/list_addresses.rs index 8249c39d..d0f2bc20 100644 --- a/zallet/src/components/json_rpc/methods/list_addresses.rs +++ b/zallet/src/components/json_rpc/methods/list_addresses.rs @@ -187,6 +187,7 @@ pub(crate) fn call(wallet: &DbConnection) -> Response { diversifier_index, .. } => diversifier_index.into(), + #[cfg(feature = "transparent-key-import")] zcash_client_backend::data_api::AddressSource::Standalone => { error!( "Unified address {} lacks HD derivation information.", diff --git a/zallet/src/components/json_rpc/methods/z_send_many.rs b/zallet/src/components/json_rpc/methods/z_send_many.rs index 79003ce2..988f1114 100644 --- a/zallet/src/components/json_rpc/methods/z_send_many.rs +++ b/zallet/src/components/json_rpc/methods/z_send_many.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::convert::Infallible; use std::num::NonZeroU32; @@ -346,24 +346,30 @@ pub(crate) async fn call( ) .map_err(|e| LegacyCode::InvalidAddressOrKey.with_message(e.to_string()))?; - let mut standalone_keys = HashMap::new(); - for step in proposal.steps() { - for input in step.transparent_inputs() { - if let Some(address) = input.txout().script_pubkey().address() { - let secret_key = keystore - .decrypt_standalone_transparent_key(&address) - .await - .map_err(|e| match e.kind() { - // TODO: Improve internal error types. - crate::error::ErrorKind::Generic if e.to_string() == "Wallet is locked" => { - LegacyCode::WalletUnlockNeeded.with_message(e.to_string()) - } - _ => LegacyCode::Database.with_message(e.to_string()), - })?; - standalone_keys.insert(address, secret_key); + #[cfg(feature = "transparent-key-import")] + let standalone_keys = { + let mut keys = std::collections::HashMap::new(); + for step in proposal.steps() { + for input in step.transparent_inputs() { + if let Some(address) = input.txout().script_pubkey().address() { + let secret_key = keystore + .decrypt_standalone_transparent_key(&address) + .await + .map_err(|e| match e.kind() { + // TODO: Improve internal error types. + crate::error::ErrorKind::Generic + if e.to_string() == "Wallet is locked" => + { + LegacyCode::WalletUnlockNeeded.with_message(e.to_string()) + } + _ => LegacyCode::Database.with_message(e.to_string()), + })?; + keys.insert(address, secret_key); + } } } - } + keys + }; // TODO: verify that the proposal satisfies the requested privacy policy @@ -380,7 +386,11 @@ pub(crate) async fn call( wallet, chain, proposal, - SpendingKeys::new(usk, standalone_keys), + SpendingKeys::new( + usk, + #[cfg(feature = "zcashd-import")] + standalone_keys, + ), ), )) } diff --git a/zallet/src/components/keystore.rs b/zallet/src/components/keystore.rs index 576a0f17..2fe1cf31 100644 --- a/zallet/src/components/keystore.rs +++ b/zallet/src/components/keystore.rs @@ -117,26 +117,30 @@ use std::time::{Duration, SystemTime}; use bip0039::{English, Mnemonic}; use rusqlite::named_params; -use sapling::zip32::{DiversifiableFullViewingKey, ExtendedSpendingKey}; use secrecy::{ExposeSecret, SecretString, SecretVec, Zeroize}; use tokio::{ sync::{Mutex, RwLock}, task::JoinHandle, time, }; -use transparent::address::TransparentAddress; -use zcash_keys::address::Address; use zip32::fingerprint::SeedFingerprint; use crate::network::Network; use crate::{ config::ZalletConfig, error::{Error, ErrorKind}, - fl, }; use super::database::Database; +#[cfg(feature = "zcashd-import")] +use { + crate::fl, + sapling::zip32::{DiversifiableFullViewingKey, ExtendedSpendingKey}, + transparent::address::TransparentAddress, + zcash_keys::address::Address, +}; + pub(super) mod db; type RelockTask = (SystemTime, JoinHandle<()>); @@ -530,7 +534,7 @@ impl KeyStore { Ok(seed_fp) } - #[allow(dead_code)] + #[cfg(feature = "zcashd-import")] pub(crate) async fn encrypt_and_store_legacy_seed( &self, legacy_seed: &SecretVec, @@ -561,6 +565,7 @@ impl KeyStore { Ok(legacy_seed_fp) } + #[cfg(feature = "zcashd-import")] pub(crate) async fn encrypt_and_store_standalone_sapling_key( &self, sapling_key: &ExtendedSpendingKey, @@ -589,6 +594,7 @@ impl KeyStore { Ok(dfvk) } + #[cfg(feature = "zcashd-import")] pub(crate) async fn encrypt_and_store_standalone_transparent_key( &self, key: &zcash_keys::keys::transparent::Key, @@ -661,6 +667,7 @@ impl KeyStore { Ok(seed) } + #[cfg(feature = "zcashd-import")] pub(crate) async fn decrypt_standalone_transparent_key( &self, address: &TransparentAddress, @@ -711,6 +718,32 @@ fn encrypt_string( Ok(ciphertext) } +fn decrypt_string( + identities: &[Box], + ciphertext: &[u8], +) -> Result { + let decryptor = age::Decryptor::new(ciphertext)?; + + // The plaintext is always shorter than the ciphertext. Over-allocating the initial + // string ensures that no internal re-allocations occur that might leave plaintext + // bytes strewn around the heap. + let mut buf = String::with_capacity(ciphertext.len()); + let res = decryptor + .decrypt(identities.iter().map(|i| i.as_ref() as _))? + .read_to_string(&mut buf); + + // We intentionally do not use `?` on the decryption expression because doing so in + // the case of a partial failure could result in part of the secret data being read + // into `buf`, which would not then be properly zeroized. Instead, we take ownership + // of the buffer in construction of a `SecretString` to ensure that the memory is + // zeroed out when we raise the error on the following line. + let mnemonic = SecretString::new(buf); + res?; + + Ok(mnemonic) +} + +#[cfg(any(feature = "transparent-key-import", feature = "zcashd-import"))] fn encrypt_secret( recipients: &[Box], secret: &SecretVec, @@ -725,6 +758,7 @@ fn encrypt_secret( Ok(ciphertext) } +#[cfg(feature = "zcashd-import")] fn encrypt_legacy_seed_bytes( recipients: &[Box], seed: &SecretVec, @@ -732,6 +766,7 @@ fn encrypt_legacy_seed_bytes( encrypt_secret(recipients, seed) } +#[cfg(feature = "zcashd-import")] fn encrypt_standalone_sapling_key( recipients: &[Box], key: &ExtendedSpendingKey, @@ -740,6 +775,7 @@ fn encrypt_standalone_sapling_key( encrypt_secret(recipients, &secret) } +#[cfg(feature = "transparent-key-import")] fn encrypt_standalone_transparent_privkey( recipients: &[Box], key: &secp256k1::SecretKey, @@ -748,31 +784,7 @@ fn encrypt_standalone_transparent_privkey( encrypt_secret(recipients, &secret) } -fn decrypt_string( - identities: &[Box], - ciphertext: &[u8], -) -> Result { - let decryptor = age::Decryptor::new(ciphertext)?; - - // The plaintext is always shorter than the ciphertext. Over-allocating the initial - // string ensures that no internal re-allocations occur that might leave plaintext - // bytes strewn around the heap. - let mut buf = String::with_capacity(ciphertext.len()); - let res = decryptor - .decrypt(identities.iter().map(|i| i.as_ref() as _))? - .read_to_string(&mut buf); - - // We intentionally do not use `?` on the decryption expression because doing so in - // the case of a partial failure could result in part of the secret data being read - // into `buf`, which would not then be properly zeroized. Instead, we take ownership - // of the buffer in construction of a `SecretString` to ensure that the memory is - // zeroed out when we raise the error on the following line. - let mnemonic = SecretString::new(buf); - res?; - - Ok(mnemonic) -} - +#[cfg(feature = "transparent-key-import")] fn decrypt_standalone_transparent_privkey( identities: &[Box], ciphertext: &[u8], diff --git a/zallet/src/lib.rs b/zallet/src/lib.rs index 9f7a706e..a818b5d1 100644 --- a/zallet/src/lib.rs +++ b/zallet/src/lib.rs @@ -25,9 +25,11 @@ mod error; mod i18n; pub mod network; mod prelude; -mod rosetta; mod task; +#[cfg(feature = "zcashd-import")] +mod rosetta; + // Needed for the `Component` derive to work. use abscissa_core::{Application, Version, component}; From f53923ba5b853454e605f43a15a3007381b2c4b3 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 12 Sep 2025 19:02:56 -0600 Subject: [PATCH 12/15] Apply suggestions from code review Co-authored-by: Jack Grigg --- supply-chain/config.toml | 28 ++++++++--------- supply-chain/imports.lock | 30 ++++++++----------- zallet/Cargo.toml | 2 +- zallet/src/cli.rs | 12 +++----- zallet/src/commands.rs | 6 ++-- .../json_rpc/methods/view_transaction.rs | 4 --- 6 files changed, 34 insertions(+), 48 deletions(-) diff --git a/supply-chain/config.toml b/supply-chain/config.toml index a93b34ed..44f03370 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -229,7 +229,7 @@ version = "1.7.3" criteria = "safe-to-deploy" [[exemptions.bc-components]] -version = "0.21.2" +version = "0.24.0" criteria = "safe-to-deploy" [[exemptions.bc-crypto]] @@ -237,7 +237,7 @@ version = "0.9.0" criteria = "safe-to-deploy" [[exemptions.bc-envelope]] -version = "0.28.1" +version = "0.33.0" criteria = "safe-to-deploy" [[exemptions.bc-rand]] @@ -249,11 +249,11 @@ version = "0.8.0" criteria = "safe-to-deploy" [[exemptions.bc-tags]] -version = "0.2.0" +version = "0.5.0" criteria = "safe-to-deploy" [[exemptions.bc-ur]] -version = "0.9.0" +version = "0.12.0" criteria = "safe-to-deploy" [[exemptions.bech32]] @@ -541,7 +541,7 @@ version = "6.1.0" criteria = "safe-to-deploy" [[exemptions.dcbor]] -version = "0.19.1" +version = "0.22.0" criteria = "safe-to-deploy" [[exemptions.deadpool]] @@ -993,7 +993,7 @@ version = "0.10.0" criteria = "safe-to-deploy" [[exemptions.known-values]] -version = "0.4.0" +version = "0.7.0" criteria = "safe-to-deploy" [[exemptions.lazy-regex]] @@ -1068,6 +1068,10 @@ criteria = "safe-to-deploy" version = "1.11.1+lz4-1.10.0" criteria = "safe-to-deploy" +[[exemptions.matchers]] +version = "0.2.0" +criteria = "safe-to-deploy" + [[exemptions.matchit]] version = "0.7.3" criteria = "safe-to-deploy" @@ -1128,6 +1132,10 @@ criteria = "safe-to-deploy" version = "0.7.0" criteria = "safe-to-deploy" +[[exemptions.nu-ansi-term]] +version = "0.50.1" +criteria = "safe-to-deploy" + [[exemptions.num-bigint]] version = "0.4.6" criteria = "safe-to-deploy" @@ -1436,18 +1444,10 @@ criteria = "safe-to-deploy" version = "1.11.1" criteria = "safe-to-deploy" -[[exemptions.regex-automata]] -version = "0.1.10" -criteria = "safe-to-deploy" - [[exemptions.regex-automata]] version = "0.4.8" criteria = "safe-to-deploy" -[[exemptions.regex-syntax]] -version = "0.6.29" -criteria = "safe-to-deploy" - [[exemptions.reqwest]] version = "0.12.15" criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 68f42bd6..06ddf9c0 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -673,23 +673,6 @@ criteria = "safe-to-deploy" delta = "0.4.22 -> 0.4.27" notes = "Lots of minor updates to macros and such, nothing touching `unsafe`" -[[audits.bytecode-alliance.audits.matchers]] -who = "Pat Hickey " -criteria = "safe-to-deploy" -version = "0.1.0" - -[[audits.bytecode-alliance.audits.nu-ansi-term]] -who = "Pat Hickey " -criteria = "safe-to-deploy" -version = "0.46.0" -notes = "one use of unsafe to call windows specific api to get console handle." - -[[audits.bytecode-alliance.audits.overload]] -who = "Pat Hickey " -criteria = "safe-to-deploy" -version = "0.1.1" -notes = "small crate, only defines macro-rules!, nicely documented as well" - [[audits.bytecode-alliance.audits.percent-encoding]] who = "Alex Crichton " criteria = "safe-to-deploy" @@ -2328,6 +2311,13 @@ delta = "0.1.1 -> 0.2.1" notes = "Very minor changes." aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.crossbeam-channel]] +who = "Jan-Erik Rediger " +criteria = "safe-to-deploy" +delta = "0.5.14 -> 0.5.15" +notes = "Fixes a regression from an earlier version which could lead to a double free" +aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml" + [[audits.mozilla.audits.crunchy]] who = "Erich Gubler " criteria = "safe-to-deploy" @@ -3022,6 +3012,12 @@ criteria = "safe-to-deploy" delta = "0.3.17 -> 0.3.19" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.tracing-subscriber]] +who = "Mark Hammond " +criteria = "safe-to-deploy" +delta = "0.3.19 -> 0.3.20" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.unic-langid]] who = "Zibi Braniecki " criteria = "safe-to-deploy" diff --git a/zallet/Cargo.toml b/zallet/Cargo.toml index 1bf459bd..6d60e43a 100644 --- a/zallet/Cargo.toml +++ b/zallet/Cargo.toml @@ -188,7 +188,7 @@ rpc-cli = ["jsonrpsee/async-client", "dep:jsonrpsee-http-client"] tokio-console = ["dep:console-subscriber", "tokio/tracing"] ## Allows `zallet` to provide transparent key import functionality, and to work -## with a wallet imported via the `migrate_zcashd_wallet` command. +## with a wallet imported via the `migrate-zcashd-wallet` command. transparent-key-import = [ "zcash_client_backend/transparent-key-import", "zcash_client_sqlite/transparent-key-import", diff --git a/zallet/src/cli.rs b/zallet/src/cli.rs index 5654f289..69934f4b 100644 --- a/zallet/src/cli.rs +++ b/zallet/src/cli.rs @@ -51,13 +51,11 @@ pub(crate) enum ZalletCmd { ExampleConfig(ExampleConfigCmd), /// Generate a `zallet.toml` config from an existing `zcash.conf` file. - #[cfg(zallet_build = "wallet")] - #[cfg(feature = "zcashd-import")] + #[cfg(all(zallet_build = "wallet", feature = "zcashd-import"))] MigrateZcashConf(MigrateZcashConfCmd), /// Add the keys and transactions of a zcashd wallet.dat file to the wallet database. - #[cfg(zallet_build = "wallet")] - #[cfg(feature = "zcashd-import")] + #[cfg(all(zallet_build = "wallet", feature = "zcashd-import"))] MigrateZcashdWallet(MigrateZcashdWalletCmd), /// Initialize wallet encryption. @@ -107,8 +105,7 @@ pub(crate) struct ExampleConfigCmd { } /// `migrate-zcash-conf` subcommand -#[cfg(zallet_build = "wallet")] -#[cfg(feature = "zcashd-import")] +#[cfg(all(zallet_build = "wallet", feature = "zcashd-import"))] #[derive(Debug, Parser)] #[cfg_attr(outside_buildscript, derive(Command))] pub(crate) struct MigrateZcashConfCmd { @@ -143,8 +140,7 @@ pub(crate) struct MigrateZcashConfCmd { } /// `migrate-zcashd-wallet` subcommand -#[cfg(zallet_build = "wallet")] -#[cfg(feature = "zcashd-import")] +#[cfg(all(zallet_build = "wallet", feature = "zcashd-import"))] #[derive(Debug, Parser)] #[cfg_attr(outside_buildscript, derive(Command))] pub(crate) struct MigrateZcashdWalletCmd { diff --git a/zallet/src/commands.rs b/zallet/src/commands.rs index c49362f3..54fa917e 100644 --- a/zallet/src/commands.rs +++ b/zallet/src/commands.rs @@ -30,11 +30,9 @@ mod generate_mnemonic; mod import_mnemonic; #[cfg(zallet_build = "wallet")] mod init_wallet_encryption; -#[cfg(zallet_build = "wallet")] -#[cfg(feature = "zcashd-import")] +#[cfg(all(zallet_build = "wallet", feature = "zcashd-import"))] mod migrate_zcash_conf; -#[cfg(zallet_build = "wallet")] -#[cfg(feature = "zcashd-import")] +#[cfg(all(zallet_build = "wallet", feature = "zcashd-import"))] mod migrate_zcashd_wallet; #[cfg(feature = "rpc-cli")] diff --git a/zallet/src/components/json_rpc/methods/view_transaction.rs b/zallet/src/components/json_rpc/methods/view_transaction.rs index 928e9d7d..a4326736 100644 --- a/zallet/src/components/json_rpc/methods/view_transaction.rs +++ b/zallet/src/components/json_rpc/methods/view_transaction.rs @@ -475,10 +475,6 @@ pub(crate) async fn call( }); } - let account_ids = wallet.get_account_ids().map_err(|e| { - LegacyCode::Database - .with_message(format!("Failed to retrieve transparent account IDs: {e}")) - })?; // Transparent outputs for (output, idx) in bundle.vout.iter().zip(0..) { let (account_uuid, address, outgoing, wallet_internal) = From e46fb92964cce7f388579eb9b1f0e0971f30e87c Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sun, 14 Sep 2025 00:42:05 +0000 Subject: [PATCH 13/15] Add required feature flags to tests, and update CI to test with all features --- .github/workflows/ci.yml | 3 +++ zallet/Cargo.toml | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0bf145b..db8a8eba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,7 @@ jobs: run: > cargo test --workspace + --all-features - name: Verify working directory is clean run: git diff --exit-code @@ -83,6 +84,7 @@ jobs: run: > cargo test --workspace + --all-features - name: Verify working directory is clean run: git diff --exit-code @@ -109,6 +111,7 @@ jobs: cargo build --workspace --all-targets + --all-features - name: Verify working directory is clean (excluding lockfile) run: git diff --exit-code ':!Cargo.lock' diff --git a/zallet/Cargo.toml b/zallet/Cargo.toml index 6d60e43a..e5c96fe3 100644 --- a/zallet/Cargo.toml +++ b/zallet/Cargo.toml @@ -72,6 +72,10 @@ assets = [ ["../README.md", "usr/share/doc/zallet/README.md", "644"], ] +[[test]] +name = "cli_tests" +required-features = ["zcashd-import"] + [dependencies] abscissa_core.workspace = true abscissa_tokio.workspace = true From bb3e630907ed8ffcffbe3657d26103578bd9a021 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 15 Sep 2025 16:13:48 -0600 Subject: [PATCH 14/15] Run acceptance tests last to avoid non-determinism `CmdRunner` uses `cargo run` under the hood, which can rebuild the main binary. If this runs before `cli_tests` it can alter the features that the latter test expects to be present. We also fix the acceptance tests to tolerate the stderr output maybe containing a rebuild. Co-authored-by: Jack Grigg --- zallet/Cargo.toml | 4 ++++ zallet/tests/acceptance.rs | 20 +++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/zallet/Cargo.toml b/zallet/Cargo.toml index e5c96fe3..dddf0e61 100644 --- a/zallet/Cargo.toml +++ b/zallet/Cargo.toml @@ -76,6 +76,10 @@ assets = [ name = "cli_tests" required-features = ["zcashd-import"] +[[test]] +name = "zallet_acceptance" +path = "tests/acceptance.rs" + [dependencies] abscissa_core.workspace = true abscissa_tokio.workspace = true diff --git a/zallet/tests/acceptance.rs b/zallet/tests/acceptance.rs index 5ed1b07e..52b6e175 100644 --- a/zallet/tests/acceptance.rs +++ b/zallet/tests/acceptance.rs @@ -18,7 +18,11 @@ use once_cell::sync::Lazy; use tempfile::tempdir; #[cfg(zallet_build = "wallet")] -use {abscissa_core::fs::File, age::secrecy::ExposeSecret, std::io::Write}; +use { + abscissa_core::fs::File, + age::secrecy::ExposeSecret, + std::io::{BufRead, Write}, +}; /// Executes your application binary via `cargo run`. /// @@ -97,8 +101,7 @@ fn setup_new_wallet() { .capture_stderr() .run(); let stderr = cmd.stderr(); - stderr.expect_regex(".*Finished.*"); - stderr.expect_regex(".*Running.*"); + wait_until_running(stderr); stderr.expect_regex(".*Creating empty database.*"); cmd.wait().unwrap().expect_code(0); } @@ -112,8 +115,7 @@ fn setup_new_wallet() { .capture_stderr() .run(); let stderr = cmd.stderr(); - stderr.expect_regex(".*Finished.*"); - stderr.expect_regex(".*Running.*"); + wait_until_running(stderr); stderr.expect_regex(".*Applying latest database migrations.*"); cmd.wait().unwrap().expect_code(0); } @@ -129,3 +131,11 @@ fn setup_new_wallet() { cmd.wait().unwrap().expect_code(1); } } + +#[cfg(zallet_build = "wallet")] +fn wait_until_running(stderr: &mut Stderr) { + let mut buf = String::new(); + while !buf.contains("Running") { + stderr.read_line(&mut buf).unwrap(); + } +} From be4d76f127f44bec491ecab7817ff14a010ce0b5 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 15 Sep 2025 23:17:48 +0000 Subject: [PATCH 15/15] cli: Add progress updates to `zallet migrate-zcashd-wallet` --- zallet/src/commands/migrate_zcashd_wallet.rs | 58 ++++++++++++++++---- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/zallet/src/commands/migrate_zcashd_wallet.rs b/zallet/src/commands/migrate_zcashd_wallet.rs index 71fdc733..46154823 100644 --- a/zallet/src/commands/migrate_zcashd_wallet.rs +++ b/zallet/src/commands/migrate_zcashd_wallet.rs @@ -9,6 +9,7 @@ use bip0039::{English, Mnemonic}; use secp256k1::PublicKey; use secrecy::SecretVec; use shardtree::error::ShardTreeError; +use transparent::address::TransparentAddress; use zaino_proto::proto::service::TxFilter; use zaino_state::{FetchServiceError, LightWalletIndexer}; use zcash_client_backend::data_api::{ @@ -16,9 +17,12 @@ use zcash_client_backend::data_api::{ wallet::decrypt_and_store_transaction, }; use zcash_client_sqlite::error::SqliteClientError; -use zcash_keys::keys::{ - DerivationError, UnifiedFullViewingKey, - zcashd::{PathParseError, ZcashdHdDerivation}, +use zcash_keys::{ + encoding::AddressCodec, + keys::{ + DerivationError, UnifiedFullViewingKey, + zcashd::{PathParseError, ZcashdHdDerivation}, + }, }; use zcash_primitives::transaction::Transaction; use zcash_protocol::consensus::{BlockHeight, BranchId, NetworkType, Parameters}; @@ -85,6 +89,8 @@ impl AsyncRunnable for MigrateZcashdWalletCmd { impl MigrateZcashdWalletCmd { fn dump_wallet(path: &Path, allow_warnings: bool) -> Result { + info!("Parsing zcashd wallet at {}", path.display()); + let db_dump = BDBDump::from_file(path).map_err(|e| MigrateError::Zewif { error_type: ZewifError::BdbDump, wallet_path: path.to_path_buf(), @@ -108,6 +114,8 @@ impl MigrateZcashdWalletCmd { } })?; + info!("Wallet version: {}", zcashd_wallet.client_version()); + Ok(zcashd_wallet) } @@ -233,6 +241,7 @@ impl MigrateZcashdWalletCmd { } } } + info!("Wallet contains {} transactions", tx_heights.len()); // Since zcashd scans in linear order, we can reliably choose the earliest wallet // transaction's mined height as the birthday height, so long as it is in the "stable" @@ -254,6 +263,10 @@ impl MigrateZcashdWalletCmd { chain_tip, ) .await?; + info!( + "Setting the wallet birthday to height {}", + wallet_birthday.height(), + ); let mnemonic_seed_data = match Self::parse_mnemonic(wallet.bip39_mnemonic().mnemonic())? { Some(m) => Some(( @@ -335,6 +348,14 @@ impl MigrateZcashdWalletCmd { // Add unified accounts. The only source of unified accounts in zcashd is derivation from // the mnemonic seed. + if wallet.unified_accounts().account_metadata.is_empty() { + info!("Wallet contains no unified accounts (z_getnewaccount was never used)"); + } else { + info!( + "Importing {} unified accounts (created with z_getnewaccount)", + wallet.unified_accounts().account_metadata.len() + ); + } for (_, account) in wallet.unified_accounts().account_metadata.iter() { // The only way that a unified account could be created in zcashd was // to be derived from the mnemonic seed, so we can safely unwrap here. @@ -372,6 +393,7 @@ impl MigrateZcashdWalletCmd { // * The mnemonic HD seed, under a standard ZIP 32 key path // * The mnemonic HD seed, under the "legacy" account with an additional hardened path element // * Zcashd Sapling spending key import + info!("Importing legacy Sapling keys"); // TODO: Expose how many there are in zewif-zcashd. for (idx, key) in wallet.sapling_keys().keypairs().enumerate() { // `zewif_zcashd` parses to an earlier version of the `sapling` types, so we // must roundtrip through the byte representation into the version we need. @@ -459,28 +481,40 @@ impl MigrateZcashdWalletCmd { Ok(key) } - for key in wallet.keys().keypairs() { + info!("Importing legacy standalone transparent keys"); // TODO: Expose how many there are in zewif-zcashd. + for (i, key) in wallet.keys().keypairs().enumerate() { let key = convert_key(key)?; + let pubkey = key.pubkey(); + debug!( + "[{i}] Importing key for address {}", + TransparentAddress::from_pubkey(&pubkey).encode(&network_params), + ); + keystore .encrypt_and_store_standalone_transparent_key(&key) .await?; db_data.import_standalone_transparent_pubkey( legacy_transparent_account_uuid.ok_or(MigrateError::SeedNotAvailable)?, - key.pubkey(), + pubkey, )?; } // Since we've retrieved the raw transaction data anyway, preemptively store it for faster // access to balance & to set priorities in the scan queue. - for (h, raw_tx) in tx_heights.values() { - let branch_id = BranchId::for_height(&network_params, *h); - if let Some(raw_tx) = raw_tx { - let tx = Transaction::read(&raw_tx.data[..], branch_id)?; - db_data.with_mut(|mut db| { - decrypt_and_store_transaction(&network_params, &mut db, &tx, Some(*h)) - })?; + if buffer_wallet_transactions { + info!("Importing transactions"); + for (h, raw_tx) in tx_heights.values() { + let branch_id = BranchId::for_height(&network_params, *h); + if let Some(raw_tx) = raw_tx { + let tx = Transaction::read(&raw_tx.data[..], branch_id)?; + db_data.with_mut(|mut db| { + decrypt_and_store_transaction(&network_params, &mut db, &tx, Some(*h)) + })?; + } } + } else { + info!("Not importing transactions (--buffer-wallet-transactions not set)"); } Ok(())