async def create_cred(self, cred_offer_json, cred_req_json: str, cred_attrs: dict, rr_size: int = None) -> (str, str, int): """ Create credential as Issuer out of credential request and dict of key:value (raw, unencoded) entries for attributes. Return credential json, and if cred def supports revocation, credential revocation identifier and revocation registry delta ledger timestamp (epoch seconds). If the credential definition supports revocation, and the current revocation registry is full, the processing creates a new revocation registry en passant. Depending on the revocation registry size (by default starting at 256 and doubling iteratively through 4096), this operation may delay credential creation by several seconds. :param cred_offer_json: credential offer json as created by Issuer :param cred_req_json: credential request json as created by HolderProver :param cred_attrs: dict mapping each attribute to its raw value (the operation encodes it); e.g., :: { 'favourite_drink': 'martini', 'height': 180, 'last_visit_date': '2017-12-31', 'weaknesses': None } :param rr_size: size of new revocation registry (default as per _create_rev_reg()) if necessary :return: newly issued credential json; credential revocation identifier (if cred def supports revocation, None otherwise), and ledger timestamp (if cred def supports revocation, None otherwise) """ LOGGER.debug( 'Issuer.create_cred >>> cred_offer_json: %s, cred_req_json: %s, cred_attrs: %s, rr_size: %s', cred_offer_json, cred_req_json, cred_attrs, rr_size) cd_id = json.loads(cred_offer_json)['cred_def_id'] if not ok_cred_def_id(cd_id): LOGGER.debug('Issuer.create_cred <!< Bad cred def id %s', cd_id) raise BadIdentifier('Bad cred def id {}'.format(cd_id)) cred_def = json.loads( await self.get_cred_def(cd_id)) # ensure cred def is in cache if 'revocation' in cred_def['value']: with REVO_CACHE.lock: rr_id = Tails.current_rev_reg_id(self._dir_tails, cd_id) tails = REVO_CACHE[rr_id].tails assert tails # at (re)start, at cred def, Issuer sync_revoc() sets this index in revocation cache try: (cred_json, cred_revoc_id, rr_delta_json) = await anoncreds.issuer_create_credential( self.wallet.handle, cred_offer_json, cred_req_json, json.dumps({ k: cred_attr_value(cred_attrs[k]) for k in cred_attrs }), rr_id, tails.reader_handle) # do not create rr delta frame and append to cached delta frames list: timestamp could lag or skew rre_req_json = await ledger.build_revoc_reg_entry_request( self.did, rr_id, 'CL_ACCUM', rr_delta_json) await self._sign_submit(rre_req_json) assert rr_id == tails.rr_id resp_json = await self._sign_submit(rre_req_json) resp = json.loads(resp_json) rv = (cred_json, cred_revoc_id, self.pool.protocol.txn2epoch(resp)) except IndyError as x_indy: if x_indy.error_code == ErrorCode.AnoncredsRevocationRegistryFullError: (tag, rr_size_suggested) = Tails.next_tag( self._dir_tails, cd_id) rr_id = rev_reg_id(cd_id, tag) await self._create_rev_reg( rr_id, rr_size or rr_size_suggested) REVO_CACHE[rr_id].tails = await Tails( self._dir_tails, cd_id).open() return await self.create_cred(cred_offer_json, cred_req_json, cred_attrs ) # should be ok now LOGGER.debug( 'Issuer.create_cred: <!< cannot create cred, indy error code %s', x_indy.error_code) raise else: try: (cred_json, _, _) = await anoncreds.issuer_create_credential( self.wallet.handle, cred_offer_json, cred_req_json, json.dumps({ k: cred_attr_value(cred_attrs[k]) for k in cred_attrs }), None, None) rv = (cred_json, _, _) except IndyError as x_indy: LOGGER.debug( 'Issuer.create_cred: <!< cannot create cred, indy error code %s', x_indy.error_code) raise LOGGER.debug('Issuer.create_cred <<< %s', rv) return rv
async def test_anchors_tails_load( pool_name, pool_genesis_txn_data, seed_trustee1): rrbx = True print(Ink.YELLOW('\n\n== Load-testing tails on {}ternal rev reg builder ==').format("ex" if rrbx else "in")) await RevRegBuilder.stop(WALLET_NAME) # in case of re-run # Set up node pool ledger config and wallets, open pool, init anchors p_mgr = NodePoolManager() if pool_name not in await p_mgr.list(): await p_mgr.add_config(pool_name, pool_genesis_txn_data) pool = p_mgr.get(pool_name) await pool.open() w_mgr = WalletManager() wallets = { 'trustee-anchor': { 'seed': seed_trustee1, 'storage_type': None, 'config': None, 'access_creds': None }, WALLET_NAME: { 'seed': 'Superstar-Anchor-000000000000000', 'storage_type': None, 'config': None, 'access_creds': { 'key': 'rrbx-test' } } } for (name, wdata) in wallets.items(): try: wdata['wallet'] = await w_mgr.create({ 'id': name, 'seed': wdata['seed'] }) except ExtantWallet: wdata['wallet'] = w_mgr.get({'id': name}) finally: await wdata['wallet'].open() tan = TrusteeAnchor(wallets['trustee-anchor']['wallet'], pool) no_prox = rrbx_prox() san = OrgHubAnchor(wallets[WALLET_NAME]['wallet'], pool, rrbx=rrbx) if rrbx: await beep('external rev reg builder process on {}'.format(WALLET_NAME), 15) if rrbx_prox() != no_prox + 1: await RevRegBuilder.stop(WALLET_NAME) assert False, "External rev reg builder process did not start" async with OrgHubAnchor( wallets[WALLET_NAME]['wallet'], pool, rrbx=rrbx): # check for exactly 1 external rev reg builder process await beep('external rev reg builder process uniqueness test on {}'.format(WALLET_NAME), 5) if rrbx_prox() != no_prox + 1: await RevRegBuilder.stop(WALLET_NAME) assert False, "External rev reg builder process was not unique" assert pool.handle await tan.open() await san.open() # Publish anchor particulars to ledger if not yet present for an in (tan, san): if not json.loads(await tan.get_nym(an.did)): await tan.send_nym(an.did, an.verkey, an.wallet.name, an.least_role()) nyms = { 'tan': json.loads(await tan.get_nym(tan.did)), 'san': json.loads(await tan.get_nym(san.did)) } print('\n\n== 1 == nyms: {}'.format(ppjson(nyms))) for k in nyms: assert 'dest' in nyms[k] # Publish schema to ledger if not yet present; get from ledger S_ID = schema_id(san.did, 'tails_load', '{}.0'.format(int(time.time()))) S_KEY = schema_key(S_ID) schema_data = { 'name': schema_key(S_ID).name, 'version': schema_key(S_ID).version, 'attr_names': [ 'number', 'remainder' ] } try: await san.get_schema(S_KEY) # may exist (almost certainly not) except AbsentSchema: await san.send_schema(json.dumps(schema_data)) schema_json = await san.get_schema(S_KEY) schema = json.loads(schema_json) assert schema # should exist now print('\n\n== 2 == SCHEMA [{} v{}]: {}'.format(S_KEY.name, S_KEY.version, ppjson(schema))) # Setup link secret for creation of cred req or proof await san.create_link_secret('LinkSecret') # SRI anchor create, store, publish cred definitions to ledger; create cred offers await san.send_cred_def(S_ID, revo=True) cd_id = cred_def_id(S_KEY.origin_did, schema['seqNo'], pool.protocol) assert ((not Tails.unlinked(san.dir_tails)) and [f for f in Tails.links(san.dir_tails, san.did) if cd_id in f]) cred_def_json = await san.get_cred_def(cd_id) # ought to exist now cred_def = json.loads(cred_def_json) print('\n\n== 3.0 == Cred def [{} v{}]: {}'.format( S_KEY.name, S_KEY.version, ppjson(json.loads(cred_def_json)))) assert cred_def.get('schemaId', None) == str(schema['seqNo']) cred_offer_json = await san.create_cred_offer(schema['seqNo']) print('\n\n== 3.1 == Credential offer [{} v{}]: {}'.format( S_KEY.name, S_KEY.version, ppjson(cred_offer_json))) (cred_req_json, cred_req_metadata_json) = await san.create_cred_req(cred_offer_json, cd_id) print('\n\n== 4 == Credential request [{} v{}]: metadata {}, cred-req {}'.format( S_KEY.name, S_KEY.version, ppjson(cred_req_metadata_json), ppjson(cred_req_json))) assert json.loads(cred_req_json) # BC Reg anchor (as Issuer) issues creds and stores at HolderProver: get cred req, create cred, store cred CREDS = 4034 # enough to kick off rev reg on size 4096 and issue two creds in it: 1 needing set-rev-reg, 1 not print('\n\n== 5 == creating {} credentials'.format(CREDS)) swatch = Stopwatch(2) optima = {} # per rev-reg, fastest/slowest pairs for number in range(CREDS): swatch.mark() (cred_json, _) = await san.create_cred( cred_offer_json, cred_req_json, { 'number': str(number), 'remainder': str(number % 100) }) elapsed = swatch.mark() tag = rev_reg_id2tag(Tails.current_rev_reg_id(san.dir_tails, cd_id)) if tag not in optima: optima[tag] = (elapsed, elapsed) else: optima[tag] = (min(optima[tag][0], elapsed), max(optima[tag][1], elapsed)) print('.', end='', flush=True) if ((number + 1) % 100) == 0: print('{}: #{}: {:.2f}-{:.2f}s'.format(number + 1, tag, *optima[tag]), flush=True) assert json.loads(cred_json) print('{}: #{}: {:.2f}-{:.2f}s'.format(number + 1, tag, *optima[tag]), flush=True) print('\n\n== 6 == best, worst times by revocation registry: {}'.format(ppjson(optima))) assert (not rrbx) or (max(optima[tag][1] for tag in optima) < 4 * min(optima[tag][1] for tag in optima if int(tag) > 0)) # if waiting on rr beyond #0, sizes increase as 2^n await san.close() if rrbx: await RevRegBuilder.stop(WALLET_NAME) await tan.close() for (name, wdata) in wallets.items(): await wdata['wallet'].close() await pool.close()
async def create_cred(self, cred_offer_json, cred_req_json: str, cred_attrs: dict, rr_size: int = None) -> (str, str): """ Create credential as Issuer out of credential request and dict of key:value (raw, unencoded) entries for attributes. Return credential json, and if cred def supports revocation, credential revocation identifier. Raise WalletState for closed wallet. If the credential definition supports revocation, and the current revocation registry is full, the processing creates a new revocation registry en passant. Depending on the revocation registry size (by default starting at 64 and doubling iteratively through a maximum of 100000) and the revocation registry builder posture (see RevRegBuilder.__init__()), this operation may delay credential creation by several seconds. The use of an external revocation registry builder runs a parallel process, skirting this delay, but is more costly at initialization. :param cred_offer_json: credential offer json as created by Issuer :param cred_req_json: credential request json as created by HolderProver :param cred_attrs: dict mapping each attribute to its original value (the operation encodes it); e.g., :: { 'favourite_drink': 'martini', 'height': 180, 'last_visit_date': '2017-12-31', 'weaknesses': None } :param rr_size: size of new revocation registry (default as per RevRegBuilder.create_rev_reg()) if necessary :return: tuple with newly issued credential json, credential revocation identifier (if cred def supports revocation, None otherwise). """ LOGGER.debug( 'Issuer.create_cred >>> cred_offer_json: %s, cred_req_json: %s, cred_attrs: %s, rr_size: %s', cred_offer_json, cred_req_json, cred_attrs, rr_size) if not self.wallet.handle: LOGGER.debug('Issuer.create_cred <!< Wallet %s is closed', self.name) raise WalletState('Wallet {} is closed'.format(self.name)) cd_id = json.loads(cred_offer_json)['cred_def_id'] if not ok_cred_def_id(cd_id): LOGGER.debug('Issuer.create_cred <!< Bad cred def id %s', cd_id) raise BadIdentifier('Bad cred def id {}'.format(cd_id)) cred_def = json.loads( await self.get_cred_def(cd_id)) # ensure cred def is in cache if 'revocation' in cred_def['value']: with REVO_CACHE.lock: rr_id = Tails.current_rev_reg_id(self.dir_tails, cd_id) tails = REVO_CACHE[rr_id].tails assert tails # at (re)start, at cred def, Issuer sync_revoc_for_issue() sets this index in revo cache try: ( cred_json, cred_revoc_id, _ ) = await anoncreds.issuer_create_credential( # issue by default to rr self.wallet.handle, cred_offer_json, cred_req_json, json.dumps({ k: cred_attr_value(cred_attrs[k]) for k in cred_attrs }), rr_id, tails.reader_handle) rv = (cred_json, cred_revoc_id) except IndyError as x_indy: if x_indy.error_code == ErrorCode.AnoncredsRevocationRegistryFullError: (tag, rr_size_suggested) = Tails.next_tag( self.dir_tails, cd_id) rr_id = rev_reg_id(cd_id, tag) if self.rrbx: await self._set_rev_reg(rr_id, rr_size) else: await self.rrb.create_rev_reg( rr_id, rr_size or rr_size_suggested) await self._send_rev_reg_def(rr_id) REVO_CACHE[rr_id].tails = await Tails( self.dir_tails, cd_id).open() # symlink OK now return await self.create_cred(cred_offer_json, cred_req_json, cred_attrs) LOGGER.debug( 'Issuer.create_cred <!< cannot create cred, indy error code %s', x_indy.error_code) raise else: try: (cred_json, _, _) = await anoncreds.issuer_create_credential( self.wallet.handle, cred_offer_json, cred_req_json, json.dumps({ k: cred_attr_value(cred_attrs[k]) for k in cred_attrs }), None, None) rv = (cred_json, None) except IndyError as x_indy: LOGGER.debug( 'Issuer.create_cred <!< cannot create cred, indy error code %s', x_indy.error_code) raise LOGGER.debug('Issuer.create_cred <<< %s', rv) return rv