diff --git a/anoncreds/src/services/verifier.rs b/anoncreds/src/services/verifier.rs index 47189ea9..960351cd 100644 --- a/anoncreds/src/services/verifier.rs +++ b/anoncreds/src/services/verifier.rs @@ -76,21 +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, - &mut nrp_warnings, - )?; - 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 @@ -105,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") })?; @@ -155,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)?; @@ -191,7 +191,7 @@ 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); + trace!("non_rev_proof_warnings <<< {:?}", nrp_warnings); Ok((valid, nrp_warnings)) } @@ -307,54 +307,41 @@ 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<()> { let mut warnings = vec![]; - pres_req - .requested_attributes + 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( &mut warnings, - received_revealed_attrs, - referent, + identifier, + &name, &pres_req.non_revoked, &info.non_revoked, ) - .or_else(|_| { - validate_timestamp( - &mut warnings, - 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!("Referent validation failed: {}", referent)) - }) }) .collect::>>()?; - pres_req - .requested_predicates + predicates_for_credential .iter() - .map(|(referent, info)| { + .map(|info| { validate_timestamp( &mut warnings, - received_predicates, - referent, + identifier, + &info.name, &pres_req.non_revoked, &info.non_revoked, ) @@ -364,7 +351,6 @@ fn compare_timestamps_from_proof_and_request( if !warnings.is_empty() { nrp_warnings.timestamps_out_of_range = Some(warnings); } - Ok(()) } @@ -384,17 +370,17 @@ fn compare_timestamps_from_proof_and_request( // a wanring is provided for the verifier to decide if they will reject the proof or not fn validate_timestamp( warnings: &mut Vec, - received_: &HashMap, - referent: &str, + identifier: &Identifier, + name: &str, global_interval: &Option, local_interval: &Option, ) -> Result<()> { 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(Some(ts)) = received_.get(referent).map(|attr| attr.timestamp) { + 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(referent.to_string()); + warnings.push(name.to_string()); } Ok(()) } else { @@ -1261,49 +1247,38 @@ 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_within_interval".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_before_from".to_string(), - Identifier { + "before_from" => Identifier { timestamp: Some(1), schema_id: SchemaId::default(), cred_def_id: CredentialDefinitionId::default(), rev_reg_id: Some(RevocationRegistryId::default()), }, - ); - res.insert( - "referent_after_to".to_string(), - Identifier { + "after_to" => Identifier { timestamp: Some(4999), schema_id: SchemaId::default(), cred_def_id: CredentialDefinitionId::default(), rev_reg_id: Some(RevocationRegistryId::default()), }, - ); - res.insert( - "referent_none".to_string(), - Identifier { + "none" => Identifier { timestamp: None, schema_id: SchemaId::default(), cred_def_id: CredentialDefinitionId::default(), rev_reg_id: Some(RevocationRegistryId::default()), }, - ); - res - } - - fn _interval(from: Option, to: Option) -> NonRevocedInterval { - NonRevocedInterval { from, to } + _ => panic!("no such identifier"), + } } #[test] @@ -1311,8 +1286,8 @@ mod tests { let mut warnings: Vec = vec![]; validate_timestamp( &mut warnings, - &_received(), - "referent_within_interval", + &_get_identifier("within_interval"), + "within_interval", &None, &None, ) @@ -1325,8 +1300,8 @@ mod tests { let mut warnings: Vec = vec![]; validate_timestamp( &mut warnings, - &_received(), - "referent_within_interval", + &_get_identifier("within_interval"), + "within_interval", &Some(_interval(Some(FROM), Some(TO))), &None, ) @@ -1339,8 +1314,8 @@ mod tests { let mut warnings: Vec = vec![]; validate_timestamp( &mut warnings, - &_received(), - "referent_within_interval", + &_get_identifier("within_interval"), + "within_interval", &None, &Some(_interval(Some(FROM), Some(TO))), ) @@ -1353,13 +1328,13 @@ mod tests { let mut warnings: Vec = vec![]; validate_timestamp( &mut warnings, - &_received(), - "referent_before_from", + &_get_identifier("before_from"), + "before_from", &Some(_interval(Some(FROM), Some(TO))), &None, ) .unwrap(); - assert_eq!(warnings.pop().unwrap(), "referent_before_from"); + assert_eq!(warnings.pop().unwrap(), "before_from"); } #[test] @@ -1367,13 +1342,13 @@ mod tests { let mut warnings: Vec = vec![]; validate_timestamp( &mut warnings, - &_received(), - "referent_after_to", + &_get_identifier("after_to"), + "after_to", &Some(_interval(Some(FROM), Some(TO))), &None, ) .unwrap(); - assert_eq!(warnings.pop().unwrap(), "referent_after_to"); + assert_eq!(warnings.pop().unwrap(), "after_to"); } #[test] @@ -1381,21 +1356,21 @@ mod tests { let mut warnings: Vec = vec![]; validate_timestamp( &mut warnings, - &_received(), - "referent_within_interval", + &_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(), "referent_within_interval"); + assert_eq!(warnings.pop().unwrap(), "within_interval"); } #[test] fn validate_timestamp_not_work_without_timestamp_for_global_interval() { validate_timestamp( &mut vec![], - &_received(), - "referent_none", + &_get_identifier("none"), + "none", &Some(_interval(Some(FROM), Some(TO))), &None, ) @@ -1406,20 +1381,8 @@ mod tests { fn validate_timestamp_fails_without_timestamp_for_local_interval() { validate_timestamp( &mut vec![], - &_received(), - "referent_none", - &None, - &Some(_interval(Some(FROM), Some(TO))), - ) - .unwrap_err(); - } - - #[test] - fn validate_timestamp_fails_without_refernt() { - validate_timestamp( - &mut vec![], - &_received(), - "referent_not_exist", + &_get_identifier("none"), + "none", &None, &Some(_interval(Some(FROM), Some(TO))), ) diff --git a/anoncreds/tests/anoncreds_demos.rs b/anoncreds/tests/anoncreds_demos.rs index 0e65d30c..1f1accc9 100644 --- a/anoncreds/tests/anoncreds_demos.rs +++ b/anoncreds/tests/anoncreds_demos.rs @@ -28,14 +28,15 @@ use self::utils::anoncreds::{IssuerWallet, 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"]; pub static REV_REG_ID: &str = "mock:uri:revregid"; pub static REV_IDX: u32 = 89; pub static MAX_CRED_NUM: u32 = 150; +pub static TF_PATH: &str = "../.tmp"; #[test] fn anoncreds_works_for_single_issuer_single_prover() { @@ -55,7 +56,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 (public, private, key_proof) = issuer::create_credential_definition( SCHEMA_ID, &gvt_schema, ISSUER_ID, @@ -66,16 +67,19 @@ fn anoncreds_works_for_single_issuer_single_prover() { }, ) .expect("Error creating gvt credential definition"); - issuer_wallet.cred_defs.push(cred_def_parts.into()); + issuer_wallet.cred_defs.insert( + CRED_DEF_ID, + utils::anoncreds::StoredCredDef { private, key_proof }, + ); // Public part would be published to the ledger - let gvt_cred_def = &issuer_wallet.cred_defs[0].public; + let gvt_cred_def = &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, + &issuer_wallet.cred_defs[&CRED_DEF_ID].key_proof, ) .expect("Error creating credential offer"); @@ -105,7 +109,7 @@ fn anoncreds_works_for_single_issuer_single_prover() { .expect("Error encoding attribute"); let (issue_cred, _, _) = issuer::create_credential( &*gvt_cred_def, - &issuer_wallet.cred_defs[0].private, + &issuer_wallet.cred_defs[&CRED_DEF_ID].private, &cred_offer, &cred_request, cred_values.into(), @@ -147,7 +151,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"); @@ -240,7 +245,6 @@ fn anoncreds_works_for_single_issuer_single_prover() { #[test] fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { - env_logger::init(); // Create Issuer pseudo wallet let mut issuer_wallet = IssuerWallet::default(); @@ -270,8 +274,7 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { .expect("Error creating gvt credential definition"); // This will create a tails file locally in the .tmp dir - let tf_path = "../.tmp"; - create_dir(tf_path) + create_dir(TF_PATH) .or_else(|e| -> Result<(), std::io::Error> { println!( "Tail file path creation error but test can still proceed {}", @@ -281,7 +284,7 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { }) .unwrap(); - let mut tf = TailsFileWriter::new(Some(tf_path.to_owned())); + let mut tf = TailsFileWriter::new(Some(TF_PATH.to_owned())); let (rev_reg_def_pub, rev_reg_def_priv, rev_reg, _) = issuer::create_revocation_registry( &cred_def_pub, CRED_DEF_ID, @@ -293,29 +296,27 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { ) .unwrap(); - issuer_wallet - .cred_defs - .push(utils::anoncreds::StoredCredDef { - public: cred_def_pub, + let cred_def_id = CredentialDefinitionId::new_unchecked(CRED_DEF_ID); + issuer_wallet.cred_defs.insert( + CRED_DEF_ID, + utils::anoncreds::StoredCredDef { 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, + &issuer_wallet.cred_defs[&CRED_DEF_ID].key_proof, ) .expect("Error creating credential offer"); // Prover creates a Credential Request let (cred_request, cred_request_metadata) = prover::create_credential_request( - None, - &*gvt_cred_def, + &prover_wallet.did, + &cred_def_pub, &prover_wallet.master_secret, "default", &cred_offer, @@ -340,7 +341,7 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { let rev_reg_id = RevocationRegistryId::new_unchecked(REV_REG_ID); // Get the location of the tails_file so it can be read - let location = rev_reg_def_pub.clone().value.tails_location; + let location = rev_reg_def_pub.value.clone().tails_location; let tr = TailsFileReader::new_tails_reader(location.as_str()); @@ -353,8 +354,8 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { // TODO: Here Delta is not needed but is it used elsewhere? let (issue_cred, cred_rev_reg, _) = issuer::create_credential( - &*gvt_cred_def, - &issuer_wallet.cred_defs[0].private, + &cred_def_pub, + &issuer_wallet.cred_defs[CRED_DEF_ID].private, &cred_offer, &cred_request, cred_values.into(), @@ -378,7 +379,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"); @@ -434,8 +435,7 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { schemas.insert(&schema_id, &gvt_schema); 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( @@ -529,49 +529,6 @@ fn anoncreds_with_revocation_works_for_single_issuer_single_prover() { 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 -} - /* #[test] fn anoncreds_works_for_multiple_issuer_single_prover() { @@ -3454,3 +3411,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..8e6f8370 --- /dev/null +++ b/anoncreds/tests/multiple-credentials.rs @@ -0,0 +1,193 @@ +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: 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"; + +// 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, + ), + ), + ( + 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; + + // 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); + + // 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 + let rev_states = 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_presentations( + PROVER_ID, + rev_states.clone(), + 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..1ac1d581 100644 --- a/anoncreds/tests/utils/anoncreds.rs +++ b/anoncreds/tests/utils/anoncreds.rs @@ -1,61 +1,79 @@ -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::{RevocationRegistry, 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, +}; +use indy_utils::did::DidValue; +use std::collections::HashMap; + +pub static ISSUER_ID: &str = "mock:issuer_id/path&q=bar"; +pub const PROVER_DID: &str = "VsKV7grR1BUE29mG2Fm2kX"; +#[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 { + // CredentialDefinition does not impl Clone + pub cred_defs: HashMap, + pub schemas: HashMap, + pub rev_reg_defs: HashMap, + pub rev_regs: HashMap>, } // A struct for keeping all issuer-related objects together -pub struct IssuerWallet { - pub cred_defs: Vec, +#[derive(Debug)] +pub struct IssuerWallet<'a> { + pub id: &'a str, + pub cred_defs: HashMap<&'a str, StoredCredDef>, + pub rev_defs: HashMap<&'a str, StoredRevDef>, + pub rev_regs: HashMap<(&'a str, u64), RevocationRegistry>, } -impl Default for IssuerWallet { +impl<'a> Default for IssuerWallet<'a> { fn default() -> Self { - Self { cred_defs: vec![] } + Self { + id: ISSUER_ID, + cred_defs: HashMap::new(), + rev_defs: HashMap::new(), + rev_regs: HashMap::new(), + } } } // A struct for keeping all issuer-related objects together -pub struct ProverWallet { +pub struct ProverWallet<'a> { pub credentials: Vec, 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![], 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..8bd09629 --- /dev/null +++ b/anoncreds/tests/utils/mock.rs @@ -0,0 +1,424 @@ +use super::anoncreds::{IssuerWallet, Ledger, ProverWallet, StoredCredDef, StoredRevDef}; +use std::{ + collections::{HashMap, HashSet}, + 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, SignatureType, + }, + verifier, +}; + +// {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, + 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()); + + for (i, presentation) in presentations.iter().enumerate() { + let (valid, nrp) = verifier::verify_presentation( + &presentation, + &reqs[i], + &schemas, + &cred_defs, + Some(&HashMap::from_iter(self.ledger.rev_reg_defs.iter())), + Some(&HashMap::from_iter(self.ledger.rev_regs.iter().map( + |(k, v)| { + ( + k.clone(), + HashMap::from_iter(v.iter().map(|(k, v)| (*k, v))), + ) + }, + ))), + ) + .expect("Error verifying presentation"); + results.push((valid, nrp)); + } + 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, + ) { + 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)], + self.issuer_wallets[issuer_id].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, rev_reg, _) = + issuer::create_revocation_registry( + &cred_def_pub, + *cred_def_id, + self.issuer_wallets[issuer_id].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, + }, + ); + + iw_mut + .rev_regs + .insert((&rev_reg_id, time_now), rev_reg.clone()); + + self.ledger.rev_reg_defs.insert( + RevocationRegistryDefinitionId::new_unchecked(*rev_reg_id), + rev_reg_def_pub, + ); + self.ledger.rev_regs.insert( + RevocationRegistryId::new_unchecked(*rev_reg_id), + HashMap::from([(time_now, rev_reg)]), + ); + } + + // 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, + cred_offer: &CredentialOffer, + rev_reg_id: &str, + cred_def_id: &str, + schema: &Schema, + values: &HashMap<&str, &str>, + prev_rev_reg_time: u64, + rev_idx: u32, + ) -> (Credential, Option) { + 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 registry_used = HashSet::from([rev_idx]); + 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: &issuer_wallet.rev_regs[&(rev_reg_id, prev_rev_reg_time)], + // The Prover's index in the revocation list is REV_IDX + registry_idx: rev_idx, + registry_used: ®istry_used, + tails_reader: tr, + }), + Some(RevocationRegistryId::new_unchecked(rev_reg_id)), + ) + } + None => (None, None), + }; + + let (issue_cred, cred_rev_reg, _) = issuer::create_credential( + &ledger + .cred_defs + .get(&CredentialDefinitionId::new_unchecked(cred_def_id)) + .unwrap(), + &issuer_wallet.cred_defs[cred_def_id].private, + cred_offer, + cred_request, + cred_values.into(), + rev_id, + rev_config, + ) + .expect("Error creating credential"); + + (issue_cred, cred_rev_reg) + } + + // 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( + &self.prover_wallets[prover_id].did, + &cred_def, + &self.prover_wallets[prover_id].master_secret, + "default", + &offer, + ) + .expect("Error creating credential request"); + + // Issuer creates a credential + let (mut recv_cred, new_rev_reg) = self.issuer_create_credential( + &self.issuer_wallets[issuer_id], + &self.ledger, + &cred_req_data.0, + &offer, + *rev_reg_id, + cred_def_id, + self.ledger.schemas.get(&offer.schema_id).unwrap(), + cred_values, + time_prev_rev_reg, + *rev_idx, + ); + + // prover processes it + prover::process_credential( + &mut recv_cred, + &cred_req_data.1, + &self.prover_wallets[prover_id].master_secret, + &cred_def, + self.issuer_wallets[issuer_id] + .rev_defs + .get(*rev_reg_id) + .map(|e| &e.public), + ) + .expect("Error processing credential"); + + // Update wallets and ledger + 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_reg) = new_rev_reg { + self.issuer_wallets + .get_mut(issuer_id) + .unwrap() + .rev_regs + .insert((*rev_reg_id, time_new_rev_reg), rev_reg.clone()); + let map = self + .ledger + .rev_regs + .get_mut(&RevocationRegistryId::new_unchecked(*rev_reg_id)) + .unwrap(); + map.insert(time_new_rev_reg, rev_reg); + } + } + } + pub fn prover_creates_revocation_states( + &self, + prover_id: &'static str, + time_to_update: u64, + ) -> Vec<(Option, Option)> { + let mut rev_states = Vec::new(); + for i in self.prover_wallets[prover_id].credentials.iter() { + match &i.rev_reg_id { + Some(id) => { + let rs = CredentialRevocationState { + timestamp: time_to_update, + rev_reg: self + .ledger + .rev_regs + .get(&id) + .unwrap() + .get(&time_to_update) + .unwrap() + .clone() + .value, + witness: i.try_clone().unwrap().witness.unwrap(), + }; + rev_states.push((Some(time_to_update), Some(rs))); + } + None => rev_states.push((None, None)), + }; + } + rev_states + } + + pub fn prover_creates_presentations( + &self, + prover_id: &'static str, + rev_states: Vec<(Option, Option)>, + 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 (idx, i) in self.prover_wallets[prover_id] + .credentials + .iter() + .enumerate() + { + let values = prover_values + .get(i.cred_def_id.to_string().as_str()) + .unwrap(); + { + let mut cred1 = + present.add_credential(i, rev_states[idx].0, rev_states[idx].1.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};