diff --git a/examples/KotlinExample/Justfile b/examples/KotlinExample/Justfile index bc39d76d..aa1c33a1 100644 --- a/examples/KotlinExample/Justfile +++ b/examples/KotlinExample/Justfile @@ -1,6 +1,7 @@ dev: mvn compile - java -cp "target/classes/:target/dependency/*" com.example.HelloWorldKt + mvn dependency:copy-dependencies + java -cp "target/classes:target/dependency/*" com.example.HelloWorldKt run: (cd ../../bound/kt; mvn clean install) diff --git a/examples/KotlinExample/pom.xml b/examples/KotlinExample/pom.xml index dcd11868..73a32155 100644 --- a/examples/KotlinExample/pom.xml +++ b/examples/KotlinExample/pom.xml @@ -1,35 +1,91 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 + http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + com.example hello-world-kotlin 1.0-SNAPSHOT + jar + - 1.9.22 + 1.8.0 11 - 1.8 - 1.8 + 11 + 11 + + + + + central + https://repo.maven.apache.org/maven2 + + + + local + file://${user.home}/.m2/repository + + + + tbd-oss-snapshots + tbd-oss-snapshots + https://blockxyz.jfrog.io/artifactory/tbd-oss-snapshots-maven2/ + + true + + + false + + + + + + org.jetbrains.kotlin kotlin-stdlib-jdk8 ${kotlin.version} + + + + + + + + - web5.sdk - web5-kt - 1.0-SNAPSHOT + xyz.block + web5 + 0.0.5 + + src/main/kotlin + org.jetbrains.kotlin kotlin-maven-plugin @@ -54,6 +110,38 @@ ${kotlin.jvm.target} + + + + maven-compiler-plugin + 3.8.1 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + + MainKt + + + + + + diff --git a/examples/KotlinExample/src/main/kotlin/com/example/HelloWorld.kt b/examples/KotlinExample/src/main/kotlin/com/example/HelloWorld.kt index ab1610d4..399ee054 100644 --- a/examples/KotlinExample/src/main/kotlin/com/example/HelloWorld.kt +++ b/examples/KotlinExample/src/main/kotlin/com/example/HelloWorld.kt @@ -1,14 +1,124 @@ package com.example -import web5.sdk.Curve -import web5.sdk.LocalKeyManager +import web5.sdk.dids.methods.jwk.DidJwk +import web5.sdk.vc.* +import web5.sdk.vc.pex.* +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue fun main(args: Array) { - val keyManager = LocalKeyManager.newInMemory() - val keyAlias = keyManager.generatePrivateKey(Curve.ED25519, null) - val payload = "hello world".toByteArray().map { it.toUByte() } - val signature = keyManager.sign(keyAlias, payload) - val publicKey = keyManager.getPublicKey(keyAlias) - publicKey.verify(payload, signature.map { it.toUByte() }) - println("Success!") -} \ No newline at end of file + // Initialize the JSON mapper + val jsonMapper = jacksonObjectMapper() + + // Step 1: Create an issuer DID (Decentralized Identifier) + // Typically, this is the entity that issues the Verifiable Credential (VC) + val issuer = DidJwk.create() + val issuerUri = issuer.did.uri + println("Issuer DID URI: $issuerUri") + + // Step 2: Define a Presentation Definition (PD) + // This specifies the requirements that credentials must meet to be accepted + val inputDescriptor = InputDescriptor( + id = "test_input", + name = "Test Input", + purpose = "Testing Input", + constraints = Constraints( + fields = listOf( + Field( + id = "field1", + name = "Field 1", + path = listOf("$.credentialSubject.id"), + purpose = "Must match DID JWK pattern", + filter = Filter( + type = "string", + pattern = "^did:jwk:.*$" // Regex pattern to match DID JWK + ), + optional = false, + predicate = Optionality.Required + ) + ) + ) + ) + + // Create the Presentation Definition with the InputDescriptor + val presentationDefinition = PresentationDefinition( + id = "test_presentation_definition", + name = "Test Presentation Definition", + purpose = "Testing Presentation Exchange", + inputDescriptors = listOf(inputDescriptor) + ) + println("Presentation Definition created: $presentationDefinition") + + // Step 3: Create a Verifiable Credential (VC) that meets the PD criteria + // The credentialSubject.id matches the pattern specified in the PD + val vc = VerifiableCredential.create( + Issuer.StringIssuer(issuerUri), + CredentialSubject(issuerUri) + ) + + // Sign the VC using the issuer's DID + val vcJwt = vc.sign(issuer) + println("Verifiable Credential JWT created and signed:\n$vcJwt\n") + + // Step 4: Select credentials that match the PD's input descriptors + val vcJwts = listOf(vcJwt) + val presentationResult = presentationDefinition.createPresentationFromCredentials(vcJwts) + println("Presentation Result after matching credentials:\n$presentationResult\n") + + // Step 5: Create a Verifiable Presentation (VP) with the selected credentials + // The holder is the entity presenting the credentials + val holder = DidJwk.create() + val holderUri = holder.did.uri + println("Holder DID URI: $holderUri") + + // Include the presentation submission data to link the presentation to the PD + val additionalData = mapOf( + "presentation_submission" to presentationResult.presentationSubmission + ) + + val vpCreateOptions = VerifiablePresentationCreateOptions( + additionalProperties = additionalData + ) + + // Generate the VP with the matched credentials and additional data + val vp = VerifiablePresentation.create( + holderUri, + presentationResult.matchedVcJwts, + vpCreateOptions + ) + println("Verifiable Presentation created:\n$vp\n") + + // Step 6: Sign the VP to generate a JWT format presentation + val signedVpJwt = vp.sign(holder) + println("Signed Verifiable Presentation JWT:\n$signedVpJwt\n") + + // Step 7: Decode and verify the signed VP to ensure correctness + val decodedVp = VerifiablePresentation.fromVpJwt(signedVpJwt, true) + println("Decoded Verifiable Presentation:\n$decodedVp\n") + + // Step 8: Print the holder URI to verify it matches the expected holder + println("Decoded VP Holder URI: ${decodedVp.holder}\n") + + // Step 9: Print the Verifiable Credentials included in the presentation + println("Verifiable Credentials in VP:") + decodedVp.verifiableCredential.forEach { credential -> + println(credential) + } + println() + + // Step 10: Retrieve the presentation_submission from the decoded VP's additional data + val decodedPresentationSubmissionMap = decodedVp.additionalProperties?.get("presentation_submission") as? Map<*, *> + println("Presentation Submission from decoded VP:\n$decodedPresentationSubmissionMap\n") + + // Step 11: Convert the map back to PresentationSubmission + val jsonPresentationSubmission = jsonMapper.writeValueAsString(decodedPresentationSubmissionMap) + val decodedPresentationSubmission = jsonMapper.readValue(jsonPresentationSubmission) + println("Decoded Presentation Submission object:\n$decodedPresentationSubmission\n") + + // Step 12: Verify that the presentation_submission in additional_data matches the original one + if (decodedPresentationSubmission == presentationResult.presentationSubmission) { + println("Presentation submissions match.") + } else { + println("Presentation submissions do not match.") + } +}