diff --git a/README.md b/README.md index 658479444f..41ba9d8519 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ ## Introduction -IOTA Identity is a [Rust](https://www.rust-lang.org/) implementation of decentralized digital identity, also known as Self-Sovereign Identity (SSI). It implements the W3C [Decentralized Identifiers (DID)](https://www.w3.org/TR/did-core/) and [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) specifications. This library can be used to create, resolve and authenticate digital identities and to create verifiable credentials and presentations in order to share information in a verifiable manner and establish trust in the digital world. It does so while supporting secure storage of cryptographic keys, which can be implemented for your preferred key management system. Many of the individual libraries (Rust crates) are agnostic over the concrete DID method, with the exception of some libraries dedicated to implement the [IOTA DID method](https://wiki.iota.org/shimmer/identity.rs/specs/did/iota_did_method_spec/), which is an implementation of decentralized digital identity on the IOTA and Shimmer networks. Written in stable Rust, IOTA Identity has strong guarantees of memory safety and process integrity while maintaining exceptional performance. +IOTA Identity is a [Rust](https://www.rust-lang.org/) implementation of decentralized digital identity, also known as Self-Sovereign Identity (SSI). It implements the W3C [Decentralized Identifiers (DID)](https://www.w3.org/TR/did-core/) and [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) specifications. This library can be used to create, resolve and authenticate digital identities and to create verifiable credentials and presentations in order to share information in a verifiable manner and establish trust in the digital world. It does so while supporting secure storage of cryptographic keys, which can be implemented for your preferred key management system. Many of the individual libraries (Rust crates) are agnostic over the concrete DID method, with the exception of some libraries dedicated to implement the [IOTA DID method](https://wiki.iota.org/identity.rs/specs/did/iota_did_method_spec/), which is an implementation of decentralized digital identity on the IOTA and Shimmer networks. Written in stable Rust, IOTA Identity has strong guarantees of memory safety and process integrity while maintaining exceptional performance. ## Bindings @@ -36,8 +36,8 @@ IOTA Identity is a [Rust](https://www.rust-lang.org/) implementation of decentra - API References: - [Rust API Reference](https://docs.rs/identity_iota/latest/identity_iota/): Package documentation (cargo docs). - - [Wasm API Reference](https://wiki.iota.org/shimmer/identity.rs/libraries/wasm/api_reference/): Wasm Package documentation. -- [Identity Documentation Pages](https://wiki.iota.org/shimmer/identity.rs/introduction): Supplementing documentation with context around identity and simple examples on library usage. + - [Wasm API Reference](https://wiki.iota.org/identity.rs/libraries/wasm/api_reference/): Wasm Package documentation. +- [Identity Documentation Pages](https://wiki.iota.org/identity.rs/introduction): Supplementing documentation with context around identity and simple examples on library usage. - [Examples](https://github.com/iotaledger/identity.rs/blob/HEAD/examples): Practical code snippets to get you started with the library. ## Prerequisites @@ -238,7 +238,7 @@ For detailed development progress, see the IOTA Identity development [kanban boa We would love to have you help us with the development of IOTA Identity. Each and every contribution is greatly valued! -Please review the [contribution](https://wiki.iota.org/shimmer/identity.rs/contribute) and [workflow](https://wiki.iota.org/shimmer/identity.rs/workflow) sections in the [IOTA Wiki](https://wiki.iota.org/). +Please review the [contribution](https://wiki.iota.org/identity.rs/contribute) and [workflow](https://wiki.iota.org/identity.rs/workflow) sections in the [IOTA Wiki](https://wiki.iota.org/). To contribute directly to the repository, simply fork the project, push your changes to your fork and create a pull request to get them included! diff --git a/bindings/wasm/docs/api-reference.md b/bindings/wasm/docs/api-reference.md index e83bbc81b6..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,8 +190,9 @@ working with storage backed DID documents.

## Members
-
CredentialStatus
-
+
StatusPurpose
+

Purpose of a StatusList2021.

+
SubjectHolderRelationship

Declares how credential subjects must relate to the presentation holder.

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

@@ -203,9 +207,8 @@ This variant is the default.

Any

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

-
StatusPurpose
-

Purpose of a StatusList2021.

-
+
StateMetadataEncoding
+
FailFast

Declares when validation should return if an error occurs.

@@ -215,6 +218,10 @@ This variant is the default.

FirstError

