use super::{secret_sharing::Threshold, types::*, utils};
use crate::{
bulletproofs::range_proof::verify_less_than_or_equal,
common::{to_bytes, types::TransactionTime},
curve_arithmetic::{Curve, Pairing},
pedersen_commitment::{Commitment, CommitmentKey, Randomness, Value},
random_oracle::RandomOracle,
sigma_protocols::{com_enc_eq, com_eq_sig, com_mult, common::*},
};
use core::fmt::{self, Display};
use ed25519_dalek::Verifier;
use either::Either;
use sha2::{Digest, Sha256};
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CdiVerificationError {
RegId,
IdCredPub,
Signature,
Dlog,
AccountOwnership,
Policy,
Ar,
Proof,
}
impl Display for CdiVerificationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
CdiVerificationError::RegId => write!(f, "RegIdVerificationError"),
CdiVerificationError::IdCredPub => write!(f, "IdCredPubVerificationError"),
CdiVerificationError::Signature => write!(f, "SignatureVerificationError"),
CdiVerificationError::Dlog => write!(f, "DlogVerificationError"),
CdiVerificationError::AccountOwnership => write!(f, "AccountOwnership"),
CdiVerificationError::Policy => write!(f, "PolicyVerificationError"),
CdiVerificationError::Ar => write!(f, "AnonymityRevokerVerificationError"),
CdiVerificationError::Proof => write!(f, "ProofVerificationError"),
}
}
}
pub fn verify_cdi<
P: Pairing,
C: Curve<Scalar = P::ScalarField>,
AttributeType: Attribute<C::Scalar>,
A: HasArPublicKey<C>,
>(
global_context: &GlobalContext<C>,
ip_info: &IpInfo<P>,
known_ars: &BTreeMap<ArIdentity, A>,
cdi: &CredentialDeploymentInfo<P, C, AttributeType>,
new_or_existing: &Either<TransactionTime, AccountAddress>,
) -> Result<(), CdiVerificationError> {
let addr = new_or_existing.as_ref().right();
let rt_usize: usize = cdi.values.threshold.into();
if rt_usize
!= cdi
.proofs
.id_proofs
.commitments
.cmm_id_cred_sec_sharing_coeff
.len()
{
return Err(CdiVerificationError::Ar);
}
let on_chain_commitment_key = global_context.on_chain_commitment_key;
let gens = global_context.bulletproof_generators();
let ip_verify_key = &ip_info.ip_verify_key;
let mut ro = RandomOracle::domain("credential");
ro.append_message(b"cred_values", &cdi.values);
ro.append_message(b"address", &addr);
ro.append_message(b"global_context", &global_context);
let commitments = &cdi.proofs.id_proofs.commitments;
let verifier_reg_id = com_mult::ComMult {
cmms: [
commitments.cmm_prf.combine(&commitments.cmm_cred_counter),
Commitment(cdi.values.cred_id),
Commitment(on_chain_commitment_key.g),
],
cmm_key: on_chain_commitment_key,
};
let response_reg_id = cdi.proofs.id_proofs.proof_reg_id.clone();
let cdv = &cdi.values;
let proofs = &cdi.proofs;
let verifier_sig = pok_sig_verifier(
&on_chain_commitment_key,
cdi.values.threshold,
&cdi.values
.ar_data
.keys()
.copied()
.collect::<BTreeSet<ArIdentity>>(),
&cdi.values.policy,
commitments,
ip_verify_key,
&cdi.proofs.id_proofs.sig,
);
let verifier_sig = if let Some(v) = verifier_sig {
v
} else {
return Err(CdiVerificationError::Signature);
};
let response_sig = cdi.proofs.id_proofs.proof_ip_sig.clone();
let (id_cred_pub_verifier, id_cred_pub_responses) = id_cred_pub_verifier(
&on_chain_commitment_key,
known_ars,
&cdi.values.ar_data,
&commitments.cmm_id_cred_sec_sharing_coeff,
&cdi.proofs.id_proofs.proof_id_cred_pub,
)?;
let verifier = AndAdapter {
first: verifier_reg_id,
second: verifier_sig,
};
let verifier = verifier.add_prover(id_cred_pub_verifier);
let response = AndResponse {
r1: AndResponse {
r1: response_reg_id,
r2: response_sig,
},
r2: id_cred_pub_responses,
};
let proof = SigmaProof {
challenge: cdi.proofs.id_proofs.challenge,
response,
};
if !verify(&mut ro, &verifier, &proof) {
return Err(CdiVerificationError::Proof);
}
if !verify_less_than_or_equal(
&mut ro,
8,
&cdi.proofs.id_proofs.commitments.cmm_cred_counter,
&cdi.proofs.id_proofs.commitments.cmm_max_accounts,
&cdi.proofs.id_proofs.cred_counter_less_than_max_accounts,
gens,
&on_chain_commitment_key,
) {
return Err(CdiVerificationError::Proof);
}
let signed = utils::credential_hash_to_sign(cdv, &proofs.id_proofs, new_or_existing);
if !utils::verify_account_ownership_proof(
&cdv.cred_key_info.keys,
cdv.cred_key_info.threshold,
&proofs.proof_acc_sk,
signed.as_ref(),
) {
return Err(CdiVerificationError::AccountOwnership);
}
let check_policy = verify_policy(&on_chain_commitment_key, commitments, &cdi.values.policy);
if !check_policy {
return Err(CdiVerificationError::Policy);
}
Ok(())
}
pub fn verify_initial_cdi<
P: Pairing,
C: Curve<Scalar = P::ScalarField>,
AttributeType: Attribute<C::Scalar>,
>(
ip_info: &IpInfo<P>,
cdi: &InitialCredentialDeploymentInfo<C, AttributeType>,
expiry: TransactionTime,
) -> Result<(), CdiVerificationError> {
let mut hasher = Sha256::new();
hasher.update(to_bytes(&expiry));
hasher.update(to_bytes(&cdi.values));
let signed = hasher.finalize();
match ip_info.ip_cdi_verify_key.verify(signed.as_ref(), &cdi.sig) {
Err(_) => Err(CdiVerificationError::Signature),
_ => Ok(()),
}
}
fn id_cred_pub_verifier<C: Curve, A: HasArPublicKey<C>>(
commitment_key: &CommitmentKey<C>,
known_ars: &BTreeMap<ArIdentity, A>,
chain_ar_data: &BTreeMap<ArIdentity, ChainArData<C>>,
cmm_sharing_coeff: &[Commitment<C>],
proof_id_cred_pub: &BTreeMap<ArIdentity, com_enc_eq::Response<C>>,
) -> Result<IdCredPubVerifiers<C>, CdiVerificationError> {
let mut provers = Vec::with_capacity(proof_id_cred_pub.len());
let mut responses = Vec::with_capacity(proof_id_cred_pub.len());
if chain_ar_data.len() != proof_id_cred_pub.len() {
return Err(CdiVerificationError::IdCredPub);
}
for ((ar_id, ar_data), (ar_id_1, response)) in
chain_ar_data.iter().zip(proof_id_cred_pub.iter())
{
if ar_id != ar_id_1 {
return Err(CdiVerificationError::IdCredPub);
}
let cmm_share = utils::commitment_to_share(&ar_id.to_scalar::<C>(), cmm_sharing_coeff);
let ar_info = known_ars
.get(ar_id)
.ok_or(CdiVerificationError::IdCredPub)?;
let item_prover = com_enc_eq::ComEncEq {
cipher: ar_data.enc_id_cred_pub_share,
commitment: cmm_share,
pub_key: *ar_info.get_public_key(),
cmm_key: *commitment_key,
encryption_in_exponent_generator: ar_info.get_public_key().generator,
};
provers.push(item_prover);
responses.push(response.clone());
}
Ok((
ReplicateAdapter { protocols: provers },
ReplicateResponse { responses },
))
}
fn verify_policy<C: Curve, AttributeType: Attribute<C::Scalar>>(
_commitment_key: &CommitmentKey<C>,
_commitments: &CredentialDeploymentCommitments<C>,
_policy: &Policy<C, AttributeType>,
) -> bool {
true
}
#[allow(clippy::too_many_arguments)]
fn pok_sig_verifier<
'a,
P: Pairing,
C: Curve<Scalar = P::ScalarField>,
AttributeType: Attribute<C::Scalar>,
>(
commitment_key: &'a CommitmentKey<C>,
threshold: Threshold,
choice_ar_parameters: &BTreeSet<ArIdentity>,
policy: &'a Policy<C, AttributeType>,
commitments: &'a CredentialDeploymentCommitments<C>,
ip_pub_key: &'a crate::ps_sig::PublicKey<P>,
blinded_sig: &'a crate::ps_sig::BlindedSignature<P>,
) -> Option<com_eq_sig::ComEqSig<P, C>> {
let ar_scalars = utils::encode_ars(choice_ar_parameters)?;
let mut comm_vec = Vec::with_capacity(4 + ar_scalars.len() + commitments.cmm_attributes.len());
let cmm_id_cred_sec = *commitments.cmm_id_cred_sec_sharing_coeff.first()?;
comm_vec.push(cmm_id_cred_sec);
comm_vec.push(commitments.cmm_prf);
let zero = Randomness::zero();
let public_params =
utils::encode_public_credential_values(policy.created_at, policy.valid_to, threshold)
.ok()?;
comm_vec.push(commitment_key.hide_worker(&public_params, &zero));
for ar in ar_scalars {
comm_vec.push(commitment_key.hide_worker(&ar, &zero));
}
let tags = {
match utils::encode_tags::<C::Scalar, _>(
policy
.policy_vec
.keys()
.chain(commitments.cmm_attributes.keys()),
) {
Ok(v) => v,
Err(_) => return None,
}
};
comm_vec.push(commitment_key.hide(&Value::<C>::new(tags), &zero));
comm_vec.push(commitments.cmm_max_accounts);
let f = |v: Either<&AttributeType, &Commitment<_>>| match v {
Either::Left(v) => {
let value = Value::<C>::new(v.to_field_element());
comm_vec.push(commitment_key.hide(&value, &zero));
}
Either::Right(v) => {
comm_vec.push(*v);
}
};
utils::merge_iter(
policy.policy_vec.iter(),
commitments.cmm_attributes.iter(),
f,
);
Some(com_eq_sig::ComEqSig {
blinded_sig: blinded_sig.clone(),
commitments: comm_vec,
ps_pub_key: ip_pub_key.clone(),
comm_key: *commitment_key,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
common::types::{KeyIndex, KeyPair},
curve_arithmetic::arkworks_instances::ArkGroup,
id::{account_holder::*, constants::*, identity_provider::*, test::*},
};
use ark_bls12_381::G1Projective;
use rand::*;
use std::collections::btree_map::BTreeMap;
use Either::{Left, Right};
type SomeCurve = ArkGroup<G1Projective>;
const EXPIRY: TransactionTime = TransactionTime {
seconds: 111111111111111111,
};
#[test]
fn test_verify_cdi() {
let mut csprng = thread_rng();
let max_attrs = 10;
let num_ars = 5;
let IpData {
public_ip_info: ip_info,
ip_secret_key,
ip_cdi_secret_key,
} = test_create_ip_info(&mut csprng, num_ars, max_attrs);
let global_ctx = GlobalContext::<SomeCurve>::generate(String::from("genesis_string"));
let (ars_infos, _) =
test_create_ars(&global_ctx.on_chain_commitment_key.g, num_ars, &mut csprng);
let id_use_data = test_create_id_use_data(&mut csprng);
let initial_acc_data = InitialAccountData {
keys: {
let mut keys = BTreeMap::new();
keys.insert(KeyIndex(0), KeyPair::generate(&mut csprng));
keys.insert(KeyIndex(1), KeyPair::generate(&mut csprng));
keys.insert(KeyIndex(2), KeyPair::generate(&mut csprng));
keys
},
threshold: SignatureThreshold::TWO,
};
let (context, pio, _) = test_create_pio(
&id_use_data,
&ip_info,
&ars_infos,
&global_ctx,
num_ars,
&initial_acc_data,
);
let alist = test_create_attributes();
let ver_ok = verify_credentials(
&pio,
context,
&alist,
EXPIRY,
&ip_secret_key,
&ip_cdi_secret_key,
);
assert!(ver_ok.is_ok());
let (ip_sig, _) = ver_ok.unwrap();
let id_object = IdentityObject {
pre_identity_object: pio,
alist,
signature: ip_sig,
};
let valid_to = YearMonth::new(2022, 5).unwrap(); let created_at = YearMonth::new(2020, 5).unwrap(); let policy = Policy {
valid_to,
created_at,
policy_vec: {
let mut tree = BTreeMap::new();
tree.insert(AttributeTag::from(8u8), AttributeKind::from(31));
tree
},
_phantom: Default::default(),
};
let cred_data = CredentialData {
keys: {
let mut keys = BTreeMap::new();
keys.insert(KeyIndex(0), KeyPair::generate(&mut csprng));
keys.insert(KeyIndex(1), KeyPair::generate(&mut csprng));
keys.insert(KeyIndex(2), KeyPair::generate(&mut csprng));
keys
},
threshold: SignatureThreshold::TWO,
};
let context = IpContext::new(&ip_info, &ars_infos, &global_ctx);
let (cdi, _) = create_credential(
context,
&id_object,
&id_use_data,
0,
policy.clone(),
&cred_data,
&SystemAttributeRandomness {},
&Left(EXPIRY),
)
.expect("Should generate the credential successfully.");
let cdi_check = verify_cdi(&global_ctx, &ip_info, &ars_infos, &cdi, &Left(EXPIRY));
assert_eq!(cdi_check, Ok(()));
let existing_reg_id = account_address_from_registration_id(&cdi.values.cred_id);
let cred_data = CredentialData {
keys: {
let mut keys = BTreeMap::new();
keys.insert(KeyIndex(0), KeyPair::generate(&mut csprng));
keys.insert(KeyIndex(1), KeyPair::generate(&mut csprng));
keys.insert(KeyIndex(2), KeyPair::generate(&mut csprng));
keys
},
threshold: SignatureThreshold::TWO,
};
let (cdi, _) = create_credential(
context,
&id_object,
&id_use_data,
1,
policy,
&cred_data,
&SystemAttributeRandomness {},
&Right(existing_reg_id),
)
.expect("Should generate the credential successfully.");
let cdi_check = verify_cdi(
&global_ctx,
&ip_info,
&ars_infos,
&cdi,
&Right(existing_reg_id),
);
assert_eq!(cdi_check, Ok(()));
}
#[test]
fn test_verify_cdi_v1() {
let mut csprng = thread_rng();
let max_attrs = 10;
let num_ars = 5;
let IpData {
public_ip_info: ip_info,
ip_secret_key,
.. } = test_create_ip_info(&mut csprng, num_ars, max_attrs);
let global_ctx = GlobalContext::<SomeCurve>::generate(String::from("genesis_string"));
let (ars_infos, _) =
test_create_ars(&global_ctx.on_chain_commitment_key.g, num_ars, &mut csprng);
let id_use_data = test_create_id_use_data(&mut csprng);
let (context, pio, randomness) =
test_create_pio_v1(&id_use_data, &ip_info, &ars_infos, &global_ctx, num_ars);
assert_eq!(*randomness, *(id_use_data.randomness));
let alist = test_create_attributes();
let ver_ok = verify_credentials_v1(&pio, context, &alist, &ip_secret_key);
assert!(ver_ok.is_ok());
let ip_sig = ver_ok.unwrap();
let id_object = IdentityObjectV1 {
pre_identity_object: pio,
alist,
signature: ip_sig,
};
let valid_to = YearMonth::new(2022, 5).unwrap(); let created_at = YearMonth::new(2020, 5).unwrap(); let policy = Policy {
valid_to,
created_at,
policy_vec: {
let mut tree = BTreeMap::new();
tree.insert(AttributeTag::from(8u8), AttributeKind::from(31));
tree
},
_phantom: Default::default(),
};
let cred_data = CredentialData {
keys: {
let mut keys = BTreeMap::new();
keys.insert(KeyIndex(0), KeyPair::generate(&mut csprng));
keys.insert(KeyIndex(1), KeyPair::generate(&mut csprng));
keys.insert(KeyIndex(2), KeyPair::generate(&mut csprng));
keys
},
threshold: SignatureThreshold::TWO,
};
let context = IpContext::new(&ip_info, &ars_infos, &global_ctx);
let (cdi, _) = create_credential(
context,
&id_object,
&id_use_data,
0,
policy.clone(),
&cred_data,
&SystemAttributeRandomness {},
&Left(EXPIRY),
)
.expect("Should generate the credential successfully.");
let cdi_check = verify_cdi(&global_ctx, &ip_info, &ars_infos, &cdi, &Left(EXPIRY));
assert_eq!(cdi_check, Ok(()));
let existing_reg_id = account_address_from_registration_id(&cdi.values.cred_id);
let cred_data = CredentialData {
keys: {
let mut keys = BTreeMap::new();
keys.insert(KeyIndex(0), KeyPair::generate(&mut csprng));
keys.insert(KeyIndex(1), KeyPair::generate(&mut csprng));
keys.insert(KeyIndex(2), KeyPair::generate(&mut csprng));
keys
},
threshold: SignatureThreshold::TWO,
};
let (cdi, _) = create_credential(
context,
&id_object,
&id_use_data,
1,
policy,
&cred_data,
&SystemAttributeRandomness {},
&Right(existing_reg_id),
)
.expect("Should generate the credential successfully.");
let cdi_check = verify_cdi(
&global_ctx,
&ip_info,
&ars_infos,
&cdi,
&Right(existing_reg_id),
);
assert_eq!(cdi_check, Ok(()));
}
#[test]
fn test_verify_initial_cdi() {
let mut csprng = thread_rng();
let max_attrs = 10;
let num_ars = 5;
let IpData {
public_ip_info: ip_info,
ip_secret_key,
ip_cdi_secret_key,
} = test_create_ip_info(&mut csprng, num_ars, max_attrs);
let global_ctx = GlobalContext::<SomeCurve>::generate(String::from("genesis_string"));
let (ars_infos, _) =
test_create_ars(&global_ctx.on_chain_commitment_key.g, num_ars, &mut csprng);
let id_use_data = test_create_id_use_data(&mut csprng);
let acc_data = InitialAccountData {
keys: {
let mut keys = BTreeMap::new();
keys.insert(KeyIndex(0), KeyPair::generate(&mut csprng));
keys.insert(KeyIndex(1), KeyPair::generate(&mut csprng));
keys.insert(KeyIndex(2), KeyPair::generate(&mut csprng));
keys
},
threshold: SignatureThreshold::TWO,
};
let (context, pio, _) = test_create_pio(
&id_use_data,
&ip_info,
&ars_infos,
&global_ctx,
num_ars,
&acc_data,
);
let alist = test_create_attributes();
let ver_ok = verify_credentials(
&pio,
context,
&alist,
EXPIRY,
&ip_secret_key,
&ip_cdi_secret_key,
);
assert!(ver_ok.is_ok());
let (_, initial_cdi) = ver_ok.unwrap();
let cdi_check = verify_initial_cdi(&ip_info, &initial_cdi, EXPIRY);
assert_eq!(cdi_check, Ok(()));
}
}