Merge pull request #1451 from zcash/devtools
Import `zcash-inspect` from `zcash/zcash` into a `devtools` crate
This commit is contained in:
commit
a5e467f335
|
@ -471,6 +471,20 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bip0039"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e68a5a99c65851e7be249f5cf510c0a136f18c9bca32139576d59bd3f577b043"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
"pbkdf2",
|
||||
"rand 0.8.5",
|
||||
"sha2 0.10.8",
|
||||
"unicode-normalization",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bip32"
|
||||
version = "0.5.1"
|
||||
|
@ -998,6 +1012,33 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"curve25519-dalek-derive",
|
||||
"digest 0.10.7",
|
||||
"fiat-crypto",
|
||||
"rustc_version",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek-derive"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.63",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "daggy"
|
||||
version = "0.8.0"
|
||||
|
@ -1184,6 +1225,38 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "devtools"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bech32",
|
||||
"bellman",
|
||||
"bip0039",
|
||||
"blake2b_simd",
|
||||
"ed25519-zebra",
|
||||
"equihash",
|
||||
"group",
|
||||
"gumdrop",
|
||||
"hex",
|
||||
"jubjub",
|
||||
"lazy_static",
|
||||
"orchard",
|
||||
"sapling-crypto",
|
||||
"secp256k1",
|
||||
"secrecy",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.8",
|
||||
"uint",
|
||||
"zcash_address",
|
||||
"zcash_encoding",
|
||||
"zcash_keys",
|
||||
"zcash_note_encryption",
|
||||
"zcash_primitives",
|
||||
"zcash_proofs",
|
||||
"zcash_protocol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
|
@ -1290,14 +1363,25 @@ dependencies = [
|
|||
"signature 1.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||
dependencies = [
|
||||
"pkcs8",
|
||||
"serde",
|
||||
"signature 2.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"ed25519",
|
||||
"curve25519-dalek 3.2.0",
|
||||
"ed25519 1.5.3",
|
||||
"merlin",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
|
@ -1305,6 +1389,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 4.1.3",
|
||||
"ed25519 2.2.3",
|
||||
"hashbrown 0.14.5",
|
||||
"hex",
|
||||
"rand_core 0.6.4",
|
||||
"serde",
|
||||
"sha2 0.10.8",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "educe"
|
||||
version = "0.4.23"
|
||||
|
@ -1428,6 +1528,12 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fiat-crypto"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "findshlibs"
|
||||
version = "0.10.2"
|
||||
|
@ -2634,6 +2740,17 @@ dependencies = [
|
|||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pasta_curves"
|
||||
version = "0.5.1"
|
||||
|
@ -2661,6 +2778,16 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
|
@ -2971,7 +3098,7 @@ checksum = "5bb182580f71dd070f88d01ce3de9f4da5021db7115d2e1c3605a754153b77c1"
|
|||
dependencies = [
|
||||
"bytes",
|
||||
"heck",
|
||||
"itertools 0.11.0",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"multimap",
|
||||
"once_cell",
|
||||
|
@ -2991,7 +3118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.11.0",
|
||||
"itertools 0.12.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.63",
|
||||
|
@ -4701,7 +4828,7 @@ dependencies = [
|
|||
"aes",
|
||||
"base64ct",
|
||||
"ctr",
|
||||
"curve25519-dalek",
|
||||
"curve25519-dalek 3.2.0",
|
||||
"derive_more",
|
||||
"digest 0.10.7",
|
||||
"ed25519-dalek",
|
||||
|
@ -5093,6 +5220,15 @@ version = "1.0.12"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
|
@ -5541,7 +5677,7 @@ version = "2.0.0-pre.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5da623d8af10a62342bcbbb230e33e58a63255a58012f8653c578e54bab48df"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"curve25519-dalek 3.2.0",
|
||||
"rand_core 0.6.4",
|
||||
"zeroize",
|
||||
]
|
||||
|
|
|
@ -6,6 +6,7 @@ members = [
|
|||
"components/zcash_encoding",
|
||||
"components/zcash_protocol",
|
||||
"components/zip321",
|
||||
"devtools",
|
||||
"zcash",
|
||||
"zcash_client_backend",
|
||||
"zcash_client_sqlite",
|
||||
|
@ -43,6 +44,7 @@ zcash_primitives = { version = "0.15", path = "zcash_primitives", default-featur
|
|||
zcash_proofs = { version = "0.15", path = "zcash_proofs", default-features = false }
|
||||
|
||||
# Shielded protocols
|
||||
bellman = { version = "0.14", default-features = false, features = ["groth16"] }
|
||||
ff = "0.13"
|
||||
group = "0.13"
|
||||
incrementalmerkletree = "0.5.1"
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
[package]
|
||||
name = "devtools"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
# General tool dependencies
|
||||
gumdrop = "0.8"
|
||||
|
||||
# General Zcash dependencies
|
||||
bech32.workspace = true
|
||||
bip0039 = "0.11"
|
||||
blake2b_simd.workspace = true
|
||||
equihash.workspace = true
|
||||
group.workspace = true
|
||||
sha2.workspace = true
|
||||
zcash_address.workspace = true
|
||||
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_protocol.workspace = true
|
||||
|
||||
# Transparent
|
||||
secp256k1.workspace = true
|
||||
|
||||
# Sprout
|
||||
ed25519-zebra = "4"
|
||||
|
||||
# Sapling
|
||||
bellman.workspace = true
|
||||
jubjub.workspace = true
|
||||
sapling.workspace = true
|
||||
|
||||
# Orchard
|
||||
orchard.workspace = true
|
||||
|
||||
# zcash-inspect tool
|
||||
hex.workspace = true
|
||||
lazy_static.workspace = true
|
||||
secrecy.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
uint = "0.9"
|
||||
|
||||
[[bin]]
|
||||
name = "zcash-inspect"
|
||||
path = "src/bin/inspect/main.rs"
|
|
@ -0,0 +1,164 @@
|
|||
use zcash_address::{
|
||||
unified::{self, Container, Encoding},
|
||||
ConversionError, Network, ToAddress, ZcashAddress,
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
enum AddressKind {
|
||||
Sprout([u8; 64]),
|
||||
Sapling([u8; 43]),
|
||||
Unified(unified::Address),
|
||||
P2pkh([u8; 20]),
|
||||
P2sh([u8; 20]),
|
||||
Tex([u8; 20]),
|
||||
}
|
||||
|
||||
struct Address {
|
||||
net: Network,
|
||||
kind: AddressKind,
|
||||
}
|
||||
|
||||
impl zcash_address::TryFromAddress for Address {
|
||||
type Error = ();
|
||||
|
||||
fn try_from_sprout(net: Network, data: [u8; 64]) -> Result<Self, ConversionError<Self::Error>> {
|
||||
Ok(Address {
|
||||
net,
|
||||
kind: AddressKind::Sprout(data),
|
||||
})
|
||||
}
|
||||
|
||||
fn try_from_sapling(
|
||||
net: Network,
|
||||
data: [u8; 43],
|
||||
) -> Result<Self, ConversionError<Self::Error>> {
|
||||
Ok(Address {
|
||||
net,
|
||||
kind: AddressKind::Sapling(data),
|
||||
})
|
||||
}
|
||||
|
||||
fn try_from_unified(
|
||||
net: Network,
|
||||
data: unified::Address,
|
||||
) -> Result<Self, ConversionError<Self::Error>> {
|
||||
Ok(Address {
|
||||
net,
|
||||
kind: AddressKind::Unified(data),
|
||||
})
|
||||
}
|
||||
|
||||
fn try_from_transparent_p2pkh(
|
||||
net: Network,
|
||||
data: [u8; 20],
|
||||
) -> Result<Self, ConversionError<Self::Error>> {
|
||||
Ok(Address {
|
||||
net,
|
||||
kind: AddressKind::P2pkh(data),
|
||||
})
|
||||
}
|
||||
|
||||
fn try_from_transparent_p2sh(
|
||||
net: Network,
|
||||
data: [u8; 20],
|
||||
) -> Result<Self, ConversionError<Self::Error>> {
|
||||
Ok(Address {
|
||||
net,
|
||||
kind: AddressKind::P2sh(data),
|
||||
})
|
||||
}
|
||||
|
||||
fn try_from_tex(net: Network, data: [u8; 20]) -> Result<Self, ConversionError<Self::Error>> {
|
||||
Ok(Address {
|
||||
net,
|
||||
kind: AddressKind::Tex(data),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn inspect(addr: ZcashAddress) {
|
||||
eprintln!("Zcash address");
|
||||
|
||||
match addr.convert::<Address>() {
|
||||
// TODO: Check for valid internals once we have migrated to a newer zcash_address
|
||||
// version with custom errors.
|
||||
Err(_) => unreachable!(),
|
||||
Ok(addr) => {
|
||||
eprintln!(
|
||||
" - Network: {}",
|
||||
match addr.net {
|
||||
Network::Main => "main",
|
||||
Network::Test => "testnet",
|
||||
Network::Regtest => "regtest",
|
||||
}
|
||||
);
|
||||
eprintln!(
|
||||
" - Kind: {}",
|
||||
match addr.kind {
|
||||
AddressKind::Sprout(_) => "Sprout",
|
||||
AddressKind::Sapling(_) => "Sapling",
|
||||
AddressKind::Unified(_) => "Unified Address",
|
||||
AddressKind::P2pkh(_) => "Transparent P2PKH",
|
||||
AddressKind::P2sh(_) => "Transparent P2SH",
|
||||
AddressKind::Tex(_) => "TEX (ZIP 320)",
|
||||
}
|
||||
);
|
||||
|
||||
match addr.kind {
|
||||
AddressKind::Unified(ua) => {
|
||||
eprintln!(" - Receivers:");
|
||||
for receiver in ua.items() {
|
||||
match receiver {
|
||||
unified::Receiver::Orchard(data) => {
|
||||
eprintln!(
|
||||
" - Orchard ({})",
|
||||
unified::Address::try_from_items(vec![
|
||||
unified::Receiver::Orchard(data)
|
||||
])
|
||||
.unwrap()
|
||||
.encode(&addr.net)
|
||||
);
|
||||
}
|
||||
unified::Receiver::Sapling(data) => {
|
||||
eprintln!(
|
||||
" - Sapling ({})",
|
||||
ZcashAddress::from_sapling(addr.net, data)
|
||||
);
|
||||
}
|
||||
unified::Receiver::P2pkh(data) => {
|
||||
eprintln!(
|
||||
" - Transparent P2PKH ({})",
|
||||
ZcashAddress::from_transparent_p2pkh(addr.net, data)
|
||||
);
|
||||
}
|
||||
unified::Receiver::P2sh(data) => {
|
||||
eprintln!(
|
||||
" - Transparent P2SH ({})",
|
||||
ZcashAddress::from_transparent_p2sh(addr.net, data)
|
||||
);
|
||||
}
|
||||
unified::Receiver::Unknown { typecode, data } => {
|
||||
eprintln!(" - Unknown");
|
||||
eprintln!(" - Typecode: {}", typecode);
|
||||
eprintln!(" - Payload: {}", hex::encode(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AddressKind::P2pkh(data) => {
|
||||
eprintln!(
|
||||
" - Corresponding TEX: {}",
|
||||
ZcashAddress::from_tex(addr.net, data),
|
||||
);
|
||||
}
|
||||
AddressKind::Tex(data) => {
|
||||
eprintln!(
|
||||
" - Corresponding P2PKH: {}",
|
||||
ZcashAddress::from_transparent_p2pkh(addr.net, data),
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,401 @@
|
|||
// To silence lints in the `uint::construct_uint!` macro.
|
||||
#![allow(clippy::assign_op_pattern)]
|
||||
#![allow(clippy::ptr_offset_with_cast)]
|
||||
|
||||
use std::cmp;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::io::{self, Read};
|
||||
|
||||
use sha2::{Digest, Sha256};
|
||||
use zcash_encoding::Vector;
|
||||
use zcash_primitives::{
|
||||
block::BlockHeader,
|
||||
consensus::{BlockHeight, BranchId, Network, NetworkUpgrade, Parameters},
|
||||
transaction::Transaction,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
transaction::{extract_height_from_coinbase, is_coinbase},
|
||||
Context, ZUint256,
|
||||
};
|
||||
|
||||
const MIN_BLOCK_VERSION: i32 = 4;
|
||||
|
||||
uint::construct_uint! {
|
||||
pub(crate) struct U256(4);
|
||||
}
|
||||
|
||||
impl U256 {
|
||||
fn from_compact(compact: u32) -> (Self, bool, bool) {
|
||||
let size = compact >> 24;
|
||||
let word = compact & 0x007fffff;
|
||||
let result = if size <= 3 {
|
||||
U256::from(word >> (8 * (3 - size)))
|
||||
} else {
|
||||
U256::from(word) << (8 * (size - 3))
|
||||
};
|
||||
(
|
||||
result,
|
||||
word != 0 && (compact & 0x00800000) != 0,
|
||||
word != 0
|
||||
&& ((size > 34) || (word > 0xff && size > 33) || (word > 0xffff && size > 32)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait BlockParams: Parameters {
|
||||
fn equihash_n(&self) -> u32;
|
||||
fn equihash_k(&self) -> u32;
|
||||
fn pow_limit(&self) -> U256;
|
||||
}
|
||||
|
||||
impl BlockParams for Network {
|
||||
fn equihash_n(&self) -> u32 {
|
||||
match self {
|
||||
Self::MainNetwork | Self::TestNetwork => 200,
|
||||
}
|
||||
}
|
||||
|
||||
fn equihash_k(&self) -> u32 {
|
||||
match self {
|
||||
Self::MainNetwork | Self::TestNetwork => 9,
|
||||
}
|
||||
}
|
||||
|
||||
fn pow_limit(&self) -> U256 {
|
||||
match self {
|
||||
Self::MainNetwork => U256::from_big_endian(
|
||||
&hex::decode("0007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
.unwrap(),
|
||||
),
|
||||
Self::TestNetwork => U256::from_big_endian(
|
||||
&hex::decode("07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
.unwrap(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn guess_params(header: &BlockHeader) -> Option<Network> {
|
||||
// If the block target falls between the testnet and mainnet powLimit, assume testnet.
|
||||
let (target, is_negative, did_overflow) = U256::from_compact(header.bits);
|
||||
if !(is_negative || did_overflow)
|
||||
&& target > Network::MainNetwork.pow_limit()
|
||||
&& target <= Network::TestNetwork.pow_limit()
|
||||
{
|
||||
return Some(Network::TestNetwork);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn check_equihash_solution(header: &BlockHeader, params: Network) -> Result<(), equihash::Error> {
|
||||
let eh_input = {
|
||||
let mut eh_input = vec![];
|
||||
header.write(&mut eh_input).unwrap();
|
||||
eh_input.truncate(4 + 32 + 32 + 32 + 4 + 4);
|
||||
eh_input
|
||||
};
|
||||
equihash::is_valid_solution(
|
||||
params.equihash_n(),
|
||||
params.equihash_k(),
|
||||
&eh_input,
|
||||
&header.nonce,
|
||||
&header.solution,
|
||||
)
|
||||
}
|
||||
|
||||
fn check_proof_of_work(header: &BlockHeader, params: Network) -> Result<(), &str> {
|
||||
let (target, is_negative, did_overflow) = U256::from_compact(header.bits);
|
||||
let hash = U256::from_little_endian(&header.hash().0);
|
||||
|
||||
if is_negative {
|
||||
Err("nBits is negative")
|
||||
} else if target.is_zero() {
|
||||
Err("target is zero")
|
||||
} else if did_overflow {
|
||||
Err("nBits overflowed")
|
||||
} else if target > params.pow_limit() {
|
||||
Err("target is larger than powLimit")
|
||||
} else if hash > target {
|
||||
Err("block hash larger than target")
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_block_commitments_hash(
|
||||
chain_history_root: [u8; 32],
|
||||
auth_data_root: [u8; 32],
|
||||
) -> [u8; 32] {
|
||||
blake2b_simd::Params::new()
|
||||
.hash_length(32)
|
||||
.personal(b"ZcashBlockCommit")
|
||||
.to_state()
|
||||
.update(&chain_history_root)
|
||||
.update(&auth_data_root)
|
||||
.update(&[0; 32])
|
||||
.finalize()
|
||||
.as_bytes()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) struct Block {
|
||||
header: BlockHeader,
|
||||
txs: Vec<Transaction>,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub(crate) fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||
let header = BlockHeader::read(&mut reader)?;
|
||||
let txs = Vector::read(reader, |r| Transaction::read(r, BranchId::Sprout))?;
|
||||
|
||||
Ok(Block { header, txs })
|
||||
}
|
||||
|
||||
pub(crate) fn guess_params(&self) -> Option<Network> {
|
||||
guess_params(&self.header)
|
||||
}
|
||||
|
||||
fn extract_height(&self) -> Option<BlockHeight> {
|
||||
self.txs.first().and_then(extract_height_from_coinbase)
|
||||
}
|
||||
|
||||
/// Builds the Merkle tree for this block and returns its root.
|
||||
///
|
||||
/// The returned `bool` indicates whether mutation was detected in the Merkle tree (a
|
||||
/// duplication of transactions in the block leading to an identical Merkle root).
|
||||
fn build_merkle_root(&self) -> ([u8; 32], bool) {
|
||||
// Safe upper bound for the number of total nodes.
|
||||
let mut merkle_tree = Vec::with_capacity(self.txs.len() * 2 + 16);
|
||||
for tx in &self.txs {
|
||||
merkle_tree.push(sha2::digest::generic_array::GenericArray::from(
|
||||
*tx.txid().as_ref(),
|
||||
));
|
||||
}
|
||||
let mut size = self.txs.len();
|
||||
let mut j = 0;
|
||||
let mut mutated = false;
|
||||
while size > 1 {
|
||||
let mut i = 0;
|
||||
while i < size {
|
||||
let i2 = cmp::min(i + 1, size - 1);
|
||||
if i2 == i + 1 && i2 + 1 == size && merkle_tree[j + i] == merkle_tree[j + i2] {
|
||||
// Two identical hashes at the end of the list at a particular level.
|
||||
mutated = true;
|
||||
}
|
||||
let mut inner_hasher = Sha256::new();
|
||||
inner_hasher.update(merkle_tree[j + i]);
|
||||
inner_hasher.update(merkle_tree[j + i2]);
|
||||
merkle_tree.push(Sha256::digest(inner_hasher.finalize()));
|
||||
i += 2;
|
||||
}
|
||||
j += size;
|
||||
size = (size + 1) / 2;
|
||||
}
|
||||
(
|
||||
merkle_tree
|
||||
.last()
|
||||
.copied()
|
||||
.map(|root| root.into())
|
||||
.unwrap_or([0; 32]),
|
||||
mutated,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_auth_data_root(&self) -> [u8; 32] {
|
||||
fn next_pow2(x: u64) -> u64 {
|
||||
// Fails if `x` is greater than `1u64 << 63`, but this can't occur because a
|
||||
// block can't feasibly contain that many transactions.
|
||||
1u64 << (64 - x.saturating_sub(1).leading_zeros())
|
||||
}
|
||||
|
||||
let perfect_size = next_pow2(self.txs.len() as u64) as usize;
|
||||
assert_eq!((perfect_size & (perfect_size - 1)), 0);
|
||||
let expected_size = cmp::max(perfect_size * 2, 1) - 1; // The total number of nodes.
|
||||
let mut tree = Vec::with_capacity(expected_size);
|
||||
|
||||
// Add the leaves to the tree. v1-v4 transactions will append empty leaves.
|
||||
for tx in &self.txs {
|
||||
tree.push(<[u8; 32]>::try_from(tx.auth_commitment().as_bytes()).unwrap());
|
||||
}
|
||||
// Append empty leaves until we get a perfect tree.
|
||||
tree.resize(perfect_size, [0; 32]);
|
||||
|
||||
let mut j = 0;
|
||||
let mut layer_width = perfect_size;
|
||||
while layer_width > 1 {
|
||||
let mut i = 0;
|
||||
while i < layer_width {
|
||||
tree.push(
|
||||
blake2b_simd::Params::new()
|
||||
.hash_length(32)
|
||||
.personal(b"ZcashAuthDatHash")
|
||||
.to_state()
|
||||
.update(&tree[j + i])
|
||||
.update(&tree[j + i + 1])
|
||||
.finalize()
|
||||
.as_bytes()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
i += 2;
|
||||
}
|
||||
|
||||
// Move to the next layer.
|
||||
j += layer_width;
|
||||
layer_width /= 2;
|
||||
}
|
||||
|
||||
assert_eq!(tree.len(), expected_size);
|
||||
tree.last().copied().unwrap_or([0; 32])
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn inspect_header(header: &BlockHeader, context: Option<Context>) {
|
||||
eprintln!("Zcash block header");
|
||||
inspect_header_inner(
|
||||
header,
|
||||
guess_params(header).or_else(|| context.and_then(|c| c.network())),
|
||||
);
|
||||
}
|
||||
|
||||
fn inspect_header_inner(header: &BlockHeader, params: Option<Network>) {
|
||||
eprintln!(" - Hash: {}", header.hash());
|
||||
eprintln!(" - Version: {}", header.version);
|
||||
if header.version < MIN_BLOCK_VERSION {
|
||||
// zcashd: version-too-low
|
||||
eprintln!("⚠️ Version too low",);
|
||||
}
|
||||
if let Some(params) = params {
|
||||
if let Err(e) = check_equihash_solution(header, params) {
|
||||
// zcashd: invalid-solution
|
||||
eprintln!("⚠️ Invalid Equihash solution: {}", e);
|
||||
}
|
||||
if let Err(e) = check_proof_of_work(header, params) {
|
||||
// zcashd: high-hash
|
||||
eprintln!("⚠️ Invalid Proof-of-Work: {}", e);
|
||||
}
|
||||
} else {
|
||||
eprintln!("🔎 To check contextual rules, add \"network\" to context (either \"main\" or \"test\")");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn inspect(block: &Block, context: Option<Context>) {
|
||||
eprintln!("Zcash block");
|
||||
let params = block
|
||||
.guess_params()
|
||||
.or_else(|| context.as_ref().and_then(|c| c.network()));
|
||||
inspect_header_inner(&block.header, params);
|
||||
|
||||
let height = match block.txs.len() {
|
||||
0 => {
|
||||
// zcashd: bad-cb-missing
|
||||
eprintln!("⚠️ Missing coinbase transaction");
|
||||
None
|
||||
}
|
||||
txs => {
|
||||
eprintln!(" - {} transaction(s) including coinbase", txs);
|
||||
|
||||
if !is_coinbase(&block.txs[0]) {
|
||||
// zcashd: bad-cb-missing
|
||||
eprintln!("⚠️ vtx[0] is not a coinbase transaction");
|
||||
None
|
||||
} else {
|
||||
let height = block.extract_height();
|
||||
match height {
|
||||
Some(h) => eprintln!(" - Height: {}", h),
|
||||
// zcashd: bad-cb-height
|
||||
None => eprintln!("⚠️ No height in coinbase transaction"),
|
||||
}
|
||||
height
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (i, tx) in block.txs.iter().enumerate().skip(1) {
|
||||
if is_coinbase(tx) {
|
||||
// zcashd: bad-cb-multiple
|
||||
eprintln!("⚠️ vtx[{}] is a coinbase transaction", i);
|
||||
}
|
||||
}
|
||||
|
||||
let (merkle_root, merkle_root_mutated) = block.build_merkle_root();
|
||||
if merkle_root != block.header.merkle_root {
|
||||
// zcashd: bad-txnmrklroot
|
||||
eprintln!("⚠️ header.merkleroot doesn't match transaction Merkle tree root");
|
||||
eprintln!(" - merkleroot (calc): {}", ZUint256(merkle_root));
|
||||
eprintln!(
|
||||
" - header.merkleroot: {}",
|
||||
ZUint256(block.header.merkle_root)
|
||||
);
|
||||
}
|
||||
if merkle_root_mutated {
|
||||
// zcashd: bad-txns-duplicate
|
||||
eprintln!("⚠️ Transaction Merkle tree is malleable");
|
||||
}
|
||||
|
||||
// The rest of the checks require network parameters and a block height.
|
||||
let (params, height) = match (params, height) {
|
||||
(Some(params), Some(height)) => (params, height),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if params.is_nu_active(NetworkUpgrade::Nu5, height) {
|
||||
if block.txs[0].expiry_height() != height {
|
||||
// zcashd: bad-cb-height
|
||||
eprintln!(
|
||||
"⚠️ [NU5] coinbase expiry height ({}) doesn't match coinbase scriptSig height ({})",
|
||||
block.txs[0].expiry_height(),
|
||||
height
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(chain_history_root) = context.and_then(|c| c.chainhistoryroot) {
|
||||
let auth_data_root = block.build_auth_data_root();
|
||||
let block_commitments_hash =
|
||||
derive_block_commitments_hash(chain_history_root.0, auth_data_root);
|
||||
|
||||
if block_commitments_hash != block.header.final_sapling_root {
|
||||
// zcashd: bad-block-commitments-hash
|
||||
eprintln!(
|
||||
"⚠️ [NU5] header.blockcommitments doesn't match ZIP 244 block commitment"
|
||||
);
|
||||
eprintln!(" - chainhistoryroot: {}", chain_history_root);
|
||||
eprintln!(" - authdataroot: {}", ZUint256(auth_data_root));
|
||||
eprintln!(
|
||||
" - blockcommitments (calc): {}",
|
||||
ZUint256(block_commitments_hash)
|
||||
);
|
||||
eprintln!(
|
||||
" - header.blockcommitments: {}",
|
||||
ZUint256(block.header.final_sapling_root)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
eprintln!("🔎 To check header.blockcommitments, add \"chainhistoryroot\" to context");
|
||||
}
|
||||
} else if Some(height) == params.activation_height(NetworkUpgrade::Heartwood) {
|
||||
if block.header.final_sapling_root != [0; 32] {
|
||||
// zcashd: bad-heartwood-root-in-block
|
||||
eprintln!("⚠️ This is the block that activates Heartwood but header.blockcommitments is not null");
|
||||
}
|
||||
} else if params.is_nu_active(NetworkUpgrade::Heartwood, height) {
|
||||
if let Some(chain_history_root) = context.and_then(|c| c.chainhistoryroot) {
|
||||
if chain_history_root.0 != block.header.final_sapling_root {
|
||||
// zcashd: bad-heartwood-root-in-block
|
||||
eprintln!(
|
||||
"⚠️ [Heartwood] header.blockcommitments doesn't match provided chain history root"
|
||||
);
|
||||
eprintln!(" - chainhistoryroot: {}", chain_history_root);
|
||||
eprintln!(
|
||||
" - header.blockcommitments: {}",
|
||||
ZUint256(block.header.final_sapling_root)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
eprintln!("🔎 To check header.blockcommitments, add \"chainhistoryroot\" to context");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,325 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{
|
||||
de::{Unexpected, Visitor},
|
||||
Deserialize, Serialize, Serializer,
|
||||
};
|
||||
use zcash_primitives::{
|
||||
consensus::Network,
|
||||
legacy::Script,
|
||||
transaction::components::{amount::NonNegativeAmount, transparent, TxOut},
|
||||
zip32::AccountId,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct JsonNetwork(Network);
|
||||
|
||||
struct JsonNetworkVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for JsonNetworkVisitor {
|
||||
type Value = JsonNetwork;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("either \"main\" or \"test\"")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
match v {
|
||||
"main" => Ok(JsonNetwork(Network::MainNetwork)),
|
||||
"test" => Ok(JsonNetwork(Network::TestNetwork)),
|
||||
_ => Err(serde::de::Error::invalid_value(
|
||||
Unexpected::Str(v),
|
||||
&"either \"main\" or \"test\"",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for JsonNetwork {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(JsonNetworkVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct JsonAccountId(AccountId);
|
||||
|
||||
struct JsonAccountIdVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for JsonAccountIdVisitor {
|
||||
type Value = JsonAccountId;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a u31")
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
u32::try_from(v)
|
||||
.map_err(|_| E::custom(format!("u32 out of range: {}", v)))
|
||||
.and_then(|a| {
|
||||
AccountId::try_from(a).map_err(|e| E::custom(format!("AccountId invalid: {}", e)))
|
||||
})
|
||||
.map(JsonAccountId)
|
||||
}
|
||||
|
||||
fn visit_i128<E>(self, v: i128) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
u32::try_from(v)
|
||||
.map_err(|_| E::custom(format!("u32 out of range: {}", v)))
|
||||
.and_then(|a| {
|
||||
AccountId::try_from(a).map_err(|e| E::custom(format!("AccountId invalid: {}", e)))
|
||||
})
|
||||
.map(JsonAccountId)
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
u32::try_from(v)
|
||||
.map_err(|_| E::custom(format!("u32 out of range: {}", v)))
|
||||
.and_then(|a| {
|
||||
AccountId::try_from(a).map_err(|e| E::custom(format!("AccountId invalid: {}", e)))
|
||||
})
|
||||
.map(JsonAccountId)
|
||||
}
|
||||
|
||||
fn visit_u128<E>(self, v: u128) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
u32::try_from(v)
|
||||
.map_err(|_| E::custom(format!("u32 out of range: {}", v)))
|
||||
.and_then(|a| {
|
||||
AccountId::try_from(a).map_err(|e| E::custom(format!("AccountId invalid: {}", e)))
|
||||
})
|
||||
.map(JsonAccountId)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for JsonAccountId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_u32(JsonAccountIdVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct ZUint256(pub [u8; 32]);
|
||||
|
||||
struct ZUint256Visitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ZUint256Visitor {
|
||||
type Value = ZUint256;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a hex-encoded 32-byte array")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let mut data = [0; 32];
|
||||
hex::decode_to_slice(v, &mut data).map_err(|e| match e {
|
||||
hex::FromHexError::InvalidHexCharacter { c, .. } => {
|
||||
serde::de::Error::invalid_value(Unexpected::Char(c), &"valid hex character")
|
||||
}
|
||||
hex::FromHexError::OddLength => {
|
||||
serde::de::Error::invalid_length(v.len(), &"an even-length string")
|
||||
}
|
||||
hex::FromHexError::InvalidStringLength => {
|
||||
serde::de::Error::invalid_length(v.len(), &"a 64-character string")
|
||||
}
|
||||
})?;
|
||||
data.reverse();
|
||||
Ok(ZUint256(data))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ZUint256 {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(ZUint256Visitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ZUint256 {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut data = self.0;
|
||||
data.reverse();
|
||||
formatter.write_str(&hex::encode(data))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ZOutputValue(NonNegativeAmount);
|
||||
|
||||
struct ZOutputValueVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ZOutputValueVisitor {
|
||||
type Value = ZOutputValue;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a non-negative integer number of zatoshis")
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
NonNegativeAmount::from_u64(v)
|
||||
.map(ZOutputValue)
|
||||
.map_err(|e| match e {
|
||||
zcash_protocol::value::BalanceError::Overflow => serde::de::Error::invalid_type(
|
||||
Unexpected::Unsigned(v),
|
||||
&"a valid zatoshi amount",
|
||||
),
|
||||
zcash_protocol::value::BalanceError::Underflow => serde::de::Error::invalid_type(
|
||||
Unexpected::Unsigned(v),
|
||||
&"a non-negative zatoshi amount",
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ZOutputValue {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_u64(ZOutputValueVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ZOutputValue {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_u64(u64::from(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ZScript(Script);
|
||||
|
||||
struct ZScriptVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ZScriptVisitor {
|
||||
type Value = ZScript;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a hex-encoded Script")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let data = hex::decode(v).map_err(|e| match e {
|
||||
hex::FromHexError::InvalidHexCharacter { c, .. } => {
|
||||
serde::de::Error::invalid_value(Unexpected::Char(c), &"valid hex character")
|
||||
}
|
||||
hex::FromHexError::OddLength => {
|
||||
serde::de::Error::invalid_length(v.len(), &"an even-length string")
|
||||
}
|
||||
hex::FromHexError::InvalidStringLength => {
|
||||
serde::de::Error::invalid_length(v.len(), &"a 64-character string")
|
||||
}
|
||||
})?;
|
||||
Ok(ZScript(Script(data)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ZScript {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(ZScriptVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ZScript {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(&hex::encode(&self.0 .0))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub(crate) struct ZTxOut {
|
||||
value: ZOutputValue,
|
||||
script_pubkey: ZScript,
|
||||
}
|
||||
|
||||
impl From<TxOut> for ZTxOut {
|
||||
fn from(out: TxOut) -> Self {
|
||||
ZTxOut {
|
||||
value: ZOutputValue(out.value),
|
||||
script_pubkey: ZScript(out.script_pubkey),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct Context {
|
||||
network: Option<JsonNetwork>,
|
||||
accounts: Option<Vec<JsonAccountId>>,
|
||||
pub(crate) chainhistoryroot: Option<ZUint256>,
|
||||
transparentcoins: Option<Vec<ZTxOut>>,
|
||||
}
|
||||
|
||||
impl FromStr for Context {
|
||||
type Err = serde_json::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
serde_json::from_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub(crate) fn network(&self) -> Option<Network> {
|
||||
self.network.map(|n| n.0)
|
||||
}
|
||||
|
||||
pub(crate) fn addr_network(&self) -> Option<zcash_address::Network> {
|
||||
self.network().map(|params| match params {
|
||||
Network::MainNetwork => zcash_address::Network::Main,
|
||||
Network::TestNetwork => zcash_address::Network::Test,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn accounts(&self) -> Option<Vec<AccountId>> {
|
||||
self.accounts
|
||||
.as_ref()
|
||||
.map(|accounts| accounts.iter().map(|id| id.0).collect())
|
||||
}
|
||||
|
||||
pub(crate) fn transparent_coins(&self) -> Option<Vec<transparent::TxOut>> {
|
||||
self.transparentcoins.as_ref().map(|coins| {
|
||||
coins
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|coin| transparent::TxOut {
|
||||
value: coin.value.0,
|
||||
script_pubkey: coin.script_pubkey.0,
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
use std::convert::TryInto;
|
||||
use std::iter;
|
||||
|
||||
use bech32::ToBase32;
|
||||
use secrecy::Zeroize;
|
||||
use zcash_address::{
|
||||
unified::{self, Encoding},
|
||||
ToAddress, ZcashAddress,
|
||||
};
|
||||
use zcash_primitives::{
|
||||
legacy::{
|
||||
keys::{AccountPrivKey, IncomingViewingKey},
|
||||
TransparentAddress,
|
||||
},
|
||||
zip32,
|
||||
};
|
||||
use zcash_protocol::consensus::NetworkConstants;
|
||||
|
||||
use crate::Context;
|
||||
|
||||
pub(crate) fn inspect_mnemonic(mnemonic: bip0039::Mnemonic, context: Option<Context>) {
|
||||
eprintln!("Mnemonic phrase");
|
||||
eprintln!(" - Language: English");
|
||||
|
||||
if let Some(((network, addr_net), accounts)) =
|
||||
context.and_then(|c| c.network().zip(c.addr_network()).zip(c.accounts()))
|
||||
{
|
||||
let mut seed = mnemonic.to_seed("");
|
||||
for account in accounts {
|
||||
eprintln!(" - Account {}:", u32::from(account));
|
||||
|
||||
let orchard_fvk = match orchard::keys::SpendingKey::from_zip32_seed(
|
||||
&seed,
|
||||
network.coin_type(),
|
||||
account,
|
||||
) {
|
||||
Ok(sk) => Some(orchard::keys::FullViewingKey::from(&sk)),
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
" ⚠️ No valid Orchard key for this account under this seed: {}",
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
eprintln!(" - Sapling:");
|
||||
let sapling_master = sapling::zip32::ExtendedSpendingKey::master(&seed);
|
||||
let sapling_extsk = sapling::zip32::ExtendedSpendingKey::from_path(
|
||||
&sapling_master,
|
||||
&[
|
||||
zip32::ChildIndex::hardened(32),
|
||||
zip32::ChildIndex::hardened(network.coin_type()),
|
||||
account.into(),
|
||||
],
|
||||
);
|
||||
#[allow(deprecated)]
|
||||
let sapling_extfvk = sapling_extsk.to_extended_full_viewing_key();
|
||||
let sapling_default_addr = sapling_extfvk.default_address();
|
||||
|
||||
let mut sapling_extsk_bytes = vec![];
|
||||
sapling_extsk.write(&mut sapling_extsk_bytes).unwrap();
|
||||
eprintln!(
|
||||
" - ExtSK: {}",
|
||||
bech32::encode(
|
||||
network.hrp_sapling_extended_spending_key(),
|
||||
sapling_extsk_bytes.to_base32(),
|
||||
bech32::Variant::Bech32,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut sapling_extfvk_bytes = vec![];
|
||||
sapling_extfvk.write(&mut sapling_extfvk_bytes).unwrap();
|
||||
eprintln!(
|
||||
" - ExtFVK: {}",
|
||||
bech32::encode(
|
||||
network.hrp_sapling_extended_full_viewing_key(),
|
||||
sapling_extfvk_bytes.to_base32(),
|
||||
bech32::Variant::Bech32,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let sapling_addr_bytes = sapling_default_addr.1.to_bytes();
|
||||
eprintln!(
|
||||
" - Default address: {}",
|
||||
bech32::encode(
|
||||
network.hrp_sapling_payment_address(),
|
||||
sapling_addr_bytes.to_base32(),
|
||||
bech32::Variant::Bech32,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let transparent_fvk = match AccountPrivKey::from_seed(&network, &seed, account)
|
||||
.map(|sk| sk.to_account_pubkey())
|
||||
{
|
||||
Ok(fvk) => {
|
||||
eprintln!(" - Transparent:");
|
||||
match fvk.derive_external_ivk().map(|ivk| ivk.default_address().0) {
|
||||
Ok(addr) => eprintln!(
|
||||
" - Default address: {}",
|
||||
match addr {
|
||||
TransparentAddress::PublicKeyHash(data) => ZcashAddress::from_transparent_p2pkh(addr_net, data),
|
||||
TransparentAddress::ScriptHash(_) => unreachable!(),
|
||||
}.encode(),
|
||||
),
|
||||
Err(e) => eprintln!(
|
||||
" ⚠️ No valid transparent default address for this account under this seed: {:?}",
|
||||
e
|
||||
),
|
||||
}
|
||||
|
||||
Some(fvk)
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
" ⚠️ No valid transparent key for this account under this seed: {:?}",
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let items: Vec<_> = iter::empty()
|
||||
.chain(
|
||||
orchard_fvk
|
||||
.map(|fvk| fvk.to_bytes())
|
||||
.map(unified::Fvk::Orchard),
|
||||
)
|
||||
.chain(Some(unified::Fvk::Sapling(
|
||||
sapling_extfvk_bytes[41..].try_into().unwrap(),
|
||||
)))
|
||||
.chain(
|
||||
transparent_fvk
|
||||
.map(|fvk| fvk.serialize()[..].try_into().unwrap())
|
||||
.map(unified::Fvk::P2pkh),
|
||||
)
|
||||
.collect();
|
||||
let item_names: Vec<_> = items
|
||||
.iter()
|
||||
.map(|item| match item {
|
||||
unified::Fvk::Orchard(_) => "Orchard",
|
||||
unified::Fvk::Sapling(_) => "Sapling",
|
||||
unified::Fvk::P2pkh(_) => "Transparent",
|
||||
unified::Fvk::Unknown { .. } => unreachable!(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
eprintln!(" - Unified ({}):", item_names.join(", "));
|
||||
let ufvk = unified::Ufvk::try_from_items(items).unwrap();
|
||||
eprintln!(" - UFVK: {}", ufvk.encode(&addr_net));
|
||||
}
|
||||
seed.zeroize();
|
||||
} else {
|
||||
eprintln!("🔎 To show account details, add \"network\" (either \"main\" or \"test\") and \"accounts\" array to context");
|
||||
}
|
||||
|
||||
eprintln!();
|
||||
eprintln!(
|
||||
"WARNING: This mnemonic phrase is now likely cached in your terminal's history buffer."
|
||||
);
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
use std::env;
|
||||
use std::io;
|
||||
use std::io::Cursor;
|
||||
use std::process;
|
||||
|
||||
use gumdrop::{Options, ParsingStyle};
|
||||
use lazy_static::lazy_static;
|
||||
use secrecy::Zeroize;
|
||||
use zcash_address::ZcashAddress;
|
||||
use zcash_primitives::{block::BlockHeader, consensus::BranchId, transaction::Transaction};
|
||||
use zcash_proofs::{default_params_folder, load_parameters, ZcashParameters};
|
||||
|
||||
mod context;
|
||||
use context::{Context, ZUint256};
|
||||
|
||||
mod address;
|
||||
mod block;
|
||||
mod keys;
|
||||
mod transaction;
|
||||
|
||||
lazy_static! {
|
||||
static ref GROTH16_PARAMS: ZcashParameters = {
|
||||
let folder = default_params_folder().unwrap();
|
||||
load_parameters(
|
||||
&folder.join("sapling-spend.params"),
|
||||
&folder.join("sapling-output.params"),
|
||||
Some(&folder.join("sprout-groth16.params")),
|
||||
)
|
||||
};
|
||||
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(free, required, help = "String or hex-encoded bytes to inspect")]
|
||||
data: String,
|
||||
|
||||
#[options(
|
||||
free,
|
||||
help = "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);
|
||||
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);
|
||||
} else if let Ok(addr) = ZcashAddress::try_from_encoded(&opts.data) {
|
||||
address::inspect(addr);
|
||||
} else {
|
||||
// Unknown data format.
|
||||
eprintln!("String does not match known Zcash data formats.");
|
||||
process::exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that the given reader completely consumes the given bytes.
|
||||
fn complete<F, T>(bytes: &[u8], f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&mut Cursor<&[u8]>) -> io::Result<T>,
|
||||
{
|
||||
let mut cursor = Cursor::new(bytes);
|
||||
let res = f(&mut cursor);
|
||||
res.ok().and_then(|t| {
|
||||
if cursor.position() >= bytes.len() as u64 {
|
||||
Some(t)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn inspect_bytes(bytes: Vec<u8>, context: Option<Context>) {
|
||||
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)) {
|
||||
block::inspect_header(&header, context);
|
||||
} 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);
|
||||
} else {
|
||||
// It's not a known variable-length format. check fixed-length data formats.
|
||||
inspect_fixed_length_bytes(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
fn inspect_fixed_length_bytes(bytes: Vec<u8>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,563 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
};
|
||||
|
||||
use bellman::groth16;
|
||||
use group::GroupEncoding;
|
||||
use orchard::note_encryption::OrchardDomain;
|
||||
use sapling::{note_encryption::SaplingDomain, SaplingVerificationContext};
|
||||
use secp256k1::{Secp256k1, VerifyOnly};
|
||||
use zcash_address::{
|
||||
unified::{self, Encoding},
|
||||
ToAddress, ZcashAddress,
|
||||
};
|
||||
use zcash_note_encryption::try_output_recovery_with_ovk;
|
||||
#[allow(deprecated)]
|
||||
use zcash_primitives::{
|
||||
consensus::BlockHeight,
|
||||
legacy::{keys::pubkey_to_address, Script, TransparentAddress},
|
||||
memo::{Memo, MemoBytes},
|
||||
transaction::{
|
||||
components::{amount::NonNegativeAmount, sapling as sapling_serialization, transparent},
|
||||
sighash::{signature_hash, SignableInput, TransparentAuthorizingContext},
|
||||
txid::TxIdDigester,
|
||||
Authorization, Transaction, TransactionData, TxId, TxVersion,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
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())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn extract_height_from_coinbase(tx: &Transaction) -> Option<BlockHeight> {
|
||||
const OP_0: u8 = 0x00;
|
||||
const OP_1NEGATE: u8 = 0x4f;
|
||||
const OP_1: u8 = 0x51;
|
||||
const OP_16: u8 = 0x60;
|
||||
|
||||
tx.transparent_bundle()
|
||||
.and_then(|bundle| bundle.vin.first())
|
||||
.and_then(|input| match input.script_sig.0.first().copied() {
|
||||
// {0, -1} will never occur as the first byte of a coinbase scriptSig.
|
||||
Some(OP_0 | OP_1NEGATE) => None,
|
||||
// Blocks 1 to 16.
|
||||
Some(h @ OP_1..=OP_16) => Some(BlockHeight::from_u32((h - OP_1 + 1).into())),
|
||||
// All other heights use CScriptNum encoding, which will never be longer
|
||||
// than 5 bytes for Zcash heights. These have the format
|
||||
// `[len(encoding)] || encoding`.
|
||||
Some(h @ 1..=5) => {
|
||||
let rest = &input.script_sig.0[1..];
|
||||
let encoding_len = h as usize;
|
||||
if rest.len() < encoding_len {
|
||||
None
|
||||
} else {
|
||||
// Parse the encoding.
|
||||
let encoding = &rest[..encoding_len];
|
||||
if encoding.last().unwrap() & 0x80 != 0 {
|
||||
// Height is never negative.
|
||||
None
|
||||
} else {
|
||||
let mut height: u64 = 0;
|
||||
for (i, b) in encoding.iter().enumerate() {
|
||||
height |= (*b as u64) << (8 * i);
|
||||
}
|
||||
height.try_into().ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
// Anything else is an invalid height encoding.
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn render_value(value: u64) -> String {
|
||||
format!(
|
||||
"{} zatoshis ({} ZEC)",
|
||||
value,
|
||||
(value as f64) / 1_0000_0000f64
|
||||
)
|
||||
}
|
||||
|
||||
fn render_memo(memo_bytes: MemoBytes) -> String {
|
||||
match Memo::try_from(memo_bytes) {
|
||||
Ok(Memo::Empty) => "No memo".to_string(),
|
||||
Ok(Memo::Text(memo)) => format!("Text memo: '{}'", String::from(memo)),
|
||||
Ok(memo) => format!("{:?}", memo),
|
||||
Err(e) => format!("Invalid memo: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct TransparentAuth {
|
||||
all_prev_outputs: Vec<transparent::TxOut>,
|
||||
}
|
||||
|
||||
impl transparent::Authorization for TransparentAuth {
|
||||
type ScriptSig = Script;
|
||||
}
|
||||
|
||||
impl TransparentAuthorizingContext for TransparentAuth {
|
||||
fn input_amounts(&self) -> Vec<NonNegativeAmount> {
|
||||
self.all_prev_outputs
|
||||
.iter()
|
||||
.map(|prevout| prevout.value)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn input_scriptpubkeys(&self) -> Vec<Script> {
|
||||
self.all_prev_outputs
|
||||
.iter()
|
||||
.map(|prevout| prevout.script_pubkey.clone())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
struct MapTransparent {
|
||||
auth: TransparentAuth,
|
||||
}
|
||||
|
||||
impl transparent::MapAuth<transparent::Authorized, TransparentAuth> for MapTransparent {
|
||||
fn map_script_sig(
|
||||
&self,
|
||||
s: <transparent::Authorized as transparent::Authorization>::ScriptSig,
|
||||
) -> <TransparentAuth as transparent::Authorization>::ScriptSig {
|
||||
s
|
||||
}
|
||||
|
||||
fn map_authorization(&self, _: transparent::Authorized) -> TransparentAuth {
|
||||
// TODO: This map should consume self, so we can move self.auth
|
||||
self.auth.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PrecomputedAuth;
|
||||
|
||||
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(tx: Transaction, context: Option<Context>) {
|
||||
eprintln!("Zcash transaction");
|
||||
eprintln!(" - ID: {}", tx.txid());
|
||||
eprintln!(" - Version: {:?}", tx.version());
|
||||
match tx.version() {
|
||||
// TODO: If pre-v5 and no branch ID provided in context, disable signature checks.
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => (),
|
||||
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);
|
||||
let height = if is_coinbase {
|
||||
eprintln!(" - Coinbase");
|
||||
extract_height_from_coinbase(&tx)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let transparent_coins = match (
|
||||
tx.transparent_bundle().map_or(false, |b| !b.vin.is_empty()),
|
||||
context.as_ref().and_then(|ctx| ctx.transparent_coins()),
|
||||
) {
|
||||
(true, coins) => coins,
|
||||
(false, Some(_)) => {
|
||||
eprintln!("⚠️ Context was given \"transparentcoins\" but this transaction has no transparent inputs");
|
||||
Some(vec![])
|
||||
}
|
||||
(false, None) => Some(vec![]),
|
||||
};
|
||||
|
||||
let sighash_params = transparent_coins.as_ref().map(|coins| {
|
||||
let f_transparent = MapTransparent {
|
||||
auth: TransparentAuth {
|
||||
all_prev_outputs: coins.clone(),
|
||||
},
|
||||
};
|
||||
|
||||
// We don't have tx.clone()
|
||||
let mut buf = vec![];
|
||||
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 txid_parts = tx.digest(TxIdDigester);
|
||||
(tx, txid_parts)
|
||||
});
|
||||
|
||||
let common_sighash = sighash_params
|
||||
.as_ref()
|
||||
.map(|(tx, txid_parts)| signature_hash(tx, &SignableInput::Shielded, txid_parts));
|
||||
if let Some(sighash) = &common_sighash {
|
||||
if tx.sprout_bundle().is_some()
|
||||
|| tx.sapling_bundle().is_some()
|
||||
|| tx.orchard_bundle().is_some()
|
||||
{
|
||||
eprintln!(
|
||||
" - Sighash for shielded signatures: {}",
|
||||
hex::encode(sighash.as_ref()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(bundle) = tx.transparent_bundle() {
|
||||
assert!(!(bundle.vin.is_empty() && bundle.vout.is_empty()));
|
||||
if !(bundle.vin.is_empty() || is_coinbase) {
|
||||
eprintln!(" - {} transparent input(s)", bundle.vin.len());
|
||||
if let Some(coins) = &transparent_coins {
|
||||
if bundle.vin.len() != coins.len() {
|
||||
eprintln!(" ⚠️ \"transparentcoins\" has length {}", coins.len());
|
||||
}
|
||||
|
||||
let (tx, txid_parts) = sighash_params.as_ref().unwrap();
|
||||
let ctx = Secp256k1::<VerifyOnly>::gen_new();
|
||||
|
||||
for (i, (txin, coin)) in bundle.vin.iter().zip(coins).enumerate() {
|
||||
eprintln!(
|
||||
" - prevout: txid {}, index {}",
|
||||
TxId::from_bytes(*txin.prevout.hash()),
|
||||
txin.prevout.n()
|
||||
);
|
||||
match coin.recipient_address() {
|
||||
Some(addr @ TransparentAddress::PublicKeyHash(_)) => {
|
||||
// Format is [sig_and_type_len] || sig || [hash_type] || [pubkey_len] || pubkey
|
||||
// where [x] encodes a single byte.
|
||||
let sig_and_type_len = txin.script_sig.0.first().map(|l| *l as usize);
|
||||
let pubkey_len = sig_and_type_len
|
||||
.and_then(|sig_len| txin.script_sig.0.get(1 + sig_len))
|
||||
.map(|l| *l as usize);
|
||||
let script_len = sig_and_type_len.zip(pubkey_len).map(
|
||||
|(sig_and_type_len, pubkey_len)| {
|
||||
1 + sig_and_type_len + 1 + pubkey_len
|
||||
},
|
||||
);
|
||||
|
||||
if Some(txin.script_sig.0.len()) != script_len {
|
||||
eprintln!(
|
||||
" ⚠️ \"transparentcoins\" {} is P2PKH; txin {} scriptSig has length {} but data {}",
|
||||
i,
|
||||
i,
|
||||
txin.script_sig.0.len(),
|
||||
if let Some(l) = script_len {
|
||||
format!("implies length {}.", l)
|
||||
} else {
|
||||
"would cause an out-of-bounds read.".to_owned()
|
||||
},
|
||||
);
|
||||
} else {
|
||||
let sig_len = sig_and_type_len.unwrap() - 1;
|
||||
|
||||
let sig = secp256k1::ecdsa::Signature::from_der(
|
||||
&txin.script_sig.0[1..1 + sig_len],
|
||||
);
|
||||
let hash_type = txin.script_sig.0[1 + sig_len];
|
||||
let pubkey_bytes = &txin.script_sig.0[1 + sig_len + 2..];
|
||||
let pubkey = secp256k1::PublicKey::from_slice(pubkey_bytes);
|
||||
|
||||
if let Err(e) = sig {
|
||||
eprintln!(
|
||||
" ⚠️ Txin {} has invalid signature encoding: {}",
|
||||
i, e
|
||||
);
|
||||
}
|
||||
if let Err(e) = pubkey {
|
||||
eprintln!(
|
||||
" ⚠️ Txin {} has invalid pubkey encoding: {}",
|
||||
i, e
|
||||
);
|
||||
}
|
||||
if let (Ok(sig), Ok(pubkey)) = (sig, pubkey) {
|
||||
#[allow(deprecated)]
|
||||
if pubkey_to_address(&pubkey) != addr {
|
||||
eprintln!(" ⚠️ Txin {} pubkey does not match coin's script_pubkey", i);
|
||||
}
|
||||
|
||||
let sighash = signature_hash(
|
||||
tx,
|
||||
&SignableInput::Transparent {
|
||||
hash_type,
|
||||
index: i,
|
||||
// For P2PKH these are the same.
|
||||
script_code: &coin.script_pubkey,
|
||||
script_pubkey: &coin.script_pubkey,
|
||||
value: coin.value,
|
||||
},
|
||||
txid_parts,
|
||||
);
|
||||
let msg = secp256k1::Message::from_slice(sighash.as_ref())
|
||||
.expect("signature_hash() returns correct length");
|
||||
|
||||
if let Err(e) = ctx.verify_ecdsa(&msg, &sig, &pubkey) {
|
||||
eprintln!(" ⚠️ Spend {} is invalid: {}", i, e);
|
||||
eprintln!(
|
||||
" - sighash is {}",
|
||||
hex::encode(sighash.as_ref())
|
||||
);
|
||||
eprintln!(" - pubkey is {}", hex::encode(pubkey_bytes));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Check P2SH structure.
|
||||
Some(TransparentAddress::ScriptHash(_)) => {
|
||||
eprintln!(" 🔎 \"transparentcoins\"[{}] is a P2SH coin.", i);
|
||||
}
|
||||
// TODO: Check arbitrary scripts.
|
||||
None => {
|
||||
eprintln!(
|
||||
" 🔎 \"transparentcoins\"[{}] has a script we can't check yet.",
|
||||
i
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!(
|
||||
" 🔎 To check transparent inputs, add \"transparentcoins\" array to context."
|
||||
);
|
||||
eprintln!(" The following transparent inputs are required: ");
|
||||
for txin in &bundle.vin {
|
||||
eprintln!(
|
||||
" - txid {}, index {}",
|
||||
TxId::from_bytes(*txin.prevout.hash()),
|
||||
txin.prevout.n()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !bundle.vout.is_empty() {
|
||||
eprintln!(" - {} transparent output(s)", bundle.vout.len());
|
||||
for txout in &bundle.vout {
|
||||
eprintln!(
|
||||
" - {}",
|
||||
serde_json::to_string(&ZTxOut::from(txout.clone())).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(bundle) = tx.sprout_bundle() {
|
||||
eprintln!(" - {} Sprout JoinSplit(s)", bundle.joinsplits.len());
|
||||
|
||||
// TODO: Verify Sprout proofs once we can access the Sprout bundle parts.
|
||||
|
||||
match ed25519_zebra::VerificationKey::try_from(bundle.joinsplit_pubkey) {
|
||||
Err(e) => eprintln!(" ⚠️ joinsplitPubkey is invalid: {}", e),
|
||||
Ok(vk) => {
|
||||
if let Some(sighash) = &common_sighash {
|
||||
if let Err(e) = vk.verify(
|
||||
&ed25519_zebra::Signature::from(bundle.joinsplit_sig),
|
||||
sighash.as_ref(),
|
||||
) {
|
||||
eprintln!(" ⚠️ joinsplitSig is invalid: {}", e);
|
||||
}
|
||||
} else {
|
||||
eprintln!(
|
||||
" 🔎 To check Sprout JoinSplit(s), add \"transparentcoins\" array to context"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(bundle) = tx.sapling_bundle() {
|
||||
assert!(!(bundle.shielded_spends().is_empty() && bundle.shielded_outputs().is_empty()));
|
||||
|
||||
// TODO: Separate into checking proofs, signatures, and other structural details.
|
||||
let mut ctx = SaplingVerificationContext::new();
|
||||
|
||||
if !bundle.shielded_spends().is_empty() {
|
||||
eprintln!(" - {} Sapling Spend(s)", bundle.shielded_spends().len());
|
||||
if let Some(sighash) = &common_sighash {
|
||||
for (i, spend) in bundle.shielded_spends().iter().enumerate() {
|
||||
if !ctx.check_spend(
|
||||
spend.cv(),
|
||||
*spend.anchor(),
|
||||
&spend.nullifier().0,
|
||||
*spend.rk(),
|
||||
sighash.as_ref(),
|
||||
*spend.spend_auth_sig(),
|
||||
groth16::Proof::read(&spend.zkproof()[..]).unwrap(),
|
||||
&GROTH16_PARAMS.spend_vk,
|
||||
) {
|
||||
eprintln!(" ⚠️ Spend {} is invalid", i);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!(
|
||||
" 🔎 To check Sapling Spend(s), add \"transparentcoins\" array to context"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if !bundle.shielded_outputs().is_empty() {
|
||||
eprintln!(" - {} Sapling Output(s)", bundle.shielded_outputs().len());
|
||||
for (i, output) in bundle.shielded_outputs().iter().enumerate() {
|
||||
if is_coinbase {
|
||||
if let Some((params, addr_net)) = context
|
||||
.as_ref()
|
||||
.and_then(|ctx| ctx.network().zip(ctx.addr_network()))
|
||||
{
|
||||
if let Some((note, addr, memo)) = try_output_recovery_with_ovk(
|
||||
&SaplingDomain::new(sapling_serialization::zip212_enforcement(
|
||||
¶ms,
|
||||
height.unwrap(),
|
||||
)),
|
||||
&sapling::keys::OutgoingViewingKey([0; 32]),
|
||||
output,
|
||||
output.cv(),
|
||||
output.out_ciphertext(),
|
||||
) {
|
||||
if note.value().inner() == 0 {
|
||||
eprintln!(" - Output {} (dummy output):", i);
|
||||
} else {
|
||||
let zaddr = ZcashAddress::from_sapling(addr_net, addr.to_bytes());
|
||||
|
||||
eprintln!(" - Output {}:", i);
|
||||
eprintln!(" - {}", zaddr);
|
||||
eprintln!(" - {}", render_value(note.value().inner()));
|
||||
}
|
||||
let memo = MemoBytes::from_bytes(&memo).expect("correct length");
|
||||
eprintln!(" - {}", render_memo(memo));
|
||||
} else {
|
||||
eprintln!(
|
||||
" ⚠️ Output {} is not recoverable with the all-zeros OVK",
|
||||
i
|
||||
);
|
||||
}
|
||||
} else {
|
||||
eprintln!(" 🔎 To check Sapling coinbase rules, add \"network\" to context (either \"main\" or \"test\")");
|
||||
}
|
||||
}
|
||||
|
||||
if !ctx.check_output(
|
||||
output.cv(),
|
||||
*output.cmu(),
|
||||
jubjub::ExtendedPoint::from_bytes(&output.ephemeral_key().0).unwrap(),
|
||||
groth16::Proof::read(&output.zkproof()[..]).unwrap(),
|
||||
&GROTH16_PARAMS.output_vk,
|
||||
) {
|
||||
eprintln!(" ⚠️ Output {} is invalid", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(sighash) = &common_sighash {
|
||||
if !ctx.final_check(
|
||||
*bundle.value_balance(),
|
||||
sighash.as_ref(),
|
||||
bundle.authorization().binding_sig,
|
||||
) {
|
||||
eprintln!("⚠️ Sapling bindingSig is invalid");
|
||||
}
|
||||
} else {
|
||||
eprintln!("🔎 To check Sapling bindingSig, add \"transparentcoins\" array to context");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(bundle) = tx.orchard_bundle() {
|
||||
eprintln!(" - {} Orchard Action(s)", bundle.actions().len());
|
||||
|
||||
// Orchard nullifiers must not be duplicated within a transaction.
|
||||
let mut nullifiers = HashMap::<[u8; 32], Vec<usize>>::default();
|
||||
for (i, action) in bundle.actions().iter().enumerate() {
|
||||
nullifiers
|
||||
.entry(action.nullifier().to_bytes())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(i);
|
||||
}
|
||||
for (_, indices) in nullifiers {
|
||||
if indices.len() > 1 {
|
||||
eprintln!("⚠️ Nullifier is duplicated between actions {:?}", indices);
|
||||
}
|
||||
}
|
||||
|
||||
if is_coinbase {
|
||||
// All coinbase outputs must be decryptable with the all-zeroes OVK.
|
||||
for (i, action) in bundle.actions().iter().enumerate() {
|
||||
let ovk = orchard::keys::OutgoingViewingKey::from([0u8; 32]);
|
||||
if let Some((note, addr, memo)) = try_output_recovery_with_ovk(
|
||||
&OrchardDomain::for_action(action),
|
||||
&ovk,
|
||||
action,
|
||||
action.cv_net(),
|
||||
&action.encrypted_note().out_ciphertext,
|
||||
) {
|
||||
if note.value().inner() == 0 {
|
||||
eprintln!(" - Output {} (dummy output):", i);
|
||||
} else {
|
||||
eprintln!(" - Output {}:", i);
|
||||
|
||||
if let Some(net) = context.as_ref().and_then(|ctx| ctx.addr_network()) {
|
||||
assert_eq!(note.recipient(), addr);
|
||||
// Construct a single-receiver UA.
|
||||
let zaddr = ZcashAddress::from_unified(
|
||||
net,
|
||||
unified::Address::try_from_items(vec![unified::Receiver::Orchard(
|
||||
addr.to_raw_address_bytes(),
|
||||
)])
|
||||
.unwrap(),
|
||||
);
|
||||
eprintln!(" - {}", zaddr);
|
||||
} else {
|
||||
eprintln!(" 🔎 To show recipient address, add \"network\" to context (either \"main\" or \"test\")");
|
||||
}
|
||||
|
||||
eprintln!(" - {}", render_value(note.value().inner()));
|
||||
}
|
||||
eprintln!(
|
||||
" - {}",
|
||||
render_memo(MemoBytes::from_bytes(&memo).unwrap())
|
||||
);
|
||||
} else {
|
||||
eprintln!(
|
||||
" ⚠️ Output {} is not recoverable with the all-zeros OVK",
|
||||
i
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(sighash) = &common_sighash {
|
||||
for (i, action) in bundle.actions().iter().enumerate() {
|
||||
if let Err(e) = action.rk().verify(sighash.as_ref(), action.authorization()) {
|
||||
eprintln!(" ⚠️ Action {} spendAuthSig is invalid: {}", i, e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!(
|
||||
"🔎 To check Orchard Action signatures, add \"transparentcoins\" array to context"
|
||||
);
|
||||
}
|
||||
|
||||
if let Err(e) = bundle.verify_proof(&ORCHARD_VK) {
|
||||
eprintln!("⚠️ Orchard proof is invalid: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -191,6 +191,10 @@ criteria = "safe-to-deploy"
|
|||
version = "0.14.0"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.bip0039]]
|
||||
version = "0.11.0"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.bitflags]]
|
||||
version = "1.3.2"
|
||||
criteria = "safe-to-run"
|
||||
|
@ -287,10 +291,6 @@ criteria = "safe-to-deploy"
|
|||
version = "0.9.6"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.cpp_demangle]]
|
||||
version = "0.4.3"
|
||||
criteria = "safe-to-run"
|
||||
|
||||
[[exemptions.cpufeatures]]
|
||||
version = "0.2.11"
|
||||
criteria = "safe-to-deploy"
|
||||
|
@ -335,6 +335,14 @@ criteria = "safe-to-deploy"
|
|||
version = "3.2.0"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.curve25519-dalek]]
|
||||
version = "4.1.0"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.curve25519-dalek-derive]]
|
||||
version = "0.1.0"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.daggy]]
|
||||
version = "0.8.0"
|
||||
criteria = "safe-to-deploy"
|
||||
|
@ -423,10 +431,18 @@ criteria = "safe-to-deploy"
|
|||
version = "1.4.1"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.ed25519]]
|
||||
version = "2.2.1"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.ed25519-dalek]]
|
||||
version = "1.0.1"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.ed25519-zebra]]
|
||||
version = "3.0.0"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.educe]]
|
||||
version = "0.4.23"
|
||||
criteria = "safe-to-deploy"
|
||||
|
@ -533,11 +549,11 @@ criteria = "safe-to-deploy"
|
|||
|
||||
[[exemptions.gumdrop]]
|
||||
version = "0.8.1"
|
||||
criteria = "safe-to-run"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.gumdrop_derive]]
|
||||
version = "0.8.1"
|
||||
criteria = "safe-to-run"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.h2]]
|
||||
version = "0.3.21"
|
||||
|
@ -763,6 +779,10 @@ criteria = "safe-to-deploy"
|
|||
version = "0.9.10"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.password-hash]]
|
||||
version = "0.3.2"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.pasta_curves]]
|
||||
version = "0.5.1"
|
||||
criteria = "safe-to-deploy"
|
||||
|
@ -775,6 +795,10 @@ criteria = "safe-to-deploy"
|
|||
version = "0.2.1"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.pbkdf2]]
|
||||
version = "0.9.0"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.pem-rfc7468]]
|
||||
version = "0.7.0"
|
||||
criteria = "safe-to-deploy"
|
||||
|
|
|
@ -72,6 +72,13 @@ user-id = 169181
|
|||
user-login = "nuttycom"
|
||||
user-name = "Kris Nuttycombe"
|
||||
|
||||
[[publisher.unicode-normalization]]
|
||||
version = "0.1.23"
|
||||
when = "2024-02-20"
|
||||
user-id = 1139
|
||||
user-login = "Manishearth"
|
||||
user-name = "Manish Goregaokar"
|
||||
|
||||
[[publisher.windows-sys]]
|
||||
version = "0.48.0"
|
||||
when = "2023-03-31"
|
||||
|
@ -951,6 +958,12 @@ Previously reviewed during security review and the audit is grandparented in.
|
|||
"""
|
||||
aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT"
|
||||
|
||||
[[audits.google.audits.cpp_demangle]]
|
||||
who = "Hidenori Kobayashi <hidenorik@chromium.org>"
|
||||
criteria = "safe-to-run"
|
||||
version = "0.4.3"
|
||||
aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT"
|
||||
|
||||
[[audits.google.audits.equivalent]]
|
||||
who = "George Burgess IV <gbiv@google.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
@ -1099,6 +1112,18 @@ 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.password-hash]]
|
||||
who = "Joshua Liebow-Feeser <joshlf@google.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.3.2 -> 0.4.2"
|
||||
aggregated-from = "https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/third_party/rust_crates/supply-chain/audits.toml?format=TEXT"
|
||||
|
||||
[[audits.google.audits.pbkdf2]]
|
||||
who = "Joshua Liebow-Feeser <joshlf@google.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.9.0 -> 0.11.0"
|
||||
aggregated-from = "https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/third_party/rust_crates/supply-chain/audits.toml?format=TEXT"
|
||||
|
||||
[[audits.google.audits.pin-project-lite]]
|
||||
who = "David Koloski <dkoloski@google.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
@ -1497,6 +1522,86 @@ who = "David Cook <dcook@divviup.org>"
|
|||
criteria = "safe-to-deploy"
|
||||
version = "1.6.1"
|
||||
|
||||
[[audits.isrg.audits.fiat-crypto]]
|
||||
who = "David Cook <dcook@divviup.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
version = "0.1.17"
|
||||
notes = """
|
||||
This crate does not contain any unsafe code, and does not use any items from
|
||||
the standard library or other crates, aside from operations backed by
|
||||
`std::ops`. All paths with array indexing use integer literals for indexes, so
|
||||
there are no panics due to indexes out of bounds (as rustc would catch an
|
||||
out-of-bounds literal index). I did not check whether arithmetic overflows
|
||||
could cause a panic, and I am relying on the Coq code having satisfied the
|
||||
necessary preconditions to ensure panics due to overflows are unreachable.
|
||||
"""
|
||||
|
||||
[[audits.isrg.audits.fiat-crypto]]
|
||||
who = "Brandon Pitman <bran@bran.land>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.1.17 -> 0.1.18"
|
||||
|
||||
[[audits.isrg.audits.fiat-crypto]]
|
||||
who = "David Cook <dcook@divviup.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.1.18 -> 0.1.19"
|
||||
notes = """
|
||||
This release renames many items and adds a new module. The code in the new
|
||||
module is entirely composed of arithmetic and array accesses.
|
||||
"""
|
||||
|
||||
[[audits.isrg.audits.fiat-crypto]]
|
||||
who = "David Cook <dcook@divviup.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.1.19 -> 0.1.20"
|
||||
|
||||
[[audits.isrg.audits.fiat-crypto]]
|
||||
who = "David Cook <dcook@divviup.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.1.20 -> 0.2.0"
|
||||
|
||||
[[audits.isrg.audits.fiat-crypto]]
|
||||
who = "Brandon Pitman <bran@bran.land>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.2.0 -> 0.2.1"
|
||||
|
||||
[[audits.isrg.audits.fiat-crypto]]
|
||||
who = "Tim Geoghegan <timg@divviup.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.2.1 -> 0.2.2"
|
||||
notes = "No changes to `unsafe` code, or any functional changes that I can detect at all."
|
||||
|
||||
[[audits.isrg.audits.fiat-crypto]]
|
||||
who = "Brandon Pitman <bran@bran.land>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.2.2 -> 0.2.4"
|
||||
|
||||
[[audits.isrg.audits.fiat-crypto]]
|
||||
who = "David Cook <dcook@divviup.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.2.4 -> 0.2.5"
|
||||
|
||||
[[audits.isrg.audits.fiat-crypto]]
|
||||
who = "Brandon Pitman <bran@bran.land>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.2.5 -> 0.2.6"
|
||||
|
||||
[[audits.isrg.audits.fiat-crypto]]
|
||||
who = "Brandon Pitman <bran@bran.land>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.2.6 -> 0.2.7"
|
||||
|
||||
[[audits.isrg.audits.fiat-crypto]]
|
||||
who = "David Cook <dcook@divviup.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.2.7 -> 0.2.8"
|
||||
|
||||
[[audits.isrg.audits.fiat-crypto]]
|
||||
who = "Tim Geoghegan <timg@divviup.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.2.8 -> 0.2.9"
|
||||
notes = "No changes to Rust code between 0.2.8 and 0.2.9"
|
||||
|
||||
[[audits.isrg.audits.getrandom]]
|
||||
who = "David Cook <dcook@divviup.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
@ -1682,6 +1787,15 @@ renew = false
|
|||
notes = "I've reviewed every source contribution that was neither authored nor reviewed by Mozilla."
|
||||
aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
|
||||
|
||||
[[audits.mozilla.wildcard-audits.unicode-normalization]]
|
||||
who = "Manish Goregaokar <manishsmail@gmail.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
user-id = 1139 # Manish Goregaokar (Manishearth)
|
||||
start = "2019-11-06"
|
||||
end = "2024-05-03"
|
||||
notes = "All code written or reviewed by Manish"
|
||||
aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
|
||||
|
||||
[[audits.mozilla.audits.ahash]]
|
||||
who = "Erich Gubler <erichdongubler@gmail.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
@ -2482,6 +2596,36 @@ criteria = "safe-to-deploy"
|
|||
delta = "0.9.17 -> 0.9.18"
|
||||
aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml"
|
||||
|
||||
[[audits.zcash.audits.curve25519-dalek]]
|
||||
who = "Jack Grigg <jack@electriccoin.co>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "4.1.0 -> 4.1.1"
|
||||
aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml"
|
||||
|
||||
[[audits.zcash.audits.curve25519-dalek]]
|
||||
who = "Daira-Emma Hopwood <daira@jacaranda.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "4.1.1 -> 4.1.2"
|
||||
aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml"
|
||||
|
||||
[[audits.zcash.audits.curve25519-dalek]]
|
||||
who = "Jack Grigg <jack@electriccoin.co>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "4.1.2 -> 4.1.3"
|
||||
notes = """
|
||||
- New unsafe is adding `core::ptr::read_volatile` calls for black box
|
||||
optimization barriers.
|
||||
- `build.rs` changes are to use `CARGO_CFG_TARGET_POINTER_WIDTH` instead of
|
||||
`TARGET` and the `platforms` crate for deciding on the target pointer width.
|
||||
"""
|
||||
aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml"
|
||||
|
||||
[[audits.zcash.audits.curve25519-dalek-derive]]
|
||||
who = "Jack Grigg <jack@electriccoin.co>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.1.0 -> 0.1.1"
|
||||
aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml"
|
||||
|
||||
[[audits.zcash.audits.der]]
|
||||
who = "Daira-Emma Hopwood <daira@jacaranda.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
@ -2489,6 +2633,57 @@ delta = "0.7.8 -> 0.7.9"
|
|||
notes = "The change to ignore RUSTSEC-2023-0071 is correct for this crate."
|
||||
aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml"
|
||||
|
||||
[[audits.zcash.audits.ed25519]]
|
||||
who = "Jack Grigg <jack@electriccoin.co>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "2.2.1 -> 2.2.2"
|
||||
aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml"
|
||||
|
||||
[[audits.zcash.audits.ed25519]]
|
||||
who = "Jack Grigg <jack@electriccoin.co>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "2.2.2 -> 2.2.3"
|
||||
aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml"
|
||||
|
||||
[[audits.zcash.audits.ed25519-zebra]]
|
||||
who = "Jack Grigg <jack@z.cash>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "3.0.0 -> 3.1.0"
|
||||
aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml"
|
||||
|
||||
[[audits.zcash.audits.ed25519-zebra]]
|
||||
who = "Daira Emma Hopwood <daira@jacaranda.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "3.1.0 -> 4.0.0"
|
||||
notes = """
|
||||
Changes are mainly in the pem and pkcs8 features and in Java or Scala code. These do not introduce unsafe code,
|
||||
but I cannot vouch for their cryptographic correctness or conformance to PEM or PKCS8 standards. I reviewed the
|
||||
remaining changes from 3.1.0 to 4.0.0 fully.
|
||||
"""
|
||||
aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml"
|
||||
|
||||
[[audits.zcash.audits.ed25519-zebra]]
|
||||
who = "Jack Grigg <jack@electriccoin.co>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "4.0.0 -> 4.0.3"
|
||||
notes = """
|
||||
`SigningKey::from([u8; 32])` parsing now uses `Scalar::from_bytes_mod_order` instead of
|
||||
`Scalar::from_bits`. This means that the clamped scalar bits are now reduced before they
|
||||
are used, which removes the implicit mul-by-cofactor during scalar multiplication (as the
|
||||
last 3 bits of the scalar are no longer guaranteed to be zero). However, this happens to
|
||||
be fine in the context of this crate:
|
||||
|
||||
- `SigningKey` does not expose its inner `Scalar` directly, so we only need to consider
|
||||
how it is used within the crate.
|
||||
- For multiplication within a prime-order (sub)group, we get the same result whether we
|
||||
reduce before or not. This means that the field-element multiplication during signing,
|
||||
and the prime-order subgroup component of any group-element scalar multiplication, are
|
||||
unaffected.
|
||||
- The only group element that the `Scalar` is multiplied by is the Ed25519 basepoint,
|
||||
which is torsion free (so the implicit mul-by-cofactor is unnecessary).
|
||||
"""
|
||||
aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml"
|
||||
|
||||
[[audits.zcash.audits.either]]
|
||||
who = "Jack Grigg <jack@electriccoin.co>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
|
|
@ -32,6 +32,25 @@ pub trait MapAuth<A: Authorization, B: Authorization> {
|
|||
fn map_authorization(&self, s: A) -> B;
|
||||
}
|
||||
|
||||
/// The identity map.
|
||||
///
|
||||
/// This can be used with [`TransactionData::map_authorization`] when you want to map the
|
||||
/// authorization of a subset of the transaction's bundles.
|
||||
///
|
||||
/// [`TransactionData::map_authorization`]: crate::transaction::TransactionData::map_authorization
|
||||
impl MapAuth<Authorized, Authorized> for () {
|
||||
fn map_witness(
|
||||
&self,
|
||||
s: <Authorized as Authorization>::Witness,
|
||||
) -> <Authorized as Authorization>::Witness {
|
||||
s
|
||||
}
|
||||
|
||||
fn map_authorization(&self, a: Authorized) -> Authorized {
|
||||
a
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Bundle<A: Authorization> {
|
||||
pub vin: Vec<TzeIn<A::Witness>>,
|
||||
|
|
|
@ -22,7 +22,7 @@ zcash_primitives.workspace = true
|
|||
|
||||
# Dependencies exposed in a public API:
|
||||
# (Breaking upgrades to these require a breaking upgrade to this crate.)
|
||||
bellman = { version = "0.14", default-features = false, features = ["groth16"] }
|
||||
bellman.workspace = true
|
||||
bls12_381.workspace = true
|
||||
group.workspace = true
|
||||
jubjub.workspace = true
|
||||
|
|
Loading…
Reference in New Issue