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

🚧 Anoncreds verifier #1359

Draft
wants to merge 22 commits into
base: master
Choose a base branch
from
Draft
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
41 changes: 36 additions & 5 deletions app/models/verifier.py
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
from typing import Optional, Union

from aries_cloudcontroller import (
AnoncredsPresentationRequest,
DIFPresSpec,
DIFProofRequest,
IndyNonRevocationInterval,
@@ -18,6 +19,7 @@ class ProofRequestType(str, Enum):
INDY: str = "indy"
JWT: str = "jwt"
LD_PROOF: str = "ld_proof"
ANONCREDS: str = "anoncreds"


class IndyProofRequest(AcaPyIndyProofRequest):
@@ -29,17 +31,24 @@ class ProofRequestBase(BaseModel):
type: ProofRequestType = ProofRequestType.INDY
indy_proof_request: Optional[IndyProofRequest] = None
dif_proof_request: Optional[DIFProofRequest] = None
anoncreds_proof_request: Optional[AnoncredsPresentationRequest] = None

@model_validator(mode="before")
@classmethod
def check_indy_proof_request(cls, values: Union[dict, "ProofRequestBase"]):
def check_proof_request(cls, values: Union[dict, "ProofRequestBase"]):
# pydantic v2 removed safe way to get key, because `values` can be a dict or this type
if not isinstance(values, dict):
values = values.__dict__

proof_type = values.get("type")
indy_proof = values.get("indy_proof_request")
dif_proof = values.get("dif_proof_request")
anoncreds_proof = values.get("anoncreds_proof_request")

if proof_type == ProofRequestType.ANONCREDS and anoncreds_proof is None:
raise CloudApiValueError(
"anoncreds_proof_request must be populated if `anoncreds` type is selected"
)

if proof_type == ProofRequestType.INDY and indy_proof is None:
raise CloudApiValueError(
@@ -51,14 +60,25 @@ def check_indy_proof_request(cls, values: Union[dict, "ProofRequestBase"]):
"dif_proof_request must be populated if `ld_proof` type is selected"
)

if proof_type == ProofRequestType.INDY and dif_proof is not None:
if proof_type == ProofRequestType.INDY and (
dif_proof is not None or anoncreds_proof is not None
):
raise CloudApiValueError(
"Only indy_proof_request must not be populated if `indy` type is selected"
)

if proof_type == ProofRequestType.LD_PROOF and (
indy_proof is not None or anoncreds_proof is not None
):
raise CloudApiValueError(
"dif_proof_request must not be populated if `indy` type is selected"
"Only dif_proof_request must not be populated if `ld_proof` type is selected"
)

if proof_type == ProofRequestType.LD_PROOF and indy_proof is not None:
if proof_type == ProofRequestType.ANONCREDS and (
indy_proof is not None or dif_proof is not None
):
raise CloudApiValueError(
"indy_proof_request must not be populated if `ld_proof` type is selected"
"Only anoncreds_proof_request must not be populated if `anoncreds` type is selected"
)

return values
@@ -86,6 +106,8 @@ class AcceptProofRequest(ProofId, SaveExchangeRecordField):
type: ProofRequestType = ProofRequestType.INDY
indy_presentation_spec: Optional[IndyPresSpec] = None
dif_presentation_spec: Optional[DIFPresSpec] = None
# Controller uses IndyPresSpec for anoncreds
anoncreds_presentation_spec: Optional[IndyPresSpec] = None

@field_validator("indy_presentation_spec", mode="before")
@classmethod
@@ -105,6 +127,15 @@ def check_dif_presentation_spec(cls, value, values: ValidationInfo):
)
return value

@field_validator("anoncreds_presentation_spec", mode="before")
@classmethod
def check_anoncreds_proof_request(cls, value, values: ValidationInfo):
if values.data.get("type") == ProofRequestType.ANONCREDS and value is None:
raise CloudApiValueError(
"anoncreds_proof_request must be populated if `anoncreds` type is selected"
)
return value


