diff --git a/bindings/wasm/docs/api-reference.md b/bindings/wasm/docs/api-reference.md
index 81d4124b69..cd49b97317 100644
--- a/bindings/wasm/docs/api-reference.md
+++ b/bindings/wasm/docs/api-reference.md
@@ -222,9 +222,9 @@ publishing to the Tangle.
**Kind**: global class
* [Account](#Account)
- * [.attachMethodRelationships(options)](#Account+attachMethodRelationships) ⇒ Promise.<void>
+ * [.createService(options)](#Account+createService) ⇒ Promise.<void>
* [.createMethod(options)](#Account+createMethod) ⇒ Promise.<void>
- * [.detachMethodRelationships(options)](#Account+detachMethodRelationships) ⇒ Promise.<void>
+ * [.attachMethodRelationships(options)](#Account+attachMethodRelationships) ⇒ Promise.<void>
* [.did()](#Account+did) ⇒ [DID
](#DID)
* [.autopublish()](#Account+autopublish) ⇒ boolean
* [.autosave()](#Account+autosave) ⇒ [AutoSave
](#AutoSave)
@@ -242,25 +242,22 @@ publishing to the Tangle.
* [.unrevokeCredentials(fragment, credentialIndices)](#Account+unrevokeCredentials) ⇒ Promise.<void>
* [.encryptData(plaintext, associated_data, encryption_algorithm, cek_algorithm, public_key)](#Account+encryptData) ⇒ [Promise.<EncryptedData>
](#EncryptedData)
* [.decryptData(data, encryption_algorithm, cek_algorithm, fragment)](#Account+decryptData) ⇒ Promise.<Uint8Array>
- * [.setAlsoKnownAs(options)](#Account+setAlsoKnownAs) ⇒ Promise.<void>
- * [.deleteMethod(options)](#Account+deleteMethod) ⇒ Promise.<void>
+ * [.detachMethodRelationships(options)](#Account+detachMethodRelationships) ⇒ Promise.<void>
* [.deleteService(options)](#Account+deleteService) ⇒ Promise.<void>
+ * [.setAlsoKnownAs(options)](#Account+setAlsoKnownAs) ⇒ Promise.<void>
* [.setController(options)](#Account+setController) ⇒ Promise.<void>
- * [.createService(options)](#Account+createService) ⇒ Promise.<void>
-
-
+ * [.deleteMethod(options)](#Account+deleteMethod) ⇒ Promise.<void>
-### account.attachMethodRelationships(options) ⇒ Promise.<void>
-Attach one or more verification relationships to a method.
+
-Note: the method must exist and be in the set of verification methods;
-it cannot be an embedded method.
+### account.createService(options) ⇒ Promise.<void>
+Adds a new Service to the DID Document.
**Kind**: instance method of [Account
](#Account)
| Param | Type |
| --- | --- |
-| options | AttachMethodRelationshipOptions
|
+| options | CreateServiceOptions
|
@@ -273,16 +270,19 @@ Adds a new verification method to the DID document.
| --- | --- |
| options | CreateMethodOptions
|
-
+
-### account.detachMethodRelationships(options) ⇒ Promise.<void>
-Detaches the given relationship from the given method, if the method exists.
+### account.attachMethodRelationships(options) ⇒ Promise.<void>
+Attach one or more verification relationships to a method.
+
+Note: the method must exist and be in the set of verification methods;
+it cannot be an embedded method.
**Kind**: instance method of [Account
](#Account)
| Param | Type |
| --- | --- |
-| options | DetachMethodRelationshipOptions
|
+| options | AttachMethodRelationshipOptions
|
@@ -475,38 +475,38 @@ Returns the decrypted text.
| cek_algorithm | [CekAlgorithm
](#CekAlgorithm) |
| fragment | string
|
-
+
-### account.setAlsoKnownAs(options) ⇒ Promise.<void>
-Sets the `alsoKnownAs` property in the DID document.
+### account.detachMethodRelationships(options) ⇒ Promise.<void>
+Detaches the given relationship from the given method, if the method exists.
**Kind**: instance method of [Account
](#Account)
| Param | Type |
| --- | --- |
-| options | SetAlsoKnownAsOptions
|
+| options | DetachMethodRelationshipOptions
|
-
+
-### account.deleteMethod(options) ⇒ Promise.<void>
-Deletes a verification method if the method exists.
+### account.deleteService(options) ⇒ Promise.<void>
+Deletes a Service if it exists.
**Kind**: instance method of [Account
](#Account)
| Param | Type |
| --- | --- |
-| options | DeleteMethodOptions
|
+| options | DeleteServiceOptions
|
-
+
-### account.deleteService(options) ⇒ Promise.<void>
-Deletes a Service if it exists.
+### account.setAlsoKnownAs(options) ⇒ Promise.<void>
+Sets the `alsoKnownAs` property in the DID document.
**Kind**: instance method of [Account
](#Account)
| Param | Type |
| --- | --- |
-| options | DeleteServiceOptions
|
+| options | SetAlsoKnownAsOptions
|
@@ -519,16 +519,16 @@ Sets the controllers of the DID document.
| --- | --- |
| options | SetControllerOptions
|
-
+
-### account.createService(options) ⇒ Promise.<void>
-Adds a new Service to the DID Document.
+### account.deleteMethod(options) ⇒ Promise.<void>
+Deletes a verification method if the method exists.
**Kind**: instance method of [Account
](#Account)
| Param | Type |
| --- | --- |
-| options | CreateServiceOptions
|
+| options | DeleteMethodOptions
|
@@ -1176,6 +1176,7 @@ Deserializes a `CredentialValidationOptions` from a JSON object.
* [.verifySignature(credential, trusted_issuers, options)](#CredentialValidator.verifySignature)
* [.check_subject_holder_relationship(credential, holder_url, relationship)](#CredentialValidator.check_subject_holder_relationship)
* [.checkStatus(credential, trustedIssuers, statusCheck)](#CredentialValidator.checkStatus)
+ * [.extractIssuer(credential)](#CredentialValidator.extractIssuer) ⇒ [DID
](#DID)
@@ -1269,7 +1270,7 @@ to verify the credential's signature will be made and an error is returned upon
| Param | Type |
| --- | --- |
| credential | [Credential
](#Credential) |
-| trusted_issuers | [Array.<Document>
](#Document) \| [Array.<ResolvedDocument>
](#ResolvedDocument) |
+| trusted_issuers | Array.<(Document\|ResolvedDocument)>
|
| options | [VerifierOptions
](#VerifierOptions) |
@@ -1298,9 +1299,24 @@ Only supports `BitmapRevocation2022`.
| Param | Type |
| --- | --- |
| credential | [Credential
](#Credential) |
-| trustedIssuers | [Array.<Document>
](#Document) \| [Array.<ResolvedDocument>
](#ResolvedDocument) |
+| trustedIssuers | Array.<(Document\|ResolvedDocument)>
|
| statusCheck | number
|
+
+
+### CredentialValidator.extractIssuer(credential) ⇒ [DID
](#DID)
+Utility for extracting the issuer field of a `Credential` as a DID.
+
+### Errors
+
+Fails if the issuer field is not a valid DID.
+
+**Kind**: static method of [CredentialValidator
](#CredentialValidator)
+
+| Param | Type |
+| --- | --- |
+| credential | [Credential
](#Credential) |
+
## DID
@@ -1755,6 +1771,7 @@ Deserializes a `DiffMessage` from a JSON object.
* [.service()](#Document+service) ⇒ [Array.<Service>
](#Service)
* [.insertService(service)](#Document+insertService) ⇒ boolean
* [.removeService(did)](#Document+removeService) ⇒ boolean
+ * [.resolveService(query)](#Document+resolveService) ⇒ [Service
](#Service) \| undefined
* [.methods()](#Document+methods) ⇒ [Array.<VerificationMethod>
](#VerificationMethod)
* [.insertMethod(method, scope)](#Document+insertMethod)
* [.removeMethod(did)](#Document+removeMethod)
@@ -1782,8 +1799,8 @@ Deserializes a `DiffMessage` from a JSON object.
* [.metadataPreviousMessageId()](#Document+metadataPreviousMessageId) ⇒ string
* [.setMetadataPreviousMessageId(value)](#Document+setMetadataPreviousMessageId)
* [.proof()](#Document+proof) ⇒ [Proof
](#Proof) \| undefined
- * [.revokeCredentials(fragment, credentialIndices)](#Document+revokeCredentials)
- * [.unrevokeCredentials(fragment, credentialIndices)](#Document+unrevokeCredentials)
+ * [.revokeCredentials(serviceQuery, credentialIndices)](#Document+revokeCredentials)
+ * [.unrevokeCredentials(serviceQuery, credentialIndices)](#Document+unrevokeCredentials)
* [.toJSON()](#Document+toJSON) ⇒ any
* [.clone()](#Document+clone) ⇒ [Document
](#Document)
* _static_
@@ -1916,6 +1933,18 @@ Returns `true` if a service was removed.
| --- | --- |
| did | [DIDUrl
](#DIDUrl) |
+
+
+### document.resolveService(query) ⇒ [Service
](#Service) \| undefined
+Returns the first [Service](#Service) with an `id` property matching the provided `query`,
+if present.
+
+**Kind**: instance method of [Document
](#Document)
+
+| Param | Type |
+| --- | --- |
+| query | [DIDUrl
](#DIDUrl) \| string
|
+
### document.methods() ⇒ [Array.<VerificationMethod>
](#VerificationMethod)
@@ -2252,28 +2281,28 @@ Returns a copy of the proof.
**Kind**: instance method of [Document
](#Document)
-### document.revokeCredentials(fragment, credentialIndices)
-If the document has a `RevocationBitmap` service identified by `fragment`,
+### document.revokeCredentials(serviceQuery, credentialIndices)
+If the document has a `RevocationBitmap` service identified by `serviceQuery`,
revoke all credentials with a revocationBitmapIndex in `credentialIndices`.
**Kind**: instance method of [Document
](#Document)
| Param | Type |
| --- | --- |
-| fragment | string
|
+| serviceQuery | [DIDUrl
](#DIDUrl) \| string
|
| credentialIndices | number
\| Array.<number>
|
-### document.unrevokeCredentials(fragment, credentialIndices)
-If the document has a `RevocationBitmap` service identified by `fragment`,
+### document.unrevokeCredentials(serviceQuery, credentialIndices)
+If the document has a `RevocationBitmap` service identified by `serviceQuery`,
unrevoke all credentials with a revocationBitmapIndex in `credentialIndices`.
**Kind**: instance method of [Document
](#Document)
| Param | Type |
| --- | --- |
-| fragment | string
|
+| serviceQuery | [DIDUrl
](#DIDUrl) \| string
|
| credentialIndices | number
\| Array.<number>
|
@@ -3591,6 +3620,7 @@ Deserializes a `PresentationValidationOptions` from a JSON object.
* [.validate(presentation, holder, issuers, options, fail_fast)](#PresentationValidator.validate)
* [.verifyPresentationSignature(presentation, holder, options)](#PresentationValidator.verifyPresentationSignature)
* [.checkStructure(presentation)](#PresentationValidator.checkStructure)
+ * [.extractHolder(presentation)](#PresentationValidator.extractHolder) ⇒ [DID
](#DID)
@@ -3628,7 +3658,7 @@ An error is returned whenever a validated condition is not satisfied.
| --- | --- |
| presentation | [Presentation
](#Presentation) |
| holder | [Document
](#Document) \| [ResolvedDocument
](#ResolvedDocument) |
-| issuers | [Array.<Document>
](#Document) \| [Array.<ResolvedDocument>
](#ResolvedDocument) |
+| issuers | Array.<(Document\|ResolvedDocument)>
|
| options | [PresentationValidationOptions
](#PresentationValidationOptions) |
| fail_fast | number
|
@@ -3663,6 +3693,21 @@ Validates the semantic structure of the `Presentation`.
| --- | --- |
| presentation | [Presentation
](#Presentation) |
+
+
+### PresentationValidator.extractHolder(presentation) ⇒ [DID
](#DID)
+Utility for extracting the holder field of a `Presentation` as a DID.
+
+### Errors
+
+Fails if the holder field is missing or not a valid DID.
+
+**Kind**: static method of [PresentationValidator
](#PresentationValidator)
+
+| Param | Type |
+| --- | --- |
+| presentation | [Presentation
](#Presentation) |
+
## Proof
@@ -4185,8 +4230,8 @@ according to the `fail_fast` parameter.
| presentation | [Presentation
](#Presentation) |
| options | [PresentationValidationOptions
](#PresentationValidationOptions) |
| fail_fast | number
|
-| holder | [ResolvedDocument
](#ResolvedDocument) \| undefined
|
-| issuers | [Array.<ResolvedDocument>
](#ResolvedDocument) \| undefined
|
+| holder | [Document
](#Document) \| [ResolvedDocument
](#ResolvedDocument) \| undefined
|
+| issuers | Array.<(Document\|ResolvedDocument)>
\| undefined
|
diff --git a/bindings/wasm/src/credential/credential_validator.rs b/bindings/wasm/src/credential/credential_validator.rs
index 8b26671556..2995e6539a 100644
--- a/bindings/wasm/src/credential/credential_validator.rs
+++ b/bindings/wasm/src/credential/credential_validator.rs
@@ -1,19 +1,21 @@
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
-use identity_iota::client::CredentialValidator;
use identity_iota::client::ResolvedIotaDocument;
-use identity_iota::client::StatusCheck;
-use identity_iota::client::ValidationError;
use identity_iota::core::Url;
+use identity_iota::credential::CredentialValidator;
+use identity_iota::credential::StatusCheck;
+use identity_iota::credential::ValidationError;
+use identity_iota::iota_core::IotaDID;
use identity_iota::iota_core::IotaDocument;
use wasm_bindgen::prelude::*;
use crate::common::WasmTimestamp;
use crate::credential::validation_options::WasmFailFast;
use crate::credential::validation_options::WasmStatusCheck;
-use crate::did::ArrayDocumentOrArrayResolvedDocument;
+use crate::did::ArrayDocumentOrResolvedDocument;
use crate::did::DocumentOrResolvedDocument;
+use crate::did::WasmDID;
use crate::did::WasmVerifierOptions;
use crate::error::Result;
use crate::error::WasmResult;
@@ -59,7 +61,7 @@ impl WasmCredentialValidator {
fail_fast: WasmFailFast,
) -> Result<()> {
let issuer_doc: ResolvedIotaDocument = issuer.into_serde().wasm_result()?;
- CredentialValidator::validate(&credential.0, &issuer_doc, &options.0, fail_fast.into()).wasm_result()
+ CredentialValidator::validate(&credential.0, &issuer_doc.document, &options.0, fail_fast.into()).wasm_result()
}
/// Validates the semantic structure of the `Credential`.
@@ -98,11 +100,11 @@ impl WasmCredentialValidator {
#[wasm_bindgen(js_name = verifySignature)]
pub fn verify_signature(
credential: &WasmCredential,
- trusted_issuers: &ArrayDocumentOrArrayResolvedDocument,
+ trusted_issuers: &ArrayDocumentOrResolvedDocument,
options: &WasmVerifierOptions,
) -> Result<()> {
- let trusted_issuers: Vec = trusted_issuers.into_serde().wasm_result()?;
- CredentialValidator::verify_signature(&credential.0, trusted_issuers.as_slice(), &options.0).wasm_result()
+ let issuers: Vec = trusted_issuers.into_serde().wasm_result()?;
+ CredentialValidator::verify_signature(&credential.0, &issuers, &options.0).wasm_result()
}
/// Validate that the relationship between the `holder` and the credential subjects is in accordance with
@@ -123,11 +125,22 @@ impl WasmCredentialValidator {
#[allow(non_snake_case)]
pub fn check_status(
credential: &WasmCredential,
- trustedIssuers: &ArrayDocumentOrArrayResolvedDocument,
+ trustedIssuers: &ArrayDocumentOrResolvedDocument,
statusCheck: WasmStatusCheck,
) -> Result<()> {
let trusted_issuers: Vec = trustedIssuers.into_serde().wasm_result()?;
let status_check: StatusCheck = StatusCheck::from(statusCheck);
- CredentialValidator::check_status(&credential.0, trusted_issuers.as_slice(), status_check).wasm_result()
+ CredentialValidator::check_status(&credential.0, &trusted_issuers, status_check).wasm_result()
+ }
+
+ /// Utility for extracting the issuer field of a `Credential` as a DID.
+ ///
+ /// ### Errors
+ ///
+ /// Fails if the issuer field is not a valid DID.
+ #[wasm_bindgen(js_name = extractIssuer)]
+ pub fn extract_issuer(credential: &WasmCredential) -> Result {
+ let did: IotaDID = CredentialValidator::extract_issuer(&credential.0).wasm_result()?;
+ Ok(WasmDID::from(did))
}
}
diff --git a/bindings/wasm/src/credential/presentation_validator.rs b/bindings/wasm/src/credential/presentation_validator.rs
index 61ca4e556b..df4cf58678 100644
--- a/bindings/wasm/src/credential/presentation_validator.rs
+++ b/bindings/wasm/src/credential/presentation_validator.rs
@@ -1,17 +1,20 @@
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
+use identity_iota::credential::PresentationValidator;
+use identity_iota::iota_core::IotaDID;
+use identity_iota::iota_core::IotaDocument;
+use wasm_bindgen::prelude::*;
+
use crate::credential::WasmFailFast;
use crate::credential::WasmPresentation;
use crate::credential::WasmPresentationValidationOptions;
-use crate::did::ArrayDocumentOrArrayResolvedDocument;
+use crate::did::ArrayDocumentOrResolvedDocument;
use crate::did::DocumentOrResolvedDocument;
+use crate::did::WasmDID;
use crate::did::WasmVerifierOptions;
use crate::error::Result;
use crate::error::WasmResult;
-use identity_iota::client::PresentationValidator;
-use identity_iota::client::ResolvedIotaDocument;
-use wasm_bindgen::prelude::*;
#[wasm_bindgen(js_name = PresentationValidator, inspectable)]
pub struct WasmPresentationValidator;
@@ -48,12 +51,12 @@ impl WasmPresentationValidator {
pub fn validate(
presentation: &WasmPresentation,
holder: &DocumentOrResolvedDocument,
- issuers: &ArrayDocumentOrArrayResolvedDocument,
+ issuers: &ArrayDocumentOrResolvedDocument,
options: &WasmPresentationValidationOptions,
fail_fast: WasmFailFast,
) -> Result<()> {
- let holder: ResolvedIotaDocument = holder.into_serde().wasm_result()?;
- let issuers: Vec = issuers.into_serde().wasm_result()?;
+ let holder: IotaDocument = holder.into_serde().wasm_result()?;
+ let issuers: Vec = issuers.into_serde().wasm_result()?;
PresentationValidator::validate(&presentation.0, &holder, &issuers, &options.0, fail_fast.into()).wasm_result()
}
@@ -71,7 +74,7 @@ impl WasmPresentationValidator {
holder: &DocumentOrResolvedDocument,
options: &WasmVerifierOptions,
) -> Result<()> {
- let holder: ResolvedIotaDocument = holder.into_serde().wasm_result()?;
+ let holder: IotaDocument = holder.into_serde().wasm_result()?;
PresentationValidator::verify_presentation_signature(&presentation.0, &holder, &options.0).wasm_result()
}
@@ -80,4 +83,15 @@ impl WasmPresentationValidator {
pub fn check_structure(presentation: &WasmPresentation) -> Result<()> {
PresentationValidator::check_structure(&presentation.0).wasm_result()
}
+
+ /// Utility for extracting the holder field of a `Presentation` as a DID.
+ ///
+ /// ### Errors
+ ///
+ /// Fails if the holder field is missing or not a valid DID.
+ #[wasm_bindgen(js_name = extractHolder)]
+ pub fn extract_holder(presentation: &WasmPresentation) -> Result {
+ let did: IotaDID = PresentationValidator::extract_holder(&presentation.0).wasm_result()?;
+ Ok(WasmDID::from(did))
+ }
}
diff --git a/bindings/wasm/src/credential/validation_options.rs b/bindings/wasm/src/credential/validation_options.rs
index 7b0958c367..be2f9174ec 100644
--- a/bindings/wasm/src/credential/validation_options.rs
+++ b/bindings/wasm/src/credential/validation_options.rs
@@ -1,11 +1,11 @@
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
-use identity_iota::client::CredentialValidationOptions;
-use identity_iota::client::FailFast;
-use identity_iota::client::PresentationValidationOptions;
-use identity_iota::client::StatusCheck;
-use identity_iota::client::SubjectHolderRelationship;
+use identity_iota::credential::CredentialValidationOptions;
+use identity_iota::credential::FailFast;
+use identity_iota::credential::PresentationValidationOptions;
+use identity_iota::credential::StatusCheck;
+use identity_iota::credential::SubjectHolderRelationship;
use serde_repr::Deserialize_repr;
use serde_repr::Serialize_repr;
use wasm_bindgen::prelude::*;
diff --git a/bindings/wasm/src/did/mod.rs b/bindings/wasm/src/did/mod.rs
index 509133e3c8..37910196ea 100644
--- a/bindings/wasm/src/did/mod.rs
+++ b/bindings/wasm/src/did/mod.rs
@@ -13,7 +13,7 @@ pub use self::wasm_method_scope::OptionMethodScope;
pub use self::wasm_method_scope::RefMethodScope;
pub use self::wasm_method_scope::WasmMethodScope;
pub use self::wasm_method_type::WasmMethodType;
-pub use self::wasm_resolved_document::ArrayDocumentOrArrayResolvedDocument;
+pub use self::wasm_resolved_document::ArrayDocumentOrResolvedDocument;
pub use self::wasm_resolved_document::ArrayResolvedDocument;
pub use self::wasm_resolved_document::DocumentOrResolvedDocument;
pub use self::wasm_resolved_document::PromiseArrayResolvedDocument;
diff --git a/bindings/wasm/src/did/wasm_document.rs b/bindings/wasm/src/did/wasm_document.rs
index 7aee10ed4a..5f95d3e10e 100644
--- a/bindings/wasm/src/did/wasm_document.rs
+++ b/bindings/wasm/src/did/wasm_document.rs
@@ -13,15 +13,15 @@ use identity_iota::credential::Presentation;
use identity_iota::crypto::PrivateKey;
use identity_iota::crypto::ProofOptions;
use identity_iota::did::verifiable::VerifiableProperties;
+use identity_iota::did::Document;
use identity_iota::did::MethodScope;
-use identity_iota::did::DID;
use identity_iota::iota_core::IotaDID;
-use identity_iota::iota_core::IotaDIDUrl;
use identity_iota::iota_core::IotaDocument;
use identity_iota::iota_core::IotaVerificationMethod;
use identity_iota::iota_core::MessageId;
use identity_iota::iota_core::NetworkName;
use wasm_bindgen::prelude::*;
+use wasm_bindgen::JsCast;
use crate::account::wasm_account::UOneOrManyNumber;
use crate::common::MapStringAny;
@@ -44,7 +44,6 @@ use crate::did::WasmVerificationMethod;
use crate::did::WasmVerifierOptions;
use crate::error::Result;
use crate::error::WasmResult;
-use wasm_bindgen::JsCast;
// =============================================================================
// =============================================================================
@@ -229,6 +228,14 @@ impl WasmDocument {
self.0.remove_service(&did.0)
}
+ /// Returns the first {@link Service} with an `id` property matching the provided `query`,
+ /// if present.
+ #[wasm_bindgen(js_name = resolveService)]
+ pub fn resolve_service(&self, query: &UDIDUrlQuery) -> Option {
+ let service_query: String = query.into_serde().ok()?;
+ self.0.resolve_service(&service_query).cloned().map(WasmService::from)
+ }
+
// ===========================================================================
// Verification Methods
// ===========================================================================
@@ -631,33 +638,35 @@ impl WasmDocument {
self.0.proof.clone().map(WasmProof::from)
}
- /// If the document has a `RevocationBitmap` service identified by `fragment`,
+ /// If the document has a `RevocationBitmap` service identified by `serviceQuery`,
/// revoke all credentials with a revocationBitmapIndex in `credentialIndices`.
#[wasm_bindgen(js_name = revokeCredentials)]
#[allow(non_snake_case)]
- pub fn revoke_credentials(&mut self, fragment: &str, credentialIndices: UOneOrManyNumber) -> Result<()> {
+ pub fn revoke_credentials(&mut self, serviceQuery: &UDIDUrlQuery, credentialIndices: UOneOrManyNumber) -> Result<()> {
+ let query: String = serviceQuery.into_serde().wasm_result()?;
let credentials_indices: OneOrMany = credentialIndices.into_serde().wasm_result()?;
- let mut service_id: IotaDIDUrl = self.0.id().to_url();
- service_id.set_fragment(Some(fragment)).wasm_result()?;
self
.0
- .revoke_credentials(&service_id, credentials_indices.as_slice())
+ .revoke_credentials(&query, credentials_indices.as_slice())
.wasm_result()
}
- /// If the document has a `RevocationBitmap` service identified by `fragment`,
+ /// If the document has a `RevocationBitmap` service identified by `serviceQuery`,
/// unrevoke all credentials with a revocationBitmapIndex in `credentialIndices`.
#[wasm_bindgen(js_name = unrevokeCredentials)]
#[allow(non_snake_case)]
- pub fn unrevoke_credentials(&mut self, fragment: &str, credentialIndices: UOneOrManyNumber) -> Result<()> {
+ pub fn unrevoke_credentials(
+ &mut self,
+ serviceQuery: &UDIDUrlQuery,
+ credentialIndices: UOneOrManyNumber,
+ ) -> Result<()> {
+ let query: String = serviceQuery.into_serde().wasm_result()?;
let credentials_indices: OneOrMany = credentialIndices.into_serde().wasm_result()?;
- let mut service_id: IotaDIDUrl = self.0.id().to_url();
- service_id.set_fragment(Some(fragment)).wasm_result()?;
self
.0
- .unrevoke_credentials(&service_id, credentials_indices.as_slice())
+ .unrevoke_credentials(&query, credentials_indices.as_slice())
.wasm_result()
}
@@ -691,6 +700,7 @@ impl From for IotaDocument {
wasm_document.0
}
}
+
/// Duck-typed union to pass either a string or WasmDIDUrl as a parameter.
#[wasm_bindgen]
extern "C" {
diff --git a/bindings/wasm/src/did/wasm_resolved_document.rs b/bindings/wasm/src/did/wasm_resolved_document.rs
index c90768b886..e7b4e3208a 100644
--- a/bindings/wasm/src/did/wasm_resolved_document.rs
+++ b/bindings/wasm/src/did/wasm_resolved_document.rs
@@ -35,8 +35,8 @@ extern "C" {
#[wasm_bindgen(typescript_type = "Document | ResolvedDocument")]
pub type DocumentOrResolvedDocument;
- #[wasm_bindgen(typescript_type = "Array | Array")]
- pub type ArrayDocumentOrArrayResolvedDocument;
+ #[wasm_bindgen(typescript_type = "Array")]
+ pub type ArrayDocumentOrResolvedDocument;
}
#[wasm_bindgen(js_class = ResolvedDocument)]
diff --git a/bindings/wasm/src/error.rs b/bindings/wasm/src/error.rs
index 296b42b315..ed0cbf80cb 100644
--- a/bindings/wasm/src/error.rs
+++ b/bindings/wasm/src/error.rs
@@ -97,7 +97,7 @@ impl_wasm_error_from!(
identity_iota::did::Error,
identity_iota::did::DIDError,
identity_iota::iota_core::Error,
- identity_iota::client::ValidationError
+ identity_iota::credential::ValidationError
);
// Similar to `impl_wasm_error_from`, but uses the types name instead of requiring/calling Into &'static str
@@ -177,8 +177,8 @@ impl From for WasmError<'_> {
}
}
-impl From for WasmError<'_> {
- fn from(error: identity_iota::client::CompoundCredentialValidationError) -> Self {
+impl From for WasmError<'_> {
+ fn from(error: identity_iota::credential::CompoundCredentialValidationError) -> Self {
Self {
name: Cow::Borrowed("CompoundCredentialValidationError"),
message: Cow::Owned(error.to_string()),
@@ -186,8 +186,8 @@ impl From for WasmErro
}
}
-impl From for WasmError<'_> {
- fn from(error: identity_iota::client::CompoundPresentationValidationError) -> Self {
+impl From for WasmError<'_> {
+ fn from(error: identity_iota::credential::CompoundPresentationValidationError) -> Self {
Self {
name: Cow::Borrowed("CompoundPresentationValidationError"),
message: Cow::Owned(error.to_string()),
diff --git a/bindings/wasm/src/tangle/resolver.rs b/bindings/wasm/src/tangle/resolver.rs
index d2c6b83ba8..531f6ce550 100644
--- a/bindings/wasm/src/tangle/resolver.rs
+++ b/bindings/wasm/src/tangle/resolver.rs
@@ -10,8 +10,10 @@ use identity_iota::client::ClientBuilder;
use identity_iota::client::ResolvedIotaDocument;
use identity_iota::client::Resolver;
use identity_iota::client::ResolverBuilder;
-use identity_iota::core::Url;
+use identity_iota::credential::PresentationValidator;
+use identity_iota::credential::ValidatorDocument;
use identity_iota::iota_core::IotaDID;
+use identity_iota::iota_core::IotaDocument;
use identity_iota::iota_core::NetworkName;
use js_sys::Promise;
use wasm_bindgen::prelude::*;
@@ -27,7 +29,8 @@ use crate::credential::WasmCredential;
use crate::credential::WasmFailFast;
use crate::credential::WasmPresentation;
use crate::credential::WasmPresentationValidationOptions;
-use crate::did::ArrayResolvedDocument;
+use crate::did::ArrayDocumentOrResolvedDocument;
+use crate::did::DocumentOrResolvedDocument;
use crate::did::PromiseArrayResolvedDocument;
use crate::did::PromiseResolvedDocument;
use crate::did::UWasmDID;
@@ -45,10 +48,6 @@ extern "C" {
// Workaround for Typescript type annotations on async function returns.
#[wasm_bindgen(typescript_type = "Promise")]
pub type PromiseResolver;
-
- // Workaround for lack of &Option/Option<&Type> support.
- #[wasm_bindgen(typescript_type = "ResolvedDocument")]
- pub type RefResolvedDocument;
}
#[wasm_bindgen(js_class = Resolver)]
@@ -215,14 +214,7 @@ impl WasmResolver {
pub fn resolve_presentation_holder(&self, presentation: &WasmPresentation) -> Result {
// TODO: reimplemented function to avoid cloning the entire presentation.
// Would be solved with Rc internal representation, pending memory leak discussions.
- let holder_url: &Url = presentation
- .0
- .holder
- .as_ref()
- .ok_or(identity_iota::client::ValidationError::MissingPresentationHolder)
- .map_err(identity_iota::client::Error::from)
- .wasm_result()?;
- let holder: IotaDID = IotaDID::parse(holder_url.as_str()).wasm_result()?;
+ let holder: IotaDID = PresentationValidator::extract_holder(&presentation.0).wasm_result()?;
let resolver: Rc>> = Rc::clone(&self.0);
let promise: Promise = future_to_promise(async move {
@@ -260,25 +252,29 @@ impl WasmResolver {
presentation: &WasmPresentation,
options: &WasmPresentationValidationOptions,
fail_fast: WasmFailFast,
- holder: Option,
- issuers: Option,
+ holder: Option,
+ issuers: Option,
) -> Result {
// TODO: reimplemented function to avoid cloning the entire presentation and validation options.
// Would be solved with Rc internal representation, pending memory leak discussions.
- let holder: Option = holder.map(|js| js.into_serde().wasm_result()).transpose()?;
- let issuers: Option> = issuers.map(|js| js.into_serde().wasm_result()).transpose()?;
+ let holder: Option = holder.map(|js| js.into_serde().wasm_result()).transpose()?;
+ let issuers: Option> = issuers.map(|js| js.into_serde().wasm_result()).transpose()?;
+
let resolver: Rc>> = Rc::clone(&self.0);
let presentation: WasmPresentation = presentation.clone();
let options: WasmPresentationValidationOptions = options.clone();
let promise: Promise = future_to_promise(async move {
+ let issuer_refs: Option> = issuers
+ .as_ref()
+ .map(|issuers| issuers.iter().map(ValidatorDocument::as_validator).collect());
resolver
.verify_presentation(
&presentation.0,
&options.0,
fail_fast.into(),
- holder.as_ref(),
- issuers.as_deref(),
+ holder.as_ref().map(ValidatorDocument::as_validator),
+ issuer_refs.as_deref(),
)
.await
.map(|_| JsValue::UNDEFINED)
diff --git a/examples/account/create_vc.rs b/examples/account/create_vc.rs
index 760e2c6c96..fdf432ae43 100644
--- a/examples/account/create_vc.rs
+++ b/examples/account/create_vc.rs
@@ -12,16 +12,15 @@ use identity_iota::account::AccountBuilder;
use identity_iota::account::IdentitySetup;
use identity_iota::account::MethodContent;
use identity_iota::account::Result;
-
-use identity_iota::client::CredentialValidationOptions;
-use identity_iota::client::CredentialValidator;
-use identity_iota::client::FailFast;
use identity_iota::core::json;
use identity_iota::core::FromJson;
use identity_iota::core::ToJson;
use identity_iota::core::Url;
use identity_iota::credential::Credential;
use identity_iota::credential::CredentialBuilder;
+use identity_iota::credential::CredentialValidationOptions;
+use identity_iota::credential::CredentialValidator;
+use identity_iota::credential::FailFast;
use identity_iota::credential::Subject;
use identity_iota::crypto::ProofOptions;
use identity_iota::did::DID;
@@ -79,7 +78,7 @@ pub async fn create_vc() -> Result {
// that the issuance date is not in the future and that the expiration date is not in the past:
CredentialValidator::validate(
&credential,
- &issuer.document(),
+ issuer.document(),
&CredentialValidationOptions::default(),
FailFast::FirstError,
)
diff --git a/examples/account/create_vp.rs b/examples/account/create_vp.rs
index 091afef443..8534195661 100644
--- a/examples/account/create_vp.rs
+++ b/examples/account/create_vp.rs
@@ -10,6 +10,8 @@ use identity_iota::account::Account;
use identity_iota::account::AccountBuilder;
use identity_iota::account::IdentitySetup;
use identity_iota::account::MethodContent;
+use identity_iota::account::Result;
+use identity_iota::client::Resolver;
use identity_iota::core::json;
use identity_iota::core::Duration;
use identity_iota::core::FromJson;
@@ -18,20 +20,16 @@ use identity_iota::core::ToJson;
use identity_iota::core::Url;
use identity_iota::credential::Credential;
use identity_iota::credential::CredentialBuilder;
+use identity_iota::credential::CredentialValidationOptions;
+use identity_iota::credential::FailFast;
use identity_iota::credential::Presentation;
use identity_iota::credential::PresentationBuilder;
+use identity_iota::credential::PresentationValidationOptions;
use identity_iota::credential::Subject;
+use identity_iota::credential::SubjectHolderRelationship;
use identity_iota::crypto::ProofOptions;
use identity_iota::did::verifiable::VerifierOptions;
-use identity_iota::account::Result;
-use identity_iota::client::CredentialValidationOptions;
-use identity_iota::client::FailFast;
-use identity_iota::client::PresentationValidationOptions;
-
-use identity_iota::client::Resolver;
-use identity_iota::client::SubjectHolderRelationship;
-
#[tokio::main]
async fn main() -> Result<()> {
// ===========================================================================
diff --git a/examples/account/revoke_vc.rs b/examples/account/revoke_vc.rs
index 4518a9a98b..3154c345ea 100644
--- a/examples/account/revoke_vc.rs
+++ b/examples/account/revoke_vc.rs
@@ -17,19 +17,20 @@ use identity_iota::account::AccountBuilder;
use identity_iota::account::IdentitySetup;
use identity_iota::account::MethodContent;
use identity_iota::account::Result;
-use identity_iota::client::CredentialValidationOptions;
-use identity_iota::client::CredentialValidator;
use identity_iota::client::ResolvedIotaDocument;
use identity_iota::client::Resolver;
-use identity_iota::client::ValidationError;
use identity_iota::core::json;
use identity_iota::core::FromJson;
use identity_iota::core::Url;
use identity_iota::credential::Credential;
use identity_iota::credential::CredentialBuilder;
+use identity_iota::credential::CredentialValidationOptions;
+use identity_iota::credential::CredentialValidator;
+use identity_iota::credential::FailFast;
use identity_iota::credential::RevocationBitmapStatus;
use identity_iota::credential::Status;
use identity_iota::credential::Subject;
+use identity_iota::credential::ValidationError;
use identity_iota::crypto::ProofOptions;
use identity_iota::did::RevocationBitmap;
use identity_iota::did::DID;
@@ -97,9 +98,9 @@ async fn main() -> Result<()> {
let validation_result = CredentialValidator::validate(
&credential,
- &issuer.document(),
+ issuer.document(),
&CredentialValidationOptions::default(),
- identity_iota::client::FailFast::FirstError,
+ FailFast::FirstError,
);
// The credential wasn't revoked, so we expect the validation to succeed.
@@ -117,9 +118,9 @@ async fn main() -> Result<()> {
let validation_result = CredentialValidator::validate(
&credential,
- &issuer.document(),
+ issuer.document(),
&CredentialValidationOptions::default(),
- identity_iota::client::FailFast::FirstError,
+ FailFast::FirstError,
);
// We expect validation to no longer succeed because the credential was revoked.
@@ -146,9 +147,9 @@ async fn main() -> Result<()> {
let resolved_issuer_doc: ResolvedIotaDocument = resolver.resolve_credential_issuer(&credential).await?;
let validation_result = CredentialValidator::validate(
&credential,
- &resolved_issuer_doc,
+ &resolved_issuer_doc.document,
&CredentialValidationOptions::default(),
- identity_iota::client::FailFast::FirstError,
+ FailFast::FirstError,
);
println!("VC validation result: {:?}", validation_result);
diff --git a/identity_account_storage/src/storage/memstore.rs b/identity_account_storage/src/storage/memstore.rs
index b404830e36..48303a220d 100644
--- a/identity_account_storage/src/storage/memstore.rs
+++ b/identity_account_storage/src/storage/memstore.rs
@@ -549,6 +549,7 @@ impl Default for MemStore {
}
#[cfg(test)]
+#[cfg(feature = "storage-test-suite")]
mod tests {
use crate::storage::Storage;
use crate::storage::StorageTestSuite;
diff --git a/identity_account_storage/src/stronghold/tests.rs b/identity_account_storage/src/stronghold/tests.rs
index 00025a2321..e4b47acdb5 100644
--- a/identity_account_storage/src/stronghold/tests.rs
+++ b/identity_account_storage/src/stronghold/tests.rs
@@ -1,6 +1,12 @@
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
+use iota_stronghold::procedures;
+use iota_stronghold::procedures::GenerateKey;
+use iota_stronghold::Client;
+use iota_stronghold::ClientVault;
+use iota_stronghold::Location;
+
use identity_core::crypto::KeyPair;
use identity_core::crypto::KeyType;
use identity_core::crypto::PrivateKey;
@@ -9,11 +15,6 @@ use identity_core::crypto::X25519;
use identity_core::utils::Base::Base16Lower;
use identity_core::utils::BaseEncoding;
use identity_iota_core::did::IotaDID;
-use iota_stronghold::procedures;
-use iota_stronghold::procedures::GenerateKey;
-use iota_stronghold::Client;
-use iota_stronghold::ClientVault;
-use iota_stronghold::Location;
use crate::storage::stronghold::aead_decrypt;
use crate::storage::stronghold::aead_encrypt;
@@ -23,8 +24,6 @@ use crate::storage::stronghold::generate_private_key;
use crate::storage::stronghold::insert_private_key;
use crate::storage::stronghold::random_location;
use crate::storage::stronghold::retrieve_public_key;
-use crate::storage::Storage;
-use crate::storage::StorageTestSuite;
use crate::stronghold::test_util::random_did;
use crate::stronghold::test_util::random_key_location;
use crate::stronghold::test_util::random_string;
@@ -190,74 +189,82 @@ async fn test_ecdhes_encryption() {
assert_eq!(plaintext.to_vec(), data);
}
-async fn test_stronghold() -> impl Storage {
- Stronghold::new(&random_temporary_path(), random_string(), Some(false))
- .await
- .unwrap()
-}
-
-#[tokio::test]
-async fn test_stronghold_did_create_with_private_key() {
- StorageTestSuite::did_create_private_key_test(test_stronghold().await)
- .await
- .unwrap()
-}
-
-#[tokio::test]
-async fn test_stronghold_did_create_generate_key() {
- StorageTestSuite::did_create_generate_key_test(test_stronghold().await)
- .await
- .unwrap()
-}
-
-#[tokio::test]
-async fn test_stronghold_key_generate() {
- StorageTestSuite::key_generate_test(test_stronghold().await)
- .await
- .unwrap()
-}
-
-#[tokio::test]
-async fn test_stronghold_key_delete() {
- StorageTestSuite::key_delete_test(test_stronghold().await)
- .await
- .unwrap()
-}
-
-#[tokio::test]
-async fn test_stronghold_did_list() {
- StorageTestSuite::did_list_test(test_stronghold().await).await.unwrap()
-}
-
-#[tokio::test]
-async fn test_stronghold_key_insert() {
- StorageTestSuite::key_insert_test(test_stronghold().await)
- .await
- .unwrap()
-}
-
-#[tokio::test]
-async fn test_stronghold_key_sign_ed25519() {
- StorageTestSuite::key_sign_ed25519_test(test_stronghold().await)
- .await
- .unwrap()
-}
-
-#[tokio::test]
-async fn test_stronghold_key_value_store() {
- StorageTestSuite::key_value_store_test(test_stronghold().await)
- .await
- .unwrap()
-}
-
-#[tokio::test]
-async fn test_stronghold_did_purge() {
- StorageTestSuite::did_purge_test(test_stronghold().await).await.unwrap()
-}
-
-#[tokio::test]
-async fn test_stronghold_encryption() {
- StorageTestSuite::encryption_test(test_stronghold().await, test_stronghold().await)
- .await
- .unwrap()
+#[cfg(feature = "storage-test-suite")]
+mod stronghold_storage_test_suite {
+ use crate::storage::Storage;
+ use crate::storage::StorageTestSuite;
+
+ use super::*;
+
+ async fn test_stronghold() -> impl Storage {
+ Stronghold::new(&random_temporary_path(), random_string(), Some(false))
+ .await
+ .unwrap()
+ }
+
+ #[tokio::test]
+ async fn test_stronghold_did_create_with_private_key() {
+ StorageTestSuite::did_create_private_key_test(test_stronghold().await)
+ .await
+ .unwrap()
+ }
+
+ #[tokio::test]
+ async fn test_stronghold_did_create_generate_key() {
+ StorageTestSuite::did_create_generate_key_test(test_stronghold().await)
+ .await
+ .unwrap()
+ }
+
+ #[tokio::test]
+ async fn test_stronghold_key_generate() {
+ StorageTestSuite::key_generate_test(test_stronghold().await)
+ .await
+ .unwrap()
+ }
+
+ #[tokio::test]
+ async fn test_stronghold_key_delete() {
+ StorageTestSuite::key_delete_test(test_stronghold().await)
+ .await
+ .unwrap()
+ }
+
+ #[tokio::test]
+ async fn test_stronghold_did_list() {
+ StorageTestSuite::did_list_test(test_stronghold().await).await.unwrap()
+ }
+
+ #[tokio::test]
+ async fn test_stronghold_key_insert() {
+ StorageTestSuite::key_insert_test(test_stronghold().await)
+ .await
+ .unwrap()
+ }
+
+ #[tokio::test]
+ async fn test_stronghold_key_sign_ed25519() {
+ StorageTestSuite::key_sign_ed25519_test(test_stronghold().await)
+ .await
+ .unwrap()
+ }
+
+ #[tokio::test]
+ async fn test_stronghold_key_value_store() {
+ StorageTestSuite::key_value_store_test(test_stronghold().await)
+ .await
+ .unwrap()
+ }
+
+ #[tokio::test]
+ async fn test_stronghold_did_purge() {
+ StorageTestSuite::did_purge_test(test_stronghold().await).await.unwrap()
+ }
+
+ #[tokio::test]
+ async fn test_stronghold_encryption() {
+ StorageTestSuite::encryption_test(test_stronghold().await, test_stronghold().await)
+ .await
+ .unwrap()
+ }
}
diff --git a/identity_core/src/crypto/proof/jcs_ed25519.rs b/identity_core/src/crypto/proof/jcs_ed25519.rs
index b41d586c25..272fb39dac 100644
--- a/identity_core/src/crypto/proof/jcs_ed25519.rs
+++ b/identity_core/src/crypto/proof/jcs_ed25519.rs
@@ -56,7 +56,7 @@ where
{
fn verify(data: &X, signature: &ProofValue, public: &T::Public) -> Result<()>
where
- X: Serialize,
+ X: Serialize + ?Sized,
{
let signature: &str = signature
.as_signature()
diff --git a/identity_core/src/crypto/signature/core.rs b/identity_core/src/crypto/signature/core.rs
index c5adbb2664..ea97561f99 100644
--- a/identity_core/src/crypto/signature/core.rs
+++ b/identity_core/src/crypto/signature/core.rs
@@ -73,17 +73,17 @@ pub trait Signer: Named {
// =============================================================================
// =============================================================================
-/// A common interface for digital signature verification
+/// A common interface for digital signature verification.
pub trait Verifier: Named {
/// Verifies the authenticity of `data` and `signature`.
fn verify(data: &T, signature: &ProofValue, public: &Public) -> Result<()>
where
- T: Serialize;
+ T: Serialize + ?Sized;
/// Extracts and verifies a proof [signature][`Proof`] from the given `data`.
fn verify_signature(data: &T, public: &Public) -> Result<()>
where
- T: Serialize + GetSignature,
+ T: Serialize + GetSignature + ?Sized,
{
let signature: &Proof = data.signature().ok_or(Error::MissingSignature)?;
@@ -93,7 +93,7 @@ pub trait Verifier: Named {
signature.hide_value();
- Self::verify(&data, signature.value(), public)?;
+ Self::verify(data, signature.value(), public)?;
signature.show_value();
diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml
index 520fc407d3..4e386489c7 100644
--- a/identity_credential/Cargo.toml
+++ b/identity_credential/Cargo.toml
@@ -8,17 +8,22 @@ keywords = ["iota", "tangle", "identity"]
license = "Apache-2.0"
readme = "./README.md"
repository = "https://github.com/iotaledger/identity.rs"
+rust-version = "1.60"
description = "An implementation of the Verifiable Credentials standard."
[dependencies]
+erased-serde = { version = "0.3.21", default-features = false, features = ["std"], optional = true }
identity_core = { version = "=0.6.0", path = "../identity_core", default-features = false }
identity_did = { version = "=0.6.0", path = "../identity_did", default-features = false }
+itertools = { version = "0.10", default-features = false, features = ["use_std"], optional = true }
lazy_static = { version = "1.4", default-features = false }
serde = { version = "1.0", default-features = false, features = ["std", "derive"] }
+serde_repr = { version = "0.1", default-features = false, optional = true }
strum = { version = "0.24.0", default-features = false, features = ["std", "derive"] }
thiserror = { version = "1.0", default-features = false }
[dev-dependencies]
+proptest = { version = "1.0.0", default-features = false, features = ["std"] }
serde_json = { version = "1.0", default-features = false }
[package.metadata.docs.rs]
@@ -28,5 +33,6 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[features]
-default = ["revocation-bitmap"]
-revocation-bitmap = []
+default = ["revocation-bitmap", "validator"]
+revocation-bitmap = ["identity_did/revocation-bitmap"]
+validator = ["dep:itertools", "dep:erased-serde", "dep:serde_repr"]
diff --git a/identity_credential/README.md b/identity_credential/README.md
index b7fa06a6d2..715b84c59b 100644
--- a/identity_credential/README.md
+++ b/identity_credential/README.md
@@ -5,6 +5,11 @@ This crate contains types representing verifiable credentials and verifiable pre
The [IOTA Identity Framework Wiki](https://wiki.iota.org/identity.rs/concepts/verifiable_credentials/overview) provides an overview of verifiable credentials and demonstrates how they may be constructed using the building blocks from this crate.
+Convenience methods for validating [Verifiable Credentials](https://wiki.iota.org/identity.rs/concepts/verifiable_credentials/overview) and [Verifiable Presentations](https://wiki.iota.org/identity.rs/concepts/verifiable_credentials/verifiable_presentations) are also provided:
+
+- [`CredentialValidator`](crate::validator::CredentialValidator)
+- [`PresentationValidator`](crate::validator::PresentationValidator)
+
## Construction
This crate follows the [builder pattern](https://rust-unofficial.github.io/patterns/patterns/creational/builder.html) for the creation of [`Credentials`](crate::credential::Credential) and [`Presentations`](crate::presentation::Presentation).
@@ -158,4 +163,4 @@ let presentation_json: &'static str = r#"{
"#;
let presentation: Presentation = serde_json::from_str(presentation_json).unwrap();
- ```
\ No newline at end of file
+```
diff --git a/identity_credential/src/credential/revocation_bitmap_status.rs b/identity_credential/src/credential/revocation_bitmap_status.rs
index ed8a295fc7..e26f0f59a1 100644
--- a/identity_credential/src/credential/revocation_bitmap_status.rs
+++ b/identity_credential/src/credential/revocation_bitmap_status.rs
@@ -13,13 +13,14 @@ use crate::credential::Status;
use crate::error::Error;
use crate::error::Result;
-/// Information used to determine the current status of a [`Credential`][crate::credential::Credential].
+/// Information used to determine the current status of a [`Credential`][crate::credential::Credential]
+/// using the `RevocationBitmap2022` specification.
#[derive(Clone, Debug, PartialEq)]
pub struct RevocationBitmapStatus(Status);
impl RevocationBitmapStatus {
const INDEX_PROPERTY_NAME: &'static str = "revocationBitmapIndex";
- /// The type name of the revocation bitmap.
+ /// Type name of the revocation bitmap.
pub const TYPE: &'static str = "RevocationBitmap2022";
/// Creates a new `RevocationBitmapStatus`.
@@ -33,7 +34,8 @@ impl RevocationBitmapStatus {
))
}
- /// Returns the [`DIDUrl`] of the bitmap status.
+ /// Returns the [`DIDUrl`] of the `RevocationBitmapStatus`, which should resolve
+ /// to a `RevocationBitmap2022` service in a DID Document.
pub fn id(&self) -> Result> {
DIDUrl::parse(self.0.id.as_str())
.map_err(|err| Error::InvalidStatus(format!("invalid DID Url '{}': {:?}", self.0.id, err)))
diff --git a/identity_credential/src/error.rs b/identity_credential/src/error.rs
index bd5ca9da2e..f713166b62 100644
--- a/identity_credential/src/error.rs
+++ b/identity_credential/src/error.rs
@@ -6,23 +6,23 @@
/// Alias for a `Result` with the error type [`Error`].
pub type Result = ::core::result::Result;
-/// This type represents all possible errors that can occur in the library.
+/// This type represents errors that can occur when constructing credentials and presentations.
#[derive(Debug, thiserror::Error, strum::IntoStaticStr)]
pub enum Error {
- /// Caused when validating a Credential without a valid base context.
- #[error("Missing Base Context")]
+ /// Caused when constructing a credential or presentation without a valid base context.
+ #[error("missing base context")]
MissingBaseContext,
- /// Caused when validating a Credential without a valid base type.
- #[error("Missing Base Type")]
+ /// Caused when constructing a credential or presentation without a valid base type.
+ #[error("missing base type")]
MissingBaseType,
- /// Caused when validating a Credential without an issuer.
- #[error("Missing Credential Issuer")]
+ /// Caused when constructing a credential without an issuer.
+ #[error("missing credential issuer")]
MissingIssuer,
- /// Caused when validating a Credential without a subject.
- #[error("Missing Credential Subject")]
+ /// Caused when constructing a credential without a subject.
+ #[error("missing Credential subject")]
MissingSubject,
- /// Caused when validating a Credential with a malformed subject.
- #[error("Invalid Credential Subject")]
+ /// Caused when constructing a credential with a malformed subject.
+ #[error("invalid credential subject")]
InvalidSubject,
/// Caused when trying to construct an invalid status.
#[error("invalid credential status: {0}")]
diff --git a/identity_credential/src/lib.rs b/identity_credential/src/lib.rs
index 454447edf7..11e39cce7f 100644
--- a/identity_credential/src/lib.rs
+++ b/identity_credential/src/lib.rs
@@ -3,7 +3,6 @@
#![forbid(unsafe_code)]
#![doc = include_str!("./../README.md")]
-#![allow(clippy::upper_case_acronyms)]
#![warn(
rust_2018_idioms,
unreachable_pub,
@@ -18,13 +17,15 @@
#[macro_use]
extern crate lazy_static;
-
#[macro_use]
extern crate serde;
+pub use self::error::Error;
+pub use self::error::Result;
+
pub mod credential;
pub mod error;
pub mod presentation;
-pub use self::error::Error;
-pub use self::error::Result;
+#[cfg(feature = "validator")]
+pub mod validator;
diff --git a/identity_iota_client/src/credential/credential_validator.rs b/identity_credential/src/validator/credential_validator.rs
similarity index 84%
rename from identity_iota_client/src/credential/credential_validator.rs
rename to identity_credential/src/validator/credential_validator.rs
index e6bda891db..81cb80eca9 100644
--- a/identity_iota_client/src/credential/credential_validator.rs
+++ b/identity_credential/src/validator/credential_validator.rs
@@ -1,23 +1,23 @@
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
+use std::str::FromStr;
+
use serde::Serialize;
use identity_core::common::OneOrMany;
use identity_core::common::Timestamp;
use identity_core::common::Url;
-use identity_credential::credential::Credential;
-#[cfg(feature = "revocation-bitmap")]
-use identity_credential::credential::RevocationBitmapStatus;
+use identity_did::did::CoreDID;
+use identity_did::did::CoreDIDUrl;
+use identity_did::did::DID;
#[cfg(feature = "revocation-bitmap")]
use identity_did::revocation::RevocationBitmap;
use identity_did::verifiable::VerifierOptions;
-use identity_iota_core::did::IotaDID;
-#[cfg(feature = "revocation-bitmap")]
-use identity_iota_core::did::IotaDIDUrl;
-use identity_iota_core::document::IotaDocument;
-use crate::Result;
+use crate::credential::Credential;
+#[cfg(feature = "revocation-bitmap")]
+use crate::credential::RevocationBitmapStatus;
use super::errors::CompoundCredentialValidationError;
use super::errors::SignerContext;
@@ -27,6 +27,7 @@ use super::validation_options::StatusCheck;
use super::CredentialValidationOptions;
use super::FailFast;
use super::SubjectHolderRelationship;
+use super::ValidatorDocument;
/// A struct for validating [`Credential`]s.
#[derive(Debug, Clone)]
@@ -51,9 +52,7 @@ impl CredentialValidator {
/// calling this method.
///
/// ## The state of the issuer's DID Document
- /// The caller must ensure that `issuer` represents an up-to-date DID Document. The convenience method
- /// [`Resolver::resolve_credential_issuer`](crate::tangle::Resolver::resolve_credential_issuer()) can help extract
- /// the latest available state of the issuer's DID Document.
+ /// The caller must ensure that `issuer` represents an up-to-date DID Document.
///
/// ## Properties that are not validated
/// There are many properties defined in [The Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) that are **not** validated, such as:
@@ -62,9 +61,9 @@ impl CredentialValidator {
///
/// # Errors
/// An error is returned whenever a validated condition is not satisfied.
- pub fn validate>(
+ pub fn validate(
credential: &Credential,
- issuer: &D,
+ issuer: &DOC,
options: &CredentialValidationOptions,
fail_fast: FailFast,
) -> CredentialValidationResult {
@@ -107,42 +106,24 @@ impl CredentialValidator {
/// This method immediately returns an error if
/// the credential issuer' url cannot be parsed to a DID belonging to one of the trusted issuers. Otherwise an attempt
/// to verify the credential's signature will be made and an error is returned upon failure.
- pub fn verify_signature>(
+ pub fn verify_signature(
credential: &Credential,
- trusted_issuers: &[D],
+ trusted_issuers: &[DOC],
options: &VerifierOptions,
) -> ValidationUnitResult {
- // try to extract the corresponding issuer from `trusted_issuers`
- let extracted_issuer_result: std::result::Result<&IotaDocument, ValidationError> = {
- let issuer_did: Result = credential.issuer.url().as_str().parse().map_err(Into::into);
- match issuer_did {
- Ok(did) => {
- // if the issuer_did corresponds to one of the trusted issuers we use the corresponding DID Document to verify
- // the signature
- trusted_issuers
- .iter()
- .map(|issuer_doc| issuer_doc.as_ref())
- .find(|issuer_doc| issuer_doc.id() == &did)
- .ok_or(ValidationError::DocumentMismatch(SignerContext::Issuer))
- }
- Err(error) => {
- // the issuer's url could not be parsed to a valid IotaDID
- Err(ValidationError::SignerUrl {
- source: error.into(),
+ let issuer_did: CoreDID = Self::extract_issuer(credential)?;
+ trusted_issuers
+ .iter()
+ .find(|issuer_doc| issuer_doc.did_str() == issuer_did.as_str())
+ .ok_or(ValidationError::DocumentMismatch(SignerContext::Issuer))
+ .and_then(|issuer| {
+ issuer
+ .verify_data(credential, options)
+ .map_err(|err| ValidationError::Signature {
+ source: err.into(),
signer_ctx: SignerContext::Issuer,
})
- }
- }
- };
- // use the extracted document to verify the signature
- extracted_issuer_result.and_then(|issuer| {
- issuer
- .verify_data(credential, options)
- .map_err(|error| ValidationError::Signature {
- source: error.into(),
- signer_ctx: SignerContext::Issuer,
- })
- })
+ })
}
/// Validate that the relationship between the `holder` and the credential subjects is in accordance with
@@ -184,9 +165,9 @@ impl CredentialValidator {
///
/// Only supports `BitmapRevocation2022`.
#[cfg(feature = "revocation-bitmap")]
- pub fn check_status>(
+ pub fn check_status(
credential: &Credential,
- trusted_issuers: &[D],
+ trusted_issuers: &[DOC],
status_check: StatusCheck,
) -> ValidationUnitResult {
if status_check == StatusCheck::SkipAll {
@@ -201,22 +182,19 @@ impl CredentialValidator {
if status_check == StatusCheck::SkipUnsupported {
return Ok(());
}
- return Err(ValidationError::InvalidStatus(
- identity_credential::Error::InvalidStatus(format!("unsupported type '{}'", status.type_)),
- ));
+ return Err(ValidationError::InvalidStatus(crate::Error::InvalidStatus(format!(
+ "unsupported type '{}'",
+ status.type_
+ ))));
}
let status: RevocationBitmapStatus =
RevocationBitmapStatus::try_from(status.clone()).map_err(ValidationError::InvalidStatus)?;
// Check the credential index against the issuer's DID Document.
- let issuer_did: IotaDID =
- IotaDID::parse(credential.issuer.url().as_str()).map_err(|e| ValidationError::SignerUrl {
- source: e.into(),
- signer_ctx: SignerContext::Issuer,
- })?;
+ let issuer_did: CoreDID = Self::extract_issuer(credential)?;
trusted_issuers
.iter()
- .find(|issuer| issuer.as_ref().id() == &issuer_did)
+ .find(|issuer| issuer.did_str() == issuer_did.as_str())
.ok_or(ValidationError::DocumentMismatch(SignerContext::Issuer))
.and_then(|issuer| CredentialValidator::check_revocation_bitmap_status(issuer, status))
}
@@ -226,24 +204,16 @@ impl CredentialValidator {
/// Check the given `status` against the matching [`RevocationBitmap`] service in the
/// issuer's DID Document.
#[cfg(feature = "revocation-bitmap")]
- fn check_revocation_bitmap_status>(
- issuer: D,
+ fn check_revocation_bitmap_status(
+ issuer: &DOC,
status: RevocationBitmapStatus,
) -> ValidationUnitResult {
- let issuer_service_url: IotaDIDUrl = status.id().map_err(ValidationError::InvalidStatus)?;
-
- // Lookup service.
- let service = issuer
- .as_ref()
- .service()
- .iter()
- .find(|service| &issuer_service_url == service.id())
- .ok_or(ValidationError::InvalidService(identity_did::Error::InvalidService(
- "revocation bitmap service not found",
- )))?;
+ let issuer_service_url: CoreDIDUrl = status.id().map_err(ValidationError::InvalidStatus)?;
// Check whether index is revoked.
- let revocation_bitmap: RevocationBitmap = service.try_into().map_err(ValidationError::InvalidService)?;
+ let revocation_bitmap: RevocationBitmap = issuer
+ .resolve_revocation_bitmap(issuer_service_url.into())
+ .map_err(ValidationError::InvalidService)?;
let index: u32 = status.index().map_err(ValidationError::InvalidStatus)?;
if revocation_bitmap.is_revoked(index) {
Err(ValidationError::Revoked)
@@ -255,9 +225,9 @@ impl CredentialValidator {
// This method takes a slice of issuer's instead of a single issuer in order to better accommodate presentation
// validation. It also validates the relation ship between a holder and the credential subjects when
// `relationship_criterion` is Some.
- pub(crate) fn validate_extended>(
+ pub(crate) fn validate_extended(
credential: &Credential,
- issuers: &[D],
+ issuers: &[DOC],
options: &CredentialValidationOptions,
relationship_criterion: Option<(&Url, SubjectHolderRelationship)>,
fail_fast: FailFast,
@@ -306,6 +276,21 @@ impl CredentialValidator {
Err(CompoundCredentialValidationError { validation_errors })
}
}
+
+ /// Utility for extracting the issuer field of a [`Credential`] as a DID.
+ ///
+ /// # Errors
+ ///
+ /// Fails if the issuer field is not a valid DID.
+ pub fn extract_issuer(credential: &Credential) -> std::result::Result
+ where
+ ::Err: std::error::Error + Send + Sync + 'static,
+ {
+ D::from_str(credential.issuer.url().as_str()).map_err(|err| ValidationError::SignerUrl {
+ signer_ctx: SignerContext::Issuer,
+ source: err.into(),
+ })
+ }
}
#[cfg(test)]
@@ -319,14 +304,14 @@ mod tests {
use identity_core::convert::FromJson;
use identity_core::crypto::KeyPair;
use identity_core::crypto::ProofOptions;
- use identity_credential::credential::Status;
- use identity_credential::credential::Subject;
use identity_did::did::DID;
- use identity_iota_core::document::IotaDocument;
- use identity_iota_core::document::IotaService;
+ use identity_did::document::CoreDocument;
+ use identity_did::service::Service;
- use crate::credential::test_utils;
- use crate::credential::CredentialValidationOptions;
+ use crate::credential::Status;
+ use crate::credential::Subject;
+ use crate::validator::test_utils;
+ use crate::validator::CredentialValidationOptions;
use super::*;
@@ -359,7 +344,7 @@ mod tests {
// Setup parameters shared by many of the tests in this module
struct Setup {
- issuer_doc: IotaDocument,
+ issuer_doc: CoreDocument,
issuer_key: KeyPair,
unsigned_credential: Credential,
issuance_date: Timestamp,
@@ -427,12 +412,10 @@ mod tests {
issuance_date,
} = Setup::new();
issuer_doc
- .sign_data(
- &mut credential,
- issuer_key.private(),
- issuer_doc.default_signing_method().unwrap().id(),
- ProofOptions::default(),
- )
+ .signer(issuer_key.private())
+ .options(ProofOptions::default())
+ .method(issuer_doc.methods().next().unwrap().id())
+ .sign(&mut credential)
.unwrap();
// declare the credential validation parameters
@@ -499,12 +482,10 @@ mod tests {
issuance_date,
} = Setup::new();
issuer_doc
- .sign_data(
- &mut credential,
- issuer_key.private(),
- issuer_doc.default_signing_method().unwrap().id(),
- ProofOptions::default(),
- )
+ .signer(issuer_key.private())
+ .options(ProofOptions::default())
+ .method(issuer_doc.methods().next().unwrap().id())
+ .sign(&mut credential)
.unwrap();
// declare the credential validation parameters
@@ -539,15 +520,13 @@ mod tests {
expiration_date,
} = Setup::new();
issuer_doc
- .sign_data(
- &mut credential,
- issuer_key.private(),
- issuer_doc.default_signing_method().unwrap().id(),
- ProofOptions::default(),
- )
+ .signer(issuer_key.private())
+ .options(ProofOptions::default())
+ .method(issuer_doc.methods().next().unwrap().id())
+ .sign(&mut credential)
.unwrap();
- // declare the credential validation parameters
+ // declare the credential validation parameters
let issued_on_or_before = issuance_date.checked_add(Duration::days(14)).unwrap();
let expires_on_or_after = expiration_date.checked_sub(Duration::hours(1)).unwrap();
let options = CredentialValidationOptions::default()
@@ -567,12 +546,10 @@ mod tests {
} = Setup::new();
let (other_doc, _) = test_utils::generate_document_with_keys();
issuer_doc
- .sign_data(
- &mut credential,
- issuer_key.private(),
- issuer_doc.default_signing_method().unwrap().id(),
- ProofOptions::default(),
- )
+ .signer(issuer_key.private())
+ .options(ProofOptions::default())
+ .method(issuer_doc.methods().next().unwrap().id())
+ .sign(&mut credential)
.unwrap();
// the credential was not signed by this issuer
@@ -580,12 +557,7 @@ mod tests {
// check that `verify_signature` returns the expected error
assert!(matches!(
- CredentialValidator::verify_signature(
- &credential,
- std::slice::from_ref(&other_doc),
- &VerifierOptions::default()
- )
- .unwrap_err(),
+ CredentialValidator::verify_signature(&credential, &[&other_doc], &VerifierOptions::default()).unwrap_err(),
ValidationError::DocumentMismatch { .. }
));
@@ -621,22 +593,15 @@ mod tests {
let (_, other_keys) = test_utils::generate_document_with_keys();
issuer_doc
- .sign_data(
- &mut credential,
- other_keys.private(), // sign with other keys
- issuer_doc.default_signing_method().unwrap().id(),
- ProofOptions::default(),
- )
+ .signer(other_keys.private())
+ .options(ProofOptions::default())
+ .method(issuer_doc.methods().next().unwrap().id())
+ .sign(&mut credential)
.unwrap();
// run the validation unit
assert!(matches!(
- CredentialValidator::verify_signature(
- &credential,
- std::slice::from_ref(&issuer_doc),
- &VerifierOptions::default()
- )
- .unwrap_err(),
+ CredentialValidator::verify_signature(&credential, &[&issuer_doc], &VerifierOptions::default()).unwrap_err(),
ValidationError::Signature { .. }
));
@@ -797,7 +762,7 @@ mod tests {
}
// Add a RevocationBitmap status to the credential.
- let service_url: IotaDIDUrl = issuer_doc.id().to_url().join("#revocation-service").unwrap();
+ let service_url: CoreDIDUrl = issuer_doc.id().to_url().join("#revocation-service").unwrap();
let index: u32 = 42;
credential.credential_status = Some(RevocationBitmapStatus::new(service_url.clone(), index).into());
@@ -815,13 +780,13 @@ mod tests {
// Add a RevocationBitmap service to the issuer.
let bitmap: RevocationBitmap = RevocationBitmap::new();
- assert!(issuer_doc.insert_service(
- IotaService::builder(Object::new())
+ assert!(issuer_doc.service_mut().append(
+ Service::builder(Object::new())
.id(service_url.clone())
.type_(RevocationBitmap::TYPE)
.service_endpoint(bitmap.to_endpoint().unwrap())
.build()
- .unwrap(),
+ .unwrap()
));
// 3: un-revoked index always succeeds.
@@ -854,12 +819,10 @@ mod tests {
} = Setup::new();
issuer_doc
- .sign_data(
- &mut credential,
- issuer_key.private(),
- issuer_doc.default_signing_method().unwrap().id(),
- ProofOptions::default(),
- )
+ .signer(issuer_key.private())
+ .options(ProofOptions::default())
+ .method(issuer_doc.methods().next().unwrap().id())
+ .sign(&mut credential)
.unwrap();
// the credential now has no credential subjects which is not semantically correct
credential.credential_subject = OneOrMany::default();
@@ -896,12 +859,10 @@ mod tests {
let (other_doc, _) = test_utils::generate_document_with_keys();
issuer_doc
- .sign_data(
- &mut credential,
- issuer_key.private(),
- issuer_doc.default_signing_method().unwrap().id(),
- ProofOptions::default(),
- )
+ .signer(issuer_key.private())
+ .options(ProofOptions::default())
+ .method(issuer_doc.methods().next().unwrap().id())
+ .sign(&mut credential)
.unwrap();
// the credential now has no credential subjects which is not semantically correct
credential.credential_subject = OneOrMany::default();
@@ -937,12 +898,10 @@ mod tests {
let (other_doc, _) = test_utils::generate_document_with_keys();
issuer_doc
- .sign_data(
- &mut credential,
- issuer_key.private(),
- issuer_doc.default_signing_method().unwrap().id(),
- ProofOptions::default(),
- )
+ .signer(issuer_key.private())
+ .options(ProofOptions::default())
+ .method(issuer_doc.methods().next().unwrap().id())
+ .sign(&mut credential)
.unwrap();
// the credential now has no credential subjects which is not semantically correct
credential.credential_subject = OneOrMany::default();
diff --git a/identity_iota_client/src/credential/errors.rs b/identity_credential/src/validator/errors.rs
similarity index 76%
rename from identity_iota_client/src/credential/errors.rs
rename to identity_credential/src/validator/errors.rs
index edcaebc38a..1ae8683876 100644
--- a/identity_iota_client/src/credential/errors.rs
+++ b/identity_credential/src/validator/errors.rs
@@ -4,6 +4,8 @@
use std::collections::BTreeMap;
use std::fmt::Display;
+use itertools;
+
#[derive(Debug, thiserror::Error, strum::IntoStaticStr)]
#[non_exhaustive]
/// An error associated with validating credentials and presentations.
@@ -14,38 +16,44 @@ pub enum ValidationError {
/// Indicates that the issuance date of the credential is not considered valid.
#[error("issuance date is in the future or later than required")]
IssuanceDate,
- /// Indicates that the credential's (resp. presentation's) signature could not be verified using the issuer's (resp.
- /// holder's) DID Document.
+ /// Indicates that the credential's (resp. presentation's) signature could not be verified using
+ /// the issuer's (resp. holder's) DID Document.
#[error("could not verify the {signer_ctx}'s signature")]
#[non_exhaustive]
Signature {
+ /// Signature verification error.
source: Box,
+ /// Specifies whether the error was from the DID Document of a credential issuer
+ /// or the presentation holder.
signer_ctx: SignerContext,
},
- /// Indicates that the credential's (resp. presentation's) issuer's (resp. holder's) URL could not be parsed as a
- /// valid DID.
+ /// Indicates that the credential's (resp. presentation's) issuer's (resp. holder's) URL could
+ /// not be parsed as a valid DID.
#[error("{signer_ctx} URL is not a valid DID")]
#[non_exhaustive]
SignerUrl {
+ /// DID parsing error.
source: Box,
+ /// Specifies whether the error relates to the DID of a credential issuer
+ /// or the presentation holder.
signer_ctx: SignerContext,
},
- /// Indicates an attempt to verify a signature of a credential (resp. presentation) using a DID Document not matching
- /// the issuer's (resp. holder's) id.
+ /// Indicates an attempt to verify a signature of a credential (resp. presentation) using a
+ /// DID Document not matching the issuer's (resp. holder's) id.
#[error("the {0}'s id does not match the provided DID Document(s)")]
#[non_exhaustive]
DocumentMismatch(SignerContext),
- /// Indicates that the structure of the [Credential](identity_credential::credential::Credential) is not semantically
+ /// Indicates that the structure of the [Credential](crate::credential::Credential) is not semantically
/// correct.
#[error("the credential's structure is not semantically correct")]
- CredentialStructure(#[source] identity_credential::Error),
- /// Indicates that the structure of the [Presentation](identity_credential::presentation::Presentation) is not
+ CredentialStructure(#[source] crate::Error),
+ /// Indicates that the structure of the [Presentation](crate::presentation::Presentation) is not
/// semantically correct.
#[error("the presentation's structure is not semantically correct")]
- PresentationStructure(#[source] identity_credential::Error),
+ PresentationStructure(#[source] crate::Error),
/// Indicates that the relationship between the presentation holder and one of the credential subjects is not valid.
#[error("expected holder = subject of the credential")]
#[non_exhaustive]
@@ -55,7 +63,7 @@ pub enum ValidationError {
MissingPresentationHolder,
/// Indicates that the credential's status is invalid.
#[error("invalid credential status")]
- InvalidStatus(#[source] identity_credential::Error),
+ InvalidStatus(#[source] crate::Error),
/// Indicates that the the credential's service is invalid.
#[error("invalid service")]
InvalidService(#[source] identity_did::Error),
@@ -64,10 +72,13 @@ pub enum ValidationError {
Revoked,
}
+/// Specifies whether an error is related to a credential issuer or the presentation holder.
#[derive(Debug)]
#[non_exhaustive]
pub enum SignerContext {
+ /// Credential issuer.
Issuer,
+ /// Presentation holder.
Holder,
}
@@ -81,9 +92,10 @@ impl Display for SignerContext {
}
}
+/// Errors caused by a failure to validate a credential.
#[derive(Debug)]
-/// An error caused by a failure to validate a Credential.
pub struct CompoundCredentialValidationError {
+ /// List of credential validation errors.
pub validation_errors: Vec,
}
@@ -104,7 +116,10 @@ impl std::error::Error for CompoundCredentialValidationError {}
#[derive(Debug)]
/// An error caused by a failure to validate a Presentation.
pub struct CompoundPresentationValidationError {
+ /// Errors that occurred during validation of individual credentials, mapped by index of their
+ /// order in the presentation.
pub credential_errors: BTreeMap,
+ /// Errors that occurred during validation of the presentation.
pub presentation_validation_errors: Vec,
}
diff --git a/identity_iota_client/src/credential/mod.rs b/identity_credential/src/validator/mod.rs
similarity index 91%
rename from identity_iota_client/src/credential/mod.rs
rename to identity_credential/src/validator/mod.rs
index 35a87e8491..f494dc2bdf 100644
--- a/identity_iota_client/src/credential/mod.rs
+++ b/identity_credential/src/validator/mod.rs
@@ -14,6 +14,7 @@ pub use self::validation_options::FailFast;
pub use self::validation_options::PresentationValidationOptions;
pub use self::validation_options::StatusCheck;
pub use self::validation_options::SubjectHolderRelationship;
+pub use self::validator_document::ValidatorDocument;
mod credential_validator;
mod errors;
@@ -21,3 +22,4 @@ mod presentation_validator;
#[cfg(test)]
mod test_utils;
mod validation_options;
+mod validator_document;
diff --git a/identity_iota_client/src/credential/presentation_validator.rs b/identity_credential/src/validator/presentation_validator.rs
similarity index 84%
rename from identity_iota_client/src/credential/presentation_validator.rs
rename to identity_credential/src/validator/presentation_validator.rs
index 0aa23bc385..ddae49f703 100644
--- a/identity_iota_client/src/credential/presentation_validator.rs
+++ b/identity_credential/src/validator/presentation_validator.rs
@@ -2,20 +2,25 @@
// SPDX-License-Identifier: Apache-2.0
use std::collections::BTreeMap;
+use std::str::FromStr;
-use identity_credential::presentation::Presentation;
-use identity_did::verifiable::VerifierOptions;
-use identity_iota_core::did::IotaDID;
-use identity_iota_core::document::IotaDocument;
use serde::Serialize;
+use identity_core::common::Url;
+use identity_did::did::CoreDID;
+use identity_did::did::DID;
+use identity_did::verifiable::VerifierOptions;
+
+use crate::presentation::Presentation;
+
use super::errors::CompoundCredentialValidationError;
use super::errors::CompoundPresentationValidationError;
use super::errors::SignerContext;
use super::errors::ValidationError;
+use super::CredentialValidator;
use super::FailFast;
use super::PresentationValidationOptions;
-use crate::credential::credential_validator::CredentialValidator;
+use super::ValidatorDocument;
/// A struct for validating [`Presentation`]s.
#[derive(Debug, Clone)]
@@ -41,10 +46,7 @@ impl PresentationValidator {
/// calling this method.
///
/// ## The state of the supplied DID Documents.
- /// The caller must ensure that the DID Documents in `holder` and `issuers` are up-to-date. The convenience methods
- /// [`Resolver::resolve_presentation_holder`](crate::tangle::Resolver::resolve_presentation_holder())
- /// and [`Resolver::resolve_presentation_issuers`](crate::tangle::Resolver::resolve_presentation_issuers())
- /// can help extract the latest available states of these DID Documents.
+ /// The caller must ensure that the DID Documents in `holder` and `issuers` are up-to-date.
///
/// ## Properties that are not validated
/// There are many properties defined in [The Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) that are **not** validated, such as:
@@ -53,10 +55,10 @@ impl PresentationValidator {
///
/// # Errors
/// An error is returned whenever a validated condition is not satisfied.
- pub fn validate>(
+ pub fn validate(
presentation: &Presentation,
- holder: &D,
- issuers: &[D],
+ holder: &HDOC,
+ issuers: &[IDOC],
options: &PresentationValidationOptions,
fail_fast: FailFast,
) -> PresentationValidationResult {
@@ -109,29 +111,19 @@ impl PresentationValidator {
/// # Errors
/// Fails if the `holder` does not match the `presentation`'s holder property.
/// Fails if signature verification against the holder document fails.
- pub fn verify_presentation_signature>(
+ pub fn verify_presentation_signature(
presentation: &Presentation,
- holder: &D,
+ holder: &DOC,
options: &VerifierOptions,
) -> ValidationUnitResult {
- let did: IotaDID = presentation
- .holder
- .as_ref()
- .ok_or(ValidationError::MissingPresentationHolder)
- .and_then(|value| {
- IotaDID::parse(value.as_str()).map_err(|error| ValidationError::SignerUrl {
- source: error.into(),
- signer_ctx: SignerContext::Holder,
- })
- })?;
- if &did != holder.as_ref().id() {
+ let did: CoreDID = Self::extract_holder(presentation)?;
+ if did.as_str() != holder.did_str() {
return Err(ValidationError::DocumentMismatch(SignerContext::Holder));
}
holder
- .as_ref()
.verify_data(&presentation, options)
- .map_err(|error| ValidationError::Signature {
- source: error.into(),
+ .map_err(|err| ValidationError::Signature {
+ source: err.into(),
signer_ctx: SignerContext::Holder,
})
}
@@ -148,9 +140,9 @@ impl PresentationValidator {
// The following properties are validated according to `options`:
// - the semantic structure of the presentation,
// - the holder's signature,
- fn validate_presentation_without_credentials>(
+ fn validate_presentation_without_credentials(
presentation: &Presentation,
- holder: &D,
+ holder: &DOC,
options: &PresentationValidationOptions,
fail_fast: FailFast,
) -> Result<(), Vec> {
@@ -181,9 +173,9 @@ impl PresentationValidator {
// - the relationship between the holder and the credential subjects,
// - the signatures and some properties of the constituent credentials (see
// [`CredentialValidator::validate`]).
- fn validate_credentials>(
+ fn validate_credentials(
presentation: &Presentation,
- issuers: &[D],
+ issuers: &[DOC],
options: &PresentationValidationOptions,
fail_fast: FailFast,
) -> Result<(), BTreeMap> {
@@ -219,6 +211,25 @@ impl PresentationValidator {
Err(credential_errors)
}
}
+
+ /// Utility for extracting the holder field of a [`Presentation`] as a DID.
+ ///
+ /// # Errors
+ ///
+ /// Fails if the holder field is missing or not a valid DID.
+ pub fn extract_holder(presentation: &Presentation) -> std::result::Result
+ where
+ ::Err: std::error::Error + Send + Sync + 'static,
+ {
+ let holder: &Url = presentation
+ .holder
+ .as_ref()
+ .ok_or(ValidationError::MissingPresentationHolder)?;
+ D::from_str(holder.as_str()).map_err(|err| ValidationError::SignerUrl {
+ signer_ctx: SignerContext::Issuer,
+ source: err.into(),
+ })
+ }
}
#[cfg(test)]
@@ -227,18 +238,19 @@ mod tests {
use identity_core::common::Url;
use identity_core::crypto::KeyPair;
use identity_core::crypto::ProofOptions;
- use identity_credential::credential::Credential;
- use identity_credential::presentation::PresentationBuilder;
- use identity_iota_core::document::IotaDocument;
+ use identity_did::document::CoreDocument;
+
+ use crate::credential::Credential;
+ use crate::presentation::PresentationBuilder;
+ use crate::validator::test_utils;
+ use crate::validator::CredentialValidationOptions;
+ use crate::validator::SubjectHolderRelationship;
use super::*;
- use crate::credential::test_utils;
- use crate::credential::CredentialValidationOptions;
- use crate::credential::SubjectHolderRelationship;
- fn build_presentation(holder: &IotaDocument, credentials: Vec) -> Presentation {
+ fn build_presentation(holder: &CoreDocument, credentials: Vec) -> Presentation {
let mut builder = PresentationBuilder::default()
- .id(Url::parse("http://example.org/credentials/3732").unwrap())
+ .id(Url::parse("https://example.org/credentials/3732").unwrap())
.holder(Url::parse(holder.id().as_ref()).unwrap());
for credential in credentials {
builder = builder.credential(credential);
@@ -249,14 +261,14 @@ mod tests {
// Convenience struct for setting up tests.
struct TestSetup {
// issuer of credential_foo
- issuer_foo_doc: IotaDocument,
+ issuer_foo_doc: CoreDocument,
issuer_foo_key: KeyPair,
// subject of credential_foo
- subject_foo_doc: IotaDocument,
+ subject_foo_doc: CoreDocument,
subject_foo_key: KeyPair,
credential_foo: Credential,
// issuer of credential_bar
- issuer_bar_doc: IotaDocument,
+ issuer_bar_doc: CoreDocument,
issuer_bar_key: KeyPair,
credential_bar: Credential,
}
@@ -304,21 +316,17 @@ mod tests {
} = setup;
// sign the credential
issuer_foo_doc
- .sign_data(
- credential_foo,
- issuer_foo_key.private(),
- issuer_foo_doc.default_signing_method().unwrap().id(),
- ProofOptions::default(),
- )
+ .signer(issuer_foo_key.private())
+ .options(ProofOptions::default())
+ .method(issuer_foo_doc.methods().next().unwrap().id())
+ .sign(credential_foo)
.unwrap();
issuer_bar_doc
- .sign_data(
- credential_bar,
- issuer_bar_key.private(),
- issuer_bar_doc.default_signing_method().unwrap().id(),
- ProofOptions::default(),
- )
+ .signer(issuer_bar_key.private())
+ .options(ProofOptions::default())
+ .method(issuer_bar_doc.methods().next().unwrap().id())
+ .sign(credential_bar)
.unwrap();
setup
}
@@ -339,14 +347,11 @@ mod tests {
let mut presentation = build_presentation(&subject_foo_doc, [credential_foo, credential_bar].to_vec());
// sign the presentation using subject_foo's document and private key
-
subject_foo_doc
- .sign_data(
- &mut presentation,
- subject_foo_key.private(),
- subject_foo_doc.default_signing_method().unwrap().id(),
- ProofOptions::new().challenge("475a7984-1bb5-4c4c-a56f-822bccd46440".to_owned()),
- )
+ .signer(subject_foo_key.private())
+ .options(ProofOptions::new().challenge("475a7984-1bb5-4c4c-a56f-822bccd46440".to_owned()))
+ .method(subject_foo_doc.methods().next().unwrap().id())
+ .sign(&mut presentation)
.unwrap();
// validate the presentation
@@ -362,15 +367,13 @@ mod tests {
.presentation_verifier_options(presentation_verifier_options)
.subject_holder_relationship(SubjectHolderRelationship::SubjectOnNonTransferable);
- let trusted_issuers = [issuer_foo_doc, issuer_bar_doc];
-
let holder_doc = subject_foo_doc;
assert!(dbg!(PresentationValidator::validate(
&presentation,
&holder_doc,
- &trusted_issuers,
+ &[&issuer_foo_doc, &issuer_bar_doc],
&presentation_validation_options,
- FailFast::FirstError
+ FailFast::FirstError,
))
.is_ok());
}
@@ -390,18 +393,14 @@ mod tests {
let mut presentation = build_presentation(&subject_foo_doc, [credential_foo, credential_bar].to_vec());
// sign the presentation using subject_foo's document and private key
-
subject_foo_doc
- .sign_data(
- &mut presentation,
- subject_foo_key.private(),
- subject_foo_doc.default_signing_method().unwrap().id(),
- ProofOptions::new().challenge("some challenge".to_owned()),
- )
+ .signer(subject_foo_key.private())
+ .options(ProofOptions::new().challenge("some challenge".to_owned()))
+ .method(subject_foo_doc.methods().next().unwrap().id())
+ .sign(&mut presentation)
.unwrap();
// check the validation unit
- let trusted_issuers = [issuer_foo_doc, issuer_bar_doc];
let presentation_verifier_options = VerifierOptions::default().challenge("another challenge".to_owned()); //validate with another challenge
let holder_doc = subject_foo_doc;
@@ -410,7 +409,7 @@ mod tests {
assert!(PresentationValidator::verify_presentation_signature(
&presentation,
&holder_doc,
- &presentation_verifier_options
+ &presentation_verifier_options,
)
.is_err());
@@ -429,7 +428,7 @@ mod tests {
let error = PresentationValidator::validate(
&presentation,
&holder_doc,
- &trusted_issuers,
+ &[&issuer_foo_doc, &issuer_bar_doc],
&presentation_validation_options,
FailFast::FirstError,
)
@@ -459,14 +458,11 @@ mod tests {
let mut presentation = build_presentation(&subject_foo_doc, [credential_foo, credential_bar].to_vec());
// sign the presentation using subject_foo's document and private key
-
subject_foo_doc
- .sign_data(
- &mut presentation,
- subject_foo_key.private(),
- subject_foo_doc.default_signing_method().unwrap().id(),
- ProofOptions::new().challenge("some challenge".to_owned()),
- )
+ .signer(subject_foo_key.private())
+ .options(ProofOptions::new().challenge("some challenge".to_owned()))
+ .method(subject_foo_doc.methods().next().unwrap().id())
+ .sign(&mut presentation)
.unwrap();
// validate the presentation
@@ -481,13 +477,11 @@ mod tests {
.presentation_verifier_options(presentation_verifier_options)
.subject_holder_relationship(SubjectHolderRelationship::SubjectOnNonTransferable);
- let trusted_issuers = [issuer_foo_doc, issuer_bar_doc];
-
let holder_doc = subject_foo_doc;
let error = PresentationValidator::validate(
&presentation,
&holder_doc,
- &trusted_issuers,
+ &[&issuer_foo_doc, &issuer_bar_doc],
&presentation_validation_options,
FailFast::FirstError,
)
@@ -517,12 +511,10 @@ mod tests {
credential_bar.non_transferable = Some(true);
// sign the credential
issuer_bar_doc
- .sign_data(
- &mut credential_bar,
- issuer_bar_key.private(),
- issuer_bar_doc.default_signing_method().unwrap().id(),
- ProofOptions::default(),
- )
+ .signer(issuer_bar_key.private())
+ .options(ProofOptions::default())
+ .method(issuer_bar_doc.methods().next().unwrap().id())
+ .sign(&mut credential_bar)
.unwrap();
// create a presentation where the subject of the first credential is the holder.
@@ -530,14 +522,11 @@ mod tests {
let mut presentation = build_presentation(&subject_foo_doc, [credential_foo, credential_bar].to_vec());
// sign the presentation using subject_foo's document and private key.
-
subject_foo_doc
- .sign_data(
- &mut presentation,
- subject_foo_key.private(),
- subject_foo_doc.default_signing_method().unwrap().id(),
- ProofOptions::new().challenge("some challenge".to_owned()),
- )
+ .signer(subject_foo_key.private())
+ .options(ProofOptions::new().challenge("some challenge".to_owned()))
+ .method(subject_foo_doc.methods().next().unwrap().id())
+ .sign(&mut presentation)
.unwrap();
// validate the presentation
@@ -552,14 +541,13 @@ mod tests {
.presentation_verifier_options(presentation_verifier_options)
.subject_holder_relationship(SubjectHolderRelationship::SubjectOnNonTransferable);
- let trusted_issuers = [issuer_foo_doc, issuer_bar_doc];
-
+ let trusted_issuers = &[issuer_foo_doc, issuer_bar_doc];
let holder_doc = subject_foo_doc;
let error = PresentationValidator::validate(
&presentation,
&holder_doc,
- &trusted_issuers,
+ trusted_issuers,
&presentation_validation_options,
FailFast::FirstError,
)
@@ -580,7 +568,7 @@ mod tests {
assert!(PresentationValidator::validate(
&presentation,
&holder_doc,
- &trusted_issuers,
+ trusted_issuers,
&options,
FailFast::FirstError,
)
@@ -591,7 +579,7 @@ mod tests {
assert!(PresentationValidator::validate(
&presentation,
&holder_doc,
- &trusted_issuers,
+ trusted_issuers,
&options,
FailFast::FirstError,
)
@@ -628,14 +616,11 @@ mod tests {
.build()
.unwrap();
// sign the presentation using subject_foo's document and private key
-
subject_foo_doc
- .sign_data(
- &mut presentation,
- subject_foo_key.private(),
- subject_foo_doc.default_signing_method().unwrap().id(),
- ProofOptions::new().challenge("some challenge".to_owned()),
- )
+ .signer(subject_foo_key.private())
+ .options(ProofOptions::new().challenge("some challenge".to_owned()))
+ .method(subject_foo_doc.methods().next().unwrap().id())
+ .sign(&mut presentation)
.unwrap();
// validate the presentation
@@ -649,8 +634,7 @@ mod tests {
.shared_validation_options(credential_validation_options)
.presentation_verifier_options(presentation_verifier_options);
- let trusted_issuers = [issuer_foo_doc, issuer_bar_doc];
-
+ let trusted_issuers = &[issuer_foo_doc, issuer_bar_doc];
let holder_document = subject_foo_doc;
let CompoundPresentationValidationError {
@@ -659,19 +643,19 @@ mod tests {
} = PresentationValidator::validate(
&presentation,
&holder_document,
- &trusted_issuers,
+ trusted_issuers,
&presentation_validation_options,
FailFast::FirstError,
)
.unwrap_err();
- assert!(
+ assert_eq!(
+ 1,
presentation_validation_errors.len()
+ credential_errors
.values()
.map(|error| error.validation_errors.len())
.sum::()
- == 1
);
}
@@ -706,12 +690,10 @@ mod tests {
// sign the presentation using subject_foo's document and private key
subject_foo_doc
- .sign_data(
- &mut presentation,
- subject_foo_key.private(),
- subject_foo_doc.default_signing_method().unwrap().id(),
- ProofOptions::new().challenge("some challenge".to_owned()),
- )
+ .signer(subject_foo_key.private())
+ .options(ProofOptions::new().challenge("some challenge".to_owned()))
+ .method(subject_foo_doc.methods().next().unwrap().id())
+ .sign(&mut presentation)
.unwrap();
// validate the presentation
@@ -726,8 +708,7 @@ mod tests {
.shared_validation_options(credential_validation_options)
.presentation_verifier_options(presentation_verifier_options);
- let trusted_issuers = [issuer_foo_doc, issuer_bar_doc];
-
+ let trusted_issuers = &[issuer_foo_doc, issuer_bar_doc];
let holder_doc = subject_foo_doc;
let CompoundPresentationValidationError {
@@ -736,7 +717,7 @@ mod tests {
} = PresentationValidator::validate(
&presentation,
&holder_doc,
- &trusted_issuers,
+ trusted_issuers,
&presentation_validation_options,
FailFast::AllErrors,
)
diff --git a/identity_iota_client/src/credential/test_utils.rs b/identity_credential/src/validator/test_utils.rs
similarity index 63%
rename from identity_iota_client/src/credential/test_utils.rs
rename to identity_credential/src/validator/test_utils.rs
index b568fe8f70..45e70aa44f 100644
--- a/identity_iota_client/src/credential/test_utils.rs
+++ b/identity_credential/src/validator/test_utils.rs
@@ -1,33 +1,45 @@
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
+use identity_core::common::Object;
use identity_core::common::Timestamp;
use identity_core::common::Url;
use identity_core::convert::FromJson;
use identity_core::crypto::KeyPair;
use identity_core::crypto::KeyType;
use identity_core::json;
-use identity_credential::credential::Credential;
-use identity_credential::credential::CredentialBuilder;
-use identity_credential::credential::Subject;
+use identity_core::utils::BaseEncoding;
+use identity_did::did::CoreDID;
use identity_did::did::DID;
-use identity_iota_core::document::IotaDocument;
+use identity_did::document::CoreDocument;
+use identity_did::verification::VerificationMethod;
-use crate::Result;
+use crate::credential::Credential;
+use crate::credential::CredentialBuilder;
+use crate::credential::Subject;
-pub(super) fn generate_document_with_keys() -> (IotaDocument, KeyPair) {
+pub(super) fn generate_document_with_keys() -> (CoreDocument, KeyPair) {
let keypair: KeyPair = KeyPair::new(KeyType::Ed25519).unwrap();
- let document: IotaDocument = IotaDocument::new(&keypair).unwrap();
+ let did: CoreDID = CoreDID::parse(&format!(
+ "did:example:{}",
+ BaseEncoding::encode_base58(keypair.public())
+ ))
+ .unwrap();
+ let document: CoreDocument = CoreDocument::builder(Object::new())
+ .id(did.clone())
+ .verification_method(VerificationMethod::new(did, KeyType::Ed25519, keypair.public(), "#root").unwrap())
+ .build()
+ .unwrap();
(document, keypair)
}
pub(super) fn generate_credential(
- issuer: &IotaDocument,
- subjects: &[IotaDocument],
+ issuer: &CoreDocument,
+ subjects: &[CoreDocument],
issuance_date: Timestamp,
expiration_date: Timestamp,
) -> Credential {
- let credential_subjects: Result> = subjects
+ let credential_subjects: Vec = subjects
.iter()
.map(|subject| {
Subject::from_json_value(json!({
@@ -39,7 +51,7 @@ pub(super) fn generate_credential(
},
"GPA": "4.0",
}))
- .map_err(Into::into)
+ .unwrap()
})
.collect();
@@ -48,7 +60,7 @@ pub(super) fn generate_credential(
.id(Url::parse("https://example.edu/credentials/3732").unwrap())
.issuer(Url::parse(issuer.id().as_str()).unwrap())
.type_("UniversityDegreeCredential")
- .subjects(credential_subjects.unwrap())
+ .subjects(credential_subjects)
.issuance_date(issuance_date)
.expiration_date(expiration_date)
.build()
@@ -56,7 +68,7 @@ pub(super) fn generate_credential(
}
// generates a triple: issuer document, issuer's keys, unsigned credential issued by issuer
-pub(super) fn credential_setup() -> (IotaDocument, KeyPair, Credential) {
+pub(super) fn credential_setup() -> (CoreDocument, KeyPair, Credential) {
let (issuer_doc, issuer_key) = generate_document_with_keys();
let (subject_doc, _) = generate_document_with_keys();
let issuance_date = Timestamp::parse("2020-01-01T00:00:00Z").unwrap();
diff --git a/identity_iota_client/src/credential/validation_options.rs b/identity_credential/src/validator/validation_options.rs
similarity index 96%
rename from identity_iota_client/src/credential/validation_options.rs
rename to identity_credential/src/validator/validation_options.rs
index c37a4b74d4..34e7416b4f 100644
--- a/identity_iota_client/src/credential/validation_options.rs
+++ b/identity_credential/src/validator/validation_options.rs
@@ -124,9 +124,8 @@ pub enum FailFast {
FirstError,
}
-/// Options to declare validation criteria for validation methods such as
-/// [`PresentationValidator::validate`](super::PresentationValidator::validate()) and
-/// [`Resolver::verify_presentation`](crate::tangle::Resolver::verify_presentation()).
+/// Criteria for validating a [`Presentation`](crate::presentation::Presentation), such as with
+/// [`PresentationValidator::validate`](crate::validator::PresentationValidator::validate()).
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
diff --git a/identity_credential/src/validator/validator_document.rs b/identity_credential/src/validator/validator_document.rs
new file mode 100644
index 0000000000..118466d9bc
--- /dev/null
+++ b/identity_credential/src/validator/validator_document.rs
@@ -0,0 +1,111 @@
+// Copyright 2020-2022 IOTA Stiftung
+// SPDX-License-Identifier: Apache-2.0
+
+use identity_core::crypto::GetSignature;
+use identity_did::did::DID;
+use identity_did::document::Document;
+use identity_did::revocation::RevocationBitmap;
+use identity_did::utils::DIDUrlQuery;
+use identity_did::verifiable::VerifierOptions;
+
+use self::private::Sealed;
+use self::private::Verifiable;
+
+/// Abstraction over DID Documents for validating presentations and credentials.
+///
+/// NOTE: this is a sealed trait and not intended to be used externally or implemented manually.
+/// A blanket implementation is provided for the [`Document`] trait, which can be implemented
+/// instead to be compatible. Any changes to this trait will be considered non-breaking.
+pub trait ValidatorDocument: Sealed {
+ /// Convenience function for casting self to the trait.
+ ///
+ /// Equivalent to `self as &dyn ValidatorDocument`.
+ fn as_validator(&self) -> &dyn ValidatorDocument
+ where
+ Self: Sized,
+ {
+ self as &dyn ValidatorDocument
+ }
+
+ /// Returns the string identifier of the DID Document.
+ fn did_str(&self) -> &str;
+
+ /// Verifies the signature of the provided data against the DID Document.
+ ///
+ /// # Errors
+ ///
+ /// Fails if an unsupported verification method is used, data
+ /// serialization fails, or the verification operation fails.
+ fn verify_data(&self, data: &dyn Verifiable, options: &VerifierOptions) -> identity_did::Result<()>;
+
+ /// Extracts the `RevocationBitmap` from the referenced service in the DID Document.
+ ///
+ /// # Errors
+ ///
+ /// Fails if the referenced service is not found, or is not a
+ /// valid `RevocationBitmap2022` service.
+ #[cfg(feature = "revocation-bitmap")]
+ fn resolve_revocation_bitmap(&self, query: DIDUrlQuery<'_>) -> identity_did::Result;
+}
+
+mod private {
+ use super::*;
+
+ pub trait Sealed {}
+
+ impl Sealed for T where T: Document {}
+ impl Sealed for &dyn ValidatorDocument {}
+
+ /// Object-safe trait workaround to satisfy the trait bounds
+ /// [`serde::Serialize`] + [`GetSignature`].
+ pub trait Verifiable: erased_serde::Serialize + GetSignature {}
+
+ impl Verifiable for T where T: erased_serde::Serialize + GetSignature {}
+
+ impl<'a> serde::Serialize for dyn Verifiable + 'a {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: serde::Serializer,
+ {
+ erased_serde::serialize(self, serializer)
+ }
+ }
+}
+
+impl ValidatorDocument for &dyn ValidatorDocument {
+ fn did_str(&self) -> &str {
+ (*self).did_str()
+ }
+
+ fn verify_data(&self, data: &dyn Verifiable, options: &VerifierOptions) -> identity_did::Result<()> {
+ (*self).verify_data(data, options)
+ }
+
+ #[cfg(feature = "revocation-bitmap")]
+ fn resolve_revocation_bitmap(&self, query: DIDUrlQuery<'_>) -> identity_did::Result {
+ (*self).resolve_revocation_bitmap(query)
+ }
+}
+
+impl ValidatorDocument for DOC
+where
+ DOC: Document,
+{
+ fn did_str(&self) -> &str {
+ self.id().as_str()
+ }
+
+ fn verify_data(&self, data: &dyn Verifiable, options: &VerifierOptions) -> identity_did::Result<()> {
+ self.verify_data(data, options).map_err(Into::into)
+ }
+
+ #[cfg(feature = "revocation-bitmap")]
+ fn resolve_revocation_bitmap(&self, query: DIDUrlQuery<'_>) -> identity_did::Result {
+ self
+ .resolve_service(query)
+ .ok_or(identity_did::Error::InvalidService(
+ "revocation bitmap service not found",
+ ))
+ .and_then(RevocationBitmap::try_from)
+ }
+}
diff --git a/identity_did/src/document/core_document.rs b/identity_did/src/document/core_document.rs
index c3f9860cfe..b3a855f147 100644
--- a/identity_did/src/document/core_document.rs
+++ b/identity_did/src/document/core_document.rs
@@ -25,6 +25,7 @@ use identity_core::crypto::Verifier;
use crate::did::CoreDID;
use crate::did::DIDUrl;
use crate::did::DID;
+use crate::document::Document;
use crate::document::DocumentBuilder;
use crate::error::Error;
use crate::error::Result;
@@ -672,7 +673,7 @@ where
/// serialization fails, or the verification operation fails.
pub fn verify_data(&self, data: &X, options: &VerifierOptions) -> Result<()>
where
- X: Serialize + GetSignature,
+ X: Serialize + GetSignature + ?Sized,
{
let signature: &Proof = data.signature().ok_or(Error::InvalidSignature("missing signature"))?;
@@ -736,7 +737,7 @@ where
/// serialization fails, or the verification operation fails.
fn do_verify(method: &VerificationMethod, data: &X) -> Result<()>
where
- X: Serialize + GetSignature,
+ X: Serialize + GetSignature + ?Sized,
{
let public_key: Vec = method.data().try_decode()?;
@@ -753,6 +754,114 @@ where
}
}
+impl Document for CoreDocument
+where
+ D: DID + KeyComparable,
+{
+ type D = D;
+ type U = U;
+ type V = V;
+
+ fn id(&self) -> &Self::D {
+ CoreDocument::id(self)
+ }
+
+ fn resolve_service<'query, 'me, Q>(&'me self, query: Q) -> Option<&Service>
+ where
+ Q: Into>,
+ {
+ self.service().query(query.into())
+ }
+
+ fn resolve_method<'query, 'me, Q>(
+ &'me self,
+ query: Q,
+ scope: Option,
+ ) -> Option<&VerificationMethod>
+ where
+ Q: Into>,
+ {
+ CoreDocument::resolve_method(self, query, scope)
+ }
+
+ fn verify_data(&self, data: &X, options: &VerifierOptions) -> Result<()>
+ where
+ X: Serialize + GetSignature + ?Sized,
+ {
+ CoreDocument::verify_data(self, data, options)
+ }
+}
+
+#[cfg(feature = "revocation-bitmap")]
+mod core_document_revocation {
+ use identity_core::common::KeyComparable;
+
+ use crate::did::DID;
+ use crate::revocation::RevocationBitmap;
+ use crate::service::Service;
+ use crate::utils::DIDUrlQuery;
+ use crate::utils::Queryable;
+ use crate::Error;
+ use crate::Result;
+
+ use super::CoreDocument;
+
+ impl CoreDocument
+ where
+ D: DID + KeyComparable,
+ {
+ /// If the document has a [`RevocationBitmap`] service identified by `service_query`,
+ /// revoke all credentials with a `revocationBitmapIndex` in `credential_indices`.
+ pub fn revoke_credentials<'query, 'me, Q>(&mut self, service_query: Q, credential_indices: &[u32]) -> Result<()>
+ where
+ Q: Into>,
+ {
+ self.update_revocation_bitmap(service_query, |revocation_bitmap| {
+ // Revoke all given credential indices.
+ for credential in credential_indices {
+ revocation_bitmap.revoke(*credential);
+ }
+ })
+ }
+
+ /// If the document has a [`RevocationBitmap`] service identified by `service_query`,
+ /// unrevoke all credentials with a `revocationBitmapIndex` in `credential_indices`.
+ pub fn unrevoke_credentials<'query, 'me, Q>(
+ &'me mut self,
+ service_query: Q,
+ credential_indices: &[u32],
+ ) -> Result<()>
+ where
+ Q: Into>,
+ {
+ self.update_revocation_bitmap(service_query, |revocation_bitmap| {
+ // Unrevoke all given credential indices.
+ for credential in credential_indices {
+ revocation_bitmap.unrevoke(*credential);
+ }
+ })
+ }
+
+ fn update_revocation_bitmap<'query, 'me, F, Q>(&'me mut self, service_query: Q, f: F) -> Result<()>
+ where
+ F: FnOnce(&mut RevocationBitmap),
+ Q: Into>,
+ {
+ let service: &mut Service = self
+ .service_mut()
+ .query_mut(service_query)
+ .ok_or(Error::InvalidService("invalid id - service not found"))?;
+
+ let mut revocation_bitmap: RevocationBitmap = RevocationBitmap::try_from(&*service)?;
+ f(&mut revocation_bitmap);
+
+ std::mem::swap(service.service_endpoint_mut(), &mut revocation_bitmap.to_endpoint()?);
+
+ Ok(())
+ }
+ }
+}
+
// =============================================================================
// Signature Extensions
// =============================================================================
diff --git a/identity_did/src/document/mod.rs b/identity_did/src/document/mod.rs
index bcd021e45e..c3de65a652 100644
--- a/identity_did/src/document/mod.rs
+++ b/identity_did/src/document/mod.rs
@@ -1,12 +1,14 @@
-// Copyright 2020-2021 IOTA Stiftung
+// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
//! Defines the core (implementation agnostic) DID Document type.
#![allow(clippy::module_inception)]
-mod builder;
-mod core_document;
-
pub use self::builder::DocumentBuilder;
pub use self::core_document::CoreDocument;
+pub use self::traits::Document;
+
+mod builder;
+mod core_document;
+mod traits;
diff --git a/identity_did/src/document/traits.rs b/identity_did/src/document/traits.rs
new file mode 100644
index 0000000000..8451786ae2
--- /dev/null
+++ b/identity_did/src/document/traits.rs
@@ -0,0 +1,88 @@
+// Copyright 2020-2022 IOTA Stiftung
+// SPDX-License-Identifier: Apache-2.0
+
+use serde::Serialize;
+
+use identity_core::crypto::GetSignature;
+
+use crate::did::DID;
+use crate::service::Service;
+use crate::utils::DIDUrlQuery;
+use crate::verifiable::VerifierOptions;
+use crate::verification::MethodScope;
+use crate::verification::VerificationMethod;
+use crate::Result;
+
+// TODO: add sign_data, split sign/verify to separate trait as first step towards
+// supporting custom signature schemes. Replace DocumentSigner with trait?
+// Add DocumentMut for mutable function returns?
+// Blanket impl for &T, &mut T, Box etc.?
+/// Common operations for DID Documents.
+pub trait Document {
+ type D: DID;
+ type U;
+ type V;
+
+ /// Returns a reference to the `Document` id.
+ fn id(&self) -> &Self::D;
+
+ /// Returns the first [`Service`] with an `id` property matching the provided `query`, if present.
+ fn resolve_service<'query, 'me, Q>(&'me self, query: Q) -> Option<&Service>
+ where
+ Q: Into>;
+
+ /// Returns the first [`VerificationMethod`] with an `id` property matching the
+ /// provided `query` and the verification relationship specified by `scope` if present.
+ fn resolve_method<'query, 'me, Q>(
+ &'me self,
+ query: Q,
+ scope: Option,
+ ) -> Option<&VerificationMethod>
+ where
+ Q: Into>;
+
+ /// Verifies the signature of the provided data.
+ ///
+ /// # Errors
+ ///
+ /// Fails if an unsupported verification method is used, data
+ /// serialization fails, or the verification operation fails.
+ fn verify_data(&self, data: &X, options: &VerifierOptions) -> Result<()>
+ where
+ X: Serialize + GetSignature + ?Sized;
+}
+
+impl Document for &DOC {
+ type D = DOC::D;
+ type U = DOC::U;
+ type V = DOC::V;
+
+ fn id(&self) -> &Self::D {
+ DOC::id(self)
+ }
+
+ fn resolve_service<'query, 'me, Q>(&'me self, query: Q) -> Option<&Service>
+ where
+ Q: Into>,
+ {
+ DOC::resolve_service(self, query)
+ }
+
+ fn resolve_method<'query, 'me, Q>(
+ &'me self,
+ query: Q,
+ scope: Option,
+ ) -> Option<&VerificationMethod>
+ where
+ Q: Into>,
+ {
+ DOC::resolve_method(self, query, scope)
+ }
+
+ fn verify_data(&self, data: &X, options: &VerifierOptions) -> Result<()>
+ where
+ X: Serialize + GetSignature + ?Sized,
+ {
+ DOC::verify_data(self, data, options)
+ }
+}
diff --git a/identity_did/src/revocation/bitmap.rs b/identity_did/src/revocation/bitmap.rs
index bffd474066..70045a844a 100644
--- a/identity_did/src/revocation/bitmap.rs
+++ b/identity_did/src/revocation/bitmap.rs
@@ -141,14 +141,12 @@ impl RevocationBitmap {
}
}
-impl TryFrom<&Service> for RevocationBitmap {
+impl TryFrom<&Service> for RevocationBitmap {
type Error = Error;
- fn try_from(service: &Service) -> Result {
+ fn try_from(service: &Service) -> Result {
if service.type_() != Self::TYPE {
- return Err(Error::InvalidService(
- "invalid service - expected a `RevocationBitmap2022`",
- ));
+ return Err(Error::InvalidService("invalid type - expected `RevocationBitmap2022`"));
}
Self::from_endpoint(service.service_endpoint())
diff --git a/identity_iota/src/lib.rs b/identity_iota/src/lib.rs
index 7447e12e51..9b79dc78e2 100644
--- a/identity_iota/src/lib.rs
+++ b/identity_iota/src/lib.rs
@@ -48,6 +48,7 @@ pub mod credential {
pub use identity_credential::credential::*;
pub use identity_credential::error::*;
pub use identity_credential::presentation::*;
+ pub use identity_credential::validator::*;
}
pub mod did {
@@ -72,7 +73,6 @@ pub mod client {
//! IOTA DID Tangle client and validators.
pub use identity_iota_client::chain::*;
- pub use identity_iota_client::credential::*;
pub use identity_iota_client::document::*;
pub use identity_iota_client::tangle::*;
diff --git a/identity_iota_client/Cargo.toml b/identity_iota_client/Cargo.toml
index de4826834b..c69ac99918 100644
--- a/identity_iota_client/Cargo.toml
+++ b/identity_iota_client/Cargo.toml
@@ -17,7 +17,7 @@ brotli = { version = "3.3", default-features = false, features = ["std"] }
form_urlencoded = { version = "1.0" }
futures = { version = "0.3" }
identity_core = { version = "=0.6.0", path = "../identity_core", default-features = false }
-identity_credential = { version = "=0.6.0", path = "../identity_credential" }
+identity_credential = { version = "=0.6.0", path = "../identity_credential", default-features = false, features = ["validator"] }
identity_did = { version = "=0.6.0", path = "../identity_did", default-features = false }
identity_iota_core = { version = "=0.6.0", path = "../identity_iota_core", default-features = false }
itertools = { version = "0.10" }
@@ -26,7 +26,6 @@ log = { version = "0.4", default-features = false }
num-derive = { version = "0.3", default-features = false }
num-traits = { version = "0.2", default-features = false, features = ["std"] }
serde = { version = "1.0", default-features = false, features = ["std", "derive"] }
-serde_repr = { version = "0.1", default-features = false }
strum = { version = "0.24.0", default-features = false, features = ["std", "derive"] }
thiserror = { version = "1.0", default-features = false }
@@ -46,14 +45,13 @@ default-features = false
features = ["blake2b"]
[dev-dependencies]
-proptest = { version = "1.0.0", default-features = false, features = ["std"] }
tokio = { version = "1.17.0", default-features = false, features = ["macros"] }
[features]
default = ["revocation-bitmap"]
# Enables revocation with `RevocationBitmap2022`.
-revocation-bitmap = ["identity_iota_core/revocation-bitmap"]
+revocation-bitmap = ["identity_iota_core/revocation-bitmap", "identity_credential/revocation-bitmap"]
[package.metadata.docs.rs]
# To build locally:
diff --git a/identity_iota_client/README.md b/identity_iota_client/README.md
index 1564148a8e..76519d0801 100644
--- a/identity_iota_client/README.md
+++ b/identity_iota_client/README.md
@@ -5,8 +5,3 @@ This crate provides interfaces for publishing and resolving DID Documents to and
- [`Client`](crate::tangle::Client)
- [`Resolver`](crate::tangle::Resolver)
-
-Convenience methods for validating [Verifiable Credentials](https://wiki.iota.org/identity.rs/concepts/verifiable_credentials/overview) and [Verifiable Presentations](https://wiki.iota.org/identity.rs/concepts/verifiable_credentials/verifiable_presentations) are also provided:
-
-- [`CredentialValidator`](crate::credential::CredentialValidator)
-- [`PresentationValidator`](crate::credential::PresentationValidator)
diff --git a/identity_iota_client/src/error.rs b/identity_iota_client/src/error.rs
index a393607ff9..4e207a68af 100644
--- a/identity_iota_client/src/error.rs
+++ b/identity_iota_client/src/error.rs
@@ -34,11 +34,11 @@ pub enum Error {
InvalidMessageFlags,
/// Caused by a single concern credential or presentation validation method failing.
#[error("A validation unit failed")]
- IsolatedValidationError(#[from] crate::credential::ValidationError),
+ IsolatedValidationError(#[from] identity_credential::validator::ValidationError),
/// Caused by one or more failures when validating a credential.
#[error("credential validation failed")]
- CredentialValidationError(#[from] crate::credential::CompoundCredentialValidationError),
+ CredentialValidationError(#[from] identity_credential::validator::CompoundCredentialValidationError),
/// Caused by one or more failures when validating a presentation.
#[error("presentation validation failed")]
- PresentationValidationError(#[from] crate::credential::CompoundPresentationValidationError),
+ PresentationValidationError(#[from] identity_credential::validator::CompoundPresentationValidationError),
}
diff --git a/identity_iota_client/src/lib.rs b/identity_iota_client/src/lib.rs
index 6df6a22f3e..c89051d4ba 100644
--- a/identity_iota_client/src/lib.rs
+++ b/identity_iota_client/src/lib.rs
@@ -4,7 +4,6 @@
#![forbid(unsafe_code)]
#![allow(deprecated)]
#![doc = include_str!("./../README.md")]
-#![allow(clippy::upper_case_acronyms)]
#![warn(
rust_2018_idioms,
unreachable_pub,
@@ -21,7 +20,6 @@ pub use self::error::Error;
pub use self::error::Result;
pub mod chain;
-pub mod credential;
pub mod document;
pub mod tangle;
diff --git a/identity_iota_client/src/tangle/resolver.rs b/identity_iota_client/src/tangle/resolver.rs
index 275acc8224..db94ebccc5 100644
--- a/identity_iota_client/src/tangle/resolver.rs
+++ b/identity_iota_client/src/tangle/resolver.rs
@@ -5,19 +5,22 @@ use std::collections::HashMap;
use std::collections::HashSet;
use std::sync::Arc;
-use identity_core::common::Url;
+use serde::Serialize;
+
use identity_credential::credential::Credential;
use identity_credential::presentation::Presentation;
+use identity_credential::validator::CredentialValidator;
+use identity_credential::validator::FailFast;
+use identity_credential::validator::PresentationValidationOptions;
+use identity_credential::validator::PresentationValidator;
+use identity_credential::validator::ValidatorDocument;
use identity_iota_core::did::IotaDID;
use identity_iota_core::diff::DiffMessage;
+use identity_iota_core::document::IotaDocument;
use identity_iota_core::tangle::NetworkName;
-use serde::Serialize;
use crate::chain::ChainHistory;
use crate::chain::DocumentHistory;
-use crate::credential::FailFast;
-use crate::credential::PresentationValidationOptions;
-use crate::credential::PresentationValidator;
use crate::document::ResolvedIotaDocument;
use crate::error::Error;
use crate::error::Result;
@@ -106,12 +109,7 @@ where
&self,
credential: &Credential,
) -> Result {
- let issuer: IotaDID = IotaDID::parse(credential.issuer.url().as_str()).map_err(|error| {
- Error::IsolatedValidationError(crate::credential::ValidationError::SignerUrl {
- signer_ctx: crate::credential::SignerContext::Issuer,
- source: error.into(),
- })
- })?;
+ let issuer: IotaDID = CredentialValidator::extract_issuer(credential).map_err(Error::IsolatedValidationError)?;
self.resolve(&issuer).await
}
@@ -129,14 +127,8 @@ where
let issuers: HashSet = presentation
.verifiable_credential
.iter()
- .map(|credential| IotaDID::parse(credential.issuer.url().as_str()))
- .map(|url_result| {
- url_result.map_err(|error| {
- Error::IsolatedValidationError(crate::credential::ValidationError::SignerUrl {
- signer_ctx: crate::credential::SignerContext::Issuer,
- source: error.into(),
- })
- })
+ .map(|credential| {
+ CredentialValidator::extract_issuer::(credential).map_err(Error::IsolatedValidationError)
})
.collect::>()?;
@@ -153,15 +145,8 @@ where
&self,
presentation: &Presentation,
) -> Result {
- let holder_url: &Url = presentation.holder.as_ref().ok_or(Error::IsolatedValidationError(
- crate::credential::ValidationError::MissingPresentationHolder,
- ))?;
- let holder: IotaDID = IotaDID::parse(holder_url.as_str()).map_err(|error| {
- Error::IsolatedValidationError(crate::credential::ValidationError::SignerUrl {
- signer_ctx: crate::credential::SignerContext::Holder,
- source: error.into(),
- })
- })?;
+ let holder: IotaDID =
+ PresentationValidator::extract_holder(presentation).map_err(Error::IsolatedValidationError)?;
self.resolve(&holder).await
}
@@ -186,28 +171,30 @@ where
presentation: &Presentation,
options: &PresentationValidationOptions,
fail_fast: FailFast,
- holder: Option<&ResolvedIotaDocument>,
- issuers: Option<&[ResolvedIotaDocument]>,
+ holder: Option<&dyn ValidatorDocument>,
+ issuers: Option<&[&dyn ValidatorDocument]>,
) -> Result<()> {
match (holder, issuers) {
(Some(holder), Some(issuers)) => {
- PresentationValidator::validate(presentation, holder, issuers, options, fail_fast)
+ PresentationValidator::validate(presentation, &holder, issuers, options, fail_fast)
}
(Some(holder), None) => {
- let issuers: Vec = self.resolve_presentation_issuers(presentation).await?;
- PresentationValidator::validate(presentation, holder, &issuers, options, fail_fast)
+ let resolved_issuers: Vec = self.resolve_presentation_issuers(presentation).await?;
+ let issuers: Vec = resolved_issuers.into_iter().map(|resolved| resolved.document).collect();
+ PresentationValidator::validate(presentation, &holder, issuers.as_slice(), options, fail_fast)
}
(None, Some(issuers)) => {
let holder: ResolvedIotaDocument = self.resolve_presentation_holder(presentation).await?;
- PresentationValidator::validate(presentation, &holder, issuers, options, fail_fast)
+ PresentationValidator::validate(presentation, &holder.document, issuers, options, fail_fast)
}
(None, None) => {
- let (holder, issuers): (ResolvedIotaDocument, Vec) = futures::future::try_join(
+ let (holder, resolved_issuers): (ResolvedIotaDocument, Vec) = futures::future::try_join(
self.resolve_presentation_holder(presentation),
self.resolve_presentation_issuers(presentation),
)
.await?;
- PresentationValidator::validate(presentation, &holder, &issuers, options, fail_fast)
+ let issuers: Vec = resolved_issuers.into_iter().map(|resolved| resolved.document).collect();
+ PresentationValidator::validate(presentation, &holder.document, &issuers, options, fail_fast)
}
}
.map_err(Into::into)
@@ -282,3 +269,322 @@ impl TangleResolve for Resolver {
self.resolve(did).await
}
}
+
+#[cfg(test)]
+mod tests {
+ use identity_core::common::Duration;
+ use identity_core::common::Object;
+ use identity_core::common::Timestamp;
+ use identity_core::common::Url;
+ use identity_core::convert::FromJson;
+ use identity_core::crypto::KeyPair;
+ use identity_core::crypto::KeyType;
+ use identity_core::crypto::ProofOptions;
+ use identity_core::json;
+ use identity_core::utils::BaseEncoding;
+ use identity_credential::credential::Credential;
+ use identity_credential::credential::CredentialBuilder;
+ use identity_credential::credential::Subject;
+ use identity_credential::validator::CredentialValidationOptions;
+ use identity_credential::validator::SubjectHolderRelationship;
+ use identity_did::did::CoreDID;
+ use identity_did::did::DID;
+ use identity_did::document::CoreDocument;
+ use identity_did::verifiable::VerifierOptions;
+ use identity_did::verification::VerificationMethod;
+ use identity_iota_core::document::IotaDocument;
+ use identity_iota_core::tangle::Network;
+
+ use super::*;
+
+ fn generate_core_document() -> (CoreDocument, KeyPair) {
+ let keypair: KeyPair = KeyPair::new(KeyType::Ed25519).unwrap();
+ let did: CoreDID = CoreDID::parse(&format!(
+ "did:example:{}",
+ BaseEncoding::encode_base58(keypair.public())
+ ))
+ .unwrap();
+ let document: CoreDocument = CoreDocument::builder(Object::new())
+ .id(did.clone())
+ .verification_method(VerificationMethod::new(did, KeyType::Ed25519, keypair.public(), "#root").unwrap())
+ .build()
+ .unwrap();
+ (document, keypair)
+ }
+
+ fn generate_credential(issuer: &str, subject: &str) -> Credential {
+ let credential_subject: Subject = Subject::from_json_value(json!({
+ "id": subject,
+ "name": "Alice",
+ "degree": {
+ "type": "BachelorDegree",
+ "name": "Bachelor of Science and Arts",
+ },
+ "GPA": "4.0",
+ }))
+ .unwrap();
+
+ // Build credential using subject above and issuer.
+ CredentialBuilder::default()
+ .id(Url::parse("https://example.edu/credentials/3732").unwrap())
+ .issuer(Url::parse(issuer).unwrap())
+ .type_("UniversityDegreeCredential")
+ .subject(credential_subject)
+ .issuance_date(Timestamp::now_utc())
+ .expiration_date(Timestamp::now_utc().checked_add(Duration::days(1)).unwrap())
+ .build()
+ .unwrap()
+ }
+
+ fn generate_presentation(holder: &str, credentials: Vec) -> Presentation {
+ let mut builder = Presentation::builder(Object::new())
+ .id(Url::parse("https://example.org/credentials/3732").unwrap())
+ .holder(Url::parse(holder).unwrap());
+ for credential in credentials {
+ builder = builder.credential(credential);
+ }
+ builder.build().unwrap()
+ }
+
+ // Convenience struct for setting up tests.
+ struct MixedTestSetup {
+ // Issuer of credential_iota.
+ issuer_iota_doc: IotaDocument,
+ issuer_iota_key: KeyPair,
+ credential_iota: Credential,
+ // Issuer of credential_core.
+ issuer_core_doc: CoreDocument,
+ issuer_core_key: KeyPair,
+ credential_core: Credential,
+ // Subject of both credentials.
+ subject_doc: CoreDocument,
+ subject_key: KeyPair,
+ }
+
+ impl MixedTestSetup {
+ // Creates DID Documents and unsigned credentials.
+ fn new() -> Self {
+ let (issuer_iota_doc, issuer_iota_key) = {
+ let keypair: KeyPair = KeyPair::new(KeyType::Ed25519).unwrap();
+ (IotaDocument::new(&keypair).unwrap(), keypair)
+ };
+ let (subject_doc, subject_key) = generate_core_document();
+ let credential_iota = generate_credential(issuer_iota_doc.id().as_str(), subject_doc.id().as_str());
+
+ let (issuer_core_doc, issuer_core_key) = generate_core_document();
+ let credential_core = generate_credential(issuer_core_doc.id().as_str(), subject_doc.id().as_str());
+
+ Self {
+ issuer_iota_doc,
+ issuer_iota_key,
+ subject_doc,
+ subject_key,
+ credential_iota,
+ issuer_core_doc,
+ issuer_core_key,
+ credential_core,
+ }
+ }
+
+ // Creates DID Documents with signed credentials.
+ fn new_with_signed_credentials() -> Self {
+ let mut setup = Self::new();
+ let MixedTestSetup {
+ ref issuer_iota_doc,
+ ref issuer_iota_key,
+ ref mut credential_iota,
+ ref issuer_core_doc,
+ ref issuer_core_key,
+ ref mut credential_core,
+ ..
+ } = setup;
+
+ issuer_iota_doc
+ .sign_data(
+ credential_iota,
+ issuer_iota_key.private(),
+ issuer_iota_doc.default_signing_method().unwrap().id(),
+ ProofOptions::default(),
+ )
+ .unwrap();
+ issuer_core_doc
+ .signer(issuer_core_key.private())
+ .options(ProofOptions::default())
+ .method(issuer_core_doc.methods().next().unwrap().id())
+ .sign(credential_core)
+ .unwrap();
+ setup
+ }
+ }
+
+ #[tokio::test]
+ async fn test_resolver_verify_presentation_mixed() {
+ let MixedTestSetup {
+ issuer_iota_doc,
+ credential_iota,
+ issuer_core_doc,
+ credential_core,
+ subject_doc,
+ subject_key,
+ ..
+ } = MixedTestSetup::new_with_signed_credentials();
+
+ // Subject signs the presentation.
+ let mut presentation =
+ generate_presentation(subject_doc.id().as_str(), [credential_iota, credential_core].to_vec());
+ let challenge: String = "475a7984-1bb5-4c4c-a56f-822bccd46441".to_owned();
+ subject_doc
+ .signer(subject_key.private())
+ .options(ProofOptions::new().challenge(challenge.clone()))
+ .method(subject_doc.methods().next().unwrap().id())
+ .sign(&mut presentation)
+ .unwrap();
+
+ // VALID: resolver supports presentations with issuers from different DID Methods.
+ let resolver: Resolver = Resolver::>::builder()
+ .client_builder(Client::builder().network(Network::Devnet).node_sync_disabled())
+ .build()
+ .await
+ .unwrap();
+ assert!(resolver
+ .verify_presentation(
+ &presentation,
+ &PresentationValidationOptions::new()
+ .presentation_verifier_options(VerifierOptions::new().challenge(challenge))
+ .subject_holder_relationship(SubjectHolderRelationship::AlwaysSubject),
+ FailFast::FirstError,
+ Some(&subject_doc),
+ Some(&[issuer_iota_doc.as_validator(), issuer_core_doc.as_validator()])
+ )
+ .await
+ .is_ok());
+ }
+
+ #[test]
+ fn test_validate_presentation_mixed() {
+ let MixedTestSetup {
+ issuer_iota_doc,
+ credential_iota,
+ issuer_core_doc,
+ credential_core,
+ subject_doc,
+ subject_key,
+ ..
+ } = MixedTestSetup::new_with_signed_credentials();
+
+ // Subject signs the presentation.
+ let mut presentation =
+ generate_presentation(subject_doc.id().as_str(), [credential_iota, credential_core].to_vec());
+ let challenge: String = "475a7984-1bb5-4c4c-a56f-822bccd46440".to_owned();
+ subject_doc
+ .signer(subject_key.private())
+ .options(ProofOptions::new().challenge(challenge.clone()))
+ .method(subject_doc.methods().next().unwrap().id())
+ .sign(&mut presentation)
+ .unwrap();
+
+ // Validate presentation.
+ let presentation_validation_options = PresentationValidationOptions::new()
+ .shared_validation_options(
+ CredentialValidationOptions::new()
+ .earliest_expiry_date(Timestamp::now_utc().checked_add(Duration::days(1)).unwrap())
+ .latest_issuance_date(Timestamp::now_utc()),
+ )
+ .presentation_verifier_options(VerifierOptions::new().challenge(challenge))
+ .subject_holder_relationship(SubjectHolderRelationship::AlwaysSubject);
+
+ // VALID: presentations with issuers from different DID Methods are supported.
+ assert!(PresentationValidator::validate(
+ &presentation,
+ &subject_doc,
+ &[issuer_iota_doc.as_validator(), issuer_core_doc.as_validator()],
+ &presentation_validation_options,
+ FailFast::FirstError,
+ )
+ .is_ok());
+
+ // INVALID: wrong holder fails.
+ assert!(PresentationValidator::validate(
+ &presentation,
+ &issuer_iota_doc,
+ &[issuer_iota_doc.as_validator(), issuer_core_doc.as_validator()],
+ &presentation_validation_options,
+ FailFast::FirstError,
+ )
+ .is_err());
+ assert!(PresentationValidator::validate(
+ &presentation,
+ &issuer_core_doc,
+ &[issuer_iota_doc.as_validator(), issuer_core_doc.as_validator()],
+ &presentation_validation_options,
+ FailFast::FirstError,
+ )
+ .is_err());
+
+ // INVALID: excluding the IOTA DID Document issuer fails.
+ assert!(PresentationValidator::validate(
+ &presentation,
+ &subject_doc,
+ &[issuer_core_doc.as_validator()],
+ &presentation_validation_options,
+ FailFast::FirstError,
+ )
+ .is_err());
+
+ // INVALID: excluding the core DID Document issuer fails.
+ assert!(PresentationValidator::validate(
+ &presentation,
+ &subject_doc,
+ &[&issuer_iota_doc],
+ &presentation_validation_options,
+ FailFast::FirstError,
+ )
+ .is_err());
+
+ // INVALID: using the wrong core DID Document fails.
+ assert!(PresentationValidator::validate(
+ &presentation,
+ &subject_doc,
+ &[issuer_iota_doc.as_validator(), subject_doc.as_validator()],
+ &presentation_validation_options,
+ FailFast::FirstError,
+ )
+ .is_err());
+
+ // INVALID: excluding all issuers fails.
+ let empty_issuers: &[&dyn ValidatorDocument] = &[];
+ assert!(PresentationValidator::validate(
+ &presentation,
+ &subject_doc,
+ empty_issuers,
+ &presentation_validation_options,
+ FailFast::FirstError,
+ )
+ .is_err());
+ }
+
+ #[test]
+ fn test_validate_credential_mixed() {
+ let MixedTestSetup {
+ issuer_iota_doc,
+ credential_iota,
+ issuer_core_doc,
+ credential_core,
+ subject_doc,
+ ..
+ } = MixedTestSetup::new_with_signed_credentials();
+ let options = CredentialValidationOptions::new()
+ .earliest_expiry_date(Timestamp::now_utc().checked_add(Duration::days(1)).unwrap())
+ .latest_issuance_date(Timestamp::now_utc());
+
+ // VALID: credential validation works for issuers with different DID Methods.
+ assert!(CredentialValidator::validate(&credential_iota, &issuer_iota_doc, &options, FailFast::FirstError).is_ok());
+ assert!(CredentialValidator::validate(&credential_core, &issuer_core_doc, &options, FailFast::FirstError).is_ok());
+
+ // INVALID: wrong issuer fails.
+ assert!(CredentialValidator::validate(&credential_iota, &issuer_core_doc, &options, FailFast::FirstError).is_err());
+ assert!(CredentialValidator::validate(&credential_iota, &subject_doc, &options, FailFast::FirstError).is_err());
+ assert!(CredentialValidator::validate(&credential_core, &issuer_iota_doc, &options, FailFast::FirstError).is_err());
+ assert!(CredentialValidator::validate(&credential_core, &subject_doc, &options, FailFast::FirstError).is_err());
+ }
+}
diff --git a/identity_iota_core/src/document/iota_document.rs b/identity_iota_core/src/document/iota_document.rs
index 875e98008d..9ed8315efa 100644
--- a/identity_iota_core/src/document/iota_document.rs
+++ b/identity_iota_core/src/document/iota_document.rs
@@ -5,6 +5,10 @@ use core::fmt;
use core::fmt::Debug;
use core::fmt::Display;
+use serde;
+use serde::Deserialize;
+use serde::Serialize;
+
use identity_core::common::Object;
use identity_core::common::OneOrSet;
use identity_core::common::OrderedSet;
@@ -22,6 +26,7 @@ use identity_core::crypto::PublicKey;
use identity_core::crypto::SetSignature;
use identity_core::crypto::Signer;
use identity_did::document::CoreDocument;
+use identity_did::document::Document;
use identity_did::service::Service;
use identity_did::utils::DIDUrlQuery;
use identity_did::verifiable::DocumentSigner;
@@ -33,9 +38,6 @@ use identity_did::verification::MethodType;
use identity_did::verification::MethodUriType;
use identity_did::verification::TryMethod;
use identity_did::verification::VerificationMethod;
-use serde;
-use serde::Deserialize;
-use serde::Serialize;
use crate::did::IotaDID;
use crate::did::IotaDIDUrl;
@@ -448,7 +450,7 @@ impl IotaDocument {
/// serialization fails, or the verification operation fails.
pub fn verify_data(&self, data: &X, options: &VerifierOptions) -> Result<()>
where
- X: Serialize + GetSignature,
+ X: Serialize + GetSignature + ?Sized,
{
self.document.verify_data(data, options).map_err(Into::into)
}
@@ -619,59 +621,79 @@ impl IotaDocument {
}
}
+impl Document for IotaDocument {
+ type D = IotaDID;
+ type U = Object;
+ type V = Object;
+
+ fn id(&self) -> &Self::D {
+ IotaDocument::id(self)
+ }
+
+ fn resolve_service<'query, 'me, Q>(&'me self, query: Q) -> Option<&Service>
+ where
+ Q: Into>,
+ {
+ self.core_document().resolve_service(query)
+ }
+
+ fn resolve_method<'query, 'me, Q>(
+ &'me self,
+ query: Q,
+ scope: Option,
+ ) -> Option<&VerificationMethod>
+ where
+ Q: Into>,
+ {
+ self.core_document().resolve_method(query, scope)
+ }
+
+ fn verify_data(&self, data: &X, options: &VerifierOptions) -> identity_did::Result<()>
+ where
+ X: Serialize + GetSignature + ?Sized,
+ {
+ self.core_document().verify_data(data, options)
+ }
+}
+
#[cfg(feature = "revocation-bitmap")]
mod iota_document_revocation {
- use super::IotaDocument;
- use crate::did::IotaDID;
- use crate::did::IotaDIDUrl;
+ use identity_did::utils::DIDUrlQuery;
+
use crate::Error;
use crate::Result;
- use identity_did::revocation::RevocationBitmap;
- use identity_did::service::Service;
- impl IotaDocument {
- /// If the document has a [`RevocationBitmap`] service identified by `fragment`,
- /// revoke all credentials with a `revocationBitmapIndex` in `credential_indices`.
- pub fn revoke_credentials(&mut self, service_id: &IotaDIDUrl, credential_indices: &[u32]) -> Result<()> {
- self.update_revocation_bitmap(service_id, |revocation_bitmap| {
- // Revoke all given credential indices.
- for credential in credential_indices {
- revocation_bitmap.revoke(*credential);
- }
- })
- }
+ use super::IotaDocument;
- /// If the document has a [`RevocationBitmap`] service identified by `fragment`,
- /// unrevoke all credentials with a `revocationBitmapIndex` in `credential_indices`.
- pub fn unrevoke_credentials(&mut self, service_id: &IotaDIDUrl, credential_indices: &[u32]) -> Result<()> {
- self.update_revocation_bitmap(service_id, |revocation_bitmap| {
- // Unrevoke all given credential indices.
- for credential in credential_indices {
- revocation_bitmap.unrevoke(*credential);
- }
- })
+ impl IotaDocument {
+ /// If the document has a [`RevocationBitmap`](identity_did::revocation::RevocationBitmap)
+ /// service identified by `service_query`, revoke all credentials with a
+ /// `revocationBitmapIndex` in `credential_indices`.
+ pub fn revoke_credentials<'query, 'me, Q>(&mut self, service_query: Q, credential_indices: &[u32]) -> Result<()>
+ where
+ Q: Into>,
+ {
+ self
+ .core_document_mut()
+ .revoke_credentials(service_query, credential_indices)
+ .map_err(Error::RevocationError)
}
- fn update_revocation_bitmap(&mut self, service_id: &IotaDIDUrl, f: F) -> Result<()>
+ /// If the document has a [`RevocationBitmap`](identity_did::revocation::RevocationBitmap)
+ /// service with an id by `service_query`, unrevoke all credentials with a
+ /// `revocationBitmapIndex` in `credential_indices`.
+ pub fn unrevoke_credentials<'query, 'me, Q>(
+ &'me mut self,
+ service_query: Q,
+ credential_indices: &[u32],
+ ) -> Result<()>
where
- F: FnOnce(&mut RevocationBitmap),
+ Q: Into>,
{
- let service: &mut Service = self
+ self
.core_document_mut()
- .service_mut()
- .iter_mut_unchecked()
- .find(|service| service.id() == service_id)
- .ok_or(Error::RevocationError(identity_did::Error::InvalidService(
- "invalid id - service not found",
- )))?;
-
- let mut revocation_bitmap: RevocationBitmap = (&*service).try_into().map_err(Error::RevocationError)?;
-
- f(&mut revocation_bitmap);
-
- std::mem::swap(service.service_endpoint_mut(), &mut revocation_bitmap.to_endpoint()?);
-
- Ok(())
+ .unrevoke_credentials(service_query, credential_indices)
+ .map_err(Error::RevocationError)
}
}
}