diff --git a/Cargo.toml b/Cargo.toml index 3ce2676..3127f5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,14 @@ [workspace] members = [ "h2s2", -] \ No newline at end of file +] + + +[workspace.dependencies] +ark-std = {version ="0.5.0", features = ["parallel"]} +ark-ec = {version = "0.5.0", features = ["parallel"]} +ark-ff = { version = "0.5", features = [ "parallel" ] } +blake2 = "0.10.6" +digest = "0.10.7" +rayon = "1.1" +ark-bn254 = "0.5.0" diff --git a/h2s2/Cargo.toml b/h2s2/Cargo.toml index 10f428c..dda7649 100644 --- a/h2s2/Cargo.toml +++ b/h2s2/Cargo.toml @@ -6,6 +6,7 @@ authors = [ "Severiano Sisneros ", "Alexis Asseman ", "Tomasz Kornuta ", + "Pedro Bufulin ", ] license = "Apache-2.0" description = "" @@ -13,7 +14,12 @@ edition = "2021" keywords = ["holographic", "homomorphic", "signature-scheme"] catagories = ["cryptography", "cryptography::cryptocurrencies"] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] +ark-ec = {workspace = true} +ark-std = { workspace = true} +ark-ff = { workspace = true} +ark-bn254 = { workspace = true} +blake2 = {workspace = true} +rayon = { workspace = true} +digest = { workspace = true} +once_cell = "1.20.2" diff --git a/h2s2/src/holographic_homomorphic_signature_scheme.rs b/h2s2/src/holographic_homomorphic_signature_scheme.rs index d0555cf..815c4b2 100644 --- a/h2s2/src/holographic_homomorphic_signature_scheme.rs +++ b/h2s2/src/holographic_homomorphic_signature_scheme.rs @@ -1,82 +1,63 @@ -//nc1 -use crate::ark_std::UniformRand; -use crate::ark_std::Zero; -use crate::Error; -use crate::HomomorphicSignatureScheme; use ark_ec::pairing::Pairing; -use ark_ec::AffineRepr; -use ark_std::{marker::PhantomData, rand::Rng}; +use ark_std::rand::Rng; use digest::Digest; -use std::ops::MulAssign; - -pub struct HolographicHomomorphicSignatureScheme { - _pairing: PhantomData

, - _hash: PhantomData, -} - -#[derive(Clone)] -pub struct H2S2Parameters { - pub g1_generators: Vec, - pub g2_generator: P::G2, -} - -impl HolographicHomomorphicSignatureScheme for NC1 { - type Parameters = H2S2Parameters

