Present Credentials for Verification

Use the agent's stored credentials to present a cryptographic proof to a Verifier

After establishing an end-to-end encrypted connection with peers, the agent is capable of receiving presentation requests and sending credential proofs to verifiers they are connected with. The ProofExchangeModule provides the functionality needed to easily walk through the Decentralized Identity protocol flow for handling credential presentation requests and responding with the desired credentials.

The functionality of the ProofExchangeModule is accessed via the agent's fields: agent.proofs.exchange. The functionality provided is described below.

Receive a Presentation Request

A ProofExchange (a pending proof presentation / proof in exchange) is created in the agent's wallet once a presentation request is received. The agent receives these requests as encrypted messages, either through the receiveMessage API, or via the the MessageSource of the agent's run loop. The presentation request must come from an established connection, and the ProofExchange will contain information about which connection the request came from.

After a request is decrypted and processed by the agent, a ProofExchange is created in the agent's wallet and can be accessed and used by the APIs mentioned below. If the agent is subscribed to agent events (see here), a new ProofExchange will invoke a proof update event.

Finding Credentials for a Presentation

After receiving a ProofExchange in the REQUEST state, the consumer should query the agent to discover what claims the Verifier is requesting to be presented, and what credentials the agent has that will satisfy each claim.

The retrieveCredentialsForProofRequest API, can be used to retrieve the credentials that are appropriate for each requested claim, as well as details about what exactly that claim is requesting. The API returns a RetrievedCredentials data object to represent this.

RetrievedCredentials will contain a set of "attribute groups" that are being requested by the Verifier. Where each "attribute group" (RetrievedAttributeGroupCredentials), will contain:

  • groupIdentifier - string reference for uniquely identifying this group amongst the set of groups. In Aries protocols, this is often called the "referent"/"attribute referent".

  • groupAttributes - set of one or more credential attribute names that this group is requesting be presented together (e.g. "Name", "Date_of_birth", "Postcode")

  • credentialIds - set of IDs of Credential objects (see Credential management section) that are suitable for presenting this group. A credential's "suitability" is determined by the agent by checking that it possesses the attributes being requested by groupAttributes, and that any restrictions imposed by the Verifier are satisfied (e.g. "only Utah Driver's Licenses").

let id: String // ID of the proof exchange to get credentials for ([ProofExchange.proofExchangeId])

do {
    let suitableCreds: RetrievedCredentials =
        try await agent.proofs.exchange.retrieveCredentialsForProofRequest(proofExchangeId: id)

    suitableCreds.requestedAttributes.forEach { group in
        print(
            "Attribute group '\(group.groupIdentifier)' " +
                "is requesting proof for the attributes: \(group.groupAttributes). " +
                "The following credentials can be used: \(group.credentialIds)"
        )
        
    }
} catch {
    // Handle error
}

In the case where an agent holds no Credentials that are appropriate for a requested attribute group, a RetrievedAttributeGroupCredentials object will still be present, but credentialIds will contain no entries.

Presenting Credentials

After identifying credentials that are appropriate for presenting a proof exchange (see above section), the consumer must select a Credential to use for each requested attribute group. This selection is done by constructing a PresentationCredentials data object before passing it to the presentProof API where credential proofs will then be created and sent to the Verifier.

The PresentationCredentials must be constructed with an entry for the requestedAttributes field. Where requestedAttributes should be a map of groupIdentifiers (for each requested RetrievedAttributeGroupCredentials), to the PresentationAttributeGroup object which specifies what credential to use for presenting that group.

The PresentationAttributeGroup object requires that a credentialId be selected (i.e. by selecting from the list of credentialIds from the RetrievedAttributeGroupCredentials object). Additionally, PresentationAttributeGroup can be used to specify whether or not the value of the credential's attributes should be revealed to the Verifier. If revealed is set to false, the Verifier will receive proof of the Credential, without seeing any of the credential's attribute values.

// ID of the proof exchange to present credentials for ([ProofExchange.proofExchangeId])
let id: String
// creds suitable for presentation. Retrieved from the retrieveCredentialsForProofRequest API
let suitableCreds: RetrievedCredentials

let credsForGroups: [RetrievedAttributeGroupCredentials] =
    suitableCreds.requestedAttributes

// select the 1st cred for presentation of group '0'
let selectedCredForFirstGroup =
    PresentationAttributeGroup(credentialId: credsForGroups[0].credentialIds[0])
// select the 1st cred for presentation of group '1'
let selectedCredForSecondGroup =
PresentationAttributeGroup(credentialId: credsForGroups[1].credentialIds[0])

