Skip to content

Commit 98d7d8c

Browse files
ColoCarletticdesaintguilhemdiegokingstonNicole
authored
Fix sample field element for default transcript. (#976)
* add new trait * impl default transcript for montgomery backend * fix plonk * refactor * add babybear test * add tests * fmt * add trait to sumcheck prover * rm Vec * rm serde-binary * impl HasDefaultTranscript for u64prmeField * fmt * Update math/src/field/traits.rs Co-authored-by: Cyprien de Saint Guilhem <[email protected]> * Update math/src/field/fields/u64_prime_field.rs Co-authored-by: Cyprien de Saint Guilhem <[email protected]> * change seed for rng * fix clippy * fix clippy * change rand version * redo rand version * fix range in u64 * fix target wasm32 * change dependencies rand no std --------- Co-authored-by: Cyprien de Saint Guilhem <[email protected]> Co-authored-by: diegokingston <[email protected]> Co-authored-by: Nicole <[email protected]>
1 parent 1d57286 commit 98d7d8c

File tree

11 files changed

+221
-21
lines changed

11 files changed

+221
-21
lines changed

crypto/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ serde = { version = "1.0", default-features = false, features = [
1717
"alloc",
1818
], optional = true }
1919
rayon = { version = "1.8.0", optional = true }
20+
rand = { version = "0.8.5", default-features = false }
21+
rand_chacha = { version = "0.3.1", default-features = false }
22+
2023
[dev-dependencies]
2124
criterion = "0.4"
2225
iai-callgrind.workspace = true

crypto/src/fiat_shamir/default_transcript.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
use super::is_transcript::IsTranscript;
22
use core::marker::PhantomData;
33
use lambdaworks_math::{
4-
field::{element::FieldElement, traits::IsField},
4+
field::{element::FieldElement, traits::HasDefaultTranscript},
55
traits::ByteConversion,
66
};
7+
use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng};
78
use sha3::{Digest, Keccak256};
89