; - type PublicKey = P::G2; - type SecretKey = P::ScalarField; - type Signature = P::G1; - type Message = P::ScalarField; - type Weight = usize; - - /// Generate G2 element and `n` G1 elements - fn setup(rng: &mut R, n: usize) -> Result {} - - /// Generate hash aggregate (H_a) with `tag` and `n` lanes - fn precompute(tag: &[u8], n: usize) -> Result {} +use std::error::Error; + +pub trait HolographicHomomorphicSignatureScheme { + type Parameters; + type PublicKey; + type SecretKey; + type Signature; + type Message; + type Weight; + type AggregatedSignature; + + /// Generate one G2 element and `n` G1 elements + fn setup(n: usize) -> Result>; + + /// Generate hash aggregate (H_a) with `tag` and `n` lanes, and a + /// allocation_id as a ScalarField + fn precompute( + pp: &Self::Parameters, + tag: P::ScalarField, + n: usize, + ) -> Result<(P::G1, P::ScalarField), Box>; /// Generate private and public receipt keys using `pp` parameters from `setup` fn keygen( pp: &Self::Parameters, rng: &mut R, - ) -> Result<(Self::PublicKey, Self::SecretKey), Error> { - } + ) -> Result<(Self::PublicKey, Self::SecretKey), Box>; /// Sign `message` with `tag` at `index` fn sign( pp: &Self::Parameters, - sk: &Self::SecretKey, - tag: &[u8], - index: &[u8], - message: &[Self::Message], - ) -> Result { - } + tag: P::ScalarField, + index: usize, + message: Self::Message, + ) -> Result>; /// Verify a single `signature` matches `message` with `tag` at `index` using `pp` parameter and `pk` public key + /// TODO: index should be restricted to a number from 1 to N (max number of lanes) fn verify( pp: &Self::Parameters, - pk: &Self::PublicKey, - tag: &[u8], - index: &[u8], - message: &[Self::Message], + tag: P::ScalarField, + index: usize, + message: &Self::Message, signature: &Self::Signature, - ) -> Result { - } + ) -> Result>; - /// Verify aggregate `signature` matches `message_aggregate` with `tag` and `hash_aggregate`using `pp` parameter and `pk` public key + /// Verify aggregate `signature` matches `message_aggregate` + /// contained in [`AggregatedSignature`] with `tag` and `hash_aggregate` using `pp` parameter and `pk` public key fn verify_aggregate( pp: &Self::Parameters, - pk: &Self::PublicKey, - tag: &[u8], - message_aggregate: &[Self::Message], hash_aggregate: &P::G1, - signature: &Self::Signature, - ) -> Result { - } + signature: &Self::AggregatedSignature, + ) -> Result>; /// Aggregate `signatures` with `weights` fn evaluate( signatures: &[Self::Signature], weights: &[Self::Weight], - ) -> Result { - } + ) -> Result>; } diff --git a/h2s2/src/lib.rs b/h2s2/src/lib.rs index 7d12d9a..23c9e0d 100644 --- a/h2s2/src/lib.rs +++ b/h2s2/src/lib.rs @@ -1,14 +1,2 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +pub mod holographic_homomorphic_signature_scheme; +pub mod ncs; diff --git a/h2s2/src/ncs.rs b/h2s2/src/ncs.rs new file mode 100644 index 0000000..b5837cc --- /dev/null +++ b/h2s2/src/ncs.rs @@ -0,0 +1,406 @@ +use std::ops::{Add, Mul, MulAssign}; +use std::{error::Error, marker::PhantomData}; + +use crate::holographic_homomorphic_signature_scheme::HolographicHomomorphicSignatureScheme; +use ark_ec::pairing::Pairing; +use ark_ec::AffineRepr; +use ark_ec::PrimeGroup; +use ark_ff::PrimeField; +use ark_ff::{BigInteger, UniformRand, Zero}; +use ark_std::rand::Rng; +use digest::Digest; + +fn hash_to_g1(message_data: Vec) -> P::G1Affine { + let mut g1_point: Option = None; + let mut counter = 0; + while g1_point.is_some() == false { + let mut tmp_message = message_data.clone(); + tmp_message.push(counter); + let hash_out = D::digest(&tmp_message); + g1_point = P::G1Affine::from_random_bytes(&hash_out); + counter += 1; + } + g1_point.unwrap() +} + +pub struct NCS { + _pairing: PhantomData

