Skip to content

Credential Presentation Request

As we explored in the previous section, Self provides a simplified interface for handling Verifiable Credentials, taking care of the complex underlying processes.

Now, let's delve into one of the core functionalities provided by Self - Credential Presentation Requests.

This key operation allows you to access and validate the credentials you need, all while maintaining the robust security and privacy standards inherent in the Verifiable Credentials system.

Implementation

TDLR; You can find the final example at https://github.com/joinself/self-go-sdk/blob/main/examples/credentials/request/main.go

1. Creating a Credential Presentation Request

content, err := message.NewCredentialPresentationRequest().
    Type([]string{"VerifiablePresentation", "CustomPresentation"}).
    Details(
        credential.CredentialTypeLiveness,
        []*message.CredentialPresentationDetailParameter{
            message.NewCredentialPresentationDetailParameter(
                message.OperatorNotEquals,
                "sourceImageHash",
                "",
            ),
        },
    ).
    Details(
        credential.CredentialTypeEmail,
        []*message.CredentialPresentationDetailParameter{
            message.NewCredentialPresentationDetailParameter(
                message.OperatorNotEquals,
                "emailAddress",
                "",
            ),
        },
    ).
    Finish()
if err != nil {
    log.Fatal("failed to encode credential request message", "error", err)
}
val credentialRequest = CredentialPresentationRequestBuilder()
    .presentationType(arrayOf("VerifiablePresentation", "CustomPresentation"))
    .details(
        credentialType = arrayOf("VerifiableCredential","LivenessCredential"),
        parameters = arrayOf(CredentialPresentationDetailParameter.create(
            operator = ComparisonOperator.NOT_EQUALS,
            claimField = "sourceImageHash",
            claimValue = ""
        ))
    )
    .details(
        credentialType = arrayOf("VerifiableCredential","EmailCredential"),
        parameters = arrayOf(CredentialPresentationDetailParameter.create(
            operator = ComparisonOperator.NOT_EQUALS,
            claimField = "emailAddress",
            claimValue = ""
        ))
    )
    .expires(Timestamp.now() + 3600)
    .finish()
 val credentialRequest = CredentialRequest.Builder()
    .setToIdentifier(receiverAddress)
    .setTypes(listOf("VerifiablePresentation", "CustomPresentation"))
    .setDetails(listOf(
        Claim.Builder()
            .setTypes(listOf("VerifiableCredential","EmailCredential"))
            .setSubject(Constants.SUBJECT_EMAIL) // emailAddress
            .setComparisonOperator(ComparisonOperator.NOT_EQUALS.name)
            .setValue("")
            .build()
    ))
    .build()
 let credentialRequest = CredentialRequest.Builder()
        .toIdentifier(receiverAddress)
        .withTypes(["VerifiablePresentation", "CustomPresentation"])
        .withDetails([
            Claim.Builder()
                .withTypes(["VerifiableCredential","EmailCredential"])
                .withSubject(Constants.SUBJECT_EMAIL) // emailAddress
                .withComparisonOperator(ComparisonOperator.NOT_EQUALS.rawValue)
                .build()
                ])
        .build()

2. Sending the Request

err = selfAccount.MessageSend(responderAddress, content)
if err != nil {
    log.Fatal("failed to send credential request message", "error", err)
}

// Store the presentation completer in a map to handle the async response
presentationCompleter := make(chan *message.CredentialPresentationResponse, 1)
requests.Store(
    hex.EncodeToString(content.ID()),
    presentationCompleter,
)
val sendStatus = account.messageSend(responderAddress, credentialRequest)
println("send CredentialPresentation request status: ${sendStatus.code()} - requestId:${credentialRequest.id().toHexString()}")
1
2
3
account.send(credentialRequest) { requestId, error ->
    println("sent credential request $requestId")
}
1
2
3
4
5
Task(priority: .background, operation: {
    try await self.account.send(message: credentialRequest, onAcknowledgement: {requestId, error in
        print("sent credential request: \(requestId) with error: \(error)")
    })
})

