diff --git a/Cargo.toml b/Cargo.toml index 3d55f89..48e300a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,46 +1,7 @@ -[package] -name = "redjubjub" -edition = "2018" -# When releasing to crates.io: -# - Update html_root_url -# - Update CHANGELOG.md -# - Create git tag. -version = "0.4.0" -authors = ["Henry de Valence ", "Deirdre Connolly ", "Chelsea Komlo "] -readme = "README.md" -license = "MIT OR Apache-2.0" -repository = "https://github.com/ZcashFoundation/redjubjub" -categories = ["cryptography"] -keywords = ["cryptography", "crypto", "jubjub", "redjubjub", "zcash"] -description = "A standalone implementation of the RedJubjub signature scheme." - -[package.metadata.docs.rs] -features = ["nightly"] - -[dependencies] -blake2b_simd = "0.5" -byteorder = "1.4" -digest = "0.9" -jubjub = "0.8" -rand_core = "0.6" -serde = { version = "1", optional = true, features = ["derive"] } -thiserror = "1.0" -zeroize = { version = "1", default-features = false, features = ["zeroize_derive"] } - -[dev-dependencies] -bincode = "1" -criterion = "0.3" -proptest-derive = "0.3" -lazy_static = "1.4" -proptest = "1.0" -rand = "0.8" -rand_chacha = "0.3" -serde_json = "1.0" - -[features] -nightly = [] -default = ["serde"] - -[[bench]] -name = "bench" -harness = false +[workspace] +resolver = "2" +members = [ + "frost-core", + "frost-redjubjub", + "frost-ristretto255", +] diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 7c2ed31..0000000 --- a/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -FROM rust:stretch as base - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - make cmake g++ gcc - -RUN mkdir /redjubjub -WORKDIR /redjubjub - -ENV RUST_BACKTRACE 1 -ENV CARGO_HOME /redjubjub/.cargo/ - -# Copy local code to the container image. -# Assumes that we are in the git repo. - -COPY . . - -RUN cargo fetch --verbose - -COPY . . - -RUN rustc -V; cargo -V; rustup -V; cargo test --all && cargo build --release diff --git a/README.md b/README.md index d041c08..65f862e 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,2 @@ -A minimal [RedJubjub][redjubjub] implementation for use in [Zebra][zebra]. +FROST (Flexible Round-Optimised Schnorr Threshold signature) implementations. -Two parameterizations of RedJubjub are used in Zcash, one for -`BindingSig` and one for `SpendAuthSig`. This library distinguishes -these in the type system, using the [sealed] `SigType` trait as a -type-level enum. - -In addition to the `Signature`, `SigningKey`, `VerificationKey` types, -the library also provides `VerificationKeyBytes`, a [refinement] of a -`[u8; 32]` indicating that bytes represent an encoding of a RedJubjub -verification key. This allows the `VerificationKey` type to cache -verification checks related to the verification key encoding. - -## Examples - -Creating a `BindingSig`, serializing and deserializing it, and -verifying the signature: - -```rust -# use std::convert::TryFrom; -use rand::thread_rng; -use redjubjub::*; - -let msg = b"Hello!"; - -// Generate a secret key and sign the message -let sk = SigningKey::::new(thread_rng()); -let sig = sk.sign(thread_rng(), msg); - -// Types can be converted to raw byte arrays using From/Into -let sig_bytes: [u8; 64] = sig.into(); -let pk_bytes: [u8; 32] = VerificationKey::from(&sk).into(); - -// Deserialize and verify the signature. -let sig: Signature = sig_bytes.into(); -assert!( - VerificationKey::try_from(pk_bytes) - .and_then(|pk| pk.verify(msg, &sig)) - .is_ok() -); -``` - -## docs - -```shell,no_run -cargo doc --features "nightly" --open -``` - -[redjubjub]: https://zips.z.cash/protocol/protocol.pdf#concretereddsa -[zebra]: https://github.com/ZcashFoundation/zebra -[refinement]: https://en.wikipedia.org/wiki/Refinement_type -[sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed diff --git a/cloudbuild.yaml b/cloudbuild.yaml deleted file mode 100644 index f531a90..0000000 --- a/cloudbuild.yaml +++ /dev/null @@ -1,11 +0,0 @@ -steps: -- name: 'gcr.io/kaniko-project/executor:latest' - args: - - --destination=gcr.io/$PROJECT_ID/$BRANCH_NAME - - --cache=true - - --cache-ttl=24h - -options: - machineType: 'N1_HIGHCPU_32' - -timeout: 3600s # One hour for all steps. diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml new file mode 100644 index 0000000..aa519de --- /dev/null +++ b/frost-core/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "frost-core" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs new file mode 100644 index 0000000..1b4a90c --- /dev/null +++ b/frost-core/src/lib.rs @@ -0,0 +1,8 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + let result = 2 + 2; + assert_eq!(result, 4); + } +} diff --git a/frost-redjubjub/Cargo.toml b/frost-redjubjub/Cargo.toml new file mode 100644 index 0000000..3d55f89 --- /dev/null +++ b/frost-redjubjub/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "redjubjub" +edition = "2018" +# When releasing to crates.io: +# - Update html_root_url +# - Update CHANGELOG.md +# - Create git tag. +version = "0.4.0" +authors = ["Henry de Valence ", "Deirdre Connolly ", "Chelsea Komlo "] +readme = "README.md" +license = "MIT OR Apache-2.0" +repository = "https://github.com/ZcashFoundation/redjubjub" +categories = ["cryptography"] +keywords = ["cryptography", "crypto", "jubjub", "redjubjub", "zcash"] +description = "A standalone implementation of the RedJubjub signature scheme." + +[package.metadata.docs.rs] +features = ["nightly"] + +[dependencies] +blake2b_simd = "0.5" +byteorder = "1.4" +digest = "0.9" +jubjub = "0.8" +rand_core = "0.6" +serde = { version = "1", optional = true, features = ["derive"] } +thiserror = "1.0" +zeroize = { version = "1", default-features = false, features = ["zeroize_derive"] } + +[dev-dependencies] +bincode = "1" +criterion = "0.3" +proptest-derive = "0.3" +lazy_static = "1.4" +proptest = "1.0" +rand = "0.8" +rand_chacha = "0.3" +serde_json = "1.0" + +[features] +nightly = [] +default = ["serde"] + +[[bench]] +name = "bench" +harness = false diff --git a/benches/bench.rs b/frost-redjubjub/benches/bench.rs similarity index 100% rename from benches/bench.rs rename to frost-redjubjub/benches/bench.rs diff --git a/src/batch.rs b/frost-redjubjub/src/batch.rs similarity index 100% rename from src/batch.rs rename to frost-redjubjub/src/batch.rs diff --git a/src/constants.rs b/frost-redjubjub/src/constants.rs similarity index 100% rename from src/constants.rs rename to frost-redjubjub/src/constants.rs diff --git a/src/error.rs b/frost-redjubjub/src/error.rs similarity index 100% rename from src/error.rs rename to frost-redjubjub/src/error.rs diff --git a/src/frost.rs b/frost-redjubjub/src/frost.rs similarity index 100% rename from src/frost.rs rename to frost-redjubjub/src/frost.rs diff --git a/src/hash.rs b/frost-redjubjub/src/hash.rs similarity index 100% rename from src/hash.rs rename to frost-redjubjub/src/hash.rs diff --git a/src/lib.rs b/frost-redjubjub/src/lib.rs similarity index 100% rename from src/lib.rs rename to frost-redjubjub/src/lib.rs diff --git a/src/messages.rs b/frost-redjubjub/src/messages.rs similarity index 100% rename from src/messages.rs rename to frost-redjubjub/src/messages.rs diff --git a/src/messages/arbitrary.rs b/frost-redjubjub/src/messages/arbitrary.rs similarity index 100% rename from src/messages/arbitrary.rs rename to frost-redjubjub/src/messages/arbitrary.rs diff --git a/src/messages/constants.rs b/frost-redjubjub/src/messages/constants.rs similarity index 100% rename from src/messages/constants.rs rename to frost-redjubjub/src/messages/constants.rs diff --git a/src/messages/serialize.rs b/frost-redjubjub/src/messages/serialize.rs similarity index 100% rename from src/messages/serialize.rs rename to frost-redjubjub/src/messages/serialize.rs diff --git a/src/messages/tests.rs b/frost-redjubjub/src/messages/tests.rs similarity index 100% rename from src/messages/tests.rs rename to frost-redjubjub/src/messages/tests.rs diff --git a/src/messages/tests/integration.rs b/frost-redjubjub/src/messages/tests/integration.rs similarity index 100% rename from src/messages/tests/integration.rs rename to frost-redjubjub/src/messages/tests/integration.rs diff --git a/src/messages/tests/prop.rs b/frost-redjubjub/src/messages/tests/prop.rs similarity index 100% rename from src/messages/tests/prop.rs rename to frost-redjubjub/src/messages/tests/prop.rs diff --git a/src/messages/validate.rs b/frost-redjubjub/src/messages/validate.rs similarity index 100% rename from src/messages/validate.rs rename to frost-redjubjub/src/messages/validate.rs diff --git a/src/scalar_mul.rs b/frost-redjubjub/src/scalar_mul.rs similarity index 100% rename from src/scalar_mul.rs rename to frost-redjubjub/src/scalar_mul.rs diff --git a/src/signature.rs b/frost-redjubjub/src/signature.rs similarity index 100% rename from src/signature.rs rename to frost-redjubjub/src/signature.rs diff --git a/src/signing_key.rs b/frost-redjubjub/src/signing_key.rs similarity index 100% rename from src/signing_key.rs rename to frost-redjubjub/src/signing_key.rs diff --git a/src/verification_key.rs b/frost-redjubjub/src/verification_key.rs similarity index 100% rename from src/verification_key.rs rename to frost-redjubjub/src/verification_key.rs diff --git a/tests/batch.rs b/frost-redjubjub/tests/batch.rs similarity index 100% rename from tests/batch.rs rename to frost-redjubjub/tests/batch.rs diff --git a/tests/bincode.rs b/frost-redjubjub/tests/bincode.rs similarity index 100% rename from tests/bincode.rs rename to frost-redjubjub/tests/bincode.rs diff --git a/tests/frost.rs b/frost-redjubjub/tests/frost.rs similarity index 100% rename from tests/frost.rs rename to frost-redjubjub/tests/frost.rs diff --git a/tests/librustzcash_vectors.rs b/frost-redjubjub/tests/librustzcash_vectors.rs similarity index 100% rename from tests/librustzcash_vectors.rs rename to frost-redjubjub/tests/librustzcash_vectors.rs diff --git a/tests/proptests.proptest-regressions b/frost-redjubjub/tests/proptests.proptest-regressions similarity index 100% rename from tests/proptests.proptest-regressions rename to frost-redjubjub/tests/proptests.proptest-regressions diff --git a/tests/proptests.rs b/frost-redjubjub/tests/proptests.rs similarity index 100% rename from tests/proptests.rs rename to frost-redjubjub/tests/proptests.rs diff --git a/tests/smallorder.rs b/frost-redjubjub/tests/smallorder.rs similarity index 100% rename from tests/smallorder.rs rename to frost-redjubjub/tests/smallorder.rs diff --git a/zcash-frost-audit-report-20210323.pdf b/frost-redjubjub/zcash-frost-audit-report-20210323.pdf similarity index 100% rename from zcash-frost-audit-report-20210323.pdf rename to frost-redjubjub/zcash-frost-audit-report-20210323.pdf diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml new file mode 100644 index 0000000..911c83a --- /dev/null +++ b/frost-ristretto255/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "frost-ristretto255" +edition = "2018" +# When releasing to crates.io: +# - Update html_root_url +# - Update CHANGELOG.md +# - Create git tag. +version = "0.1.0" +authors = ["Henry de Valence ", "Deirdre Connolly ", "Chelsea Komlo "] +readme = "README.md" +license = "MIT OR Apache-2.0" +repository = "https://github.com/ZcashFoundation/frost" +categories = ["cryptography"] +keywords = ["cryptography", "crypto", "ristretto", "threshold", "signature"] +description = "A Schnorr signature scheme over the prime-order Ristretto group that supports FROST ." + +[package.metadata.docs.rs] +features = ["nightly"] + +[dependencies] +byteorder = "1.4" +curve25519-dalek = "4.0.0-pre.1" +digest = "0.9" +rand_core = "0.6" +serde = { version = "1", optional = true, features = ["derive"] } +sha2 = "0.9.0" +thiserror = "1.0" +zeroize = { version = "1", default-features = false, features = ["zeroize_derive"] } + +[dev-dependencies] +bincode = "1" +criterion = "0.3" +proptest-derive = "0.3" +lazy_static = "1.4" +proptest = "1.0" +rand = "0.8" +rand_chacha = "0.3" +serde_json = "1.0" + +[features] +nightly = [] +default = ["serde"] + +[[bench]] +name = "bench" +harness = false diff --git a/frost-ristretto255/README.md b/frost-ristretto255/README.md new file mode 100644 index 0000000..64addf7 --- /dev/null +++ b/frost-ristretto255/README.md @@ -0,0 +1,45 @@ +An implementation of Schnorr sigantures on the Ristretto group for both single and threshold numbers +of signers (FROST). + +In addition to the `Signature`, `SigningKey`, `VerificationKey` types, the library also provides +`VerificationKeyBytes`, a [refinement] of a `[u8; 32]` indicating that bytes represent an encoding +of averification key. This allows the `VerificationKey` type to cache verification checks related to +the verification key encoding. + +## Examples + +Creating a `Signature` with a single signer, serializing and deserializing it, and verifying the +signature: + +```rust +# use std::convert::TryFrom; +use rand::thread_rng; +use frost-ristretto255::*; + +let msg = b"Hello!"; + +// Generate a secret key and sign the message +let sk = SigningKey::new(thread_rng()); +let sig = sk.sign(thread_rng(), msg); + +// Types can be converted to raw byte arrays using From/Into +let sig_bytes: [u8; 64] = sig.into(); +let pk_bytes: [u8; 32] = VerificationKey::from(&sk).into(); + +// Deserialize and verify the signature. +let sig: Signature = sig_bytes.into(); +assert!( + VerificationKey::try_from(pk_bytes) + .and_then(|pk| pk.verify(msg, &sig)) + .is_ok() +); +``` + +## docs + +```shell,no_run +cargo doc --features "nightly" --open +``` + +[redjubjub]: https://zips.z.cash/protocol/protocol.pdf#concretereddsa +[refinement]: https://en.wikipedia.org/wiki/Refinement_type diff --git a/frost-ristretto255/benches/bench.rs b/frost-ristretto255/benches/bench.rs new file mode 100644 index 0000000..cd9777e --- /dev/null +++ b/frost-ristretto255/benches/bench.rs @@ -0,0 +1,95 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; + +use rand::{thread_rng, Rng}; +use redjubjub::*; +use std::convert::TryFrom; + +enum Item { + SpendAuth { + vk_bytes: VerificationKeyBytes, + sig: Signature, + }, + Binding { + vk_bytes: VerificationKeyBytes, + sig: Signature, + }, +} + +fn sigs_with_distinct_keys() -> impl Iterator { + std::iter::repeat_with(|| { + let mut rng = thread_rng(); + let msg = b"Bench"; + match rng.gen::() % 2 { + 0 => { + let sk = SigningKey::::new(thread_rng()); + let vk_bytes = VerificationKey::from(&sk).into(); + let sig = sk.sign(thread_rng(), &msg[..]); + Item::SpendAuth { vk_bytes, sig } + } + 1 => { + let sk = SigningKey::::new(thread_rng()); + let vk_bytes = VerificationKey::from(&sk).into(); + let sig = sk.sign(thread_rng(), &msg[..]); + Item::Binding { vk_bytes, sig } + } + _ => panic!(), + } + }) +} + +fn bench_batch_verify(c: &mut Criterion) { + let mut group = c.benchmark_group("Batch Verification"); + for &n in [8usize, 16, 24, 32, 40, 48, 56, 64].iter() { + group.throughput(Throughput::Elements(n as u64)); + + let sigs = sigs_with_distinct_keys().take(n).collect::>(); + + group.bench_with_input( + BenchmarkId::new("Unbatched verification", n), + &sigs, + |b, sigs| { + b.iter(|| { + for item in sigs.iter() { + let msg = b"Bench"; + match item { + Item::SpendAuth { vk_bytes, sig } => { + let _ = VerificationKey::try_from(*vk_bytes) + .and_then(|vk| vk.verify(msg, sig)); + } + Item::Binding { vk_bytes, sig } => { + let _ = VerificationKey::try_from(*vk_bytes) + .and_then(|vk| vk.verify(msg, sig)); + } + } + } + }) + }, + ); + + group.bench_with_input( + BenchmarkId::new("Batched verification", n), + &sigs, + |b, sigs| { + b.iter(|| { + let mut batch = batch::Verifier::new(); + for item in sigs.iter() { + let msg = b"Bench"; + match item { + Item::SpendAuth { vk_bytes, sig } => { + batch.queue((*vk_bytes, *sig, msg)); + } + Item::Binding { vk_bytes, sig } => { + batch.queue((*vk_bytes, *sig, msg)); + } + } + } + batch.verify(thread_rng()) + }) + }, + ); + } + group.finish(); +} + +criterion_group!(benches, bench_batch_verify); +criterion_main!(benches); diff --git a/frost-ristretto255/src/batch.rs b/frost-ristretto255/src/batch.rs new file mode 100644 index 0000000..95ef5da --- /dev/null +++ b/frost-ristretto255/src/batch.rs @@ -0,0 +1,172 @@ +// -*- mode: rust; -*- +// +// This file is part of frost-ristretto255. +// Copyright (c) 2019-2021 Zcash Foundation +// See LICENSE for licensing information. +// +// Authors: +// - Deirdre Connolly +// - Henry de Valence + +//! Performs batch Schnorr signature verification on the Ristretto group. +//! +//! Batch verification asks whether *all* signatures in some set are valid, +//! rather than asking whether *each* of them is valid. This allows sharing +//! computations among all signature verifications, performing less work overall +//! at the cost of higher latency (the entire batch must complete), complexity +//! of caller code (which must assemble a batch of signatures across +//! work-items), and loss of the ability to easily pinpoint failing signatures. + +use std::convert::TryFrom; + +use curve25519_dalek::{ + ristretto::{CompressedRistretto, RistrettoPoint}, + scalar::Scalar, + traits::{Identity, VartimeMultiscalarMul}, +}; +use rand_core::{CryptoRng, RngCore}; +use sha2::{Digest, Sha512}; + +use crate::*; + +/// A batch verification item. +/// +/// This struct exists to allow batch processing to be decoupled from the +/// lifetime of the message. This is useful when using the batch verification +/// API in an async context. +#[derive(Clone, Debug)] +pub struct Item { + vk_bytes: VerificationKeyBytes, + sig: Signature, + c: Scalar, +} + +impl<'msg, M: AsRef<[u8]>> From<(VerificationKeyBytes, Signature, &'msg M)> for Item { + fn from((vk_bytes, sig, msg): (VerificationKeyBytes, Signature, &'msg M)) -> Self { + // Compute c now to avoid dependency on the msg lifetime. + let c = Scalar::from_hash( + Sha512::new() + .chain(&sig.r_bytes[..]) + .chain(&vk_bytes.bytes[..]) + .chain(msg), + ); + Self { vk_bytes, sig, c } + } +} + +impl Item { + /// Perform non-batched verification of this `Item`. + /// + /// This is useful (in combination with `Item::clone`) for implementing + /// fallback logic when batch verification fails. In contrast to + /// [`VerificationKey::verify`](crate::VerificationKey::verify), which + /// requires borrowing the message data, the `Item` type is unlinked + /// from the lifetime of the message. + #[allow(non_snake_case)] + pub fn verify_single(self) -> Result<(), Error> { + VerificationKey::try_from(self.vk_bytes) + .and_then(|vk| vk.verify_prehashed(&self.sig, self.c)) + } +} + +#[derive(Default)] +/// A batch verification context. +pub struct Verifier { + /// Signature data queued for verification. + signatures: Vec, +} + +impl Verifier { + /// Construct a new batch verifier. + pub fn new() -> Verifier { + Verifier::default() + } + + /// Queue an Item for verification. + pub fn queue>(&mut self, item: I) { + self.signatures.push(item.into()); + } + + /// Perform batch verification, returning `Ok(())` if all signatures were + /// valid and `Err` otherwise. + /// + /// The batch verification equation is: + /// + /// h_G * -[sum(z_i * s_i)]P_G + sum(\[z_i\]R_i + [z_i * c_i]VK_i) = 0_G + /// + /// which we split out into: + /// + /// h_G * -[sum(z_i * s_i)]P_G + sum(\[z_i\]R_i) + sum([z_i * c_i]VK_i) = + /// 0_G + /// + /// so that we can use multiscalar multiplication speedups. + /// + /// where for each signature i, + /// - VK_i is the verification key; + /// - R_i is the signature's R value; + /// - s_i is the signature's s value; + /// - c_i is the hash of the message and other data; + /// - z_i is a random 128-bit Scalar; + /// - h_G is the cofactor of the group; + /// - P_G is the generator of the subgroup; + /// + /// As follows elliptic curve scalar multiplication convention, + /// scalar variables are lowercase and group point variables + /// are uppercase. This does not exactly match the RedDSA + /// notation in the [protocol specification §B.1][ps]. + /// + /// [ps]: https://zips.z.cash/protocol/protocol.pdf#reddsabatchverify + #[allow(non_snake_case)] + pub fn verify(self, mut rng: R) -> Result<(), Error> { + let n = self.signatures.len(); + + let mut VK_coeffs = Vec::with_capacity(n); + let mut VKs = Vec::with_capacity(n); + let mut R_coeffs = Vec::with_capacity(self.signatures.len()); + let mut Rs = Vec::with_capacity(self.signatures.len()); + let mut P_coeff_acc = Scalar::zero(); + + for item in self.signatures.iter() { + let (s_bytes, r_bytes, c) = (item.sig.s_bytes, item.sig.r_bytes, item.c); + + let s = Scalar::from_bytes_mod_order(s_bytes); + + let R = { + match CompressedRistretto::from_slice(&r_bytes).decompress() { + Some(point) => point, + None => return Err(Error::InvalidSignature), + } + }; + + let VK = VerificationKey::try_from(item.vk_bytes.bytes)?.point; + + let z = Scalar::random(&mut rng); + + let P_coeff = z * s; + P_coeff_acc -= P_coeff; + + R_coeffs.push(z); + Rs.push(R); + + VK_coeffs.push(Scalar::zero() + (z * c)); + VKs.push(VK); + } + + use std::iter::once; + + let scalars = once(&P_coeff_acc) + .chain(VK_coeffs.iter()) + .chain(R_coeffs.iter()); + + let basepoints = [curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT]; + let points = basepoints.iter().chain(VKs.iter()).chain(Rs.iter()); + + let check = RistrettoPoint::vartime_multiscalar_mul(scalars, points); + + if check == RistrettoPoint::identity() { + Ok(()) + } else { + Err(Error::InvalidSignature) + } + } +} diff --git a/frost-ristretto255/src/error.rs b/frost-ristretto255/src/error.rs new file mode 100644 index 0000000..e130f45 --- /dev/null +++ b/frost-ristretto255/src/error.rs @@ -0,0 +1,25 @@ +// -*- mode: rust; -*- +// +// This file is part of redjubjub. +// Copyright (c) 2019-2021 Zcash Foundation +// See LICENSE for licensing information. +// +// Authors: +// - Deirdre Connolly +// - Henry de Valence + +use thiserror::Error; + +/// An error related to RedJubJub signatures. +#[derive(Error, Debug, Copy, Clone, Eq, PartialEq)] +pub enum Error { + /// The encoding of a signing key was malformed. + #[error("Malformed signing key encoding.")] + MalformedSigningKey, + /// The encoding of a verification key was malformed. + #[error("Malformed verification key encoding.")] + MalformedVerificationKey, + /// Signature verification failed. + #[error("Invalid signature.")] + InvalidSignature, +} diff --git a/frost-ristretto255/src/frost.rs b/frost-ristretto255/src/frost.rs new file mode 100644 index 0000000..f19976e --- /dev/null +++ b/frost-ristretto255/src/frost.rs @@ -0,0 +1,738 @@ +// -*- mode: rust; -*- +// +// This file is part of frost-ristretto255. +// Copyright (c) 2020-2021 Zcash Foundation +// See LICENSE for licensing information. +// +// Authors: +// - Chelsea H. Komlo +// - Deirdre Connolly +// - isis agora lovecruft + +//! An implementation of FROST (Flexible Round-Optimized Schnorr Threshold) +//! signatures. +//! +//! This implementation has been [independently +//! audited](https://github.com/ZcashFoundation/redjubjub/blob/main/zcash-frost-audit-report-20210323.pdf) +//! as of commit 76ba4ef / March 2021. If you are interested in deploying +//! FROST, please do not hesitate to consult the FROST authors. +//! +//! This implementation currently only supports key generation using a central +//! dealer. In the future, we will add support for key generation via a DKG, +//! as specified in the FROST paper. +//! Internally, keygen_with_dealer generates keys using Verifiable Secret +//! Sharing, where shares are generated using Shamir Secret Sharing. + +use std::{collections::HashMap, convert::TryFrom}; + +use curve25519_dalek::{ + constants::RISTRETTO_BASEPOINT_POINT, ristretto::RistrettoPoint, scalar::Scalar, + traits::Identity, +}; +use rand_core::{CryptoRng, RngCore}; +use sha2::{Digest, Sha512}; +use zeroize::DefaultIsZeroes; + +use crate::{Signature, VerificationKey}; + +/// A secret scalar value representing a single signer's secret key. +#[derive(Clone, Copy, Default, PartialEq)] +pub struct Secret(pub(crate) Scalar); + +// Zeroizes `Secret` to be the `Default` value on drop (when it goes out of +// scope). Luckily the derived `Default` includes the `Default` impl of +// Scalar, which is four 0u64's under the hood. +impl DefaultIsZeroes for Secret {} + +impl From for Secret { + fn from(source: Scalar) -> Secret { + Secret(source) + } +} + +/// A public group element that represents a single signer's public key. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Public(RistrettoPoint); + +impl From for Public { + fn from(source: RistrettoPoint) -> Public { + Public(source) + } +} + +/// A share generated by performing a (t-out-of-n) secret sharing scheme where +/// n is the total number of shares and t is the threshold required to +/// reconstruct the secret; in this case we use Shamir's secret sharing. +#[derive(Clone)] +pub struct Share { + receiver_index: u64, + /// Secret Key. + pub(crate) value: Secret, + /// The commitments to be distributed among signers. + pub(crate) commitment: ShareCommitment, +} + +/// A Jubjub point that is a commitment to one coefficient of our secret +/// polynomial. +/// +/// This is a (public) commitment to one coefficient of a secret polynomial used +/// for performing verifiable secret sharing for a Shamir secret share. +#[derive(Clone, PartialEq)] +pub(crate) struct Commitment(pub(crate) RistrettoPoint); + +/// Contains the commitments to the coefficients for our secret polynomial _f_, +/// used to generate participants' key shares. +/// +/// [`ShareCommitment`] contains a set of commitments to the coefficients (which +/// themselves are scalars) for a secret polynomial f, where f is used to +/// generate each ith participant's key share f(i). Participants use this set of +/// commitments to perform verifiable secret sharing. +/// +/// Note that participants MUST be assured that they have the *same* +/// [`ShareCommitment`], either by performing pairwise comparison, or by using +/// some agreed-upon public location for publication, where each participant can +/// ensure that they received the correct (and same) value. +#[derive(Clone)] +pub struct ShareCommitment(pub(crate) Vec); + +/// The product of all signers' individual commitments, published as part of the +/// final signature. +#[derive(PartialEq)] +pub struct GroupCommitment(pub(crate) RistrettoPoint); + +/// Secret and public key material generated by a dealer performing +/// [`keygen_with_dealer`]. +/// +/// To derive a FROST keypair, the receiver of the [`SharePackage`] *must* call +/// .into(), which under the hood also performs validation. +pub struct SharePackage { + /// The public signing key that represents the entire group. + pub(crate) group_public: VerificationKey, + /// Denotes the participant index each share is owned by. + pub index: u64, + /// This participant's public key. + pub(crate) public: Public, + /// This participant's share. + pub(crate) share: Share, +} + +impl TryFrom for KeyPackage { + type Error = &'static str; + + /// Tries to verify a share and construct a [`KeyPackage`] from it. + /// + /// When participants receive a [`SharePackage`] from the dealer, they + /// *MUST* verify the integrity of the share before continuing on to + /// transform it into a signing/verification keypair. Here, we assume that + /// every participant has the same view of the commitment issued by the + /// dealer, but implementations *MUST* make sure that all participants have + /// a consistent view of this commitment in practice. + fn try_from(sharepackage: SharePackage) -> Result { + verify_share(&sharepackage.share)?; + + Ok(KeyPackage { + index: sharepackage.index, + secret_share: sharepackage.share.value, + public: sharepackage.public, + group_public: sharepackage.group_public, + }) + } +} + +/// A FROST keypair, which can be generated either by a trusted dealer or using +/// a DKG. +/// +/// When using a central dealer, [`SharePackage`]s are distributed to +/// participants, who then perform verification, before deriving +/// [`KeyPackage`]s, which they store to later use during signing. +#[allow(dead_code)] +pub struct KeyPackage { + index: u64, + secret_share: Secret, + public: Public, + group_public: VerificationKey, +} + +/// Public data that contains all the signer's public keys as well as the +/// group public key. +/// +/// Used for verification purposes before publishing a signature. +pub struct PublicKeyPackage { + /// When performing signing, the coordinator must ensure that they have the + /// correct view of participant's public keys to perform verification before + /// publishing a signature. signer_pubkeys represents all signers for a + /// signing operation. + pub(crate) signer_pubkeys: HashMap, + /// group_public represents the joint public key for the entire group. + pub group_public: VerificationKey, +} + +/// Allows all participants' keys to be generated using a central, trusted +/// dealer. +/// +/// Under the hood, this performs verifiable secret sharing, which itself uses +/// Shamir secret sharing, from which each share becomes a participant's secret +/// key. The output from this function is a set of shares along with one single +/// commitment that participants use to verify the integrity of the share. The +/// number of signers is limited to 255. +pub fn keygen_with_dealer( + num_signers: u8, + threshold: u8, + mut rng: R, +) -> Result<(Vec, PublicKeyPackage), &'static str> { + let mut bytes = [0; 64]; + rng.fill_bytes(&mut bytes); + + let secret = Secret(Scalar::random(&mut rng)); + let group_public = VerificationKey::from(&secret.0); + let shares = generate_shares(&secret, num_signers, threshold, rng)?; + let mut sharepackages: Vec = Vec::with_capacity(num_signers as usize); + let mut signer_pubkeys: HashMap = HashMap::with_capacity(num_signers as usize); + + for share in shares { + let signer_public = Public(RISTRETTO_BASEPOINT_POINT * share.value.0); + sharepackages.push(SharePackage { + index: share.receiver_index, + share: share.clone(), + public: signer_public, + group_public, + }); + + signer_pubkeys.insert(share.receiver_index, signer_public); + } + + Ok(( + sharepackages, + PublicKeyPackage { + signer_pubkeys, + group_public, + }, + )) +} + +/// Verifies that a share is consistent with a commitment. +/// +/// This ensures that this participant's share has been generated using the same +/// mechanism as all other signing participants. Note that participants *MUST* +/// ensure that they have the same view as all other participants of the +/// commitment! +fn verify_share(share: &Share) -> Result<(), &'static str> { + let f_result = RISTRETTO_BASEPOINT_POINT * share.value.0; + + let x = Scalar::from(share.receiver_index as u64); + + let (_, result) = share.commitment.0.iter().fold( + (Scalar::one(), RistrettoPoint::identity()), + |(x_to_the_i, sum_so_far), comm_i| (x_to_the_i * x, sum_so_far + comm_i.0 * x_to_the_i), + ); + + if !(f_result == result) { + return Err("Share is invalid."); + } + + Ok(()) +} + +/// Creates secret shares for a given secret. +/// +/// This function accepts a secret from which shares are generated. While in +/// FROST this secret should always be generated randomly, we allow this secret +/// to be specified for this internal function for testability. +/// +/// Internally, [`generate_shares`] performs verifiable secret sharing, which +/// generates shares via Shamir Secret Sharing, and then generates public +/// commitments to those shares. +/// +/// More specifically, [`generate_shares`]: +/// - Randomly samples of coefficents [a, b, c], this represents a secret +/// polynomial f +/// - For each participant i, their secret share is f(i) +/// - The commitment to the secret polynomial f is [g^a, g^b, g^c] +fn generate_shares( + secret: &Secret, + numshares: u8, + threshold: u8, + mut rng: R, +) -> Result, &'static str> { + if threshold < 1 { + return Err("Threshold cannot be 0"); + } + + if numshares < 1 { + return Err("Number of shares cannot be 0"); + } + + if threshold > numshares { + return Err("Threshold cannot exceed numshares"); + } + + let numcoeffs = threshold - 1; + + let mut coefficients: Vec = Vec::with_capacity(threshold as usize); + + let mut shares: Vec = Vec::with_capacity(numshares as usize); + + let mut commitment: ShareCommitment = ShareCommitment(Vec::with_capacity(threshold as usize)); + + for _ in 0..numcoeffs { + coefficients.push(Scalar::random(&mut rng)); + } + + // Verifiable secret sharing, to make sure that participants can ensure their + // secret is consistent with every other participant's. + commitment + .0 + .push(Commitment(RISTRETTO_BASEPOINT_POINT * secret.0)); + + for c in &coefficients { + commitment.0.push(Commitment(RISTRETTO_BASEPOINT_POINT * c)); + } + + // Evaluate the polynomial with `secret` as the constant term + // and `coeffs` as the other coefficients at the point x=share_index, + // using Horner's method. + for index in 1..numshares + 1 { + let scalar_index = Scalar::from(index as u64); + let mut value = Scalar::zero(); + + // Polynomial evaluation, for this index + for i in (0..numcoeffs).rev() { + value += &coefficients[i as usize]; + value *= scalar_index; + } + value += secret.0; + + shares.push(Share { + receiver_index: index as u64, + value: Secret(value), + commitment: commitment.clone(), + }); + } + + Ok(shares) +} + +/// Comprised of hiding and binding nonces. +/// +/// Note that [`SigningNonces`] must be used *only once* for a signing +/// operation; re-using nonces will result in leakage of a signer's long-lived +/// signing key. +#[derive(Clone, Copy, Default)] +pub struct SigningNonces { + hiding: Scalar, + binding: Scalar, +} + +// Zeroizes `SigningNonces` to be the `Default` value on drop (when it goes out +// of scope). Luckily the derived `Default` includes the `Default` impl of the +// `jubjub::Fr/Scalar`'s, which is four 0u64's under the hood. +impl DefaultIsZeroes for SigningNonces {} + +impl SigningNonces { + /// Generates a new signing nonce. + /// + /// Each participant generates signing nonces before performing a signing + /// operation. + pub fn new(rng: &mut R) -> Self + where + R: CryptoRng + RngCore, + { + fn random_nonzero_bytes(rng: &mut R) -> [u8; 64] + where + R: CryptoRng + RngCore, + { + let mut bytes = [0; 64]; + loop { + rng.fill_bytes(&mut bytes); + if bytes != [0; 64] { + return bytes; + } + } + } + + // The values of 'hiding' and 'binding' must be non-zero so that commitments are + // not the identity. + let hiding = Scalar::from_bytes_mod_order_wide(&random_nonzero_bytes(rng)); + let binding = Scalar::from_bytes_mod_order_wide(&random_nonzero_bytes(rng)); + + Self { hiding, binding } + } +} + +/// Published by each participant in the first round of the signing protocol. +/// +/// This step can be batched if desired by the implementation. Each +/// SigningCommitment can be used for exactly *one* signature. +#[derive(Copy, Clone)] +pub struct SigningCommitments { + /// The participant index + pub(crate) index: u64, + /// The hiding point. + pub(crate) hiding: RistrettoPoint, + /// The binding point. + pub(crate) binding: RistrettoPoint, +} + +impl From<(u64, &SigningNonces)> for SigningCommitments { + /// For SpendAuth signatures only, not Binding signatures, in + /// RedJubjub/Zcash. + fn from((index, nonces): (u64, &SigningNonces)) -> Self { + Self { + index, + hiding: RISTRETTO_BASEPOINT_POINT * nonces.hiding, + binding: RISTRETTO_BASEPOINT_POINT * nonces.binding, + } + } +} + +/// Generated by the coordinator of the signing operation and distributed to +/// each signing party. +pub struct SigningPackage { + /// The set of commitments participants published in the first round of the + /// protocol. + pub signing_commitments: Vec, + /// Message which each participant will sign. + /// + /// Each signer should perform protocol-specific verification on the + /// message. + pub message: Vec, +} + +/// A representation of a single signature used in FROST structures and +/// messages. +#[derive(Clone, Copy, Default, PartialEq)] +pub struct SignatureResponse(pub(crate) Scalar); + +/// A participant's signature share, which the coordinator will use to aggregate +/// with all other signer's shares into the joint signature. +#[derive(Clone, Copy, Default)] +pub struct SignatureShare { + /// Represents the participant index. + pub(crate) index: u64, + /// This participant's signature over the message. + pub(crate) signature: SignatureResponse, +} + +// Zeroizes `SignatureShare` to be the `Default` value on drop (when it goes out +// of scope). Luckily the derived `Default` includes the `Default` impl of +// Scalar, which is four 0u64's under the hood, and u32, which is +// 0u32. +impl DefaultIsZeroes for SignatureShare {} + +impl SignatureShare { + /// Tests if a signature share issued by a participant is valid before + /// aggregating it into a final joint signature to publish. + pub fn check_is_valid( + &self, + pubkey: &Public, + lambda_i: Scalar, + commitment: RistrettoPoint, + challenge: Scalar, + ) -> Result<(), &'static str> { + if (RISTRETTO_BASEPOINT_POINT * self.signature.0) + != (commitment + pubkey.0 * challenge * lambda_i) + { + return Err("Invalid signature share"); + } + Ok(()) + } +} + +/// Done once by each participant, to generate _their_ nonces and commitments +/// that are then used during signing. +/// +/// When performing signing using two rounds, num_nonces would equal 1, to +/// perform the first round. Batching entails generating more than one +/// nonce/commitment pair at a time. Nonces should be stored in secret storage +/// for later use, whereas the commitments are published. +/// +/// The number of nonces is limited to 255. This limit can be increased if it +/// turns out to be too conservative. +// TODO: Make sure the above is a correct statement, fix if needed in: +// https://github.com/ZcashFoundation/redjubjub/issues/111 +pub fn preprocess( + num_nonces: u8, + participant_index: u64, + rng: &mut R, +) -> (Vec, Vec) +where + R: CryptoRng + RngCore, +{ + let mut signing_nonces: Vec = Vec::with_capacity(num_nonces as usize); + let mut signing_commitments: Vec = Vec::with_capacity(num_nonces as usize); + + for _ in 0..num_nonces { + let nonces = SigningNonces::new(rng); + signing_commitments.push(SigningCommitments::from((participant_index, &nonces))); + signing_nonces.push(nonces); + } + + (signing_nonces, signing_commitments) +} + +/// Generates the binding factor that ensures each signature share is strongly +/// bound to a signing set, specific set of commitments, and a specific message. +fn gen_rho_i(index: u64, signing_package: &SigningPackage) -> Scalar { + // Hash signature message with SHA-512 before deriving the binding factor. + // + // To avoid a collision with other inputs to the hash that generates the + // binding factor, we should hash our input message first. Our 'standard' + // hash is SHA-512, which uses a domain separator already, and is the same one + // that generates the binding factor. + let message_hash = Sha512::new() + .chain(signing_package.message.as_slice()) + .finalize(); + + let mut hasher = Sha512::new() + .chain("FROST_rho".as_bytes()) + .chain(index.to_be_bytes()) + .chain(message_hash); + + for item in signing_package.signing_commitments.iter() { + hasher.update(item.index.to_be_bytes()); + let hiding_bytes = RistrettoPoint::from(item.hiding).compress().to_bytes(); + hasher.update(hiding_bytes); + let binding_bytes = RistrettoPoint::from(item.binding).compress().to_bytes(); + hasher.update(binding_bytes); + } + + Scalar::from_hash(hasher) +} + +/// Generates the group commitment which is published as part of the joint +/// Schnorr signature. +fn gen_group_commitment( + signing_package: &SigningPackage, + bindings: &HashMap, +) -> Result { + let identity = RistrettoPoint::identity(); + let mut accumulator = identity; + + for commitment in signing_package.signing_commitments.iter() { + // The following check prevents a party from accidentally revealing their share. + // Note that the '&&' operator would be sufficient. + if identity == commitment.binding || identity == commitment.hiding { + return Err("Commitment equals the identity."); + } + + let rho_i = bindings + .get(&commitment.index) + .ok_or("No matching commitment index")?; + accumulator += commitment.hiding + (commitment.binding * rho_i) + } + + Ok(GroupCommitment(RistrettoPoint::from(accumulator))) +} + +/// Generates the challenge as is required for Schnorr signatures. +fn gen_challenge( + signing_package: &SigningPackage, + group_commitment: &GroupCommitment, + group_public: &VerificationKey, +) -> Scalar { + let group_commitment_bytes = RistrettoPoint::from(group_commitment.0) + .compress() + .to_bytes(); + + Scalar::from_hash( + Sha512::new() + .chain(group_commitment_bytes) + .chain(group_public.bytes.bytes) + .chain(signing_package.message.as_slice()), + ) +} + +/// Generates the lagrange coefficient for the i'th participant. +fn gen_lagrange_coeff( + signer_index: u64, + signing_package: &SigningPackage, +) -> Result { + let mut num = Scalar::one(); + let mut den = Scalar::one(); + for commitment in signing_package.signing_commitments.iter() { + if commitment.index == signer_index { + continue; + } + num *= Scalar::from(commitment.index as u64); + den *= Scalar::from(commitment.index as u64) - Scalar::from(signer_index as u64); + } + + if den == Scalar::zero() { + return Err("Duplicate shares provided"); + } + + // TODO: handle this unwrap better like other CtOption's + let lagrange_coeff = num * den.invert(); + + Ok(lagrange_coeff) +} + +/// Performed once by each participant selected for the signing operation. +/// +/// Receives the message to be signed and a set of signing commitments and a set +/// of randomizing commitments to be used in that signing operation, including +/// that for this participant. +/// +/// Assumes the participant has already determined which nonce corresponds with +/// the commitment that was assigned by the coordinator in the SigningPackage. +pub fn sign( + signing_package: &SigningPackage, + participant_nonces: SigningNonces, + share_package: &SharePackage, +) -> Result { + let mut bindings: HashMap = + HashMap::with_capacity(signing_package.signing_commitments.len()); + + for comm in signing_package.signing_commitments.iter() { + let rho_i = gen_rho_i(comm.index, &signing_package); + bindings.insert(comm.index, rho_i); + } + + let lambda_i = gen_lagrange_coeff(share_package.index, &signing_package)?; + + let group_commitment = gen_group_commitment(&signing_package, &bindings)?; + + let challenge = gen_challenge( + &signing_package, + &group_commitment, + &share_package.group_public, + ); + + let participant_rho_i = bindings + .get(&share_package.index) + .ok_or("No matching binding!")?; + + // The Schnorr signature share + let signature: Scalar = participant_nonces.hiding + + (participant_nonces.binding * participant_rho_i) + + (lambda_i * share_package.share.value.0 * challenge); + + Ok(SignatureShare { + index: share_package.index, + signature: SignatureResponse(signature), + }) +} + +/// Verifies each participant's signature share, and if all are valid, +/// aggregates the shares into a signature to publish. +/// +/// Resulting signature is compatible with verification of a plain SpendAuth +/// signature. +/// +/// This operation is performed by a coordinator that can communicate with all +/// the signing participants before publishing the final signature. The +/// coordinator can be one of the participants or a semi-trusted third party +/// (who is trusted to not perform denial of service attacks, but does not learn +/// any secret information). Note that because the coordinator is trusted to +/// report misbehaving parties in order to avoid publishing an invalid +/// signature, if the coordinator themselves is a signer and misbehaves, they +/// can avoid that step. However, at worst, this results in a denial of +/// service attack due to publishing an invalid signature. +pub fn aggregate( + signing_package: &SigningPackage, + signing_shares: &[SignatureShare], + pubkeys: &PublicKeyPackage, +) -> Result { + let mut bindings: HashMap = + HashMap::with_capacity(signing_package.signing_commitments.len()); + + for comm in signing_package.signing_commitments.iter() { + let rho_i = gen_rho_i(comm.index, &signing_package); + bindings.insert(comm.index, rho_i); + } + + let group_commitment = gen_group_commitment(&signing_package, &bindings)?; + + let challenge = gen_challenge(&signing_package, &group_commitment, &pubkeys.group_public); + + for signing_share in signing_shares { + let signer_pubkey = pubkeys.signer_pubkeys[&signing_share.index]; + let lambda_i = gen_lagrange_coeff(signing_share.index, &signing_package)?; + let signer_commitment = signing_package + .signing_commitments + .iter() + .find(|comm| comm.index == signing_share.index) + .ok_or("No matching signing commitment for signer")?; + + let commitment_i = + signer_commitment.hiding + (signer_commitment.binding * bindings[&signing_share.index]); + + signing_share.check_is_valid(&signer_pubkey, lambda_i, commitment_i, challenge)?; + } + + // The aggregation of the signature shares by summing them up, resulting in + // a plain Schnorr signature. + let mut z = Scalar::zero(); + for signature_share in signing_shares { + z += signature_share.signature.0; + } + + Ok(Signature { + r_bytes: RistrettoPoint::from(group_commitment.0) + .compress() + .to_bytes(), + s_bytes: z.to_bytes(), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::thread_rng; + + fn reconstruct_secret(shares: Vec) -> Result { + let numshares = shares.len(); + + if numshares < 1 { + return Err("No shares provided"); + } + + let mut lagrange_coeffs: Vec = Vec::with_capacity(numshares as usize); + + for i in 0..numshares { + let mut num = Scalar::one(); + let mut den = Scalar::one(); + for j in 0..numshares { + if j == i { + continue; + } + num *= Scalar::from(shares[j].receiver_index as u64); + den *= Scalar::from(shares[j].receiver_index as u64) + - Scalar::from(shares[i].receiver_index as u64); + } + if den == Scalar::zero() { + return Err("Duplicate shares provided"); + } + lagrange_coeffs.push(num * den.invert().unwrap()); + } + + let mut secret = Scalar::zero(); + + for i in 0..numshares { + secret += lagrange_coeffs[i] * shares[i].value.0; + } + + Ok(secret) + } + + /// This is testing that Shamir's secret sharing to compute and arbitrary + /// value is working. + #[test] + fn check_share_generation() { + let mut rng = thread_rng(); + + let mut bytes = [0; 64]; + rng.fill_bytes(&mut bytes); + let secret = Secret(Scalar::from_bytes_wide(&bytes)); + + let _ = RISTRETTO_BASEPOINT_POINT * secret.0; + + let shares = generate_shares(&secret, 5, 3, rng).unwrap(); + + for share in shares.iter() { + assert_eq!(verify_share(&share), Ok(())); + } + + assert_eq!(reconstruct_secret(shares).unwrap(), secret.0) + } +} diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs new file mode 100644 index 0000000..97943bc --- /dev/null +++ b/frost-ristretto255/src/lib.rs @@ -0,0 +1,25 @@ +// -*- mode: rust; -*- +// +// This file is part of redjubjub. +// Copyright (c) 2019-2021 Zcash Foundation +// See LICENSE for licensing information. +// +// Authors: +// - Deirdre Connolly +// - Henry de Valence + +#![deny(missing_docs)] +#![doc = include_str!("../README.md")] + +pub mod batch; +mod error; +pub mod frost; +mod messages; +pub(crate) mod signature; +mod signing_key; +mod verification_key; + +pub use error::Error; +pub use signature::Signature; +pub use signing_key::SigningKey; +pub use verification_key::{VerificationKey, VerificationKeyBytes}; diff --git a/frost-ristretto255/src/messages.rs b/frost-ristretto255/src/messages.rs new file mode 100644 index 0000000..b731a83 --- /dev/null +++ b/frost-ristretto255/src/messages.rs @@ -0,0 +1,289 @@ +//! The FROST communication messages specified in [RFC-001] +//! +//! [RFC-001]: https://github.com/ZcashFoundation/redjubjub/blob/main/rfcs/0001-messages.md + +use std::collections::BTreeMap; + +use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; +#[cfg(test)] +use proptest_derive::Arbitrary; +use serde::{Deserialize, Serialize}; + +#[cfg(test)] +mod arbitrary; +mod constants; +mod serialize; +#[cfg(test)] +mod tests; +mod validate; + +use crate::{frost, signature, verification_key}; + +/// Define our own `Secret` type instead of using [`frost::Secret`]. +/// +/// The serialization design specifies that `Secret` is a +/// [`curve25519_dalek::scalar::Scalar`] that uses: "a 32-byte little-endian +/// canonical representation". +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct Secret([u8; 32]); + +/// Define our own `Commitment` type instead of using [`frost::Commitment`]. +/// +/// The serialization design specifies that `Commitment` is an +/// [`RistrettoPoint`] that uses: "a 32-byte little-endian canonical +/// representation". +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct Commitment([u8; 32]); + +impl From for Commitment { + fn from(value: frost::Commitment) -> Commitment { + Commitment(RistrettoPoint::from(value.0).compress().to_bytes()) + } +} + +/// Define our own `GroupCommitment` type instead of using +/// [`frost::GroupCommitment`]. +/// +/// The serialization design specifies that `GroupCommitment` is an +/// [`RistrettoPoint`] that uses: "a 32-byte little-endian canonical +/// representation". +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct GroupCommitment([u8; 32]); + +/// Define our own `SignatureResponse` type instead of using +/// [`frost::SignatureResponse`]. +/// +/// The serialization design specifies that `SignatureResponse` is a +/// [`jubjub::Scalar`] that uses: "a 32-byte little-endian canonical +/// representation". +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct SignatureResponse([u8; 32]); + +impl From for SignatureResponse { + fn from(value: signature::Signature) -> SignatureResponse { + SignatureResponse(value.s_bytes) + } +} + +impl From for GroupCommitment { + fn from(value: signature::Signature) -> GroupCommitment { + GroupCommitment(value.r_bytes) + } +} + +/// Define our own `VerificationKey` type instead of using +/// [`verification_key::VerificationKey`]. +/// +/// The serialization design specifies that `VerificationKey` is a +/// [`verification_key::VerificationKeyBytes`] that uses: "a 32-byte +/// little-endian canonical representation". +#[derive(Serialize, Deserialize, PartialEq, Debug, Copy, Clone)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct VerificationKey([u8; 32]); + +impl From for VerificationKey { + fn from(value: verification_key::VerificationKey) -> VerificationKey { + VerificationKey(<[u8; 32]>::from(value)) + } +} + +/// The data required to serialize a frost message. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct Message { + header: Header, + payload: Payload, +} + +/// The data required to serialize the common header fields for every message. +/// +/// Note: the `msg_type` is derived from the `payload` enum variant. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Copy)] +pub struct Header { + version: MsgVersion, + sender: ParticipantId, + receiver: ParticipantId, +} + +/// The data required to serialize the payload for a message. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[cfg_attr(test, derive(Arbitrary))] +pub enum Payload { + SharePackage(SharePackage), + SigningCommitments(SigningCommitments), + SigningPackage(SigningPackage), + SignatureShare(SignatureShare), + AggregateSignature(AggregateSignature), +} + +/// The numeric values used to identify each [`Payload`] variant during +/// serialization. +// TODO: spec says `#[repr(u8)]` but it is incompatible with `bincode` +// manual serialization and deserialization is needed. +#[repr(u32)] +#[non_exhaustive] +#[derive(Serialize, Deserialize, Debug, PartialEq)] +enum MsgType { + SharePackage, + SigningCommitments, + SigningPackage, + SignatureShare, + AggregateSignature, +} + +/// The numeric values used to identify the protocol version during +/// serialization. +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, Copy)] +pub struct MsgVersion(u8); + +/// The numeric values used to identify each participant during serialization. +/// +/// In the `frost` module, participant ID `0` should be invalid. +/// But in serialization, we want participants to be indexed from `0..n`, +/// where `n` is the number of participants. +/// This helps us look up their shares and commitments in serialized arrays. +/// So in serialization, we assign the dealer and aggregator the highest IDs, +/// and mark those IDs as invalid for signers. +/// +/// "When performing Shamir secret sharing, a polynomial `f(x)` is used to +/// generate each party’s share of the secret. The actual secret is `f(0)` and +/// the party with ID `i` will be given a share with value `f(i)`. +/// Since a DKG may be implemented in the future, we recommend that the ID `0` +/// be declared invalid." https://raw.githubusercontent.com/ZcashFoundation/redjubjub/main/zcash-frost-audit-report-20210323.pdf#d +#[derive(PartialEq, Eq, Hash, PartialOrd, Debug, Copy, Clone, Ord)] +pub enum ParticipantId { + /// A serialized participant ID for a signer. + /// + /// Must be less than or equal to [`constants::MAX_SIGNER_PARTICIPANT_ID`]. + Signer(u64), + /// The fixed participant ID for the dealer as defined in + /// [`constants::DEALER_PARTICIPANT_ID`]. + Dealer, + /// The fixed participant ID for the aggregator as defined in + /// [`constants::AGGREGATOR_PARTICIPANT_ID`]. + Aggregator, +} + +impl From for u64 { + fn from(value: ParticipantId) -> u64 { + match value { + // An id of `0` is invalid in frost. + ParticipantId::Signer(id) => id + 1, + ParticipantId::Dealer => constants::DEALER_PARTICIPANT_ID, + ParticipantId::Aggregator => constants::AGGREGATOR_PARTICIPANT_ID, + } + } +} + +/// The data required to serialize [`frost::SharePackage`]. +/// +/// The dealer sends this message to each signer for this round. +/// With this, the signer should be able to build a [`SharePackage`] and use +/// the [`frost::sign()`] function. +/// +/// Note: [`frost::SharePackage::public`] can be calculated from +/// [`SharePackage::secret_share`]. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct SharePackage { + /// The public signing key that represents the entire group: + /// [`frost::SharePackage::group_public`]. + group_public: VerificationKey, + /// This participant's secret key share: [`frost::SharePackage::share`]. + secret_share: Secret, + /// The commitments to the coefficients for our secret polynomial _f_, + /// used to generate participants' key shares. Participants use these to + /// perform verifiable secret sharing. + /// Share packages that contain duplicate or missing [`ParticipantId`]s are + /// invalid. [`ParticipantId`]s must be serialized in ascending numeric + /// order. + share_commitment: BTreeMap, +} + +/// The data required to serialize [`frost::SigningCommitments`]. +/// +/// Each signer must send this message to the aggregator. +/// A signing commitment from the first round of the signing protocol. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct SigningCommitments { + /// The hiding point: [`frost::SigningCommitments::hiding`] + hiding: Commitment, + /// The binding point: [`frost::SigningCommitments::binding`] + binding: Commitment, +} + +/// The data required to serialize [`frost::SigningPackage`]. +/// +/// The aggregator decides what message is going to be signed and +/// sends it to each signer with all the commitments collected. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct SigningPackage { + /// The collected commitments for each signer as a hashmap of + /// unique participant identifiers: + /// [`frost::SigningPackage::signing_commitments`] + /// + /// Signing packages that contain duplicate or missing [`ParticipantId`]s + /// are invalid. + signing_commitments: BTreeMap, + /// The message to be signed: [`frost::SigningPackage::message`]. + /// + /// Each signer should perform protocol-specific verification on the + /// message. + message: Vec, +} + +impl From for frost::SigningPackage { + fn from(value: SigningPackage) -> frost::SigningPackage { + let mut signing_commitments = Vec::new(); + for (participant_id, commitment) in &value.signing_commitments { + let s = frost::SigningCommitments { + index: u64::from(*participant_id), + hiding: CompressedRistretto::from_slice(&commitment.hiding.0) + .decompress() + .unwrap(), + binding: CompressedRistretto::from_slice(&commitment.binding.0) + .decompress() + .unwrap(), + }; + signing_commitments.push(s); + } + + frost::SigningPackage { + signing_commitments, + message: value.message, + } + } +} + +/// The data required to serialize [`frost::SignatureShare`]. +/// +/// Each signer sends their signatures to the aggregator who is going to collect +/// them and generate a final spend signature. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct SignatureShare { + /// This participant's signature over the message: + /// [`frost::SignatureShare::signature`] + signature: SignatureResponse, +} + +/// The data required to serialize a successful output from +/// [`frost::aggregate()`]. +/// +/// The final signature is broadcasted by the aggregator to all signers. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct AggregateSignature { + /// The aggregated group commitment: [`signature::Signature::r_bytes`] + /// returned by [`frost::aggregate()`] + group_commitment: GroupCommitment, + /// A plain Schnorr signature created by summing all the signature shares: + /// [`signature::Signature::s_bytes`] returned by [`frost::aggregate()`] + schnorr_signature: SignatureResponse, +} diff --git a/frost-ristretto255/src/messages/arbitrary.rs b/frost-ristretto255/src/messages/arbitrary.rs new file mode 100644 index 0000000..515b7a0 --- /dev/null +++ b/frost-ristretto255/src/messages/arbitrary.rs @@ -0,0 +1,55 @@ +use proptest::{ + arbitrary::{any, Arbitrary}, + prelude::*, +}; + +use super::*; + +impl Arbitrary for Header { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + ( + any::(), + any::(), + any::(), + ) + .prop_filter( + "Sender and receiver participant IDs can not be the same", + |(_, sender, receiver)| sender != receiver, + ) + .prop_map(|(version, sender, receiver)| Header { + version: version, + sender: sender, + receiver: receiver, + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} + +impl Arbitrary for MsgVersion { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + Just(constants::BASIC_FROST_SERIALIZATION).boxed() + } + + type Strategy = BoxedStrategy; +} + +impl Arbitrary for ParticipantId { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + (u64::MIN..=constants::MAX_SIGNER_PARTICIPANT_ID).prop_map(ParticipantId::Signer), + Just(ParticipantId::Dealer), + Just(ParticipantId::Aggregator), + ] + .boxed() + } + + type Strategy = BoxedStrategy; +} diff --git a/frost-ristretto255/src/messages/constants.rs b/frost-ristretto255/src/messages/constants.rs new file mode 100644 index 0000000..1a77103 --- /dev/null +++ b/frost-ristretto255/src/messages/constants.rs @@ -0,0 +1,31 @@ +//! Definitions of constants. + +use super::MsgVersion; + +/// The first version of FROST messages +pub const BASIC_FROST_SERIALIZATION: MsgVersion = MsgVersion(0); + +/// The fixed participant ID for the dealer. +pub const DEALER_PARTICIPANT_ID: u64 = u64::MAX - 1; + +/// The fixed participant ID for the aggregator. +pub const AGGREGATOR_PARTICIPANT_ID: u64 = u64::MAX; + +/// The maximum `ParticipantId::Signer` in this serialization format. +/// +/// We reserve two participant IDs for the dealer and aggregator. +pub const MAX_SIGNER_PARTICIPANT_ID: u64 = u64::MAX - 2; + +/// The maximum number of signers +/// +/// By protocol the number of signers can'e be more than 255. +pub const MAX_SIGNERS: u8 = 255; + +/// The maximum length of a Zcash message, in bytes. +pub const ZCASH_MAX_PROTOCOL_MESSAGE_LEN: usize = 2 * 1024 * 1024; + +/// The minimum number of signers of any FROST setup. +pub const MIN_SIGNERS: usize = 2; + +/// The minimum number of signers that must sign. +pub const MIN_THRESHOLD: usize = 2; diff --git a/frost-ristretto255/src/messages/serialize.rs b/frost-ristretto255/src/messages/serialize.rs new file mode 100644 index 0000000..0fba8fb --- /dev/null +++ b/frost-ristretto255/src/messages/serialize.rs @@ -0,0 +1,68 @@ +//! Serialization rules specified in [RFC-001#Serialize-Deserialize] +//! +//! We automatically serialize and deserialize using serde derivations where possible. +//! Sometimes we need to implement ourselves, this file holds that code. +//! +//! [RFC-001#Serialize-Deserialize]: https://github.com/ZcashFoundation/redjubjub/blob/main/rfcs/0001-messages.md#serializationdeserialization + +use serde::ser::{Serialize, Serializer}; + +use serde::de::{self, Deserialize, Deserializer, Visitor}; + +use super::constants::{ + AGGREGATOR_PARTICIPANT_ID, DEALER_PARTICIPANT_ID, MAX_SIGNER_PARTICIPANT_ID, +}; +use super::*; + +use std::fmt; + +impl Serialize for ParticipantId { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + ParticipantId::Signer(id) => { + assert!(id <= MAX_SIGNER_PARTICIPANT_ID); + serializer.serialize_u64(id) + } + ParticipantId::Dealer => serializer.serialize_u64(DEALER_PARTICIPANT_ID), + ParticipantId::Aggregator => serializer.serialize_u64(AGGREGATOR_PARTICIPANT_ID), + } + } +} + +struct ParticipantIdVisitor; + +impl<'de> Visitor<'de> for ParticipantIdVisitor { + type Value = ParticipantId; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str( + format!("an integer between {} and {}", std::u64::MIN, std::u64::MAX).as_str(), + ) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + // Note: deserialization can't fail, because all values are valid. + if value == DEALER_PARTICIPANT_ID { + return Ok(ParticipantId::Dealer); + } else if value == AGGREGATOR_PARTICIPANT_ID { + return Ok(ParticipantId::Aggregator); + } else { + return Ok(ParticipantId::Signer(value)); + } + } +} + +impl<'de> Deserialize<'de> for ParticipantId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_u64(ParticipantIdVisitor) + } +} diff --git a/frost-ristretto255/src/messages/tests.rs b/frost-ristretto255/src/messages/tests.rs new file mode 100644 index 0000000..f0f320e --- /dev/null +++ b/frost-ristretto255/src/messages/tests.rs @@ -0,0 +1,2 @@ +mod integration; +mod prop; diff --git a/frost-ristretto255/src/messages/tests/integration.rs b/frost-ristretto255/src/messages/tests/integration.rs new file mode 100644 index 0000000..e571669 --- /dev/null +++ b/frost-ristretto255/src/messages/tests/integration.rs @@ -0,0 +1,805 @@ +use crate::{ + frost, + messages::{ + validate::{MsgErr, Validate}, + *, + }, + verification_key, +}; +use rand::thread_rng; +use serde_json; +use std::convert::TryFrom; + +#[test] +fn validate_version() { + // A version number that we expect to be always invalid + const INVALID_VERSION: u8 = u8::MAX; + + let setup = basic_setup(); + + let header = Header { + version: MsgVersion(INVALID_VERSION), + sender: setup.dealer, + receiver: setup.signer1, + }; + + let validate = Validate::validate(&header); + assert_eq!(validate, Err(MsgErr::WrongVersion)); + + let validate = Validate::validate(&Header { + version: constants::BASIC_FROST_SERIALIZATION, + sender: setup.dealer, + receiver: setup.signer1, + }) + .err(); + + assert_eq!(validate, None); +} + +#[test] +fn validate_sender_receiver() { + let setup = basic_setup(); + + let header = Header { + version: constants::BASIC_FROST_SERIALIZATION, + sender: setup.signer1, + receiver: setup.signer1, + }; + + let validate = Validate::validate(&header); + assert_eq!(validate, Err(MsgErr::SameSenderAndReceiver)); +} + +#[test] +fn validate_sharepackage() { + let setup = basic_setup(); + let (mut shares, _pubkeys) = + frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); + + let header = create_valid_header(setup.signer1, setup.signer2); + + let group_public = VerificationKey::from( + verification_key::VerificationKey::try_from(shares[0].group_public.bytes).unwrap(), + ); + let secret_share = Secret(shares[0].share.value.0.to_bytes()); + + let participants = vec![setup.signer1, setup.signer2]; + shares.truncate(2); + let share_commitment = generate_share_commitment(&shares, participants); + + let payload = Payload::SharePackage(SharePackage { + group_public, + secret_share: secret_share, + share_commitment: share_commitment, + }); + let validate_payload = Validate::validate(&payload); + let valid_payload = validate_payload.expect("a valid payload").clone(); + + let message = Message { + header, + payload: valid_payload.clone(), + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::SenderMustBeDealer)); + + // change the header + let header = create_valid_header(setup.dealer, setup.aggregator); + + let message = Message { + header, + payload: valid_payload, + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::ReceiverMustBeSigner)); + + let participants = vec![setup.signer1]; + shares.truncate(1); + let mut share_commitment = generate_share_commitment(&shares, participants); + + // change the payload to have only 1 commitment + let payload = Payload::SharePackage(SharePackage { + group_public, + secret_share: secret_share, + share_commitment: share_commitment.clone(), + }); + let validate_payload = Validate::validate(&payload); + assert_eq!( + validate_payload, + Err(MsgErr::NotEnoughCommitments(constants::MIN_SIGNERS)) + ); + + // build and use too many commitments + for i in 2..constants::MAX_SIGNERS as u64 + 2 { + share_commitment.insert( + ParticipantId::Signer(i), + share_commitment.clone()[&setup.signer1], + ); + } + let payload = Payload::SharePackage(SharePackage { + group_public, + secret_share, + share_commitment, + }); + let validate_payload = Validate::validate(&payload); + assert_eq!(validate_payload, Err(MsgErr::TooManyCommitments)); +} + +#[test] +fn serialize_sharepackage() { + let setup = basic_setup(); + + let (mut shares, _pubkeys) = + frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); + + let header = create_valid_header(setup.dealer, setup.signer1); + + let group_public = VerificationKey::from( + verification_key::VerificationKey::try_from(shares[0].group_public.bytes).unwrap(), + ); + let secret_share = Secret(shares[0].share.value.0.to_bytes()); + + let participants = vec![setup.signer1]; + shares.truncate(1); + let share_commitment = generate_share_commitment(&shares, participants); + + let payload = Payload::SharePackage(SharePackage { + group_public, + secret_share, + share_commitment: share_commitment.clone(), + }); + + let message = Message { + header: header, + payload: payload.clone(), + }; + + // check general structure and header serialization/deserialization + serialize_message(message, setup.dealer, setup.signer1); + + // check payload serialization/deserialization + let mut payload_serialized_bytes = bincode::serialize(&payload).unwrap(); + // check the message type is correct + let deserialized_msg_type: MsgType = + bincode::deserialize(&payload_serialized_bytes[0..4]).unwrap(); + assert_eq!(deserialized_msg_type, MsgType::SharePackage); + + // remove the msg_type from the the payload + payload_serialized_bytes = + (&payload_serialized_bytes[4..payload_serialized_bytes.len()]).to_vec(); + + // group_public is 32 bytes + let deserialized_group_public: VerificationKey = + bincode::deserialize(&payload_serialized_bytes[0..32]).unwrap(); + // secret share is 32 bytes + let deserialized_secret_share: Secret = + bincode::deserialize(&payload_serialized_bytes[32..64]).unwrap(); + // rest of the message is the map: 32(Commitment) + 8(ParticipantId) + 8(map.len()) + let deserialized_share_commitment: BTreeMap = + bincode::deserialize(&payload_serialized_bytes[64..112]).unwrap(); + + // check the map len + let deserialized_map_len: u64 = + bincode::deserialize(&payload_serialized_bytes[64..72]).unwrap(); + assert_eq!(deserialized_map_len, 1); + + // no leftover bytes + assert_eq!(payload_serialized_bytes.len(), 112); + + assert_eq!(deserialized_group_public, group_public); + assert_eq!(deserialized_secret_share, secret_share); + assert_eq!(deserialized_share_commitment, share_commitment); +} + +#[test] +fn validate_signingcommitments() { + let mut setup = basic_setup(); + + let (_nonce, commitment) = frost::preprocess(1, u64::from(setup.signer1), &mut setup.rng); + + let header = create_valid_header(setup.aggregator, setup.signer2); + + let payload = Payload::SigningCommitments(SigningCommitments { + hiding: Commitment(jubjub::AffinePoint::from(commitment[0].hiding).to_bytes()), + binding: Commitment(jubjub::AffinePoint::from(commitment[0].binding).to_bytes()), + }); + + let message = Message { + header, + payload: payload.clone(), + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::SenderMustBeSigner)); + + // change the header + let header = create_valid_header(setup.signer1, setup.signer2); + + let message = Message { + header, + payload: payload.clone(), + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::ReceiverMustBeAggregator)); + + // change the header to be valid + let header = create_valid_header(setup.signer1, setup.aggregator); + + let validate_message = Validate::validate(&Message { header, payload }).err(); + + assert_eq!(validate_message, None); +} + +#[test] +fn serialize_signingcommitments() { + let mut setup = basic_setup(); + + let (_nonce, commitment) = frost::preprocess(1, u64::from(setup.signer1), &mut setup.rng); + + let header = create_valid_header(setup.aggregator, setup.signer1); + + let hiding = Commitment(jubjub::AffinePoint::from(commitment[0].hiding).to_bytes()); + let binding = Commitment(jubjub::AffinePoint::from(commitment[0].binding).to_bytes()); + + let payload = Payload::SigningCommitments(SigningCommitments { hiding, binding }); + + let message = Message { + header: header, + payload: payload.clone(), + }; + + // check general structure serialization/deserialization + serialize_message(message, setup.aggregator, setup.signer1); + + // check payload serialization/deserialization + let mut payload_serialized_bytes = bincode::serialize(&payload).unwrap(); + // check the message type is correct + let deserialized_msg_type: MsgType = + bincode::deserialize(&payload_serialized_bytes[0..4]).unwrap(); + assert_eq!(deserialized_msg_type, MsgType::SigningCommitments); + + // remove the msg_type from the the payload + payload_serialized_bytes = + (&payload_serialized_bytes[4..payload_serialized_bytes.len()]).to_vec(); + + // hiding is 32 bytes + let deserialized_hiding: Commitment = + bincode::deserialize(&payload_serialized_bytes[0..32]).unwrap(); + // binding is 43 bytes kore + let deserialized_binding: Commitment = + bincode::deserialize(&payload_serialized_bytes[32..64]).unwrap(); + + // no leftover bytes + assert_eq!(payload_serialized_bytes.len(), 64); + + assert_eq!(deserialized_hiding, hiding); + assert_eq!(deserialized_binding, binding); +} + +#[test] +fn validate_signingpackage() { + let mut setup = basic_setup(); + + let (_nonce, commitment1) = frost::preprocess(1, u64::from(setup.signer1), &mut setup.rng); + let (_nonce, commitment2) = frost::preprocess(1, u64::from(setup.signer2), &mut setup.rng); + + let header = create_valid_header(setup.signer1, setup.signer2); + + // try with only 1 commitment + let commitments = vec![commitment1[0]]; + let participants = vec![setup.signer1]; + let signing_commitments = create_signing_commitments(commitments, participants); + + let payload = Payload::SigningPackage(SigningPackage { + signing_commitments: signing_commitments.clone(), + message: "hola".as_bytes().to_vec(), + }); + let validate_payload = Validate::validate(&payload); + assert_eq!( + validate_payload, + Err(MsgErr::NotEnoughCommitments(constants::MIN_SIGNERS)) + ); + + // add too many commitments + let mut big_signing_commitments = BTreeMap::::new(); + for i in 0..constants::MAX_SIGNERS as u64 + 1 { + big_signing_commitments.insert( + ParticipantId::Signer(i), + signing_commitments[&setup.signer1].clone(), + ); + } + let payload = Payload::SigningPackage(SigningPackage { + signing_commitments: big_signing_commitments, + message: "hola".as_bytes().to_vec(), + }); + let validate_payload = Validate::validate(&payload); + assert_eq!(validate_payload, Err(MsgErr::TooManyCommitments)); + + // change to 2 commitments + let commitments = vec![commitment1[0], commitment2[0]]; + let participants = vec![setup.signer1, setup.signer2]; + let signing_commitments = create_signing_commitments(commitments, participants); + + let big_message = [0u8; constants::ZCASH_MAX_PROTOCOL_MESSAGE_LEN + 1].to_vec(); + let payload = Payload::SigningPackage(SigningPackage { + signing_commitments: signing_commitments.clone(), + message: big_message, + }); + let validate_payload = Validate::validate(&payload); + assert_eq!(validate_payload, Err(MsgErr::MsgTooBig)); + + let message = Message { + header, + payload: payload.clone(), + }; + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::SenderMustBeAggregator)); + + // change header + let header = create_valid_header(setup.aggregator, setup.dealer); + + let message = Message { + header: header, + payload: payload.clone(), + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::ReceiverMustBeSigner)); + + let header = create_valid_header(setup.aggregator, setup.signer1); + let payload = Payload::SigningPackage(SigningPackage { + signing_commitments, + message: "hola".as_bytes().to_vec(), + }); + + let validate_message = Validate::validate(&Message { header, payload }).err(); + assert_eq!(validate_message, None); +} + +#[test] +fn serialize_signingpackage() { + let mut setup = basic_setup(); + + let (_nonce, commitment1) = frost::preprocess(1, u64::from(setup.signer1), &mut setup.rng); + let (_nonce, commitment2) = frost::preprocess(1, u64::from(setup.signer2), &mut setup.rng); + + let header = create_valid_header(setup.aggregator, setup.signer1); + + let commitments = vec![commitment1[0], commitment2[0]]; + let participants = vec![setup.signer1, setup.signer2]; + let signing_commitments = create_signing_commitments(commitments, participants); + + let payload = Payload::SigningPackage(SigningPackage { + signing_commitments: signing_commitments.clone(), + message: "hola".as_bytes().to_vec(), + }); + + let message = Message { + header: header, + payload: payload.clone(), + }; + + // check general structure serialization/deserialization + serialize_message(message, setup.aggregator, setup.signer1); + + // check payload serialization/deserialization + let mut payload_serialized_bytes = bincode::serialize(&payload).unwrap(); + + // check the message type is correct + let deserialized_msg_type: MsgType = + bincode::deserialize(&payload_serialized_bytes[0..4]).unwrap(); + assert_eq!(deserialized_msg_type, MsgType::SigningPackage); + + // remove the msg_type from the the payload + payload_serialized_bytes = + (&payload_serialized_bytes[4..payload_serialized_bytes.len()]).to_vec(); + + // check the map len + let deserialized_map_len: u64 = bincode::deserialize(&payload_serialized_bytes[0..8]).unwrap(); + assert_eq!(deserialized_map_len, 2); + + // Each SigningCommitment is 64 bytes and the ParticipantId is 8 bytes. + // This is multiplied by the map len, also include the map len bytes. + let deserialized_signing_commitments: BTreeMap = + bincode::deserialize(&payload_serialized_bytes[0..152]).unwrap(); + + // Message is from the end of the map up to the end of the message. + let deserialized_message: Vec = + bincode::deserialize(&payload_serialized_bytes[152..payload_serialized_bytes.len()]) + .unwrap(); + + // no leftover bytes + assert_eq!(payload_serialized_bytes.len(), 164); + + assert_eq!(deserialized_signing_commitments, signing_commitments); + assert_eq!(deserialized_message, "hola".as_bytes().to_vec()); +} + +#[test] +fn validate_signatureshare() { + let mut setup = basic_setup(); + + // signers and aggregator should have this data from `SharePackage` + let (shares, _pubkeys) = + frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); + + // create a signing package, this is done in the aggregator side. + // the signrs should have this data from `SigningPackage` + let (nonce1, commitment1) = frost::preprocess(1, u64::from(setup.signer1), &mut setup.rng); + let (_nonce2, commitment2) = frost::preprocess(1, u64::from(setup.signer2), &mut setup.rng); + let commitments = vec![commitment1[0], commitment2[0]]; + let participants = vec![setup.signer1, setup.signer2]; + let signing_commitments = create_signing_commitments(commitments, participants); + + let signing_package = frost::SigningPackage::from(SigningPackage { + signing_commitments: signing_commitments.clone(), + message: "hola".as_bytes().to_vec(), + }); + + // here we get started with the `SignatureShare` message. + let signature_share = frost::sign(&signing_package, nonce1[0], &shares[0]).unwrap(); + + // this header is invalid + let header = create_valid_header(setup.aggregator, setup.signer1); + + let payload = Payload::SignatureShare(SignatureShare { + signature: SignatureResponse(signature_share.signature.0.to_bytes()), + }); + + let message = Message { + header, + payload: payload.clone(), + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::SenderMustBeSigner)); + + // change the header, still invalid. + let header = create_valid_header(setup.signer1, setup.signer2); + + let message = Message { + header, + payload: payload.clone(), + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::ReceiverMustBeAggregator)); + + // change the header to be valid + let header = create_valid_header(setup.signer1, setup.aggregator); + + let validate_message = Validate::validate(&Message { header, payload }).err(); + + assert_eq!(validate_message, None); +} + +#[test] +fn serialize_signatureshare() { + let mut setup = basic_setup(); + + // signers and aggregator should have this data from `SharePackage` + let (shares, _pubkeys) = + frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); + + // create a signing package, this is done in the aggregator side. + // the signers should have this data from `SigningPackage` + let (nonce1, commitment1) = frost::preprocess(1, u64::from(setup.signer1), &mut setup.rng); + let (_nonce2, commitment2) = frost::preprocess(1, u64::from(setup.signer2), &mut setup.rng); + let commitments = vec![commitment1[0], commitment2[0]]; + let participants = vec![setup.signer1, setup.signer2]; + let signing_commitments = create_signing_commitments(commitments, participants); + + let signing_package = frost::SigningPackage::from(SigningPackage { + signing_commitments: signing_commitments.clone(), + message: "hola".as_bytes().to_vec(), + }); + + // here we get started with the `SignatureShare` message. + let signature_share = frost::sign(&signing_package, nonce1[0], &shares[0]).unwrap(); + + // valid header + let header = create_valid_header(setup.signer1, setup.aggregator); + + let signature = SignatureResponse(signature_share.signature.0.to_bytes()); + let payload = Payload::SignatureShare(SignatureShare { signature }); + + let message = Message { + header: header, + payload: payload.clone(), + }; + + // check general structure serialization/deserialization + serialize_message(message, setup.signer1, setup.aggregator); + + // check payload serialization/deserialization + let mut payload_serialized_bytes = bincode::serialize(&payload).unwrap(); + + // check the message type is correct + let deserialized_msg_type: MsgType = + bincode::deserialize(&payload_serialized_bytes[0..4]).unwrap(); + assert_eq!(deserialized_msg_type, MsgType::SignatureShare); + + // remove the msg_type from the the payload + payload_serialized_bytes = + (&payload_serialized_bytes[4..payload_serialized_bytes.len()]).to_vec(); + + // signature is 32 bytes + let deserialized_signature: SignatureResponse = + bincode::deserialize(&payload_serialized_bytes[0..32]).unwrap(); + + // no leftover bytes + assert_eq!(payload_serialized_bytes.len(), 32); + + assert_eq!(deserialized_signature, signature); +} + +#[test] +fn validate_aggregatesignature() { + let (setup, group_signature_res) = full_setup(); + + // this header is invalid + let header = create_valid_header(setup.signer1, setup.aggregator); + + let payload = Payload::AggregateSignature(AggregateSignature { + group_commitment: GroupCommitment::from(group_signature_res), + schnorr_signature: SignatureResponse::from(group_signature_res), + }); + + let message = Message { + header, + payload: payload.clone(), + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::SenderMustBeAggregator)); + + // change the header, still invalid. + let header = create_valid_header(setup.aggregator, setup.dealer); + + let message = Message { + header, + payload: payload.clone(), + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::ReceiverMustBeSigner)); + + // change the header to be valid + let header = create_valid_header(setup.aggregator, setup.signer1); + + let validate_message = Validate::validate(&Message { header, payload }).err(); + + assert_eq!(validate_message, None); +} + +#[test] +fn serialize_aggregatesignature() { + let (setup, group_signature_res) = full_setup(); + + let header = create_valid_header(setup.aggregator, setup.signer1); + + let group_commitment = GroupCommitment::from(group_signature_res); + let schnorr_signature = SignatureResponse::from(group_signature_res); + let payload = Payload::AggregateSignature(AggregateSignature { + group_commitment, + schnorr_signature, + }); + + let message = Message { + header, + payload: payload.clone(), + }; + + // check general structure serialization/deserialization + serialize_message(message, setup.aggregator, setup.signer1); + + // check payload serialization/deserialization + let mut payload_serialized_bytes = bincode::serialize(&payload).unwrap(); + + // check the message type is correct + let deserialized_msg_type: MsgType = + bincode::deserialize(&payload_serialized_bytes[0..4]).unwrap(); + assert_eq!(deserialized_msg_type, MsgType::AggregateSignature); + + // remove the msg_type from the the payload + payload_serialized_bytes = + (&payload_serialized_bytes[4..payload_serialized_bytes.len()]).to_vec(); + + // group_commitment is 32 bytes + let deserialized_group_commiment: GroupCommitment = + bincode::deserialize(&payload_serialized_bytes[0..32]).unwrap(); + // schnorr_signature is 32 bytes + let deserialized_schnorr_signature: SignatureResponse = + bincode::deserialize(&payload_serialized_bytes[32..64]).unwrap(); + + // no leftover bytes + assert_eq!(payload_serialized_bytes.len(), 64); + + assert_eq!(deserialized_group_commiment, group_commitment); + assert_eq!(deserialized_schnorr_signature, schnorr_signature); +} + +#[test] +fn btreemap() { + let mut setup = basic_setup(); + let mut map = BTreeMap::new(); + + let (_nonce, commitment) = frost::preprocess(1, u64::from(setup.signer1), &mut setup.rng); + + let commitments = vec![commitment[0]]; + let participants = vec![setup.signer1]; + let signing_commitments = create_signing_commitments(commitments, participants); + + map.insert(ParticipantId::Signer(1), &signing_commitments); + map.insert(ParticipantId::Signer(2), &signing_commitments); + map.insert(ParticipantId::Signer(0), &signing_commitments); + + // Check the ascending order + let mut map_iter = map.iter(); + let (key, _) = map_iter.next().unwrap(); + assert_eq!(*key, ParticipantId::Signer(0)); + let (key, _) = map_iter.next().unwrap(); + assert_eq!(*key, ParticipantId::Signer(1)); + let (key, _) = map_iter.next().unwrap(); + assert_eq!(*key, ParticipantId::Signer(2)); + + // Add a repeated key + map.insert(ParticipantId::Signer(1), &signing_commitments); + // BTreeMap is not increasing + assert_eq!(map.len(), 3); +} + +// utility functions + +fn create_valid_header(sender: ParticipantId, receiver: ParticipantId) -> Header { + Validate::validate(&Header { + version: constants::BASIC_FROST_SERIALIZATION, + sender: sender, + receiver: receiver, + }) + .expect("always a valid header") + .clone() +} + +fn serialize_header( + header_serialized_bytes: Vec, + sender: ParticipantId, + receiver: ParticipantId, +) { + let deserialized_version: MsgVersion = + bincode::deserialize(&header_serialized_bytes[0..1]).unwrap(); + let deserialized_sender: ParticipantId = + bincode::deserialize(&header_serialized_bytes[1..9]).unwrap(); + let deserialized_receiver: ParticipantId = + bincode::deserialize(&header_serialized_bytes[9..17]).unwrap(); + assert_eq!(deserialized_version, constants::BASIC_FROST_SERIALIZATION); + assert_eq!(deserialized_sender, sender); + assert_eq!(deserialized_receiver, receiver); +} + +fn serialize_message(message: Message, sender: ParticipantId, receiver: ParticipantId) { + let serialized_bytes = bincode::serialize(&message).unwrap(); + let deserialized_bytes: Message = bincode::deserialize(&serialized_bytes).unwrap(); + assert_eq!(message, deserialized_bytes); + + let serialized_json = serde_json::to_string(&message).unwrap(); + let deserialized_json: Message = serde_json::from_str(serialized_json.as_str()).unwrap(); + assert_eq!(message, deserialized_json); + + let header_serialized_bytes = bincode::serialize(&message.header).unwrap(); + serialize_header(header_serialized_bytes, sender, receiver); + + // make sure the message fields are in the right order + let message_serialized_bytes = bincode::serialize(&message).unwrap(); + let deserialized_header: Header = + bincode::deserialize(&message_serialized_bytes[0..17]).unwrap(); + let deserialized_payload: Payload = + bincode::deserialize(&message_serialized_bytes[17..message_serialized_bytes.len()]) + .unwrap(); + assert_eq!(deserialized_header, message.header); + assert_eq!(deserialized_payload, message.payload); +} + +struct Setup { + rng: rand::rngs::ThreadRng, + num_signers: u8, + threshold: u8, + dealer: ParticipantId, + aggregator: ParticipantId, + signer1: ParticipantId, + signer2: ParticipantId, +} + +fn basic_setup() -> Setup { + Setup { + rng: thread_rng(), + num_signers: 3, + threshold: 2, + dealer: ParticipantId::Dealer, + aggregator: ParticipantId::Aggregator, + signer1: ParticipantId::Signer(0), + signer2: ParticipantId::Signer(1), + } +} + +fn full_setup() -> (Setup, signature::Signature) { + let mut setup = basic_setup(); + + // aggregator creates the shares and pubkeys for this round + let (shares, pubkeys) = + frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); + + let mut nonces: std::collections::HashMap> = + std::collections::HashMap::with_capacity(setup.threshold as usize); + let mut commitments: Vec = + Vec::with_capacity(setup.threshold as usize); + + // aggregator generates nonces and signing commitments for each participant. + for participant_index in 1..(setup.threshold + 1) { + let (nonce, commitment) = frost::preprocess(1, participant_index as u64, &mut setup.rng); + nonces.insert(participant_index as u64, nonce); + commitments.push(commitment[0]); + } + + // aggregator generates a signing package + let mut signature_shares: Vec = + Vec::with_capacity(setup.threshold as usize); + let message = "message to sign".as_bytes().to_vec(); + let signing_package = frost::SigningPackage { + message: message.clone(), + signing_commitments: commitments, + }; + + // each participant generates their signature share + for (participant_index, nonce) in nonces { + let share_package = shares + .iter() + .find(|share| participant_index == share.index) + .unwrap(); + let nonce_to_use = nonce[0]; + let signature_share = frost::sign(&signing_package, nonce_to_use, share_package).unwrap(); + signature_shares.push(signature_share); + } + + // aggregator generate the final signature + let final_signature = + frost::aggregate(&signing_package, &signature_shares[..], &pubkeys).unwrap(); + (setup, final_signature) +} + +fn generate_share_commitment( + shares: &Vec, + participants: Vec, +) -> BTreeMap { + assert_eq!(shares.len(), participants.len()); + participants + .into_iter() + .zip(shares) + .map(|(participant_id, share)| { + ( + participant_id, + Commitment::from(share.share.commitment.0[0].clone()), + ) + }) + .collect() +} + +fn create_signing_commitments( + commitments: Vec, + participants: Vec, +) -> BTreeMap { + assert_eq!(commitments.len(), participants.len()); + participants + .into_iter() + .zip(commitments) + .map(|(participant_id, commitment)| { + let signing_commitment = SigningCommitments { + hiding: Commitment(jubjub::AffinePoint::from(commitment.hiding).to_bytes()), + binding: Commitment(jubjub::AffinePoint::from(commitment.binding).to_bytes()), + }; + (participant_id, signing_commitment) + }) + .collect() +} diff --git a/frost-ristretto255/src/messages/tests/prop.rs b/frost-ristretto255/src/messages/tests/prop.rs new file mode 100644 index 0000000..e307a92 --- /dev/null +++ b/frost-ristretto255/src/messages/tests/prop.rs @@ -0,0 +1,15 @@ +use proptest::prelude::*; + +use crate::messages::*; + +proptest! { + #[test] + fn serialize_message( + message in any::(), + ) { + let serialized = bincode::serialize(&message).unwrap(); + let deserialized: Message = bincode::deserialize(serialized.as_slice()).unwrap(); + + prop_assert_eq!(message, deserialized); + } +} diff --git a/frost-ristretto255/src/messages/validate.rs b/frost-ristretto255/src/messages/validate.rs new file mode 100644 index 0000000..5d1d9ce --- /dev/null +++ b/frost-ristretto255/src/messages/validate.rs @@ -0,0 +1,143 @@ +//! Validation rules specified in [RFC-001#rules] +//! +//! [RFC-001#rules]: https://github.com/ZcashFoundation/redjubjub/blob/main/rfcs/0001-messages.md#rules + +use super::constants::{ + BASIC_FROST_SERIALIZATION, MAX_SIGNERS, MIN_SIGNERS, MIN_THRESHOLD, + ZCASH_MAX_PROTOCOL_MESSAGE_LEN, +}; +use super::*; + +use thiserror::Error; + +pub trait Validate { + fn validate(&self) -> Result<&Self, MsgErr>; +} + +impl Validate for Message { + fn validate(&self) -> Result<&Self, MsgErr> { + match self.payload { + Payload::SharePackage(_) => { + if self.header.sender != ParticipantId::Dealer { + return Err(MsgErr::SenderMustBeDealer); + } + if !matches!(self.header.receiver, ParticipantId::Signer(_)) { + return Err(MsgErr::ReceiverMustBeSigner); + } + } + Payload::SigningCommitments(_) => { + if !matches!(self.header.sender, ParticipantId::Signer(_)) { + return Err(MsgErr::SenderMustBeSigner); + } + if self.header.receiver != ParticipantId::Aggregator { + return Err(MsgErr::ReceiverMustBeAggregator); + } + } + Payload::SigningPackage(_) => { + if self.header.sender != ParticipantId::Aggregator { + return Err(MsgErr::SenderMustBeAggregator); + } + if !matches!(self.header.receiver, ParticipantId::Signer(_)) { + return Err(MsgErr::ReceiverMustBeSigner); + } + } + Payload::SignatureShare(_) => { + if !matches!(self.header.sender, ParticipantId::Signer(_)) { + return Err(MsgErr::SenderMustBeSigner); + } + if self.header.receiver != ParticipantId::Aggregator { + return Err(MsgErr::ReceiverMustBeAggregator); + } + } + Payload::AggregateSignature(_) => { + if self.header.sender != ParticipantId::Aggregator { + return Err(MsgErr::SenderMustBeAggregator); + } + if !matches!(self.header.receiver, ParticipantId::Signer(_)) { + return Err(MsgErr::ReceiverMustBeSigner); + } + } + } + self.header.validate()?; + self.payload.validate()?; + Ok(self) + } +} + +impl Validate for Header { + fn validate(&self) -> Result<&Self, MsgErr> { + // Validate the message version. + // By now we only have 1 valid version so we compare against that. + if self.version != BASIC_FROST_SERIALIZATION { + return Err(MsgErr::WrongVersion); + } + + // Make sure the sender and the receiver are not the same. + if self.sender == self.receiver { + return Err(MsgErr::SameSenderAndReceiver); + } + Ok(self) + } +} + +impl Validate for Payload { + fn validate(&self) -> Result<&Self, MsgErr> { + match self { + Payload::SharePackage(share_package) => { + if share_package.share_commitment.len() < MIN_SIGNERS { + return Err(MsgErr::NotEnoughCommitments(MIN_SIGNERS)); + } + + if share_package.share_commitment.len() > MAX_SIGNERS.into() { + return Err(MsgErr::TooManyCommitments); + } + } + Payload::SigningCommitments(_) => {} + Payload::SigningPackage(signing_package) => { + if signing_package.message.len() > ZCASH_MAX_PROTOCOL_MESSAGE_LEN { + return Err(MsgErr::MsgTooBig); + } + + if signing_package.signing_commitments.len() < MIN_THRESHOLD { + return Err(MsgErr::NotEnoughCommitments(MIN_THRESHOLD)); + } + + if signing_package.signing_commitments.len() > MAX_SIGNERS.into() { + return Err(MsgErr::TooManyCommitments); + } + } + Payload::SignatureShare(_) => {} + Payload::AggregateSignature(_) => {} + } + + Ok(self) + } +} + +/// The error a message can produce if it fails validation. +#[derive(Error, Debug, PartialEq)] +pub enum MsgErr { + #[error("wrong version number")] + WrongVersion, + #[error("sender and receiver are the same")] + SameSenderAndReceiver, + #[error("the sender of this message must be the dealer")] + SenderMustBeDealer, + #[error("the receiver of this message must be a signer")] + ReceiverMustBeSigner, + #[error("the sender of this message must be a signer")] + SenderMustBeSigner, + #[error("the receiver of this message must be the aggregator")] + ReceiverMustBeAggregator, + #[error("the sender of this message must be the aggregator")] + SenderMustBeAggregator, + #[error("the number of signers must be at least {0}")] + NotEnoughCommitments(usize), + #[error("the number of signers can't be more than {}", MAX_SIGNERS)] + TooManyCommitments, + #[error( + "the message field can't be bigger than {}", + ZCASH_MAX_PROTOCOL_MESSAGE_LEN + )] + MsgTooBig, +} diff --git a/frost-ristretto255/src/signature.rs b/frost-ristretto255/src/signature.rs new file mode 100644 index 0000000..d55a6ac --- /dev/null +++ b/frost-ristretto255/src/signature.rs @@ -0,0 +1,38 @@ +// -*- mode: rust; -*- +// +// This file is part of frost-ristretto. +// Copyright (c) 2019-2021 Zcash Foundation +// See LICENSE for licensing information. +// +// Authors: +// - Henry de Valence +// - Deirdre Connolly + +//! Schnorr Signatures on the Ristretto group + +/// A Schnorr signature on the Ristretto group. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Signature { + pub(crate) r_bytes: [u8; 32], + pub(crate) s_bytes: [u8; 32], +} + +impl From<[u8; 64]> for Signature { + fn from(bytes: [u8; 64]) -> Signature { + let mut r_bytes = [0; 32]; + r_bytes.copy_from_slice(&bytes[0..32]); + let mut s_bytes = [0; 32]; + s_bytes.copy_from_slice(&bytes[32..64]); + Signature { r_bytes, s_bytes } + } +} + +impl From for [u8; 64] { + fn from(sig: Signature) -> [u8; 64] { + let mut bytes = [0; 64]; + bytes[0..32].copy_from_slice(&sig.r_bytes[..]); + bytes[32..64].copy_from_slice(&sig.s_bytes[..]); + bytes + } +} diff --git a/frost-ristretto255/src/signing_key.rs b/frost-ristretto255/src/signing_key.rs new file mode 100644 index 0000000..ba70f3b --- /dev/null +++ b/frost-ristretto255/src/signing_key.rs @@ -0,0 +1,116 @@ +// -*- mode: rust; -*- +// +// This file is part of redjubjub. +// Copyright (c) 2019-2021 Zcash Foundation +// See LICENSE for licensing information. +// +// Authors: +// - Deirdre Connolly +// - Henry de Valence + +use std::convert::{TryFrom, TryInto}; + +use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, scalar::Scalar}; +use rand_core::{CryptoRng, RngCore}; +use sha2::{Digest, Sha512}; + +use crate::{Error, Signature, VerificationKey}; + +/// A signing key for a Schnorr signature on the Ristretto group. +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(try_from = "SerdeHelper"))] +#[cfg_attr(feature = "serde", serde(into = "SerdeHelper"))] +pub struct SigningKey { + sk: Scalar, + pk: VerificationKey, +} + +impl<'a> From<&'a SigningKey> for VerificationKey { + fn from(sk: &'a SigningKey) -> VerificationKey { + sk.pk.clone() + } +} + +impl From for [u8; 32] { + fn from(sk: SigningKey) -> [u8; 32] { + sk.sk.to_bytes() + } +} + +impl TryFrom<[u8; 32]> for SigningKey { + type Error = Error; + + fn try_from(bytes: [u8; 32]) -> Result { + match Scalar::from_canonical_bytes(bytes) { + Some(sk) => { + let pk = VerificationKey::from(&sk); + return Ok(SigningKey { sk, pk }); + } + None => Err(Error::MalformedSigningKey), + } + } +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +struct SerdeHelper([u8; 32]); + +impl TryFrom for SigningKey { + type Error = Error; + + fn try_from(helper: SerdeHelper) -> Result { + helper.0.try_into() + } +} + +impl From for SerdeHelper { + fn from(sk: SigningKey) -> Self { + Self(sk.into()) + } +} + +impl SigningKey { + /// Generate a new signing key. + pub fn new(mut rng: R) -> SigningKey { + let sk = { + let mut bytes = [0; 64]; + rng.fill_bytes(&mut bytes); + Scalar::from_bytes_mod_order_wide(&bytes) + }; + let pk = VerificationKey::from(&sk); + SigningKey { sk, pk } + } + + /// Create a signature `msg` using this `SigningKey`. + // Similar to signature::Signer but without boxed errors. + pub fn sign(&self, mut rng: R, msg: &[u8]) -> Signature { + // Choose a byte sequence uniformly at random of length + // (\ell_H + 128)/8 bytes. For RedJubjub this is (512 + 128)/8 = 80. + let random_bytes = { + let mut bytes = [0; 80]; + rng.fill_bytes(&mut bytes); + bytes + }; + + let nonce = Scalar::from_hash( + Sha512::new() + .chain(&random_bytes[..]) + .chain(&self.pk.bytes.bytes[..]) // XXX ugly + .chain(msg), + ); + + // XXX: does this need `RistrettoPoint::from_uniform_bytes()` ? + let r_bytes = (RISTRETTO_BASEPOINT_POINT * nonce).compress().to_bytes(); + + let c = Scalar::from_hash( + Sha512::new() + .chain(&r_bytes[..]) + .chain(&self.pk.bytes.bytes[..]) // XXX ugly + .chain(msg), + ); + + let s_bytes = (&nonce + &(c * &self.sk)).to_bytes(); + + Signature { r_bytes, s_bytes } + } +} diff --git a/frost-ristretto255/src/verification_key.rs b/frost-ristretto255/src/verification_key.rs new file mode 100644 index 0000000..e7b4965 --- /dev/null +++ b/frost-ristretto255/src/verification_key.rs @@ -0,0 +1,157 @@ +// -*- mode: rust; -*- +// +// This file is part of redjubjub. +// Copyright (c) 2019-2021 Zcash Foundation +// See LICENSE for licensing information. +// +// Authors: +// - Deirdre Connolly +// - Henry de Valence + +use std::{ + convert::{TryFrom, TryInto}, + hash::{Hash, Hasher}, +}; + +use curve25519_dalek::{ + ristretto::{CompressedRistretto, RistrettoPoint}, + scalar::Scalar, + traits::Identity, +}; +use sha2::{Digest, Sha512}; + +use crate::{Error, Signature}; + +/// A refinement type for `[u8; 32]` indicating that the bytes represent an +/// encoding of a verification key for Schnorr signatures over the Ristretto +/// group. +/// +/// This is useful for representing a compressed verification key; the +/// [`VerificationKey`] type in this library holds other decompressed state +/// used in signature verification. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct VerificationKeyBytes { + pub(crate) bytes: [u8; 32], +} + +impl From<[u8; 32]> for VerificationKeyBytes { + fn from(bytes: [u8; 32]) -> VerificationKeyBytes { + VerificationKeyBytes { bytes } + } +} + +impl From for [u8; 32] { + fn from(refined: VerificationKeyBytes) -> [u8; 32] { + refined.bytes + } +} + +impl Hash for VerificationKeyBytes { + fn hash(&self, state: &mut H) { + self.bytes.hash(state); + } +} + +/// A valid verification key for Schnorr signatures over the Ristretto group. +/// +/// This type holds decompressed state used in signature verification; if the +/// verification key may not be used immediately, it is probably better to use +/// [`VerificationKeyBytes`], which is a refinement type for `[u8; 32]`. +/// +/// ## Consensus properties +/// +/// The `TryFrom` conversion performs the following Zcash +/// consensus rule checks: +/// +/// 1. The check that the bytes are a canonical encoding of a verification key; +/// 2. The check that the verification key is not a point of small order. +#[derive(PartialEq, Copy, Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(try_from = "VerificationKeyBytes"))] +#[cfg_attr(feature = "serde", serde(into = "VerificationKeyBytes"))] +pub struct VerificationKey { + pub(crate) point: RistrettoPoint, + pub(crate) bytes: VerificationKeyBytes, +} + +impl From for VerificationKeyBytes { + fn from(pk: VerificationKey) -> VerificationKeyBytes { + pk.bytes + } +} + +impl From for [u8; 32] { + fn from(pk: VerificationKey) -> [u8; 32] { + pk.bytes.bytes + } +} + +impl TryFrom for VerificationKey { + type Error = Error; + + fn try_from(bytes: VerificationKeyBytes) -> Result { + // This checks that the encoding is canonical... + match CompressedRistretto::from_slice(&bytes.bytes).decompress() { + Some(point) => Ok(VerificationKey { point, bytes }), + None => Err(Error::MalformedVerificationKey), + } + } +} + +impl TryFrom<[u8; 32]> for VerificationKey { + type Error = Error; + + fn try_from(bytes: [u8; 32]) -> Result { + VerificationKeyBytes::from(bytes).try_into() + } +} + +impl VerificationKey { + pub(crate) fn from(s: &Scalar) -> VerificationKey { + let point = curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT * s; + let bytes = VerificationKeyBytes { + bytes: point.compress().to_bytes(), + }; + VerificationKey { bytes, point } + } + + /// Verify a purported `signature` over `msg` made by this verification key. + // This is similar to impl signature::Verifier but without boxed errors + pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { + let mut c = Sha512::new(); + + c.update(&signature.r_bytes[..]); + c.update(&self.bytes.bytes[..]); // XXX ugly + c.update(msg); + + self.verify_prehashed(signature, Scalar::from_hash(c)) + } + + /// Verify a purported `signature` with a prehashed challenge. + #[allow(non_snake_case)] + pub(crate) fn verify_prehashed(&self, signature: &Signature, c: Scalar) -> Result<(), Error> { + let r = match CompressedRistretto::from_slice(&signature.r_bytes).decompress() { + Some(point) => point, + None => return Err(Error::InvalidSignature), + }; + + let s = match Scalar::from_canonical_bytes(signature.s_bytes) { + Some(s) => s, + None => return Err(Error::InvalidSignature), + }; + + // XXX rewrite as normal double scalar mul + // Verify check is h * ( - s * B + R + c * A) == 0 + // h * ( s * B - c * A - R) == 0 + let sB = curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT * &s; + let cA = &self.point * &c; + let check = sB - cA - r; + + if check == RistrettoPoint::identity() { + Ok(()) + } else { + Err(Error::InvalidSignature) + } + } +} diff --git a/frost-ristretto255/tests/batch.rs b/frost-ristretto255/tests/batch.rs new file mode 100644 index 0000000..fe095dd --- /dev/null +++ b/frost-ristretto255/tests/batch.rs @@ -0,0 +1,99 @@ +use rand::thread_rng; + +use redjubjub::*; + +#[test] +fn spendauth_batch_verify() { + let mut rng = thread_rng(); + let mut batch = batch::Verifier::new(); + for _ in 0..32 { + let sk = SigningKey::::new(&mut rng); + let vk = VerificationKey::from(&sk); + let msg = b"BatchVerifyTest"; + let sig = sk.sign(&mut rng, &msg[..]); + batch.queue((vk.into(), sig, msg)); + } + assert!(batch.verify(rng).is_ok()); +} + +#[test] +fn binding_batch_verify() { + let mut rng = thread_rng(); + let mut batch = batch::Verifier::new(); + for _ in 0..32 { + let sk = SigningKey::::new(&mut rng); + let vk = VerificationKey::from(&sk); + let msg = b"BatchVerifyTest"; + let sig = sk.sign(&mut rng, &msg[..]); + batch.queue((vk.into(), sig, msg)); + } + assert!(batch.verify(rng).is_ok()); +} + +#[test] +fn alternating_batch_verify() { + let mut rng = thread_rng(); + let mut batch = batch::Verifier::new(); + for i in 0..32 { + let item: batch::Item = match i % 2 { + 0 => { + let sk = SigningKey::::new(&mut rng); + let vk = VerificationKey::from(&sk); + let msg = b"BatchVerifyTest"; + let sig = sk.sign(&mut rng, &msg[..]); + (vk.into(), sig, msg).into() + } + 1 => { + let sk = SigningKey::::new(&mut rng); + let vk = VerificationKey::from(&sk); + let msg = b"BatchVerifyTest"; + let sig = sk.sign(&mut rng, &msg[..]); + (vk.into(), sig, msg).into() + } + _ => unreachable!(), + }; + batch.queue(item); + } + assert!(batch.verify(rng).is_ok()); +} + +#[test] +fn bad_batch_verify() { + let mut rng = thread_rng(); + let bad_index = 4; // must be even + let mut batch = batch::Verifier::new(); + let mut items = Vec::new(); + for i in 0..32 { + let item: batch::Item = match i % 2 { + 0 => { + let sk = SigningKey::::new(&mut rng); + let vk = VerificationKey::from(&sk); + let msg = b"BatchVerifyTest"; + let sig = if i != bad_index { + sk.sign(&mut rng, &msg[..]) + } else { + sk.sign(&mut rng, b"bad") + }; + (vk.into(), sig, msg).into() + } + 1 => { + let sk = SigningKey::::new(&mut rng); + let vk = VerificationKey::from(&sk); + let msg = b"BatchVerifyTest"; + let sig = sk.sign(&mut rng, &msg[..]); + (vk.into(), sig, msg).into() + } + _ => unreachable!(), + }; + items.push(item.clone()); + batch.queue(item); + } + assert!(batch.verify(rng).is_err()); + for (i, item) in items.drain(..).enumerate() { + if i != bad_index { + assert!(item.verify_single().is_ok()); + } else { + assert!(item.verify_single().is_err()); + } + } +} diff --git a/frost-ristretto255/tests/bincode.rs b/frost-ristretto255/tests/bincode.rs new file mode 100644 index 0000000..3cca7cd --- /dev/null +++ b/frost-ristretto255/tests/bincode.rs @@ -0,0 +1,111 @@ +use std::convert::TryFrom; + +use proptest::prelude::*; + +use redjubjub::*; + +proptest! { + #[test] + fn secretkey_serialization( + bytes in prop::array::uniform32(any::()), + ) { + let sk_result_from = SigningKey::::try_from(bytes); + let sk_result_bincode: Result, _> + = bincode::deserialize(&bytes[..]); + + // Check 1: both decoding methods should agree + match (sk_result_from, sk_result_bincode) { + // Both agree on success + (Ok(sk_from), Ok(sk_bincode)) => { + let pk_bytes_from = VerificationKeyBytes::from(VerificationKey::from(&sk_from)); + let pk_bytes_bincode = VerificationKeyBytes::from(VerificationKey::from(&sk_bincode)); + assert_eq!(pk_bytes_from, pk_bytes_bincode); + + // Check 2: bincode encoding should match original bytes. + let bytes_bincode = bincode::serialize(&sk_from).unwrap(); + assert_eq!(&bytes[..], &bytes_bincode[..]); + + // Check 3: From encoding should match original bytes. + let bytes_from: [u8; 32] = sk_bincode.into(); + assert_eq!(&bytes[..], &bytes_from[..]); + } + // Both agree on failure + (Err(_), Err(_)) => {}, + _ => panic!("bincode and try_from do not agree"), + } + } + + #[test] + fn publickeybytes_serialization( + bytes in prop::array::uniform32(any::()), + ) { + let pk_bytes_from = VerificationKeyBytes::::from(bytes); + let pk_bytes_bincode: VerificationKeyBytes:: + = bincode::deserialize(&bytes[..]).unwrap(); + + // Check 1: both decoding methods should have the same result. + assert_eq!(pk_bytes_from, pk_bytes_bincode); + + // Check 2: bincode encoding should match original bytes. + let bytes_bincode = bincode::serialize(&pk_bytes_from).unwrap(); + assert_eq!(&bytes[..], &bytes_bincode[..]); + + // Check 3: From encoding should match original bytes. + let bytes_from: [u8; 32] = pk_bytes_bincode.into(); + assert_eq!(&bytes[..], &bytes_from[..]); + } + + #[test] + fn publickey_serialization( + bytes in prop::array::uniform32(any::()), + ) { + let pk_result_try_from = VerificationKey::::try_from(bytes); + let pk_result_bincode: Result, _> + = bincode::deserialize(&bytes[..]); + + // Check 1: both decoding methods should have the same result + match (pk_result_try_from, pk_result_bincode) { + // Both agree on success + (Ok(pk_try_from), Ok(pk_bincode)) => { + // Check 2: bincode encoding should match original bytes + let bytes_bincode = bincode::serialize(&pk_try_from).unwrap(); + assert_eq!(&bytes[..], &bytes_bincode[..]); + // Check 3: From encoding should match original bytes + let bytes_from: [u8; 32] = pk_bincode.into(); + assert_eq!(&bytes[..], &bytes_from[..]); + }, + // Both agree on failure + (Err(_), Err(_)) => {}, + _ => panic!("bincode and try_from do not agree"), + } + } + + #[test] + fn signature_serialization( + lo in prop::array::uniform32(any::()), + hi in prop::array::uniform32(any::()), + ) { + // array length hack + let bytes = { + let mut bytes = [0; 64]; + bytes[0..32].copy_from_slice(&lo[..]); + bytes[32..64].copy_from_slice(&hi[..]); + bytes + }; + + let sig_bytes_from = Signature::::from(bytes); + let sig_bytes_bincode: Signature:: + = bincode::deserialize(&bytes[..]).unwrap(); + + // Check 1: both decoding methods should have the same result. + assert_eq!(sig_bytes_from, sig_bytes_bincode); + + // Check 2: bincode encoding should match original bytes. + let bytes_bincode = bincode::serialize(&sig_bytes_from).unwrap(); + assert_eq!(&bytes[..], &bytes_bincode[..]); + + // Check 3: From encoding should match original bytes. + let bytes_from: [u8; 64] = sig_bytes_bincode.into(); + assert_eq!(&bytes[..], &bytes_from[..]); + } +} diff --git a/frost-ristretto255/tests/frost.rs b/frost-ristretto255/tests/frost.rs new file mode 100644 index 0000000..fa47b76 --- /dev/null +++ b/frost-ristretto255/tests/frost.rs @@ -0,0 +1,62 @@ +use rand::thread_rng; +use std::collections::HashMap; + +use redjubjub::frost; + +#[test] +fn check_sign_with_dealer() { + let mut rng = thread_rng(); + let numsigners = 5; + let threshold = 3; + let (shares, pubkeys) = frost::keygen_with_dealer(numsigners, threshold, &mut rng).unwrap(); + + let mut nonces: HashMap> = + HashMap::with_capacity(threshold as usize); + let mut commitments: Vec = Vec::with_capacity(threshold as usize); + + // Round 1, generating nonces and signing commitments for each participant. + for participant_index in 1..(threshold + 1) { + // Generate one (1) nonce and one SigningCommitments instance for each + // participant, up to _threshold_. + let (nonce, commitment) = frost::preprocess(1, participant_index as u64, &mut rng); + nonces.insert(participant_index as u64, nonce); + commitments.push(commitment[0]); + } + + // This is what the signature aggregator / coordinator needs to do: + // - decide what message to sign + // - take one (unused) commitment per signing participant + let mut signature_shares: Vec = Vec::with_capacity(threshold as usize); + let message = "message to sign".as_bytes(); + let signing_package = frost::SigningPackage { + message: message.to_vec(), + signing_commitments: commitments, + }; + + // Round 2: each participant generates their signature share + for (participant_index, nonce) in nonces { + let share_package = shares + .iter() + .find(|share| participant_index == share.index) + .unwrap(); + let nonce_to_use = nonce[0]; + // Each participant generates their signature share. + let signature_share = frost::sign(&signing_package, nonce_to_use, share_package).unwrap(); + signature_shares.push(signature_share); + } + + // The aggregator collects the signing shares from all participants and + // generates the final signature. + let group_signature_res = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys); + assert!(group_signature_res.is_ok()); + let group_signature = group_signature_res.unwrap(); + + // Check that the threshold signature can be verified by the group public + // key (aka verification key). + assert!(pubkeys + .group_public + .verify(&message, &group_signature) + .is_ok()); + + // TODO: also check that the SharePackage.group_public also verifies the group signature. +} diff --git a/frost-ristretto255/tests/librustzcash_vectors.rs b/frost-ristretto255/tests/librustzcash_vectors.rs new file mode 100644 index 0000000..7e2af6c --- /dev/null +++ b/frost-ristretto255/tests/librustzcash_vectors.rs @@ -0,0 +1,1251 @@ +use std::convert::TryFrom; + +#[macro_use] +extern crate lazy_static; + +use redjubjub::*; + +#[test] +fn verify_librustzcash_spendauth() { + for (msg, sig, pk_bytes) in LIBRUSTZCASH_SPENDAUTH_SIGS.iter() { + assert!(VerificationKey::try_from(*pk_bytes) + .and_then(|pk| pk.verify(&msg, &sig)) + .is_ok()); + } +} + +#[test] +fn verify_librustzcash_binding() { + for (msg, sig, pk_bytes) in LIBRUSTZCASH_BINDING_SIGS.iter() { + assert!(VerificationKey::try_from(*pk_bytes) + .and_then(|pk| pk.verify(&msg, &sig)) + .is_ok()); + } +} + +lazy_static! { + static ref LIBRUSTZCASH_SPENDAUTH_SIGS: [( + Vec, + Signature, + VerificationKeyBytes + ); 32] = [ + ( + [ + 16, 28, 190, 75, 156, 66, 96, 79, 4, 199, 3, 195, 150, 247, 136, 198, 203, 45, 109, + 125, 88, 244, 84, 48, 177, 46, 178, 237, 214, 64, 7, 108 + ] + .to_vec(), + [ + 184, 72, 147, 0, 184, 42, 112, 23, 132, 161, 146, 154, 96, 228, 121, 19, 57, 57, + 59, 142, 209, 233, 151, 29, 9, 55, 142, 153, 71, 124, 203, 76, 203, 183, 165, 39, + 20, 49, 230, 24, 162, 232, 156, 176, 115, 184, 191, 122, 27, 103, 243, 15, 226, 72, + 177, 219, 67, 176, 95, 27, 108, 40, 69, 10 + ] + .into(), + [ + 181, 241, 137, 93, 46, 42, 77, 236, 42, 240, 254, 156, 102, 146, 236, 44, 166, 93, + 85, 228, 118, 90, 109, 138, 25, 70, 46, 202, 61, 255, 216, 4 + ] + .into(), + ), + ( + [ + 108, 56, 40, 255, 104, 11, 219, 166, 98, 154, 153, 67, 75, 194, 79, 17, 86, 115, + 26, 175, 150, 173, 228, 209, 66, 119, 33, 94, 87, 187, 19, 49 + ] + .to_vec(), + [ + 79, 51, 180, 117, 215, 124, 136, 130, 125, 50, 132, 219, 196, 16, 28, 85, 68, 163, + 54, 62, 86, 36, 115, 180, 28, 35, 105, 224, 1, 31, 165, 186, 26, 50, 252, 46, 139, + 219, 234, 182, 96, 126, 97, 94, 221, 61, 19, 90, 143, 166, 200, 7, 185, 134, 183, + 2, 81, 255, 168, 130, 34, 25, 142, 12 + ] + .into(), + [ + 32, 138, 141, 49, 232, 43, 241, 22, 66, 21, 45, 171, 245, 191, 115, 78, 218, 71, + 232, 16, 49, 246, 189, 199, 239, 171, 119, 186, 146, 179, 252, 155 + ] + .into(), + ), + ( + [ + 145, 63, 229, 40, 221, 46, 129, 128, 68, 42, 148, 149, 87, 96, 109, 94, 188, 197, + 117, 95, 73, 255, 11, 214, 198, 41, 249, 226, 22, 130, 163, 215 + ] + .to_vec(), + [ + 72, 6, 48, 149, 135, 161, 137, 34, 233, 249, 121, 150, 228, 0, 165, 120, 106, 27, + 21, 249, 158, 109, 128, 193, 73, 236, 14, 86, 21, 248, 160, 44, 27, 223, 183, 139, + 19, 121, 238, 236, 194, 40, 243, 249, 223, 15, 191, 65, 182, 7, 23, 61, 76, 70, 37, + 11, 45, 98, 208, 218, 81, 97, 67, 12 + ] + .into(), + [ + 209, 140, 211, 141, 81, 2, 124, 211, 60, 118, 106, 253, 141, 253, 192, 30, 2, 216, + 130, 239, 243, 55, 48, 65, 33, 4, 110, 71, 247, 172, 6, 65 + ] + .into(), + ), + ( + [ + 208, 243, 205, 249, 49, 107, 35, 33, 206, 165, 99, 248, 160, 186, 39, 50, 71, 156, + 226, 164, 9, 17, 162, 158, 198, 154, 10, 123, 189, 221, 247, 116 + ] + .to_vec(), + [ + 96, 38, 240, 221, 163, 157, 103, 24, 101, 88, 180, 51, 66, 149, 94, 187, 20, 236, + 117, 125, 118, 174, 22, 98, 249, 46, 76, 86, 183, 87, 138, 29, 38, 103, 100, 29, + 156, 175, 232, 144, 186, 10, 98, 237, 220, 98, 116, 83, 230, 58, 38, 91, 241, 162, + 38, 216, 38, 248, 51, 35, 116, 229, 245, 1 + ] + .into(), + [ + 19, 244, 2, 26, 154, 118, 210, 35, 37, 167, 110, 77, 38, 171, 92, 151, 150, 251, + 63, 160, 138, 203, 225, 171, 44, 47, 186, 127, 26, 94, 145, 69 + ] + .into(), + ), + ( + [ + 73, 53, 27, 188, 30, 74, 75, 136, 216, 22, 115, 179, 41, 12, 101, 135, 241, 208, + 177, 226, 3, 109, 120, 48, 2, 120, 150, 27, 29, 59, 61, 180 + ] + .to_vec(), + [ + 203, 244, 129, 94, 156, 32, 138, 223, 81, 124, 198, 181, 173, 91, 243, 132, 223, + 81, 53, 71, 49, 159, 197, 177, 192, 2, 36, 14, 165, 50, 88, 158, 211, 201, 110, + 119, 247, 80, 67, 71, 53, 170, 20, 67, 1, 67, 47, 73, 19, 253, 251, 175, 121, 94, + 162, 58, 126, 42, 13, 85, 33, 134, 4, 2 + ] + .into(), + [ + 47, 222, 182, 103, 31, 179, 90, 219, 250, 195, 128, 213, 41, 186, 62, 232, 156, 68, + 205, 52, 43, 71, 73, 212, 6, 108, 217, 73, 156, 166, 145, 102 + ] + .into(), + ), + ( + [ + 158, 55, 154, 163, 138, 43, 56, 169, 209, 184, 225, 86, 39, 131, 0, 194, 62, 122, + 84, 176, 197, 115, 88, 216, 47, 127, 36, 131, 215, 205, 251, 69 + ] + .to_vec(), + [ + 253, 159, 166, 4, 58, 37, 220, 26, 159, 123, 69, 114, 202, 2, 113, 136, 103, 251, + 181, 27, 143, 135, 117, 89, 251, 177, 67, 86, 26, 39, 64, 194, 194, 16, 19, 242, + 189, 157, 128, 9, 17, 6, 148, 194, 43, 164, 238, 156, 15, 135, 62, 99, 247, 58, + 246, 104, 162, 24, 205, 152, 193, 214, 118, 5 + ] + .into(), + [ + 17, 132, 1, 251, 36, 233, 78, 198, 122, 35, 231, 247, 175, 15, 214, 37, 37, 18, + 247, 20, 16, 165, 66, 133, 243, 75, 69, 78, 5, 101, 42, 174 + ] + .into(), + ), + ( + [ + 217, 101, 76, 11, 40, 202, 129, 190, 187, 84, 92, 68, 181, 190, 66, 89, 233, 122, + 72, 49, 98, 38, 40, 129, 112, 67, 119, 39, 61, 230, 140, 107 + ] + .to_vec(), + [ + 94, 211, 95, 97, 114, 31, 97, 88, 94, 137, 189, 186, 206, 228, 157, 10, 148, 94, + 114, 109, 178, 182, 138, 199, 223, 55, 230, 174, 76, 199, 187, 28, 250, 221, 85, + 96, 100, 217, 24, 129, 219, 27, 10, 97, 195, 112, 148, 210, 62, 188, 61, 93, 90, + 185, 107, 58, 177, 123, 81, 220, 219, 199, 119, 0 + ] + .into(), + [ + 244, 173, 81, 29, 254, 24, 62, 207, 198, 165, 40, 87, 251, 222, 99, 74, 26, 190, + 240, 181, 176, 132, 16, 109, 149, 143, 130, 140, 15, 151, 79, 108 + ] + .into(), + ), + ( + [ + 10, 212, 76, 139, 47, 234, 73, 233, 131, 223, 100, 101, 161, 23, 26, 161, 12, 107, + 27, 76, 50, 232, 165, 234, 34, 170, 126, 169, 179, 7, 168, 216 + ] + .to_vec(), + [ + 139, 199, 128, 202, 224, 132, 196, 210, 244, 72, 85, 142, 184, 248, 176, 86, 173, + 70, 205, 224, 161, 234, 51, 253, 239, 182, 81, 171, 192, 201, 180, 134, 212, 132, + 101, 145, 250, 244, 18, 252, 14, 150, 26, 140, 197, 241, 209, 226, 217, 98, 82, + 135, 131, 167, 91, 146, 190, 125, 147, 152, 175, 221, 232, 3 + ] + .into(), + [ + 68, 179, 107, 23, 199, 215, 237, 215, 12, 78, 92, 244, 133, 177, 115, 46, 167, 114, + 174, 10, 70, 38, 232, 102, 4, 27, 229, 238, 5, 128, 86, 155 + ] + .into(), + ), + ( + [ + 7, 121, 157, 250, 188, 252, 108, 217, 56, 87, 183, 45, 98, 129, 233, 210, 121, 143, + 173, 195, 172, 2, 154, 125, 176, 148, 3, 234, 3, 250, 127, 116 + ] + .to_vec(), + [ + 200, 10, 84, 163, 98, 111, 226, 140, 225, 250, 208, 165, 104, 197, 0, 68, 118, 236, + 77, 160, 142, 38, 137, 192, 73, 143, 48, 192, 131, 2, 212, 217, 190, 212, 114, 106, + 232, 178, 156, 18, 25, 116, 101, 150, 208, 248, 222, 183, 159, 19, 181, 83, 211, + 153, 122, 105, 92, 118, 10, 208, 109, 14, 14, 10 + ] + .into(), + [ + 111, 10, 31, 231, 161, 58, 10, 220, 2, 11, 155, 212, 150, 68, 103, 109, 227, 82, + 36, 22, 102, 4, 3, 56, 64, 4, 34, 171, 198, 46, 102, 161 + ] + .into(), + ), + ( + [ + 161, 98, 15, 93, 184, 31, 193, 208, 84, 234, 38, 229, 208, 207, 27, 172, 152, 170, + 39, 138, 187, 88, 195, 82, 253, 108, 101, 128, 110, 86, 114, 242 + ] + .to_vec(), + [ + 18, 41, 243, 39, 192, 12, 24, 229, 74, 197, 174, 162, 214, 152, 159, 92, 220, 52, + 175, 178, 170, 145, 240, 21, 161, 10, 133, 49, 216, 192, 232, 28, 119, 245, 163, + 58, 63, 207, 253, 42, 134, 12, 204, 25, 169, 223, 66, 152, 132, 178, 190, 255, 1, + 193, 120, 237, 17, 164, 1, 172, 146, 11, 176, 2 + ] + .into(), + [ + 29, 23, 116, 183, 78, 100, 121, 77, 61, 83, 68, 115, 247, 127, 102, 90, 216, 110, + 86, 250, 43, 110, 250, 34, 97, 132, 99, 182, 146, 45, 84, 75 + ] + .into(), + ), + ( + [ + 123, 7, 11, 119, 243, 237, 229, 160, 242, 163, 173, 216, 117, 94, 8, 29, 112, 178, + 23, 50, 180, 75, 18, 189, 215, 221, 21, 8, 86, 209, 177, 87 + ] + .to_vec(), + [ + 86, 236, 148, 81, 183, 70, 209, 165, 2, 184, 111, 25, 206, 21, 195, 112, 16, 99, + 185, 161, 2, 19, 27, 53, 80, 133, 39, 39, 227, 103, 94, 140, 0, 10, 28, 243, 255, + 131, 163, 170, 206, 229, 116, 99, 153, 52, 99, 205, 177, 133, 247, 63, 43, 78, 154, + 105, 161, 55, 68, 177, 211, 17, 132, 10 + ] + .into(), + [ + 29, 230, 209, 28, 238, 102, 138, 220, 223, 130, 169, 79, 197, 133, 90, 183, 214, + 204, 190, 100, 0, 89, 221, 164, 60, 131, 235, 21, 68, 150, 204, 174 + ] + .into(), + ), + ( + [ + 181, 94, 198, 199, 78, 194, 64, 118, 146, 51, 79, 70, 73, 206, 87, 51, 96, 13, 145, + 198, 78, 245, 179, 182, 169, 147, 95, 137, 77, 33, 247, 4 + ] + .to_vec(), + [ + 236, 92, 56, 252, 142, 62, 64, 84, 192, 160, 2, 119, 164, 201, 38, 248, 102, 22, + 225, 65, 13, 211, 236, 96, 223, 56, 108, 205, 49, 55, 177, 22, 11, 66, 165, 120, + 114, 57, 144, 81, 170, 50, 119, 41, 138, 151, 109, 248, 211, 141, 208, 110, 241, + 74, 89, 83, 44, 229, 150, 19, 22, 20, 87, 14 + ] + .into(), + [ + 148, 203, 97, 110, 231, 9, 144, 249, 147, 244, 215, 94, 152, 157, 76, 192, 180, 57, + 176, 250, 88, 62, 162, 242, 207, 194, 43, 31, 224, 131, 21, 60 + ] + .into(), + ), + ( + [ + 108, 144, 146, 213, 254, 69, 48, 80, 172, 3, 26, 161, 90, 132, 50, 0, 192, 101, 98, + 65, 167, 199, 175, 147, 29, 177, 217, 226, 72, 233, 93, 118 + ] + .to_vec(), + [ + 56, 134, 173, 220, 187, 159, 248, 241, 192, 65, 91, 20, 145, 141, 192, 118, 101, + 83, 180, 225, 148, 168, 3, 234, 209, 34, 73, 8, 8, 234, 82, 191, 135, 191, 151, 1, + 205, 158, 224, 35, 144, 77, 131, 209, 80, 26, 118, 189, 39, 76, 246, 20, 37, 26, + 16, 70, 36, 69, 253, 26, 251, 230, 155, 10 + ] + .into(), + [ + 173, 141, 143, 17, 147, 238, 245, 194, 249, 178, 20, 48, 35, 192, 16, 157, 150, 22, + 130, 105, 149, 14, 242, 169, 63, 116, 193, 121, 41, 196, 140, 50 + ] + .into(), + ), + ( + [ + 106, 232, 15, 114, 137, 49, 56, 62, 28, 42, 176, 201, 156, 86, 109, 234, 3, 191, + 44, 231, 22, 110, 103, 24, 204, 71, 207, 80, 255, 34, 150, 208 + ] + .to_vec(), + [ + 111, 66, 64, 152, 248, 159, 2, 254, 64, 180, 110, 136, 157, 80, 123, 138, 169, 106, + 86, 180, 105, 131, 109, 52, 252, 70, 48, 86, 230, 35, 181, 88, 158, 173, 73, 67, + 200, 96, 243, 192, 24, 34, 242, 54, 113, 199, 214, 118, 39, 244, 142, 254, 143, + 188, 217, 138, 190, 137, 201, 249, 154, 116, 129, 10 + ] + .into(), + [ + 43, 53, 235, 27, 103, 60, 163, 115, 78, 225, 5, 178, 113, 55, 93, 90, 210, 191, 0, + 249, 175, 80, 72, 140, 22, 173, 81, 237, 10, 176, 48, 20 + ] + .into(), + ), + ( + [ + 115, 113, 54, 112, 20, 175, 145, 55, 156, 102, 8, 55, 94, 188, 73, 151, 45, 250, + 55, 84, 176, 22, 100, 238, 102, 177, 44, 154, 49, 69, 93, 64 + ] + .to_vec(), + [ + 50, 60, 156, 134, 29, 212, 219, 84, 244, 133, 219, 209, 173, 253, 95, 255, 33, 237, + 112, 125, 118, 237, 53, 93, 136, 130, 244, 81, 208, 90, 39, 202, 152, 111, 35, 218, + 255, 167, 173, 5, 160, 80, 97, 104, 109, 174, 255, 108, 171, 192, 91, 209, 108, + 211, 145, 157, 160, 72, 3, 137, 176, 59, 241, 13 + ] + .into(), + [ + 59, 231, 146, 62, 217, 119, 251, 42, 75, 155, 214, 126, 56, 81, 69, 171, 74, 233, + 46, 25, 1, 115, 192, 232, 135, 133, 182, 16, 6, 255, 185, 143 + ] + .into(), + ), + ( + [ + 93, 203, 169, 70, 124, 231, 163, 178, 40, 136, 241, 181, 37, 231, 93, 208, 80, 45, + 166, 216, 63, 169, 147, 117, 109, 223, 26, 76, 4, 125, 65, 115 + ] + .to_vec(), + [ + 50, 143, 129, 178, 231, 153, 77, 215, 240, 11, 70, 238, 116, 184, 15, 53, 241, 64, + 218, 248, 113, 64, 112, 68, 44, 253, 43, 176, 149, 190, 198, 31, 159, 23, 55, 150, + 205, 228, 255, 194, 176, 93, 150, 100, 254, 82, 5, 105, 47, 204, 171, 137, 210, + 184, 133, 234, 41, 242, 21, 221, 226, 3, 96, 5 + ] + .into(), + [ + 223, 237, 26, 32, 148, 201, 169, 138, 2, 140, 193, 114, 251, 9, 165, 58, 162, 221, + 21, 86, 227, 81, 70, 110, 73, 26, 60, 174, 201, 202, 155, 8 + ] + .into(), + ), + ( + [ + 10, 18, 144, 141, 46, 99, 152, 10, 220, 179, 10, 31, 65, 109, 230, 15, 245, 46, + 235, 2, 149, 188, 163, 203, 110, 98, 125, 129, 101, 155, 205, 189 + ] + .to_vec(), + [ + 197, 92, 160, 39, 41, 130, 79, 201, 86, 5, 94, 19, 212, 221, 176, 36, 85, 10, 10, + 233, 57, 235, 195, 79, 236, 245, 170, 80, 10, 165, 163, 162, 9, 166, 219, 231, 132, + 233, 78, 252, 87, 160, 104, 37, 36, 66, 252, 8, 39, 130, 227, 91, 215, 255, 167, + 111, 182, 21, 143, 220, 88, 233, 72, 10 + ] + .into(), + [ + 102, 59, 228, 88, 48, 218, 240, 208, 210, 166, 88, 74, 248, 76, 3, 159, 248, 34, + 163, 166, 251, 51, 150, 127, 247, 151, 82, 124, 167, 104, 89, 186 + ] + .into(), + ), + ( + [ + 188, 253, 211, 252, 199, 54, 126, 222, 14, 31, 80, 124, 161, 10, 199, 39, 248, 45, + 154, 68, 162, 227, 125, 107, 172, 41, 41, 151, 234, 171, 164, 136 + ] + .to_vec(), + [ + 105, 147, 47, 0, 211, 122, 10, 13, 159, 118, 59, 227, 149, 223, 237, 2, 225, 167, + 66, 17, 49, 217, 15, 139, 210, 153, 215, 249, 168, 178, 156, 61, 128, 100, 72, 84, + 108, 84, 245, 17, 123, 78, 64, 220, 253, 63, 114, 157, 176, 1, 3, 87, 36, 121, 246, + 244, 88, 153, 84, 173, 143, 170, 27, 2 + ] + .into(), + [ + 75, 29, 132, 106, 68, 195, 233, 158, 128, 93, 96, 70, 99, 43, 43, 19, 126, 162, + 176, 32, 72, 79, 194, 8, 59, 203, 16, 219, 85, 226, 17, 198 + ] + .into(), + ), + ( + [ + 226, 227, 113, 72, 108, 239, 130, 37, 21, 121, 69, 158, 159, 30, 187, 20, 25, 129, + 18, 211, 152, 237, 217, 225, 103, 173, 225, 84, 130, 14, 98, 152 + ] + .to_vec(), + [ + 237, 88, 120, 116, 191, 17, 131, 145, 14, 29, 52, 144, 131, 236, 139, 72, 220, 147, + 29, 218, 137, 162, 242, 146, 103, 115, 140, 233, 68, 111, 36, 156, 179, 18, 92, + 188, 154, 211, 159, 152, 76, 56, 48, 11, 53, 67, 249, 47, 236, 72, 54, 90, 145, 13, + 112, 129, 201, 234, 39, 40, 94, 110, 181, 2 + ] + .into(), + [ + 71, 123, 7, 83, 166, 112, 96, 41, 72, 21, 124, 204, 220, 242, 114, 18, 136, 114, + 188, 3, 109, 89, 207, 142, 211, 220, 75, 170, 138, 159, 91, 65 + ] + .into(), + ), + ( + [ + 225, 242, 213, 131, 75, 206, 121, 1, 11, 169, 169, 241, 41, 113, 255, 138, 109, + 216, 145, 166, 98, 81, 85, 105, 178, 76, 8, 213, 3, 25, 15, 164 + ] + .to_vec(), + [ + 218, 39, 132, 40, 46, 40, 16, 196, 214, 110, 211, 229, 200, 208, 146, 67, 169, 158, + 66, 110, 184, 166, 117, 120, 16, 27, 105, 176, 36, 74, 187, 103, 245, 186, 59, 233, + 171, 172, 120, 25, 182, 219, 84, 55, 219, 242, 39, 138, 89, 74, 1, 193, 184, 194, + 188, 190, 113, 136, 30, 158, 182, 91, 215, 0 + ] + .into(), + [ + 111, 220, 167, 18, 14, 97, 147, 165, 160, 155, 21, 255, 127, 176, 119, 237, 210, + 112, 99, 193, 66, 166, 167, 180, 108, 198, 153, 227, 94, 85, 232, 13 + ] + .into(), + ), + ( + [ + 225, 47, 87, 102, 125, 86, 130, 79, 147, 184, 26, 138, 230, 229, 254, 180, 191, + 252, 9, 107, 29, 126, 101, 54, 98, 134, 228, 105, 109, 143, 118, 42 + ] + .to_vec(), + [ + 197, 43, 231, 143, 82, 220, 24, 243, 80, 117, 1, 39, 150, 133, 77, 40, 26, 178, 95, + 158, 79, 60, 194, 143, 98, 128, 244, 255, 65, 191, 239, 179, 92, 159, 105, 207, + 138, 161, 38, 24, 201, 232, 230, 204, 200, 157, 93, 195, 178, 71, 88, 242, 173, 77, + 0, 56, 16, 165, 227, 188, 40, 49, 155, 7 + ] + .into(), + [ + 251, 172, 204, 143, 30, 134, 108, 243, 119, 104, 161, 93, 43, 50, 86, 5, 110, 151, + 249, 48, 103, 221, 89, 79, 235, 116, 245, 103, 94, 170, 172, 227 + ] + .into(), + ), + ( + [ + 208, 177, 124, 14, 226, 70, 2, 251, 231, 125, 90, 16, 123, 73, 47, 104, 159, 135, + 54, 131, 77, 244, 234, 106, 178, 9, 156, 169, 107, 233, 234, 187 + ] + .to_vec(), + [ + 1, 233, 198, 32, 153, 142, 54, 180, 79, 77, 194, 197, 49, 31, 59, 191, 245, 228, + 202, 195, 97, 67, 182, 115, 151, 80, 207, 183, 198, 87, 179, 129, 8, 92, 28, 164, + 209, 203, 46, 58, 160, 26, 157, 8, 149, 163, 214, 113, 127, 37, 212, 125, 179, 177, + 95, 244, 189, 21, 246, 72, 183, 184, 185, 9 + ] + .into(), + [ + 246, 252, 81, 48, 0, 123, 41, 78, 72, 116, 61, 73, 248, 229, 4, 234, 152, 149, 48, + 198, 137, 14, 231, 240, 242, 205, 159, 99, 9, 183, 104, 31 + ] + .into(), + ), + ( + [ + 71, 61, 57, 222, 190, 1, 120, 139, 238, 48, 237, 151, 52, 201, 242, 21, 200, 29, + 54, 2, 193, 153, 8, 73, 78, 186, 156, 183, 224, 126, 164, 52 + ] + .to_vec(), + [ + 209, 4, 57, 213, 70, 129, 222, 208, 129, 130, 35, 133, 204, 89, 47, 250, 107, 176, + 210, 179, 51, 173, 240, 112, 146, 112, 138, 1, 244, 87, 116, 144, 250, 178, 27, + 125, 61, 125, 186, 206, 249, 173, 60, 142, 188, 89, 96, 102, 246, 43, 184, 96, 157, + 190, 240, 123, 169, 191, 249, 8, 183, 64, 170, 12 + ] + .into(), + [ + 243, 221, 190, 199, 165, 66, 152, 23, 26, 31, 188, 218, 196, 35, 177, 38, 169, 55, + 212, 132, 148, 172, 124, 129, 232, 238, 162, 154, 224, 80, 154, 109 + ] + .into(), + ), + ( + [ + 88, 104, 132, 39, 159, 181, 16, 226, 167, 230, 84, 135, 90, 15, 145, 23, 88, 99, + 83, 19, 224, 144, 136, 116, 8, 33, 201, 84, 20, 31, 189, 203 + ] + .to_vec(), + [ + 198, 23, 212, 133, 252, 186, 229, 167, 235, 162, 178, 212, 131, 110, 44, 140, 219, + 147, 61, 202, 117, 207, 65, 200, 234, 244, 31, 94, 126, 125, 73, 129, 119, 15, 241, + 221, 212, 70, 245, 188, 178, 112, 3, 245, 89, 109, 193, 44, 0, 195, 97, 51, 167, + 147, 58, 75, 170, 172, 57, 217, 159, 38, 218, 7 + ] + .into(), + [ + 110, 105, 34, 78, 21, 10, 22, 184, 31, 158, 81, 25, 80, 20, 80, 134, 249, 158, 153, + 79, 235, 183, 66, 227, 204, 37, 48, 98, 90, 16, 176, 177 + ] + .into(), + ), + ( + [ + 234, 4, 248, 40, 225, 229, 244, 118, 26, 187, 63, 246, 11, 176, 53, 108, 51, 54, 2, + 132, 191, 16, 8, 85, 111, 150, 228, 94, 80, 192, 144, 159 + ] + .to_vec(), + [ + 218, 62, 49, 124, 86, 4, 230, 89, 33, 189, 120, 96, 24, 96, 252, 254, 143, 23, 237, + 110, 139, 76, 156, 136, 19, 227, 27, 95, 35, 27, 139, 41, 7, 137, 94, 250, 244, + 182, 21, 18, 179, 83, 149, 0, 217, 26, 254, 153, 10, 124, 163, 238, 174, 97, 247, + 27, 25, 122, 23, 217, 151, 174, 175, 4 + ] + .into(), + [ + 24, 112, 210, 128, 205, 41, 123, 217, 111, 198, 27, 183, 47, 137, 121, 176, 224, + 199, 232, 150, 60, 2, 61, 77, 173, 222, 150, 210, 111, 117, 114, 41 + ] + .into(), + ), + ( + [ + 188, 124, 52, 209, 210, 48, 97, 114, 229, 41, 172, 41, 106, 43, 131, 106, 26, 107, + 32, 24, 108, 47, 188, 99, 255, 193, 23, 43, 140, 103, 188, 88 + ] + .to_vec(), + [ + 37, 194, 31, 193, 246, 146, 76, 202, 97, 55, 83, 155, 34, 170, 227, 154, 233, 129, + 3, 185, 171, 72, 145, 76, 41, 81, 48, 56, 191, 105, 136, 58, 131, 113, 119, 246, + 112, 224, 61, 98, 22, 194, 40, 197, 166, 146, 250, 12, 181, 35, 169, 216, 80, 209, + 245, 244, 85, 242, 74, 236, 97, 194, 68, 12 + ] + .into(), + [ + 169, 123, 177, 52, 156, 222, 254, 190, 143, 121, 35, 177, 170, 147, 58, 246, 210, + 32, 213, 223, 102, 205, 179, 158, 7, 55, 113, 112, 36, 65, 202, 110 + ] + .into(), + ), + ( + [ + 17, 95, 185, 175, 219, 86, 64, 58, 174, 21, 201, 150, 129, 250, 36, 20, 195, 110, + 0, 113, 182, 24, 206, 73, 68, 16, 116, 151, 217, 0, 183, 164 + ] + .to_vec(), + [ + 9, 110, 191, 175, 105, 143, 76, 125, 98, 79, 34, 200, 167, 236, 63, 108, 131, 130, + 245, 74, 214, 68, 138, 239, 163, 250, 49, 52, 34, 188, 67, 2, 119, 229, 90, 91, + 153, 236, 151, 216, 105, 48, 164, 148, 165, 209, 106, 20, 20, 22, 237, 200, 103, + 194, 62, 55, 190, 28, 26, 210, 183, 197, 167, 1 + ] + .into(), + [ + 206, 64, 12, 105, 63, 20, 210, 176, 8, 212, 248, 201, 164, 150, 222, 49, 166, 39, + 119, 217, 70, 248, 8, 110, 185, 11, 69, 69, 36, 71, 244, 51 + ] + .into(), + ), + ( + [ + 237, 133, 151, 46, 146, 77, 61, 123, 70, 133, 58, 223, 107, 97, 229, 81, 87, 62, + 207, 195, 97, 170, 15, 82, 211, 5, 20, 89, 163, 217, 128, 35 + ] + .to_vec(), + [ + 231, 246, 248, 149, 194, 92, 119, 254, 228, 30, 210, 128, 15, 253, 106, 166, 147, + 225, 234, 233, 237, 43, 36, 251, 10, 165, 108, 167, 232, 223, 25, 48, 47, 106, 238, + 79, 44, 147, 10, 185, 132, 95, 41, 145, 246, 136, 218, 229, 196, 167, 150, 183, 19, + 31, 12, 166, 186, 121, 95, 255, 144, 25, 145, 6 + ] + .into(), + [ + 75, 0, 191, 179, 0, 38, 200, 103, 240, 232, 146, 127, 48, 255, 143, 78, 126, 37, + 225, 254, 192, 232, 53, 173, 108, 253, 215, 87, 105, 175, 132, 65 + ] + .into(), + ), + ( + [ + 109, 96, 113, 89, 67, 135, 43, 157, 40, 37, 177, 153, 123, 63, 40, 163, 123, 81, + 136, 113, 150, 209, 88, 176, 208, 58, 208, 160, 86, 26, 225, 66 + ] + .to_vec(), + [ + 75, 105, 202, 118, 233, 163, 238, 49, 119, 252, 136, 92, 19, 145, 50, 165, 182, + 178, 187, 249, 59, 201, 146, 191, 137, 65, 18, 72, 116, 85, 97, 186, 74, 17, 104, + 200, 141, 102, 38, 80, 13, 68, 231, 138, 76, 14, 32, 167, 62, 37, 156, 126, 23, 81, + 76, 227, 70, 246, 11, 187, 144, 151, 242, 8 + ] + .into(), + [ + 179, 224, 82, 251, 155, 17, 16, 45, 185, 115, 203, 211, 196, 116, 225, 39, 224, 18, + 222, 193, 92, 208, 170, 176, 232, 53, 34, 151, 235, 167, 237, 33 + ] + .into(), + ), + ( + [ + 216, 78, 65, 19, 9, 23, 203, 211, 17, 208, 180, 215, 155, 65, 140, 75, 188, 241, + 162, 82, 190, 215, 90, 217, 220, 160, 31, 168, 221, 172, 182, 129 + ] + .to_vec(), + [ + 80, 223, 42, 239, 232, 12, 152, 95, 94, 83, 91, 78, 234, 167, 180, 158, 3, 179, + 180, 66, 38, 57, 135, 13, 48, 35, 33, 46, 126, 202, 192, 84, 76, 31, 156, 164, 157, + 134, 187, 206, 16, 241, 165, 166, 99, 70, 150, 62, 233, 42, 181, 123, 68, 253, 219, + 241, 90, 65, 24, 22, 19, 248, 102, 10 + ] + .into(), + [ + 242, 136, 176, 89, 81, 79, 168, 124, 193, 245, 92, 245, 54, 59, 132, 221, 149, 60, + 59, 239, 141, 207, 146, 80, 131, 218, 22, 9, 26, 44, 78, 217 + ] + .into(), + ), + ( + [ + 113, 173, 51, 242, 20, 55, 96, 26, 102, 45, 249, 157, 86, 94, 171, 22, 215, 33, + 156, 121, 103, 97, 95, 98, 87, 226, 153, 54, 60, 83, 140, 122 + ] + .to_vec(), + [ + 245, 115, 96, 107, 149, 128, 251, 197, 15, 162, 231, 250, 22, 228, 83, 221, 99, + 198, 55, 58, 252, 150, 167, 83, 47, 227, 226, 48, 32, 245, 112, 226, 156, 79, 128, + 147, 171, 72, 104, 101, 111, 241, 233, 80, 37, 163, 13, 64, 72, 124, 68, 78, 67, + 204, 190, 119, 81, 119, 228, 238, 191, 126, 50, 5 + ] + .into(), + [ + 243, 223, 148, 190, 197, 217, 142, 40, 5, 20, 215, 119, 252, 195, 180, 3, 47, 186, + 57, 72, 136, 28, 143, 75, 213, 11, 238, 183, 105, 249, 0, 238 + ] + .into(), + ), + ( + [ + 49, 72, 74, 103, 248, 213, 54, 113, 74, 246, 9, 23, 71, 53, 169, 246, 99, 224, 151, + 195, 252, 116, 201, 4, 137, 105, 42, 92, 70, 108, 150, 227 + ] + .to_vec(), + [ + 176, 80, 159, 43, 192, 138, 206, 126, 251, 224, 143, 144, 99, 140, 182, 171, 242, + 189, 90, 83, 4, 85, 210, 211, 44, 62, 235, 229, 119, 240, 73, 56, 167, 44, 29, 148, + 110, 104, 58, 89, 132, 146, 38, 163, 44, 21, 148, 15, 248, 91, 85, 100, 23, 71, + 140, 45, 34, 193, 6, 248, 100, 1, 140, 5 + ] + .into(), + [ + 66, 17, 61, 186, 34, 197, 117, 75, 215, 64, 48, 114, 168, 139, 201, 95, 250, 32, + 242, 102, 65, 198, 97, 157, 250, 228, 211, 122, 140, 107, 22, 73 + ] + .into(), + ), + ]; + static ref LIBRUSTZCASH_BINDING_SIGS: [(Vec, Signature, VerificationKeyBytes); 32] = [ + ( + [ + 16, 28, 190, 75, 156, 66, 96, 79, 4, 199, 3, 195, 150, 247, 136, 198, 203, 45, 109, + 125, 88, 244, 84, 48, 177, 46, 178, 237, 214, 64, 7, 108 + ] + .to_vec(), + [ + 237, 87, 106, 226, 60, 11, 207, 242, 46, 84, 76, 231, 183, 226, 137, 168, 172, 81, + 133, 166, 208, 118, 55, 133, 102, 155, 236, 16, 30, 208, 68, 84, 22, 83, 106, 161, + 209, 8, 179, 99, 82, 239, 252, 99, 238, 87, 130, 246, 133, 95, 231, 43, 122, 10, + 251, 88, 227, 199, 85, 210, 70, 164, 190, 13 + ] + .into(), + [ + 231, 192, 142, 217, 209, 34, 78, 213, 129, 213, 83, 32, 218, 125, 131, 62, 236, + 189, 238, 23, 236, 235, 101, 182, 241, 2, 117, 248, 83, 18, 94, 133 + ] + .into(), + ), + ( + [ + 108, 56, 40, 255, 104, 11, 219, 166, 98, 154, 153, 67, 75, 194, 79, 17, 86, 115, + 26, 175, 150, 173, 228, 209, 66, 119, 33, 94, 87, 187, 19, 49 + ] + .to_vec(), + [ + 25, 185, 147, 122, 61, 235, 191, 32, 59, 56, 250, 154, 49, 19, 148, 205, 195, 201, + 60, 131, 133, 238, 221, 50, 237, 81, 7, 211, 116, 217, 205, 115, 236, 103, 208, 32, + 75, 8, 47, 250, 144, 184, 131, 108, 140, 53, 151, 106, 128, 163, 19, 69, 15, 42, + 82, 114, 234, 34, 191, 159, 57, 241, 36, 9 + ] + .into(), + [ + 21, 9, 29, 239, 240, 242, 41, 16, 105, 198, 200, 212, 207, 17, 76, 88, 75, 79, 223, + 157, 217, 114, 102, 78, 169, 140, 244, 4, 152, 73, 216, 47 + ] + .into(), + ), + ( + [ + 145, 63, 229, 40, 221, 46, 129, 128, 68, 42, 148, 149, 87, 96, 109, 94, 188, 197, + 117, 95, 73, 255, 11, 214, 198, 41, 249, 226, 22, 130, 163, 215 + ] + .to_vec(), + [ + 151, 162, 38, 68, 132, 8, 213, 252, 81, 56, 12, 242, 57, 61, 182, 221, 111, 66, + 218, 143, 184, 210, 235, 125, 18, 226, 157, 128, 213, 96, 89, 84, 208, 167, 64, 60, + 57, 240, 43, 230, 124, 148, 16, 233, 41, 194, 218, 171, 10, 250, 25, 93, 243, 71, + 213, 87, 74, 250, 214, 223, 30, 52, 193, 3 + ] + .into(), + [ + 208, 32, 161, 164, 158, 193, 116, 72, 31, 83, 149, 118, 131, 138, 140, 54, 21, 92, + 188, 31, 124, 204, 144, 72, 186, 253, 87, 4, 193, 138, 130, 157 + ] + .into(), + ), + ( + [ + 208, 243, 205, 249, 49, 107, 35, 33, 206, 165, 99, 248, 160, 186, 39, 50, 71, 156, + 226, 164, 9, 17, 162, 158, 198, 154, 10, 123, 189, 221, 247, 116 + ] + .to_vec(), + [ + 129, 23, 184, 104, 87, 77, 84, 102, 62, 149, 61, 114, 220, 195, 21, 194, 115, 44, + 26, 30, 72, 30, 86, 61, 179, 74, 70, 16, 192, 0, 220, 195, 129, 209, 112, 61, 226, + 172, 132, 127, 52, 103, 213, 217, 213, 42, 202, 6, 179, 184, 7, 17, 29, 5, 28, 24, + 189, 18, 135, 5, 58, 118, 93, 4 + ] + .into(), + [ + 15, 202, 69, 167, 230, 248, 5, 36, 71, 19, 94, 186, 96, 22, 6, 232, 59, 99, 131, + 148, 100, 216, 51, 195, 129, 89, 238, 56, 81, 50, 245, 220 + ] + .into(), + ), + ( + [ + 73, 53, 27, 188, 30, 74, 75, 136, 216, 22, 115, 179, 41, 12, 101, 135, 241, 208, + 177, 226, 3, 109, 120, 48, 2, 120, 150, 27, 29, 59, 61, 180 + ] + .to_vec(), + [ + 158, 145, 214, 27, 179, 127, 199, 48, 80, 13, 226, 197, 111, 19, 72, 183, 73, 51, + 34, 147, 241, 236, 21, 160, 99, 218, 202, 73, 231, 123, 62, 243, 148, 119, 42, 185, + 145, 225, 239, 177, 132, 157, 101, 0, 210, 63, 72, 22, 62, 130, 52, 222, 36, 13, + 124, 0, 208, 141, 234, 3, 38, 207, 72, 13 + ] + .into(), + [ + 13, 167, 148, 29, 141, 128, 10, 130, 105, 36, 188, 29, 169, 176, 172, 169, 162, 21, + 90, 3, 180, 221, 68, 90, 252, 154, 62, 61, 20, 47, 119, 145 + ] + .into(), + ), + ( + [ + 158, 55, 154, 163, 138, 43, 56, 169, 209, 184, 225, 86, 39, 131, 0, 194, 62, 122, + 84, 176, 197, 115, 88, 216, 47, 127, 36, 131, 215, 205, 251, 69 + ] + .to_vec(), + [ + 244, 19, 20, 115, 160, 228, 19, 65, 61, 5, 118, 252, 98, 166, 95, 8, 152, 88, 170, + 220, 129, 233, 169, 252, 99, 157, 119, 63, 243, 158, 82, 170, 248, 192, 197, 111, + 151, 237, 195, 54, 128, 105, 62, 34, 177, 118, 224, 123, 185, 188, 212, 14, 56, 56, + 177, 208, 159, 190, 229, 97, 43, 231, 65, 7 + ] + .into(), + [ + 89, 97, 162, 229, 121, 16, 170, 148, 38, 103, 34, 212, 200, 198, 164, 51, 143, 191, + 7, 42, 178, 208, 97, 236, 230, 113, 1, 197, 82, 39, 203, 83 + ] + .into(), + ), + ( + [ + 217, 101, 76, 11, 40, 202, 129, 190, 187, 84, 92, 68, 181, 190, 66, 89, 233, 122, + 72, 49, 98, 38, 40, 129, 112, 67, 119, 39, 61, 230, 140, 107 + ] + .to_vec(), + [ + 218, 247, 117, 213, 208, 142, 220, 44, 216, 96, 137, 132, 148, 31, 152, 156, 113, + 69, 28, 193, 149, 80, 30, 133, 55, 153, 151, 101, 55, 180, 57, 222, 123, 37, 88, 7, + 15, 26, 6, 186, 101, 236, 56, 130, 31, 172, 73, 71, 16, 248, 103, 15, 141, 24, 57, + 119, 157, 228, 42, 253, 120, 43, 98, 1 + ] + .into(), + [ + 122, 178, 202, 205, 106, 145, 155, 121, 178, 142, 171, 204, 133, 94, 219, 185, 58, + 222, 73, 44, 245, 198, 158, 142, 246, 214, 29, 113, 110, 55, 234, 95 + ] + .into(), + ), + ( + [ + 10, 212, 76, 139, 47, 234, 73, 233, 131, 223, 100, 101, 161, 23, 26, 161, 12, 107, + 27, 76, 50, 232, 165, 234, 34, 170, 126, 169, 179, 7, 168, 216 + ] + .to_vec(), + [ + 48, 232, 149, 96, 68, 229, 204, 221, 41, 15, 163, 211, 84, 69, 221, 144, 189, 141, + 144, 1, 47, 243, 97, 195, 247, 246, 120, 43, 220, 114, 88, 95, 94, 191, 240, 214, + 205, 145, 222, 102, 134, 72, 175, 115, 87, 216, 118, 1, 122, 112, 186, 41, 167, + 179, 167, 120, 59, 222, 49, 226, 201, 166, 210, 5 + ] + .into(), + [ + 176, 34, 144, 56, 153, 248, 153, 225, 109, 38, 93, 184, 90, 94, 254, 213, 242, 15, + 245, 212, 5, 200, 50, 48, 195, 249, 197, 75, 249, 58, 59, 65 + ] + .into(), + ), + ( + [ + 7, 121, 157, 250, 188, 252, 108, 217, 56, 87, 183, 45, 98, 129, 233, 210, 121, 143, + 173, 195, 172, 2, 154, 125, 176, 148, 3, 234, 3, 250, 127, 116 + ] + .to_vec(), + [ + 236, 49, 41, 146, 67, 207, 165, 207, 121, 176, 150, 26, 46, 207, 118, 112, 226, 31, + 19, 98, 57, 97, 183, 215, 227, 144, 101, 255, 163, 15, 186, 104, 231, 212, 237, + 159, 240, 78, 188, 177, 74, 109, 117, 211, 2, 2, 88, 98, 208, 83, 235, 61, 194, + 114, 178, 8, 215, 207, 175, 206, 156, 183, 212, 4 + ] + .into(), + [ + 114, 45, 120, 112, 212, 36, 97, 130, 84, 73, 186, 171, 73, 208, 112, 166, 193, 161, + 139, 138, 33, 191, 122, 112, 173, 253, 115, 135, 25, 216, 165, 128 + ] + .into(), + ), + ( + [ + 161, 98, 15, 93, 184, 31, 193, 208, 84, 234, 38, 229, 208, 207, 27, 172, 152, 170, + 39, 138, 187, 88, 195, 82, 253, 108, 101, 128, 110, 86, 114, 242 + ] + .to_vec(), + [ + 250, 82, 164, 64, 95, 96, 202, 190, 186, 124, 162, 48, 64, 185, 207, 25, 159, 197, + 148, 50, 180, 55, 10, 11, 1, 34, 219, 194, 215, 160, 187, 204, 161, 176, 232, 43, + 129, 43, 17, 183, 80, 223, 55, 39, 58, 160, 120, 162, 247, 92, 64, 233, 145, 81, + 136, 129, 158, 168, 47, 193, 17, 243, 141, 0 + ] + .into(), + [ + 51, 144, 219, 246, 218, 85, 243, 42, 222, 208, 32, 16, 237, 244, 131, 139, 69, 139, + 164, 231, 14, 239, 64, 94, 192, 127, 86, 97, 228, 50, 123, 65 + ] + .into(), + ), + ( + [ + 123, 7, 11, 119, 243, 237, 229, 160, 242, 163, 173, 216, 117, 94, 8, 29, 112, 178, + 23, 50, 180, 75, 18, 189, 215, 221, 21, 8, 86, 209, 177, 87 + ] + .to_vec(), + [ + 25, 68, 135, 235, 188, 239, 156, 9, 63, 43, 65, 178, 241, 114, 230, 156, 94, 118, + 114, 97, 121, 71, 206, 185, 144, 55, 241, 233, 254, 111, 209, 39, 206, 8, 217, 109, + 180, 71, 175, 235, 226, 39, 101, 241, 231, 147, 14, 121, 250, 107, 212, 228, 201, + 63, 134, 232, 110, 101, 43, 40, 209, 83, 254, 10 + ] + .into(), + [ + 196, 239, 106, 25, 44, 155, 136, 240, 60, 108, 136, 236, 150, 16, 37, 71, 89, 205, + 31, 166, 13, 136, 31, 105, 126, 55, 63, 223, 79, 123, 41, 144 + ] + .into(), + ), + ( + [ + 181, 94, 198, 199, 78, 194, 64, 118, 146, 51, 79, 70, 73, 206, 87, 51, 96, 13, 145, + 198, 78, 245, 179, 182, 169, 147, 95, 137, 77, 33, 247, 4 + ] + .to_vec(), + [ + 201, 195, 83, 81, 83, 219, 229, 100, 171, 188, 138, 28, 52, 163, 215, 12, 33, 12, + 163, 52, 146, 64, 74, 212, 148, 195, 255, 242, 96, 87, 106, 212, 174, 204, 31, 138, + 144, 32, 191, 21, 193, 63, 4, 204, 126, 248, 171, 61, 19, 156, 114, 244, 46, 18, + 160, 156, 211, 83, 216, 156, 222, 131, 105, 6 + ] + .into(), + [ + 170, 211, 15, 217, 62, 99, 68, 140, 87, 122, 65, 150, 24, 91, 62, 183, 205, 43, + 222, 241, 69, 89, 213, 235, 51, 214, 156, 238, 67, 119, 239, 62 + ] + .into(), + ), + ( + [ + 108, 144, 146, 213, 254, 69, 48, 80, 172, 3, 26, 161, 90, 132, 50, 0, 192, 101, 98, + 65, 167, 199, 175, 147, 29, 177, 217, 226, 72, 233, 93, 118 + ] + .to_vec(), + [ + 85, 140, 40, 250, 36, 52, 31, 161, 79, 218, 140, 106, 155, 15, 129, 202, 227, 206, + 72, 110, 176, 134, 218, 71, 19, 139, 164, 230, 148, 55, 34, 6, 245, 17, 95, 19, + 253, 21, 21, 156, 119, 213, 25, 201, 31, 130, 28, 215, 2, 0, 120, 56, 248, 134, + 224, 82, 138, 50, 150, 134, 144, 86, 176, 2 + ] + .into(), + [ + 5, 132, 40, 151, 157, 13, 233, 182, 145, 5, 148, 185, 157, 60, 26, 187, 1, 142, 87, + 236, 45, 171, 63, 192, 150, 27, 3, 65, 129, 120, 47, 137 + ] + .into(), + ), + ( + [ + 106, 232, 15, 114, 137, 49, 56, 62, 28, 42, 176, 201, 156, 86, 109, 234, 3, 191, + 44, 231, 22, 110, 103, 24, 204, 71, 207, 80, 255, 34, 150, 208 + ] + .to_vec(), + [ + 76, 143, 197, 238, 95, 10, 247, 248, 222, 235, 98, 201, 131, 124, 227, 105, 9, 253, + 233, 16, 51, 122, 62, 96, 253, 17, 146, 247, 90, 90, 226, 45, 0, 148, 219, 178, + 245, 188, 14, 131, 241, 153, 54, 65, 124, 176, 112, 161, 117, 130, 154, 212, 169, + 222, 227, 123, 23, 159, 43, 212, 240, 195, 105, 2 + ] + .into(), + [ + 129, 237, 183, 32, 252, 37, 62, 9, 191, 183, 110, 221, 208, 147, 195, 155, 170, 90, + 165, 62, 148, 239, 184, 96, 5, 193, 251, 175, 104, 22, 86, 20 + ] + .into(), + ), + ( + [ + 115, 113, 54, 112, 20, 175, 145, 55, 156, 102, 8, 55, 94, 188, 73, 151, 45, 250, + 55, 84, 176, 22, 100, 238, 102, 177, 44, 154, 49, 69, 93, 64 + ] + .to_vec(), + [ + 173, 45, 205, 174, 114, 159, 62, 74, 64, 1, 168, 92, 241, 121, 55, 124, 160, 55, + 250, 71, 230, 228, 135, 71, 28, 232, 153, 135, 249, 105, 179, 85, 169, 85, 150, + 126, 168, 172, 253, 116, 217, 80, 179, 39, 245, 185, 138, 144, 204, 111, 7, 194, + 185, 241, 184, 6, 166, 103, 210, 97, 27, 24, 83, 2 + ] + .into(), + [ + 102, 255, 40, 224, 138, 207, 8, 238, 77, 169, 100, 66, 36, 118, 240, 162, 156, 55, + 103, 103, 65, 234, 190, 160, 149, 137, 75, 180, 194, 77, 163, 102 + ] + .into(), + ), + ( + [ + 93, 203, 169, 70, 124, 231, 163, 178, 40, 136, 241, 181, 37, 231, 93, 208, 80, 45, + 166, 216, 63, 169, 147, 117, 109, 223, 26, 76, 4, 125, 65, 115 + ] + .to_vec(), + [ + 229, 204, 69, 161, 126, 241, 215, 126, 254, 103, 223, 201, 45, 103, 237, 217, 74, + 150, 218, 24, 154, 200, 249, 94, 241, 88, 231, 134, 242, 211, 12, 78, 99, 31, 12, + 60, 63, 235, 62, 5, 147, 224, 243, 161, 46, 209, 65, 113, 105, 220, 49, 153, 213, + 16, 244, 133, 36, 110, 201, 30, 35, 129, 4, 7 + ] + .into(), + [ + 206, 114, 177, 72, 56, 89, 233, 157, 190, 23, 239, 84, 26, 103, 247, 220, 31, 117, + 100, 129, 54, 116, 9, 254, 141, 21, 69, 145, 40, 247, 240, 109 + ] + .into(), + ), + ( + [ + 10, 18, 144, 141, 46, 99, 152, 10, 220, 179, 10, 31, 65, 109, 230, 15, 245, 46, + 235, 2, 149, 188, 163, 203, 110, 98, 125, 129, 101, 155, 205, 189 + ] + .to_vec(), + [ + 176, 119, 123, 137, 30, 22, 101, 77, 181, 10, 152, 72, 84, 223, 25, 119, 147, 186, + 213, 209, 251, 176, 185, 110, 74, 111, 62, 51, 91, 85, 209, 158, 201, 200, 44, 28, + 235, 76, 102, 83, 228, 57, 246, 69, 127, 114, 203, 202, 6, 106, 44, 94, 153, 66, + 230, 238, 30, 44, 159, 31, 19, 151, 110, 12 + ] + .into(), + [ + 64, 131, 183, 133, 193, 39, 56, 61, 212, 66, 113, 5, 204, 197, 56, 160, 202, 240, + 50, 93, 214, 52, 90, 201, 106, 70, 190, 136, 73, 153, 110, 8 + ] + .into(), + ), + ( + [ + 188, 253, 211, 252, 199, 54, 126, 222, 14, 31, 80, 124, 161, 10, 199, 39, 248, 45, + 154, 68, 162, 227, 125, 107, 172, 41, 41, 151, 234, 171, 164, 136 + ] + .to_vec(), + [ + 17, 154, 126, 54, 92, 150, 140, 160, 171, 95, 141, 143, 250, 132, 59, 14, 50, 54, + 116, 195, 157, 200, 83, 83, 53, 167, 141, 93, 125, 64, 197, 95, 95, 14, 230, 27, 2, + 137, 105, 185, 52, 223, 164, 48, 90, 134, 63, 185, 32, 204, 11, 147, 124, 216, 235, + 239, 228, 145, 48, 40, 141, 163, 98, 0 + ] + .into(), + [ + 68, 208, 194, 182, 6, 214, 24, 148, 56, 42, 161, 152, 138, 217, 59, 172, 193, 119, + 237, 56, 238, 225, 133, 77, 224, 14, 75, 145, 53, 5, 142, 101 + ] + .into(), + ), + ( + [ + 226, 227, 113, 72, 108, 239, 130, 37, 21, 121, 69, 158, 159, 30, 187, 20, 25, 129, + 18, 211, 152, 237, 217, 225, 103, 173, 225, 84, 130, 14, 98, 152 + ] + .to_vec(), + [ + 246, 81, 2, 59, 76, 187, 86, 118, 239, 175, 45, 169, 81, 49, 216, 203, 99, 159, + 145, 134, 248, 26, 79, 247, 17, 49, 133, 39, 73, 106, 60, 173, 37, 129, 155, 148, + 224, 137, 174, 230, 242, 206, 205, 1, 254, 247, 32, 212, 34, 154, 21, 145, 54, 173, + 234, 52, 104, 40, 106, 6, 242, 69, 96, 10 + ] + .into(), + [ + 49, 83, 150, 31, 12, 102, 183, 80, 9, 184, 33, 207, 190, 17, 184, 63, 229, 152, + 183, 90, 179, 119, 135, 199, 193, 61, 251, 141, 132, 254, 53, 19 + ] + .into(), + ), + ( + [ + 225, 242, 213, 131, 75, 206, 121, 1, 11, 169, 169, 241, 41, 113, 255, 138, 109, + 216, 145, 166, 98, 81, 85, 105, 178, 76, 8, 213, 3, 25, 15, 164 + ] + .to_vec(), + [ + 144, 121, 70, 141, 125, 29, 95, 18, 88, 111, 23, 199, 155, 19, 207, 156, 118, 120, + 148, 46, 38, 254, 235, 48, 228, 123, 158, 166, 63, 244, 228, 4, 202, 228, 160, 81, + 68, 19, 187, 107, 109, 149, 80, 238, 223, 104, 91, 210, 115, 22, 217, 74, 43, 239, + 186, 194, 133, 90, 224, 160, 220, 38, 140, 13 + ] + .into(), + [ + 138, 225, 61, 235, 12, 42, 193, 198, 100, 212, 166, 123, 121, 117, 207, 170, 164, + 30, 120, 74, 16, 151, 239, 199, 48, 11, 36, 162, 187, 205, 70, 147 + ] + .into(), + ), + ( + [ + 225, 47, 87, 102, 125, 86, 130, 79, 147, 184, 26, 138, 230, 229, 254, 180, 191, + 252, 9, 107, 29, 126, 101, 54, 98, 134, 228, 105, 109, 143, 118, 42 + ] + .to_vec(), + [ + 68, 166, 113, 4, 147, 21, 5, 61, 112, 131, 210, 120, 128, 9, 14, 44, 10, 108, 23, + 196, 44, 185, 172, 243, 180, 73, 220, 122, 240, 52, 94, 86, 65, 85, 132, 140, 74, + 17, 172, 85, 19, 230, 180, 79, 34, 166, 24, 180, 131, 97, 102, 44, 208, 99, 236, + 107, 124, 2, 199, 192, 241, 229, 106, 2 + ] + .into(), + [ + 125, 43, 204, 57, 195, 59, 117, 225, 126, 221, 27, 33, 79, 38, 139, 176, 186, 47, + 7, 150, 242, 182, 218, 190, 23, 94, 132, 3, 140, 245, 239, 18 + ] + .into(), + ), + ( + [ + 208, 177, 124, 14, 226, 70, 2, 251, 231, 125, 90, 16, 123, 73, 47, 104, 159, 135, + 54, 131, 77, 244, 234, 106, 178, 9, 156, 169, 107, 233, 234, 187 + ] + .to_vec(), + [ + 152, 136, 190, 89, 167, 242, 143, 37, 179, 64, 119, 195, 44, 125, 172, 222, 184, + 49, 95, 18, 64, 121, 3, 211, 213, 191, 252, 187, 60, 81, 12, 137, 228, 95, 216, 32, + 78, 235, 209, 241, 2, 228, 161, 132, 75, 203, 49, 154, 81, 66, 14, 78, 205, 123, + 249, 65, 106, 43, 72, 208, 183, 158, 178, 9 + ] + .into(), + [ + 114, 113, 231, 198, 235, 57, 190, 236, 51, 245, 171, 233, 225, 34, 227, 33, 144, + 210, 9, 70, 101, 117, 163, 244, 82, 164, 70, 162, 164, 202, 249, 173 + ] + .into(), + ), + ( + [ + 71, 61, 57, 222, 190, 1, 120, 139, 238, 48, 237, 151, 52, 201, 242, 21, 200, 29, + 54, 2, 193, 153, 8, 73, 78, 186, 156, 183, 224, 126, 164, 52 + ] + .to_vec(), + [ + 198, 185, 85, 138, 190, 54, 233, 102, 247, 52, 43, 110, 176, 53, 49, 66, 65, 165, + 166, 130, 43, 157, 250, 149, 135, 102, 253, 98, 31, 205, 62, 88, 70, 37, 179, 241, + 29, 57, 104, 204, 159, 111, 28, 249, 250, 38, 13, 101, 60, 101, 47, 48, 193, 114, + 223, 36, 51, 45, 33, 150, 219, 69, 193, 2 + ] + .into(), + [ + 250, 27, 172, 162, 248, 64, 155, 57, 38, 153, 71, 119, 202, 180, 187, 215, 160, 63, + 243, 69, 10, 226, 114, 197, 154, 168, 127, 211, 82, 53, 5, 91 + ] + .into(), + ), + ( + [ + 88, 104, 132, 39, 159, 181, 16, 226, 167, 230, 84, 135, 90, 15, 145, 23, 88, 99, + 83, 19, 224, 144, 136, 116, 8, 33, 201, 84, 20, 31, 189, 203 + ] + .to_vec(), + [ + 24, 190, 145, 96, 30, 91, 86, 153, 77, 234, 166, 254, 76, 245, 157, 23, 152, 129, + 141, 84, 0, 105, 92, 100, 108, 196, 197, 126, 156, 15, 209, 230, 50, 216, 235, 177, + 223, 116, 54, 192, 134, 106, 33, 82, 110, 136, 54, 47, 198, 141, 123, 47, 191, 163, + 129, 176, 165, 224, 121, 158, 119, 168, 219, 6 + ] + .into(), + [ + 205, 92, 57, 103, 120, 65, 0, 136, 125, 30, 25, 141, 176, 72, 191, 198, 200, 70, 9, + 5, 52, 155, 123, 23, 16, 196, 109, 169, 146, 225, 165, 171 + ] + .into(), + ), + ( + [ + 234, 4, 248, 40, 225, 229, 244, 118, 26, 187, 63, 246, 11, 176, 53, 108, 51, 54, 2, + 132, 191, 16, 8, 85, 111, 150, 228, 94, 80, 192, 144, 159 + ] + .to_vec(), + [ + 13, 69, 57, 44, 76, 45, 23, 39, 17, 117, 23, 6, 98, 211, 163, 51, 119, 240, 48, 2, + 88, 73, 205, 190, 4, 203, 4, 250, 75, 134, 75, 82, 5, 89, 255, 171, 109, 22, 83, + 76, 177, 99, 224, 243, 84, 192, 83, 127, 105, 61, 96, 247, 107, 67, 78, 144, 206, + 56, 230, 210, 55, 150, 3, 0 + ] + .into(), + [ + 7, 46, 165, 146, 105, 160, 80, 138, 114, 236, 217, 138, 9, 100, 41, 231, 215, 92, + 215, 229, 24, 32, 129, 25, 16, 1, 243, 176, 0, 204, 147, 35 + ] + .into(), + ), + ( + [ + 188, 124, 52, 209, 210, 48, 97, 114, 229, 41, 172, 41, 106, 43, 131, 106, 26, 107, + 32, 24, 108, 47, 188, 99, 255, 193, 23, 43, 140, 103, 188, 88 + ] + .to_vec(), + [ + 115, 233, 189, 93, 167, 14, 110, 88, 157, 118, 162, 29, 195, 11, 250, 122, 252, + 224, 68, 12, 243, 10, 159, 127, 50, 65, 235, 115, 91, 120, 47, 211, 89, 0, 180, 99, + 140, 182, 215, 120, 146, 16, 247, 74, 149, 104, 13, 30, 64, 239, 55, 77, 204, 75, + 94, 231, 232, 217, 60, 7, 53, 133, 124, 4 + ] + .into(), + [ + 251, 201, 115, 163, 5, 40, 10, 63, 211, 146, 80, 78, 1, 16, 107, 199, 146, 72, 180, + 141, 183, 19, 199, 93, 33, 229, 205, 70, 32, 143, 163, 54 + ] + .into(), + ), + ( + [ + 17, 95, 185, 175, 219, 86, 64, 58, 174, 21, 201, 150, 129, 250, 36, 20, 195, 110, + 0, 113, 182, 24, 206, 73, 68, 16, 116, 151, 217, 0, 183, 164 + ] + .to_vec(), + [ + 5, 171, 71, 27, 104, 155, 152, 225, 171, 250, 77, 64, 133, 52, 21, 11, 195, 1, 249, + 69, 239, 136, 150, 180, 164, 139, 50, 132, 81, 146, 130, 39, 115, 69, 122, 24, 113, + 125, 135, 202, 222, 68, 56, 18, 111, 90, 170, 209, 231, 75, 90, 125, 210, 192, 36, + 168, 7, 253, 154, 47, 142, 204, 253, 12 + ] + .into(), + [ + 228, 135, 125, 188, 76, 87, 101, 6, 105, 175, 105, 96, 87, 202, 53, 118, 51, 202, + 103, 210, 10, 155, 106, 16, 142, 210, 53, 248, 207, 131, 121, 51 + ] + .into(), + ), + ( + [ + 237, 133, 151, 46, 146, 77, 61, 123, 70, 133, 58, 223, 107, 97, 229, 81, 87, 62, + 207, 195, 97, 170, 15, 82, 211, 5, 20, 89, 163, 217, 128, 35 + ] + .to_vec(), + [ + 94, 58, 85, 222, 114, 105, 9, 111, 156, 63, 24, 17, 189, 80, 195, 29, 204, 168, 0, + 170, 46, 12, 220, 2, 27, 31, 178, 205, 217, 146, 221, 242, 32, 34, 62, 194, 157, + 102, 152, 161, 12, 11, 220, 49, 126, 197, 57, 143, 98, 24, 127, 211, 128, 189, 79, + 65, 75, 88, 157, 47, 67, 40, 95, 6 + ] + .into(), + [ + 204, 43, 68, 82, 129, 227, 172, 253, 204, 243, 143, 138, 221, 73, 236, 38, 143, 9, + 41, 52, 38, 227, 152, 202, 198, 118, 237, 255, 65, 5, 251, 229 + ] + .into(), + ), + ( + [ + 109, 96, 113, 89, 67, 135, 43, 157, 40, 37, 177, 153, 123, 63, 40, 163, 123, 81, + 136, 113, 150, 209, 88, 176, 208, 58, 208, 160, 86, 26, 225, 66 + ] + .to_vec(), + [ + 78, 191, 253, 51, 223, 212, 196, 105, 82, 191, 242, 41, 117, 77, 104, 209, 172, + 202, 40, 253, 38, 157, 54, 13, 235, 157, 47, 31, 88, 249, 2, 196, 88, 126, 211, + 173, 47, 13, 111, 53, 112, 179, 185, 203, 119, 4, 74, 67, 53, 11, 245, 184, 42, + 160, 147, 154, 209, 242, 196, 248, 159, 150, 142, 1 + ] + .into(), + [ + 120, 105, 195, 25, 208, 187, 21, 168, 238, 79, 44, 94, 96, 0, 112, 179, 221, 190, + 141, 53, 19, 61, 42, 48, 175, 210, 148, 183, 79, 40, 7, 45 + ] + .into(), + ), + ( + [ + 216, 78, 65, 19, 9, 23, 203, 211, 17, 208, 180, 215, 155, 65, 140, 75, 188, 241, + 162, 82, 190, 215, 90, 217, 220, 160, 31, 168, 221, 172, 182, 129 + ] + .to_vec(), + [ + 153, 116, 51, 238, 50, 167, 226, 181, 122, 111, 73, 88, 42, 159, 251, 212, 194, 20, + 98, 43, 48, 125, 80, 116, 7, 243, 13, 203, 169, 71, 111, 165, 16, 236, 122, 203, 4, + 125, 111, 20, 96, 233, 121, 71, 138, 215, 169, 154, 134, 205, 9, 68, 197, 236, 179, + 102, 229, 168, 162, 245, 111, 85, 106, 3 + ] + .into(), + [ + 48, 161, 121, 172, 29, 185, 169, 8, 50, 189, 168, 130, 140, 254, 197, 6, 188, 93, + 253, 67, 198, 198, 185, 212, 42, 224, 217, 195, 129, 84, 230, 5 + ] + .into(), + ), + ( + [ + 113, 173, 51, 242, 20, 55, 96, 26, 102, 45, 249, 157, 86, 94, 171, 22, 215, 33, + 156, 121, 103, 97, 95, 98, 87, 226, 153, 54, 60, 83, 140, 122 + ] + .to_vec(), + [ + 235, 56, 216, 155, 20, 252, 54, 184, 249, 162, 116, 122, 221, 112, 171, 18, 182, + 233, 210, 116, 84, 216, 69, 75, 219, 2, 138, 87, 172, 233, 50, 200, 135, 188, 136, + 55, 81, 43, 101, 66, 176, 248, 24, 208, 106, 131, 44, 240, 166, 159, 162, 107, 58, + 126, 190, 11, 22, 56, 17, 96, 27, 179, 173, 9 + ] + .into(), + [ + 87, 69, 133, 181, 55, 135, 65, 98, 105, 71, 175, 166, 169, 105, 11, 82, 66, 250, + 166, 198, 232, 223, 130, 20, 236, 2, 0, 207, 179, 65, 151, 72 + ] + .into(), + ), + ( + [ + 49, 72, 74, 103, 248, 213, 54, 113, 74, 246, 9, 23, 71, 53, 169, 246, 99, 224, 151, + 195, 252, 116, 201, 4, 137, 105, 42, 92, 70, 108, 150, 227 + ] + .to_vec(), + [ + 138, 52, 43, 171, 87, 79, 36, 251, 109, 39, 155, 54, 200, 106, 231, 234, 75, 234, + 140, 168, 179, 68, 180, 227, 220, 212, 43, 44, 175, 177, 75, 108, 47, 216, 185, 32, + 126, 143, 204, 58, 46, 123, 85, 224, 214, 181, 247, 48, 252, 37, 5, 186, 193, 168, + 142, 205, 97, 153, 36, 161, 178, 162, 152, 3 + ] + .into(), + [ + 75, 64, 164, 20, 239, 96, 148, 68, 235, 208, 11, 77, 136, 192, 177, 116, 193, 84, + 44, 205, 239, 143, 46, 162, 51, 228, 61, 95, 160, 21, 200, 102 + ] + .into(), + ), + ]; +} diff --git a/frost-ristretto255/tests/proptests.proptest-regressions b/frost-ristretto255/tests/proptests.proptest-regressions new file mode 100644 index 0000000..41c883a --- /dev/null +++ b/frost-ristretto255/tests/proptests.proptest-regressions @@ -0,0 +1,8 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 25716e9dc4549b01b395fca1fc076fc34300ad972ac59c5e098f7ec90a03446b # shrinks to tweaks = [ChangePubkey], rng_seed = 946433020594646748 +cc ddb674b23f131d33cdbe34f3959f7fc076f4815a12ad5d6f741ae38d2689d1c2 # shrinks to tweaks = [ChangePubkey, ChangePubkey, ChangePubkey, ChangePubkey], rng_seed = 8346595811973717667 diff --git a/frost-ristretto255/tests/proptests.rs b/frost-ristretto255/tests/proptests.rs new file mode 100644 index 0000000..f03fbbc --- /dev/null +++ b/frost-ristretto255/tests/proptests.rs @@ -0,0 +1,153 @@ +use std::convert::TryFrom; + +use proptest::prelude::*; +use rand_core::{CryptoRng, RngCore}; + +use redjubjub::*; + +/// A signature test-case, containing signature data and expected validity. +#[derive(Clone, Debug)] +struct SignatureCase { + msg: Vec, + sig: Signature, + pk_bytes: VerificationKeyBytes, + invalid_pk_bytes: VerificationKeyBytes, + is_valid: bool, +} + +/// A modification to a test-case. +#[derive(Copy, Clone, Debug)] +enum Tweak { + /// No-op, used to check that unchanged cases verify. + None, + /// Change the message the signature is defined for, invalidating the signature. + ChangeMessage, + /// Change the public key the signature is defined for, invalidating the signature. + ChangePubkey, + /* XXX implement this -- needs to regenerate a custom signature because the + nonce commitment is fed into the hash, so it has to have torsion at signing + time. + /// Change the case to have a torsion component in the signature's `r` value. + AddTorsion, + */ + /* XXX implement this -- needs custom handling of field arithmetic. + /// Change the signature's `s` scalar to be unreduced (mod L), invalidating the signature. + UnreducedScalar, + */ +} + +impl SignatureCase { + fn new(mut rng: R, msg: Vec) -> Self { + let sk = SigningKey::new(&mut rng); + let sig = sk.sign(&mut rng, &msg); + let pk_bytes = VerificationKey::from(&sk).into(); + let invalid_pk_bytes = VerificationKey::from(&SigningKey::new(&mut rng)).into(); + Self { + msg, + sig, + pk_bytes, + invalid_pk_bytes, + is_valid: true, + } + } + + // Check that signature verification succeeds or fails, as expected. + fn check(&self) -> bool { + // The signature data is stored in (refined) byte types, but do a round trip + // conversion to raw bytes to exercise those code paths. + let sig = { + let bytes: [u8; 64] = self.sig.into(); + Signature::::from(bytes) + }; + let pk_bytes = { + let bytes: [u8; 32] = self.pk_bytes.into(); + VerificationKeyBytes::::from(bytes) + }; + + // Check that the verification key is a valid RedJubjub verification key. + let pub_key = VerificationKey::try_from(pk_bytes) + .expect("The test verification key to be well-formed."); + + // Check that signature validation has the expected result. + self.is_valid == pub_key.verify(&self.msg, &sig).is_ok() + } + + fn apply_tweak(&mut self, tweak: &Tweak) { + match tweak { + Tweak::None => {} + Tweak::ChangeMessage => { + // Changing the message makes the signature invalid. + self.msg.push(90); + self.is_valid = false; + } + Tweak::ChangePubkey => { + // Changing the public key makes the signature invalid. + self.pk_bytes = self.invalid_pk_bytes; + self.is_valid = false; + } + } + } +} + +fn tweak_strategy() -> impl Strategy { + prop_oneof![ + 10 => Just(Tweak::None), + 1 => Just(Tweak::ChangeMessage), + 1 => Just(Tweak::ChangePubkey), + ] +} + +use rand_chacha::ChaChaRng; +use rand_core::SeedableRng; + +proptest! { + + #[test] + fn tweak_signature( + tweaks in prop::collection::vec(tweak_strategy(), (0,5)), + rng_seed in prop::array::uniform32(any::()), + ) { + // Use a deterministic RNG so that test failures can be reproduced. + // Seeding with 64 bits of entropy is INSECURE and this code should + // not be copied outside of this test! + let mut rng = ChaChaRng::from_seed(rng_seed); + + // Create a test case for each signature type. + let msg = b"test message for proptests"; + let mut binding = SignatureCase::::new(&mut rng, msg.to_vec()); + let mut spendauth = SignatureCase::::new(&mut rng, msg.to_vec()); + + // Apply tweaks to each case. + for t in &tweaks { + binding.apply_tweak(t); + spendauth.apply_tweak(t); + } + + assert!(binding.check()); + assert!(spendauth.check()); + } + + #[test] + fn randomization_commutes_with_pubkey_homomorphism(rng_seed in prop::array::uniform32(any::())) { + // Use a deterministic RNG so that test failures can be reproduced. + let mut rng = ChaChaRng::from_seed(rng_seed); + + let r = { + // XXX-jubjub: better API for this + let mut bytes = [0; 64]; + rng.fill_bytes(&mut bytes[..]); + Randomizer::from_bytes_wide(&bytes) + }; + + let sk = SigningKey::::new(&mut rng); + let pk = VerificationKey::from(&sk); + + let sk_r = sk.randomize(&r); + let pk_r = pk.randomize(&r); + + let pk_r_via_sk_rand: [u8; 32] = VerificationKeyBytes::from(VerificationKey::from(&sk_r)).into(); + let pk_r_via_pk_rand: [u8; 32] = VerificationKeyBytes::from(pk_r).into(); + + assert_eq!(pk_r_via_pk_rand, pk_r_via_sk_rand); + } +} diff --git a/frost-ristretto255/tests/smallorder.rs b/frost-ristretto255/tests/smallorder.rs new file mode 100644 index 0000000..019068e --- /dev/null +++ b/frost-ristretto255/tests/smallorder.rs @@ -0,0 +1,24 @@ +use std::convert::TryFrom; + +use jubjub::{AffinePoint, Fq}; + +use redjubjub::*; + +#[test] +fn identity_publickey_passes() { + let identity = AffinePoint::identity(); + assert_eq!(::from(identity.is_small_order()), true); + let bytes = identity.to_bytes(); + let pk_bytes = VerificationKeyBytes::::from(bytes); + assert!(VerificationKey::::try_from(pk_bytes).is_ok()); +} + +#[test] +fn smallorder_publickey_passes() { + // (1,0) is a point of order 4 on any Edwards curve + let order4 = AffinePoint::from_raw_unchecked(Fq::one(), Fq::zero()); + assert_eq!(::from(order4.is_small_order()), true); + let bytes = order4.to_bytes(); + let pk_bytes = VerificationKeyBytes::::from(bytes); + assert!(VerificationKey::::try_from(pk_bytes).is_ok()); +}