Repository: hyperledger/aries-cloudagent-python

Issue:
Giving the Controller control over what to use to satisfy a presentation request when using DIF PE
When processing a proof request, the general flow within ACA-Py is:

- Search the wallet for the credentials that could be used to satisfy the presentation
- Construct the presentation using the identified credentials

As well, at some point, the Controller needs to be contacted to provide consent in responding to the presentation request.

In some cases, the search of the wallet will return multiple credentials that could be used in the presentation, and a selection is made to choose the one(s) that will be used in the presentation sent to the verifier.  Some examples:

- The request might be for a single type of credential, but the holder has more than one of those in their wallet.
- In DIF PE, there are places where the user can specify "ORs" about what is to be presented (e.g. "present your passport or driver's license credential") and the prover holds credentials that satisfy several of the "OR" conditions.
- The request might allow the presentation of a credential from one of multiple issuers, and the prover's wallet contains credentials from more than one of the permitted issuers.

The desired flow is that the prover code in ACA-Py should default to picking one (e.g. the most recent one issued in the first case), and then the Controller shown the request, the selected credential(s) to be used, and the list of other credentials that could be used.  The Controller can then respond with the credentials that they want to use for the presentation.  This "selection" process should be included as part of the process of getting consent to send the presentation from the Controller.

If the "auto" flag is set for sending a proof request, then the default set of credentials should be used to send the proof.

This capability is in place (at least I think it is...) in the Indy processing, and it should also be in place for DIF PE.

Let's discuss the complexity of this and make sure it is a reasonable request.  I think it is, but...


Discussion:
- In case, `"rule": "pick"` is set in the request with one of `min`, `max` and `count` operators, currently ACA-Py DIF PE handler picks first X credentials from the list of credentials passed to it [X is specified by the operator].
In order to match the desired flow as with Indy, I propose no changes to be made to the handler. Instead, when the W3C credentials are fetched, the list of `VCRecord` can be sorted in descending order by the `issuanceDate` and then passed to the DIF PE handler.  This will involve `records.sort(key=lambda v: dateutil_parser(v.cred_value.get("issuanceDate")), reverse=True)
`
## Current support for controller [Holder] to control
- Search for credentials that apply to `presentation_request` using `/present-proof-2.0/records/{pres_ex_id}/credentials`
- Selecting which credentials to use [Not applicable when `--auto-respond-presentation-request` is set]
1. When calling `/present-proof-2.0/records/{pres_ex_id}/send-presentation`, controller has the ability to override/update the `presentation_request`

### Example
Holder used `/present-proof-2.0/records/{pres_ex_id}/credentials` to identify that multiple credentials would apply to the original request. Holder wants only a credential with issuer `did:key:test` to be included/applied for presentation.
**Original Constraint in Request**
```
"constraints": {
      "limit_disclosure": "required",
      "fields": [
          {
              "path": [
                  "$.credentialSubject.givenName"
              ],
              "filter": {
                  "type": "string",
                  "enum": [
                      "JOHN",
                      "CAI"
                  ]
              }
          }
      ]
  }
```
**Updated by the controller so that only credentials from a particular issuer are applied for presentation**
```
"constraints": {
      "limit_disclosure": "required",
      "fields": [
          {
              "path": [
                  "$.credentialSubject.givenName"
              ],
              "filter": {
                  "type": "string",
                  "enum": [
                      "JOHN",
                      "CAI"
                  ]
              }
          }, {
              "path": [
                  "$.issuer"
              ],
              "filter": {
                  "type": "string",
                  "enum": [
                      "did:key:test"
                  ]
              }
          }
      ]
  }
```
**Request Body - `/present-proof-2.0/records/{pres_ex_id}/send-presentation`**
```
{
  "dif": {
        "presentation_definition": {
            "id": "32f54163-7166-48f1-93d8-ff217bdb0654",
              "submission_requirements": [
                  {
                       "rule": "pick",
                       "count": 1,
                       "from": "A"
                  }
              ],
              "input_descriptors": [
                  {
                      "id": "pr_input_1",
                      "group": [
                         "A"
                      ],
                      "schema": [
                          {
                              "uri": "https://www.w3.org/2018/credentials#VerifiableCredential"
                          },
                          {
                              "uri":"https://w3id.org/citizenship#PermanentResidentCard"
                          }
                      ],
                      "constraints": {
                          "limit_disclosure": "required",
                          "fields": [
                              {
                                  "path": [
                                      "$.credentialSubject.givenName"
                                  ],
                                  "filter": {
                                      "type": "string",
                                      "enum": [
                                          "JOHN",
                                          "CAI"
                                      ]
                                  }
                              },  {
                                "path": [
                                    "$.issuer"
                                ],
                                "filter": {
                                    "type": "string",
                                    "enum": [
                                        "did:key:test"
                                    ]
                                }
                            }
                          ]
                      }
                  }
              ]
        },
    }
}
```
2. Holder can control which credentials are applied to each `input_descriptor` of `presentation_request`. 
**Example**
- Holder received a `presentation_request` [same as example above] and used `/present-proof-2.0/records/{pres_ex_id}/credentials` to find that 3 credentials with `record_id` as `test1`, `test2` and `test3` are applicable to the request
- Holder wants only the credential with `record_id` as `test_1` to be applied for the presentation
- When the controller is calling `/present-proof-2.0/records/{pres_ex_id}/send-presentation`, the `request_body` should be like:
```
{
   "dif": {
     "record_ids": {
        "pr_input_1": [
          "test_1"
        ]
    }
}
```  

