diff --git a/examples/simple_client.py b/examples/simple_client.py index f1ae4c4..fc19903 100755 --- a/examples/simple_client.py +++ b/examples/simple_client.py @@ -13,6 +13,7 @@ from plenum.common.looper import Looper from plenum.common.signer_simple import SimpleSigner + from sovrin.client.client import Client from sovrin.common.txn import TXN_TYPE, NYM diff --git a/examples/simple_node.py b/examples/simple_node.py index 4d86602..7f16b9a 100755 --- a/examples/simple_node.py +++ b/examples/simple_node.py @@ -14,6 +14,7 @@ from tempfile import TemporaryDirectory from plenum.common.looper import Looper + from sovrin.server.node import Node diff --git a/post-setup.py b/post-setup.py index 4c4a8b9..1c6acd6 100644 --- a/post-setup.py +++ b/post-setup.py @@ -1,4 +1,6 @@ -from sovrin.common.setup_util import Setup import os + +from sovrin.common.setup_util import Setup + BASE_DIR = os.path.join(os.path.expanduser("~"), ".sovrin") Setup(BASE_DIR).setupAll() \ No newline at end of file diff --git a/sample/acme-job-application.sovrin b/sample/acme-job-application.sovrin index c8c133e..20300ec 100644 --- a/sample/acme-job-application.sovrin +++ b/sample/acme-job-application.sovrin @@ -15,7 +15,8 @@ "degree": "string", "status": "string", "ssn": "string" - } + }, + "verifiableAttributes": ["degree", "status", "ssn"] }], "sig": "oeGeMdt5HRjRsbaXybGpRmkkijhHrGT82syxofEJQbkjTCLW63tM3jMn1boaf62vCSEEDyTaVJZnrpfDXAHtLZ9" } \ No newline at end of file diff --git a/sample/thrift-loan-application.sovrin b/sample/thrift-loan-application.sovrin index e2b81c7..afd6bf4 100644 --- a/sample/thrift-loan-application.sovrin +++ b/sample/thrift-loan-application.sovrin @@ -11,7 +11,8 @@ "attributes": { "salary_bracket": "string", "employee_status": "string" - } + }, + "verifiableAttributes": ["salary_bracket", "employee_status"] }, { "name": "Loan-Application-KYC", "version": "0.1", @@ -19,7 +20,8 @@ "first_name": "string", "last_name": "string", "ssn": "string" - } + }, + "verifiableAttributes": ["first_name", "last_name", "ssn"] }], "sig": "D1vU5fbtJbqWKdCoVJgqHBLLhh5CYspikuEXdnBVVyCnLHiYC9ZsZrDWpz3GkFFGvfC4RQ4kuB64vUFLo3F7Xk6" } diff --git a/scripts/add_new_node.py b/scripts/add_new_node similarity index 99% rename from scripts/add_new_node.py rename to scripts/add_new_node index 165d961..a300b7a 100644 --- a/scripts/add_new_node.py +++ b/scripts/add_new_node @@ -1,5 +1,8 @@ +#! /usr/bin/env python3 + from plenum.common.looper import Looper from plenum.common.signer_simple import SimpleSigner + from sovrin.client.client import Client from sovrin.common.config_util import getConfig diff --git a/scripts/change_node_ha b/scripts/change_node_ha index 594f087..25ed076 100755 --- a/scripts/change_node_ha +++ b/scripts/change_node_ha @@ -7,6 +7,7 @@ from plenum.common.types import HA from plenum.common.util import getMaxFailures from plenum.test.eventually import eventually from plenum.test.helper import checkSufficientRepliesRecvd + from sovrin.common.config_util import getConfig diff --git a/scripts/gen_load_ratcheting b/scripts/gen_load_ratcheting index 6a264c3..1e80b8a 100755 --- a/scripts/gen_load_ratcheting +++ b/scripts/gen_load_ratcheting @@ -4,12 +4,12 @@ import sys from jsonpickle import json - from ledger.stores.text_file_store import TextFileStore from plenum.common.looper import Looper from plenum.common.signer_simple import SimpleSigner from plenum.common.txn import TARGET_NYM, TXN_TYPE, NYM, ROLE, USER, NEW_NODE, DATA, ALIAS, CLIENT_IP, CLIENT_PORT from plenum.common.types import HA, CLIENT_STACK_SUFFIX + from sovrin.client.client import Client from sovrin.common.config_util import getConfig diff --git a/scripts/generate_sovrin_pool_transactions b/scripts/generate_sovrin_pool_transactions index cafaff1..9b1c237 100755 --- a/scripts/generate_sovrin_pool_transactions +++ b/scripts/generate_sovrin_pool_transactions @@ -1,10 +1,9 @@ #! /usr/bin/env python3 -import os -from sovrin.common.txn import getTxnOrderedFields - from plenum.common.test_network_setup import TestNetworkSetup + from sovrin.common.config_util import getConfig +from sovrin.common.txn import getTxnOrderedFields config = getConfig() keepDir = config.baseDir diff --git a/scripts/init_sovrin_raet_keep b/scripts/init_sovrin_raet_keep index 691f3d9..46cdf6d 100755 --- a/scripts/init_sovrin_raet_keep +++ b/scripts/init_sovrin_raet_keep @@ -1,7 +1,6 @@ #! /usr/bin/env python3 import argparse -import os import os.path from plenum.common.raet import initLocalKeep diff --git a/scripts/sovrin b/scripts/sovrin index 407b402..16390e6 100755 --- a/scripts/sovrin +++ b/scripts/sovrin @@ -16,9 +16,10 @@ $ sovrin "new nodes all" # Do not remove this import import plenum.cli.ensure_logging_not_setup +import logging import os import sys -import logging + logging.root.handlers = [] logger = logging.getLogger() logger.propagate = False @@ -44,12 +45,15 @@ def run_cli(): commands = sys.argv[1:] + withNode = True if '--with-node' in commands else True + with Looper(debug=False) as looper: curDir = os.getcwd() logFilePath = os.path.join(curDir, config.logFilePath) cli = SovrinCli(looper=looper, basedirpath=baseDir, - logFileName=logFilePath + logFileName=logFilePath, + withNode=withNode ) looper.run(cli.shell(*commands)) diff --git a/scripts/start_sovrin_node b/scripts/start_sovrin_node index dee5a2d..bc6d1f5 100755 --- a/scripts/start_sovrin_node +++ b/scripts/start_sovrin_node @@ -3,13 +3,11 @@ import os import sys -from plenum.common.types import HA - - -from plenum.common.raet import initRemoteKeep from ioflo.aid.consoling import Console from plenum.common.log import setupLogging, TRACE_LOG_LEVEL, \ getRAETLogLevelFromConfig +from plenum.common.types import HA + from sovrin.common.config_util import getConfig config = getConfig() diff --git a/setup-dev.py b/setup-dev.py index ec518a1..76b5046 100644 --- a/setup-dev.py +++ b/setup-dev.py @@ -1,14 +1,14 @@ import glob +import os import shutil import sys -import os -from setuptools import setup, find_packages, __version__ -from pip.req import parse_requirements from shutil import copyfile + +from setuptools import setup, find_packages, __version__ + import data import sample - v = sys.version_info if sys.version_info < (3, 5): msg = "FAIL: Requires Python 3.5 or later, " \ @@ -62,7 +62,11 @@ '*.css', '*.ico', '*.png', 'LICENSE', 'LEGAL', '*.sovrin']}, include_package_data=True, data_files=[( - (BASE_DIR, ['data/pool_transactions_sandbox', ]) + (BASE_DIR, ['data/pool_transactions_sandbox', + 'data/pool_transactions_local', + 'data/transactions_sandbox', + 'data/transactions_local', + ]) )], install_requires=['base58', 'pyorient', 'plenum-dev', 'ledger-dev', 'semver', 'anoncreds-dev'], diff --git a/setup.py b/setup.py index b49c05b..c4ae50a 100644 --- a/setup.py +++ b/setup.py @@ -1,17 +1,10 @@ -import glob -import shutil -import sys import os +import subprocess +import sys -import data from setuptools import setup, find_packages, __version__ -from setuptools.command.install import install from setuptools.command.develop import develop -from pip.req import parse_requirements -from shutil import copyfile -import subprocess - -import sample +from setuptools.command.install import install v = sys.version_info if sys.version_info < (3, 5): @@ -51,14 +44,14 @@ def post_install(): class PostInstall(install): - def run(self): - install.run(self) + def do_egg_install(self): + install.do_egg_install(self) post_install() class PostInstallDev(develop): - def run(self): - develop.run(self) + def do_egg_install(self): + develop.do_egg_install(self) post_install() @@ -79,7 +72,11 @@ def run(self): '*.css', '*.ico', '*.png', 'LICENSE', 'LEGAL', '*.sovrin']}, include_package_data=True, data_files=[( - (BASE_DIR, ['data/pool_transactions_sandbox', ]) + (BASE_DIR, ['data/pool_transactions_sandbox', + 'data/pool_transactions_local', + 'data/transactions_sandbox', + 'data/transactions_local', + ]) )], install_requires=['base58', 'pyorient', 'plenum', 'ledger', 'semver', 'anoncreds'], diff --git a/sovrin/__init__.py b/sovrin/__init__.py index b10181e..aa48e95 100644 --- a/sovrin/__init__.py +++ b/sovrin/__init__.py @@ -1,8 +1,9 @@ import os -import sovrin from plenum.common.pkg_util import check_deps +import sovrin + check_deps(sovrin) from sovrin.common.plugin_helper import writeAnonCredPlugin diff --git a/sovrin/__metadata__.py b/sovrin/__metadata__.py index eec84fa..2c3aba9 100644 --- a/sovrin/__metadata__.py +++ b/sovrin/__metadata__.py @@ -1,7 +1,7 @@ """ Sovrin package metadata """ -__version_info__ = (0, 1, 140) +__version_info__ = (0, 1, 141) __version__ = '{}.{}.{}'.format(*__version_info__) __author__ = "Evernym, Inc." __license__ = "Apache 2.0" diff --git a/sovrin/agent/agent.py b/sovrin/agent/agent.py index 77c3fb0..22498ac 100644 --- a/sovrin/agent/agent.py +++ b/sovrin/agent/agent.py @@ -1,7 +1,7 @@ +import asyncio from typing import Dict -from typing import Tuple, Callable +from typing import Tuple -import asyncio from plenum.common.error import fault from plenum.common.exceptions import RemoteNotFound from plenum.common.log import getlogger @@ -11,14 +11,19 @@ from plenum.common.startable import Status from plenum.common.types import Identifier from plenum.common.util import randomString + +from anoncreds.protocol.repo.attributes_repo import AttributeRepoInMemory from sovrin.agent.agent_net import AgentNet from sovrin.agent.caching import Caching from sovrin.agent.walleted import Walleted +from sovrin.anon_creds.sovrin_issuer import SovrinIssuer +from sovrin.anon_creds.sovrin_prover import SovrinProver +from sovrin.anon_creds.sovrin_verifier import SovrinVerifier from sovrin.client.client import Client from sovrin.client.wallet.wallet import Wallet +from sovrin.common.config_util import getConfig from sovrin.common.identity import Identity from sovrin.common.strict_types import strict_types, decClassMethods -from sovrin.common.config_util import getConfig logger = getlogger() @@ -28,13 +33,14 @@ class Agent(Motor, AgentNet): def __init__(self, name: str, basedirpath: str, - client: Client=None, - port: int=None, + client: Client = None, + port: int = None, loop=None): Motor.__init__(self) self.loop = loop or asyncio.get_event_loop() - self._eventListeners = {} # Dict[str, set(Callable)] + self._eventListeners = {} # Dict[str, set(Callable)] self._name = name + self._port = port AgentNet.__init__(self, name=self._name.replace(" ", ""), @@ -60,6 +66,10 @@ def client(self, client): def name(self): return self._name + @property + def port(self): + return self._port + async def prod(self, limit) -> int: c = 0 if self.get_status() == Status.starting: @@ -118,9 +128,9 @@ def ensureConnectedToDest(self, destHa, clbk, *args): clbk(*args) else: self.loop.call_later(.2, self.ensureConnectedToDest, - destHa, clbk, *args) + destHa, clbk, *args) - def sendMessage(self, msg, name: str=None, ha: Tuple=None): + def sendMessage(self, msg, name: str = None, ha: Tuple = None): try: remote = self.endpoint.getRemote(name=name, ha=ha) except RemoteNotFound as ex: @@ -156,17 +166,31 @@ class WalletedAgent(Walleted, Agent, Caching): def __init__(self, name: str, basedirpath: str, - client: Client=None, - wallet: Wallet=None, - port: int=None, - loop=None): - Agent.__init__(self, name, basedirpath, client, port, loop) + client: Client = None, + wallet: Wallet = None, + port: int = None, + loop=None, + attrRepo=None): + Agent.__init__(self, name, basedirpath, client, port, loop=loop) self._wallet = wallet or Wallet(name) + self._attrRepo = attrRepo or AttributeRepoInMemory() Walleted.__init__(self) + if self.client: + self._initIssuerProverVerifier() + def _initIssuerProverVerifier(self): + self.issuer = SovrinIssuer(client=self.client, wallet=self._wallet, attrRepo=self._attrRepo) + self.prover = SovrinProver(client=self.client, wallet=self._wallet) + self.verifier = SovrinVerifier(client=self.client, wallet=self._wallet) -def runAgent(agentClass, name, wallet=None, basedirpath=None, port=None, - startRunning=True, bootstrap=False, loop=None, clientClass=Client): + @Agent.client.setter + def client(self, client): + Agent.client.fset(self, client) + if self.client: + self._initIssuerProverVerifier() + + +def createAgent(agentClass, name, wallet=None, basedirpath=None, port=None, loop=None, clientClass=Client): config = getConfig() if not wallet: @@ -181,19 +205,31 @@ def runAgent(agentClass, name, wallet=None, basedirpath=None, port=None, ha=("0.0.0.0", clientPort), basedirpath=basedirpath) - agent = agentClass(basedirpath=basedirpath, - client=client, - wallet=wallet, - port=port, - loop=loop) - if bootstrap: - agent.bootstrap() - - if startRunning: - with Looper(debug=True, loop=loop) as looper: - looper.add(agent) - logger.debug("Running {} now (port: {})".format(name, port)) - looper.run() + return agentClass(basedirpath=basedirpath, + client=client, + wallet=wallet, + port=port, + loop=loop) + + +def runAgent(agent, looper=None, bootstrap=True): + def doRun(looper): + looper.add(agent) + logger.debug("Running {} now (port: {})".format(agent.name, agent.port)) + if bootstrap: + looper.run(agent.bootstrap()) + + if looper: + doRun(looper) else: - return agent + with Looper(debug=True, loop=agent.loop) as looper: + doRun(looper) + looper.run() + +def createAndRunAgent(agentClass, name, wallet=None, basedirpath=None, + port=None, looper=None, clientClass=Client, bootstrap=True): + loop = looper.loop if looper else None + agent = createAgent(agentClass, name, wallet, basedirpath, port, loop, clientClass) + runAgent(agent, looper, bootstrap) + return agent diff --git a/sovrin/agent/agent_issuer.py b/sovrin/agent/agent_issuer.py new file mode 100644 index 0000000..5567dce --- /dev/null +++ b/sovrin/agent/agent_issuer.py @@ -0,0 +1,57 @@ +from abc import abstractmethod +from typing import Dict, Any + +from plenum.common.txn import NAME, VERSION, ORIGIN +from plenum.common.types import f + +from anoncreds.protocol.issuer import Issuer +from anoncreds.protocol.types import ClaimDefinitionKey, ID +from anoncreds.protocol.types import ClaimRequest +from sovrin.agent.constants import EVENT_NOTIFY_MSG +from sovrin.agent.msg_constants import CLAIM, CLAIM_REQ_FIELD, CLAIM_FIELD + + +class AgentIssuer: + def __init__(self, issuer: Issuer): + self.issuer = issuer + + async def processReqClaim(self, msg): + body, (frm, ha) = msg + link = self.verifyAndGetLink(msg) + if not link: + raise NotImplementedError + name = body[NAME] + if not self.isClaimAvailable(link, name): + self.notifyToRemoteCaller( + EVENT_NOTIFY_MSG, "This claim is not yet available", + self.issuer.wallet.defaultId, frm, + origReqId=body.get(f.REQ_ID.nm)) + return + + version = body[VERSION] + origin = body[ORIGIN] + claimReq = ClaimRequest.fromStrDict(body[CLAIM_REQ_FIELD]) + + claimDefKey = ClaimDefinitionKey(name, version, origin) + claimDef = await self.issuer.wallet.getClaimDef(ID(claimDefKey)) + claimDefId = ID(claimDefKey=claimDefKey, claimDefId=claimDef.seqId) + + self._addAtrribute(claimDefKey=claimDefKey, proverId=claimReq.userId, + link=link) + + claim = await self.issuer.issueClaim(claimDefId, claimReq) + + claimDetails = { + NAME: claimDef.name, + VERSION: claimDef.version, + CLAIM_FIELD: claim.toStrDict(), + f.IDENTIFIER.nm: claimDef.issuerId + } + + resp = self.getCommonMsg(CLAIM, claimDetails) + self.signAndSend(resp, link.localIdentifier, frm, + origReqId=body.get(f.REQ_ID.nm)) + + @abstractmethod + def _addAtrribute(self, claimDefKey, proverId, link) -> Dict[str, Any]: + raise NotImplementedError diff --git a/sovrin/agent/agent_prover.py b/sovrin/agent/agent_prover.py new file mode 100644 index 0000000..94b32e5 --- /dev/null +++ b/sovrin/agent/agent_prover.py @@ -0,0 +1,136 @@ +import asyncio +from typing import Any + +from plenum.common.txn import NONCE, TYPE, NAME, VERSION, ORIGIN, IDENTIFIER, \ + DATA +from plenum.common.types import f +from plenum.common.util import getCryptonym + +from anoncreds.protocol.prover import Prover +from anoncreds.protocol.types import ClaimDefinitionKey, ID, Claims, ProofInput +from anoncreds.protocol.utils import toDictWithStrValues +from sovrin.agent.msg_constants import REQUEST_CLAIM, CLAIM_PROOF, CLAIM_FIELD, \ + CLAIM_REQ_FIELD, PROOF_FIELD, PROOF_INPUT_FIELD, REVEALED_ATTRS_FIELD +from sovrin.client.wallet.link import ClaimProofRequest, Link +from sovrin.common.util import getNonceForProof + + +class AgentProver: + def __init__(self, prover: Prover): + self.prover = prover + + def sendReqClaim(self, link: Link, claimDefKey): + if self.loop.is_running(): + self.loop.call_soon(asyncio.ensure_future, + self.sendReqClaimAsync(link, claimDefKey)) + else: + self.loop.run_until_complete( + self.sendReqClaimAsync(link, claimDefKey)) + + async def sendReqClaimAsync(self, link: Link, claimDefKey): + name, version, origin = claimDefKey + claimDefKey = ClaimDefinitionKey(name, version, origin) + + claimReq = await self.prover.createClaimRequest(claimDefId=ID( + claimDefKey), proverId=link.invitationNonce, + reqNonRevoc=False) + + op = { + NONCE: link.invitationNonce, + TYPE: REQUEST_CLAIM, + NAME: name, + VERSION: version, + ORIGIN: origin, + CLAIM_REQ_FIELD: claimReq.toStrDict() + } + + self.signAndSend(msg=op, linkName=link.name) + + async def handleReqClaimResponse(self, msg): + body, _ = msg + issuerId = body.get(IDENTIFIER) + claim = body[DATA] + li = self._getLinkByTarget(getCryptonym(issuerId)) + if li: + self.notifyResponseFromMsg(li.name, body.get(f.REQ_ID.nm)) + self.notifyMsgListener(' Received claim "{}".\n'.format( + claim[NAME])) + name, version, claimAuthor = \ + claim[NAME], claim[VERSION], claim[f.IDENTIFIER.nm] + + claimDefKey = ClaimDefinitionKey(name, version, claimAuthor) + claimDef = await self.prover.wallet.getClaimDef(ID(claimDefKey)) + claimDefId = ID(claimDefKey=claimDefKey, claimDefId=claimDef.seqId) + + claim = Claims.fromStrDict(claim[CLAIM_FIELD]) + + await self.prover.processClaim(claimDefId, claim) + else: + self.notifyMsgListener("No matching link found") + + def sendProof(self, link: Link, claimPrfReq: ClaimProofRequest): + if self.loop.is_running(): + self.loop.call_soon(asyncio.ensure_future, + self.sendProofAsync(link, claimPrfReq)) + else: + self.loop.run_until_complete(self.sendProofAsync(link, claimPrfReq)) + + async def sendProofAsync(self, link: Link, claimPrfReq: ClaimProofRequest): + nonce = getNonceForProof(link.invitationNonce) + + revealedAttrNames = claimPrfReq.verifiableAttributes + proofInput = ProofInput(revealedAttrs=revealedAttrNames) + proof, revealedAttrs = await self.prover.presentProof(proofInput, nonce) + + op = { + NAME: claimPrfReq.name, + VERSION: claimPrfReq.version, + NONCE: link.invitationNonce, + TYPE: CLAIM_PROOF, + PROOF_FIELD: proof.toStrDict(), + PROOF_INPUT_FIELD: proofInput.toStrDict(), + REVEALED_ATTRS_FIELD: toDictWithStrValues(revealedAttrs) + } + + self.signAndSend(msg=op, linkName=link.name) + + def handleProofStatusResponse(self, msg: Any): + body, _ = msg + data = body.get(DATA) + identifier = body.get(IDENTIFIER) + li = self._getLinkByTarget(getCryptonym(identifier)) + self.notifyResponseFromMsg(li.name, body.get(f.REQ_ID.nm)) + self.notifyMsgListener(data) + + async def getMatchingLinksWithReceivedClaimAsync(self, claimName=None): + matchingLinkAndAvailableClaim = self.wallet.getMatchingLinksWithAvailableClaim( + claimName) + matchingLinkAndReceivedClaim = [] + for li, cl in matchingLinkAndAvailableClaim: + name, version, origin = cl + claimDefKeyId = ID( + ClaimDefinitionKey(name=name, version=version, issuerId=origin)) + claimDef = await self.prover.wallet.getClaimDef(claimDefKeyId) + claimAttrs = set(claimDef.attrNames) + claim = None + try: + claim = await self.prover.wallet.getClaims(claimDefKeyId) + except ValueError: + pass # it means no claim was issued + attrs = {k: None for k in claimAttrs} + if claim: + issuedAttributes = claim.primaryClaim.attrs + if claimAttrs.intersection(issuedAttributes.keys()): + attrs = {k: issuedAttributes[k] for k in claimAttrs} + matchingLinkAndReceivedClaim.append((li, cl, attrs)) + return matchingLinkAndReceivedClaim + + async def getMatchingRcvdClaimsAsync(self, attributes): + linksAndReceivedClaim = await self.getMatchingLinksWithReceivedClaimAsync() + attributes = set(attributes) + + matchingLinkAndRcvdClaim = [] + for li, cl, issuedAttrs in linksAndReceivedClaim: + if attributes.intersection(issuedAttrs.keys()): + matchingLinkAndRcvdClaim.append((li, cl, issuedAttrs)) + return matchingLinkAndRcvdClaim diff --git a/sovrin/agent/agent_verifier.py b/sovrin/agent/agent_verifier.py new file mode 100644 index 0000000..8f99118 --- /dev/null +++ b/sovrin/agent/agent_verifier.py @@ -0,0 +1,43 @@ +from typing import Any + +from plenum.common.txn import NAME, NONCE, TYPE, DATA, VERSION +from plenum.common.types import f + +from anoncreds.protocol.types import FullProof +from anoncreds.protocol.types import ProofInput +from anoncreds.protocol.utils import fromDictWithStrValues +from anoncreds.protocol.verifier import Verifier +from sovrin.agent.msg_constants import CLAIM_PROOF_STATUS, PROOF_FIELD, \ + PROOF_INPUT_FIELD, REVEALED_ATTRS_FIELD +from sovrin.common.util import getNonceForProof + + +class AgentVerifier(Verifier): + def __init__(self, verifier: Verifier): + self.verifier = verifier + + async def verifyClaimProof(self, msg: Any): + body, (frm, ha) = msg + link = self.verifyAndGetLink(msg) + if not link: + raise NotImplementedError + + claimName = body[NAME] + nonce = getNonceForProof(body[NONCE]) + proof = FullProof.fromStrDict(body[PROOF_FIELD]) + proofInput = ProofInput.fromStrDict(body[PROOF_INPUT_FIELD]) + revealedAttrs = fromDictWithStrValues(body[REVEALED_ATTRS_FIELD]) + + result = await self.verifier.verify(proofInput, proof, revealedAttrs, nonce) + + status = 'verified' if result else 'failed verification' + resp = { + TYPE: CLAIM_PROOF_STATUS, + DATA: ' Your claim {} {} was received and {}\n'. + format(body[NAME], body[VERSION], status), + } + self.signAndSend(resp, link.localIdentifier, frm, + origReqId=body.get(f.REQ_ID.nm)) + + if result: + await self._postClaimVerif(claimName, link, frm) diff --git a/sovrin/agent/endpoint.py b/sovrin/agent/endpoint.py index 4807adf..36ac20f 100644 --- a/sovrin/agent/endpoint.py +++ b/sovrin/agent/endpoint.py @@ -1,4 +1,4 @@ -from typing import Callable, Any, List, Dict, Tuple +from typing import Callable, Any, List from plenum.common.log import getlogger from plenum.common.raet import getHaFromLocalEstate diff --git a/sovrin/agent/msg_types.py b/sovrin/agent/msg_constants.py similarity index 87% rename from sovrin/agent/msg_types.py rename to sovrin/agent/msg_constants.py index da6b814..4c5554a 100644 --- a/sovrin/agent/msg_types.py +++ b/sovrin/agent/msg_constants.py @@ -9,8 +9,15 @@ CLAIM_PROOF_STATUS = 'CLAIM_PROOF_STATUS' NEW_AVAILABLE_CLAIMS = "NEW_AVAILABLE_CLAIMS" +CLAIM_REQ_FIELD = 'claimReq' +CLAIM_FIELD = 'claim' +PROOF_FIELD = 'proof' +PROOF_INPUT_FIELD = 'proofInput' +REVEALED_ATTRS_FIELD = 'revealedAttrs' + # Other CLAIM_NAME_FIELD = "claimName" +REF_REQUEST_ID = "refRequestId" """ ACCEPT_INVITE diff --git a/sovrin/agent/walleted.py b/sovrin/agent/walleted.py index fa55acc..cd48194 100644 --- a/sovrin/agent/walleted.py +++ b/sovrin/agent/walleted.py @@ -1,47 +1,50 @@ import asyncio import collections +import inspect import json +import time from abc import abstractmethod from datetime import datetime -from typing import Dict, Any +from typing import Dict, Union -import time -from anoncreds.protocol.issuer import Issuer -from anoncreds.protocol.issuer_secret_key import IssuerSecretKey -from anoncreds.protocol.proof_builder import ProofBuilder -from anoncreds.protocol.utils import strToCryptoInteger -from anoncreds.protocol.verifier import Verifier +from base58 import b58decode from plenum.common.log import getlogger from plenum.common.signer_did import DidSigner -from plenum.common.signer_simple import SimpleSigner +from plenum.common.signing import serializeMsg from plenum.common.txn import TYPE, DATA, NONCE, IDENTIFIER, NAME, VERSION, \ - TARGET_NYM, ORIGIN, ATTRIBUTES + TARGET_NYM, ATTRIBUTES, VERKEY from plenum.common.types import f from plenum.common.util import getTimeBasedId, getCryptonym, \ isMaxCheckTimeExpired, convertTimeBasedReqIdToMillis +from plenum.common.verifier import DidVerifier + +from anoncreds.protocol.issuer import Issuer +from anoncreds.protocol.prover import Prover +from anoncreds.protocol.verifier import Verifier +from sovrin.agent.agent_issuer import AgentIssuer +from sovrin.agent.agent_prover import AgentProver +from sovrin.agent.agent_verifier import AgentVerifier from sovrin.agent.constants import ALREADY_ACCEPTED_FIELD, CLAIMS_LIST_FIELD, \ REQ_MSG, PING, ERROR, EVENT, EVENT_NAME, EVENT_NOTIFY_MSG, \ EVENT_POST_ACCEPT_INVITE, PONG from sovrin.agent.exception import NonceNotFound, SignatureRejected -from sovrin.agent.msg_types import ACCEPT_INVITE, REQUEST_CLAIM, CLAIM_PROOF, \ - AVAIL_CLAIM_LIST, CLAIM, CLAIM_PROOF_STATUS, NEW_AVAILABLE_CLAIMS -from sovrin.anon_creds.constant import CRED_A, CRED_E, V_PRIME_PRIME +from sovrin.agent.msg_constants import ACCEPT_INVITE, REQUEST_CLAIM, \ + CLAIM_PROOF, \ + AVAIL_CLAIM_LIST, CLAIM, CLAIM_PROOF_STATUS, NEW_AVAILABLE_CLAIMS, \ + REF_REQUEST_ID from sovrin.client.wallet.attribute import Attribute, LedgerStore -from sovrin.client.wallet.claim import ClaimProofRequest -from sovrin.client.wallet.credential import Credential -from sovrin.client.wallet.link import Link, constant +from sovrin.client.wallet.link import Link, constant, ClaimProofRequest from sovrin.client.wallet.wallet import Wallet from sovrin.common.exceptions import LinkNotFound, LinkAlreadyExists, \ - NotConnectedToNetwork + NotConnectedToNetwork, LinkNotReady from sovrin.common.identity import Identity -from sovrin.common.txn import ATTR_NAMES, ENDPOINT -from sovrin.common.util import verifySig, ensureReqCompleted, getEncodedAttrs, \ - stringDictToCharmDict, getCredDefIsrKeyAndExecuteCallback, getNonceForProof +from sovrin.common.txn import ENDPOINT +from sovrin.common.util import ensureReqCompleted logger = getlogger() -class Walleted: +class Walleted(AgentIssuer, AgentProver, AgentVerifier): """ An agent with a self-contained wallet. @@ -50,7 +53,15 @@ class Walleted: case, the agent holds a wallet. """ - def __init__(self): + def __init__(self, + issuer: Issuer = None, + prover: Prover = None, + verifier: Verifier = None): + + AgentIssuer.__init__(self, issuer) + AgentProver.__init__(self, prover) + AgentVerifier.__init__(self, verifier) + # TODO Why are we syncing the client here? if self.client: self.syncClient() @@ -60,14 +71,17 @@ def __init__(self): EVENT: self._eventHandler, PING: self._handlePing, - ACCEPT_INVITE: self._acceptInvite, - REQUEST_CLAIM: self._reqClaim, + ACCEPT_INVITE: self._handleAcceptance, + + REQUEST_CLAIM: self.processReqClaim, + CLAIM: self.handleReqClaimResponse, + CLAIM_PROOF: self.verifyClaimProof, + CLAIM_PROOF_STATUS: self.handleProofStatusResponse, PONG: self._handlePong, AVAIL_CLAIM_LIST: self._handleAcceptInviteResponse, - CLAIM: self._handleReqClaimResponse, - CLAIM_PROOF_STATUS: self.handleClaimProofStatus, + NEW_AVAILABLE_CLAIMS: self._handleNewAvailableClaimsDataResponse } @@ -91,17 +105,17 @@ def wallet(self, wallet): def lockedMsgs(self): # Msgs for which signature verification is required return ACCEPT_INVITE, REQUEST_CLAIM, CLAIM_PROOF, \ - CLAIM, AVAIL_CLAIM_LIST, EVENT, PING, PONG + CLAIM, AVAIL_CLAIM_LIST, EVENT, PONG - def postClaimVerif(self, claimName, link, frm): + async def postClaimVerif(self, claimName, link, frm): raise NotImplementedError def isClaimAvailable(self, link, claimName): raise NotImplementedError - def _postClaimVerif(self, claimName, link, frm): + async def _postClaimVerif(self, claimName, link, frm): link.verifiedClaimProofs.append(claimName) - self.postClaimVerif(claimName, link, frm) + await self.postClaimVerif(claimName, link, frm) def getAvailableClaimList(self): raise NotImplementedError @@ -119,18 +133,19 @@ def logAndSendErrorResp(self, to, reqBody, respMsg, logMsg): self.signAndSend(msg=self.getErrorResponse(reqBody, respMsg), signingIdr=self.wallet.defaultId, toRaetStackName=to) - # TODO: Verification needs to be moved out of it, use `_isVerified` instead + # TODO: Verification needs to be moved out of it, + # use `verifySignature` instead def verifyAndGetLink(self, msg): body, (frm, ha) = msg - key = body.get(f.IDENTIFIER.nm) - - signature = body.get(f.SIG.nm) - verified = verifySig(key, signature, body) - if not verified: - self.logAndSendErrorResp(frm, body, "Signature Rejected", - "Signature verification failed for msg: {}" - .format(str(msg))) - return None + # key = body.get(f.IDENTIFIER.nm) + # + # signature = body.get(f.SIG.nm) + # verified = verifySig(key, signature, body) + # if not self.verifySignature(body): + # self.logAndSendErrorResp(frm, body, "Signature Rejected", + # "Signature verification failed for msg: {}" + # .format(str(msg))) + # return None nonce = body.get(NONCE) try: @@ -147,6 +162,8 @@ def linkFromNonce(self, nonce, remoteIdr, remoteHa): internalId = self.getInternalIdByInvitedNonce(nonce) link = self.wallet.getLinkByInternalId(internalId) if not link: + # QUESTION: We use wallet.defaultId as the local identifier, + # this looks ok for test code, but not production code link = Link(str(internalId), self.wallet.defaultId, invitationNonce=nonce, @@ -163,25 +180,33 @@ def linkFromNonce(self, nonce, remoteIdr, remoteHa): def getInternalIdByInvitedNonce(self, nonce): raise NotImplementedError - def signAndSend(self, msg, signingIdr, toRaetStackName, + def signAndSend(self, msg, signingIdr=None, toRaetStackName=None, linkName=None, origReqId=None): - if linkName: - assert not signingIdr - assert not toRaetStackName + assert not (signingIdr or toRaetStackName) self.connectTo(linkName) link = self.wallet.getLink(linkName, required=True) ha = link.getRemoteEndpoint(required=True) - signingIdr = self.wallet._requiredIdr(link.localIdentifier) + + # TODO ensure status is appropriate with code like the following + # if link.linkStatus != constant.LINK_STATUS_ACCEPTED: + # raise LinkNotReady('link status is {}'.format(link.linkStatus)) + + if not link.localIdentifier: + raise LinkNotReady('local identifier not set up yet') + signingIdr = link.localIdentifier params = dict(ha=ha) else: params = dict(name=toRaetStackName) # origReqId needs to be supplied when you want to respond to request # so that on receiving end, response can be matched with request + # if origReqId: + # msg[f.REQ_ID.nm] = origReqId + # else: + # msg[f.REQ_ID.nm] = getTimeBasedId() + msg[f.REQ_ID.nm] = getTimeBasedId() if origReqId: - msg[f.REQ_ID.nm] = origReqId - else: - msg[f.REQ_ID.nm] = getTimeBasedId() + msg[REF_REQUEST_ID] = origReqId msg[IDENTIFIER] = signingIdr signature = self.wallet.signMsg(msg, signingIdr) @@ -234,10 +259,10 @@ def notifyMsgListener(self, msg): def isSignatureVerifRespRequired(self, typ): return typ in self.lockedMsgs and typ not in [EVENT, PING, PONG] - def sendSigVerifResponseMsg(self, respMsg, to, reqMsgTyp): + def sendSigVerifResponseMsg(self, respMsg, to, reqMsgTyp, identifier): if self.isSignatureVerifRespRequired(reqMsgTyp): self.notifyToRemoteCaller(EVENT_NOTIFY_MSG, - respMsg, self.wallet.defaultId, to) + respMsg, identifier, to) def handleEndpointMessage(self, msg): body, frm = msg @@ -253,12 +278,21 @@ def handleEndpointMessage(self, msg): return typ = body.get(TYPE) + link = self.wallet.getLinkInvitationByTarget(body.get(f.IDENTIFIER.nm)) + + # If accept invite is coming the first time, then use the default + # identifier of the wallet since link wont be created + if typ == ACCEPT_INVITE and link is None: + localIdr = self.wallet.defaultId + else: + localIdr = link.localIdentifier + if typ in self.lockedMsgs: try: - self._isVerified(body) + self.verifySignature(body) except SignatureRejected: self.sendSigVerifResponseMsg("\nSignature rejected.", - frm, typ) + frm, typ, localIdr) return reqId = body.get(f.REQ_ID.nm) @@ -268,13 +302,18 @@ def handleEndpointMessage(self, msg): else: self.rcvdMsgStore[reqId] = [msg] - self.sendSigVerifResponseMsg("\nSignature accepted.", frm, typ) + # TODO: Question: Should we sending an acknowledgement for every message? + # We are sending, ACKs for "signature accepted" messages too + self.sendSigVerifResponseMsg("\nSignature accepted.", + frm, typ, localIdr) handler = self.msgHandlers.get(typ) if handler: # TODO we should verify signature here frmHa = self.endpoint.getRemote(frm).ha - handler((body, (frm, frmHa))) + res = handler((body, (frm, frmHa))) + if inspect.isawaitable(res): + self.loop.call_soon(asyncio.ensure_future, res) else: raise NotImplementedError("No type handle found for {} message". format(typ)) @@ -284,21 +323,12 @@ def _handleError(self, msg): self.notifyMsgListener("Error ({}) occurred while processing this " "msg: {}".format(body[DATA], body[REQ_MSG])) - def _sendGetClaimDefRequests(self, availableClaims, postFetchCredDef=None): - for name, version, origin in availableClaims: - req = self.wallet.requestClaimDef((name, version, origin), - sender=self.wallet.defaultId) - - self.client.submitReqs(req) - - if postFetchCredDef: - self.loop.call_later(.2, ensureReqCompleted, self.loop, - req.key, self.client, postFetchCredDef) - def _handlePing(self, msg): body, (frm, ha) = msg - self.signAndSend({TYPE: 'pong'}, self.wallet.defaultId, frm, - origReqId=body.get(f.REQ_ID.nm)) + link = self.wallet.getLinkByNonce(body.get(NONCE)) + if link: + self.signAndSend({TYPE: 'pong'}, self.wallet.defaultId, frm, + origReqId=body.get(f.REQ_ID.nm)) def _handlePong(self, msg): body, (frm, ha) = msg @@ -309,70 +339,32 @@ def _handlePong(self, msg): else: self.notifyMsgListener(" Pong received from unknown endpoint") - def _fetchAllAvailableClaimsInWallet(self, li, newAvailableClaims, - postAllFetched): - - fetchedCount = 0 - - def postEachCredDefFetch(reply, err): - nonlocal fetchedCount - fetchedCount += 1 - postAllCreDefFetched() - - self._sendGetClaimDefRequests(newAvailableClaims, postEachCredDefFetch) - - # TODO: Find a better name - def postAllCreDefFetched(): - if fetchedCount == len(newAvailableClaims): - postAllFetched(li, newAvailableClaims) - - postAllCreDefFetched() - def _handleNewAvailableClaimsDataResponse(self, msg): body, _ = msg - isVerified = self._isVerified(body) + isVerified = self.verifySignature(body) if isVerified: identifier = body.get(IDENTIFIER) li = self._getLinkByTarget(getCryptonym(identifier)) if li: self.notifyResponseFromMsg(li.name, body.get(f.REQ_ID.nm)) - def postAllFetched(li, newAvailableClaims): - if newAvailableClaims: - claimNames = ", ".join( - [n for n, _, _ in newAvailableClaims]) - self.notifyMsgListener( - " Available Claim(s): {}\n".format(claimNames)) + rcvdAvailableClaims = body[DATA][CLAIMS_LIST_FIELD] + newAvailableClaims = self._getNewAvailableClaims( + li, rcvdAvailableClaims) + if newAvailableClaims: + li.availableClaims.extend(newAvailableClaims) + claimNames = ", ".join( + [n for n, _, _ in newAvailableClaims]) + self.notifyMsgListener( + " Available Claim(s): {}\n".format(claimNames)) - self._processNewAvailableClaimsData( - li, body[DATA][CLAIMS_LIST_FIELD], postAllFetched) else: self.notifyMsgListener("No matching link found") - def _getNewAvailableClaims(self, li, rcvdAvailableClaims): - availableClaims = [] - for cl in rcvdAvailableClaims: - if not self.wallet.getClaimDef(seqNo=cl['claimDefSeqNo']): - name, version = cl[NAME], cl[VERSION] - availableClaims.append((name, version, - li.remoteIdentifier)) - - return availableClaims - - def _processNewAvailableClaimsData(self, li, rcvdAvailableClaims, - postAllFetched): - newAvailableClaims = self._getNewAvailableClaims( - li, rcvdAvailableClaims) - - # TODO: Handle case where agent can send claims in batches. - # So consider a scenario where first time an accept invite is - # sent, agent sends 2 claims and the second time accept - # invite is sent, agent sends 3 claims. - if newAvailableClaims: - li.availableClaims.extend(newAvailableClaims) - - self._fetchAllAvailableClaimsInWallet(li, newAvailableClaims, - postAllFetched) + @staticmethod + def _getNewAvailableClaims(li, rcvdAvailableClaims): + return [(cl[NAME], cl[VERSION], li.remoteIdentifier) for cl in + rcvdAvailableClaims] def _handleAcceptInviteResponse(self, msg): body, _ = msg @@ -389,52 +381,64 @@ def _handleAcceptInviteResponse(self, msg): self.notifyMsgListener(" Identifier created in Sovrin.") li.linkStatus = constant.LINK_STATUS_ACCEPTED - li.targetVerkey = constant.TARGET_VER_KEY_SAME_AS_ID - self._processNewAvailableClaimsData( - li, body[DATA][CLAIMS_LIST_FIELD], - self._syncLinkPostAvailableClaimsRcvd) + # li.targetVerkey = constant.TARGET_VER_KEY_SAME_AS_ID + + rcvdAvailableClaims = body[DATA][CLAIMS_LIST_FIELD] + newAvailableClaims = self._getNewAvailableClaims( + li, rcvdAvailableClaims) + if newAvailableClaims: + li.availableClaims.extend(newAvailableClaims) + self.notifyMsgListener(" Available Claim(s): {}". + format(",".join( + [n for n, _, _ in newAvailableClaims]))) + + self._checkIfLinkIdentifierWrittenToSovrin(li, + newAvailableClaims) + else: self.notifyMsgListener("No matching link found") - def _handleReqClaimResponse(self, msg): - body, _ = msg - issuerId = body.get(IDENTIFIER) - claim = body[DATA] - li = self._getLinkByTarget(getCryptonym(issuerId)) - if li: - self.notifyResponseFromMsg(li.name, body.get(f.REQ_ID.nm)) - self.notifyMsgListener(' Received claim "{}".\n'.format( - claim[NAME])) - name, version, claimAuthor = \ - claim[NAME], claim[VERSION], claim[f.IDENTIFIER.nm] - claimDefKey = (name, version, claimAuthor) - attributes = claim['attributes'] - self.wallet.addAttrFrom(issuerId, attributes) - issuerKey = self.wallet.getIssuerPublicKeyForClaimDef( - issuerId=issuerId, claimDefKey=claimDefKey) - if not issuerKey: - raise RuntimeError("Issuer key not available for claim def {}". - format(claimDefKey)) - vprime = next(iter(self.wallet.getVPrimes(issuerId).values())) - credential = Credential.buildFromIssuerProvidedCred( - issuerKey.seqNo, A=claim[CRED_A], e=claim[CRED_E], - v=claim[V_PRIME_PRIME], - vprime=vprime) - self.wallet.addCredential("{} {} {}". - format(li.name, name, version), - credential) + def getVerkeyForLink(self, link): + # TODO: Get latest verkey for this link's remote identifier from Sovrin + if link.targetVerkey: + return link.targetVerkey else: - self.notifyMsgListener("No matching link found") + raise Exception("verkey not set in link") - def _isVerified(self, msg: Dict[str, str], verifyWithIdr=None): - # v = DidVerifier() - signature = msg.get(f.SIG.nm) - identifier = verifyWithIdr or msg.get(IDENTIFIER) + def getLinkForMsg(self, msg): + nonce = msg.get(NONCE) + link = self.wallet.getLinkByNonce(nonce) + if link: + return link + else: + raise LinkNotFound + def verifySignature(self, msg: Dict[str, str]): + signature = msg.get(f.SIG.nm) + identifier = msg.get(IDENTIFIER) msgWithoutSig = {k: v for k, v in msg.items() if k != f.SIG.nm} # TODO This assumes the current key is the cryptonym. This is a BAD # ASSUMPTION!!! Sovrin needs to provide the current key. - if not verifySig(identifier, signature, msgWithoutSig): + ser = serializeMsg(msgWithoutSig) + signature = b58decode(signature.encode()) + typ = msg.get(TYPE) + # TODO: Maybe keeping ACCEPT_INVITE open is a better option than keeping + # an if condition here? + if typ == ACCEPT_INVITE: + verkey = msg.get(VERKEY) + else: + try: + link = self.getLinkForMsg(msg) + verkey = self.getVerkeyForLink(link) + except LinkNotFound: + # This is for verification of `NOTIFY` events + link = self.wallet.getLinkInvitationByTarget(identifier) + # TODO: If verkey is None, it should be fetched from Sovrin. + # Assuming CID for now. + verkey = link.targetVerkey + + v = DidVerifier(verkey, identifier=identifier) + if not v.verify(signature, ser): raise SignatureRejected else: return True @@ -442,20 +446,13 @@ def _isVerified(self, msg: Dict[str, str], verifyWithIdr=None): def _getLinkByTarget(self, target) -> Link: return self.wallet.getLinkInvitationByTarget(target) - def _syncLinkPostAvailableClaimsRcvd(self, li, newAvailableClaims): - if newAvailableClaims: - self.notifyMsgListener(" Available Claim(s): {}". - format(",".join( - [n for n, _, _ in newAvailableClaims]))) - self._checkIfLinkIdentifierWrittenToSovrin(li, newAvailableClaims) - def _checkIfLinkIdentifierWrittenToSovrin(self, li: Link, availableClaims): - req = self.getIdentity(li.verkey) + req = self.getIdentity(li.localIdentifier) self.notifyMsgListener("\nSynchronizing...") def getNymReply(reply, err, availableClaims, li: Link): if reply.get(DATA) and json.loads(reply[DATA])[TARGET_NYM] == \ - li.verkey: + li.localIdentifier: self.notifyMsgListener( " Confirmed identifier written to Sovrin.") availableClaimNames = [n for n, _, _ in availableClaims] @@ -470,126 +467,16 @@ def getNymReply(reply, err, availableClaims, li: Link): self.loop.call_later(.2, ensureReqCompleted, self.loop, req.key, self.client, getNymReply, (availableClaims, li)) - def _reqClaim(self, msg): - body, (frm, ha) = msg - link = self.verifyAndGetLink(msg) - if link: - name = body[NAME] - if not self.isClaimAvailable(link, name): - self.notifyToRemoteCaller( - EVENT_NOTIFY_MSG, "This claim is not yet available", - self.wallet.defaultId, frm, origReqId=body.get(f.REQ_ID.nm)) - return - - version = body[VERSION] - origin = body[ORIGIN] - # TODO: Need to do validation - uValue = strToCryptoInteger(body['U']) - claimDef = self.wallet.getClaimDef(key=(name, version, origin)) - attributes = self._getClaimsAttrsFor(link.internalId, - claimDef.attrNames) - encodedAttrs = next(iter(getEncodedAttrs(link.verkey, - attributes).values())) - pk = self.wallet.getIssuerPublicKeyForClaimDef(link.localIdentifier, - claimDef.seqNo) - sk = self.wallet.getClaimDefSk(claimDefSeqNo=claimDef.seqNo) - cred = Issuer.generateCredential(uValue, encodedAttrs, pk, sk) - claimDetails = { - NAME: claimDef.name, - VERSION: claimDef.version, - 'attributes': attributes, - # TODO: the name should not be identifier but origin - f.IDENTIFIER.nm: claimDef.origin, - 'A': str(cred[0]), - 'e': str(cred[1]), - 'vprimeprime': str(cred[2]) - } - resp = self.createClaimMsg(claimDetails) - self.signAndSend(resp, link.localIdentifier, frm, - origReqId=body.get(f.REQ_ID.nm)) - else: - raise NotImplementedError - - def verifyClaimProof(self, msg: Any): - body, (frm, ha) = msg - link = self.verifyAndGetLink(msg) - if link: - proof = body['proof'] - encodedAttrs = body['encodedAttrs'] - for iid, attrs in encodedAttrs.items(): - encodedAttrs[iid] = stringDictToCharmDict(attrs) - revealedAttrs = body['revealedAttrs'] - nonce = getNonceForProof(body[NONCE]) - claimDefKeys = {iid: tuple(_) - for iid, _ in body['claimDefKeys'].items()} - - fetchedClaimDefs = 0 - - def verify(r, e): - # ASSUMPTION: This assumes that author of claimDef is same - # as the author of issuerPublicKey - # TODO: Do json validation - nonlocal proof, nonce, body, claimDefKeys, fetchedClaimDefs - # TODO: This is not thread safe, can lead to race condition - fetchedClaimDefs += 1 - if fetchedClaimDefs < len(claimDefKeys): - return - - proof = ProofBuilder.prepareProofFromDict(proof) - issuerPks = {} - for issuerId, claimDefKey in claimDefKeys.items(): - # name, version, origin = claimDefKey - claimDef = self.wallet.getClaimDef(key=claimDefKey) - issuerKey = self.wallet.getIssuerPublicKeyForClaimDef( - issuerId=issuerId, seqNo=claimDef.seqNo) - issuerPks[issuerId] = issuerKey - - claimName = body[NAME] - - # REMOVE-LOG: Remove the next log - logger.debug("issuerPks, proof, nonce, encoded, revealed is " - "{} {} {} {} {}". - format(issuerPks, proof, nonce, encodedAttrs, - revealedAttrs)) - - result = Verifier.verifyProof(issuerPks, proof, nonce, - encodedAttrs, - revealedAttrs) - - if result: - logger.info("proof {} verified".format(claimName)) - else: - logger.warning("proof {} failed verification". - format(claimName)) - - status = 'verified' if result else 'failed verification' - resp = { - TYPE: CLAIM_PROOF_STATUS, - DATA: ' Your claim {} {} was received and {}\n'. - format(body[NAME], body[VERSION], status), - } - self.signAndSend(resp, link.localIdentifier, frm, - origReqId=body.get(f.REQ_ID.nm)) - - if result: - self._postClaimVerif(claimName, link, frm) - - for claimDefKey in claimDefKeys.values(): - getCredDefIsrKeyAndExecuteCallback(self.wallet, - self.client, - print, - self.loop, - claimDefKey, - verify) - def notifyResponseFromMsg(self, linkName, reqId=None): if reqId: # TODO: This logic assumes that the req id is time based curTimeBasedId = getTimeBasedId() - timeTakenInMillis = convertTimeBasedReqIdToMillis(curTimeBasedId - reqId) + timeTakenInMillis = convertTimeBasedReqIdToMillis( + curTimeBasedId - reqId) if timeTakenInMillis >= 1000: - responseTime = ' ({} sec)'.format(round(timeTakenInMillis/1000, 2)) + responseTime = ' ({} sec)'.format( + round(timeTakenInMillis / 1000, 2)) else: responseTime = ' ({} ms)'.format(round(timeTakenInMillis, 2)) else: @@ -598,23 +485,15 @@ def notifyResponseFromMsg(self, linkName, reqId=None): self.notifyMsgListener("\nResponse from {}{}:".format(linkName, responseTime)) - def handleClaimProofStatus(self, msg: Any): - body, _ = msg - data = body.get(DATA) - identifier = body.get(IDENTIFIER) - li = self._getLinkByTarget(getCryptonym(identifier)) - self.notifyResponseFromMsg(li.name, body.get(f.REQ_ID.nm)) - self.notifyMsgListener(data) - - def notifyToRemoteCaller(self, event, msg, signingIdr, frm, origReqId=None): + def notifyToRemoteCaller(self, event, msg, signingIdr, to, origReqId=None): resp = { TYPE: EVENT, EVENT_NAME: event, DATA: {'msg': msg} } - self.signAndSend(resp, signingIdr, frm, origReqId=origReqId) + self.signAndSend(resp, signingIdr, to, origReqId=origReqId) - def _acceptInvite(self, msg): + def _handleAcceptance(self, msg): body, (frm, ha) = msg link = self.verifyAndGetLink(msg) # TODO this is really kludgy code... needs refactoring @@ -623,7 +502,9 @@ def _acceptInvite(self, msg): return logger.debug("proceeding with link: {}".format(link.name)) identifier = body.get(f.IDENTIFIER.nm) - idy = Identity(identifier) + verkey = body.get(VERKEY) + idy = Identity(identifier, verkey=verkey) + link.targetVerkey = verkey try: pendingCount = self.wallet.addSponsoredIdentity(idy) logger.debug("pending request count {}".format(pendingCount)) @@ -652,34 +533,23 @@ def sendClaimList(reply=None, error=None): # link.verkey, frm) else: logger.debug( - "not accepted, so add nym to sovrin " + "not added to the ledger, so add nym to the ledger " "and then will send available claims") reqs = self.wallet.preparePending() # Assuming there was only one pending request logger.debug("sending to sovrin {}".format(reqs[0])) self._sendToSovrinAndDo(reqs[0], clbk=sendClaimList) - # TODO: If I have the below exception thrown, somehow the - # error msg which is sent in verifyAndGetLink is not being received - # on the other end, so for now, commented, need to come back to this - # else: - # raise NotImplementedError + # TODO: If I have the below exception thrown, somehow the + # error msg which is sent in verifyAndGetLink is not being received + # on the other end, so for now, commented, need to come back to this + # else: + # raise NotImplementedError def _sendToSovrinAndDo(self, req, clbk=None, *args): self.client.submitReqs(req) ensureReqCompleted(self.loop, req.key, self.client, clbk, *args) - def _getClaimsAttrsFor(self, internalId, attrNames): - res = {} - attributes = self.getAttributes(internalId) - if attributes: - for nm in attrNames: - res[nm] = attributes.get(nm) - return res - - def getAttributes(self, nonce): - raise NotImplementedError - def newAvailableClaimsPostClaimVerif(self, claimName): raise NotImplementedError @@ -688,8 +558,19 @@ def sendNewAvailableClaimsData(self, nac, frm, link): resp = self.createNewAvailableClaimsMsg(nac) self.signAndSend(resp, link.localIdentifier, frm) - def sendPing(self, name): - reqId = self.signAndSend({TYPE: 'ping'}, None, None, name) + def sendPing(self, linkName): + link = self.wallet.getLink(linkName, required=True) + self.connectTo(linkName) + ha = link.getRemoteEndpoint(required=True) + params = dict(ha=ha) + msg = { + TYPE: 'ping', + NONCE: link.invitationNonce, + f.REQ_ID.nm: getTimeBasedId(), + f.IDENTIFIER.nm: link.localIdentifier + } + reqId = self.sendMessage(msg, **params) + self.notifyMsgListener(" Ping sent.") return reqId @@ -711,25 +592,24 @@ def loadInvitation(self, invitationData): if claimProofRequestsJson: for cr in claimProofRequestsJson: claimProofRequests.append( - ClaimProofRequest(cr[NAME], cr[VERSION], cr[ATTRIBUTES])) + ClaimProofRequest(cr[NAME], cr[VERSION], cr[ATTRIBUTES], + cr['verifiableAttributes'])) self.notifyMsgListener("1 link invitation found for {}.". format(linkInvitationName)) - # TODO: Assuming it is cryptographic identifier - alias = "cid-" + str(len(self.wallet.identifiers) + 1) - signer = SimpleSigner(alias=alias) - self.wallet.addIdentifier(signer=signer) self.notifyMsgListener("Creating Link for {}.". format(linkInvitationName)) self.notifyMsgListener("Generating Identifier and Signing key.") # TODO: Would we always have a trust anchor corresponding ot a link? - trustAnchor = linkInvitationName - li = Link(linkInvitationName, - signer.alias + ":" + signer.identifier, - trustAnchor, remoteIdentifier, - remoteEndPoint, linkNonce, - claimProofRequests, invitationData=invitationData) + + li = Link(name=linkInvitationName, + trustAnchor=linkInvitationName, + remoteIdentifier=remoteIdentifier, + remoteEndPoint=remoteEndPoint, + invitationNonce=linkNonce, + claimProofRequests=claimProofRequests) + self.wallet.addLink(li) return li @@ -741,7 +621,7 @@ def loadInvitationFile(self, filePath): if not linkInvitation: raise LinkNotFound linkName = linkInvitation["name"] - existingLinkInvites = self.wallet.\ + existingLinkInvites = self.wallet. \ getMatchingLinks(linkName) if len(existingLinkInvites) >= 1: raise LinkAlreadyExists @@ -749,15 +629,28 @@ def loadInvitationFile(self, filePath): link = self.loadInvitation(invitationData) return link - def acceptInvitation(self, linkName): - link = self.wallet.getLink(linkName, required=True) - idr = self.wallet._requiredIdr(link.localIdentifier) + def acceptInvitation(self, link: Union[str, Link]): + if isinstance(link, str): + link = self.wallet.getLink(link, required=True) + elif isinstance(link, Link): + pass + else: + raise TypeError("Type of link must be either string or Link but " + "provided {}".format(type(link))) + # TODO should move to wallet in a method like accept(link) + if not link.localIdentifier: + signer = DidSigner() + self.wallet.addIdentifier(signer=signer) + link.localIdentifier = signer.identifier msg = { TYPE: ACCEPT_INVITE, - f.IDENTIFIER.nm: idr, + # TODO should not send this... because origin should be the sender NONCE: link.invitationNonce, + VERKEY: self.wallet.getVerkey(link.localIdentifier) } - self.signAndSend(msg, None, None, linkName) + logger.debug("{} accepting invitation from {} with id {}". + format(self.name, link.name, link.localIdentifier)) + self.signAndSend(msg, None, None, link.name) def _handleSyncResp(self, link, additionalCallback): def _(reply, err): @@ -772,6 +665,7 @@ def _(reply, err): additionalCallback, reply, err) else: additionalCallback(reply, err) + return _ def _updateLinkWithLatestInfo(self, link: Link, reply): @@ -783,14 +677,16 @@ def _updateLinkWithLatestInfo(self, link: Link, reply): link.linkLastSynced = datetime.now() self.notifyMsgListener(" Link {} synced".format(link.name)) - if link.remoteEndPoint: - reqId = self._pingToEndpoint(link.name, link.remoteEndPoint) - return reqId + # TODO need to move this to after acceptance, + # unless we want to support an anonymous ping + # if link.remoteEndPoint: + # reqId = self._pingToEndpoint(link.name, link.remoteEndPoint) + # return reqId def _pingToEndpoint(self, name, endpoint): self.notifyMsgListener("\nPinging target endpoint: {}". format(endpoint)) - reqId = self.sendPing(name=name) + reqId = self.sendPing(linkName=name) return reqId def sync(self, linkName, doneCallback=None): @@ -802,8 +698,7 @@ def sync(self, linkName, doneCallback=None): value=None, dest=nym, ledgerStore=LedgerStore.RAW) - req = self.wallet.requestAttribute( - attrib, sender=self.wallet.defaultId) + req = self.wallet.requestAttribute(attrib, sender=self.wallet.defaultId) self.client.submitReqs(req) if doneCallback: @@ -819,9 +714,9 @@ def executeWhenResponseRcvd(self, startTime, maxCheckForMillis, checkIfLinkExists, clbk, *args): if isMaxCheckTimeExpired(startTime, maxCheckForMillis): - clbk(None, "No response received within specified time ({} mills). " - "Retry the command and see if that works.\n". - format(maxCheckForMillis)) + clbk(None, "No response received within specified time ({} mills). " + "Retry the command and see if that works.\n". + format(maxCheckForMillis)) else: found = False rcvdResponses = self.rcvdMsgStore.get(reqId) @@ -846,4 +741,3 @@ def executeWhenResponseRcvd(self, startTime, maxCheckForMillis, loop.call_later(.2, self.executeWhenResponseRcvd, startTime, maxCheckForMillis, loop, reqId, respType, checkIfLinkExists, clbk, *args) - diff --git a/sovrin/anon_creds/cred_def.py b/sovrin/anon_creds/cred_def.py deleted file mode 100644 index 66a4a59..0000000 --- a/sovrin/anon_creds/cred_def.py +++ /dev/null @@ -1,50 +0,0 @@ -from abc import abstractmethod, abstractproperty -from enum import Enum - - -class CredDefPublicKey: - @abstractmethod - def __init__(self, *args, **kwargs): - pass - - -class CredDef: - @abstractmethod - def __init__(self, *args, **kwargs): - pass - - @abstractproperty - def name(self) -> str: - pass - - @abstractproperty - def version(self) -> str: - pass - - @abstractproperty - def serializedSK(self) -> str: - pass - - @abstractmethod - def get(self, *args, **kwargs): - pass - - @abstractmethod - def getPk(*args, **kwargs): - pass - - @abstractmethod - def getCryptoInteger(cls, *args, **kwargs): - pass - - @abstractmethod - def getStaticPPrime(cls, *args, **kwargs): - pass - - @abstractmethod - def getStaticQPrime(cls, *args, **kwargs): - pass - - @abstractmethod - def getEncodedAttrs(cls, *args, **kwargs): - pass \ No newline at end of file diff --git a/sovrin/anon_creds/issuer.py b/sovrin/anon_creds/issuer.py deleted file mode 100644 index ae76c46..0000000 --- a/sovrin/anon_creds/issuer.py +++ /dev/null @@ -1,82 +0,0 @@ -from abc import abstractmethod - -from sovrin.anon_creds.cred_def import CredDef - - -class Credential: - @abstractmethod - def __init__(self, *args, **kwargs): - pass - - -class AttribType: - @abstractmethod - def __init__(self, *args, **kwargs): - pass - - -class Attribs: - @abstractmethod - def __init__(self, *args, **kwargs): - pass - - @abstractmethod - def encoded(self): - pass - - -class AttribDef: - @abstractmethod - def __init__(self, *args, **kwargs): - pass - - @abstractmethod - def attribs(self, *args, **kwargs) -> Attribs: - pass - - @abstractmethod - def attribNames(self): - pass - - -class AttrRepo: - @abstractmethod - def getAttributes(self, *args, **kwargs): - pass - - @abstractmethod - def addAttributes(self, *args, **kwargs): - pass - - -class InMemoryAttrRepo(AttrRepo): - @abstractmethod - def __init__(self): - pass - - @abstractmethod - def getAttributes(self, *args, **kwargs): - pass - - @abstractmethod - def addAttributes(self, *args, **kwargs): - pass - - -class Issuer: - - @abstractmethod - def __init__(self, *args, **kwargs): - pass - - @abstractmethod - def addNewCredDef(self, *args, **kwargs) -> CredDef: - pass - - @abstractmethod - def getCredDef(self, *args, **kwargs)-> CredDef: - pass - - @abstractmethod - def createCred(self, *args, **kwargs): - pass diff --git a/sovrin/anon_creds/proof_builder.py b/sovrin/anon_creds/proof_builder.py deleted file mode 100644 index 357fa3c..0000000 --- a/sovrin/anon_creds/proof_builder.py +++ /dev/null @@ -1,66 +0,0 @@ -from abc import abstractmethod, abstractproperty - - -class Proof: - @abstractmethod - def __init__(self, *args, **kwargs): - pass - - -class PredicateProof: - @abstractmethod - def __init__(self, *args, **kwargs): - pass - - -class Credential: - def __init__(self, *args, **kwargs): - pass - - -class ProofBuilder: - - @abstractmethod - def __init__(self, *args, **kwargs): - pass - - @abstractproperty - def id(self): - pass - - @abstractproperty - def masterSecret(self): - pass - - @abstractproperty - def U(self): - pass - - @abstractproperty - def vprime(self): - pass - - @abstractmethod - def setParams(self, *args, **kwargs): - pass - - @abstractmethod - def prepareProof(*args, **kwargs) -> Proof: - pass - - @abstractmethod - def prepareProofAsDict(*args, **kwargs) -> dict: - pass - - @abstractmethod - def prepareProofFromDict(*args, **kwargs) -> Proof: - pass - - @abstractmethod - def preparePredicateProof(self, *args, **kwargs) -> PredicateProof: - pass - - - - - diff --git a/sovrin/anon_creds/prover.py b/sovrin/anon_creds/prover.py deleted file mode 100644 index e0aaa8f..0000000 --- a/sovrin/anon_creds/prover.py +++ /dev/null @@ -1,28 +0,0 @@ -from abc import abstractmethod - -from sovrin.anon_creds.cred_def import CredDefPublicKey, CredDef -from sovrin.anon_creds.proof_builder import ProofBuilder - - -class Prover: - - @abstractmethod - def __init__(self, *args, **kwargs): - pass - - @abstractmethod - def createProofBuilder(self, *args, **kwargs) -> ProofBuilder: - pass - - # TODO: mention return type - @abstractmethod - def fetchNonce(self, *args, **kwargs): - pass - - @abstractmethod - def fetchCredentialDefinition(self, *args, **kwargs) -> CredDef: - pass - - @abstractmethod - def fetchCredential(self, *args, **kwargs): - pass diff --git a/sovrin/anon_creds/sovrin_issuer.py b/sovrin/anon_creds/sovrin_issuer.py new file mode 100644 index 0000000..14aef16 --- /dev/null +++ b/sovrin/anon_creds/sovrin_issuer.py @@ -0,0 +1,13 @@ +from anoncreds.protocol.issuer import Issuer +from anoncreds.protocol.repo.attributes_repo import AttributeRepo +from anoncreds.protocol.repo.public_repo import PublicRepo +from anoncreds.protocol.wallet.issuer_wallet import IssuerWalletInMemory +from sovrin.anon_creds.sovrin_public_repo import SovrinPublicRepo +from sovrin.client.wallet.wallet import Wallet + + +class SovrinIssuer(Issuer): + def __init__(self, client, wallet: Wallet, attrRepo: AttributeRepo, publicRepo: PublicRepo = None): + publicRepo = publicRepo or SovrinPublicRepo(client=client, wallet=wallet) + issuerWallet = IssuerWalletInMemory(wallet.name, publicRepo) + super().__init__(issuerWallet, attrRepo) diff --git a/sovrin/anon_creds/sovrin_prover.py b/sovrin/anon_creds/sovrin_prover.py new file mode 100644 index 0000000..6d5134c --- /dev/null +++ b/sovrin/anon_creds/sovrin_prover.py @@ -0,0 +1,12 @@ +from anoncreds.protocol.prover import Prover +from anoncreds.protocol.repo.public_repo import PublicRepo +from anoncreds.protocol.wallet.prover_wallet import ProverWalletInMemory +from sovrin.anon_creds.sovrin_public_repo import SovrinPublicRepo +from sovrin.client.wallet.wallet import Wallet + + +class SovrinProver(Prover): + def __init__(self, client, wallet: Wallet, publicRepo: PublicRepo = None): + publicRepo = publicRepo or SovrinPublicRepo(client=client, wallet=wallet) + proverWallet = ProverWalletInMemory(wallet.name, publicRepo) + super().__init__(proverWallet) diff --git a/sovrin/anon_creds/sovrin_public_repo.py b/sovrin/anon_creds/sovrin_public_repo.py new file mode 100644 index 0000000..45ee748 --- /dev/null +++ b/sovrin/anon_creds/sovrin_public_repo.py @@ -0,0 +1,158 @@ +import json + +from ledger.util import F +from plenum.common.txn import TARGET_NYM, TXN_TYPE, DATA, NAME, VERSION, TYPE, \ + ORIGIN +from plenum.test.eventually import eventually + +from anoncreds.protocol.repo.public_repo import PublicRepo +from anoncreds.protocol.types import ClaimDefinition, ID, PublicKey, \ + RevocationPublicKey, AccumulatorPublicKey, \ + Accumulator, TailsType, TimestampType +from sovrin.common.txn import GET_CLAIM_DEF, CLAIM_DEF, ATTR_NAMES, \ + GET_ISSUER_KEY, REF, ISSUER_KEY, PRIMARY, REVOCATION +from sovrin.common.types import Request + + +def _ensureReqCompleted(reqKey, client, clbk): + reply, err = client.replyIfConsensus(*reqKey) + if reply is None: + raise ValueError('not completed') + return clbk(reply, err) + + +def _getData(result, error): + data = json.loads(result.get(DATA).replace("\'", '"')) + seqNo = None if not data else data.get(F.seqNo.name) + return data, seqNo + + +def _submitData(result, error): + data = json.loads(result.get(DATA).replace("\'", '"')) + seqNo = result.get(F.seqNo.name) + return data, seqNo + + +class SovrinPublicRepo(PublicRepo): + def __init__(self, client, wallet): + self.client = client + self.wallet = wallet + self.displayer = print + + async def getClaimDef(self, id: ID) -> ClaimDefinition: + op = { + TARGET_NYM: id.claimDefKey.issuerId, + TXN_TYPE: GET_CLAIM_DEF, + DATA: { + NAME: id.claimDefKey.name, + VERSION: id.claimDefKey.version, + } + } + data, seqNo = await self._sendGetReq(op) + return ClaimDefinition(name=data[NAME], + version=data[VERSION], + claimDefType=data[TYPE], + attrNames=data[ATTR_NAMES].split(","), + issuerId=data[ORIGIN], + seqId=seqNo) + + async def getPublicKey(self, id: ID) -> PublicKey: + op = { + TXN_TYPE: GET_ISSUER_KEY, + REF: id.claimDefId, + ORIGIN: id.claimDefKey.issuerId + } + + data, seqNo = await self._sendGetReq(op) + if not data: + return None + + data = data[DATA][PRIMARY] + pk = PublicKey.fromStrDict(data)._replace(seqId=seqNo) + return pk + + async def getPublicKeyRevocation(self, id: ID) -> RevocationPublicKey: + op = { + TXN_TYPE: GET_ISSUER_KEY, + REF: id.claimDefId, + ORIGIN: id.claimDefKey.issuerId + } + + data, seqNo = await self._sendGetReq(op) + if not data: + return None + + data = data[DATA][REVOCATION] + pkR = RevocationPublicKey.fromStrDict(data)._replace(seqId=seqNo) + return pkR + + async def getPublicKeyAccumulator(self, id: ID) -> AccumulatorPublicKey: + pass + + async def getAccumulator(self, id: ID) -> Accumulator: + pass + + async def getTails(self, id: ID) -> TailsType: + pass + + # SUBMIT + + async def submitClaimDef(self, + claimDef: ClaimDefinition) -> ClaimDefinition: + op = { + TXN_TYPE: CLAIM_DEF, + DATA: { + NAME: claimDef.name, + VERSION: claimDef.version, + TYPE: claimDef.claimDefType, + ATTR_NAMES: ",".join(claimDef.attrNames) + } + } + + data, seqNo = await self._sendSubmitReq(op) + if not seqNo: + return None + claimDef = claimDef._replace(issuerId=self.wallet.defaultId, + seqId=seqNo) + return claimDef + + async def submitPublicKeys(self, id: ID, pk: PublicKey, + pkR: RevocationPublicKey = None) -> ( + PublicKey, RevocationPublicKey): + pkData = pk.toStrDict() + pkRData = pkR.toStrDict() + op = { + TXN_TYPE: ISSUER_KEY, + REF: id.claimDefId, + DATA: {PRIMARY: pkData, REVOCATION: pkRData} + } + + data, seqNo = await self._sendSubmitReq(op) + if not seqNo: + return None + pk = pk._replace(seqId=seqNo) + pkR = pkR._replace(seqId=seqNo) + return (pk, pkR) + + async def submitAccumulator(self, id: ID, accumPK: AccumulatorPublicKey, + accum: Accumulator, tails: TailsType): + pass + + async def submitAccumUpdate(self, id: ID, accum: Accumulator, + timestampMs: TimestampType): + pass + + async def _sendSubmitReq(self, op): + return await self._sendReq(op, _submitData) + + async def _sendGetReq(self, op): + return await self._sendReq(op, _getData) + + async def _sendReq(self, op, clbk): + req = Request(identifier=self.wallet.defaultId, operation=op) + req = self.wallet.prepReq(req) + self.client.submitReqs(req) + + return await eventually(_ensureReqCompleted, + req.key, self.client, clbk, + timeout=20, retryWait=0.5) diff --git a/sovrin/anon_creds/sovrin_verifier.py b/sovrin/anon_creds/sovrin_verifier.py new file mode 100644 index 0000000..8e7278d --- /dev/null +++ b/sovrin/anon_creds/sovrin_verifier.py @@ -0,0 +1,14 @@ +from anoncreds.protocol.repo.public_repo import PublicRepo + +from anoncreds.protocol.verifier import Verifier +from anoncreds.protocol.wallet.wallet import WalletInMemory + +from sovrin.anon_creds.sovrin_public_repo import SovrinPublicRepo +from sovrin.client.wallet.wallet import Wallet + + +class SovrinVerifier(Verifier): + def __init__(self, client, wallet: Wallet, publicRepo: PublicRepo = None): + publicRepo = publicRepo or SovrinPublicRepo(client=client, wallet=wallet) + verifierWallet = WalletInMemory(wallet.defaultId, publicRepo) + super().__init__(verifierWallet) diff --git a/sovrin/anon_creds/verifier.py b/sovrin/anon_creds/verifier.py deleted file mode 100644 index 25dd8de..0000000 --- a/sovrin/anon_creds/verifier.py +++ /dev/null @@ -1,32 +0,0 @@ -from abc import abstractmethod -from typing import Any - -from sovrin.anon_creds.cred_def import CredDef - - -class Verifier: - - @abstractmethod - def __init__(self, *args, **kwargs): - pass - - # TODO: mention return type - @abstractmethod - def generateNonce(self, *args, **kwargs): - pass - - @abstractmethod - def getCredDef(self, *args, **kwargs) -> CredDef: - pass - - @abstractmethod - def fetchCredDef(self, *args, **kwargs) -> CredDef: - pass - - @abstractmethod - def verifyPredicateProof(self, *args, **kwargs)-> bool: - pass - - @abstractmethod - def verifyProof(cls, *args, **kwargs) -> bool: - pass \ No newline at end of file diff --git a/sovrin/cli/cli.py b/sovrin/cli/cli.py index 2f1ae01..d7c43cb 100644 --- a/sovrin/cli/cli.py +++ b/sovrin/cli/cli.py @@ -1,61 +1,47 @@ import ast import datetime +import importlib import json import os -import uuid from functools import partial from hashlib import sha256 from typing import Dict, Any, Tuple, Callable +import asyncio +from plenum.cli.cli import Cli as PlenumCli +from plenum.cli.helper import getClientGrams from plenum.common.constants import ENVS +from plenum.common.signer_simple import SimpleSigner +from plenum.common.txn import NAME, VERSION, TYPE +from plenum.common.txn_util import createGenesisTxnFile +from plenum.common.util import randomString from prompt_toolkit.contrib.completers import WordCompleter from prompt_toolkit.layout.lexers import SimpleLexer from pygments.token import Token -import sovrin.anon_creds.cred_def as CredDefModule -from anoncreds.protocol.cred_def_secret_key import CredDefSecretKey from anoncreds.protocol.globals import KEYS -from anoncreds.protocol.issuer_secret_key import IssuerSecretKey -from anoncreds.protocol.types import SerFmt -from anoncreds.protocol.utils import strToCryptoInteger -from anoncreds.test.conftest import staticPrimes -from anoncreds.test.cred_def_test_store import MemoryCredDefStore -from anoncreds.test.issuer_key_test_store import MemoryIssuerKeyStore -from ledger.util import F -from plenum.cli.cli import Cli as PlenumCli -from plenum.cli.helper import getClientGrams -from plenum.common.signer_simple import SimpleSigner -from plenum.common.txn import NAME, VERSION, TYPE, ORIGIN -from plenum.common.txn_util import createGenesisTxnFile -from plenum.common.types import f -from plenum.common.util import randomString, getTimeBasedId -from sovrin.agent.constants import EVENT_NOTIFY_MSG, EVENT_POST_ACCEPT_INVITE +from anoncreds.protocol.types import ClaimDefinition, ID from sovrin.agent.agent import WalletedAgent -from sovrin.agent.msg_types import ACCEPT_INVITE, REQUEST_CLAIM, CLAIM_PROOF -from sovrin.anon_creds.constant import V_PRIME_PRIME, ISSUER, \ - CRED_E, CRED_A, NONCE, ATTRS, PROOF, REVEALED_ATTRS -from sovrin.anon_creds.issuer import AttribDef, AttribType -from sovrin.anon_creds.issuer import InMemoryAttrRepo, Issuer -from sovrin.anon_creds.proof_builder import ProofBuilder -from sovrin.anon_creds.verifier import Verifier +from sovrin.agent.constants import EVENT_NOTIFY_MSG, EVENT_POST_ACCEPT_INVITE from sovrin.cli.helper import getNewClientGrams, \ USAGE_TEXT, NEXT_COMMANDS_TO_TRY_TEXT from sovrin.client.client import Client from sovrin.client.wallet.attribute import Attribute, LedgerStore -from sovrin.client.wallet.claim import ClaimProofRequest -from sovrin.client.wallet.claim_def import IssuerPubKey, ClaimDef -from sovrin.client.wallet.credential import Credential as WalletCredential -from sovrin.client.wallet.link import Link +from sovrin.client.wallet.link import Link, ClaimProofRequest from sovrin.client.wallet.wallet import Wallet +from sovrin.common.config_util import getConfig from sovrin.common.exceptions import InvalidLinkException, LinkAlreadyExists, \ LinkNotFound, NotConnectedToNetwork, ClaimDefNotFound from sovrin.common.identity import Identity from sovrin.common.txn import TARGET_NYM, STEWARD, ROLE, TXN_TYPE, NYM, \ SPONSOR, TXN_ID, REF, USER, getTxnOrderedFields -from sovrin.common.util import getEncodedAttrs, ensureReqCompleted, \ - getCredDefIsrKeyAndExecuteCallback, charmDictToStringDict, getNonceForProof -from sovrin.common.config_util import getConfig -from sovrin.server.node import Node +from sovrin.common.util import ensureReqCompleted + +try: + nodeMod = importlib.import_module('sovrin.server.node') + nodeClass = nodeMod.Node +except ImportError: + nodeClass = None """ Objective @@ -75,13 +61,12 @@ class SovrinCli(PlenumCli): name = 'sovrin' properName = 'Sovrin' fullName = 'Sovrin Identity platform' - - NodeClass = Node + NodeClass = nodeClass ClientClass = Client _genesisTransactions = [] def __init__(self, *args, **kwargs): - self.aliases = {} # type: Dict[str, Signer] + self.aliases = {} # type: Dict[str, Signer] self.sponsors = set() self.users = set() # Available environments @@ -89,13 +74,6 @@ def __init__(self, *args, **kwargs): # This specifies which environment the cli is connected to test or live self.activeEnv = None super().__init__(*args, **kwargs) - self.attributeRepo = None # type: AttrRepo - # DEPR JAL removed following because it doesn't seem right, testing now - # LH: Shouldn't the Cli have a `Verifier` so it can act as a Verifier - # entity too? - # TODO: Confirm this decision - self.verifier = Verifier(randomString(), MemoryCredDefStore(), - MemoryIssuerKeyStore()) _, port = self.nextAvailableClientAddr() self.curContext = (None, None, {}) # Current Link, Current Claim Req, # set attributes @@ -109,17 +87,7 @@ def lexers(self): 'send_attrib', 'send_cred_def', 'send_isr_key', - 'send_cred', - 'list_cred', - 'prep_proof', - 'verif_proof', 'add_genesis', - 'req_cred', - 'gen_cred', - 'store_cred', - 'gen_verif_nonce', - 'init_attr_repo', - 'add_attrs', 'show_file', 'conn' 'load_file', @@ -145,23 +113,10 @@ def completers(self): completers["send_nym"] = WordCompleter(["send", "NYM"]) completers["send_get_nym"] = WordCompleter(["send", "GET_NYM"]) completers["send_attrib"] = WordCompleter(["send", "ATTRIB"]) - completers["send_cred_def"] = WordCompleter(["send", "CRED_DEF"]) + completers["send_cred_def"] = WordCompleter(["send", "CLAIM_DEF"]) completers["send_isr_key"] = WordCompleter(["send", "ISSUER_KEY"]) - completers["req_cred"] = WordCompleter(["request", "credential"]) - completers["gen_cred"] = WordCompleter(["generate", "credential"]) - completers["store_cred"] = WordCompleter(["store", "credential"]) - completers["list_cred"] = WordCompleter(["list", "CRED"]) - completers["gen_verif_nonce"] = WordCompleter( - ["generate", "verification", "nonce"]) - completers["prep_proof"] = WordCompleter( - ["prepare", "proof", "of"]) - completers["verif_proof"] = WordCompleter( - ["verify", "status", "is"]) completers["add_genesis"] = WordCompleter( ["add", "genesis", "transaction"]) - completers["init_attr_repo"] = WordCompleter( - ["initialize", "mock", "attribute", "repo"]) - completers["add_attrs"] = WordCompleter(["add", "attribute"]) completers["show_file"] = WordCompleter(["show"]) completers["load_file"] = WordCompleter(["load"]) completers["show_link"] = WordCompleter(["show", "link"]) @@ -191,19 +146,9 @@ def actions(self): actions.extend([self._sendNymAction, self._sendGetNymAction, self._sendAttribAction, - self._sendCredDefAction, + self._sendClaimDefAction, self._sendIssuerKeyAction, - self._reqCredAction, - self._listCredAction, - self._verifyProofAction, self._addGenesisAction, - self._initAttrRepoAction, - self._addAttrsToRepoAction, - self._addAttrsToProverAction, - self._storeCredAction, - self._genVerifNonceAction, - self._prepProofAction, - self._genCredAction, self._showFile, self._loadFile, self._showLink, @@ -268,7 +213,7 @@ def _getPromptUsage(): @property def allEnvNames(self): - return "|".join(sorted(self.envs.keys(),reverse=True)) + return "|".join(sorted(self.envs.keys(), reverse=True)) def _getConnectUsage(self): return ["connect <{}>".format(self.allEnvNames)] @@ -483,111 +428,6 @@ def chk(reply, error, *args, **kwargs): self.looper.loop.call_later(.2, self._ensureReqCompleted, req.key, self.activeClient, chk) - # DEPR: moved to issuer_wallet as createClaimDef - # def _buildCredDef(self, matchedVars): - # """ - # Helper function to build CredentialDefinition function from given values - # """ - # name = matchedVars.get('name') - # version = matchedVars.get('version') - # keys = matchedVars.get('keys') - # attrNames = [s.strip() for s in keys.split(",")] - # # TODO: Directly using anoncreds lib, should use plugin - # claimDef = ClaimDef(seqNo=None, - # attrNames=attrNames, - # name=name, - # version=version, - # origin=self.activeWallet.defaultId, - # typ=matchedVars.get(TYPE)) - # return claimDef - - # DEPR: moved to issuer_wallet - # def _buildIssuerKey(self, origin, reference): - # wallet = self.activeWallet - # claimDef = wallet.getClaimDef(seqNo=reference) - # if claimDef: - # csk = CredDefSecretKey.fromStr(wallet.getClaimDefSk(claimDef.secretKey)) - # isk = IssuerSecretKey(claimDef, csk, uid=str(uuid.uuid4())) - # ipk = IssuerPubKey(N=isk.PK.N, R=isk.PK.R, S=isk.PK.S, Z=isk.PK.Z, - # claimDefSeqNo=reference, - # secretKeyUid=isk.pubkey.uid, origin=wallet.defaultId) - # - # return ipk - # else: - # self.print("Reference {} not found".format(reference), - # Token.BoldOrange) - - def _printCredReq(self, reply, err, credName, - credVersion, issuerId, proverId): - # Assuming the author of issuer key and claim def is same - claimDefKey = (credName, credVersion, issuerId) - U = self.activeWallet.getUValueForIssuerKeys(claimDefs=[(issuerId, - claimDefKey)]) - u = U[issuerId] - ipk = self.activeWallet.getIssuerPublicKeyForClaimDef( - issuerId=issuerId, claimDefKey=claimDefKey) - tokens = [] - tokens.append((Token.BoldBlue, "Credential request for {} for " - "{} {} is: ". - format(proverId, credName, credVersion))) - tokens.append((Token, "Requesting credential for public key ")) - tokens.append((Token.BoldBlue, "{} ". - format(ipk.seqNo))) - tokens.append((Token, "and U is ")) - tokens.append((Token.BoldBlue, "{}".format(u))) - tokens.append((Token, "\n")) - self.printTokens(tokens, separator='') - - def setProofBuilderAttrs(self, pb, issuerId): - attributes = self.attributeRepo.getAttributes(issuerId) - pb.setParams(encodedAttrs=getEncodedAttrs(issuerId, attributes)) - - @staticmethod - def pKFromCredDef(keys): - return CredDefModule.claimDef.getPk(keys) - - def _initAttrRepoAction(self, matchedVars): - if matchedVars.get('init_attr_repo') == 'initialize mock attribute repo': - self.attributeRepo = InMemoryAttrRepo() - self.print("attribute repo initialized", Token.BoldBlue) - return True - - def _genVerifNonceAction(self, matchedVars): - if matchedVars.get('gen_verif_nonce') == 'generate verification nonce': - # TODO: For now I am generating random interaction id, but we need - # to come back to this - interactionId = randomString(7) - nonce = self.verifier.generateNonce(interactionId) - self.print("Verification nonce is {}".format(nonce), Token.BoldBlue) - return True - - def _storeCredAction(self, matchedVars): - if matchedVars.get('store_cred') == 'store credential': - # TODO: Assuming single issuer credential only, make it accept - # multi-issuer credential - credStr = matchedVars.get('cred') - alias = matchedVars.get('alias').strip() - issuerKeyId = int(matchedVars.get('pk_id').strip()) - cred = {} - for val in credStr.split(","): - name, value = val.split('=', 1) - name, value = name.strip(), value.strip() - cred[name] = value - - issuerKey = self.activeWallet.getIssuerPublicKey(seqNo=issuerKeyId) - issuerId = issuerKey.origin - vprime = next(iter(self.activeWallet.getVPrimes(issuerId).values())) - credential = WalletCredential.buildFromIssuerProvidedCred( - issuerKey.seqNo, A=cred[CRED_A], e=cred[CRED_E], - v=cred[V_PRIME_PRIME], vprime=vprime) - - # TODO: What if alias is not given (we don't have issuer id and - # cred name here) ??? - # TODO: is the below way of storing cred in dict ok? - self.activeWallet.addCredential(alias, credential) - self.print("Credential stored", Token.BoldBlue) - return True - @staticmethod def parseAttributeString(attrs): attrInput = {} @@ -597,34 +437,6 @@ def parseAttributeString(attrs): attrInput[name] = value return attrInput - def _addAttrsToRepoAction(self, matchedVars): - if matchedVars.get('add_attrs') == 'add attribute': - attrs = matchedVars.get('attrs') - proverId = matchedVars.get('prover_id') - attribTypes = [] - attributes = self.parseAttributeString(attrs) - for name in attributes: - attribTypes.append(AttribType(name, encode=True)) - attribsDef = AttribDef(self.name, attribTypes) - attribs = attribsDef.attribs(**attributes) - self.attributeRepo.addAttributes(proverId, attribs) - self.print("attribute added successfully for prover id {}". - format(proverId), Token.BoldBlue) - return True - - def _addAttrsToProverAction(self, matchedVars): - if matchedVars.get('add_attrs') == 'attribute known to': - attrs = matchedVars.get('attrs') - issuerId = matchedVars.get('issuer_id') - attributes = self.parseAttributeString(attrs) - # TODO: Refactor ASAP - if not hasattr(self.activeClient, "attributes"): - self.attributeRepo = InMemoryAttrRepo() - self.attributeRepo.addAttributes(issuerId, attributes) - self.print("attribute added successfully for issuer id {}". - format(issuerId), Token.BoldBlue) - return True - def _sendNymAction(self, matchedVars): if matchedVars.get('send_nym') == 'send NYM': if not self.canMakeSovrinRequest: @@ -656,30 +468,23 @@ def _sendAttribAction(self, matchedVars): self._addAttribToNym(nym, raw, enc, hsh) return True - def _sendCredDefAction(self, matchedVars): - if matchedVars.get('send_cred_def') == 'send CRED_DEF': + def _sendClaimDefAction(self, matchedVars): + if matchedVars.get('send_cred_def') == 'send CLAIM_DEF': if not self.canMakeSovrinRequest: return True - claimDef = self.activeWallet.createClaimDef( - name=matchedVars.get(NAME), - version=matchedVars.get(VERSION), - attrNames=[s.strip() for s in matchedVars.get(KEYS).split(",")], - typ=matchedVars.get(TYPE)) - reqs = self.activeWallet.preparePending() - self.activeClient.submitReqs(*reqs) + claimDef = self.agent.issuer.genClaimDef(name=matchedVars.get(NAME), + version=matchedVars.get(VERSION), + ttrNames=[s.strip() for s in matchedVars.get(KEYS).split(",")], + typ=matchedVars.get(TYPE)) - def published(reply, error, *args, **kwargs): - self.print("The following credential definition is published" - "to the Sovrin distributed ledger\n", Token.BoldBlue, - newline=False) - self.print("{}".format(claimDef.get(serFmt=SerFmt.base58))) - self.print("Sequence number is {}".format(reply[F.seqNo.name]), - Token.BoldBlue) + self.print("The following credential definition is published" + "to the Sovrin distributed ledger\n", Token.BoldBlue, + newline=False) + self.print("{}".format(str(claimDef))) + self.print("Sequence number is {}".format(claimDef.id), + Token.BoldBlue) - self.looper.loop.call_later(.2, self._ensureReqCompleted, - reqs[0].key, self.activeClient, - published) return True def _sendIssuerKeyAction(self, matchedVars): @@ -687,149 +492,23 @@ def _sendIssuerKeyAction(self, matchedVars): if not self.canMakeSovrinRequest: return True reference = int(matchedVars.get(REF)) + id = ID(claimDefId=reference) try: - ipk = self.activeWallet.createIssuerKey(reference) + self.agent.issuer.genKeys(id) except ClaimDefNotFound: self.print("Reference {} not found".format(reference), Token.BoldOrange) - reqs = self.activeWallet.preparePending() - self.activeClient.submitReqs(*reqs) - - def published(reply, error, *args, **kwargs): - self.print("The following issuer key is published to the" - " Sovrin distributed ledger\n", Token.BoldBlue, - newline=False) - self.print("{}".format(ipk.get(serFmt=SerFmt.base58))) - self.print("Sequence number is {}".format(reply[F.seqNo.name]), - Token.BoldBlue) - - self.looper.loop.call_later(.2, self._ensureReqCompleted, - reqs[0].key, self.activeClient, - published) - return True + ipk = self.agent.wallet.getPublicKey(id) + self.print("The following issuer key is published to the" + " Sovrin distributed ledger\n", Token.BoldBlue, + newline=False) + self.print("{}".format(str(ipk))) + # self.print("Sequence number is {}".format(reply[F.seqNo.name]), + # Token.BoldBlue) - # will get invoked when prover cli enters request credential command - def _reqCredAction(self, matchedVars): - if matchedVars.get('req_cred') == 'request credential': - if not self.canMakeSovrinRequest: - return True - origin = matchedVars.get('issuer_id') - credName = matchedVars.get('cred_name') - proverName = matchedVars.get('prover_id') - credVersion = matchedVars.get('version') - claimDefKey = (credName, credVersion, origin) - getCredDefIsrKeyAndExecuteCallback(self.activeWallet, - self.activeClient, - self.print, - self.looper.loop, - claimDefKey, - self._printCredReq, - pargs=(credName, credVersion, - origin, proverName)) - return True - - def _listCredAction(self, matchedVars): - if matchedVars.get('list_cred') == 'list CRED': - self.print('\n'.join(self.activeWallet.credNames)) - return True - - def _prepProofAction(self, matchedVars): - if matchedVars.get('prep_proof') == 'prepare proof of': - nonce = self.getCryptoInteger(matchedVars.get('nonce')) - revealedAttrs = (matchedVars.get('revealed_attrs'), ) - credAlias = matchedVars.get('cred_alias') - - credential = self.activeWallet.getCredential(credAlias) - issuerKeyId = credential.issuerKeyId - issuerPubKey = self.activeWallet.getIssuerPublicKey(seqNo=issuerKeyId) - claimDef = self.activeWallet.getClaimDef( - seqNo=issuerPubKey.claimDefSeqNo) - issuerId = issuerPubKey.origin - - credDefPks = { - issuerId: issuerPubKey - } - masterSecret = self.getCryptoInteger( - self.activeWallet.masterSecret) - attributes = self.attributeRepo.getAttributes(issuerId) - attribTypes = [] - for nm in attributes.keys(): - attribTypes.append(AttribType(nm, encode=True)) - attribsDef = AttribDef(self.name, attribTypes) - attribs = attribsDef.attribs(**attributes).encoded() - encodedAttrs = { - issuerId: next(iter(attribs.values())) - } - proof = ProofBuilder.prepareProofAsDict(issuerPks=credDefPks, - masterSecret=masterSecret, - creds={ - issuerId: - credential.toNamedTuple}, - revealedAttrs=revealedAttrs, - nonce=nonce, - encodedAttrs=encodedAttrs) - out = {} - out[PROOF] = proof - out[NAME] = claimDef.name - out[VERSION] = claimDef.version - out[ISSUER] = issuerId - out[NONCE] = str(nonce) - out[ATTRS] = { - issuerId: {k: str(v) for k, v in - next(iter(attribs.values())).items()} - } - out[REVEALED_ATTRS] = revealedAttrs - self.print("Proof is: ", newline=False) - self.print("{}".format(json.dumps(out)), Token.BoldBlue) - return True - - @staticmethod - def getCryptoInteger(x): - return strToCryptoInteger(x) if isinstance(x, str) else x - - def _verifyProofAction(self, matchedVars): - if matchedVars.get('verif_proof') == 'verify status is': - status = matchedVars.get('status') - proof = json.loads(matchedVars.get('proof')) - self._verifyProof(status, proof) return True - def _verifyProof(self, status, proof): - claimDefKey = (proof[NAME], proof[VERSION], proof["issuer"]) - getCredDefIsrKeyAndExecuteCallback(self.activeWallet, - self.activeClient, - self.print, - self.looper.loop, - claimDefKey, - self.doVerification, - pargs=(status, proof)) - - def doVerification(self, reply, err, status, proof): - issuer = proof[ISSUER] - claimDef = self.activeWallet.getClaimDef((proof[NAME], - proof[VERSION], issuer)) - issuerPubKey = self.activeWallet.getIssuerPublicKey( - (issuer, claimDef.seqNo)) - pk = { - issuer: issuerPubKey - } - prf = ProofBuilder.prepareProofFromDict(proof[PROOF]) - attrs = { - issuer: {k: self.getCryptoInteger(v) for k, v in - next(iter(proof[ATTRS].values())).items()} - } - result = self.verifier.verifyProof(pk, prf, - self.getCryptoInteger( - proof["nonce"]), attrs, - proof[REVEALED_ATTRS]) - if not result: - self.print("Proof verification failed", Token.BoldOrange) - elif result and status in proof["revealedAttrs"]: - self.print("Proof verified successfully", Token.BoldBlue) - else: - self.print("Status not in proof", Token.BoldOrange) - def printUsageMsgs(self, msgs): for m in msgs: self.print(' {}'.format(m)) @@ -932,6 +611,10 @@ def _printUsagePostSync(self, link): self._printShowAndAcceptLinkUsage(link.name) def _getTargetEndpoint(self, li, postSync): + if not self.activeWallet.identifiers: + self.print("No key present in keyring for making request on Sovrin," + " so adding one") + self._newSigner(wallet=self.activeWallet) if self._isConnectedToAnyEnv(): self.print("\nSynchronizing...") doneCallback = partial(self._syncLinkPostEndPointRetrieval, @@ -961,39 +644,8 @@ def _getOneLinkForFurtherProcessing(self, linkName): self.print('Expanding {} to "{}"'.format(linkName, li.name)) return li - def _sendReqToTargetEndpoint(self, op, link: Link): - op[f.IDENTIFIER.nm] = link.verkey - op[NONCE] = link.invitationNonce - op[f.REQ_ID.nm] = getTimeBasedId() - signature = self.activeWallet.signMsg(op, link.verkey) - op[f.SIG.nm] = signature - self.sendToAgent(op, link) - - # TODO: This should be moved to agent - def sendReqClaim(self, reply, error, link, claimDefKey): - name, version, origin = claimDefKey - U = self.activeWallet.getUValueForIssuerKeys(claimDefs=[(origin, - claimDefKey)]) - uValue = U[origin] - op = { - NONCE: link.invitationNonce, - TYPE: REQUEST_CLAIM, - NAME: name, - VERSION: version, - ORIGIN: origin, - 'U': str(uValue) - } - signature = self.activeWallet.signMsg(op, link.verkey) - op[f.SIG.nm] = signature - self.print("Requesting claim {} from {}...".format( - name, link.name)) - self._sendReqToTargetEndpoint(op, link) - def _sendAcceptInviteToTargetEndpoint(self, link: Link): - op = { - TYPE: ACCEPT_INVITE - } - self._sendReqToTargetEndpoint(op, link) + self.agent.acceptInvitation(link) def _acceptLinkPostSync(self, link: Link): if link.isRemoteEndpointAvailable: @@ -1037,8 +689,7 @@ def isNotMatching(source, target): @staticmethod def removeSpecialChars(name): - return name.replace('"', '').replace("'","") - + return name.replace('"', '').replace("'", "") def _printSyncLinkUsage(self, linkName): msgs = self._getSyncLinkUsage(linkName) @@ -1132,7 +783,7 @@ def _printMoreThanOneLinkFoundMsg(self, linkName, exactlyMatchedLinks, def _showLink(self, matchedVars): if matchedVars.get('show_link') == 'show link': - linkName = matchedVars.get('link_name').replace('"','') + linkName = matchedVars.get('link_name').replace('"', '') totalFound, exactlyMatchedLinks, likelyMatchedLinks = \ self._getMatchingInvitationsDetail(linkName) @@ -1172,7 +823,7 @@ def _printMoreThanOneLinkFoundForRequest(self, requestedName, linkNames): self.print('More than one link matches "{}"'.format(requestedName)) for li in linkNames: self.print("{}".format(li)) - # TODO: Any suggestion in more than one link? + # TODO: Any suggestion in more than one link? # TODO: Refactor following three methods # as most of the pattern looks similar @@ -1188,10 +839,10 @@ def _printMoreThanOneClaimFoundForRequest(self, claimName, linkAndClaimNames): for li, cl in linkAndClaimNames: self.print("{} in {}".format(li, cl)) - def _getOneLinkAndClaimReq(self, claimReqName) -> \ + def _getOneLinkAndClaimReq(self, claimReqName, linkName=None) -> \ (Link, ClaimProofRequest): - matchingLinksWithClaimReq = self.activeWallet.\ - getMatchingLinksWithClaimReq(claimReqName) + matchingLinksWithClaimReq = self.activeWallet. \ + getMatchingLinksWithClaimReq(claimReqName, linkName) if len(matchingLinksWithClaimReq) == 0: self._printNoClaimReqFoundMsg() @@ -1204,9 +855,9 @@ def _getOneLinkAndClaimReq(self, claimReqName) -> \ return matchingLinksWithClaimReq[0] - def _getOneLinkAndAvailableClaim(self, claimName, printMsgs:bool=True) -> \ - (Link, ClaimDef): - matchingLinksWithAvailableClaim = self.activeWallet.\ + def _getOneLinkAndAvailableClaim(self, claimName, printMsgs: bool = True) -> \ + (Link, ClaimDefinition): + matchingLinksWithAvailableClaim = self.activeWallet. \ getMatchingLinksWithAvailableClaim(claimName) if len(matchingLinksWithAvailableClaim) == 0: @@ -1222,10 +873,9 @@ def _getOneLinkAndAvailableClaim(self, claimName, printMsgs:bool=True) -> \ return matchingLinksWithAvailableClaim[0] - def _getOneLinkAndReceivedClaim(self, claimName, printMsgs:bool=True) -> \ + async def _getOneLinkAndReceivedClaim(self, claimName, printMsgs: bool = True) -> \ (Link, Tuple, Dict): - matchingLinksWithRcvdClaim = self.activeWallet.\ - getMatchingLinksWithReceivedClaim(claimName) + matchingLinksWithRcvdClaim = await self.agent.getMatchingLinksWithReceivedClaimAsync(claimName) if len(matchingLinksWithRcvdClaim) == 0: if printMsgs: @@ -1271,14 +921,10 @@ def _reqClaim(self, matchedVars): return True claimDefKey = (name, version, origin) - getCredDefIsrKeyAndExecuteCallback(self.activeWallet, - self.activeClient, - self.print, - self.looper.loop, - claimDefKey, - self.sendReqClaim, - pargs=(matchingLink, - claimDefKey)) + self.print("Requesting claim {} from {}...".format( + name, matchingLink.name)) + + self.agent.sendReqClaim(matchingLink, claimDefKey) else: self._printNoClaimFoundMsg() return True @@ -1287,123 +933,63 @@ def _sendClaim(self, matchedVars): if matchedVars.get('send_claim') == 'send claim': claimName = matchedVars.get('claim_name').strip() linkName = matchedVars.get('link_name').strip() - reqs = self.activeWallet.getMatchingLinksWithClaimReq(claimName) - if not reqs: - self._printNoClaimFoundMsg() - elif len(reqs) > 1: - self._printMoreThanOneLinkFoundForRequest(claimName, - [(li, cr.name) for - (li, cr) in reqs]) - else: - links = self.activeWallet.getMatchingLinks(linkName) - if not links: - self._printNoLinkFoundMsg() - elif len(links) > 1: - self._printMoreThanOneLinkFoundMsg(linkName, {}, links) - else: - link = links[0] - claimPrfReq = reqs[0][1] - nonce = getNonceForProof(link.invitationNonce) - self.logger.debug("Building proof using {} for {}". - format(claimPrfReq, link)) - proof, encodedAttrs, revealedAttrs, claimDefKeys = \ - self.activeWallet.buildClaimProof( - nonce, claimPrfReq) - self.logger.debug("Prepared proof {}".format(proof)) - self.logger.debug("Current context {} {} {}". - format(*self.curContext)) - - for iid, attrs in encodedAttrs.items(): - encodedAttrs[iid] = charmDictToStringDict(attrs) - op = { - NAME: claimPrfReq.name, - VERSION: claimPrfReq.version, - NONCE: link.invitationNonce, - TYPE: CLAIM_PROOF, - 'proof': proof, - 'encodedAttrs': encodedAttrs, - 'revealedAttrs': revealedAttrs, - 'claimDefKeys': claimDefKeys - } - signature = self.activeWallet.signMsg(op, link.verkey) - op[f.SIG.nm] = signature - self._sendReqToTargetEndpoint(op, link) - return True - def _showReceivedOrAvailableClaim(self, claimName): - self._showReceivedClaimIfExists(claimName) or \ - self._showAvailableClaimIfExists(claimName) + li, claimPrfReq = self._getOneLinkAndClaimReq(claimName, linkName) - def _printRequestClaimMsg(self, claimName): - self.printSuggestion(self._getReqClaimUsage(claimName)) + if not li or not claimPrfReq: + return False + + self.logger.debug("Building proof using {} for {}". + format(claimPrfReq, li)) - def _showReceivedClaimIfExists(self, claimName): + self.agent.sendProof(li, claimPrfReq) + + return True + + async def _showReceivedOrAvailableClaim(self, claimName): matchingLink, rcvdClaim, attributes = \ - self._getOneLinkAndReceivedClaim(claimName, printMsgs=False) + await self._getOneLinkAndReceivedClaim(claimName) if matchingLink: self.print("Found claim {} in link {}". format(claimName, matchingLink.name)) # TODO: Figure out how to get time of issuance - self.print("Status: {}".format(datetime.datetime.now())) - self.print('Name: {}\nVersion: {}'.format(claimName, rcvdClaim[1])) - self.print("Attributes:") - for n, v in attributes.items(): - self.print(' {}: {}'.format(n, v)) - self.print("") - return rcvdClaim + issued = None not in attributes.values() - def _showAvailableClaimIfExists(self, claimName): - matchingLink, ac = \ - self._getOneLinkAndAvailableClaim(claimName, printMsgs=False) - if matchingLink: - self.print("Found claim {} in link {}". - format(claimName, matchingLink.name)) - name, version, origin = ac - claimDef = self.activeWallet.getClaimDef(key=ac) - claimAttr = self.activeWallet.getClaimAttrs(ac) - if claimAttr: - #TODO: Figure out how to get time of issuance - # self.print("Status: {}".format(ca.dateOfIssue)) + if issued: self.print("Status: {}".format(datetime.datetime.now())) else: self.print("Status: available (not yet issued)") - if claimDef: - self.print('Name: {}\nVersion: {}'.format(name, version)) - - if not (claimAttr or claimDef): - raise NotImplementedError - else: - self.print("Attributes:") - - attrs = [] - if not claimAttr: - if claimDef: - attrs = [(n, '') for n in claimDef.attrNames] - else: - attrs = [(n, ': {}'.format(v)) for n, v in claimAttr.items()] - if attrs: - for n, v in attrs: - self.print(' {}{}'.format(n, v)) + self.print('Name: {}\nVersion: {}'.format(claimName, rcvdClaim[1])) + self.print("Attributes:") + for n, v in attributes.items(): + if v: + self.print(' {}: {}'.format(n, v)) + else: + self.print(' {}'.format(n)) - if not claimAttr: + if not issued: self._printRequestClaimMsg(claimName) - return ac + else: + self.print("") + return rcvdClaim else: self.print("No matching claim(s) found " "in any links in current keyring") - def _showMatchingClaimProof(self, claimProofReq: ClaimProofRequest, - selfAttestedAttrs): - matchingLinkAndRcvdClaims = \ - self.activeWallet.getMatchingRcvdClaims(claimProofReq.attributes) + def _printRequestClaimMsg(self, claimName): + self.printSuggestion(self._getReqClaimUsage(claimName)) + + async def _showMatchingClaimProof(self, claimProofReq: ClaimProofRequest, + selfAttestedAttrs, matchingLink): + matchingLinkAndReceivedClaim = await self.agent.getMatchingRcvdClaimsAsync(claimProofReq.attributes) attributesWithValue = claimProofReq.attributes for k, v in claimProofReq.attributes.items(): - for ml, _, commonAttrs, allAttrs in matchingLinkAndRcvdClaims: - if k in commonAttrs: - attributesWithValue[k] = allAttrs[k] + for li, cl, issuedAttrs in matchingLinkAndReceivedClaim: + if k in issuedAttrs: + attributesWithValue[k] = issuedAttrs[k] else: defaultValue = attributesWithValue[k] or v attributesWithValue[k] = selfAttestedAttrs.get(k, defaultValue) @@ -1411,12 +997,15 @@ def _showMatchingClaimProof(self, claimProofReq: ClaimProofRequest, claimProofReq.attributes = attributesWithValue self.print(str(claimProofReq)) - for ml, (name, ver, _), commonAttrs, allAttrs in matchingLinkAndRcvdClaims: + for li, (name, ver, _), issuedAttrs in matchingLinkAndReceivedClaim: self.print('\n Claim proof ({} v{} from {})'.format( - name, ver, ml.name)) - for k, v in allAttrs.items(): + name, ver, li.name)) + for k, v in issuedAttrs.items(): self.print(' ' + k + ': ' + v + ' (verifiable)') + self._printPostShowClaimReqSuggestion(claimProofReq.name, + matchingLink.name) + def _showClaimReq(self, matchedVars): if matchedVars.get('show_claim_req') == 'show claim request': claimReqName = SovrinCli.removeSpecialChars( @@ -1431,16 +1020,16 @@ def _showClaimReq(self, matchedVars): self.curContext = matchingLink, claimReq, attributes self.print('Found claim request "{}" in link "{}"'. format(claimReq.name, matchingLink.name)) - self._showMatchingClaimProof(claimReq, attributes) - self._printPostShowClaimReqSuggestion(claimReq.name, - matchingLink.name) + + self.agent.loop.call_soon(asyncio.ensure_future, + self._showMatchingClaimProof(claimReq, attributes, matchingLink)) return True def _showClaim(self, matchedVars): if matchedVars.get('show_claim') == 'show claim': claimName = SovrinCli.removeSpecialChars( matchedVars.get('claim_name')) - self._showReceivedOrAvailableClaim(claimName) + self.agent.loop.call_soon(asyncio.ensure_future, self._showReceivedOrAvailableClaim(claimName)) return True @@ -1508,7 +1097,7 @@ def getStatus(self): if self.activeClient.hasSufficientConnections: msg = "Connected to {} Sovrin network".format(self.activeEnv) else: - msg = "Attempting connection to {} Sovrin network".\ + msg = "Attempting connection to {} Sovrin network". \ format(self.activeEnv) self.print(msg) @@ -1518,33 +1107,6 @@ def _setPrompt(self, promptText): promptText = "{}@{}".format(promptText, self.activeEnv) super()._setPrompt(promptText) - # This function would be invoked, when, issuer cli enters the send GEN_CRED - # command received from prover. This is required for demo for sure, we'll - # see if it will be required for real execution or not - def _genCredAction(self, matchedVars): - if matchedVars.get('gen_cred') == 'generate credential': - proverId = matchedVars.get('prover_id') - credName = matchedVars.get('cred_name') - credVersion = matchedVars.get('cred_version') - uValue = matchedVars.get('u_value') - credDefKey = (credName, credVersion, self.activeWallet.defaultId) - claimDef = self.activeWallet.getClaimDef(credDefKey) - pk = self.activeWallet.getIssuerPublicKeyForClaimDef( - issuerId=self.activeWallet.defaultId, seqNo=claimDef.seqNo) - attributes = self.attributeRepo.getAttributes(proverId).encoded() - if attributes: - attributes = list(attributes.values())[0] - sk = self.activeWallet.getClaimDefSk(claimDef.seqNo) - cred = Issuer.generateCredential(uValue, attributes, pk, sk) - # TODO: For real scenario, do we need to send this credential back - # or it will be out of band? - self.print("Credential: ", newline=False) - self.print("A={}, e={}, vprimeprime={}".format(*cred), - Token.BoldBlue) - # TODO: For real scenario, do we need to send this - # credential back or it will be out of band? - return True - def _addGenesisAction(self, matchedVars): if matchedVars.get('add_genesis'): nym = matchedVars.get('dest_id') @@ -1569,7 +1131,7 @@ def ensureClientConnected(self): else: self.looper.loop.call_later(.2, self.ensureClientConnected) - def ensureAgentConnected(self, otherAgentHa, clbk: Callable=None, + def ensureAgentConnected(self, otherAgentHa, clbk: Callable = None, *args): if not self.agent: return diff --git a/sovrin/cli/constants.py b/sovrin/cli/constants.py index 8eb77c4..936e098 100644 --- a/sovrin/cli/constants.py +++ b/sovrin/cli/constants.py @@ -32,7 +32,7 @@ "\s+ dest=\s*(?P[A-Za-z0-9+=/]+) " \ "\s+ raw=(?P\{\s*.*\}) \s*) " -SEND_CRED_DEF_REG_EX = "(\s*(?Psend\s+CRED_DEF)" \ +SEND_CLAIM_DEF_REG_EX = "(\s*(?Psend\s+CLAIM_DEF)" \ "\s+(?Pname=)\s*(?P[A-Za-z0-9-_]+)" \ "\s*(?Pversion=)\s*(?P[0-9.]+)" \ "\s*(?Ptype=)\s*(?P[A-Z0-9]+)" \ @@ -139,7 +139,7 @@ SEND_NYM_FORMATTED_REG_EX = getPipedRegEx(SEND_NYM_REG_EX) GET_NYM_FORMATTED_REG_EX = getPipedRegEx(GET_NYM_REG_EX) ADD_ATTRIB_FORMATTED_REG_EX = getPipedRegEx(ADD_ATTRIB_REG_EX) -SEND_CRED_DEF_FORMATTED_REG_EX = getPipedRegEx(SEND_CRED_DEF_REG_EX) +SEND_CLAIM_DEF_FORMATTED_REG_EX = getPipedRegEx(SEND_CLAIM_DEF_REG_EX) SEND_ISSUER_KEY_FORMATTED_REG_EX = getPipedRegEx(SEND_ISSUER_KEY_REG_EX) REQ_CRED_FORMATTED_REG_EX = getPipedRegEx(REQ_CRED_REG_EX) LIST_CREDS_FORMATTED_REG_EX = getPipedRegEx(LIST_CREDS_REG_EX) diff --git a/sovrin/cli/genesisTxns.py b/sovrin/cli/genesisTxns.py index f119c36..b2c1609 100644 --- a/sovrin/cli/genesisTxns.py +++ b/sovrin/cli/genesisTxns.py @@ -1,4 +1,5 @@ from plenum.common.types import f + from sovrin.common.txn import TXN_TYPE, TARGET_NYM, ROLE, TXN_ID, NYM,\ STEWARD, SPONSOR diff --git a/sovrin/cli/helper.py b/sovrin/cli/helper.py index f7cd485..471c89d 100644 --- a/sovrin/cli/helper.py +++ b/sovrin/cli/helper.py @@ -2,7 +2,7 @@ CLIENT_GRAMS_CLIENT_WITH_IDENTIFIER_FORMATTED_REG_EX, \ CLIENT_GRAMS_CLIENT_ADD_FORMATTED_REG_EX, SEND_NYM_FORMATTED_REG_EX, \ GET_NYM_FORMATTED_REG_EX, \ - ADD_ATTRIB_FORMATTED_REG_EX, SEND_CRED_DEF_FORMATTED_REG_EX, \ + ADD_ATTRIB_FORMATTED_REG_EX, SEND_CLAIM_DEF_FORMATTED_REG_EX, \ REQ_CRED_FORMATTED_REG_EX, LIST_CREDS_FORMATTED_REG_EX, \ GEN_CRED_FORMATTED_REG_EX, ADD_GENESIS_FORMATTED_REG_EX, \ INIT_ATTR_REPO_FORMATTED_REG_EX, ADD_ATTRS_FORMATTED_REG_EX, \ @@ -32,7 +32,7 @@ def getNewClientGrams(): SEND_NYM_FORMATTED_REG_EX, GET_NYM_FORMATTED_REG_EX, ADD_ATTRIB_FORMATTED_REG_EX, - SEND_CRED_DEF_FORMATTED_REG_EX, + SEND_CLAIM_DEF_FORMATTED_REG_EX, SEND_ISSUER_KEY_FORMATTED_REG_EX, REQ_CRED_FORMATTED_REG_EX, LIST_CREDS_FORMATTED_REG_EX, diff --git a/sovrin/client/client.py b/sovrin/client/client.py index 030f4da..ac307bd 100644 --- a/sovrin/client/client.py +++ b/sovrin/client/client.py @@ -4,26 +4,25 @@ from collections import deque from typing import Dict, Union, Tuple, Optional, Callable -from base58 import b58decode, b58encode import pyorient - -from raet.raeting import AutoMode - +from base58 import b58decode, b58encode +from plenum.client.client import Client as PlenumClient from plenum.common.error import fault from plenum.common.log import getlogger -from plenum.client.client import Client as PlenumClient -from plenum.server.router import Router -from plenum.common.startable import Status from plenum.common.stacked import SimpleStack +from plenum.common.startable import Status from plenum.common.txn import REPLY, STEWARD, NAME, VERSION, REQACK, REQNACK, \ TXN_ID, TARGET_NYM, NONCE from plenum.common.types import OP_FIELD_NAME, f, HA from plenum.common.util import libnacl from plenum.persistence.orientdb_store import OrientDbStore +from plenum.server.router import Router +from raet.raeting import AutoMode + +from sovrin.common.config_util import getConfig from sovrin.common.txn import TXN_TYPE, ATTRIB, DATA, GET_NYM, ROLE, \ - SPONSOR, NYM, GET_TXNS, LAST_TXN, TXNS, CRED_DEF, ISSUER_KEY, SKEY, DISCLO,\ + SPONSOR, NYM, GET_TXNS, LAST_TXN, TXNS, CLAIM_DEF, ISSUER_KEY, SKEY, DISCLO,\ GET_ATTR -from sovrin.common.config_util import getConfig from sovrin.persistence.client_req_rep_store_file import ClientReqRepStoreFile from sovrin.persistence.client_req_rep_store_orientdb import \ ClientReqRepStoreOrientDB @@ -36,12 +35,12 @@ class Client(PlenumClient): def __init__(self, name: str, - nodeReg: Dict[str, HA]=None, - ha: Union[HA, Tuple[str, int]]=None, - peerHA: Union[HA, Tuple[str, int]]=None, - basedirpath: str=None, + nodeReg: Dict[str, HA] = None, + ha: Union[HA, Tuple[str, int]] = None, + peerHA: Union[HA, Tuple[str, int]] = None, + basedirpath: str = None, config=None, - sighex: str=None): + sighex: str = None): config = config or getConfig() super().__init__(name, nodeReg, @@ -116,6 +115,12 @@ def postReplyRecvd(self, identifier, reqId, frm, result, numReplies): try: self._observers[name](name, reqId, frm, result, numReplies) except Exception as ex: + # TODO: All errors should not be shown on CLI, or maybe we + # show errors with different color according to the + # severity. Like an error occurring due to node sending + # a malformed message should not result in an error message + # being shown on the cli since the clients would anyway + # collect enough replies from other nodes. logger.error("Observer threw an exception", exc_info=ex) if isinstance(self.reqRepStore, ClientReqRepStoreOrientDB): self.reqRepStore.setConsensus(identifier, reqId) @@ -145,14 +150,14 @@ def postReplyRecvd(self, identifier, reqId, frm, result, numReplies): fault(ex, "An exception was raised while " "adding attribute") - elif result[TXN_TYPE] == CRED_DEF: + elif result[TXN_TYPE] == CLAIM_DEF: if self.graphStore: - self.graphStore.addCredDefTxnToGraph(result) + self.graphStore.addClaimDefTxnToGraph(result) elif result[TXN_TYPE] == ISSUER_KEY: if self.graphStore: self.graphStore.addIssuerKeyTxnToGraph(result) - else: - logger.debug("Unknown type {}".format(result[TXN_TYPE])) + # else: + # logger.debug("Unknown type {}".format(result[TXN_TYPE])) def requestConfirmed(self, identifier: str, reqId: int) -> bool: if isinstance(self.reqRepStore, ClientReqRepStoreOrientDB): @@ -185,7 +190,7 @@ def getTxnById(self, txnId: str): # TODO: Add support for fetching reply by transaction id # serTxn = self.reqRepStore.getResultForTxnId(txnId) pass - # TODO Add merkleInfo as well + # TODO Add merkleInfo as well def getTxnsByNym(self, nym: str): raise NotImplementedError @@ -202,7 +207,7 @@ def getTxnsByType(self, txnType): else: txns = self.txnLog.getTxnsByType(txnType) # TODO: Fix ASAP - if txnType == CRED_DEF: + if txnType == CLAIM_DEF: for txn in txns: txn[DATA] = json.loads(txn[DATA].replace("\'", '"') .replace('"{', '{') @@ -234,7 +239,6 @@ def doGetAttributeTxn(self, identifier, attrName): } self.submit(op, identifier=identifier) - @staticmethod def _getDecryptedData(encData, key): data = bytes(bytearray.fromhex(encData)) diff --git a/sovrin/client/wallet/attribute.py b/sovrin/client/wallet/attribute.py index a89c553..365c646 100644 --- a/sovrin/client/wallet/attribute.py +++ b/sovrin/client/wallet/attribute.py @@ -3,6 +3,7 @@ from plenum.common.txn import TXN_TYPE, TARGET_NYM, RAW, ORIGIN from plenum.common.types import Identifier + from sovrin.common.generates_request import GeneratesRequest from sovrin.common.txn import ATTRIB, GET_ATTR from sovrin.common.types import Request diff --git a/sovrin/client/wallet/claim.py b/sovrin/client/wallet/claim.py deleted file mode 100644 index 3d71892..0000000 --- a/sovrin/client/wallet/claim.py +++ /dev/null @@ -1,40 +0,0 @@ - -AVAILABLE_BUT_NOT_ISSUED_STATUS = "available (not yet issued)" - - -class ClaimProofRequest: - def __init__(self, name, version, attributes): - self.name = name - self.version = version - self.attributes = attributes - - @property - def toDict(self): - return { - "name": self.name, - "version": self.version, - "attributes": self.attributes - } - - @property - def attributeValues(self): - return \ - 'Attributes:' + '\n ' + \ - format("\n ".join( - ['{}: {}'.format(k, v) - for k,v in self.attributes.items()])) - - def __str__(self): - - fixedInfo = \ - 'Status: Requested' + '\n' \ - 'Name: ' + self.name + '\n' \ - 'Version: ' + self.version + '\n' - - return fixedInfo + self.attributeValues - - -class Claim: - def __init__(self, issuerId, issuerPubKey): - self.issuerId = issuerId - self.issuerPubKey = issuerPubKey diff --git a/sovrin/client/wallet/claim_def.py b/sovrin/client/wallet/claim_def.py deleted file mode 100644 index e246484..0000000 --- a/sovrin/client/wallet/claim_def.py +++ /dev/null @@ -1,142 +0,0 @@ -from typing import Optional - -from anoncreds.protocol.issuer_key import IssuerKey -from anoncreds.protocol.credential_definition import CredentialDefinition -from plenum.common.txn import TXN_TYPE, DATA, NAME, VERSION, TARGET_NYM, TYPE,\ - ORIGIN -from plenum.common.types import Identifier -from sovrin.anon_creds.constant import ZERO_INDEX -from sovrin.common.txn import CRED_DEF, GET_CRED_DEF, ATTR_NAMES, ISSUER_KEY, \ - GET_ISSUER_KEY, REF -from sovrin.common.types import Request - - -class HasSeqNo: - @property - def seqNo(self): - return self.uid - - @seqNo.setter - def seqNo(self, value): - self.uid = value - - -class ClaimDef(CredentialDefinition, HasSeqNo): - def __init__(self, - name: str, - version: str, - origin: Optional[Identifier] = None, - seqNo: Optional[int] = None, - attrNames=None, - typ: str=None, - ): - super().__init__(uid=seqNo, - attrNames=attrNames, - name=name, - version=version) - self.typ = typ - self.origin = origin - - @property - def key(self): - return self.name, self.version, self.origin - - @property - def request(self): - if not self.seqNo: - assert self.origin is not None - op = { - TXN_TYPE: CRED_DEF, - DATA: { - NAME: self.name, - VERSION: self.version, - TYPE: self.typ, - ATTR_NAMES: ",".join(self.attrNames) - } - } - return Request(identifier=self.origin, operation=op) - - def _opForGet(self): - op = { - TARGET_NYM: self.origin, # TODO: Should be origin - TXN_TYPE: GET_CRED_DEF, - DATA: { - NAME: self.name, - VERSION: self.version, - } - } - return op - - def getRequest(self, requestAuthor: Identifier): - if not self.seqNo: - return Request(identifier=requestAuthor, operation=self._opForGet()) - - @property - def attributes(self): - return \ - 'Attributes:' + '\n ' + \ - format("\n ".join( - ['{}'.format(k) - for k in self.attrNames])) - - def __str__(self): - return \ - 'Name: ' + self.name + '\n' + \ - 'Version: ' + self.version + '\n' + \ - self.attributes - - -# TODO this pub key class should HAVE an IssuerKey, not BE and IssuerKey (also solves the late initialization) -# TODO also, not sure why a secretKeyUid is needed. The point of a uid is an external reference, and we would never store a secret key externally. -class IssuerPubKey(IssuerKey, HasSeqNo): - def __init__(self, claimDefSeqNo: int, - origin, N=None, R=None, S=None, Z=None, secretKeyUid=None, - seqNo: Optional[int]=None): - if all([x is not None for x in (N, R, S, Z)]): - self.initPubKey(seqNo, N, R, S, Z) - else: - self.uid = seqNo - self.claimDefSeqNo = claimDefSeqNo - self.origin = origin - - # TODO: Remove this late initialisation. - def initPubKey(self, seqNo, N, R, S, Z): - IssuerKey.__init__(self, seqNo, N, R, S, Z) - - @property - def key(self): - return self.origin, self.claimDefSeqNo - - @property - def request(self): - if not self.seqNo: - assert self.origin is not None - op = { - TXN_TYPE: ISSUER_KEY, - REF: self.claimDefSeqNo, - DATA: self.toKeys - } - return Request(identifier=self.origin, operation=op) - - def _opForGet(self): - op = { - TXN_TYPE: GET_ISSUER_KEY, - REF: self.claimDefSeqNo, - ORIGIN: self.origin - } - return op - - def getRequest(self, requestAuthor: Identifier): - if not self.seqNo: - return Request(identifier=requestAuthor, operation=self._opForGet()) - - @property - def attributeNames(self): - R = getattr(self, "R", None) - if not R: - raise RuntimeError("Cannot get attribute names since key has not been fetched completely") - return [n for n in R.keys() if n != ZERO_INDEX] - - def canBeUsedForAttrsFrom(self, issuerId, attrNames): - return issuerId == self.origin and \ - set(attrNames).issubset(set(self.attributeNames)) diff --git a/sovrin/client/wallet/credential.py b/sovrin/client/wallet/credential.py deleted file mode 100644 index 1e04a59..0000000 --- a/sovrin/client/wallet/credential.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Dict - -from anoncreds.protocol.utils import strToCryptoInteger -from anoncreds.protocol.types import Credential as CredType - - -# A credential object is issued by an issuer to a prover for a particular -# issuer key. The prover is identified by a unique prover id. The wallet knows -# which prover is the owner of this credential -class Credential: - def __init__(self, issuerKeyId: int, A, e, v): - self.issuerKeyId = issuerKeyId - self.A = strToCryptoInteger(A) if isinstance(A, str) else A - self.e = strToCryptoInteger(e) if isinstance(e, str) else e - self.v = strToCryptoInteger(v) if isinstance(v, str) else v - - @property - def key(self): - return self.issuerKeyId - - @classmethod - def buildFromIssuerProvidedCred(cls, issuerKeyId, A, e, v, vprime): - vprime = strToCryptoInteger(vprime) if isinstance(vprime, str) else vprime - cred = cls(issuerKeyId, A, e, v) - cred.v += vprime - return cred - - @property - def toNamedTuple(self): - return CredType(self.A, self.e, self.v) diff --git a/sovrin/client/wallet/issuer_wallet.py b/sovrin/client/wallet/issuer_wallet.py deleted file mode 100644 index 6f61460..0000000 --- a/sovrin/client/wallet/issuer_wallet.py +++ /dev/null @@ -1,79 +0,0 @@ -import uuid -from typing import Dict, Tuple, Iterable, Optional - -from anoncreds.protocol.cred_def_secret_key import CredDefSecretKey -from anoncreds.protocol.issuer_secret_key import IssuerSecretKey -from plenum.common.log import getlogger -from sovrin.client.wallet.claim_def import IssuerPubKey, ClaimDef -from sovrin.client.wallet.credential import Credential -from sovrin.common.exceptions import ClaimDefNotFound - - -logger = getlogger() - - -class IssuerWallet: - def __init__(self, defaultClaimType=None): - self._masterSecret = None - self._vprimes = {} - self._credentials = {} # type: Dict[str, Credential] - self._defaultClaimType = defaultClaimType - - # Attributes this wallet has for others. Think of an Issuer's attribute - # repo containing attributes for different Provers. Key is a nonce and - # value is a map of attributes - self.attributesFor = {} # type: Dict[str, Dict] - - def createClaimDef(self, name, version, attrNames, typ=None, - credDefSeqNo=None, idr=None): - idr = idr or self.defaultId - # TODO: Directly using anoncreds lib, should use plugin - claimDef = ClaimDef(seqNo=credDefSeqNo, - attrNames=attrNames, - name=name, - version=version, - origin=idr, - typ=typ or self._defaultClaimType) - self.addClaimDef(claimDef) - return claimDef - - def _generateIssuerSecretKey(self, claimDef, csk=None): - csk = csk or CredDefSecretKey() - - # TODO why are we using a uuid here? The uid should be the - # seqNo of the pubkey in Sovrin - isk = IssuerSecretKey(claimDef, csk, uid=str(uuid.uuid4())) - return isk - - def createIssuerKey(self, - claimDefSeqNo=None, # this or a claimDef must exist - claimDef=None, - seqNo=None, - identifier=None, - csk=None): - idr = identifier or self.defaultId - claimDef = claimDef or self.getClaimDef(seqNo=claimDefSeqNo) - # TODO this code assumes the claim def is local. It should retrieve - # it if not found - if not claimDef: - raise ClaimDefNotFound(claimDefSeqNo) - - isk = self._generateIssuerSecretKey(claimDef, csk=csk) - self.addIssuerSecretKey(isk) - - ipk = IssuerPubKey(N=isk.PK.N, R=isk.PK.R, S=isk.PK.S, Z=isk.PK.Z, - claimDefSeqNo=claimDef.seqNo, - seqNo=seqNo, - origin=idr) - self.addIssuerPublicKey(ipk) - return ipk - - def addClaimDefSk(self, claimDefSk): - uid = str(uuid.uuid4()) - self._claimDefSks[uid] = claimDefSk - return uid - - def getClaimDefSk(self, claimDefSeqNo) -> Optional[IssuerSecretKey]: - for isk in self._issuerSks.values(): - if isk.cd.seqNo == claimDefSeqNo: - return isk.sk diff --git a/sovrin/client/wallet/link.py b/sovrin/client/wallet/link.py index 432467e..68c09b7 100644 --- a/sovrin/client/wallet/link.py +++ b/sovrin/client/wallet/link.py @@ -1,11 +1,9 @@ -from typing import Dict - from plenum.common.txn import NAME, NONCE from plenum.common.types import f from plenum.common.util import prettyDateDifference + from sovrin.common.exceptions import InvalidLinkException, \ RemoteEndpointNotFound -from sovrin.common.util import getNonce, verifySig, getMsgWithoutSig class constant: @@ -36,21 +34,25 @@ class constant: NOT_AVAILABLE = "Not Available" + NOT_ASSIGNED = "Not Assigned yet" + class Link: - def __init__(self, name, localIdentifier, trustAnchor=None, - remoteIdentifier=None, remoteEndPoint=None, invitationNonce=None, - claimProofRequests=None, invitationData: Dict=None, + def __init__(self, + name, + localIdentifier=None, + trustAnchor=None, + remoteIdentifier=None, + remoteEndPoint=None, + invitationNonce=None, + claimProofRequests=None, internalId=None): self.name = name self.localIdentifier = localIdentifier - self.verkey = self.localIdentifier.split(":")[-1] - self.trustAnchor = trustAnchor self.remoteIdentifier = remoteIdentifier self.remoteEndPoint = remoteEndPoint self.invitationNonce = invitationNonce - self.invitationData = invitationData # for optionally storing a reference to an identifier in another system # for example, a college may already have a student ID for a particular @@ -59,7 +61,7 @@ def __init__(self, name, localIdentifier, trustAnchor=None, self.claimProofRequests = claimProofRequests or [] self.verifiedClaimProofs = [] - self.availableClaims = [] # type: List[tupe(name, version, origin)] + self.availableClaims = [] # type: List[tupe(name, version, origin)] self.targetVerkey = None self.linkStatus = None self.linkLastSynced = None @@ -82,6 +84,8 @@ def isAccepted(self): return self.linkStatus == constant.LINK_STATUS_ACCEPTED def __str__(self): + localIdr = self.localIdentifier if self.localIdentifier \ + else constant.NOT_ASSIGNED trustAnchor = self.trustAnchor or "" trustAnchorStatus = '(not yet written to Sovrin)' targetVerKey = constant.UNKNOWN_WAITING_FOR_SYNC @@ -104,6 +108,8 @@ def __str__(self): # TODO: The verkey would be same as the local identifier until we # support key rotation + # TODO: This should be set as verkey in case of DID but need it from + # wallet verKey = constant.SIGNER_VER_KEY_SAME_AS_ID fixedLinkHeading = "Link " if not self.isAccepted: @@ -114,7 +120,7 @@ def __str__(self): fixedLinkItems = \ '\n' \ 'Name: ' + self.name + '\n' \ - 'Identifier: ' + self.localIdentifier + '\n' \ + 'Identifier: ' + localIdr + '\n' \ 'Trust anchor: ' + trustAnchor + ' ' + trustAnchorStatus + '\n' \ 'Verification key: ' + verKey + '\n' \ 'Signing key: ' '\n' \ @@ -131,13 +137,13 @@ def __str__(self): optionalLinkItems = "" if len(self.claimProofRequests) > 0: optionalLinkItems += "Claim Request(s): {}". \ - format(", ".join([cr.name for cr in self.claimProofRequests])) \ + format(", ".join([cr.name for cr in self.claimProofRequests])) \ + '\n' if self.availableClaims: - optionalLinkItems += "Available Claim(s): {}".\ - format(", ".join([name - for name, _, _ in self.availableClaims])) \ + optionalLinkItems += "Available Claim(s): {}". \ + format(", ".join([name + for name, _, _ in self.availableClaims])) \ + '\n' if self.linkLastSyncNo: @@ -174,4 +180,44 @@ def getRemoteEndpoint(self, required=False): return self.remoteEndPoint else: ip, port = self.remoteEndPoint.split(":") - return ip, int(port) \ No newline at end of file + return ip, int(port) + + +class ClaimProofRequest: + def __init__(self, name, version, attributes, verifiableAttributes): + self.name = name + self.version = version + self.attributes = attributes + self.verifiableAttributes = verifiableAttributes + + @property + def toDict(self): + return { + "name": self.name, + "version": self.version, + "attributes": self.attributes + } + + @property + def attributeValues(self): + return \ + 'Attributes:' + '\n ' + \ + format("\n ".join( + ['{}: {}'.format(k, v) + for k, v in self.attributes.items()])) + '\n' + + @property + def verifiableAttributeValues(self): + return \ + 'Verifiable Attributes:' + '\n ' + \ + format("\n ".join( + ['{}'.format(v) + for v in self.verifiableAttributes])) + '\n' + + def __str__(self): + fixedInfo = \ + 'Status: Requested' + '\n' \ + 'Name: ' + self.name + '\n' \ + 'Version: ' + self.version + '\n' + + return fixedInfo + self.attributeValues + self.verifiableAttributeValues diff --git a/sovrin/client/wallet/prover_wallet.py b/sovrin/client/wallet/prover_wallet.py deleted file mode 100644 index ddebfb8..0000000 --- a/sovrin/client/wallet/prover_wallet.py +++ /dev/null @@ -1,147 +0,0 @@ -from typing import Dict, Tuple, Iterable, Optional - -from anoncreds.protocol.proof_builder import ProofBuilder -from anoncreds.protocol.prover import Prover -from anoncreds.protocol.utils import generateMasterSecret, generateVPrime -from plenum.common.log import getlogger -from sovrin.client.wallet.claim import ClaimProofRequest -from sovrin.client.wallet.credential import Credential -from sovrin.common.util import getEncodedAttrs - - -logger = getlogger() - - -class ProverWallet: - def __init__(self): - self._masterSecret = None - self._vprimes = {} - self._credentials = {} # type: Dict[str, Credential] - - # Attributes this wallet has from others. Think of an Prover's - # attribute repo containing attributes from different Issuers. Key is a - # identifier and value is a map of attributes - self.attributesFrom = {} # type: Dict[str, Dict] - - @property - def masterSecret(self): - if not self._masterSecret: - self._masterSecret = generateMasterSecret() - return self._masterSecret - - def getVPrimes(self, *keys): - return Prover.getVPrimes(self, *keys) - - def getIssuedAttributes(self, issuerId, encoded=False): - attributes = self.attributesFrom.get(issuerId, {}) - if encoded: - attributes = getEncodedAttrs(issuerId, attributes).get(issuerId) - return attributes - - def addCredential(self, alias, cred: Credential): - self._credentials[alias] = cred - - def getCredential(self, name: str): - return self._credentials.get(name) - - def getCredentialByIssuerKey(self, seqNo: int, required=False): - issuerPk = self.getIssuerPublicKey(seqNo=seqNo) - if not issuerPk: - raise RuntimeError("Cannot find issuer key with seqNo {} in wallet" - .format(seqNo)) - for cred in self._credentials.values(): - if cred.issuerKeyId == seqNo: - return cred - if required: - raise RuntimeError("Credential not found in wallet for issuer key" - " {}".format(issuerPk)) - - @property - def credNames(self): - return self._credentials.keys() - - def buildClaimProof(self, nonce, cpr: ClaimProofRequest): - # Assuming building proof from a single claim - attrNames = set(cpr.attributes.keys()) - matchedAttrs = set() - issuerAttrs = {} - for iid, attributes in self.attributesFrom.items(): - lookingFor = attrNames - matchedAttrs - commonAttrs = lookingFor.intersection(set(attributes.keys())) - if commonAttrs: - issuerAttrs[iid] = commonAttrs - matchedAttrs.update(commonAttrs) - if len(matchedAttrs) == len(attrNames): - break - - creds = {} - issuerPks = {} - encodedAttrs = {} - claimDefKeys = {} - revealedAttrs = [] - - # Use credential for each issuer's attributes - for issuerId, attrs in issuerAttrs.items(): - # Get issuer key for these `attrs` - # Then get credential for that issuer key - for uid, ipk in self._issuerPks.items(): - if ipk.canBeUsedForAttrsFrom(issuerId, attrs): - issuerPks[issuerId] = ipk - creds[issuerId] = self.getCredentialByIssuerKey( - seqNo=ipk.seqNo).toNamedTuple - claimDef = self.getClaimDef(seqNo=ipk.claimDefSeqNo) - claimDefKeys[issuerId] = list(claimDef.key) - revealedAttrs.extend(list(attrs)) - encodedAttrs.update(getEncodedAttrs(issuerId, - self.attributesFrom[issuerId])) - - # REMOVE-LOG: Remove the next log - logger.debug("issuerPks, masterSecret, creds, revealedAttrs, nonce, " - "encodedAttrs {} {} {} {} {} {}".format(issuerPks, - self.masterSecret, - creds, - revealedAttrs, - nonce, - encodedAttrs)) - proof = ProofBuilder.prepareProofAsDict(issuerPks=issuerPks, - masterSecret=self.masterSecret, - creds=creds, - revealedAttrs=revealedAttrs, - nonce=nonce, - encodedAttrs=encodedAttrs) - return proof, encodedAttrs, revealedAttrs, claimDefKeys - - def getUValueForIssuerKeys(self, seqNos: Optional[Iterable[int]]=None, - keys: Optional[Iterable[Tuple[str, int]]]=None, - claimDefs: Optional[Iterable[ - Tuple[str, Tuple[str, str, str]]]]=None): - # 2 of the 3 args should be None - assert (seqNos, keys, claimDefs).count(None) == 2 - if seqNos: - count = len(seqNos) - getter = self.getIssuerPublicKey - args = [{'seqNo': seqNo} for seqNo in seqNos] - elif keys: - count = len(keys) - getter = self.getIssuerPublicKey - args = [{'key': key} for key in keys] - else: - count = len(claimDefs) - getter = self.getIssuerPublicKeyForClaimDef - args = [{'issuerId': iid, 'claimDefKey': cdKey} for iid, cdKey in - claimDefs] - - # TODO: Question: What if we have more get more than one issuer key - # for any issuer - issuerKeys = {} - for arg in args: - ipk = getter(**arg) - if ipk: - issuerKeys[ipk.origin] = ipk - - assert len(issuerKeys) == count, "Required {} keys but found {}".\ - format(count, len(issuerKeys)) - masterSecret = self.masterSecret - vprime = self.getVPrimes(*tuple(issuerKeys.keys())) - proofBuilder = ProofBuilder(issuerKeys, masterSecret, vprime=vprime) - return proofBuilder.U diff --git a/sovrin/client/wallet/sponsoring.py b/sovrin/client/wallet/sponsoring.py index e14c90a..0fb681b 100644 --- a/sovrin/client/wallet/sponsoring.py +++ b/sovrin/client/wallet/sponsoring.py @@ -2,6 +2,7 @@ from plenum.common.txn import STEWARD from plenum.common.types import Identifier + from sovrin.common.identity import Identity from sovrin.common.txn import SPONSOR @@ -20,6 +21,9 @@ def addSponsoredIdentity(self, idy: Identity): if idy.identifier in self._sponsored: raise RuntimeError("identifier already added") self._sponsored[idy.identifier] = idy + self._sendIdReq(idy) + + def _sendIdReq(self, idy): req = idy.ledgerRequest() if req: if not req.identifier: @@ -27,4 +31,11 @@ def addSponsoredIdentity(self, idy: Identity): self.pendRequest(req, idy.identifier) return len(self._pending) + def updateSponsoredIdentity(self, idy): + storedId = self._sponsored.get(idy.identifier) + if storedId: + storedId.seqNo = None + self._sendIdReq(idy) + def getSponsoredIdentity(self, idr): + return self._sponsored.get(idr) diff --git a/sovrin/client/wallet/wallet.py b/sovrin/client/wallet/wallet.py index 35f62dc..79e7a33 100644 --- a/sovrin/client/wallet/wallet.py +++ b/sovrin/client/wallet/wallet.py @@ -1,32 +1,25 @@ import datetime import json import operator -import uuid from collections import deque from typing import Dict from typing import Optional -from anoncreds.protocol.utils import strToCryptoInteger from ledger.util import F from plenum.client.wallet import Wallet as PWallet from plenum.common.did_method import DidMethods from plenum.common.log import getlogger from plenum.common.txn import TXN_TYPE, TARGET_NYM, DATA, \ - IDENTIFIER, NAME, VERSION, TYPE, NYM, ROLE, ORIGIN + IDENTIFIER, NYM, ROLE, VERKEY from plenum.common.types import Identifier, f + from sovrin.client.wallet.attribute import Attribute, AttributeKey -from sovrin.client.wallet.claim_def import ClaimDef, IssuerPubKey -from sovrin.client.wallet.issuer_wallet import IssuerWallet from sovrin.client.wallet.link import Link -from sovrin.client.wallet.prover_wallet import ProverWallet from sovrin.client.wallet.sponsoring import Sponsoring from sovrin.common.did_method import DefaultDidMethods from sovrin.common.exceptions import LinkNotFound from sovrin.common.identity import Identity -from sovrin.common.txn import ATTRIB, GET_TXNS, GET_ATTR, CRED_DEF, \ - GET_CRED_DEF, \ - GET_NYM, ATTR_NAMES, ISSUER_KEY, GET_ISSUER_KEY, REF -from sovrin.common.util import stringDictToCharmDict +from sovrin.common.txn import ATTRIB, GET_TXNS, GET_ATTR, GET_NYM ENCODING = "utf-8" @@ -34,33 +27,22 @@ # TODO: Maybe we should have a thinner wallet which should not have ProverWallet -class Wallet(PWallet, Sponsoring, ProverWallet, IssuerWallet): +class Wallet(PWallet, Sponsoring): clientNotPresentMsg = "The wallet does not have a client associated with it" def __init__(self, name: str, - supportedDidMethods: DidMethods=None, - defaultClaimType=None): + supportedDidMethods: DidMethods=None): PWallet.__init__(self, name, supportedDidMethods or DefaultDidMethods) Sponsoring.__init__(self) - ProverWallet.__init__(self) - IssuerWallet.__init__(self, defaultClaimType or 'CL') self._attributes = {} # type: Dict[(str, Identifier, # Optional[Identifier]), Attribute] - self._claimDefs = {} # type: Dict[(str, str, str), ClaimDef] - self._claimDefSks = {} # type: Dict[(str, str, str), ClaimDefSk] self._links = {} # type: Dict[str, Link] self.knownIds = {} # type: Dict[str, Identifier] - # TODO: Shouldnt proof builders be uniquely identified by claim def - # and issuerId - # TODO: Create claim objects - - self._issuerSks = {} - self._issuerPks = {} # transactions not yet submitted self._pending = deque() # type Tuple[Request, Tuple[str, Identifier, # Optional[Identifier]] @@ -72,13 +54,9 @@ def __init__(self, self.replyHandler = { ATTRIB: self._attribReply, GET_ATTR: self._getAttrReply, - CRED_DEF: self._claimDefReply, - GET_CRED_DEF: self._getClaimDefReply, NYM: self._nymReply, GET_NYM: self._getNymReply, GET_TXNS: self._getTxnsReply, - ISSUER_KEY: self._issuerKeyReply, - GET_ISSUER_KEY: self._getIssuerKeyReply, } @property @@ -89,69 +67,20 @@ def pendingCount(self): def _isMatchingName(needle, haystack): return needle.lower() in haystack.lower() - def getClaimAttrs(self, claimDefKey) -> Dict: - # TODO: The issuer can be different than the author of the claim - # definition. But assuming that issuer is the author of the claim - # definition for now - issuerId = claimDefKey[2] - claimDef = self.getClaimDef(key=claimDefKey) - if claimDef and claimDef.attrNames and issuerId in self.attributesFrom: - return {nm: self.attributesFrom[issuerId].get(nm) for nm - in claimDef.attrNames} - return {} - - def getMatchingRcvdClaims(self, attributes): - matchingLinkAndRcvdClaim = [] - matched = set() - attrNames = set(attributes.keys()) - for li in self._links.values(): - issuedAttributes = self.attributesFrom.get(li.remoteIdentifier) - if issuedAttributes: - commonAttrs = (attrNames.difference(matched).intersection( - set(issuedAttributes.keys()))) - if commonAttrs: - for nm, ver, origin in li.availableClaims: - cd = self.getClaimDef(key=(nm, ver, origin)) - if cd and cd.seqNo and set(cd.attrNames).intersection( - commonAttrs): - matchingLinkAndRcvdClaim.append((li, - (nm, ver, origin), - commonAttrs, - issuedAttributes)) - matched.update(commonAttrs) - break - return matchingLinkAndRcvdClaim - # TODO: The names getMatchingLinksWithAvailableClaim and # getMatchingLinksWithReceivedClaim should be fixed. Difference between # `AvailableClaim` and `ReceivedClaim` is that for ReceivedClaim we # have attribute values from issuer. # TODO: Few of the below methods have duplicate code, need to refactor it - def getMatchingLinksWithAvailableClaim(self, claimName): + def getMatchingLinksWithAvailableClaim(self, claimName=None): matchingLinkAndAvailableClaim = [] for k, li in self._links.items(): for cl in li.availableClaims: - if Wallet._isMatchingName(claimName, cl[0]): + if not claimName or Wallet._isMatchingName(claimName, cl[0]): matchingLinkAndAvailableClaim.append((li, cl)) return matchingLinkAndAvailableClaim - def getMatchingLinksWithReceivedClaim(self, claimName): - matchingLinkAndReceivedClaim = [] - for k, li in self._links.items(): - for cl in li.availableClaims: - if Wallet._isMatchingName(claimName, cl[0]): - claimDef = self.getClaimDef(key=cl) - issuedAttributes = self.attributesFrom.get( - li.remoteIdentifier) - if issuedAttributes: - claimAttrs = set(claimDef.attrNames) - if claimAttrs.intersection(issuedAttributes.keys()): - matchingLinkAndReceivedClaim.append( - (li, cl, {k: issuedAttributes[k] for k in - claimAttrs})) - return matchingLinkAndReceivedClaim - def getMatchingLinksWithClaimReq(self, claimReqName, linkName=None): matchingLinkAndClaimReq = [] for k, li in self._links.items(): @@ -188,33 +117,6 @@ def getAttribute(self, key: AttributeKey): def getAttributesForNym(self, idr: Identifier): return [a for a in self._attributes.values() if a.dest == idr] - def addAttrFrom(self, frm, attrs): - assert isinstance(attrs, dict) - if frm not in self.attributesFrom: - self.attributesFrom[frm] = {} - self.attributesFrom[frm].update(attrs) - - def addClaimDef(self, claimDef: ClaimDef): - """ - Used to create a new cred def on Sovrin - :param claimDef: claimDef to add - :return: number of pending txns - """ - self._claimDefs[claimDef.key] = claimDef - req = claimDef.request - if req: - self.pendRequest(req, claimDef.key) - return len(self._pending) - - def getClaimDef(self, key=None, seqNo=None): - assert key or seqNo - if key: - return self._claimDefs.get(key) - else: - for _, cd in self._claimDefs.items(): - if cd.seqNo == seqNo: - return cd - def addLink(self, link: Link): self._links[link.key] = link @@ -279,8 +181,8 @@ def handleIncomingReply(self, observer_name, reqId, frm, result, typ = result.get(TXN_TYPE) if typ and typ in self.replyHandler: self.replyHandler[typ](result, preparedReq) - else: - raise NotImplementedError('No handler for {}'.format(typ)) + # else: + # raise NotImplementedError('No handler for {}'.format(typ)) def _attribReply(self, result, preparedReq): _, attrKey = preparedReq @@ -299,34 +201,6 @@ def _getAttrReply(self, result, preparedReq): else: logger.debug("No attribute found") - def _claimDefReply(self, result, preparedReq): - # TODO: Duplicate code from _attribReply, abstract this behavior, - # Have a mixin like `HasSeqNo` - _, key = preparedReq - claimDef = self.getClaimDef(key) - claimDef.seqNo = result[F.seqNo.name] - - def _getClaimDefReply(self, result, preparedReq): - data = json.loads(result.get(DATA)) - if data: - claimDef = self.getClaimDef((data.get(NAME), data.get(VERSION), - data.get(ORIGIN))) - if claimDef: - if not claimDef.seqNo: - claimDef.seqNo = data.get(F.seqNo.name) - claimDef.attrNames = data[ATTR_NAMES].split(",") - claimDef.typ = data[TYPE] - else: - claimDef = ClaimDef(seqNo=data.get(F.seqNo.name), - attrNames=data.get(ATTR_NAMES).split(","), - name=data[NAME], - version=data[VERSION], - origin=data[ORIGIN], - typ=data[TYPE]) - self._claimDefs[claimDef.key] = claimDef - else: - logger.info("Requested claim def was not found on the ledger") - def _nymReply(self, result, preparedReq): target = result[TARGET_NYM] idy = self._sponsored.get(target) @@ -334,7 +208,7 @@ def _nymReply(self, result, preparedReq): idy.seqNo = result[F.seqNo.name] else: logger.error("Target {} not found in sponsored".format(target)) - raise NotImplementedError + raise KeyError def _getNymReply(self, result, preparedReq): jsonData = result.get(DATA) @@ -346,53 +220,16 @@ def _getNymReply(self, result, preparedReq): idy.role = data.get(ROLE) idy.sponsor = data.get(f.IDENTIFIER.nm) idy.last_synced = datetime.datetime.utcnow() + idy.verkey = data.get(VERKEY) # TODO: THE GET_NYM reply should contain the sequence number of # the NYM transaction else: - raise NotImplementedError("'DATA' in reply was None") + raise ValueError("'DATA' in reply was None") def _getTxnsReply(self, result, preparedReq): # TODO pass - def _issuerKeyReply(self, result, preparedReq): - data = result.get(DATA) - ref = result.get(REF) - key = self._getMatchingIssuerKey(data) - if key and self._issuerPks[key].claimDefSeqNo == ref: - self._issuerPks[key].seqNo = result.get(F.seqNo.name) - return self._issuerPks[key].seqNo - else: - raise Exception("Not found appropriate issuer key to update") - - def _getIssuerKeyReply(self, result, preparedReq): - data = json.loads(result.get(DATA)) - if data: - key = data.get(ORIGIN), data.get(REF) - isPk = self.getIssuerPublicKey(key=key) - keys = data.get(DATA) - for k in ('N', 'S', 'Z'): - keys[k] = strToCryptoInteger(keys[k]) - keys['R'] = stringDictToCharmDict(keys['R']) - isPk.initPubKey(data.get(F.seqNo.name), keys['N'], keys['R'], - keys['S'], keys['Z']) - else: - logger.info("Requested issuer key was not found on the ledger") - - def _getMatchingIssuerKey(self, data): - for key, pk in self._issuerPks.items(): - if str(pk.N) == data.get("N") and str(pk.S) == data.get("S") and \ - str(pk.Z) == data.get("Z"): - matches = 0 - for k, v in pk.R.items(): - if str(pk.R.get(k)) == data.get("R").get(k): - matches += 1 - else: - break - if matches == len(pk.R): - return key - return None - def pendRequest(self, req, key=None): self._pending.appendleft((req, key)) @@ -431,103 +268,23 @@ def requestIdentity(self, identity: Identity, sender): if req: return self.prepReq(req) - # TODO: sender by default should be `self.defaultId` - def requestClaimDef(self, claimDefKey, sender): - # Used to get a cred def from Sovrin - name, version, origin = claimDefKey - claimDef = ClaimDef(name=name, version=version, origin=origin) - self._claimDefs[claimDefKey] = claimDef - req = claimDef.getRequest(sender) - if req: - return self.prepReq(req) - - # TODO: sender by default should be `self.defaultId` - def requestIssuerKey(self, issuerKey, sender): - # Used to get a issuer key from Sovrin - origin, claimDefSeqNo = issuerKey - isPk = IssuerPubKey(origin=origin, claimDefSeqNo=claimDefSeqNo) - self._issuerPks[issuerKey] = isPk - req = isPk.getRequest(sender) - if req: - return self.prepReq(req) - def prepReq(self, req, key=None): self.pendRequest(req, key=key) return self.preparePending()[0] # DEPR - # def getLinkByNonce(self, nonce) -> Optional[Link]: - # for _, li in self._links.items(): - # if li.nonce == nonce: - # return li + # Why shouldn't we fetch link by nonce + def getLinkByNonce(self, nonce) -> Optional[Link]: + for _, li in self._links.items(): + if li.invitationNonce == nonce: + return li def getLinkByInternalId(self, internalId) -> Optional[Link]: for _, li in self._links.items(): if li.internalId == internalId: return li - def addIssuerSecretKey(self, issuerSk): - self._issuerSks[issuerSk.pubkey.uid] = issuerSk - return issuerSk.pubkey.uid - - def getIssuerSecretKey(self, uid): - return self._issuerSks.get(uid) - - def addIssuerPublicKey(self, issuerPk): - # Add an issuer key on Sovrin - self._issuerPks[issuerPk.key] = issuerPk - req = issuerPk.request - if req: - self.pendRequest(req, None) - return len(self._pending) - - def getIssuerPublicKey(self, key=None, seqNo=None) -> Optional[ - IssuerPubKey]: - assert key or seqNo - if key: - return self._issuerPks.get(key) - else: - for _, pk in self._issuerPks.items(): - if pk.seqNo == seqNo: - return pk - return self._issuerPks.get(key) - - def getIssuerPublicKeyForClaimDef(self, issuerId, seqNo=None, - claimDefKey=None, required=False) -> \ - Optional[IssuerPubKey]: - notFoundMsg = "Issuer public key not found" - if not seqNo: - claimDef = self.getClaimDef(key=claimDefKey) - if not claimDef: - logger.info("Cannot get issuer key by claim def since claim " - "def not fetched yet: {}".format(claimDefKey)) - if required: - raise RuntimeError(notFoundMsg) - return None - else: - seqNo = claimDef.seqNo - ipk = self.getIssuerPublicKey(key=(issuerId, seqNo)) - if ipk: - return ipk - - if required: - raise RuntimeError(notFoundMsg) - - def getAllIssuerKeysForClaimDef(self, seqNo): - return [ipk for ipk in self._issuerPks.values() - if ipk.claimDefSeqNo == seqNo] - - def getAvailableClaimList(self): - resp = [] - for k, v in self._claimDefs.items(): - ipks = self.getAllIssuerKeysForClaimDef(v.seqNo) - resp.extend([(v, ipk) for ipk in ipks]) - return resp - - def isClaimDefComplete(self, claimDefKey): - claimDef = self.getClaimDef(key=claimDefKey) - return claimDef and claimDef.seqNo - - def isIssuerKeyComplete(self, origin, reference): - ipk = self.getIssuerPublicKey(key=(origin, reference)) - return ipk and ipk.seqNo + def getIdentity(self, idr): + # TODO, Question: Should it consider self owned identities too or + # should it just have identities that are retrieved from the DL + return self.knownIds.get(idr) \ No newline at end of file diff --git a/sovrin/common/config_util.py b/sovrin/common/config_util.py index ac64982..8f505c8 100644 --- a/sovrin/common/config_util.py +++ b/sovrin/common/config_util.py @@ -1,6 +1,6 @@ +import os from importlib import import_module from importlib.util import module_from_spec, spec_from_file_location -import os from plenum.common.config_util import getConfig as PlenumConfig diff --git a/sovrin/common/exceptions.py b/sovrin/common/exceptions.py index 17c39ff..95feb52 100644 --- a/sovrin/common/exceptions.py +++ b/sovrin/common/exceptions.py @@ -28,5 +28,12 @@ class LinkAlreadyExists(RuntimeError): pass +class LinkNotReady(RuntimeError): + """ + Some operation is attempted on a link that is not ready for that operation + """ + pass + + class NotConnectedToNetwork(RuntimeError): pass diff --git a/sovrin/common/identity.py b/sovrin/common/identity.py index 71243d0..aebcf6b 100644 --- a/sovrin/common/identity.py +++ b/sovrin/common/identity.py @@ -1,5 +1,6 @@ from plenum.common.txn import TARGET_NYM, TXN_TYPE, NYM, ROLE, STEWARD, VERKEY from plenum.common.types import Identifier + from sovrin.common.generates_request import GeneratesRequest from sovrin.common.txn import SPONSOR, GET_NYM from sovrin.common.types import Request diff --git a/sovrin/common/plugin_helper.py b/sovrin/common/plugin_helper.py index 34565bc..df98d3d 100644 --- a/sovrin/common/plugin_helper.py +++ b/sovrin/common/plugin_helper.py @@ -1,9 +1,10 @@ - import os + from sovrin.common.config_util import getConfig +#TODO: make anoncreds pluggable -def writeAnonCredPlugin(baseDir, reloadTestModules:bool=False): +def writeAnonCredPlugin(baseDir, reloadTestModules: bool = False): config = getConfig() pluginsPath = os.path.expanduser(os.path.join(baseDir, config.PluginsDir)) @@ -14,40 +15,9 @@ def writeAnonCredPlugin(baseDir, reloadTestModules:bool=False): with open(initFile, "a"): pass - anonPluginFilePath = pluginsPath + "/anoncreds.py" - anonPluginContent = "" \ - "import importlib\n" \ - "\n" \ - "import anoncreds.protocol.issuer\n" \ - "import anoncreds.protocol.verifier\n" \ - "import anoncreds.protocol.prover\n" \ - "\n" \ - "import sovrin.anon_creds.issuer\n" \ - "import sovrin.anon_creds.verifier\n"\ - "import sovrin.anon_creds.prover\n" \ - "\n" \ - "Name = \"Anon creds\"\n" \ - "Version = 1.1\n" \ - "SovrinVersion = 1.1\n" \ - "\n" \ - "sovrin.anon_creds.issuer.Credential = anoncreds.protocol.types.Credential\n" \ - "sovrin.anon_creds.issuer.AttribType = anoncreds.protocol.types.AttribType\n" \ - "sovrin.anon_creds.issuer.AttribDef = anoncreds.protocol.types.AttribDef\n" \ - "sovrin.anon_creds.issuer.Attribs = anoncreds.protocol.types.Attribs\n" \ - "sovrin.anon_creds.issuer.AttrRepo = anoncreds.protocol.attribute_repo.AttrRepo\n" \ - "sovrin.anon_creds.issuer.InMemoryAttrRepo = anoncreds.protocol.attribute_repo.InMemoryAttrRepo\n" \ - "sovrin.anon_creds.issuer.Issuer = anoncreds.protocol.issuer.Issuer\n" \ - "sovrin.anon_creds.prover.Prover = anoncreds.protocol.prover.Prover\n" \ - "sovrin.anon_creds.verifier.Verifier = anoncreds.protocol.verifier.Verifier\n" \ - "sovrin.anon_creds.proof_builder.ProofBuilder = anoncreds.protocol.proof_builder.ProofBuilder\n" \ - "sovrin.anon_creds.proof_builder.Proof = anoncreds.protocol.types.Proof\n" \ - "sovrin.anon_creds.cred_def.CredDef = anoncreds.protocol.credential_definition.CredentialDefinition\n" \ - modules_to_reload = ["sovrin.cli.cli"] test_modules_to_reload = [ - "sovrin.test.helper", "sovrin.test.cli.helper", - "sovrin.test.anon_creds.conftest", - "sovrin.test.anon_creds.test_anon_creds", + "sovrin.test.helper", "sovrin.test.cli.helper" # "sovrin.test.anon_creds.anon_creds_demo" ] @@ -56,14 +26,10 @@ def writeAnonCredPlugin(baseDir, reloadTestModules:bool=False): reload_module_code = \ "reload_modules = " + str(modules_to_reload) + "\n" \ - "for m in reload_modules:\n" \ - " try:\n" \ - " module_obj = importlib.import_module(m)\n" \ - " importlib.reload(module_obj)\n" \ - " except AttributeError as ae:\n" \ - " print(\"Plugin loading failed: module {}, detail: {}\".format(m, str(ae)))\n" \ - "\n" - - anonPluginContent += reload_module_code - with open(anonPluginFilePath, "w") as f: - f.write(anonPluginContent) + "for m in reload_modules:\n" \ + " try:\n" \ + " module_obj = importlib.import_module(m)\n" \ + " importlib.reload(module_obj)\n" \ + " except AttributeError as ae:\n" \ + " print(\"Plugin loading failed: module {}, detail: {}\".format(m, str(ae)))\n" \ + "\n" diff --git a/sovrin/common/setup_util.py b/sovrin/common/setup_util.py index 9389460..96a72ab 100644 --- a/sovrin/common/setup_util.py +++ b/sovrin/common/setup_util.py @@ -1,12 +1,7 @@ import glob import os - -from shutil import copyfile - import shutil - -import data -import sample +from shutil import copyfile class Setup: @@ -19,6 +14,8 @@ def setupAll(self): self.setupSampleInvites() def setupTxns(self): + import data + pool_txn_file = os.path.join(self.base_dir, "pool_transactions_sandbox") pool_txn_local_file = os.path.join(self.base_dir, "pool_transactions_local") identity_txn_file = os.path.join(self.base_dir, "transactions_sandbox") @@ -32,6 +29,7 @@ def setupTxns(self): return self def setupSampleInvites(self): + import sample sdir = os.path.dirname(sample.__file__) sidir = os.path.join(self.base_dir, "sample") os.makedirs(sidir, exist_ok=True) diff --git a/sovrin/common/txn.py b/sovrin/common/txn.py index 4a53067..0b4de55 100644 --- a/sovrin/common/txn.py +++ b/sovrin/common/txn.py @@ -2,7 +2,7 @@ from collections import OrderedDict from plenum.common.txn import TXN_TYPE, TARGET_NYM, ORIGIN, DATA, TXN_ID, TXN_TIME, \ - RAW, ENC, HASH, NAME, VERSION, TYPE, KEYS, IP, PORT, POOL_TXN_TYPES, ALIAS, \ + RAW, ENC, HASH, NAME, VERSION, TYPE, POOL_TXN_TYPES, ALIAS, \ STEWARD, NYM, VERKEY from plenum.common.types import f @@ -17,6 +17,8 @@ ENC_TYPE = "encType" SKEY = "secretKey" REF = "ref" +PRIMARY = "primary" +REVOCATION = "revocation" allOpKeys = (TXN_TYPE, TARGET_NYM, VERKEY, ORIGIN, ROLE, DATA, NONCE, REF, RAW, ENC, HASH, ALIAS) @@ -37,8 +39,8 @@ GET_NYM = "GET_NYM" GET_TXNS = "GET_TXNS" GET_TXN = "GET_TXN" -CRED_DEF = "CRED_DEF" -GET_CRED_DEF = "GET_CRED_DEF" +CLAIM_DEF = "CLAIM_DEF" +GET_CLAIM_DEF = "GET_CLAIM_DEF" ADD_PKI = "ADD_PKI" REQ_CRED = "REQ_CRED" GET_NONCE = "GET_NONCE" @@ -49,14 +51,14 @@ # Temp for demo GEN_CRED = "GEN_CRED" -openTxns = (GET_NYM, GET_ATTR, GET_CRED_DEF, GET_ISSUER_KEY) +openTxns = (GET_NYM, GET_ATTR, GET_CLAIM_DEF, GET_ISSUER_KEY) # TXN_TYPE -> (requireds, optionals) fields = {NYM: ([TARGET_NYM], [ROLE]), ATTRIB: ([], [RAW, ENC, HASH]), - CRED_DEF: ([NAME, VERSION, ATTR_NAMES], [TYPE, ]), - GET_CRED_DEF: ([], []), + CLAIM_DEF: ([NAME, VERSION, ATTR_NAMES], [TYPE, ]), + GET_CLAIM_DEF: ([], []), ISSUER_KEY: ([REF, DATA]), GET_ISSUER_KEY: ([REF, ORIGIN]) } @@ -69,8 +71,8 @@ GET_ATTR, GET_NYM, GET_TXNS, - CRED_DEF, - GET_CRED_DEF, + CLAIM_DEF, + GET_CLAIM_DEF, ISSUER_KEY, GET_ISSUER_KEY} validTxnTypes.update(POOL_TXN_TYPES) diff --git a/sovrin/common/types.py b/sovrin/common/types.py index 16c5af0..b6eae17 100644 --- a/sovrin/common/types.py +++ b/sovrin/common/types.py @@ -1,9 +1,10 @@ from copy import deepcopy from hashlib import sha256 +from plenum.common.request import Request as PRequest from plenum.common.txn import TXN_TYPE, RAW, ENC, HASH from plenum.common.types import OPERATION -from plenum.common.request import Request as PRequest + from sovrin.common.txn import ATTRIB diff --git a/sovrin/common/util.py b/sovrin/common/util.py index 8472494..951b1f2 100644 --- a/sovrin/common/util.py +++ b/sovrin/common/util.py @@ -1,21 +1,14 @@ import datetime -import json import random -from functools import partial from typing import Tuple, Union import libnacl.secret from base58 import b58decode from plenum.common.signing import serializeMsg -from plenum.common.txn import KEYS, DATA, ORIGIN from plenum.common.types import f from plenum.common.util import isHex, error, cryptonymToHex from raet.nacling import Verifier -from anoncreds.protocol.types import AttribType, AttribDef -from anoncreds.protocol.utils import strToCryptoInteger, isCryptoInteger -from ledger.util import F - def getMsgWithoutSig(msg, sigFieldName=f.SIG.nm): msgWithoutSig = {} @@ -35,7 +28,7 @@ def verifySig(identifier, signature, msg) -> bool: return vr.verify(sig, ser) -def getSymmetricallyEncryptedVal(val, secretKey: Union[str, bytes]=None) -> \ +def getSymmetricallyEncryptedVal(val, secretKey: Union[str, bytes] = None) -> \ Tuple[str, str]: """ Encrypt the provided value with symmetric encryption @@ -78,106 +71,11 @@ def dateTimeEncoding(obj): raise TypeError('Not sure how to serialize %s' % (obj,)) -def getCredDefTxnData(claimDef): - claimDef = claimDef.get() - keys = claimDef[KEYS] - keys["R"].pop("0") - keys = { - "master_secret_rand": int(keys.get("master_secret_rand")), - "N": int(keys.get("N")), - "S": int(keys.get("S")), - "Z": int(keys.get("Z")), - "attributes": {k: int(v) for k, v in keys["R"].items()} - } - claimDef[KEYS] = json.dumps(keys) - return claimDef - - def getNonce(length=32): hexChars = [hex(i)[2:] for i in range(0, 16)] return "".join([random.choice(hexChars) for i in range(length)]) -# TODO: Bad interface, should return just attributes in a dictionary -def getEncodedAttrs(issuerId, attributes): - attribTypes = [] - for nm in attributes.keys(): - attribTypes.append(AttribType(nm, encode=True)) - attribsDef = AttribDef(issuerId, attribTypes) - attribs = attribsDef.attribs(**attributes).encoded() - return { - issuerId: next(iter(attribs.values())) - } - - -def stringDictToCharmDict(dictionary): - for k, v in dictionary.items(): - if isinstance(v, str): - dictionary[k] = strToCryptoInteger(v) - return dictionary - - -def charmDictToStringDict(dictionary): - for k, v in dictionary.items(): - if isCryptoInteger(v) or isinstance(v, int): - dictionary[k] = str(v) - return dictionary - - -def getIssuerKeyAndExecuteClbk(wallet, client, displayer, loop, origin, - reference, clbk, pargs=None): - - chk = partial(wallet.isIssuerKeyComplete, origin, reference) - if not chk(): - req = wallet.requestIssuerKey((origin, reference), wallet.defaultId) - client.submitReqs(req) - if displayer: - displayer("Getting Keys for the Claim Definition from Sovrin") - if pargs is not None: - loop.call_later(.2, ensureReqCompleted, loop, req.key, client, - clbk, pargs, None, chk) - else: - loop.call_later(.2, ensureReqCompleted, loop, req.key, client, - clbk, None, None, chk) - else: - # Since reply and error will be none - if pargs is None: - clbk(None, None) - else: - clbk(None, None, *pargs) - - -def getCredDefIsrKeyAndExecuteCallback(wallet, client, displayer, - loop, claimDefKey, clbk, pargs=None): - - # TODO Fix the following - # ATTENTION: This assumes that author of claimDef is same as the author of - # issuerPublicKey - def _getKey(result, error): - data = json.loads(result.get(DATA)) - if data: - origin = data.get(ORIGIN) - seqNo = data.get(F.seqNo.name) - else: - origin = None - seqNo = None - getIssuerKeyAndExecuteClbk(wallet, client, displayer, loop, origin, - seqNo, clbk, pargs) - - chk = partial(wallet.isClaimDefComplete, claimDefKey) - if not chk(): - req = wallet.requestClaimDef(claimDefKey, wallet.defaultId) - client.submitReqs(req) - displayer("Getting Claim Definition from Sovrin: {} {}" - .format(claimDefKey[0], claimDefKey[1])) - loop.call_later(.2, ensureReqCompleted, loop, req.key, client, - _getKey, None, None, chk) - else: - claimDef = wallet.getClaimDef(key=claimDefKey) - getIssuerKeyAndExecuteClbk(wallet, client, displayer, loop, - claimDef.origin, claimDef.seqNo, clbk, pargs) - - # TODO: Should have a timeout, should not have kwargs def ensureReqCompleted(loop, reqKey, client, clbk=None, pargs=None, kwargs=None, cond=None): @@ -201,4 +99,4 @@ def ensureReqCompleted(loop, reqKey, client, clbk=None, pargs=None, kwargs=None, def getNonceForProof(nonce): - return int(nonce, 16) \ No newline at end of file + return int(nonce, 16) diff --git a/sovrin/config.py b/sovrin/config.py index efc733d..db66d37 100644 --- a/sovrin/config.py +++ b/sovrin/config.py @@ -69,4 +69,4 @@ RAETMessageTimeout = 30 -PluginsToLoad = ["anoncreds"] +PluginsToLoad = [] diff --git a/sovrin/persistence/attribute_store_file.py b/sovrin/persistence/attribute_store_file.py index 635744c..c50cdd2 100644 --- a/sovrin/persistence/attribute_store_file.py +++ b/sovrin/persistence/attribute_store_file.py @@ -4,6 +4,7 @@ from ledger.stores.directory_store import DirectoryStore from plenum.common.txn import ORIGIN, TARGET_NYM, NAME, RAW, ENC, HASH + from sovrin.common.txn import SKEY, ENC_TYPE from sovrin.persistence.attribute_store import AttributeStore diff --git a/sovrin/persistence/client_req_rep_store_file.py b/sovrin/persistence/client_req_rep_store_file.py index 44b90c2..bb115cd 100644 --- a/sovrin/persistence/client_req_rep_store_file.py +++ b/sovrin/persistence/client_req_rep_store_file.py @@ -1,6 +1,6 @@ import json - import os + from plenum.common.util import updateFieldsWithSeqNo from plenum.persistence.client_req_rep_store_file import ClientReqRepStoreFile \ as PClientReqRepStoreFile diff --git a/sovrin/persistence/client_req_rep_store_orientdb.py b/sovrin/persistence/client_req_rep_store_orientdb.py index 6b49c5e..51bbee2 100644 --- a/sovrin/persistence/client_req_rep_store_orientdb.py +++ b/sovrin/persistence/client_req_rep_store_orientdb.py @@ -3,12 +3,12 @@ from plenum.common.txn import TXN_ID from plenum.common.txn import TXN_TYPE, TXN_TIME from plenum.common.types import f -from sovrin.common.types import Request from plenum.common.util import checkIfMoreThanFSameItems, getMaxFailures, \ updateFieldsWithSeqNo from plenum.persistence.orientdb_store import OrientDbStore from sovrin.common.txn import getTxnOrderedFields +from sovrin.common.types import Request from sovrin.persistence.client_req_rep_store import ClientReqRepStore REQ_DATA = "ReqData" diff --git a/sovrin/persistence/credential_def_store.py b/sovrin/persistence/credential_def_store.py deleted file mode 100644 index 9fed642..0000000 --- a/sovrin/persistence/credential_def_store.py +++ /dev/null @@ -1,12 +0,0 @@ -from abc import abstractmethod -from typing import Dict - - -class CredDefStore: - def addCredDef(self, name: str, version: str, dest: str, type: str, ip: str, - port: int, keys: Dict): - pass - - @abstractmethod - def getCredDef(self, name: str, version: str, dest: str = None): - pass diff --git a/sovrin/persistence/credential_def_store_file.py b/sovrin/persistence/credential_def_store_file.py deleted file mode 100644 index 072eac7..0000000 --- a/sovrin/persistence/credential_def_store_file.py +++ /dev/null @@ -1,39 +0,0 @@ -import base64 -import json -from typing import Dict - -from ledger.stores.directory_store import DirectoryStore -from plenum.common.txn import NAME, TYPE, IP, PORT, KEYS, VERSION, TARGET_NYM -from sovrin.persistence.credential_def_store import CredDefStore - - -class CredDefStoreFile(CredDefStore): - def __init__(self, baseDir: str, name: str): - self.store = DirectoryStore(baseDir, name) - - @staticmethod - def key(name: str, version: str, dest: str): - key = "{}_{}_{}".format(name, version, dest) - return base64.urlsafe_b64encode(key.encode()).decode() - - def addCredDef(self, name: str, version: str, dest: str, type: str, ip: str, - port: int, keys: Dict): - key = self.key(name, version, dest) - self.store.put(key, json.dumps({ - TYPE: type, - IP: ip, - PORT: port, - KEYS: keys - })) - - def getCredDef(self, name: str, version: str, dest: str = None): - key = self.key(name, version, dest) - value = self.store.get(key) - if value: - credDef = json.loads(value) - credDef.update({ - NAME: name, - VERSION: version, - TARGET_NYM: dest - }) - return credDef diff --git a/sovrin/persistence/entity_file_store.py b/sovrin/persistence/entity_file_store.py index 203181f..2f95de5 100644 --- a/sovrin/persistence/entity_file_store.py +++ b/sovrin/persistence/entity_file_store.py @@ -1,4 +1,5 @@ from ledger.stores.text_file_store import TextFileStore + from sovrin.persistence.entity_store import EntityStore diff --git a/sovrin/persistence/identity_graph.py b/sovrin/persistence/identity_graph.py index 0ae39cb..43fb8bd 100644 --- a/sovrin/persistence/identity_graph.py +++ b/sovrin/persistence/identity_graph.py @@ -1,8 +1,8 @@ -import json -from itertools import chain import datetime +import json import time from functools import reduce +from itertools import chain from typing import Dict, Optional import pyorient @@ -17,10 +17,9 @@ from plenum.server.node import Node from sovrin.common.txn import NYM, TXN_ID, TARGET_NYM, USER, SPONSOR, \ - STEWARD, ROLE, REF, TXN_TIME, ATTRIB, CRED_DEF, isValidRole, \ + STEWARD, ROLE, REF, TXN_TIME, ATTRIB, CLAIM_DEF, isValidRole, \ ATTR_NAMES, ISSUER_KEY - logger = getlogger() MIN_TXN_TIME = time.mktime(datetime.datetime(2000, 1, 1).timetuple()) @@ -29,13 +28,13 @@ class Vertices: Nym = NYM Attribute = "Attribute" - CredDef = "CredDef" + ClaimDef = "ClaimDef" IssuerKey = "IssuerKey" _Properties = { - Nym: (NYM, TXN_ID, ROLE, F.seqNo.name), + Nym: (NYM, VERKEY, TXN_ID, ROLE, F.seqNo.name), Attribute: (RAW, ENC, HASH), - CredDef: (TYPE, ATTR_NAMES), + ClaimDef: (TYPE, ATTR_NAMES), IssuerKey: (REF, DATA) } @@ -52,14 +51,14 @@ class Edges: # TODO: Create KnowsAttribute in case the attribute is shared (disclosed) # with someone AliasOf = "AliasOf" - AddsCredDef = "AddsCredDef" + AddsClaimDef = "AddsClaimDef" HasIssuerKey = "HasIssuerKey" txnEdges = { NYM: Edges.AddsNym, ATTRIB: Edges.AddsAttribute, - CRED_DEF: Edges.AddsCredDef, + CLAIM_DEF: Edges.AddsClaimDef, ISSUER_KEY: Edges.HasIssuerKey } @@ -84,13 +83,13 @@ def classesNeeded(self): return [ (Vertices.Nym, self.createNymClass), (Vertices.Attribute, self.createAttributeClass), - (Vertices.CredDef, self.createCredDefClass), + (Vertices.ClaimDef, self.createClaimDefClass), (Vertices.IssuerKey, self.createIssuerKeyClass), (Edges.AddsNym, self.createAddsNymClass), (Edges.AliasOf, self.createAliasOfClass), (Edges.AddsAttribute, self.createAddsAttributeClass), (Edges.HasAttribute, self.createHasAttributeClass), - (Edges.AddsCredDef, self.createAddsCredDefClass), + (Edges.AddsClaimDef, self.createAddsClaimDefClass), (Edges.HasIssuerKey, self.createHasIssuerClass) ] @@ -143,8 +142,8 @@ def createAttributeClass(self): self.createVertexClass(Vertices.Attribute, properties={"data": "string"}) - def createCredDefClass(self): - self.createVertexClass(Vertices.CredDef, properties={ + def createClaimDefClass(self): + self.createVertexClass(Vertices.ClaimDef, properties={ ATTR_NAMES: "string", TYPE: "string", }) @@ -177,13 +176,13 @@ def createHasAttributeClass(self): self.createUniqueTxnIdEdgeClass(Edges.HasAttribute) self.addEdgeConstraint(Edges.HasAttribute, iN=Vertices.Attribute) - def createAddsCredDefClass(self): + def createAddsClaimDefClass(self): # TODO: Add compound index on the name and version - self.createUniqueTxnIdEdgeClass(Edges.AddsCredDef, properties={ + self.createUniqueTxnIdEdgeClass(Edges.AddsClaimDef, properties={ NAME: "string", VERSION: "string" }) - self.addEdgeConstraint(Edges.AddsCredDef, iN=Vertices.CredDef) + self.addEdgeConstraint(Edges.AddsClaimDef, iN=Vertices.ClaimDef) def createHasIssuerClass(self): self.createUniqueTxnIdEdgeClass(Edges.HasIssuerKey) @@ -259,13 +258,13 @@ def addAttribute(self, frm, txnId, raw=None, enc=None, hash=None, to=None): } self.createEdge(Edges.HasAttribute, to, attrVertex._rid, **kwargs) - def addCredDef(self, frm, txnId, name, version, attrNames, + def addClaimDef(self, frm, txnId, name, version, attrNames, typ: Optional[str]=None): kwargs = { TYPE: typ, ATTR_NAMES: attrNames } - vertex = self.createVertex(Vertices.CredDef, **kwargs) + vertex = self.createVertex(Vertices.ClaimDef, **kwargs) frm = "(select from {} where {} = '{}')".format(Vertices.Nym, NYM, frm) kwargs = { @@ -273,7 +272,7 @@ def addCredDef(self, frm, txnId, name, version, attrNames, NAME: name, VERSION: version } - self.createEdge(Edges.AddsCredDef, frm, vertex._rid, **kwargs) + self.createEdge(Edges.AddsClaimDef, frm, vertex._rid, **kwargs) def addIssuerKey(self, frm, txnId, data, reference): kwargs = { @@ -288,6 +287,13 @@ def addIssuerKey(self, frm, txnId, data, reference): } self.createEdge(Edges.HasIssuerKey, frm, vertex._rid, **kwargs) + def updateNym(self, txnId, nym, verkey, seqNo): + self.updateEntityWithUniqueId(Vertices.Nym, NYM, nym, **{ + TXN_ID: txnId, + VERKEY: verkey, + F.seqNo.name: seqNo + }) + def getRawAttrs(self, frm, *attrNames): cmd = 'select expand(outE("{}").inV("{}")) from {} where {}="{}"'.\ format(Edges.HasAttribute, Vertices.Attribute, Vertices.Nym, @@ -309,25 +315,25 @@ def getRawAttrs(self, frm, *attrNames): result[key] = [value, seqNos[attrRec._rid]] return result - def getCredDef(self, frm, name, version): + def getClaimDef(self, frm, name, version): # TODO: Can this query be made similar to get attribute? cmd = "select outV('{}')[{}='{}'], expand(inV('{}')) from {} where " \ "name = '{}' and version = '{}'".format(Vertices.Nym, NYM, frm, - Vertices.CredDef, - Edges.AddsCredDef, name, + Vertices.ClaimDef, + Edges.AddsClaimDef, name, version) - credDefs = self.client.command(cmd) - if credDefs: - credDef = credDefs[0].oRecordData + claimDefs = self.client.command(cmd) + if claimDefs: + claimDef = claimDefs[0].oRecordData edgeData = self.client.command( "select expand(inE('{}')) from {}".format( - Edges.AddsCredDef, credDefs[0]._rid))[0].oRecordData + Edges.AddsClaimDef, claimDefs[0]._rid))[0].oRecordData return { NAME: name, VERSION: version, - TYPE: credDef.get(TYPE), + TYPE: claimDef.get(TYPE), F.seqNo.name: edgeData.get(F.seqNo.name), - ATTR_NAMES: credDef.get(ATTR_NAMES), + ATTR_NAMES: claimDef.get(ATTR_NAMES), ORIGIN: frm, } return None @@ -419,7 +425,8 @@ def getAddNymTxn(self, nym): return { TXN_ID: nymV.oRecordData.get(TXN_ID), TARGET_NYM: nym, - ROLE: nymV.oRecordData.get(ROLE) + ROLE: nymV.oRecordData.get(ROLE), + VERKEY: nymV.oRecordData.get(VERKEY) } else: edgeData = nymEdge.oRecordData @@ -431,6 +438,7 @@ def getAddNymTxn(self, nym): edgeData['in'].get()) result[f.IDENTIFIER.nm] = frm.oRecordData.get(NYM) result[TARGET_NYM] = to.oRecordData.get(NYM) + result[VERKEY] = to.oRecordData.get(VERKEY) return result def getAddAttributeTxnIds(self, nym): @@ -537,10 +545,22 @@ def addNymTxnToGraph(self, txn): verkey = txn.get(VERKEY) or '' try: txnId = txn[TXN_ID] - self.addNym(txnId, nym, verkey, role, - frm=origin, reference=txn.get(REF), - seqNo=txn.get(F.seqNo.name)) - self._updateTxnIdEdgeWithTxn(txnId, Edges.AddsNym, txn) + seqNo = txn.get(F.seqNo.name) + # Since NYM vertex has a unique index on the identifier, + # (CID or DID) a unique constraint violattion would occur if the + # nym exists. Instead of catching an exception, a call to hasNym or + # getNym could be done but since NYM update txns would be less + # common then NYM adding transactions so avoidinhg the cost of + # extra db query + try: + self.addNym(txnId, nym, verkey, role, + frm=origin, reference=txn.get(REF), + seqNo=seqNo) + except pyorient.PyOrientORecordDuplicatedException: + self.updateNym(txnId, nym, verkey, seqNo) + else: + # Only update edge in case of new NYM transaction + self._updateTxnIdEdgeWithTxn(txnId, Edges.AddsNym, txn) except pyorient.PyOrientORecordDuplicatedException: logger.debug("The nym {} was already added to graph". format(nym)) @@ -560,19 +580,19 @@ def addAttribTxnToGraph(self, txn): fault(ex, "An exception was raised while adding attribute: {}". format(ex)) - def addCredDefTxnToGraph(self, txn): + def addClaimDefTxnToGraph(self, txn): origin = txn.get(f.IDENTIFIER.nm) txnId = txn[TXN_ID] data = txn.get(DATA) try: - self.addCredDef( + self.addClaimDef( frm=origin, txnId=txnId, name=data.get(NAME), version=data.get(VERSION), attrNames=data.get(ATTR_NAMES), typ=data.get(TYPE)) - self._updateTxnIdEdgeWithTxn(txnId, Edges.AddsCredDef, txn) + self._updateTxnIdEdgeWithTxn(txnId, Edges.AddsClaimDef, txn) except Exception as ex: fault(ex, "Error adding cred def to orientdb") @@ -659,7 +679,7 @@ def makeResult(txnType, oRecordData): result[n] = oRecordData[n] break - if txnType == CRED_DEF: + if txnType == CLAIM_DEF: result[DATA] = {} for n in [IP, PORT, KEYS, TYPE, NAME, VERSION]: if n in oRecordData: diff --git a/sovrin/persistence/secondary_storage.py b/sovrin/persistence/secondary_storage.py index ccafd49..ce05120 100644 --- a/sovrin/persistence/secondary_storage.py +++ b/sovrin/persistence/secondary_storage.py @@ -1,6 +1,7 @@ from ledger.util import F from plenum.common.txn import TXN_TYPE from plenum.persistence.secondary_storage import SecondaryStorage as PlenumSS + from sovrin.common.txn import NYM diff --git a/sovrin/persistence/wallet_storage.py b/sovrin/persistence/wallet_storage.py deleted file mode 100644 index 1542469..0000000 --- a/sovrin/persistence/wallet_storage.py +++ /dev/null @@ -1,61 +0,0 @@ -# DEPR -# from abc import abstractmethod, abstractproperty -# from typing import Any, Dict -# -# from plenum.persistence.wallet_storage import WalletStorage as PWalletStorage -# -# -# class WalletStorage(PWalletStorage): -# @abstractmethod -# def addAttribute(self, name: str, val: Any, origin: str, dest: str=None, -# encKey: str=None, encType: str=None, hashed: bool=False): -# pass -# -# @abstractmethod -# def getAttribute(self, name: str, dest: str=None): -# pass -# -# @abstractproperty -# def attributes(self): -# pass -# -# @abstractmethod -# def addCredDef(self, name: str, version: str, dest: str, type: str, ip: str, -# port: int, keys: Dict): -# pass -# -# @abstractmethod -# def getCredDef(self, name: str, version: str, dest: str = None): -# pass -# -# @abstractmethod -# def addCredDefSk(self, name: str, version: str, secretKey): -# pass -# -# @abstractmethod -# def getCredDefSk(self, name: str, version: str): -# pass -# -# @abstractmethod -# def addCredential(self, name: str, data: Dict): -# pass -# -# @abstractmethod -# def getCredential(self, name: str): -# pass -# -# @abstractmethod -# def addMasterSecret(self, masterSecret): -# pass -# -# @abstractproperty -# def masterSecret(self): -# pass -# -# @abstractmethod -# def addLinkInvitation(self, linkInvitation): -# pass -# -# @abstractmethod -# def getMatchingLinkInvitations(self, name: str): -# pass \ No newline at end of file diff --git a/sovrin/persistence/wallet_storage_file.py b/sovrin/persistence/wallet_storage_file.py deleted file mode 100644 index 0adf949..0000000 --- a/sovrin/persistence/wallet_storage_file.py +++ /dev/null @@ -1,111 +0,0 @@ -# DEPRECATED -# import json -# import os -# from typing import Any, Dict -# -# from ledger.stores.directory_store import DirectoryStore -# from ledger.stores.text_file_store import TextFileStore -# from plenum.persistence.wallet_storage_file import WalletStorageFile \ -# as PWalletStorageFile -# from sovrin.client.link import LinkInvitation -# -# from sovrin.persistence.attribute_store_file import AttributeStoreFile -# from sovrin.persistence.credential_def_store_file import CredDefStoreFile -# from sovrin.persistence.wallet_storage import WalletStorage -# -# -# class WalletStorageFile(WalletStorage, PWalletStorageFile): -# def __init__(self, walletDir: str): -# PWalletStorageFile.__init__(self, walletDir) -# attrsDirName = "attributes" -# credDefDirName = "credential_definitions" -# credFileName = "credentials" -# credDefKeys = "credential_definition_keys" -# masterSecret = "master_secret" -# linkInvitations = "link_invitations" -# -# dataDir = self.dataLocation -# -# self.attrStore = AttributeStoreFile(dataDir, attrsDirName) -# # type: AttributeStoreFile -# -# self.credDefStore = CredDefStoreFile(dataDir, credDefDirName) -# # type: CredDefStoreFile -# -# self.credStore = TextFileStore(dataDir, credFileName, -# storeContentHash=False) -# self.credDefKeyStore = TextFileStore(dataDir, credDefKeys, -# storeContentHash=False) -# self.masterSecretStore = TextFileStore(dataDir, masterSecret, -# isLineNoKey = True, -# storeContentHash=False) -# -# self.linkInvitationStore = DirectoryStore(dataDir, linkInvitations) -# -# -# def addAttribute(self, name: str, val: Any, origin: str, dest: str = None, -# encKey: str = None, encType: str = None, -# hashed: bool = False): -# self.attrStore.addAttribute(name, val, origin, dest, encKey, encType, -# hashed) -# -# def getAttribute(self, name: str, dest: str = None): -# return self.attrStore.getAttribute(name, dest) -# -# @property -# def attributes(self): -# return self.attrStore.attributes -# -# def addCredDef(self, name: str, version: str, dest: str, type: str, ip: str, -# port: int, keys: Dict): -# self.credDefStore.addCredDef(name, version, dest, type, ip, port, keys) -# -# def getCredDef(self, name: str, version: str, dest: str = None): -# return self.credDefStore.getCredDef(name, version, dest) -# -# def addCredential(self, name: str, data: Dict): -# self.credStore.put(key=name, value=json.dumps(data)) -# -# def getCredential(self, name: str): -# return self.credStore.get(name) -# -# @property -# def credNames(self): -# keys = [] -# for k, v in self.credStore.iterator(): -# keys.append(k) -# return keys -# -# @staticmethod -# def credDefKeyStoreKey(name, version): -# return "{},{}".format(name, version) -# -# def addCredDefSk(self, name: str, version: str, secretKey): -# key = self.credDefKeyStoreKey(name, version) -# self.credDefKeyStore.put(key=key, value=secretKey) -# -# def getCredDefSk(self, name: str, version: str): -# key = self.credDefKeyStoreKey(name, version) -# return self.credDefKeyStore.get(key) -# -# def addMasterSecret(self, masterSecret): -# self.masterSecretStore.put(value=masterSecret) -# -# def addLinkInvitation(self, linkInvitation): -# self.linkInvitationStore.put(key=linkInvitation.name, -# value=json.dumps( -# linkInvitation.getDictToBeStored())) -# -# def getMatchingLinkInvitations(self, name: str): -# allMatched = [] -# for k, v in self.linkInvitationStore.iterator(): -# if name == k or name.lower() in k.lower(): -# liValues = json.loads(v) -# li = LinkInvitation.getFromDict(k, liValues) -# allMatched.append(li) -# return allMatched -# -# @property -# def masterSecret(self): -# # Getting the first line of the file, so using key `1` -# return self.masterSecretStore.get("1") diff --git a/sovrin/server/client_authn.py b/sovrin/server/client_authn.py index f0195aa..5c320d6 100644 --- a/sovrin/server/client_authn.py +++ b/sovrin/server/client_authn.py @@ -1,10 +1,10 @@ -from _sha256 import sha256 +from hashlib import sha256 from copy import deepcopy from plenum.common.exceptions import UnknownIdentifier from plenum.common.txn import TXN_TYPE, RAW, ENC, HASH - from plenum.server.client_authn import NaclAuthNr + from sovrin.common.txn import ATTRIB from sovrin.persistence.identity_graph import IdentityGraph diff --git a/sovrin/server/node.py b/sovrin/server/node.py index d30bc23..193fb76 100644 --- a/sovrin/server/node.py +++ b/sovrin/server/node.py @@ -1,5 +1,5 @@ import json -from _sha256 import sha256 +from hashlib import sha256 from copy import deepcopy from operator import itemgetter from typing import Iterable, Any @@ -8,26 +8,26 @@ from ledger.compact_merkle_tree import CompactMerkleTree from ledger.ledger import Ledger from ledger.serializers.compact_serializer import CompactSerializer - from ledger.util import F from plenum.common.exceptions import InvalidClientRequest, \ UnauthorizedClientRequest from plenum.common.log import getlogger from plenum.common.txn import RAW, ENC, HASH, NAME, VERSION, ORIGIN -from sovrin.common.types import Request from plenum.common.types import Reply, RequestAck, RequestNack, f, \ NODE_PRIMARY_STORAGE_SUFFIX, OPERATION from plenum.common.util import error from plenum.persistence.storage import initStorage from plenum.server.node import Node as PlenumNode + +from sovrin.common.config_util import getConfig from sovrin.common.txn import TXN_TYPE, \ TARGET_NYM, allOpKeys, validTxnTypes, ATTRIB, SPONSOR, NYM,\ ROLE, STEWARD, USER, GET_ATTR, DISCLO, DATA, GET_NYM, \ TXN_ID, TXN_TIME, reqOpKeys, GET_TXNS, LAST_TXN, TXNS, \ - getTxnOrderedFields, CRED_DEF, GET_CRED_DEF, isValidRole, openTxns, \ + getTxnOrderedFields, CLAIM_DEF, GET_CLAIM_DEF, isValidRole, openTxns, \ ISSUER_KEY, GET_ISSUER_KEY, REF +from sovrin.common.types import Request from sovrin.common.util import dateTimeEncoding -from sovrin.common.config_util import getConfig from sovrin.persistence import identity_graph from sovrin.persistence.secondary_storage import SecondaryStorage from sovrin.server.client_authn import TxnBasedAuthNr @@ -87,7 +87,8 @@ def getPrimaryStorage(self): return Ledger(CompactMerkleTree(hashStore=self.hashStore), dataDir=self.dataLocation, serializer=CompactSerializer(fields=fields), - fileName=self.config.domainTransactionsFile) + fileName=self.config.domainTransactionsFile, + ensureDurability=self.config.EnsureLedgerDurability) else: return initStorage(self.config.primaryStorage, name=self.name + NODE_PRIMARY_STORAGE_SUFFIX, @@ -150,7 +151,7 @@ def checkValidSovrinOperation(self, identifier, reqId, msg): 'JSON'.format(msg[RAW])) if not (not msg.get(TARGET_NYM) or - self.graphStore.hasNym(msg[TARGET_NYM])): + self.graphStore.hasNym(msg[TARGET_NYM])): raise InvalidClientRequest(identifier, reqId, '{} should be added before adding ' 'attribute for it'. @@ -158,14 +159,20 @@ def checkValidSovrinOperation(self, identifier, reqId, msg): if msg[TXN_TYPE] == NYM: role = msg.get(ROLE) or USER + nym = msg.get(TARGET_NYM) + if not nym: + raise InvalidClientRequest(identifier, reqId, + "{} needs to be present". + format(TARGET_NYM)) if not isValidRole(role): raise InvalidClientRequest(identifier, reqId, "{} not a valid role". format(role)) - if self.graphStore.hasNym(msg[TARGET_NYM]): + # Only + if not self.canNymRequestBeProcessed(identifier, msg): raise InvalidClientRequest(identifier, reqId, "{} is already present". - format(msg[TARGET_NYM])) + format(nym)) def checkRequestAuthorized(self, request: Request): op = request.operation @@ -200,12 +207,19 @@ def checkRequestAuthorized(self, request: Request): request.reqId, "Only user's sponsor can add attribute for that user") # TODO: Just for now. Later do something meaningful here - elif typ in [DISCLO, GET_ATTR, CRED_DEF, GET_CRED_DEF, ISSUER_KEY, + elif typ in [DISCLO, GET_ATTR, CLAIM_DEF, GET_CLAIM_DEF, ISSUER_KEY, GET_ISSUER_KEY]: pass else: return super().checkRequestAuthorized(request) + def canNymRequestBeProcessed(self, identifier, msg): + nym = msg.get(TARGET_NYM) + if self.graphStore.hasNym(nym) and \ + self.graphStore.getSponsorFor(nym) != identifier: + return False + return True + def defaultAuthNr(self): return TxnBasedAuthNr(self.graphStore) @@ -265,17 +279,17 @@ def processGetTxnReq(self, request: Request, frm: str): }) self.transmitToClient(Reply(result), frm) - def processGetCredDefReq(self, request: Request, frm: str): + def processGetClaimDefReq(self, request: Request, frm: str): issuerNym = request.operation[TARGET_NYM] name = request.operation[DATA][NAME] version = request.operation[DATA][VERSION] - credDef = self.graphStore.getCredDef(issuerNym, name, version) + claimDef = self.graphStore.getClaimDef(issuerNym, name, version) result = { TXN_ID: self.genTxnId( request.identifier, request.reqId) } result.update(request.operation) - result[DATA] = json.dumps(credDef, sort_keys=True) + result[DATA] = json.dumps(claimDef, sort_keys=True) result.update({ f.IDENTIFIER.nm: request.identifier, f.REQ_ID.nm: request.reqId, @@ -323,8 +337,8 @@ def processRequest(self, request: Request, frm: str): self.processGetNymReq(request, frm) elif request.operation[TXN_TYPE] == GET_TXNS: self.processGetTxnReq(request, frm) - elif request.operation[TXN_TYPE] == GET_CRED_DEF: - self.processGetCredDefReq(request, frm) + elif request.operation[TXN_TYPE] == GET_CLAIM_DEF: + self.processGetClaimDefReq(request, frm) elif request.operation[TXN_TYPE] == GET_ATTR: self.processGetAttrsReq(request, frm) elif request.operation[TXN_TYPE] == GET_ISSUER_KEY: @@ -351,9 +365,7 @@ def storeTxnAndSendToClient(self, reply): def storeTxnInLedger(self, result): if result[TXN_TYPE] == ATTRIB: result = self.hashAttribTxn(result) - merkleInfo = self.addToLedger(result) - else: - merkleInfo = self.addToLedger(result) + merkleInfo = self.addToLedger(result) result.update(merkleInfo) return result @@ -384,8 +396,8 @@ def storeTxnInGraph(self, result): self.graphStore.addNymTxnToGraph(result) elif result[TXN_TYPE] == ATTRIB: self.graphStore.addAttribTxnToGraph(result) - elif result[TXN_TYPE] == CRED_DEF: - self.graphStore.addCredDefTxnToGraph(result) + elif result[TXN_TYPE] == CLAIM_DEF: + self.graphStore.addClaimDefTxnToGraph(result) elif result[TXN_TYPE] == ISSUER_KEY: self.graphStore.addIssuerKeyTxnToGraph(result) else: @@ -426,8 +438,8 @@ def doCustomAction(self, ppTime: float, req: Request) -> None: :param ppTime: the time at which PRE-PREPARE was sent :param req: the client REQUEST """ - if req.operation[TXN_TYPE] == NYM and \ - self.graphStore.hasNym(req.operation[TARGET_NYM]): + if req.operation[TXN_TYPE] == NYM and not \ + self.canNymRequestBeProcessed(req.identifier, req.operation): reason = "nym {} is already added".format(req.operation[TARGET_NYM]) if req.key in self.requestSender: self.transmitToClient(RequestNack(*req.key, reason), diff --git a/sovrin/test/agent/acme.py b/sovrin/test/agent/acme.py index cae0607..84da01b 100644 --- a/sovrin/test/agent/acme.py +++ b/sovrin/test/agent/acme.py @@ -1,33 +1,29 @@ import os -from anoncreds.protocol.cred_def_secret_key import CredDefSecretKey from plenum.common.log import getlogger from plenum.common.txn import NAME, VERSION -from anoncreds.protocol.types import AttribType, AttribDef -from sovrin.agent.agent import runAgent +from anoncreds.protocol.types import AttribType, AttribDef, ClaimDefinitionKey, \ + ID +from sovrin.agent.agent import createAgent, runAgent from sovrin.agent.exception import NonceNotFound from sovrin.client.client import Client from sovrin.client.wallet.wallet import Wallet from sovrin.common.config_util import getConfig - from sovrin.test.agent.helper import buildAcmeWallet from sovrin.test.agent.test_walleted_agent import TestWalletedAgent +from sovrin.test.conftest import primes from sovrin.test.helper import TestClient logger = getlogger() class AcmeAgent(TestWalletedAgent): - credDefSecretKey = CredDefSecretKey( - p=281510790031673293930276619603927743196841646256795847064219403348133278500884496133426719151371079182558480270299769814938220686172645009573713670952475703496783875912436235928500441867163946246219499572100554186255001186037971377948507437993345047481989113938038765221910989549806472045341069625389921020319, - q=350024478159288302454189301319318317490551219044369889911215183350615705419868722006578530322735670686148639754382100627201250616926263978453441645496880232733783587241897694734699668219445029433427409979471473248066452686224760324273968172651114901114731981044897755380965310877273130485988045688817305189839) - def __init__(self, basedirpath: str, - client: Client=None, - wallet: Wallet=None, - port: int=None, + client: Client = None, + wallet: Wallet = None, + port: int = None, loop=None): if not basedirpath: config = getConfig() @@ -48,37 +44,63 @@ def __init__(self, "810b78be79f29fc81335abaa4ee1c5e8": 4 } - self._attributes = { - 1: { - "first_name": "Alice", - "last_name": "Garcia", - "employee_status": "Permanent", - "experience": "3 years", - "salary_bracket": "between $50,000 to $100,000" - }, - 2: { - "first_name": "Carol", - "last_name": "Atkinson", - "employee_status": "Permanent", - "experience": "2 years", - "salary_bracket": "between $60,000 to $90,000" - }, - 3: { - "first_name": "Frank", - "last_name": "Jeffrey", - "employee_status": "Temporary", - "experience": "4 years", - "salary_bracket": "between $40,000 to $80,000" - }, - 4: { - "first_name": "Craig", - "last_name": "Richards", - "employee_status": "On Contract", - "experience": "3 years", - "salary_bracket": "between $50,000 to $70,000" - }, + self._attrDefJobCert = AttribDef('Acme Job Certificat', + [AttribType('first_name', encode=True), + AttribType('last_name', encode=True), + AttribType('employee_status', + encode=True), + AttribType('experience', encode=True), + AttribType('salary_bracket', + encode=True)]) + + self._attrDefJobApp = AttribDef('Acme Job Application', + [AttribType('first_name', encode=True), + AttribType('last_name', encode=True), + AttribType('phone_number', + encode=True), + AttribType('degree', encode=True), + AttribType('status', encode=True), + AttribType('ssn', encode=True)]) + + # maps internal ids to attributes + self._attrsJobCert = { + 1: self._attrDefJobCert.attribs( + first_name="Alice", + last_name="Garcia", + employee_status="Permanent", + experience="3 years", + salary_bracket="between $50,000 to $100,000"), + 2: self._attrDefJobCert.attribs( + first_name="Carol", + last_name="Atkinson", + employee_status="Permanent", + experience="2 years", + salary_bracket="between $60,000 to $90,000"), + 3: self._attrDefJobCert.attribs( + first_name="Frank", + last_name="Jeffrey", + employee_status="Temporary", + experience="4 years", + salary_bracket="between $40,000 to $80,000"), + 4: self._attrDefJobCert.attribs( + first_name="Craig", + last_name="Richards", + employee_status="On Contract", + experience="3 years", + salary_bracket="between $50,000 to $70,000") } + self._claimDefJobCertKey = ClaimDefinitionKey("Job-Certificate", "0.2", + self.wallet.defaultId) + self._claimDefJobAppKey = ClaimDefinitionKey("Job-Application", "0.2", + self.wallet.defaultId) + + def _addAtrribute(self, claimDefKey, proverId, link): + attr = self._attrsJobCert[self.getInternalIdByInvitedNonce(proverId)] + self.issuer._attrRepo.addAttributes(claimDefKey=claimDefKey, + userId=proverId, + attributes=attr) + def getInternalIdByInvitedNonce(self, nonce): if nonce in self._invites: return self._invites[nonce] @@ -86,66 +108,52 @@ def getInternalIdByInvitedNonce(self, nonce): raise NonceNotFound def isClaimAvailable(self, link, claimName): - if claimName == "Job-Certificate" and \ - "Job-Application" in link.verifiedClaimProofs: - return True - else: - return False + return claimName == "Job-Certificate" and \ + "Job-Application" in link.verifiedClaimProofs def getAvailableClaimList(self): return self.availableClaims - def postClaimVerif(self, claimName, link, frm): - nac = self.newAvailableClaimsPostClaimVerif(claimName) + async def postClaimVerif(self, claimName, link, frm): + nac = await self.newAvailableClaimsPostClaimVerif(claimName) self.sendNewAvailableClaimsData(nac, frm, link) - def newAvailableClaimsPostClaimVerif(self, claimName): + async def newAvailableClaimsPostClaimVerif(self, claimName): if claimName == "Job-Application": - return self.getJobCertAvailableClaimList() + return await self.getJobCertAvailableClaimList() - def getJobCertAvailableClaimList(self): - claimDef = self.wallet.getClaimDef(key=("Job-Certificate", "0.2", - self.wallet.defaultId)) + async def getJobCertAvailableClaimList(self): + claimDef = await self.issuer.wallet.getClaimDef( + ID(self._claimDefJobCertKey)) return [{ - NAME: "Job-Certificate", - VERSION: "0.2", - "claimDefSeqNo": claimDef.seqNo + NAME: claimDef.name, + VERSION: claimDef.version, + "claimDefSeqNo": claimDef.seqId }] - def addClaimDefsToWallet(self): - name, version = "Job-Certificate", "0.2" - attrNames = ["first_name", "last_name", "employee_status", - "experience", "salary_bracket"] - self.addCredDefAndIskIfNotFoundOnLedger(name, version, - origin=self.wallet.defaultId, - attrNames=attrNames, typ='CL', - credDefSecretKey= - self.credDefSecretKey) - - def getAttributes(self, internalId): - attrs = self._attributes.get(internalId) - if not attrs: - if not attrs: - raise RuntimeError('attributes for internal ID {} not found'. - format(internalId)) - - attribTypes = [] - for name in attrs: - attribTypes.append(AttribType(name, encode=True)) - attribsDef = AttribDef("Job-Certificate", attribTypes) - attribs = attribsDef.attribs(**attrs) - return attribs - - def bootstrap(self): - self.addClaimDefsToWallet() - - -def runAcme(name=None, wallet=None, basedirpath=None, port=None, - startRunning=True, bootstrap=True): - - return runAgent(AcmeAgent, name or "Acme Corp", - wallet or buildAcmeWallet(), basedirpath, - port, startRunning, bootstrap, clientClass=TestClient) + async def addClaimDefsToWallet(self): + claimDefJobCert = await self.issuer.genClaimDef( + self._claimDefJobCertKey.name, + self._claimDefJobCertKey.version, + self._attrDefJobCert.attribNames(), + 'CL') + claimDefJobCertId = ID(claimDefKey=claimDefJobCert.getKey(), + claimDefId=claimDefJobCert.seqId) + p_prime, q_prime = primes["prime1"] + await self.issuer.genKeys(claimDefJobCertId, p_prime=p_prime, + q_prime=q_prime) + await self.issuer.issueAccumulator(claimDefId=claimDefJobCertId, iA='110', L=5) + + async def bootstrap(self): + await self.addClaimDefsToWallet() + + +def createAcme(name=None, wallet=None, basedirpath=None, port=None): + return createAgent(AcmeAgent, name or "Acme Corp", + wallet or buildAcmeWallet(), + basedirpath, port, clientClass=TestClient) + if __name__ == "__main__": - runAcme(port=6666) + acme = createAcme(port=6666) + runAgent(acme) diff --git a/sovrin/test/agent/conftest.py b/sovrin/test/agent/conftest.py index 9713bbb..32a436a 100644 --- a/sovrin/test/agent/conftest.py +++ b/sovrin/test/agent/conftest.py @@ -1,4 +1,6 @@ from plenum.common.port_dispenser import genHa +from plenum.common.signer_did import DidSigner + from sovrin.common.strict_types import strict_types from sovrin.test.agent.test_walleted_agent import TestWalletedAgent @@ -19,26 +21,20 @@ import sample from plenum.common.looper import Looper -from plenum.common.signer_simple import SimpleSigner from plenum.common.util import randomString from plenum.test.eventually import eventually from plenum.test.helper import assertFunc +from sovrin.agent.agent import runAgent from sovrin.agent.agent import WalletedAgent -from sovrin.client.client import Client from sovrin.client.wallet.attribute import Attribute, LedgerStore from sovrin.client.wallet.wallet import Wallet from sovrin.common.txn import SPONSOR, ENDPOINT -from sovrin.test.agent.acme import runAcme -from sovrin.test.agent.faber import runFaber +from sovrin.test.agent.acme import createAcme +from sovrin.test.agent.faber import createFaber from sovrin.test.agent.helper import ensureAgentsConnected, buildFaberWallet, \ buildAcmeWallet, buildThriftWallet -from sovrin.test.agent.thrift import runThrift -from sovrin.test.helper import addClaimDefAndIssuerKeys, TestClient -from sovrin.test.helper import createNym, addAttributeAndCheck - -from sovrin.test.conftest import nodeSet, updatedDomainTxnFile, \ - tdirWithDomainTxns, genesisTxns -from plenum.test.conftest import poolTxnStewardData, poolTxnStewardNames +from sovrin.test.agent.thrift import createThrift +from sovrin.test.helper import createNym, addAttributeAndCheck, TestClient # noinspection PyUnresolvedReferences from sovrin.test.conftest import nodeSet, updatedDomainTxnFile, \ @@ -58,7 +54,7 @@ def emptyLooper(): def walletBuilder(): def _(name): wallet = Wallet(name) - wallet.addIdentifier(signer=SimpleSigner()) + wallet.addIdentifier(signer=DidSigner()) return wallet return _ @@ -143,11 +139,9 @@ def thriftAgentPort(): @pytest.fixture(scope="module") def faberAgent(tdirWithPoolTxns, faberAgentPort, faberWallet): - agent = runFaber(faberWallet.name, faberWallet, - basedirpath=tdirWithPoolTxns, - port=faberAgentPort, - startRunning=False, bootstrap=False) - return agent + return createFaber(faberWallet.name, faberWallet, + basedirpath=tdirWithPoolTxns, + port=faberAgentPort) @pytest.fixture(scope="module") @@ -157,7 +151,6 @@ def faberAdded(nodeSet, emptyLooper, faberAgentPort, faberAgent): - attrib = createAgentAndAddEndpoint(emptyLooper, faberAgent.wallet.defaultId, faberAgent.wallet.getVerkey(), @@ -174,40 +167,26 @@ def faberIsRunning(emptyLooper, tdirWithPoolTxns, faberWallet, faberWallet.pendSyncRequests() prepared = faberWallet.preparePending() faber.client.submitReqs(*prepared) - emptyLooper.add(faber) - claimName, claimVersion = "Transcript", "1.2" - claimDef = { - "name": claimName, - "version": claimVersion, - "type": "CL", - "attr_names": ["student_name", "ssn", "degree", "year", "status"] - } - - cdSeqNo, iskSeqNo = addClaimDefAndIssuerKeys(emptyLooper, faber, claimDef) - faber._seqNos = { - (claimName, claimVersion): (cdSeqNo, iskSeqNo) - } - faber.initAvailableClaimList() + + runAgent(faber, emptyLooper) return faber, faberWallet @pytest.fixture(scope="module") def acmeAgent(tdirWithPoolTxns, acmeAgentPort, acmeWallet): - agent = runAcme(acmeWallet.name, acmeWallet, - basedirpath=tdirWithPoolTxns, - port=acmeAgentPort, - startRunning=False, bootstrap=False) - return agent + return createAcme(acmeWallet.name, acmeWallet, + basedirpath=tdirWithPoolTxns, + port=acmeAgentPort) @pytest.fixture(scope="module") def acmeAdded(nodeSet, - steward, - stewardWallet, - emptyLooper, - acmeAgentPort, - acmeAgent): + steward, + stewardWallet, + emptyLooper, + acmeAgentPort, + acmeAgent): attrib = createAgentAndAddEndpoint(emptyLooper, acmeAgent.wallet.defaultId, acmeAgent.wallet.getVerkey(), @@ -224,30 +203,17 @@ def acmeIsRunning(emptyLooper, tdirWithPoolTxns, acmeWallet, acmeAgent, acmeWallet.pendSyncRequests() prepared = acmeWallet.preparePending() acme.client.submitReqs(*prepared) - emptyLooper.add(acme) - claimName, claimVersion = "Job-Certificate", "0.2" - claimDef = { - "name": claimName, - "version": claimVersion, - "type": "CL", - "attr_names": ["first_name", "last_name", "employee_status", - "experience", "salary_bracket"] - } - - cdSeqNo, iskSeqNo = addClaimDefAndIssuerKeys(emptyLooper, acme, claimDef) - acme._seqNos = { - (claimName, claimVersion): (cdSeqNo, iskSeqNo) - } + + runAgent(acme, emptyLooper) + return acme, acmeWallet @pytest.fixture(scope="module") def thriftAgent(tdirWithPoolTxns, thriftAgentPort, thriftWallet): - agent = runThrift(thriftWallet.name, thriftWallet, - basedirpath=tdirWithPoolTxns, - port=thriftAgentPort, - startRunning=False, bootstrap=False) - return agent + return createThrift(thriftWallet.name, thriftWallet, + basedirpath=tdirWithPoolTxns, + port=thriftAgentPort) @pytest.fixture(scope="module") @@ -257,7 +223,9 @@ def thriftIsRunning(emptyLooper, tdirWithPoolTxns, thriftWallet, thriftWallet.pendSyncRequests() prepared = thriftWallet.preparePending() thrift.client.submitReqs(*prepared) - emptyLooper.add(thrift) + + runAgent(thrift, emptyLooper) + return thrift, thriftWallet @@ -320,12 +288,12 @@ def aliceFaberInvitationLoaded(aliceAgent, faberInvitation): @pytest.fixture(scope="module") def aliceFaberInvitationLinkSynced(aliceFaberInvitationLoaded, - aliceAgentConnected, - aliceAgent: WalletedAgent, - emptyLooper, - faberAdded - ): - agentInvitationLinkSynced(aliceAgent, aliceFaberInvitationLoaded.name, + aliceAgentConnected, + aliceAgent: WalletedAgent, + emptyLooper, + faberAdded): + agentInvitationLinkSynced(aliceAgent, + aliceFaberInvitationLoaded.name, emptyLooper) @@ -365,29 +333,33 @@ def aliceAcceptedAcme(acmeIsRunning, acmeNonceForAlice, acmeAdded, def checkAcceptInvitation(emptyLooper, nonce, - userAgent: WalletedAgent, - agentIsRunning, + inviteeAgent: WalletedAgent, + inviterAgentAndWallet, linkName): """ Assumes link identified by linkName is already created """ assert nonce - agent, awallet = agentIsRunning - a = agent # type: WalletedAgent - - userAgent.connectTo(linkName) - ensureAgentsConnected(emptyLooper, userAgent, agent) + inviterAgent, inviterWallet = inviterAgentAndWallet # type: WalletedAgent, Wallet - userAgent.acceptInvitation(linkName) + inviteeWallet = inviteeAgent.wallet + inviteeAgent.connectTo(linkName) + ensureAgentsConnected(emptyLooper, inviteeAgent, inviterAgent) - internalId = a.getInternalIdByInvitedNonce(nonce) + inviteeAgent.acceptInvitation(linkName) + inviteeAcceptanceId = inviteeWallet.getLink(linkName, + required=True).localIdentifier + internalId = inviterAgent.getInternalIdByInvitedNonce(nonce) def chk(): - link = a.wallet.getLinkByInternalId(internalId) + link = inviterWallet.getLinkByInternalId(internalId) assert link - linkAtUser = userAgent.wallet.getLinkInvitationByTarget(link.localIdentifier) - assert link.remoteIdentifier == linkAtUser.verkey - assert link.remoteEndPoint[1] == userAgent.endpoint.ha[1] + # if not link: + # raise RuntimeError("Link not found for internal ID {}". + # format(internalId)) + # TODO: Get link from invitee wallet to check. + assert link.remoteIdentifier == inviteeAcceptanceId + assert link.remoteEndPoint[1] == inviteeAgent.endpoint.ha[1] emptyLooper.run(eventually(chk)) @@ -418,8 +390,8 @@ def getInvitationFile(fileName): return os.path.join(sampleDir, fileName) -def agentInvitationLoaded(agent, invitaition): - link = agent.loadInvitationFile(invitaition) +def agentInvitationLoaded(agent, invitation): + link = agent.loadInvitationFile(invitation) assert link return link @@ -436,7 +408,7 @@ def cb(reply, err): done = True def checkDone(): - assert done + assert done, 'never got reply for agent link sync' agent.sync(linkName, cb) looper.run(eventually(checkDone)) diff --git a/sovrin/test/agent/faber.py b/sovrin/test/agent/faber.py index 403cbce..d66415a 100644 --- a/sovrin/test/agent/faber.py +++ b/sovrin/test/agent/faber.py @@ -1,32 +1,28 @@ import os -from anoncreds.protocol.cred_def_secret_key import CredDefSecretKey from plenum.common.log import getlogger from plenum.common.txn import NAME, VERSION -from anoncreds.protocol.types import AttribType, AttribDef -from sovrin.agent.agent import runAgent +from anoncreds.protocol.types import AttribType, AttribDef, ID, ClaimDefinitionKey +from sovrin.agent.agent import createAgent, runAgent from sovrin.agent.exception import NonceNotFound from sovrin.client.client import Client from sovrin.client.wallet.wallet import Wallet from sovrin.common.config_util import getConfig - from sovrin.test.agent.helper import buildFaberWallet from sovrin.test.agent.test_walleted_agent import TestWalletedAgent +from sovrin.test.conftest import primes from sovrin.test.helper import TestClient logger = getlogger() class FaberAgent(TestWalletedAgent): - credDefSecretKey = CredDefSecretKey(293672994294601538460023894424280657882248991230397936278278721070227017571960229217003029542172804429372056725385213277754094188540395813914384157706891192254644330822344382798277953427101186508616955910010980515685469918970002852483572038959508885430544201790234678752166995847136179984303153769450295059547, - 346129266351333939705152453226207841619953213173429444538411282110012597917194461301159547344552711191280095222396141806532237180979404522416636139654540172375588671099885266296364558380028106566373280517225387715617569246539059672383418036690030219091474419102674344117188434085686103371044898029209202469967) - def __init__(self, basedirpath: str, - client: Client=None, - wallet: Wallet=None, - port: int=None, + client: Client = None, + wallet: Wallet = None, + port: int = None, loop=None): if not basedirpath: config = getConfig() @@ -47,38 +43,43 @@ def __init__(self, "710b78be79f29fc81335abaa4ee1c5e8": 4 } + self._attrDef = AttribDef('faber', + [AttribType('student_name', encode=True), + AttribType('ssn', encode=True), + AttribType('degree', encode=True), + AttribType('year', encode=True), + AttribType('status', encode=True)]) + # maps internal ids to attributes - self._attributes = { - 1: { - "student_name": "Alice Garcia", - "ssn": "123-45-6789", - "degree": "Bachelor of Science, Marketing", - "year": "2015", - "status": "graduated" - }, - 2: { - "student_name": "Carol Atkinson", - "ssn": "783-41-2695", - "degree": "Bachelor of Science, Physics", - "year": "2012", - "status": "graduated" - }, - 3: { - "student_name": "Frank Jeffrey", - "ssn": "996-54-1211", - "degree": "Bachelor of Arts, History", - "year": "2013", - "status": "dropped" - }, - 4: { - "student_name": "Craig Richards", - "ssn": "151-44-5876", - "degree": "MBA, Finance", - "year": "2014", - "status": "graduated" - } + self._attrs = { + 1: self._attrDef.attribs( + student_name="Alice Garcia", + ssn="123-45-6789", + degree="Bachelor of Science, Marketing", + year="2015", + status="graduated"), + 2: self._attrDef.attribs( + student_name="Carol Atkinson", + ssn="783-41-2695", + degree="Bachelor of Science, Physics", + year="2012", + status="graduated"), + 3: self._attrDef.attribs( + student_name="Frank Jeffrey", + ssn="996-54-1211", + degree="Bachelor of Arts, History", + year="2013", + status="dropped"), + 4: self._attrDef.attribs( + student_name="Craig Richards", + ssn="151-44-5876", + degree="MBA, Finance", + year="2015", + status="graduated") } + self._claimDefKey = ClaimDefinitionKey("Transcript", "1.2", self.wallet.defaultId) + def getInternalIdByInvitedNonce(self, nonce): if nonce in self._invites: return self._invites[nonce] @@ -86,63 +87,49 @@ def getInternalIdByInvitedNonce(self, nonce): raise NonceNotFound def isClaimAvailable(self, link, claimName): - if claimName == "Transcript": - return True - else: - return False + return claimName == "Transcript" def getAvailableClaimList(self): return self.availableClaims - def postClaimVerif(self, claimName, link, frm): + async def postClaimVerif(self, claimName, link, frm): pass - def initAvailableClaimList(self): - acl = self.wallet.getAvailableClaimList() - logger.debug("Faber has {} claims: {}".format(len(acl), acl)) - for cd, ik in acl: - self.availableClaims.append({ - NAME: cd.name, - VERSION: cd.version, - "claimDefSeqNo": cd.seqNo - }) - - def addClaimDefsToWallet(self): - name, version = "Transcript", "1.2" - attrNames = ["student_name", "ssn", "degree", "year", "status"] - self.addCredDefAndIskIfNotFoundOnLedger(name, version, - origin=self.wallet.defaultId, - attrNames=attrNames, typ='CL', - credDefSecretKey= - self.credDefSecretKey, - clbk= - self.initAvailableClaimList) - - def getAttributes(self, internalId): - attrs = self._attributes.get(internalId) - - if not attrs: - raise RuntimeError('attributes for internal ID {} not found'. - format(internalId)) - - attribTypes = [] - for name in attrs: - attribTypes.append(AttribType(name, encode=True)) - attribsDef = AttribDef("Transcript", attribTypes) - attribs = attribsDef.attribs(**attrs) - return attribs - - def bootstrap(self): - self.addClaimDefsToWallet() - - -def runFaber(name=None, wallet=None, basedirpath=None, port=None, - startRunning=True, bootstrap=True): - - return runAgent(FaberAgent, name or "Faber College", - wallet or buildFaberWallet(), basedirpath, - port, startRunning, bootstrap, clientClass=TestClient) + async def initAvailableClaimList(self): + claimDef = await self.issuer.wallet.getClaimDef(ID(self._claimDefKey)) + self.availableClaims.append({ + NAME: claimDef.name, + VERSION: claimDef.version, + "claimDefSeqNo": claimDef.seqId + }) + + def _addAtrribute(self, claimDefKey, proverId, link): + attr = self._attrs[self.getInternalIdByInvitedNonce(proverId)] + self.issuer._attrRepo.addAttributes(claimDefKey=claimDefKey, + userId=proverId, + attributes=attr) + + async def addClaimDefsToWallet(self): + claimDef = await self.issuer.genClaimDef(self._claimDefKey.name, + self._claimDefKey.version, + self._attrDef.attribNames(), + 'CL') + claimDefId = ID(claimDefKey=claimDef.getKey(), claimDefId=claimDef.seqId) + p_prime, q_prime = primes["prime2"] + await self.issuer.genKeys(claimDefId, p_prime=p_prime, q_prime=q_prime) + await self.issuer.issueAccumulator(claimDefId=claimDefId, iA='110', L=5) + await self.initAvailableClaimList() + + async def bootstrap(self): + await self.addClaimDefsToWallet() + + +def createFaber(name=None, wallet=None, basedirpath=None, port=None): + return createAgent(FaberAgent, name or "Faber College", + wallet or buildFaberWallet(), + basedirpath, port, clientClass=TestClient) if __name__ == "__main__": - runFaber(port=5555) + faber = createFaber(port=5555) + runAgent(faber) diff --git a/sovrin/test/agent/helper.py b/sovrin/test/agent/helper.py index e618ec7..21c1b3f 100644 --- a/sovrin/test/agent/helper.py +++ b/sovrin/test/agent/helper.py @@ -1,10 +1,10 @@ import argparse - import sys from plenum.common.signer_simple import SimpleSigner from plenum.test.eventually import eventually from plenum.test.test_stack import checkRemoteExists, CONNECTED + from sovrin.client.wallet.wallet import Wallet diff --git a/sovrin/test/agent/test_accept_invitation.py b/sovrin/test/agent/test_accept_invitation.py index ccc5ae7..fe6cbba 100644 --- a/sovrin/test/agent/test_accept_invitation.py +++ b/sovrin/test/agent/test_accept_invitation.py @@ -1,6 +1,17 @@ +import logging + import pytest + from sovrin.test.agent.conftest import checkAcceptInvitation +concerningLogLevels = [logging.WARNING, + logging.ERROR, + logging.CRITICAL] + +# TODO need to solve the root cause of this warning, which is agents +# presuming an identifier is already created on startup +whitelist = ['discarding message.*GET_TXNS.*UnknownIdentifier'] + def testFaberCreateLink(faberLinkAdded): pass @@ -61,8 +72,8 @@ def testMultipleAcceptance(aliceAcceptedFaber, checkAcceptInvitation(emptyLooper, nonce=faberNonceForAlice, - userAgent=otherAgent, - agentIsRunning=faberIsRunning, linkName=link.name) + inviteeAgent=otherAgent, + inviterAgentAndWallet=faberIsRunning, linkName=link.name) assert len(faberAgent.wallet._links) == 2 diff --git a/sovrin/test/agent/test_anoncreds_agent.py b/sovrin/test/agent/test_anoncreds_agent.py new file mode 100644 index 0000000..1eb1bea --- /dev/null +++ b/sovrin/test/agent/test_anoncreds_agent.py @@ -0,0 +1,30 @@ +from plenum.test.eventually import eventually + +from anoncreds.protocol.types import ClaimDefinitionKey, ID + + +def testAnonCreds(aliceAgent, aliceAcceptedFaber, aliceAcceptedAcme, acmeAgent, emptyLooper): + # 1. request Claims from Faber + faberLink = aliceAgent.wallet.getLink('Faber College') + name, version, origin = faberLink.availableClaims[0] + claimDefKey = ClaimDefinitionKey(name, version, origin) + aliceAgent.sendReqClaim(faberLink, claimDefKey) + + # 2. check that claim is received from Faber + async def chkClaims(): + claim = await aliceAgent.prover.wallet.getClaims(ID(claimDefKey)) + assert claim.primaryClaim + + emptyLooper.run(eventually(chkClaims, timeout=20)) + + # 3. send claim proof to Acme + acmeLink, acmeClaimPrfReq = aliceAgent.wallet.getMatchingLinksWithClaimReq("Job-Application", "Acme Corp")[0] + aliceAgent.sendProof(acmeLink, acmeClaimPrfReq) + + # 4. check that claim proof is verified by Acme + def chkProof(): + internalId = acmeAgent.getInternalIdByInvitedNonce(acmeLink.invitationNonce) + link = acmeAgent.wallet.getLinkByInternalId(internalId) + assert "Job-Application" in link.verifiedClaimProofs + + emptyLooper.run(eventually(chkProof, timeout=20)) diff --git a/sovrin/test/agent/test_ping.py b/sovrin/test/agent/test_ping.py index 4db3f80..7e26dd5 100644 --- a/sovrin/test/agent/test_ping.py +++ b/sovrin/test/agent/test_ping.py @@ -1,11 +1,4 @@ -import pytest -from plenum.common.txn import TYPE, NONCE, IDENTIFIER - -from plenum.common.types import f from plenum.test.eventually import eventually -from sovrin.agent.msg_types import ACCEPT_INVITE, AVAIL_CLAIM_LIST - -from sovrin.test.agent.helper import ensureAgentsConnected def testPing(aliceAcceptedFaber, faberIsRunning, aliceAgent, emptyLooper): diff --git a/sovrin/test/agent/test_startup_shutdown.py b/sovrin/test/agent/test_startup_shutdown.py index 518361d..173f326 100644 --- a/sovrin/test/agent/test_startup_shutdown.py +++ b/sovrin/test/agent/test_startup_shutdown.py @@ -1,5 +1,6 @@ from plenum.common.startable import Status from pytest import fixture + from sovrin.agent.agent import Agent diff --git a/sovrin/test/agent/test_walleted_agent.py b/sovrin/test/agent/test_walleted_agent.py index 854ad67..52cbdc0 100644 --- a/sovrin/test/agent/test_walleted_agent.py +++ b/sovrin/test/agent/test_walleted_agent.py @@ -1,10 +1,10 @@ -from functools import partial - -from ledger.util import F from plenum.common.log import getlogger +from plenum.common.types import f from plenum.test.testable import Spyable + from sovrin.agent.agent import WalletedAgent -from sovrin.common.util import ensureReqCompleted +from sovrin.common.exceptions import LinkNotFound +from sovrin.common.txn import NONCE from sovrin.test.agent.helper import getAgentCmdLineParams logger = getlogger() @@ -13,50 +13,19 @@ @Spyable( methods=[WalletedAgent._handlePing, WalletedAgent._handlePong]) class TestWalletedAgent(WalletedAgent): + def getLinkForMsg(self, msg): + nonce = msg.get(NONCE) + identifier = msg.get(f.IDENTIFIER.nm) + link = None + for _, li in self.wallet._links.items(): + if li.invitationNonce == nonce and li.remoteIdentifier == identifier: + link = li + break + if link: + return link + else: + raise LinkNotFound + @staticmethod def getPassedArgs(): return getAgentCmdLineParams() - - def addCredDefAndIskIfNotFoundOnLedger(self, name, version, origin, - attrNames, typ, - credDefSecretKey=None, clbk=None): - claimDefKey = (name, version, origin) - - def postClaimDefWritten(reply, error, claimDef): - claimDefSeqNo = reply.get(F.seqNo.name) - logger.debug("Claim def written on ledger: {}".format(claimDef.key)) - self.wallet.createIssuerKey(claimDefSeqNo=claimDefSeqNo, - claimDef=claimDef, csk=credDefSecretKey) - req, = self.wallet.preparePending() - self.client.submitReqs(req) - chk = partial(self.wallet.isIssuerKeyComplete, - self.wallet.defaultId, claimDefSeqNo) - - # TODO: Refactor ASAP - def dummy(r, e): - logger.debug("Issuer key written on ledger") - if clbk: - logger.debug("Calling the callback") - clbk() - - self.loop.call_later(.2, ensureReqCompleted, self.loop, - req.key, self.client, - dummy, None, None, - chk) - - claimDef = self.wallet.getClaimDef(key=claimDefKey) - chk = partial(self.wallet.isClaimDefComplete, claimDefKey) - if chk(): - # # Assuming if credential definition is present on ledger the - # # issuer key would be - self.wallet.getIssuerPublicKey(key=(origin, claimDef.seqNo)) - clbk() - else: - claimDef = self.wallet.createClaimDef(name=name, version=version, - attrNames=attrNames, typ=typ) - req, = self.wallet.preparePending() - self.client.submitReqs(req) - self.loop.call_later(.2, ensureReqCompleted, self.loop, - req.key, self.client, - postClaimDefWritten, (claimDef,), None, - chk) diff --git a/sovrin/test/agent/thrift.py b/sovrin/test/agent/thrift.py index 43ec706..27da166 100644 --- a/sovrin/test/agent/thrift.py +++ b/sovrin/test/agent/thrift.py @@ -2,13 +2,12 @@ from plenum.common.log import getlogger -from sovrin.agent.agent import runAgent +from sovrin.agent.agent import createAgent, runAgent from sovrin.agent.constants import EVENT_NOTIFY_MSG from sovrin.agent.exception import NonceNotFound from sovrin.client.client import Client from sovrin.client.wallet.wallet import Wallet from sovrin.common.config_util import getConfig - from sovrin.test.agent.helper import buildThriftWallet from sovrin.test.agent.test_walleted_agent import TestWalletedAgent from sovrin.test.helper import TestClient @@ -19,9 +18,9 @@ class ThriftAgent(TestWalletedAgent): def __init__(self, basedirpath: str, - client: Client=None, - wallet: Wallet=None, - port: int=None, + client: Client = None, + wallet: Wallet = None, + port: int = None, loop=None): if not basedirpath: config = getConfig() @@ -32,8 +31,6 @@ def __init__(self, super().__init__('Thrift Bank', basedirpath, client, wallet, portParam or port, loop=loop) - self._attributes = {} - # maps invitation nonces to internal ids self._invites = { "77fbf9dc8c8e6acde33de98c6d747b28c": 1 @@ -51,7 +48,10 @@ def isClaimAvailable(self, link, claimName): def getAvailableClaimList(self): return [] - def postClaimVerif(self, claimName, link, frm): + def _addAtrribute(self, claimDefKey, proverId, link): + pass + + async def postClaimVerif(self, claimName, link, frm): if claimName == "Loan-Application-Basic": self.notifyToRemoteCaller(EVENT_NOTIFY_MSG, " Loan eligibility criteria satisfied," @@ -59,23 +59,16 @@ def postClaimVerif(self, claimName, link, frm): "'Loan-Application-KYC'\n", self.wallet.defaultId, frm) - def addClaimDefsToWallet(self): + async def bootstrap(self): pass - def getAttributes(self, nonce): - pass - - def bootstrap(self): - self.addClaimDefsToWallet() - - -def runThrift(name=None, wallet=None, basedirpath=None, port=None, - startRunning=True, bootstrap=True): - return runAgent(ThriftAgent, name or "Thrift Bank", - wallet or buildThriftWallet(), basedirpath, - port, startRunning, bootstrap, clientClass=TestClient) +def createThrift(name=None, wallet=None, basedirpath=None, port=None): + return createAgent(ThriftAgent, name or "Thrift Bank", + wallet or buildThriftWallet(), + basedirpath, port, clientClass=TestClient) if __name__ == "__main__": - runThrift(port=7777) + thrift = createThrift(port=7777) + runAgent(thrift) diff --git a/sovrin/test/anon_creds/anon_creds_demo.py b/sovrin/test/anon_creds/anon_creds_demo.py deleted file mode 100644 index 653bc47..0000000 --- a/sovrin/test/anon_creds/anon_creds_demo.py +++ /dev/null @@ -1,245 +0,0 @@ -import logging -import os -import pprint -import shutil -import tempfile - -# The following setup of logging needs to happen before everything else -from plenum.common.log import getlogger -from sovrin.anon_creds.cred_def import CredDef -from sovrin.anon_creds.issuer import InMemoryAttrRepo -from sovrin.anon_creds.proof_builder import ProofBuilder -from sovrin.anon_creds.verifier import Verifier - -from plenum.common.txn_util import createGenesisTxnFile - -logging.root.handlers = [] -# setupLogging(DISPLAY_LOG_LEVEL, -# Console.Wordage.mute) -logger = getlogger("anon_creds_demo") - -from plenum.common.looper import Looper -from plenum.common.txn import DATA, ORIGIN -from plenum.common.txn import TXN_TYPE -from plenum.test.helper import genHa, ensureElectionsDone, \ - checkNodesConnected, genNodeReg - -from sovrin.test.helper import genTestClient, submitAndCheck, createNym, \ - TestNodeSet, _newWallet, makePendingTxnsRequest -from sovrin.common.txn import CRED_DEF, SPONSOR, getTxnOrderedFields -from sovrin.test.conftest import genesisTxns -from sovrin.common.util import getCredDefTxnData -from sovrin.common.config_util import getConfig -import sovrin.anon_creds.issuer as IssuerModule -import sovrin.anon_creds.prover as ProverModule -import sovrin.anon_creds.verifier as VerifierModule - - -config = getConfig() - - -rawAttributes = { - "first_name": "John", - "last_name": "Doe", - "birth_date": "1970-01-01", - "expire_date": "2300-01-01", - "undergrad": "True", - "postgrad": "False" -} - -attrNames = tuple(rawAttributes.keys()) - -attrTypes = [IssuerModule.AttribType(name, encode=True) for name in attrNames] -attrDefs = IssuerModule.AttribDef('BYU', attrTypes) -attributes = attrDefs.attribs(**rawAttributes) - -dataDir = '/tmp/data' -if os.path.exists(dataDir): - shutil.rmtree(dataDir) -tdir = tempfile.TemporaryDirectory().name - -stewardWallet = _newWallet() -sponsorWallet = _newWallet() -issuerWallet = _newWallet() -proverWallet = _newWallet() -verifierWallet = _newWallet() - -createGenesisTxnFile(genesisTxns(stewardWallet), tdir, - config.domainTransactionsFile, - getTxnOrderedFields()) - -nodes = TestNodeSet(nodeReg=genNodeReg(count=4), tmpdir=tdir, - primaryDecider=None) - - -def whitelistClient(nodes, *clientNames): - for node in nodes: - for nm in clientNames: - node.whitelistClient(nm) - -looper = Looper(nodes, autoStart=True) -for node in nodes: - node.startKeySharing() - node.start(looper) - # node.addGenesisTxns(genesisTxns(stewardSigner)) - -looper.run(checkNodesConnected(nodes)) -ensureElectionsDone(looper=looper, nodes=nodes, retryWait=1, timeout=30) - -steward, _ = genTestClient(nodes, tmpdir=tdir) -# whitelistClient(nodes, steward.name) -steward.registerObserver(stewardWallet.handleIncomingReply) -looper.add(steward) -looper.run(steward.ensureConnectedToNodes()) -makePendingTxnsRequest(steward, stewardWallet) - - -createNym(looper, sponsorWallet.defaultId, steward, stewardWallet, SPONSOR) - -sponsor, _ = genTestClient(nodes, tmpdir=tdir) -sponsor.registerObserver(sponsorWallet.handleIncomingReply) -# whitelistClient(nodes, sponsor.name) -looper.add(sponsor) -looper.run(sponsor.ensureConnectedToNodes()) -makePendingTxnsRequest(sponsor, sponsorWallet) - -iNym = issuerWallet.defaultId -pNym = proverWallet.defaultId -vNym = verifierWallet.defaultId - - -for nym in (iNym, pNym, vNym): - createNym(looper, nym, sponsor, sponsorWallet) - -issuerHA = genHa() -proverHA = genHa() -verifierHA = genHa() - - -def runAnonCredFlow(): - # 3 Sovrin clients acting as Issuer, Signer and Verifier - issuerC, _ = genTestClient(nodes, tmpdir=tdir, peerHA=genHa()) - proverC, _ = genTestClient(nodes, tmpdir=tdir, peerHA=genHa()) - verifierC, _ = genTestClient(nodes, tmpdir=tdir, peerHA=genHa()) - - looper.add(issuerC) - looper.add(proverC) - looper.add(verifierC) - looper.run(issuerC.ensureConnectedToNodes(), - proverC.ensureConnectedToNodes(), - verifierC.ensureConnectedToNodes()) - # Adding signers - # issuer.signers[issuerWallet.defaultId] = issuerSigner - logger.display("Key pair for Issuer created \n" - "Public key is {} \n" - "Private key is stored on disk\n".format(issuerWallet.defaultId)) - # prover.signers[proverWallet.defaultId] = proverSigner - input() - logger.display("Key pair for Prover created \n" - "Public key is {} \n" - "Private key is stored on disk\n".format(proverWallet.defaultId)) - # verifier.signers[verifierWallet.defaultId] = verifierSigner - input() - logger.display("Key pair for Verifier created \n" - "Public key is {} \n" - "Private key is stored on disk\n".format( - verifierWallet.defaultId)) - - # Issuer's attribute repository - attrRepo = InMemoryAttrRepo() - attrRepo.attributes = {proverWallet.defaultId: attributes} - # issuer.attributeRepo = attrRepo - name1 = "Qualifications" - version1 = "1.0" - ip = issuerC.peerHA[0] - port = issuerC.peerHA[1] - issuerId = issuerWallet.defaultId - proverId = proverWallet.defaultId - verifierId = verifierWallet.defaultId - interactionId = 'LOGIN-1' - - issuer = IssuerModule.Issuer(issuerId, attrRepo) - # Issuer publishes credential definition to Sovrin ledger - credDef = issuer.addNewCredDef(attrNames, name1, version1, ip=ip, port=port) - # issuer.credentialDefinitions = {(name1, version1): credDef} - input() - logger.display("Issuer: Creating version {} of credential definition" - " for {}".format(version1, name1)) - print("Credential definition: ") - pprint.pprint(credDef.get()) # Pretty-printing the big object. - input() - op = {ORIGIN: issuerWallet.defaultId, TXN_TYPE: CRED_DEF, DATA: - getCredDefTxnData(credDef)} - logger.display("Issuer: Writing credential definition to " - "Sovrin Ledger...") - submitAndCheck(looper, issuer, op, identifier=issuerWallet.defaultId) - - # Prover requests Issuer for credential (out of band) - input() - logger.display("Prover: Requested credential from Issuer") - # Issuer issues a credential for prover - input() - logger.display("Issuer: Creating credential for " - "{}".format(proverWallet.defaultId)) - prover = ProverModule.Prover(proverId) - - encodedAttributes = {issuerId: CredDef.getEncodedAttrs(attributes)} - pk = { - issuerId: prover.getPk(credDef) - } - proofBuilder = ProofBuilder(pk) - prover.proofBuilders[proofBuilder.id] = proofBuilder - cred = issuer.createCred(proverId, name1, version1, proofBuilder.U[issuerId]) - input() - logger.display("Prover: Received credential from " - "{}".format(issuerWallet.defaultId)) - - # Prover intends to prove certain attributes to a Verifier - # Verifier issues a nonce - input() - logger.display("Prover: Requesting Nonce from verifier…") - verifier = VerifierModule.Verifier(verifierId) - logger.display("Verifier: Nonce received from prover" - " {}".format(proverId)) - nonce = verifier.generateNonce(interactionId) - input() - logger.display("Verifier: Nonce sent.") - input() - logger.display("Prover: Nonce received") - prover.proofBuilders[proofBuilder.id]['nonce'] = nonce - - # Prover discovers Issuer's credential definition - prover.credentialDefinitions = {(issuerId, attrNames): credDef} - revealedAttrs = ["undergrad"] - input() - logger.display("Prover: Preparing proof for attributes: " - "{}".format(revealedAttrs)) - proofBuilder.setParams(encodedAttributes, revealedAttrs, nonce) - prf = proofBuilder.prepareProof() - logger.display("Prover: Proof prepared.") - logger.display("Prover: Proof submitted") - input() - logger.display("Verifier: Proof received.") - input() - logger.display("Verifier: Looking up Credential Definition" - " on Sovrin Ledger...") - prover.proofs[proofBuilder.id] = proofBuilder - - # Verifier fetches the credential definition from ledger - verifier.credentialDefinitions = { - (issuerId, name1, version1): credDef - } - # Verifier verifies proof - logger.display("Verifier: Verifying proof...") - verified = Verifier.verifyProof(pk, prf, nonce, - encodedAttributes, - revealedAttrs) - input() - logger.display("Verifier: Proof verified.") - assert verified - input() - logger.display("Prover: Proof accepted.") - - -if __name__ == "__main__": - runAnonCredFlow() diff --git a/sovrin/test/anon_creds/conftest.py b/sovrin/test/anon_creds/conftest.py index 6f87b93..8c8eda1 100644 --- a/sovrin/test/anon_creds/conftest.py +++ b/sovrin/test/anon_creds/conftest.py @@ -1,161 +1,9 @@ -import uuid +from anoncreds.protocol.types import AttribType, AttribDef -import pytest -import sovrin.anon_creds.cred_def as CredDefModule -from plenum.common.port_dispenser import genHa +GVT = AttribDef('gvt', + [AttribType('name', encode=True), + AttribType('age', encode=False), + AttribType('height', encode=False), + AttribType('sex', encode=True)]) -from anoncreds.protocol.cred_def_secret_key import CredDefSecretKey -from anoncreds.protocol.issuer_secret_key import IssuerSecretKey -from anoncreds.protocol.types import SerFmt -from plenum.common.txn import NAME, VERSION, TYPE, IP, PORT, KEYS -from plenum.common.util import randomString -from plenum.test.eventually import eventually -from sovrin.client.wallet.claim_def import ClaimDef, IssuerPubKey -from sovrin.common.config_util import getConfig -from sovrin.test.helper import createNym, _newWallet -# noinspection PyUnresolvedReferences -from anoncreds.test.conftest import staticPrimes - -# TODO Make a fixture for creating a client with a anon-creds features -# enabled. - -config = getConfig() - - -@pytest.fixture(scope="module") -def issuerWallet(): - return _newWallet() - - -@pytest.fixture(scope="module") -def proverWallet(): - return _newWallet() - - -@pytest.fixture(scope="module") -def verifierWallet(): - return _newWallet() - - -@pytest.fixture(scope="module") -def issuerHA(): - return genHa() - - -@pytest.fixture(scope="module") -def proverHA(): - return genHa() - - -@pytest.fixture(scope="module") -def verifierHA(): - return genHa() - - -@pytest.fixture(scope="module") -def proverAttributeNames(): - return sorted(['name', 'age', 'sex', 'country']) - - -@pytest.fixture(scope="module") -def proverAttributes(): - return {'name': 'Mario', 'age': '25', 'sex': 'Male', 'country': 'Italy'} - - -@pytest.fixture(scope="module") -def addedIPV(looper, nodeSet, addedSponsor, sponsor, sponsorWallet, - issuerWallet, proverWallet, verifierWallet, issuerHA, proverHA, - verifierHA): - """ - Creating nyms for issuer, prover and verifier on Sovrin. - """ - iNym = issuerWallet.defaultId - pNym = proverWallet.defaultId - vNym = verifierWallet.defaultId - - for nym in (iNym, pNym, vNym): - createNym(looper, nym, sponsor, sponsorWallet) - - -@pytest.fixture(scope="module") -def attrNames(): - return ["first_name", "last_name", "birth_date", "expire_date", - "undergrad", "postgrad"] - - -@pytest.fixture(scope="module") -def claimDef(attrNames): - return CredDefModule.CredDef(str(uuid.uuid4()), attrNames, name='name1', - version='version1') - - -@pytest.fixture(scope="module") -def claimDefSecretKeyAdded(nodeSet, steward, addedSponsor, sponsor, - sponsorWallet, looper, tdir, - staticPrimes): - csk = CredDefSecretKey(*staticPrimes.get("prime1")) - return csk - - -@pytest.fixture(scope="module") -def claimDefinitionAdded(nodeSet, steward, addedSponsor, sponsor, - sponsorWallet, looper, tdir, attrNames, - claimDef, claimDefSecretKeyAdded): - old = sponsorWallet.pendingCount - data = claimDef.get(serFmt=SerFmt.base58) - claimDef = ClaimDef(seqNo=None, - attrNames=attrNames, - name=data[NAME], - version=data[VERSION], - origin=sponsorWallet.defaultId, - typ=data[TYPE]) - pending = sponsorWallet.addClaimDef(claimDef) - assert pending == old + 1 - reqs = sponsorWallet.preparePending() - sponsor.submitReqs(*reqs) - - key = claimDef.key - - def chk(): - assert sponsorWallet.getClaimDef(key).seqNo is not None - - looper.run(eventually(chk, retryWait=1, timeout=30)) - return sponsorWallet.getClaimDef(key).seqNo - - -@pytest.fixture(scope="module") -def issuerSecretKeyAdded(nodeSet, steward, addedSponsor, sponsor, - sponsorWallet, looper, tdir, - staticPrimes, claimDefSecretKeyAdded, - claimDefinitionAdded): - csk = claimDefSecretKeyAdded - cd = sponsorWallet.getClaimDef(seqNo=claimDefinitionAdded) - # This uid would be updated with the sequence number of the transaction - # which writes the public key on Sovrin - isk = IssuerSecretKey(cd, csk, uid=str(uuid.uuid4())) - # TODO: Need to serialize it and then deserialize while doing get - return sponsorWallet.addIssuerSecretKey(isk) - - -@pytest.fixture(scope="module") -def issuerPublicKeysAdded(nodeSet, steward, addedSponsor, sponsor, - sponsorWallet, looper, tdir, - staticPrimes, claimDefinitionAdded, - issuerSecretKeyAdded): - isk = sponsorWallet.getIssuerSecretKey(issuerSecretKeyAdded) - # TODO refactor IssuerPubKey to just take an IssuerKey as a constructor param - ipk = IssuerPubKey(N=isk.PK.N, R=isk.PK.R, S=isk.PK.S, Z=isk.PK.Z, - claimDefSeqNo=claimDefinitionAdded, - secretKeyUid=isk.pubkey.uid, origin=sponsorWallet.defaultId) - sponsorWallet.addIssuerPublicKey(ipk) - reqs = sponsorWallet.preparePending() - sponsor.submitReqs(*reqs) - - key = (sponsorWallet.defaultId, claimDefinitionAdded) - - def chk(): - assert sponsorWallet.getIssuerPublicKey(key).seqNo is not None - - looper.run(eventually(chk, retryWait=1, timeout=30)) - return sponsorWallet.getIssuerPublicKey(key).seqNo diff --git a/sovrin/test/anon_creds/test_anon_cred_plugin.py b/sovrin/test/anon_creds/test_anon_cred_plugin.py deleted file mode 100644 index 60da7d3..0000000 --- a/sovrin/test/anon_creds/test_anon_cred_plugin.py +++ /dev/null @@ -1,10 +0,0 @@ -import pytest - - -@pytest.mark.skipif(True, reason="Client no longer has reference to Isuuer, " - "Prover and Verifier. Maybe we check whether " - "classes like CredDef have concrete methods " - "or abstract methods. But if that enough?") -def testAnonCredPlugin(steward): - assert steward.id is not None - assert steward.attributeRepo is None diff --git a/sovrin/test/anon_creds/test_anon_cred_wallet.py b/sovrin/test/anon_creds/test_anon_cred_wallet.py deleted file mode 100644 index e4cea11..0000000 --- a/sovrin/test/anon_creds/test_anon_cred_wallet.py +++ /dev/null @@ -1,26 +0,0 @@ -import pytest -import sovrin.anon_creds.cred_def as cred_def -import sovrin.anon_creds.issuer as issuer -from anoncreds.protocol.cred_def_secret_key import CredDefSecretKey -from sovrin.client.wallet.wallet import Wallet -# from sovrin.client.wallet.cred_def import CredDefSk, CredDefKey - - -# TODO: Confirm and update/remove -@pytest.mark.skipif(True, reason="What is being tested here. CredDefSk " - "and CredDef do not have a one to one relation") -def testCredDefSecretKey(tdir, staticPrimes): - GVT = issuer.AttribDef('gvt', - [issuer.AttribType('name', encode=True), - issuer.AttribType('age', encode=False), - issuer.AttribType('sex', encode=True)]) - sprimes = staticPrimes["prime1"] - # sk = CredDefSecretKey(*sprimes) - sk = CredDefSecretKey(*sprimes) - cd = cred_def.CredDef(322324, GVT.attribNames()) - - wallet = Wallet("testWallet") - # cdsk = CredDefSk(name, version, serializedSk) - wallet.addClaimDefSk(str(sk)) - stored = wallet.getClaimDefSk(CredDefKey(name, version)) - assert serializedSk == stored.secretKey diff --git a/sovrin/test/anon_creds/test_anon_creds.py b/sovrin/test/anon_creds/test_anon_creds.py deleted file mode 100644 index 5c5f866..0000000 --- a/sovrin/test/anon_creds/test_anon_creds.py +++ /dev/null @@ -1,242 +0,0 @@ -# The following setup of logging needs to happen before everything else -import pytest - -from plenum.common.log import DISPLAY_LOG_LEVEL, setupLogging, \ - DemoHandler, getlogger -from plenum.common.port_dispenser import genHa -from plenum.test.eventually import eventually -from sovrin.client.wallet.claim_def import ClaimDef - -from anoncreds.protocol.cred_def_secret_key import CredDefSecretKey -from anoncreds.test.conftest import staticPrimes - - -def out(logger, record, extra_cli_value=None): - """ - Callback so that this cli can manage colors - - :param record: a log record served up from a custom handler - :param extra_cli_value: the "cli" value in the extra dictionary - :return: - """ - logger.display(record.msg) - -import logging -import pprint - -from ioflo.aid.consoling import Console -from functools import partial - -import sovrin.anon_creds.issuer as IssuerModule -import sovrin.anon_creds.prover as ProverModule -import sovrin.anon_creds.proof_builder as ProofBuilderModule -import sovrin.anon_creds.verifier as VerifierModule - -from plenum.common.txn import DATA, TXN_TYPE -from sovrin.common.txn import CRED_DEF -from sovrin.common.util import getCredDefTxnData -from sovrin.test.helper import submitAndCheck, makePendingTxnsRequest - -from sovrin.client.wallet.wallet import Wallet - - -# TODO: This test checks for things already checked in `test_anon_cred_cli.py`. -# It fails. Updated it a bit. Will come back to it after taking care of more -# pressing issues. - -@pytest.mark.skipif(True, reason="Refactoring incomplete") -def testAnonCredFlow(nodeSet, - looper, - tdir, - issuerWallet: Wallet, - proverWallet: Wallet, - verifierWallet, - addedIPV): - - # Don't move the following import outside of this method, otherwise that - # client class doesn't gets reloaded and it doesn't get updated with the - # correct plugin class/methods and it gives an error. - # (for permanent solution bug is created: #130181205) - from sovrin.test.helper import genTestClient - - BYU = IssuerModule.AttribDef('BYU', - [IssuerModule.AttribType("first_name", encode=True), - IssuerModule.AttribType("last_name", encode=True), - IssuerModule.AttribType("birth_date", encode=True), - IssuerModule.AttribType("expire_date", encode=True), - IssuerModule.AttribType("undergrad", encode=True), - IssuerModule.AttribType("postgrad", encode=True)] - ) - - setupLogging(DISPLAY_LOG_LEVEL, - Console.Wordage.mute) - logger = getlogger("test_anon_creds") - logging.root.addHandler(DemoHandler(partial(out, logger))) - logging.root.handlers = [] - - attributes = BYU.attribs( - first_name="John", - last_name="Doe", - birth_date="1970-01-01", - expire_date="2300-01-01", - undergrad="True", - postgrad="False" - ) - - attrNames = tuple(attributes.keys()) - # 3 Sovrin clients acting as Issuer, Signer and Verifier - issuerC, _ = genTestClient(nodeSet, tmpdir=tdir, peerHA=genHa(), - usePoolLedger=True) - proverC, _ = genTestClient(nodeSet, tmpdir=tdir, peerHA=genHa(), - usePoolLedger=True) - verifierC, _ = genTestClient(nodeSet, tmpdir=tdir, peerHA=genHa(), - usePoolLedger=True) - - looper.add(issuerC) - looper.add(proverC) - looper.add(verifierC) - looper.run(issuerC.ensureConnectedToNodes(), - proverC.ensureConnectedToNodes(), - verifierC.ensureConnectedToNodes()) - makePendingTxnsRequest(issuerC, issuerWallet) - makePendingTxnsRequest(proverC, proverWallet) - makePendingTxnsRequest(verifierC, verifierWallet) - # Adding signers - # issuer.signers[issuerSigner.identifier] = issuerSigner - logger.display("Key pair for Issuer created \n" - "Public key is {} \n" - "Private key is stored on disk\n". - format(issuerWallet.defaultId)) - # prover.signers[proverSigner.identifier] = proverSigner - logger.display("Key pair for Prover created \n" - "Public key is {} \n" - "Private key is stored on disk\n". - format(proverWallet.defaultId)) - # verifier.signers[verifierSigner.identifier] = verifierSigner - logger.display("Key pair for Verifier created \n" - "Public key is {} \n" - "Private key is stored on disk\n". - format(verifierWallet.defaultId)) - - # TODO BYU.name is used here instead of issuerSigner.identifier due to - # tight coupling in Attribs.encoded() - issuerId = BYU.name - proverId = proverWallet.defaultId - # Issuer's attribute repository - attrRepo = IssuerModule.InMemoryAttrRepo() - attrRepo.attributes = {proverId: attributes} - - name1 = "Qualifications" - version1 = "1.0" - ip = issuerC.peerHA[0] - port = issuerC.peerHA[1] - interactionId = 'LOGIN-1' - - # This is the issuer entity - issuer = IssuerModule.Issuer(issuerId, attrRepo) - # issuer.attributeRepo = attrRepo - # Issuer publishes credential definition to Sovrin ledger - - csk = CredDefSecretKey(*staticPrimes().get("prime1")) - cskId = issuerWallet.addClaimDefSk(str(csk)) - credDef = ClaimDef(seqNo=None, - attrNames=attrNames, - name=name1, - version=version1, - origin=issuerWallet.defaultId, - secretKey=cskId) - # credDef = issuer.addNewCredDef(attrNames, name1, version1, - # p_prime="prime1", q_prime="prime1", ip=ip, - # port=port) - # issuer.credentialDefinitions = {(name1, version1): credDef} - logger.display("Issuer: Creating version {} of credential definition" - " for {}".format(version1, name1)) - print("Credential definition: ") - pprint.pprint(credDef.get()) # Pretty-printing the big object. - pending = issuerWallet.addClaimDef(credDef) - reqs = issuerWallet.preparePending() - - logger.display("Issuer: Writing credential definition to " - "Sovrin Ledger...") - issuerC.submitReqs(*reqs) - - def chk(): - assert issuerWallet.getClaimDef((name1, - version1, - issuerWallet.defaultId)).seqNo is not None - - looper.run(eventually(chk, retryWait=.1, timeout=30)) - - # submitAndCheck(looper, issuerC, issuerWallet, op) - - - # Prover requests Issuer for credential (out of band) - logger.display("Prover: Requested credential from Issuer") - # Issuer issues a credential for prover - logger.display("Issuer: Creating credential for " - "{}".format(proverWallet.defaultId)) - - encodedAttributes = attributes.encoded() - revealedAttrs = ["undergrad"] - - prover = ProverModule.Prover(proverId) - pk = { - issuerId: prover.getPk(credDef) - } - proofBuilder = ProofBuilderModule.ProofBuilder(pk) - proofId = proofBuilder.id - prover.proofBuilders[proofId] = proofBuilder - cred = issuer.createCred(proverId, name1, version1, - proofBuilder.U[issuerId]) - logger.display("Prover: Received credential from " - "{}".format(issuerWallet.defaultId)) - - # Prover intends to prove certain attributes to a Verifier - # Verifier issues a nonce - logger.display("Prover: Requesting Nonce from verifier…") - logger.display("Verifier: Nonce received from prover" - " {}".format(proverId)) - - verifierId = verifierWallet.defaultId - verifier = VerifierModule.Verifier(verifierId) - nonce = verifier.generateNonce(interactionId) - logger.display("Verifier: Nonce sent.") - logger.display("Prover: Nonce received") - - presentationToken = { - issuerId: ( - cred[0], cred[1], - proofBuilder.vprime[issuerId] + cred[2]) - } - # Prover discovers Issuer's credential definition - logger.display("Prover: Preparing proof for attributes: " - "{}".format(revealedAttrs)) - proofBuilder.setParams(presentationToken, - revealedAttrs, nonce) - prf = ProofBuilderModule.ProofBuilder.prepareProof( - credDefPks=proofBuilder.issuerPks, - masterSecret=proofBuilder.masterSecret, - creds=presentationToken, - encodedAttrs=encodedAttributes, - revealedAttrs=revealedAttrs, - nonce=nonce) - logger.display("Prover: Proof prepared.") - logger.display("Prover: Proof submitted") - logger.display("Verifier: Proof received.") - logger.display("Verifier: Looking up Credential Definition" - " on Sovrin Ledger...") - - # Verifier fetches the credential definition from ledger - verifier.credentialDefinitions = { - (issuerId, name1, version1): credDef - } - verified = VerifierModule.Verifier.verifyProof(pk, prf, nonce, - attributes.encoded(), - revealedAttrs) - # Verifier verifies proof - logger.display("Verifier: Verifying proof...") - logger.display("Verifier: Proof verified.") - assert verified - logger.display("Prover: Proof accepted.") - - # TODO: Reset the log level back to original. diff --git a/sovrin/test/anon_creds/test_anoncreds_usage.py b/sovrin/test/anon_creds/test_anoncreds_usage.py new file mode 100644 index 0000000..b3170a7 --- /dev/null +++ b/sovrin/test/anon_creds/test_anoncreds_usage.py @@ -0,0 +1,63 @@ +import pytest + +from anoncreds.protocol.repo.attributes_repo import AttributeRepoInMemory +from anoncreds.protocol.types import ID, ProofInput, PredicateGE +from sovrin.anon_creds.sovrin_issuer import SovrinIssuer +from sovrin.anon_creds.sovrin_prover import SovrinProver +from sovrin.anon_creds.sovrin_verifier import SovrinVerifier +from sovrin.test.anon_creds.conftest import GVT + + +@pytest.fixture(scope="module") +def attrRepo(): + return AttributeRepoInMemory() + + +@pytest.fixture(scope="module") +def issuer(steward, stewardWallet, attrRepo): + return SovrinIssuer(steward, stewardWallet, attrRepo) + + +@pytest.fixture(scope="module") +def prover(userClientA, userWalletA): + return SovrinProver(userClientA, userWalletA) + + +@pytest.fixture(scope="module") +def verifier(userClientB, userWalletB): + return SovrinVerifier(userClientB, userWalletB) + + +def testAnonCredsPrimaryOnly(issuer, prover, verifier, attrRepo, primes1, looper): + async def doTestAnonCredsPrimaryOnly(): + # 1. Create a Claim Def + claimDef = await issuer.genClaimDef('GVT', '1.0', GVT.attribNames()) + claimDefId = ID(claimDefKey=claimDef.getKey(), + claimDefId=claimDef.seqId) + + # 2. Create keys for the Claim Def + await issuer.genKeys(claimDefId, **primes1) + + # 3. Issue accumulator + await issuer.issueAccumulator(claimDefId=claimDefId, iA='110', L=5) + + # 4. set attributes for user1 + attrs = GVT.attribs(name='Alex', age=28, height=175, sex='male') + proverId = str(prover.proverId) + attrRepo.addAttributes(claimDef.getKey(), proverId, attrs) + + # 5. request Claims + claimsReq = await prover.createClaimRequest(claimDefId, proverId, False) + claims = await issuer.issueClaim(claimDefId, claimsReq) + await prover.processClaim(claimDefId, claims) + + # 6. proof Claims + proofInput = ProofInput( + ['name'], + [PredicateGE('age', 18)]) + + nonce = verifier.generateNonce() + proof, revealedAttrs = await prover.presentProof(proofInput, nonce) + assert await verifier.verify(proofInput, proof, revealedAttrs, nonce) + + looper.run(doTestAnonCredsPrimaryOnly) diff --git a/sovrin/test/anon_creds/test_credentials.py b/sovrin/test/anon_creds/test_credentials.py deleted file mode 100644 index 891b528..0000000 --- a/sovrin/test/anon_creds/test_credentials.py +++ /dev/null @@ -1,84 +0,0 @@ -import json - -import pytest -from ledger.util import F - -from anoncreds.protocol.types import SerFmt -from plenum.common.txn import NAME, VERSION, DATA -from plenum.common.util import getMaxFailures -from plenum.test.eventually import eventually -from plenum.test.helper import checkSufficientRepliesRecvd -from sovrin.common.txn import ATTR_NAMES - - -@pytest.fixture(scope="module") -def curiousClient(userWalletA, nodeSet, looper, tdir): - from sovrin.test.helper import genTestClient - client, _ = genTestClient(nodeSet, tmpdir=tdir, usePoolLedger=True) - client.registerObserver(userWalletA.handleIncomingReply) - looper.add(client) - looper.run(client.ensureConnectedToNodes()) - return client - - -def testIssuerWritesCredDef(claimDefinitionAdded): - """ - A credential definition is added - """ - pass - - -def testIssuerWritesPublicKey(issuerPublicKeysAdded): - """ - An issuer key is added - """ - pass - - -def testProverGetsCredDef(claimDefinitionAdded, userWalletA, tdir, - nodeSet, looper, sponsorWallet, claimDef, curiousClient): - """ - A credential definition is received - """ - - # Don't move below import outside of this method - # else that client class doesn't gets reloaded - # and hence it doesn't get updated with correct plugin class/methods - # and it gives error (for permanent solution bug is created: #130181205). - - definition = claimDef.get(serFmt=SerFmt.base58) - credDefKey = (definition[NAME], definition[VERSION], - sponsorWallet.defaultId) - req = userWalletA.requestClaimDef(credDefKey, userWalletA.defaultId) - curiousClient.submitReqs(req) - f = getMaxFailures(len(nodeSet)) - looper.run(eventually(checkSufficientRepliesRecvd, curiousClient.inBox, - req.reqId, f, - retryWait=1, timeout=5)) - reply, status = curiousClient.getReply(*req.key) - assert status == "CONFIRMED" - recvdCredDef = json.loads(reply[DATA]) - assert recvdCredDef[NAME] == definition[NAME] - assert recvdCredDef[VERSION] == definition[VERSION] - assert recvdCredDef[ATTR_NAMES].split(",") == definition[ATTR_NAMES] - claimDef = userWalletA.getClaimDef(seqNo=recvdCredDef[F.seqNo.name]) - assert claimDef.attrNames == definition[ATTR_NAMES] - - -def testGetIssuerKey(claimDefinitionAdded, userWalletA, tdir, - nodeSet, looper, sponsorWallet, claimDef, - issuerPublicKeysAdded, curiousClient): - key = (sponsorWallet.defaultId, claimDefinitionAdded) - req = userWalletA.requestIssuerKey(key, - userWalletA.defaultId) - curiousClient.submitReqs(req) - f = getMaxFailures(len(nodeSet)) - looper.run(eventually(checkSufficientRepliesRecvd, curiousClient.inBox, - req.reqId, f, - retryWait=1, timeout=5)) - reply, status = curiousClient.getReply(*req.key) - assert status == "CONFIRMED" - assert userWalletA.getIssuerPublicKey(key).seqNo - # TODO: Confirm that issuer key retrieved from ledger is same as the one - # added, modify the `issuerPublicKeysAdded` fixture to return the key as well - diff --git a/sovrin/test/anon_creds/test_public_repo.py b/sovrin/test/anon_creds/test_public_repo.py new file mode 100644 index 0000000..bf80513 --- /dev/null +++ b/sovrin/test/anon_creds/test_public_repo.py @@ -0,0 +1,108 @@ +import pytest + +from anoncreds.protocol.issuer import Issuer +from anoncreds.protocol.repo.attributes_repo import AttributeRepoInMemory +from anoncreds.protocol.types import ClaimDefinition, ID +from anoncreds.protocol.wallet.issuer_wallet import IssuerWalletInMemory +from sovrin.anon_creds.sovrin_public_repo import SovrinPublicRepo +from sovrin.test.anon_creds.conftest import GVT + + +@pytest.fixture(scope="module") +def publicRepo(steward, stewardWallet): + return SovrinPublicRepo(steward, stewardWallet) + + +@pytest.fixture(scope="module") +def issuerGvt(publicRepo): + return Issuer(IssuerWalletInMemory('issuer1', publicRepo), + AttributeRepoInMemory()) + + +@pytest.fixture(scope="module") +def claimDefGvt(stewardWallet): + return ClaimDefinition('GVT', '1.0', GVT.attribNames(), 'CL', + stewardWallet.defaultId) + + +@pytest.fixture(scope="module") +def submittedClaimDefGvt(publicRepo, claimDefGvt, looper): + return looper.run(publicRepo.submitClaimDef(claimDefGvt)) + + +@pytest.fixture(scope="module") +def submittedClaimDefGvtID(submittedClaimDefGvt): + return ID(claimDefKey=submittedClaimDefGvt.getKey(), + claimDefId=submittedClaimDefGvt.seqId) + + +@pytest.fixture(scope="module") +def publicSecretKey(submittedClaimDefGvtID, issuerGvt, primes1, looper): + return looper.run( + issuerGvt._primaryIssuer.genKeys(submittedClaimDefGvtID, **primes1)) + + +@pytest.fixture(scope="module") +def publicSecretRevocationKey(issuerGvt, looper): + return looper.run(issuerGvt._nonRevocationIssuer.genRevocationKeys()) + + +@pytest.fixture(scope="module") +def publicKey(publicSecretKey): + return publicSecretKey[0] + + +@pytest.fixture(scope="module") +def publicRevocationKey(publicSecretRevocationKey): + return publicSecretRevocationKey[0] + + +@pytest.fixture(scope="module") +def submittedPublicKeys(submittedClaimDefGvtID, publicRepo, publicSecretKey, + publicSecretRevocationKey, looper): + pk, sk = publicSecretKey + pkR, skR = publicSecretRevocationKey + return looper.run( + publicRepo.submitPublicKeys(id=submittedClaimDefGvtID, pk=pk, pkR=pkR)) + + +@pytest.fixture(scope="module") +def submittedPublicKey(submittedPublicKeys): + return submittedPublicKeys[0] + + +@pytest.fixture(scope="module") +def submittedPublicRevocationKey(submittedPublicKeys): + return submittedPublicKeys[1] + + +def testSubmitClaimDef(submittedClaimDefGvt, claimDefGvt): + assert submittedClaimDefGvt + assert submittedClaimDefGvt.seqId + submittedClaimDefGvt = submittedClaimDefGvt._replace( + seqId=None) # initial claim def didn't have seqNo + assert submittedClaimDefGvt == claimDefGvt + + +def testGetClaimDef(submittedClaimDefGvt, publicRepo, looper): + claimDef = looper.run( + publicRepo.getClaimDef(ID(claimDefKey=submittedClaimDefGvt.getKey()))) + assert claimDef == submittedClaimDefGvt + + +def testSubmitPublicKey(submittedPublicKeys): + assert submittedPublicKeys + + +def testGetPrimaryPublicKey(submittedClaimDefGvtID, submittedPublicKey, + publicRepo, looper): + pk = looper.run(publicRepo.getPublicKey(id=submittedClaimDefGvtID)) + assert pk == submittedPublicKey + + +def testGetRevocationPublicKey(submittedClaimDefGvtID, + submittedPublicRevocationKey, + publicRepo, looper): + pk = looper.run( + publicRepo.getPublicKeyRevocation(id=submittedClaimDefGvtID)) + assert pk == submittedPublicRevocationKey diff --git a/sovrin/test/cli/conftest.py b/sovrin/test/cli/conftest.py index 1e43ea6..74fed30 100644 --- a/sovrin/test/cli/conftest.py +++ b/sovrin/test/cli/conftest.py @@ -1,6 +1,5 @@ import json import traceback -import uuid import plenum import pytest @@ -16,7 +15,6 @@ from plenum.common.looper import Looper from plenum.test.cli.helper import newKeyPair, checkAllNodesStarted, \ checkCmdValid -from plenum.test.conftest import poolTxnStewardData, poolTxnStewardNames from sovrin.common.config_util import getConfig from sovrin.test.cli.helper import newCLI, ensureNodesCreated, getLinkInvitation @@ -26,8 +24,6 @@ acmeAgent, thriftIsRunning as runningThrift, thriftAgentPort, thriftWallet,\ thriftAgent -from anoncreds.test.conftest import staticPrimes - config = getConfig() @@ -508,8 +504,7 @@ def jobCertificateClaimMap(): @pytest.fixture(scope="module") def reqClaimOut(): return ["Found claim {name} in link {inviter}", - "Requesting claim {name} from {inviter}...", - "Getting Keys for the Claim Definition from Sovrin"] + "Requesting claim {name} from {inviter}..."] # TODO Change name @@ -517,7 +512,6 @@ def reqClaimOut(): def reqClaimOut1(): return ["Found claim {name} in link {inviter}", "Requesting claim {name} from {inviter}...", - "Getting Claim Definition from Sovrin", "Signature accepted.", 'Received claim "{name}".'] @@ -812,7 +806,7 @@ def thriftIsRunning(emptyLooper, tdirWithPoolTxns, thriftWallet, @pytest.fixture(scope="module") -def credDefAdded(): +def claimDefAdded(): return ["credential definition is published"] diff --git a/sovrin/test/cli/helper.py b/sovrin/test/cli/helper.py index cc0650d..9109df9 100644 --- a/sovrin/test/cli/helper.py +++ b/sovrin/test/cli/helper.py @@ -1,17 +1,15 @@ import json - import os -from plenum.test.eventually import eventually -from sovrin.client.wallet.link import Link - -from sovrin.common.plugin_helper import writeAnonCredPlugin -from sovrin.test.helper import TestNode, TestClient +from plenum.common.txn import TARGET_NYM, ROLE from plenum.test.cli.helper import TestCliCore, newCLI as newPlenumCLI, \ assertAllNodesCreated, checkAllNodesStarted, initDirWithGenesisTxns +from plenum.test.eventually import eventually from plenum.test.testable import Spyable -from plenum.common.txn import TARGET_NYM, ROLE + from sovrin.cli.cli import SovrinCli +from sovrin.client.wallet.link import Link +from sovrin.test.helper import TestNode, TestClient @Spyable(methods=[SovrinCli.print, SovrinCli.printTokens]) @@ -24,7 +22,6 @@ def newCLI(looper, tdir, subDirectory=None, conf=None, poolDir=None, tempDir = os.path.join(tdir, subDirectory) if subDirectory else tdir if poolDir or domainDir: initDirWithGenesisTxns(tempDir, conf, poolDir, domainDir) - writeAnonCredPlugin(tempDir, reloadTestModules=True) return newPlenumCLI(looper, tempDir, cliClass=TestCLI, nodeClass=TestNode, clientClass=TestClient, config=conf) @@ -81,7 +78,7 @@ def ensureNymAdded(cli, nym, role=None): cli.enterCmd('send ATTRIB {dest}={nym} raw={raw}'. format(dest=TARGET_NYM, nym=nym, # raw='{\"attrName\":\"attrValue\"}')) - raw=json.dumps({"attrName":"attrValue"}))) + raw=json.dumps({"attrName": "attrValue"}))) cli.looper.run(eventually(checkAddAttr, cli, retryWait=1, timeout=10)) diff --git a/sovrin/test/cli/test_add_genesis_transaction.py b/sovrin/test/cli/test_add_genesis_transaction.py index 41ce029..5385411 100644 --- a/sovrin/test/cli/test_add_genesis_transaction.py +++ b/sovrin/test/cli/test_add_genesis_transaction.py @@ -1,4 +1,5 @@ from plenum.test.cli.helper import checkCmdValid + from sovrin.common.txn import STEWARD, NYM from sovrin.common.txn import TARGET_NYM, ROLE diff --git a/sovrin/test/cli/test_anon_creds_cli.py b/sovrin/test/cli/test_anon_creds_cli.py deleted file mode 100644 index 52b8a45..0000000 --- a/sovrin/test/cli/test_anon_creds_cli.py +++ /dev/null @@ -1,473 +0,0 @@ -import pytest -import re - -import sovrin.anon_creds.cred_def as cred_def - -from plenum.common.looper import Looper -from plenum.common.txn import NAME, VERSION, ORIGIN -from plenum.test.cli.helper import newKeyPair, checkCmdValid, \ - checkClientConnected -from plenum.test.eventually import eventually -from sovrin.common.txn import SPONSOR, USER, CRED_DEF, ISSUER_KEY, REF -from sovrin.test.cli.helper import newCLI, ensureConnectedToTestEnv, \ - ensureNymAdded - -""" -Test Plan: - -6 CLI instances - 1 to run the consensus pool and 5 for clients. -5 clients - Phil, Tyler, BookStore, BYU and Trustee - -Phil is a pre-existing sponsor on Sovrin -BYU is a sponsor added by Phil to Sovrin -Tyler is a user added by BYU to Sovrin -BookStore is a user already on Sovrin -Trustee is a generic trustee who will add BookStore's nym. - -Plenum Fixtures: -1. For Phil, -Create a sponsor on Sovrin with keypair alias Phil. This should be done on -his CLI, so that when Phil gives a 'list ids' command, he can see his -keypair and the alias. -2. Fixture to add BookStore as a user. -3. BYU does the same operation as Phil, but without alias. (same fixture as - Phil) -4. Tyler creates a keypair and sends it to BYU. BYU creates the NYM for -Tyler. (2 fixtures) - -Sovrin Fixtures: -1. Adding credDef to the ledger. -2. - -Optional: -BYU adds a public attribute, mailing address - -Out of Band communication: -Passing public keys from user to sponsor: The test case passes the -objects from one cli to another directly. - - -""" - - -@pytest.yield_fixture(scope="module") -def byuCLI(CliBuilder): - yield from CliBuilder("byu") - - -@pytest.yield_fixture(scope="module") -def philCLI(CliBuilder): - yield from CliBuilder("phil") - - -@pytest.yield_fixture(scope="module") -def trusteeCLI(CliBuilder): - yield from CliBuilder("trustee") - - -@pytest.yield_fixture(scope="module") -def tylerCLI(CliBuilder): - yield from CliBuilder("tyler") - - -@pytest.yield_fixture(scope="module") -def bookStoreCLI(CliBuilder): - yield from CliBuilder("bookStore") - - -@pytest.fixture(scope="module") -def nodesSetup(philCreated, trusteeCreated, poolNodesCreated): - pass - - -@pytest.fixture(scope="module") -def philPubKey(philCLI): - return newKeyPair(philCLI) - - -@pytest.fixture(scope="module") -def trusteePubKey(trusteeCLI): - return newKeyPair(trusteeCLI) - - -@pytest.fixture(scope="module") -def byuPubKey(byuCLI): - return newKeyPair(byuCLI) - - -@pytest.fixture(scope="module") -def tylerPubKey(tylerCLI): - return newKeyPair(tylerCLI, alias='Tyler') # or should it be "forBYU"? - - -@pytest.fixture(scope="module") -def bookStorePubKey(bookStoreCLI): - return newKeyPair(bookStoreCLI, alias='BookStore') - - -@pytest.fixture(scope="module") -def philCreated(poolCLI, philPubKey): - checkCmdValid(poolCLI, "add genesis transaction NYM dest={} role=STEWARD". - format(philPubKey)) - assert "Genesis transaction added." in poolCLI.lastCmdOutput - - -# TODO: Find a better name for `trustee`. -@pytest.fixture(scope="module") -def trusteeCreated(poolCLI, trusteePubKey): - checkCmdValid(poolCLI, "add genesis transaction NYM dest={} role=STEWARD". - format(trusteePubKey)) - assert "Genesis transaction added." in poolCLI.lastCmdOutput - - -@pytest.fixture(scope="module") -def philConnected(philCreated, philCLI, nodesSetup): - ensureConnectedToTestEnv(philCLI) - - -@pytest.fixture(scope="module") -def bookStoreCreated(bookStorePubKey, trusteeCreated, trusteeCLI, - nodesSetup): - ensureNymAdded(trusteeCLI, bookStorePubKey, USER) - - -@pytest.fixture(scope="module") -def bookStoreConnected(bookStoreCreated, bookStoreCLI, nodesSetup): - ensureConnectedToTestEnv(bookStoreCLI) - bookStoreCLI.logger.debug("Book store connected") - - -@pytest.fixture(scope="module") -def byuCreated(byuPubKey, philCreated, philCLI, nodesSetup): - ensureNymAdded(philCLI, byuPubKey, SPONSOR) - - -@pytest.fixture(scope="module") -def tylerCreated(byuCreated, tylerPubKey, byuCLI, nodesSetup): - ensureNymAdded(byuCLI, tylerPubKey) - - -@pytest.fixture(scope="module") -def tylerConnected(tylerCreated, tylerCLI): - ensureConnectedToTestEnv(tylerCLI) - - -@pytest.fixture(scope="module") -def tylerStoresAttributesAsKnownToBYU(tylerCreated, tylerCLI, nodesSetup, - byuCLI, tylerConnected): - issuerId = byuCLI.activeWallet.defaultId - checkCmdValid(tylerCLI, - "attribute known to {} first_name=Tyler, last_name=Ruff, " - "birth_date=12/17/1991, expiry_date=12/31/2101, " - "undergrad=True, " - "postgrad=False".format(issuerId)) - assert issuerId in tylerCLI.attributeRepo.attributes - - -@pytest.fixture(scope="module") -def credDefNameVersion(): - credDefName = "Degree" - credDefVersion = "1.0" - return credDefName, credDefVersion - - -@pytest.fixture(scope="module") -def byuAddsCredDef(byuCLI, byuCreated, tylerCreated, byuPubKey, - credDefNameVersion): - credDefName, credDefVersion = credDefNameVersion - # TODO tylerAdded ensures that activeClient is already set. - """BYU writes a credential definition to Sovrin.""" - cmd = ("send CRED_DEF name={} version={} " - "type=CL keys=undergrad,last_name," - "first_name,birth_date,postgrad,expiry_date". - format(credDefName, credDefVersion)) - checkCmdValid(byuCLI, cmd) - - def checkCredAdded(): - txns = byuCLI.activeClient.getTxnsByType(CRED_DEF) - assert any(txn[NAME] == credDefName and - txn[VERSION] == credDefVersion - for txn in txns) - output = byuCLI.lastCmdOutput - assert "credential definition is published" in output - assert credDefName in output - - byuCLI.looper.run(eventually(checkCredAdded, retryWait=1, timeout=15)) - return byuCLI.activeWallet.defaultId - - -@pytest.fixture(scope="module") -def byuAddsIssuerKey(byuCLI, byuAddsCredDef, credDefNameVersion): - origin = byuAddsCredDef - key = (*credDefNameVersion, origin) - claimDef = byuCLI.activeWallet.getClaimDef(key=key) - cmd = ("send ISSUER_KEY ref={}" .format(claimDef.seqNo)) - checkCmdValid(byuCLI, cmd) - - def checkIsKAdded(): - assert byuCLI.activeWallet.getIssuerPublicKey((origin, claimDef.seqNo)) - output = byuCLI.lastCmdOutput - assert "issuer key is published" in output - - byuCLI.looper.run(eventually(checkIsKAdded, retryWait=1, timeout=15)) - return byuCLI.activeWallet.getIssuerPublicKey((origin, claimDef.seqNo)) - - -@pytest.fixture(scope="module") -def tylerPreparedU(nodesSetup, tylerCreated, tylerCLI, byuCLI, - attrAddedToRepo, byuAddsCredDef, byuAddsIssuerKey, - credDefNameVersion, tylerConnected, - tylerStoresAttributesAsKnownToBYU): - credDefName, credDefVersion = credDefNameVersion - issuerIdentifier = byuAddsCredDef - proverName = tylerCLI.activeWallet.defaultAlias - checkCmdValid(tylerCLI, "request credential {} version {} from {} for {}" - .format(credDefName, credDefVersion, issuerIdentifier, - proverName)) - - def chk(): - out = "Credential request for {} for {} {} is".format(proverName, - credDefName, - credDefVersion) - assert out in tylerCLI.lastCmdOutput - - tylerCLI.looper.run(eventually(chk, retryWait=1, timeout=15)) - pat = re.compile( - "Requesting credential for public key ([0-9]+) and U is ([0-9]+\s+mod\s+[0-9]+)") - m = pat.search(tylerCLI.lastCmdOutput) - assert m - publicKeyId, U = m.groups() - return publicKeyId, U - - -@pytest.fixture(scope="module") -def byuCreatedCredential(nodesSetup, byuCLI, tylerCLI, - tylerStoresAttributesAsKnownToBYU, tylerPreparedU, - credDefNameVersion): - credDefName, credDefVersion = credDefNameVersion - publicKeyId, U = tylerPreparedU - assert publicKeyId - assert U - proverId = tylerCLI.activeWallet.defaultAlias - checkCmdValid(byuCLI, "generate credential for {} for {} version {} with {}" - .format(proverId, credDefName, credDefVersion, U)) - assert "Credential:" in byuCLI.lastCmdOutput - pat = re.compile( - "A\s*=\s*([mod0-9\s]+), e\s*=\s*([mod0-9\s]+), vprimeprime\s*=\s*([" - "mod0-9\s]+)") - m = pat.search(byuCLI.lastCmdOutput) - if m: - A, e, vprimeprime = m.groups() - return A, e, vprimeprime - - -@pytest.fixture(scope="module") -def attrRepoInitialized(byuCLI, byuCreated): - assert byuCLI.attributeRepo is None - byuCLI.enterCmd("initialize mock attribute repo") - assert byuCLI.lastCmdOutput == "attribute repo initialized" - assert byuCLI.attributeRepo is not None - return byuCLI - - -@pytest.fixture(scope="module") -def attrAddedToRepo(attrRepoInitialized): - byuCLI = attrRepoInitialized - proverId = "Tyler" - assert byuCLI.attributeRepo.getAttributes(proverId) is None - checkCmdValid(byuCLI, "add attribute first_name=Tyler, last_name=Ruff, " - "birth_date=12/17/1991, expiry_date=12/31/2101, " - "undergrad=True, " - "postgrad=False for {}".format(proverId)) - assert byuCLI.lastCmdOutput == \ - "attribute added successfully for prover id {}".format(proverId) - assert byuCLI.attributeRepo.getAttributes(proverId) is not None - - -@pytest.fixture(scope="module") -def storedCredAlias(): - return 'CRED-BYU-QUAL' - - -@pytest.fixture(scope="module") -def revealedAtrr(): - return "undergrad" - - -@pytest.fixture(scope="module") -def storedCred(tylerCLI, storedCredAlias, byuCreatedCredential, - credDefNameVersion, byuPubKey, byuCLI, tylerPreparedU): - publicKeyId, U = tylerPreparedU - assert len(tylerCLI.activeWallet.credNames) == 0 - checkCmdValid(tylerCLI, "store credential A={}, e={}, vprimeprime={} for " - "credential {} as {}".format(*byuCreatedCredential, - publicKeyId, - storedCredAlias)) - assert len(tylerCLI.activeWallet.credNames) == 1 - assert tylerCLI.lastCmdOutput == "Credential stored" - - -@pytest.fixture(scope="module") -def listedCred(tylerCLI, storedCred, storedCredAlias): - credName = storedCredAlias - tylerCLI.enterCmd("list CRED") - assert credName in tylerCLI.lastCmdOutput - - -@pytest.fixture(scope="module") -def preparedProof(tylerCLI, storedCred, verifNonce, storedCredAlias, - tylerPreparedU, revealedAtrr): - """ - prepare proof of using nonce for - """ - checkCmdValid(tylerCLI, "prepare proof of {} using nonce {} for {}". - format(storedCredAlias, verifNonce, revealedAtrr)) - assert tylerCLI.lastCmdOutput.startswith("Proof is:") - pat = re.compile("Proof is: \n(.+)$") - m = pat.search(tylerCLI.lastCmdOutput) - assert m - proof = m.groups()[0] - assert proof - return proof - - -@pytest.fixture(scope="module") -def verifNonce(bookStoreCLI, bookStoreConnected): - checkCmdValid(bookStoreCLI, "generate verification nonce") - search = re.search("^Verification nonce is (.*)$", - bookStoreCLI.lastCmdOutput, - re.MULTILINE) - assert search - nonce = search.group(1) - assert nonce - return nonce - - -def testNodesCreatedOnPoolCLI(nodesSetup): - pass - - -def testPhilCreatesNewKeypair(philPubKey): - pass - - -def testPhilCreated(philCreated): - pass - - -def testBYUCreated(byuCreated): - pass - - -def testTylerCreated(tylerCreated): - pass - - -def testBYUAddsCredDef(byuAddsCredDef): - pass - - -def testBYUAddsIssuerKey(byuAddsIssuerKey): - pass - - -def testBookStoreCreated(bookStoreCreated): - pass - - -def testInitAttrRepo(attrRepoInitialized): - pass - - -def testAddAttrToRepo(attrAddedToRepo): - pass - - -def testTylerAddsBYUKnownAttributes(tylerConnected, - tylerStoresAttributesAsKnownToBYU): - pass - - -def testReqCred(tylerPreparedU): - pass - - -def testGenCred(byuCreatedCredential): - pass - - -def testStoreCred(byuCreatedCredential, tylerCLI, storedCred): - pass - - -def testListCred(byuCreatedCredential, storedCred, listedCred): - pass - - -def testGenVerifNonce(verifNonce): - pass - - -def testPrepareProof(preparedProof): - """ - prepare proof of using nonce for - """ - pass - - -def testVerifyProof(preparedProof, bookStoreCLI, bookStoreConnected, - revealedAtrr): - assert preparedProof - checkCmdValid(bookStoreCLI, "verify status is {} in proof {}" - .format(revealedAtrr, preparedProof)) - - def chk(): - out = "Proof verified successfully" - # TODO: Find out why this cant be done using lastCmdOutput - assert out in [o['msg'] for o in bookStoreCLI.printeds] - - bookStoreCLI.looper.run(eventually(chk, retryWait=1, timeout=15)) - - -def testStrTointeger(philCLI): - s = "10516306386726286019672155171905126058592128650909982334149995178986" \ - "73041451400057431499605181158943503786100424862329515980934309973855" \ - "43633459501210681944476644996687930156869752481111735518704772442711" \ - "32920275895833800639728797016576397009810826557954409165313076062707" \ - "47896162050899399482026144231267729603011571552636739780628091227072" \ - "28494440544261006174039338285983989215624037187355611285872388705963" \ - "92055048166735124754509553245871190284165785505721277426571438891467" \ - "29655980852237635516682805164630997143549509439718228947745728568873" \ - "10731055328114318748960481507556680409317152030165076731535544266284" \ - "97344415043361299612836649013009811372870411059001759588530641278227" \ - "57354566174219004200609850136175582501103513229230409927604286888631" \ - "75800846390172847752237996720841151109593372359247207743122920739798" \ - "82866204968535074422800013404448542530994158870307914545156283091106" \ - "37115909820184225905798609572477314653567493587473310985742198863055" \ - "06587955028364104696132700843261021195133842375205258169506504549494" \ - "29889788207677394004011987322884615951959337492651215535237394870438" \ - "68207276588075432476520034758009673995991879029329589895963661033408" \ - "38408748799357047790102800661841526315212047820316288243833530421278" \ - "49220882904454974822462754425677230712125202287893066412470634696970" \ - "66231997995973482950088138763475549481696937039149518646360622862464" \ - "50748921772308784787805092674994571742201612047716630085199077191636" \ - "4373279780274868194055796561573920450780745745182897139619 " \ - "mod " \ - "17110939584351099093696670018389609679123235613458011379970547802872" \ - "97845880693420604905384069214955803671128011572590150617889133331019" \ - "87767287757635214004081316079534532746984329504603946034535455284745" \ - "68133662112187368369825445516306247795885376833794985882661431944005" \ - "56774611220327379825358059647159046779617474158142416357512573091169" \ - "21742481914530128558200847594640862842694412593095042311521914864035" \ - "92285503796577197095138752688777268824531776522996865594649891459269" \ - "52651057269564711873631049284032786289125795739748747186484622340137" \ - "09891887981964130042842902760426557389856693142665410665604318462645" \ - "45166408163005165263132781945822482669510449665227350126593542756259" \ - "49016086602052739665101758864010913600376302252580874442010241767160" \ - "04738260768285471771028761205014421557974943906343096963043686300824" \ - "41915506224275376177925859647928995537185609046742534998874286371343" \ - "66703575716284805303334603968057185045321034098727958620904195146881" \ - "865483244806147104667605098138613899840626729916612169723470228930801507396158440259774040553984850335586645194467365045176677506537296253654429662975816874630847874003647935529333964941855401786336352853043803498640759072173609203160413437402970023625421911392981092263211748047448929085861379410272047860536995972453496075851660446485058108906037436369067625674495155937598646143535510599911729010586276679305856525112130907097314388354485920043436412137797426978774012573863335500074359101826932761239032674620096110906293228090163" - i = philCLI.getCryptoInteger(s) - assert str(i) == s diff --git a/sovrin/test/cli/test_command_reg_ex.py b/sovrin/test/cli/test_command_reg_ex.py index f584597..a05f09d 100644 --- a/sovrin/test/cli/test_command_reg_ex.py +++ b/sovrin/test/cli/test_command_reg_ex.py @@ -1,8 +1,9 @@ import pytest +from plenum.cli.helper import getClientGrams from plenum.test.cli.helper import assertCliTokens -from prompt_toolkit.contrib.regular_languages.compiler import compile -from plenum.cli.helper import getUtilGrams, getNodeGrams, getClientGrams, getAllGrams from plenum.test.cli.test_command_reg_ex import getMatchedVariables +from prompt_toolkit.contrib.regular_languages.compiler import compile + from sovrin.cli.helper import getNewClientGrams @@ -21,59 +22,24 @@ def testSendNymWithoutRole(grammar): def testSendAttribRegEx(grammar): - getMatchedVariables(grammar, 'send ATTRIB dest=LNAyBZUjvLF7duhrNtOWgdAKs18nHdbJUxJLT39iEGU= raw={"legal org": "BRIGHAM YOUNG UNIVERSITY, PROVO, UT", "email":"mail@byu.edu"}') - - -def testInitAttrRepoRegEx(grammar): - getMatchedVariables(grammar, "initialize mock attribute repo") + getMatchedVariables(grammar, + 'send ATTRIB dest=LNAyBZUjvLF7duhrNtOWgdAKs18nHdbJUxJLT39iEGU= raw={"legal org": "BRIGHAM YOUNG UNIVERSITY, PROVO, UT", "email":"mail@byu.edu"}') def testAddAttrRegEx(grammar): - getMatchedVariables(grammar, "add attribute first_name=Tyler,last_name=Ruff,birth_date=12/17/1991,undergrad=True,postgrad=True,expiry_date=12/31/2101 for Tyler") + getMatchedVariables(grammar, + "add attribute first_name=Tyler,last_name=Ruff,birth_date=12/17/1991,undergrad=True,postgrad=True,expiry_date=12/31/2101 for Tyler") def testAddAttrProverRegEx(grammar): - getMatchedVariables(grammar, "attribute known to BYU first_name=Tyler, last_name=Ruff, birth_date=12/17/1991, undergrad=True, postgrad=True, expiry_date=12/31/2101") + getMatchedVariables(grammar, + "attribute known to BYU first_name=Tyler, last_name=Ruff, birth_date=12/17/1991, undergrad=True, postgrad=True, expiry_date=12/31/2101") def testSendIssuerKeyRegEx(grammar): getMatchedVariables(grammar, "send ISSUER_KEY ref=15") -def testReqCredRegEx(grammar): - getMatchedVariables(grammar, - "request credential Degree version 1.0 from o7NzafnAlkhNaEM5njaH+I7Y19BEbEORmFB13p87zhM= for Tyler") - getMatchedVariables(grammar, - "request credential Degree version 1.0 from utNKIOcuy796g3jc+cQclAYn2/NUWRtyy/4q+EvZqQM= for Tyler") - - -def testGenCredRegEx(grammar): - getMatchedVariables(grammar, "generate credential for Tyler for Degree version 1.0 with uvalue") - - -def testStoreCredRegEx(grammar): - getMatchedVariables(grammar, "store credential A=avalue, e=evalue, vprimeprime=vprimevalue for credential cred-id as tyler-degree") - - -def testListCredRegEx(grammar): - getMatchedVariables(grammar, "list CRED") - - -def testGenVerifNonceRegEx(grammar): - getMatchedVariables(grammar, "generate verification nonce") - - -def testPrepProofRegEx(grammar): - getMatchedVariables(grammar, - "prepare proof of degree using nonce " - "mynonce for undergrad") - - -def testVerifyProofRegEx(grammar): - getMatchedVariables(grammar, - "verify status is undergrad in proof degreeproof") - - def testShowFileCommandRegEx(grammar): matchedVars = getMatchedVariables(grammar, "show sample/faber-invitation.sovrin") diff --git a/sovrin/test/cli/test_connect_env.py b/sovrin/test/cli/test_connect_env.py index e55ab8f..4a1c2b7 100644 --- a/sovrin/test/cli/test_connect_env.py +++ b/sovrin/test/cli/test_connect_env.py @@ -1,6 +1,6 @@ from plenum.test.eventually import eventually -from sovrin.test.cli.conftest import notConnectedStatus -from sovrin.test.cli.helper import checkConnectedToEnv, ensureNodesCreated + +from sovrin.test.cli.helper import checkConnectedToEnv def testConnectEnv(poolNodesCreated, looper, notConnectedStatus): diff --git a/sovrin/test/cli/test_nym.py b/sovrin/test/cli/test_nym.py index 2087a5b..47280dc 100644 --- a/sovrin/test/cli/test_nym.py +++ b/sovrin/test/cli/test_nym.py @@ -1,8 +1,8 @@ import pytest - from plenum.common.signer_simple import SimpleSigner from plenum.common.txn import TARGET_NYM from plenum.test.eventually import eventually + from sovrin.test.cli.helper import newCLI, checkGetNym, chkNymAddedOutput diff --git a/sovrin/test/cli/test_tutorial.py b/sovrin/test/cli/test_tutorial.py index ed73261..0a5542d 100644 --- a/sovrin/test/cli/test_tutorial.py +++ b/sovrin/test/cli/test_tutorial.py @@ -1,11 +1,10 @@ import pytest -import time -from plenum.common.types import f - from plenum.common.txn import TYPE, NONCE, IDENTIFIER +from plenum.common.types import f from plenum.common.util import getTimeBasedId from plenum.test.eventually import eventually -from sovrin.agent.msg_types import ACCEPT_INVITE + +from sovrin.agent.msg_constants import ACCEPT_INVITE from sovrin.client.wallet.link import Link, constant from sovrin.common.exceptions import InvalidLinkException from sovrin.common.txn import ENDPOINT @@ -47,70 +46,36 @@ def x(cli): def poolNodesStarted(be, do, poolCLI): be(poolCLI) - do('new node all', within=6, - expect=['Alpha now connected to Beta', - 'Alpha now connected to Gamma', - 'Alpha now connected to Delta', - 'Beta now connected to Alpha', - 'Beta now connected to Gamma', - 'Beta now connected to Delta', - 'Gamma now connected to Alpha', - 'Gamma now connected to Beta', - 'Gamma now connected to Delta', - 'Delta now connected to Alpha', - 'Delta now connected to Beta', - 'Delta now connected to Gamma']) + do('new node all', within=6, + expect=['Alpha now connected to Beta', + 'Alpha now connected to Gamma', + 'Alpha now connected to Delta', + 'Beta now connected to Alpha', + 'Beta now connected to Gamma', + 'Beta now connected to Delta', + 'Gamma now connected to Alpha', + 'Gamma now connected to Beta', + 'Gamma now connected to Delta', + 'Delta now connected to Alpha', + 'Delta now connected to Beta', + 'Delta now connected to Gamma']) return poolCLI -@pytest.fixture(scope="module") -def faberCli(be, do, faberCLI): - be(faberCLI) - - do('prompt FABER', expect=prompt_is('FABER')) - - do('new keyring Faber', expect=['New keyring Faber created', - 'Active keyring set to "Faber"']) - seed = 'Faber000000000000000000000000000' - idr = 'FuN98eH2eZybECWkofW6A9BKJxxnTatBCopfUiNxo6ZB' - - do('new key with seed ' + seed, expect=['Key created in keyring Faber', - 'Identifier for key is ' + idr, - 'Current identifier set to ' + idr]) - return faberCLI - - -@pytest.fixture(scope="module") -def acmeCli(be, do, acmeCLI): - be(acmeCLI) - - do('prompt Acme', expect=prompt_is('Acme')) - - do('new keyring Acme', expect=['New keyring Acme created', - 'Active keyring set to "Acme"']) - seed = 'Acme0000000000000000000000000000' - idr = '7YD5NKn3P4wVJLesAmA1rr7sLPqW9mR1nhFdKD518k21' - - do('new key with seed ' + seed, expect=['Key created in keyring Acme', - 'Identifier for key is ' + idr, - 'Current identifier set to ' + idr]) - return acmeCLI - - @pytest.fixture(scope="module") def philCli(be, do, philCLI): be(philCLI) - do('prompt Phil', expect=prompt_is('Phil')) + do('prompt Phil', expect=prompt_is('Phil')) - do('new keyring Phil', expect=['New keyring Phil created', - 'Active keyring set to "Phil"']) + do('new keyring Phil', expect=['New keyring Phil created', + 'Active keyring set to "Phil"']) mapper = { 'seed': '11111111111111111111111111111111', 'idr': '5rArie7XKukPCaEwq5XGQJnM9Fc5aZE3M9HAPVfMU2xC'} - do('new key with seed {seed}', expect=['Key created in keyring Phil', - 'Identifier for key is {idr}', - 'Current identifier set to {idr}'], + do('new key with seed {seed}', expect=['Key created in keyring Phil', + 'Identifier for key is {idr}', + 'Current identifier set to {idr}'], mapper=mapper) return philCLI @@ -121,95 +86,91 @@ def faberAddedByPhil(be, do, poolNodesStarted, philCli, connectedToTest, nymAddedOut, faberMap): be(philCli) if not philCli._isConnectedToAnyEnv(): - do('connect test', within=3, - expect=connectedToTest) + do('connect test', within=3, + expect=connectedToTest) do('send NYM dest={target} role=SPONSOR', - within=3, - expect=nymAddedOut, mapper=faberMap) + within=3, + expect=nymAddedOut, mapper=faberMap) return philCli @pytest.fixture(scope="module") def acmeAddedByPhil(be, do, poolNodesStarted, philCli, connectedToTest, - nymAddedOut, acmeMap): + nymAddedOut, acmeMap): be(philCli) if not philCli._isConnectedToAnyEnv(): - do('connect test', within=3, - expect=connectedToTest) + do('connect test', within=3, + expect=connectedToTest) do('send NYM dest={target} role=SPONSOR', - within=3, - expect=nymAddedOut, mapper=acmeMap) + within=3, + expect=nymAddedOut, mapper=acmeMap) return philCli @pytest.fixture(scope="module") def thriftAddedByPhil(be, do, poolNodesStarted, philCli, connectedToTest, - nymAddedOut, thriftMap): + nymAddedOut, thriftMap): be(philCli) if not philCli._isConnectedToAnyEnv(): - do('connect test', within=3, - expect=connectedToTest) + do('connect test', within=3, + expect=connectedToTest) do('send NYM dest={target} role=SPONSOR', - within=3, - expect=nymAddedOut, mapper=thriftMap) + within=3, + expect=nymAddedOut, mapper=thriftMap) return philCli @pytest.fixture(scope="module") def faberWithEndpointAdded(be, do, philCli, faberAddedByPhil, faberMap, attrAddedOut): - be(philCli) do('send ATTRIB dest={target} raw={endpointAttr}', - within=3, - expect=attrAddedOut, - mapper=faberMap) + within=3, + expect=attrAddedOut, + mapper=faberMap) return philCli @pytest.fixture(scope="module") def acmeWithEndpointAdded(be, do, philCli, acmeAddedByPhil, - acmeMap, attrAddedOut): - + acmeMap, attrAddedOut): be(philCli) do('send ATTRIB dest={target} raw={endpointAttr}', - within=3, - expect=attrAddedOut, - mapper=acmeMap) + within=3, + expect=attrAddedOut, + mapper=acmeMap) return philCli @pytest.fixture(scope="module") def thriftWithEndpointAdded(be, do, philCli, thriftAddedByPhil, thriftMap, attrAddedOut): - be(philCli) do('send ATTRIB dest={target} raw={endpointAttr}', - within=3, - expect=attrAddedOut, - mapper=thriftMap) + within=3, + expect=attrAddedOut, + mapper=thriftMap) return philCli def connectIfNotAlreadyConnected(do, expectMsgs, userCli, userMap): # TODO: Shouldn't this be testing the cli command `status`? if not userCli._isConnectedToAnyEnv(): - do('connect test', within=3, - expect=expectMsgs, - mapper=userMap) + do('connect test', within=3, + expect=expectMsgs, + mapper=userMap) def checkWalletStates(userCli, totalLinks, totalAvailableClaims, - totalCredDefs, + totalClaimDefs, totalClaimsRcvd, within=None): - - def check(): + async def check(): assert totalLinks == len(userCli.activeWallet._links) tac = 0 @@ -217,10 +178,9 @@ def check(): tac += len(li.availableClaims) assert totalAvailableClaims == tac - assert totalCredDefs == len(userCli.activeWallet._claimDefs) + assert totalClaimDefs == len(await userCli.agent.prover.wallet.getAllClaimDef()) - assert totalClaimsRcvd == \ - len(userCli.activeWallet.attributesFrom.keys()) + assert totalClaimsRcvd == len((await userCli.agent.prover.wallet.getAllClaims()).keys()) if within: userCli.looper.run(eventually(check, timeout=within)) @@ -229,8 +189,8 @@ def check(): def setPromptAndKeyring(do, name, newKeyringOut, userMap): - do('prompt {}'.format(name), expect=prompt_is(name)) - do('new keyring {}'.format(name), expect=newKeyringOut, mapper=userMap) + do('prompt {}'.format(name), expect=prompt_is(name)) + do('new keyring {}'.format(name), expect=newKeyringOut, mapper=userMap) @pytest.fixture(scope="module") @@ -250,21 +210,21 @@ def aliceCli(preRequisite, be, do, aliceCLI, newKeyringOut, aliceMap): def testNotConnected(be, do, aliceCli, notConnectedStatus): be(aliceCli) - do('status', expect=notConnectedStatus) + do('status', expect=notConnectedStatus) def testShowInviteNotExists(be, do, aliceCli, fileNotExists, faberMap): checkWalletStates(aliceCli, totalLinks=0, totalAvailableClaims=0, - totalCredDefs=0, totalClaimsRcvd=0) + totalClaimDefs=0, totalClaimsRcvd=0) be(aliceCli) - do('show {invite-not-exists}', expect=fileNotExists, mapper=faberMap) + do('show {invite-not-exists}', expect=fileNotExists, mapper=faberMap) def testShowInviteWithDirPath(be, do, aliceCli, fileNotExists, faberMap): checkWalletStates(aliceCli, totalLinks=0, totalAvailableClaims=0, - totalCredDefs=0, totalClaimsRcvd=0) + totalClaimDefs=0, totalClaimsRcvd=0) be(aliceCli) - do('show sample', expect=fileNotExists, mapper=faberMap) + do('show sample', expect=fileNotExists, mapper=faberMap) def testLoadLinkInviteWithoutSig(): @@ -279,23 +239,23 @@ def testShowFaberInvite(be, do, aliceCli, faberMap): be(aliceCli) inviteContents = getFileLines(faberMap.get("invite")) - do('show {invite}', expect=inviteContents, - mapper=faberMap) + do('show {invite}', expect=inviteContents, + mapper=faberMap) def testLoadInviteNotExists(be, do, aliceCli, fileNotExists, faberMap): be(aliceCli) - do('load {invite-not-exists}', expect=fileNotExists, mapper=faberMap) + do('load {invite-not-exists}', expect=fileNotExists, mapper=faberMap) @pytest.fixture(scope="module") def faberInviteLoadedByAlice(be, do, aliceCli, loadInviteOut, faberMap): checkWalletStates(aliceCli, totalLinks=0, totalAvailableClaims=0, - totalCredDefs=0, totalClaimsRcvd=0) + totalClaimDefs=0, totalClaimsRcvd=0) be(aliceCli) - do('load {invite}', expect=loadInviteOut, mapper=faberMap) + do('load {invite}', expect=loadInviteOut, mapper=faberMap) checkWalletStates(aliceCli, totalLinks=1, totalAvailableClaims=0, - totalCredDefs=0, totalClaimsRcvd=0) + totalClaimDefs=0, totalClaimsRcvd=0) return aliceCli @@ -306,15 +266,15 @@ def testLoadFaberInvite(faberInviteLoadedByAlice): def testShowLinkNotExists(be, do, aliceCli, linkNotExists, faberMap): be(aliceCli) do('show link {inviter-not-exists}', - expect=linkNotExists, - mapper=faberMap) + expect=linkNotExists, + mapper=faberMap) def testShowFaberLink(be, do, aliceCli, faberInviteLoadedByAlice, - showUnSyncedLinkOut, faberMap): + showUnSyncedLinkOut, faberMap): be(aliceCli) - do('show link {inviter}', expect=showUnSyncedLinkOut, - mapper=faberMap) + do('show link {inviter}', expect=showUnSyncedLinkOut, + mapper=faberMap) def testSyncLinkNotExists(be, do, aliceCli, linkNotExists, faberMap): @@ -323,21 +283,21 @@ def testSyncLinkNotExists(be, do, aliceCli, linkNotExists, faberMap): def testSyncFaberWhenNotConnected(be, do, aliceCli, faberMap, - faberInviteLoadedByAlice, - syncWhenNotConnected): + faberInviteLoadedByAlice, + syncWhenNotConnected): be(aliceCli) - do('sync {inviter}', expect=syncWhenNotConnected, - mapper=faberMap) + do('sync {inviter}', expect=syncWhenNotConnected, + mapper=faberMap) def testAcceptUnSyncedFaberInviteWhenNotConnected(be, do, aliceCli, - faberInviteLoadedByAlice, - acceptUnSyncedWhenNotConnected, - faberMap): + faberInviteLoadedByAlice, + acceptUnSyncedWhenNotConnected, + faberMap): be(aliceCli) do('accept invitation from {inviter}', - expect=acceptUnSyncedWhenNotConnected, - mapper=faberMap) + expect=acceptUnSyncedWhenNotConnected, + mapper=faberMap) def testAcceptUnSyncedFaberInvite(be, do, aliceCli, faberInviteLoadedByAlice, @@ -350,13 +310,13 @@ def testAcceptUnSyncedFaberInvite(be, do, aliceCli, faberInviteLoadedByAlice, connectIfNotAlreadyConnected(do, connectedToTest, aliceCli, faberMap) checkWalletStates(aliceCli, totalLinks=1, totalAvailableClaims=0, - totalCredDefs=0, totalClaimsRcvd=0) + totalClaimDefs=0, totalClaimsRcvd=0) do('accept invitation from {inviter}', - within=3, - expect=acceptUnSyncedWithoutEndpointWhenConnected, - mapper=faberMap) + within=3, + expect=acceptUnSyncedWithoutEndpointWhenConnected, + mapper=faberMap) checkWalletStates(aliceCli, totalLinks=1, totalAvailableClaims=0, - totalCredDefs=0, totalClaimsRcvd=0) + totalClaimDefs=0, totalClaimsRcvd=0) @pytest.fixture(scope="module") @@ -370,9 +330,9 @@ def faberInviteSyncedWithoutEndpoint(be, do, aliceCli, faberMap, be(aliceCli) connectIfNotAlreadyConnected(do, connectedToTest, aliceCli, faberMap) - do('sync {inviter}', within=2, - expect=syncLinkOutWithoutEndpoint, - mapper=faberMap) + do('sync {inviter}', within=2, + expect=syncLinkOutWithoutEndpoint, + mapper=faberMap) return aliceCli @@ -383,14 +343,13 @@ def testSyncFaberInviteWithoutEndpoint(faberInviteSyncedWithoutEndpoint): def testShowSyncedFaberInvite(be, do, aliceCli, faberMap, linkNotYetSynced, faberInviteSyncedWithoutEndpoint, showSyncedLinkWithoutEndpointOut): - be(aliceCli) - do('show link {inviter}', within=4, - expect=showSyncedLinkWithoutEndpointOut, - # TODO, need to come back to not_expect - # not_expect=linkNotYetSynced, - mapper=faberMap) + do('show link {inviter}', within=4, + expect=showSyncedLinkWithoutEndpointOut, + # TODO, need to come back to not_expect + # not_expect=linkNotYetSynced, + mapper=faberMap) def testEndpointAddedForFaber(faberWithEndpointAdded): @@ -400,9 +359,9 @@ def testEndpointAddedForFaber(faberWithEndpointAdded): def syncInvite(be, do, userCli, expectedMsgs, mapping): be(userCli) - do('sync {inviter}', within=2, - expect=expectedMsgs, - mapper=mapping) + do('sync {inviter}', within=2, + expect=expectedMsgs, + mapper=mapping) @pytest.fixture(scope="module") @@ -421,16 +380,16 @@ def testSyncFaberInvite(faberInviteSyncedWithEndpoint): def testShowSyncedFaberInviteWithEndpoint(be, do, aliceCLI, faberInviteSyncedWithEndpoint, - showSyncedLinkWithEndpointOut, faberMap): + showSyncedLinkWithEndpointOut, faberMap): be(aliceCLI) - do('show link {inviter}', expect=showSyncedLinkWithEndpointOut, - mapper=faberMap) + do('show link {inviter}', expect=showSyncedLinkWithEndpointOut, + mapper=faberMap) def testAcceptNotExistsLink(be, do, aliceCli, linkNotExists, faberMap): be(aliceCli) do('accept invitation from {inviter-not-exists}', - expect=linkNotExists, mapper=faberMap) + expect=linkNotExists, mapper=faberMap) def getSignedRespMsg(msg, signer): @@ -442,13 +401,13 @@ def getSignedRespMsg(msg, signer): def acceptInvitation(be, do, userCli, agentMap, expect): be(userCli) do("accept invitation from {inviter}", - within=15, - mapper=agentMap, - expect=expect, - not_expect=[ - "Observer threw an exception", - "Identifier is not yet written to Sovrin"] - ) + within=15, + mapper=agentMap, + expect=expect, + not_expect=[ + "Observer threw an exception", + "Identifier is not yet written to Sovrin"] + ) @pytest.fixture(scope="module") @@ -458,11 +417,11 @@ def aliceAcceptedFaberInvitation(be, do, aliceCli, faberMap, faberLinkAdded, faberIsRunning, faberInviteSyncedWithEndpoint): checkWalletStates(aliceCli, totalLinks=1, totalAvailableClaims=0, - totalCredDefs=0, totalClaimsRcvd=0) + totalClaimDefs=0, totalClaimsRcvd=0) acceptInvitation(be, do, aliceCli, faberMap, syncedInviteAcceptedWithClaimsOut) checkWalletStates(aliceCli, totalLinks=1, totalAvailableClaims=1, - totalCredDefs=1, totalClaimsRcvd=0) + totalClaimDefs=1, totalClaimsRcvd=0) return aliceCli @@ -474,26 +433,25 @@ def testPingFaber(be, do, aliceCli, faberMap, aliceAcceptedFaberInvitation): be(aliceCli) do('ping {inviter}', - within=3, - expect=[ - "Ping sent.", - "Pong received."], - mapper=faberMap) + within=3, + expect=[ + "Ping sent.", + "Pong received."], + mapper=faberMap) def testAliceAcceptFaberInvitationAgain(be, do, aliceCli, faberMap, unsycedAlreadyAcceptedInviteAcceptedOut, aliceAcceptedFaberInvitation): - li = aliceCli.activeWallet.getLinkInvitationByTarget(faberMap['target']) li.linkStatus = None be(aliceCli) checkWalletStates(aliceCli, totalLinks=1, totalAvailableClaims=1, - totalCredDefs=1, totalClaimsRcvd=0) + totalClaimDefs=1, totalClaimsRcvd=0) acceptInvitation(be, do, aliceCli, faberMap, unsycedAlreadyAcceptedInviteAcceptedOut) checkWalletStates(aliceCli, totalLinks=1, totalAvailableClaims=1, - totalCredDefs=1, totalClaimsRcvd=0) + totalClaimDefs=1, totalClaimsRcvd=0) li.linkStatus = constant.LINK_STATUS_ACCEPTED @@ -505,104 +463,91 @@ def testShowFaberLinkAfterInviteAccept(be, do, aliceCli, faberMap, aliceAcceptedFaberInvitation): be(aliceCli) - do("show link {inviter}", expect=showAcceptedLinkOut, - # not_expect="Link (not yet accepted)", - mapper=faberMap) + do("show link {inviter}", expect=showAcceptedLinkOut, + # not_expect="Link (not yet accepted)", + mapper=faberMap) def testShowClaimNotExists(be, do, aliceCli, faberMap, showClaimNotFoundOut, - aliceAcceptedFaberInvitation): + aliceAcceptedFaberInvitation): be(aliceCli) do("show claim claim-to-show-not-exists", - expect=showClaimNotFoundOut, - mapper=faberMap) + expect=showClaimNotFoundOut, + mapper=faberMap, + within=3) def testShowTranscriptClaim(be, do, aliceCli, transcriptClaimMap, showTranscriptClaimOut, aliceAcceptedFaberInvitation): - be(aliceCli) do("show claim {name}", - expect=showTranscriptClaimOut, - mapper=transcriptClaimMap) + expect=showTranscriptClaimOut, + mapper=transcriptClaimMap, + within=3) def testReqClaimNotExists(be, do, aliceCli, faberMap, showClaimNotFoundOut, - aliceAcceptedFaberInvitation): + aliceAcceptedFaberInvitation): be(aliceCli) do("request claim claim-to-req-not-exists", - expect=showClaimNotFoundOut, - mapper=faberMap) + expect=showClaimNotFoundOut, + mapper=faberMap) @pytest.fixture(scope="module") def aliceRequestedTranscriptClaim(be, do, aliceCli, transcriptClaimMap, - reqClaimOut, - faberIsRunning, - aliceAcceptedFaberInvitation): + reqClaimOut, + faberIsRunning, + aliceAcceptedFaberInvitation): be(aliceCli) checkWalletStates(aliceCli, totalLinks=1, totalAvailableClaims=1, - totalCredDefs=1, totalClaimsRcvd=0) - do("request claim {name}", within=5, - expect=reqClaimOut, - mapper=transcriptClaimMap) + totalClaimDefs=1, totalClaimsRcvd=0) + do("request claim {name}", within=5, + expect=reqClaimOut, + mapper=transcriptClaimMap) checkWalletStates(aliceCli, totalLinks=1, totalAvailableClaims=1, - totalCredDefs=1, totalClaimsRcvd=1, within=2) + totalClaimDefs=1, totalClaimsRcvd=1, within=2) def testAliceReqClaim(aliceRequestedTranscriptClaim): pass -def testReqTranscriptClaimWithClaimDefNotInWallet(be, do, aliceCli, - transcriptClaimMap, reqClaimOut1, faberIsRunning, - aliceAcceptedFaberInvitation): - be(aliceCli) - inviter = transcriptClaimMap["inviter"] - links = aliceCli.activeWallet.getMatchingLinks(inviter) - assert len(links) == 1 - faberId = links[0].remoteIdentifier - name, version = transcriptClaimMap["name"], transcriptClaimMap["version"] - aliceCli.activeWallet._claimDefs.pop((name, version, faberId)) - do("request claim {name}", within=5, - expect=reqClaimOut1, - mapper=transcriptClaimMap) - - def testShowFaberClaimPostReqClaim(be, do, aliceCli, aliceRequestedTranscriptClaim, transcriptClaimValueMap, rcvdTranscriptClaimOut): be(aliceCli) do("show claim {name}", - expect=rcvdTranscriptClaimOut, - mapper=transcriptClaimValueMap) + expect=rcvdTranscriptClaimOut, + mapper=transcriptClaimValueMap, + within=3) def testShowAcmeInvite(be, do, aliceCli, acmeMap): be(aliceCli) inviteContents = getFileLines(acmeMap.get("invite")) - do('show {invite}', expect=inviteContents, - mapper=acmeMap) + do('show {invite}', expect=inviteContents, + mapper=acmeMap) @pytest.fixture(scope="module") def acmeInviteLoadedByAlice(be, do, aliceCli, loadInviteOut, acmeMap): checkWalletStates(aliceCli, totalLinks=1, totalAvailableClaims=1, - totalCredDefs=1, totalClaimsRcvd=1) + totalClaimDefs=1, totalClaimsRcvd=1) be(aliceCli) - do('load {invite}', expect=loadInviteOut, mapper=acmeMap) + do('load {invite}', expect=loadInviteOut, mapper=acmeMap) link = aliceCli.activeWallet.getLinkInvitation(acmeMap.get("inviter")) link.remoteEndPoint = acmeMap.get(ENDPOINT) checkWalletStates(aliceCli, totalLinks=2, totalAvailableClaims=1, - totalCredDefs=1, totalClaimsRcvd=1) + totalClaimDefs=1, totalClaimsRcvd=1) return aliceCli @@ -612,12 +557,12 @@ def testLoadAcmeInvite(acmeInviteLoadedByAlice): def testShowAcmeLink(be, do, aliceCli, acmeInviteLoadedByAlice, - showUnSyncedLinkOut, showLinkWithClaimReqOut, acmeMap): + showUnSyncedLinkOut, showLinkWithClaimReqOut, acmeMap): showUnSyncedLinkWithClaimReqs = \ showUnSyncedLinkOut + showLinkWithClaimReqOut be(aliceCli) - do('show link {inviter}', expect=showUnSyncedLinkWithClaimReqs, - mapper=acmeMap) + do('show link {inviter}', expect=showUnSyncedLinkWithClaimReqs, + mapper=acmeMap) @pytest.fixture(scope="module") @@ -628,13 +573,13 @@ def aliceAcceptedAcmeJobInvitation(aliceCli, be, do, acmeIsRunning, acmeMap, acmeLinkAdded, acmeWithEndpointAdded): checkWalletStates(aliceCli, totalLinks=2, totalAvailableClaims=1, - totalCredDefs=1, totalClaimsRcvd=1) + totalClaimDefs=1, totalClaimsRcvd=1) be(aliceCli) acceptInvitation(be, do, aliceCli, acmeMap, unsycedAcceptedInviteWithoutClaimOut) checkWalletStates(aliceCli, totalLinks=2, totalAvailableClaims=1, - totalCredDefs=1, totalClaimsRcvd=1) + totalClaimDefs=1, totalClaimsRcvd=1) return aliceCli @@ -644,34 +589,34 @@ def testAliceAcceptAcmeJobInvitation(aliceAcceptedAcmeJobInvitation): def testSetAttrWithoutContext(be, do, aliceCli): be(aliceCli) - do("set first_name to Alice", expect=[ - "No context, " - "use below command to " - "set the context"]) + do("set first_name to Alice", expect=[ + "No context, " + "use below command to " + "set the context"]) def testShowAcmeLinkAfterInviteAccept(be, do, aliceCli, acmeMap, aliceAcceptedAcmeJobInvitation, showAcceptedLinkWithoutAvailableClaimsOut): - be(aliceCli) - do("show link {inviter}", expect=showAcceptedLinkWithoutAvailableClaimsOut, - not_expect="Link (not yet accepted)", - mapper=acmeMap) + do("show link {inviter}", expect=showAcceptedLinkWithoutAvailableClaimsOut, + not_expect="Link (not yet accepted)", + mapper=acmeMap) def testShowClaimReqNotExists(be, do, aliceCli, acmeMap, claimReqNotExists): be(aliceCli) do("show claim request claim-req-to-show-not-exists", - expect=claimReqNotExists, - mapper=acmeMap) + expect=claimReqNotExists, + mapper=acmeMap, + within=3) def claimReqShown(be, do, userCli, agentMap, - claimReqOut, - claimReqMap, - claimAttrValueMap): + claimReqOut, + claimReqMap, + claimAttrValueMap): be(userCli) mapping = { @@ -683,23 +628,24 @@ def claimReqShown(be, do, userCli, agentMap, mapping.update(claimReqMap) mapping.update(claimAttrValueMap) do("show claim request {claim-req-to-show}", - expect=claimReqOut, - mapper=mapping) + expect=claimReqOut, + mapper=mapping, + within=3) def testShowJobAppClaimReqWithShortName(be, do, aliceCli, acmeMap, - showJobAppClaimReqOut, - jobApplicationClaimReqMap, - transcriptClaimAttrValueMap, - aliceAcceptedAcmeJobInvitation): + showJobAppClaimReqOut, + jobApplicationClaimReqMap, + transcriptClaimAttrValueMap, + aliceAcceptedAcmeJobInvitation): newAcmeMap = {} newAcmeMap.update(acmeMap) newAcmeMap["claim-req-to-show"] = "Job" claimReqShown(be, do, aliceCli, newAcmeMap, - showJobAppClaimReqOut, - jobApplicationClaimReqMap, - transcriptClaimAttrValueMap) + showJobAppClaimReqOut, + jobApplicationClaimReqMap, + transcriptClaimAttrValueMap) def testShowJobAppilcationClaimReq(be, do, aliceCli, acmeMap, @@ -708,17 +654,17 @@ def testShowJobAppilcationClaimReq(be, do, aliceCli, acmeMap, transcriptClaimAttrValueMap, aliceAcceptedAcmeJobInvitation): claimReqShown(be, do, aliceCli, acmeMap, - showJobAppClaimReqOut, - jobApplicationClaimReqMap, - transcriptClaimAttrValueMap) + showJobAppClaimReqOut, + jobApplicationClaimReqMap, + transcriptClaimAttrValueMap) @pytest.fixture(scope="module") def aliceSelfAttestsAttributes(be, do, aliceCli, acmeMap, - showJobAppClaimReqOut, - jobApplicationClaimReqMap, - transcriptClaimAttrValueMap, - aliceAcceptedAcmeJobInvitation): + showJobAppClaimReqOut, + jobApplicationClaimReqMap, + transcriptClaimAttrValueMap, + aliceAcceptedAcmeJobInvitation): be(aliceCli) mapping = { @@ -731,7 +677,8 @@ def aliceSelfAttestsAttributes(be, do, aliceCli, acmeMap, mapping.update(transcriptClaimAttrValueMap) do("show claim request {claim-req-to-show}", expect=showJobAppClaimReqOut, - mapper=mapping) + mapper=mapping, + within=3) do("set first_name to Alice") do("set last_name to Garcia") do("set phone_number to 123-555-1212") @@ -748,14 +695,14 @@ def testShowJobApplicationClaimReqAfterSetAttr(be, do, aliceCli, aliceSelfAttestsAttributes): be(aliceCli) do("show claim request {claim-req-to-show}", - expect=showJobAppClaimReqOut, - mapper=aliceSelfAttestsAttributes) + expect=showJobAppClaimReqOut, + mapper=aliceSelfAttestsAttributes, + within=3) def testInvalidSigErrorResponse(be, do, aliceCli, faberMap, faberIsRunning, faberInviteSyncedWithoutEndpoint): - msg = { f.REQ_ID.nm: getTimeBasedId(), TYPE: ACCEPT_INVITE, @@ -769,14 +716,13 @@ def testInvalidSigErrorResponse(be, do, aliceCli, faberMap, aliceCli.sendToAgent(msg, link) be(aliceCli) - do(None, within=3, - expect=["Signature rejected.". - format(msg)]) + do(None, within=3, + expect=["Signature rejected.". + format(msg)]) def testLinkNotFoundErrorResponse(be, do, aliceCli, faberMap, - faberInviteSyncedWithoutEndpoint): - + faberInviteSyncedWithoutEndpoint): msg = { f.REQ_ID.nm: getTimeBasedId(), TYPE: ACCEPT_INVITE, @@ -791,7 +737,7 @@ def testLinkNotFoundErrorResponse(be, do, aliceCli, faberMap, be(aliceCli) do(None, within=3, - expect=["Nonce not found".format(msg)]) + expect=["Nonce not found".format(msg)]) def sendClaim(be, do, userCli, agentMap, newAvailableClaims, extraMsgs=None): @@ -811,64 +757,22 @@ def sendClaim(be, do, userCli, agentMap, newAvailableClaims, extraMsgs=None): expectMsgs.append("Available Claim(s): {new-available-claims}") do("send claim {claim-req-to-match} to {inviter}", - within=7, - expect=expectMsgs, - mapper=mapping) - - -def testReqUnavailableClaim(be, do, aliceCli, - acmeMap, - reqClaimOut1, - acmeIsRunning, - aliceAcceptedAcmeJobInvitation): - - acme, _ = acmeIsRunning - be(aliceCli) - - checkWalletStates(aliceCli, totalLinks=2, totalAvailableClaims=1, - totalCredDefs=1, totalClaimsRcvd=1) - - link = aliceCli.activeWallet._links[acmeMap.get('inviter')] - oldAvailableClaims = [] - oldCredDefs = {} - for ac in link.availableClaims: - oldAvailableClaims.append(ac) - - for cd in aliceCli.activeWallet._claimDefs.values(): - oldCredDefs[cd.key] = cd - - newAvailableClaims = acme.getJobCertAvailableClaimList() - - def dummyPostCredDef(li, nac): - pass - - aliceCli.agent._processNewAvailableClaimsData( - link, newAvailableClaims, dummyPostCredDef) - - time.sleep(3) - - do("request claim Job-Certificate", - within=7, - expect=["This claim is not yet available"]) - - link.availableClaims = oldAvailableClaims - aliceCli.activeWallet._claimDefs = oldCredDefs - checkWalletStates(aliceCli, totalLinks=2, totalAvailableClaims=1, - totalCredDefs=1, totalClaimsRcvd=1) + within=7, + expect=expectMsgs, + mapper=mapping) @pytest.fixture(scope="module") def jobApplicationClaimSent(be, do, aliceCli, acmeMap, - aliceAcceptedAcmeJobInvitation, - aliceRequestedTranscriptClaim, - aliceSelfAttestsAttributes): - + aliceAcceptedAcmeJobInvitation, + aliceRequestedTranscriptClaim, + aliceSelfAttestsAttributes): checkWalletStates(aliceCli, totalLinks=2, totalAvailableClaims=1, - totalCredDefs=1, totalClaimsRcvd=1) + totalClaimDefs=1, totalClaimsRcvd=1) sendClaim(be, do, aliceCli, acmeMap, "Job-Certificate") checkWalletStates(aliceCli, totalLinks=2, totalAvailableClaims=2, - totalCredDefs=2, totalClaimsRcvd=1) + totalClaimDefs=2, totalClaimsRcvd=1) def testAliceSendClaimProofToAcme(jobApplicationClaimSent): @@ -882,38 +786,35 @@ def testAliceSendClaimProofToAcme(jobApplicationClaimSent): def testShowAcmeLinkAfterClaimSent(be, do, aliceCli, acmeMap, jobApplicationClaimSent, showAcceptedLinkWithAvailableClaimsOut): - be(aliceCli) mapping = {} mapping.update(acmeMap) mapping["claims"] = "Job-Certificate" acmeMap.update(acmeMap) - do("show link {inviter}", expect=showAcceptedLinkWithAvailableClaimsOut, - mapper=mapping) + do("show link {inviter}", expect=showAcceptedLinkWithAvailableClaimsOut, + mapper=mapping) def testShowJobCertClaim(be, do, aliceCli, jobCertificateClaimMap, showJobCertClaimOut, jobApplicationClaimSent): - be(aliceCli) checkWalletStates(aliceCli, totalLinks=2, totalAvailableClaims=2, - totalCredDefs=2, totalClaimsRcvd=1) + totalClaimDefs=2, totalClaimsRcvd=1) do("show claim {name}", - within=2, - expect=showJobCertClaimOut, - mapper=jobCertificateClaimMap) + within=2, + expect=showJobCertClaimOut, + mapper=jobCertificateClaimMap) checkWalletStates(aliceCli, totalLinks=2, totalAvailableClaims=2, - totalCredDefs=2, totalClaimsRcvd=1) + totalClaimDefs=2, totalClaimsRcvd=1) @pytest.fixture(scope="module") def jobCertClaimRequested(be, do, aliceCli, - jobCertificateClaimMap, reqClaimOut1, acmeIsRunning, - jobApplicationClaimSent): - + jobCertificateClaimMap, reqClaimOut1, acmeIsRunning, + jobApplicationClaimSent): def removeClaimDef(): inviter = jobCertificateClaimMap["inviter"] links = aliceCli.activeWallet.getMatchingLinks(inviter) @@ -924,18 +825,18 @@ def removeClaimDef(): aliceCli.activeWallet._claimDefs.pop((name, version, faberId)) # Removing claim def to check if it fetches the claim def again or not - removeClaimDef() + # removeClaimDef() be(aliceCli) checkWalletStates(aliceCli, totalLinks=2, totalAvailableClaims=2, - totalCredDefs=1, totalClaimsRcvd=1) + totalClaimDefs=1, totalClaimsRcvd=1) - do("request claim {name}", within=7, - expect=reqClaimOut1, - mapper=jobCertificateClaimMap) + do("request claim {name}", within=7, + expect=reqClaimOut1, + mapper=jobCertificateClaimMap) checkWalletStates(aliceCli, totalLinks=2, totalAvailableClaims=2, - totalCredDefs=2, totalClaimsRcvd=2) + totalClaimDefs=2, totalClaimsRcvd=2) def testReqJobCertClaim(jobCertClaimRequested): @@ -948,8 +849,9 @@ def testShowAcmeClaimPostReqClaim(be, do, aliceCli, rcvdJobCertClaimOut): be(aliceCli) do("show claim {name}", - expect=rcvdJobCertClaimOut, - mapper=jobCertificateClaimValueMap) + expect=rcvdJobCertClaimOut, + mapper=jobCertificateClaimValueMap, + within=3) @pytest.fixture(scope="module") @@ -959,12 +861,12 @@ def thriftInviteLoadedByAlice(be, do, aliceCli, loadInviteOut, thriftMap, thriftWithEndpointAdded): be(aliceCli) checkWalletStates(aliceCli, totalLinks=2, totalAvailableClaims=2, - totalCredDefs=2, totalClaimsRcvd=2, within=2) + totalClaimDefs=2, totalClaimsRcvd=2, within=2) - do('load {invite}', expect=loadInviteOut, mapper=thriftMap) + do('load {invite}', expect=loadInviteOut, mapper=thriftMap) checkWalletStates(aliceCli, totalLinks=3, totalAvailableClaims=2, - totalCredDefs=2, totalClaimsRcvd=2) + totalClaimDefs=2, totalClaimsRcvd=2) return aliceCli @@ -975,7 +877,7 @@ def testAliceLoadedThriftLoanApplication(thriftInviteLoadedByAlice): def testPingThriftBeforeSync(be, do, aliceCli, thriftMap, thriftInviteLoadedByAlice): be(aliceCli) - do('ping {inviter}', expect=['Ping sent.'], mapper=thriftMap) + do('ping {inviter}', expect=['Ping sent.'], mapper=thriftMap) @pytest.fixture(scope="module") @@ -985,7 +887,7 @@ def aliceAcceptedThriftLoanApplication(be, do, aliceCli, thriftMap, thriftInviteLoadedByAlice, syncedInviteAcceptedOutWithoutClaims): checkWalletStates(aliceCli, totalLinks=3, totalAvailableClaims=2, - totalCredDefs=2, totalClaimsRcvd=2, within=2) + totalClaimDefs=2, totalClaimsRcvd=2, within=2) connectIfNotAlreadyConnected(do, connectedToTest, aliceCli, thriftMap) @@ -993,7 +895,7 @@ def aliceAcceptedThriftLoanApplication(be, do, aliceCli, thriftMap, syncedInviteAcceptedOutWithoutClaims) checkWalletStates(aliceCli, totalLinks=3, totalAvailableClaims=2, - totalCredDefs=2, totalClaimsRcvd=2) + totalClaimDefs=2, totalClaimsRcvd=2) return aliceCli @@ -1008,12 +910,12 @@ def bankBasicClaimSent(be, do, aliceCli, thriftMap, mapping.update(thriftMap) mapping["claim-req-to-match"] = "Loan-Application-Basic" checkWalletStates(aliceCli, totalLinks=3, totalAvailableClaims=2, - totalCredDefs=2, totalClaimsRcvd=2) + totalClaimDefs=2, totalClaimsRcvd=2) extraMsgs = ["Loan eligibility criteria satisfied, " "please send another claim 'Loan-Application-KYC'"] sendClaim(be, do, aliceCli, mapping, None, extraMsgs) checkWalletStates(aliceCli, totalLinks=3, totalAvailableClaims=2, - totalCredDefs=2, totalClaimsRcvd=2) + totalClaimDefs=2, totalClaimsRcvd=2) def testAliceSendBankBasicClaim(bankBasicClaimSent): @@ -1027,10 +929,10 @@ def bankKYCClaimSent(be, do, aliceCli, thriftMap, mapping.update(thriftMap) mapping["claim-req-to-match"] = "Loan-Application-KYC" checkWalletStates(aliceCli, totalLinks=3, totalAvailableClaims=2, - totalCredDefs=2, totalClaimsRcvd=2) + totalClaimDefs=2, totalClaimsRcvd=2) sendClaim(be, do, aliceCli, mapping, None) checkWalletStates(aliceCli, totalLinks=3, totalAvailableClaims=2, - totalCredDefs=2, totalClaimsRcvd=2) + totalClaimDefs=2, totalClaimsRcvd=2) def testAliceSendBankKYCClaim(bankKYCClaimSent): diff --git a/sovrin/test/cli/test_tutorial_manual.py b/sovrin/test/cli/test_tutorial_manual.py index f717121..0c028c6 100644 --- a/sovrin/test/cli/test_tutorial_manual.py +++ b/sovrin/test/cli/test_tutorial_manual.py @@ -1,18 +1,12 @@ import json import logging - import re -import uuid import pytest - -from anoncreds.protocol.cred_def_secret_key import CredDefSecretKey -from anoncreds.protocol.issuer_secret_key import IssuerSecretKey -from plenum.common.looper import Looper -from plenum.common.util import adict from plenum.test.eventually import eventually -from sovrin.agent.agent import runAgent +from anoncreds.protocol.types import ClaimDefinitionKey, ID +from sovrin.agent.agent import createAndRunAgent from sovrin.common.setup_util import Setup from sovrin.common.txn import ENDPOINT from sovrin.test.agent.acme import AcmeAgent @@ -21,16 +15,12 @@ buildThriftWallet from sovrin.test.agent.thrift import ThriftAgent from sovrin.test.cli.conftest import faberMap, acmeMap, \ - jobCertificateClaimMap, reqClaimOut1, thriftMap + thriftMap from sovrin.test.cli.helper import newCLI - -# noinspection PyUnresolvedReferences -from sovrin.test.cli.test_tutorial import poolNodesStarted, \ - faberCli as createFaberCli, aliceCli as createAliceCli, \ - acmeCli as createAcmeCli, syncInvite, acceptInvitation, \ +from sovrin.test.cli.test_tutorial import syncInvite, acceptInvitation, \ aliceRequestedTranscriptClaim, jobApplicationClaimSent, \ jobCertClaimRequested, bankBasicClaimSent, bankKYCClaimSent, \ - setPromptAndKeyring + setPromptAndKeyring, poolNodesStarted from sovrin.test.helper import TestClient concerningLogLevels = [logging.WARNING, @@ -59,91 +49,12 @@ def testGettingStartedTutorialAgainstSandbox(newGuyCLI, be, do): # TODO finish the entire set of steps -@pytest.fixture(scope="module") -def dangerousPrimes(): - """ - Hard-coded 'random' values are risky. Be careful only to use them in tests. - """ - primes = { - 'Faber': adict( - p=293672994294601538460023894424280657882248991230397936278278721070227017571960229217003029542172804429372056725385213277754094188540395813914384157706891192254644330822344382798277953427101186508616955910010980515685469918970002852483572038959508885430544201790234678752166995847136179984303153769450295059547, - q=346129266351333939705152453226207841619953213173429444538411282110012597917194461301159547344552711191280095222396141806532237180979404522416636139654540172375588671099885266296364558380028106566373280517225387715617569246539059672383418036690030219091474419102674344117188434085686103371044898029209202469967), - 'Acme': adict( - p=281510790031673293930276619603927743196841646256795847064219403348133278500884496133426719151371079182558480270299769814938220686172645009573713670952475703496783875912436235928500441867163946246219499572100554186255001186037971377948507437993345047481989113938038765221910989549806472045341069625389921020319, - q=350024478159288302454189301319318317490551219044369889911215183350615705419868722006578530322735670686148639754382100627201250616926263978453441645496880232733783587241897694734699668219445029433427409979471473248066452686224760324273968172651114901114731981044897755380965310877273130485988045688817305189839), - 'a': adict( - p=325893868236621235685694342853432128255662596084742701630702803797806397294325402126812757845898715987972301190236998259516672548030808077984482276443442275412976713276054747296981029661235027642854800917540051083248474400463038536670902017981223160527354520330405656489282463667701954783996896664500118936099, - q=329899409150429298980613370321347023124241841562420222433597820015210005147824582997389773476164551065534815591959653484448297130127431301599544100359581695361882553029421713003518248069805870532619939187184623975486004399184422683818071105282880441021638024968277532668086638742939857932672778880586683619427), - 'b': adict( - p=137625875492730856581920045634328472076227883212343498061881347876601486447638060405909079974596150020605200288974027885569380568142840230064337470172429862090037175028717471892027911829490200603632148340299661343748157395977746098316619165216971801180123723346384427006442977805419357117614536654841048327153, - q=174253041093052798701667571910226261404941393093190977718291073968460639961528934463129759692190831567048894268291904325434607975497286179225006675346517650220247705979378238167561442959756275051080641831576510902583454881786632591919424557913638530560924594456691043111974653727236685189305891774598986798393), - 'c': adict( - p=357389849121512584643721726419388406135160452970756835008680480758001408866242282133578003343373991135134728781213719599468354463325569361816295826511820663837329847190031645328446434960696872458759029613405263925650944945925366196751085870339897196326096345425833597399047277960837332356322350009300355607499, - q=342079455850237921617548729688014020508617110965411358875830852105879462998168770280383363392240034400489921463325775158131883890744417222631439331458057973915480291350325381445669998803570049794284055237511890313226991412462187061975186009312420006599271275344978245567133314578077001632811263312103952490607), - 'd': adict( - p=332615613455389134262091508687859712652487785774426521677776716415120896566798708679335127326530485021866445816872743433044403868096140973808942531141689070814490413391688989812008220219029542103111103626396452617240533570500332154989491647938292954575513848199909962636640482454800831961692930469571854354907, - q=308592107685817602231521289897109126801421013999043087644348219937686528101839031931423133087064535989998981004405625767526881272361079929581656753389852529248408155631570924202880435723027240101431800967698020779108871957584102762385017863053675835932387107449861775836674272512681907749060484758533671391899), - 'e': adict( - p=299947403925844187500938651923078192211173068457701788519569502067036416772701552518035669308026429038600822250748009608079143275300509204046811254826233303189173061188165140792583684166871078368539606776281631370482578625335542398889230742650595648099364536793795362672467496177008766400576214167133327422127, - q=338791859680567544877003808885392999712107789322239943250126285690928766099592525792021210087168148701322452232431731541465353031400141525283798283235118972010570442479326924886391399557767009048408960299131181064189730811847288626602007418659461573545176465332056986208193992177961822340945854850951115953899), - 'f': adict( - p=346463967541078361423745396498613784047214990828692629923000685322788515467207286932048730034039108353040399157798718865862699846702385221002706860869880265969986230487017456453294776067025680240648872227512660520427958147822622411074964199718344639053640014636041329357083262728740111561084581072529334579359, - q=356385654235711166808832238039575439686451868129570558862901806461689671557975173255142533498556775321652602612476364330248259499421206424271465463626100925843087762335970701868056240428020816115196887042319104157965451421065925227180961377529734290600459373750559066073564810350818411406346239135247843024739), - 'g': adict( - p=358742633929601263466592246013496459232656225007529536541515477364191843937865827547314619140514088534024397739591715210825395566978131361840478166093607458536769627096836259572559175327660795670259516897167801642240085980134037573389146904219327619628267242148829147884756904388534064759074235533350825536479, - q=349393140856141611060192393340627395693094985673582812536975188666688404411176975766290003561641364655357291063489175690355045710913446385860640551672877974252828615339277336608379543961207976234518072905153537753244366094185059055928348220455738365435922087263335071249145948055208410359126009838518089186259), - 'h': adict( - p=272350919439131518536668185723072482852926593554064019524369462343814526044511142103755498776619175126624938406364075917273451126568938668793961447374680502184029341737342100944414006903393754970664508203167093956633403100722241987837215822336410623517291152730072177767420285681479618823085013796383002848747, - q=330425081558727167183816881221812849639549278034179184998296946366372434698517744313470524347706441922103884261563320081312205956446975847646990540428520632599603039481052004311129133768975937741073190081929985021145777509205395239068144377490406341441931166594773796692669520280184282602515003107867133236639), - } - return primes - - -# TODO: Remove this, dont need this anymore -@pytest.fixture(scope="module") -def forceSecrets(dangerousPrimes): - - dp = dangerousPrimes - - for k in dp.keys(): - dp[k].used = False - - pubkeys = {} - - def _generateIssuerSecretKey_INSECURE(self, claimDef): - # if self.name not in dp: - # raise BlowUp("A test key pair for {} has not been created.". - # format(self.name)) - # pair = dp[self.name] - # if pair.used: - # raise BlowUp("A test key pair for {} has already been used.". - # format(self.name)) - # pair = next(iter(dp.values())) - pair = dp['Faber'] - csk = CredDefSecretKey(pair.p, pair.q) - pair.used = True - - # TODO we shouldn't be storing claimdefsk, we are already storing - # IssuerSecretKey which holds the ClaimDefSK - sid = self.addClaimDefSk(str(csk)) - # TODO why are we using a uuid here? The uid should be the seqNo of - # the pubkey in Sovrin - isk = IssuerSecretKey(claimDef, csk, uid=str(uuid.uuid4()), - pubkey=pubkeys.get(claimDef.key)) - if not pubkeys.get(claimDef.key): - pubkeys[claimDef.key] = isk.pubkey - return isk - - # IssuerWallet._generateIssuerSecretKey = _generateIssuerSecretKey_INSECURE - - -def testManual(forceSecrets, do, be, poolNodesStarted, poolTxnStewardData, philCLI, +def testManual(do, be, poolNodesStarted, poolTxnStewardData, philCLI, connectedToTest, nymAddedOut, attrAddedOut, - credDefAdded, issuerKeyAdded, aliceCLI, newKeyringOut, aliceMap, + claimDefAdded, issuerKeyAdded, aliceCLI, newKeyringOut, aliceMap, tdir, syncLinkOutWithEndpoint, jobCertificateClaimMap, syncedInviteAcceptedOutWithoutClaims, transcriptClaimMap, reqClaimOut, reqClaimOut1, susanCLI, susanMap): - eventually.slowFactor = 3 # Create steward and add nyms and endpoint atttributes of all agents @@ -177,19 +88,17 @@ def testManual(forceSecrets, do, be, poolNodesStarted, poolTxnStewardData, philC agentParams = [ (FaberAgent, "Faber College", faberAgentPort, - buildFaberWallet), + buildFaberWallet), (AcmeAgent, "Acme Corp", acmeAgentPort, - buildAcmeWallet), + buildAcmeWallet), (ThriftAgent, "Thrift Bank", thriftAgentPort, - buildThriftWallet) - ] + buildThriftWallet) + ] for agentCls, agentName, agentPort, buildAgentWalletFunc in \ agentParams: agentCls.getPassedArgs = lambda _: (agentPort,) - agent = runAgent(agentCls, agentName, buildAgentWalletFunc(), tdir, - agentPort, False, True, clientClass=TestClient) - philCLI.looper.add(agent) + createAndRunAgent(agentCls, agentName, buildAgentWalletFunc(), tdir, agentPort, philCLI.looper, TestClient) for p in philCLI.looper.prodables: if p.name == 'Faber College': @@ -199,23 +108,26 @@ def testManual(forceSecrets, do, be, poolNodesStarted, poolTxnStewardData, philC if p.name == 'Thrift Bank': thriftAgent = p - def checkTranscriptWritten(): + async def checkTranscriptWritten(): faberId = faberAgent.wallet.defaultId - claimDef = faberAgent.wallet.getClaimDef(key=("Transcript", - "1.2", faberId)) - assert claimDef.seqNo is not None - issuerKey = faberAgent.wallet.getIssuerPublicKey(key=(faberId, - claimDef.seqNo)) - assert issuerKey.seqNo is not None - - def checkJobCertWritten(): + claimDefId = ID(ClaimDefinitionKey("Transcript", "1.2", faberId)) + claimDef = await faberAgent.issuer.wallet.getClaimDef(claimDefId) + assert claimDef + assert claimDef.seqId + + issuerKey = faberAgent.issuer.wallet.getPublicKey(claimDefId) + assert issuerKey + + async def checkJobCertWritten(): acmeId = acmeAgent.wallet.defaultId - claimDef = acmeAgent.wallet.getClaimDef(key=("Job-Certificate", - "0.2", acmeId)) - assert claimDef.seqNo is not None - issuerKey = acmeAgent.wallet.getIssuerPublicKey(key=(acmeId, - claimDef.seqNo)) - assert issuerKey.seqNo is not None + claimDefId = ID(ClaimDefinitionKey("Job-Certificate", "0.2", acmeId)) + claimDef = await acmeAgent.issuer.wallet.getClaimDef(claimDefId) + assert claimDef + assert claimDef.seqId + + issuerKey = await acmeAgent.issuer.wallet.getPublicKey(claimDefId) + assert issuerKey + assert issuerKey.seqId philCLI.looper.run(eventually(checkTranscriptWritten, timeout=10)) philCLI.looper.run(eventually(checkJobCertWritten, timeout=10)) @@ -226,6 +138,13 @@ def executeGstFlow(name, userCLI, userMap, be, connectedToTest, do, fMap, reqClaimOut1, syncLinkOutWithEndpoint, syncedInviteAcceptedOutWithoutClaims, tMap, transcriptClaimMap): + + async def getPublicKey(wallet, claimDefId): + return await wallet.getPublicKey(claimDefId) + + async def getClaim(claimDefId): + return userCLI.agent.prover.wallet.getClaims(claimDefId) + # Start User cli be(userCLI) @@ -243,21 +162,14 @@ def executeGstFlow(name, userCLI, userMap, be, connectedToTest, do, fMap, reqClaimOut, None, # Passing None since its not used None) # Passing None since its not used - faberId = fMap['target'] - transKey = ('Transcript', '1.2', faberId) - faberIssuerKey = faberAgent.wallet.getIssuerPublicKeyForClaimDef( - faberId, claimDefKey=transKey) - userIssuerKeyForTrans = userCLI.activeWallet.\ - getIssuerPublicKeyForClaimDef(faberId, claimDefKey=transKey) - assert faberIssuerKey == userIssuerKeyForTrans - do('show claim Transcript') - # TODO - # do('show claim Transcript verbose') - cred = userCLI.activeWallet.getCredential( - 'Faber College Transcript 1.2') + faberClaimDefId = ID(ClaimDefinitionKey('Transcript', '1.2', fMap['target'])) + faberIssuerKey = userCLI.looper.run(getPublicKey(faberAgent.issuer.wallet, faberClaimDefId)) + userFaberIssuerKey = userCLI.looper.run(getPublicKey(userCLI.agent.prover.wallet, faberClaimDefId)) + assert faberIssuerKey == userFaberIssuerKey - # assert cred.issuerKeyId == faberIssuerKey.seqNo + do('show claim Transcript') + assert userCLI.looper.run(getClaim(faberClaimDefId)) # Accept acme do('load sample/acme-job-application.sovrin') @@ -276,21 +188,13 @@ def executeGstFlow(name, userCLI, userMap, be, connectedToTest, do, fMap, jobCertClaimRequested(be, do, userCLI, jobCertificateClaimMap, reqClaimOut1, None, None) - acmeId = aMap['target'] - certKey = ('Job-Certificate', '0.2', acmeId) - acmeIssuerKey = acmeAgent.wallet.getIssuerPublicKeyForClaimDef( - acmeId, claimDefKey=certKey) - userIssuerKeyForCert = userCLI.activeWallet. \ - getIssuerPublicKeyForClaimDef(acmeId, claimDefKey=certKey) - assert acmeIssuerKey == userIssuerKeyForCert + acmeClaimDefId = ID(ClaimDefinitionKey('Job-Certificate', '0.2', aMap['target'])) + acmeIssuerKey = userCLI.looper.run(getPublicKey(acmeAgent.issuer.wallet, acmeClaimDefId)) + userAcmeIssuerKey = userCLI.looper.run(getPublicKey(userCLI.agent.prover.wallet, acmeClaimDefId)) + assert acmeIssuerKey == userAcmeIssuerKey do('show claim Job-Certificate') - # TODO - # do('show claim Transcript verbose') - cred = userCLI.activeWallet.getCredential( - 'Acme Corp Job-Certificate 0.2') - - assert cred.issuerKeyId == acmeIssuerKey.seqNo + assert userCLI.looper.run(getClaim(acmeClaimDefId)) # Accept thrift do('load sample/thrift-loan-application.sovrin') @@ -298,15 +202,16 @@ def executeGstFlow(name, userCLI, userMap, be, connectedToTest, do, fMap, syncedInviteAcceptedOutWithoutClaims) # Send claims bankBasicClaimSent(be, do, userCLI, tMap, None) - assert acmeIssuerKey == thriftAgent.wallet.\ - getIssuerPublicKeyForClaimDef(acmeId, claimDefKey=certKey) + + thriftAcmeIssuerKey = userCLI.looper.run(getPublicKey(thriftAgent.issuer.wallet, acmeClaimDefId)) + assert acmeIssuerKey == thriftAcmeIssuerKey passed = False try: bankKYCClaimSent(be, do, userCLI, tMap, None) passed = True except: - assert faberIssuerKey == thriftAgent.wallet. \ - getIssuerPublicKeyForClaimDef(faberId, claimDefKey=transKey) + thriftFaberIssuerKey = userCLI.looper.run(getPublicKey(thriftAgent.issuer.wallet, faberClaimDefId)) + assert faberIssuerKey == thriftFaberIssuerKey assert passed executeGstFlow("Alice", aliceCLI, aliceMap, be, connectedToTest, do, fMap, diff --git a/sovrin/test/conftest.py b/sovrin/test/conftest.py index 30ed724..b0cc851 100644 --- a/sovrin/test/conftest.py +++ b/sovrin/test/conftest.py @@ -1,9 +1,10 @@ +from config.config import cmod + from sovrin.common import strict_types # typecheck during tests strict_types.defaultShouldCheck = True - import pytest from ledger.compact_merkle_tree import CompactMerkleTree @@ -11,12 +12,10 @@ from ledger.serializers.compact_serializer import CompactSerializer from plenum.common.looper import Looper -from plenum.common.plugin_helper import loadPlugins from plenum.common.signer_simple import SimpleSigner from plenum.common.txn import VERKEY from plenum.test.plugin.helper import getPluginPath from sovrin.client.wallet.wallet import Wallet -from sovrin.common.plugin_helper import writeAnonCredPlugin from sovrin.common.txn import STEWARD, NYM, SPONSOR from sovrin.common.txn import TXN_TYPE, TARGET_NYM, TXN_ID, ROLE, \ getTxnOrderedFields @@ -25,21 +24,41 @@ from sovrin.test.helper import genTestClient, createNym, addUser, TestNode, \ makePendingTxnsRequest, buildStewardClient +primes = { + "prime1": + (cmod.integer( + 157329491389375793912190594961134932804032426403110797476730107804356484516061051345332763141806005838436304922612495876180233509449197495032194146432047460167589034147716097417880503952139805241591622353828629383332869425029086898452227895418829799945650973848983901459733426212735979668835984691928193677469), + cmod.integer( + 151323892648373196579515752826519683836764873607632072057591837216698622729557534035138587276594156320800768525825023728398410073692081011811496168877166664537052088207068061172594879398773872352920912390983199416927388688319207946493810449203702100559271439586753256728900713990097168484829574000438573295723)) + , "prime2": + (cmod.integer( + 150619677884468353208058156632953891431975271416620955614548039937246769610622017033385394658879484186852231469238992217246264205570458379437126692055331206248530723117202131739966737760399755490935589223401123762051823602343810554978803032803606907761937587101969193241921351011430750970746500680609001799529), + cmod.integer( + 171590857568436644992359347719703764048501078398666061921719064395827496970696879481740311141148273607392657321103691543916274965279072000206208571551864201305434022165176563363954921183576230072812635744629337290242954699427160362586102068962285076213200828451838142959637006048439307273563604553818326766703)) +} + + +@pytest.fixture(scope="module") +def primes1(): + P_PRIME1, Q_PRIME1 = primes.get("prime1") + return dict(p_prime=P_PRIME1, q_prime=Q_PRIME1) + + +@pytest.fixture(scope="module") +def primes2(): + P_PRIME2, Q_PRIME2 = primes.get("prime2") + return dict(p_prime=P_PRIME2, q_prime=Q_PRIME2) + + # noinspection PyUnresolvedReferences from plenum.test.conftest import tdir, counter, nodeReg, up, ready, \ whitelist, concerningLogLevels, logcapture, tconf, keySharedNodes, \ startedNodes, tdirWithDomainTxns, txnPoolNodeSet, poolTxnData, dirName, \ - poolTxnNodeNames,allPluginsPath, tdirWithNodeKeepInited, tdirWithPoolTxns, \ + poolTxnNodeNames, allPluginsPath, tdirWithNodeKeepInited, tdirWithPoolTxns, \ poolTxnStewardData, poolTxnStewardNames, getValueFromModule, \ txnPoolNodesLooper -@pytest.fixture(scope="module", autouse=True) -def anonCredPluginFileCreated(tdir): - writeAnonCredPlugin(tdir, reloadTestModules=True) - loadPlugins(tdir) - - @pytest.fixture(scope="module") def allPluginsPath(): return [getPluginPath('stats_consumer')] @@ -74,7 +93,7 @@ def genesisTxns(stewardWallet: Wallet): TXN_ID: "9c86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b", ROLE: STEWARD, VERKEY: stewardWallet.getVerkey() - },] + }, ] @pytest.fixture(scope="module") @@ -174,12 +193,12 @@ def addedSponsor(nodeSet, steward, stewardWallet, looper, @pytest.fixture(scope="module") def userWalletA(nodeSet, addedSponsor, sponsorWallet, looper, sponsor): - return addUser(looper, sponsor, sponsorWallet, 'userA') + return addUser(looper, sponsor, sponsorWallet, 'userA', useDid=False) @pytest.fixture(scope="module") def userWalletB(nodeSet, addedSponsor, sponsorWallet, looper, sponsor): - return addUser(looper, sponsor, sponsorWallet, 'userB') + return addUser(looper, sponsor, sponsorWallet, 'userB', useDid=False) @pytest.fixture(scope="module") @@ -202,6 +221,16 @@ def userClientA(nodeSet, userWalletA, looper, tdir): return u +@pytest.fixture(scope="module") +def userClientB(nodeSet, userWalletB, looper, tdir): + u, _ = genTestClient(nodeSet, tmpdir=tdir, usePoolLedger=True) + u.registerObserver(userWalletB.handleIncomingReply) + looper.add(u) + looper.run(u.ensureConnectedToNodes()) + makePendingTxnsRequest(u, userWalletB) + return u + + def pytest_assertrepr_compare(op, left, right): if isinstance(left, str) and isinstance(right, str): if op in ('in', 'not in'): diff --git a/sovrin/test/did/conftest.py b/sovrin/test/did/conftest.py index 4655b2b..84a836b 100644 --- a/sovrin/test/did/conftest.py +++ b/sovrin/test/did/conftest.py @@ -1,3 +1,4 @@ +import base58 import pytest from sovrin.client.wallet.wallet import Wallet @@ -23,4 +24,11 @@ def abbrevVerkey(wallet, abbrevIdr): @pf def noKeyIdr(wallet): - return wallet.addIdentifier(vertype='none').identifier + idr = base58.b58encode(b'1'*16) + return wallet.addIdentifier(identifier=idr)[0] + + +@pf +def fullKeyIdr(wallet): + idr = base58.b58encode(b'2'*16) + return wallet.addIdentifier(identifier=idr)[0] diff --git a/sovrin/test/did/helper.py b/sovrin/test/did/helper.py new file mode 100644 index 0000000..d7d9665 --- /dev/null +++ b/sovrin/test/did/helper.py @@ -0,0 +1,73 @@ +import base58 +from plenum.common.signer_did import DidSigner +from plenum.common.verifier import DidVerifier +from plenum.test.eventually import eventually +from plenum.test.helper import assertEquality + +from sovrin.common.identity import Identity + +MsgForSigning = {'sender': 'Mario', 'msg': 'Lorem ipsum'} + + +def signMsg(wallet, idr): + return wallet.signMsg(MsgForSigning, identifier=idr) + + +def verifyMsg(verifier, sig): + sig = base58.b58decode(sig) + return verifier.verifyMsg(sig, MsgForSigning) + + +def chkVerifyForRetrievedIdentity(signerWallet, verifierWallet, idr): + sig = signMsg(signerWallet, idr) + verkey = verifierWallet.getIdentity(idr).verkey + assert verifyMsg(DidVerifier(verkey, idr), sig) + + +def updateWalletIdrWithFullKeySigner(wallet, idr): + newSigner = DidSigner(identifier=idr) + wallet.updateSigner(idr, newSigner) + assertEquality(newSigner.verkey, wallet.getVerkey(idr)) + checkFullVerkeySize(wallet.getVerkey(idr)) + return newSigner.verkey + + +def updateSovrinIdrWithFullKey(looper, senderWallet, senderClient, ownerWallet, + idr, fullKey): + idy = Identity(identifier=idr, verkey=fullKey) + senderWallet.updateSponsoredIdentity(idy) + # TODO: What if the request fails, there must be some rollback mechanism + assert senderWallet.getSponsoredIdentity(idr).seqNo is None + reqs = senderWallet.preparePending() + senderClient.submitReqs(*reqs) + + def chk(): + assert senderWallet.getSponsoredIdentity(idr).seqNo is not None + + looper.run(eventually(chk, retryWait=1, timeout=5)) + return ownerWallet + + +def fetchFullVerkeyFromSovrin(looper, senderWallet, senderClient, ownerWallet, + idr): + identity = Identity(identifier=idr) + req = senderWallet.requestIdentity(identity, sender=senderWallet.defaultId) + senderClient.submitReqs(req) + + def chk(): + retrievedVerkey = senderWallet.getIdentity(idr).verkey + assertEquality(retrievedVerkey, ownerWallet.getVerkey(idr)) + checkFullVerkeySize(retrievedVerkey) + looper.run(eventually(chk, retryWait=1, timeout=5)) + + +def checkAbbrVerkeySize(verkey): + # A base58 encoding of 32 bytes string can be either 44 bytes or 43 bytes, + # since the did takes first 22 bytes, abbreviated verkey will take + # remaining 22 or 21 characters + assert len(verkey) == 23 or len(verkey) == 22 + + +def checkFullVerkeySize(verkey): + # A base58 encoding of 32 bytes string can be either 44 bytes or 43 bytes. + assert len(verkey) == 44 or len(verkey) == 43 diff --git a/sovrin/test/did/test_did_with_abbreviated_verkey.py b/sovrin/test/did/test_did_with_abbreviated_verkey.py index 115fa84..26d8b4e 100644 --- a/sovrin/test/did/test_did_with_abbreviated_verkey.py +++ b/sovrin/test/did/test_did_with_abbreviated_verkey.py @@ -1,27 +1,4 @@ """ -Empty verkey tests - Add a nym (16 byte, base58) without a verkey (Form 2). - { type: NYM, dest: } - Retrieve the verkey. - { type: GET_NYM, dest: } - Change verkey to new verkey (32 byte) - { type: NYM, dest: , verkey: } - Retrieve new verkey - { type: GET_NYM, dest: } - Verify a signature from this identifier with the new verkey - -Full verkey tests - Add a nym (16 byte, base58) with a full verkey (32 byte, base58) (Form 1) - { type: NYM, dest: , verkey: <32byte key> } - Retrieve the verkey. - { type: GET_NYM, dest: } - Verify a signature from this identifier - Change a verkey for a nym with a full verkey. - { type: NYM, dest: , verkey: <32byte ED25519 key> } - Retrieve new verkey - { type: GET_NYM, dest: } - Verify a signature from this identifier with the new verkey - Abbreviated verkey tests Add a nym (16 byte, base58) with an abbreviated verkey (‘~’ with 16 bytes, base58) (Form 3) { type: NYM, dest: , verkey: ~<16byte abbreviated key> } @@ -33,44 +10,97 @@ Retrieve new verkey { type: GET_NYM, dest: } Verify a signature from this identifier with the new verkey - -DID Objects tests - Store a DID object - Retrieve a DID object - Change a whole DID object - Update just a portion of a DID object - -DID forms tests - Allow for identifiers that have the ‘did:sovrin:’ prefix - did:sovrin:<16 byte, base58> - Don’t store the prefix - Allow for identifiers that omit the ‘did:sovrin:’ prefix - <16 byte, base58> - Allow for legacy cryptonyms - Test that a 32-byte identifier is assumed to be a cryptonym, and the first 16 bytes are the identifier, and the last 16 bytes are the abbreviated verkey, and it is stored that way - Any other forms are rejected. """ -import pytest +from plenum.common.signer_did import DidSigner +from plenum.test.eventually import eventually +from plenum.test.helper import assertLength, assertEquality + +from sovrin.common.identity import Identity +from sovrin.test.did.conftest import pf +from sovrin.test.did.helper import chkVerifyForRetrievedIdentity, \ + updateWalletIdrWithFullKeySigner, updateSovrinIdrWithFullKey, \ + fetchFullVerkeyFromSovrin, checkAbbrVerkeySize +from sovrin.test.helper import createNym + + +@pf +def didAddedWithAbbrvVerkey(addedSponsor, looper, sponsor, sponsorWallet, + wallet, abbrevIdr): + """{ type: NYM, dest: }""" + createNym(looper, abbrevIdr, sponsor, sponsorWallet, + verkey=wallet.getVerkey(abbrevIdr)) + return wallet + -ni = pytest.mark.skip("Not yet implemented") +@pf +def newAbbrvKey(wallet, abbrevIdr): + newSigner = DidSigner(identifier=abbrevIdr) + wallet.updateSigner(abbrevIdr, newSigner) + assertEquality(newSigner.verkey, wallet.getVerkey(abbrevIdr)) + return newSigner.verkey + + +@pf +def newFullKey(wallet, abbrevIdr): + return updateWalletIdrWithFullKeySigner(wallet, abbrevIdr) + + +@pf +def didUpdatedWithFullVerkey(didAddedWithAbbrvVerkey, looper, sponsor, + sponsorWallet, abbrevIdr, newFullKey, wallet): + """{ type: NYM, dest: , verkey: }""" + updateSovrinIdrWithFullKey(looper, sponsorWallet, sponsor, wallet, + abbrevIdr, newFullKey) + + +@pf +def newVerkeyFetched(didAddedWithAbbrvVerkey, looper, sponsor, sponsorWallet, + abbrevIdr, wallet): + """{ type: GET_NYM, dest: }""" + fetchFullVerkeyFromSovrin(looper, sponsorWallet, sponsor, wallet, + abbrevIdr) def testNewIdentifierInWalletIsDid(abbrevIdr): - assert len(abbrevIdr) == 22 + assertLength(abbrevIdr, 22) def testDefaultVerkeyIsAbbreviated(abbrevVerkey): - verkeySize = len(abbrevVerkey) # A base58 encoding of 32 bytes string can be either 44 bytes or 43 bytes, # since the did takes first 22 bytes, abbreviated verkey will take # remaining 22 or 21 characters - assert verkeySize == 23 or verkeySize == 22 + checkAbbrVerkeySize(abbrevVerkey) assert abbrevVerkey[0] == '~' -@ni -def testRetrieveEmptyVerkey(): +def testAddDidWithVerkey(didAddedWithAbbrvVerkey): + pass + + +def testRetrieveAbbrvVerkey(didAddedWithAbbrvVerkey, looper, sponsor, + sponsorWallet, wallet, abbrevIdr): """{ type: GET_NYM, dest: }""" - raise NotImplementedError + identity = Identity(identifier=abbrevIdr) + req = sponsorWallet.requestIdentity(identity, + sender=sponsorWallet.defaultId) + sponsor.submitReqs(req) + + def chk(): + retrievedVerkey = sponsorWallet.getIdentity(abbrevIdr).verkey + assertEquality(retrievedVerkey, wallet.getVerkey(abbrevIdr)) + checkAbbrVerkeySize(retrievedVerkey) + looper.run(eventually(chk, retryWait=1, timeout=5)) + chkVerifyForRetrievedIdentity(wallet, sponsorWallet, abbrevIdr) + + +def testChangeVerkeyToNewVerkey(didUpdatedWithFullVerkey): + pass + + +def testRetrieveChangedVerkey(didUpdatedWithFullVerkey, newVerkeyFetched): + pass +def testVerifySigWithChangedVerkey(didUpdatedWithFullVerkey, newVerkeyFetched, + sponsorWallet, abbrevIdr, wallet): + chkVerifyForRetrievedIdentity(wallet, sponsorWallet, abbrevIdr) diff --git a/sovrin/test/did/test_did_with_full_verkey.py b/sovrin/test/did/test_did_with_full_verkey.py new file mode 100644 index 0000000..eb5c01a --- /dev/null +++ b/sovrin/test/did/test_did_with_full_verkey.py @@ -0,0 +1,85 @@ +""" +Full verkey tests + Add a nym (16 byte, base58) with a full verkey (32 byte, base58) (Form 1) + { type: NYM, dest: , verkey: <32byte key> } + Retrieve the verkey. + { type: GET_NYM, dest: } + Verify a signature from this identifier + Change a verkey for a nym with a full verkey. + { type: NYM, dest: , verkey: <32byte ED25519 key> } + Retrieve new verkey + { type: GET_NYM, dest: } + Verify a signature from this identifier with the new verkey +""" +from plenum.test.eventually import eventually + +from sovrin.common.identity import Identity +from sovrin.test.did.conftest import pf +from sovrin.test.did.helper import chkVerifyForRetrievedIdentity, \ + updateWalletIdrWithFullKeySigner, updateSovrinIdrWithFullKey, \ + fetchFullVerkeyFromSovrin, checkFullVerkeySize +from sovrin.test.helper import createNym + + +@pf +def didAddedWithFullVerkey(addedSponsor, looper, sponsor, sponsorWallet, + wallet, fullKeyIdr): + """{ type: NYM, dest: }""" + createNym(looper, fullKeyIdr, sponsor, sponsorWallet, + verkey=wallet.getVerkey(fullKeyIdr)) + return wallet + + +@pf +def newFullKey(wallet, fullKeyIdr): + return updateWalletIdrWithFullKeySigner(wallet, fullKeyIdr) + + +@pf +def didUpdatedWithFullVerkey(didAddedWithFullVerkey, looper, sponsor, + sponsorWallet, fullKeyIdr, newFullKey, wallet): + """{ type: NYM, dest: , verkey: }""" + updateSovrinIdrWithFullKey(looper, sponsorWallet, sponsor, wallet, + fullKeyIdr, newFullKey) + + +@pf +def newVerkeyFetched(didAddedWithFullVerkey, looper, sponsor, sponsorWallet, + fullKeyIdr, wallet): + """{ type: GET_NYM, dest: }""" + fetchFullVerkeyFromSovrin(looper, sponsorWallet, sponsor, wallet, + fullKeyIdr) + + +def testAddDidWithVerkey(didAddedWithFullVerkey): + pass + + +def testRetrieveFullVerkey(didAddedWithFullVerkey, looper, sponsor, + sponsorWallet, wallet, fullKeyIdr): + """{ type: GET_NYM, dest: }""" + identity = Identity(identifier=fullKeyIdr) + req = sponsorWallet.requestIdentity(identity, + sender=sponsorWallet.defaultId) + sponsor.submitReqs(req) + + def chk(): + retrievedVerkey = sponsorWallet.getIdentity(fullKeyIdr).verkey + assert retrievedVerkey == wallet.getVerkey(fullKeyIdr) + checkFullVerkeySize(retrievedVerkey) + + looper.run(eventually(chk, retryWait=1, timeout=5)) + chkVerifyForRetrievedIdentity(wallet, sponsorWallet, fullKeyIdr) + + +def testChangeVerkeyToNewVerkey(didUpdatedWithFullVerkey): + pass + + +def testRetrieveChangedVerkey(didUpdatedWithFullVerkey, newVerkeyFetched): + pass + + +def testVerifySigWithChangedVerkey(didUpdatedWithFullVerkey, newVerkeyFetched, + sponsorWallet, fullKeyIdr, wallet): + chkVerifyForRetrievedIdentity(wallet, sponsorWallet, fullKeyIdr) diff --git a/sovrin/test/did/test_did_with_no_verkey.py b/sovrin/test/did/test_did_with_no_verkey.py index f0ca9d7..609eb2b 100644 --- a/sovrin/test/did/test_did_with_no_verkey.py +++ b/sovrin/test/did/test_did_with_no_verkey.py @@ -10,30 +10,6 @@ { type: GET_NYM, dest: } Verify a signature from this identifier with the new verkey -Full verkey tests - Add a nym (16 byte, base58) with a full verkey (32 byte, base58) (Form 1) - { type: NYM, dest: , verkey: <32byte key> } - Retrieve the verkey. - { type: GET_NYM, dest: } - Verify a signature from this identifier - Change a verkey for a nym with a full verkey. - { type: NYM, dest: , verkey: <32byte ED25519 key> } - Retrieve new verkey - { type: GET_NYM, dest: } - Verify a signature from this identifier with the new verkey - -Abbreviated verkey tests - Add a nym (16 byte, base58) with an abbreviated verkey (‘~’ with 16 bytes, base58) (Form 3) - { type: NYM, dest: , verkey: ~<16byte abbreviated key> } - Retrieve the verkey. - { type: GET_NYM, dest: } - Verify a signature from this identifier - Change a verkey for a nym with a full verkey. - { type: NYM, dest: , verkey: <32byte ED25519 key> } - Retrieve new verkey - { type: GET_NYM, dest: } - Verify a signature from this identifier with the new verkey - DID Objects tests Store a DID object Retrieve a DID object @@ -51,40 +27,76 @@ Any other forms are rejected. """ -import pytest -from sovrin.test.helper import addUser +from plenum.test.eventually import eventually + +from sovrin.common.identity import Identity +from sovrin.test.did.conftest import pf +from sovrin.test.did.helper import chkVerifyForRetrievedIdentity, \ + updateSovrinIdrWithFullKey +from sovrin.test.helper import createNym -ni = pytest.mark.skip("Not yet implemented") + +@pf +def didAddedWithoutVerkey(addedSponsor, looper, sponsor, sponsorWallet, + wallet, noKeyIdr): + """{ type: NYM, dest: }""" + createNym(looper, noKeyIdr, sponsor, sponsorWallet) + return wallet + +@pf +def didUpdatedWithVerkey(didAddedWithoutVerkey, looper, sponsor, + sponsorWallet, noKeyIdr, wallet): + """{ type: NYM, dest: , verkey: }""" + updateSovrinIdrWithFullKey(looper, sponsorWallet, sponsor, wallet, + noKeyIdr, wallet.getVerkey(noKeyIdr)) + + +@pf +def verkeyFetched(didUpdatedWithVerkey, looper, sponsor, sponsorWallet, + noKeyIdr, wallet): + """{ type: GET_NYM, dest: }""" + identity = Identity(identifier=noKeyIdr) + req = sponsorWallet.requestIdentity(identity, + sender=sponsorWallet.defaultId) + sponsor.submitReqs(req) + + def chk(): + assert sponsorWallet.getIdentity(noKeyIdr).verkey == wallet.getVerkey( + noKeyIdr) + + looper.run(eventually(chk, retryWait=1, timeout=5)) -@ni def testWalletCanProvideAnIdentifierWithoutAKey(wallet, noKeyIdr): - assert wallet.getverkey(noKeyIdr) is None + # TODO, Question: Why would `getVerkey` return `None` for a DID?. + assert wallet.getVerkey(noKeyIdr) -def testAddDidWithoutAVerkey(addedSponsor, looper, sponsor, sponsorWallet): - """{ type: NYM, dest: }""" - addUser(looper, sponsor, sponsorWallet, 'userA') +def testAddDidWithoutAVerkey(didAddedWithoutVerkey): + pass -@ni -def testRetrieveEmptyVerkey(): +def testRetrieveEmptyVerkey(didAddedWithoutVerkey, looper, sponsor, + sponsorWallet, noKeyIdr): """{ type: GET_NYM, dest: }""" - raise NotImplementedError + identity = Identity(identifier=noKeyIdr) + req = sponsorWallet.requestIdentity(identity, sender=sponsorWallet.defaultId) + sponsor.submitReqs(req) + def chk(): + assert sponsorWallet.getIdentity(noKeyIdr).verkey is None -@ni -def testChangeEmptyVerkeyToNewVerkey(): - """{ type: NYM, dest: , verkey: }""" - raise NotImplementedError + looper.run(eventually(chk, retryWait=1, timeout=5)) -@ni -def testRetrieveChangedVerkey(): - """{ type: GET_NYM, dest: }""" - raise NotImplementedError +def testChangeEmptyVerkeyToNewVerkey(didUpdatedWithVerkey): + pass + + +def testRetrieveChangedVerkey(didUpdatedWithVerkey, verkeyFetched): + pass -@ni -def testVerifySigWithChangedVerkey(): - raise NotImplementedError +def testVerifySigWithChangedVerkey(didUpdatedWithVerkey, verkeyFetched, + sponsorWallet, noKeyIdr, wallet): + chkVerifyForRetrievedIdentity(wallet, sponsorWallet, noKeyIdr) diff --git a/sovrin/test/helper.py b/sovrin/test/helper.py index 2cb3d94..6b9f3cd 100644 --- a/sovrin/test/helper.py +++ b/sovrin/test/helper.py @@ -2,22 +2,15 @@ import json import os import shutil -import uuid from contextlib import ExitStack from typing import Iterable, Union, Tuple import pyorient -from plenum.test.test_node import checkNodesAreReady, TestNodeCore -from plenum.test.test_node import checkNodesConnected -from plenum.test.test_stack import StackedTester, TestStack - -from anoncreds.protocol.cred_def_secret_key import CredDefSecretKey -from anoncreds.protocol.issuer_secret_key import IssuerSecretKey -from anoncreds.test.conftest import staticPrimes from plenum.common.log import getlogger from plenum.common.looper import Looper +from plenum.common.signer_did import DidSigner from plenum.common.signer_simple import SimpleSigner -from plenum.common.txn import REQACK, NAME, VERSION, TYPE +from plenum.common.txn import REQACK from plenum.common.types import HA, Identifier from plenum.common.util import getMaxFailures, runall from plenum.persistence import orientdb_store @@ -28,16 +21,17 @@ checkLastClientReqForNode, buildCompletedTxnFromReply from plenum.test.test_client import genTestClient as genPlenumTestClient, \ genTestClientProvider as genPlenumTestClientProvider -from plenum.test.pool_transactions.helper import buildPoolClientAndWallet +from plenum.test.test_node import checkNodesAreReady, TestNodeCore +from plenum.test.test_node import checkNodesConnected +from plenum.test.test_stack import StackedTester, TestStack from plenum.test.testable import Spyable + from sovrin.client.client import Client from sovrin.client.wallet.attribute import LedgerStore, Attribute -from sovrin.client.wallet.claim_def import ClaimDef, IssuerPubKey from sovrin.client.wallet.wallet import Wallet -from sovrin.common.identity import Identity -from sovrin.common.txn import ATTRIB, TARGET_NYM, TXN_TYPE, TXN_ID, GET_NYM, \ - ATTR_NAMES from sovrin.common.config_util import getConfig +from sovrin.common.identity import Identity +from sovrin.common.txn import ATTRIB, TARGET_NYM, TXN_TYPE, TXN_ID, GET_NYM from sovrin.server.node import Node logger = getlogger() @@ -410,10 +404,12 @@ def check(): looper.run(eventually(check, timeout=4)) -def addUser(looper, creatorClient, creatorWallet, name): +def addUser(looper, creatorClient, creatorWallet, name, useDid=True, + addVerkey=True): wallet = Wallet(name) - idr, _ = wallet.addIdentifier() - verkey = wallet.getVerkey(idr) + signer = DidSigner() if useDid else SimpleSigner() + idr, _ = wallet.addIdentifier(signer=signer) + verkey = wallet.getVerkey(idr) if addVerkey else None createNym(looper, idr, creatorClient, creatorWallet, verkey=verkey) return wallet @@ -510,40 +506,6 @@ def addRawAttribute(looper, client, wallet, name, value, dest=None, addAttributeAndCheck(looper, client, wallet, attrib) -def addClaimDefAndIssuerKeys(looper, agent, claimDefToBeAdded): - csk = CredDefSecretKey(*staticPrimes().get("prime1")) - sid = agent.wallet.addClaimDefSk(str(csk)) - claimDef = ClaimDef(seqNo=None, - attrNames=claimDefToBeAdded[ATTR_NAMES], - name=claimDefToBeAdded[NAME], - version=claimDefToBeAdded[VERSION], - origin=agent.wallet.defaultId, - typ=claimDefToBeAdded[TYPE]) - agent.wallet.addClaimDef(claimDef) - reqs = agent.wallet.preparePending() - agent.client.submitReqs(*reqs) - - def chk(): - assert claimDef.seqNo is not None - - looper.run(eventually(chk, retryWait=1, timeout=10)) - - isk = IssuerSecretKey(claimDef, csk, uid=str(uuid.uuid4())) - agent.wallet.addIssuerSecretKey(isk) - ipk = IssuerPubKey(N=isk.PK.N, R=isk.PK.R, S=isk.PK.S, Z=isk.PK.Z, - claimDefSeqNo=claimDef.seqNo, - secretKeyUid=isk.pubkey.uid, origin=agent.wallet.defaultId) - agent.wallet.addIssuerPublicKey(ipk) - reqs = agent.wallet.preparePending() - agent.client.submitReqs(*reqs) - - key = (agent.wallet.defaultId, claimDef.seqNo) - - def chk(): - assert agent.wallet.getIssuerPublicKey(key).seqNo is not None - - looper.run(eventually(chk, retryWait=1, timeout=10)) - return claimDef.seqNo, ipk.seqNo def buildStewardClient(looper, tdir, stewardWallet): diff --git a/sovrin/test/persistence/test_identity_graph.py b/sovrin/test/persistence/test_identity_graph.py index 630b1af..62b4d82 100644 --- a/sovrin/test/persistence/test_identity_graph.py +++ b/sovrin/test/persistence/test_identity_graph.py @@ -1,7 +1,8 @@ +import time +from datetime import datetime, timedelta + from ledger.util import F from plenum.common.txn import TXN_TIME -from datetime import datetime, timedelta -import time from sovrin.persistence.identity_graph import IdentityGraph diff --git a/sovrin/test/test_client.py b/sovrin/test/test_client.py index 616792f..0e27267 100644 --- a/sovrin/test/test_client.py +++ b/sovrin/test/test_client.py @@ -225,6 +225,7 @@ def check(): assert(count == len(nodeSet)) +@pytest.mark.skipif(True, reason="NYM transaction now used to update too") def testAddNymsInQuickSuccession(nymsAddedInQuickSuccession): pass diff --git a/sovrin/test/test_sponsor.py b/sovrin/test/test_sponsor.py index 78292be..7e7ceed 100644 --- a/sovrin/test/test_sponsor.py +++ b/sovrin/test/test_sponsor.py @@ -2,8 +2,8 @@ import libnacl.secret import pytest - from plenum.common.util import randomString + from sovrin.common.txn import NYM, IDPROOF, newTxn from sovrin.test.helper import Scenario