diff --git a/bindings/wasm/docs/api-reference.md b/bindings/wasm/docs/api-reference.md index 73ff47d4c6..2f50e4ed3d 100644 --- a/bindings/wasm/docs/api-reference.md +++ b/bindings/wasm/docs/api-reference.md @@ -11,6 +11,9 @@ if the object is being concurrently modified.

Credential
+
CustomMethodData
+

A custom verification method data format.

+
DIDUrl

A method agnostic DID Url.

@@ -187,30 +190,23 @@ working with storage backed DID documents.

## Members
-
StatusCheck
-

Controls validation behaviour when checking whether or not a credential has been revoked by its -credentialStatus.

+
StatusPurpose
+

Purpose of a StatusList2021.

-
Strict
-

Validate the status if supported, reject any unsupported -credentialStatus types.

-

Only RevocationBitmap2022 is currently supported.

-

This is the default.

+
SubjectHolderRelationship
+

Declares how credential subjects must relate to the presentation holder.

+

See also the Subject-Holder Relationship section of the specification.

-
SkipUnsupported
-

Validate the status if supported, skip any unsupported -credentialStatus types.

+
AlwaysSubject
+

The holder must always match the subject on all credentials, regardless of their nonTransferable property. +This variant is the default.

-
SkipAll
-

Skip all status checks.

+
SubjectOnNonTransferable
+

The holder must match the subject only for credentials where the nonTransferable property is true.

-
StatusPurpose
-

Purpose of a StatusList2021.

+
Any
+

The holder is not required to have any kind of relationship to any credential subject.

-
MethodRelationship
-
-
CredentialStatus
-
StateMetadataEncoding
FailFast
@@ -222,31 +218,32 @@ working with storage backed DID documents.

FirstError

Return after the first error occurs.

-
SubjectHolderRelationship
-

Declares how credential subjects must relate to the presentation holder.

-

See also the Subject-Holder Relationship section of the specification.

+
MethodRelationship
+
+
CredentialStatus
+
+
StatusCheck
+

Controls validation behaviour when checking whether or not a credential has been revoked by its +credentialStatus.

-
AlwaysSubject
-

The holder must always match the subject on all credentials, regardless of their nonTransferable property. -This variant is the default.

+
Strict
+

Validate the status if supported, reject any unsupported +credentialStatus types.

+

Only RevocationBitmap2022 is currently supported.

+

This is the default.

-
SubjectOnNonTransferable
-

The holder must match the subject only for credentials where the nonTransferable property is true.

+
SkipUnsupported
+

Validate the status if supported, skip any unsupported +credentialStatus types.

-
Any
-

The holder is not required to have any kind of relationship to any credential subject.

+
SkipAll
+

Skip all status checks.

## Functions
-
encodeB64(data)string
-

Encode the given bytes in url-safe base64.

-
-
decodeB64(data)Uint8Array
-

Decode the given url-safe base64-encoded slice into its raw bytes.

-
verifyEd25519(alg, signingInput, decodedSignature, publicKey)

Verify a JWS signature secured with the EdDSA algorithm and curve Ed25519.

This function is useful when one is composing a IJwsVerifier that delegates @@ -255,6 +252,12 @@ This variant is the default.

This function does not check whether alg = EdDSA in the protected header. Callers are expected to assert this prior to calling the function.

+
encodeB64(data)string
+

Encode the given bytes in url-safe base64.

+
+
decodeB64(data)Uint8Array
+

Decode the given url-safe base64-encoded slice into its raw bytes.

+
start()

Initializes the console error panic hook for better error messages

