diff --git a/Cargo.toml b/Cargo.toml index f7099ea..135ba44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ ark-ec ="^0.3.0" ark-ff ="^0.3.0" ark-bls12-381 = "^0.3.0" ark-std ="^0.3.0" -ark-serialize = "^0.3.0" +ark-serialize = { version = "^0.3.0", features = ["derive"] } thiserror = "1.0.32" hashbrown = "0.12.3" diff --git a/src/commitments.rs b/src/commitments.rs index 02684a2..9a2fa89 100644 --- a/src/commitments.rs +++ b/src/commitments.rs @@ -23,13 +23,14 @@ use ark_bls12_381::{Fr, G1Projective}; use ark_ec::group::Group; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; use std::ops::{Add, Mul}; /// A GroupCommitment object /// /// $GroupCommitment((G , H); T ; r ) = cm_T = (cm_{T,1} , cm_{T,2} ) = (r G , T + r H)$ -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, CanonicalDeserialize, CanonicalSerialize, Debug, PartialEq, Eq)] pub struct GroupCommitment { /// Given $GroupCommitment((G , H); T ; r )$ this is $rG$ pub T_1: G1Projective, diff --git a/src/curdleproofs.rs b/src/curdleproofs.rs index 80f0bd0..70c05dc 100644 --- a/src/curdleproofs.rs +++ b/src/curdleproofs.rs @@ -1,7 +1,9 @@ #![allow(non_snake_case)] -use ark_bls12_381::{Fr, G1Affine, G1Projective}; +pub use ark_bls12_381::{Fr, G1Affine, G1Projective}; use ark_ec::ProjectiveCurve; use ark_ff::PrimeField; +pub use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError}; +use ark_serialize::{Read, Write}; use ark_std::rand::RngCore; use ark_std::rand::{rngs::StdRng, SeedableRng}; use ark_std::{UniformRand, Zero}; @@ -21,7 +23,7 @@ use crate::same_scalar_argument::SameScalarProof; use crate::N_BLINDERS; /// The Curdleproofs CRS -#[derive(Clone, Debug)] +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] pub struct CurdleproofsCrs { /// Pedersen commitment bases pub vec_G: Vec, @@ -67,7 +69,7 @@ pub fn generate_crs(ell: usize) -> CurdleproofsCrs { } /// A Curdleproofs proof object -#[derive(Clone, Debug)] +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] pub struct CurdleproofsProof { A: G1Projective, cm_T: GroupCommitment, diff --git a/src/grand_product_argument.rs b/src/grand_product_argument.rs index f517d80..d8bb3b5 100644 --- a/src/grand_product_argument.rs +++ b/src/grand_product_argument.rs @@ -4,6 +4,7 @@ use core::iter; use ark_bls12_381::{Fr, G1Affine, G1Projective}; use ark_ec::{AffineCurve, ProjectiveCurve}; use ark_ff::{Field, One}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; use ark_std::rand::RngCore; use ark_std::Zero; @@ -16,7 +17,7 @@ use crate::msm_accumulator::MsmAccumulator; use crate::util::{generate_blinders, inner_product, msm}; /// A GrandProduct proof object -#[derive(Clone, Debug)] +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] pub struct GrandProductProof { C: G1Projective, diff --git a/src/inner_product_argument.rs b/src/inner_product_argument.rs index f610671..5056932 100644 --- a/src/inner_product_argument.rs +++ b/src/inner_product_argument.rs @@ -4,6 +4,7 @@ use ark_ec::AffineCurve; use ark_ec::ProjectiveCurve; use ark_ff::PrimeField; use ark_ff::{batch_inversion, Field}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; use ark_std::rand::RngCore; use ark_std::{One, Zero}; @@ -17,7 +18,7 @@ use crate::util::{ }; /// An IPA proof object -#[derive(Clone, Debug)] +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] pub struct InnerProductProof { B_c: G1Projective, B_d: G1Projective, diff --git a/src/lib.rs b/src/lib.rs index 85ba951..164b9f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,10 @@ pub mod same_permutation_argument; pub mod same_scalar_argument; pub mod transcript; pub mod util; +pub mod whisk; + +// To use in whisk code +pub use ark_bls12_381::g1::G1_GENERATOR_X; #[doc = include_str!("../doc/notes.md")] pub mod notes { diff --git a/src/same_multiscalar_argument.rs b/src/same_multiscalar_argument.rs index e9df993..83ccf05 100644 --- a/src/same_multiscalar_argument.rs +++ b/src/same_multiscalar_argument.rs @@ -4,6 +4,7 @@ use ark_ec::AffineCurve; use ark_ec::ProjectiveCurve; use ark_ff::PrimeField; use ark_ff::{batch_inversion, Field, One}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; use ark_std::rand::RngCore; use crate::transcript::CurdleproofsTranscript; @@ -16,7 +17,7 @@ use crate::util::{ }; /// A $SameMsm$ proof object -#[derive(Clone, Debug)] +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] pub struct SameMultiscalarProof { B_a: G1Projective, B_t: G1Projective, diff --git a/src/same_permutation_argument.rs b/src/same_permutation_argument.rs index b6948c6..cd45d4d 100644 --- a/src/same_permutation_argument.rs +++ b/src/same_permutation_argument.rs @@ -5,6 +5,7 @@ use ark_bls12_381::{Fr, G1Affine, G1Projective}; use ark_ec::group::Group; use ark_ec::ProjectiveCurve; use ark_ff::PrimeField; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; use ark_std::rand::RngCore; use crate::transcript::CurdleproofsTranscript; @@ -16,7 +17,7 @@ use crate::msm_accumulator::MsmAccumulator; use crate::util::{get_permutation, msm}; /// A same permutation proof object -#[derive(Clone, Debug)] +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] pub struct SamePermutationProof { B: G1Projective, diff --git a/src/same_scalar_argument.rs b/src/same_scalar_argument.rs index c06227d..8d4b475 100644 --- a/src/same_scalar_argument.rs +++ b/src/same_scalar_argument.rs @@ -3,6 +3,7 @@ use ark_bls12_381::{Fr, G1Projective}; use ark_ec::ProjectiveCurve; use ark_ff::PrimeField; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; use ark_std::rand::RngCore; use ark_std::UniformRand; @@ -11,7 +12,7 @@ use crate::errors::ProofError; use crate::transcript::CurdleproofsTranscript; use merlin::Transcript; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] pub struct SameScalarProof { cm_A: GroupCommitment, cm_B: GroupCommitment, diff --git a/src/whisk.rs b/src/whisk.rs new file mode 100644 index 0000000..04b7893 --- /dev/null +++ b/src/whisk.rs @@ -0,0 +1,565 @@ +#![allow(non_snake_case)] +pub use ark_bls12_381::g1::G1_GENERATOR_X; +pub use ark_bls12_381::{Fr, G1Affine, G1Projective}; +use ark_ec::AffineCurve; +use ark_ff::{PrimeField, ToBytes}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; +use ark_std::rand::prelude::SliceRandom; +use ark_std::rand::RngCore; +use ark_std::UniformRand; +use merlin::Transcript; +use std::io::Cursor; + +use crate::{ + curdleproofs::{CurdleproofsCrs, CurdleproofsProof}, + transcript::CurdleproofsTranscript, + util::shuffle_permute_and_commit_input, + N_BLINDERS, +}; + +pub const FIELD_ELEMENT_SIZE: usize = 32; +pub const G1POINT_SIZE: usize = 48; +pub const SHUFFLE_PROOF_SIZE: usize = 4528; +// 48+48+32 +pub const TRACKER_PROOF_SIZE: usize = 128; + +// TODO: Customize +const N: usize = 128; +const ELL: usize = N - N_BLINDERS; + +pub type ShuffleProofBytes = [u8; SHUFFLE_PROOF_SIZE]; +pub type TrackerProofBytes = [u8; TRACKER_PROOF_SIZE]; +pub type FieldElementBytes = [u8; FIELD_ELEMENT_SIZE]; + +#[derive(Clone, Debug)] +pub struct WhiskTracker { + pub r_G: G1Affine, // r * G + pub k_r_G: G1Affine, // k * r * G +} + +/// A tracker proof object +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct TrackerProof { + A: G1Projective, + B: G1Projective, + s: Fr, +} + +/// Verify a whisk shuffle proof +/// +/// # Arguments +/// +/// * `crs` - Curdleproofs CRS (Common Reference String), a trusted setup +/// * `pre_trackers` - Trackers before shuffling +/// * `post_trackers` - Trackers after shuffling +/// * `m` - Commitment to secret permutation +/// * `shuffle_proof` - Shuffle proof struct +pub fn is_valid_whisk_shuffle_proof( + rng: &mut T, + crs: &CurdleproofsCrs, + pre_trackers: &Vec, + post_trackers: &Vec, + m: &G1Affine, + shuffle_proof: &ShuffleProofBytes, +) -> Result { + let (vec_r, vec_s) = unzip_trackers(pre_trackers); + let (vec_t, vec_u) = unzip_trackers(post_trackers); + let m_projective = G1Projective::from(*m); + let shuffle_proof_instance = deserialize_shuffle_proof(shuffle_proof)?; + + Ok(shuffle_proof_instance + .verify(crs, &vec_r, &vec_s, &vec_t, &vec_u, &m_projective, rng) + .is_ok()) +} + +/// Create a whisk shuffle proof and serialize it for Whisk +/// +/// # Arguments +/// +/// * `crs` - Curdleproofs CRS (Common Reference String), a trusted setup +/// * `pre_trackers` - Whisk trackers to shuffle +/// +/// # Returns +/// +/// A tuple containing +/// * `0` `post_trackers` - Resulting shuffled trackers +/// * `1` `m` - Commitment to secret permutation +/// * `2` `shuffle_proof` - Shuffle proof struct +pub fn generate_whisk_shuffle_proof( + rng: &mut T, + crs: &CurdleproofsCrs, + pre_trackers: &Vec, +) -> Result<(Vec, G1Affine, ShuffleProofBytes), SerializationError> { + // Get witnesses: the permutation, the randomizer, and a bunch of blinders + let mut permutation: Vec = (0..ELL as u32).collect(); + + // permutation and k (randomizer) can be forgotten immediately after creating the proof + permutation.shuffle(rng); + let k = Fr::rand(rng); + + // Get shuffle inputs + let (vec_r, vec_s) = unzip_trackers(pre_trackers); + + let (vec_t, vec_u, m, vec_m_blinders) = + shuffle_permute_and_commit_input(crs, &vec_r, &vec_s, &permutation, &k, rng); + + let shuffle_proof_instance = CurdleproofsProof::new( + crs, + vec_r.clone(), + vec_s.clone(), + vec_t.clone(), + vec_u.clone(), + m, + permutation.clone(), + k, + vec_m_blinders.clone(), + rng, + ); + + let mut shuffle_proof: Vec = vec![]; + shuffle_proof_instance.serialize(&mut shuffle_proof)?; + + Ok(( + zip_trackers(&vec_t, &vec_u), + G1Affine::from(m), + serialize_shuffle_proof(&shuffle_proof_instance)?, + )) +} + +/// Verify knowledge of `k` such that `tracker.k_r_g == k * tracker.r_g` and `k_commitment == k * BLS_G1_GENERATOR`. +/// Defined in https://github.com/nalinbhardwaj/curdleproofs.pie/blob/59eb1d54fe193f063a718fc3bdded4734e66bddc/curdleproofs/curdleproofs/whisk_interface.py#L48-L68 +pub fn is_valid_whisk_tracker_proof( + tracker: &WhiskTracker, + k_commitment: &G1Affine, + tracker_proof: &TrackerProofBytes, +) -> Result { + let tracker_proof = deserialize_tracker_proof(tracker_proof)?; + + // TODO: deserializing here to serialize immediately after in append_list() + // serde could be avoided but there's value in checking point's ok before proof gen + let k_r_G = &tracker.k_r_G; + let r_G = &tracker.r_G; + let k_G = &k_commitment; + let G = G1Affine::prime_subgroup_generator(); + + // `k_r_G`: Existing WhiskTracker.k_r_g + // `r_G`: Existing WhiskTracker.k_r_g + // `k_G`: Existing k commitment + // `G`: Generator point, omit as is public knowledge + // `A`: From py impl `A = multiply(G, int(blinder))` + // `B`: From py impl `B = multiply(r_G, int(blinder))` + // `s`: From py impl `s = blinder - challenge * k` + + let mut transcript = Transcript::new(b"whisk_opening_proof"); + + // TODO: Check points before creating proof? + transcript.append_list( + b"tracker_opening_proof", + [ + &k_G, + &G1Affine::prime_subgroup_generator(), + &k_r_G, + &r_G, + &G1Affine::from(tracker_proof.A), + &G1Affine::from(tracker_proof.B), + ] + .as_slice(), + ); + let challenge = transcript.get_and_append_challenge(b"tracker_opening_proof_challenge"); + + let A_prime = G.mul(tracker_proof.s) + k_G.mul(challenge); + let B_prime = r_G.mul(tracker_proof.s) + k_r_G.mul(challenge); + + Ok(A_prime == tracker_proof.A && B_prime == tracker_proof.B) +} + +pub fn generate_whisk_tracker_proof( + rng: &mut T, + tracker: &WhiskTracker, + k_commitment: &G1Affine, + k: &Fr, +) -> Result { + let k_r_g = tracker.k_r_G; + let r_g = tracker.r_G; + let k_G = k_commitment; + let G = G1Affine::prime_subgroup_generator(); + + let blinder = Fr::rand(rng); + let A = G.mul(blinder); + let B = r_g.mul(blinder); + + let mut transcript = Transcript::new(b"whisk_opening_proof"); + + transcript.append_list( + b"tracker_opening_proof", + [ + &k_G, + &G, + &k_r_g, + &r_g, + &G1Affine::from(A), + &G1Affine::from(B), + ] + .as_slice(), + ); + + let challenge = transcript.get_and_append_challenge(b"tracker_opening_proof_challenge"); + let s = blinder - challenge * k; + + let tracker_proof = TrackerProof { A, B, s }; + + serialize_tracker_proof(&tracker_proof) +} + +fn unzip_trackers(trackers: &Vec) -> (Vec, Vec) { + let vec_r: Vec = trackers.iter().map(|tracker| tracker.r_G).collect(); + let vec_s: Vec = trackers.iter().map(|tracker| tracker.k_r_G).collect(); + (vec_r, vec_s) +} + +fn zip_trackers(vec_r: &Vec, vec_s: &Vec) -> Vec { + vec_r + .into_iter() + .zip(vec_s.into_iter()) + .map(|(r_G, k_r_G)| WhiskTracker { + r_G: *r_G, + k_r_G: *k_r_G, + }) + .collect() +} + +fn serialize_tracker_proof(proof: &TrackerProof) -> Result { + let mut out = [0; TRACKER_PROOF_SIZE]; + proof.serialize(out.as_mut_slice())?; + Ok(out) +} + +fn deserialize_tracker_proof( + proof_bytes: &TrackerProofBytes, +) -> Result { + TrackerProof::deserialize(Cursor::new(proof_bytes)) +} + +fn serialize_shuffle_proof( + proof: &CurdleproofsProof, +) -> Result { + let mut out = [0; SHUFFLE_PROOF_SIZE]; + proof.serialize(out.as_mut_slice())?; + Ok(out) +} + +fn deserialize_shuffle_proof( + proof_bytes: &ShuffleProofBytes, +) -> Result { + CurdleproofsProof::deserialize(Cursor::new(proof_bytes)) +} + +pub fn to_g1_compressed(g1: &G1Affine) -> Result<[u8; 48], SerializationError> { + let mut out = [0; 48]; + g1.serialize(out.as_mut_slice())?; + Ok(out) +} + +pub fn from_g1_compressed(buf: &[u8; 48]) -> Result { + G1Affine::deserialize(Cursor::new(buf)) +} + +/// Returns G1 generator (x,y) +pub fn g1_generator() -> G1Affine { + G1Affine::prime_subgroup_generator() +} + +/// Convert bytes to a BLS field scalar. The output is not uniform over the BLS field. +/// +/// Reads bytes in little-endian, and converts them to a field element. +/// If the bytes are larger than the modulus, it will reduce them. +pub fn bytes_to_bls_field(bytes: &[u8]) -> Fr { + Fr::from_le_bytes_mod_order(bytes) +} + +/// G1 scalar multiplication +pub fn bls_g1_scalar_multiply(g1: &G1Affine, scalar: &Fr) -> G1Affine { + G1Affine::from(g1.mul(*scalar)) +} + +/// Rand scalar +pub fn rand_scalar(rng: &mut T) -> Fr { + Fr::rand(rng) +} + +/// Serialize field element to bytes +pub fn serialize_fr(fr: &Fr) -> FieldElementBytes { + let mut bytes = [0u8; FIELD_ELEMENT_SIZE]; + fr.write(&mut bytes[..]).unwrap(); + bytes +} + +/// Returns field element from big endian bytes +pub fn deserialize_fr(bytes: &[u8]) -> Fr { + Fr::from_be_bytes_mod_order(bytes) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::curdleproofs::generate_crs; + use ark_ff::PrimeField; + use ark_std::rand::rngs::StdRng; + use ark_std::rand::SeedableRng; + use core::iter; + + fn generate_tracker(rng: &mut T, k: &Fr) -> WhiskTracker { + // r can be forgotten + let r = Fr::rand(rng); + let G = G1Affine::prime_subgroup_generator(); + + let r_G = G.mul(r); + let k_r_G = G1Affine::from(r_G).mul(*k); + + WhiskTracker { + r_G: G1Affine::from(r_G), + k_r_G: G1Affine::from(k_r_G), + } + } + + fn get_k_commitment(k: &Fr) -> G1Affine { + let G = G1Affine::prime_subgroup_generator(); + G1Affine::from(G.mul(*k)) + } + + fn generate_shuffle_trackers(rng: &mut T) -> Vec { + iter::repeat_with(|| { + let k = Fr::rand(rng); + generate_tracker(rng, &k) + }) + .take(ELL) + .collect() + } + + #[test] + fn whisk_tracker_proof() { + let mut rng = StdRng::seed_from_u64(0u64); + + let k = Fr::rand(&mut rng); + let tracker = generate_tracker(&mut rng, &k); + let k_commitment = get_k_commitment(&k); + + let tracker_proof = + generate_whisk_tracker_proof(&mut rng, &tracker, &k_commitment, &k).unwrap(); + assert!(is_valid_whisk_tracker_proof(&tracker, &k_commitment, &tracker_proof).unwrap()); + + // Assert correct TRACKER_PROOF_SIZE + let mut out_data = vec![]; + let mut out = Cursor::new(&mut out_data); + deserialize_tracker_proof(&tracker_proof) + .unwrap() + .serialize(&mut out) + .unwrap(); + assert_eq!(out_data.len(), TRACKER_PROOF_SIZE); + } + + #[test] + fn whisk_shuffle_proof() { + let mut rng = StdRng::seed_from_u64(0u64); + let crs: CurdleproofsCrs = generate_crs(ELL); + + let k = Fr::rand(&mut rng); + let tracker = generate_tracker(&mut rng, &k); + let k_commitment = get_k_commitment(&k); + let shuffled_trackers = generate_shuffle_trackers(&mut rng); + + let (whisk_post_shuffle_trackers, whisk_shuffle_proof_m_commitment, whisk_shuffle_proof) = + generate_whisk_shuffle_proof(&mut rng, &crs, &shuffled_trackers).unwrap(); + assert!(is_valid_whisk_shuffle_proof( + &mut rng, + &crs, + &shuffled_trackers, + &whisk_post_shuffle_trackers, + &whisk_shuffle_proof_m_commitment, + &whisk_shuffle_proof + ) + .unwrap()); + + // Assert correct TRACKER_PROOF_SIZE + let mut out_data = vec![]; + let mut out = Cursor::new(&mut out_data); + deserialize_shuffle_proof(&whisk_shuffle_proof) + .unwrap() + .serialize(&mut out) + .unwrap(); + assert_eq!(out_data.len(), SHUFFLE_PROOF_SIZE); + } + + // Construct the CRS + + struct Block { + pub whisk_opening_proof: TrackerProofBytes, + pub whisk_post_shuffle_trackers: Vec, + pub whisk_shuffle_proof: ShuffleProofBytes, + pub whisk_shuffle_proof_m_commitment: G1Affine, + pub whisk_registration_proof: TrackerProofBytes, + pub whisk_tracker: WhiskTracker, + pub whisk_k_commitment: G1Affine, + } + + struct State { + pub proposer_tracker: WhiskTracker, + pub proposer_k_commitment: G1Affine, + pub shuffled_trackers: Vec, + } + + fn process_block(crs: &CurdleproofsCrs, state: &mut State, block: &Block) { + let mut rng = StdRng::seed_from_u64(0u64); + + // process_whisk_opening_proof + assert!( + is_valid_whisk_tracker_proof( + &state.proposer_tracker, + &state.proposer_k_commitment, + &block.whisk_opening_proof, + ) + .unwrap(), + "invalid whisk_opening_proof" + ); + + // whisk_process_shuffled_trackers + assert!( + is_valid_whisk_shuffle_proof( + &mut rng, + &crs, + &state.shuffled_trackers, + &block.whisk_post_shuffle_trackers, + &block.whisk_shuffle_proof_m_commitment, + &block.whisk_shuffle_proof + ) + .unwrap(), + "invalid whisk_shuffle_proof" + ); + + // whisk_process_tracker_registration + let G = G1Affine::prime_subgroup_generator(); + if state.proposer_tracker.r_G == G { + // First proposal + assert!( + is_valid_whisk_tracker_proof( + &block.whisk_tracker, + &block.whisk_k_commitment, + &block.whisk_registration_proof, + ) + .unwrap(), + "invalid whisk_registration_proof" + ); + state.proposer_tracker = block.whisk_tracker.clone(); + state.proposer_k_commitment = block.whisk_k_commitment; + } else { + // Next proposals, registration data not used + } + } + + fn produce_block( + crs: &CurdleproofsCrs, + state: &State, + proposer_k: &Fr, + proposer_index: u64, + ) -> Block { + let mut rng = StdRng::seed_from_u64(0u64); + + let (whisk_post_shuffle_trackers, whisk_shuffle_proof_m_commitment, whisk_shuffle_proof) = + generate_whisk_shuffle_proof(&mut rng, &crs, &state.shuffled_trackers).unwrap(); + + let is_first_proposal = state.proposer_tracker.r_G == G1Affine::prime_subgroup_generator(); + + let (whisk_registration_proof, whisk_tracker, whisk_k_commitment) = if is_first_proposal { + // First proposal, validator creates tracker for registering + let whisk_tracker = generate_tracker(&mut rng, &proposer_k); + let whisk_k_commitment = get_k_commitment(&proposer_k); + let whisk_registration_proof = generate_whisk_tracker_proof( + &mut rng, + &whisk_tracker, + &whisk_k_commitment, + &proposer_k, + ) + .unwrap(); + (whisk_registration_proof, whisk_tracker, whisk_k_commitment) + } else { + // And subsequent proposals leave registration fields empty + let whisk_registration_proof = [0; TRACKER_PROOF_SIZE]; + let whisk_tracker = WhiskTracker { + r_G: G1Affine::prime_subgroup_generator(), + k_r_G: G1Affine::prime_subgroup_generator(), + }; + let whisk_k_commitment = G1Affine::prime_subgroup_generator(); + (whisk_registration_proof, whisk_tracker, whisk_k_commitment) + }; + + let k_prev_proposal = if is_first_proposal { + // On first proposal the k is computed deterministically and known to all + compute_initial_k(proposer_index) + } else { + // Subsequent proposals use same k for registered tracker + *proposer_k + }; + + let whisk_opening_proof = generate_whisk_tracker_proof( + &mut rng, + &state.proposer_tracker, + &state.proposer_k_commitment, + &k_prev_proposal, + ) + .unwrap(); + + Block { + whisk_opening_proof, + whisk_post_shuffle_trackers, + whisk_shuffle_proof, + whisk_shuffle_proof_m_commitment, + whisk_registration_proof, + whisk_tracker, + whisk_k_commitment, + } + } + + fn compute_initial_k(index: u64) -> Fr { + Fr::from_be_bytes_mod_order(&index.to_be_bytes()) + } + + #[test] + fn whisk_full_lifecycle() { + let mut rng = StdRng::seed_from_u64(0u64); + let crs: CurdleproofsCrs = generate_crs(ELL); + + // Initial tracker in state + let shuffled_trackers: Vec = iter::repeat_with(|| { + let k = Fr::rand(&mut rng); + generate_tracker(&mut rng, &k) + }) + .take(ELL) + .collect(); + + let proposer_index = 15400; + let proposer_initial_k = compute_initial_k(proposer_index); + + // Initial dummy values, r = 1 + let mut state = State { + proposer_tracker: WhiskTracker { + r_G: G1Affine::prime_subgroup_generator(), + k_r_G: get_k_commitment(&proposer_initial_k), + }, + proposer_k_commitment: get_k_commitment(&proposer_initial_k), + shuffled_trackers, + }; + + // k must be kept + let proposer_k = Fr::rand(&mut rng); + + // On first proposal, validator creates tracker for registering + let block_0 = produce_block(&crs, &state, &proposer_k, proposer_index); + // Block is valid + process_block(&crs, &mut state, &block_0); + + // On second proposal, validator opens previously submited tracker + let block_1 = produce_block(&crs, &state, &proposer_k, proposer_index); + // Block is valid + process_block(&crs, &mut state, &block_1); + } +}