diff --git a/anoncreds/src/data_types/anoncreds/rev_reg.rs b/anoncreds/src/data_types/anoncreds/rev_reg.rs index 846dd0fc..d2441fda 100644 --- a/anoncreds/src/data_types/anoncreds/rev_reg.rs +++ b/anoncreds/src/data_types/anoncreds/rev_reg.rs @@ -91,6 +91,7 @@ impl RevocationStatusList { revoked: Option>, timestamp: Option, ) -> Result<(), error::Error> { + // only update if input is Some if let Some(reg) = registry { self.registry = Some(reg) }; @@ -120,6 +121,7 @@ impl RevocationStatusList { *bit = true; } } + // only update if input is Some if let Some(t) = timestamp { self.timestamp = Some(t); } @@ -195,6 +197,28 @@ pub mod serde_revocation_list { } } +// NonRevocationProof warnings: +// - timestamps_out_of_range: all timestamps used for rev_state that are o.o.r +// this can be for request / attribute / predicate level time intervals +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct NonRevProofWarnings { + pub timestamps_out_of_range: Option>, +} + +impl NonRevProofWarnings { + pub fn new(referents: Vec) -> Self { + if referents.is_empty() { + Self { + timestamps_out_of_range: None, + } + } else { + Self { + timestamps_out_of_range: Some(referents), + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/anoncreds/src/ffi/credential.rs b/anoncreds/src/ffi/credential.rs index 7630cfc3..988fef85 100644 --- a/anoncreds/src/ffi/credential.rs +++ b/anoncreds/src/ffi/credential.rs @@ -30,7 +30,6 @@ pub struct FfiCredRevInfo<'a> { struct RevocationConfig { reg_def: AnonCredsObject, reg_def_private: AnonCredsObject, - registry: AnonCredsObject, reg_idx: u32, tails_path: String, } @@ -40,7 +39,6 @@ impl RevocationConfig { Ok(CredentialRevocationConfig { reg_def: self.reg_def.cast_ref()?, reg_def_private: self.reg_def_private.cast_ref()?, - registry: self.registry.cast_ref()?, registry_idx: self.reg_idx, tails_reader: TailsFileReader::new_tails_reader(self.tails_path.as_str()), }) @@ -112,7 +110,6 @@ pub extern "C" fn anoncreds_create_credential( Some(RevocationConfig { reg_def: revocation.reg_def.load()?, reg_def_private: revocation.reg_def_private.load()?, - registry: revocation.registry.load()?, reg_idx: revocation .reg_idx .try_into() diff --git a/anoncreds/src/ffi/presentation.rs b/anoncreds/src/ffi/presentation.rs index 24382b35..67d03fbc 100644 --- a/anoncreds/src/ffi/presentation.rs +++ b/anoncreds/src/ffi/presentation.rs @@ -6,7 +6,7 @@ use super::error::{catch_error, ErrorCode}; use super::object::{AnonCredsObject, AnonCredsObjectList, ObjectHandle}; use super::util::{FfiList, FfiStrList}; use crate::data_types::anoncreds::cred_def::{CredentialDefinition, CredentialDefinitionId}; -use crate::data_types::anoncreds::rev_reg::RevocationRegistryId; +use crate::data_types::anoncreds::rev_reg::{NonRevProofWarnings, RevocationRegistryId}; use crate::data_types::anoncreds::rev_reg_def::{ RevocationRegistryDefinition, RevocationRegistryDefinitionId, }; @@ -21,6 +21,12 @@ use crate::services::{ impl_anoncreds_object!(Presentation, "Presentation"); impl_anoncreds_object_from_json!(Presentation, anoncreds_presentation_from_json); +impl_anoncreds_object!(NonRevProofWarnings, "NonRevProofWarnings"); +impl_anoncreds_object_from_json!( + NonRevProofWarnings, + anoncreds_non_rev_proof_warnings_from_json +); + #[derive(Debug)] #[repr(C)] pub struct FfiCredentialEntry { @@ -230,6 +236,7 @@ pub extern "C" fn anoncreds_verify_presentation( rev_reg_def_ids: FfiStrList, rev_reg_entries: FfiList, result_p: *mut i8, + non_rev_proof_warning_p: *mut ObjectHandle, ) -> ErrorCode { catch_error(|| { if schemas.len() != schema_ids.len() { @@ -301,7 +308,7 @@ pub extern "C" fn anoncreds_verify_presentation( &rev_reg_def_identifiers, )?; - let verify = verify_presentation( + let (verify, nrp) = verify_presentation( presentation.load()?.cast_ref()?, pres_req.load()?.cast_ref()?, &schemas, @@ -310,6 +317,8 @@ pub extern "C" fn anoncreds_verify_presentation( Some(&rev_regs), )?; unsafe { *result_p = verify as i8 }; + let nrp = ObjectHandle::create(nrp)?; + unsafe { *non_rev_proof_warning_p = nrp }; Ok(()) }) } diff --git a/anoncreds/src/services/issuer.rs b/anoncreds/src/services/issuer.rs index 755040b1..33c2e769 100644 --- a/anoncreds/src/services/issuer.rs +++ b/anoncreds/src/services/issuer.rs @@ -1,7 +1,6 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use std::collections::{BTreeSet, HashSet}; -use std::convert::{From, Into}; use std::iter::FromIterator; use indy_utils::{Validatable, ValidationError}; @@ -219,61 +218,60 @@ pub fn create_revocation_status_list( ) } +/// Update the timestamp only without changing any actual state +pub fn update_revocation_status_list_timestamp_only( + timestamp: u64, + current_list: &RevocationStatusList, +) -> RevocationStatusList { + let mut list = current_list.clone(); + // this does not error as only timestamp is updated + list.update(None, None, None, Some(timestamp)).unwrap(); + list +} /// Update Revocation Status List -/// - if `rev_reg_def` is `Some`: updates will be made to the accumulator and status_list bitvec -/// according to the `BTreeSet` -/// - if `rev_reg_def` is `None`: only the timestamp will be updated +/// - if `timestamp` is `None`: the timestamp is not updated pub fn update_revocation_status_list( timestamp: Option, issued: Option>, revoked: Option>, - rev_reg_def: Option<&RevocationRegistryDefinition>, + rev_reg_def: &RevocationRegistryDefinition, current_list: &RevocationStatusList, ) -> Result { let mut new_list = current_list.clone(); - if let Some(rev_reg_def) = rev_reg_def { - let issued = issued.map(|i_list| { - BTreeSet::from_iter( - i_list - .into_iter() - .filter(|&i| current_list.get(i as usize).unwrap_or(false)), - ) - }); - - let revoked = revoked.map(|r_list| { - BTreeSet::from_iter( - r_list - .into_iter() - .filter(|&i| !current_list.get(i as usize).unwrap_or(true)), - ) - }); - - let rev_reg_opt: Option = current_list.into(); - let mut rev_reg = rev_reg_opt.ok_or_else(|| { - Error::from_msg( - ErrorKind::Unexpected, - "Require Accumulator Value to update Rev Status List", - ) - })?; - let tails_reader = TailsFileReader::new_tails_reader(&rev_reg_def.value.tails_location); - let max_cred_num = rev_reg_def.value.max_cred_num; + let issued = issued.map(|i_list| { + BTreeSet::from_iter( + i_list + .into_iter() + .filter(|&i| current_list.get(i as usize).unwrap_or(false)), + ) + }); + + let revoked = revoked.map(|r_list| { + BTreeSet::from_iter( + r_list + .into_iter() + .filter(|&i| !current_list.get(i as usize).unwrap_or(true)), + ) + }); + + let rev_reg_opt: Option = current_list.into(); + let mut rev_reg = rev_reg_opt.ok_or_else(|| { + Error::from_msg( + ErrorKind::Unexpected, + "Require Accumulator Value to update Rev Status List", + ) + })?; + let tails_reader = TailsFileReader::new_tails_reader(&rev_reg_def.value.tails_location); + let max_cred_num = rev_reg_def.value.max_cred_num; - CryptoIssuer::update_revocation_registry( - &mut rev_reg, - max_cred_num, - issued.clone().unwrap_or_default(), - revoked.clone().unwrap_or_default(), - &tails_reader, - )?; - new_list.update(Some(rev_reg), issued, revoked, timestamp)?; - } else if timestamp.is_some() { - new_list.update(None, None, None, timestamp)?; - } else { - return Err(err_msg!( - Unexpected, - "Either timestamp or Revocation Registry Definition must be provided" - )); - } + CryptoIssuer::update_revocation_registry( + &mut rev_reg, + max_cred_num, + issued.clone().unwrap_or_default(), + revoked.clone().unwrap_or_default(), + &tails_reader, + )?; + new_list.update(Some(rev_reg), issued, revoked, timestamp)?; Ok(new_list) } @@ -332,7 +330,13 @@ pub fn create_credential( match (revocation_config, rev_status_list) { (Some(revocation_config), Some(rev_status_list)) => { let rev_reg_def = &revocation_config.reg_def.value; - let mut rev_reg = revocation_config.registry.value.clone(); + let rev_reg: Option = rev_status_list.into(); + let mut rev_reg = rev_reg.ok_or_else(|| { + err_msg!( + Unexpected, + "RevocationStatusList should have accumulator value" + ) + })?; let status = rev_status_list .get(revocation_config.registry_idx as usize) @@ -423,7 +427,7 @@ pub fn create_credential( values: cred_values, signature: credential_signature, signature_correctness_proof, - rev_reg: rev_reg.clone(), + rev_reg, witness, }; diff --git a/anoncreds/src/services/prover.rs b/anoncreds/src/services/prover.rs index aff27063..f56fae75 100644 --- a/anoncreds/src/services/prover.rs +++ b/anoncreds/src/services/prover.rs @@ -21,8 +21,9 @@ use crate::error::{Error, Result}; use crate::services::helpers::*; use crate::ursa::cl::{ issuer::Issuer as CryptoIssuer, prover::Prover as CryptoProver, - verifier::Verifier as CryptoVerifier, CredentialPublicKey, RevocationRegistryDelta, - SubProofRequest, Witness, + verifier::Verifier as CryptoVerifier, CredentialPublicKey, + RevocationRegistry as CryptoRevocationRegistry, RevocationRegistryDelta, SubProofRequest, + Witness, }; use indy_utils::Validatable; @@ -246,6 +247,25 @@ pub fn create_presentation( Ok(full_proof) } +pub fn create_or_update_revocation_state_with_witness( + witness: Witness, + revocation_status_list: &RevocationStatusList, + timestamp: u64, +) -> Result { + let rev_reg = <&RevocationStatusList as Into>>::into( + revocation_status_list, + ) + .ok_or_else(|| err_msg!(Unexpected, "Revocation Status List must have accum value"))?; + + Ok(CredentialRevocationState { + witness, + rev_reg, + timestamp, + }) +} + +// This can be done by anyone, allowing prover to offload this task +// The tails_path here is used instead of tails_location in `revoc_reg_def` so prover can provide it pub fn create_or_update_revocation_state( tails_path: &str, revoc_reg_def: &RevocationRegistryDefinition, @@ -317,10 +337,6 @@ pub fn create_or_update_revocation_state( &mut issued, &mut revoked, ); - let rev_reg: Option = rev_status_list.into(); - let rev_reg = rev_reg.ok_or_else(|| { - err_msg!("revocation registry is not inside the revocation status list") - })?; let rev_reg_delta = RevocationRegistryDelta::from_parts(None, &rev_reg, &issued, &revoked); Witness::new( rev_reg_idx, diff --git a/anoncreds/src/services/types.rs b/anoncreds/src/services/types.rs index f62d90c8..d412cf85 100644 --- a/anoncreds/src/services/types.rs +++ b/anoncreds/src/services/types.rs @@ -217,8 +217,6 @@ impl Validatable for CredentialRevocationState { pub struct CredentialRevocationConfig<'a> { pub reg_def: &'a RevocationRegistryDefinition, pub reg_def_private: &'a RevocationRegistryDefinitionPrivate, - // TODO remove this! - pub registry: &'a RevocationRegistry, pub registry_idx: u32, pub tails_reader: TailsReader, } @@ -227,10 +225,9 @@ impl<'a> std::fmt::Debug for CredentialRevocationConfig<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "CredentialRevocationConfig {{ reg_def: {:?}, private: {:?}, registry: {:?}, idx: {}, reader: {:?} }}", + "CredentialRevocationConfig {{ reg_def: {:?}, private: {:?}, idx: {}, reader: {:?} }}", self.reg_def, secret!(self.reg_def_private), - self.registry, secret!(self.registry_idx), self.tails_reader, ) diff --git a/anoncreds/src/services/verifier.rs b/anoncreds/src/services/verifier.rs index bf85b598..960351cd 100644 --- a/anoncreds/src/services/verifier.rs +++ b/anoncreds/src/services/verifier.rs @@ -8,7 +8,7 @@ use super::types::*; use crate::data_types::anoncreds::cred_def::CredentialDefinition; use crate::data_types::anoncreds::cred_def::CredentialDefinitionId; use crate::data_types::anoncreds::issuer_id::IssuerId; -use crate::data_types::anoncreds::rev_reg::RevocationRegistryId; +use crate::data_types::anoncreds::rev_reg::{NonRevProofWarnings, RevocationRegistryId}; use crate::data_types::anoncreds::rev_reg_def::RevocationRegistryDefinitionId; use crate::data_types::anoncreds::schema::Schema; use crate::data_types::anoncreds::schema::SchemaId; @@ -42,7 +42,7 @@ pub fn verify_presentation( cred_defs: &HashMap<&CredentialDefinitionId, &CredentialDefinition>, rev_reg_defs: Option<&HashMap<&RevocationRegistryDefinitionId, &RevocationRegistryDefinition>>, rev_regs: Option<&HashMap>>, -) -> Result { +) -> Result<(bool, NonRevProofWarnings)> { trace!("verify >>> presentation: {:?}, pres_req: {:?}, schemas: {:?}, cred_defs: {:?}, rev_reg_defs: {:?} rev_regs: {:?}", presentation, pres_req, schemas, cred_defs, rev_reg_defs, rev_regs); @@ -53,6 +53,7 @@ pub fn verify_presentation( received_unrevealed_attrs(presentation)?; let received_predicates: HashMap = received_predicates(presentation)?; let received_self_attested_attrs: HashSet = received_self_attested_attrs(presentation); + let mut nrp_warnings = NonRevProofWarnings::default(); compare_attr_from_proof_and_request( pres_req, @@ -75,20 +76,21 @@ 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()?; for sub_proof_index in 0..presentation.identifiers.len() { + 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 identifier = presentation.identifiers[sub_proof_index].clone(); let schema = schemas @@ -103,7 +105,18 @@ pub fn verify_presentation( ) })?; - let (rev_reg_def, rev_reg) = if let Some(timestamp) = identifier.timestamp { + // Checks that there is a NRP requirement in the request AND that credential is revocable + let (rev_reg_def, rev_reg) = if let (Some(timestamp), Some(_)) = + (identifier.timestamp, cred_def.value.revocation.as_ref()) + { + checks_nrp_is_provided( + &pres_req, + &attrs_for_credential, + &predicates_for_credential, + &identifier, + &mut nrp_warnings, + )?; + let rev_reg_id = identifier.rev_reg_id.clone().ok_or_else(|| { err_msg!("Timestamp provided but Revocation Registry Id not found") })?; @@ -153,17 +166,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)?; @@ -189,8 +191,9 @@ pub fn verify_presentation( let valid = proof_verifier.verify(&presentation.proof, pres_req.nonce.as_native())?; trace!("verify <<< valid: {:?}", valid); + trace!("non_rev_proof_warnings <<< {:?}", nrp_warnings); - Ok(valid) + Ok((valid, nrp_warnings)) } pub fn generate_nonce() -> Result { @@ -304,55 +307,50 @@ 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( +//checks_nrp_is_provided(pres_req.non_revoked, attrs_for_credential, predicates_for_credential, identifier, nrp_warnings)? +fn checks_nrp_is_provided( pres_req: &PresentationRequestPayload, - received_revealed_attrs: &HashMap, - received_unrevealed_attrs: &HashMap, - received_self_attested_attrs: &HashSet, - received_predicates: &HashMap, + attrs_for_credential: &[AttributeInfo], + predicates_for_credential: &[PredicateInfo], + identifier: &Identifier, + nrp_warnings: &mut NonRevProofWarnings, ) -> Result<()> { - pres_req - .requested_attributes + let mut warnings = vec![]; + attrs_for_credential .iter() - .map(|(referent, info)| { + .map(|info| { + let name = match (info.name.clone(), info.names.clone()) { + (Some(name), None) => name, + (None, Some(names)) => format!("{:?}", names), + _ => return Err(err_msg!("missing name / names for attribute")), + }; + validate_timestamp( - received_revealed_attrs, - referent, + &mut warnings, + identifier, + &name, &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")) - }) }) .collect::>>()?; - pres_req - .requested_predicates + predicates_for_credential .iter() - .map(|(referent, info)| { + .map(|info| { validate_timestamp( - received_predicates, - referent, + &mut warnings, + identifier, + &info.name, &pres_req.non_revoked, &info.non_revoked, ) }) .collect::>>()?; + if !warnings.is_empty() { + nrp_warnings.timestamps_out_of_range = Some(warnings); + } Ok(()) } @@ -368,26 +366,29 @@ fn compare_timestamps_from_proof_and_request( // which was added by the prover when creating `PresentCredentials`, // an arg for `create_presentation`. // -// TODO: this timestamp should be compared with the provided interval +// If the NRP is given at a timestamp outside of the request interval, +// a wanring is provided for the verifier to decide if they will reject the proof or not fn validate_timestamp( - received_: &HashMap, - referent: &str, + warnings: &mut Vec, + identifier: &Identifier, + name: &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")); + if let Some(interval) = get_non_revoc_interval(global_interval, local_interval) { + // If there is global or local interval, we compare the timestamp provided + if let Some(ts) = identifier.timestamp { + if ts.gt(&interval.to.unwrap_or(u64::MAX)) || ts.lt(&interval.from.unwrap_or(0)) { + // We add to NonRevProofWarnings that the referent is out of range + warnings.push(name.to_string()); + } + Ok(()) + } else { + Err(err_msg!("Missing timestamp")) + } + } else { + Ok(()) } - - Ok(()) } fn received_revealed_attrs(proof: &Presentation) -> Result> { @@ -943,6 +944,8 @@ mod tests { pub const SCHEMA_VERSION: &str = "1.2.3"; pub const CRED_DEF_ID: &str = "345"; pub const ISSUER_ID: &str = "1111111111111111111111"; + pub const FROM: u64 = 1000; + pub const TO: u64 = 2000; fn schema_id_tag() -> String { "schema_id".to_string() @@ -1244,47 +1247,145 @@ mod tests { assert!(_process_operator("zip", &op, &filter, Some("NOT HERE")).is_err()); } - fn _received() -> HashMap { - let mut res: HashMap = HashMap::new(); - res.insert( - "referent_1".to_string(), - Identifier { + fn _interval(from: Option, to: Option) -> NonRevocedInterval { + NonRevocedInterval { from, to } + } + + fn _get_identifier(identifier: &str) -> Identifier { + match identifier { + "within_interval" => Identifier { timestamp: Some(1234), schema_id: SchemaId::default(), cred_def_id: CredentialDefinitionId::default(), rev_reg_id: Some(RevocationRegistryId::default()), }, - ); - res.insert( - "referent_2".to_string(), - Identifier { + "before_from" => Identifier { + timestamp: Some(1), + schema_id: SchemaId::default(), + cred_def_id: CredentialDefinitionId::default(), + rev_reg_id: Some(RevocationRegistryId::default()), + }, + "after_to" => Identifier { + timestamp: Some(4999), + schema_id: SchemaId::default(), + cred_def_id: CredentialDefinitionId::default(), + rev_reg_id: Some(RevocationRegistryId::default()), + }, + "none" => Identifier { timestamp: None, schema_id: SchemaId::default(), cred_def_id: CredentialDefinitionId::default(), rev_reg_id: Some(RevocationRegistryId::default()), }, - ); - res + _ => panic!("no such identifier"), + } } - fn _interval() -> NonRevocedInterval { - NonRevocedInterval { - from: None, - to: Some(1234), - } + #[test] + fn validate_timestamp_without_interval_works() { + let mut warnings: Vec = vec![]; + validate_timestamp( + &mut warnings, + &_get_identifier("within_interval"), + "within_interval", + &None, + &None, + ) + .unwrap(); + assert!(warnings.is_empty()); + } + + #[test] + fn validate_timestamp_with_global_interval_within_range() { + let mut warnings: Vec = vec![]; + validate_timestamp( + &mut warnings, + &_get_identifier("within_interval"), + "within_interval", + &Some(_interval(Some(FROM), Some(TO))), + &None, + ) + .unwrap(); + assert!(warnings.is_empty()); + } + + #[test] + fn validate_timestamp_with_local_interval_within_range() { + let mut warnings: Vec = vec![]; + validate_timestamp( + &mut warnings, + &_get_identifier("within_interval"), + "within_interval", + &None, + &Some(_interval(Some(FROM), Some(TO))), + ) + .unwrap(); + assert!(warnings.is_empty()); } #[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(); + fn validate_timestamp_with_global_interval_out_of_range_before() { + let mut warnings: Vec = vec![]; + validate_timestamp( + &mut warnings, + &_get_identifier("before_from"), + "before_from", + &Some(_interval(Some(FROM), Some(TO))), + &None, + ) + .unwrap(); + assert_eq!(warnings.pop().unwrap(), "before_from"); } #[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(); + fn validate_timestamp_with_global_interval_out_of_range_after() { + let mut warnings: Vec = vec![]; + validate_timestamp( + &mut warnings, + &_get_identifier("after_to"), + "after_to", + &Some(_interval(Some(FROM), Some(TO))), + &None, + ) + .unwrap(); + assert_eq!(warnings.pop().unwrap(), "after_to"); + } + + #[test] + fn validate_timestamp_with_local_interval_requirements_over_global_interval() { + let mut warnings: Vec = vec![]; + validate_timestamp( + &mut warnings, + &_get_identifier("within_interval"), + "within_interval", + &Some(_interval(Some(FROM), Some(TO))), + &Some(_interval(Some(FROM + 500), Some(TO))), + ) + .unwrap(); + assert_eq!(warnings.pop().unwrap(), "within_interval"); + } + + #[test] + fn validate_timestamp_not_work_without_timestamp_for_global_interval() { + validate_timestamp( + &mut vec![], + &_get_identifier("none"), + "none", + &Some(_interval(Some(FROM), Some(TO))), + &None, + ) + .unwrap_err(); + } + + #[test] + fn validate_timestamp_fails_without_timestamp_for_local_interval() { + validate_timestamp( + &mut vec![], + &_get_identifier("none"), + "none", + &None, + &Some(_interval(Some(FROM), Some(TO))), + ) + .unwrap_err(); } } diff --git a/anoncreds/tests/anoncreds_demos.rs b/anoncreds/tests/anoncreds_demos.rs index 72e97f45..0ff3fcd5 100644 --- a/anoncreds/tests/anoncreds_demos.rs +++ b/anoncreds/tests/anoncreds_demos.rs @@ -23,12 +23,12 @@ use anoncreds::{ use serde_json::json; -use self::utils::anoncreds::{IssuerWallet, ProverWallet}; +use self::utils::anoncreds::ProverWallet; mod utils; -pub static SCHEMA_ID: &str = "mock:uri"; -pub static CRED_DEF_ID: &str = "mock:uri"; +pub static SCHEMA_ID: &str = "mock:uri:schema"; +pub static CRED_DEF_ID: &str = "mock:uri:1"; pub static ISSUER_ID: &str = "mock:issuer_id/path&q=bar"; pub const GVT_SCHEMA_NAME: &str = "gvt"; pub const GVT_SCHEMA_ATTRIBUTES: &[&str; 4] = &["name", "age", "sex", "height"]; @@ -38,9 +38,6 @@ pub static MAX_CRED_NUM: u32 = 10; #[test] fn anoncreds_works_for_single_issuer_single_prover() { - // Create Issuer pseudo wallet - let mut issuer_wallet = IssuerWallet::default(); - // Create Prover pseudo wallet and master secret let mut prover_wallet = ProverWallet::default(); @@ -54,7 +51,7 @@ fn anoncreds_works_for_single_issuer_single_prover() { .expect("Error creating gvt schema for issuer"); // Issuer creates Credential Definition - let cred_def_parts = issuer::create_credential_definition( + let (cred_def_pub, cred_def_priv, cred_def_correctness) = issuer::create_credential_definition( SCHEMA_ID, &gvt_schema, ISSUER_ID, @@ -65,23 +62,15 @@ fn anoncreds_works_for_single_issuer_single_prover() { }, ) .expect("Error creating gvt credential definition"); - issuer_wallet.cred_defs.push(cred_def_parts.into()); - - // Public part would be published to the ledger - let gvt_cred_def = &issuer_wallet.cred_defs[0].public; // Issuer creates a Credential Offer - let cred_offer = issuer::create_credential_offer( - SCHEMA_ID, - CRED_DEF_ID, - &issuer_wallet.cred_defs[0].key_proof, - ) - .expect("Error creating credential offer"); + let cred_offer = issuer::create_credential_offer(SCHEMA_ID, CRED_DEF_ID, &cred_def_correctness) + .expect("Error creating credential offer"); // Prover creates a Credential Request let (cred_request, cred_request_metadata) = prover::create_credential_request( None, - &*gvt_cred_def, + &cred_def_pub, &prover_wallet.master_secret, "default", &cred_offer, @@ -103,8 +92,8 @@ fn anoncreds_works_for_single_issuer_single_prover() { .add_raw("age", "28") .expect("Error encoding attribute"); let issue_cred = issuer::create_credential( - &*gvt_cred_def, - &issuer_wallet.cred_defs[0].private, + &cred_def_pub, + &cred_def_priv, &cred_offer, &cred_request, cred_values.into(), @@ -120,7 +109,7 @@ fn anoncreds_works_for_single_issuer_single_prover() { &mut recv_cred, &cred_request_metadata, &prover_wallet.master_secret, - &*gvt_cred_def, + &cred_def_pub, None, ) .expect("Error processing credential"); @@ -146,7 +135,8 @@ fn anoncreds_works_for_single_issuer_single_prover() { }, "requested_predicates":{ "predicate1_referent":{"name":"age","p_type":">=","p_value":18} - } + }, + "non_revoked": {"from": 10, "to": 200} })) .expect("Error creating proof request"); @@ -175,7 +165,7 @@ fn anoncreds_works_for_single_issuer_single_prover() { let mut cred_defs = HashMap::new(); let cred_def_id = CredentialDefinitionId::new_unchecked(CRED_DEF_ID); - cred_defs.insert(&cred_def_id, &*gvt_cred_def); + cred_defs.insert(&cred_def_id, &cred_def_pub); let presentation = prover::create_presentation( &pres_request, @@ -234,14 +224,11 @@ fn anoncreds_works_for_single_issuer_single_prover() { None, ) .expect("Error verifying presentation"); - assert!(valid); + assert!(valid.0); } #[test] fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { - // Create Issuer pseudo wallet - let mut issuer_wallet = IssuerWallet::default(); - // Create Prover pseudo wallet and master secret let mut prover_wallet = ProverWallet::default(); @@ -303,29 +290,14 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { ) .unwrap(); - issuer_wallet - .cred_defs - .push(utils::anoncreds::StoredCredDef { - public: cred_def_pub, - private: cred_def_priv, - key_proof: cred_def_correctness, - }); - - // Public part would be published to the ledger - let gvt_cred_def = &issuer_wallet.cred_defs[0].public; - // Issuer creates a Credential Offer - let cred_offer = issuer::create_credential_offer( - SCHEMA_ID, - CRED_DEF_ID, - &issuer_wallet.cred_defs[0].key_proof, - ) - .expect("Error creating credential offer"); + let cred_offer = issuer::create_credential_offer(SCHEMA_ID, CRED_DEF_ID, &cred_def_correctness) + .expect("Error creating credential offer"); // Prover creates a Credential Request let (cred_request, cred_request_metadata) = prover::create_credential_request( None, - &*gvt_cred_def, + &cred_def_pub, &prover_wallet.master_secret, "default", &cred_offer, @@ -355,8 +327,8 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { let tr = TailsFileReader::new_tails_reader(location.as_str()); let issue_cred = issuer::create_credential( - &*gvt_cred_def, - &issuer_wallet.cred_defs[0].private, + &cred_def_pub, + &cred_def_priv, &cred_offer, &cred_request, cred_values.into(), @@ -365,10 +337,6 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { Some(CredentialRevocationConfig { reg_def: &rev_reg_def_pub, reg_def_private: &rev_reg_def_priv, - registry: &<&RevocationStatusList as Into>>::into( - &revocation_status_list, - ) - .unwrap(), registry_idx: REV_IDX, tails_reader: tr, }), @@ -380,7 +348,7 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { Some(time_after_creating_cred), Some(BTreeSet::from([REV_IDX])), None, - Some(&rev_reg_def_pub), + &rev_reg_def_pub, &revocation_status_list, ) .unwrap(); @@ -391,7 +359,7 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { &mut recv_cred, &cred_request_metadata, &prover_wallet.master_secret, - &*gvt_cred_def, + &cred_def_pub, Some(&rev_reg_def_pub), ) .expect("Error processing credential"); @@ -443,7 +411,7 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { let mut cred_defs = HashMap::new(); let cred_def_id = CredentialDefinitionId::new_unchecked(CRED_DEF_ID); - cred_defs.insert(&cred_def_id, &*gvt_cred_def); + cred_defs.insert(&cred_def_id, &cred_def_pub); // Prover creates presentation let presentation = _create_presentation( @@ -472,7 +440,7 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { Some(&rev_reg_map), ) .expect("Error verifying presentation"); - assert!(valid); + assert!(valid.0); // ===================== Issuer revokes credential ================ let time_revoke_cred = time_after_creating_cred + 1; @@ -480,7 +448,7 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { Some(time_revoke_cred), None, Some(BTreeSet::from([REV_IDX])), - Some(&rev_reg_def_pub), + &rev_reg_def_pub, &issued_rev_status_list, ) .unwrap(); @@ -521,50 +489,7 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { Some(&rev_reg_map), ) .expect("Error verifying presentation"); - assert!(!valid); -} - -fn _create_presentation( - schemas: &HashMap<&SchemaId, &Schema>, - cred_defs: &HashMap<&CredentialDefinitionId, &CredentialDefinition>, - pres_request: &PresentationRequest, - prover_wallet: &ProverWallet, - rev_state_timestamp: Option, - rev_state: Option<&CredentialRevocationState>, -) -> Presentation { - let mut present = PresentCredentials::default(); - { - // Here we add credential with the timestamp of which the rev_state is updated to, - // also the rev_reg has to be provided for such a time. - // TODO: this timestamp is not verified by the `NonRevokedInterval`? - let mut cred1 = present.add_credential( - &prover_wallet.credentials[0], - rev_state_timestamp, - rev_state, - ); - cred1.add_requested_attribute("attr1_referent", true); - cred1.add_requested_attribute("attr2_referent", false); - cred1.add_requested_attribute("attr4_referent", true); - cred1.add_requested_predicate("predicate1_referent"); - } - - let mut self_attested = HashMap::new(); - let self_attested_phone = "8-800-300"; - self_attested.insert( - "attr3_referent".to_string(), - self_attested_phone.to_string(), - ); - - let presentation = prover::create_presentation( - pres_request, - present, - Some(self_attested), - &prover_wallet.master_secret, - schemas, - cred_defs, - ) - .expect("Error creating presentation"); - presentation + assert!(!valid.0); } /* @@ -3449,3 +3374,45 @@ fn anoncreds_works_for_restrictions_as_empty_array() { wallet::close_and_delete_wallet(prover_wallet_handle, &prover_wallet_config).unwrap(); } */ + +fn _create_presentation( + schemas: &HashMap<&SchemaId, &Schema>, + cred_defs: &HashMap<&CredentialDefinitionId, &CredentialDefinition>, + pres_request: &PresentationRequest, + prover_wallet: &ProverWallet, + rev_state_timestamp: Option, + rev_state: Option<&CredentialRevocationState>, +) -> Presentation { + let mut present = PresentCredentials::default(); + { + // Here we add credential with the timestamp of which the rev_state is updated to, + // also the rev_reg has to be provided for such a time. + let mut cred1 = present.add_credential( + &prover_wallet.credentials[0], + rev_state_timestamp, + rev_state, + ); + cred1.add_requested_attribute("attr1_referent", true); + cred1.add_requested_attribute("attr2_referent", false); + cred1.add_requested_attribute("attr4_referent", true); + cred1.add_requested_predicate("predicate1_referent"); + } + + let mut self_attested = HashMap::new(); + let self_attested_phone = "8-800-300"; + self_attested.insert( + "attr3_referent".to_string(), + self_attested_phone.to_string(), + ); + + let presentation = prover::create_presentation( + pres_request, + present, + Some(self_attested), + &prover_wallet.master_secret, + schemas, + cred_defs, + ) + .expect("Error creating presentation"); + presentation +} diff --git a/anoncreds/tests/multiple-credentials.rs b/anoncreds/tests/multiple-credentials.rs new file mode 100644 index 00000000..9ecbab67 --- /dev/null +++ b/anoncreds/tests/multiple-credentials.rs @@ -0,0 +1,202 @@ +use std::collections::HashMap; + +use anoncreds::{ + data_types::anoncreds::{ + pres_request::{NonRevocedInterval, PresentationRequestPayload}, + schema::SchemaId, + }, + types::PresentationRequest, + verifier, +}; + +use serde_json::json; + +mod utils; + +pub static ISSUER_ID: &str = "mock:issuer_id/path&q=bar"; +pub static PROVER_ID: &str = "mock:prover_id/path&q=bar"; +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"; +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"}"#; +const SCHEMA_2: &str = r#"{"name":"hogwarts","version":"1.0","attrNames":["wand","house","year"],"issuerId":"mock:issuer_id/path&q=hogwarts"}"#; +static CRED_DEF_ID_1: &'static str = "mock:uri:1"; +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"); + + let json = json!({ + "nonce": nonce_1, + "name":"pres_req_1", + "version":"0.1", + "requested_attributes":{ + "attr1_referent":{ + "name":"name", + "issuer_id": ISSUER_ID + }, + "attr2_referent":{ + "name":"sex" + }, + "attr3_referent":{"name":"phone"}, + "attr4_referent":{ + "names": ["height"], + }, + "attr5_referent": {"names": ["wand", "house", "year"]}, + + }, + "requested_predicates":{ + "predicate1_referent":{"name":"age","p_type":">=","p_value":18} + }, + }); + + let mut p1: PresentationRequestPayload = serde_json::from_value(json.clone()).unwrap(); + let mut p2: PresentationRequestPayload = serde_json::from_value(json).unwrap(); + + // 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"); + } + + vec![ + PresentationRequest::PresentationRequestV1(p1), + PresentationRequest::PresentationRequestV1(p2), + ] +} + +#[test] +fn anoncreds_with_multiple_credentials_per_request() { + let mut mock = utils::Mock::new(&[ISSUER_ID], &[PROVER_ID], TF_PATH, MAX_CRED_NUM); + + // These are what the issuer knows + let issuer1_creds: utils::IsserValues = HashMap::from([ + ( + CRED_DEF_ID_1, + ( + SCHEMA_ID_1, + HashMap::from([ + ("sex", "male"), + ("name", "Alex"), + ("height", "175"), + ("age", "28"), + ]), + true, + REV_REG_ID_1, + REV_IDX_1, + ), + ), + ( + CRED_DEF_ID_2, + ( + SCHEMA_ID_2, + HashMap::from([ + ("wand", "dragon-heart-string"), + ("house", "Hufflepuff"), + ("year", "1990"), + ]), + false, + "", + 0, + ), + ), + ]); + + let schemas = HashMap::from([ + ( + SchemaId::new_unchecked(SCHEMA_ID_1), + serde_json::from_str(SCHEMA_1).unwrap(), + ), + ( + SchemaId::new_unchecked(SCHEMA_ID_2), + serde_json::from_str(SCHEMA_2).unwrap(), + ), + ]); + + 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; + 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(); + + // 1: Issuer setup (credate cred defs, rev defs(optional), cred_offers) + mock.issuer_setup( + ISSUER_ID, + PROVER_ID, + &issuer1_creds, + time_initial_rev_reg, + issuance_by_default, + ); + + // 2: prover requests and gets credential stored in their wallets + mock.issuer_create_credential_and_store_in_prover_wallet( + ISSUER_ID, + PROVER_ID, + &issuer1_creds, + time_initial_rev_reg, + time_after_credential, + ); + + // 3. Prover creates revocation states for all credentials with ledger values + // rev_states are stored in the prover wallet + mock.prover_creates_revocation_states(PROVER_ID, time_after_credential); + + // 4. Prover create presentations + let prover_values: utils::ProverValues = HashMap::from([ + ( + CRED_DEF_ID_1, + ( + vec!["attr1_referent", "attr2_referent", "attr4_referent"], + vec!["predicate1_referent"], + ), + ), + (CRED_DEF_ID_2, (vec!["attr5_referent"], vec![])), + ]); + let self_attested = HashMap::from([("attr3_referent".to_string(), "8-800-300".to_string())]); + + let mut presentations = vec![]; + for req in &reqs { + let p = mock.prover_creates_presentation( + PROVER_ID, + prover_values.clone(), + self_attested.clone(), + req, + ); + presentations.push(p) + } + // 5. Verifier verifies one presentation per request + let results = mock.verifer_verifies_presentations_for_requests(presentations, &reqs); + + assert!(results[0].0); + assert!(results[0].1.timestamps_out_of_range.is_some()); + assert!(results[1].0); + assert!(results[1].1.timestamps_out_of_range.is_some()); +} diff --git a/anoncreds/tests/utils/anoncreds.rs b/anoncreds/tests/utils/anoncreds.rs index 4bcb114e..d9dc1e67 100644 --- a/anoncreds/tests/utils/anoncreds.rs +++ b/anoncreds/tests/utils/anoncreds.rs @@ -1,61 +1,76 @@ -use anoncreds::types::{CredentialDefinitionPrivate, CredentialKeyCorrectnessProof}; - -use anoncreds::data_types::anoncreds::cred_def::CredentialDefinition; +use anoncreds::data_types::anoncreds::cred_def::{CredentialDefinition, CredentialDefinitionId}; use anoncreds::data_types::anoncreds::credential::Credential; use anoncreds::data_types::anoncreds::master_secret::MasterSecret; +use anoncreds::data_types::anoncreds::rev_reg::RevocationRegistryId; +use anoncreds::data_types::anoncreds::rev_reg_def::{ + RevocationRegistryDefinition, RevocationRegistryDefinitionId, + RevocationRegistryDefinitionPrivate, +}; +use anoncreds::data_types::anoncreds::schema::{Schema, SchemaId}; +use anoncreds::types::{ + CredentialDefinitionPrivate, CredentialKeyCorrectnessProof, CredentialOffer, CredentialRequest, + CredentialRequestMetadata, CredentialRevocationState, RevocationStatusList, +}; +use std::collections::HashMap; +#[derive(Debug)] pub struct StoredCredDef { - pub public: CredentialDefinition, pub private: CredentialDefinitionPrivate, pub key_proof: CredentialKeyCorrectnessProof, } -impl - From<( - CredentialDefinition, - CredentialDefinitionPrivate, - CredentialKeyCorrectnessProof, - )> for StoredCredDef -{ - fn from( - parts: ( - CredentialDefinition, - CredentialDefinitionPrivate, - CredentialKeyCorrectnessProof, - ), - ) -> Self { - let (public, private, key_proof) = parts; - Self { - public, - private, - key_proof, - } - } +#[derive(Debug)] +pub struct StoredRevDef { + pub public: RevocationRegistryDefinition, + pub private: RevocationRegistryDefinitionPrivate, +} + +#[derive(Debug, Default)] +pub struct Ledger<'a> { + // CredentialDefinition does not impl Clone + pub cred_defs: HashMap, + pub schemas: HashMap, + pub rev_reg_defs: HashMap, + pub revcation_list: HashMap<&'a str, HashMap>, } // A struct for keeping all issuer-related objects together -pub struct IssuerWallet { - pub cred_defs: Vec, +#[derive(Debug)] +pub struct IssuerWallet<'a> { + // cred_def_id: StoredRevDef + pub cred_defs: HashMap<&'a str, StoredCredDef>, + // revocation_reg_id: StoredRevDef + pub rev_defs: HashMap<&'a str, StoredRevDef>, } -impl Default for IssuerWallet { +impl<'a> Default for IssuerWallet<'a> { fn default() -> Self { - Self { cred_defs: vec![] } + Self { + cred_defs: HashMap::new(), + rev_defs: HashMap::new(), + } } } // A struct for keeping all issuer-related objects together -pub struct ProverWallet { +#[derive(Debug)] +pub struct ProverWallet<'a> { pub credentials: Vec, + pub rev_states: HashMap, Option)>, pub master_secret: MasterSecret, + pub cred_offers: HashMap<&'a str, CredentialOffer>, + pub cred_reqs: Vec<(CredentialRequest, CredentialRequestMetadata)>, } -impl Default for ProverWallet { +impl<'a> Default for ProverWallet<'a> { fn default() -> Self { let master_secret = MasterSecret::new().expect("Error creating prover master secret"); Self { credentials: vec![], + rev_states: HashMap::new(), master_secret, + cred_offers: HashMap::new(), + cred_reqs: vec![], } } } diff --git a/anoncreds/tests/utils/mock.rs b/anoncreds/tests/utils/mock.rs new file mode 100644 index 00000000..2767b369 --- /dev/null +++ b/anoncreds/tests/utils/mock.rs @@ -0,0 +1,451 @@ +use super::anoncreds::{IssuerWallet, Ledger, ProverWallet, StoredCredDef, StoredRevDef}; +use std::{ + collections::{BTreeSet, HashMap}, + fs::create_dir, +}; + +use anoncreds::{ + data_types::anoncreds::{ + cred_def::{CredentialDefinition, CredentialDefinitionId}, + cred_offer::CredentialOffer, + credential::Credential, + presentation::Presentation, + rev_reg::{NonRevProofWarnings, RevocationRegistry, RevocationRegistryId}, + rev_reg_def::RevocationRegistryDefinitionId, + schema::{Schema, SchemaId}, + }, + issuer, prover, + tails::{TailsFileReader, TailsFileWriter}, + types::{ + CredentialDefinitionConfig, CredentialRequest, CredentialRevocationConfig, + CredentialRevocationState, MakeCredentialValues, PresentCredentials, PresentationRequest, + RegistryType, RevocationStatusList, SignatureType, + }, + verifier, +}; +use bitvec::bitvec; + +// {cred_def_id: { +// schema_id, credential_values, support_revocation, rev_reg_id, rev_idx +// }} +pub type IsserValues<'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>)>; + +#[derive(Debug)] +pub struct Mock<'a> { + pub issuer_wallets: HashMap<&'a str, IssuerWallet<'a>>, + pub prover_wallets: HashMap<&'a str, ProverWallet<'a>>, + pub ledger: Ledger<'a>, + pub tails_path: &'a str, + pub max_cred_num: u32, +} + +impl<'a> Mock<'a> { + pub fn new( + issuer_ids: &[&'a str], + prover_ids: &[&'a str], + tails_path: &'a str, + max_cred_num: u32, + ) -> Self { + let mut iws = HashMap::new(); + let mut pws = HashMap::new(); + for i in issuer_ids { + iws.insert(*i, IssuerWallet::default()); + } + for i in prover_ids { + pws.insert(*i, ProverWallet::default()); + } + + Self { + issuer_wallets: iws, + prover_wallets: pws, + ledger: Ledger::default(), + tails_path, + max_cred_num, + } + } + + pub fn verifer_verifies_presentations_for_requests( + &self, + presentations: Vec, + reqs: &[PresentationRequest], + ) -> Vec<(bool, NonRevProofWarnings)> { + let mut results = vec![]; + let schemas: HashMap<&SchemaId, &Schema> = HashMap::from_iter(self.ledger.schemas.iter()); + let cred_defs: HashMap<&CredentialDefinitionId, &CredentialDefinition> = + HashMap::from_iter(self.ledger.cred_defs.iter()); + let rev_reg_map: HashMap> = + HashMap::from_iter(self.ledger.revcation_list.iter().map(|(&k, v)| { + ( + RevocationRegistryId::new_unchecked(k), + HashMap::from_iter(v.iter().map(|(&time, v)| { + let rev_reg: Option = v.into(); + (time, rev_reg.unwrap()) + })), + ) + })); + let rev_reg_def_map = HashMap::from_iter(self.ledger.rev_reg_defs.iter()); + + for (i, presentation) in presentations.iter().enumerate() { + let res = verifier::verify_presentation( + &presentation, + &reqs[i], + &schemas, + &cred_defs, + Some(&rev_reg_def_map), + Some(&HashMap::from_iter(rev_reg_map.iter().map(|(k, v)| { + ( + k.clone(), + HashMap::from_iter(v.iter().map(|(k, v)| (*k, v))), + ) + }))), + ) + .expect("Error verifying presentation"); + results.push(res); + } + results + } + + // This creates cred defs based on the schemas, assumes 1 schema 1 cred def + // issuer wallet holds all data relating to cred def and rev def + // prover wallet contains the cred offers from the credentials + // ledger holds the rev reg def / rev reg info + pub fn issuer_setup( + &mut self, + issuer_id: &'static str, + prover_id: &'static str, + values: &'a IsserValues, + time_now: u64, + issuance_by_default: bool, + ) { + for (cred_def_id, (schema_id, _, support_revocation, rev_reg_id, _)) in values.iter() { + let (cred_def_pub, cred_def_priv, cred_def_correctness) = + issuer::create_credential_definition( + schema_id.to_string(), + &self.ledger.schemas[&SchemaId::new_unchecked(*schema_id)], + issuer_id, + "tag", + SignatureType::CL, + CredentialDefinitionConfig { + support_revocation: *support_revocation, + }, + ) + .expect("Error creating gvt credential definition"); + + if *support_revocation { + // This will create a tails file locally in the .tmp dir + create_dir(self.tails_path) + .or_else(|e| -> Result<(), std::io::Error> { + println!( + "Tail file path creation error but test can still proceed {}", + e + ); + Ok(()) + }) + .unwrap(); + + let mut tf = TailsFileWriter::new(Some(self.tails_path.to_owned())); + let (rev_reg_def_pub, rev_reg_def_priv) = issuer::create_revocation_registry_def( + &cred_def_pub, + *cred_def_id, + issuer_id, + "some_tag", + RegistryType::CL_ACCUM, + self.max_cred_num, + &mut tf, + ) + .unwrap(); + + let iw_mut = self.issuer_wallets.get_mut(issuer_id).unwrap(); + iw_mut.rev_defs.insert( + &rev_reg_id, + StoredRevDef { + public: rev_reg_def_pub.clone(), + private: rev_reg_def_priv, + }, + ); + + let revocation_status_list = issuer::create_revocation_status_list( + *rev_reg_id, + &rev_reg_def_pub, + Some(time_now), + issuance_by_default, + ) + .unwrap(); + + self.ledger.revcation_list.insert( + rev_reg_id, + HashMap::from([(time_now, revocation_status_list)]), + ); + + self.ledger.rev_reg_defs.insert( + RevocationRegistryDefinitionId::new_unchecked(*rev_reg_id), + rev_reg_def_pub, + ); + } + + // Issuer creates a Credential Offer + let cred_offer = issuer::create_credential_offer( + schema_id.to_string(), + *cred_def_id, + &cred_def_correctness, + ) + .expect("Error creating credential offer"); + + // Update wallets and ledger + self.prover_wallets + .get_mut(prover_id) + .unwrap() + .cred_offers + .insert(*cred_def_id, cred_offer); + + self.issuer_wallets + .get_mut(issuer_id) + .unwrap() + .cred_defs + .insert( + *cred_def_id, + StoredCredDef { + private: cred_def_priv, + key_proof: cred_def_correctness, + }, + ); + self.ledger.cred_defs.insert( + CredentialDefinitionId::new_unchecked(*cred_def_id), + cred_def_pub, + ); + } + } + + fn issuer_create_credential( + &self, + issuer_wallet: &IssuerWallet, + ledger: &Ledger, + cred_request: &CredentialRequest, + offer: &CredentialOffer, + rev_reg_id: &str, + cred_def_id: &str, + values: &HashMap<&str, &str>, + prev_rev_reg_time: u64, + rev_idx: u32, + ) -> Credential { + let schema = self.ledger.schemas.get(&offer.schema_id).unwrap(); + let revocation_list = self + .ledger + .revcation_list + .get(rev_reg_id) + .map(|h| h.get(&prev_rev_reg_time)) + .flatten(); + let mut cred_values = MakeCredentialValues::default(); + let names: Vec = schema.attr_names.clone().0.into_iter().collect(); + for (i, v) in names.iter().enumerate() { + if let Some(value) = values.get(&v.as_str()) { + cred_values + .add_raw(names[i].clone(), value.to_string()) + .expect("Error encoding attribute"); + } else { + panic!( + "No credential value given for attribute name: {} in {:?}", + v, values + ); + } + } + + let (rev_config, rev_id) = match issuer_wallet.rev_defs.get(rev_reg_id) { + Some(stored_rev_def) => { + let tr = TailsFileReader::new_tails_reader( + stored_rev_def.public.value.tails_location.as_str(), + ); + ( + Some(CredentialRevocationConfig { + reg_def: &stored_rev_def.public, + reg_def_private: &stored_rev_def.private, + registry_idx: rev_idx, + tails_reader: tr, + }), + Some(RevocationRegistryId::new_unchecked(rev_reg_id)), + ) + } + None => (None, None), + }; + + let issue_cred = issuer::create_credential( + &ledger + .cred_defs + .get(&CredentialDefinitionId::new_unchecked(cred_def_id)) + .unwrap(), + &issuer_wallet.cred_defs[cred_def_id].private, + offer, + cred_request, + cred_values.into(), + rev_id, + revocation_list, + rev_config, + ) + .expect("Error creating credential"); + + issue_cred + } + + // prover requests and gets credential stored in their wallets + // This updates ledger on revocation reg also + pub fn issuer_create_credential_and_store_in_prover_wallet( + &mut self, + issuer_id: &'static str, + prover_id: &'static str, + values: &'a IsserValues, + time_prev_rev_reg: u64, + time_new_rev_reg: u64, + ) { + for (cred_def_id, (_, cred_values, _, rev_reg_id, rev_idx)) in values.iter() { + let offer = &self.prover_wallets[prover_id].cred_offers[cred_def_id]; + let cred_def = self + .ledger + .cred_defs + .get(&CredentialDefinitionId::new_unchecked(cred_def_id.clone())) + .unwrap(); + // Prover creates a Credential Request + let cred_req_data = prover::create_credential_request( + Some(prover_id), + &cred_def, + &self.prover_wallets[prover_id].master_secret, + "default", + &offer, + ) + .expect("Error creating credential request"); + + // Issuer creates a credential + let mut recv_cred = self.issuer_create_credential( + &self.issuer_wallets[issuer_id], + &self.ledger, + &cred_req_data.0, + &offer, + *rev_reg_id, + cred_def_id, + cred_values, + time_prev_rev_reg, + *rev_idx, + ); + + let rev_def = self.issuer_wallets[issuer_id] + .rev_defs + .get(*rev_reg_id) + .map(|e| &e.public); + + // prover processes it + prover::process_credential( + &mut recv_cred, + &cred_req_data.1, + &self.prover_wallets[prover_id].master_secret, + &cred_def, + rev_def, + ) + .expect("Error processing credential"); + + // Update prover wallets and ledger with new revocation status list + let pw = self.prover_wallets.get_mut(prover_id).unwrap(); + pw.cred_reqs.push(cred_req_data); + pw.credentials.push(recv_cred); + + if let Some(rev_def) = rev_def { + let list = self + .ledger + .revcation_list + .get(*rev_reg_id) + .unwrap() + .get(&time_prev_rev_reg) + .unwrap(); + + let updated_list = issuer::update_revocation_status_list( + Some(time_new_rev_reg), + Some(BTreeSet::from([*rev_idx])), + None, + rev_def, + list, + ) + .unwrap(); + + let map = self.ledger.revcation_list.get_mut(&*rev_reg_id).unwrap(); + map.insert(time_new_rev_reg, updated_list); + } + } + } + + pub fn prover_creates_revocation_states( + &mut self, + prover_id: &'static str, + time_to_update_to: u64, + ) { + let mut rev_states = self.prover_wallets[prover_id].rev_states.clone(); + for cred in &self.prover_wallets[prover_id].credentials { + if let Some(id) = &cred.rev_reg_id { + let rev_status_list = self + .ledger + .revcation_list + .get(id.to_string().as_str()) + .unwrap() + .get(&time_to_update_to) + .unwrap(); + + let state = prover::create_or_update_revocation_state_with_witness( + cred.witness.as_ref().unwrap().clone(), + rev_status_list, + time_to_update_to, + ) + .unwrap(); + + // this overwrites the rev_state as there should only just be one that works + rev_states.insert(id.clone(), (Some(state), Some(time_to_update_to))); + }; + } + self.prover_wallets.get_mut(prover_id).unwrap().rev_states = rev_states; + } + + pub fn prover_creates_presentation( + &self, + prover_id: &'static str, + prover_values: ProverValues, + self_attested: HashMap, + req: &PresentationRequest, + ) -> Presentation { + let schemas: HashMap<&SchemaId, &Schema> = HashMap::from_iter(self.ledger.schemas.iter()); + let cred_defs: HashMap<&CredentialDefinitionId, &CredentialDefinition> = + HashMap::from_iter(self.ledger.cred_defs.iter()); + + let mut present = PresentCredentials::default(); + for cred in self.prover_wallets[prover_id].credentials.iter() { + let values = prover_values + .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 mut cred1 = present.add_credential(cred, *timestamp, rev_state.as_ref()); + for a in &values.0 { + cred1.add_requested_attribute(a.clone(), true); + } + for p in &values.1 { + cred1.add_requested_predicate(p.clone()); + } + } + } + + let presentation = prover::create_presentation( + req, + present, + Some(self_attested.clone()), + &self.prover_wallets[prover_id].master_secret, + &schemas, + &cred_defs, + ) + .expect("Error creating presentation"); + + presentation + } +} diff --git a/anoncreds/tests/utils/mod.rs b/anoncreds/tests/utils/mod.rs index 58f285b8..393bb2c9 100644 --- a/anoncreds/tests/utils/mod.rs +++ b/anoncreds/tests/utils/mod.rs @@ -1 +1,4 @@ pub mod anoncreds; +pub mod mock; + +pub use mock::{IsserValues, Mock, ProverValues};