diff --git a/Cargo.lock b/Cargo.lock index 564fd59cbe..c0fdf0fda0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1365,6 +1365,7 @@ version = "0.1.8" dependencies = [ "aes", "aes-gcm", + "ark-bn254", "ark-ec", "ark-ff", "ark-secp256r1", @@ -1504,8 +1505,8 @@ dependencies = [ "ark-serialize", "ark-snark", "ark-std", + "bcs", "blake2", - "blst", "byte-slice-cast", "criterion 0.5.1", "derive_more", diff --git a/fastcrypto-zkp/Cargo.toml b/fastcrypto-zkp/Cargo.toml index d99907eb13..77ba31fb08 100644 --- a/fastcrypto-zkp/Cargo.toml +++ b/fastcrypto-zkp/Cargo.toml @@ -21,7 +21,6 @@ name = "poseidon" harness = false [dependencies] -ark-bls12-381 = "0.4.0" ark-bn254 = "0.4.0" ark-ec = { version = "0.4.1" } ark-ff = { version = "0.4.1", features = ["asm"] } @@ -29,7 +28,7 @@ ark-groth16 = { version = "0.4.0", default-features = false } ark-relations = "0.4.0" ark-serialize = "0.4.1" ark-snark = "0.4.0" -blst = "0.3.11" +bcs.workspace = true byte-slice-cast = "1.2.2" fastcrypto = { path = "../fastcrypto", version = "0.1.5" } derive_more = "0.99.16" @@ -48,6 +47,7 @@ itertools = "0.12.0" [dev-dependencies] ark-bls12-377 = "0.4.0" +ark-bls12-381 = "0.4.0" ark-crypto-primitives = { version = "0.4.0", features = ["r1cs", "prf"] } ark-r1cs-std = "0.4.0" ark-std = "0.4.0" diff --git a/fastcrypto-zkp/benches/conversions.rs b/fastcrypto-zkp/benches/conversions.rs deleted file mode 100644 index 918beb694d..0000000000 --- a/fastcrypto-zkp/benches/conversions.rs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) 2022, Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use ark_bls12_381::{Fq, Fq2, Fr as BlsFr}; -use ark_bls12_381::{Fq12, G2Affine as BlsG2Affine}; -use ark_bls12_381::{Fq6, G1Affine as BlsG1Affine}; -use ark_ec::{AffineRepr, CurveGroup}; -use ark_ff::UniformRand; -use criterion::{criterion_group, Criterion}; -use criterion::{measurement::Measurement, BenchmarkGroup}; -use fastcrypto_zkp::bls12381::conversions::{ - bls_fq12_to_blst_fp12, bls_fq2_to_blst_fp2, bls_fq6_to_blst_fp6, bls_fq_to_blst_fp, - bls_fr_to_blst_fr, bls_g1_affine_to_blst_g1_affine, bls_g2_affine_to_blst_g2_affine, - blst_fp12_to_bls_fq12, blst_fp2_to_bls_fq2, blst_fp6_to_bls_fq6, blst_fp_to_bls_fq, - blst_fr_to_bls_fr, blst_g1_affine_to_bls_g1_affine, blst_g2_affine_to_bls_g2_affine, -}; -use std::ops::Mul; - -fn convert_from_arkworks(grp: &mut BenchmarkGroup) { - let mut rng = ark_std::test_rng(); - let element = BlsFr::rand(&mut rng); - grp.bench_with_input("BlsFr -> blst_fr", &element, |b, element| { - b.iter(|| bls_fr_to_blst_fr(element)); - }); - - let element = Fq::rand(&mut rng); - grp.bench_with_input("Fq -> blst_fp", &element, |b, element| { - b.iter(|| bls_fq_to_blst_fp(element)); - }); - - let element = Fq2::rand(&mut rng); - grp.bench_with_input("Fq2 -> blst_fp2", &element, |b, element| { - b.iter(|| bls_fq2_to_blst_fp2(element)); - }); - - let element = Fq6::rand(&mut rng); - grp.bench_with_input("Fq6 -> blst_fp6", &element, |b, element| { - b.iter(|| bls_fq6_to_blst_fp6(element)); - }); - - let element = Fq12::rand(&mut rng); - grp.bench_with_input("Fq12 -> blst_fp12", &element, |b, element| { - b.iter(|| bls_fq12_to_blst_fp12(element)); - }); - - let scalar = BlsFr::rand(&mut rng); - let element = BlsG1Affine::generator().mul(scalar).into_affine(); - grp.bench_with_input("G1Affine -> blst_p1_affine", &element, |b, element| { - b.iter(|| bls_g1_affine_to_blst_g1_affine(element)); - }); - - let scalar = BlsFr::rand(&mut rng); - let element = BlsG2Affine::generator().mul(scalar).into_affine(); - grp.bench_with_input("G2Affine -> blst_p2_affine", &element, |b, element| { - b.iter(|| bls_g2_affine_to_blst_g2_affine(element)); - }); -} - -fn convert_from_blst(grp: &mut BenchmarkGroup) { - let mut rng = ark_std::test_rng(); - let bls = BlsFr::rand(&mut rng); - let element = bls_fr_to_blst_fr(&bls); - grp.bench_with_input("blst_fr -> BlsFr", &element, |b, element| { - b.iter(|| blst_fr_to_bls_fr(element)); - }); - - let bls = Fq::rand(&mut rng); - let element = bls_fq_to_blst_fp(&bls); - grp.bench_with_input("blst_fp -> Fp", &element, |b, element| { - b.iter(|| blst_fp_to_bls_fq(element)); - }); - - let bls = Fq2::rand(&mut rng); - let element = bls_fq2_to_blst_fp2(&bls); - grp.bench_with_input("blst_fp2 -> Fq2", &element, |b, element| { - b.iter(|| blst_fp2_to_bls_fq2(element)); - }); - - let bls = Fq6::rand(&mut rng); - let element = bls_fq6_to_blst_fp6(&bls); - grp.bench_with_input("blst_fp6 -> Fq6", &element, |b, element| { - b.iter(|| blst_fp6_to_bls_fq6(element)); - }); - - let bls = Fq12::rand(&mut rng); - let element = bls_fq12_to_blst_fp12(&bls); - grp.bench_with_input("blst_fp12 -> Fq12", &element, |b, element| { - b.iter(|| blst_fp12_to_bls_fq12(element)); - }); - - let scalar = BlsFr::rand(&mut rng); - let bls = BlsG1Affine::generator().mul(scalar).into_affine(); - let element = bls_g1_affine_to_blst_g1_affine(&bls); - grp.bench_with_input("blst_p1_affine -> G1Affine", &element, |b, element| { - b.iter(|| blst_g1_affine_to_bls_g1_affine(element)); - }); - - let scalar = BlsFr::rand(&mut rng); - let bls = BlsG2Affine::generator().mul(scalar).into_affine(); - let element = bls_g2_affine_to_blst_g2_affine(&bls); - grp.bench_with_input("blst_p2_affine -> G2Affine", &element, |b, element| { - b.iter(|| blst_g2_affine_to_bls_g2_affine(element)); - }); -} - -fn to_from_arkworks(c: &mut Criterion) { - let mut group: BenchmarkGroup<_> = c.benchmark_group("Conversions Arkworks -> Blst"); - convert_from_arkworks(&mut group); - group.finish(); - let mut group: BenchmarkGroup<_> = c.benchmark_group("Conversions Blst -> Arkworks"); - convert_from_blst(&mut group); - group.finish(); -} - -criterion_group! { - name = conversion_benches; - config = Criterion::default(); - targets = - to_from_arkworks, -} diff --git a/fastcrypto-zkp/benches/proving.rs b/fastcrypto-zkp/benches/proving.rs index 375332513a..08a45217d4 100644 --- a/fastcrypto-zkp/benches/proving.rs +++ b/fastcrypto-zkp/benches/proving.rs @@ -1,24 +1,27 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use ark_bls12_377::{Bls12_377, Fr as Bls377Fr}; + +use std::ops::Mul; + use ark_bls12_381::{Bls12_381, Fr as BlsFr}; use ark_bn254::{Bn254, Fr as Bn254Fr}; - use ark_ec::pairing::Pairing; use ark_ff::{PrimeField, UniformRand}; use ark_groth16::Groth16; +use ark_serialize::CanonicalSerialize; use ark_snark::SNARK; use ark_std::rand::thread_rng; use criterion::{ criterion_group, criterion_main, measurement::Measurement, BenchmarkGroup, BenchmarkId, Criterion, SamplingMode, }; -use fastcrypto_zkp::dummy_circuits::DummyCircuit; -use fastcrypto_zkp::{bls12381, bn254}; -use std::ops::Mul; -#[path = "./conversions.rs"] -mod conversions; +use crate::utils::vk_from_arkworks; +use fastcrypto::groups::bls12381::{G1Element, Scalar}; +use fastcrypto::serde_helpers::ToFromByteArray; +use fastcrypto_zkp::bn254; +use fastcrypto_zkp::dummy_circuits::DummyCircuit; +use fastcrypto_zkp::groth16::{Proof, VerifyingKey}; #[path = "./utils.rs"] mod utils; @@ -238,7 +241,7 @@ fn bench_verify_elusiv_circuit(grp: &mut BenchmarkGroup) { ), ]; - let vk: bn254::VerifyingKey = ark_groth16::VerifyingKey { + let vk: bn254::VerifyingKey = vk_from_arkworks(ark_groth16::VerifyingKey { alpha_g1: utils::G1Affine_from_str_projective(( "8057073471822347335074195152835286348058235024870127707965681971765888348219", "14493022634743109860560137600871299171677470588934003383462482807829968516757", @@ -357,8 +360,7 @@ fn bench_verify_elusiv_circuit(grp: &mut BenchmarkGroup) { .into_iter() .map(|s| utils::G1Affine_from_str_projective((s[0], s[1], s[2]))) .collect(), - } - .into(); + }); grp.bench_with_input( BenchmarkId::new( @@ -367,12 +369,12 @@ fn bench_verify_elusiv_circuit(grp: &mut BenchmarkGroup) { ), &vk, |b, vk| { - b.iter(|| bn254::verifier::PreparedVerifyingKey::from(vk)); + b.iter(|| bn254::PreparedVerifyingKey::from(vk)); }, ); - let pvk = bn254::verifier::PreparedVerifyingKey::from(&vk); - let bytes = pvk.serialize().unwrap(); + let pvk = bn254::PreparedVerifyingKey::from(&vk); + let bytes = pvk.serialize_into_parts(); let vk_gamma_abc_g1_bytes = &bytes[0]; let alpha_g1_beta_g2_bytes = &bytes[1]; let gamma_g2_neg_pc_bytes = &bytes[2]; @@ -427,25 +429,36 @@ fn bench_our_verify(grp: &mut BenchmarkGroup) { }; let (pk, ark_vk) = Groth16::::circuit_specific_setup(c, rng).unwrap(); - let proof = bls12381::Proof::from(Groth16::::prove(&pk, c, rng).unwrap()); + + let ark_proof = Groth16::::prove(&pk, c, rng).unwrap(); + let mut proof_bytes = Vec::new(); + ark_proof.serialize_compressed(&mut proof_bytes).unwrap(); + let proof: Proof = bcs::from_bytes(&proof_bytes).unwrap(); + let v = c.a.unwrap().mul(c.b.unwrap()); + let mut v_bytes = [0u8; 32]; + v.serialize_compressed(v_bytes.as_mut_slice()).unwrap(); + v_bytes.reverse(); + let v = Scalar::from_byte_array(&v_bytes).unwrap(); - let vk = ark_vk.into(); + let mut vk_bytes = Vec::new(); + ark_vk.serialize_compressed(&mut vk_bytes).unwrap(); + let vk = VerifyingKey::from_arkworks_format(&vk_bytes).unwrap(); grp.bench_with_input( BenchmarkId::new("BLST-based Groth16 process verifying key", *size), &vk, |b, vk| { - b.iter(|| bls12381::verifier::PreparedVerifyingKey::from(vk)); + b.iter(|| fastcrypto_zkp::bls12381::PreparedVerifyingKey::from(vk)); }, ); - let pvk = bls12381::verifier::PreparedVerifyingKey::from(&vk); + let pvk = fastcrypto_zkp::bls12381::PreparedVerifyingKey::from(&vk); grp.bench_with_input( BenchmarkId::new("BLST-based Groth16 verify with processed vk", *size), &(pvk, v), |b, (pvk, v)| { - b.iter(|| pvk.verify(&[(*v).into()], &proof).unwrap()); + b.iter(|| pvk.verify(&[*v], &proof).unwrap()); }, ); } @@ -464,12 +477,6 @@ fn prove(c: &mut Criterion) { group.sample_size(10); bench_prove::(&mut group); group.finish(); - - let mut group: BenchmarkGroup<_> = c.benchmark_group("BLS12-377 Proving"); - group.sampling_mode(SamplingMode::Flat); // This can take a *while* - group.sample_size(10); - bench_prove::(&mut group); - group.finish(); } fn verify(c: &mut Criterion) { @@ -484,10 +491,6 @@ fn verify(c: &mut Criterion) { bench_verify::(&mut group); group.finish(); - let mut group: BenchmarkGroup<_> = c.benchmark_group("BLS12-377 Verification"); - bench_verify::(&mut group); - group.finish(); - let mut group: BenchmarkGroup<_> = c.benchmark_group("Elusiv Circuit Verification"); bench_verify_elusiv_circuit::<_>(&mut group); group.finish(); @@ -501,4 +504,4 @@ criterion_group! { prove, } -criterion_main!(conversions::conversion_benches, proving_benches,); +criterion_main!(proving_benches,); diff --git a/fastcrypto-zkp/benches/utils.rs b/fastcrypto-zkp/benches/utils.rs index 9f8414d313..2e6847617a 100644 --- a/fastcrypto-zkp/benches/utils.rs +++ b/fastcrypto-zkp/benches/utils.rs @@ -1,11 +1,25 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use ark_bn254::{Fq, Fq2, G1Affine, G1Projective, G2Affine}; +use ark_bn254::{Bn254, Fq, Fq2, G1Affine, G1Projective, G2Affine, G2Projective}; +use fastcrypto_zkp::bn254::VerifyingKey; type StrPair = (&'static str, &'static str); type StrTriplet = (&'static str, &'static str, &'static str); +pub fn vk_from_arkworks(vk: ark_groth16::VerifyingKey) -> VerifyingKey { + VerifyingKey::new( + G1Projective::from(vk.alpha_g1).into(), + G2Projective::from(vk.beta_g2).into(), + G2Projective::from(vk.gamma_g2).into(), + G2Projective::from(vk.delta_g2).into(), + vk.gamma_abc_g1 + .iter() + .map(|x| G1Projective::from(*x).into()) + .collect(), + ) +} + #[allow(non_snake_case)] pub fn G1Affine_from_str_projective(#[allow(clippy::type_complexity)] s: StrTriplet) -> G1Affine { G1Projective::new( @@ -18,7 +32,6 @@ pub fn G1Affine_from_str_projective(#[allow(clippy::type_complexity)] s: StrTrip #[allow(non_snake_case)] pub fn G2Affine_from_str_projective(s: (StrPair, StrPair, StrPair)) -> G2Affine { - use ark_bn254::G2Projective; G2Projective::new( Fq2::new(s.0 .0.parse::().unwrap(), s.0 .1.parse::().unwrap()), Fq2::new(s.1 .0.parse::().unwrap(), s.1 .1.parse::().unwrap()), diff --git a/fastcrypto-zkp/examples/blake2s_demo.rs b/fastcrypto-zkp/examples/blake2s_demo.rs deleted file mode 100644 index dba3b0b1de..0000000000 --- a/fastcrypto-zkp/examples/blake2s_demo.rs +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) 2022, Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use ark_bls12_381::{Bls12_381, Fr}; -pub use ark_ff::ToConstraintField; - -use ark_crypto_primitives::prf::{PRFGadget, PRF}; -use ark_crypto_primitives::{ - prf::blake2s::constraints::Blake2sGadget, prf::blake2s::Blake2s as B2SPRF, -}; -use ark_groth16::Groth16; -use ark_relations::r1cs::{ - ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, -}; -use blake2::{digest::Digest, Blake2s256}; - -use ark_r1cs_std::prelude::*; -use ark_std::rand::thread_rng; -use fastcrypto_zkp::bls12381::conversions::BlsFr; -use fastcrypto_zkp::bls12381::verifier::PreparedVerifyingKey; -use fastcrypto_zkp::bls12381::{FieldElement, Proof}; - -#[derive(Clone, Copy, Debug)] -struct Blake2sCircuit { - input: [u8; 32], - blake2_seed: [u8; 32], - pub expected_output: [u8; 32], -} - -impl Blake2sCircuit { - fn new() -> Self { - // We're going to prove knowledge of the blake2 hash of this secret - // see https://en.wikipedia.org/wiki/Cluedo - let statement = b"butler in the yard with a wrench"; - let bytes = statement.to_vec(); - - // as a sanity-check, we'll also compute the blake2 hash of the secret - // with the reference implementation - - // Blake2s takes a seed parameter. We use a default seed. - let seed: [u8; 32] = [0u8; 32]; - - // Compute the hash by traditional means. - let mut h = Blake2s256::new_with_prefix(seed); - h.update(&bytes); - let hash_result = h.finalize(); - - // We use the arkworks API for blake2s as well and check it matches - let input: [u8; 32] = bytes[..].try_into().unwrap(); - let out = B2SPRF::evaluate(&seed, &input).unwrap(); - - // the traditional means and the arkworks implementation match - assert_eq!(hash_result.as_slice(), out.as_slice()); - - // at this stage, we can publish the seed and the expected hash output, - // and we'll prove we know the input - Self { - input: statement.to_owned(), - blake2_seed: seed, - expected_output: out, - } - } -} - -impl ConstraintSynthesizer for Blake2sCircuit { - fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { - // initialize the blake2s gadget and allocate the seed - let seed_var = UInt8::new_input_vec(cs.clone(), &self.blake2_seed).unwrap(); - - // declare the witnesses - let input_var = UInt8::new_witness_vec(cs.clone(), &self.input).unwrap(); - - // declare the public intended output - let desired_out_var = >::OutputVar::new_input(cs, || { - Ok(self.expected_output) - }) - .unwrap(); - - // link the intended output to a blake2s computation on the input, i.e. constrain Blake2s(seed, input) == output - let output_var = Blake2sGadget::evaluate(&seed_var, &input_var).unwrap(); - output_var.enforce_equal(&desired_out_var).unwrap(); - - Ok(()) - } -} - -fn main() { - let mut rng = &mut thread_rng(); - - let circuit = Blake2sCircuit::new(); - // Sanity-check - { - let cs = ConstraintSystem::::new_ref(); - - circuit.generate_constraints(cs.clone()).unwrap(); - println!("Num constraints: {}", cs.num_constraints()); - assert!(cs.is_satisfied().unwrap()); - } - - let params = - Groth16::::generate_random_parameters_with_reduction(circuit, rng).unwrap(); - - // prepare a proof (note: there is nothing trustable about the trivial setup involved here) - let proof = Proof::from( - Groth16::::create_random_proof_with_reduction(circuit, ¶ms, &mut rng) - .unwrap(), - ); - - println!( - "Generated proof of knowledge of Blake2s preimage of {}", - hex::encode(circuit.expected_output) - ); - - // prepare the verification key - let pvk = PreparedVerifyingKey::from(¶ms.vk.into()); - - // provide the public inputs (the hash target) for verification - let inputs: Vec = [&circuit.blake2_seed[..], &circuit.expected_output[..]] - .iter() - .flat_map::, _>(|x| x.to_field_elements().unwrap()) - .map(FieldElement::from) - .collect(); - - // Verify the proof - assert!(pvk.verify(&inputs, &proof).unwrap()); - println!( - "Checked proof of knowledge of Blake2s preimage of {}!", - hex::encode(circuit.expected_output) - ); -} diff --git a/fastcrypto-zkp/src/bls12381/api.rs b/fastcrypto-zkp/src/bls12381/api.rs deleted file mode 100644 index 62b688ac61..0000000000 --- a/fastcrypto-zkp/src/bls12381/api.rs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2022, Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use fastcrypto::error::FastCryptoError; - -use crate::bls12381::verifier::PreparedVerifyingKey; -use crate::bls12381::FieldElement; -use crate::bls12381::Proof; -use crate::bls12381::VerifyingKey; - -#[cfg(test)] -#[path = "unit_tests/api_tests.rs"] -mod api_tests; - -/// Deserialize bytes as an Arkwork representation of a verifying key, and return a vector of the four components of a prepared verified key (see more at [`crate::verifier::PreparedVerifyingKey`]). -pub fn prepare_pvk_bytes(vk_bytes: &[u8]) -> Result>, FastCryptoError> { - let vk = VerifyingKey::deserialize(vk_bytes)?; - PreparedVerifyingKey::from(&vk).serialize() -} - -/// Verify Groth16 proof using the serialized form of the four components in a prepared verifying key -/// (see more at [`crate::verifier::PreparedVerifyingKey`]), serialized proof public input, which should -/// be concatenated serialized field elements of the scalar field of [`crate::conversions::SCALAR_SIZE`] -/// bytes each, and serialized proof points. -pub fn verify_groth16_in_bytes( - vk_gamma_abc_g1_bytes: &[u8], - alpha_g1_beta_g2_bytes: &[u8], - gamma_g2_neg_pc_bytes: &[u8], - delta_g2_neg_pc_bytes: &[u8], - proof_public_inputs_as_bytes: &[u8], - proof_points_as_bytes: &[u8], -) -> Result { - let x = FieldElement::deserialize_vector(proof_public_inputs_as_bytes)?; - let proof = Proof::deserialize(proof_points_as_bytes)?; - let blst_pvk = PreparedVerifyingKey::deserialize(&vec![ - vk_gamma_abc_g1_bytes, - alpha_g1_beta_g2_bytes, - gamma_g2_neg_pc_bytes, - delta_g2_neg_pc_bytes, - ])?; - blst_pvk.verify(x.as_slice(), &proof) -} diff --git a/fastcrypto-zkp/src/bls12381/api/conversions.rs b/fastcrypto-zkp/src/bls12381/api/conversions.rs new file mode 100644 index 0000000000..bd26d9e761 --- /dev/null +++ b/fastcrypto-zkp/src/bls12381/api/conversions.rs @@ -0,0 +1,225 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use fastcrypto::error::FastCryptoResult; +use fastcrypto::groups::bls12381::{ + GTElement, Scalar, FP_BYTE_LENGTH, GT_ELEMENT_BYTE_LENGTH, SCALAR_LENGTH, +}; +use fastcrypto::serde_helpers::ToFromByteArray; + +use crate::groth16::api::{FromLittleEndianByteArray, GTSerialize}; + +impl FromLittleEndianByteArray for Scalar { + fn from_little_endian_byte_array(bytes: &[u8; SCALAR_LENGTH]) -> FastCryptoResult { + let mut reversed = *bytes; + reversed.reverse(); + Scalar::from_byte_array(&reversed) + } +} + +impl GTSerialize for GTElement { + fn to_arkworks_bytes(&self) -> [u8; GT_ELEMENT_BYTE_LENGTH] { + gt_element_to_arkworks(&self.to_byte_array()) + } + + fn from_arkworks_bytes(bytes: &[u8; GT_ELEMENT_BYTE_LENGTH]) -> FastCryptoResult { + GTElement::from_byte_array(&arkworks_to_gt_element(bytes)) + } +} + +/// Split the input into `TOTAL_SIZE / permutation.len()` chunks, and permute the chunks according +/// to the given permutation. +fn permute_elements( + bytes: &[u8; TOTAL_SIZE], + permutation: &[usize], +) -> [u8; TOTAL_SIZE] { + let elements = permutation.len(); + assert_eq!(TOTAL_SIZE % elements, 0); + let element_size = TOTAL_SIZE / elements; + let mut result = [0u8; TOTAL_SIZE]; + for (from, to) in permutation.iter().enumerate() { + let from = from * element_size; + let to = to * element_size; + result[to..to + element_size].copy_from_slice(&bytes[from..from + element_size]); + } + result +} + +/// Reverse the endianness of each element in the input array, where each element is `N` bytes long. +fn reverse_endianness_for_elements( + bytes: &mut [u8; TOTAL_SIZE], + element_size: usize, +) { + assert_eq!(TOTAL_SIZE % element_size, 0); + bytes + .chunks_exact_mut(element_size) + .for_each(|chunk| chunk.reverse()); +} + +/// Given a serialization of a arkworks [`PairingOutput`] element, this function returns a +/// serialization of the corresponding [`GTElement`] element. It is _not_ verified whether the input +/// is a valid serialization. +fn arkworks_to_gt_element(bytes: &[u8; GT_ELEMENT_BYTE_LENGTH]) -> [u8; GT_ELEMENT_BYTE_LENGTH] { + // This permutation flips the order of the i in 0..3 and j in 0..2 loops and may be computed as: + // for i in 0..3 { + // for j in 0..2 { + // PERMUTATION[i + j * 3] = i * 2 + j; + // } + // } + let mut bytes = permute_elements(bytes, &[0, 2, 4, 1, 3, 5]); + reverse_endianness_for_elements(&mut bytes, FP_BYTE_LENGTH); + bytes +} + +/// Given a serialization of a [`GTElement`], this function returns a serialization of the +/// corresponding element as a arkworks [`PairingOutput`] type. It is _not_ verified whether the +/// input is a valid serialization. +fn gt_element_to_arkworks(bytes: &[u8; GT_ELEMENT_BYTE_LENGTH]) -> [u8; GT_ELEMENT_BYTE_LENGTH] { + // This is the inverse of the permutation in `arkworks_to_gt_element`. + let mut bytes = permute_elements(bytes, &[0, 3, 1, 4, 2, 5]); + reverse_endianness_for_elements(&mut bytes, FP_BYTE_LENGTH); + bytes +} + +#[cfg(test)] +mod tests { + use ark_bls12_381::{Bls12_381, Fr, G1Projective, G2Projective}; + use ark_ec::pairing::PairingOutput; + use ark_ec::Group; + use ark_ff::Zero; + use ark_serialize::CanonicalSerialize; + use fastcrypto::error::FastCryptoError; + + use crate::bls12381::api::conversions::{arkworks_to_gt_element, gt_element_to_arkworks}; + use crate::groth16::api::{FromLittleEndianByteArray, GTSerialize}; + use fastcrypto::groups::bls12381::{ + G1Element, G2Element, GTElement, Scalar, G1_ELEMENT_BYTE_LENGTH, G2_ELEMENT_BYTE_LENGTH, + GT_ELEMENT_BYTE_LENGTH, SCALAR_LENGTH, + }; + use fastcrypto::groups::GroupElement; + use fastcrypto::serde_helpers::ToFromByteArray; + + #[test] + fn test_gt_element_conversion() { + let generator = PairingOutput::::generator(); + let mut arkworks_bytes = Vec::new(); + let mut uncompressed_bytes = Vec::new(); + + // GT elements cannot be compressed, so compressed and uncompressed serialization should be the same. + generator.serialize_compressed(&mut arkworks_bytes).unwrap(); + generator + .serialize_uncompressed(&mut uncompressed_bytes) + .unwrap(); + assert_eq!(arkworks_bytes, uncompressed_bytes); + + // The arkworks serialization does not match the GroupElement serialization. + let fc_bytes = GTElement::generator().to_byte_array(); + assert_eq!(arkworks_bytes.len(), fc_bytes.len()); + assert_ne!(arkworks_bytes, fc_bytes); + + // After conversion, the arkworks serialization should match the GroupElement serialization. + assert_eq!(arkworks_bytes, gt_element_to_arkworks(&fc_bytes)); + assert_eq!( + arkworks_to_gt_element(&arkworks_bytes.try_into().unwrap()), + fc_bytes + ); + + // Compare serializations of the identity element + let arkworks_id = PairingOutput::::zero(); + let mut arkworks_bytes = Vec::new(); + arkworks_id + .serialize_uncompressed(&mut arkworks_bytes) + .unwrap(); + let fc_bytes = GTElement::zero().to_byte_array(); + assert_ne!(&fc_bytes.to_vec(), &arkworks_bytes); + assert_eq!(>_element_to_arkworks(&fc_bytes).to_vec(), &arkworks_bytes); + assert_eq!( + arkworks_to_gt_element(&arkworks_bytes.try_into().unwrap()), + fc_bytes + ); + } + + fn test_arkworks_compatability_for_group_element< + const SIZE: usize, + G: GroupElement, + A: Group + CanonicalSerialize, + >( + g: G, + a: A, + serializer: fn(G) -> [u8; SIZE], + deserializer: fn(&[u8; SIZE]) -> Result, + ) { + let bytes = serializer(g); + let mut arkworks_bytes = Vec::new(); + a.serialize_compressed(&mut arkworks_bytes).unwrap(); + assert_eq!(bytes.to_vec(), arkworks_bytes); + assert_eq!(bytes.len(), SIZE); + + let g2 = deserializer(&bytes).unwrap(); + assert_eq!(g, g2); + + let a2 = A::deserialize_compressed(&arkworks_bytes[..]).unwrap(); + assert_eq!(a, a2); + } + + fn test_arkworks_compatability_for_group< + const SIZE: usize, + G: GroupElement + ToFromByteArray, + A: Group + CanonicalSerialize, + >( + serializer: fn(G) -> [u8; SIZE], + deserializer: fn(&[u8; SIZE]) -> Result, + ) { + test_arkworks_compatability_for_group_element::( + G::zero(), + A::zero(), + serializer, + deserializer, + ); + test_arkworks_compatability_for_group_element::( + G::generator(), + A::generator(), + serializer, + deserializer, + ); + let scalar = 12345u128; + test_arkworks_compatability_for_group_element::( + G::generator() * G::ScalarType::from(scalar), + A::generator() * A::ScalarField::from(scalar), + serializer, + deserializer, + ); + } + + #[test] + fn test_arkworks_compatability() { + test_arkworks_compatability_for_group::( + |g| g.to_byte_array(), + G1Element::from_byte_array, + ); + test_arkworks_compatability_for_group::( + |g| g.to_byte_array(), + G2Element::from_byte_array, + ); + test_arkworks_compatability_for_group::>( + |g| g.to_arkworks_bytes(), + GTElement::from_arkworks_bytes, + ); + } + + #[test] + fn test_from_le_bytes() { + let x = 12345678u128; + let arkworks_scalar = Fr::from(x); + let mut arkworks_bytes = Vec::new(); + arkworks_scalar + .serialize_compressed(&mut arkworks_bytes) + .unwrap(); + assert_eq!(arkworks_bytes.len(), SCALAR_LENGTH); + assert_eq!(arkworks_bytes[..16], x.to_le_bytes()); + + let scalar = + Scalar::from_little_endian_byte_array(&arkworks_bytes.try_into().unwrap()).unwrap(); + assert_eq!(scalar, Scalar::from(x)); + } +} diff --git a/fastcrypto-zkp/src/bls12381/api/mod.rs b/fastcrypto-zkp/src/bls12381/api/mod.rs new file mode 100644 index 0000000000..8ead029502 --- /dev/null +++ b/fastcrypto-zkp/src/bls12381/api/mod.rs @@ -0,0 +1,52 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use fastcrypto::error::FastCryptoError; +use fastcrypto::groups::bls12381::{ + G1Element, G1_ELEMENT_BYTE_LENGTH, G2_ELEMENT_BYTE_LENGTH, GT_ELEMENT_BYTE_LENGTH, + SCALAR_LENGTH, +}; + +use crate::groth16::api; + +mod conversions; +#[cfg(test)] +mod tests; + +/// Create a prepared verifying key for Groth16 over the BLS12-381 curve construction. See +/// [`api::prepare_pvk_bytes`]. +pub fn prepare_pvk_bytes(vk_bytes: &[u8]) -> Result>, FastCryptoError> { + api::prepare_pvk_bytes::< + G1Element, + { G1_ELEMENT_BYTE_LENGTH }, + { G2_ELEMENT_BYTE_LENGTH }, + { GT_ELEMENT_BYTE_LENGTH }, + { SCALAR_LENGTH }, + >(vk_bytes) +} + +/// Verify Groth16 proof over the BLS12-381 curve construction. See +/// [`api::verify_groth16_in_bytes`]. +pub fn verify_groth16_in_bytes( + vk_gamma_abc_g1_bytes: &[u8], + alpha_g1_beta_g2_bytes: &[u8], + gamma_g2_neg_pc_bytes: &[u8], + delta_g2_neg_pc_bytes: &[u8], + public_inputs_as_bytes: &[u8], + proof_points_as_bytes: &[u8], +) -> Result { + api::verify_groth16_in_bytes::< + G1Element, + { G1_ELEMENT_BYTE_LENGTH }, + { G2_ELEMENT_BYTE_LENGTH }, + { GT_ELEMENT_BYTE_LENGTH }, + { SCALAR_LENGTH }, + >( + vk_gamma_abc_g1_bytes, + alpha_g1_beta_g2_bytes, + gamma_g2_neg_pc_bytes, + delta_g2_neg_pc_bytes, + public_inputs_as_bytes, + proof_points_as_bytes, + ) +} diff --git a/fastcrypto-zkp/src/bls12381/unit_tests/api_tests.rs b/fastcrypto-zkp/src/bls12381/api/tests.rs similarity index 62% rename from fastcrypto-zkp/src/bls12381/unit_tests/api_tests.rs rename to fastcrypto-zkp/src/bls12381/api/tests.rs index 8d4885676f..372204948a 100644 --- a/fastcrypto-zkp/src/bls12381/unit_tests/api_tests.rs +++ b/fastcrypto-zkp/src/bls12381/api/tests.rs @@ -4,20 +4,82 @@ use std::ops::Mul; use ark_bls12_381::{Bls12_381, Fq12, Fr, G1Affine}; -use ark_ff::Zero; +use ark_ff::One; use ark_groth16::Groth16; use ark_serialize::CanonicalSerialize; use ark_snark::SNARK; use ark_std::rand::thread_rng; use ark_std::UniformRand; +use fastcrypto::groups::bls12381::{G1Element, G2Element}; use fastcrypto::groups::GroupElement; use fastcrypto::serde_helpers::ToFromByteArray; use crate::bls12381::api::{prepare_pvk_bytes, verify_groth16_in_bytes}; -use crate::bls12381::verifier::PreparedVerifyingKey; -use crate::bls12381::FieldElement; +use crate::bls12381::test_helpers::from_arkworks_scalar; +use crate::bls12381::{PreparedVerifyingKey, VerifyingKey}; use crate::dummy_circuits::{DummyCircuit, Fibonacci}; +use crate::groth16::Proof; + +#[test] +fn test_verify() { + // Success case. + let mut vk_bytes = hex::decode("ada3c24e8c2e63579cc03fd1f112a093a17fc8ab0ff6eee7e04cab7bf8e03e7645381f309ec113309e05ac404c77ac7c8585d5e4328594f5a70a81f6bd4f29073883ee18fd90e2aa45d0fc7376e81e2fdf5351200386f5732e58eb6ff4d318dc").unwrap(); + let alpha_bytes = hex::decode("8b0f85a9e7d929244b0af9a35af10717bd667b6227aae37a6d336e815fb0d850873e0d87968345a493b2d31aa8aa400d9820af1d35fa862d1b339ea1f98ac70db7faa304bff120a151a1741d782d08b8f1c1080d4d2f3ebee63ac6cadc666605be306de0973be38fbbf0f54b476bbb002a74ff9506a2b9b9a34b99bfa7481a84a2c9face7065c19d7069cc5738c5350b886a5eeebe656499d2ffb360afc7aff20fa9ee689fb8b46863e90c85224e8f597bf323ad4efb02ee96eb40221fc89918a2c740eabd2886476c7f247a3eb34f0106b3b51cf040e2cdcafea68b0d8eecabf58b5aa2ece3d86259cf2dfa3efab1170c6eb11948826def533849b68335d76d60f3e16bb5c629b1c24df2bdd1a7f13c754d7fe38617ecd7783504e4615e5c13168185cc08de8d63a0f7032ab7e82ff78cf0bc46a84c98f2d95bb5af355cbbe525c44d5c1549c169dfe119a219dbf9038ec73729d187bd0e3ed369e4a2ec2be837f3dcfd958aea7110627d2c0192d262f17e722509c17196005b646a556cf010ef9bd2a2a9b937516a5ecdee516e77d14278e96bc891b630fc833dda714343554ae127c49460416430b7d4f048d08618058335dec0728ad37d10dd9d859c385a38673e71cc98e8439da0accc29de5c92d3c3dc98e199361e9f7558e8b0a2a315ccc5a72f54551f07fad6f6f4615af498aba98aea01a13a4eb84667fd87ee9782b1d812a03f8814f042823a7701238d0fec1e7dec2a26ffea00330b5c7930e95138381435d2a59f51313a48624e30b0a685e357874d41a0a19d83f7420c1d9c04").unwrap(); + let gamma_bytes = hex::decode("b675d1ff988116d1f2965d3c0c373569b74d0a1762ea7c4f4635faa5b5a8fa198a2a2ce6153f390a658dc9ad01a415491747e9de7d5f493f59cf05a52eb46eaac397ffc47aef1396cf0d8b75d0664077ea328ad6b63284b42972a8f11c523a60").unwrap(); + let delta_bytes = hex::decode("8229cb9443ef1fb72887f917f500e2aef998717d91857bcb92061ecd74d1d24c2b2b282736e8074e4316939b4c9853c117aa08ed49206860d648818b2cccb526585f5790161b1730d39c73603b482424a27bba891aaa6d99f3025d3df2a6bd42").unwrap(); + + let inputs_bytes = + hex::decode("440758042e68b76a376f2fecf3a5a8105edb194c3e774e5a760140305aec8849").unwrap(); + let proof_bytes = hex::decode("a29981304df8e0f50750b558d4de59dbc8329634b81c986e28e9fff2b0faa52333b14a1f7b275b029e13499d1f5dd8ab955cf5fa3000a097920180381a238ce12df52207597eade4a365a6872c0a19a39c08a9bfb98b69a15615f90cc32660180ca32e565c01a49b505dd277713b1eae834df49643291a3601b11f56957bde02d5446406d0e4745d1bd32c8ccb8d8e80b877712f5f373016d2ecdeebb58caebc7a425b8137ebb1bd0c5b81c1d48151b25f0f24fe9602ba4e403811fb17db6f14").unwrap(); + + // Success case + assert!(verify_groth16_in_bytes( + &vk_bytes, + &alpha_bytes, + &gamma_bytes, + &delta_bytes, + &inputs_bytes, + &proof_bytes + ) + .unwrap()); + + // Invalid public inputs bytes. + let invalid_inputs = hex::decode("cf").unwrap(); + assert!(verify_groth16_in_bytes( + &alpha_bytes, + &gamma_bytes, + &delta_bytes, + &inputs_bytes, + &invalid_inputs, + &proof_bytes + ) + .is_err()); + + // Invalid proof bytes. + let invalid_proof = hex::decode("4a").unwrap(); + assert!(verify_groth16_in_bytes( + &alpha_bytes, + &gamma_bytes, + &delta_bytes, + &inputs_bytes, + &inputs_bytes, + &invalid_proof + ) + .is_err()); + + // Invalid prepared verifying key. + vk_bytes.pop(); + assert!(verify_groth16_in_bytes( + &vk_bytes, + &alpha_bytes, + &gamma_bytes, + &delta_bytes, + &inputs_bytes, + &proof_bytes + ) + .is_err()); +} #[test] fn test_verify_groth16_in_bytes_api() { @@ -32,21 +94,24 @@ fn test_verify_groth16_in_bytes_api() { let (pk, vk) = Groth16::::circuit_specific_setup(c, rng).unwrap(); let proof = Groth16::::prove(&pk, c, rng).unwrap(); - let v = c.a.unwrap().mul(c.b.unwrap()); + let public_input = c.a.unwrap().mul(c.b.unwrap()); + let mut vk_bytes = vec![]; vk.serialize_compressed(&mut vk_bytes).unwrap(); - let bytes = prepare_pvk_bytes(vk_bytes.as_slice()).unwrap(); + let bytes = prepare_pvk_bytes(&vk_bytes).unwrap(); let vk_gamma_abc_g1_bytes = &bytes[0]; let alpha_g1_beta_g2_bytes = &bytes[1]; let gamma_g2_neg_pc_bytes = &bytes[2]; let delta_g2_neg_pc_bytes = &bytes[3]; - let mut proof_inputs_bytes = vec![]; - v.serialize_compressed(&mut proof_inputs_bytes).unwrap(); + let mut public_inputs_bytes = vec![]; + public_input + .serialize_compressed(&mut public_inputs_bytes) + .unwrap(); - let mut proof_points_bytes = vec![]; - proof.serialize_compressed(&mut proof_points_bytes).unwrap(); + let mut proof_bytes = vec![]; + proof.serialize_compressed(&mut proof_bytes).unwrap(); // Success case. assert!(verify_groth16_in_bytes( @@ -54,13 +119,13 @@ fn test_verify_groth16_in_bytes_api() { alpha_g1_beta_g2_bytes, gamma_g2_neg_pc_bytes, delta_g2_neg_pc_bytes, - &proof_inputs_bytes, - &proof_points_bytes + &public_inputs_bytes, + &proof_bytes ) .unwrap()); // Negative test: Replace the A element with a random point. - let mut modified_proof_points_bytes = proof_points_bytes.clone(); + let mut modified_proof_points_bytes = proof_bytes.clone(); let _ = &G1Affine::rand(rng) .serialize_compressed(&mut modified_proof_points_bytes[0..48]) .unwrap(); @@ -69,7 +134,7 @@ fn test_verify_groth16_in_bytes_api() { alpha_g1_beta_g2_bytes, gamma_g2_neg_pc_bytes, delta_g2_neg_pc_bytes, - &proof_inputs_bytes, + &public_inputs_bytes, &modified_proof_points_bytes ) .unwrap()); @@ -82,13 +147,13 @@ fn test_verify_groth16_in_bytes_api() { alpha_g1_beta_g2_bytes, gamma_g2_neg_pc_bytes, delta_g2_neg_pc_bytes, - &proof_inputs_bytes, - &proof_points_bytes + &public_inputs_bytes, + &proof_bytes ) .is_err()); // Length of public inputs is incorrect. - let mut modified_proof_inputs_bytes = proof_inputs_bytes.clone(); + let mut modified_proof_inputs_bytes = public_inputs_bytes.clone(); modified_proof_inputs_bytes.pop(); assert!(verify_groth16_in_bytes( vk_gamma_abc_g1_bytes, @@ -96,24 +161,41 @@ fn test_verify_groth16_in_bytes_api() { gamma_g2_neg_pc_bytes, delta_g2_neg_pc_bytes, &modified_proof_inputs_bytes, - &proof_points_bytes + &proof_bytes ) .is_err()); // length of proof is incorrect - let mut modified_proof_points_bytes = proof_points_bytes.to_vec(); + let mut modified_proof_points_bytes = proof_bytes.to_vec(); modified_proof_points_bytes.pop(); assert!(verify_groth16_in_bytes( vk_gamma_abc_g1_bytes, alpha_g1_beta_g2_bytes, gamma_g2_neg_pc_bytes, delta_g2_neg_pc_bytes, - &proof_inputs_bytes, + &public_inputs_bytes, &modified_proof_points_bytes ) .is_err()); } +#[test] +fn test_prepare_pvk_bytes_regression() { + // Test vector + let vk_bytes = hex::decode("a84d039ad1ae98eeeee4c8ba9af9b6c5d1cfcb98c3fc92ccfcebd77bcccffa1d170d39da29e9b4aa83b98680cb90bb25946b2b70f9e3565510c5361d5d65cb458a0b3177d612dd340b8f8f8493c2772454e3e8f577a3f77865df851d1a159b800c2ec5bae889029fc419678e83dee900465d60e7ef26f614940e719c6f7c0c7db57464fa0481a93c18d52cb2fbf8dcf0a398b153643614fc1071a54e288edb6402f1d9e00d3408c76d95c16885cc992dff5c6ebee3b739cb22359ab2d126026a1626c43ea7b898a7c1d2904c1bd4bbce5d0b1b16fab8535a52d1b08a5217df2e912ee1b0f4140892afa31d479f78dfbc82ab58a209ad00df6c86ab14841e8daa7a380a6853f28bacf38aad9903b6149fff4b119dea16de8aa3e5050b9d563a01009e061a950c233f66511c8fae2a8c58503059821df7f6defbba8f93d26e412cc07b66a9f3cdd740cce5c8488ce94fc8020000000000000081aabea18713222ac45a6ef3208a09f55ce2dde8a11cc4b12788be2ae77ae318176d631d36d80942df576af651b57a31a95f2e9bcaebbb53a588251634715599f7a7e9d51fe872fe312edf0b39d98f0d7f8b5554f96f759c041ea38b4b1e5e19").unwrap(); + let expected_vk_bytes = hex::decode("81aabea18713222ac45a6ef3208a09f55ce2dde8a11cc4b12788be2ae77ae318176d631d36d80942df576af651b57a31a95f2e9bcaebbb53a588251634715599f7a7e9d51fe872fe312edf0b39d98f0d7f8b5554f96f759c041ea38b4b1e5e19").unwrap(); + let expected_alpha_bytes = hex::decode("097ca8074c7f1d661e25d70fc2e6f14aa874dabe3d8a5d7751a012a737d30b59fc0f5f6d4ce0ea6f6c4562912dfb2a1442df06f9f0b8fc2d834ca007c8620823926b2fc09367d0dfa9b205a216921715e13deedd93580c77cae413cbb83134051cb724633c58759c77e4eda4147a54b03b1f443b68c65247166465105ab5065847ae61ba9d8bdfec536212b0dadedc042dab119d0eeea16349493a4118d481761b1e75f559fbad57c926d599e81d98dde586a2cfcc37b49972e2f9db554e5a0ba56bec2d57a8bfed629ae29c95002e3e943311b7b0d1690d2329e874b179ce5d720bd7c5fb5a2f756b37e3510582cb0c0f8fc8047305fc222c309a5a8234c5ff31a7b311aabdcebf4a43d98b69071a9e5796372146f7199ba05f9ca0a3d14b0c421e7f1bd02ac87b365fd8ce992c0f87994d0ca66f75c72fed0ce94ca174fcb9e5092f0474e07e71e9fd687b3daa441193f264ca2059760faa9c5ca5ef38f6ecefef2ac7d8c47df67b99c36efa64f625fe3f55f40ad1865abbdf2ff4c3fc3a162e28b953f6faec70a6a61c76f4dca1eecc86544b88352994495ae7fc7a77d387880e59b2357d9dd1277ae7f7ee9ba00b440e0e6923dc3971de9050a977db59d767195622f200f2bf0d00e4a986e94a6932627954dd2b7da39b4fcb32c991a0190bdc44562ad83d34e0af7656b51d6cde03530b5d523380653130b87346720ad6dd425d8133ffb02f39a95fc70e9707181ecb168bd8d2d0e9e85e262255fecab15f1ada809ecbefa42a7082fa7326a1d494261a8954fe5b215c5b761fb10b7f18").unwrap(); + let expected_gamma_bytes = hex::decode("8398b153643614fc1071a54e288edb6402f1d9e00d3408c76d95c16885cc992dff5c6ebee3b739cb22359ab2d126026a1626c43ea7b898a7c1d2904c1bd4bbce5d0b1b16fab8535a52d1b08a5217df2e912ee1b0f4140892afa31d479f78dfbc").unwrap(); + let expected_delta_bytes = hex::decode("a2ab58a209ad00df6c86ab14841e8daa7a380a6853f28bacf38aad9903b6149fff4b119dea16de8aa3e5050b9d563a01009e061a950c233f66511c8fae2a8c58503059821df7f6defbba8f93d26e412cc07b66a9f3cdd740cce5c8488ce94fc8").unwrap(); + + let prepared_vk_bytes = prepare_pvk_bytes(&vk_bytes).unwrap(); + + assert_eq!(prepared_vk_bytes[0], expected_vk_bytes); + assert_eq!(prepared_vk_bytes[1], expected_alpha_bytes); + assert_eq!(prepared_vk_bytes[2], expected_gamma_bytes); + assert_eq!(prepared_vk_bytes[3], expected_delta_bytes); +} + #[test] fn test_prepare_pvk_bytes() { const PUBLIC_SIZE: usize = 128; @@ -164,12 +246,17 @@ fn test_verify_groth16_in_bytes_multiple_inputs() { proof.b.serialize_compressed(&mut proof_bytes).unwrap(); proof.c.serialize_compressed(&mut proof_bytes).unwrap(); - let pvk = PreparedVerifyingKey::from(¶ms.vk.into()); + let mut vk_bytes = Vec::new(); + params.vk.serialize_compressed(&mut vk_bytes).unwrap(); + let vk = VerifyingKey::from_arkworks_format(&vk_bytes).unwrap(); + let pvk = PreparedVerifyingKey::from(&vk); + + let inputs: Vec<_> = vec![from_arkworks_scalar(&a), from_arkworks_scalar(&b)]; - let inputs: Vec<_> = [FieldElement(a), FieldElement(b)].to_vec(); - assert!(pvk.verify(&inputs, &proof.into()).unwrap()); + let proof: Proof = bcs::from_bytes(&proof_bytes).unwrap(); + assert!(pvk.verify(&inputs, &proof).is_ok()); - let pvk = pvk.serialize().unwrap(); + let pvk = pvk.serialize_into_parts(); // This circuit has two public inputs: let mut inputs_bytes = Vec::new(); @@ -251,25 +338,23 @@ fn api_regression_tests() { .to_vec(); // Expects two public inputs, so the vk needs three elements here - vk_gamma_abc_g1_bytes - .extend_from_slice(&fastcrypto::groups::bls12381::G1Element::zero().to_byte_array()); - vk_gamma_abc_g1_bytes - .extend_from_slice(&fastcrypto::groups::bls12381::G1Element::zero().to_byte_array()); + vk_gamma_abc_g1_bytes.extend_from_slice(&G1Element::zero().to_byte_array()); + vk_gamma_abc_g1_bytes.extend_from_slice(&G1Element::zero().to_byte_array()); // The API expects serialization like in Arkworks for GT elements let mut alpha_g1_beta_g2_bytes = vec![]; - Fq12::zero() + Fq12::one() .serialize_compressed(&mut alpha_g1_beta_g2_bytes) .unwrap(); - let gamma_g2_neg_pc_bytes = fastcrypto::groups::bls12381::G2Element::zero().to_byte_array(); - let delta_g2_neg_pc_bytes = fastcrypto::groups::bls12381::G2Element::zero().to_byte_array(); + let gamma_g2_neg_pc_bytes = G2Element::zero().to_byte_array(); + let delta_g2_neg_pc_bytes = G2Element::zero().to_byte_array(); let public_inputs_bytes = hex::decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(); let mut proof_bytes = vec![]; - proof_bytes.extend_from_slice(&fastcrypto::groups::bls12381::G1Element::zero().to_byte_array()); - proof_bytes.extend_from_slice(&fastcrypto::groups::bls12381::G2Element::zero().to_byte_array()); - proof_bytes.extend_from_slice(&fastcrypto::groups::bls12381::G1Element::zero().to_byte_array()); + proof_bytes.extend_from_slice(&G1Element::zero().to_byte_array()); + proof_bytes.extend_from_slice(&G2Element::zero().to_byte_array()); + proof_bytes.extend_from_slice(&G1Element::zero().to_byte_array()); // The trivial proof should pass verification assert!(verify_groth16_in_bytes( diff --git a/fastcrypto-zkp/src/bls12381/conversions.rs b/fastcrypto-zkp/src/bls12381/conversions.rs deleted file mode 100644 index c0a6858c9e..0000000000 --- a/fastcrypto-zkp/src/bls12381/conversions.rs +++ /dev/null @@ -1,826 +0,0 @@ -// Copyright (c) 2022, Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 -use ark_ff::{BigInteger384, Fp384, PrimeField, Zero}; -use ark_serialize::{CanonicalSerialize, CanonicalSerializeWithFlags, Compress, EmptyFlags}; -use blst::{blst_fp, blst_fp12, blst_fp6, blst_fp_from_lendian, blst_p1_affine}; -use blst::{blst_fp2, blst_p1_deserialize}; -use blst::{blst_p1_affine_serialize, blst_uint64_from_fp}; -use blst::{blst_p2_affine, blst_p2_affine_serialize, blst_p2_deserialize, BLST_ERROR}; - -pub use ark_bls12_381::Fr as BlsFr; -use ark_bls12_381::{Fq, Fq2}; -use ark_bls12_381::{Fq12, G2Affine as BlsG2Affine}; -use ark_bls12_381::{Fq6, G1Affine as BlsG1Affine}; -use ark_ec::AffineRepr; - -use ark_serialize::CanonicalDeserialize; -use blst::{blst_fr, blst_fr_from_uint64, blst_uint64_from_fr}; -use byte_slice_cast::AsByteSlice; - -/// Size of scalar elements. -pub const SCALAR_SIZE: usize = 32; -/// G1 affine point compressed size. -pub const G1_COMPRESSED_SIZE: usize = 48; -const G2_COMPRESSED_SIZE: usize = 96; - -const G1_UNCOMPRESSED_SIZE: usize = 96; -const G2_UNCOMPRESSED_SIZE: usize = 192; - -#[inline] -fn u64s_from_bytes(bytes: &[u8; 32]) -> [u64; 4] { - [ - u64::from_le_bytes(bytes[0..8].try_into().unwrap()), - u64::from_le_bytes(bytes[8..16].try_into().unwrap()), - u64::from_le_bytes(bytes[16..24].try_into().unwrap()), - u64::from_le_bytes(bytes[24..32].try_into().unwrap()), - ] -} - -// Scalar Field conversions -/// Convert an Arkworks BLS12-381 scalar field element to a blst scalar field element. -pub fn bls_fr_to_blst_fr(fe: &BlsFr) -> blst_fr { - debug_assert_eq!(fe.serialized_size(Compress::Yes), SCALAR_SIZE); - let mut bytes = [0u8; SCALAR_SIZE]; - fe.serialize_with_flags(&mut bytes[..], EmptyFlags).unwrap(); - - let mut out = blst_fr::default(); - let bytes_u64 = u64s_from_bytes(&bytes); - - unsafe { blst_fr_from_uint64(&mut out, bytes_u64.as_ptr()) }; - out -} - -/// Convert a blst scalar field element to an Arkworks BLS12-381 scalar field element. -pub fn blst_fr_to_bls_fr(fe: &blst_fr) -> BlsFr { - let mut out = [0u64; 4]; - unsafe { blst_uint64_from_fr(out.as_mut_ptr(), fe) }; - let bytes = out.as_byte_slice(); - - BlsFr::from_le_bytes_mod_order(bytes) -} - -// Base Field conversions -/// Convert an Arkworks BLS12-381 prime field element to a blst prime field element. -pub fn bls_fq_to_blst_fp(f: &Fq) -> blst_fp { - let mut fp_bytes_le = [0u8; G1_UNCOMPRESSED_SIZE / 2]; - f.serialize_uncompressed(&mut fp_bytes_le[..]) - .expect("fp size correct"); - - let mut blst_fp = blst_fp::default(); - unsafe { - blst_fp_from_lendian(&mut blst_fp, fp_bytes_le.as_ptr()); - } - blst_fp -} - -/// Convert a blst prime field element to an Arkworks BLS12-381 prime field element. -pub fn blst_fp_to_bls_fq(f: &blst_fp) -> Fq { - let mut out = [0u64; 6]; - unsafe { blst_uint64_from_fp(out.as_mut_ptr(), f) }; - let bytes = out.as_byte_slice(); - Fq::deserialize_compressed(bytes).unwrap() -} - -// QFE conversions - -/// Convert an Arkworks BLS12-381 quadratic extension field element to a blst quadratic extension -/// field element. -pub fn bls_fq2_to_blst_fp2(f: &Fq2) -> blst_fp2 { - let mut fp_bytes_le = [0u8; G2_UNCOMPRESSED_SIZE / 2]; - f.serialize_uncompressed(&mut fp_bytes_le[..]) - .expect("fp size correct"); - - blst_fp2 { - fp: fp_bytes_le - .chunks(48) - .map(|fp_bytes| { - let mut blst_fp = blst_fp::default(); - unsafe { - blst_fp_from_lendian(&mut blst_fp, fp_bytes.as_ptr()); - } - blst_fp - }) - .collect::>() - .try_into() - .unwrap(), - } -} - -/// Convert a blst quadratic extension field element to an Arkworks BLS12-381 quadratic extension -/// field element. -pub fn blst_fp2_to_bls_fq2(f: &blst_fp2) -> Fq2 { - let [fp1, fp2] = f.fp; - let bls_fp1 = blst_fp_to_bls_fq(&fp1); - let bls_fp2 = blst_fp_to_bls_fq(&fp2); - Fq2::new(bls_fp1, bls_fp2) -} - -// Target Field conversions - -/// Convert an Arkworks BLS12-381 degree-6 extension field element to a blst degree-6 extension -/// field element. -pub fn bls_fq6_to_blst_fp6(f: &Fq6) -> blst_fp6 { - let c0 = bls_fq2_to_blst_fp2(&f.c0); - let c1 = bls_fq2_to_blst_fp2(&f.c1); - let c2 = bls_fq2_to_blst_fp2(&f.c2); - blst_fp6 { fp2: [c0, c1, c2] } -} - -/// Convert a blst degree-6 extension field element to an Arkworks BLS12-381 degree-6 extension -/// field element. -pub fn blst_fp6_to_bls_fq6(f: &blst_fp6) -> Fq6 { - let c0 = blst_fp2_to_bls_fq2(&f.fp2[0]); - let c1 = blst_fp2_to_bls_fq2(&f.fp2[1]); - let c2 = blst_fp2_to_bls_fq2(&f.fp2[2]); - Fq6::new(c0, c1, c2) -} - -/// Convert an Arkworks BLS12-381 target field element to a blst target field element. -pub fn bls_fq12_to_blst_fp12(f: &Fq12) -> blst_fp12 { - let c0 = bls_fq6_to_blst_fp6(&f.c0); - let c1 = bls_fq6_to_blst_fp6(&f.c1); - blst_fp12 { fp6: [c0, c1] } -} - -/// Convert a blst target field element to an Arkworks BLS12-381 target field element. -pub fn blst_fp12_to_bls_fq12(f: &blst_fp12) -> Fq12 { - let c0 = blst_fp6_to_bls_fq6(&f.fp6[0]); - let c1 = blst_fp6_to_bls_fq6(&f.fp6[1]); - Fq12::new(c0, c1) -} - -// Affine point translations: those mostly allow us to receive the -// proof points, provided in affine form. - -fn blst_g1_affine_infinity() -> blst_p1_affine { - blst_p1_affine { - x: blst_fp::default(), - y: blst_fp::default(), - } -} - -fn bls_g1_affine_infinity() -> BlsG1Affine { - BlsG1Affine::zero() -} - -/// Convert an Arkworks BLS12-381 affine G1 point to a blst affine G1 point. -pub fn bls_g1_affine_to_blst_g1_affine(pt: &BlsG1Affine) -> blst_p1_affine { - debug_assert_eq!(pt.uncompressed_size(), G1_UNCOMPRESSED_SIZE); - // BLST disagrees with Arkworks on the uncompressed representation of the infinity point on G1 - // https://github.com/supranational/blst/blob/6382d67c72119d563975892ed49ba32e92d3d0da/src/e1.c#L153-L162 - // the infinity bit notwithstanding, - // Arkworks: x = Fq::zero(), y = Fq::one() - // BLST: x = y = Fq::zero() - // BLST follows the standard here (see https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-10#appendix-C) - if pt.infinity { - return blst_g1_affine_infinity(); - } - let tmp_p1 = blst_p1_affine { - x: bls_fq_to_blst_fp(&pt.x), - y: bls_fq_to_blst_fp(&pt.y), - }; - // See https://github.com/arkworks-rs/curves/issues/14 for why the double serialize - // we're in fact applying correct masks that arkworks does not use. This may be solved - // alternatively using https://github.com/arkworks-rs/algebra/issues/308 in a later release of - // Arkworks. - // Note: this is an uncompressed serialization - deserialization. - let mut tmp2 = [0u8; 96]; - unsafe { - blst_p1_affine_serialize(tmp2.as_mut_ptr(), &tmp_p1); - }; - - let mut g1 = blst_p1_affine::default(); - let result = unsafe { blst_p1_deserialize(&mut g1, tmp2.as_ptr()) }; - debug_assert_eq!(result, BLST_ERROR::BLST_SUCCESS); - g1 -} - -/// Convert a blst affine G1 point to an Arkworks BLS12-381 affine G1 point. -pub fn blst_g1_affine_to_bls_g1_affine(pt: &blst_p1_affine) -> BlsG1Affine { - let mut out = [0u8; G1_UNCOMPRESSED_SIZE]; - unsafe { - blst_p1_affine_serialize(out.as_mut_ptr(), pt); - } - let infinity = out[0] == 0x40; - // BLST disagrees with Arkworks on the uncompressed representation of the infinity point on G1 - // https://github.com/supranational/blst/blob/6382d67c72119d563975892ed49ba32e92d3d0da/src/e1.c#L153-L162 - // the infinity bit notwithstanding, - // Arkworks: x = Fq::zero(), y = Fq::one() - // BLST: x = y = Fq::zero() - // BLST follows the standard here (see https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-10#appendix-C) - if infinity { - return bls_g1_affine_infinity(); - } - let y = Fp384::from_be_bytes_mod_order(&out[48..]); - let x = { - // Mask away the flag bits (which Arkworks doesn't use). - out[0] &= 0b0001_1111; - Fp384::from_be_bytes_mod_order(&out[..48]) - }; - BlsG1Affine::new(x, y) -} - -fn blst_g2_affine_infinity() -> blst_p2_affine { - blst_p2_affine { - x: blst_fp2::default(), - y: blst_fp2::default(), - } -} - -fn bls_g2_affine_infinity() -> BlsG2Affine { - BlsG2Affine::zero() -} - -/// Convert an Arkworks BLS12-381 affine G2 point to a blst affine G2 point. -pub fn bls_g2_affine_to_blst_g2_affine(pt: &BlsG2Affine) -> blst_p2_affine { - debug_assert_eq!(pt.uncompressed_size(), G2_UNCOMPRESSED_SIZE); - // BLST disagrees with Arkworks on the uncompressed representation of the infinity point on G2 - // https://github.com/supranational/blst/blob/6382d67c72119d563975892ed49ba32e92d3d0da/src/e2.c#L194-L203 - // the infinity bit notwithstanding, - // Arkworks: x = Fq2::zero(), y = Fq2::one() - // BLST: x = y = Fq2::zero() - // BLST follows the standard here (see https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-10#appendix-C) - if pt.infinity { - return blst_g2_affine_infinity(); - } - let tmp_p2 = blst_p2_affine { - x: bls_fq2_to_blst_fp2(&pt.x), - y: bls_fq2_to_blst_fp2(&pt.y), - }; - // See https://github.com/arkworks-rs/curves/issues/14 for why the double serialize - // we're in fact applying correct masks that arkworks does not use. This may be solved - // alternatively using https://github.com/arkworks-rs/algebra/issues/308 in a later release of - // Arkworks. - // Note: this is an uncompressed serialization - deserialization. - let mut tmp2 = [0u8; G2_UNCOMPRESSED_SIZE]; - unsafe { - blst_p2_affine_serialize(tmp2.as_mut_ptr(), &tmp_p2); - }; - - let mut g2 = blst_p2_affine::default(); - let result = unsafe { blst_p2_deserialize(&mut g2, tmp2.as_ptr()) }; - debug_assert_eq!(result, BLST_ERROR::BLST_SUCCESS); - g2 -} - -/// Convert a blst affine G2 point to an Arkworks BLS12-381 affine G2 point. -pub fn blst_g2_affine_to_bls_g2_affine(pt: &blst_p2_affine) -> BlsG2Affine { - let ptx = blst_fp2_to_bls_fq2(&pt.x); - let pty = blst_fp2_to_bls_fq2(&pt.y); - - let infinity = ptx == Fq2::zero(); - // BLST disagrees with Arkworks on the uncompressed representation of the infinity point on G2 - // https://github.com/supranational/blst/blob/6382d67c72119d563975892ed49ba32e92d3d0da/src/e2.c#L194-L203 - // the infinity bit notwithstanding, - // Arkworks: x = Fq2::zero(), y = Fq2::one() - // BLST: x = y = Fq2::zero() - // BLST follows the standard here (see https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-10#appendix-C) - if infinity { - bls_g2_affine_infinity() - } else { - BlsG2Affine::new(ptx, pty) - } -} - -///////////////////////////////////////////////////////////// -// Zcash point encodings to Arkworks points and back // -///////////////////////////////////////////////////////////// - -// The standard for serialization of compressed G1, G2 points of BLS12-381 -// is not followed by Arkworks 0.3.0. This is a translation layer to allow -// us to use (and receive) the standard serialization format. -// See section 5.4.9.2 of https://zips.z.cash/protocol/protocol.pdf -// and https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/ Appendix A -// for its choice as a standard. - -// A note on BLST: blst uses Zcash point encoding formats across the board. - -fn bls_fq_to_zcash_bytes(field: &Fq) -> [u8; G1_COMPRESSED_SIZE] { - let mut result = [0u8; G1_COMPRESSED_SIZE]; - - let rep = field.into_bigint(); - - result[0..8].copy_from_slice(&rep.0[5].to_be_bytes()); - result[8..16].copy_from_slice(&rep.0[4].to_be_bytes()); - result[16..24].copy_from_slice(&rep.0[3].to_be_bytes()); - result[24..32].copy_from_slice(&rep.0[2].to_be_bytes()); - result[32..40].copy_from_slice(&rep.0[1].to_be_bytes()); - result[40..48].copy_from_slice(&rep.0[0].to_be_bytes()); - - result -} - -fn bls_fq_from_zcash_bytes(bytes: &[u8; G1_COMPRESSED_SIZE]) -> Option { - let mut tmp = BigInteger384::default(); - - tmp.0[5] = u64::from_be_bytes(bytes[0..8].try_into().unwrap()); - tmp.0[4] = u64::from_be_bytes(bytes[8..16].try_into().unwrap()); - tmp.0[3] = u64::from_be_bytes(bytes[16..24].try_into().unwrap()); - tmp.0[2] = u64::from_be_bytes(bytes[24..32].try_into().unwrap()); - tmp.0[1] = u64::from_be_bytes(bytes[32..40].try_into().unwrap()); - tmp.0[0] = u64::from_be_bytes(bytes[40..48].try_into().unwrap()); - - Fq::from_bigint(tmp) -} - -struct EncodingFlags { - is_compressed: bool, - is_infinity: bool, - is_lexicographically_largest: bool, -} - -impl From<&[u8]> for EncodingFlags { - fn from(bytes: &[u8]) -> Self { - Self { - is_compressed: ((bytes[0] >> 7) & 1) == 1, - is_infinity: (bytes[0] >> 6) & 1 == 1, - is_lexicographically_largest: (bytes[0] >> 5) & 1 == 1, - } - } -} - -impl EncodingFlags { - fn encode_flags(&self, bytes: &mut [u8]) { - if self.is_compressed { - bytes[0] |= 1 << 7; - } - - if self.is_infinity { - bytes[0] |= 1 << 6; - } - - if self.is_compressed && !self.is_infinity && self.is_lexicographically_largest { - bytes[0] |= 1 << 5; - } - } -} - -/// This deserializes an Arkworks G1Affine point from a Zcash point encoding. -pub fn bls_g1_affine_from_zcash_bytes(bytes: &[u8; G1_COMPRESSED_SIZE]) -> Option { - // Obtain the three flags from the start of the byte sequence. - let flags = EncodingFlags::from(&bytes[..]); - - if !flags.is_compressed { - return None; // We only support compressed points. - } - - if flags.is_infinity { - return Some(BlsG1Affine::zero()); - } - // Attempt to obtain the x-coordinate. - let x = obtain_x_coordinate(bytes.as_slice())?; - - BlsG1Affine::get_point_from_x_unchecked(x, flags.is_lexicographically_largest) -} - -/// This serializes an Arkworks G1Affine point into a Zcash point encoding. -pub fn bls_g1_affine_to_zcash_bytes(p: &BlsG1Affine) -> [u8; G1_COMPRESSED_SIZE] { - let mut result = bls_fq_to_zcash_bytes(&p.x); - let encoding = EncodingFlags { - is_compressed: true, - is_infinity: p.infinity, - is_lexicographically_largest: p.y > -p.y, - }; - encoding.encode_flags(&mut result[..]); - result -} - -/// This deserializes an Arkworks G2Affine point from a Zcash point encoding. -pub fn bls_g2_affine_from_zcash_bytes(bytes: &[u8; G2_COMPRESSED_SIZE]) -> Option { - // Obtain the three flags from the start of the byte sequence - let flags = EncodingFlags::from(&bytes[..]); - - if !flags.is_compressed { - return None; // We only support compressed points. - } - - if flags.is_infinity { - return Some(BlsG2Affine::default()); - } - - let xc1 = obtain_x_coordinate(bytes.as_slice())?; - let xc0 = { - let mut tmp = [0; G1_COMPRESSED_SIZE]; - tmp.copy_from_slice(&bytes[48..96]); - - bls_fq_from_zcash_bytes(&tmp)? - }; - - let x = Fq2::new(xc0, xc1); - - BlsG2Affine::get_point_from_x_unchecked(x, flags.is_lexicographically_largest) -} - -/// This serializes an Arkworks G2Affine point into a Zcash point encoding. -pub fn bls_g2_affine_to_zcash_bytes(p: &BlsG2Affine) -> [u8; G2_COMPRESSED_SIZE] { - let mut bytes = [0u8; G2_COMPRESSED_SIZE]; - - let c1_bytes = bls_fq_to_zcash_bytes(&p.x.c1); - let c0_bytes = bls_fq_to_zcash_bytes(&p.x.c0); - bytes[0..48].copy_from_slice(&c1_bytes[..]); - bytes[48..96].copy_from_slice(&c0_bytes[..]); - - let encoding = EncodingFlags { - is_compressed: true, - is_infinity: p.infinity, - is_lexicographically_largest: p.y > -p.y, - }; - - encoding.encode_flags(&mut bytes[..]); - bytes -} - -/// Attempt to obtain x_coordinate -fn obtain_x_coordinate(bytes: &[u8]) -> Option { - let mut tmp = [0; G1_COMPRESSED_SIZE]; - // this is safe as the private obtain_x_coordinate function is only invoked with G1 (48) and - // G2 (96) size inputs. - tmp.copy_from_slice(&bytes[0..G1_COMPRESSED_SIZE]); - - // Mask away the flag bits - tmp[0] &= 0b0001_1111; - - bls_fq_from_zcash_bytes(&tmp) -} - -#[cfg(test)] -pub(crate) mod tests { - use super::*; - use ark_bls12_381::Fr as BlsFr; - use ark_ec::AffineRepr; - use ark_ff::Field; - use blst::{ - blst_encode_to_g1, blst_encode_to_g2, blst_fp_from_uint64, blst_fr, blst_fr_from_uint64, - blst_p1, blst_p1_affine_compress, blst_p1_to_affine, blst_p1_uncompress, blst_p2, - blst_p2_affine_compress, blst_p2_to_affine, blst_p2_uncompress, - }; - use proptest::{collection, prelude::*}; - use std::ops::Mul; - - // Scalar roundtrips. - - pub(crate) fn arb_bls_fr() -> impl Strategy { - collection::vec(any::(), 32..=32) - .prop_map(|bytes| BlsFr::from_random_bytes(&bytes[..])) - .prop_filter("Valid field elements", Option::is_some) - .prop_map(|opt_fr| opt_fr.unwrap()) - .no_shrink() // this is arbitrary. - } - - fn arb_blst_fr() -> impl Strategy { - collection::vec(any::(), 4..=4) - .prop_map(|u64s| { - let mut out = blst_fr::default(); - unsafe { blst_fr_from_uint64(&mut out, u64s[..].as_ptr()) }; - out - }) - .no_shrink() // this is arbitrary. - } - - proptest! { - #[test] - fn roundtrip_bls_fr(b in arb_bls_fr()) { - let blst_variant = bls_fr_to_blst_fr(&b); - let roundtrip = blst_fr_to_bls_fr(&blst_variant); - prop_assert_eq!(b, roundtrip); - } - - #[test] - fn roundtrip_blst_fr(b in arb_blst_fr()) { - let bls_variant = blst_fr_to_bls_fr(&b); - let roundtrip = bls_fr_to_blst_fr(&bls_variant); - prop_assert_eq!(b, roundtrip); - } - } - - // Base field roundtrips. - - fn arb_bls_fq() -> impl Strategy { - collection::vec(any::(), 48..=48) - .prop_map(|bytes| Fp384::from_random_bytes(&bytes[..])) - .prop_filter("Valid field elements", Option::is_some) - .prop_map(|opt_fr| opt_fr.unwrap()) - .no_shrink() // this is arbitrary. - } - - fn arb_blst_fp() -> impl Strategy { - collection::vec(any::(), 6..=6) - .prop_map(|u64s| { - let mut out = blst_fp::default(); - unsafe { blst_fp_from_uint64(&mut out, u64s[..].as_ptr()) }; - out - }) - .no_shrink() // this is arbitrary. - } - - proptest! { - #[test] - fn roundtrip_bls_fq(b in arb_bls_fq()) { - let blst_variant = bls_fq_to_blst_fp(&b); - let roundtrip = blst_fp_to_bls_fq(&blst_variant); - prop_assert_eq!(b, roundtrip); - } - - #[test] - fn roundtrip_blst_fp(b in arb_blst_fp()) { - let bls_variant = blst_fp_to_bls_fq(&b); - let roundtrip = bls_fq_to_blst_fp(&bls_variant); - prop_assert_eq!(b, roundtrip); - } - - #[test] - fn roundtrip_bls_fq_to_zcash_bytes(b in arb_bls_fq()) { - let zcash_bytes = bls_fq_to_zcash_bytes(&b); - let roundtrip = bls_fq_from_zcash_bytes(&zcash_bytes); - prop_assert_eq!(Some(b), roundtrip); - } - } - - // QFE roundtrips. - - fn arb_bls_fq2() -> impl Strategy { - (arb_bls_fq(), arb_bls_fq()) - .prop_map(|(fp1, fp2)| Fq2::new(fp1, fp2)) - .no_shrink() - } - - fn arb_blst_fp2() -> impl Strategy { - (arb_blst_fp(), arb_blst_fp()) - .prop_map(|(fp1, fp2)| blst_fp2 { fp: [fp1, fp2] }) - .no_shrink() - } - - proptest! { - #[test] - fn roundtrip_bls_fq2(b in arb_bls_fq2()) { - let blst_variant = bls_fq2_to_blst_fp2(&b); - let roundtrip = blst_fp2_to_bls_fq2(&blst_variant); - prop_assert_eq!(b, roundtrip); - } - - #[test] - fn roundtrip_blst_fp2(b in arb_blst_fp2()) { - let bls_variant = blst_fp2_to_bls_fq2(&b); - let roundtrip = bls_fq2_to_blst_fp2(&bls_variant); - prop_assert_eq!(b, roundtrip); - } - } - - // Target field roundtrips. - - fn arb_bls_fq6() -> impl Strategy { - (arb_bls_fq2(), arb_bls_fq2(), arb_bls_fq2()) - .prop_map(|(f_c0, f_c1, f_c2)| Fq6::new(f_c0, f_c1, f_c2)) - .no_shrink() - } - - fn arb_blst_fp6() -> impl Strategy { - (arb_blst_fp2(), arb_blst_fp2(), arb_blst_fp2()) - .prop_map(|(f_c0, f_c1, f_c2)| blst_fp6 { - fp2: [f_c0, f_c1, f_c2], - }) - .no_shrink() - } - - proptest! { - #[test] - fn roundtrip_bls_fq6(b in arb_bls_fq6()){ - let blst_variant = bls_fq6_to_blst_fp6(&b); - let roundtrip = blst_fp6_to_bls_fq6(&blst_variant); - prop_assert_eq!(b, roundtrip); - } - - #[test] - fn roundtrip_blst_fp6(b in arb_blst_fp6()){ - let bls_variant = blst_fp6_to_bls_fq6(&b); - let roundtrip = bls_fq6_to_blst_fp6(&bls_variant); - prop_assert_eq!(b, roundtrip); - } - } - - fn arb_bls_fq12() -> impl Strategy { - (arb_bls_fq6(), arb_bls_fq6()) - .prop_map(|(f_c0, f_c1)| Fq12::new(f_c0, f_c1)) - .no_shrink() - } - - fn arb_blst_fp12() -> impl Strategy { - (arb_blst_fp6(), arb_blst_fp6()) - .prop_map(|(f_c0, f_c1)| blst_fp12 { fp6: [f_c0, f_c1] }) - .no_shrink() - } - - proptest! { - #[test] - fn roundtrip_bls_fq12(b in arb_bls_fq12()) { - let blst_variant = bls_fq12_to_blst_fp12(&b); - let roundtrip = blst_fp12_to_bls_fq12(&blst_variant); - prop_assert_eq!(b, roundtrip); - } - - #[test] - fn roundtrip_blst_fp12(b in arb_blst_fp12()) { - let bls_variant = blst_fp12_to_bls_fq12(&b); - let roundtrip = bls_fq12_to_blst_fp12(&bls_variant); - prop_assert_eq!(b, roundtrip); - } - } - - // Affine point roundtrips. - - fn bls_g1_affine_infinity() -> BlsG1Affine { - BlsG1Affine::zero() - } - - pub(crate) fn arb_bls_g1_affine() -> impl Strategy { - prop_oneof![ - // 1% chance of being the point at infinity. - 1 => - Just(bls_g1_affine_infinity()), - 99 => // slow, but good enough for tests. - (arb_bls_fr()).prop_map(|s| { - - BlsG1Affine::generator() - .mul(s) - .into() - - }) - ] - } - - fn blst_g1_affine_infinity() -> blst_p1_affine { - let mut res = [0u8; G1_UNCOMPRESSED_SIZE]; - res[0] = 0x40; - let mut g1_infinity = blst_p1_affine::default(); - assert_eq!( - unsafe { blst_p1_deserialize(&mut g1_infinity, res.as_ptr()) }, - BLST_ERROR::BLST_SUCCESS - ); - g1_infinity - } - - pub(crate) fn arb_blst_g1_affine() -> impl Strategy { - prop_oneof![ - // 1% chance of being the point at infinity. - 1 => Just(blst_g1_affine_infinity()), - 99 => - (collection::vec(any::(), 32..=32)).prop_map(|msg| { - - // We actually hash to a G1Projective, then convert to affine. - let mut out = blst_p1::default(); - const DST: [u8; 16] = [0; 16]; - const AUG: [u8; 16] = [0; 16]; - - unsafe { - blst_encode_to_g1( - &mut out, - msg.as_ptr(), - msg.len(), - DST.as_ptr(), - DST.len(), - AUG.as_ptr(), - AUG.len(), - ) - }; - - let mut res = blst_p1_affine::default(); - - unsafe { blst_p1_to_affine(&mut res, &out) }; - res - } - ) - ] - } - - proptest! { - #[test] - fn roundtrip_bls_g1_affine(b in arb_bls_g1_affine()) { - let blst_variant = bls_g1_affine_to_blst_g1_affine(&b); - let roundtrip = blst_g1_affine_to_bls_g1_affine(&blst_variant); - assert_eq!(b, roundtrip); - } - - #[test] - fn roundtrip_blst_g1_affine(b in arb_blst_g1_affine()) { - let bls_variant = blst_g1_affine_to_bls_g1_affine(&b); - let roundtrip = bls_g1_affine_to_blst_g1_affine(&bls_variant); - assert_eq!(b, roundtrip); - } - - #[test] - fn roundtrip_bls_g1_affine_zcash(b in arb_bls_g1_affine()) { - let zcash_bytes = bls_g1_affine_to_zcash_bytes(&b); - let roundtrip = bls_g1_affine_from_zcash_bytes(&zcash_bytes).unwrap(); - assert_eq!(b, roundtrip); - } - - #[test] - fn compatibility_bls_blst_g1_affine_serde(b in arb_bls_g1_affine(), bt in arb_blst_g1_affine()) { - let zcash_bytes = bls_g1_affine_to_zcash_bytes(&b); - // blst accepts & expects Zcash encodings. - let mut g1 = blst_p1_affine::default(); - assert_eq!(unsafe { blst_p1_uncompress(&mut g1, zcash_bytes.as_ptr()) }, BLST_ERROR::BLST_SUCCESS); - - // blst produces Zcash encodings. - let mut tmp2 = [0u8; 48]; - unsafe { - blst_p1_affine_compress(tmp2.as_mut_ptr(), &bt); - }; - assert!(bls_g1_affine_from_zcash_bytes(&tmp2).is_some()); - } - } - - fn bls_g2_affine_infinity() -> BlsG2Affine { - BlsG2Affine::zero() - } - - fn arb_bls_g2_affine() -> impl Strategy { - // slow, but good enough for tests. - (arb_bls_fr(), any::()).prop_map(|(s, maybe_infinity)| { - if maybe_infinity < 0.1 { - bls_g2_affine_infinity() - } else { - BlsG2Affine::generator().mul(s).into() - } - }) - } - - fn blst_g2_affine_infinity() -> blst_p2_affine { - let mut res = [0u8; G2_UNCOMPRESSED_SIZE]; - res[0] = 0x40; - let mut g2_infinity = blst_p2_affine::default(); - assert_eq!( - unsafe { blst_p2_deserialize(&mut g2_infinity, res.as_ptr()) }, - BLST_ERROR::BLST_SUCCESS - ); - g2_infinity - } - - pub(crate) fn arb_blst_g2_affine() -> impl Strategy { - prop_oneof![ - 1 => // 1% chance of being the point at infinity. - Just(blst_g2_affine_infinity()), - 99 => (collection::vec(any::(), 32..=32)).prop_map(|msg| { - // We actually hash to a G2Projective, then convert to affine. - let mut out = blst_p2::default(); - const DST: [u8; 16] = [0; 16]; - const AUG: [u8; 16] = [0; 16]; - - unsafe { - blst_encode_to_g2( - &mut out, - msg.as_ptr(), - msg.len(), - DST.as_ptr(), - DST.len(), - AUG.as_ptr(), - AUG.len(), - ) - }; - - let mut res = blst_p2_affine::default(); - - unsafe { blst_p2_to_affine(&mut res, &out) }; - res - }) - ] - } - - proptest! { - #[test] - fn roundtrip_bls_g2_affine(b in arb_bls_g2_affine()) { - let blst_variant = bls_g2_affine_to_blst_g2_affine(&b); - let roundtrip = blst_g2_affine_to_bls_g2_affine(&blst_variant); - assert_eq!(b, roundtrip); - } - - #[test] - fn roundtrip_blst_g2_affine(b in arb_blst_g2_affine()) { - let bls_variant = blst_g2_affine_to_bls_g2_affine(&b); - let roundtrip = bls_g2_affine_to_blst_g2_affine(&bls_variant); - assert_eq!(b, roundtrip); - } - - #[test] - fn roundtrip_bls_g2_affine_zcash(b in arb_bls_g2_affine()) { - let zcash_bytes = bls_g2_affine_to_zcash_bytes(&b); - let roundtrip = bls_g2_affine_from_zcash_bytes(&zcash_bytes).unwrap(); - assert_eq!(b, roundtrip); - } - - #[test] - fn compatibility_bls_blst_g2_affine_serde(b in arb_bls_g2_affine(), bt in arb_blst_g2_affine()) { - let zcash_bytes = bls_g2_affine_to_zcash_bytes(&b); - let mut g2 = blst_p2_affine::default(); - // blst accepts & expects Zcash encodings. - assert!(unsafe { blst_p2_uncompress(&mut g2, zcash_bytes.as_ptr()) } == BLST_ERROR::BLST_SUCCESS); - - // blst produces Zcash encodings. - let mut tmp2 = [0u8; 96]; - unsafe { - blst_p2_affine_compress(tmp2.as_mut_ptr(), &bt); - }; - assert!(bls_g2_affine_from_zcash_bytes(&tmp2).is_some()); - } - } -} diff --git a/fastcrypto-zkp/src/bls12381/mod.rs b/fastcrypto-zkp/src/bls12381/mod.rs index 4516db71e3..d96d7e4c8d 100644 --- a/fastcrypto-zkp/src/bls12381/mod.rs +++ b/fastcrypto-zkp/src/bls12381/mod.rs @@ -5,63 +5,61 @@ //! Groth16 verifier over the BLS12-381 elliptic curve construction. -use crate::bls12381::conversions::{BlsFr, SCALAR_SIZE}; -use ark_bls12_381::Bls12_381; -use ark_serialize::CanonicalDeserialize; -use derive_more::From; -use fastcrypto::error::{FastCryptoError, FastCryptoResult}; +use fastcrypto::groups::bls12381::G1Element; -/// Conversions between arkworks <-> blst -pub mod conversions; - -/// Groth16 SNARK verifier -pub mod verifier; +use crate::groth16; /// API that takes in serialized inputs pub mod api; -/// A field element in the BLS12-381 construction. Thin wrapper around `conversions::BlsFr`. -#[derive(Debug, From, Copy, Clone)] -pub struct FieldElement(pub(crate) BlsFr); +#[cfg(test)] +mod test_helpers; -/// A Groth16 proof in the BLS12-381 construction. Thin wrapper around `ark_groth16::Proof::`. -#[derive(Debug, From)] -pub struct Proof(pub(crate) ark_groth16::Proof); +/// A prepared Groth16 verifying key in the BLS12-381 construction. +pub type PreparedVerifyingKey = groth16::PreparedVerifyingKey; -/// A Groth16 verifying key in the BLS12-381 construction. Thin wrapper around `ark_groth16::VerifyingKey::`. -#[derive(Debug, From)] -pub struct VerifyingKey(pub(crate) ark_groth16::VerifyingKey); +/// A Groth16 verifying key in the BLS12-381 construction. +pub type VerifyingKey = groth16::VerifyingKey; -impl Proof { - /// Deserialize a serialized Groth16 proof using arkworks' canonical serialisation format: https://docs.rs/ark-serialize/latest/ark_serialize/. - pub fn deserialize(proof_points_as_bytes: &[u8]) -> FastCryptoResult { - ark_groth16::Proof::::deserialize_compressed(proof_points_as_bytes) - .map_err(|_| FastCryptoError::InvalidInput) - .map(Proof) - } -} +/// A Groth16 proof in the BLS12-381 construction. +pub type Proof = groth16::Proof; -impl FieldElement { - /// Deserialize 32 bytes into a BLS12-381 field element using little-endian format. - pub(crate) fn deserialize(bytes: &[u8]) -> FastCryptoResult { - if bytes.len() != SCALAR_SIZE { - return Err(FastCryptoError::InputLengthWrong(bytes.len())); - } - BlsFr::deserialize_compressed(bytes) - .map(FieldElement) - .map_err(|_| FastCryptoError::InvalidInput) - } +#[cfg(test)] +mod tests { + use std::ops::Mul; + + use crate::bls12381::PreparedVerifyingKey; + use ark_bls12_381::{Bls12_381, Fr}; + use ark_groth16::Groth16; + use ark_snark::SNARK; + use ark_std::rand::thread_rng; + use ark_std::UniformRand; + + use crate::bls12381::test_helpers::{ + from_arkworks_proof, from_arkworks_scalar, from_arkworks_vk, + }; + use crate::dummy_circuits::DummyCircuit; + + #[test] + fn test_verify_with_processed_vk() { + const PUBLIC_SIZE: usize = 128; + let rng = &mut thread_rng(); + let c = DummyCircuit:: { + a: Some(::rand(rng)), + b: Some(::rand(rng)), + num_variables: PUBLIC_SIZE, + num_constraints: 256, + }; + + let (pk, vk) = Groth16::::circuit_specific_setup(c, rng).unwrap(); + let ark_proof = Groth16::::prove(&pk, c, rng).unwrap(); + let public_input = c.a.unwrap().mul(c.b.unwrap()); + + let proof = from_arkworks_proof(&ark_proof); + let vk = from_arkworks_vk(&vk); + let prepared_vk = PreparedVerifyingKey::from(&vk); + let public_inputs = vec![from_arkworks_scalar(&public_input)]; - /// Deserialize a vector of bytes into a vector of BLS12-381 field elements, assuming that each element - /// is serialized as a chunk of 32 bytes. See also [`FieldElement::deserialize`]. - pub(crate) fn deserialize_vector(bytes: &[u8]) -> FastCryptoResult> { - if bytes.len() % SCALAR_SIZE != 0 { - return Err(FastCryptoError::InputLengthWrong(bytes.len())); - } - let mut field_elements = Vec::new(); - for chunk in bytes.chunks(SCALAR_SIZE) { - field_elements.push(Self::deserialize(chunk)?); - } - Ok(field_elements) + assert!(prepared_vk.verify(&public_inputs, &proof).is_ok()); } } diff --git a/fastcrypto-zkp/src/bls12381/test_helpers.rs b/fastcrypto-zkp/src/bls12381/test_helpers.rs new file mode 100644 index 0000000000..3f889dde2b --- /dev/null +++ b/fastcrypto-zkp/src/bls12381/test_helpers.rs @@ -0,0 +1,27 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use ark_bls12_381::{Bls12_381, Fr}; +use ark_ff::{BigInteger, PrimeField}; +use ark_serialize::CanonicalSerialize; + +use fastcrypto::groups::bls12381::Scalar; +use fastcrypto::serde_helpers::ToFromByteArray; + +use crate::bls12381::{Proof, VerifyingKey}; + +pub(super) fn from_arkworks_proof(ark_proof: &ark_groth16::Proof) -> Proof { + let mut proof_bytes = Vec::new(); + ark_proof.serialize_compressed(&mut proof_bytes).unwrap(); + bcs::from_bytes(&proof_bytes).unwrap() +} + +pub(super) fn from_arkworks_vk(ark_vk: &ark_groth16::VerifyingKey) -> VerifyingKey { + let mut vk_bytes = Vec::new(); + ark_vk.serialize_compressed(&mut vk_bytes).unwrap(); + VerifyingKey::from_arkworks_format(&vk_bytes).unwrap() +} + +pub(super) fn from_arkworks_scalar(scalar: &Fr) -> Scalar { + Scalar::from_byte_array(&scalar.into_bigint().to_bytes_be().try_into().unwrap()).unwrap() +} diff --git a/fastcrypto-zkp/src/bls12381/unit_tests/verifier_tests.rs b/fastcrypto-zkp/src/bls12381/unit_tests/verifier_tests.rs deleted file mode 100644 index 1f8d202c3f..0000000000 --- a/fastcrypto-zkp/src/bls12381/unit_tests/verifier_tests.rs +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (c) 2022, Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 -use crate::bls12381::verifier::{BlsFr, PreparedVerifyingKey as CustomPVK}; -use ark_bls12_381::{Bls12_381, Fq12, Fr, G1Projective}; -use ark_ec::bls12::G1Prepared; -use ark_ec::pairing::Pairing as _; -use ark_ec::CurveGroup; -use ark_ff::{One, UniformRand}; -use ark_groth16::{Groth16, PreparedVerifyingKey}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_snark::SNARK; -use ark_std::rand::thread_rng; -use blst::{ - blst_final_exp, blst_fp12, blst_fp12_mul, blst_fr, blst_miller_loop, blst_p1, blst_p1_affine, - blst_p1_affine_is_inf, blst_p1_to_affine, blst_p2_affine_is_inf, Pairing, -}; -use proptest::{collection, prelude::*}; -use std::{ - iter, - ops::{AddAssign, Mul, Neg}, -}; - -use crate::{ - bls12381::conversions::{ - bls_fq12_to_blst_fp12, bls_fr_to_blst_fr, bls_g1_affine_to_blst_g1_affine, - bls_g2_affine_to_blst_g2_affine, blst_fp12_to_bls_fq12, - tests::{arb_bls_fr, arb_bls_g1_affine, arb_blst_g1_affine, arb_blst_g2_affine}, - }, - bls12381::verifier::{g1_linear_combination, multipairing_with_processed_vk, BLST_FR_ONE}, - dummy_circuits::DummyCircuit, -}; - -#[test] -fn fr_one_test() { - let bls_one = Fr::one(); - let blst_one = bls_fr_to_blst_fr(&bls_one); - assert_eq!(blst_one, BLST_FR_ONE); -} - -// This emulates the process_vk function of the arkworks verifier, but using blst to compute the term -// alpha_g1_beta_g2. See [`test_prepare_vk`]. -fn ark_process_vk(vk: &ark_groth16::VerifyingKey) -> PreparedVerifyingKey { - let g1_alpha = bls_g1_affine_to_blst_g1_affine(&vk.alpha_g1); - let g2_beta = bls_g2_affine_to_blst_g2_affine(&vk.beta_g2); - let blst_alpha_g1_beta_g2 = { - let mut tmp = blst_fp12::default(); - unsafe { blst_miller_loop(&mut tmp, &g2_beta, &g1_alpha) }; - - let mut out = blst_fp12::default(); - unsafe { blst_final_exp(&mut out, &tmp) }; - out - }; - let alpha_g1_beta_g2 = blst_fp12_to_bls_fq12(&blst_alpha_g1_beta_g2); - PreparedVerifyingKey { - vk: vk.clone(), - alpha_g1_beta_g2, - gamma_g2_neg_pc: vk.gamma_g2.neg().into(), - delta_g2_neg_pc: vk.delta_g2.neg().into(), - } -} - -// This computes the result of the multi-pairing involved in the Groth16 verification, using arkworks. -// See [`test_multipairing_with_processed_vk`] -fn ark_multipairing_with_prepared_vk( - pvk: &PreparedVerifyingKey, - proof: &ark_groth16::Proof, - public_inputs: &[Fr], -) -> Fq12 { - let mut g_ic = G1Projective::from(pvk.vk.gamma_abc_g1[0]); - for (i, b) in public_inputs.iter().zip(pvk.vk.gamma_abc_g1.iter().skip(1)) { - g_ic.add_assign(&b.mul(i)); - } - - let qap = Bls12_381::multi_miller_loop( - [ - G1Prepared::from(proof.a), - G1Prepared::from(g_ic), - G1Prepared::from(proof.c), - ], - [ - proof.b.into(), - pvk.gamma_g2_neg_pc.clone(), - pvk.delta_g2_neg_pc.clone(), - ], - ); - - Bls12_381::final_exponentiation(qap).unwrap().0 -} - -const LEN: usize = 10; - -proptest! { - // This technical test is necessary because blst does not expose a generic multi-miller - // loop operation, and forces us to abuse the signature-oriented pairing engine - // that it does expose. Here we show the use of the pairing engine is equivalent to iterated - // use of one-off pairings. - // see https://github.com/supranational/blst/issues/136 - #[test] - fn test_blst_miller_loops( - a_s in collection::vec(arb_blst_g1_affine().prop_filter("values must be non-infinity", |v| unsafe{!blst_p1_affine_is_inf(v)}), LEN..=LEN), - b_s in collection::vec(arb_blst_g2_affine().prop_filter("values must be non-infinity", |v| unsafe{!blst_p2_affine_is_inf(v)}), LEN..=LEN) - ) { - let pairing_engine_result = { - let dst = [0u8; 3]; - let mut pairing_blst = Pairing::new(false, &dst); - for (b, a) in b_s.iter().zip(a_s.iter()) { - pairing_blst.raw_aggregate(b, a); - } - pairing_blst.as_fp12() // this implies pairing_blst.commit() - }; - - let mut res = blst_fp12::default(); - let mut loop0 = blst_fp12::default(); - - for i in 0..LEN { - unsafe { - blst_miller_loop(&mut loop0, b_s[i..].as_ptr(), a_s[i..].as_ptr()); - blst_fp12_mul(&mut res, &res, &loop0); - loop0 = blst_fp12::default(); - } - } - - prop_assert_eq!(res, pairing_engine_result); - } - -} - -#[test] -fn test_prepare_vk() { - const PUBLIC_SIZE: usize = 128; - let rng = &mut thread_rng(); - let c = DummyCircuit:: { - a: Some(::rand(rng)), - b: Some(::rand(rng)), - num_variables: PUBLIC_SIZE, - num_constraints: 65536, - }; - - let (_pk, vk) = Groth16::::circuit_specific_setup(c, rng).unwrap(); - - let ark_pvk = Groth16::::process_vk(&vk).unwrap(); - let blst_pvk = ark_process_vk(&vk); - assert_eq!(ark_pvk.alpha_g1_beta_g2, blst_pvk.alpha_g1_beta_g2); -} - -proptest! { - #[test] - fn test_g1_linear_combination( - frs in collection::vec(arb_bls_fr(), LEN-1..=LEN-1), - a_s in collection::vec(arb_bls_g1_affine(), LEN..=LEN), - ) { - - let pts: Vec = a_s - .iter() - .map(bls_g1_affine_to_blst_g1_affine) - .collect(); - let one = BLST_FR_ONE; - let ss: Vec = iter::once(one) - .chain(frs.iter().map(bls_fr_to_blst_fr)) - .collect(); - let mut blst_res = blst_p1::default(); - g1_linear_combination(&mut blst_res, &pts, &ss[..], ss.len()); - let mut blst_res_affine = blst_p1_affine::default(); - unsafe { blst_p1_to_affine(&mut blst_res_affine, &blst_res) }; - - let mut g_ic = G1Projective::from(a_s[0]); - for (i, b) in frs.iter().zip(a_s.iter().skip(1)) { - g_ic.add_assign(&b.mul(i)); - } - - // TODO: convert this so we can make a projective comparison - prop_assert_eq!(blst_res_affine, bls_g1_affine_to_blst_g1_affine(&g_ic.into_affine())); - - } -} - -#[test] -fn test_verify_with_processed_vk() { - const PUBLIC_SIZE: usize = 128; - let rng = &mut thread_rng(); - let c = DummyCircuit:: { - a: Some(::rand(rng)), - b: Some(::rand(rng)), - num_variables: PUBLIC_SIZE, - num_constraints: 65536, - }; - - let (pk, vk) = Groth16::::circuit_specific_setup(c, rng).unwrap(); - let proof = Groth16::::prove(&pk, c, rng).unwrap().into(); - let v = c.a.unwrap().mul(c.b.unwrap()); - - let blst_pvk = CustomPVK::from(&vk.into()); - assert!(blst_pvk.verify(&[v.into()], &proof).unwrap()); - - // Roundtrip serde of the proof public input bytes. - let mut public_inputs_bytes = Vec::new(); - v.serialize_compressed(&mut public_inputs_bytes).unwrap(); - - let deserialized_public_inputs = BlsFr::deserialize_compressed(public_inputs_bytes.as_slice()) - .unwrap() - .into(); - - // Roundtrip serde of the proof points bytes. - let mut proof_points_bytes = Vec::new(); - proof - .0 - .serialize_compressed(&mut proof_points_bytes) - .unwrap(); - let deserialized_proof_points = - ark_groth16::Proof::::deserialize_compressed(proof_points_bytes.as_slice()) - .unwrap() - .into(); - - // Roundtrip serde of the prepared verifying key. - let serialized = blst_pvk.serialize().unwrap(); - let serialized_pvk = CustomPVK::deserialize(&serialized).unwrap(); - - assert!(serialized_pvk - .verify(&[deserialized_public_inputs], &deserialized_proof_points) - .unwrap()); -} - -#[test] -fn test_multipairing_with_processed_vk() { - const PUBLIC_SIZE: usize = 128; - let rng = &mut thread_rng(); - let c = DummyCircuit:: { - a: Some(::rand(rng)), - b: Some(::rand(rng)), - num_variables: PUBLIC_SIZE, - num_constraints: 65536, - }; - - let (pk, vk) = Groth16::::circuit_specific_setup(c, rng).unwrap(); - let proof = Groth16::::prove(&pk, c, rng).unwrap(); - let v = c.a.unwrap().mul(c.b.unwrap()); - - let ark_pvk = Groth16::::process_vk(&vk).unwrap(); - let blst_pvk = CustomPVK::from(&vk.into()); - - let ark_fe = ark_multipairing_with_prepared_vk(&ark_pvk, &proof, &[v]); - let blst_fe = multipairing_with_processed_vk(&blst_pvk, &[v], &proof); - - assert_eq!(bls_fq12_to_blst_fp12(&ark_fe), blst_fe); -} diff --git a/fastcrypto-zkp/src/bls12381/verifier.rs b/fastcrypto-zkp/src/bls12381/verifier.rs deleted file mode 100644 index 1f0f84eea9..0000000000 --- a/fastcrypto-zkp/src/bls12381/verifier.rs +++ /dev/null @@ -1,370 +0,0 @@ -// Copyright (c) 2022, Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 -use std::borrow::Borrow; -use std::{iter, ops::Neg}; - -use ark_bls12_381::{Bls12_381, Fq12, Fr as BlsFr, G1Affine, G2Affine}; - -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use blst::{ - blst_final_exp, blst_fp, blst_fp12, blst_fr, blst_miller_loop, blst_p1, blst_p1_add_or_double, - blst_p1_affine, blst_p1_from_affine, blst_p1_mult, blst_p1_to_affine, blst_scalar, - blst_scalar_from_fr, Pairing, -}; -use fastcrypto::error::FastCryptoResult; -use fastcrypto::{error::FastCryptoError, utils::log2_byte}; - -use crate::bls12381::conversions::{ - bls_fq12_to_blst_fp12, bls_fr_to_blst_fr, bls_g1_affine_to_blst_g1_affine, - bls_g2_affine_to_blst_g2_affine, blst_fp12_to_bls_fq12, G1_COMPRESSED_SIZE, -}; -use crate::bls12381::{FieldElement, Proof, VerifyingKey}; - -#[cfg(test)] -#[path = "unit_tests/verifier_tests.rs"] -mod verifier_tests; - -/// This is a helper function to store a pre-processed version of the verifying key. -/// This is roughly homologous to [`ark_groth16::data_structures::PreparedVerifyingKey`]. -/// Note that contrary to Arkworks, we don't store a "prepared" version of the gamma_g2_neg_pc, -/// delta_g2_neg_pc fields, because we can't use them with blst's pairing engine and also because -/// they are very large and unpractical to use in the binary api. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct PreparedVerifyingKey { - /// The element vk.gamma_abc_g1, - /// aka the `[gamma^{-1} * (beta * a_i + alpha * b_i + c_i) * G]`, where i spans the public inputs - pub vk_gamma_abc_g1: Vec, - /// The element `e(alpha * G, beta * H)` in `E::GT`. - pub alpha_g1_beta_g2: blst_fp12, - /// The element `- gamma * H` in `E::G2`, for use in pairings. - pub gamma_g2_neg_pc: G2Affine, - /// The element `- delta * H` in `E::G2`, for use in pairings. - pub delta_g2_neg_pc: G2Affine, -} - -impl PreparedVerifyingKey { - /// Returns the validity of the Groth16 proof passed as argument. The format of the inputs is assumed to be in arkworks format. - /// See [`multipairing_with_processed_vk`] for the actual pairing computation details. - /// - /// ## Example - /// ``` - /// use fastcrypto_zkp::dummy_circuits::Fibonacci; - /// use ark_bls12_381::{Bls12_381, Fr}; - /// use ark_ff::One; - /// use ark_groth16::Groth16; - /// use ark_std::rand::thread_rng; - /// use blake2::digest::Mac; - /// use fastcrypto_zkp::bls12381::FieldElement; - /// use fastcrypto_zkp::bls12381::verifier::PreparedVerifyingKey; - /// - /// let mut rng = thread_rng(); - /// - /// let params = { - /// let circuit = Fibonacci::::new(42, Fr::one(), Fr::one()); // 42 constraints, initial a = b = 1 - /// Groth16::::generate_random_parameters_with_reduction(circuit, &mut rng).unwrap() - /// }; - /// - /// let proof = { - /// let circuit = Fibonacci::::new(42, Fr::one(), Fr::one()); // 42 constraints, initial a = b = 1 - /// // Create a proof with our parameters, picking a random witness assignment - /// Groth16::::create_random_proof_with_reduction(circuit, ¶ms, &mut rng).unwrap() - /// }; - /// - /// // Prepare the verification key (for proof verification). Ideally, we would like to do this only - /// // once per circuit. - /// let pvk = PreparedVerifyingKey::from(¶ms.vk.into()); - /// - /// // We provide the public inputs which we know are used in our circuits - /// // this must be the same as the inputs used in the proof right above. - /// let inputs: Vec = [Fr::one().into(); 2].to_vec(); - /// - /// // Verify the proof - /// let r = pvk.verify(&inputs, &proof.into()).unwrap(); - /// ``` - pub fn verify(&self, x: &[FieldElement], proof: &Proof) -> Result { - // Note the "+1" : this API implies the first scalar coefficient is 1 and not sent - if (x.len() + 1) != self.vk_gamma_abc_g1.len() { - return Err(FastCryptoError::InvalidInput); - } - let x: Vec = x.iter().map(|x| x.0).collect(); - - let res = multipairing_with_processed_vk(self, &x, &proof.0); - Ok(res == self.alpha_g1_beta_g2) - } - - /// Deserialize the prepared verifying key from the serialized fields of vk_gamma_abc_g1, alpha_g1_beta_g2, gamma_g2_neg_pc, delta_g2_neg_pc - pub fn deserialize>(bytes: &Vec) -> Result { - if bytes.len() != 4 { - return Err(FastCryptoError::InputLengthWrong(bytes.len())); - } - - let vk_gamma_abc_g1_bytes = bytes[0].borrow(); - if vk_gamma_abc_g1_bytes.len() % G1_COMPRESSED_SIZE != 0 { - return Err(FastCryptoError::InvalidInput); - } - let mut vk_gamma_abc_g1: Vec = Vec::new(); - for g1_bytes in vk_gamma_abc_g1_bytes.chunks(G1_COMPRESSED_SIZE) { - let g1 = G1Affine::deserialize_compressed(g1_bytes) - .map_err(|_| FastCryptoError::InvalidInput)?; - vk_gamma_abc_g1.push(g1); - } - - let alpha_g1_beta_g2 = bls_fq12_to_blst_fp12( - &Fq12::deserialize_compressed(bytes[1].borrow()) - .map_err(|_| FastCryptoError::InvalidInput)?, - ); - - let gamma_g2_neg_pc = G2Affine::deserialize_compressed(bytes[2].borrow()) - .map_err(|_| FastCryptoError::InvalidInput)?; - - let delta_g2_neg_pc = G2Affine::deserialize_compressed(bytes[3].borrow()) - .map_err(|_| FastCryptoError::InvalidInput)?; - - Ok(PreparedVerifyingKey { - vk_gamma_abc_g1, - alpha_g1_beta_g2, - gamma_g2_neg_pc, - delta_g2_neg_pc, - }) - } - - /// Serialize the prepared verifying key to its vectors form. - pub fn serialize(&self) -> Result>, FastCryptoError> { - let mut res = Vec::new(); - - let mut vk_gamma = Vec::new(); - for g1 in &self.vk_gamma_abc_g1 { - let mut g1_bytes = Vec::new(); - g1.serialize_compressed(&mut g1_bytes) - .map_err(|_| FastCryptoError::InvalidInput)?; - vk_gamma.append(&mut g1_bytes); - } - res.push(vk_gamma); - - let mut fq12 = Vec::new(); - blst_fp12_to_bls_fq12(&self.alpha_g1_beta_g2) - .serialize_compressed(&mut fq12) - .map_err(|_| FastCryptoError::InvalidInput)?; - res.push(fq12); - - let mut gamma_bytes = Vec::new(); - self.gamma_g2_neg_pc - .serialize_compressed(&mut gamma_bytes) - .map_err(|_| FastCryptoError::InvalidInput)?; - res.push(gamma_bytes); - - let mut delta_bytes = Vec::new(); - self.delta_g2_neg_pc - .serialize_compressed(&mut delta_bytes) - .map_err(|_| FastCryptoError::InvalidInput)?; - res.push(delta_bytes); - Ok(res) - } -} - -impl From<&VerifyingKey> for PreparedVerifyingKey { - /// Takes an input [`ark_groth16::VerifyingKey`] `vk` and returns a `PreparedVerifyingKey`. This is roughly homologous to - /// [`ark_groth16::PreparedVerifyingKey::process_vk`], but uses a blst representation of the elements. - /// - /// ## Example: - /// ``` - /// use fastcrypto_zkp::{dummy_circuits::Fibonacci}; - /// use ark_bls12_381::{Bls12_381, Fr}; - /// use ark_ff::One; - /// use ark_groth16::Groth16; - /// use ark_std::rand::thread_rng; - /// use fastcrypto_zkp::bls12381::verifier::PreparedVerifyingKey; - /// - /// let mut rng = thread_rng(); - /// let params = { - /// let c = Fibonacci::::new(42, Fr::one(), Fr::one()); // 42 constraints, initial a = b = 1 (standard Fibonacci) - /// Groth16::::generate_random_parameters_with_reduction(c, &mut rng).unwrap() - /// }; - /// - /// // Prepare the verification key (for proof verification). Ideally, we would like to do this only - /// // once per circuit. - /// let pvk = PreparedVerifyingKey::from(¶ms.vk.into()); - /// ``` - fn from(vk: &VerifyingKey) -> Self { - let g1_alpha = bls_g1_affine_to_blst_g1_affine(&vk.0.alpha_g1); - let g2_beta = bls_g2_affine_to_blst_g2_affine(&vk.0.beta_g2); - let blst_alpha_g1_beta_g2 = { - let mut tmp = blst_fp12::default(); - unsafe { blst_miller_loop(&mut tmp, &g2_beta, &g1_alpha) }; - - let mut out = blst_fp12::default(); - unsafe { blst_final_exp(&mut out, &tmp) }; - out - }; - PreparedVerifyingKey { - vk_gamma_abc_g1: vk.0.gamma_abc_g1.clone(), - alpha_g1_beta_g2: blst_alpha_g1_beta_g2, - gamma_g2_neg_pc: vk.0.gamma_g2.neg(), - delta_g2_neg_pc: vk.0.delta_g2.neg(), - } - } -} - -impl VerifyingKey { - /// Deserialize a serialized Groth16 verifying key in compressed format using arkworks' canonical serialisation format: https://docs.rs/ark-serialize/latest/ark_serialize/. - pub fn deserialize(bytes: &[u8]) -> FastCryptoResult { - ark_groth16::VerifyingKey::::deserialize_compressed(bytes) - .map(VerifyingKey) - .map_err(|_| FastCryptoError::InvalidInput) - } -} - -/// This helper constant makes it easier to use compute the linear combination involved in the pairing inputs. -const G1_IDENTITY: blst_p1 = blst_p1 { - x: blst_fp { l: [0; 6] }, - y: blst_fp { l: [0; 6] }, - z: blst_fp { l: [0; 6] }, -}; - -/// Returns a single scalar multiplication of `pt` by `b`. -fn mul(pt: &blst_p1, b: &blst_fr) -> blst_p1 { - let mut scalar: blst_scalar = blst_scalar::default(); - unsafe { - blst_scalar_from_fr(&mut scalar, b); - } - - // Count the number of bytes to be multiplied. - let mut i = scalar.b.len(); - while i != 0 && scalar.b[i - 1] == 0 { - i -= 1; - } - - let mut result = blst_p1::default(); - if i == 0 { - return G1_IDENTITY; - } else if i == 1 && scalar.b[0] == 1 { - return *pt; - } else { - // Count the number of bits to be multiplied. - unsafe { - blst_p1_mult( - &mut result, - pt, - &(scalar.b[0]), - 8 * i - 7 + log2_byte(scalar.b[i - 1]), - ); - } - } - result -} - -/// Facade for the blst_p1_add_or_double function. -fn add_or_dbl(a: &blst_p1, b: &blst_p1) -> blst_p1 { - let mut ret = blst_p1::default(); - unsafe { - blst_p1_add_or_double(&mut ret, a, b); - } - ret -} - -/// Computes the [\sum p_i * b_i, p_i in p_affine, b_i in coeffs] in G1. -fn g1_linear_combination( - out: &mut blst_p1, - p_affine: &[blst_p1_affine], - coeffs: &[blst_fr], - len: usize, -) { - // Direct approach - let mut tmp; - *out = G1_IDENTITY; - for i in 0..len { - let mut p = blst_p1::default(); - unsafe { blst_p1_from_affine(&mut p, &p_affine[i]) }; - - tmp = mul(&p, &coeffs[i]); - *out = add_or_dbl(out, &tmp); - } -} - -/// This represents the multiplicative unit scalar, see fr_one_test -pub(crate) const BLST_FR_ONE: blst_fr = blst_fr { - l: [ - 8589934590, - 6378425256633387010, - 11064306276430008309, - 1739710354780652911, - ], -}; - -/// Returns the result of the multi-pairing involved in the verification equation. This will then be compared to the pre-computed term -/// pvk.alpha_g1_beta_g2 to check the validity of the proof. -/// -/// The textbook Groth16 equation is (in additive notation): -/// e(A, B) = e(g * alpha, h * beta) + e(g * f, h * gamma) + e(C, h * delta) -/// where f is the linear combination of the a_i points in the verifying key with the input scalars -/// -/// Due to the pre-processing of e(g * alpha, h * beta), and using the pairing inverse, we instead compute: -/// e(A, B) + e(g * f, h * - gamma) + e(C, h * - delta). -/// -/// Eventually, we will compare this value to e(g * alpha, h * beta) -/// -fn multipairing_with_processed_vk( - pvk: &PreparedVerifyingKey, - x: &[BlsFr], - proof: &ark_groth16::Proof, -) -> blst_fp12 { - // Linear combination: note that the arkworks interface assumes the 1st scalar is an implicit 1 - let pts: Vec = pvk - .vk_gamma_abc_g1 - .iter() - .map(bls_g1_affine_to_blst_g1_affine) - .collect(); - let one = BLST_FR_ONE; - let ss: Vec = iter::once(one) - .chain(x.iter().map(bls_fr_to_blst_fr)) - .collect(); - let mut out = blst_p1::default(); - g1_linear_combination(&mut out, &pts, &ss[..], ss.len()); - - let blst_proof_a = bls_g1_affine_to_blst_g1_affine(&proof.a); - let blst_proof_b = bls_g2_affine_to_blst_g2_affine(&proof.b); - - let mut blst_proof_1_g1 = blst_p1_affine::default(); - unsafe { blst_p1_to_affine(&mut blst_proof_1_g1, &out) }; - let blst_proof_1_g2 = bls_g2_affine_to_blst_g2_affine(&pvk.gamma_g2_neg_pc); - - let blst_proof_2_g1 = bls_g1_affine_to_blst_g1_affine(&proof.c); - let blst_proof_2_g2 = bls_g2_affine_to_blst_g2_affine(&pvk.delta_g2_neg_pc); - - let dst = [0u8; 3]; - let mut pairing_blst = Pairing::new(false, &dst); - pairing_blst.raw_aggregate(&blst_proof_b, &blst_proof_a); - pairing_blst.raw_aggregate(&blst_proof_1_g2, &blst_proof_1_g1); - pairing_blst.raw_aggregate(&blst_proof_2_g2, &blst_proof_2_g1); - pairing_blst.as_fp12().final_exp() -} - -#[cfg(test)] -mod tests { - use crate::bls12381::verifier::PreparedVerifyingKey; - use crate::bls12381::VerifyingKey; - use crate::dummy_circuits::DummyCircuit; - use ark_bls12_381::{Bls12_381, Fr}; - use ark_groth16::Groth16; - use ark_snark::SNARK; - use ark_std::rand::thread_rng; - use ark_std::UniformRand; - - #[test] - fn test_serialization() { - const PUBLIC_SIZE: usize = 128; - let rng = &mut thread_rng(); - let c = DummyCircuit:: { - a: Some(::rand(rng)), - b: Some(::rand(rng)), - num_variables: PUBLIC_SIZE, - num_constraints: 10, - }; - let (_, vk) = Groth16::::circuit_specific_setup(c, rng).unwrap(); - let pvk = PreparedVerifyingKey::from(&VerifyingKey(vk)); - - let serialized = pvk.serialize().unwrap(); - let deserialized = PreparedVerifyingKey::deserialize(&serialized).unwrap(); - assert_eq!(pvk, deserialized); - } -} diff --git a/fastcrypto-zkp/src/bn254/api.rs b/fastcrypto-zkp/src/bn254/api.rs index 621a895396..3c0bf410b8 100644 --- a/fastcrypto-zkp/src/bn254/api.rs +++ b/fastcrypto-zkp/src/bn254/api.rs @@ -1,9 +1,12 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::bn254::verifier::PreparedVerifyingKey; -use crate::bn254::{FieldElement, Proof, VerifyingKey}; +use crate::groth16::api; use fastcrypto::error::FastCryptoError; +use fastcrypto::groups::bn254::{ + G1Element, G1_ELEMENT_BYTE_LENGTH, G2_ELEMENT_BYTE_LENGTH, GT_ELEMENT_BYTE_LENGTH, + SCALAR_LENGTH, +}; #[cfg(test)] #[path = "unit_tests/api_tests.rs"] @@ -11,46 +14,40 @@ mod api_tests; /// Size of scalars in the BN254 construction. pub const SCALAR_SIZE: usize = 32; - -/// Deserialize bytes as an Arkwork representation of a verifying key, and return a vector of the -/// four components of a prepared verified key (see more at [`PreparedVerifyingKey`]). +/// Create a prepared verifying key for Groth16 over the BLS12-381 curve construction. See +/// [`api::prepare_pvk_bytes`]. pub fn prepare_pvk_bytes(vk_bytes: &[u8]) -> Result>, FastCryptoError> { - PreparedVerifyingKey::from(&VerifyingKey::deserialize(vk_bytes)?).serialize() + api::prepare_pvk_bytes::< + G1Element, + { G1_ELEMENT_BYTE_LENGTH }, + { G2_ELEMENT_BYTE_LENGTH }, + { GT_ELEMENT_BYTE_LENGTH }, + { SCALAR_LENGTH }, + >(vk_bytes) } -/// Verify Groth16 proof using the serialized form of the prepared verifying key (see more at -/// [`crate::bn254::verifier::PreparedVerifyingKey`]), serialized proof public input and serialized -/// proof points. +/// Verify Groth16 proof over the BLS12-381 curve construction. See +/// [`api::verify_groth16_in_bytes`]. pub fn verify_groth16_in_bytes( vk_gamma_abc_g1_bytes: &[u8], alpha_g1_beta_g2_bytes: &[u8], gamma_g2_neg_pc_bytes: &[u8], delta_g2_neg_pc_bytes: &[u8], - proof_public_inputs_as_bytes: &[u8], + public_inputs_as_bytes: &[u8], proof_points_as_bytes: &[u8], ) -> Result { - if proof_public_inputs_as_bytes.len() % SCALAR_SIZE != 0 { - return Err(FastCryptoError::InputLengthWrong(SCALAR_SIZE)); - } - - let pvk = PreparedVerifyingKey::deserialize(&vec![ + api::verify_groth16_in_bytes::< + G1Element, + { G1_ELEMENT_BYTE_LENGTH }, + { G2_ELEMENT_BYTE_LENGTH }, + { GT_ELEMENT_BYTE_LENGTH }, + { SCALAR_LENGTH }, + >( vk_gamma_abc_g1_bytes, alpha_g1_beta_g2_bytes, gamma_g2_neg_pc_bytes, delta_g2_neg_pc_bytes, - ])?; - - verify_groth16(&pvk, proof_public_inputs_as_bytes, proof_points_as_bytes) -} - -/// Verify proof with a given verifying key in [struct PreparedVerifyingKey], serialized public inputs -/// and serialized proof points. -pub fn verify_groth16( - pvk: &PreparedVerifyingKey, - proof_public_inputs_as_bytes: &[u8], - proof_points_as_bytes: &[u8], -) -> Result { - let proof = Proof::deserialize(proof_points_as_bytes)?; - let public_inputs = FieldElement::deserialize_vector(proof_public_inputs_as_bytes)?; - pvk.verify(&public_inputs, &proof) + public_inputs_as_bytes, + proof_points_as_bytes, + ) } diff --git a/fastcrypto-zkp/src/bn254/mod.rs b/fastcrypto-zkp/src/bn254/mod.rs index 5fddd8df1e..b09b51e71d 100644 --- a/fastcrypto-zkp/src/bn254/mod.rs +++ b/fastcrypto-zkp/src/bn254/mod.rs @@ -4,18 +4,16 @@ #![deny(unused_must_use, missing_debug_implementations)] //! Groth16 verifier over the BN254 elliptic curve construction. -use crate::bn254::api::SCALAR_SIZE; -use ark_bn254::{Bn254, Fr}; -use ark_serialize::CanonicalDeserialize; -use derive_more::From; -use fastcrypto::error::{FastCryptoError, FastCryptoResult}; +use crate::groth16; +use crate::groth16::api::{FromLittleEndianByteArray, GTSerialize}; +use fastcrypto::error::FastCryptoResult; +use fastcrypto::groups::bn254::G1Element; +use fastcrypto::groups::bn254::{GTElement, Scalar, GT_ELEMENT_BYTE_LENGTH, SCALAR_LENGTH}; +use fastcrypto::serde_helpers::ToFromByteArray; /// API that takes in serialized inputs pub mod api; -/// Groth16 SNARK verifier -pub mod verifier; - /// Poseidon hash function over BN254 pub mod poseidon; @@ -28,50 +26,27 @@ pub mod zk_login_api; /// Zk login utils pub mod utils; -/// A field element in the BN254 construction. Thin wrapper around `api::Bn254Fr`. -#[derive(Debug, From)] -pub struct FieldElement(pub(crate) ark_bn254::Fr); +/// A prepared Groth16 verifying key in the BN254 construction. +pub type PreparedVerifyingKey = groth16::PreparedVerifyingKey; -/// A Groth16 proof in the BN254 construction. Thin wrapper around `ark_groth16::Proof::`. -#[derive(Debug, From)] -pub struct Proof(pub(crate) ark_groth16::Proof); +/// A Groth16 verifying key in the BN254 construction. +pub type VerifyingKey = groth16::VerifyingKey; -/// A Groth16 verifying key in the BN254 construction. Thin wrapper around `ark_groth16::VerifyingKey::`. -#[derive(Debug, From)] -pub struct VerifyingKey(pub(crate) ark_groth16::VerifyingKey); +/// A Groth16 proof in the BN254 construction. +pub type Proof = groth16::Proof; -impl Proof { - /// Deserialize a serialized Groth16 proof using arkworks' canonical serialisation format: https://docs.rs/ark-serialize/latest/ark_serialize/. - pub fn deserialize(proof_points_as_bytes: &[u8]) -> FastCryptoResult { - ark_groth16::Proof::::deserialize_compressed(proof_points_as_bytes) - .map_err(|_| FastCryptoError::InvalidInput) - .map(Proof) +impl FromLittleEndianByteArray for Scalar { + fn from_little_endian_byte_array(bytes: &[u8; SCALAR_LENGTH]) -> FastCryptoResult { + Scalar::from_byte_array(bytes) } } -impl FieldElement { - /// Deserialize 32 bytes into a BN254 field element using little-endian format. - pub(crate) fn deserialize(bytes: &[u8]) -> FastCryptoResult { - if bytes.len() != SCALAR_SIZE { - return Err(FastCryptoError::InputLengthWrong(bytes.len())); - } - Fr::deserialize_compressed(bytes) - .map_err(|_| FastCryptoError::InvalidInput) - .map(FieldElement) +impl GTSerialize for GTElement { + fn to_arkworks_bytes(&self) -> [u8; GT_ELEMENT_BYTE_LENGTH] { + self.to_byte_array() } - /// Deserialize a vector of bytes into a vector of BN254 field elements, assuming that each element - /// is serialized as a chunk of 32 bytes. See also [`FieldElement::deserialize`]. - pub(crate) fn deserialize_vector( - field_element_bytes: &[u8], - ) -> FastCryptoResult> { - if field_element_bytes.len() % SCALAR_SIZE != 0 { - return Err(FastCryptoError::InputLengthWrong(field_element_bytes.len())); - } - let mut public_inputs = Vec::new(); - for chunk in field_element_bytes.chunks(SCALAR_SIZE) { - public_inputs.push(FieldElement::deserialize(chunk)?); - } - Ok(public_inputs) + fn from_arkworks_bytes(bytes: &[u8; GT_ELEMENT_BYTE_LENGTH]) -> FastCryptoResult { + GTElement::from_byte_array(bytes) } } diff --git a/fastcrypto-zkp/src/bn254/unit_tests/api_tests.rs b/fastcrypto-zkp/src/bn254/unit_tests/api_tests.rs index 79b209e0bb..939aa23381 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/api_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/api_tests.rs @@ -2,8 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::bn254::api::{prepare_pvk_bytes, verify_groth16_in_bytes}; -use crate::bn254::verifier::PreparedVerifyingKey; -use crate::bn254::VerifyingKey; +use crate::bn254::{PreparedVerifyingKey, VerifyingKey}; use crate::dummy_circuits::{DummyCircuit, Fibonacci}; use ark_bn254::{Bn254, Fq12, Fr, G1Projective, G2Projective}; use ark_ff::{One, Zero}; @@ -17,6 +16,19 @@ use std::ops::Mul; #[path = "./utils.rs"] mod utils; +fn vk_from_arkworks(vk: ark_groth16::VerifyingKey) -> VerifyingKey { + VerifyingKey::new( + G1Projective::from(vk.alpha_g1).into(), + G2Projective::from(vk.beta_g2).into(), + G2Projective::from(vk.gamma_g2).into(), + G2Projective::from(vk.delta_g2).into(), + vk.gamma_abc_g1 + .iter() + .map(|x| G1Projective::from(*x).into()) + .collect(), + ) +} + #[test] fn test_verify_groth16_in_bytes_api() { const PUBLIC_SIZE: usize = 128; @@ -32,9 +44,9 @@ fn test_verify_groth16_in_bytes_api() { let proof = Groth16::::prove(&pk, c, rng).unwrap(); let v = c.a.unwrap().mul(c.b.unwrap()); - let pvk = PreparedVerifyingKey::from(&vk); + let pvk = PreparedVerifyingKey::from(&vk_from_arkworks(vk)); - let bytes = pvk.serialize().unwrap(); + let bytes = pvk.serialize_into_parts(); let vk_gamma_abc_g1_bytes = &bytes[0]; let alpha_g1_beta_g2_bytes = &bytes[1]; let gamma_g2_neg_pc_bytes = &bytes[2]; @@ -113,12 +125,7 @@ fn test_verify_groth16_in_bytes_multiple_inputs() { Groth16::::create_random_proof_with_reduction(circuit, ¶ms, &mut rng).unwrap() }; - let pvk = PreparedVerifyingKey::from(¶ms.vk); - - let inputs: Vec<_> = [a, b].to_vec(); - assert!(Groth16::::verify_with_processed_vk(&(&pvk).into(), &inputs, &proof).unwrap()); - - let pvk = pvk.serialize().unwrap(); + let pvk = PreparedVerifyingKey::from(&vk_from_arkworks(params.vk)).serialize_into_parts(); // This circuit has two public inputs: let mut inputs_bytes = Vec::new(); @@ -193,7 +200,7 @@ fn test_verify_groth16_elusiv_proof_in_bytes_api() { ], ); - let vk = VerifyingKey(ark_groth16::VerifyingKey { + let vk = vk_from_arkworks(ark_groth16::VerifyingKey { alpha_g1: utils::G1Affine_from_str_projective(( "8057073471822347335074195152835286348058235024870127707965681971765888348219", "14493022634743109860560137600871299171677470588934003383462482807829968516757", @@ -316,7 +323,7 @@ fn test_verify_groth16_elusiv_proof_in_bytes_api() { let pvk = PreparedVerifyingKey::from(&vk); - let bytes = pvk.serialize().unwrap(); + let bytes = pvk.serialize_into_parts(); let vk_gamma_abc_g1_bytes = &bytes[0]; let alpha_g1_beta_g2_bytes = &bytes[1]; let gamma_g2_neg_pc_bytes = &bytes[2]; @@ -528,9 +535,9 @@ fn fail_verify_groth16_invalid_elusiv_proof_in_bytes_api() { .collect(), }; - let pvk = PreparedVerifyingKey::from(&vk); + let pvk = PreparedVerifyingKey::from(&vk_from_arkworks(vk)); - let bytes = pvk.serialize().unwrap(); + let bytes = pvk.serialize_into_parts(); let vk_gamma_abc_g1_bytes = &bytes[0]; let alpha_g1_beta_g2_bytes = &bytes[1]; let gamma_g2_neg_pc_bytes = &bytes[2]; diff --git a/fastcrypto-zkp/src/bn254/unit_tests/utils.rs b/fastcrypto-zkp/src/bn254/unit_tests/utils.rs index b8ce3799ef..5907b8e012 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/utils.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/utils.rs @@ -1,7 +1,7 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use ark_bn254::{Fq, Fq2, G1Affine, G1Projective, G2Affine}; +use ark_bn254::{Fq, Fq2, G1Affine, G1Projective, G2Affine, G2Projective}; type StrPair = (&'static str, &'static str); type StrTriplet = (&'static str, &'static str, &'static str); @@ -20,7 +20,6 @@ pub(crate) fn G1Affine_from_str_projective( #[allow(non_snake_case)] pub(crate) fn G2Affine_from_str_projective(s: (StrPair, StrPair, StrPair)) -> G2Affine { - use ark_bn254::G2Projective; G2Projective::new( Fq2::new(s.0 .0.parse::().unwrap(), s.0 .1.parse::().unwrap()), Fq2::new(s.1 .0.parse::().unwrap(), s.1 .1.parse::().unwrap()), diff --git a/fastcrypto-zkp/src/bn254/verifier.rs b/fastcrypto-zkp/src/bn254/verifier.rs deleted file mode 100644 index 18aa0a41ef..0000000000 --- a/fastcrypto-zkp/src/bn254/verifier.rs +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (c) 2022, Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use std::borrow::Borrow; -use std::ops::Neg; - -use ark_bn254::{Bn254, Fq12, Fr, G1Affine, G2Affine}; -use ark_ec::bn::G2Prepared; -use ark_ec::pairing::Pairing; -use ark_groth16::{Groth16, PreparedVerifyingKey as ArkPreparedVerifyingKey}; -use ark_snark::SNARK; - -use crate::bn254::api::SCALAR_SIZE; -use crate::bn254::{FieldElement, Proof, VerifyingKey}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use fastcrypto::error::{FastCryptoError, FastCryptoResult}; - -#[cfg(test)] -#[path = "unit_tests/verifier_tests.rs"] -mod verifier_tests; - -/// This is a helper function to store a pre-processed version of the verifying key. -/// This is roughly homologous to [`ark_groth16::data_structures::PreparedVerifyingKey`]. -/// Note that contrary to Arkworks, we don't store a "prepared" version of the gamma_g2_neg_pc, -/// delta_g2_neg_pc fields because they are very large and unpractical to use in the binary api. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct PreparedVerifyingKey { - /// The element vk.gamma_abc_g1, - /// aka the `[gamma^{-1} * (beta * a_i + alpha * b_i + c_i) * G]`, where i spans the public inputs - pub vk_gamma_abc_g1: Vec, - /// The element `e(alpha * G, beta * H)` in `E::GT`. - pub alpha_g1_beta_g2: Fq12, - /// The element `- gamma * H` in `E::G2`, for use in pairings. - pub gamma_g2_neg_pc: G2Affine, - /// The element `- delta * H` in `E::G2`, for use in pairings. - pub delta_g2_neg_pc: G2Affine, -} - -impl PreparedVerifyingKey { - /// Verify Groth16 proof using the prepared verifying key (see more at - /// [`PreparedVerifyingKey`]), a vector of public inputs and - /// the proof. - pub fn verify( - &self, - public_inputs: &[FieldElement], - proof: &Proof, - ) -> Result { - let x: Vec = public_inputs.iter().map(|x| x.0).collect(); - Groth16::::verify_with_processed_vk(&self.into(), &x, &proof.0) - .map_err(|e| FastCryptoError::GeneralError(e.to_string())) - } - - /// Serialize the prepared verifying key to its vectors form. - pub fn serialize(&self) -> Result>, FastCryptoError> { - let mut res = Vec::new(); - - let mut vk_gamma = Vec::new(); - for g1 in &self.vk_gamma_abc_g1 { - let mut g1_bytes = Vec::new(); - g1.serialize_compressed(&mut g1_bytes) - .map_err(|_| FastCryptoError::InvalidInput)?; - vk_gamma.append(&mut g1_bytes); - } - res.push(vk_gamma); - - let mut fq12 = Vec::new(); - self.alpha_g1_beta_g2 - .serialize_compressed(&mut fq12) - .map_err(|_| FastCryptoError::InvalidInput)?; - res.push(fq12); - - let mut gamma_bytes = Vec::new(); - self.gamma_g2_neg_pc - .serialize_compressed(&mut gamma_bytes) - .map_err(|_| FastCryptoError::InvalidInput)?; - res.push(gamma_bytes); - - let mut delta_bytes = Vec::new(); - self.delta_g2_neg_pc - .serialize_compressed(&mut delta_bytes) - .map_err(|_| FastCryptoError::InvalidInput)?; - res.push(delta_bytes); - Ok(res) - } - - /// Deserialize the prepared verifying key from the serialized fields of vk_gamma_abc_g1, - /// alpha_g1_beta_g2, gamma_g2_neg_pc, delta_g2_neg_pc - pub fn deserialize>(bytes: &Vec) -> Result { - if bytes.len() != 4 { - return Err(FastCryptoError::InputLengthWrong(bytes.len())); - } - - let vk_gamma_abc_g1_bytes = bytes[0].borrow(); - if vk_gamma_abc_g1_bytes.len() % SCALAR_SIZE != 0 { - return Err(FastCryptoError::InvalidInput); - } - - let mut vk_gamma_abc_g1: Vec = Vec::new(); - for g1_bytes in vk_gamma_abc_g1_bytes.chunks(SCALAR_SIZE) { - let g1 = G1Affine::deserialize_compressed(g1_bytes) - .map_err(|_| FastCryptoError::InvalidInput)?; - vk_gamma_abc_g1.push(g1); - } - - let alpha_g1_beta_g2 = Fq12::deserialize_compressed(bytes[1].borrow()) - .map_err(|_| FastCryptoError::InvalidInput)?; - - let gamma_g2_neg_pc = G2Affine::deserialize_compressed(bytes[2].borrow()) - .map_err(|_| FastCryptoError::InvalidInput)?; - - let delta_g2_neg_pc = G2Affine::deserialize_compressed(bytes[3].borrow()) - .map_err(|_| FastCryptoError::InvalidInput)?; - - Ok(PreparedVerifyingKey { - vk_gamma_abc_g1, - alpha_g1_beta_g2, - gamma_g2_neg_pc, - delta_g2_neg_pc, - }) - } -} - -impl From<&PreparedVerifyingKey> for ArkPreparedVerifyingKey { - /// Returns a [`ark_groth16::data_structures::PreparedVerifyingKey`] corresponding to this for - /// usage in the arkworks api. - fn from(pvk: &PreparedVerifyingKey) -> Self { - // Note that not all the members are set here, but we set enough to be able to run - // Groth16::::verify_with_processed_vk. - let mut ark_pvk = ArkPreparedVerifyingKey::default(); - ark_pvk.vk.gamma_abc_g1 = pvk.vk_gamma_abc_g1.clone(); - ark_pvk.alpha_g1_beta_g2 = pvk.alpha_g1_beta_g2; - ark_pvk.gamma_g2_neg_pc = G2Prepared::from(&pvk.gamma_g2_neg_pc); - ark_pvk.delta_g2_neg_pc = G2Prepared::from(&pvk.delta_g2_neg_pc); - ark_pvk - } -} - -impl From<&VerifyingKey> for PreparedVerifyingKey { - /// Takes an input [`ark_groth16::VerifyingKey`] `vk` and returns a `PreparedVerifyingKey`. This is roughly homologous to - /// [`ark_groth16::PreparedVerifyingKey::process_vk`]. - /// - /// ## Example: - /// ``` - /// use fastcrypto_zkp::{dummy_circuits::Fibonacci}; - /// use ark_bn254::{Bn254, Fr}; - /// use ark_ff::One; - /// use ark_groth16::Groth16; - /// use ark_std::rand::thread_rng; - /// use fastcrypto_zkp::bn254::verifier::PreparedVerifyingKey; - /// use fastcrypto_zkp::bn254::VerifyingKey; - /// - /// let mut rng = thread_rng(); - /// let params = { - /// let c = Fibonacci::::new(42, Fr::one(), Fr::one()); // 42 constraints, initial a = b = 1 (standard Fibonacci) - /// Groth16::::generate_random_parameters_with_reduction(c, &mut rng).unwrap() - /// }; - /// - /// // Prepare the verification key (for proof verification). Ideally, we would like to do this only - /// // once per circuit. - /// let pvk = PreparedVerifyingKey::from(&VerifyingKey::from(params.vk)); - /// ``` - fn from(vk: &VerifyingKey) -> Self { - (&vk.0).into() - } -} - -impl VerifyingKey { - /// Deserialize a serialized Groth16 verifying key in compressed format using arkworks' canonical serialisation format: https://docs.rs/ark-serialize/latest/ark_serialize/. - pub fn deserialize(bytes: &[u8]) -> FastCryptoResult { - ark_groth16::VerifyingKey::::deserialize_compressed(bytes) - .map(VerifyingKey) - .map_err(|_| FastCryptoError::InvalidInput) - } -} - -impl From<&ark_groth16::VerifyingKey> for PreparedVerifyingKey { - fn from(vk: &ark_groth16::VerifyingKey) -> Self { - PreparedVerifyingKey { - vk_gamma_abc_g1: vk.gamma_abc_g1.clone(), - alpha_g1_beta_g2: Bn254::pairing(vk.alpha_g1, vk.beta_g2).0, - gamma_g2_neg_pc: vk.gamma_g2.neg(), - delta_g2_neg_pc: vk.delta_g2.neg(), - } - } -} - -#[cfg(test)] -mod tests { - use crate::bn254::verifier::PreparedVerifyingKey; - use crate::dummy_circuits::DummyCircuit; - use ark_bn254::{Bn254, Fr}; - use ark_groth16::Groth16; - use ark_snark::SNARK; - use ark_std::rand::thread_rng; - use ark_std::UniformRand; - - #[test] - fn test_serialization() { - const PUBLIC_SIZE: usize = 128; - let rng = &mut thread_rng(); - let c = DummyCircuit:: { - a: Some(::rand(rng)), - b: Some(::rand(rng)), - num_variables: PUBLIC_SIZE, - num_constraints: 10, - }; - let (_, vk) = Groth16::::circuit_specific_setup(c, rng).unwrap(); - let pvk = PreparedVerifyingKey::from(&vk); - - let serialized = pvk.serialize().unwrap(); - let deserialized = PreparedVerifyingKey::deserialize(&serialized).unwrap(); - assert_eq!(pvk, deserialized); - } -} diff --git a/fastcrypto-zkp/src/groth16/api.rs b/fastcrypto-zkp/src/groth16/api.rs new file mode 100644 index 0000000000..65d18ed207 --- /dev/null +++ b/fastcrypto-zkp/src/groth16/api.rs @@ -0,0 +1,210 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::mem::size_of; + +use serde::de::DeserializeOwned; + +use fastcrypto::error::{FastCryptoError, FastCryptoResult}; +use fastcrypto::groups::{GroupElement, MultiScalarMul, Pairing}; +use fastcrypto::serde_helpers::{deserialize_vector, serialize_vector, ToFromByteArray}; + +use crate::groth16::{PreparedVerifyingKey, VerifyingKey}; + +/// Deserialize bytes as an Arkworks representation of a verifying key, and return a vector of the +/// four components of a prepared verified key (see more at [`PreparedVerifyingKey`]). +pub fn prepare_pvk_bytes< + G1, + const G1_SIZE: usize, + const G2_SIZE: usize, + const GT_SIZE: usize, + const FR_SIZE: usize, +>( + vk_bytes: &[u8], +) -> FastCryptoResult>> +where + G1: Pairing + ToFromByteArray, + ::Other: ToFromByteArray, + ::Output: GTSerialize, +{ + let vk = VerifyingKey::::from_arkworks_format::(vk_bytes)?; + Ok(PreparedVerifyingKey::from(&vk).serialize_into_parts()) +} + +/// Verify Groth16 proof using the serialized form of the four components in a prepared verifying key +/// (see more at [`PreparedVerifyingKey`]), serialized proof public input, which should +/// be concatenated serialized field elements of the scalar field of [`crate::conversions::SCALAR_SIZE`] +/// bytes each in little-endian format, and serialized proof points. +pub fn verify_groth16_in_bytes< + G1: Pairing, + const G1_SIZE: usize, + const G2_SIZE: usize, + const GT_SIZE: usize, + const FR_SIZE: usize, +>( + vk_gamma_abc_g1_bytes: &[u8], + alpha_g1_beta_g2_bytes: &[u8], + gamma_g2_neg_pc_bytes: &[u8], + delta_g2_neg_pc_bytes: &[u8], + public_inputs_as_bytes: &[u8], + proof_points_as_bytes: &[u8], +) -> Result +where + G1: ToFromByteArray + DeserializeOwned + MultiScalarMul, + ::Other: ToFromByteArray + DeserializeOwned, + ::Output: GroupElement + GTSerialize, + G1::ScalarType: FromLittleEndianByteArray, +{ + let public_inputs = deserialize_vector( + public_inputs_as_bytes, + G1::ScalarType::from_little_endian_byte_array, + )?; + let proof = + bcs::from_bytes(proof_points_as_bytes).map_err(|_| FastCryptoError::InvalidInput)?; + let prepared_vk = PreparedVerifyingKey::::deserialize_from_parts( + vk_gamma_abc_g1_bytes, + alpha_g1_beta_g2_bytes, + gamma_g2_neg_pc_bytes, + delta_g2_neg_pc_bytes, + )?; + Ok(prepared_vk.verify(&public_inputs, &proof).is_ok()) +} + +impl VerifyingKey { + pub fn from_arkworks_format( + bytes: &[u8], + ) -> FastCryptoResult + where + G1: ToFromByteArray, + ::Other: ToFromByteArray, + { + // The verifying key consists of: + // - alpha: G1 + // - beta: G2 + // - gamma: G2 + // - delta: G2 + // - n: u64 lendian (size of gamma_abc) + // - gamma_abc: Vec + + // We can't use bcs because there, the length of the vector is prefixed as a single byte and + // not a little-endian u64 as it is here. + + if (bytes.len() - (G1_SIZE + 3 * G2_SIZE + size_of::())) % G1_SIZE != 0 { + return Err(FastCryptoError::InvalidInput); + } + + let (alpha, bytes) = bytes.split_at(G1_SIZE); + let alpha = G1::from_byte_array(alpha.try_into().expect("Length already checked"))?; + + let (beta, bytes) = bytes.split_at(G2_SIZE); + let beta = G1::Other::from_byte_array(beta.try_into().expect("Length already checked"))?; + + let (gamma, bytes) = bytes.split_at(G2_SIZE); + let gamma = G1::Other::from_byte_array(gamma.try_into().expect("Length already checked"))?; + + let (delta, bytes) = bytes.split_at(G2_SIZE); + let delta = G1::Other::from_byte_array(delta.try_into().expect("Length already checked"))?; + + let (gamma_abc_length, bytes) = bytes.split_at(size_of::()); + let gamma_abc_length = + u64::from_le_bytes(gamma_abc_length.try_into().expect("Length already checked")); + + // There must be at least one element in gamma_abc, since this should be equal to the number + // of public inputs + 1. + if gamma_abc_length == 0 { + return Err(FastCryptoError::InvalidInput); + } + + let gamma_abc = deserialize_vector(bytes, G1::from_byte_array)?; + + if gamma_abc.len() != gamma_abc_length as usize { + return Err(FastCryptoError::InvalidInput); + } + + Ok(VerifyingKey { + alpha, + beta, + gamma, + delta, + gamma_abc, + }) + } +} + +impl PreparedVerifyingKey { + pub fn serialize_into_parts( + &self, + ) -> Vec> + where + G1: ToFromByteArray, + G1::Other: ToFromByteArray, + ::Output: GTSerialize, + { + vec![ + serialize_vector(&self.vk_gamma_abc, G1::to_byte_array), + self.alpha_beta.to_arkworks_bytes().to_vec(), + self.gamma_neg.to_byte_array().to_vec(), + self.delta_neg.to_byte_array().to_vec(), + ] + } + + pub fn deserialize_from_parts< + const G1_SIZE: usize, + const G2_SIZE: usize, + const GT_SIZE: usize, + >( + vk_gamma_abc_bytes: &[u8], + alpha_beta_bytes: &[u8], + gamma_neg_bytes: &[u8], + delta_neg_bytes: &[u8], + ) -> FastCryptoResult + where + G1: ToFromByteArray, + G1::Other: ToFromByteArray, + ::Output: GTSerialize, + { + let vk_gamma_abc = + deserialize_vector::(vk_gamma_abc_bytes, G1::from_byte_array)?; + + let alpha_beta = ::Output::from_arkworks_bytes( + alpha_beta_bytes + .try_into() + .map_err(|_| FastCryptoError::InvalidInput)?, + )?; + + let gamma_neg = ::Other::from_byte_array( + gamma_neg_bytes + .try_into() + .map_err(|_| FastCryptoError::InvalidInput)?, + )?; + + let delta_neg = ::Other::from_byte_array( + delta_neg_bytes + .try_into() + .map_err(|_| FastCryptoError::InvalidInput)?, + )?; + + Ok(Self { + vk_gamma_abc, + alpha_beta, + gamma_neg, + delta_neg, + }) + } +} + +/// Serialization of GT elements is typically not standardized across libraries, so implementations +/// must specify what implementation to use here to be compatible with the arkworks format (see +/// [`ark_ec::pairing::PairingOutput`]). +pub trait GTSerialize: Sized { + /// Serialize the element into a byte array. + fn to_arkworks_bytes(&self) -> [u8; SIZE_IN_BYTES]; + + /// Deserialize the element from a byte array. + fn from_arkworks_bytes(bytes: &[u8; SIZE_IN_BYTES]) -> FastCryptoResult; +} + +/// Scalars given to the API are expected to be in little-endian format. +pub trait FromLittleEndianByteArray: Sized { + fn from_little_endian_byte_array(bytes: &[u8; SIZE_IN_BYTES]) -> FastCryptoResult; +} diff --git a/fastcrypto-zkp/src/groth16/mod.rs b/fastcrypto-zkp/src/groth16/mod.rs new file mode 100644 index 0000000000..dcdcba3f3f --- /dev/null +++ b/fastcrypto-zkp/src/groth16/mod.rs @@ -0,0 +1,147 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::fmt::Debug; + +use fastcrypto::error::{FastCryptoError, FastCryptoResult}; +use serde::Deserialize; + +use fastcrypto::groups::{GroupElement, MultiScalarMul, Pairing}; + +pub mod api; + +#[derive(Debug, Deserialize)] +pub struct Proof +where + G1::Other: Debug, +{ + a: G1, + b: G1::Other, + c: G1, +} + +#[derive(Debug)] +pub struct VerifyingKey +where + G1::Other: Debug, +{ + alpha: G1, + beta: G1::Other, + gamma: G1::Other, + delta: G1::Other, + gamma_abc: Vec, +} + +/// This is a helper function to store a pre-processed version of the verifying key. +/// This is roughly homologous to [`ark_groth16::data_structures::PreparedVerifyingKey`]. +/// Note that contrary to Arkworks, we don't store a "prepared" version of the `gamma_neg` and +/// `delta_neg` fields because they are very large and unpractical to use in the binary API. +pub struct PreparedVerifyingKey +where + G1: Pairing, +{ + /// The element vk.gamma_abc, + /// aka the `[gamma^{-1} * (beta * a_i + alpha * b_i + c_i) * G]`, where i spans the public inputs + vk_gamma_abc: Vec, + + /// The element `e(alpha * G, beta * H)` in `GT`. + alpha_beta: ::Output, + + /// The element `- gamma * H` in `G2`, for use in pairings. + gamma_neg: ::Other, + + /// The element `- delta * H` in `G2`, for use in pairings. + delta_neg: ::Other, +} + +impl Proof { + pub fn new(a: G1, b: G1::Other, c: G1) -> Self { + Proof { a, b, c } + } +} + +impl VerifyingKey { + pub fn new( + alpha: G1, + beta: G1::Other, + gamma: G1::Other, + delta: G1::Other, + gamma_abc: Vec, + ) -> Self { + VerifyingKey { + alpha, + beta, + gamma, + delta, + gamma_abc, + } + } +} + +impl From<&VerifyingKey> for PreparedVerifyingKey +where + G1: Pairing, +{ + fn from(vk: &VerifyingKey) -> Self { + PreparedVerifyingKey { + vk_gamma_abc: vk.gamma_abc.clone(), + alpha_beta: vk.alpha.pairing(&vk.beta), + gamma_neg: -vk.gamma, + delta_neg: -vk.delta, + } + } +} + +impl PreparedVerifyingKey { + /// Verify Groth16 proof using the prepared verifying key (see more at + /// [`crate::bn254::verifier::PreparedVerifyingKey`]), a vector of public inputs and the proof. + pub fn verify( + &self, + public_inputs: &[G1::ScalarType], + proof: &Proof, + ) -> FastCryptoResult<()> + where + G1: MultiScalarMul, + ::Output: GroupElement, + { + let prepared_inputs = self.prepare_inputs(public_inputs)?; + self.verify_with_prepared_inputs(&prepared_inputs, proof) + } + + /// Verify Groth16 proof using the prepared verifying key (see more at + /// [`crate::bn254::verifier::PreparedVerifyingKey`]), a prepared public input (see + /// [`prepare_inputs`]) and the proof. + pub fn verify_with_prepared_inputs( + &self, + prepared_inputs: &G1, + proof: &Proof, + ) -> FastCryptoResult<()> + where + ::Output: GroupElement, + { + let lhs = G1::multi_pairing( + &[proof.a, *prepared_inputs, proof.c], + &[proof.b, self.gamma_neg, self.delta_neg], + )?; + if lhs == self.alpha_beta { + Ok(()) + } else { + Err(FastCryptoError::InvalidProof) + } + } + + /// Prepare the public inputs for use in [`verify_with_prepared_inputs`]. + pub fn prepare_inputs(&self, public_inputs: &[G1::ScalarType]) -> FastCryptoResult + where + G1: MultiScalarMul, + { + if (public_inputs.len() + 1) != self.vk_gamma_abc.len() { + return Err(FastCryptoError::InvalidInput); + } + if public_inputs.is_empty() { + return Ok(self.vk_gamma_abc[0]); + } + G1::multi_scalar_mul(public_inputs, &self.vk_gamma_abc[1..]) + .map(|x| x + self.vk_gamma_abc[0]) + } +} diff --git a/fastcrypto-zkp/src/lib.rs b/fastcrypto-zkp/src/lib.rs index 4631dd3b32..7807a4a785 100644 --- a/fastcrypto-zkp/src/lib.rs +++ b/fastcrypto-zkp/src/lib.rs @@ -25,3 +25,5 @@ pub mod dummy_circuits; /// Circom-compatible deserialization of points pub mod zk_login_utils; + +pub mod groth16; diff --git a/fastcrypto/Cargo.toml b/fastcrypto/Cargo.toml index ff452b5be3..caff4a584a 100644 --- a/fastcrypto/Cargo.toml +++ b/fastcrypto/Cargo.toml @@ -46,6 +46,7 @@ elliptic-curve = { version = "0.13.2", features = ["hash2curve"] } rsa = { version = "0.8.2", features = ["sha2"] } static_assertions = "1.1.0" ark-secp256r1 = "0.4.0" +ark-bn254 = "0.4.0" ark-ec = "0.4.1" ark-ff = "0.4.1" ark-serialize = "0.4.1" diff --git a/fastcrypto/src/groups/bls12381.rs b/fastcrypto/src/groups/bls12381.rs index f6c8399062..50318b0e70 100644 --- a/fastcrypto/src/groups/bls12381.rs +++ b/fastcrypto/src/groups/bls12381.rs @@ -231,6 +231,42 @@ impl Pairing for G1Element { } GTElement(res) } + + fn multi_pairing( + points_g1: &[Self], + points_g2: &[Self::Other], + ) -> FastCryptoResult<::Output> + where + ::Output: GroupElement, + { + if points_g1.len() != points_g2.len() { + return Err(FastCryptoError::InvalidInput); + } + + let (points_g1, points_g2): (Vec<_>, Vec<_>) = points_g1 + .iter() + .zip(points_g2.iter()) + .filter(|(&g1, &g2)| g1 != G1Element::zero() && g2 != G2Element::zero()) + .map(|(&g1, &g2)| (g1, g2)) + .unzip(); + + if points_g1.is_empty() { + return Ok(::Output::zero()); + } + + let mut blst_pairing = blst::Pairing::new(false, &[]); + for (g1, g2) in points_g1.iter().zip(points_g2.iter()) { + let mut g1_affine = blst_p1_affine::default(); + let mut g2_affine = blst_p2_affine::default(); + unsafe { + blst_p1_to_affine(&mut g1_affine, &g1.0); + blst_p2_to_affine(&mut g2_affine, &g2.0); + } + blst_pairing.raw_aggregate(&g2_affine, &g1_affine); + } + let result = blst_pairing.as_fp12().final_exp(); + Ok(GTElement(result)) + } } impl HashToGroupElement for G1Element { diff --git a/fastcrypto/src/groups/bn254/g1.rs b/fastcrypto/src/groups/bn254/g1.rs new file mode 100644 index 0000000000..338af32f4e --- /dev/null +++ b/fastcrypto/src/groups/bn254/g1.rs @@ -0,0 +1,96 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::ops::{Div, Mul}; + +use crate::error::{FastCryptoError, FastCryptoResult}; +use crate::groups::bn254::Scalar; +use crate::groups::bn254::{G1Element, G1_ELEMENT_BYTE_LENGTH}; +use crate::groups::{FromTrustedByteArray, GroupElement, MultiScalarMul, Scalar as ScalarType}; +use crate::serde_helpers::ToFromByteArray; +use crate::serialize_deserialize_with_to_from_byte_array; +use ark_bn254::{G1Affine, G1Projective}; +use ark_ec::{AffineRepr, CurveGroup, Group, VariableBaseMSM}; +use ark_ff::Zero; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use serde::{de, Deserialize}; + +impl GroupElement for G1Element { + type ScalarType = Scalar; + + fn zero() -> Self { + G1Element(G1Projective::zero()) + } + + fn generator() -> Self { + G1Element(G1Projective::generator()) + } +} + +impl Div for G1Element { + type Output = FastCryptoResult; + + fn div(self, rhs: Scalar) -> Self::Output { + let inverse = rhs.inverse()?; + Ok(self.mul(inverse)) + } +} + +impl Mul for G1Element { + type Output = Self; + + fn mul(self, rhs: Scalar) -> Self::Output { + Self(self.0.mul(rhs.0)) + } +} + +impl ToFromByteArray for G1Element { + fn from_byte_array(bytes: &[u8; G1_ELEMENT_BYTE_LENGTH]) -> Result { + let point = G1Affine::deserialize_compressed(bytes.as_slice()) + .map_err(|_| FastCryptoError::InvalidInput)?; + + // Arkworks only checks the infinity flag, but we require all-zeros to have unique serialization + if point.is_zero() + && bytes[0..G1_ELEMENT_BYTE_LENGTH - 1] + .iter() + .any(|x| !x.is_zero()) + { + return Err(FastCryptoError::InvalidInput); + } + + Ok(Self(G1Projective::from(point))) + } + + fn to_byte_array(&self) -> [u8; G1_ELEMENT_BYTE_LENGTH] { + let mut bytes = [0u8; G1_ELEMENT_BYTE_LENGTH]; + self.0 + .serialize_compressed(bytes.as_mut_slice()) + .expect("Never fails"); + bytes + } +} + +impl FromTrustedByteArray for G1Element { + fn from_trusted_byte_array(bytes: &[u8; G1_ELEMENT_BYTE_LENGTH]) -> FastCryptoResult { + G1Projective::deserialize_compressed_unchecked(bytes.as_slice()) + .map_err(|_| FastCryptoError::InvalidInput) + .map(G1Element) + } +} + +serialize_deserialize_with_to_from_byte_array!(G1Element); + +impl MultiScalarMul for G1Element { + fn multi_scalar_mul(scalars: &[Self::ScalarType], points: &[Self]) -> FastCryptoResult { + if scalars.len() != points.len() { + return Err(FastCryptoError::InvalidInput); + } + if scalars.is_empty() { + return Ok(Self::zero()); + } + Ok(Self(G1Projective::msm_unchecked( + &points.iter().map(|x| x.0.into_affine()).collect::>(), + &scalars.iter().map(|x| x.0).collect::>(), + ))) + } +} diff --git a/fastcrypto/src/groups/bn254/g2.rs b/fastcrypto/src/groups/bn254/g2.rs new file mode 100644 index 0000000000..b0edbaf7e9 --- /dev/null +++ b/fastcrypto/src/groups/bn254/g2.rs @@ -0,0 +1,80 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::{FastCryptoError, FastCryptoResult}; +use crate::groups::bn254::G2Element; +use crate::groups::bn254::{Scalar, G2_ELEMENT_BYTE_LENGTH}; +use crate::groups::{FromTrustedByteArray, GroupElement, Scalar as ScalarType}; +use crate::serde_helpers::ToFromByteArray; +use crate::serialize_deserialize_with_to_from_byte_array; +use ark_bn254::{G2Affine, G2Projective}; +use ark_ec::{AffineRepr, Group}; +use ark_ff::Zero; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use serde::{de, Deserialize}; +use std::ops::{Div, Mul}; + +impl GroupElement for G2Element { + type ScalarType = Scalar; + + fn zero() -> Self { + G2Element(G2Projective::zero()) + } + + fn generator() -> Self { + G2Element(G2Projective::generator()) + } +} + +impl Div for G2Element { + type Output = FastCryptoResult; + + fn div(self, rhs: Scalar) -> Self::Output { + let inverse = rhs.inverse()?; + Ok(self.mul(inverse)) + } +} + +impl Mul for G2Element { + type Output = Self; + + fn mul(self, rhs: Scalar) -> Self::Output { + Self(self.0.mul(rhs.0)) + } +} + +impl ToFromByteArray for G2Element { + fn from_byte_array(bytes: &[u8; G2_ELEMENT_BYTE_LENGTH]) -> Result { + let point = G2Affine::deserialize_compressed(bytes.as_slice()) + .map_err(|_| FastCryptoError::InvalidInput)?; + + // Arkworks only checks the infinty flag, but we require all-zeros to have unique serialization + if point.is_zero() + && bytes[0..G2_ELEMENT_BYTE_LENGTH - 1] + .iter() + .any(|x| !x.is_zero()) + { + return Err(FastCryptoError::InvalidInput); + } + + Ok(Self(G2Projective::from(point))) + } + + fn to_byte_array(&self) -> [u8; G2_ELEMENT_BYTE_LENGTH] { + let mut bytes = [0u8; G2_ELEMENT_BYTE_LENGTH]; + self.0 + .serialize_compressed(bytes.as_mut_slice()) + .expect("Never fails"); + bytes + } +} + +impl FromTrustedByteArray for G2Element { + fn from_trusted_byte_array(bytes: &[u8; G2_ELEMENT_BYTE_LENGTH]) -> FastCryptoResult { + G2Projective::deserialize_compressed_unchecked(bytes.as_slice()) + .map_err(|_| FastCryptoError::InvalidInput) + .map(G2Element) + } +} + +serialize_deserialize_with_to_from_byte_array!(G2Element); diff --git a/fastcrypto/src/groups/bn254/gt.rs b/fastcrypto/src/groups/bn254/gt.rs new file mode 100644 index 0000000000..ad610a5e21 --- /dev/null +++ b/fastcrypto/src/groups/bn254/gt.rs @@ -0,0 +1,80 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::{FastCryptoError, FastCryptoResult}; +use crate::groups::bn254::G2Element; +use crate::groups::bn254::{G1Element, GTElement}; +use crate::groups::bn254::{Scalar, GT_ELEMENT_BYTE_LENGTH}; +use crate::groups::{FromTrustedByteArray, GroupElement, Pairing, Scalar as ScalarType}; +use crate::serde_helpers::ToFromByteArray; +use crate::serialize_deserialize_with_to_from_byte_array; +use ark_bn254::Bn254; +use ark_ec::pairing::PairingOutput; +use ark_ff::Zero; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use once_cell::sync::OnceCell; +use serde::{de, Deserialize}; +use std::ops::{Div, Mul}; + +#[allow(clippy::suspicious_arithmetic_impl)] +impl Div for GTElement { + type Output = FastCryptoResult; + + fn div(self, rhs: Scalar) -> Self::Output { + let inverse = rhs.inverse()?; + Ok(self * inverse) + } +} + +impl Mul for GTElement { + type Output = GTElement; + + fn mul(self, rhs: Scalar) -> Self::Output { + Self(self.0.mul(rhs.0)) + } +} + +impl GroupElement for GTElement { + type ScalarType = Scalar; + + fn zero() -> Self { + GTElement(PairingOutput::zero()) + } + + fn generator() -> Self { + static G: OnceCell> = OnceCell::new(); + Self(*G.get_or_init(Self::compute_generator)) + } +} + +impl GTElement { + fn compute_generator() -> PairingOutput { + G1Element::generator().pairing(&G2Element::generator()).0 + } +} + +impl FromTrustedByteArray for GTElement { + fn from_trusted_byte_array(bytes: &[u8; GT_ELEMENT_BYTE_LENGTH]) -> FastCryptoResult { + PairingOutput::::deserialize_compressed_unchecked(bytes.as_ref()) + .map_err(|_| FastCryptoError::InvalidInput) + .map(GTElement) + } +} + +impl ToFromByteArray for GTElement { + fn from_byte_array(bytes: &[u8; GT_ELEMENT_BYTE_LENGTH]) -> Result { + PairingOutput::::deserialize_compressed(bytes.as_ref()) + .map_err(|_| FastCryptoError::InvalidInput) + .map(GTElement) + } + + fn to_byte_array(&self) -> [u8; GT_ELEMENT_BYTE_LENGTH] { + let mut bytes = [0u8; GT_ELEMENT_BYTE_LENGTH]; + self.0 + .serialize_compressed(bytes.as_mut_slice()) + .expect("Never fails"); + bytes + } +} + +serialize_deserialize_with_to_from_byte_array!(GTElement); diff --git a/fastcrypto/src/groups/bn254/mod.rs b/fastcrypto/src/groups/bn254/mod.rs new file mode 100644 index 0000000000..ebe38fe4ef --- /dev/null +++ b/fastcrypto/src/groups/bn254/mod.rs @@ -0,0 +1,56 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::groups::{GroupElement, Pairing}; +use ark_bn254::{Bn254, Fr, G1Projective, G2Projective}; +use ark_ec::pairing::{Pairing as ArkworksPairing, PairingOutput}; +use derive_more::{Add, From, Neg, Sub}; +use fastcrypto_derive::GroupOpsExtend; + +mod g1; +mod g2; +mod gt; +mod scalar; + +#[cfg(test)] +mod tests; + +/// The byte length of a compressed element of G1. +pub const G1_ELEMENT_BYTE_LENGTH: usize = 32; + +/// The byte length of a compressed element of G2. +pub const G2_ELEMENT_BYTE_LENGTH: usize = 64; + +/// The byte length of a compressed element of GT. +pub const GT_ELEMENT_BYTE_LENGTH: usize = 384; + +/// The byte length of a scalar. +pub const SCALAR_LENGTH: usize = 32; + +/// Elements of the group G1 in BN254. +#[derive(Clone, Copy, Eq, PartialEq, Debug, Add, Sub, Neg, GroupOpsExtend, From)] +#[repr(transparent)] +pub struct G1Element(G1Projective); + +/// Elements of the group G2 in BN254. +#[derive(Clone, Copy, Eq, PartialEq, Debug, Add, Sub, Neg, GroupOpsExtend, From)] +#[repr(transparent)] +pub struct G2Element(G2Projective); + +/// Elements of the subgroup GT of F_q^{12} in BN254. Note that it is written in additive notation here. +#[derive(Clone, Copy, Eq, PartialEq, Debug, Add, Sub, Neg, GroupOpsExtend, From)] +pub struct GTElement(PairingOutput); + +/// This represents a scalar modulo r = 21888242871839275222246405745257275088548364400416034343698204186575808495617 +/// which is the order of the groups G1, G2 and GT. Note that r is a 254 bit prime. +#[derive(Clone, Copy, Eq, PartialEq, Debug, Add, Sub, Neg, GroupOpsExtend, From)] +pub struct Scalar(Fr); + +impl Pairing for G1Element { + type Other = G2Element; + type Output = GTElement; + + fn pairing(&self, other: &Self::Other) -> ::Output { + GTElement(Bn254::pairing(self.0, other.0)) + } +} diff --git a/fastcrypto/src/groups/bn254/scalar.rs b/fastcrypto/src/groups/bn254/scalar.rs new file mode 100644 index 0000000000..e44f405913 --- /dev/null +++ b/fastcrypto/src/groups/bn254/scalar.rs @@ -0,0 +1,82 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::ops::{Div, Mul}; + +use crate::error::{FastCryptoError, FastCryptoResult}; +use crate::groups::bn254::{Scalar, SCALAR_LENGTH}; +use crate::groups::GroupElement; +use crate::serde_helpers::ToFromByteArray; +use crate::traits::AllowedRng; +use crate::{groups, serialize_deserialize_with_to_from_byte_array}; +use ark_bn254::Fr; +use ark_ff::{Field, One, UniformRand, Zero}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use serde::{de, Deserialize}; + +impl Div for Scalar { + type Output = FastCryptoResult; + + fn div(self, rhs: Self) -> FastCryptoResult { + if rhs.0.is_zero() { + return Err(FastCryptoError::InvalidInput); + } + Ok(Self(self.0.div(rhs.0))) + } +} + +impl Mul for Scalar { + type Output = Self; + + fn mul(self, rhs: Scalar) -> Self::Output { + Self(self.0.mul(rhs.0)) + } +} + +impl GroupElement for Scalar { + type ScalarType = Scalar; + + fn zero() -> Self { + Self(Fr::zero()) + } + + fn generator() -> Self { + Self(Fr::one()) + } +} + +impl From for Scalar { + fn from(value: u128) -> Self { + Self(Fr::from(value)) + } +} + +impl groups::Scalar for Scalar { + fn rand(rng: &mut R) -> Self { + Self(Fr::rand(rng)) + } + + fn inverse(&self) -> FastCryptoResult { + Ok(Self(self.0.inverse().ok_or(FastCryptoError::InvalidInput)?)) + } +} + +impl ToFromByteArray for Scalar { + fn from_byte_array(bytes: &[u8; SCALAR_LENGTH]) -> Result { + // Note that arkworks uses little-endian byte order for serialization here. + Fr::deserialize_compressed(bytes.as_slice()) + .map_err(|_| FastCryptoError::InvalidInput) + .map(Scalar) + } + + fn to_byte_array(&self) -> [u8; SCALAR_LENGTH] { + // Note that arkworks uses little-endian byte order for serialization here. + let mut bytes = [0u8; SCALAR_LENGTH]; + self.0 + .serialize_compressed(bytes.as_mut_slice()) + .expect("Never fails"); + bytes + } +} + +serialize_deserialize_with_to_from_byte_array!(Scalar); diff --git a/fastcrypto/src/groups/bn254/tests.rs b/fastcrypto/src/groups/bn254/tests.rs new file mode 100644 index 0000000000..549d54c9e7 --- /dev/null +++ b/fastcrypto/src/groups/bn254/tests.rs @@ -0,0 +1,372 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use ark_bn254::{G1Affine, G2Affine}; +use ark_ec::AffineRepr; +use ark_serialize::CanonicalSerialize; +use rand::thread_rng; + +use crate::groups::bn254::G1Element; +use crate::groups::bn254::G2Element; +use crate::groups::bn254::GTElement; +use crate::groups::bn254::Scalar; +use crate::groups::{FromTrustedByteArray, GroupElement, Pairing, Scalar as ScalarTrait}; +use crate::serde_helpers::ToFromByteArray; +use crate::test_helpers::verify_serialization; + +#[test] +fn test_scalar_arithmetic() { + let zero = Scalar::zero(); + let one = Scalar::generator(); + + assert_eq!(zero, zero - zero); + assert_eq!(zero, -zero); + + let four = one + zero + one + one + one; + assert_eq!(four, Scalar::from(4)); + + let three = four - one; + assert_eq!(three, one + one + one); + + let six = three * Scalar::from(2); + assert_eq!(six, Scalar::from(6)); + + let two = (six / three).unwrap(); + assert_eq!(two, Scalar::from(2)); + + assert!((six / zero).is_err()); + + let inv_two = two.inverse().unwrap(); + assert_eq!(inv_two * two, one); + + // Check that u128 is decoded correctly. + let x: u128 = 2 << 66; + let x_scalar = Scalar::from(x); + let res = x_scalar / Scalar::from(8); + assert_eq!(res.unwrap(), Scalar::from(2 << 63)); +} + +#[test] +fn test_g1_arithmetic() { + // Test that different ways of computing [5]G gives the expected result + let g = G1Element::generator(); + + let p1 = g * Scalar::from(5); + + let p2 = g + g + g + g + g; + assert_eq!(p1, p2); + + let mut p3 = G1Element::zero(); + p3 += p2; + assert_eq!(p1, p3); + + let mut p4 = g; + p4 *= Scalar::from(5); + assert_eq!(p1, p4); + + let p5 = g * (Scalar::from(7) - Scalar::from(2)); + assert_eq!(p1, p5); + + let p6 = g * Scalar::zero(); + assert_eq!(G1Element::zero(), p6); + + let sc = Scalar::rand(&mut thread_rng()); + let p7 = g * sc; + assert_eq!(p7 * Scalar::from(1), p7); + + assert_ne!(G1Element::zero(), g); + assert_eq!(G1Element::zero(), g - g); + + assert!((G1Element::generator() / Scalar::zero()).is_err()); + assert_eq!((p5 / Scalar::from(5)).unwrap(), g); + + let identity = G1Element::zero(); + assert_eq!(identity, identity - identity); + assert_eq!(identity, -identity); +} + +#[test] +fn test_g2_arithmetic() { + // Test that different ways of computing [5]G gives the expected result + let g = G2Element::generator(); + + let p1 = g * Scalar::from(5); + + let p2 = g + g + g + g + g + g - g; + assert_eq!(p1, p2); + + let mut p3 = G2Element::zero(); + p3 += p2; + assert_eq!(p1, p3); + + let mut p4 = g; + p4 *= Scalar::from(5); + assert_eq!(p1, p4); + + let p5 = g * (Scalar::from(7) - Scalar::from(2)); + assert_eq!(p1, p5); + + let p6 = g * Scalar::zero(); + assert_eq!(G2Element::zero(), p6); + + let sc = Scalar::rand(&mut thread_rng()); + let p7 = g * sc; + assert_eq!(p7 * Scalar::from(1), p7); + + assert!((G2Element::generator() / Scalar::zero()).is_err()); + assert_eq!((p5 / Scalar::from(5)).unwrap(), g); + + assert_ne!(G2Element::zero(), g); + assert_eq!(G2Element::zero(), g - g); + + let identity = G2Element::zero(); + assert_eq!(identity, identity - identity); + assert_eq!(identity, -identity); +} + +#[test] +fn test_gt_arithmetic() { + // Test that different ways of computing [5]G gives the expected result + let g = GTElement::generator(); + + let p1 = g * Scalar::from(5); + + let p2 = g + g + g + g + g + g - g; + assert_eq!(p1, p2); + + let mut p3 = GTElement::zero(); + p3 += p2; + assert_eq!(p1, p3); + + let mut p4 = g; + p4 *= Scalar::from(5); + assert_eq!(p1, p4); + + let p5 = g * (Scalar::from(7) - Scalar::from(2)); + assert_eq!(p1, p5); + + let p6 = g * Scalar::zero(); + assert_eq!(GTElement::zero(), p6); + + let sc = Scalar::rand(&mut thread_rng()); + let p7 = g * sc; + assert_eq!(p7 * Scalar::from(1), p7); + + assert_ne!(GTElement::zero(), g); + assert_eq!(GTElement::zero(), g - g); + assert_eq!(GTElement::zero(), GTElement::zero() - GTElement::zero()); + + assert!((GTElement::generator() / Scalar::zero()).is_err()); + assert_eq!((p5 / Scalar::from(5)).unwrap(), g); +} + +#[test] +fn test_pairing() { + let a = Scalar::rand(&mut thread_rng()); + + assert_eq!( + G1Element::pairing(&(G1Element::generator() * a), &G2Element::generator()), + G1Element::pairing(&G1Element::generator(), &(G2Element::generator() * a)) + ); +} + +#[test] +fn test_serde_and_regression() { + let s1 = Scalar::generator(); + let g1 = G1Element::generator(); + let g2 = G2Element::generator(); + let gt = GTElement::generator(); + let id1 = G1Element::zero(); + let id2 = G2Element::zero(); + let id3 = GTElement::zero(); + let id4 = Scalar::zero(); + + verify_serialization( + &s1, + Some( + hex::decode("0100000000000000000000000000000000000000000000000000000000000000") + .unwrap() + .as_slice(), + ), + ); + verify_serialization( + &g1, + Some( + hex::decode("0100000000000000000000000000000000000000000000000000000000000000") + .unwrap() + .as_slice(), + ), + ); + verify_serialization(&g2, Some(hex::decode("edf692d95cbdde46ddda5ef7d422436779445c5e66006a42761e1f12efde0018c212f3aeb785e49712e7a9353349aaf1255dfb31b7bf60723a480d9293938e19").unwrap().as_slice())); + verify_serialization(>, Some(hex::decode("950e879d73631f5eb5788589eb5f7ef8d63e0a28de1ba00dfe4ca9ed3f252b264a8afb8eb4349db466ed1809ea4d7c39bdab7938821f1b0a00a295c72c2de002e01dbdfd0254134efcb1ec877395d25f937719b344adb1a58d129be2d6f2a9132b16a16e8ab030b130e69c69bd20b4c45986e6744a98314b5c1a0f50faa90b04dbaf9ef8aeeee3f50be31c210b598f4752f073987f9d35be8f6770d83f2ffc0af0d18dd9d2dbcdf943825acc12a7a9ddca45e629d962c6bd64908c3930a5541cfe2924dcc5580d5cef7a4bfdec90a91b59926f850d4a7923c01a5a5dbf0f5c094a2b9fb9d415820fa6b40c59bb9eade9c953407b0fc11da350a9d872cad6d3142974ca385854afdf5f583c04231adc5957c8914b6b20dc89660ed7c3bbe7c01d972be2d53ecdb27a1bcc16ac610db95aa7d237c8ff55a898cb88645a0e32530b23d7ebf5dafdd79b0f9c2ac4ba07ce18d3d16cf36e47916c4cae5d08d3afa813972c769e8514533e380c9443b3e1ee5c96fa3a0a73f301b626454721527bf900").unwrap().as_slice())); + verify_serialization( + &id1, + Some( + hex::decode("0000000000000000000000000000000000000000000000000000000000000040") + .unwrap() + .as_slice(), + ), + ); + verify_serialization(&id2, Some(hex::decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040").unwrap().as_slice())); + verify_serialization(&id3, Some(hex::decode("010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().as_slice())); + verify_serialization( + &id4, + Some( + hex::decode("0000000000000000000000000000000000000000000000000000000000000000") + .unwrap() + .as_slice(), + ), + ); +} + +#[test] +fn test_serialization_scalar() { + let bytes = [0u8; 32]; + assert_eq!(Scalar::from_byte_array(&bytes).unwrap(), Scalar::zero()); + + // Scalar::from_byte_array should not accept the order or above it. + let mut order = + hex::decode("30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001").unwrap(); + order.reverse(); // Little-endian + assert!(Scalar::from_byte_array(<&[u8; 32]>::try_from(order.as_slice()).unwrap()).is_err()); + + // Scalar::from_byte_array should accept the order - 1. + let mut order_minus_one = + hex::decode("30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000").unwrap(); + order_minus_one.reverse(); // Little-endian + assert_eq!( + Scalar::from_byte_array(<&[u8; 32]>::try_from(order_minus_one.as_slice()).unwrap()) + .unwrap(), + Scalar::zero() - Scalar::generator() + ); + + for _ in 0..100 { + let s = Scalar::rand(&mut thread_rng()); + let bytes = s.to_byte_array(); + assert_eq!(s, Scalar::from_byte_array(&bytes).unwrap()); + } +} + +#[test] +fn test_serialization_g1() { + let infinity_bit = 0x40; + + // All zero serialization for G1 should fail. + let mut bytes = [0u8; 32]; + assert!(G1Element::from_byte_array(&bytes).is_err()); + + // Valid infinity + bytes[31] |= infinity_bit; + assert_eq!( + G1Element::zero(), + G1Element::from_byte_array(&bytes).unwrap() + ); + + // to and from_byte_array should be inverses. + let bytes = G1Element::generator().to_byte_array(); + assert_eq!( + G1Element::generator(), + G1Element::from_byte_array(&bytes).unwrap() + ); + + // Test correct uncompressed serialization of a point + let mut uncompressed_bytes = [0u8; 64]; + G1Affine::generator() + .serialize_uncompressed(uncompressed_bytes.as_mut_slice()) + .unwrap(); + // This works because from_byte_array the compressed format is just the first coordinate. + assert_eq!( + G1Element::generator(), + G1Element::from_byte_array(&(uncompressed_bytes[0..32].try_into().unwrap())).unwrap() + ); + + // Test FromTrustedByteArray. + let mut bytes = G1Element::generator().to_byte_array(); + let g1 = G1Element::from_trusted_byte_array(&bytes).unwrap(); + assert_eq!(g1, G1Element::generator()); + // Also when the input is not a valid point. + bytes[bytes.len() - 1] += 2; + assert!(G1Element::from_trusted_byte_array(&bytes).is_ok()); +} + +#[test] +fn test_serialization_g2() { + let infinity_bit = 0x40; + + // All zero serialization for G2 should fail. + let mut bytes = [0u8; 64]; + assert!(G2Element::from_byte_array(&bytes).is_err()); + + // Valid infinity when the right bits are set. + bytes[63] |= infinity_bit; + assert_eq!( + G2Element::zero(), + G2Element::from_byte_array(&bytes).unwrap() + ); + + // to and from_byte_array should be inverses. + let bytes = G2Element::generator().to_byte_array(); + assert_eq!( + G2Element::generator(), + G2Element::from_byte_array(&bytes).unwrap() + ); + + // Test correct uncompressed serialization of a point + let mut uncompressed_bytes = [0u8; 128]; + G2Affine::generator() + .serialize_uncompressed(uncompressed_bytes.as_mut_slice()) + .unwrap(); + + // This works because the compressed format is just the first coordinate. + assert_eq!( + G2Element::generator(), + G2Element::from_byte_array(&(uncompressed_bytes[0..64].try_into().unwrap())).unwrap() + ); + + // Test FromTrustedByteArray. + let mut bytes = G2Element::generator().to_byte_array(); + let g2 = G2Element::from_trusted_byte_array(&bytes).unwrap(); + assert_eq!(g2, G2Element::generator()); + // Also when the input is not a valid point. + bytes[bytes.len() - 1] += 2; + assert!(G2Element::from_trusted_byte_array(&bytes).is_ok()); + assert!(G2Element::from_byte_array(&bytes).is_err()); +} + +#[test] +fn test_serialization_gt() { + // All zero serialization for GT should fail. + let bytes = [0u8; 384]; + assert!(GTElement::from_byte_array(&bytes).is_err()); + + // to and from_byte_array should be inverses. + let bytes = GTElement::generator().to_byte_array(); + assert_eq!( + GTElement::generator(), + GTElement::from_byte_array(&bytes).unwrap() + ); + + // reject if one of the elements >= P + let mut bytes = GTElement::generator().to_byte_array(); + let p = + hex::decode("30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47").unwrap(); + let mut carry = 0; + let mut target = [0; 32]; + for i in (0..32).rev() { + let sum = (bytes[i] as u16) + (p[i] as u16) + carry; + target[i] = (sum % 256) as u8; + carry = sum / 256; + } + assert_eq!(carry, 0); + bytes[0..32].copy_from_slice(&target); + assert!(GTElement::from_byte_array(&bytes).is_err()); + + // Test FromTrustedByteArray. + let mut bytes = GTElement::generator().to_byte_array(); + let gt = GTElement::from_trusted_byte_array(&bytes).unwrap(); + assert_eq!(gt, GTElement::generator()); + // Also when the input is not a valid point. + bytes[bytes.len() - 1] += 2; + assert!(GTElement::from_trusted_byte_array(&bytes).is_ok()); + assert!(GTElement::from_byte_array(&bytes).is_err()); +} diff --git a/fastcrypto/src/groups/mod.rs b/fastcrypto/src/groups/mod.rs index e561178e9f..8ec1327136 100644 --- a/fastcrypto/src/groups/mod.rs +++ b/fastcrypto/src/groups/mod.rs @@ -10,6 +10,7 @@ use std::fmt::Debug; use std::ops::{AddAssign, SubAssign}; pub mod bls12381; +pub mod bn254; pub mod ristretto255; pub mod secp256r1; @@ -64,6 +65,31 @@ pub trait Pairing: GroupElement { type Output; fn pairing(&self, other: &Self::Other) -> ::Output; + + /// Multi-pairing operation that computes the sum of pairings of two slices of elements. + fn multi_pairing( + points_g1: &[Self], + points_g2: &[Self::Other], + ) -> FastCryptoResult<::Output> + where + ::Output: GroupElement, + { + if points_g1.len() != points_g2.len() { + return Err(FastCryptoError::InvalidInput); + } + if points_g1.is_empty() { + return Ok(::Output::zero()); + } + Ok(points_g1 + .iter() + .skip(1) + .zip(points_g2.iter().skip(1)) + .map(|(g1, g2)| g1.pairing(g2)) + .fold( + points_g1[0].pairing(&points_g2[0]), + ::Output::add, + )) + } } /// Trait for groups that have a reduction from a random buffer to a group element that is secure diff --git a/fastcrypto/src/serde_helpers.rs b/fastcrypto/src/serde_helpers.rs index b4da855931..692ae85d22 100644 --- a/fastcrypto/src/serde_helpers.rs +++ b/fastcrypto/src/serde_helpers.rs @@ -12,7 +12,7 @@ use serde_with::serde_as; use std::fmt; use std::fmt::{Debug, Display}; -use crate::error::FastCryptoError; +use crate::error::{FastCryptoError, FastCryptoResult}; use crate::{ encoding::{Base64, Encoding}, traits::{KeyPair, SigningKey, ToFromBytes, VerifyingKey}, @@ -267,6 +267,36 @@ impl Display for BytesRepresentation { } } +/// Given a byte array of length `N * SIZE_IN_BYTES`, deserialize it into a vector of `N` elements +/// of type `T` with the given deserialization function. +pub fn deserialize_vector( + bytes: &[u8], + from_byte_array: fn(&[u8; SIZE_IN_BYTES]) -> FastCryptoResult, +) -> FastCryptoResult> { + if bytes.len() % SIZE_IN_BYTES != 0 { + return Err(FastCryptoError::InvalidInput); + } + bytes + .chunks_exact(SIZE_IN_BYTES) + .map(|chunk| { + from_byte_array( + &chunk + .try_into() + .expect("Length of `chunk` is always equal to SIZE_IN_BYTES"), + ) + }) + .collect::>>() +} + +/// Serialize a vector of elements of type T into a byte array by simply concatenating their binary +/// representations with the given deserialization function. +pub fn serialize_vector( + elements: &[T], + to_byte_array: fn(&T) -> [u8; SIZE_IN_BYTES], +) -> Vec { + elements.iter().flat_map(to_byte_array).collect() +} + // This is needed in Narwhal certificates but we don't want default implementations for all BytesRepresentations. impl Default for crate::bls12381::min_sig::BLS12381AggregateSignatureAsBytes { fn default() -> Self { diff --git a/fastcrypto/src/tests/bls12381_group_tests.rs b/fastcrypto/src/tests/bls12381_group_tests.rs index edae659bd0..e4f494c220 100644 --- a/fastcrypto/src/tests/bls12381_group_tests.rs +++ b/fastcrypto/src/tests/bls12381_group_tests.rs @@ -347,6 +347,41 @@ fn test_pairing_and_hash_to_curve() { let _ = G2Element::hash_to_group_element(&[]); let _ = G1Element::hash_to_group_element(&[1]); let _ = G2Element::hash_to_group_element(&[1]); + + // Test multi-pairing + assert!(G1Element::multi_pairing(&[], &[pk1]).is_err()); + assert_eq!( + G1Element::multi_pairing(&[], &[]).unwrap(), + GTElement::zero() + ); + assert_eq!( + G1Element::multi_pairing(&[e1], &[pk1]).unwrap(), + e1.pairing(&pk1) + ); + assert_eq!( + G1Element::multi_pairing(&[e1, pk2], &[pk1, e2]).unwrap(), + e1.pairing(&pk1) + pk2.pairing(&e2) + ); + assert_eq!( + G1Element::multi_pairing(&[G1Element::zero()], &[G2Element::zero()]).unwrap(), + GTElement::zero() + ); + assert_eq!( + G1Element::multi_pairing( + &[G1Element::zero(), G1Element::zero()], + &[G2Element::zero(), G2Element::zero()] + ) + .unwrap(), + GTElement::zero() + ); + assert_eq!( + G1Element::multi_pairing(&[G1Element::generator()], &[G2Element::zero()]).unwrap(), + GTElement::zero() + ); + assert_eq!( + G1Element::multi_pairing(&[G1Element::zero()], &[G2Element::generator()]).unwrap(), + GTElement::zero() + ); } #[test]