Example #1
0
    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
Example #3
0
    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 <<<')
Example #4
0
    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
Example #5
0
    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
Example #6
0
    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
Example #7
0
    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
Example #8
0
 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
Example #9
0
    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
Example #10
0
    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 <<<')
Example #11
0
    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
Example #12
0
    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
Example #13
0
    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 <<<')
Example #14
0
    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
Example #15
0
    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
Example #16
0
    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
Example #17
0
    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
Example #18
0
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))
Example #19
0
    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__ <<<')
Example #20
0
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))
Example #21
0
    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 <<<')
Example #22
0
    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
Example #23
0
 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
Example #24
0
 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
Example #25
0
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))
Example #26
0
    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))
Example #28
0
    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
Example #29
0
    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
Example #30
0
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))