Skip to content

Commit

Permalink
WIP: vc di proof request - authored by ianco(openwallet-foundation#3043)
Browse files Browse the repository at this point in the history
Signed-off-by: Sarthak Vijayvergiya <[email protected]>
  • Loading branch information
sarthakvijayvergiya committed Jun 20, 2024
1 parent 9e92e23 commit 60086b6
Show file tree
Hide file tree
Showing 15 changed files with 701 additions and 63 deletions.
4 changes: 3 additions & 1 deletion aries_cloudagent/anoncreds/default/legacy_indy/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ def __init__(self):
B58 = alphabet if isinstance(alphabet, str) else alphabet.decode("ascii")
INDY_DID = rf"^(did:sov:)?[{B58}]{{21,22}}$"
INDY_SCHEMA_ID = rf"^[{B58}]{{21,22}}:2:.+:[0-9.]+$"
# the schema id can be just a number (this is how the schema_id is referenced in a cred def)
INDY_SCHEMA_TXN_ID = rf"^[0-9.]+$"
INDY_CRED_DEF_ID = (
rf"^([{B58}]{{21,22}})" # issuer DID
f":3" # cred def id marker
Expand All @@ -131,7 +133,7 @@ def __init__(self):
rf"CL_ACCUM:(.+$)"
)
self._supported_identifiers_regex = re.compile(
rf"{INDY_DID}|{INDY_SCHEMA_ID}|{INDY_CRED_DEF_ID}|{INDY_REV_REG_DEF_ID}"
rf"{INDY_DID}|{INDY_SCHEMA_ID}|{INDY_SCHEMA_TXN_ID}|{INDY_CRED_DEF_ID}|{INDY_REV_REG_DEF_ID}"
)

@property
Expand Down
117 changes: 112 additions & 5 deletions aries_cloudagent/anoncreds/holder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import asyncio
import json
import logging
from marshmallow import INCLUDE
import re
from typing import Dict, Optional, Sequence, Tuple, Union
from pyld import jsonld
from pyld.jsonld import JsonLdProcessor

from anoncreds import (
AnoncredsError,
Expand All @@ -14,16 +17,25 @@
Presentation,
PresentCredentials,
W3cCredential,
W3cPresentation,
create_link_secret,
)
from aries_askar import AskarError, AskarErrorCode
from uuid_utils import uuid4

from ..protocols.issue_credential.v2_0.models.detail.ld_proof import (
V20CredExRecordLDProof,
)

from ..anoncreds.models.anoncreds_schema import AnonCredsSchema
from ..askar.profile_anon import AskarAnoncredsProfile
from ..core.error import BaseError
from ..core.profile import Profile
from ..ledger.base import BaseLedger
from ..storage.vc_holder.base import VCHolder
from ..storage.vc_holder.vc_record import VCRecord
from ..vc.vc_ld import VerifiableCredential
from ..vc.ld_proofs import DocumentLoader
from ..wallet.error import WalletNotFoundError
from .error_messages import ANONCREDS_PROFILE_REQUIRED_MSG
from .models.anoncreds_cred_def import CredDef
Expand Down Expand Up @@ -307,7 +319,7 @@ async def store_credential_w3c(
try:
secret = await self.get_master_secret()
cred_w3c = W3cCredential.load(credential_data)
await asyncio.get_event_loop().run_in_executor(
cred_w3c_recvd = await asyncio.get_event_loop().run_in_executor(
None,
cred_w3c.process,
credential_request_metadata,
Expand All @@ -327,7 +339,7 @@ async def store_credential_w3c(
except AnoncredsError as err:
raise AnonCredsHolderError("Error processing received credential") from err

return await self._finish_store_credential(
credential_id = await self._finish_store_credential(
credential_definition,
cred_recvd,
credential_request_metadata,
Expand All @@ -336,6 +348,45 @@ async def store_credential_w3c(
rev_reg_def,
)

# also store in W3C format
# create VC record for storage
cred_w3c_recvd_dict = cred_w3c_recvd.to_dict()
cred_w3c_recvd_dict["proof"] = cred_w3c_recvd_dict["proof"][0]
cred_w3c_recvd_vc = VerifiableCredential.deserialize(
cred_w3c_recvd_dict, unknown=INCLUDE
)

# Saving expanded type as a cred_tag
document_loader = self.profile.inject(DocumentLoader)
expanded = jsonld.expand(
cred_w3c_recvd_dict, options={"documentLoader": document_loader}
)
types = JsonLdProcessor.get_values(
expanded[0],
"@type",
)

vc_record = VCRecord(
contexts=cred_w3c_recvd_vc.context_urls,
expanded_types=types,
issuer_id=cred_w3c_recvd_vc.issuer_id,
subject_ids=cred_w3c_recvd_vc.credential_subject_ids,
schema_ids=[], # Schemas not supported yet
proof_types=[cred_w3c_recvd_vc.proof.type],
cred_value=cred_w3c_recvd_vc.serialize(),
given_id=cred_w3c_recvd_vc.id,
record_id=credential_id,
cred_tags=None, # Tags should be derived from credential values
)

# save credential in storage
async with self.profile.session() as session:
vc_holder = session.inject(VCHolder)

await vc_holder.store_credential(vc_record)

return credential_id

async def get_credentials(self, start: int, count: int, wql: dict):
"""Get credentials stored in the wallet.
Expand Down Expand Up @@ -384,16 +435,13 @@ async def get_credentials_for_presentation_request_by_referent(
extra_query: wql query dict
"""

if not referents:
referents = (
*presentation_request["requested_attributes"],
*presentation_request["requested_predicates"],
)
extra_query = extra_query or {}

creds = {}

for reft in referents:
names = set()
if reft in presentation_request["requested_attributes"]:
Expand Down Expand Up @@ -640,6 +688,65 @@ def get_rev_state(cred_id: str, detail: dict):

return presentation.to_json()

async def create_presentation_w3c(
self,
presentation_request: dict,
requested_credentials_w3c: list,
credentials_w3c_metadata: list,
schemas: Dict[str, AnonCredsSchema],
credential_definitions: Dict[str, CredDef],
rev_states: dict = None,
) -> dict:
"""Get credentials stored in the wallet.
Args:
presentation_request: Valid indy format presentation request
requested_credentials: W3C format requested credentials
schemas: Indy formatted schemas JSON
credential_definitions: Indy formatted credential definitions JSON
rev_states: Indy format revocation states JSON
"""
# TODO implement this!!!!! prepare and return an anoncreds proof
present_creds = PresentCredentials()
for idx, cred in enumerate(requested_credentials_w3c):
meta = credentials_w3c_metadata[idx]

# TODO deal with revocation
rev_state = None
timestamp = None

for attr in meta["proof_attrs"]:
present_creds.add_attributes(
cred,
attr,
reveal=True,
timestamp=timestamp,
rev_state=rev_state,
)

for pred in meta["proof_preds"]:
present_creds.add_predicates(
cred,
pred,
timestamp=timestamp,
rev_state=rev_state,
)

try:
secret = await self.get_master_secret()
presentation = W3cPresentation.create(
presentation_request,
present_creds,
secret,
schemas,
credential_definitions,
)
except AnoncredsError as err:
raise AnonCredsHolderError("Error creating presentation") from err

return presentation.to_dict()

async def create_revocation_state(
self,
cred_rev_id: str,
Expand Down
72 changes: 71 additions & 1 deletion aries_cloudagent/anoncreds/verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
from time import time
from typing import List, Mapping, Tuple

from anoncreds import AnoncredsError, Presentation
from anoncreds import AnoncredsError, Presentation, W3cPresentation

from ..core.profile import Profile
from ..indy.models.xform import indy_proof_req2non_revoc_intervals
from ..messaging.util import canon, encode
from .models.anoncreds_cred_def import GetCredDefResult
from .registry import AnonCredsRegistry
from ..vc.vc_ld.validation_result import PresentationVerificationResult

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -496,3 +497,72 @@ async def verify_presentation(
verified = False

return (verified, msgs)

async def verify_presentation_w3c(
self,
pres_req,
pres,
) -> PresentationVerificationResult:
credentials = pres["verifiableCredential"]
cred_def_ids = []
for credential in credentials:
cred_def_id = credential["proof"]["verificationMethod"]
cred_def_ids.append(cred_def_id)

cred_defs = {}
schemas = {}
msgs = []

# TODO this should use the process_pres_identifiers() method, which will also fetch the revocation info
for cred_def_id in cred_def_ids:
anoncreds_registry = self.profile.inject(AnonCredsRegistry)
# Build schemas for anoncreds
if cred_def_id not in cred_defs:
cred_def = (
await anoncreds_registry.get_credential_definition(
self.profile, cred_def_id
)
).credential_definition.serialize()
cred_defs[cred_def_id] = cred_def
schema_id = cred_def["schemaId"]
schema = (
await anoncreds_registry.get_schema(self.profile, schema_id)
).serialize()
if schema["schema_id"] not in schemas:
schemas[schema["schema_id"]] = schema["schema"]

# TODO - this should get loaded from process_pres_identifiers() (with schemas and cred defs)
rev_reg_defs = {}
rev_lists = {}

try:
# TODO not sure why this attr causes an error
del pres["presentation_submission"]

presentation = W3cPresentation.load(pres)

verified = await asyncio.get_event_loop().run_in_executor(
None,
presentation.verify,
pres_req,
schemas,
cred_defs,
rev_reg_defs,
[
rev_list
for timestamp_to_list in rev_lists.values()
for rev_list in timestamp_to_list.values()
],
)
except AnoncredsError as err:
s = str(err)
msgs.append(f"{PresVerifyMsg.PRES_VERIFY_ERROR.value}::{s}")
LOGGER.exception(
f"Validation of presentation on nonce={pres_req['nonce']} "
"failed with error"
)
verified = False

result = PresentationVerificationResult(verified=verified, errors=msgs)

return result
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,10 @@ async def _create():
credential=credential,
)

return self.get_format_data(
cred_offer = self.get_format_data(
CRED_20_OFFER, json.loads(vcdi_cred_abstract.to_json())
)
return cred_offer

async def receive_offer(
self, cred_ex_record: V20CredExRecord, cred_offer_message: V20CredOffer
Expand Down Expand Up @@ -573,7 +574,9 @@ async def store_credential(
except AnonCredsHolderError as e:
LOGGER.error(f"Error receiving credential: {e.error_code} - {e.message}")
raise e
if rev_reg_id:
# argh even with no revocation id (rev_reg_id = None) the following code gets called and fails
# TODO figure this out :-( (somehow the value is getting set to String "None")
if rev_reg_id and rev_reg_id != "None":
rev_reg_def_result = (
await anoncreds_registry.get_revocation_registry_definition(
self.profile, rev_reg_id
Expand Down
3 changes: 3 additions & 0 deletions aries_cloudagent/protocols/present_proof/dif/pres_exch.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def __init__(
ldp: Mapping = None,
ldp_vc: Mapping = None,
ldp_vp: Mapping = None,
di_vc: Mapping = None,
):
"""Initialize format."""
self.jwt = jwt
Expand All @@ -45,6 +46,7 @@ def __init__(
self.ldp = ldp
self.ldp_vc = ldp_vc
self.ldp_vp = ldp_vp
self.di_vc = di_vc


class ClaimFormatSchema(BaseModelSchema):
Expand All @@ -62,6 +64,7 @@ class Meta:
ldp = fields.Dict(required=False)
ldp_vc = fields.Dict(required=False)
ldp_vp = fields.Dict(required=False)
di_vc = fields.Dict(required=False)


class SubmissionRequirements(BaseModel):
Expand Down
Loading

0 comments on commit 60086b6

Please sign in to comment.