Migrate `zcash-inspect` to `zcash-devtool inspect`

This commit is contained in:
Jack Grigg 2024-12-20 00:25:50 +00:00
parent db8bccc45b
commit e67eef8315
10 changed files with 125 additions and 110 deletions

31
Cargo.lock generated
View File

@ -1758,6 +1758,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
"pkcs8",
"serde",
"signature",
]
@ -1777,6 +1778,22 @@ dependencies = [
"zeroize",
]
[[package]]
name = "ed25519-zebra"
version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9"
dependencies = [
"curve25519-dalek",
"ed25519",
"hashbrown 0.14.5",
"hex",
"rand_core",
"serde",
"sha2",
"zeroize",
]
[[package]]
name = "educe"
version = "0.4.23"
@ -2448,6 +2465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash 0.8.11",
"allocator-api2",
]
[[package]]
@ -7501,16 +7519,23 @@ version = "0.1.0"
dependencies = [
"age",
"anyhow",
"bech32 0.11.0",
"bellman",
"bip0039",
"bip32",
"blake2b_simd",
"chrono",
"clap",
"crossterm",
"ed25519-zebra",
"equihash",
"futures-util",
"group",
"hex",
"image",
"iso_currency",
"jubjub",
"lazy_static",
"minicbor",
"nokhwa",
"orchard",
@ -7525,8 +7550,11 @@ dependencies = [
"rust_decimal",
"sapling-crypto",
"schemerz",
"secp256k1",
"secrecy 0.8.0",
"serde",
"serde_json",
"sha2",
"time",
"tokio",
"tokio-util",
@ -7535,12 +7563,15 @@ dependencies = [
"tracing",
"tracing-subscriber",
"tui-logger",
"uint",
"ur",
"uuid",
"zcash_address",
"zcash_client_backend",
"zcash_client_sqlite",
"zcash_encoding",
"zcash_keys",
"zcash_note_encryption",
"zcash_primitives",
"zcash_proofs",
"zcash_protocol",

View File

@ -37,7 +37,7 @@ zcash_client_sqlite = { version = "0.14", features = ["unstable", "orchard", "se
zcash_keys = { version = "0.6", features = ["unstable", "orchard"] }
zcash_primitives = "0.21"
zcash_proofs = "0.21"
zcash_protocol = "0.4"
zcash_protocol = { version = "0.4", features = ["local-consensus"] }
zip32 = "0.1"
zip321 = "0.2"
@ -52,6 +52,21 @@ chrono = "0.4"
iso_currency = { version = "0.5", features = ["with-serde"] }
rust_decimal = "1"
# Inspect
bech32 = "0.11"
bellman = "0.14"
blake2b_simd = "1"
ed25519-zebra = "4"
equihash = "0.2"
group = "0.13"
lazy_static = "1"
secp256k1 = "0.27"
serde_json = "1"
sha2 = "0.10"
uint = "0.9"
zcash_encoding = "0.2"
zcash_note_encryption = "0.4"
# PCZT QR codes
image = { version = "0.25", optional = true }
minicbor = { version = "0.19", optional = true }
@ -82,6 +97,7 @@ tui = [
]
[patch.crates-io]
equihash = { git = "https://github.com/zcash/librustzcash.git", rev = "9c6d1b958bd015f3fc3f8d5e5815b2bfc54e484b" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "4fa6d3b549f8803016a309281404eab095d04de8" }
pczt = { git = "https://github.com/zcash/librustzcash.git", rev = "9c6d1b958bd015f3fc3f8d5e5815b2bfc54e484b" }
sapling = { package = "sapling-crypto", git = "https://github.com/zcash/sapling-crypto.git", rev = "3c2235747553da642fb142d1eeb9b1afa8391987" }
@ -89,6 +105,7 @@ transparent = { package = "zcash_transparent", git = "https://github.com/zcash/l
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "9c6d1b958bd015f3fc3f8d5e5815b2bfc54e484b" }
zcash_client_backend = { git = "https://github.com/zcash/librustzcash.git", rev = "9c6d1b958bd015f3fc3f8d5e5815b2bfc54e484b" }
zcash_client_sqlite = { git = "https://github.com/zcash/librustzcash.git", rev = "9c6d1b958bd015f3fc3f8d5e5815b2bfc54e484b" }
zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "9c6d1b958bd015f3fc3f8d5e5815b2bfc54e484b" }
zcash_keys = { git = "https://github.com/zcash/librustzcash.git", rev = "9c6d1b958bd015f3fc3f8d5e5815b2bfc54e484b" }
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "9c6d1b958bd015f3fc3f8d5e5815b2bfc54e484b" }
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "9c6d1b958bd015f3fc3f8d5e5815b2bfc54e484b" }

View File

@ -4,6 +4,7 @@ use uuid::Uuid;
use zcash_client_backend::data_api::WalletRead;
use zcash_client_sqlite::AccountUuid;
pub(crate) mod inspect;
pub(crate) mod pczt;
pub(crate) mod wallet;

View File

@ -1,14 +1,12 @@
use std::env;
use std::io;
use std::io::Cursor;
use std::process;
use bech32::primitives::decode::CheckedHrpstring;
use bech32::Bech32;
use gumdrop::{Options, ParsingStyle};
use clap::Args;
use lazy_static::lazy_static;
use secrecy::Zeroize;
use tokio::runtime::Runtime;
use zcash_address::{
unified::{self, Encoding},
ZcashAddress,
@ -39,81 +37,68 @@ lazy_static! {
static ref ORCHARD_VK: orchard::circuit::VerifyingKey = orchard::circuit::VerifyingKey::build();
}
#[derive(Debug, Options)]
struct CliOptions {
#[options(help = "Print this help output")]
help: bool,
#[options(help = "Query information from the chain to help determine what the data is")]
#[derive(Debug, Args)]
pub(crate) struct Command {
/// Query information from the chain to help determine what the data is
#[arg(short, long)]
lookup: bool,
#[options(free, required, help = "String or hex-encoded bytes to inspect")]
/// String or hex-encoded bytes to inspect
data: String,
#[options(
free,
help = "JSON object with keys corresponding to requested context information"
)]
/// JSON object with keys corresponding to requested context information
context: Option<Context>,
}
fn main() {
let args = env::args().collect::<Vec<_>>();
let mut opts =
CliOptions::parse_args(&args[1..], ParsingStyle::default()).unwrap_or_else(|e| {
eprintln!("{}: {}", args[0], e);
impl Command {
pub(crate) async fn run(self) -> Result<(), anyhow::Error> {
let mut opts = self;
if let Ok(mnemonic) = bip0039::Mnemonic::from_phrase(&opts.data) {
opts.data.zeroize();
keys::inspect_mnemonic(mnemonic, opts.context);
} else if let Ok(bytes) = hex::decode(&opts.data) {
inspect_bytes(bytes, opts.context, opts.lookup).await;
} else if let Ok(addr) = ZcashAddress::try_from_encoded(&opts.data) {
address::inspect(addr);
} else if let Ok((network, uivk)) = unified::Uivk::decode(&opts.data) {
keys::view::inspect_uivk(uivk, network);
} else if let Ok((network, ufvk)) = unified::Ufvk::decode(&opts.data) {
keys::view::inspect_ufvk(ufvk, network);
} else if let Ok(parsed) = CheckedHrpstring::new::<Bech32>(&opts.data) {
let data = parsed.byte_iter().collect::<Vec<_>>();
match parsed.hrp().as_str() {
constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => {
keys::view::inspect_sapling_extfvk(data, NetworkType::Main);
}
constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => {
keys::view::inspect_sapling_extfvk(data, NetworkType::Test);
}
constants::regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => {
keys::view::inspect_sapling_extfvk(data, NetworkType::Regtest);
}
constants::mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY => {
keys::inspect_sapling_extsk(data, NetworkType::Main);
}
constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY => {
keys::inspect_sapling_extsk(data, NetworkType::Test);
}
constants::regtest::HRP_SAPLING_EXTENDED_SPENDING_KEY => {
keys::inspect_sapling_extsk(data, NetworkType::Regtest);
}
_ => {
// Unknown data format.
eprintln!("String does not match known Zcash data formats.");
process::exit(2);
}
}
} else {
// Unknown data format.
eprintln!("String does not match known Zcash data formats.");
process::exit(2);
});
if opts.help_requested() {
println!("Usage: {} data [context]", args[0]);
println!();
println!("{}", CliOptions::usage());
return;
}
if let Ok(mnemonic) = bip0039::Mnemonic::from_phrase(&opts.data) {
opts.data.zeroize();
keys::inspect_mnemonic(mnemonic, opts.context);
} else if let Ok(bytes) = hex::decode(&opts.data) {
inspect_bytes(bytes, opts.context, opts.lookup);
} else if let Ok(addr) = ZcashAddress::try_from_encoded(&opts.data) {
address::inspect(addr);
} else if let Ok((network, uivk)) = unified::Uivk::decode(&opts.data) {
keys::view::inspect_uivk(uivk, network);
} else if let Ok((network, ufvk)) = unified::Ufvk::decode(&opts.data) {
keys::view::inspect_ufvk(ufvk, network);
} else if let Ok(parsed) = CheckedHrpstring::new::<Bech32>(&opts.data) {
let data = parsed.byte_iter().collect::<Vec<_>>();
match parsed.hrp().as_str() {
constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => {
keys::view::inspect_sapling_extfvk(data, NetworkType::Main);
}
constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => {
keys::view::inspect_sapling_extfvk(data, NetworkType::Test);
}
constants::regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => {
keys::view::inspect_sapling_extfvk(data, NetworkType::Regtest);
}
constants::mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY => {
keys::inspect_sapling_extsk(data, NetworkType::Main);
}
constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY => {
keys::inspect_sapling_extsk(data, NetworkType::Test);
}
constants::regtest::HRP_SAPLING_EXTENDED_SPENDING_KEY => {
keys::inspect_sapling_extsk(data, NetworkType::Regtest);
}
_ => {
// Unknown data format.
eprintln!("String does not match known Zcash data formats.");
process::exit(2);
}
}
} else {
// Unknown data format.
eprintln!("String does not match known Zcash data formats.");
process::exit(2);
Ok(())
}
}
@ -133,7 +118,7 @@ where
})
}
fn inspect_bytes(bytes: Vec<u8>, context: Option<Context>, lookup: bool) {
async fn inspect_bytes(bytes: Vec<u8>, context: Option<Context>, 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)) {
@ -145,7 +130,7 @@ fn inspect_bytes(bytes: Vec<u8>, context: Option<Context>, lookup: bool) {
} else {
// It's not a known variable-length format. check fixed-length data formats.
match bytes.len() {
32 => inspect_possible_hash(bytes.try_into().unwrap(), context, lookup),
32 => inspect_possible_hash(bytes.try_into().unwrap(), context, lookup).await,
64 => {
// Could be a signature
eprintln!("This is most likely a signature.");
@ -158,7 +143,7 @@ fn inspect_bytes(bytes: Vec<u8>, context: Option<Context>, lookup: bool) {
}
}
fn inspect_possible_hash(bytes: [u8; 32], context: Option<Context>, lookup: bool) {
async fn inspect_possible_hash(bytes: [u8; 32], context: Option<Context>, lookup: bool) {
let maybe_mainnet_block_hash = bytes.iter().take(4).all(|c| c == &0);
if lookup {
@ -167,8 +152,7 @@ fn inspect_possible_hash(bytes: [u8; 32], context: Option<Context>, lookup: bool
let mut candidate = bytes;
candidate.reverse();
let rt = Runtime::new().unwrap();
let found = rt.block_on(async {
let found = async {
match lookup::Lightwalletd::mainnet().await {
Err(e) => eprintln!("Error: Failed to connect to mainnet lightwalletd: {:?}", e),
Ok(mut mainnet) => {
@ -190,7 +174,8 @@ fn inspect_possible_hash(bytes: [u8; 32], context: Option<Context>, lookup: bool
};
false
});
}
.await;
if found {
return;

View File

@ -14,7 +14,7 @@ use zcash_primitives::{
transaction::Transaction,
};
use crate::{
use super::{
transaction::{extract_height_from_coinbase, is_coinbase},
Context, ZUint256,
};

View File

@ -18,7 +18,7 @@ pub(crate) struct JsonNetwork(Network);
struct JsonNetworkVisitor;
impl<'de> Visitor<'de> for JsonNetworkVisitor {
impl Visitor<'_> for JsonNetworkVisitor {
type Value = JsonNetwork;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
@ -54,7 +54,7 @@ struct JsonAccountId(AccountId);
struct JsonAccountIdVisitor;
impl<'de> Visitor<'de> for JsonAccountIdVisitor {
impl Visitor<'_> for JsonAccountIdVisitor {
type Value = JsonAccountId;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
@ -124,7 +124,7 @@ pub(crate) struct ZUint256(pub [u8; 32]);
struct ZUint256Visitor;
impl<'de> Visitor<'de> for ZUint256Visitor {
impl Visitor<'_> for ZUint256Visitor {
type Value = ZUint256;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
@ -174,7 +174,7 @@ struct ZOutputValue(NonNegativeAmount);
struct ZOutputValueVisitor;
impl<'de> Visitor<'de> for ZOutputValueVisitor {
impl Visitor<'_> for ZOutputValueVisitor {
type Value = ZOutputValue;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
@ -220,7 +220,7 @@ struct ZScript(Script);
struct ZScriptVisitor;
impl<'de> Visitor<'de> for ZScriptVisitor {
impl Visitor<'_> for ZScriptVisitor {
type Value = ZScript;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
@ -276,7 +276,7 @@ impl From<TxOut> for ZTxOut {
}
}
#[derive(Debug, Deserialize)]
#[derive(Clone, Debug, Deserialize)]
pub(crate) struct Context {
network: Option<JsonNetwork>,
accounts: Option<Vec<JsonAccountId>>,

View File

@ -20,7 +20,7 @@ use zcash_protocol::{
local_consensus::LocalNetwork,
};
use crate::Context;
use super::Context;
pub(crate) mod view;
@ -199,8 +199,6 @@ pub(crate) fn inspect_sapling_extsk(data: Vec<u8>, network: NetworkType) {
canopy: None,
nu5: None,
nu6: None,
#[cfg(zcash_unstable = "zfuture")]
z_future: None,
}),
};
eprintln!("- UFVK: {encoded_ufvk}");
@ -217,8 +215,6 @@ pub(crate) fn inspect_sapling_extsk(data: Vec<u8>, network: NetworkType) {
canopy: None,
nu5: None,
nu6: None,
#[cfg(zcash_unstable = "zfuture")]
z_future: None,
}),
};
eprintln!(" - Default address: {encoded_ua}");

View File

@ -119,8 +119,6 @@ pub(crate) fn inspect_sapling_extfvk(data: Vec<u8>, network: NetworkType) {
canopy: None,
nu5: None,
nu6: None,
#[cfg(zcash_unstable = "zfuture")]
z_future: None,
}),
};
eprintln!("- Equivalent UFVK: {encoded_ufvk}");
@ -137,8 +135,6 @@ pub(crate) fn inspect_sapling_extfvk(data: Vec<u8>, network: NetworkType) {
canopy: None,
nu5: None,
nu6: None,
#[cfg(zcash_unstable = "zfuture")]
z_future: None,
}),
};
eprintln!(" - Default address: {encoded_ua}");

View File

@ -27,14 +27,11 @@ use zcash_primitives::{
},
};
use crate::{
use super::{
context::{Context, ZTxOut},
GROTH16_PARAMS, ORCHARD_VK,
};
#[cfg(zcash_unstable = "zfuture")]
use zcash_primitives::transaction::components::tze;
pub fn is_coinbase(tx: &Transaction) -> bool {
tx.transparent_bundle()
.map(|b| b.is_coinbase())
@ -148,9 +145,6 @@ impl Authorization for PrecomputedAuth {
type TransparentAuth = TransparentAuth;
type SaplingAuth = sapling::bundle::Authorized;
type OrchardAuth = orchard::bundle::Authorized;
#[cfg(zcash_unstable = "zfuture")]
type TzeAuth = tze::Authorized;
}
pub(crate) fn inspect(
@ -170,10 +164,6 @@ pub(crate) fn inspect(
TxVersion::Zip225 => {
eprintln!(" - Consensus branch ID: {:?}", tx.consensus_branch_id());
}
#[cfg(zcash_unstable = "zfuture")]
TxVersion::ZFuture => {
eprintln!(" - Consensus branch ID: {:?}", tx.consensus_branch_id());
}
}
let is_coinbase = is_coinbase(&tx);
@ -208,13 +198,8 @@ pub(crate) fn inspect(
tx.write(&mut buf).unwrap();
let tx = Transaction::read(&buf[..], tx.consensus_branch_id()).unwrap();
let tx: TransactionData<PrecomputedAuth> = tx.into_data().map_authorization(
f_transparent,
(),
(),
#[cfg(zcash_unstable = "zfuture")]
(),
);
let tx: TransactionData<PrecomputedAuth> =
tx.into_data().map_authorization(f_transparent, (), ());
let txid_parts = tx.digest(TxIdDigester);
(tx, txid_parts)
});

View File

@ -40,6 +40,9 @@ pub(crate) struct MyOptions {
#[derive(Debug, Subcommand)]
pub(crate) enum Command {
/// Inspect Zcash-related data
Inspect(commands::inspect::Command),
/// Manipulate a local wallet backed by `zcash_client_sqlite`
Wallet(commands::Wallet),
@ -114,6 +117,7 @@ fn main() -> Result<(), anyhow::Error> {
let shutdown = ShutdownListener::new();
match opts.command {
Some(Command::Inspect(command)) => command.run().await,
Some(Command::Wallet(commands::Wallet {
wallet_dir,
command,