aries-cloudagent-python icon indicating copy to clipboard operation
aries-cloudagent-python copied to clipboard

Indy proof request returns credentials even when predicate is not satisfied

Open nb-vivek-bodar opened this issue 2 months ago • 5 comments

Description:

  • When a Verifier sends a proof request containing a predicate that the Holder's credential does not satisfy, the Holder agent still returns that credential in the GET /present-proof-2.0/records/{pres_ex_id}/credentials API call. This leads to confusion, as the expectation is that only matching credentials (those satisfying all restrictions, including predicates) should be returned.

Steps to Reproduce:

  • Preconditions:
  1. ACA-Py instance running in Holder role.
  2. Holder already has a stored Indy credential in their wallet as shown below
  3. GET /credentials call should return Indy credential
{
 "results": [
   {
     "referent": "253e2370-284c-4254-9bee-be80b96a51b2",
     "schema_id": "9ZUiWwsNkk2iZZu67BRu3K:2:Driving_license:1.0",
     "cred_def_id": "9ZUiWwsNkk2iZZu67BRu3K:3:CL:2926088:Driving_License",
     "rev_reg_id": "9ZUiWwsNkk2iZZu67BRu3K:4:9ZUiWwsNkk2iZZu67BRu3K:3:CL:2926088:Driving_License:CL_ACCUM:5c2950a7-60ec-49c2-af3a-df8761dd8cec",
     "cred_rev_id": "1",
     "attrs": {
       "dob_dateint": "19900101",
       "first_name": "John",
       "last_name": "Doe"
     }
   }
 ]
}

1. Verifier sends proof request (via POST /present-proof-2.0/send-request) with:

  • Requested attributes: first_name, last_name
  • Predicate: dob_dateint >= 20000101 (which is not satisfied, as the stored value is 19900101)
{
  "auto_remove": false,
  "auto_verify": false,
  "comment": "string",
  "connection_id": "66c0622e-b33b-450c-9e85-0d6fb7055331",
  "presentation_request": {
    "indy": {
      "name": "Proof request",
      "non_revoked": {
        "from": 1640995199,
        "to": 1640995199
      },
      "nonce": "1",
      "requested_attributes": {
        "additionalProp1": {
          "names": [
            "first_name",
            "last_name"
          ],
          "non_revoked": {
            "from": 1640995199,
            "to": 1640995199
          },
          "restrictions": [
            {
              "cred_def_id": "9ZUiWwsNkk2iZZu67BRu3K:3:CL:2926088:Driving_License"
            }
          ]
        }
      },
      "requested_predicates": {
        "additionalProp1": {
          "name": "dob_dateint",
          "non_revoked": {
            "from": 1640995199,
            "to": 1640995199
          },
          "p_type": ">=",
          "p_value": 20000101,
          "restrictions": [
            {
               "cred_def_id": "9ZUiWwsNkk2iZZu67BRu3K:3:CL:2926088:Driving_License"
            }
          ]
        }
      }
    }
  },
  "trace": false
}

2. Holder queries matching credentials via:

curl -X GET \
  'http://<aca-py-holder-host>/present-proof-2.0/records/<pres_ex_id>/credentials?count=10&start=0' \
  -H 'accept: application/json' \
  -H 'X-API-KEY: <api-key>'
  • Expected Result:

  • No credentials should be returned for the predicate (dob_dateint >= 20000101), since the credential value is 19900101, which does not meet the predicate.

  • Actual Result:

  • The credential is returned as a matching candidate:

[
  {
    "cred_info": {
      "referent": "253e2370-284c-4254-9bee-be80b96a51b2",
      "schema_id": "9ZUiWwsNkk2iZZu67BRu3K:2:Driving_license:1.0",
      "cred_def_id": "9ZUiWwsNkk2iZZu67BRu3K:3:CL:2926088:Driving_License",
      "rev_reg_id": "9ZUiWwsNkk2iZZu67BRu3K:4:9ZUiWwsNkk2iZZu67BRu3K:3:CL:2926088:Driving_License:CL_ACCUM:5c2950a7-60ec-49c2-af3a-df8761dd8cec",
      "cred_rev_id": "1",
      "attrs": {
        "first_name": "John",
        "dob_dateint": "19900101",
        "last_name": "Doe"
      }
    },
    "interval": {
      "from": 1640995199,
      "to": 1640995199
    },
    "presentation_referents": [
      "additionalProp1"
    ]
  }
]
  • Impact:
  • This behavior can mislead application logic or end-users, as it appears that a valid credential exists for the predicate — but attempting to build the presentation will ultimately fail if the proof is actually constructed and sent.

Environment

  • ACA-Py version: 1.3.0
  • Protocol: didexchange/1.0
  • Transport: HTTP / Admin API

nb-vivek-bodar avatar Oct 03 '25 12:10 nb-vivek-bodar

I read through the AnonCreds spec about this (here and how the credentials that could satisfy the presentation request is outside the scope of the spec, but the "Aries" version of the approach is described. It only talks about the restrictions on the attributes and predicates, and does not talk about the determination of whether each predicate is checked. As such, I think it is possible that ACA-Py is not checking for a true for each predicate.

I agree that it would make sense for that to calculated as part of the determination of what credential sets satisfy a presentation request, if (as it seems) it is not already being done.

swcurran avatar Oct 03 '25 15:10 swcurran

Can confirm that predicate / restrictions are not applied for the "matching credentials"

It looks like the intention was there, to add restrictions to a base tag filter:

            tag_filter = {"$exist": [f"attr::{name}::value" for name in names]}

            if restr:
                # FIXME check if restr is a list or dict? validate WQL format
                tag_filter = {"$and": [tag_filter] + restr}

But the way that restr is read from the requested_attributes and requested_predicates seems to be faulty. I debugged a bit and it shouldn't be too hard to add.

Note: there is an extra_query param that can be passed to the matching credentials endpoint, and that'll be added to the tag filter:

            if extra_query:
                tag_filter = {"$and": [tag_filter, extra_query]}

So it might take some experimenting, but you can manually pass a WQL string to enforce the predicate filtering. It would make sense for it to be automatic.

ff137 avatar Oct 03 '25 21:10 ff137

Are you saying restrictions are not being applied either? That would be very surprising. Is that restrictions on predicates specifically or restrictions in general (attributes and predicates)?

swcurran avatar Oct 03 '25 22:10 swcurran

I think requested_attributes should be filtering correctly. I just tested with requested_predicates, after adding some debug logs.

From the code, all of the names in requested_predicates or requested_attributes are added to the base tag filter. And looks like it adds restrictions too, if passed. But the predicates (p_type and p_value) don't get added into a predicate filter.

I quickly tried to add a predicate filter, but ran into some confusion with the direction of the operations. i.e., using "greater than or equal" gave me credentials that match "less than", so I was maybe doing something wrong. Just hacked something together with Claude, so will have to dig a bit deeper. I can make a draft PR on Monday to show what I tried and maybe get some pointers on using WQL properly

ff137 avatar Oct 03 '25 23:10 ff137

I've opened a draft PR to show what I've tried so far (only for the AnonCreds section, not Indy yet).

As mentioned, the WQL filtering is working opposite to how I expect, so I'll have to dig a bit more when I have opportunity. Just wanted to make the draft so long so I don't forget

ff137 avatar Oct 06 '25 10:10 ff137