class RejectProofRequest(ProofId):
problem_report: str = Field(
13 changes: 13 additions & 0 deletions app/services/verifier/acapy_verifier_v2.py
Original file line number Diff line number Diff line change
@@ -46,6 +46,10 @@ async def create_proof_request(
presentation_request = V20PresRequestByFormat(
dif=create_proof_request.dif_proof_request
)
elif create_proof_request.type == ProofRequestType.ANONCREDS:
presentation_request = V20PresRequestByFormat(
anoncreds=create_proof_request.anoncreds_proof_request
)
else:
raise CloudApiException(
f"Unsupported credential type: {create_proof_request.type.value}",
@@ -89,6 +93,10 @@ async def send_proof_request(
presentation_request = V20PresRequestByFormat(
dif=send_proof_request.dif_proof_request
)
elif send_proof_request.type == ProofRequestType.ANONCREDS:
presentation_request = V20PresRequestByFormat(
anoncreds=send_proof_request.anoncreds_proof_request
)
else:
raise CloudApiException(
f"Unsupported credential type: {send_proof_request.type.value}",
@@ -134,6 +142,11 @@ async def accept_proof_request(
presentation_spec = V20PresSpecByFormatRequest(
auto_remove=auto_remove, dif=accept_proof_request.dif_presentation_spec
)
elif accept_proof_request.type == ProofRequestType.ANONCREDS:
presentation_spec = V20PresSpecByFormatRequest(
auto_remove=auto_remove,
anoncreds=accept_proof_request.anoncreds_presentation_spec,
)
else:
raise CloudApiException(
f"Unsupported credential type: {accept_proof_request.type.value}",
43 changes: 39 additions & 4 deletions app/tests/models/test_verifier.py
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@

from app.models.verifier import (
AcceptProofRequest,
AnoncredsPresentationRequest,
DIFPresSpec,
IndyPresSpec,
IndyProofRequest,
@@ -19,7 +20,11 @@ def test_proof_request_base_model():
assert exc.value.detail == (
"indy_proof_request must be populated if `indy` type is selected"
)

with pytest.raises(CloudApiValueError) as exc:
ProofRequestBase(type=ProofRequestType.ANONCREDS, anoncreds_proof_request=None)
assert exc.value.detail == (
"anoncreds_proof_request must be populated if `anoncreds` type is selected"
)
with pytest.raises(CloudApiValueError) as exc:
ProofRequestBase(
type=ProofRequestType.LD_PROOF,
@@ -31,7 +36,7 @@ def test_proof_request_base_model():
),
)
assert exc.value.detail == (
"indy_proof_request must not be populated if `ld_proof` type is selected"
"Only dif_proof_request must not be populated if `ld_proof` type is selected"
)

with pytest.raises(CloudApiValueError) as exc:
@@ -45,7 +50,21 @@ def test_proof_request_base_model():
),
)
assert exc.value.detail == (
"dif_proof_request must not be populated if `indy` type is selected"
"Only indy_proof_request must not be populated if `indy` type is selected"
)

with pytest.raises(CloudApiValueError) as exc:
ProofRequestBase(
type=ProofRequestType.ANONCREDS,
anoncreds_proof_request=AnoncredsPresentationRequest(
requested_attributes={}, requested_predicates={}
),
dif_proof_request=DIFProofRequest(
presentation_definition=PresentationDefinition()
),
)
assert exc.value.detail == (
"Only anoncreds_proof_request must not be populated if `anoncreds` type is selected"
)

with pytest.raises(CloudApiValueError) as exc:
@@ -54,7 +73,7 @@ def test_proof_request_base_model():
"dif_proof_request must be populated if `ld_proof` type is selected"
)

ProofRequestBase.check_indy_proof_request(
ProofRequestBase.check_proof_request(
values=ProofRequestBase(
indy_proof_request=IndyProofRequest(
requested_attributes={}, requested_predicates={}
@@ -79,6 +98,15 @@ def test_accept_proof_request_model():
dif_presentation_spec=DIFPresSpec(),
)

AcceptProofRequest(
proof_id="abc",
anoncreds_presentation_spec=IndyPresSpec(
requested_attributes={},
requested_predicates={},
self_attested_attributes={},
),
)

with pytest.raises(CloudApiValueError) as exc:
AcceptProofRequest(type=ProofRequestType.INDY, indy_presentation_spec=None)
assert exc.value.detail == (
@@ -89,6 +117,13 @@ def test_accept_proof_request_model():
assert exc.value.detail == (
"dif_presentation_spec must be populated if `ld_proof` type is selected"
)
with pytest.raises(CloudApiValueError) as exc:
AcceptProofRequest(
type=ProofRequestType.ANONCREDS, anoncreds_presentation_spec=None
)
assert exc.value.detail == (
"anoncreds_proof_request must be populated if `anoncreds` type is selected"
)


def test_reject_proof_request_model():
41 changes: 38 additions & 3 deletions app/tests/services/verifier/test_acapy_verifier_v2.py
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@
from app.services.verifier.acapy_verifier_v2 import VerifierV2
from app.tests.services.verifier.utils import (
dif_proof_request,
sample_anoncreds_proof_request,
sample_indy_proof_request,
v20_presentation_exchange_records,
)
@@ -37,7 +38,12 @@
@pytest.mark.anyio
@pytest.mark.parametrize(
"proof_type",
[ProofRequestType.INDY, ProofRequestType.LD_PROOF, ProofRequestType.JWT],
[
ProofRequestType.INDY,
ProofRequestType.LD_PROOF,
ProofRequestType.JWT,
ProofRequestType.ANONCREDS,
],
)
async def test_create_proof_request(mock_agent_controller: AcaPyClient, proof_type):
if proof_type != ProofRequestType.JWT:
@@ -52,6 +58,11 @@ async def test_create_proof_request(mock_agent_controller: AcaPyClient, proof_ty
dif_proof_request=(
dif_proof_request if proof_type.value == "ld_proof" else None
),
anoncreds_proof_request=(
sample_anoncreds_proof_request()
if proof_type.value == "anoncreds"
else None
),
type=proof_type,
)

@@ -102,7 +113,12 @@ async def test_create_proof_request_exception(
@pytest.mark.anyio
@pytest.mark.parametrize(
"proof_type",
[ProofRequestType.INDY, ProofRequestType.LD_PROOF, ProofRequestType.JWT],
[
ProofRequestType.INDY,
ProofRequestType.LD_PROOF,
ProofRequestType.JWT,
ProofRequestType.ANONCREDS,
],
)
async def test_send_proof_request(mock_agent_controller: AcaPyClient, proof_type):
if proof_type != ProofRequestType.JWT:
@@ -118,6 +134,11 @@ async def test_send_proof_request(mock_agent_controller: AcaPyClient, proof_type
dif_proof_request=(
dif_proof_request if proof_type.value == "ld_proof" else None
),
anoncreds_proof_request=(
sample_anoncreds_proof_request()
if proof_type.value == "anoncreds"
else None
),
connection_id="abcde",
)

@@ -177,7 +198,12 @@ async def test_send_proof_request_exception(
@pytest.mark.anyio
@pytest.mark.parametrize(
"proof_type",
[ProofRequestType.INDY, ProofRequestType.LD_PROOF, ProofRequestType.JWT],
[
ProofRequestType.INDY,
ProofRequestType.LD_PROOF,
ProofRequestType.JWT,
ProofRequestType.ANONCREDS,
],
)
async def test_accept_proof_request(mock_agent_controller: AcaPyClient, proof_type):
if proof_type != ProofRequestType.JWT:
@@ -199,6 +225,15 @@ async def test_accept_proof_request(mock_agent_controller: AcaPyClient, proof_ty
dif_presentation_spec=(
DIFPresSpec() if proof_type.value == "ld_proof" else None
),
anoncreds_presentation_spec=(
IndyPresSpec(
requested_attributes={},
requested_predicates={},
self_attested_attributes={},
)
if proof_type.value == "anoncreds"
else None
),
proof_id="v2-123",
)

445 changes: 445 additions & 0 deletions app/tests/services/verifier/test_verifier.py

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions app/tests/services/verifier/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from aries_cloudcontroller import (
AnoncredsPresentationReqAttrSpec,
AnoncredsPresentationRequest,
AnoncredsPresentationRequestNonRevoked,
AttachDecorator,
AttachDecoratorData,
DIFProofRequest,
@@ -50,6 +53,22 @@ def sample_indy_proof_request(restrictions=None) -> IndyProofRequest:
)


def sample_anoncreds_proof_request(restrictions=None) -> AnoncredsPresentationRequest:
return AnoncredsPresentationRequest(
name="string",
non_revoked=AnoncredsPresentationRequestNonRevoked(),
nonce="12345",
requested_attributes={
"0_speed_uuid": AnoncredsPresentationReqAttrSpec(
name="speed",
restrictions=restrictions,
)
},
requested_predicates={},
version="1.0",
)


dif_proof_request = DIFProofRequest(
options=None, presentation_definition=PresentationDefinition()
)
18 changes: 10 additions & 8 deletions shared/models/presentation_exchange.py
Original file line number Diff line number Diff line change
@@ -46,19 +46,21 @@ class PresentationExchange(BaseModel):
def presentation_record_to_model(record: V20PresExRecord) -> PresentationExchange:
if isinstance(record, V20PresExRecord):
try:
presentation = (
IndyProof(**record.by_format.pres["indy"])
if record.by_format.pres
else None
)
presentation = None
if record.by_format.pres:
key = list(record.by_format.pres.keys())[0]
presentation = record.by_format.pres[key]

except AttributeError:
logger.info("Presentation record has no indy presentation")
presentation = None

try:
presentation_request = IndyProofRequest(
**record.by_format.pres_request["indy"]
)
presentation_request = None
if record.by_format.pres_request:
key = list(record.by_format.pres_request.keys())[0]
presentation_request = record.by_format.pres_request[key]

except AttributeError:
logger.info("Presentation record has no indy presentation request")
presentation_request = None