## How the examples [comment above] can be handled:
1st and 3rd examples can be handled using either of the 2 control mechanism.

Regarding 2nd example, a quick clarification, `from_nested` is a `xor` operation [either or] and multiple `requirement` inside `submission_requirements` is an `and` operation. There is no `or` equivalent operation within `submission_requirments`.
In the case of `from_nested` in `submission_requirement`, if the holder has credentials that meet both nested requirements then no credentials will be included in the presentation sent to verifier and no exception is raised currently. This can be overcome by the holder using the above control mechanism but the difficulty actually lies in realizing that this is the case before the presentation is processed and sent. 
The solution for the first and third examples is OK, but I'd prefer something a little more direct.  I would be nice if the Controller could use the Cred IDs to say, for this part of the submission, use this credential?  Is there a way to do that?  In other words generalize the the examples (at least 1 and 2) by asking the controller to pick for groups by credential id whenever multiple credentials could be used -- regardless of the reason.

I'm not sure I understand about the second example and your comments -- especially this ` if the holder has credentials that meet both nested requirements then no credentials will be included in the presentation sent to verifier and no exception is raised currently.`  Did you really mean that when this type of presentation definition is used, no credentials are included in the presentation?  That doesn't seem right :-)
> I'm not sure I understand about the second example and your comments -- especially this ` if the holder has credentials that meet both nested requirements then no credentials will be included in the presentation sent to verifier and no exception is raised currently.` Did you really mean that when this type of presentation definition is used, no credentials are included in the presentation?

Sorry about the confusion, the complexity here is that credentials can be restricted from within `submission_requirements` in addition to `constraints.fields`. I think there is no mechanism to specify `ORs` within `submission_requirements`, only able to specify `XORs` & `ANDs` in there. 
`ORs` need to specified in the `constraints.fields.filter` using `enum` or `pattern` detailed below.

For example, if the holder has 2 credentials, one with `type` `Passport` and `DriversLicense` and verifier requests proof of `$.credentialSubject.dob`.

- Note: `"rule": "pick" and "count": 1` will return a single credential. With sorting of credentials in reverse by `issuanceDate` [proposed in my last comment] it will be the most recent credential.
```
{
  "dif": {
        "presentation_definition": {
            "id": "32f54163-7166-48f1-93d8-ff217bdb0654",
              "submission_requirements": [
                  {
                       "rule": "pick",
                       "count": 1,
                       "from": "A"
                  }
              ],
              "input_descriptors": [
                  {
                      "id": "dob_input_1",
                      "group": [
                         "A"
                      ],
                      "schema": [
                          {
                              "uri": "https://www.w3.org/2018/credentials#VerifiableCredential"
                          }
                      ],
                      "constraints": {
                          "limit_disclosure": "required",
                          "fields": [
                              {
                                  "path": [
                                      "$.type[*]"
                                  ],
                                  "filter": {
                                      "type": "string",
                                      "enum": [
                                          "Passport",
                                          "DriversLicense"
                                      ]
                                  }
                              },  {
                                "path": [
                                    "$.credentialSubject.dob"
                                ]
                            }
                          ]
                      }
                  }
              ]
        },
    }
}
``` 
- To return both/all credentials, the following will be updated:
```
"submission_requirements": [
      {
           "rule": "all",
           "from": "A"
      }
  ],
```

> The solution for the first and third examples is OK, but I'd prefer something a little more direct. I would be nice if the Controller could use the Cred IDs to say, for this part of the submission, use this credential? Is there a way to do that? 

A controller can control which credentials to use, for example, if they only want credential with `type` as `Passport` with `record_id` as '1234' to use for `dob_input_1` part of submission:
```
/present-proof-2.0/records/{pres_ex_id}/send-presentation
Request Body:
{
   "dif": {
     "record_ids": {
        "dob_input_1": [
          "1234"
        ]
    }
}
```

> In other words generalize the the examples (at least 1 and 2) by asking the controller to pick for groups by credential id whenever multiple credentials could be used -- regardless of the reason.


