diff --git a/src/data_types/pres_request.rs b/src/data_types/pres_request.rs index 58de223f..a4aef6e8 100644 --- a/src/data_types/pres_request.rs +++ b/src/data_types/pres_request.rs @@ -22,7 +22,7 @@ pub struct PresentationRequestPayload { pub requested_attributes: HashMap, #[serde(default)] pub requested_predicates: HashMap, - pub non_revoked: Option, + pub non_revoked: Option, } #[derive(Debug, PartialEq, Eq)] @@ -122,12 +122,62 @@ impl Serialize for PresentationRequest { #[allow(unused)] pub type PresentationRequestExtraQuery = HashMap; -#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] -pub struct NonRevocedInterval { +#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub struct NonRevokedInterval { pub from: Option, pub to: Option, } +impl NonRevokedInterval { + pub fn new(from: Option, to: Option) -> Self { + Self { from, to } + } + // Returns the most stringent interval, + // i.e. the latest from and the earliest to + pub fn compare_and_set(&mut self, to_compare: &NonRevokedInterval) { + // Update if + // - the new `from` value is later, smaller interval + // - the new `from` value is Some if previouly was None + match (self.from, to_compare.from) { + (Some(old_from), Some(new_from)) => { + if old_from.lt(&new_from) { + self.from = to_compare.from + } + } + (None, Some(_)) => self.from = to_compare.from, + _ => (), + } + // Update if + // - the new `to` value is earlier, smaller interval + // - the new `to` value is Some if previouly was None + match (self.to, to_compare.to) { + (Some(old_to), Some(new_to)) => { + if new_to.lt(&old_to) { + self.to = to_compare.to + } + } + (None, Some(_)) => self.to = to_compare.to, + _ => (), + } + } + + pub fn update_with_override(&mut self, override_map: &HashMap) { + self.from.map(|from| { + override_map + .get(&from) + .map(|&override_timestamp| self.from = Some(override_timestamp)) + }); + } + + pub fn is_valid(&self, timestamp: u64) -> Result<(), ValidationError> { + if timestamp.lt(&self.from.unwrap_or(0)) || timestamp.gt(&self.to.unwrap_or(u64::MAX)) { + Err(invalid!("Invalid timestamp")) + } else { + Ok(()) + } + } +} + #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] pub struct AttributeInfo { #[serde(skip_serializing_if = "Option::is_none")] @@ -135,7 +185,7 @@ pub struct AttributeInfo { #[serde(skip_serializing_if = "Option::is_none")] pub names: Option>, pub restrictions: Option, - pub non_revoked: Option, + pub non_revoked: Option, } #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] @@ -144,7 +194,7 @@ pub struct PredicateInfo { pub p_type: PredicateTypes, pub p_value: i32, pub restrictions: Option, - pub non_revoked: Option, + pub non_revoked: Option, } #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] @@ -330,4 +380,44 @@ mod tests { serde_json::from_str::(&req_json).unwrap_err(); } } + + #[test] + fn override_works() { + let mut interval = NonRevokedInterval::default(); + let override_map = HashMap::from([(10u64, 5u64)]); + + interval.from = Some(10); + interval.update_with_override(&override_map); + assert_eq!(interval.from.unwrap(), 5u64); + } + + #[test] + fn compare_and_set_works() { + let mut int = NonRevokedInterval::default(); + let wide_int = NonRevokedInterval::new(Some(1), Some(100)); + let mid_int = NonRevokedInterval::new(Some(5), Some(80)); + let narrow_int = NonRevokedInterval::new(Some(10), Some(50)); + + assert_eq!(int.from, None); + assert_eq!(int.to, None); + + // From None to Some + int.compare_and_set(&wide_int); + assert_eq!(int.from, wide_int.from); + assert_eq!(int.to, wide_int.to); + + // Update when more narrow + int.compare_and_set(&mid_int); + assert_eq!(int.from, mid_int.from); + assert_eq!(int.to, mid_int.to); + + // Do Not Update when wider + int.compare_and_set(&wide_int); + assert_eq!(int.from, mid_int.from); + assert_eq!(int.to, mid_int.to); + + int.compare_and_set(&narrow_int); + assert_eq!(int.from, narrow_int.from); + assert_eq!(int.to, narrow_int.to); + } } diff --git a/src/ffi/presentation.rs b/src/ffi/presentation.rs index b1b89bba..0116d17f 100644 --- a/src/ffi/presentation.rs +++ b/src/ffi/presentation.rs @@ -24,7 +24,7 @@ impl_anoncreds_object_from_json!(Presentation, anoncreds_presentation_from_json) #[repr(C)] pub struct FfiCredentialEntry { credential: ObjectHandle, - timestamp: i64, + timestamp: i32, rev_state: ObjectHandle, } @@ -195,6 +195,50 @@ pub extern "C" fn anoncreds_create_presentation( }) } +/// Optional value for overriding the non-revoked interval in the PresentationRequest +/// This only overrides the `from` value as a Revocation Status List is deemed valid until the next +/// entry. +/// +/// E.g. if the ledger has Revocation Status List at timestamps [0, 100, 200], +/// let's call them List0, List100, List200. Then: +/// +/// ```txt +/// +/// List0 is valid List100 is valid +/// ______|_______ _______|_______ +/// | | | +/// List 0 ----------- 100 ----------- 200 +/// ``` +/// +/// A `nonrevoked_interval = {from: 50, to: 150}` should accept both List0 and +/// List100. +/// +#[derive(Debug)] +#[repr(C)] +pub struct FfiNonrevokedIntervalOverride<'a> { + rev_reg_def_id: FfiStr<'a>, + /// Timestamp in the `PresentationRequest` + requested_from_ts: i32, + /// Timestamp from which verifier accepts, + /// should be less than `req_timestamp` + override_rev_status_list_ts: i32, +} + +impl<'a> FfiNonrevokedIntervalOverride<'a> { + fn load(&self) -> Result<(RevocationRegistryDefinitionId, u64, u64)> { + let id = RevocationRegistryDefinitionId::new(self.rev_reg_def_id.as_str().to_owned())?; + let requested_from_ts = self + .requested_from_ts + .try_into() + .map_err(|_| err_msg!("Invalid req timestamp "))?; + let override_rev_status_list_ts = self + .override_rev_status_list_ts + .try_into() + .map_err(|_| err_msg!("Invalid override timestamp "))?; + Ok((id, requested_from_ts, override_rev_status_list_ts)) + } +} + #[no_mangle] pub extern "C" fn anoncreds_verify_presentation( presentation: ObjectHandle, @@ -206,6 +250,7 @@ pub extern "C" fn anoncreds_verify_presentation( rev_reg_defs: FfiList, rev_reg_def_ids: FfiStrList, rev_status_list: FfiList, + nonrevoked_interval_override: FfiList, result_p: *mut i8, ) -> ErrorCode { catch_error(|| { @@ -266,6 +311,24 @@ pub extern "C" fn anoncreds_verify_presentation( let rev_status_list: Result> = rev_status_list.refs(); let rev_status_list = rev_status_list.ok(); + let override_entries = { + let override_ffi_entries = nonrevoked_interval_override.as_slice(); + override_ffi_entries.iter().try_fold( + Vec::with_capacity(override_ffi_entries.len()), + |mut v, entry| -> Result> { + v.push(entry.load()?); + Ok(v) + }, + )? + }; + let mut map_nonrevoked_interval_override = HashMap::new(); + for (id, req_timestamp, override_timestamp) in override_entries.iter() { + map_nonrevoked_interval_override + .entry(id) + .or_insert_with(HashMap::new) + .insert(*req_timestamp, *override_timestamp); + } + let verify = verify_presentation( presentation.load()?.cast_ref()?, pres_req.load()?.cast_ref()?, @@ -273,6 +336,7 @@ pub extern "C" fn anoncreds_verify_presentation( &cred_defs, rev_reg_defs, rev_status_list, + Some(&map_nonrevoked_interval_override), )?; unsafe { *result_p = verify as i8 }; Ok(()) diff --git a/src/services/helpers.rs b/src/services/helpers.rs index ba8633e2..897a7904 100644 --- a/src/services/helpers.rs +++ b/src/services/helpers.rs @@ -3,7 +3,8 @@ use std::collections::{HashMap, HashSet}; use crate::data_types::{ credential::AttributeValues, nonce::Nonce, - pres_request::{AttributeInfo, NonRevocedInterval, PredicateInfo}, + pres_request::{AttributeInfo, NonRevokedInterval, PredicateInfo, PresentationRequestPayload}, + presentation::RequestedProof, }; use crate::utils::hash::SHA256; @@ -134,55 +135,108 @@ pub fn build_sub_proof_request( Ok(res) } -pub fn get_non_revoc_interval( - global_interval: &Option, - local_interval: &Option, -) -> Option { - trace!( - "get_non_revoc_interval >>> global_interval: {:?}, local_interval: {:?}", - global_interval, - local_interval - ); - - let interval = local_interval - .clone() - .or_else(|| global_interval.clone().or(None)); - - trace!("get_non_revoc_interval <<< interval: {:?}", interval); - - interval -} - pub fn new_nonce() -> Result { Nonce::new().map_err(err_map!(Unexpected)) } -#[cfg(test)] -mod tests { - use super::*; +pub fn get_revealed_attributes_for_credential( + sub_proof_index: usize, + requested_proof: &RequestedProof, + pres_req: &PresentationRequestPayload, +) -> Result<(Vec, Option)> { + trace!("_get_revealed_attributes_for_credential >>> sub_proof_index: {:?}, requested_credentials: {:?}, pres_req: {:?}", + sub_proof_index, requested_proof, pres_req); + let mut non_revoked_interval: Option = None; + let mut revealed_attrs_for_credential = requested_proof + .revealed_attrs + .iter() + .filter(|&(attr_referent, revealed_attr_info)| { + sub_proof_index == revealed_attr_info.sub_proof_index as usize + && pres_req.requested_attributes.contains_key(attr_referent) + }) + .map(|(attr_referent, _)| { + let info = pres_req.requested_attributes[attr_referent].clone(); + if let Some(int) = &info.non_revoked { + match non_revoked_interval.as_mut() { + Some(ni) => { + ni.compare_and_set(int); + } + None => non_revoked_interval = Some(int.clone()), + } + }; + + info + }) + .collect::>(); + + revealed_attrs_for_credential.append( + &mut requested_proof + .revealed_attr_groups + .iter() + .filter(|&(attr_referent, revealed_attr_info)| { + sub_proof_index == revealed_attr_info.sub_proof_index as usize + && pres_req.requested_attributes.contains_key(attr_referent) + }) + .map(|(attr_referent, _)| { + let info = pres_req.requested_attributes[attr_referent].clone(); + if let Some(int) = &info.non_revoked { + match non_revoked_interval.as_mut() { + Some(ni) => { + ni.compare_and_set(int); + } + None => non_revoked_interval = Some(NonRevokedInterval::default()), + } + }; + info + }) + .collect::>(), + ); - fn _interval() -> NonRevocedInterval { - NonRevocedInterval { - from: None, - to: Some(123), - } - } + trace!( + "_get_revealed_attributes_for_credential <<< revealed_attrs_for_credential: {:?}", + revealed_attrs_for_credential + ); - #[test] - fn get_non_revoc_interval_for_global() { - let res = get_non_revoc_interval(&Some(_interval()), &None).unwrap(); - assert_eq!(_interval(), res); - } + Ok((revealed_attrs_for_credential, non_revoked_interval)) +} - #[test] - fn get_non_revoc_interval_for_local() { - let res = get_non_revoc_interval(&None, &Some(_interval())).unwrap(); - assert_eq!(_interval(), res); - } +pub fn get_predicates_for_credential( + sub_proof_index: usize, + requested_proof: &RequestedProof, + pres_req: &PresentationRequestPayload, +) -> Result<(Vec, Option)> { + trace!("_get_predicates_for_credential >>> sub_proof_index: {:?}, requested_credentials: {:?}, pres_req: {:?}", + sub_proof_index, requested_proof, pres_req); + + let mut non_revoked_interval: Option = None; + let predicates_for_credential = requested_proof + .predicates + .iter() + .filter(|&(predicate_referent, requested_referent)| { + sub_proof_index == requested_referent.sub_proof_index as usize + && pres_req + .requested_predicates + .contains_key(predicate_referent) + }) + .map(|(predicate_referent, _)| { + let info = pres_req.requested_predicates[predicate_referent].clone(); + if let Some(int) = &info.non_revoked { + match non_revoked_interval.as_mut() { + Some(ni) => { + ni.compare_and_set(int); + } + None => non_revoked_interval = Some(int.clone()), + } + }; + + info + }) + .collect::>(); - #[test] - fn get_non_revoc_interval_for_none() { - let res = get_non_revoc_interval(&None, &None); - assert_eq!(None, res); - } + trace!( + "_get_predicates_for_credential <<< predicates_for_credential: {:?}", + predicates_for_credential + ); + + Ok((predicates_for_credential, non_revoked_interval)) } diff --git a/src/services/prover.rs b/src/services/prover.rs index 9caafd9e..717bb079 100644 --- a/src/services/prover.rs +++ b/src/services/prover.rs @@ -203,6 +203,52 @@ pub fn create_presentation( )?; let sub_proof_request = build_sub_proof_request(&req_attrs, &req_predicates)?; + update_requested_proof( + req_attrs, + req_predicates, + pres_req_val, + credential, + sub_proof_index, + &mut requested_proof, + )?; + + // Checks conditions to add revocation proof + let (rev_reg, witness) = if pres_req_val.non_revoked.is_some() { + // Global revocation request + ( + present.rev_state.as_ref().map(|r_info| &r_info.rev_reg), + present.rev_state.as_ref().map(|r_info| &r_info.witness), + ) + } else { + // There exists at least 1 local revocation request + let ((_, nonrevoked_attr), (_, nonrevoked_preds)) = ( + get_revealed_attributes_for_credential( + sub_proof_index as usize, + &requested_proof, + pres_req_val, + )?, + get_predicates_for_credential( + sub_proof_index as usize, + &requested_proof, + pres_req_val, + )?, + ); + if nonrevoked_attr.is_some() || nonrevoked_preds.is_some() { + ( + present.rev_state.as_ref().map(|r_info| &r_info.rev_reg), + present.rev_state.as_ref().map(|r_info| &r_info.witness), + ) + } else { + // Neither global nor local is required + (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, @@ -210,8 +256,8 @@ pub fn create_presentation( &credential.signature, &credential_values, &credential_pub_key, - present.rev_state.as_ref().map(|r_info| &r_info.rev_reg), - present.rev_state.as_ref().map(|r_info| &r_info.witness), + rev_reg, + witness, )?; let identifier = match pres_req { @@ -231,15 +277,6 @@ pub fn create_presentation( identifiers.push(identifier); - update_requested_proof( - req_attrs, - req_predicates, - pres_req_val, - credential, - sub_proof_index, - &mut requested_proof, - )?; - sub_proof_index += 1; } diff --git a/src/services/verifier.rs b/src/services/verifier.rs index 707d7cf2..38388874 100644 --- a/src/services/verifier.rs +++ b/src/services/verifier.rs @@ -13,7 +13,7 @@ use crate::data_types::schema::Schema; use crate::data_types::schema::SchemaId; use crate::data_types::{ nonce::Nonce, - pres_request::{AttributeInfo, NonRevocedInterval, PredicateInfo, PresentationRequestPayload}, + pres_request::{AttributeInfo, NonRevokedInterval, PresentationRequestPayload}, presentation::{Identifier, RequestedProof, RevealedAttributeInfo}, }; use crate::error::Result; @@ -44,11 +44,15 @@ pub fn verify_presentation( cred_defs: &HashMap<&CredentialDefinitionId, &CredentialDefinition>, rev_reg_defs: Option<&HashMap<&RevocationRegistryDefinitionId, &RevocationRegistryDefinition>>, rev_status_lists: Option>, + // Override Map: HashMap + nonrevoke_interval_override: Option< + &HashMap<&RevocationRegistryDefinitionId, HashMap>, + >, ) -> Result { trace!("verify >>> presentation: {:?}, pres_req: {:?}, schemas: {:?}, cred_defs: {:?}, rev_reg_defs: {:?} rev_status_lists: {:?}", presentation, pres_req, schemas, cred_defs, rev_reg_defs, rev_status_lists); - let pres_req = pres_req.value(); + // These values are from the prover and cannot be trusted let received_revealed_attrs: HashMap = received_revealed_attrs(presentation)?; let received_unrevealed_attrs: HashMap = @@ -56,6 +60,9 @@ pub fn verify_presentation( let received_predicates: HashMap = received_predicates(presentation)?; let received_self_attested_attrs: HashSet = received_self_attested_attrs(presentation); + let pres_req = pres_req.value(); + + // Ensures that all attributes in the request is also in the presentation compare_attr_from_proof_and_request( pres_req, &received_revealed_attrs, @@ -64,8 +71,10 @@ pub fn verify_presentation( &received_predicates, )?; + // Ensures the encoded values are same as request verify_revealed_attribute_values(pres_req, presentation)?; + // Ensures the restrictinos set out in the request is met verify_requested_restrictions( pres_req, schemas, @@ -77,16 +86,6 @@ pub fn verify_presentation( &received_self_attested_attrs, )?; - // makes sure the for revocable request or attribute, - // there is a timestamp in the `Identifier` - compare_timestamps_from_proof_and_request( - pres_req, - &received_revealed_attrs, - &received_unrevealed_attrs, - &received_self_attested_attrs, - &received_predicates, - )?; - let mut proof_verifier = CryptoVerifier::new_proof_verifier()?; let non_credential_schema = build_non_credential_schema()?; @@ -124,26 +123,64 @@ pub fn verify_presentation( Into::>::into(*list) .ok_or_else(|| err_msg!(Unexpected, "RevStatusList missing Accum"))?; - if map.get(&id).and_then(|t| t.get(×tamp)).is_some() { - return Err(err_msg!( - Unexpected, - "Duplicated timestamp for Revocation Status List" - )); - } else if map.get(&id).is_none() { - map.insert(id, HashMap::from([(timestamp, rev_reg)])); - } else { - map.get_mut(&id).unwrap().insert(timestamp, rev_reg); - } + map.entry(id) + .or_insert_with(HashMap::new) + .insert(timestamp, rev_reg); } Some(map) } else { None }; - let (rev_reg_def, rev_reg) = if let Some(timestamp) = identifier.timestamp { - let rev_reg_id = identifier.rev_reg_id.clone().ok_or_else(|| { - err_msg!("Timestamp provided but Revocation Registry Id not found") - })?; + let (attrs_for_credential, attrs_nonrevoked_interval) = + get_revealed_attributes_for_credential( + sub_proof_index, + &presentation.requested_proof, + pres_req, + )?; + let (predicates_for_credential, pred_nonrevoked_interval) = get_predicates_for_credential( + sub_proof_index, + &presentation.requested_proof, + pres_req, + )?; + + // Collaspe to the most stringent local interval for the attributes / predicates, + // we can do this because there is only 1 revocation status list for this credential + // if it satsifies the most stringent interval, it will satisfy all intervals + let mut cred_nonrevoked_interval: Option = + match (attrs_nonrevoked_interval, pred_nonrevoked_interval) { + (Some(attr), None) => Some(attr), + (None, Some(pred)) => Some(pred), + (Some(mut attr), Some(pred)) => { + attr.compare_and_set(&pred); + Some(attr) + } + _ => None, + }; + + // Global interval is override by the local one, + // we only need to update if local is None and Global is Some, + // do not need to update if global is more stringent + if let (Some(global), None) = ( + pres_req.non_revoked.clone(), + cred_nonrevoked_interval.as_mut(), + ) { + cred_nonrevoked_interval = Some(global); + }; + + // 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 = identifier + .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" @@ -155,8 +192,28 @@ pub fn verify_presentation( )); } + let rev_reg_id = identifier + .rev_reg_id + .clone() + .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())?; + + // Override Interval if an earlier `from` value is accepted by the verifier + nonrevoke_interval_override.map(|maps| { + maps.get(&rev_reg_def_id).map(|map| { + cred_nonrevoked_interval + .as_mut() + .map(|int| int.update_with_override(map)) + }) + }); + + // Validate timestamp + cred_nonrevoked_interval + .map(|int| int.is_valid(timestamp)) + .transpose()?; + let rev_reg_def = Some( rev_reg_defs .as_ref() @@ -190,17 +247,6 @@ pub fn verify_presentation( (None, None) }; - let attrs_for_credential = get_revealed_attributes_for_credential( - sub_proof_index, - &presentation.requested_proof, - pres_req, - )?; - let predicates_for_credential = get_predicates_for_credential( - sub_proof_index, - &presentation.requested_proof, - pres_req, - )?; - let credential_schema = build_credential_schema(&schema.attr_names.0)?; let sub_pres_request = build_sub_proof_request(&attrs_for_credential, &predicates_for_credential)?; @@ -233,72 +279,6 @@ pub fn generate_nonce() -> Result { new_nonce() } -fn get_revealed_attributes_for_credential( - sub_proof_index: usize, - requested_proof: &RequestedProof, - pres_req: &PresentationRequestPayload, -) -> Result> { - trace!("_get_revealed_attributes_for_credential >>> sub_proof_index: {:?}, requested_credentials: {:?}, pres_req: {:?}", - sub_proof_index, requested_proof, pres_req); - - let mut revealed_attrs_for_credential = requested_proof - .revealed_attrs - .iter() - .filter(|&(attr_referent, revealed_attr_info)| { - sub_proof_index == revealed_attr_info.sub_proof_index as usize - && pres_req.requested_attributes.contains_key(attr_referent) - }) - .map(|(attr_referent, _)| pres_req.requested_attributes[attr_referent].clone()) - .collect::>(); - - revealed_attrs_for_credential.append( - &mut requested_proof - .revealed_attr_groups - .iter() - .filter(|&(attr_referent, revealed_attr_info)| { - sub_proof_index == revealed_attr_info.sub_proof_index as usize - && pres_req.requested_attributes.contains_key(attr_referent) - }) - .map(|(attr_referent, _)| pres_req.requested_attributes[attr_referent].clone()) - .collect::>(), - ); - - trace!( - "_get_revealed_attributes_for_credential <<< revealed_attrs_for_credential: {:?}", - revealed_attrs_for_credential - ); - - Ok(revealed_attrs_for_credential) -} - -fn get_predicates_for_credential( - sub_proof_index: usize, - requested_proof: &RequestedProof, - pres_req: &PresentationRequestPayload, -) -> Result> { - trace!("_get_predicates_for_credential >>> sub_proof_index: {:?}, requested_credentials: {:?}, pres_req: {:?}", - sub_proof_index, requested_proof, pres_req); - - let predicates_for_credential = requested_proof - .predicates - .iter() - .filter(|&(predicate_referent, requested_referent)| { - sub_proof_index == requested_referent.sub_proof_index as usize - && pres_req - .requested_predicates - .contains_key(predicate_referent) - }) - .map(|(predicate_referent, _)| pres_req.requested_predicates[predicate_referent].clone()) - .collect::>(); - - trace!( - "_get_predicates_for_credential <<< predicates_for_credential: {:?}", - predicates_for_credential - ); - - Ok(predicates_for_credential) -} - fn compare_attr_from_proof_and_request( pres_req: &PresentationRequestPayload, received_revealed_attrs: &HashMap, @@ -340,92 +320,6 @@ fn compare_attr_from_proof_and_request( Ok(()) } -// This does not actually compare the non_revoke interval -// see `validate_timestamp` function comments -fn compare_timestamps_from_proof_and_request( - pres_req: &PresentationRequestPayload, - received_revealed_attrs: &HashMap, - received_unrevealed_attrs: &HashMap, - received_self_attested_attrs: &HashSet, - received_predicates: &HashMap, -) -> Result<()> { - pres_req - .requested_attributes - .iter() - .map(|(referent, info)| { - validate_timestamp( - received_revealed_attrs, - referent, - &pres_req.non_revoked, - &info.non_revoked, - ) - .or_else(|_| { - validate_timestamp( - received_unrevealed_attrs, - referent, - &pres_req.non_revoked, - &info.non_revoked, - ) - }) - .or_else(|_| { - received_self_attested_attrs - .get(referent) - .map(|_| ()) - .ok_or_else(|| err_msg!("Missing referent: {}", referent)) - }) - }) - .collect::>>()?; - - pres_req - .requested_predicates - .iter() - .map(|(referent, info)| { - validate_timestamp( - received_predicates, - referent, - &pres_req.non_revoked, - &info.non_revoked, - ) - }) - .collect::>>()?; - - Ok(()) -} - -// This validates that a timestamp is given if either: -// - the `global_interval` rev requirement -// - the `local_interval` rev requirement -// from the PresentationRequest are satisfied. -// -// If either the attribute nor the request has a revocation internal -// i.e. they are non-revocable, then `OK` is returned directly. -// -// Otherwise the Identifier for the referent (attribute) has to have a timestamp, -// which was added by the prover when creating `PresentCredentials`, -// an arg for `create_presentation`. -// -// TODO: this timestamp should be compared with the provided interval -fn validate_timestamp( - received_: &HashMap, - referent: &str, - global_interval: &Option, - local_interval: &Option, -) -> Result<()> { - if get_non_revoc_interval(global_interval, local_interval).is_none() { - return Ok(()); - } - - if !received_ - .get(referent) - .map(|attr| attr.timestamp.is_some()) - .unwrap_or(false) - { - return Err(err_msg!("Missing timestamp")); - } - - Ok(()) -} - fn received_revealed_attrs(proof: &Presentation) -> Result> { let mut revealed_identifiers: HashMap = HashMap::new(); for (referent, info) in proof.requested_proof.revealed_attrs.iter() { @@ -597,6 +491,7 @@ fn verify_revealed_attribute_value( Ok(()) } +#[allow(clippy::too_many_arguments)] fn verify_requested_restrictions( pres_req: &PresentationRequestPayload, schemas: &HashMap<&SchemaId, &Schema>, @@ -891,7 +786,7 @@ fn process_filter( } tag_ @ "schema_name" => precess_filed(tag_, &filter.schema_name, tag_value), tag_ @ "schema_version" => precess_filed(tag_, &filter.schema_version, tag_value), - tag_ @ "cred_def_id" => precess_filed(tag_, &filter.cred_def_id.to_string(), tag_value), + tag_ @ "cred_def_id" => precess_filed(tag_, filter.cred_def_id.to_string(), tag_value), tag_ @ "issuer_did" => precess_filed(tag_, filter.issuer_id.to_owned(), tag_value), tag_ @ "issuer_id" => precess_filed(tag_, filter.issuer_id.to_owned(), tag_value), x if is_attr_internal_tag(x, attr_value_map) => { @@ -1303,25 +1198,4 @@ mod tests { ); res } - - fn _interval() -> NonRevocedInterval { - NonRevocedInterval { - from: None, - to: Some(1234), - } - } - - #[test] - fn validate_timestamp_works() { - validate_timestamp(&_received(), "referent_1", &None, &None).unwrap(); - validate_timestamp(&_received(), "referent_1", &Some(_interval()), &None).unwrap(); - validate_timestamp(&_received(), "referent_1", &None, &Some(_interval())).unwrap(); - } - - #[test] - fn validate_timestamp_not_work() { - validate_timestamp(&_received(), "referent_2", &Some(_interval()), &None).unwrap_err(); - validate_timestamp(&_received(), "referent_2", &None, &Some(_interval())).unwrap_err(); - validate_timestamp(&_received(), "referent_3", &None, &Some(_interval())).unwrap_err(); - } } diff --git a/tests/anoncreds_demos.rs b/tests/anoncreds_demos.rs index 45efa978..aed70f54 100644 --- a/tests/anoncreds_demos.rs +++ b/tests/anoncreds_demos.rs @@ -22,8 +22,7 @@ use anoncreds::{ use serde_json::json; -use self::utils::anoncreds::ProverWallet; - +use utils::*; mod utils; pub static SCHEMA_ID: &str = "mock:uri"; @@ -220,6 +219,7 @@ fn anoncreds_works_for_single_issuer_single_prover() { &cred_defs, None, None, + None, ) .expect("Error verifying presentation"); assert!(valid); @@ -432,6 +432,7 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { &cred_defs, Some(&rev_reg_def_map), Some(rev_status_list.clone()), + None, ) .expect("Error verifying presentation"); assert!(valid); @@ -477,6 +478,7 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { &cred_defs, Some(&rev_reg_def_map), Some(rev_status_list), + None, ) .expect("Error verifying presentation"); assert!(!valid); diff --git a/tests/multiple-credentials.rs b/tests/multiple-credentials.rs index a72aa5d7..431d94db 100644 --- a/tests/multiple-credentials.rs +++ b/tests/multiple-credentials.rs @@ -2,7 +2,8 @@ use std::collections::HashMap; use anoncreds::{ data_types::{ - pres_request::{NonRevocedInterval, PresentationRequestPayload}, + pres_request::{NonRevokedInterval, PresentationRequestPayload}, + rev_reg_def::RevocationRegistryDefinitionId, schema::SchemaId, }, types::PresentationRequest, @@ -19,6 +20,15 @@ pub static REV_IDX_1: u32 = 9; pub static REV_IDX_2: u32 = 9; pub static MAX_CRED_NUM: u32 = 10; pub static TF_PATH: &str = "../.tmp"; + +// NonRevoked Interval consts +const GLOBAL_FROM: u64 = 5; +const GLOBAL_TO: u64 = 25; +const LOCAL_FROM: u64 = 10; +const OVERRIDE_LOCAL_FROM: u64 = 8; +const LOCAL_TO: u64 = 20; +const TS_WITHIN_LOCAL_OVERRIDE: u64 = 9; + const SCHEMA_ID_1: &str = "mock:uri:schema1"; const SCHEMA_ID_2: &str = "mock:uri:schema2"; const SCHEMA_1: &str = r#"{"name":"gvt","version":"1.0","attrNames":["name","sex","age","height"],"issuerId":"mock:issuer_id/path&q=bar"}"#; @@ -28,21 +38,16 @@ static CRED_DEF_ID_2: &'static str = "mock:uri:2"; static REV_REG_ID_1: &'static str = "mock:uri:revregid1"; static REV_REG_ID_2: &'static str = "mock:uri:revregid2"; -// Credential 1 is revocable -// Credential 2 is non-revocable -// There are 2 definitions, issued by 2 issuers -fn test_2_different_revoke_reqs() -> Vec { - let nonce_1 = verifier::generate_nonce().expect("Error generating presentation request nonce"); - let nonce_2 = verifier::generate_nonce().expect("Error generating presentation request nonce"); - +fn create_request(input: &ReqInput) -> PresentationRequest { + let nonce = verifier::generate_nonce().unwrap(); let json = json!({ - "nonce": nonce_1, - "name":"pres_req_1", + "nonce": nonce, + "name":input.req_name , "version":"0.1", "requested_attributes":{ "attr1_referent":{ "name":"name", - "issuer_id": ISSUER_ID + "issuer_id": input.issuer, }, "attr2_referent":{ "name":"sex" @@ -59,39 +64,29 @@ fn test_2_different_revoke_reqs() -> Vec { }, }); - let mut p1: PresentationRequestPayload = serde_json::from_value(json.clone()).unwrap(); - let mut p2: PresentationRequestPayload = serde_json::from_value(json).unwrap(); + let mut presentation: PresentationRequestPayload = serde_json::from_value(json).unwrap(); + presentation.non_revoked = input.global_nonrevoke.clone(); - // Global non_revoked - p1.non_revoked = Some(NonRevocedInterval { - from: Some(10), - to: Some(20), - }); - p1.nonce = nonce_1; - p2.nonce = nonce_2; - - // Local non_revoked - if let Some(at) = p2.requested_attributes.get_mut("attr4_referent") { - at.non_revoked = Some(NonRevocedInterval { - from: Some(10), - to: Some(20), - }) - } else { - panic!("Cannot add non_revoke to attri"); + for ni in input.attr_nonrevoke.iter() { + let at = presentation.requested_attributes.get_mut(ni.0).unwrap(); + at.non_revoked = Some(ni.1.clone()); } - vec![ - PresentationRequest::PresentationRequestV1(p1), - PresentationRequest::PresentationRequestV1(p2), - ] -} + for ni in input.pred_nonrevoke.iter() { + let at = presentation.requested_predicates.get_mut(ni.0).unwrap(); + at.non_revoked = Some(ni.1.clone()); + } -#[test] -fn anoncreds_with_multiple_credentials_per_request() { - let mut mock = utils::Mock::new(&[ISSUER_ID], &[PROVER_ID], TF_PATH, MAX_CRED_NUM); + log::info!("\n Request: {:?}", presentation); + PresentationRequest::PresentationRequestV1(presentation) +} +fn create_issuer_data<'a>() -> utils::IssuerValues<'a> { // These are what the issuer knows - let issuer1_creds: utils::IsserValues = HashMap::from([ + // Credential 1 is revocable + // Credential 2 is non-revocable + // There are 2 definitions, issued by 1 issuer + let issuer1_creds: utils::IssuerValues = HashMap::from([ ( CRED_DEF_ID_1, ( @@ -116,14 +111,90 @@ fn anoncreds_with_multiple_credentials_per_request() { ("house", "Hufflepuff"), ("year", "1990"), ]), - // Issue 42 addresses if it is not revocable, - // revocation should still pass - true, + false, REV_REG_ID_2, REV_IDX_2, ), ), ]); + issuer1_creds +} + +pub struct ReqInput<'a> { + pub req_name: &'a str, + pub issuer: &'a str, + pub global_nonrevoke: Option, + pub attr_nonrevoke: Vec<(&'a str, NonRevokedInterval)>, + pub pred_nonrevoke: Vec<(&'a str, NonRevokedInterval)>, +} + +fn test_requests_generate<'a>() -> Vec> { + let r0 = ReqInput { + req_name: "global_rev", + issuer: ISSUER_ID, + global_nonrevoke: Some(NonRevokedInterval::new(Some(GLOBAL_FROM), Some(GLOBAL_TO))), + attr_nonrevoke: vec![], + pred_nonrevoke: vec![], + }; + let r1 = ReqInput { + req_name: "local_rev", + issuer: ISSUER_ID, + global_nonrevoke: None, + attr_nonrevoke: vec![ + ( + "attr2_referent", + NonRevokedInterval::new(Some(LOCAL_FROM), Some(LOCAL_TO)), + ), + ( + "attr5_referent", + NonRevokedInterval::new(Some(LOCAL_FROM), Some(LOCAL_TO)), + ), + ], + pred_nonrevoke: vec![], + }; + let r2 = ReqInput { + req_name: "both_rev_attr", + issuer: ISSUER_ID, + global_nonrevoke: Some(NonRevokedInterval::new(Some(GLOBAL_FROM), Some(GLOBAL_TO))), + attr_nonrevoke: vec![ + ( + "attr2_referent", + NonRevokedInterval::new(Some(LOCAL_FROM), Some(LOCAL_TO)), + ), + ( + "attr5_referent", + NonRevokedInterval::new(Some(LOCAL_FROM), Some(LOCAL_TO)), + ), + ], + pred_nonrevoke: vec![], + }; + let r3 = ReqInput { + req_name: "both_rev_pred", + issuer: ISSUER_ID, + global_nonrevoke: Some(NonRevokedInterval::new(Some(GLOBAL_FROM), Some(GLOBAL_TO))), + attr_nonrevoke: vec![], + pred_nonrevoke: vec![( + "predicate1_referent", + NonRevokedInterval::new(Some(LOCAL_FROM), Some(LOCAL_TO)), + )], + }; + let r4 = ReqInput { + req_name: "no_rev", + issuer: ISSUER_ID, + global_nonrevoke: None, + attr_nonrevoke: vec![], + pred_nonrevoke: vec![], + }; + + vec![r0, r1, r2, r3, r4] +} + +#[test] +fn anoncreds_with_multiple_credentials_per_request() { + env_logger::init(); + let mut mock = utils::Mock::new(&[ISSUER_ID], &[PROVER_ID], TF_PATH, MAX_CRED_NUM); + + let issuer1_creds = create_issuer_data(); let schemas = HashMap::from([ ( @@ -138,16 +209,21 @@ fn anoncreds_with_multiple_credentials_per_request() { mock.ledger.schemas = schemas; - // This is out of range of revoke interval, should get warning - let time_initial_rev_reg = 123u64; - let time_after_credential = 124u64; + // These are within interval + let time_initial_rev_reg = 8u64; + let time_after_credential = TS_WITHIN_LOCAL_OVERRIDE; let issuance_by_default = true; - // To test: - // pres_request_1: global interval; Tests verification for revocable credentials only - // pres_request_2: local intervals for both credential; Tests verification for revocable credentials only - // Verifier creates a presentation request for each - let reqs = test_2_different_revoke_reqs(); + // This returns Presentation Requests with following nonrevoked intervals + // [0]: Global + // [1]: Local for attributes belonging to both credentials + // [2]: Global and Local attributes , where local is more stringent + // [3]: Global and Local predeicate, where local is more stringent + // [4]: no NRP required + let reqs: Vec = test_requests_generate() + .iter() + .map(|x| create_request(&x)) + .collect(); // 1: Issuer setup (credate cred defs, rev defs(optional), cred_offers) mock.issuer_setup( @@ -194,9 +270,32 @@ fn anoncreds_with_multiple_credentials_per_request() { ); presentations.push(p) } + // 5. Verifier verifies one presentation per request - let results = mock.verifer_verifies_presentations_for_requests(presentations, &reqs); + // + // Without override fails + let overrides = vec![None; 5]; + let results = + mock.verifer_verifies_presentations_for_requests(&presentations, &reqs, &overrides); + assert!(results[0].is_ok()); + assert!(results[4].is_ok()); + assert!(results[1].is_err()); + assert!(results[2].is_err()); + assert!(results[3].is_err()); - assert!(results[0]); - assert!(results[1]); + // Create overrides for timestamps + let id = RevocationRegistryDefinitionId::new_unchecked(REV_REG_ID_1); + let override_rev1 = HashMap::from([(&id, HashMap::from([(LOCAL_FROM, OVERRIDE_LOCAL_FROM)]))]); + let overrides = vec![ + None, + Some(&override_rev1), + Some(&override_rev1), + Some(&override_rev1), + None, + ]; + let results = + mock.verifer_verifies_presentations_for_requests(&presentations, &reqs, &overrides); + assert!(results[1].is_ok()); + assert!(results[2].is_ok()); + assert!(results[3].is_ok()); } diff --git a/tests/utils/mock.rs b/tests/utils/mock.rs index d06ed66a..09b310c9 100644 --- a/tests/utils/mock.rs +++ b/tests/utils/mock.rs @@ -1,4 +1,4 @@ -use super::anoncreds::{IssuerWallet, Ledger, ProverWallet, StoredCredDef, StoredRevDef}; +use super::storage::{IssuerWallet, Ledger, ProverWallet, StoredCredDef, StoredRevDef}; use std::{ collections::{BTreeSet, HashMap}, fs::create_dir, @@ -23,16 +23,21 @@ use anoncreds::{ verifier, }; +pub struct TestError(String); + // {cred_def_id: { // schema_id, credential_values, support_revocation, rev_reg_id, rev_idx // }} -pub type IsserValues<'a> = +pub type IssuerValues<'a> = HashMap<&'a str, (&'a str, HashMap<&'a str, &'a str>, bool, &'a str, u32)>; // {cred_def_id: { // attribute_per_credential, predicate_for_credential }} pub type ProverValues<'a> = HashMap<&'a str, (Vec<&'a str>, Vec<&'a str>)>; +// { rev_reg_def_id: {req_timestamp, override_timestamp} } +pub type Override<'a> = HashMap<&'a RevocationRegistryDefinitionId, HashMap>; + #[derive(Debug)] pub struct Mock<'a> { pub issuer_wallets: HashMap<&'a str, IssuerWallet<'a>>, @@ -69,9 +74,10 @@ impl<'a> Mock<'a> { pub fn verifer_verifies_presentations_for_requests( &self, - presentations: Vec, + presentations: &[Presentation], reqs: &[PresentationRequest], - ) -> Vec { + overrides: &[Option<&Override>], + ) -> Vec> { let mut results = vec![]; let schemas: HashMap<&SchemaId, &Schema> = HashMap::from_iter(self.ledger.schemas.iter()); let cred_defs: HashMap<&CredentialDefinitionId, &CredentialDefinition> = @@ -82,7 +88,6 @@ impl<'a> Mock<'a> { .revcation_list .iter() .for_each(|(_, v)| v.iter().for_each(|(_, list)| rev_status_lists.push(list))); - let rev_reg_def_map = HashMap::from_iter(self.ledger.rev_reg_defs.iter()); for (i, presentation) in presentations.iter().enumerate() { @@ -93,8 +98,9 @@ impl<'a> Mock<'a> { &cred_defs, Some(&rev_reg_def_map), Some(rev_status_lists.clone()), + overrides[i], ) - .expect("Error verifying presentation"); + .map_err(|e| TestError(e.to_string())); results.push(valid); } results @@ -108,7 +114,7 @@ impl<'a> Mock<'a> { &mut self, issuer_id: &'static str, prover_id: &'static str, - values: &'a IsserValues, + values: &'a IssuerValues, time_now: u64, issuance_by_default: bool, ) { @@ -287,7 +293,7 @@ impl<'a> Mock<'a> { &mut self, issuer_id: &'static str, prover_id: &'static str, - values: &'a IsserValues, + values: &'a IssuerValues, time_prev_rev_reg: u64, time_new_rev_reg: u64, ) { @@ -412,10 +418,12 @@ impl<'a> Mock<'a> { .get(cred.cred_def_id.to_string().as_str()) .unwrap(); { - let (rev_state, timestamp) = match &cred.rev_reg_id { - Some(id) => self.prover_wallets[prover_id].rev_states.get(&id).unwrap(), - None => &(None, None), + let (rev_state, timestamp) = if let Some(id) = &cred.rev_reg_id { + self.prover_wallets[prover_id].rev_states.get(&id).unwrap() + } else { + &(None, None) }; + let mut cred1 = present.add_credential(cred, *timestamp, rev_state.as_ref()); for a in &values.0 { cred1.add_requested_attribute(a.clone(), true); diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 393bb2c9..c61542c2 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -1,4 +1,5 @@ -pub mod anoncreds; pub mod mock; +pub mod storage; -pub use mock::{IsserValues, Mock, ProverValues}; +pub use mock::*; +pub use storage::*; diff --git a/tests/utils/anoncreds.rs b/tests/utils/storage.rs similarity index 100% rename from tests/utils/anoncreds.rs rename to tests/utils/storage.rs