From 3918f850bb2dbf14ab5cf88afadde348495a6e82 Mon Sep 17 00:00:00 2001 From: "artem.ivanov" Date: Tue, 16 Jan 2024 18:09:01 +0300 Subject: [PATCH] Support numbers values in AnonCreds W3C VC Signed-off-by: artem.ivanov --- src/data_types/w3c/credential_attributes.rs | 61 +++++++++++++++------ src/services/verifier.rs | 24 ++++---- src/services/w3c/credential_conversion.rs | 6 +- src/services/w3c/helpers.rs | 24 +++++--- src/services/w3c/types.rs | 2 +- src/services/w3c/verifier.rs | 17 +++--- tests/anoncreds_demos.rs | 36 ++++++++---- tests/utils/mock.rs | 22 +++++--- 8 files changed, 122 insertions(+), 70 deletions(-) diff --git a/src/data_types/w3c/credential_attributes.rs b/src/data_types/w3c/credential_attributes.rs index 70129473..95cd7898 100644 --- a/src/data_types/w3c/credential_attributes.rs +++ b/src/data_types/w3c/credential_attributes.rs @@ -18,7 +18,7 @@ impl Drop for CredentialAttributes { impl Zeroize for CredentialAttributes { fn zeroize(&mut self) { for attr in self.0.values_mut() { - if let CredentialAttributeValue::Attribute(attr) = attr { + if let CredentialAttributeValue::String(attr) = attr { attr.zeroize() } } @@ -43,10 +43,17 @@ impl From<&CredentialValues> for CredentialAttributes { .0 .iter() .map(|(attribute, values)| { - ( - attribute.to_owned(), - CredentialAttributeValue::Attribute(values.raw.to_owned()), - ) + if let Ok(number) = values.raw.parse::() { + ( + attribute.to_string(), + CredentialAttributeValue::Number(number), + ) + } else { + ( + attribute.to_string(), + CredentialAttributeValue::String(values.raw.to_string()), + ) + } }) .collect(), ) @@ -60,30 +67,35 @@ impl CredentialAttributes { pub(crate) fn add_predicate(&mut self, attribute: String) -> crate::Result<()> { match self.0.get(&attribute) { - Some(value) => match value { - CredentialAttributeValue::Attribute(_) => { - return Err(err_msg!("Predicate cannot be added for revealed attribute")); + Some(value) => { + match value { + CredentialAttributeValue::String(_) | CredentialAttributeValue::Number(_) => { + Err(err_msg!("Predicate cannot be added for revealed attribute")) + } + CredentialAttributeValue::Bool(_) => { + // predicate already exists + Ok(()) + } } - CredentialAttributeValue::Predicate(_) => { - // predicate already exists - return Ok(()); - } - }, + } None => { self.0 - .insert(attribute, CredentialAttributeValue::Predicate(true)); + .insert(attribute, CredentialAttributeValue::Bool(true)); + Ok(()) } } - Ok(()) } pub(crate) fn encode(&self) -> crate::Result { let mut cred_values = MakeCredentialValues::default(); for (attribute, raw_value) in self.0.iter() { match raw_value { - CredentialAttributeValue::Attribute(raw_value) => { + CredentialAttributeValue::String(raw_value) => { cred_values.add_raw(attribute, raw_value)? } + CredentialAttributeValue::Number(raw_value) => { + cred_values.add_raw(attribute, raw_value.to_string())? + } value => { return Err(err_msg!( "Encoding is not supported for credential value {:?}", @@ -99,6 +111,19 @@ impl CredentialAttributes { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde(untagged)] pub enum CredentialAttributeValue { - Attribute(String), - Predicate(bool), + // attribute representation + String(String), + Number(i32), + // predicates representation + Bool(bool), +} + +impl ToString for CredentialAttributeValue { + fn to_string(&self) -> String { + match self { + CredentialAttributeValue::String(string) => string.to_owned(), + CredentialAttributeValue::Number(number) => number.to_string(), + CredentialAttributeValue::Bool(bool) => bool.to_string(), + } + } } diff --git a/src/services/verifier.rs b/src/services/verifier.rs index 6d0718ae..27505fdb 100644 --- a/src/services/verifier.rs +++ b/src/services/verifier.rs @@ -432,7 +432,7 @@ pub(crate) fn verify_requested_restrictions( })?; let filter = gather_filter_info(identifier, schemas, cred_defs)?; - let attr_value_map: HashMap> = if let Some(name) = + let attr_value_map: HashMap> = if let Some(name) = info.name.as_ref() { let mut map = HashMap::new(); @@ -441,17 +441,17 @@ pub(crate) fn verify_requested_restrictions( requested_proof .revealed_attrs .get(referent) - .map(|attr| attr.raw.as_str()), + .map(|attr| attr.raw.to_string()), ); map } else if let Some(names) = info.names.as_ref() { - let mut map = HashMap::new(); + let mut map: HashMap> = HashMap::new(); let attrs = requested_proof .revealed_attr_groups .get(referent) .ok_or_else(|| err_msg!("Proof does not have referent from proof request"))?; for name in names { - let val = attrs.values.get(name).map(|attr| attr.raw.as_str()); + let val = attrs.values.get(name).map(|attr| attr.raw.clone()); map.insert(name.clone(), val); } map @@ -484,7 +484,7 @@ pub(crate) fn verify_requested_restrictions( let filter = gather_filter_info(identifier, schemas, cred_defs)?; // start with the predicate requested attribute, which is un-revealed - let mut attr_value_map = HashMap::new(); + let mut attr_value_map: HashMap> = HashMap::new(); attr_value_map.insert(info.name.to_string(), None); // include any revealed attributes for the same credential (based on sub_proof_index) @@ -499,7 +499,7 @@ pub(crate) fn verify_requested_restrictions( if pred_sub_proof_index == attr_sub_proof_index { let attr_name = requested_attrs.get(attr_referent).unwrap().name.clone(); if let Some(name) = attr_name { - attr_value_map.insert(name, Some(attr_info.raw.as_str())); + attr_value_map.insert(name, Some(attr_info.raw.clone())); } } } @@ -511,7 +511,7 @@ pub(crate) fn verify_requested_restrictions( let attr_sub_proof_index = attr_info.sub_proof_index; if pred_sub_proof_index == attr_sub_proof_index { for name in attr_info.values.keys() { - let raw_val = attr_info.values.get(name).unwrap().raw.as_str(); + let raw_val = attr_info.values.get(name).unwrap().raw.clone(); attr_value_map.insert(name.to_string(), Some(raw_val)); } } @@ -568,7 +568,7 @@ pub(crate) fn gather_filter_info( } pub(crate) fn process_operator( - attr_value_map: &HashMap>, + attr_value_map: &HashMap>, restriction_op: &Query, filter: &Filter, ) -> Result<()> { @@ -637,7 +637,7 @@ pub(crate) fn process_operator( } fn process_filter( - attr_value_map: &HashMap>, + attr_value_map: &HashMap>, tag: &str, tag_value: &str, filter: &Filter, @@ -695,7 +695,7 @@ fn precess_filed(filed: &str, filter_value: impl Into, tag_value: &str) } } -fn is_attr_internal_tag(key: &str, attr_value_map: &HashMap>) -> bool { +fn is_attr_internal_tag(key: &str, attr_value_map: &HashMap>) -> bool { INTERNAL_TAG_MATCHER.captures(key).map_or(false, |caps| { caps.get(1).map_or(false, |s| { attr_value_map.contains_key(&s.as_str().to_string()) @@ -706,7 +706,7 @@ fn is_attr_internal_tag(key: &str, attr_value_map: &HashMap fn check_internal_tag_revealed_value( key: &str, tag_value: &str, - attr_value_map: &HashMap>, + attr_value_map: &HashMap>, ) -> Result<()> { let attr_name = INTERNAL_TAG_MATCHER .captures(key) @@ -1019,7 +1019,7 @@ mod tests { revealed_value: Option<&str>, ) -> Result<()> { let mut attr_value_map = HashMap::new(); - attr_value_map.insert(attr.to_string(), revealed_value); + attr_value_map.insert(attr.to_string(), revealed_value.map(String::from)); process_operator(&attr_value_map, restriction_op, filter) } diff --git a/src/services/w3c/credential_conversion.rs b/src/services/w3c/credential_conversion.rs index 37b14b3f..d0c23f9c 100644 --- a/src/services/w3c/credential_conversion.rs +++ b/src/services/w3c/credential_conversion.rs @@ -314,7 +314,7 @@ pub(crate) mod tests { pub fn w3c_credential() -> W3CCredential { W3CCredential::new( issuer_id(), - CredentialAttributes::from(&cred_values()), + CredentialAttributes::try_from(&cred_values()).unwrap(), DataIntegrityProof::new_credential_proof(&credential_signature_proof()).unwrap(), None, ) @@ -345,9 +345,11 @@ pub(crate) mod tests { assert_eq!(w3c_credential.context, expected_context.clone()); assert_eq!(w3c_credential.type_, ANONCREDS_CREDENTIAL_TYPES.clone()); + + let expected_attributes = CredentialAttributes::from(&legacy_credential.values); assert_eq!( w3c_credential.credential_subject.attributes, - CredentialAttributes::from(&legacy_credential.values) + expected_attributes ); let proof = w3c_credential diff --git a/src/services/w3c/helpers.rs b/src/services/w3c/helpers.rs index 23c925ca..cd0634db 100644 --- a/src/services/w3c/helpers.rs +++ b/src/services/w3c/helpers.rs @@ -19,25 +19,31 @@ impl W3CCredential { .ok_or_else(|| err_msg!("Credential attribute {} not found", requested_attribute)) } - pub(crate) fn get_attribute(&self, requested_attribute: &str) -> Result<(String, String)> { + pub(crate) fn get_attribute( + &self, + requested_attribute: &str, + ) -> Result<(String, CredentialAttributeValue)> { let (attribute, value) = self.get_case_insensitive_attribute(requested_attribute)?; match value { - CredentialAttributeValue::Attribute(value) => Ok((attribute, value)), - CredentialAttributeValue::Predicate(_) => Err(err_msg!( + CredentialAttributeValue::String(_) => Ok((attribute, value)), + CredentialAttributeValue::Number(_) => Ok((attribute, value)), + CredentialAttributeValue::Bool(_) => Err(err_msg!( "Credential attribute {} not found", requested_attribute )), } } - pub(crate) fn get_predicate(&self, requested_predicate: &str) -> Result<(String, bool)> { + pub(crate) fn get_predicate( + &self, + requested_predicate: &str, + ) -> Result<(String, CredentialAttributeValue)> { let (attribute, value) = self.get_case_insensitive_attribute(requested_predicate)?; match value { - CredentialAttributeValue::Predicate(value) => Ok((attribute, value)), - CredentialAttributeValue::Attribute(_) => Err(err_msg!( - "Credential predicate {} not found", - requested_predicate - )), + CredentialAttributeValue::Bool(_) => Ok((attribute, value)), + CredentialAttributeValue::String(_) | CredentialAttributeValue::Number(_) => Err( + err_msg!("Credential predicate {} not found", requested_predicate), + ), } } } diff --git a/src/services/w3c/types.rs b/src/services/w3c/types.rs index e92d2acb..14b17505 100644 --- a/src/services/w3c/types.rs +++ b/src/services/w3c/types.rs @@ -8,7 +8,7 @@ impl MakeCredentialAttributes { pub fn add(&mut self, name: impl Into, raw: impl Into) { self.0 .0 - .insert(name.into(), CredentialAttributeValue::Attribute(raw.into())); + .insert(name.into(), CredentialAttributeValue::String(raw.into())); } } diff --git a/src/services/w3c/verifier.rs b/src/services/w3c/verifier.rs index 6176acc2..8b2b8c4a 100644 --- a/src/services/w3c/verifier.rs +++ b/src/services/w3c/verifier.rs @@ -108,10 +108,13 @@ fn check_credential_restrictions( timestamp: None, }; let filter = gather_filter_info(&identifier, schemas, cred_defs)?; - let mut attr_value_map: HashMap> = HashMap::new(); + let mut attr_value_map: HashMap> = HashMap::new(); for (attribute, value) in credential.credential_subject.attributes.0.iter() { - if let CredentialAttributeValue::Attribute(value) = value { - attr_value_map.insert(attribute.to_owned(), Some(value)); + if let CredentialAttributeValue::String(value) = value { + attr_value_map.insert(attribute.to_owned(), Some(value.to_string())); + } + if let CredentialAttributeValue::Number(value) = value { + attr_value_map.insert(attribute.to_owned(), Some(value.to_string())); } } process_operator(&attr_value_map, restrictions, &filter).map_err(err_map!( @@ -194,7 +197,7 @@ fn check_requested_attribute<'a>( .get(index) .ok_or_else(|| err_msg!("Unable to get credential proof for index {}", index))?; - let encoded = encode_credential_attribute(&value)?; + let encoded = encode_credential_attribute(&value.to_string())?; if verify_revealed_attribute_value(&attribute, &proof.sub_proof, &encoded).is_err() { continue; } @@ -394,13 +397,13 @@ pub(crate) mod tests { CredentialAttributes(HashMap::from([ ( "name".to_string(), - CredentialAttributeValue::Attribute("Alice".to_string()), + CredentialAttributeValue::String("Alice".to_string()), ), ( "height".to_string(), - CredentialAttributeValue::Attribute("178".to_string()), + CredentialAttributeValue::String("178".to_string()), ), - ("age".to_string(), CredentialAttributeValue::Predicate(true)), + ("age".to_string(), CredentialAttributeValue::Bool(true)), ])) } diff --git a/tests/anoncreds_demos.rs b/tests/anoncreds_demos.rs index e1ffb130..be96a31f 100644 --- a/tests/anoncreds_demos.rs +++ b/tests/anoncreds_demos.rs @@ -151,7 +151,9 @@ fn anoncreds_demo_works_for_single_issuer_single_prover( PresentedAttribute { referent: "attr1_referent", name: "name", - expected: ExpectedAttributeValue::RevealedAttribute("Alex"), + expected: ExpectedAttributeValue::RevealedAttribute(CredentialAttributeValue::String( + "Alex".to_string(), + )), }, ); @@ -169,7 +171,9 @@ fn anoncreds_demo_works_for_single_issuer_single_prover( PresentedAttribute { referent: "attr3_referent", name: "name", - expected: ExpectedAttributeValue::GroupedAttribute("Alex"), + expected: ExpectedAttributeValue::GroupedAttribute(CredentialAttributeValue::String( + "Alex".to_string(), + )), }, ); @@ -178,7 +182,9 @@ fn anoncreds_demo_works_for_single_issuer_single_prover( PresentedAttribute { referent: "attr3_referent", name: "height", - expected: ExpectedAttributeValue::GroupedAttribute("175"), + expected: ExpectedAttributeValue::GroupedAttribute(CredentialAttributeValue::Number( + 175, + )), }, ); } @@ -851,7 +857,9 @@ fn anoncreds_demo_works_for_requested_attribute_in_upper_case( PresentedAttribute { referent: "attr1_referent", name: "NAME", - expected: ExpectedAttributeValue::RevealedAttribute("Alex"), + expected: ExpectedAttributeValue::RevealedAttribute(CredentialAttributeValue::String( + "Alex".to_string(), + )), }, ); @@ -869,7 +877,9 @@ fn anoncreds_demo_works_for_requested_attribute_in_upper_case( PresentedAttribute { referent: "attr3_referent", name: "NAME", - expected: ExpectedAttributeValue::GroupedAttribute("Alex"), + expected: ExpectedAttributeValue::GroupedAttribute(CredentialAttributeValue::String( + "Alex".to_string(), + )), }, ); @@ -878,7 +888,9 @@ fn anoncreds_demo_works_for_requested_attribute_in_upper_case( PresentedAttribute { referent: "attr3_referent", name: "HEIGHT", - expected: ExpectedAttributeValue::GroupedAttribute("175"), + expected: ExpectedAttributeValue::GroupedAttribute(CredentialAttributeValue::Number( + 175, + )), }, ); @@ -2964,7 +2976,7 @@ fn anoncreds_demo_works_for_issue_legacy_credential_convert_into_w3c_and_present // Verifier verifies presentation let presentation = presentation.w3c(); assert_eq!( - &CredentialAttributeValue::Attribute("Alex".to_string()), + &CredentialAttributeValue::String("Alex".to_string()), presentation.verifiable_credential[0] .credential_subject .attributes @@ -2974,7 +2986,7 @@ fn anoncreds_demo_works_for_issue_legacy_credential_convert_into_w3c_and_present ); assert_eq!( - CredentialAttributeValue::Predicate(true), + CredentialAttributeValue::Bool(true), presentation.verifiable_credential[0] .credential_subject .attributes @@ -3283,7 +3295,7 @@ fn anoncreds_demo_works_for_issue_two_credentials_in_different_forms_and_present // Verifier verifies presentation let presentation = presentation.w3c(); assert_eq!( - &CredentialAttributeValue::Attribute("Alex".to_string()), + &CredentialAttributeValue::String("Alex".to_string()), presentation.verifiable_credential[0] .credential_subject .attributes @@ -3293,7 +3305,7 @@ fn anoncreds_demo_works_for_issue_two_credentials_in_different_forms_and_present ); assert_eq!( - &CredentialAttributeValue::Attribute("male".to_string()), + &CredentialAttributeValue::String("male".to_string()), presentation.verifiable_credential[0] .credential_subject .attributes @@ -3303,7 +3315,7 @@ fn anoncreds_demo_works_for_issue_two_credentials_in_different_forms_and_present ); assert_eq!( - &CredentialAttributeValue::Attribute("Developer".to_string()), + &CredentialAttributeValue::String("Developer".to_string()), presentation.verifiable_credential[1] .credential_subject .attributes @@ -3313,7 +3325,7 @@ fn anoncreds_demo_works_for_issue_two_credentials_in_different_forms_and_present ); assert_eq!( - &CredentialAttributeValue::Attribute("IT".to_string()), + &CredentialAttributeValue::String("IT".to_string()), presentation.verifiable_credential[1] .credential_subject .attributes diff --git a/tests/utils/mock.rs b/tests/utils/mock.rs index f68349d6..492e36f0 100644 --- a/tests/utils/mock.rs +++ b/tests/utils/mock.rs @@ -1,4 +1,5 @@ use super::storage::{IssuerWallet, Ledger, ProverWallet, StoredCredDef, StoredRevDef}; +use serde::Serialize; use serde_json::json; use std::{ collections::{BTreeSet, HashMap}, @@ -58,7 +59,7 @@ pub enum PresentationFormat { W3C, } -#[derive(Debug)] +#[derive(Debug, Serialize)] pub enum Presentations { Legacy(Presentation), W3C(W3CPresentation), @@ -764,7 +765,8 @@ impl IssuerWallet { cred_def_private, &cred_offer, &cred_request, - CredentialAttributes::from(&cred_values), + CredentialAttributes::try_from(&cred_values) + .expect("Error generating credential attributes"), revocation_config, version, ) @@ -1033,7 +1035,7 @@ impl VerifierWallet { match attribute.expected { ExpectedAttributeValue::RevealedAttribute(expected) => { assert_eq!( - expected, + expected.to_string(), presentation .requested_proof .revealed_attrs @@ -1049,7 +1051,7 @@ impl VerifierWallet { .get(attribute.referent) .unwrap(); assert_eq!( - expected, + expected.to_string(), revealed_attr_groups.values.get(attribute.name).unwrap().raw ); } @@ -1088,14 +1090,16 @@ impl VerifierWallet { .contains_key(&attribute.name.to_lowercase()) }) .unwrap(); + assert_eq!( - &CredentialAttributeValue::Attribute(expected.to_string()), + expected, credential .credential_subject .attributes .0 .get(&attribute.name.to_lowercase()) .unwrap() + .clone() ); } ExpectedAttributeValue::UnrevealedAttribute(expected) => { @@ -1145,13 +1149,13 @@ pub enum PresentAttributeForm { pub struct PresentedAttribute<'a> { pub referent: &'a str, pub name: &'a str, - pub expected: ExpectedAttributeValue<'a>, + pub expected: ExpectedAttributeValue, } -pub enum ExpectedAttributeValue<'a> { - RevealedAttribute(&'a str), +pub enum ExpectedAttributeValue { + RevealedAttribute(CredentialAttributeValue), UnrevealedAttribute(u32), - GroupedAttribute(&'a str), + GroupedAttribute(CredentialAttributeValue), Predicate, }