This can be done. So if no `--auto-respond-presentation_request` and multiple credentials could be used for a `presentation_request` then check whether `record_ids` in the request body is included by the controller. If not, then raise an exception asking the controller to pick credentials to use and specify them in `record_ids`.

With `--auto` flag, just pass sorted credentials by `issuanceDate` and process the `presentation_request` as it is.

## Changes needed to accomplish above:
- Sort credentials using `records.sort(key=lambda v: dateutil_parser(v.cred_value.get("issuanceDate")), reverse=True)` and passing it to DIF PE handler.
- In `../present_proof/v2_0/formats/dif/handler.py -> create_pres` , if no `--auto`, check if number of applicable credentials are more than 1 and `record_ids` is specified otherwise raise an exception.

Got it -- sorry it took me so long.  Thanks for clarifying what is allowed.  I was thinking of the "pick" case, but obviously didn't remember that it applied only at the group level, not across groups.

I think your solution looks good.  Thanks!

Resolving patch:
diff --git a/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py
--- a/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py
+++ b/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py
@@ -1131,6 +1131,17 @@ async def create_vp(
         applicable_creds_list = []
         for credential in applicable_creds:
             applicable_creds_list.append(credential.cred_value)
+        if (
+            not self.profile.settings.get("debug.auto_respond_presentation_request")
+            and not records_filter
+            and len(applicable_creds_list) > 1
+        ):
+            raise DIFPresExchError(
+                "Multiple credentials are applicable for presentation_definition "
+                f"{pd.id} and --auto-respond-presentation-request setting is not "
+                "enabled. Please specify which credentials should be applied to "
+                "which input_descriptors using record_ids filter."
+            )
         # submission_property
         submission_property = PresentationSubmission(
             id=str(uuid4()), definition_id=pd.id, descriptor_maps=descriptor_maps
diff --git a/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/handler.py b/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/handler.py
--- a/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/handler.py
+++ b/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/handler.py
@@ -2,6 +2,8 @@
 
 import logging
 
+from dateutil.parser import parse as dateutil_parser
+from dateutil.parser import ParserError
 from marshmallow import RAISE
 from typing import Mapping, Tuple, Sequence
 from uuid import uuid4
@@ -292,6 +294,19 @@ async def create_pres(
                 # For now, setting to 1000
                 max_results = 1000
                 records = await search.fetch(max_results)
+                # sort records by issuanceDate in reverse_order
+                try:
+                    records.sort(
+                        key=lambda v: dateutil_parser(v.cred_value.get("issuanceDate")),
+                        reverse=True,
+                    )
+                except ParserError:
+                    raise V20PresFormatHandlerError(
+                        "A credential applicable to the "
+                        "presentation_request of pres_ex_id "
+                        f"{pres_ex_record._id} is not a valid "
+                        "date in RFC3339 format."
+                    )
                 # Avoiding addition of duplicate records
                 (
                     vcrecord_list,
diff --git a/aries_cloudagent/protocols/present_proof/v2_0/routes.py b/aries_cloudagent/protocols/present_proof/v2_0/routes.py
--- a/aries_cloudagent/protocols/present_proof/v2_0/routes.py
+++ b/aries_cloudagent/protocols/present_proof/v2_0/routes.py
@@ -2,8 +2,6 @@
 
 import json
 
-from typing import Mapping, Sequence, Tuple
-
 from aiohttp import web
 from aiohttp_apispec import (
     docs,
@@ -12,7 +10,10 @@
     request_schema,
     response_schema,
 )
+from dateutil.parser import parse as dateutil_parser
+from dateutil.parser import ParserError
 from marshmallow import fields, validate, validates_schema, ValidationError
+from typing import Mapping, Sequence, Tuple
 
 from ....admin.request_context import AdminRequestContext
 from ....connections.models.conn_record import ConnRecord
@@ -602,6 +603,21 @@ async def present_proof_credentials_list(request: web.BaseRequest):
                     pd_uri_list=uri_list,
                 )
                 records = await search.fetch(count)
+                # sort records by issuanceDate in reverse_order
+                try:
+                    records.sort(
+                        key=lambda v: dateutil_parser(v.cred_value.get("issuanceDate")),
+                        reverse=True,
+                    )
+                except ParserError:
+                    raise web.HTTPBadRequest(
+                        reason=(
+                            "A credential applicable to the "
+                            "presentation_request of pres_ex_id "
+                            f"{pres_ex_id} is not a valid date "
+                            "in RFC3339 format."
+                        )
+                    )
                 # Avoiding addition of duplicate records
                 vcrecord_list, vcrecord_ids_set = await process_vcrecords_return_list(
                     records, record_ids


Test patch:
diff --git a/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch_handler.py
--- a/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch_handler.py
+++ b/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch_handler.py
@@ -66,6 +66,7 @@ def profile():
     context.injector.bind_instance(DIDResolverRegistry, did_resolver_registry)
     context.injector.bind_instance(DIDResolver, DIDResolver(did_resolver_registry))
     context.injector.bind_instance(DocumentLoader, custom_document_loader)
+    context.settings["debug.auto_respond_presentation_request"] = True
     return profile
 
 
@@ -3003,3 +3004,61 @@ async def test_multiple_applicable_creds_with_no_id(self, profile, setup_tuple):
             .get("givenName")
             == "TEST"
         )
+
+    @pytest.mark.asyncio
+    @pytest.mark.ursa_bbs_signatures
+    async def test_multiple_applicable_creds_with_no_auto_and_no_record_ids(
+        self, profile, setup_tuple
+    ):
+        cred_list, pd_list = setup_tuple
+        context = profile.context
+        context.settings = {}
+        dif_pres_exch_handler = DIFPresExchHandler(profile)
+        test_pd_max_length = """
+            {
+                "id":"32f54163-7166-48f1-93d8-ff217bdb0653",
+                "submission_requirements":[
+                    {
+                        "name": "European Union Citizenship Proofs",
+                        "rule": "all",
+                        "from": "A"
+                    }
+                ],
+                "input_descriptors":[
+                    {
+                        "id":"citizenship_input_1",
+                        "name":"EU Driver's License",
+                        "group":[
+                            "A"
+                        ],
+                        "schema":[
+                            {
+                                "uri":"https://www.w3.org/2018/credentials#VerifiableCredential"
+                            }
+                        ],
+                        "constraints":{
+                            "fields":[
+                                {
+                                    "path":[
+                                        "$.issuer.id",
+                                        "$.issuer",
+                                        "$.vc.issuer.id"
+                                    ],
+                                    "filter":{
+                                        "type":"string",
+                                        "maxLength": 150
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                ]
+            }
+        """
+        tmp_pd = PresentationDefinition.deserialize(test_pd_max_length)
+        with pytest.raises(DIFPresExchError):
+            tmp_vp = await dif_pres_exch_handler.create_vp(
+                credentials=cred_list,
+                pd=tmp_pd,
+                challenge="1f44d55f-f161-4938-a659-f8026467f126",
+            )
diff --git a/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/tests/test_handler.py b/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/tests/test_handler.py
--- a/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/tests/test_handler.py
+++ b/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/tests/test_handler.py
@@ -461,14 +461,36 @@ async def test_create_pres_prover_proof_spec_with_record_ids(self):
                     "https://www.w3.org/2018/credentials#VerifiableCredential",
                     "https://example.org/examples#UniversityDegreeCredential",
                 ],
-                issuer_id="https://example.edu/issuers/565049",
+                issuer_id="did:example:489398593",
                 subject_ids=[
-                    "did:sov:LjgpST2rjsoxYegQDRm7EL",
-                    "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL",
+                    "did:sov:WgWxqztrNooG92RXvxSTWv",
                 ],
-                proof_types=["BbsBlsSignature2020"],
+                proof_types=["Ed25519Signature2018"],
                 schema_ids=["https://example.org/examples/degree.json"],
-                cred_value={"...": "..."},
+                cred_value={
+                    "@context": [
+                        "https://www.w3.org/2018/credentials/v1",
+                        "https://www.w3.org/2018/credentials/examples/v1",
+                    ],
+                    "id": "http://example.edu/credentials/3732",
+                    "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                    "issuer": "did:example:489398593",
+                    "identifier": "83627467",
+                    "name": "University Degree",
+                    "issuanceDate": "2010-01-01T19:53:24Z",
+                    "credentialSubject": {
+                        "id": "did:sov:WgWxqztrNooG92RXvxSTWv",
+                        "givenName": "Cai",
+                        "familyName": "Leblanc",
+                    },
+                    "proof": {
+                        "type": "Ed25519Signature2018",
+                        "verificationMethod": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL",
+                        "created": "2021-05-07T08:50:17.626625",
+                        "proofPurpose": "assertionMethod",
+                        "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..rubQvgig7cN-F6cYn_AJF1BCSaMpkoR517Ot_4pqwdJnQ-JwKXq6d6cNos5JR73E9WkwYISXapY0fYTIG9-fBA",
+                    },
+                },
                 given_id="http://example.edu/credentials/3732",
                 cred_tags={"some": "tag"},
                 record_id="test1",
@@ -525,6 +547,93 @@ async def test_create_pres_prover_proof_spec_with_record_ids(self):
             )
             assert output[1].data.json_ == DIF_PRES
 
+    async def test_sort_vcrecords_catch_parsererror(self):
+        cred_list = [
+            VCRecord(
+                contexts=[
+                    "https://www.w3.org/2018/credentials/v1",
+                    "https://www.w3.org/2018/credentials/examples/v1",
+                ],
+                expanded_types=[
+                    "https://www.w3.org/2018/credentials#VerifiableCredential",
+                    "https://example.org/examples#UniversityDegreeCredential",
+                ],
+                issuer_id="did:example:489398593",
+                subject_ids=[
+                    "did:sov:WgWxqztrNooG92RXvxSTWv",
+                ],
+                proof_types=["Ed25519Signature2018"],
+                schema_ids=["https://example.org/examples/degree.json"],
+                cred_value={
+                    "@context": [
+                        "https://www.w3.org/2018/credentials/v1",
+                        "https://www.w3.org/2018/credentials/examples/v1",
+                    ],
+                    "id": "http://example.edu/credentials/3732",
+                    "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                    "issuer": "did:example:489398593",
+                    "identifier": "83627467",
+                    "name": "University Degree",
+                    "issuanceDate": "20180-10-29T21:02:19.201+0000",
+                    "credentialSubject": {
+                        "id": "did:sov:WgWxqztrNooG92RXvxSTWv",
+                        "givenName": "Cai",
+                        "familyName": "Leblanc",
+                    },
+                    "proof": {
+                        "type": "Ed25519Signature2018",
+                        "verificationMethod": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL",
+                        "created": "2021-05-07T08:50:17.626625",
+                        "proofPurpose": "assertionMethod",
+                        "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..rubQvgig7cN-F6cYn_AJF1BCSaMpkoR517Ot_4pqwdJnQ-JwKXq6d6cNos5JR73E9WkwYISXapY0fYTIG9-fBA",
+                    },
+                },
+                given_id="http://example.edu/credentials/3732",
+                cred_tags={"some": "tag"},
+                record_id="test1",
+            )
+        ]
+        dif_pres_request = V20PresRequest(
+            formats=[
+                V20PresFormat(
+                    attach_id="dif",
+                    format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][
+                        V20PresFormat.Format.DIF.api
+                    ],
+                )
+            ],
+            request_presentations_attach=[
+                AttachDecorator.data_json(DIF_PRES_REQUEST_B, ident="dif")
+            ],
+        )
+        record = V20PresExRecord(
+            pres_ex_id="pxid",
+            thread_id="thid",
+            connection_id="conn_id",
+            initiator="init",
+            role="role",
+            state="state",
+            pres_request=dif_pres_request,
+            verified="false",
+            auto_present=True,
+            error_msg="error",
+        )
+        request_data = {"dif": {}}
+
+        self.context.injector.bind_instance(
+            VCHolder,
+            async_mock.MagicMock(
+                search_credentials=async_mock.MagicMock(
+                    return_value=async_mock.MagicMock(
+                        fetch=async_mock.CoroutineMock(return_value=cred_list)
+                    )
+                )
+            ),
+        )
+
+        with self.assertRaises(V20PresFormatHandlerError):
+            await self.handler.create_pres(record, request_data)
+
     async def test_create_pres_no_challenge(self):
         dif_pres_req = deepcopy(DIF_PRES_REQUEST_B)
         del dif_pres_req["options"]["challenge"]
