def associate(base_dir: str, rr_id: str, tails_hash: str) -> None: """ Create symbolic link to tails file named tails_hash for rev reg id rr_id. :param rr_id: rev reg id :param tails_hash: hash of tails file, serving as file name """ LOGGER.debug( 'Tails.associate >>> base_dir: %s, rr_id: %s, tails_hash: %s', base_dir, rr_id, tails_hash) if not ok_rev_reg_id(rr_id): LOGGER.debug('Tails.associate <!< Bad rev reg id %s', rr_id) raise BadIdentifier('Bad rev reg id {}'.format(rr_id)) if not Tails.ok_hash(tails_hash): LOGGER.debug('Tails.associate <!< Bad tails hash %s', tails_hash) raise BadIdentifier('Bad tails hash {}'.format(tails_hash)) cd_id = rev_reg_id2cred_def_id(rr_id) directory = join(base_dir, cd_id) cwd = getcwd() makedirs(directory, exist_ok=True) chdir(directory) symlink(tails_hash, rr_id) chdir(cwd) LOGGER.debug('Tails.associate <<<')
def canon_ref(did: str, ref: str, delimiter: str = None): """ Given a reference in a DID document, return it in its canonical form of a URI. :param did: DID acting as the identifier of the DID document :param ref: reference to canonicalize, either a DID or a fragment pointing to a location in the DID doc :param delimiter: delimiter character marking fragment (default '#') or introducing identifier (';') against DID resource """ if not ok_did(did): raise BadIdentifier('Bad DID {} cannot act as DID document identifier'.format(did)) if ok_did(ref): # e.g., LjgpST2rjsoxYegQDRm7EL return 'did:sov:{}'.format(did) if ok_did(resource(ref, delimiter)): # e.g., LjgpST2rjsoxYegQDRm7EL#keys-1 return 'did:sov:{}'.format(ref) if ref.startswith('did:sov:'): # e.g., did:sov:LjgpST2rjsoxYegQDRm7EL, did:sov:LjgpST2rjsoxYegQDRm7EL#3 rv = ref[8:] if ok_did(resource(rv, delimiter)): return ref raise BadIdentifier('Bad URI {} does not correspond to a sovrin DID'.format(ref)) if urlparse(ref).scheme: # e.g., https://example.com/messages/8377464 return ref return 'did:sov:{}{}{}'.format(did, delimiter if delimiter else '#', ref) # e.g., 3
async def send_nym(self, did: str, verkey: str = None, alias: str = None, role: Role = None) -> None: """ Send input anchor's cryptonym (including DID, verification key, plus optional alias and role) to the distributed ledger. Raise BadLedgerTxn on failure, BadIdentifier for bad DID, or BadRole for bad role. :param did: anchor DID to send to ledger :param verkey: optional anchor verification key :param alias: optional alias :param role: anchor role on the ledger (default value of USER) """ LOGGER.debug( 'AnchorSmith.send_nym >>> did: %s, verkey: %s, alias: %s, role: %s', did, verkey, alias, role) if not ok_did(did): LOGGER.debug('AnchorSmith.send_nym <!< Bad DID %s', did) raise BadIdentifier('Bad DID {}'.format(did)) req_json = await ledger.build_nym_request(self.did, did, verkey, alias, (role or Role.USER).token()) await self._sign_submit(req_json) LOGGER.debug('AnchorSmith.send_nym <<<')
async def get_nym(self, target_did: str = None) -> str: """ Get json cryptonym (including current verification key) for input (anchor) DID from ledger. Return empty production {} if the ledger has no such cryptonym. Raise BadLedgerTxn on failure. Raise WalletState if target DID is default (own DID) value but wallet does not have it (neither created nor opened since initialization). :param target_did: DID of cryptonym to fetch (default own DID) :return: cryptonym json """ LOGGER.debug('BaseAnchor.get_nym >>> target_did: %s', target_did) if target_did and not ok_did(target_did): LOGGER.debug('BaseAnchor.get_nym <!< Bad DID %s', target_did) raise BadIdentifier('Bad DID {}'.format(target_did)) if not (target_did or self.did): LOGGER.debug('BaseAnchor.get_nym <!< Bad wallet state: DID for %s unavailable', self.name) raise WalletState('Bad wallet state: DID for {} unavailable'.format(self.name)) rv = json.dumps({}) get_nym_req = await ledger.build_get_nym_request(self.did, target_did or self.did) resp_json = await self._submit(get_nym_req) data_json = (json.loads(resp_json))['result']['data'] # it's double-encoded on the ledger if data_json: rv = data_json LOGGER.debug('BaseAnchor.get_nym <<< %s', rv) return rv
def next_tag(base_dir: str, cd_id: str) -> (str, int): """ Return the next tag name available for a new rev reg id on input cred def id in base directory, and suggested size of associated rev reg. :param base_dir: base directory for tails files, thereafter split by cred def id :param cd_id: credential definition identifier of interest :return: stringified least non-negative integer not yet used in a rev reg id associated with a tails file in base directory, and recommendation for next size to use """ LOGGER.debug('Tails.next_tag >>> base_dir: %s, cd_id: %s', base_dir, cd_id) if not ok_cred_def_id(cd_id): LOGGER.debug('Tails.next_tag <!< Bad cred def id %s', cd_id) raise BadIdentifier('Bad cred def id {}'.format(cd_id)) # append [-1] first to max existing tags: next tag is '0' if no tags so far tag = 1 + max([ int(rev_reg_id2tag(basename(f))) for f in Tails.links(base_dir) if cd_id in basename(f) ] + [-1]) size = min(2**(tag + 6), Tails.MAX_SIZE) rv = (tag, size) LOGGER.debug('Tails.next_tag <<< %s', rv) return rv
def current_rev_reg_id(base_dir: str, cd_id: str) -> str: """ Return the current revocation registry identifier for input credential definition identifier, in input directory. Raise AbsentTails if no corresponding tails file, signifying no such revocation registry defined. :param base_dir: base directory for tails files, thereafter split by cred def id :param cd_id: credential definition identifier of interest :return: identifier for current revocation registry on input credential definition identifier """ LOGGER.debug('Tails.current_rev_reg_id >>> base_dir: %s, cd_id: %s', base_dir, cd_id) if not ok_cred_def_id(cd_id): LOGGER.debug('Tails.current_rev_reg_id <!< Bad cred def id %s', cd_id) raise BadIdentifier('Bad cred def id {}'.format(cd_id)) tags = [ int(rev_reg_id2tag(basename(f))) for f in Tails.links(base_dir) if cd_id in basename(f) ] if not tags: raise AbsentTails( 'No tails files present for cred def id {}'.format(cd_id)) rv = rev_reg_id(cd_id, str(max(tags))) # ensure 10 > 9, not '9' > '10' LOGGER.debug('Tails.current_rev_reg_id <<< %s', rv) return rv
def links(base_dir: str, issuer_did: str = None) -> set: """ Return set of all paths to symbolic links (rev reg ids) associating their respective tails files, in specified base tails directory, on input issuer DID if specified. :param base_dir: base directory for tails files, thereafter split by cred def id :param issuer_did: issuer DID of interest :return: set of paths to symbolic links associating tails files """ LOGGER.debug('Tails.links >>> base_dir: %s, issuer_did: %s', base_dir, issuer_did) if issuer_did and not ok_did(issuer_did): LOGGER.debug('Tails.links <!< Bad DID %s', issuer_did) raise BadIdentifier('Bad DID {}'.format(issuer_did)) rv = { join(dp, f) for dp, dn, fn in walk(base_dir) for f in fn if islink(join(dp, f)) and (not issuer_did or ( ok_rev_reg_id(f) and f.startswith('{}:4:'.format(issuer_did)))) } LOGGER.debug('Tails.links <<< %s', rv) return rv
async def _set_cred_def(cd_id: str) -> None: nonlocal cd_id2cred_def if not ok_cred_def_id(cd_id): LOGGER.debug('Verifier.verify_proof <!< Bad cred def id %s', cd_id) raise BadIdentifier('Bad cred def id {}'.format(cd_id)) if cd_id not in cd_id2cred_def: cd_id2cred_def[cd_id] = json.loads(await self.get_cred_def(cd_id)) # add to cache en passant
async def get_did_endpoint(self, remote_did: str) -> EndpointInfo: """ Return endpoint info for remote DID. Raise BadIdentifier for bad remote DID. Raise WalletState if bypassing cache but wallet is closed. Raise AbsentRecord for no such endpoint. :param remote_did: pairwise remote DID :return: endpoint and (transport) verification key as EndpointInfo """ LOGGER.debug('BaseAnchor.get_did_endpoint >>> remote_did: %s', remote_did) if not ok_did(remote_did): LOGGER.debug('BaseAnchor.get_did_endpoint <!< Bad DID %s', remote_did) raise BadIdentifier('Bad DID {}'.format(remote_did)) pairwise_info = (await self.wallet.get_pairwise(remote_did)).get( remote_did, None) if not (pairwise_info and 'did_endpoint' in pairwise_info.metadata): LOGGER.debug( 'BaseAnchor.get_did_endpoint <!< No endpoint for remote DID %s', remote_did) raise AbsentRecord( 'No endpoint for remote DID {}'.format(remote_did)) rv = EndpointInfo(pairwise_info.metadata['did_endpoint'], pairwise_info.their_verkey) LOGGER.debug('BaseAnchor.get_did_endpoint <<< %s', rv) return rv
async def send_nym(self, did: str, verkey: str, alias: str = None, role: str = None) -> None: """ Send input anchor's cryptonym (including DID, verification key, plus optional alias and role) to the distributed ledger. Raise BadLedgerTxn on failure. :param did: anchor DID to send to ledger :param verkey: anchor verification key :param alias: optional alias :param role: anchor role on the ledger; specify one of 'TRUSTEE', 'STEWARD', 'TRUST_ANCHOR', or else '' to reset role """ LOGGER.debug( 'AnchorSmith.send_nym >>> did: %s, verkey: %s, alias: %s, role: %s', did, verkey, alias, role or '') if not ok_did(did): LOGGER.debug('AnchorSmith <!< Bad DID %s', did) raise BadIdentifier('Bad DID {}'.format(did)) req_json = await ledger.build_nym_request(self.did, did, verkey, alias, role or '') await self._sign_submit(req_json) LOGGER.debug('AnchorSmith.send_nym <<<')
async def get_nym(self, did: str) -> str: """ Get json cryptonym (including current verification key) for input (anchor) DID from ledger. Raise BadLedgerTxn on failure. :param did: DID of cryptonym to fetch :return: cryptonym json """ LOGGER.debug('_BaseAnchor.get_nym >>> did: %s', did) if not ok_did(did): LOGGER.debug('_BaseAnchor._get_nym <!< Bad DID %s', did) raise BadIdentifier('Bad DID {}'.format(did)) rv = json.dumps({}) get_nym_req = await ledger.build_get_nym_request(self.did, did) resp_json = await self._submit(get_nym_req) data_json = (json.loads(resp_json) )['result']['data'] # it's double-encoded on the ledger if data_json: rv = data_json LOGGER.debug('_BaseAnchor.get_nym <<< %s', rv) return rv
def links(base_dir: str, issuer_did: str = None) -> set: """ Return set of all paths to symbolic links (rev reg ids) associating their respective tails files, in specified base tails directory recursively (omitting the .hopper subdirectory), on input issuer DID if specified. :param base_dir: base directory for tails files, thereafter split by cred def id :param issuer_did: issuer DID of interest :return: set of paths to symbolic links associating tails files """ LOGGER.debug('Tails.links >>> base_dir: %s, issuer_did: %s', base_dir, issuer_did) if issuer_did and not ok_did(issuer_did): LOGGER.debug('Tails.links <!< Bad DID %s', issuer_did) raise BadIdentifier('Bad DID {}'.format(issuer_did)) rv = set() for dir_path, dir_names, file_names in walk(base_dir, topdown=True): dir_names[:] = [d for d in dir_names if not d.startswith('.')] for file_name in file_names: if islink(join(dir_path, file_name)) and ( not issuer_did or ok_rev_reg_id(file_name, issuer_did)): rv.add(join(dir_path, file_name)) LOGGER.debug('Tails.links <<< %s', rv) return rv
async def _create_rev_reg(self, rr_id: str, rr_size: int = None) -> None: """ Create revocation registry and new tails file (and association to corresponding revocation registry definition via symbolic link) for input revocation registry identifier. :param rr_id: revocation registry identifier :param rr_size: revocation registry size (defaults to 256) """ LOGGER.debug('Issuer._create_rev_reg >>> rr_id: %s, rr_size: %s', rr_id, rr_size) if not ok_rev_reg_id(rr_id): LOGGER.debug('Issuer._create_rev_reg <!< Bad rev reg id %s', rr_id) raise BadIdentifier('Bad rev reg id {}'.format(rr_id)) rr_size = rr_size or 256 (cd_id, tag) = rev_reg_id2cred_def_id_tag(rr_id) LOGGER.info( 'Creating revocation registry (capacity %s) for rev reg id %s', rr_size, rr_id) tails_writer_handle = await blob_storage.open_writer( 'default', json.dumps({ 'base_dir': Tails.dir(self._dir_tails, rr_id), 'uri_pattern': '' })) apriori = Tails.unlinked(self._dir_tails) (rr_id, rrd_json, rre_json) = await anoncreds.issuer_create_and_store_revoc_reg( self.wallet.handle, self.did, 'CL_ACCUM', tag, cd_id, json.dumps({ 'max_cred_num': rr_size, 'issuance_type': 'ISSUANCE_ON_DEMAND' }), tails_writer_handle) delta = Tails.unlinked(self._dir_tails) - apriori if len(delta) != 1: LOGGER.debug( 'Issuer._create_rev_reg: <!< Could not create tails file for rev reg id: %s', rr_id) raise CorruptTails( 'Could not create tails file for rev reg id {}'.format(rr_id)) tails_hash = basename(delta.pop()) Tails.associate(self._dir_tails, rr_id, tails_hash) with REVO_CACHE.lock: rrd_req_json = await ledger.build_revoc_reg_def_request( self.did, rrd_json) await self._sign_submit(rrd_req_json) await self._get_rev_reg_def(rr_id) # add to cache en passant rre_req_json = await ledger.build_revoc_reg_entry_request( self.did, rr_id, 'CL_ACCUM', rre_json) await self._sign_submit(rre_req_json) LOGGER.debug('Issuer._create_rev_reg <<<')
async def revoke_cred(self, rr_id: str, cr_id) -> int: """ Revoke credential that input revocation registry identifier and credential revocation identifier specify. Return (epoch seconds) time of revocation. Raise AbsentTails if no tails file is available for input revocation registry identifier. Raise WalletState for closed wallet. Raise BadRevocation if issuer cannot revoke specified credential for any other reason (e.g., did not issue it, already revoked it). :param rr_id: revocation registry identifier :param cr_id: credential revocation identifier :return: time of revocation, in epoch seconds """ LOGGER.debug('Issuer.revoke_cred >>> rr_id: %s, cr_id: %s', rr_id, cr_id) if not self.wallet.handle: LOGGER.debug('Issuer.revoke_cred <!< Wallet %s is closed', self.wallet.name) raise WalletState('Wallet {} is closed'.format(self.wallet.name)) if not ok_rev_reg_id(rr_id): LOGGER.debug('Issuer.revoke_cred <!< Bad rev reg id %s', rr_id) raise BadIdentifier('Bad rev reg id {}'.format(rr_id)) tails_reader_handle = (await Tails( self._dir_tails, *rev_reg_id2cred_def_id_tag(rr_id)).open()).reader_handle try: rrdelta_json = await anoncreds.issuer_revoke_credential( self.wallet.handle, tails_reader_handle, rr_id, cr_id) except IndyError as x_indy: LOGGER.debug( 'Issuer.revoke_cred <!< Could not revoke revoc reg id %s, cred rev id %s: indy error code %s', rr_id, cr_id, x_indy.error_code) raise BadRevocation( 'Could not revoke revoc reg id {}, cred rev id {}: indy error code {}'.format( rr_id, cr_id, x_indy.error_code)) rr_ent_req_json = await ledger.build_revoc_reg_entry_request(self.did, rr_id, 'CL_ACCUM', rrdelta_json) resp_json = await self._sign_submit(rr_ent_req_json) # raises AbsentPool or ClosedPool if applicable resp = json.loads(resp_json) rv = self.pool.protocol.txn2epoch(resp) LOGGER.debug('Issuer.revoke_cred <<< %s', rv) return rv
async def get_cred_def(self, cd_id: str) -> str: """ Get credential definition from ledger by its identifier. Raise AbsentCredDef for no such credential definition, logging any error condition and raising BadLedgerTxn on bad request. Raise ClosedPool if cred def not in cache and pool is closed. Retrieve the credential definition from the anchor's credential definition cache if it has it; cache it en passant if it does not (and if there is a corresponding credential definition on the ledger). :param cd_id: (credential definition) identifier string ('<issuer-did>:3:CL:<schema-seq-no>:<tag>') :return: credential definition json as retrieved from ledger, empty production for no such cred def """ LOGGER.debug('_BaseAnchor.get_cred_def >>> cd_id: %s', cd_id) if not ok_cred_def_id(cd_id): LOGGER.debug('_BaseAnchor._get_cred_def <!< Bad cred def id %s', cd_id) raise BadIdentifier('Bad cred def id {}'.format(cd_id)) rv_json = json.dumps({}) with CRED_DEF_CACHE.lock: if cd_id in CRED_DEF_CACHE: LOGGER.info( '_BaseAnchor.get_cred_def: got cred def for %s from cache', cd_id) rv_json = json.dumps(CRED_DEF_CACHE[cd_id]) LOGGER.debug('_BaseAnchor.get_cred_def <<< %s', rv_json) return rv_json req_json = await ledger.build_get_cred_def_request(self.did, cd_id) resp_json = await self._submit(req_json) resp = json.loads(resp_json) if not ('result' in resp and resp['result'].get('data', None)): LOGGER.debug( '_BaseAnchor.get_cred_def: <!< no cred def exists on %s', cd_id) raise AbsentCredDef('No cred def exists on {}'.format(cd_id)) try: (_, rv_json) = await ledger.parse_get_cred_def_response(resp_json) except IndyError: # ledger replied, but there is no such cred def LOGGER.debug( '_BaseAnchor.get_cred_def: <!< no cred def exists on %s', cd_id) raise AbsentCredDef('No cred def exists on {}'.format(cd_id)) CRED_DEF_CACHE[cd_id] = json.loads(rv_json) LOGGER.info( '_BaseAnchor.get_cred_def: got cred def %s from ledger', cd_id) LOGGER.debug('_BaseAnchor.get_cred_def <<< %s', rv_json) return rv_json
async def _build_rr_state_json(self, rr_id: str, timestamp: int) -> (str, int): """ Build rev reg state json at a given requested timestamp. Return rev reg state json and its transaction time on the distributed ledger, with upper bound at input timestamp of interest. Raise AbsentRevReg if no revocation registry exists on input rev reg id, or BadRevStateTime if requested timestamp predates revocation registry creation. :param rr_id: rev reg id :param timestamp: timestamp of interest (epoch seconds) :return: rev reg state json and ledger timestamp (epoch seconds) """ LOGGER.debug( '_Verifier._build_rr_state_json >>> rr_id: %s, timestamp: %s', rr_id, timestamp) if not ok_rev_reg_id(rr_id): LOGGER.debug('Verifier._build_rr_state_json <!< Bad rev reg id %s', rr_id) raise BadIdentifier('Bad rev reg id {}'.format(rr_id)) rr_json = None ledger_timestamp = None get_rr_req_json = await ledger.build_get_revoc_reg_request( self.did, rr_id, timestamp) resp_json = await self._submit(get_rr_req_json) resp = json.loads(resp_json) if resp.get('result', {}).get( 'data', None) and resp['result']['data'].get('value', None): # timestamp at or beyond rev reg creation, carry on try: (_, rr_json, ledger_timestamp ) = await ledger.parse_get_revoc_reg_response(resp_json) except IndyError: # ledger replied, but there is no such rev reg available LOGGER.debug( 'Verifier._build_rr_state_json <!< no rev reg exists on %s', rr_id) raise AbsentRevReg('No rev reg exists on {}'.format(rr_id)) else: LOGGER.debug( '_Verifier._build_rr_state_json <!< Rev reg %s created after asked-for time %s', rr_id, timestamp) raise BadRevStateTime( 'Rev reg {} created after asked-for time {}'.format( rr_id, timestamp)) rv = (rr_json, ledger_timestamp) LOGGER.debug('_Verifier._build_rr_state_json <<< %s', rv) return rv
async def _get_rev_reg_def(self, rr_id: str) -> str: """ Get revocation registry definition from ledger by its identifier. Raise AbsentRevReg for no such revocation registry, logging any error condition and raising BadLedgerTxn on bad request. Retrieve the revocation registry definition from the anchor's revocation cache if it has it; cache it en passant if it does not (and such a revocation registry definition exists on the ledger). :param rr_id: (revocation registry) identifier string, of the format '<issuer-did>:4:<issuer-did>:3:CL:<schema-seq-no>:<tag>:CL_ACCUM:<tag>' :return: revocation registry definition json as retrieved from ledger """ LOGGER.debug('_BaseAnchor._get_rev_reg_def >>> rr_id: %s', rr_id) if not ok_rev_reg_id(rr_id): LOGGER.debug('_BaseAnchor._get_rev_reg_def <!< Bad rev reg id %s', rr_id) raise BadIdentifier('Bad rev reg id {}'.format(rr_id)) rv_json = json.dumps({}) with REVO_CACHE.lock: revo_cache_entry = REVO_CACHE.get(rr_id, None) rr_def = revo_cache_entry.rev_reg_def if revo_cache_entry else None if rr_def: LOGGER.info( '_BaseAnchor._get_rev_reg_def: rev reg def for %s from cache', rr_id) rv_json = json.dumps(rr_def) else: get_rrd_req_json = await ledger.build_get_revoc_reg_def_request( self.did, rr_id) resp_json = await self._submit(get_rrd_req_json) try: (_, rv_json) = await ledger.parse_get_revoc_reg_def_response( resp_json) rr_def = json.loads(rv_json) except IndyError: # ledger replied, but there is no such rev reg LOGGER.debug( '_BaseAnchor._get_rev_reg_def: <!< no rev reg exists on %s', rr_id) raise AbsentRevReg('No rev reg exists on {}'.format(rr_id)) if revo_cache_entry is None: REVO_CACHE[rr_id] = RevoCacheEntry(rr_def, None) else: REVO_CACHE[rr_id].rev_reg_def = rr_def LOGGER.debug('_BaseAnchor._get_rev_reg_def <<< %s', rv_json) return rv_json
def rev_reg_id2cred_def_id(rr_id: str) -> str: """ Given a revocation registry identifier, return its corresponding credential definition identifier. Raise BadIdentifier if input is not a revocation registry identifier. :param rr_id: revocation registry identifier :return: credential definition identifier """ if ok_rev_reg_id(rr_id): return ':'.join(rr_id.split(':')[2:-2]) # rev reg id comprises (prefixes):<cred_def_id>:(suffixes) raise BadIdentifier('Bad revocation registry identifier {}'.format(rr_id))
def __init__(self, base_dir: str, cd_id: str, tag: str = None): """ Initialize programmatic association between revocation registry identifier (on credential definition on input identifier plus tag, default most recent), and tails file, via symbolic link. Raise AbsentTails if (rev reg id) symbolic link or (tails hash) tails file not present. :param base_dir: base directory for tails files, thereafter split by cred def id :param cd_id: credential definition identifier of interest :param tag: revocation registry identifier tag of interest, default to most recent """ LOGGER.debug('Issuer.__init__ >>> base_dir: %s, cd_id: %s, tag: %s', base_dir, cd_id, tag) if not ok_cred_def_id(cd_id): LOGGER.debug('Tails.__init__ <!< Bad cred def id %s', cd_id) raise BadIdentifier('Bad cred def id {}'.format(cd_id)) if tag is None: self._rr_id = Tails.current_rev_reg_id(base_dir, cd_id) else: # including tag == 0 self._rr_id = rev_reg_id(cd_id, tag) if self._rr_id not in [basename(f) for f in Tails.links(base_dir)]: LOGGER.debug( 'Tails.__init__ <!< No tails file present for cred def id %s on rev reg id tag %s', cd_id, tag) raise AbsentTails( 'No tails file present for cred def id {} on rev reg id tag {}' .format(cd_id, tag)) path_link = join(Tails.dir(base_dir, self._rr_id), self._rr_id) if not islink(path_link): raise AbsentTails( 'No symbolic link present at {} for rev reg id {}'.format( path_link, self._rr_id)) path_tails = Tails.linked(base_dir, self._rr_id) if not isfile(path_tails): raise AbsentTails( 'No tails file present at {} for rev reg id {}'.format( path_tails, self._rr_id)) self._tails_config_json = json.dumps({ 'base_dir': dirname(path_tails), 'uri_pattern': '', 'file': basename(path_tails) }) self._reader_handle = None LOGGER.debug('Tails.__init__ <<<')
def cred_def_id2seq_no(cd_id: str) -> int: """ Given a credential definition identifier, return its schema sequence number. Raise BadIdentifier on input that is not a credential definition identifier. :param cd_id: credential definition identifier :return: sequence number """ if ok_cred_def_id(cd_id): return int(cd_id.split(':')[3]) # sequence number is token at 0-based position 3 raise BadIdentifier('Bad credential definition identifier {}'.format(cd_id))
async def _sync_revoc_for_issue(self, rr_id: str, rr_size: int = None) -> None: """ Create revocation registry if need be for input revocation registry identifier; open and cache tails file reader. :param rr_id: revocation registry identifier :param rr_size: if new revocation registry necessary, its size (default as per RevRegBuilder.create_rev_reg()) """ LOGGER.debug('Issuer._sync_revoc_for_issue >>> rr_id: %s, rr_size: %s', rr_id, rr_size) if not ok_rev_reg_id(rr_id): LOGGER.debug('Issuer._sync_revoc_for_issue <!< Bad rev reg id %s', rr_id) raise BadIdentifier('Bad rev reg id {}'.format(rr_id)) (cd_id, tag) = rev_reg_id2cred_def_id_tag(rr_id) try: await self.get_cred_def(cd_id) except AbsentCredDef: LOGGER.debug( 'Issuer._sync_revoc_for_issue <!< tails tree %s may be for another ledger; no cred def found on %s', self.dir_tails, cd_id) raise AbsentCredDef( 'Tails tree {} may be for another ledger; no cred def found on {}' .format(self.dir_tails, cd_id)) with REVO_CACHE.lock: revo_cache_entry = REVO_CACHE.get(rr_id, None) tails = None if revo_cache_entry is None else revo_cache_entry.tails if tails is None: # it's a new revocation registry, or not yet set in cache try: tails = await Tails(self.dir_tails, cd_id, tag).open() except AbsentTails: # it's a new revocation registry if self.rrbx: await self._set_rev_reg(rr_id, rr_size) else: await self.rrb.create_rev_reg(rr_id, rr_size) await self._send_rev_reg_def(rr_id) tails = await Tails(self.dir_tails, cd_id, tag).open() # symlink should exist now if revo_cache_entry is None: REVO_CACHE[rr_id] = RevoCacheEntry(None, tails) else: REVO_CACHE[rr_id].tails = tails LOGGER.debug('Issuer._sync_revoc_for_issue <<<')
def dflt_interval(self, cd_id: str) -> (int, int): """ Return default non-revocation interval from latest 'to' times on delta frames of revocation cache entries on indices stemming from input cred def id. Compute the 'from'/'to' values as the earliest/latest 'to' values of all cached delta frames on all rev reg ids stemming from the input cred def id. E.g., on frames for rev-reg-0: -[xx]---[xxxx]-[x]---[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]--> time rev-reg-1: ----------------------[xxxx]----[xxx]---[xxxxxxxxxxxxxxxxxxxx]---------> time rev-reg-2: -------------------------------------------[xx]-----[xxxx]-----[xxxxx]-> time rev-reg-3: -----------------------------------------------------------[xxxxxxxx]--> time return the most recent interval covering all matching revocation registries in the cache; i.e.,: interval: -------------------------------------------------------------[*******]-> time Raise CacheIndex if there are no matching entries. :param cd_id: cred def identifier to match :return: default non-revocation interval as 2-tuple (fro, to) """ LOGGER.debug('RevocationCache.dflt_interval >>>') if not ok_cred_def_id(cd_id): LOGGER.debug( 'RevocationCache.dflt_interval <!< Bad cred def id %s', cd_id) raise BadIdentifier('Bad cred def id {}'.format(cd_id)) fro = None to = None for rr_id in self: if cd_id != rev_reg_id2cred_def_id(rr_id): continue entry = self[rr_id] if entry.rr_delta_frames: to = max(entry.rr_delta_frames, key=lambda f: f.to).to fro = min(fro or to, to) if not (fro and to): LOGGER.debug( 'RevocationCache.dflt_interval <!< No data for default non-revoc interval on cred def id %s', cd_id) raise CacheIndex( 'No data for default non-revoc interval on cred def id {}'. format(cd_id)) rv = (fro, to) LOGGER.debug('RevocationCache.dflt_interval <<< %s', rv) return rv
async def _set_schema(s_id: str) -> None: nonlocal s_id2schema if not ok_schema_id(s_id): LOGGER.debug('Verifier.verify_proof <!< Bad schema id %s', s_id) raise BadIdentifier('Bad schema id {}'.format(s_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
async def _set_rev_reg_def(rr_id: str) -> bool: """ Return true to continue to timestamp setting, false to short-circuit """ nonlocal rr_id2rr_def if not rr_id: return False if not ok_rev_reg_id(rr_id): LOGGER.debug('Verifier.verify_proof <!< Bad rev reg id %s', rr_id) raise BadIdentifier('Bad rev reg id {}'.format(rr_id)) if rr_id not in rr_id2rr_def: rr_id2rr_def[rr_id] = json.loads(await self.get_rev_reg_def(rr_id)) return True
def rev_reg_id2cred_def_id_tag(rr_id: str) -> (str, str): """ Given a revocation registry identifier, return its corresponding credential definition identifier and (stringified int) tag. Raise BadIdentifier if input is not a revocation registry identifier. :param rr_id: revocation registry identifier :return: credential definition identifier and tag """ if ok_rev_reg_id(rr_id): return ( ':'.join(rr_id.split(':')[2:-2]), # rev reg id comprises (prefixes):<cred_def_id>:(suffixes) str(rr_id.split(':')[-1]) # tag is last token ) raise BadIdentifier('Bad revocation registry identifier {}'.format(rr_id))
async def delete_pairwise(self, their_did: str) -> None: """ Remove a pairwise DID record by its remote DID. Silently return if no such record is present. Raise WalletState for closed wallet, or BadIdentifier for invalid pairwise DID. :param their_did: remote DID marking pairwise DID to remove """ LOGGER.debug('Wallet.delete_pairwise >>> their_did: %s', their_did) if not ok_did(their_did): LOGGER.debug('Wallet.delete_pairwise <!< Bad DID %s', their_did) raise BadIdentifier('Bad DID {}'.format(their_did)) await self.delete_non_secret(TYPE_PAIRWISE, their_did) LOGGER.debug('Wallet.delete_pairwise <<<')
def canon_did(uri: str) -> str: """ Convert a URI into a DID if need be, left-stripping 'did:sov:' if present. Return input if already a DID. Raise BadIdentifier for invalid input. :param uri: input URI or DID :return: corresponding DID """ if ok_did(uri): return uri if uri.startswith('did:sov:'): rv = uri[8:] if ok_did(rv): return rv raise BadIdentifier('Bad specification {} does not correspond to a sovrin DID'.format(uri))
def path_tails(self, rr_id: str) -> str: """ Return path to tails file for input revocation registry identifier. :param rr_id: revocation registry identifier of interest :return: path to tails file for input revocation registry identifier """ LOGGER.debug('Issuer.path_tails >>>') if not ok_rev_reg_id(rr_id): LOGGER.debug('Issuer.path_tails <!< Bad rev reg id %s', rr_id) raise BadIdentifier('Bad rev reg id {}'.format(rr_id)) rv = Tails.linked(self._dir_tails, rr_id) LOGGER.debug('Issuer.path_tails <<< %s', rv) return rv
def dir(base_dir: str, rr_id: str) -> str: """ Return correct subdirectory of input base dir for artifacts corresponding to input rev reg id. :param base_dir: base directory for tails files, thereafter split by cred def id :param rr_id: rev reg id """ LOGGER.debug('Tails.dir >>> base_dir: %s, rr_id: %s', base_dir, rr_id) if not ok_rev_reg_id(rr_id): LOGGER.debug('Tails.dir <!< Bad rev reg id %s', rr_id) raise BadIdentifier('Bad rev reg id {}'.format(rr_id)) rv = join(base_dir, rev_reg_id2cred_def_id(rr_id)) LOGGER.debug('Tails.dir <<< %s', rv) return rv
def cred_def_id2schema_seq_no_or_id(cd_id: str) -> Union[str, int]: """ Given a credential definition identifier, return its schema sequence number, or its schema identifier if the credential definition identifier is long-form; i.e., it has a schema identifier instead of a sequence number. Raise BadIdentifier on input that is not a credential definition identifier. :param cd_id: credential definition identifier :return: sequence number """ if ok_cred_def_id(cd_id): tokens = cd_id.split(':') if len(tokens) == 5: return int(tokens[3]) # seq no is token at 0-based position 3 if len(tokens) == 8: return ':'.join(tokens[3:7]) # schema id spans 0-based positions 3 through 6 inclusively raise BadIdentifier('Bad credential definition identifier {}'.format(cd_id))