From 264f29b5783f63b4553775ca6fee0a3a174096ba Mon Sep 17 00:00:00 2001 From: Mario Rugiero Date: Thu, 29 Aug 2024 10:42:44 -0300 Subject: [PATCH 1/6] fix: avoid `panic` on `MerkleTree::build` for empty leaves (#896) * fix: avoid `panic` on `MerkleTree::build` for empty leaves Changes the interface to return `None` when no leaves are passed. * cargo fmt * fix: avoid panic on one-element tree * fix: build merkle example * Add missing newline --------- Co-authored-by: MauroFab --- README.md | 2 +- .../src/merkle_tree/backends/field_element.rs | 12 +++++++---- .../backends/field_element_vector.rs | 15 ++++++++----- crypto/src/merkle_tree/merkle.rs | 21 +++++++++++++------ crypto/src/merkle_tree/proof.rs | 8 +++---- crypto/src/merkle_tree/utils.rs | 14 ++++++++----- examples/merkle-tree-cli/src/main.rs | 6 ++++-- provers/stark/src/fri/mod.rs | 2 +- provers/stark/src/prover.rs | 2 +- 9 files changed, 53 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index d08143210..18515f996 100644 --- a/README.md +++ b/README.md @@ -203,4 +203,4 @@ The following links, repos, companies and projects have been important in the de - [CAIRO whitepaper](https://eprint.iacr.org/2021/1063.pdf) - [Gnark](https://github.com/Consensys/gnark) - [Constantine](https://github.com/mratsim/constantine) -- [Plonky3](https://github.com/Plonky3/Plonky3) \ No newline at end of file +- [Plonky3](https://github.com/Plonky3/Plonky3) diff --git a/crypto/src/merkle_tree/backends/field_element.rs b/crypto/src/merkle_tree/backends/field_element.rs index fddc23630..68fed0b6d 100644 --- a/crypto/src/merkle_tree/backends/field_element.rs +++ b/crypto/src/merkle_tree/backends/field_element.rs @@ -91,7 +91,8 @@ mod tests { #[test] fn hash_data_field_element_backend_works_with_keccak_256() { let values: Vec = (1..6).map(FE::from).collect(); - let merkle_tree = MerkleTree::>::build(&values); + let merkle_tree = + MerkleTree::>::build(&values).unwrap(); let proof = merkle_tree.get_proof_by_pos(0).unwrap(); assert!(proof.verify::>( &merkle_tree.root, @@ -103,7 +104,8 @@ mod tests { #[test] fn hash_data_field_element_backend_works_with_sha3_256() { let values: Vec = (1..6).map(FE::from).collect(); - let merkle_tree = MerkleTree::>::build(&values); + let merkle_tree = + MerkleTree::>::build(&values).unwrap(); let proof = merkle_tree.get_proof_by_pos(0).unwrap(); assert!(proof.verify::>( &merkle_tree.root, @@ -115,7 +117,8 @@ mod tests { #[test] fn hash_data_field_element_backend_works_with_keccak_512() { let values: Vec = (1..6).map(FE::from).collect(); - let merkle_tree = MerkleTree::>::build(&values); + let merkle_tree = + MerkleTree::>::build(&values).unwrap(); let proof = merkle_tree.get_proof_by_pos(0).unwrap(); assert!(proof.verify::>( &merkle_tree.root, @@ -127,7 +130,8 @@ mod tests { #[test] fn hash_data_field_element_backend_works_with_sha3_512() { let values: Vec = (1..6).map(FE::from).collect(); - let merkle_tree = MerkleTree::>::build(&values); + let merkle_tree = + MerkleTree::>::build(&values).unwrap(); let proof = merkle_tree.get_proof_by_pos(0).unwrap(); assert!(proof.verify::>( &merkle_tree.root, diff --git a/crypto/src/merkle_tree/backends/field_element_vector.rs b/crypto/src/merkle_tree/backends/field_element_vector.rs index 4c042e4aa..8d5f132dd 100644 --- a/crypto/src/merkle_tree/backends/field_element_vector.rs +++ b/crypto/src/merkle_tree/backends/field_element_vector.rs @@ -111,7 +111,8 @@ mod tests { vec![FE::from(8u64), FE::from(19u64)], vec![FE::from(9u64), FE::from(21u64)], ]; - let merkle_tree = MerkleTree::>::build(&values); + let merkle_tree = + MerkleTree::>::build(&values).unwrap(); let proof = merkle_tree.get_proof_by_pos(0).unwrap(); assert!(proof.verify::>( &merkle_tree.root, @@ -132,7 +133,8 @@ mod tests { vec![FE::from(8u64), FE::from(19u64)], vec![FE::from(9u64), FE::from(21u64)], ]; - let merkle_tree = MerkleTree::>::build(&values); + let merkle_tree = + MerkleTree::>::build(&values).unwrap(); let proof = merkle_tree.get_proof_by_pos(0).unwrap(); assert!(proof.verify::>( &merkle_tree.root, @@ -153,7 +155,8 @@ mod tests { vec![FE::from(8u64), FE::from(19u64)], vec![FE::from(9u64), FE::from(21u64)], ]; - let merkle_tree = MerkleTree::>::build(&values); + let merkle_tree = + MerkleTree::>::build(&values).unwrap(); let proof = merkle_tree.get_proof_by_pos(0).unwrap(); assert!(proof.verify::>( &merkle_tree.root, @@ -174,7 +177,8 @@ mod tests { vec![FE::from(8u64), FE::from(19u64)], vec![FE::from(9u64), FE::from(21u64)], ]; - let merkle_tree = MerkleTree::>::build(&values); + let merkle_tree = + MerkleTree::>::build(&values).unwrap(); let proof = merkle_tree.get_proof_by_pos(0).unwrap(); assert!(proof.verify::>( &merkle_tree.root, @@ -195,7 +199,8 @@ mod tests { vec![FE::from(8u64), FE::from(19u64)], vec![FE::from(9u64), FE::from(21u64)], ]; - let merkle_tree = MerkleTree::>::build(&values); + let merkle_tree = + MerkleTree::>::build(&values).unwrap(); let proof = merkle_tree.get_proof_by_pos(0).unwrap(); assert!(proof.verify::>( &merkle_tree.root, diff --git a/crypto/src/merkle_tree/merkle.rs b/crypto/src/merkle_tree/merkle.rs index 9780602cf..9f4b126d1 100644 --- a/crypto/src/merkle_tree/merkle.rs +++ b/crypto/src/merkle_tree/merkle.rs @@ -30,7 +30,11 @@ impl MerkleTree where B: IsMerkleTreeBackend, { - pub fn build(unhashed_leaves: &[B::Data]) -> Self { + pub fn build(unhashed_leaves: &[B::Data]) -> Option { + if unhashed_leaves.is_empty() { + return None; + } + let hashed_leaves: Vec = B::hash_leaves(unhashed_leaves); //The leaf must be a power of 2 set @@ -45,10 +49,10 @@ where //Build the inner nodes of the tree build::(&mut nodes, leaves_len); - MerkleTree { + Some(MerkleTree { root: nodes[ROOT].clone(), nodes, - } + }) } pub fn get_proof_by_pos(&self, pos: usize) -> Option> { @@ -95,7 +99,7 @@ mod tests { #[test] fn build_merkle_tree_from_a_power_of_two_list_of_values() { let values: Vec = (1..5).map(FE::new).collect(); - let merkle_tree = MerkleTree::>::build(&values); + let merkle_tree = MerkleTree::>::build(&values).unwrap(); assert_eq!(merkle_tree.root, FE::new(7)); // Adjusted expected value } @@ -107,7 +111,7 @@ mod tests { type FE = FieldElement; let values: Vec = (1..6).map(FE::new).collect(); - let merkle_tree = MerkleTree::>::build(&values); + let merkle_tree = MerkleTree::>::build(&values).unwrap(); assert_eq!(merkle_tree.root, FE::new(8)); // Adjusted expected value } @@ -118,7 +122,12 @@ mod tests { type FE = FieldElement; let values: Vec = vec![FE::new(1)]; // Single element - let merkle_tree = MerkleTree::>::build(&values); + let merkle_tree = MerkleTree::>::build(&values).unwrap(); assert_eq!(merkle_tree.root, FE::new(4)); // Adjusted expected value } + + #[test] + fn build_empty_tree_should_not_panic() { + assert!(MerkleTree::>::build(&[]).is_none()); + } } diff --git a/crypto/src/merkle_tree/proof.rs b/crypto/src/merkle_tree/proof.rs index fa46dd968..6f875fa6a 100644 --- a/crypto/src/merkle_tree/proof.rs +++ b/crypto/src/merkle_tree/proof.rs @@ -122,7 +122,7 @@ mod tests { fn create_a_proof_over_value_that_belongs_to_a_given_merkle_tree_when_given_the_leaf_position() { let values: Vec = (1..6).map(FE::new).collect(); - let merkle_tree = MerkleTree::>::build(&values); + let merkle_tree = MerkleTree::>::build(&values).unwrap(); let proof = &merkle_tree.get_proof_by_pos(1).unwrap(); assert_merkle_path(&proof.merkle_path, &[FE::new(2), FE::new(1), FE::new(1)]); assert!(proof.verify::>(&merkle_tree.root, 1, &FE::new(2))); @@ -132,7 +132,7 @@ mod tests { #[cfg(feature = "alloc")] fn merkle_proof_verifies_after_serialization_and_deserialization() { let values: Vec = (1..6).map(Ecgfp5FE::new).collect(); - let merkle_tree = TestMerkleTreeEcgfp::build(&values); + let merkle_tree = TestMerkleTreeEcgfp::build(&values).unwrap(); let proof = merkle_tree.get_proof_by_pos(1).unwrap(); let serialize_proof = proof.serialize(); let proof: TestProofEcgfp5 = Proof::deserialize(&serialize_proof).unwrap(); @@ -142,7 +142,7 @@ mod tests { #[test] fn create_a_merkle_tree_with_10000_elements_and_verify_that_an_element_is_part_of_it() { let values: Vec = (1..10000).map(Ecgfp5FE::new).collect(); - let merkle_tree = TestMerkleTreeEcgfp::build(&values); + let merkle_tree = TestMerkleTreeEcgfp::build(&values).unwrap(); let proof = merkle_tree.get_proof_by_pos(9349).unwrap(); assert!(proof.verify::>(&merkle_tree.root, 9349, &Ecgfp5FE::new(9350))); } @@ -160,7 +160,7 @@ mod tests { type FE = FieldElement; let values: Vec = vec![FE::new(1)]; // Single element - let merkle_tree = MerkleTree::>::build(&values); + let merkle_tree = MerkleTree::>::build(&values).unwrap(); // Update the expected root value based on the actual logic of TestBackend // For example, if combining two `1`s results in `4`, update this accordingly diff --git a/crypto/src/merkle_tree/utils.rs b/crypto/src/merkle_tree/utils.rs index 59c7a769a..33740780e 100644 --- a/crypto/src/merkle_tree/utils.rs +++ b/crypto/src/merkle_tree/utils.rs @@ -47,7 +47,7 @@ where { let mut level_begin_index = leaves_len - 1; let mut level_end_index = 2 * level_begin_index; - loop { + while level_begin_index != level_end_index { let new_level_begin_index = level_begin_index / 2; let new_level_length = level_begin_index - new_level_begin_index; @@ -68,10 +68,6 @@ where level_end_index = level_begin_index - 1; level_begin_index = new_level_begin_index; - - if level_begin_index == level_end_index { - return; - } } } @@ -87,6 +83,14 @@ mod tests { const MODULUS: u64 = 13; type U64PF = U64PrimeField; type FE = FieldElement; + + #[test] + fn build_merkle_tree_one_element_must_succeed() { + let mut nodes = [FE::zero()]; + + build::>(&mut nodes, 1); + } + #[test] // expected |2|4|6|8| fn hash_leaves_from_a_list_of_field_elemnts() { diff --git a/examples/merkle-tree-cli/src/main.rs b/examples/merkle-tree-cli/src/main.rs index b23b3f5e1..8b62eba00 100644 --- a/examples/merkle-tree-cli/src/main.rs +++ b/examples/merkle-tree-cli/src/main.rs @@ -31,7 +31,8 @@ fn load_tree_values(tree_path: &String) -> Result, io::Error> { fn generate_merkle_tree(tree_path: String) -> Result<(), io::Error> { let values: Vec = load_tree_values(&tree_path)?; - let merkle_tree = MerkleTree::>::build(&values); + let merkle_tree = MerkleTree::>::build(&values) + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "requested empty tree"))?; let root = merkle_tree.root.representative().to_string(); println!("Generated merkle tree with root: {:?}", root); @@ -45,7 +46,8 @@ fn generate_merkle_tree(tree_path: String) -> Result<(), io::Error> { fn generate_merkle_proof(tree_path: String, pos: usize) -> Result<(), io::Error> { let values: Vec = load_tree_values(&tree_path)?; - let merkle_tree = MerkleTree::>::build(&values); + let merkle_tree = MerkleTree::>::build(&values) + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "requested empty tree"))?; let Some(proof) = merkle_tree.get_proof_by_pos(pos) else { return Err(io::Error::new(io::ErrorKind::Other, "Index out of bounds")); diff --git a/provers/stark/src/fri/mod.rs b/provers/stark/src/fri/mod.rs index a841a8aea..b0b102319 100644 --- a/provers/stark/src/fri/mod.rs +++ b/provers/stark/src/fri/mod.rs @@ -131,7 +131,7 @@ where to_commit.push(vec![chunk[0].clone(), chunk[1].clone()]); } - let merkle_tree = BatchedMerkleTree::build(&to_commit); + let merkle_tree = BatchedMerkleTree::build(&to_commit).unwrap(); FriLayer::new( &evaluation, diff --git a/provers/stark/src/prover.rs b/provers/stark/src/prover.rs index 7a5a4210e..49ed326ce 100644 --- a/provers/stark/src/prover.rs +++ b/provers/stark/src/prover.rs @@ -178,7 +178,7 @@ pub trait IsStarkProver { E: IsSubFieldOf, A::Field: IsSubFieldOf, { - let tree = BatchedMerkleTree::::build(vectors); + let tree = BatchedMerkleTree::::build(vectors).unwrap(); let commitment = tree.root; (tree, commitment) } From a68d5556eefeb9d0a8c31f81bff6f89ea36b6c55 Mon Sep 17 00:00:00 2001 From: Mauro Toscano <12560266+MauroToscano@users.noreply.github.com> Date: Fri, 30 Aug 2024 13:36:28 -0300 Subject: [PATCH 2/6] Fix and improve fuzzers (#898) * Fix and improve fuzzers * Remove u64 since now they are full fields * Fix mini goldilocks fuzz --- README.md | 8 +++- fuzz/no_gpu_fuzz/Cargo.toml | 10 ++--- .../field/{field_from_hex.rs => from_hex.rs} | 0 .../field/{field_from_raw.rs => from_raw.rs} | 0 .../{field_mersenne31.rs => mersenne31.rs} | 0 ..._mini_goldilocks.rs => mini_goldilocks.rs} | 0 .../field/{field_fuzzer.rs => stark252.rs} | 44 ++++++++++--------- 7 files changed, 35 insertions(+), 27 deletions(-) rename fuzz/no_gpu_fuzz/fuzz_targets/field/{field_from_hex.rs => from_hex.rs} (100%) rename fuzz/no_gpu_fuzz/fuzz_targets/field/{field_from_raw.rs => from_raw.rs} (100%) rename fuzz/no_gpu_fuzz/fuzz_targets/field/{field_mersenne31.rs => mersenne31.rs} (100%) rename fuzz/no_gpu_fuzz/fuzz_targets/field/{field_mini_goldilocks.rs => mini_goldilocks.rs} (100%) rename fuzz/no_gpu_fuzz/fuzz_targets/field/{field_fuzzer.rs => stark252.rs} (65%) diff --git a/README.md b/README.md index 18515f996..dde2ae09d 100644 --- a/README.md +++ b/README.md @@ -136,12 +136,18 @@ This can be used in a multi prover setting for extra security, or as a standalon Fuzzers are divided between the ones that use only the CPU, the ones that use Metal, and the ones that use CUDA. +To use them, make sure you have installed ```cargo fuzzer``` + +You can install it with: + +```cargo install cargo-fuzz``` + CPU Fuzzers can be run with the command ```bash make run-fuzzer FUZZER=fuzzer_name``` For example: ```bash -make run-fuzzer FUZZER=field_from_hex +make run-fuzzer FUZZER=stark252 ``` The list of fuzzers can be found in `fuzz/no_gpu_fuzz` diff --git a/fuzz/no_gpu_fuzz/Cargo.toml b/fuzz/no_gpu_fuzz/Cargo.toml index ec9f53fb3..397c4a139 100644 --- a/fuzz/no_gpu_fuzz/Cargo.toml +++ b/fuzz/no_gpu_fuzz/Cargo.toml @@ -36,19 +36,19 @@ test = false doc = false [[bin]] -name = "field_fuzzer" -path = "fuzz_targets/field/fuzzer.rs" +name = "stark252" +path = "fuzz_targets/field/stark252.rs" test = false doc = false [[bin]] -name = "field_fuzz_mersenne31" +name = "mersenne31" path = "fuzz_targets/field/mersenne31.rs" test = false doc = false [[bin]] -name = "field_mini_goldilocks" +name = "mini_goldilocks" path = "fuzz_targets/field/mini_goldilocks.rs" test = false doc = false @@ -66,7 +66,7 @@ test = false doc = false [[bin]] -name = "stark_field_addition" +name = "stark252_addition" path = "fuzz_targets/field/stark_field_addition.rs" test = false doc = false diff --git a/fuzz/no_gpu_fuzz/fuzz_targets/field/field_from_hex.rs b/fuzz/no_gpu_fuzz/fuzz_targets/field/from_hex.rs similarity index 100% rename from fuzz/no_gpu_fuzz/fuzz_targets/field/field_from_hex.rs rename to fuzz/no_gpu_fuzz/fuzz_targets/field/from_hex.rs diff --git a/fuzz/no_gpu_fuzz/fuzz_targets/field/field_from_raw.rs b/fuzz/no_gpu_fuzz/fuzz_targets/field/from_raw.rs similarity index 100% rename from fuzz/no_gpu_fuzz/fuzz_targets/field/field_from_raw.rs rename to fuzz/no_gpu_fuzz/fuzz_targets/field/from_raw.rs diff --git a/fuzz/no_gpu_fuzz/fuzz_targets/field/field_mersenne31.rs b/fuzz/no_gpu_fuzz/fuzz_targets/field/mersenne31.rs similarity index 100% rename from fuzz/no_gpu_fuzz/fuzz_targets/field/field_mersenne31.rs rename to fuzz/no_gpu_fuzz/fuzz_targets/field/mersenne31.rs diff --git a/fuzz/no_gpu_fuzz/fuzz_targets/field/field_mini_goldilocks.rs b/fuzz/no_gpu_fuzz/fuzz_targets/field/mini_goldilocks.rs similarity index 100% rename from fuzz/no_gpu_fuzz/fuzz_targets/field/field_mini_goldilocks.rs rename to fuzz/no_gpu_fuzz/fuzz_targets/field/mini_goldilocks.rs diff --git a/fuzz/no_gpu_fuzz/fuzz_targets/field/field_fuzzer.rs b/fuzz/no_gpu_fuzz/fuzz_targets/field/stark252.rs similarity index 65% rename from fuzz/no_gpu_fuzz/fuzz_targets/field/field_fuzzer.rs rename to fuzz/no_gpu_fuzz/fuzz_targets/field/stark252.rs index 990af7b8f..ca180cb0b 100644 --- a/fuzz/no_gpu_fuzz/fuzz_targets/field/field_fuzzer.rs +++ b/fuzz/no_gpu_fuzz/fuzz_targets/field/stark252.rs @@ -6,43 +6,47 @@ use lambdaworks_math::field::{ fields::fft_friendly::stark_252_prime_field::Stark252PrimeField }; use ibig::{modular::ModuloRing, UBig}; +use lambdaworks_math::traits::ByteConversion; -fuzz_target!(|values: (u64, u64)| { +fuzz_target!(|bytes: ([u8;32], [u8;32])| { - let (value_u64_a, value_u64_b) = values; let cairo_prime = UBig::from_str_radix("800000000000011000000000000000000000000000000000000000000000001", 16).unwrap(); - let ring = ModuloRing::new(&cairo_prime); + let stark252_ring_prime = ModuloRing::new(&cairo_prime); - let a = FieldElement::::from(value_u64_a); - let b = FieldElement::::from(value_u64_b); + let (bytes_a, bytes_b) = bytes; + let a = FieldElement::from_bytes_be(&bytes_a).unwrap(); + let b = FieldElement::from_bytes_be(&bytes_b).unwrap(); - let a_expected = ring.from(value_u64_a); - let b_expected = ring.from(value_u64_b); + let a_hex = a.to_string()[2..].to_string(); + let b_hex = b.to_string()[2..].to_string(); - let add_u64 = &a + &b; - let addition = &a_expected + &b_expected; + let a_ring = stark252_ring_prime.from(&UBig::from_str_radix(&a_hex, 16).unwrap()); + let b_ring = stark252_ring_prime.from(&UBig::from_str_radix(&b_hex, 16).unwrap()); + + let add = &a + &b; + let addition = &a_ring + &b_ring; - assert_eq!(&(add_u64.to_string())[2..], addition.residue().in_radix(16).to_string()); + assert_eq!(&(add.to_string())[2..], addition.residue().in_radix(16).to_string()); - let sub_u64 = &a - &b; - let substraction = &a_expected - &b_expected; - assert_eq!(&(sub_u64.to_string())[2..], substraction.residue().in_radix(16).to_string()); + let sub = &a - &b; + let substraction = &a_ring - &b_ring; + assert_eq!(&(sub.to_string())[2..], substraction.residue().in_radix(16).to_string()); - let mul_u64 = &a * &b; - let multiplication = &a_expected * &b_expected; - assert_eq!(&(mul_u64.to_string())[2..], multiplication.residue().in_radix(16).to_string()); + let mul = &a * &b; + let multiplication = &a_ring * &b_ring; + assert_eq!(&(mul.to_string())[2..], multiplication.residue().in_radix(16).to_string()); let pow = &a.pow(b.representative()); - let expected_pow = a_expected.pow(&b_expected.residue()); + let expected_pow = a_ring.pow(&b_ring.residue()); assert_eq!(&(pow.to_string())[2..], expected_pow.residue().in_radix(16).to_string()); - if value_u64_b != 0 { + if b != FieldElement::zero() { let div = &a / &b; assert_eq!(&div * &b, a.clone()); - let expected_div = &a_expected / &b_expected; + let expected_div = &a_ring / &b_ring; assert_eq!(&(div.to_string())[2..], expected_div.residue().in_radix(16).to_string()); } @@ -84,6 +88,4 @@ fuzz_target!(|values: (u64, u64)| { if b != zero { assert_eq!(&b * b.inv().unwrap(), one, "Inverse mul b failed"); } - - }); From 9f7b3694702b6d476fa386949936b78b7cf91ab3 Mon Sep 17 00:00:00 2001 From: Diego K <43053772+diegokingston@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:02:00 -0300 Subject: [PATCH 3/6] fix overflow (#894) * fix overflow * fmt * simplify condition * Fix and improve fuzzers * Remove u64 since now they are full fields * Fix mini goldilocks fuzz * Add fuzzer * Fix * Fix * Fix * Fix * Fix --------- Co-authored-by: MauroFab --- fuzz/no_gpu_fuzz/Cargo.toml | 6 + .../fuzz_targets/field/secp256k1.rs | 103 ++++++++++++++++++ .../fields/montgomery_backed_prime_fields.rs | 6 +- math/src/unsigned_integer/montgomery.rs | 4 +- 4 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 fuzz/no_gpu_fuzz/fuzz_targets/field/secp256k1.rs diff --git a/fuzz/no_gpu_fuzz/Cargo.toml b/fuzz/no_gpu_fuzz/Cargo.toml index 397c4a139..265ecbdd0 100644 --- a/fuzz/no_gpu_fuzz/Cargo.toml +++ b/fuzz/no_gpu_fuzz/Cargo.toml @@ -35,6 +35,12 @@ path = "fuzz_targets/curve/grumpkin.rs" test = false doc = false +[[bin]] +name = "secp256k1" +path = "fuzz_targets/field/secp256k1.rs" +test = false +doc = false + [[bin]] name = "stark252" path = "fuzz_targets/field/stark252.rs" diff --git a/fuzz/no_gpu_fuzz/fuzz_targets/field/secp256k1.rs b/fuzz/no_gpu_fuzz/fuzz_targets/field/secp256k1.rs new file mode 100644 index 000000000..b2d8d87dd --- /dev/null +++ b/fuzz/no_gpu_fuzz/fuzz_targets/field/secp256k1.rs @@ -0,0 +1,103 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use lambdaworks_math::field::{ + element::FieldElement +}; +use ibig::{modular::ModuloRing, UBig}; +use lambdaworks_math::traits::ByteConversion; +use lambdaworks_math::field::fields::montgomery_backed_prime_fields::U256PrimeField; +use lambdaworks_math::unsigned_integer::element::U256; +use lambdaworks_math::field::fields::montgomery_backed_prime_fields::IsModulus; + +#[derive(Clone, Debug, Hash, Copy)] +pub struct MontgomeryConfigSecpPrimeField; + +impl IsModulus for MontgomeryConfigSecpPrimeField { + const MODULUS: U256 = + U256::from_hex_unchecked("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F"); +} + +pub type SecpPrimeField = U256PrimeField; + +fuzz_target!(|bytes: ([u8;32], [u8;32])| { + + let secp256k1_prime = + UBig::from_str_radix("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16).unwrap(); + + let secp256k1_ring_prime = ModuloRing::new(&secp256k1_prime); + + let (bytes_a, bytes_b) = bytes; + let a = FieldElement::::from_bytes_be(&bytes_a).unwrap(); + let b = FieldElement::::from_bytes_be(&bytes_b).unwrap(); + + let a_hex = a.to_string()[2..].to_string(); + let b_hex = b.to_string()[2..].to_string(); + + let a_ring = secp256k1_ring_prime.from(&UBig::from_str_radix(&a_hex, 16).unwrap()); + let b_ring = secp256k1_ring_prime.from(&UBig::from_str_radix(&b_hex, 16).unwrap()); + + let add = &a + &b; + let addition = &a_ring + &b_ring; + + assert_eq!(&(add.to_string())[2..], addition.residue().in_radix(16).to_string()); + + let sub = &a - &b; + let substraction = &a_ring - &b_ring; + assert_eq!(&(sub.to_string())[2..], substraction.residue().in_radix(16).to_string()); + + let mul = &a * &b; + let multiplication = &a_ring * &b_ring; + assert_eq!(&(mul.to_string())[2..], multiplication.residue().in_radix(16).to_string()); + + let pow = &a.pow(b.representative()); + let expected_pow = a_ring.pow(&b_ring.residue()); + assert_eq!(&(pow.to_string())[2..], expected_pow.residue().in_radix(16).to_string()); + + if b != FieldElement::zero() { + + let div = &a / &b; + assert_eq!(&div * &b, a.clone()); + let expected_div = &a_ring / &b_ring; + assert_eq!(&(div.to_string())[2..], expected_div.residue().in_radix(16).to_string()); + } + + for n in [&a, &b] { + match n.sqrt() { + Some((fst_sqrt, snd_sqrt)) => { + assert_eq!(fst_sqrt.square(), snd_sqrt.square(), "Squared roots don't match each other"); + assert_eq!(n, &fst_sqrt.square(), "Squared roots don't match original number"); + } + None => {} + }; + } + + // Axioms soundness + + let one = FieldElement::::one(); + let zero = FieldElement::::zero(); + + assert_eq!(&a + &zero, a, "Neutral add element a failed"); + assert_eq!(&b + &zero, b, "Neutral mul element b failed"); + assert_eq!(&a * &one, a, "Neutral add element a failed"); + assert_eq!(&b * &one, b, "Neutral mul element b failed"); + + assert_eq!(&a + &b, &b + &a, "Commutative add property failed"); + assert_eq!(&a * &b, &b * &a, "Commutative mul property failed"); + + let c = &a * &b; + assert_eq!((&a + &b) + &c, &a + (&b + &c), "Associative add property failed"); + assert_eq!((&a * &b) * &c, &a * (&b * &c), "Associative mul property failed"); + + assert_eq!(&a * (&b + &c), &a * &b + &a * &c, "Distributive property failed"); + + assert_eq!(&a - &a, zero, "Inverse add a failed"); + assert_eq!(&b - &b, zero, "Inverse add b failed"); + + if a != zero { + assert_eq!(&a * a.inv().unwrap(), one, "Inverse mul a failed"); + } + if b != zero { + assert_eq!(&b * b.inv().unwrap(), one, "Inverse mul b failed"); + } +}); diff --git a/math/src/field/fields/montgomery_backed_prime_fields.rs b/math/src/field/fields/montgomery_backed_prime_fields.rs index bef1d312f..d2c426330 100644 --- a/math/src/field/fields/montgomery_backed_prime_fields.rs +++ b/math/src/field/fields/montgomery_backed_prime_fields.rs @@ -150,11 +150,7 @@ where #[inline(always)] fn square(a: &UnsignedInteger) -> UnsignedInteger { - if Self::MODULUS_HAS_ONE_SPARE_BIT { - MontgomeryAlgorithms::sos_square(a, &M::MODULUS, &Self::MU) - } else { - MontgomeryAlgorithms::cios(a, a, &M::MODULUS, &Self::MU) - } + MontgomeryAlgorithms::sos_square(a, &M::MODULUS, &Self::MU) } #[inline(always)] diff --git a/math/src/unsigned_integer/montgomery.rs b/math/src/unsigned_integer/montgomery.rs index 65222d269..e9202f004 100644 --- a/math/src/unsigned_integer/montgomery.rs +++ b/math/src/unsigned_integer/montgomery.rs @@ -158,6 +158,7 @@ impl MontgomeryAlgorithms { // `q`. let mut c: u128 = 0; let mut i = NUM_LIMBS; + let mut overflow = false; while i > 0 { i -= 1; c = 0; @@ -186,6 +187,7 @@ impl MontgomeryAlgorithms { hi.limbs[i - t] = cs as u64; t += 1; } + overflow |= c > 0; } // Step 3: At this point `overflow * 2^{2 * NUM_LIMBS * 64} + (hi, lo)` is a multiple @@ -197,7 +199,7 @@ impl MontgomeryAlgorithms { // The easy case is when `overflow` is zero. We just use the `sub` function. // If `overflow` is 1, then `hi` is smaller than `q`. The function `sub(hi, q)` wraps // around `2^{NUM_LIMBS * 64}`. This is the result we need. - let overflow = c > 0; + overflow |= c > 0; if overflow || UnsignedInteger::const_le(q, &hi) { (hi, _) = UnsignedInteger::sub(&hi, q); } From 5bad6b6ea6eda7ef82840a98dc938459c944be53 Mon Sep 17 00:00:00 2001 From: Diego K <43053772+diegokingston@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:27:39 -0300 Subject: [PATCH 4/6] fix overflow sum (#900) Co-authored-by: Mauro Toscano <12560266+MauroToscano@users.noreply.github.com> --- .../fields/montgomery_backed_prime_fields.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/math/src/field/fields/montgomery_backed_prime_fields.rs b/math/src/field/fields/montgomery_backed_prime_fields.rs index d2c426330..763d77526 100644 --- a/math/src/field/fields/montgomery_backed_prime_fields.rs +++ b/math/src/field/fields/montgomery_backed_prime_fields.rs @@ -224,15 +224,17 @@ where if v <= u { u = u - v; if b < c { - b = b + modulus; + b = modulus - c + b; + } else { + b = b - c; } - b = b - c; } else { v = v - u; if c < b { - c = c + modulus; + c = modulus - b + c; + } else { + c = c - b; } - c = c - b; } } @@ -1243,6 +1245,14 @@ mod tests_u256_prime_fields { assert_eq!(minus_3_pow_2, nine); } + #[test] + fn secp256k1_inv_works() { + let a = SecpMontElement::from_hex_unchecked("0x456"); + let a_inv = a.inv().unwrap(); + + assert_eq!(a * a_inv, SecpMontElement::one()); + } + #[test] fn test_cios_overflow_case() { let a = GoldilocksElement::from(732582227915286439); From 0c23464a16a02739bbef252910ef9ab67d2fb923 Mon Sep 17 00:00:00 2001 From: Mario Rugiero Date: Mon, 2 Sep 2024 14:58:53 -0300 Subject: [PATCH 5/6] Release v0.10.0: Crazy Chipotle (#903) --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 606911f06..38c9a23e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,16 +5,16 @@ exclude = ["ensure-no_std"] resolver = "2" [workspace.package] -version = "0.9.0" +version = "0.10.0" edition = "2021" license = "Apache-2.0" repository = "https://github.com/lambdaclass/lambdaworks" [workspace.dependencies] iai-callgrind = "0.3.1" -lambdaworks-crypto = { path = "./crypto", version = "0.9.0", default-features = false } -lambdaworks-gpu = { path = "./gpu", version = "0.9.0" } -lambdaworks-math = { path = "./math", version = "0.9.0", default-features = false } +lambdaworks-crypto = { path = "./crypto", version = "0.10.0", default-features = false } +lambdaworks-gpu = { path = "./gpu", version = "0.10.0" } +lambdaworks-math = { path = "./math", version = "0.10.0", default-features = false } stark-platinum-prover = { path = "./provers/stark" } cairo-platinum-prover = { path = "./provers/cairo" } lambdaworks-winterfell-adapter = { path = "./provers/winterfell_adapter"} From 3795dbc6368d50a29e987ce9dea2944c7022e1c8 Mon Sep 17 00:00:00 2001 From: Luca Lazcano <64329820+lazcanoluca@users.noreply.github.com> Date: Wed, 4 Sep 2024 13:20:57 -0300 Subject: [PATCH 6/6] Compression for bls12381 (#904) * Define trait Compress * Implement `Compress` for short weierstrass with BLS12381 curve The implementation was ported and adapted from https://github.com/lambdaclass/lambdaworks_kzg/blob/8f031b1f32e170c1af06029e46c73404f4c85e2e/src/compression.rs#L29. * Implement `decompress_g2_point` for BLS12381 The implementation was ported from https://github.com/lambdaclass/lambdaworks_kzg/blob/8f031b1f32e170c1af06029e46c73404f4c85e2e/src/compression.rs#L105 * Use `Compress` trait in BLS12381 benches * Fix return type `Compress` for no std * Fix no std for Compress trait * Remove alias types public visibility * Fix typo * Change `Compress` implementation from G1 point to bls12381 curve * Fix use of bls12381 compression in benches --- math/benches/elliptic_curves/bls12_381.rs | 16 +- math/benches/elliptic_curves/iai_bls12_381.rs | 13 +- .../curves/bls12_381/compression.rs | 222 +++++++++++------- .../short_weierstrass/traits.rs | 14 ++ 4 files changed, 167 insertions(+), 98 deletions(-) diff --git a/math/benches/elliptic_curves/bls12_381.rs b/math/benches/elliptic_curves/bls12_381.rs index 44613e5b1..9e7b74ada 100644 --- a/math/benches/elliptic_curves/bls12_381.rs +++ b/math/benches/elliptic_curves/bls12_381.rs @@ -2,11 +2,11 @@ use criterion::{black_box, Criterion}; use lambdaworks_math::{ cyclic_group::IsGroup, elliptic_curve::{ - short_weierstrass::curves::bls12_381::{ - compression::{compress_g1_point, decompress_g1_point}, - curve::BLS12381Curve, - pairing::BLS12381AtePairing, - twist::BLS12381TwistCurve, + short_weierstrass::{ + curves::bls12_381::{ + curve::BLS12381Curve, pairing::BLS12381AtePairing, twist::BLS12381TwistCurve, + }, + traits::Compress, }, traits::{IsEllipticCurve, IsPairing}, }, @@ -70,13 +70,13 @@ pub fn bls12_381_elliptic_curve_benchmarks(c: &mut Criterion) { // Compress_G1_point group.bench_function("Compress G1 point", |bencher| { - bencher.iter(|| black_box(compress_g1_point(black_box(&a_g1)))); + bencher.iter(|| black_box(BLS12381Curve::compress_g1_point(black_box(&a_g1)))); }); // Decompress_G1_point group.bench_function("Decompress G1 Point", |bencher| { - let a: [u8; 48] = compress_g1_point(&a_g1).try_into().unwrap(); - bencher.iter(|| black_box(decompress_g1_point(&mut black_box(a))).unwrap()); + let a: [u8; 48] = BLS12381Curve::compress_g1_point(&a_g1).try_into().unwrap(); + bencher.iter(|| black_box(BLS12381Curve::decompress_g1_point(&mut black_box(a))).unwrap()); }); // Subgroup Check G1 diff --git a/math/benches/elliptic_curves/iai_bls12_381.rs b/math/benches/elliptic_curves/iai_bls12_381.rs index b743e9bf8..8eeb67ec6 100644 --- a/math/benches/elliptic_curves/iai_bls12_381.rs +++ b/math/benches/elliptic_curves/iai_bls12_381.rs @@ -4,16 +4,15 @@ use lambdaworks_math::{ elliptic_curve::{ short_weierstrass::{ curves::bls12_381::{ - compression::{compress_g1_point, decompress_g1_point}, - curve::BLS12381Curve, - pairing::BLS12381AtePairing, - twist::BLS12381TwistCurve, + curve::BLS12381Curve, pairing::BLS12381AtePairing, twist::BLS12381TwistCurve, }, point::ShortWeierstrassProjectivePoint, + traits::Compress, }, traits::{IsEllipticCurve, IsPairing}, }, }; + use rand::{rngs::StdRng, Rng, SeedableRng}; #[allow(dead_code)] type G1 = ShortWeierstrassProjectivePoint; @@ -91,14 +90,14 @@ pub fn bls12_381_neg_g2() { #[allow(dead_code)] pub fn bls12_381_compress_g1() { let (a, _, _, _) = rand_points_g1(); - let _ = black_box(compress_g1_point(black_box(&a))); + let _ = black_box(BLS12381Curve::compress_g1_point(black_box(&a))); } #[allow(dead_code)] pub fn bls12_381_decompress_g1() { let (a, _, _, _) = rand_points_g1(); - let a: [u8; 48] = compress_g1_point(&a).try_into().unwrap(); - let _ = black_box(decompress_g1_point(&mut black_box(a))).unwrap(); + let a: [u8; 48] = BLS12381Curve::compress_g1_point(&a).try_into().unwrap(); + let _ = black_box(BLS12381Curve::decompress_g1_point(&mut black_box(a))).unwrap(); } #[allow(dead_code)] diff --git a/math/src/elliptic_curve/short_weierstrass/curves/bls12_381/compression.rs b/math/src/elliptic_curve/short_weierstrass/curves/bls12_381/compression.rs index 058496f76..a7fec81e0 100644 --- a/math/src/elliptic_curve/short_weierstrass/curves/bls12_381/compression.rs +++ b/math/src/elliptic_curve/short_weierstrass/curves/bls12_381/compression.rs @@ -1,7 +1,14 @@ -use super::field_extension::BLS12381PrimeField; +use super::{field_extension::BLS12381PrimeField, twist::BLS12381TwistCurve}; use crate::{ - elliptic_curve::short_weierstrass::{ - curves::bls12_381::curve::BLS12381Curve, point::ShortWeierstrassProjectivePoint, + elliptic_curve::{ + short_weierstrass::{ + curves::bls12_381::{ + curve::BLS12381Curve, field_extension::Degree2ExtensionField, sqrt, + }, + point::ShortWeierstrassProjectivePoint, + traits::Compress, + }, + traits::IsEllipticCurve, }, field::element::FieldElement, }; @@ -12,83 +19,127 @@ use crate::{ traits::ByteConversion, }; -pub type G1Point = ShortWeierstrassProjectivePoint; -pub type BLS12381FieldElement = FieldElement; - -pub fn decompress_g1_point(input_bytes: &mut [u8; 48]) -> Result { - let first_byte = input_bytes.first().unwrap(); - // We get the 3 most significant bits - let prefix_bits = first_byte >> 5; - let first_bit = (prefix_bits & 4_u8) >> 2; - // If first bit is not 1, then the value is not compressed. - if first_bit != 1 { - return Err(ByteConversionError::ValueNotCompressed); - } - let second_bit = (prefix_bits & 2_u8) >> 1; - // If the second bit is 1, then the compressed point is the - // point at infinity and we return it directly. - if second_bit == 1 { - return Ok(G1Point::neutral_element()); +type G1Point = ShortWeierstrassProjectivePoint; +type BLS12381FieldElement = FieldElement; + +impl Compress for BLS12381Curve { + type G1Point = G1Point; + + type G2Point = ::PointRepresentation; + + type Error = ByteConversionError; + + #[cfg(feature = "alloc")] + fn compress_g1_point(point: &Self::G1Point) -> alloc::vec::Vec { + if *point == G1Point::neutral_element() { + // point is at infinity + let mut x_bytes = alloc::vec![0_u8; 48]; + x_bytes[0] |= 1 << 7; + x_bytes[0] |= 1 << 6; + x_bytes + } else { + // point is not at infinity + let point_affine = point.to_affine(); + let x = point_affine.x(); + let y = point_affine.y(); + + let mut x_bytes = x.to_bytes_be(); + + // Set first bit to to 1 indicate this is compressed element. + x_bytes[0] |= 1 << 7; + + let y_neg = core::ops::Neg::neg(y); + if y_neg.representative() < y.representative() { + x_bytes[0] |= 1 << 5; + } + x_bytes + } } - let third_bit = prefix_bits & 1_u8; - - let first_byte_without_control_bits = (first_byte << 3) >> 3; - input_bytes[0] = first_byte_without_control_bits; - - let x = BLS12381FieldElement::from_bytes_be(input_bytes)?; - - // We apply the elliptic curve formula to know the y^2 value. - let y_squared = x.pow(3_u16) + BLS12381FieldElement::from(4); - - let (y_sqrt_1, y_sqrt_2) = &y_squared.sqrt().ok_or(ByteConversionError::InvalidValue)?; - - // we call "negative" to the greate root, - // if the third bit is 1, we take this grater value. - // Otherwise, we take the second one. - let y = match ( - y_sqrt_1.representative().cmp(&y_sqrt_2.representative()), - third_bit, - ) { - (Ordering::Greater, 0) => y_sqrt_2, - (Ordering::Greater, _) => y_sqrt_1, - (Ordering::Less, 0) => y_sqrt_1, - (Ordering::Less, _) => y_sqrt_2, - (Ordering::Equal, _) => y_sqrt_1, - }; - let point = - G1Point::from_affine(x, y.clone()).map_err(|_| ByteConversionError::InvalidValue)?; + fn decompress_g1_point(input_bytes: &mut [u8; 48]) -> Result { + let first_byte = input_bytes.first().unwrap(); + // We get the 3 most significant bits + let prefix_bits = first_byte >> 5; + let first_bit = (prefix_bits & 4_u8) >> 2; + // If first bit is not 1, then the value is not compressed. + if first_bit != 1 { + return Err(ByteConversionError::ValueNotCompressed); + } + let second_bit = (prefix_bits & 2_u8) >> 1; + // If the second bit is 1, then the compressed point is the + // point at infinity and we return it directly. + if second_bit == 1 { + return Ok(G1Point::neutral_element()); + } + let third_bit = prefix_bits & 1_u8; - point - .is_in_subgroup() - .then_some(point) - .ok_or(ByteConversionError::PointNotInSubgroup) -} + let first_byte_without_control_bits = (first_byte << 3) >> 3; + input_bytes[0] = first_byte_without_control_bits; + + let x = BLS12381FieldElement::from_bytes_be(input_bytes)?; + + // We apply the elliptic curve formula to know the y^2 value. + let y_squared = x.pow(3_u16) + BLS12381FieldElement::from(4); + + let (y_sqrt_1, y_sqrt_2) = &y_squared.sqrt().ok_or(ByteConversionError::InvalidValue)?; + + // we call "negative" to the greate root, + // if the third bit is 1, we take this grater value. + // Otherwise, we take the second one. + let y = match ( + y_sqrt_1.representative().cmp(&y_sqrt_2.representative()), + third_bit, + ) { + (Ordering::Greater, 0) => y_sqrt_2, + (Ordering::Greater, _) => y_sqrt_1, + (Ordering::Less, 0) => y_sqrt_1, + (Ordering::Less, _) => y_sqrt_2, + (Ordering::Equal, _) => y_sqrt_1, + }; + + let point = + G1Point::from_affine(x, y.clone()).map_err(|_| ByteConversionError::InvalidValue)?; + + point + .is_in_subgroup() + .then_some(point) + .ok_or(ByteConversionError::PointNotInSubgroup) + } + + #[allow(unused)] + fn decompress_g2_point(input_bytes: &mut [u8; 96]) -> Result { + let first_byte = input_bytes.first().unwrap(); -#[cfg(feature = "alloc")] -pub fn compress_g1_point(point: &G1Point) -> alloc::vec::Vec { - if *point == G1Point::neutral_element() { - // point is at infinity - let mut x_bytes = alloc::vec![0_u8; 48]; - x_bytes[0] |= 1 << 7; - x_bytes[0] |= 1 << 6; - x_bytes - } else { - // point is not at infinity - let point_affine = point.to_affine(); - let x = point_affine.x(); - let y = point_affine.y(); - - let mut x_bytes = x.to_bytes_be(); - - // Set first bit to to 1 indicate this is compressed element. - x_bytes[0] |= 1 << 7; - - let y_neg = core::ops::Neg::neg(y); - if y_neg.representative() < y.representative() { - x_bytes[0] |= 1 << 5; + // We get the first 3 bits + let prefix_bits = first_byte >> 5; + let first_bit = (prefix_bits & 4_u8) >> 2; + // If first bit is not 1, then the value is not compressed. + if first_bit != 1 { + return Err(ByteConversionError::InvalidValue); + } + let second_bit = (prefix_bits & 2_u8) >> 1; + // If the second bit is 1, then the compressed point is the + // point at infinity and we return it directly. + if second_bit == 1 { + return Ok(Self::G2Point::neutral_element()); } - x_bytes + + let first_byte_without_control_bits = (first_byte << 3) >> 3; + input_bytes[0] = first_byte_without_control_bits; + + let input0 = &input_bytes[48..]; + let input1 = &input_bytes[0..48]; + let x0 = BLS12381FieldElement::from_bytes_be(input0).unwrap(); + let x1 = BLS12381FieldElement::from_bytes_be(input1).unwrap(); + let x: FieldElement = FieldElement::new([x0, x1]); + + const VALUE: BLS12381FieldElement = BLS12381FieldElement::from_hex_unchecked("4"); + let b_param_qfe = FieldElement::::new([VALUE, VALUE]); + + let y = sqrt::sqrt_qfe(&(x.pow(3_u64) + b_param_qfe), 0) + .ok_or(ByteConversionError::InvalidValue)?; + + Self::G2Point::from_affine(x, y).map_err(|_| ByteConversionError::InvalidValue) } } @@ -96,11 +147,10 @@ pub fn compress_g1_point(point: &G1Point) -> alloc::vec::Vec { mod tests { use super::{BLS12381FieldElement, G1Point}; use crate::elliptic_curve::short_weierstrass::curves::bls12_381::curve::BLS12381Curve; + use crate::elliptic_curve::short_weierstrass::traits::Compress; use crate::elliptic_curve::traits::{FromAffine, IsEllipticCurve}; #[cfg(feature = "alloc")] - use super::compress_g1_point; - use super::decompress_g1_point; use crate::{ cyclic_group::IsGroup, traits::ByteConversion, unsigned_integer::element::UnsignedInteger, }; @@ -121,8 +171,10 @@ mod tests { #[cfg(feature = "alloc")] #[test] fn test_g1_compress_generator() { + use crate::elliptic_curve::short_weierstrass::traits::Compress; + let g = BLS12381Curve::generator(); - let mut compressed_g = compress_g1_point(&g); + let mut compressed_g = BLS12381Curve::compress_g1_point(&g); let first_byte = compressed_g.first().unwrap(); let first_byte_without_control_bits = (first_byte << 3) >> 3; @@ -137,8 +189,10 @@ mod tests { #[cfg(feature = "alloc")] #[test] fn test_g1_compress_point_at_inf() { + use crate::elliptic_curve::short_weierstrass::traits::Compress; + let inf = G1Point::neutral_element(); - let compressed_inf = compress_g1_point(&inf); + let compressed_inf = BLS12381Curve::compress_g1_point(&inf); let first_byte = compressed_inf.first().unwrap(); assert_eq!(*first_byte >> 6, 3_u8); @@ -147,11 +201,13 @@ mod tests { #[cfg(feature = "alloc")] #[test] fn test_compress_decompress_generator() { + use crate::elliptic_curve::short_weierstrass::traits::Compress; + let g = BLS12381Curve::generator(); - let compressed_g = compress_g1_point(&g); + let compressed_g = BLS12381Curve::compress_g1_point(&g); let mut compressed_g_slice: [u8; 48] = compressed_g.try_into().unwrap(); - let decompressed_g = decompress_g1_point(&mut compressed_g_slice).unwrap(); + let decompressed_g = BLS12381Curve::decompress_g1_point(&mut compressed_g_slice).unwrap(); assert_eq!(g, decompressed_g); } @@ -163,10 +219,10 @@ mod tests { // calculate g point operate with itself let g_2 = g.operate_with_self(UnsignedInteger::<4>::from("2")); - let compressed_g2 = compress_g1_point(&g_2); + let compressed_g2 = BLS12381Curve::compress_g1_point(&g_2); let mut compressed_g2_slice: [u8; 48] = compressed_g2.try_into().unwrap(); - let decompressed_g2 = decompress_g1_point(&mut compressed_g2_slice).unwrap(); + let decompressed_g2 = BLS12381Curve::decompress_g1_point(&mut compressed_g2_slice).unwrap(); assert_eq!(g_2, decompressed_g2); } diff --git a/math/src/elliptic_curve/short_weierstrass/traits.rs b/math/src/elliptic_curve/short_weierstrass/traits.rs index 100623303..7b708799a 100644 --- a/math/src/elliptic_curve/short_weierstrass/traits.rs +++ b/math/src/elliptic_curve/short_weierstrass/traits.rs @@ -1,3 +1,4 @@ +use crate::cyclic_group::IsGroup; use crate::elliptic_curve::traits::IsEllipticCurve; use crate::field::element::FieldElement; use core::fmt::Debug; @@ -18,3 +19,16 @@ pub trait IsShortWeierstrass: IsEllipticCurve + Clone + Debug { y.pow(2_u16) - x.pow(3_u16) - Self::a() * x - Self::b() } } + +pub trait Compress { + type G1Point: IsGroup; + type G2Point: IsGroup; + type Error; + + #[cfg(feature = "alloc")] + fn compress_g1_point(point: &Self::G1Point) -> alloc::vec::Vec; + + fn decompress_g1_point(input_bytes: &mut [u8; 48]) -> Result; + + fn decompress_g2_point(input_bytes: &mut [u8; 96]) -> Result; +}