diff --git a/vcx/libvcx/src/disclosed_proof.rs b/vcx/libvcx/src/disclosed_proof.rs index 23ad18642a..638ed3c1c9 100644 --- a/vcx/libvcx/src/disclosed_proof.rs +++ b/vcx/libvcx/src/disclosed_proof.rs @@ -127,12 +127,14 @@ fn credential_def_identifiers(credentials: &str, proof_req: &ProofRequestData) - } fn _get_revocation_interval(attr_name: &str, proof_req: &ProofRequestData) -> VcxResult> { - let attr = proof_req.requested_attributes.get(attr_name) - .ok_or(VcxError::from_msg(VcxErrorKind::InvalidProofCredentialData, format!("Attribute not found for: {}", attr_name)))?; - - Ok(attr.non_revoked.clone().or(proof_req.non_revoked.clone().or(None))) - - // Todo: Handle case for predicates + if let Some(attr) = proof_req.requested_attributes.get(attr_name) { + Ok(attr.non_revoked.clone().or(proof_req.non_revoked.clone().or(None))) + } else if let Some(attr) = proof_req.requested_predicates.get(attr_name) { + // Handle case for predicates + Ok(attr.non_revoked.clone().or(proof_req.non_revoked.clone().or(None))) + } else { + Err(VcxError::from_msg(VcxErrorKind::InvalidProofCredentialData, format!("Attribute not found for: {}", attr_name))) + } } // Also updates timestamp in credentials_identifiers @@ -300,21 +302,35 @@ impl DisclosedProof { Ok(rtn.to_string()) } - fn build_requested_credentials_json(&self, credentials_identifiers: &Vec, self_attested_attrs: &str) -> VcxResult { + fn build_requested_credentials_json(&self, + credentials_identifiers: &Vec, + self_attested_attrs: &str, + proof_req: &ProofRequestData) -> VcxResult { let mut rtn: Value = json!({ "self_attested_attributes":{}, "requested_attributes":{}, "requested_predicates":{} }); - //Todo: need to do same for predicates and self_attested - //Todo: need to handle if the attribute is not revealed + // do same for predicates and self_attested if let Value::Object(ref mut map) = rtn["requested_attributes"] { for ref cred_info in credentials_identifiers { - let insert_val = json!({"cred_id": cred_info.referent, "revealed": true, "timestamp": cred_info.timestamp}); - map.insert(cred_info.requested_attr.to_owned(), insert_val); + if let Some(ref attr) = proof_req.requested_attributes.get(&cred_info.requested_attr) { + let insert_val = json!({"cred_id": cred_info.referent, "revealed": true, "timestamp": cred_info.timestamp}); + map.insert(cred_info.requested_attr.to_owned(), insert_val); + } + } + } + + if let Value::Object(ref mut map) = rtn["requested_predicates"] { + for ref cred_info in credentials_identifiers { + if let Some(ref attr) = proof_req.requested_predicates.get(&cred_info.requested_attr) { + let insert_val = json!({"cred_id": cred_info.referent, "timestamp": cred_info.timestamp}); + map.insert(cred_info.requested_attr.to_owned(), insert_val); + } } } + // handle if the attribute is not revealed let self_attested_attrs: Value = serde_json::from_str(self_attested_attrs) .map_err(|err| VcxError::from_msg(VcxErrorKind::InvalidJson, format!("Cannot deserialize self attested attributes: {}", err)))?; rtn["self_attested_attributes"] = self_attested_attrs; @@ -333,10 +349,13 @@ impl DisclosedProof { let proof_req_data_json = serde_json::to_string(&proof_req.proof_request_data) .map_err(|err| VcxError::from_msg(VcxErrorKind::InvalidJson, format!("Cannot serialize proof request: {}", err)))?; - let mut credentials_identifiers = credential_def_identifiers(credentials, &proof_req.proof_request_data)?; + let mut credentials_identifiers = credential_def_identifiers(credentials, + &proof_req.proof_request_data)?; let revoc_states_json = build_rev_states_json(&mut credentials_identifiers)?; - let requested_credentials = self.build_requested_credentials_json(&credentials_identifiers, self_attested_attrs)?; + let requested_credentials = self.build_requested_credentials_json(&credentials_identifiers, + self_attested_attrs, + &proof_req.proof_request_data)?; let schemas_json = self.build_schemas_json(&credentials_identifiers)?; let credential_defs_json = self.build_cred_def_json(&credentials_identifiers)?; @@ -829,7 +848,22 @@ mod tests { }); let proof: DisclosedProof = Default::default(); - let requested_credential = proof.build_requested_credentials_json(&creds, &self_attested_attrs).unwrap(); + let proof_req = json!({ + "nonce": "123432421212", + "name": "proof_req_1", + "version": "0.1", + "requested_attributes": { + "height_1": { + "name": "height_1", + "non_revoked": {"from": 123, "to": 456} + }, + "zip_2": { "name": "zip_2" } + }, + "requested_predicates": {}, + "non_revoked": {"from": 098, "to": 123} + }); + let proof_req: ProofRequestData = serde_json::from_value(proof_req).unwrap(); + let requested_credential = proof.build_requested_credentials_json(&creds, &self_attested_attrs, &proof_req).unwrap(); assert_eq!(test.to_string(), requested_credential); } @@ -1207,6 +1241,67 @@ mod tests { assert!(generated_proof.is_ok()); } + #[cfg(feature = "pool_tests")] + #[test] + fn test_generate_proof_with_predicates() { + init!("ledger"); + let did = settings::get_config_value(settings::CONFIG_INSTITUTION_DID).unwrap(); + let (schema_id, _, cred_def_id, _, _, _, _, cred_id, _, _) = ::utils::libindy::anoncreds::tests::create_and_store_credential(::utils::constants::DEFAULT_SCHEMA_ATTRS, true); + let mut proof_req = ProofRequestMessage::create(); + let to = time::get_time().sec; + let indy_proof_req = json!({ + "nonce": "123432421212", + "name": "proof_req_1", + "version": "0.1", + "requested_attributes": { + "address1_1": { + "name": "address1", + "restrictions": [{"issuer_did": did}], + "non_revoked": {"from": 123, "to": to} + }, + "zip_2": { "name": "zip" } + }, + "self_attested_attr_3": json!({ + "name":"self_attested_attr", + }), + "requested_predicates": json!({ + "zip_3": {"name":"zip", "p_type":">=", "p_value":18} + }), + "non_revoked": {"from": 098, "to": to} + }).to_string(); + proof_req.proof_request_data = serde_json::from_str(&indy_proof_req).unwrap(); + + let mut proof: DisclosedProof = Default::default(); + proof.proof_request = Some(proof_req); + proof.link_secret_alias = "main".to_string(); + + let all_creds: Value = serde_json::from_str(&proof.retrieve_credentials().unwrap()).unwrap(); + let selected_credentials: Value = json!({ + "attrs":{ + "address1_1": { + "credential": all_creds["attrs"]["address1_1"][0], + "tails_file": get_temp_dir_path(Some(TEST_TAILS_FILE)).to_str().unwrap().to_string() + }, + "zip_2": { + "credential": all_creds["attrs"]["zip_2"][0], + "tails_file": get_temp_dir_path(Some(TEST_TAILS_FILE)).to_str().unwrap().to_string() + }, + }, + "predicates":{ + "zip_3": { + "credential": all_creds["attrs"]["zip_3"][0], + } + } + }); + + let self_attested: Value = json!({ + "self_attested_attr_3":"attested_val" + }); + + let generated_proof = proof.generate_proof(&selected_credentials.to_string(), &self_attested.to_string()); + assert!(generated_proof.is_ok()); + } + #[test] fn test_build_rev_states_json() { init!("true"); diff --git a/vcx/libvcx/src/utils/constants.rs b/vcx/libvcx/src/utils/constants.rs index 6f18f3565e..8aad8634bd 100644 --- a/vcx/libvcx/src/utils/constants.rs +++ b/vcx/libvcx/src/utils/constants.rs @@ -142,6 +142,7 @@ pub static PROOF_LIBINDY: &str = r#"{"proofs":{"claim::1f927d68-8905-4188-afd6-3 pub static PROOF_REQUEST: &str = r#"{"name":"proof name","nonce":"2771519439","requested_attrs":{"height_0":{"issuer_did":"DunkM3x1y7S4ECgSL4Wkru","name":"height","schema_seq_no":694},"weight_1":{"issuer_did":"DunkM3x1y7S4ECgSL4Wkru","name":"weight","schema_seq_no":694}},"requested_predicates":{"age_2":{"attr_name":"age","p_type":"GE","issuer_did":"DunkM3x1y7S4ECgSL4Wkru","schema_seq_no":694,"value":18}},"version":"0.1"}"#; pub static REQUESTED_ATTRIBUTES: &str = "requested_attributes"; +pub static PROOF_REQUESTED_PREDICATES: &str = "requested_predicates"; pub static ATTRS: &str = "attrs"; pub static ENTERPRISE_PREFIX: &str = "enterprise"; pub static CONSUMER_PREFIX: &str = "consumer"; diff --git a/vcx/libvcx/src/utils/libindy/anoncreds.rs b/vcx/libvcx/src/utils/libindy/anoncreds.rs index 6f1db1815a..4f8af2b50f 100644 --- a/vcx/libvcx/src/utils/libindy/anoncreds.rs +++ b/vcx/libvcx/src/utils/libindy/anoncreds.rs @@ -5,8 +5,8 @@ use indy::{anoncreds, blob_storage, ledger}; use time; use settings; -use utils::constants::{LIBINDY_CRED_OFFER, REQUESTED_ATTRIBUTES, ATTRS, REV_STATE_JSON}; -use utils::libindy::{error_codes::map_rust_indy_sdk_error, mock_libindy_rc, wallet::get_wallet_handle}; +use utils::constants::{ LIBINDY_CRED_OFFER, REQUESTED_ATTRIBUTES, PROOF_REQUESTED_PREDICATES, ATTRS, REV_STATE_JSON}; +use utils::libindy::{ error_codes::map_rust_indy_sdk_error, mock_libindy_rc, wallet::get_wallet_handle }; use utils::libindy::payments::{pay_for_txn, PaymentTxn}; use utils::libindy::ledger::*; use utils::constants::{SCHEMA_ID, SCHEMA_JSON, SCHEMA_TXN_TYPE, CRED_DEF_ID, CRED_DEF_JSON, CRED_DEF_TXN_TYPE, REV_REG_DEF_TXN_TYPE, REV_REG_DELTA_TXN_TYPE, REVOC_REG_TYPE, rev_def_json, REV_REG_ID, REV_REG_DELTA_JSON, REV_REG_JSON}; @@ -150,28 +150,47 @@ pub fn libindy_prover_get_credentials_for_proof_req(proof_req: &str) -> VcxResul // since the search_credentials_for_proof request validates that the proof_req is properly structured, this get() // fn should never fail, unless libindy changes their formats. - let requested_attributes: Option> = proof_request_json.get(REQUESTED_ATTRIBUTES) + let requested_attributes:Option> = proof_request_json.get(REQUESTED_ATTRIBUTES) .and_then(|v| { - serde_json::from_value(v.clone()) - .map_err(|_| { - error!("Invalid Json Parsing of Requested Attributes Retrieved From Libindy. Did Libindy change its structure?"); - }).ok() + serde_json::from_value(v.clone()).map_err(|_| { + error!("Invalid Json Parsing of Requested Attributes Retrieved From Libindy. Did Libindy change its structure?"); + }).ok() }); + + let requested_predicates:Option> = proof_request_json.get(PROOF_REQUESTED_PREDICATES).and_then(|v| { + serde_json::from_value(v.clone()).map_err(|_| { + error!("Invalid Json Parsing of Requested Predicates Retrieved From Libindy. Did Libindy change its structure?"); + }).ok() + }); + + // handle special case of "empty because json is bad" vs "empty because no attributes sepected" + if requested_attributes == None && requested_predicates == None { + return Err(VcxError::from_msg(VcxErrorKind::InvalidAttributesStructure, "Invalid Json Parsing of Requested Attributes Retrieved From Libindy")); + } - match requested_attributes { - Some(attrs) => { - let search_handle = anoncreds::prover_search_credentials_for_proof_req(wallet_handle, proof_req, None) - .wait() - .map_err(map_rust_indy_sdk_error)?; - let creds: String = fetch_credentials(search_handle, attrs)?; - // should an error on closing a search handle throw an error, or just a warning? - // for now we're are just outputting to the user that there is an issue, and continuing on. - let _ = close_search_handle(search_handle); - Ok(creds) - } - None => { - Err(VcxError::from_msg(VcxErrorKind::InvalidAttributesStructure, "Invalid Json Parsing of Requested Attributes Retrieved From Libindy")) - } + let mut fetch_attrs: Map = match requested_attributes { + Some(attrs) => attrs.clone(), + None => Map::new() + }; + match requested_predicates { + Some(attrs) => fetch_attrs.extend(attrs), + None => () + } + if 0 < fetch_attrs.len() { + let search_handle = anoncreds::prover_search_credentials_for_proof_req(wallet_handle, proof_req, None) + .wait() + .map_err(|ec| { + error!("Opening Indy Search for Credentials Failed"); + map_rust_indy_sdk_error(ec) + })?; + let creds: String = fetch_credentials(search_handle, fetch_attrs)?; + + // should an error on closing a search handle throw an error, or just a warning? + // for now we're are just outputting to the user that there is an issue, and continuing on. + let _ = close_search_handle(search_handle); + Ok(creds) + } else { + Ok("{}".to_string()) } } @@ -665,6 +684,77 @@ pub mod tests { (proof_req, proof) } + pub fn create_proof_with_predicate(include_predicate_cred: bool) -> (String, String, String, String) { + let did = settings::get_config_value(settings::CONFIG_INSTITUTION_DID).unwrap(); + let (schema_id, schema_json, cred_def_id, cred_def_json, offer, req, req_meta, cred_id, _, _) + = create_and_store_credential(::utils::constants::DEFAULT_SCHEMA_ATTRS, false); + + let proof_req = json!({ + "nonce":"123432421212", + "name":"proof_req_1", + "version":"0.1", + "requested_attributes": json!({ + "address1_1": json!({ + "name":"address1", + "restrictions": [json!({ "issuer_did": did })] + }), + "self_attest_3": json!({ + "name":"self_attest", + }), + }), + "requested_predicates": json!({ + "zip_3": {"name":"zip", "p_type":">=", "p_value":18} + }), + }).to_string(); + + let requested_credentials_json; + if include_predicate_cred { + requested_credentials_json = json!({ + "self_attested_attributes":{ + "self_attest_3": "my_self_attested_val" + }, + "requested_attributes":{ + "address1_1": {"cred_id": cred_id, "revealed": true} + }, + "requested_predicates":{ + "zip_3": {"cred_id": cred_id} + } + }).to_string(); + } else { + requested_credentials_json = json!({ + "self_attested_attributes":{ + "self_attest_3": "my_self_attested_val" + }, + "requested_attributes":{ + "address1_1": {"cred_id": cred_id, "revealed": true} + }, + "requested_predicates":{ + } + }).to_string(); + } + + let schema_json: serde_json::Value = serde_json::from_str(&schema_json).unwrap(); + let schemas = json!({ + schema_id: schema_json, + }).to_string(); + + let cred_def_json: serde_json::Value = serde_json::from_str(&cred_def_json).unwrap(); + let cred_defs = json!({ + cred_def_id: cred_def_json, + }).to_string(); + + libindy_prover_get_credentials_for_proof_req(&proof_req).unwrap(); + + let proof = libindy_prover_create_proof( + &proof_req, + &requested_credentials_json, + "main", + &schemas, + &cred_defs, + None).unwrap(); + (schemas, cred_defs, proof_req, proof) + } + #[cfg(feature = "pool_tests")] #[test] fn test_prover_verify_proof() { @@ -685,6 +775,44 @@ pub mod tests { assert!(proof_validation, true); } + #[cfg(feature = "pool_tests")] + #[test] + fn test_prover_verify_proof_with_predicate_success_case() { + init!("ledger"); + let (schemas, cred_defs, proof_req, proof) = create_proof_with_predicate(true); + + let result = libindy_verifier_verify_proof( + &proof_req, + &proof, + &schemas, + &cred_defs, + "{}", + "{}", + ); + + assert!(result.is_ok()); + let proof_validation = result.unwrap(); + assert!(proof_validation, true); + } + + #[cfg(feature = "pool_tests")] + #[test] + fn test_prover_verify_proof_with_predicate_fail_case() { + init!("ledger"); + let (schemas, cred_defs, proof_req, proof) = create_proof_with_predicate(false); + + let result = libindy_verifier_verify_proof( + &proof_req, + &proof, + &schemas, + &cred_defs, + "{}", + "{}", + ); + + assert!(!result.is_ok()); + } + #[cfg(feature = "pool_tests")] #[test] fn tests_libindy_prover_get_credentials() { diff --git a/vcx/wrappers/python3/demo/demo_utils.py b/vcx/wrappers/python3/demo/demo_utils.py index f5dffae9bf..b1a0ee45b6 100644 --- a/vcx/wrappers/python3/demo/demo_utils.py +++ b/vcx/wrappers/python3/demo/demo_utils.py @@ -85,10 +85,10 @@ async def send_credential_request(my_connection, cred_def_json, schema_attrs, cr print("Done") -async def send_proof_request(my_connection, institution_did, proof_attrs, proof_uuid, proof_name): +async def send_proof_request(my_connection, institution_did, proof_attrs, proof_uuid, proof_name, proof_predicates): print("#19 Create a Proof object") - proof = await Proof.create(proof_uuid, proof_name, proof_attrs, {}) + proof = await Proof.create(proof_uuid, proof_name, proof_attrs, {}, requested_predicates=proof_predicates) print("#20 Request proof of degree from alice") await proof.request_proof(my_connection) @@ -188,15 +188,26 @@ async def handle_proof_request(my_connection, request): print("#24 Query for credentials in the wallet that satisfy the proof request") credentials = await proof.get_creds() - # TODO list credentials and let Alice select + # include self-attested attributes (not included in credentials) + self_attested = {} + # Use the first available credentials to satisfy the proof request for attr in credentials['attrs']: - credentials['attrs'][attr] = { - 'credential': credentials['attrs'][attr][0] - } + if 0 < len(credentials['attrs'][attr]): + credentials['attrs'][attr] = { + 'credential': credentials['attrs'][attr][0] + } + else: + self_attested[attr] = 'my self-attested value' + + for attr in self_attested: + del credentials['attrs'][attr] + + print('credentials', credentials) + print('self_attested', self_attested) print("#25 Generate the proof") - await proof.generate_proof(credentials, {}) + await proof.generate_proof(credentials, self_attested) # TODO figure out why this always segfaults print("#26 Send the proof to X") diff --git a/vcx/wrappers/python3/demo/faber-pg.py b/vcx/wrappers/python3/demo/faber-pg.py index 128f5109c5..9598baf2c8 100644 --- a/vcx/wrappers/python3/demo/faber-pg.py +++ b/vcx/wrappers/python3/demo/faber-pg.py @@ -74,7 +74,7 @@ async def main(): print("#3 Create a new schema and cred def on the ledger") schema_uuid = 'schema_uuid' schema_name = 'degree schema' - schema_attrs = ['name', 'date', 'degree'] + schema_attrs = ['name', 'date', 'degree', 'age'] creddef_uuid = 'credef_uuid' creddef_name = 'degree' cred_def_json = await create_schema_and_cred_def(schema_uuid, schema_name, schema_attrs, creddef_uuid, creddef_name) @@ -121,6 +121,7 @@ async def main(): 'name': 'alice', 'date': '05-2018', 'degree': 'maths', + 'age': '24', } cred_tag = 'alice_degree' cred_name = 'cred' @@ -131,12 +132,14 @@ async def main(): proof_attrs = [ {'name': 'name', 'restrictions': [{'issuer_did': config['institution_did']}]}, {'name': 'date', 'restrictions': [{'issuer_did': config['institution_did']}]}, - {'name': 'degree', 'restrictions': [{'issuer_did': config['institution_did']}]} + {'name': 'degree', 'restrictions': [{'issuer_did': config['institution_did']}]}, + {'name': 'self_attested_thing'} ] + proof_predicates = [{'name':'age', 'p_type':'>=', 'p_value':18},] proof_uuid = 'proof_uuid' proof_name = 'proof_from_alice' - await send_proof_request(my_connection, config['institution_did'], proof_attrs, proof_uuid, proof_name) + await send_proof_request(my_connection, config['institution_did'], proof_attrs, proof_uuid, proof_name, proof_predicates) elif option == '3': await handle_messages(my_connection, handled_offers, handled_requests) diff --git a/vcx/wrappers/python3/vcx/api/proof.py b/vcx/wrappers/python3/vcx/api/proof.py index 89c7f76048..8b4f1f0ad0 100644 --- a/vcx/wrappers/python3/vcx/api/proof.py +++ b/vcx/wrappers/python3/vcx/api/proof.py @@ -24,7 +24,7 @@ def proof_state(self, x): self._proof_state = x @staticmethod - async def create(source_id: str, name: str, requested_attrs: list, revocation_interval: dict): + async def create(source_id: str, name: str, requested_attrs: list, revocation_interval: dict, requested_predicates: list = []): """ Builds a generic proof object :param source_id: Tag associated by user of sdk @@ -42,7 +42,7 @@ async def create(source_id: str, name: str, requested_attrs: list, revocation_in c_source_id = c_char_p(source_id.encode('utf-8')) c_name = c_char_p(name.encode('utf-8')) - c_req_predicates = c_char_p('[]'.encode('utf-8')) + c_req_predicates = c_char_p(json.dumps(requested_predicates).encode('utf-8')) c_req_attrs = c_char_p(json.dumps(requested_attrs).encode('utf-8')) c_revocation_interval = c_char_p(json.dumps(revocation_interval).encode('utf-8')) c_params = (c_source_id, c_req_attrs, c_req_predicates, c_revocation_interval, c_name)