, + _hash: PhantomData, +} + +pub struct H2S2Parameters { + pub g1_generators: Vec, + pub g2_generator: P::G2, + // this public_key is the `u` param in the notes. + // both the indexer and verifier need it. The secret key + // remains with the gateway + pub public_key: P::G2, + pub secret_key: Option, + pub max_lanes: usize, +} + +#[derive(Clone)] +pub struct Signature { + pub signature: P::G1, + pub lane_id: usize, + pub value: P::ScalarField, +} + +#[derive(Clone)] +pub struct AggregatedSignature { + pub signature: P::G1, + pub total_value: P::ScalarField, +} + +#[derive(Clone)] +pub struct AllocationParameters { + pub allocation_hash: P::G1, + pub allocation_id: P::ScalarField, +} + +impl HolographicHomomorphicSignatureScheme + for NCS +{ + type Parameters = H2S2Parameters

; + type PublicKey = P::G2; + type SecretKey = P::ScalarField; + type Signature = Signature

; + type Message = P::ScalarField; + type Weight = usize; + type AggregatedSignature = AggregatedSignature

; + + // n represents the max_lanes amount + fn setup(n: usize) -> Result> { + // Use the hardcoded G2 generator from the Pairing trait + let g2_generator = P::G2::generator(); + + // Generate a deterministic set of G1 generators based on the hardcoded G1 generator + let g1_base_generator = P::G1::generator(); + // In practice, we only ever use the first g1 generator + // so it is going to be generated only g1[0] + let g1_generators = vec![g1_base_generator]; + + // Initialize parameters without secret/public keys + let pp: H2S2Parameters

= H2S2Parameters { + g1_generators, + g2_generator, + secret_key: Some(P::ScalarField::zero()), // Temporary placeholder + public_key: P::G2::zero(), // Temporary placeholder + max_lanes: n, + }; + + Ok(pp) + } + + //TODO: allocationn_ids (tag in this case) must be unpredictable + // some random value has to be appended during initialization, prior + // to the precompute in this function + fn precompute( + _pp: &Self::Parameters, + tag: P::ScalarField, + n: usize, + ) -> Result<(P::G1, P::ScalarField), Box> { + let hash_vec = (0..n) + .into_iter() + .map(|lane_id| { + let mut message_data = tag.into_bigint().to_bytes_be(); + message_data.append(&mut lane_id.to_be_bytes().to_vec()); + hash_to_g1::(message_data) + }) + .collect::>(); + let mut allocation_hash = P::G1::zero(); + for hash_val in hash_vec { + allocation_hash += hash_val; + } + Ok((allocation_hash, tag)) + } + + fn keygen( + pp: &Self::Parameters, + rng: &mut R, + ) -> Result<(Self::PublicKey, Self::SecretKey), Box> { + // Generate the private key as a random scalar + let secret_key = P::ScalarField::rand(rng); + + // Compute the public key as the secret_key multiplied by the G2 generator + let public_key = pp.g2_generator.mul(secret_key); + + Ok((public_key, secret_key)) + } + + fn sign( + pp: &Self::Parameters, + tag: P::ScalarField, + index: usize, + message:

::ScalarField, + ) -> Result> { + let mut lane_data = tag.into_bigint().to_bytes_be(); + lane_data.append(&mut index.to_be_bytes().to_vec()); + let lane_point = hash_to_g1::(lane_data); + + let mut value_point = pp.g1_generators[0].clone(); + value_point.mul_assign(message); + + let message_point = lane_point.into_group() + value_point; + let mut signature = message_point.clone(); + signature.mul_assign(pp.secret_key.unwrap()); + Ok(Signature { + signature, + lane_id: index, + value: message, + }) + } + + fn verify( + pp: &Self::Parameters, + tag: P::ScalarField, + index: usize, + message: &Self::Message, + signature: &Self::Signature, + ) -> Result> { + let mut lane_data = tag.into_bigint().to_bytes_be(); + lane_data.append(&mut index.to_be_bytes().to_vec()); + let lane_point = hash_to_g1::(lane_data); + + let mut value_point = pp.g1_generators[0].clone(); + value_point.mul_assign(message); + + let rhs_pairing = P::pairing(lane_point.into_group() + value_point, pp.public_key); + let lhs_pairing = P::pairing(signature.signature, pp.g2_generator); + Ok(lhs_pairing == rhs_pairing) + } + + fn verify_aggregate( + pp: &Self::Parameters, + hash_aggregate: &P::G1, + signature: &Self::AggregatedSignature, + ) -> Result> { + let lane_point = hash_aggregate; + let mut value_point = pp.g1_generators[0].clone(); + value_point.mul_assign(signature.total_value); + + let rhs_pairing = P::pairing(lane_point.add(value_point), pp.public_key); + let lhs_pairing = P::pairing(signature.signature, pp.g2_generator); + Ok(lhs_pairing == rhs_pairing) + } + + fn evaluate( + signatures: &[Self::Signature], + weights: &[Self::Weight], + ) -> Result> { + // Ensure that the lengths of the inputs match + if signatures.len() != weights.len() { + return Err("Signatures and weights must have the same length".into()); + } + + let mut aggregate_signature = P::G1::zero(); + let mut total_value = P::ScalarField::zero(); + + for (sig, &wt) in signatures.iter().zip(weights.iter()) { + let weight_scalar = P::ScalarField::from(wt as u64); + aggregate_signature += sig.signature.mul(weight_scalar); + total_value += weight_scalar.mul(sig.value); + } + + Ok(AggregatedSignature { + signature: aggregate_signature, + total_value, + }) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use ark_bn254::Bn254; + //we could also use the ark_bls12_381 curve which was intended to substitute this one: + //https://docs.rs/ark-bls12-381/latest/ark_bls12_381/ + // Ethereum is reviewing using it: + // https://eips.ethereum.org/EIPS/eip-2537 + // use ark_bls12_381::Bn254; + use ark_std::test_rng; + use once_cell::sync::Lazy; + type Curve = Bn254; + type Fr = ark_bn254::Fr; + type Hasher = blake2::Blake2b512; + static N: usize = 10; // Define the number of generators + + static PARAMS: Lazy> = Lazy::new(|| { + let mut rng = test_rng(); + + let mut params = NCS::::setup(N).expect("Setup failed"); + + // Generate the secret and public keys using keygen + let (pk, sk) = NCS::::keygen(¶ms, &mut rng).expect("Keygen failed"); + + params.secret_key = Some(sk); + params.public_key = pk; + params + }); + + #[test] + fn test_setup_and_keygen() { + // Use the correct WBConfig implementation for G1 + + let mut rng = test_rng(); + let n = 10; + + let params = &*PARAMS; // Explicit reference to PARAMS + + let (pk, sk) = NCS::::keygen(¶ms, &mut rng).expect("Keygen failed"); + + assert_eq!( + params.g1_generators.len(), + 1, + "Incorrect number of G1 generators" + ); + assert_eq!(params.max_lanes, n, "Max lanes value 'mismatch"); + + // Verify the public key matches the secret key and G2 generator relationship + let calculated_public_key = params.g2_generator.mul(sk); + assert_eq!( + calculated_public_key, pk, + "Public key does not match the calculated value from secret key and G2 generator" + ); + + println!("Setup and Keygen tests passed!"); + } + + #[test] + fn test_precompute() { + let params = &*PARAMS; + + let allocation_id = ark_bn254::Fr::from_be_bytes_mod_order(&Hasher::digest(&b"test")); + let (hash_aggregate, alloc_id) = + NCS::::precompute(¶ms, allocation_id, N).expect("Precompute failed"); + + println!("Precomputed Hash Aggregate: {:?}", hash_aggregate); + println!("allocation_id {:?}", alloc_id); + } + + #[test] + fn test_sign_and_verify() { + let mut rng = test_rng(); + let params = &*PARAMS; + + // Precompute the hash aggregate and allocation ID + let allocation_id = ark_bn254::Fr::from_be_bytes_mod_order(&Hasher::digest(&b"test")); + let (_, allocation_id) = + NCS::::precompute(¶ms, allocation_id, N).expect("Precompute failed"); + + // Generate messages for each lane/index + let messages: Vec = (0..N).map(|_| Fr::rand(&mut rng)).collect(); + + // Iterate through indices and sign each message + for index in 0..N { + // Sign the message with the current index + let signature = + NCS::::sign(¶ms, allocation_id, index, messages[index]) + .expect("Sign failed"); + + // Verify the signature with the same index + let is_valid = NCS::::verify( + ¶ms, + allocation_id, + index, + &messages[index], + &signature, + ) + .expect("Verify failed"); + + assert!( + is_valid, + "Signature verification failed for index {}!", + index + ); + } + + println!("All signatures successfully verified for indices 0..{}!", N); + } + + #[test] + fn test_aggregate() { + let mut rng = test_rng(); + let params = &*PARAMS; + + // Generate random messages for each lane/index + let messages: Vec = (0..N).map(|_| Fr::rand(&mut rng)).collect(); + + // Precompute the hash aggregate and allocation ID + let allocation_id = ark_bn254::Fr::from_be_bytes_mod_order(&Hasher::digest(&b"test")); + let (hash_aggregate, allocation_id) = + NCS::::precompute(¶ms, allocation_id, N).expect("Precompute failed"); + + // Generate individual signatures for each message + let mut signatures: Vec<_> = (0..N) + .map(|index| { + NCS::::sign(¶ms, allocation_id, index, messages[index]) + .expect("Sign failed") + }) + .collect(); + + // Verify each individual signature + for (index, signature) in signatures.iter().enumerate() { + let is_valid = NCS::::verify( + ¶ms, + allocation_id, + index, + &messages[index], + signature, + ) + .expect("Verify failed"); + assert!(is_valid, "Invalid signature for index {}!", index); + } + + // Generate weights (all set to 1 for uniform aggregation) + let weights: Vec = vec![1; N]; + + // Aggregate the signatures + let aggregated_signature = + NCS::::evaluate(&signatures, &weights).expect("Evaluate failed"); + + // Verify the aggregated signature + let is_valid = + NCS::::verify_aggregate(¶ms, &hash_aggregate, &aggregated_signature) + .expect("Verify failed"); + + assert!( + is_valid, + "Aggregated signature verification failed for the entire set of messages!" + ); + + println!( + "Aggregated signature successfully verified for all {} messages!", + N + ); + + // this next signature aggregation test should fail + // Introduce a duplicate signature to simulate a lying indexer + let random_index = rng.gen_range(0..N); + let duplicate_signature = signatures[random_index].clone(); + signatures.push(duplicate_signature); + // adds 1 more weight to match the added signature + let weights: Vec = vec![1; N + 1]; + + // Aggregate the signatures, including the duplicate + let tampered_aggregate_signature = + NCS::::evaluate(&signatures, &weights).expect("Evaluate failed"); + + // Verify the aggregated signature with the tampered signature table + let is_valid = NCS::::verify_aggregate( + ¶ms, + &hash_aggregate, + &tampered_aggregate_signature, + ) + .expect("Verify failed"); + + // Assert that verification fails + assert!( + !is_valid, + "Aggregated signature verification should fail with a tampered signature table!" + ); + + println!("Tampered aggregated signature verification correctly failed as expected!"); + } +}