diff --git a/credentials/src/test/kotlin/web5/sdk/credentials/VerifiableCredentialTest.kt b/credentials/src/test/kotlin/web5/sdk/credentials/VerifiableCredentialTest.kt index 2451e76f8..277f1f6c9 100644 --- a/credentials/src/test/kotlin/web5/sdk/credentials/VerifiableCredentialTest.kt +++ b/credentials/src/test/kotlin/web5/sdk/credentials/VerifiableCredentialTest.kt @@ -19,8 +19,8 @@ import web5.sdk.dids.Did import web5.sdk.dids.extensions.load 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 web5.sdk.testing.TestVectors import java.io.File import java.security.SignatureException @@ -129,7 +129,7 @@ class VerifiableCredentialTest { ) val issuerDid = DidIon.create( InMemoryKeyManager(), - CreateDidIonOptions(verificationMethodsToAdd = listOf(key)) + CreateDidIonOptions(verificationMethods = listOf(key)) ) val header = JWSHeader.Builder(JWSAlgorithm.ES256K) diff --git a/dids/src/main/kotlin/web5/sdk/dids/Models.kt b/dids/src/main/kotlin/web5/sdk/dids/Models.kt index 8ebc9da94..787bcedab 100644 --- a/dids/src/main/kotlin/web5/sdk/dids/Models.kt +++ b/dids/src/main/kotlin/web5/sdk/dids/Models.kt @@ -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. @@ -11,4 +22,47 @@ public enum class PublicKeyPurpose(@get:JsonValue public val code: String) { 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 = 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 { + /** + * [Serializer] implements [JsonSerializer] for use with the [JsonSerialize] annotation from Jackson. + */ + public object Serializer : JsonSerializer() { + 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() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): JWK { + val typeRef = object : TypeReference>() {} + val node = p.readValueAs(typeRef) as HashMap + return JWK.parse(node) + } + } } \ No newline at end of file diff --git a/dids/src/main/kotlin/web5/sdk/dids/methods/dht/DidDht.kt b/dids/src/main/kotlin/web5/sdk/dids/methods/dht/DidDht.kt index 1a5205690..d443378bb 100644 --- a/dids/src/main/kotlin/web5/sdk/dids/methods/dht/DidDht.kt +++ b/dids/src/main/kotlin/web5/sdk/dids/methods/dht/DidDht.kt @@ -30,6 +30,8 @@ import web5.sdk.dids.DidResolutionResult 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 /** @@ -77,14 +79,14 @@ private class DidDhtApiImpl(configuration: DidDhtConfiguration) : DidDhtApi(conf /** * 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 + * 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>>? = null, - public val services: Iterable? = null, + public val verificationMethods: Iterable = emptyList(), + public val services: Iterable = emptyList(), public val publish: Boolean = true, ) : CreateDidOptions @@ -146,27 +148,35 @@ public sealed class DidDhtApi(configuration: DidDhtConfiguration) : DidMethod - 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}")) + .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) @@ -178,7 +188,7 @@ public sealed class DidDhtApi(configuration: DidDhtConfiguration) : DidMethod = emptyList(), + override val services: Iterable = emptyList(), val idsOfServicesToRemove: Iterable = emptyList(), - override val verificationMethodsToAdd: Iterable = emptyList(), + override val verificationMethods: Iterable = emptyList(), val idsOfPublicKeysToRemove: Iterable = emptyList(), ) : CommonOptions { internal fun toPatches(publicKeys: Iterable): List { @@ -113,7 +112,7 @@ public data class UpdateDidIonOptions( } return buildList { - addIfNotEmpty(servicesToAdd, ::AddServicesAction) + addIfNotEmpty(services, ::AddServicesAction) addIfNotEmpty(idsOfServicesToRemove, ::RemoveServicesAction) addIfNotEmpty(publicKeys, ::AddPublicKeysAction) addIfNotEmpty(idsOfPublicKeysToRemove, ::RemovePublicKeysAction) @@ -126,8 +125,8 @@ public data class UpdateDidIonOptions( * * @param recoveryKeyAlias is the alias within the keyManager to use when signing. It must match the recovery used with * the last recovery operation. - * @param verificationMethodsToAdd List of specs that will be added to the DID ION document. - * @param servicesToAdd When provided, the services will be added to the DID document. Note that for each of the + * @param verificationMethods List of specs that will be added to the DID ION document. + * @param services When provided, the services will be added to the DID document. Note that for each of the * services that should be added, the following must hold: * - The `id` field cannot be over 50 chars and must only use characters from the Base64URL character set. * - The `type` field cannot be over 30 characters. @@ -135,8 +134,8 @@ public data class UpdateDidIonOptions( */ public class RecoverDidIonOptions( public val recoveryKeyAlias: String, - public override val verificationMethodsToAdd: Iterable = emptyList(), - public override val servicesToAdd: Iterable = emptyList(), + public override val verificationMethods: Iterable = emptyList(), + public override val services: Iterable = emptyList(), ) : CommonOptions /** @@ -363,9 +362,9 @@ public sealed class DidIonApi( val reveal = updatePublicKey.reveal() val commitment = newUpdatePublicKey.commitment() - validateServices(options.servicesToAdd) + validateServices(options.services) - val publicKeysWithAliases = options.verificationMethodsToAdd.toPublicKeys(keyManager) + val publicKeysWithAliases = options.verificationMethods.toPublicKeys(keyManager) val publicKeys = publicKeysWithAliases.map { it.second } validateDidDocumentKeys(publicKeys) @@ -453,7 +452,7 @@ public sealed class DidIonApi( val publicKeysToAdd = publicKeysWithAlias.map { it.second } validateDidDocumentKeys(publicKeysToAdd) - validateServices(options?.servicesToAdd ?: emptyList()) + validateServices(options?.services ?: emptyList()) val createOperationDelta = Delta( patches = options.toPatches(publicKeysToAdd), @@ -482,7 +481,7 @@ public sealed class DidIonApi( } private fun publicKeysWithAliasesToAdd(options: CommonOptions?, keyManager: KeyManager) = - if (options == null || options.verificationMethodsToAdd.count() == 0) { + if (options == null || options.verificationMethods.count() == 0) { listOf( VerificationMethodCreationParams( JWSAlgorithm.ES256K, @@ -490,7 +489,7 @@ public sealed class DidIonApi( ) ).toPublicKeys(keyManager) } else { - options.verificationMethodsToAdd.toPublicKeys(keyManager) + options.verificationMethods.toPublicKeys(keyManager) } private fun validateServices(services: Iterable) = services.forEach { @@ -558,7 +557,7 @@ public sealed class DidIonApi( val publicKeysToAdd = publicKeyWithAliases.map { it.second } validateDidDocumentKeys(publicKeysToAdd) - validateServices(options.servicesToAdd) + validateServices(options.services) val delta = Delta( patches = options.toPatches(publicKeysToAdd), @@ -671,7 +670,7 @@ private fun CommonOptions?.toPatches(publicKeysToAdd: Iterable): Iter ReplaceAction( Document( publicKeys = publicKeysToAdd, - services = this?.servicesToAdd ?: emptyList() + services = this?.services ?: emptyList() ) ) ) @@ -693,8 +692,8 @@ public class IonRecoverResult( public val operationsResponse: String) private interface CommonOptions { - val verificationMethodsToAdd: Iterable - val servicesToAdd: Iterable + val verificationMethods: Iterable + val services: Iterable } private fun JWK.commitment(): Commitment { @@ -778,115 +777,18 @@ public data class KeyAliases( * Options available when creating an ion did. * * - * @param verificationMethodsToAdd List of specs that will be added to the DID ION document. - * @param servicesToAdd When provided, the services will be added to the DID document. Note that for each of the + * @param verificationMethods List of specs that will be added to the DID ION document. + * @param services When provided, the services will be added to the DID document. Note that for each of the * services that should be added, the following must hold: * - The `id` field cannot be over 50 chars and must only use characters from the Base64URL character set. * - The `type` field cannot be over 30 characters. * - The `serviceEndpoint` must be a valid URI. */ public class CreateDidIonOptions( - override val verificationMethodsToAdd: Iterable = emptyList(), - override val servicesToAdd: Iterable = emptyList(), + override val verificationMethods: Iterable = emptyList(), + override val services: Iterable = emptyList(), ) : CreateDidOptions, CommonOptions -/** Common interface for options available when adding a VerificationMethod. */ -public interface VerificationMethodSpec - -private interface VerificationMethodGenerator { - fun generate(): Pair -} - -/** - * A [VerificationMethodSpec] where a [KeyManager] will be used to generate the underlying verification method keys. - * The parameters [algorithm], [curve], and [options] will be forwarded to the keyManager. - * - * [relationships] will be used to determine the verification relationships in the DID Document being created. - * */ -public class VerificationMethodCreationParams( - public val algorithm: Algorithm, - public val curve: Curve? = null, - public val options: KeyGenOptions? = null, - public val relationships: Iterable -) : VerificationMethodSpec { - internal fun toGenerator(keyManager: KeyManager): VerificationMethodKeyManagerGenerator { - return VerificationMethodKeyManagerGenerator(keyManager, this) - } -} - -/** - * A [VerificationMethodSpec] according to https://w3c-ccg.github.io/lds-jws2020/. - * - * The [id] property cannot be over 50 chars and must only use characters from the Base64URL character set. - */ -public class JsonWebKey2020VerificationMethod( - public val id: String, - public val controller: String? = null, - public val publicKeyJwk: JWK, - public val relationships: Iterable = emptySet() -) : VerificationMethodSpec, VerificationMethodGenerator { - override fun generate(): Pair { - return Pair(null, PublicKey(id, "JsonWebKey2020", controller, publicKeyJwk, relationships)) - } -} - -/** - * A [VerificationMethodSpec] according to https://w3c-ccg.github.io/lds-ecdsa-secp256k1-2019/. - * - * The [id] property cannot be over 50 chars and must only use characters from the Base64URL character set. - */ -public class EcdsaSecp256k1VerificationKey2019VerificationMethod( - public val id: String, - public val controller: String? = null, - public val publicKeyJwk: JWK, - public val relationships: Iterable = emptySet() -) : VerificationMethodSpec, VerificationMethodGenerator { - override fun generate(): Pair { - return Pair(id, PublicKey(id, "EcdsaSecp256k1VerificationKey2019", controller, publicKeyJwk, relationships)) - } -} - -internal class VerificationMethodKeyManagerGenerator( - val keyManager: KeyManager, - val params: VerificationMethodCreationParams, -) : VerificationMethodGenerator { - - override fun generate(): Pair { - val alias = keyManager.generatePrivateKey( - algorithm = params.algorithm, - curve = params.curve, - options = params.options - ) - val publicKeyJwk = keyManager.getPublicKey(alias) - return Pair( - alias, - PublicKey( - id = UUID.randomUUID().toString(), - type = "JsonWebKey2020", - publicKeyJwk = publicKeyJwk, - purposes = params.relationships, - ) - ) - } -} - - -private fun Iterable.toGenerators(keyManager: KeyManager): List { - return buildList { - for (verificationMethodSpec in this@toGenerators) { - when (verificationMethodSpec) { - is VerificationMethodCreationParams -> add(verificationMethodSpec.toGenerator(keyManager)) - - is VerificationMethodGenerator -> add(verificationMethodSpec) - } - } - } - -} - -private fun Iterable.toPublicKeys(keyManager: KeyManager) = toGenerators( - keyManager -).map { it.generate() } /** * Metadata related to the creation of a DID (Decentralized Identifier) on the Sidetree protocol. diff --git a/dids/src/main/kotlin/web5/sdk/dids/methods/ion/models/Models.kt b/dids/src/main/kotlin/web5/sdk/dids/methods/ion/models/Models.kt index d0d141a19..434b9ddf2 100644 --- a/dids/src/main/kotlin/web5/sdk/dids/methods/ion/models/Models.kt +++ b/dids/src/main/kotlin/web5/sdk/dids/methods/ion/models/Models.kt @@ -3,13 +3,8 @@ package web5.sdk.dids.methods.ion.models import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonSubTypes import com.fasterxml.jackson.annotation.JsonTypeInfo -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 @@ -18,7 +13,8 @@ import com.fasterxml.jackson.databind.ser.std.StdSerializer import com.nimbusds.jose.jwk.JWK import web5.sdk.common.Convert import web5.sdk.common.EncodingFormat -import web5.sdk.dids.PublicKeyPurpose +import web5.sdk.dids.JacksonJwk +import web5.sdk.dids.PublicKey /** * Represents an ION document containing public keys and services. See bullet 2 in https://identity.foundation/sidetree/spec/#replace. @@ -45,49 +41,6 @@ public data class Service( public val serviceEndpoint: String ) -/** - * 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 = 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. - */ -private class JacksonJwk { - /** - * [Serializer] implements [JsonSerializer] for use with the [JsonSerialize] annotation from Jackson. - */ - object Serializer : JsonSerializer() { - 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. - */ - object Deserializer : JsonDeserializer() { - override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): JWK { - val typeRef = object : TypeReference>() {} - val node = p.readValueAs(typeRef) as HashMap - return JWK.parse(node) - } - } -} - /** * Sealed class representing a patch action in the ION document. See https://identity.foundation/sidetree/spec/#did-state-patches */ diff --git a/dids/src/main/kotlin/web5/sdk/dids/verificationmethods/VerificationMethod.kt b/dids/src/main/kotlin/web5/sdk/dids/verificationmethods/VerificationMethod.kt new file mode 100644 index 000000000..90bdbc826 --- /dev/null +++ b/dids/src/main/kotlin/web5/sdk/dids/verificationmethods/VerificationMethod.kt @@ -0,0 +1,125 @@ +package web5.sdk.dids.verificationmethods + +import com.nimbusds.jose.Algorithm +import com.nimbusds.jose.jwk.Curve +import com.nimbusds.jose.jwk.JWK +import web5.sdk.crypto.KeyGenOptions +import web5.sdk.crypto.KeyManager +import web5.sdk.dids.PublicKey +import web5.sdk.dids.PublicKeyPurpose +import java.util.UUID + +/** Common interface for options available when adding a VerificationMethod. */ +public interface VerificationMethodSpec + +/** Common interface for classes that can generate a [PublicKey] from a [VerificationMethodSpec]. */ +public interface VerificationMethodGenerator { + /** + * Generates a [PublicKey] from a [VerificationMethodSpec]. The first element of the pair is an optional id that can + * be used to identify the private key associated with the generated public key. + */ + public fun generate(): Pair +} + +/** + * A [VerificationMethodSpec] where a [KeyManager] will be used to generate the underlying verification method keys. + * The parameters [algorithm], [curve], and [options] will be forwarded to the keyManager. + * + * [relationships] will be used to determine the verification relationships in the DID Document being created. + * */ +public class VerificationMethodCreationParams( + public val algorithm: Algorithm, + public val curve: Curve? = null, + public val options: KeyGenOptions? = null, + public val relationships: Iterable +) : VerificationMethodSpec { + internal fun toGenerator(keyManager: KeyManager): VerificationMethodKeyManagerGenerator { + return VerificationMethodKeyManagerGenerator(keyManager, this) + } +} + +private const val JsonWebKey2020Type = "JsonWebKey2020" + +/** + * A [VerificationMethodSpec] according to https://w3c-ccg.github.io/lds-jws2020/. + * + * The [id] property cannot be over 50 chars and must only use characters from the Base64URL character set. + */ +public class JsonWebKey2020VerificationMethod( + public val id: String, + public val controller: String? = null, + public val publicKeyJwk: JWK, + public val relationships: Iterable = emptySet() +) : VerificationMethodSpec, VerificationMethodGenerator { + override fun generate(): Pair { + return Pair(null, PublicKey(id, JsonWebKey2020Type, controller, publicKeyJwk, relationships)) + } +} + +/** + * A [VerificationMethodSpec] according to https://w3c-ccg.github.io/lds-ecdsa-secp256k1-2019/. + * + * The [id] property cannot be over 50 chars and must only use characters from the Base64URL character set. + */ +public class EcdsaSecp256k1VerificationKey2019VerificationMethod( + public val id: String, + public val controller: String? = null, + public val publicKeyJwk: JWK, + public val relationships: Iterable = emptySet() +) : VerificationMethodSpec, VerificationMethodGenerator { + override fun generate(): Pair { + return Pair(id, PublicKey(id, "EcdsaSecp256k1VerificationKey2019", controller, publicKeyJwk, relationships)) + } +} + +internal class VerificationMethodKeyManagerGenerator( + val keyManager: KeyManager, + val params: VerificationMethodCreationParams, +) : VerificationMethodGenerator { + + override fun generate(): Pair { + val alias = keyManager.generatePrivateKey( + algorithm = params.algorithm, + curve = params.curve, + options = params.options + ) + val publicKeyJwk = keyManager.getPublicKey(alias) + return Pair( + alias, + PublicKey( + id = UUID.randomUUID().toString(), + type = JsonWebKey2020Type, + publicKeyJwk = publicKeyJwk, + purposes = params.relationships, + ) + ) + } +} + +/** + * Converts a [VerificationMethodSpec] to a [VerificationMethodGenerator] with the given [keyManager]. + */ +public fun VerificationMethodSpec.toGenerator(keyManager: KeyManager): VerificationMethodGenerator { + return when (this) { + is VerificationMethodCreationParams -> toGenerator(keyManager) + is VerificationMethodGenerator -> this + else -> { + throw IllegalArgumentException("Unsupported VerificationMethodSpec type: ${this::class.simpleName}") + } + } +} + +/** + * Converts a list of [VerificationMethodSpec] to a list of [VerificationMethodGenerator]s with the given [keyManager]. + */ +public fun Iterable.toGenerators(keyManager: KeyManager): List { + return this.map { it.toGenerator(keyManager) } +} + +/** + * Converts a list of [VerificationMethodSpec] to a list of [PublicKey]s with the given [keyManager]. + */ +public fun Iterable.toPublicKeys(keyManager: KeyManager) + : List> = toGenerators( + keyManager +).map { it.generate() } \ No newline at end of file diff --git a/dids/src/test/kotlin/web5/sdk/dids/methods/dht/DidDhtTest.kt b/dids/src/test/kotlin/web5/sdk/dids/methods/dht/DidDhtTest.kt index 2a73b2d6e..e7ad17f9b 100644 --- a/dids/src/test/kotlin/web5/sdk/dids/methods/dht/DidDhtTest.kt +++ b/dids/src/test/kotlin/web5/sdk/dids/methods/dht/DidDhtTest.kt @@ -2,14 +2,12 @@ package web5.sdk.dids.methods.dht import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.jwk.Curve -import com.nimbusds.jose.jwk.JWK import foundation.identity.did.Service import foundation.identity.did.parser.ParserException import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.respond import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode -import io.ktor.util.hex import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow @@ -17,6 +15,8 @@ import org.junit.jupiter.api.assertThrows import web5.sdk.common.ZBase32 import web5.sdk.crypto.InMemoryKeyManager import web5.sdk.dids.PublicKeyPurpose +import web5.sdk.dids.verificationmethods.JsonWebKey2020VerificationMethod +import web5.sdk.dids.verificationmethods.VerificationMethodCreationParams import java.net.URI import kotlin.test.assertContains import kotlin.test.assertEquals @@ -43,6 +43,7 @@ class DidDhtTest { @Test fun `validate identity key`() { val manager = InMemoryKeyManager() + DidDht.create(manager, CreateDidDhtOptions(publish = false)) val keyAlias = manager.generatePrivateKey(JWSAlgorithm.EdDSA, Curve.Ed25519) val publicKey = manager.getPublicKey(keyAlias) val identifier = DidDht.getDidIdentifier(publicKey) @@ -97,14 +98,51 @@ class DidDhtTest { assertNull(did.didDocument!!.services) } + @Test + fun `create with bad verification method id throws error`() { + val manager = InMemoryKeyManager() + + val exception = assertThrows { + DidDht.create( + manager, + CreateDidDhtOptions( + verificationMethods = listOf( + JsonWebKey2020VerificationMethod( + id = "0", + publicKeyJwk = manager.getPublicKey( + manager.generatePrivateKey( + JWSAlgorithm.EdDSA, + Curve.Ed25519 + ) + ).toPublicJWK(), + relationships = listOf(PublicKeyPurpose.AUTHENTICATION) + ) + ), + publish = false + ) + ) + } + + assertEquals("id for verification method cannot be \"0\"", exception.message) + } + @Test fun `create with another key and service`() { val manager = InMemoryKeyManager() val otherKey = manager.generatePrivateKey(JWSAlgorithm.ES256K, Curve.SECP256K1) val publicKeyJwk = manager.getPublicKey(otherKey).toPublicJWK() - val verificationMethodsToAdd: Iterable>> = listOf( - Pair(publicKeyJwk, arrayOf(PublicKeyPurpose.AUTHENTICATION, PublicKeyPurpose.ASSERTION_METHOD)) + val verificationMethodsToAdd = listOf( + JsonWebKey2020VerificationMethod( + id = publicKeyJwk.keyID, + publicKeyJwk = publicKeyJwk, + relationships = listOf(PublicKeyPurpose.AUTHENTICATION, PublicKeyPurpose.ASSERTION_METHOD) + ), + VerificationMethodCreationParams( + algorithm = JWSAlgorithm.EdDSA, + curve = Curve.Ed25519, + relationships = listOf(PublicKeyPurpose.ASSERTION_METHOD, PublicKeyPurpose.CAPABILITY_INVOCATION) + ), ) val serviceToAdd = @@ -121,11 +159,11 @@ class DidDhtTest { assertNotNull(did) assertNotNull(did.didDocument) - assertEquals(2, did.didDocument!!.verificationMethods.size) - assertEquals(2, did.didDocument!!.assertionMethodVerificationMethods.size) + assertEquals(3, did.didDocument!!.verificationMethods.size) + assertEquals(3, did.didDocument!!.assertionMethodVerificationMethods.size) assertEquals(2, did.didDocument!!.authenticationVerificationMethods.size) assertEquals(1, did.didDocument!!.capabilityDelegationVerificationMethods.size) - assertEquals(1, did.didDocument!!.capabilityInvocationVerificationMethods.size) + assertEquals(2, did.didDocument!!.capabilityInvocationVerificationMethods.size) assertNull(did.didDocument!!.keyAgreementVerificationMethods) assertNotNull(did.didDocument!!.services) assertEquals(1, did.didDocument!!.services.size) @@ -252,8 +290,12 @@ class DidDhtTest { val otherKey = manager.generatePrivateKey(JWSAlgorithm.ES256K, Curve.SECP256K1) val publicKeyJwk = manager.getPublicKey(otherKey).toPublicJWK() - val verificationMethodsToAdd: Iterable>> = listOf( - Pair(publicKeyJwk, arrayOf(PublicKeyPurpose.AUTHENTICATION, PublicKeyPurpose.ASSERTION_METHOD)) + val verificationMethodsToAdd = listOf( + JsonWebKey2020VerificationMethod( + id = publicKeyJwk.keyID, + publicKeyJwk = publicKeyJwk, + relationships = listOf(PublicKeyPurpose.AUTHENTICATION, PublicKeyPurpose.ASSERTION_METHOD) + ) ) val serviceToAdd = Service.builder() diff --git a/dids/src/test/kotlin/web5/sdk/dids/methods/ion/DidIonTest.kt b/dids/src/test/kotlin/web5/sdk/dids/methods/ion/DidIonTest.kt index 2f50e1052..5b18758e6 100644 --- a/dids/src/test/kotlin/web5/sdk/dids/methods/ion/DidIonTest.kt +++ b/dids/src/test/kotlin/web5/sdk/dids/methods/ion/DidIonTest.kt @@ -22,12 +22,16 @@ import org.mockito.kotlin.spy import org.mockito.kotlin.whenever import web5.sdk.crypto.AwsKeyManager import web5.sdk.crypto.InMemoryKeyManager +import web5.sdk.dids.PublicKey import web5.sdk.dids.PublicKeyPurpose -import web5.sdk.dids.methods.ion.models.PublicKey import web5.sdk.dids.methods.ion.models.Service import web5.sdk.dids.methods.ion.models.SidetreeCreateOperation import web5.sdk.dids.methods.ion.models.SidetreeUpdateOperation import web5.sdk.dids.methods.util.readKey +import web5.sdk.dids.verificationmethods.EcdsaSecp256k1VerificationKey2019VerificationMethod +import web5.sdk.dids.verificationmethods.JsonWebKey2020VerificationMethod +import web5.sdk.dids.verificationmethods.VerificationMethodCreationParams +import web5.sdk.dids.verificationmethods.VerificationMethodSpec import java.io.File import kotlin.test.Ignore import kotlin.test.Test @@ -82,7 +86,7 @@ class DidIonTest { DidIon.create( InMemoryKeyManager(), CreateDidIonOptions( - verificationMethodsToAdd = listOf( + verificationMethods = listOf( JsonWebKey2020VerificationMethod( id = "space is not part of the base64 url chars", publicKeyJwk = verificationKey @@ -133,7 +137,7 @@ class DidIonTest { DidIon.create( InMemoryKeyManager(), CreateDidIonOptions( - servicesToAdd = listOf(testCase.service) + services = listOf(testCase.service) ) ) } @@ -149,7 +153,7 @@ class DidIonTest { DidIon.create( InMemoryKeyManager(), CreateDidIonOptions( - verificationMethodsToAdd = listOf( + verificationMethods = listOf( JsonWebKey2020VerificationMethod( id = "something_thats_really_really_really_really_really_really_long", publicKeyJwk = verificationKey @@ -178,14 +182,14 @@ class DidIonTest { engine = mockEngine() } val opts = CreateDidIonOptions( - verificationMethodsToAdd = listOf( + verificationMethods = listOf( JsonWebKey2020VerificationMethod( id = verificationKey.keyID, publicKeyJwk = verificationKey, relationships = listOf(PublicKeyPurpose.AUTHENTICATION), ) ), - servicesToAdd = listOf( + services = listOf( Service( id = "dwn", type = "DWN", @@ -222,7 +226,7 @@ class DidIonTest { engine = mockEngine() }.create( keyManager, CreateDidIonOptions( - verificationMethodsToAdd = listOf( + verificationMethods = listOf( VerificationMethodCreationParams( JWSAlgorithm.ES256K, relationships = listOf(PublicKeyPurpose.AUTHENTICATION, PublicKeyPurpose.ASSERTION_METHOD) @@ -312,8 +316,8 @@ class DidIonTest { "did:ion:123", UpdateDidIonOptions( updateKeyAlias = updateKeyAlias, - servicesToAdd = testCase.services, - verificationMethodsToAdd = testCase.publicKeys, + services = testCase.services, + verificationMethods = testCase.publicKeys, ) ) } @@ -355,8 +359,8 @@ class DidIonTest { val (result, _) = DidIon.createOperation( keyManager, CreateDidIonOptions( - verificationMethodsToAdd = listOf(verificationMethod1), - servicesToAdd = listOf(service), + verificationMethods = listOf(verificationMethod1), + services = listOf(service), ) ) @@ -422,9 +426,9 @@ class DidIonTest { "did:ion:EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg", UpdateDidIonOptions( updateKeyAlias = updateKeyId, - servicesToAdd = listOf(service), + services = listOf(service), idsOfServicesToRemove = setOf("someId1"), - verificationMethodsToAdd = listOf(publicKey1), + verificationMethods = listOf(publicKey1), idsOfPublicKeysToRemove = setOf("someId2"), ), ) @@ -457,8 +461,8 @@ class DidIonTest { "did:ion:EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg", RecoverDidIonOptions( recoveryKeyAlias = recoveryKeyAlias, - verificationMethodsToAdd = listOf(publicKey1), - servicesToAdd = listOf(service), + verificationMethods = listOf(publicKey1), + services = listOf(service), ) )