Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace verificationMethods type in did:dht to use VerificationMethodSpec #146

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import web5.sdk.crypto.AwsKeyManager
import web5.sdk.crypto.InMemoryKeyManager
import web5.sdk.dids.methods.ion.CreateDidIonOptions
import web5.sdk.dids.methods.ion.DidIon
import web5.sdk.dids.methods.ion.JsonWebKey2020VerificationMethod
import web5.sdk.dids.methods.key.DidKey
import web5.sdk.dids.verificationmethods.JsonWebKey2020VerificationMethod
import java.security.SignatureException
import java.text.ParseException
import java.util.UUID
Expand Down Expand Up @@ -161,7 +161,7 @@ class VerifiableCredentialTest {
)
val issuerDid = DidIon.create(
InMemoryKeyManager(),
CreateDidIonOptions(verificationMethodsToAdd = listOf(key))
CreateDidIonOptions(verificationMethods = listOf(key))
)

val header = JWSHeader.Builder(JWSAlgorithm.ES256K)
Expand Down
54 changes: 54 additions & 0 deletions dids/src/main/kotlin/web5/sdk/dids/Models.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
package web5.sdk.dids

import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.nimbusds.jose.jwk.JWK

/**
* Enum representing the purpose of a public key.
Expand All @@ -11,4 +22,47 @@
ASSERTION_METHOD("assertionMethod"),
CAPABILITY_DELEGATION("capabilityDelegation"),
CAPABILITY_INVOCATION("capabilityInvocation"),
}

/**
* Represents a public key in the ION document as defined in item 3 of https://identity.foundation/sidetree/spec/#add-public-keys
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public data class PublicKey(
public val id: String,
public val type: String,
public val controller: String? = null,

@JsonSerialize(using = JacksonJwk.Serializer::class)
@JsonDeserialize(using = JacksonJwk.Deserializer::class)
public val publicKeyJwk: JWK,
public val purposes: Iterable<PublicKeyPurpose> = emptyList()
)

/**
* JacksonJWK is a utility class that facilitates serialization for [JWK] types, so that it's easy to integrate with any
* class that is meant to be serialized to/from JSON.
*/
public class JacksonJwk {

Check warning on line 46 in dids/src/main/kotlin/web5/sdk/dids/Models.kt

View check run for this annotation

Codecov / codecov/patch

dids/src/main/kotlin/web5/sdk/dids/Models.kt#L46

Added line #L46 was not covered by tests
/**
* [Serializer] implements [JsonSerializer] for use with the [JsonSerialize] annotation from Jackson.
*/
public object Serializer : JsonSerializer<JWK>() {
override fun serialize(value: JWK, gen: JsonGenerator, serializers: SerializerProvider) {
with(gen) {
writeObject(value.toJSONObject())
}
}
}

/**
* [Deserializer] implements [JsonDeserializer] for use with the [JsonDeserialize] annotation from Jackson.
*/
public object Deserializer : JsonDeserializer<JWK>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): JWK {
val typeRef = object : TypeReference<HashMap<String, Any>>() {}
val node = p.readValueAs(typeRef) as HashMap<String, Any>
return JWK.parse(node)
}
}
}
80 changes: 46 additions & 34 deletions dids/src/main/kotlin/web5/sdk/dids/methods/dht/DidDht.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import web5.sdk.dids.PublicKeyPurpose
import web5.sdk.dids.ResolveDidOptions
import web5.sdk.dids.validateKeyMaterialInsideKeyManager
import web5.sdk.dids.verificationmethods.VerificationMethodSpec
import web5.sdk.dids.verificationmethods.toPublicKeys
import java.net.URI

/**
Expand Down Expand Up @@ -77,14 +79,14 @@

/**
* Specifies options for creating a new "did:dht" Decentralized Identifier (DID).
* @property verificationMethods A list of [JWK]s to add to the DID Document mapped to their purposes
* as verification methods.
* @property verificationMethods List of specs that will be added to the DID ION document. It's important to note
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @property verificationMethods List of specs that will be added to the DID ION document. It's important to note
* @property verificationMethods List of verification methods that will be added to the DID DHT document. It's important to note

* that each verification method's id must be different from "0".
* @property services A list of [Service]s to add to the DID Document.
* @property publish Whether to publish the DID Document to the DHT after creation.
*/
public class CreateDidDhtOptions(
public val verificationMethods: Iterable<Pair<JWK, Array<PublicKeyPurpose>>>? = null,
public val services: Iterable<Service>? = null,
public val verificationMethods: Iterable<VerificationMethodSpec> = emptyList(),
public val services: Iterable<Service> = emptyList(),
public val publish: Boolean = true,
) : CreateDidOptions

