diff --git a/Cargo.toml b/Cargo.toml index 8925c85a..8406d5e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,8 @@ members = [ "merlin", "bulletproofs_plus_plus", "smc_range_proof", - "short_group_sig" + "short_group_sig", + "syra" ] resolver = "2" @@ -58,4 +59,4 @@ inherits = "release" # https://doc.rust-lang.org/rustc/linker-plugin-lto.html lto = "fat" # https://doc.rust-lang.org/rustc/codegen-options/index.html#codegen-units -codegen-units = 1 \ No newline at end of file +codegen-units = 1 diff --git a/delegatable_credentials/src/protego/show/known_signer.rs b/delegatable_credentials/src/protego/show/known_signer.rs index 2fd328a7..970c1cfb 100644 --- a/delegatable_credentials/src/protego/show/known_signer.rs +++ b/delegatable_credentials/src/protego/show/known_signer.rs @@ -379,10 +379,10 @@ impl CredentialShowProtocol { if let Some(ct) = &self.ct { P1.serialize_compressed(&mut writer)?; apk.unwrap().serialize_compressed(&mut writer)?; - ct.ct.enc1.serialize_compressed(&mut writer)?; + ct.ct.encrypted.serialize_compressed(&mut writer)?; ct.com1.serialize_compressed(&mut writer)?; ct.ciphertext_rand_protocol - .challenge_contribution(P1, &ct.ct.enc2, &mut writer)?; + .challenge_contribution(P1, &ct.ct.eph_pk, &mut writer)?; } Ok(()) } @@ -603,7 +603,7 @@ impl CredentialShow { if P1 .mul(ct_proof.z1) .add(&apk.0.mul(ct_proof.ciphertext_rand_proof.response)) - .sub(ct.enc1.mul_bigint(challenge.into_bigint())) + .sub(ct.encrypted.mul_bigint(challenge.into_bigint())) .into_affine() != ct_proof.com1 { @@ -612,7 +612,7 @@ impl CredentialShow { if !ct_proof .ciphertext_rand_proof - .verify(&ct.enc2, P1, challenge) + .verify(&ct.eph_pk, P1, challenge) { return Err(DelegationError::InvalidAuditShow); } @@ -622,7 +622,7 @@ impl CredentialShow { let t3_prep = E::G2Prepared::from(ct_proof.t3); if !E::multi_pairing( - [ct.enc2, (-P1.into_group()).into_affine()], + [ct.eph_pk, (-P1.into_group()).into_affine()], [t1_prep.clone(), t3_prep.clone()], ) .is_zero() @@ -630,7 +630,7 @@ impl CredentialShow { return Err(DelegationError::InvalidAuditShow); } if !E::multi_pairing( - [ct.enc2, (-self.core.C3.into_group()).into_affine()], + [ct.eph_pk, (-self.core.C3.into_group()).into_affine()], [t2_prep.clone(), t3_prep.clone()], ) .is_zero() @@ -639,7 +639,7 @@ impl CredentialShow { } if !E::multi_pairing( [ - (-ct.enc1.into_group()).into_affine(), + (-ct.encrypted.into_group()).into_affine(), ct_proof.C6, ct_proof.C7, ], diff --git a/schnorr_pok/src/discrete_log_pairing.rs b/schnorr_pok/src/discrete_log_pairing.rs index db373476..a7034c9b 100644 --- a/schnorr_pok/src/discrete_log_pairing.rs +++ b/schnorr_pok/src/discrete_log_pairing.rs @@ -132,10 +132,10 @@ macro_rules! impl_protocol { impl_protocol!( /// Protocol for proving knowledge of discrete log in group G1, i.e. given public `Y` and `B`, prove knowledge of `A` in `e(A, B) = Y` - PokG1DiscreteLogInPairingProtocol, PokG1DiscreteLogInPairing, E::G1Affine, E::G2Affine, E::G2Prepared, pair_g2_g1 + PoKG1DiscreteLogInPairingProtocol, PoKG1DiscreteLogInPairing, E::G1Affine, E::G2Affine, E::G2Prepared, pair_g2_g1 ); -impl PokG1DiscreteLogInPairing { +impl PoKG1DiscreteLogInPairing { pub fn verify_with_randomized_pairing_checker( &self, y: &PairingOutput, @@ -149,10 +149,10 @@ impl PokG1DiscreteLogInPairing { impl_protocol!( /// Protocol for proving knowledge of discrete log in group G2, i.e. given public `Y` and `A`, prove knowledge of `B` in `e(A, B) = Y` - PokG2DiscreteLogInPairingProtocol, PokG2DiscreteLogInPairing, E::G2Affine, E::G1Affine, E::G1Prepared, pair_g1_g2 + PoKG2DiscreteLogInPairingProtocol, PoKG2DiscreteLogInPairing, E::G2Affine, E::G1Affine, E::G1Prepared, pair_g1_g2 ); -impl PokG2DiscreteLogInPairing { +impl PoKG2DiscreteLogInPairing { pub fn verify_with_randomized_pairing_checker( &self, y: &PairingOutput, @@ -261,16 +261,16 @@ mod tests { } check!( - PokG1DiscreteLogInPairingProtocol, - PokG1DiscreteLogInPairing, + PoKG1DiscreteLogInPairingProtocol, + PoKG1DiscreteLogInPairing, G1Affine, G2Affine, G2Prepared, pair_g2_g1 ); check!( - PokG2DiscreteLogInPairingProtocol, - PokG2DiscreteLogInPairing, + PoKG2DiscreteLogInPairingProtocol, + PoKG2DiscreteLogInPairing, G2Affine, G1Affine, G1Prepared, diff --git a/secret_sharing_and_dkg/src/distributed_dlog_check/maliciously_secure.rs b/secret_sharing_and_dkg/src/distributed_dlog_check/maliciously_secure.rs index 31cca4e1..6f7c93e4 100644 --- a/secret_sharing_and_dkg/src/distributed_dlog_check/maliciously_secure.rs +++ b/secret_sharing_and_dkg/src/distributed_dlog_check/maliciously_secure.rs @@ -22,8 +22,8 @@ use dock_crypto_utils::{ use schnorr_pok::{ compute_random_oracle_challenge, discrete_log_pairing::{ - PokG1DiscreteLogInPairing, PokG1DiscreteLogInPairingProtocol, PokG2DiscreteLogInPairing, - PokG2DiscreteLogInPairingProtocol, + PoKG1DiscreteLogInPairing, PoKG1DiscreteLogInPairingProtocol, PoKG2DiscreteLogInPairing, + PoKG2DiscreteLogInPairingProtocol, }, }; use serde::{Deserialize, Serialize}; @@ -372,8 +372,8 @@ impl_protocol!( ComputationShareG1, ComputationShareG1Proof, deal_secret_in_g1, - PokG1DiscreteLogInPairingProtocol, - PokG1DiscreteLogInPairing, + PoKG1DiscreteLogInPairingProtocol, + PoKG1DiscreteLogInPairing, G1Af, G2Af, pair_g2_g1 @@ -386,8 +386,8 @@ impl_protocol!( ComputationShareG2, ComputationShareG2Proof, deal_secret_in_g2, - PokG2DiscreteLogInPairingProtocol, - PokG2DiscreteLogInPairing, + PoKG2DiscreteLogInPairingProtocol, + PoKG2DiscreteLogInPairing, G2Af, G1Af, pair_g1_g2 diff --git a/syra/Cargo.toml b/syra/Cargo.toml new file mode 100644 index 00000000..8f223aad --- /dev/null +++ b/syra/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "syra" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +description = "SyRA: Sybil-Resilient Anonymous Signatures with Applications to Decentralized Identity" + +[dependencies] +ark-serialize.workspace = true +ark-ff.workspace = true +ark-ec.workspace = true +ark-std.workspace = true +digest.workspace = true +rayon = {workspace = true, optional = true} +serde.workspace = true +serde_with.workspace = true +zeroize.workspace = true +dock_crypto_utils = { version = "0.20.0", default-features = false, path = "../utils" } +schnorr_pok = { version = "0.20.0", default-features = false, path = "../schnorr_pok" } + +[dev-dependencies] +blake2.workspace = true +ark-bls12-381.workspace = true + +[features] +default = [ "parallel" ] +std = [ "ark-ff/std", "ark-ec/std", "ark-std/std", "ark-serialize/std", "dock_crypto_utils/std", "schnorr_pok/std", "serde/std"] +print-trace = [ "ark-std/print-trace", "dock_crypto_utils/print-trace" ] +parallel = [ "std", "ark-ff/parallel", "ark-ec/parallel", "ark-std/parallel", "rayon", "dock_crypto_utils/parallel", "schnorr_pok/parallel" ] \ No newline at end of file diff --git a/syra/README.md b/syra/README.md new file mode 100644 index 00000000..fdbbd931 --- /dev/null +++ b/syra/README.md @@ -0,0 +1,7 @@ + + +Implements the protocol from the paper [SyRA: Sybil-Resilient Anonymous Signatures with Applications to Decentralized Identity](https://eprint.iacr.org/2024/379) + +This will be used to generate pseudonym for low-entropy user attributes. The issuer will create "signature" for a unique user attribute and user uses this "signature" to create the pseudonym. + + diff --git a/syra/src/error.rs b/syra/src/error.rs new file mode 100644 index 00000000..bf3709eb --- /dev/null +++ b/syra/src/error.rs @@ -0,0 +1,13 @@ +use ark_serialize::SerializationError; + +#[derive(Debug)] +pub enum SyraError { + InvalidProof, + Serialization(SerializationError), +} + +impl From for SyraError { + fn from(e: SerializationError) -> Self { + Self::Serialization(e) + } +} diff --git a/syra/src/lib.rs b/syra/src/lib.rs new file mode 100644 index 00000000..84a09ba9 --- /dev/null +++ b/syra/src/lib.rs @@ -0,0 +1,11 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(non_snake_case)] + +//! Implements the protocol from the paper [SyRA: Sybil-Resilient Anonymous Signatures with Applications to Decentralized Identity](https://eprint.iacr.org/2024/379) +//! +//! This will be used to generate pseudonym for low-entropy user attributes. The issuer will create "signature" for a unique user attribute and user uses this "signature" to create the pseudonym. + +mod error; +pub mod pseudonym; +pub mod setup; +pub mod vrf; diff --git a/syra/src/pseudonym.rs b/syra/src/pseudonym.rs new file mode 100644 index 00000000..db15f2a5 --- /dev/null +++ b/syra/src/pseudonym.rs @@ -0,0 +1,478 @@ +//! Generate a pseudonym. This is generated by following the protocol described in section 4 of the paper. +//! +//! Notation +//! ```docs +//! T = e(Z, usk_hat) +//! A = e(Z, W_hat) +//! B = e(Z, C2_hat) / T +//! E = e(C2, g_hat)e(g^-1, C2_hat) +//! F = e(W, g_hat) +//! G = e(g^-1, W_hat) +//! H = e(C2, ivk_hat)e(g^-1, g_hat) +//! I = e(W, ivk) +//! J = e(C2, g_hat^-1) +//! ``` +//! +//! Following is a description of the protocol to prove `E = F^{β}.G^{α} ∧ H = I^{β}.F^{β.s}.J^s`. This is only part of the protocol but the rest is straightforward (implemention is of the full protocol). +//! Follows the notation of the paper and thus uses the multiplicative notation. +//! +//! The user creates `K_1 = F^s.G^r_1` and `K_2 = F^{β.s}.G^r_2` and uses the protocol for multiplicative relation among `K_1`, `K_2` and `E` +//! to prove that the exponents of `F` in `K_2` is the product of exponents of `F` in `K_1` and `E` while also proving the equality of those exponents +//! with the ones in `H`. Following is the detailed protocol with P and V denoting the prover and verifier respectively. +//! +//! 1. P chooses random `r_1, r_2` and computes `K_1 = F^s.G^r_1` and `K_2 = F^{β.s}.G^r_2`. Now `K_2 = E^s.G^{r_2 - α.s}`. Let `r3 = r2 - α*s` +//! 2. Now P starts executing the Schnorr protocol. It chooses random θ_1, θ_2, θ_3, R_1, R_2, R_3, R_4. +//! 3. P computes +//! T_1 = F^{θ_1}.G^{R_1} (for K_1) +//! T_2 = F^{θ_2}.G^{R_2} (for E) +//! T_3 = F^{θ_3}.G^{R_3} (for K_2) +//! T_4 = E^{θ_1}.G^{R_4} (for K_2) +//! T_5 = I^{θ_2}.F^{θ_3}.J^{θ_1} (for H) +//! 4. V gives the challenge c. +//! 5. P computes the following (s_i, t_i) and sends to V along with T_i. +//! s_1 = θ_1 + c.s +//! s_2 = θ_2 + c.β +//! s_3 = θ_3 + c.β.s +//! t_1 = R_1 + c.r_1 +//! t_2 = R_2 + c.α +//! t_3 = R_3 + c.r_2 +//! t_4 = R_4 + c(r_2 - α.s) +//! 6. V checks the following +//! T_1 == K_1^-c.F^{s_1}.G^{t_1} +//! T_2 == E^-c.F^{s_2}.G^{t_2} +//! T_3 == K_2^-c.F^{s_3}.G^{t_3} +//! T_4 == K_2^-c.E^{s_1}.G^{t_4} +//! T_5 == H^-c.I^{s_2}.F^{s_3}.J^{s_1} +//! +//! The implementation uses precomputation but if pre-computation is not done then some of these can utilize multi-pairings +//! which are more efficient. But using precomputation is faster. + +use crate::{ + error::SyraError, + setup::{PreparedIssuerPublicKey, PreparedSetupParams, PreparedUserSecretKey}, +}; +use ark_ec::{ + pairing::{Pairing, PairingOutput}, + AffineRepr, CurveGroup, +}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{io::Write, mem, ops::Neg, rand::RngCore, vec::Vec, UniformRand}; +use dock_crypto_utils::elgamal::Ciphertext as ElgamalCiphertext; +use serde_with::serde_as; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +/// Protocol to generate a pseudonym and the proof of correctness of it. +#[derive( + Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop, CanonicalSerialize, CanonicalDeserialize, +)] +pub struct PseudonymGenProtocol { + alpha: E::ScalarField, + beta: E::ScalarField, + s: E::ScalarField, + beta_times_s: E::ScalarField, + r1: E::ScalarField, + r2: E::ScalarField, + r3: E::ScalarField, + /// Pseudonym + #[zeroize(skip)] + pub T: PairingOutput, + #[zeroize(skip)] + pub C: ElgamalCiphertext, + #[zeroize(skip)] + pub C_hat: ElgamalCiphertext, + #[zeroize(skip)] + pub K1: PairingOutput, + #[zeroize(skip)] + pub K2: PairingOutput, + blinding_alpha: E::ScalarField, + blinding_beta: E::ScalarField, + blinding_s: E::ScalarField, + blinding_beta_times_s: E::ScalarField, + blinding_r1: E::ScalarField, + blinding_r2: E::ScalarField, + blinding_r3: E::ScalarField, + pub t_C1: E::G1Affine, + pub t_C1_hat: E::G2Affine, + pub t_B: PairingOutput, + pub t_E: PairingOutput, + pub t_H: PairingOutput, + pub t_K1: PairingOutput, + pub t_K2: PairingOutput, + pub t_K2_product: PairingOutput, +} + +/// This contains the pseudonym as well its proof of correctness +#[serde_as] +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct PseudonymProof { + /// Pseudonym + pub T: PairingOutput, + pub C: ElgamalCiphertext, + pub C_hat: ElgamalCiphertext, + pub K1: PairingOutput, + pub K2: PairingOutput, + pub t_C1: E::G1Affine, + pub t_C1_hat: E::G2Affine, + pub t_B: PairingOutput, + pub t_E: PairingOutput, + pub t_H: PairingOutput, + pub t_K1: PairingOutput, + pub t_K2: PairingOutput, + pub t_K2_product: PairingOutput, + pub resp_alpha: E::ScalarField, + pub resp_beta: E::ScalarField, + pub resp_s: E::ScalarField, + pub resp_beta_times_s: E::ScalarField, + pub resp_r1: E::ScalarField, + pub resp_r2: E::ScalarField, + pub resp_r3: E::ScalarField, +} + +impl PseudonymGenProtocol { + /// `Z` is the context (ctx, msg) pair mapped (hashed) to a group element + /// `s` is the user-id which was the message in the VRF and `blinding` is the randomness used for `s` in the Schnorr protocol. + /// This will be set by the caller when this is used in conjunction with another Schnorr protocol and `s` has to be + /// proved equal to the witness. + pub fn init( + rng: &mut R, + Z: E::G1Affine, + s: E::ScalarField, + blinding: Option, + user_sk: impl Into>, + issuer_pk: PreparedIssuerPublicKey, + params: impl Into>, + ) -> Self { + let user_sk = user_sk.into(); + let params = params.into(); + let alpha = E::ScalarField::rand(rng); + let beta = E::ScalarField::rand(rng); + let beta_times_s = beta * s; + let T = E::pairing(E::G1Prepared::from(&Z), user_sk.1.clone()); + let C = ElgamalCiphertext::::new_given_randomness( + &user_sk.0 .0, + &beta, + &issuer_pk.w, + ¶ms.g, + ); + let C_hat = ElgamalCiphertext::::new_given_randomness( + &user_sk.0 .1, + &alpha, + &issuer_pk.w_hat, + ¶ms.g_hat, + ); + let z_prepared = E::G1Prepared::from(Z); + let C2_prepared = E::G1Prepared::from(C.encrypted); + let C2_hat_prepared = E::G2Prepared::from(C_hat.encrypted); + let A = E::pairing(z_prepared.clone(), issuer_pk.w_hat_prepared.clone()); + let E = E::pairing(C2_prepared.clone(), params.g_hat_prepared.clone()) + - E::pairing(E::G1Prepared::from(params.g), C2_hat_prepared); + // e(C2, -g_hat) = e(-C2, g_hat) + let J = E::pairing( + E::G1Prepared::from(C.encrypted.into_group().neg()), + params.g_hat_prepared, + ); + // F, G and I are part of the precomputed public params + let F = issuer_pk.w_g_hat; + let G = issuer_pk.minus_g_w_hat; + let I = issuer_pk.w_vk; + + let r1 = E::ScalarField::rand(rng); + let r2 = E::ScalarField::rand(rng); + let r3 = r2 - (alpha * s); + // K1 = F*s + G*r1 + let K1 = F * s + G * r1; + // K2 = F * {β.s} + G*r2 = E*s + G*{r2 - α.s} + let K2 = F * beta_times_s + G * r2; + + // Create blindings for first phase of the Schnorr protocol + let blinding_alpha = E::ScalarField::rand(rng); + let blinding_beta = E::ScalarField::rand(rng); + let blinding_s = blinding.unwrap_or_else(|| E::ScalarField::rand(rng)); + let blinding_beta_times_s = E::ScalarField::rand(rng); + let blinding_r1 = E::ScalarField::rand(rng); + let blinding_r2 = E::ScalarField::rand(rng); + let blinding_r3 = E::ScalarField::rand(rng); + + // Commit to those blindings + let t_C1 = (params.g * blinding_beta).into_affine(); + let t_C1_hat = (params.g_hat * blinding_alpha).into_affine(); + let t_B = A * blinding_alpha; + let t_E = F * blinding_beta + G * blinding_alpha; + let F_bs = F * blinding_beta_times_s; + let t_H = I * blinding_beta + F_bs + J * blinding_s; + let t_K1 = F * blinding_s + G * blinding_r1; + let t_K2 = F_bs + G * blinding_r2; + let t_K2_product = E * blinding_s + G * blinding_r3; + Self { + T, + C, + C_hat, + K1, + K2, + alpha, + beta, + s, + beta_times_s, + r1, + r2, + r3, + blinding_alpha, + blinding_beta, + blinding_s, + blinding_beta_times_s, + blinding_r1, + blinding_r2, + blinding_r3, + t_C1, + t_C1_hat, + t_B, + t_E, + t_H, + t_K1, + t_K2, + t_K2_product, + } + } + + pub fn challenge_contribution( + &self, + Z: &E::G1Affine, + writer: W, + ) -> Result<(), SyraError> { + Self::compute_challenge_contribution( + Z, + &self.T, + &self.C, + &self.C_hat, + &self.K1, + &self.K2, + &self.t_C1, + &self.t_C1_hat, + &self.t_B, + &self.t_E, + &self.t_H, + &self.t_K1, + &self.t_K2, + &self.t_K2_product, + writer, + ) + } + + pub fn gen_proof(mut self, challenge: &E::ScalarField) -> PseudonymProof { + // Create responses for final phase of the Schnorr protocol + let resp_beta = self.blinding_beta + self.beta * challenge; + let resp_alpha = self.blinding_alpha + self.alpha * challenge; + let resp_s = self.blinding_s + self.s * challenge; + let resp_beta_times_s = self.blinding_beta_times_s + self.beta_times_s * challenge; + let resp_r1 = self.blinding_r1 + self.r1 * challenge; + let resp_r2 = self.blinding_r2 + self.r2 * challenge; + let resp_r3 = self.blinding_r3 + self.r3 * challenge; + PseudonymProof { + T: self.T, + C: mem::take(&mut self.C), + C_hat: mem::take(&mut self.C_hat), + K1: self.K1, + K2: self.K2, + t_C1: self.t_C1, + t_C1_hat: self.t_C1_hat, + t_B: self.t_B, + t_E: self.t_E, + t_H: self.t_H, + t_K1: self.t_K1, + t_K2: self.t_K2, + t_K2_product: self.t_K2_product, + resp_beta, + resp_alpha, + resp_s, + resp_beta_times_s, + resp_r1, + resp_r2, + resp_r3, + } + } + + pub fn compute_challenge_contribution( + Z: &E::G1Affine, + T: &PairingOutput, + C: &ElgamalCiphertext, + C_hat: &ElgamalCiphertext, + K1: &PairingOutput, + K2: &PairingOutput, + t_C1: &E::G1Affine, + t_C1_hat: &E::G2Affine, + t_B: &PairingOutput, + t_E: &PairingOutput, + t_H: &PairingOutput, + t_K1: &PairingOutput, + t_K2: &PairingOutput, + t_K2_product: &PairingOutput, + mut writer: W, + ) -> Result<(), SyraError> { + Z.serialize_compressed(&mut writer)?; + T.serialize_compressed(&mut writer)?; + C.serialize_compressed(&mut writer)?; + C_hat.serialize_compressed(&mut writer)?; + K1.serialize_compressed(&mut writer)?; + K2.serialize_compressed(&mut writer)?; + t_C1.serialize_compressed(&mut writer)?; + t_C1_hat.serialize_compressed(&mut writer)?; + t_B.serialize_compressed(&mut writer)?; + t_E.serialize_compressed(&mut writer)?; + t_H.serialize_compressed(&mut writer)?; + t_K1.serialize_compressed(&mut writer)?; + t_K2.serialize_compressed(&mut writer)?; + t_K2_product.serialize_compressed(&mut writer)?; + Ok(()) + } +} + +impl PseudonymProof { + pub fn verify( + &self, + challenge: &E::ScalarField, + Z: E::G1Affine, + issuer_pk: PreparedIssuerPublicKey, + params: impl Into>, + ) -> Result<(), SyraError> { + let params = params.into(); + let z_prepared = E::G1Prepared::from(Z); + let C2_prepared = E::G1Prepared::from(self.C.encrypted); + let C2_hat_prepared = E::G2Prepared::from(self.C_hat.encrypted); + let A = E::pairing(z_prepared.clone(), issuer_pk.w_hat_prepared.clone()); + let B = E::pairing(z_prepared.clone(), C2_hat_prepared.clone()) - self.T; + let E = E::pairing(C2_prepared.clone(), params.g_hat_prepared.clone()) + - E::pairing(E::G1Prepared::from(params.g), C2_hat_prepared); + let H = E::pairing(C2_prepared, issuer_pk.vk_prepared) - params.pairing; + // e(C2, -g_hat) = e(-C2, g_hat) + let J = E::pairing( + E::G1Prepared::from(self.C.encrypted.into_group().neg()), + params.g_hat_prepared, + ); + // F , G, I are part of the precomputed public params + let F = issuer_pk.w_g_hat; + let G = issuer_pk.minus_g_w_hat; + let I = issuer_pk.w_vk; + let minus_challenge = challenge.neg(); + + // Verify each response + if self.t_C1 != (params.g * self.resp_beta + self.C.eph_pk * minus_challenge).into() { + return Err(SyraError::InvalidProof); + } + if self.t_C1_hat + != (params.g_hat * self.resp_alpha + self.C_hat.eph_pk * minus_challenge).into() + { + return Err(SyraError::InvalidProof); + } + if self.t_B != A * self.resp_alpha + B * minus_challenge { + return Err(SyraError::InvalidProof); + } + if self.t_E != F * self.resp_beta + G * self.resp_alpha + E * minus_challenge { + return Err(SyraError::InvalidProof); + } + let F_bs = F * self.resp_beta_times_s; + if self.t_H != I * self.resp_beta + F_bs + J * self.resp_s + H * minus_challenge { + return Err(SyraError::InvalidProof); + } + if self.t_K1 != F * self.resp_s + G * self.resp_r1 + self.K1 * minus_challenge { + return Err(SyraError::InvalidProof); + } + let K2_c = self.K2 * minus_challenge; + if self.t_K2 != F_bs + G * self.resp_r2 + K2_c { + return Err(SyraError::InvalidProof); + } + if self.t_K2_product != E * self.resp_s + G * self.resp_r3 + K2_c { + return Err(SyraError::InvalidProof); + } + Ok(()) + } + + pub fn challenge_contribution( + &self, + Z: &E::G1Affine, + writer: W, + ) -> Result<(), SyraError> { + PseudonymGenProtocol::compute_challenge_contribution( + Z, + &self.T, + &self.C, + &self.C_hat, + &self.K1, + &self.K2, + &self.t_C1, + &self.t_C1_hat, + &self.t_B, + &self.t_E, + &self.t_H, + &self.t_K1, + &self.t_K2, + &self.t_K2_product, + writer, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::Instant; + + use crate::setup::{IssuerPublicKey, IssuerSecretKey, SetupParams, UserSecretKey}; + use ark_bls12_381::{Bls12_381, Fr, G1Affine}; + use ark_std::rand::{rngs::StdRng, SeedableRng}; + use blake2::Blake2b512; + use dock_crypto_utils::hashing_utils::affine_group_elem_from_try_and_incr; + use schnorr_pok::compute_random_oracle_challenge; + + #[test] + fn pseudonym() { + let mut rng = StdRng::seed_from_u64(0u64); + + let params = SetupParams::::new::(b"test"); + + // Signer's setup + let isk = IssuerSecretKey::new(&mut rng); + let ipk = IssuerPublicKey::new(&mut rng, &isk, ¶ms); + let prepared_ipk = PreparedIssuerPublicKey::new(ipk.clone(), params.clone()); + + // Signer creates user secret key + let user_id = compute_random_oracle_challenge::(b"low entropy user-id"); + let usk = UserSecretKey::new(user_id, &isk, params.clone()); + + // Verifier gives message and context to user + let context = b"test-context"; + let msg = b"test-message"; + let mut pair = vec![]; + pair.extend_from_slice(context); + pair.extend_from_slice(msg); + let Z = affine_group_elem_from_try_and_incr::(&pair); + + // User generates a pseudonym + let start = Instant::now(); + let protocol = PseudonymGenProtocol::init( + &mut rng, + Z.clone(), + user_id.clone(), + None, + &usk, + prepared_ipk.clone(), + params.clone(), + ); + let mut chal_bytes = vec![]; + protocol + .challenge_contribution(&Z, &mut chal_bytes) + .unwrap(); + let challenge_prover = compute_random_oracle_challenge::(&chal_bytes); + let proof = protocol.gen_proof(&challenge_prover); + println!("Time to create proof {:?}", start.elapsed()); + + // Verifier checks the correctness of the pseudonym + let start = Instant::now(); + let mut chal_bytes = vec![]; + proof.challenge_contribution(&Z, &mut chal_bytes).unwrap(); + let challenge_verifier = compute_random_oracle_challenge::(&chal_bytes); + proof + .verify(&challenge_verifier, Z, prepared_ipk.clone(), params.clone()) + .unwrap(); + println!("Time to verify proof {:?}", start.elapsed()); + } +} diff --git a/syra/src/setup.rs b/syra/src/setup.rs new file mode 100644 index 00000000..1045a123 --- /dev/null +++ b/syra/src/setup.rs @@ -0,0 +1,218 @@ +use crate::{ + error::SyraError, + vrf::{Output, Proof}, +}; +use ark_ec::{ + pairing::{Pairing, PairingOutput}, + AffineRepr, CurveGroup, +}; +use ark_ff::PrimeField; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{ops::Neg, rand::RngCore, vec::Vec, UniformRand}; +use digest::Digest; +use dock_crypto_utils::{affine_group_element_from_byte_slices, serde_utils::ArkObjectBytes}; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +/// System parameters +#[serde_as] +#[derive( + Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, +)] +pub struct SetupParams { + #[serde_as(as = "ArkObjectBytes")] + pub g: E::G1Affine, + #[serde_as(as = "ArkObjectBytes")] + pub g_hat: E::G2Affine, +} + +/// System parameters with precomputation +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct PreparedSetupParams { + pub g: E::G1Affine, + pub g_hat: E::G2Affine, + pub g_hat_prepared: E::G2Prepared, + /// e(g, g_hat) + pub pairing: PairingOutput, +} + +#[serde_as] +#[derive( + Clone, + PartialEq, + Eq, + Debug, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, + Zeroize, + ZeroizeOnDrop, +)] +pub struct IssuerSecretKey(#[serde_as(as = "ArkObjectBytes")] pub F); + +/// Issuer's public key +#[serde_as] +#[derive( + Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, +)] +pub struct IssuerPublicKey { + #[serde_as(as = "ArkObjectBytes")] + pub vk: E::G2Affine, + // NOTE: w and w_hat don't need to be part of the issuer's public key. These could be agreed upon between each + // pair of user and verifier and chosen such that they are random (hash string to group) + #[serde_as(as = "ArkObjectBytes")] + pub w: E::G1Affine, + #[serde_as(as = "ArkObjectBytes")] + pub w_hat: E::G2Affine, +} + +/// Issuer's public key with precomputation +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct PreparedIssuerPublicKey { + pub vk: E::G2Affine, + // NOTE: w and w_hat don't need to be part of the issuer's public key. These could be agreed upon between each + // pair of user and verifier and chosen such that they are random (hash string to group) + pub w: E::G1Affine, + pub w_hat: E::G2Affine, + pub vk_prepared: E::G2Prepared, + pub w_hat_prepared: E::G2Prepared, + /// e(w, vk) + pub w_vk: PairingOutput, + /// e(w, g_hat) + pub w_g_hat: PairingOutput, + /// -e(g, w_hat) + pub minus_g_w_hat: PairingOutput, +} + +#[serde_as] +#[derive( + Clone, + PartialEq, + Eq, + Debug, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, + Zeroize, + ZeroizeOnDrop, +)] +pub struct UserSecretKey( + pub Output, + /// (usk, usk_hat) + pub Proof, +); + +/// User's secret key with precomputation +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct PreparedUserSecretKey( + /// (usk, usk_hat) + pub Proof, + /// usk_hat prepared + pub E::G2Prepared, +); + +impl SetupParams { + pub fn new(label: &[u8]) -> Self { + let g = affine_group_element_from_byte_slices!(label, b" : g"); + let g_hat = affine_group_element_from_byte_slices!(label, b" : g_hat"); + Self { g, g_hat } + } +} + +impl From> for PreparedSetupParams { + fn from(params: SetupParams) -> Self { + let g_hat_prepared = E::G2Prepared::from(params.g_hat); + let pairing = E::pairing(params.g, params.g_hat); + Self { + g: params.g, + g_hat: params.g_hat, + g_hat_prepared, + pairing, + } + } +} + +impl IssuerSecretKey { + pub fn new(rng: &mut R) -> Self { + Self(F::rand(rng)) + } +} + +impl AsRef for IssuerSecretKey { + fn as_ref(&self) -> &F { + &self.0 + } +} + +impl IssuerPublicKey { + pub fn new( + rng: &mut R, + sk: &IssuerSecretKey, + params: &SetupParams, + ) -> Self { + let vk = (params.g_hat * sk.0).into_affine(); + let w = E::G1Affine::rand(rng); + let w_hat = E::G2Affine::rand(rng); + Self { vk, w, w_hat } + } +} + +impl AsRef for IssuerPublicKey { + fn as_ref(&self) -> &E::G2Affine { + &self.vk + } +} + +impl PreparedIssuerPublicKey { + pub fn new(pk: IssuerPublicKey, params: SetupParams) -> Self { + let vk_prepared = E::G2Prepared::from(pk.vk); + let w_hat_prepared = E::G2Prepared::from(pk.w_hat); + let w_prepared = E::G1Prepared::from(pk.w); + let w_vk = E::pairing(w_prepared.clone(), vk_prepared.clone()); + let w_g_hat = E::pairing(w_prepared, E::G2Prepared::from(params.g_hat)); + let minus_g_w_hat = E::pairing( + E::G1Prepared::from(params.g.into_group().neg()), + w_hat_prepared.clone(), + ); + Self { + vk: pk.vk, + w: pk.w, + w_hat: pk.w_hat, + vk_prepared, + w_hat_prepared, + w_vk, + w_g_hat, + minus_g_w_hat, + } + } +} + +impl UserSecretKey { + pub fn new( + user_id: E::ScalarField, + issuer_sk: &IssuerSecretKey, + params: impl Into>, + ) -> Self { + let (out, proof) = Output::generate(user_id, issuer_sk.as_ref(), params); + Self(out, proof) + } + + pub fn verify( + &self, + user_id: E::ScalarField, + issuer_pk: &IssuerPublicKey, + params: impl Into>, + ) -> Result<(), SyraError> { + self.1.verify(user_id, &self.0, issuer_pk.as_ref(), params) + } +} + +impl From<&UserSecretKey> for PreparedUserSecretKey { + fn from(sk: &UserSecretKey) -> Self { + let usk_hat_prepared = E::G2Prepared::from(sk.1 .1); + Self(sk.1.clone(), usk_hat_prepared) + } +} diff --git a/syra/src/vrf.rs b/syra/src/vrf.rs new file mode 100644 index 00000000..9f2b445a --- /dev/null +++ b/syra/src/vrf.rs @@ -0,0 +1,127 @@ +//! Asymmetric Dodis-Yampolskiy VRF + +use crate::{error::SyraError, setup::PreparedSetupParams}; +use ark_ec::{ + pairing::{Pairing, PairingOutput}, + AffineRepr, +}; +use ark_ff::{Field, Zero}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{ops::Neg, vec::Vec}; +use dock_crypto_utils::serde_utils::ArkObjectBytes; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +/// PRF output +#[serde_as] +#[derive( + Clone, + PartialEq, + Eq, + Debug, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, + Zeroize, + ZeroizeOnDrop, +)] +pub struct Output(#[serde_as(as = "ArkObjectBytes")] pub PairingOutput); + +/// Proof of correct PRF output +#[serde_as] +#[derive( + Clone, + PartialEq, + Eq, + Debug, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, + Zeroize, + ZeroizeOnDrop, +)] +pub struct Proof( + #[serde_as(as = "ArkObjectBytes")] pub E::G1Affine, + #[serde_as(as = "ArkObjectBytes")] pub E::G2Affine, +); + +impl Output { + pub fn generate<'a>( + message: E::ScalarField, + secret_key: impl Into<&'a E::ScalarField>, + params: impl Into>, + ) -> (Self, Proof) { + let params = params.into(); + let exp = (message + secret_key.into()).inverse().unwrap(); + let out = params.pairing * exp; + let proof = Proof((params.g * exp).into(), (params.g_hat * exp).into()); + (Self(out), proof) + } +} + +impl Proof { + pub fn verify<'a>( + &self, + message: E::ScalarField, + output: &Output, + public_key: impl Into<&'a E::G2Affine>, + params: impl Into>, + ) -> Result<(), SyraError> { + let params = params.into(); + let prep_0 = E::G1Prepared::from(self.0); + let prep_1 = E::G2Prepared::from(self.1); + if E::pairing(prep_0.clone(), (params.g_hat * message) + public_key.into()) + != params.pairing + { + return Err(SyraError::InvalidProof); + } + if E::pairing(prep_0.clone(), params.g_hat) != output.0 { + return Err(SyraError::InvalidProof); + } + if !E::multi_pairing( + [E::G1Prepared::from(params.g), prep_0], + [prep_1, params.g_hat.into_group().neg().into().into()], + ) + .is_zero() + { + return Err(SyraError::InvalidProof); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::time::Instant; + + use super::*; + + use crate::setup::{IssuerPublicKey, IssuerSecretKey, SetupParams}; + use ark_bls12_381::{Bls12_381, Fr}; + use ark_std::{ + rand::{rngs::StdRng, SeedableRng}, + UniformRand, + }; + use blake2::Blake2b512; + + #[test] + fn output_verify() { + let mut rng = StdRng::seed_from_u64(0u64); + + let params = SetupParams::::new::(b"test"); + let sk = IssuerSecretKey::new(&mut rng); + let pk = IssuerPublicKey::new(&mut rng, &sk, ¶ms); + + let message = Fr::rand(&mut rng); + let start = Instant::now(); + let (out, proof) = Output::generate(message.clone(), sk.as_ref(), params.clone()); + println!("Time to create VRF output {:?}", start.elapsed()); + + let start = Instant::now(); + proof.verify(message, &out, pk.as_ref(), params).unwrap(); + println!("Time to verify VRF output {:?}", start.elapsed()); + } +} diff --git a/utils/src/elgamal.rs b/utils/src/elgamal.rs index 8ccba02b..168cc67f 100644 --- a/utils/src/elgamal.rs +++ b/utils/src/elgamal.rs @@ -1,9 +1,12 @@ //! Elgamal encryption +use crate::serde_utils::ArkObjectBytes; use ark_ec::{AffineRepr, CurveGroup}; use ark_ff::PrimeField; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{ops::Neg, rand::RngCore, vec::Vec, UniformRand}; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; use zeroize::{Zeroize, ZeroizeOnDrop}; #[derive( @@ -36,12 +39,25 @@ pub fn keygen( } /// Elgamal encryption of a group element `m` -#[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] +#[serde_as] +#[derive( + Default, + Clone, + Debug, + PartialEq, + Eq, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, +)] pub struct Ciphertext { /// `m + r * pk` - pub enc1: G, + #[serde_as(as = "ArkObjectBytes")] + pub encrypted: G, /// Ephemeral public key `r * gen` - pub enc2: G, + #[serde_as(as = "ArkObjectBytes")] + pub eph_pk: G, } impl Ciphertext { @@ -53,19 +69,29 @@ impl Ciphertext { gen: &G, ) -> (Self, G::ScalarField) { let alpha = G::ScalarField::rand(rng); - let alpha_bi = alpha.into_bigint(); - let enc1 = (public_key.mul_bigint(alpha_bi) + msg).into_affine(); ( - Self { - enc1, - enc2: gen.mul_bigint(alpha_bi).into_affine(), - }, + Self::new_given_randomness(msg, &alpha, public_key, gen), alpha, ) } + /// Returns the ciphertext + pub fn new_given_randomness( + msg: &G, + randomness: &G::ScalarField, + public_key: &G, + gen: &G, + ) -> Self { + let b = randomness.into_bigint(); + let enc1 = (public_key.mul_bigint(b) + msg).into_affine(); + Self { + encrypted: enc1, + eph_pk: gen.mul_bigint(b).into_affine(), + } + } + pub fn decrypt(&self, secret_key: &G::ScalarField) -> G { - (self.enc2.mul(secret_key).neg() + self.enc1).into_affine() + (self.eph_pk.mul(secret_key).neg() + self.encrypted).into_affine() } } diff --git a/vb_accumulator/README.md b/vb_accumulator/README.md index 82ea8dd9..a6f4f160 100644 --- a/vb_accumulator/README.md +++ b/vb_accumulator/README.md @@ -1,9 +1,10 @@ -# Accumulators based on bilinear map (pairings) +# Accumulators, based on bilinear map (pairings) and without them ## vb_accumulator Dynamic Positive and Universal accumulators according to the paper: [Dynamic Universal Accumulator with Batch Update over Bilinear Groups](https://eprint.iacr.org/2020/777) + Implements - a dynamic positive accumulator [`PositiveAccumulator`], that supports membership proofs. - a dynamic universal accumulator [`UniversalAccumulator`], that supports membership and non-membership proofs. @@ -11,7 +12,7 @@ Implements These are essentially proofs of knowledge of a weak-BB signature - an alternate and more efficient protocol of zero knowledge proof of membership and non-membership based on a more efficient protocol for proving knowledge of a weak-BB signature. This isn't described in the paper. -- keyed verification proofs of membership and non-membership where the verifier knows the secret key +- keyed verification proofs of membership and non-membership where the verifier knows the secret key. Such accumulator don't need pairings Allows - single and batch updates (additions, removals or both) to the accumulators. @@ -26,13 +27,14 @@ The implementation tries to use the same variable names as the paper and thus vi ## kb_accumulator Dynamic Positive and Universal accumulators according to the paper: [Efficient Constructions of Pairing Based Accumulators](https://eprint.iacr.org/2021/638) + Implements - a dynamic positive accumulator [`KBPositiveAccumulator`], that supports membership proofs. Based on construction 2 in the paper. - a dynamic universal accumulator [`KBUniversalAccumulator`], that supports membership and non-membership proofs. Based on construction 3 in the paper - zero knowledge proofs of membership and non-membership in the accumulators. These are essentially proofs of knowledge of a BB signature and weak-BB signature. - an alternate and more efficient protocol for membership and non-membership proofs -- keyed verification proofs of membership and non-membership where the verifier knows the secret key +- keyed verification proofs of membership and non-membership where the verifier knows the secret key. Such accumulator don't need pairings Allows batch updates to the accumulator and the witness using the techniques from `vb_accumulator` diff --git a/vb_accumulator/src/lib.rs b/vb_accumulator/src/lib.rs index 76c2a60c..e1443f53 100644 --- a/vb_accumulator/src/lib.rs +++ b/vb_accumulator/src/lib.rs @@ -1,7 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(non_snake_case)] -//! # Accumulators based on bilinear map (pairings) +//! # Accumulators, based on bilinear map (pairings) and without them //! //! ## vb_accumulator //! Dynamic Positive and Universal accumulators according to the paper: [Dynamic Universal Accumulator with Batch Update over Bilinear Groups](https://eprint.iacr.org/2020/777) @@ -13,7 +13,7 @@ //! These are essentially proofs of knowledge of a weak-BB signature //! - an alternate and more efficient protocol of zero knowledge proof of membership and non-membership based on a more //! efficient protocol for proving knowledge of a weak-BB signature. This isn't described in the paper. -//! - keyed verification proofs of membership and non-membership where the verifier knows the secret key +//! - keyed verification proofs of membership and non-membership where the verifier knows the secret key. Such accumulator don't need pairings //! //! Allows //! - single and batch updates (additions, removals or both) to the accumulators. @@ -35,7 +35,7 @@ //! - zero knowledge proofs of membership and non-membership in the accumulators. These are essentially proofs of knowledge of a //! BB signature and weak-BB signature. //! - an alternate and more efficient protocol for membership and non-membership proofs -//! - keyed verification proofs of membership and non-membership where the verifier knows the secret key +//! - keyed verification proofs of membership and non-membership where the verifier knows the secret key. Such accumulator don't need pairings //! //! Allows batch updates to the accumulator and the witness using the techniques from `vb_accumulator` //!