From 86409235c2a308ecb0b50b3dc8887ca0a2bf33b7 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 24 Jul 2024 18:53:18 +0000 Subject: [PATCH] devtools: Add `--lookup` argument to `zcash-inspect` This queries `lightwalletd` for things that might be txids, and could be extended to other queryable formats in future. --- Cargo.lock | 88 ++++++++++++++++++++++--- devtools/Cargo.toml | 6 +- devtools/src/bin/inspect/lookup.rs | 85 ++++++++++++++++++++++++ devtools/src/bin/inspect/main.rs | 81 +++++++++++++++++------ devtools/src/bin/inspect/transaction.rs | 9 ++- supply-chain/config.toml | 24 +++++++ 6 files changed, 261 insertions(+), 32 deletions(-) create mode 100644 devtools/src/bin/inspect/lookup.rs diff --git a/Cargo.lock b/Cargo.lock index 7aba3a9424..53b0d01ea7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -276,7 +276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecfa55659849ace733f86ccd219da40abd8bc14124e40b312433e85a5a266e77" dependencies = [ "futures-io", - "rustls", + "rustls 0.21.12", ] [[package]] @@ -1229,6 +1229,7 @@ dependencies = [ name = "devtools" version = "0.0.0" dependencies = [ + "anyhow", "bech32", "bellman", "bip0039", @@ -1247,8 +1248,11 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.8", + "tokio", + "tonic", "uint", "zcash_address", + "zcash_client_backend", "zcash_encoding", "zcash_keys", "zcash_note_encryption", @@ -2422,9 +2426,9 @@ checksum = "763d142cdff44aaadd9268bebddb156ef6c65a0e13486bb81673cf2d8739f9b0" dependencies = [ "log", "once_cell", - "rustls", - "rustls-webpki", - "webpki-roots", + "rustls 0.21.12", + "rustls-webpki 0.101.7", + "webpki-roots 0.25.4", ] [[package]] @@ -3523,10 +3527,41 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring 0.17.8", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "log", + "once_cell", + "ring 0.17.8", + "rustls-pki-types", + "rustls-webpki 0.102.6", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -3537,6 +3572,17 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustls-webpki" +version = "0.102.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.17" @@ -4280,7 +4326,18 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5" dependencies = [ - "rustls", + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.12", + "rustls-pki-types", "tokio", ] @@ -4363,6 +4420,7 @@ dependencies = [ "axum", "base64 0.22.1", "bytes", + "flate2", "h2", "http 1.1.0", "http-body", @@ -4373,13 +4431,16 @@ dependencies = [ "percent-encoding", "pin-project", "prost", + "rustls-pemfile", "socket2", "tokio", + "tokio-rustls 0.26.0", "tokio-stream", "tower", "tower-layer", "tower-service", "tracing", + "webpki-roots 0.26.3", ] [[package]] @@ -5006,7 +5067,7 @@ dependencies = [ "educe", "futures", "pin-project", - "rustls", + "rustls 0.21.12", "thiserror", "tokio", "tokio-util", @@ -5462,6 +5523,15 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "4.4.2" @@ -5771,12 +5841,12 @@ dependencies = [ "subtle", "time", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.0", "tonic", "tonic-build", "tor-rtcompat", "tracing", - "webpki-roots", + "webpki-roots 0.25.4", "which", "zcash_address", "zcash_encoding", diff --git a/devtools/Cargo.toml b/devtools/Cargo.toml index 006e724997..15a21d5a69 100644 --- a/devtools/Cargo.toml +++ b/devtools/Cargo.toml @@ -17,11 +17,12 @@ equihash.workspace = true group.workspace = true sha2.workspace = true zcash_address.workspace = true +zcash_client_backend ={ workspace = true, features = ["lightwalletd-tonic-transport"] } zcash_encoding.workspace = true zcash_keys.workspace = true zcash_note_encryption.workspace = true zcash_primitives = { workspace = true, features = ["transparent-inputs"] } -zcash_proofs.workspace = true +zcash_proofs = { workspace = true, features = ["directories"] } zcash_protocol.workspace = true # Transparent @@ -39,11 +40,14 @@ sapling.workspace = true orchard.workspace = true # zcash-inspect tool +anyhow = "1" hex.workspace = true lazy_static.workspace = true secrecy.workspace = true serde.workspace = true serde_json.workspace = true +tonic = { workspace = true, features = ["gzip", "tls-webpki-roots"] } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } uint = "0.9" [[bin]] diff --git a/devtools/src/bin/inspect/lookup.rs b/devtools/src/bin/inspect/lookup.rs new file mode 100644 index 0000000000..8a902f378d --- /dev/null +++ b/devtools/src/bin/inspect/lookup.rs @@ -0,0 +1,85 @@ +use tonic::transport::{Channel, ClientTlsConfig}; +use zcash_client_backend::proto::service::{ + compact_tx_streamer_client::CompactTxStreamerClient, TxFilter, +}; +use zcash_primitives::transaction::Transaction; +use zcash_protocol::consensus::{BlockHeight, BranchId, Network}; + +const MAINNET: Server = Server { + host: "zec.rocks", + port: 443, +}; + +const TESTNET: Server = Server { + host: "testnet.zec.rocks", + port: 443, +}; + +struct Server { + host: &'static str, + port: u16, +} + +impl Server { + fn endpoint(&self) -> String { + format!("https://{}:{}", self.host, self.port) + } +} + +async fn connect(server: &Server) -> anyhow::Result> { + let channel = Channel::from_shared(server.endpoint())?; + + let tls = ClientTlsConfig::new() + .domain_name(server.host.to_string()) + .with_webpki_roots(); + let channel = channel.tls_config(tls)?; + + Ok(CompactTxStreamerClient::new(channel.connect().await?)) +} + +#[derive(Debug)] +pub(crate) struct Lightwalletd { + inner: CompactTxStreamerClient, + parameters: Network, +} + +impl Lightwalletd { + pub(crate) async fn mainnet() -> anyhow::Result { + Ok(Self { + inner: connect(&MAINNET).await?, + parameters: Network::MainNetwork, + }) + } + + pub(crate) async fn testnet() -> anyhow::Result { + Ok(Self { + inner: connect(&TESTNET).await?, + parameters: Network::TestNetwork, + }) + } + + pub(crate) async fn lookup_txid( + &mut self, + candidate: [u8; 32], + ) -> Option<(Transaction, Option)> { + let request = TxFilter { + hash: candidate.into(), + ..Default::default() + }; + let response = self.inner.get_transaction(request).await.ok()?.into_inner(); + + // `RawTransaction.height` has type u64 in the protobuf format, but is documented + // as using -1 for the "not mined" sentinel. Given that we only support u32 block + // heights, -1 in two's complement will fall outside that range. + let mined_height = response.height.try_into().ok(); + + Transaction::read( + &response.data[..], + mined_height + .map(|height| BranchId::for_height(&self.parameters, height)) + .unwrap_or(BranchId::Nu5), + ) + .ok() + .map(|tx| (tx, mined_height)) + } +} diff --git a/devtools/src/bin/inspect/main.rs b/devtools/src/bin/inspect/main.rs index f09c702e49..cad487df94 100644 --- a/devtools/src/bin/inspect/main.rs +++ b/devtools/src/bin/inspect/main.rs @@ -6,6 +6,7 @@ use std::process; use gumdrop::{Options, ParsingStyle}; use lazy_static::lazy_static; use secrecy::Zeroize; +use tokio::runtime::Runtime; use zcash_address::ZcashAddress; use zcash_primitives::{block::BlockHeader, consensus::BranchId, transaction::Transaction}; use zcash_proofs::{default_params_folder, load_parameters, ZcashParameters}; @@ -16,6 +17,7 @@ use context::{Context, ZUint256}; mod address; mod block; mod keys; +mod lookup; mod transaction; lazy_static! { @@ -35,6 +37,9 @@ struct CliOptions { #[options(help = "Print this help output")] help: bool, + #[options(help = "Query information from the chain to help determine what the data is")] + lookup: bool, + #[options(free, required, help = "String or hex-encoded bytes to inspect")] data: String, @@ -64,7 +69,7 @@ fn main() { opts.data.zeroize(); keys::inspect_mnemonic(mnemonic, opts.context); } else if let Ok(bytes) = hex::decode(&opts.data) { - inspect_bytes(bytes, opts.context); + inspect_bytes(bytes, opts.context, opts.lookup); } else if let Ok(addr) = ZcashAddress::try_from_encoded(&opts.data) { address::inspect(addr); } else { @@ -90,7 +95,7 @@ where }) } -fn inspect_bytes(bytes: Vec, context: Option) { +fn inspect_bytes(bytes: Vec, context: Option, lookup: bool) { if let Some(block) = complete(&bytes, |r| block::Block::read(r)) { block::inspect(&block, context); } else if let Some(header) = complete(&bytes, |r| BlockHeader::read(r)) { @@ -98,30 +103,64 @@ fn inspect_bytes(bytes: Vec, context: Option) { } else if let Some(tx) = complete(&bytes, |r| Transaction::read(r, BranchId::Nu5)) { // TODO: Take the branch ID used above from the context if present. // https://github.com/zcash/zcash/issues/6831 - transaction::inspect(tx, context); + transaction::inspect(tx, context, None); } else { // It's not a known variable-length format. check fixed-length data formats. - inspect_fixed_length_bytes(bytes); + match bytes.len() { + 32 => inspect_possible_hash(bytes.try_into().unwrap(), context, lookup), + 64 => { + // Could be a signature + eprintln!("This is most likely a signature."); + } + _ => { + eprintln!("Binary data does not match known Zcash data formats."); + process::exit(2); + } + } } } -fn inspect_fixed_length_bytes(bytes: Vec) { - match bytes.len() { - 32 => { - eprintln!( - "This is most likely a hash of some sort, or maybe a commitment or nullifier." - ); - if bytes.iter().take(4).all(|c| c == &0) { - eprintln!("- It could be a mainnet block hash."); - } - } - 64 => { - // Could be a signature - eprintln!("This is most likely a signature."); - } - _ => { - eprintln!("Binary data does not match known Zcash data formats."); - process::exit(2); +fn inspect_possible_hash(bytes: [u8; 32], context: Option, lookup: bool) { + let maybe_mainnet_block_hash = bytes.iter().take(4).all(|c| c == &0); + + if lookup { + // Block hashes and txids are byte-reversed; we didn't do this when parsing the + // original hex because other hex byte encodings are not byte-reversed. + let mut candidate = bytes; + candidate.reverse(); + + let rt = Runtime::new().unwrap(); + let found = rt.block_on(async { + match lookup::Lightwalletd::mainnet().await { + Err(e) => eprintln!("Error: Failed to connect to mainnet lightwalletd: {:?}", e), + Ok(mut mainnet) => { + if let Some((tx, mined_height)) = mainnet.lookup_txid(candidate).await { + transaction::inspect(tx, context, mined_height); + return true; + } + } + }; + + match lookup::Lightwalletd::testnet().await { + Err(e) => eprintln!("Error: Failed to connect to testnet lightwalletd: {:?}", e), + Ok(mut testnet) => { + if let Some((tx, mined_height)) = testnet.lookup_txid(candidate).await { + transaction::inspect(tx, context, mined_height); + return true; + } + } + }; + + false + }); + + if found { + return; } } + + eprintln!("This is most likely a hash of some sort, or maybe a commitment or nullifier."); + if maybe_mainnet_block_hash { + eprintln!("- It could be a mainnet block hash."); + } } diff --git a/devtools/src/bin/inspect/transaction.rs b/devtools/src/bin/inspect/transaction.rs index 0c22c8b3d3..3521430765 100644 --- a/devtools/src/bin/inspect/transaction.rs +++ b/devtools/src/bin/inspect/transaction.rs @@ -152,9 +152,16 @@ impl Authorization for PrecomputedAuth { type TzeAuth = tze::Authorized; } -pub(crate) fn inspect(tx: Transaction, context: Option) { +pub(crate) fn inspect( + tx: Transaction, + context: Option, + mined_height: Option, +) { eprintln!("Zcash transaction"); eprintln!(" - ID: {}", tx.txid()); + if let Some(height) = mined_height { + eprintln!(" - Mined in block {}", height); + } eprintln!(" - Version: {:?}", tx.version()); match tx.version() { // TODO: If pre-v5 and no branch ID provided in context, disable signature checks. diff --git a/supply-chain/config.toml b/supply-chain/config.toml index c57435df06..c2b3872617 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -1023,10 +1023,26 @@ criteria = "safe-to-deploy" version = "0.21.8" criteria = "safe-to-deploy" +[[exemptions.rustls]] +version = "0.23.12" +criteria = "safe-to-deploy" + +[[exemptions.rustls-pemfile]] +version = "2.1.2" +criteria = "safe-to-deploy" + +[[exemptions.rustls-pki-types]] +version = "1.7.0" +criteria = "safe-to-deploy" + [[exemptions.rustls-webpki]] version = "0.101.7" criteria = "safe-to-deploy" +[[exemptions.rustls-webpki]] +version = "0.102.6" +criteria = "safe-to-deploy" + [[exemptions.rusty-fork]] version = "0.3.0" criteria = "safe-to-deploy" @@ -1211,6 +1227,10 @@ criteria = "safe-to-deploy" version = "2.2.0" criteria = "safe-to-deploy" +[[exemptions.tokio-rustls]] +version = "0.26.0" +criteria = "safe-to-deploy" + [[exemptions.tokio-util]] version = "0.7.10" criteria = "safe-to-deploy" @@ -1439,6 +1459,10 @@ criteria = "safe-to-deploy" version = "0.3.65" criteria = "safe-to-deploy" +[[exemptions.webpki-roots]] +version = "0.26.3" +criteria = "safe-to-deploy" + [[exemptions.which]] version = "4.4.2" criteria = "safe-to-deploy"