Expand Down Expand Up @@ -146,27 +148,35 @@
}

// map to the DID object model's verification methods
val verificationMethods = (opts.verificationMethods?.map { (key, purposes) ->
VerificationMethod.builder()
.id(URI.create("$id#${key.keyID}"))
.type("JsonWebKey2020")
.controller(URI.create(id))
.publicKeyJwk(key.toPublicJWK().toJSONObject())
.build().also { verificationMethod ->
purposes.forEach { relationship ->
relationshipsMap.getOrPut(relationship) { mutableListOf() }.add(
VerificationMethod.builder().id(verificationMethod.id).build()
)
}
}
} ?: emptyList()) + identityVerificationMethod
opts.services?.forEach { service ->
val verificationMethodToPublicKey = opts.verificationMethods
.toPublicKeys(keyManager)
.associate { (alias, publicKey) ->
val key = publicKey.publicKeyJwk
require(publicKey.id != "0") { "id for verification method cannot be \"0\"" }

val verificationMethod = VerificationMethod.builder()
.id(URI.create("$id#${publicKey.id}"))
decentralgabe marked this conversation as resolved.
Show resolved Hide resolved
.type(publicKey.type)
.controller(URI.create(id))
.publicKeyJwk(key.toPublicJWK().toJSONObject())
.build()
verificationMethod to publicKey
}
verificationMethodToPublicKey.forEach { (verificationMethod, publicKey) ->
publicKey.purposes.forEach { relationship ->
relationshipsMap.getOrPut(relationship) { mutableListOf() }.add(
VerificationMethod.builder().id(verificationMethod.id).build()
)
}
}
val verificationMethods = verificationMethodToPublicKey.keys + identityVerificationMethod
opts.services.forEach { service ->
requireNotNull(service.id) { "Service id cannot be null" }
requireNotNull(service.type) { "Service type cannot be null" }
requireNotNull(service.serviceEndpoint) { "Service serviceEndpoint cannot be null" }
}
// map to the DID object model's services
val services = opts.services?.map { service ->
val services = opts.services.map { service ->
Service.builder()
.id(URI.create("$id#${service.id}"))
.type(service.type)
Expand All @@ -178,7 +188,7 @@
val didDocument =
DIDDocument.builder()
.id(URI(id))
.verificationMethods(verificationMethods)
.verificationMethods(verificationMethods.toList())
.services(services)
.assertionMethodVerificationMethods(relationshipsMap[PublicKeyPurpose.ASSERTION_METHOD])
.authenticationVerificationMethods(relationshipsMap[PublicKeyPurpose.AUTHENTICATION])
Expand Down Expand Up @@ -263,22 +273,24 @@
}

override fun load(uri: String, keyManager: KeyManager): DidDht {
validateKeyMaterialInsideKeyManager(uri, keyManager)
validateIdentityKey(uri, keyManager)
return DidDht(uri, keyManager, null, this)
validateKeyMaterialInsideKeyManager(uri, keyManager)
validateIdentityKey(uri, keyManager)
return DidDht(uri, keyManager, null, this)

Check warning on line 278 in dids/src/main/kotlin/web5/sdk/dids/methods/dht/DidDht.kt

View check run for this annotation

Codecov / codecov/patch

dids/src/main/kotlin/web5/sdk/dids/methods/dht/DidDht.kt#L276-L278

Added lines #L276 - L278 were not covered by tests
}

internal fun validateIdentityKey(did: String, keyManager: KeyManager) {
val parsedDid = DID.fromString(did)
val decodedId = ZBase32.decode(parsedDid.methodSpecificId)
require(decodedId.size == 32) {
"expected size of decoded identifier \"${parsedDid.methodSpecificId}\" to be 32"
}

internal fun validateIdentityKey(did: String, keyManager: KeyManager) {
val parsedDid = DID.fromString(did)
val decodedId = ZBase32.decode(parsedDid.methodSpecificId)
require(decodedId.size == 32) {
"expected size of decoded identifier \"${parsedDid.methodSpecificId}\" to be 32"
}
val publicKeyJwk = Ed25519.bytesToPublicKey(decodedId)
val identityKeyAlias = keyManager.getDeterministicAlias(publicKeyJwk)
keyManager.getPublicKey(identityKeyAlias)
}

val publicKeyJwk = Ed25519.bytesToPublicKey(decodedId)
val identityKeyAlias = keyManager.getDeterministicAlias(publicKeyJwk)
keyManager.getPublicKey(identityKeyAlias)
}/**
/**
* Generates the identifier for a did:dht DID given its identity key.
*
* @param identityKey the key used to generate the DID's identifier
Expand Down
Loading