use crate::{
error::SyraError,
setup::{IssuerSecretKey, PreparedSetupParams, SetupParams},
};
use ark_ec::pairing::{Pairing, PairingOutput};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::{io::Write, rand::RngCore, vec::Vec, UniformRand};
use schnorr_pok::discrete_log_pairing::{
PoKG1DiscreteLogInPairing, PoKG1DiscreteLogInPairingProtocol,
};
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use short_group_sig::{
weak_bb_sig::{PublicKeyG2, SignatureG1},
weak_bb_sig_pok_cdh::{PoKOfSignatureG1, PoKOfSignatureG1Protocol},
};
use zeroize::{Zeroize, ZeroizeOnDrop};
#[serde_as]
#[derive(
Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize,
)]
pub struct IssuerPublicKey<E: Pairing>(pub PublicKeyG2<E>);
#[serde_as]
#[derive(
Clone,
PartialEq,
Eq,
Debug,
CanonicalSerialize,
CanonicalDeserialize,
Serialize,
Deserialize,
Zeroize,
ZeroizeOnDrop,
)]
pub struct UserSecretKey<E: Pairing>(pub SignatureG1<E>);
impl<E: Pairing> IssuerPublicKey<E> {
pub fn new(sk: &IssuerSecretKey<E::ScalarField>, params: &SetupParams<E>) -> Self {
Self(PublicKeyG2((params.g_hat * sk.0).into()))
}
}
impl<E: Pairing> AsRef<E::G2Affine> for IssuerPublicKey<E> {
fn as_ref(&self) -> &E::G2Affine {
&self.0 .0
}
}
impl<E: Pairing> UserSecretKey<E> {
pub fn new(
user_id: &E::ScalarField,
issuer_sk: &IssuerSecretKey<E::ScalarField>,
params: &SetupParams<E>,
) -> Self {
Self(SignatureG1::new(user_id, issuer_sk, params))
}
pub fn verify(
&self,
user_id: E::ScalarField,
issuer_pk: &IssuerPublicKey<E>,
params: impl Into<PreparedSetupParams<E>>,
) -> Result<(), SyraError> {
let params = params.into();
self.0
.verify_given_destructured_params_with_pairing(
&user_id,
&issuer_pk.0,
params.g_hat,
params.pairing,
)
.map_err(|e| e.into())
}
}
impl<E: Pairing> AsRef<E::G1Affine> for UserSecretKey<E> {
fn as_ref(&self) -> &E::G1Affine {
&self.0 .0
}
}
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
pub struct PseudonymGenProtocol<E: Pairing> {
pub pok_usk: PoKG1DiscreteLogInPairingProtocol<E>,
pub pok_usk_bb_sig: PoKOfSignatureG1Protocol<E>,
#[zeroize(skip)]
pub T: PairingOutput<E>,
#[zeroize(skip)]
pub T_prime: PairingOutput<E>,
#[zeroize(skip)]
pub J: PairingOutput<E>,
}
#[serde_as]
#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)]
pub struct PseudonymProof<E: Pairing> {
pub pok_usk: PoKG1DiscreteLogInPairing<E>,
pub pok_usk_bb_sig: PoKOfSignatureG1<E>,
pub T: PairingOutput<E>,
pub J: PairingOutput<E>,
}
impl<E: Pairing> PseudonymGenProtocol<E> {
pub fn init<R: RngCore>(
rng: &mut R,
Z: E::G2Affine,
s: E::ScalarField,
blinding: Option<E::ScalarField>,
user_sk: UserSecretKey<E>,
params: &SetupParams<E>,
) -> Self {
let T = E::pairing(E::G1Prepared::from(user_sk.0 .0), E::G2Prepared::from(Z));
let r = E::ScalarField::rand(rng);
let r_blinding = E::ScalarField::rand(rng);
let msg_blinding = blinding.unwrap_or_else(|| E::ScalarField::rand(rng));
let pok_usk = PoKG1DiscreteLogInPairingProtocol::init(
user_sk.0 .0.clone(),
E::G1Affine::rand(rng),
&Z,
);
let pok_usk_bb_sig = PoKOfSignatureG1Protocol::init_with_given_randomness(
r,
msg_blinding,
r_blinding,
user_sk,
s,
¶ms.g,
);
let T_prime = T * r;
let J = T * r_blinding;
Self {
pok_usk,
pok_usk_bb_sig,
T,
T_prime,
J,
}
}
pub fn challenge_contribution<W: Write>(
&self,
Z: &E::G2Affine,
issuer_pk: &IssuerPublicKey<E>,
g: &E::G1Affine,
mut writer: W,
) -> Result<(), SyraError> {
issuer_pk.serialize_compressed(&mut writer)?;
self.J.serialize_compressed(&mut writer)?;
self.pok_usk
.challenge_contribution(&Z, &self.T, &mut writer)?;
self.pok_usk_bb_sig.challenge_contribution(g, &mut writer)?;
Ok(())
}
pub fn gen_proof(self, challenge: &E::ScalarField) -> PseudonymProof<E> {
let pok_usk = self.pok_usk.clone().gen_proof(challenge);
let pok_usk_bb_sig = self.pok_usk_bb_sig.clone().gen_proof(challenge);
PseudonymProof {
pok_usk,
pok_usk_bb_sig,
T: self.T,
J: self.J,
}
}
}
impl<E: Pairing> PseudonymProof<E> {
pub fn verify(
&self,
challenge: &E::ScalarField,
Z: E::G2Affine,
issuer_pk: &IssuerPublicKey<E>,
params: impl Into<PreparedSetupParams<E>>,
) -> Result<(), SyraError> {
if !self.pok_usk.verify(&self.T, Z, challenge) {
return Err(SyraError::InvalidProof);
}
let T_prime = E::pairing(self.pok_usk_bb_sig.A_prime, Z);
if (T_prime * challenge) + self.J
!= self.T * self.pok_usk_bb_sig.sc.as_ref().unwrap().response1
{
return Err(SyraError::InvalidProof);
}
let params = params.into();
self.pok_usk_bb_sig
.verify(
challenge,
issuer_pk.0 .0.clone(),
¶ms.g,
params.g_hat_prepared,
)
.map_err(|e| e.into())
}
pub fn challenge_contribution<W: Write>(
&self,
Z: &E::G2Affine,
issuer_pk: &IssuerPublicKey<E>,
g: &E::G1Affine,
mut writer: W,
) -> Result<(), SyraError> {
issuer_pk.serialize_compressed(&mut writer)?;
self.J.serialize_compressed(&mut writer)?;
self.pok_usk
.challenge_contribution(&Z, &self.T, &mut writer)?;
self.pok_usk_bb_sig.challenge_contribution(g, &mut writer)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use ark_bls12_381::{Bls12_381, Fr, G2Affine};
use ark_std::rand::{prelude::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;
use std::time::Instant;
#[test]
fn pseudonym() {
let mut rng = StdRng::seed_from_u64(0u64);
let params = SetupParams::<Bls12_381>::new::<Blake2b512>(b"test");
let prepared_params = PreparedSetupParams::<Bls12_381>::from(params.clone());
let isk = IssuerSecretKey::new(&mut rng);
let ipk = IssuerPublicKey::new(&isk, ¶ms);
let user_id = compute_random_oracle_challenge::<Fr, Blake2b512>(b"low entropy user-id");
let start = Instant::now();
let usk = UserSecretKey::new(&user_id, &isk, ¶ms);
println!("Time to create user secret key {:?}", start.elapsed());
let start = Instant::now();
usk.verify(user_id, &ipk, prepared_params.clone()).unwrap();
println!("Time to verify user secret key {:?}", start.elapsed());
let context = b"test-context";
let msg = b"test-message";
let Z = affine_group_elem_from_try_and_incr::<G2Affine, Blake2b512>(context);
let start = Instant::now();
let protocol =
PseudonymGenProtocol::init(&mut rng, Z.clone(), user_id.clone(), None, usk, ¶ms);
let mut chal_bytes = vec![];
protocol
.challenge_contribution(&Z, &ipk, ¶ms.g, &mut chal_bytes)
.unwrap();
chal_bytes.extend_from_slice(msg);
let challenge_prover = compute_random_oracle_challenge::<Fr, Blake2b512>(&chal_bytes);
let proof = protocol.gen_proof(&challenge_prover);
println!("Time to create proof {:?}", start.elapsed());
println!("Size of proof {} bytes", proof.compressed_size());
let start = Instant::now();
let mut chal_bytes = vec![];
proof
.challenge_contribution(&Z, &ipk, ¶ms.g, &mut chal_bytes)
.unwrap();
chal_bytes.extend_from_slice(msg);
let challenge_verifier = compute_random_oracle_challenge::<Fr, Blake2b512>(&chal_bytes);
proof
.verify(&challenge_verifier, Z, &ipk, prepared_params.clone())
.unwrap();
println!("Time to verify proof {:?}", start.elapsed());
}
}