use crate::common_types::{BlindedSignature, Signature, SignerIndex};
use crate::error::{CompactEcashError, Result};
use crate::helpers::{date_scalar, type_scalar};
use crate::proofs::proof_withdrawal::{
WithdrawalReqInstance, WithdrawalReqProof, WithdrawalReqWitness,
};
use crate::scheme::keygen::{PublicKeyUser, SecretKeyAuth, SecretKeyUser, VerificationKeyAuth};
use crate::scheme::setup::GroupParameters;
use crate::scheme::PartialWallet;
use crate::utils::{check_bilinear_pairing, hash_g1};
use crate::{constants, ecash_group_parameters, Attribute, EncodedDate, EncodedTicketType};
use group::{Curve, Group, GroupEncoding};
use nym_bls12_381_fork::{multi_miller_loop, G1Projective, G2Prepared, G2Projective, Scalar};
use serde::{Deserialize, Serialize};
use std::ops::Neg;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct WithdrawalRequest {
joined_commitment_hash: G1Projective,
joined_commitment: G1Projective,
private_attributes_commitments: Vec<G1Projective>,
zk_proof: WithdrawalReqProof,
}
impl WithdrawalRequest {
pub fn get_private_attributes_commitments(&self) -> &[G1Projective] {
&self.private_attributes_commitments
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
pub struct RequestInfo {
joined_commitment_hash: G1Projective,
joined_commitment_opening: Scalar,
private_attributes_openings: Vec<Scalar>,
wallet_secret: Scalar,
expiration_date: Scalar,
t_type: Scalar,
}
impl RequestInfo {
pub fn get_joined_commitment_hash(&self) -> &G1Projective {
&self.joined_commitment_hash
}
pub fn get_joined_commitment_opening(&self) -> &Scalar {
&self.joined_commitment_opening
}
pub fn get_private_attributes_openings(&self) -> &[Scalar] {
&self.private_attributes_openings
}
pub fn get_v(&self) -> &Scalar {
&self.wallet_secret
}
pub fn get_expiration_date(&self) -> &Scalar {
&self.expiration_date
}
pub fn get_t_type(&self) -> &Scalar {
&self.t_type
}
}
fn compute_private_attribute_commitments(
params: &GroupParameters,
joined_commitment_hash: &G1Projective,
private_attributes: &[&Scalar],
) -> (Vec<Scalar>, Vec<G1Projective>) {
let (openings, commitments): (Vec<Scalar>, Vec<G1Projective>) = private_attributes
.iter()
.map(|&m_j| {
let o_j = params.random_scalar();
(o_j, params.gen1() * o_j + joined_commitment_hash * m_j)
})
.unzip();
(openings, commitments)
}
fn generate_non_identity_h(
params: &GroupParameters,
sk_user: &SecretKeyUser,
v: &Scalar,
expiration_date: Scalar,
t_type: Scalar,
) -> (G1Projective, G1Projective, Scalar) {
let gamma = params.gammas();
loop {
let joined_commitment_opening = params.random_scalar();
let joined_commitment =
params.gen1() * joined_commitment_opening + gamma[0] * sk_user.sk + gamma[1] * v;
let joined_commitment_hash = hash_g1(
(joined_commitment + gamma[2] * expiration_date + gamma[3] * t_type).to_bytes(),
);
if !bool::from(joined_commitment_hash.is_identity()) {
return (
joined_commitment,
joined_commitment_hash,
joined_commitment_opening,
);
}
}
}
pub fn withdrawal_request(
sk_user: &SecretKeyUser,
expiration_date: EncodedDate,
t_type: EncodedTicketType,
) -> Result<(WithdrawalRequest, RequestInfo)> {
let params = ecash_group_parameters();
let v = params.random_scalar();
let expiration_date = date_scalar(expiration_date);
let t_type = type_scalar(t_type);
let (joined_commitment, joined_commitment_hash, joined_commitment_opening) =
generate_non_identity_h(params, sk_user, &v, expiration_date, t_type);
let private_attributes = vec![&sk_user.sk, &v];
let (private_attributes_openings, private_attributes_commitments) =
compute_private_attribute_commitments(params, &joined_commitment_hash, &private_attributes);
let instance = WithdrawalReqInstance {
joined_commitment,
joined_commitment_hash,
private_attributes_commitments: private_attributes_commitments.clone(),
pk_user: PublicKeyUser {
pk: params.gen1() * sk_user.sk,
},
};
let witness = WithdrawalReqWitness {
private_attributes,
joined_commitment_opening: &joined_commitment_opening,
private_attributes_openings: &private_attributes_openings,
};
let zk_proof = WithdrawalReqProof::construct(&instance, &witness);
Ok((
WithdrawalRequest {
joined_commitment_hash,
joined_commitment,
private_attributes_commitments,
zk_proof,
},
RequestInfo {
joined_commitment_hash,
joined_commitment_opening,
private_attributes_openings,
wallet_secret: v,
expiration_date,
t_type,
},
))
}
pub fn request_verify(
req: &WithdrawalRequest,
pk_user: PublicKeyUser,
expiration_date: EncodedDate,
t_type: EncodedTicketType,
) -> Result<()> {
let params = ecash_group_parameters();
let gamma = params.gammas();
let expiration_date = date_scalar(expiration_date);
let t_type = type_scalar(t_type);
if bool::from(req.joined_commitment_hash.is_identity()) {
return Err(CompactEcashError::IdentityCommitmentHash);
}
let expected_commitment_hash = hash_g1(
(req.joined_commitment + gamma[2] * expiration_date + gamma[3] * t_type).to_bytes(),
);
if req.joined_commitment_hash != expected_commitment_hash {
return Err(CompactEcashError::WithdrawalRequestVerification);
}
let instance = WithdrawalReqInstance {
joined_commitment: req.joined_commitment,
joined_commitment_hash: req.joined_commitment_hash,
private_attributes_commitments: req.private_attributes_commitments.clone(),
pk_user,
};
if !req.zk_proof.verify(&instance) {
return Err(CompactEcashError::WithdrawalRequestVerification);
}
Ok(())
}
fn sign_expiration_date(
joined_commitment_hash: &G1Projective,
expiration_date: EncodedDate,
sk_auth: &SecretKeyAuth,
) -> G1Projective {
joined_commitment_hash * (sk_auth.ys[2] * date_scalar(expiration_date))
}
fn sign_t_type(
joined_commitment_hash: &G1Projective,
t_type: EncodedTicketType,
sk_auth: &SecretKeyAuth,
) -> G1Projective {
joined_commitment_hash * (sk_auth.ys[3] * type_scalar(t_type))
}
pub fn issue(
sk_auth: &SecretKeyAuth,
pk_user: PublicKeyUser,
withdrawal_req: &WithdrawalRequest,
expiration_date: EncodedDate,
t_type: EncodedTicketType,
) -> Result<BlindedSignature> {
request_verify(withdrawal_req, pk_user, expiration_date, t_type)?;
if sk_auth.ys.len() < constants::ATTRIBUTES_LEN {
return Err(CompactEcashError::KeyTooShort);
}
let blind_signatures: G1Projective = withdrawal_req
.private_attributes_commitments
.iter()
.zip(sk_auth.ys.iter().take(2))
.map(|(pc, yi)| pc * yi)
.sum();
let expiration_date_sign = sign_expiration_date(
&withdrawal_req.joined_commitment_hash,
expiration_date,
sk_auth,
);
let t_type_sign = sign_t_type(&withdrawal_req.joined_commitment_hash, t_type, sk_auth);
let signature = blind_signatures
+ withdrawal_req.joined_commitment_hash * sk_auth.x
+ expiration_date_sign
+ t_type_sign;
Ok(BlindedSignature {
h: withdrawal_req.joined_commitment_hash,
c: signature,
})
}
pub fn issue_verify(
vk_auth: &VerificationKeyAuth,
sk_user: &SecretKeyUser,
blind_signature: &BlindedSignature,
req_info: &RequestInfo,
signer_index: SignerIndex,
) -> Result<PartialWallet> {
let params = ecash_group_parameters();
if req_info.joined_commitment_hash != blind_signature.h {
return Err(CompactEcashError::IssuanceVerification);
}
if bool::from(blind_signature.h.is_identity()) {
return Err(CompactEcashError::IdentitySignature);
}
let blinding_removers = vk_auth
.beta_g1
.iter()
.zip(&req_info.private_attributes_openings)
.map(|(beta, opening)| beta * opening)
.sum::<G1Projective>();
let unblinded_c = blind_signature.c - blinding_removers;
let attr = [
sk_user.sk,
req_info.wallet_secret,
req_info.expiration_date,
req_info.t_type,
];
let signed_attributes = attr
.iter()
.zip(vk_auth.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
if !check_bilinear_pairing(
&blind_signature.h.to_affine(),
&G2Prepared::from((vk_auth.alpha + signed_attributes).to_affine()),
&unblinded_c.to_affine(),
params.prepared_miller_g2(),
) {
return Err(CompactEcashError::IssuanceVerification);
}
Ok(PartialWallet {
sig: Signature {
h: blind_signature.h,
s: unblinded_c,
},
v: req_info.wallet_secret,
idx: signer_index,
expiration_date: req_info.expiration_date,
t_type: req_info.t_type,
})
}
pub fn verify_partial_blind_signature(
private_attribute_commitments: &[G1Projective],
public_attributes: &[&Attribute],
blind_sig: &BlindedSignature,
partial_verification_key: &VerificationKeyAuth,
) -> bool {
let params = ecash_group_parameters();
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.h.is_identity()) {
return false;
}
let c_neg = blind_sig.c.to_affine().neg();
let g2_prep = params.prepared_miller_g2();
let mut terms = vec![
(c_neg, g2_prep.clone()),
(
blind_sig.h.to_affine(),
G2Prepared::from(partial_verification_key.alpha.to_affine()),
),
];
for (private_attr_commit, beta_g2) in private_attribute_commitments
.iter()
.zip(&partial_verification_key.beta_g2)
{
terms.push((
private_attr_commit.to_affine(),
G2Prepared::from(beta_g2.to_affine()),
))
}
for (&pub_attr, beta_g2) in public_attributes.iter().zip(
partial_verification_key
.beta_g2
.iter()
.skip(num_private_attributes),
) {
terms.push((
(blind_sig.h * pub_attr).to_affine(),
G2Prepared::from(beta_g2.to_affine()),
))
}
#[allow(clippy::map_identity)]
let terms_refs = terms.iter().map(|(g1, g2)| (g1, g2)).collect::<Vec<_>>();
multi_miller_loop(&terms_refs)
.final_exponentiation()
.is_identity()
.into()
}
#[cfg(test)]
mod tests {
use super::{generate_non_identity_h, verify_partial_blind_signature};
use crate::common_types::BlindedSignature;
use crate::ecash_group_parameters;
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
use nym_bls12_381_fork::G1Projective;
#[test]
fn test_generate_non_identity_h() {
let params = ecash_group_parameters();
let sk_user = SecretKeyUser {
sk: params.random_scalar(),
};
let v = params.random_scalar();
let expiration_date = params.random_scalar();
let t_type = params.random_scalar();
let (_, joined_commitment_hash, _) =
generate_non_identity_h(params, &sk_user, &v, expiration_date, t_type);
assert!(
!bool::from(joined_commitment_hash.is_identity()),
"Joined commitment hash should not be the identity element"
);
}
#[test]
fn test_verify_partial_blind_signature_blind_sig_identity() {
let params = ecash_group_parameters();
let private_attribute_commitments = vec![params.gen1() * params.random_scalar()];
let public_attributes = vec![];
let blind_sig = BlindedSignature {
h: G1Projective::identity(),
c: params.gen1() * params.random_scalar(),
};
let partial_verification_key = VerificationKeyAuth {
alpha: params.gen2() * params.random_scalar(),
beta_g1: vec![params.gen1() * params.random_scalar()],
beta_g2: vec![params.gen2() * params.random_scalar()],
};
assert!(
!verify_partial_blind_signature(
&private_attribute_commitments,
&public_attributes,
&blind_sig,
&partial_verification_key
),
"Expected verification to return false for identity h in blind signature"
);
}
}