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.

Proof Exchange Formats

Similar to Credential Exchanges, the ProofExchange object can represent different proof exchange formats (see Standards and Protocols). ProofExchange contains a formatData field, which is a ProofExchangeFormatData data structure with variants for the different supported formats. Currently these format variants include:

  • .Indy: contains the details of a presentation exchange being performed with Aries Indy Attachments. This format is used to exchange Anoncreds presentation requests & presentations. When the Edge Agent receives a ProofExchange request of this format, the SDK can select and present it's AnoncredV1 Credentials in response.

  • .Dif: contains the details of a presentation exchange being performed with a DIF Presentation Exchange. When the Edge Agent receives a ProofExchange request of this format, the SDK can select and present it's W3C Credentials in response.

The inner contents of these formatData objects particularly contains important details about what presentation items (requirements) which are being requested. In the case of Anoncreds, this may include groups of attributes and/or predicates that the verifier wishes to be presented (each with their own requirements). Or in the case of DIF, it will be a Presentation Definition, including several Input Descriptors that the verifier wishes to be presented (each with their own requirements).

In all cases, the inner data is structured in line with the relevant specifications. SDK consumers should make themselves familiar with these standard formats to understand how the presentation request data should be processed and/or displayed to the end application user.

Check out our sample apps for a reference on how these data formats can be processed in an application.

Finding Credentials for a Presentation

After receiving a ProofExchange in the REQUEST state, the consumer should query the agent to discover which credentials the agent has that will satisfy each item.

The retrieveCredentialsForProofRequest API, can be used for this. This API returns a RetrievedPresentationCredentials data structure to represent this.

RetrievedPresentationCredentials has variants depending on the Proof Exchange format that is being used (see Proof Exchange Formats). The inner data of each variant maps the identifiers of the requested items to the IDs of Credentials that are suitable for presenting the requested item.

Credentials for an Anoncreds Presentation

When the retrieveCredentialsForProofRequest API is used on an .Indy formatted ProofExchange, it will return a RetrievedPresentationCredentials.Indy data object. This object contains:

  • credentialsForRequestedAttributes: a map of requested attribute group referents/IDs, to the list of Credential IDs which may be used for presenting the attribute group. Attribute group IDs correspond to the referents in the AnoncredProofRequestInfo object of formatData.

  • credentialsForRequestedPredicates: similar to above, this contains a map of requested predicate referents/IDs, to the list of Credential IDs which may be used for presenting the predicate.

let agent: SudoDIEdgeAgent
let proofExchange: ProofExchange

do {
    guard case .indy(let anoncredsProofRequest) = proofExchange.formatData else {
        return
    }
    
    let suitableCreds = try await agent.proofs.exchange.retrieveCredentialsForProofRequest(
        proofExchangeId: proofExchange.proofExchangeId
    )
    guard case .indy(
        let credentialsForRequestedAttributes,
        let credentialsForRequestedPredicates
    ) = suitableCreds else {
        return
    }

    credentialsForRequestedAttributes.forEach { groupIdentifier, creds in
        let groupInfo = anoncredsProofRequest.requestedAttributes[groupIdentifier]!
        print("Attribute group \(groupInfo) can be presented with the following creds: \(creds)")
    }
    
    credentialsForRequestedPredicates.forEach { predicateId, creds in
        let predicateInfo = anoncredsProofRequest.requestedPredicates[predicateId]!
        print("predicate \(predicateInfo) can be presented with the following creds: \(creds)")
    }
} catch {
    // handle error
}

In the case where an agent holds no Credentials that are appropriate for a requested attribute group or predicate, a mapping to the referent will still be present, but will be mapped to an empty list.

Credentials for a DIF (W3C) Presentation

When the retrieveCredentialsForProofRequest API is used on an .Dif formatted ProofExchange, it will return a RetrievedPresentationCredentials.Dif data object. This object contains:

  • credentialsForRequestedDescriptors: a map of requested DIF Input Descriptor IDs, to the list of Credential IDs which may be used for presenting the input descriptor. Descriptor IDs correspond to the InputDescriptors within formatData.

let agent: SudoDIEdgeAgent
let proofExchange: ProofExchange

