diff --git a/plonky2/src/fri/commitment.rs b/plonky2/src/fri/commitment.rs index 52ed0645..cd61a3c2 100644 --- a/plonky2/src/fri/commitment.rs +++ b/plonky2/src/fri/commitment.rs @@ -135,7 +135,7 @@ impl, C: GenericConfig, const D: usize> pub(crate) fn open_plonk( commitments: &[&Self; 4], zeta: F::Extension, - challenger: &mut Challenger, + challenger: &mut Challenger, common_data: &CommonCircuitData, timing: &mut TimingTree, ) -> (FriProof, OpeningSet) { diff --git a/plonky2/src/fri/prover.rs b/plonky2/src/fri/prover.rs index 20c4f133..21311939 100644 --- a/plonky2/src/fri/prover.rs +++ b/plonky2/src/fri/prover.rs @@ -21,7 +21,7 @@ pub fn fri_proof, C: GenericConfig, const lde_polynomial_coeffs: PolynomialCoeffs, // Evaluation of the polynomial on the large domain. lde_polynomial_values: PolynomialValues, - challenger: &mut Challenger, + challenger: &mut Challenger, common_data: &CommonCircuitData, timing: &mut TimingTree, ) -> FriProof { @@ -63,7 +63,7 @@ pub fn fri_proof, C: GenericConfig, const fn fri_committed_trees, C: GenericConfig, const D: usize>( mut coeffs: PolynomialCoeffs, mut values: PolynomialValues, - challenger: &mut Challenger, + challenger: &mut Challenger, common_data: &CommonCircuitData, ) -> ( Vec>, @@ -140,7 +140,7 @@ fn fri_prover_query_rounds< >( initial_merkle_trees: &[&MerkleTree], trees: &[MerkleTree], - challenger: &mut Challenger, + challenger: &mut Challenger, n: usize, common_data: &CommonCircuitData, ) -> Vec> { @@ -156,7 +156,7 @@ fn fri_prover_query_round< >( initial_merkle_trees: &[&MerkleTree], trees: &[MerkleTree], - challenger: &mut Challenger, + challenger: &mut Challenger, n: usize, common_data: &CommonCircuitData, ) -> FriQueryRound { diff --git a/plonky2/src/hash/gmimc.rs b/plonky2/src/hash/gmimc.rs index d5c0fb81..3492e08f 100644 --- a/plonky2/src/hash/gmimc.rs +++ b/plonky2/src/hash/gmimc.rs @@ -1,7 +1,15 @@ +use plonky2_field::extension_field::Extendable; use plonky2_field::field_types::Field; use plonky2_field::goldilocks_field::GoldilocksField; use unroll::unroll_for_loops; +use crate::gates::gmimc::GMiMCGate; +use crate::hash::hash_types::{HashOut, RichField}; +use crate::hash::hashing::{compress, hash_n_to_hash, PlonkyPermutation, SPONGE_WIDTH}; +use crate::iop::target::{BoolTarget, Target}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::config::{AlgebraicHasher, Hasher}; + pub(crate) const NUM_ROUNDS: usize = 101; pub trait GMiMC: Field @@ -85,6 +93,61 @@ impl GMiMC<12> for GoldilocksField { const ROUND_CONSTANTS: [u64; NUM_ROUNDS] = GOLDILOCKS_ROUND_CONSTANTS; } +pub struct GMiMCPermutation; +impl PlonkyPermutation for GMiMCPermutation { + fn permute(input: [F; SPONGE_WIDTH]) -> [F; SPONGE_WIDTH] { + F::gmimc_permute(input) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct GMiMCHash; +impl Hasher for GMiMCHash { + const HASH_SIZE: usize = 4 * 8; + type Hash = HashOut; + type Permutation = GMiMCPermutation; + + fn hash(input: Vec, pad: bool) -> Self::Hash { + hash_n_to_hash::(input, pad) + } + + fn two_to_one(left: Self::Hash, right: Self::Hash) -> Self::Hash { + compress::(left, right) + } +} + +impl AlgebraicHasher for GMiMCHash { + fn permute_swapped( + inputs: [Target; SPONGE_WIDTH], + swap: BoolTarget, + builder: &mut CircuitBuilder, + ) -> [Target; SPONGE_WIDTH] + where + F: RichField + Extendable, + { + let gate_type = GMiMCGate::::new(); + let gate = builder.add_gate(gate_type, vec![]); + + let swap_wire = GMiMCGate::::WIRE_SWAP; + let swap_wire = Target::wire(gate, swap_wire); + builder.connect(swap.target, swap_wire); + + // Route input wires. + for i in 0..SPONGE_WIDTH { + let in_wire = GMiMCGate::::wire_input(i); + let in_wire = Target::wire(gate, in_wire); + builder.connect(inputs[i], in_wire); + } + + // Collect output wires. + (0..SPONGE_WIDTH) + .map(|i| Target::wire(gate, GMiMCGate::::wire_output(i))) + .collect::>() + .try_into() + .unwrap() + } +} + #[cfg(test)] mod tests { use plonky2_field::goldilocks_field::GoldilocksField; diff --git a/plonky2/src/hash/hashing.rs b/plonky2/src/hash/hashing.rs index 45ae01dd..2f6a725c 100644 --- a/plonky2/src/hash/hashing.rs +++ b/plonky2/src/hash/hashing.rs @@ -97,19 +97,6 @@ pub trait PlonkyPermutation { fn permute(input: [F; SPONGE_WIDTH]) -> [F; SPONGE_WIDTH]; } -pub struct PoseidonPermutation; -impl PlonkyPermutation for PoseidonPermutation { - fn permute(input: [F; SPONGE_WIDTH]) -> [F; SPONGE_WIDTH] { - F::poseidon(input) - } -} -pub struct GMiMCPermutation; -impl PlonkyPermutation for GMiMCPermutation { - fn permute(input: [F; SPONGE_WIDTH]) -> [F; SPONGE_WIDTH] { - F::gmimc_permute(input) - } -} - /// If `pad` is enabled, the message is padded using the pad10*1 rule. In general this is required /// for the hash to be secure, but it can safely be disabled in certain cases, like if the input /// length is fixed. diff --git a/plonky2/src/hash/keccak.rs b/plonky2/src/hash/keccak.rs new file mode 100644 index 00000000..84275091 --- /dev/null +++ b/plonky2/src/hash/keccak.rs @@ -0,0 +1,81 @@ +use std::mem::size_of; + +use keccak_hash::keccak; + +use crate::hash::hash_types::{BytesHash, RichField}; +use crate::hash::hashing::{PlonkyPermutation, SPONGE_WIDTH}; +use crate::plonk::config::Hasher; +use crate::util::serialization::Buffer; + +/// Keccak-256 pseudo-permutation (not necessarily one-to-one) used in the challenger. +/// A state `input: [F; 12]` is sent to the field representation of `H(input) || H(H(input)) || H(H(H(input)))` +/// where `H` is the Keccak-256 hash. +pub struct KeccakPermutation; +impl PlonkyPermutation for KeccakPermutation { + fn permute(input: [F; SPONGE_WIDTH]) -> [F; SPONGE_WIDTH] { + // Use rejection sampling so that if one of the `u64` values in the output is larger than + // the field order, we increment the nonce and start again. + 'rejection_sampling: for nonce in 0u64.. { + // Fill a byte array with the little-endian representation of the field array. + let mut buffer = [0u8; (SPONGE_WIDTH + 1) * size_of::()]; + for i in 0..SPONGE_WIDTH { + buffer[i * size_of::()..(i + 1) * size_of::()] + .copy_from_slice(&input[i].to_canonical_u64().to_le_bytes()); + } + // Add the nonce at the end of the buffer. + buffer[SPONGE_WIDTH * size_of::()..].copy_from_slice(&nonce.to_le_bytes()); + // Concatenate `H(input), H(H(input)), H(H(H(input)))`. + let permutated_input_bytes = { + let mut ans = [0u8; 96]; + ans[0..32].copy_from_slice(&keccak(buffer).0); + ans[32..64].copy_from_slice(&keccak(keccak(buffer).0).0); + ans[64..96].copy_from_slice(&keccak(keccak(keccak(buffer).0).0).0); + ans + }; + // Write the hashed byte array to a field array. + let mut permutated_input = [F::ZERO; SPONGE_WIDTH]; + for i in 0..SPONGE_WIDTH { + let perm_u64 = u64::from_le_bytes( + permutated_input_bytes[i * size_of::()..(i + 1) * size_of::()] + .try_into() + .unwrap(), + ); + if perm_u64 >= F::ORDER { + // If a value is larger than the field order, we break and start again with a new nonce. + continue 'rejection_sampling; + } else { + permutated_input[i] = F::from_canonical_u64(perm_u64); + } + } + return permutated_input; + } + panic!("Improbable.") + } +} + +/// Keccak-256 hash function. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct KeccakHash; +impl Hasher for KeccakHash { + const HASH_SIZE: usize = N; + type Hash = BytesHash; + type Permutation = KeccakPermutation; + + fn hash(input: Vec, _pad: bool) -> Self::Hash { + let mut buffer = Buffer::new(Vec::new()); + buffer.write_field_vec(&input).unwrap(); + let mut arr = [0; N]; + let hash_bytes = keccak(buffer.bytes()).0; + arr.copy_from_slice(&hash_bytes[..N]); + BytesHash(arr) + } + + fn two_to_one(left: Self::Hash, right: Self::Hash) -> Self::Hash { + let mut v = vec![0; N * 2]; + v[0..N].copy_from_slice(&left.0); + v[N..].copy_from_slice(&right.0); + let mut arr = [0; N]; + arr.copy_from_slice(&keccak(v).0[..N]); + BytesHash(arr) + } +} diff --git a/plonky2/src/hash/mod.rs b/plonky2/src/hash/mod.rs index d6846de8..5a8ccb3f 100644 --- a/plonky2/src/hash/mod.rs +++ b/plonky2/src/hash/mod.rs @@ -2,6 +2,7 @@ mod arch; pub mod gmimc; pub mod hash_types; pub mod hashing; +pub mod keccak; pub mod merkle_proofs; pub mod merkle_tree; pub mod path_compression; diff --git a/plonky2/src/hash/poseidon.rs b/plonky2/src/hash/poseidon.rs index a224fa55..606dfd13 100644 --- a/plonky2/src/hash/poseidon.rs +++ b/plonky2/src/hash/poseidon.rs @@ -6,11 +6,14 @@ use plonky2_field::field_types::{Field, PrimeField}; use unroll::unroll_for_loops; use crate::gates::gate::Gate; +use crate::gates::poseidon::PoseidonGate; use crate::gates::poseidon_mds::PoseidonMdsGate; -use crate::hash::hash_types::RichField; -use crate::hash::hashing::SPONGE_WIDTH; +use crate::hash::hash_types::{HashOut, RichField}; +use crate::hash::hashing::{compress, hash_n_to_hash, PlonkyPermutation, SPONGE_WIDTH}; use crate::iop::ext_target::ExtensionTarget; +use crate::iop::target::{BoolTarget, Target}; use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::config::{AlgebraicHasher, Hasher}; // The number of full rounds and partial rounds is given by the // calc_round_numbers.py script. They happen to be the same for both @@ -615,6 +618,62 @@ pub trait Poseidon: PrimeField { } } +pub struct PoseidonPermutation; +impl PlonkyPermutation for PoseidonPermutation { + fn permute(input: [F; SPONGE_WIDTH]) -> [F; SPONGE_WIDTH] { + F::poseidon(input) + } +} + +/// Poseidon hash function. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct PoseidonHash; +impl Hasher for PoseidonHash { + const HASH_SIZE: usize = 4 * 8; + type Hash = HashOut; + type Permutation = PoseidonPermutation; + + fn hash(input: Vec, pad: bool) -> Self::Hash { + hash_n_to_hash::(input, pad) + } + + fn two_to_one(left: Self::Hash, right: Self::Hash) -> Self::Hash { + compress::(left, right) + } +} + +impl AlgebraicHasher for PoseidonHash { + fn permute_swapped( + inputs: [Target; SPONGE_WIDTH], + swap: BoolTarget, + builder: &mut CircuitBuilder, + ) -> [Target; SPONGE_WIDTH] + where + F: RichField + Extendable, + { + let gate_type = PoseidonGate::::new(); + let gate = builder.add_gate(gate_type, vec![]); + + let swap_wire = PoseidonGate::::WIRE_SWAP; + let swap_wire = Target::wire(gate, swap_wire); + builder.connect(swap.target, swap_wire); + + // Route input wires. + for i in 0..SPONGE_WIDTH { + let in_wire = PoseidonGate::::wire_input(i); + let in_wire = Target::wire(gate, in_wire); + builder.connect(inputs[i], in_wire); + } + + // Collect output wires. + (0..SPONGE_WIDTH) + .map(|i| Target::wire(gate, PoseidonGate::::wire_output(i))) + .collect::>() + .try_into() + .unwrap() + } +} + #[cfg(test)] pub(crate) mod test_helpers { use plonky2_field::field_types::Field; diff --git a/plonky2/src/iop/challenger.rs b/plonky2/src/iop/challenger.rs index a97cb5f1..b8ca4fb7 100644 --- a/plonky2/src/iop/challenger.rs +++ b/plonky2/src/iop/challenger.rs @@ -15,7 +15,7 @@ use crate::plonk::proof::{OpeningSet, OpeningSetTarget}; /// Observes prover messages, and generates challenges by hashing the transcript, a la Fiat-Shamir. #[derive(Clone)] -pub struct Challenger> { +pub struct Challenger> { sponge_state: [F; SPONGE_WIDTH], input_buffer: Vec, output_buffer: Vec, @@ -30,7 +30,7 @@ pub struct Challenger> { /// design, but it can be viewed as a duplex sponge whose inputs are sometimes zero (when we perform /// multiple squeezes) and whose outputs are sometimes ignored (when we perform multiple /// absorptions). Thus the security properties of a duplex sponge still apply to our design. -impl> Challenger { +impl> Challenger { pub fn new() -> Challenger { Challenger { sponge_state: [F::ZERO; SPONGE_WIDTH], diff --git a/plonky2/src/plonk/config.rs b/plonky2/src/plonk/config.rs index 252e8e3a..34f92f58 100644 --- a/plonky2/src/plonk/config.rs +++ b/plonky2/src/plonk/config.rs @@ -1,23 +1,18 @@ -use std::convert::TryInto; use std::fmt::Debug; -use keccak_hash::keccak; use plonky2_field::extension_field::quadratic::QuadraticExtension; use plonky2_field::extension_field::{Extendable, FieldExtension}; use plonky2_field::goldilocks_field::GoldilocksField; use serde::{de::DeserializeOwned, Serialize}; -use crate::gates::gmimc::GMiMCGate; -use crate::gates::poseidon::PoseidonGate; +use crate::hash::gmimc::GMiMCHash; +use crate::hash::hash_types::HashOut; use crate::hash::hash_types::RichField; -use crate::hash::hash_types::{BytesHash, HashOut}; -use crate::hash::hashing::{ - compress, hash_n_to_hash, GMiMCPermutation, PlonkyPermutation, PoseidonPermutation, - SPONGE_WIDTH, -}; +use crate::hash::hashing::{PlonkyPermutation, SPONGE_WIDTH}; +use crate::hash::keccak::KeccakHash; +use crate::hash::poseidon::PoseidonHash; use crate::iop::target::{BoolTarget, Target}; use crate::plonk::circuit_builder::CircuitBuilder; -use crate::util::serialization::Buffer; pub trait GenericHashOut: Copy + Clone + Debug + Eq + PartialEq + Send + Sync + Serialize + DeserializeOwned @@ -34,6 +29,9 @@ pub trait Hasher: Sized + Clone + Debug + Eq + PartialEq { const HASH_SIZE: usize; type Hash: GenericHashOut; + /// Permutation used in the sponge construction. + type Permutation: PlonkyPermutation; + fn hash(input: Vec, pad: bool) -> Self::Hash; fn two_to_one(left: Self::Hash, right: Self::Hash) -> Self::Hash; } @@ -43,8 +41,6 @@ pub trait AlgebraicHasher: Hasher> { // TODO: Adding a `const WIDTH: usize` here yields a compiler error down the line. // Maybe try again in a while. - /// Permutation used in the sponge construction. - type Permutation: PlonkyPermutation; /// Circuit to conditionally swap two chunks of the inputs (useful in verifying Merkle proofs), /// then apply the permutation. fn permute_swapped( @@ -56,131 +52,6 @@ pub trait AlgebraicHasher: Hasher> { F: RichField + Extendable; } -/// Poseidon hash function. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct PoseidonHash; -impl Hasher for PoseidonHash { - const HASH_SIZE: usize = 4 * 8; - type Hash = HashOut; - - fn hash(input: Vec, pad: bool) -> Self::Hash { - hash_n_to_hash::>::Permutation>(input, pad) - } - - fn two_to_one(left: Self::Hash, right: Self::Hash) -> Self::Hash { - compress::>::Permutation>(left, right) - } -} - -impl AlgebraicHasher for PoseidonHash { - type Permutation = PoseidonPermutation; - - fn permute_swapped( - inputs: [Target; SPONGE_WIDTH], - swap: BoolTarget, - builder: &mut CircuitBuilder, - ) -> [Target; SPONGE_WIDTH] - where - F: RichField + Extendable, - { - let gate_type = PoseidonGate::::new(); - let gate = builder.add_gate(gate_type, vec![]); - - let swap_wire = PoseidonGate::::WIRE_SWAP; - let swap_wire = Target::wire(gate, swap_wire); - builder.connect(swap.target, swap_wire); - - // Route input wires. - for i in 0..SPONGE_WIDTH { - let in_wire = PoseidonGate::::wire_input(i); - let in_wire = Target::wire(gate, in_wire); - builder.connect(inputs[i], in_wire); - } - - // Collect output wires. - (0..SPONGE_WIDTH) - .map(|i| Target::wire(gate, PoseidonGate::::wire_output(i))) - .collect::>() - .try_into() - .unwrap() - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct GMiMCHash; -impl Hasher for GMiMCHash { - const HASH_SIZE: usize = 4 * 8; - type Hash = HashOut; - - fn hash(input: Vec, pad: bool) -> Self::Hash { - hash_n_to_hash::>::Permutation>(input, pad) - } - - fn two_to_one(left: Self::Hash, right: Self::Hash) -> Self::Hash { - compress::>::Permutation>(left, right) - } -} - -impl AlgebraicHasher for GMiMCHash { - type Permutation = GMiMCPermutation; - - fn permute_swapped( - inputs: [Target; SPONGE_WIDTH], - swap: BoolTarget, - builder: &mut CircuitBuilder, - ) -> [Target; SPONGE_WIDTH] - where - F: RichField + Extendable, - { - let gate_type = GMiMCGate::::new(); - let gate = builder.add_gate(gate_type, vec![]); - - let swap_wire = GMiMCGate::::WIRE_SWAP; - let swap_wire = Target::wire(gate, swap_wire); - builder.connect(swap.target, swap_wire); - - // Route input wires. - for i in 0..SPONGE_WIDTH { - let in_wire = GMiMCGate::::wire_input(i); - let in_wire = Target::wire(gate, in_wire); - builder.connect(inputs[i], in_wire); - } - - // Collect output wires. - (0..SPONGE_WIDTH) - .map(|i| Target::wire(gate, GMiMCGate::::wire_output(i))) - .collect::>() - .try_into() - .unwrap() - } -} - -/// Keccak-256 hash function. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct KeccakHash; -impl Hasher for KeccakHash { - const HASH_SIZE: usize = N; - type Hash = BytesHash; - - fn hash(input: Vec, _pad: bool) -> Self::Hash { - let mut buffer = Buffer::new(Vec::new()); - buffer.write_field_vec(&input).unwrap(); - let mut arr = [0; N]; - let hash_bytes = keccak(buffer.bytes()).0; - arr.copy_from_slice(&hash_bytes[..N]); - BytesHash(arr) - } - - fn two_to_one(left: Self::Hash, right: Self::Hash) -> Self::Hash { - let mut v = vec![0; N * 2]; - v[0..N].copy_from_slice(&left.0); - v[N..].copy_from_slice(&right.0); - let mut arr = [0; N]; - arr.copy_from_slice(&keccak(v).0[..N]); - BytesHash(arr) - } -} - /// Generic configuration trait. pub trait GenericConfig: Debug + Clone + Sync + Sized + Send + Eq + PartialEq diff --git a/plonky2/src/plonk/get_challenges.rs b/plonky2/src/plonk/get_challenges.rs index 12c069a0..4e90e27b 100644 --- a/plonky2/src/plonk/get_challenges.rs +++ b/plonky2/src/plonk/get_challenges.rs @@ -32,7 +32,7 @@ fn get_challenges, C: GenericConfig, cons let num_fri_queries = config.fri_config.num_query_rounds; let lde_size = common_data.lde_size(); - let mut challenger = Challenger::::new(); + let mut challenger = Challenger::::new(); // Observe the instance. challenger.observe_hash::(common_data.circuit_digest); diff --git a/plonky2/src/plonk/prover.rs b/plonky2/src/plonk/prover.rs index 1ae85f34..169f78bb 100644 --- a/plonky2/src/plonk/prover.rs +++ b/plonky2/src/plonk/prover.rs @@ -79,7 +79,7 @@ pub(crate) fn prove, C: GenericConfig, co ) ); - let mut challenger = Challenger::new(); + let mut challenger = Challenger::::new(); // Observe the instance. challenger.observe_hash::(common_data.circuit_digest);