diff --git a/bindings/web5_uniffi/src/lib.rs b/bindings/web5_uniffi/src/lib.rs index d8297f2e..6ffc9fdd 100644 --- a/bindings/web5_uniffi/src/lib.rs +++ b/bindings/web5_uniffi/src/lib.rs @@ -18,7 +18,7 @@ use web5_uniffi_wrapper::{ did::Did, methods::{ did_dht::{did_dht_resolve, DidDht}, - did_jwk::{did_jwk_resolve, DidJwk}, + did_jwk::{did_jwk_create, did_jwk_resolve, DidJwkCreateOptions}, did_web::{did_web_resolve, DidWeb}, }, portable_did::PortableDid, @@ -36,10 +36,7 @@ use web5::{ verification_method::VerificationMethod as VerificationMethodData, }, did::Did as DidData, - methods::{ - did_dht::DidDht as DidDhtData, did_jwk::DidJwk as DidJwkData, - did_web::DidWeb as DidWebData, - }, + methods::{did_dht::DidDht as DidDhtData, did_web::DidWeb as DidWebData}, portable_did::PortableDid as PortableDidData, resolution::{ document_metadata::DocumentMetadata as DocumentMetadataData, diff --git a/bindings/web5_uniffi/src/web5.udl b/bindings/web5_uniffi/src/web5.udl index 9c84a3d8..1575b919 100644 --- a/bindings/web5_uniffi/src/web5.udl +++ b/bindings/web5_uniffi/src/web5.udl @@ -1,7 +1,10 @@ namespace web5 { JwkData ed25519_generator_generate(); + [Throws=Web5Error] + BearerDid did_jwk_create(DidJwkCreateOptions? options); ResolutionResult did_jwk_resolve([ByRef] string uri); + [Async, Throws=Web5Error] ResolutionResult did_web_resolve([ByRef] string uri); [Throws=Web5Error] @@ -31,6 +34,8 @@ interface Jwk { [Trait, WithForeign] interface KeyManager { + [Throws=Web5Error] + JwkData import_private_jwk(JwkData private_jwk); [Throws=Web5Error] Signer get_signer(JwkData public_jwk); }; @@ -38,14 +43,15 @@ interface KeyManager { interface InMemoryKeyManager { constructor(); [Throws=Web5Error] - Signer get_signer(JwkData public_jwk); + JwkData import_private_jwk(JwkData private_jwk); [Throws=Web5Error] - JwkData import_private_jwk(JwkData private_key); + Signer get_signer(JwkData public_jwk); KeyManager get_as_key_manager(); }; enum Dsa { - "Ed25519" + "Ed25519", + "Secp256k1" }; [Trait, WithForeign] @@ -158,17 +164,9 @@ interface ResolutionResult { ResolutionResultData get_data(); }; -dictionary DidJwkData { - DidData did; - DocumentData document; -}; - -interface DidJwk { - [Name=from_public_jwk, Throws=Web5Error] - constructor(JwkData public_jwk); - [Name=from_uri, Throws=Web5Error] - constructor([ByRef] string uri); - DidJwkData get_data(); +dictionary DidJwkCreateOptions { + KeyManager? key_manager; + Dsa? dsa; }; dictionary DidWebData { diff --git a/bindings/web5_uniffi_wrapper/src/crypto/in_memory_key_manager.rs b/bindings/web5_uniffi_wrapper/src/crypto/in_memory_key_manager.rs index f90f0f91..af6a394b 100644 --- a/bindings/web5_uniffi_wrapper/src/crypto/in_memory_key_manager.rs +++ b/bindings/web5_uniffi_wrapper/src/crypto/in_memory_key_manager.rs @@ -8,7 +8,7 @@ use web5::crypto::{ jwk::Jwk, key_managers::{ in_memory_key_manager::InMemoryKeyManager as InnerInMemoryKeyManager, - key_manager::KeyManager as InnerKeyManager, + KeyManager as InnerKeyManager, }, }; @@ -30,6 +30,10 @@ impl InMemoryKeyManager { } impl KeyManager for InMemoryKeyManager { + fn import_private_jwk(&self, private_jwk: Jwk) -> Result { + Ok(self.0.import_private_jwk(private_jwk)?) + } + fn get_signer(&self, public_jwk: Jwk) -> Result> { let signer = self.0.get_signer(public_jwk)?; let outer_signer = ToOuterSigner(signer); diff --git a/bindings/web5_uniffi_wrapper/src/crypto/key_manager.rs b/bindings/web5_uniffi_wrapper/src/crypto/key_manager.rs index 6686fb72..7104fede 100644 --- a/bindings/web5_uniffi_wrapper/src/crypto/key_manager.rs +++ b/bindings/web5_uniffi_wrapper/src/crypto/key_manager.rs @@ -1,15 +1,20 @@ use super::dsa::{Signer, ToInnerSigner, ToOuterSigner}; use crate::errors::Result; use std::sync::Arc; -use web5::crypto::{jwk::Jwk, key_managers::key_manager::KeyManager as InnerKeyManager}; +use web5::crypto::{jwk::Jwk, key_managers::KeyManager as InnerKeyManager}; pub trait KeyManager: Send + Sync { + fn import_private_jwk(&self, private_jwk: Jwk) -> Result; fn get_signer(&self, public_jwk: Jwk) -> Result>; } pub struct ToOuterKeyManager(pub Arc); impl KeyManager for ToOuterKeyManager { + fn import_private_jwk(&self, private_jwk: Jwk) -> Result { + Ok(self.0.import_private_jwk(private_jwk)?) + } + fn get_signer(&self, public_jwk: Jwk) -> Result> { let signer = self.0.get_signer(public_jwk)?; let outer_signer = ToOuterSigner(signer); @@ -20,6 +25,10 @@ impl KeyManager for ToOuterKeyManager { pub struct ToInnerKeyManager(pub Arc); impl InnerKeyManager for ToInnerKeyManager { + fn import_private_jwk(&self, private_jwk: Jwk) -> web5::errors::Result { + Ok(self.0.import_private_jwk(private_jwk)?) + } + fn get_signer( &self, public_jwk: Jwk, diff --git a/bindings/web5_uniffi_wrapper/src/dids/methods/did_jwk.rs b/bindings/web5_uniffi_wrapper/src/dids/methods/did_jwk.rs index bfd02e62..b13e084d 100644 --- a/bindings/web5_uniffi_wrapper/src/dids/methods/did_jwk.rs +++ b/bindings/web5_uniffi_wrapper/src/dids/methods/did_jwk.rs @@ -1,6 +1,13 @@ -use crate::{dids::resolution::resolution_result::ResolutionResult, errors::Result}; +use crate::{ + crypto::key_manager::{KeyManager, ToInnerKeyManager}, + dids::{bearer_did::BearerDid, resolution::resolution_result::ResolutionResult}, + errors::Result, +}; use std::sync::Arc; -use web5::{crypto::jwk::Jwk, dids::methods::did_jwk::DidJwk as InnerDidJwk}; +use web5::{ + crypto::dsa::Dsa, + dids::methods::did_jwk::{DidJwk as InnerDidJwk, DidJwkCreateOptions as InnerCreateOptions}, +}; pub struct DidJwk(pub InnerDidJwk); @@ -9,18 +16,21 @@ pub fn did_jwk_resolve(uri: &str) -> Arc { Arc::new(ResolutionResult(resolution_result)) } -impl DidJwk { - pub fn from_public_jwk(public_key: Jwk) -> Result { - let did_jwk = InnerDidJwk::from_public_jwk(public_key)?; - Ok(Self(did_jwk)) - } +#[derive(Default)] +pub struct DidJwkCreateOptions { + pub key_manager: Option>, + pub dsa: Option, +} - pub fn from_uri(uri: &str) -> Result { - let did_jwk = InnerDidJwk::from_uri(uri)?; - Ok(Self(did_jwk)) - } +pub fn did_jwk_create(options: Option) -> Result> { + let inner_options = options.map(|o| InnerCreateOptions { + dsa: o.dsa, + key_manager: match o.key_manager { + None => None, + Some(km) => Some(Arc::new(ToInnerKeyManager(km))), + }, + }); - pub fn get_data(&self) -> InnerDidJwk { - self.0.clone() - } + let inner_bearer_did = InnerDidJwk::create(inner_options)?; + Ok(Arc::new(BearerDid(inner_bearer_did))) } diff --git a/bound/kt/src/main/kotlin/web5/sdk/crypto/keys/InMemoryKeyManager.kt b/bound/kt/src/main/kotlin/web5/sdk/crypto/keys/InMemoryKeyManager.kt index 5f968f72..e2ad83b1 100644 --- a/bound/kt/src/main/kotlin/web5/sdk/crypto/keys/InMemoryKeyManager.kt +++ b/bound/kt/src/main/kotlin/web5/sdk/crypto/keys/InMemoryKeyManager.kt @@ -18,6 +18,17 @@ class InMemoryKeyManager (privateJwks: List) : KeyManager { } } + /** + * Imports a private key which may be stored somewhere such as environment variables. + * + * @param privateJwk The private key represented as a JWK. + * @return Jwk The public key represented as a JWK. + */ + override fun importPrivateJwk(privateJwk: Jwk): Jwk { + val rustCoreJwkData = this.rustCoreInMemoryKeyManager.importPrivateJwk(privateJwk.rustCoreJwkData) + return Jwk.fromRustCoreJwkData(rustCoreJwkData) + } + /** * Returns the Signer for the given public key. * @@ -28,15 +39,4 @@ class InMemoryKeyManager (privateJwks: List) : KeyManager { val rustCoreSigner = this.rustCoreInMemoryKeyManager.getSigner(publicJwk.rustCoreJwkData) return ToOuterSigner(rustCoreSigner) } - - /** - * Imports a private key which may be stored somewhere such as environment variables. - * - * @param privateJwk The private key represented as a JWK. - * @return Jwk The public key represented as a JWK. - */ - fun importPrivateJwk(privateJwk: Jwk): Jwk { - val rustCoreJwkData = this.rustCoreInMemoryKeyManager.importPrivateJwk(privateJwk.rustCoreJwkData) - return Jwk.fromRustCoreJwkData(rustCoreJwkData) - } } \ No newline at end of file diff --git a/bound/kt/src/main/kotlin/web5/sdk/crypto/keys/KeyManager.kt b/bound/kt/src/main/kotlin/web5/sdk/crypto/keys/KeyManager.kt index 2420587b..6e5f0b50 100644 --- a/bound/kt/src/main/kotlin/web5/sdk/crypto/keys/KeyManager.kt +++ b/bound/kt/src/main/kotlin/web5/sdk/crypto/keys/KeyManager.kt @@ -3,15 +3,22 @@ package web5.sdk.crypto.keys import web5.sdk.crypto.signers.ToOuterSigner import web5.sdk.crypto.signers.Signer import web5.sdk.crypto.signers.ToInnerSigner +import web5.sdk.rust.JwkData import web5.sdk.rust.JwkData as RustCoreJwkData import web5.sdk.rust.KeyManager as RustCoreKeyManager import web5.sdk.rust.Signer as RustCoreSigner interface KeyManager { + fun importPrivateJwk(privateJwk: Jwk): Jwk fun getSigner(publicJwk: Jwk): Signer } internal class ToOuterKeyManager(private val rustCoreKeyManager: RustCoreKeyManager) : KeyManager { + override fun importPrivateJwk(privateJwk: Jwk): Jwk { + val rustCoreJwkData = rustCoreKeyManager.importPrivateJwk(privateJwk.rustCoreJwkData) + return Jwk.fromRustCoreJwkData(rustCoreJwkData) + } + override fun getSigner(publicJwk: Jwk): Signer { val rustCoreSigner = rustCoreKeyManager.getSigner(publicJwk.rustCoreJwkData) return ToOuterSigner(rustCoreSigner) @@ -19,6 +26,12 @@ internal class ToOuterKeyManager(private val rustCoreKeyManager: RustCoreKeyMana } internal class ToInnerKeyManager(private val keyManager: KeyManager) : RustCoreKeyManager { + override fun importPrivateJwk(privateJwk: JwkData): JwkData { + val rustCoreJwkData = Jwk.fromRustCoreJwkData(privateJwk) + val jwk = keyManager.importPrivateJwk(rustCoreJwkData) + return jwk.rustCoreJwkData + } + override fun getSigner(publicJwk: RustCoreJwkData): RustCoreSigner { val jwk = Jwk.fromRustCoreJwkData(publicJwk) val signer = keyManager.getSigner(jwk) diff --git a/bound/kt/src/main/kotlin/web5/sdk/dids/methods/jwk/DidJwk.kt b/bound/kt/src/main/kotlin/web5/sdk/dids/methods/jwk/DidJwk.kt index ec4c16d0..66702b00 100644 --- a/bound/kt/src/main/kotlin/web5/sdk/dids/methods/jwk/DidJwk.kt +++ b/bound/kt/src/main/kotlin/web5/sdk/dids/methods/jwk/DidJwk.kt @@ -1,47 +1,46 @@ package web5.sdk.dids.methods.jwk import web5.sdk.crypto.keys.Jwk +import web5.sdk.crypto.keys.KeyManager +import web5.sdk.crypto.keys.ToInnerKeyManager +import web5.sdk.crypto.keys.ToOuterKeyManager +import web5.sdk.dids.BearerDid import web5.sdk.dids.Did import web5.sdk.dids.Document import web5.sdk.dids.ResolutionResult +import web5.sdk.rust.Dsa +import web5.sdk.rust.didJwkCreate +import web5.sdk.rust.didJwkCreate as rustCoreJwkCreate +import web5.sdk.rust.DidJwkCreateOptions as RustCoreDidJwkCreateOptions import web5.sdk.rust.didJwkResolve as rustCoreDidJwkResolve -import web5.sdk.rust.DidJwk as RustCoreDidJwk + +data class DidJwkCreateOptions( + val keyManager: KeyManager? = null, + val dsa: Dsa? = null +) /** * A class representing a DID (Decentralized Identifier) using the JWK (JSON Web Key) method. - * - * @property did The DID associated with this instance. - * @property document The DID document associated with this instance. */ class DidJwk { - val did: Did - val document: Document - - /** - * Constructs a DidJwk instance using a public key. - * - * @param publicKey The public key represented as a Jwk. - */ - constructor(publicKey: Jwk) { - val rustCoreDidJwk = RustCoreDidJwk.fromPublicJwk(publicKey.rustCoreJwkData) - - this.did = Did.fromRustCoreDidData(rustCoreDidJwk.getData().did) - this.document = rustCoreDidJwk.getData().document - } - - /** - * Constructs a DidJwk instance using a DID URI. - * - * @param uri The DID URI. - */ - constructor(uri: String) { - val rustCoreDidJwk = RustCoreDidJwk.fromUri(uri) - - this.did = Did.fromRustCoreDidData(rustCoreDidJwk.getData().did) - this.document = rustCoreDidJwk.getData().document - } - companion object { + /** + * Create a DidJwk BearerDid using available options. + * + * @param options The set of options to configure creation. + */ + fun create(options: DidJwkCreateOptions? = null): BearerDid { + val rustCoreOptions = options?.let { opts -> + RustCoreDidJwkCreateOptions( + keyManager = opts.keyManager?.let { ToInnerKeyManager(it) }, + dsa = opts.dsa + ) } + val rustCoreBearerDid = didJwkCreate(rustCoreOptions) + val rustCoreBearerDidData = rustCoreBearerDid.getData() + val keyManager = ToOuterKeyManager(rustCoreBearerDidData.keyManager) + return BearerDid(rustCoreBearerDidData.did.uri, keyManager) + } + /** * Resolves a DID URI to a DidResolutionResult. * diff --git a/bound/kt/src/main/kotlin/web5/sdk/rust/UniFFI.kt b/bound/kt/src/main/kotlin/web5/sdk/rust/UniFFI.kt index b61a19ab..09da0bff 100644 --- a/bound/kt/src/main/kotlin/web5/sdk/rust/UniFFI.kt +++ b/bound/kt/src/main/kotlin/web5/sdk/rust/UniFFI.kt @@ -643,24 +643,30 @@ internal interface UniffiForeignFutureCompleteVoid : com.sun.jna.Callback { internal interface UniffiCallbackInterfaceKeyManagerMethod0 : com.sun.jna.Callback { fun callback(`uniffiHandle`: Long,`publicJwk`: RustBuffer.ByValue,`uniffiOutReturn`: PointerByReference,uniffiCallStatus: UniffiRustCallStatus,) } +internal interface UniffiCallbackInterfaceKeyManagerMethod1 : com.sun.jna.Callback { + fun callback(`uniffiHandle`: Long,`privateJwk`: RustBuffer.ByValue,`uniffiOutReturn`: RustBuffer,uniffiCallStatus: UniffiRustCallStatus,) +} internal interface UniffiCallbackInterfaceSignerMethod0 : com.sun.jna.Callback { fun callback(`uniffiHandle`: Long,`payload`: RustBuffer.ByValue,`uniffiOutReturn`: RustBuffer,uniffiCallStatus: UniffiRustCallStatus,) } internal interface UniffiCallbackInterfaceVerifierMethod0 : com.sun.jna.Callback { fun callback(`uniffiHandle`: Long,`message`: RustBuffer.ByValue,`signature`: RustBuffer.ByValue,`uniffiOutReturn`: Pointer,uniffiCallStatus: UniffiRustCallStatus,) } -@Structure.FieldOrder("getSigner", "uniffiFree") +@Structure.FieldOrder("getSigner", "importPrivateJwk", "uniffiFree") internal open class UniffiVTableCallbackInterfaceKeyManager( @JvmField internal var `getSigner`: UniffiCallbackInterfaceKeyManagerMethod0? = null, + @JvmField internal var `importPrivateJwk`: UniffiCallbackInterfaceKeyManagerMethod1? = null, @JvmField internal var `uniffiFree`: UniffiCallbackInterfaceFree? = null, ) : Structure() { class UniffiByValue( `getSigner`: UniffiCallbackInterfaceKeyManagerMethod0? = null, + `importPrivateJwk`: UniffiCallbackInterfaceKeyManagerMethod1? = null, `uniffiFree`: UniffiCallbackInterfaceFree? = null, - ): UniffiVTableCallbackInterfaceKeyManager(`getSigner`,`uniffiFree`,), Structure.ByValue + ): UniffiVTableCallbackInterfaceKeyManager(`getSigner`,`importPrivateJwk`,`uniffiFree`,), Structure.ByValue internal fun uniffiSetValue(other: UniffiVTableCallbackInterfaceKeyManager) { `getSigner` = other.`getSigner` + `importPrivateJwk` = other.`importPrivateJwk` `uniffiFree` = other.`uniffiFree` } @@ -877,10 +883,6 @@ internal open class UniffiVTableCallbackInterfaceVerifier( - - - - @@ -942,16 +944,6 @@ internal interface UniffiLib : Library { ): RustBuffer.ByValue fun uniffi_web5_uniffi_fn_method_diddht_publish(`ptr`: Pointer,`signer`: Pointer,uniffi_out_err: UniffiRustCallStatus, ): Unit - fun uniffi_web5_uniffi_fn_clone_didjwk(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Pointer - fun uniffi_web5_uniffi_fn_free_didjwk(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_web5_uniffi_fn_constructor_didjwk_from_public_jwk(`publicJwk`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, - ): Pointer - fun uniffi_web5_uniffi_fn_constructor_didjwk_from_uri(`uri`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, - ): Pointer - fun uniffi_web5_uniffi_fn_method_didjwk_get_data(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue fun uniffi_web5_uniffi_fn_clone_didweb(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, ): Pointer fun uniffi_web5_uniffi_fn_free_didweb(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, @@ -996,7 +988,7 @@ internal interface UniffiLib : Library { ): Pointer fun uniffi_web5_uniffi_fn_method_inmemorykeymanager_get_signer(`ptr`: Pointer,`publicJwk`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): Pointer - fun uniffi_web5_uniffi_fn_method_inmemorykeymanager_import_private_jwk(`ptr`: Pointer,`privateKey`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + fun uniffi_web5_uniffi_fn_method_inmemorykeymanager_import_private_jwk(`ptr`: Pointer,`privateJwk`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue fun uniffi_web5_uniffi_fn_clone_jwk(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, ): Pointer @@ -1016,6 +1008,8 @@ internal interface UniffiLib : Library { ): Unit fun uniffi_web5_uniffi_fn_method_keymanager_get_signer(`ptr`: Pointer,`publicJwk`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): Pointer + fun uniffi_web5_uniffi_fn_method_keymanager_import_private_jwk(`ptr`: Pointer,`privateJwk`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue fun uniffi_web5_uniffi_fn_clone_portabledid(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, ): Pointer fun uniffi_web5_uniffi_fn_free_portabledid(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, @@ -1070,6 +1064,8 @@ internal interface UniffiLib : Library { ): Unit fun uniffi_web5_uniffi_fn_func_did_dht_resolve(`uri`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): Pointer + fun uniffi_web5_uniffi_fn_func_did_jwk_create(`options`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): Pointer fun uniffi_web5_uniffi_fn_func_did_jwk_resolve(`uri`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): Pointer fun uniffi_web5_uniffi_fn_func_did_web_resolve(`uri`: RustBuffer.ByValue, @@ -1190,6 +1186,8 @@ internal interface UniffiLib : Library { ): Unit fun uniffi_web5_uniffi_checksum_func_did_dht_resolve( ): Short + fun uniffi_web5_uniffi_checksum_func_did_jwk_create( + ): Short fun uniffi_web5_uniffi_checksum_func_did_jwk_resolve( ): Short fun uniffi_web5_uniffi_checksum_func_did_web_resolve( @@ -1208,8 +1206,6 @@ internal interface UniffiLib : Library { ): Short fun uniffi_web5_uniffi_checksum_method_diddht_publish( ): Short - fun uniffi_web5_uniffi_checksum_method_didjwk_get_data( - ): Short fun uniffi_web5_uniffi_checksum_method_didweb_get_data( ): Short fun uniffi_web5_uniffi_checksum_method_document_get_data( @@ -1230,6 +1226,8 @@ internal interface UniffiLib : Library { ): Short fun uniffi_web5_uniffi_checksum_method_keymanager_get_signer( ): Short + fun uniffi_web5_uniffi_checksum_method_keymanager_import_private_jwk( + ): Short fun uniffi_web5_uniffi_checksum_method_portabledid_get_data( ): Short fun uniffi_web5_uniffi_checksum_method_portabledid_to_json_string( @@ -1256,10 +1254,6 @@ internal interface UniffiLib : Library { ): Short fun uniffi_web5_uniffi_checksum_constructor_diddht_from_uri( ): Short - fun uniffi_web5_uniffi_checksum_constructor_didjwk_from_public_jwk( - ): Short - fun uniffi_web5_uniffi_checksum_constructor_didjwk_from_uri( - ): Short fun uniffi_web5_uniffi_checksum_constructor_didweb_from_public_jwk( ): Short fun uniffi_web5_uniffi_checksum_constructor_didweb_from_uri( @@ -1302,6 +1296,9 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_web5_uniffi_checksum_func_did_dht_resolve() != 54117.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_web5_uniffi_checksum_func_did_jwk_create() != 64914.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_web5_uniffi_checksum_func_did_jwk_resolve() != 47278.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -1329,9 +1326,6 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_web5_uniffi_checksum_method_diddht_publish() != 3488.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_didjwk_get_data() != 58319.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } if (lib.uniffi_web5_uniffi_checksum_method_didweb_get_data() != 40916.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -1350,7 +1344,7 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_web5_uniffi_checksum_method_inmemorykeymanager_get_signer() != 64632.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_inmemorykeymanager_import_private_jwk() != 54213.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_method_inmemorykeymanager_import_private_jwk() != 1224.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_web5_uniffi_checksum_method_jwk_compute_thumbprint() != 15254.toShort()) { @@ -1362,6 +1356,9 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_web5_uniffi_checksum_method_keymanager_get_signer() != 27148.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_web5_uniffi_checksum_method_keymanager_import_private_jwk() != 6800.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_web5_uniffi_checksum_method_portabledid_get_data() != 27045.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -1401,12 +1398,6 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_web5_uniffi_checksum_constructor_diddht_from_uri() != 63936.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_constructor_didjwk_from_public_jwk() != 39843.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_web5_uniffi_checksum_constructor_didjwk_from_uri() != 21472.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } if (lib.uniffi_web5_uniffi_checksum_constructor_didweb_from_public_jwk() != 22173.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -2596,261 +2587,6 @@ public object FfiConverterTypeDidDht: FfiConverter { // -public interface DidJwkInterface { - - fun `getData`(): DidJwkData - - companion object -} - -open class DidJwk: Disposable, AutoCloseable, DidJwkInterface { - - constructor(pointer: Pointer) { - this.pointer = pointer - this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) - } - - /** - * This constructor can be used to instantiate a fake object. Only used for tests. Any - * attempt to actually use an object constructed this way will fail as there is no - * connected Rust object. - */ - @Suppress("UNUSED_PARAMETER") - constructor(noPointer: NoPointer) { - this.pointer = null - this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) - } - - protected val pointer: Pointer? - protected val cleanable: UniffiCleaner.Cleanable - - private val wasDestroyed = AtomicBoolean(false) - private val callCounter = AtomicLong(1) - - override fun destroy() { - // Only allow a single call to this method. - // TODO: maybe we should log a warning if called more than once? - if (this.wasDestroyed.compareAndSet(false, true)) { - // This decrement always matches the initial count of 1 given at creation time. - if (this.callCounter.decrementAndGet() == 0L) { - cleanable.clean() - } - } - } - - @Synchronized - override fun close() { - this.destroy() - } - - internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { - // Check and increment the call counter, to keep the object alive. - // This needs a compare-and-set retry loop in case of concurrent updates. - do { - val c = this.callCounter.get() - if (c == 0L) { - throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") - } - if (c == Long.MAX_VALUE) { - throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") - } - } while (! this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. - try { - return block(this.uniffiClonePointer()) - } finally { - // This decrement always matches the increment we performed above. - if (this.callCounter.decrementAndGet() == 0L) { - cleanable.clean() - } - } - } - - // Use a static inner class instead of a closure so as not to accidentally - // capture `this` as part of the cleanable's action. - private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { - override fun run() { - pointer?.let { ptr -> - uniffiRustCall { status -> - UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_free_didjwk(ptr, status) - } - } - } - } - - fun uniffiClonePointer(): Pointer { - return uniffiRustCall() { status -> - UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_clone_didjwk(pointer!!, status) - } - } - - override fun `getData`(): DidJwkData { - return FfiConverterTypeDidJwkData.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_didjwk_get_data( - it, _status) -} - } - ) - } - - - - - - companion object { - - @Throws(Web5Exception::class) fun `fromPublicJwk`(`publicJwk`: JwkData): DidJwk { - return FfiConverterTypeDidJwk.lift( - uniffiRustCallWithError(Web5Exception) { _status -> - UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_constructor_didjwk_from_public_jwk( - FfiConverterTypeJwkData.lower(`publicJwk`),_status) -} - ) - } - - - - @Throws(Web5Exception::class) fun `fromUri`(`uri`: kotlin.String): DidJwk { - return FfiConverterTypeDidJwk.lift( - uniffiRustCallWithError(Web5Exception) { _status -> - UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_constructor_didjwk_from_uri( - FfiConverterString.lower(`uri`),_status) -} - ) - } - - - - } - -} - -public object FfiConverterTypeDidJwk: FfiConverter { - - override fun lower(value: DidJwk): Pointer { - return value.uniffiClonePointer() - } - - override fun lift(value: Pointer): DidJwk { - return DidJwk(value) - } - - override fun read(buf: ByteBuffer): DidJwk { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return lift(Pointer(buf.getLong())) - } - - override fun allocationSize(value: DidJwk) = 8UL - - override fun write(value: DidJwk, buf: ByteBuffer) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(lower(value))) - } -} - - -// This template implements a class for working with a Rust struct via a Pointer/Arc -// to the live Rust struct on the other side of the FFI. -// -// Each instance implements core operations for working with the Rust `Arc` and the -// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. -// -// There's some subtlety here, because we have to be careful not to operate on a Rust -// struct after it has been dropped, and because we must expose a public API for freeing -// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: -// -// * Each instance holds an opaque pointer to the underlying Rust struct. -// Method calls need to read this pointer from the object's state and pass it in to -// the Rust FFI. -// -// * When an instance is no longer needed, its pointer should be passed to a -// special destructor function provided by the Rust FFI, which will drop the -// underlying Rust struct. -// -// * Given an instance, calling code is expected to call the special -// `destroy` method in order to free it after use, either by calling it explicitly -// or by using a higher-level helper like the `use` method. Failing to do so risks -// leaking the underlying Rust struct. -// -// * We can't assume that calling code will do the right thing, and must be prepared -// to handle Kotlin method calls executing concurrently with or even after a call to -// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. -// -// * We must never allow Rust code to operate on the underlying Rust struct after -// the destructor has been called, and must never call the destructor more than once. -// Doing so may trigger memory unsafety. -// -// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` -// is implemented to call the destructor when the Kotlin object becomes unreachable. -// This is done in a background thread. This is not a panacea, and client code should be aware that -// 1. the thread may starve if some there are objects that have poorly performing -// `drop` methods or do significant work in their `drop` methods. -// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, -// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). -// -// If we try to implement this with mutual exclusion on access to the pointer, there is the -// possibility of a race between a method call and a concurrent call to `destroy`: -// -// * Thread A starts a method call, reads the value of the pointer, but is interrupted -// before it can pass the pointer over the FFI to Rust. -// * Thread B calls `destroy` and frees the underlying Rust struct. -// * Thread A resumes, passing the already-read pointer value to Rust and triggering -// a use-after-free. -// -// One possible solution would be to use a `ReadWriteLock`, with each method call taking -// a read lock (and thus allowed to run concurrently) and the special `destroy` method -// taking a write lock (and thus blocking on live method calls). However, we aim not to -// generate methods with any hidden blocking semantics, and a `destroy` method that might -// block if called incorrectly seems to meet that bar. -// -// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track -// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` -// has been called. These are updated according to the following rules: -// -// * The initial value of the counter is 1, indicating a live object with no in-flight calls. -// The initial value for the flag is false. -// -// * At the start of each method call, we atomically check the counter. -// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. -// If it is nonzero them we atomically increment it by 1 and proceed with the method call. -// -// * At the end of each method call, we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// * When `destroy` is called, we atomically flip the flag from false to true. -// If the flag was already true we silently fail. -// Otherwise we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, -// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. -// -// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been -// called *and* all in-flight method calls have completed, avoiding violating any of the expectations -// of the underlying Rust code. -// -// This makes a cleaner a better alternative to _not_ calling `destroy()` as -// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` -// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner -// thread may be starved, and the app will leak memory. -// -// In this case, `destroy`ing manually may be a better solution. -// -// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects -// with Rust peers are reclaimed: -// -// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: -// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: -// 3. The memory is reclaimed when the process terminates. -// -// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 -// - - public interface DidWebInterface { fun `getData`(): DidWebData @@ -3835,7 +3571,7 @@ public interface InMemoryKeyManagerInterface { fun `getSigner`(`publicJwk`: JwkData): Signer - fun `importPrivateJwk`(`privateKey`: JwkData): JwkData + fun `importPrivateJwk`(`privateJwk`: JwkData): JwkData companion object } @@ -3954,12 +3690,12 @@ open class InMemoryKeyManager: Disposable, AutoCloseable, InMemoryKeyManagerInte - @Throws(Web5Exception::class)override fun `importPrivateJwk`(`privateKey`: JwkData): JwkData { + @Throws(Web5Exception::class)override fun `importPrivateJwk`(`privateJwk`: JwkData): JwkData { return FfiConverterTypeJwkData.lift( callWithPointer { uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_inmemorykeymanager_import_private_jwk( - it, FfiConverterTypeJwkData.lower(`privateKey`),_status) + it, FfiConverterTypeJwkData.lower(`privateJwk`),_status) } } ) @@ -4356,6 +4092,8 @@ public interface KeyManager { fun `getSigner`(`publicJwk`: JwkData): Signer + fun `importPrivateJwk`(`privateJwk`: JwkData): JwkData + companion object } @@ -4454,6 +4192,19 @@ open class KeyManagerImpl: Disposable, AutoCloseable, KeyManager { + @Throws(Web5Exception::class)override fun `importPrivateJwk`(`privateJwk`: JwkData): JwkData { + return FfiConverterTypeJwkData.lift( + callWithPointer { + uniffiRustCallWithError(Web5Exception) { _status -> + UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_keymanager_import_private_jwk( + it, FfiConverterTypeJwkData.lower(`privateJwk`),_status) +} + } + ) + } + + + @@ -4509,6 +4260,23 @@ internal object uniffiCallbackInterfaceKeyManager { ) } } + internal object `importPrivateJwk`: UniffiCallbackInterfaceKeyManagerMethod1 { + override fun callback(`uniffiHandle`: Long,`privateJwk`: RustBuffer.ByValue,`uniffiOutReturn`: RustBuffer,uniffiCallStatus: UniffiRustCallStatus,) { + val uniffiObj = FfiConverterTypeKeyManager.handleMap.get(uniffiHandle) + val makeCall = { -> + uniffiObj.`importPrivateJwk`( + FfiConverterTypeJwkData.lift(`privateJwk`), + ) + } + val writeReturn = { value: JwkData -> uniffiOutReturn.setValue(FfiConverterTypeJwkData.lower(value)) } + uniffiTraitInterfaceCallWithError( + uniffiCallStatus, + makeCall, + writeReturn, + { e: Web5Exception -> FfiConverterTypeWeb5Error.lower(e) } + ) + } + } internal object uniffiFree: UniffiCallbackInterfaceFree { override fun callback(handle: Long) { @@ -4518,6 +4286,7 @@ internal object uniffiCallbackInterfaceKeyManager { internal var vtable = UniffiVTableCallbackInterfaceKeyManager.UniffiByValue( `getSigner`, + `importPrivateJwk`, uniffiFree, ) @@ -6223,30 +5992,38 @@ public object FfiConverterTypeDidDhtData: FfiConverterRustBuffer { -data class DidJwkData ( - var `did`: DidData, - var `document`: DocumentData -) { +data class DidJwkCreateOptions ( + var `keyManager`: KeyManager?, + var `dsa`: Dsa? +) : Disposable { + + @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here + override fun destroy() { + + Disposable.destroy( + this.`keyManager`, + this.`dsa`) + } companion object } -public object FfiConverterTypeDidJwkData: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): DidJwkData { - return DidJwkData( - FfiConverterTypeDidData.read(buf), - FfiConverterTypeDocumentData.read(buf), +public object FfiConverterTypeDidJwkCreateOptions: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): DidJwkCreateOptions { + return DidJwkCreateOptions( + FfiConverterOptionalTypeKeyManager.read(buf), + FfiConverterOptionalTypeDsa.read(buf), ) } - override fun allocationSize(value: DidJwkData) = ( - FfiConverterTypeDidData.allocationSize(value.`did`) + - FfiConverterTypeDocumentData.allocationSize(value.`document`) + override fun allocationSize(value: DidJwkCreateOptions) = ( + FfiConverterOptionalTypeKeyManager.allocationSize(value.`keyManager`) + + FfiConverterOptionalTypeDsa.allocationSize(value.`dsa`) ) - override fun write(value: DidJwkData, buf: ByteBuffer) { - FfiConverterTypeDidData.write(value.`did`, buf) - FfiConverterTypeDocumentData.write(value.`document`, buf) + override fun write(value: DidJwkCreateOptions, buf: ByteBuffer) { + FfiConverterOptionalTypeKeyManager.write(value.`keyManager`, buf) + FfiConverterOptionalTypeDsa.write(value.`dsa`, buf) } } @@ -6698,7 +6475,8 @@ public object FfiConverterTypeVerificationMethodData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): KeyManager? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypeKeyManager.read(buf) + } + + override fun allocationSize(value: KeyManager?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypeKeyManager.allocationSize(value) + } + } + + override fun write(value: KeyManager?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypeKeyManager.write(value, buf) + } + } +} + + + + +public object FfiConverterOptionalTypeDidJwkCreateOptions: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): DidJwkCreateOptions? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypeDidJwkCreateOptions.read(buf) + } + + override fun allocationSize(value: DidJwkCreateOptions?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypeDidJwkCreateOptions.allocationSize(value) + } + } + + override fun write(value: DidJwkCreateOptions?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypeDidJwkCreateOptions.write(value, buf) + } + } +} + + + + public object FfiConverterOptionalTypeDocumentData: FfiConverterRustBuffer { override fun read(buf: ByteBuffer): DocumentData? { if (buf.get().toInt() == 0) { @@ -6995,6 +6831,35 @@ public object FfiConverterOptionalTypeVerifiableCredentialCreateOptionsData: Ffi +public object FfiConverterOptionalTypeDsa: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Dsa? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypeDsa.read(buf) + } + + override fun allocationSize(value: Dsa?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypeDsa.allocationSize(value) + } + } + + override fun write(value: Dsa?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypeDsa.write(value, buf) + } + } +} + + + + public object FfiConverterOptionalTypeResolutionMetadataError: FfiConverterRustBuffer { override fun read(buf: ByteBuffer): ResolutionMetadataError? { if (buf.get().toInt() == 0) { @@ -7259,6 +7124,16 @@ public object FfiConverterMapStringString: FfiConverterRustBuffer + UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_func_did_jwk_create( + FfiConverterOptionalTypeDidJwkCreateOptions.lower(`options`),_status) +} + ) + } + fun `didJwkResolve`(`uri`: kotlin.String): ResolutionResult { return FfiConverterTypeResolutionResult.lift( uniffiRustCall() { _status -> diff --git a/bound/kt/src/test/kotlin/web5/sdk/dids/BearerDidTest.kt b/bound/kt/src/test/kotlin/web5/sdk/dids/BearerDidTest.kt index 423b778f..7be027eb 100644 --- a/bound/kt/src/test/kotlin/web5/sdk/dids/BearerDidTest.kt +++ b/bound/kt/src/test/kotlin/web5/sdk/dids/BearerDidTest.kt @@ -1,41 +1,5 @@ package web5.sdk.dids -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.Test -import web5.sdk.crypto.keys.InMemoryKeyManager -import web5.sdk.crypto.keys.Jwk -import web5.sdk.dids.methods.jwk.DidJwk -import web5.sdk.rust.ed25519GeneratorGenerate - class BearerDidTest { - - @Test - fun `test basic bearer did creation`() { - val privateJwk = ed25519GeneratorGenerate() - - val keyManager = InMemoryKeyManager(listOf()) - val publicJwk = keyManager.importPrivateJwk(Jwk.fromRustCoreJwkData(privateJwk)) - - val didJwk = DidJwk(publicJwk) - - val bearerDid = BearerDid(didJwk.did.uri, keyManager) - - assertEquals(bearerDid.document.id, didJwk.document.id) - } - - @Test - fun `test bearer did sign`() { - val privateJwk = ed25519GeneratorGenerate() - - val keyManager = InMemoryKeyManager(listOf()) - val publicJwk = keyManager.importPrivateJwk(Jwk.fromRustCoreJwkData(privateJwk)) - - val didJwk = DidJwk(publicJwk) - - val bearerDid = BearerDid(didJwk.did.uri, keyManager) - - val signedPayload = bearerDid.getSigner().sign("hi".toByteArray()) - assertNotNull(signedPayload) - } + // TODO } \ No newline at end of file diff --git a/bound/kt/src/test/kotlin/web5/sdk/dids/methods/jwk/DidJwkTests.kt b/bound/kt/src/test/kotlin/web5/sdk/dids/methods/jwk/DidJwkTests.kt index 86191aeb..3af3d095 100644 --- a/bound/kt/src/test/kotlin/web5/sdk/dids/methods/jwk/DidJwkTests.kt +++ b/bound/kt/src/test/kotlin/web5/sdk/dids/methods/jwk/DidJwkTests.kt @@ -1,36 +1,97 @@ package web5.sdk.dids.methods.jwk +import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test +import org.junit.jupiter.api.fail +import web5.sdk.UnitTestSuite +import web5.sdk.crypto.keys.InMemoryKeyManager import web5.sdk.crypto.keys.Jwk +import web5.sdk.rust.Dsa +import web5.sdk.rust.ResolutionMetadataError -import web5.sdk.rust.DidJwk as RustCoreDidJwk +class DidJwkTests { + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class Create { + private val testSuite = UnitTestSuite("did_jwk_create") -import web5.sdk.rust.ed25519GeneratorGenerate as rustCoreEd25519GeneratorGenerate + @AfterAll + fun verifyAllTestsIncluded() { + if (testSuite.tests.isNotEmpty()) { + println("The following tests were not included or executed:") + testSuite.tests.forEach { println(it) } + fail("Not all tests were executed! ${this.testSuite.tests}") + } + } -class DidJwkTests { - @Test - fun `can create did jwk same as rust core`() { - val jwk = rustCoreEd25519GeneratorGenerate() + @Test + fun test_can_specify_key_manager() { + this.testSuite.include() - val didJwk = DidJwk(Jwk.fromRustCoreJwkData(jwk)) + val keyManager = InMemoryKeyManager(listOf()) + val bearerDid = DidJwk.create(DidJwkCreateOptions(keyManager)) - val rustCoreDidJwk = RustCoreDidJwk.fromPublicJwk(jwk); - assertEquals(rustCoreDidJwk.getData().did.uri, didJwk.did.uri) - assertEquals(rustCoreDidJwk.getData().document.id, didJwk.document.id) - } + // TODO publicKeyJwk on the document should be of type Jwk + val publicJwk = bearerDid.document.verificationMethod.first().publicKeyJwk + assertDoesNotThrow { + keyManager.getSigner(Jwk.fromRustCoreJwkData(publicJwk)) + } + } + + @Test + fun test_can_specify_secp256k1() { + this.testSuite.include() + + val bearerDid = DidJwk.create(DidJwkCreateOptions(dsa = Dsa.SECP256K1)) + + val publicJwk = bearerDid.document.verificationMethod.first().publicKeyJwk + assertEquals("ES256K", publicJwk.alg) + assertEquals("EC", publicJwk.kty) + assertEquals("secp256k1", publicJwk.crv) + } - @Test - fun `can resolve did jwk uri`() { - val didUri = "did:jwk:eyJhbGciOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwiY3J2IjoiRWQyNTUxOSIsImQiOm51bGwsIngiOiJPQ1RWd1pReWFkUWpnVnR4bHZ3aTZTNGFTeEF0OVg2dHl3NU5OZkRoeEtrIiwieSI6bnVsbH0" - val resolvedDid = DidJwk.resolve(didUri) + @Test + fun test_defaults_to_ed25519() { + this.testSuite.include() - assertEquals(resolvedDid.document!!.id, didUri) + val bearerDid = DidJwk.create() + + val publicJwk = bearerDid.document.verificationMethod.first().publicKeyJwk + assertEquals("Ed25519", publicJwk.alg) + assertEquals("OKP", publicJwk.kty) + assertEquals("Ed25519", publicJwk.crv) + } } - @Test - fun `throws exception if did method is not jwk`() { - val resolutionResult = DidJwk.resolve("did:example:123") - assertEquals(resolutionResult.resolutionMetadata.error!!.name, "INVALID_DID"); + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class Resolve { + private val testSuite = UnitTestSuite("did_jwk_resolve") + + @AfterAll + fun verifyAllTestsIncluded() { + if (testSuite.tests.isNotEmpty()) { + println("The following tests were not included or executed:") + testSuite.tests.forEach { println(it) } + fail("Not all tests were executed! ${this.testSuite.tests}") + } + } + + @Test + fun test_invalid_did() { + this.testSuite.include() + + val resolutionResult = DidJwk.resolve("something invalid") + assertEquals(ResolutionMetadataError.INVALID_DID, resolutionResult.resolutionMetadata.error) + } + + @Test + fun test_create_then_resolve() { + this.testSuite.include() + + val bearerDid = DidJwk.create() + val resolutionResult = DidJwk.resolve(bearerDid.did.uri) + assertEquals(bearerDid.document, resolutionResult.document) + } } } \ No newline at end of file diff --git a/crates/web5/src/crypto/dsa/mod.rs b/crates/web5/src/crypto/dsa/mod.rs index 0371af05..6e593ca4 100644 --- a/crates/web5/src/crypto/dsa/mod.rs +++ b/crates/web5/src/crypto/dsa/mod.rs @@ -5,7 +5,6 @@ pub mod secp256k1; pub enum Dsa { Ed25519, - #[cfg(test)] Secp256k1, } @@ -15,7 +14,6 @@ impl std::str::FromStr for Dsa { fn from_str(input: &str) -> std::result::Result { match input.to_ascii_lowercase().as_str() { "ed25519" => Ok(Dsa::Ed25519), - #[cfg(test)] "secp256k1" => Ok(Dsa::Secp256k1), _ => Err(Web5Error::Parameter(format!("unsupported dsa {}", input))), } diff --git a/crates/web5/src/crypto/key_managers/in_memory_key_manager.rs b/crates/web5/src/crypto/key_managers/in_memory_key_manager.rs index 998a9576..35939269 100644 --- a/crates/web5/src/crypto/key_managers/in_memory_key_manager.rs +++ b/crates/web5/src/crypto/key_managers/in_memory_key_manager.rs @@ -1,4 +1,4 @@ -use super::key_manager::KeyManager; +use super::KeyManager; use crate::{ crypto::{ dsa::{ed25519::Ed25519Signer, Signer}, @@ -31,8 +31,10 @@ impl InMemoryKeyManager { map: RwLock::new(HashMap::new()), } } +} - pub fn import_private_jwk(&self, private_jwk: Jwk) -> Result { +impl KeyManager for InMemoryKeyManager { + fn import_private_jwk(&self, private_jwk: Jwk) -> Result { if private_jwk.is_public_key() { return Err(Web5Error::Parameter( "private_jwk must be a private key".to_string(), @@ -46,9 +48,7 @@ impl InMemoryKeyManager { map_lock.insert(public_jwk.compute_thumbprint()?, private_jwk); Ok(public_jwk) } -} -impl KeyManager for InMemoryKeyManager { fn get_signer(&self, public_jwk: Jwk) -> Result> { if !public_jwk.is_public_key() { return Err(Web5Error::Parameter( diff --git a/crates/web5/src/crypto/key_managers/key_manager.rs b/crates/web5/src/crypto/key_managers/key_manager.rs deleted file mode 100644 index d3c9c427..00000000 --- a/crates/web5/src/crypto/key_managers/key_manager.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::{ - crypto::{dsa::Signer, jwk::Jwk}, - errors::Result, -}; -use std::sync::Arc; - -pub trait KeyManager: Send + Sync { - fn get_signer(&self, public_jwk: Jwk) -> Result>; -} diff --git a/crates/web5/src/crypto/key_managers/mod.rs b/crates/web5/src/crypto/key_managers/mod.rs index f49b5a32..db5d2f0b 100644 --- a/crates/web5/src/crypto/key_managers/mod.rs +++ b/crates/web5/src/crypto/key_managers/mod.rs @@ -1,2 +1,12 @@ +use crate::{ + crypto::{dsa::Signer, jwk::Jwk}, + errors::Result, +}; +use std::sync::Arc; + pub mod in_memory_key_manager; -pub mod key_manager; + +pub trait KeyManager: Send + Sync { + fn import_private_jwk(&self, private_jwk: Jwk) -> Result; + fn get_signer(&self, public_jwk: Jwk) -> Result>; +} diff --git a/crates/web5/src/dids/bearer_did.rs b/crates/web5/src/dids/bearer_did.rs index 00a12c09..a9fabf82 100644 --- a/crates/web5/src/dids/bearer_did.rs +++ b/crates/web5/src/dids/bearer_did.rs @@ -9,7 +9,7 @@ use super::{ use crate::{ crypto::{ dsa::Signer, - key_managers::{in_memory_key_manager::InMemoryKeyManager, key_manager::KeyManager}, + key_managers::{in_memory_key_manager::InMemoryKeyManager, KeyManager}, }, errors::Web5Error, }; diff --git a/crates/web5/src/dids/methods/did_jwk.rs b/crates/web5/src/dids/methods/did_jwk.rs index 32106cca..25789185 100644 --- a/crates/web5/src/dids/methods/did_jwk.rs +++ b/crates/web5/src/dids/methods/did_jwk.rs @@ -1,44 +1,63 @@ -use super::{MethodError, Result}; use crate::{ - crypto::jwk::Jwk, + crypto::{ + dsa::{ed25519::Ed25519Generator, secp256k1::Secp256k1Generator, Dsa}, + jwk::Jwk, + key_managers::{in_memory_key_manager::InMemoryKeyManager, KeyManager}, + }, dids::{ + bearer_did::BearerDid, data_model::{document::Document, verification_method::VerificationMethod}, did::Did, resolution::{ - resolution_metadata::{ResolutionMetadata, ResolutionMetadataError}, - resolution_result::ResolutionResult, + resolution_metadata::ResolutionMetadataError, resolution_result::ResolutionResult, }, }, + errors::Result, }; use base64::{engine::general_purpose, Engine as _}; +use std::sync::Arc; -#[derive(Clone)] -pub struct DidJwk { - pub did: Did, - pub document: Document, +#[derive(Default)] +pub struct DidJwkCreateOptions { + pub key_manager: Option>, + pub dsa: Option, } +pub struct DidJwk; + impl DidJwk { - pub fn from_public_jwk(public_jwk: Jwk) -> Result { + pub fn create(options: Option) -> Result { + let options = options.unwrap_or_default(); + + let key_manager = options + .key_manager + .unwrap_or_else(|| Arc::new(InMemoryKeyManager::new())); + + let private_jwk = match options.dsa.unwrap_or(Dsa::Ed25519) { + Dsa::Ed25519 => Ed25519Generator::generate(), + Dsa::Secp256k1 => Secp256k1Generator::generate(), + }; + let mut public_jwk = key_manager.import_private_jwk(private_jwk)?; + public_jwk.d = None; + let jwk_string = serde_json::to_string(&public_jwk)?; let method_specific_id = general_purpose::URL_SAFE_NO_PAD.encode(jwk_string); - let uri = format!("did:jwk:{}", method_specific_id); - - let did = Did::parse(&uri)?; + let did_uri = format!("did:jwk:{}", method_specific_id); - let verification_method_id = format!("{}#0", uri); + let did = Did::parse(&did_uri)?; - let verification_method = VerificationMethod { - id: verification_method_id.clone(), - r#type: "JsonWebKey".to_string(), - controller: uri.clone(), - public_key_jwk: public_jwk.clone(), - }; + let verification_method_id = format!("{}#0", did_uri); let document = Document { - id: uri.clone(), - verification_method: vec![verification_method.clone()], + context: Some(vec!["https://www.w3.org/ns/did/v1".to_string()]), + id: did_uri.clone(), + verification_method: vec![VerificationMethod { + id: verification_method_id.clone(), + r#type: "JsonWebKey".to_string(), + controller: did_uri.clone(), + public_key_jwk: public_jwk.clone(), + }], authentication: Some(vec![verification_method_id.clone()]), assertion_method: Some(vec![verification_method_id.clone()]), capability_invocation: Some(vec![verification_method_id.clone()]), @@ -46,72 +65,178 @@ impl DidJwk { ..Default::default() }; - Ok(Self { did, document }) + Ok(BearerDid { + did, + document, + key_manager, + }) } - pub fn from_uri(uri: &str) -> Result { - let resolution_result = DidJwk::resolve(uri); - - match resolution_result.document { - None => Err(match resolution_result.resolution_metadata.error { - None => MethodError::ResolutionError(ResolutionMetadataError::InternalError), - Some(e) => MethodError::ResolutionError(e), - }), - Some(document) => { - let did = Did::parse(uri)?; - Ok(Self { did, document }) - } + pub fn resolve(uri: &str) -> ResolutionResult { + let did = match Did::parse(uri) { + Ok(d) => d, + Err(_) => return ResolutionResult::from(ResolutionMetadataError::InvalidDid), + }; + + let decoded_jwk = match general_purpose::URL_SAFE_NO_PAD.decode(did.id) { + Ok(dj) => dj, + Err(_) => return ResolutionResult::from(ResolutionMetadataError::InvalidDid), + }; + + let public_jwk = match serde_json::from_slice::(&decoded_jwk) { + Ok(pj) => pj, + Err(_) => return ResolutionResult::from(ResolutionMetadataError::InvalidDid), + }; + + let kid = format!("{}#0", did.uri); + let document = Document { + context: Some(vec!["https://www.w3.org/ns/did/v1".to_string()]), + id: did.uri.clone(), + verification_method: vec![VerificationMethod { + id: kid.clone(), + r#type: "JsonWebKey".to_string(), + controller: did.uri.clone(), + public_key_jwk: public_jwk, + }], + assertion_method: Some(vec![kid.clone()]), + authentication: Some(vec![kid.clone()]), + capability_invocation: Some(vec![kid.clone()]), + capability_delegation: Some(vec![kid.clone()]), + + // TODO: https://github.com/TBD54566975/web5-rs/issues/257 - If the JWK contains a `use` property with the value "sig" then the `keyAgreement` property + // is not included in the DID Document. If the `use` value is "enc" then only the `keyAgreement` + // property is included in the DID Document. + // key_agreement: if public_jwk.use_.as_deref() != Some("sig") { Some(vec![kid.clone()]) } else { None }, + ..Default::default() + }; + + ResolutionResult { + document: Some(document), + ..Default::default() } } +} - pub fn resolve(uri: &str) -> ResolutionResult { - let result: Result = (|| { - let did = Did::parse(uri).map_err(|_| ResolutionMetadataError::InvalidDid)?; - let decoded_jwk = general_purpose::URL_SAFE_NO_PAD - .decode(did.id) - .map_err(|_| ResolutionMetadataError::InvalidDid)?; - let public_jwk = serde_json::from_slice::(&decoded_jwk) - .map_err(|_| ResolutionMetadataError::InvalidDid)?; - - let kid = format!("{}#0", did.uri); - let document = Document { - context: Some(vec!["https://www.w3.org/ns/did/v1".to_string()]), - id: did.uri.clone(), - verification_method: vec![VerificationMethod { - id: kid.clone(), - r#type: "JsonWebKey".to_string(), - controller: did.uri.clone(), - public_key_jwk: public_jwk, - }], - assertion_method: Some(vec![kid.clone()]), - authentication: Some(vec![kid.clone()]), - capability_invocation: Some(vec![kid.clone()]), - capability_delegation: Some(vec![kid.clone()]), - - // TODO: https://github.com/TBD54566975/web5-rs/issues/257 - If the JWK contains a `use` property with the value "sig" then the `keyAgreement` property - // is not included in the DID Document. If the `use` value is "enc" then only the `keyAgreement` - // property is included in the DID Document. - // key_agreement: if public_jwk.use_.as_deref() != Some("sig") { Some(vec![kid.clone()]) } else { None }, - ..Default::default() - }; +#[cfg(test)] +mod tests { + use super::*; + use crate::{test_helpers::UnitTestSuite, test_name}; + use std::sync::LazyLock; + + mod create { + use super::*; + + static TEST_SUITE: LazyLock = + LazyLock::new(|| UnitTestSuite::new("did_jwk_create")); + + #[test] + fn z_assert_all_suite_cases_covered() { + // fn name prefixed with `z_*` b/c rust test harness executes in alphabetical order, + // unless intentionally executed with "shuffle" https://doc.rust-lang.org/rustc/tests/index.html#--shuffle + // this may not work if shuffled or if test list grows to the extent of 100ms being insufficient wait time + + // wait 100ms to be last-in-queue of mutex lock + std::thread::sleep(std::time::Duration::from_millis(100)); - Ok(ResolutionResult { - document: Some(document), + TEST_SUITE.assert_coverage() + } + + #[test] + fn test_can_specify_key_manager() { + TEST_SUITE.include(test_name!()); + + let key_manager = Arc::new(InMemoryKeyManager::new()); + let result = DidJwk::create(Some(DidJwkCreateOptions { + key_manager: Some(key_manager.clone()), ..Default::default() - }) - })(); - - match result { - Ok(resolution_result) => resolution_result, - Err(err) => ResolutionResult { - resolution_metadata: ResolutionMetadata { - error: Some(match err { - MethodError::ResolutionError(e) => e, - _ => ResolutionMetadataError::InternalError, - }), - }, + })); + + assert!(result.is_ok()); + + let bearer_did = result.unwrap(); + let public_jwk = bearer_did.document.verification_method[0] + .public_key_jwk + .clone(); + let result = key_manager.get_signer(public_jwk); + assert!(result.is_ok()) + } + + #[test] + fn test_can_specify_secp256k1() { + TEST_SUITE.include(test_name!()); + + let result = DidJwk::create(Some(DidJwkCreateOptions { + dsa: Some(Dsa::Secp256k1), ..Default::default() - }, + })); + + assert!(result.is_ok()); + + let bearer_did = result.unwrap(); + let public_jwk = bearer_did.document.verification_method[0] + .public_key_jwk + .clone(); + assert_eq!(public_jwk.alg, Some("ES256K".to_string())); + assert_eq!(public_jwk.kty, "EC".to_string()); + assert_eq!(public_jwk.crv, "secp256k1".to_string()); + } + + #[test] + fn test_defaults_to_ed25519() { + TEST_SUITE.include(test_name!()); + + let result = DidJwk::create(None); + assert!(result.is_ok()); + + let bearer_did = result.unwrap(); + let public_jwk = bearer_did.document.verification_method[0] + .public_key_jwk + .clone(); + assert_eq!(public_jwk.alg, Some("Ed25519".to_string())); + assert_eq!(public_jwk.kty, "OKP".to_string()); + assert_eq!(public_jwk.crv, "Ed25519".to_string()); + } + } + + mod resolve { + use super::*; + + static TEST_SUITE: LazyLock = + LazyLock::new(|| UnitTestSuite::new("did_jwk_resolve")); + + #[test] + fn z_assert_all_suite_cases_covered() { + // fn name prefixed with `z_*` b/c rust test harness executes in alphabetical order, + // unless intentionally executed with "shuffle" https://doc.rust-lang.org/rustc/tests/index.html#--shuffle + // this may not work if shuffled or if test list grows to the extent of 100ms being insufficient wait time + + // wait 100ms to be last-in-queue of mutex lock + std::thread::sleep(std::time::Duration::from_millis(100)); + + TEST_SUITE.assert_coverage() + } + + #[test] + fn test_invalid_did() { + TEST_SUITE.include(test_name!()); + + let resolution_result = DidJwk::resolve("something invalid"); + assert_eq!( + resolution_result.resolution_metadata.error, + Some(ResolutionMetadataError::InvalidDid) + ) + } + + #[test] + fn test_create_then_resolve() { + TEST_SUITE.include(test_name!()); + + let result = DidJwk::create(None); + assert!(result.is_ok()); + let bearer_did = result.unwrap(); + + let resolution_result = DidJwk::resolve(&bearer_did.did.uri); + assert_eq!(resolution_result.document, Some(bearer_did.document)); } } } diff --git a/crates/web5/src/dids/resolution/resolution_result.rs b/crates/web5/src/dids/resolution/resolution_result.rs index bfe8200d..e38ed924 100644 --- a/crates/web5/src/dids/resolution/resolution_result.rs +++ b/crates/web5/src/dids/resolution/resolution_result.rs @@ -42,6 +42,15 @@ impl ResolutionResult { } } +impl From for ResolutionResult { + fn from(error: ResolutionMetadataError) -> Self { + Self { + resolution_metadata: ResolutionMetadata { error: Some(error) }, + ..Default::default() + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/web5_cli/src/dids/create.rs b/crates/web5_cli/src/dids/create.rs index c98cbf19..b5d2b8a4 100644 --- a/crates/web5_cli/src/dids/create.rs +++ b/crates/web5_cli/src/dids/create.rs @@ -1,9 +1,16 @@ use clap::Subcommand; use std::sync::Arc; use web5::{ - crypto::dsa::ed25519::{Ed25519Generator, Ed25519Signer}, + crypto::{ + dsa::ed25519::{Ed25519Generator, Ed25519Signer}, + key_managers::{in_memory_key_manager::InMemoryKeyManager, KeyManager}, + }, dids::{ - methods::{did_dht::DidDht, did_jwk::DidJwk, did_web::DidWeb}, + methods::{ + did_dht::DidDht, + did_jwk::{DidJwk, DidJwkCreateOptions}, + did_web::DidWeb, + }, portable_did::PortableDid, }, }; @@ -54,10 +61,14 @@ impl Commands { json_escape, } => { let private_jwk = Ed25519Generator::generate(); - let mut public_jwk = private_jwk.clone(); - public_jwk.d = None; + let key_manager = InMemoryKeyManager::new(); + key_manager.import_private_jwk(private_jwk.clone()).unwrap(); - let did_jwk = DidJwk::from_public_jwk(public_jwk).unwrap(); + let did_jwk = DidJwk::create(Some(DidJwkCreateOptions { + key_manager: Some(Arc::new(key_manager)), + ..Default::default() + })) + .unwrap(); let portable_did = PortableDid { did_uri: did_jwk.did.uri, diff --git a/docs/API_DESIGN.md b/docs/API_DESIGN.md index 0a2a123d..e66f394f 100644 --- a/docs/API_DESIGN.md +++ b/docs/API_DESIGN.md @@ -53,9 +53,7 @@ - [`DocumentMetadata`](#documentmetadata) - [Methods](#methods) - [`DidJwk`](#didjwk) - - [Example: Create a `did:jwk`](#example-create-a-didjwk) - - [Example: Instantiate an existing `did:jwk`](#example-instantiate-an-existing-didjwk) - - [Example: Resolve a `did:jwk`](#example-resolve-a-didjwk) + - [`DidJwkCreateOptions`](#didjwkcreateoptions) - [`DidWeb`](#didweb) - [Example: Instantiate an existing `did:web`](#example-instantiate-an-existing-didweb) - [Example: Resolve a `did:web`](#example-resolve-a-didweb) @@ -221,6 +219,9 @@ CLASS Jwk INTERFACE KeyManager /// Returns the signer for the given public key. METHOD get_signer(public_jwk: Jwk): Signer + + /// For importing keys which may be stored somewhere such as environment variables. Return Jwk is the public key for the given private key. + METHOD import_private_jwk(private_jwk: Jwk): Jwk ``` ### `InMemoryKeyManager` @@ -247,6 +248,7 @@ CLASS InMemoryKeyManager IMPLEMENTS KeyManager /// The set of Digital Signature Algorithms natively supported within this SDK. ENUM Dsa Ed25519 + Secp256k1 ``` > We must add support for `X25519`, `secp256k1`, and `secp256r1` for [full did:dht conformance](https://did-dht.com/registry/index.html#key-type-index). @@ -606,33 +608,16 @@ CLASS DocumentMetadata ```pseudocode! CLASS DidJwk - PUBLIC DATA did: Did - PUBLIC DATA document: Document - CONSTRUCTOR(public_jwk: Jwk) - CONSTRUCTOR(uri: string) + STATIC METHOD create(options: DidJwkCreateOptions): BearerDid STATIC METHOD resolve(uri: string): ResolutionResult ``` -#### Example: Create a `did:jwk` - -```pseudocode! -key_manager = new InMemoryKeyManager() -public_jwk = key_manager.import_private_jwk(Ed25519Generator::generate()) -did_jwk = new DidJwk(public_jwk) -``` - -#### Example: Instantiate an existing `did:jwk` - -```pseudocode! -uri = "did:jwk:eyJrdHkiOiJFQyIsInVzZSI6InNpZyIsImNydiI6InNlY3AyNTZrMSIsImtpZCI6ImkzU1BSQnRKS292SEZzQmFxTTkydGk2eFFDSkxYM0U3WUNld2lIVjJDU2ciLCJ4IjoidmRyYnoyRU96dmJMRFZfLWtMNGVKdDdWS04VEZaTm1BOVlnV3p2aGg3VSIsInkiOiJWTEZxUU1aUF9Bc3B1Y1hvV1gyLWJHWHBBTzFmUTVMbjE5VjVSQXhyZZVIiwiYWxnIjoiRVMyNTZLIn0" -did_jwk = new DidJwk(uri) -``` - -#### Example: Resolve a `did:jwk` +#### `DidJwkCreateOptions` ```pseudocode! -uri = "did:jwk:eyJrdHkiOiJFQyIsInVzZSI6InNpZyIsImNydiI6InNlY3AyNTZrMSIsImtpZCI6ImkzU1BSQnRKS292SEZzQmFxTTkydGk2eFFDSkxYM0U3WUNld2lIVjJDU2ciLCJ4IjoidmRyYnoyRU96dmJMRFZfLWtMNGVKdDdWS04VEZaTm1BOVlnV3p2aGg3VSIsInkiOiJWTEZxUU1aUF9Bc3B1Y1hvV1gyLWJHWHBBTzFmUTVMbjE5VjVSQXhyZZVIiwiYWxnIjoiRVMyNTZLIn0" -resolution_result = DidJwk.resolve(uri) +CLASS DidJwkCreateOptions + PUBLIC DATA key_manager: KeyManager? + PUBLIC DATA dsa: Dsa? ``` ### `DidWeb` diff --git a/tests/unit_test_cases/did_jwk_create.json b/tests/unit_test_cases/did_jwk_create.json new file mode 100644 index 00000000..f8feae03 --- /dev/null +++ b/tests/unit_test_cases/did_jwk_create.json @@ -0,0 +1,5 @@ +[ + "test_can_specify_key_manager", + "test_can_specify_secp256k1", + "test_defaults_to_ed25519" +] diff --git a/tests/unit_test_cases/did_jwk_resolve.json b/tests/unit_test_cases/did_jwk_resolve.json new file mode 100644 index 00000000..6ba98d69 --- /dev/null +++ b/tests/unit_test_cases/did_jwk_resolve.json @@ -0,0 +1 @@ +["test_invalid_did", "test_create_then_resolve"]