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

VC DI proof request #2960

Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
23ad5bd
WIP: vc di proof request - authored by ianco(https://github.com/hyper…
sarthakvijayvergiya Jun 18, 2024
742a7df
fixed lint checks, cleanup
sarthakvijayvergiya Jun 20, 2024
92db3ea
fix: verify_pres, get_sign_key_credential_subject_id
EmadAnwer Jun 28, 2024
dd800b0
WIP: debugging revocation & fixes
sarthakvijayvergiya Jun 28, 2024
c7077dd
WIP: fix ununsed import
sarthakvijayvergiya Jun 28, 2024
147fbb9
refactor: create_signed_anoncreds_presentation, faber vcdi proof_re…
EmadAnwer Jun 29, 2024
72f2d08
Refactor:Add W3cCredential loading for VCDI format handler
EmadAnwer Jun 30, 2024
6ad8125
fix: tests
EmadAnwer Jul 1, 2024
c13a3df
WPA: using static data to test the revocation validation
EmadAnwer Jul 4, 2024
2626b19
feat: Add revocation support to VCDI
EmadAnwer Jul 6, 2024
1034c80
Remove unused code for credential definition and revocation
EmadAnwer Jul 7, 2024
d02441e
Merge branch 'main' into whatscookin/feat/vc-di-proof
EmadAnwer Jul 7, 2024
5083bc3
WPA: fix lint
EmadAnwer Jul 7, 2024
e760786
Fix cred search for vc_di proof
EmadAnwer Jul 8, 2024
d25bb56
Merge branch 'main' into whatscookin/feat/vc-di-proof
EmadAnwer Jul 8, 2024
4adf5a1
Additional integration tests for vc_di and revocation
ianco Jul 8, 2024
432923b
refactor: remove unused comments and TODO's
EmadAnwer Jul 8, 2024
e58f1aa
refactor: split create_signed_anoncreds_presentation
EmadAnwer Jul 8, 2024
a037382
refactor: `create_signed_anoncreds_presentation`
EmadAnwer Jul 9, 2024
dc351a4
Merge pull request #3 from ianco/w3c-proof-req-2
sarthakvijayvergiya Jul 9, 2024
852fa6a
Merge branch 'main' into whatscookin/feat/vc-di-proof
sarthakvijayvergiya Jul 9, 2024
c10c928
add: tests
EmadAnwer Jul 10, 2024
494ab81
add: tests, remove todos
EmadAnwer Jul 11, 2024
b623dae
add: tests
EmadAnwer Jul 11, 2024
6365822
Merge branch 'main' into whatscookin/feat/vc-di-proof
EmadAnwer Jul 11, 2024
b30d88d
fix: linter
EmadAnwer Jul 11, 2024
d13136c
fix: remove unused imports
EmadAnwer Jul 11, 2024
0170055
add: tests
EmadAnwer Jul 11, 2024
116a992
refactor: remove extra variables and comments
EmadAnwer Jul 12, 2024
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
5 changes: 4 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,9 @@ 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 = r"^[0-9.]+$"
INDY_CRED_DEF_ID = (
rf"^([{B58}]{{21,22}})" # issuer DID
f":3" # cred def id marker
Expand All @@ -131,7 +134,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
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
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.]+$"
INDY_SCHEMA_TXN_ID = r"^[0-9.]+$"
INDY_CRED_DEF_ID = (
rf"^([{B58}]{{21,22}})" # issuer DID
f":3" # cred def id marker
Expand All @@ -73,7 +74,7 @@
rf"CL_ACCUM:(.+$)"
)
SUPPORTED_ID_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}"
)

