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:
Deirdre Connolly 2021-03-24 12:28:25 -04:00 committed by GitHub
parent e0643f3664
commit 7efc700aca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 743 additions and 286 deletions

166
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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]

View File

@ -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[..])?;

View File

@ -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()

View File

@ -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
}
}

View File

@ -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)?;

View File

@ -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)?;

View File

@ -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 {

View File

@ -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"

View File

@ -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")]

View File

@ -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;

View File

@ -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));
}
}

View File

@ -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)
}
}

View File

@ -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,
})
}
}

View File

@ -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()
}

View File

@ -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())))
}

View File

@ -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

View File

@ -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(())
}
}

View File

@ -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"

View File

@ -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"

View File

@ -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