From d06082b308650eb7af7e2e82f083bfd039a9b5d0 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Mon, 6 Jan 2025 18:43:03 +0100 Subject: [PATCH 1/2] Mina-poseidon: top-level documentation to ues the library The interface to instantiate a sponge is not straightforward to use. This commit adds an example at the top-level of the library to guide the users. --- poseidon/src/lib.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/poseidon/src/lib.rs b/poseidon/src/lib.rs index e2ff0f1e60..5b023d0ba2 100644 --- a/poseidon/src/lib.rs +++ b/poseidon/src/lib.rs @@ -1,3 +1,28 @@ +//! This crate provides a generic implementation of the Poseidon hash function. +//! It provides a [Sponge](crate::sponge::FqSponge) trait that can be +//! implemented for any field. +//! +//! Some parameters for the Pasta fields are provided in the sub-crate +//! [crate::pasta]. +//! +//! To instantiate an object that can be used to generate challenges for the +//! Fiat-Shamir transformation, use the +//! [DefaultFqSponge](crate::sponge::DefaultFqSponge) struct. For instance, to +//! instantiate with the parameters used by the Mina hard-fork called Berkeley, +//! use: +//! ```rust +//! use mina_curves::pasta::{VestaParameters}; +//! use mina_poseidon::sponge::DefaultFqSponge; +//! use mina_poseidon::FqSponge; +//! use mina_poseidon::constants::PlonkSpongeConstantsKimchi; +//! use mina_poseidon::pasta::fq_kimchi; +//! +//! let mut sponge = DefaultFqSponge::::new( +//! fq_kimchi::static_params(), +//! ); +//! let challenge = sponge.challenge(); +//! ``` + pub mod constants; pub mod dummy_values; pub mod pasta; From 9b5c4061f9cde4938799a97ce0667e616ea84f96 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Mon, 6 Jan 2025 18:44:24 +0100 Subject: [PATCH 2/2] Mina-poseidon: regression tests + check challenge is 128 bits It is not straightforward by reading the documentation that the method `challenge` outputs a 128 bits value for Pallas/Vesta. Therefore, adding a test to be sure this invariant is respected over time. This commit also adds a regression test when squeezing the empty state. --- poseidon/tests/poseidon_tests.rs | 74 ++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 8 deletions(-) diff --git a/poseidon/tests/poseidon_tests.rs b/poseidon/tests/poseidon_tests.rs index 61345b2298..37cafe940f 100644 --- a/poseidon/tests/poseidon_tests.rs +++ b/poseidon/tests/poseidon_tests.rs @@ -1,10 +1,14 @@ -use mina_curves::pasta::Fp; +use ark_ff::{Field, UniformRand}; +use mina_curves::pasta::{Fp, Fq, PallasParameters, VestaParameters}; use mina_poseidon::{ constants::{PlonkSpongeConstantsKimchi, PlonkSpongeConstantsLegacy}, - pasta::{fp_kimchi as SpongeParametersKimchi, fp_legacy as SpongeParametersLegacy}, + pasta::{fp_kimchi, fp_legacy, fq_kimchi}, poseidon::{ArithmeticSponge as Poseidon, Sponge as _}, + sponge::DefaultFqSponge, + FqSponge as _, }; use o1_utils::FieldHelpers; +use rand::Rng; use serde::Deserialize; use std::{fs::File, path::PathBuf}; // needed for ::new() sponge @@ -58,9 +62,7 @@ where #[test] fn poseidon_test_vectors_legacy() { fn hash(input: &[Fp]) -> Fp { - let mut hash = Poseidon::::new( - SpongeParametersLegacy::static_params(), - ); + let mut hash = Poseidon::::new(fp_legacy::static_params()); hash.absorb(input); hash.squeeze() } @@ -70,11 +72,67 @@ fn poseidon_test_vectors_legacy() { #[test] fn poseidon_test_vectors_kimchi() { fn hash(input: &[Fp]) -> Fp { - let mut hash = Poseidon::::new( - SpongeParametersKimchi::static_params(), - ); + let mut hash = Poseidon::::new(fp_kimchi::static_params()); hash.absorb(input); hash.squeeze() } test_vectors("kimchi.json", hash); } + +#[test] +fn test_regression_challenge_empty_vesta_kimchi() { + let mut sponge = DefaultFqSponge::::new( + fq_kimchi::static_params(), + ); + let output = sponge.challenge(); + let exp_output = + Fp::from_hex("c1e504c0184cce70a605d2f942d579c500000000000000000000000000000000").unwrap(); + assert_eq!(output, exp_output); +} + +#[test] +fn test_regression_challenge_empty_pallas_kimchi() { + let mut sponge = DefaultFqSponge::::new( + fp_kimchi::static_params(), + ); + let output = sponge.challenge(); + let exp_output = + Fq::from_hex("a8eb9ee0f30046308abbfa5d20af73c800000000000000000000000000000000").unwrap(); + assert_eq!(output, exp_output); +} + +#[test] +fn test_poseidon_vesta_kimchi_challenge_is_squeezed_to_128_bits() { + // Test that the challenge is less than 2^128, i.e. the sponge state is + // squeezed to 128 bits + let mut sponge = DefaultFqSponge::::new( + fq_kimchi::static_params(), + ); + let mut rng = o1_utils::tests::make_test_rng(None); + let random_n = rng.gen_range(1..50); + let random_fq_vec = (0..random_n) + .map(|_| Fq::rand(&mut rng)) + .collect::>(); + sponge.absorb_fq(&random_fq_vec); + let challenge = sponge.challenge(); + let two_128 = Fp::from(2).pow([128]); + assert!(challenge < two_128); +} + +#[test] +fn test_poseidon_pallas_kimchi_challenge_is_squeezed_to_128_bits() { + // Test that the challenge is less than 2^128, i.e. the sponge state is + // squeezed to 128 bits + let mut sponge = DefaultFqSponge::::new( + fp_kimchi::static_params(), + ); + let mut rng = o1_utils::tests::make_test_rng(None); + let random_n = rng.gen_range(1..50); + let random_fp_vec = (0..random_n) + .map(|_| Fp::rand(&mut rng)) + .collect::>(); + sponge.absorb_fq(&random_fp_vec); + let challenge = sponge.challenge(); + let two_128 = Fq::from(2).pow([128]); + assert!(challenge < two_128); +}