diff --git a/rust-src/concordium_base/src/id/account_holder.rs b/rust-src/concordium_base/src/id/account_holder.rs index 2625aa487..1506be0aa 100644 --- a/rust-src/concordium_base/src/id/account_holder.rs +++ b/rust-src/concordium_base/src/id/account_holder.rs @@ -1099,7 +1099,7 @@ fn compute_pok_sig< /// For the other values the verifier (the chain) will compute commitments with /// randomness 0 in order to verify knowledge of the signature. #[allow(clippy::too_many_arguments)] -pub fn compute_commitments, R: Rng>( +fn compute_commitments, R: Rng>( commitment_key: &PedersenKey, alist: &AttributeList, prf_key: &prf::SecretKey, diff --git a/rust-src/concordium_base/src/id/identity_attributes.rs b/rust-src/concordium_base/src/id/identity_attributes_credentials.rs similarity index 57% rename from rust-src/concordium_base/src/id/identity_attributes.rs rename to rust-src/concordium_base/src/id/identity_attributes_credentials.rs index d02c57b45..dc5874f1d 100644 --- a/rust-src/concordium_base/src/id/identity_attributes.rs +++ b/rust-src/concordium_base/src/id/identity_attributes_credentials.rs @@ -1,834 +1,974 @@ -//! Functionality to prove and verify attribute commitments based on identity credentials. These are to a large -//! extent equivalent to attribute commitments deployed on chain, but there is no on-chain account credentials involved. - -use super::{account_holder, secret_sharing::*, types::*, utils}; -use crate::bulletproofs::range_proof::verify_less_than_or_equal; -use crate::common::types::TransactionTime; -use crate::pedersen_commitment::{CommitmentKey, Randomness}; -use crate::{ - bulletproofs::range_proof::prove_less_than_or_equal, - curve_arithmetic::{Curve, Field, Pairing}, - dodis_yampolskiy_prf as prf, - pedersen_commitment::{ - Commitment, CommitmentKey as PedersenKey, Randomness as PedersenRandomness, Value, - }, - random_oracle::RandomOracle, - sigma_protocols::{com_enc_eq, com_eq_sig, com_mult, common::*}, -}; -use anyhow::{bail, ensure}; -use core::fmt; -use core::fmt::Display; -use either::Either; -use rand::*; -use std::collections::{btree_map::BTreeMap, hash_map::HashMap, BTreeSet}; - -/// Construct proof for attribute commitments from identity credential -#[allow(clippy::too_many_arguments)] -pub fn prove_identity_attributes< - P: Pairing, - C: Curve, - AttributeType: Clone + Attribute, ->( - context: IpContext<'_, P, C>, - id_object: &impl HasIdentityObjectFields, - id_object_use_data: &IdObjectUseData, - cred_counter: u8, - policy: Policy, - cred_key_info: CredentialPublicKeys, - addr: Option<&AccountAddress>, - secret_data: &impl HasAttributeRandomness, -) -> anyhow::Result<( - IdentityAttributesCommitmentInfo, - IdentityAttributesCommitmentRandomness, -)> { - let mut csprng = thread_rng(); - - let (ip_sig, prio, alist) = ( - id_object.get_signature(), - id_object.get_common_pio_fields(), - id_object.get_attribute_list(), - ); - let sig_retrieval_rand = &id_object_use_data.randomness; - let aci = &id_object_use_data.aci; - - let prf_key = &aci.prf_key; - let id_cred_sec = &aci.cred_holder_info.id_cred.id_cred_sec; - let cred_id_exponent = match aci.prf_key.prf_exponent(cred_counter) { - Ok(exp) => exp, - Err(_) => bail!( - "Cannot create CDI with this account number because K + {} = 0.", - cred_counter - ), - }; - - // RegId as well as Prf key commitments must be computed - // with the same generators as in the commitment key. - let cred_id = context - .global_context - .on_chain_commitment_key - .hide( - &Value::::new(cred_id_exponent), - &PedersenRandomness::zero(), - ) - .0; - - // Check that all the chosen identity providers (in the pre-identity object) are - // available in the given context, and remove the ones that are not. - let chosen_ars = { - let mut chosen_ars = BTreeMap::new(); - for ar_id in prio.choice_ar_parameters.ar_identities.iter() { - if let Some(info) = context.ars_infos.get(ar_id) { - // FIXME: We could get rid of this clone if we passed in a map of references. - let _ = chosen_ars.insert(*ar_id, info.clone()); // since we are - // iterating over - // a set, this - // will always - // be Some - } else { - bail!("Cannot find anonymity revoker {} in the context.", ar_id) - } - } - chosen_ars - }; - - // sharing data for id cred sec - let (id_cred_data, cmm_id_cred_sec_sharing_coeff, cmm_coeff_randomness) = - account_holder::compute_sharing_data( - id_cred_sec, - &chosen_ars, - prio.choice_ar_parameters.threshold, - &context.global_context.on_chain_commitment_key, - ); - - let number_of_ars = prio.choice_ar_parameters.ar_identities.len(); - // filling ar data - let ar_data = id_cred_data - .iter() - .map(|item| { - ( - item.ar.ar_identity, - ChainArData { - enc_id_cred_pub_share: item.encrypted_share, - }, - ) - }) - .collect::>(); - - let ip_pub_key = &context.ip_info.ip_verify_key; - - // retrieve the signature on the underlying idcredsec + prf_key + attribute_list - let retrieved_sig = ip_sig.retrieve(sig_retrieval_rand); - - // and then we blind the signature to disassociate it from the message. - // only the second part is used (as per the protocol) - let (blinded_sig, blind_rand) = retrieved_sig.blind(&mut csprng); - // We now compute commitments to all the items in the attribute list. - // We use the on-chain pedersen commitment key. - let (commitments, commitment_rands) = compute_commitments( - &context.global_context.on_chain_commitment_key, - alist, - prf_key, - cred_counter, - &cmm_id_cred_sec_sharing_coeff, - cmm_coeff_randomness, - &policy, - secret_data, - &mut csprng, - )?; - - // We have all the values now. - let cred_values = IdentityAttributesCommitmentValues { - cred_id, - threshold: prio.choice_ar_parameters.threshold, - ar_data, - ip_identity: context.ip_info.ip_identity, - policy, - cred_key_info, - }; - - // We now produce all the proofs. - // Compute the challenge prefix by hashing the values. - // FIXME: We should do something different here. - // Eventually we'll have to include the genesis hash. - let mut ro = RandomOracle::domain("credential"); - ro.append_message(b"cred_values", &cred_values); - ro.append_message(b"address", &addr); - ro.append_message(b"global_context", &context.global_context); - - let mut id_cred_pub_share_numbers = Vec::with_capacity(number_of_ars); - let mut id_cred_pub_provers = Vec::with_capacity(number_of_ars); - let mut id_cred_pub_secrets = Vec::with_capacity(number_of_ars); - - // create provers for knowledge of id_cred_sec. - for item in id_cred_data.iter() { - let secret = com_enc_eq::ComEncEqSecret { - value: item.share.clone(), - elgamal_rand: item.encryption_randomness.clone(), - pedersen_rand: item.randomness_cmm_to_share.clone(), - }; - - let item_prover = com_enc_eq::ComEncEq { - cipher: item.encrypted_share, - commitment: item.cmm_to_share, - pub_key: item.ar.ar_public_key, - cmm_key: context.global_context.on_chain_commitment_key, - encryption_in_exponent_generator: item.ar.ar_public_key.generator, - }; - - id_cred_pub_share_numbers.push(item.ar.ar_identity); - id_cred_pub_provers.push(item_prover); - id_cred_pub_secrets.push(secret); - } - - // Proof that the registration id is computed correctly from the prf key K and - // the cred_counter x. - let (prover_reg_id, secret_reg_id) = compute_pok_reg_id( - &context.global_context.on_chain_commitment_key, - prf_key.clone(), - &commitments.cmm_prf, - &commitment_rands.prf_rand, - cred_counter, - &commitments.cmm_cred_counter, - &commitment_rands.cred_counter_rand, - &commitment_rands.max_accounts_rand, - cred_id_exponent, - cred_id, - ); - - let choice_ar_handles = cred_values.ar_data.keys().copied().collect::>(); - - // Proof of knowledge of the signature of the identity provider. - let (prover_sig, secret_sig) = compute_pok_sig( - &context.global_context.on_chain_commitment_key, - &commitments, - &commitment_rands, - id_cred_sec, - prf_key, - alist, - prio.choice_ar_parameters.threshold, - &choice_ar_handles, - ip_pub_key, - &blinded_sig, - blind_rand, - )?; - - let prover = AndAdapter { - first: prover_reg_id, - second: prover_sig, - }; - let prover = prover.add_prover(ReplicateAdapter { - protocols: id_cred_pub_provers, - }); - - let secret = ((secret_reg_id, secret_sig), id_cred_pub_secrets); - let proof = match prove(&mut ro, &prover, secret, &mut csprng) { - Some(x) => x, - None => bail!("Cannot produce zero knowledge proof."), - }; - - let cred_counter_less_than_max_accounts = match prove_less_than_or_equal( - &mut ro, - &mut csprng, - 8, - u64::from(cred_counter), - u64::from(alist.max_accounts), - context.global_context.bulletproof_generators(), - &context.global_context.on_chain_commitment_key, - &commitment_rands.cred_counter_rand, - &commitment_rands.max_accounts_rand, - ) { - Some(x) => x, - None => bail!("Cannot produce proof that cred_counter <= max_accounts."), - }; - - // A list of signatures on the challenge used by the other proofs using the - // credential keys. - // The challenge has domain separator "credential" followed by appending all - // values of the credential to the ro, specifically appending the - // CredentialDeploymentValues struct. - // - // The domain seperator in combination with appending all the data of the - // credential deployment should make it non-reusable. - - let id_proofs = IdentityAttributesCommitmentProofs { - sig: blinded_sig, - commitments, - challenge: proof.challenge, - proof_id_cred_pub: id_cred_pub_share_numbers - .into_iter() - .zip(proof.response.r2.responses) - .collect(), - proof_reg_id: proof.response.r1.r1, - proof_ip_sig: proof.response.r1.r2, - cred_counter_less_than_max_accounts, - }; - - let info = IdentityAttributesCommitmentInfo { - values: cred_values, - proofs: id_proofs, - }; - Ok((info, commitment_rands)) -} - -#[allow(clippy::too_many_arguments)] -fn compute_pok_sig< - P: Pairing, - C: Curve, - AttributeType: Attribute, ->( - commitment_key: &PedersenKey, - commitments: &IdentityAttributesCommitments, - commitment_rands: &IdentityAttributesCommitmentRandomness, - id_cred_sec: &Value, - prf_key: &prf::SecretKey, - alist: &AttributeList, - threshold: Threshold, - ar_list: &BTreeSet, - ip_pub_key: &crate::ps_sig::PublicKey

