Skip to content
This repository was archived by the owner on Jun 13, 2023. It is now read-only.

Commit 736ad44

Browse files
Keith Smithdbluhm
Keith Smith
andauthored
Adding support for the issue-credential protocol family. (#28)
Signed-off-by: Keith Smith <[email protected]> Co-authored-by: Daniel Bluhm <[email protected]>
1 parent 9b562e8 commit 736ad44

9 files changed

+592
-0
lines changed

config.sample.toml

+12
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,24 @@ port = 3000
99
endpoint = "http://localhost:3000"
1010
backchannel = 'default.ManualBackchannel'
1111

12+
#
13+
# The 'provider' variable is used to dynamically load a provider.
14+
# A provider is required to test the issue-credential protocol family.
15+
# The Indy provider is specified as follows:
16+
# provider = 'indy_provider.IndyProvider'
17+
# The Indy provider requires 'genesis_url' to be set to a URL from which the genesis
18+
# transaction file can be downloaded. For example, it could be similar to the following:
19+
# genesis_url = 'http://ledger/sandbox/pool_transactions_genesis'
20+
#
21+
1222
# List of regular expressions used to select tests.
1323
# If a test name matches at least one regex in this list, it will be selected
1424
# for execution. matches the @meta data in the desired tests,
1525
# for example 'connections,1.0,inviter,response-post-sig-verify-valid'.
1626
tests = [
1727
"connections.*",
28+
"discover-features.*",
29+
# "issue-credential.*",
1830
]
1931

2032
[config.subject]

indy_provider.py

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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)

protocol_tests/__init__.py

+11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from aries_staticagent import StaticConnection, Message, crypto
88
from .backchannel import Backchannel
9+
from .provider import Provider
910

1011

1112
def _recipients_from_packed_message(packed_message: bytes) -> Iterable[str]:
@@ -41,6 +42,7 @@ class Suite:
4142
def __init__(self):
4243
self.frontchannels: Dict[str, StaticConnection] = {}
4344
self._backchannel = None
45+
self._provider = None
4446
self._reply = None
4547

4648
@property
@@ -52,6 +54,15 @@ def set_backchannel(self, backchannel: Backchannel):
5254
"""Set backchannel."""
5355
self._backchannel = backchannel
5456

57+
@property
58+
def provider(self):
59+
"""Return a reference to the provider (self)."""
60+
return self._provider
61+
62+
def set_provider(self, provider: Provider):
63+
"""Set provider."""
64+
self._provider = provider
65+
5566
@contextmanager
5667
def reply(self, handler):
5768
"""Handle potential to reply."""

protocol_tests/conftest.py

+17
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
# pylint: disable=redefined-outer-name
2222

23+
2324
@pytest.fixture(scope='session')
2425
def event_loop():
2526
""" Create a session scoped event loop.
@@ -36,11 +37,13 @@ def config(pytestconfig):
3637
"""
3738
yield pytestconfig.suite_config
3839

40+
3941
@pytest.fixture(scope='session')
4042
def suite():
4143
"""Get channel manager for test suite."""
4244
yield Suite()
4345

46+
4447
@pytest.fixture(scope='session')
4548
async def http_endpoint(config, suite):
4649
"""Create http server task."""
@@ -86,6 +89,20 @@ async def backchannel(config, http_endpoint, suite):
8689
yield suite.backchannel
8790

8891

92+
@pytest.fixture(scope='session')
93+
async def provider(config, suite):
94+
"""Get provider to test subject."""
95+
if not 'provider' in config and not config['provider']:
96+
raise "No 'provider' was specified in the config file"
97+
path_parts = config['provider'].split('.')
98+
mod_path, class_name = '.'.join(path_parts[:-1]), path_parts[-1]
99+
mod = import_module(mod_path)
100+
provider_class = getattr(mod, class_name)
101+
suite.set_provider(provider_class())
102+
await suite.provider.setup(config)
103+
yield suite.provider
104+
105+
89106
@pytest.fixture(scope='session')
90107
def temporary_channel(http_endpoint, suite):
91108
"""Get contextmanager for using a temporary channel."""

0 commit comments

Comments
 (0)