Skip to content

Commit

Permalink
feat: Poseidon2-based Sponge function (#713)
Browse files Browse the repository at this point in the history
  • Loading branch information
alxiong authored Dec 14, 2024
1 parent f8fa50d commit fdd8e44
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 10 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ sha2 = { version = "0.10", default-features = false }
sha3 = { version = "0.10", default-features = false }
itertools = { version = "0.12", default-features = false }
tagged-base64 = "0.4"
zeroize = { version = "^1.8" }
2 changes: 1 addition & 1 deletion elgamal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ displaydoc = { workspace = true }
jf-relation = { version = "0.4.4", git = "https://github.com/EspressoSystems/jellyfish", tag = "0.4.5", optional = true, default-features = false }
jf-rescue = { version = "0.1.0", git = "https://github.com/EspressoSystems/jellyfish", tag = "0.4.5", default-features = false }
rayon = { version = "1.5.0", optional = true }
zeroize = { version = "1.5", default-features = false }
zeroize = { workspace = true }

[dev-dependencies]
ark-ed-on-bls12-377 = "0.4.0"
Expand Down
2 changes: 1 addition & 1 deletion merkle_tree/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ displaydoc = { workspace = true }
hashbrown = { workspace = true }
hex = "0.4.3"
itertools = { workspace = true, features = ["use_alloc"] }
jf-poseidon2 = { path = "../poseidon2" }
jf-poseidon2 = { version = "0.1.0", git = "https://github.com/EspressoSystems/jellyfish" }
jf-relation = { version = "0.4.4", git = "https://github.com/EspressoSystems/jellyfish", tag = "0.4.5", optional = true, default-features = false }
jf-rescue = { version = "0.1.0", git = "https://github.com/EspressoSystems/jellyfish", tag = "0.4.5", default-features = false }
jf-utils = { version = "0.4.4", git = "https://github.com/EspressoSystems/jellyfish", tag = "0.4.5", default-features = false }
Expand Down
1 change: 0 additions & 1 deletion plonk/src/proof_system/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,6 @@ impl<T: PrimeField> ProofEvaluations<T> {
/// create variables for the ProofEvaluations who's field
/// is smaller than plonk circuit field.
/// The output wires are in the FpElemVar form.
pub(crate) fn create_variables<F>(
&self,
circuit: &mut PlonkCircuit<F>,
Expand Down
4 changes: 4 additions & 0 deletions poseidon2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ ark-bn254 = { workspace = true, optional = true }
ark-ff = { workspace = true }
ark-std = { workspace = true }
hex = "0.4.3"
jf-crhf = { version = "0.1.0", git = "https://github.com/EspressoSystems/jellyfish", tag = "0.4.5", default-features = false }
lazy_static = "1.5.0"
# TODO: fix this once upstream release a new tag
nimue = { git = "https://github.com/alxiong/nimue", branch = "ax/0.1.1", features = ["ark"] }
zeroize = { workspace = true }

[dev-dependencies]
criterion = "0.5.1"
Expand Down
2 changes: 2 additions & 0 deletions poseidon2/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod bls12_381;
#[cfg(feature = "bn254")]
pub mod bn254;

#[allow(dead_code)]
#[inline]
pub(crate) fn from_hex<F: PrimeField>(s: &str) -> F {
F::from_be_bytes_mod_order(&<[u8; 32]>::from_hex(s).expect("Invalid HexStr"))
Expand All @@ -31,6 +32,7 @@ macro_rules! define_poseidon2_params {
/// - sbox size = $sbox_size
/// - external rounds = $ext_rounds
/// - internal rounds = $int_rounds
#[derive(Clone, Debug)]
pub struct $struct_name;

impl Poseidon2Params<Fr, $state_size> for $struct_name {
Expand Down
3 changes: 2 additions & 1 deletion poseidon2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use ark_std::{borrow::ToOwned, marker::PhantomData};
pub mod constants;
mod external;
mod internal;
pub mod sponge;

/// Parameters required for a Poseidon2 permutation instance.
///
Expand All @@ -33,7 +34,7 @@ mod internal;
/// - `T`: state size = rate + capacity, `T` is made generic for easy trait
/// bound on `permute<F,T>(input: [F; N])` and type safety on `external_rc()`
/// return type.
pub trait Poseidon2Params<F: PrimeField, const T: usize> {
pub trait Poseidon2Params<F: PrimeField, const T: usize>: Clone {
/// t: state size = rate + capacity
const T: usize = T;
/// d: sbox degree
Expand Down
197 changes: 197 additions & 0 deletions poseidon2/src/sponge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
//! Poseidon2-based Cryptographic Sponge
use crate::{Poseidon2, Poseidon2Params};
use ark_ff::PrimeField;
use ark_std::marker::PhantomData;
use nimue::{hash::sponge::Sponge, Unit};
use zeroize::Zeroize;

/// Poseidon2-based Cryptographic Sponge
///
/// # Generic parameters:
/// - N: state size = rate (R) + capacity (C)
/// - R: rate (number of field abosrbed/squeezed)
///
/// For security, for b=128-bit security, field size |F|, C*|F|>=2b:
/// i.e. 128-bit for 256-bit fields, C>=1.
/// This check is being down during `Poseidon2Sponge::new(&iv)`
/// (See Poseidon2 paper Page 7 Footnote 5)
///
/// For BLS12-381, we choose C=1 for 128 security
/// For BN254, we choose C=1 for (100<*<128)-security
#[derive(Clone, Debug)]
pub struct Poseidon2Sponge<F: PrimeField, const N: usize, const R: usize, P: Poseidon2Params<F, N>>
{
/// state of sponge
pub(crate) state: [F; N],
_rate: PhantomData<[(); R]>,
_p: PhantomData<P>,
}

impl<F, const N: usize, const R: usize, P> Sponge for Poseidon2Sponge<F, N, R, P>
where
F: PrimeField + Unit,
P: Poseidon2Params<F, N>,
{
type U = F;
const N: usize = N;
const R: usize = R;

fn new(iv: [u8; 32]) -> Self {
assert!(N >= 2 && R > 0 && N > R);
// For security, for b-bit security, field size |F|, C*|F|>=2b:
// at least 100 security required
assert!((N - R) as u32 * <F as PrimeField>::MODULUS_BIT_SIZE >= 200);

// fill capacity portion with initial vector IV
let mut state = [F::default(); N];
state[R] = F::from_be_bytes_mod_order(&iv);
Self {
state,
_rate: PhantomData,
_p: PhantomData,
}
}

fn permute(&mut self) {
Poseidon2::permute_mut::<P, N>(&mut self.state);
}
}
impl<F, const N: usize, const R: usize, P> Default for Poseidon2Sponge<F, N, R, P>
where
F: PrimeField,
P: Poseidon2Params<F, N>,
{
fn default() -> Self {
Self {
state: [F::default(); N],
_rate: PhantomData,
_p: PhantomData,
}
}
}

impl<F, const N: usize, const R: usize, P> AsRef<[F]> for Poseidon2Sponge<F, N, R, P>
where
F: PrimeField,
P: Poseidon2Params<F, N>,
{
fn as_ref(&self) -> &[F] {
&self.state
}
}
impl<F, const N: usize, const R: usize, P> AsMut<[F]> for Poseidon2Sponge<F, N, R, P>
where
F: PrimeField,
P: Poseidon2Params<F, N>,
{
fn as_mut(&mut self) -> &mut [F] {
&mut self.state
}
}

impl<F, const N: usize, const R: usize, P> Zeroize for Poseidon2Sponge<F, N, R, P>
where
F: PrimeField,
P: Poseidon2Params<F, N>,
{
fn zeroize(&mut self) {
self.state.zeroize();
}
}

#[cfg(feature = "bls12-381")]
mod bls12_381 {
#![allow(dead_code)]
use super::*;
use crate::constants::bls12_381::*;
use ark_bls12_381::Fr;
use nimue::hash::sponge::DuplexSponge;
/// A sponge over BLS12-381 scalar field, state_size=2, rate=1.
pub type Poseidon2SpongeBlsN2R1 = DuplexSponge<Poseidon2Sponge<Fr, 2, 1, Poseidon2ParamsBls2>>;
/// A sponge over BLS12-381 scalar field, state_size=3, rate=1.
pub type Poseidon2SpongeBlsN3R1 = DuplexSponge<Poseidon2Sponge<Fr, 3, 1, Poseidon2ParamsBls3>>;
/// A sponge over BLS12-381 scalar field, state_size=3, rate=2.
pub type Poseidon2SpongeBlsN3R2 = DuplexSponge<Poseidon2Sponge<Fr, 3, 2, Poseidon2ParamsBls3>>;

#[test]
fn test_bls_sponge() {
use super::tests::test_sponge;
test_sponge::<Fr, Poseidon2SpongeBlsN2R1>();
test_sponge::<Fr, Poseidon2SpongeBlsN3R1>();
test_sponge::<Fr, Poseidon2SpongeBlsN3R2>();
}
}

#[cfg(feature = "bn254")]
mod bn254 {
#![allow(dead_code)]
use super::*;
use crate::constants::bn254::*;
use ark_bn254::Fr;
use nimue::hash::sponge::DuplexSponge;
/// A sponge over BN254 scalar field, state_size=3, rate=1.
pub type Poseidon2SpongeBnN3R1 = DuplexSponge<Poseidon2Sponge<Fr, 3, 1, Poseidon2ParamsBn3>>;
/// A sponge over BN254 scalar field, state_size=3, rate=2.
pub type Poseidon2SpongeBnN3R2 = DuplexSponge<Poseidon2Sponge<Fr, 3, 2, Poseidon2ParamsBn3>>;

#[test]
fn test_bn_sponge() {
use super::tests::test_sponge;
test_sponge::<Fr, Poseidon2SpongeBnN3R1>();
test_sponge::<Fr, Poseidon2SpongeBnN3R2>();
}
}

#[cfg(test)]
pub(crate) mod tests {
use super::*;
use ark_ff::BigInteger;
use ark_std::vec::Vec;
use nimue::{DuplexHash, IOPattern, UnitTranscript};

// inspired by:
// <https://github.com/arkworks-rs/nimue/blob/bdec8c446b804930a8375a8d2a3703a6071abf6b/nimue-poseidon/src/tests.rs#L16C4-L16C44>
pub(crate) fn test_sponge<F: PrimeField + Unit, H: DuplexHash<F>>() {
let io = IOPattern::<H, F>::new("test")
.absorb(1, "in")
.squeeze(2048, "out");

// prover transcript
let mut merlin = io.to_merlin();
// prover first message (label: "in")
merlin.add_units(&[F::from(42u32)]).unwrap();

let mut merlin_challenges = [F::default(); 2048];
merlin.fill_challenge_units(&mut merlin_challenges).unwrap();

// verifier transcript
let mut arthur = io.to_arthur(merlin.transcript());
// reading prover's first message labelled "in", since we don't need it, read
// into a one-time throw-away array
arthur.fill_next_units(&mut [F::default()]).unwrap();
let mut arthur_challenges = [F::default(); 2048];
arthur.fill_challenge_units(&mut arthur_challenges).unwrap();

// challenge computed from both sides should be the same
assert_eq!(merlin_challenges, arthur_challenges);

// Looking at byte distribution, whether it's close to uniform
let chal_bytes: Vec<u8> = merlin_challenges
.iter()
.flat_map(|c| c.into_bigint().to_bytes_le())
.collect();
let frequencies = (0u8..=255)
.map(|i| chal_bytes.iter().filter(|&&x| x == i).count())
.collect::<Vec<_>>();
// the expected frequency if it's uniformly random
let expected_mean = (F::MODULUS_BIT_SIZE / 8 * 2048 / 256) as usize;
assert!(
frequencies
.iter()
.all(|&x| x < expected_mean * 2 && x > expected_mean / 2),
"Counts for each value shouldn't be too far away from mean: {:?}",
frequencies
);
}
}
9 changes: 7 additions & 2 deletions signature/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ ark-ec = { workspace = true }
ark-ff = { workspace = true }
ark-serialize = { workspace = true }
ark-std = { workspace = true }
blst = { version = "0.3.11", default-features = false }
blst = { version = "0.3.13", default-features = false }
derivative = { workspace = true }
digest = { workspace = true }
displaydoc = { workspace = true }
Expand All @@ -32,7 +32,12 @@ num-traits = { version = "0.2.15", default-features = false }
serde = { workspace = true }
sha3 = { workspace = true }
tagged-base64 = { workspace = true }
zeroize = { version = "1.5", default-features = false }
zeroize = { workspace = true }

[target.wasm32-unknown-unknown.dependencies]
# not directly used, but used by every member crate via ark-std
# since we can't specify [target] in workspace Cargo.toml, specify here
getrandom = { version = "^0.2", features = ["js"] }

[dev-dependencies]
ark-ed-on-bls12-377 = "0.4.0"
Expand Down
6 changes: 3 additions & 3 deletions vid/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ ark-std = { workspace = true }
derivative = { workspace = true }
digest = { workspace = true }
displaydoc = { workspace = true }
generic-array = { version = "0", features = [
"serde",
] } # not a direct dependency, but we need serde
# not a direct dependency, but we need serde;
# inherited from digest-v0.10.7->crypto_common->generic-array
generic-array = { version = "0.14.6", features = ["more_lengths", "serde"] }
itertools = { workspace = true, features = ["use_alloc"] }
jf-merkle-tree = { version = "0.1.0", git = "https://github.com/EspressoSystems/jellyfish", tag = "0.4.5", default-features = false }
jf-pcs = { version = "0.1.0", git = "https://github.com/EspressoSystems/jellyfish", tag = "0.4.5", default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion vrf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ displaydoc = { workspace = true }
jf-signature = { git = "https://github.com/EspressoSystems/jellyfish", tag = "jf-signature-v0.2.0", default-features = false, features = [ "bls" ] }
serde = { workspace = true }
sha2 = { workspace = true }
zeroize = { version = "1.5", default-features = false }
zeroize = { workspace = true }

[dev-dependencies]
jf-utils = { version = "0.4.4", git = "https://github.com/EspressoSystems/jellyfish", tag = "0.4.5", default-features = false }
Expand Down

0 comments on commit fdd8e44

Please sign in to comment.