From dfc550708113850381d92499889f3baa8dbb77ae Mon Sep 17 00:00:00 2001 From: Neal Date: Mon, 20 Nov 2023 13:28:00 -0600 Subject: [PATCH 01/18] adding satisfy pd --- .../sdk/credentials/PresentationExchange.kt | 132 +++++++++----- .../credentials/PresentationExchangeTest.kt | 166 +++++++++++++++++- .../src/test/resources/pd_filter_array.json | 5 +- ...lter_array_multiple_input_descriptors.json | 38 ++++ .../pd_mixed_multiple_input_descriptors.json | 35 ++++ .../test/resources/pd_path_no_filter_dob.json | 18 ++ ..._no_filter_multiple_input_descriptors.json | 31 ++++ 7 files changed, 370 insertions(+), 55 deletions(-) create mode 100644 credentials/src/test/resources/pd_filter_array_multiple_input_descriptors.json create mode 100644 credentials/src/test/resources/pd_mixed_multiple_input_descriptors.json create mode 100644 credentials/src/test/resources/pd_path_no_filter_dob.json create mode 100644 credentials/src/test/resources/pd_path_no_filter_multiple_input_descriptors.json diff --git a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt index 5c2095dfc..fbf6d6387 100644 --- a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt +++ b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt @@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.JsonNode import com.networknt.schema.JsonSchema import com.nfeld.jsonpathkt.JsonPath import com.nfeld.jsonpathkt.extension.read -import com.nimbusds.jose.Payload import com.nimbusds.jwt.JWTParser import com.nimbusds.jwt.SignedJWT @@ -33,73 +32,120 @@ public object PresentationExchange { /** * Validates if a Verifiable Credential JWT satisfies a Presentation Definition. * - * @param vcJwt The Verifiable Credential JWT as a string. + * @param vcJwts The Verifiable Credentials as a JWT string. * @param presentationDefinition The Presentation Definition to validate against. - * @throws UnsupportedOperationException If the Presentation Definition's Submission Requirements - * feature is not implemented. + * @throws UnsupportedOperationException If the Presentation Definition's Submission Requirements exists + * @throws PresentationExchangeError If the Presentation Definition is not fulfilled */ public fun satisfiesPresentationDefinition( - vcJwt: String, + vcJwts: List, presentationDefinition: PresentationDefinitionV2 ) { - val vc = JWTParser.parse(vcJwt) as SignedJWT - if (!presentationDefinition.submissionRequirements.isNullOrEmpty()) { throw UnsupportedOperationException( "Presentation Definition's Submission Requirements feature is not implemented" ) } - presentationDefinition.inputDescriptors - .filter { !it.constraints.fields.isNullOrEmpty() } - .forEach { inputDescriptorWithFields -> - validateInputDescriptorsWithFields(inputDescriptorWithFields, vc.payload) + val inputDescriptorToVcMap = mapInputDescriptorsToVCs(vcJwts, presentationDefinition) + + if (inputDescriptorToVcMap.size != presentationDefinition.inputDescriptors.size) { + throw PresentationExchangeError( + "Missing input descriptors: The presentation definition requires " + + "${presentationDefinition.inputDescriptors.size} descriptors, but only " + + "${inputDescriptorToVcMap.size} were found. Check and provide the missing descriptors." + ) + } + } + private fun mapInputDescriptorsToVCs( + vcJwtList: List, + presentationDefinition: PresentationDefinitionV2 + ): Map> { + val map = mutableMapOf>() + + presentationDefinition.inputDescriptors.forEach { inputDescriptor -> + val satisfyingVCs = mutableListOf() + + vcJwtList.forEach { vcJwt -> + if (vcSatisfiesInputDescriptor(vcJwt, inputDescriptor)) { + satisfyingVCs.add(vcJwt) + } } + + if (satisfyingVCs.size > 0) { + map[inputDescriptor] = satisfyingVCs + } + } + + return map } /** - * Validates the input descriptors with associated fields in a Verifiable Credential. + * Evaluates if a Verifiable Credential (VC) satisfies the criteria defined in an Input Descriptor. * - * @param inputDescriptorWithFields The Input Descriptor with associated fields. - * @param vcPayload The payload of the Verifiable Credential. + * Parses a Verifiable Credential (VC) from JWT format and verifies if it satisfies the Input Descriptor's criteria. + * This function evaluates each required field (where 'optional' is not true) in the descriptor against the VC's JSON payload. + * It extracts data from the VC payload using JSON paths defined in each field and checks compliance with any defined schema. + * Returns false if any required field is missing or fails schema validation, indicating non-compliance with the Input Descriptor. + * Otherwise, it returns true, signifying that the VC meets all criteria. + * + * @param vcJwt The JWT string representing the Verifiable Credential. + * @param inputDescriptor An instance of InputDescriptorV2 defining the criteria to be satisfied by the VC. + * @return Boolean indicating whether the VC satisfies the criteria of the Input Descriptor. + * @throws PresentationExchangeError Any errors during processing */ - private fun validateInputDescriptorsWithFields( - inputDescriptorWithFields: InputDescriptorV2, - vcPayload: Payload - ) { - val requiredFields = inputDescriptorWithFields.constraints.fields!!.filter { it.optional != true } + private fun vcSatisfiesInputDescriptor( + vcJwt: String, + inputDescriptor: InputDescriptorV2 + ): Boolean { + val vc = JWTParser.parse(vcJwt) as SignedJWT - requiredFields.forEach { field -> - val vcPayloadJson = JsonPath.parse(vcPayload.toString()) - ?: throw PresentationExchangeError("Failed to parse VC $vcPayload as JsonNode") + val vcPayloadJson = JsonPath.parse(vc.payload.toString()) + ?: throw PresentationExchangeError("Failed to parse VC payload as JSON.") - val matchedFields = field.path.mapNotNull { path -> vcPayloadJson.read(path) } - if (matchedFields.isEmpty()) { - throw PresentationExchangeError("Could not find matching field for path: ${field.path.joinToString()}") - } + // If the Input Descriptor has constraints and fields defined, evaluate them. + inputDescriptor.constraints?.fields?.let { fields -> + val requiredFields = fields.filter { field -> field.optional != true } + + for (field in requiredFields) { + val matchedFields = field.path.mapNotNull { path -> vcPayloadJson.read(path) } + if (matchedFields.isEmpty()) { + // If no matching fields are found for a required field, the VC does not satisfy this Input Descriptor. + return false + } - when { - field.filterSchema != null -> { - matchedFields.any { fieldValue -> - when { - // When the field is an array, JSON schema is applied to each array item. - fieldValue.isArray -> { - if (fieldValue.none { valueSatisfiesFieldFilterSchema(it, field.filterSchema!!) }) - throw PresentationExchangeError("Validating $fieldValue against ${field.filterSchema} failed") - true - } - - // Otherwise, JSON schema is applied to the entire value. - else -> { - valueSatisfiesFieldFilterSchema(fieldValue, field.filterSchema!!) - } - } + // If there is a filter schema, process it + if (field.filterSchema != null) { + val satisfiesSchema = evaluateMatchedFields(matchedFields, field.filterSchema!!) + if (!satisfiesSchema) { + // If the field value does not satisfy the schema, the VC does not satisfy this Input Descriptor. + return false } } + } + } + + // If the VC passes all the checks, it satisfies the criteria of the Input Descriptor. + return true + } - else -> return + /** + * Checks if any JsonNode in 'matchedFields' satisfies the 'schema'. + * Iterates through nodes: if a node or any element in a node array meets the schema, returns true; otherwise false. + * + * @param matchedFields List of JsonNodes to validate. + * @param schema JsonSchema to validate against. + * @return True if any field satisfies the schema, false if none do. + */ + private fun evaluateMatchedFields(matchedFields: List, schema: JsonSchema): Boolean { + for (fieldValue in matchedFields) { + if (fieldValue.isArray() && fieldValue.any { valueSatisfiesFieldFilterSchema(it, schema) }) { + return true + } else if (!fieldValue.isArray() && valueSatisfiesFieldFilterSchema(fieldValue, schema)) { + return true } } + return false } /** diff --git a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt index ba49e932d..aad94fe5d 100644 --- a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt +++ b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt @@ -12,6 +12,9 @@ import web5.sdk.dids.methods.key.DidKey import java.io.File import kotlin.test.Test +data class DateOfBirth(val dateOfBirth: String) +data class Address(val address: String) +data class DateOfBirthSSN(val dateOfBirth: String, val ssn: String) class PresentationExchangeTest { private val keyManager = InMemoryKeyManager() private val issuerDid = DidKey.create(keyManager) @@ -38,7 +41,7 @@ class PresentationExchangeTest { PresentationDefinitionV2::class.java ) - assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(sanctionsVcJwt, pd) } + assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(sanctionsVcJwt), pd) } } @Test @@ -55,7 +58,7 @@ class PresentationExchangeTest { ) val vcJwt = vc.sign(issuerDid) - assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(vcJwt, pd) } + assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd) } } @Test @@ -72,11 +75,11 @@ class PresentationExchangeTest { ) val vcJwt = vc.sign(issuerDid) - assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(vcJwt, pd) } + assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd) } } @Test - fun `does not throw when VC satisfies PD with field constraint`() { + fun `does not throw when VC satisfies PD with no filter field constraint`() { val pd = jsonMapper.readValue( readPd("src/test/resources/pd_path_no_filter.json"), PresentationDefinitionV2::class.java @@ -89,11 +92,119 @@ class PresentationExchangeTest { ) val vcJwt = vc.sign(issuerDid) - assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(vcJwt, pd) } + assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd) } } @Test - fun `throws when VC does not satisfy requirements`() { + fun `does not throw when VC satisfies PD with no filter dob filed constraint`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_path_no_filter_dob.json"), + PresentationDefinitionV2::class.java + ) + + val vc = VerifiableCredential.create( + type = "DateOfBirthVc", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = DateOfBirth(dateOfBirth = "1/1/1111") + ) + + val vcJwt = vc.sign(issuerDid) + assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd) } + } + + @Test + fun `does not throw when VC satisfies PD with no filter dob filed constraint and extra VC`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_path_no_filter_dob.json"), + PresentationDefinitionV2::class.java + ) + + val vc1 = VerifiableCredential.create( + type = "DateOfBirth", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = DateOfBirth(dateOfBirth = "Data1") + ) + val vcJwt1 = vc1.sign(issuerDid) + + val vc2 = VerifiableCredential.create( + type = "Address", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = Address("abc street 123") + ) + val vcJwt2 = vc2.sign(issuerDid) + + assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt2, vcJwt1), pd) } + } + + @Test + fun `does not throw when one VC satisfies both input descriptors PD`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_filter_array_multiple_input_descriptors.json"), + PresentationDefinitionV2::class.java + ) + + val vc1 = VerifiableCredential.create( + type = "DateOfBirthSSN", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = DateOfBirthSSN(dateOfBirth = "1999-01-01", ssn = "456-123-123") + ) + val vcJwt1 = vc1.sign(issuerDid) + + assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt1), pd) } + } + + @Test + fun `does not throw when one VC satisfies both input descriptors PD mixed filter`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_mixed_multiple_input_descriptors.json"), + PresentationDefinitionV2::class.java + ) + + val vc1 = VerifiableCredential.create( + type = "DateOfBirthSSN", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = DateOfBirthSSN(dateOfBirth = "1999-01-01", ssn = "456-123-123") + ) + val vcJwt1 = vc1.sign(issuerDid) + + assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt1), pd) } + } + + @Test + fun `does not throw when a valid presentation submission has two vc`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_path_no_filter_multiple_input_descriptors.json"), + PresentationDefinitionV2::class.java + ) + + val vc1 = VerifiableCredential.create( + type = "DateOfBirthVc", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = DateOfBirth(dateOfBirth = "1/1/1111") + ) + + val vcJwt1 = vc1.sign(issuerDid) + + val vc2 = VerifiableCredential.create( + type = "Address", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = Address(address = "123 abc street") + ) + + val vcJwt2 = vc2.sign(issuerDid) + + assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt2, vcJwt1), pd) } + } + + @Test + fun `throws when VC does not satisfy sanctions requirements`() { val pd = jsonMapper.readValue( readPd("src/test/resources/pd_sanctions.json"), PresentationDefinitionV2::class.java @@ -107,8 +218,47 @@ class PresentationExchangeTest { val vcJwt = vc.sign(issuerDid) assertFailure { - PresentationExchange.satisfiesPresentationDefinition(vcJwt, pd) - }.messageContains("Validating [\"VerifiableCredential\",\"StreetCred\"]") + PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd) + }.messageContains("Missing input descriptors: The presentation definition requires") + } + + + @Test + fun `throws when VC does not satisfy no filter dob requirements`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_path_no_filter_dob.json"), + PresentationDefinitionV2::class.java + ) + val vc = VerifiableCredential.create( + type = "StreetCred", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = StreetCredibility(localRespect = "high", legit = true) + ) + val vcJwt = vc.sign(issuerDid) + + assertFailure { + PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd) + }.messageContains("Missing input descriptors: The presentation definition requires") + } + + @Test + fun `throws when VC does not satisfy filter streetCred requirements`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_filter_array.json"), + PresentationDefinitionV2::class.java + ) + val vc = VerifiableCredential.create( + type = "DateOfBirth", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = DateOfBirth(dateOfBirth = "01-02-03") + ) + val vcJwt = vc.sign(issuerDid) + + assertFailure { + PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd) + }.messageContains("Missing input descriptors: The presentation definition requires") } } } \ No newline at end of file diff --git a/credentials/src/test/resources/pd_filter_array.json b/credentials/src/test/resources/pd_filter_array.json index 202f83e54..5e983f9c7 100644 --- a/credentials/src/test/resources/pd_filter_array.json +++ b/credentials/src/test/resources/pd_filter_array.json @@ -12,10 +12,7 @@ ], "filter": { "type": "string", - "contains": { - "type": "string", - "const": "StreetCred" - } + "pattern": ".*StreetCred.*" } } ] diff --git a/credentials/src/test/resources/pd_filter_array_multiple_input_descriptors.json b/credentials/src/test/resources/pd_filter_array_multiple_input_descriptors.json new file mode 100644 index 000000000..e71c3eb17 --- /dev/null +++ b/credentials/src/test/resources/pd_filter_array_multiple_input_descriptors.json @@ -0,0 +1,38 @@ +{ + "id": "ec11a434-fe24-479b-aae0-511428b37e4f", + "input_descriptors": [ + { + "id": "input-descriptor-id-1", + "constraints": { + "fields": [ + { + "path": [ + "$.credentialSubject.dateOfBirth", + "$.vc.credentialSubject.dateOfBirth" + ], + "filter": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + } + } + ] + } + }, + { + "id": "input-descriptor-id-2", + "constraints": { + "fields": [ + { + "path": [ + "$.credentialSubject.ssn", + "$.vc.credentialSubject.ssn" + ], + "filter": { + "type": "string" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/credentials/src/test/resources/pd_mixed_multiple_input_descriptors.json b/credentials/src/test/resources/pd_mixed_multiple_input_descriptors.json new file mode 100644 index 000000000..576578301 --- /dev/null +++ b/credentials/src/test/resources/pd_mixed_multiple_input_descriptors.json @@ -0,0 +1,35 @@ +{ + "id": "ec11a434-fe24-479b-aae0-511428b37e4f", + "input_descriptors": [ + { + "id": "input-descriptor-id-1", + "constraints": { + "fields": [ + { + "path": [ + "$.credentialSubject.dateOfBirth", + "$.vc.credentialSubject.dateOfBirth" + ], + "filter": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + } + } + ] + } + }, + { + "id": "input-descriptor-id-2", + "constraints": { + "fields": [ + { + "path": [ + "$.credentialSubject.ssn", + "$.vc.credentialSubject.ssn" + ] + } + ] + } + } + ] +} \ No newline at end of file diff --git a/credentials/src/test/resources/pd_path_no_filter_dob.json b/credentials/src/test/resources/pd_path_no_filter_dob.json new file mode 100644 index 000000000..af1edbdd4 --- /dev/null +++ b/credentials/src/test/resources/pd_path_no_filter_dob.json @@ -0,0 +1,18 @@ +{ + "id": "32f54163-7166-48f1-93d8-ff217bdb0653", + "input_descriptors": [ + { + "id": "wa_driver_license", + "constraints": { + "fields": [ + { + "path": [ + "$.credentialSubject.dateOfBirth", + "$.vc.credentialSubject.dateOfBirth" + ] + } + ] + } + } + ] +} \ No newline at end of file diff --git a/credentials/src/test/resources/pd_path_no_filter_multiple_input_descriptors.json b/credentials/src/test/resources/pd_path_no_filter_multiple_input_descriptors.json new file mode 100644 index 000000000..12d26b606 --- /dev/null +++ b/credentials/src/test/resources/pd_path_no_filter_multiple_input_descriptors.json @@ -0,0 +1,31 @@ +{ + "id": "32f54163-7166-48f1-93d8-ff217bdb0653", + "input_descriptors": [ + { + "id": "dob-input-descriptor-id", + "constraints": { + "fields": [ + { + "path": [ + "$.credentialSubject.dateOfBirth", + "$.vc.credentialSubject.dateOfBirth" + ] + } + ] + } + }, + { + "id": "address-input-descriptor-id", + "constraints": { + "fields": [ + { + "path": [ + "$.credentialSubject.address", + "$.vc.credentialSubject.address" + ] + } + ] + } + } + ] +} \ No newline at end of file From 744dad6bb4ee23d00d8a8d483171ca04fdf91baa Mon Sep 17 00:00:00 2001 From: Neal Date: Mon, 20 Nov 2023 13:32:31 -0600 Subject: [PATCH 02/18] new documentation --- .../web5/sdk/credentials/PresentationExchange.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt index fbf6d6387..3dd844e46 100644 --- a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt +++ b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt @@ -30,12 +30,17 @@ public object PresentationExchange { } /** - * Validates if a Verifiable Credential JWT satisfies a Presentation Definition. + * Validates a list of Verifiable Credentials (VCs) against a specified Presentation Definition. * - * @param vcJwts The Verifiable Credentials as a JWT string. - * @param presentationDefinition The Presentation Definition to validate against. - * @throws UnsupportedOperationException If the Presentation Definition's Submission Requirements exists - * @throws PresentationExchangeError If the Presentation Definition is not fulfilled + * This function ensures that the provided VCs meet the criteria defined in the Presentation Definition. + * It first checks for the presence of Submission Requirements in the definition and throws an exception if they exist, + * as this feature is not implemented. Then, it maps the input descriptors in the presentation definition to the + * corresponding VCs. If the number of mapped descriptors does not match the required count, an error is thrown. + * + * @param vcJwts List of VCs in JWT format to validate. + * @param presentationDefinition The Presentation Definition V2 object against which VCs are validated. + * @throws UnsupportedOperationException If Submission Requirements are present in the definition. + * @throws PresentationExchangeError If the number of input descriptors matched is less than required. */ public fun satisfiesPresentationDefinition( vcJwts: List, From 5e1c646492166363ec11ae6508f70e2ccbeba676 Mon Sep 17 00:00:00 2001 From: Neal Date: Mon, 20 Nov 2023 13:34:56 -0600 Subject: [PATCH 03/18] add doc --- .../src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt index 3dd844e46..0a27391fa 100644 --- a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt +++ b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt @@ -62,6 +62,7 @@ public object PresentationExchange { ) } } + private fun mapInputDescriptorsToVCs( vcJwtList: List, presentationDefinition: PresentationDefinitionV2 From 7067c254431214a2f044a95e6a5231b871461962 Mon Sep 17 00:00:00 2001 From: Neal Date: Mon, 20 Nov 2023 17:13:31 -0600 Subject: [PATCH 04/18] adding to support filter array on single path --- .../sdk/credentials/PresentationExchange.kt | 47 +++++++++++-------- .../credentials/PresentationExchangeTest.kt | 36 ++++++++++++++ .../pd_filter_array_single_path.json | 24 ++++++++++ 3 files changed, 87 insertions(+), 20 deletions(-) create mode 100644 credentials/src/test/resources/pd_filter_array_single_path.json diff --git a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt index 0a27391fa..11270dd2f 100644 --- a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt +++ b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt @@ -1,6 +1,8 @@ package web5.sdk.credentials import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.node.ObjectNode +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.networknt.schema.JsonSchema import com.nfeld.jsonpathkt.JsonPath import com.nfeld.jsonpathkt.extension.read @@ -37,13 +39,13 @@ public object PresentationExchange { * as this feature is not implemented. Then, it maps the input descriptors in the presentation definition to the * corresponding VCs. If the number of mapped descriptors does not match the required count, an error is thrown. * - * @param vcJwts List of VCs in JWT format to validate. + * @param vcJwts Iterable of VCs in JWT format to validate. * @param presentationDefinition The Presentation Definition V2 object against which VCs are validated. * @throws UnsupportedOperationException If Submission Requirements are present in the definition. * @throws PresentationExchangeError If the number of input descriptors matched is less than required. */ public fun satisfiesPresentationDefinition( - vcJwts: List, + vcJwts: Iterable, presentationDefinition: PresentationDefinitionV2 ) { if (!presentationDefinition.submissionRequirements.isNullOrEmpty()) { @@ -64,26 +66,15 @@ public object PresentationExchange { } private fun mapInputDescriptorsToVCs( - vcJwtList: List, + vcJwtList: Iterable, presentationDefinition: PresentationDefinitionV2 ): Map> { - val map = mutableMapOf>() - - presentationDefinition.inputDescriptors.forEach { inputDescriptor -> - val satisfyingVCs = mutableListOf() - - vcJwtList.forEach { vcJwt -> - if (vcSatisfiesInputDescriptor(vcJwt, inputDescriptor)) { - satisfyingVCs.add(vcJwt) - } - } - - if (satisfyingVCs.size > 0) { - map[inputDescriptor] = satisfyingVCs + return presentationDefinition.inputDescriptors.associateWith { inputDescriptor -> + val satisfyingVCs = vcJwtList.filter { vcJwt -> + vcSatisfiesInputDescriptor(vcJwt, inputDescriptor) } - } - - return map + satisfyingVCs + }.filterValues { it.isNotEmpty() } } /** @@ -147,13 +138,29 @@ public object PresentationExchange { for (fieldValue in matchedFields) { if (fieldValue.isArray() && fieldValue.any { valueSatisfiesFieldFilterSchema(it, schema) }) { return true - } else if (!fieldValue.isArray() && valueSatisfiesFieldFilterSchema(fieldValue, schema)) { + } + + if (fieldValue.isArray() && schema.isExpectingArray() && valueSatisfiesFieldFilterSchema(fieldValue, schema)) { + return true + } + + if (!fieldValue.isArray() && valueSatisfiesFieldFilterSchema(fieldValue, schema)) { return true } } return false } + private fun JsonSchema.isExpectingArray(): Boolean { + val schemaNode: JsonNode = this.schemaNode + return if (schemaNode is ObjectNode) { + val typeNode = schemaNode.get("type") + typeNode != null && typeNode.asText() == "array" + } else { + false + } + } + /** * Checks if a field's value satisfies the given JSON schema. * diff --git a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt index aad94fe5d..2b242d905 100644 --- a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt +++ b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt @@ -61,6 +61,23 @@ class PresentationExchangeTest { assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd) } } + @Test + fun `does not throw when VC satisfies PD with field filter schema on array and single path`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_filter_array_single_path.json"), + PresentationDefinitionV2::class.java + ) + val vc = VerifiableCredential.create( + type = "StreetCred", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = StreetCredibility(localRespect = "high", legit = true) + ) + val vcJwt = vc.sign(issuerDid) + + assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd) } + } + @Test fun `does not throw when VC satisfies PD with field filter schema on value`() { val pd = jsonMapper.readValue( @@ -260,5 +277,24 @@ class PresentationExchangeTest { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd) }.messageContains("Missing input descriptors: The presentation definition requires") } + + @Test + fun `throws when VC does not satisfy filter streetCred requirements single path`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_filter_array_single_path.json"), + PresentationDefinitionV2::class.java + ) + val vc = VerifiableCredential.create( + type = "DateOfBirth", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = DateOfBirth(dateOfBirth = "01-02-03") + ) + val vcJwt = vc.sign(issuerDid) + + assertFailure { + PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd) + }.messageContains("Missing input descriptors: The presentation definition requires") + } } } \ No newline at end of file diff --git a/credentials/src/test/resources/pd_filter_array_single_path.json b/credentials/src/test/resources/pd_filter_array_single_path.json new file mode 100644 index 000000000..059aa1924 --- /dev/null +++ b/credentials/src/test/resources/pd_filter_array_single_path.json @@ -0,0 +1,24 @@ +{ + "id": "ec11a434-fe24-479b-aae0-511428b37e4f", + "input_descriptors": [ + { + "id": "7b928839-f0b1-4237-893d-b27124b57952", + "constraints": { + "fields": [ + { + "path": [ + "$.vc.type" + ], + "filter": { + "type": "array", + "contains": { + "type": "string", + "const": "StreetCred" + } + } + } + ] + } + } + ] +} \ No newline at end of file From ee106e0bedd0f7895ea1c4de2ea3a6c5788cfff6 Mon Sep 17 00:00:00 2001 From: Neal Date: Thu, 30 Nov 2023 12:13:01 -0600 Subject: [PATCH 05/18] adding createPresentationFromCredentials --- build.gradle.kts | 3 + .../sdk/credentials/PresentationExchange.kt | 54 +++++++++- .../{ => model}/PresentationDefinition.kt | 2 +- .../model/PresentationSubmission.kt | 29 +++++ .../credentials/PresentationDefinitionTest.kt | 4 + .../credentials/PresentationExchangeTest.kt | 102 ++++++++++++++++++ 6 files changed, 191 insertions(+), 3 deletions(-) rename credentials/src/main/kotlin/web5/sdk/credentials/{ => model}/PresentationDefinition.kt (99%) create mode 100644 credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt diff --git a/build.gradle.kts b/build.gradle.kts index 2e3da9c9a..02748f012 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -103,6 +103,9 @@ subprojects { tasks.test { useJUnitPlatform() + reports { + junitXml + } testLogging { events("passed", "skipped", "failed", "standardOut", "standardError") exceptionFormat = TestExceptionFormat.FULL diff --git a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt index 11270dd2f..083a0d25a 100644 --- a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt +++ b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt @@ -2,12 +2,16 @@ package web5.sdk.credentials import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.node.ObjectNode -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.networknt.schema.JsonSchema import com.nfeld.jsonpathkt.JsonPath import com.nfeld.jsonpathkt.extension.read import com.nimbusds.jwt.JWTParser import com.nimbusds.jwt.SignedJWT +import web5.sdk.credentials.model.DescriptorMap +import web5.sdk.credentials.model.InputDescriptorV2 +import web5.sdk.credentials.model.PresentationDefinitionV2 +import web5.sdk.credentials.model.PresentationSubmission +import java.util.UUID /** * The `PresentationExchange` object provides functions for working with Verifiable Credentials @@ -65,6 +69,52 @@ public object PresentationExchange { } } + /** + * Creates a Presentation Submission against a list of Verifiable Credentials (VCs) against a specified + * Presentation Definition. + * + * + * @param vcJwts Iterable of VCs in JWT format to validate. + * @param presentationDefinition The Presentation Definition V2 object against which VCs are validated. + * @return A PresentationSubmission object. + * @throws UnsupportedOperationException if the presentation definition contains submission requirements. + * @throws IllegalStateException if no VC corresponds to an input descriptor or if a VC's index is not found. + * @throws PresentationExchangeError If the number of input descriptors matched is less than required. + */ + public fun createPresentationFromCredentials( + vcJwts: Iterable, + presentationDefinition: PresentationDefinitionV2 + ): PresentationSubmission { + + satisfiesPresentationDefinition(vcJwts, presentationDefinition) + + val inputDescriptorToVcMap = mapInputDescriptorsToVCs(vcJwts, presentationDefinition) + val vcJwtToIndexMap = vcJwts.withIndex().associate { (index, vcJwt) -> vcJwt to index } + + val descriptorMapList = mutableListOf() + for ((inputDescriptor, vcList) in inputDescriptorToVcMap) { + // Even if multiple VCs satisfy the input descriptor we use the first + val vcJwt = vcList.firstOrNull() + checkNotNull(vcJwt) { "Illegal state: no vc corresponds to input descriptor" } + + val vcIndex = vcJwtToIndexMap[vcJwt] + checkNotNull(vcIndex) { "Illegal state: vcJwt index not found" } + + descriptorMapList.add( + DescriptorMap( + id = inputDescriptor.id, + path = "$.verifiableCredential[$vcIndex]", + format = "jwt_vc" + )) + } + + return PresentationSubmission( + id = UUID.randomUUID().toString(), + definitionId = presentationDefinition.id, + descriptorMap = descriptorMapList + ) + } + private fun mapInputDescriptorsToVCs( vcJwtList: Iterable, presentationDefinition: PresentationDefinitionV2 @@ -101,7 +151,7 @@ public object PresentationExchange { ?: throw PresentationExchangeError("Failed to parse VC payload as JSON.") // If the Input Descriptor has constraints and fields defined, evaluate them. - inputDescriptor.constraints?.fields?.let { fields -> + inputDescriptor.constraints.fields?.let { fields -> val requiredFields = fields.filter { field -> field.optional != true } for (field in requiredFields) { diff --git a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationDefinition.kt b/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationDefinition.kt similarity index 99% rename from credentials/src/main/kotlin/web5/sdk/credentials/PresentationDefinition.kt rename to credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationDefinition.kt index c541b4ba8..9f46229b0 100644 --- a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationDefinition.kt +++ b/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationDefinition.kt @@ -1,4 +1,4 @@ -package web5.sdk.credentials +package web5.sdk.credentials.model import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty diff --git a/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt b/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt new file mode 100644 index 000000000..f723c6d97 --- /dev/null +++ b/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt @@ -0,0 +1,29 @@ +package web5.sdk.credentials.model + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * Represents a presentation submission object. + * + * @see [Presentation Submission](https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-submission) + */ +public data class PresentationSubmission( + val id: String, + @JsonProperty("definition_id") + val definitionId: String, + @JsonProperty("descriptor_map") + val descriptorMap: List +) + +/** + * Represents descriptor map for a presentation submission. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public data class DescriptorMap( + val id: String, + val format: String, + val path: String, + @JsonProperty("path_nested") + val pathNested: DescriptorMap? = null +) \ No newline at end of file diff --git a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationDefinitionTest.kt b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationDefinitionTest.kt index 53f25bada..8b302926f 100644 --- a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationDefinitionTest.kt +++ b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationDefinitionTest.kt @@ -11,6 +11,10 @@ import org.erdtman.jcs.JsonCanonicalizer import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow +import web5.sdk.credentials.model.ConstraintsV2 +import web5.sdk.credentials.model.FieldV2 +import web5.sdk.credentials.model.InputDescriptorV2 +import web5.sdk.credentials.model.PresentationDefinitionV2 import java.io.File class PresentationDefinitionTest { diff --git a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt index 2b242d905..7f935a558 100644 --- a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt +++ b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt @@ -7,10 +7,14 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.registerKotlinModule import org.junit.jupiter.api.Nested import org.junit.jupiter.api.assertDoesNotThrow +import web5.sdk.credentials.model.PresentationDefinitionV2 import web5.sdk.crypto.InMemoryKeyManager import web5.sdk.dids.methods.key.DidKey import java.io.File import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue data class DateOfBirth(val dateOfBirth: String) data class Address(val address: String) @@ -297,4 +301,102 @@ class PresentationExchangeTest { }.messageContains("Missing input descriptors: The presentation definition requires") } } + + @Nested + inner class CreatePresentationFromCredentials { + @Test + fun `creates valid submission when VC satisfies tbdex PD`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_sanctions.json"), + PresentationDefinitionV2::class.java + ) + + val presentationSubmission = PresentationExchange.createPresentationFromCredentials(listOf(sanctionsVcJwt), pd) + + assertNotNull(presentationSubmission.id) + assertEquals(pd.id, presentationSubmission.definitionId) + + assertEquals(1, presentationSubmission.descriptorMap.size) + assertNotNull(presentationSubmission.descriptorMap[0].id) + assertEquals("jwt_vc", presentationSubmission.descriptorMap[0].format) + assertEquals("$.verifiableCredential[0]", presentationSubmission.descriptorMap[0].path) + } + + @Test + fun `creates valid submission when VC satisfies PD with no filter dob filed constraint and extra VC`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_path_no_filter_dob.json"), + PresentationDefinitionV2::class.java + ) + + val vc1 = VerifiableCredential.create( + type = "DateOfBirth", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = DateOfBirth(dateOfBirth = "Data1") + ) + val vcJwt1 = vc1.sign(issuerDid) + + val vc2 = VerifiableCredential.create( + type = "Address", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = Address("abc street 123") + ) + val vcJwt2 = vc2.sign(issuerDid) + + val presentationSubmission = PresentationExchange.createPresentationFromCredentials(listOf(vcJwt2, vcJwt1), pd) + + assertEquals(1, presentationSubmission.descriptorMap.size) + assertEquals("$.verifiableCredential[1]", presentationSubmission.descriptorMap[0].path) + } + + @Test + fun `throws when VC does not satisfy sanctions requirements`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_sanctions.json"), + PresentationDefinitionV2::class.java + ) + val vc = VerifiableCredential.create( + type = "StreetCred", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = StreetCredibility(localRespect = "high", legit = true) + ) + val vcJwt = vc.sign(issuerDid) + + assertFailure { + PresentationExchange.createPresentationFromCredentials(listOf(vcJwt), pd) + }.messageContains("Missing input descriptors: The presentation definition requires") + } + + @Test + fun `creates valid submission when VC two vcs satisfy the same input descriptor`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_path_no_filter_dob.json"), + PresentationDefinitionV2::class.java + ) + + val vc1 = VerifiableCredential.create( + type = "DateOfBirth", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = DateOfBirth(dateOfBirth = "11/11/2011") + ) + val vcJwt1 = vc1.sign(issuerDid) + + val vc2 = VerifiableCredential.create( + type = "DateOfBirth", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = DateOfBirth(dateOfBirth = "12/12/2012") + ) + val vcJwt2 = vc2.sign(issuerDid) + + val presentationSubmission = PresentationExchange.createPresentationFromCredentials(listOf(vcJwt2, vcJwt1), pd) + + assertEquals(1, presentationSubmission.descriptorMap.size) + assertEquals("$.verifiableCredential[0]", presentationSubmission.descriptorMap[0].path) + } + } } \ No newline at end of file From 74cd5e18cd4740563323c8afd3f111f55ae7dbc6 Mon Sep 17 00:00:00 2001 From: nitro-neal <5314059+nitro-neal@users.noreply.github.com> Date: Tue, 5 Dec 2023 13:25:40 -0600 Subject: [PATCH 06/18] Update credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt Co-authored-by: Andres Uribe --- .../main/kotlin/web5/sdk/credentials/PresentationExchange.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt index 083a0d25a..c39e22917 100644 --- a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt +++ b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt @@ -70,7 +70,7 @@ public object PresentationExchange { } /** - * Creates a Presentation Submission against a list of Verifiable Credentials (VCs) against a specified + * Creates a Presentation Submission in which the list of Verifiable Credentials JWTs (VCs) fulfills the given Presentation Definition. * Presentation Definition. * * From 66e9e6ffdedd2485cf2ff3a9e8e1bd2fa6fb615b Mon Sep 17 00:00:00 2001 From: nitro-neal <5314059+nitro-neal@users.noreply.github.com> Date: Tue, 5 Dec 2023 13:25:56 -0600 Subject: [PATCH 07/18] Update credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt Co-authored-by: Andres Uribe --- .../kotlin/web5/sdk/credentials/model/PresentationSubmission.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt b/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt index f723c6d97..84caaf6c6 100644 --- a/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt +++ b/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt @@ -20,7 +20,7 @@ public data class PresentationSubmission( * Represents descriptor map for a presentation submission. */ @JsonInclude(JsonInclude.Include.NON_NULL) -public data class DescriptorMap( +public class DescriptorMap( val id: String, val format: String, val path: String, From 6734551709f8a6bc43b4e0db327d4180da5efe58 Mon Sep 17 00:00:00 2001 From: nitro-neal <5314059+nitro-neal@users.noreply.github.com> Date: Tue, 5 Dec 2023 13:26:03 -0600 Subject: [PATCH 08/18] Update credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt Co-authored-by: Andres Uribe --- .../kotlin/web5/sdk/credentials/model/PresentationSubmission.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt b/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt index 84caaf6c6..76f099f49 100644 --- a/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt +++ b/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt @@ -8,7 +8,7 @@ import com.fasterxml.jackson.annotation.JsonProperty * * @see [Presentation Submission](https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-submission) */ -public data class PresentationSubmission( +public class PresentationSubmission( val id: String, @JsonProperty("definition_id") val definitionId: String, From c8380f72d624b58563996dd3a3e34f3589e53f86 Mon Sep 17 00:00:00 2001 From: Neal Date: Tue, 5 Dec 2023 13:45:24 -0600 Subject: [PATCH 09/18] updates --- .../credentials/model/PresentationSubmission.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt b/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt index 76f099f49..347e129b8 100644 --- a/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt +++ b/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt @@ -9,11 +9,11 @@ import com.fasterxml.jackson.annotation.JsonProperty * @see [Presentation Submission](https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-submission) */ public class PresentationSubmission( - val id: String, + public val id: String, @JsonProperty("definition_id") - val definitionId: String, + public val definitionId: String, @JsonProperty("descriptor_map") - val descriptorMap: List + public val descriptorMap: List ) /** @@ -21,9 +21,9 @@ public class PresentationSubmission( */ @JsonInclude(JsonInclude.Include.NON_NULL) public class DescriptorMap( - val id: String, - val format: String, - val path: String, + public val id: String, + public val format: String, + public val path: String, @JsonProperty("path_nested") - val pathNested: DescriptorMap? = null + public val pathNested: DescriptorMap? = null ) \ No newline at end of file From c573e36b260579b958d95ee6a35de689df074743 Mon Sep 17 00:00:00 2001 From: Neal Date: Thu, 7 Dec 2023 16:49:38 -0600 Subject: [PATCH 10/18] adding select credentials and test vectors --- .../sdk/credentials/PresentationExchange.kt | 11 +- .../credentials/PresentationExchangeTest.kt | 151 +++++++++++++++++- .../select-credentials-v1.json | 59 +++++++ 3 files changed, 214 insertions(+), 7 deletions(-) create mode 100644 test-vectors/presentation-exchange/select-credentials-v1.json diff --git a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt index c39e22917..16d60cbe6 100644 --- a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt +++ b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt @@ -21,18 +21,17 @@ public object PresentationExchange { /** * Selects credentials that satisfy a given presentation definition. * - * @param credentials The list of Verifiable Credentials to select from. + * @param vcJwts Iterable of VCs in JWT format to select from. * @param presentationDefinition The Presentation Definition to match against. * @return A list of Verifiable Credentials that satisfy the Presentation Definition. * @throws UnsupportedOperationException If the method is untested and not recommended for use. */ public fun selectCredentials( - credentials: List, + vcJwts: Iterable, presentationDefinition: PresentationDefinitionV2 - ): List { - throw UnsupportedOperationException("pex is untested") - // Uncomment the following line to filter credentials based on the Presentation Definition - // return credentials.filter { satisfiesPresentationDefinition(it, presentationDefinition) } + ): List { + val inputDescriptorToVcMap = mapInputDescriptorsToVCs(vcJwts, presentationDefinition) + return inputDescriptorToVcMap.flatMap { it.value }.toSet().toList() } /** diff --git a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt index f9d74339d..44f33a697 100644 --- a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt +++ b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt @@ -14,7 +14,6 @@ import java.io.File import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull -import kotlin.test.assertTrue data class DateOfBirth(val dateOfBirth: String) data class Address(val address: String) @@ -399,4 +398,154 @@ class PresentationExchangeTest { assertEquals("$.verifiableCredential[0]", presentationSubmission.descriptorMap[0].path) } } + + @Nested + inner class SelectCredentials { + @Test + fun `selects 1 correct credential`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_filter_array_single_path.json"), + PresentationDefinitionV2::class.java + ) + + val vc = VerifiableCredential.create( + type = "StreetCred", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = StreetCredibility(localRespect = "high", legit = true) + ) + val vcJwt = vc.sign(issuerDid) + + val selectedCreds = PresentationExchange.selectCredentials(listOf(vcJwt), pd) + + assertEquals( 1, selectedCreds.size) + assertEquals( vcJwt, selectedCreds[0]) + } + + @Test + fun `selects 2 correct credential`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_filter_array_single_path.json"), + PresentationDefinitionV2::class.java + ) + + val vc1 = VerifiableCredential.create( + type = "StreetCred", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = StreetCredibility(localRespect = "high", legit = true) + ) + val vcJwt1 = vc1.sign(issuerDid) + + val vc2 = VerifiableCredential.create( + type = "StreetCred", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = StreetCredibility(localRespect = "high", legit = true) + ) + val vcJwt2 = vc2.sign(issuerDid) + + val selectedCreds = PresentationExchange.selectCredentials(listOf(vcJwt1, vcJwt2), pd) + + assertEquals( 2, selectedCreds.size) + assertEquals( listOf(vcJwt1, vcJwt2), selectedCreds) + } + + @Test + fun `selects 2 correct credential out of 3`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_filter_array_single_path.json"), + PresentationDefinitionV2::class.java + ) + + val vc1 = VerifiableCredential.create( + type = "StreetCred", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = StreetCredibility(localRespect = "high", legit = true) + ) + val vcJwt1 = vc1.sign(issuerDid) + + val vc2 = VerifiableCredential.create( + type = "StreetCred", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = StreetCredibility(localRespect = "high", legit = true) + ) + + val vcJwt2 = vc2.sign(issuerDid) + + val vc3 = VerifiableCredential.create( + type = "DateOfBirth", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = DateOfBirth(dateOfBirth = "1-1-1111") + ) + + val vcJwt3 = vc3.sign(issuerDid) + + val selectedCreds = PresentationExchange.selectCredentials(listOf(vcJwt1, vcJwt2, vcJwt3), pd) + + assertEquals( 2, selectedCreds.size) + assertEquals( listOf(vcJwt1, vcJwt2), selectedCreds) + } + + @Test + fun `selects 2 correct credential with two input descriptors`() { + val pd = jsonMapper.readValue( + readPd("src/test/resources/pd_filter_array_multiple_input_descriptors.json"), + PresentationDefinitionV2::class.java + ) + + val vc1 = VerifiableCredential.create( + type = "DateOfBirthSSN", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = DateOfBirthSSN(dateOfBirth = "1999-01-01", ssn = "456-123-123") + ) + val vcJwt1 = vc1.sign(issuerDid) + + val vc2 = VerifiableCredential.create( + type = "DateOfBirthSSN", + issuer = issuerDid.uri, + subject = holderDid.uri, + data = DateOfBirth(dateOfBirth = "1999-01-01") + ) + val vcJwt2 = vc2.sign(issuerDid) + + val selectedCreds = PresentationExchange.selectCredentials(listOf(vcJwt1, vcJwt2), pd) + + assertEquals( 2, selectedCreds.size) + assertEquals( listOf(vcJwt1, vcJwt2), selectedCreds) + } + } + + @Nested + inner class SelectCredentialsSpec { + @org.junit.jupiter.api.Test + fun select_credentials_v1() { + val jsonString = File("../test-vectors/presentation-exchange/select-credentials-v1.json").readText() + val jsonNode = jsonMapper.readTree(jsonString) + + val vectors = jsonNode.get("vectors") + + for (i in 0 until vectors.size()) { + val input = vectors[i].get("input") + + val inputPdJsonString = input.get("presentationDefinition").toString() + val inputVcJwts = input.get("credentialJwts").map { it.asText() } + + val expectedOutput = vectors[i].get("output").get("selectedCredentials").map { it.asText() } + + val inputPd = jsonMapper.readValue( + inputPdJsonString, + PresentationDefinitionV2::class.java + ) + + val selectedCreds = PresentationExchange.selectCredentials(inputVcJwts, inputPd) + + assertEquals(expectedOutput, selectedCreds) + } + } + } } \ No newline at end of file diff --git a/test-vectors/presentation-exchange/select-credentials-v1.json b/test-vectors/presentation-exchange/select-credentials-v1.json new file mode 100644 index 000000000..6cc2aaa0c --- /dev/null +++ b/test-vectors/presentation-exchange/select-credentials-v1.json @@ -0,0 +1,59 @@ +{ + "description":"Select Credentials", + "vectors":[ + { + "description":"select credentials for presentation", + "input":{ + "presentationDefinition":{ + "id":"test-pd-id", + "name":"simple PD", + "purpose":"pd for testing", + "input_descriptors":[ + { + "id":"whatever", + "purpose":"id for testing", + "constraints":{ + "fields":[ + { + "path":[ + "$.vc.credentialSubject.btcAddress", + "$.credentialSubject.btcAddress", + "$.btcAddress" + ] + } + ] + } + }, + { + "id":"whatever2", + "purpose":"id for testing2", + "constraints":{ + "fields":[ + { + "path":[ + "$.vc.credentialSubject.dogeAddress", + "$.credentialSubject.dogeAddress", + "$.dogeAddress" + ] + } + ] + } + } + ] + }, + "credentialJwts":[ + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HI3o2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyIsInN1YiI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlN0cmVldENyZWQiXSwiaWQiOiJ1cm46dXVpZDoxM2Q1YTg3YS1kY2Y1LTRmYjktOWUyOS0wZTYyZTI0YzQ0ODYiLCJpc3N1ZXIiOiJkaWQ6a2V5Ono2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMTItMDdUMTc6MTk6MTNaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6a2V5Ono2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyIsIm90aGVydGhpbmciOiJvdGhlcnN0dWZmIn19fQ.FVvL3z8LHJXm7lGX2bGFvH_U-bTyoheRbLzE7zIk_P1BKwRYeW4sbYNzsovFX59twXrnpF-hHkqVVsejSljxDw", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HI3o2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyIsInN1YiI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkJpdGNvaW5Eb2dlQ3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjViZTkwNzQ0LWE3MjQtNGJlNy1hN2EzLTlmMjYwZWMwNDhkMSIsImlzc3VlciI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMi0wN1QxNzoxOToxM1oiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwiYnRjQWRkcmVzcyI6ImJ0Y0FkZHJlc3MxMjMiLCJkb2dlQWRkcmVzcyI6ImRvZ2VBZGRyZXNzMTIzIn19fQ.gTfgbVTj_IQS_rM-mOAURGan6Ojk7MSSgFHeog6cqo6DWpDq0pwSRxceAqZhZbSKsW2MFpbBpTko1BgNNMIrDQ", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HI3o2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyIsInN1YiI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkJpdGNvaW5DcmVkZW50aWFsIl0sImlkIjoidXJuOnV1aWQ6NGE0OGIyNzUtNTBmZC00MTQ0LWJmMTctY2E5ODMxYzlkYTYyIiwiaXNzdWVyIjoiZGlkOmtleTp6Nk1rZkhCTm1jNGNDaE50RHZhcjE4VFppZ2ZYdDdQNjVGQkd3cVFUdHhRb1FQbkciLCJpc3N1YW5jZURhdGUiOiIyMDIzLTEyLTA3VDE3OjE5OjEzWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rZkhCTm1jNGNDaE50RHZhcjE4VFppZ2ZYdDdQNjVGQkd3cVFUdHhRb1FQbkciLCJidGNBZGRyZXNzIjoiYnRjQWRkcmVzczEyMyJ9fX0.75Xyx-SWSeo8rfvHK-mxl3ixa3QZxj7waPuJZ58s52yTffs6AjpO3uSNAO3WOV-rtS-puIRm7vClZCsUA3JRAQ", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HI3o2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyIsInN1YiI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkJpdGNvaW5DcmVkZW50aWFsIl0sImlkIjoidXJuOnV1aWQ6NGE0OGIyNzUtNTBmZC00MTQ0LWJmMTctY2E5ODMxYzlkYTYyIiwiaXNzdWVyIjoiZGlkOmtleTp6Nk1rZkhCTm1jNGNDaE50RHZhcjE4VFppZ2ZYdDdQNjVGQkd3cVFUdHhRb1FQbkciLCJpc3N1YW5jZURhdGUiOiIyMDIzLTEyLTA3VDE3OjE5OjEzWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rZkhCTm1jNGNDaE50RHZhcjE4VFppZ2ZYdDdQNjVGQkd3cVFUdHhRb1FQbkciLCJidGNBZGRyZXNzIjoiYnRjQWRkcmVzczEyMyJ9fX0.75Xyx-SWSeo8rfvHK-mxl3ixa3QZxj7waPuJZ58s52yTffs6AjpO3uSNAO3WOV-rtS-puIRm7vClZCsUA3JRAQ" + ] + }, + "output":{ + "selectedCredentials":[ + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HI3o2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyIsInN1YiI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkJpdGNvaW5Eb2dlQ3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjViZTkwNzQ0LWE3MjQtNGJlNy1hN2EzLTlmMjYwZWMwNDhkMSIsImlzc3VlciI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMi0wN1QxNzoxOToxM1oiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwiYnRjQWRkcmVzcyI6ImJ0Y0FkZHJlc3MxMjMiLCJkb2dlQWRkcmVzcyI6ImRvZ2VBZGRyZXNzMTIzIn19fQ.gTfgbVTj_IQS_rM-mOAURGan6Ojk7MSSgFHeog6cqo6DWpDq0pwSRxceAqZhZbSKsW2MFpbBpTko1BgNNMIrDQ", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HI3o2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyIsInN1YiI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkJpdGNvaW5DcmVkZW50aWFsIl0sImlkIjoidXJuOnV1aWQ6NGE0OGIyNzUtNTBmZC00MTQ0LWJmMTctY2E5ODMxYzlkYTYyIiwiaXNzdWVyIjoiZGlkOmtleTp6Nk1rZkhCTm1jNGNDaE50RHZhcjE4VFppZ2ZYdDdQNjVGQkd3cVFUdHhRb1FQbkciLCJpc3N1YW5jZURhdGUiOiIyMDIzLTEyLTA3VDE3OjE5OjEzWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rZkhCTm1jNGNDaE50RHZhcjE4VFppZ2ZYdDdQNjVGQkd3cVFUdHhRb1FQbkciLCJidGNBZGRyZXNzIjoiYnRjQWRkcmVzczEyMyJ9fX0.75Xyx-SWSeo8rfvHK-mxl3ixa3QZxj7waPuJZ58s52yTffs6AjpO3uSNAO3WOV-rtS-puIRm7vClZCsUA3JRAQ" + ] + } + } + ] +} \ No newline at end of file From d8ecb4a8331845317fa225f57858496de9a3e31c Mon Sep 17 00:00:00 2001 From: Neal Date: Thu, 7 Dec 2023 16:52:31 -0600 Subject: [PATCH 11/18] fix --- .../src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt index 16d60cbe6..51fcdf7c2 100644 --- a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt +++ b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt @@ -24,7 +24,6 @@ public object PresentationExchange { * @param vcJwts Iterable of VCs in JWT format to select from. * @param presentationDefinition The Presentation Definition to match against. * @return A list of Verifiable Credentials that satisfy the Presentation Definition. - * @throws UnsupportedOperationException If the method is untested and not recommended for use. */ public fun selectCredentials( vcJwts: Iterable, From 5bc317a61fe7876871875f8b9d0272e037a07859 Mon Sep 17 00:00:00 2001 From: Neal Date: Fri, 8 Dec 2023 14:26:33 -0600 Subject: [PATCH 12/18] added underscore --- .../kotlin/web5/sdk/credentials/PresentationExchangeTest.kt | 2 +- .../{select-credentials-v1.json => select_credentials_v1.json} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename test-vectors/presentation-exchange/{select-credentials-v1.json => select_credentials_v1.json} (100%) diff --git a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt index 44f33a697..753fa27d5 100644 --- a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt +++ b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt @@ -524,7 +524,7 @@ class PresentationExchangeTest { inner class SelectCredentialsSpec { @org.junit.jupiter.api.Test fun select_credentials_v1() { - val jsonString = File("../test-vectors/presentation-exchange/select-credentials-v1.json").readText() + val jsonString = File("../test-vectors/presentation-exchange/select_credentials_v1.json").readText() val jsonNode = jsonMapper.readTree(jsonString) val vectors = jsonNode.get("vectors") diff --git a/test-vectors/presentation-exchange/select-credentials-v1.json b/test-vectors/presentation-exchange/select_credentials_v1.json similarity index 100% rename from test-vectors/presentation-exchange/select-credentials-v1.json rename to test-vectors/presentation-exchange/select_credentials_v1.json From 3b74af45234bbf2c116a95c7624d80521140ee36 Mon Sep 17 00:00:00 2001 From: Neal Date: Mon, 11 Dec 2023 12:50:19 -0600 Subject: [PATCH 13/18] updates --- .github/workflows/ci.yml | 3 +- .../credentials/PresentationExchangeTest.kt | 43 +++++++------- .../select_credentials_v1.json | 59 ------------------- 3 files changed, 24 insertions(+), 81 deletions(-) delete mode 100644 test-vectors/presentation-exchange/select_credentials_v1.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4e87b0d8..aa79bdb8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,6 @@ jobs: path: ~/.gradle/caches key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle.kts') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/versions.properties') }} - - name: Run Gradle Tasks run: ./gradlew build koverXmlReport @@ -36,4 +35,4 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true - flags: ${{ runner.os }} + flags: ${{ runner.os }} \ No newline at end of file diff --git a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt index f1bd82104..d150a5efc 100644 --- a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt +++ b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt @@ -548,33 +548,36 @@ class PresentationExchangeTest { assertEquals( listOf(vcJwt1, vcJwt2), selectedCreds) } } +} - @Nested - inner class SelectCredentialsSpec { - @org.junit.jupiter.api.Test - fun select_credentials_v1() { - val jsonString = File("../test-vectors/presentation-exchange/select_credentials_v1.json").readText() - val jsonNode = jsonMapper.readTree(jsonString) - val vectors = jsonNode.get("vectors") +class Web5TestVectorsPresentationExchangeTest { + private val jsonMapper: ObjectMapper = ObjectMapper() + .registerKotlinModule() + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + @Test + fun select_credentials() { + val jsonString = File("../test-vectors/presentation-exchange/select_credentials.json").readText() + val jsonNode = jsonMapper.readTree(jsonString) + + val vectors = jsonNode.get("vectors") - for (i in 0 until vectors.size()) { - val input = vectors[i].get("input") + for (i in 0 until vectors.size()) { + val input = vectors[i].get("input") - val inputPdJsonString = input.get("presentationDefinition").toString() - val inputVcJwts = input.get("credentialJwts").map { it.asText() } + val inputPdJsonString = input.get("presentationDefinition").toString() + val inputVcJwts = input.get("credentialJwts").map { it.asText() } - val expectedOutput = vectors[i].get("output").get("selectedCredentials").map { it.asText() } + val expectedOutput = vectors[i].get("output").get("selectedCredentials").map { it.asText() } - val inputPd = jsonMapper.readValue( - inputPdJsonString, - PresentationDefinitionV2::class.java - ) + val inputPd = jsonMapper.readValue( + inputPdJsonString, + PresentationDefinitionV2::class.java + ) - val selectedCreds = PresentationExchange.selectCredentials(inputVcJwts, inputPd) + val selectedCreds = PresentationExchange.selectCredentials(inputVcJwts, inputPd) - assertEquals(expectedOutput, selectedCreds) - } + assertEquals(expectedOutput, selectedCreds) } } -} +} \ No newline at end of file diff --git a/test-vectors/presentation-exchange/select_credentials_v1.json b/test-vectors/presentation-exchange/select_credentials_v1.json deleted file mode 100644 index 6cc2aaa0c..000000000 --- a/test-vectors/presentation-exchange/select_credentials_v1.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "description":"Select Credentials", - "vectors":[ - { - "description":"select credentials for presentation", - "input":{ - "presentationDefinition":{ - "id":"test-pd-id", - "name":"simple PD", - "purpose":"pd for testing", - "input_descriptors":[ - { - "id":"whatever", - "purpose":"id for testing", - "constraints":{ - "fields":[ - { - "path":[ - "$.vc.credentialSubject.btcAddress", - "$.credentialSubject.btcAddress", - "$.btcAddress" - ] - } - ] - } - }, - { - "id":"whatever2", - "purpose":"id for testing2", - "constraints":{ - "fields":[ - { - "path":[ - "$.vc.credentialSubject.dogeAddress", - "$.credentialSubject.dogeAddress", - "$.dogeAddress" - ] - } - ] - } - } - ] - }, - "credentialJwts":[ - "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HI3o2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyIsInN1YiI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlN0cmVldENyZWQiXSwiaWQiOiJ1cm46dXVpZDoxM2Q1YTg3YS1kY2Y1LTRmYjktOWUyOS0wZTYyZTI0YzQ0ODYiLCJpc3N1ZXIiOiJkaWQ6a2V5Ono2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMTItMDdUMTc6MTk6MTNaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6a2V5Ono2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyIsIm90aGVydGhpbmciOiJvdGhlcnN0dWZmIn19fQ.FVvL3z8LHJXm7lGX2bGFvH_U-bTyoheRbLzE7zIk_P1BKwRYeW4sbYNzsovFX59twXrnpF-hHkqVVsejSljxDw", - "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HI3o2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyIsInN1YiI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkJpdGNvaW5Eb2dlQ3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjViZTkwNzQ0LWE3MjQtNGJlNy1hN2EzLTlmMjYwZWMwNDhkMSIsImlzc3VlciI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMi0wN1QxNzoxOToxM1oiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwiYnRjQWRkcmVzcyI6ImJ0Y0FkZHJlc3MxMjMiLCJkb2dlQWRkcmVzcyI6ImRvZ2VBZGRyZXNzMTIzIn19fQ.gTfgbVTj_IQS_rM-mOAURGan6Ojk7MSSgFHeog6cqo6DWpDq0pwSRxceAqZhZbSKsW2MFpbBpTko1BgNNMIrDQ", - "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HI3o2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyIsInN1YiI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkJpdGNvaW5DcmVkZW50aWFsIl0sImlkIjoidXJuOnV1aWQ6NGE0OGIyNzUtNTBmZC00MTQ0LWJmMTctY2E5ODMxYzlkYTYyIiwiaXNzdWVyIjoiZGlkOmtleTp6Nk1rZkhCTm1jNGNDaE50RHZhcjE4VFppZ2ZYdDdQNjVGQkd3cVFUdHhRb1FQbkciLCJpc3N1YW5jZURhdGUiOiIyMDIzLTEyLTA3VDE3OjE5OjEzWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rZkhCTm1jNGNDaE50RHZhcjE4VFppZ2ZYdDdQNjVGQkd3cVFUdHhRb1FQbkciLCJidGNBZGRyZXNzIjoiYnRjQWRkcmVzczEyMyJ9fX0.75Xyx-SWSeo8rfvHK-mxl3ixa3QZxj7waPuJZ58s52yTffs6AjpO3uSNAO3WOV-rtS-puIRm7vClZCsUA3JRAQ", - "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HI3o2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyIsInN1YiI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkJpdGNvaW5DcmVkZW50aWFsIl0sImlkIjoidXJuOnV1aWQ6NGE0OGIyNzUtNTBmZC00MTQ0LWJmMTctY2E5ODMxYzlkYTYyIiwiaXNzdWVyIjoiZGlkOmtleTp6Nk1rZkhCTm1jNGNDaE50RHZhcjE4VFppZ2ZYdDdQNjVGQkd3cVFUdHhRb1FQbkciLCJpc3N1YW5jZURhdGUiOiIyMDIzLTEyLTA3VDE3OjE5OjEzWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rZkhCTm1jNGNDaE50RHZhcjE4VFppZ2ZYdDdQNjVGQkd3cVFUdHhRb1FQbkciLCJidGNBZGRyZXNzIjoiYnRjQWRkcmVzczEyMyJ9fX0.75Xyx-SWSeo8rfvHK-mxl3ixa3QZxj7waPuJZ58s52yTffs6AjpO3uSNAO3WOV-rtS-puIRm7vClZCsUA3JRAQ" - ] - }, - "output":{ - "selectedCredentials":[ - "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HI3o2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyIsInN1YiI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkJpdGNvaW5Eb2dlQ3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjViZTkwNzQ0LWE3MjQtNGJlNy1hN2EzLTlmMjYwZWMwNDhkMSIsImlzc3VlciI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMi0wN1QxNzoxOToxM1oiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwiYnRjQWRkcmVzcyI6ImJ0Y0FkZHJlc3MxMjMiLCJkb2dlQWRkcmVzcyI6ImRvZ2VBZGRyZXNzMTIzIn19fQ.gTfgbVTj_IQS_rM-mOAURGan6Ojk7MSSgFHeog6cqo6DWpDq0pwSRxceAqZhZbSKsW2MFpbBpTko1BgNNMIrDQ", - "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HI3o2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtmSEJObWM0Y0NoTnREdmFyMThUWmlnZlh0N1A2NUZCR3dxUVR0eFFvUVBuRyIsInN1YiI6ImRpZDprZXk6ejZNa2ZIQk5tYzRjQ2hOdER2YXIxOFRaaWdmWHQ3UDY1RkJHd3FRVHR4UW9RUG5HIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkJpdGNvaW5DcmVkZW50aWFsIl0sImlkIjoidXJuOnV1aWQ6NGE0OGIyNzUtNTBmZC00MTQ0LWJmMTctY2E5ODMxYzlkYTYyIiwiaXNzdWVyIjoiZGlkOmtleTp6Nk1rZkhCTm1jNGNDaE50RHZhcjE4VFppZ2ZYdDdQNjVGQkd3cVFUdHhRb1FQbkciLCJpc3N1YW5jZURhdGUiOiIyMDIzLTEyLTA3VDE3OjE5OjEzWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rZkhCTm1jNGNDaE50RHZhcjE4VFppZ2ZYdDdQNjVGQkd3cVFUdHhRb1FQbkciLCJidGNBZGRyZXNzIjoiYnRjQWRkcmVzczEyMyJ9fX0.75Xyx-SWSeo8rfvHK-mxl3ixa3QZxj7waPuJZ58s52yTffs6AjpO3uSNAO3WOV-rtS-puIRm7vClZCsUA3JRAQ" - ] - } - } - ] -} \ No newline at end of file From 40b69f94d851e8919759d5a96191b5b8ae0c26da Mon Sep 17 00:00:00 2001 From: Neal Date: Mon, 11 Dec 2023 12:52:48 -0600 Subject: [PATCH 14/18] uypdate --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa79bdb8a..947bfb480 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,7 @@ jobs: path: ~/.gradle/caches key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle.kts') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/versions.properties') }} + - name: Run Gradle Tasks run: ./gradlew build koverXmlReport From c8a30c098d67579c46fd455a24930c7fd40a292b Mon Sep 17 00:00:00 2001 From: Neal Date: Tue, 12 Dec 2023 11:57:23 -0600 Subject: [PATCH 15/18] merge and updates --- .../credentials/PresentationExchangeTest.kt | 42 +++++++++---------- .../credentials/VerifiableCredentialTest.kt | 18 +++++--- .../select_credentials.json | 0 .../kotlin/web5/sdk/testing/TestVectors.kt | 10 ++--- 4 files changed, 38 insertions(+), 32 deletions(-) rename test-vectors/{presentation-exchange => presentation_exchange}/select_credentials.json (100%) diff --git a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt index d150a5efc..123f27db8 100644 --- a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt +++ b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt @@ -3,7 +3,9 @@ package web5.sdk.credentials import assertk.assertFailure import assertk.assertions.messageContains import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.registerKotlinModule import org.junit.jupiter.api.Nested import org.junit.jupiter.api.assertDoesNotThrow @@ -11,6 +13,7 @@ import org.junit.jupiter.api.assertThrows import web5.sdk.credentials.model.PresentationDefinitionV2 import web5.sdk.crypto.InMemoryKeyManager import web5.sdk.dids.methods.key.DidKey +import web5.sdk.testing.TestVectors import java.io.File import kotlin.test.Test import kotlin.test.assertEquals @@ -552,32 +555,27 @@ class PresentationExchangeTest { class Web5TestVectorsPresentationExchangeTest { - private val jsonMapper: ObjectMapper = ObjectMapper() - .registerKotlinModule() - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - @Test - fun select_credentials() { - val jsonString = File("../test-vectors/presentation-exchange/select_credentials.json").readText() - val jsonNode = jsonMapper.readTree(jsonString) - - val vectors = jsonNode.get("vectors") + data class SelectCredTestInput( + val presentationDefinition: PresentationDefinitionV2, + val credentialJwts: List + ) - for (i in 0 until vectors.size()) { - val input = vectors[i].get("input") + data class SelectCredTestOutput( + val selectedCredentials: List + ) - val inputPdJsonString = input.get("presentationDefinition").toString() - val inputVcJwts = input.get("credentialJwts").map { it.asText() } - - val expectedOutput = vectors[i].get("output").get("selectedCredentials").map { it.asText() } + private val mapper = jacksonObjectMapper() + @Test + fun select_credentials() { + val typeRef = object : TypeReference>() {} + val testVectors = mapper.readValue(File("../test-vectors/presentation_exchange/select_credentials.json"), typeRef) - val inputPd = jsonMapper.readValue( - inputPdJsonString, - PresentationDefinitionV2::class.java + testVectors.vectors.forEach { vector -> + val selectedCreds = PresentationExchange.selectCredentials( + vector.input.credentialJwts, + vector.input.presentationDefinition ) - - val selectedCreds = PresentationExchange.selectCredentials(inputVcJwts, inputPd) - - assertEquals(expectedOutput, selectedCreds) + assertEquals(vector.output!!.selectedCredentials, selectedCreds) } } } \ No newline at end of file diff --git a/credentials/src/test/kotlin/web5/sdk/credentials/VerifiableCredentialTest.kt b/credentials/src/test/kotlin/web5/sdk/credentials/VerifiableCredentialTest.kt index 2451e76f8..14a69fa34 100644 --- a/credentials/src/test/kotlin/web5/sdk/credentials/VerifiableCredentialTest.kt +++ b/credentials/src/test/kotlin/web5/sdk/credentials/VerifiableCredentialTest.kt @@ -238,11 +238,15 @@ class Web5TestVectorsCredentialsTest { val vcJwt: String, ) + data class VerifySuccessInput( + val vcJwt: LinkedHashMap, + ) + private val mapper = jacksonObjectMapper() @Test fun create_success() { - val typeRef = object : TypeReference>() {} + val typeRef = object : TypeReference>() {} val testVectors = mapper.readValue(File("../test-vectors/credentials/create_success.json"), typeRef) testVectors.vectors.forEach { vector -> @@ -259,19 +263,23 @@ class Web5TestVectorsCredentialsTest { @Test fun verify_success() { - val typeRef = object : TypeReference>() {} + val typeRef = object : TypeReference>() {} val testVectors = mapper.readValue(File("../test-vectors/credentials/verify_success.json"), typeRef) testVectors.vectors.forEach { vector -> + + val inputMap = vector.input as Map + val vcJwt = inputMap["vcJwt"] as String + assertDoesNotThrow { - VerifiableCredential.verify(vector.input.vcJwt) + VerifiableCredential.verify(vcJwt) } } } @Test fun verify_failure() { - val typeRef = object : TypeReference>() {} + val typeRef = object : TypeReference>() {} val testVectors = mapper.readValue(File("../test-vectors/credentials/verify_failure.json"), typeRef) testVectors.vectors.forEach { vector -> @@ -283,7 +291,7 @@ class Web5TestVectorsCredentialsTest { @Test fun create_failure() { - val typeRef = object : TypeReference>() {} + val typeRef = object : TypeReference>() {} val testVectors = mapper.readValue(File("../test-vectors/credentials/create_failure.json"), typeRef) testVectors.vectors.forEach { vector -> diff --git a/test-vectors/presentation-exchange/select_credentials.json b/test-vectors/presentation_exchange/select_credentials.json similarity index 100% rename from test-vectors/presentation-exchange/select_credentials.json rename to test-vectors/presentation_exchange/select_credentials.json diff --git a/testing/src/main/kotlin/web5/sdk/testing/TestVectors.kt b/testing/src/main/kotlin/web5/sdk/testing/TestVectors.kt index cf4bcd985..a07d5b15d 100644 --- a/testing/src/main/kotlin/web5/sdk/testing/TestVectors.kt +++ b/testing/src/main/kotlin/web5/sdk/testing/TestVectors.kt @@ -5,9 +5,9 @@ package web5.sdk.testing * * See https://github.com/TBD54566975/sdk-development/blob/main/web5-test-vectors/README.md for more details. */ -public class TestVectors( +public class TestVectors( public val description: String, - public val vectors: List> + public val vectors: List> ) /** @@ -15,9 +15,9 @@ public class TestVectors( * * See https://github.com/TBD54566975/sdk-development/blob/main/web5-test-vectors/README.md for more details. */ -public class TestVector( +public class TestVector( public val description: String, - public val input: T, - public val output: String?, + public val input: I, + public val output: O?, public val errors: Boolean?, ) \ No newline at end of file From 77d0118f4c4737ea22bde47bad4e9d415b4d3d59 Mon Sep 17 00:00:00 2001 From: Neal Date: Tue, 12 Dec 2023 12:01:37 -0600 Subject: [PATCH 16/18] clean up --- .../kotlin/web5/sdk/credentials/VerifiableCredentialTest.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/credentials/src/test/kotlin/web5/sdk/credentials/VerifiableCredentialTest.kt b/credentials/src/test/kotlin/web5/sdk/credentials/VerifiableCredentialTest.kt index 14a69fa34..4e92efbdf 100644 --- a/credentials/src/test/kotlin/web5/sdk/credentials/VerifiableCredentialTest.kt +++ b/credentials/src/test/kotlin/web5/sdk/credentials/VerifiableCredentialTest.kt @@ -238,10 +238,6 @@ class Web5TestVectorsCredentialsTest { val vcJwt: String, ) - data class VerifySuccessInput( - val vcJwt: LinkedHashMap, - ) - private val mapper = jacksonObjectMapper() @Test From 20a1aba44eb3c0749cdaab2b57958a66b18ce13b Mon Sep 17 00:00:00 2001 From: Neal Date: Thu, 14 Dec 2023 11:29:40 -0600 Subject: [PATCH 17/18] update name --- .../kotlin/web5/sdk/credentials/PresentationExchange.kt | 8 ++++---- .../web5/sdk/credentials/model/PresentationSubmission.kt | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt index fdc3c76bf..bcdf02f62 100644 --- a/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt +++ b/credentials/src/main/kotlin/web5/sdk/credentials/PresentationExchange.kt @@ -7,7 +7,7 @@ import com.nfeld.jsonpathkt.JsonPath import com.nfeld.jsonpathkt.extension.read import com.nimbusds.jwt.JWTParser import com.nimbusds.jwt.SignedJWT -import web5.sdk.credentials.model.DescriptorMap +import web5.sdk.credentials.model.InputDescriptorMapping import web5.sdk.credentials.model.InputDescriptorV2 import web5.sdk.credentials.model.PresentationDefinitionV2 import web5.sdk.credentials.model.PresentationSubmission @@ -69,7 +69,7 @@ public object PresentationExchange { } /** - * Creates a Presentation Submission in which the list of Verifiable Credentials JWTs (VCs) fulfills the given Presentation Definition. + * Creates a Presentation Submission in which the list of Verifiable Credentials JWTs (VCs) fulfills the given Presentation Definition. * Presentation Definition. * * @@ -90,7 +90,7 @@ public object PresentationExchange { val inputDescriptorToVcMap = mapInputDescriptorsToVCs(vcJwts, presentationDefinition) val vcJwtToIndexMap = vcJwts.withIndex().associate { (index, vcJwt) -> vcJwt to index } - val descriptorMapList = mutableListOf() + val descriptorMapList = mutableListOf() for ((inputDescriptor, vcList) in inputDescriptorToVcMap) { // Even if multiple VCs satisfy the input descriptor we use the first val vcJwt = vcList.firstOrNull() @@ -100,7 +100,7 @@ public object PresentationExchange { checkNotNull(vcIndex) { "Illegal state: vcJwt index not found" } descriptorMapList.add( - DescriptorMap( + InputDescriptorMapping( id = inputDescriptor.id, path = "$.verifiableCredential[$vcIndex]", format = "jwt_vc" diff --git a/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt b/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt index 347e129b8..73155be90 100644 --- a/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt +++ b/credentials/src/main/kotlin/web5/sdk/credentials/model/PresentationSubmission.kt @@ -13,17 +13,17 @@ public class PresentationSubmission( @JsonProperty("definition_id") public val definitionId: String, @JsonProperty("descriptor_map") - public val descriptorMap: List + public val descriptorMap: List ) /** * Represents descriptor map for a presentation submission. */ @JsonInclude(JsonInclude.Include.NON_NULL) -public class DescriptorMap( +public class InputDescriptorMapping( public val id: String, public val format: String, public val path: String, @JsonProperty("path_nested") - public val pathNested: DescriptorMap? = null + public val pathNested: InputDescriptorMapping? = null ) \ No newline at end of file From 8d457034faea128f5e901a14c26b4366b81b2452 Mon Sep 17 00:00:00 2001 From: Neal Date: Tue, 19 Dec 2023 10:04:07 -0600 Subject: [PATCH 18/18] change name --- .../kotlin/web5/sdk/credentials/PresentationExchangeTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt index 123f27db8..528a51d4e 100644 --- a/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt +++ b/credentials/src/test/kotlin/web5/sdk/credentials/PresentationExchangeTest.kt @@ -554,7 +554,7 @@ class PresentationExchangeTest { } -class Web5TestVectorsPresentationExchangeTest { +class Web5TestVectorsPresentationExchange { data class SelectCredTestInput( val presentationDefinition: PresentationDefinitionV2, val credentialJwts: List