|
| 1 | +import json |
| 2 | +import aiohttp |
| 3 | +import base64 |
| 4 | +import time |
| 5 | +import sys |
| 6 | +import hashlib |
| 7 | + |
| 8 | +from protocol_tests.provider import Provider |
| 9 | +from protocol_tests.issue_credential.provider import IssueCredentialProvider |
| 10 | +from indy import anoncreds, wallet, ledger, pool, crypto, did, pairwise, non_secrets, ledger, wallet, blob_storage |
| 11 | +from indy.error import IndyError, ErrorCode |
| 12 | + |
| 13 | + |
| 14 | +class IndyProvider(Provider, IssueCredentialProvider): |
| 15 | + """ |
| 16 | + The indy provider isolates all indy-specific code required by the test suite. |
| 17 | + """ |
| 18 | + |
| 19 | + async def setup(self, config): |
| 20 | + if not 'genesis_url' in config: |
| 21 | + raise Exception( |
| 22 | + "The Indy provider requires a 'genesis_url' value in config.toml") |
| 23 | + self.genesis_url = config['genesis_url'] |
| 24 | + self.pool_name = config.get('pool_name', 'arie-test') |
| 25 | + id = config.get('name', 'test') |
| 26 | + key = config.get('pass', 'testpw') |
| 27 | + seed = config.get('seed', '000000000000000000000000Steward1') |
| 28 | + self.cfg = json.dumps({'id': id}) |
| 29 | + self.creds = json.dumps({'key': key}) |
| 30 | + self.seed = json.dumps({'seed': seed}) |
| 31 | + try: |
| 32 | + await wallet.delete_wallet(self.cfg, self.creds) |
| 33 | + except Exception as e: |
| 34 | + pass |
| 35 | + await wallet.create_wallet(self.cfg, self.creds) |
| 36 | + self.wallet = await wallet.open_wallet(self.cfg, self.creds) |
| 37 | + self.master_secret_id = await anoncreds.prover_create_master_secret(self.wallet, None) |
| 38 | + (self.did, self.verkey) = await did.create_and_store_my_did(self.wallet, self.seed) |
| 39 | + # Download the genesis file |
| 40 | + resp = await aiohttp.ClientSession().get(self.genesis_url) |
| 41 | + genesis = await resp.read() |
| 42 | + genesisFileName = "genesis.aries-test" |
| 43 | + with open(genesisFileName, 'wb') as output: |
| 44 | + output.write(genesis) |
| 45 | + await self._open_pool({'genesis_txn': genesisFileName}) |
| 46 | + |
| 47 | + async def issue_credential_v1_0_issuer_create_credential_schema(self, name: str, version: str, attrs: [str]) -> str: |
| 48 | + (schema_id, schema) = await anoncreds.issuer_create_schema( |
| 49 | + self.did, name, version, json.dumps(attrs)) |
| 50 | + schema_request = await ledger.build_schema_request(self.did, schema) |
| 51 | + await ledger.sign_and_submit_request(self.pool, |
| 52 | + self.wallet, |
| 53 | + self.did, |
| 54 | + schema_request) |
| 55 | + return schema_id |
| 56 | + |
| 57 | + async def issue_credential_v1_0_issuer_create_credential_definition(self, schema_id) -> str: |
| 58 | + request = await ledger.build_get_schema_request(self.did, schema_id) |
| 59 | + response = await ledger.submit_request(self.pool, request) |
| 60 | + (_, schema) = await ledger.parse_get_schema_response(response) |
| 61 | + (cred_def_id, cred_def_json) = await anoncreds.issuer_create_and_store_credential_def( |
| 62 | + self.wallet, self.did, schema, 'TAG1', 'CL', '{"support_revocation": false}') |
| 63 | + cred_def_request = await ledger.build_cred_def_request(self.did, cred_def_json) |
| 64 | + await ledger.sign_and_submit_request(self.pool, self.wallet, self.did, cred_def_request) |
| 65 | + return cred_def_id |
| 66 | + |
| 67 | + async def issue_credential_v1_0_issuer_create_credential_offer(self, cred_def_id: str) -> (str, any): |
| 68 | + offer = await anoncreds.issuer_create_credential_offer(self.wallet, cred_def_id) |
| 69 | + attach = base64.b64encode(offer.encode()).decode() |
| 70 | + return (attach, offer) |
| 71 | + |
| 72 | + async def issue_credential_v1_0_issuer_create_credential(self, offer: any, b64_request_attach: any, attrs: dict) -> str: |
| 73 | + req = base64.b64decode(b64_request_attach).decode() |
| 74 | + attrs = json.dumps(self._encode_attrs(attrs)) |
| 75 | + (cred_json, _, _) = await anoncreds.issuer_create_credential(self.wallet, offer, req, attrs, None, None) |
| 76 | + attach = base64.b64encode(cred_json.encode()).decode() |
| 77 | + return attach |
| 78 | + |
| 79 | + async def issue_credential_v1_0_holder_create_credential_request(self, b64_offer_attach: str) -> (str, dict): |
| 80 | + offer = json.loads(base64.b64decode(b64_offer_attach)) |
| 81 | + credDefId = offer['cred_def_id'] |
| 82 | + # Get the cred def from the ledger |
| 83 | + (_, credDef) = await self._get_cred_def(credDefId) |
| 84 | + # Create the credential request |
| 85 | + (req_data, req_metadata) = await anoncreds.prover_create_credential_req( |
| 86 | + self.wallet, self.did, json.dumps(offer), credDef, self.master_secret_id) |
| 87 | + b64_request_attach = base64.b64encode(req_data.encode()).decode() |
| 88 | + store_credential_passback = { |
| 89 | + "req_metadata": req_metadata, |
| 90 | + "cred_def": credDef |
| 91 | + } |
| 92 | + return (b64_request_attach, store_credential_passback) |
| 93 | + |
| 94 | + async def issue_credential_v1_0_holder_store_credential(self, b64_credential_attach: str, store_credential_passback: dict): |
| 95 | + cred = base64.b64decode(b64_credential_attach).decode() |
| 96 | + pb = store_credential_passback |
| 97 | + await anoncreds.prover_store_credential(self.wallet, None, pb["req_metadata"], cred, pb["cred_def"], None) |
| 98 | + |
| 99 | + async def _get_cred_def(self, credDefId): |
| 100 | + req = await ledger.build_get_cred_def_request(self.did, credDefId) |
| 101 | + resp = await ledger.submit_request(self.pool, req) |
| 102 | + credDef = await ledger.parse_get_cred_def_response(resp) |
| 103 | + return credDef |
| 104 | + |
| 105 | + async def _open_pool(self, cfg): |
| 106 | + # Create the pool, but ignore the error if it already exists |
| 107 | + await pool.set_protocol_version(2) |
| 108 | + try: |
| 109 | + await pool.create_pool_ledger_config(self.pool_name, json.dumps(cfg)) |
| 110 | + except IndyError as e: |
| 111 | + if e.error_code != ErrorCode.PoolLedgerConfigAlreadyExistsError: |
| 112 | + raise e |
| 113 | + self.pool = await pool.open_pool_ledger(self.pool_name, json.dumps(cfg)) |
| 114 | + |
| 115 | + def _encode_attrs(self, attrs: dict) -> dict: |
| 116 | + result = {} |
| 117 | + for name, val in attrs.items(): |
| 118 | + result[name] = { |
| 119 | + 'raw': val, |
| 120 | + 'encoded': self._encode_attr(val), |
| 121 | + } |
| 122 | + return result |
| 123 | + |
| 124 | + def _encode_attr(self, value) -> str: |
| 125 | + if value is None: |
| 126 | + return '4294967297' # sentinel 2**32 + 1 |
| 127 | + s = str(value) |
| 128 | + try: |
| 129 | + i = int(value) |
| 130 | + if 0 <= i < 2**32: # it's an i32, leave it (as numeric string) |
| 131 | + return s |
| 132 | + except (ValueError, TypeError): |
| 133 | + pass |
| 134 | + # Compute sha256 decimal string |
| 135 | + hash = hashlib.sha256(value.encode()).digest() |
| 136 | + num = int.from_bytes(hash, byteorder=sys.byteorder, signed=False) |
| 137 | + return str(num) |
0 commit comments