Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

integrate revocation into present-proof protocol; test coverage #429

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 24 additions & 26 deletions aries_cloudagent/protocols/present_proof/v1_0/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,11 @@ async def create_bound_request(
presentation_exchange_record.presentation_proposal_dict
)
).presentation_proposal.indy_proof_request(
name=name, version=version, nonce=nonce
name=name,
version=version,
nonce=nonce,
ledger=await self.context.inject(BaseLedger)
)

presentation_request_message = PresentationRequest(
comment=comment,
request_presentations_attach=[
Expand Down Expand Up @@ -258,7 +260,6 @@ async def create_presentation(
# Get all credentials for this presentation
holder: BaseHolder = await self.context.inject(BaseHolder)
credentials = {}
non_revoked_timespan = {}

# extract credential ids and non_revoked
requested_referents = {}
Expand All @@ -273,7 +274,7 @@ async def create_presentation(
]

preds_creds = requested_credentials.get("requested_predicates", {})
req_preds = presentation_request.get("requested_attributes", {})
req_preds = presentation_request.get("requested_predicates", {})
for referent in preds_creds:
requested_referents[referent] = {
"cred_id": preds_creds[referent]["cred_id"],
Expand Down Expand Up @@ -309,7 +310,7 @@ async def create_presentation(
credential_definition_id
] = await ledger.get_credential_definition(credential_definition_id)

if "rev_reg_id" in credential and credential["rev_reg_id"] is not None:
if credential.get("rev_reg_id"):
revocation_registry_id = credential["rev_reg_id"]
if revocation_registry_id not in revocation_registries:
revocation_registries[
Expand All @@ -318,40 +319,40 @@ async def create_presentation(
await ledger.get_revoc_reg_def(revocation_registry_id), True
)

# Get delta with timespan defined in "non_revoked"
# Get delta with non-revocation interval defined in "non_revoked"
# of the presentation request or attributes
current_timestamp = int(time.time())
non_revoked_timespan = presentation_exchange_record.presentation_request.get(
"non_revoked", None

non_revoc_interval = {
"from": 0,
"to": current_timestamp
}
non_revoc_interval.update(
presentation_exchange_record.presentation_request.get("non_revoked", {})
)

revoc_reg_deltas = {}
async with ledger:
for referented in requested_referents.values():
credential_id = referented["cred_id"]
if "rev_reg_id" not in credentials[credential_id]:
if not credentials[credential_id].get("rev_reg_id"):
continue

rev_reg_id = credentials[credential_id]["rev_reg_id"]
referent_non_revoked_timespan = referented.get(
"non_revoked", non_revoked_timespan
referent_non_revoc_interval = referented.get(
"non_revoked", non_revoc_interval
)

if referent_non_revoked_timespan:
if "from" not in non_revoked_timespan:
non_revoked_timespan["from"] = 0
if "to" not in non_revoked_timespan:
non_revoked_timespan["to"] = current_timestamp

if referent_non_revoc_interval:
key = (
f"{rev_reg_id}_{non_revoked_timespan['from']}_"
f"{non_revoked_timespan['to']}"
f"{rev_reg_id}_{non_revoc_interval['from']}_"
f"{non_revoc_interval['to']}"
)
if key not in revoc_reg_deltas:
(delta, delta_timestamp) = await ledger.get_revoc_reg_delta(
rev_reg_id,
non_revoked_timespan["from"],
non_revoked_timespan["to"],
non_revoc_interval["from"],
non_revoc_interval["to"],
)
revoc_reg_deltas[key] = (
rev_reg_id,
Expand Down Expand Up @@ -536,16 +537,13 @@ async def verify_presentation(
identifier["cred_def_id"]
)

if "rev_reg_id" in identifier and identifier["rev_reg_id"] is not None:
if identifier.get("rev_reg_id"):
if identifier["rev_reg_id"] not in rev_reg_defs:
rev_reg_defs[
identifier["rev_reg_id"]
] = await ledger.get_revoc_reg_def(identifier["rev_reg_id"])

if (
"timestamp" in identifier
and identifier["timestamp"] is not None
):
if identifier.get("timestamp"):
(
found_rev_reg_entry,
found_timestamp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
from ......messaging.models.base import BaseModel, BaseModelSchema
from ......messaging.util import canon
from ......messaging.valid import INDY_CRED_DEF_ID, INDY_PREDICATE
from ......revocation.models.indy import NonRevocationInterval
from ......wallet.util import b64_to_str


from ...message_types import PRESENTATION_PREVIEW
from ...util.predicate import Predicate

Expand Down Expand Up @@ -283,7 +285,7 @@ async def indy_proof_request(
version: str = None,
nonce: str = None,
ledger: IndyLedger = None,
timestamps: Mapping[str, int] = None,
non_revoc_intervals: Mapping[str, NonRevocationInterval] = None,
) -> dict:
"""
Return indy proof request corresponding to presentation preview.
Expand All @@ -295,23 +297,26 @@ async def indy_proof_request(
version: version for proof request
nonce: nonce for proof request
ledger: ledger with credential definitions, to check for revocation support
timestamps: dict mapping cred def ids to non-revocation
timestamps to use (default current time where applicable)
non_revoc_intervals: non-revocation interval to use per cred def id
where applicable (default from and to the current time if applicable)

Returns:
Indy proof request dict.

"""

def non_revo(cred_def_id: str):
"""Non-revocation timestamp to use for input cred def id."""
def non_revoc(cred_def_id: str) -> NonRevocationInterval:
"""Non-revocation interval to use for input cred def id."""

nonlocal epoch_now
nonlocal timestamps
nonlocal non_revoc_intervals

return (timestamps or {}).get(cred_def_id, epoch_now)
return (non_revoc_intervals or {}).get(
cred_def_id,
NonRevocationInterval(epoch_now, epoch_now)
)

epoch_now = int(time()) # TODO: take cred_def_id->timestamp here, default now
epoch_now = int(time())

proof_req = {
"name": name or "proof-request",
Expand All @@ -331,14 +336,13 @@ def non_revo(cred_def_id: str):
}
else:
cd_id = attr_spec.cred_def_id
revo_support = bool(
ledger
and await ledger.get_credential_definition(cd_id)["value"][
"revocation"
]
revoc_support = bool(
ledger and (
await ledger.get_credential_definition(cd_id)
)["value"]["revocation"]
)

timestamp = non_revo(attr_spec.cred_def_id)
interval = non_revoc(cd_id) if revoc_support else None

if attr_spec.referent:
if attr_spec.referent in attr_specs_names:
Expand All @@ -350,9 +354,9 @@ def non_revo(cred_def_id: str):
"names": [canon(attr_spec.name)],
"restrictions": [{"cred_def_id": cd_id}],
**{
"non_revoked": {"from": timestamp, "to": timestamp}
"non_revoked": interval.serialize()
for _ in [""]
if revo_support
if revoc_support
},
}
else:
Expand All @@ -365,9 +369,9 @@ def non_revo(cred_def_id: str):
"name": canon(attr_spec.name),
"restrictions": [{"cred_def_id": cd_id}],
**{
"non_revoked": {"from": timestamp, "to": timestamp}
"non_revoked": interval.serialize()
for _ in [""]
if revo_support
if revoc_support
},
}
for (reft, attr_spec) in attr_specs_names.items():
Expand All @@ -380,12 +384,14 @@ def non_revo(cred_def_id: str):

for pred_spec in self.predicates:
cd_id = pred_spec.cred_def_id
revo_support = bool(
ledger
and await ledger.get_credential_definition(cd_id)["value"]["revocation"]
revoc_support = bool(
ledger and (
await ledger.get_credential_definition(cd_id)
)["value"]["revocation"]
)

timestamp = non_revo(pred_spec.cred_def_id)
interval = non_revoc(cd_id) if revoc_support else None

proof_req["requested_predicates"][
"{}_{}_{}_uuid".format(
len(proof_req["requested_predicates"]),
Expand All @@ -397,11 +403,7 @@ def non_revo(cred_def_id: str):
"p_type": pred_spec.predicate,
"p_value": pred_spec.threshold,
"restrictions": [{"cred_def_id": cd_id}],
**{
"non_revoked": {"from": timestamp, "to": timestamp}
for _ in [""]
if revo_support
},
**{"non_revoked": interval.serialize() for _ in [""] if revoc_support}
}

return proof_req
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import json
import pytest

from copy import deepcopy
from datetime import datetime, timezone
from time import time
from unittest import TestCase

import json

from asynctest import TestCase as AsyncTestCase
from asynctest import mock as async_mock

import pytest

from .......holder.indy import IndyHolder
from .......messaging.util import canon, str_to_datetime, str_to_epoch
from .......messaging.util import canon
from .......revocation.models.indy import NonRevocationInterval

from ....message_types import PRESENTATION_PREVIEW
from ....util.predicate import Predicate
Expand All @@ -22,8 +21,6 @@
)


NOW_8601 = datetime.utcnow().replace(tzinfo=timezone.utc).isoformat(" ", "seconds")
NOW_EPOCH = str_to_epoch(NOW_8601)
S_ID = {
"score": "NcYxiDXkpYi6ov5FcYDi1e:2:score:1.0",
"membership": "NcYxiDXkpYi6ov5FcYDi1e:2:membership:1.0"
Expand Down Expand Up @@ -490,6 +487,95 @@ async def test_to_indy_proof_request_self_attested(self):
for attr_spec in indy_proof_req_selfie["requested_attributes"].values()
)

@pytest.mark.asyncio
async def test_to_indy_proof_request_revo_default_interval(self):
"""Test pres preview to indy proof req with revocation support, defaults."""

canon_indy_proof_req = deepcopy(INDY_PROOF_REQ)
for spec in canon_indy_proof_req["requested_attributes"].values():
spec["name"] = canon(spec["name"])
for spec in canon_indy_proof_req["requested_predicates"].values():
spec["name"] = canon(spec["name"])

pres_preview = deepcopy(PRES_PREVIEW)
mock_ledger = async_mock.MagicMock(
get_credential_definition=async_mock.CoroutineMock(
return_value={
"value": {
"revocation": {
"...": "..."
}
}
}
)
)

indy_proof_req = await pres_preview.indy_proof_request(
**{k: INDY_PROOF_REQ[k] for k in ("name", "version", "nonce")},
ledger=mock_ledger
)

for uuid, attr_spec in indy_proof_req["requested_attributes"].items():
assert set(attr_spec.get("non_revoked", {}).keys()) == {"from", "to"}
canon_indy_proof_req["requested_attributes"][uuid]["non_revoked"] = (
attr_spec["non_revoked"]
)
for uuid, pred_spec in indy_proof_req["requested_predicates"].items():
assert set(pred_spec.get("non_revoked", {}).keys()) == {"from", "to"}
canon_indy_proof_req["requested_predicates"][uuid]["non_revoked"] = (
pred_spec["non_revoked"]
)

assert canon_indy_proof_req == indy_proof_req

@pytest.mark.asyncio
async def test_to_indy_proof_request_revo(self):
"""Test pres preview to indy proof req with revocation support, interval."""

EPOCH_NOW = int(time())
canon_indy_proof_req = deepcopy(INDY_PROOF_REQ)
for spec in canon_indy_proof_req["requested_attributes"].values():
spec["name"] = canon(spec["name"])
for spec in canon_indy_proof_req["requested_predicates"].values():
spec["name"] = canon(spec["name"])

pres_preview = deepcopy(PRES_PREVIEW)
mock_ledger = async_mock.MagicMock(
get_credential_definition=async_mock.CoroutineMock(
return_value={
"value": {
"revocation": {
"...": "..."
}
}
}
)
)

indy_proof_req = await pres_preview.indy_proof_request(
**{k: INDY_PROOF_REQ[k] for k in ("name", "version", "nonce")},
ledger=mock_ledger,
non_revoc_intervals={
CD_ID[s_id]: NonRevocationInterval(
1234567890,
EPOCH_NOW
) for s_id in S_ID
}
)

for uuid, attr_spec in indy_proof_req["requested_attributes"].items():
assert set(attr_spec.get("non_revoked", {}).keys()) == {"from", "to"}
canon_indy_proof_req["requested_attributes"][uuid]["non_revoked"] = (
attr_spec["non_revoked"]
)
for uuid, pred_spec in indy_proof_req["requested_predicates"].items():
assert set(pred_spec.get("non_revoked", {}).keys()) == {"from", "to"}
canon_indy_proof_req["requested_predicates"][uuid]["non_revoked"] = (
pred_spec["non_revoked"]
)

assert canon_indy_proof_req == indy_proof_req

@pytest.mark.asyncio
async def test_satisfaction(self):
"""Test presentation preview predicate satisfaction."""
Expand Down Expand Up @@ -523,6 +609,11 @@ def test_init(self):
"""Test initializer."""
assert PRES_PREVIEW.attributes
assert PRES_PREVIEW.predicates
assert PRES_PREVIEW.has_attr_spec(
cred_def_id=CD_ID["score"],
name="player",
value="Richie Knucklez"
)

def test_type(self):
"""Test type."""
Expand Down
Loading