, - blinded_sig: &crate::ps_sig::BlindedSignature

, - blind_rand: crate::ps_sig::BlindingRandomness

, -) -> anyhow::Result<(com_eq_sig::ComEqSig, com_eq_sig::ComEqSigSecret)> { - let att_vec = &alist.alist; - // number of user chosen attributes (+4 is for tags, valid_to, created_at, - // max_accounts) - let num_user_attributes = att_vec.len() + 4; - // To these there are always two attributes (idCredSec and prf key) added. - let num_total_attributes = num_user_attributes + 2; - let ar_scalars = match utils::encode_ars(ar_list) { - Some(x) => x, - None => bail!("Cannot encode anonymity revokers."), - }; - let num_ars = ar_scalars.len(); // we commit to each anonymity revoker, with randomness 0 - // and finally we also commit to the anonymity revocation threshold. - // so the total number of commitments is as follows - let num_total_commitments = num_total_attributes + num_ars + 1; - - let y_tildas = &ip_pub_key.y_tildas; - - ensure!( - y_tildas.len() > att_vec.len() + num_ars + 5, - "The PS key must be long enough to accommodate all the attributes" - ); - - ensure!( - y_tildas.len() >= num_total_attributes, - "Too many attributes {} >= {}", - y_tildas.len(), - num_total_attributes - ); - - let mut gxs = Vec::with_capacity(num_total_commitments); - - let mut secrets = Vec::with_capacity(num_total_commitments); - secrets.push(( - id_cred_sec.clone(), - commitment_rands.id_cred_sec_rand.clone(), - )); - gxs.push(y_tildas[0]); - secrets.push((prf_key.to_value(), commitment_rands.prf_rand.clone())); - gxs.push(y_tildas[1]); - - let public_vals = - utils::encode_public_credential_values(alist.created_at, alist.valid_to, threshold)?; - - // commitment randomness (0) for the public parameters. - let zero = PedersenRandomness::::zero(); - secrets.push((Value::new(public_vals), zero.clone())); - gxs.push(y_tildas[2]); - for i in 3..num_ars + 3 { - // the encoded id revoker are commited with randomness 0. - secrets.push((Value::new(ar_scalars[i - 3]), zero.clone())); - gxs.push(y_tildas[i]); - } - - let att_rands = &commitment_rands.attributes_rand; - - let tags_val = utils::encode_tags(alist.alist.keys())?; - let tags_cmm = commitment_key.hide_worker(&tags_val, &zero); - - let max_accounts_val = Value::new(C::scalar_from_u64(alist.max_accounts.into())); - let max_accounts_cmm = - commitment_key.hide(&max_accounts_val, &commitment_rands.max_accounts_rand); - - secrets.push((Value::new(tags_val), zero.clone())); - gxs.push(y_tildas[num_ars + 3]); - secrets.push((max_accounts_val, commitment_rands.max_accounts_rand.clone())); - gxs.push(y_tildas[num_ars + 4]); - - // NB: It is crucial here that we use a btreemap. This guarantees that - // the att_vec.iter() iterator is ordered by keys. - for (&g, (tag, v)) in y_tildas.iter().skip(num_ars + 3 + 1).zip(att_vec.iter()) { - secrets.push(( - Value::new(v.to_field_element()), - // if we commited with non-zero randomness get it. - // otherwise we must have commited with zero randomness - // which we should use - att_rands.get(tag).cloned().unwrap_or_else(|| zero.clone()), - )); - gxs.push(g); - } - - let mut comm_vec = Vec::with_capacity(num_total_commitments); - let cmm_id_cred_sec = commitments.cmm_id_cred_sec_sharing_coeff[0]; - comm_vec.push(cmm_id_cred_sec); - comm_vec.push(commitments.cmm_prf); - - // add commitment to threshold with randomness 0 - comm_vec.push(commitment_key.hide_worker(&public_vals, &zero)); - - // and all commitments to ARs with randomness 0 - for ar in ar_scalars.iter() { - comm_vec.push(commitment_key.hide_worker(ar, &zero)); - } - - comm_vec.push(tags_cmm); - comm_vec.push(max_accounts_cmm); - - for (idx, v) in alist.alist.iter() { - match commitments.cmm_attributes.get(idx) { - None => { - // need to commit with randomness 0 - let value = Value::::new(v.to_field_element()); - let cmm = commitment_key.hide(&value, &zero); - comm_vec.push(cmm); - } - Some(cmm) => comm_vec.push(*cmm), - } - } - - let secret = com_eq_sig::ComEqSigSecret { - blind_rand, - values_and_rands: secrets, - }; - let prover = com_eq_sig::ComEqSig { - blinded_sig: blinded_sig.clone(), - commitments: comm_vec, - // FIXME: Figure out how to get rid of the clone - ps_pub_key: ip_pub_key.clone(), - comm_key: *commitment_key, - }; - Ok((prover, secret)) -} - -/// Computing the commitments for the credential deployment info. We only -/// compute commitments for values that are not revealed as part of the policy. -/// For the other values the verifier (the chain) will compute commitments with -/// randomness 0 in order to verify knowledge of the signature. -#[allow(clippy::too_many_arguments)] -pub fn compute_commitments, R: Rng>( - commitment_key: &PedersenKey, - alist: &AttributeList, - prf_key: &prf::SecretKey, - cred_counter: u8, - cmm_id_cred_sec_sharing_coeff: &[Commitment], - cmm_coeff_randomness: Vec>, - policy: &Policy, - secret_data: &impl HasAttributeRandomness, - csprng: &mut R, -) -> anyhow::Result<( - IdentityAttributesCommitments, - IdentityAttributesCommitmentRandomness, -)> { - let id_cred_sec_rand = if let Some(v) = cmm_coeff_randomness.first() { - v.clone() - } else { - bail!("Commitment randomness is an empty vector."); - }; - - let (cmm_prf, prf_rand) = commitment_key.commit(&prf_key, csprng); - - let cred_counter = Value::::new(C::scalar_from_u64(u64::from(cred_counter))); - let (cmm_cred_counter, cred_counter_rand) = commitment_key.commit(&cred_counter, csprng); - let max_accounts = Value::::new(C::scalar_from_u64(u64::from(alist.max_accounts))); - let (cmm_max_accounts, max_accounts_rand) = commitment_key.commit(&max_accounts, csprng); - let att_vec = &alist.alist; - let n = att_vec.len(); - // only commitments to attributes which are not revealed. - ensure!( - n >= policy.policy_vec.len(), - "Attribute list is shorter than the number of revealed items in the policy." - ); - let cmm_len = n - policy.policy_vec.len(); - let mut cmm_attributes = BTreeMap::new(); - let mut attributes_rand = HashMap::with_capacity(cmm_len); - for (&i, val) in att_vec.iter() { - // in case the value is openened there is no need to hide it. - // We can just commit with randomness 0. - if !policy.policy_vec.contains_key(&i) { - let value = Value::::new(val.to_field_element()); - let attr_rand = secret_data.get_attribute_commitment_randomness(&i)?; - let cmm = commitment_key.hide(&value, &attr_rand); - cmm_attributes.insert(i, cmm); - attributes_rand.insert(i, attr_rand); - } - } - let cdc = IdentityAttributesCommitments { - cmm_prf, - cmm_cred_counter, - cmm_max_accounts, - cmm_attributes, - cmm_id_cred_sec_sharing_coeff: cmm_id_cred_sec_sharing_coeff.to_owned(), - }; - - let cr = IdentityAttributesCommitmentRandomness { - id_cred_sec_rand, - prf_rand, - cred_counter_rand, - max_accounts_rand, - attributes_rand, - }; - Ok((cdc, cr)) -} - -/// proof of knowledge of registration id -#[allow(clippy::too_many_arguments)] -fn compute_pok_reg_id( - on_chain_commitment_key: &PedersenKey, - prf_key: prf::SecretKey, - cmm_prf: &Commitment, - prf_rand: &PedersenRandomness, - cred_counter: u8, - cmm_cred_counter: &Commitment, - cred_counter_rand: &PedersenRandomness, - // max_accounts_rand is not used at the moment. - // it should be used for the range proof that cred_counter < max_accounts, but - // that is not yet available - _max_accounts_rand: &PedersenRandomness, - reg_id_exponent: C::Scalar, - reg_id: C, -) -> (com_mult::ComMult, com_mult::ComMultSecret) { - // Commitment to 1 with randomness 0, to serve as the right-hand side in - // com_mult proof. - // NOTE: In order for this to work the reg_id must be computed - // with the same base as the first element of the commitment key. - let cmm_one = on_chain_commitment_key.hide( - &Value::::new(C::Scalar::one()), - &PedersenRandomness::zero(), - ); - - // commitments are the public values. They all have to - let public = [ - cmm_prf.combine(cmm_cred_counter), - Commitment(reg_id), - cmm_one, - ]; - // finally the secret keys are derived from actual committed values - // and the randomness. - - let mut k = C::scalar_from_u64(u64::from(cred_counter)); - k.add_assign(&prf_key); - - // combine the two random values - let mut rand_1 = C::Scalar::zero(); - rand_1.add_assign(prf_rand); - rand_1.add_assign(cred_counter_rand); - // reg_id is the commitment to reg_id_exponent with randomness 0 - // the right-hand side of the equation is commitment to 1 with randomness 0 - let values = [Value::new(k), Value::new(reg_id_exponent)]; - let rands = [ - PedersenRandomness::new(rand_1), - PedersenRandomness::zero(), - PedersenRandomness::zero(), - ]; - - let secret = com_mult::ComMultSecret { values, rands }; - - let prover = com_mult::ComMult { - cmms: public, - cmm_key: *on_chain_commitment_key, - }; - (prover, secret) -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -/// Reason why verification of a credential commitment failed. -pub enum AttributeCommitmentVerificationError { - RegId, - IdCredPub, - Signature, - Dlog, - Policy, - Ar, - Proof, -} - -impl Display for AttributeCommitmentVerificationError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - AttributeCommitmentVerificationError::RegId => write!(f, "RegIdVerificationError"), - AttributeCommitmentVerificationError::IdCredPub => { - write!(f, "IdCredPubVerificationError") - } - AttributeCommitmentVerificationError::Signature => { - write!(f, "SignatureVerificationError") - } - AttributeCommitmentVerificationError::Dlog => write!(f, "DlogVerificationError"), - AttributeCommitmentVerificationError::Policy => write!(f, "PolicyVerificationError"), - AttributeCommitmentVerificationError::Ar => { - write!(f, "AnonymityRevokerVerificationError") - } - AttributeCommitmentVerificationError::Proof => write!(f, "ProofVerificationError"), - } - } -} -/// Verify attribute commitments created from identity credential. -pub fn verify_identity_attributes< - P: Pairing, - C: Curve, - AttributeType: Attribute, - A: HasArPublicKey, ->( - global_context: &GlobalContext, - ip_info: &IpInfo

, - // NB: The following map only needs to be a superset of the ars - // in the cdi. - known_ars: &BTreeMap, - cdi: &IdentityAttributesCommitmentInfo, - new_or_existing: &Either, -) -> Result<(), AttributeCommitmentVerificationError> { - // We need to check that the threshold is actually equal to - // the number of coefficients in the sharing polynomial - // (corresponding to the degree+1) - let addr = new_or_existing.as_ref().right(); - let rt_usize: usize = cdi.values.threshold.into(); - if rt_usize != cdi.proofs.commitments.cmm_id_cred_sec_sharing_coeff.len() { - return Err(AttributeCommitmentVerificationError::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; - // Compute the challenge prefix by hashing the values. - 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.commitments; - - // We now need to construct a uniform verifier - // since we cannot check proofs independently. - - 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, - }; - // FIXME: Figure out a pattern to get rid of these clone's. - let response_reg_id = cdi.proofs.proof_reg_id.clone(); - - let verifier_sig = pok_sig_verifier( - &on_chain_commitment_key, - cdi.values.threshold, - &cdi.values - .ar_data - .keys() - .copied() - .collect::>(), - &cdi.values.policy, - commitments, - ip_verify_key, - &cdi.proofs.sig, - ); - let verifier_sig = if let Some(v) = verifier_sig { - v - } else { - return Err(AttributeCommitmentVerificationError::Signature); - }; - - let response_sig = cdi.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.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.challenge, - response, - }; - - if !verify(&mut ro, &verifier, &proof) { - return Err(AttributeCommitmentVerificationError::Proof); - } - - if !verify_less_than_or_equal( - &mut ro, - 8, - &cdi.proofs.commitments.cmm_cred_counter, - &cdi.proofs.commitments.cmm_max_accounts, - &cdi.proofs.cred_counter_less_than_max_accounts, - gens, - &on_chain_commitment_key, - ) { - return Err(AttributeCommitmentVerificationError::Proof); - } - - let check_policy = verify_policy(&on_chain_commitment_key, commitments, &cdi.values.policy); - - if !check_policy { - return Err(AttributeCommitmentVerificationError::Policy); - } - - Ok(()) -} - -/// verify id_cred data -fn id_cred_pub_verifier>( - commitment_key: &CommitmentKey, - known_ars: &BTreeMap, - chain_ar_data: &BTreeMap>, - cmm_sharing_coeff: &[Commitment], - proof_id_cred_pub: &BTreeMap>, -) -> Result, AttributeCommitmentVerificationError> { - let mut provers = Vec::with_capacity(proof_id_cred_pub.len()); - let mut responses = Vec::with_capacity(proof_id_cred_pub.len()); - - // The encryptions and the proofs have to match. - if chain_ar_data.len() != proof_id_cred_pub.len() { - return Err(AttributeCommitmentVerificationError::IdCredPub); - } - - // The following relies on the fact that iterators over BTreeMap are - // over sorted values. - 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(AttributeCommitmentVerificationError::IdCredPub); - } - let cmm_share = utils::commitment_to_share(&ar_id.to_scalar::(), cmm_sharing_coeff); - - // finding the correct AR data. - let ar_info = known_ars - .get(ar_id) - .ok_or(AttributeCommitmentVerificationError::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 }, - )) -} - -/// Verify a policy. This currently does not do anything since -/// the only check that is done is that the commitments are opened correctly, -/// and that check is part of the signature check. -fn verify_policy>( - _commitment_key: &CommitmentKey, - _commitments: &IdentityAttributesCommitments, - _policy: &Policy, -) -> bool { - true -} - -/// Verify the proof of knowledge of signature on the attribute list. -/// A none return value means we cannot construct a verifier, and consequently -/// it should be interperted as the signature being invalid. -#[allow(clippy::too_many_arguments)] -fn pok_sig_verifier< - 'a, - P: Pairing, - C: Curve, - AttributeType: Attribute, ->( - commitment_key: &'a CommitmentKey, - threshold: Threshold, - choice_ar_parameters: &BTreeSet, - policy: &'a Policy, - commitments: &'a IdentityAttributesCommitments, - ip_pub_key: &'a crate::ps_sig::PublicKey

, - blinded_sig: &'a crate::ps_sig::BlindedSignature

, -) -> Option> { - let ar_scalars = utils::encode_ars(choice_ar_parameters)?; - // Capacity for id_cred_sec, cmm_prf, (threshold, valid_to, created_at), tags - // ar_scalars and cmm_attributes - 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); - - // compute commitments with randomness 0 - let zero = Randomness::zero(); - let public_params = - utils::encode_public_credential_values(policy.created_at, policy.valid_to, threshold) - .ok()?; - // add commitment to public values with randomness 0 - comm_vec.push(commitment_key.hide_worker(&public_params, &zero)); - // and all commitments to ARs with randomness 0 - for ar in ar_scalars { - comm_vec.push(commitment_key.hide_worker(&ar, &zero)); - } - - let tags = { - match utils::encode_tags::( - policy - .policy_vec - .keys() - .chain(commitments.cmm_attributes.keys()), - ) { - Ok(v) => v, - Err(_) => return None, - } - }; - - // add commitment with randomness 0 for variant, valid_to and created_at - comm_vec.push(commitment_key.hide(&Value::::new(tags), &zero)); - comm_vec.push(commitments.cmm_max_accounts); - - // now, we go through the policy and remaining commitments and - // put them into the vector of commitments in order to check the signature. - // NB: It is crucial that they are put into the vector ordered by tags, since - // otherwise the signature will not check out. - // At this point we know all tags are distinct. - - let f = |v: Either<&AttributeType, &Commitment<_>>| match v { - Either::Left(v) => { - let value = Value::::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 { - // FIXME: Figure out how to restructure to get rid of this clone. - blinded_sig: blinded_sig.clone(), - commitments: comm_vec, - // FIXME: Figure out how to restructure to get rid of this clone. - ps_pub_key: ip_pub_key.clone(), - comm_key: *commitment_key, - }) -} +//! Functionality to prove and verify attribute commitments based on identity credentials. These are to a large +//! extent equivalent to attribute commitments deployed on chain, but there is no on-chain account credentials involved. + +use super::{account_holder, secret_sharing::*, types::*, utils}; +use crate::pedersen_commitment::{CommitmentKey, Randomness}; +use crate::{ + curve_arithmetic::{Curve, Pairing}, + dodis_yampolskiy_prf as prf, + pedersen_commitment::{ + Commitment, CommitmentKey as PedersenKey, Randomness as PedersenRandomness, Value, + }, + random_oracle::RandomOracle, + sigma_protocols::{com_enc_eq, com_eq_sig, common::*}, +}; +use anyhow::{bail, ensure}; +use core::fmt; +use core::fmt::Display; +use either::Either; +use rand::*; +use std::collections::{btree_map::BTreeMap, BTreeSet}; + +/// Construct proof for attribute credentials from identity credential +pub fn prove_identity_attributes< + P: Pairing, + C: Curve, + AttributeType: Clone + Attribute, +>( + context: IpContext<'_, P, C>, + id_object: &impl HasIdentityObjectFields, + id_object_use_data: &IdObjectUseData, + policy: Policy, + transcript: &mut RandomOracle, +) -> anyhow::Result<( + IdentityAttributesCredentialsInfo, + IdentityAttributesCredentialsRandomness, +)> { + let mut csprng = thread_rng(); + + let (ip_sig, prio, alist) = ( + id_object.get_signature(), + id_object.get_common_pio_fields(), + id_object.get_attribute_list(), + ); + let sig_retrieval_rand = &id_object_use_data.randomness; + let aci = &id_object_use_data.aci; + + let prf_key = &aci.prf_key; + let id_cred_sec = &aci.cred_holder_info.id_cred.id_cred_sec; + + // Check that all the chosen identity providers (in the pre-identity object) are + // available in the given context, and remove the ones that are not. + let chosen_ars = { + let mut chosen_ars = BTreeMap::new(); + for ar_id in prio.choice_ar_parameters.ar_identities.iter() { + if let Some(info) = context.ars_infos.get(ar_id) { + let _ = chosen_ars.insert(*ar_id, info.clone()); // since we are + // iterating over + // a set, this + // will always + // be Some + } else { + bail!("Cannot find anonymity revoker {} in the context.", ar_id) + } + } + chosen_ars + }; + + // sharing data for id cred sec + let (id_cred_data, cmm_id_cred_sec_sharing_coeff, cmm_coeff_randomness) = + account_holder::compute_sharing_data( + id_cred_sec, + &chosen_ars, + prio.choice_ar_parameters.threshold, + &context.global_context.on_chain_commitment_key, + ); + + // filling ar data + let ar_data = id_cred_data + .iter() + .map(|item| { + ( + item.ar.ar_identity, + ChainArData { + enc_id_cred_pub_share: item.encrypted_share, + }, + ) + }) + .collect::>(); + + let ip_pub_key = &context.ip_info.ip_verify_key; + + // retrieve the signature on the underlying idcredsec + prf_key + attribute_list + let retrieved_sig = ip_sig.retrieve(sig_retrieval_rand); + + // and then we blind the signature to disassociate it from the message. + // only the second part is used (as per the protocol) + let (blinded_sig, blind_rand) = retrieved_sig.blind(&mut csprng); + // We now compute commitments to all the items in the attribute list. + // We use the on-chain pedersen commitment key. + let (commitments, commitment_rands) = compute_commitments( + &context.global_context.on_chain_commitment_key, + alist, + prf_key, + &cmm_id_cred_sec_sharing_coeff, + cmm_coeff_randomness, + &policy, + &mut csprng, + )?; + + // We have all the values now. + let id_attribute_values = IdentityAttributesCredentialsValues { + threshold: prio.choice_ar_parameters.threshold, + ar_data, + ip_identity: context.ip_info.ip_identity, + policy, + }; + + // The label "IdentityAttributesCredentials" is appended to the transcript followed all + // values of the identity attributes, specifically appending the + // IdentityAttributesCommitmentValues struct. + // This should make the proof non-reusable. + // We should add the genesis hash also at some point + transcript.add_bytes(b"IdentityAttributesCredentials"); + transcript.append_message(b"identity_attribute_values", &id_attribute_values); + transcript.append_message(b"global_context", &context.global_context); + + // We now produce all the proofs. + + let mut id_cred_pub_share_numbers = Vec::with_capacity(id_cred_data.len()); + let mut id_cred_pub_provers = Vec::with_capacity(id_cred_data.len()); + let mut id_cred_pub_secrets = Vec::with_capacity(id_cred_data.len()); + + // create provers for knowledge of id_cred_sec. + for item in id_cred_data.iter() { + let secret = com_enc_eq::ComEncEqSecret { + value: item.share.clone(), + elgamal_rand: item.encryption_randomness.clone(), + pedersen_rand: item.randomness_cmm_to_share.clone(), + }; + + let item_prover = com_enc_eq::ComEncEq { + cipher: item.encrypted_share, + commitment: item.cmm_to_share, + pub_key: item.ar.ar_public_key, + cmm_key: context.global_context.on_chain_commitment_key, + encryption_in_exponent_generator: item.ar.ar_public_key.generator, + }; + + id_cred_pub_share_numbers.push(item.ar.ar_identity); + id_cred_pub_provers.push(item_prover); + id_cred_pub_secrets.push(secret); + } + + let choice_ar_handles = id_attribute_values + .ar_data + .keys() + .copied() + .collect::>(); + + // Proof of knowledge of the signature of the identity provider. + let (prover_sig, secret_sig) = compute_pok_sig( + &context.global_context.on_chain_commitment_key, + &commitments, + &commitment_rands, + id_cred_sec, + prf_key, + alist, + prio.choice_ar_parameters.threshold, + &choice_ar_handles, + ip_pub_key, + &blinded_sig, + blind_rand, + )?; + + let prover = AndAdapter { + first: prover_sig, + second: ReplicateAdapter { + protocols: id_cred_pub_provers, + }, + }; + + let secret = (secret_sig, id_cred_pub_secrets); + let proof = match prove(transcript, &prover, secret, &mut csprng) { + Some(x) => x, + None => bail!("Cannot produce zero knowledge proof."), + }; + + let id_proofs = IdentityAttributesCredentialsProofs { + sig: blinded_sig, + commitments, + challenge: proof.challenge, + proof_id_cred_pub: id_cred_pub_share_numbers + .into_iter() + .zip(proof.response.r2.responses) + .collect(), + proof_ip_sig: proof.response.r1, + }; + + let info = IdentityAttributesCredentialsInfo { + values: id_attribute_values, + proofs: id_proofs, + }; + + let cmm_rand = IdentityAttributesCredentialsRandomness { + attributes_rand: commitment_rands.attributes_rand, + }; + + Ok((info, cmm_rand)) +} + +#[allow(clippy::too_many_arguments)] +fn compute_pok_sig< + P: Pairing, + C: Curve, + AttributeType: Attribute, +>( + commitment_key: &PedersenKey, + commitments: &IdentityAttributesCredentialsCommitments, + commitment_rands: &CommitmentRandomness, + id_cred_sec: &Value, + prf_key: &prf::SecretKey, + alist: &AttributeList, + threshold: Threshold, + ar_list: &BTreeSet, + ip_pub_key: &crate::ps_sig::PublicKey

, + blinded_sig: &crate::ps_sig::BlindedSignature

, + blind_rand: crate::ps_sig::BlindingRandomness

, +) -> anyhow::Result<(com_eq_sig::ComEqSig, com_eq_sig::ComEqSigSecret)> { + let att_vec = &alist.alist; + // number of user chosen attributes (+4 is for tags, valid_to, created_at, + // max_accounts) + let num_user_attributes = att_vec.len() + 4; + // To these there are always two attributes (idCredSec and prf key) added. + let num_total_attributes = num_user_attributes + 2; + let ar_scalars = match utils::encode_ars(ar_list) { + Some(x) => x, + None => bail!("Cannot encode anonymity revokers."), + }; + let num_ars = ar_scalars.len(); // we commit to each anonymity revoker, with randomness 0 + // and finally we also commit to the anonymity revocation threshold. + // so the total number of commitments is as follows + let num_total_commitments = num_total_attributes + num_ars + 1; + + let y_tildas = &ip_pub_key.y_tildas; + + ensure!( + y_tildas.len() > att_vec.len() + num_ars + 5, + "The PS key must be long enough to accommodate all the attributes" + ); + + ensure!( + y_tildas.len() >= num_total_attributes, + "Too many attributes {} >= {}", + y_tildas.len(), + num_total_attributes + ); + + let mut gxs = Vec::with_capacity(num_total_commitments); + + let mut secrets = Vec::with_capacity(num_total_commitments); + secrets.push(( + id_cred_sec.clone(), + commitment_rands.id_cred_sec_rand.clone(), + )); + gxs.push(y_tildas[0]); + secrets.push((prf_key.to_value(), commitment_rands.prf_rand.clone())); + gxs.push(y_tildas[1]); + + let public_vals = + utils::encode_public_credential_values(alist.created_at, alist.valid_to, threshold)?; + + // commitment randomness (0) for the public parameters. + let zero = PedersenRandomness::::zero(); + secrets.push((Value::new(public_vals), zero.clone())); + gxs.push(y_tildas[2]); + for i in 3..num_ars + 3 { + // the encoded id revoker are commited with randomness 0. + secrets.push((Value::new(ar_scalars[i - 3]), zero.clone())); + gxs.push(y_tildas[i]); + } + + let att_rands = &commitment_rands.attributes_rand; + + let tags_val = utils::encode_tags(alist.alist.keys())?; + let tags_cmm = commitment_key.hide_worker(&tags_val, &zero); + + let max_accounts_val = Value::new(C::scalar_from_u64(alist.max_accounts.into())); + let max_accounts_cmm = + commitment_key.hide(&max_accounts_val, &commitment_rands.max_accounts_rand); + + secrets.push((Value::new(tags_val), zero.clone())); + gxs.push(y_tildas[num_ars + 3]); + secrets.push((max_accounts_val, commitment_rands.max_accounts_rand.clone())); + gxs.push(y_tildas[num_ars + 4]); + + // NB: It is crucial here that we use a btreemap. This guarantees that + // the att_vec.iter() iterator is ordered by keys. + for (&g, (tag, v)) in y_tildas.iter().skip(num_ars + 3 + 1).zip(att_vec.iter()) { + secrets.push(( + Value::new(v.to_field_element()), + // if we commited with non-zero randomness get it. + // otherwise we must have commited with zero randomness + // which we should use + att_rands.get(tag).cloned().unwrap_or_else(|| zero.clone()), + )); + gxs.push(g); + } + + let mut comm_vec = Vec::with_capacity(num_total_commitments); + let cmm_id_cred_sec = commitments.cmm_id_cred_sec_sharing_coeff[0]; + comm_vec.push(cmm_id_cred_sec); + comm_vec.push(commitments.cmm_prf); + + // add commitment to threshold with randomness 0 + comm_vec.push(commitment_key.hide_worker(&public_vals, &zero)); + + // and all commitments to ARs with randomness 0 + for ar in ar_scalars.iter() { + comm_vec.push(commitment_key.hide_worker(ar, &zero)); + } + + comm_vec.push(tags_cmm); + comm_vec.push(max_accounts_cmm); + + for (idx, v) in alist.alist.iter() { + match commitments.cmm_attributes.get(idx) { + None => { + // need to commit with randomness 0 + let value = Value::::new(v.to_field_element()); + let cmm = commitment_key.hide(&value, &zero); + comm_vec.push(cmm); + } + Some(cmm) => comm_vec.push(*cmm), + } + } + + let secret = com_eq_sig::ComEqSigSecret { + blind_rand, + values_and_rands: secrets, + }; + let prover = com_eq_sig::ComEqSig { + blinded_sig: blinded_sig.clone(), + commitments: comm_vec, + ps_pub_key: ip_pub_key.clone(), + comm_key: *commitment_key, + }; + Ok((prover, secret)) +} + +/// Randomness for commitments +struct CommitmentRandomness { + /// Randomness of the commitment to idCredSec. + id_cred_sec_rand: PedersenRandomness, + /// Randomness of the commitment to the PRF key. + prf_rand: PedersenRandomness, + /// Randomness of the commitment to the maximum number of accounts the user + /// may create from the identity object. + max_accounts_rand: PedersenRandomness, + /// Randomness, if any, used to commit to user-chosen attributes, such as + /// country of nationality. + attributes_rand: BTreeMap>, +} + +/// Computing the commitments for the credential deployment info. We only +/// compute commitments for values that are not revealed as part of the policy. +/// For the other values the verifier (the chain) will compute commitments with +/// randomness 0 in order to verify knowledge of the signature. +fn compute_commitments, R: Rng>( + commitment_key: &PedersenKey, + alist: &AttributeList, + prf_key: &prf::SecretKey, + cmm_id_cred_sec_sharing_coeff: &[Commitment], + cmm_coeff_randomness: Vec>, + policy: &Policy, + csprng: &mut R, +) -> anyhow::Result<( + IdentityAttributesCredentialsCommitments, + CommitmentRandomness, +)> { + let id_cred_sec_rand = if let Some(v) = cmm_coeff_randomness.first() { + v.clone() + } else { + bail!("Commitment randomness is an empty vector."); + }; + + let (cmm_prf, prf_rand) = commitment_key.commit(&prf_key, csprng); + + let max_accounts = Value::::new(C::scalar_from_u64(u64::from(alist.max_accounts))); + let (cmm_max_accounts, max_accounts_rand) = commitment_key.commit(&max_accounts, csprng); + let att_vec = &alist.alist; + let n = att_vec.len(); + // only commitments to attributes which are not revealed. + ensure!( + n >= policy.policy_vec.len(), + "Attribute list is shorter than the number of revealed items in the policy." + ); + let mut cmm_attributes = BTreeMap::new(); + let mut attributes_rand = BTreeMap::new(); + for (&i, val) in att_vec.iter() { + // in case the value is openened there is no need to hide it. + // We can just commit with randomness 0. + if !policy.policy_vec.contains_key(&i) { + let value = Value::::new(val.to_field_element()); + let (cmm, attr_rand) = commitment_key.commit(&value, csprng); + cmm_attributes.insert(i, cmm); + attributes_rand.insert(i, attr_rand); + } + } + let id_attr_cmms = IdentityAttributesCredentialsCommitments { + cmm_prf, + cmm_max_accounts, + cmm_attributes, + cmm_id_cred_sec_sharing_coeff: cmm_id_cred_sec_sharing_coeff.to_owned(), + }; + + let cmm_rand = CommitmentRandomness { + id_cred_sec_rand, + prf_rand, + max_accounts_rand, + attributes_rand, + }; + Ok((id_attr_cmms, cmm_rand)) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// Reason why verification of a credential commitment failed. +pub enum AttributeCommitmentVerificationError { + IdCredPub, + Signature, + Ar, + Proof, +} + +impl Display for AttributeCommitmentVerificationError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + AttributeCommitmentVerificationError::IdCredPub => { + write!(f, "IdCredPubVerificationError") + } + AttributeCommitmentVerificationError::Signature => { + write!(f, "SignatureVerificationError") + } + AttributeCommitmentVerificationError::Ar => { + write!(f, "AnonymityRevokerVerificationError") + } + AttributeCommitmentVerificationError::Proof => write!(f, "ProofVerificationError"), + } + } +} +/// Verify attribute commitments created from identity credential. +pub fn verify_identity_attributes< + P: Pairing, + C: Curve, + AttributeType: Attribute, + A: HasArPublicKey, +>( + global_context: &GlobalContext, + ip_info: &IpInfo

, + // NB: The following map only needs to be a superset of the ars + // in the identity attribute values. + known_ars: &BTreeMap, + id_attr_info: &IdentityAttributesCredentialsInfo, + transcript: &mut RandomOracle, +) -> Result<(), AttributeCommitmentVerificationError> { + if ip_info.ip_identity != id_attr_info.values.ip_identity { + return Err(AttributeCommitmentVerificationError::Signature); + } + // We need to check that the threshold is actually equal to + // the number of coefficients in the sharing polynomial + // (corresponding to the degree+1) + let rt_usize: usize = id_attr_info.values.threshold.into(); + if rt_usize + != id_attr_info + .proofs + .commitments + .cmm_id_cred_sec_sharing_coeff + .len() + { + return Err(AttributeCommitmentVerificationError::Ar); + } + let on_chain_commitment_key = global_context.on_chain_commitment_key; + let ip_verify_key = &ip_info.ip_verify_key; + // Compute the challenge prefix by hashing the values. + transcript.add_bytes(b"IdentityAttributesCredentials"); + transcript.append_message(b"identity_attribute_values", &id_attr_info.values); + transcript.append_message(b"global_context", &global_context); + + let commitments = &id_attr_info.proofs.commitments; + + let verifier_sig = pok_sig_verifier( + &on_chain_commitment_key, + id_attr_info.values.threshold, + &id_attr_info + .values + .ar_data + .keys() + .copied() + .collect::>(), + &id_attr_info.values.policy, + commitments, + ip_verify_key, + &id_attr_info.proofs.sig, + ); + let verifier_sig = if let Some(v) = verifier_sig { + v + } else { + return Err(AttributeCommitmentVerificationError::Signature); + }; + + let response_sig = id_attr_info.proofs.proof_ip_sig.clone(); + + let (id_cred_pub_verifier, id_cred_pub_responses) = id_cred_pub_verifier( + &on_chain_commitment_key, + known_ars, + &id_attr_info.values.ar_data, + &commitments.cmm_id_cred_sec_sharing_coeff, + &id_attr_info.proofs.proof_id_cred_pub, + )?; + + let verifier = AndAdapter { + first: verifier_sig, + second: id_cred_pub_verifier, + }; + let response = AndResponse { + r1: response_sig, + r2: id_cred_pub_responses, + }; + let proof = SigmaProof { + challenge: id_attr_info.proofs.challenge, + response, + }; + + if !verify(transcript, &verifier, &proof) { + return Err(AttributeCommitmentVerificationError::Proof); + } + + Ok(()) +} + +/// verify id_cred data +fn id_cred_pub_verifier>( + commitment_key: &CommitmentKey, + known_ars: &BTreeMap, + chain_ar_data: &BTreeMap>, + cmm_sharing_coeff: &[Commitment], + proof_id_cred_pub: &BTreeMap>, +) -> Result, AttributeCommitmentVerificationError> { + let mut provers = Vec::with_capacity(proof_id_cred_pub.len()); + let mut responses = Vec::with_capacity(proof_id_cred_pub.len()); + + // The encryptions and the proofs have to match. + if chain_ar_data.len() != proof_id_cred_pub.len() { + return Err(AttributeCommitmentVerificationError::IdCredPub); + } + + // The following relies on the fact that iterators over BTreeMap are + // over sorted values. + 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(AttributeCommitmentVerificationError::IdCredPub); + } + let cmm_share = utils::commitment_to_share(&ar_id.to_scalar::(), cmm_sharing_coeff); + + // finding the correct AR data. + let ar_info = known_ars + .get(ar_id) + .ok_or(AttributeCommitmentVerificationError::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 }, + )) +} + +/// Verify the proof of knowledge of signature on the attribute list. +/// A none return value means we cannot construct a verifier, and consequently +/// it should be interpreted as the signature being invalid. +fn pok_sig_verifier< + 'a, + P: Pairing, + C: Curve, + AttributeType: Attribute, +>( + commitment_key: &'a CommitmentKey, + threshold: Threshold, + choice_ar_parameters: &BTreeSet, + policy: &'a Policy, + commitments: &'a IdentityAttributesCredentialsCommitments, + ip_pub_key: &'a crate::ps_sig::PublicKey

, + blinded_sig: &'a crate::ps_sig::BlindedSignature

, +) -> Option> { + let ar_scalars = utils::encode_ars(choice_ar_parameters)?; + // Capacity for id_cred_sec, cmm_prf, (threshold, valid_to, created_at), tags + // ar_scalars and cmm_attributes + 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); + + // compute commitments with randomness 0 + let zero = Randomness::zero(); + let public_params = + utils::encode_public_credential_values(policy.created_at, policy.valid_to, threshold) + .ok()?; + // add commitment to public values with randomness 0 + comm_vec.push(commitment_key.hide_worker(&public_params, &zero)); + // and all commitments to ARs with randomness 0 + for ar in ar_scalars { + comm_vec.push(commitment_key.hide_worker(&ar, &zero)); + } + + let tags = { + match utils::encode_tags::( + policy + .policy_vec + .keys() + .chain(commitments.cmm_attributes.keys()), + ) { + Ok(v) => v, + Err(_) => return None, + } + }; + + // add commitment with randomness 0 for variant, valid_to and created_at + comm_vec.push(commitment_key.hide(&Value::::new(tags), &zero)); + comm_vec.push(commitments.cmm_max_accounts); + + // now, we go through the policy and remaining commitments and + // put them into the vector of commitments in order to check the signature. + // NB: It is crucial that they are put into the vector ordered by tags, since + // otherwise the signature will not check out. + // At this point we know all tags are distinct. + + let f = |v: Either<&AttributeType, &Commitment<_>>| match v { + Either::Left(v) => { + let value = Value::::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 test { + use crate::curve_arithmetic::Curve; + use crate::id::constants::{ArCurve, AttributeKind, IpPairing}; + use crate::id::identity_attributes_credentials::{ + prove_identity_attributes, verify_identity_attributes, AttributeCommitmentVerificationError, + }; + use crate::id::types::{ + ArIdentity, ArInfo, GlobalContext, IdObjectUseData, IdentityObjectV1, IpContext, IpData, + IpInfo, Policy, + }; + use crate::id::{identity_provider, test}; + use crate::random_oracle::RandomOracle; + use assert_matches::assert_matches; + use std::collections::BTreeMap; + + struct IdentityObjectFixture { + id_object: IdentityObjectV1, + id_use_data: IdObjectUseData, + ip_info: IpInfo, + ars_infos: BTreeMap>, + global_ctx: GlobalContext, + } + + /// Create identity object for use in tests + fn identity_object_fixture() -> IdentityObjectFixture { + let mut csprng = rand::thread_rng(); + + let max_attrs = 10; + let num_ars = 5; + let IpData { + public_ip_info: ip_info, + ip_secret_key, + .. + } = test::test_create_ip_info(&mut csprng, num_ars, max_attrs); + + let global_ctx = GlobalContext::generate(String::from("genesis_string")); + + let (ars_infos, _ars_secret) = + test::test_create_ars(&global_ctx.on_chain_commitment_key.g, num_ars, &mut csprng); + + let id_use_data = test::test_create_id_use_data(&mut csprng); + let (context, pio, _randomness) = + test::test_create_pio_v1(&id_use_data, &ip_info, &ars_infos, &global_ctx, num_ars); + let alist = test::test_create_attributes(); + let ip_sig = + identity_provider::verify_credentials_v1(&pio, context, &alist, &ip_secret_key) + .expect("verify credentials"); + + let id_object = IdentityObjectV1 { + pre_identity_object: pio, + alist: alist.clone(), + signature: ip_sig, + }; + + IdentityObjectFixture { + id_object, + id_use_data, + ars_infos, + ip_info, + global_ctx, + } + } + + fn ip_context(id_object_fixture: &IdentityObjectFixture) -> IpContext<'_, IpPairing, ArCurve> { + IpContext { + ip_info: &id_object_fixture.ip_info, + ars_infos: &id_object_fixture.ars_infos, + global_context: &id_object_fixture.global_ctx, + } + } + + /// Test that the verifier accepts a valid proof + #[test] + pub fn test_identity_attributes_completeness() { + let id_object_fixture = identity_object_fixture(); + + let policy = Policy { + valid_to: id_object_fixture.id_object.alist.valid_to, + created_at: id_object_fixture.id_object.alist.created_at, + policy_vec: Default::default(), + _phantom: Default::default(), + }; + + let mut transcript = RandomOracle::empty(); + let (id_attr_info, _) = prove_identity_attributes( + ip_context(&id_object_fixture), + &id_object_fixture.id_object, + &id_object_fixture.id_use_data, + policy, + &mut transcript, + ) + .expect("prove"); + + let mut transcript = RandomOracle::empty(); + verify_identity_attributes( + &id_object_fixture.global_ctx, + &id_object_fixture.ip_info, + &id_object_fixture.ars_infos, + &id_attr_info, + &mut transcript, + ) + .expect("verify"); + + println!("transcript log:\n {:#?}", transcript.log()) + } + + /// Test that the verifier accepts a valid proof. Test variant with revealed attribute values + #[test] + pub fn test_identity_attributes_completeness_with_revealed_attributes() { + let id_object_fixture = identity_object_fixture(); + let reveal = id_object_fixture + .id_object + .alist + .alist + .first_key_value() + .unwrap(); + + let policy = Policy { + valid_to: id_object_fixture.id_object.alist.valid_to, + created_at: id_object_fixture.id_object.alist.created_at, + policy_vec: [(*reveal.0, reveal.1.clone())].into_iter().collect(), + _phantom: Default::default(), + }; + + let mut transcript = RandomOracle::empty(); + let (id_attr_info, _) = prove_identity_attributes( + ip_context(&id_object_fixture), + &id_object_fixture.id_object, + &id_object_fixture.id_use_data, + policy, + &mut transcript, + ) + .expect("prove"); + + let mut transcript = RandomOracle::empty(); + verify_identity_attributes( + &id_object_fixture.global_ctx, + &id_object_fixture.ip_info, + &id_object_fixture.ars_infos, + &id_attr_info, + &mut transcript, + ) + .expect("verify"); + } + + /// Test that the verifier does not accept the proof if the + /// id cred pub encryption + #[test] + pub fn test_identity_attributes_soundness_ar_shares_encryption() { + let id_object_fixture = identity_object_fixture(); + + let policy = Policy { + valid_to: id_object_fixture.id_object.alist.valid_to, + created_at: id_object_fixture.id_object.alist.created_at, + policy_vec: Default::default(), + _phantom: Default::default(), + }; + + let mut transcript = RandomOracle::empty(); + let (mut id_attr_info, _) = prove_identity_attributes( + ip_context(&id_object_fixture), + &id_object_fixture.id_object, + &id_object_fixture.id_use_data, + policy, + &mut transcript, + ) + .expect("prove"); + + // make one of the ar share encryptions invalid + let enc = id_attr_info.values.ar_data.values_mut().next().unwrap(); + enc.enc_id_cred_pub_share.1 = enc + .enc_id_cred_pub_share + .1 + .plus_point(&ArCurve::one_point()); + + let mut transcript = RandomOracle::empty(); + let res = verify_identity_attributes( + &id_object_fixture.global_ctx, + &id_object_fixture.ip_info, + &id_object_fixture.ars_infos, + &id_attr_info, + &mut transcript, + ); + + assert_matches!(res, Err(AttributeCommitmentVerificationError::Proof)); + } + + /// Test that the verifier fails if identity provider is not set correctly. + #[test] + pub fn test_identity_attributes_soundness_ip() { + let id_object_fixture = identity_object_fixture(); + + let policy = Policy { + valid_to: id_object_fixture.id_object.alist.valid_to, + created_at: id_object_fixture.id_object.alist.created_at, + policy_vec: Default::default(), + _phantom: Default::default(), + }; + + let mut transcript = RandomOracle::empty(); + let (mut id_attr_info, _) = prove_identity_attributes( + ip_context(&id_object_fixture), + &id_object_fixture.id_object, + &id_object_fixture.id_use_data, + policy, + &mut transcript, + ) + .expect("prove"); + + id_attr_info.values.ip_identity.0 += 1; + + let mut transcript = RandomOracle::empty(); + let res = verify_identity_attributes( + &id_object_fixture.global_ctx, + &id_object_fixture.ip_info, + &id_object_fixture.ars_infos, + &id_attr_info, + &mut transcript, + ); + assert_matches!(res, Err(AttributeCommitmentVerificationError::Signature)); + } + + /// Test that the verifier does not accept the proof if the + /// identity provider signature does not match the provided values. + #[test] + pub fn test_identity_attributes_soundness_ip_signature() { + let id_object_fixture = identity_object_fixture(); + + let policy = Policy { + valid_to: id_object_fixture.id_object.alist.valid_to, + created_at: id_object_fixture.id_object.alist.created_at, + policy_vec: Default::default(), + _phantom: Default::default(), + }; + + let mut transcript = RandomOracle::empty(); + let (id_attr_info, _) = prove_identity_attributes( + ip_context(&id_object_fixture), + &id_object_fixture.id_object, + &id_object_fixture.id_use_data, + policy, + &mut transcript, + ) + .expect("prove"); + + // change one of the public values in the signature: decrease ar threshold + let mut id_attr_info_invalid = id_attr_info.clone(); + id_attr_info_invalid.values.threshold.0 -= 1; + id_attr_info_invalid + .proofs + .commitments + .cmm_id_cred_sec_sharing_coeff + .pop(); + + let mut transcript = RandomOracle::empty(); + let res = verify_identity_attributes( + &id_object_fixture.global_ctx, + &id_object_fixture.ip_info, + &id_object_fixture.ars_infos, + &id_attr_info_invalid, + &mut transcript, + ); + assert_matches!(res, Err(AttributeCommitmentVerificationError::Proof)); + + // change one of the public values in the signature: remove one of the ars + let mut id_attr_info_invalid = id_attr_info.clone(); + let ar_to_remove = *id_attr_info_invalid.values.ar_data.keys().next().unwrap(); + id_attr_info_invalid.values.ar_data.remove(&ar_to_remove); + id_attr_info_invalid + .proofs + .proof_id_cred_pub + .remove(&ar_to_remove); + + let mut transcript = RandomOracle::empty(); + let res = verify_identity_attributes( + &id_object_fixture.global_ctx, + &id_object_fixture.ip_info, + &id_object_fixture.ars_infos, + &id_attr_info_invalid, + &mut transcript, + ); + assert_matches!(res, Err(AttributeCommitmentVerificationError::Proof)); + + // change one of the committed values in the signature + let mut id_attr_info_invalid = id_attr_info.clone(); + let attr_cmm = id_attr_info_invalid + .proofs + .commitments + .cmm_attributes + .values_mut() + .next() + .unwrap(); + attr_cmm.0 = attr_cmm.0.plus_point(&ArCurve::one_point()); + + let mut transcript = RandomOracle::empty(); + let res = verify_identity_attributes( + &id_object_fixture.global_ctx, + &id_object_fixture.ip_info, + &id_object_fixture.ars_infos, + &id_attr_info_invalid, + &mut transcript, + ); + assert_matches!(res, Err(AttributeCommitmentVerificationError::Proof)); + } +} diff --git a/rust-src/concordium_base/src/id/identity_provider.rs b/rust-src/concordium_base/src/id/identity_provider.rs index e229c600b..1d5dc0638 100644 --- a/rust-src/concordium_base/src/id/identity_provider.rs +++ b/rust-src/concordium_base/src/id/identity_provider.rs @@ -591,6 +591,7 @@ pub fn compute_message>( // - created_at and valid_to dates of the attribute list // - encoding of anonymity revokers. // - tags of the attribute list + // - max accounts // - attribute list elements let ar_encoded = match utils::encode_ars(ar_list) { diff --git a/rust-src/concordium_base/src/id/mod.rs b/rust-src/concordium_base/src/id/mod.rs index a6b5720f2..742f0bc8d 100644 --- a/rust-src/concordium_base/src/id/mod.rs +++ b/rust-src/concordium_base/src/id/mod.rs @@ -10,7 +10,7 @@ mod ffi; pub mod id_proof_types; pub mod id_prover; pub mod id_verifier; -pub mod identity_attributes; +pub mod identity_attributes_credentials; pub mod identity_provider; pub mod secret_sharing; pub mod types; diff --git a/rust-src/concordium_base/src/id/types.rs b/rust-src/concordium_base/src/id/types.rs index be934e143..f5f701927 100644 --- a/rust-src/concordium_base/src/id/types.rs +++ b/rust-src/concordium_base/src/id/types.rs @@ -2703,15 +2703,12 @@ impl HasAttributeRandomness for SystemAttributeRandomness { } } -/// The commitments produced by identity attribute commitments created from identity credential +/// The commitments produced by identity attribute credentials created from identity credential #[derive(Debug, PartialEq, Eq, Clone, Serialize, SerdeSerialize, SerdeDeserialize)] -pub struct IdentityAttributesCommitments { +pub struct IdentityAttributesCredentialsCommitments { /// commitment to the prf key #[serde(rename = "cmmPrf")] pub cmm_prf: PedersenCommitment, - /// commitment to credential counter - #[serde(rename = "cmmCredCounter")] - pub cmm_cred_counter: PedersenCommitment, /// commitment to the max account number. #[serde(rename = "cmmMaxAccounts")] pub cmm_max_accounts: PedersenCommitment, @@ -2731,37 +2728,20 @@ pub struct IdentityAttributesCommitments { } /// Randomness that is generated to commit to attributes when -/// proving identity attribute commitments. +/// proving identity attribute credentials. /// This randomness is needed later on if the user wishes to do /// something with those commitments, for example reveal the commited value, or /// prove a property of the value. -#[derive(SerdeSerialize, SerdeDeserialize)] -pub struct IdentityAttributesCommitmentRandomness { - #[serde(rename = "idCredSecRand")] - /// Randomness of the commitment to idCredSec. - pub id_cred_sec_rand: PedersenRandomness, - #[serde(rename = "prfRand")] - /// Randomness of the commitment to the PRF key. - pub prf_rand: PedersenRandomness, - #[serde(rename = "credCounterRand")] - /// Randomness of the commitment to the credential nonce. This nonce is the - /// number that is used to ensure that only a limited number of credentials - /// can be created from a given identity object. - pub cred_counter_rand: PedersenRandomness, - #[serde(rename = "maxAccountsRand")] - /// Randomness of the commitment to the maximum number of accounts the user - /// may create from the identity object. - pub max_accounts_rand: PedersenRandomness, - #[serde(rename = "attributesRand")] +pub struct IdentityAttributesCredentialsRandomness { /// Randomness, if any, used to commit to user-chosen attributes, such as /// country of nationality. - pub attributes_rand: HashMap>, + pub attributes_rand: BTreeMap>, } -/// This structure contains all proofs, which are required to prove identity attributes commitments created +/// This structure contains all proofs, which are required to prove identity attributes credentials created /// from identity credential. #[derive(Debug, Serialize, SerdeSerialize, SerdeDeserialize, Clone)] -pub struct IdentityAttributesCommitmentProofs> { +pub struct IdentityAttributesCredentialsProofs> { /// (Blinded) Signature derived from the signature on the pre-identity /// object by the IP #[serde( @@ -2776,7 +2756,7 @@ pub struct IdentityAttributesCommitmentProofs, + pub commitments: IdentityAttributesCredentialsCommitments, /// Challenge used for all of the proofs. #[serde( rename = "challenge", @@ -2802,36 +2782,11 @@ pub struct IdentityAttributesCommitmentProofs, - /// Proof that reg_id = prf_K(x). Also establishes that reg_id is computed - /// from the prf key signed by the identity provider. - #[serde( - rename = "proofRegId", - serialize_with = "base16_encode", - deserialize_with = "base16_decode" - )] - pub proof_reg_id: com_mult::Response, - /// Proof that cred_counter is less than or equal to max_accounts - #[serde( - rename = "credCounterLessThanMaxAccounts", - serialize_with = "base16_encode", - deserialize_with = "base16_decode" - )] - pub cred_counter_less_than_max_accounts: RangeProof, } -/// Values (as opposed to proofs) in identity attribute commitments created from identity credential. +/// Values (as opposed to proofs) in identity attribute credentials created from identity credential. #[derive(Debug, PartialEq, Eq, Serialize, SerdeSerialize, SerdeDeserialize, Clone)] -pub struct IdentityAttributesCommitmentValues> { - /// Credential keys (i.e. account holder keys). - #[serde(rename = "credentialPublicKeys")] - pub cred_key_info: CredentialPublicKeys, - /// Credential registration id of the credential. - #[serde( - rename = "credId", - serialize_with = "base16_encode", - deserialize_with = "base16_decode" - )] - pub cred_id: CredId, +pub struct IdentityAttributesCredentialsValues> { /// Identity of the identity provider who signed the identity object from /// which this credential is derived. #[serde(rename = "ipIdentity")] @@ -2851,18 +2806,18 @@ pub struct IdentityAttributesCommitmentValues, } -/// Identity attributes commitments created from identity credential, and proofs that it is +/// Identity attributes credentials created from identity credential, and proofs that it is /// well-formed. #[derive(Debug, Serialize, SerdeSerialize, SerdeDeserialize, Clone)] -pub struct IdentityAttributesCommitmentInfo< +pub struct IdentityAttributesCredentialsInfo< P: Pairing, C: Curve, AttributeType: Attribute, > { #[serde(flatten)] - pub values: IdentityAttributesCommitmentValues, + pub values: IdentityAttributesCredentialsValues, #[serde(rename = "proofs")] - pub proofs: IdentityAttributesCommitmentProofs, + pub proofs: IdentityAttributesCredentialsProofs, } /// A request for recovering an identity diff --git a/rust-src/concordium_base/src/random_oracle/mod.rs b/rust-src/concordium_base/src/random_oracle/mod.rs index f0a52138f..6c60b1427 100644 --- a/rust-src/concordium_base/src/random_oracle/mod.rs +++ b/rust-src/concordium_base/src/random_oracle/mod.rs @@ -14,12 +14,38 @@ use crate::{common::*, curve_arithmetic::Curve}; use sha3::{Digest, Sha3_256}; +use std::fmt::{Debug, Formatter}; use std::io::Write; /// State of the random oracle, used to incrementally build up the output. -#[repr(transparent)] +#[cfg_attr(not(test), repr(transparent))] #[derive(Debug)] -pub struct RandomOracle(Sha3_256); +pub struct RandomOracle { + /// Hash of all input given to the oracle + hash: Sha3_256, + /// All operations performed on the oracle. Used for security analysis + #[cfg(test)] + log: Vec, +} + +#[derive(Clone)] +pub enum RandomOracleLog { + Bytes(Vec), +} + +impl Debug for RandomOracleLog { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + RandomOracleLog::Bytes(bytes) => { + if let Ok(string) = String::from_utf8(bytes.clone()) { + f.write_str(&string) + } else { + f.write_str("") + } + } + } + } +} /// Type of challenges computed from the random oracle. /// We use 32 byte output of SHA3-256 @@ -39,13 +65,17 @@ impl AsRef<[u8]> for Challenge { impl Write for RandomOracle { #[inline(always)] fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.0.update(buf); + self.hash.update(buf); + #[cfg(test)] + self.log.push(RandomOracleLog::Bytes(buf.to_vec())); Ok(buf.len()) } #[inline(always)] fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { - self.0.update(buf); + self.hash.update(buf); + #[cfg(test)] + self.log.push(RandomOracleLog::Bytes(buf.to_vec())); Ok(()) } @@ -67,7 +97,7 @@ impl Buffer for RandomOracle { // Compute the result in the given state, consuming the state. fn result(self) -> Self::Result { - self.0.finalize() + self.hash.finalize() } } @@ -75,25 +105,37 @@ impl Eq for RandomOracle {} impl PartialEq for RandomOracle { fn eq(&self, other: &Self) -> bool { - self.0.clone().finalize() == other.0.clone().finalize() + self.hash.clone().finalize() == other.hash.clone().finalize() } } impl RandomOracle { /// Start with the initial empty state of the oracle. pub fn empty() -> Self { - RandomOracle(Sha3_256::new()) + RandomOracle { + hash: Sha3_256::new(), + #[cfg(test)] + log: Default::default(), + } } /// Start with the initial domain string. pub fn domain>(data: B) -> Self { - RandomOracle(Sha3_256::new().chain_update(data)) + RandomOracle { + hash: Sha3_256::new().chain_update(data), + #[cfg(test)] + log: Default::default(), + } } /// Duplicate the random oracle, creating a fresh copy of it. /// Further updates are independent. pub fn split(&self) -> Self { - RandomOracle(self.0.clone()) + RandomOracle { + hash: self.hash.clone(), + #[cfg(test)] + log: self.log.clone(), + } } /// Append the input to the state of the oracle. @@ -102,7 +144,10 @@ impl RandomOracle { } pub fn add_bytes>(&mut self, data: B) { - self.0.update(data) + self.hash.update(data.as_ref()); + #[cfg(test)] + self.log + .push(RandomOracleLog::Bytes(data.as_ref().to_vec())); } /// Append the input to the state of the oracle, using `label` as domain @@ -146,6 +191,11 @@ impl RandomOracle { self.add_bytes(label); self.split().result_to_scalar::() } + + #[cfg(test)] + pub fn log(&self) -> &[RandomOracleLog] { + &self.log + } } #[cfg(test)]