From 2e18d4bd115d264bb7f2306d69726bf28ee0f57a Mon Sep 17 00:00:00 2001 From: "Jason.Huang" Date: Thu, 13 Jun 2024 17:02:42 +0800 Subject: [PATCH] add hash_to_scalar --- ecgfp5/src/curve/scalar_field.rs | 14 ++++- ecgfp5/src/gadgets/mod.rs | 1 + ecgfp5/src/gadgets/poseidon.rs | 95 ++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 ecgfp5/src/gadgets/poseidon.rs diff --git a/ecgfp5/src/curve/scalar_field.rs b/ecgfp5/src/curve/scalar_field.rs index 344695f891..05c860c516 100644 --- a/ecgfp5/src/curve/scalar_field.rs +++ b/ecgfp5/src/curve/scalar_field.rs @@ -9,6 +9,7 @@ use core::{ iter::{Product, Sum}, ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}, }; +use plonky2::hash::hash_types::HashOut; use plonky2_field::extension::quintic::QuinticExtension; use rand::RngCore; @@ -16,7 +17,11 @@ use itertools::Itertools; use num::{bigint::BigUint, One}; use serde::{Deserialize, Serialize}; -use plonky2_field::types::{Field, PrimeField, PrimeField64, Sample}; +use plonky2_field::{ + extension::FieldExtension, + goldilocks_field::GoldilocksField, + types::{Field, PrimeField, PrimeField64, Sample}, +}; use super::GFp5; @@ -462,6 +467,13 @@ impl Scalar { Self::from_noncanonical_biguint(biguint_from_array(limbs.map(|l| l.to_canonical_u64()))) } + pub fn from_hashout(x: HashOut) -> Self { + let mut arr: [GoldilocksField; 5] = [GoldilocksField::ZERO; 5]; + arr[1..].copy_from_slice(&x.elements); + let gfp5 = GFp5::from_basefield_array(arr); + Self::from_gfp5(gfp5) + } + /// Decode the provided byte slice into a scalar. The bytes are /// interpreted into an integer in little-endian unsigned convention. /// All slice bytes are read, and the value is REDUCED modulo n. This diff --git a/ecgfp5/src/gadgets/mod.rs b/ecgfp5/src/gadgets/mod.rs index 2d2ef235a4..2a9d08dc1d 100644 --- a/ecgfp5/src/gadgets/mod.rs +++ b/ecgfp5/src/gadgets/mod.rs @@ -1,4 +1,5 @@ pub mod base_field; pub mod curve; +pub mod poseidon; pub mod scalar_field; pub mod schnorr; diff --git a/ecgfp5/src/gadgets/poseidon.rs b/ecgfp5/src/gadgets/poseidon.rs new file mode 100644 index 0000000000..0c73667385 --- /dev/null +++ b/ecgfp5/src/gadgets/poseidon.rs @@ -0,0 +1,95 @@ +use crate::curve::scalar_field::Scalar; +use plonky2::{ + hash::poseidon::PoseidonHash, + iop::target::Target, + plonk::{circuit_builder::CircuitBuilder, config::Hasher}, +}; +use plonky2_ecdsa::gadgets::nonnative::NonNativeTarget; +use plonky2_field::{goldilocks_field::GoldilocksField, types::Field}; + +use super::base_field::{CircuitBuilderGFp5, QuinticExtensionTarget}; + +pub fn hash_to_scalar(domain: &[u8], msg: &[u8]) -> Scalar { + let f_domain = u8_to_goldilocks(domain); + let f_msg = u8_to_goldilocks(msg); + let hashout = PoseidonHash::hash_no_pad(&[f_domain.as_slice(), f_msg.as_slice()].concat()); + Scalar::from_hashout(hashout) +} + +pub fn hash_to_scalar_target( + builder: &mut CircuitBuilder, + domain: &[u8], + msg: Vec, +) -> NonNativeTarget { + let mut preimage = vec![]; + let f_domain: Vec = + u8_to_goldilocks(domain).iter().map(|x| builder.constant(*x)).collect(); + preimage.extend(f_domain); + preimage.extend(msg); + let hashout = builder.hash_n_to_hash_no_pad::(preimage); + let mut limbs = [builder.zero(); 5]; + limbs[1..].copy_from_slice(&hashout.elements); + let result = QuinticExtensionTarget::new(limbs); + builder.encode_quintic_ext_as_scalar(result) +} + +/// Convert [u8; 8] to one GoldilocksField +/// +/// non-canoncial [u8; 8] will panic +pub fn u8_to_goldilocks(data: &[u8]) -> Vec { + const CHUNK_SIZE: usize = 8; + data.chunks(CHUNK_SIZE) + .map(|chunk| { + let mut padded = [0u8; CHUNK_SIZE]; + let len = chunk.len().min(CHUNK_SIZE); + padded[..len].copy_from_slice(&chunk[..len]); + GoldilocksField::from_canonical_u64(u64::from_le_bytes(padded)) + }) + .collect::>() +} + +#[cfg(test)] +mod tests { + use plonky2::{ + iop::witness::PartialWitness, + plonk::{ + circuit_data::CircuitConfig, + config::{GenericConfig, PoseidonGoldilocksConfig}, + }, + }; + use plonky2_ecdsa::gadgets::nonnative::PartialWitnessNonNative; + use plonky2_field::types::Field64; + + use super::*; + + #[test] + #[should_panic] + fn test_u8_to_goldilocks_noncanonical() { + let order_plus_one = (GoldilocksField::ORDER + 1).to_le_bytes(); + // noncanonical u64 will panic + u8_to_goldilocks(&order_plus_one); + } + + #[test] + fn test_hash_to_scalar() { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let domain = b"domain-plonky2-ecgfp5-poseidon"; + let msg = b"msg-to-hash-to-scalar"; + let scalar = hash_to_scalar(domain, msg); + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + let msg_target: Vec = + u8_to_goldilocks(msg).iter().map(|x| builder.constant(*x)).collect(); + let scalar_target = hash_to_scalar_target(&mut builder, domain, msg_target); + + let circuit = builder.build::(); + let mut pw = PartialWitness::new(); + pw.set_nonnative_target(scalar_target, scalar); + let proof = circuit.prove(pw).unwrap(); + circuit.verify(proof).unwrap(); + } +}