TEST_INDY_DID = "WgWxqztrNooG92RXvxSTWv"
Expand Down
109 changes: 104 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,6 +17,7 @@
Presentation,
PresentCredentials,
W3cCredential,
W3cPresentation,
create_link_secret,
)
from aries_askar import AskarError, AskarErrorCode
Expand All @@ -23,6 +27,10 @@
from ..askar.profile_anon import AskarAnoncredsProfile
from ..core.error import BaseError
from ..core.profile import Profile
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 +315,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 +335,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 +344,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 +431,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 @@ -646,6 +690,61 @@ 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: W3C format requested credentials
credentials_w3c_metadata: W3C format credential metadata
schemas: Indy formatted schemas JSON
credential_definitions: Indy formatted credential definitions JSON
rev_states: Indy format revocation states JSON

"""
present_creds = PresentCredentials()
for idx, cred in enumerate(requested_credentials_w3c):
meta = credentials_w3c_metadata[idx]
rev_state = rev_states.get(meta["rev_reg_id"]) if rev_states else None
for attr in meta["proof_attrs"]:
present_creds.add_attributes(
cred,
attr,
reveal=True,
timestamp=meta.get("timestamp"),
rev_state=rev_state,
)

for pred in meta["proof_preds"]:
present_creds.add_predicates(
cred,
pred,
timestamp=meta.get("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
71 changes: 70 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 @@ -497,3 +498,71 @@ async def verify_presentation(
verified = False

return (verified, msgs)

async def verify_presentation_w3c(
self, pres_req, pres, cred_metadata
) -> PresentationVerificationResult:
"""Verify a W3C presentation.

Args:
pres_req: The presentation request data.
pres: The presentation data.
cred_metadata: The credential metadata.

Returns:
PresentationVerificationResult: An object containing the verification result,
errors, and other details.

Raises:
AnoncredsError: If there is an error during the verification process.

"""
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 = []

anoncreds_verifier = AnonCredsVerifier(self.profile)
(
schemas,
cred_defs,
rev_reg_defs,
rev_reg_entries,
) = await anoncreds_verifier.process_pres_identifiers(cred_metadata)

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_reg_entries.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 @@ -7,8 +7,8 @@
import json
import logging
from typing import Mapping, Tuple
from anoncreds import W3cCredential
from ...models.cred_ex_record import V20CredExRecord
from anoncreds import W3cCredential
from ...models.detail.indy import (
V20CredExRecordIndy,
)
Expand Down 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 @@ -461,7 +462,7 @@ async def issue_credential(
async with ledger:
schema_id = await ledger.credential_definition_id2schema_id(cred_def_id)
cred_def = await ledger.get_credential_definition(cred_def_id)
revocable = cred_def["value"].get("revocation")
revocable = True if cred_def["value"].get("revocation") else False

legacy_offer = await self._prepare_legacy_offer(cred_offer, schema_id)
legacy_request = await self._prepare_legacy_request(cred_request, cred_def_id)
Expand Down Expand Up @@ -559,21 +560,22 @@ async def store_credential(
"""Store vcdi credential."""
cred = cred_ex_record.cred_issue.attachment(VCDICredFormatHandler.format)
cred = cred["credential"]

try:
w3cred = W3cCredential.load(cred)
except AnonCredsHolderError as e:
LOGGER.error(f"Error receiving credential: {e.error_code} - {e.message}")
rev_reg_def = None
anoncreds_registry = self.profile.inject(AnonCredsRegistry)
cred_def_result = await anoncreds_registry.get_credential_definition(
self.profile, cred["proof"][0]["verificationMethod"]
)
# TODO: remove loading of W3cCredential and use the credential directly
try:
cred_w3c = W3cCredential.load(cred)
rev_reg_id = cred_w3c.rev_reg_id
rev_reg_index = cred_w3c.rev_reg_index
except AnonCredsHolderError as e:
LOGGER.error(f"Error receiving credential: {e.error_code} - {e.message}")
raise e
if rev_reg_id:
rev_reg_id = None
rev_reg_index = None
if (
w3cred.rev_reg_id != "None"
): # String None because rev_reg_id property wrapped str()
rev_reg_id = w3cred.rev_reg_id

rev_reg_def_result = (
await anoncreds_registry.get_revocation_registry_definition(
self.profile, rev_reg_id
Expand Down
Loading
Loading