let selectedCreds = PresentationCredentials(
    requestedAttributes: [
        credsForGroups[0].groupIdentifier : selectedCredForFirstGroup,
        credsForGroups[1].groupIdentifier : selectedCredForSecondGroup
    ]
)

do {
    try await agent.proofs.exchange.presentProof(proofExchangeId: id, presentationCredentials: selectedCreds)
} catch {
    // Handle error
}

For simplicity, the example assumes exactly 2 attribute groups have been requested, and each group has at least 1 suitable credential. In most use cases, the selection would be made dynamically by the UI.

Get a Pending Presentation by ID

As mentioned above, a pending presentation is represented by ProofExchange objects. To retrieve the current state of a specific ProofExchange in the agent's wallet, the getById API can be used. If a proof exchange cannot be found by the given ID, then null is returned:

let id: String // ID of the proof exchange to get ([ProofExchange.proofExchangeId])

do {
    let proofExchange = try await agent.proofs.exchange.getById(proofExchangeId: id)
} catch {
    // handle error
}

Delete a Pending Presentation by ID

Similarly, a ProofExchange in the wallet can be easily deleted via the deleteById API:

let id: String // ID of the proof exchange to delete ([ProofExchange.proofExchangeId])

do {
    try await agent.proofs.exchange.deleteById(proofExchangeId: id)
} catch {
    // handle error
}

Updating the Metadata of a Pending Presentation

ProofExchange objects contain some metadata that can be controlled by SDK consumers, allowing custom information to be attached to each ProofExchange, and allowing custom listing functionality to be leveraged.

Each ProofExchange contains a list of RecordTag (ProofExchange.tags) attached to it, where a RecordTag is simply a name-value pair stored with the record. By default, some tags are attached to new ProofExchanges, this includes:

  • tag-name: ~started_timestamp, tag-value: The UNIX epoch seconds which this presentation began exchange

The tags on a ProofExchange can be replaced or updated by using the updateProofExchange API, and providing a new set to update. This will replace whatever the current set of tags is:

let id: String // ID of the proof exchange to update ([ProofExchange.proofExchangeId])

// add a 'category' of 'work' to this proof, and a 'priority' of '1'
let update = ProofExchangeUpdate(tags: [
    RecordTag(name: "category", value: "work"),
    RecordTag(name: "~priority", value: "1")
])

do {
    try await agent.proofs.exchange.updateProofExchange(proofExchangeId: id, proofExchangeUpdate: update)
} catch {
    // handle error
}

Like most data in the wallet, RecordTag will be stored encrypted. Unless, the tag name is prefixed with ~, then the tag value will be stored unencrypted. Storing a tag value as unencrypted will allow some additional listing queries to be performed (see below).

Listing Pending Presentations

To list all pending presentations in the agent's wallet, the listAll API can be used:

do {
    let proofs = try await agent.proofs.exchange.listAll(options: nil)
} catch {
    // handle error
}

Filtered Listing

More complicated ProofExchange list queries can also be achieved by utilizing the ListProofExchangeFilters.

These filters allow for lists of ProofExchange to be filtered by; their state, by their tags, or filtered by both together.

Filtering by state can be achieved like so:

// get all the pending proofs which are waiting to be presented
let filters = ListProofExchangeFilters(state: ProofExchangeState.request)
let options = ListProofExchangeOptions(filters: filters)
    
do {
    let proofsPendingPresentation = try await agent.proofs.exchange.listAll(options: options)
} catch {
    // Handle error
}

To filter by tags applied to the ProofExchange (i.e. applied via the update API), the tagFilter field of ListProofExchangeFilters should be used. This field takes a String in compliance with a Wallet Query Language (WQL) Query.

Continuing from the example in the Update API section:

// WQL Query, filter for 'category' == 'work'
let wqlQuery = "{ \"category\": \"work\" }"
let filters = ListProofExchangeFilters(tagFilter: wqlQuery)
let options = ListProofExchangeOptions(filters: filters)
do {
    let pendingWorkProofs = try await agent.proofs.exchange.listAll(options: options)
} catch {
    // handle error
}
// WQL Query, filter for priority < 2 (e.g. 'high' priority items)
let wqlQuery = "{ \"~priority\": { \"$lt\": \"2\" } }"
let filters = ListProofExchangeFilters(tagFilter: wqlQuery)
let options = ListProofExchangeOptions(filters: filters)

do {
    let highPriorityPendingProofs = try await agent.proofs.exchange.listAll(options: options)
} catch {
    // Handle error
}

Last updated