Merge pull request #1713 from ZcashFoundation/use-groth16-batch-math
Use batch optimizations, load params in groth16::Verifier, verify Spend & Output descriptions in transaction verifier
This commit is contained in:
parent
e0643f3664
commit
7efc700aca
|
@ -227,21 +227,20 @@ checksum = "474a626a67200bd107d44179bb3d4fc61891172d11696609264589be6a0e6a43"
|
|||
|
||||
[[package]]
|
||||
name = "bellman"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7089887635778eabf0038a166f586eee5413fb85c8fa6c9a754914f0f644f49f"
|
||||
version = "0.9.0"
|
||||
source = "git+https://github.com/zkcrypto/bellman?rev=bd4af09f50a4d020a3672aff37c4f3f2da2bb36b#bd4af09f50a4d020a3672aff37c4f3f2da2bb36b"
|
||||
dependencies = [
|
||||
"bitvec 0.18.4",
|
||||
"bitvec 0.20.1",
|
||||
"blake2s_simd",
|
||||
"byteorder",
|
||||
"crossbeam",
|
||||
"ff 0.8.0",
|
||||
"ff",
|
||||
"futures 0.1.30",
|
||||
"futures-cpupool",
|
||||
"group 0.8.0",
|
||||
"group",
|
||||
"num_cpus",
|
||||
"pairing",
|
||||
"rand_core 0.5.1",
|
||||
"rand_core 0.6.1",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
|
@ -319,17 +318,6 @@ dependencies = [
|
|||
"radium 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "0.18.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d2838fdd79e8776dbe07a106c784b0f8dda571a21b2750a092cc4cbaa653c8e"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium 0.4.1",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "0.20.1"
|
||||
|
@ -403,27 +391,15 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bls12_381"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4caf0101205582491f772d60a6fcb6bcec19963e68209cb631851eeadb01421f"
|
||||
dependencies = [
|
||||
"bitvec 0.18.4",
|
||||
"ff 0.8.0",
|
||||
"group 0.8.0",
|
||||
"pairing",
|
||||
"rand_core 0.5.1",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bls12_381"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c56609cc42c628848e7b18e0baf42a4ef626b8c50442dc08b8094bd21d8ad32"
|
||||
dependencies = [
|
||||
"ff 0.9.0",
|
||||
"ff",
|
||||
"group",
|
||||
"pairing",
|
||||
"rand_core 0.6.1",
|
||||
"subtle",
|
||||
]
|
||||
|
@ -1007,17 +983,6 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01646e077d4ebda82b73f1bca002ea1e91561a77df2431a9e79729bcc31950ef"
|
||||
dependencies = [
|
||||
"bitvec 0.18.4",
|
||||
"rand_core 0.5.1",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.9.0"
|
||||
|
@ -1280,18 +1245,6 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc11f9f5fbf1943b48ae7c2bf6846e7d827a512d1be4f23af708f5ca5d01dde1"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"ff 0.8.0",
|
||||
"rand_core 0.5.1",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.9.0"
|
||||
|
@ -1299,7 +1252,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "61b3c1e8b4f1ca07e6605ea1be903a5f6956aec5c8a67fd44d56076631675ed8"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"ff 0.9.0",
|
||||
"ff",
|
||||
"rand_core 0.6.1",
|
||||
"subtle",
|
||||
]
|
||||
|
@ -1667,8 +1620,8 @@ checksum = "4d7e7fef85ae7b26dd89f34175b7f3c5ace64067a110c2ac86cf92407a6666ca"
|
|||
dependencies = [
|
||||
"bitvec 0.20.1",
|
||||
"bls12_381 0.4.0",
|
||||
"ff 0.9.0",
|
||||
"group 0.9.0",
|
||||
"ff",
|
||||
"group",
|
||||
"rand_core 0.6.1",
|
||||
"subtle",
|
||||
]
|
||||
|
@ -2077,12 +2030,12 @@ checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55"
|
|||
|
||||
[[package]]
|
||||
name = "pairing"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f702cdbee9e0a6272452c20dec82465bc821116598b4eeb63e9a71a69dbf7fd"
|
||||
checksum = "9be899ebf10363f018353dba1baabb7e83145f3683c7b83b73b93b563e3167cc"
|
||||
dependencies = [
|
||||
"ff 0.8.0",
|
||||
"group 0.8.0",
|
||||
"ff",
|
||||
"group",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2346,12 +2299,6 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac"
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64de9a0c5361e034f1aefc9f71a86871ec870e766fe31a009734a989b329286a"
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.6.2"
|
||||
|
@ -2381,7 +2328,7 @@ dependencies = [
|
|||
"libc",
|
||||
"rand_chacha 0.2.2",
|
||||
"rand_core 0.5.1",
|
||||
"rand_hc",
|
||||
"rand_hc 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2393,6 +2340,7 @@ dependencies = [
|
|||
"libc",
|
||||
"rand_chacha 0.3.0",
|
||||
"rand_core 0.6.1",
|
||||
"rand_hc 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2457,6 +2405,15 @@ dependencies = [
|
|||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
|
||||
dependencies = [
|
||||
"rand_core 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.2.0"
|
||||
|
@ -2512,14 +2469,13 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "redjubjub"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63266b4e0d748bf7514f794bcdb1db681a22b11ed4dea3984a3148d6cd5733c3"
|
||||
source = "git+https://github.com/ZcashFoundation/redjubjub?rev=8101eaff1cb2fca45334f77a65caa4c46e3d545b#8101eaff1cb2fca45334f77a65caa4c46e3d545b"
|
||||
dependencies = [
|
||||
"blake2b_simd",
|
||||
"byteorder",
|
||||
"digest 0.9.0",
|
||||
"jubjub 0.3.0",
|
||||
"rand_core 0.5.1",
|
||||
"rand_core 0.6.1",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
@ -3776,6 +3732,56 @@ dependencies = [
|
|||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wagyu-zcash-parameters"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c904628658374e651288f000934c33ef738b2d8b3e65d4100b70b395dbe2bb"
|
||||
dependencies = [
|
||||
"wagyu-zcash-parameters-1",
|
||||
"wagyu-zcash-parameters-2",
|
||||
"wagyu-zcash-parameters-3",
|
||||
"wagyu-zcash-parameters-4",
|
||||
"wagyu-zcash-parameters-5",
|
||||
"wagyu-zcash-parameters-6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wagyu-zcash-parameters-1"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bf2e21bb027d3f8428c60d6a720b54a08bf6ce4e6f834ef8e0d38bb5695da8"
|
||||
|
||||
[[package]]
|
||||
name = "wagyu-zcash-parameters-2"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a616ab2e51e74cc48995d476e94de810fb16fc73815f390bf2941b046cc9ba2c"
|
||||
|
||||
[[package]]
|
||||
name = "wagyu-zcash-parameters-3"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14da1e2e958ff93c0830ee68e91884069253bf3462a67831b02b367be75d6147"
|
||||
|
||||
[[package]]
|
||||
name = "wagyu-zcash-parameters-4"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f058aeef03a2070e8666ffb5d1057d8bb10313b204a254a6e6103eb958e9a6d6"
|
||||
|
||||
[[package]]
|
||||
name = "wagyu-zcash-parameters-5"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ffe916b30e608c032ae1b734f02574a3e12ec19ab5cc5562208d679efe4969d"
|
||||
|
||||
[[package]]
|
||||
name = "wagyu-zcash-parameters-6"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7b6d5a78adc3e8f198e9cd730f219a695431467f7ec29dcfc63ade885feebe1"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.0"
|
||||
|
@ -4053,7 +4059,7 @@ dependencies = [
|
|||
"primitive-types",
|
||||
"proptest",
|
||||
"proptest-derive",
|
||||
"rand_core 0.5.1",
|
||||
"rand_core 0.6.1",
|
||||
"redjubjub",
|
||||
"ripemd160",
|
||||
"secp256k1",
|
||||
|
@ -4076,18 +4082,19 @@ name = "zebra-consensus"
|
|||
version = "1.0.0-alpha.5"
|
||||
dependencies = [
|
||||
"bellman",
|
||||
"bls12_381 0.3.1",
|
||||
"blake2b_simd",
|
||||
"bls12_381 0.4.0",
|
||||
"chrono",
|
||||
"color-eyre",
|
||||
"displaydoc",
|
||||
"futures 0.3.13",
|
||||
"futures-util",
|
||||
"jubjub 0.6.0",
|
||||
"lazy_static",
|
||||
"metrics",
|
||||
"once_cell",
|
||||
"pairing",
|
||||
"rand 0.7.3",
|
||||
"redjubjub",
|
||||
"rand 0.8.1",
|
||||
"serde",
|
||||
"spandoc",
|
||||
"thiserror",
|
||||
|
@ -4099,6 +4106,7 @@ dependencies = [
|
|||
"tracing-error",
|
||||
"tracing-futures",
|
||||
"tracing-subscriber 0.2.17",
|
||||
"wagyu-zcash-parameters",
|
||||
"zebra-chain",
|
||||
"zebra-script",
|
||||
"zebra-state",
|
||||
|
@ -4121,7 +4129,7 @@ dependencies = [
|
|||
"pin-project 0.4.27",
|
||||
"proptest",
|
||||
"proptest-derive",
|
||||
"rand 0.7.3",
|
||||
"rand 0.8.1",
|
||||
"regex",
|
||||
"serde",
|
||||
"thiserror",
|
||||
|
@ -4236,7 +4244,7 @@ dependencies = [
|
|||
"metrics-exporter-prometheus",
|
||||
"once_cell",
|
||||
"pin-project 0.4.27",
|
||||
"rand 0.7.3",
|
||||
"rand 0.8.1",
|
||||
"sentry",
|
||||
"sentry-tracing",
|
||||
"serde",
|
||||
|
|
|
@ -6,21 +6,24 @@ license = "MIT"
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.3.6", features = ["time", "sync", "stream", "tracing", "macros"] }
|
||||
tower = { version = "0.4", features = ["util", "buffer"] }
|
||||
futures = "0.3.13"
|
||||
futures-core = "0.3.13"
|
||||
pin-project = "0.4.27"
|
||||
tokio = { version = "0.3.6", features = ["time", "sync", "stream", "tracing", "macros"] }
|
||||
tower = { version = "0.4", features = ["util", "buffer"] }
|
||||
tracing = "0.1.25"
|
||||
tracing-futures = "0.2.5"
|
||||
futures = "0.3.13"
|
||||
|
||||
[dev-dependencies]
|
||||
color-eyre = "0.5.10"
|
||||
ed25519-zebra = "2.1.0"
|
||||
rand = "0.7"
|
||||
tokio = { version = "0.3.6", features = ["full"]}
|
||||
tokio-test = "0.4.1"
|
||||
tower-fallback = { path = "../tower-fallback/" }
|
||||
tower-test = "0.4.0"
|
||||
tracing = "0.1.25"
|
||||
zebra-test = { path = "../zebra-test/" }
|
||||
tower-fallback = { path = "../tower-fallback/" }
|
||||
color-eyre = "0.5.10"
|
||||
tokio-test = "0.4.1"
|
||||
tower-test = "0.4.0"
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ hex = "0.4"
|
|||
jubjub = "0.6.0"
|
||||
lazy_static = "1.4.0"
|
||||
primitive-types = "0.9.0"
|
||||
rand_core = "0.5.1"
|
||||
rand_core = "0.6"
|
||||
ripemd160 = "0.8.0"
|
||||
secp256k1 = { version = "0.20.1", features = ["serde"] }
|
||||
serde = { version = "1", features = ["serde_derive", "rc"] }
|
||||
|
@ -41,7 +41,8 @@ proptest-derive = { version = "0.3.0", optional = true }
|
|||
displaydoc = "0.2.0"
|
||||
ed25519-zebra = "2"
|
||||
equihash = "0.1"
|
||||
redjubjub = "0.2"
|
||||
#redjubjub = "0.2"
|
||||
redjubjub = {git = "https://github.com/ZcashFoundation/redjubjub", rev = "8101eaff1cb2fca45334f77a65caa4c46e3d545b"}
|
||||
bitflags = "1.2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -27,14 +27,26 @@ impl Clone for Groth16Proof {
|
|||
}
|
||||
}
|
||||
|
||||
impl Eq for Groth16Proof {}
|
||||
|
||||
impl From<[u8; 192]> for Groth16Proof {
|
||||
fn from(bytes: [u8; 192]) -> Groth16Proof {
|
||||
Self(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Groth16Proof> for [u8; 192] {
|
||||
fn from(rt: Groth16Proof) -> [u8; 192] {
|
||||
rt.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Groth16Proof {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0[..] == other.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Groth16Proof {}
|
||||
|
||||
impl ZcashSerialize for Groth16Proof {
|
||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
writer.write_all(&self.0[..])?;
|
||||
|
|
|
@ -885,6 +885,12 @@ impl fmt::Debug for EphemeralPublicKey {
|
|||
|
||||
impl Eq for EphemeralPublicKey {}
|
||||
|
||||
impl From<EphemeralPublicKey> for [u8; 32] {
|
||||
fn from(nk: EphemeralPublicKey) -> [u8; 32] {
|
||||
nk.0.to_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&EphemeralPublicKey> for [u8; 32] {
|
||||
fn from(nk: &EphemeralPublicKey) -> [u8; 32] {
|
||||
nk.0.to_bytes()
|
||||
|
|
|
@ -52,3 +52,41 @@ impl From<Nullifier> for [u8; 32] {
|
|||
n.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Nullifier> for [jubjub::Fq; 2] {
|
||||
/// Add the nullifier through multiscalar packing
|
||||
///
|
||||
/// Informed by https://github.com/zkcrypto/bellman/blob/main/src/gadgets/multipack.rs
|
||||
fn from(n: Nullifier) -> Self {
|
||||
use std::ops::AddAssign;
|
||||
|
||||
let nullifier_bits_le: Vec<bool> =
|
||||
n.0.iter()
|
||||
.flat_map(|&v| (0..8).map(move |i| (v >> i) & 1 == 1))
|
||||
.collect();
|
||||
|
||||
// The number of bits needed to represent the modulus, minus 1.
|
||||
const CAPACITY: usize = 255 - 1;
|
||||
|
||||
let mut result = [jubjub::Fq::zero(); 2];
|
||||
|
||||
// Since we know the max bits of the input (256) and the chunk size
|
||||
// (254), this will always result in 2 chunks.
|
||||
for (i, bits) in nullifier_bits_le.chunks(CAPACITY).enumerate() {
|
||||
let mut cur = jubjub::Fq::zero();
|
||||
let mut coeff = jubjub::Fq::one();
|
||||
|
||||
for bit in bits {
|
||||
if *bit {
|
||||
cur.add_assign(&coeff);
|
||||
}
|
||||
|
||||
coeff = coeff.double();
|
||||
}
|
||||
|
||||
result[i] = cur
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,30 @@ pub struct Output {
|
|||
pub zkproof: Groth16Proof,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
/// Encodes the primary inputs for the proof statement as 5 Bls12_381 base
|
||||
/// field elements, to match bellman::groth16::verify_proof.
|
||||
///
|
||||
/// NB: jubjub::Fq is a type alias for bls12_381::Scalar.
|
||||
///
|
||||
/// https://zips.z.cash/protocol/protocol.pdf#cctsaplingoutput
|
||||
pub fn primary_inputs(&self) -> Vec<jubjub::Fq> {
|
||||
let mut inputs = vec![];
|
||||
|
||||
let cv_affine = jubjub::AffinePoint::from_bytes(self.cv.into()).unwrap();
|
||||
inputs.push(cv_affine.get_u());
|
||||
inputs.push(cv_affine.get_v());
|
||||
|
||||
let epk_affine = jubjub::AffinePoint::from_bytes(self.ephemeral_key.into()).unwrap();
|
||||
inputs.push(epk_affine.get_u());
|
||||
inputs.push(epk_affine.get_v());
|
||||
|
||||
inputs.push(self.cm_u);
|
||||
|
||||
inputs
|
||||
}
|
||||
}
|
||||
|
||||
impl ZcashSerialize for Output {
|
||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
self.cv.zcash_serialize(&mut writer)?;
|
||||
|
|
|
@ -31,6 +31,35 @@ pub struct Spend {
|
|||
pub spend_auth_sig: redjubjub::Signature<SpendAuth>,
|
||||
}
|
||||
|
||||
impl Spend {
|
||||
/// Encodes the primary inputs for the proof statement as 7 Bls12_381 base
|
||||
/// field elements, to match bellman::groth16::verify_proof.
|
||||
///
|
||||
/// NB: jubjub::Fq is a type alias for bls12_381::Scalar.
|
||||
///
|
||||
/// https://zips.z.cash/protocol/protocol.pdf#cctsaplingspend
|
||||
pub fn primary_inputs(&self) -> Vec<jubjub::Fq> {
|
||||
let mut inputs = vec![];
|
||||
|
||||
let rk_affine = jubjub::AffinePoint::from_bytes(self.rk.into()).unwrap();
|
||||
inputs.push(rk_affine.get_u());
|
||||
inputs.push(rk_affine.get_v());
|
||||
|
||||
let cv_affine = jubjub::AffinePoint::from_bytes(self.cv.into()).unwrap();
|
||||
inputs.push(cv_affine.get_u());
|
||||
inputs.push(cv_affine.get_v());
|
||||
|
||||
inputs.push(jubjub::Fq::from_bytes(&self.anchor.into()).unwrap());
|
||||
|
||||
let nullifier_limbs: [jubjub::Fq; 2] = self.nullifier.into();
|
||||
|
||||
inputs.push(nullifier_limbs[0]);
|
||||
inputs.push(nullifier_limbs[1]);
|
||||
|
||||
inputs
|
||||
}
|
||||
}
|
||||
|
||||
impl ZcashSerialize for Spend {
|
||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
self.cv.zcash_serialize(&mut writer)?;
|
||||
|
|
|
@ -84,6 +84,18 @@ impl fmt::Debug for Root {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for Root {
|
||||
fn from(bytes: [u8; 32]) -> Root {
|
||||
Self(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Root> for [u8; 32] {
|
||||
fn from(root: Root) -> Self {
|
||||
root.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Sapling Note Commitment Tree
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
struct NoteCommitmentTree {
|
||||
|
|
|
@ -6,15 +6,18 @@ license = "MIT OR Apache-2.0"
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
blake2b_simd = "0.5.11"
|
||||
#bellman = "0.8"
|
||||
bellman = { git = "https://github.com/zkcrypto/bellman", rev = "bd4af09f50a4d020a3672aff37c4f3f2da2bb36b" }
|
||||
bls12_381 = "0.4.0"
|
||||
chrono = "0.4.19"
|
||||
displaydoc = "0.2.0"
|
||||
jubjub = "0.6.0"
|
||||
lazy_static = "1.4.0"
|
||||
once_cell = "1.7"
|
||||
rand = "0.7"
|
||||
redjubjub = "0.2"
|
||||
pairing = "0.19.0"
|
||||
rand = "0.8"
|
||||
serde = { version = "1", features = ["serde_derive"] }
|
||||
bellman = "0.8"
|
||||
bls12_381 = "0.3.1"
|
||||
|
||||
futures = "0.3.13"
|
||||
futures-util = "0.3.6"
|
||||
|
@ -30,11 +33,10 @@ tower-batch = { path = "../tower-batch/" }
|
|||
zebra-chain = { path = "../zebra-chain" }
|
||||
zebra-state = { path = "../zebra-state" }
|
||||
zebra-script = { path = "../zebra-script" }
|
||||
pairing = "0.18.0"
|
||||
wagyu-zcash-parameters = "0.2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
color-eyre = "0.5.10"
|
||||
rand = "0.7"
|
||||
spandoc = "0.2"
|
||||
tokio = { version = "0.3.6", features = ["full"] }
|
||||
tracing-error = "0.1.2"
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
use thiserror::Error;
|
||||
|
||||
use zebra_chain::{block, primitives::ed25519};
|
||||
|
||||
use crate::BoxError;
|
||||
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
|
@ -55,6 +53,9 @@ pub enum TransactionError {
|
|||
#[error("could not verify a transparent script")]
|
||||
Script(#[from] zebra_script::Error),
|
||||
|
||||
#[error("spend description cv and rk MUST NOT be of small order")]
|
||||
SmallOrder,
|
||||
|
||||
// XXX change this when we align groth16 verifier errors with bellman
|
||||
// and add a from annotation when the error type is more precise
|
||||
#[error("spend proof MUST be valid given a primary input formed from the other fields except spendAuthSig")]
|
||||
|
@ -63,10 +64,10 @@ pub enum TransactionError {
|
|||
#[error(
|
||||
"joinSplitSig MUST represent a valid signature under joinSplitPubKey of dataToBeSigned"
|
||||
)]
|
||||
Ed25519(#[from] ed25519::Error),
|
||||
Ed25519(#[from] zebra_chain::primitives::ed25519::Error),
|
||||
|
||||
#[error("bindingSig MUST represent a valid signature under the transaction binding validating key bvk of SigHash")]
|
||||
RedJubjub(redjubjub::Error),
|
||||
RedJubjub(zebra_chain::primitives::redjubjub::Error),
|
||||
|
||||
// temporary error type until #1186 is fixed
|
||||
#[error("Downcast from BoxError to redjubjub::Error failed")]
|
||||
|
@ -75,7 +76,7 @@ pub enum TransactionError {
|
|||
|
||||
impl From<BoxError> for TransactionError {
|
||||
fn from(err: BoxError) -> Self {
|
||||
match err.downcast::<redjubjub::Error>() {
|
||||
match err.downcast::<zebra_chain::primitives::redjubjub::Error>() {
|
||||
Ok(e) => TransactionError::RedJubjub(*e),
|
||||
Err(e) => TransactionError::InternalDowncastError(format!(
|
||||
"downcast to redjubjub::Error failed, original error: {:?}",
|
||||
|
@ -101,8 +102,8 @@ pub enum BlockError {
|
|||
|
||||
#[error("block has mismatched merkle root")]
|
||||
BadMerkleRoot {
|
||||
actual: block::merkle::Root,
|
||||
expected: block::merkle::Root,
|
||||
actual: zebra_chain::block::merkle::Root,
|
||||
expected: zebra_chain::block::merkle::Root,
|
||||
},
|
||||
|
||||
#[error("block contains duplicate transactions")]
|
||||
|
|
|
@ -51,7 +51,6 @@ mod checkpoint;
|
|||
mod config;
|
||||
#[allow(dead_code)]
|
||||
mod parameters;
|
||||
#[allow(dead_code)] // Remove this once transaction verification is implemented
|
||||
mod primitives;
|
||||
mod script;
|
||||
mod transaction;
|
||||
|
|
|
@ -9,120 +9,119 @@ use std::{
|
|||
};
|
||||
|
||||
use bellman::{
|
||||
groth16::{PreparedVerifyingKey, Proof},
|
||||
groth16::{batch, prepare_verifying_key, VerifyingKey},
|
||||
VerificationError,
|
||||
};
|
||||
use bls12_381::Bls12;
|
||||
use pairing::MultiMillerLoop;
|
||||
use rand::{thread_rng, CryptoRng, RngCore};
|
||||
use futures::future::{ready, Ready};
|
||||
use once_cell::sync::Lazy;
|
||||
use rand::thread_rng;
|
||||
use tokio::sync::broadcast::{channel, error::RecvError, Sender};
|
||||
use tower::Service;
|
||||
use tower_batch::BatchControl;
|
||||
use tower::{util::ServiceFn, Service};
|
||||
use tower_batch::{Batch, BatchControl};
|
||||
use tower_fallback::Fallback;
|
||||
use zebra_chain::sapling::{Output, Spend};
|
||||
|
||||
use crate::BoxError;
|
||||
mod hash_reader;
|
||||
mod params;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
// === TEMPORARY BATCH BELLMAN SUBSTITUTE ===
|
||||
// These types are meant to be API compatible with the work in progress batch
|
||||
// verification API being implemented in Bellman. Once we've finished that
|
||||
// implementation and upgraded our dependency, we should be able to remove this
|
||||
// section of code and replace each of these types with the commented out items
|
||||
// from the rest of this file.
|
||||
use self::hash_reader::HashReader;
|
||||
use params::PARAMS;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Item<E: MultiMillerLoop> {
|
||||
proof: Proof<E>,
|
||||
public_inputs: Vec<E::Fr>,
|
||||
}
|
||||
/// Global batch verification context for Groth16 proofs of Spend statements.
|
||||
///
|
||||
/// This service transparently batches contemporaneous proof verifications,
|
||||
/// handling batch failures by falling back to individual verification.
|
||||
///
|
||||
/// Note that making a `Service` call requires mutable access to the service, so
|
||||
/// you should call `.clone()` on the global handle to create a local, mutable
|
||||
/// handle.
|
||||
pub static SPEND_VERIFIER: Lazy<
|
||||
Fallback<Batch<Verifier, Item>, ServiceFn<fn(Item) -> Ready<Result<(), VerificationError>>>>,
|
||||
> = Lazy::new(|| {
|
||||
Fallback::new(
|
||||
Batch::new(
|
||||
Verifier::new(&PARAMS.sapling.spend.vk),
|
||||
super::MAX_BATCH_SIZE,
|
||||
super::MAX_BATCH_LATENCY,
|
||||
),
|
||||
// We want to fallback to individual verification if batch verification
|
||||
// fails, so we need a Service to use. The obvious way to do this would
|
||||
// be to write a closure that returns an async block. But because we
|
||||
// have to specify the type of a static, we need to be able to write the
|
||||
// type of the closure and its return value, and both closures and async
|
||||
// blocks have eldritch types whose names cannot be written. So instead,
|
||||
// we use a Ready to avoid an async block and cast the closure to a
|
||||
// function (which is possible because it doesn't capture any state).
|
||||
tower::service_fn(
|
||||
(|item: Item| {
|
||||
ready(item.verify_single(&prepare_verifying_key(&PARAMS.sapling.spend.vk)))
|
||||
}) as fn(_) -> _,
|
||||
),
|
||||
)
|
||||
});
|
||||
|
||||
impl<E: MultiMillerLoop> Item<E> {
|
||||
fn verify_single(self, pvk: &PreparedVerifyingKey<E>) -> Result<(), VerificationError> {
|
||||
let Item {
|
||||
proof,
|
||||
public_inputs,
|
||||
} = self;
|
||||
/// Global batch verification context for Groth16 proofs of Output statements.
|
||||
///
|
||||
/// This service transparently batches contemporaneous proof verifications,
|
||||
/// handling batch failures by falling back to individual verification.
|
||||
///
|
||||
/// Note that making a `Service` call requires mutable access to the service, so
|
||||
/// you should call `.clone()` on the global handle to create a local, mutable
|
||||
/// handle.
|
||||
pub static OUTPUT_VERIFIER: Lazy<
|
||||
Fallback<Batch<Verifier, Item>, ServiceFn<fn(Item) -> Ready<Result<(), VerificationError>>>>,
|
||||
> = Lazy::new(|| {
|
||||
Fallback::new(
|
||||
Batch::new(
|
||||
Verifier::new(&PARAMS.sapling.output.vk),
|
||||
super::MAX_BATCH_SIZE,
|
||||
super::MAX_BATCH_LATENCY,
|
||||
),
|
||||
// We want to fallback to individual verification if batch verification
|
||||
// fails, so we need a Service to use. The obvious way to do this would
|
||||
// be to write a closure that returns an async block. But because we
|
||||
// have to specify the type of a static, we need to be able to write the
|
||||
// type of the closure and its return value, and both closures and async
|
||||
// blocks have eldritch types whose names cannot be written. So instead,
|
||||
// we use a Ready to avoid an async block and cast the closure to a
|
||||
// function (which is possible because it doesn't capture any state).
|
||||
tower::service_fn(
|
||||
(|item: Item| {
|
||||
ready(item.verify_single(&prepare_verifying_key(&PARAMS.sapling.output.vk)))
|
||||
}) as fn(_) -> _,
|
||||
),
|
||||
)
|
||||
});
|
||||
|
||||
bellman::groth16::verify_proof(pvk, &proof, &public_inputs)
|
||||
/// A Groth16 verification item, used as the request type of the service.
|
||||
pub type Item = batch::Item<Bls12>;
|
||||
|
||||
pub struct ItemWrapper(Item);
|
||||
|
||||
impl From<&Spend> for ItemWrapper {
|
||||
fn from(spend: &Spend) -> Self {
|
||||
Self(Item::from((
|
||||
bellman::groth16::Proof::read(&spend.zkproof.0[..]).unwrap(),
|
||||
spend.primary_inputs(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: MultiMillerLoop> From<(&Proof<E>, &[E::Fr])> for Item<E> {
|
||||
fn from((proof, public_inputs): (&Proof<E>, &[E::Fr])) -> Self {
|
||||
(proof.clone(), public_inputs.to_owned()).into()
|
||||
impl From<&Output> for ItemWrapper {
|
||||
fn from(output: &Output) -> Self {
|
||||
Self(Item::from((
|
||||
bellman::groth16::Proof::read(&output.zkproof.0[..]).unwrap(),
|
||||
output.primary_inputs(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: MultiMillerLoop> From<(Proof<E>, Vec<E::Fr>)> for Item<E> {
|
||||
fn from((proof, public_inputs): (Proof<E>, Vec<E::Fr>)) -> Self {
|
||||
Self {
|
||||
proof,
|
||||
public_inputs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Batch {
|
||||
queue: Vec<Item<Bls12>>,
|
||||
}
|
||||
|
||||
impl Batch {
|
||||
fn queue(&mut self, item: Item<Bls12>) {
|
||||
self.queue.push(item);
|
||||
}
|
||||
|
||||
fn verify<R: RngCore + CryptoRng>(
|
||||
self,
|
||||
_rng: R,
|
||||
pvk: &PreparedVerifyingKey<Bls12>,
|
||||
) -> Result<(), VerificationError> {
|
||||
for item in self.queue {
|
||||
item.verify_single(pvk)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// === TEMPORARY BATCH BELLMAN SUBSTITUTE END ===
|
||||
|
||||
// /// A Groth16 verification item, used as the request type of the service.
|
||||
// pub type Item = batch::Item<Bls12>;
|
||||
|
||||
/// Groth16 signature verifier service
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Verifier {
|
||||
inner: Fallback<tower_batch::Batch<VerifierImpl, Item<Bls12>>, FallbackVerifierImpl>,
|
||||
}
|
||||
|
||||
impl Verifier {
|
||||
/// Constructs a new verifier.
|
||||
pub fn new(pvk: &'static PreparedVerifyingKey<Bls12>) -> Self {
|
||||
let verifier_impl = VerifierImpl::new(pvk);
|
||||
let fallback_impl = FallbackVerifierImpl::new(pvk);
|
||||
|
||||
let max_items = super::MAX_BATCH_SIZE;
|
||||
let max_latency = super::MAX_BATCH_LATENCY;
|
||||
|
||||
let inner = tower_batch::Batch::new(verifier_impl, max_items, max_latency);
|
||||
let inner = Fallback::new(inner, fallback_impl);
|
||||
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl Service<Item<Bls12>> for Verifier {
|
||||
type Response = ();
|
||||
type Error = BoxError;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<(), BoxError>> + Send + 'static>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.inner.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Item<Bls12>) -> Self::Future {
|
||||
use futures::FutureExt;
|
||||
self.inner.call(req).boxed()
|
||||
impl From<ItemWrapper> for Item {
|
||||
fn from(item_wrapper: ItemWrapper) -> Self {
|
||||
item_wrapper.0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,37 +130,35 @@ impl Service<Item<Bls12>> for Verifier {
|
|||
/// This is the core implementation for the batch verification logic of the groth
|
||||
/// verifier. It handles batching incoming requests, driving batches to
|
||||
/// completion, and reporting results.
|
||||
struct VerifierImpl {
|
||||
// batch: batch::Verifier<Bls12>,
|
||||
batch: Batch,
|
||||
pub struct Verifier {
|
||||
batch: batch::Verifier<Bls12>,
|
||||
// Making this 'static makes managing lifetimes much easier.
|
||||
pvk: &'static PreparedVerifyingKey<Bls12>,
|
||||
vk: &'static VerifyingKey<Bls12>,
|
||||
/// Broadcast sender used to send the result of a batch verification to each
|
||||
/// request source in the batch.
|
||||
tx: Sender<Result<(), VerificationError>>,
|
||||
}
|
||||
|
||||
impl VerifierImpl {
|
||||
fn new(pvk: &'static PreparedVerifyingKey<Bls12>) -> Self {
|
||||
// let batch = batch::Verifier::default();
|
||||
let batch = Batch::default();
|
||||
impl Verifier {
|
||||
fn new(vk: &'static VerifyingKey<Bls12>) -> Self {
|
||||
let batch = batch::Verifier::default();
|
||||
let (tx, _) = channel(super::BROADCAST_BUFFER_SIZE);
|
||||
Self { batch, pvk, tx }
|
||||
Self { batch, vk, tx }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for VerifierImpl {
|
||||
impl fmt::Debug for Verifier {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let name = "VerifierImpl";
|
||||
let name = "Verifier";
|
||||
f.debug_struct(name)
|
||||
.field("batch", &"..")
|
||||
.field("pvk", &"..")
|
||||
.field("vk", &"..")
|
||||
.field("tx", &self.tx)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Service<BatchControl<Item<Bls12>>> for VerifierImpl {
|
||||
impl Service<BatchControl<Item>> for Verifier {
|
||||
type Response = ();
|
||||
type Error = VerificationError;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<(), VerificationError>> + Send + 'static>>;
|
||||
|
@ -170,7 +167,7 @@ impl Service<BatchControl<Item<Bls12>>> for VerifierImpl {
|
|||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: BatchControl<Item<Bls12>>) -> Self::Future {
|
||||
fn call(&mut self, req: BatchControl<Item>) -> Self::Future {
|
||||
match req {
|
||||
BatchControl::Item(item) => {
|
||||
tracing::trace!("got item");
|
||||
|
@ -193,52 +190,17 @@ impl Service<BatchControl<Item<Bls12>>> for VerifierImpl {
|
|||
BatchControl::Flush => {
|
||||
tracing::trace!("got flush command");
|
||||
let batch = mem::take(&mut self.batch);
|
||||
let _ = self.tx.send(batch.verify(thread_rng(), self.pvk));
|
||||
let _ = self.tx.send(batch.verify(thread_rng(), self.vk));
|
||||
Box::pin(async { Ok(()) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VerifierImpl {
|
||||
impl Drop for Verifier {
|
||||
fn drop(&mut self) {
|
||||
// We need to flush the current batch in case there are still any pending futures.
|
||||
let batch = mem::take(&mut self.batch);
|
||||
let _ = self.tx.send(batch.verify(thread_rng(), self.pvk));
|
||||
}
|
||||
}
|
||||
|
||||
/// Groth16 signature verifier fallback implementation
|
||||
#[derive(Clone)]
|
||||
struct FallbackVerifierImpl {
|
||||
pvk: &'static PreparedVerifyingKey<Bls12>,
|
||||
}
|
||||
|
||||
impl FallbackVerifierImpl {
|
||||
fn new(pvk: &'static PreparedVerifyingKey<Bls12>) -> Self {
|
||||
Self { pvk }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FallbackVerifierImpl {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let name = "FallbackVerifierImpl";
|
||||
f.debug_struct(name).field("pvk", &"..").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Service<Item<Bls12>> for FallbackVerifierImpl {
|
||||
type Response = ();
|
||||
type Error = VerificationError;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<(), VerificationError>> + Send + 'static>>;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, item: Item<Bls12>) -> Self::Future {
|
||||
tracing::trace!("got item");
|
||||
let pvk = self.pvk;
|
||||
Box::pin(async move { item.verify_single(pvk) })
|
||||
let _ = self.tx.send(batch.verify(thread_rng(), self.vk));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
use std::io::{self, Read};
|
||||
|
||||
use blake2b_simd::State;
|
||||
|
||||
/// Abstraction over a reader which hashes the data being read.
|
||||
pub struct HashReader<R: Read> {
|
||||
reader: R,
|
||||
hasher: State,
|
||||
}
|
||||
|
||||
impl<R: Read> HashReader<R> {
|
||||
/// Construct a new `HashReader` given an existing `reader` by value.
|
||||
pub fn new(reader: R) -> Self {
|
||||
HashReader {
|
||||
reader,
|
||||
hasher: State::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Destroy this reader and return the hash of what was read.
|
||||
pub fn into_hash(self) -> String {
|
||||
let hash = self.hasher.finalize();
|
||||
|
||||
let mut s = String::new();
|
||||
for c in hash.as_bytes().iter() {
|
||||
s += &format!("{:02x}", c);
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> Read for HashReader<R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let bytes = self.reader.read(buf)?;
|
||||
|
||||
if bytes > 0 {
|
||||
self.hasher.update(&buf[0..bytes]);
|
||||
}
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
use std::io::{self, BufReader};
|
||||
|
||||
use bellman::groth16;
|
||||
use bls12_381::Bls12;
|
||||
|
||||
use super::HashReader;
|
||||
|
||||
const SAPLING_SPEND_HASH: &str = "8270785a1a0d0bc77196f000ee6d221c9c9894f55307bd9357c3f0105d31ca63991ab91324160d8f53e2bbd3c2633a6eb8bdf5205d822e7f3f73edac51b2b70c";
|
||||
const SAPLING_OUTPUT_HASH: &str = "657e3d38dbb5cb5e7dd2970e8b03d69b4787dd907285b5a7f0790dcc8072f60bf593b32cc2d1c030e00ff5ae64bf84c5c3beb84ddc841d48264b4a171744d028";
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref PARAMS: Groth16Params = Groth16Params::new();
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct Groth16Params {
|
||||
pub sapling: SaplingParams,
|
||||
}
|
||||
|
||||
impl Groth16Params {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
sapling: SaplingParams::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct SaplingParams {
|
||||
pub spend: groth16::Parameters<Bls12>,
|
||||
pub spend_prepared_verifying_key: groth16::PreparedVerifyingKey<Bls12>,
|
||||
|
||||
pub output: groth16::Parameters<Bls12>,
|
||||
pub output_prepared_verifying_key: groth16::PreparedVerifyingKey<Bls12>,
|
||||
}
|
||||
|
||||
impl SaplingParams {
|
||||
fn new() -> Self {
|
||||
let (spend, output) = wagyu_zcash_parameters::load_sapling_parameters();
|
||||
let spend_fs = BufReader::with_capacity(1024 * 1024, &spend[..]);
|
||||
let output_fs = BufReader::with_capacity(1024 * 1024, &output[..]);
|
||||
|
||||
Self::read(spend_fs, output_fs)
|
||||
.expect("reading parameters from wagyu zcash parameter's vec will always succeed")
|
||||
}
|
||||
|
||||
fn read<R: io::Read>(spend_fs: R, output_fs: R) -> Result<Self, io::Error> {
|
||||
let mut spend_fs = HashReader::new(spend_fs);
|
||||
let mut output_fs = HashReader::new(output_fs);
|
||||
|
||||
// Deserialize params
|
||||
let spend = groth16::Parameters::<Bls12>::read(&mut spend_fs, false)?;
|
||||
let output = groth16::Parameters::<Bls12>::read(&mut output_fs, false)?;
|
||||
|
||||
// There is extra stuff (the transcript) at the end of the parameter file which is
|
||||
// used to verify the parameter validity, but we're not interested in that. We do
|
||||
// want to read it, though, so that the BLAKE2b computed afterward is consistent
|
||||
// with `b2sum` on the files.
|
||||
let mut sink = io::sink();
|
||||
io::copy(&mut spend_fs, &mut sink)?;
|
||||
io::copy(&mut output_fs, &mut sink)?;
|
||||
|
||||
if spend_fs.into_hash() != SAPLING_SPEND_HASH {
|
||||
panic!("Sapling spend parameter is not correct.");
|
||||
}
|
||||
|
||||
if output_fs.into_hash() != SAPLING_OUTPUT_HASH {
|
||||
panic!("Sapling output parameter is not correct.");
|
||||
}
|
||||
|
||||
// Prepare verifying keys
|
||||
let spend_prepared_verifying_key = groth16::prepare_verifying_key(&spend.vk);
|
||||
let output_prepared_verifying_key = groth16::prepare_verifying_key(&output.vk);
|
||||
|
||||
Ok(Self {
|
||||
spend,
|
||||
spend_prepared_verifying_key,
|
||||
output,
|
||||
output_prepared_verifying_key,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
//! Tests for transaction verification
|
||||
|
||||
use futures::stream::{FuturesUnordered, StreamExt};
|
||||
use tower::ServiceExt;
|
||||
|
||||
use zebra_chain::{block::Block, serialization::ZcashDeserializeInto, transaction::Transaction};
|
||||
|
||||
use crate::primitives::groth16;
|
||||
|
||||
use super::*;
|
||||
|
||||
async fn verify_groth16_spends_and_outputs<V>(
|
||||
spend_verifier: &mut V,
|
||||
output_verifier: &mut V,
|
||||
transactions: Vec<std::sync::Arc<Transaction>>,
|
||||
) -> Result<(), V::Error>
|
||||
where
|
||||
V: tower::Service<Item, Response = ()>,
|
||||
<V as tower::Service<bellman::groth16::batch::Item<bls12_381::Bls12>>>::Error:
|
||||
std::convert::From<
|
||||
std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>,
|
||||
>,
|
||||
{
|
||||
zebra_test::init();
|
||||
|
||||
let mut async_checks = FuturesUnordered::new();
|
||||
|
||||
for tx in transactions {
|
||||
match &*tx {
|
||||
Transaction::V1 { .. }
|
||||
| Transaction::V2 { .. }
|
||||
| Transaction::V3 { .. }
|
||||
| Transaction::V5 { .. } => (),
|
||||
Transaction::V4 { shielded_data, .. } => {
|
||||
if let Some(shielded_data) = shielded_data {
|
||||
for spend in shielded_data.spends() {
|
||||
tracing::trace!(?spend);
|
||||
|
||||
let spend_rsp = spend_verifier
|
||||
.ready_and()
|
||||
.await?
|
||||
.call(groth16::ItemWrapper::from(spend).into());
|
||||
|
||||
async_checks.push(spend_rsp);
|
||||
}
|
||||
|
||||
for output in shielded_data.outputs() {
|
||||
tracing::trace!(?output);
|
||||
|
||||
let output_rsp = output_verifier
|
||||
.ready_and()
|
||||
.await?
|
||||
.call(groth16::ItemWrapper::from(output).into());
|
||||
|
||||
async_checks.push(output_rsp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(result) = async_checks.next().await {
|
||||
result?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn verify_sapling_groth16() {
|
||||
// Since we expect these to pass, we can use the communal verifiers.
|
||||
let mut spend_verifier = groth16::SPEND_VERIFIER.clone();
|
||||
let mut output_verifier = groth16::OUTPUT_VERIFIER.clone();
|
||||
|
||||
let transactions = zebra_test::vectors::MAINNET_BLOCKS
|
||||
.clone()
|
||||
.iter()
|
||||
.flat_map(|(_, bytes)| {
|
||||
let block = bytes
|
||||
.zcash_deserialize_into::<Block>()
|
||||
.expect("a valid block");
|
||||
block.transactions
|
||||
})
|
||||
.collect();
|
||||
|
||||
// This should fail if any of the proofs fail to validate.
|
||||
verify_groth16_spends_and_outputs(&mut spend_verifier, &mut output_verifier, transactions)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn verify_invalid_groth16_output_description<V>(
|
||||
output_verifier: &mut V,
|
||||
transactions: Vec<std::sync::Arc<Transaction>>,
|
||||
) -> Result<(), V::Error>
|
||||
where
|
||||
V: tower::Service<Item, Response = ()>,
|
||||
<V as tower::Service<bellman::groth16::batch::Item<bls12_381::Bls12>>>::Error:
|
||||
std::convert::From<
|
||||
std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>,
|
||||
>,
|
||||
{
|
||||
zebra_test::init();
|
||||
|
||||
let mut async_checks = FuturesUnordered::new();
|
||||
|
||||
for tx in transactions {
|
||||
match &*tx {
|
||||
Transaction::V1 { .. }
|
||||
| Transaction::V2 { .. }
|
||||
| Transaction::V3 { .. }
|
||||
| Transaction::V5 { .. } => (),
|
||||
Transaction::V4 { shielded_data, .. } => {
|
||||
if let Some(shielded_data) = shielded_data {
|
||||
for output in shielded_data.outputs() {
|
||||
let mut modified_output = output.clone();
|
||||
modified_output.cm_u = jubjub::Fq::zero();
|
||||
|
||||
tracing::trace!(?modified_output);
|
||||
|
||||
let output_rsp = output_verifier
|
||||
.ready_and()
|
||||
.await?
|
||||
.call(groth16::ItemWrapper::from(&modified_output).into());
|
||||
|
||||
async_checks.push(output_rsp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(result) = async_checks.next().await {
|
||||
result?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn correctly_err_on_invalid_output_proof() {
|
||||
// Since we expect these to fail, we don't want to poison the communal
|
||||
// verifiers.
|
||||
let mut output_verifier = Fallback::new(
|
||||
Batch::new(
|
||||
Verifier::new(&PARAMS.sapling.output.vk),
|
||||
crate::primitives::MAX_BATCH_SIZE,
|
||||
crate::primitives::MAX_BATCH_LATENCY,
|
||||
),
|
||||
tower::service_fn(
|
||||
(|item: Item| {
|
||||
ready(item.verify_single(&prepare_verifying_key(&PARAMS.sapling.output.vk)))
|
||||
}) as fn(_) -> _,
|
||||
),
|
||||
);
|
||||
|
||||
let block = zebra_test::vectors::BLOCK_MAINNET_903001_BYTES
|
||||
.clone()
|
||||
.zcash_deserialize_into::<Block>()
|
||||
.expect("a valid block");
|
||||
|
||||
verify_invalid_groth16_output_description(&mut output_verifier, block.transactions)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
|
@ -13,7 +13,7 @@ async fn sign_and_verify<V>(mut verifier: V, n: usize) -> Result<(), V::Error>
|
|||
where
|
||||
V: Service<Item, Response = ()>,
|
||||
{
|
||||
let rng = thread_rng();
|
||||
let mut rng = thread_rng();
|
||||
let mut results = FuturesUnordered::new();
|
||||
for i in 0..n {
|
||||
let span = tracing::trace_span!("sig", i);
|
||||
|
@ -21,16 +21,16 @@ where
|
|||
|
||||
match i % 2 {
|
||||
0 => {
|
||||
let sk = SigningKey::<SpendAuth>::new(rng);
|
||||
let sk = SigningKey::<SpendAuth>::new(&mut rng);
|
||||
let vk = VerificationKey::from(&sk);
|
||||
let sig = sk.sign(rng, &msg[..]);
|
||||
let sig = sk.sign(&mut rng, &msg[..]);
|
||||
verifier.ready_and().await?;
|
||||
results.push(span.in_scope(|| verifier.call((vk.into(), sig, msg).into())))
|
||||
}
|
||||
1 => {
|
||||
let sk = SigningKey::<Binding>::new(rng);
|
||||
let sk = SigningKey::<Binding>::new(&mut rng);
|
||||
let vk = VerificationKey::from(&sk);
|
||||
let sig = sk.sign(rng, &msg[..]);
|
||||
let sig = sk.sign(&mut rng, &msg[..]);
|
||||
verifier.ready_and().await?;
|
||||
results.push(span.in_scope(|| verifier.call((vk.into(), sig, msg).into())))
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ use zebra_chain::{
|
|||
use zebra_script::CachedFfiTransaction;
|
||||
use zebra_state as zs;
|
||||
|
||||
use crate::{error::TransactionError, script, BoxError};
|
||||
use crate::{error::TransactionError, primitives, script, BoxError};
|
||||
|
||||
mod check;
|
||||
|
||||
|
@ -126,9 +126,14 @@ where
|
|||
} => (transaction, known_utxos, upgrade),
|
||||
};
|
||||
|
||||
let mut redjubjub_verifier = crate::primitives::redjubjub::VERIFIER.clone();
|
||||
let mut spend_verifier = primitives::groth16::SPEND_VERIFIER.clone();
|
||||
let mut output_verifier = primitives::groth16::OUTPUT_VERIFIER.clone();
|
||||
|
||||
let mut redjubjub_verifier = primitives::redjubjub::VERIFIER.clone();
|
||||
let mut script_verifier = self.script_verifier.clone();
|
||||
|
||||
let span = tracing::debug_span!("tx", hash = %tx.hash());
|
||||
|
||||
async move {
|
||||
tracing::trace!(?tx);
|
||||
match &*tx {
|
||||
|
@ -195,11 +200,35 @@ where
|
|||
|
||||
if let Some(shielded_data) = shielded_data {
|
||||
check::shielded_balances_match(&shielded_data, *value_balance)?;
|
||||
for spend in shielded_data.spends() {
|
||||
// TODO: check that spend.cv and spend.rk are NOT of small
|
||||
// order.
|
||||
// https://zips.z.cash/protocol/protocol.pdf#spenddesc
|
||||
|
||||
for spend in shielded_data.spends() {
|
||||
// Consensus rule: cv and rk MUST NOT be of small
|
||||
// order, i.e. [h_J]cv MUST NOT be 𝒪_J and [h_J]rk
|
||||
// MUST NOT be 𝒪_J.
|
||||
//
|
||||
// https://zips.z.cash/protocol/protocol.pdf#spenddesc
|
||||
check::spend_cv_rk_not_small_order(spend)?;
|
||||
|
||||
// Consensus rule: The proof π_ZKSpend MUST be valid
|
||||
// given a primary input formed from the other
|
||||
// fields except spendAuthSig.
|
||||
//
|
||||
// Queue the verification of the Groth16 spend proof
|
||||
// for each Spend description while adding the
|
||||
// resulting future to our collection of async
|
||||
// checks that (at a minimum) must pass for the
|
||||
// transaction to verify.
|
||||
let spend_rsp = spend_verifier
|
||||
.ready_and()
|
||||
.await?
|
||||
.call(primitives::groth16::ItemWrapper::from(spend).into());
|
||||
|
||||
async_checks.push(spend_rsp.boxed());
|
||||
|
||||
// Consensus rule: The spend authorization signature
|
||||
// MUST be a valid SpendAuthSig signature over
|
||||
// SigHash using rk as the validating key.
|
||||
//
|
||||
// Queue the validation of the RedJubjub spend
|
||||
// authorization signature for each Spend
|
||||
// description while adding the resulting future to
|
||||
|
@ -211,32 +240,33 @@ where
|
|||
.call((spend.rk, spend.spend_auth_sig, &sighash).into());
|
||||
|
||||
// Disable pending sighash check #1377
|
||||
//async_checks.push(rsp.boxed());
|
||||
|
||||
// TODO: prepare public inputs for spends, then create
|
||||
// a groth16::Item and pass to self.spend
|
||||
|
||||
// Queue the verification of the Groth16 spend proof
|
||||
// for each Spend description while adding the
|
||||
// resulting future to our collection of async
|
||||
// checks that (at a minimum) must pass for the
|
||||
// transaction to verify.
|
||||
// async_checks.push(rsp.boxed());
|
||||
}
|
||||
|
||||
shielded_data.outputs().for_each(|_output| {
|
||||
// TODO: check that output.cv and output.epk are NOT of small
|
||||
// order.
|
||||
for output in shielded_data.outputs() {
|
||||
// Consensus rule: cv and wpk MUST NOT be of small
|
||||
// order, i.e. [h_J]cv MUST NOT be 𝒪_J and [h_J]wpk
|
||||
// MUST NOT be 𝒪_J.
|
||||
//
|
||||
// https://zips.z.cash/protocol/protocol.pdf#outputdesc
|
||||
check::output_cv_epk_not_small_order(output)?;
|
||||
|
||||
// TODO: prepare public inputs for outputs, then create
|
||||
// a groth16::Item and pass to self.output
|
||||
|
||||
// Consensus rule: The proof π_ZKOutput MUST be
|
||||
// valid given a primary input formed from the other
|
||||
// fields except C^enc and C^out.
|
||||
//
|
||||
// Queue the verification of the Groth16 output
|
||||
// proof for each Output description while adding
|
||||
// the resulting future to our collection of async
|
||||
// checks that (at a minimum) must pass for the
|
||||
// transaction to verify.
|
||||
});
|
||||
let output_rsp = output_verifier
|
||||
.ready_and()
|
||||
.await?
|
||||
.call(primitives::groth16::ItemWrapper::from(output).into());
|
||||
|
||||
async_checks.push(output_rsp.boxed());
|
||||
}
|
||||
|
||||
let bvk = shielded_data.binding_verification_key(*value_balance);
|
||||
let _rsp = redjubjub_verifier
|
||||
|
@ -246,7 +276,7 @@ where
|
|||
.boxed();
|
||||
|
||||
// Disable pending sighash check #1377
|
||||
//async_checks.push(rsp);
|
||||
// async_checks.push(rsp);
|
||||
}
|
||||
|
||||
// Finally, wait for all asynchronous checks to complete
|
||||
|
|
|
@ -7,6 +7,7 @@ use std::convert::TryFrom;
|
|||
use zebra_chain::{
|
||||
amount::Amount,
|
||||
primitives::{ed25519, Groth16Proof},
|
||||
sapling::{Output, Spend},
|
||||
transaction::{JoinSplitData, ShieldedData, Transaction},
|
||||
};
|
||||
|
||||
|
@ -126,3 +127,39 @@ pub fn coinbase_tx_no_joinsplit_or_spend(tx: &Transaction) -> Result<(), Transac
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that a Spend description's cv and rk are not of small order,
|
||||
/// i.e. [h_J]cv MUST NOT be 𝒪_J and [h_J]rk MUST NOT be 𝒪_J.
|
||||
///
|
||||
/// https://zips.z.cash/protocol/protocol.pdf#spenddesc
|
||||
pub fn spend_cv_rk_not_small_order(spend: &Spend) -> Result<(), TransactionError> {
|
||||
if bool::from(spend.cv.0.is_small_order())
|
||||
|| bool::from(
|
||||
jubjub::AffinePoint::from_bytes(spend.rk.into())
|
||||
.unwrap()
|
||||
.is_small_order(),
|
||||
)
|
||||
{
|
||||
Err(TransactionError::SmallOrder)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that a Output description's cv and epk are not of small order,
|
||||
/// i.e. [h_J]cv MUST NOT be 𝒪_J and [h_J]epk MUST NOT be 𝒪_J.
|
||||
///
|
||||
/// https://zips.z.cash/protocol/protocol.pdf#outputdesc
|
||||
pub fn output_cv_epk_not_small_order(output: &Output) -> Result<(), TransactionError> {
|
||||
if bool::from(output.cv.0.is_small_order())
|
||||
|| bool::from(
|
||||
jubjub::AffinePoint::from_bytes(output.ephemeral_key.into())
|
||||
.unwrap()
|
||||
.is_small_order(),
|
||||
)
|
||||
{
|
||||
Err(TransactionError::SmallOrder)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ hex = "0.4"
|
|||
indexmap = { version = "1.6", default-features = false }
|
||||
lazy_static = "1.4.0"
|
||||
pin-project = "0.4"
|
||||
rand = "0.7"
|
||||
rand = "0.8"
|
||||
regex = "1"
|
||||
serde = { version = "1", features = ["serde_derive"] }
|
||||
thiserror = "1"
|
||||
|
|
|
@ -20,7 +20,7 @@ gumdrop = "0.7"
|
|||
serde = { version = "1", features = ["serde_derive"] }
|
||||
toml = "0.5"
|
||||
chrono = "0.4"
|
||||
rand = "0.7"
|
||||
rand = "0.8"
|
||||
|
||||
hyper = { version = "0.14.0-dev", features = ["full"] }
|
||||
futures = "0.3"
|
||||
|
|
|
@ -919,7 +919,9 @@ fn random_known_port() -> u16 {
|
|||
// - macOS and Windows sequential ephemeral port allocations,
|
||||
// starting from 49152:
|
||||
// - https://dataplane.org/ephemeralports.html
|
||||
rand::thread_rng().gen_range(53500, 60999)
|
||||
use rand::Rng;
|
||||
|
||||
rand::thread_rng().gen_range(53500..60999)
|
||||
}
|
||||
|
||||
/// Returns the "magic" port number that tells the operating system to
|
||||
|
|
Loading…
Reference in New Issue