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

ecgfp5: add hash_to_scalar #33

Merged
merged 1 commit into from
Jun 13, 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
14 changes: 13 additions & 1 deletion ecgfp5/src/curve/scalar_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@ 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;

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;

Expand Down Expand Up @@ -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<GoldilocksField>) -> 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
Expand Down
1 change: 1 addition & 0 deletions ecgfp5/src/gadgets/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod base_field;
pub mod curve;
pub mod poseidon;
pub mod scalar_field;
pub mod schnorr;
95 changes: 95 additions & 0 deletions ecgfp5/src/gadgets/poseidon.rs
Original file line number Diff line number Diff line change
@@ -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<GoldilocksField, 2>,
domain: &[u8],
msg: Vec<Target>,
) -> NonNativeTarget<Scalar> {
let mut preimage = vec![];
let f_domain: Vec<Target> =
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::<PoseidonHash>(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<GoldilocksField> {
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::<Vec<GoldilocksField>>()
}

#[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 = <C as GenericConfig<D>>::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::<F, D>::new(config);
let msg_target: Vec<Target> =
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::<C>();
let mut pw = PartialWitness::new();
pw.set_nonnative_target(scalar_target, scalar);
let proof = circuit.prove(pw).unwrap();
circuit.verify(proof).unwrap();
}
}