From 69b24485001b4cea9e345d974dc480dc5fe2f081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 26 Feb 2025 10:02:55 +0000 Subject: [PATCH] chore: removed all old coconut code (#5500) --- Cargo.lock | 28 +- Cargo.toml | 1 - common/nymcoconut/Cargo.toml | 48 -- common/nymcoconut/README.md | 1 - common/nymcoconut/benches/benchmarks.rs | 360 --------- common/nymcoconut/src/elgamal.rs | 354 --------- common/nymcoconut/src/error.rs | 69 -- common/nymcoconut/src/impls/clone.rs | 15 - common/nymcoconut/src/impls/mod.rs | 2 - common/nymcoconut/src/impls/serde.rs | 57 -- common/nymcoconut/src/lib.rs | 56 -- common/nymcoconut/src/proofs/mod.rs | 619 --------------- common/nymcoconut/src/scheme/aggregation.rs | 432 ----------- common/nymcoconut/src/scheme/double_use.rs | 79 -- common/nymcoconut/src/scheme/issuance.rs | 660 ---------------- common/nymcoconut/src/scheme/keygen.rs | 722 ------------------ common/nymcoconut/src/scheme/mod.rs | 672 ---------------- common/nymcoconut/src/scheme/setup.rs | 91 --- common/nymcoconut/src/scheme/verification.rs | 432 ----------- common/nymcoconut/src/tests/e2e.rs | 84 -- common/nymcoconut/src/tests/helpers.rs | 186 ----- common/nymcoconut/src/tests/mod.rs | 3 - common/nymcoconut/src/traits.rs | 88 --- common/nymcoconut/src/utils.rs | 382 --------- nym-api/Cargo.toml | 1 - nym-api/src/ecash/dkg/controller/keys.rs | 14 +- nym-api/src/ecash/keys/mod.rs | 17 - nym-api/src/ecash/keys/persistence.rs | 36 +- .../src/client.rs | 26 - .../src/http/router/api/v1/openapi.rs | 42 - wasm/zknym-lib/Cargo.toml | 1 - wasm/zknym-lib/src/error.rs | 6 - .../src/generic_scheme/coconut/mod.rs | 338 -------- .../src/generic_scheme/coconut/types.rs | 158 ---- wasm/zknym-lib/src/generic_scheme/mod.rs | 1 - wasm/zknym-lib/src/lib.rs | 4 - wasm/zknym-lib/src/vpn_api_client/client.rs | 35 +- wasm/zknym-lib/src/vpn_api_client/types.rs | 16 - 38 files changed, 4 insertions(+), 6132 deletions(-) delete mode 100644 common/nymcoconut/Cargo.toml delete mode 100644 common/nymcoconut/README.md delete mode 100644 common/nymcoconut/benches/benchmarks.rs delete mode 100644 common/nymcoconut/src/elgamal.rs delete mode 100644 common/nymcoconut/src/error.rs delete mode 100644 common/nymcoconut/src/impls/clone.rs delete mode 100644 common/nymcoconut/src/impls/mod.rs delete mode 100644 common/nymcoconut/src/impls/serde.rs delete mode 100644 common/nymcoconut/src/lib.rs delete mode 100644 common/nymcoconut/src/proofs/mod.rs delete mode 100644 common/nymcoconut/src/scheme/aggregation.rs delete mode 100644 common/nymcoconut/src/scheme/double_use.rs delete mode 100644 common/nymcoconut/src/scheme/issuance.rs delete mode 100644 common/nymcoconut/src/scheme/keygen.rs delete mode 100644 common/nymcoconut/src/scheme/mod.rs delete mode 100644 common/nymcoconut/src/scheme/setup.rs delete mode 100644 common/nymcoconut/src/scheme/verification.rs delete mode 100644 common/nymcoconut/src/tests/e2e.rs delete mode 100644 common/nymcoconut/src/tests/helpers.rs delete mode 100644 common/nymcoconut/src/tests/mod.rs delete mode 100644 common/nymcoconut/src/traits.rs delete mode 100644 common/nymcoconut/src/utils.rs delete mode 100644 wasm/zknym-lib/src/generic_scheme/coconut/mod.rs delete mode 100644 wasm/zknym-lib/src/generic_scheme/coconut/types.rs diff --git a/Cargo.lock b/Cargo.lock index 90e0495ee2..d9ee2ed197 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4775,7 +4775,6 @@ dependencies = [ "nym-api-requests", "nym-bandwidth-controller", "nym-bin-common", - "nym-coconut", "nym-coconut-dkg-common", "nym-compact-ecash", "nym-config", @@ -5258,30 +5257,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "nym-coconut" -version = "0.5.0" -dependencies = [ - "bls12_381", - "bs58", - "criterion", - "digest 0.9.0", - "doc-comment", - "ff", - "getrandom 0.2.15", - "group", - "itertools 0.13.0", - "nym-dkg", - "nym-pemstore", - "rand 0.8.5", - "rand_chacha 0.3.1", - "serde", - "serde_derive", - "sha2 0.9.9", - "thiserror 2.0.11", - "zeroize", -] - [[package]] name = "nym-coconut-bandwidth-contract-common" version = "0.1.0" @@ -5888,7 +5863,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "subtle 2.6.1", + "subtle 2.5.0", "tower 0.5.2", "tracing", "utoipa", @@ -12202,7 +12177,6 @@ dependencies = [ "getrandom 0.2.15", "js-sys", "nym-bin-common", - "nym-coconut", "nym-compact-ecash", "nym-credentials", "nym-crypto", diff --git a/Cargo.toml b/Cargo.toml index a750d7dbc7..c1fee9aca5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,6 @@ members = [ "common/nym-id", "common/nym-metrics", "common/nym_offline_compact_ecash", - "common/nymcoconut", "common/nymsphinx", "common/nymsphinx/acknowledgements", "common/nymsphinx/addressing", diff --git a/common/nymcoconut/Cargo.toml b/common/nymcoconut/Cargo.toml deleted file mode 100644 index c4266b3459..0000000000 --- a/common/nymcoconut/Cargo.toml +++ /dev/null @@ -1,48 +0,0 @@ -[package] -name = "nym-coconut" -version = "0.5.0" -authors = ["Jedrzej Stuczynski ", "Ania Piotrowska "] -edition = "2021" -license.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -bls12_381 = { workspace = true, default-features = false, features = ["pairings", "alloc", "experimental"] } -itertools = { workspace = true } -digest = "0.9" -rand = { workspace = true } -thiserror = { workspace = true } -serde = { workspace = true } -serde_derive = { workspace = true } -bs58 = { workspace = true } -sha2 = "0.9" -zeroize = { workspace = true, optional = true } - -nym-dkg = { path = "../dkg" } -nym-pemstore = { path = "../pemstore" } - -[dependencies.ff] -workspace = true -default-features = false - -[dependencies.group] -workspace = true -default-features = false - -[dev-dependencies] -criterion = { workspace = true, features = ["html_reports"] } -doc-comment = { workspace = true } -rand_chacha = { workspace = true } - -[[bench]] -name = "benchmarks" -harness = false - -[features] -key-zeroize = ["zeroize", "bls12_381/zeroize"] -default = [] - - -[target.'cfg(target_env = "wasm32-unknown-unknown")'.dependencies] -getrandom = { version="0.2", features=["js"] } diff --git a/common/nymcoconut/README.md b/common/nymcoconut/README.md deleted file mode 100644 index 2433e80e34..0000000000 --- a/common/nymcoconut/README.md +++ /dev/null @@ -1 +0,0 @@ -This project was partially funded through the NGI0 PET Fund, a fund established by NL.net with financial support from the European Commission's NGI programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 825310. \ No newline at end of file diff --git a/common/nymcoconut/benches/benchmarks.rs b/common/nymcoconut/benches/benchmarks.rs deleted file mode 100644 index 42fb36dc82..0000000000 --- a/common/nymcoconut/benches/benchmarks.rs +++ /dev/null @@ -1,360 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use bls12_381::{multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, Scalar}; -use criterion::{criterion_group, criterion_main, Criterion}; -use ff::Field; -use group::{Curve, Group}; -use nym_coconut::{ - aggregate_signature_shares_and_verify, aggregate_verification_keys, blind_sign, - prepare_blind_sign, prove_bandwidth_credential, random_scalars_refs, setup, ttp_keygen, - verify_credential, verify_partial_blind_signature, Attribute, BlindedSignature, Parameters, - Signature, SignatureShare, VerificationKey, -}; -use rand::seq::SliceRandom; -use std::ops::Neg; -use std::time::Duration; - -#[allow(unused)] -fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) { - let gt1 = bls12_381::pairing(g11, g21); - let gt2 = bls12_381::pairing(g12, g22); - assert_eq!(gt1, gt2) -} - -#[allow(unused)] -fn multi_miller_pairing_affine(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) { - let miller_loop_result = multi_miller_loop(&[ - (g11, &G2Prepared::from(*g21)), - (&g12.neg(), &G2Prepared::from(*g22)), - ]); - assert!(bool::from( - miller_loop_result.final_exponentiation().is_identity() - )) -} - -#[allow(unused)] -fn bench_pairings(c: &mut Criterion) { - let mut rng = rand::thread_rng(); - - let g1 = G1Affine::generator(); - let g2 = G2Affine::generator(); - let r = Scalar::random(&mut rng); - let s = Scalar::random(&mut rng); - - let g11 = (g1 * r).to_affine(); - let g21 = (g2 * s).to_affine(); - let g21_prep = G2Prepared::from(g21); - - let g12 = (g1 * s).to_affine(); - let g22 = (g2 * r).to_affine(); - let g22_prep = G2Prepared::from(g22); - - c.bench_function("double pairing", |b| { - b.iter(|| double_pairing(&g11, &g21, &g12, &g22)) - }); - - c.bench_function("multi miller in affine", |b| { - b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22)) - }); - - c.bench_function("multi miller with prepared g2", |b| { - b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep)) - }); - - c.bench_function("multi miller with semi-prepared g2", |b| { - b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep)) - }); -} - -#[allow(unused)] -fn multi_miller_pairing_with_prepared( - g11: &G1Affine, - g21: &G2Prepared, - g12: &G1Affine, - g22: &G2Prepared, -) { - let miller_loop_result = multi_miller_loop(&[(g11, g21), (&g12.neg(), g22)]); - assert!(bool::from( - miller_loop_result.final_exponentiation().is_identity() - )) -} - -// the case of being able to prepare G2 generator -#[allow(unused)] -fn multi_miller_pairing_with_semi_prepared( - g11: &G1Affine, - g21: &G2Affine, - g12: &G1Affine, - g22: &G2Prepared, -) { - let miller_loop_result = - multi_miller_loop(&[(g11, &G2Prepared::from(*g21)), (&g12.neg(), g22)]); - assert!(bool::from( - miller_loop_result.final_exponentiation().is_identity() - )) -} - -#[allow(clippy::too_many_arguments)] -fn unblind_and_aggregate( - params: &Parameters, - blinded_signatures: &[BlindedSignature], - partial_verification_keys: &[VerificationKey], - private_attributes: &[&Attribute], - public_attributes: &[&Attribute], - commitment_hash: &G1Projective, - pedersen_commitments_openings: &[Scalar], - verification_key: &VerificationKey, -) -> Signature { - // Unblind all partial signatures - let unblinded_signatures: Vec = blinded_signatures - .iter() - .zip(partial_verification_keys.iter()) - .map(|(signature, partial_verification_key)| { - signature - .unblind_and_verify( - params, - partial_verification_key, - private_attributes, - public_attributes, - commitment_hash, - pedersen_commitments_openings, - ) - .unwrap() - }) - .collect(); - - let unblinded_signature_shares: Vec = unblinded_signatures - .iter() - .enumerate() - .map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64)) - .collect(); - - let mut attributes = vec![]; - attributes.extend_from_slice(private_attributes); - attributes.extend_from_slice(public_attributes); - aggregate_signature_shares_and_verify( - params, - verification_key, - &attributes, - &unblinded_signature_shares, - ) - .unwrap() -} - -struct BenchCase { - num_authorities: u64, - threshold_p: f32, - num_public_attrs: u32, - num_private_attrs: u32, -} - -impl BenchCase { - fn threshold(&self) -> u64 { - (self.num_authorities as f32 * self.threshold_p).round() as u64 - } - - fn num_attrs(&self) -> u32 { - self.num_public_attrs + self.num_private_attrs - } -} - -fn bench_coconut(c: &mut Criterion) { - let mut group = c.benchmark_group("benchmark-coconut"); - group.measurement_time(Duration::from_secs(1000)); - let case = BenchCase { - num_authorities: 100, - threshold_p: 0.7, - num_public_attrs: 2, - num_private_attrs: 2, - }; - - let params = setup(case.num_public_attrs + case.num_private_attrs).unwrap(); - - random_scalars_refs!(public_attributes, params, case.num_public_attrs as usize); - let serial_number = params.random_scalar(); - let binding_number = params.random_scalar(); - let private_attributes = vec![&serial_number, &binding_number]; - - // The prepare blind sign is performed by the user - let (pedersen_commitments_openings, blind_sign_request) = - prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - - // CLIENT BENCHMARK: Data needed to ask for a credential - // Let's benchmark the operations the client has to perform - // to ask for a credential - group.bench_function( - format!( - "[Client] prepare_blind_sign_{}_authorities_{}_attributes_{}_threshold", - case.num_authorities, - case.num_attrs(), - case.threshold_p, - ), - |b| { - b.iter(|| prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap()) - }, - ); - - // keys for the validators - let coconut_keypairs = ttp_keygen(¶ms, case.threshold(), case.num_authorities).unwrap(); - - // VALIDATOR BENCHMARK: Issue partial credential - // we pick only one key pair, as we want to validate how much does it - // take for a single validator to issue a partial credential - let mut rng = rand::thread_rng(); - let keypair = coconut_keypairs.choose(&mut rng).unwrap(); - - group.bench_function( - format!( - "[Validator] compute_single_blind_sign_for_credential_with_{}_attributes", - case.num_attrs(), - ), - |b| { - b.iter(|| { - blind_sign( - ¶ms, - keypair.secret_key(), - &blind_sign_request, - &public_attributes, - ) - .unwrap() - }) - }, - ); - - // computing all partial credentials - // NOTE: in reality, each validator computes only single signature - let mut blinded_signatures = Vec::new(); - for keypair in coconut_keypairs.iter() { - let blinded_signature = blind_sign( - ¶ms, - keypair.secret_key(), - &blind_sign_request, - &public_attributes, - ) - .unwrap(); - blinded_signatures.push(blinded_signature) - } - - let verification_keys: Vec = coconut_keypairs - .iter() - .map(|keypair| keypair.verification_key().clone()) - .collect(); - - // verify a random partial blind signature - let rand_idx = 1; - let random_blind_signature = blinded_signatures.get(rand_idx).unwrap(); - let partial_verification_key = verification_keys.get(rand_idx).unwrap(); - - group.bench_function( - format!( - "verify_partial_blind_signature_{}_private_attributes_{}_public_attributes", - case.num_private_attrs, case.num_public_attrs - ), - |b| { - b.iter(|| { - verify_partial_blind_signature( - ¶ms, - blind_sign_request.get_private_attributes_pedersen_commitments(), - &public_attributes, - random_blind_signature, - partial_verification_key, - ) - }) - }, - ); - - // Lets bench worse case, ie aggregating all - let indices: Vec = (1..=case.num_authorities).collect(); - // aggregate verification keys - let aggr_verification_key = - aggregate_verification_keys(&verification_keys, Some(&indices)).unwrap(); - - // CLIENT OPERATION: Unblind partial singatures and aggregate into single signature - let aggregated_signature = unblind_and_aggregate( - ¶ms, - &blinded_signatures, - &verification_keys, - &private_attributes, - &public_attributes, - &blind_sign_request.get_commitment_hash(), - &pedersen_commitments_openings, - &aggr_verification_key, - ); - - // CLIENT BENCHMARK: aggregate all partial credentials - group.bench_function( - format!( - "[Client] unblind_and_aggregate_partial_credentials_{}_authorities_{}_attributes_{}_threshold", - case.num_authorities, - case.num_attrs(), - case.threshold_p, - ), - |b| { - b.iter(|| { - unblind_and_aggregate( - ¶ms, - &blinded_signatures, - &verification_keys, - &private_attributes, - &public_attributes, - &blind_sign_request.get_commitment_hash(), - &pedersen_commitments_openings, - &aggr_verification_key) - }) - }, - ); - - // CLIENT OPERATION: Randomize credentials and generate any cryptographic material to verify them - let theta = prove_bandwidth_credential( - ¶ms, - &aggr_verification_key, - &aggregated_signature, - &serial_number, - &binding_number, - ) - .unwrap(); - - // CLIENT BENCHMARK - group.bench_function( - format!( - "[Client] randomize_and_prove_credential_{}_authorities_{}_attributes_{}_threshold", - case.num_authorities, - case.num_attrs(), - case.threshold_p, - ), - |b| { - b.iter(|| { - prove_bandwidth_credential( - ¶ms, - &aggr_verification_key, - &aggregated_signature, - &serial_number, - &binding_number, - ) - .unwrap() - }) - }, - ); - - // VERIFIER OPERATION - // Verify credentials - verify_credential(¶ms, &aggr_verification_key, &theta, &public_attributes); - - // VERIFICATION BENCHMARK - group.bench_function( - format!( - "[Verifier] verify_credentials_{}_authorities_{}_attributes_{}_threshold", - case.num_authorities, - case.num_attrs(), - case.threshold_p, - ), - |b| { - b.iter(|| { - verify_credential(¶ms, &aggr_verification_key, &theta, &public_attributes) - }) - }, - ); -} -criterion_group!(benches, bench_coconut); -criterion_main!(benches); diff --git a/common/nymcoconut/src/elgamal.rs b/common/nymcoconut/src/elgamal.rs deleted file mode 100644 index 1b814f0bc5..0000000000 --- a/common/nymcoconut/src/elgamal.rs +++ /dev/null @@ -1,354 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use core::ops::{Deref, Mul}; - -use bls12_381::{G1Projective, Scalar}; -use group::Curve; -use serde_derive::{Deserialize, Serialize}; - -use crate::error::{CoconutError, Result}; -use crate::scheme::setup::Parameters; -use crate::traits::{Base58, Bytable}; -use crate::utils::{try_deserialize_g1_projective, try_deserialize_scalar}; -use crate::Attribute; - -/// Type alias for the ephemeral key generated during ElGamal encryption -pub type EphemeralKey = Scalar; - -/// Two G1 points representing ElGamal ciphertext -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq, Eq))] -pub struct Ciphertext(pub(crate) G1Projective, pub(crate) G1Projective); - -impl TryFrom<&[u8]> for Ciphertext { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - if bytes.len() != 96 { - return Err(CoconutError::Deserialization(format!( - "Ciphertext must be exactly 96 bytes, got {}", - bytes.len() - ))); - } - - // safety: we just checked for the length so the unwraps are fine - #[allow(clippy::unwrap_used)] - let c1_bytes: &[u8; 48] = &bytes[..48].try_into().unwrap(); - #[allow(clippy::unwrap_used)] - let c2_bytes: &[u8; 48] = &bytes[48..].try_into().unwrap(); - - let c1 = try_deserialize_g1_projective( - c1_bytes, - CoconutError::Deserialization("Failed to deserialize compressed c1".to_string()), - )?; - let c2 = try_deserialize_g1_projective( - c2_bytes, - CoconutError::Deserialization("Failed to deserialize compressed c2".to_string()), - )?; - - Ok(Ciphertext(c1, c2)) - } -} - -impl Ciphertext { - pub fn c1(&self) -> &G1Projective { - &self.0 - } - - pub fn c2(&self) -> &G1Projective { - &self.1 - } - - pub fn to_bytes(&self) -> [u8; 96] { - let mut bytes = [0u8; 96]; - bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed()); - bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed()); - bytes - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - Ciphertext::try_from(bytes) - } -} - -/// PrivateKey used in the ElGamal encryption scheme to recover the plaintext -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq, Eq))] -pub struct PrivateKey(pub(crate) Scalar); - -impl PrivateKey { - /// Decrypt takes the ElGamal encryption of a message and returns a point on the G1 curve - /// that represents original h^m. - pub fn decrypt(&self, ciphertext: &Ciphertext) -> G1Projective { - let (c1, c2) = &(ciphertext.0, ciphertext.1); - - // (gamma^k * h^m) / (g1^{d * k}) | note: gamma = g1^d - c2 - c1 * self.0 - } - - pub fn public_key(&self, params: &Parameters) -> PublicKey { - PublicKey(params.gen1() * self.0) - } - - pub fn to_bytes(&self) -> [u8; 32] { - self.0.to_bytes() - } - - pub fn from_bytes(bytes: &[u8; 32]) -> Result { - try_deserialize_scalar( - bytes, - CoconutError::Deserialization( - "Failed to deserialize ElGamal private key - it was not in the canonical form" - .to_string(), - ), - ) - .map(PrivateKey) - } -} - -impl Bytable for PrivateKey { - fn to_byte_vec(&self) -> Vec { - self.to_bytes().to_vec() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - let received = slice.len(); - let Ok(arr) = slice.try_into() else { - return Err(CoconutError::UnexpectedArrayLength { - typ: "elgamal::PrivateKey".to_string(), - received, - expected: 32, - }); - }; - - PrivateKey::from_bytes(arr) - } -} - -impl Base58 for PrivateKey {} - -// TODO: perhaps be more explicit and apart from gamma also store generator and group order? -/// PublicKey used in the ElGamal encryption scheme to produce the ciphertext -#[derive(Debug, Clone)] -#[cfg_attr(test, derive(PartialEq, Eq))] -pub struct PublicKey(G1Projective); - -impl PublicKey { - /// Encrypt encrypts the given message in the form of h^m, - /// where h is a point on the G1 curve using the given public key. - /// The random k is returned alongside the encryption - /// as it is required by the Coconut Scheme to create proofs of knowledge. - pub fn encrypt( - &self, - params: &Parameters, - h: &G1Projective, - msg: &Scalar, - ) -> (Ciphertext, EphemeralKey) { - let k = params.random_scalar(); - // c1 = g1^k - let c1 = params.gen1() * k; - // c2 = gamma^k * h^m - let c2 = self.0 * k + h * msg; - - (Ciphertext(c1, c2), k) - } - - pub fn to_bytes(&self) -> [u8; 48] { - self.0.to_affine().to_compressed() - } - - pub fn from_bytes(bytes: &[u8; 48]) -> Result { - try_deserialize_g1_projective( - bytes, - CoconutError::Deserialization( - "Failed to deserialize compressed ElGamal public key".to_string(), - ), - ) - .map(PublicKey) - } -} - -impl Bytable for PublicKey { - fn to_byte_vec(&self) -> Vec { - self.to_bytes().into() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - let received = slice.len(); - let Ok(arr) = slice.try_into() else { - return Err(CoconutError::UnexpectedArrayLength { - typ: "elgamal::PublicKey".to_string(), - received, - expected: 48, - }); - }; - - PublicKey::from_bytes(arr) - } -} - -impl TryFrom<&[u8]> for PublicKey { - type Error = CoconutError; - - fn try_from(slice: &[u8]) -> Result { - PublicKey::try_from_byte_slice(slice) - } -} - -impl Base58 for PublicKey {} - -impl Deref for PublicKey { - type Target = G1Projective; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'a> Mul<&'a Scalar> for &PublicKey { - type Output = G1Projective; - - fn mul(self, rhs: &'a Scalar) -> Self::Output { - self.0 * rhs - } -} - -#[derive(Serialize, Deserialize)] -/// A convenient wrapper for both keys of the ElGamal keypair -pub struct ElGamalKeyPair { - private_key: PrivateKey, - public_key: PublicKey, -} - -impl ElGamalKeyPair { - pub fn public_key(&self) -> &PublicKey { - &self.public_key - } - - pub fn private_key(&self) -> &PrivateKey { - &self.private_key - } -} - -/// Generate a fresh ElGamal keypair using the group generator specified by the provided [Parameters] -pub fn elgamal_keygen(params: &Parameters) -> ElGamalKeyPair { - let private_key = params.random_scalar(); - let gamma = params.gen1() * private_key; - - ElGamalKeyPair { - private_key: PrivateKey(private_key), - public_key: PublicKey(gamma), - } -} - -pub fn compute_attribute_encryption( - params: &Parameters, - private_attributes: &[&Attribute], - pub_key: &PublicKey, - commitment_hash: G1Projective, -) -> (Vec, Vec) { - private_attributes - .iter() - .map(|m| pub_key.encrypt(params, &commitment_hash, m)) - .unzip() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn keygen() { - let params = Parameters::default(); - let keypair = super::elgamal_keygen(¶ms); - - let expected = params.gen1() * keypair.private_key.0; - let gamma = keypair.public_key.0; - assert_eq!( - expected, gamma, - "Public key, gamma, should be equal to g1^d, where d is the private key" - ); - } - - #[test] - fn encryption() { - let params = Parameters::default(); - let keypair = super::elgamal_keygen(¶ms); - - let r = params.random_scalar(); - let h = params.gen1() * r; - let m = params.random_scalar(); - - let (ciphertext, ephemeral_key) = keypair.public_key.encrypt(¶ms, &h, &m); - - let expected_c1 = params.gen1() * ephemeral_key; - assert_eq!(expected_c1, ciphertext.0, "c1 should be equal to g1^k"); - - let expected_c2 = keypair.public_key.0 * ephemeral_key + h * m; - assert_eq!( - expected_c2, ciphertext.1, - "c2 should be equal to gamma^k * h^m" - ); - } - - #[test] - fn decryption() { - let params = Parameters::default(); - let keypair = super::elgamal_keygen(¶ms); - - let r = params.random_scalar(); - let h = params.gen1() * r; - let m = params.random_scalar(); - - let (ciphertext, _) = keypair.public_key.encrypt(¶ms, &h, &m); - let dec = keypair.private_key.decrypt(&ciphertext); - - let expected = h * m; - assert_eq!( - expected, dec, - "after ElGamal decryption, original h^m should be obtained" - ); - } - - #[test] - fn private_key_bytes_roundtrip() { - let params = Parameters::default(); - let private_key = PrivateKey(params.random_scalar()); - let bytes = private_key.to_bytes(); - - // also make sure it is equivalent to the internal scalar's bytes - assert_eq!(private_key.0.to_bytes(), bytes); - assert_eq!(private_key, PrivateKey::from_bytes(&bytes).unwrap()) - } - - #[test] - fn public_key_bytes_roundtrip() { - let params = Parameters::default(); - let r = params.random_scalar(); - let public_key = PublicKey(params.gen1() * r); - let bytes = public_key.to_bytes(); - - // also make sure it is equivalent to the internal g1 compressed bytes - assert_eq!(public_key.0.to_affine().to_compressed(), bytes); - assert_eq!(public_key, PublicKey::from_bytes(&bytes).unwrap()) - } - - #[test] - fn ciphertext_bytes_roundtrip() { - let params = Parameters::default(); - let r = params.random_scalar(); - let s = params.random_scalar(); - let ciphertext = Ciphertext(params.gen1() * r, params.gen1() * s); - let bytes = ciphertext.to_bytes(); - - // also make sure it is equivalent to the internal g1 compressed bytes concatenated - let expected_bytes = [ - ciphertext.0.to_affine().to_compressed(), - ciphertext.1.to_affine().to_compressed(), - ] - .concat(); - assert_eq!(expected_bytes, bytes); - assert_eq!(ciphertext, Ciphertext::try_from(&bytes[..]).unwrap()) - } -} diff --git a/common/nymcoconut/src/error.rs b/common/nymcoconut/src/error.rs deleted file mode 100644 index d502c76c95..0000000000 --- a/common/nymcoconut/src/error.rs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use thiserror::Error; - -/// A `Result` alias where the `Err` case is `coconut_rs::Error`. -pub type Result = std::result::Result; - -#[derive(Error, Debug)] -pub enum CoconutError { - #[error("Setup error: {0}")] - Setup(String), - - #[error("encountered error during keygen")] - Keygen, - - #[error("Issuance related error: {0}")] - Issuance(String), - - #[error("Tried to prepare blind sign request for higher than specified number of attributes (max: {}, requested: {})", max, requested)] - IssuanceMaxAttributes { max: usize, requested: usize }, - - #[error("Interpolation error: {0}")] - Interpolation(String), - - #[error("Aggregation error: {0}")] - Aggregation(String), - - #[error("Unblind error: {0}")] - Unblind(String), - - #[error("Verification error: {0}")] - Verification(String), - - #[error("Deserialization error: {0}")] - Deserialization(String), - - #[error( - "Deserailization error, expected at least {} bytes, got {}", - min, - actual - )] - DeserializationMinLength { min: usize, actual: usize }, - - #[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {object} or {modulus_target} % {modulus} == 0")] - DeserializationInvalidLength { - actual: usize, - target: usize, - modulus_target: usize, - modulus: usize, - object: String, - }, - - #[error("received an array of unexpected size for deserialization of {typ}. got {received} but expected {expected}")] - UnexpectedArrayLength { - typ: String, - received: usize, - expected: usize, - }, - - #[error("failed to decode the base58 representation: {0}")] - Base58DecodingFailure(#[from] bs58::decode::Error), - - #[error("failed to deserialize scalar from the received bytes - it might not have been canonically encoded")] - ScalarDeserializationFailure, - - #[error("failed to deserialize G1Projective point from the received bytes - it might not have been canonically encoded")] - G1ProjectiveDeserializationFailure, -} diff --git a/common/nymcoconut/src/impls/clone.rs b/common/nymcoconut/src/impls/clone.rs deleted file mode 100644 index 88ee6664c0..0000000000 --- a/common/nymcoconut/src/impls/clone.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::{BlindSignRequest, BlindedSignature, Bytable, VerifyCredentialRequest}; - -macro_rules! impl_clone { - ($struct:ident) => { - impl Clone for $struct { - fn clone(&self) -> Self { - Self::try_from_byte_slice(&self.to_byte_vec()).unwrap() - } - } - }; -} - -impl_clone!(BlindSignRequest); -impl_clone!(BlindedSignature); -impl_clone!(VerifyCredentialRequest); diff --git a/common/nymcoconut/src/impls/mod.rs b/common/nymcoconut/src/impls/mod.rs deleted file mode 100644 index efacad272d..0000000000 --- a/common/nymcoconut/src/impls/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod clone; -mod serde; diff --git a/common/nymcoconut/src/impls/serde.rs b/common/nymcoconut/src/impls/serde.rs deleted file mode 100644 index 7d1e020cb1..0000000000 --- a/common/nymcoconut/src/impls/serde.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::elgamal::PrivateKey; -use crate::scheme::SecretKey; -use crate::{ - Base58, BlindSignRequest, BlindedSignature, PublicKey, Signature, VerificationKey, - VerifyCredentialRequest, -}; -use serde::de::Unexpected; -use serde::{de::Error, de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; -use std::fmt; - -macro_rules! impl_serde { - ($struct:ident, $visitor:ident) => { - pub struct $visitor {} - - impl Serialize for $struct { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.to_bs58()) - } - } - - impl<'de> Visitor<'de> for $visitor { - type Value = $struct; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(formatter, "A base58 encoded struct") - } - - fn visit_str(self, s: &str) -> Result { - match $struct::try_from_bs58(s) { - Ok(x) => Ok(x), - Err(_) => Err(Error::invalid_value(Unexpected::Str(s), &self)), - } - } - } - - impl<'de> Deserialize<'de> for $struct { - fn deserialize(deserializer: D) -> Result<$struct, D::Error> - where - D: Deserializer<'de>, - { - deserializer.deserialize_str($visitor {}) - } - } - }; -} - -impl_serde!(SecretKey, V1); -impl_serde!(VerificationKey, V2); -impl_serde!(PublicKey, V3); -impl_serde!(PrivateKey, V4); -impl_serde!(BlindSignRequest, V5); -impl_serde!(BlindedSignature, V6); -impl_serde!(Signature, V7); -impl_serde!(VerifyCredentialRequest, V8); diff --git a/common/nymcoconut/src/lib.rs b/common/nymcoconut/src/lib.rs deleted file mode 100644 index dbcbda5b39..0000000000 --- a/common/nymcoconut/src/lib.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -#![warn(clippy::expect_used)] -#![warn(clippy::unwrap_used)] - -pub use bls12_381::Scalar; -pub use elgamal::elgamal_keygen; -pub use elgamal::ElGamalKeyPair; -pub use elgamal::PublicKey; -pub use error::CoconutError; -pub use scheme::aggregation::aggregate_key_shares; -pub use scheme::aggregation::aggregate_signature_shares; -pub use scheme::aggregation::aggregate_signature_shares_and_verify; -pub use scheme::aggregation::aggregate_verification_keys; -pub use scheme::issuance::blind_sign; -pub use scheme::issuance::prepare_blind_sign; -pub use scheme::issuance::sign; -pub use scheme::issuance::verify_partial_blind_signature; -pub use scheme::issuance::BlindSignRequest; -pub use scheme::keygen::keygen; -pub use scheme::keygen::ttp_keygen; -pub use scheme::keygen::KeyPair; -pub use scheme::keygen::SecretKey; -pub use scheme::keygen::VerificationKey; -pub use scheme::keygen::VerificationKeyShare; -pub use scheme::setup::setup; -pub use scheme::setup::Parameters; -pub use scheme::verification::check_vk_pairing; -pub use scheme::verification::prove_bandwidth_credential; -pub use scheme::verification::verify; -pub use scheme::verification::verify_credential; -pub use scheme::verification::BlindedSerialNumber; -pub use scheme::verification::VerifyCredentialRequest; -pub use scheme::BlindedSignature; -pub use scheme::Signature; -pub use scheme::SignatureShare; -pub use scheme::SignerIndex; -pub use traits::Base58; -pub use traits::Bytable; -pub use utils::hash_to_scalar; - -pub mod elgamal; -mod error; -mod impls; -mod proofs; -mod scheme; -pub mod tests; -mod traits; -pub mod utils; - -pub type Attribute = bls12_381::Scalar; -pub type PrivateAttribute = Attribute; -pub type PublicAttribute = Attribute; - -pub use bls12_381::G1Projective; diff --git a/common/nymcoconut/src/proofs/mod.rs b/common/nymcoconut/src/proofs/mod.rs deleted file mode 100644 index 17a8416c59..0000000000 --- a/common/nymcoconut/src/proofs/mod.rs +++ /dev/null @@ -1,619 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -// TODO: look at https://crates.io/crates/merlin to perhaps use it instead? - -use std::borrow::Borrow; - -use bls12_381::{G1Projective, G2Projective, Scalar}; -use digest::generic_array::typenum::Unsigned; -use digest::Digest; -use group::GroupEncoding; -use itertools::izip; -use sha2::Sha256; - -use crate::error::{CoconutError, Result}; -use crate::scheme::issuance::compute_hash; -use crate::scheme::setup::Parameters; -use crate::scheme::VerificationKey; -use crate::utils::{try_deserialize_scalar, try_deserialize_scalar_vec}; -use crate::Attribute; - -// as per the reference python implementation -type ChallengeDigest = Sha256; - -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq, Eq))] -pub struct ProofCmCs { - challenge: Scalar, - response_opening: Scalar, - response_openings: Vec, - response_attributes: Vec, -} - -// note: this is slightly different from the reference python implementation -// as we omit the unnecessary string conversion. Instead we concatenate byte -// representations together and hash that. -// note2: G1 and G2 elements are using their compressed representations -// and as per the bls12-381 library all elements are using big-endian form -/// Generates a Scalar [or Fp] challenge by hashing a number of elliptic curve points. -fn compute_challenge(iter: I) -> Scalar -where - D: Digest, - I: Iterator, - B: AsRef<[u8]>, -{ - let mut h = D::new(); - for point_representation in iter { - h.update(point_representation); - } - let digest = h.finalize(); - - // TODO: I don't like the 0 padding here (though it's what we've been using before, - // but we never had a security audit anyway...) - // instead we could maybe use the `from_bytes` variant and adding some suffix - // when computing the digest until we produce a valid scalar. - let mut bytes = [0u8; 64]; - let pad_size = 64usize - .checked_sub(D::OutputSize::to_usize()) - .unwrap_or_default(); - - bytes[pad_size..].copy_from_slice(&digest); - - Scalar::from_bytes_wide(&bytes) -} - -fn produce_response(witness: &Scalar, challenge: &Scalar, secret: &Scalar) -> Scalar { - witness - challenge * secret -} - -// note: it's caller's responsibility to ensure witnesses.len() = secrets.len() -fn produce_responses(witnesses: &[Scalar], challenge: &Scalar, secrets: &[S]) -> Vec -where - S: Borrow, -{ - debug_assert_eq!(witnesses.len(), secrets.len()); - - witnesses - .iter() - .zip(secrets.iter()) - .map(|(w, x)| produce_response(w, challenge, x.borrow())) - .collect() -} - -impl ProofCmCs { - /// Construct non-interactive zero-knowledge proof of correctness of the ciphertexts and the commitment - /// using the Fiat-Shamir heuristic. - pub(crate) fn construct( - params: &Parameters, - commitment: &G1Projective, - commitment_opening: &Scalar, - commitments: &[G1Projective], - pedersen_commitments_openings: &[Scalar], - private_attributes: &[&Attribute], - public_attributes: &[&Attribute], - ) -> Self { - // note: this is only called from `prepare_blind_sign` that already checks - // whether private attributes are non-empty and whether we don't have too many - // attributes in total to sign. - // we also know, due to the single call place, that ephemeral_keys.len() == private_attributes.len() - - // witness creation - let witness_commitment_opening = params.random_scalar(); - let witness_pedersen_commitments_openings = - params.n_random_scalars(pedersen_commitments_openings.len()); - let witness_attributes = params.n_random_scalars(private_attributes.len()); - - // recompute h - let h = compute_hash(*commitment, public_attributes); - let hs_bytes = params - .gen_hs() - .iter() - .map(|h| h.to_bytes()) - .collect::>(); - - let g1 = params.gen1(); - - // compute commitments - - // zkp commitment for the attributes commitment cm - // Ccm = (wr * g1) + (wm[0] * hs[0]) + ... + (wm[i] * hs[i]) - let commitment_attributes = g1 * witness_commitment_opening - + witness_attributes - .iter() - .zip(params.gen_hs().iter()) - .map(|(wm_i, hs_i)| hs_i * wm_i) - .sum::(); - - // zkp commitments for the individual attributes - let commitments_attributes = witness_pedersen_commitments_openings - .iter() - .zip(witness_attributes.iter()) - .map(|(o_j, m_j)| g1 * o_j + h * m_j) - .collect::>(); - - let commitments_bytes = commitments - .iter() - .map(|cm| cm.to_bytes()) - .collect::>(); - - let commitments_attributes_bytes = commitments_attributes - .iter() - .map(|cm| cm.to_bytes()) - .collect::>(); - - // compute challenge - let challenge = compute_challenge::( - std::iter::once(params.gen1().to_bytes().as_ref()) - .chain(hs_bytes.iter().map(|hs| hs.as_ref())) - .chain(std::iter::once(h.to_bytes().as_ref())) - .chain(std::iter::once(commitment.to_bytes().as_ref())) - .chain(commitments_bytes.iter().map(|cm| cm.as_ref())) - .chain(std::iter::once(commitment_attributes.to_bytes().as_ref())) - .chain(commitments_attributes_bytes.iter().map(|cm| cm.as_ref())), - ); - - // Responses - let response_opening = - produce_response(&witness_commitment_opening, &challenge, commitment_opening); - let response_openings = produce_responses( - &witness_pedersen_commitments_openings, - &challenge, - &pedersen_commitments_openings.iter().collect::>(), - ); - let response_attributes = - produce_responses(&witness_attributes, &challenge, private_attributes); - - ProofCmCs { - challenge, - response_opening, - response_openings, - response_attributes, - } - } - - pub(crate) fn verify( - &self, - params: &Parameters, - commitment: &G1Projective, - commitments: &[G1Projective], - public_attributes: &[&Attribute], - ) -> bool { - if self.response_attributes.len() != commitments.len() { - return false; - } - - // recompute h - let h = compute_hash(*commitment, public_attributes); - let g1 = params.gen1(); - - let hs_bytes = params - .gen_hs() - .iter() - .map(|h| h.to_bytes()) - .collect::>(); - - // recompute witnesses commitments - // Cw = (cm * c) + (rr * g1) + (rm[0] * hs[0]) + ... + (rm[n] * hs[n]) - let commitment_attributes = (commitment - - public_attributes - .iter() - .zip(params.gen_hs().iter().skip(self.response_attributes.len())) - .map(|(&pub_attr, hs)| hs * pub_attr) - .sum::()) - * self.challenge - + g1 * self.response_opening - + self - .response_attributes - .iter() - .zip(params.gen_hs().iter()) - .map(|(res_attr, hs)| hs * res_attr) - .sum::(); - - let commitments_attributes = izip!( - commitments.iter(), - self.response_openings.iter(), - self.response_attributes.iter() - ) - .map(|(cm_j, r_o_j, r_m_j)| cm_j * self.challenge + g1 * r_o_j + h * r_m_j) - .collect::>(); - - let commitments_bytes = commitments - .iter() - .map(|cm| cm.to_bytes()) - .collect::>(); - - let commitments_attributes_bytes = commitments_attributes - .iter() - .map(|cm| cm.to_bytes()) - .collect::>(); - - // re-compute the challenge - let challenge = compute_challenge::( - std::iter::once(params.gen1().to_bytes().as_ref()) - .chain(hs_bytes.iter().map(|hs| hs.as_ref())) - .chain(std::iter::once(h.to_bytes().as_ref())) - .chain(std::iter::once(commitment.to_bytes().as_ref())) - .chain(commitments_bytes.iter().map(|cm| cm.as_ref())) - .chain(std::iter::once(commitment_attributes.to_bytes().as_ref())) - .chain(commitments_attributes_bytes.iter().map(|cm| cm.as_ref())), - ); - - challenge == self.challenge - } - - // challenge || response opening || openings len || response openings || attributes len || - // response attributes - pub(crate) fn to_bytes(&self) -> Vec { - let openings_len = self.response_openings.len() as u64; - let attributes_len = self.response_attributes.len() as u64; - - let mut bytes = Vec::with_capacity(16 + (2 + openings_len + attributes_len) as usize * 32); - - bytes.extend_from_slice(&self.challenge.to_bytes()); - bytes.extend_from_slice(&self.response_opening.to_bytes()); - - bytes.extend_from_slice(&openings_len.to_le_bytes()); - for ro in &self.response_openings { - bytes.extend_from_slice(&ro.to_bytes()); - } - - bytes.extend_from_slice(&attributes_len.to_le_bytes()); - for rm in &self.response_attributes { - bytes.extend_from_slice(&rm.to_bytes()); - } - - bytes - } - - pub(crate) fn from_bytes(bytes: &[u8]) -> Result { - // at the very minimum there must be a single attribute being proven - if bytes.len() < 32 * 4 + 16 || (bytes.len() - 16) % 32 != 0 { - return Err(CoconutError::Deserialization( - "tried to deserialize proof of commitments with bytes of invalid length" - .to_string(), - )); - } - - let mut idx = 0; - // safety: bound checked + constant offset - #[allow(clippy::unwrap_used)] - let challenge_bytes = bytes[idx..idx + 32].try_into().unwrap(); - idx += 32; - // safety: bound checked + constant offset - #[allow(clippy::unwrap_used)] - let response_opening_bytes = bytes[idx..idx + 32].try_into().unwrap(); - idx += 32; - - let challenge = try_deserialize_scalar( - &challenge_bytes, - CoconutError::Deserialization("Failed to deserialize challenge".to_string()), - )?; - - let response_opening = try_deserialize_scalar( - &response_opening_bytes, - CoconutError::Deserialization( - "Failed to deserialize the response to the random".to_string(), - ), - )?; - - // safety: bound checked + constant offset - #[allow(clippy::unwrap_used)] - let ro_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap()); - idx += 8; - if bytes[idx..].len() < ro_len as usize * 32 + 8 { - return Err( - CoconutError::Deserialization( - "tried to deserialize proof of ciphertexts and commitment with insufficient number of bytes provided".to_string()), - ); - } - - let ro_end = idx + ro_len as usize * 32; - let response_openings = try_deserialize_scalar_vec( - ro_len, - &bytes[idx..ro_end], - CoconutError::Deserialization("Failed to deserialize openings response".to_string()), - )?; - - // safety: bound checked + constant offset - #[allow(clippy::unwrap_used)] - let rm_len = u64::from_le_bytes(bytes[ro_end..ro_end + 8].try_into().unwrap()); - let response_attributes = try_deserialize_scalar_vec( - rm_len, - &bytes[ro_end + 8..], - CoconutError::Deserialization("Failed to deserialize attributes response".to_string()), - )?; - - Ok(ProofCmCs { - challenge, - response_opening, - response_openings, - response_attributes, - }) - } -} - -#[derive(Debug, PartialEq, Eq)] -pub struct ProofKappaZeta { - // c - challenge: Scalar, - - // responses - response_serial_number: Scalar, - response_binding_number: Scalar, - response_blinder: Scalar, -} - -impl ProofKappaZeta { - pub(crate) fn construct( - params: &Parameters, - verification_key: &VerificationKey, - serial_number: &Attribute, - binding_number: &Attribute, - blinding_factor: &Scalar, - blinded_message: &G2Projective, - blinded_serial_number: &G2Projective, - ) -> Self { - // create the witnesses - let witness_blinder = params.random_scalar(); - let witness_serial_number = params.random_scalar(); - let witness_binding_number = params.random_scalar(); - let witness_attributes = [witness_serial_number, witness_binding_number]; - - let beta_bytes = verification_key - .beta_g2 - .iter() - .map(|beta_i| beta_i.to_bytes()) - .collect::>(); - - // witnesses commitments - // Aw = g2 * wt + alpha + beta[0] * wm[0] + ... + beta[i] * wm[i] - let commitment_kappa = params.gen2() * witness_blinder - + verification_key.alpha - + witness_attributes - .iter() - .zip(verification_key.beta_g2.iter()) - .map(|(wm_i, beta_i)| beta_i * wm_i) - .sum::(); - - // zeta is the public value associated with the serial number - let commitment_zeta = params.gen2() * witness_serial_number; - - let challenge = compute_challenge::( - std::iter::once(params.gen2().to_bytes().as_ref()) - .chain(std::iter::once(blinded_message.to_bytes().as_ref())) - .chain(std::iter::once(blinded_serial_number.to_bytes().as_ref())) - .chain(std::iter::once(verification_key.alpha.to_bytes().as_ref())) - .chain(beta_bytes.iter().map(|b| b.as_ref())) - .chain(std::iter::once(commitment_kappa.to_bytes().as_ref())) - .chain(std::iter::once(commitment_zeta.to_bytes().as_ref())), - ); - - // responses - let response_blinder = produce_response(&witness_blinder, &challenge, blinding_factor); - let response_serial_number = - produce_response(&witness_serial_number, &challenge, serial_number); - let response_binding_number = - produce_response(&witness_binding_number, &challenge, binding_number); - - ProofKappaZeta { - challenge, - response_serial_number, - response_binding_number, - response_blinder, - } - } - - pub(crate) fn private_attributes_len(&self) -> usize { - 2 - } - - pub(crate) fn verify( - &self, - params: &Parameters, - verification_key: &VerificationKey, - kappa: &G2Projective, - zeta: &G2Projective, - ) -> bool { - let beta_bytes = verification_key - .beta_g2 - .iter() - .map(|beta_i| beta_i.to_bytes()) - .collect::>(); - - let response_attributes = [self.response_serial_number, self.response_binding_number]; - // re-compute witnesses commitments - // Aw = (c * kappa) + (rt * g2) + ((1 - c) * alpha) + (rm[0] * beta[0]) + ... + (rm[i] * beta[i]) - let commitment_kappa = kappa * self.challenge - + params.gen2() * self.response_blinder - + verification_key.alpha * (Scalar::one() - self.challenge) - + response_attributes - .iter() - .zip(verification_key.beta_g2.iter()) - .map(|(priv_attr, beta_i)| beta_i * priv_attr) - .sum::(); - - // zeta is the public value associated with the serial number - let commitment_zeta = zeta * self.challenge + params.gen2() * self.response_serial_number; - - // compute the challenge - let challenge = compute_challenge::( - std::iter::once(params.gen2().to_bytes().as_ref()) - .chain(std::iter::once(kappa.to_bytes().as_ref())) - .chain(std::iter::once(zeta.to_bytes().as_ref())) - .chain(std::iter::once(verification_key.alpha.to_bytes().as_ref())) - .chain(beta_bytes.iter().map(|b| b.as_ref())) - .chain(std::iter::once(commitment_kappa.to_bytes().as_ref())) - .chain(std::iter::once(commitment_zeta.to_bytes().as_ref())), - ); - - challenge == self.challenge - } - - // challenge || response serial number || response binding number || repose blinder - pub(crate) fn to_bytes(&self) -> Vec { - let attributes_len = 2; // because we have serial number and the binding number - let mut bytes = Vec::with_capacity((1 + attributes_len + 1) as usize * 32); - - bytes.extend_from_slice(&self.challenge.to_bytes()); - bytes.extend_from_slice(&self.response_serial_number.to_bytes()); - bytes.extend_from_slice(&self.response_binding_number.to_bytes()); - - bytes.extend_from_slice(&self.response_blinder.to_bytes()); - - bytes - } - - pub(crate) fn from_bytes(bytes: &[u8]) -> Result { - // at the very minimum there must be a single attribute being proven - if bytes.len() != 128 { - return Err(CoconutError::DeserializationInvalidLength { - actual: bytes.len(), - modulus_target: bytes.len(), - modulus: 32, - object: "kappa and zeta".to_string(), - target: 32 * 4, - }); - } - - // safety: bound checked + constant offset - #[allow(clippy::unwrap_used)] - let challenge_bytes = bytes[..32].try_into().unwrap(); - let challenge = try_deserialize_scalar( - &challenge_bytes, - CoconutError::Deserialization("Failed to deserialize challenge".to_string()), - )?; - - // safety: bound checked + constant offset - #[allow(clippy::unwrap_used)] - let serial_number_bytes = &bytes[32..64].try_into().unwrap(); - let response_serial_number = try_deserialize_scalar( - serial_number_bytes, - CoconutError::Deserialization("failed to deserialize the serial number".to_string()), - )?; - - // safety: bound checked + constant offset - #[allow(clippy::unwrap_used)] - let binding_number_bytes = &bytes[64..96].try_into().unwrap(); - let response_binding_number = try_deserialize_scalar( - binding_number_bytes, - CoconutError::Deserialization("failed to deserialize the binding number".to_string()), - )?; - - // safety: bound checked + constant offset - #[allow(clippy::unwrap_used)] - let blinder_bytes = bytes[96..].try_into().unwrap(); - let response_blinder = try_deserialize_scalar( - &blinder_bytes, - CoconutError::Deserialization("failed to deserialize the blinder".to_string()), - )?; - - Ok(ProofKappaZeta { - challenge, - response_serial_number, - response_binding_number, - response_blinder, - }) - } -} - -// proof builder: -// - commitment -// - challenge -// - responses - -#[cfg(test)] -mod tests { - use super::*; - use crate::scheme::keygen::keygen; - use crate::scheme::setup::setup; - use crate::scheme::verification::{compute_kappa, compute_zeta}; - use crate::tests::helpers::random_scalars_refs; - use group::Group; - use rand::thread_rng; - - #[test] - fn proof_cm_cs_bytes_roundtrip() { - let mut rng = thread_rng(); - - let params = setup(1).unwrap(); - let cm = G1Projective::random(&mut rng); - let r = params.random_scalar(); - let cms: [G1Projective; 1] = [G1Projective::random(&mut rng)]; - let rs = params.n_random_scalars(1); - random_scalars_refs!(private_attributes, params, 1); - - // 0 public 1 private - let pi_s = ProofCmCs::construct(¶ms, &cm, &r, &cms, &rs, &private_attributes, &[]); - - let bytes = pi_s.to_bytes(); - assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s); - - let params = setup(2).unwrap(); - let cm = G1Projective::random(&mut rng); - let r = params.random_scalar(); - let cms: [G1Projective; 2] = [ - G1Projective::random(&mut rng), - G1Projective::random(&mut rng), - ]; - let rs = params.n_random_scalars(2); - random_scalars_refs!(private_attributes, params, 2); - - // 0 public 2 privates - let pi_s = ProofCmCs::construct(¶ms, &cm, &r, &cms, &rs, &private_attributes, &[]); - - let bytes = pi_s.to_bytes(); - assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s); - } - - #[test] - fn proof_kappa_zeta_bytes_roundtrip() { - let params = setup(4).unwrap(); - - let keypair = keygen(¶ms); - - // we don't care about 'correctness' of the proof. only whether we can correctly recover it from bytes - let serial_number = ¶ms.random_scalar(); - let binding_number = ¶ms.random_scalar(); - let private_attributes = vec![serial_number, binding_number]; - - let r = params.random_scalar(); - let kappa = compute_kappa(¶ms, keypair.verification_key(), &private_attributes, r); - let zeta = compute_zeta(¶ms, serial_number); - - // 0 public 2 private - let pi_v = ProofKappaZeta::construct( - ¶ms, - keypair.verification_key(), - serial_number, - binding_number, - &r, - &kappa, - &zeta, - ); - - let proof_bytes = pi_v.to_bytes(); - - let proof_from_bytes = ProofKappaZeta::from_bytes(&proof_bytes).unwrap(); - assert_eq!(proof_from_bytes, pi_v); - - // 2 public 2 private - let params = setup(4).unwrap(); - let keypair = keygen(¶ms); - - let pi_v = ProofKappaZeta::construct( - ¶ms, - keypair.verification_key(), - serial_number, - binding_number, - &r, - &kappa, - &zeta, - ); - - let proof_bytes = pi_v.to_bytes(); - - let proof_from_bytes = ProofKappaZeta::from_bytes(&proof_bytes).unwrap(); - assert_eq!(proof_from_bytes, pi_v); - } -} diff --git a/common/nymcoconut/src/scheme/aggregation.rs b/common/nymcoconut/src/scheme/aggregation.rs deleted file mode 100644 index e954e5b8b0..0000000000 --- a/common/nymcoconut/src/scheme/aggregation.rs +++ /dev/null @@ -1,432 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use core::iter::Sum; -use core::ops::Mul; - -use bls12_381::{G2Prepared, G2Projective, Scalar}; -use group::Curve; -use itertools::Itertools; - -use crate::error::{CoconutError, Result}; -use crate::scheme::verification::check_bilinear_pairing; -use crate::scheme::{PartialSignature, Signature, SignatureShare, SignerIndex, VerificationKey}; -use crate::utils::perform_lagrangian_interpolation_at_origin; -use crate::{Attribute, Parameters, VerificationKeyShare}; - -pub(crate) trait Aggregatable: Sized { - fn aggregate(aggregatable: &[Self], indices: Option<&[SignerIndex]>) -> Result; - - fn check_unique_indices(indices: &[SignerIndex]) -> bool { - // if aggregation is a threshold one, all indices should be unique - indices.iter().unique_by(|&index| index).count() == indices.len() - } -} - -// includes `VerificationKey` -impl Aggregatable for T -where - T: Sum, - for<'a> T: Sum<&'a T>, - for<'a> &'a T: Mul, -{ - fn aggregate(aggregatable: &[T], indices: Option<&[u64]>) -> Result { - if aggregatable.is_empty() { - return Err(CoconutError::Aggregation("Empty set of values".to_string())); - } - - if let Some(indices) = indices { - if !Self::check_unique_indices(indices) { - return Err(CoconutError::Aggregation("Non-unique indices".to_string())); - } - perform_lagrangian_interpolation_at_origin(indices, aggregatable) - } else { - // non-threshold - Ok(aggregatable.iter().sum()) - } - } -} - -impl Aggregatable for PartialSignature { - fn aggregate(sigs: &[PartialSignature], indices: Option<&[u64]>) -> Result { - let h = sigs - .first() - .ok_or_else(|| CoconutError::Aggregation("Empty set of signatures".to_string()))? - .sig1(); - - // TODO: is it possible to avoid this allocation? - let sigmas = sigs.iter().map(|sig| *sig.sig2()).collect::>(); - let aggr_sigma = Aggregatable::aggregate(&sigmas, indices)?; - - Ok(Signature(*h, aggr_sigma)) - } -} - -/// Ensures all provided verification keys were generated to verify the same number of attributes. -fn check_same_key_size(keys: &[VerificationKey]) -> bool { - keys.iter().map(|vk| vk.beta_g1.len()).all_equal() - && keys.iter().map(|vk| vk.beta_g2.len()).all_equal() -} - -pub fn aggregate_verification_keys( - keys: &[VerificationKey], - indices: Option<&[SignerIndex]>, -) -> Result { - if !check_same_key_size(keys) { - return Err(CoconutError::Aggregation( - "Verification keys are of different sizes".to_string(), - )); - } - Aggregatable::aggregate(keys, indices) -} - -pub fn aggregate_key_shares(shares: &[VerificationKeyShare]) -> Result { - let (keys, indices): (Vec<_>, Vec<_>) = shares - .iter() - .map(|share| (share.key.clone(), share.index)) - .unzip(); - - aggregate_verification_keys(&keys, Some(&indices)) -} - -pub fn aggregate_signatures( - signatures: &[PartialSignature], - indices: Option<&[SignerIndex]>, -) -> Result { - Aggregatable::aggregate(signatures, indices) -} - -pub fn aggregate_signatures_and_verify( - params: &Parameters, - verification_key: &VerificationKey, - attributes: &[&Attribute], - signatures: &[PartialSignature], - indices: Option<&[SignerIndex]>, -) -> Result { - // aggregate the signature - let signature = aggregate_signatures(signatures, indices)?; - - // Verify the signature - let alpha = verification_key.alpha; - - let tmp = attributes - .iter() - .zip(verification_key.beta_g2.iter()) - .map(|(&attr, beta_i)| beta_i * attr) - .sum::(); - - if bool::from(signature.0.is_identity()) { - return Err(CoconutError::Aggregation( - "Verification of the aggregated signature failed - h is an identity point".to_string(), - )); - } - if !check_bilinear_pairing( - &signature.0.to_affine(), - &G2Prepared::from((alpha + tmp).to_affine()), - &signature.1.to_affine(), - params.prepared_miller_g2(), - ) { - return Err(CoconutError::Aggregation( - "Verification of the aggregated signature failed".to_string(), - )); - } - Ok(signature) -} - -pub fn aggregate_signature_shares(shares: &[SignatureShare]) -> Result { - let (signatures, indices): (Vec<_>, Vec<_>) = shares - .iter() - .map(|share| (*share.signature(), share.index())) - .unzip(); - - aggregate_signatures(&signatures, Some(&indices)) -} - -pub fn aggregate_signature_shares_and_verify( - params: &Parameters, - verification_key: &VerificationKey, - attributes: &[&Attribute], - shares: &[SignatureShare], -) -> Result { - let (signatures, indices): (Vec<_>, Vec<_>) = shares - .iter() - .map(|share| (*share.signature(), share.index())) - .unzip(); - - aggregate_signatures_and_verify( - params, - verification_key, - attributes, - &signatures, - Some(&indices), - ) -} - -#[cfg(test)] -mod tests { - use crate::scheme::issuance::sign; - use crate::scheme::keygen::ttp_keygen; - use crate::scheme::verification::verify; - use crate::tests::helpers::random_scalars_refs; - use bls12_381::G1Projective; - use group::Group; - - use super::*; - - #[test] - fn key_aggregation_works_for_any_subset_of_keys() { - let params = Parameters::new(2).unwrap(); - let keypairs = ttp_keygen(¶ms, 3, 5).unwrap(); - - let vks = keypairs - .into_iter() - .map(|keypair| keypair.verification_key().clone()) - .collect::>(); - - let aggr_vk1 = aggregate_verification_keys(&vks[..3], Some(&[1, 2, 3])).unwrap(); - let aggr_vk2 = aggregate_verification_keys(&vks[2..], Some(&[3, 4, 5])).unwrap(); - - assert_eq!(aggr_vk1, aggr_vk2); - - // TODO: should those two actually work or not? - // aggregating threshold+1 - let aggr_more = aggregate_verification_keys(&vks[1..], Some(&[2, 3, 4, 5])).unwrap(); - assert_eq!(aggr_vk1, aggr_more); - - // aggregating all - let aggr_all = aggregate_verification_keys(&vks, Some(&[1, 2, 3, 4, 5])).unwrap(); - assert_eq!(aggr_all, aggr_vk1); - - // not taking enough points (threshold was 3) - let aggr_not_enough = aggregate_verification_keys(&vks[..2], Some(&[1, 2])).unwrap(); - assert_ne!(aggr_not_enough, aggr_vk1); - - // taking wrong index - let aggr_bad = aggregate_verification_keys(&vks[2..], Some(&[42, 123, 100])).unwrap(); - assert_ne!(aggr_vk1, aggr_bad); - } - - #[test] - fn key_aggregation_doesnt_work_for_empty_set_of_keys() { - let keys: Vec = vec![]; - assert!(aggregate_verification_keys(&keys, None).is_err()); - } - - #[test] - fn key_aggregation_doesnt_work_if_indices_have_invalid_length() { - let keys = vec![VerificationKey::identity(3)]; - - assert!(aggregate_verification_keys(&keys, Some(&[])).is_err()); - assert!(aggregate_verification_keys(&keys, Some(&[1, 2])).is_err()); - } - - #[test] - fn key_aggregation_doesnt_work_for_non_unique_indices() { - let keys = vec![VerificationKey::identity(3), VerificationKey::identity(3)]; - - assert!(aggregate_verification_keys(&keys, Some(&[1, 1])).is_err()); - } - - #[test] - fn key_aggregation_doesnt_work_for_keys_of_different_size() { - let keys = vec![VerificationKey::identity(3), VerificationKey::identity(1)]; - - assert!(aggregate_verification_keys(&keys, None).is_err()) - } - - #[test] - fn signature_aggregation_works_for_any_subset_of_signatures() { - let params = Parameters::new(2).unwrap(); - random_scalars_refs!(attributes, params, 2); - - let keypairs = ttp_keygen(¶ms, 3, 5).unwrap(); - - let (sks, vks): (Vec<_>, Vec<_>) = keypairs - .into_iter() - .map(|keypair| { - ( - keypair.secret_key().clone(), - keypair.verification_key().clone(), - ) - }) - .unzip(); - - let sigs = sks - .iter() - .map(|sk| sign(sk, &attributes).unwrap()) - .collect::>(); - - // aggregating (any) threshold works - let aggr_vk_1 = aggregate_verification_keys(&vks[..3], Some(&[1, 2, 3])).unwrap(); - let aggr_sig1 = aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_1, - &attributes, - &sigs[..3], - Some(&[1, 2, 3]), - ) - .unwrap(); - - let aggr_vk_2 = aggregate_verification_keys(&vks[2..], Some(&[3, 4, 5])).unwrap(); - let aggr_sig2 = aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_1, - &attributes, - &sigs[2..], - Some(&[3, 4, 5]), - ) - .unwrap(); - assert_eq!(aggr_sig1, aggr_sig2); - - // verify credential for good measure - assert!(verify(¶ms, &aggr_vk_1, &attributes, &aggr_sig1)); - assert!(verify(¶ms, &aggr_vk_2, &attributes, &aggr_sig2)); - - // aggregating threshold+1 works - let aggr_vk_more = aggregate_verification_keys(&vks[1..], Some(&[2, 3, 4, 5])).unwrap(); - let aggr_more = aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_more, - &attributes, - &sigs[1..], - Some(&[2, 3, 4, 5]), - ) - .unwrap(); - assert_eq!(aggr_sig1, aggr_more); - - // aggregating all - let aggr_vk_all = aggregate_verification_keys(&vks, Some(&[1, 2, 3, 4, 5])).unwrap(); - let aggr_all = aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_all, - &attributes, - &sigs, - Some(&[1, 2, 3, 4, 5]), - ) - .unwrap(); - assert_eq!(aggr_all, aggr_sig1); - - // not taking enough points (threshold was 3) should fail - let aggr_vk_not_enough = aggregate_verification_keys(&vks[..2], Some(&[1, 2])).unwrap(); - let aggr_not_enough = aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_not_enough, - &attributes, - &sigs[..2], - Some(&[1, 2]), - ) - .unwrap(); - assert_ne!(aggr_not_enough, aggr_sig1); - - // taking wrong index should fail - let aggr_vk_bad = aggregate_verification_keys(&vks[2..], Some(&[1, 2, 3])).unwrap(); - assert!(aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_bad, - &attributes, - &sigs[2..], - Some(&[42, 123, 100]), - ) - .is_err()); - } - - fn random_signature() -> Signature { - let mut rng = rand::thread_rng(); - Signature( - G1Projective::random(&mut rng), - G1Projective::random(&mut rng), - ) - } - - #[test] - fn signature_aggregation_doesnt_work_for_empty_set_of_signatures() { - let signatures: Vec = vec![]; - let params = Parameters::new(2).unwrap(); - random_scalars_refs!(attributes, params, 2); - let keypairs = ttp_keygen(¶ms, 3, 5).unwrap(); - - let (_, vks): (Vec<_>, Vec<_>) = keypairs - .into_iter() - .map(|keypair| { - ( - keypair.secret_key().clone(), - keypair.verification_key().clone(), - ) - }) - .unzip(); - - let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap(); - assert!(aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_all, - &attributes, - &signatures, - None - ) - .is_err()); - } - - #[test] - fn signature_aggregation_doesnt_work_if_indices_have_invalid_length() { - let signatures = vec![random_signature()]; - let params = Parameters::new(2).unwrap(); - random_scalars_refs!(attributes, params, 2); - let keypairs = ttp_keygen(¶ms, 3, 5).unwrap(); - let (_, vks): (Vec<_>, Vec<_>) = keypairs - .into_iter() - .map(|keypair| { - ( - keypair.secret_key().clone(), - keypair.verification_key().clone(), - ) - }) - .unzip(); - let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap(); - - assert!(aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_all, - &attributes, - &signatures, - Some(&[]) - ) - .is_err()); - assert!(aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_all, - &attributes, - &signatures, - Some(&[1, 2]), - ) - .is_err()); - } - - #[test] - fn signature_aggregation_doesnt_work_for_non_unique_indices() { - let signatures = vec![random_signature(), random_signature()]; - let params = Parameters::new(2).unwrap(); - random_scalars_refs!(attributes, params, 2); - let keypairs = ttp_keygen(¶ms, 3, 5).unwrap(); - let (_, vks): (Vec<_>, Vec<_>) = keypairs - .into_iter() - .map(|keypair| { - ( - keypair.secret_key().clone(), - keypair.verification_key().clone(), - ) - }) - .unzip(); - let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap(); - - assert!(aggregate_signatures_and_verify( - ¶ms, - &aggr_vk_all, - &attributes, - &signatures, - Some(&[1, 1]), - ) - .is_err()); - } - - // TODO: test for aggregating non-threshold keys -} diff --git a/common/nymcoconut/src/scheme/double_use.rs b/common/nymcoconut/src/scheme/double_use.rs deleted file mode 100644 index f0a6a1eed3..0000000000 --- a/common/nymcoconut/src/scheme/double_use.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2022-2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::error::{CoconutError, Result}; -use crate::traits::{Base58, Bytable}; -use crate::utils::try_deserialize_g2_projective; -use bls12_381::{G2Affine, G2Projective}; -use group::Curve; - -use std::fmt::{Debug, Formatter}; -use std::ops::Deref; - -#[derive(PartialEq, Eq, Clone, Copy)] -pub struct BlindedSerialNumber(G2Projective); - -// use custom Debug implementation to show base58 encoding (rather than raw curve elements) -impl Debug for BlindedSerialNumber { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("BlindedSerialNumber") - .field(&self.to_bs58()) - .finish() - } -} - -impl From for BlindedSerialNumber { - fn from(value: G2Projective) -> Self { - BlindedSerialNumber(value) - } -} - -impl From for BlindedSerialNumber { - fn from(value: G2Affine) -> Self { - BlindedSerialNumber(value.into()) - } -} - -impl Deref for BlindedSerialNumber { - type Target = G2Projective; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl TryFrom<&[u8]> for BlindedSerialNumber { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - if bytes.len() != 96 { - return Err( - CoconutError::Deserialization( - format!("Tried to deserialize blinded serial number with incorrect number of bytes, expected 96, got {}", bytes.len()), - )); - } - - // safety: we've just made a check for 96 bytes - #[allow(clippy::unwrap_used)] - let inner = try_deserialize_g2_projective( - &bytes.try_into().unwrap(), - CoconutError::Deserialization( - "failed to deserialize the blinded serial number (zeta)".to_string(), - ), - )?; - - Ok(BlindedSerialNumber(inner)) - } -} - -impl Bytable for BlindedSerialNumber { - fn to_byte_vec(&self) -> Vec { - self.0.to_affine().to_compressed().to_vec() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - Self::try_from(slice) - } -} - -impl Base58 for BlindedSerialNumber {} diff --git a/common/nymcoconut/src/scheme/issuance.rs b/common/nymcoconut/src/scheme/issuance.rs deleted file mode 100644 index 24a4c85bbf..0000000000 --- a/common/nymcoconut/src/scheme/issuance.rs +++ /dev/null @@ -1,660 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use std::ops::Neg; - -use bls12_381::{multi_miller_loop, G1Affine, G1Projective, G2Prepared, Scalar}; -use group::{Curve, Group, GroupEncoding}; - -use crate::error::{CoconutError, Result}; -use crate::proofs::ProofCmCs; -use crate::scheme::keygen::VerificationKey; -use crate::scheme::setup::Parameters; -use crate::scheme::BlindedSignature; -use crate::scheme::SecretKey; -use crate::Attribute; -use crate::Signature; - -// TODO: possibly completely remove those two functions. -// They only exist to have a simpler and smaller code snippets to test -// basic functionalities. -use crate::traits::{Base58, Bytable}; -use crate::utils::{hash_g1, try_deserialize_g1_projective}; - -// TODO NAMING: double check this one -// Lambda -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq, Eq))] -pub struct BlindSignRequest { - // cm - commitment: G1Projective, - // h - commitment_hash: G1Projective, - // c - private_attributes_commitments: Vec, - // pi_s - pi_s: ProofCmCs, -} - -impl TryFrom<&[u8]> for BlindSignRequest { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - if bytes.len() < 48 + 48 + 8 + 48 { - return Err(CoconutError::DeserializationMinLength { - min: 48 + 48 + 8 + 48, - actual: bytes.len(), - }); - } - - let mut j = 0; - let commitment_bytes_len = 48; - let commitment_hash_bytes_len = 48; - - // safety: we made bound check and we're using constant offest - #[allow(clippy::unwrap_used)] - let cm_bytes = bytes[..j + commitment_bytes_len].try_into().unwrap(); - let commitment = try_deserialize_g1_projective( - &cm_bytes, - CoconutError::Deserialization( - "Failed to deserialize compressed commitment".to_string(), - ), - )?; - j += commitment_bytes_len; - - // safety: we made bound check and we're using constant offest - #[allow(clippy::unwrap_used)] - let cm_hash_bytes = bytes[j..j + commitment_hash_bytes_len].try_into().unwrap(); - let commitment_hash = try_deserialize_g1_projective( - &cm_hash_bytes, - CoconutError::Deserialization( - "Failed to deserialize compressed commitment hash".to_string(), - ), - )?; - j += commitment_hash_bytes_len; - - // safety: we made bound check and we're using constant offest - #[allow(clippy::unwrap_used)] - let c_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap()); - j += 8; - if bytes[j..].len() < c_len as usize * 48 { - return Err(CoconutError::DeserializationMinLength { - min: c_len as usize * 48, - actual: bytes[56..].len(), - }); - } - - let mut private_attributes_commitments = Vec::with_capacity(c_len as usize); - for i in 0..c_len as usize { - let start = j + i * 48; - let end = start + 48; - - if bytes.len() < end { - return Err(CoconutError::Deserialization( - "Failed to deserialize compressed commitment".to_string(), - )); - } - - // safety: we made bound check and we're using constant offest - #[allow(clippy::unwrap_used)] - let private_attributes_commitment_bytes = bytes[start..end].try_into().unwrap(); - let private_attributes_commitment = try_deserialize_g1_projective( - &private_attributes_commitment_bytes, - CoconutError::Deserialization( - "Failed to deserialize compressed commitment".to_string(), - ), - )?; - - private_attributes_commitments.push(private_attributes_commitment) - } - - let pi_s = ProofCmCs::from_bytes(&bytes[j + c_len as usize * 48..])?; - - Ok(BlindSignRequest { - commitment, - commitment_hash, - private_attributes_commitments, - pi_s, - }) - } -} - -impl Bytable for BlindSignRequest { - fn to_byte_vec(&self) -> Vec { - let cm_bytes = self.commitment.to_affine().to_compressed(); - let cm_hash_bytes = self.commitment_hash.to_affine().to_compressed(); - let c_len = self.private_attributes_commitments.len() as u64; - let proof_bytes = self.pi_s.to_bytes(); - - let mut bytes = Vec::with_capacity(48 + 48 + 8 + c_len as usize * 48 + proof_bytes.len()); - - bytes.extend_from_slice(&cm_bytes); - bytes.extend_from_slice(&cm_hash_bytes); - bytes.extend_from_slice(&c_len.to_le_bytes()); - for c in &self.private_attributes_commitments { - bytes.extend_from_slice(&c.to_affine().to_compressed()); - } - - bytes.extend_from_slice(&proof_bytes); - - bytes - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - BlindSignRequest::from_bytes(slice) - } -} - -impl Base58 for BlindSignRequest {} - -impl BlindSignRequest { - fn verify_proof(&self, params: &Parameters, public_attributes: &[&Attribute]) -> bool { - self.pi_s.verify( - params, - &self.commitment, - &self.private_attributes_commitments, - public_attributes, - ) - } - - pub fn verify_commitment_hash(&self, public_attributes: &[&Attribute]) -> bool { - self.commitment_hash == compute_hash(self.commitment, public_attributes) - } - - pub fn get_commitment_hash(&self) -> G1Projective { - self.commitment_hash - } - - pub fn get_private_attributes_pedersen_commitments(&self) -> &[G1Projective] { - &self.private_attributes_commitments - } - - pub fn to_bytes(&self) -> Vec { - self.to_byte_vec() - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - BlindSignRequest::try_from(bytes) - } - - pub fn num_private_attributes(&self) -> usize { - self.private_attributes_commitments.len() - } -} - -pub fn compute_attributes_commitment( - params: &Parameters, - private_attributes: &[&Attribute], - public_attributes: &[&Attribute], - hs: &[G1Affine], -) -> (Scalar, G1Projective) { - let commitment_opening = params.random_scalar(); - - // Produces h0 ^ m0 * h1^m1 * .... * hn^mn - // where m0, m1, ...., mn are attributes - let attr_cm = private_attributes - .iter() - .chain(public_attributes.iter()) - .zip(hs) - .map(|(&m, h)| h * m) - .sum::(); - - // Produces g1^r * h0 ^ m0 * h1^m1 * .... * hn^mn - let commitment = params.gen1() * commitment_opening + attr_cm; - (commitment_opening, commitment) -} - -pub fn compute_pedersen_commitments_for_private_attributes( - params: &Parameters, - private_attributes: &[&Attribute], - h: &G1Projective, -) -> (Vec, Vec) { - // Generate openings for Pedersen commitment for each private attribute - let commitments_openings = params.n_random_scalars(private_attributes.len()); - - // Compute Pedersen commitment for each private attribute - let pedersen_commitments = commitments_openings - .iter() - .zip(private_attributes.iter()) - .map(|(o_j, &m_j)| params.gen1() * o_j + h * m_j) - .collect::>(); - - (commitments_openings, pedersen_commitments) -} - -pub fn compute_hash(commitment: G1Projective, public_attributes: &[&Attribute]) -> G1Projective { - let mut buff = Vec::new(); - buff.extend_from_slice(commitment.to_bytes().as_ref()); - for attr in public_attributes { - buff.extend_from_slice(attr.to_bytes().as_ref()); - } - hash_g1(buff) -} - -/// Builds cryptographic material required for blind sign. -pub fn prepare_blind_sign( - params: &Parameters, - private_attributes: &[&Attribute], - public_attributes: &[&Attribute], -) -> Result<(Vec, BlindSignRequest)> { - if private_attributes.is_empty() { - return Err(CoconutError::Issuance( - "Tried to prepare blind sign request for an empty set of private attributes" - .to_string(), - )); - } - - let hs = params.gen_hs(); - if private_attributes.len() + public_attributes.len() > hs.len() { - return Err(CoconutError::IssuanceMaxAttributes { - max: hs.len(), - requested: private_attributes.len() + public_attributes.len(), - }); - } - - let mut commitment_hash; - let mut commitment; - let mut commitment_opening; - - loop { - // Compute the attributes commitment - let (c_opening, c) = - compute_attributes_commitment(params, private_attributes, public_attributes, hs); - commitment_opening = c_opening; - commitment = c; - - // Compute the commitment hash - commitment_hash = compute_hash(commitment, public_attributes); - - // Check if the commitment hash is not the identity point - if !bool::from(commitment_hash.is_identity()) { - break; - } - } - - let (pedersen_commitments_openings, pedersen_commitments) = - compute_pedersen_commitments_for_private_attributes( - params, - private_attributes, - &commitment_hash, - ); - - let pi_s = ProofCmCs::construct( - params, - &commitment, - &commitment_opening, - &pedersen_commitments, - &pedersen_commitments_openings, - private_attributes, - public_attributes, - ); - - Ok(( - pedersen_commitments_openings, - BlindSignRequest { - commitment, - commitment_hash, - private_attributes_commitments: pedersen_commitments, - pi_s, - }, - )) -} - -pub fn blind_sign( - params: &Parameters, - signing_secret_key: &SecretKey, - blind_sign_request: &BlindSignRequest, - public_attributes: &[&Attribute], -) -> Result { - let num_private = blind_sign_request.private_attributes_commitments.len(); - let hs = params.gen_hs(); - - if num_private + public_attributes.len() > hs.len() { - return Err(CoconutError::IssuanceMaxAttributes { - max: hs.len(), - requested: num_private + public_attributes.len(), - }); - } - - // Verify the commitment hash - let h = compute_hash(blind_sign_request.commitment, public_attributes); - if bool::from(blind_sign_request.commitment_hash.is_identity()) { - return Err(CoconutError::Issuance( - "Commitment hash should not be an identity point".to_string(), - )); - } - if !(h == blind_sign_request.commitment_hash) { - return Err(CoconutError::Issuance( - "Failed to verify the commitment hash".to_string(), - )); - } - - // Verify the ZK proof - if !blind_sign_request.verify_proof(params, public_attributes) { - return Err(CoconutError::Issuance( - "Failed to verify the proof of knowledge".to_string(), - )); - } - - // in python implementation there are n^2 G1 multiplications, let's do it with a single one instead. - // i.e. compute h ^ (pub_m[0] * y[m + 1] + ... + pub_m[n] * y[m + n]) directly (where m is number of PRIVATE attributes) - // rather than ((h ^ pub_m[0]) ^ y[m + 1] , (h ^ pub_m[1]) ^ y[m + 2] , ...).sum() separately - let signed_public = h * public_attributes - .iter() - .zip(signing_secret_key.ys.iter().skip(num_private)) - .map(|(&attr, yi)| attr * yi) - .sum::(); - - // h ^ x + c[0] ^ y[0] + ... c[m] ^ y[m] + h ^ (pub_m[0] * y[m + 1] + ... + pub_m[n] * y[m + n]) - let sig = blind_sign_request - .private_attributes_commitments - .iter() - .zip(signing_secret_key.ys.iter()) - .map(|(c, yi)| c * yi) - .chain(std::iter::once(h * signing_secret_key.x)) - .chain(std::iter::once(signed_public)) - .sum(); - - Ok(BlindedSignature(h, sig)) -} - -/// Verifies a partial blind signature using the provided parameters and validator's verification key. -/// -/// # Arguments -/// -/// * `params` - A reference to the cryptographic parameters. -/// * `blind_sign_request` - A reference to the blind signature request signed by the client. -/// * `public_attributes` - A reference to the public attributes included in the client's request. -/// * `blind_sig` - A reference to the issued partial blinded signature to be verified. -/// * `partial_verification_key` - A reference to the validator's partial verification key. -/// -/// # Returns -/// -/// A boolean indicating whether the partial blind signature is valid (`true`) or not (`false`). -/// -/// # Remarks -/// -/// This function verifies the correctness and validity of a partial blind signature using -/// the provided cryptographic parameters, blind signature request, blinded signature, -/// and partial verification key. -/// It calculates pairings based on the provided values and checks whether the partial blind signature -/// is consistent with the verification key and commitments in the blind signature request. -/// The function returns `true` if the partial blind signature is valid, and `false` otherwise. -pub fn verify_partial_blind_signature( - params: &Parameters, - private_attribute_commitments: &[G1Projective], - public_attributes: &[&Attribute], - blind_sig: &BlindedSignature, - partial_verification_key: &VerificationKey, -) -> bool { - let num_private_attributes = private_attribute_commitments.len(); - if num_private_attributes + public_attributes.len() > partial_verification_key.beta_g2.len() { - return false; - } - if bool::from(blind_sig.0.is_identity()) { - return false; - } - - // TODO: we're losing some memory here due to extra allocation, - // but worst-case scenario (given SANE amount of attributes), it's just few kb at most - let c_neg = blind_sig.1.to_affine().neg(); - let g2_prep = params.prepared_miller_g2(); - - let mut terms = vec![ - // (c^{-1}, g2) - (c_neg, g2_prep.clone()), - // (s, alpha) - ( - blind_sig.0.to_affine(), - G2Prepared::from(partial_verification_key.alpha.to_affine()), - ), - ]; - - // for each private attribute, add (cm_i, beta_i) to the miller terms - for (private_attr_commit, beta_g2) in private_attribute_commitments - .iter() - .zip(&partial_verification_key.beta_g2) - { - // (cm_i, beta_i) - terms.push(( - private_attr_commit.to_affine(), - G2Prepared::from(beta_g2.to_affine()), - )) - } - - // for each public attribute, add (s^pub_j, beta_{priv + j}) to the miller terms - for (&pub_attr, beta_g2) in public_attributes.iter().zip( - partial_verification_key - .beta_g2 - .iter() - .skip(num_private_attributes), - ) { - // (s^pub_j, beta_j) - terms.push(( - (blind_sig.0 * pub_attr).to_affine(), - G2Prepared::from(beta_g2.to_affine()), - )) - } - - // get the references to all the terms to get the arguments the miller loop expects - #[allow(clippy::map_identity)] - let terms_refs = terms.iter().map(|(g1, g2)| (g1, g2)).collect::>(); - - // since checking whether e(a, b) == e(c, d) - // is equivalent to checking e(a, b) • e(c, d)^{-1} == id - // and thus to e(a, b) • e(c^{-1}, d) == id - // - // compute e(c^{-1}, g2) • e(s, alpha) • e(cm_0, beta_0) • e(cm_i, beta_i) • (s^pub_0, beta_{i+1}) (s^pub_j, beta_{i + j}) - multi_miller_loop(&terms_refs) - .final_exponentiation() - .is_identity() - .into() -} - -/// Creates a Coconut Signature under a given secret key on a set of public attributes only. -pub fn sign(secret_key: &SecretKey, public_attributes: &[&Attribute]) -> Result { - if public_attributes.len() > secret_key.ys.len() { - return Err(CoconutError::IssuanceMaxAttributes { - max: secret_key.ys.len(), - requested: public_attributes.len(), - }); - } - - //Serialize the array structure of the public attributes into a byte array - let mut serialized_attributes = Vec::new(); - //Prepend the length of the entire array (in bytes) - let array_len = public_attributes.len() as u64; - serialized_attributes.extend_from_slice(&array_len.to_le_bytes()); - //Serialize each attribute with its length - for &attribute in public_attributes.iter() { - let attr_bytes = attribute.to_bytes(); - let attr_len = attr_bytes.len() as u64; - - // Prefix the attribute with its length - serialized_attributes.extend_from_slice(&attr_len.to_le_bytes()); - serialized_attributes.extend_from_slice(&attr_bytes); - } - - //Hash the resulting byte array to derive the point H - let h = hash_g1(serialized_attributes); - - // x + m0 * y0 + m1 * y1 + ... mn * yn - let exponent = secret_key.x - + public_attributes - .iter() - .zip(secret_key.ys.iter()) - .map(|(&m_i, y_i)| m_i * y_i) - .sum::(); - - let sig2 = h * exponent; - Ok(Signature(h, sig2)) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::scheme::keygen::keygen; - use crate::tests::helpers::random_scalars_refs; - - #[test] - fn blind_sign_request_bytes_roundtrip() { - // 0 public and 1 private attribute - let params = Parameters::new(1).unwrap(); - random_scalars_refs!(private_attributes, params, 1); - random_scalars_refs!(public_attributes, params, 0); - - let (_commitments_openings, lambda) = - prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - - let bytes = lambda.to_bytes(); - assert_eq!( - BlindSignRequest::try_from(bytes.as_slice()).unwrap(), - lambda - ); - - // 2 public and 2 private attributes - let params = Parameters::new(4).unwrap(); - random_scalars_refs!(private_attributes, params, 2); - random_scalars_refs!(public_attributes, params, 2); - - let (_commitments_openings, lambda) = - prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - - let bytes = lambda.to_bytes(); - assert_eq!( - BlindSignRequest::try_from(bytes.as_slice()).unwrap(), - lambda - ); - } - - #[test] - fn test_prepare_blind_sign_non_identity_commitment_hash() { - let params = Parameters::new(1).unwrap(); - random_scalars_refs!(private_attributes, params, 1); - random_scalars_refs!(public_attributes, params, 0); - - // Call the function to prepare the blind sign - let result = prepare_blind_sign(¶ms, &private_attributes, &public_attributes); - - // Ensure the result is Ok - assert!(result.is_ok(), "prepare_blind_sign should succeed"); - - let (_, blind_sign_request) = result.unwrap(); - - // Ensure the commitment_hash is not the identity point - assert!( - !bool::from(blind_sign_request.commitment_hash.is_identity()), - "commitment_hash should not be the identity point" - ); - } - - #[test] - fn test_blind_sign_with_identity_commitment_hash() { - let params = Parameters::new(1).unwrap(); - random_scalars_refs!(private_attributes, params, 1); - random_scalars_refs!(public_attributes, params, 0); - - // Call the function to prepare the blind sign - let (_commitments_openings, blind_sign_request) = - prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - let blind_sign_request = BlindSignRequest { - commitment_hash: G1Projective::identity(), - ..blind_sign_request // This copies the other fields from the existing instance - }; - - let signing_secret_key = SecretKey { - x: params.random_scalar(), - ys: vec![params.random_scalar()], - }; - - // Call blind_sign and ensure it returns an error due to identity commitment hash - let result = blind_sign( - ¶ms, - &signing_secret_key, - &blind_sign_request, - &public_attributes, - ); - - // The result should be an error - assert!( - result.is_err(), - "blind_sign should return an error when commitment_hash is the identity point" - ); - } - - #[test] - fn successful_verify_partial_blind_signature() { - let params = Parameters::new(4).unwrap(); - random_scalars_refs!(private_attributes, params, 2); - random_scalars_refs!(public_attributes, params, 2); - - let (_commitments_openings, request) = - prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - - let validator_keypair = keygen(¶ms); - let blind_sig = blind_sign( - ¶ms, - validator_keypair.secret_key(), - &request, - &public_attributes, - ) - .unwrap(); - - assert!(verify_partial_blind_signature( - ¶ms, - &request.private_attributes_commitments, - &public_attributes, - &blind_sig, - validator_keypair.verification_key() - )); - } - - #[test] - fn successful_verify_partial_blind_signature_no_public_attributes() { - let params = Parameters::new(4).unwrap(); - random_scalars_refs!(private_attributes, params, 2); - - let (_commitments_openings, request) = - prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap(); - - let validator_keypair = keygen(¶ms); - let blind_sig = blind_sign(¶ms, validator_keypair.secret_key(), &request, &[]).unwrap(); - - assert!(verify_partial_blind_signature( - ¶ms, - &request.private_attributes_commitments, - &[], - &blind_sig, - validator_keypair.verification_key() - )); - } - - #[test] - fn fail_verify_partial_blind_signature_with_wrong_key() { - let params = Parameters::new(4).unwrap(); - random_scalars_refs!(private_attributes, params, 2); - random_scalars_refs!(public_attributes, params, 2); - - let (_commitments_openings, request) = - prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - - let validator_keypair = keygen(¶ms); - let validator2_keypair = keygen(¶ms); - let blind_sig = blind_sign( - ¶ms, - validator_keypair.secret_key(), - &request, - &public_attributes, - ) - .unwrap(); - - // this assertion should fail, as we try to verify with a wrong validator key - assert!(!verify_partial_blind_signature( - ¶ms, - &request.private_attributes_commitments, - &public_attributes, - &blind_sig, - validator2_keypair.verification_key() - ),); - } -} diff --git a/common/nymcoconut/src/scheme/keygen.rs b/common/nymcoconut/src/scheme/keygen.rs deleted file mode 100644 index f80c131143..0000000000 --- a/common/nymcoconut/src/scheme/keygen.rs +++ /dev/null @@ -1,722 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use core::borrow::Borrow; -use core::iter::Sum; -use core::ops::{Add, Mul}; - -use bls12_381::{G1Projective, G2Projective, Scalar}; -use group::Curve; -use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; -use serde_derive::{Deserialize, Serialize}; - -use crate::error::{CoconutError, Result}; -use crate::scheme::aggregation::aggregate_verification_keys; -use crate::scheme::setup::Parameters; -use crate::scheme::SignerIndex; -use crate::traits::Bytable; -use crate::utils::{ - try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar, - try_deserialize_scalar_vec, Polynomial, -}; -use crate::Base58; - -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq, Eq, Clone))] -#[cfg_attr( - feature = "key-zeroize", - derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop) -)] -pub struct SecretKey { - pub(crate) x: Scalar, - pub(crate) ys: Vec, -} - -impl PemStorableKey for SecretKey { - type Error = CoconutError; - - fn pem_type() -> &'static str { - "COCONUT SECRET KEY" - } - - fn to_bytes(&self) -> Vec { - self.to_bytes() - } - - fn from_bytes(bytes: &[u8]) -> std::result::Result { - Self::from_bytes(bytes) - } -} - -impl TryFrom<&[u8]> for SecretKey { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - // There should be x and at least one y - if bytes.len() < 32 * 2 + 8 || (bytes.len() - 8) % 32 != 0 { - return Err(CoconutError::DeserializationInvalidLength { - actual: bytes.len(), - modulus_target: bytes.len() - 8, - target: 32 * 2 + 8, - modulus: 32, - object: "secret key".to_string(), - }); - } - - // this conversion will not fail as we are taking the same length of data - #[allow(clippy::unwrap_used)] - let x_bytes: [u8; 32] = bytes[..32].try_into().unwrap(); - #[allow(clippy::unwrap_used)] - let ys_len = u64::from_le_bytes(bytes[32..40].try_into().unwrap()); - let actual_ys_len = (bytes.len() - 40) / 32; - - if ys_len as usize != actual_ys_len { - return Err(CoconutError::Deserialization(format!( - "Tried to deserialize secret key with inconsistent ys len (expected {ys_len}, got {actual_ys_len})" - ))); - } - - let x = try_deserialize_scalar( - &x_bytes, - CoconutError::Deserialization("Failed to deserialize secret key scalar".to_string()), - )?; - let ys = try_deserialize_scalar_vec( - ys_len, - &bytes[40..], - CoconutError::Deserialization("Failed to deserialize secret key scalars".to_string()), - )?; - - Ok(SecretKey { x, ys }) - } -} - -impl SecretKey { - /// Following a (distributed) key generation process, scalar values can be obtained - /// outside of the normal key generation process. - pub fn create_from_raw(x: Scalar, ys: Vec) -> Self { - Self { x, ys } - } - - /// Extract the Scalar copy of the underlying secrets. - /// The caller of this function must exercise extreme care to not misuse the data and ensuring it gets zeroized - pub fn hazmat_to_raw(&self) -> (Scalar, Vec) { - (self.x, self.ys.clone()) - } - - pub fn size(&self) -> usize { - self.ys.len() - } - - /// Derive verification key using this secret key. - pub fn verification_key(&self, params: &Parameters) -> VerificationKey { - let g1 = params.gen1(); - let g2 = params.gen2(); - VerificationKey { - alpha: g2 * self.x, - beta_g1: self.ys.iter().map(|y| g1 * y).collect(), - beta_g2: self.ys.iter().map(|y| g2 * y).collect(), - } - } - - // x || ys.len() || ys - pub fn to_bytes(&self) -> Vec { - let ys_len = self.ys.len(); - let mut bytes = Vec::with_capacity(8 + (ys_len + 1) * 32); - - bytes.extend_from_slice(&self.x.to_bytes()); - bytes.extend_from_slice(&ys_len.to_le_bytes()); - for y in self.ys.iter() { - bytes.extend_from_slice(&y.to_bytes()) - } - bytes - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - SecretKey::try_from(bytes) - } -} - -impl Bytable for SecretKey { - fn to_byte_vec(&self) -> Vec { - self.to_bytes() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - SecretKey::try_from(slice) - } -} - -impl Base58 for SecretKey {} - -// TODO: perhaps change points to affine representation -// to make verification slightly more efficient? -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct VerificationKey { - // TODO add gen2 as per the paper or imply it from the fact library is using bls381? - pub(crate) alpha: G2Projective, - pub(crate) beta_g1: Vec, - pub(crate) beta_g2: Vec, -} - -impl PemStorableKey for VerificationKey { - type Error = CoconutError; - - fn pem_type() -> &'static str { - "COCONUT VERIFICATION KEY" - } - - fn to_bytes(&self) -> Vec { - self.to_bytes() - } - - fn from_bytes(bytes: &[u8]) -> std::result::Result { - Self::from_bytes(bytes) - } -} - -impl TryFrom<&[u8]> for VerificationKey { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - // There should be at least alpha, one betaG1 and one betaG2 and their length - if bytes.len() < 96 * 2 + 48 + 8 || (bytes.len() - 8 - 96) % (96 + 48) != 0 { - return Err(CoconutError::DeserializationInvalidLength { - actual: bytes.len(), - modulus_target: bytes.len() - 8 - 96, - target: 96 * 2 + 48 + 8, - modulus: 96 + 48, - object: "verification key".to_string(), - }); - } - - // this conversion will not fail as we are taking the same length of data - #[allow(clippy::unwrap_used)] - let alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap(); - #[allow(clippy::unwrap_used)] - let betas_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap()); - - let actual_betas_len = (bytes.len() - 104) / (96 + 48); - - if betas_len as usize != actual_betas_len { - return Err( - CoconutError::Deserialization( - format!("Tried to deserialize verification key with inconsistent betas len (expected {betas_len}, got {actual_betas_len})" - ))); - } - - let alpha = try_deserialize_g2_projective( - &alpha_bytes, - CoconutError::Deserialization( - "Failed to deserialize verification key G2 point (alpha)".to_string(), - ), - )?; - - let mut beta_g1 = Vec::with_capacity(betas_len as usize); - let mut beta_g1_end: u64 = 0; - for i in 0..betas_len { - let start = (104 + i * 48) as usize; - let end = start + 48; - // we're using a constant 48 byte offset (which is the size of G1 compressed) so unwrap is fine - #[allow(clippy::unwrap_used)] - let beta_i_bytes = bytes[start..end].try_into().unwrap(); - let beta_i = try_deserialize_g1_projective( - &beta_i_bytes, - CoconutError::Deserialization( - "Failed to deserialize verification key G1 point (beta)".to_string(), - ), - )?; - - beta_g1_end = end as u64; - beta_g1.push(beta_i) - } - - let mut beta_g2 = Vec::with_capacity(betas_len as usize); - for i in 0..betas_len { - let start = (beta_g1_end + i * 96) as usize; - let end = start + 96; - // we're using a constant 96 byte offset (which is the size of G2 compressed) so unwrap is fine - #[allow(clippy::unwrap_used)] - let beta_i_bytes = bytes[start..end].try_into().unwrap(); - let beta_i = try_deserialize_g2_projective( - &beta_i_bytes, - CoconutError::Deserialization( - "Failed to deserialize verification key G2 point (beta)".to_string(), - ), - )?; - - beta_g2.push(beta_i) - } - - Ok(VerificationKey { - alpha, - beta_g1, - beta_g2, - }) - } -} - -impl<'b> Add<&'b VerificationKey> for VerificationKey { - type Output = VerificationKey; - - #[inline] - fn add(self, rhs: &'b VerificationKey) -> VerificationKey { - // If you're trying to add two keys together that were created - // for different number of attributes, just panic as it's a - // nonsense operation. - assert_eq!( - self.beta_g1.len(), - rhs.beta_g1.len(), - "trying to add verification keys generated for different number of attributes [G1]" - ); - - assert_eq!( - self.beta_g2.len(), - rhs.beta_g2.len(), - "trying to add verification keys generated for different number of attributes [G2]" - ); - - assert_eq!( - self.beta_g1.len(), - self.beta_g2.len(), - "this key is incorrect - the number of elements G1 and G2 does not match" - ); - - assert_eq!( - rhs.beta_g1.len(), - rhs.beta_g2.len(), - "they key you want to add is incorrect - the number of elements G1 and G2 does not match" - ); - - VerificationKey { - alpha: self.alpha + rhs.alpha, - beta_g1: self - .beta_g1 - .iter() - .zip(rhs.beta_g1.iter()) - .map(|(self_beta_g1, rhs_beta_g1)| self_beta_g1 + rhs_beta_g1) - .collect(), - beta_g2: self - .beta_g2 - .iter() - .zip(rhs.beta_g2.iter()) - .map(|(self_beta_g2, rhs_beta_g2)| self_beta_g2 + rhs_beta_g2) - .collect(), - } - } -} - -impl Mul for &VerificationKey { - type Output = VerificationKey; - - #[inline] - fn mul(self, rhs: Scalar) -> Self::Output { - VerificationKey { - alpha: self.alpha * rhs, - beta_g1: self.beta_g1.iter().map(|b_i| b_i * rhs).collect(), - beta_g2: self.beta_g2.iter().map(|b_i| b_i * rhs).collect(), - } - } -} - -impl Sum for VerificationKey -where - T: Borrow, -{ - #[inline] - fn sum(iter: I) -> Self - where - I: Iterator, - { - let mut peekable = iter.peekable(); - let head_attributes = match peekable.peek() { - Some(head) => head.borrow().beta_g2.len(), - None => { - // TODO: this is a really weird edge case. You're trying to sum an EMPTY iterator - // of VerificationKey. So should it panic here or just return some nonsense value? - return VerificationKey::identity(0); - } - }; - - peekable.fold(VerificationKey::identity(head_attributes), |acc, item| { - acc + item.borrow() - }) - } -} - -impl VerificationKey { - /// Create a (kinda) identity verification key using specified - /// number of 'beta' elements - pub(crate) fn identity(beta_size: usize) -> Self { - VerificationKey { - alpha: G2Projective::identity(), - beta_g1: vec![G1Projective::identity(); beta_size], - beta_g2: vec![G2Projective::identity(); beta_size], - } - } - - pub fn aggregate(sigs: &[Self], indices: Option<&[SignerIndex]>) -> Result { - aggregate_verification_keys(sigs, indices) - } - - pub fn alpha(&self) -> &G2Projective { - &self.alpha - } - - pub fn beta_g1(&self) -> &Vec { - &self.beta_g1 - } - - pub fn beta_g2(&self) -> &Vec { - &self.beta_g2 - } - - pub fn to_bytes(&self) -> Vec { - let beta_g1_len = self.beta_g1.len(); - let beta_g2_len = self.beta_g2.len(); - let mut bytes = Vec::with_capacity(96 + 8 + beta_g1_len * 48 + beta_g2_len * 96); - - bytes.extend_from_slice(&self.alpha.to_affine().to_compressed()); - - bytes.extend_from_slice(&beta_g1_len.to_le_bytes()); - - for beta_g1 in self.beta_g1.iter() { - bytes.extend_from_slice(&beta_g1.to_affine().to_compressed()) - } - - for beta_g2 in self.beta_g2.iter() { - bytes.extend_from_slice(&beta_g2.to_affine().to_compressed()) - } - - bytes - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - VerificationKey::try_from(bytes) - } -} - -impl Bytable for VerificationKey { - fn to_byte_vec(&self) -> Vec { - self.to_bytes() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - VerificationKey::try_from(slice) - } -} - -impl Base58 for VerificationKey {} - -#[derive(Debug, Clone)] -pub struct VerificationKeyShare { - pub key: VerificationKey, - pub index: SignerIndex, -} - -impl From<(VerificationKey, SignerIndex)> for VerificationKeyShare { - fn from(value: (VerificationKey, SignerIndex)) -> Self { - VerificationKeyShare { - key: value.0, - index: value.1, - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -#[cfg_attr(test, derive(PartialEq, Eq, Clone))] -pub struct KeyPair { - secret_key: SecretKey, - verification_key: VerificationKey, - - /// Optional index value specifying polynomial point used during threshold key generation. - pub index: Option, -} - -impl From for (SecretKey, VerificationKey) { - fn from(value: KeyPair) -> Self { - (value.secret_key, value.verification_key) - } -} - -impl PemStorableKeyPair for KeyPair { - type PrivatePemKey = SecretKey; - type PublicPemKey = VerificationKey; - - fn private_key(&self) -> &Self::PrivatePemKey { - &self.secret_key - } - - fn public_key(&self) -> &Self::PublicPemKey { - &self.verification_key - } - - fn from_keys(secret_key: Self::PrivatePemKey, verification_key: Self::PublicPemKey) -> Self { - Self::from_keys(secret_key, verification_key) - } -} - -impl KeyPair { - const MARKER_BYTES: &'static [u8] = b"coconutkeypair"; - - pub fn from_keys(secret_key: SecretKey, verification_key: VerificationKey) -> Self { - Self { - secret_key, - verification_key, - index: None, - } - } - - pub fn secret_key(&self) -> &SecretKey { - &self.secret_key - } - - pub fn verification_key(&self) -> &VerificationKey { - &self.verification_key - } - - pub fn to_verification_key_share(&self) -> Option { - self.index.map(|index| VerificationKeyShare { - key: self.verification_key.clone(), - index, - }) - } - - pub fn to_bytes(&self) -> Vec { - // Schema is coconutkeypair[14]|secret_key_len[8]|secret_key[secret_key_len]|verification_key_len[8]|verification_key[verification_key_len]|signer_index[8] - optional - self.to_byte_vec() - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - KeyPair::try_from_byte_slice(bytes) - } -} - -impl Bytable for KeyPair { - fn to_byte_vec(&self) -> Vec { - // Schema is coconutkeypair[14]|secret_key_len[8]|secret_key[secret_key_len]|verification_key_len[8]|verification_key[verification_key_len]|signer_index[8] - optional - let mut byts = vec![]; - let secret_key_bytes = self.secret_key.to_bytes(); - let secret_key_len = (secret_key_bytes.len() as u64).to_le_bytes(); - let verification_key_bytes = self.verification_key.to_bytes(); - let verification_key_len = (verification_key_bytes.len() as u64).to_le_bytes(); - byts.extend_from_slice(Self::MARKER_BYTES); - byts.extend_from_slice(&secret_key_len); - byts.extend_from_slice(&secret_key_bytes); - byts.extend_from_slice(&verification_key_len); - byts.extend_from_slice(&verification_key_bytes); - if let Some(index) = self.index { - byts.extend_from_slice(&index.to_le_bytes()) - } - byts - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - KeyPair::try_from(slice) - } -} - -impl Base58 for KeyPair {} - -impl TryFrom<&[u8]> for KeyPair { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - let header_len = Self::MARKER_BYTES.len(); - - // we must be able to at the very least read the length of secret key which is past the header - // and is 8 bytes long - if bytes.len() < header_len + 8 { - return Err(CoconutError::DeserializationMinLength { - min: header_len + 8, - actual: bytes.len(), - }); - } - - // safety: we made bound check and we're using constant offest - #[allow(clippy::unwrap_used)] - let secret_key_len = - u64::from_le_bytes(bytes[header_len..header_len + 8].try_into().unwrap()) as usize; - let secret_key_start = header_len + 8; - - let secret_key = - SecretKey::try_from(&bytes[secret_key_start..secret_key_start + secret_key_len])?; - - // we must be able to read the length of verification key - if bytes.len() < secret_key_start + secret_key_len + 8 { - return Err(CoconutError::DeserializationMinLength { - min: secret_key_start + secret_key_len + 8, - actual: bytes.len(), - }); - } - - // safety: we made bound check - #[allow(clippy::unwrap_used)] - let verification_key_len = u64::from_le_bytes( - bytes[secret_key_start + secret_key_len..secret_key_start + secret_key_len + 8] - .try_into() - .unwrap(), - ) as usize; - let verification_key_start = secret_key_start + secret_key_len + 8; - - let verification_key = VerificationKey::try_from( - &bytes[verification_key_start..verification_key_start + verification_key_len], - )?; - let consumed_bytes = verification_key_start + verification_key_len; - let index = if consumed_bytes < bytes.len() && [consumed_bytes..].len() == 8 { - #[allow(clippy::unwrap_used)] - Some(u64::from_le_bytes( - bytes[consumed_bytes..consumed_bytes + 8] - .try_into() - .unwrap(), - )) - } else { - None - }; - Ok(KeyPair { - secret_key, - verification_key, - index, - }) - } -} - -/// Generate a single Coconut keypair ((x, y0, y1...), (g2^x, g2^y0, ...)). -/// -/// It is not suitable for threshold credentials as all subsequent calls to `keygen` generate keys -/// that are independent of each other. -pub fn keygen(params: &Parameters) -> KeyPair { - let attributes = params.gen_hs().len(); - - let x = params.random_scalar(); - let ys = params.n_random_scalars(attributes); - - let secret_key = SecretKey { x, ys }; - let verification_key = secret_key.verification_key(params); - - KeyPair { - secret_key, - verification_key, - index: None, - } -} - -/// Generate Coconut keypairs. -/// -/// Generate a set of n Coconut keypairs [((x, y0, y1...), (g2^x, g2^y0, ...)), ...], -/// such that they support threshold aggregation by `threshold` number of parties. -/// It is expected that this procedure is executed by a Trusted Third Party. -pub fn ttp_keygen( - params: &Parameters, - threshold: u64, - num_authorities: u64, -) -> Result> { - if threshold == 0 { - return Err(CoconutError::Setup( - "Tried to generate threshold keys with a 0 threshold value".to_string(), - )); - } - - if threshold > num_authorities { - return Err( - CoconutError::Setup( - "Tried to generate threshold keys for threshold value being higher than number of the signing authorities".to_string(), - )); - } - - let attributes = params.gen_hs().len(); - - // generate polynomials - let v = Polynomial::new_random(params, threshold - 1); - let ws = (0..attributes) - .map(|_| Polynomial::new_random(params, threshold - 1)) - .collect::>(); - - // TODO: potentially if we had some known authority identifier we could use that instead - // of the increasing (1,2,3,...) sequence - let polynomial_indices = (1..=num_authorities).collect::>(); - - // generate polynomial shares - let x = polynomial_indices - .iter() - .map(|&id| v.evaluate(&Scalar::from(id))); - let ys = polynomial_indices.iter().map(|&id| { - ws.iter() - .map(|w| w.evaluate(&Scalar::from(id))) - .collect::>() - }); - - // finally set the keys - let secret_keys = x.zip(ys).map(|(x, ys)| SecretKey { x, ys }); - - let keypairs = secret_keys - .zip(polynomial_indices.iter()) - .map(|(secret_key, index)| { - let verification_key = secret_key.verification_key(params); - KeyPair { - secret_key, - verification_key, - index: Some(*index), - } - }) - .collect(); - - Ok(keypairs) -} - -#[cfg(test)] -mod tests { - use crate::scheme::setup::setup; - - use super::*; - - #[test] - fn keypair_bytes_roundtrip() { - let params1 = setup(1).unwrap(); - let params5 = setup(5).unwrap(); - - let keypair1 = keygen(¶ms1); - let keypair5 = keygen(¶ms5); - - let bytes1 = keypair1.to_bytes(); - let bytes5 = keypair5.to_bytes(); - - assert_eq!(KeyPair::from_bytes(&bytes1).unwrap(), keypair1); - assert_eq!(KeyPair::from_bytes(&bytes5).unwrap(), keypair5); - } - - #[test] - fn secret_key_bytes_roundtrip() { - let params1 = setup(1).unwrap(); - let params5 = setup(5).unwrap(); - - let keypair1 = keygen(¶ms1); - let keypair5 = keygen(¶ms5); - - let bytes1 = keypair1.secret_key.to_bytes(); - let bytes5 = keypair5.secret_key.to_bytes(); - - assert_eq!(SecretKey::from_bytes(&bytes1).unwrap(), keypair1.secret_key); - assert_eq!(SecretKey::from_bytes(&bytes5).unwrap(), keypair5.secret_key); - } - - #[test] - fn verification_key_bytes_roundtrip() { - let params1 = setup(1).unwrap(); - let params5 = setup(5).unwrap(); - - let keypair1 = &keygen(¶ms1); - let keypair5 = &keygen(¶ms5); - - let bytes1: Vec = keypair1.verification_key.to_bytes(); - let bytes5: Vec = keypair5.verification_key.to_bytes(); - - assert_eq!( - VerificationKey::try_from(bytes1.as_slice()).unwrap(), - keypair1.verification_key - ); - assert_eq!( - VerificationKey::try_from(bytes5.as_slice()).unwrap(), - keypair5.verification_key - ); - } -} diff --git a/common/nymcoconut/src/scheme/mod.rs b/common/nymcoconut/src/scheme/mod.rs deleted file mode 100644 index b811b6e250..0000000000 --- a/common/nymcoconut/src/scheme/mod.rs +++ /dev/null @@ -1,672 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -// TODO: implement https://crates.io/crates/signature traits? - -use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar}; -use group::Curve; - -pub use keygen::{SecretKey, VerificationKey}; - -use crate::error::{CoconutError, Result}; -use crate::scheme::setup::Parameters; -use crate::scheme::verification::check_bilinear_pairing; -use crate::traits::{Base58, Bytable}; -use crate::utils::try_deserialize_g1_projective; -use crate::Attribute; - -pub mod aggregation; -pub mod double_use; -pub mod issuance; -pub mod keygen; -pub mod setup; -pub mod verification; - -pub type SignerIndex = u64; - -// (h, s) -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective); - -pub type PartialSignature = Signature; - -impl TryFrom<&[u8]> for Signature { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - if bytes.len() != 96 { - return Err(CoconutError::Deserialization(format!( - "Signature must be exactly 96 bytes, got {}", - bytes.len() - ))); - } - - // safety: we just checked for the length so the unwraps are fine - #[allow(clippy::expect_used)] - let sig1_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48"); - #[allow(clippy::expect_used)] - let sig2_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48"); - - let sig1 = try_deserialize_g1_projective( - sig1_bytes, - CoconutError::Deserialization("Failed to deserialize compressed sig1".to_string()), - )?; - - let sig2 = try_deserialize_g1_projective( - sig2_bytes, - CoconutError::Deserialization("Failed to deserialize compressed sig2".to_string()), - )?; - - Ok(Signature(sig1, sig2)) - } -} - -impl Signature { - pub(crate) fn sig1(&self) -> &G1Projective { - &self.0 - } - - pub(crate) fn sig2(&self) -> &G1Projective { - &self.1 - } - - pub fn randomise_simple(&self, params: &Parameters) -> Signature { - let r = params.random_scalar(); - Signature(self.0 * r, self.1 * r) - } - - pub fn randomise(&self, params: &Parameters) -> (Signature, Scalar) { - let r = params.random_scalar(); - let r_prime = params.random_scalar(); - let h_prime = self.0 * r_prime; - let s_prime = (self.1 * r_prime) + (h_prime * r); - (Signature(h_prime, s_prime), r) - } - - pub fn to_bytes(self) -> [u8; 96] { - let mut bytes = [0u8; 96]; - bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed()); - bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed()); - bytes - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - Signature::try_from(bytes) - } - - pub fn verify( - &self, - params: &Parameters, - partial_verification_key: &VerificationKey, - private_attributes: &[&Attribute], - public_attributes: &[&Attribute], - commitment_hash: &G1Projective, - ) -> Result<()> { - // Verify the commitment hash - if bool::from(self.0.is_identity()) { - return Err(CoconutError::Verification( - "Commitment hash should not be an identity point".to_string(), - )); - } - if !(commitment_hash == &self.0) { - return Err(CoconutError::Verification( - "Verification of commitment hash from signature failed".to_string(), - )); - } - - let alpha = partial_verification_key.alpha; - - let signed_attributes = private_attributes - .iter() - .chain(public_attributes.iter()) - .zip(partial_verification_key.beta_g2.iter()) - .map(|(&attr, beta_i)| beta_i * attr) - .sum::(); - - // Verify the signature share - if !check_bilinear_pairing( - &self.0.to_affine(), - &G2Prepared::from((alpha + signed_attributes).to_affine()), - &self.1.to_affine(), - params.prepared_miller_g2(), - ) { - return Err(CoconutError::Unblind( - "Verification of signature share failed".to_string(), - )); - } - - Ok(()) - } -} - -impl Bytable for Signature { - fn to_byte_vec(&self) -> Vec { - self.to_bytes().to_vec() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - Signature::from_bytes(slice) - } -} - -impl Base58 for Signature {} - -#[derive(Debug, PartialEq, Eq)] -pub struct BlindedSignature(G1Projective, G1Projective); - -impl Bytable for BlindedSignature { - fn to_byte_vec(&self) -> Vec { - self.to_bytes().to_vec() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - Self::from_bytes(slice) - } -} - -impl Base58 for BlindedSignature {} - -impl TryFrom<&[u8]> for BlindedSignature { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - if bytes.len() != 96 { - return Err(CoconutError::Deserialization(format!( - "BlindedSignature must be exactly 96 bytes, got {}", - bytes.len() - ))); - } - - // safety: we just checked for the length so the unwraps are fine - #[allow(clippy::expect_used)] - let h_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48"); - #[allow(clippy::expect_used)] - let sig_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48"); - - let h = try_deserialize_g1_projective( - h_bytes, - CoconutError::Deserialization("Failed to deserialize compressed h".to_string()), - )?; - let sig = try_deserialize_g1_projective( - sig_bytes, - CoconutError::Deserialization("Failed to deserialize compressed sig".to_string()), - )?; - - Ok(BlindedSignature(h, sig)) - } -} - -impl BlindedSignature { - pub fn unblind( - &self, - partial_verification_key: &VerificationKey, - pedersen_commitments_openings: &[Scalar], - ) -> Signature { - // parse the signature - let h = &self.0; - let c = &self.1; - let blinding_removers = partial_verification_key - .beta_g1 - .iter() - .zip(pedersen_commitments_openings.iter()) - .map(|(beta, opening)| beta * opening) - .sum::(); - - let unblinded_c = c - blinding_removers; - - Signature(*h, unblinded_c) - } - - pub fn unblind_and_verify( - &self, - params: &Parameters, - partial_verification_key: &VerificationKey, - private_attributes: &[&Attribute], - public_attributes: &[&Attribute], - commitment_hash: &G1Projective, - pedersen_commitments_openings: &[Scalar], - ) -> Result { - let unblinded = self.unblind(partial_verification_key, pedersen_commitments_openings); - unblinded.verify( - params, - partial_verification_key, - private_attributes, - public_attributes, - commitment_hash, - )?; - Ok(unblinded) - } - - pub fn to_bytes(&self) -> [u8; 96] { - let mut bytes = [0u8; 96]; - bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed()); - bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed()); - bytes - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - BlindedSignature::try_from(bytes) - } -} - -// perhaps this should take signature by reference? we'll see how it goes -#[derive(Clone, Copy)] -pub struct SignatureShare { - signature: Signature, - index: SignerIndex, -} - -impl From<(Signature, SignerIndex)> for SignatureShare { - fn from(value: (Signature, SignerIndex)) -> Self { - SignatureShare { - signature: value.0, - index: value.1, - } - } -} - -impl SignatureShare { - pub fn new(signature: Signature, index: SignerIndex) -> Self { - SignatureShare { signature, index } - } - - pub fn signature(&self) -> &Signature { - &self.signature - } - - pub fn index(&self) -> SignerIndex { - self.index - } - - // pub fn aggregate(shares: &[Self]) -> Result { - // aggregate_signature_shares(shares) - // } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::hash_to_scalar; - use crate::scheme::aggregation::{ - aggregate_signatures_and_verify, aggregate_verification_keys, - }; - use crate::scheme::issuance::{blind_sign, compute_hash, prepare_blind_sign, sign}; - use crate::scheme::keygen::{keygen, ttp_keygen}; - use crate::scheme::verification::{prove_bandwidth_credential, verify, verify_credential}; - use crate::tests::helpers::random_scalars_refs; - - #[test] - fn unblind_returns_error_if_integrity_check_on_commitment_hash_fails() { - let params = Parameters::new(2).unwrap(); - random_scalars_refs!(private_attributes, params, 2); - - let (_commitments_openings, lambda) = - prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap(); - - let keypair1 = keygen(¶ms); - - let sig1 = blind_sign(¶ms, keypair1.secret_key(), &lambda, &[]).unwrap(); - - let wrong_commitment_opening = params.random_scalar(); - let wrong_commitment = params.gen1() * wrong_commitment_opening; - let fake_commitment_hash = compute_hash(wrong_commitment, &[]); - let wrong_commitments_openings = params.n_random_scalars(private_attributes.len()); - - assert!(sig1 - .unblind_and_verify( - ¶ms, - keypair1.verification_key(), - &private_attributes, - &[], - &fake_commitment_hash, - &wrong_commitments_openings, - ) - .is_err()); - } - - #[test] - fn unblind_returns_error_if_signature_verification_fails() { - let params = Parameters::new(2).unwrap(); - let p = [hash_to_scalar("Attribute1"), hash_to_scalar("Attribute2")]; - let private_attributes = vec![&p[0], &p[1]]; - - let p2 = [hash_to_scalar("Attribute3"), hash_to_scalar("Attribute4")]; - let private_attributes2 = vec![&p2[0], &p2[1]]; - - let (commitments_openings, lambda) = - prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap(); - - let keypair1 = keygen(¶ms); - - let sig1 = blind_sign(¶ms, keypair1.secret_key(), &lambda, &[]).unwrap(); - - assert!(sig1 - .unblind_and_verify( - ¶ms, - keypair1.verification_key(), - &private_attributes2, - &[], - &lambda.get_commitment_hash(), - &commitments_openings, - ) - .is_err()); - } - - #[test] - fn verification_on_two_private_attributes() { - let params = Parameters::new(2).unwrap(); - let serial_number = params.random_scalar(); - let binding_number = params.random_scalar(); - let private_attributes = vec![&serial_number, &binding_number]; - - let keypair1 = keygen(¶ms); - let keypair2 = keygen(¶ms); - - let (commitments_openings, lambda) = - prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap(); - - let sig1 = blind_sign(¶ms, keypair1.secret_key(), &lambda, &[]) - .unwrap() - .unblind_and_verify( - ¶ms, - keypair1.verification_key(), - &private_attributes, - &[], - &lambda.get_commitment_hash(), - &commitments_openings, - ) - .unwrap(); - - let sig2 = blind_sign(¶ms, keypair2.secret_key(), &lambda, &[]) - .unwrap() - .unblind_and_verify( - ¶ms, - keypair2.verification_key(), - &private_attributes, - &[], - &lambda.get_commitment_hash(), - &commitments_openings, - ) - .unwrap(); - - let theta1 = prove_bandwidth_credential( - ¶ms, - keypair1.verification_key(), - &sig1, - &serial_number, - &binding_number, - ) - .unwrap(); - - let theta2 = prove_bandwidth_credential( - ¶ms, - keypair2.verification_key(), - &sig2, - &serial_number, - &binding_number, - ) - .unwrap(); - - assert!(verify_credential( - ¶ms, - keypair1.verification_key(), - &theta1, - &[], - )); - - assert!(verify_credential( - ¶ms, - keypair2.verification_key(), - &theta2, - &[], - )); - - assert!(!verify_credential( - ¶ms, - keypair1.verification_key(), - &theta2, - &[], - )); - } - - #[test] - fn verification_on_two_public_attributes() { - let params = Parameters::new(2).unwrap(); - random_scalars_refs!(attributes, params, 2); - - let keypair1 = keygen(¶ms); - let keypair2 = keygen(¶ms); - let sig1 = sign(keypair1.secret_key(), &attributes).unwrap(); - let sig2 = sign(keypair2.secret_key(), &attributes).unwrap(); - - assert!(verify( - ¶ms, - keypair1.verification_key(), - &attributes, - &sig1, - )); - - assert!(!verify( - ¶ms, - keypair2.verification_key(), - &attributes, - &sig1, - )); - - assert!(!verify( - ¶ms, - keypair1.verification_key(), - &attributes, - &sig2, - )); - } - - #[test] - fn verification_on_two_public_and_two_private_attributes() { - let params = Parameters::new(4).unwrap(); - random_scalars_refs!(public_attributes, params, 2); - - let serial_number = params.random_scalar(); - let binding_number = params.random_scalar(); - let private_attributes = vec![&serial_number, &binding_number]; - - let keypair1 = keygen(¶ms); - let keypair2 = keygen(¶ms); - - let (commitments_openings, lambda) = - prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - - let sig1 = blind_sign(¶ms, keypair1.secret_key(), &lambda, &public_attributes) - .unwrap() - .unblind_and_verify( - ¶ms, - keypair1.verification_key(), - &private_attributes, - &public_attributes, - &lambda.get_commitment_hash(), - &commitments_openings, - ) - .unwrap(); - - let sig2 = blind_sign(¶ms, keypair2.secret_key(), &lambda, &public_attributes) - .unwrap() - .unblind_and_verify( - ¶ms, - keypair2.verification_key(), - &private_attributes, - &public_attributes, - &lambda.get_commitment_hash(), - &commitments_openings, - ) - .unwrap(); - - let theta1 = prove_bandwidth_credential( - ¶ms, - keypair1.verification_key(), - &sig1, - &serial_number, - &binding_number, - ) - .unwrap(); - - let theta2 = prove_bandwidth_credential( - ¶ms, - keypair2.verification_key(), - &sig2, - &serial_number, - &binding_number, - ) - .unwrap(); - - assert!(verify_credential( - ¶ms, - keypair1.verification_key(), - &theta1, - &public_attributes, - )); - - assert!(verify_credential( - ¶ms, - keypair2.verification_key(), - &theta2, - &public_attributes, - )); - - assert!(!verify_credential( - ¶ms, - keypair1.verification_key(), - &theta2, - &public_attributes, - )); - } - - #[test] - fn verification_on_two_public_and_two_private_attributes_from_two_signers() { - let params = Parameters::new(4).unwrap(); - random_scalars_refs!(public_attributes, params, 2); - - let serial_number = params.random_scalar(); - let binding_number = params.random_scalar(); - let private_attributes = vec![&serial_number, &binding_number]; - - let keypairs = ttp_keygen(¶ms, 2, 3).unwrap(); - - let (commitments_openings, lambda) = - prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - - let sigs = keypairs - .iter() - .map(|keypair| { - blind_sign(¶ms, keypair.secret_key(), &lambda, &public_attributes) - .unwrap() - .unblind_and_verify( - ¶ms, - keypair.verification_key(), - &private_attributes, - &public_attributes, - &lambda.get_commitment_hash(), - &commitments_openings, - ) - .unwrap() - }) - .collect::>(); - - let vks = keypairs - .into_iter() - .map(|keypair| keypair.verification_key().clone()) - .collect::>(); - - let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len()); - attributes.extend_from_slice(&private_attributes); - attributes.extend_from_slice(&public_attributes); - - let aggr_vk = aggregate_verification_keys(&vks[..2], Some(&[1, 2])).unwrap(); - let aggr_sig = aggregate_signatures_and_verify( - ¶ms, - &aggr_vk, - &attributes, - &sigs[..2], - Some(&[1, 2]), - ) - .unwrap(); - - let theta = prove_bandwidth_credential( - ¶ms, - &aggr_vk, - &aggr_sig, - &serial_number, - &binding_number, - ) - .unwrap(); - - assert!(verify_credential( - ¶ms, - &aggr_vk, - &theta, - &public_attributes, - )); - - // taking different subset of keys and credentials - let aggr_vk = aggregate_verification_keys(&vks[1..], Some(&[2, 3])).unwrap(); - let aggr_sig = aggregate_signatures_and_verify( - ¶ms, - &aggr_vk, - &attributes, - &sigs[1..], - Some(&[2, 3]), - ) - .unwrap(); - - let theta = prove_bandwidth_credential( - ¶ms, - &aggr_vk, - &aggr_sig, - &serial_number, - &binding_number, - ) - .unwrap(); - - assert!(verify_credential( - ¶ms, - &aggr_vk, - &theta, - &public_attributes, - )); - } - - #[test] - fn signature_bytes_roundtrip() { - let params = Parameters::default(); - let r = params.random_scalar(); - let s = params.random_scalar(); - let signature = Signature(params.gen1() * r, params.gen1() * s); - let bytes = signature.to_bytes(); - - // also make sure it is equivalent to the internal g1 compressed bytes concatenated - let expected_bytes = [ - signature.0.to_affine().to_compressed(), - signature.1.to_affine().to_compressed(), - ] - .concat(); - assert_eq!(expected_bytes, bytes); - assert_eq!(signature, Signature::try_from(&bytes[..]).unwrap()) - } - - #[test] - fn blinded_signature_bytes_roundtrip() { - let params = Parameters::default(); - let r = params.random_scalar(); - let s = params.random_scalar(); - let blinded_sig = BlindedSignature(params.gen1() * r, params.gen1() * s); - let bytes = blinded_sig.to_bytes(); - - // also make sure it is equivalent to the internal g1 compressed bytes concatenated - let expected_bytes = [ - blinded_sig.0.to_affine().to_compressed(), - blinded_sig.1.to_affine().to_compressed(), - ] - .concat(); - assert_eq!(expected_bytes, bytes); - assert_eq!(blinded_sig, BlindedSignature::try_from(&bytes[..]).unwrap()) - } -} diff --git a/common/nymcoconut/src/scheme/setup.rs b/common/nymcoconut/src/scheme/setup.rs deleted file mode 100644 index 65c994ec8f..0000000000 --- a/common/nymcoconut/src/scheme/setup.rs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use bls12_381::{G1Affine, G2Affine, G2Prepared, Scalar}; -use ff::Field; -use group::Curve; -use rand::thread_rng; - -use crate::error::{CoconutError, Result}; -use crate::utils::hash_g1; - -/// System-wide parameters used for the protocol -#[derive(Clone)] -pub struct Parameters { - /// Generator of the G1 group - g1: G1Affine, - - /// Additional generators of the G1 group - hs: Vec, - - /// Generator of the G2 group - g2: G2Affine, - - /// Precomputed G2 generator used for the miller loop - _g2_prepared_miller: G2Prepared, -} - -impl Parameters { - pub fn new(num_attributes: u32) -> Result { - if num_attributes == 0 { - return Err(CoconutError::Setup( - "Tried to setup the scheme for 0 attributes".to_string(), - )); - } - - let hs = (1..=num_attributes) - .map(|i| hash_g1(format!("h{i}")).to_affine()) - .collect(); - - Ok(Parameters { - g1: G1Affine::generator(), - hs, - g2: G2Affine::generator(), - _g2_prepared_miller: G2Prepared::from(G2Affine::generator()), - }) - } - - pub fn gen1(&self) -> &G1Affine { - &self.g1 - } - - pub fn gen2(&self) -> &G2Affine { - &self.g2 - } - - pub(crate) fn prepared_miller_g2(&self) -> &G2Prepared { - &self._g2_prepared_miller - } - - pub fn gen_hs(&self) -> &[G1Affine] { - &self.hs - } - - pub fn random_scalar(&self) -> Scalar { - // lazily-initialized thread-local random number generator, seeded by the system - let mut rng = thread_rng(); - Scalar::random(&mut rng) - } - - pub fn n_random_scalars(&self, n: usize) -> Vec { - (0..n).map(|_| self.random_scalar()).collect() - } -} - -pub fn setup(num_attributes: u32) -> Result { - Parameters::new(num_attributes) -} - -// for ease of use in tests requiring params -// TODO: not sure if this will have to go away when tests require some specific number of generators -#[cfg(test)] -impl Default for Parameters { - fn default() -> Self { - Parameters { - g1: G1Affine::generator(), - hs: Vec::new(), - g2: G2Affine::generator(), - _g2_prepared_miller: G2Prepared::from(G2Affine::generator()), - } - } -} diff --git a/common/nymcoconut/src/scheme/verification.rs b/common/nymcoconut/src/scheme/verification.rs deleted file mode 100644 index 149c60d3e3..0000000000 --- a/common/nymcoconut/src/scheme/verification.rs +++ /dev/null @@ -1,432 +0,0 @@ -// Copyright 2021-2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::error::{CoconutError, Result}; -use crate::proofs::ProofKappaZeta; -use crate::scheme::setup::Parameters; -use crate::scheme::Signature; -use crate::scheme::VerificationKey; -use crate::traits::{Base58, Bytable}; -use crate::utils::try_deserialize_g2_projective; -use crate::Attribute; -use bls12_381::{multi_miller_loop, G1Affine, G2Prepared, G2Projective, Scalar}; -use core::ops::Neg; -use group::{Curve, Group}; - -pub use crate::scheme::double_use::BlindedSerialNumber; - -// TODO NAMING: this whole thing -// Theta -#[derive(Debug, PartialEq, Eq)] -pub struct VerifyCredentialRequest { - // blinded_message (kappa) - pub blinded_message: G2Projective, - // blinded serial number (zeta) - pub blinded_serial_number: BlindedSerialNumber, - // sigma - pub credential: Signature, - // pi_v - pub pi_v: ProofKappaZeta, -} - -impl TryFrom<&[u8]> for VerifyCredentialRequest { - type Error = CoconutError; - - fn try_from(bytes: &[u8]) -> Result { - if bytes.len() < 288 { - return Err( - CoconutError::Deserialization( - format!("Tried to deserialize theta with insufficient number of bytes, expected >= 288, got {}", bytes.len()), - )); - } - - // safety: we just checked for the length so the unwraps are fine - #[allow(clippy::unwrap_used)] - let blinded_message_bytes = bytes[..96].try_into().unwrap(); - let blinded_message = try_deserialize_g2_projective( - &blinded_message_bytes, - CoconutError::Deserialization( - "failed to deserialize the blinded message (kappa)".to_string(), - ), - )?; - - let blinded_serial_number_bytes = &bytes[96..192]; - let blinded_serial_number = - BlindedSerialNumber::try_from_byte_slice(blinded_serial_number_bytes)?; - - let credential = Signature::try_from(&bytes[192..288])?; - - let pi_v = ProofKappaZeta::from_bytes(&bytes[288..])?; - - Ok(VerifyCredentialRequest { - blinded_message, - blinded_serial_number, - credential, - pi_v, - }) - } -} - -impl VerifyCredentialRequest { - fn verify_proof(&self, params: &Parameters, verification_key: &VerificationKey) -> bool { - self.pi_v.verify( - params, - verification_key, - &self.blinded_message, - &self.blinded_serial_number, - ) - } - - pub fn has_blinded_serial_number(&self, blinded_serial_number_bs58: &str) -> Result { - let blinded_serial_number = BlindedSerialNumber::try_from_bs58(blinded_serial_number_bs58)?; - let ret = self.blinded_serial_number.eq(&blinded_serial_number); - Ok(ret) - } - - // blinded message (kappa) || blinded serial number (zeta) || credential || pi_v - pub fn to_bytes(&self) -> Vec { - let blinded_message_bytes = self.blinded_message.to_affine().to_compressed(); - let blinded_serial_number_bytes = self.blinded_serial_number.to_affine().to_compressed(); - let credential_bytes = self.credential.to_bytes(); - let proof_bytes = self.pi_v.to_bytes(); - - let mut bytes = Vec::with_capacity(288 + proof_bytes.len()); - bytes.extend_from_slice(&blinded_message_bytes); - bytes.extend_from_slice(&blinded_serial_number_bytes); - bytes.extend_from_slice(&credential_bytes); - bytes.extend_from_slice(&proof_bytes); - - bytes - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - VerifyCredentialRequest::try_from(bytes) - } - - pub fn blinded_serial_number(&self) -> BlindedSerialNumber { - self.blinded_serial_number - } - - pub fn blinded_serial_number_bs58(&self) -> String { - self.blinded_serial_number.to_bs58() - } -} - -impl Bytable for VerifyCredentialRequest { - fn to_byte_vec(&self) -> Vec { - self.to_bytes() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - VerifyCredentialRequest::try_from(slice) - } -} - -impl Base58 for VerifyCredentialRequest {} - -pub fn compute_kappa( - params: &Parameters, - verification_key: &VerificationKey, - private_attributes: &[&Attribute], - blinding_factor: Scalar, -) -> G2Projective { - params.gen2() * blinding_factor - + verification_key.alpha - + private_attributes - .iter() - .zip(verification_key.beta_g2.iter()) - .map(|(&priv_attr, beta_i)| beta_i * priv_attr) - .sum::() -} - -pub fn compute_zeta(params: &Parameters, serial_number: &Attribute) -> G2Projective { - params.gen2() * serial_number -} - -pub fn prove_bandwidth_credential( - params: &Parameters, - verification_key: &VerificationKey, - signature: &Signature, - serial_number: &Attribute, - binding_number: &Attribute, -) -> Result { - if verification_key.beta_g2.len() < 2 { - return Err( - CoconutError::Verification( - format!("Tried to prove a credential for higher than supported by the provided verification key number of attributes (max: {}, requested: 2)", - verification_key.beta_g2.len() - ))); - } - - // Randomize the signature - let (signature_prime, sign_blinding_factor) = signature.randomise(params); - - // blinded_message : kappa in the paper. - // Value kappa is needed since we want to show a signature sigma'. - // In order to verify sigma' we need both the verification key vk and the message m. - // However, we do not want to reveal m to whomever we are showing the signature. - // Thus, we need kappa which allows us to verify sigma'. In particular, - // kappa is computed on m as input, but thanks to the use or random value r, - // it does not reveal any information about m. - let private_attributes = [serial_number, binding_number]; - let blinded_message = compute_kappa( - params, - verification_key, - &private_attributes, - sign_blinding_factor, - ); - - // zeta is a commitment to the serial number (i.e., a public value associated with the serial number) - let blinded_serial_number = compute_zeta(params, serial_number); - - let pi_v = ProofKappaZeta::construct( - params, - verification_key, - serial_number, - binding_number, - &sign_blinding_factor, - &blinded_message, - &blinded_serial_number, - ); - - Ok(VerifyCredentialRequest { - blinded_message, - blinded_serial_number: blinded_serial_number.into(), - credential: signature_prime, - pi_v, - }) -} - -/// Checks whether e(P, Q) * e(-R, S) == id -pub fn check_bilinear_pairing(p: &G1Affine, q: &G2Prepared, r: &G1Affine, s: &G2Prepared) -> bool { - // checking e(P, Q) * e(-R, S) == id - // is equivalent to checking e(P, Q) == e(R, S) - // but requires only a single final exponentiation rather than two of them - // and therefore, as seen via benchmarks.rs, is almost 50% faster - // (1.47ms vs 2.45ms, tested on R9 5900X) - - let multi_miller = multi_miller_loop(&[(p, q), (&r.neg(), s)]); - multi_miller.final_exponentiation().is_identity().into() -} - -pub fn check_vk_pairing( - params: &Parameters, - dkg_values: &[G2Projective], - vk: &VerificationKey, -) -> bool { - let values_len = dkg_values.len(); - if values_len == 0 || values_len - 1 != vk.beta_g1.len() || values_len - 1 != vk.beta_g2.len() { - return false; - } - - // safety: we made an explicit check for if the length of the slice is 0, thus unwrap here is fine - #[allow(clippy::unwrap_used)] - if &vk.alpha != *dkg_values.first().as_ref().unwrap() { - return false; - } - let dkg_betas = &dkg_values[1..]; - if dkg_betas - .iter() - .zip(vk.beta_g2.iter()) - .any(|(dkg_beta, vk_beta)| dkg_beta != vk_beta) - { - return false; - } - if vk.beta_g1.iter().zip(vk.beta_g2.iter()).any(|(g1, g2)| { - !check_bilinear_pairing( - params.gen1(), - &G2Prepared::from(g2.to_affine()), - &g1.to_affine(), - params.prepared_miller_g2(), - ) - }) { - return false; - } - - true -} - -pub fn verify_credential( - params: &Parameters, - verification_key: &VerificationKey, - theta: &VerifyCredentialRequest, - public_attributes: &[&Attribute], -) -> bool { - if public_attributes.len() + theta.pi_v.private_attributes_len() - > verification_key.beta_g2.len() - { - return false; - } - - if !theta.verify_proof(params, verification_key) { - return false; - } - - let kappa = if public_attributes.is_empty() { - theta.blinded_message - } else { - let signed_public_attributes = public_attributes - .iter() - .zip( - verification_key - .beta_g2 - .iter() - .skip(theta.pi_v.private_attributes_len()), - ) - .map(|(&pub_attr, beta_i)| beta_i * pub_attr) - .sum::(); - - theta.blinded_message + signed_public_attributes - }; - - check_bilinear_pairing( - &theta.credential.0.to_affine(), - &G2Prepared::from(kappa.to_affine()), - &(theta.credential.1).to_affine(), - params.prepared_miller_g2(), - ) && !bool::from(theta.credential.0.is_identity()) -} - -// Used in tests only -pub fn verify( - params: &Parameters, - verification_key: &VerificationKey, - public_attributes: &[&Attribute], - sig: &Signature, -) -> bool { - let kappa = (verification_key.alpha - + public_attributes - .iter() - .zip(verification_key.beta_g2.iter()) - .map(|(&m_i, b_i)| b_i * m_i) - .sum::()) - .to_affine(); - - check_bilinear_pairing( - &sig.0.to_affine(), - &G2Prepared::from(kappa), - &sig.1.to_affine(), - params.prepared_miller_g2(), - ) && !bool::from(sig.0.is_identity()) -} - -#[cfg(test)] -mod tests { - use crate::scheme::issuance::sign; - use crate::scheme::keygen::keygen; - use crate::scheme::setup::setup; - - use super::*; - - #[test] - fn vk_pairing() { - let params = setup(2).unwrap(); - let keypair = keygen(¶ms); - let vk = keypair.verification_key(); - - let mut dkg_values = vec![vk.alpha]; - dkg_values.append(&mut vk.beta_g2.clone()); - assert!(check_vk_pairing(¶ms, &dkg_values, vk)); - } - - #[test] - fn theta_bytes_roundtrip() { - let params = setup(2).unwrap(); - - let keypair = keygen(¶ms); - let r = params.random_scalar(); - let s = params.random_scalar(); - - let signature = Signature(params.gen1() * r, params.gen1() * s); - let serial_number = params.random_scalar(); - let binding_number = params.random_scalar(); - - let theta = prove_bandwidth_credential( - ¶ms, - keypair.verification_key(), - &signature, - &serial_number, - &binding_number, - ) - .unwrap(); - - let bytes = theta.to_bytes(); - assert_eq!( - VerifyCredentialRequest::try_from(bytes.as_slice()).unwrap(), - theta - ); - } - - #[test] - fn reject_forged_signature_via_linear_combination() { - // This test checks if the protocol correctly rejects forged signatures created - // by linear combinations of valid signatures. The verification for forged - // signatures should fail. - let params = Parameters::new(4).unwrap(); - - let scalar_2 = Scalar::one() + Scalar::one(); - let scalar_2_inv = Scalar::invert(&scalar_2).unwrap(); - - //#1 - let a = params.random_scalar(); - let zero = Scalar::zero(); - let a_zero = vec![&a, &zero]; - let zero_a = vec![&zero, &a]; - - let validator_keypair = keygen(¶ms); - - //#2 - let sig_a_zero = sign(validator_keypair.secret_key(), &a_zero).unwrap(); - let sig_zero_a = sign(validator_keypair.secret_key(), &zero_a).unwrap(); - - assert!(verify( - ¶ms, - validator_keypair.verification_key(), - &a_zero, - &sig_a_zero - )); - assert!(verify( - ¶ms, - validator_keypair.verification_key(), - &zero_a, - &sig_zero_a - )); - - //#3 - let h0 = sig_a_zero.0; - // Removed unnecessary references - let h1 = scalar_2_inv * sig_a_zero.1 + scalar_2_inv * sig_zero_a.1; - let forged_signature = Signature(h0, h1); - let a_half = a * scalar_2_inv; - let new_plaintext = vec![&a_half, &a_half]; - - // The forged signature should not pass verification - assert!(!verify( - ¶ms, - validator_keypair.verification_key(), - &new_plaintext, - &forged_signature - )); - - //#4 - let scalar_3 = Scalar::one() + Scalar::one() + Scalar::one(); - let scalar_4 = Scalar::one() + Scalar::one() + Scalar::one() + Scalar::one(); - let scalar_4_inv = Scalar::invert(&scalar_4).unwrap(); - let scalar_3_over_4 = scalar_3 * scalar_4_inv; - - // Removed unnecessary references - let h1_2 = scalar_4_inv * sig_a_zero.1 + scalar_3_over_4 * sig_zero_a.1; - let forged_signature_2 = Signature(h0, h1_2); - let a_quarter = a * scalar_4_inv; - let a_3_over_4 = a * scalar_3_over_4; - let new_plaintext_2 = vec![&a_quarter, &a_3_over_4]; - - // The second forged signature should also not pass verification - assert!(!verify( - ¶ms, - validator_keypair.verification_key(), - &new_plaintext_2, - &forged_signature_2 - )); - } -} diff --git a/common/nymcoconut/src/tests/e2e.rs b/common/nymcoconut/src/tests/e2e.rs deleted file mode 100644 index b9cc397845..0000000000 --- a/common/nymcoconut/src/tests/e2e.rs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::random_scalars_refs; -use crate::tests::helpers::tests::generate_dkg_keys; -use crate::{ - aggregate_verification_keys, setup, tests::helpers::*, ttp_keygen, verify_credential, - CoconutError, VerificationKey, -}; - -#[test] -fn keygen() -> Result<(), CoconutError> { - let params = setup(5)?; - let node_indices = vec![15u64, 248, 33521]; - - random_scalars_refs!(public_attributes, params, 2); - - // generate_keys - let coconut_keypairs = ttp_keygen(¶ms, 2, 3)?; - - let verification_keys: Vec = coconut_keypairs - .iter() - .map(|keypair| keypair.verification_key().clone()) - .collect(); - - // aggregate verification keys - let verification_key = aggregate_verification_keys(&verification_keys, Some(&node_indices))?; - - // Generate cryptographic material to verify them - let theta = theta_from_keys_and_attributes( - ¶ms, - &coconut_keypairs, - &node_indices, - &public_attributes, - )?; - - // Verify credentials - assert!(verify_credential( - ¶ms, - &verification_key, - &theta, - &public_attributes, - )); - - Ok(()) -} - -#[test] -#[ignore] // expensive test -fn dkg() -> Result<(), CoconutError> { - let params = setup(5)?; - let node_indices = vec![15u64, 248, 33521]; - - random_scalars_refs!(public_attributes, params, 2); - - // generate_keys - let coconut_keypairs = generate_dkg_keys(5, &node_indices); - - let verification_keys: Vec = coconut_keypairs - .iter() - .map(|keypair| keypair.verification_key().clone()) - .collect(); - - // aggregate verification keys - let verification_key = aggregate_verification_keys(&verification_keys, Some(&node_indices))?; - - // Generate cryptographic material to verify them - let theta = theta_from_keys_and_attributes( - ¶ms, - &coconut_keypairs, - &node_indices, - &public_attributes, - )?; - - // Verify credentials - assert!(verify_credential( - ¶ms, - &verification_key, - &theta, - &public_attributes, - )); - - Ok(()) -} diff --git a/common/nymcoconut/src/tests/helpers.rs b/common/nymcoconut/src/tests/helpers.rs deleted file mode 100644 index f01081bbf1..0000000000 --- a/common/nymcoconut/src/tests/helpers.rs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::*; -use itertools::izip; -use std::fmt::Debug; - -// unwraps are fine in the test code -#[allow(clippy::unwrap_used)] -pub fn theta_from_keys_and_attributes( - params: &Parameters, - coconut_keypairs: &Vec, - indices: &[scheme::SignerIndex], - public_attributes: &[&PublicAttribute], -) -> Result { - let serial_number = params.random_scalar(); - let binding_number = params.random_scalar(); - let private_attributes = vec![&serial_number, &binding_number]; - - // generate commitment - let (commitments_openings, blind_sign_request) = - prepare_blind_sign(params, &private_attributes, public_attributes)?; - - let verification_keys: Vec = coconut_keypairs - .iter() - .map(|keypair| keypair.verification_key().clone()) - .collect(); - - // aggregate verification keys - let verification_key = aggregate_verification_keys(&verification_keys, Some(indices))?; - - // generate blinded signatures - let mut blinded_signatures = Vec::new(); - - for keypair in coconut_keypairs { - let blinded_signature = blind_sign( - params, - keypair.secret_key(), - &blind_sign_request, - public_attributes, - )?; - blinded_signatures.push(blinded_signature) - } - - // Unblind - let unblinded_signatures: Vec<(scheme::SignerIndex, Signature)> = izip!( - indices.iter(), - blinded_signatures.iter(), - verification_keys.iter() - ) - .map(|(idx, s, vk)| { - ( - *idx, - s.unblind_and_verify( - params, - vk, - &private_attributes, - public_attributes, - &blind_sign_request.get_commitment_hash(), - &commitments_openings, - ) - .unwrap(), - ) - }) - .collect(); - - // Aggregate signatures - let signature_shares: Vec = unblinded_signatures - .iter() - .map(|(idx, signature)| SignatureShare::new(*signature, *idx)) - .collect(); - - let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len()); - attributes.extend_from_slice(&private_attributes); - attributes.extend_from_slice(public_attributes); - - // Randomize credentials and generate any cryptographic material to verify them - let signature = aggregate_signature_shares_and_verify( - params, - &verification_key, - &attributes, - &signature_shares, - )?; - - // Generate cryptographic material to verify them - let theta = prove_bandwidth_credential( - params, - &verification_key, - &signature, - &serial_number, - &binding_number, - )?; - - Ok(theta) -} - -// unwraps are fine in the test code -#[allow(clippy::unwrap_used)] -pub fn transpose_matrix(matrix: Vec>) -> Vec> { - if matrix.is_empty() { - return vec![]; - } - let len = matrix[0].len(); - let mut iters: Vec<_> = matrix.into_iter().map(|d| d.into_iter()).collect(); - (0..len) - .map(|_| { - iters - .iter_mut() - .map(|it| it.next().unwrap()) - .collect::>() - }) - .collect::>() -} - -#[macro_export] -macro_rules! random_scalars_refs { - ( $x: ident, $params: expr, $n: expr ) => { - let _vec = $params.n_random_scalars($n); - #[allow(clippy::map_identity)] - let $x = _vec.iter().collect::>(); - }; -} - -pub use random_scalars_refs; - -#[cfg(test)] -pub mod tests { - use super::*; - use bls12_381::Scalar; - use nym_dkg::{bte::decrypt_share, combine_shares, Dealing, NodeIndex}; - use rand_chacha::rand_core::SeedableRng; - - pub fn generate_dkg_secrets(node_indices: &[NodeIndex]) -> Vec { - let dummy_seed = [42u8; 32]; - let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed); - let params = nym_dkg::bte::setup(); - - // the simplest possible case - let threshold = 2; - - let mut receivers = std::collections::BTreeMap::new(); - let mut full_keys = Vec::new(); - for index in node_indices { - let (dk, pk) = nym_dkg::bte::keygen(¶ms, &mut rng); - receivers.insert(*index, *pk.public_key()); - full_keys.push((dk, pk)) - } - let dealings = node_indices - .iter() - .map(|&dealer_index| { - Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 - }) - .collect::>(); - let mut derived_secrets = Vec::new(); - for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { - let shares = dealings - .iter() - .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) - .collect(); - - let recovered_secret = - combine_shares(shares, &receivers.keys().copied().collect::>()).unwrap(); - - derived_secrets.push(recovered_secret) - } - derived_secrets - } - pub fn generate_dkg_keys(num_attributes: u32, node_indices: &[NodeIndex]) -> Vec { - let params = Parameters::new(num_attributes).unwrap(); - let mut all_secrets = vec![]; - for _ in 0..num_attributes { - let secrets = generate_dkg_secrets(node_indices); - all_secrets.push(secrets); - } - let signers = transpose_matrix(all_secrets); - signers - .into_iter() - .map(|mut secrets| { - let x = secrets.pop().unwrap(); - let sk = SecretKey::create_from_raw(x, secrets); - let vk = sk.verification_key(¶ms); - KeyPair::from_keys(sk, vk) - }) - .collect() - } -} diff --git a/common/nymcoconut/src/tests/mod.rs b/common/nymcoconut/src/tests/mod.rs deleted file mode 100644 index c13b7a3b20..0000000000 --- a/common/nymcoconut/src/tests/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -#[cfg(test)] -mod e2e; -pub mod helpers; diff --git a/common/nymcoconut/src/traits.rs b/common/nymcoconut/src/traits.rs deleted file mode 100644 index b2aa13944c..0000000000 --- a/common/nymcoconut/src/traits.rs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -#![warn(clippy::expect_used)] -#![warn(clippy::unwrap_used)] - -use crate::CoconutError; -use bls12_381::{G1Affine, G1Projective, Scalar}; -use group::GroupEncoding; - -pub trait Bytable -where - Self: Sized, -{ - fn to_byte_vec(&self) -> Vec; - - fn try_from_byte_slice(slice: &[u8]) -> Result; -} - -pub trait Base58 -where - Self: Bytable, -{ - fn try_from_bs58>(x: S) -> Result { - let bs58_decoded = &bs58::decode(x.as_ref()).into_vec()?; - Self::try_from_byte_slice(bs58_decoded) - } - fn to_bs58(&self) -> String { - bs58::encode(self.to_byte_vec()).into_string() - } -} - -impl Bytable for Scalar { - fn to_byte_vec(&self) -> Vec { - self.to_bytes().to_vec() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - let received = slice.len(); - let Ok(arr) = slice.try_into() else { - return Err(CoconutError::UnexpectedArrayLength { - typ: "Scalar".to_string(), - received, - expected: 32, - }); - }; - - let maybe_scalar = Scalar::from_bytes(arr); - if maybe_scalar.is_none().into() { - Err(CoconutError::ScalarDeserializationFailure) - } else { - // safety: this unwrap is fine as we've just checked the element is not none - #[allow(clippy::unwrap_used)] - Ok(maybe_scalar.unwrap()) - } - } -} - -impl Base58 for Scalar {} - -impl Bytable for G1Projective { - fn to_byte_vec(&self) -> Vec { - self.to_bytes().as_ref().to_vec() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - let received = slice.len(); - let arr: Result<[u8; 48], _> = slice.try_into(); - let Ok(bytes) = arr else { - return Err(CoconutError::UnexpectedArrayLength { - typ: "G1Projective".to_string(), - received, - expected: 48, - }); - }; - - let maybe_g1 = G1Affine::from_compressed(&bytes); - if maybe_g1.is_none().into() { - Err(CoconutError::G1ProjectiveDeserializationFailure) - } else { - // safety: this unwrap is fine as we've just checked the element is not none - #[allow(clippy::unwrap_used)] - Ok(maybe_g1.unwrap().into()) - } - } -} - -impl Base58 for G1Projective {} diff --git a/common/nymcoconut/src/utils.rs b/common/nymcoconut/src/utils.rs deleted file mode 100644 index d12707408c..0000000000 --- a/common/nymcoconut/src/utils.rs +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use core::iter::Sum; -use core::ops::Mul; - -use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; -use bls12_381::{G1Affine, G1Projective, G2Affine, G2Projective, Scalar}; -use ff::Field; - -use crate::error::{CoconutError, Result}; -use crate::scheme::setup::Parameters; -use crate::scheme::SignerIndex; - -pub struct Polynomial { - coefficients: Vec, -} - -impl Polynomial { - // for polynomial of degree n, we generate n+1 values - // (for example for degree 1, like y = x + 2, we need [2,1]) - pub fn new_random(params: &Parameters, degree: u64) -> Self { - Polynomial { - coefficients: params.n_random_scalars((degree + 1) as usize), - } - } - - /// Evaluates the polynomial at point x. - pub fn evaluate(&self, x: &Scalar) -> Scalar { - if self.coefficients.is_empty() { - Scalar::zero() - // if x is zero then we can ignore most of the expensive computation and - // just return the last term of the polynomial - } else if x.is_zero().into() { - // we checked that coefficients are not empty so unwrap here is fine - #[allow(clippy::unwrap_used)] - *self.coefficients.first().unwrap() - } else { - self.coefficients - .iter() - .enumerate() - // coefficient[n] * x ^ n - .map(|(i, coefficient)| coefficient * x.pow(&[i as u64, 0, 0, 0])) - .sum() - } - } -} - -#[inline] -fn generate_lagrangian_coefficients_at_origin(points: &[u64]) -> Vec { - let x = Scalar::zero(); - - points - .iter() - .enumerate() - .map(|(i, point_i)| { - let mut numerator = Scalar::one(); - let mut denominator = Scalar::one(); - let xi = Scalar::from(*point_i); - - for (j, point_j) in points.iter().enumerate() { - if j != i { - let xj = Scalar::from(*point_j); - - // numerator = (x - xs[0]) * ... * (x - xs[j]), j != i - numerator *= x - xj; - - // denominator = (xs[i] - x[0]) * ... * (xs[i] - x[j]), j != i - denominator *= xi - xj; - } - } - // numerator / denominator - numerator * denominator.invert().unwrap() - }) - .collect() -} - -/// Performs a Lagrange interpolation at the origin for a polynomial defined by `points` and `values`. -/// It can be used for Scalars, G1 and G2 points. -pub(crate) fn perform_lagrangian_interpolation_at_origin( - points: &[SignerIndex], - values: &[T], -) -> Result -where - T: Sum, - for<'a> &'a T: Mul, -{ - if points.is_empty() || values.is_empty() { - return Err(CoconutError::Interpolation( - "Tried to perform lagrangian interpolation for an empty set of coordinates".to_string(), - )); - } - - if points.len() != values.len() { - return Err(CoconutError::Interpolation( - "Tried to perform lagrangian interpolation for an incomplete set of coordinates" - .to_string(), - )); - } - - let coefficients = generate_lagrangian_coefficients_at_origin(points); - - Ok(coefficients - .into_iter() - .zip(values.iter()) - .map(|(coeff, val)| val * coeff) - .sum()) -} - -// A temporary way of hashing particular message into G1. -// Implementation idea was taken from `threshold_crypto`: -// https://github.com/poanetwork/threshold_crypto/blob/7709462f2df487ada3bb3243060504b5881f2628/src/lib.rs#L691 -// Eventually it should get replaced by, most likely, the osswu map -// method once ideally it's implemented inside the pairing crate. - -// note: I have absolutely no idea what are the correct domains for those. I just used whatever -// was given in the test vectors of `Hashing to Elliptic Curves draft-irtf-cfrg-hash-to-curve-11` - -// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-J.9.1 -const G1_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_"; - -// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-K.1 -const SCALAR_HASH_DOMAIN: &[u8] = b"QUUX-V01-CS02-with-expander"; - -pub fn hash_g1>(msg: M) -> G1Projective { - >>::hash_to_curve(msg, G1_HASH_DOMAIN) -} - -pub fn hash_to_scalar>(msg: M) -> Scalar { - let mut output = vec![Scalar::zero()]; - - Scalar::hash_to_field::>( - msg.as_ref(), - SCALAR_HASH_DOMAIN, - &mut output, - ); - output[0] -} - -pub fn try_deserialize_scalar_vec( - expected_len: u64, - bytes: &[u8], - err: CoconutError, -) -> Result> { - if bytes.len() != expected_len as usize * 32 { - return Err(err); - } - - let mut out = Vec::with_capacity(expected_len as usize); - for i in 0..expected_len as usize { - // we just checked we have exactly the amount of bytes we need and thus the unwrap is fine - #[allow(clippy::unwrap_used)] - let s_bytes = bytes[i * 32..(i + 1) * 32].try_into().unwrap(); - let s = match Into::>::into(Scalar::from_bytes(&s_bytes)) { - None => return Err(err), - Some(scalar) => scalar, - }; - out.push(s) - } - - Ok(out) -} - -pub fn try_deserialize_scalar(bytes: &[u8; 32], err: CoconutError) -> Result { - Into::>::into(Scalar::from_bytes(bytes)).ok_or(err) -} - -pub fn try_deserialize_g1_projective(bytes: &[u8; 48], err: CoconutError) -> Result { - Into::>::into(G1Affine::from_compressed(bytes)) - .ok_or(err) - .map(G1Projective::from) -} - -pub fn try_deserialize_g2_projective(bytes: &[u8; 96], err: CoconutError) -> Result { - Into::>::into(G2Affine::from_compressed(bytes)) - .ok_or(err) - .map(G2Projective::from) -} - -// use core::fmt; -// #[cfg(feature = "serde")] -// use serde::de::Visitor; -// #[cfg(feature = "serde")] -// use serde::{self, Deserialize, Deserializer, Serialize, Serializer}; -// -// // #[cfg(feature = "serde")] -// #[serde(remote = "Scalar")] -// pub(crate) struct ScalarDef(pub Scalar); -// -// // #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -// -// impl Serialize for ScalarDef { -// fn serialize(&self, serializer: S) -> core::result::Result -// where -// S: Serializer, -// { -// use serde::ser::SerializeTuple; -// let mut tup = serializer.serialize_tuple(32)?; -// for byte in self.0.to_bytes().iter() { -// tup.serialize_element(byte)?; -// } -// tup.end() -// } -// } -// -// impl<'de> Deserialize<'de> for ScalarDef { -// fn deserialize(deserializer: D) -> core::result::Result -// where -// D: Deserializer<'de>, -// { -// struct ScalarVisitor; -// -// impl<'de> Visitor<'de> for ScalarVisitor { -// type Value = ScalarDef; -// -// fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { -// formatter.write_str("a 32-byte canonical bls12_381 scalar") -// } -// -// fn visit_seq(self, mut seq: A) -> core::result::Result -// where -// A: serde::de::SeqAccess<'de>, -// { -// let mut bytes = [0u8; 32]; -// for i in 0..32 { -// bytes[i] = seq -// .next_element()? -// .ok_or_else(|| serde::de::Error::invalid_length(i, &"expected 32 bytes"))?; -// } -// -// let res = Scalar::from_bytes(&bytes); -// if res.is_some().into() { -// Ok(ScalarDef(res.unwrap())) -// } else { -// Err(serde::de::Error::custom( -// &"scalar was not canonically encoded", -// )) -// } -// } -// } -// -// deserializer.deserialize_tuple(32, ScalarVisitor) -// } -// } -// -// #[cfg(feature = "serde")] -// pub(crate) struct G1ProjectiveSerdeHelper(Scalar); -// -// #[cfg(feature = "serde")] -// pub(crate) struct G2ProjectiveSerdeHelper(Scalar); - -#[cfg(test)] -mod tests { - use rand::RngCore; - - use super::*; - - #[test] - fn polynomial_evaluation() { - // y = 42 (it should be 42 regardless of x) - let poly = Polynomial { - coefficients: vec![Scalar::from(42)], - }; - - assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(1))); - assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(0))); - assert_eq!(Scalar::from(42), poly.evaluate(&Scalar::from(10))); - - // y = x + 10, at x = 2 (exp: 12) - let poly = Polynomial { - coefficients: vec![Scalar::from(10), Scalar::from(1)], - }; - - assert_eq!(Scalar::from(12), poly.evaluate(&Scalar::from(2))); - - // y = x^4 - 5x^2 + 2x - 3, at x = 3 (exp: 39) - let poly = Polynomial { - coefficients: vec![ - (-Scalar::from(3)), - Scalar::from(2), - (-Scalar::from(5)), - Scalar::zero(), - Scalar::from(1), - ], - }; - - assert_eq!(Scalar::from(39), poly.evaluate(&Scalar::from(3))); - - // empty polynomial - let poly = Polynomial { - coefficients: vec![], - }; - - // should always be 0 - assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(1))); - assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(0))); - assert_eq!(Scalar::from(0), poly.evaluate(&Scalar::from(10))); - } - - #[test] - fn performing_lagrangian_scalar_interpolation_at_origin() { - // x^2 + 3 - // x, f(x): - // 1, 4, - // 2, 7, - // 3, 12, - let points = vec![1, 2, 3]; - let values = vec![Scalar::from(4), Scalar::from(7), Scalar::from(12)]; - - assert_eq!( - Scalar::from(3), - perform_lagrangian_interpolation_at_origin(&points, &values).unwrap() - ); - - // x^3 + 3x^2 - 5x + 11 - // x, f(x): - // 1, 10 - // 2, 21 - // 3, 50 - // 4, 103 - let points = vec![1, 2, 3, 4]; - let values = vec![ - Scalar::from(10), - Scalar::from(21), - Scalar::from(50), - Scalar::from(103), - ]; - - assert_eq!( - Scalar::from(11), - perform_lagrangian_interpolation_at_origin(&points, &values).unwrap() - ); - - // more points than it is required - // x^2 + x + 10 - // x, f(x) - // 1, 12 - // 2, 16 - // 3, 22 - // 4, 30 - // 5, 40 - let points = vec![1, 2, 3, 4, 5]; - let values = vec![ - Scalar::from(12), - Scalar::from(16), - Scalar::from(22), - Scalar::from(30), - Scalar::from(40), - ]; - - assert_eq!( - Scalar::from(10), - perform_lagrangian_interpolation_at_origin(&points, &values).unwrap() - ); - } - - #[test] - fn hash_g1_sanity_check() { - let mut rng = rand::thread_rng(); - let mut msg1 = [0u8; 1024]; - rng.fill_bytes(&mut msg1); - let mut msg2 = [0u8; 1024]; - rng.fill_bytes(&mut msg2); - - assert_eq!(hash_g1(msg1), hash_g1(msg1)); - assert_eq!(hash_g1(msg2), hash_g1(msg2)); - assert_ne!(hash_g1(msg1), hash_g1(msg2)); - } - - #[test] - fn hash_scalar_sanity_check() { - let mut rng = rand::thread_rng(); - let mut msg1 = [0u8; 1024]; - rng.fill_bytes(&mut msg1); - let mut msg2 = [0u8; 1024]; - rng.fill_bytes(&mut msg2); - - assert_eq!(hash_to_scalar(msg1), hash_to_scalar(msg1)); - assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2)); - assert_ne!(hash_to_scalar(msg1), hash_to_scalar(msg2)); - } -} diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 45eacae059..a6830e21c1 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -105,7 +105,6 @@ nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet nym-vesting-contract-common = { path = "../common/cosmwasm-smart-contracts/vesting-contract" } nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common", features = ["naive_float", "utoipa"] } nym-multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" } -nym-coconut = { path = "../common/nymcoconut", features = ["key-zeroize"] } nym-sphinx = { path = "../common/nymsphinx" } nym-pemstore = { path = "../common/pemstore" } nym-task = { path = "../common/task" } diff --git a/nym-api/src/ecash/dkg/controller/keys.rs b/nym-api/src/ecash/dkg/controller/keys.rs index 474d6ab0b1..bfbaa8c28d 100644 --- a/nym-api/src/ecash/dkg/controller/keys.rs +++ b/nym-api/src/ecash/dkg/controller/keys.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::ecash::client::Client; -use crate::ecash::keys::{KeyPairWithEpoch, LegacyCoconutKeyWithEpoch}; +use crate::ecash::keys::KeyPairWithEpoch; use crate::support::{config, nyxd}; use anyhow::{anyhow, bail, Context}; use nym_coconut_dkg_common::types::{EpochId, EpochState}; @@ -43,24 +43,12 @@ pub(crate) fn load_ecash_keypair_if_exists( return Ok(None); } - // first attempt to load ecash keys directly, - // if that fails fallback to coconut keys and perform migration if let Ok(ecash_key) = nym_pemstore::load_key::(&config.storage_paths.ecash_key_path) { return Ok(Some(ecash_key)); } - if let Ok(legacy_coconut_key) = - nym_pemstore::load_key::(&config.storage_paths.ecash_key_path) - { - let migrated_key: KeyPairWithEpoch = legacy_coconut_key.into(); - nym_pemstore::store_key(&migrated_key, &config.storage_paths.ecash_key_path) - .context("migrated key storage failure")?; - - return Ok(Some(migrated_key)); - } - bail!("ecash key load failure") } diff --git a/nym-api/src/ecash/keys/mod.rs b/nym-api/src/ecash/keys/mod.rs index 87d01865e2..bf315f8b1a 100644 --- a/nym-api/src/ecash/keys/mod.rs +++ b/nym-api/src/ecash/keys/mod.rs @@ -24,23 +24,6 @@ pub struct KeyPairWithEpoch { pub(crate) issued_for_epoch: EpochId, } -impl From for KeyPairWithEpoch { - fn from(value: LegacyCoconutKeyWithEpoch) -> Self { - let (x, ys) = value.secret_key.hazmat_to_raw(); - let sk = nym_compact_ecash::SecretKeyAuth::create_from_raw(x, ys); - - KeyPairWithEpoch { - keys: sk.into(), - issued_for_epoch: value.issued_for_epoch, - } - } -} - -pub struct LegacyCoconutKeyWithEpoch { - pub(crate) secret_key: nym_coconut::SecretKey, - pub(crate) issued_for_epoch: EpochId, -} - impl KeyPairWithEpoch { pub(crate) fn new(keys: nym_compact_ecash::KeyPairAuth, issued_for_epoch: EpochId) -> Self { KeyPairWithEpoch { diff --git a/nym-api/src/ecash/keys/persistence.rs b/nym-api/src/ecash/keys/persistence.rs index 976be0dfd6..b45b8a7e2d 100644 --- a/nym-api/src/ecash/keys/persistence.rs +++ b/nym-api/src/ecash/keys/persistence.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::ecash::keys::{KeyPairWithEpoch, LegacyCoconutKeyWithEpoch}; +use crate::ecash::keys::KeyPairWithEpoch; use nym_coconut_dkg_common::types::EpochId; use nym_compact_ecash::{error::CompactEcashError, scheme::keygen::SecretKeyAuth, KeyPairAuth}; use nym_pemstore::traits::PemStorableKey; @@ -41,37 +41,3 @@ impl PemStorableKey for KeyPairWithEpoch { }) } } - -impl PemStorableKey for LegacyCoconutKeyWithEpoch { - // that's not the best error for this, but it felt like an overkill to define a dedicated struct just for this purpose - type Error = nym_coconut::CoconutError; - - fn pem_type() -> &'static str { - "COCONUT KEY WITH EPOCH" - } - - fn to_bytes(&self) -> Vec { - let mut bytes = self.issued_for_epoch.to_be_bytes().to_vec(); - bytes.append(&mut self.secret_key.to_bytes()); - bytes - } - - fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() <= mem::size_of::() { - return Err(nym_coconut::CoconutError::DeserializationMinLength { - min: mem::size_of::(), - actual: bytes.len(), - }); - } - let epoch_id = EpochId::from_be_bytes([ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], - ]); - - let sk = nym_coconut::SecretKey::from_bytes(&bytes[mem::size_of::()..])?; - - Ok(LegacyCoconutKeyWithEpoch { - secret_key: sk, - issued_for_epoch: epoch_id, - }) - } -} diff --git a/nym-credential-proxy/nym-credential-proxy-requests/src/client.rs b/nym-credential-proxy/nym-credential-proxy-requests/src/client.rs index b168bca4cb..c55dd4f914 100644 --- a/nym-credential-proxy/nym-credential-proxy-requests/src/client.rs +++ b/nym-credential-proxy/nym-credential-proxy-requests/src/client.rs @@ -145,30 +145,4 @@ impl NymVpnApiClient for VpnApiClient { parse_response(res, false).await } - - // async fn get_bandwidth_voucher_blinded_shares( - // &self, - // blind_sign_request: BlindSignRequest, - // ) -> Result { - // let req = self.inner.create_post_request( - // &["/api", "/v1", "/bandwidth-voucher", "/obtain"], - // NO_PARAMS, - // &BandwidthVoucherRequest { blind_sign_request }, - // ); - // - // let fut = req.bearer_auth(&self.bearer_token).send(); - // - // // the only reason for that target lock is so that I could call this method from an ephemeral test - // // running in non-wasm mode (since I wanted to use tokio) - // - // #[cfg(target_arch = "wasm32")] - // let res = wasmtimer::tokio::timeout(std::time::Duration::from_secs(5), fut) - // .await - // .map_err(|_timeout| HttpClientError::RequestTimeout)??; - // - // #[cfg(not(target_arch = "wasm32"))] - // let res = fut.await?; - // - // parse_response(res, false).await - // } } diff --git a/nym-credential-proxy/nym-credential-proxy/src/http/router/api/v1/openapi.rs b/nym-credential-proxy/nym-credential-proxy/src/http/router/api/v1/openapi.rs index 812392056a..d2ede701cd 100644 --- a/nym-credential-proxy/nym-credential-proxy/src/http/router/api/v1/openapi.rs +++ b/nym-credential-proxy/nym-credential-proxy/src/http/router/api/v1/openapi.rs @@ -9,48 +9,6 @@ use utoipa::openapi::security::{Http, HttpAuthScheme, SecurityScheme}; use utoipa::{Modify, OpenApi}; use utoipa_swagger_ui::SwaggerUi; -/* -#[derive(OpenApi)] -#[openapi( - info(title = "Nym VPN Api"), - paths( - api::v1::freepass::generate_freepass, - api::v1::bandwidth_voucher::obtain_bandwidth_voucher_shares, - api::v1::bandwidth_voucher::obtain_async_bandwidth_voucher_shares, - api::v1::bandwidth_voucher::current_deposit, - api::v1::bandwidth_voucher::prehashed_public_attributes, - api::v1::bandwidth_voucher::partial_verification_keys, - api::v1::bandwidth_voucher::master_verification_key, - api::v1::bandwidth_voucher::current_epoch, - api::v1::bandwidth_voucher::shares::query_for_shares_by_id, - ), - components( - schemas( - api::Output, - api::OutputParams, - api_requests::v1::ErrorResponse, - api_requests::v1::freepass::models::FreepassCredentialResponse, - api_requests::v1::freepass::models::FreepassQueryParams, - api_requests::v1::bandwidth_voucher::models::DepositResponse, - api_requests::v1::bandwidth_voucher::models::AttributesResponse, - api_requests::v1::bandwidth_voucher::models::BandwidthVoucherResponse, - api_requests::v1::bandwidth_voucher::models::BandwidthVoucherAsyncResponse, - api_requests::v1::bandwidth_voucher::models::PartialVerificationKeysResponse, - api_requests::v1::bandwidth_voucher::models::CurrentEpochResponse, - api_requests::v1::bandwidth_voucher::models::CredentialShare, - api_requests::v1::bandwidth_voucher::models::PartialVerificationKey, - api_requests::v1::bandwidth_voucher::models::MasterVerificationKeyResponse, - api_requests::v1::bandwidth_voucher::models::BandwidthVoucherAsyncRequest, - api_requests::v1::bandwidth_voucher::models::BandwidthVoucherRequest, - api_requests::v1::bandwidth_voucher::models::BlindSignRequestJsonSchemaWrapper - ), - responses(RequestError), - ), - modifiers(&SecurityAddon), -)] -pub(crate) struct ApiDoc; - */ - #[derive(OpenApi)] #[openapi( info(title = "Nym Credential Proxy Api"), diff --git a/wasm/zknym-lib/Cargo.toml b/wasm/zknym-lib/Cargo.toml index 75a203b727..572e26c6bd 100644 --- a/wasm/zknym-lib/Cargo.toml +++ b/wasm/zknym-lib/Cargo.toml @@ -31,7 +31,6 @@ rand = { workspace = true } nym-bin-common = { path = "../../common/bin-common" } -nym-coconut = { path = "../../common/nymcoconut", features = ["key-zeroize"] } nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" } nym-credentials = { path = "../../common/credentials" } nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } diff --git a/wasm/zknym-lib/src/error.rs b/wasm/zknym-lib/src/error.rs index 37c49aa672..f232daddf3 100644 --- a/wasm/zknym-lib/src/error.rs +++ b/wasm/zknym-lib/src/error.rs @@ -7,12 +7,6 @@ use wasm_utils::wasm_error; #[derive(Debug, Error)] pub enum ZkNymError { - #[error("[coconut] cryptographic failure: {source}")] - CoconutFailure { - #[from] - source: nym_coconut::CoconutError, - }, - #[error("[ecash] cryptographic failure: {source}")] EcashFailure { #[from] diff --git a/wasm/zknym-lib/src/generic_scheme/coconut/mod.rs b/wasm/zknym-lib/src/generic_scheme/coconut/mod.rs deleted file mode 100644 index 5ac1b00dd7..0000000000 --- a/wasm/zknym-lib/src/generic_scheme/coconut/mod.rs +++ /dev/null @@ -1,338 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use super::coconut::types::{ - BlindSignRequestData, BlindSignRequestWrapper, BlindedCredentialWrapper, - CredentialShareWrapper, CredentialWrapper, KeyPairWrapper, ParametersWrapper, ScalarsWrapper, - VerificationKeyShareWrapper, VerificationKeyWrapper, VerifyCredentialRequestWrapper, -}; -use crate::error::ZkNymError; -use crate::GLOBAL_COCONUT_PARAMS; -use nym_coconut::{hash_to_scalar, Parameters, SignerIndex}; -use serde::{Deserialize, Serialize}; -use std::sync::OnceLock; -use tsify::Tsify; -use wasm_bindgen::prelude::wasm_bindgen; - -pub mod types; - -// works under the assumption of having 4 attributes in the underlying credential(s) -const DEFAULT_ATTRIBUTES: u32 = 4; - -pub fn default_bandwidth_credential_params() -> &'static Parameters { - static BANDWIDTH_CREDENTIAL_PARAMS: OnceLock = OnceLock::new(); - BANDWIDTH_CREDENTIAL_PARAMS.get_or_init(|| Parameters::new(DEFAULT_ATTRIBUTES).unwrap()) -} - -// attempt to extract appropriate system parameters in the following order: -// 1. attempt to get explicit provided value -// 2. then try a globally set value -// 3. finally fallback to sane default: the bandwidth credential params -pub(crate) fn get_params(explicit_params: &Option) -> &Parameters { - if let Some(explicit) = explicit_params.as_ref() { - return explicit; - } - - if let Some(global) = GLOBAL_COCONUT_PARAMS.get() { - return global; - } - - default_bandwidth_credential_params() -} - -#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize, Default)] -#[tsify(into_wasm_abi, from_wasm_abi)] -#[serde(rename_all = "camelCase")] -pub struct SetupOpts { - #[tsify(optional)] - pub num_attributes: Option, - - #[tsify(optional)] - pub set_global: Option, -} - -#[wasm_bindgen(js_name = "coconutSetup")] -pub fn setup(opts: SetupOpts) -> Result { - let num_attributes = opts.num_attributes.unwrap_or(DEFAULT_ATTRIBUTES); - - let params = nym_coconut::setup(num_attributes)?; - - if let Some(true) = opts.set_global { - GLOBAL_COCONUT_PARAMS - .set(params.clone()) - .map_err(|_| ZkNymError::GlobalParamsAlreadySet)?; - } - - Ok(params.into()) -} - -#[wasm_bindgen(js_name = "coconutKeygen")] -pub fn keygen(parameters: Option) -> KeyPairWrapper { - let params = get_params(¶meters); - nym_coconut::keygen(params).into() -} - -#[derive(Tsify, Debug, Clone, Copy, Serialize, Deserialize)] -#[tsify(into_wasm_abi, from_wasm_abi)] -#[serde(rename_all = "camelCase")] -pub struct TtpKeygenOpts { - pub threshold: u64, - - pub authorities: u64, -} - -#[wasm_bindgen(js_name = "coconutTtpKeygen")] -pub fn ttp_keygen( - opts: TtpKeygenOpts, - parameters: Option, -) -> Result, ZkNymError> { - let params = get_params(¶meters); - - let keys = nym_coconut::ttp_keygen(params, opts.threshold, opts.authorities)?; - Ok(keys.into_iter().map(Into::into).collect()) -} - -#[wasm_bindgen(js_name = "coconutSignSimple")] -pub fn sign_simple( - attributes: Vec, - keys: &KeyPairWrapper, -) -> Result { - let public_attributes = attributes - .into_iter() - .map(hash_to_scalar) - .collect::>(); - let attributes_ref = public_attributes.iter().collect::>(); - - nym_coconut::sign(keys.secret_key(), &attributes_ref) - .map(Into::into) - .map_err(Into::into) -} - -#[wasm_bindgen(js_name = "coconutPrepareBlindSign")] -pub fn prepare_blind_sign( - private_attributes: Vec, - public_attributes: Vec, - parameters: Option, -) -> Result { - let params = get_params(¶meters); - - let public_attributes = public_attributes - .into_iter() - .map(hash_to_scalar) - .collect::>(); - let public_attributes_ref = public_attributes.iter().collect::>(); - - let private_attributes = private_attributes - .into_iter() - .map(hash_to_scalar) - .collect::>(); - let private_attributes_ref = private_attributes.iter().collect::>(); - let (pedersen_commitments_openings, blind_sign_request) = - nym_coconut::prepare_blind_sign(params, &private_attributes_ref, &public_attributes_ref)?; - - Ok(BlindSignRequestData { - blind_sign_request, - pedersen_commitments_openings, - }) -} - -#[wasm_bindgen(js_name = "coconutBlindSign")] -pub fn blind_sign( - keys: &KeyPairWrapper, - blind_sign_request: &BlindSignRequestWrapper, - public_attributes: Vec, - parameters: Option, -) -> Result { - let params = get_params(¶meters); - - let public_attributes = public_attributes - .into_iter() - .map(hash_to_scalar) - .collect::>(); - let public_attributes_ref = public_attributes.iter().collect::>(); - - nym_coconut::blind_sign( - params, - keys.secret_key(), - blind_sign_request, - &public_attributes_ref, - ) - .map(Into::into) - .map_err(Into::into) -} - -#[wasm_bindgen(js_name = "coconutUnblindSignatureShare")] -pub fn unblind_signature_share( - blinded_signature: &BlindedCredentialWrapper, - partial_verification_key: &VerificationKeyWrapper, - pedersen_commitments_openings: &ScalarsWrapper, -) -> CredentialWrapper { - BlindedCredentialWrapper::unblind( - blinded_signature, - partial_verification_key, - pedersen_commitments_openings, - ) -} - -#[wasm_bindgen(js_name = "coconutUnblindAndVerifySignatureShare")] -pub fn unblind_and_verify_signature_share( - blinded_signature: &BlindedCredentialWrapper, - partial_verification_key: &VerificationKeyWrapper, - request: &BlindSignRequestData, - private_attributes: Vec, - public_attributes: Vec, - parameters: Option, -) -> Result { - BlindedCredentialWrapper::unblind_and_verify( - blinded_signature, - partial_verification_key, - request, - private_attributes, - public_attributes, - parameters, - ) -} - -#[wasm_bindgen(js_name = "coconutAggregateSignatureShares")] -pub fn aggregate_signature_shares( - shares: Vec, -) -> Result { - let shares = shares.into_iter().map(Into::into).collect::>(); - - nym_coconut::aggregate_signature_shares(&shares) - .map(Into::into) - .map_err(Into::into) -} - -#[wasm_bindgen(js_name = "coconutAggregateSignatureSharesAndVerify")] -pub fn aggregate_signature_shares_and_verify( - verification_key: &VerificationKeyWrapper, - parameters: Option, - private_attributes: Vec, - public_attributes: Vec, - shares: Vec, -) -> Result { - let params = get_params(¶meters); - - let public_attributes = public_attributes - .into_iter() - .map(hash_to_scalar) - .collect::>(); - - let private_attributes = private_attributes - .into_iter() - .map(hash_to_scalar) - .collect::>(); - - let attributes = private_attributes - .iter() - .chain(public_attributes.iter()) - .collect::>(); - let shares = shares.into_iter().map(Into::into).collect::>(); - - nym_coconut::aggregate_signature_shares_and_verify( - params, - verification_key, - &attributes, - &shares, - ) - .map(Into::into) - .map_err(Into::into) -} - -#[wasm_bindgen(js_name = "coconutAggregateVerificationKeyShares")] -pub fn aggregate_verification_key_shares( - shares: Vec, -) -> Result { - let shares = shares.into_iter().map(Into::into).collect::>(); - - nym_coconut::aggregate_key_shares(&shares) - .map(Into::into) - .map_err(Into::into) -} - -#[wasm_bindgen(js_name = "coconutAggregateVerificationKeys")] -pub fn aggregate_verification_keys( - keys: Vec, - indices: Vec, -) -> Result { - let keys = keys.into_iter().map(Into::into).collect::>(); - - nym_coconut::aggregate_verification_keys(&keys, Some(&indices)) - .map(Into::into) - .map_err(Into::into) -} - -#[wasm_bindgen(js_name = "coconutProveBandwidthCredential")] -pub fn prove_bandwidth_credential( - verification_key: &VerificationKeyWrapper, - credential: &CredentialWrapper, - serial_number: String, - binding_number: String, - parameters: Option, -) -> Result { - let params = get_params(¶meters); - - nym_coconut::prove_bandwidth_credential( - params, - verification_key, - credential, - &hash_to_scalar(serial_number), - &hash_to_scalar(binding_number), - ) - .map(Into::into) - .map_err(Into::into) -} - -#[wasm_bindgen(js_name = "coconutVerifyCredential")] -pub fn verify_credential( - verification_key: &VerificationKeyWrapper, - verification_request: &VerifyCredentialRequestWrapper, - public_attributes: Vec, - parameters: Option, -) -> bool { - let params = get_params(¶meters); - - let public_attributes = public_attributes - .into_iter() - .map(hash_to_scalar) - .collect::>(); - let attributes_ref = public_attributes.iter().collect::>(); - - nym_coconut::verify_credential( - params, - verification_key, - verification_request, - &attributes_ref, - ) -} - -#[wasm_bindgen(js_name = "coconutVerifySimple")] -pub fn verify_simple( - verification_key: &VerificationKeyWrapper, - attributes: Vec, - credential: &CredentialWrapper, - parameters: Option, -) -> bool { - let params = get_params(¶meters); - - let public_attributes = attributes - .into_iter() - .map(hash_to_scalar) - .collect::>(); - let attributes_ref = public_attributes.iter().collect::>(); - - nym_coconut::verify(params, verification_key, &attributes_ref, credential) -} - -#[wasm_bindgen(js_name = "coconutSimpleRandomiseCredential")] -pub fn simple_randomise_credential( - credential: &CredentialWrapper, - parameters: Option, -) -> CredentialWrapper { - let params = get_params(¶meters); - - CredentialWrapper { - inner: credential.inner.randomise_simple(params), - } -} diff --git a/wasm/zknym-lib/src/generic_scheme/coconut/types.rs b/wasm/zknym-lib/src/generic_scheme/coconut/types.rs deleted file mode 100644 index 875de418cd..0000000000 --- a/wasm/zknym-lib/src/generic_scheme/coconut/types.rs +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::error::ZkNymError; -use crate::{data_pointer_clone, wasm_wrapper, wasm_wrapper_bs58}; -use nym_coconut::{ - hash_to_scalar, Base58, BlindSignRequest, BlindedSignature, KeyPair, Parameters, Scalar, - SecretKey, Signature, SignatureShare, SignerIndex, VerificationKey, VerificationKeyShare, - VerifyCredentialRequest, -}; -use serde::{Deserialize, Serialize}; -use std::ops::Deref; -use std::str::FromStr; -use tsify::Tsify; -use wasm_bindgen::prelude::wasm_bindgen; -use zeroize::{Zeroize, ZeroizeOnDrop}; - -wasm_wrapper!(Parameters, ParametersWrapper); -wasm_wrapper_bs58!(Signature, CredentialWrapper); -wasm_wrapper_bs58!(BlindedSignature, BlindedCredentialWrapper); -wasm_wrapper!(SignatureShare, CredentialShareWrapper); -wasm_wrapper_bs58!(Scalar, ScalarWrapper); - -wasm_wrapper!(KeyPair, KeyPairWrapper); -wasm_wrapper!(SecretKey, SecretKeyWrapper); -wasm_wrapper!(BlindSignRequest, BlindSignRequestWrapper); -wasm_wrapper_bs58!(VerificationKey, VerificationKeyWrapper); -wasm_wrapper_bs58!(VerifyCredentialRequest, VerifyCredentialRequestWrapper); -wasm_wrapper!(VerificationKeyShare, VerificationKeyShareWrapper); - -data_pointer_clone!(VerificationKeyShareWrapper); -data_pointer_clone!(CredentialShareWrapper); -data_pointer_clone!(BlindSignRequestWrapper); - -#[wasm_bindgen] -impl BlindedCredentialWrapper { - pub fn unblind( - &self, - partial_verification_key: &VerificationKeyWrapper, - pedersen_commitments_openings: &ScalarsWrapper, - ) -> CredentialWrapper { - self.inner - .unblind(partial_verification_key, pedersen_commitments_openings) - .into() - } - - #[wasm_bindgen(js_name = "unblindAndVerify")] - pub fn unblind_and_verify( - &self, - partial_verification_key: &VerificationKeyWrapper, - request: &BlindSignRequestData, - private_attributes: Vec, - public_attributes: Vec, - parameters: Option, - ) -> Result { - let params = super::get_params(¶meters); - - let public_attributes = public_attributes - .into_iter() - .map(hash_to_scalar) - .collect::>(); - let public_attributes_ref = public_attributes.iter().collect::>(); - - let private_attributes = private_attributes - .into_iter() - .map(hash_to_scalar) - .collect::>(); - let private_attributes_ref = private_attributes.iter().collect::>(); - - let unblinded_signature = self.inner.unblind_and_verify( - params, - partial_verification_key, - &private_attributes_ref, - &public_attributes_ref, - &request.blind_sign_request.get_commitment_hash(), - &request.pedersen_commitments_openings, - )?; - - Ok(unblinded_signature.into()) - } -} - -#[wasm_bindgen] -impl CredentialWrapper { - #[wasm_bindgen(js_name = "intoShare")] - pub fn into_share(self, index: SignerIndex) -> CredentialShareWrapper { - CredentialShareWrapper { - inner: SignatureShare::new(self.inner, index), - } - } -} - -#[wasm_bindgen] -impl KeyPairWrapper { - #[wasm_bindgen(js_name = "verificationKey")] - pub fn verification_key(&self) -> VerificationKeyWrapper { - self.inner.verification_key().clone().into() - } - - pub fn index(&self) -> Option { - self.inner.index - } - - #[wasm_bindgen(js_name = "verificationKeyShare")] - pub fn verification_key_share(&self) -> Option { - self.inner.to_verification_key_share().map(Into::into) - } -} - -#[wasm_bindgen] -pub struct BlindSignRequestData { - pub(crate) blind_sign_request: BlindSignRequest, - pub(crate) pedersen_commitments_openings: Vec, -} - -#[wasm_bindgen] -impl BlindSignRequestData { - #[wasm_bindgen(js_name = "blindSignRequest")] - pub fn blind_sign_request(&self) -> BlindSignRequestWrapper { - self.blind_sign_request.clone().into() - } - - #[wasm_bindgen(js_name = "pedersenCommitmentsOpenings")] - pub fn pedersen_commitments_openings(&self) -> ScalarsWrapper { - ScalarsWrapper(self.pedersen_commitments_openings.clone()) - } -} - -#[wasm_bindgen] -#[derive(Zeroize, ZeroizeOnDrop)] -pub struct ScalarsWrapper(pub(crate) Vec); - -impl Deref for ScalarsWrapper { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[derive( - Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop, -)] -#[tsify(into_wasm_abi, from_wasm_abi)] -#[serde(rename_all = "camelCase")] -pub struct KeypairWrapper { - pub private_key: String, - pub public_key: String, -} - -#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)] -#[tsify(into_wasm_abi, from_wasm_abi)] -#[serde(rename_all = "camelCase")] -pub struct UnblindableShare { - pub issuer_index: u64, - pub issuer_key_bs58: String, - pub blinded_share_bs58: String, -} diff --git a/wasm/zknym-lib/src/generic_scheme/mod.rs b/wasm/zknym-lib/src/generic_scheme/mod.rs index ad786c7841..0fa97dc952 100644 --- a/wasm/zknym-lib/src/generic_scheme/mod.rs +++ b/wasm/zknym-lib/src/generic_scheme/mod.rs @@ -1,5 +1,4 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub mod coconut; pub mod ecash; diff --git a/wasm/zknym-lib/src/lib.rs b/wasm/zknym-lib/src/lib.rs index 540a386ee8..4d0b342232 100644 --- a/wasm/zknym-lib/src/lib.rs +++ b/wasm/zknym-lib/src/lib.rs @@ -7,8 +7,6 @@ // due to the code generated by Tsify #![allow(clippy::empty_docs)] -use nym_coconut::Parameters; -use std::sync::OnceLock; use wasm_bindgen::prelude::*; pub mod bandwidth_voucher; @@ -22,8 +20,6 @@ pub mod types; // I mostly got it, so I could test the whole thing end to end pub(crate) mod vpn_api_client; -pub(crate) static GLOBAL_COCONUT_PARAMS: OnceLock = OnceLock::new(); - #[wasm_bindgen(start)] // #[cfg(target_arch = "wasm32")] pub fn main() { diff --git a/wasm/zknym-lib/src/vpn_api_client/client.rs b/wasm/zknym-lib/src/vpn_api_client/client.rs index b6ac1ccbe8..846ec11b64 100644 --- a/wasm/zknym-lib/src/vpn_api_client/client.rs +++ b/wasm/zknym-lib/src/vpn_api_client/client.rs @@ -4,11 +4,9 @@ use super::NymVpnApiClientError; use crate::error::ZkNymError; use crate::vpn_api_client::types::{ - AttributesResponse, BandwidthVoucherRequest, BandwidthVoucherResponse, - MasterVerificationKeyResponse, PartialVerificationKeysResponse, + AttributesResponse, MasterVerificationKeyResponse, PartialVerificationKeysResponse, }; use async_trait::async_trait; -use nym_coconut::BlindSignRequest; pub use nym_http_api_client::Client; use nym_http_api_client::{parse_response, ApiClient, PathSegments, NO_PARAMS}; use reqwest::IntoUrl; @@ -76,11 +74,6 @@ pub trait NymVpnApiClient { ]) .await } - - async fn get_bandwidth_voucher_blinded_shares( - &self, - blind_sign_request: BlindSignRequest, - ) -> Result; } #[async_trait(?Send)] @@ -108,30 +101,4 @@ impl NymVpnApiClient for VpnApiClient { parse_response(res, false).await } - - async fn get_bandwidth_voucher_blinded_shares( - &self, - blind_sign_request: BlindSignRequest, - ) -> Result { - let req = self.inner.create_post_request( - &["/api", "/v1", "/bandwidth-voucher", "/obtain"], - NO_PARAMS, - &BandwidthVoucherRequest { blind_sign_request }, - ); - - let fut = req.bearer_auth(&self.bearer_token).send(); - - // the only reason for that target lock is so that I could call this method from an ephemeral test - // running in non-wasm mode (since I wanted to use tokio) - - #[cfg(target_arch = "wasm32")] - let res = wasmtimer::tokio::timeout(std::time::Duration::from_secs(5), fut) - .await - .map_err(|_timeout| HttpClientError::RequestTimeout)??; - - #[cfg(not(target_arch = "wasm32"))] - let res = fut.await?; - - parse_response(res, false).await - } } diff --git a/wasm/zknym-lib/src/vpn_api_client/types.rs b/wasm/zknym-lib/src/vpn_api_client/types.rs index cdb8d47534..89da067fc8 100644 --- a/wasm/zknym-lib/src/vpn_api_client/types.rs +++ b/wasm/zknym-lib/src/vpn_api_client/types.rs @@ -3,27 +3,11 @@ // just copied over from dot com repo -use nym_coconut::BlindSignRequest; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use tsify::Tsify; use uuid::Uuid; -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct BandwidthVoucherRequest { - /// base58 encoded blind sign request - pub blind_sign_request: BlindSignRequest, -} - -#[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)] -#[tsify(into_wasm_abi, from_wasm_abi)] -#[serde(rename_all = "camelCase")] -pub struct BandwidthVoucherResponse { - pub epoch_id: u64, - pub shares: Vec, -} - #[derive(Tsify, Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)] #[tsify(into_wasm_abi, from_wasm_abi)] #[serde(rename_all = "camelCase")]