From 731734fd69c2c0ccca583adc01aeebc622c948c9 Mon Sep 17 00:00:00 2001 From: "artem.ivanov" Date: Wed, 15 Nov 2023 11:58:45 +0300 Subject: [PATCH] Support case incentive case + refactored presentation creation/verification Signed-off-by: artem.ivanov --- src/services/conversion.rs | 2 +- src/services/helpers.rs | 16 ++ src/services/prover.rs | 248 +++++++++++---------- src/services/types.rs | 2 +- src/services/verifier.rs | 427 ++++++++++++++++++++----------------- tests/anoncreds_demos.rs | 173 ++++++++++++++- 6 files changed, 558 insertions(+), 310 deletions(-) diff --git a/src/services/conversion.rs b/src/services/conversion.rs index 571d1369..e9355410 100644 --- a/src/services/conversion.rs +++ b/src/services/conversion.rs @@ -228,7 +228,7 @@ mod tests { use super::*; use crate::data_types::cred_def::CredentialDefinitionId; use crate::data_types::issuer_id::IssuerId; - use crate::data_types::schema::{Schema, SchemaId}; + use crate::data_types::schema::SchemaId; use crate::data_types::w3c::constants::{ANONCREDS_CONTEXTS, ANONCREDS_TYPES}; use crate::data_types::w3c::one_or_many::OneOrMany; use crate::types::{ diff --git a/src/services/helpers.rs b/src/services/helpers.rs index 244a729c..df0f9686 100644 --- a/src/services/helpers.rs +++ b/src/services/helpers.rs @@ -5,6 +5,7 @@ use crate::cl::{ use crate::data_types::presentation::RequestedProof; use crate::data_types::rev_reg_def::RevocationRegistryDefinitionId; use crate::data_types::schema::Schema; +use crate::data_types::w3c::credential::W3CCredential; use crate::data_types::{ credential::CredentialValues, link_secret::LinkSecret, @@ -13,6 +14,7 @@ use crate::data_types::{ }; use crate::error::Result; use crate::utils::hash::SHA256; +use serde_json::Value; use std::collections::{HashMap, HashSet}; pub fn attr_common_view(attr: &str) -> String { @@ -277,3 +279,17 @@ impl RequestedProof { referents } } + +impl W3CCredential { + pub(crate) fn get_attribute(&self, requested_attribute: &str) -> Result<(String, Value)> { + for (attribute, value) in self.credential_subject.attributes.0.iter() { + if attr_common_view(attribute) == attr_common_view(requested_attribute) { + return Ok((attribute.to_owned(), value.to_owned())); + } + } + Err(err_msg!( + "Credential attribute {} not found", + requested_attribute + )) + } +} diff --git a/src/services/prover.rs b/src/services/prover.rs index 1c31262d..3c9a0a57 100644 --- a/src/services/prover.rs +++ b/src/services/prover.rs @@ -37,7 +37,7 @@ use crate::data_types::w3c::presentation_proof::{ PresentationProof, PresentationProofValue, }; use anoncreds_clsignatures::{ - CredentialSignature as CLCredentialSignature, NonCredentialSchema, ProofBuilder, + CredentialSignature as CLCredentialSignature, NonCredentialSchema, Proof, ProofBuilder, SignatureCorrectnessProof, }; use bitvec::bitvec; @@ -578,8 +578,7 @@ pub fn create_presentation( let mut sub_proof_index = 0; let mut identifiers: Vec = Vec::with_capacity(credentials.len()); - let mut proof_builder = _proof_builder()?; - let non_credential_schema = build_non_credential_schema()?; + let mut proof_builder = CLProofBuilder::init(pres_req_val, schemas, cred_defs)?; for present in credentials.0 { if present.is_empty() { @@ -596,18 +595,13 @@ pub fn create_presentation( &mut requested_proof, )?; - _add_sub_proof( - &mut proof_builder, - pres_req_val, + proof_builder.add_sub_proof( &credential.values, &credential.signature, link_secret, &present, - &non_credential_schema, &credential.schema_id, - schemas, &credential.cred_def_id, - cred_defs, credential.rev_reg_id.as_ref(), )?; @@ -626,7 +620,7 @@ pub fn create_presentation( sub_proof_index += 1; } - let proof = proof_builder.finalize(pres_req_val.nonce.as_native())?; + let proof = proof_builder.build()?; let full_proof = Presentation { proof, @@ -659,9 +653,8 @@ pub fn create_w3c_presentation( credentials.validate()?; let pres_req = pres_req.value(); - let mut proof_builder = _proof_builder()?; - let non_credential_schema = build_non_credential_schema()?; + let mut proof_builder = CLProofBuilder::init(pres_req, schemas, cred_defs)?; for present in credentials.0.iter() { if present.is_empty() { @@ -675,23 +668,18 @@ pub fn create_w3c_presentation( let proof = credential.get_credential_signature_proof()?; let signature = proof.get_credential_signature()?; - _add_sub_proof( - &mut proof_builder, - pres_req, + proof_builder.add_sub_proof( &credential_values, &signature.signature, link_secret, present, - &non_credential_schema, &credential.credential_schema.schema, - schemas, &credential.credential_schema.definition, - cred_defs, credential.credential_schema.revocation_registry.as_ref(), )?; } - let cl_proof = proof_builder.finalize(pres_req.nonce.as_native())?; + let cl_proof = proof_builder.build()?; let presentation_proof_value = PresentationProofValue::new(cl_proof.aggregated_proof); let presentation_proof = @@ -729,83 +717,6 @@ fn _proof_builder() -> Result { Ok(proof_builder) } -#[allow(clippy::too_many_arguments)] -fn _add_sub_proof( - proof_builder: &mut ProofBuilder, - presentation_request: &PresentationRequestPayload, - credential_values: &CredentialValues, - credential_signature: &anoncreds_clsignatures::CredentialSignature, - link_secret: &LinkSecret, - present: &PresentCredential, - non_credential_schema: &NonCredentialSchema, - schema_id: &SchemaId, - schemas: &HashMap, - cred_def_id: &CredentialDefinitionId, - cred_defs: &HashMap, - rev_reg_id: Option<&RevocationRegistryDefinitionId>, -) -> Result<()> { - let schema = schemas - .get(schema_id) - .ok_or_else(|| err_msg!("Schema not provided for ID: {}", schema_id))?; - - let cred_def = cred_defs - .get(cred_def_id) - .ok_or_else(|| err_msg!("Credential Definition not provided for ID: {}", cred_def_id))?; - - let credential_pub_key = CredentialPublicKey::build_from_parts( - &cred_def.value.primary, - cred_def.value.revocation.as_ref(), - )?; - - let credential_schema = build_credential_schema(schema)?; - let credential_values = build_credential_values(credential_values, Some(link_secret))?; - - let attributes = present.attribute_referents(); - - let (attrs_for_credential, attrs_nonrevoked_interval) = - presentation_request.get_requested_attributes(&attributes)?; - let (predicates_for_credential, pred_nonrevoked_interval) = - presentation_request.get_requested_predicates(&present.requested_predicates)?; - - let non_revoked_interval = get_non_revoked_interval( - attrs_nonrevoked_interval, - pred_nonrevoked_interval, - presentation_request, - rev_reg_id, - None, - ); - - let sub_proof_request = - build_sub_proof_request(&attrs_for_credential, &predicates_for_credential)?; - - // Checks conditions to add revocation proof - let (rev_reg, witness) = match non_revoked_interval { - Some(_interval) => ( - present.rev_state.as_ref().map(|r_info| &r_info.rev_reg), - present.rev_state.as_ref().map(|r_info| &r_info.witness), - ), - None => (None, None), - }; - - // if `present.rev_state` is available, - // then it will create an init_proof that contains NRP. - // - // Therefore, this will have to be part of the finalised `aggregated_proof`. - // Regardless if nonrevoke_interval is requested by the verifier - proof_builder.add_sub_proof_request( - &sub_proof_request, - &credential_schema, - non_credential_schema, - credential_signature, - &credential_values, - &credential_pub_key, - rev_reg, - witness, - )?; - - Ok(()) -} - /// Create a [`CredentialRevocationState`] based on a [`Witness`], [`RevocationStatusList`] and /// timestamp. pub fn create_revocation_state_with_witness( @@ -1142,28 +1053,19 @@ fn build_credential_subject<'p>( .ok_or_else(|| err_msg!("attribute {} not found request", referent))?; if let Some(ref name) = requested_attribute.name { - let value = credentials - .cred - .credential_subject - .attributes - .get_attribute(name)?; - + let (attribute, value) = credentials.cred.get_attribute(name)?; if *reveal { credential_subject .attributes - .add_attribute(name.to_string(), value.to_owned()); + .add_attribute(attribute, value); } } if let Some(ref names) = requested_attribute.names { for name in names { - let value = credentials - .cred - .credential_subject - .attributes - .get_attribute(name)?; + let (attribute, value) = credentials.cred.get_attribute(name)?; credential_subject .attributes - .add_attribute(name.to_string(), value.to_owned()); + .add_attribute(attribute, value); } } } @@ -1174,14 +1076,136 @@ fn build_credential_subject<'p>( .get(referent) .unwrap() .clone(); - let name = predicate_info.name.to_owned(); - let attribute = PredicateAttribute::from(predicate_info); - credential_subject.attributes.add_predicate(name, attribute); + let (attribute, _) = credentials.cred.get_attribute(&predicate_info.name)?; + let predicate = PredicateAttribute::from(predicate_info); + credential_subject + .attributes + .add_predicate(attribute, predicate); } Ok(credential_subject) } +struct CLProofBuilder<'a> { + proof_builder: ProofBuilder, + non_credential_schema: NonCredentialSchema, + presentation_request: &'a PresentationRequestPayload, + schemas: &'a HashMap, + cred_defs: &'a HashMap, +} + +impl<'a> CLProofBuilder<'a> { + fn init( + presentation_request: &'a PresentationRequestPayload, + schemas: &'a HashMap, + cred_defs: &'a HashMap, + ) -> Result> { + let mut proof_builder = Prover::new_proof_builder()?; + proof_builder.add_common_attribute("master_secret")?; + let non_credential_schema = build_non_credential_schema()?; + Ok(CLProofBuilder { + proof_builder, + non_credential_schema, + presentation_request, + schemas, + cred_defs, + }) + } + + #[allow(clippy::too_many_arguments)] + fn add_sub_proof( + &mut self, + credential_values: &CredentialValues, + credential_signature: &anoncreds_clsignatures::CredentialSignature, + link_secret: &LinkSecret, + present: &PresentCredential, + schema_id: &SchemaId, + cred_def_id: &CredentialDefinitionId, + rev_reg_id: Option<&RevocationRegistryDefinitionId>, + ) -> Result<()> { + let schema = self.get_schema(schema_id)?; + let cred_def = self.get_credential_definition(cred_def_id)?; + + let credential_pub_key = CredentialPublicKey::build_from_parts( + &cred_def.value.primary, + cred_def.value.revocation.as_ref(), + )?; + + let credential_schema = build_credential_schema(schema)?; + let credential_values = build_credential_values(credential_values, Some(link_secret))?; + + let (attrs_for_credential, attrs_nonrevoked_interval) = self + .presentation_request + .get_requested_attributes(&present.requested_attributes())?; + let (predicates_for_credential, pred_nonrevoked_interval) = self + .presentation_request + .get_requested_predicates(&present.requested_predicates)?; + + let non_revoked_interval = get_non_revoked_interval( + attrs_nonrevoked_interval, + pred_nonrevoked_interval, + self.presentation_request, + rev_reg_id, + None, + ); + + let sub_proof_request = + build_sub_proof_request(&attrs_for_credential, &predicates_for_credential)?; + + // Checks conditions to add revocation proof + let (rev_reg, witness) = match non_revoked_interval { + Some(_interval) => ( + present.rev_state.as_ref().map(|r_info| &r_info.rev_reg), + present.rev_state.as_ref().map(|r_info| &r_info.witness), + ), + None => (None, None), + }; + + // if `present.rev_state` is available, + // then it will create an init_proof that contains NRP. + // + // Therefore, this will have to be part of the finalised `aggregated_proof`. + // Regardless if nonrevoke_interval is requested by the verifier + self.proof_builder.add_sub_proof_request( + &sub_proof_request, + &credential_schema, + &self.non_credential_schema, + credential_signature, + &credential_values, + &credential_pub_key, + rev_reg, + witness, + )?; + + Ok(()) + } + + fn build(&mut self) -> Result { + let proof = self + .proof_builder + .finalize(self.presentation_request.nonce.as_native())?; + Ok(proof) + } + + fn get_schema(&self, schema_id: &SchemaId) -> Result<&'a Schema> { + self.schemas + .get(schema_id) + .ok_or_else(|| err_msg!("Schema not provided for ID: {:?}", schema_id)) + } + + fn get_credential_definition( + &self, + cred_def_id: &CredentialDefinitionId, + ) -> Result<&'a CredentialDefinition> { + self.cred_defs.get(cred_def_id).ok_or_else(|| { + err_msg!( + "Credential Definition not provided for ID: {:?}", + cred_def_id + ) + }) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/services/types.rs b/src/services/types.rs index 5ae226d1..a508ebdd 100644 --- a/src/services/types.rs +++ b/src/services/types.rs @@ -178,7 +178,7 @@ impl PresentCredential<'_, T> { self.requested_attributes.is_empty() && self.requested_predicates.is_empty() } - pub(crate) fn attribute_referents(&self) -> HashSet { + pub(crate) fn requested_attributes(&self) -> HashSet { let mut referents = HashSet::new(); for (referent, revealed) in self.requested_attributes.iter() { if *revealed { diff --git a/src/services/verifier.rs b/src/services/verifier.rs index 7b5a376c..304041ea 100644 --- a/src/services/verifier.rs +++ b/src/services/verifier.rs @@ -93,8 +93,13 @@ pub fn verify_presentation( &received_self_attested_attrs, )?; - let mut proof_verifier = Verifier::new_proof_verifier()?; - let non_credential_schema = build_non_credential_schema()?; + let mut proof_verifier = CLProofVerifier::init( + pres_req, + schemas, + cred_defs, + rev_reg_defs, + rev_status_lists.as_ref(), + )?; for sub_proof_index in 0..presentation.identifiers.len() { let identifier = presentation.identifiers[sub_proof_index].clone(); @@ -106,25 +111,18 @@ pub fn verify_presentation( .requested_proof .predicate_referents(sub_proof_index as u32); - _add_sub_proof( - &mut proof_verifier, - pres_req, + proof_verifier.add_sub_proof( &attributes, &predicates, - &non_credential_schema, &identifier.schema_id, - schemas, &identifier.cred_def_id, - cred_defs, identifier.rev_reg_id.as_ref(), - rev_reg_defs, - rev_status_lists.as_ref(), identifier.timestamp, nonrevoke_interval_override, )?; } - let valid = proof_verifier.verify(&presentation.proof, pres_req.nonce.as_native())?; + let valid = proof_verifier.verify(&presentation.proof)?; trace!("verify <<< valid: {:?}", valid); @@ -182,8 +180,13 @@ pub fn verify_w3c_presentation( aggregated_proof: proof_data.aggregated, }; - let mut proof_verifier = Verifier::new_proof_verifier()?; - let non_credential_schema = build_non_credential_schema()?; + let mut proof_verifier = CLProofVerifier::init( + pres_req, + schemas, + cred_defs, + rev_reg_defs, + rev_status_lists.as_ref(), + )?; for verifiable_credential in presentation.verifiable_credential.iter() { let credential_proof = verifiable_credential.get_presentation_proof()?; @@ -199,27 +202,19 @@ pub fn verify_w3c_presentation( credential_proof.mapping.revealed_attributes.clone(); revealed_attribute.extend(credential_proof.mapping.revealed_attribute_groups.clone()); - _add_sub_proof( - &mut proof_verifier, - pres_req, + proof_verifier.add_sub_proof( &revealed_attribute, &credential_proof.mapping.predicates, - &non_credential_schema, schema_id, - schemas, cred_def_id, - cred_defs, rev_reg_id, - rev_reg_defs, - rev_status_lists.as_ref(), credential_proof.timestamp, nonrevoke_interval_override, )?; - proof.proofs.push(proof_data.sub_proof); } - let valid = proof_verifier.verify(&proof, pres_req.nonce.as_native())?; + let valid = proof_verifier.verify(&proof)?; trace!("verify <<< valid: {:?}", valid); @@ -231,166 +226,6 @@ pub fn generate_nonce() -> Result { new_nonce() } -#[allow(clippy::too_many_arguments)] -fn _add_sub_proof( - proof_verifier: &mut ProofVerifier, - pres_req: &PresentationRequestPayload, - attributes: &HashSet, - predicates: &HashSet, - non_credential_schema: &NonCredentialSchema, - schema_id: &SchemaId, - schemas: &HashMap, - cred_def_id: &CredentialDefinitionId, - cred_defs: &HashMap, - rev_reg_id: Option<&RevocationRegistryDefinitionId>, - rev_reg_defs: Option<&HashMap>, - rev_status_lists: Option<&Vec>, - timestamp: Option, - nonrevoke_interval_override: Option< - &HashMap>, - >, -) -> Result<()> { - let schema = schemas - .get(schema_id) - .ok_or_else(|| err_msg!("Schema not provided for ID: {:?}", schema_id))?; - - let cred_def = cred_defs.get(cred_def_id).ok_or_else(|| { - err_msg!( - "Credential Definition not provided for ID: {:?}", - cred_def_id - ) - })?; - - let credential_schema = build_credential_schema(schema)?; - - let credential_pub_key = CredentialPublicKey::build_from_parts( - &cred_def.value.primary, - cred_def.value.revocation.as_ref(), - )?; - - let (attrs_for_credential, attrs_nonrevoked_interval) = - pres_req.get_requested_attributes(attributes)?; - let (predicates_for_credential, pred_nonrevoked_interval) = - pres_req.get_requested_predicates(predicates)?; - - let cred_nonrevoked_interval = get_non_revoked_interval( - attrs_nonrevoked_interval, - pred_nonrevoked_interval, - pres_req, - rev_reg_id, - nonrevoke_interval_override, - ); - - let sub_pres_request = - build_sub_proof_request(&attrs_for_credential, &predicates_for_credential)?; - - let rev_reg_map = if let Some(lists) = rev_status_lists { - let mut map: HashMap> = - HashMap::new(); - - for list in lists { - let id = list - .id() - .ok_or_else(|| err_msg!(Unexpected, "RevStatusList missing Id"))?; - - let timestamp = list - .timestamp() - .ok_or_else(|| err_msg!(Unexpected, "RevStatusList missing timestamp"))?; - - let rev_reg: Option = (list).into(); - let rev_reg = rev_reg.ok_or_else(|| { - err_msg!(Unexpected, "Revocation status list missing accumulator") - })?; - - map.entry(id) - .or_insert_with(HashMap::new) - .insert(timestamp, rev_reg); - } - Some(map) - } else { - None - }; - - // Revocation checks is required iff both conditions are met: - // - Credential is revokable (input from verifier, trustable) - // - PresentationReq has asked for NRP* (input from verifier, trustable) - // - // * This is done by setting a NonRevokedInterval either for attr / predicate / global - let (rev_reg_def, rev_reg) = if let (Some(_), true) = ( - cred_def.value.revocation.as_ref(), - cred_nonrevoked_interval.is_some(), - ) { - let timestamp = timestamp - .ok_or_else(|| err_msg!("Identifier timestamp not found for revocation check"))?; - - if rev_reg_defs.is_none() { - return Err(err_msg!( - "Timestamp provided but no Revocation Registry Definitions found" - )); - } - if rev_reg_map.is_none() { - return Err(err_msg!( - "Timestamp provided but no Revocation Registries found" - )); - } - - let rev_reg_id = rev_reg_id - .ok_or_else(|| err_msg!("Revocation Registry Id not found for revocation check"))?; - - // Revocation registry definition id is the same as the rev reg id - let rev_reg_def_id = RevocationRegistryDefinitionId::new(rev_reg_id.clone())?; - - // Validate timestamp - cred_nonrevoked_interval - .map(|int| int.is_valid(timestamp)) - .transpose()?; - - let rev_reg_def = Some( - rev_reg_defs - .ok_or_else(|| err_msg!("Could not load the Revocation Registry Definition"))? - .get(&rev_reg_def_id) - .ok_or_else(|| { - err_msg!( - "Revocation Registry Definition not provided for ID: {:?}", - rev_reg_def_id - ) - })?, - ); - - let rev_reg = Some( - rev_reg_map - .as_ref() - .ok_or_else(|| err_msg!("Could not load the Revocation Registry mapping"))? - .get(&rev_reg_def_id) - .and_then(|regs| regs.get(×tamp)) - .ok_or_else(|| { - err_msg!( - "Revocation Registry not provided for ID and timestamp: {:?}, {:?}", - rev_reg_id, - timestamp - ) - })?, - ); - - (rev_reg_def, rev_reg) - } else { - (None, None) - }; - - let rev_key_pub = rev_reg_def.map(|d| &d.value.public_keys.accum_key); - - proof_verifier.add_sub_proof_request( - &sub_pres_request, - &credential_schema, - non_credential_schema, - &credential_pub_key, - rev_key_pub, - rev_reg, - )?; - - Ok(()) -} - fn compare_attr_from_proof_and_request( pres_req: &PresentationRequestPayload, received_revealed_attrs: &HashMap, @@ -1037,12 +872,7 @@ fn build_requested_proof_from_w3c_presentation( .name .ok_or_else(|| err_msg!("Requested Attribute expected to have a name attribute"))?; - let raw = credential - .credential_subject - .attributes - .0 - .get(&name) - .ok_or_else(|| err_msg!("Attribute {} not found in credential", &name))?; + let (_, raw) = credential.get_attribute(&name)?; requested_proof.revealed_attrs.insert( referent.clone(), RevealedAttributeInfo { @@ -1066,12 +896,7 @@ fn build_requested_proof_from_w3c_presentation( values: HashMap::new(), }; for name in names.iter() { - let raw = credential - .credential_subject - .attributes - .0 - .get(name) - .ok_or_else(|| err_msg!("Attribute {} not found in credential", &name))?; + let (_, raw) = credential.get_attribute(name)?; group_info.values.insert( name.clone(), AttributeValue { @@ -1098,6 +923,218 @@ fn build_requested_proof_from_w3c_presentation( Ok(requested_proof) } +fn build_revocation_registry_map( + rev_status_lists: Option<&Vec>, +) -> Result>>> { + let rev_reg_map = if let Some(lists) = rev_status_lists { + let mut map: HashMap> = + HashMap::new(); + + for list in lists { + let id = list + .id() + .ok_or_else(|| err_msg!(Unexpected, "RevStatusList missing Id"))?; + + let timestamp = list + .timestamp() + .ok_or_else(|| err_msg!(Unexpected, "RevStatusList missing timestamp"))?; + + let rev_reg: Option = (list).into(); + let rev_reg = rev_reg.ok_or_else(|| { + err_msg!(Unexpected, "Revocation status list missing accumulator") + })?; + + map.entry(id) + .or_insert_with(HashMap::new) + .insert(timestamp, rev_reg); + } + Some(map) + } else { + None + }; + Ok(rev_reg_map) +} + +struct CLProofVerifier<'a> { + proof_verifier: ProofVerifier, + presentation_request: &'a PresentationRequestPayload, + non_credential_schema: NonCredentialSchema, + schemas: &'a HashMap, + cred_defs: &'a HashMap, + rev_reg_defs: Option<&'a HashMap>, + revocation_map: + Option>>, +} + +impl<'a> CLProofVerifier<'a> { + fn init( + presentation_request: &'a PresentationRequestPayload, + schemas: &'a HashMap, + cred_defs: &'a HashMap, + rev_reg_defs: Option< + &'a HashMap, + >, + rev_status_lists: Option<&'a Vec>, + ) -> Result> { + let proof_verifier = Verifier::new_proof_verifier()?; + let non_credential_schema = build_non_credential_schema()?; + let revocation_map = build_revocation_registry_map(rev_status_lists)?; + Ok(CLProofVerifier { + proof_verifier, + presentation_request, + non_credential_schema, + schemas, + cred_defs, + rev_reg_defs, + revocation_map, + }) + } + + #[allow(clippy::too_many_arguments)] + fn add_sub_proof( + &mut self, + attributes: &HashSet, + predicates: &HashSet, + schema_id: &SchemaId, + cred_def_id: &CredentialDefinitionId, + rev_reg_def_id: Option<&RevocationRegistryDefinitionId>, + timestamp: Option, + nonrevoke_interval_override: Option< + &HashMap>, + >, + ) -> Result<()> { + let schema = self.get_schema(schema_id)?; + let cred_def = self.get_credential_definition(cred_def_id)?; + let (rev_reg_def, rev_reg) = self.get_revocation_registry(rev_reg_def_id, timestamp)?; + + let credential_schema = build_credential_schema(schema)?; + let credential_pub_key = CredentialPublicKey::build_from_parts( + &cred_def.value.primary, + cred_def.value.revocation.as_ref(), + )?; + + let (attrs_for_credential, attrs_nonrevoked_interval) = self + .presentation_request + .get_requested_attributes(attributes)?; + let (predicates_for_credential, pred_nonrevoked_interval) = self + .presentation_request + .get_requested_predicates(predicates)?; + + let cred_nonrevoked_interval = get_non_revoked_interval( + attrs_nonrevoked_interval, + pred_nonrevoked_interval, + self.presentation_request, + rev_reg_def_id, + nonrevoke_interval_override, + ); + + if let (Some(_), true) = ( + cred_def.value.revocation.as_ref(), + cred_nonrevoked_interval.is_some(), + ) { + let timestamp = timestamp + .ok_or_else(|| err_msg!("Identifier timestamp not found for revocation check"))?; + + // Validate timestamp + cred_nonrevoked_interval + .map(|int| int.is_valid(timestamp)) + .transpose()?; + }; + + let sub_pres_request = + build_sub_proof_request(&attrs_for_credential, &predicates_for_credential)?; + + let rev_key_pub = rev_reg_def.map(|d| &d.value.public_keys.accum_key); + self.proof_verifier.add_sub_proof_request( + &sub_pres_request, + &credential_schema, + &self.non_credential_schema, + &credential_pub_key, + rev_key_pub.cloned().as_ref(), + rev_reg.cloned().as_ref(), + )?; + + Ok(()) + } + + fn verify(&mut self, proof: &Proof) -> Result { + let valid = self + .proof_verifier + .verify(proof, self.presentation_request.nonce.as_native())?; + Ok(valid) + } + + fn get_schema(&self, schema_id: &SchemaId) -> Result<&'a Schema> { + self.schemas + .get(schema_id) + .ok_or_else(|| err_msg!("Schema not provided for ID: {:?}", schema_id)) + } + + fn get_credential_definition( + &self, + cred_def_id: &CredentialDefinitionId, + ) -> Result<&'a CredentialDefinition> { + self.cred_defs.get(cred_def_id).ok_or_else(|| { + err_msg!( + "Credential Definition not provided for ID: {:?}", + cred_def_id + ) + }) + } + + fn get_revocation_registry( + &'a self, + rev_reg_id: Option<&RevocationRegistryDefinitionId>, + timestamp: Option, + ) -> Result<( + Option<&'a RevocationRegistryDefinition>, + Option<&'a RevocationRegistry>, + )> { + let (rev_reg_def, rev_reg) = if rev_reg_id.is_some() { + let timestamp = timestamp + .ok_or_else(|| err_msg!("Identifier timestamp not found for revocation check"))?; + + let rev_reg_defs = self.rev_reg_defs.ok_or_else(|| { + err_msg!("Could not load the Revocation Registry Definitions mapping") + })?; + + let rev_reg_map = self + .revocation_map + .as_ref() + .ok_or_else(|| err_msg!("Could not load the Revocation Registry mapping"))?; + + let rev_reg_id = rev_reg_id + .ok_or_else(|| err_msg!("Revocation Registry Id not found for revocation check"))?; + + let rev_reg_def = Some(rev_reg_defs.get(rev_reg_id).ok_or_else(|| { + err_msg!( + "Revocation Registry Definition not provided for ID: {:?}", + rev_reg_id + ) + })?); + + let rev_reg = Some( + rev_reg_map + .get(rev_reg_id) + .and_then(|regs| regs.get(×tamp)) + .ok_or_else(|| { + err_msg!( + "Revocation Registry not provided for ID and timestamp: {:?}, {:?}", + rev_reg_id, + timestamp + ) + })?, + ); + + (rev_reg_def, rev_reg) + } else { + (None, None) + }; + + Ok((rev_reg_def, rev_reg)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/tests/anoncreds_demos.rs b/tests/anoncreds_demos.rs index 32d35700..b3f6e589 100644 --- a/tests/anoncreds_demos.rs +++ b/tests/anoncreds_demos.rs @@ -7,7 +7,7 @@ use anoncreds::data_types::w3c::uri::URI; use anoncreds::issuer; use anoncreds::prover; use anoncreds::tails::TailsFileWriter; -use anoncreds::types::{CredentialRevocationConfig, PresentCredentials}; +use anoncreds::types::{CredentialRevocationConfig, MakeCredentialAttributes, PresentCredentials}; use anoncreds::verifier; use serde_json::json; use std::collections::{BTreeSet, HashMap}; @@ -3646,3 +3646,174 @@ fn anoncreds_demo_works_for_issue_w3c_credential_add_identity_proof_present_w3c_ assert!(valid); } + +#[test] +fn anoncreds_demo_works_for_issue_w3c_credential_and_present_w3c_presentation_for_restrictions() { + // Create Prover pseudo wallet and link secret + let mut prover_wallet = ProverWallet::default(); + + // Create schema + let (gvt_schema, gvt_schema_id) = fixtures::create_schema("GVT"); + + // Create credential definition + let ((gvt_cred_def, gvt_cred_def_priv, gvt_cred_key_correctness_proof), gvt_cred_def_id) = + fixtures::create_cred_def(&gvt_schema, false); + + // Issue GVT credential in W3C form + + // Issuer creates a Credential Offer + let cred_offer = issuer::create_credential_offer( + gvt_schema_id.try_into().unwrap(), + gvt_cred_def_id.try_into().unwrap(), + &gvt_cred_key_correctness_proof, + ) + .expect("Error creating credential offer"); + + // Prover creates a Credential Request + let (cred_request, cred_request_metadata) = prover::create_credential_request( + Some("entropy"), + None, + &gvt_cred_def, + &prover_wallet.link_secret, + "default", + &cred_offer, + ) + .expect("Error creating credential request"); + + // Issuer creates a credential + let mut gvt_cred_values = MakeCredentialAttributes::default(); + gvt_cred_values.add("Sex", "male"); + gvt_cred_values.add("Name", "Alex"); + gvt_cred_values.add("Height", "175"); + gvt_cred_values.add("Age", "28"); + + let issue_cred = issuer::create_w3c_credential( + &gvt_cred_def, + &gvt_cred_def_priv, + &cred_offer, + &cred_request, + gvt_cred_values.into(), + None, + None, + ) + .expect("Error creating w3c credential"); + + // Prover receives the credential and processes it + let mut w3c_credential = issue_cred; + prover::process_w3c_credential( + &mut w3c_credential, + &cred_request_metadata, + &prover_wallet.link_secret, + &gvt_cred_def, + None, + ) + .expect("Error processing credential"); + + // Store W3C credential form in wallet + prover_wallet.w3c_credentials.push(w3c_credential); + + // Create and Verify W3C presentation using W3C credential + + // Verifier creates a presentation request + let nonce = verifier::generate_nonce().expect("Error generating presentation request nonce"); + let pres_request = serde_json::from_value(json!({ + "nonce": nonce, + "name":"pres_req_1", + "version":"0.1", + "requested_attributes":{ + "attr1_referent":{ + "name":"name" + }, + "attr2_referent":{ + "names":["SEX", "height"] + } + }, + "requested_predicates":{ + "predicate1_referent":{"name":"AGE","p_type":">=","p_value":18} + } + })) + .expect("Error creating proof request"); + + // Prover creates presentation + let mut present = PresentCredentials::default(); + { + let mut cred1 = present.add_credential(&prover_wallet.w3c_credentials[0], None, None); + cred1.add_requested_attribute("attr1_referent", true); + cred1.add_requested_attribute("attr2_referent", true); + cred1.add_requested_predicate("predicate1_referent"); + } + + let mut schemas = HashMap::new(); + let gvt_schema_id = SchemaId::new_unchecked(gvt_schema_id); + schemas.insert(gvt_schema_id, gvt_schema.clone()); + + let mut cred_defs = HashMap::new(); + let gvt_cred_def_id = CredentialDefinitionId::new_unchecked(gvt_cred_def_id); + cred_defs.insert(gvt_cred_def_id, gvt_cred_def.try_clone().unwrap()); + + let presentation = prover::create_w3c_presentation( + &pres_request, + present, + &prover_wallet.link_secret, + &schemas, + &cred_defs, + ) + .expect("Error creating presentation"); + + // Verifier verifies presentation + assert_eq!( + "Alex", + presentation.verifiable_credential[0] + .credential_subject + .attributes + .0 + .get("Name") + .unwrap() + ); + + // Verifier verifies presentation + assert_eq!( + "male", + presentation.verifiable_credential[0] + .credential_subject + .attributes + .0 + .get("Sex") + .unwrap() + ); + + // Verifier verifies presentation + assert_eq!( + "175", + presentation.verifiable_credential[0] + .credential_subject + .attributes + .0 + .get("Height") + .unwrap() + ); + + assert_eq!( + json!({ "type": "AnonCredsPredicate", "p_type": ">=", "p_value": 18 }), + presentation.verifiable_credential[0] + .credential_subject + .attributes + .0 + .get("Age") + .cloned() + .unwrap() + ); + + let valid = verifier::verify_w3c_presentation( + &presentation, + &pres_request, + &schemas, + &cred_defs, + None, + None, + None, + ) + .expect("Error verifying presentation"); + + assert!(valid); +}