do {
    guard case .dif(let presentationDefinition) = proofExchange.formatData else {
        return
    }
    
    let suitableCreds = try await agent.proofs.exchange.retrieveCredentialsForProofRequest(
        proofExchangeId: proofExchange.proofExchangeId
    )
    guard case .dif(let credentialsForRequestedDescriptors) = suitableCreds else {
        return
    }

    credentialsForRequestedDescriptors.forEach { descriptorId, creds in
        let inputDescriptor = presentationDefinition.inputDescriptors.first { $0.id == descriptorId }!
        print("Input Descriptor \(inputDescriptor) can be presented with the following creds: \(creds)")
    }
} catch {
    // handle error
}

In the case where an agent holds no Credentials that are appropriate for a requested Input Descriptor, a mapping to the descriptor will still be present, but will be mapped to an empty list.

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 presentation item. 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.

However, as before, the PresentationCredentials object has variants depending on the presentation exchange format being presented.

Presenting for an Anoncreds Presentation

Like other APIs, if presenting an Anoncreds presentation, then the .Indy variant should be used. In this case, a PresentationCredentials.Indy data object should be constructed with the following data:

  • credentialsForRequestedAttributes: a map of requested attribute group referents/IDs, to an AnoncredPresentationAttributeGroup object. This object contains the credentialId of the credential to present for the attribute group, and a revealed boolean flag. "revealed" is an Anoncreds zero-knowledge-proof feature: if revealed is set to false, the Verifier will receive proof of the Credential, without seeing any of the credential's attribute values.

  • credentialsForRequestedPredicates: a map of requested predicate referents/IDs, to an AnoncredPresentationPredicate object. This object simply contains the credentialId of the credential to present for this predicate.

let agent: SudoDIEdgeAgent
let id: String // the id of the [ProofExchange]

do {
    let suitableCreds = try await agent.proofs.exchange.retrieveCredentialsForProofRequest(
        proofExchangeId: id
    )
    guard case .indy(
        let credentialsForRequestedAttributes,
        let credentialsForRequestedPredicates
    ) = suitableCreds else {
        return
    }
    
    let selectedCredsForRequestedAttributes = credentialsForRequestedAttributes.mapValues { creds in
        // auto-select the first credential for presentation
        AnoncredPresentationAttributeGroup(credentialId: creds[0], revealed: true)
    }
    
    let selectedCredsForRequestedPredicates = credentialsForRequestedPredicates.mapValues { creds in
        // auto-select the first credential for presentation
        AnoncredPresentationPredicate(credentialId: creds[0])
    }
    
    try await agent.proofs.exchange.presentProof(
        proofExchangeId: id,
        presentationCredentials: .indy(
            credentialsForRequestedAttributes: selectedCredsForRequestedAttributes,
            credentialsForRequestedPredicates: selectedCredsForRequestedPredicates
        )
    )
} catch {
    // handle error
}

For simplicity, this example assumes that there is at least 1 suitable credential for each attribute group and predicate. Additionally this example implements "auto-select", apps may wish to let their users manually select credentials. See our sample apps for a reference.

Presenting for a DIF (W3C) Presentation

Like other APIs, if presenting a DIF presentation, then the .Dif variant should be used. In this case, a PresentationCredentials.Dif data object should be constructed with the following data:

  • credentialsForDescriptors: a map of requested input descriptor IDs, to the ID of the Credential that should present the descriptor.

If possible, the Edge Agent SDK will automatically attempt to derive a selectively disclosed credential using BBS+. Meaning the agent will not present more data then necessary to the verifier; similar to Anoncreds. See Aries RFC 0646 and Standards and Protocols.

let agent: SudoDIEdgeAgent
let id: String // the id of the [ProofExchange]

do {
    let suitableCreds = try await agent.proofs.exchange.retrieveCredentialsForProofRequest(
        proofExchangeId: id
    )
    guard case .dif(let credentialsForRequestedDescriptors) = suitableCreds else {
        return
    }
    
    let selectedCredsForDescriptors = credentialsForRequestedDescriptors.mapValues { creds in
        // auto-select the first credential for presentation
        creds[0]
    }
    
    try await agent.proofs.exchange.presentProof(
        proofExchangeId: id,
        presentationCredentials: .dif(
            credentialsForDescriptors: selectedCredsForDescriptors
        )
    )
} catch {
    // handle error
}

For simplicity, this example assumes that there is at least 1 suitable credential for each input descriptor. Additionally this example implements "auto-select", apps may wish to let their users manually select credentials. See our sample apps for a reference.

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