diff --git a/Cargo.lock b/Cargo.lock index c45886a61da..33aec52b1db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -206,21 +206,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", ] @@ -298,17 +297,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" @@ -382,27 +370,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", ] @@ -994,17 +970,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" @@ -1267,18 +1232,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" @@ -1286,7 +1239,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", ] @@ -1654,8 +1607,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", ] @@ -2064,12 +2017,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]] @@ -2333,12 +2286,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" @@ -2368,7 +2315,7 @@ dependencies = [ "libc", "rand_chacha 0.2.2", "rand_core 0.5.1", - "rand_hc", + "rand_hc 0.2.0", ] [[package]] @@ -2380,6 +2327,7 @@ dependencies = [ "libc", "rand_chacha 0.3.0", "rand_core 0.6.1", + "rand_hc 0.3.0", ] [[package]] @@ -2444,6 +2392,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" @@ -2499,14 +2456,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", ] @@ -3708,6 +3664,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" @@ -3985,7 +3991,7 @@ dependencies = [ "primitive-types", "proptest", "proptest-derive", - "rand_core 0.5.1", + "rand_core 0.6.1", "redjubjub", "ripemd160", "secp256k1", @@ -4008,18 +4014,19 @@ name = "zebra-consensus" version = "1.0.0-alpha.3" 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", @@ -4031,6 +4038,7 @@ dependencies = [ "tracing-error", "tracing-futures", "tracing-subscriber 0.2.16", + "wagyu-zcash-parameters", "zebra-chain", "zebra-script", "zebra-state", @@ -4053,7 +4061,7 @@ dependencies = [ "pin-project 0.4.27", "proptest", "proptest-derive", - "rand 0.7.3", + "rand 0.8.1", "regex", "serde", "thiserror", @@ -4168,7 +4176,7 @@ dependencies = [ "metrics-exporter-prometheus", "once_cell", "pin-project 0.4.27", - "rand 0.7.3", + "rand 0.8.1", "sentry", "sentry-tracing", "serde", diff --git a/tower-batch/Cargo.toml b/tower-batch/Cargo.toml index a9d680eb6ce..6211dd0fb7d 100644 --- a/tower-batch/Cargo.toml +++ b/tower-batch/Cargo.toml @@ -6,19 +6,19 @@ 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"]} +tower-fallback = { path = "../tower-fallback/" } tracing = "0.1.25" zebra-test = { path = "../zebra-test/" } -tower-fallback = { path = "../tower-fallback/" } -color-eyre = "0.5.10" diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index 037134c6481..9a6fca595a4 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -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.1.7" ed25519-zebra = "1" equihash = "0.1" -redjubjub = "0.2" +#redjubjub = "0.2" +redjubjub = {git = "https://github.com/ZcashFoundation/redjubjub", rev = "8101eaff1cb2fca45334f77a65caa4c46e3d545b"} bitflags = "1.2.1" [dev-dependencies] diff --git a/zebra-chain/src/primitives/proofs/groth16.rs b/zebra-chain/src/primitives/proofs/groth16.rs index 41494bbc5f4..8e4b9add64c 100644 --- a/zebra-chain/src/primitives/proofs/groth16.rs +++ b/zebra-chain/src/primitives/proofs/groth16.rs @@ -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 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(&self, mut writer: W) -> Result<(), io::Error> { writer.write_all(&self.0[..])?; diff --git a/zebra-chain/src/sapling/keys.rs b/zebra-chain/src/sapling/keys.rs index 41f0ec0eb31..cd7464080f6 100644 --- a/zebra-chain/src/sapling/keys.rs +++ b/zebra-chain/src/sapling/keys.rs @@ -885,6 +885,12 @@ impl fmt::Debug for EphemeralPublicKey { impl Eq for EphemeralPublicKey {} +impl From 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() diff --git a/zebra-chain/src/sapling/note/nullifiers.rs b/zebra-chain/src/sapling/note/nullifiers.rs index d7fc14187e7..a3b8febd62e 100644 --- a/zebra-chain/src/sapling/note/nullifiers.rs +++ b/zebra-chain/src/sapling/note/nullifiers.rs @@ -52,3 +52,41 @@ impl From for [u8; 32] { n.0 } } + +impl From 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 = + 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 + } +} diff --git a/zebra-chain/src/sapling/output.rs b/zebra-chain/src/sapling/output.rs index 0cbd252d741..9300705664b 100644 --- a/zebra-chain/src/sapling/output.rs +++ b/zebra-chain/src/sapling/output.rs @@ -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 { + 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(&self, mut writer: W) -> Result<(), io::Error> { self.cv.zcash_serialize(&mut writer)?; diff --git a/zebra-chain/src/sapling/spend.rs b/zebra-chain/src/sapling/spend.rs index de9a2dd6549..a599b721bc6 100644 --- a/zebra-chain/src/sapling/spend.rs +++ b/zebra-chain/src/sapling/spend.rs @@ -31,6 +31,35 @@ pub struct Spend { pub spend_auth_sig: redjubjub::Signature, } +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 { + 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(&self, mut writer: W) -> Result<(), io::Error> { self.cv.zcash_serialize(&mut writer)?; diff --git a/zebra-chain/src/sapling/tree.rs b/zebra-chain/src/sapling/tree.rs index ad45b40fa91..38b8485b9f8 100644 --- a/zebra-chain/src/sapling/tree.rs +++ b/zebra-chain/src/sapling/tree.rs @@ -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 for [u8; 32] { + fn from(root: Root) -> Self { + root.0 + } +} + /// Sapling Note Commitment Tree #[derive(Clone, Debug, Default, Eq, PartialEq)] struct NoteCommitmentTree { diff --git a/zebra-consensus/Cargo.toml b/zebra-consensus/Cargo.toml index ba1fedb18d7..6a0aeb18fe3 100644 --- a/zebra-consensus/Cargo.toml +++ b/zebra-consensus/Cargo.toml @@ -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.1.7" 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" diff --git a/zebra-consensus/src/error.rs b/zebra-consensus/src/error.rs index 9adf31fe6fb..48cd020720e 100644 --- a/zebra-consensus/src/error.rs +++ b/zebra-consensus/src/error.rs @@ -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 for TransactionError { fn from(err: BoxError) -> Self { - match err.downcast::() { + match err.downcast::() { 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")] diff --git a/zebra-consensus/src/lib.rs b/zebra-consensus/src/lib.rs index 798be109138..7aa2d803500 100644 --- a/zebra-consensus/src/lib.rs +++ b/zebra-consensus/src/lib.rs @@ -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; diff --git a/zebra-consensus/src/primitives/groth16.rs b/zebra-consensus/src/primitives/groth16.rs index ed3a8faa189..382c1dcb35c 100644 --- a/zebra-consensus/src/primitives/groth16.rs +++ b/zebra-consensus/src/primitives/groth16.rs @@ -9,120 +9,117 @@ 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; -// === 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 { - proof: Proof, - public_inputs: Vec, -} - -impl Item { - fn verify_single(self, pvk: &PreparedVerifyingKey) -> Result<(), VerificationError> { - let Item { - proof, - public_inputs, - } = self; - - bellman::groth16::verify_proof(pvk, &proof, &public_inputs) - } -} - -impl From<(&Proof, &[E::Fr])> for Item { - fn from((proof, public_inputs): (&Proof, &[E::Fr])) -> Self { - (proof.clone(), public_inputs.to_owned()).into() - } -} - -impl From<(Proof, Vec)> for Item { - fn from((proof, public_inputs): (Proof, Vec)) -> Self { - Self { - proof, - public_inputs, - } - } -} - -#[derive(Default)] -struct Batch { - queue: Vec>, -} - -impl Batch { - fn queue(&mut self, item: Item) { - self.queue.push(item); - } - - fn verify( - self, - _rng: R, - pvk: &PreparedVerifyingKey, - ) -> 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; - -/// Groth16 signature verifier service -#[derive(Clone, Debug)] -pub struct Verifier { - inner: Fallback>, FallbackVerifierImpl>, -} - -impl Verifier { - /// Constructs a new verifier. - pub fn new(pvk: &'static PreparedVerifyingKey) -> 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> for Verifier { - type Response = (); - type Error = BoxError; - type Future = Pin> + Send + 'static>>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.inner.poll_ready(cx) - } - - fn call(&mut self, req: Item) -> Self::Future { - use futures::FutureExt; - self.inner.call(req).boxed() +/// 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, ServiceFn Ready>>>, +> = 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(_) -> _, + ), + ) +}); + +/// 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, ServiceFn Ready>>>, +> = 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(_) -> _, + ), + ) +}); + +/// A Groth16 verification item, used as the request type of the service. +pub type Item = batch::Item; + +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 From<&Output> for ItemWrapper { + fn from(output: &Output) -> Self { + Self(Item::from(( + bellman::groth16::Proof::read(&output.zkproof.0[..]).unwrap(), + output.primary_inputs(), + ))) + } +} + +impl From for Item { + fn from(item_wrapper: ItemWrapper) -> Self { + item_wrapper.0 } } @@ -131,37 +128,35 @@ impl Service> 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, - batch: Batch, +pub struct Verifier { + batch: batch::Verifier, // Making this 'static makes managing lifetimes much easier. - pvk: &'static PreparedVerifyingKey, + vk: &'static VerifyingKey, /// Broadcast sender used to send the result of a batch verification to each /// request source in the batch. tx: Sender>, } -impl VerifierImpl { - fn new(pvk: &'static PreparedVerifyingKey) -> Self { - // let batch = batch::Verifier::default(); - let batch = Batch::default(); +impl Verifier { + fn new(vk: &'static VerifyingKey) -> Self { + let batch = batch::Verifier::default(); let (tx, _) = channel(super::BROADCAST_BUFFER_SIZE); - Self { batch, tx, pvk } + 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>> for VerifierImpl { +impl Service> for Verifier { type Response = (); type Error = VerificationError; type Future = Pin> + Send + 'static>>; @@ -170,7 +165,7 @@ impl Service>> for VerifierImpl { Poll::Ready(Ok(())) } - fn call(&mut self, req: BatchControl>) -> Self::Future { + fn call(&mut self, req: BatchControl) -> Self::Future { match req { BatchControl::Item(item) => { tracing::trace!("got item"); @@ -193,52 +188,17 @@ impl Service>> 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, -} - -impl FallbackVerifierImpl { - fn new(pvk: &'static PreparedVerifyingKey) -> 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> for FallbackVerifierImpl { - type Response = (); - type Error = VerificationError; - type Future = Pin> + Send + 'static>>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, item: Item) -> 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)); } } diff --git a/zebra-consensus/src/primitives/groth16/hash_reader.rs b/zebra-consensus/src/primitives/groth16/hash_reader.rs new file mode 100644 index 00000000000..165e004dca2 --- /dev/null +++ b/zebra-consensus/src/primitives/groth16/hash_reader.rs @@ -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 { + reader: R, + hasher: State, +} + +impl HashReader { + /// 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 Read for HashReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let bytes = self.reader.read(buf)?; + + if bytes > 0 { + self.hasher.update(&buf[0..bytes]); + } + + Ok(bytes) + } +} diff --git a/zebra-consensus/src/primitives/groth16/params.rs b/zebra-consensus/src/primitives/groth16/params.rs new file mode 100644 index 00000000000..80c2c1d3bc9 --- /dev/null +++ b/zebra-consensus/src/primitives/groth16/params.rs @@ -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, + pub spend_prepared_verifying_key: groth16::PreparedVerifyingKey, + + pub output: groth16::Parameters, + pub output_prepared_verifying_key: groth16::PreparedVerifyingKey, +} + +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(spend_fs: R, output_fs: R) -> Result { + let mut spend_fs = HashReader::new(spend_fs); + let mut output_fs = HashReader::new(output_fs); + + // Deserialize params + let spend = groth16::Parameters::::read(&mut spend_fs, false)?; + let output = groth16::Parameters::::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, + }) + } +} diff --git a/zebra-consensus/src/primitives/groth16/tests.rs b/zebra-consensus/src/primitives/groth16/tests.rs new file mode 100644 index 00000000000..b94f2c340fe --- /dev/null +++ b/zebra-consensus/src/primitives/groth16/tests.rs @@ -0,0 +1,84 @@ +//! Tests for redjubjub signature verification + +use super::*; + +use std::time::Duration; + +use color_eyre::eyre::{eyre, Result}; +use futures::stream::{FuturesUnordered, StreamExt}; +use tower::ServiceExt; +use tower_batch::Batch; + +async fn sign_and_verify(mut verifier: V, n: usize) -> Result<(), V::Error> +where + V: Service, +{ + let rng = thread_rng(); + let mut results = FuturesUnordered::new(); + for i in 0..n { + let span = tracing::trace_span!("sig", i); + let msg = b"BatchVerifyTest"; + + match i % 2 { + 0 => { + let sk = SigningKey::::new(rng); + let vk = VerificationKey::from(&sk); + let sig = sk.sign(rng, &msg[..]); + verifier.ready_and().await?; + results.push(span.in_scope(|| verifier.call((vk.into(), sig, msg).into()))) + } + 1 => { + let sk = SigningKey::::new(rng); + let vk = VerificationKey::from(&sk); + let sig = sk.sign(rng, &msg[..]); + verifier.ready_and().await?; + results.push(span.in_scope(|| verifier.call((vk.into(), sig, msg).into()))) + } + _ => panic!(), + } + } + + while let Some(result) = results.next().await { + result?; + } + + Ok(()) +} + +#[tokio::test] +async fn batch_flushes_on_max_items_test() -> Result<()> { + batch_flushes_on_max_items().await +} + +#[spandoc::spandoc] +async fn batch_flushes_on_max_items() -> Result<()> { + use tokio::time::timeout; + + // Use a very long max_latency and a short timeout to check that + // flushing is happening based on hitting max_items. + let verifier = Batch::new(Verifier::default(), 10, Duration::from_secs(1000)); + timeout(Duration::from_secs(5), sign_and_verify(verifier, 100)) + .await? + .map_err(|e| eyre!(e))?; + + Ok(()) +} + +#[tokio::test] +async fn batch_flushes_on_max_latency_test() -> Result<()> { + batch_flushes_on_max_latency().await +} + +#[spandoc::spandoc] +async fn batch_flushes_on_max_latency() -> Result<()> { + use tokio::time::timeout; + + // Use a very high max_items and a short timeout to check that + // flushing is happening based on hitting max_latency. + let verifier = Batch::new(Verifier::default(), 100, Duration::from_millis(500)); + timeout(Duration::from_secs(5), sign_and_verify(verifier, 10)) + .await? + .map_err(|e| eyre!(e))?; + + Ok(()) +} diff --git a/zebra-consensus/src/primitives/redjubjub/tests.rs b/zebra-consensus/src/primitives/redjubjub/tests.rs index b94f2c340fe..0822e0dfbfa 100644 --- a/zebra-consensus/src/primitives/redjubjub/tests.rs +++ b/zebra-consensus/src/primitives/redjubjub/tests.rs @@ -13,7 +13,7 @@ async fn sign_and_verify(mut verifier: V, n: usize) -> Result<(), V::Error> where V: Service, { - 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::::new(rng); + let sk = SigningKey::::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::::new(rng); + let sk = SigningKey::::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()))) } diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index ae684ae9fa4..7b51b9b4af2 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -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. + // 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 diff --git a/zebra-consensus/src/transaction/check.rs b/zebra-consensus/src/transaction/check.rs index 40e199edaad..bf6b51507af 100644 --- a/zebra-consensus/src/transaction/check.rs +++ b/zebra-consensus/src/transaction/check.rs @@ -7,6 +7,7 @@ use std::convert::TryFrom; use zebra_chain::{ amount::Amount, primitives::{ed25519, Groth16Proof}, + sapling::{Output, Spend}, transaction::{JoinSplitData, ShieldedData, Transaction}, }; @@ -119,3 +120,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(()) + } +} diff --git a/zebra-network/Cargo.toml b/zebra-network/Cargo.toml index 40ee0d57e1e..c4187781f28 100644 --- a/zebra-network/Cargo.toml +++ b/zebra-network/Cargo.toml @@ -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" diff --git a/zebrad/Cargo.toml b/zebrad/Cargo.toml index 1fa3f3a6de1..e247b898ddf 100644 --- a/zebrad/Cargo.toml +++ b/zebrad/Cargo.toml @@ -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" diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 032b75974de..6cae5e087e4 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -918,7 +918,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