async def create_cred_req(self, cred_offer_json: str, cd_id: str) -> (str, str): """ Create credential request as HolderProver and store in wallet; return credential json and metadata json. Raise AbsentLinkSecret if link secret not set. :param cred_offer_json: credential offer json :param cd_id: credential definition identifier :return: cred request json and corresponding metadata json as created and stored in wallet """ LOGGER.debug( 'HolderProver.create_cred_req >>> cred_offer_json: %s, cd_id: %s', cred_offer_json, cd_id) self._assert_link_secret('create_cred_req') # Check that ledger has schema on ledger where cred def expects - in case of pool reset with extant wallet cred_def_json = await self.get_cred_def(cd_id) schema_seq_no = int(json.loads(cred_def_json)['schemaId']) schema_json = await self.get_schema(schema_seq_no) schema = json.loads(schema_json) if not schema: LOGGER.debug( 'HolderProver.create_cred_req: <!< absent schema@#%s, cred req may be for another ledger', schema_seq_no) raise AbsentSchema( 'Absent schema@#{}, cred req may be for another ledger'.format( schema_seq_no)) (cred_req_json, cred_req_metadata_json ) = await anoncreds.prover_create_credential_req( self.wallet.handle, self.did, cred_offer_json, cred_def_json, self._link_secret) rv = (cred_req_json, cred_req_metadata_json) LOGGER.debug('HolderProver.create_cred_req <<< %s', rv) return rv
async def get_schema(self, index: Union[SchemaKey, int, str]) -> str: """ Get schema from ledger by SchemaKey namedtuple (origin DID, name, version), sequence number, or schema identifier. Raise AbsentSchema for no such schema, logging any error condition and raising BadLedgerTxn on bad request. Retrieve the schema from the agent's schema cache if it has it; cache it en passant if it does not (and there is a corresponding schema on the ledger). :param index: schema key (origin DID, name, version), sequence number, or schema identifier :return: schema json, parsed from ledger """ LOGGER.debug('_BaseAgent.get_schema >>> index: %s', index) rv_json = json.dumps({}) with SCHEMA_CACHE.lock: if SCHEMA_CACHE.contains(index): LOGGER.info( '_BaseAgent.get_schema: got schema %s from schema cache', index) rv_json = SCHEMA_CACHE[index] LOGGER.debug('_BaseAgent.get_schema <<< %s', rv_json) return json.dumps(rv_json) if isinstance(index, SchemaKey) or (isinstance(index, str) and ':2:' in index): s_id = schema_id( *index) if isinstance(index, SchemaKey) else index s_key = schema_key(s_id) req_json = await ledger.build_get_schema_request( self.did, s_id) resp_json = await self._submit(req_json) resp = json.loads(resp_json) if not ('result' in resp and resp['result'].get( 'data', {}).get('attr_names', None)): LOGGER.debug( '_BaseAgent.get_schema: <!< no schema exists on %s', index) raise AbsentSchema('No schema exists on {}'.format(index)) try: (_, rv_json ) = await ledger.parse_get_schema_response(resp_json) except IndyError: # ledger replied, but there is no such schema LOGGER.debug( '_BaseAgent.get_schema: <!< no schema exists on %s', index) raise AbsentSchema('No schema exists on {}'.format(index)) SCHEMA_CACHE[s_key] = json.loads( rv_json ) # cache indexes by both txn# and schema key en passant LOGGER.info('_BaseAgent.get_schema: got schema %s from ledger', index) elif isinstance( index, (int, str) ): # ':2:' not in index - it's a stringified int txn num if it's a str txn_json = await self.get_txn(int(index)) txn = json.loads(txn_json) if txn.get( 'type', None ) == '101': # {} for no such txn; 101 marks indy-sdk schema txn type rv_json = await self.get_schema( SchemaKey(txn['metadata']['from'], txn['data']['data']['name'], txn['data']['data']['version'])) else: LOGGER.info( '_BaseAgent.get_schema: no schema at seq #%s on ledger', index) else: LOGGER.debug( '_BaseAgent.get_schema: <!< bad schema index type') raise AbsentSchema( 'Attempt to get schema on ({}) {} , must use schema key or an int' .format(type(index), index)) LOGGER.debug('_BaseAgent.get_schema <<< %s', rv_json) return rv_json
async def verify_proof(self, proof_req: dict, proof: dict) -> str: """ Verify proof as Verifier. Raise AbsentRevReg if a proof cites a revocation registry that does not exist on the distributed ledger. :param proof_req: proof request as Verifier creates, as per proof_req_json above :param proof: proof as HolderProver creates :return: json encoded True if proof is valid; False if not """ LOGGER.debug('Verifier.verify_proof >>> proof_req: %s, proof: %s', proof_req, proof) s_id2schema = {} cd_id2cred_def = {} rr_id2rr_def = {} rr_id2rr = {} proof_ids = proof['identifiers'] for proof_id in proof_ids: # schema s_id = proof_id['schema_id'] if s_id not in s_id2schema: schema = json.loads( await self.get_schema(s_id)) # add to cache en passant if not schema: LOGGER.debug( 'Verifier.verify_proof: <!< absent schema %s, proof req may be for another ledger', s_id) raise AbsentSchema( 'Absent schema {}, proof req may be for another ledger' .format(s_id)) s_id2schema[s_id] = schema # cred def cd_id = proof_id['cred_def_id'] if cd_id not in cd_id2cred_def: cred_def = json.loads( await self.get_cred_def(cd_id)) # add to cache en passant cd_id2cred_def[cd_id] = cred_def # rev reg def rr_id = proof_id['rev_reg_id'] if not rr_id: continue rr_def_json = await self._get_rev_reg_def(rr_id) rr_id2rr_def[rr_id] = json.loads(rr_def_json) # timestamp timestamp = proof_id['timestamp'] with REVO_CACHE.lock: revo_cache_entry = REVO_CACHE.get(rr_id, None) (rr_json, _) = await revo_cache_entry.get_state_json( self._build_rr_state_json, timestamp, timestamp) if rr_id not in rr_id2rr: rr_id2rr[rr_id] = {} rr_id2rr[rr_id][timestamp] = json.loads(rr_json) rv = json.dumps(await anoncreds.verifier_verify_proof( json.dumps(proof_req), json.dumps(proof), json.dumps(s_id2schema), json.dumps(cd_id2cred_def), json.dumps(rr_id2rr_def), json.dumps(rr_id2rr))) LOGGER.debug('Verifier.verify_proof <<< %s', rv) return rv
async def create_proof(self, proof_req: dict, creds: dict, requested_creds: dict) -> str: """ Create proof as HolderProver. Raise: * AbsentLinkSecret if link secret not set * CredentialFocus on attempt to create proof on no creds or multiple creds for a credential definition * AbsentTails if missing required tails file * BadRevStateTime if a timestamp for a revocation registry state in the proof request occurs before revocation registry creation * IndyError for any other indy-sdk error. * AbsentInterval if creds missing non-revocation interval, but cred def supports revocation :param proof_req: proof request as per get_creds() above :param creds: credentials to prove :param requested_creds: data structure with self-attested attribute info, requested attribute info and requested predicate info, assembled from get_creds() and filtered for content of interest. I.e., :: { 'self_attested_attributes': {}, 'requested_attributes': { 'attr0_uuid': { 'cred_id': string, 'timestamp': integer, # for revocation state 'revealed': bool }, ... }, 'requested_predicates': { 'predicate0_uuid': { 'cred_id': string, 'timestamp': integer # for revocation state } } } :return: proof json """ LOGGER.debug( 'HolderProver.create_proof >>> proof_req: %s, creds: %s, requested_creds: %s', proof_req, creds, requested_creds) self._assert_link_secret('create_proof') x_uuids = [ attr_uuid for attr_uuid in creds['attrs'] if len(creds['attrs'][attr_uuid]) != 1 ] if x_uuids: LOGGER.debug( 'HolderProver.create_proof: <!< creds specification out of focus (non-uniqueness)' ) raise CredentialFocus( 'Proof request requires unique cred per attribute; violators: {}' .format(x_uuids)) s_id2schema = {} # schema identifier to schema cd_id2cred_def = { } # credential definition identifier to credential definition rr_id2timestamp = { } # revocation registry of interest to timestamp of interest (or None) rr_id2cr_id = { } # revocation registry of interest to credential revocation identifier for referents in {**creds['attrs'], **creds['predicates']}.values(): interval = referents[0].get('interval', None) cred_info = referents[0]['cred_info'] s_id = cred_info['schema_id'] if s_id not in s_id2schema: schema = json.loads( await self.get_schema(s_id)) # add to cache en passant if not schema: LOGGER.debug( 'HolderProver.create_proof: <!< absent schema %s, proof req may be for another ledger', s_id) raise AbsentSchema( 'Absent schema {}, proof req may be for another ledger' .format(s_id)) s_id2schema[s_id] = schema cd_id = cred_info['cred_def_id'] if cd_id not in cd_id2cred_def: cred_def = json.loads( await self.get_cred_def(cd_id)) # add to cache en passant cd_id2cred_def[cd_id] = cred_def rr_id = cred_info['rev_reg_id'] if rr_id: await self._sync_revoc( rr_id) # link tails file to its rr_id if it's new if interval: if rr_id not in rr_id2timestamp: if interval['to'] > int(time()): LOGGER.debug( 'HolderProver.create_proof: <!< interval to %s for rev reg %s is in the future', interval['to'], rr_id) raise BadRevStateTime( 'Revocation registry {} timestamp {} is in the future' .format(rr_id, interval['to'])) rr_id2timestamp[rr_id] = interval['to'] elif 'revocation' in cd_id2cred_def[cd_id]['value']: LOGGER.debug( 'HolderProver.create_proof: <!< creds on cred def id %s missing non-revocation interval', cd_id) raise AbsentInterval( 'Creds on cred def id {} missing non-revocation interval' .format(cd_id)) if rr_id in rr_id2cr_id: continue rr_id2cr_id[rr_id] = cred_info['cred_rev_id'] rr_id2rev_state = {} # revocation registry identifier to its state with REVO_CACHE.lock: for rr_id in rr_id2timestamp: revo_cache_entry = REVO_CACHE.get(rr_id, None) tails = revo_cache_entry.tails if revo_cache_entry else None if tails is None: # missing tails file LOGGER.debug( 'HolderProver.create_proof: <!< missing tails file for rev reg id %s', rr_id) raise AbsentTails( 'Missing tails file for rev reg id {}'.format(rr_id)) rr_def_json = await self._get_rev_reg_def(rr_id) (rr_delta_json, ledger_timestamp) = await revo_cache_entry.get_delta_json( self._build_rr_delta_json, rr_id2timestamp[rr_id], rr_id2timestamp[rr_id]) rr_state_json = await anoncreds.create_revocation_state( tails.reader_handle, rr_def_json, rr_delta_json, ledger_timestamp, rr_id2cr_id[rr_id]) rr_id2rev_state[rr_id] = { rr_id2timestamp[rr_id]: json.loads(rr_state_json) } rv = await anoncreds.prover_create_proof(self.wallet.handle, json.dumps(proof_req), json.dumps(requested_creds), self._link_secret, json.dumps(s_id2schema), json.dumps(cd_id2cred_def), json.dumps(rr_id2rev_state)) LOGGER.debug('HolderProver.create_proof <<< %s', rv) return rv