Return after the first error occurs.

+
MethodRelationship
+
+
CredentialStatus
+
StatusCheck

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

@@ -232,18 +239,11 @@ This variant is the default.

SkipAll

Skip all status checks.

-
StateMetadataEncoding
-
-
MethodRelationship
-
## Functions
-
start()
-

Initializes the console error panic hook for better error messages

-
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 @@ -258,6 +258,9 @@ prior to calling the function.

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 @@ -1967,7 +2017,7 @@ if the object is being concurrently modified. * _instance_ * [.id()](#IotaDocument+id) ⇒ [IotaDID](#IotaDID) * [.controller()](#IotaDocument+controller) ⇒ [Array.<IotaDID>](#IotaDID) - * [.setController(controllers)](#IotaDocument+setController) + * [.setController(controller)](#IotaDocument+setController) * [.alsoKnownAs()](#IotaDocument+alsoKnownAs) ⇒ Array.<string> * [.setAlsoKnownAs(urls)](#IotaDocument+setAlsoKnownAs) * [.properties()](#IotaDocument+properties) ⇒ Map.<string, any> @@ -2042,7 +2092,7 @@ during resolution and are omitted when publishing. **Kind**: instance method of [IotaDocument](#IotaDocument) -### iotaDocument.setController(controllers) +### iotaDocument.setController(controller) Sets the controllers of the document. Note: Duplicates will be ignored. @@ -2052,7 +2102,7 @@ Use `null` to remove all controllers. | Param | Type | | --- | --- | -| controllers | [CoreDID](#CoreDID) \| [Array.<CoreDID>](#CoreDID) \| null | +| controller | [Array.<IotaDID>](#IotaDID) \| null | @@ -4343,6 +4393,7 @@ Supported verification method data formats. * [MethodData](#MethodData) * _instance_ + * [.tryCustom()](#MethodData+tryCustom) ⇒ [CustomMethodData](#CustomMethodData) * [.tryDecode()](#MethodData+tryDecode) ⇒ Uint8Array * [.tryPublicKeyJwk()](#MethodData+tryPublicKeyJwk) ⇒ [Jwk](#Jwk) * [.toJSON()](#MethodData+toJSON) ⇒ any @@ -4351,8 +4402,15 @@ Supported verification method data formats. * [.newBase58(data)](#MethodData.newBase58) ⇒ [MethodData](#MethodData) * [.newMultibase(data)](#MethodData.newMultibase) ⇒ [MethodData](#MethodData) * [.newJwk(key)](#MethodData.newJwk) ⇒ [MethodData](#MethodData) + * [.newCustom(name, data)](#MethodData.newCustom) ⇒ [MethodData](#MethodData) * [.fromJSON(json)](#MethodData.fromJSON) ⇒ [MethodData](#MethodData) + + +### methodData.tryCustom() ⇒ [CustomMethodData](#CustomMethodData) +Returns the wrapped custom method data format is `Custom`. + +**Kind**: instance method of [MethodData](#MethodData) ### methodData.tryDecode() ⇒ Uint8Array @@ -4419,6 +4477,18 @@ An error is thrown if the given `key` contains any private components. | --- | --- | | key | [Jwk](#Jwk) | + + +### MethodData.newCustom(name, data) ⇒ [MethodData](#MethodData) +Creates a new custom [MethodData](#MethodData). + +**Kind**: static method of [MethodData](#MethodData) + +| Param | Type | +| --- | --- | +| name | string | +| data | any | + ### MethodData.fromJSON(json) ⇒ [MethodData](#MethodData) @@ -4570,6 +4640,7 @@ Supported verification method types. * [.Ed25519VerificationKey2018()](#MethodType.Ed25519VerificationKey2018) ⇒ [MethodType](#MethodType) * [.X25519KeyAgreementKey2019()](#MethodType.X25519KeyAgreementKey2019) ⇒ [MethodType](#MethodType) * [.JsonWebKey()](#MethodType.JsonWebKey) ⇒ [MethodType](#MethodType) + * [.custom(type_)](#MethodType.custom) ⇒ [MethodType](#MethodType) * [.fromJSON(json)](#MethodType.fromJSON) ⇒ [MethodType](#MethodType) @@ -4605,6 +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.custom(type_) ⇒ [MethodType](#MethodType) +A custom method. + +**Kind**: static method of [MethodType](#MethodType) + +| Param | Type | +| --- | --- | +| type_ | string | + ### MethodType.fromJSON(json) ⇒ [MethodType](#MethodType) @@ -5006,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) @@ -5053,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) @@ -5080,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 @@ -5967,6 +6030,7 @@ A DID Document Verification Method. **Kind**: global class * [VerificationMethod](#VerificationMethod) + * [new VerificationMethod(id, controller, type_, data)](#new_VerificationMethod_new) * _instance_ * [.id()](#VerificationMethod+id) ⇒ [DIDUrl](#DIDUrl) * [.setId(id)](#VerificationMethod+setId) @@ -5984,6 +6048,19 @@ A DID Document Verification Method. * [.newFromJwk(did, key, [fragment])](#VerificationMethod.newFromJwk) ⇒ [VerificationMethod](#VerificationMethod) * [.fromJSON(json)](#VerificationMethod.fromJSON) ⇒ [VerificationMethod](#VerificationMethod) + + +### new VerificationMethod(id, controller, type_, data) +Create a custom [VerificationMethod](#VerificationMethod). + + +| Param | Type | +| --- | --- | +| id | [DIDUrl](#DIDUrl) | +| controller | [CoreDID](#CoreDID) | +| type_ | [MethodType](#MethodType) | +| data | [MethodData](#MethodData) | + ### verificationMethod.id() ⇒ [DIDUrl](#DIDUrl) @@ -6119,9 +6196,11 @@ Deserializes an instance from a JSON object. | --- | --- | | json | any | - + + +## StatusPurpose +Purpose of a [StatusList2021](#StatusList2021). -## CredentialStatus **Kind**: global variable @@ -6150,11 +6229,9 @@ The holder must match the subject only for credentials where the [`nonTransferab The holder is not required to have any kind of relationship to any credential subject. **Kind**: global variable - - -## StatusPurpose -Purpose of a [StatusList2021](#StatusList2021). + +## StateMetadataEncoding **Kind**: global variable @@ -6173,6 +6250,14 @@ Return all errors that occur during validation. ## FirstError Return after the first error occurs. +**Kind**: global variable + + +## MethodRelationship +**Kind**: global variable + + +## CredentialStatus **Kind**: global variable @@ -6205,20 +6290,6 @@ Validate the status if supported, skip any unsupported Skip all status checks. **Kind**: global variable - - -## StateMetadataEncoding -**Kind**: global variable - - -## MethodRelationship -**Kind**: global variable - - -## start() -Initializes the console error panic hook for better error messages - -**Kind**: global function ## verifyEd25519(alg, signingInput, decodedSignature, publicKey) @@ -6263,3 +6334,9 @@ Decode the given url-safe base64-encoded slice into its raw bytes. | --- | --- | | data | Uint8Array | + + +## start() +Initializes the console error panic hook for better error messages + +**Kind**: global function diff --git a/bindings/wasm/src/sd_jwt/wasm_sd_jwt.rs b/bindings/wasm/src/sd_jwt/wasm_sd_jwt.rs index 7b4f201206..c55de229e6 100644 --- a/bindings/wasm/src/sd_jwt/wasm_sd_jwt.rs +++ b/bindings/wasm/src/sd_jwt/wasm_sd_jwt.rs @@ -77,5 +77,4 @@ impl WasmSdJwt { } } -impl_wasm_json!(WasmSdJwt, SdJwt); impl_wasm_clone!(WasmSdJwt, SdJwt); diff --git a/bindings/wasm/src/verification/wasm_method_data.rs b/bindings/wasm/src/verification/wasm_method_data.rs index 5bba4aa5a9..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,6 +46,27 @@ impl WasmMethodData { Ok(Self(MethodData::PublicKeyJwk(key.0.clone()))) } + /// 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 custom method data format is `Custom`. + #[wasm_bindgen(js_name = tryCustom)] + pub fn try_custom(&self) -> Result { + self + .0 + .custom() + .map(|custom| custom.clone().into()) + .ok_or(WasmError::new( + Cow::Borrowed("MethodDataFormatError"), + Cow::Borrowed("method data format is not Custom"), + )) + .wasm_result() + } + /// Returns a `Uint8Array` containing the decoded bytes of the {@link MethodData}. /// /// This is generally a public key identified by a {@link MethodData} value. @@ -78,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 9fb1fff660..4b7d297a62 100644 --- a/bindings/wasm/src/verification/wasm_method_type.rs +++ b/bindings/wasm/src/verification/wasm_method_type.rs @@ -27,6 +27,11 @@ impl WasmMethodType { WasmMethodType(MethodType::JSON_WEB_KEY) } + /// A custom method. + pub fn custom(type_: String) -> WasmMethodType { + WasmMethodType(MethodType::custom(type_)) + } + /// Returns the {@link MethodType} as a string. #[allow(clippy::inherent_to_string)] #[wasm_bindgen(js_name = toString)] diff --git a/bindings/wasm/src/verification/wasm_verification_method.rs b/bindings/wasm/src/verification/wasm_verification_method.rs index 62b5103c9d..6f01436ffe 100644 --- a/bindings/wasm/src/verification/wasm_verification_method.rs +++ b/bindings/wasm/src/verification/wasm_verification_method.rs @@ -8,6 +8,7 @@ use crate::did::WasmCoreDID; use crate::did::WasmDIDUrl; use crate::error::Result; use crate::error::WasmResult; +use identity_iota::core::Object; use identity_iota::did::CoreDID; use identity_iota::verification::VerificationMethod; use wasm_bindgen::prelude::*; @@ -37,6 +38,24 @@ impl WasmVerificationMethod { .wasm_result() } + /// Create a custom {@link VerificationMethod}. + #[wasm_bindgen(constructor)] + pub fn new( + id: &WasmDIDUrl, + controller: &WasmCoreDID, + type_: &WasmMethodType, + data: &WasmMethodData, + ) -> Result { + VerificationMethod::builder(Object::new()) + .type_(type_.0.clone()) + .data(data.0.clone()) + .controller(controller.0.clone()) + .id(id.0.clone()) + .build() + .map(Self) + .wasm_result() + } + /// Returns a copy of the {@link DIDUrl} of the {@link VerificationMethod}'s `id`. #[wasm_bindgen] pub fn id(&self) -> WasmDIDUrl { diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 136049bb28..0121a2d10c 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -14,7 +14,7 @@ iota-sdk = { version = "1.0", default-features = false, features = ["tls", "clie json-proof-token.workspace = true primitive-types = "0.12.1" rand = "0.8.5" -sd-jwt-payload = { version = "0.2.0", default-features = false, features = ["sha"] } +sd-jwt-payload = { version = "0.2.1", default-features = false, features = ["sha"] } serde_json = { version = "1.0", default-features = false } tokio = { version = "1.29", default-features = false, features = ["rt"] } diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml index a09b971799..964c5a26bc 100644 --- a/identity_credential/Cargo.toml +++ b/identity_credential/Cargo.toml @@ -22,8 +22,8 @@ indexmap = { version = "2.0", default-features = false, features = ["std", "serd itertools = { version = "0.11", default-features = false, features = ["use_std"], optional = true } once_cell = { version = "1.18", default-features = false, features = ["std"] } reqwest = { version = "0.11", default-features = false, features = ["default-tls", "json", "stream"], optional = true } -roaring = { version = "0.10", default-features = false, features = ["std"], optional = true } -sd-jwt-payload = { version = "0.2.0", default-features = false, features = ["sha"], optional = true } +roaring = { version = "0.10.2", default-features = false, features = ["serde"], optional = true } +sd-jwt-payload = { version = "0.2.1", default-features = false, features = ["sha"], optional = true } serde.workspace = true serde-aux = { version = "4.3.1", default-features = false, optional = true } serde_json.workspace = true diff --git a/identity_iota/README.md b/identity_iota/README.md index e210d6e10d..69d68defd8 100644 --- a/identity_iota/README.md +++ b/identity_iota/README.md @@ -24,7 +24,7 @@ ## Introduction -IOTA Identity is a [Rust](https://www.rust-lang.org/) implementation of decentralized digital identity, also known as Self-Sovereign Identity (SSI). It implements the W3C [Decentralized Identifiers (DID)](https://www.w3.org/TR/did-core/) and [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) specifications. This library can be used to create, resolve and authenticate digital identities and to create verifiable credentials and presentations in order to share information in a verifiable manner and establish trust in the digital world. It does so while supporting secure storage of cryptographic keys, which can be implemented for your preferred key management system. Many of the individual libraries (Rust crates) are agnostic over the concrete DID method, with the exception of some libraries dedicated to implement the [IOTA DID method](https://wiki.iota.org/shimmer/identity.rs/specs/did/iota_did_method_spec/), which is an implementation of decentralized digital identity on the IOTA and Shimmer networks. Written in stable Rust, IOTA Identity has strong guarantees of memory safety and process integrity while maintaining exceptional performance. +IOTA Identity is a [Rust](https://www.rust-lang.org/) implementation of decentralized digital identity, also known as Self-Sovereign Identity (SSI). It implements the W3C [Decentralized Identifiers (DID)](https://www.w3.org/TR/did-core/) and [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) specifications. This library can be used to create, resolve and authenticate digital identities and to create verifiable credentials and presentations in order to share information in a verifiable manner and establish trust in the digital world. It does so while supporting secure storage of cryptographic keys, which can be implemented for your preferred key management system. Many of the individual libraries (Rust crates) are agnostic over the concrete DID method, with the exception of some libraries dedicated to implement the [IOTA DID method](https://wiki.iota.org/identity.rs/specs/did/iota_did_method_spec/), which is an implementation of decentralized digital identity on the IOTA and Shimmer networks. Written in stable Rust, IOTA Identity has strong guarantees of memory safety and process integrity while maintaining exceptional performance. ## Bindings @@ -36,8 +36,8 @@ IOTA Identity is a [Rust](https://www.rust-lang.org/) implementation of decentra - API References: - [Rust API Reference](https://docs.rs/identity_iota/latest/identity_iota/): Package documentation (cargo docs). - - [Wasm API Reference](https://wiki.iota.org/shimmer/identity.rs/libraries/wasm/api_reference/): Wasm Package documentation. -- [Identity Documentation Pages](https://wiki.iota.org/shimmer/identity.rs/introduction): Supplementing documentation with context around identity and simple examples on library usage. + - [Wasm API Reference](https://wiki.iota.org/identity.rs/libraries/wasm/api_reference/): Wasm Package documentation. +- [Identity Documentation Pages](https://wiki.iota.org/identity.rs/introduction): Supplementing documentation with context around identity and simple examples on library usage. - [Examples](https://github.com/iotaledger/identity.rs/blob/HEAD/examples): Practical code snippets to get you started with the library. ## Prerequisites @@ -74,7 +74,7 @@ version = "1.0.0" edition = "2021" [dependencies] -identity_iota = {version = "1.1.1", features = ["memstore"]} +identity_iota = { version = "1.1.1", features = ["memstore"] } iota-sdk = { version = "1.0.2", default-features = true, features = ["tls", "client", "stronghold"] } tokio = { version = "1", features = ["full"] } anyhow = "1.0.62" @@ -214,7 +214,7 @@ For detailed development progress, see the IOTA Identity development [kanban boa We would love to have you help us with the development of IOTA Identity. Each and every contribution is greatly valued! -Please review the [contribution](https://wiki.iota.org/shimmer/identity.rs/contribute) and [workflow](https://wiki.iota.org/shimmer/identity.rs/workflow) sections in the [IOTA Wiki](https://wiki.iota.org/). +Please review the [contribution](https://wiki.iota.org/identity.rs/contribute) and [workflow](https://wiki.iota.org/identity.rs/workflow) sections in the [IOTA Wiki](https://wiki.iota.org/). To contribute directly to the repository, simply fork the project, push your changes to your fork and create a pull request to get them included! diff --git a/identity_verification/Cargo.toml b/identity_verification/Cargo.toml index 1b6bb11d77..3e4cec6663 100644 --- a/identity_verification/Cargo.toml +++ b/identity_verification/Cargo.toml @@ -14,7 +14,7 @@ identity_did = { version = "=1.1.1", path = "./../identity_did", default-feature identity_jose = { version = "=1.1.1", path = "./../identity_jose", default-features = false } serde.workspace = true strum.workspace = true +serde_json.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 4d5f5775aa..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,6 +45,11 @@ impl MethodData { Self::PublicKeyMultibase(BaseEncoding::encode_multibase(&data, None)) } + /// 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`. /// /// This is generally a public key identified by a `MethodType` value. @@ -48,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) => { @@ -71,6 +82,15 @@ impl MethodData { pub fn try_public_key_jwk(&self) -> Result<&Jwk> { self.public_key_jwk().ok_or(Error::NotPublicKeyJwk) } + + /// 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 + } + } } impl Debug for MethodData { @@ -79,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;