Repository: hyperledger/aries-cloudagent-python

Issue:
RFC 0510 - ACA-Py DIF PE is_holder support
-[ RFC 0510](https://github.com/hyperledger/aries-rfcs/tree/master/features/0510-dif-pres-exch-attach) in regards to `is_holder` states that `preferred or required as defined in the spec.Note that this feature allows the Holder to present credentials with a different subject identifier than the DID used to establish the DIDComm connection with the Verifier.`
- This means when `is_holder` is required we should only present and sign those credentials where `credentialSubject.id` is the same as the ConnRecord my_did.
- Since, current connection DID will be `did:sov:`, it will leave out all of BbsBls credentials and not support limit_disclosure. So is_holder cannot be used alongside limit_disclosure

Is this as expected and should be implemented in ACA-Py like this? To support this:
- When fetching W3C credentials, check if `is_holder`
- add `subject_ids = [ConnRecord.my_did]` as a parameter and call `search_credentials()`


Discussion:
No -- that's sounds very wrong.  The DID used for the connection with the verifier should (almost) never be used for anything to do with verifiable credentials.  I'll take a look at that in the RFC.
That doesn't sound right indeed.

`is_holder` means you want the presenter of the credential to be the same as the credential holder. In most cases this translates to the signature on the VP coming from the same did as the credentialSubject.id
I'm not sure why this:

>  `preferred or required as defined in the spec.
Note that this feature allows the Holder to present credentials with a different subject identifier than the DID used to establish the DIDComm connection with the Verifier.` 

is in the RFC at all. 

IMHO you would never want to use the DIDComm DID as the Verifier.  This wording suggests using the DID from the DIDComm connection is the normal way, but if you need to change it, you can use this other way.

Is it just my interpretation?  If you want to use your DIDComm DID for multiple purposes (such as with a VC), that's up to you, but it should never be suggested or recommended (or implied) in any RFCs.
> That doesn't sound right indeed.
> 
> `is_holder` means you want the presenter of the credential to be the same as the credential holder. In most cases this translates to the signature on the VP coming from the same did as the credentialSubject.id

This is what is happening currently in ACA-Py [ even when `is_holder` is not specified in constraint], so what is expected when there is `is_holder` specified?
### Currently in ACA-Py
If a `presentation_request` is processed and 3 credentials are applicable to be included in the `VP`. 
- If all 3 credentials have `did:...:test` as `credentialSubject.id`, VP is signed with suite having `public_key_base58` as `did:...:test`'s `did_info.verkey`
-  If one or more of the 3 credentials have `did:...:test` as `credentialSubject.id` and the remainder have no `credentialSubject.id`, VP is signed with suite having `public_key_base58` as `did:...:test`'s `did_info.verkey`
- If one of 3 credentials have `did:...:other_test` and the other 2 have `did:...:test` as `credentialSubject.id`, an exception is raised stating `Applicable credentials have different credentialSubject.id, multiple proofs are not supported currently`

The following is from the DIF spec:
```
The constraints object MAY contain an is_holder property. If present, its value MUST be an array of objects composed as follows:

- The is-holder object MUST contain a field_id property. The value of this property MUST be an array of strings, with each string
matching the string value from a field object’s id property. This identifies the attribute whose Subject is of concern to the Verifier.
- The is-holder object MUST contain a directive property. The value of this property MUST be one of the following strings:
1. required - This indicates that the processing entity MUST include proof that the Subject of each attribute identified by a value
in the field_id array is the same as the entity submitting the response.
2. preferred - This indicates that it is RECOMMENDED that the processing entity include proof that the Subject of each attribute
identified by a value in the field_id array is the same as the entity submitting the response.

The is_holder property would be used by a Verifier to require that certain inputs be provided by a certain Subject. 
For example, an identity verification Presentation Definition might contain an Input Descriptor for a birthdate from a birth
certificate. Using is_holder, the Verifier would be able to require that the Holder of the birth certificate Claim is the same as the
Subject of the birthdate attribute. This is especially useful in cases where a Claim may have multiple Subjects.
```
The verifier must specify which `fields` in the `input_descriptors`, so only credentials filtered from these fields should be proven to be provided by a certain subject. Can signing the presentation still provide holder binding?
The way you have it currently implemented seems correct, if the following is also true:

- If none of the credentials have a credentialSubject.id, an exception is raised stating that it is not possible to prove the `is_holder` requirement.

I think the statement should just be removed from the RFC
> The way you have it currently implemented seems correct if the following is also true:
> 
> * If none of the credentials have a credentialSubject.id, an exception is raised stating that it is not possible to prove the `is_holder` requirement.
> 
> I think the statement should just be removed from the RFC

Sounds good. For the following will this be the right approach:
- But if `is_holder` is missing/not specified then don't sign the `VP` at all? I guess it cannot be verified by the verifier then.
- In reference to the DIF spec [snippet above], currently, the `field_ids` are not being used. Maybe we should also check that each of the filtered credentials from that `field` have the same `credentialSubject.id`, otherwise raise an exception?
> Sounds good. For the following will this be the right approach:
> 
> * But if `is_holder` is missing/not specified then don't sign the `VP` at all? I guess it cannot be verified by the verifier then.
> * In reference to the DIF spec [snippet above], currently, the `field_ids` are not being used. Maybe we should also check that each of the filtered credentials from that `field` have the same `credentialSubject.id`, otherwise raise an exception?

@TimoGlastra Any feedback will be appreciated.

Resolving patch:
diff --git a/aries_cloudagent/protocols/present_proof/dif/pres_exch.py b/aries_cloudagent/protocols/present_proof/dif/pres_exch.py
--- a/aries_cloudagent/protocols/present_proof/dif/pres_exch.py
+++ b/aries_cloudagent/protocols/present_proof/dif/pres_exch.py
@@ -384,6 +384,7 @@ class Meta:
     def __init__(
         self,
         *,
+        id: str = None,
         paths: Sequence[str] = None,
         purpose: str = None,
         predicate: str = None,
@@ -394,6 +395,7 @@ def __init__(
         self.purpose = purpose
         self.predicate = predicate
         self._filter = _filter
+        self.id = id
 
 
 class DIFFieldSchema(BaseModelSchema):
@@ -405,6 +407,7 @@ class Meta:
         model_class = DIFField
         unknown = EXCLUDE
 
+    id = fields.Str(description="ID", required=False)
     paths = fields.List(
         fields.Str(description="Path", required=False),
         required=False,
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
@@ -38,6 +38,7 @@
 )
 from ....vc.vc_ld.prove import sign_presentation, create_presentation, derive_credential
 from ....wallet.base import BaseWallet, DIDInfo
+from ....wallet.error import WalletError, WalletNotFoundError
 from ....wallet.key_type import KeyType
 
 from .pres_exch import (
@@ -99,6 +100,7 @@ def __init__(
             self.proof_type = Ed25519Signature2018.signature_type
         else:
             self.proof_type = proof_type
+        self.is_holder = False
 
     async def _get_issue_suite(
         self,
@@ -191,7 +193,7 @@ async def get_sign_key_credential_subject_id(
         else:
             reqd_key_type = KeyType.ED25519
         for cred in applicable_creds:
-            if len(cred.subject_ids) > 0:
+            if cred.subject_ids and len(cred.subject_ids) > 0:
                 if not issuer_id:
                     for cred_subject_id in cred.subject_ids:
                         if not cred_subject_id.startswith("urn:"):
@@ -373,13 +375,27 @@ async def filter_constraints(
                 continue
 
             applicable = False
+            is_holder_field_ids = self.field_ids_for_is_holder(constraints)
             for field in constraints._fields:
                 applicable = await self.filter_by_field(field, credential)
-                if applicable:
+                # all fields in the constraint should be satisfied
+                if not applicable:
                     break
+                # is_holder with required directive requested for this field
+                if applicable and field.id and field.id in is_holder_field_ids:
+                    # Missing credentialSubject.id - cannot verify that holder of claim
+                    # is same as subject
+                    if not credential.subject_ids or len(credential.subject_ids) == 0:
+                        applicable = False
+                        break
+                    # Holder of claim is not same as the subject
+                    if not await self.process_constraint_holders(
+                        subject_ids=credential.subject_ids
+                    ):
+                        applicable = False
+                        break
             if not applicable:
                 continue
-
             if constraints.limit_disclosure == "required":
                 credential_dict = credential.cred_value
                 new_credential_dict = self.reveal_doc(
@@ -400,6 +416,32 @@ async def filter_constraints(
             result.append(credential)
         return result
 
+    def field_ids_for_is_holder(self, constraints: Constraints) -> Sequence[str]:
+        """Return list of field ids for whose subject holder verification is requested."""
+        reqd_field_ids = set()
+        if not constraints.holders:
+            reqd_field_ids = []
+            return reqd_field_ids
+        for holder in constraints.holders:
+            if holder.directive == "required":
+                reqd_field_ids = set.union(reqd_field_ids, set(holder.field_ids))
+        return list(reqd_field_ids)
+
+    async def process_constraint_holders(
+        self,
+        subject_ids: Sequence[str],
+    ) -> bool:
+        """Check if holder or subject of claim still controls the identifier."""
+        async with self.profile.session() as session:
+            wallet = session.inject(BaseWallet)
+            try:
+                for subject_id in subject_ids:
+                    await wallet.get_local_did(subject_id.replace("did:sov:", ""))
+                self.is_holder = True
+                return True
+            except (WalletError, WalletNotFoundError):
+                return False
+
     def create_vcrecord(self, cred_dict: dict) -> VCRecord:
         """Return VCRecord from a credential dict."""
         proofs = cred_dict.get("proof") or []
@@ -1146,7 +1188,7 @@ async def create_vp(
         submission_property = PresentationSubmission(
             id=str(uuid4()), definition_id=pd.id, descriptor_maps=descriptor_maps
         )
-        if self.check_sign_pres(applicable_creds):
+        if self.is_holder:
             (
                 issuer_id,
                 filtered_creds_list,
@@ -1199,15 +1241,6 @@ async def create_vp(
             else:
                 return vp
 
-    def check_sign_pres(self, creds: Sequence[VCRecord]) -> bool:
-        """Check if applicable creds have CredentialSubject.id set."""
-        for cred in creds:
-            if len(cred.subject_ids) > 0 and not self.check_if_cred_id_derived(
-                next(iter(cred.subject_ids))
-            ):
-                return True
-        return False
-
     def check_if_cred_id_derived(self, id: str) -> bool:
         """Check if credential or credentialSubjet id is derived."""
         if id.startswith("urn:bnid:_:c14n"):


Test patch:
diff --git a/aries_cloudagent/protocols/present_proof/dif/tests/test_data.py b/aries_cloudagent/protocols/present_proof/dif/tests/test_data.py
--- a/aries_cloudagent/protocols/present_proof/dif/tests/test_data.py
+++ b/aries_cloudagent/protocols/present_proof/dif/tests/test_data.py
@@ -49,6 +49,153 @@ def create_vcrecord(cred_dict: dict, expanded_types: list):
     )
 
 
+is_holder_pd = PresentationDefinition.deserialize(
+    {
+        "id": "32f54163-7166-48f1-93d8-ff217bdb0653",
+        "submission_requirements": [
+            {
+                "name": "European Union Citizenship Proofs",
+                "rule": "all",
+                "from": "A",
+            }
+        ],
+        "input_descriptors": [
+            {
+                "id": "citizenship_input_1",
+                "group": ["A"],
+                "schema": [
+                    {"uri": "https://www.w3.org/2018/credentials#VerifiableCredential"},
+                    {"uri": "https://w3id.org/citizenship#PermanentResidentCard"},
+                ],
+                "constraints": {
+                    "is_holder": [
+                        {
+                            "directive": "required",
+                            "field_id": ["1f44d55f-f161-4938-a659-f8026467f126"],
+                        }
+                    ],
+                    "fields": [
+                        {
+                            "id": "1f44d55f-f161-4938-a659-f8026467f126",
+                            "path": ["$.issuanceDate", "$.vc.issuanceDate"],
+                            "filter": {
+                                "type": "string",
+                                "format": "date",
+                                "maximum": "2014-5-16",
+                            },
+                        }
+                    ],
+                },
+            }
+        ],
+    }
+)
+
+is_holder_pd_multiple_fields_included = PresentationDefinition.deserialize(
+    {
+        "id": "32f54163-7166-48f1-93d8-ff217bdb0653",
+        "submission_requirements": [
+            {
+                "name": "European Union Citizenship Proofs",
+                "rule": "all",
+                "from": "A",
+            }
+        ],
+        "input_descriptors": [
+            {
+                "id": "citizenship_input_1",
+                "group": ["A"],
+                "schema": [
+                    {"uri": "https://www.w3.org/2018/credentials#VerifiableCredential"},
+                    {"uri": "https://w3id.org/citizenship#PermanentResidentCard"},
+                ],
+                "constraints": {
+                    "is_holder": [
+                        {
+                            "directive": "required",
+                            "field_id": [
+                                "1f44d55f-f161-4938-a659-f8026467f126",
+                                "1f44d55f-f161-4938-a659-f8026467f127",
+                            ],
+                        }
+                    ],
+                    "fields": [
+                        {
+                            "id": "1f44d55f-f161-4938-a659-f8026467f126",
+                            "path": ["$.issuanceDate", "$.vc.issuanceDate"],
+                            "filter": {
+                                "type": "string",
+                                "format": "date",
+                                "maximum": "2014-5-16",
+                            },
+                        },
+                        {
+                            "id": "1f44d55f-f161-4938-a659-f8026467f127",
+                            "path": ["$.issuanceDate", "$.vc.issuanceDate"],
+                            "filter": {
+                                "type": "string",
+                                "format": "date",
+                                "minimum": "2005-5-16",
+                            },
+                        },
+                    ],
+                },
+            }
+        ],
+    }
+)
+
+is_holder_pd_multiple_fields_excluded = PresentationDefinition.deserialize(
+    {
+        "id": "32f54163-7166-48f1-93d8-ff217bdb0653",
+        "submission_requirements": [
+            {
+                "name": "European Union Citizenship Proofs",
+                "rule": "all",
+                "from": "A",
+            }
+        ],
+        "input_descriptors": [
+            {
+                "id": "citizenship_input_1",
+                "group": ["A"],
+                "schema": [
+                    {"uri": "https://www.w3.org/2018/credentials#VerifiableCredential"},
+                    {"uri": "https://w3id.org/citizenship#PermanentResidentCard"},
+                ],
+                "constraints": {
+                    "is_holder": [
+                        {
+                            "directive": "required",
+                            "field_id": ["1f44d55f-f161-4938-a659-f8026467f126"],
+                        }
+                    ],
+                    "fields": [
+                        {
+                            "id": "1f44d55f-f161-4938-a659-f8026467f126",
+                            "path": ["$.issuanceDate", "$.vc.issuanceDate"],
+                            "filter": {
+                                "type": "string",
+                                "format": "date",
+                                "maximum": "2014-5-16",
+                            },
+                        },
+                        {
+                            "id": "1f44d55f-f161-4938-a659-f8026467f127",
+                            "path": ["$.issuanceDate", "$.vc.issuanceDate"],
+                            "filter": {
+                                "type": "string",
+                                "format": "date",
+                                "minimum": "2005-5-16",
+                            },
+                        },
+                    ],
+                },
+            }
+        ],
+    }
+)
+
 creds_with_no_id = [
     create_vcrecord(
         {
diff --git a/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch.py b/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch.py
--- a/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch.py
+++ b/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch.py
@@ -142,6 +142,17 @@ def test_submission_requirements_from_both_present(self):
         with self.assertRaises(BaseModelError) as cm:
             (SubmissionRequirements.deserialize(test_json)).serialize()
 
+    def test_submission_requirements_from_both_missing(self):
+        test_json = """
+            {
+                "name": "Citizenship Information",
+                "rule": "pick",
+                "count": 1
+            }
+        """
+        with self.assertRaises(BaseModelError) as cm:
+            (SubmissionRequirements.deserialize(test_json)).serialize()
+
     def test_is_holder(self):
         test_json = """
             {
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
@@ -48,6 +48,9 @@
     bbs_signed_cred_no_credsubjectid,
     bbs_signed_cred_credsubjectid,
     creds_with_no_id,
+    is_holder_pd,
+    is_holder_pd_multiple_fields_excluded,
+    is_holder_pd_multiple_fields_included,
 )
 
 
@@ -77,6 +80,10 @@ async def setup_tuple(profile):
         await wallet.create_local_did(
             method=DIDMethod.SOV, key_type=KeyType.ED25519, did="WgWxqztrNooG92RXvxSTWv"
         )
+        await wallet.create_local_did(
+            method=DIDMethod.KEY,
+            key_type=KeyType.BLS12381G2,
+        )
         creds, pds = get_test_data()
         return creds, pds
 
@@ -2149,10 +2156,6 @@ async def test_create_vp_no_issuer(self, profile, setup_tuple):
             "merge",
             async_mock.CoroutineMock(),
         ) as mock_merge, async_mock.patch.object(
-            DIFPresExchHandler,
-            "check_sign_pres",
-            async_mock.CoroutineMock(),
-        ) as mock_check_sign_pres, async_mock.patch.object(
             test_module,
             "create_presentation",
             async_mock.CoroutineMock(),
@@ -2160,7 +2163,7 @@ async def test_create_vp_no_issuer(self, profile, setup_tuple):
             mock_make_req.return_value = async_mock.MagicMock()
             mock_apply_req.return_value = async_mock.MagicMock()
             mock_merge.return_value = (VC_RECORDS, {})
-            mock_check_sign_pres.return_value = True
+            dif_pres_exch_handler.is_holder = True
             mock_create_vp.return_value = {"test": "1"}
             did_info = DIDInfo(
                 did="did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL",
@@ -2204,10 +2207,6 @@ async def test_create_vp_with_bbs_suite(self, profile, setup_tuple):
             "merge",
             async_mock.CoroutineMock(),
         ) as mock_merge, async_mock.patch.object(
-            DIFPresExchHandler,
-            "check_sign_pres",
-            async_mock.CoroutineMock(),
-        ) as mock_check_sign_pres, async_mock.patch.object(
             test_module,
             "create_presentation",
             async_mock.CoroutineMock(),
@@ -2219,7 +2218,7 @@ async def test_create_vp_with_bbs_suite(self, profile, setup_tuple):
             mock_make_req.return_value = async_mock.MagicMock()
             mock_apply_req.return_value = async_mock.MagicMock()
             mock_merge.return_value = (cred_list, {})
-            mock_check_sign_pres.return_value = True
+            dif_pres_exch_handler.is_holder = True
             mock_create_vp.return_value = {"test": "1", "@context": ["test"]}
             mock_sign_vp.return_value = {
                 "test": "1",
@@ -2264,10 +2263,6 @@ async def test_create_vp_no_issuer_with_bbs_suite(self, profile, setup_tuple):
             "merge",
             async_mock.CoroutineMock(),
         ) as mock_merge, async_mock.patch.object(
-            DIFPresExchHandler,
-            "check_sign_pres",
-            async_mock.CoroutineMock(),
-        ) as mock_check_sign_pres, async_mock.patch.object(
             test_module,
             "create_presentation",
             async_mock.CoroutineMock(),
@@ -2279,7 +2274,7 @@ async def test_create_vp_no_issuer_with_bbs_suite(self, profile, setup_tuple):
             mock_make_req.return_value = async_mock.MagicMock()
             mock_apply_req.return_value = async_mock.MagicMock()
             mock_merge.return_value = (cred_list, {})
-            mock_check_sign_pres.return_value = True
+            dif_pres_exch_handler.is_holder = True
             mock_create_vp.return_value = {"test": "1", "@context": ["test"]}
             mock_sign_key_cred_subject.return_value = (None, [])
             did_info = DIDInfo(
@@ -3062,3 +3057,95 @@ async def test_multiple_applicable_creds_with_no_auto_and_no_record_ids(
                 pd=tmp_pd,
                 challenge="1f44d55f-f161-4938-a659-f8026467f126",
             )
+
+    @pytest.mark.asyncio
+    @pytest.mark.ursa_bbs_signatures
+    async def test_is_holder_valid_a(self, profile, setup_tuple):
+        context = profile.context
+        context.update_settings({"debug.auto_respond_presentation_request": True})
+        dif_pres_exch_handler = DIFPresExchHandler(profile)
+        cred_list, pd_list = setup_tuple
+        tmp_vp = await dif_pres_exch_handler.create_vp(
+            credentials=cred_list,
+            pd=is_holder_pd,
+            challenge="1f44d55f-f161-4938-a659-f8026467f126",
+        )
+        assert len(tmp_vp.get("verifiableCredential")) == 6
+        assert tmp_vp.get("proof")
+
+    @pytest.mark.asyncio
+    @pytest.mark.ursa_bbs_signatures
+    async def test_is_holder_valid_b(self, profile, setup_tuple):
+        dif_pres_exch_handler = DIFPresExchHandler(profile)
+        cred_list, pd_list = setup_tuple
+        tmp_vp = await dif_pres_exch_handler.create_vp(
+            credentials=cred_list,
+            pd=is_holder_pd_multiple_fields_included,
+            challenge="1f44d55f-f161-4938-a659-f8026467f126",
+        )
+        assert len(tmp_vp.get("verifiableCredential")) == 6
+        assert tmp_vp.get("proof")
+
+    @pytest.mark.asyncio
+    @pytest.mark.ursa_bbs_signatures
+    async def test_is_holder_valid_c(self, profile, setup_tuple):
+        dif_pres_exch_handler = DIFPresExchHandler(profile)
+        cred_list, pd_list = setup_tuple
+        tmp_vp = await dif_pres_exch_handler.create_vp(
+            credentials=cred_list,
+            pd=is_holder_pd_multiple_fields_excluded,
+            challenge="1f44d55f-f161-4938-a659-f8026467f126",
+        )
+        assert len(tmp_vp.get("verifiableCredential")) == 6
+        assert tmp_vp.get("proof")
+
+    @pytest.mark.asyncio
+    @pytest.mark.ursa_bbs_signatures
+    async def test_is_holder_signature_suite_mismatch(self, profile, setup_tuple):
+        dif_pres_exch_handler = DIFPresExchHandler(
+            profile, proof_type=BbsBlsSignature2020.signature_type
+        )
+        cred_list, pd_list = setup_tuple
+        tmp_vp = await dif_pres_exch_handler.create_vp(
+            credentials=cred_list,
+            pd=is_holder_pd,
+            challenge="1f44d55f-f161-4938-a659-f8026467f126",
+        )
+        assert len(tmp_vp.get("verifiableCredential")) == 6
+        assert not tmp_vp.get("proof")
+
+    @pytest.mark.asyncio
+    @pytest.mark.ursa_bbs_signatures
+    async def test_is_holder_subject_mismatch(self, profile, setup_tuple):
+        dif_pres_exch_handler = DIFPresExchHandler(
+            profile, proof_type=BbsBlsSignature2020.signature_type
+        )
+        cred_list, pd_list = setup_tuple
+        updated_cred_list = []
+        for tmp_cred in deepcopy(cred_list):
+            tmp_cred.subject_ids = ["did:sov:test"]
+            updated_cred_list.append(tmp_cred)
+        tmp_vp = await dif_pres_exch_handler.create_vp(
+            credentials=updated_cred_list,
+            pd=is_holder_pd,
+            challenge="1f44d55f-f161-4938-a659-f8026467f126",
+        )
+        assert len(tmp_vp.get("verifiableCredential")) == 0
+        assert not tmp_vp.get("proof")
+
+    @pytest.mark.asyncio
+    @pytest.mark.ursa_bbs_signatures
+    async def test_is_holder_missing_subject(self, profile, setup_tuple):
+        dif_pres_exch_handler = DIFPresExchHandler(
+            profile, proof_type=BbsBlsSignature2020.signature_type
+        )
+        cred_list, pd_list = setup_tuple
+        tmp_cred = deepcopy(cred_list[0])
+        tmp_cred.subject_ids = None
+        tmp_vp = await dif_pres_exch_handler.create_vp(
+            credentials=[tmp_cred],
+            pd=is_holder_pd,
+            challenge="1f44d55f-f161-4938-a659-f8026467f126",
+        )
+        assert len(tmp_vp.get("verifiableCredential")) == 0
+        assert not tmp_vp.get("proof")