async def _set_rev_reg(self, rr_id: str, rr_size: int) -> None: """ Move precomputed revocation registry data from hopper into place within tails directory. :param rr_id: revocation registry identifier :param rr_size: revocation registry size, in case creation required """ LOGGER.debug('Issuer._set_rev_reg >>> rr_id: %s, rr_size: %s', rr_id, rr_size) assert self._rrbx dir_hopper_rr_id = join(self._dir_tails_hopper, rr_id) while Tails.linked(dir_hopper_rr_id, rr_id) is None: await asyncio.sleep(1) await self._send_rev_reg_def(rr_id) cd_id = rev_reg_id2cred_def_id(rr_id) (next_tag, rr_size_suggested) = Tails.next_tag(self._dir_tails, cd_id) rr_id = rev_reg_id(cd_id, next_tag) try: makedirs(join(self._dir_tails_sentinel, rr_id), exist_ok=False) except FileExistsError: LOGGER.warning('Rev reg %s construction already in progress', rr_id) else: open(join(self._dir_tails_sentinel, rr_id, '.{}'.format(rr_size or rr_size_suggested)), 'w').close() LOGGER.debug('Issuer._set_rev_reg <<<')
async def list_tails(request: Request, ident: str) -> HTTPResponse: """ List tails files by corresponding rev reg ids: all, by rev reg id, by cred def id, or by issuer DID. :param request: Sanic request structure :param ident: 'all' for no filter; rev reg id, cred def id, or issuer DID to filter by any such identifier :return: HTTP response with JSON array of rev reg ids corresponding to available tails files """ rv = [] dir_tails = join(dirname(dirname(realpath(__file__))), 'tails') if ident == 'all': # list everything: 'all' is not valid base58 so it can't be any case below rv = [basename(link) for link in Tails.links(dir_tails)] elif ok_rev_reg_id(ident): # it's a rev reg id if Tails.linked(dir_tails, ident): rv = [ident] elif ok_cred_def_id(ident): # it's a cred def id (starts with issuer DID) rv = [basename(link) for link in Tails.links(dir_tails, ident.split(':')[0]) if rev_reg_id2cred_def_id(basename(link)) == ident] elif ok_did(ident): # it's an issuer DID rv = [basename(link) for link in Tails.links(dir_tails, ident)] else: LOGGER.error('Token %s is not a valid specifier for tails files', ident) return response.text('Token {} is not a valid specifier for tails files'.format(ident), status=400) LOGGER.info('Fulfilling GET request listing tails files on filter %s', ident) return response.json(rv)
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 <<<')
async def list_tails(request, ident): """ List tails files by corresponding rev reg ids: all, by rev reg id, by cred def id, or by issuer DID. :param request: Sanic request structure :param ident: 'all' for no filter; rev reg id, cred def id, or issuer DID to filter by any such identifier :return: JSON array of rev reg ids corresponding to tails files available """ rv = [] dir_tails = pjoin(dirname(dirname(abspath(__file__))), 'tails') if ident == 'all': # list everything: 'all' is not valid base58 so it can't be any case below rv = [basename(link) for link in Tails.links(dir_tails)] elif ok_rev_reg_id(ident) and Tails.linked(dir_tails, ident): # it's a rev reg id rv = [ident] elif ok_cred_def_id(ident): # it's a cred def id (starts with issuer DID) rv = [basename(link) for link in Tails.links(dir_tails, ident.split(':')[0]) if rev_reg_id2cred_def_id(basename(link)) == ident] elif ok_did(ident): # it's an issuer DID rv = [basename(link) for link in Tails.links(dir_tails, ident)] else: LOGGER.error("Token %s must be 'all', rev reg id, cred def id, or issuer DID", ident) raise InvalidUsage("Token {} must be 'all', rev reg id, cred def id, or issuer DID".format(ident)) LOGGER.info('Fulfilling GET request listing tails files on filter %s', ident) return response.json(rv)
def dir_tails_target(self, rr_id) -> str: """ Return target directory for revocation registry and tails file production. :param rr_id: revocation registry identifier :return: tails target directory """ return join(self.dir_tails_top(rr_id), rev_reg_id2cred_def_id(rr_id))
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
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 linked(base_dir: str, rr_id: str) -> str: """ Get, from the specified directory, the path to the tails file associated with the input revocation registry identifier, or None for no such file. :param base_dir: base directory for tails files, thereafter split by cred def id :param rr_id: rev reg id :return: (stringified) path to tails file of interest, or None for no such file. """ LOGGER.debug('Tails.linked >>> base_dir: %s, rr_id: %s', base_dir, rr_id) if not ok_rev_reg_id(rr_id): LOGGER.debug('Tails.linked <!< Bad rev reg id %s', rr_id) raise BadIdentifier('Bad rev reg id {}'.format(rr_id)) cd_id = rev_reg_id2cred_def_id(rr_id) link = join(base_dir, cd_id, rr_id) rv = join(base_dir, cd_id, readlink(link)) if islink(link) else None LOGGER.debug('Tails.linked <<< %s', rv) return rv
async def _set_rev_reg(self, rr_id: str, rr_size: int) -> None: """ Move precomputed revocation registry data from hopper into place within tails directory. :param rr_id: revocation registry identifier :param rr_size: revocation registry size, in case creation required """ LOGGER.debug('Issuer._set_rev_reg >>> rr_id: %s, rr_size: %s', rr_id, rr_size) assert self.rrbx dir_hopper_rr_id = join(self.rrb.dir_tails_hopper, rr_id) while Tails.linked(dir_hopper_rr_id, rr_id) is None: await asyncio.sleep(1) await self._send_rev_reg_def(rr_id) cd_id = rev_reg_id2cred_def_id(rr_id) (next_tag, rr_size_suggested) = Tails.next_tag(self.dir_tails, cd_id) rr_id = rev_reg_id(cd_id, next_tag) self.rrb.mark_in_progress(rr_id, rr_size or rr_size_suggested) LOGGER.debug('Issuer._set_rev_reg <<<')
async def test_ids(): print(Ink.YELLOW('\n\n== Testing Identifier Checks ==')) assert ok_wallet_reft('49ad0727-8663-45ae-a115-12b09860f9c6') assert not ok_wallet_reft('Q4zqM7aXqm7gDQkUVLng9I') assert not ok_wallet_reft('49ad0727-45ae-a115-12b09860f9c6') print('\n\n== 1 == Wallet referent identifier checks pass OK') assert ok_did('Q4zqM7aXqm7gDQkUVLng9h') assert not ok_did('Q4zqM7aXqm7gDQkUVLng9I') # 'I' not a base58 char assert not ok_did('Q4zqM7aXqm7gDQkUVLng') # too short print('\n\n== 2 == Distributed identifier checks pass OK') for value in (None, 'TRUSTEE', 'STEWARD', 'TRUST_ANCHOR', ''): assert ok_role(value) for value in (123, 'TRUSTY', 'STEW', 'ANCHOR', ' '): assert not ok_role(value) print('\n\n== 3 == Role identifier checks pass OK') assert Tails.ok_hash('Q4zqM7aXqm7gDQkUVLng9hQ4zqM7aXqm7gDQkUVLng9h') assert Tails.ok_hash('Q4zqM7aXqm7gDQkUVLng9hQ4zqM7aXqm7gDQkUVLng') assert not Tails.ok_hash('Q4zqM7aXqm7gDQkUVLng9h') assert not Tails.ok_hash('Q4zqM7aXqm7gDQkUVLng9hQ4zqM7aXqm7gDQkUVLng9hx') assert not Tails.ok_hash('Q4zqM7aXqm7gDQkUVLng9hQ4zqM7aXqm7gDQkUVLng90') print('\n\n== 4 == Tails hash identifier checks pass OK') assert ok_schema_id('Q4zqM7aXqm7gDQkUVLng9h:2:bc-reg:1.0') assert not ok_schema_id('Q4zqM7aXqm7gDQkUVLng9h:3:bc-reg:1.0') assert not ok_schema_id('Q4zqM7aXqm7gDQkUVLng9h::bc-reg:1.0') assert not ok_schema_id('Q4zqM7aXqm7gDQkUVLng9h:bc-reg:1.0') assert not ok_schema_id('Q4zqM7aXqm7gDQkUVLng9h:2:1.0') assert not ok_schema_id('Q4zqM7aXqm7gDQkUVLng9h:2::1.0') assert not ok_schema_id('Q4zqM7aXqm7gDQkUVLng9h:2:bc-reg:') assert not ok_schema_id('Q4zqM7aXqm7gDQkUVLng9h:2:bc-reg:1.0a') assert not ok_schema_id( 'Q4zqM7aXqm7gDQkUVLng9I:2:bc-reg:1.0') # I is not in base58 print('\n\n== 5 == Schema identifier checks pass OK') assert ok_cred_def_id( 'Q4zqM7aXqm7gDQkUVLng9h:3:CL:18:tag') # protocol >= 1.4 assert ok_cred_def_id( 'Q4zqM7aXqm7gDQkUVLng9h:3:CL:Q4zqM7aXqm7gDQkUVLng9h:2:schema_name:1.0:tag' ) # long form assert ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:3:CL:18:tag', 'Q4zqM7aXqm7gDQkUVLng9h') # issuer-did assert ok_cred_def_id( 'Q4zqM7aXqm7gDQkUVLng9h:3:CL:Q999999999999999999999:2:schema_name:1.0:tag', 'Q4zqM7aXqm7gDQkUVLng9h') # long form, issuer-did assert not ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:3:CL:18:tag', 'Xxxxxxxxxxxxxxxxxxxxxx') assert not ok_cred_def_id( 'Q4zqM7aXqm7gDQkUVLng9h:3:CL:Q4zqM7aXqm7gDQkUVLng9h:2:schema_name:1.0:tag', 'Xxxxxxxxxxxxxxxxxxxxxx') # long form, issuer-did assert ok_cred_def_id( 'Q4zqM7aXqm7gDQkUVLng9h:3:CL:Q4zqM7aXqm7gDQkUVLng9h:2:schema_name:1.0:tag' ) # long form assert not ok_cred_def_id( 'Q4zqM7aXqm7gDQkUVLng9h:3:CL:Q4zqM7aXqm7gDQkUVLng9h:schema_name:1.0:tag' ) # no :2: assert not ok_cred_def_id( 'Q4zqM7aXqm7gDQkUVLng9h:3:CL:QIIIIIIIII7gDQkUVLng9h:schema_name:1.0:tag' ) # I not base58 assert not ok_cred_def_id( 'Q4zqM7aXqm7gDQkUVLng9h:3:CL:QIIIIIIIII7gDQkUVLng9h:schema_name:v1.0:tag' ) # bad version assert not ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:4:CL:18:0') assert not ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h::CL:18:0') assert not ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9I:3:CL:18:tag') assert not ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:3::18:tag') assert not ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:3:18:tag') assert not ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:3:CL:18z:tag') assert ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:3:CL:18') # protocol == 1.3 assert ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:3:CL:18', 'Q4zqM7aXqm7gDQkUVLng9h') assert not ok_cred_def_id('Q4zqM7aXqm7gDQkUVLng9h:3:CL:18', 'Xxxxxxxxxxxxxxxxxxxxxx') assert ok_cred_def_id( rev_reg_id2cred_def_id( 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:Q4zqM7aXqm7gDQkUVLng9h:2:schema_name:1.0:tag:CL_ACCUM:1' )) print('\n\n== 6 == Credential definition identifier checks pass OK') assert ok_rev_reg_id( 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:tag:CL_ACCUM:1' ) # protocol >= 1.4 assert ok_rev_reg_id( 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:tag:CL_ACCUM:1', 'LjgpST2rjsoxYegQDRm7EL') assert ok_rev_reg_id( # long form 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:Q4zqM7aXqm7gDQkUVLng9h:2:schema_name:1.0:tag:CL_ACCUM:1' ) assert ok_rev_reg_id( # long form 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:Q4zqM7aXqm7gDQkUVLng9h:2:schema_name:1.0:tag:CL_ACCUM:1', 'LjgpST2rjsoxYegQDRm7EL') assert not ok_rev_reg_id( 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:tag:CL_ACCUM:1', 'Xxxxxxxxxxxxxxxxxxxxxx') assert not ok_rev_reg_id( 'LjgpST2rjsoxYegQDRm7EL:5:LjgpST2rjsoxYegQDRm7EL:3:CL:20:tag:CL_ACCUM:1' ) assert not ok_rev_reg_id( 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:4:CL:20:0:CL_ACCUM:1') assert not ok_rev_reg_id( 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL::CL:20:0:CL_ACCUM:1') assert not ok_rev_reg_id( 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:NOT_CL:20:tag:CL_ACCUM:1' ) assert not ok_rev_reg_id( 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20z:tag:CL_ACCUM:1' ) assert not ok_rev_reg_id( 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20::CL_ACCUM:1') assert not ok_rev_reg_id( 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:tag::1') assert not ok_rev_reg_id( 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:tag:1') assert not ok_rev_reg_id( 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:tag:CL_ACCUM:' ) assert not ok_rev_reg_id( 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:tag:CL_ACCUM') assert ok_rev_reg_id( 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:CL_ACCUM:1' ) # protocol == 1.3 assert ok_rev_reg_id( 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:CL_ACCUM:1', 'LjgpST2rjsoxYegQDRm7EL') assert not ok_rev_reg_id( 'LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:20:CL_ACCUM:1', 'Xxxxxxxxxxxxxxxxxxxxxx') print('\n\n== 7 == Revocation registry identifier checks pass OK')
async def _send_rev_reg_def(self, rr_id: str) -> None: """ Move tails file from hopper; deserialize revocation registry definition and initial entry; send to ledger and cache revocation registry definition. Operation serializes to subdirectory within tails hopper directory; symbolic link presence signals completion. Raise AbsentRevReg if revocation registry is not ready in hopper, or AbsentTails if tails file is not yet linked by its revocation registry identifier. :param rr_id: revocation registry identifier """ LOGGER.debug('Issuer._send_rev_reg_def >>> rr_id: %s', rr_id) dir_tails_rr_id = self.rrb.dir_tails_top(rr_id) dir_target = self.rrb.dir_tails_target(rr_id) if not Tails.linked(dir_tails_rr_id, rr_id): LOGGER.debug( 'Issuer._send_rev_reg_def <!< Tails file for rev reg %s not ready in dir %s', rr_id, dir_target) raise AbsentRevReg( 'Tails file for rev reg {} not ready in dir {}'.format( rr_id, dir_target)) file_rr_def = join(dir_target, 'rr_def.json') if not isfile(file_rr_def): LOGGER.debug( 'Issuer._send_rev_reg_def <!< Rev reg def file %s not present', file_rr_def) raise AbsentRevReg( 'Rev reg def file {} not present'.format(file_rr_def)) with open(file_rr_def, 'r') as fh_rr_def: rr_def_json = fh_rr_def.read() file_rr_ent = join(dir_target, 'rr_ent.json') if not isfile(file_rr_ent): LOGGER.debug( 'Issuer._send_rev_reg_def <!< Rev reg entry file %s not present', file_rr_ent) raise AbsentRevReg( 'Rev reg entry file {} not present'.format(file_rr_ent)) with open(file_rr_ent, 'r') as fh_rr_ent: rr_ent_json = fh_rr_ent.read() file_tails = Tails.linked(dir_tails_rr_id, rr_id) if not file_tails: LOGGER.debug( 'Issuer._send_rev_reg_def <!< Tails link %s not present in dir %s', rr_id, dir_target) raise AbsentTails('Tails link {} not present in dir {}'.format( rr_id, dir_target)) if self.rrbx: dir_cd_id = join(self.dir_tails, rev_reg_id2cred_def_id(rr_id)) makedirs(dir_cd_id, exist_ok=True) rename(file_tails, join(dir_cd_id, basename(file_tails))) with REVO_CACHE.lock: rr_def_req_json = await ledger.build_revoc_reg_def_request( self.did, rr_def_json) await self._sign_submit(rr_def_req_json) await self.get_rev_reg_def(rr_id) # add to cache en passant rr_ent_req_json = await ledger.build_revoc_reg_entry_request( self.did, rr_id, 'CL_ACCUM', rr_ent_json) await self._sign_submit(rr_ent_req_json) if self.rrbx: Tails.associate(self.dir_tails, rr_id, basename(file_tails)) rmtree(dir_tails_rr_id) else: remove(file_rr_def) remove(file_rr_ent) LOGGER.debug('Issuer._send_rev_reg_def <<<')