Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Poseidon2-based Sponge function #713

Merged
merged 5 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also have a version number bump for jf-merkle-tree after CRHF is done?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I'll introduce the CRHF in the next PR, then another one after that to bump jf-merkle-tree

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
Loading