3. Receiving and Processing the Response

response := <-presentationCompleter

// Validate the presentations
for _, p := range response.Presentations() {
    err = p.Validate()
    if err != nil {
        log.Warn("failed to validate presentation", "error", err)
        continue
    }

    // Check the presentation references the address we are communicating with
    if !p.Holder().Address().Matches(responderAddress) {
        log.Warn("received a presentation response for a different holder address")
        continue
    }

    // Process credentials...
}
ContentType.CREDENTIAL_PRESENTATION_RESPONSE -> {
    val credentialResponse = CredentialPresentationResponse.decode(content)
    println("Response received with status:${credentialResponse.status().name}")

    credentialResponse.presentations().forEach { presentation ->
        val credentials = presentation.credentials()
        credentials.forEach { credential ->
            val claims = credential.credentialSubjectClaims()
            claims.forEach {
                println(
                    "credential value" +
                    "\ncredentialType:${credential.credentialType()}" +
                    "\nfield:${it.key}" +
                    "\nvalue:${it.value}"
                )
            }
        }
    }
}
account.setOnResponseListener { msg ->
    when (msg) {
        is CredentialResponse -> {
            println("Response received with status:${msg.status().name}")
            msg.credentials().forEach { credential ->
                println("crendential types: ${credential.types()}")
                credential.claims().forEach { claim ->
                    println("claim ${claim.subject()}:${claim.value()}")
                }
            }
        }                
    }
}
account.setOnResponseListener { message in
    print("setOnResponseListener: \(message)")
    switch message {
    case is CredentialResponse:
        let response = message as! CredentialResponse
        print("Handle credential response: \(response)")

    default:
        print("TODO: Handle For other response: \(message)")
        break;
    }
}

4. Validating Presentations and Credentials

For each presentation, we perform several checks: a. Validate the presentation itself:

err = p.Validate()

b. Ensure the presentation is from the expected user:

1
2
3
4
if !p.Holder().Address().Matches(responderAddress) {
    log.Warn("received a presentation response for a different holder address")
    continue
}

c. For each credential in the presentation:

for _, c := range p.Credentials() {
    // Validate the credential
    err = c.Validate()

    // Check if the credential is currently valid
    if c.ValidFrom().After(time.Now()) {
        log.Warn("credential is intended to be used in the future")
        continue
    }

    // Additional checks for non-key-based issuers
    if c.Issuer().Method() != credential.MethodKey {
        // Verify the issuer's validity at the time of issuance
        document, err := selfAccount.IdentityResolve(c.Issuer().Address())
        if !document.HasRolesAt(c.Issuer().SigningKey(), identity.RoleAssertion, c.Created()) {
            log.Warn("credential signing key was not valid at the time of issuance")
            continue
        }
    }

    // Extract and process the claims
    claims, err := c.CredentialSubjectClaims()
    for k, v := range claims {
        log.Info(
            "credential value",
            "credentialType", c.CredentialType(),
            "field", k,
            "value", v,
        )
    }
}

This process ensures that: 1. The presentation and credentials are cryptographically valid. 2. The credentials are from the expected user. 3. The credentials are currently valid (not future-dated). 4. For non-key-based issuers, the issuer was authorized to create the credential at the time of issuance. 5. The actual claim values are extracted and can be used in your application.

By following these steps, you can securely request, receive, and verify credentials from users, enabling trust in the information provided without needing to store sensitive data yourself.

Conclusion

Implementing Verifiable Credentials using Self's SDK provides a robust and secure way to handle sensitive user information. This approach offers several benefits:

  1. Enhanced Security: Cryptographic validation ensures the integrity and authenticity of credentials.
  2. Privacy Preservation: You can verify claims without storing sensitive user data.
  3. Flexibility: The system can handle various types of credentials and claims.
  4. Compliance: Adheres to W3C standards, ensuring interoperability and future-proofing.
  5. Simplified Implementation: Self's SDK abstracts complex cryptographic operations, making integration easier.