Skip to content

💥 Remove Indy proof type and models #1529

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

Merged
merged 20 commits into from
May 8, 2025
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
140 changes: 44 additions & 96 deletions app/models/verifier.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from enum import Enum
from typing import Optional, Union
from typing import Literal, Optional, Union

from aries_cloudcontroller import (
AnonCredsPresentationRequest as AcaPyAnonCredsPresentationRequest,
Expand All @@ -9,37 +8,21 @@
DIFPresSpec,
DIFProofRequest,
IndyNonRevocationInterval,
IndyPresSpec,
)
from aries_cloudcontroller import IndyProofRequest as AcaPyIndyProofRequest
from pydantic import BaseModel, Field, field_validator, model_validator

from app.util.save_exchange_record import SaveExchangeRecordField
from shared.exceptions import CloudApiValueError


class ProofRequestType(str, Enum):
INDY: str = "indy"
JWT: str = "jwt"
LD_PROOF: str = "ld_proof"
ANONCREDS: str = "anoncreds"


class AnonCredsPresentationRequest(AcaPyAnonCredsPresentationRequest):
name: str = Field(default="Proof", description="Proof request name")
version: str = Field(default="1.0", description="Proof request version")


class IndyProofRequest(AcaPyIndyProofRequest):
name: str = Field(default="Proof", description="Proof request name")
version: str = Field(default="1.0", description="Proof request version")


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
dif_proof_request: Optional[DIFProofRequest] = None

@model_validator(mode="before")
@classmethod
Expand All @@ -48,49 +31,29 @@ def check_proof_request(cls, values: Union[dict, "ProofRequestBase"]):
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(
"indy_proof_request must be populated if `indy` type is selected"
)

if proof_type == ProofRequestType.LD_PROOF and dif_proof is None:
raise CloudApiValueError(
"dif_proof_request must be populated if `ld_proof` type is selected"
)

if proof_type == ProofRequestType.INDY and (
dif_proof is not None or anoncreds_proof is not None
):
raise CloudApiValueError(
"Only indy_proof_request must 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
):
if anoncreds_proof is None and dif_proof is None:
raise CloudApiValueError(
"Only dif_proof_request must be populated if `ld_proof` type is selected"
"One of anoncreds_proof_request or dif_proof_request must be populated"
)

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

return values

def get_proof_type(self) -> Literal["anoncreds", "dif"]:
if self.anoncreds_proof_request is not None:
return "anoncreds"
elif self.dif_proof_request is not None:
return "dif"
else:
raise CloudApiValueError("No proof type provided")


class ProofRequestMetadata(BaseModel):
comment: Optional[str] = None
Expand All @@ -111,53 +74,38 @@ class ProofId(BaseModel):


class AcceptProofRequest(ProofId, SaveExchangeRecordField):
type: ProofRequestType = ProofRequestType.INDY
indy_presentation_spec: Optional[IndyPresSpec] = None
dif_presentation_spec: Optional[DIFPresSpec] = None
anoncreds_presentation_spec: Optional[AnonCredsPresSpec] = None
dif_presentation_spec: Optional[DIFPresSpec] = None

