diff --git a/Cargo.toml b/Cargo.toml index ba46825..3f58cff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,8 @@ repository = "https://github.com/EspressoSystems/hotshot-primitives" [dependencies] anyhow = "1.0" -ark-bls12-381 = "0.4.0" ark-bls12-377 = "0.4.0" +ark-bls12-381 = "0.4.0" ark-bn254 = "0.4.0" ark-ec = "0.4.0" ark-ff = "0.4.0" @@ -26,9 +26,9 @@ digest = { version = "0.10" } displaydoc = { version = "0.2.3", default-features = false } ethereum-types = { version = "0.14.1", features = ["impl-serde"] } generic-array = "0.14.7" -jf-relation = { git = "https://github.com/espressosystems/jellyfish"} -jf-primitives = { git = "https://github.com/espressosystems/jellyfish"} -jf-utils = { git = "https://github.com/espressosystems/jellyfish"} +jf-primitives = { git = "https://github.com/espressosystems/jellyfish" } +jf-relation = { git = "https://github.com/espressosystems/jellyfish" } +jf-utils = { git = "https://github.com/espressosystems/jellyfish" } serde = { version = "1.0", default-features = false, features = ["derive", "rc"] } sha3 = "0.10.7" tagged-base64 = { git = "https://github.com/espressosystems/tagged-base64", tag = "0.3.0" } @@ -36,8 +36,8 @@ thiserror = "1.0" typenum = { version = "1.16.0" } [dev-dependencies] -jf-primitives = { git = "https://github.com/espressosystems/jellyfish", features = ["test-srs"]} criterion = { version = "0.5.1", features = ["html_reports"] } +jf-primitives = { git = "https://github.com/espressosystems/jellyfish", features = ["test-srs"] } sha2 = { version = "0.10" } [[bench]] diff --git a/flake.nix b/flake.nix index cdc3281..549c1be 100644 --- a/flake.nix +++ b/flake.nix @@ -19,19 +19,19 @@ inputs.pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix"; inputs.pre-commit-hooks.inputs.nixpkgs.follows = "nixpkgs"; - outputs = { self, nixpkgs, flake-utils, flake-compat, rust-overlay, pre-commit-hooks, ... }: + outputs = { self, nixpkgs, flake-utils, flake-compat, rust-overlay + , pre-commit-hooks, ... }: flake-utils.lib.eachDefaultSystem (system: let overlays = [ (import rust-overlay) ]; pkgs = import nixpkgs { inherit system overlays; }; - nightlyToolchain = pkgs.rust-bin.selectLatestNightlyWith - (toolchain: toolchain.minimal.override { extensions = [ "rustfmt" ]; }); + nightlyToolchain = pkgs.rust-bin.selectLatestNightlyWith (toolchain: + toolchain.minimal.override { extensions = [ "rustfmt" ]; }); stableToolchain = pkgs.rust-bin.stable.latest.minimal.override { extensions = [ "clippy" "llvm-tools-preview" "rust-src" ]; }; - in with pkgs; - { + in with pkgs; { check = { pre-commit-check = pre-commit-hooks.lib.${system}.run { src = ./.; @@ -74,18 +74,20 @@ nightlyToolchain cargo-sort - ] ++ lib.optionals stdenv.isDarwin [ darwin.apple_sdk.frameworks.Security ]; + ] ++ lib.optionals stdenv.isDarwin + [ darwin.apple_sdk.frameworks.Security ]; shellHook = '' export RUST_BACKTRACE=full export PATH="$PATH:$(pwd)/target/debug:$(pwd)/target/release" + # Prevent cargo aliases from using programs in `~/.cargo` to avoid conflicts with local rustup installations. + export CARGO_HOME=$HOME/.cargo-nix # Ensure `cargo fmt` uses `rustfmt` from nightly. export RUSTFMT="${nightlyToolchain}/bin/rustfmt" '' - # install pre-commit hooks - + self.check.${system}.pre-commit-check.shellHook; + # install pre-commit hooks + + self.check.${system}.pre-commit-check.shellHook; }; - } - ); + }); } diff --git a/src/lib.rs b/src/lib.rs index a57ff62..7711e72 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ // #![warn(missing_docs)] // TODO need rustdoc for stake_table pub mod circuit; -pub mod quorum_certificate; +pub mod qc; pub mod stake_table; pub mod vdf; pub mod vid; diff --git a/src/quorum_certificate.rs b/src/qc/bit_vector.rs similarity index 50% rename from src/quorum_certificate.rs rename to src/qc/bit_vector.rs index 748ca37..40ffb82 100644 --- a/src/quorum_certificate.rs +++ b/src/qc/bit_vector.rs @@ -1,88 +1,29 @@ -use ark_std::fmt::Debug; -use bitvec::prelude::*; -use core::marker::PhantomData; -use typenum::U32; +//! Implementation for BitVectorQC that uses BLS signature + Bit vector. +//! See more details in HotShot paper. +use crate::qc::QuorumCertificate; use ark_std::{ + fmt::Debug, format, + marker::PhantomData, rand::{CryptoRng, RngCore}, vec, vec::Vec, }; +use bitvec::prelude::*; use ethereum_types::U256; -use generic_array::{ArrayLength, GenericArray}; +use generic_array::GenericArray; use jf_primitives::errors::PrimitivesError; use jf_primitives::errors::PrimitivesError::ParameterError; use jf_primitives::signatures::AggregateableSignatureSchemes; use serde::{Deserialize, Serialize}; +use typenum::U32; -/// Trait for validating a QC built from different signatures on the same message -pub trait QuorumCertificateValidation< - A: AggregateableSignatureSchemes + Serialize + for<'a> Deserialize<'a>, -> -{ - /// Public parameters for generating the QC - /// E.g: snark proving/verifying keys, list of (or pointer to) public keys stored in the smart contract. - type QCProverParams: Serialize + for<'a> Deserialize<'a>; - - /// Public parameters for validating the QC - /// E.g: verifying keys, stake table commitment - type QCVerifierParams: Serialize + for<'a> Deserialize<'a>; - - /// Extra value to check the aggregated signature of the QC - /// E.g: snark proof, bitmap corresponding to the public keys involved in signing - type Proof: Serialize + for<'a> Deserialize<'a>; - - /// Allows to fix the size of the message at compilation time. - type MessageLength: ArrayLength; - - /// Type of some auxiliary information returned by te check function in order to feed oth - type CheckedType; - - /// Produces a partial signature on a message with a single user signing key - /// NOTE: the original message (vote) should be prefixed with the hash of the stake table. - /// * `agg_sig_pp` - public parameters for aggregate signature - /// * `message` - message to be signed - /// * `signing_keys` - user signing key - /// * `returns` - a "simple" signature - fn partial_sign( - agg_sig_pp: &A::PublicParameter, - message: &GenericArray, - sig_key: &A::SigningKey, - prng: &mut R, - ) -> Result; - - /// Computes an aggregated signature from a set of partial signatures and the verification keys involved - /// * `qc_pp` - public parameters for generating the QC - /// * `active_keys` - a bool vector indicating the list of verification keys corresponding to the set of partial signatures - /// * `partial_sigs` - partial signatures on the same message - /// * `returns` - an error if some of the partial signatures provided are invalid - /// or the number of partial signatures / verifications keys are different. - /// Otherwise return an aggregated signature with a proof. - fn assemble( - qc_pp: &Self::QCProverParams, - active_keys: &BitSlice, - partial_sigs: &[A::Signature], - ) -> Result<(A::Signature, Self::Proof), PrimitivesError>; - - /// Checks an aggregated signature over some message provided as input - /// * `qc_vp` - public parameters for validating the QC - /// * `message` - message to check the aggregated signature against - /// * `sig` - aggregated signature on message - /// * `proof` - auxiliary information to check the signature - /// * `returns` - nothing if the signature is valid, an error otherwise. - fn check( - qc_pp: &Self::QCVerifierParams, - message: &GenericArray, - sig: &A::Signature, - proof: &Self::Proof, - ) -> Result; -} - +/// An implementation of QC using BLS signature and a bit-vector. #[derive(Serialize, Deserialize)] -pub struct BitvectorQuorumCertificate< - A: AggregateableSignatureSchemes + Serialize + for<'a> Deserialize<'a>, ->(PhantomData); +pub struct BitVectorQC Deserialize<'a>>( + PhantomData, +); #[derive(Serialize, Deserialize, PartialEq, Debug)] pub struct StakeTableEntry { @@ -97,7 +38,7 @@ pub struct QCParams { pub agg_sig_pp: P, } -impl QuorumCertificateValidation for BitvectorQuorumCertificate +impl QuorumCertificate for BitVectorQC where A: AggregateableSignatureSchemes + Serialize + for<'a> Deserialize<'a>, { @@ -106,41 +47,43 @@ where // TODO: later with SNARKs we'll use a smaller verifier parameter type QCVerifierParams = QCParams; - type Proof = BitVec; + type QC = (A::Signature, BitVec); type MessageLength = U32; - type CheckedType = (); + type QuorumSize = U256; - fn partial_sign( + fn sign( agg_sig_pp: &A::PublicParameter, message: &GenericArray, - sig_key: &A::SigningKey, + sk: &A::SigningKey, prng: &mut R, ) -> Result { - A::sign(agg_sig_pp, sig_key, message, prng) + A::sign(agg_sig_pp, sk, message, prng) } fn assemble( qc_pp: &Self::QCProverParams, - active_keys: &BitSlice, - partial_sigs: &[A::Signature], - ) -> Result<(A::Signature, Self::Proof), PrimitivesError> { - if active_keys.len() != qc_pp.stake_entries.len() { + signers: &BitSlice, + sigs: &[A::Signature], + ) -> Result { + if signers.len() != qc_pp.stake_entries.len() { return Err(ParameterError(format!( "bit vector len {} != the number of stake entries {}", - active_keys.len(), + signers.len(), qc_pp.stake_entries.len(), ))); } - let total_weight: U256 = qc_pp.stake_entries.iter().zip(active_keys.iter()).fold( - U256::zero(), - |acc, (entry, b)| { - if *b { - acc + entry.stake_amount - } else { - acc - } - }, - ); + let total_weight: U256 = + qc_pp + .stake_entries + .iter() + .zip(signers.iter()) + .fold(U256::zero(), |acc, (entry, b)| { + if *b { + acc + entry.stake_amount + } else { + acc + } + }); if total_weight < qc_pp.threshold { return Err(ParameterError(format!( "total_weight {} less than threshold {}", @@ -148,33 +91,33 @@ where ))); } let mut ver_keys = vec![]; - for (entry, b) in qc_pp.stake_entries.iter().zip(active_keys.iter()) { + for (entry, b) in qc_pp.stake_entries.iter().zip(signers.iter()) { if *b { ver_keys.push(entry.stake_key.clone()); } } - if ver_keys.len() != partial_sigs.len() { + if ver_keys.len() != sigs.len() { return Err(ParameterError(format!( "the number of ver_keys {} != the number of partial signatures {}", ver_keys.len(), - partial_sigs.len(), + sigs.len(), ))); } - let sig = A::aggregate(&qc_pp.agg_sig_pp, &ver_keys[..], partial_sigs)?; + let sig = A::aggregate(&qc_pp.agg_sig_pp, &ver_keys[..], sigs)?; - Ok((sig, active_keys.into())) + Ok((sig, signers.into())) } fn check( qc_vp: &Self::QCVerifierParams, message: &GenericArray, - sig: &A::Signature, - proof: &Self::Proof, - ) -> Result { - if proof.len() != qc_vp.stake_entries.len() { + qc: &Self::QC, + ) -> Result { + let (sig, signers) = qc; + if signers.len() != qc_vp.stake_entries.len() { return Err(ParameterError(format!( - "proof bit vector len {} != the number of stake entries {}", - proof.len(), + "signers bit vector len {} != the number of stake entries {}", + signers.len(), qc_vp.stake_entries.len(), ))); } @@ -182,7 +125,7 @@ where qc_vp .stake_entries .iter() - .zip(proof.iter()) + .zip(signers.iter()) .fold(U256::zero(), |acc, (entry, b)| { if *b { acc + entry.stake_amount @@ -197,12 +140,40 @@ where ))); } let mut ver_keys = vec![]; - for (entry, b) in qc_vp.stake_entries.iter().zip(proof.iter()) { + for (entry, b) in qc_vp.stake_entries.iter().zip(signers.iter()) { if *b { ver_keys.push(entry.stake_key.clone()); } } - A::multi_sig_verify(&qc_vp.agg_sig_pp, &ver_keys[..], message, sig) + A::multi_sig_verify(&qc_vp.agg_sig_pp, &ver_keys[..], message, sig)?; + + Ok(total_weight) + } + + fn trace( + qc_vp: &Self::QCVerifierParams, + message: &GenericArray<::MessageUnit, Self::MessageLength>, + qc: &Self::QC, + ) -> Result::VerificationKey>, PrimitivesError> { + let (_sig, signers) = qc; + if signers.len() != qc_vp.stake_entries.len() { + return Err(ParameterError(format!( + "signers bit vector len {} != the number of stake entries {}", + signers.len(), + qc_vp.stake_entries.len(), + ))); + } + + Self::check(qc_vp, message, qc)?; + + let signer_pks: Vec<_> = qc_vp + .stake_entries + .iter() + .zip(signers.iter()) + .filter(|(_, b)| **b) + .map(|(pk, _)| pk.stake_key.clone()) + .collect(); + Ok(signer_pks) } } @@ -237,21 +208,21 @@ mod tests { agg_sig_pp, }; let msg = [72u8; 32]; - let sig1 = BitvectorQuorumCertificate::<$aggsig>::partial_sign( + let sig1 = BitVectorQC::<$aggsig>::sign( &agg_sig_pp, &msg.into(), key_pair1.sign_key_ref(), &mut rng, ) .unwrap(); - let sig2 = BitvectorQuorumCertificate::<$aggsig>::partial_sign( + let sig2 = BitVectorQC::<$aggsig>::sign( &agg_sig_pp, &msg.into(), key_pair2.sign_key_ref(), &mut rng, ) .unwrap(); - let sig3 = BitvectorQuorumCertificate::<$aggsig>::partial_sign( + let sig3 = BitVectorQC::<$aggsig>::sign( &agg_sig_pp, &msg.into(), key_pair3.sign_key_ref(), @@ -260,20 +231,18 @@ mod tests { .unwrap(); // happy path - let active_keys = bitvec![0, 1, 1]; - let qc = BitvectorQuorumCertificate::<$aggsig>::assemble( + let signers = bitvec![0, 1, 1]; + let qc = BitVectorQC::<$aggsig>::assemble( &qc_pp, - active_keys.as_bitslice(), + signers.as_bitslice(), &[sig2.clone(), sig3.clone()], ) .unwrap(); - assert!(BitvectorQuorumCertificate::<$aggsig>::check( - &qc_pp, - &msg.into(), - &qc.0, - &qc.1 - ) - .is_ok()); + assert!(BitVectorQC::<$aggsig>::check(&qc_pp, &msg.into(), &qc).is_ok()); + assert_eq!( + BitVectorQC::<$aggsig>::trace(&qc_pp, &msg.into(), &qc).unwrap(), + vec![key_pair2.ver_key(), key_pair3.ver_key()], + ); // Check the QC and the QCParams can be serialized / deserialized assert_eq!( @@ -288,15 +257,15 @@ mod tests { // bad paths // number of signatures unmatch - assert!(BitvectorQuorumCertificate::<$aggsig>::assemble( + assert!(BitVectorQC::<$aggsig>::assemble( &qc_pp, - active_keys.as_bitslice(), + signers.as_bitslice(), &[sig2.clone()] ) .is_err()); // total weight under threshold let active_bad = bitvec![1, 1, 0]; - assert!(BitvectorQuorumCertificate::<$aggsig>::assemble( + assert!(BitVectorQC::<$aggsig>::assemble( &qc_pp, active_bad.as_bitslice(), &[sig1.clone(), sig2.clone()] @@ -304,44 +273,33 @@ mod tests { .is_err()); // wrong bool vector length let active_bad_2 = bitvec![0, 1, 1, 0]; - assert!(BitvectorQuorumCertificate::<$aggsig>::assemble( + assert!(BitVectorQC::<$aggsig>::assemble( &qc_pp, active_bad_2.as_bitslice(), &[sig2, sig3], ) .is_err()); - assert!(BitvectorQuorumCertificate::<$aggsig>::check( + assert!(BitVectorQC::<$aggsig>::check( &qc_pp, &msg.into(), - &qc.0, - &active_bad + &(qc.0.clone(), active_bad) ) .is_err()); - assert!(BitvectorQuorumCertificate::<$aggsig>::check( + assert!(BitVectorQC::<$aggsig>::check( &qc_pp, &msg.into(), - &qc.0, - &active_bad_2 + &(qc.0.clone(), active_bad_2) ) .is_err()); let bad_msg = [70u8; 32]; - assert!(BitvectorQuorumCertificate::<$aggsig>::check( - &qc_pp, - &bad_msg.into(), - &qc.0, - &qc.1 - ) - .is_err()); + assert!(BitVectorQC::<$aggsig>::check(&qc_pp, &bad_msg.into(), &qc).is_err()); let bad_sig = &sig1; - assert!(BitvectorQuorumCertificate::<$aggsig>::check( - &qc_pp, - &msg.into(), - &bad_sig, - &qc.1 - ) - .is_err()); + assert!( + BitVectorQC::<$aggsig>::check(&qc_pp, &msg.into(), &(bad_sig.clone(), qc.1)) + .is_err() + ); }; } #[test] diff --git a/src/qc/mod.rs b/src/qc/mod.rs new file mode 100644 index 0000000..8d70b0f --- /dev/null +++ b/src/qc/mod.rs @@ -0,0 +1,78 @@ +//! Quorum Certificate traits and implementations. + +use ark_std::{ + rand::{CryptoRng, RngCore}, + vec::Vec, +}; +use bitvec::prelude::*; +use generic_array::{ArrayLength, GenericArray}; +use jf_primitives::errors::PrimitivesError; +use jf_primitives::signatures::AggregateableSignatureSchemes; +use serde::{Deserialize, Serialize}; + +pub mod bit_vector; + +/// Trait for validating a QC built from different signatures on the same message +pub trait QuorumCertificate Deserialize<'a>> +{ + /// Public parameters for generating the QC + /// E.g: snark proving/verifying keys, list of (or pointer to) public keys stored in the smart contract. + type QCProverParams: Serialize + for<'a> Deserialize<'a>; + + /// Public parameters for validating the QC + /// E.g: verifying keys, stake table commitment + type QCVerifierParams: Serialize + for<'a> Deserialize<'a>; + + /// Allows to fix the size of the message at compilation time. + type MessageLength: ArrayLength; + + /// Type of the actual quorum certificate object + type QC; + + /// Type of the quorum size (e.g. number of votes or accumulated weight of signatures) + type QuorumSize; + + /// Produces a partial signature on a message with a single user signing key + /// NOTE: the original message (vote) should be prefixed with the hash of the stake table. + /// * `agg_sig_pp` - public parameters for aggregate signature + /// * `message` - message to be signed + /// * `sk` - user signing key + /// * `returns` - a "simple" signature + fn sign( + agg_sig_pp: &A::PublicParameter, + message: &GenericArray, + sk: &A::SigningKey, + prng: &mut R, + ) -> Result; + + /// Computes an aggregated signature from a set of partial signatures and the verification keys involved + /// * `qc_pp` - public parameters for generating the QC + /// * `signers` - a bool vector indicating the list of verification keys corresponding to the set of partial signatures + /// * `sigs` - partial signatures on the same message + /// * `returns` - an error if some of the partial signatures provided are invalid + /// or the number of partial signatures / verifications keys are different. + /// Otherwise return an obtained quorum certificate. + fn assemble( + qc_pp: &Self::QCProverParams, + signers: &BitSlice, + sigs: &[A::Signature], + ) -> Result; + + /// Checks an aggregated signature over some message provided as input + /// * `qc_vp` - public parameters for validating the QC + /// * `message` - message to check the aggregated signature against + /// * `qc` - quroum certificate + /// * `returns` - the quorum size if the qc is valid, an error otherwise. + fn check( + qc_vp: &Self::QCVerifierParams, + message: &GenericArray, + qc: &Self::QC, + ) -> Result; + + /// Trace the list of signers given a qc. + fn trace( + qc_vp: &Self::QCVerifierParams, + message: &GenericArray, + qc: &Self::QC, + ) -> Result, PrimitivesError>; +}