def check_encoding(proof_req: dict, proof: dict) -> bool: """ Return whether the proof's raw values correspond to their encodings as cross-referenced against proof request. :param proof request: proof request :param proof: corresponding proof to check :return: True if OK, False for encoding mismatch """ LOGGER.debug('Verifier.check_encoding <<< proof_req: %s, proof: %s', proof_req, proof) cd_id2proof_id = {} # invert proof['identifiers'] per cd_id p_preds = {} # cd_id and attr to bound for idx in range(len(proof['identifiers'])): cd_id = proof['identifiers'][idx]['cred_def_id'] cd_id2proof_id[cd_id] = idx # since at most 1 cred per cred def p_preds[cd_id] = { ge_proof['predicate']['attr_name']: ge_proof['predicate']['value'] for ge_proof in proof['proof']['proofs'][idx]['primary_proof'] ['ge_proofs'] } for (uuid, req_attr) in proof_req['requested_attributes'].items( ): # proof req xref proof per revealed attr for attr in [req_attr['name'] ] if 'name' in req_attr else req_attr['names']: canon_attr = canon(attr) proof_ident_idx = cd_id2proof_id[req_attr['restrictions'][0] ['cred_def_id']] primary_enco = proof['proof']['proofs'][proof_ident_idx][ 'primary_proof']['eq_proof']['revealed_attrs'].get( canon_attr) if primary_enco is None: continue # requested but declined from revelation in proof: must appear in a predicate req_revealed = proof['requested_proof'].get( 'revealed_attr_groups', {}).get( uuid, {'values': { attr: None }})['values'][attr] or proof['requested_proof'][ 'revealed_attrs'][uuid] if primary_enco != req_revealed['encoded']: LOGGER.debug('Verifier.check_proof_encoding <<< False') return False if primary_enco != encode(req_revealed['raw']): LOGGER.debug('Verifier.check_proof_encoding <<< False') return False for (uuid, req_pred) in proof_req['requested_predicates'].items( ): # proof req xref proof per pred canon_attr = canon(req_pred['name']) if p_preds[req_pred['restrictions'][0]['cred_def_id']].get( canon_attr) != req_pred['p_value']: LOGGER.debug('Verifier.check_proof_encoding <<< False') return False LOGGER.debug('Verifier.check_proof_encoding <<< True') return True
async def test_canon(): print(Ink.YELLOW('\n\n== Testing Attribute Canonicalization ==')) assert canon('testAttr') == 'testattr' assert canon(' test Attr ') == 'testattr' assert canon('testattr') == 'testattr' assert canon('testAttrZeroOneTwoThree') == 'testattrzeroonetwothree' print('\n\n== Canonicalization for attr values works as expected')
def revealed_attrs(proof: dict) -> dict: """ Fetch revealed attributes from input proof and return dict mapping credential definition identifiers to dicts, each dict mapping attribute names to (raw) values, for processing in further creds downstream. :param proof: indy-sdk proof as dict :return: dict mapping cred-ids to dicts, each mapping revealed attribute names to (raw) values """ rv = {} sub_index2cd_id = {} for (sub_index, sub_proof) in enumerate(proof['proof']['proofs']): cd_id = proof['identifiers'][sub_index]['cred_def_id'] sub_index2cd_id[sub_index] = cd_id rv[cd_id] = sub_proof['primary_proof']['eq_proof']['revealed_attrs'] # start with encoded for revealed_attr in proof['requested_proof'].get('revealed_attrs', {}).values(): cd_id = sub_index2cd_id[revealed_attr['sub_proof_index']] for (canon_attr, enco) in rv[cd_id]: if revealed_attr['encoded'] == enco: rv[cd_id][canon_attr] = revealed_attr['raw'] # replace encoded (replaces repeated values one at a time) break for revealed_attr_group in proof['requested_proof'].get('revealed_attr_groups', {}).values(): cd_id = sub_index2cd_id[revealed_attr_group['sub_proof_index']] for (attr, spec) in revealed_attr_group['values'].items(): rv[cd_id][canon(attr)] = spec['raw'] return rv
async def build_proof_req_json(self, cd_id2spec: dict) -> str: """ Build and return indy-sdk proof request for input attributes and non-revocation intervals by cred def id. :param cd_id2spec: dict mapping cred def ids to: - (optionally) 'attrs': lists of names of attributes of interest (omit for all, empty list or None for none) - (optionally) '>=': (pred) inclusive int lower-bounds of interest (omit, empty list, or None for none) - (optionally) '>': (pred) exclusive int lower-bounds of interest (omit, empty list, or None for none) - (optionally) '<=': (pred) inclusive int upper-bounds of interest (omit, empty list, or None for none) - (optionally) '<': (pred) exclusive int upper-bounds of interest (omit, empty list, or None for none) - (optionally), 'interval': either - (2-tuple) pair of epoch second counts marking 'from' and 'to' timestamps, or - | single epoch second count to set 'from' and 'to' the same; default | (now, now) for cred defs supporting revocation or None otherwise; e.g., :: { 'Vx4E82R17q...:3:CL:16:tag': { 'attrs': [ # request attrs 'name' and 'favouriteDrink' from this cred def's schema 'name', 'favouriteDrink' ], '>=': { # request predicate score>=80 from this cred def 'score': 80 } '<=': { # request ranking <=10 from this cred def 'ranking': 10 } 'interval': 1528116008 # same instant for all attrs and preds of corresponding schema }, 'R17v42T4pk...:3:CL:19:tag': None, # request all attrs, no preds, default intervals on all attrs 'e3vc5K168n...:3:CL:23:tag': {}, # request all attrs, no preds, default intervals on all attrs 'Z9ccax812j...:3:CL:27:tag': { # request all attrs, no preds, this interval on all attrs 'interval': (1528112408, 1528116008) }, '9cHbp54C8n...:3:CL:37:tag': { # request no attrs and some predicates; specify interval 'attrs': [], # or equivalently, 'attrs': None '>=': { 'employees': '50' # nicety: implementation converts to int for caller }, '>=': { 'revenue': '10000000' # nicety: implementation converts to int for caller 'ebidta': 0 } 'interval': (1528029608, 1528116008) }, '6caBcmLi33...:3:CL:41:tag': { # all attrs, one pred, default intervals to now on attrs & pred '>': { 'regEpoch': 1514782800 } }, ... } :return: indy-sdk proof request json """ LOGGER.debug('Verifier.build_proof_req_json >>> cd_id2spec: %s', cd_id2spec) cd_id2schema = {} now = int(time()) rv = { 'nonce': str(int(time())), 'name': 'proof_req', 'version': '0.0', 'requested_attributes': {}, 'requested_predicates': {} } for cd_id in cd_id2spec: if not ok_cred_def_id(cd_id): LOGGER.debug( 'Verifier.build_proof_req_json <!< Bad cred def id %s', cd_id) raise BadIdentifier('Bad cred def id {}'.format(cd_id)) interval = None cred_def = json.loads(await self.get_cred_def(cd_id)) seq_no = cred_def_id2seq_no(cd_id) cd_id2schema[cd_id] = json.loads(await self.get_schema(seq_no)) if 'revocation' in cred_def['value']: fro_to = cd_id2spec[cd_id].get( 'interval', (now, now)) if cd_id2spec[cd_id] else (now, now) interval = { 'from': fro_to if isinstance(fro_to, int) else min(fro_to), 'to': fro_to if isinstance(fro_to, int) else max(fro_to) } for attr in (cd_id2spec[cd_id].get( 'attrs', cd_id2schema[cd_id]['attrNames']) or [] if cd_id2spec[cd_id] else cd_id2schema[cd_id]['attrNames']): attr_uuid = '{}_{}_uuid'.format(seq_no, canon(attr)) rv['requested_attributes'][attr_uuid] = { 'name': attr, 'restrictions': [{ 'cred_def_id': cd_id }] } if interval: rv['requested_attributes'][attr_uuid][ 'non_revoked'] = interval for pred in Predicate: for attr in (cd_id2spec[cd_id].get(pred.value.math, {}) or {} if cd_id2spec[cd_id] else {}): pred_uuid = '{}_{}_{}_uuid'.format(seq_no, canon(attr), pred.value.fortran) try: rv['requested_predicates'][pred_uuid] = { 'name': attr, 'p_type': pred.value.math, 'p_value': Predicate.to_int( cd_id2spec[cd_id][pred.value.math][attr]), 'restrictions': [{ 'cred_def_id': cd_id }] } except ValueError: LOGGER.info( 'cannot build %s predicate on non-int bound %s for %s', pred.value.fortran, cd_id2spec[cd_id][pred.value.math][attr], attr) continue # int conversion failed - reject candidate if interval: rv['requested_predicates'][pred_uuid][ 'non_revoked'] = interval LOGGER.debug('Verifier.build_proof_req_json <<< %s', json.dumps(rv)) return json.dumps(rv)
async def test_canon_cred_wql(): print(Ink.YELLOW('\n\n== Testing credential WQL canonicalization ==')) invariant = [ {}, { 'attr::test::marker': '1' }, { 'schema_id': None }, { '$or': [{ 'attr::test::value': '0' }, { 'attr::test::value': { '$gt': '10' } }, { 'attr::test::value': { '$lt': '-10' } }] }, { # and 'attr::test::marker': '1', 'attr::test::value': { '$in': ['1', '2', '3', '5', '8', '13'] }, 'attr::another::value': { '$like': 'hello%' } } ] assert all(canon_cred_wql(q) == q for q in invariant) print( '\n\n== Canonicalization for invariant credential WQL works as expected' ) # simplest case q = {'attr::testAttributeName::marker': 1} canon_q = canon_cred_wql(q) assert all(canon_q[canon(k)] == raw(q[k]) for k in q) # and q = { 'attr::testAttributeName::marker': 1, 'attr::testAttributeName::value': 0 } canon_q = canon_cred_wql(q) assert all(canon_q[canon(k)] == raw(q[k]) for k in q) # or q = { '$or': [{ 'attr::testAttributeName::value': 0 }, { 'attr::testAttributeName::value': 1 }, { 'attr::testAttributeName::value': 2 }] } canon_q = canon_cred_wql(q) assert canon_q['$or'] == [ { 'attr::testattributename::value': '0' }, { 'attr::testattributename::value': '1' }, { 'attr::testattributename::value': '2' } # canonicalize tag names ] # and, not, like q = { 'attr::testAttributeName::value': { '$like': '%' }, '$not': { '$or': [ { 'attr::testAttributeName::value': 0 }, { 'attr::testAttributeName::value': 1 }, { 'attr::testAttributeName::value': { '$gt': 10 } }, { 'attr::testAttributeName::value': { '$in': [-3, -7] } }, ] } } canon_q = canon_cred_wql(q) assert canon_q['attr::testattributename::value'] == {'$like': '%'} canon_q.pop('attr::testattributename::value') assert canon_q['$not']['$or'] == [ { 'attr::testattributename::value': '0' }, { 'attr::testattributename::value': '1' }, { 'attr::testattributename::value': { '$gt': '10' } }, { 'attr::testattributename::value': { '$in': ['-3', '-7'] } }, ] canon_q.pop('$not') assert not canon_q # bad 'or' q = { '$or': { 'attr::testAttributeName::value': 0, 'attr::testAttributeName::value': 1 } } try: canon_q = canon_cred_wql(q) assert False except BadWalletQuery: pass print('\n\n== Canonicalization for credential WQL works as expected')