async def create_cred_offer(self, schema_seq_no: int) -> str: """ Create credential offer as Issuer for given schema. Raise CorruptWallet if the wallet has no private key for the corresponding credential definition. :param schema_seq_no: schema sequence number :return: credential offer json for use in storing credentials at HolderProver. """ LOGGER.debug('Issuer.create_cred_offer >>> schema_seq_no: %s', schema_seq_no) rv = None cd_id = cred_def_id(self.did, schema_seq_no, self.pool.protocol) try: rv = await anoncreds.issuer_create_credential_offer( self.wallet.handle, cd_id) except IndyError as x_indy: if x_indy.error_code == ErrorCode.WalletNotFoundError: LOGGER.debug( 'Issuer.create_cred_offer: <!< did not issue cred definition from wallet %s', self.wallet.name) raise CorruptWallet( 'Cannot create cred offer: did not issue cred definition from wallet {}' .format(self.wallet.name)) else: LOGGER.debug( 'Issuer.create_cred_offer: <!< cannot create cred offer, indy error code %s', x_indy.error_code) raise LOGGER.debug('Issuer.create_cred_offer <<< %s', rv) return rv
async def _sign_submit(self, req_json: str) -> str: """ Sign and submit (json) request to ledger; return (json) result. Raise ClosedPool if pool is not yet open, CorruptWallet if existing wallet's pool is no longer extant, or BadLedgerTxn on any other failure. :param req_json: json of request to sign and submit :param wait: whether to wait for the transaction to appear on the ledger before proceeding :return: json response """ LOGGER.debug('_BaseAnchor._sign_submit >>> json: %s', req_json) if not self.pool.handle: LOGGER.debug('_BaseAnchor._submit <!< closed pool %s', self.pool.name) raise ClosedPool('Cannot submit request to closed pool {}'.format( self.pool.name)) try: rv_json = await ledger.sign_and_submit_request( self.pool.handle, self.wallet.handle, self.did, req_json) await asyncio.sleep(0) except IndyError as x_indy: if x_indy.error_code == ErrorCode.WalletIncompatiblePoolError: LOGGER.debug( '_BaseAnchor._sign_submit: <!< Corrupt wallet %s is not compatible with pool %s', self.wallet.name, self.pool.name) raise CorruptWallet( 'Corrupt wallet {} is not compatible with pool {}'.format( self.wallet.name, self.pool.name)) else: LOGGER.debug( '_BaseAnchor._sign_submit: <!< cannot sign/submit request for ledger: indy error code %s', self.wallet.name) raise BadLedgerTxn( 'Cannot sign/submit request for ledger: indy error code {}' .format(x_indy.error_code)) resp = json.loads(rv_json) if ('op' in resp) and (resp['op'] in ('REQNACK', 'REJECT')): LOGGER.debug( '_BaseAnchor._sign_submit: ledger rejected request: %s', resp['reason']) raise BadLedgerTxn( 'Ledger rejected transaction request: {}'.format( resp['reason'])) seq_no = resp.get('result', {}).get('seqNo', None) if 'reason' in resp and seq_no is None: LOGGER.debug( '_BaseAnchor._sign_submit: <!< response indicates no transaction: %s', resp['reason']) raise BadLedgerTxn('Response indicates no transaction: {}'.format( resp['reason'])) LOGGER.debug('_BaseAnchor._sign_submit <<< %s', rv_json) return rv_json
async def _create_cred_def(self, schema: dict, ledger_cred_def: dict, revo: bool) -> (str, bool): """ Create credential definition in wallet as part of the send_cred_def() sequence. Return whether the private key for the cred def is OK to continue with the sequence, propagating the cred def and revocation registry info to the ledger. :param schema: schema on which to create cred def :param ledger_cred_def: credential definition as ledger has it (typically, None) :param revo: whether cred def supports revocation :return: cred def json and whether local cred def private key is OK, hence cred def is OK to send to the ledger """ LOGGER.debug( 'Issuer._create_cred_def >>> schema: %s, ledger_cred_def: %s, revo: %s', schema, ledger_cred_def, revo) cred_def_json = '{}' private_key_ok = True try: (_, cred_def_json ) = await anoncreds.issuer_create_and_store_credential_def( self.wallet.handle, self.did, # issuer DID json.dumps(schema), self.pool.protocol.cd_id_tag( False), # expect only one cred def per schema and issuer 'CL', json.dumps({'support_revocation': revo})) if ledger_cred_def: private_key_ok = False LOGGER.warning( 'New cred def on %s in wallet shadows existing one on ledger: private key not usable', cred_def_id(self.did, schema['seqNo'], self.pool.protocol)) # carry on though, this anchor may have other capacities so public key may be good enough except IndyError as x_indy: if x_indy.error_code == ErrorCode.AnoncredsCredDefAlreadyExistsError: if ledger_cred_def: LOGGER.info( 'Issuer wallet %s reusing existing cred def on schema %s version %s', self.name, schema['name'], schema['version']) else: LOGGER.debug( 'Issuer._create_cred_def <!< corrupt wallet %s', self.name) raise CorruptWallet( 'Corrupt Issuer wallet {} has cred def on schema {} not on ledger' .format(self.name, schema['id'])) else: LOGGER.debug( 'Issuer._create_cred_def <!< cannot store cred def in wallet %s: indy error code %s', self.name, x_indy.error_code) raise rv = (cred_def_json, private_key_ok) LOGGER.debug('Issuer._create_cred_def <<< %s', rv) return rv
async def _sign_submit(self, req_json: str) -> str: """ Sign and submit (json) request to ledger; return (json) result. Raise: * AbsentPool for no pool * ClosedPool if pool is not yet open * CorruptWallet if existing wallet appears not to pertain to the anchor's pool * WalletState if wallet is closed * BadLedgerTxn on ledger rejection of transaction. :param req_json: json of request to sign and submit :return: json response """ LOGGER.debug('BaseAnchor._sign_submit >>> req_json: %s', req_json) if not self.pool: LOGGER.debug('BaseAnchor._sign_submit <!< absent pool') raise AbsentPool('Cannot sign and submit request: absent pool') if not self.pool.handle: LOGGER.debug('BaseAnchor._submit <!< closed pool %s', self.pool.name) raise ClosedPool('Cannot sign and submit request to closed pool {}'.format(self.pool.name)) if not self.wallet.handle: LOGGER.debug('BaseAnchor._sign_submit <!< Wallet %s is closed', self.name) raise WalletState('Wallet {} is closed'.format(self.name)) try: rv_json = await ledger.sign_and_submit_request(self.pool.handle, self.wallet.handle, self.did, req_json) await asyncio.sleep(0) except IndyError as x_indy: if x_indy.error_code == ErrorCode.WalletIncompatiblePoolError: LOGGER.debug( 'BaseAnchor._sign_submit <!< Corrupt wallet %s is not compatible with pool %s', self.name, self.pool.name) raise CorruptWallet('Corrupt wallet {} is not compatible with pool {}'.format( self.name, self.pool.name)) LOGGER.debug( 'BaseAnchor._sign_submit <!< cannot sign/submit request for ledger: indy error code %s', x_indy.error_code) raise BadLedgerTxn('Cannot sign/submit request for ledger: indy error code {}'.format( x_indy.error_code)) resp = json.loads(rv_json) if resp.get('op', '') in ('REQNACK', 'REJECT'): LOGGER.debug('BaseAnchor._sign_submit: ledger rejected request: %s', resp['reason']) raise BadLedgerTxn('Ledger rejected transaction request: {}'.format(resp['reason'])) LOGGER.debug('BaseAnchor._sign_submit <<< %s', rv_json) return rv_json
async def send_cred_def(self, s_id: str, revocation: bool = True, rr_size: int = None) -> str: """ Create a credential definition as Issuer, store it in its wallet, and send it to the ledger. Raise CorruptWallet for wallet not pertaining to current ledger, BadLedgerTxn on failure to send credential definition to ledger if need be, or IndyError for any other failure to create and store credential definition in wallet. :param s_id: schema identifier :param revocation: whether to support revocation for cred def :param rr_size: size of initial revocation registry (default as per _create_rev_reg()), if revocation supported :return: json credential definition as it appears on ledger """ LOGGER.debug( 'Issuer.send_cred_def >>> s_id: %s, revocation: %s, rr_size: %s', s_id, revocation, rr_size) if not ok_schema_id(s_id): LOGGER.debug('Issuer.send_cred_def <!< Bad schema id %s', s_id) raise BadIdentifier('Bad schema id {}'.format(s_id)) rv_json = json.dumps({}) schema_json = await self.get_schema(schema_key(s_id)) schema = json.loads(schema_json) cd_id = cred_def_id(self.did, schema['seqNo'], self.pool.protocol) private_key_ok = True with CRED_DEF_CACHE.lock: try: rv_json = await self.get_cred_def(cd_id) LOGGER.info( 'Cred def on schema %s version %s already exists on ledger; Issuer %s not sending another', schema['name'], schema['version'], self.wallet.name) except AbsentCredDef: pass # OK - about to create, store, and send it try: (_, cred_def_json ) = await anoncreds.issuer_create_and_store_credential_def( self.wallet.handle, self.did, # issuer DID schema_json, self.pool.protocol.cd_id_tag( False ), # expect only one cred def per schema and issuer 'CL', json.dumps({'support_revocation': revocation})) if json.loads(rv_json): private_key_ok = False LOGGER.warning( 'New cred def on %s in wallet shadows existing one on ledger: private key not usable', cd_id) # carry on though, this anchor may have other roles so public key may be good enough except IndyError as x_indy: if x_indy.error_code == ErrorCode.AnoncredsCredDefAlreadyExistsError: if json.loads(rv_json): LOGGER.info( 'Issuer wallet %s reusing existing cred def on schema %s version %s', self.wallet.name, schema['name'], schema['version']) else: LOGGER.debug( 'Issuer.send_cred_def: <!< corrupt wallet %s', self.wallet.name) raise CorruptWallet( 'Corrupt Issuer wallet {} has cred def on schema {} version {} not on ledger' .format(self.wallet.name, schema['name'], schema['version'])) else: LOGGER.debug( 'Issuer.send_cred_def: <!< cannot store cred def in wallet %s: indy error code %s', self.wallet.name, x_indy.error_code) raise if not json.loads( rv_json ): # checking the ledger returned no cred def: send it req_json = await ledger.build_cred_def_request( self.did, cred_def_json) await self._sign_submit(req_json) for _ in range(16): # reasonable timeout try: rv_json = await self.get_cred_def(cd_id ) # adds to cache break except AbsentCredDef: await sleep(1) LOGGER.info( 'Sent cred def %s to ledger, waiting 1s for its appearance', cd_id) if not rv_json: LOGGER.debug( 'Issuer.send_cred_def <!< timed out waiting on sent cred_def %s', cd_id) raise BadLedgerTxn( 'Timed out waiting on sent cred_def {}'.format(cd_id)) if revocation: await self._sync_revoc( rev_reg_id(cd_id, 0), rr_size) # create new rev reg, tails file for tag 0 if revocation and private_key_ok: for tag in [ str(t) for t in range( int(Tails.next_tag(self._dir_tails, cd_id)[0])) ]: # '0' to str(next-1) await self._sync_revoc(rev_reg_id(cd_id, tag), rr_size if tag == 0 else None) makedirs(join(self._dir_tails, cd_id), exist_ok=True ) # make sure dir exists for box id collection, revo or not LOGGER.debug('Issuer.send_cred_def <<< %s', rv_json) return rv_json
async def create(self) -> 'Wallet': """ Create wallet as configured and store DID, or else re-use any existing configuration. Operation sequence create/store-DID/close does not auto-remove the wallet on close, even if so configured. :return: current object """ LOGGER.debug('Wallet.create >>>') try: await wallet.create_wallet( config=json.dumps(self.cfg), credentials=json.dumps(self.access_creds)) self._created = True LOGGER.info('Created wallet %s', self.name) except IndyError as x_indy: if x_indy.error_code == ErrorCode.WalletAlreadyExistsError: LOGGER.info('Wallet already exists: %s', self.name) else: LOGGER.debug( 'Wallet.create <!< indy error code %s on creation of wallet %s', x_indy.error_code, self.name) raise LOGGER.debug('Attempting to open wallet %s', self.name) self._handle = await wallet.open_wallet( json.dumps(self.cfg), json.dumps(self.access_creds)) LOGGER.info('Opened wallet %s on handle %s', self.name, self.handle) if self._created: (self._did, self.verkey) = await did.create_and_store_my_did( self.handle, json.dumps({'seed': self._seed})) LOGGER.debug('Wallet %s stored new DID %s, verkey %s from seed', self.name, self.did, self.verkey) await did.set_did_metadata( self.handle, self.did, json.dumps({ 'seed_hash': sha256(self._seed.encode()).hexdigest() })) LOGGER.info('Wallet %s set seed hash metadata for DID %s', self.name, self.did) else: self._created = True LOGGER.debug('Attempting to derive seed to did for wallet %s', self.name) self._did = await self._seed2did() try: self.verkey = await did.key_for_local_did(self.handle, self.did) except IndyError: LOGGER.debug( 'Wallet.create <!< no verkey for DID %s on ledger, wallet %s may pertain to another', self.did, self.name) raise CorruptWallet( 'No verkey for DID {} on ledger, wallet {} may pertain to another'.format( self.did, self.name)) LOGGER.info('Wallet %s got verkey %s for existing DID %s', self.name, self.verkey, self.did) await wallet.close_wallet(self.handle) LOGGER.debug('Wallet.create <<<') return self