9-
pub struct DefaultTranscript<F: IsField> {
10+
pub struct DefaultTranscript<F: HasDefaultTranscript> {
1011
hasher: Keccak256,
1112
phantom: PhantomData<F>,
1213
}
1314

1415
impl<F> DefaultTranscript<F>
1516
where
16-
F: IsField,
17+
F: HasDefaultTranscript,
1718
FieldElement<F>: ByteConversion,
1819
{
1920
pub fn new(data: &[u8]) -> Self {
@@ -36,7 +37,7 @@ where
3637

3738
impl<F> Default for DefaultTranscript<F>
3839
where
39-
F: IsField,
40+
F: HasDefaultTranscript,
4041
FieldElement<F>: ByteConversion,
4142
{
4243
fn default() -> Self {
@@ -46,7 +47,7 @@ where
4647

4748
impl<F> IsTranscript<F> for DefaultTranscript<F>
4849
where
49-
F: IsField,
50+
F: HasDefaultTranscript,
5051
FieldElement<F>: ByteConversion,
5152
{
5253
fn append_bytes(&mut self, new_bytes: &[u8]) {
@@ -62,7 +63,8 @@ where
6263
}
6364

6465
fn sample_field_element(&mut self) -> FieldElement<F> {
65-
FieldElement::from_bytes_be(&self.sample()).unwrap()
66+
let mut rng = <ChaCha20Rng as SeedableRng>::from_seed(self.sample());
67+
F::get_random_field_element_from_rng(&mut rng)
6668
}
6769

6870
fn sample_u64(&mut self, upper_bound: u64) -> u64 {
@@ -74,7 +76,6 @@ where
7476
mod tests {
7577
use super::*;
7678

77-
extern crate alloc;
7879
use alloc::vec::Vec;
7980
use lambdaworks_math::elliptic_curve::short_weierstrass::curves::bls12_381::default_types::FrField;
8081

math/Cargo.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ serde_json = { version = "1.0", default-features = false, features = [
1717
proptest = { version = "1.1.0", optional = true }
1818
winter-math = { package = "winter-math", version = "0.6.4", default-features = false, optional = true }
1919
miden-core = { package = "miden-core", version = "0.7", default-features = false, optional = true }
20+
rand = { version = "0.8.5", default-features = false }
2021

2122
# rayon
2223
rayon = { version = "1.7", optional = true }
@@ -28,11 +29,9 @@ objc = { version = "0.2.7", optional = true }
2829
# cuda
2930
cudarc = { version = "0.9.7", optional = true }
3031

31-
3232
lambdaworks-gpu = { workspace = true, optional = true }
3333

3434
[dev-dependencies]
35-
rand = { version = "0.8.5", features = ["std"] }
3635
rand_chacha = "0.3.1"
3736
criterion = "0.5.1"
3837
const-random = "0.1.15"
@@ -41,6 +40,8 @@ proptest = "1.1.0"
4140
pprof = { version = "0.13.0", features = ["criterion", "flamegraph"] }
4241
p3-baby-bear = { git = "https://github.com/Plonky3/Plonky3" }
4342
p3-field = { git = "https://github.com/Plonky3/Plonky3" }
43+
rand = { version = "0.8.5", features = ["std"] }
44+
4445
[features]
4546
default = ["parallel", "std"]
4647
std = ["alloc", "serde?/std", "serde_json?/std"]
@@ -52,6 +53,7 @@ proptest = ["dep:proptest"]
5253
winter_compatibility = ["winter-math", "miden-core"]
5354
instruments = []
5455

56+
5557
# gpu
5658
metal = [
5759
"dep:metal",
@@ -61,6 +63,9 @@ metal = [
6163
]
6264
cuda = ["dep:cudarc", "dep:lambdaworks-gpu"]
6365

66+
[target.wasm32-unknown-unknown.dependencies]
67+
getrandom = { version = "0.2.15", features = ["js"] }
68+
6469
[[bench]]
6570
name = "criterion_elliptic_curve"
6671
harness = false

math/src/field/fields/fft_friendly/quartic_babybear.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::field::{
22
element::FieldElement,
33
errors::FieldError,
44
fields::fft_friendly::babybear::Babybear31PrimeField,
5-
traits::{IsFFTField, IsField, IsSubFieldOf},
5+
traits::{HasDefaultTranscript, IsFFTField, IsField, IsSubFieldOf},
66
};
77

88
use crate::traits::ByteConversion;
@@ -330,6 +330,39 @@ impl IsFFTField for Degree4BabyBearExtensionField {
330330
];
331331
}
332332

333+
impl HasDefaultTranscript for Degree4BabyBearExtensionField {
334+
fn get_random_field_element_from_rng(rng: &mut impl rand::Rng) -> FieldElement<Self> {
335+
//Babybear Prime p = 2^31 - 2^27 + 1
336+
const MODULUS: u64 = 2013265921;
337+
338+
//Babybear prime needs 31 bits and is represented with 32 bits.
339+
//The mask is used to remove the first bit.
340+
const MASK: u64 = 0x7FFF_FFFF;
341+
342+
let mut sample = [0u8; 8];
343+
344+
let mut coeffs = [
345+
FieldElement::from(0u64),
346+
FieldElement::from(0u64),
347+
FieldElement::from(0u64),
348+
FieldElement::from(0u64),
349+
];
350+
351+
for coeff in &mut coeffs {
352+
loop {
353+
rng.fill(&mut sample);
354+
let int_sample = u64::from_be_bytes(sample) & MASK;
355+
if int_sample < MODULUS {
356+
*coeff = FieldElement::from(int_sample);
357+
break;
358+
}
359+
}
360+
}
361+
362+
FieldElement::<Self>::new(coeffs)
363+
}
364+
}
365+
333366
#[cfg(test)]
334367
mod tests {
335368
use super::*;

math/src/field/fields/montgomery_backed_prime_fields.rs

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::field::element::FieldElement;
22
use crate::field::errors::FieldError;
3-
use crate::field::traits::IsPrimeField;
3+
use crate::field::traits::{HasDefaultTranscript, IsPrimeField};
44
#[cfg(feature = "alloc")]
55
use crate::traits::AsBytes;
66
use crate::traits::ByteConversion;
@@ -385,6 +385,47 @@ where
385385
}
386386
}
387387

388+
impl<M, const NUM_LIMBS: usize> HasDefaultTranscript for MontgomeryBackendPrimeField<M, NUM_LIMBS>
389+
where
390+
M: IsModulus<UnsignedInteger<NUM_LIMBS>> + Clone + Debug,
391+
{
392+
/// # Panics
393+
///
394+
/// This function will panic if NUM_LIMBS is greater than 6.
395+
fn get_random_field_element_from_rng(
396+
rng: &mut impl rand::Rng,
397+
) -> FieldElement<MontgomeryBackendPrimeField<M, NUM_LIMBS>> {
398+
let mut buffer = [0u8; 6 * 8];
399+
let first_non_zero_limb_index = M::MODULUS
400+
.limbs
401+
.iter()
402+
.position(|&x| x != 0)
403+
.expect("modulus should be non-zero");
404+
let mask = u64::MAX >> M::MODULUS.limbs[first_non_zero_limb_index].leading_zeros();
405+
406+
let bits_start_idx = first_non_zero_limb_index * 8;
407+
let bits_end_idx = NUM_LIMBS * 8;
408+
let mut uint_sample;
409+
410+
loop {
411+
let sample_bytes = &mut buffer[bits_start_idx..bits_end_idx];
412+
rng.fill(sample_bytes);
413+
414+
uint_sample = UnsignedInteger::from_bytes_be(&buffer).unwrap();
415+
416+
uint_sample.limbs[first_non_zero_limb_index] &= mask;
417+
418+
if uint_sample < M::MODULUS {
419+
break;
420+
}
421+
}
422+
423+
FieldElement::new(MontgomeryBackendPrimeField::<M, NUM_LIMBS>::from_base_type(
424+
uint_sample,
425+
))
426+
}
427+
}
428+
388429
#[cfg(test)]
389430
mod tests_u384_prime_fields {
390431
use crate::field::element::FieldElement;
@@ -393,13 +434,17 @@ mod tests_u384_prime_fields {
393434
use crate::field::fields::montgomery_backed_prime_fields::{
394435
IsModulus, U256PrimeField, U384PrimeField,
395436
};
437+
use crate::field::traits::HasDefaultTranscript;
396438
use crate::field::traits::IsField;
397439
use crate::field::traits::IsPrimeField;
398440
#[cfg(feature = "alloc")]
399441
use crate::traits::ByteConversion;
400442
use crate::unsigned_integer::element::U384;
401443
use crate::unsigned_integer::element::{UnsignedInteger, U256};
402444

445+
use rand::Rng;
446+
use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng};
447+
403448
#[derive(Clone, Debug)]
404449
struct U384Modulus23;
405450
impl IsModulus<U384> for U384Modulus23 {
@@ -644,6 +689,48 @@ mod tests_u384_prime_fields {
644689
assert_eq!(-&zero, zero);
645690
}
646691

692+
#[test]
693+
fn test_random_field_element_from_rng_0() {
694+
// This seed generates a sample that is less than the modulus;
695+
let mut rng = <ChaCha20Rng as SeedableRng>::from_seed([1; 32]);
696+
let mut expected_rng = rng.clone();
697+
698+
let mut buffer = [0u8; 48];
699+
let sample_bytes = &mut buffer[40..];
700+
expected_rng.fill(sample_bytes);
701+
702+
let mut expected_uint = UnsignedInteger::from_bytes_be(&buffer).unwrap();
703+
704+
expected_uint.limbs[5] &= 31_u64;
705+
706+
let expected = FieldElement::new(U384F23::from_base_type(expected_uint));
707+
708+
let result = U384F23::get_random_field_element_from_rng(&mut rng);
709+
710+
assert_eq!(result, expected);
711+
}
712+
713+
#[test]
714+
fn test_random_field_element_from_rng_1() {
715+
// This seed generates a sample that is grater than the modulus;
716+
let mut rng = <ChaCha20Rng as SeedableRng>::from_seed([5; 32]);
717+
let mut expected_rng = rng.clone();
718+
719+
let mut buffer = [0u8; 48];
720+
let sample_bytes = &mut buffer[40..];
721+
expected_rng.fill(sample_bytes);
722+
723+
let mut expected_uint = UnsignedInteger::from_bytes_be(&buffer).unwrap();
724+
725+
expected_uint.limbs[5] &= 31_u64;
726+
727+
let expected = FieldElement::new(U384F23::from_base_type(expected_uint));
728+
729+
let result = U384F23::get_random_field_element_from_rng(&mut rng);
730+
731+
assert_ne!(result, expected);
732+
}
733+
647734
// FP1
648735
#[derive(Clone, Debug)]
649736
struct U384ModulusP1;
@@ -788,12 +875,15 @@ mod tests_u256_prime_fields {
788875
use crate::field::element::FieldElement;
789876
use crate::field::errors::FieldError;
790877
use crate::field::fields::montgomery_backed_prime_fields::{IsModulus, U256PrimeField};
878+
use crate::field::traits::HasDefaultTranscript;
791879
use crate::field::traits::IsField;
792880
use crate::field::traits::IsPrimeField;
793881
#[cfg(feature = "alloc")]
794882
use crate::traits::ByteConversion;
795883
use crate::unsigned_integer::element::U256;
796884
use crate::unsigned_integer::element::{UnsignedInteger, U64};
885+
use rand::Rng;
886+
use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng};
797887

798888
use super::U64PrimeField;
799889

@@ -992,7 +1082,48 @@ mod tests_u256_prime_fields {
9921082
assert_eq!(-&zero, zero);
9931083
}
9941084

995-
// FP1
1085+
#[test]
1086+
fn test_random_field_element_from_rng_0() {
1087+
// This seed generates a sample that is less than the modulus;
1088+
let mut rng = <ChaCha20Rng as SeedableRng>::from_seed([1; 32]);
1089+
let mut expected_rng = rng.clone();
1090+
1091+
let mut buffer = [0u8; 48];
1092+
let sample_bytes = &mut buffer[24..32];
1093+
expected_rng.fill(sample_bytes);
1094+
1095+
let mut expected_uint = UnsignedInteger::from_bytes_be(&buffer).unwrap();
1096+
1097+
expected_uint.limbs[3] &= 31_u64;
1098+
1099+
let expected = FieldElement::new(U256F29::from_base_type(expected_uint));
1100+
1101+
let result = U256F29::get_random_field_element_from_rng(&mut rng);
1102+
1103+
assert_eq!(result, expected);
1104+
}
1105+
1106+
#[test]
1107+
fn test_random_field_element_from_rng_1() {
1108+
// This seed generates a sample that is grater than the modulus;
1109+
let mut rng = <ChaCha20Rng as SeedableRng>::from_seed([5; 32]);
1110+
let mut expected_rng = rng.clone();
1111+
1112+
let mut buffer = [0u8; 48];
1113+
let sample_bytes = &mut buffer[24..32];
1114+
expected_rng.fill(sample_bytes);
1115+
1116+
let mut expected_uint = UnsignedInteger::from_bytes_be(&buffer).unwrap();
1117+
1118+
expected_uint.limbs[3] &= 31_u64;
1119+
1120+
let expected = FieldElement::new(U256F29::from_base_type(expected_uint));
1121+
1122+
let result = U256F29::get_random_field_element_from_rng(&mut rng);
1123+
1124+
assert_ne!(result, expected);
1125+
}
1126+
9961127
#[derive(Clone, Debug)]
9971128
struct U256ModulusP1;
9981129
impl IsModulus<U256> for U256ModulusP1 {

math/src/field/fields/u64_prime_field.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::errors::CreationError;
44
use crate::errors::DeserializationError;
55
use crate::field::element::FieldElement;
66
use crate::field::errors::FieldError;
7-
use crate::field::traits::{IsFFTField, IsField, IsPrimeField};
7+
use crate::field::traits::{HasDefaultTranscript, IsFFTField, IsField, IsPrimeField};
88
use crate::traits::{ByteConversion, Deserializable};
99

1010
/// Type representing prime fields over unsigned 64-bit integers.
@@ -153,6 +153,23 @@ impl<const MODULUS: u64> Deserializable for FieldElement<U64PrimeField<MODULUS>>
153153
}
154154
}
155155

156+
impl<const MODULUS: u64> HasDefaultTranscript for U64PrimeField<MODULUS> {
157+
fn get_random_field_element_from_rng(rng: &mut impl rand::Rng) -> FieldElement<Self> {
158+
let mask = u64::MAX >> MODULUS.leading_zeros();
159+
let mut sample = [0u8; 8];
160+
let field;
161+
loop {
162+
rng.fill(&mut sample);
163+
let int_sample = u64::from_be_bytes(sample) & mask;
164+
if int_sample < MODULUS {
165+
field = FieldElement::from(int_sample);
166+
break;
167+
}
168+
}
169+
field
170+
}
171+
}
172+
156173
#[cfg(test)]
157174
mod tests {
158175
use super::*;

math/src/field/traits.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,3 +282,10 @@ pub trait IsPrimeField: IsField {
282282
Some((x, neg_x))
283283
}
284284
}
285+
286+
/// This trait is necessary for sampling a random field element with a uniform distribution.
287+
pub trait HasDefaultTranscript: IsField {
288+
/// This function should truncates the sampled bits to the quantity required to represent the order of the base field
289+
/// and returns a field element.
290+
fn get_random_field_element_from_rng(rng: &mut impl rand::Rng) -> FieldElement<Self>;
291+
}

0 commit comments

Comments
 (0)