@@ -1138,6 +1141,53 @@ Deserializes an instance from a JSON object. | --- | --- | | json | any | + + +## CustomMethodData +A custom verification method data format. + +**Kind**: global class + +* [CustomMethodData](#CustomMethodData) + * [new CustomMethodData(name, data)](#new_CustomMethodData_new) + * _instance_ + * [.clone()](#CustomMethodData+clone) ⇒ [CustomMethodData](#CustomMethodData) + * [.toJSON()](#CustomMethodData+toJSON) ⇒ any + * _static_ + * [.fromJSON(json)](#CustomMethodData.fromJSON) ⇒ [CustomMethodData](#CustomMethodData) + + + +### new CustomMethodData(name, data) + +| Param | Type | +| --- | --- | +| name | string | +| data | any | + + + +### customMethodData.clone() ⇒ [CustomMethodData](#CustomMethodData) +Deep clones the object. + +**Kind**: instance method of [CustomMethodData](#CustomMethodData) + + +### customMethodData.toJSON() ⇒ any +Serializes this to a JSON object. + +**Kind**: instance method of [CustomMethodData](#CustomMethodData) + + +### CustomMethodData.fromJSON(json) ⇒ [CustomMethodData](#CustomMethodData) +Deserializes an instance from a JSON object. + +**Kind**: static method of [CustomMethodData](#CustomMethodData) + +| Param | Type | +| --- | --- | +| json | any | + ## DIDUrl @@ -4343,7 +4393,7 @@ Supported verification method data formats. * [MethodData](#MethodData) * _instance_ - * [.tryBlockchainAccountId()](#MethodData+tryBlockchainAccountId) ⇒ string + * [.tryCustom()](#MethodData+tryCustom) ⇒ [CustomMethodData](#CustomMethodData) * [.tryDecode()](#MethodData+tryDecode) ⇒ Uint8Array * [.tryPublicKeyJwk()](#MethodData+tryPublicKeyJwk) ⇒ [Jwk](#Jwk) * [.toJSON()](#MethodData+toJSON) ⇒ any @@ -4352,13 +4402,13 @@ Supported verification method data formats. * [.newBase58(data)](#MethodData.newBase58) ⇒ [MethodData](#MethodData) * [.newMultibase(data)](#MethodData.newMultibase) ⇒ [MethodData](#MethodData) * [.newJwk(key)](#MethodData.newJwk) ⇒ [MethodData](#MethodData) - * [.newBlockchainAccountId(data)](#MethodData.newBlockchainAccountId) ⇒ [MethodData](#MethodData) + * [.newCustom(name, data)](#MethodData.newCustom) ⇒ [MethodData](#MethodData) * [.fromJSON(json)](#MethodData.fromJSON) ⇒ [MethodData](#MethodData) - + -### methodData.tryBlockchainAccountId() ⇒ string -Returns the wrapped blockchain account id if the format is `BlockchainAccountId`. +### methodData.tryCustom() ⇒ [CustomMethodData](#CustomMethodData) +Returns the wrapped custom method data format is `Custom`. **Kind**: instance method of [MethodData](#MethodData) @@ -4427,16 +4477,17 @@ An error is thrown if the given `key` contains any private components. | --- | --- | | key | [Jwk](#Jwk) | - + -### MethodData.newBlockchainAccountId(data) ⇒ [MethodData](#MethodData) -Creates a new [MethodData](#MethodData) variant in CAIP-10 format. +### MethodData.newCustom(name, data) ⇒ [MethodData](#MethodData) +Creates a new custom [MethodData](#MethodData). **Kind**: static method of [MethodData](#MethodData) | Param | Type | | --- | --- | -| data | string | +| name | string | +| data | any | @@ -4589,7 +4640,7 @@ Supported verification method types. * [.Ed25519VerificationKey2018()](#MethodType.Ed25519VerificationKey2018) ⇒ [MethodType](#MethodType) * [.X25519KeyAgreementKey2019()](#MethodType.X25519KeyAgreementKey2019) ⇒ [MethodType](#MethodType) * [.JsonWebKey()](#MethodType.JsonWebKey) ⇒ [MethodType](#MethodType) - * [.EcdsaSecp256k1RecoverySignature2020()](#MethodType.EcdsaSecp256k1RecoverySignature2020) ⇒ [MethodType](#MethodType) + * [.custom(type_)](#MethodType.custom) ⇒ [MethodType](#MethodType) * [.fromJSON(json)](#MethodType.fromJSON) ⇒ [MethodType](#MethodType) @@ -4625,12 +4676,17 @@ A verification method for use with JWT verification as prescribed by the [Jwk](# in the `publicKeyJwk` entry. **Kind**: static method of [MethodType](#MethodType) - + -### MethodType.EcdsaSecp256k1RecoverySignature2020() ⇒ [MethodType](#MethodType) -The `EcdsaSecp256k1RecoverySignature2020` method type. +### MethodType.custom(type_) ⇒ [MethodType](#MethodType) +A custom method. **Kind**: static method of [MethodType](#MethodType) + +| Param | Type | +| --- | --- | +| type_ | string | + ### MethodType.fromJSON(json) ⇒ [MethodType](#MethodType) @@ -5032,11 +5088,9 @@ Representation of an SD-JWT of the format * [.jwt()](#SdJwt+jwt) ⇒ string * [.disclosures()](#SdJwt+disclosures) ⇒ Array.<string> * [.keyBindingJwt()](#SdJwt+keyBindingJwt) ⇒ string \| undefined - * [.toJSON()](#SdJwt+toJSON) ⇒ any * [.clone()](#SdJwt+clone) ⇒ [SdJwt](#SdJwt) * _static_ * [.parse(sd_jwt)](#SdJwt.parse) ⇒ [SdJwt](#SdJwt) - * [.fromJSON(json)](#SdJwt.fromJSON) ⇒ [SdJwt](#SdJwt) @@ -5079,12 +5133,6 @@ The disclosures part. ### sdJwt.keyBindingJwt() ⇒ string \| undefined The optional key binding JWT. -**Kind**: instance method of [SdJwt](#SdJwt) - - -### sdJwt.toJSON() ⇒ any -Serializes this to a JSON object. - **Kind**: instance method of [SdJwt](#SdJwt) @@ -5106,17 +5154,6 @@ Returns `DeserializationError` if parsing fails. | --- | --- | | sd_jwt | string | - - -### SdJwt.fromJSON(json) ⇒ [SdJwt](#SdJwt) -Deserializes an instance from a JSON object. - -**Kind**: static method of [SdJwt](#SdJwt) - -| Param | Type | -| --- | --- | -| json | any | - ## SdJwtCredentialValidator @@ -6159,50 +6196,38 @@ Deserializes an instance from a JSON object. | --- | --- | | json | any | - + -## StatusCheck -Controls validation behaviour when checking whether or not a credential has been revoked by its -[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status). +## StatusPurpose +Purpose of a [StatusList2021](#StatusList2021). **Kind**: global variable - - -## Strict -Validate the status if supported, reject any unsupported -[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status) types. - -Only `RevocationBitmap2022` is currently supported. - -This is the default. + -**Kind**: global variable - +## SubjectHolderRelationship +Declares how credential subjects must relate to the presentation holder. -## SkipUnsupported -Validate the status if supported, skip any unsupported -[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status) types. +See also the [Subject-Holder Relationship](https://www.w3.org/TR/vc-data-model/#subject-holder-relationships) section of the specification. **Kind**: global variable - + -## SkipAll -Skip all status checks. +## AlwaysSubject +The holder must always match the subject on all credentials, regardless of their [`nonTransferable`](https://www.w3.org/TR/vc-data-model/#nontransferable-property) property. +This variant is the default. **Kind**: global variable - + -## StatusPurpose -Purpose of a [StatusList2021](#StatusList2021). +## SubjectOnNonTransferable +The holder must match the subject only for credentials where the [`nonTransferable`](https://www.w3.org/TR/vc-data-model/#nontransferable-property) property is `true`. **Kind**: global variable - + -## MethodRelationship -**Kind**: global variable - +## Any +The holder is not required to have any kind of relationship to any credential subject. -## CredentialStatus **Kind**: global variable @@ -6226,55 +6251,45 @@ Return all errors that occur during validation. Return after the first error occurs. **Kind**: global variable - - -## SubjectHolderRelationship -Declares how credential subjects must relate to the presentation holder. - -See also the [Subject-Holder Relationship](https://www.w3.org/TR/vc-data-model/#subject-holder-relationships) section of the specification. - -**Kind**: global variable - - -## AlwaysSubject -The holder must always match the subject on all credentials, regardless of their [`nonTransferable`](https://www.w3.org/TR/vc-data-model/#nontransferable-property) property. -This variant is the default. + +## MethodRelationship **Kind**: global variable - - -## SubjectOnNonTransferable -The holder must match the subject only for credentials where the [`nonTransferable`](https://www.w3.org/TR/vc-data-model/#nontransferable-property) property is `true`. + +## CredentialStatus **Kind**: global variable - + -## Any -The holder is not required to have any kind of relationship to any credential subject. +## StatusCheck +Controls validation behaviour when checking whether or not a credential has been revoked by its +[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status). **Kind**: global variable - + -## encodeB64(data) ⇒ string -Encode the given bytes in url-safe base64. +## Strict +Validate the status if supported, reject any unsupported +[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status) types. -**Kind**: global function +Only `RevocationBitmap2022` is currently supported. -| Param | Type | -| --- | --- | -| data | Uint8Array | +This is the default. - +**Kind**: global variable + -## decodeB64(data) ⇒ Uint8Array -Decode the given url-safe base64-encoded slice into its raw bytes. +## SkipUnsupported +Validate the status if supported, skip any unsupported +[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status) types. -**Kind**: global function +**Kind**: global variable + -| Param | Type | -| --- | --- | -| data | Uint8Array | +## SkipAll +Skip all status checks. +**Kind**: global variable ## verifyEd25519(alg, signingInput, decodedSignature, publicKey) @@ -6297,6 +6312,28 @@ prior to calling the function. | decodedSignature | Uint8Array | | publicKey | [Jwk](#Jwk) | + + +## encodeB64(data) ⇒ string +Encode the given bytes in url-safe base64. + +**Kind**: global function + +| Param | Type | +| --- | --- | +| data | Uint8Array | + + + +## decodeB64(data) ⇒ Uint8Array +Decode the given url-safe base64-encoded slice into its raw bytes. + +**Kind**: global function + +| Param | Type | +| --- | --- | +| data | Uint8Array | + ## start() diff --git a/bindings/wasm/src/verification/wasm_method_data.rs b/bindings/wasm/src/verification/wasm_method_data.rs index 809eab22e4..58a9c65820 100644 --- a/bindings/wasm/src/verification/wasm_method_data.rs +++ b/bindings/wasm/src/verification/wasm_method_data.rs @@ -1,6 +1,7 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use identity_iota::verification::CustomMethodData; use identity_iota::verification::MethodData; use wasm_bindgen::prelude::*; @@ -45,22 +46,23 @@ impl WasmMethodData { Ok(Self(MethodData::PublicKeyJwk(key.0.clone()))) } - /// Creates a new {@link MethodData} variant in CAIP-10 format. - #[wasm_bindgen(js_name = newBlockchainAccountId)] - pub fn new_blockchain_account_id(data: String) -> Self { - Self(MethodData::new_blockchain_account_id(data)) + /// Creates a new custom {@link MethodData}. + #[wasm_bindgen(js_name = newCustom)] + pub fn new_custom(name: String, data: JsValue) -> Result { + let data = data.into_serde::().wasm_result()?; + Ok(Self(MethodData::Custom(CustomMethodData { name, data }))) } - /// Returns the wrapped blockchain account id if the format is `BlockchainAccountId`. - #[wasm_bindgen(js_name = tryBlockchainAccountId)] - pub fn try_blockchain_account_id(&self) -> Result { + /// Returns the wrapped custom method data format is `Custom`. + #[wasm_bindgen(js_name = tryCustom)] + pub fn try_custom(&self) -> Result { self .0 - .blockchain_account_id() - .map(|id| id.to_string()) + .custom() + .map(|custom| custom.clone().into()) .ok_or(WasmError::new( Cow::Borrowed("MethodDataFormatError"), - Cow::Borrowed("method data format is not BlockchainAccountId"), + Cow::Borrowed("method data format is not Custom"), )) .wasm_result() } @@ -98,3 +100,31 @@ impl From for WasmMethodData { WasmMethodData(data) } } + +/// A custom verification method data format. +#[wasm_bindgen(js_name = CustomMethodData, inspectable)] +pub struct WasmCustomMethodData(pub(crate) CustomMethodData); + +#[wasm_bindgen(js_class = CustomMethodData)] +impl WasmCustomMethodData { + #[wasm_bindgen(constructor)] + pub fn new(name: String, data: JsValue) -> Result { + let data = data.into_serde::().wasm_result()?; + Ok(Self(CustomMethodData { name, data })) + } +} + +impl From for WasmCustomMethodData { + fn from(value: CustomMethodData) -> Self { + Self(value) + } +} + +impl From for CustomMethodData { + fn from(value: WasmCustomMethodData) -> Self { + value.0 + } +} + +impl_wasm_clone!(WasmCustomMethodData, CustomMethodData); +impl_wasm_json!(WasmCustomMethodData, CustomMethodData); diff --git a/bindings/wasm/src/verification/wasm_method_type.rs b/bindings/wasm/src/verification/wasm_method_type.rs index 850ba08890..4b7d297a62 100644 --- a/bindings/wasm/src/verification/wasm_method_type.rs +++ b/bindings/wasm/src/verification/wasm_method_type.rs @@ -27,10 +27,9 @@ impl WasmMethodType { WasmMethodType(MethodType::JSON_WEB_KEY) } - /// The `EcdsaSecp256k1RecoverySignature2020` method type. - #[wasm_bindgen(js_name = EcdsaSecp256k1RecoverySignature2020)] - pub fn ecdsa_secp256k1_recovery_signature_2020() -> WasmMethodType { - WasmMethodType(MethodType::ECDSA_SECP256K1_RECOVERY_SIGNATURE_2020) + /// A custom method. + pub fn custom(type_: String) -> WasmMethodType { + WasmMethodType(MethodType::custom(type_)) } /// Returns the {@link MethodType} as a string. diff --git a/identity_verification/Cargo.toml b/identity_verification/Cargo.toml index 1b6bb11d77..e400aeebeb 100644 --- a/identity_verification/Cargo.toml +++ b/identity_verification/Cargo.toml @@ -13,8 +13,8 @@ identity_core = { version = "=1.1.1", path = "./../identity_core", default-featu identity_did = { version = "=1.1.1", path = "./../identity_did", default-features = false } identity_jose = { version = "=1.1.1", path = "./../identity_jose", default-features = false } serde.workspace = true +serde_json.workspace = true strum.workspace = true thiserror.workspace = true [dev-dependencies] -serde_json.workspace = true diff --git a/identity_verification/src/verification_method/material.rs b/identity_verification/src/verification_method/material.rs index 23a4843cc6..8e881253c5 100644 --- a/identity_verification/src/verification_method/material.rs +++ b/identity_verification/src/verification_method/material.rs @@ -5,6 +5,12 @@ use crate::jose::jwk::Jwk; use core::fmt::Debug; use core::fmt::Formatter; use identity_core::convert::BaseEncoding; +use serde::de::Visitor; +use serde::ser::SerializeMap; +use serde::Deserialize; +use serde::Serialize; +use serde::Serializer; +use serde_json::Value; use crate::error::Error; use crate::error::Result; @@ -21,9 +27,9 @@ pub enum MethodData { PublicKeyBase58(String), /// Verification Material in the JSON Web Key format. PublicKeyJwk(Jwk), - /// Verification Material in CAIP-10 format. - /// [CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md) - BlockchainAccountId(String), + /// Arbitrary verification material. + #[serde(untagged)] + Custom(CustomMethodData), } impl MethodData { @@ -39,9 +45,9 @@ impl MethodData { Self::PublicKeyMultibase(BaseEncoding::encode_multibase(&data, None)) } - /// Verification Material in CAIP-10 format. - pub fn new_blockchain_account_id(data: String) -> Self { - Self::BlockchainAccountId(data) + /// Creates a new `MethodData` variant from custom data. + pub fn new_custom(data: impl Into) -> Self { + Self::Custom(data.into()) } /// Returns a `Vec` containing the decoded bytes of the `MethodData`. @@ -53,7 +59,7 @@ impl MethodData { /// represented as a vector of bytes. pub fn try_decode(&self) -> Result> { match self { - Self::PublicKeyJwk(_) | Self::BlockchainAccountId(_) => Err(Error::InvalidMethodDataTransformation( + Self::PublicKeyJwk(_) | Self::Custom(_) => Err(Error::InvalidMethodDataTransformation( "method data is not base encoded", )), Self::PublicKeyMultibase(input) => { @@ -77,10 +83,10 @@ impl MethodData { self.public_key_jwk().ok_or(Error::NotPublicKeyJwk) } - /// Returns the wrapped Blockchain Account Id if the format is [`MethodData::BlockchainAccountId`]. - pub fn blockchain_account_id(&self) -> Option<&str> { - if let Self::BlockchainAccountId(id) = self { - Some(id) + /// Returns the custom method data, if any. + pub fn custom(&self) -> Option<&CustomMethodData> { + if let Self::Custom(method_data) = self { + Some(method_data) } else { None } @@ -93,7 +99,94 @@ impl Debug for MethodData { Self::PublicKeyJwk(inner) => f.write_fmt(format_args!("PublicKeyJwk({inner:#?})")), Self::PublicKeyMultibase(inner) => f.write_fmt(format_args!("PublicKeyMultibase({inner})")), Self::PublicKeyBase58(inner) => f.write_fmt(format_args!("PublicKeyBase58({inner})")), - Self::BlockchainAccountId(inner) => f.write_fmt(format_args!("BlockchainAccountId({inner})")), + Self::Custom(CustomMethodData { name, data }) => f.write_fmt(format_args!("{name}({data})")), } } } + +#[derive(Clone, Debug, PartialEq, Eq)] +/// Custom verification method. +pub struct CustomMethodData { + /// Verification method's name. + pub name: String, + /// Verification method's data. + pub data: Value, +} + +impl Serialize for CustomMethodData { + fn serialize(&self, serializer: S) -> std::prelude::v1::Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_entry(&self.name, &self.data)?; + map.end() + } +} + +impl<'de> Deserialize<'de> for CustomMethodData { + fn deserialize(deserializer: D) -> std::prelude::v1::Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_map(CustomMethodDataVisitor) + } +} + +struct CustomMethodDataVisitor; + +impl<'de> Visitor<'de> for CustomMethodDataVisitor { + type Value = CustomMethodData; + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("\"\": ") + } + fn visit_map(self, mut map: A) -> std::prelude::v1::Result + where + A: serde::de::MapAccess<'de>, + { + let mut custom_method_data = CustomMethodData { + name: String::default(), + data: Value::Null, + }; + while let Some((name, data)) = map.next_entry::()? { + custom_method_data = CustomMethodData { name, data }; + } + + Ok(custom_method_data) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn serialize_custom_method_data() { + let custom = MethodData::Custom(CustomMethodData { + name: "anArbitraryMethod".to_owned(), + data: json!({"a": 1, "b": 2}), + }); + let target_str = json!({ + "anArbitraryMethod": {"a": 1, "b": 2}, + }) + .to_string(); + assert_eq!(serde_json::to_string(&custom).unwrap(), target_str); + } + #[test] + fn deserialize_custom_method_data() { + let inner_data = json!({ + "firstCustomField": "a random string", + "secondCustomField": 420, + }); + let json_method_data = json!({ + "myCustomVerificationMethod": &inner_data, + }); + let custom = serde_json::from_value::(json_method_data.clone()).unwrap(); + let target_method_data = MethodData::Custom(CustomMethodData { + name: "myCustomVerificationMethod".to_owned(), + data: inner_data, + }); + assert_eq!(custom, target_method_data); + } +} diff --git a/identity_verification/src/verification_method/method.rs b/identity_verification/src/verification_method/method.rs index 360f2efe55..8c48e06893 100644 --- a/identity_verification/src/verification_method/method.rs +++ b/identity_verification/src/verification_method/method.rs @@ -20,6 +20,7 @@ use crate::verification_method::MethodBuilder; use crate::verification_method::MethodData; use crate::verification_method::MethodRef; use crate::verification_method::MethodType; +use crate::CustomMethodData; use identity_did::CoreDID; use identity_did::DIDUrl; use identity_did::DID; @@ -28,8 +29,8 @@ use identity_did::DID; /// /// [Specification](https://www.w3.org/TR/did-core/#verification-method-properties) #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[serde(from = "_VerificationMethod")] pub struct VerificationMethod { - #[serde(deserialize_with = "deserialize_id_with_fragment")] pub(crate) id: DIDUrl, pub(crate) controller: CoreDID, #[serde(rename = "type")] @@ -245,3 +246,46 @@ impl KeyComparable for VerificationMethod { self.id() } } + +// Horrible workaround for a tracked serde issue https://github.com/serde-rs/serde/issues/2200. Serde doesn't "consume" +// the input when deserializing flattened enums (MethodData in this case) causing duplication of data (in this case +// it ends up in the properties object). This workaround simply removes the duplication. +#[derive(Deserialize)] +struct _VerificationMethod { + #[serde(deserialize_with = "deserialize_id_with_fragment")] + pub(crate) id: DIDUrl, + pub(crate) controller: CoreDID, + #[serde(rename = "type")] + pub(crate) type_: MethodType, + #[serde(flatten)] + pub(crate) data: MethodData, + #[serde(flatten)] + pub(crate) properties: Object, +} + +impl From<_VerificationMethod> for VerificationMethod { + fn from(value: _VerificationMethod) -> Self { + let _VerificationMethod { + id, + controller, + type_, + data, + mut properties, + } = value; + let key = match &data { + MethodData::PublicKeyBase58(_) => "publicKeyBase58", + MethodData::PublicKeyJwk(_) => "publicKeyJwk", + MethodData::PublicKeyMultibase(_) => "publicKeyMultibase", + MethodData::Custom(CustomMethodData { name, .. }) => name.as_str(), + }; + properties.remove(key); + + VerificationMethod { + id, + controller, + type_, + data, + properties, + } + } +} diff --git a/identity_verification/src/verification_method/method_type.rs b/identity_verification/src/verification_method/method_type.rs index aa80ef4580..ae3877948d 100644 --- a/identity_verification/src/verification_method/method_type.rs +++ b/identity_verification/src/verification_method/method_type.rs @@ -12,7 +12,6 @@ use crate::error::Result; const ED25519_VERIFICATION_KEY_2018_STR: &str = "Ed25519VerificationKey2018"; const X25519_KEY_AGREEMENT_KEY_2019_STR: &str = "X25519KeyAgreementKey2019"; const JSON_WEB_KEY_METHOD_TYPE: &str = "JsonWebKey"; -const ECDSA_SECP256K1_RECOVERY_SIGNATURE_2020_STR: &str = "EcdsaSecp256k1RecoverySignature2020"; /// verification method types. #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] @@ -26,9 +25,10 @@ impl MethodType { /// A verification method for use with JWT verification as prescribed by the [`Jwk`](::identity_jose::jwk::Jwk) /// in the [`publicKeyJwk`](crate::MethodData::PublicKeyJwk) entry. pub const JSON_WEB_KEY: Self = Self(Cow::Borrowed(JSON_WEB_KEY_METHOD_TYPE)); - /// The `EcdsaSecp256k1RecoverySignature2020` method type. - pub const ECDSA_SECP256K1_RECOVERY_SIGNATURE_2020: Self = - Self(Cow::Borrowed(ECDSA_SECP256K1_RECOVERY_SIGNATURE_2020_STR)); + /// Construct a custom method type. + pub fn custom(type_: impl AsRef) -> Self { + Self(Cow::Owned(type_.as_ref().to_owned())) + } } impl MethodType { @@ -58,7 +58,6 @@ impl FromStr for MethodType { ED25519_VERIFICATION_KEY_2018_STR => Ok(Self::ED25519_VERIFICATION_KEY_2018), X25519_KEY_AGREEMENT_KEY_2019_STR => Ok(Self::X25519_KEY_AGREEMENT_KEY_2019), JSON_WEB_KEY_METHOD_TYPE => Ok(Self::JSON_WEB_KEY), - ECDSA_SECP256K1_RECOVERY_SIGNATURE_2020_STR => Ok(Self::ECDSA_SECP256K1_RECOVERY_SIGNATURE_2020), _ => Ok(Self(Cow::Owned(string.to_owned()))), } } diff --git a/identity_verification/src/verification_method/mod.rs b/identity_verification/src/verification_method/mod.rs index af6da98529..585b58639c 100644 --- a/identity_verification/src/verification_method/mod.rs +++ b/identity_verification/src/verification_method/mod.rs @@ -15,6 +15,7 @@ mod method_scope; mod method_type; pub use self::builder::MethodBuilder; +pub use self::material::CustomMethodData; pub use self::material::MethodData; pub use self::method::VerificationMethod; pub use self::method_ref::MethodRef;