@model_validator(mode="before")
@classmethod
def validate_specs(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__

dif_pres_spec = values.get("dif_presentation_spec")
anoncreds_pres_spec = values.get("anoncreds_presentation_spec")

if anoncreds_pres_spec is None and dif_pres_spec is None:
raise CloudApiValueError(
"One of anoncreds_presentation_spec or dif_presentation_spec must be populated"
)

if anoncreds_pres_spec is not None and dif_pres_spec is not None:
raise CloudApiValueError(
"Only one of anoncreds_presentation_spec or dif_presentation_spec must be populated"
)

return values

@model_validator(mode="after")
def validate_specs(self) -> "AcceptProofRequest":
if self.type == ProofRequestType.INDY:
if self.indy_presentation_spec is None:
raise CloudApiValueError(
"indy_presentation_spec must be populated if `indy` type is selected"
)
if (
self.dif_presentation_spec is not None
or self.anoncreds_presentation_spec is not None
):
raise CloudApiValueError(
"Only indy_presentation_spec should be provided for `indy` type"
)

elif self.type == ProofRequestType.LD_PROOF:
if self.dif_presentation_spec is None:
raise CloudApiValueError(
"dif_presentation_spec must be populated if `ld_proof` type is selected"
)
if (
self.indy_presentation_spec is not None
or self.anoncreds_presentation_spec is not None
):
raise CloudApiValueError(
"Only dif_presentation_spec should be provided for `ld_proof` type"
)

elif self.type == ProofRequestType.ANONCREDS:
if self.anoncreds_presentation_spec is None:
raise CloudApiValueError(
"anoncreds_presentation_spec must be populated if `anoncreds` type is selected"
)
if (
self.indy_presentation_spec is not None
or self.dif_presentation_spec is not None
):
raise CloudApiValueError(
"Only anoncreds_presentation_spec should be provided for `anoncreds` type"
)

return self
def get_proof_type(self) -> Literal["anoncreds", "dif"]:
if self.anoncreds_presentation_spec is not None:
return "anoncreds"
elif self.dif_presentation_spec is not None:
return "dif"
else:
raise CloudApiValueError("No proof type provided")


class RejectProofRequest(ProofId):
Expand Down
27 changes: 11 additions & 16 deletions app/routes/verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,11 @@ async def send_proof_request(

The verifier uses this endpoint to send a proof request to a specific connection, by providing the connection ID.

The proof request type must be one of indy, anoncreds or ld_proof.
The proof request type must be one of anoncreds or ld_proof.
```json
{
"type": "indy", "anoncreds" or "ld_proof",
"indy_proof_request": {...}, <-- Required if type is "indy"
"anoncreds_proof_request": {...}, <-- Required if type is "anoncreds"
"dif_proof_request": {...}, <-- Required if type is "ld_proof"
"anoncreds_proof_request": {...},
"dif_proof_request": {...},
"save_exchange_record": true <-- Whether the proof exchange record should be preserved after completion.
"comment": "string", <-- This comment will appear in the proof record for the recipient as well
"connection_id": "string", <-- The verifier's reference to the connection to send this proof request to
Expand Down Expand Up @@ -117,13 +115,11 @@ async def create_proof_request(
The OOB protocol allows proof requests to be sent over alternative channels, such as email or QR code, where a
connection does not yet exist between holder and verifier.

The proof request type must be one of indy, anoncreds or ld_proof.
The proof request type must be one of anoncreds or ld_proof.
```json
{
"type": "indy", "anoncreds" or "ld_proof",
"indy_proof_request": {...}, <-- Required if type is "indy"
"anoncreds_proof_request": {...}, <-- Required if type is "anoncreds"
"dif_proof_request": {...}, <-- Required if type is "ld_proof"
"anoncreds_proof_request": {...},
"dif_proof_request": {...},
"save_exchange_record": true <-- Whether the proof exchange record should be preserved after completion.
"comment": "string", <-- This comment will appear in the proof record for the recipient as well
}
Expand Down Expand Up @@ -177,22 +173,21 @@ async def accept_proof_request(
---
A prover uses this endpoint to respond to a proof request, by sending a presentation to the verifier.

An Indy presentation contains a mapping of the requested attributes to the wallet credential id of the prover.
An AnonCreds presentation contains a mapping of the requested attributes to the wallet credential id of the prover.

The prover must provide the proof ID of the request that they are responding to, and the presentation object.
```json
{
"proof_id": "string", <-- The proof ID of the presentation request that is being accepted
"indy_presentation_spec": {...}, <-- Required if type is "indy"
"dif_presentation_spec": {...}, <-- Required if type is "ld_proof"
"anoncreds_presentation_spec": {...}, <-- Required if type is "anoncreds"
"anoncreds_presentation_spec": {...},
"dif_presentation_spec": {...},
}
```

Example of an Indy presentation object:
Example of an AnonCreds presentation object:
```json
{
"indy_presentation_spec": {
"anoncreds_presentation_spec": {
"requested_attributes": {
"surname": {
"cred_id": "10e6b03f-2b60-431a-9634-731594423120",
Expand Down
47 changes: 20 additions & 27 deletions app/services/verifier/acapy_verifier_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from app.models.verifier import (
AcceptProofRequest,
CreateProofRequest,
ProofRequestType,
RejectProofRequest,
SendProofRequest,
)
Expand All @@ -38,21 +37,19 @@ async def create_proof_request(
controller: AcaPyClient,
create_proof_request: CreateProofRequest,
) -> PresentationExchange:
if create_proof_request.type == ProofRequestType.INDY:
proof_type = create_proof_request.get_proof_type()
if proof_type == "anoncreds":
presentation_request = V20PresRequestByFormat(
indy=create_proof_request.indy_proof_request
anoncreds=create_proof_request.anoncreds_proof_request
)
elif create_proof_request.type == ProofRequestType.LD_PROOF:
elif proof_type == "dif":
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}",
"Unsupported credential request. One of anoncreds_proof_request "
"or dif_proof_request must be populated.",
status_code=501,
)

Expand All @@ -64,6 +61,7 @@ async def create_proof_request(
auto_verify=True,
comment=create_proof_request.comment,
)
bound_logger.debug("Sending create request: {}", request_body)
try:
presentation_exchange = await handle_acapy_call(
logger=bound_logger,
Expand All @@ -85,21 +83,19 @@ async def send_proof_request(
controller: AcaPyClient,
send_proof_request: SendProofRequest,
) -> PresentationExchange:
if send_proof_request.type == ProofRequestType.INDY:
proof_type = send_proof_request.get_proof_type()
if proof_type == "anoncreds":
presentation_request = V20PresRequestByFormat(
indy=send_proof_request.indy_proof_request
anoncreds=send_proof_request.anoncreds_proof_request
)
elif send_proof_request.type == ProofRequestType.LD_PROOF:
elif proof_type == "dif":
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}",
"Unsupported credential request. One of anoncreds_proof_request "
"or dif_proof_request must be populated.",
status_code=501,
)

Expand All @@ -111,6 +107,7 @@ async def send_proof_request(
auto_verify=True,
comment=send_proof_request.comment,
)
bound_logger.debug("Sending request: {}", request_body)
try:
bound_logger.debug("Send free v2 presentation request")
presentation_exchange = await handle_acapy_call(
Expand All @@ -132,24 +129,20 @@ async def accept_proof_request(
cls, controller: AcaPyClient, accept_proof_request: AcceptProofRequest
) -> PresentationExchange:
auto_remove = accept_proof_request.auto_remove

if accept_proof_request.type == ProofRequestType.INDY:
proof_type = accept_proof_request.get_proof_type()
if proof_type == "anoncreds":
presentation_spec = V20PresSpecByFormatRequest(
auto_remove=auto_remove,
indy=accept_proof_request.indy_presentation_spec,
anoncreds=accept_proof_request.anoncreds_presentation_spec,
)
elif accept_proof_request.type == ProofRequestType.LD_PROOF:
elif proof_type == "dif":
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}",
"Unsupported credential. One of anoncreds_presentation_spec "
"or dif_presentation_spec must be populated.",
status_code=501,
)

Expand Down
1 change: 0 additions & 1 deletion app/tests/e2e/issuer/ld_proof/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

def create_credential(proof_type: str) -> dict:
return SendCredential(
type="ld_proof",
connection_id="",
ld_credential_detail=LDProofVCDetail(
credential=Credential(
Expand Down
3 changes: 0 additions & 3 deletions app/tests/e2e/issuer/test_anoncreds_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ async def test_send_credential_oob(
alice_member_client: RichAsyncClient,
):
credential = {
"type": "anoncreds",
"anoncreds_credential_detail": {
"credential_definition_id": anoncreds_credential_definition_id,
"attributes": sample_credential_attributes,
Expand Down Expand Up @@ -90,7 +89,6 @@ async def test_send_credential_and_request(
):
credential = {
"connection_id": faber_anoncreds_and_alice_connection.faber_connection_id,
"type": "anoncreds",
"anoncreds_credential_detail": {
"credential_definition_id": anoncreds_credential_definition_id,
"attributes": sample_credential_attributes,
Expand Down Expand Up @@ -162,7 +160,6 @@ async def test_revoke_credential(

credential = {
"connection_id": faber_connection_id,
"type": "anoncreds",
"anoncreds_credential_detail": {
"credential_definition_id": anoncreds_credential_definition_id_revocable,
"attributes": sample_credential_attributes,
Expand Down
Loading
Loading