diff --git a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py
--- a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py
+++ b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py
@@ -391,8 +391,32 @@ async def test_present_proof_credentials_list_dif(self):
         self.request.query = {"extra_query": {}}
 
         returned_credentials = [
-            async_mock.MagicMock(cred_value={"name": "Credential1"}),
-            async_mock.MagicMock(cred_value={"name": "Credential2"}),
+            async_mock.MagicMock(
+                cred_value={
+                    "@context": [
+                        "https://www.w3.org/2018/credentials/v1",
+                        "https://www.w3.org/2018/credentials/examples/v1",
+                    ],
+                    "id": "http://example.edu/credentials/3732",
+                    "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                    "issuer": "did:example:489398593",
+                    "issuanceDate": "2010-01-01T19:53:24Z",
+                    "credentialSubject": {},
+                }
+            ),
+            async_mock.MagicMock(
+                cred_value={
+                    "@context": [
+                        "https://www.w3.org/2018/credentials/v1",
+                        "https://www.w3.org/2018/credentials/examples/v1",
+                    ],
+                    "id": "http://example.edu/credentials/3733",
+                    "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                    "issuer": "did:example:489398593",
+                    "issuanceDate": "2011-01-01T19:53:24Z",
+                    "credentialSubject": {},
+                }
+            ),
         ]
         self.profile.context.injector.bind_instance(
             IndyHolder,
@@ -453,8 +477,30 @@ async def test_present_proof_credentials_list_dif(self):
             await test_module.present_proof_credentials_list(self.request)
             mock_response.assert_called_once_with(
                 [
-                    {"name": "Credential1", "record_id": ANY},
-                    {"name": "Credential2", "record_id": ANY},
+                    {
+                        "@context": [
+                            "https://www.w3.org/2018/credentials/v1",
+                            "https://www.w3.org/2018/credentials/examples/v1",
+                        ],
+                        "id": "http://example.edu/credentials/3733",
+                        "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                        "issuer": "did:example:489398593",
+                        "issuanceDate": "2011-01-01T19:53:24Z",
+                        "credentialSubject": {},
+                        "record_id": ANY,
+                    },
+                    {
+                        "@context": [
+                            "https://www.w3.org/2018/credentials/v1",
+                            "https://www.w3.org/2018/credentials/examples/v1",
+                        ],
+                        "id": "http://example.edu/credentials/3732",
+                        "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                        "issuer": "did:example:489398593",
+                        "issuanceDate": "2010-01-01T19:53:24Z",
+                        "credentialSubject": {},
+                        "record_id": ANY,
+                    },
                 ]
             )
 
@@ -471,8 +517,32 @@ async def test_present_proof_credentials_dif_no_tag_query(self):
             "required"
         ] = False
         returned_credentials = [
-            async_mock.MagicMock(cred_value={"name": "Credential1"}),
-            async_mock.MagicMock(cred_value={"name": "Credential2"}),
+            async_mock.MagicMock(
+                cred_value={
+                    "@context": [
+                        "https://www.w3.org/2018/credentials/v1",
+                        "https://www.w3.org/2018/credentials/examples/v1",
+                    ],
+                    "id": "http://example.edu/credentials/3732",
+                    "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                    "issuer": "did:example:489398593",
+                    "issuanceDate": "2010-01-01T19:53:24Z",
+                    "credentialSubject": {},
+                }
+            ),
+            async_mock.MagicMock(
+                cred_value={
+                    "@context": [
+                        "https://www.w3.org/2018/credentials/v1",
+                        "https://www.w3.org/2018/credentials/examples/v1",
+                    ],
+                    "id": "http://example.edu/credentials/3733",
+                    "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                    "issuer": "did:example:489398593",
+                    "issuanceDate": "2011-01-01T19:53:24Z",
+                    "credentialSubject": {},
+                }
+            ),
         ]
         self.profile.context.injector.bind_instance(
             IndyHolder,
@@ -533,8 +603,30 @@ async def test_present_proof_credentials_dif_no_tag_query(self):
             await test_module.present_proof_credentials_list(self.request)
             mock_response.assert_called_once_with(
                 [
-                    {"name": "Credential1", "record_id": ANY},
-                    {"name": "Credential2", "record_id": ANY},
+                    {
+                        "@context": [
+                            "https://www.w3.org/2018/credentials/v1",
+                            "https://www.w3.org/2018/credentials/examples/v1",
+                        ],
+                        "id": "http://example.edu/credentials/3733",
+                        "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                        "issuer": "did:example:489398593",
+                        "issuanceDate": "2011-01-01T19:53:24Z",
+                        "credentialSubject": {},
+                        "record_id": ANY,
+                    },
+                    {
+                        "@context": [
+                            "https://www.w3.org/2018/credentials/v1",
+                            "https://www.w3.org/2018/credentials/examples/v1",
+                        ],
+                        "id": "http://example.edu/credentials/3732",
+                        "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                        "issuer": "did:example:489398593",
+                        "issuanceDate": "2010-01-01T19:53:24Z",
+                        "credentialSubject": {},
+                        "record_id": ANY,
+                    },
                 ]
             )
 
@@ -551,8 +643,32 @@ async def test_present_proof_credentials_single_ldp_vp_claim_format(self):
             "limit_disclosure"
         ]
         returned_credentials = [
-            async_mock.MagicMock(cred_value={"name": "Credential1"}),
-            async_mock.MagicMock(cred_value={"name": "Credential2"}),
+            async_mock.MagicMock(
+                cred_value={
+                    "@context": [
+                        "https://www.w3.org/2018/credentials/v1",
+                        "https://www.w3.org/2018/credentials/examples/v1",
+                    ],
+                    "id": "http://example.edu/credentials/3732",
+                    "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                    "issuer": "did:example:489398593",
+                    "issuanceDate": "2010-01-01T19:53:24Z",
+                    "credentialSubject": {},
+                }
+            ),
+            async_mock.MagicMock(
+                cred_value={
+                    "@context": [
+                        "https://www.w3.org/2018/credentials/v1",
+                        "https://www.w3.org/2018/credentials/examples/v1",
+                    ],
+                    "id": "http://example.edu/credentials/3733",
+                    "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                    "issuer": "did:example:489398593",
+                    "issuanceDate": "2011-01-01T19:53:24Z",
+                    "credentialSubject": {},
+                }
+            ),
         ]
         self.profile.context.injector.bind_instance(
             IndyHolder,
@@ -613,8 +729,30 @@ async def test_present_proof_credentials_single_ldp_vp_claim_format(self):
             await test_module.present_proof_credentials_list(self.request)
             mock_response.assert_called_once_with(
                 [
-                    {"name": "Credential1", "record_id": ANY},
-                    {"name": "Credential2", "record_id": ANY},
+                    {
+                        "@context": [
+                            "https://www.w3.org/2018/credentials/v1",
+                            "https://www.w3.org/2018/credentials/examples/v1",
+                        ],
+                        "id": "http://example.edu/credentials/3733",
+                        "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                        "issuer": "did:example:489398593",
+                        "issuanceDate": "2011-01-01T19:53:24Z",
+                        "credentialSubject": {},
+                        "record_id": ANY,
+                    },
+                    {
+                        "@context": [
+                            "https://www.w3.org/2018/credentials/v1",
+                            "https://www.w3.org/2018/credentials/examples/v1",
+                        ],
+                        "id": "http://example.edu/credentials/3732",
+                        "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                        "issuer": "did:example:489398593",
+                        "issuanceDate": "2010-01-01T19:53:24Z",
+                        "credentialSubject": {},
+                        "record_id": ANY,
+                    },
                 ]
             )
 
@@ -631,8 +769,32 @@ async def test_present_proof_credentials_double_ldp_vp_claim_format(self):
             "limit_disclosure"
         ]
         returned_credentials = [
-            async_mock.MagicMock(cred_value={"name": "Credential1"}),
-            async_mock.MagicMock(cred_value={"name": "Credential2"}),
+            async_mock.MagicMock(
+                cred_value={
+                    "@context": [
+                        "https://www.w3.org/2018/credentials/v1",
+                        "https://www.w3.org/2018/credentials/examples/v1",
+                    ],
+                    "id": "http://example.edu/credentials/3732",
+                    "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                    "issuer": "did:example:489398593",
+                    "issuanceDate": "2010-01-01T19:53:24Z",
+                    "credentialSubject": {},
+                }
+            ),
+            async_mock.MagicMock(
+                cred_value={
+                    "@context": [
+                        "https://www.w3.org/2018/credentials/v1",
+                        "https://www.w3.org/2018/credentials/examples/v1",
+                    ],
+                    "id": "http://example.edu/credentials/3733",
+                    "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                    "issuer": "did:example:489398593",
+                    "issuanceDate": "2011-01-01T19:53:24Z",
+                    "credentialSubject": {},
+                }
+            ),
         ]
         self.profile.context.injector.bind_instance(
             IndyHolder,
@@ -693,8 +855,30 @@ async def test_present_proof_credentials_double_ldp_vp_claim_format(self):
             await test_module.present_proof_credentials_list(self.request)
             mock_response.assert_called_once_with(
                 [
-                    {"name": "Credential1", "record_id": ANY},
-                    {"name": "Credential2", "record_id": ANY},
+                    {
+                        "@context": [
+                            "https://www.w3.org/2018/credentials/v1",
+                            "https://www.w3.org/2018/credentials/examples/v1",
+                        ],
+                        "id": "http://example.edu/credentials/3733",
+                        "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                        "issuer": "did:example:489398593",
+                        "issuanceDate": "2011-01-01T19:53:24Z",
+                        "credentialSubject": {},
+                        "record_id": ANY,
+                    },
+                    {
+                        "@context": [
+                            "https://www.w3.org/2018/credentials/v1",
+                            "https://www.w3.org/2018/credentials/examples/v1",
+                        ],
+                        "id": "http://example.edu/credentials/3732",
+                        "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                        "issuer": "did:example:489398593",
+                        "issuanceDate": "2010-01-01T19:53:24Z",
+                        "credentialSubject": {},
+                        "record_id": ANY,
+                    },
                 ]
             )
 
@@ -987,8 +1171,32 @@ async def test_present_proof_credentials_list_schema_uri(self):
         )
 
         returned_credentials = [
-            async_mock.MagicMock(cred_value={"name": "Credential1"}),
-            async_mock.MagicMock(cred_value={"name": "Credential2"}),
+            async_mock.MagicMock(
+                cred_value={
+                    "@context": [
+                        "https://www.w3.org/2018/credentials/v1",
+                        "https://www.w3.org/2018/credentials/examples/v1",
+                    ],
+                    "id": "http://example.edu/credentials/3732",
+                    "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                    "issuer": "did:example:489398593",
+                    "issuanceDate": "2010-01-01T19:53:24Z",
+                    "credentialSubject": {},
+                }
+            ),
+            async_mock.MagicMock(
+                cred_value={
+                    "@context": [
+                        "https://www.w3.org/2018/credentials/v1",
+                        "https://www.w3.org/2018/credentials/examples/v1",
+                    ],
+                    "id": "http://example.edu/credentials/3733",
+                    "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                    "issuer": "did:example:489398593",
+                    "issuanceDate": "2011-01-01T19:53:24Z",
+                    "credentialSubject": {},
+                }
+            ),
         ]
         self.profile.context.injector.bind_instance(
             IndyHolder,
@@ -1020,11 +1228,127 @@ async def test_present_proof_credentials_list_schema_uri(self):
             await test_module.present_proof_credentials_list(self.request)
             mock_response.assert_called_once_with(
                 [
-                    {"name": "Credential1", "record_id": ANY},
-                    {"name": "Credential2", "record_id": ANY},
+                    {
+                        "@context": [
+                            "https://www.w3.org/2018/credentials/v1",
+                            "https://www.w3.org/2018/credentials/examples/v1",
+                        ],
+                        "id": "http://example.edu/credentials/3733",
+                        "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                        "issuer": "did:example:489398593",
+                        "issuanceDate": "2011-01-01T19:53:24Z",
+                        "credentialSubject": {},
+                        "record_id": ANY,
+                    },
+                    {
+                        "@context": [
+                            "https://www.w3.org/2018/credentials/v1",
+                            "https://www.w3.org/2018/credentials/examples/v1",
+                        ],
+                        "id": "http://example.edu/credentials/3732",
+                        "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                        "issuer": "did:example:489398593",
+                        "issuanceDate": "2010-01-01T19:53:24Z",
+                        "credentialSubject": {},
+                        "record_id": ANY,
+                    },
                 ]
             )
 
+    async def test_present_proof_credentials_list_catch_parser_error(self):
+        self.request.match_info = {
+            "pres_ex_id": "123-456-789",
+        }
+        self.request.query = {"extra_query": {}}
+        test_pd = deepcopy(DIF_PROOF_REQ)
+        test_pd["presentation_definition"]["input_descriptors"][0]["schema"][0][
+            "uri"
+        ] = "https://example.org/test.json"
+        test_pd["presentation_definition"]["input_descriptors"][0]["schema"].pop(1)
+        record = V20PresExRecord(
+            state="request-received",
+            role="prover",
+            pres_proposal=None,
+            pres_request={
+                "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/present-proof/2.0/request-presentation",
+                "@id": "6ae00c6c-87fa-495a-b546-5f5953817c92",
+                "comment": "string",
+                "formats": [
+                    {
+                        "attach_id": "dif",
+                        "format": "dif/presentation-exchange/definitions@v1.0",
+                    }
+                ],
+                "request_presentations~attach": [
+                    {
+                        "@id": "dif",
+                        "mime-type": "application/json",
+                        "data": {"json": test_pd},
+                    }
+                ],
+                "will_confirm": True,
+            },
+            pres=None,
+            verified=None,
+            auto_present=False,
+            error_msg=None,
+        )
+
+        returned_credentials = [
+            async_mock.MagicMock(
+                cred_value={
+                    "@context": [
+                        "https://www.w3.org/2018/credentials/v1",
+                        "https://www.w3.org/2018/credentials/examples/v1",
+                    ],
+                    "id": "http://example.edu/credentials/3732",
+                    "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                    "issuer": "did:example:489398593",
+                    "issuanceDate": "2010-01-01T19:53:24Z",
+                    "credentialSubject": {},
+                }
+            ),
+            async_mock.MagicMock(
+                cred_value={
+                    "@context": [
+                        "https://www.w3.org/2018/credentials/v1",
+                        "https://www.w3.org/2018/credentials/examples/v1",
+                    ],
+                    "id": "http://example.edu/credentials/3733",
+                    "type": ["VerifiableCredential", "UniversityDegreeCredential"],
+                    "issuer": "did:example:489398593",
+                    "issuanceDate": "2010-13-29T21:02:19.201+0000",
+                    "credentialSubject": {},
+                }
+            ),
+        ]
+        self.profile.context.injector.bind_instance(
+            IndyHolder,
+            async_mock.MagicMock(
+                get_credentials_for_presentation_request_by_referent=(
+                    async_mock.CoroutineMock()
+                )
+            ),
+        )
+        self.profile.context.injector.bind_instance(
+            VCHolder,
+            async_mock.MagicMock(
+                search_credentials=async_mock.MagicMock(
+                    return_value=async_mock.MagicMock(
+                        fetch=async_mock.CoroutineMock(
+                            return_value=returned_credentials
+                        )
+                    )
+                )
+            ),
+        )
+        with async_mock.patch.object(
+            test_module, "V20PresExRecord", autospec=True
+        ) as mock_pres_ex_rec_cls:
+            with self.assertRaises(test_module.web.HTTPBadRequest):
+                mock_pres_ex_rec_cls.retrieve_by_id.return_value = record
+                await test_module.present_proof_credentials_list(self.request)
+
     async def test_present_proof_credentials_list_dif_error(self):
         self.request.match_info = {
             "pres_ex_id": "123-456-789",