From cbd53ed8185d4018d0dd99b8541776e190144fa5 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Fri, 29 Jan 2021 10:26:23 -0800 Subject: [PATCH 01/28] Remove crypto re-export --- examples/credential/src/main.rs | 2 +- examples/diff-chain/src/main.rs | 2 +- examples/resolution/src/main.rs | 6 ++++-- identity-iota/src/lib.rs | 5 ----- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/examples/credential/src/main.rs b/examples/credential/src/main.rs index b2a810dd00..390357c265 100644 --- a/examples/credential/src/main.rs +++ b/examples/credential/src/main.rs @@ -8,6 +8,7 @@ use identity_core::{ common::{Url, Value}, convert::{FromJson as _, ToJson as _}, credential::{Credential, CredentialBuilder, CredentialSubject, VerifiableCredential}, + crypto::KeyPair, did_doc::MethodScope, did_url::DID, json, @@ -15,7 +16,6 @@ use identity_core::{ use identity_iota::{ client::Client, credential::{CredentialValidation, CredentialValidator}, - crypto::KeyPair, did::IotaDocument, error::Result, }; diff --git a/examples/diff-chain/src/main.rs b/examples/diff-chain/src/main.rs index ce6cf554e3..be4f864fe9 100644 --- a/examples/diff-chain/src/main.rs +++ b/examples/diff-chain/src/main.rs @@ -4,13 +4,13 @@ //! An example that utilizes a diff and auth chain to publish updates to a //! DID Document. use identity_core::{ + crypto::KeyPair, did_doc::{MethodBuilder, MethodData, MethodRef, MethodType}, proof::JcsEd25519Signature2020, }; use identity_iota::{ chain::{AuthChain, DocumentChain}, client::{Client, ClientBuilder, Network}, - crypto::KeyPair, did::{DocumentDiff, IotaDocument}, error::Result, tangle::MessageId, diff --git a/examples/resolution/src/main.rs b/examples/resolution/src/main.rs index 2f94791b79..3f206d0eef 100644 --- a/examples/resolution/src/main.rs +++ b/examples/resolution/src/main.rs @@ -3,10 +3,12 @@ //! A basic example that generates a DID Document, publishes it to the Tangle, //! and retrieves information through DID Document resolution/dereferencing. -use identity_core::resolver::{dereference, resolve, Dereference, Resolution}; +use identity_core::{ + crypto::KeyPair, + resolver::{dereference, resolve, Dereference, Resolution}, +}; use identity_iota::{ client::Client, - crypto::KeyPair, did::{IotaDID, IotaDocument}, error::Result, }; diff --git a/identity-iota/src/lib.rs b/identity-iota/src/lib.rs index 671517de97..481a1bc27d 100644 --- a/identity-iota/src/lib.rs +++ b/identity-iota/src/lib.rs @@ -27,8 +27,3 @@ pub mod did; pub mod error; pub mod tangle; pub mod utils; - -/// Re-export `identity_core::crypto`; in the future this will be `crypto.rs`. -pub mod crypto { - pub use identity_core::crypto::*; -} From f455f0dea4d603d1ddcb4599e5a0989a6bd0cefd Mon Sep 17 00:00:00 2001 From: l1h3r Date: Fri, 29 Jan 2021 10:31:47 -0800 Subject: [PATCH 02/28] Use 2 spaces --- bindings/wasm/src/did.rs | 88 +- bindings/wasm/src/doc.rs | 528 +++--- bindings/wasm/src/iota.rs | 120 +- bindings/wasm/src/key.rs | 104 +- bindings/wasm/src/lib.rs | 6 +- bindings/wasm/src/pubkey.rs | 82 +- bindings/wasm/src/vc.rs | 94 +- bindings/wasm/src/vp.rs | 94 +- examples/credential/src/main.rs | 118 +- examples/diff-chain/src/main.rs | 252 +-- examples/resolution/src/main.rs | 62 +- identity-core/examples/merkle_tree.rs | 70 +- identity-core/src/common/context.rs | 42 +- identity-core/src/common/one_or_many.rs | 262 +-- identity-core/src/common/timestamp.rs | 162 +- identity-core/src/common/url.rs | 72 +- identity-core/src/convert/json.rs | 158 +- identity-core/src/convert/serde_into.rs | 18 +- identity-core/src/credential/credential.rs | 356 ++-- .../src/credential/credential_builder.rs | 401 ++-- identity-core/src/credential/presentation.rs | 180 +- .../src/credential/presentation_builder.rs | 277 +-- .../src/credential/types/credential_schema.rs | 60 +- .../src/credential/types/credential_status.rs | 56 +- .../credential/types/credential_subject.rs | 82 +- .../src/credential/types/evidence.rs | 104 +- identity-core/src/credential/types/issuer.rs | 46 +- .../src/credential/types/refresh_service.rs | 56 +- .../src/credential/types/terms_of_use.rs | 106 +- .../src/credential/verifiable_credential.rs | 100 +- .../src/credential/verifiable_presentation.rs | 102 +- identity-core/src/crypto/key_impl.rs | 84 +- identity-core/src/crypto/key_pair.rs | 44 +- .../src/crypto/merkle_tree/digest.rs | 30 +- identity-core/src/crypto/merkle_tree/hash.rs | 82 +- identity-core/src/crypto/merkle_tree/math.rs | 54 +- .../src/crypto/merkle_tree/merkle.rs | 306 ++-- identity-core/src/crypto/merkle_tree/node.rs | 80 +- identity-core/src/crypto/merkle_tree/proof.rs | 56 +- identity-core/src/crypto/merkle_tree/tree.rs | 70 +- identity-core/src/error.rs | 72 +- identity-core/src/lib.rs | 14 +- .../src/proof/jcsed25519signature2020.rs | 367 ++-- identity-core/src/resolver/dereference.rs | 32 +- .../src/resolver/document_metadata.rs | 40 +- identity-core/src/resolver/error_kind.rs | 22 +- identity-core/src/resolver/impls.rs | 518 +++--- identity-core/src/resolver/input_metadata.rs | 32 +- identity-core/src/resolver/resolution.rs | 32 +- .../src/resolver/resolution_metadata.rs | 58 +- identity-core/src/resolver/resource.rs | 92 +- identity-core/src/resolver/traits.rs | 34 +- identity-core/src/utils/base58.rs | 14 +- identity-core/src/utils/jcs_sha256.rs | 4 +- identity-diff/derive/src/impls.rs | 4 +- identity-diff/derive/src/impls/enums.rs | 1622 ++++++++--------- identity-diff/derive/src/impls/structs.rs | 1188 ++++++------ identity-diff/derive/src/lib.rs | 58 +- identity-diff/derive/src/model.rs | 622 +++---- identity-diff/derive/src/utils.rs | 90 +- identity-diff/src/did_doc.rs | 1515 ++++++++------- identity-diff/src/error.rs | 48 +- identity-diff/src/hashmap.rs | 338 ++-- identity-diff/src/hashset.rs | 250 +-- identity-diff/src/option.rs | 132 +- identity-diff/src/string.rs | 112 +- identity-diff/src/traits.rs | 20 +- identity-diff/src/value.rs | 108 +- identity-diff/src/vec.rs | 401 ++-- identity-diff/tests/derive_enum_test.rs | 364 ++-- identity-iota/src/chain/auth.rs | 203 ++- identity-iota/src/chain/diff.rs | 224 +-- identity-iota/src/chain/document.rs | 280 +-- identity-iota/src/client/client.rs | 342 ++-- identity-iota/src/client/client_builder.rs | 76 +- identity-iota/src/client/network.rs | 170 +- identity-iota/src/client/resolver.rs | 46 +- identity-iota/src/client/txn_printer.rs | 76 +- identity-iota/src/credential/validator.rs | 233 +-- identity-iota/src/did/did.rs | 780 ++++---- identity-iota/src/did/did_segments.rs | 54 +- identity-iota/src/did/document.rs | 804 ++++---- identity-iota/src/did/document_builder.rs | 200 +- identity-iota/src/did/document_diff.rs | 190 +- identity-iota/src/did/document_properties.rs | 36 +- identity-iota/src/error.rs | 56 +- identity-iota/src/tangle/message.rs | 142 +- identity-iota/src/tangle/message_id.rs | 86 +- identity-iota/src/tangle/message_index.rs | 190 +- identity-iota/src/tangle/traits.rs | 8 +- identity-iota/src/utils.rs | 104 +- rustfmt.toml | 1 + 92 files changed, 8789 insertions(+), 8779 deletions(-) diff --git a/bindings/wasm/src/did.rs b/bindings/wasm/src/did.rs index 77666459f4..d88803cacb 100644 --- a/bindings/wasm/src/did.rs +++ b/bindings/wasm/src/did.rs @@ -14,56 +14,56 @@ pub struct DID(pub(crate) IotaDID); #[wasm_bindgen] impl DID { - fn create(pubkey: &[u8], network: Option<&str>) -> Result { - IotaDID::with_network(pubkey, network).map_err(js_err).map(Self) - } + fn create(pubkey: &[u8], network: Option<&str>) -> Result { + IotaDID::with_network(pubkey, network).map_err(js_err).map(Self) + } - /// Creates a new `DID` from a `Key` object. - #[wasm_bindgen(constructor)] - pub fn new(key: &Key, network: Option) -> Result { - Self::create(key.0.public().as_ref(), network.as_deref()) - } + /// Creates a new `DID` from a `Key` object. + #[wasm_bindgen(constructor)] + pub fn new(key: &Key, network: Option) -> Result { + Self::create(key.0.public().as_ref(), network.as_deref()) + } - /// Creates a new `DID` from a base58-encoded public key. - #[wasm_bindgen(js_name = fromBase58Key)] - pub fn from_base58_key(key: &str, network: Option) -> Result { - Self::create(&decode_b58(key).map_err(js_err)?, network.as_deref()) - } + /// Creates a new `DID` from a base58-encoded public key. + #[wasm_bindgen(js_name = fromBase58Key)] + pub fn from_base58_key(key: &str, network: Option) -> Result { + Self::create(&decode_b58(key).map_err(js_err)?, network.as_deref()) + } - /// Parses a `DID` from the input string. - #[wasm_bindgen] - pub fn parse(input: String) -> Result { - IotaDID::parse(input).map_err(js_err).map(Self) - } + /// Parses a `DID` from the input string. + #[wasm_bindgen] + pub fn parse(input: String) -> Result { + IotaDID::parse(input).map_err(js_err).map(Self) + } - /// Returns the IOTA tangle network of the `DID`. - #[wasm_bindgen(getter)] - pub fn network(&self) -> String { - self.0.network().into() - } + /// Returns the IOTA tangle network of the `DID`. + #[wasm_bindgen(getter)] + pub fn network(&self) -> String { + self.0.network().into() + } - /// Returns the IOTA tangle shard of the `DID` (if any). - #[wasm_bindgen(getter)] - pub fn shard(&self) -> Option { - self.0.shard().map(Into::into) - } + /// Returns the IOTA tangle shard of the `DID` (if any). + #[wasm_bindgen(getter)] + pub fn shard(&self) -> Option { + self.0.shard().map(Into::into) + } - /// Returns the unique tag of the `DID`. - #[wasm_bindgen(getter)] - pub fn tag(&self) -> String { - self.0.method_id().into() - } + /// Returns the unique tag of the `DID`. + #[wasm_bindgen(getter)] + pub fn tag(&self) -> String { + self.0.method_id().into() + } - /// Returns the IOTA tangle address of the `DID`. - #[wasm_bindgen(getter)] - pub fn address(&self) -> String { - self.0.address() - } + /// Returns the IOTA tangle address of the `DID`. + #[wasm_bindgen(getter)] + pub fn address(&self) -> String { + self.0.address() + } - /// Returns the `DID` object as a string. - #[allow(clippy::inherent_to_string)] - #[wasm_bindgen(js_name = toString)] - pub fn to_string(&self) -> String { - self.0.to_string() - } + /// Returns the `DID` object as a string. + #[allow(clippy::inherent_to_string)] + #[wasm_bindgen(js_name = toString)] + pub fn to_string(&self) -> String { + self.0.to_string() + } } diff --git a/bindings/wasm/src/doc.rs b/bindings/wasm/src/doc.rs index b41944da0b..f07d59dd52 100644 --- a/bindings/wasm/src/doc.rs +++ b/bindings/wasm/src/doc.rs @@ -2,41 +2,39 @@ // SPDX-License-Identifier: Apache-2.0 use identity_core::{ - convert::{FromJson as _, SerdeInto as _}, - did_doc::{ - DIDKey, Document, DocumentBuilder, MethodIndex, MethodScope, Service, ServiceBuilder, VerifiableDocument, - }, + convert::{FromJson as _, SerdeInto as _}, + did_doc::{DIDKey, Document, DocumentBuilder, MethodIndex, MethodScope, Service, ServiceBuilder, VerifiableDocument}, }; use identity_iota::{ - did::{DocumentDiff, IotaDocument, Properties}, - tangle::MessageId, + did::{DocumentDiff, IotaDocument, Properties}, + tangle::MessageId, }; use wasm_bindgen::prelude::*; use crate::{ - did::DID, - js_err, - key::Key, - pubkey::{PubKey, DEFAULT_KEY}, + did::DID, + js_err, + key::Key, + pubkey::{PubKey, DEFAULT_KEY}, }; #[wasm_bindgen(inspectable)] pub struct NewDoc { - key: Key, - doc: Doc, + key: Key, + doc: Doc, } #[wasm_bindgen] impl NewDoc { - #[wasm_bindgen(getter)] - pub fn key(&self) -> Key { - self.key.clone() - } - - #[wasm_bindgen(getter)] - pub fn doc(&self) -> Doc { - self.doc.clone() - } + #[wasm_bindgen(getter)] + pub fn key(&self) -> Key { + self.key.clone() + } + + #[wasm_bindgen(getter)] + pub fn doc(&self) -> Doc { + self.doc.clone() + } } #[wasm_bindgen(inspectable)] @@ -45,246 +43,252 @@ pub struct Doc(pub(crate) IotaDocument); #[wasm_bindgen] impl Doc { - #[wasm_bindgen(constructor)] - pub fn new(authentication: &PubKey) -> Result { - let mut did = authentication.0.id().clone(); - did.set_fragment(None); - - let base: VerifiableDocument = DocumentBuilder::new(Properties::new()) - .id(did) - // Note: We use a reference to the verification method due to - // upstream limitations. - .authentication(authentication.0.id().clone()) - .verification_method(authentication.0.clone()) - .build() - .map(VerifiableDocument::new) - .map_err(js_err)?; - - IotaDocument::try_from_document(base.serde_into().map_err(js_err)?) - .map_err(js_err) - .map(Self) - } - - /// Generates a keypair and DID Document, supported key_type is "Ed25519VerificationKey2018" - #[wasm_bindgen(js_name = generateRandom)] - pub fn generate_random(key_type: &str, network: Option, tag: Option) -> Result { - let key: Key = Key::new(key_type)?; - let did: DID = DID::new(&key, network)?; - let pkey: PubKey = PubKey::new(&did, key_type, &key.public(), tag)?; - - Ok(NewDoc { - doc: Self::new(&pkey)?, - key, - }) - } - - /// Generates an Ed25519 keypair and DID Document - #[wasm_bindgen(js_name = generateEd25519)] - pub fn generate_ed25519(network: Option, tag: Option) -> Result { - Self::generate_random(DEFAULT_KEY, network, tag) - } - - #[wasm_bindgen(getter)] - pub fn id(&self) -> String { - self.0.id().to_string() - } - - #[wasm_bindgen(getter, js_name = authChain)] - pub fn auth_chain(&self) -> String { - self.0.id().address() - } - - #[wasm_bindgen(getter, js_name = diffChain)] - pub fn diff_chain(&self) -> String { - String::new() // TODO: FIXME - } - - #[wasm_bindgen(getter)] - pub fn proof(&self) -> Result { - self.0 - .proof() - .map(|proof| JsValue::from_serde(proof)) - .transpose() - .map_err(js_err) - .map(|option| option.unwrap_or(JsValue::NULL)) - } - - #[wasm_bindgen] - pub fn sign(&mut self, key: &Key) -> Result { - self.0.sign(key.0.secret()).map_err(js_err).map(|_| JsValue::NULL) - } - - /// Verify the signature with the authentication_key - #[wasm_bindgen] - pub fn verify(&self) -> bool { - self.0.verify().is_ok() - } - - /// Generate the difference between two DID Documents and sign it - #[wasm_bindgen] - pub fn diff(&self, other: &Doc, key: &Key, prev_msg: String) -> Result { - let doc: IotaDocument = other.0.clone(); - - let diff: DocumentDiff = self - .0 - .diff(&doc, key.0.secret(), MessageId::new(prev_msg)) - .map_err(js_err)?; - - JsValue::from_serde(&diff).map_err(js_err) - } - - /// Verify the signature in a diff with the authentication_key - #[wasm_bindgen(js_name = verifyDiff)] - pub fn verify_diff(&self, diff: String) -> bool { - match DocumentDiff::from_json(&diff) { - Ok(diff) => self.0.verify_data(&diff).is_ok(), - Err(_) => false, - } - } - - #[wasm_bindgen(js_name = updateService)] - pub fn update_service(&mut self, did: DID, url: String, service_type: String) -> Result<(), JsValue> { - let service: Service = ServiceBuilder::default() - .id(did.0.into()) - .type_(service_type) - .service_endpoint(url.parse().map_err(js_err)?) - .build() - .map_err(js_err)?; - - Self::mutate(self, |doc| doc.service_mut().update(DIDKey::new(service)))?; - - Ok(()) - } - - #[wasm_bindgen(js_name = clearServices)] - pub fn clear_services(&mut self) -> Result<(), JsValue> { - Self::mutate(self, |doc| doc.service_mut().clear()) - } - - #[wasm_bindgen(js_name = updateAuth)] - pub fn update_auth(&mut self, public_key: &PubKey) -> Result { - Self::mutate(self, |doc| { - doc.authentication_mut() - .update(DIDKey::new(public_key.0.clone().into())) - }) - } - - #[wasm_bindgen(js_name = clearAuth)] - pub fn clear_auth(&mut self) -> Result<(), JsValue> { - Self::mutate(self, |doc| doc.authentication_mut().clear()) - } - - #[wasm_bindgen(js_name = updateAssert)] - pub fn update_assert(&mut self, public_key: &PubKey) -> Result { - Self::mutate(self, |doc| { - doc.assertion_method_mut() - .update(DIDKey::new(public_key.0.clone().into())) - }) - } - - #[wasm_bindgen(js_name = clearAssert)] - pub fn clear_assert(&mut self) -> Result<(), JsValue> { - Self::mutate(self, |doc| doc.assertion_method_mut().clear()) - } - - #[wasm_bindgen(js_name = updateVerification)] - pub fn update_verification(&mut self, public_key: &PubKey) -> Result { - Self::mutate(self, |doc| { - doc.verification_method_mut().update(DIDKey::new(public_key.0.clone())) - }) - } - - #[wasm_bindgen(js_name = clearVerification)] - pub fn clear_verification(&mut self) -> Result<(), JsValue> { - Self::mutate(self, |doc| doc.verification_method_mut().clear()) - } - - #[wasm_bindgen(js_name = updateDelegation)] - pub fn update_delegation(&mut self, public_key: &PubKey) -> Result { - Self::mutate(self, |doc| { - doc.capability_delegation_mut() - .update(DIDKey::new(public_key.0.clone().into())) - }) - } - - #[wasm_bindgen(js_name = clearDelegation)] - pub fn clear_delegation(&mut self) -> Result<(), JsValue> { - Self::mutate(self, |doc| doc.capability_delegation_mut().clear()) - } - - #[wasm_bindgen(js_name = updateInvocation)] - pub fn update_invocation(&mut self, public_key: &PubKey) -> Result { - Self::mutate(self, |doc| { - doc.capability_invocation_mut() - .update(DIDKey::new(public_key.0.clone().into())) - }) - } - - #[wasm_bindgen(js_name = clearInvocation)] - pub fn clear_invocation(&mut self) -> Result<(), JsValue> { - Self::mutate(self, |doc| doc.capability_invocation_mut().clear()) - } - - #[wasm_bindgen(js_name = updateAgreement)] - pub fn update_agreement(&mut self, public_key: &PubKey) -> Result { - Self::mutate(self, |doc| { - doc.key_agreement_mut().update(DIDKey::new(public_key.0.clone().into())) - }) - } - - #[wasm_bindgen(js_name = clearAgreement)] - pub fn clear_agreement(&mut self) -> Result<(), JsValue> { - Self::mutate(self, |doc| doc.key_agreement_mut().clear()) - } - - #[wasm_bindgen(js_name = resolveKey)] - pub fn resolve_key(&mut self, ident: JsValue, scope: Option) -> Result { - let borrow: String; - - let ident: MethodIndex = if let Some(number) = ident.as_f64() { - MethodIndex::Index(number.to_string().parse().map_err(js_err)?) - } else if let Some(ident) = ident.as_string() { - borrow = ident; - MethodIndex::Ident(&borrow) - } else { - return Err("Invalid Key Identifier".into()); - }; - - let scope: MethodScope = scope - .map(|scope| scope.parse::()) - .transpose() - .map_err(js_err)? - .unwrap_or(MethodScope::Authentication); - - self.0 - .resolve((ident, scope)) - .map(|wrap| wrap.into_method().clone()) - .map(PubKey) - .ok_or_else(|| "Key Not Found".into()) - } - - /// Serializes a `Doc` object as a JSON object. - #[wasm_bindgen(js_name = toJSON)] - pub fn to_json(&self) -> Result { - JsValue::from_serde(&self.0).map_err(js_err) - } - - /// Deserializes a `Doc` object from a JSON object. - #[wasm_bindgen(js_name = fromJSON)] - pub fn from_json(json: &JsValue) -> Result { - json.into_serde().map_err(js_err).map(Self) - } - - // Bypass IotaDocument Deref limitations and allow modifications to the - // core DID Document type. - // - // Uses `serde` for conversions and re-validates the document after mutation. - fn mutate(this: &mut Self, f: impl FnOnce(&mut Document) -> T) -> Result { - let mut document: Document = this.0.serde_into().map_err(js_err)?; - let output: T = f(&mut document); - - this.0 = IotaDocument::try_from_document(document).map_err(js_err)?; - - Ok(output) + #[wasm_bindgen(constructor)] + pub fn new(authentication: &PubKey) -> Result { + let mut did = authentication.0.id().clone(); + did.set_fragment(None); + + let base: VerifiableDocument = DocumentBuilder::new(Properties::new()) + .id(did) + // Note: We use a reference to the verification method due to + // upstream limitations. + .authentication(authentication.0.id().clone()) + .verification_method(authentication.0.clone()) + .build() + .map(VerifiableDocument::new) + .map_err(js_err)?; + + IotaDocument::try_from_document(base.serde_into().map_err(js_err)?) + .map_err(js_err) + .map(Self) + } + + /// Generates a keypair and DID Document, supported key_type is "Ed25519VerificationKey2018" + #[wasm_bindgen(js_name = generateRandom)] + pub fn generate_random(key_type: &str, network: Option, tag: Option) -> Result { + let key: Key = Key::new(key_type)?; + let did: DID = DID::new(&key, network)?; + let pkey: PubKey = PubKey::new(&did, key_type, &key.public(), tag)?; + + Ok(NewDoc { + doc: Self::new(&pkey)?, + key, + }) + } + + /// Generates an Ed25519 keypair and DID Document + #[wasm_bindgen(js_name = generateEd25519)] + pub fn generate_ed25519(network: Option, tag: Option) -> Result { + Self::generate_random(DEFAULT_KEY, network, tag) + } + + #[wasm_bindgen(getter)] + pub fn id(&self) -> String { + self.0.id().to_string() + } + + #[wasm_bindgen(getter, js_name = authChain)] + pub fn auth_chain(&self) -> String { + self.0.id().address() + } + + #[wasm_bindgen(getter, js_name = diffChain)] + pub fn diff_chain(&self) -> String { + String::new() // TODO: FIXME + } + + #[wasm_bindgen(getter)] + pub fn proof(&self) -> Result { + self + .0 + .proof() + .map(|proof| JsValue::from_serde(proof)) + .transpose() + .map_err(js_err) + .map(|option| option.unwrap_or(JsValue::NULL)) + } + + #[wasm_bindgen] + pub fn sign(&mut self, key: &Key) -> Result { + self.0.sign(key.0.secret()).map_err(js_err).map(|_| JsValue::NULL) + } + + /// Verify the signature with the authentication_key + #[wasm_bindgen] + pub fn verify(&self) -> bool { + self.0.verify().is_ok() + } + + /// Generate the difference between two DID Documents and sign it + #[wasm_bindgen] + pub fn diff(&self, other: &Doc, key: &Key, prev_msg: String) -> Result { + let doc: IotaDocument = other.0.clone(); + + let diff: DocumentDiff = self + .0 + .diff(&doc, key.0.secret(), MessageId::new(prev_msg)) + .map_err(js_err)?; + + JsValue::from_serde(&diff).map_err(js_err) + } + + /// Verify the signature in a diff with the authentication_key + #[wasm_bindgen(js_name = verifyDiff)] + pub fn verify_diff(&self, diff: String) -> bool { + match DocumentDiff::from_json(&diff) { + Ok(diff) => self.0.verify_data(&diff).is_ok(), + Err(_) => false, } + } + + #[wasm_bindgen(js_name = updateService)] + pub fn update_service(&mut self, did: DID, url: String, service_type: String) -> Result<(), JsValue> { + let service: Service = ServiceBuilder::default() + .id(did.0.into()) + .type_(service_type) + .service_endpoint(url.parse().map_err(js_err)?) + .build() + .map_err(js_err)?; + + Self::mutate(self, |doc| doc.service_mut().update(DIDKey::new(service)))?; + + Ok(()) + } + + #[wasm_bindgen(js_name = clearServices)] + pub fn clear_services(&mut self) -> Result<(), JsValue> { + Self::mutate(self, |doc| doc.service_mut().clear()) + } + + #[wasm_bindgen(js_name = updateAuth)] + pub fn update_auth(&mut self, public_key: &PubKey) -> Result { + Self::mutate(self, |doc| { + doc + .authentication_mut() + .update(DIDKey::new(public_key.0.clone().into())) + }) + } + + #[wasm_bindgen(js_name = clearAuth)] + pub fn clear_auth(&mut self) -> Result<(), JsValue> { + Self::mutate(self, |doc| doc.authentication_mut().clear()) + } + + #[wasm_bindgen(js_name = updateAssert)] + pub fn update_assert(&mut self, public_key: &PubKey) -> Result { + Self::mutate(self, |doc| { + doc + .assertion_method_mut() + .update(DIDKey::new(public_key.0.clone().into())) + }) + } + + #[wasm_bindgen(js_name = clearAssert)] + pub fn clear_assert(&mut self) -> Result<(), JsValue> { + Self::mutate(self, |doc| doc.assertion_method_mut().clear()) + } + + #[wasm_bindgen(js_name = updateVerification)] + pub fn update_verification(&mut self, public_key: &PubKey) -> Result { + Self::mutate(self, |doc| { + doc.verification_method_mut().update(DIDKey::new(public_key.0.clone())) + }) + } + + #[wasm_bindgen(js_name = clearVerification)] + pub fn clear_verification(&mut self) -> Result<(), JsValue> { + Self::mutate(self, |doc| doc.verification_method_mut().clear()) + } + + #[wasm_bindgen(js_name = updateDelegation)] + pub fn update_delegation(&mut self, public_key: &PubKey) -> Result { + Self::mutate(self, |doc| { + doc + .capability_delegation_mut() + .update(DIDKey::new(public_key.0.clone().into())) + }) + } + + #[wasm_bindgen(js_name = clearDelegation)] + pub fn clear_delegation(&mut self) -> Result<(), JsValue> { + Self::mutate(self, |doc| doc.capability_delegation_mut().clear()) + } + + #[wasm_bindgen(js_name = updateInvocation)] + pub fn update_invocation(&mut self, public_key: &PubKey) -> Result { + Self::mutate(self, |doc| { + doc + .capability_invocation_mut() + .update(DIDKey::new(public_key.0.clone().into())) + }) + } + + #[wasm_bindgen(js_name = clearInvocation)] + pub fn clear_invocation(&mut self) -> Result<(), JsValue> { + Self::mutate(self, |doc| doc.capability_invocation_mut().clear()) + } + + #[wasm_bindgen(js_name = updateAgreement)] + pub fn update_agreement(&mut self, public_key: &PubKey) -> Result { + Self::mutate(self, |doc| { + doc.key_agreement_mut().update(DIDKey::new(public_key.0.clone().into())) + }) + } + + #[wasm_bindgen(js_name = clearAgreement)] + pub fn clear_agreement(&mut self) -> Result<(), JsValue> { + Self::mutate(self, |doc| doc.key_agreement_mut().clear()) + } + + #[wasm_bindgen(js_name = resolveKey)] + pub fn resolve_key(&mut self, ident: JsValue, scope: Option) -> Result { + let borrow: String; + + let ident: MethodIndex = if let Some(number) = ident.as_f64() { + MethodIndex::Index(number.to_string().parse().map_err(js_err)?) + } else if let Some(ident) = ident.as_string() { + borrow = ident; + MethodIndex::Ident(&borrow) + } else { + return Err("Invalid Key Identifier".into()); + }; + + let scope: MethodScope = scope + .map(|scope| scope.parse::()) + .transpose() + .map_err(js_err)? + .unwrap_or(MethodScope::Authentication); + + self + .0 + .resolve((ident, scope)) + .map(|wrap| wrap.into_method().clone()) + .map(PubKey) + .ok_or_else(|| "Key Not Found".into()) + } + + /// Serializes a `Doc` object as a JSON object. + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> Result { + JsValue::from_serde(&self.0).map_err(js_err) + } + + /// Deserializes a `Doc` object from a JSON object. + #[wasm_bindgen(js_name = fromJSON)] + pub fn from_json(json: &JsValue) -> Result { + json.into_serde().map_err(js_err).map(Self) + } + + // Bypass IotaDocument Deref limitations and allow modifications to the + // core DID Document type. + // + // Uses `serde` for conversions and re-validates the document after mutation. + fn mutate(this: &mut Self, f: impl FnOnce(&mut Document) -> T) -> Result { + let mut document: Document = this.0.serde_into().map_err(js_err)?; + let output: T = f(&mut document); + + this.0 = IotaDocument::try_from_document(document).map_err(js_err)?; + + Ok(output) + } } diff --git a/bindings/wasm/src/iota.rs b/bindings/wasm/src/iota.rs index fb38d4c54a..80cd83e70b 100644 --- a/bindings/wasm/src/iota.rs +++ b/bindings/wasm/src/iota.rs @@ -3,9 +3,9 @@ use identity_core::common::Object; use identity_iota::{ - client::{Client, ClientBuilder, Network, TxnPrinter}, - credential::CredentialValidator, - did::IotaDID, + client::{Client, ClientBuilder, Network, TxnPrinter}, + credential::CredentialValidator, + did::IotaDID, }; use serde::Deserialize; use wasm_bindgen::prelude::*; @@ -14,100 +14,100 @@ use crate::js_err; #[wasm_bindgen] extern "C" { - #[wasm_bindgen(js_namespace = console)] - pub fn log(s: &str); + #[wasm_bindgen(js_namespace = console)] + pub fn log(s: &str); - #[wasm_bindgen(js_namespace = console)] - pub fn error(s: &str); + #[wasm_bindgen(js_namespace = console)] + pub fn error(s: &str); } #[derive(Debug, Deserialize)] pub enum ClientNode { - #[serde(rename = "node")] - Node(String), - #[serde(rename = "nodes")] - List(Vec), + #[serde(rename = "node")] + Node(String), + #[serde(rename = "nodes")] + List(Vec), } #[derive(Debug, Deserialize)] pub struct ClientParams { - network: Option, - #[serde(flatten)] - node: ClientNode, + network: Option, + #[serde(flatten)] + node: ClientNode, } impl ClientParams { - pub fn build(self, mut builder: ClientBuilder) -> ClientBuilder { - builder = Self::build_node(builder, self.node); - builder = Self::build_network(builder, self.network); - builder - } + pub fn build(self, mut builder: ClientBuilder) -> ClientBuilder { + builder = Self::build_node(builder, self.node); + builder = Self::build_network(builder, self.network); + builder + } - fn build_node(builder: ClientBuilder, node: ClientNode) -> ClientBuilder { - match node { - ClientNode::Node(node) => builder.node(node), - ClientNode::List(node) => builder.nodes(node), - } + fn build_node(builder: ClientBuilder, node: ClientNode) -> ClientBuilder { + match node { + ClientNode::Node(node) => builder.node(node), + ClientNode::List(node) => builder.nodes(node), } + } - fn build_network(builder: ClientBuilder, network: Option) -> ClientBuilder { - match network.as_deref() { - Some("main") | Some("mainnet") => builder.network(Network::Mainnet), - Some("com") | Some("comnet") => builder.network(Network::Comnet), - Some("dev") | Some("devnet") => builder.network(Network::Devnet), - Some(_) | None => builder.network(Network::Mainnet), - } + fn build_network(builder: ClientBuilder, network: Option) -> ClientBuilder { + match network.as_deref() { + Some("main") | Some("mainnet") => builder.network(Network::Mainnet), + Some("com") | Some("comnet") => builder.network(Network::Comnet), + Some("dev") | Some("devnet") => builder.network(Network::Devnet), + Some(_) | None => builder.network(Network::Mainnet), } + } } fn client(params: JsValue) -> Result { - if params.is_object() { - let params: ClientParams = params.into_serde().map_err(js_err)?; - params.build(ClientBuilder::new()).build().map_err(js_err) - } else if let Some(node) = params.as_string() { - ClientBuilder::new().node(node).build().map_err(js_err) - } else { - Err("Invalid Arguments for `new Client(..)`".into()) - } + if params.is_object() { + let params: ClientParams = params.into_serde().map_err(js_err)?; + params.build(ClientBuilder::new()).build().map_err(js_err) + } else if let Some(node) = params.as_string() { + ClientBuilder::new().node(node).build().map_err(js_err) + } else { + Err("Invalid Arguments for `new Client(..)`".into()) + } } /// Publishes a DID Document to the Tangle, params looks like { node: "http://localhost:14265", network: "main" } #[wasm_bindgen] pub async fn publish(doc: JsValue, params: JsValue) -> Result { - client(params)? - .publish_document(&doc.into_serde().map_err(js_err)?) - .await - .map_err(js_err) - .map(|response| TxnPrinter::hash(&response).to_string()) - .map(Into::into) + client(params)? + .publish_document(&doc.into_serde().map_err(js_err)?) + .await + .map_err(js_err) + .map(|response| TxnPrinter::hash(&response).to_string()) + .map(Into::into) } /// Resolves the latest DID Document from the Tangle, params looks like { node: "http://localhost:14265", network: "main" } #[wasm_bindgen] pub async fn resolve(did: String, params: JsValue) -> Result { - client(params)? - .read_document(&IotaDID::parse(did).map_err(js_err)?) - .await - .map_err(js_err) - .and_then(|response| JsValue::from_serde(&response).map_err(js_err)) + client(params)? + .read_document(&IotaDID::parse(did).map_err(js_err)?) + .await + .map_err(js_err) + .and_then(|response| JsValue::from_serde(&response).map_err(js_err)) } /// Validates a credential with the DID Document from the Tangle, params looks like { node: "http://localhost:14265", network: "main" } #[wasm_bindgen(js_name = checkCredential)] pub async fn check_credential(data: String, params: JsValue) -> Result { - CredentialValidator::new(&client(params)?) - .check::(&data) - .await - .map_err(js_err) - .and_then(|validation| JsValue::from_serde(&validation).map_err(js_err)) + CredentialValidator::new(&client(params)?) + .check::(&data) + .await + .map_err(js_err) + .and_then(|validation| JsValue::from_serde(&validation).map_err(js_err)) } /// Validates a presentation with the DID Document from the Tangle, params looks like { node: "http://localhost:14265", network: "main" } #[wasm_bindgen(js_name = checkPresentation)] pub async fn check_presentation(data: String, params: JsValue) -> Result { - CredentialValidator::new(&client(params)?) - .check_presentation::(&data) - .await - .map_err(js_err) - .and_then(|validation| JsValue::from_serde(&validation).map_err(js_err)) + CredentialValidator::new(&client(params)?) + .check_presentation::(&data) + .await + .map_err(js_err) + .and_then(|validation| JsValue::from_serde(&validation).map_err(js_err)) } diff --git a/bindings/wasm/src/key.rs b/bindings/wasm/src/key.rs index a2fc5a27f0..01a5897eb8 100644 --- a/bindings/wasm/src/key.rs +++ b/bindings/wasm/src/key.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use identity_core::{ - crypto::{KeyPair, PublicKey, SecretKey}, - did_doc::MethodType, - proof::JcsEd25519Signature2020, - utils::{decode_b58, encode_b58}, + crypto::{KeyPair, PublicKey, SecretKey}, + did_doc::MethodType, + proof::JcsEd25519Signature2020, + utils::{decode_b58, encode_b58}, }; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; @@ -18,67 +18,67 @@ pub struct Key(pub(crate) KeyPair); #[wasm_bindgen] impl Key { - /// Generates a new `Key` object. - #[wasm_bindgen(constructor)] - pub fn new(key_type: &str) -> Result { - match key_type.parse().map_err(js_err)? { - MethodType::Ed25519VerificationKey2018 => Ok(Self::generate_ed25519()), - _ => Err("Invalid Key Type".into()), - } + /// Generates a new `Key` object. + #[wasm_bindgen(constructor)] + pub fn new(key_type: &str) -> Result { + match key_type.parse().map_err(js_err)? { + MethodType::Ed25519VerificationKey2018 => Ok(Self::generate_ed25519()), + _ => Err("Invalid Key Type".into()), } + } - /// Generates a new `Key` object suitable for ed25519 signatures. - #[wasm_bindgen(js_name = generateEd25519)] - pub fn generate_ed25519() -> Key { - Self(JcsEd25519Signature2020::new_keypair()) - } + /// Generates a new `Key` object suitable for ed25519 signatures. + #[wasm_bindgen(js_name = generateEd25519)] + pub fn generate_ed25519() -> Key { + Self(JcsEd25519Signature2020::new_keypair()) + } - /// Parses a `Key` object from base58-encoded public/private keys. - #[wasm_bindgen(js_name = fromBase58)] - pub fn from_base58(public_key: &str, private_key: &str) -> Result { - let public: PublicKey = decode_b58(public_key).map_err(js_err)?.into(); - let private: SecretKey = decode_b58(private_key).map_err(js_err)?.into(); + /// Parses a `Key` object from base58-encoded public/private keys. + #[wasm_bindgen(js_name = fromBase58)] + pub fn from_base58(public_key: &str, private_key: &str) -> Result { + let public: PublicKey = decode_b58(public_key).map_err(js_err)?.into(); + let private: SecretKey = decode_b58(private_key).map_err(js_err)?.into(); - Ok(Self(KeyPair::new(public, private))) - } + Ok(Self(KeyPair::new(public, private))) + } - /// Returns the public key as a base58-encoded string. - #[wasm_bindgen(getter)] - pub fn public(&self) -> String { - encode_b58(self.0.public()) - } + /// Returns the public key as a base58-encoded string. + #[wasm_bindgen(getter)] + pub fn public(&self) -> String { + encode_b58(self.0.public()) + } - /// Returns the private key as a base58-encoded string. - #[wasm_bindgen(getter)] - pub fn private(&self) -> String { - encode_b58(self.0.secret()) - } + /// Returns the private key as a base58-encoded string. + #[wasm_bindgen(getter)] + pub fn private(&self) -> String { + encode_b58(self.0.secret()) + } - /// Serializes a `Key` object as a JSON object. - #[wasm_bindgen(js_name = toJSON)] - pub fn to_json(&self) -> Result { - JsValue::from_serde(&self.to_key_data()).map_err(js_err) - } + /// Serializes a `Key` object as a JSON object. + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> Result { + JsValue::from_serde(&self.to_key_data()).map_err(js_err) + } - /// Deserializes a `Key` object from a JSON object. - #[wasm_bindgen(js_name = fromJSON)] - pub fn from_json(json: &JsValue) -> Result { - let data: KeyData = json.into_serde().map_err(js_err)?; - let this: Self = Self::from_base58(&data.public, &data.private)?; + /// Deserializes a `Key` object from a JSON object. + #[wasm_bindgen(js_name = fromJSON)] + pub fn from_json(json: &JsValue) -> Result { + let data: KeyData = json.into_serde().map_err(js_err)?; + let this: Self = Self::from_base58(&data.public, &data.private)?; - Ok(this) - } + Ok(this) + } - fn to_key_data(&self) -> KeyData { - KeyData { - public: self.public(), - private: self.private(), - } + fn to_key_data(&self) -> KeyData { + KeyData { + public: self.public(), + private: self.private(), } + } } #[derive(Deserialize, Serialize)] struct KeyData { - public: String, - private: String, + public: String, + private: String, } diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index ddf324285c..96076e6579 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -14,11 +14,11 @@ pub mod vp; /// Initializes the console error panic hook for better error messages #[wasm_bindgen(start)] pub fn start() -> Result<(), JsValue> { - console_error_panic_hook::set_once(); - Ok(()) + console_error_panic_hook::set_once(); + Ok(()) } /// Convert errors so they are readable in JS pub fn js_err(error: T) -> JsValue { - error.to_string().into() + error.to_string().into() } diff --git a/bindings/wasm/src/pubkey.rs b/bindings/wasm/src/pubkey.rs index 9a1de26996..fab974284d 100644 --- a/bindings/wasm/src/pubkey.rs +++ b/bindings/wasm/src/pubkey.rs @@ -16,51 +16,51 @@ pub struct PubKey(pub(crate) Method); #[wasm_bindgen] impl PubKey { - // TODO: Support non-base58 key data - #[wasm_bindgen(constructor)] - pub fn new(did: &DID, key_type: &str, key_data: &str, tag: Option) -> Result { - let tag: &str = tag.as_deref().unwrap_or(DEFAULT_TAG); - let key: _ = format!("{}#{}", did.0, tag).parse().map_err(js_err)?; + // TODO: Support non-base58 key data + #[wasm_bindgen(constructor)] + pub fn new(did: &DID, key_type: &str, key_data: &str, tag: Option) -> Result { + let tag: &str = tag.as_deref().unwrap_or(DEFAULT_TAG); + let key: _ = format!("{}#{}", did.0, tag).parse().map_err(js_err)?; - MethodBuilder::default() - .id(key) - .controller(did.0.clone().into()) - .key_type(key_type.parse().map_err(js_err)?) - .key_data(MethodData::PublicKeyBase58(key_data.into())) - .build() - .map_err(js_err) - .map(Self) - } + MethodBuilder::default() + .id(key) + .controller(did.0.clone().into()) + .key_type(key_type.parse().map_err(js_err)?) + .key_data(MethodData::PublicKeyBase58(key_data.into())) + .build() + .map_err(js_err) + .map(Self) + } - /// Generates a new `PubKey` object suitable for ed25519 signatures. - #[wasm_bindgen(js_name = generateEd25519)] - pub fn generate_ed25519(did: &DID, key_data: &str, tag: Option) -> Result { - Self::new(did, DEFAULT_KEY, key_data, tag) - } + /// Generates a new `PubKey` object suitable for ed25519 signatures. + #[wasm_bindgen(js_name = generateEd25519)] + pub fn generate_ed25519(did: &DID, key_data: &str, tag: Option) -> Result { + Self::new(did, DEFAULT_KEY, key_data, tag) + } - /// Returns the `id` DID of the `PubKey` object. - #[wasm_bindgen(getter)] - pub fn id(&self) -> Result { - IotaDID::try_from_owned(self.0.id().clone()).map_err(js_err).map(DID) - } + /// Returns the `id` DID of the `PubKey` object. + #[wasm_bindgen(getter)] + pub fn id(&self) -> Result { + IotaDID::try_from_owned(self.0.id().clone()).map_err(js_err).map(DID) + } - /// Returns the `controller` DID of the `PubKey` object. - #[wasm_bindgen(getter)] - pub fn controller(&self) -> Result { - IotaDID::try_from_owned(self.0.controller().clone()) - .map_err(js_err) - .map(DID) - } + /// Returns the `controller` DID of the `PubKey` object. + #[wasm_bindgen(getter)] + pub fn controller(&self) -> Result { + IotaDID::try_from_owned(self.0.controller().clone()) + .map_err(js_err) + .map(DID) + } - /// Serializes a `PubKey` object as a JSON object. - #[wasm_bindgen(js_name = toJSON)] - pub fn to_json(&self) -> Result { - JsValue::from_serde(&self.0).map_err(js_err) - } + /// Serializes a `PubKey` object as a JSON object. + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> Result { + JsValue::from_serde(&self.0).map_err(js_err) + } - /// Deserializes a `PubKey` object from a JSON object. - #[wasm_bindgen(js_name = fromJSON)] - pub fn from_json(json: &JsValue) -> Result { - json.into_serde().map_err(js_err).map(Self) - } + /// Deserializes a `PubKey` object from a JSON object. + #[wasm_bindgen(js_name = fromJSON)] + pub fn from_json(json: &JsValue) -> Result { + json.into_serde().map_err(js_err).map(Self) + } } diff --git a/bindings/wasm/src/vc.rs b/bindings/wasm/src/vc.rs index a458800fda..17631a1cb5 100644 --- a/bindings/wasm/src/vc.rs +++ b/bindings/wasm/src/vc.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use identity_core::{ - common::{OneOrMany, Url}, - credential::{Credential, CredentialBuilder, CredentialSubject, VerifiableCredential as VC}, + common::{OneOrMany, Url}, + credential::{Credential, CredentialBuilder, CredentialSubject, VerifiableCredential as VC}, }; use wasm_bindgen::prelude::*; @@ -15,60 +15,60 @@ pub struct VerifiableCredential(pub(crate) VC); #[wasm_bindgen] impl VerifiableCredential { - #[wasm_bindgen(constructor)] - pub fn new( - issuer_doc: &Doc, - issuer_key: &Key, - subject_data: JsValue, - credential_type: Option, - credential_id: Option, - ) -> Result { - let subjects: OneOrMany = subject_data.into_serde().map_err(js_err)?; - let issuer_url: Url = Url::parse(issuer_doc.id().as_str()).map_err(js_err)?; + #[wasm_bindgen(constructor)] + pub fn new( + issuer_doc: &Doc, + issuer_key: &Key, + subject_data: JsValue, + credential_type: Option, + credential_id: Option, + ) -> Result { + let subjects: OneOrMany = subject_data.into_serde().map_err(js_err)?; + let issuer_url: Url = Url::parse(issuer_doc.id().as_str()).map_err(js_err)?; - let mut builder: CredentialBuilder = CredentialBuilder::default().issuer(issuer_url); + let mut builder: CredentialBuilder = CredentialBuilder::default().issuer(issuer_url); - for subject in subjects.into_vec() { - builder = builder.credential_subject(subject); - } + for subject in subjects.into_vec() { + builder = builder.credential_subject(subject); + } - if let Some(credential_type) = credential_type { - builder = builder.type_(credential_type); - } + if let Some(credential_type) = credential_type { + builder = builder.type_(credential_type); + } - if let Some(credential_id) = credential_id { - builder = builder.id(Url::parse(credential_id).map_err(js_err)?); - } + if let Some(credential_id) = credential_id { + builder = builder.id(Url::parse(credential_id).map_err(js_err)?); + } - let credential: Credential = builder.build().map_err(js_err)?; - let mut this: Self = Self(VC::new(credential, Vec::new())); + let credential: Credential = builder.build().map_err(js_err)?; + let mut this: Self = Self(VC::new(credential, Vec::new())); - this.sign(issuer_doc, issuer_key)?; + this.sign(issuer_doc, issuer_key)?; - Ok(this) - } + Ok(this) + } - /// Signs the credential with the given issuer `Doc` and `Key` object. - #[wasm_bindgen] - pub fn sign(&mut self, issuer: &Doc, key: &Key) -> Result<(), JsValue> { - issuer.0.sign_data(&mut self.0, key.0.secret()).map_err(js_err) - } + /// Signs the credential with the given issuer `Doc` and `Key` object. + #[wasm_bindgen] + pub fn sign(&mut self, issuer: &Doc, key: &Key) -> Result<(), JsValue> { + issuer.0.sign_data(&mut self.0, key.0.secret()).map_err(js_err) + } - /// Verifies the credential signature against the issuer `Doc`. - #[wasm_bindgen] - pub fn verify(&self, issuer: &Doc) -> Result { - issuer.0.verify_data(&self.0).map_err(js_err).map(|_| true) - } + /// Verifies the credential signature against the issuer `Doc`. + #[wasm_bindgen] + pub fn verify(&self, issuer: &Doc) -> Result { + issuer.0.verify_data(&self.0).map_err(js_err).map(|_| true) + } - /// Serializes a `VerifiableCredential` object as a JSON object. - #[wasm_bindgen(js_name = toJSON)] - pub fn to_json(&self) -> Result { - JsValue::from_serde(&self.0).map_err(js_err) - } + /// Serializes a `VerifiableCredential` object as a JSON object. + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> Result { + JsValue::from_serde(&self.0).map_err(js_err) + } - /// Deserializes a `VerifiableCredential` object from a JSON object. - #[wasm_bindgen(js_name = fromJSON)] - pub fn from_json(json: &JsValue) -> Result { - json.into_serde().map_err(js_err).map(Self) - } + /// Deserializes a `VerifiableCredential` object from a JSON object. + #[wasm_bindgen(js_name = fromJSON)] + pub fn from_json(json: &JsValue) -> Result { + json.into_serde().map_err(js_err).map(Self) + } } diff --git a/bindings/wasm/src/vp.rs b/bindings/wasm/src/vp.rs index 1fe5fcde26..5558f06dbb 100644 --- a/bindings/wasm/src/vp.rs +++ b/bindings/wasm/src/vp.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use identity_core::{ - common::{OneOrMany, Url}, - credential::{Presentation, PresentationBuilder, VerifiableCredential, VerifiablePresentation as VP}, + common::{OneOrMany, Url}, + credential::{Presentation, PresentationBuilder, VerifiableCredential, VerifiablePresentation as VP}, }; use wasm_bindgen::prelude::*; @@ -15,60 +15,60 @@ pub struct VerifiablePresentation(pub(crate) VP); #[wasm_bindgen] impl VerifiablePresentation { - #[wasm_bindgen(constructor)] - pub fn new( - holder_doc: &Doc, - holder_key: &Key, - credential_data: JsValue, - presentation_type: Option, - presentation_id: Option, - ) -> Result { - let credentials: OneOrMany = credential_data.into_serde().map_err(js_err)?; - let holder_url: Url = Url::parse(holder_doc.id().as_str()).map_err(js_err)?; + #[wasm_bindgen(constructor)] + pub fn new( + holder_doc: &Doc, + holder_key: &Key, + credential_data: JsValue, + presentation_type: Option, + presentation_id: Option, + ) -> Result { + let credentials: OneOrMany = credential_data.into_serde().map_err(js_err)?; + let holder_url: Url = Url::parse(holder_doc.id().as_str()).map_err(js_err)?; - let mut builder: PresentationBuilder = PresentationBuilder::default().holder(holder_url); + let mut builder: PresentationBuilder = PresentationBuilder::default().holder(holder_url); - for credential in credentials.into_vec() { - builder = builder.verifiable_credential(credential); - } + for credential in credentials.into_vec() { + builder = builder.verifiable_credential(credential); + } - if let Some(presentation_type) = presentation_type { - builder = builder.type_(presentation_type); - } + if let Some(presentation_type) = presentation_type { + builder = builder.type_(presentation_type); + } - if let Some(presentation_id) = presentation_id { - builder = builder.id(Url::parse(presentation_id).map_err(js_err)?); - } + if let Some(presentation_id) = presentation_id { + builder = builder.id(Url::parse(presentation_id).map_err(js_err)?); + } - let presentation: Presentation = builder.build().map_err(js_err)?; - let mut this: Self = Self(VP::new(presentation, Vec::new())); + let presentation: Presentation = builder.build().map_err(js_err)?; + let mut this: Self = Self(VP::new(presentation, Vec::new())); - this.sign(holder_doc, holder_key)?; + this.sign(holder_doc, holder_key)?; - Ok(this) - } + Ok(this) + } - /// Signs the presentation with the given holder `Doc` and `Key` object. - #[wasm_bindgen] - pub fn sign(&mut self, holder: &Doc, key: &Key) -> Result<(), JsValue> { - holder.0.sign_data(&mut self.0, key.0.secret()).map_err(js_err) - } + /// Signs the presentation with the given holder `Doc` and `Key` object. + #[wasm_bindgen] + pub fn sign(&mut self, holder: &Doc, key: &Key) -> Result<(), JsValue> { + holder.0.sign_data(&mut self.0, key.0.secret()).map_err(js_err) + } - /// Verifies the presentation signature against the holder `Doc`. - #[wasm_bindgen] - pub fn verify(&self, holder: &Doc) -> Result { - holder.0.verify_data(&self.0).map_err(js_err).map(|_| true) - } + /// Verifies the presentation signature against the holder `Doc`. + #[wasm_bindgen] + pub fn verify(&self, holder: &Doc) -> Result { + holder.0.verify_data(&self.0).map_err(js_err).map(|_| true) + } - /// Serializes a `VerifiablePresentation` object as a JSON object. - #[wasm_bindgen(js_name = toJSON)] - pub fn to_json(&self) -> Result { - JsValue::from_serde(&self.0).map_err(js_err) - } + /// Serializes a `VerifiablePresentation` object as a JSON object. + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> Result { + JsValue::from_serde(&self.0).map_err(js_err) + } - /// Deserializes a `VerifiablePresentation` object from a JSON object. - #[wasm_bindgen(js_name = fromJSON)] - pub fn from_json(json: &JsValue) -> Result { - json.into_serde().map_err(js_err).map(Self) - } + /// Deserializes a `VerifiablePresentation` object from a JSON object. + #[wasm_bindgen(js_name = fromJSON)] + pub fn from_json(json: &JsValue) -> Result { + json.into_serde().map_err(js_err).map(Self) + } } diff --git a/examples/credential/src/main.rs b/examples/credential/src/main.rs index 390357c265..90101deb8a 100644 --- a/examples/credential/src/main.rs +++ b/examples/credential/src/main.rs @@ -5,91 +5,91 @@ //! Documents, creates a VerifiableCredential specifying claims about the //! subject, and retrieves information through the CredentialValidator API. use identity_core::{ - common::{Url, Value}, - convert::{FromJson as _, ToJson as _}, - credential::{Credential, CredentialBuilder, CredentialSubject, VerifiableCredential}, - crypto::KeyPair, - did_doc::MethodScope, - did_url::DID, - json, + common::{Url, Value}, + convert::{FromJson as _, ToJson as _}, + credential::{Credential, CredentialBuilder, CredentialSubject, VerifiableCredential}, + crypto::KeyPair, + did_doc::MethodScope, + did_url::DID, + json, }; use identity_iota::{ - client::Client, - credential::{CredentialValidation, CredentialValidator}, - did::IotaDocument, - error::Result, + client::Client, + credential::{CredentialValidation, CredentialValidator}, + did::IotaDocument, + error::Result, }; // A helper function to generate and new DID Document/KeyPair, sign the // document, publish it to the Tangle, and return the Document/KeyPair. async fn document(client: &Client) -> Result<(IotaDocument, KeyPair)> { - let (mut document, keypair): (IotaDocument, KeyPair) = IotaDocument::builder() - .authentication_tag("key-1") - .did_network(client.network().as_str()) - .build()?; + let (mut document, keypair): (IotaDocument, KeyPair) = IotaDocument::builder() + .authentication_tag("key-1") + .did_network(client.network().as_str()) + .build()?; - document.sign(keypair.secret())?; + document.sign(keypair.secret())?; - println!("DID Document (signed) > {:#}", document); - println!(); + println!("DID Document (signed) > {:#}", document); + println!(); - let transaction: _ = client.publish_document(&document).await?; + let transaction: _ = client.publish_document(&document).await?; - println!("DID Document Transaction > {}", client.transaction_url(&transaction)); - println!(); + println!("DID Document Transaction > {}", client.transaction_url(&transaction)); + println!(); - Ok((document, keypair)) + Ok((document, keypair)) } fn subject(subject: &DID) -> Result { - let json: Value = json!({ - "id": subject.as_str(), - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts" - } - }); - - CredentialSubject::from_json_value(json).map_err(Into::into) + let json: Value = json!({ + "id": subject.as_str(), + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts" + } + }); + + CredentialSubject::from_json_value(json).map_err(Into::into) } #[smol_potat::main] async fn main() -> Result<()> { - let client: Client = Client::new()?; + let client: Client = Client::new()?; - let (doc_iss, key_iss): (IotaDocument, KeyPair) = document(&client).await?; - let (doc_sub, _key_sub): (IotaDocument, KeyPair) = document(&client).await?; + let (doc_iss, key_iss): (IotaDocument, KeyPair) = document(&client).await?; + let (doc_sub, _key_sub): (IotaDocument, KeyPair) = document(&client).await?; - // Create a new Credential with claims about "subject", specified by "issuer". - let credential: Credential = CredentialBuilder::default() - .issuer(Url::parse(doc_iss.id())?) - .type_("UniversityDegreeCredential") - .credential_subject(subject(&doc_sub.id())?) - .build()?; + // Create a new Credential with claims about "subject", specified by "issuer". + let credential: Credential = CredentialBuilder::default() + .issuer(Url::parse(doc_iss.id())?) + .type_("UniversityDegreeCredential") + .credential_subject(subject(&doc_sub.id())?) + .build()?; - // Extract the default verification method from the authentication scope and - // create a Verifiable Credential signed by the issuer. - let vc: VerifiableCredential = credential.sign(&doc_iss, MethodScope::Authentication, key_iss.secret())?; + // Extract the default verification method from the authentication scope and + // create a Verifiable Credential signed by the issuer. + let vc: VerifiableCredential = credential.sign(&doc_iss, MethodScope::Authentication, key_iss.secret())?; - println!("Credential > {:#}", vc); - println!(); + println!("Credential > {:#}", vc); + println!(); - // ==================== - // ==================== - // - // Out-Of-Band DID/Credential Exchange - // - // ==================== - // ==================== + // ==================== + // ==================== + // + // Out-Of-Band DID/Credential Exchange + // + // ==================== + // ==================== - let vc: String = vc.to_json()?; + let vc: String = vc.to_json()?; - // Validate the Credential and resolve all DID Documents. - let validator: CredentialValidator = CredentialValidator::new(&client); - let validation: CredentialValidation = validator.check(&vc).await?; + // Validate the Credential and resolve all DID Documents. + let validator: CredentialValidator = CredentialValidator::new(&client); + let validation: CredentialValidation = validator.check(&vc).await?; - println!("Credential Validation > {:#?}", validation); - println!(); + println!("Credential Validation > {:#?}", validation); + println!(); - Ok(()) + Ok(()) } diff --git a/examples/diff-chain/src/main.rs b/examples/diff-chain/src/main.rs index be4f864fe9..52ac984c4b 100644 --- a/examples/diff-chain/src/main.rs +++ b/examples/diff-chain/src/main.rs @@ -4,178 +4,178 @@ //! An example that utilizes a diff and auth chain to publish updates to a //! DID Document. use identity_core::{ - crypto::KeyPair, - did_doc::{MethodBuilder, MethodData, MethodRef, MethodType}, - proof::JcsEd25519Signature2020, + crypto::KeyPair, + did_doc::{MethodBuilder, MethodData, MethodRef, MethodType}, + proof::JcsEd25519Signature2020, }; use identity_iota::{ - chain::{AuthChain, DocumentChain}, - client::{Client, ClientBuilder, Network}, - did::{DocumentDiff, IotaDocument}, - error::Result, - tangle::MessageId, + chain::{AuthChain, DocumentChain}, + client::{Client, ClientBuilder, Network}, + did::{DocumentDiff, IotaDocument}, + error::Result, + tangle::MessageId, }; use std::{thread::sleep, time::Duration}; #[smol_potat::main] async fn main() -> Result<()> { - let client: Client = ClientBuilder::new().network(Network::Comnet).build()?; - let network: &str = client.network().as_str(); + let client: Client = ClientBuilder::new().network(Network::Comnet).build()?; + let network: &str = client.network().as_str(); - // Keep track of the chain state locally, for reference - let mut chain: DocumentChain; - let mut keys: Vec = Vec::new(); + // Keep track of the chain state locally, for reference + let mut chain: DocumentChain; + let mut keys: Vec = Vec::new(); - // ========================================================================= - // Publish Initial Document - // ========================================================================= + // ========================================================================= + // Publish Initial Document + // ========================================================================= - { - let (mut document, keypair): (IotaDocument, KeyPair) = IotaDocument::builder().did_network(network).build()?; + { + let (mut document, keypair): (IotaDocument, KeyPair) = IotaDocument::builder().did_network(network).build()?; - document.sign(keypair.secret())?; - document.publish_with_client(&client).await?; + document.sign(keypair.secret())?; + document.publish_with_client(&client).await?; - chain = DocumentChain::new(AuthChain::new(document)?); - keys.push(keypair); + chain = DocumentChain::new(AuthChain::new(document)?); + keys.push(keypair); - println!("Chain (1) > {:#?}", chain); - println!(); - } + println!("Chain (1) > {:#?}", chain); + println!(); + } - // ========================================================================= - // Publish Auth Chain Update - // ========================================================================= + // ========================================================================= + // Publish Auth Chain Update + // ========================================================================= - sleep(Duration::from_secs(1)); + sleep(Duration::from_secs(1)); - { - let mut new: IotaDocument = chain.current().clone(); - let keypair: KeyPair = JcsEd25519Signature2020::new_keypair(); + { + let mut new: IotaDocument = chain.current().clone(); + let keypair: KeyPair = JcsEd25519Signature2020::new_keypair(); - let authentication: MethodRef = MethodBuilder::default() - .id(chain.id().join("#key-2")?.into()) - .controller(chain.id().clone().into()) - .key_type(MethodType::Ed25519VerificationKey2018) - .key_data(MethodData::new_b58(keypair.public())) - .build() - .map(Into::into)?; + let authentication: MethodRef = MethodBuilder::default() + .id(chain.id().join("#key-2")?.into()) + .controller(chain.id().clone().into()) + .key_type(MethodType::Ed25519VerificationKey2018) + .key_data(MethodData::new_b58(keypair.public())) + .build() + .map(Into::into)?; - unsafe { - new.as_document_mut().authentication_mut().clear(); - new.as_document_mut().authentication_mut().append(authentication.into()); - } + unsafe { + new.as_document_mut().authentication_mut().clear(); + new.as_document_mut().authentication_mut().append(authentication.into()); + } - new.set_updated_now(); - new.set_previous_message_id(chain.auth_message_id().clone()); + new.set_updated_now(); + new.set_previous_message_id(chain.auth_message_id().clone()); - chain.current().sign_data(&mut new, keys[0].secret())?; - new.publish_with_client(&client).await?; + chain.current().sign_data(&mut new, keys[0].secret())?; + new.publish_with_client(&client).await?; - keys.push(keypair); - chain.try_push_auth(new)?; + keys.push(keypair); + chain.try_push_auth(new)?; - println!("Chain (2) > {:#?}", chain); - println!(); - } + println!("Chain (2) > {:#?}", chain); + println!(); + } - // ========================================================================= - // Publish Diff Chain Update - // ========================================================================= + // ========================================================================= + // Publish Diff Chain Update + // ========================================================================= - sleep(Duration::from_secs(1)); + sleep(Duration::from_secs(1)); - { - let new: IotaDocument = { - let mut this: IotaDocument = chain.current().clone(); - this.properties_mut().insert("foo".into(), 123.into()); - this.properties_mut().insert("bar".into(), 456.into()); - this.set_updated_now(); - this - }; + { + let new: IotaDocument = { + let mut this: IotaDocument = chain.current().clone(); + this.properties_mut().insert("foo".into(), 123.into()); + this.properties_mut().insert("bar".into(), 456.into()); + this.set_updated_now(); + this + }; - let message_id: MessageId = chain.diff_message_id().clone(); - let mut diff: DocumentDiff = chain.current().diff(&new, keys[1].secret(), message_id)?; + let message_id: MessageId = chain.diff_message_id().clone(); + let mut diff: DocumentDiff = chain.current().diff(&new, keys[1].secret(), message_id)?; - diff.publish_with_client(&client, chain.auth_message_id()).await?; - chain.try_push_diff(diff)?; + diff.publish_with_client(&client, chain.auth_message_id()).await?; + chain.try_push_diff(diff)?; - println!("Chain (3) > {:#?}", chain); - println!(); - } + println!("Chain (3) > {:#?}", chain); + println!(); + } - // ========================================================================= - // Publish Phony Auth Update - // ========================================================================= + // ========================================================================= + // Publish Phony Auth Update + // ========================================================================= - sleep(Duration::from_secs(1)); + sleep(Duration::from_secs(1)); - { - let mut new: IotaDocument = chain.current().clone(); - let keypair: KeyPair = JcsEd25519Signature2020::new_keypair(); + { + let mut new: IotaDocument = chain.current().clone(); + let keypair: KeyPair = JcsEd25519Signature2020::new_keypair(); - let authentication: MethodRef = MethodBuilder::default() - .id(new.id().join("#bad-key")?.into()) - .controller(new.id().clone().into()) - .key_type(MethodType::Ed25519VerificationKey2018) - .key_data(MethodData::new_b58(keypair.public())) - .build() - .map(Into::into)?; + let authentication: MethodRef = MethodBuilder::default() + .id(new.id().join("#bad-key")?.into()) + .controller(new.id().clone().into()) + .key_type(MethodType::Ed25519VerificationKey2018) + .key_data(MethodData::new_b58(keypair.public())) + .build() + .map(Into::into)?; - unsafe { - new.as_document_mut().authentication_mut().clear(); - new.as_document_mut().authentication_mut().append(authentication.into()); - } + unsafe { + new.as_document_mut().authentication_mut().clear(); + new.as_document_mut().authentication_mut().append(authentication.into()); + } - new.set_updated_now(); - new.set_previous_message_id(chain.auth_message_id().clone()); + new.set_updated_now(); + new.set_previous_message_id(chain.auth_message_id().clone()); - new.sign(keypair.secret())?; - new.publish_with_client(&client).await?; + new.sign(keypair.secret())?; + new.publish_with_client(&client).await?; - println!("Chain Err > {:?}", chain.try_push_auth(new).unwrap_err()); - } + println!("Chain Err > {:?}", chain.try_push_auth(new).unwrap_err()); + } - // ========================================================================= - // Publish Second Diff Chain Update - // ========================================================================= + // ========================================================================= + // Publish Second Diff Chain Update + // ========================================================================= - sleep(Duration::from_secs(1)); + sleep(Duration::from_secs(1)); - { - let new: IotaDocument = { - let mut this: IotaDocument = chain.current().clone(); - this.properties_mut().insert("baz".into(), 789.into()); - this.properties_mut().remove("bar"); - this.set_updated_now(); - this - }; + { + let new: IotaDocument = { + let mut this: IotaDocument = chain.current().clone(); + this.properties_mut().insert("baz".into(), 789.into()); + this.properties_mut().remove("bar"); + this.set_updated_now(); + this + }; - let message_id: MessageId = chain.diff_message_id().clone(); - let mut diff: DocumentDiff = chain.current().diff(&new, keys[1].secret(), message_id)?; + let message_id: MessageId = chain.diff_message_id().clone(); + let mut diff: DocumentDiff = chain.current().diff(&new, keys[1].secret(), message_id)?; - diff.publish_with_client(&client, chain.auth_message_id()).await?; - chain.try_push_diff(diff)?; + diff.publish_with_client(&client, chain.auth_message_id()).await?; + chain.try_push_diff(diff)?; - println!("Chain (4) > {:#?}", chain); - println!(); - } + println!("Chain (4) > {:#?}", chain); + println!(); + } - // ========================================================================= - // Read Document Chain - // ========================================================================= + // ========================================================================= + // Read Document Chain + // ========================================================================= - let remote: DocumentChain = client.read_document_chain(chain.id()).await?; + let remote: DocumentChain = client.read_document_chain(chain.id()).await?; - println!("Chain (R) {:#?}", remote); - println!(); + println!("Chain (R) {:#?}", remote); + println!(); - let a: &IotaDocument = chain.current(); - let b: &IotaDocument = remote.current(); + let a: &IotaDocument = chain.current(); + let b: &IotaDocument = remote.current(); - // The current document in the resolved chain should be identical to the - // current document in our local chain. - assert_eq!(a, b); + // The current document in the resolved chain should be identical to the + // current document in our local chain. + assert_eq!(a, b); - Ok(()) + Ok(()) } diff --git a/examples/resolution/src/main.rs b/examples/resolution/src/main.rs index 3f206d0eef..fa9e8467ad 100644 --- a/examples/resolution/src/main.rs +++ b/examples/resolution/src/main.rs @@ -4,51 +4,51 @@ //! A basic example that generates a DID Document, publishes it to the Tangle, //! and retrieves information through DID Document resolution/dereferencing. use identity_core::{ - crypto::KeyPair, - resolver::{dereference, resolve, Dereference, Resolution}, + crypto::KeyPair, + resolver::{dereference, resolve, Dereference, Resolution}, }; use identity_iota::{ - client::Client, - did::{IotaDID, IotaDocument}, - error::Result, + client::Client, + did::{IotaDID, IotaDocument}, + error::Result, }; #[smol_potat::main] async fn main() -> Result<()> { - let client: Client = Client::new()?; + let client: Client = Client::new()?; - // Generate a new DID Document and public/private key pair. - // - // The generated document will have an authentication key associated with - // the keypair. - let (mut document, keypair): (IotaDocument, KeyPair) = IotaDocument::builder() - .authentication_tag("key-1") - .did_network(client.network().as_str()) - .build()?; + // Generate a new DID Document and public/private key pair. + // + // The generated document will have an authentication key associated with + // the keypair. + let (mut document, keypair): (IotaDocument, KeyPair) = IotaDocument::builder() + .authentication_tag("key-1") + .did_network(client.network().as_str()) + .build()?; - // Sign the DID Document with the default authentication key. - document.sign(keypair.secret())?; + // Sign the DID Document with the default authentication key. + document.sign(keypair.secret())?; - println!("DID Document (signed) > {:#}", document); - println!(); + println!("DID Document (signed) > {:#}", document); + println!(); - // Use the client created above to publish the DID Document to the Tangle. - document.publish_with_client(&client).await?; + // Use the client created above to publish the DID Document to the Tangle. + document.publish_with_client(&client).await?; - let did: &IotaDID = document.id(); + let did: &IotaDID = document.id(); - // Resolve the DID and retrieve the published DID Document from the Tangle. - let resolution: Resolution = resolve(did.as_str(), Default::default(), &client).await?; + // Resolve the DID and retrieve the published DID Document from the Tangle. + let resolution: Resolution = resolve(did.as_str(), Default::default(), &client).await?; - println!("DID Document Resolution > {:#?}", resolution); - println!(); + println!("DID Document Resolution > {:#?}", resolution); + println!(); - // Dereference the DID and retrieve the authentication method generated above. - let did: IotaDID = did.join("#key-1")?; - let dereference: Dereference = dereference(did.as_str(), Default::default(), &client).await?; + // Dereference the DID and retrieve the authentication method generated above. + let did: IotaDID = did.join("#key-1")?; + let dereference: Dereference = dereference(did.as_str(), Default::default(), &client).await?; - println!("DID Document Dereference > {:#?}", dereference); - println!(); + println!("DID Document Dereference > {:#?}", dereference); + println!(); - Ok(()) + Ok(()) } diff --git a/identity-core/examples/merkle_tree.rs b/identity-core/examples/merkle_tree.rs index 7393d17ca9..d0320ebef5 100644 --- a/identity-core/examples/merkle_tree.rs +++ b/identity-core/examples/merkle_tree.rs @@ -18,56 +18,56 @@ use sha2::Sha256; const LEAVES: usize = 1 << 8; fn generate_leaves(count: usize) -> Vec { - (0..count).map(|_| JcsEd25519Signature2020::new_keypair()).collect() + (0..count).map(|_| JcsEd25519Signature2020::new_keypair()).collect() } fn generate_hashes<'a, D, T, I>(digest: &mut D, leaves: I) -> Vec> where - D: Digest, - T: AsRef<[u8]> + 'a, - I: IntoIterator, + D: Digest, + T: AsRef<[u8]> + 'a, + I: IntoIterator, { - leaves - .into_iter() - .map(AsRef::as_ref) - .map(|leaf| digest.hash_leaf(leaf)) - .collect() + leaves + .into_iter() + .map(AsRef::as_ref) + .map(|leaf| digest.hash_leaf(leaf)) + .collect() } fn main() -> Result<()> { - let mut digest: Sha256 = Sha256::new(); + let mut digest: Sha256 = Sha256::new(); - // Choose a random index from 0..LEAVES. - // - // We will generate a proof-of-inclusion for this public key. - let index: usize = OsRng.gen_range(0, LEAVES); + // Choose a random index from 0..LEAVES. + // + // We will generate a proof-of-inclusion for this public key. + let index: usize = OsRng.gen_range(0, LEAVES); - println!("Target Leaves: {}", LEAVES); - println!("Target Index: {}", index); + println!("Target Leaves: {}", LEAVES); + println!("Target Index: {}", index); - // Generate a list of keypairs to use for the Merkle tree. - let kpairs: Vec = generate_leaves(LEAVES); + // Generate a list of keypairs to use for the Merkle tree. + let kpairs: Vec = generate_leaves(LEAVES); - // Hash all keypairs with SHA-256. - let leaves: _ = kpairs.iter().map(KeyPair::public); - let hashes: Vec> = generate_hashes(&mut digest, leaves); + // Hash all keypairs with SHA-256. + let leaves: _ = kpairs.iter().map(KeyPair::public); + let hashes: Vec> = generate_hashes(&mut digest, leaves); - // Construct the Merkle tree from the list of hashes. - let tree: MTree = MTree::from_leaves(&hashes).unwrap(); - println!("Merkle Tree: {:#?}", tree); + // Construct the Merkle tree from the list of hashes. + let tree: MTree = MTree::from_leaves(&hashes).unwrap(); + println!("Merkle Tree: {:#?}", tree); - // Generate a proof-of-inclusion for the leaf node at the specified index. - let proof: Proof = tree.proof(index).unwrap(); - println!("Inclusion Proof: {:#?}", proof); + // Generate a proof-of-inclusion for the leaf node at the specified index. + let proof: Proof = tree.proof(index).unwrap(); + println!("Inclusion Proof: {:#?}", proof); - // Hash the target public key with SHA-256. - let target: Hash = digest.hash_leaf(kpairs[index].public().as_ref()); - println!("Target Hash: {:?}", target); + // Hash the target public key with SHA-256. + let target: Hash = digest.hash_leaf(kpairs[index].public().as_ref()); + println!("Target Hash: {:?}", target); - // Use the generated proof to verify inclusion of the target hash in the - // Merkle tree. - let verified: bool = tree.verify(&proof, target); - println!("Proof Verified: {:#?}", verified); + // Use the generated proof to verify inclusion of the target hash in the + // Merkle tree. + let verified: bool = tree.verify(&proof, target); + println!("Proof Verified: {:#?}", verified); - Ok(()) + Ok(()) } diff --git a/identity-core/src/common/context.rs b/identity-core/src/common/context.rs index 4326bae95c..95e8801dcd 100644 --- a/identity-core/src/common/context.rs +++ b/identity-core/src/common/context.rs @@ -12,41 +12,41 @@ use crate::common::{Object, Url}; #[derive(Clone, PartialEq, Deserialize, Serialize)] #[serde(untagged)] pub enum Context { - /// A JSON-LD context expressed as a Url. - Url(Url), - /// A JSON-LD context expressed as a JSON object. - Obj(Object), + /// A JSON-LD context expressed as a Url. + Url(Url), + /// A JSON-LD context expressed as a JSON object. + Obj(Object), } impl Debug for Context { - fn fmt(&self, f: &mut Formatter) -> Result { - match self { - Self::Url(inner) => Debug::fmt(inner, f), - Self::Obj(inner) => Debug::fmt(inner, f), - } + fn fmt(&self, f: &mut Formatter) -> Result { + match self { + Self::Url(inner) => Debug::fmt(inner, f), + Self::Obj(inner) => Debug::fmt(inner, f), } + } } impl From for Context { - fn from(other: Url) -> Self { - Self::Url(other) - } + fn from(other: Url) -> Self { + Self::Url(other) + } } impl From for Context { - fn from(other: Object) -> Self { - Self::Obj(other) - } + fn from(other: Object) -> Self { + Self::Obj(other) + } } impl PartialEq for Context where - T: AsRef + ?Sized, + T: AsRef + ?Sized, { - fn eq(&self, other: &T) -> bool { - match self { - Self::Url(inner) => inner.as_str() == other.as_ref(), - Self::Obj(_) => false, - } + fn eq(&self, other: &T) -> bool { + match self { + Self::Url(inner) => inner.as_str() == other.as_ref(), + Self::Obj(_) => false, } + } } diff --git a/identity-core/src/common/one_or_many.rs b/identity-core/src/common/one_or_many.rs index aaeab6dec7..18c0c01a39 100644 --- a/identity-core/src/common/one_or_many.rs +++ b/identity-core/src/common/one_or_many.rs @@ -2,169 +2,169 @@ // SPDX-License-Identifier: Apache-2.0 use core::{ - fmt::{Debug, Display, Formatter, Result as FmtResult}, - hash::Hash, - mem::replace, - ops::Deref, - slice::from_ref, + fmt::{Debug, Display, Formatter, Result as FmtResult}, + hash::Hash, + mem::replace, + ops::Deref, + slice::from_ref, }; /// A generic container that stores one or many values of a given type. #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] #[serde(untagged)] pub enum OneOrMany { - /// A single instance of `T`. - One(T), - /// Multiple instances of `T`. - Many(Vec), + /// A single instance of `T`. + One(T), + /// Multiple instances of `T`. + Many(Vec), } impl OneOrMany { - /// Returns the number of elements in the collection - pub fn len(&self) -> usize { - match self { - Self::One(_) => 1, - Self::Many(inner) => inner.len(), - } - } - - /// Returns `true` if the collection is empty - pub fn is_empty(&self) -> bool { - match self { - Self::One(_) => false, - Self::Many(inner) => inner.is_empty(), - } - } - - /// Returns a reference to the element at the given index. - pub fn get(&self, index: usize) -> Option<&T> { - match self { - Self::One(inner) if index == 0 => Some(inner), - Self::One(_) => None, - Self::Many(inner) => inner.get(index), - } - } - - /// Returns a mutable reference to the element at the given index. - pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { - match self { - Self::One(ref mut inner) if index == 0 => Some(inner), - Self::One(_) => None, - Self::Many(inner) => inner.get_mut(index), - } - } - - /// Returns `true` if the collection contains the given value. - pub fn contains(&self, value: &T) -> bool - where - T: PartialEq, - { - match self { - Self::One(inner) => inner == value, - Self::Many(inner) => inner.contains(value), - } - } - - /// Adds a new value to the collection. - pub fn push(&mut self, value: T) { - match self { - Self::One(_) => match replace(self, Self::Many(Vec::new())) { - Self::One(inner) => *self = Self::Many(vec![inner, value]), - Self::Many(_) => unreachable!(), - }, - Self::Many(ref mut inner) => { - inner.push(value); - } - } - } - - /// Returns an `Iterator` that yields items from the collection. - pub fn iter(&self) -> impl Iterator + '_ { - OneOrManyIter::new(self) - } - - /// Returns a reference to the contents as a slice. - pub fn as_slice(&self) -> &[T] { - &*self - } - - /// Consumes the [`OneOrMany`] and returns the contents as a [`Vec`]. - pub fn into_vec(self) -> Vec { - match self { - Self::One(inner) => vec![inner], - Self::Many(inner) => inner, - } - } + /// Returns the number of elements in the collection + pub fn len(&self) -> usize { + match self { + Self::One(_) => 1, + Self::Many(inner) => inner.len(), + } + } + + /// Returns `true` if the collection is empty + pub fn is_empty(&self) -> bool { + match self { + Self::One(_) => false, + Self::Many(inner) => inner.is_empty(), + } + } + + /// Returns a reference to the element at the given index. + pub fn get(&self, index: usize) -> Option<&T> { + match self { + Self::One(inner) if index == 0 => Some(inner), + Self::One(_) => None, + Self::Many(inner) => inner.get(index), + } + } + + /// Returns a mutable reference to the element at the given index. + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + match self { + Self::One(ref mut inner) if index == 0 => Some(inner), + Self::One(_) => None, + Self::Many(inner) => inner.get_mut(index), + } + } + + /// Returns `true` if the collection contains the given value. + pub fn contains(&self, value: &T) -> bool + where + T: PartialEq, + { + match self { + Self::One(inner) => inner == value, + Self::Many(inner) => inner.contains(value), + } + } + + /// Adds a new value to the collection. + pub fn push(&mut self, value: T) { + match self { + Self::One(_) => match replace(self, Self::Many(Vec::new())) { + Self::One(inner) => *self = Self::Many(vec![inner, value]), + Self::Many(_) => unreachable!(), + }, + Self::Many(ref mut inner) => { + inner.push(value); + } + } + } + + /// Returns an `Iterator` that yields items from the collection. + pub fn iter(&self) -> impl Iterator + '_ { + OneOrManyIter::new(self) + } + + /// Returns a reference to the contents as a slice. + pub fn as_slice(&self) -> &[T] { + &*self + } + + /// Consumes the [`OneOrMany`] and returns the contents as a [`Vec`]. + pub fn into_vec(self) -> Vec { + match self { + Self::One(inner) => vec![inner], + Self::Many(inner) => inner, + } + } } impl Debug for OneOrMany where - T: Debug, + T: Debug, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - match self { - Self::One(inner) => Debug::fmt(inner, f), - Self::Many(inner) => Debug::fmt(inner, f), - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match self { + Self::One(inner) => Debug::fmt(inner, f), + Self::Many(inner) => Debug::fmt(inner, f), } + } } impl Display for OneOrMany where - T: Display, - Vec: Display, + T: Display, + Vec: Display, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - match self { - Self::One(inner) => Display::fmt(inner, f), - Self::Many(inner) => Display::fmt(inner, f), - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match self { + Self::One(inner) => Display::fmt(inner, f), + Self::Many(inner) => Display::fmt(inner, f), } + } } impl Deref for OneOrMany { - type Target = [T]; + type Target = [T]; - fn deref(&self) -> &Self::Target { - match self { - Self::One(inner) => from_ref(inner), - Self::Many(inner) => &*inner, - } + fn deref(&self) -> &Self::Target { + match self { + Self::One(inner) => from_ref(inner), + Self::Many(inner) => &*inner, } + } } impl AsRef<[T]> for OneOrMany { - fn as_ref(&self) -> &[T] { - &*self - } + fn as_ref(&self) -> &[T] { + &*self + } } impl Default for OneOrMany { - fn default() -> Self { - Self::Many(Vec::new()) - } + fn default() -> Self { + Self::Many(Vec::new()) + } } impl From for OneOrMany { - fn from(other: T) -> Self { - Self::One(other) - } + fn from(other: T) -> Self { + Self::One(other) + } } impl From> for OneOrMany { - fn from(mut other: Vec) -> Self { - if other.len() == 1 { - Self::One(other.pop().expect("infallible")) - } else { - Self::Many(other) - } + fn from(mut other: Vec) -> Self { + if other.len() == 1 { + Self::One(other.pop().expect("infallible")) + } else { + Self::Many(other) } + } } impl From> for Vec { - fn from(other: OneOrMany) -> Self { - other.into_vec() - } + fn from(other: OneOrMany) -> Self { + other.into_vec() + } } // ============================================================================= @@ -172,21 +172,21 @@ impl From> for Vec { // ============================================================================= struct OneOrManyIter<'a, T> { - inner: &'a OneOrMany, - index: usize, + inner: &'a OneOrMany, + index: usize, } impl<'a, T> OneOrManyIter<'a, T> { - pub fn new(inner: &'a OneOrMany) -> Self { - Self { inner, index: 0 } - } + pub fn new(inner: &'a OneOrMany) -> Self { + Self { inner, index: 0 } + } } impl<'a, T> Iterator for OneOrManyIter<'a, T> { - type Item = &'a T; + type Item = &'a T; - fn next(&mut self) -> Option { - self.index += 1; - self.inner.get(self.index - 1) - } + fn next(&mut self) -> Option { + self.index += 1; + self.inner.get(self.index - 1) + } } diff --git a/identity-core/src/common/timestamp.rs b/identity-core/src/common/timestamp.rs index 39da062280..6ecde93ccf 100644 --- a/identity-core/src/common/timestamp.rs +++ b/identity-core/src/common/timestamp.rs @@ -3,9 +3,9 @@ use chrono::{DateTime, SecondsFormat, Utc}; use core::{ - convert::TryFrom, - fmt::{Debug, Display, Formatter, Result as FmtResult}, - str::FromStr, + convert::TryFrom, + fmt::{Debug, Display, Formatter, Result as FmtResult}, + str::FromStr, }; use crate::error::{Error, Result}; @@ -17,110 +17,110 @@ use crate::error::{Error, Result}; pub struct Timestamp(DateTime); impl Timestamp { - /// Parses a [`Timestamp`] from the provided input string. - pub fn parse(input: &str) -> Result { - DateTime::parse_from_rfc3339(input) - .map_err(Into::into) - .map(Into::into) - .map(Self) - } - - /// Creates a new [`Timestamp`] with the current date and time. - pub fn now() -> Self { - Self::parse(&Self::to_rfc3339(&Self(Utc::now()))).unwrap() - } - - /// Returns the [`Timestamp`] as a Unix timestamp. - pub fn to_unix(&self) -> i64 { - self.0.timestamp() - } - - /// Returns the [`Timestamp`] as an RFC 3339 `String`. - pub fn to_rfc3339(&self) -> String { - self.0.to_rfc3339_opts(SecondsFormat::Secs, true) - } + /// Parses a [`Timestamp`] from the provided input string. + pub fn parse(input: &str) -> Result { + DateTime::parse_from_rfc3339(input) + .map_err(Into::into) + .map(Into::into) + .map(Self) + } + + /// Creates a new [`Timestamp`] with the current date and time. + pub fn now() -> Self { + Self::parse(&Self::to_rfc3339(&Self(Utc::now()))).unwrap() + } + + /// Returns the [`Timestamp`] as a Unix timestamp. + pub fn to_unix(&self) -> i64 { + self.0.timestamp() + } + + /// Returns the [`Timestamp`] as an RFC 3339 `String`. + pub fn to_rfc3339(&self) -> String { + self.0.to_rfc3339_opts(SecondsFormat::Secs, true) + } } impl Default for Timestamp { - fn default() -> Self { - Self::now() - } + fn default() -> Self { + Self::now() + } } impl Debug for Timestamp { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - write!(f, "{:?}", self.to_rfc3339()) - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!(f, "{:?}", self.to_rfc3339()) + } } impl Display for Timestamp { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - write!(f, "{}", self.to_rfc3339()) - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!(f, "{}", self.to_rfc3339()) + } } impl From for String { - fn from(other: Timestamp) -> Self { - other.to_rfc3339() - } + fn from(other: Timestamp) -> Self { + other.to_rfc3339() + } } impl TryFrom<&'_ str> for Timestamp { - type Error = Error; + type Error = Error; - fn try_from(string: &'_ str) -> Result { - Self::parse(string) - } + fn try_from(string: &'_ str) -> Result { + Self::parse(string) + } } impl TryFrom for Timestamp { - type Error = Error; + type Error = Error; - fn try_from(string: String) -> Result { - Self::parse(&string) - } + fn try_from(string: String) -> Result { + Self::parse(&string) + } } impl FromStr for Timestamp { - type Err = Error; + type Err = Error; - fn from_str(string: &str) -> Result { - Self::parse(string) - } + fn from_str(string: &str) -> Result { + Self::parse(string) + } } #[cfg(test)] mod tests { - use crate::common::Timestamp; - - #[test] - fn test_parse_valid() { - let original = "2020-01-01T00:00:00Z"; - let timestamp = Timestamp::parse(original).unwrap(); - - assert_eq!(timestamp.to_rfc3339(), original); - - let original = "1980-01-01T12:34:56Z"; - let timestamp = Timestamp::parse(original).unwrap(); - - assert_eq!(timestamp.to_rfc3339(), original); - } - - #[test] - #[should_panic = "InvalidTimestamp"] - fn test_parse_empty() { - Timestamp::parse("").unwrap(); - } - - #[test] - #[should_panic = "InvalidTimestamp"] - fn test_parse_invalid_date() { - Timestamp::parse("foo bar").unwrap(); - } - - #[test] - #[should_panic = "InvalidTimestamp"] - fn test_parse_invalid_fmt() { - Timestamp::parse("2020/01/01 03:30:16").unwrap(); - } + use crate::common::Timestamp; + + #[test] + fn test_parse_valid() { + let original = "2020-01-01T00:00:00Z"; + let timestamp = Timestamp::parse(original).unwrap(); + + assert_eq!(timestamp.to_rfc3339(), original); + + let original = "1980-01-01T12:34:56Z"; + let timestamp = Timestamp::parse(original).unwrap(); + + assert_eq!(timestamp.to_rfc3339(), original); + } + + #[test] + #[should_panic = "InvalidTimestamp"] + fn test_parse_empty() { + Timestamp::parse("").unwrap(); + } + + #[test] + #[should_panic = "InvalidTimestamp"] + fn test_parse_invalid_date() { + Timestamp::parse("foo bar").unwrap(); + } + + #[test] + #[should_panic = "InvalidTimestamp"] + fn test_parse_invalid_fmt() { + Timestamp::parse("2020/01/01 03:30:16").unwrap(); + } } diff --git a/identity-core/src/common/url.rs b/identity-core/src/common/url.rs index 41612b3848..6b7e76ea7c 100644 --- a/identity-core/src/common/url.rs +++ b/identity-core/src/common/url.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use core::{ - fmt::{Debug, Display, Formatter, Result as FmtResult}, - ops::{Deref, DerefMut}, - str::FromStr, + fmt::{Debug, Display, Formatter, Result as FmtResult}, + ops::{Deref, DerefMut}, + str::FromStr, }; use did_doc::url; @@ -17,61 +17,61 @@ use crate::error::{Error, Result}; pub struct Url(url::Url); impl Url { - /// Parses an absolute [`Url`] from the given input string. - pub fn parse(input: impl AsRef) -> Result { - url::Url::parse(input.as_ref()).map_err(Into::into).map(Self) - } + /// Parses an absolute [`Url`] from the given input string. + pub fn parse(input: impl AsRef) -> Result { + url::Url::parse(input.as_ref()).map_err(Into::into).map(Self) + } - /// Consumes the [`Url`] and returns the value as a `String`. - pub fn into_string(self) -> String { - self.0.into_string() - } + /// Consumes the [`Url`] and returns the value as a `String`. + pub fn into_string(self) -> String { + self.0.into_string() + } - /// Parses the given input string as a [`Url`], with `self` as the base Url. - pub fn join(&self, input: impl AsRef) -> Result { - self.0.join(input.as_ref()).map_err(Into::into).map(Self) - } + /// Parses the given input string as a [`Url`], with `self` as the base Url. + pub fn join(&self, input: impl AsRef) -> Result { + self.0.join(input.as_ref()).map_err(Into::into).map(Self) + } } impl Debug for Url { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - Debug::fmt(&self.0, f) - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + Debug::fmt(&self.0, f) + } } impl Display for Url { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - Display::fmt(&self.0, f) - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + Display::fmt(&self.0, f) + } } impl Deref for Url { - type Target = url::Url; + type Target = url::Url; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } impl DerefMut for Url { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } } impl FromStr for Url { - type Err = Error; + type Err = Error; - fn from_str(string: &str) -> Result { - Self::parse(string) - } + fn from_str(string: &str) -> Result { + Self::parse(string) + } } impl PartialEq for Url where - T: AsRef + ?Sized, + T: AsRef + ?Sized, { - fn eq(&self, other: &T) -> bool { - self.as_str() == other.as_ref() - } + fn eq(&self, other: &T) -> bool { + self.as_str() == other.as_ref() + } } diff --git a/identity-core/src/convert/json.rs b/identity-core/src/convert/json.rs index b53b0603ad..1c099163fb 100644 --- a/identity-core/src/convert/json.rs +++ b/identity-core/src/convert/json.rs @@ -6,31 +6,31 @@ use serde::{Deserialize, Serialize}; /// A convenience-trait for types that can be serialized as JSON. pub trait ToJson: Serialize { - /// Serialize `self` as a string of JSON. - fn to_json(&self) -> Result { - serde_json::to_string(self).map_err(Error::EncodeJSON) - } - - /// Serialize `self` as a JSON byte vector. - fn to_json_vec(&self) -> Result> { - serde_json::to_vec(self).map_err(Error::EncodeJSON) - } - - /// Serialize `self` as a [`serde_json::Value`]. - fn to_json_value(&self) -> Result { - serde_json::to_value(self).map_err(Error::EncodeJSON) - } - - /// Serialize `self` as a pretty-printed string of JSON. - fn to_json_pretty(&self) -> Result { - serde_json::to_string_pretty(self).map_err(Error::EncodeJSON) - } - - /// Serialize `self` as a JSON byte vector, normalized using JSON - /// Canonicalization Scheme (JCS). - fn to_jcs(&self) -> Result> { - serde_jcs::to_vec(self).map_err(Error::EncodeJSON) - } + /// Serialize `self` as a string of JSON. + fn to_json(&self) -> Result { + serde_json::to_string(self).map_err(Error::EncodeJSON) + } + + /// Serialize `self` as a JSON byte vector. + fn to_json_vec(&self) -> Result> { + serde_json::to_vec(self).map_err(Error::EncodeJSON) + } + + /// Serialize `self` as a [`serde_json::Value`]. + fn to_json_value(&self) -> Result { + serde_json::to_value(self).map_err(Error::EncodeJSON) + } + + /// Serialize `self` as a pretty-printed string of JSON. + fn to_json_pretty(&self) -> Result { + serde_json::to_string_pretty(self).map_err(Error::EncodeJSON) + } + + /// Serialize `self` as a JSON byte vector, normalized using JSON + /// Canonicalization Scheme (JCS). + fn to_jcs(&self) -> Result> { + serde_jcs::to_vec(self).map_err(Error::EncodeJSON) + } } impl ToJson for T where T: Serialize {} @@ -40,20 +40,20 @@ impl ToJson for T where T: Serialize {} /// A convenience-trait for types that can be deserialized from JSON. pub trait FromJson: for<'de> Deserialize<'de> + Sized { - /// Deserialize `Self` from a string of JSON text. - fn from_json(json: &(impl AsRef + ?Sized)) -> Result { - serde_json::from_str(json.as_ref()).map_err(Error::DecodeJSON) - } - - /// Deserialize `Self` from bytes of JSON text. - fn from_json_slice(json: &(impl AsRef<[u8]> + ?Sized)) -> Result { - serde_json::from_slice(json.as_ref()).map_err(Error::DecodeJSON) - } - - /// Deserialize `Self` from a [`serde_json::Value`]. - fn from_json_value(json: serde_json::Value) -> Result { - serde_json::from_value(json).map_err(Error::DecodeJSON) - } + /// Deserialize `Self` from a string of JSON text. + fn from_json(json: &(impl AsRef + ?Sized)) -> Result { + serde_json::from_str(json.as_ref()).map_err(Error::DecodeJSON) + } + + /// Deserialize `Self` from bytes of JSON text. + fn from_json_slice(json: &(impl AsRef<[u8]> + ?Sized)) -> Result { + serde_json::from_slice(json.as_ref()).map_err(Error::DecodeJSON) + } + + /// Deserialize `Self` from a [`serde_json::Value`]. + fn from_json_value(json: serde_json::Value) -> Result { + serde_json::from_value(json).map_err(Error::DecodeJSON) + } } impl FromJson for T where T: for<'de> Deserialize<'de> + Sized {} @@ -63,46 +63,46 @@ impl FromJson for T where T: for<'de> Deserialize<'de> + Sized {} /// A convenience-trait for types that can be converted to and from JSON. pub trait AsJson: FromJson + ToJson { - /// Deserialize `Self` from a string of JSON text. - fn from_json(json: &(impl AsRef + ?Sized)) -> Result { - ::from_json(json) - } - - /// Deserialize `Self` from bytes of JSON text. - fn from_json_slice(json: &(impl AsRef<[u8]> + ?Sized)) -> Result { - ::from_json_slice(json) - } - - /// Deserialize `Self` from a [`serde_json::Value`]. - fn from_json_value(json: serde_json::Value) -> Result { - ::from_json_value(json) - } - - /// Serialize `self` as a string of JSON. - fn to_json(&self) -> Result { - ::to_json(self) - } - - /// Serialize `self` as a JSON byte vector. - fn to_json_vec(&self) -> Result> { - ::to_json_vec(self) - } - - /// Serialize `self` as a [`serde_json::Value`]. - fn to_json_value(&self) -> Result { - ::to_json_value(self) - } - - /// Serialize `self` as a pretty-printed string of JSON. - fn to_json_pretty(&self) -> Result { - ::to_json_pretty(self) - } - - /// Serialize `self` as a JSON byte vector, normalized using JSON - /// Canonicalization Scheme (JCS). - fn to_jcs(&self) -> Result> { - ::to_jcs(self) - } + /// Deserialize `Self` from a string of JSON text. + fn from_json(json: &(impl AsRef + ?Sized)) -> Result { + ::from_json(json) + } + + /// Deserialize `Self` from bytes of JSON text. + fn from_json_slice(json: &(impl AsRef<[u8]> + ?Sized)) -> Result { + ::from_json_slice(json) + } + + /// Deserialize `Self` from a [`serde_json::Value`]. + fn from_json_value(json: serde_json::Value) -> Result { + ::from_json_value(json) + } + + /// Serialize `self` as a string of JSON. + fn to_json(&self) -> Result { + ::to_json(self) + } + + /// Serialize `self` as a JSON byte vector. + fn to_json_vec(&self) -> Result> { + ::to_json_vec(self) + } + + /// Serialize `self` as a [`serde_json::Value`]. + fn to_json_value(&self) -> Result { + ::to_json_value(self) + } + + /// Serialize `self` as a pretty-printed string of JSON. + fn to_json_pretty(&self) -> Result { + ::to_json_pretty(self) + } + + /// Serialize `self` as a JSON byte vector, normalized using JSON + /// Canonicalization Scheme (JCS). + fn to_jcs(&self) -> Result> { + ::to_jcs(self) + } } impl AsJson for T where T: FromJson + ToJson {} diff --git a/identity-core/src/convert/serde_into.rs b/identity-core/src/convert/serde_into.rs index 6cf5ba5d80..a4e1dc8477 100644 --- a/identity-core/src/convert/serde_into.rs +++ b/identity-core/src/convert/serde_into.rs @@ -2,20 +2,20 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - convert::{AsJson, ToJson}, - error::Result, + convert::{AsJson, ToJson}, + error::Result, }; /// An escape-hatch for converting between types that represent the same JSON /// structure. pub trait SerdeInto: ToJson { - /// Converts `self` to `T` by converting to/from JSON. - fn serde_into(&self) -> Result - where - T: AsJson, - { - ::to_json_value(self).and_then(::from_json_value) - } + /// Converts `self` to `T` by converting to/from JSON. + fn serde_into(&self) -> Result + where + T: AsJson, + { + ::to_json_value(self).and_then(::from_json_value) + } } impl SerdeInto for T where T: ToJson {} diff --git a/identity-core/src/credential/credential.rs b/identity-core/src/credential/credential.rs index ed1c5e9171..18400e7c95 100644 --- a/identity-core/src/credential/credential.rs +++ b/identity-core/src/credential/credential.rs @@ -6,19 +6,19 @@ use did_doc::{Document, LdSuite, MethodQuery, MethodType, MethodWrap, SignatureO use serde::Serialize; use crate::{ - common::{Context, Object, OneOrMany, Timestamp, Url}, - convert::ToJson as _, - credential::{ - CredentialBuilder, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, RefreshService, - TermsOfUse, VerifiableCredential, - }, - crypto::SecretKey, - error::{Error, Result}, - proof::JcsEd25519Signature2020, + common::{Context, Object, OneOrMany, Timestamp, Url}, + convert::ToJson as _, + credential::{ + CredentialBuilder, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, RefreshService, + TermsOfUse, VerifiableCredential, + }, + crypto::SecretKey, + error::{Error, Result}, + proof::JcsEd25519Signature2020, }; lazy_static! { - static ref BASE_CONTEXT: Context = Context::Url(Url::parse("https://www.w3.org/2018/credentials/v1").unwrap()); + static ref BASE_CONTEXT: Context = Context::Url(Url::parse("https://www.w3.org/2018/credentials/v1").unwrap()); } /// A `Credential` represents a set of claims describing an entity. @@ -26,195 +26,195 @@ lazy_static! { /// `Credential`s can be signed with `Document`s to create `VerifiableCredential`s. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct Credential { - /// The JSON-LD context(s) applicable to the `Credential`. - #[serde(rename = "@context")] - pub context: OneOrMany, - /// A unique `URI` referencing the subject of the `Credential`. - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - /// One or more URIs defining the type of the `Credential`. - #[serde(rename = "type")] - pub types: OneOrMany, - /// One or more `Object`s representing the `Credential` subject(s). - #[serde(rename = "credentialSubject")] - pub credential_subject: OneOrMany, - /// A reference to the issuer of the `Credential`. - pub issuer: Issuer, - /// A timestamp of when the `Credential` becomes valid. - #[serde(rename = "issuanceDate")] - pub issuance_date: Timestamp, - /// A timestamp of when the `Credential` should no longer be considered valid. - #[serde(rename = "expirationDate", skip_serializing_if = "Option::is_none")] - pub expiration_date: Option, - /// Information used to determine the current status of the `Credential`. - #[serde(default, rename = "credentialStatus", skip_serializing_if = "OneOrMany::is_empty")] - pub credential_status: OneOrMany, - /// Information used to assist in the enforcement of a specific `Credential` structure. - #[serde(default, rename = "credentialSchema", skip_serializing_if = "OneOrMany::is_empty")] - pub credential_schema: OneOrMany, - /// Service(s) used to refresh an expired `Credential`. - #[serde(default, rename = "refreshService", skip_serializing_if = "OneOrMany::is_empty")] - pub refresh_service: OneOrMany, - /// Terms-of-use specified by the `Credential` issuer. - #[serde(default, rename = "termsOfUse", skip_serializing_if = "OneOrMany::is_empty")] - pub terms_of_use: OneOrMany, - /// Human-readable evidence used to support the claims within the `Credential`. - #[serde(default, skip_serializing_if = "OneOrMany::is_empty")] - pub evidence: OneOrMany, - /// Indicates that the `Credential` must only be contained within a - /// `Presentation` with a proof issued from the `Credential` subject. - #[serde(rename = "nonTransferable", skip_serializing_if = "Option::is_none")] - pub non_transferable: Option, - /// Miscellaneous properties. - #[serde(flatten)] - pub properties: T, + /// The JSON-LD context(s) applicable to the `Credential`. + #[serde(rename = "@context")] + pub context: OneOrMany, + /// A unique `URI` referencing the subject of the `Credential`. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + /// One or more URIs defining the type of the `Credential`. + #[serde(rename = "type")] + pub types: OneOrMany, + /// One or more `Object`s representing the `Credential` subject(s). + #[serde(rename = "credentialSubject")] + pub credential_subject: OneOrMany, + /// A reference to the issuer of the `Credential`. + pub issuer: Issuer, + /// A timestamp of when the `Credential` becomes valid. + #[serde(rename = "issuanceDate")] + pub issuance_date: Timestamp, + /// A timestamp of when the `Credential` should no longer be considered valid. + #[serde(rename = "expirationDate", skip_serializing_if = "Option::is_none")] + pub expiration_date: Option, + /// Information used to determine the current status of the `Credential`. + #[serde(default, rename = "credentialStatus", skip_serializing_if = "OneOrMany::is_empty")] + pub credential_status: OneOrMany, + /// Information used to assist in the enforcement of a specific `Credential` structure. + #[serde(default, rename = "credentialSchema", skip_serializing_if = "OneOrMany::is_empty")] + pub credential_schema: OneOrMany, + /// Service(s) used to refresh an expired `Credential`. + #[serde(default, rename = "refreshService", skip_serializing_if = "OneOrMany::is_empty")] + pub refresh_service: OneOrMany, + /// Terms-of-use specified by the `Credential` issuer. + #[serde(default, rename = "termsOfUse", skip_serializing_if = "OneOrMany::is_empty")] + pub terms_of_use: OneOrMany, + /// Human-readable evidence used to support the claims within the `Credential`. + #[serde(default, skip_serializing_if = "OneOrMany::is_empty")] + pub evidence: OneOrMany, + /// Indicates that the `Credential` must only be contained within a + /// `Presentation` with a proof issued from the `Credential` subject. + #[serde(rename = "nonTransferable", skip_serializing_if = "Option::is_none")] + pub non_transferable: Option, + /// Miscellaneous properties. + #[serde(flatten)] + pub properties: T, } impl Credential { - /// Returns the base JSON-LD context for `Credential`s. - pub fn base_context() -> &'static Context { - &*BASE_CONTEXT + /// Returns the base JSON-LD context for `Credential`s. + pub fn base_context() -> &'static Context { + &*BASE_CONTEXT + } + + /// Returns the base type for `Credential`s. + pub const fn base_type() -> &'static str { + "VerifiableCredential" + } + + /// Creates a `CredentialBuilder` to configure a new `Credential`. + /// + /// This is the same as `CredentialBuilder::new()`. + pub fn builder(properties: T) -> CredentialBuilder { + CredentialBuilder::new(properties) + } + + /// Returns a new `Credential` based on the `CredentialBuilder` configuration. + pub fn from_builder(builder: CredentialBuilder) -> Result { + let this: Self = Self { + context: builder.context.into(), + id: builder.id, + types: builder.types.into(), + credential_subject: builder.credential_subject.into(), + issuer: builder + .issuer + .ok_or_else(|| Error::InvalidCredential("Missing Credential Issuer".into()))?, + issuance_date: builder.issuance_date.unwrap_or_default(), + expiration_date: builder.expiration_date, + credential_status: builder.credential_status.into(), + credential_schema: builder.credential_schema.into(), + refresh_service: builder.refresh_service.into(), + terms_of_use: builder.terms_of_use.into(), + evidence: builder.evidence.into(), + non_transferable: builder.non_transferable, + properties: builder.properties, + }; + + this.check_structure()?; + + Ok(this) + } + + /// Validates the semantic structure of the `Credential`. + pub fn check_structure(&self) -> Result<()> { + // Ensure the base context is present and in the correct location + match self.context.get(0) { + Some(context) if context == Self::base_context() => {} + Some(_) | None => return Err(Error::InvalidCredential("Missing Base Context".into())), } - /// Returns the base type for `Credential`s. - pub const fn base_type() -> &'static str { - "VerifiableCredential" + // The set of types MUST contain the base type + if !self.types.iter().any(|type_| type_ == Self::base_type()) { + return Err(Error::InvalidCredential("Missing Base Type".into())); } - /// Creates a `CredentialBuilder` to configure a new `Credential`. - /// - /// This is the same as `CredentialBuilder::new()`. - pub fn builder(properties: T) -> CredentialBuilder { - CredentialBuilder::new(properties) + // Credentials MUST have at least one subject + if self.credential_subject.is_empty() { + return Err(Error::InvalidCredential("Missing Subject".into())); } - /// Returns a new `Credential` based on the `CredentialBuilder` configuration. - pub fn from_builder(builder: CredentialBuilder) -> Result { - let this: Self = Self { - context: builder.context.into(), - id: builder.id, - types: builder.types.into(), - credential_subject: builder.credential_subject.into(), - issuer: builder - .issuer - .ok_or_else(|| Error::InvalidCredential("Missing Credential Issuer".into()))?, - issuance_date: builder.issuance_date.unwrap_or_default(), - expiration_date: builder.expiration_date, - credential_status: builder.credential_status.into(), - credential_schema: builder.credential_schema.into(), - refresh_service: builder.refresh_service.into(), - terms_of_use: builder.terms_of_use.into(), - evidence: builder.evidence.into(), - non_transferable: builder.non_transferable, - properties: builder.properties, - }; - - this.check_structure()?; - - Ok(this) + // Each subject is defined as one or more properties - no empty objects + for subject in self.credential_subject.iter() { + if subject.id.is_none() && subject.properties.is_empty() { + return Err(Error::InvalidCredential("Invalid Subject".into())); + } } - /// Validates the semantic structure of the `Credential`. - pub fn check_structure(&self) -> Result<()> { - // Ensure the base context is present and in the correct location - match self.context.get(0) { - Some(context) if context == Self::base_context() => {} - Some(_) | None => return Err(Error::InvalidCredential("Missing Base Context".into())), - } - - // The set of types MUST contain the base type - if !self.types.iter().any(|type_| type_ == Self::base_type()) { - return Err(Error::InvalidCredential("Missing Base Type".into())); - } - - // Credentials MUST have at least one subject - if self.credential_subject.is_empty() { - return Err(Error::InvalidCredential("Missing Subject".into())); - } - - // Each subject is defined as one or more properties - no empty objects - for subject in self.credential_subject.iter() { - if subject.id.is_none() && subject.properties.is_empty() { - return Err(Error::InvalidCredential("Invalid Subject".into())); - } - } - - Ok(()) - } + Ok(()) + } + + /// Creates a new [`VerifiableCredential`] by signing `self` with `document` + /// and `secret`. + pub fn sign<'a, Q, D1, D2, D3>( + self, + document: &Document, + query: Q, + secret: &SecretKey, + ) -> Result> + where + T: Serialize, + Q: Into>, + { + let method: MethodWrap<'_, D2> = document.try_resolve(query)?; + + match method.key_type() { + MethodType::Ed25519VerificationKey2018 => { + let options: SignatureOptions = method.into(); + let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); + + let mut verifiable: VerifiableCredential = VerifiableCredential::new(self, Vec::new()); - /// Creates a new [`VerifiableCredential`] by signing `self` with `document` - /// and `secret`. - pub fn sign<'a, Q, D1, D2, D3>( - self, - document: &Document, - query: Q, - secret: &SecretKey, - ) -> Result> - where - T: Serialize, - Q: Into>, - { - let method: MethodWrap<'_, D2> = document.try_resolve(query)?; - - match method.key_type() { - MethodType::Ed25519VerificationKey2018 => { - let options: SignatureOptions = method.into(); - let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); - - let mut verifiable: VerifiableCredential = VerifiableCredential::new(self, Vec::new()); - - suite.sign(&mut verifiable, options, secret)?; - - Ok(verifiable) - } - _ => Err(Error::InvalidCredential("Verification Method Not Supported".into())), - } + suite.sign(&mut verifiable, options, secret)?; + + Ok(verifiable) + } + _ => Err(Error::InvalidCredential("Verification Method Not Supported".into())), } + } } impl Display for Credential where - T: Serialize, + T: Serialize, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - if f.alternate() { - f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) - } else { - f.write_str(&self.to_json().map_err(|_| FmtError)?) - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + if f.alternate() { + f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) + } else { + f.write_str(&self.to_json().map_err(|_| FmtError)?) } + } } #[cfg(test)] mod tests { - use crate::{convert::FromJson as _, credential::Credential}; - - const JSON1: &str = include_str!("../../tests/fixtures/vc/credential-1.json"); - const JSON2: &str = include_str!("../../tests/fixtures/vc/credential-2.json"); - const JSON3: &str = include_str!("../../tests/fixtures/vc/credential-3.json"); - const JSON4: &str = include_str!("../../tests/fixtures/vc/credential-4.json"); - const JSON5: &str = include_str!("../../tests/fixtures/vc/credential-5.json"); - const JSON6: &str = include_str!("../../tests/fixtures/vc/credential-6.json"); - const JSON7: &str = include_str!("../../tests/fixtures/vc/credential-7.json"); - const JSON8: &str = include_str!("../../tests/fixtures/vc/credential-8.json"); - const JSON9: &str = include_str!("../../tests/fixtures/vc/credential-9.json"); - const JSON10: &str = include_str!("../../tests/fixtures/vc/credential-10.json"); - const JSON11: &str = include_str!("../../tests/fixtures/vc/credential-11.json"); - const JSON12: &str = include_str!("../../tests/fixtures/vc/credential-12.json"); - - #[test] - fn test_from_json() { - let _credential: Credential = Credential::from_json(JSON1).unwrap(); - let _credential: Credential = Credential::from_json(JSON2).unwrap(); - let _credential: Credential = Credential::from_json(JSON3).unwrap(); - let _credential: Credential = Credential::from_json(JSON4).unwrap(); - let _credential: Credential = Credential::from_json(JSON5).unwrap(); - let _credential: Credential = Credential::from_json(JSON6).unwrap(); - let _credential: Credential = Credential::from_json(JSON7).unwrap(); - let _credential: Credential = Credential::from_json(JSON8).unwrap(); - let _credential: Credential = Credential::from_json(JSON9).unwrap(); - let _credential: Credential = Credential::from_json(JSON10).unwrap(); - let _credential: Credential = Credential::from_json(JSON11).unwrap(); - let _credential: Credential = Credential::from_json(JSON12).unwrap(); - } + use crate::{convert::FromJson as _, credential::Credential}; + + const JSON1: &str = include_str!("../../tests/fixtures/vc/credential-1.json"); + const JSON2: &str = include_str!("../../tests/fixtures/vc/credential-2.json"); + const JSON3: &str = include_str!("../../tests/fixtures/vc/credential-3.json"); + const JSON4: &str = include_str!("../../tests/fixtures/vc/credential-4.json"); + const JSON5: &str = include_str!("../../tests/fixtures/vc/credential-5.json"); + const JSON6: &str = include_str!("../../tests/fixtures/vc/credential-6.json"); + const JSON7: &str = include_str!("../../tests/fixtures/vc/credential-7.json"); + const JSON8: &str = include_str!("../../tests/fixtures/vc/credential-8.json"); + const JSON9: &str = include_str!("../../tests/fixtures/vc/credential-9.json"); + const JSON10: &str = include_str!("../../tests/fixtures/vc/credential-10.json"); + const JSON11: &str = include_str!("../../tests/fixtures/vc/credential-11.json"); + const JSON12: &str = include_str!("../../tests/fixtures/vc/credential-12.json"); + + #[test] + fn test_from_json() { + let _credential: Credential = Credential::from_json(JSON1).unwrap(); + let _credential: Credential = Credential::from_json(JSON2).unwrap(); + let _credential: Credential = Credential::from_json(JSON3).unwrap(); + let _credential: Credential = Credential::from_json(JSON4).unwrap(); + let _credential: Credential = Credential::from_json(JSON5).unwrap(); + let _credential: Credential = Credential::from_json(JSON6).unwrap(); + let _credential: Credential = Credential::from_json(JSON7).unwrap(); + let _credential: Credential = Credential::from_json(JSON8).unwrap(); + let _credential: Credential = Credential::from_json(JSON9).unwrap(); + let _credential: Credential = Credential::from_json(JSON10).unwrap(); + let _credential: Credential = Credential::from_json(JSON11).unwrap(); + let _credential: Credential = Credential::from_json(JSON12).unwrap(); + } } diff --git a/identity-core/src/credential/credential_builder.rs b/identity-core/src/credential/credential_builder.rs index c89647211f..9328a57f5a 100644 --- a/identity-core/src/credential/credential_builder.rs +++ b/identity-core/src/credential/credential_builder.rs @@ -2,214 +2,215 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - common::{Context, Object, Timestamp, Url, Value}, - credential::{ - Credential, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, RefreshService, TermsOfUse, - }, - error::Result, + common::{Context, Object, Timestamp, Url, Value}, + credential::{ + Credential, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, RefreshService, TermsOfUse, + }, + error::Result, }; /// A `CredentialBuilder` is used to create a customized `Credential`. #[derive(Clone, Debug)] pub struct CredentialBuilder { - pub(crate) context: Vec, - pub(crate) id: Option, - pub(crate) types: Vec, - pub(crate) credential_subject: Vec, - pub(crate) issuer: Option, - pub(crate) issuance_date: Option, - pub(crate) expiration_date: Option, - pub(crate) credential_status: Vec, - pub(crate) credential_schema: Vec, - pub(crate) refresh_service: Vec, - pub(crate) terms_of_use: Vec, - pub(crate) evidence: Vec, - pub(crate) non_transferable: Option, - pub(crate) properties: T, + pub(crate) context: Vec, + pub(crate) id: Option, + pub(crate) types: Vec, + pub(crate) credential_subject: Vec, + pub(crate) issuer: Option, + pub(crate) issuance_date: Option, + pub(crate) expiration_date: Option, + pub(crate) credential_status: Vec, + pub(crate) credential_schema: Vec, + pub(crate) refresh_service: Vec, + pub(crate) terms_of_use: Vec, + pub(crate) evidence: Vec, + pub(crate) non_transferable: Option, + pub(crate) properties: T, } impl CredentialBuilder { - /// Creates a new `CredentialBuilder`. - pub fn new(properties: T) -> Self { - Self { - context: vec![Credential::::base_context().clone()], - id: None, - types: vec![Credential::::base_type().into()], - credential_subject: Vec::new(), - issuer: None, - issuance_date: None, - expiration_date: None, - credential_status: Vec::new(), - credential_schema: Vec::new(), - refresh_service: Vec::new(), - terms_of_use: Vec::new(), - evidence: Vec::new(), - non_transferable: None, - properties, - } - } - - /// Adds a value to the `Credential` context set. - #[must_use] - pub fn context(mut self, value: impl Into) -> Self { - self.context.push(value.into()); - self - } - - /// Sets the value of the `Credential` `id`. - #[must_use] - pub fn id(mut self, value: Url) -> Self { - self.id = Some(value); - self - } - - /// Adds a value to the `Credential` type set. - #[must_use] - pub fn type_(mut self, value: impl Into) -> Self { - self.types.push(value.into()); - self - } - - /// Adds a value to the `credentialSubject` set. - #[must_use] - pub fn credential_subject(mut self, value: CredentialSubject) -> Self { - self.credential_subject.push(value); - self - } - - /// Sets the value of the `Credential` `issuer`. - #[must_use] - pub fn issuer(mut self, value: impl Into) -> Self { - self.issuer = Some(value.into()); - self - } - - /// Sets the value of the `Credential` `issuanceDate`. - #[must_use] - pub fn issuance_date(mut self, value: Timestamp) -> Self { - self.issuance_date = Some(value); - self - } - - /// Sets the value of the `Credential` `expirationDate`. - #[must_use] - pub fn expiration_date(mut self, value: Timestamp) -> Self { - self.expiration_date = Some(value); - self - } - - /// Adds a value to the `credentialStatus` set. - #[must_use] - pub fn credential_status(mut self, value: CredentialStatus) -> Self { - self.credential_status.push(value); - self - } - - /// Adds a value to the `credentialSchema` set. - #[must_use] - pub fn credential_schema(mut self, value: CredentialSchema) -> Self { - self.credential_schema.push(value); - self - } - - /// Adds a value to the `refreshService` set. - #[must_use] - pub fn refresh_service(mut self, value: RefreshService) -> Self { - self.refresh_service.push(value); - self - } - - /// Adds a value to the `termsOfUse` set. - #[must_use] - pub fn terms_of_use(mut self, value: TermsOfUse) -> Self { - self.terms_of_use.push(value); - self - } - - /// Adds a value to the `evidence` set. - #[must_use] - pub fn evidence(mut self, value: Evidence) -> Self { - self.evidence.push(value); - self - } - - /// Sets the value of the `Credential` `nonTransferable` property. - #[must_use] - pub fn non_transferable(mut self, value: bool) -> Self { - self.non_transferable = Some(value); - self - } - - /// Returns a new `Credential` based on the `CredentialBuilder` configuration. - pub fn build(self) -> Result> { - Credential::from_builder(self) - } + /// Creates a new `CredentialBuilder`. + pub fn new(properties: T) -> Self { + Self { + context: vec![Credential::::base_context().clone()], + id: None, + types: vec![Credential::::base_type().into()], + credential_subject: Vec::new(), + issuer: None, + issuance_date: None, + expiration_date: None, + credential_status: Vec::new(), + credential_schema: Vec::new(), + refresh_service: Vec::new(), + terms_of_use: Vec::new(), + evidence: Vec::new(), + non_transferable: None, + properties, + } + } + + /// Adds a value to the `Credential` context set. + #[must_use] + pub fn context(mut self, value: impl Into) -> Self { + self.context.push(value.into()); + self + } + + /// Sets the value of the `Credential` `id`. + #[must_use] + pub fn id(mut self, value: Url) -> Self { + self.id = Some(value); + self + } + + /// Adds a value to the `Credential` type set. + #[must_use] + pub fn type_(mut self, value: impl Into) -> Self { + self.types.push(value.into()); + self + } + + /// Adds a value to the `credentialSubject` set. + #[must_use] + pub fn credential_subject(mut self, value: CredentialSubject) -> Self { + self.credential_subject.push(value); + self + } + + /// Sets the value of the `Credential` `issuer`. + #[must_use] + pub fn issuer(mut self, value: impl Into) -> Self { + self.issuer = Some(value.into()); + self + } + + /// Sets the value of the `Credential` `issuanceDate`. + #[must_use] + pub fn issuance_date(mut self, value: Timestamp) -> Self { + self.issuance_date = Some(value); + self + } + + /// Sets the value of the `Credential` `expirationDate`. + #[must_use] + pub fn expiration_date(mut self, value: Timestamp) -> Self { + self.expiration_date = Some(value); + self + } + + /// Adds a value to the `credentialStatus` set. + #[must_use] + pub fn credential_status(mut self, value: CredentialStatus) -> Self { + self.credential_status.push(value); + self + } + + /// Adds a value to the `credentialSchema` set. + #[must_use] + pub fn credential_schema(mut self, value: CredentialSchema) -> Self { + self.credential_schema.push(value); + self + } + + /// Adds a value to the `refreshService` set. + #[must_use] + pub fn refresh_service(mut self, value: RefreshService) -> Self { + self.refresh_service.push(value); + self + } + + /// Adds a value to the `termsOfUse` set. + #[must_use] + pub fn terms_of_use(mut self, value: TermsOfUse) -> Self { + self.terms_of_use.push(value); + self + } + + /// Adds a value to the `evidence` set. + #[must_use] + pub fn evidence(mut self, value: Evidence) -> Self { + self.evidence.push(value); + self + } + + /// Sets the value of the `Credential` `nonTransferable` property. + #[must_use] + pub fn non_transferable(mut self, value: bool) -> Self { + self.non_transferable = Some(value); + self + } + + /// Returns a new `Credential` based on the `CredentialBuilder` configuration. + pub fn build(self) -> Result> { + Credential::from_builder(self) + } } impl CredentialBuilder { - /// Adds a new custom property to the `Credential`. - #[must_use] - pub fn property(mut self, key: K, value: V) -> Self - where - K: Into, - V: Into, - { - self.properties.insert(key.into(), value.into()); - self - } - - /// Adds a series of custom properties to the `Credential`. - #[must_use] - pub fn properties(mut self, iter: I) -> Self - where - I: IntoIterator, - K: Into, - V: Into, - { - self.properties - .extend(iter.into_iter().map(|(k, v)| (k.into(), v.into()))); - self - } + /// Adds a new custom property to the `Credential`. + #[must_use] + pub fn property(mut self, key: K, value: V) -> Self + where + K: Into, + V: Into, + { + self.properties.insert(key.into(), value.into()); + self + } + + /// Adds a series of custom properties to the `Credential`. + #[must_use] + pub fn properties(mut self, iter: I) -> Self + where + I: IntoIterator, + K: Into, + V: Into, + { + self + .properties + .extend(iter.into_iter().map(|(k, v)| (k.into(), v.into()))); + self + } } impl Default for CredentialBuilder where - T: Default, + T: Default, { - fn default() -> Self { - Self::new(T::default()) - } + fn default() -> Self { + Self::new(T::default()) + } } #[cfg(test)] mod tests { - use serde_json::{json, Value}; - - use crate::{ - common::{Object, Timestamp, Url}, - convert::FromJson as _, - credential::{Credential as Credential_, CredentialBuilder, CredentialSubject}, - }; - - type Credential = Credential_; - - fn subject() -> CredentialSubject { - let json: Value = json!({ - "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts" - } - }); - - CredentialSubject::from_json_value(json).unwrap() - } + use serde_json::{json, Value}; + + use crate::{ + common::{Object, Timestamp, Url}, + convert::FromJson as _, + credential::{Credential as Credential_, CredentialBuilder, CredentialSubject}, + }; + + type Credential = Credential_; + + fn subject() -> CredentialSubject { + let json: Value = json!({ + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts" + } + }); - fn issuer() -> Url { - Url::parse("did:example:issuer").unwrap() - } + CredentialSubject::from_json_value(json).unwrap() + } + + fn issuer() -> Url { + Url::parse("did:example:issuer").unwrap() + } - #[test] + #[test] #[rustfmt::skip] fn test_credential_builder_valid() { let credential: Credential = CredentialBuilder::default() @@ -237,18 +238,18 @@ mod tests { assert_eq!(credential.credential_subject.get(0).unwrap().properties["degree"]["name"], "Bachelor of Science and Arts"); } - #[test] - #[should_panic = "Missing Subject"] - fn test_builder_missing_subjects() { - let _: Credential = CredentialBuilder::default().issuer(issuer()).build().unwrap(); - } - - #[test] - #[should_panic = "Missing Credential Issuer"] - fn test_builder_missing_issuer() { - let _: Credential = CredentialBuilder::default() - .credential_subject(subject()) - .build() - .unwrap(); - } + #[test] + #[should_panic = "Missing Subject"] + fn test_builder_missing_subjects() { + let _: Credential = CredentialBuilder::default().issuer(issuer()).build().unwrap(); + } + + #[test] + #[should_panic = "Missing Credential Issuer"] + fn test_builder_missing_issuer() { + let _: Credential = CredentialBuilder::default() + .credential_subject(subject()) + .build() + .unwrap(); + } } diff --git a/identity-core/src/credential/presentation.rs b/identity-core/src/credential/presentation.rs index 5d418fda03..7e3252e323 100644 --- a/identity-core/src/credential/presentation.rs +++ b/identity-core/src/credential/presentation.rs @@ -5,10 +5,10 @@ use core::fmt::{Display, Error as FmtError, Formatter, Result as FmtResult}; use serde::Serialize; use crate::{ - common::{Context, Object, OneOrMany, Url}, - convert::ToJson as _, - credential::{Credential, PresentationBuilder, RefreshService, TermsOfUse, VerifiableCredential}, - error::{Error, Result}, + common::{Context, Object, OneOrMany, Url}, + convert::ToJson as _, + credential::{Credential, PresentationBuilder, RefreshService, TermsOfUse, VerifiableCredential}, + error::{Error, Result}, }; /// A `Presentation` represents a bundle of one or more `VerifiableCredential`s. @@ -16,114 +16,114 @@ use crate::{ /// `Presentation`s can be signed with `Document`s to create `VerifiablePresentation`s. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct Presentation { - /// The JSON-LD context(s) applicable to the `Presentation`. - #[serde(rename = "@context")] - pub context: OneOrMany, - /// A unique `URI` referencing the subject of the `Presentation`. - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - /// One or more URIs defining the type of the `Presentation`. - #[serde(rename = "type")] - pub types: OneOrMany, - /// Credential(s) expressing the claims of the `Presentation`. - #[serde(default = "Default::default", rename = "verifiableCredential")] - pub verifiable_credential: OneOrMany>, - /// The entity that generated the presentation. - #[serde(skip_serializing_if = "Option::is_none")] - pub holder: Option, - /// Service(s) used to refresh an expired `Presentation`. - #[serde(default, rename = "refreshService", skip_serializing_if = "OneOrMany::is_empty")] - pub refresh_service: OneOrMany, - /// Terms-of-use specified by the `Presentation` holder. - #[serde(default, rename = "termsOfUse", skip_serializing_if = "OneOrMany::is_empty")] - pub terms_of_use: OneOrMany, - /// Miscellaneous properties. - #[serde(flatten)] - pub properties: T, + /// The JSON-LD context(s) applicable to the `Presentation`. + #[serde(rename = "@context")] + pub context: OneOrMany, + /// A unique `URI` referencing the subject of the `Presentation`. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + /// One or more URIs defining the type of the `Presentation`. + #[serde(rename = "type")] + pub types: OneOrMany, + /// Credential(s) expressing the claims of the `Presentation`. + #[serde(default = "Default::default", rename = "verifiableCredential")] + pub verifiable_credential: OneOrMany>, + /// The entity that generated the presentation. + #[serde(skip_serializing_if = "Option::is_none")] + pub holder: Option, + /// Service(s) used to refresh an expired `Presentation`. + #[serde(default, rename = "refreshService", skip_serializing_if = "OneOrMany::is_empty")] + pub refresh_service: OneOrMany, + /// Terms-of-use specified by the `Presentation` holder. + #[serde(default, rename = "termsOfUse", skip_serializing_if = "OneOrMany::is_empty")] + pub terms_of_use: OneOrMany, + /// Miscellaneous properties. + #[serde(flatten)] + pub properties: T, } impl Presentation { - /// Returns the base JSON-LD context for `Presentation`s. - pub fn base_context() -> &'static Context { - Credential::::base_context() - } + /// Returns the base JSON-LD context for `Presentation`s. + pub fn base_context() -> &'static Context { + Credential::::base_context() + } + + /// Returns the base type for `Presentation`s. + pub const fn base_type() -> &'static str { + "VerifiablePresentation" + } + + /// Creates a `PresentationBuilder` to configure a new `Presentation`. + /// + /// This is the same as `PresentationBuilder::new()`. + pub fn builder(properties: T) -> PresentationBuilder { + PresentationBuilder::new(properties) + } + + /// Returns a new `Presentation` based on the `PresentationBuilder` configuration. + pub fn from_builder(builder: PresentationBuilder) -> Result { + let this: Self = Self { + context: builder.context.into(), + id: builder.id, + types: builder.types.into(), + verifiable_credential: builder.verifiable_credential.into(), + holder: builder.holder, + refresh_service: builder.refresh_service.into(), + terms_of_use: builder.terms_of_use.into(), + properties: builder.properties, + }; - /// Returns the base type for `Presentation`s. - pub const fn base_type() -> &'static str { - "VerifiablePresentation" - } + this.check_structure()?; - /// Creates a `PresentationBuilder` to configure a new `Presentation`. - /// - /// This is the same as `PresentationBuilder::new()`. - pub fn builder(properties: T) -> PresentationBuilder { - PresentationBuilder::new(properties) + Ok(this) + } + + /// Validates the semantic structure of the `Presentation`. + pub fn check_structure(&self) -> Result<()> { + // Ensure the base context is present and in the correct location + match self.context.get(0) { + Some(context) if context == Self::base_context() => {} + Some(_) | None => return Err(Error::InvalidPresentation("Missing Base Context".into())), } - /// Returns a new `Presentation` based on the `PresentationBuilder` configuration. - pub fn from_builder(builder: PresentationBuilder) -> Result { - let this: Self = Self { - context: builder.context.into(), - id: builder.id, - types: builder.types.into(), - verifiable_credential: builder.verifiable_credential.into(), - holder: builder.holder, - refresh_service: builder.refresh_service.into(), - terms_of_use: builder.terms_of_use.into(), - properties: builder.properties, - }; - - this.check_structure()?; - - Ok(this) + // The set of types MUST contain the base type + if !self.types.iter().any(|type_| type_ == Self::base_type()) { + return Err(Error::InvalidPresentation("Missing Base Type".into())); } - /// Validates the semantic structure of the `Presentation`. - pub fn check_structure(&self) -> Result<()> { - // Ensure the base context is present and in the correct location - match self.context.get(0) { - Some(context) if context == Self::base_context() => {} - Some(_) | None => return Err(Error::InvalidPresentation("Missing Base Context".into())), - } - - // The set of types MUST contain the base type - if !self.types.iter().any(|type_| type_ == Self::base_type()) { - return Err(Error::InvalidPresentation("Missing Base Type".into())); - } - - // Check all credentials. - for credential in self.verifiable_credential.iter() { - credential.check_structure()?; - } - - Ok(()) + // Check all credentials. + for credential in self.verifiable_credential.iter() { + credential.check_structure()?; } + + Ok(()) + } } impl Display for Presentation where - T: Serialize, - U: Serialize, + T: Serialize, + U: Serialize, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - if f.alternate() { - f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) - } else { - f.write_str(&self.to_json().map_err(|_| FmtError)?) - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + if f.alternate() { + f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) + } else { + f.write_str(&self.to_json().map_err(|_| FmtError)?) } + } } #[cfg(test)] mod tests { - use crate::{ - convert::FromJson as _, - credential::{CredentialSubject, VerifiableCredential, VerifiablePresentation}, - }; + use crate::{ + convert::FromJson as _, + credential::{CredentialSubject, VerifiableCredential, VerifiablePresentation}, + }; - const JSON: &str = include_str!("../../tests/fixtures/vc/presentation-1.json"); + const JSON: &str = include_str!("../../tests/fixtures/vc/presentation-1.json"); - #[test] + #[test] #[rustfmt::skip] fn test_from_json() { let presentation: VerifiablePresentation = VerifiablePresentation::from_json(JSON).unwrap(); diff --git a/identity-core/src/credential/presentation_builder.rs b/identity-core/src/credential/presentation_builder.rs index e5fa8e8abb..6c108866bd 100644 --- a/identity-core/src/credential/presentation_builder.rs +++ b/identity-core/src/credential/presentation_builder.rs @@ -2,167 +2,168 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - common::{Context, Object, Url, Value}, - credential::{Presentation, RefreshService, TermsOfUse, VerifiableCredential}, - error::Result, + common::{Context, Object, Url, Value}, + credential::{Presentation, RefreshService, TermsOfUse, VerifiableCredential}, + error::Result, }; /// A `PresentationBuilder` is used to create a customized `Presentation`. #[derive(Clone, Debug)] pub struct PresentationBuilder { - pub(crate) context: Vec, - pub(crate) id: Option, - pub(crate) types: Vec, - pub(crate) verifiable_credential: Vec>, - pub(crate) holder: Option, - pub(crate) refresh_service: Vec, - pub(crate) terms_of_use: Vec, - pub(crate) properties: T, + pub(crate) context: Vec, + pub(crate) id: Option, + pub(crate) types: Vec, + pub(crate) verifiable_credential: Vec>, + pub(crate) holder: Option, + pub(crate) refresh_service: Vec, + pub(crate) terms_of_use: Vec, + pub(crate) properties: T, } impl PresentationBuilder { - /// Creates a new `PresentationBuilder`. - pub fn new(properties: T) -> Self { - Self { - context: vec![Presentation::::base_context().clone()], - id: None, - types: vec![Presentation::::base_type().into()], - verifiable_credential: Vec::new(), - holder: None, - refresh_service: Vec::new(), - terms_of_use: Vec::new(), - properties, - } - } - - /// Adds a value to the `Presentation` context set. - #[must_use] - pub fn context(mut self, value: impl Into) -> Self { - self.context.push(value.into()); - self - } - - /// Sets the value of the `Presentation` `id`. - #[must_use] - pub fn id(mut self, value: Url) -> Self { - self.id = Some(value); - self - } - - /// Adds a value to the `Presentation` type set. - #[must_use] - pub fn type_(mut self, value: impl Into) -> Self { - self.types.push(value.into()); - self - } - - /// Adds a value to the `verifiableCredential` set. - #[must_use] - pub fn verifiable_credential(mut self, value: VerifiableCredential) -> Self { - self.verifiable_credential.push(value); - self - } - - /// Sets the value of the `Credential` `holder`. - #[must_use] - pub fn holder(mut self, value: Url) -> Self { - self.holder = Some(value); - self - } - - /// Adds a value to the `refreshService` set. - #[must_use] - pub fn refresh_service(mut self, value: RefreshService) -> Self { - self.refresh_service.push(value); - self - } - - /// Adds a value to the `termsOfUse` set. - #[must_use] - pub fn terms_of_use(mut self, value: TermsOfUse) -> Self { - self.terms_of_use.push(value); - self - } - - /// Returns a new `Presentation` based on the `PresentationBuilder` configuration. - pub fn build(self) -> Result> { - Presentation::from_builder(self) + /// Creates a new `PresentationBuilder`. + pub fn new(properties: T) -> Self { + Self { + context: vec![Presentation::::base_context().clone()], + id: None, + types: vec![Presentation::::base_type().into()], + verifiable_credential: Vec::new(), + holder: None, + refresh_service: Vec::new(), + terms_of_use: Vec::new(), + properties, } + } + + /// Adds a value to the `Presentation` context set. + #[must_use] + pub fn context(mut self, value: impl Into) -> Self { + self.context.push(value.into()); + self + } + + /// Sets the value of the `Presentation` `id`. + #[must_use] + pub fn id(mut self, value: Url) -> Self { + self.id = Some(value); + self + } + + /// Adds a value to the `Presentation` type set. + #[must_use] + pub fn type_(mut self, value: impl Into) -> Self { + self.types.push(value.into()); + self + } + + /// Adds a value to the `verifiableCredential` set. + #[must_use] + pub fn verifiable_credential(mut self, value: VerifiableCredential) -> Self { + self.verifiable_credential.push(value); + self + } + + /// Sets the value of the `Credential` `holder`. + #[must_use] + pub fn holder(mut self, value: Url) -> Self { + self.holder = Some(value); + self + } + + /// Adds a value to the `refreshService` set. + #[must_use] + pub fn refresh_service(mut self, value: RefreshService) -> Self { + self.refresh_service.push(value); + self + } + + /// Adds a value to the `termsOfUse` set. + #[must_use] + pub fn terms_of_use(mut self, value: TermsOfUse) -> Self { + self.terms_of_use.push(value); + self + } + + /// Returns a new `Presentation` based on the `PresentationBuilder` configuration. + pub fn build(self) -> Result> { + Presentation::from_builder(self) + } } impl PresentationBuilder { - /// Adds a new custom property to the `Presentation`. - #[must_use] - pub fn property(mut self, key: K, value: V) -> Self - where - K: Into, - V: Into, - { - self.properties.insert(key.into(), value.into()); - self - } - - /// Adds a series of custom properties to the `Presentation`. - #[must_use] - pub fn properties(mut self, iter: I) -> Self - where - I: IntoIterator, - K: Into, - V: Into, - { - self.properties - .extend(iter.into_iter().map(|(k, v)| (k.into(), v.into()))); - self - } + /// Adds a new custom property to the `Presentation`. + #[must_use] + pub fn property(mut self, key: K, value: V) -> Self + where + K: Into, + V: Into, + { + self.properties.insert(key.into(), value.into()); + self + } + + /// Adds a series of custom properties to the `Presentation`. + #[must_use] + pub fn properties(mut self, iter: I) -> Self + where + I: IntoIterator, + K: Into, + V: Into, + { + self + .properties + .extend(iter.into_iter().map(|(k, v)| (k.into(), v.into()))); + self + } } impl Default for PresentationBuilder where - T: Default, + T: Default, { - fn default() -> Self { - Self::new(T::default()) - } + fn default() -> Self { + Self::new(T::default()) + } } #[cfg(test)] mod tests { - use did_doc::{Document, DocumentBuilder, Method, MethodBuilder, MethodData, MethodType}; - use did_url::DID; - use serde_json::{json, Value}; - - use crate::{ - common::{Object, Url}, - convert::FromJson as _, - credential::{ - Credential as Credential_, CredentialBuilder, CredentialSubject, Presentation as Presentation_, - PresentationBuilder, VerifiableCredential, - }, - crypto::KeyPair, - proof::JcsEd25519Signature2020, - utils::encode_b58, - }; - - type Credential = Credential_; - type Presentation = Presentation_; - - fn subject() -> CredentialSubject { - let json: Value = json!({ - "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts" - } - }); - - CredentialSubject::from_json_value(json).unwrap() - } + use did_doc::{Document, DocumentBuilder, Method, MethodBuilder, MethodData, MethodType}; + use did_url::DID; + use serde_json::{json, Value}; + + use crate::{ + common::{Object, Url}, + convert::FromJson as _, + credential::{ + Credential as Credential_, CredentialBuilder, CredentialSubject, Presentation as Presentation_, + PresentationBuilder, VerifiableCredential, + }, + crypto::KeyPair, + proof::JcsEd25519Signature2020, + utils::encode_b58, + }; + + type Credential = Credential_; + type Presentation = Presentation_; + + fn subject() -> CredentialSubject { + let json: Value = json!({ + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts" + } + }); - fn issuer() -> Url { - Url::parse("did:example:issuer").unwrap() - } + CredentialSubject::from_json_value(json).unwrap() + } + + fn issuer() -> Url { + Url::parse("did:example:issuer").unwrap() + } - #[test] + #[test] #[rustfmt::skip] fn test_presentation_builder_valid() { let keypair: KeyPair = JcsEd25519Signature2020::new_keypair(); diff --git a/identity-core/src/credential/types/credential_schema.rs b/identity-core/src/credential/types/credential_schema.rs index f5613676d9..c99c0e4bc7 100644 --- a/identity-core/src/credential/types/credential_schema.rs +++ b/identity-core/src/credential/types/credential_schema.rs @@ -8,47 +8,47 @@ use crate::common::{Object, OneOrMany, Url}; /// [More Info](https://www.w3.org/TR/vc-data-model/#data-schemas) #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct CredentialSchema { - /// A Url identifying the credential schema file. - pub id: Url, - /// The type(s) of the credential schema. - #[serde(rename = "type")] - pub types: OneOrMany, - /// Additional properties of the credential schema. - #[serde(flatten)] - pub properties: Object, + /// A Url identifying the credential schema file. + pub id: Url, + /// The type(s) of the credential schema. + #[serde(rename = "type")] + pub types: OneOrMany, + /// Additional properties of the credential schema. + #[serde(flatten)] + pub properties: Object, } impl CredentialSchema { - /// Creates a new [`CredentialSchema`]. - pub fn new(id: Url, types: T) -> Self - where - T: Into>, - { - Self::with_properties(id, types, Object::new()) - } + /// Creates a new [`CredentialSchema`]. + pub fn new(id: Url, types: T) -> Self + where + T: Into>, + { + Self::with_properties(id, types, Object::new()) + } - /// Creates a new [`CredentialSchema`] with the given `properties`. - pub fn with_properties(id: Url, types: T, properties: Object) -> Self - where - T: Into>, - { - Self { - id, - types: types.into(), - properties, - } + /// Creates a new [`CredentialSchema`] with the given `properties`. + pub fn with_properties(id: Url, types: T, properties: Object) -> Self + where + T: Into>, + { + Self { + id, + types: types.into(), + properties, } + } } #[cfg(test)] mod tests { - use crate::{convert::FromJson as _, credential::CredentialSchema}; + use crate::{convert::FromJson as _, credential::CredentialSchema}; - const JSON1: &str = include_str!("../../../tests/fixtures/vc/credential-schema-1.json"); - const JSON2: &str = include_str!("../../../tests/fixtures/vc/credential-schema-2.json"); - const JSON3: &str = include_str!("../../../tests/fixtures/vc/credential-schema-3.json"); + const JSON1: &str = include_str!("../../../tests/fixtures/vc/credential-schema-1.json"); + const JSON2: &str = include_str!("../../../tests/fixtures/vc/credential-schema-2.json"); + const JSON3: &str = include_str!("../../../tests/fixtures/vc/credential-schema-3.json"); - #[test] + #[test] #[rustfmt::skip] fn test_from_json() { let schema: CredentialSchema = CredentialSchema::from_json(JSON1).unwrap(); diff --git a/identity-core/src/credential/types/credential_status.rs b/identity-core/src/credential/types/credential_status.rs index a40a8e5604..0fc67344e8 100644 --- a/identity-core/src/credential/types/credential_status.rs +++ b/identity-core/src/credential/types/credential_status.rs @@ -8,45 +8,45 @@ use crate::common::{Object, OneOrMany, Url}; /// [More Info](https://www.w3.org/TR/vc-data-model/#status) #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct CredentialStatus { - /// A Url identifying the credential status. - pub id: Url, - /// The type(s) of the credential status. - #[serde(rename = "type")] - pub types: OneOrMany, - /// Additional properties of the credential status. - #[serde(flatten)] - pub properties: Object, + /// A Url identifying the credential status. + pub id: Url, + /// The type(s) of the credential status. + #[serde(rename = "type")] + pub types: OneOrMany, + /// Additional properties of the credential status. + #[serde(flatten)] + pub properties: Object, } impl CredentialStatus { - /// Creates a new [`CredentialStatus`]. - pub fn new(id: Url, types: T) -> Self - where - T: Into>, - { - Self::with_properties(id, types, Object::new()) - } + /// Creates a new [`CredentialStatus`]. + pub fn new(id: Url, types: T) -> Self + where + T: Into>, + { + Self::with_properties(id, types, Object::new()) + } - /// Creates a new [`CredentialStatus`] with the given `properties`. - pub fn with_properties(id: Url, types: T, properties: Object) -> Self - where - T: Into>, - { - Self { - id, - types: types.into(), - properties, - } + /// Creates a new [`CredentialStatus`] with the given `properties`. + pub fn with_properties(id: Url, types: T, properties: Object) -> Self + where + T: Into>, + { + Self { + id, + types: types.into(), + properties, } + } } #[cfg(test)] mod tests { - use crate::{convert::FromJson as _, credential::CredentialStatus}; + use crate::{convert::FromJson as _, credential::CredentialStatus}; - const JSON: &str = include_str!("../../../tests/fixtures/vc/credential-status-1.json"); + const JSON: &str = include_str!("../../../tests/fixtures/vc/credential-status-1.json"); - #[test] + #[test] #[rustfmt::skip] fn test_from_json() { let status: CredentialStatus = CredentialStatus::from_json(JSON).unwrap(); diff --git a/identity-core/src/credential/types/credential_subject.rs b/identity-core/src/credential/types/credential_subject.rs index 892fb8a804..d79f37668b 100644 --- a/identity-core/src/credential/types/credential_subject.rs +++ b/identity-core/src/credential/types/credential_subject.rs @@ -8,55 +8,55 @@ use crate::common::{Object, Url}; /// [More Info](https://www.w3.org/TR/vc-data-model/#credential-subject) #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct CredentialSubject { - /// A Url identifying the credential subject. - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - /// Additional properties of the credential subject. - #[serde(flatten)] - pub properties: Object, + /// A Url identifying the credential subject. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + /// Additional properties of the credential subject. + #[serde(flatten)] + pub properties: Object, } impl CredentialSubject { - /// Creates a new [`CredentialSubject`]. - pub fn new() -> Self { - Self::with_properties(Object::new()) - } - - /// Creates a new [`CredentialSubject`] with the given `id`. - pub fn with_id(id: Url) -> Self { - Self::with_id_and_properties(id, Object::new()) - } - - /// Creates a new [`CredentialSubject`] with the given `properties`. - pub fn with_properties(properties: Object) -> Self { - Self { id: None, properties } - } - - /// Creates a new [`CredentialSubject`] with the given `id` and `properties`. - pub fn with_id_and_properties(id: Url, properties: Object) -> Self { - Self { - id: Some(id), - properties, - } + /// Creates a new [`CredentialSubject`]. + pub fn new() -> Self { + Self::with_properties(Object::new()) + } + + /// Creates a new [`CredentialSubject`] with the given `id`. + pub fn with_id(id: Url) -> Self { + Self::with_id_and_properties(id, Object::new()) + } + + /// Creates a new [`CredentialSubject`] with the given `properties`. + pub fn with_properties(properties: Object) -> Self { + Self { id: None, properties } + } + + /// Creates a new [`CredentialSubject`] with the given `id` and `properties`. + pub fn with_id_and_properties(id: Url, properties: Object) -> Self { + Self { + id: Some(id), + properties, } + } } #[cfg(test)] mod tests { - use crate::{convert::FromJson as _, credential::CredentialSubject}; - - const JSON1: &str = include_str!("../../../tests/fixtures/vc/credential-subject-1.json"); - const JSON2: &str = include_str!("../../../tests/fixtures/vc/credential-subject-2.json"); - const JSON3: &str = include_str!("../../../tests/fixtures/vc/credential-subject-3.json"); - const JSON4: &str = include_str!("../../../tests/fixtures/vc/credential-subject-4.json"); - const JSON5: &str = include_str!("../../../tests/fixtures/vc/credential-subject-5.json"); - const JSON6: &str = include_str!("../../../tests/fixtures/vc/credential-subject-6.json"); - const JSON7: &str = include_str!("../../../tests/fixtures/vc/credential-subject-7.json"); - const JSON8: &str = include_str!("../../../tests/fixtures/vc/credential-subject-8.json"); - const JSON9: &str = include_str!("../../../tests/fixtures/vc/credential-subject-9.json"); - const JSON10: &str = include_str!("../../../tests/fixtures/vc/credential-subject-10.json"); - - #[test] + use crate::{convert::FromJson as _, credential::CredentialSubject}; + + const JSON1: &str = include_str!("../../../tests/fixtures/vc/credential-subject-1.json"); + const JSON2: &str = include_str!("../../../tests/fixtures/vc/credential-subject-2.json"); + const JSON3: &str = include_str!("../../../tests/fixtures/vc/credential-subject-3.json"); + const JSON4: &str = include_str!("../../../tests/fixtures/vc/credential-subject-4.json"); + const JSON5: &str = include_str!("../../../tests/fixtures/vc/credential-subject-5.json"); + const JSON6: &str = include_str!("../../../tests/fixtures/vc/credential-subject-6.json"); + const JSON7: &str = include_str!("../../../tests/fixtures/vc/credential-subject-7.json"); + const JSON8: &str = include_str!("../../../tests/fixtures/vc/credential-subject-8.json"); + const JSON9: &str = include_str!("../../../tests/fixtures/vc/credential-subject-9.json"); + const JSON10: &str = include_str!("../../../tests/fixtures/vc/credential-subject-10.json"); + + #[test] #[rustfmt::skip] fn test_from_json() { let subject: CredentialSubject = CredentialSubject::from_json(JSON1).unwrap(); diff --git a/identity-core/src/credential/types/evidence.rs b/identity-core/src/credential/types/evidence.rs index 43aecc8009..1b71bfcc09 100644 --- a/identity-core/src/credential/types/evidence.rs +++ b/identity-core/src/credential/types/evidence.rs @@ -8,73 +8,73 @@ use crate::common::{Object, OneOrMany}; /// [More Info](https://www.w3.org/TR/vc-data-model/#evidence) #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct Evidence { - /// A Url that allows retrieval of information about the evidence. - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - /// The type(s) of the credential evidence. - #[serde(rename = "type")] - pub types: OneOrMany, - /// Additional properties of the credential evidence. - #[serde(flatten)] - pub properties: Object, + /// A Url that allows retrieval of information about the evidence. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + /// The type(s) of the credential evidence. + #[serde(rename = "type")] + pub types: OneOrMany, + /// Additional properties of the credential evidence. + #[serde(flatten)] + pub properties: Object, } impl Evidence { - /// Creates a new [`Evidence`] instance. - pub fn new(types: T) -> Self - where - T: Into>, - { - Self::with_properties(types, Object::new()) - } + /// Creates a new [`Evidence`] instance. + pub fn new(types: T) -> Self + where + T: Into>, + { + Self::with_properties(types, Object::new()) + } - /// Creates a new [`Evidence`] instance with the given `id`. - pub fn with_id(types: T, id: U) -> Self - where - T: Into>, - U: Into, - { - Self { - id: Some(id.into()), - types: types.into(), - properties: Object::new(), - } + /// Creates a new [`Evidence`] instance with the given `id`. + pub fn with_id(types: T, id: U) -> Self + where + T: Into>, + U: Into, + { + Self { + id: Some(id.into()), + types: types.into(), + properties: Object::new(), } + } - /// Creates a new [`Evidence`] instance with the given `properties`. - pub fn with_properties(types: T, properties: Object) -> Self - where - T: Into>, - { - Self { - id: None, - types: types.into(), - properties, - } + /// Creates a new [`Evidence`] instance with the given `properties`. + pub fn with_properties(types: T, properties: Object) -> Self + where + T: Into>, + { + Self { + id: None, + types: types.into(), + properties, } + } - /// Creates a new [`Evidence`] instance with the given `id` and `properties`. - pub fn with_id_and_properties(types: T, id: U, properties: Object) -> Self - where - T: Into>, - U: Into, - { - Self { - id: Some(id.into()), - types: types.into(), - properties, - } + /// Creates a new [`Evidence`] instance with the given `id` and `properties`. + pub fn with_id_and_properties(types: T, id: U, properties: Object) -> Self + where + T: Into>, + U: Into, + { + Self { + id: Some(id.into()), + types: types.into(), + properties, } + } } #[cfg(test)] mod tests { - use crate::{convert::FromJson as _, credential::Evidence}; + use crate::{convert::FromJson as _, credential::Evidence}; - const JSON1: &str = include_str!("../../../tests/fixtures/vc/evidence-1.json"); - const JSON2: &str = include_str!("../../../tests/fixtures/vc/evidence-2.json"); + const JSON1: &str = include_str!("../../../tests/fixtures/vc/evidence-1.json"); + const JSON2: &str = include_str!("../../../tests/fixtures/vc/evidence-2.json"); - #[test] + #[test] #[rustfmt::skip] fn test_from_json() { let evidence: Evidence = Evidence::from_json(JSON1).unwrap(); diff --git a/identity-core/src/credential/types/issuer.rs b/identity-core/src/credential/types/issuer.rs index 9754116c01..a10f134040 100644 --- a/identity-core/src/credential/types/issuer.rs +++ b/identity-core/src/credential/types/issuer.rs @@ -8,11 +8,11 @@ use crate::common::{Object, Url}; /// [More Info](https://www.w3.org/TR/vc-data-model/#issuer) #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct IssuerData { - /// A Url identifying the credential issuer. - pub id: Url, - /// Additional properties of the credential issuer. - #[serde(flatten)] - pub properties: Object, + /// A Url identifying the credential issuer. + pub id: Url, + /// Additional properties of the credential issuer. + #[serde(flatten)] + pub properties: Object, } /// An identifier representing the issuer of a `Credential`. @@ -21,39 +21,39 @@ pub struct IssuerData { #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] #[serde(untagged)] pub enum Issuer { - /// A credential issuer expressed as a Url. - Url(Url), - /// A credential issuer expressed as a JSON object. - Obj(IssuerData), + /// A credential issuer expressed as a Url. + Url(Url), + /// A credential issuer expressed as a JSON object. + Obj(IssuerData), } impl Issuer { - /// Returns a reference to the credential issuer Url. - pub fn url(&self) -> &Url { - match self { - Self::Url(url) => url, - Self::Obj(obj) => &obj.id, - } + /// Returns a reference to the credential issuer Url. + pub fn url(&self) -> &Url { + match self { + Self::Url(url) => url, + Self::Obj(obj) => &obj.id, } + } } impl From for Issuer where - T: Into, + T: Into, { - fn from(other: T) -> Self { - Self::Url(other.into()) - } + fn from(other: T) -> Self { + Self::Url(other.into()) + } } #[cfg(test)] mod tests { - use crate::{convert::FromJson as _, credential::Issuer}; + use crate::{convert::FromJson as _, credential::Issuer}; - const JSON1: &str = include_str!("../../../tests/fixtures/vc/issuer-1.json"); - const JSON2: &str = include_str!("../../../tests/fixtures/vc/issuer-2.json"); + const JSON1: &str = include_str!("../../../tests/fixtures/vc/issuer-1.json"); + const JSON2: &str = include_str!("../../../tests/fixtures/vc/issuer-2.json"); - #[test] + #[test] #[rustfmt::skip] fn test_from_json() { let issuer: Issuer = Issuer::from_json(JSON1).unwrap(); diff --git a/identity-core/src/credential/types/refresh_service.rs b/identity-core/src/credential/types/refresh_service.rs index df88470981..267f31dbd8 100644 --- a/identity-core/src/credential/types/refresh_service.rs +++ b/identity-core/src/credential/types/refresh_service.rs @@ -8,45 +8,45 @@ use crate::common::{Object, OneOrMany, Url}; /// [More Info](https://www.w3.org/TR/vc-data-model/#refreshing) #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct RefreshService { - /// The Url of the credential refresh service. - pub id: Url, - /// The type(s) of the credential refresh service. - #[serde(rename = "type")] - pub types: OneOrMany, - /// Additional properties of the credential refresh service. - #[serde(flatten)] - pub properties: Object, + /// The Url of the credential refresh service. + pub id: Url, + /// The type(s) of the credential refresh service. + #[serde(rename = "type")] + pub types: OneOrMany, + /// Additional properties of the credential refresh service. + #[serde(flatten)] + pub properties: Object, } impl RefreshService { - /// Creates a new [`RefreshService`]. - pub fn new(id: Url, types: T) -> Self - where - T: Into>, - { - Self::with_properties(id, types, Object::new()) - } + /// Creates a new [`RefreshService`]. + pub fn new(id: Url, types: T) -> Self + where + T: Into>, + { + Self::with_properties(id, types, Object::new()) + } - /// Creates a new [`RefreshService`] with the given `properties`. - pub fn with_properties(id: Url, types: T, properties: Object) -> Self - where - T: Into>, - { - Self { - id, - types: types.into(), - properties, - } + /// Creates a new [`RefreshService`] with the given `properties`. + pub fn with_properties(id: Url, types: T, properties: Object) -> Self + where + T: Into>, + { + Self { + id, + types: types.into(), + properties, } + } } #[cfg(test)] mod tests { - use crate::{convert::FromJson as _, credential::RefreshService}; + use crate::{convert::FromJson as _, credential::RefreshService}; - const JSON: &str = include_str!("../../../tests/fixtures/vc/refresh-service-1.json"); + const JSON: &str = include_str!("../../../tests/fixtures/vc/refresh-service-1.json"); - #[test] + #[test] #[rustfmt::skip] fn test_from_json() { let service: RefreshService = RefreshService::from_json(JSON).unwrap(); diff --git a/identity-core/src/credential/types/terms_of_use.rs b/identity-core/src/credential/types/terms_of_use.rs index 278eae35af..bd2bfebc5f 100644 --- a/identity-core/src/credential/types/terms_of_use.rs +++ b/identity-core/src/credential/types/terms_of_use.rs @@ -9,75 +9,75 @@ use crate::common::{Object, OneOrMany, Url}; /// [More Info](https://www.w3.org/TR/vc-data-model/#terms-of-use) #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct TermsOfUse { - /// The instance id of the credential terms-of-use. - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - /// The type(s) of the credential terms-of-use. - #[serde(rename = "type")] - pub types: OneOrMany, - /// Additional properties of the credential terms-of-use. - #[serde(flatten)] - pub properties: Object, + /// The instance id of the credential terms-of-use. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + /// The type(s) of the credential terms-of-use. + #[serde(rename = "type")] + pub types: OneOrMany, + /// Additional properties of the credential terms-of-use. + #[serde(flatten)] + pub properties: Object, } impl TermsOfUse { - /// Creates a new [`TermsOfUse`] instance. - pub fn new(types: T) -> Self - where - T: Into>, - { - Self { - id: None, - types: types.into(), - properties: Object::new(), - } + /// Creates a new [`TermsOfUse`] instance. + pub fn new(types: T) -> Self + where + T: Into>, + { + Self { + id: None, + types: types.into(), + properties: Object::new(), } + } - /// Creates a new [`TermsOfUse`] instance with the given `id`. - pub fn with_id(types: T, id: Url) -> Self - where - T: Into>, - { - Self { - id: Some(id), - types: types.into(), - properties: Object::new(), - } + /// Creates a new [`TermsOfUse`] instance with the given `id`. + pub fn with_id(types: T, id: Url) -> Self + where + T: Into>, + { + Self { + id: Some(id), + types: types.into(), + properties: Object::new(), } + } - /// Creates a new [`TermsOfUse`] instance with the given `properties`. - pub fn with_properties(types: T, properties: Object) -> Self - where - T: Into>, - { - Self { - id: None, - types: types.into(), - properties, - } + /// Creates a new [`TermsOfUse`] instance with the given `properties`. + pub fn with_properties(types: T, properties: Object) -> Self + where + T: Into>, + { + Self { + id: None, + types: types.into(), + properties, } + } - /// Creates a new [`TermsOfUse`] instance with the given `id` and `properties`. - pub fn with_id_and_properties(types: T, id: Url, properties: Object) -> Self - where - T: Into>, - { - Self { - id: Some(id), - types: types.into(), - properties, - } + /// Creates a new [`TermsOfUse`] instance with the given `id` and `properties`. + pub fn with_id_and_properties(types: T, id: Url, properties: Object) -> Self + where + T: Into>, + { + Self { + id: Some(id), + types: types.into(), + properties, } + } } #[cfg(test)] mod tests { - use crate::{convert::FromJson as _, credential::TermsOfUse}; + use crate::{convert::FromJson as _, credential::TermsOfUse}; - const JSON1: &str = include_str!("../../../tests/fixtures/vc/terms-of-use-1.json"); - const JSON2: &str = include_str!("../../../tests/fixtures/vc/terms-of-use-2.json"); + const JSON1: &str = include_str!("../../../tests/fixtures/vc/terms-of-use-1.json"); + const JSON2: &str = include_str!("../../../tests/fixtures/vc/terms-of-use-2.json"); - #[test] + #[test] #[rustfmt::skip] fn test_from_json() { let policy: TermsOfUse = TermsOfUse::from_json(JSON1).unwrap(); diff --git a/identity-core/src/credential/verifiable_credential.rs b/identity-core/src/credential/verifiable_credential.rs index dbdb9889a3..044e2724c9 100644 --- a/identity-core/src/credential/verifiable_credential.rs +++ b/identity-core/src/credential/verifiable_credential.rs @@ -2,92 +2,92 @@ // SPDX-License-Identifier: Apache-2.0 use core::{ - fmt::{Display, Error as FmtError, Formatter, Result as FmtResult}, - ops::{Deref, DerefMut}, + fmt::{Display, Error as FmtError, Formatter, Result as FmtResult}, + ops::{Deref, DerefMut}, }; use serde::{Deserialize, Serialize}; use crate::{ - common::{Object, OneOrMany}, - convert::ToJson as _, - credential::Credential, - did_doc::{SetSignature, Signature, TrySignature, TrySignatureMut}, + common::{Object, OneOrMany}, + convert::ToJson as _, + credential::Credential, + did_doc::{SetSignature, Signature, TrySignature, TrySignatureMut}, }; /// A `VerifiableCredential` represents a `Credential` with an associated /// digital proof. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct VerifiableCredential { - #[serde(flatten)] - credential: Credential, - #[serde(skip_serializing_if = "OneOrMany::is_empty")] - proof: OneOrMany, + #[serde(flatten)] + credential: Credential, + #[serde(skip_serializing_if = "OneOrMany::is_empty")] + proof: OneOrMany, } impl VerifiableCredential { - /// Creates a new `VerifiableCredential`. - pub fn new

(credential: Credential, proof: P) -> Self - where - P: Into>, - { - Self { - credential, - proof: proof.into(), - } + /// Creates a new `VerifiableCredential`. + pub fn new

(credential: Credential, proof: P) -> Self + where + P: Into>, + { + Self { + credential, + proof: proof.into(), } + } - /// Returns a reference to the `VerifiableCredential` proof. - pub fn proof(&self) -> &OneOrMany { - &self.proof - } + /// Returns a reference to the `VerifiableCredential` proof. + pub fn proof(&self) -> &OneOrMany { + &self.proof + } - /// Returns a mutable reference to the `VerifiableCredential` proof. - pub fn proof_mut(&mut self) -> &mut OneOrMany { - &mut self.proof - } + /// Returns a mutable reference to the `VerifiableCredential` proof. + pub fn proof_mut(&mut self) -> &mut OneOrMany { + &mut self.proof + } } impl Deref for VerifiableCredential { - type Target = Credential; + type Target = Credential; - fn deref(&self) -> &Self::Target { - &self.credential - } + fn deref(&self) -> &Self::Target { + &self.credential + } } impl Display for VerifiableCredential where - T: Serialize, + T: Serialize, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - if f.alternate() { - f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) - } else { - f.write_str(&self.to_json().map_err(|_| FmtError)?) - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + if f.alternate() { + f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) + } else { + f.write_str(&self.to_json().map_err(|_| FmtError)?) } + } } impl DerefMut for VerifiableCredential { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.credential - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.credential + } } impl TrySignature for VerifiableCredential { - fn signature(&self) -> Option<&Signature> { - self.proof.get(0) - } + fn signature(&self) -> Option<&Signature> { + self.proof.get(0) + } } impl TrySignatureMut for VerifiableCredential { - fn signature_mut(&mut self) -> Option<&mut Signature> { - self.proof.get_mut(0) - } + fn signature_mut(&mut self) -> Option<&mut Signature> { + self.proof.get_mut(0) + } } impl SetSignature for VerifiableCredential { - fn set_signature(&mut self, value: Signature) { - self.proof = OneOrMany::One(value); - } + fn set_signature(&mut self, value: Signature) { + self.proof = OneOrMany::One(value); + } } diff --git a/identity-core/src/credential/verifiable_presentation.rs b/identity-core/src/credential/verifiable_presentation.rs index 774591bf0c..4224036552 100644 --- a/identity-core/src/credential/verifiable_presentation.rs +++ b/identity-core/src/credential/verifiable_presentation.rs @@ -2,93 +2,93 @@ // SPDX-License-Identifier: Apache-2.0 use core::{ - fmt::{Display, Error as FmtError, Formatter, Result as FmtResult}, - ops::{Deref, DerefMut}, + fmt::{Display, Error as FmtError, Formatter, Result as FmtResult}, + ops::{Deref, DerefMut}, }; use serde::{Deserialize, Serialize}; use crate::{ - common::{Object, OneOrMany}, - convert::ToJson as _, - credential::Presentation, - did_doc::{SetSignature, Signature, TrySignature, TrySignatureMut}, + common::{Object, OneOrMany}, + convert::ToJson as _, + credential::Presentation, + did_doc::{SetSignature, Signature, TrySignature, TrySignatureMut}, }; /// A `VerifiablePresentation` represents a `Presentation` with an associated /// digital proof. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct VerifiablePresentation { - #[serde(flatten)] - presentation: Presentation, - #[serde(skip_serializing_if = "OneOrMany::is_empty")] - proof: OneOrMany, + #[serde(flatten)] + presentation: Presentation, + #[serde(skip_serializing_if = "OneOrMany::is_empty")] + proof: OneOrMany, } impl VerifiablePresentation { - /// Creates a new `VerifiablePresentation`. - pub fn new

(presentation: Presentation, proof: P) -> Self - where - P: Into>, - { - Self { - presentation, - proof: proof.into(), - } + /// Creates a new `VerifiablePresentation`. + pub fn new

(presentation: Presentation, proof: P) -> Self + where + P: Into>, + { + Self { + presentation, + proof: proof.into(), } + } - /// Returns a reference to the `VerifiablePresentation` proof. - pub fn proof(&self) -> &OneOrMany { - &self.proof - } + /// Returns a reference to the `VerifiablePresentation` proof. + pub fn proof(&self) -> &OneOrMany { + &self.proof + } - /// Returns a mutable reference to the `VerifiablePresentation` proof. - pub fn proof_mut(&mut self) -> &mut OneOrMany { - &mut self.proof - } + /// Returns a mutable reference to the `VerifiablePresentation` proof. + pub fn proof_mut(&mut self) -> &mut OneOrMany { + &mut self.proof + } } impl Deref for VerifiablePresentation { - type Target = Presentation; + type Target = Presentation; - fn deref(&self) -> &Self::Target { - &self.presentation - } + fn deref(&self) -> &Self::Target { + &self.presentation + } } impl DerefMut for VerifiablePresentation { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.presentation - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.presentation + } } impl Display for VerifiablePresentation where - T: Serialize, - U: Serialize, + T: Serialize, + U: Serialize, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - if f.alternate() { - f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) - } else { - f.write_str(&self.to_json().map_err(|_| FmtError)?) - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + if f.alternate() { + f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) + } else { + f.write_str(&self.to_json().map_err(|_| FmtError)?) } + } } impl TrySignature for VerifiablePresentation { - fn signature(&self) -> Option<&Signature> { - self.proof.get(0) - } + fn signature(&self) -> Option<&Signature> { + self.proof.get(0) + } } impl TrySignatureMut for VerifiablePresentation { - fn signature_mut(&mut self) -> Option<&mut Signature> { - self.proof.get_mut(0) - } + fn signature_mut(&mut self) -> Option<&mut Signature> { + self.proof.get_mut(0) + } } impl SetSignature for VerifiablePresentation { - fn set_signature(&mut self, value: Signature) { - self.proof = OneOrMany::One(value); - } + fn set_signature(&mut self, value: Signature) { + self.proof = OneOrMany::One(value); + } } diff --git a/identity-core/src/crypto/key_impl.rs b/identity-core/src/crypto/key_impl.rs index 69c426cfe3..f31f92b74d 100644 --- a/identity-core/src/crypto/key_impl.rs +++ b/identity-core/src/crypto/key_impl.rs @@ -2,48 +2,48 @@ // SPDX-License-Identifier: Apache-2.0 macro_rules! impl_bytes { - ($ident:ident) => { - /// A cryptographic key. - #[derive(Clone)] - pub struct $ident(Vec); - - impl From> for $ident { - fn from(other: Vec) -> $ident { - Self(other) - } - } - - impl AsRef<[u8]> for $ident { - fn as_ref(&self) -> &[u8] { - self.0.as_slice() - } - } - - impl Drop for $ident { - fn drop(&mut self) { - use ::zeroize::Zeroize; - self.0.zeroize(); - } - } - - impl ::zeroize::Zeroize for $ident { - fn zeroize(&mut self) { - self.0.zeroize(); - } - } - - impl ::core::fmt::Debug for $ident { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - f.write_str(stringify!($ident)) - } - } - - impl ::core::fmt::Display for $ident { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - f.write_str(stringify!($ident)) - } - } - }; + ($ident:ident) => { + /// A cryptographic key. + #[derive(Clone)] + pub struct $ident(Vec); + + impl From> for $ident { + fn from(other: Vec) -> $ident { + Self(other) + } + } + + impl AsRef<[u8]> for $ident { + fn as_ref(&self) -> &[u8] { + self.0.as_slice() + } + } + + impl Drop for $ident { + fn drop(&mut self) { + use ::zeroize::Zeroize; + self.0.zeroize(); + } + } + + impl ::zeroize::Zeroize for $ident { + fn zeroize(&mut self) { + self.0.zeroize(); + } + } + + impl ::core::fmt::Debug for $ident { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + f.write_str(stringify!($ident)) + } + } + + impl ::core::fmt::Display for $ident { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + f.write_str(stringify!($ident)) + } + } + }; } impl_bytes!(PublicKey); diff --git a/identity-core/src/crypto/key_pair.rs b/identity-core/src/crypto/key_pair.rs index b5ad3a8df7..bf16caad4d 100644 --- a/identity-core/src/crypto/key_pair.rs +++ b/identity-core/src/crypto/key_pair.rs @@ -10,32 +10,32 @@ use crate::crypto::{PublicKey, SecretKey}; pub struct KeyPair(PublicKey, SecretKey); impl KeyPair { - /// Creates a new [`KeyPair`] from the given keys. - pub const fn new(public: PublicKey, secret: SecretKey) -> Self { - Self(public, secret) - } - - /// Returns a reference to the [`PublicKey`] object. - pub const fn public(&self) -> &PublicKey { - &self.0 - } - - /// Returns a reference to the [`SecretKey`] object. - pub const fn secret(&self) -> &SecretKey { - &self.1 - } + /// Creates a new [`KeyPair`] from the given keys. + pub const fn new(public: PublicKey, secret: SecretKey) -> Self { + Self(public, secret) + } + + /// Returns a reference to the [`PublicKey`] object. + pub const fn public(&self) -> &PublicKey { + &self.0 + } + + /// Returns a reference to the [`SecretKey`] object. + pub const fn secret(&self) -> &SecretKey { + &self.1 + } } impl Drop for KeyPair { - fn drop(&mut self) { - self.0.zeroize(); - self.1.zeroize(); - } + fn drop(&mut self) { + self.0.zeroize(); + self.1.zeroize(); + } } impl Zeroize for KeyPair { - fn zeroize(&mut self) { - self.0.zeroize(); - self.1.zeroize(); - } + fn zeroize(&mut self) { + self.0.zeroize(); + self.1.zeroize(); + } } diff --git a/identity-core/src/crypto/merkle_tree/digest.rs b/identity-core/src/crypto/merkle_tree/digest.rs index 9c2d2675fb..f2ca4f5861 100644 --- a/identity-core/src/crypto/merkle_tree/digest.rs +++ b/identity-core/src/crypto/merkle_tree/digest.rs @@ -9,22 +9,22 @@ use crate::crypto::merkle_tree::Hash; /// An extension of the [`Digest`] trait for Merkle tree construction. pub trait DigestExt: Sized + Digest { - /// Computes the [`struct@Hash`] of a Merkle tree leaf node. - fn hash_leaf(&mut self, data: &[u8]) -> Hash { - self.reset(); - self.update(consts::PREFIX_L); - self.update(data); - self.finalize_reset().into() - } + /// Computes the [`struct@Hash`] of a Merkle tree leaf node. + fn hash_leaf(&mut self, data: &[u8]) -> Hash { + self.reset(); + self.update(consts::PREFIX_L); + self.update(data); + self.finalize_reset().into() + } - /// Computes the parent [`struct@Hash`] of two Merkle tree nodes. - fn hash_branch(&mut self, lhs: &Hash, rhs: &Hash) -> Hash { - self.reset(); - self.update(consts::PREFIX_B); - self.update(lhs.as_ref()); - self.update(rhs.as_ref()); - self.finalize_reset().into() - } + /// Computes the parent [`struct@Hash`] of two Merkle tree nodes. + fn hash_branch(&mut self, lhs: &Hash, rhs: &Hash) -> Hash { + self.reset(); + self.update(consts::PREFIX_B); + self.update(lhs.as_ref()); + self.update(rhs.as_ref()); + self.finalize_reset().into() + } } impl DigestExt for D where D: Digest {} diff --git a/identity-core/src/crypto/merkle_tree/hash.rs b/identity-core/src/crypto/merkle_tree/hash.rs index 45be798b4f..a3b24d02e7 100644 --- a/identity-core/src/crypto/merkle_tree/hash.rs +++ b/identity-core/src/crypto/merkle_tree/hash.rs @@ -16,89 +16,89 @@ use crate::utils::encode_b58; /// The output of a hash function. pub struct Hash(Output) where - D: Digest; + D: Digest; impl Hash { - /// Creates a new [`struct@Hash`] from a slice. - pub fn from_slice(slice: &[u8]) -> Option { - if slice.len() != D::OutputSize::USIZE { - return None; - } + /// Creates a new [`struct@Hash`] from a slice. + pub fn from_slice(slice: &[u8]) -> Option { + if slice.len() != D::OutputSize::USIZE { + return None; + } - let mut this: Self = Self::default(); + let mut this: Self = Self::default(); - this.0.copy_from_slice(slice); + this.0.copy_from_slice(slice); - Some(this) - } + Some(this) + } } impl Clone for Hash where - Output: Clone, + Output: Clone, { - fn clone(&self) -> Self { - Self(self.0.clone()) - } + fn clone(&self) -> Self { + Self(self.0.clone()) + } } impl Copy for Hash where Output: Copy {} impl PartialEq for Hash where - Output: PartialEq, + Output: PartialEq, { - fn eq(&self, other: &Self) -> bool { - self.0.eq(&other.0) - } + fn eq(&self, other: &Self) -> bool { + self.0.eq(&other.0) + } } impl Eq for Hash where Output: Eq {} impl PartialOrd for Hash where - Output: PartialOrd, + Output: PartialOrd, { - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&other.0) - } + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } } impl Ord for Hash where - Output: Ord, + Output: Ord, { - fn cmp(&self, other: &Self) -> Ordering { - self.0.cmp(&other.0) - } + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } } impl Debug for Hash { - fn fmt(&self, f: &mut Formatter) -> Result { - f.write_str(&encode_b58(self)) - } + fn fmt(&self, f: &mut Formatter) -> Result { + f.write_str(&encode_b58(self)) + } } impl Default for Hash { - fn default() -> Self { - Self(Output::::default()) - } + fn default() -> Self { + Self(Output::::default()) + } } impl AsRef<[u8]> for Hash { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } } impl From> for Hash { - fn from(other: Output) -> Self { - Self(other) - } + fn from(other: Output) -> Self { + Self(other) + } } impl ConstantTimeEq for Hash { - fn ct_eq(&self, other: &Self) -> Choice { - self.as_ref().ct_eq(other.as_ref()) - } + fn ct_eq(&self, other: &Self) -> Choice { + self.as_ref().ct_eq(other.as_ref()) + } } diff --git a/identity-core/src/crypto/merkle_tree/math.rs b/identity-core/src/crypto/merkle_tree/math.rs index 89c544ea7e..bdfc688a65 100644 --- a/identity-core/src/crypto/merkle_tree/math.rs +++ b/identity-core/src/crypto/merkle_tree/math.rs @@ -6,42 +6,42 @@ use crate::crypto::merkle_tree::consts; /// Return true if `value` is a power of two greater than `1`. #[inline(always)] pub const fn is_pow2(value: usize) -> bool { - value > 1 && value.is_power_of_two() + value > 1 && value.is_power_of_two() } /// Returns the base-2 logarithm of `value`, rounded up. #[inline(always)] pub fn log2c(value: usize) -> usize { - (consts::SIZE_USIZE * 8) - value.leading_zeros() as usize - value.is_power_of_two() as usize + (consts::SIZE_USIZE * 8) - value.leading_zeros() as usize - value.is_power_of_two() as usize } #[cfg(test)] mod tests { - use super::is_pow2; - use super::log2c; + use super::is_pow2; + use super::log2c; - #[test] - fn test_is_pow2() { - assert_eq!(is_pow2(0), false); - assert_eq!(is_pow2(1), false); - assert_eq!(is_pow2(2), true); - assert_eq!(is_pow2(3), false); - } + #[test] + fn test_is_pow2() { + assert_eq!(is_pow2(0), false); + assert_eq!(is_pow2(1), false); + assert_eq!(is_pow2(2), true); + assert_eq!(is_pow2(3), false); + } - #[test] - fn test_log2c() { - assert_eq!(log2c(0), 0); - assert_eq!(log2c(1), 0); - assert_eq!(log2c(1 << 1), 1); - assert_eq!(log2c(1 << 2), 2); - assert_eq!(log2c(1 << 3), 3); - assert_eq!(log2c(1 << 4), 4); - assert_eq!(log2c(1 << 5), 5); - assert_eq!(log2c(1 << 6), 6); - assert_eq!(log2c(1 << 7), 7); - assert_eq!(log2c(1 << 8), 8); - assert_eq!(log2c(1 << 9), 9); - assert_eq!(log2c(1 << 10), 10); - assert_eq!(log2c(1 << 11), 11); - } + #[test] + fn test_log2c() { + assert_eq!(log2c(0), 0); + assert_eq!(log2c(1), 0); + assert_eq!(log2c(1 << 1), 1); + assert_eq!(log2c(1 << 2), 2); + assert_eq!(log2c(1 << 3), 3); + assert_eq!(log2c(1 << 4), 4); + assert_eq!(log2c(1 << 5), 5); + assert_eq!(log2c(1 << 6), 6); + assert_eq!(log2c(1 << 7), 7); + assert_eq!(log2c(1 << 8), 8); + assert_eq!(log2c(1 << 9), 9); + assert_eq!(log2c(1 << 10), 10); + assert_eq!(log2c(1 << 11), 11); + } } diff --git a/identity-core/src/crypto/merkle_tree/merkle.rs b/identity-core/src/crypto/merkle_tree/merkle.rs index bd55bf4cf0..233c6542ca 100644 --- a/identity-core/src/crypto/merkle_tree/merkle.rs +++ b/identity-core/src/crypto/merkle_tree/merkle.rs @@ -51,203 +51,203 @@ use crate::crypto::merkle_tree::Proof; // H(H(H(A | B) | H(C | D)) | H(H(E | F) | H(G | H))) pub struct MTree where - D: Digest, + D: Digest, { - nodes: Box<[Hash]>, - marker: PhantomData, + nodes: Box<[Hash]>, + marker: PhantomData, } impl MTree where - D: Digest, + D: Digest, { - /// Returns the number of leaf nodes in the tree. - pub fn leaves(&self) -> usize { - tree::leaves(self.nodes.len()) - } - - /// Returns the height of the tree. - pub fn height(&self) -> usize { - tree::height(tree::leaves(self.nodes.len())) - } - - /// Returns the root hash of the tree. - pub fn root(&self) -> &Hash { - &self.nodes[0] - } - - /// Returns a slice of the leaf nodes in the tree. - pub fn data(&self) -> &[Hash] { - &self.nodes[self.nodes.len() - self.leaves()..] - } - - /// Returns a slice of nodes at the specified `height`. - pub fn layer(&self, height: usize) -> &[Hash] { - let leaves: usize = 2_usize.pow(height as u32); - let total: usize = tree::total(leaves); - - if total <= self.nodes.len() { - &self.nodes[total - leaves..total] - } else { - &[] - } + /// Returns the number of leaf nodes in the tree. + pub fn leaves(&self) -> usize { + tree::leaves(self.nodes.len()) + } + + /// Returns the height of the tree. + pub fn height(&self) -> usize { + tree::height(tree::leaves(self.nodes.len())) + } + + /// Returns the root hash of the tree. + pub fn root(&self) -> &Hash { + &self.nodes[0] + } + + /// Returns a slice of the leaf nodes in the tree. + pub fn data(&self) -> &[Hash] { + &self.nodes[self.nodes.len() - self.leaves()..] + } + + /// Returns a slice of nodes at the specified `height`. + pub fn layer(&self, height: usize) -> &[Hash] { + let leaves: usize = 2_usize.pow(height as u32); + let total: usize = tree::total(leaves); + + if total <= self.nodes.len() { + &self.nodes[total - leaves..total] + } else { + &[] } + } } impl MTree where - D: Digest, - Output: Copy, + D: Digest, + Output: Copy, { - /// Creates a new [`MTree`] from a slice of pre-hashed data. - pub fn from_leaves(leaves: &[Hash]) -> Option { - // This Merkle tree only supports pow2 sequences - if !math::is_pow2(leaves.len()) { - return None; - } - - Some(Self { - nodes: tree::compute_nodes(&mut D::new(), leaves), - marker: PhantomData, - }) + /// Creates a new [`MTree`] from a slice of pre-hashed data. + pub fn from_leaves(leaves: &[Hash]) -> Option { + // This Merkle tree only supports pow2 sequences + if !math::is_pow2(leaves.len()) { + return None; } - /// Generates a proof-of-inclusion for the leaf node at the specified index. - pub fn proof(&self, local: usize) -> Option> { - let leaves: usize = self.leaves(); + Some(Self { + nodes: tree::compute_nodes(&mut D::new(), leaves), + marker: PhantomData, + }) + } - assert!(leaves >= 2); + /// Generates a proof-of-inclusion for the leaf node at the specified index. + pub fn proof(&self, local: usize) -> Option> { + let leaves: usize = self.leaves(); - if local >= leaves { - return None; - } + assert!(leaves >= 2); - let mut nodes: Vec> = Vec::new(); - let mut index: usize = tree::total(leaves) - leaves + local; + if local >= leaves { + return None; + } - while index > 0 { - if index & 1 == 0 { - nodes.push(Node::L(self.nodes[index - 1])); - } else { - nodes.push(Node::R(self.nodes[index + 1])); - } + let mut nodes: Vec> = Vec::new(); + let mut index: usize = tree::total(leaves) - leaves + local; - index = (index - 1) >> 1; - } + while index > 0 { + if index & 1 == 0 { + nodes.push(Node::L(self.nodes[index - 1])); + } else { + nodes.push(Node::R(self.nodes[index + 1])); + } - Some(Proof::new(nodes.into_boxed_slice())) + index = (index - 1) >> 1; } - /// Verifies the computed root of `proof` with the root hash of `self`. - pub fn verify(&self, proof: &Proof, hash: Hash) -> bool { - proof.verify(self.root(), hash) - } + Some(Proof::new(nodes.into_boxed_slice())) + } + + /// Verifies the computed root of `proof` with the root hash of `self`. + pub fn verify(&self, proof: &Proof, hash: Hash) -> bool { + proof.verify(self.root(), hash) + } } impl Debug for MTree where - D: Digest, + D: Digest, { - fn fmt(&self, f: &mut Formatter) -> Result { - let mut f = f.debug_struct("MTree"); + fn fmt(&self, f: &mut Formatter) -> Result { + let mut f = f.debug_struct("MTree"); - let total: usize = self.height(); - let count: usize = total.min(4); + let total: usize = self.height(); + let count: usize = total.min(4); - f.field("layer (root)", &self.layer(0)); + f.field("layer (root)", &self.layer(0)); - for index in 1..count { - f.field(&format!("layer (#{})", index), &self.layer(index)); - } + for index in 1..count { + f.field(&format!("layer (#{})", index), &self.layer(index)); + } - f.field("height", &total); - f.field("leaves", &self.leaves()); + f.field("height", &total); + f.field("leaves", &self.leaves()); - f.finish() - } + f.finish() + } } #[cfg(test)] mod tests { - use digest::Digest; - use sha2::Sha256; - - use crate::crypto::merkle_tree::DigestExt; - use crate::crypto::merkle_tree::Hash; - use crate::crypto::merkle_tree::MTree; - use crate::crypto::merkle_tree::Proof; - - macro_rules! h { - ($leaf:expr) => { - Sha256::new().hash_leaf($leaf) - }; - ($lhs:expr, $rhs:expr) => { - Sha256::new().hash_branch(&$lhs, &$rhs) - }; - } + use digest::Digest; + use sha2::Sha256; - type Sha256Hash = Hash; - type Sha256Proof = Proof; + use crate::crypto::merkle_tree::DigestExt; + use crate::crypto::merkle_tree::Hash; + use crate::crypto::merkle_tree::MTree; + use crate::crypto::merkle_tree::Proof; - #[test] - fn test_works() { - let nodes: Vec> = (0..(1 << 7)) - .map(|byte: u8| byte as char) - .map(String::from) - .map(String::into_bytes) - .collect(); + macro_rules! h { + ($leaf:expr) => { + Sha256::new().hash_leaf($leaf) + }; + ($lhs:expr, $rhs:expr) => { + Sha256::new().hash_branch(&$lhs, &$rhs) + }; + } - let mut digest: Sha256 = Sha256::new(); + type Sha256Hash = Hash; + type Sha256Proof = Proof; - let hashes: Vec = nodes.iter().map(|node| digest.hash_leaf(node.as_ref())).collect(); + #[test] + fn test_works() { + let nodes: Vec> = (0..(1 << 7)) + .map(|byte: u8| byte as char) + .map(String::from) + .map(String::into_bytes) + .collect(); - let tree: MTree = MTree::from_leaves(&hashes).unwrap(); + let mut digest: Sha256 = Sha256::new(); - assert_eq!(tree.data(), &hashes[..]); - assert_eq!(tree.leaves(), hashes.len()); + let hashes: Vec = nodes.iter().map(|node| digest.hash_leaf(node.as_ref())).collect(); - for (index, hash) in hashes.iter().enumerate() { - let proof: Sha256Proof = tree.proof(index).unwrap(); - let root: Sha256Hash = proof.root(*hash); + let tree: MTree = MTree::from_leaves(&hashes).unwrap(); - assert_eq!(tree.root(), &root); - } - } + assert_eq!(tree.data(), &hashes[..]); + assert_eq!(tree.leaves(), hashes.len()); + + for (index, hash) in hashes.iter().enumerate() { + let proof: Sha256Proof = tree.proof(index).unwrap(); + let root: Sha256Hash = proof.root(*hash); - #[test] - #[allow(non_snake_case)] - fn test_root() { - let A: Sha256Hash = h!(b"A"); - let B: Sha256Hash = h!(b"B"); - let C: Sha256Hash = h!(b"C"); - let D: Sha256Hash = h!(b"D"); - let E: Sha256Hash = h!(b"E"); - let F: Sha256Hash = h!(b"F"); - let G: Sha256Hash = h!(b"G"); - let H: Sha256Hash = h!(b"H"); - - let AB: Sha256Hash = h!(A, B); - let CD: Sha256Hash = h!(C, D); - let EF: Sha256Hash = h!(E, F); - let GH: Sha256Hash = h!(G, H); - - let ABCD: Sha256Hash = h!(AB, CD); - let EFGH: Sha256Hash = h!(EF, GH); - - let ABCDEFGH: Sha256Hash = h!(ABCD, EFGH); - - let data: [Sha256Hash; 8] = [A, B, C, D, E, F, G, H]; - let tree: MTree = MTree::from_leaves(&data).unwrap(); - - assert_eq!(tree.root(), &ABCDEFGH); - assert_eq!(tree.data(), &[A, B, C, D, E, F, G, H]); - assert_eq!(tree.height(), 3); - assert_eq!(tree.leaves(), 8); - - assert_eq!(tree.layer(0), &[ABCDEFGH]); - assert_eq!(tree.layer(1), &[ABCD, EFGH]); - assert_eq!(tree.layer(2), &[AB, CD, EF, GH]); - assert_eq!(tree.layer(3), &[A, B, C, D, E, F, G, H]); - assert_eq!(tree.layer(4), &[]); + assert_eq!(tree.root(), &root); } + } + + #[test] + #[allow(non_snake_case)] + fn test_root() { + let A: Sha256Hash = h!(b"A"); + let B: Sha256Hash = h!(b"B"); + let C: Sha256Hash = h!(b"C"); + let D: Sha256Hash = h!(b"D"); + let E: Sha256Hash = h!(b"E"); + let F: Sha256Hash = h!(b"F"); + let G: Sha256Hash = h!(b"G"); + let H: Sha256Hash = h!(b"H"); + + let AB: Sha256Hash = h!(A, B); + let CD: Sha256Hash = h!(C, D); + let EF: Sha256Hash = h!(E, F); + let GH: Sha256Hash = h!(G, H); + + let ABCD: Sha256Hash = h!(AB, CD); + let EFGH: Sha256Hash = h!(EF, GH); + + let ABCDEFGH: Sha256Hash = h!(ABCD, EFGH); + + let data: [Sha256Hash; 8] = [A, B, C, D, E, F, G, H]; + let tree: MTree = MTree::from_leaves(&data).unwrap(); + + assert_eq!(tree.root(), &ABCDEFGH); + assert_eq!(tree.data(), &[A, B, C, D, E, F, G, H]); + assert_eq!(tree.height(), 3); + assert_eq!(tree.leaves(), 8); + + assert_eq!(tree.layer(0), &[ABCDEFGH]); + assert_eq!(tree.layer(1), &[ABCD, EFGH]); + assert_eq!(tree.layer(2), &[AB, CD, EF, GH]); + assert_eq!(tree.layer(3), &[A, B, C, D, E, F, G, H]); + assert_eq!(tree.layer(4), &[]); + } } diff --git a/identity-core/src/crypto/merkle_tree/node.rs b/identity-core/src/crypto/merkle_tree/node.rs index 764b1e2ff8..ac075ce02b 100644 --- a/identity-core/src/crypto/merkle_tree/node.rs +++ b/identity-core/src/crypto/merkle_tree/node.rs @@ -11,64 +11,64 @@ use crate::crypto::merkle_tree::Hash; /// A tagged [`struct@Hash`]. pub enum Node { - /// A node tagged with `L`. - L(Hash), - /// A node tagged with `R`. - R(Hash), + /// A node tagged with `L`. + L(Hash), + /// A node tagged with `R`. + R(Hash), } impl Node { - /// Returns the [`struct@Hash`] of the node. - pub fn get(&self) -> &Hash { - match self { - Self::L(hash) => hash, - Self::R(hash) => hash, - } + /// Returns the [`struct@Hash`] of the node. + pub fn get(&self) -> &Hash { + match self { + Self::L(hash) => hash, + Self::R(hash) => hash, } + } - /// Computes the parent hash of `self` and `other` using a default digest. - pub fn hash(&self, other: &Hash) -> Hash { - self.hash_with(&mut D::new(), other) - } + /// Computes the parent hash of `self` and `other` using a default digest. + pub fn hash(&self, other: &Hash) -> Hash { + self.hash_with(&mut D::new(), other) + } - /// Computes the parent hash of `self` and `other` using the given `digest`. - pub fn hash_with(&self, digest: &mut D, other: &Hash) -> Hash { - match self { - Self::L(hash) => digest.hash_branch(hash, other), - Self::R(hash) => digest.hash_branch(other, hash), - } + /// Computes the parent hash of `self` and `other` using the given `digest`. + pub fn hash_with(&self, digest: &mut D, other: &Hash) -> Hash { + match self { + Self::L(hash) => digest.hash_branch(hash, other), + Self::R(hash) => digest.hash_branch(other, hash), } + } } impl Debug for Node { - fn fmt(&self, f: &mut Formatter) -> Result { - match self { - Self::L(hash) => f.write_fmt(format_args!("L({:?})", hash)), - Self::R(hash) => f.write_fmt(format_args!("R({:?})", hash)), - } + fn fmt(&self, f: &mut Formatter) -> Result { + match self { + Self::L(hash) => f.write_fmt(format_args!("L({:?})", hash)), + Self::R(hash) => f.write_fmt(format_args!("R({:?})", hash)), } + } } #[cfg(test)] mod tests { - use digest::Digest; - use sha2::Sha256; + use digest::Digest; + use sha2::Sha256; - use crate::crypto::merkle_tree::DigestExt; - use crate::crypto::merkle_tree::Hash; - use crate::crypto::merkle_tree::Node; + use crate::crypto::merkle_tree::DigestExt; + use crate::crypto::merkle_tree::Hash; + use crate::crypto::merkle_tree::Node; - #[test] - fn test_hash() { - let mut digest: Sha256 = Sha256::new(); + #[test] + fn test_hash() { + let mut digest: Sha256 = Sha256::new(); - let h1: Hash = digest.hash_leaf(b"A"); - let h2: Hash = digest.hash_leaf(b"B"); + let h1: Hash = digest.hash_leaf(b"A"); + let h2: Hash = digest.hash_leaf(b"B"); - assert_eq!(Node::L(h1).hash(&h2), digest.hash_branch(&h1, &h2)); - assert_eq!(Node::R(h1).hash(&h2), digest.hash_branch(&h2, &h1)); + assert_eq!(Node::L(h1).hash(&h2), digest.hash_branch(&h1, &h2)); + assert_eq!(Node::R(h1).hash(&h2), digest.hash_branch(&h2, &h1)); - assert_eq!(Node::L(h2).hash(&h1), digest.hash_branch(&h2, &h1)); - assert_eq!(Node::R(h2).hash(&h1), digest.hash_branch(&h1, &h2)); - } + assert_eq!(Node::L(h2).hash(&h1), digest.hash_branch(&h2, &h1)); + assert_eq!(Node::R(h2).hash(&h1), digest.hash_branch(&h1, &h2)); + } } diff --git a/identity-core/src/crypto/merkle_tree/proof.rs b/identity-core/src/crypto/merkle_tree/proof.rs index f63435a294..da3354183e 100644 --- a/identity-core/src/crypto/merkle_tree/proof.rs +++ b/identity-core/src/crypto/merkle_tree/proof.rs @@ -13,38 +13,38 @@ use crate::crypto::merkle_tree::Node; /// An inclusion proof that allows proving the existence of data in a /// [`Merkle tree`](`struct@super::MTree`). pub struct Proof { - nodes: Box<[Node]>, + nodes: Box<[Node]>, } impl Proof { - /// Creates a new [`Proof`] from a boxed slice of nodes. - pub fn new(nodes: Box<[Node]>) -> Self { - Self { nodes } - } - - /// Returns the nodes as a slice. - pub fn nodes(&self) -> &[Node] { - &self.nodes - } - - /// Verifies the computed root of `self` with the given `root` hash. - pub fn verify(&self, root: &Hash, hash: Hash) -> bool { - self.root(hash).ct_eq(root).into() - } - - /// Computes the root hash from `target` using a default digest. - pub fn root(&self, target: Hash) -> Hash { - self.root_with(&mut D::new(), target) - } - - /// Computes the root hash from `target` using the given `digest`. - pub fn root_with(&self, digest: &mut D, target: Hash) -> Hash { - self.nodes.iter().fold(target, |acc, item| item.hash_with(digest, &acc)) - } + /// Creates a new [`Proof`] from a boxed slice of nodes. + pub fn new(nodes: Box<[Node]>) -> Self { + Self { nodes } + } + + /// Returns the nodes as a slice. + pub fn nodes(&self) -> &[Node] { + &self.nodes + } + + /// Verifies the computed root of `self` with the given `root` hash. + pub fn verify(&self, root: &Hash, hash: Hash) -> bool { + self.root(hash).ct_eq(root).into() + } + + /// Computes the root hash from `target` using a default digest. + pub fn root(&self, target: Hash) -> Hash { + self.root_with(&mut D::new(), target) + } + + /// Computes the root hash from `target` using the given `digest`. + pub fn root_with(&self, digest: &mut D, target: Hash) -> Hash { + self.nodes.iter().fold(target, |acc, item| item.hash_with(digest, &acc)) + } } impl Debug for Proof { - fn fmt(&self, f: &mut Formatter) -> Result { - f.debug_struct("Proof").field("nodes", &self.nodes).finish() - } + fn fmt(&self, f: &mut Formatter) -> Result { + f.debug_struct("Proof").field("nodes", &self.nodes).finish() + } } diff --git a/identity-core/src/crypto/merkle_tree/tree.rs b/identity-core/src/crypto/merkle_tree/tree.rs index 156d888406..df5df96562 100644 --- a/identity-core/src/crypto/merkle_tree/tree.rs +++ b/identity-core/src/crypto/merkle_tree/tree.rs @@ -10,74 +10,74 @@ use crate::crypto::merkle_tree::Hash; #[inline(always)] pub fn height(leaves: usize) -> usize { - math::log2c(leaves) + math::log2c(leaves) } #[inline(always)] pub const fn total(leaves: usize) -> usize { - // 2l - 1 - (leaves << 1) - 1 + // 2l - 1 + (leaves << 1) - 1 } #[inline(always)] pub const fn leaves(nodes: usize) -> usize { - // l = (n + 1) / 2 - (nodes + 1) >> 1 + // l = (n + 1) / 2 + (nodes + 1) >> 1 } #[inline(always)] pub const fn index_lhs(index: usize) -> usize { - // 2i + 1 - (index << 1) + 1 + // 2i + 1 + (index << 1) + 1 } #[inline(always)] pub const fn index_rhs(index: usize) -> usize { - // 2i + 2 - (index << 1) + 2 + // 2i + 2 + (index << 1) + 2 } pub fn compute_nodes(digest: &mut D, leaves: &[Hash]) -> Box<[Hash]> where - D: Digest, - Output: Copy, + D: Digest, + Output: Copy, { - let count: usize = leaves.len(); - let total: usize = self::total(count); - let offset: usize = total - count; - let height: usize = self::height(count); + let count: usize = leaves.len(); + let total: usize = self::total(count); + let offset: usize = total - count; + let height: usize = self::height(count); - assert_eq!(count, 1 << height); + assert_eq!(count, 1 << height); - // Create a vec for the entire set of nodes - let mut nodes: Vec> = vec![Hash::default(); total]; + // Create a vec for the entire set of nodes + let mut nodes: Vec> = vec![Hash::default(); total]; - // Copy the initial hashes to the end of the vec - nodes[offset..total].copy_from_slice(leaves); + // Copy the initial hashes to the end of the vec + nodes[offset..total].copy_from_slice(leaves); - // Compute parent hashes in bottom-up order - for index in 0..height { - compute(digest, &mut nodes, height - index); - } + // Compute parent hashes in bottom-up order + for index in 0..height { + compute(digest, &mut nodes, height - index); + } - nodes.into_boxed_slice() + nodes.into_boxed_slice() } fn compute(digest: &mut D, nodes: &mut Vec>, index: usize) where - D: Digest, + D: Digest, { - let update: usize = 1 << (index - 1); - let offset: usize = update - 1; + let update: usize = 1 << (index - 1); + let offset: usize = update - 1; - for index in 0..update { - let local: usize = offset + index; + for index in 0..update { + let local: usize = offset + index; - assert!(nodes.len() > local); + assert!(nodes.len() > local); - let lhs: &Hash = &nodes[index_lhs(local)]; - let rhs: &Hash = &nodes[index_rhs(local)]; + let lhs: &Hash = &nodes[index_lhs(local)]; + let rhs: &Hash = &nodes[index_rhs(local)]; - nodes[local] = digest.hash_branch(lhs, rhs); - } + nodes[local] = digest.hash_branch(lhs, rhs); + } } diff --git a/identity-core/src/error.rs b/identity-core/src/error.rs index 9a7e551d87..7397a255f5 100644 --- a/identity-core/src/error.rs +++ b/identity-core/src/error.rs @@ -9,40 +9,40 @@ pub type Result = ::core::result::Result; /// This type represents all possible errors that can occur in the library. #[derive(Debug, thiserror::Error)] pub enum Error { - /// Caused by a failure to encode Rust types as JSON. - #[error("Failed to encode JSON: {0}")] - EncodeJSON(serde_json::Error), - /// Caused by a failure to decode Rust types from JSON. - #[error("Failed to decode JSON: {0}")] - DecodeJSON(serde_json::Error), - /// Caused by a failure to decode base58-encoded data. - #[error("Failed to decode base58 data: {0}")] - DecodeBase58(#[from] bs58::decode::Error), - /// Caused by attempting to perform an invalid `DID` operation. - #[error("Invalid DID: {0}")] - InvalidDID(#[from] did_url::Error), - /// Caused by attempting to perform an invalid DID `Document` operation. - #[error("Invalid DID Document: {0}")] - InvalidDocument(#[from] did_doc::Error), - /// Caused by attempting to perform an invalid `Diff` operation. - #[error("Invalid Document Diff: {0}")] - InvalidDiff(#[from] identity_diff::Error), - /// Caused by attempting to parse an invalid `Url`. - #[error("Invalid Url: {0}")] - InvalidUrl(#[from] did_doc::url::ParseError), - /// Caused by attempting to parse an invalid `Timestamp`. - #[error("Invalid Timestamp: {0}")] - InvalidTimestamp(#[from] chrono::ParseError), - /// Caused by attempting to perform an invalid `Credential` operation. - #[error("Invalid Credential: {0}")] - InvalidCredential(String), - /// Caused by attempting to perform an invalid `Presentation` operation. - #[error("Invalid Presentation: {0}")] - InvalidPresentation(String), - /// Caused by a failure to resolve a DID. - #[error("DID Resolution Error: {0}")] - ResolutionError(anyhow::Error), - /// Caused by a failure to dereference a DID URL. - #[error("DID Dereference Error: {0}")] - DereferenceError(anyhow::Error), + /// Caused by a failure to encode Rust types as JSON. + #[error("Failed to encode JSON: {0}")] + EncodeJSON(serde_json::Error), + /// Caused by a failure to decode Rust types from JSON. + #[error("Failed to decode JSON: {0}")] + DecodeJSON(serde_json::Error), + /// Caused by a failure to decode base58-encoded data. + #[error("Failed to decode base58 data: {0}")] + DecodeBase58(#[from] bs58::decode::Error), + /// Caused by attempting to perform an invalid `DID` operation. + #[error("Invalid DID: {0}")] + InvalidDID(#[from] did_url::Error), + /// Caused by attempting to perform an invalid DID `Document` operation. + #[error("Invalid DID Document: {0}")] + InvalidDocument(#[from] did_doc::Error), + /// Caused by attempting to perform an invalid `Diff` operation. + #[error("Invalid Document Diff: {0}")] + InvalidDiff(#[from] identity_diff::Error), + /// Caused by attempting to parse an invalid `Url`. + #[error("Invalid Url: {0}")] + InvalidUrl(#[from] did_doc::url::ParseError), + /// Caused by attempting to parse an invalid `Timestamp`. + #[error("Invalid Timestamp: {0}")] + InvalidTimestamp(#[from] chrono::ParseError), + /// Caused by attempting to perform an invalid `Credential` operation. + #[error("Invalid Credential: {0}")] + InvalidCredential(String), + /// Caused by attempting to perform an invalid `Presentation` operation. + #[error("Invalid Presentation: {0}")] + InvalidPresentation(String), + /// Caused by a failure to resolve a DID. + #[error("DID Resolution Error: {0}")] + ResolutionError(anyhow::Error), + /// Caused by a failure to dereference a DID URL. + #[error("DID Dereference Error: {0}")] + DereferenceError(anyhow::Error), } diff --git a/identity-core/src/lib.rs b/identity-core/src/lib.rs index ba9c7c0328..e31ec3edbe 100644 --- a/identity-core/src/lib.rs +++ b/identity-core/src/lib.rs @@ -4,13 +4,13 @@ //! Identity Core #![warn( - missing_docs, - missing_crate_level_docs, - broken_intra_doc_links, - private_intra_doc_links, - private_doc_tests, - clippy::missing_safety_doc, - // clippy::missing_errors_doc + missing_docs, + missing_crate_level_docs, + broken_intra_doc_links, + private_intra_doc_links, + private_doc_tests, + clippy::missing_safety_doc, + // clippy::missing_errors_doc )] #[macro_use] diff --git a/identity-core/src/proof/jcsed25519signature2020.rs b/identity-core/src/proof/jcsed25519signature2020.rs index b8c6298f41..24518f37ae 100644 --- a/identity-core/src/proof/jcsed25519signature2020.rs +++ b/identity-core/src/proof/jcsed25519signature2020.rs @@ -10,8 +10,8 @@ use sha2::{digest::Output, Sha256}; use subtle::ConstantTimeEq; use crate::{ - crypto::KeyPair, - utils::{decode_b58, encode_b58, jcs_sha256}, + crypto::KeyPair, + utils::{decode_b58, encode_b58, jcs_sha256}, }; const SIGNATURE_NAME: &str = "JcsEd25519Signature2020"; @@ -28,130 +28,131 @@ const SECRET_KEY_BYTES: usize = 32; pub struct JcsEd25519Signature2020; impl JcsEd25519Signature2020 { - /// The name of the signature suite. - pub const NAME: &'static str = SIGNATURE_NAME; - - /// Generates a new [`KeyPair`] appropriate for this signature suite. - pub fn new_keypair() -> KeyPair { - let secret: SigningKey = SigningKey::new(OsRng); - let public: VerificationKey = (&secret).into(); - let public: VerificationKeyBytes = public.into(); - - KeyPair::new(public.as_ref().to_vec().into(), secret.as_ref().to_vec().into()) - } - - /// Signs the given `data` with `secret` and returns a digital signature. - pub fn sign_data(data: &T, secret: &[u8]) -> Result - where - T: Serialize, - { - Self::normalize(data) - .and_then(|data| ed25519_sign(&data, secret)) - .map_err(|_| Error::message("Cannot Sign Document")) - .map(|data| encode_b58(&data)) - .map(SignatureData::Signature) - } - - /// Verifies the authenticity of `data` using `signature` and `public`. - pub fn verify_data(data: &T, signature: &SignatureData, public: &[u8]) -> Result<()> - where - T: Serialize, - { - let signature: &str = signature - .try_signature() - .ok_or_else(|| Error::message("Signature Data Not Found"))?; - - let signature: Vec = decode_b58(&signature).map_err(|_| Error::message("Invalid Signature Data"))?; - let verified: Vec = ed25519_verify(&signature, public)?; - let digest: _ = Self::normalize(data)?; - - if digest[..].ct_eq(&verified[..]).into() { - Ok(()) - } else { - Err(Error::message("Invalid Signature Digest")) - } - } - - fn normalize(data: &T) -> Result> - where - T: Serialize, - { - jcs_sha256(data).map_err(|_| Error::message("Cannot Serialize Document")) + /// The name of the signature suite. + pub const NAME: &'static str = SIGNATURE_NAME; + + /// Generates a new [`KeyPair`] appropriate for this signature suite. + pub fn new_keypair() -> KeyPair { + let secret: SigningKey = SigningKey::new(OsRng); + let public: VerificationKey = (&secret).into(); + let public: VerificationKeyBytes = public.into(); + + KeyPair::new(public.as_ref().to_vec().into(), secret.as_ref().to_vec().into()) + } + + /// Signs the given `data` with `secret` and returns a digital signature. + pub fn sign_data(data: &T, secret: &[u8]) -> Result + where + T: Serialize, + { + Self::normalize(data) + .and_then(|data| ed25519_sign(&data, secret)) + .map_err(|_| Error::message("Cannot Sign Document")) + .map(|data| encode_b58(&data)) + .map(SignatureData::Signature) + } + + /// Verifies the authenticity of `data` using `signature` and `public`. + pub fn verify_data(data: &T, signature: &SignatureData, public: &[u8]) -> Result<()> + where + T: Serialize, + { + let signature: &str = signature + .try_signature() + .ok_or_else(|| Error::message("Signature Data Not Found"))?; + + let signature: Vec = decode_b58(&signature).map_err(|_| Error::message("Invalid Signature Data"))?; + let verified: Vec = ed25519_verify(&signature, public)?; + let digest: _ = Self::normalize(data)?; + + if digest[..].ct_eq(&verified[..]).into() { + Ok(()) + } else { + Err(Error::message("Invalid Signature Digest")) } + } + + fn normalize(data: &T) -> Result> + where + T: Serialize, + { + jcs_sha256(data).map_err(|_| Error::message("Cannot Serialize Document")) + } } impl SuiteName for JcsEd25519Signature2020 { - fn name(&self) -> String { - Self::NAME.to_string() - } + fn name(&self) -> String { + Self::NAME.to_string() + } } impl Sign for JcsEd25519Signature2020 { - fn sign(&self, data: &T, secret: &[u8]) -> Result - where - T: Serialize, - { - Self::sign_data(data, secret) - } + fn sign(&self, data: &T, secret: &[u8]) -> Result + where + T: Serialize, + { + Self::sign_data(data, secret) + } } impl Verify for JcsEd25519Signature2020 { - fn verify(&self, data: &T, signature: &SignatureData, method: &Method) -> Result<()> - where - T: Serialize, - U: Serialize, - { - Self::verify_data(data, signature, &method.key_data().try_decode()?) - } + fn verify(&self, data: &T, signature: &SignatureData, method: &Method) -> Result<()> + where + T: Serialize, + U: Serialize, + { + Self::verify_data(data, signature, &method.key_data().try_decode()?) + } } fn pubkey(slice: &[u8]) -> Result { - if slice.len() < PUBLIC_KEY_BYTES { - return Err(Error::message("Invalid Key Format")); - } + if slice.len() < PUBLIC_KEY_BYTES { + return Err(Error::message("Invalid Key Format")); + } - slice[..PUBLIC_KEY_BYTES] - .try_into() - .map_err(|_| Error::message("Invalid Key Format")) + slice[..PUBLIC_KEY_BYTES] + .try_into() + .map_err(|_| Error::message("Invalid Key Format")) } fn seckey(slice: &[u8]) -> Result { - if slice.len() < SECRET_KEY_BYTES { - return Err(Error::message("Invalid Key Format")); - } + if slice.len() < SECRET_KEY_BYTES { + return Err(Error::message("Invalid Key Format")); + } - slice[..SECRET_KEY_BYTES] - .try_into() - .map_err(|_| Error::message("Invalid Key Format")) + slice[..SECRET_KEY_BYTES] + .try_into() + .map_err(|_| Error::message("Invalid Key Format")) } // output = fn ed25519_sign(message: &[u8], secret: &[u8]) -> Result> { - let key: SigningKey = seckey(secret)?; - let sig: [u8; SIGNATURE_SIZE] = key.sign(message).into(); + let key: SigningKey = seckey(secret)?; + let sig: [u8; SIGNATURE_SIZE] = key.sign(message).into(); - Ok([&sig, message].concat()) + Ok([&sig, message].concat()) } // signature = fn ed25519_verify(signature: &[u8], public: &[u8]) -> Result> { - if signature.len() < SIGNATURE_SIZE { - return Err(Error::message("Invalid Key Format")); - } + if signature.len() < SIGNATURE_SIZE { + return Err(Error::message("Invalid Key Format")); + } - let key: VerificationKey = pubkey(public)?; - let (sig, msg): (&[u8], &[u8]) = signature.split_at(SIGNATURE_SIZE); - let sig: Signature = sig.try_into().map_err(|_| Error::message("Invalid Key Format"))?; + let key: VerificationKey = pubkey(public)?; + let (sig, msg): (&[u8], &[u8]) = signature.split_at(SIGNATURE_SIZE); + let sig: Signature = sig.try_into().map_err(|_| Error::message("Invalid Key Format"))?; - key.verify(&sig, msg) - .map_err(|_| Error::message("Invalid Key Format"))?; + key + .verify(&sig, msg) + .map_err(|_| Error::message("Invalid Key Format"))?; - Ok(msg.to_vec()) + Ok(msg.to_vec()) } #[cfg(test)] mod tests { - const UNSIGNED: &str = r##" + const UNSIGNED: &str = r##" { "id": "did:example:123", "verificationMethod": [ @@ -172,7 +173,7 @@ mod tests { } "##; - const SIGNED: &str = r##" + const SIGNED: &str = r##" { "id": "did:example:123", "verificationMethod": [ @@ -198,99 +199,99 @@ mod tests { } "##; - use super::{ed25519_sign, ed25519_verify, JcsEd25519Signature2020}; + use super::{ed25519_sign, ed25519_verify, JcsEd25519Signature2020}; - use crate::{ - convert::FromJson as _, - did_doc::{LdSuite, SignatureData, SignatureOptions, VerifiableDocument}, - utils::decode_b58, - }; + use crate::{ + convert::FromJson as _, + did_doc::{LdSuite, SignatureData, SignatureOptions, VerifiableDocument}, + utils::decode_b58, + }; - const PUBLIC_B58: &str = "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c"; - const SECRET_B58: &str = "3qsrFcQqVuPpuGrRkU4wkQRvw1tc1C5EmEDPioS1GzQ2pLoThy5TYS2BsrwuzHYDnVqcYhMSpDhTXGst6H5ttFkG"; + const PUBLIC_B58: &str = "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c"; + const SECRET_B58: &str = "3qsrFcQqVuPpuGrRkU4wkQRvw1tc1C5EmEDPioS1GzQ2pLoThy5TYS2BsrwuzHYDnVqcYhMSpDhTXGst6H5ttFkG"; - #[rustfmt::skip] + #[rustfmt::skip] const SIGNATURE_HELLO: &[u8] = &[12, 203, 235, 144, 80, 6, 163, 39, 181, 17, 44, 123, 250, 162, 165, 145, 135, 132, 32, 152, 24, 168, 55, 80, 84, 139, 153, 101, 102, 27, 157, 29, 70, 124, 64, 120, 250, 172, 186, 163, 108, 27, 208, 248, 134, 115, 3, 154, 222, 165, 31, 93, 33, 108, 212, 92, 191, 14, 21, 40, 251, 103, 241, 10, 104, 101, 108, 108, 111]; - const SIGNATURE_DOCUMENT: &str = "piKnvB438vWsinW1dqq2EYRzcYFuR7Qm9X8t2S6TPPLDokLwcFBXnnERk6jmS8RXKTJnXKWw1Q9oNhYTwbR7vJkaJT8ZGgwDHNxa6mrMNsQsWkM4rg6EYY99xQko7FnpAMn"; - - #[test] - fn test_ed25519_can_sign_and_verify() { - let public: Vec = decode_b58(PUBLIC_B58).unwrap(); - let secret: Vec = decode_b58(SECRET_B58).unwrap(); - - let signature = ed25519_sign(b"hello", &secret).unwrap(); - let verified = ed25519_verify(&signature, &public).unwrap(); - - assert_eq!(&signature, SIGNATURE_HELLO); - assert_eq!(&verified, b"hello"); - } - - #[test] - fn test_jcsed25519signature2020_can_sign_and_verify() { - let secret = decode_b58(SECRET_B58).unwrap(); - let mut unsigned: VerifiableDocument = VerifiableDocument::from_json(UNSIGNED).unwrap(); - let signed: VerifiableDocument = VerifiableDocument::from_json(SIGNED).unwrap(); - let method = unsigned.try_resolve("#key-1").unwrap(); - let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); - let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); - - suite.sign(&mut unsigned, options, &secret).unwrap(); - - assert!(suite.verify(&unsigned).is_ok()); - assert_eq!( - unsigned.properties().proof().unwrap().data().as_str(), - SIGNATURE_DOCUMENT - ); - - assert_eq!( - serde_jcs::to_vec(&unsigned).unwrap(), - serde_jcs::to_vec(&signed).unwrap() - ); - } - - #[test] - fn test_jcsed25519signature2020_fails_when_key_is_mutated() { - let secret = decode_b58(SECRET_B58).unwrap(); - let mut document: VerifiableDocument = VerifiableDocument::from_json(UNSIGNED).unwrap(); - let method = document.try_resolve("#key-1").unwrap(); - let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); - let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); - - suite.sign(&mut document, options, &secret).unwrap(); - - assert!(suite.verify(&document).is_ok()); - assert_eq!( - document.properties().proof().unwrap().data().as_str(), - SIGNATURE_DOCUMENT - ); - - document.proof_mut().unwrap().verification_method = "#key-2".into(); - - assert!(suite.verify(&document).is_err()); - } - - #[test] - fn test_jcsed25519signature2020_fails_when_signature_is_mutated() { - let secret = decode_b58(SECRET_B58).unwrap(); - let mut document: VerifiableDocument = VerifiableDocument::from_json(UNSIGNED).unwrap(); - let method = document.try_resolve("#key-1").unwrap(); - let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); - let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); - - suite.sign(&mut document, options, &secret).unwrap(); - - assert!(suite.verify(&document).is_ok()); - assert_eq!( - document.properties().proof().unwrap().data().as_str(), - SIGNATURE_DOCUMENT - ); - - document - .proof_mut() - .unwrap() - .set_data(SignatureData::Signature("foo".into())); - - assert!(suite.verify(&document).is_err()); - } + const SIGNATURE_DOCUMENT: &str = "piKnvB438vWsinW1dqq2EYRzcYFuR7Qm9X8t2S6TPPLDokLwcFBXnnERk6jmS8RXKTJnXKWw1Q9oNhYTwbR7vJkaJT8ZGgwDHNxa6mrMNsQsWkM4rg6EYY99xQko7FnpAMn"; + + #[test] + fn test_ed25519_can_sign_and_verify() { + let public: Vec = decode_b58(PUBLIC_B58).unwrap(); + let secret: Vec = decode_b58(SECRET_B58).unwrap(); + + let signature = ed25519_sign(b"hello", &secret).unwrap(); + let verified = ed25519_verify(&signature, &public).unwrap(); + + assert_eq!(&signature, SIGNATURE_HELLO); + assert_eq!(&verified, b"hello"); + } + + #[test] + fn test_jcsed25519signature2020_can_sign_and_verify() { + let secret = decode_b58(SECRET_B58).unwrap(); + let mut unsigned: VerifiableDocument = VerifiableDocument::from_json(UNSIGNED).unwrap(); + let signed: VerifiableDocument = VerifiableDocument::from_json(SIGNED).unwrap(); + let method = unsigned.try_resolve("#key-1").unwrap(); + let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); + let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); + + suite.sign(&mut unsigned, options, &secret).unwrap(); + + assert!(suite.verify(&unsigned).is_ok()); + assert_eq!( + unsigned.properties().proof().unwrap().data().as_str(), + SIGNATURE_DOCUMENT + ); + + assert_eq!( + serde_jcs::to_vec(&unsigned).unwrap(), + serde_jcs::to_vec(&signed).unwrap() + ); + } + + #[test] + fn test_jcsed25519signature2020_fails_when_key_is_mutated() { + let secret = decode_b58(SECRET_B58).unwrap(); + let mut document: VerifiableDocument = VerifiableDocument::from_json(UNSIGNED).unwrap(); + let method = document.try_resolve("#key-1").unwrap(); + let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); + let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); + + suite.sign(&mut document, options, &secret).unwrap(); + + assert!(suite.verify(&document).is_ok()); + assert_eq!( + document.properties().proof().unwrap().data().as_str(), + SIGNATURE_DOCUMENT + ); + + document.proof_mut().unwrap().verification_method = "#key-2".into(); + + assert!(suite.verify(&document).is_err()); + } + + #[test] + fn test_jcsed25519signature2020_fails_when_signature_is_mutated() { + let secret = decode_b58(SECRET_B58).unwrap(); + let mut document: VerifiableDocument = VerifiableDocument::from_json(UNSIGNED).unwrap(); + let method = document.try_resolve("#key-1").unwrap(); + let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); + let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); + + suite.sign(&mut document, options, &secret).unwrap(); + + assert!(suite.verify(&document).is_ok()); + assert_eq!( + document.properties().proof().unwrap().data().as_str(), + SIGNATURE_DOCUMENT + ); + + document + .proof_mut() + .unwrap() + .set_data(SignatureData::Signature("foo".into())); + + assert!(suite.verify(&document).is_err()); + } } diff --git a/identity-core/src/resolver/dereference.rs b/identity-core/src/resolver/dereference.rs index a15f755886..0e8a120707 100644 --- a/identity-core/src/resolver/dereference.rs +++ b/identity-core/src/resolver/dereference.rs @@ -10,24 +10,24 @@ use crate::resolver::{DocumentMetadata, ResolutionMetadata, Resource}; /// [SPEC]: https://www.w3.org/TR/did-core/#dfn-did-url-dereferencing #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct Dereference { - /// Metadata regarding the base resolution process. - #[serde(rename = "did-url-dereferencing-metadata")] - pub metadata: ResolutionMetadata, - /// The output resource of a successful dereference. - #[serde(rename = "content-stream", skip_serializing_if = "Option::is_none")] - pub content: Option, - /// Content-specific metadata. - #[serde(rename = "content-metadata", skip_serializing_if = "Option::is_none")] - pub content_metadata: Option, + /// Metadata regarding the base resolution process. + #[serde(rename = "did-url-dereferencing-metadata")] + pub metadata: ResolutionMetadata, + /// The output resource of a successful dereference. + #[serde(rename = "content-stream", skip_serializing_if = "Option::is_none")] + pub content: Option, + /// Content-specific metadata. + #[serde(rename = "content-metadata", skip_serializing_if = "Option::is_none")] + pub content_metadata: Option, } impl Dereference { - /// Creates a new [`Dereference`]. - pub fn new() -> Self { - Self { - metadata: ResolutionMetadata::new(), - content: None, - content_metadata: None, - } + /// Creates a new [`Dereference`]. + pub fn new() -> Self { + Self { + metadata: ResolutionMetadata::new(), + content: None, + content_metadata: None, } + } } diff --git a/identity-core/src/resolver/document_metadata.rs b/identity-core/src/resolver/document_metadata.rs index da01cbc047..fa9ba12917 100644 --- a/identity-core/src/resolver/document_metadata.rs +++ b/identity-core/src/resolver/document_metadata.rs @@ -7,28 +7,28 @@ use serde::{Deserialize, Serialize}; /// Metadata associated with a resolved DID Document. #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct DocumentMetadata { - /// The timestamp of the Create operation. - /// - /// [More Info](https://www.w3.org/TR/did-spec-registries/#created) - #[serde(skip_serializing_if = "Option::is_none")] - pub created: Option, - /// The timestamp of the last Update operation. - /// - /// [More Info](https://www.w3.org/TR/did-spec-registries/#updated) - #[serde(skip_serializing_if = "Option::is_none")] - pub updated: Option, - /// Additional document metadata properties. - #[serde(flatten)] - pub properties: Object, + /// The timestamp of the Create operation. + /// + /// [More Info](https://www.w3.org/TR/did-spec-registries/#created) + #[serde(skip_serializing_if = "Option::is_none")] + pub created: Option, + /// The timestamp of the last Update operation. + /// + /// [More Info](https://www.w3.org/TR/did-spec-registries/#updated) + #[serde(skip_serializing_if = "Option::is_none")] + pub updated: Option, + /// Additional document metadata properties. + #[serde(flatten)] + pub properties: Object, } impl DocumentMetadata { - /// Creates a new [`DocumentMetadata`]. - pub fn new() -> Self { - Self { - created: None, - updated: None, - properties: Object::new(), - } + /// Creates a new [`DocumentMetadata`]. + pub fn new() -> Self { + Self { + created: None, + updated: None, + properties: Object::new(), } + } } diff --git a/identity-core/src/resolver/error_kind.rs b/identity-core/src/resolver/error_kind.rs index 428a44de9a..fb90e7e578 100644 --- a/identity-core/src/resolver/error_kind.rs +++ b/identity-core/src/resolver/error_kind.rs @@ -8,15 +8,15 @@ use serde::{Deserialize, Serialize}; /// [SPEC]: https://www.w3.org/TR/did-core/#dfn-did-resolution #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] pub enum ErrorKind { - /// The DID supplied to the DID resolution function does not conform to - /// valid syntax. - #[serde(rename = "invalid-did")] - InvalidDID, - /// The DID resolver does not support the specified method. - #[serde(rename = "not-supported")] - NotSupported, - /// The DID resolver was unable to return the DID document resulting from - /// this resolution request. - #[serde(rename = "not-found")] - NotFound, + /// The DID supplied to the DID resolution function does not conform to + /// valid syntax. + #[serde(rename = "invalid-did")] + InvalidDID, + /// The DID resolver does not support the specified method. + #[serde(rename = "not-supported")] + NotSupported, + /// The DID resolver was unable to return the DID document resulting from + /// this resolution request. + #[serde(rename = "not-found")] + NotFound, } diff --git a/identity-core/src/resolver/impls.rs b/identity-core/src/resolver/impls.rs index d3e280dab3..8003d86e1f 100644 --- a/identity-core/src/resolver/impls.rs +++ b/identity-core/src/resolver/impls.rs @@ -7,11 +7,11 @@ use did_url::DID; use std::time::Instant; use crate::{ - error::{Error, Result}, - resolver::{ - Dereference, DocumentMetadata, ErrorKind, InputMetadata, MetaDocument, PrimaryResource, Resolution, - ResolverMethod, Resource, SecondaryResource, - }, + error::{Error, Result}, + resolver::{ + Dereference, DocumentMetadata, ErrorKind, InputMetadata, MetaDocument, PrimaryResource, Resolution, ResolverMethod, + Resource, SecondaryResource, + }, }; /// Resolves a DID into a DID Document by using the "Read" operation of the DID method. @@ -21,43 +21,43 @@ use crate::{ /// [SPEC]: https://www.w3.org/TR/did-core/#did-resolution pub async fn resolve(did: &str, input: InputMetadata, method: R) -> Result where - R: ResolverMethod, + R: ResolverMethod, { - let mut context: ResolveContext = ResolveContext::new(); - - // 1. Validate that the input DID conforms to the did rule of the DID Syntax. - let did: DID = match did.parse() { - Ok(did) => did, - Err(_) => return Ok(context.finish_error(ErrorKind::InvalidDID)), - }; - - // 2. Determine if the input DID method is supported by the DID resolver - // that implements this algorithm. - if !method.is_supported(&did) { - return Ok(context.finish_error(ErrorKind::NotSupported)); - } - - // 3. Obtain the DID document for the input DID by executing the Read - // operation against the input DID's verifiable data registry. - let doc: MetaDocument = match method.read(&did, input).await? { - Some(doc) => doc, - None => return Ok(context.finish_error(ErrorKind::NotFound)), - }; - - // 4. Validate that the output DID document conforms to a conformant - // serialization of the DID document data model. - // if did.method() != doc.data.id.method() || did.method_id() != doc.data.id.method_id() { - // return Ok(context.finish_error(ErrorKind::InvalidDID)); - // } - - // TODO: Handle deactivated DIDs - // TODO: Handle signature verification - - context.set_document(doc.data); - context.set_metadata(doc.meta); - context.set_resolved(did); - - Ok(context.finish()) + let mut context: ResolveContext = ResolveContext::new(); + + // 1. Validate that the input DID conforms to the did rule of the DID Syntax. + let did: DID = match did.parse() { + Ok(did) => did, + Err(_) => return Ok(context.finish_error(ErrorKind::InvalidDID)), + }; + + // 2. Determine if the input DID method is supported by the DID resolver + // that implements this algorithm. + if !method.is_supported(&did) { + return Ok(context.finish_error(ErrorKind::NotSupported)); + } + + // 3. Obtain the DID document for the input DID by executing the Read + // operation against the input DID's verifiable data registry. + let doc: MetaDocument = match method.read(&did, input).await? { + Some(doc) => doc, + None => return Ok(context.finish_error(ErrorKind::NotFound)), + }; + + // 4. Validate that the output DID document conforms to a conformant + // serialization of the DID document data model. + // if did.method() != doc.data.id.method() || did.method_id() != doc.data.id.method_id() { + // return Ok(context.finish_error(ErrorKind::InvalidDID)); + // } + + // TODO: Handle deactivated DIDs + // TODO: Handle signature verification + + context.set_document(doc.data); + context.set_metadata(doc.meta); + context.set_resolved(did); + + Ok(context.finish()) } /// Dereferences a DID URL into a primary or secondary resource. @@ -67,173 +67,173 @@ where /// [SPEC]: https://www.w3.org/TR/did-core/#did-url-dereferencing pub async fn dereference(did: &str, input: InputMetadata, method: R) -> Result where - R: ResolverMethod, + R: ResolverMethod, { - // Create the context immediately, for accurate durations. - let mut context: DerefContext = DerefContext::new(); - - // 1. Obtain the DID document for the input DID by executing the DID - // resolution algorithm. - let resolution: Resolution = resolve(did, input, method).await?; - - // If the resolution result contains an error, bail early. - if let Some(error) = resolution.metadata.error { - return Ok(context.finish_error(error)); - } - - // Extract the document and metadata - Both properties MUST exist as we - // checked for resolution errors above. - let (document, metadata): (Document, DocumentMetadata) = resolution - .document - .zip(resolution.document_metadata) - .ok_or_else(|| Error::DereferenceError(anyhow!("Missing Document/Metadata")))?; - - // Extract the parsed DID from the resolution output - It MUST exist as we - // checked for resolution errors above. - let did: DID = resolution - .metadata - .resolved - .ok_or_else(|| Error::DereferenceError(anyhow!("Missing Resolved DID")))?; - - // Add the resolution document metadata to the response. - context.set_metadata(metadata); - - // 2. Execute the algorithm for Dereferencing the Primary Resource. - let primary: PrimaryResource = match dereference_primary(document, did.clone())? { - Some(primary) => primary, - None => return Ok(context.finish_error(ErrorKind::NotFound)), - }; - - // 3. If the original input DID URL contained a DID fragment, execute the - // algorithm for Dereferencing the Secondary Resource. - if let Some(fragment) = did.fragment() { - // - // Dereferencing the Secondary Resource - // - match primary { - // 1. If the result is a resolved DID document. - PrimaryResource::Document(inner) => { - // 1.1 From the resolved DID document, select the JSON object whose id - // property matches the input DID URL. - if let Some(resource) = dereference_document(inner, fragment)? { - // 1.2. Return the output resource. - context.set_content(resource); - } - } - // 2. Otherwise, if the result is an output service endpoint URL. - PrimaryResource::Service(mut inner) => { - // 2.1. Append the DID fragment to the output service endpoint URL. - inner.set_fragment(Some(fragment)); - - // 2.2. Return the output service endpoint URL. - context.set_content(PrimaryResource::Service(inner)); - } + // Create the context immediately, for accurate durations. + let mut context: DerefContext = DerefContext::new(); + + // 1. Obtain the DID document for the input DID by executing the DID + // resolution algorithm. + let resolution: Resolution = resolve(did, input, method).await?; + + // If the resolution result contains an error, bail early. + if let Some(error) = resolution.metadata.error { + return Ok(context.finish_error(error)); + } + + // Extract the document and metadata - Both properties MUST exist as we + // checked for resolution errors above. + let (document, metadata): (Document, DocumentMetadata) = resolution + .document + .zip(resolution.document_metadata) + .ok_or_else(|| Error::DereferenceError(anyhow!("Missing Document/Metadata")))?; + + // Extract the parsed DID from the resolution output - It MUST exist as we + // checked for resolution errors above. + let did: DID = resolution + .metadata + .resolved + .ok_or_else(|| Error::DereferenceError(anyhow!("Missing Resolved DID")))?; + + // Add the resolution document metadata to the response. + context.set_metadata(metadata); + + // 2. Execute the algorithm for Dereferencing the Primary Resource. + let primary: PrimaryResource = match dereference_primary(document, did.clone())? { + Some(primary) => primary, + None => return Ok(context.finish_error(ErrorKind::NotFound)), + }; + + // 3. If the original input DID URL contained a DID fragment, execute the + // algorithm for Dereferencing the Secondary Resource. + if let Some(fragment) = did.fragment() { + // + // Dereferencing the Secondary Resource + // + match primary { + // 1. If the result is a resolved DID document. + PrimaryResource::Document(inner) => { + // 1.1 From the resolved DID document, select the JSON object whose id + // property matches the input DID URL. + if let Some(resource) = dereference_document(inner, fragment)? { + // 1.2. Return the output resource. + context.set_content(resource); } - } else { - context.set_content(primary); + } + // 2. Otherwise, if the result is an output service endpoint URL. + PrimaryResource::Service(mut inner) => { + // 2.1. Append the DID fragment to the output service endpoint URL. + inner.set_fragment(Some(fragment)); + + // 2.2. Return the output service endpoint URL. + context.set_content(PrimaryResource::Service(inner)); + } } + } else { + context.set_content(primary); + } - Ok(context.finish()) + Ok(context.finish()) } #[derive(Debug)] struct ResolveContext(Resolution, Instant); impl ResolveContext { - fn new() -> Self { - Self(Resolution::new(), Instant::now()) - } - - fn set_document(&mut self, value: Document) { - self.0.document = Some(value); - } - - fn set_metadata(&mut self, value: DocumentMetadata) { - self.0.document_metadata = Some(value); - } - - fn set_resolved(&mut self, value: DID) { - self.0.metadata.resolved = Some(value); - } - - fn set_error(&mut self, value: ErrorKind) { - self.0.metadata.error = Some(value); - } - - fn finish_error(mut self, value: ErrorKind) -> Resolution { - self.set_error(value); - self.finish() - } - - fn finish(mut self) -> Resolution { - self.0.metadata.duration = self.1.elapsed(); - self.0 - } + fn new() -> Self { + Self(Resolution::new(), Instant::now()) + } + + fn set_document(&mut self, value: Document) { + self.0.document = Some(value); + } + + fn set_metadata(&mut self, value: DocumentMetadata) { + self.0.document_metadata = Some(value); + } + + fn set_resolved(&mut self, value: DID) { + self.0.metadata.resolved = Some(value); + } + + fn set_error(&mut self, value: ErrorKind) { + self.0.metadata.error = Some(value); + } + + fn finish_error(mut self, value: ErrorKind) -> Resolution { + self.set_error(value); + self.finish() + } + + fn finish(mut self) -> Resolution { + self.0.metadata.duration = self.1.elapsed(); + self.0 + } } #[derive(Debug)] struct DerefContext(Dereference, Instant); impl DerefContext { - fn new() -> Self { - Self(Dereference::new(), Instant::now()) - } - - fn set_content(&mut self, value: impl Into) { - self.0.content = Some(value.into()); - } - - fn set_metadata(&mut self, value: DocumentMetadata) { - self.0.content_metadata = Some(value); - } - - fn set_error(&mut self, value: ErrorKind) { - self.0.metadata.error = Some(value); - } - - fn finish_error(mut self, value: ErrorKind) -> Dereference { - self.set_error(value); - self.finish() - } - - fn finish(mut self) -> Dereference { - self.0.metadata.duration = self.1.elapsed(); - self.0 - } + fn new() -> Self { + Self(Dereference::new(), Instant::now()) + } + + fn set_content(&mut self, value: impl Into) { + self.0.content = Some(value.into()); + } + + fn set_metadata(&mut self, value: DocumentMetadata) { + self.0.content_metadata = Some(value); + } + + fn set_error(&mut self, value: ErrorKind) { + self.0.metadata.error = Some(value); + } + + fn finish_error(mut self, value: ErrorKind) -> Dereference { + self.set_error(value); + self.finish() + } + + fn finish(mut self) -> Dereference { + self.0.metadata.duration = self.1.elapsed(); + self.0 + } } fn dereference_primary(document: Document, mut did: DID) -> Result> { - // Remove the DID fragment from the input DID URL. - did.set_fragment(None); - - // 1. If the input DID URL contains the DID parameter service... - if let Some((_, target)) = did.query_pairs().find(|(key, _)| key == "service") { - // 1.1. From the resolved DID document, select the service endpoint whose - // id property contains a fragment which matches the value of the - // service DID parameter of the input DID URL. - document - .service() - .iter() - .find(|service| matches!(service.id().fragment(), Some(fragment) if fragment == target)) - .map(|service| service.service_endpoint()) - // 1.2. Execute the Service Endpoint Construction algorithm. - .map(|url| service_endpoint_ctor(did, url)) - .transpose()? - // 1.3. Return the output service endpoint URL. - .map(Into::into) - .map(Ok) - .transpose() - // 3. Otherwise, if the input DID URL contains no DID path and no DID query. - } else if did.path().is_empty() && did.query().is_none() { - // 3.1. Return the resolved DID document. - Ok(Some(document.into())) - } else { - todo!("Handle Method-Specific Dereference") - } + // Remove the DID fragment from the input DID URL. + did.set_fragment(None); + + // 1. If the input DID URL contains the DID parameter service... + if let Some((_, target)) = did.query_pairs().find(|(key, _)| key == "service") { + // 1.1. From the resolved DID document, select the service endpoint whose + // id property contains a fragment which matches the value of the + // service DID parameter of the input DID URL. + document + .service() + .iter() + .find(|service| matches!(service.id().fragment(), Some(fragment) if fragment == target)) + .map(|service| service.service_endpoint()) + // 1.2. Execute the Service Endpoint Construction algorithm. + .map(|url| service_endpoint_ctor(did, url)) + .transpose()? + // 1.3. Return the output service endpoint URL. + .map(Into::into) + .map(Ok) + .transpose() + // 3. Otherwise, if the input DID URL contains no DID path and no DID query. + } else if did.path().is_empty() && did.query().is_none() { + // 3.1. Return the resolved DID document. + Ok(Some(document.into())) + } else { + todo!("Handle Method-Specific Dereference") + } } fn dereference_document(document: Document, fragment: &str) -> Result> { - macro_rules! extract { + macro_rules! extract { ($base:expr, $target:expr, $iter:expr) => { for object in $iter.iter() { let did: DID = $base.join(object.id())?; @@ -245,86 +245,86 @@ fn dereference_document(document: Document, fragment: &str) -> Result Result { - // The input DID URL and input service endpoint URL MUST NOT both have a - // query component. - if did.query().is_some() && url.query().is_some() { - return Err(Error::DereferenceError(anyhow!("Multiple DID Queries"))); + // The input DID URL and input service endpoint URL MUST NOT both have a + // query component. + if did.query().is_some() && url.query().is_some() { + return Err(Error::DereferenceError(anyhow!("Multiple DID Queries"))); + } + + // The input DID URL and input service endpoint URL MUST NOT both have a + // fragment component. + if did.fragment().is_some() && url.fragment().is_some() { + return Err(Error::DereferenceError(anyhow!("Multiple DID Fragments"))); + } + + // The input service endpoint URL MUST be an HTTP(S) URL. + if url.scheme() != "https" { + return Err(Error::DereferenceError(anyhow!("Invalid Service Protocol"))); + } + + // 1. Initialize a string output service endpoint URL to the value of + // the input service endpoint URL. + let mut output: Url = url.clone(); + + // 2. If the output service endpoint URL has a query component, remove it. + output.set_query(None); + + // 3. If the output service endpoint URL has a fragment component, remove it. + output.set_fragment(None); + + // Decode and join the `relative-ref` query param, if it exists. + if let Some((_, relative)) = did.query_pairs().find(|(key, _)| key == "relative-ref") { + output = output.join(&relative)?; + } + + // 4. Append the path component of the input DID URL to the output + // service endpoint URL. + output + .path_segments_mut() + .unwrap() + .pop_if_empty() + .extend(did.path().split('/')); + + // 5. If the input service endpoint URL has a query component, append ? + // plus the query to the output service endpoint URL. + // 6. If the input DID URL has a query component, append ? plus the + // query to the output service endpoint URL. + match (did.query(), url.query()) { + (Some(_), None) => { + output.query_pairs_mut().extend_pairs(did.query_pairs()); } - - // The input DID URL and input service endpoint URL MUST NOT both have a - // fragment component. - if did.fragment().is_some() && url.fragment().is_some() { - return Err(Error::DereferenceError(anyhow!("Multiple DID Fragments"))); + (None, Some(_)) => { + output.query_pairs_mut().extend_pairs(url.query_pairs()); } - - // The input service endpoint URL MUST be an HTTP(S) URL. - if url.scheme() != "https" { - return Err(Error::DereferenceError(anyhow!("Invalid Service Protocol"))); - } - - // 1. Initialize a string output service endpoint URL to the value of - // the input service endpoint URL. - let mut output: Url = url.clone(); - - // 2. If the output service endpoint URL has a query component, remove it. - output.set_query(None); - - // 3. If the output service endpoint URL has a fragment component, remove it. - output.set_fragment(None); - - // Decode and join the `relative-ref` query param, if it exists. - if let Some((_, relative)) = did.query_pairs().find(|(key, _)| key == "relative-ref") { - output = output.join(&relative)?; - } - - // 4. Append the path component of the input DID URL to the output - // service endpoint URL. - output - .path_segments_mut() - .unwrap() - .pop_if_empty() - .extend(did.path().split('/')); - - // 5. If the input service endpoint URL has a query component, append ? - // plus the query to the output service endpoint URL. - // 6. If the input DID URL has a query component, append ? plus the - // query to the output service endpoint URL. - match (did.query(), url.query()) { - (Some(_), None) => { - output.query_pairs_mut().extend_pairs(did.query_pairs()); - } - (None, Some(_)) => { - output.query_pairs_mut().extend_pairs(url.query_pairs()); - } - (Some(_), Some(_)) => unreachable!(), - (None, None) => {} - } - - // 7. If the input service endpoint URL has a fragment component, append - // # plus the fragment to the output service endpoint URL. - // 8. If the input DID URL has a fragment component, append # plus the - // fragment to the output service endpoint URL. - match (did.fragment(), url.fragment()) { - (fragment @ Some(_), None) | (None, fragment @ Some(_)) => output.set_fragment(fragment), - (Some(_), Some(_)) => unreachable!(), - (None, None) => {} - } - - // 9. Return the output service endpoint URL. - Ok(output) + (Some(_), Some(_)) => unreachable!(), + (None, None) => {} + } + + // 7. If the input service endpoint URL has a fragment component, append + // # plus the fragment to the output service endpoint URL. + // 8. If the input DID URL has a fragment component, append # plus the + // fragment to the output service endpoint URL. + match (did.fragment(), url.fragment()) { + (fragment @ Some(_), None) | (None, fragment @ Some(_)) => output.set_fragment(fragment), + (Some(_), Some(_)) => unreachable!(), + (None, None) => {} + } + + // 9. Return the output service endpoint URL. + Ok(output) } diff --git a/identity-core/src/resolver/input_metadata.rs b/identity-core/src/resolver/input_metadata.rs index 582a94b9be..edc4be6334 100644 --- a/identity-core/src/resolver/input_metadata.rs +++ b/identity-core/src/resolver/input_metadata.rs @@ -19,24 +19,24 @@ pub const MIME_DID_LD: &str = "application/did+ld+json"; /// [SPEC]: https://www.w3.org/TR/did-core/#dfn-did-resolution #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct InputMetadata { - /// The MIME type of the preferred representation of the DID document. - /// - /// Note: This is only relevant when using stream-based resolution. - /// - /// [More Info](https://www.w3.org/TR/did-spec-registries/#accept) - #[serde(skip_serializing_if = "Option::is_none")] - pub accept: Option, - /// Additional input metadata properties. - #[serde(flatten)] - pub properties: Object, + /// The MIME type of the preferred representation of the DID document. + /// + /// Note: This is only relevant when using stream-based resolution. + /// + /// [More Info](https://www.w3.org/TR/did-spec-registries/#accept) + #[serde(skip_serializing_if = "Option::is_none")] + pub accept: Option, + /// Additional input metadata properties. + #[serde(flatten)] + pub properties: Object, } impl InputMetadata { - /// Creates a new [`InputMetadata`]. - pub fn new() -> Self { - Self { - accept: None, - properties: Object::new(), - } + /// Creates a new [`InputMetadata`]. + pub fn new() -> Self { + Self { + accept: None, + properties: Object::new(), } + } } diff --git a/identity-core/src/resolver/resolution.rs b/identity-core/src/resolver/resolution.rs index 4d805a980c..8c32a45c27 100644 --- a/identity-core/src/resolver/resolution.rs +++ b/identity-core/src/resolver/resolution.rs @@ -11,24 +11,24 @@ use crate::resolver::{DocumentMetadata, ResolutionMetadata}; /// [SPEC]: https://www.w3.org/TR/did-core/#dfn-did-resolution #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct Resolution { - /// Resolution-specific metadata. - #[serde(rename = "did-resolution-metadata")] - pub metadata: ResolutionMetadata, - /// The DID Document of a successful resolution. - #[serde(rename = "did-document", skip_serializing_if = "Option::is_none")] - pub document: Option, - /// Document-specific metadata. - #[serde(rename = "did-document-metadata", skip_serializing_if = "Option::is_none")] - pub document_metadata: Option, + /// Resolution-specific metadata. + #[serde(rename = "did-resolution-metadata")] + pub metadata: ResolutionMetadata, + /// The DID Document of a successful resolution. + #[serde(rename = "did-document", skip_serializing_if = "Option::is_none")] + pub document: Option, + /// Document-specific metadata. + #[serde(rename = "did-document-metadata", skip_serializing_if = "Option::is_none")] + pub document_metadata: Option, } impl Resolution { - /// Creates a new [`Resolution`]. - pub fn new() -> Self { - Self { - metadata: ResolutionMetadata::new(), - document: None, - document_metadata: None, - } + /// Creates a new [`Resolution`]. + pub fn new() -> Self { + Self { + metadata: ResolutionMetadata::new(), + document: None, + document_metadata: None, } + } } diff --git a/identity-core/src/resolver/resolution_metadata.rs b/identity-core/src/resolver/resolution_metadata.rs index 9a3662cdca..75400e4086 100644 --- a/identity-core/src/resolver/resolution_metadata.rs +++ b/identity-core/src/resolver/resolution_metadata.rs @@ -12,37 +12,37 @@ use crate::{common::Object, resolver::ErrorKind}; /// [SPEC]: https://www.w3.org/TR/did-core/#dfn-did-resolution #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct ResolutionMetadata { - /// The error code from the resolution process, if an error occurred. - /// - /// [More Info](https://www.w3.org/TR/did-spec-registries/#error) - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// The MIME type of the returned document stream. - /// - /// Note: This is only relevant when using stream-based resolution. - /// - /// [More Info](https://www.w3.org/TR/did-spec-registries/#content-type) - #[serde(skip_serializing_if = "Option::is_none")] - pub content_type: Option, - /// The elapsed time of the resolution operation. - pub duration: Duration, - /// The parsed DID that was used for resolution. - #[serde(skip_serializing_if = "Option::is_none")] - pub resolved: Option, - /// Additional resolution metadata properties. - #[serde(flatten)] - pub properties: Object, + /// The error code from the resolution process, if an error occurred. + /// + /// [More Info](https://www.w3.org/TR/did-spec-registries/#error) + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// The MIME type of the returned document stream. + /// + /// Note: This is only relevant when using stream-based resolution. + /// + /// [More Info](https://www.w3.org/TR/did-spec-registries/#content-type) + #[serde(skip_serializing_if = "Option::is_none")] + pub content_type: Option, + /// The elapsed time of the resolution operation. + pub duration: Duration, + /// The parsed DID that was used for resolution. + #[serde(skip_serializing_if = "Option::is_none")] + pub resolved: Option, + /// Additional resolution metadata properties. + #[serde(flatten)] + pub properties: Object, } impl ResolutionMetadata { - /// Creates a new [`ResolutionMetadata`]. - pub fn new() -> Self { - Self { - error: None, - content_type: None, - duration: Duration::from_secs(0), - resolved: None, - properties: Object::new(), - } + /// Creates a new [`ResolutionMetadata`]. + pub fn new() -> Self { + Self { + error: None, + content_type: None, + duration: Duration::from_secs(0), + resolved: None, + properties: Object::new(), } + } } diff --git a/identity-core/src/resolver/resource.rs b/identity-core/src/resolver/resource.rs index eb0d877f41..67d039f527 100644 --- a/identity-core/src/resolver/resource.rs +++ b/identity-core/src/resolver/resource.rs @@ -10,22 +10,22 @@ use serde::{Deserialize, Serialize}; /// [SPEC]: https://www.w3.org/TR/did-core/#dfn-did-url-dereferencing #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum Resource { - /// A dereferenced primary resource. - Primary(PrimaryResource), - /// A dereferenced secondary resource. - Secondary(SecondaryResource), + /// A dereferenced primary resource. + Primary(PrimaryResource), + /// A dereferenced secondary resource. + Secondary(SecondaryResource), } impl From for Resource { - fn from(other: PrimaryResource) -> Self { - Self::Primary(other) - } + fn from(other: PrimaryResource) -> Self { + Self::Primary(other) + } } impl From for Resource { - fn from(other: SecondaryResource) -> Self { - Self::Secondary(other) - } + fn from(other: SecondaryResource) -> Self { + Self::Secondary(other) + } } /// A primary resource returned from a [DID URL dereferencing][SPEC] process. @@ -34,22 +34,22 @@ impl From for Resource { #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] #[serde(untagged)] pub enum PrimaryResource { - /// A dereferenced DID Document. - Document(Document), - /// A dereferenced DID Document service endpoint. - Service(Url), + /// A dereferenced DID Document. + Document(Document), + /// A dereferenced DID Document service endpoint. + Service(Url), } impl From for PrimaryResource { - fn from(other: Document) -> Self { - Self::Document(other) - } + fn from(other: Document) -> Self { + Self::Document(other) + } } impl From for PrimaryResource { - fn from(other: Url) -> Self { - Self::Service(other) - } + fn from(other: Url) -> Self { + Self::Service(other) + } } /// A secondary resource returned from a [DID URL dereferencing][SPEC] process. @@ -59,49 +59,49 @@ impl From for PrimaryResource { #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] #[serde(untagged)] pub enum SecondaryResource { - /// A DID Document Method Id. - VerificationDID(DID), - /// A DID Document Verification Method. - VerificationKey(Method), - /// A DID Document Service. - Service(Service), + /// A DID Document Method Id. + VerificationDID(DID), + /// A DID Document Verification Method. + VerificationKey(Method), + /// A DID Document Service. + Service(Service), } impl From for SecondaryResource { - fn from(other: DID) -> Self { - Self::VerificationDID(other) - } + fn from(other: DID) -> Self { + Self::VerificationDID(other) + } } impl From for SecondaryResource { - fn from(other: Method) -> Self { - Self::VerificationKey(other) - } + fn from(other: Method) -> Self { + Self::VerificationKey(other) + } } impl From for SecondaryResource { - fn from(other: MethodRef) -> Self { - match other { - MethodRef::Refer(inner) => inner.into(), - MethodRef::Embed(inner) => inner.into(), - } + fn from(other: MethodRef) -> Self { + match other { + MethodRef::Refer(inner) => inner.into(), + MethodRef::Embed(inner) => inner.into(), } + } } impl From> for SecondaryResource { - fn from(other: DIDKey) -> Self { - other.into_inner().into() - } + fn from(other: DIDKey) -> Self { + other.into_inner().into() + } } impl From> for SecondaryResource { - fn from(other: DIDKey) -> Self { - other.into_inner().into() - } + fn from(other: DIDKey) -> Self { + other.into_inner().into() + } } impl From> for SecondaryResource { - fn from(other: DIDKey) -> Self { - Self::Service(other.into_inner()) - } + fn from(other: DIDKey) -> Self { + Self::Service(other.into_inner()) + } } diff --git a/identity-core/src/resolver/traits.rs b/identity-core/src/resolver/traits.rs index 73fea7520c..f18075dd94 100644 --- a/identity-core/src/resolver/traits.rs +++ b/identity-core/src/resolver/traits.rs @@ -7,39 +7,39 @@ use did_url::DID; use serde::{Deserialize, Serialize}; use crate::{ - error::Result, - resolver::{DocumentMetadata, InputMetadata}, + error::Result, + resolver::{DocumentMetadata, InputMetadata}, }; /// A resolved [`Document`] and associated [`DocumentMetadata`]. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct MetaDocument { - /// A resolved DID Document. - pub data: Document, - /// Information regarding the associated Documents resolution process. - pub meta: DocumentMetadata, + /// A resolved DID Document. + pub data: Document, + /// Information regarding the associated Documents resolution process. + pub meta: DocumentMetadata, } /// A trait for generic DID Resolvers. #[async_trait(?Send)] pub trait ResolverMethod { - /// Returns `true` if the given `did` is supported by this DID Resolver. - fn is_supported(&self, did: &DID) -> bool; + /// Returns `true` if the given `did` is supported by this DID Resolver. + fn is_supported(&self, did: &DID) -> bool; - /// Performs the "Read" operation of the DID method. - async fn read(&self, did: &DID, input: InputMetadata) -> Result>; + /// Performs the "Read" operation of the DID method. + async fn read(&self, did: &DID, input: InputMetadata) -> Result>; } #[async_trait(?Send)] impl ResolverMethod for &'_ T where - T: ResolverMethod + Send + Sync, + T: ResolverMethod + Send + Sync, { - fn is_supported(&self, did: &DID) -> bool { - (**self).is_supported(did) - } + fn is_supported(&self, did: &DID) -> bool { + (**self).is_supported(did) + } - async fn read(&self, did: &DID, input: InputMetadata) -> Result> { - (**self).read(did, input).await - } + async fn read(&self, did: &DID, input: InputMetadata) -> Result> { + (**self).read(did, input).await + } } diff --git a/identity-core/src/utils/base58.rs b/identity-core/src/utils/base58.rs index c28a73ac82..e719cda0ac 100644 --- a/identity-core/src/utils/base58.rs +++ b/identity-core/src/utils/base58.rs @@ -6,18 +6,18 @@ use crate::error::{Error, Result}; /// Decodes the given `data` as base58-btc. pub fn decode_b58(data: &T) -> Result> where - T: AsRef<[u8]> + ?Sized, + T: AsRef<[u8]> + ?Sized, { - bs58::decode(data) - .with_alphabet(bs58::Alphabet::BITCOIN) - .into_vec() - .map_err(Error::DecodeBase58) + bs58::decode(data) + .with_alphabet(bs58::Alphabet::BITCOIN) + .into_vec() + .map_err(Error::DecodeBase58) } /// Encodes the given `data` as base58-btc. pub fn encode_b58(data: &T) -> String where - T: AsRef<[u8]> + ?Sized, + T: AsRef<[u8]> + ?Sized, { - bs58::encode(data).with_alphabet(bs58::Alphabet::BITCOIN).into_string() + bs58::encode(data).with_alphabet(bs58::Alphabet::BITCOIN).into_string() } diff --git a/identity-core/src/utils/jcs_sha256.rs b/identity-core/src/utils/jcs_sha256.rs index de464e6035..5f59845bd9 100644 --- a/identity-core/src/utils/jcs_sha256.rs +++ b/identity-core/src/utils/jcs_sha256.rs @@ -9,7 +9,7 @@ use crate::{convert::ToJson, error::Result}; /// hashed using SHA-256. pub fn jcs_sha256(data: &T) -> Result> where - T: ToJson, + T: ToJson, { - data.to_jcs().map(|json| Sha256::digest(&json)) + data.to_jcs().map(|json| Sha256::digest(&json)) } diff --git a/identity-diff/derive/src/impls.rs b/identity-diff/derive/src/impls.rs index 87fd42c886..0f33a05b6b 100644 --- a/identity-diff/derive/src/impls.rs +++ b/identity-diff/derive/src/impls.rs @@ -5,6 +5,6 @@ mod enums; mod structs; pub use crate::impls::{ - enums::{derive_diff_enum, impl_debug_enum, impl_diff_enum}, - structs::{debug_impl, derive_diff_struct, diff_impl, impl_from_into}, + enums::{derive_diff_enum, impl_debug_enum, impl_diff_enum}, + structs::{debug_impl, derive_diff_struct, diff_impl, impl_from_into}, }; diff --git a/identity-diff/derive/src/impls/enums.rs b/identity-diff/derive/src/impls/enums.rs index d049531a47..8b56dc5648 100644 --- a/identity-diff/derive/src/impls/enums.rs +++ b/identity-diff/derive/src/impls/enums.rs @@ -10,902 +10,902 @@ use syn::{punctuated::Punctuated, token::Comma, GenericParam, Type, WhereClause} /// derive a Diff type Enum from an incoming `InputModel`. pub fn derive_diff_enum(input: &InputModel) -> TokenStream { - // collect appropriate data and generate param declarations. - let diff: &Ident = input.diff(); - let evariants: &Vec = input.e_variants(); + // collect appropriate data and generate param declarations. + let diff: &Ident = input.diff(); + let evariants: &Vec = input.e_variants(); - let serde_attrs = if input.from_into() { - let name = input.name(); - let stype = quote!(#name).to_string(); + let serde_attrs = if input.from_into() { + let name = input.name(); + let stype = quote!(#name).to_string(); - quote! { - #[serde(from=#stype, into=#stype)] - } - } else { - quote! {} - }; - - let param_decls: &Punctuated = input.param_decls(); - - let clause = quote! {}; + quote! { + #[serde(from=#stype, into=#stype)] + } + } else { + quote! {} + }; - let param_decls: Vec = param_decls - .iter() - .map(|p| match p { - GenericParam::Lifetime(life) => quote! { #life }, - GenericParam::Const(cp) => quote! { #cp }, - GenericParam::Type(typ) => { - let S: &Ident = &typ.ident; + let param_decls: &Punctuated = input.param_decls(); - let bounds: Vec = typ.bounds.iter().map(|bound| quote! { #bound }).collect(); + let clause = quote! {}; - quote! { - #S: identity_diff::Diff - #(+ #bounds)* - } - } - }) - .collect(); + let param_decls: Vec = param_decls + .iter() + .map(|p| match p { + GenericParam::Lifetime(life) => quote! { #life }, + GenericParam::Const(cp) => quote! { #cp }, + GenericParam::Type(typ) => { + let S: &Ident = &typ.ident; - // create the Diff enum body. - let body: TokenStream = evariants - .iter() - .map(|var| { - let vname = &var.name; - let typs: Vec = var.fields.iter().map(|f| f.typ_as_tokens()).collect(); - - match var.variant { - // for named variant. - SVariant::Named => { - let fnames: Vec<&Ident> = var.fields.iter().map(|f| f.name()).collect(); - - // generate code. - quote! { - #vname { - #( - #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] #fnames: #typs, - )* - }, - } - } - // generate code for tuple variant. - SVariant::Tuple => quote! { - #vname( #( #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] #typs, )* ), - }, - // generate code for unit variant. - SVariant::Unit => quote! { - #vname, - }, - } - }) - .collect(); + let bounds: Vec = typ.bounds.iter().map(|bound| quote! { #bound }).collect(); - // create the main code and insert into the body. - quote! { - #[derive(Clone, PartialEq)] - #[derive(serde::Deserialize, serde::Serialize)] - #serde_attrs - pub enum #diff<#(#param_decls),*> - #clause - { - #body - } - } + quote! { + #S: identity_diff::Diff + #(+ #bounds)* + } + } + }) + .collect(); + + // create the Diff enum body. + let body: TokenStream = evariants + .iter() + .map(|var| { + let vname = &var.name; + let typs: Vec = var.fields.iter().map(|f| f.typ_as_tokens()).collect(); + + match var.variant { + // for named variant. + SVariant::Named => { + let fnames: Vec<&Ident> = var.fields.iter().map(|f| f.name()).collect(); + + // generate code. + quote! { + #vname { + #( + #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] #fnames: #typs, + )* + }, + } + } + // generate code for tuple variant. + SVariant::Tuple => quote! { + #vname( #( #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] #typs, )* ), + }, + // generate code for unit variant. + SVariant::Unit => quote! { + #vname, + }, + } + }) + .collect(); + + // create the main code and insert into the body. + quote! { + #[derive(Clone, PartialEq)] + #[derive(serde::Deserialize, serde::Serialize)] + #serde_attrs + pub enum #diff<#(#param_decls),*> + #clause + { + #body + } + } } // implement Debug on the Enum from the `InputModel`. pub fn impl_debug_enum(input: &InputModel) -> TokenStream { - // collect appropriate data and generate param declarations. - let diff: &Ident = input.diff(); - let evariants: &Vec = input.e_variants(); - - let param_decls: &Punctuated = input.param_decls(); - let params: &Punctuated = input.params(); + // collect appropriate data and generate param declarations. + let diff: &Ident = input.diff(); + let evariants: &Vec = input.e_variants(); - let param_decls: Vec = param_decls - .iter() - .map(|p| match p { - GenericParam::Lifetime(life) => quote! { #life }, - GenericParam::Const(cp) => quote! { #cp }, - GenericParam::Type(typ) => { - let S: &Ident = &typ.ident; - - let bounds: Vec = typ.bounds.iter().map(|tb| quote! { #tb }).collect(); - - quote! { - #S: identity_diff::Diff - + std::fmt::Debug - #(+ #bounds)* - } - } - }) - .collect(); + let param_decls: &Punctuated = input.param_decls(); + let params: &Punctuated = input.params(); - // create where clause from predicates. - let clause: &WhereClause = input.clause(); - let predicates: Vec = clause.predicates.iter().map(|pred| quote! { #pred }).collect(); - let clause = quote! { where #(#predicates),*}; + let param_decls: Vec = param_decls + .iter() + .map(|p| match p { + GenericParam::Lifetime(life) => quote! { #life }, + GenericParam::Const(cp) => quote! { #cp }, + GenericParam::Type(typ) => { + let S: &Ident = &typ.ident; - // get patterns and bodies. - let (patterns, bodies) = parse_evariants(evariants, diff); + let bounds: Vec = typ.bounds.iter().map(|tb| quote! { #tb }).collect(); - // create a body. - let body = quote! { - match self { - #( - #patterns => #bodies, - )* + quote! { + #S: identity_diff::Diff + + std::fmt::Debug + #(+ #bounds)* } - }; - - // generate code. - quote! { - impl<#(#param_decls),*> std::fmt::Debug for #diff<#params> - #clause - { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result - { - #body - } - } - } + } + }) + .collect(); + + // create where clause from predicates. + let clause: &WhereClause = input.clause(); + let predicates: Vec = clause.predicates.iter().map(|pred| quote! { #pred }).collect(); + let clause = quote! { where #(#predicates),*}; + + // get patterns and bodies. + let (patterns, bodies) = parse_evariants(evariants, diff); + + // create a body. + let body = quote! { + match self { + #( + #patterns => #bodies, + )* + } + }; + + // generate code. + quote! { + impl<#(#param_decls),*> std::fmt::Debug for #diff<#params> + #clause + { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result + { + #body + } + } + } } /// derive the `Diff` trait for incoming Enum in `InputModel`. pub fn impl_diff_enum(input: &InputModel) -> TokenStream { - // collect appropriate data and generate param declarations. - let name: &Ident = input.name(); - let diff: &Ident = input.diff(); - let evariants: &Vec = input.e_variants(); - - let param_decls = input.param_decls(); - let params = input.params(); - - let clause: &WhereClause = input.clause(); - - let param_decls: Vec = param_decls - .iter() - .map(|p| match p { - GenericParam::Lifetime(life) => quote! { #life }, - GenericParam::Const(cp) => quote! { #cp }, - GenericParam::Type(typ) => { - let S: &Ident = &typ.ident; - - let bounds: Vec = typ.bounds.iter().map(|tb| quote! { #tb }).collect(); - - quote! { - #S: std::clone::Clone - + identity_diff::Diff - + std::fmt::Debug - + std::cmp::PartialEq - + for<'de> serde::Deserialize<'de> - + serde::Serialize - #(+ #bounds)* - } - } - }) - .collect(); + // collect appropriate data and generate param declarations. + let name: &Ident = input.name(); + let diff: &Ident = input.diff(); + let evariants: &Vec = input.e_variants(); - // create where clause from predicates. - let preds: Vec = clause.predicates.iter().map(|cl| quote! { #cl }).collect(); - let clause = quote! { where #(#preds),* }; + let param_decls = input.param_decls(); + let params = input.params(); - // setup vectors for merge, diff, into_diff and from_diff data. - let mut merge_rpatterns: Vec = vec![]; - let mut merge_lpatterns: Vec = vec![]; - let mut merge_bodies: Vec = vec![]; + let clause: &WhereClause = input.clause(); - let mut diff_rpatterns: Vec = vec![]; - let mut diff_lpatterns: Vec = vec![]; - let mut diff_bodies: Vec = vec![]; + let param_decls: Vec = param_decls + .iter() + .map(|p| match p { + GenericParam::Lifetime(life) => quote! { #life }, + GenericParam::Const(cp) => quote! { #cp }, + GenericParam::Type(typ) => { + let S: &Ident = &typ.ident; - let mut from_body: Vec = vec![]; + let bounds: Vec = typ.bounds.iter().map(|tb| quote! { #tb }).collect(); - let mut into_body: Vec = vec![]; - - // sort through each enum variant to generate data for each function. - evariants.iter().for_each(|var| { - let vname = &var.name; - let vfields = &var.fields; - let struct_type = var.variant.clone(); - - // get merge data. - let (mlp, mrp, mb) = parse_merge(vname, vfields, struct_type.clone()); - // get diff data. - let (dlp, drp, db) = parse_diff(vname, vfields, struct_type.clone()); + quote! { + #S: std::clone::Clone + + identity_diff::Diff + + std::fmt::Debug + + std::cmp::PartialEq + + for<'de> serde::Deserialize<'de> + + serde::Serialize + #(+ #bounds)* + } + } + }) + .collect(); + + // create where clause from predicates. + let preds: Vec = clause.predicates.iter().map(|cl| quote! { #cl }).collect(); + let clause = quote! { where #(#preds),* }; + + // setup vectors for merge, diff, into_diff and from_diff data. + let mut merge_rpatterns: Vec = vec![]; + let mut merge_lpatterns: Vec = vec![]; + let mut merge_bodies: Vec = vec![]; + + let mut diff_rpatterns: Vec = vec![]; + let mut diff_lpatterns: Vec = vec![]; + let mut diff_bodies: Vec = vec![]; + + let mut from_body: Vec = vec![]; + + let mut into_body: Vec = vec![]; + + // sort through each enum variant to generate data for each function. + evariants.iter().for_each(|var| { + let vname = &var.name; + let vfields = &var.fields; + let struct_type = var.variant.clone(); + + // get merge data. + let (mlp, mrp, mb) = parse_merge(vname, vfields, struct_type.clone()); + // get diff data. + let (dlp, drp, db) = parse_diff(vname, vfields, struct_type.clone()); + + // get the from and into data. + let (fb, ib) = parse_from_into(var, vname, vfields, diff, struct_type); + + merge_lpatterns.extend(mlp); + merge_rpatterns.extend(mrp); + merge_bodies.extend(mb); + + diff_lpatterns.extend(dlp); + diff_rpatterns.extend(drp); + diff_bodies.extend(db); + + from_body.extend(fb); + into_body.extend(ib); + }); + + // generate the code. + quote! { + impl<#(#param_decls),*> identity_diff::Diff for #name<#params> + #clause + { + type Type = #diff<#params>; + + #[allow(unused)] + fn merge(&self, diff: Self::Type) -> identity_diff::Result { + match(self, &diff) { + #( + (#merge_lpatterns, #merge_rpatterns) => { + #merge_bodies + }, + )* + } + } + + fn diff(&self, other: &Self) -> identity_diff::Result { + match (self, other) { + #( + (#diff_lpatterns, #diff_rpatterns) => { #diff_bodies }, + )* + } + } + + #[allow(unused)] + fn from_diff(diff: Self::Type) -> identity_diff::Result { + match diff { + #( + #from_body + )* + } + } + + #[allow(unused)] + fn into_diff(self) -> identity_diff::Result { + match self { + #( + #into_body + )* + } + } + } + } +} - // get the from and into data. - let (fb, ib) = parse_from_into(var, vname, vfields, diff, struct_type); +/// function that parses and sorts the variants into twp Vec types. +fn parse_evariants(evariants: &[EVariant], diff: &Ident) -> (Vec, Vec) { + // setup vectors for patterns and bodies. + let mut patterns: Vec = vec![]; + let mut bodies: Vec = vec![]; + + evariants + .iter() + .for_each(|var| match (var.variant.clone(), &var.name, &var.fields) { + // Named variants. + (SVariant::Named, vname, fields) => { + let fnames: Vec<&Ident> = fields.iter().map(|f| f.name()).collect(); + let buf: Ident = format_ident!("buf"); + + // format fields and create code. + let fields: Vec = fields + .iter() + .map(|f| { + let (fname, ftyp) = (f.name(), f.typ()); + + let str_name = format!("{}", fname); + + if f.should_ignore() { + quote! { + #buf.field(stringify!(#str_name), &#fname); + } + } else { + quote! { + if let Some(val) = &#fname { + #buf.field(#str_name, &val); + } else { + #buf.field(#str_name, &None as &Option<#ftyp>); + } + } + } + }) + .collect(); - merge_lpatterns.extend(mlp); - merge_rpatterns.extend(mrp); - merge_bodies.extend(mb); + // push into patterns and bodies. + patterns.push(quote! { + Self::#vname { #(#fnames),* } + }); + bodies.push(quote! {{ + let typ_name = String::new() + stringify!(#diff) + "::" + stringify!(#vname); + let mut #buf = f.debug_struct(&typ_name); + #( #fields )* + + #buf.finish() + }}); + } + // Tuple variants. + (SVariant::Tuple, vname, vfields) => { + // setup data. + let field_typs: Vec<&Type> = vfields.iter().map(|f| f.typ()).collect(); + + let field_max = field_typs.len(); + let field_names: Vec = (0..field_max).map(|ident| format_ident!("field_{}", ident)).collect(); + + let buf: Ident = format_ident!("buf"); + + let fields: Vec = vfields + .iter() + .enumerate() + .map(|(idx, fld)| { + let fname = format_ident!("field_{}", idx); + let ftyp = fld.typ(); + + match field_max { + 1 => quote! {}, + _ if fld.should_ignore() => quote! { + #buf.field(&#fname); + }, + _ => quote! { + if let Some(val) = #fname { + #buf.field(&val); + } else { + #buf.field(&None as &Option<#ftyp>); + } + }, + } + }) + .collect(); + // push into patterns and bodies + patterns.push(quote! { + Self::#vname( #(#field_names),* ) + }); + bodies.push(match field_max { + 1 => quote! {{ + let typ_name = String::new() + stringify!(#diff) + "::" + stringify!(#vname); + + if let Some(val) = &field_0 { + write!(f, "{}({:?})", typ_name, val) + } else { + let val = &None as &Option<()>; + write!(f, "{}({:?})", typ_name, val) + } + }}, + _ => quote! {{ + let typ_name = String::new() + stringify!(#diff) + "::" + stringify!(#vname); + let mut #buf = f.debug_tuple(&typ_name); + #( #fields )* + #buf.finish() + }}, + }); + } + // unit variant. + (SVariant::Unit, vname, _vfields) => { + patterns.push(quote! { + Self::#vname + }); + bodies.push(quote! {{ + let typ_name = String::new() + stringify!(#diff) + "::" + stringify!(#vname); + f.debug_struct(&typ_name).finish() + }}); + } + }); - diff_lpatterns.extend(dlp); - diff_rpatterns.extend(drp); - diff_bodies.extend(db); + // return patterns and bodies. + (patterns.to_vec(), bodies.to_vec()) +} - from_body.extend(fb); - into_body.extend(ib); - }); +// parse data to generate the merge functions. +fn parse_merge( + vname: &Ident, + vfields: &[DataFields], + struct_type: SVariant, +) -> (Vec, Vec, Vec) { + let mut merge_rpatterns: Vec = vec![]; + let mut merge_lpatterns: Vec = vec![]; + let mut merge_bodies: Vec = vec![]; - // generate the code. - quote! { - impl<#(#param_decls),*> identity_diff::Diff for #name<#params> - #clause - { - type Type = #diff<#params>; - - #[allow(unused)] - fn merge(&self, diff: Self::Type) -> identity_diff::Result { - match(self, &diff) { - #( - (#merge_lpatterns, #merge_rpatterns) => { - #merge_bodies - }, - )* - } - } + match struct_type { + // named variant. + SVariant::Named => { + // get field names. + let fnames: Vec<&Ident> = vfields.iter().map(|f| f.name()).collect(); - fn diff(&self, other: &Self) -> identity_diff::Result { - match (self, other) { - #( - (#diff_lpatterns, #diff_rpatterns) => { #diff_bodies }, - )* - } - } + let (left_names, right_names) = populate_field_names(vfields, 0, struct_type); - #[allow(unused)] - fn from_diff(diff: Self::Type) -> identity_diff::Result { - match diff { - #( - #from_body - )* + // setup merge code. + let merge_fvalues: Vec = vfields + .iter() + .zip(left_names.iter()) + .zip(right_names.iter()) + .map(|((f, ln), rn)| { + if f.should_ignore() { + quote! { #ln.clone() } + } else { + quote! { + if let Some(diff) = #rn { + #ln.merge(diff.clone())? + } else { + #ln.clone() } } + } + }) + .collect(); - #[allow(unused)] - fn into_diff(self) -> identity_diff::Result { - match self { - #( - #into_body - )* - } - } - } + // push merge code into vectors. + merge_lpatterns.push(quote! { + Self::#vname { + #(#fnames: #left_names),* + } + }); + + merge_rpatterns.push(quote! { + Self::Type::#vname { + #(#fnames: #right_names),* + } + }); + + merge_bodies.push(quote! { + Ok(Self::#vname { + #(#fnames: #merge_fvalues),* + }) + }); + + merge_lpatterns.push(quote! {_}); + + merge_rpatterns.push(quote! { + diff @Self::Type::#vname { .. } + }); + + merge_bodies.push(quote! { + Self::from_diff(diff.clone()) + }); + + ( + merge_lpatterns.to_vec(), + merge_rpatterns.to_vec(), + merge_bodies.to_vec(), + ) } -} + // tuple variants. + SVariant::Tuple => { + // get field names based on position. + let ftyps: Vec<&Type> = vfields.iter().map(|f| f.typ()).collect(); -/// function that parses and sorts the variants into twp Vec types. -fn parse_evariants(evariants: &[EVariant], diff: &Ident) -> (Vec, Vec) { - // setup vectors for patterns and bodies. - let mut patterns: Vec = vec![]; - let mut bodies: Vec = vec![]; + let fmax = ftyps.len(); + + let (left_names, right_names) = populate_field_names(vfields, fmax, struct_type); - evariants + // setup merge logic. + let merge_fvalues: Vec = vfields .iter() - .for_each(|var| match (var.variant.clone(), &var.name, &var.fields) { - // Named variants. - (SVariant::Named, vname, fields) => { - let fnames: Vec<&Ident> = fields.iter().map(|f| f.name()).collect(); - let buf: Ident = format_ident!("buf"); - - // format fields and create code. - let fields: Vec = fields - .iter() - .map(|f| { - let (fname, ftyp) = (f.name(), f.typ()); - - let str_name = format!("{}", fname); - - if f.should_ignore() { - quote! { - #buf.field(stringify!(#str_name), &#fname); - } - } else { - quote! { - if let Some(val) = &#fname { - #buf.field(#str_name, &val); - } else { - #buf.field(#str_name, &None as &Option<#ftyp>); - } - } - } - }) - .collect(); - - // push into patterns and bodies. - patterns.push(quote! { - Self::#vname { #(#fnames),* } - }); - bodies.push(quote! {{ - let typ_name = String::new() + stringify!(#diff) + "::" + stringify!(#vname); - let mut #buf = f.debug_struct(&typ_name); - #( #fields )* - - #buf.finish() - }}); - } - // Tuple variants. - (SVariant::Tuple, vname, vfields) => { - // setup data. - let field_typs: Vec<&Type> = vfields.iter().map(|f| f.typ()).collect(); - - let field_max = field_typs.len(); - let field_names: Vec = (0..field_max).map(|ident| format_ident!("field_{}", ident)).collect(); - - let buf: Ident = format_ident!("buf"); - - let fields: Vec = vfields - .iter() - .enumerate() - .map(|(idx, fld)| { - let fname = format_ident!("field_{}", idx); - let ftyp = fld.typ(); - - match field_max { - 1 => quote! {}, - _ if fld.should_ignore() => quote! { - #buf.field(&#fname); - }, - _ => quote! { - if let Some(val) = #fname { - #buf.field(&val); - } else { - #buf.field(&None as &Option<#ftyp>); - } - }, - } - }) - .collect(); - // push into patterns and bodies - patterns.push(quote! { - Self::#vname( #(#field_names),* ) - }); - bodies.push(match field_max { - 1 => quote! {{ - let typ_name = String::new() + stringify!(#diff) + "::" + stringify!(#vname); - - if let Some(val) = &field_0 { - write!(f, "{}({:?})", typ_name, val) - } else { - let val = &None as &Option<()>; - write!(f, "{}({:?})", typ_name, val) - } - }}, - _ => quote! {{ - let typ_name = String::new() + stringify!(#diff) + "::" + stringify!(#vname); - let mut #buf = f.debug_tuple(&typ_name); - #( #fields )* - #buf.finish() - }}, - }); - } - // unit variant. - (SVariant::Unit, vname, _vfields) => { - patterns.push(quote! { - Self::#vname - }); - bodies.push(quote! {{ - let typ_name = String::new() + stringify!(#diff) + "::" + stringify!(#vname); - f.debug_struct(&typ_name).finish() - }}); + .zip(left_names.iter()) + .zip(right_names.iter()) + .map(|((f, ln), rn)| { + if f.should_ignore() { + quote! { #ln.clone() } + } else { + quote! { + if let Some(diff) = #rn { + #ln.merge(diff.clone())? + } else { + #ln.clone() + } } - }); + } + }) + .collect(); - // return patterns and bodies. - (patterns.to_vec(), bodies.to_vec()) + // push into vectors. + merge_lpatterns.push(quote! { + Self::#vname( + #(#left_names),* + ) + }); + + merge_rpatterns.push(quote! { + Self::Type::#vname( + #(#right_names),* + ) + }); + + merge_bodies.push(quote! { + Ok(Self::#vname(#(#merge_fvalues),*)) + }); + + merge_lpatterns.push(quote! { _ }); + + merge_rpatterns.push(quote! { + diff @ Self::Type::#vname(..) + }); + + merge_bodies.push(quote! { + Self::from_diff(diff.clone()) + }); + ( + merge_lpatterns.to_vec(), + merge_rpatterns.to_vec(), + merge_bodies.to_vec(), + ) + } + // unit variants. + SVariant::Unit => { + // push into vectors. + merge_lpatterns.push(quote! { + Self::#vname + }); + + merge_rpatterns.push(quote! { + Self::Type::#vname + }); + + merge_bodies.push(quote! { + Ok(Self::#vname) + }); + + merge_lpatterns.push(quote! { _ }); + + merge_rpatterns.push(quote! { + diff @ Self::Type::#vname + }); + + merge_bodies.push(quote! { + Self::from_diff(diff.clone()) + }); + + // return generated code. + ( + merge_lpatterns.to_vec(), + merge_rpatterns.to_vec(), + merge_bodies.to_vec(), + ) + } + } } -// parse data to generate the merge functions. -fn parse_merge( - vname: &Ident, - vfields: &[DataFields], - struct_type: SVariant, +/// parses data for the derived diff function. +fn parse_diff( + vname: &Ident, + vfields: &[DataFields], + + struct_type: SVariant, ) -> (Vec, Vec, Vec) { - let mut merge_rpatterns: Vec = vec![]; - let mut merge_lpatterns: Vec = vec![]; - let mut merge_bodies: Vec = vec![]; + let mut diff_rpatterns: Vec = vec![]; + let mut diff_lpatterns: Vec = vec![]; + let mut diff_bodies: Vec = vec![]; - match struct_type { - // named variant. - SVariant::Named => { - // get field names. - let fnames: Vec<&Ident> = vfields.iter().map(|f| f.name()).collect(); - - let (left_names, right_names) = populate_field_names(vfields, 0, struct_type); - - // setup merge code. - let merge_fvalues: Vec = vfields - .iter() - .zip(left_names.iter()) - .zip(right_names.iter()) - .map(|((f, ln), rn)| { - if f.should_ignore() { - quote! { #ln.clone() } - } else { - quote! { - if let Some(diff) = #rn { - #ln.merge(diff.clone())? - } else { - #ln.clone() - } - } - } - }) - .collect(); + match struct_type { + // named variant. + SVariant::Named => { + let fnames: Vec<&Ident> = vfields.iter().map(|f| f.name()).collect(); - // push merge code into vectors. - merge_lpatterns.push(quote! { - Self::#vname { - #(#fnames: #left_names),* - } - }); + let (left_names, right_names) = populate_field_names(vfields, 0, struct_type); - merge_rpatterns.push(quote! { - Self::Type::#vname { - #(#fnames: #right_names),* + // setup diff logic. + let diff_fvalues: Vec = vfields + .iter() + .zip(left_names.iter()) + .zip(right_names.iter()) + .map(|((f, ln), rn)| { + if f.should_ignore() { + quote! { + Option::None + } + } else if f.is_option() { + quote! { + if #ln != #rn && *#ln != None && *#rn != None { + Some(#ln.diff(#rn)?) + } else { + None + } + } + } else { + quote! { + if #ln == #rn { + None + } else { + Some(#ln.diff(#rn)?) } - }); + } + } + }) + .collect(); - merge_bodies.push(quote! { - Ok(Self::#vname { - #(#fnames: #merge_fvalues),* - }) - }); + // push into vectors. + diff_lpatterns.push(quote! { + Self::#vname { #(#fnames: #left_names),* } + }); - merge_lpatterns.push(quote! {_}); + diff_rpatterns.push(quote! { + Self::#vname { #(#fnames: #right_names),* } + }); - merge_rpatterns.push(quote! { - diff @Self::Type::#vname { .. } - }); + diff_bodies.push(quote! { + Ok(Self::Type::#vname { + #(#fnames: #diff_fvalues),* + }) + }); - merge_bodies.push(quote! { - Self::from_diff(diff.clone()) - }); + diff_lpatterns.push(quote! { _ }); - ( - merge_lpatterns.to_vec(), - merge_rpatterns.to_vec(), - merge_bodies.to_vec(), - ) - } - // tuple variants. - SVariant::Tuple => { - // get field names based on position. - let ftyps: Vec<&Type> = vfields.iter().map(|f| f.typ()).collect(); - - let fmax = ftyps.len(); - - let (left_names, right_names) = populate_field_names(vfields, fmax, struct_type); - - // setup merge logic. - let merge_fvalues: Vec = vfields - .iter() - .zip(left_names.iter()) - .zip(right_names.iter()) - .map(|((f, ln), rn)| { - if f.should_ignore() { - quote! { #ln.clone() } - } else { - quote! { - if let Some(diff) = #rn { - #ln.merge(diff.clone())? - } else { - #ln.clone() - } - } - } - }) - .collect(); - - // push into vectors. - merge_lpatterns.push(quote! { - Self::#vname( - #(#left_names),* - ) - }); - - merge_rpatterns.push(quote! { - Self::Type::#vname( - #(#right_names),* - ) - }); - - merge_bodies.push(quote! { - Ok(Self::#vname(#(#merge_fvalues),*)) - }); - - merge_lpatterns.push(quote! { _ }); - - merge_rpatterns.push(quote! { - diff @ Self::Type::#vname(..) - }); - - merge_bodies.push(quote! { - Self::from_diff(diff.clone()) - }); - ( - merge_lpatterns.to_vec(), - merge_rpatterns.to_vec(), - merge_bodies.to_vec(), - ) - } - // unit variants. - SVariant::Unit => { - // push into vectors. - merge_lpatterns.push(quote! { - Self::#vname - }); - - merge_rpatterns.push(quote! { - Self::Type::#vname - }); - - merge_bodies.push(quote! { - Ok(Self::#vname) - }); - - merge_lpatterns.push(quote! { _ }); - - merge_rpatterns.push(quote! { - diff @ Self::Type::#vname - }); - - merge_bodies.push(quote! { - Self::from_diff(diff.clone()) - }); - - // return generated code. - ( - merge_lpatterns.to_vec(), - merge_rpatterns.to_vec(), - merge_bodies.to_vec(), - ) - } + diff_rpatterns.push(quote! { other @ Self::#vname {..} }); + diff_bodies.push(quote! { + other.clone().into_diff() + }); } -} + // tuple variants. + SVariant::Tuple => { + let ftyps: Vec<&Type> = vfields.iter().map(|f| f.typ()).collect(); -/// parses data for the derived diff function. -fn parse_diff( - vname: &Ident, - vfields: &[DataFields], + let fmax = ftyps.len(); - struct_type: SVariant, -) -> (Vec, Vec, Vec) { - let mut diff_rpatterns: Vec = vec![]; - let mut diff_lpatterns: Vec = vec![]; - let mut diff_bodies: Vec = vec![]; + let (left_names, right_names) = populate_field_names(vfields, fmax, struct_type); - match struct_type { - // named variant. - SVariant::Named => { - let fnames: Vec<&Ident> = vfields.iter().map(|f| f.name()).collect(); - - let (left_names, right_names) = populate_field_names(vfields, 0, struct_type); - - // setup diff logic. - let diff_fvalues: Vec = vfields - .iter() - .zip(left_names.iter()) - .zip(right_names.iter()) - .map(|((f, ln), rn)| { - if f.should_ignore() { - quote! { - Option::None - } - } else if f.is_option() { - quote! { - if #ln != #rn && *#ln != None && *#rn != None { - Some(#ln.diff(#rn)?) - } else { - None - } - } - } else { - quote! { - if #ln == #rn { - None - } else { - Some(#ln.diff(#rn)?) - } - } - } - }) - .collect(); - - // push into vectors. - diff_lpatterns.push(quote! { - Self::#vname { #(#fnames: #left_names),* } - }); - - diff_rpatterns.push(quote! { - Self::#vname { #(#fnames: #right_names),* } - }); - - diff_bodies.push(quote! { - Ok(Self::Type::#vname { - #(#fnames: #diff_fvalues),* - }) - }); - - diff_lpatterns.push(quote! { _ }); - - diff_rpatterns.push(quote! { other @ Self::#vname {..} }); - diff_bodies.push(quote! { - other.clone().into_diff() - }); - } - // tuple variants. - SVariant::Tuple => { - let ftyps: Vec<&Type> = vfields.iter().map(|f| f.typ()).collect(); - - let fmax = ftyps.len(); - - let (left_names, right_names) = populate_field_names(vfields, fmax, struct_type); - - // setup diff logic. - let diff_fvalues: Vec = vfields - .iter() - .zip(left_names.iter().zip(right_names.iter())) - .map(|(f, (ln, rn))| { - if f.should_ignore() { - quote! { - Option::None - } - } else if f.is_option() { - quote! { - if #ln != #rn && *#ln != None && *#rn != None { - Some(#ln.diff(#rn)?) - } else { - None - } - } - } else { - quote! { - if #ln == #rn { - None - } else { - Some(#ln.diff(#rn)?) - } - } - } - }) - .collect(); + // setup diff logic. + let diff_fvalues: Vec = vfields + .iter() + .zip(left_names.iter().zip(right_names.iter())) + .map(|(f, (ln, rn))| { + if f.should_ignore() { + quote! { + Option::None + } + } else if f.is_option() { + quote! { + if #ln != #rn && *#ln != None && *#rn != None { + Some(#ln.diff(#rn)?) + } else { + None + } + } + } else { + quote! { + if #ln == #rn { + None + } else { + Some(#ln.diff(#rn)?) + } + } + } + }) + .collect(); - // push into vectors. - diff_lpatterns.push(quote! { - Self::#vname( #(#left_names),* ) - }); + // push into vectors. + diff_lpatterns.push(quote! { + Self::#vname( #(#left_names),* ) + }); - diff_rpatterns.push(quote! { - Self::#vname( #(#right_names),* ) - }); + diff_rpatterns.push(quote! { + Self::#vname( #(#right_names),* ) + }); - diff_bodies.push(quote! { - Ok(Self::Type::#vname( #(#diff_fvalues),* )) - }); + diff_bodies.push(quote! { + Ok(Self::Type::#vname( #(#diff_fvalues),* )) + }); - diff_lpatterns.push(quote! {_}); + diff_lpatterns.push(quote! {_}); - diff_rpatterns.push(quote! { other @ Self::#vname(..) }); - diff_bodies.push(quote! { - other.clone().into_diff() - }); - } - // push into vectors for unit variants. - SVariant::Unit => { - diff_lpatterns.push(quote! { - Self::#vname - }); + diff_rpatterns.push(quote! { other @ Self::#vname(..) }); + diff_bodies.push(quote! { + other.clone().into_diff() + }); + } + // push into vectors for unit variants. + SVariant::Unit => { + diff_lpatterns.push(quote! { + Self::#vname + }); - diff_rpatterns.push(quote! { - Self::#vname - }); + diff_rpatterns.push(quote! { + Self::#vname + }); - diff_lpatterns.push(quote! { _ }); + diff_lpatterns.push(quote! { _ }); - diff_rpatterns.push(quote! { - other @ Self::#vname - }); + diff_rpatterns.push(quote! { + other @ Self::#vname + }); - diff_bodies.push(quote! { - Ok(Self::Type::#vname) - }); + diff_bodies.push(quote! { + Ok(Self::Type::#vname) + }); - diff_bodies.push(quote! { - other.clone().into_diff() - }); - } + diff_bodies.push(quote! { + other.clone().into_diff() + }); } - (diff_lpatterns.to_vec(), diff_rpatterns.to_vec(), diff_bodies.to_vec()) + } + (diff_lpatterns.to_vec(), diff_rpatterns.to_vec(), diff_bodies.to_vec()) } // parse data for from_diff and into_diff functions. fn parse_from_into( - var: &EVariant, - vname: &Ident, - vfields: &[DataFields], - diff: &Ident, - struct_type: SVariant, + var: &EVariant, + vname: &Ident, + vfields: &[DataFields], + diff: &Ident, + struct_type: SVariant, ) -> (Vec, Vec) { - let mut from_body: Vec = vec![]; - let mut into_body: Vec = vec![]; - - match struct_type { - // named structs. - SVariant::Named => { - let fnames: Vec<&Ident> = vfields.iter().map(|f| f.name()).collect(); - // setup from logic. - let from_fassign: Vec = var - .fields - .iter() - .map(|f| { - let fname = f.name(); - let ftyp = f.typ(); - - if f.should_ignore() { - quote! {#fname: Default::default()} - } else { - quote! { - #fname: <#ftyp>::from_diff( - match #fname { - Some(v) => v, - None => <#ftyp>::default().into_diff()? - } - )? - } - } - }) - .collect(); - - // setup into logic. - let into_fassign: Vec = var - .fields - .iter() - .map(|f| { - let fname = f.name(); - - if f.should_ignore() { - quote! { #fname: Option::None } - } else if f.is_option() { - quote! { - #fname: if let identity_diff::option::DiffOption::Some(_) = #fname.clone().into_diff()? { - Some(#fname.into_diff()?) - } else { - None - } - } - } else { - quote! { - #fname: Some(#fname.into_diff()?) - } + let mut from_body: Vec = vec![]; + let mut into_body: Vec = vec![]; + + match struct_type { + // named structs. + SVariant::Named => { + let fnames: Vec<&Ident> = vfields.iter().map(|f| f.name()).collect(); + // setup from logic. + let from_fassign: Vec = var + .fields + .iter() + .map(|f| { + let fname = f.name(); + let ftyp = f.typ(); + + if f.should_ignore() { + quote! {#fname: Default::default()} + } else { + quote! { + #fname: <#ftyp>::from_diff( + match #fname { + Some(v) => v, + None => <#ftyp>::default().into_diff()? } - }) - .collect(); + )? + } + } + }) + .collect(); - // push into vectors. - from_body.push(quote! { - #diff::#vname { #(#fnames),* } => { - Ok(Self::#vname { #(#from_fassign),* }) + // setup into logic. + let into_fassign: Vec = var + .fields + .iter() + .map(|f| { + let fname = f.name(); + + if f.should_ignore() { + quote! { #fname: Option::None } + } else if f.is_option() { + quote! { + #fname: if let identity_diff::option::DiffOption::Some(_) = #fname.clone().into_diff()? { + Some(#fname.into_diff()?) + } else { + None } - }); + } + } else { + quote! { + #fname: Some(#fname.into_diff()?) + } + } + }) + .collect(); - into_body.push(quote! { - Self::#vname { #(#fnames),* } => { - Ok(#diff::#vname { #(#into_fassign),* }) - } - }); - } - // tuple variants. - SVariant::Tuple => { - let ftyps: Vec<&Type> = vfields.iter().map(|f| f.typ()).collect(); - let fmax = ftyps.len(); - let fnames: Vec = (0..fmax).map(|ident| format_ident!("field_{}", ident)).collect(); - - // from logic. - let from_fassign: Vec = var - .fields - .iter() - .enumerate() - .map(|(idx, f)| { - let fname = &fnames[idx]; - let ftyp = f.typ(); - - if f.should_ignore() { - quote! { Default::default() } - } else { - quote! { - <#ftyp>::from_diff( - match #fname { - Some(v) => v, - None => <#ftyp>::default().into_diff()? - } - )? - } - } - }) - .collect(); - - // into logic. - let into_fassign: Vec = var - .fields - .iter() - .enumerate() - .map(|(idx, f)| { - let fname = &fnames[idx]; - - if f.should_ignore() { - quote! { Option::None } - } else if f.is_option() { - quote! { - if #fname.clone().into_diff()? == identity_diff::option::DiffOption::None { - None - } else { - Some(#fname.into_diff()?) - } - } - } else { - quote! { - Some(#fname.into_diff()?) - } + // push into vectors. + from_body.push(quote! { + #diff::#vname { #(#fnames),* } => { + Ok(Self::#vname { #(#from_fassign),* }) + } + }); + + into_body.push(quote! { + Self::#vname { #(#fnames),* } => { + Ok(#diff::#vname { #(#into_fassign),* }) + } + }); + } + // tuple variants. + SVariant::Tuple => { + let ftyps: Vec<&Type> = vfields.iter().map(|f| f.typ()).collect(); + let fmax = ftyps.len(); + let fnames: Vec = (0..fmax).map(|ident| format_ident!("field_{}", ident)).collect(); + + // from logic. + let from_fassign: Vec = var + .fields + .iter() + .enumerate() + .map(|(idx, f)| { + let fname = &fnames[idx]; + let ftyp = f.typ(); + + if f.should_ignore() { + quote! { Default::default() } + } else { + quote! { + <#ftyp>::from_diff( + match #fname { + Some(v) => v, + None => <#ftyp>::default().into_diff()? } - }) - .collect(); + )? + } + } + }) + .collect(); - // push code into vectors. - from_body.push(quote! { - #diff::#vname( #(#fnames),* ) => { - Ok(Self::#vname( #(#from_fassign),* )) + // into logic. + let into_fassign: Vec = var + .fields + .iter() + .enumerate() + .map(|(idx, f)| { + let fname = &fnames[idx]; + + if f.should_ignore() { + quote! { Option::None } + } else if f.is_option() { + quote! { + if #fname.clone().into_diff()? == identity_diff::option::DiffOption::None { + None + } else { + Some(#fname.into_diff()?) } - }); + } + } else { + quote! { + Some(#fname.into_diff()?) + } + } + }) + .collect(); - into_body.push(quote! { - Self::#vname( #(#fnames),* ) => { - Ok(#diff::#vname( - #(#into_fassign),* - )) - } - }); - } - // setup code for unit variants. - SVariant::Unit => { - from_body.push(quote! { - #diff::#vname => { - Ok(Self::#vname) - }, - }); - - into_body.push(quote! { - Self::#vname => { - Ok(#diff::#vname) - }, - }); - } + // push code into vectors. + from_body.push(quote! { + #diff::#vname( #(#fnames),* ) => { + Ok(Self::#vname( #(#from_fassign),* )) + } + }); + + into_body.push(quote! { + Self::#vname( #(#fnames),* ) => { + Ok(#diff::#vname( + #(#into_fassign),* + )) + } + }); } + // setup code for unit variants. + SVariant::Unit => { + from_body.push(quote! { + #diff::#vname => { + Ok(Self::#vname) + }, + }); + + into_body.push(quote! { + Self::#vname => { + Ok(#diff::#vname) + }, + }); + } + } - (from_body.to_vec(), into_body.to_vec()) + (from_body.to_vec(), into_body.to_vec()) } // create field names based on thee size of an enum. fn populate_field_names(vfields: &[DataFields], fmax: usize, struct_type: SVariant) -> (Vec, Vec) { - match struct_type { - SVariant::Named => ( - vfields - .iter() - .map(|f| f.name()) - .map(|ident| format_ident!("left_{}", ident)) - .collect(), - vfields - .iter() - .map(|f| f.name()) - .map(|ident| format_ident!("right_{}", ident)) - .collect(), - ), - SVariant::Tuple => ( - (0..fmax).map(|ident| format_ident!("left_{}", ident)).collect(), - (0..fmax).map(|ident| format_ident!("right_{}", ident)).collect(), - ), - _ => panic!("Wrong Struct Type!"), - } + match struct_type { + SVariant::Named => ( + vfields + .iter() + .map(|f| f.name()) + .map(|ident| format_ident!("left_{}", ident)) + .collect(), + vfields + .iter() + .map(|f| f.name()) + .map(|ident| format_ident!("right_{}", ident)) + .collect(), + ), + SVariant::Tuple => ( + (0..fmax).map(|ident| format_ident!("left_{}", ident)).collect(), + (0..fmax).map(|ident| format_ident!("right_{}", ident)).collect(), + ), + _ => panic!("Wrong Struct Type!"), + } } diff --git a/identity-diff/derive/src/impls/structs.rs b/identity-diff/derive/src/impls/structs.rs index a57db906f9..8369ba0da1 100644 --- a/identity-diff/derive/src/impls/structs.rs +++ b/identity-diff/derive/src/impls/structs.rs @@ -10,649 +10,649 @@ use syn::{GenericParam, Type}; /// Derive the difference struct code from the `InputModel` pub fn derive_diff_struct(input: &InputModel) -> TokenStream { - // setup the relevant fields. - let svariant = input.s_variant(); - let diff = input.diff(); - let fields = input.fields(); - let param_decls = input.param_decls(); - let clause = input.clause(); - let serde_attrs = if input.from_into() { - let name = input.name(); - let stype = quote!(#name).to_string(); - - quote! { - #[serde(from=#stype, into=#stype)] - } - } else { - quote! {} - }; + // setup the relevant fields. + let svariant = input.s_variant(); + let diff = input.diff(); + let fields = input.fields(); + let param_decls = input.param_decls(); + let clause = input.clause(); + let serde_attrs = if input.from_into() { + let name = input.name(); + let stype = quote!(#name).to_string(); - // set the param declarations. - let param_decls: Vec = param_decls - .iter() - .map(|tp_decl| match tp_decl { - GenericParam::Lifetime(life) => quote! { #life }, - GenericParam::Const(consts) => quote! { #consts}, - GenericParam::Type(typ) => { - let S: &Ident = &typ.ident; - - let bounds: Vec = typ - .bounds - .iter() - .map(|bound| { - quote! { - #bound - } - }) - .collect(); - - quote! { - #S: identity_diff::Diff - #(+ #bounds)* - } + quote! { + #[serde(from=#stype, into=#stype)] + } + } else { + quote! {} + }; + + // set the param declarations. + let param_decls: Vec = param_decls + .iter() + .map(|tp_decl| match tp_decl { + GenericParam::Lifetime(life) => quote! { #life }, + GenericParam::Const(consts) => quote! { #consts}, + GenericParam::Type(typ) => { + let S: &Ident = &typ.ident; + + let bounds: Vec = typ + .bounds + .iter() + .map(|bound| { + quote! { + #bound } - }) - .collect(); + }) + .collect(); - // get the field types as tokens. - let field_tps: Vec = fields.iter().map(|field| field.typ_as_tokens()).collect(); - - match svariant { - // for name structs. - SVariant::Named => { - // get te field names - let field_names: Vec<&Ident> = fields.iter().map(|field| field.name()).collect(); + quote! { + #S: identity_diff::Diff + #(+ #bounds)* + } + } + }) + .collect(); + + // get the field types as tokens. + let field_tps: Vec = fields.iter().map(|field| field.typ_as_tokens()).collect(); + + match svariant { + // for name structs. + SVariant::Named => { + // get te field names + let field_names: Vec<&Ident> = fields.iter().map(|field| field.name()).collect(); + + // generate the Diff struct. + quote! { + #[derive(Clone, PartialEq, Default)] + #[derive(serde::Deserialize, serde::Serialize)] + #serde_attrs + pub struct #diff<#(#param_decls),*> + #clause + { + #( #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] pub(self) #field_names: #field_tps, )* + } + } + } + // for Tuple variant Structs. + SVariant::Tuple => { + // generate the Diff struct. + quote! { + #[derive(Clone, PartialEq, serde::Deserialize, serde::Serialize, Default)] + #serde_attrs + pub struct #diff<#(#param_decls),*> ( + #( #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] pub(self) #field_tps, )* + ) #clause ; + } + } + // for unit variant Structs. + SVariant::Unit => quote! { + // generate the Diff struct. + #[derive(Clone, PartialEq, serde::Deserialize, serde::Serialize, Default)] + #serde_attrs + pub struct #diff<#(#param_decls),*> #clause ; + }, + } +} - // generate the Diff struct. +/// Implement the Debug trait on a derived struct. +pub fn debug_impl(input: &InputModel) -> TokenStream { + // collect relevant fields. + let svariant = input.s_variant(); + let diff = input.diff(); + let fields = input.fields(); + let param_decls = input.param_decls(); + let params = input.params(); + let clause = input.clause(); + + // setup param declarations. + let param_decls: Vec = param_decls + .iter() + .map(|tp_decl| match tp_decl { + GenericParam::Lifetime(life) => quote! { #life }, + GenericParam::Const(consts) => quote! { #consts}, + GenericParam::Type(typ) => { + let S: &Ident = &typ.ident; + + let bounds: Vec = typ + .bounds + .iter() + .map(|bound| { quote! { - #[derive(Clone, PartialEq, Default)] - #[derive(serde::Deserialize, serde::Serialize)] - #serde_attrs - pub struct #diff<#(#param_decls),*> - #clause - { - #( #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] pub(self) #field_names: #field_tps, )* - } + #bound } + }) + .collect(); + + quote! { + #S: identity_diff::Diff + std::fmt::Debug + #(+ #bounds)* } - // for Tuple variant Structs. - SVariant::Tuple => { - // generate the Diff struct. - quote! { - #[derive(Clone, PartialEq, serde::Deserialize, serde::Serialize, Default)] - #serde_attrs - pub struct #diff<#(#param_decls),*> ( - #( #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] pub(self) #field_tps, )* - ) #clause ; + } + }) + .collect(); + + // setup the where clause predicates and the where clause code. + let preds: Vec = clause.predicates.iter().map(|pred| quote! { #pred }).collect(); + let clause = quote! { where #(#preds),*}; + + match svariant { + // name struct + SVariant::Named => { + // create a buffer and generate the logic. + let mut mac = TokenStream::new(); + let buf: Ident = format_ident!("buf"); + for field in fields.iter() { + let (fname, ftype) = (field.name(), field.typ()); + + let str_name = format!("{}", fname); + + mac.extend(if field.should_ignore() { + quote! { + #buf.field(#str_name, &self.#fname); + } + } else { + quote! { + if let Some(val) = &self.#fname { + #buf.field(#str_name, val); + } else { + #buf.field(#str_name, &None as &Option<#ftype>); + } + } + }); + } + + // generate code. + quote! { + impl<#(#param_decls),*> std::fmt::Debug + for #diff<#params> + #clause + { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + const NAME: &str = stringify!(diff); + let mut #buf = f.debug_struct(NAME); + #mac + #buf.finish() + } + } + } + } + // Tuple Struct. + SVariant::Tuple => { + // create buffer and logic. + let ftyps: Vec<&Type> = fields.iter().map(|field| field.typ()).collect(); + + let count = ftyps.len(); + + let mut f_tokens = TokenStream::new(); + let buf = format_ident!("buf"); + for field in fields.iter() { + let (fpos, ftyp) = (field.position(), field.typ()); + + f_tokens.extend(match count { + 1 => quote! {}, + _ if field.should_ignore() => quote! { + #buf.field(&self.#fpos); + }, + _ => quote! { + if let Some(val) = &self.#fpos { + #buf.field(val); + } else { + #buf.field(&None as &Option<#ftyp>); + } + }, + }); + } + let mac = match count { + 1 => quote! { + const NAME: &str = stringify!(#diff); + if let Some(val) = &self.0 { + write!(f, "{}({:?})", NAME, val) + } else { + let field = &None as &Option<()>; + write!(f, "{}({:?})", NAME, field) } - } - // for unit variant Structs. - SVariant::Unit => quote! { - // generate the Diff struct. - #[derive(Clone, PartialEq, serde::Deserialize, serde::Serialize, Default)] - #serde_attrs - pub struct #diff<#(#param_decls),*> #clause ; }, + _ => quote! { + const NAME: &str = stringify!(#diff); + let mut #buf = f.debug_tuple(NAME); + #f_tokens + #buf.finish() + }, + }; + + // generate code. + quote! { + impl<#(#param_decls),*> std::fmt::Debug for #diff<#params> + #clause + { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + #mac + } + } + } } + // generate code for unit struct. + SVariant::Unit => quote! { + impl<#(#param_decls),*> std::fmt::Debug for #diff<#params> + #clause + { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result + { + f.debug_struct(stringify!(#diff)).finish() + } + } + }, + } } -/// Implement the Debug trait on a derived struct. -pub fn debug_impl(input: &InputModel) -> TokenStream { - // collect relevant fields. - let svariant = input.s_variant(); +pub fn impl_from_into(input: &InputModel) -> TokenStream { + if input.from_into() { let diff = input.diff(); - let fields = input.fields(); let param_decls = input.param_decls(); let params = input.params(); let clause = input.clause(); - - // setup param declarations. + let name = input.name(); let param_decls: Vec = param_decls - .iter() - .map(|tp_decl| match tp_decl { - GenericParam::Lifetime(life) => quote! { #life }, - GenericParam::Const(consts) => quote! { #consts}, - GenericParam::Type(typ) => { - let S: &Ident = &typ.ident; - - let bounds: Vec = typ - .bounds - .iter() - .map(|bound| { - quote! { - #bound - } - }) - .collect(); - - quote! { - #S: identity_diff::Diff + std::fmt::Debug - #(+ #bounds)* - } - } - }) - .collect(); + .iter() + .map(|tp_decl| match tp_decl { + GenericParam::Lifetime(life) => quote! { #life }, + GenericParam::Const(consts) => quote! { #consts}, + GenericParam::Type(typ) => { + let S: &Ident = &typ.ident; + + let bounds: Vec = typ + .bounds + .iter() + .map(|bound| { + quote! { + #bound + } + }) + .collect(); + + quote! { + #S: std::clone::Clone + + std::default::Default + + identity_diff::Diff + + for<'de> serde::Deserialize<'de> + + serde::Serialize + #(+ #bounds)* + } + } + }) + .collect(); - // setup the where clause predicates and the where clause code. let preds: Vec = clause.predicates.iter().map(|pred| quote! { #pred }).collect(); let clause = quote! { where #(#preds),*}; - match svariant { - // name struct - SVariant::Named => { - // create a buffer and generate the logic. - let mut mac = TokenStream::new(); - let buf: Ident = format_ident!("buf"); - for field in fields.iter() { - let (fname, ftype) = (field.name(), field.typ()); - - let str_name = format!("{}", fname); - - mac.extend(if field.should_ignore() { - quote! { - #buf.field(#str_name, &self.#fname); - } - } else { - quote! { - if let Some(val) = &self.#fname { - #buf.field(#str_name, val); - } else { - #buf.field(#str_name, &None as &Option<#ftype>); - } - } - }); + quote! { + impl <#(#param_decls),*> std::convert::From<#name<#params>> for #diff<#params> #clause + { + fn from(name: #name<#params>) -> Self { + name.into_diff().expect("Unable to convert to diff") } + } - // generate code. + impl <#(#param_decls),*> std::convert::From<#diff<#params>> for #name<#params> #clause + { + fn from(diff: #diff<#params>) -> Self { + Self::from_diff(diff).expect("Unable to convert from diff") + } + } + } + } else { + quote! {} + } +} + +/// implement Diff for the struct. +pub fn diff_impl(input: &InputModel) -> TokenStream { + // collect relevant fields and generate param declarations. + let svariant = input.s_variant(); + let name = input.name(); + let diff = input.diff(); + let fields = input.fields(); + let param_decls = input.param_decls(); + let params = input.params(); + let clause = input.clause(); + let param_decls: Vec = param_decls + .iter() + .map(|tp_decl| match tp_decl { + GenericParam::Lifetime(life) => quote! { #life }, + GenericParam::Const(consts) => quote! { #consts}, + GenericParam::Type(typ) => { + let S: &Ident = &typ.ident; + + let bounds: Vec = typ + .bounds + .iter() + .map(|bound| { quote! { - impl<#(#param_decls),*> std::fmt::Debug - for #diff<#params> - #clause - { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - const NAME: &str = stringify!(diff); - let mut #buf = f.debug_struct(NAME); - #mac - #buf.finish() - } - } + #bound } + }) + .collect(); + + quote! { + #S: std::clone::Clone + + std::default::Default + + identity_diff::Diff + + std::fmt::Debug + + std::cmp::PartialEq + + for<'de> serde::Deserialize<'de> + + serde::Serialize + #(+ #bounds)* } - // Tuple Struct. - SVariant::Tuple => { - // create buffer and logic. - let ftyps: Vec<&Type> = fields.iter().map(|field| field.typ()).collect(); - - let count = ftyps.len(); - - let mut f_tokens = TokenStream::new(); - let buf = format_ident!("buf"); - for field in fields.iter() { - let (fpos, ftyp) = (field.position(), field.typ()); - - f_tokens.extend(match count { - 1 => quote! {}, - _ if field.should_ignore() => quote! { - #buf.field(&self.#fpos); - }, - _ => quote! { - if let Some(val) = &self.#fpos { - #buf.field(val); - } else { - #buf.field(&None as &Option<#ftyp>); - } - }, - }); + } + }) + .collect(); + + // get predicates and generate where clause. + let preds: Vec = clause.predicates.iter().map(|pred| quote! { #pred }).collect(); + let clause = quote! { where #(#preds),* }; + + match svariant { + // named struct. + SVariant::Named => { + // get field names. + let fnames: Vec<&Ident> = fields.iter().map(|field| field.name()).collect(); + // get merge field logic. + let field_merge: Vec = fields + .iter() + .map(|field| { + let fname = field.name(); + if field.should_ignore() { + quote! { + #fname: self.#fname.clone(), } - let mac = match count { - 1 => quote! { - const NAME: &str = stringify!(#diff); - if let Some(val) = &self.0 { - write!(f, "{}({:?})", NAME, val) - } else { - let field = &None as &Option<()>; - write!(f, "{}({:?})", NAME, field) - } - }, - _ => quote! { - const NAME: &str = stringify!(#diff); - let mut #buf = f.debug_tuple(NAME); - #f_tokens - #buf.finish() + } else { + quote! { + #fname: if let Some(d) = diff.#fname { + self.#fname.merge(d)? + } else { + self.#fname.clone() }, - }; + } + } + }) + .collect(); - // generate code. + // get diff field logic. + let fields_diff: Vec = fields + .iter() + .map(|field| { + let fname = field.name(); + if field.should_ignore() { quote! { - impl<#(#param_decls),*> std::fmt::Debug for #diff<#params> - #clause - { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - #mac - } - } + #fname: Option::None } - } - // generate code for unit struct. - SVariant::Unit => quote! { - impl<#(#param_decls),*> std::fmt::Debug for #diff<#params> - #clause - { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result - { - f.debug_struct(stringify!(#diff)).finish() - } + } else if field.is_option() { + quote! { + #fname: if self.#fname == other.#fname || other.#fname == None { + None + } else { + Some(self.#fname.diff(&other.#fname)?) } - }, - } -} - -pub fn impl_from_into(input: &InputModel) -> TokenStream { - if input.from_into() { - let diff = input.diff(); - let param_decls = input.param_decls(); - let params = input.params(); - let clause = input.clause(); - let name = input.name(); - let param_decls: Vec = param_decls - .iter() - .map(|tp_decl| match tp_decl { - GenericParam::Lifetime(life) => quote! { #life }, - GenericParam::Const(consts) => quote! { #consts}, - GenericParam::Type(typ) => { - let S: &Ident = &typ.ident; - - let bounds: Vec = typ - .bounds - .iter() - .map(|bound| { - quote! { - #bound - } - }) - .collect(); - - quote! { - #S: std::clone::Clone - + std::default::Default - + identity_diff::Diff - + for<'de> serde::Deserialize<'de> - + serde::Serialize - #(+ #bounds)* - } + } + } else { + quote! { + #fname: if self.#fname == other.#fname { + None + } else { + Some(self.#fname.diff(&other.#fname)?) } - }) - .collect(); - - let preds: Vec = clause.predicates.iter().map(|pred| quote! { #pred }).collect(); - let clause = quote! { where #(#preds),*}; + } + } + }) + .collect(); - quote! { - impl <#(#param_decls),*> std::convert::From<#name<#params>> for #diff<#params> #clause - { - fn from(name: #name<#params>) -> Self { - name.into_diff().expect("Unable to convert to diff") - } + // get from_diff field logic. + let fields_from: Vec = fields + .iter() + .map(|field| { + let fname = field.name(); + let ftyp = field.typ(); + if field.should_ignore() { + quote! { #fname: Default::default() } + } else { + quote! { + #fname: <#ftyp>::from_diff( + match #fname { + Some(v) => v, + None => <#ftyp>::default().into_diff()? + }, + )? } + } + }) + .collect(); - impl <#(#param_decls),*> std::convert::From<#diff<#params>> for #name<#params> #clause - { - fn from(diff: #diff<#params>) -> Self { - Self::from_diff(diff).expect("Unable to convert from diff") + // get into_diff field logic. + let fields_into: Vec = fields + .iter() + .map(|field| { + let fname = field.name(); + if field.should_ignore() { + quote! { #fname: Option::None } + } else if field.is_option() { + quote! { + #fname: if let identity_diff::option::DiffOption::Some(_) = #fname.clone().into_diff()? { + Some(#fname.into_diff()?) + } else { + None } } - } - } else { - quote! {} - } -} + } else { + quote! { + #fname: Some(#fname.into_diff()?) + } + } + }) + .collect(); -/// implement Diff for the struct. -pub fn diff_impl(input: &InputModel) -> TokenStream { - // collect relevant fields and generate param declarations. - let svariant = input.s_variant(); - let name = input.name(); - let diff = input.diff(); - let fields = input.fields(); - let param_decls = input.param_decls(); - let params = input.params(); - let clause = input.clause(); - let param_decls: Vec = param_decls + // generate body and code. + quote! { + impl<#(#param_decls),*> identity_diff::Diff + for #name<#params> + #clause + { + type Type = #diff<#params>; + + #[allow(unused)] + fn merge(&self, diff: Self::Type) -> identity_diff::Result { + Ok(Self{ #(#field_merge)* }) + } + + fn diff(&self, other: &Self) -> identity_diff::Result { + Ok(#diff { #(#fields_diff),* }) + } + + #[allow(unused)] + fn from_diff(diff: Self::Type) -> identity_diff::Result { + match diff { + #diff { #(#fnames),* } => { + Ok(Self{ #(#fields_from),* }) + } + } + } + + #[allow(unused)] + fn into_diff(self) -> identity_diff::Result { + match self { + Self { #(#fnames),* } => { + Ok(#diff { #(#fields_into),* }) + } + } + } + } + + } + } + // Tuple struct. + SVariant::Tuple => { + // get types and create markers for the positioned fields. + let field_typs: Vec<_> = fields.iter().map(|f| f.typ()).collect(); + let field_max = field_typs.len(); + let field_markers: Vec = (0..field_max).map(|t| format_ident!("field_{}", t)).collect(); + + // get merge field logic. + let field_merge: Vec = fields .iter() - .map(|tp_decl| match tp_decl { - GenericParam::Lifetime(life) => quote! { #life }, - GenericParam::Const(consts) => quote! { #consts}, - GenericParam::Type(typ) => { - let S: &Ident = &typ.ident; - - let bounds: Vec = typ - .bounds - .iter() - .map(|bound| { - quote! { - #bound - } - }) - .collect(); - - quote! { - #S: std::clone::Clone - + std::default::Default - + identity_diff::Diff - + std::fmt::Debug - + std::cmp::PartialEq - + for<'de> serde::Deserialize<'de> - + serde::Serialize - #(+ #bounds)* - } + .map(|field| { + let pos = field.position(); + if field.should_ignore() { + quote! { + self.#pos.clone(), + } + } else { + quote! { + if let Some(v) = diff.#pos { + self.#pos.merge(v)? + } else { + self.#pos.clone() + }, } + } }) .collect(); - // get predicates and generate where clause. - let preds: Vec = clause.predicates.iter().map(|pred| quote! { #pred }).collect(); - let clause = quote! { where #(#preds),* }; - - match svariant { - // named struct. - SVariant::Named => { - // get field names. - let fnames: Vec<&Ident> = fields.iter().map(|field| field.name()).collect(); - // get merge field logic. - let field_merge: Vec = fields - .iter() - .map(|field| { - let fname = field.name(); - if field.should_ignore() { - quote! { - #fname: self.#fname.clone(), - } - } else { - quote! { - #fname: if let Some(d) = diff.#fname { - self.#fname.merge(d)? - } else { - self.#fname.clone() - }, - } - } - }) - .collect(); - - // get diff field logic. - let fields_diff: Vec = fields - .iter() - .map(|field| { - let fname = field.name(); - if field.should_ignore() { - quote! { - #fname: Option::None - } - } else if field.is_option() { - quote! { - #fname: if self.#fname == other.#fname || other.#fname == None { - None - } else { - Some(self.#fname.diff(&other.#fname)?) - } - } - } else { - quote! { - #fname: if self.#fname == other.#fname { - None - } else { - Some(self.#fname.diff(&other.#fname)?) - } - } - } - }) - .collect(); - - // get from_diff field logic. - let fields_from: Vec = fields - .iter() - .map(|field| { - let fname = field.name(); - let ftyp = field.typ(); - if field.should_ignore() { - quote! { #fname: Default::default() } - } else { - quote! { - #fname: <#ftyp>::from_diff( - match #fname { - Some(v) => v, - None => <#ftyp>::default().into_diff()? - }, - )? - } - } - }) - .collect(); - - // get into_diff field logic. - let fields_into: Vec = fields - .iter() - .map(|field| { - let fname = field.name(); - if field.should_ignore() { - quote! { #fname: Option::None } - } else if field.is_option() { - quote! { - #fname: if let identity_diff::option::DiffOption::Some(_) = #fname.clone().into_diff()? { - Some(#fname.into_diff()?) - } else { - None - } - } - } else { - quote! { - #fname: Some(#fname.into_diff()?) - } - } - }) - .collect(); - - // generate body and code. + // get diff field logic. + let fields_diff: Vec = fields + .iter() + .map(|field| { + let pos = field.position(); + if field.should_ignore() { quote! { - impl<#(#param_decls),*> identity_diff::Diff - for #name<#params> - #clause - { - type Type = #diff<#params>; - - #[allow(unused)] - fn merge(&self, diff: Self::Type) -> identity_diff::Result { - Ok(Self{ #(#field_merge)* }) - } - - fn diff(&self, other: &Self) -> identity_diff::Result { - Ok(#diff { #(#fields_diff),* }) - } - - #[allow(unused)] - fn from_diff(diff: Self::Type) -> identity_diff::Result { - match diff { - #diff { #(#fnames),* } => { - Ok(Self{ #(#fields_from),* }) - } - } - } - - #[allow(unused)] - fn into_diff(self) -> identity_diff::Result { - match self { - Self { #(#fnames),* } => { - Ok(#diff { #(#fields_into),* }) - } - } - } - } + Option::None + } + } else if field.is_option() { + quote! { + if self.#pos != other.#pos && other.#pos != None { + Some(self.#pos.diff(&other.#pos)?) + } else { + None + }, + } + } else { + quote! { + if self.#pos != other.#pos { + Some(self.#pos.diff(&other.#pos)?) + } else { + None + }, + } + } + }) + .collect(); + // get from_diff field logic. + let fields_from: Vec = fields + .iter() + .enumerate() + .map(|(idx, field)| { + let marker = &field_markers[idx]; + let typ = field.typ(); + + if field.should_ignore() { + quote! { Default::default() } + } else { + quote! { + #marker.map(|v| <#typ>::from_diff(v).expect("Unable to convert from diff")).unwrap_or_default() } - } - // Tuple struct. - SVariant::Tuple => { - // get types and create markers for the positioned fields. - let field_typs: Vec<_> = fields.iter().map(|f| f.typ()).collect(); - let field_max = field_typs.len(); - let field_markers: Vec = (0..field_max).map(|t| format_ident!("field_{}", t)).collect(); - - // get merge field logic. - let field_merge: Vec = fields - .iter() - .map(|field| { - let pos = field.position(); - if field.should_ignore() { - quote! { - self.#pos.clone(), - } - } else { - quote! { - if let Some(v) = diff.#pos { - self.#pos.merge(v)? - } else { - self.#pos.clone() - }, - } - } - }) - .collect(); - - // get diff field logic. - let fields_diff: Vec = fields - .iter() - .map(|field| { - let pos = field.position(); - if field.should_ignore() { - quote! { - Option::None - } - } else if field.is_option() { - quote! { - if self.#pos != other.#pos && other.#pos != None { - Some(self.#pos.diff(&other.#pos)?) - } else { - None - }, - } - } else { - quote! { - if self.#pos != other.#pos { - Some(self.#pos.diff(&other.#pos)?) - } else { - None - }, - } - } - }) - .collect(); - - // get from_diff field logic. - let fields_from: Vec = fields - .iter() - .enumerate() - .map(|(idx, field)| { - let marker = &field_markers[idx]; - let typ = field.typ(); - - if field.should_ignore() { - quote! { Default::default() } - } else { - quote! { - #marker.map(|v| <#typ>::from_diff(v).expect("Unable to convert from diff")).unwrap_or_default() - } - } - }) - .collect(); - - // get into_diff field logic. - let fields_into: Vec = fields - .iter() - .enumerate() - .map(|(idx, field)| { - let marker = &field_markers[idx]; - if field.should_ignore() { - quote! { Option::None } - } else if field.is_option() { - quote! { - if #marker.clone().into_diff()? == identity_diff::option::DiffOption::None { - None - } else { - Some(#marker.into_diff()?) - } - } - } else { - quote! { - Some(#marker.into_diff()?) - } - } - }) - .collect(); - - // generate code for the `Diff` Trait. + } + }) + .collect(); + + // get into_diff field logic. + let fields_into: Vec = fields + .iter() + .enumerate() + .map(|(idx, field)| { + let marker = &field_markers[idx]; + if field.should_ignore() { + quote! { Option::None } + } else if field.is_option() { quote! { - impl<#(#param_decls),*> identity_diff::Diff - for #name<#params> - #clause - { - type Type = #diff<#params>; - - #[allow(unused)] - fn merge(&self, diff: Self::Type) -> identity_diff::Result { - Ok(Self( #(#field_merge)* )) - } - #[allow(unused)] - fn diff(&self, other: &Self) -> identity_diff::Result { - Ok(#diff( #(#fields_diff)* )) - } - - #[allow(unused)] - fn from_diff(diff: Self::Type) -> identity_diff::Result { - match diff { - #diff ( #(#field_markers),*) => { - Ok(Self( #(#fields_from),* )) - } - } - } - - #[allow(unused)] - fn into_diff(self) -> identity_diff::Result { - match self { - Self ( #(#field_markers,)* ..) => { - Ok(#diff ( #(#fields_into),* )) - }, - } - } + if #marker.clone().into_diff()? == identity_diff::option::DiffOption::None { + None + } else { + Some(#marker.into_diff()?) } } - } - // generated code for unit structs. - SVariant::Unit => quote! { - impl<#(#param_decls),*> identity_diff::Diff - for #name<#params> - #clause - { - type Type = #diff<#params>; - - #[allow(unused)] - fn merge(&self, diff: Self::Type) -> identity_diff::Result { - Ok(Self) - } - - fn diff(&self, other: &Self) -> identity_diff::Result { - Ok(#diff) - } - - #[allow(unused)] - fn from_diff(diff: Self::Type) -> identity_diff::Result { - Ok(Self) - } - - #[allow(unused)] - fn into_diff(self) -> identity_diff::Result { - Ok(#diff) - } - } - }, + } else { + quote! { + Some(#marker.into_diff()?) + } + } + }) + .collect(); + + // generate code for the `Diff` Trait. + quote! { + impl<#(#param_decls),*> identity_diff::Diff + for #name<#params> + #clause + { + type Type = #diff<#params>; + + #[allow(unused)] + fn merge(&self, diff: Self::Type) -> identity_diff::Result { + Ok(Self( #(#field_merge)* )) + } + #[allow(unused)] + fn diff(&self, other: &Self) -> identity_diff::Result { + Ok(#diff( #(#fields_diff)* )) + } + + #[allow(unused)] + fn from_diff(diff: Self::Type) -> identity_diff::Result { + match diff { + #diff ( #(#field_markers),*) => { + Ok(Self( #(#fields_from),* )) + } + } + } + + #[allow(unused)] + fn into_diff(self) -> identity_diff::Result { + match self { + Self ( #(#field_markers,)* ..) => { + Ok(#diff ( #(#fields_into),* )) + }, + } + } + } + } } + // generated code for unit structs. + SVariant::Unit => quote! { + impl<#(#param_decls),*> identity_diff::Diff + for #name<#params> + #clause + { + type Type = #diff<#params>; + + #[allow(unused)] + fn merge(&self, diff: Self::Type) -> identity_diff::Result { + Ok(Self) + } + + fn diff(&self, other: &Self) -> identity_diff::Result { + Ok(#diff) + } + + #[allow(unused)] + fn from_diff(diff: Self::Type) -> identity_diff::Result { + Ok(Self) + } + + #[allow(unused)] + fn into_diff(self) -> identity_diff::Result { + Ok(#diff) + } + } + }, + } } diff --git a/identity-diff/derive/src/lib.rs b/identity-diff/derive/src/lib.rs index 75f5335716..96bbef56e5 100644 --- a/identity-diff/derive/src/lib.rs +++ b/identity-diff/derive/src/lib.rs @@ -6,8 +6,8 @@ use quote::quote; use syn::{parse_macro_input, DeriveInput}; use crate::{ - model::InputModel, - utils::{extract_option_segment, parse_from_into, should_ignore}, + model::InputModel, + utils::{extract_option_segment, parse_from_into, should_ignore}, }; mod impls; @@ -18,35 +18,35 @@ mod utils; /// any Enum or Struct type. Contains and optional attribute `should_ignore` which will ignore an appended field. #[proc_macro_derive(Diff, attributes(diff))] pub fn derive_diff(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - internal(input) + let input = parse_macro_input!(input as DeriveInput); + internal(input) } /// Function for dealing with the internal logic of the macro. fn internal(input: DeriveInput) -> TokenStream { - let model: InputModel = InputModel::parse(&input); - // debug implementation derivation. - let debug = model.impl_debug(); - // diff type derivation. - let diff = model.impl_diff(); - // diff trait implementation derivation. - let diff_typ = model.derive_diff(); - - let from_into = model.impl_from_into(); - - let output = quote! { - #diff_typ - #debug - #diff - #from_into - }; - - // for debugging. - // println!("{}", from_into); - // println!("{}", diff_typ); - // println!("{}", debug); - // println!("{}", diff); - - // A hack. - TokenStream::from(output) + let model: InputModel = InputModel::parse(&input); + // debug implementation derivation. + let debug = model.impl_debug(); + // diff type derivation. + let diff = model.impl_diff(); + // diff trait implementation derivation. + let diff_typ = model.derive_diff(); + + let from_into = model.impl_from_into(); + + let output = quote! { + #diff_typ + #debug + #diff + #from_into + }; + + // for debugging. + // println!("{}", from_into); + // println!("{}", diff_typ); + // println!("{}", debug); + // println!("{}", diff); + + // A hack. + TokenStream::from(output) } diff --git a/identity-diff/derive/src/model.rs b/identity-diff/derive/src/model.rs index 84f129d2c3..0aedfb68fb 100644 --- a/identity-diff/derive/src/model.rs +++ b/identity-diff/derive/src/model.rs @@ -4,411 +4,411 @@ use proc_macro2::{Ident, Literal, Span, TokenStream}; use quote::{format_ident, quote}; use syn::{ - punctuated::Punctuated, token::Comma, Data, DataEnum, DataStruct, DeriveInput, Fields, GenericParam, Token, Type, - Variant, WhereClause, + punctuated::Punctuated, token::Comma, Data, DataEnum, DataStruct, DeriveInput, Fields, GenericParam, Token, Type, + Variant, WhereClause, }; use crate::{ - extract_option_segment, - impls::{ - debug_impl, derive_diff_enum, derive_diff_struct, diff_impl, impl_debug_enum, impl_diff_enum, impl_from_into, - }, - parse_from_into, should_ignore, + extract_option_segment, + impls::{ + debug_impl, derive_diff_enum, derive_diff_struct, diff_impl, impl_debug_enum, impl_diff_enum, impl_from_into, + }, + parse_from_into, should_ignore, }; /// A model for dealing with the different input from the incoming AST. #[derive(Clone, Debug)] pub enum InputModel { - Enum(InputEnum), - Struct(InputStruct), + Enum(InputEnum), + Struct(InputStruct), } /// Sorts attributes regarding incoming Enums. #[derive(Clone, Debug)] pub struct InputEnum { - // name identifier. - pub name: Ident, - // diff identifier. - pub diff: Ident, - // variants for the Enum. - pub variants: Vec, - // generics and traits declarations. - pub param_decls: Punctuated, - // generics and trait bounds for the Enum. - pub params: Punctuated, - // where clause for the Enum. - pub clause: WhereClause, - // should this enum be serialized/deserialized as its non-diff counterpart. - pub from_into: bool, + // name identifier. + pub name: Ident, + // diff identifier. + pub diff: Ident, + // variants for the Enum. + pub variants: Vec, + // generics and traits declarations. + pub param_decls: Punctuated, + // generics and trait bounds for the Enum. + pub params: Punctuated, + // where clause for the Enum. + pub clause: WhereClause, + // should this enum be serialized/deserialized as its non-diff counterpart. + pub from_into: bool, } /// Sorts data regarding incoming Structs. #[derive(Clone, Debug)] pub struct InputStruct { - // struct variant. - pub variant: SVariant, - // struct name. - pub name: Ident, - // struct diff name. - pub diff: Ident, - // struct fields. - pub fields: Vec, - // generics and traits declarations. - pub param_decls: Punctuated, - // generics and trait bounds for the Enum. - pub params: Punctuated, - // where clause for the Enum. - pub clause: WhereClause, - // should this struct be serialized/deserialized as its non-diff counterpart/ - pub from_into: bool, + // struct variant. + pub variant: SVariant, + // struct name. + pub name: Ident, + // struct diff name. + pub diff: Ident, + // struct fields. + pub fields: Vec, + // generics and traits declarations. + pub param_decls: Punctuated, + // generics and trait bounds for the Enum. + pub params: Punctuated, + // where clause for the Enum. + pub clause: WhereClause, + // should this struct be serialized/deserialized as its non-diff counterpart/ + pub from_into: bool, } /// Enum variant data. #[derive(Clone, Debug)] pub struct EVariant { - // Struct variant type. - pub variant: SVariant, - // variant name. - pub name: Ident, - // variant fields. - pub fields: Vec, + // Struct variant type. + pub variant: SVariant, + // variant name. + pub name: Ident, + // variant fields. + pub fields: Vec, } /// Struct Variant structure types. #[derive(Clone, Debug)] pub enum SVariant { - Named, - Tuple, - Unit, + Named, + Tuple, + Unit, } /// sorts data for fields inside of a struct or enum. #[derive(Clone, Debug)] pub enum DataFields { - Named { - // field name. - name: Ident, - // field type. - typ: Type, - // should ignore flag. - should_ignore: bool, - }, - Unnamed { - // field position. - position: Literal, - // field type. - typ: Type, - // should ignore flag. - should_ignore: bool, - }, + Named { + // field name. + name: Ident, + // field type. + typ: Type, + // should ignore flag. + should_ignore: bool, + }, + Unnamed { + // field position. + position: Literal, + // field type. + typ: Type, + // should ignore flag. + should_ignore: bool, + }, } impl InputModel { - // parse the `DeriveInput` into an `InputModel`. - pub fn parse(input: &DeriveInput) -> Self { - match &input.data { - // Check for a struct with fields. - Data::Struct(DataStruct { fields, .. }) if !fields.is_empty() => Self::parse_struct(input, fields), - // check for a unit struct. - Data::Struct(DataStruct { .. }) => Self::parse_unit(input), - // check for an enum. - Data::Enum(DataEnum { variants, .. }) => Self::parse_enum(input, variants), - _ => panic!("Data Type not supported"), - } + // parse the `DeriveInput` into an `InputModel`. + pub fn parse(input: &DeriveInput) -> Self { + match &input.data { + // Check for a struct with fields. + Data::Struct(DataStruct { fields, .. }) if !fields.is_empty() => Self::parse_struct(input, fields), + // check for a unit struct. + Data::Struct(DataStruct { .. }) => Self::parse_unit(input), + // check for an enum. + Data::Enum(DataEnum { variants, .. }) => Self::parse_enum(input, variants), + _ => panic!("Data Type not supported"), } - - /// parse structs. - fn parse_struct(input: &DeriveInput, fields: &Fields) -> Self { - Self::Struct(InputStruct::parse(input, fields)) + } + + /// parse structs. + fn parse_struct(input: &DeriveInput, fields: &Fields) -> Self { + Self::Struct(InputStruct::parse(input, fields)) + } + + /// parse unit structs. + fn parse_unit(input: &DeriveInput) -> Self { + Self::Struct(InputStruct::parse_unit(input)) + } + + /// parse enums. + fn parse_enum(input: &DeriveInput, variants: &Punctuated) -> Self { + Self::Enum(InputEnum::parse(input, variants)) + } + + /// get struct variant. + pub fn s_variant(&self) -> &SVariant { + match self { + Self::Enum(InputEnum { name, .. }) => panic!("{} isn't a struct", name), + Self::Struct(InputStruct { variant, .. }) => variant, } + } - /// parse unit structs. - fn parse_unit(input: &DeriveInput) -> Self { - Self::Struct(InputStruct::parse_unit(input)) + /// get enum variant. + pub fn e_variants(&self) -> &Vec { + match self { + Self::Enum(InputEnum { variants, .. }) => variants, + Self::Struct(InputStruct { name, .. }) => panic!("{} isn't an Enum", name), } + } - /// parse enums. - fn parse_enum(input: &DeriveInput, variants: &Punctuated) -> Self { - Self::Enum(InputEnum::parse(input, variants)) + /// get name of struct or enum. + pub fn name(&self) -> &Ident { + match self { + Self::Enum(InputEnum { name, .. }) => name, + Self::Struct(InputStruct { name, .. }) => name, } + } - /// get struct variant. - pub fn s_variant(&self) -> &SVariant { - match self { - Self::Enum(InputEnum { name, .. }) => panic!("{} isn't a struct", name), - Self::Struct(InputStruct { variant, .. }) => variant, - } + /// get diff name for enum or struct. + pub fn diff(&self) -> &Ident { + match self { + Self::Enum(InputEnum { diff, .. }) => diff, + Self::Struct(InputStruct { diff, .. }) => diff, } + } - /// get enum variant. - pub fn e_variants(&self) -> &Vec { - match self { - Self::Enum(InputEnum { variants, .. }) => variants, - Self::Struct(InputStruct { name, .. }) => panic!("{} isn't an Enum", name), - } + /// get the params for the Enum or Struct. + pub fn params(&self) -> &Punctuated { + match self { + Self::Enum(InputEnum { params, .. }) => params, + Self::Struct(InputStruct { params, .. }) => params, } + } - /// get name of struct or enum. - pub fn name(&self) -> &Ident { - match self { - Self::Enum(InputEnum { name, .. }) => name, - Self::Struct(InputStruct { name, .. }) => name, - } + /// get the param declarations for the Enum or Struct. + pub fn param_decls(&self) -> &Punctuated { + match self { + Self::Enum(InputEnum { param_decls, .. }) => param_decls, + Self::Struct(InputStruct { param_decls, .. }) => param_decls, } + } - /// get diff name for enum or struct. - pub fn diff(&self) -> &Ident { - match self { - Self::Enum(InputEnum { diff, .. }) => diff, - Self::Struct(InputStruct { diff, .. }) => diff, - } + /// get the fields for the Enum or Struct. + pub fn fields(&self) -> &Vec { + match self { + Self::Enum(InputEnum { name, .. }) => panic!("{} isn't a Struct", name), + Self::Struct(InputStruct { fields, .. }) => fields, } + } - /// get the params for the Enum or Struct. - pub fn params(&self) -> &Punctuated { - match self { - Self::Enum(InputEnum { params, .. }) => params, - Self::Struct(InputStruct { params, .. }) => params, - } + /// Get the where clause. + pub fn clause(&self) -> &WhereClause { + match self { + Self::Enum(InputEnum { clause, .. }) => clause, + Self::Struct(InputStruct { clause, .. }) => clause, } + } - /// get the param declarations for the Enum or Struct. - pub fn param_decls(&self) -> &Punctuated { - match self { - Self::Enum(InputEnum { param_decls, .. }) => param_decls, - Self::Struct(InputStruct { param_decls, .. }) => param_decls, - } + /// Implement the `Debug` trait on Enums and Structs. + pub fn impl_debug(&self) -> TokenStream { + match self { + Self::Struct(InputStruct { .. }) => debug_impl(self), + Self::Enum(InputEnum { .. }) => impl_debug_enum(self), } + } - /// get the fields for the Enum or Struct. - pub fn fields(&self) -> &Vec { - match self { - Self::Enum(InputEnum { name, .. }) => panic!("{} isn't a Struct", name), - Self::Struct(InputStruct { fields, .. }) => fields, - } + pub fn impl_from_into(&self) -> TokenStream { + match self { + Self::Struct(InputStruct { .. }) => impl_from_into(self), + Self::Enum(InputEnum { .. }) => impl_from_into(self), } + } - /// Get the where clause. - pub fn clause(&self) -> &WhereClause { - match self { - Self::Enum(InputEnum { clause, .. }) => clause, - Self::Struct(InputStruct { clause, .. }) => clause, - } + /// Implement the `Diff` trait on Enums and Structs. + pub fn impl_diff(&self) -> TokenStream { + match self { + Self::Struct(InputStruct { .. }) => diff_impl(self), + Self::Enum(InputEnum { .. }) => impl_diff_enum(self), } + } - /// Implement the `Debug` trait on Enums and Structs. - pub fn impl_debug(&self) -> TokenStream { - match self { - Self::Struct(InputStruct { .. }) => debug_impl(self), - Self::Enum(InputEnum { .. }) => impl_debug_enum(self), - } + /// Build the Diff Type for the Enum or Struct. + pub fn derive_diff(&self) -> TokenStream { + match self { + Self::Struct(InputStruct { .. }) => derive_diff_struct(self), + Self::Enum(InputEnum { .. }) => derive_diff_enum(self), } + } - pub fn impl_from_into(&self) -> TokenStream { - match self { - Self::Struct(InputStruct { .. }) => impl_from_into(self), - Self::Enum(InputEnum { .. }) => impl_from_into(self), - } - } - - /// Implement the `Diff` trait on Enums and Structs. - pub fn impl_diff(&self) -> TokenStream { - match self { - Self::Struct(InputStruct { .. }) => diff_impl(self), - Self::Enum(InputEnum { .. }) => impl_diff_enum(self), - } - } - - /// Build the Diff Type for the Enum or Struct. - pub fn derive_diff(&self) -> TokenStream { - match self { - Self::Struct(InputStruct { .. }) => derive_diff_struct(self), - Self::Enum(InputEnum { .. }) => derive_diff_enum(self), - } - } - - pub fn from_into(&self) -> bool { - match self { - Self::Struct(InputStruct { from_into, .. }) => *from_into, - Self::Enum(InputEnum { from_into, .. }) => *from_into, - } + pub fn from_into(&self) -> bool { + match self { + Self::Struct(InputStruct { from_into, .. }) => *from_into, + Self::Enum(InputEnum { from_into, .. }) => *from_into, } + } } impl InputEnum { - /// create a new `InputEnum`. - pub fn new(input: &DeriveInput) -> Self { - let from_into = parse_from_into(&input); - Self { - name: input.ident.clone(), - diff: format_ident!("Diff{}", &input.ident), - variants: Vec::new(), - param_decls: input.generics.params.clone(), - params: input - .generics - .type_params() - .map(|type_param| type_param.ident.clone()) - .collect(), - clause: input.generics.where_clause.clone().unwrap_or_else(|| WhereClause { - where_token: Token![where](Span::call_site()), - predicates: Punctuated::new(), - }), - from_into, - } + /// create a new `InputEnum`. + pub fn new(input: &DeriveInput) -> Self { + let from_into = parse_from_into(&input); + Self { + name: input.ident.clone(), + diff: format_ident!("Diff{}", &input.ident), + variants: Vec::new(), + param_decls: input.generics.params.clone(), + params: input + .generics + .type_params() + .map(|type_param| type_param.ident.clone()) + .collect(), + clause: input.generics.where_clause.clone().unwrap_or_else(|| WhereClause { + where_token: Token![where](Span::call_site()), + predicates: Punctuated::new(), + }), + from_into, } + } + + /// parse the enum. + fn parse(input: &DeriveInput, variants: &Punctuated) -> Self { + let mut model = Self::new(input); + variants.iter().for_each(|vars| { + let mut variant = EVariant::new(&vars.ident); + + vars.fields.iter().enumerate().for_each(|(idx, fs)| { + if let Some(ident) = fs.ident.as_ref() { + variant.variant = SVariant::Named; + variant.fields.push(DataFields::Named { + name: ident.clone(), + typ: fs.ty.clone(), + should_ignore: should_ignore(fs), + }); + } else { + variant.variant = SVariant::Tuple; + variant.fields.push(DataFields::Unnamed { + position: Literal::usize_unsuffixed(idx), + typ: fs.ty.clone(), + should_ignore: should_ignore(fs), + }); + } + }); + model.variants.push(variant); + }); - /// parse the enum. - fn parse(input: &DeriveInput, variants: &Punctuated) -> Self { - let mut model = Self::new(input); - variants.iter().for_each(|vars| { - let mut variant = EVariant::new(&vars.ident); - - vars.fields.iter().enumerate().for_each(|(idx, fs)| { - if let Some(ident) = fs.ident.as_ref() { - variant.variant = SVariant::Named; - variant.fields.push(DataFields::Named { - name: ident.clone(), - typ: fs.ty.clone(), - should_ignore: should_ignore(fs), - }); - } else { - variant.variant = SVariant::Tuple; - variant.fields.push(DataFields::Unnamed { - position: Literal::usize_unsuffixed(idx), - typ: fs.ty.clone(), - should_ignore: should_ignore(fs), - }); - } - }); - model.variants.push(variant); - }); - - model - } + model + } } impl InputStruct { - /// create a new `InputStruct`. - pub fn new(input: &DeriveInput) -> Self { - let from_into = parse_from_into(&input); - - Self { - variant: SVariant::Unit, - name: input.ident.clone(), - diff: format_ident!("Diff{}", &input.ident), - fields: Vec::new(), - param_decls: input.generics.params.clone(), - params: input.generics.type_params().map(|tp| tp.ident.clone()).collect(), - clause: input.generics.where_clause.clone().unwrap_or_else(|| WhereClause { - where_token: Token![where](Span::call_site()), - predicates: Punctuated::new(), - }), - from_into, - } + /// create a new `InputStruct`. + pub fn new(input: &DeriveInput) -> Self { + let from_into = parse_from_into(&input); + + Self { + variant: SVariant::Unit, + name: input.ident.clone(), + diff: format_ident!("Diff{}", &input.ident), + fields: Vec::new(), + param_decls: input.generics.params.clone(), + params: input.generics.type_params().map(|tp| tp.ident.clone()).collect(), + clause: input.generics.where_clause.clone().unwrap_or_else(|| WhereClause { + where_token: Token![where](Span::call_site()), + predicates: Punctuated::new(), + }), + from_into, } - - /// parse the ast into for the `InputStruct`. - fn parse(input: &DeriveInput, fields: &Fields) -> Self { - let mut model = Self::new(input); - fields.iter().enumerate().for_each(|(idx, fs)| { - if let Some(ident) = fs.ident.as_ref() { - model.variant = SVariant::Named; - model.fields.push(DataFields::Named { - name: ident.clone(), - typ: fs.ty.clone(), - should_ignore: should_ignore(fs), - }); - } else { - model.variant = SVariant::Tuple; - model.fields.push(DataFields::Unnamed { - position: Literal::usize_unsuffixed(idx), - typ: fs.ty.clone(), - should_ignore: should_ignore(fs), - }); - } + } + + /// parse the ast into for the `InputStruct`. + fn parse(input: &DeriveInput, fields: &Fields) -> Self { + let mut model = Self::new(input); + fields.iter().enumerate().for_each(|(idx, fs)| { + if let Some(ident) = fs.ident.as_ref() { + model.variant = SVariant::Named; + model.fields.push(DataFields::Named { + name: ident.clone(), + typ: fs.ty.clone(), + should_ignore: should_ignore(fs), }); + } else { + model.variant = SVariant::Tuple; + model.fields.push(DataFields::Unnamed { + position: Literal::usize_unsuffixed(idx), + typ: fs.ty.clone(), + should_ignore: should_ignore(fs), + }); + } + }); - model - } + model + } - /// parse data for a unit struct. - fn parse_unit(input: &DeriveInput) -> Self { - let mut model = Self::new(input); - model.variant = SVariant::Unit; + /// parse data for a unit struct. + fn parse_unit(input: &DeriveInput) -> Self { + let mut model = Self::new(input); + model.variant = SVariant::Unit; - model - } + model + } } impl EVariant { - /// create a new enum variant type. - pub fn new(name: &Ident) -> Self { - Self { - variant: SVariant::Unit, - name: name.clone(), - fields: Vec::new(), - } + /// create a new enum variant type. + pub fn new(name: &Ident) -> Self { + Self { + variant: SVariant::Unit, + name: name.clone(), + fields: Vec::new(), } + } } impl DataFields { - /// get the field name. - pub fn name(&self) -> &Ident { - match self { - Self::Named { name, .. } => name, - Self::Unnamed { .. } => panic!("Positional Field has no name"), - } + /// get the field name. + pub fn name(&self) -> &Ident { + match self { + Self::Named { name, .. } => name, + Self::Unnamed { .. } => panic!("Positional Field has no name"), } + } - /// get the field position. - pub fn position(&self) -> &Literal { - match self { - Self::Named { .. } => panic!("Named fields has no position"), - Self::Unnamed { position, .. } => position, - } + /// get the field position. + pub fn position(&self) -> &Literal { + match self { + Self::Named { .. } => panic!("Named fields has no position"), + Self::Unnamed { position, .. } => position, } + } - /// get the field type. - pub fn typ(&self) -> &Type { - match self { - Self::Named { typ, .. } => typ, - Self::Unnamed { typ, .. } => typ, - } + /// get the field type. + pub fn typ(&self) -> &Type { + match self { + Self::Named { typ, .. } => typ, + Self::Unnamed { typ, .. } => typ, } + } - /// get the type of the field wrapped in an `Option` where T = the field type for an ignored field and T is an - /// `identity_diff::Diff::Type` for a non-ignored field. - pub fn typ_as_tokens(&self) -> TokenStream { - let typ = self.typ(); + /// get the type of the field wrapped in an `Option` where T = the field type for an ignored field and T is an + /// `identity_diff::Diff::Type` for a non-ignored field. + pub fn typ_as_tokens(&self) -> TokenStream { + let typ = self.typ(); - if self.should_ignore() { - quote! {Option<#typ>} - } else { - quote! { Option<<#typ as identity_diff::Diff>::Type> } - } + if self.should_ignore() { + quote! {Option<#typ>} + } else { + quote! { Option<<#typ as identity_diff::Diff>::Type> } } + } - /// check if the field is an Option to avoid nested Options. - pub fn is_option(&self) -> bool { - let typ = self.typ(); + /// check if the field is an Option to avoid nested Options. + pub fn is_option(&self) -> bool { + let typ = self.typ(); - let opt = match typ { - syn::Type::Path(typepath) if typepath.qself.is_none() => Some(typepath.path.clone()), - _ => None, - }; + let opt = match typ { + syn::Type::Path(typepath) if typepath.qself.is_none() => Some(typepath.path.clone()), + _ => None, + }; - if let Some(o) = opt { - extract_option_segment(&o).is_some() - } else { - false - } + if let Some(o) = opt { + extract_option_segment(&o).is_some() + } else { + false } + } - /// check to see if the should ignore flag is set for the field. - pub fn should_ignore(&self) -> bool { - match self { - Self::Named { should_ignore, .. } => *should_ignore, - Self::Unnamed { should_ignore, .. } => *should_ignore, - } + /// check to see if the should ignore flag is set for the field. + pub fn should_ignore(&self) -> bool { + match self { + Self::Named { should_ignore, .. } => *should_ignore, + Self::Unnamed { should_ignore, .. } => *should_ignore, } + } } diff --git a/identity-diff/derive/src/utils.rs b/identity-diff/derive/src/utils.rs index ceaf5305f6..de18986c2c 100644 --- a/identity-diff/derive/src/utils.rs +++ b/identity-diff/derive/src/utils.rs @@ -12,64 +12,64 @@ const PARENS: Delimiter = Delimiter::Parenthesis; /// checks to see if a field's type is `Option`. This logic is necessary to find cases where fields contain nested /// Options and avoid a `Some(None)` case. pub fn extract_option_segment(path: &Path) -> Option<&PathSegment> { - let idents_of_path = path.segments.iter().fold(String::new(), |mut acc, v| { - acc.push_str(&v.ident.to_string()); - acc.push('|'); - acc - }); - vec!["Option|", "std|option|Option|", "core|option|Option|"] - .into_iter() - .find(|s| idents_of_path == *s) - .and_then(|_| path.segments.last()) + let idents_of_path = path.segments.iter().fold(String::new(), |mut acc, v| { + acc.push_str(&v.ident.to_string()); + acc.push('|'); + acc + }); + vec!["Option|", "std|option|Option|", "core|option|Option|"] + .into_iter() + .find(|s| idents_of_path == *s) + .and_then(|_| path.segments.last()) } /// checks to see if the `should_ignore` attribute has been put before a field. pub fn should_ignore(field: &Field) -> bool { - let find = field.attrs.iter().find(|field| { - let attr_seg: Vec = field.path.segments.iter().map(|seg| format!("{}", seg.ident)).collect(); + let find = field.attrs.iter().find(|field| { + let attr_seg: Vec = field.path.segments.iter().map(|seg| format!("{}", seg.ident)).collect(); - let diff_attr = attr_seg == ["diff"]; - let arg_iter = field.tokens.clone().into_iter().next(); + let diff_attr = attr_seg == ["diff"]; + let arg_iter = field.tokens.clone().into_iter().next(); - let should_ignore = match arg_iter { - Some(TokenTree::Group(gr)) if gr.delimiter() == PARENS => { - let tokens: Vec = gr.stream().into_iter().map(|tt| format!("{}", tt)).collect(); + let should_ignore = match arg_iter { + Some(TokenTree::Group(gr)) if gr.delimiter() == PARENS => { + let tokens: Vec = gr.stream().into_iter().map(|tt| format!("{}", tt)).collect(); - tokens.contains(&"should_ignore".into()) - } - _ => false, - }; + tokens.contains(&"should_ignore".into()) + } + _ => false, + }; - diff_attr && should_ignore - }); + diff_attr && should_ignore + }); - find.is_some() + find.is_some() } pub fn parse_from_into(input: &DeriveInput) -> bool { - let find = input.attrs.iter().find(|a| { - if let Meta::List(MetaList { path, nested, .. }) = a.parse_meta().unwrap() { - { - if let Some(ident) = path.get_ident() { - if "diff" == format!("{}", format_ident!("{}", ident)) { - let find_nested = nested.iter().find(|m| { - if let NestedMeta::Meta(Meta::Path(p)) = m { - if let Some(ident) = p.get_ident() { - if "from_into" == format!("{}", format_ident!("{}", ident)) { - return true; - } - } - } - false - }); - - return find_nested.is_some(); - } + let find = input.attrs.iter().find(|a| { + if let Meta::List(MetaList { path, nested, .. }) = a.parse_meta().unwrap() { + { + if let Some(ident) = path.get_ident() { + if "diff" == format!("{}", format_ident!("{}", ident)) { + let find_nested = nested.iter().find(|m| { + if let NestedMeta::Meta(Meta::Path(p)) = m { + if let Some(ident) = p.get_ident() { + if "from_into" == format!("{}", format_ident!("{}", ident)) { + return true; + } } - } + } + false + }); + + return find_nested.is_some(); + } } - false - }); + } + } + false + }); - find.is_some() + find.is_some() } diff --git a/identity-diff/src/did_doc.rs b/identity-diff/src/did_doc.rs index 462ac8b7e3..4f17f0046d 100644 --- a/identity-diff/src/did_doc.rs +++ b/identity-diff/src/did_doc.rs @@ -3,8 +3,8 @@ use core::convert::TryFrom as _; use did_doc::{ - url::Url, DIDKey, Document, DocumentBuilder, Method, MethodBuilder, MethodData, MethodRef, MethodType, Object, - OrderedSet, Service, ServiceBuilder, + url::Url, DIDKey, Document, DocumentBuilder, Method, MethodBuilder, MethodData, MethodRef, MethodType, Object, + OrderedSet, Service, ServiceBuilder, }; use did_url::DID; use serde::{Deserialize, Serialize}; @@ -12,61 +12,63 @@ use serde_json::Value; use std::collections::HashMap; use crate::{ - error::{Error, Result}, - hashmap::DiffHashMap, - string::DiffString, - traits::Diff, - vec::DiffVec, + error::{Error, Result}, + hashmap::DiffHashMap, + string::DiffString, + traits::Diff, + vec::DiffVec, }; // ============================================================================= // ============================================================================= impl Diff for DID { - type Type = DiffString; - - fn diff(&self, other: &Self) -> Result { - self.to_string().diff(&other.to_string()) - } - - fn merge(&self, diff: Self::Type) -> Result { - self.to_string() - .merge(diff) - .and_then(|this| Self::parse(&this).map_err(Error::merge)) - } - - fn from_diff(diff: Self::Type) -> Result { - String::from_diff(diff).and_then(|this| Self::parse(&this).map_err(Error::convert)) - } - - fn into_diff(self) -> Result { - self.to_string().into_diff() - } + type Type = DiffString; + + fn diff(&self, other: &Self) -> Result { + self.to_string().diff(&other.to_string()) + } + + fn merge(&self, diff: Self::Type) -> Result { + self + .to_string() + .merge(diff) + .and_then(|this| Self::parse(&this).map_err(Error::merge)) + } + + fn from_diff(diff: Self::Type) -> Result { + String::from_diff(diff).and_then(|this| Self::parse(&this).map_err(Error::convert)) + } + + fn into_diff(self) -> Result { + self.to_string().into_diff() + } } // ============================================================================= // ============================================================================= impl Diff for Url { - type Type = DiffString; - - fn diff(&self, other: &Self) -> Result { - self.to_string().diff(&other.to_string()) - } - - fn merge(&self, diff: Self::Type) -> Result { - self.to_string() - .merge(diff) - .and_then(|this| Self::parse(&this).map_err(Error::merge)) - } - - fn from_diff(diff: Self::Type) -> Result { - String::from_diff(diff).and_then(|this| Self::parse(&this).map_err(Error::convert)) - } - - fn into_diff(self) -> Result { - self.to_string().into_diff() - } + type Type = DiffString; + + fn diff(&self, other: &Self) -> Result { + self.to_string().diff(&other.to_string()) + } + + fn merge(&self, diff: Self::Type) -> Result { + self + .to_string() + .merge(diff) + .and_then(|this| Self::parse(&this).map_err(Error::merge)) + } + + fn from_diff(diff: Self::Type) -> Result { + String::from_diff(diff).and_then(|this| Self::parse(&this).map_err(Error::convert)) + } + + fn into_diff(self) -> Result { + self.to_string().into_diff() + } } // ============================================================================= @@ -74,28 +76,29 @@ impl Diff for Url { impl Diff for OrderedSet where - T: Diff + Serialize + for<'de> Deserialize<'de>, + T: Diff + Serialize + for<'de> Deserialize<'de>, { - type Type = DiffVec; - - fn diff(&self, other: &Self) -> Result { - self.clone().into_vec().diff(&other.clone().into_vec()) - } - - fn merge(&self, diff: Self::Type) -> Result { - self.clone() - .into_vec() - .merge(diff) - .and_then(|this| Self::try_from(this).map_err(Error::merge)) - } - - fn from_diff(diff: Self::Type) -> Result { - Vec::from_diff(diff).and_then(|this| Self::try_from(this).map_err(Error::convert)) - } - - fn into_diff(self) -> Result { - self.into_vec().into_diff() - } + type Type = DiffVec; + + fn diff(&self, other: &Self) -> Result { + self.clone().into_vec().diff(&other.clone().into_vec()) + } + + fn merge(&self, diff: Self::Type) -> Result { + self + .clone() + .into_vec() + .merge(diff) + .and_then(|this| Self::try_from(this).map_err(Error::merge)) + } + + fn from_diff(diff: Self::Type) -> Result { + Vec::from_diff(diff).and_then(|this| Self::try_from(this).map_err(Error::convert)) + } + + fn into_diff(self) -> Result { + self.into_vec().into_diff() + } } // ============================================================================= @@ -103,25 +106,25 @@ where impl Diff for DIDKey where - T: AsRef + Diff, + T: AsRef + Diff, { - type Type = ::Type; + type Type = ::Type; - fn diff(&self, other: &Self) -> Result { - self.clone().into_inner().diff(&other.clone().into_inner()) - } + fn diff(&self, other: &Self) -> Result { + self.clone().into_inner().diff(&other.clone().into_inner()) + } - fn merge(&self, diff: Self::Type) -> Result { - self.clone().into_inner().merge(diff).map(Self::new) - } + fn merge(&self, diff: Self::Type) -> Result { + self.clone().into_inner().merge(diff).map(Self::new) + } - fn from_diff(diff: Self::Type) -> Result { - T::from_diff(diff).map(Self::new) - } + fn from_diff(diff: Self::Type) -> Result { + T::from_diff(diff).map(Self::new) + } - fn into_diff(self) -> Result { - self.into_inner().into_diff() - } + fn into_diff(self) -> Result { + self.into_inner().into_diff() + } } // ============================================================================= @@ -131,29 +134,29 @@ pub type DiffObject = DiffHashMap; pub type MapProxy = HashMap; impl Diff for Object { - type Type = DiffObject; + type Type = DiffObject; - fn diff(&self, other: &Self) -> Result { - let a: MapProxy = self.clone().into_iter().collect(); - let b: MapProxy = other.clone().into_iter().collect(); + fn diff(&self, other: &Self) -> Result { + let a: MapProxy = self.clone().into_iter().collect(); + let b: MapProxy = other.clone().into_iter().collect(); - a.diff(&b) - } + a.diff(&b) + } - fn merge(&self, diff: Self::Type) -> Result { - let this: MapProxy = self.clone().into_iter().collect(); - let this: MapProxy = this.merge(diff)?; + fn merge(&self, diff: Self::Type) -> Result { + let this: MapProxy = self.clone().into_iter().collect(); + let this: MapProxy = this.merge(diff)?; - Ok(this.into_iter().collect()) - } + Ok(this.into_iter().collect()) + } - fn from_diff(diff: Self::Type) -> Result { - Ok(MapProxy::from_diff(diff)?.into_iter().collect()) - } + fn from_diff(diff: Self::Type) -> Result { + Ok(MapProxy::from_diff(diff)?.into_iter().collect()) + } - fn into_diff(self) -> Result { - self.into_iter().collect::().into_diff() - } + fn into_diff(self) -> Result { + self.into_iter().collect::().into_diff() + } } // ============================================================================= @@ -163,344 +166,344 @@ impl Diff for Object { #[serde(bound(deserialize = ""))] pub struct DiffDocument where - T: Diff + Serialize + for<'__de> Deserialize<'__de>, - U: Diff + Serialize + for<'__de> Deserialize<'__de>, - V: Diff + Serialize + for<'__de> Deserialize<'__de>, + T: Diff + Serialize + for<'__de> Deserialize<'__de>, + U: Diff + Serialize + for<'__de> Deserialize<'__de>, + V: Diff + Serialize + for<'__de> Deserialize<'__de>, { - #[serde(skip_serializing_if = "Option::is_none")] - id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - controller: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - also_known_as: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - verification_method: Option>>>, - #[serde(skip_serializing_if = "Option::is_none")] - authentication: Option>>>, - #[serde(skip_serializing_if = "Option::is_none")] - assertion_method: Option>>>, - #[serde(skip_serializing_if = "Option::is_none")] - key_agreement: Option>>>, - #[serde(skip_serializing_if = "Option::is_none")] - capability_delegation: Option>>>, - #[serde(skip_serializing_if = "Option::is_none")] - capability_invocation: Option>>>, - #[serde(skip_serializing_if = "Option::is_none")] - service: Option>>>, - #[serde(skip_serializing_if = "Option::is_none")] - properties: Option<::Type>, + #[serde(skip_serializing_if = "Option::is_none")] + id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + controller: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + also_known_as: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + verification_method: Option>>>, + #[serde(skip_serializing_if = "Option::is_none")] + authentication: Option>>>, + #[serde(skip_serializing_if = "Option::is_none")] + assertion_method: Option>>>, + #[serde(skip_serializing_if = "Option::is_none")] + key_agreement: Option>>>, + #[serde(skip_serializing_if = "Option::is_none")] + capability_delegation: Option>>>, + #[serde(skip_serializing_if = "Option::is_none")] + capability_invocation: Option>>>, + #[serde(skip_serializing_if = "Option::is_none")] + service: Option>>>, + #[serde(skip_serializing_if = "Option::is_none")] + properties: Option<::Type>, } impl Diff for Document where - T: Diff + Serialize + for<'de> Deserialize<'de>, - U: Diff + Serialize + for<'de> Deserialize<'de>, - V: Diff + Serialize + for<'de> Deserialize<'de>, + T: Diff + Serialize + for<'de> Deserialize<'de>, + U: Diff + Serialize + for<'de> Deserialize<'de>, + V: Diff + Serialize + for<'de> Deserialize<'de>, { - type Type = DiffDocument; - - fn diff(&self, other: &Self) -> Result { - Ok(DiffDocument { - id: if self.id() == other.id() { - None - } else { - Some(self.id().diff(other.id())?) - }, - controller: if self.controller() == other.controller() { - None - } else { - match (self.controller(), other.controller()) { - (Some(a), Some(b)) => Some(Some(a.diff(&b)?)), - (None, Some(b)) => Some(Some(b.clone().into_diff()?)), - _ => Some(None), - } - }, - also_known_as: if self.also_known_as() == other.also_known_as() { - None - } else { - Some(self.also_known_as().to_vec().diff(&other.also_known_as().to_vec())?) - }, - verification_method: if self.verification_method() == other.verification_method() { - None - } else { - Some(self.verification_method().diff(other.verification_method())?) - }, - authentication: if self.authentication() == other.authentication() { - None - } else { - Some(self.authentication().diff(other.authentication())?) - }, - assertion_method: if self.assertion_method() == other.assertion_method() { - None - } else { - Some(self.assertion_method().diff(other.assertion_method())?) - }, - key_agreement: if self.key_agreement() == other.key_agreement() { - None - } else { - Some(self.key_agreement().diff(other.key_agreement())?) - }, - capability_delegation: if self.capability_delegation() == other.capability_delegation() { - None - } else { - Some(self.capability_delegation().diff(other.capability_delegation())?) - }, - capability_invocation: if self.capability_invocation() == other.capability_invocation() { - None - } else { - Some(self.capability_invocation().diff(other.capability_invocation())?) - }, - service: if self.service() == other.service() { - None - } else { - Some(self.service().diff(&other.service())?) - }, - properties: if self.properties() == other.properties() { - None - } else { - Some(self.properties().diff(other.properties())?) - }, - }) + type Type = DiffDocument; + + fn diff(&self, other: &Self) -> Result { + Ok(DiffDocument { + id: if self.id() == other.id() { + None + } else { + Some(self.id().diff(other.id())?) + }, + controller: if self.controller() == other.controller() { + None + } else { + match (self.controller(), other.controller()) { + (Some(a), Some(b)) => Some(Some(a.diff(&b)?)), + (None, Some(b)) => Some(Some(b.clone().into_diff()?)), + _ => Some(None), + } + }, + also_known_as: if self.also_known_as() == other.also_known_as() { + None + } else { + Some(self.also_known_as().to_vec().diff(&other.also_known_as().to_vec())?) + }, + verification_method: if self.verification_method() == other.verification_method() { + None + } else { + Some(self.verification_method().diff(other.verification_method())?) + }, + authentication: if self.authentication() == other.authentication() { + None + } else { + Some(self.authentication().diff(other.authentication())?) + }, + assertion_method: if self.assertion_method() == other.assertion_method() { + None + } else { + Some(self.assertion_method().diff(other.assertion_method())?) + }, + key_agreement: if self.key_agreement() == other.key_agreement() { + None + } else { + Some(self.key_agreement().diff(other.key_agreement())?) + }, + capability_delegation: if self.capability_delegation() == other.capability_delegation() { + None + } else { + Some(self.capability_delegation().diff(other.capability_delegation())?) + }, + capability_invocation: if self.capability_invocation() == other.capability_invocation() { + None + } else { + Some(self.capability_invocation().diff(other.capability_invocation())?) + }, + service: if self.service() == other.service() { + None + } else { + Some(self.service().diff(&other.service())?) + }, + properties: if self.properties() == other.properties() { + None + } else { + Some(self.properties().diff(other.properties())?) + }, + }) + } + + fn merge(&self, diff: Self::Type) -> Result { + let id: DID = diff + .id + .map(|value| self.id().merge(value)) + .transpose()? + .unwrap_or_else(|| self.id().clone()); + + let controller: Option = diff + .controller + .flatten() + .and_then(|value| self.controller().map(|controller| controller.merge(value))) + .transpose()?; + + let also_known_as: Vec = diff + .also_known_as + .map(|value| self.also_known_as().to_vec().merge(value)) + .transpose()? + .unwrap_or_else(|| self.also_known_as().to_vec()); + + let verification_method: OrderedSet>> = diff + .verification_method + .map(|value| self.verification_method().merge(value)) + .transpose()? + .unwrap_or_else(|| self.verification_method().clone()); + + let authentication: OrderedSet>> = diff + .authentication + .map(|value| self.authentication().merge(value)) + .transpose()? + .unwrap_or_else(|| self.authentication().clone()); + + let assertion_method: OrderedSet>> = diff + .assertion_method + .map(|value| self.assertion_method().merge(value)) + .transpose()? + .unwrap_or_else(|| self.assertion_method().clone()); + + let key_agreement: OrderedSet>> = diff + .key_agreement + .map(|value| self.key_agreement().merge(value)) + .transpose()? + .unwrap_or_else(|| self.key_agreement().clone()); + + let capability_delegation: OrderedSet>> = diff + .capability_delegation + .map(|value| self.capability_delegation().merge(value)) + .transpose()? + .unwrap_or_else(|| self.capability_delegation().clone()); + + let capability_invocation: OrderedSet>> = diff + .capability_invocation + .map(|value| self.capability_invocation().merge(value)) + .transpose()? + .unwrap_or_else(|| self.capability_invocation().clone()); + + let service: OrderedSet>> = diff + .service + .map(|value| self.service().merge(value)) + .transpose()? + .unwrap_or_else(|| self.service().clone()); + + let properties: T = diff + .properties + .map(|value| self.properties().merge(value)) + .transpose()? + .unwrap_or_else(|| self.properties().clone()); + + let mut builder: DocumentBuilder = DocumentBuilder::new(properties); + + builder = builder.id(id); + + if let Some(controller) = controller { + builder = builder.controller(controller); + } + + for element in also_known_as { + builder = builder.also_known_as(element); + } + + for element in verification_method.to_vec() { + builder = builder.verification_method(element.into_inner()); + } + + for element in authentication.to_vec() { + builder = builder.authentication(element.into_inner()); + } + + for element in assertion_method.to_vec() { + builder = builder.assertion_method(element.into_inner()); + } + + for element in key_agreement.to_vec() { + builder = builder.key_agreement(element.into_inner()); } - fn merge(&self, diff: Self::Type) -> Result { - let id: DID = diff - .id - .map(|value| self.id().merge(value)) - .transpose()? - .unwrap_or_else(|| self.id().clone()); - - let controller: Option = diff - .controller - .flatten() - .and_then(|value| self.controller().map(|controller| controller.merge(value))) - .transpose()?; - - let also_known_as: Vec = diff - .also_known_as - .map(|value| self.also_known_as().to_vec().merge(value)) - .transpose()? - .unwrap_or_else(|| self.also_known_as().to_vec()); - - let verification_method: OrderedSet>> = diff - .verification_method - .map(|value| self.verification_method().merge(value)) - .transpose()? - .unwrap_or_else(|| self.verification_method().clone()); - - let authentication: OrderedSet>> = diff - .authentication - .map(|value| self.authentication().merge(value)) - .transpose()? - .unwrap_or_else(|| self.authentication().clone()); - - let assertion_method: OrderedSet>> = diff - .assertion_method - .map(|value| self.assertion_method().merge(value)) - .transpose()? - .unwrap_or_else(|| self.assertion_method().clone()); - - let key_agreement: OrderedSet>> = diff - .key_agreement - .map(|value| self.key_agreement().merge(value)) - .transpose()? - .unwrap_or_else(|| self.key_agreement().clone()); - - let capability_delegation: OrderedSet>> = diff - .capability_delegation - .map(|value| self.capability_delegation().merge(value)) - .transpose()? - .unwrap_or_else(|| self.capability_delegation().clone()); - - let capability_invocation: OrderedSet>> = diff - .capability_invocation - .map(|value| self.capability_invocation().merge(value)) - .transpose()? - .unwrap_or_else(|| self.capability_invocation().clone()); - - let service: OrderedSet>> = diff - .service - .map(|value| self.service().merge(value)) - .transpose()? - .unwrap_or_else(|| self.service().clone()); - - let properties: T = diff - .properties - .map(|value| self.properties().merge(value)) - .transpose()? - .unwrap_or_else(|| self.properties().clone()); - - let mut builder: DocumentBuilder = DocumentBuilder::new(properties); - - builder = builder.id(id); - - if let Some(controller) = controller { - builder = builder.controller(controller); - } + for element in capability_delegation.to_vec() { + builder = builder.capability_delegation(element.into_inner()); + } - for element in also_known_as { - builder = builder.also_known_as(element); - } + for element in capability_invocation.to_vec() { + builder = builder.capability_invocation(element.into_inner()); + } - for element in verification_method.to_vec() { - builder = builder.verification_method(element.into_inner()); - } + for element in service.to_vec() { + builder = builder.service(element.into_inner()); + } - for element in authentication.to_vec() { - builder = builder.authentication(element.into_inner()); - } + builder.build().map_err(Error::convert) + } - for element in assertion_method.to_vec() { - builder = builder.assertion_method(element.into_inner()); - } + fn from_diff(diff: Self::Type) -> Result { + let id: DID = diff + .id + .map(DID::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `id`"))?; - for element in key_agreement.to_vec() { - builder = builder.key_agreement(element.into_inner()); - } + let controller: Option = diff + .controller + .map(|diff| match diff { + Some(diff) => Some(DID::from_diff(diff)).transpose(), + None => Ok(None), + }) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `controller`"))?; - for element in capability_delegation.to_vec() { - builder = builder.capability_delegation(element.into_inner()); - } + let also_known_as: Vec = diff + .also_known_as + .map(Diff::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `also_known_as`"))?; - for element in capability_invocation.to_vec() { - builder = builder.capability_invocation(element.into_inner()); - } + let verification_method: OrderedSet>> = diff + .verification_method + .map(Diff::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `verification_method`"))?; - for element in service.to_vec() { - builder = builder.service(element.into_inner()); - } + let authentication: OrderedSet>> = diff + .authentication + .map(Diff::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `authentication`"))?; - builder.build().map_err(Error::convert) - } + let assertion_method: OrderedSet>> = diff + .assertion_method + .map(Diff::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `assertion_method`"))?; - fn from_diff(diff: Self::Type) -> Result { - let id: DID = diff - .id - .map(DID::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `id`"))?; - - let controller: Option = diff - .controller - .map(|diff| match diff { - Some(diff) => Some(DID::from_diff(diff)).transpose(), - None => Ok(None), - }) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `controller`"))?; - - let also_known_as: Vec = diff - .also_known_as - .map(Diff::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `also_known_as`"))?; - - let verification_method: OrderedSet>> = diff - .verification_method - .map(Diff::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `verification_method`"))?; - - let authentication: OrderedSet>> = diff - .authentication - .map(Diff::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `authentication`"))?; - - let assertion_method: OrderedSet>> = diff - .assertion_method - .map(Diff::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `assertion_method`"))?; - - let key_agreement: OrderedSet>> = diff - .key_agreement - .map(Diff::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `key_agreement`"))?; - - let capability_delegation: OrderedSet>> = diff - .capability_delegation - .map(Diff::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `capability_delegation`"))?; - - let capability_invocation: OrderedSet>> = diff - .capability_invocation - .map(Diff::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `capability_invocation`"))?; - - let service: OrderedSet>> = diff - .service - .map(Diff::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `service`"))?; - - let properties: T = diff - .properties - .map(T::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `properties`"))?; - - let mut builder: DocumentBuilder = DocumentBuilder::new(properties); - - builder = builder.id(id); - - if let Some(controller) = controller { - builder = builder.controller(controller); - } + let key_agreement: OrderedSet>> = diff + .key_agreement + .map(Diff::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `key_agreement`"))?; - for element in also_known_as { - builder = builder.also_known_as(element); - } + let capability_delegation: OrderedSet>> = diff + .capability_delegation + .map(Diff::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `capability_delegation`"))?; - for element in verification_method.to_vec() { - builder = builder.verification_method(element.into_inner()); - } + let capability_invocation: OrderedSet>> = diff + .capability_invocation + .map(Diff::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `capability_invocation`"))?; - for element in authentication.to_vec() { - builder = builder.authentication(element.into_inner()); - } + let service: OrderedSet>> = diff + .service + .map(Diff::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `service`"))?; - for element in assertion_method.to_vec() { - builder = builder.assertion_method(element.into_inner()); - } + let properties: T = diff + .properties + .map(T::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `properties`"))?; - for element in key_agreement.to_vec() { - builder = builder.key_agreement(element.into_inner()); - } + let mut builder: DocumentBuilder = DocumentBuilder::new(properties); - for element in capability_delegation.to_vec() { - builder = builder.capability_delegation(element.into_inner()); - } + builder = builder.id(id); - for element in capability_invocation.to_vec() { - builder = builder.capability_invocation(element.into_inner()); - } + if let Some(controller) = controller { + builder = builder.controller(controller); + } - for element in service.to_vec() { - builder = builder.service(element.into_inner()); - } + for element in also_known_as { + builder = builder.also_known_as(element); + } + + for element in verification_method.to_vec() { + builder = builder.verification_method(element.into_inner()); + } + + for element in authentication.to_vec() { + builder = builder.authentication(element.into_inner()); + } + + for element in assertion_method.to_vec() { + builder = builder.assertion_method(element.into_inner()); + } + + for element in key_agreement.to_vec() { + builder = builder.key_agreement(element.into_inner()); + } - builder.build().map_err(Error::convert) + for element in capability_delegation.to_vec() { + builder = builder.capability_delegation(element.into_inner()); } - fn into_diff(self) -> Result { - Ok(DiffDocument { - id: Some(self.id().clone().into_diff()?), - controller: Some(self.controller().cloned().map(|value| value.into_diff()).transpose()?), - also_known_as: Some(self.also_known_as().to_vec().into_diff()?), - verification_method: Some(self.verification_method().to_vec().into_diff()?), - authentication: Some(self.authentication().to_vec().into_diff()?), - assertion_method: Some(self.assertion_method().to_vec().into_diff()?), - key_agreement: Some(self.key_agreement().to_vec().into_diff()?), - capability_delegation: Some(self.capability_delegation().to_vec().into_diff()?), - capability_invocation: Some(self.capability_invocation().to_vec().into_diff()?), - service: Some(self.service().to_vec().into_diff()?), - properties: Some(self.properties().clone().into_diff()?), - }) + for element in capability_invocation.to_vec() { + builder = builder.capability_invocation(element.into_inner()); } + + for element in service.to_vec() { + builder = builder.service(element.into_inner()); + } + + builder.build().map_err(Error::convert) + } + + fn into_diff(self) -> Result { + Ok(DiffDocument { + id: Some(self.id().clone().into_diff()?), + controller: Some(self.controller().cloned().map(|value| value.into_diff()).transpose()?), + also_known_as: Some(self.also_known_as().to_vec().into_diff()?), + verification_method: Some(self.verification_method().to_vec().into_diff()?), + authentication: Some(self.authentication().to_vec().into_diff()?), + assertion_method: Some(self.assertion_method().to_vec().into_diff()?), + key_agreement: Some(self.key_agreement().to_vec().into_diff()?), + capability_delegation: Some(self.capability_delegation().to_vec().into_diff()?), + capability_invocation: Some(self.capability_invocation().to_vec().into_diff()?), + service: Some(self.service().to_vec().into_diff()?), + properties: Some(self.properties().clone().into_diff()?), + }) + } } // ============================================================================= @@ -509,145 +512,145 @@ where #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct DiffMethod where - T: Diff, + T: Diff, { - #[serde(skip_serializing_if = "Option::is_none")] - id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - controller: Option, - #[serde(skip_serializing_if = "Option::is_none")] - key_type: Option, - #[serde(skip_serializing_if = "Option::is_none")] - key_data: Option, - #[serde(skip_serializing_if = "Option::is_none")] - properties: Option<::Type>, + #[serde(skip_serializing_if = "Option::is_none")] + id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + controller: Option, + #[serde(skip_serializing_if = "Option::is_none")] + key_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + key_data: Option, + #[serde(skip_serializing_if = "Option::is_none")] + properties: Option<::Type>, } impl Diff for Method where - T: Diff + Serialize + for<'de> Deserialize<'de>, + T: Diff + Serialize + for<'de> Deserialize<'de>, { - type Type = DiffMethod; - - fn diff(&self, other: &Self) -> Result { - Ok(DiffMethod { - id: if self.id() == other.id() { - None - } else { - Some(self.id().diff(other.id())?) - }, - controller: if self.controller() == other.controller() { - None - } else { - Some(self.controller().diff(other.controller())?) - }, - key_type: if self.key_type() == other.key_type() { - None - } else { - Some(self.key_type().diff(&other.key_type())?) - }, - key_data: if self.key_data() == other.key_data() { - None - } else { - Some(self.key_data().diff(other.key_data())?) - }, - properties: if self.properties() == other.properties() { - None - } else { - Some(self.properties().diff(other.properties())?) - }, - }) - } - - fn merge(&self, diff: Self::Type) -> Result { - let id: DID = diff - .id - .map(|value| self.id().merge(value)) - .transpose()? - .unwrap_or_else(|| self.id().clone()); - - let controller: DID = diff - .controller - .map(|value| self.controller().merge(value)) - .transpose()? - .unwrap_or_else(|| self.controller().clone()); - - let key_data: MethodData = diff - .key_data - .map(|value| self.key_data().merge(value)) - .transpose()? - .unwrap_or_else(|| self.key_data().clone()); - - let key_type: MethodType = diff - .key_type - .map(|value| self.key_type().merge(value)) - .transpose()? - .unwrap_or_else(|| self.key_type()); - - let properties: T = diff - .properties - .map(|value| self.properties().merge(value)) - .transpose()? - .unwrap_or_else(|| self.properties().clone()); - - MethodBuilder::new(properties) - .id(id) - .controller(controller) - .key_type(key_type) - .key_data(key_data) - .build() - .map_err(Error::merge) - } - - fn from_diff(diff: Self::Type) -> Result { - let id: DID = diff - .id - .map(DID::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `id`"))?; - - let controller: DID = diff - .controller - .map(DID::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `controller`"))?; - - let key_type: MethodType = diff - .key_type - .map(MethodType::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `key_type`"))?; - - let key_data: MethodData = diff - .key_data - .map(MethodData::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `key_data`"))?; - - let properties: T = diff - .properties - .map(T::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `properties`"))?; - - MethodBuilder::new(properties) - .id(id) - .controller(controller) - .key_type(key_type) - .key_data(key_data) - .build() - .map_err(Error::convert) - } - - fn into_diff(self) -> Result { - Ok(DiffMethod { - id: Some(self.id().clone().into_diff()?), - controller: Some(self.controller().clone().into_diff()?), - key_type: Some(self.key_type().into_diff()?), - key_data: Some(self.key_data().clone().into_diff()?), - properties: Some(self.properties().clone().into_diff()?), - }) - } + type Type = DiffMethod; + + fn diff(&self, other: &Self) -> Result { + Ok(DiffMethod { + id: if self.id() == other.id() { + None + } else { + Some(self.id().diff(other.id())?) + }, + controller: if self.controller() == other.controller() { + None + } else { + Some(self.controller().diff(other.controller())?) + }, + key_type: if self.key_type() == other.key_type() { + None + } else { + Some(self.key_type().diff(&other.key_type())?) + }, + key_data: if self.key_data() == other.key_data() { + None + } else { + Some(self.key_data().diff(other.key_data())?) + }, + properties: if self.properties() == other.properties() { + None + } else { + Some(self.properties().diff(other.properties())?) + }, + }) + } + + fn merge(&self, diff: Self::Type) -> Result { + let id: DID = diff + .id + .map(|value| self.id().merge(value)) + .transpose()? + .unwrap_or_else(|| self.id().clone()); + + let controller: DID = diff + .controller + .map(|value| self.controller().merge(value)) + .transpose()? + .unwrap_or_else(|| self.controller().clone()); + + let key_data: MethodData = diff + .key_data + .map(|value| self.key_data().merge(value)) + .transpose()? + .unwrap_or_else(|| self.key_data().clone()); + + let key_type: MethodType = diff + .key_type + .map(|value| self.key_type().merge(value)) + .transpose()? + .unwrap_or_else(|| self.key_type()); + + let properties: T = diff + .properties + .map(|value| self.properties().merge(value)) + .transpose()? + .unwrap_or_else(|| self.properties().clone()); + + MethodBuilder::new(properties) + .id(id) + .controller(controller) + .key_type(key_type) + .key_data(key_data) + .build() + .map_err(Error::merge) + } + + fn from_diff(diff: Self::Type) -> Result { + let id: DID = diff + .id + .map(DID::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `id`"))?; + + let controller: DID = diff + .controller + .map(DID::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `controller`"))?; + + let key_type: MethodType = diff + .key_type + .map(MethodType::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `key_type`"))?; + + let key_data: MethodData = diff + .key_data + .map(MethodData::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `key_data`"))?; + + let properties: T = diff + .properties + .map(T::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `properties`"))?; + + MethodBuilder::new(properties) + .id(id) + .controller(controller) + .key_type(key_type) + .key_data(key_data) + .build() + .map_err(Error::convert) + } + + fn into_diff(self) -> Result { + Ok(DiffMethod { + id: Some(self.id().clone().into_diff()?), + controller: Some(self.controller().clone().into_diff()?), + key_type: Some(self.key_type().into_diff()?), + key_data: Some(self.key_data().clone().into_diff()?), + properties: Some(self.properties().clone().into_diff()?), + }) + } } // ============================================================================= @@ -656,53 +659,53 @@ where #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum DiffMethodRef where - T: Diff, + T: Diff, { - Embed(#[serde(skip_serializing_if = "Option::is_none")] Option>), - Refer(#[serde(skip_serializing_if = "Option::is_none")] Option), + Embed(#[serde(skip_serializing_if = "Option::is_none")] Option>), + Refer(#[serde(skip_serializing_if = "Option::is_none")] Option), } impl Diff for MethodRef where - T: Diff + Serialize + for<'de> Deserialize<'de>, + T: Diff + Serialize + for<'de> Deserialize<'de>, { - type Type = DiffMethodRef; - - fn diff(&self, other: &Self) -> Result { - match (self, other) { - (Self::Embed(a), Self::Embed(b)) if a == b => Ok(DiffMethodRef::Embed(None)), - (Self::Embed(a), Self::Embed(b)) => a.diff(b).map(Some).map(DiffMethodRef::Embed), - (Self::Refer(a), Self::Refer(b)) if a == b => Ok(DiffMethodRef::Refer(None)), - (Self::Refer(a), Self::Refer(b)) => a.diff(b).map(Some).map(DiffMethodRef::Refer), - (_, _) => other.clone().into_diff(), - } - } - - fn merge(&self, diff: Self::Type) -> Result { - match (self, diff) { - (Self::Embed(a), DiffMethodRef::Embed(Some(ref b))) => a.merge(b.clone()).map(Self::Embed), - (Self::Embed(a), DiffMethodRef::Embed(None)) => Ok(Self::Embed(a.clone())), - (Self::Refer(a), DiffMethodRef::Refer(Some(ref b))) => a.merge(b.clone()).map(Self::Refer), - (Self::Refer(a), DiffMethodRef::Refer(None)) => Ok(Self::Refer(a.clone())), - (_, diff) => Self::from_diff(diff), - } - } - - fn from_diff(diff: Self::Type) -> Result { - match diff { - DiffMethodRef::Embed(Some(value)) => Diff::from_diff(value).map(Self::Embed), - DiffMethodRef::Embed(None) => Err(Error::convert("Invalid MethodRef Diff")), - DiffMethodRef::Refer(Some(value)) => Diff::from_diff(value).map(Self::Refer), - DiffMethodRef::Refer(None) => Err(Error::convert("Invalid MethodRef Diff")), - } - } - - fn into_diff(self) -> Result { - match self { - Self::Embed(value) => value.into_diff().map(Some).map(DiffMethodRef::Embed), - Self::Refer(value) => value.into_diff().map(Some).map(DiffMethodRef::Refer), - } - } + type Type = DiffMethodRef; + + fn diff(&self, other: &Self) -> Result { + match (self, other) { + (Self::Embed(a), Self::Embed(b)) if a == b => Ok(DiffMethodRef::Embed(None)), + (Self::Embed(a), Self::Embed(b)) => a.diff(b).map(Some).map(DiffMethodRef::Embed), + (Self::Refer(a), Self::Refer(b)) if a == b => Ok(DiffMethodRef::Refer(None)), + (Self::Refer(a), Self::Refer(b)) => a.diff(b).map(Some).map(DiffMethodRef::Refer), + (_, _) => other.clone().into_diff(), + } + } + + fn merge(&self, diff: Self::Type) -> Result { + match (self, diff) { + (Self::Embed(a), DiffMethodRef::Embed(Some(ref b))) => a.merge(b.clone()).map(Self::Embed), + (Self::Embed(a), DiffMethodRef::Embed(None)) => Ok(Self::Embed(a.clone())), + (Self::Refer(a), DiffMethodRef::Refer(Some(ref b))) => a.merge(b.clone()).map(Self::Refer), + (Self::Refer(a), DiffMethodRef::Refer(None)) => Ok(Self::Refer(a.clone())), + (_, diff) => Self::from_diff(diff), + } + } + + fn from_diff(diff: Self::Type) -> Result { + match diff { + DiffMethodRef::Embed(Some(value)) => Diff::from_diff(value).map(Self::Embed), + DiffMethodRef::Embed(None) => Err(Error::convert("Invalid MethodRef Diff")), + DiffMethodRef::Refer(Some(value)) => Diff::from_diff(value).map(Self::Refer), + DiffMethodRef::Refer(None) => Err(Error::convert("Invalid MethodRef Diff")), + } + } + + fn into_diff(self) -> Result { + match self { + Self::Embed(value) => value.into_diff().map(Some).map(DiffMethodRef::Embed), + Self::Refer(value) => value.into_diff().map(Some).map(DiffMethodRef::Refer), + } + } } // ============================================================================= @@ -710,88 +713,82 @@ where #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum DiffMethodData { - PublicKeyBase58(#[serde(skip_serializing_if = "Option::is_none")] Option), - PublicKeyHex(#[serde(skip_serializing_if = "Option::is_none")] Option), - PublicKeyJwk(#[serde(skip_serializing_if = "Option::is_none")] Option), + PublicKeyBase58(#[serde(skip_serializing_if = "Option::is_none")] Option), + PublicKeyHex(#[serde(skip_serializing_if = "Option::is_none")] Option), + PublicKeyJwk(#[serde(skip_serializing_if = "Option::is_none")] Option), } impl Diff for MethodData { - type Type = DiffMethodData; - - fn diff(&self, other: &Self) -> Result { - match (self, other) { - (Self::PublicKeyBase58(a), Self::PublicKeyBase58(b)) if a == b => Ok(DiffMethodData::PublicKeyBase58(None)), - (Self::PublicKeyBase58(a), Self::PublicKeyBase58(b)) => { - a.diff(b).map(Some).map(DiffMethodData::PublicKeyBase58) - } - (Self::PublicKeyHex(a), Self::PublicKeyHex(b)) if a == b => Ok(DiffMethodData::PublicKeyHex(None)), - (Self::PublicKeyHex(a), Self::PublicKeyHex(b)) => a.diff(b).map(Some).map(DiffMethodData::PublicKeyHex), - (Self::PublicKeyJwk(a), Self::PublicKeyJwk(b)) if a == b => Ok(DiffMethodData::PublicKeyJwk(None)), - (Self::PublicKeyJwk(a), Self::PublicKeyJwk(b)) => a.diff(b).map(Some).map(DiffMethodData::PublicKeyJwk), - (_, _) => other.clone().into_diff(), - } - } - - fn merge(&self, diff: Self::Type) -> Result { - match (self, diff) { - (Self::PublicKeyBase58(a), DiffMethodData::PublicKeyBase58(Some(ref b))) => { - a.merge(b.clone()).map(Self::PublicKeyBase58) - } - (Self::PublicKeyBase58(a), DiffMethodData::PublicKeyBase58(None)) => Ok(Self::PublicKeyBase58(a.clone())), - (Self::PublicKeyHex(a), DiffMethodData::PublicKeyHex(Some(ref b))) => { - a.merge(b.clone()).map(Self::PublicKeyHex) - } - (Self::PublicKeyHex(a), DiffMethodData::PublicKeyHex(None)) => Ok(Self::PublicKeyHex(a.clone())), - (Self::PublicKeyJwk(a), DiffMethodData::PublicKeyJwk(Some(ref b))) => { - a.merge(b.clone()).map(Self::PublicKeyJwk) - } - (Self::PublicKeyJwk(a), DiffMethodData::PublicKeyJwk(None)) => Ok(Self::PublicKeyJwk(a.clone())), - (_, diff) => Self::from_diff(diff), - } - } - - fn from_diff(diff: Self::Type) -> Result { - match diff { - DiffMethodData::PublicKeyBase58(Some(value)) => Diff::from_diff(value).map(Self::PublicKeyBase58), - DiffMethodData::PublicKeyBase58(None) => Ok(Self::PublicKeyBase58(Default::default())), - DiffMethodData::PublicKeyHex(Some(value)) => Diff::from_diff(value).map(Self::PublicKeyHex), - DiffMethodData::PublicKeyHex(None) => Ok(Self::PublicKeyHex(Default::default())), - DiffMethodData::PublicKeyJwk(Some(value)) => Diff::from_diff(value).map(Self::PublicKeyJwk), - DiffMethodData::PublicKeyJwk(None) => Ok(Self::PublicKeyJwk(Default::default())), - } - } - - fn into_diff(self) -> Result { - match self { - Self::PublicKeyBase58(value) => value.into_diff().map(Some).map(DiffMethodData::PublicKeyBase58), - Self::PublicKeyHex(value) => value.into_diff().map(Some).map(DiffMethodData::PublicKeyHex), - Self::PublicKeyJwk(value) => value.into_diff().map(Some).map(DiffMethodData::PublicKeyJwk), - _ => Err(Error::convert("Unknown Method Data Variant")), - } - } + type Type = DiffMethodData; + + fn diff(&self, other: &Self) -> Result { + match (self, other) { + (Self::PublicKeyBase58(a), Self::PublicKeyBase58(b)) if a == b => Ok(DiffMethodData::PublicKeyBase58(None)), + (Self::PublicKeyBase58(a), Self::PublicKeyBase58(b)) => a.diff(b).map(Some).map(DiffMethodData::PublicKeyBase58), + (Self::PublicKeyHex(a), Self::PublicKeyHex(b)) if a == b => Ok(DiffMethodData::PublicKeyHex(None)), + (Self::PublicKeyHex(a), Self::PublicKeyHex(b)) => a.diff(b).map(Some).map(DiffMethodData::PublicKeyHex), + (Self::PublicKeyJwk(a), Self::PublicKeyJwk(b)) if a == b => Ok(DiffMethodData::PublicKeyJwk(None)), + (Self::PublicKeyJwk(a), Self::PublicKeyJwk(b)) => a.diff(b).map(Some).map(DiffMethodData::PublicKeyJwk), + (_, _) => other.clone().into_diff(), + } + } + + fn merge(&self, diff: Self::Type) -> Result { + match (self, diff) { + (Self::PublicKeyBase58(a), DiffMethodData::PublicKeyBase58(Some(ref b))) => { + a.merge(b.clone()).map(Self::PublicKeyBase58) + } + (Self::PublicKeyBase58(a), DiffMethodData::PublicKeyBase58(None)) => Ok(Self::PublicKeyBase58(a.clone())), + (Self::PublicKeyHex(a), DiffMethodData::PublicKeyHex(Some(ref b))) => a.merge(b.clone()).map(Self::PublicKeyHex), + (Self::PublicKeyHex(a), DiffMethodData::PublicKeyHex(None)) => Ok(Self::PublicKeyHex(a.clone())), + (Self::PublicKeyJwk(a), DiffMethodData::PublicKeyJwk(Some(ref b))) => a.merge(b.clone()).map(Self::PublicKeyJwk), + (Self::PublicKeyJwk(a), DiffMethodData::PublicKeyJwk(None)) => Ok(Self::PublicKeyJwk(a.clone())), + (_, diff) => Self::from_diff(diff), + } + } + + fn from_diff(diff: Self::Type) -> Result { + match diff { + DiffMethodData::PublicKeyBase58(Some(value)) => Diff::from_diff(value).map(Self::PublicKeyBase58), + DiffMethodData::PublicKeyBase58(None) => Ok(Self::PublicKeyBase58(Default::default())), + DiffMethodData::PublicKeyHex(Some(value)) => Diff::from_diff(value).map(Self::PublicKeyHex), + DiffMethodData::PublicKeyHex(None) => Ok(Self::PublicKeyHex(Default::default())), + DiffMethodData::PublicKeyJwk(Some(value)) => Diff::from_diff(value).map(Self::PublicKeyJwk), + DiffMethodData::PublicKeyJwk(None) => Ok(Self::PublicKeyJwk(Default::default())), + } + } + + fn into_diff(self) -> Result { + match self { + Self::PublicKeyBase58(value) => value.into_diff().map(Some).map(DiffMethodData::PublicKeyBase58), + Self::PublicKeyHex(value) => value.into_diff().map(Some).map(DiffMethodData::PublicKeyHex), + Self::PublicKeyJwk(value) => value.into_diff().map(Some).map(DiffMethodData::PublicKeyJwk), + _ => Err(Error::convert("Unknown Method Data Variant")), + } + } } // ============================================================================= // ============================================================================= impl Diff for MethodType { - type Type = MethodType; + type Type = MethodType; - fn diff(&self, other: &Self) -> Result { - Ok(*other) - } + fn diff(&self, other: &Self) -> Result { + Ok(*other) + } - fn merge(&self, diff: Self::Type) -> Result { - Ok(diff) - } + fn merge(&self, diff: Self::Type) -> Result { + Ok(diff) + } - fn from_diff(diff: Self::Type) -> Result { - Ok(diff) - } + fn from_diff(diff: Self::Type) -> Result { + Ok(diff) + } - fn into_diff(self) -> Result { - Ok(self) - } + fn into_diff(self) -> Result { + Ok(self) + } } // ============================================================================= @@ -800,121 +797,121 @@ impl Diff for MethodType { #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct DiffService where - T: Diff, + T: Diff, { - #[serde(skip_serializing_if = "Option::is_none")] - id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - type_: Option, - #[serde(skip_serializing_if = "Option::is_none")] - service_endpoint: Option, - #[serde(skip_serializing_if = "Option::is_none")] - properties: Option<::Type>, + #[serde(skip_serializing_if = "Option::is_none")] + id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + type_: Option, + #[serde(skip_serializing_if = "Option::is_none")] + service_endpoint: Option, + #[serde(skip_serializing_if = "Option::is_none")] + properties: Option<::Type>, } impl Diff for Service where - T: Diff + Serialize + for<'de> Deserialize<'de>, + T: Diff + Serialize + for<'de> Deserialize<'de>, { - type Type = DiffService; - - fn diff(&self, other: &Self) -> Result { - Ok(DiffService { - id: if self.id() == other.id() { - None - } else { - Some(self.id().diff(other.id())?) - }, - type_: if self.type_() == other.type_() { - None - } else { - Some(self.type_().to_string().diff(&other.type_().to_string())?) - }, - service_endpoint: if self.service_endpoint() == other.service_endpoint() { - None - } else { - Some(self.service_endpoint().diff(other.service_endpoint())?) - }, - properties: if self.properties() == other.properties() { - None - } else { - Some(self.properties().diff(other.properties())?) - }, - }) - } - - fn merge(&self, diff: Self::Type) -> Result { - let id: DID = diff - .id - .map(|value| self.id().merge(value)) - .transpose()? - .unwrap_or_else(|| self.id().clone()); - - let type_: String = diff - .type_ - .map(|value| self.type_().to_string().merge(value)) - .transpose()? - .unwrap_or_else(|| self.type_().to_string()); - - let service_endpoint: Url = diff - .service_endpoint - .map(|value| self.service_endpoint().merge(value)) - .transpose()? - .unwrap_or_else(|| self.service_endpoint().clone()); - - let properties: T = diff - .properties - .map(|value| self.properties().merge(value)) - .transpose()? - .unwrap_or_else(|| self.properties().clone()); - - ServiceBuilder::new(properties) - .id(id) - .type_(type_) - .service_endpoint(service_endpoint) - .build() - .map_err(Error::merge) - } - - fn from_diff(diff: Self::Type) -> Result { - let id: DID = diff - .id - .map(DID::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `id`"))?; - - let type_: String = diff - .type_ - .map(String::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `type_`"))?; - - let service_endpoint: Url = diff - .service_endpoint - .map(Url::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `service_endpoint`"))?; - - let properties: T = diff - .properties - .map(T::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `properties`"))?; - - ServiceBuilder::new(properties) - .id(id) - .type_(type_) - .service_endpoint(service_endpoint) - .build() - .map_err(Error::convert) - } - - fn into_diff(self) -> Result { - Ok(DiffService { - id: Some(self.id().clone().into_diff()?), - type_: Some(self.type_().to_string().into_diff()?), - service_endpoint: Some(self.service_endpoint().clone().into_diff()?), - properties: Some(self.properties().clone().into_diff()?), - }) - } + type Type = DiffService; + + fn diff(&self, other: &Self) -> Result { + Ok(DiffService { + id: if self.id() == other.id() { + None + } else { + Some(self.id().diff(other.id())?) + }, + type_: if self.type_() == other.type_() { + None + } else { + Some(self.type_().to_string().diff(&other.type_().to_string())?) + }, + service_endpoint: if self.service_endpoint() == other.service_endpoint() { + None + } else { + Some(self.service_endpoint().diff(other.service_endpoint())?) + }, + properties: if self.properties() == other.properties() { + None + } else { + Some(self.properties().diff(other.properties())?) + }, + }) + } + + fn merge(&self, diff: Self::Type) -> Result { + let id: DID = diff + .id + .map(|value| self.id().merge(value)) + .transpose()? + .unwrap_or_else(|| self.id().clone()); + + let type_: String = diff + .type_ + .map(|value| self.type_().to_string().merge(value)) + .transpose()? + .unwrap_or_else(|| self.type_().to_string()); + + let service_endpoint: Url = diff + .service_endpoint + .map(|value| self.service_endpoint().merge(value)) + .transpose()? + .unwrap_or_else(|| self.service_endpoint().clone()); + + let properties: T = diff + .properties + .map(|value| self.properties().merge(value)) + .transpose()? + .unwrap_or_else(|| self.properties().clone()); + + ServiceBuilder::new(properties) + .id(id) + .type_(type_) + .service_endpoint(service_endpoint) + .build() + .map_err(Error::merge) + } + + fn from_diff(diff: Self::Type) -> Result { + let id: DID = diff + .id + .map(DID::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `id`"))?; + + let type_: String = diff + .type_ + .map(String::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `type_`"))?; + + let service_endpoint: Url = diff + .service_endpoint + .map(Url::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `service_endpoint`"))?; + + let properties: T = diff + .properties + .map(T::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `properties`"))?; + + ServiceBuilder::new(properties) + .id(id) + .type_(type_) + .service_endpoint(service_endpoint) + .build() + .map_err(Error::convert) + } + + fn into_diff(self) -> Result { + Ok(DiffService { + id: Some(self.id().clone().into_diff()?), + type_: Some(self.type_().to_string().into_diff()?), + service_endpoint: Some(self.service_endpoint().clone().into_diff()?), + properties: Some(self.properties().clone().into_diff()?), + }) + } } diff --git a/identity-diff/src/error.rs b/identity-diff/src/error.rs index 77c07b24f3..5229828b82 100644 --- a/identity-diff/src/error.rs +++ b/identity-diff/src/error.rs @@ -7,35 +7,35 @@ use thiserror::Error as DeriveError; #[derive(Debug, DeriveError)] pub enum Error { - #[error("Diff Error: {0}")] - DiffError(String), - #[error("Merge Error: {0}")] - MergeError(String), - #[error("Conversion Error: {0}")] - ConversionError(String), + #[error("Diff Error: {0}")] + DiffError(String), + #[error("Merge Error: {0}")] + MergeError(String), + #[error("Conversion Error: {0}")] + ConversionError(String), } impl Error { - pub fn diff(message: T) -> Self - where - T: Display, - { - Self::DiffError(format!("{}", message)) - } + pub fn diff(message: T) -> Self + where + T: Display, + { + Self::DiffError(format!("{}", message)) + } - pub fn merge(message: T) -> Self - where - T: Display, - { - Self::MergeError(format!("{}", message)) - } + pub fn merge(message: T) -> Self + where + T: Display, + { + Self::MergeError(format!("{}", message)) + } - pub fn convert(message: T) -> Self - where - T: Display, - { - Self::ConversionError(format!("{}", message)) - } + pub fn convert(message: T) -> Self + where + T: Display, + { + Self::ConversionError(format!("{}", message)) + } } pub type Result = AnyhowResult; diff --git a/identity-diff/src/hashmap.rs b/identity-diff/src/hashmap.rs index 628ce99390..d82f105798 100644 --- a/identity-diff/src/hashmap.rs +++ b/identity-diff/src/hashmap.rs @@ -4,200 +4,200 @@ use crate::Diff; use serde::{Deserialize, Serialize}; use std::{ - collections::{HashMap, HashSet}, - fmt::{Debug, Formatter, Result as FmtResult}, - hash::Hash, - iter::empty, + collections::{HashMap, HashSet}, + fmt::{Debug, Formatter, Result as FmtResult}, + hash::Hash, + iter::empty, }; /// Inner value of the `DiffHashMap` type. #[derive(Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum InnerValue { - // Logs if a value has changed between the two types being Diffed. - Change { - #[serde(rename = "c:k")] - key: K, - #[serde(rename = "c:v")] - value: ::Type, - }, - // Logs an addition. - Add { - #[serde(rename = "a:k")] - key: K, - #[serde(rename = "a:v")] - value: ::Type, - }, - // Logs a removal. - Remove { - #[serde(rename = "r:k")] - key: K, - }, + // Logs if a value has changed between the two types being Diffed. + Change { + #[serde(rename = "c:k")] + key: K, + #[serde(rename = "c:v")] + value: ::Type, + }, + // Logs an addition. + Add { + #[serde(rename = "a:k")] + key: K, + #[serde(rename = "a:v")] + value: ::Type, + }, + // Logs a removal. + Remove { + #[serde(rename = "r:k")] + key: K, + }, } /// A `DiffHashMap` type which represents a Diffed `HashMap`. By default this value is transparent to `serde`. #[derive(Clone, PartialEq, Serialize, Deserialize)] #[serde(transparent)] pub struct DiffHashMap( - #[serde(skip_serializing_if = "Option::is_none")] pub Option>>, + #[serde(skip_serializing_if = "Option::is_none")] pub Option>>, ); /// Diff Implementation on a HashMap impl Diff for HashMap where - K: Clone + Debug + PartialEq + Eq + Hash + Diff + for<'de> Deserialize<'de> + Serialize + Default, - V: Clone + Debug + PartialEq + Diff + for<'de> Deserialize<'de> + Serialize + Default, + K: Clone + Debug + PartialEq + Eq + Hash + Diff + for<'de> Deserialize<'de> + Serialize + Default, + V: Clone + Debug + PartialEq + Diff + for<'de> Deserialize<'de> + Serialize + Default, { - /// the Diff type of the HashMap - type Type = DiffHashMap; + /// the Diff type of the HashMap + type Type = DiffHashMap; - /// Diffs two `HashMaps`; `self` and `other` and creates a `DiffHashMap` - fn diff(&self, other: &Self) -> crate::Result { - let old: HashSet<&K> = self.keys().collect(); - let new: HashSet<&K> = other.keys().collect(); + /// Diffs two `HashMaps`; `self` and `other` and creates a `DiffHashMap` + fn diff(&self, other: &Self) -> crate::Result { + let old: HashSet<&K> = self.keys().collect(); + let new: HashSet<&K> = other.keys().collect(); - let changed_keys = old.intersection(&new).filter(|k| self[k] != other[k]); - let removed_keys = old.difference(&new); - let added_keys = new.difference(&old); + let changed_keys = old.intersection(&new).filter(|k| self[k] != other[k]); + let removed_keys = old.difference(&new); + let added_keys = new.difference(&old); - let mut changes: Vec> = Vec::new(); + let mut changes: Vec> = Vec::new(); - for key in changed_keys { - let (old_val, new_val): (&V, &V) = (&self[key], &other[key]); + for key in changed_keys { + let (old_val, new_val): (&V, &V) = (&self[key], &other[key]); - let diff = old_val.diff(new_val)?; + let diff = old_val.diff(new_val)?; - changes.push(InnerValue::Change { - key: (*key).clone(), - value: diff, - }); - } - for key in added_keys { - changes.push(InnerValue::Add { - key: (*key).clone(), - value: other[key].clone().into_diff()?, - }); - } - for key in removed_keys { - changes.push(InnerValue::Remove { key: (*key).clone() }); - } - - Ok(DiffHashMap(if changes.is_empty() { None } else { Some(changes) })) + changes.push(InnerValue::Change { + key: (*key).clone(), + value: diff, + }); + } + for key in added_keys { + changes.push(InnerValue::Add { + key: (*key).clone(), + value: other[key].clone().into_diff()?, + }); + } + for key in removed_keys { + changes.push(InnerValue::Remove { key: (*key).clone() }); } - /// Merges the changes in a `DiffHashMap`, `diff` with a `HashMap`, `self`. - fn merge(&self, diff: Self::Type) -> crate::Result { - let mut new = self.clone(); - - for change in diff.0.into_iter().flatten() { - match change { - InnerValue::Change { key, value } => { - let fake: &mut V = &mut *new.get_mut(&key).expect("Failed to get value"); - - *fake = ::from_diff(value)?; - } - InnerValue::Add { key, value } => { - new.insert(key, ::from_diff(value)?); - } - InnerValue::Remove { key } => { - new.remove(&key); - } - } - } + Ok(DiffHashMap(if changes.is_empty() { None } else { Some(changes) })) + } - Ok(new) - } + /// Merges the changes in a `DiffHashMap`, `diff` with a `HashMap`, `self`. + fn merge(&self, diff: Self::Type) -> crate::Result { + let mut new = self.clone(); - /// Converts a `DiffHashMap`, `diff` into a `HashMap`. - fn from_diff(diff: Self::Type) -> crate::Result { - let mut map = Self::new(); - if let Some(diff) = diff.0 { - for (idx, elm) in diff.into_iter().enumerate() { - match elm { - InnerValue::Add { key, value } => { - map.insert(key, ::from_diff(value)?); - } - _ => { - panic!("Unable to create Diff at index: {:?}", idx); - } - } - } - } + for change in diff.0.into_iter().flatten() { + match change { + InnerValue::Change { key, value } => { + let fake: &mut V = &mut *new.get_mut(&key).expect("Failed to get value"); - Ok(map) + *fake = ::from_diff(value)?; + } + InnerValue::Add { key, value } => { + new.insert(key, ::from_diff(value)?); + } + InnerValue::Remove { key } => { + new.remove(&key); + } + } } - /// Converts a `HashMap`, `diff` into a `DiffHashMap`. - fn into_diff(self) -> crate::Result { - let mut changes: Vec> = Vec::new(); - for (key, val) in self { - changes.push(InnerValue::Add { - key, - value: val.into_diff()?, - }); + Ok(new) + } + + /// Converts a `DiffHashMap`, `diff` into a `HashMap`. + fn from_diff(diff: Self::Type) -> crate::Result { + let mut map = Self::new(); + if let Some(diff) = diff.0 { + for (idx, elm) in diff.into_iter().enumerate() { + match elm { + InnerValue::Add { key, value } => { + map.insert(key, ::from_diff(value)?); + } + _ => { + panic!("Unable to create Diff at index: {:?}", idx); + } } + } + } - Ok(DiffHashMap(if changes.is_empty() { None } else { Some(changes) })) + Ok(map) + } + + /// Converts a `HashMap`, `diff` into a `DiffHashMap`. + fn into_diff(self) -> crate::Result { + let mut changes: Vec> = Vec::new(); + for (key, val) in self { + changes.push(InnerValue::Add { + key, + value: val.into_diff()?, + }); } + + Ok(DiffHashMap(if changes.is_empty() { None } else { Some(changes) })) + } } /// Debug implementation for the `DiffHashMap` type. impl Debug for DiffHashMap where - K: Debug + Diff, - V: Debug + Diff, + K: Debug + Diff, + V: Debug + Diff, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - write!(f, "DiffHashMap")?; + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!(f, "DiffHashMap")?; - let mut buf = f.debug_list(); + let mut buf = f.debug_list(); - if let Some(val) = &self.0 { - buf.entries(val.iter()); - } else { - buf.entries(empty::>>()); - } - buf.finish() + if let Some(val) = &self.0 { + buf.entries(val.iter()); + } else { + buf.entries(empty::>>()); } + buf.finish() + } } /// Default implementation for the `DiffHashMap` type. impl Default for DiffHashMap where - K: Default + Diff, - V: Default + Diff, + K: Default + Diff, + V: Default + Diff, { - fn default() -> Self { - DiffHashMap(None) - } + fn default() -> Self { + DiffHashMap(None) + } } /// Debug implementation for the `InnerValue` type. impl Debug for InnerValue where - K: Debug + Diff, - V: Debug + Diff, + K: Debug + Diff, + V: Debug + Diff, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - match &self { - Self::Change { key, value } => f - .debug_struct("Change") - .field("key", key) - .field("value", value) - .finish(), - Self::Add { key, value } => f.debug_struct("Add").field("key", key).field("value", value).finish(), - Self::Remove { key } => f.debug_struct("Remove").field("key", key).finish(), - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match &self { + Self::Change { key, value } => f + .debug_struct("Change") + .field("key", key) + .field("value", value) + .finish(), + Self::Add { key, value } => f.debug_struct("Add").field("key", key).field("value", value).finish(), + Self::Remove { key } => f.debug_struct("Remove").field("key", key).finish(), } + } } #[cfg(test)] mod tests { - use super::*; - use std::collections::HashMap; + use super::*; + use std::collections::HashMap; - /// Quickly creates a simple map using `map! { "key" => "value"} - macro_rules! map { + /// Quickly creates a simple map using `map! { "key" => "value"} + macro_rules! map { ($($key:expr => $val:expr),* $(,)?) => {{ let mut map = HashMap::new(); $( map.insert($key, $val); )* @@ -205,40 +205,40 @@ mod tests { }} } - #[test] - fn test_hashmap_diff() { - let m0: HashMap = map! { - "test".into() => 300usize, - "foo".into() => 10usize, - "bar".into() => 20usize, - "baz".into() => 1usize, - }; - - let m1: HashMap = map! { - "test".into() => 300usize, - "foo".into() => 0usize, - "bar".into() => 20usize, - "quux".into() => 10usize, - }; - - let diff = m0.diff(&m1).unwrap(); - - let expected: DiffHashMap = DiffHashMap(Some(vec![ - InnerValue::Change { - key: "foo".into(), - value: 0usize.into_diff().unwrap(), - }, - InnerValue::Add { - key: "quux".into(), - value: 10usize.into_diff().unwrap(), - }, - InnerValue::Remove { key: "baz".into() }, - ])); - - assert_eq!(expected, diff); - - let m2 = m0.merge(diff).unwrap(); - - assert_eq!(m1, m2); - } + #[test] + fn test_hashmap_diff() { + let m0: HashMap = map! { + "test".into() => 300usize, + "foo".into() => 10usize, + "bar".into() => 20usize, + "baz".into() => 1usize, + }; + + let m1: HashMap = map! { + "test".into() => 300usize, + "foo".into() => 0usize, + "bar".into() => 20usize, + "quux".into() => 10usize, + }; + + let diff = m0.diff(&m1).unwrap(); + + let expected: DiffHashMap = DiffHashMap(Some(vec![ + InnerValue::Change { + key: "foo".into(), + value: 0usize.into_diff().unwrap(), + }, + InnerValue::Add { + key: "quux".into(), + value: 10usize.into_diff().unwrap(), + }, + InnerValue::Remove { key: "baz".into() }, + ])); + + assert_eq!(expected, diff); + + let m2 = m0.merge(diff).unwrap(); + + assert_eq!(m1, m2); + } } diff --git a/identity-diff/src/hashset.rs b/identity-diff/src/hashset.rs index 990621ea27..11cb0b272e 100644 --- a/identity-diff/src/hashset.rs +++ b/identity-diff/src/hashset.rs @@ -4,10 +4,10 @@ use crate::Diff; use serde::{Deserialize, Serialize}; use std::{ - collections::HashSet, - fmt::{Debug, Formatter, Result as FmtResult}, - hash::Hash, - iter::empty, + collections::HashSet, + fmt::{Debug, Formatter, Result as FmtResult}, + hash::Hash, + iter::empty, }; #[derive(Clone, PartialEq, Serialize, Deserialize)] @@ -16,120 +16,120 @@ pub struct DiffHashSet(#[serde(skip_serializing_if = "Option::is_none") #[derive(Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum InnerValue { - Add(::Type), - Remove { remove: ::Type }, + Add(::Type), + Remove { remove: ::Type }, } impl Diff for HashSet where - T: Debug + Clone + PartialEq + Eq + Diff + Hash + for<'de> Deserialize<'de> + Serialize, + T: Debug + Clone + PartialEq + Eq + Diff + Hash + for<'de> Deserialize<'de> + Serialize, { - type Type = DiffHashSet; - - fn diff(&self, other: &Self) -> crate::Result { - Ok(DiffHashSet(if self == other { - None - } else { - let mut val_diffs: Vec> = vec![]; - for add in other.difference(&self) { - let add = add.clone().into_diff()?; - val_diffs.push(InnerValue::Add(add)); + type Type = DiffHashSet; + + fn diff(&self, other: &Self) -> crate::Result { + Ok(DiffHashSet(if self == other { + None + } else { + let mut val_diffs: Vec> = vec![]; + for add in other.difference(&self) { + let add = add.clone().into_diff()?; + val_diffs.push(InnerValue::Add(add)); + } + + for remove in self.difference(&other) { + let remove = remove.clone().into_diff()?; + val_diffs.push(InnerValue::Remove { remove }); + } + + Some(val_diffs) + })) + } + + fn merge(&self, diff: Self::Type) -> crate::Result { + match diff.0 { + None => Ok(self.clone()), + Some(val_diffs) => { + let mut new: Self = self.clone(); + for val_diff in val_diffs { + match val_diff { + InnerValue::Add(val) => { + new.insert(::from_diff(val)?); } - - for remove in self.difference(&other) { - let remove = remove.clone().into_diff()?; - val_diffs.push(InnerValue::Remove { remove }); - } - - Some(val_diffs) - })) - } - - fn merge(&self, diff: Self::Type) -> crate::Result { - match diff.0 { - None => Ok(self.clone()), - Some(val_diffs) => { - let mut new: Self = self.clone(); - for val_diff in val_diffs { - match val_diff { - InnerValue::Add(val) => { - new.insert(::from_diff(val)?); - } - InnerValue::Remove { remove } => { - new.remove(&(::from_diff(remove)?)); - } - } - } - Ok(new) + InnerValue::Remove { remove } => { + new.remove(&(::from_diff(remove)?)); } + } } + Ok(new) + } } - - fn into_diff(self) -> crate::Result { - Ok(DiffHashSet(if self.is_empty() { - None - } else { - let mut diffs: Vec> = vec![]; - for val in self { - diffs.push(InnerValue::Add(val.into_diff()?)); - } - Some(diffs) - })) - } - - fn from_diff(diff: Self::Type) -> crate::Result { - let mut set = Self::new(); - if let Some(vals) = diff.0 { - for val in vals { - match val { - InnerValue::Add(val) => { - set.insert(::from_diff(val)?); - } - InnerValue::Remove { remove } => { - let val = ::from_diff(remove)?; - set.remove(&val); - } - } - } + } + + fn into_diff(self) -> crate::Result { + Ok(DiffHashSet(if self.is_empty() { + None + } else { + let mut diffs: Vec> = vec![]; + for val in self { + diffs.push(InnerValue::Add(val.into_diff()?)); + } + Some(diffs) + })) + } + + fn from_diff(diff: Self::Type) -> crate::Result { + let mut set = Self::new(); + if let Some(vals) = diff.0 { + for val in vals { + match val { + InnerValue::Add(val) => { + set.insert(::from_diff(val)?); + } + InnerValue::Remove { remove } => { + let val = ::from_diff(remove)?; + set.remove(&val); + } } - Ok(set) + } } + Ok(set) + } } impl Debug for DiffHashSet where - T: Debug + Diff, + T: Debug + Diff, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - write!(f, "DiffHashSet")?; - let mut buf = f.debug_list(); - if let Some(d) = &self.0 { - buf.entries(d.iter()); - } else { - buf.entries(empty::>>()); - } - buf.finish() + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!(f, "DiffHashSet")?; + let mut buf = f.debug_list(); + if let Some(d) = &self.0 { + buf.entries(d.iter()); + } else { + buf.entries(empty::>>()); } + buf.finish() + } } impl Debug for InnerValue where - T: Debug + Diff, + T: Debug + Diff, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - match &self { - Self::Add(val) => f.debug_tuple("Add").field(val).finish(), - Self::Remove { remove } => f.debug_tuple("Remove").field(remove).finish(), - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match &self { + Self::Add(val) => f.debug_tuple("Add").field(val).finish(), + Self::Remove { remove } => f.debug_tuple("Remove").field(remove).finish(), } + } } #[cfg(test)] mod tests { - use super::*; - use std::collections::HashSet; + use super::*; + use std::collections::HashSet; - macro_rules! set { + macro_rules! set { ($($val:expr),* $(,)?) => {{ #[allow(redundant_semicolons)] { let mut set = HashSet::new(); $( set.insert($val); )* ; @@ -137,50 +137,50 @@ mod tests { }}} } - #[test] - fn test_hashset_diff() { - let s: HashSet = set! { - "test".into(), - "foo".into(), - }; + #[test] + fn test_hashset_diff() { + let s: HashSet = set! { + "test".into(), + "foo".into(), + }; - let s1: HashSet = set! { - "test".into(), - "foo".into(), - }; + let s1: HashSet = set! { + "test".into(), + "foo".into(), + }; - let diff = s.diff(&s1).unwrap(); - let expected = DiffHashSet(None); + let diff = s.diff(&s1).unwrap(); + let expected = DiffHashSet(None); - assert_eq!(diff, expected); - let s2 = s.merge(diff).unwrap(); + assert_eq!(diff, expected); + let s2 = s.merge(diff).unwrap(); - assert_eq!(s, s2); - assert_eq!(s1, s2); - } + assert_eq!(s, s2); + assert_eq!(s1, s2); + } - #[test] - fn test_hashset_diff_add_and_remove() { - let s: HashSet = set! { - "test".into(), - "foo".into(), - "faux".into(), - }; + #[test] + fn test_hashset_diff_add_and_remove() { + let s: HashSet = set! { + "test".into(), + "foo".into(), + "faux".into(), + }; - let s1: HashSet = set! { - "test".into(), - "foo".into(), - "bar".into(), - }; + let s1: HashSet = set! { + "test".into(), + "foo".into(), + "bar".into(), + }; - let diff = s.diff(&s1).unwrap(); + let diff = s.diff(&s1).unwrap(); - let json = serde_json::to_string(&diff).unwrap(); + let json = serde_json::to_string(&diff).unwrap(); - println!("{}", json); + println!("{}", json); - let diff: DiffHashSet = serde_json::from_str(&json).unwrap(); + let diff: DiffHashSet = serde_json::from_str(&json).unwrap(); - println!("{:?}", diff); - } + println!("{:?}", diff); + } } diff --git a/identity-diff/src/option.rs b/identity-diff/src/option.rs index 4a03ff579e..79bc9a1444 100644 --- a/identity-diff/src/option.rs +++ b/identity-diff/src/option.rs @@ -12,113 +12,113 @@ use crate::Diff; #[derive(Clone, PartialEq, Deserialize, Serialize)] #[serde(untagged, into = "Option", from = "Option")] pub enum DiffOption { - Some(::Type), - None, + Some(::Type), + None, } /// `Diff` Implementation for `Option` impl Diff for Option where - T: Diff + Clone + Debug + PartialEq + Default + for<'de> Deserialize<'de> + Serialize, + T: Diff + Clone + Debug + PartialEq + Default + for<'de> Deserialize<'de> + Serialize, { - /// The Corresponding Diff type for `Option` - type Type = DiffOption; - - /// Compares two `Option` types; `self` and `other` and finds the Difference between them, returning a - /// `DiffOption` type. - fn diff(&self, other: &Self) -> crate::Result { - match (self, other) { - (Some(x), Some(y)) => Ok(Self::Type::Some(x.diff(&y)?)), - (None, Some(y)) => Ok(Self::Type::Some(y.clone().into_diff()?)), - _ => Ok(Self::Type::None), - } + /// The Corresponding Diff type for `Option` + type Type = DiffOption; + + /// Compares two `Option` types; `self` and `other` and finds the Difference between them, returning a + /// `DiffOption` type. + fn diff(&self, other: &Self) -> crate::Result { + match (self, other) { + (Some(x), Some(y)) => Ok(Self::Type::Some(x.diff(&y)?)), + (None, Some(y)) => Ok(Self::Type::Some(y.clone().into_diff()?)), + _ => Ok(Self::Type::None), } - - /// Merges a `DiffOption`; `diff` type with an `Option` type; `self`. - fn merge(&self, diff: Self::Type) -> crate::Result { - match (self, diff) { - (None, DiffOption::None) => Ok(None), - (Some(_), DiffOption::None) => Ok(self.clone()), - (None, DiffOption::Some(ref d)) => Ok(Some(::from_diff(d.clone())?)), - (Some(t), DiffOption::Some(ref d)) => Ok(Some(t.merge(d.clone())?)), - } + } + + /// Merges a `DiffOption`; `diff` type with an `Option` type; `self`. + fn merge(&self, diff: Self::Type) -> crate::Result { + match (self, diff) { + (None, DiffOption::None) => Ok(None), + (Some(_), DiffOption::None) => Ok(self.clone()), + (None, DiffOption::Some(ref d)) => Ok(Some(::from_diff(d.clone())?)), + (Some(t), DiffOption::Some(ref d)) => Ok(Some(t.merge(d.clone())?)), } + } - /// converts a `DiffOption`; `diff` to an `Option` type. - fn from_diff(diff: Self::Type) -> crate::Result { - match diff { - Self::Type::None => Ok(None), - Self::Type::Some(diff) => Ok(Some(::from_diff(diff)?)), - } + /// converts a `DiffOption`; `diff` to an `Option` type. + fn from_diff(diff: Self::Type) -> crate::Result { + match diff { + Self::Type::None => Ok(None), + Self::Type::Some(diff) => Ok(Some(::from_diff(diff)?)), } + } - /// converts a `Option`; `self` to an `DiffOption` type. - fn into_diff(self) -> crate::Result { - match self { - Self::None => Ok(DiffOption::None), - Self::Some(t) => Ok(DiffOption::Some(t.into_diff()?)), - } + /// converts a `Option`; `self` to an `DiffOption` type. + fn into_diff(self) -> crate::Result { + match self { + Self::None => Ok(DiffOption::None), + Self::Some(t) => Ok(DiffOption::Some(t.into_diff()?)), } + } } /// Debug implementation for `DiffOption`. impl std::fmt::Debug for DiffOption { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - match &self { - Self::Some(d) => write!(f, "DiffOption::Some({:#?})", d), - Self::None => write!(f, "DiffOption::None"), - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match &self { + Self::Some(d) => write!(f, "DiffOption::Some({:#?})", d), + Self::None => write!(f, "DiffOption::None"), } + } } /// Default implementation for `DiffOption`. impl Default for DiffOption { - fn default() -> Self { - Self::None - } + fn default() -> Self { + Self::None + } } /// Into `Option` implementation for `DiffOption`. impl Into> for DiffOption where - T: Diff, + T: Diff, { - fn into(self) -> Option { - match self { - DiffOption::Some(s) => Some(Diff::from_diff(s).expect("Unable to convert from diff")), - DiffOption::None => None, - } + fn into(self) -> Option { + match self { + DiffOption::Some(s) => Some(Diff::from_diff(s).expect("Unable to convert from diff")), + DiffOption::None => None, } + } } /// From `Option` implementation for `DiffOption`. impl From> for DiffOption where - T: Diff, + T: Diff, { - fn from(opt: Option) -> Self { - match opt { - Some(s) => DiffOption::Some(s.into_diff().expect("Unable to convert to diff")), - None => DiffOption::None, - } + fn from(opt: Option) -> Self { + match opt { + Some(s) => DiffOption::Some(s.into_diff().expect("Unable to convert to diff")), + None => DiffOption::None, } + } } #[cfg(test)] mod tests { - use super::*; - use crate::string::DiffString; + use super::*; + use crate::string::DiffString; - #[test] - fn test_option_diff() { - let a = Some("A".to_owned()); - let b = Some("B".to_owned()); + #[test] + fn test_option_diff() { + let a = Some("A".to_owned()); + let b = Some("B".to_owned()); - let diff = a.diff(&b).unwrap(); + let diff = a.diff(&b).unwrap(); - assert_eq!(diff, DiffOption::Some(DiffString(Some("B".to_owned())))); + assert_eq!(diff, DiffOption::Some(DiffString(Some("B".to_owned())))); - let c = a.merge(diff).unwrap(); + let c = a.merge(diff).unwrap(); - assert_eq!(b, c); - } + assert_eq!(b, c); + } } diff --git a/identity-diff/src/string.rs b/identity-diff/src/string.rs index e74f367912..dc72cdf764 100644 --- a/identity-diff/src/string.rs +++ b/identity-diff/src/string.rs @@ -13,82 +13,82 @@ pub struct DiffString(#[serde(skip_serializing_if = "Option::is_none")] pub Opti /// `Diff` trait implementation for `String`. impl Diff for String { - /// Diff type for `String` - type Type = DiffString; - - /// compares two `String` types; `self`, `other` and returns a `DiffString` type. - fn diff(&self, other: &Self) -> crate::Result { - if self == other { - Ok(DiffString(None)) - } else { - other.clone().into_diff() - } + /// Diff type for `String` + type Type = DiffString; + + /// compares two `String` types; `self`, `other` and returns a `DiffString` type. + fn diff(&self, other: &Self) -> crate::Result { + if self == other { + Ok(DiffString(None)) + } else { + other.clone().into_diff() } - - /// Merges a `DiffString`; `diff` with a `String`; `self`. - fn merge(&self, diff: Self::Type) -> crate::Result { - if diff.0.is_none() { - Ok(self.to_string()) - } else { - Self::from_diff(diff) - } + } + + /// Merges a `DiffString`; `diff` with a `String`; `self`. + fn merge(&self, diff: Self::Type) -> crate::Result { + if diff.0.is_none() { + Ok(self.to_string()) + } else { + Self::from_diff(diff) } - - /// Converts a `DiffString` into a `String` type. - fn from_diff(diff: Self::Type) -> crate::Result { - match diff.0 { - Some(s) => Ok(s), - None => Err(crate::Error::ConversionError( - "Problem converting from DiffString".into(), - )), - } + } + + /// Converts a `DiffString` into a `String` type. + fn from_diff(diff: Self::Type) -> crate::Result { + match diff.0 { + Some(s) => Ok(s), + None => Err(crate::Error::ConversionError( + "Problem converting from DiffString".into(), + )), } + } - /// Converts a `String` into a `DiffString` type. - fn into_diff(self) -> crate::Result { - Ok(DiffString(Some(self))) - } + /// Converts a `String` into a `DiffString` type. + fn into_diff(self) -> crate::Result { + Ok(DiffString(Some(self))) + } } /// Debug trait implementation for DiffString. impl Debug for DiffString { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - match &self.0 { - Some(val) => write!(f, "DiffString({:#?})", val), - None => write!(f, "DiffString None"), - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match &self.0 { + Some(val) => write!(f, "DiffString({:#?})", val), + None => write!(f, "DiffString None"), } + } } #[cfg(test)] mod tests { - use super::*; + use super::*; - #[test] - fn test_string_diff() { - let sa = String::from("test"); - let sb = String::from("another_string"); + #[test] + fn test_string_diff() { + let sa = String::from("test"); + let sb = String::from("another_string"); - let diff = sa.diff(&sb).unwrap(); + let diff = sa.diff(&sb).unwrap(); - assert_eq!(diff, DiffString(Some("another_string".into()))); + assert_eq!(diff, DiffString(Some("another_string".into()))); - let sc = sa.merge(diff).unwrap(); + let sc = sa.merge(diff).unwrap(); - assert_eq!(sb, sc); - } + assert_eq!(sb, sc); + } - #[test] - fn test_same_string() { - let sa = String::from("test"); - let sb = String::from("test"); + #[test] + fn test_same_string() { + let sa = String::from("test"); + let sb = String::from("test"); - let diff = sa.diff(&sb).unwrap(); + let diff = sa.diff(&sb).unwrap(); - assert_eq!(diff, DiffString(None)); + assert_eq!(diff, DiffString(None)); - let sc = sa.merge(diff).unwrap(); + let sc = sa.merge(diff).unwrap(); - assert_eq!(sb, sc); - assert_eq!(sa, sc); - } + assert_eq!(sb, sc); + assert_eq!(sa, sc); + } } diff --git a/identity-diff/src/traits.rs b/identity-diff/src/traits.rs index aa7ae37cbc..7cfc4f1dac 100644 --- a/identity-diff/src/traits.rs +++ b/identity-diff/src/traits.rs @@ -6,18 +6,18 @@ use std::fmt::Debug; /// The primary `Diff` Trait type. pub trait Diff: Clone + Debug + PartialEq { - /// The Corresponding Diff Type for the implemented Type. - type Type: Sized + Clone + Debug + PartialEq + for<'de> Deserialize<'de> + Serialize; + /// The Corresponding Diff Type for the implemented Type. + type Type: Sized + Clone + Debug + PartialEq + for<'de> Deserialize<'de> + Serialize; - /// Finds the difference between two types; `self` and `other` and returns `Self::Type` - fn diff(&self, other: &Self) -> crate::Result; + /// Finds the difference between two types; `self` and `other` and returns `Self::Type` + fn diff(&self, other: &Self) -> crate::Result; - /// Merges a `Self::Type` with `Self` - fn merge(&self, diff: Self::Type) -> crate::Result; + /// Merges a `Self::Type` with `Self` + fn merge(&self, diff: Self::Type) -> crate::Result; - /// Converts a `diff` of type `Self::Type` to a `Self`. - fn from_diff(diff: Self::Type) -> crate::Result; + /// Converts a `diff` of type `Self::Type` to a `Self`. + fn from_diff(diff: Self::Type) -> crate::Result; - /// Converts a type of `Self` to a `diff` of `Self::Type`. - fn into_diff(self) -> crate::Result; + /// Converts a type of `Self` to a `diff` of `Self::Type`. + fn into_diff(self) -> crate::Result; } diff --git a/identity-diff/src/value.rs b/identity-diff/src/value.rs index 98db0cf613..daf993a219 100644 --- a/identity-diff/src/value.rs +++ b/identity-diff/src/value.rs @@ -13,85 +13,85 @@ pub struct DiffValue(#[serde(skip_serializing_if = "Option::is_none")] pub Optio /// A default implementation for `DiffValue`. impl Default for DiffValue { - fn default() -> Self { - DiffValue(None) - } + fn default() -> Self { + DiffValue(None) + } } /// The Diff implementation for `serde_json::Value`. impl Diff for Value { - /// The Diff Type for `serde_json::Value`. - type Type = DiffValue; - - /// Compares two `serde_json::Value` types; `self`, `diff` and outputs a `DiffValue` type. - fn diff(&self, other: &Self) -> crate::Result { - if self == other { - Ok(DiffValue(None)) - } else { - other.clone().into_diff() - } + /// The Diff Type for `serde_json::Value`. + type Type = DiffValue; + + /// Compares two `serde_json::Value` types; `self`, `diff` and outputs a `DiffValue` type. + fn diff(&self, other: &Self) -> crate::Result { + if self == other { + Ok(DiffValue(None)) + } else { + other.clone().into_diff() } - - /// Merges a `DiffValue`; `diff` with `self`; a `serde_json::Value` to create a new `serde_json::Value`. - fn merge(&self, diff: Self::Type) -> crate::Result { - if diff.0.is_none() { - Ok(self.clone()) - } else { - Self::from_diff(diff) - } + } + + /// Merges a `DiffValue`; `diff` with `self`; a `serde_json::Value` to create a new `serde_json::Value`. + fn merge(&self, diff: Self::Type) -> crate::Result { + if diff.0.is_none() { + Ok(self.clone()) + } else { + Self::from_diff(diff) } - - /// Converts from a `diff` of type `DiffValue` to a `serde_json::Value`. - fn from_diff(diff: Self::Type) -> crate::Result { - match diff.0 { - Some(s) => Ok(s), - None => Err(crate::Error::ConversionError( - "Error converting from serde_json::Value".into(), - )), - } + } + + /// Converts from a `diff` of type `DiffValue` to a `serde_json::Value`. + fn from_diff(diff: Self::Type) -> crate::Result { + match diff.0 { + Some(s) => Ok(s), + None => Err(crate::Error::ConversionError( + "Error converting from serde_json::Value".into(), + )), } + } - /// converts a `serde_json::Value` to a `DiffValue`. - fn into_diff(self) -> crate::Result { - Ok(DiffValue(Some(self))) - } + /// converts a `serde_json::Value` to a `DiffValue`. + fn into_diff(self) -> crate::Result { + Ok(DiffValue(Some(self))) + } } #[cfg(test)] mod test { - use super::*; - use serde_json::json; + use super::*; + use serde_json::json; - #[test] - fn test_json_value() { - let v = Value::Null; + #[test] + fn test_json_value() { + let v = Value::Null; - let v2 = Value::Bool(true); + let v2 = Value::Bool(true); - let diff = v.diff(&v2).unwrap(); + let diff = v.diff(&v2).unwrap(); - let res = v.merge(diff).unwrap(); + let res = v.merge(diff).unwrap(); - let expected = Value::Bool(true); + let expected = Value::Bool(true); - assert_eq!(expected, res); + assert_eq!(expected, res); - let v = json!("A string"); + let v = json!("A string"); - let v2 = json!("A string"); + let v2 = json!("A string"); - let diff = v.diff(&v2).unwrap(); + let diff = v.diff(&v2).unwrap(); - let res = v.merge(diff).unwrap(); + let res = v.merge(diff).unwrap(); - assert_eq!(res, v2); + assert_eq!(res, v2); - let v3 = json!("Another string"); + let v3 = json!("Another string"); - let diff = v.diff(&v3).unwrap(); + let diff = v.diff(&v3).unwrap(); - let res = v.merge(diff).unwrap(); + let res = v.merge(diff).unwrap(); - assert_eq!(v3, res); - } + assert_eq!(v3, res); + } } diff --git a/identity-diff/src/vec.rs b/identity-diff/src/vec.rs index 0101959bd1..ab1d741357 100644 --- a/identity-diff/src/vec.rs +++ b/identity-diff/src/vec.rs @@ -14,255 +14,256 @@ pub struct DiffVec(pub Vec>); #[derive(Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum InnerVec { - /// logs a change in a `Vec` type. - Change { index: usize, item: ::Type }, - /// Logs a remove event in a `Vec` type. - Remove { count: usize }, - /// logs an Add event in a `Vec` type. - Add(::Type), + /// logs a change in a `Vec` type. + Change { index: usize, item: ::Type }, + /// Logs a remove event in a `Vec` type. + Remove { count: usize }, + /// logs an Add event in a `Vec` type. + Add(::Type), } /// `Diff` trait implementation for `Vec` impl Diff for Vec where - T: Clone + Debug + PartialEq + Diff + for<'de> Deserialize<'de> + Serialize, + T: Clone + Debug + PartialEq + Diff + for<'de> Deserialize<'de> + Serialize, { - /// Corresponding Diff Type for `Vec` - type Type = DiffVec; - - /// Compares two `Vec` types; `self`, `other` and returns a `DiffVec` type. - fn diff(&self, other: &Self) -> crate::Result { - let (l_len, r_len) = (self.len(), other.len()); - let max = usize::max(l_len, r_len); - let mut changes: Vec> = vec![]; - - for index in 0..max { - match (self.get(index), other.get(index)) { - (None, None) => panic!("No data to match"), - (Some(x), Some(y)) if x == y => {} - (Some(x), Some(y)) => changes.push(InnerVec::Change { - index, - item: x.diff(y)?, - }), - (None, Some(x)) => changes.push(InnerVec::Add(x.clone().into_diff()?)), - (Some(_), None) => match changes.last_mut() { - Some(InnerVec::Remove { ref mut count }) => *count += 1, - _ => changes.push(InnerVec::Remove { count: 1 }), - }, - } - } - - Ok(DiffVec(changes)) + /// Corresponding Diff Type for `Vec` + type Type = DiffVec; + + /// Compares two `Vec` types; `self`, `other` and returns a `DiffVec` type. + fn diff(&self, other: &Self) -> crate::Result { + let (l_len, r_len) = (self.len(), other.len()); + let max = usize::max(l_len, r_len); + let mut changes: Vec> = vec![]; + + for index in 0..max { + match (self.get(index), other.get(index)) { + (None, None) => panic!("No data to match"), + (Some(x), Some(y)) if x == y => {} + (Some(x), Some(y)) => changes.push(InnerVec::Change { + index, + item: x.diff(y)?, + }), + (None, Some(x)) => changes.push(InnerVec::Add(x.clone().into_diff()?)), + (Some(_), None) => match changes.last_mut() { + Some(InnerVec::Remove { ref mut count }) => *count += 1, + _ => changes.push(InnerVec::Remove { count: 1 }), + }, + } } - /// Merges a `DiffVec`; `diff` with `self`; a `Vec` to create a new `Vec`. - fn merge(&self, diff: Self::Type) -> crate::Result { - let mut vec: Self = self.clone(); - - for change in diff.0.into_iter() { - match change { - InnerVec::Add(d) => vec.push(::from_diff(d)?), - InnerVec::Change { index, item } => vec[index] = self[index].merge(item)?, - InnerVec::Remove { count } => { - for _ in 0..count { - vec.pop() - .ok_or_else(|| crate::Error::MergeError("Unable to pop value".into()))?; - } - } - } + Ok(DiffVec(changes)) + } + + /// Merges a `DiffVec`; `diff` with `self`; a `Vec` to create a new `Vec`. + fn merge(&self, diff: Self::Type) -> crate::Result { + let mut vec: Self = self.clone(); + + for change in diff.0.into_iter() { + match change { + InnerVec::Add(d) => vec.push(::from_diff(d)?), + InnerVec::Change { index, item } => vec[index] = self[index].merge(item)?, + InnerVec::Remove { count } => { + for _ in 0..count { + vec + .pop() + .ok_or_else(|| crate::Error::MergeError("Unable to pop value".into()))?; + } } - - Ok(vec) + } } - /// Converts a `DiffVec`; `diff` into a `Vec`. - fn from_diff(diff: Self::Type) -> crate::Result { - let mut vec: Vec = vec![]; - - for (_idx, elm) in diff.0.into_iter().enumerate() { - match elm { - InnerVec::Add(add) => vec.push(::from_diff(add)?), - InnerVec::Change { index: _, item } => { - vec.push(::from_diff(item)?); - } - _ => {} - } - } + Ok(vec) + } - Ok(vec) - } + /// Converts a `DiffVec`; `diff` into a `Vec`. + fn from_diff(diff: Self::Type) -> crate::Result { + let mut vec: Vec = vec![]; - /// Converts a `Vec` into a `DiffVec` - fn into_diff(self) -> crate::Result { - let mut changes: Vec> = vec![]; - for inner in self { - changes.push(InnerVec::Add(inner.into_diff()?)); + for (_idx, elm) in diff.0.into_iter().enumerate() { + match elm { + InnerVec::Add(add) => vec.push(::from_diff(add)?), + InnerVec::Change { index: _, item } => { + vec.push(::from_diff(item)?); } - Ok(DiffVec(changes)) + _ => {} + } + } + + Ok(vec) + } + + /// Converts a `Vec` into a `DiffVec` + fn into_diff(self) -> crate::Result { + let mut changes: Vec> = vec![]; + for inner in self { + changes.push(InnerVec::Add(inner.into_diff()?)); } + Ok(DiffVec(changes)) + } } /// Debug trait for `DiffVec` impl Debug for DiffVec { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - write!(f, "DiffVec ")?; - f.debug_list().entries(self.0.iter()).finish() - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!(f, "DiffVec ")?; + f.debug_list().entries(self.0.iter()).finish() + } } /// Debug trait for `InnerVec` impl Debug for InnerVec { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - match &self { - Self::Change { index, item } => f - .debug_struct("Change") - .field("index", index) - .field("item", item) - .finish(), - Self::Remove { count } => f.debug_struct("Remove").field("count", count).finish(), - Self::Add(diff) => f.debug_tuple("Add").field(diff).finish(), - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match &self { + Self::Change { index, item } => f + .debug_struct("Change") + .field("index", index) + .field("item", item) + .finish(), + Self::Remove { count } => f.debug_struct("Remove").field("count", count).finish(), + Self::Add(diff) => f.debug_tuple("Add").field(diff).finish(), } + } } /// Default trait for `DiffVec` impl Default for DiffVec { - fn default() -> Self { - DiffVec(Vec::new()) - } + fn default() -> Self { + DiffVec(Vec::new()) + } } #[cfg(test)] mod tests { - use super::*; + use super::*; - #[test] - fn test_diff_same_val() { - let vec_a: Vec = vec![1, 2, 3]; - let vec_b: Vec = vec![1, 2, 3]; + #[test] + fn test_diff_same_val() { + let vec_a: Vec = vec![1, 2, 3]; + let vec_b: Vec = vec![1, 2, 3]; - assert_eq!(vec_a, vec_b); + assert_eq!(vec_a, vec_b); - let diff = vec_a.diff(&vec_b).unwrap(); + let diff = vec_a.diff(&vec_b).unwrap(); - assert_eq!(diff, DiffVec(vec![])); + assert_eq!(diff, DiffVec(vec![])); - let vec_c = vec_a.merge(diff).unwrap(); + let vec_c = vec_a.merge(diff).unwrap(); - assert_eq!(vec_b, vec_c); + assert_eq!(vec_b, vec_c); - let diff = vec_b.diff(&vec_a).unwrap(); + let diff = vec_b.diff(&vec_a).unwrap(); - assert_eq!(diff, DiffVec(vec![])); + assert_eq!(diff, DiffVec(vec![])); - let vec_c = vec_b.merge(diff).unwrap(); + let vec_c = vec_b.merge(diff).unwrap(); - assert_eq!(vec_a, vec_c); - } + assert_eq!(vec_a, vec_c); + } - #[test] - fn test_different_vals() { - let vec_a = vec![1, 2, 3, 4, 5]; - let vec_b = vec![4, 2, 3, 4, 6]; - - let diff = vec_a.diff(&vec_b).unwrap(); - - assert_eq!( - diff, - DiffVec(vec![ - InnerVec::Change { - index: 0, - item: 4i32.into_diff().unwrap(), - }, - InnerVec::Change { - index: 4, - item: 6i32.into_diff().unwrap(), - } - ]) - ); - - let vec_c = vec_a.merge(diff).unwrap(); - - assert_eq!(vec_b, vec_c); - - let diff = vec_b.diff(&vec_a).unwrap(); - - assert_eq!( - diff, - DiffVec(vec![ - InnerVec::Change { - index: 0, - item: 1i32.into_diff().unwrap(), - }, - InnerVec::Change { - index: 4, - item: 5i32.into_diff().unwrap(), - } - ]) - ); - - let vec_c = vec_b.merge(diff).unwrap(); - - assert_eq!(vec_a, vec_c); - } + #[test] + fn test_different_vals() { + let vec_a = vec![1, 2, 3, 4, 5]; + let vec_b = vec![4, 2, 3, 4, 6]; - #[test] - fn test_diff_lengths() { - let vec_a = vec![1, 2, 3, 4, 5, 6]; - let vec_b = vec![1, 2, 3, 4, 6, 7, 8]; - - let diff = vec_a.diff(&vec_b).unwrap(); - - assert_eq!( - diff, - DiffVec(vec![ - InnerVec::Change { - index: 4, - item: 6i32.into_diff().unwrap(), - }, - InnerVec::Change { - index: 5, - item: 7i32.into_diff().unwrap(), - }, - InnerVec::Add(8.into_diff().unwrap()) - ]) - ); - - let vec_c = vec_a.merge(diff).unwrap(); - - assert_eq!(vec_b, vec_c); - - let diff = vec_b.diff(&vec_a).unwrap(); - - assert_eq!( - diff, - DiffVec(vec![ - InnerVec::Change { - index: 4, - item: 5i32.into_diff().unwrap(), - }, - InnerVec::Change { - index: 5, - item: 6i32.into_diff().unwrap(), - }, - InnerVec::Remove { count: 1 }, - ]) - ); - - let vec_c = vec_b.merge(diff).unwrap(); - - assert_eq!(vec_a, vec_c); - } + let diff = vec_a.diff(&vec_b).unwrap(); + + assert_eq!( + diff, + DiffVec(vec![ + InnerVec::Change { + index: 0, + item: 4i32.into_diff().unwrap(), + }, + InnerVec::Change { + index: 4, + item: 6i32.into_diff().unwrap(), + } + ]) + ); + + let vec_c = vec_a.merge(diff).unwrap(); + + assert_eq!(vec_b, vec_c); + + let diff = vec_b.diff(&vec_a).unwrap(); + + assert_eq!( + diff, + DiffVec(vec![ + InnerVec::Change { + index: 0, + item: 1i32.into_diff().unwrap(), + }, + InnerVec::Change { + index: 4, + item: 5i32.into_diff().unwrap(), + } + ]) + ); + + let vec_c = vec_b.merge(diff).unwrap(); + + assert_eq!(vec_a, vec_c); + } + + #[test] + fn test_diff_lengths() { + let vec_a = vec![1, 2, 3, 4, 5, 6]; + let vec_b = vec![1, 2, 3, 4, 6, 7, 8]; + + let diff = vec_a.diff(&vec_b).unwrap(); + + assert_eq!( + diff, + DiffVec(vec![ + InnerVec::Change { + index: 4, + item: 6i32.into_diff().unwrap(), + }, + InnerVec::Change { + index: 5, + item: 7i32.into_diff().unwrap(), + }, + InnerVec::Add(8.into_diff().unwrap()) + ]) + ); + + let vec_c = vec_a.merge(diff).unwrap(); + + assert_eq!(vec_b, vec_c); + + let diff = vec_b.diff(&vec_a).unwrap(); + + assert_eq!( + diff, + DiffVec(vec![ + InnerVec::Change { + index: 4, + item: 5i32.into_diff().unwrap(), + }, + InnerVec::Change { + index: 5, + item: 6i32.into_diff().unwrap(), + }, + InnerVec::Remove { count: 1 }, + ]) + ); + + let vec_c = vec_b.merge(diff).unwrap(); + + assert_eq!(vec_a, vec_c); + } } #[test] fn test_into_from_diff() { - let vec_a = vec![1, 2, 3, 4, 5, 6]; - let vec_b = vec![2, 3, 4, 3, 2, 1, 10, 20]; + let vec_a = vec![1, 2, 3, 4, 5, 6]; + let vec_b = vec![2, 3, 4, 3, 2, 1, 10, 20]; - let diff = vec_a.diff(&vec_b).unwrap(); + let diff = vec_a.diff(&vec_b).unwrap(); - let vec = Vec::from_diff(diff).unwrap(); + let vec = Vec::from_diff(diff).unwrap(); - assert_eq!(vec, vec_b); + assert_eq!(vec, vec_b); } diff --git a/identity-diff/tests/derive_enum_test.rs b/identity-diff/tests/derive_enum_test.rs index 3661b2cde6..762548c423 100644 --- a/identity-diff/tests/derive_enum_test.rs +++ b/identity-diff/tests/derive_enum_test.rs @@ -8,356 +8,356 @@ use serde::{Deserialize, Serialize}; #[derive(Diff, Debug, Clone, PartialEq)] pub enum StructEnum { - A { x: usize }, - B { y: usize }, + A { x: usize }, + B { y: usize }, } #[derive(Diff, Debug, Clone, PartialEq)] pub enum UnitEnum { - A, - B, - C, + A, + B, + C, } #[derive(Diff, Debug, Clone, PartialEq)] pub enum TupleEnum { - A(usize), - B(String), - C(usize, usize), + A(usize), + B(String), + C(usize, usize), } #[derive(Diff, Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum MixedEnum { - A, - B(usize), - C { y: String }, + A, + B(usize), + C { y: String }, } #[derive(Diff, Debug, Clone, PartialEq)] pub enum NestedEnum { - Nest(InnerEnum), + Nest(InnerEnum), } #[derive(Diff, Debug, Clone, PartialEq)] pub enum InnerEnum { - Inner { y: InnerStruct }, + Inner { y: InnerStruct }, } impl Default for InnerEnum { - fn default() -> Self { - Self::Inner { - y: InnerStruct::default(), - } + fn default() -> Self { + Self::Inner { + y: InnerStruct::default(), } + } } #[derive(Diff, Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum TestOpt { - Inner(Option), - InnerS { a: Option }, + Inner(Option), + InnerS { a: Option }, } impl Default for TestOpt { - fn default() -> Self { - TestOpt::Inner(None) - } + fn default() -> Self { + TestOpt::Inner(None) + } } #[derive(Diff, Debug, Clone, PartialEq, Default)] pub struct InnerStruct { - y: usize, + y: usize, } #[derive(Diff, Debug, Clone, PartialEq)] pub enum EnumWithGeneric where - T: Clone + Default, - S: Clone + Default, + T: Clone + Default, + S: Clone + Default, { - A(T), - B(S), + A(T), + B(S), } #[derive(Diff, Debug, Clone, PartialEq)] pub enum IgnoreEnum { - A { - #[diff(should_ignore)] - x: usize, - y: usize, - }, - B(#[diff(should_ignore)] String, usize), + A { + #[diff(should_ignore)] + x: usize, + y: usize, + }, + B(#[diff(should_ignore)] String, usize), } #[derive(Diff, Debug, Clone, PartialEq, Serialize, Deserialize)] #[diff(from_into)] #[serde(untagged)] pub enum IntoFrom { - Test(TestOpt), - SomeField(String), + Test(TestOpt), + SomeField(String), } #[test] fn test_struct_enum() { - let t = StructEnum::A { x: 100 }; + let t = StructEnum::A { x: 100 }; - let t2 = StructEnum::B { y: 200 }; + let t2 = StructEnum::B { y: 200 }; - let diff = t.diff(&t2).unwrap(); + let diff = t.diff(&t2).unwrap(); - let res = t.merge(diff).unwrap(); + let res = t.merge(diff).unwrap(); - assert_eq!(t2, res); + assert_eq!(t2, res); - let diff = t2.into_diff().unwrap(); + let diff = t2.into_diff().unwrap(); - assert_eq!( - DiffStructEnum::B { - y: Some(200_usize.into_diff().unwrap()) - }, - diff - ); + assert_eq!( + DiffStructEnum::B { + y: Some(200_usize.into_diff().unwrap()) + }, + diff + ); - let res = StructEnum::from_diff(diff).unwrap(); + let res = StructEnum::from_diff(diff).unwrap(); - assert_eq!(StructEnum::B { y: 200 }, res); + assert_eq!(StructEnum::B { y: 200 }, res); } #[test] fn test_unit_enum() { - let t = UnitEnum::A; - let t2 = UnitEnum::B; + let t = UnitEnum::A; + let t2 = UnitEnum::B; - let diff = t.diff(&t2).unwrap(); + let diff = t.diff(&t2).unwrap(); - let res = t.merge(diff).unwrap(); + let res = t.merge(diff).unwrap(); - assert_eq!(t2, res); + assert_eq!(t2, res); - let diff = t2.into_diff().unwrap(); + let diff = t2.into_diff().unwrap(); - assert_eq!(DiffUnitEnum::B, diff); + assert_eq!(DiffUnitEnum::B, diff); - let res = UnitEnum::from_diff(diff).unwrap(); + let res = UnitEnum::from_diff(diff).unwrap(); - assert_eq!(UnitEnum::B, res); + assert_eq!(UnitEnum::B, res); } #[test] fn test_tuple_enum() { - let t = TupleEnum::A(10); - let t2 = TupleEnum::C(20, 30); + let t = TupleEnum::A(10); + let t2 = TupleEnum::C(20, 30); - let diff = t.diff(&t2).unwrap(); + let diff = t.diff(&t2).unwrap(); - let res = t.merge(diff).unwrap(); + let res = t.merge(diff).unwrap(); - assert_eq!(t2, res); + assert_eq!(t2, res); - let diff = t2.into_diff().unwrap(); + let diff = t2.into_diff().unwrap(); - assert_eq!( - DiffTupleEnum::C(Some(20_usize.into_diff().unwrap()), Some(30_usize.into_diff().unwrap())), - diff - ); + assert_eq!( + DiffTupleEnum::C(Some(20_usize.into_diff().unwrap()), Some(30_usize.into_diff().unwrap())), + diff + ); - let res = TupleEnum::from_diff(diff).unwrap(); + let res = TupleEnum::from_diff(diff).unwrap(); - assert_eq!(TupleEnum::C(20, 30), res); + assert_eq!(TupleEnum::C(20, 30), res); } #[test] fn test_mixed_enum() { - let t = MixedEnum::B(10); - let t2 = MixedEnum::C { - y: String::from("test"), - }; + let t = MixedEnum::B(10); + let t2 = MixedEnum::C { + y: String::from("test"), + }; - let diff = t.diff(&t2).unwrap(); + let diff = t.diff(&t2).unwrap(); - let res = t.merge(diff).unwrap(); + let res = t.merge(diff).unwrap(); - assert_eq!(t2, res); + assert_eq!(t2, res); - let diff = t2.into_diff().unwrap(); + let diff = t2.into_diff().unwrap(); - assert_eq!( - DiffMixedEnum::C { - y: Some(String::from("test").into_diff().unwrap()) - }, - diff - ); + assert_eq!( + DiffMixedEnum::C { + y: Some(String::from("test").into_diff().unwrap()) + }, + diff + ); - let res = MixedEnum::from_diff(diff).unwrap(); + let res = MixedEnum::from_diff(diff).unwrap(); - assert_eq!( - MixedEnum::C { - y: String::from("test"), - }, - res - ); + assert_eq!( + MixedEnum::C { + y: String::from("test"), + }, + res + ); } #[test] fn test_nested_enum() { - let t = NestedEnum::Nest(InnerEnum::default()); - let t2 = NestedEnum::Nest(InnerEnum::Inner { - y: InnerStruct { y: 10 }, - }); + let t = NestedEnum::Nest(InnerEnum::default()); + let t2 = NestedEnum::Nest(InnerEnum::Inner { + y: InnerStruct { y: 10 }, + }); - let diff = t.diff(&t2).unwrap(); + let diff = t.diff(&t2).unwrap(); - let res = t.merge(diff).unwrap(); + let res = t.merge(diff).unwrap(); - assert_eq!(t2, res); + assert_eq!(t2, res); - let diff = t2.into_diff().unwrap(); + let diff = t2.into_diff().unwrap(); - assert_eq!( - DiffNestedEnum::Nest(Some(DiffInnerEnum::Inner { - y: Some(DiffInnerStruct { - y: Some(10_usize.into_diff().unwrap()) - }) - })), - diff - ); + assert_eq!( + DiffNestedEnum::Nest(Some(DiffInnerEnum::Inner { + y: Some(DiffInnerStruct { + y: Some(10_usize.into_diff().unwrap()) + }) + })), + diff + ); - let res = NestedEnum::from_diff(diff).unwrap(); + let res = NestedEnum::from_diff(diff).unwrap(); - assert_eq!( - NestedEnum::Nest(InnerEnum::Inner { - y: InnerStruct { y: 10 }, - }), - res - ); + assert_eq!( + NestedEnum::Nest(InnerEnum::Inner { + y: InnerStruct { y: 10 }, + }), + res + ); } #[test] fn test_enum_with_generics() { - let t: EnumWithGeneric = EnumWithGeneric::A(String::from("test")); - let t2: EnumWithGeneric = EnumWithGeneric::B(10); + let t: EnumWithGeneric = EnumWithGeneric::A(String::from("test")); + let t2: EnumWithGeneric = EnumWithGeneric::B(10); - let diff = t.diff(&t2).unwrap(); + let diff = t.diff(&t2).unwrap(); - let res = t.merge(diff).unwrap(); + let res = t.merge(diff).unwrap(); - assert_eq!(t2, res); + assert_eq!(t2, res); - let diff = t2.into_diff().unwrap(); + let diff = t2.into_diff().unwrap(); - assert_eq!(DiffEnumWithGeneric::B(Some(10_usize.into_diff().unwrap())), diff); + assert_eq!(DiffEnumWithGeneric::B(Some(10_usize.into_diff().unwrap())), diff); - let res = EnumWithGeneric::from_diff(diff).unwrap(); + let res = EnumWithGeneric::from_diff(diff).unwrap(); - assert_eq!(EnumWithGeneric::B(10), res); + assert_eq!(EnumWithGeneric::B(10), res); } #[test] fn test_ignore_enum() { - let t = IgnoreEnum::A { x: 10, y: 10 }; - let t2 = IgnoreEnum::B(String::from("test"), 30); + let t = IgnoreEnum::A { x: 10, y: 10 }; + let t2 = IgnoreEnum::B(String::from("test"), 30); - let diff = t.diff(&t2).unwrap(); + let diff = t.diff(&t2).unwrap(); - let res = t.merge(diff).unwrap(); + let res = t.merge(diff).unwrap(); - let expected = IgnoreEnum::B(String::new(), 30); + let expected = IgnoreEnum::B(String::new(), 30); - assert_eq!(expected, res) + assert_eq!(expected, res) } #[test] fn test_serde_enum() { - let t = MixedEnum::B(10); - let t2 = MixedEnum::C { - y: String::from("test"), - }; + let t = MixedEnum::B(10); + let t2 = MixedEnum::C { + y: String::from("test"), + }; - let diff = t.diff(&t2).unwrap(); + let diff = t.diff(&t2).unwrap(); - let json = serde_json::to_string(&diff).unwrap(); + let json = serde_json::to_string(&diff).unwrap(); - let diff = serde_json::from_str(&json).unwrap(); + let diff = serde_json::from_str(&json).unwrap(); - let res = t.merge(diff).unwrap(); + let res = t.merge(diff).unwrap(); - assert_eq!(t2, res); + assert_eq!(t2, res); - let diff = t2.into_diff().unwrap(); + let diff = t2.into_diff().unwrap(); - assert_eq!( - DiffMixedEnum::C { - y: Some(String::from("test").into_diff().unwrap()) - }, - diff - ); + assert_eq!( + DiffMixedEnum::C { + y: Some(String::from("test").into_diff().unwrap()) + }, + diff + ); - let res = MixedEnum::from_diff(diff).unwrap(); + let res = MixedEnum::from_diff(diff).unwrap(); - assert_eq!( - MixedEnum::C { - y: String::from("test"), - }, - res - ); + assert_eq!( + MixedEnum::C { + y: String::from("test"), + }, + res + ); } #[test] fn test_enum_opt() { - let t = TestOpt::Inner(None); - let t2 = TestOpt::Inner(None); + let t = TestOpt::Inner(None); + let t2 = TestOpt::Inner(None); - let diff1 = t.diff(&t2).unwrap(); + let diff1 = t.diff(&t2).unwrap(); - let diff2 = t2.into_diff().unwrap(); + let diff2 = t2.into_diff().unwrap(); - assert_eq!(diff1, diff2); + assert_eq!(diff1, diff2); - let t = TestOpt::InnerS { a: None }; - let diff = t.into_diff().unwrap(); + let t = TestOpt::InnerS { a: None }; + let diff = t.into_diff().unwrap(); - let json = serde_json::to_string(&diff).unwrap(); + let json = serde_json::to_string(&diff).unwrap(); - let expected = r#"{"InnerS":{}}"#; + let expected = r#"{"InnerS":{}}"#; - assert_eq!(expected, json); + assert_eq!(expected, json); - let t = TestOpt::InnerS { a: None }; - let t2 = TestOpt::InnerS { a: None }; + let t = TestOpt::InnerS { a: None }; + let t2 = TestOpt::InnerS { a: None }; - let diff = t.diff(&t2).unwrap(); + let diff = t.diff(&t2).unwrap(); - let json = serde_json::to_string(&diff).unwrap(); + let json = serde_json::to_string(&diff).unwrap(); - assert_eq!(expected, json); + assert_eq!(expected, json); - let t = TestOpt::Inner(None); - let t2 = TestOpt::InnerS { a: None }; + let t = TestOpt::Inner(None); + let t2 = TestOpt::InnerS { a: None }; - let diff = t.diff(&t2).unwrap(); + let diff = t.diff(&t2).unwrap(); - let json = serde_json::to_string(&diff).unwrap(); + let json = serde_json::to_string(&diff).unwrap(); - assert_eq!(expected, json); + assert_eq!(expected, json); } #[test] fn test_from_into() { - let t = IntoFrom::SomeField(String::from("Test")); + let t = IntoFrom::SomeField(String::from("Test")); - let t2 = IntoFrom::Test(TestOpt::Inner(Some(10))); + let t2 = IntoFrom::Test(TestOpt::Inner(Some(10))); - let diff = t.diff(&t2).unwrap(); + let diff = t.diff(&t2).unwrap(); - let json = serde_json::to_string(&diff).unwrap(); + let json = serde_json::to_string(&diff).unwrap(); - let expected = r#"{"Inner":10}"#; + let expected = r#"{"Inner":10}"#; - assert_eq!(expected, json); + assert_eq!(expected, json); - let diff: DiffIntoFrom = serde_json::from_str(&json).unwrap(); + let diff: DiffIntoFrom = serde_json::from_str(&json).unwrap(); - let merge = t2.merge(diff).unwrap(); + let merge = t2.merge(diff).unwrap(); - let expected = IntoFrom::Test(TestOpt::Inner(Some(10))); + let expected = IntoFrom::Test(TestOpt::Inner(Some(10))); - assert_eq!(expected, merge); + assert_eq!(expected, merge); } diff --git a/identity-iota/src/chain/auth.rs b/identity-iota/src/chain/auth.rs index a284aefdcc..e10301ab45 100644 --- a/identity-iota/src/chain/auth.rs +++ b/identity-iota/src/chain/auth.rs @@ -4,128 +4,129 @@ use core::mem; use crate::{ - did::{IotaDID, IotaDocument}, - error::{Error, Result}, - tangle::{Message, MessageId, MessageIndex, TangleRef as _}, + did::{IotaDID, IotaDocument}, + error::{Error, Result}, + tangle::{Message, MessageId, MessageIndex, TangleRef as _}, }; #[derive(Debug)] pub struct AuthChain { - pub(crate) current: IotaDocument, - pub(crate) history: Option>, + pub(crate) current: IotaDocument, + pub(crate) history: Option>, } impl AuthChain { - /// Constructs a new `AuthChain` from a slice of `Message`s. - pub fn try_from_messages(did: &IotaDID, messages: &[Message]) -> Result { - let mut index: MessageIndex = messages - .iter() - .flat_map(|message| message.try_extract_document(did)) - .collect(); - - let current: IotaDocument = - index - .remove_where(&MessageId::NONE, |doc| doc.verify().is_ok()) - .ok_or(Error::ChainError { - error: "Invalid Root Document", - })?; - - let mut this: Self = Self::new(current)?; - - while let Some(mut list) = index.remove(this.current_message_id()) { - 'inner: while let Some(document) = list.pop() { - if this.try_push(document).is_ok() { - break 'inner; - } - } + /// Constructs a new `AuthChain` from a slice of `Message`s. + pub fn try_from_messages(did: &IotaDID, messages: &[Message]) -> Result { + let mut index: MessageIndex = messages + .iter() + .flat_map(|message| message.try_extract_document(did)) + .collect(); + + let current: IotaDocument = + index + .remove_where(&MessageId::NONE, |doc| doc.verify().is_ok()) + .ok_or(Error::ChainError { + error: "Invalid Root Document", + })?; + + let mut this: Self = Self::new(current)?; + + while let Some(mut list) = index.remove(this.current_message_id()) { + 'inner: while let Some(document) = list.pop() { + if this.try_push(document).is_ok() { + break 'inner; } - - Ok(this) + } } - /// Creates a new `AuthChain` with the given `IotaDocument` as the latest. - pub fn new(current: IotaDocument) -> Result { - if current.verify().is_err() { - return Err(Error::ChainError { - error: "Invalid Signature", - }); - } - - if current.message_id().is_none() { - return Err(Error::ChainError { - error: "Invalid Message Id", - }); - } + Ok(this) + } - Ok(Self { current, history: None }) + /// Creates a new `AuthChain` with the given `IotaDocument` as the latest. + pub fn new(current: IotaDocument) -> Result { + if current.verify().is_err() { + return Err(Error::ChainError { + error: "Invalid Signature", + }); } - /// Returns a reference to the latest document in the auth chain. - pub fn current(&self) -> &IotaDocument { - &self.current + if current.message_id().is_none() { + return Err(Error::ChainError { + error: "Invalid Message Id", + }); } - /// Returns a mutable reference to the latest document in the auth chain. - pub fn current_mut(&mut self) -> &mut IotaDocument { - &mut self.current + Ok(Self { current, history: None }) + } + + /// Returns a reference to the latest document in the auth chain. + pub fn current(&self) -> &IotaDocument { + &self.current + } + + /// Returns a mutable reference to the latest document in the auth chain. + pub fn current_mut(&mut self) -> &mut IotaDocument { + &mut self.current + } + + /// Returns the Tangle message Id of the latest auth document. + pub fn current_message_id(&self) -> &MessageId { + self.current.message_id() + } + + /// Adds a new document to the auth chain. + /// + /// # Errors + /// + /// Fails if the document signature is invalid or the Tangle message + /// references within the document are invalid. + pub fn try_push(&mut self, document: IotaDocument) -> Result<()> { + self.check_validity(&document)?; + + self + .history + .get_or_insert_with(Vec::new) + .push(mem::replace(&mut self.current, document)); + + Ok(()) + } + + /// Returns `true` if the `IotaDocument` can be added to the auth chain. + pub fn is_valid(&self, document: &IotaDocument) -> bool { + self.check_validity(document).is_ok() + } + + /// Checks if the `IotaDocument` can be added to the auth chain. + /// + /// # Errors + /// + /// Fails if the `IotaDocument` is not a valid addition. + pub fn check_validity(&self, document: &IotaDocument) -> Result<()> { + if self.current.verify_data(document).is_err() { + return Err(Error::ChainError { + error: "Invalid Signature", + }); } - /// Returns the Tangle message Id of the latest auth document. - pub fn current_message_id(&self) -> &MessageId { - self.current.message_id() + if document.message_id().is_none() { + return Err(Error::ChainError { + error: "Invalid Message Id", + }); } - /// Adds a new document to the auth chain. - /// - /// # Errors - /// - /// Fails if the document signature is invalid or the Tangle message - /// references within the document are invalid. - pub fn try_push(&mut self, document: IotaDocument) -> Result<()> { - self.check_validity(&document)?; - - self.history - .get_or_insert_with(Vec::new) - .push(mem::replace(&mut self.current, document)); - - Ok(()) + if document.previous_message_id().is_none() { + return Err(Error::ChainError { + error: "Invalid Previous Message Id", + }); } - /// Returns `true` if the `IotaDocument` can be added to the auth chain. - pub fn is_valid(&self, document: &IotaDocument) -> bool { - self.check_validity(document).is_ok() + if self.current_message_id() != document.previous_message_id() { + return Err(Error::ChainError { + error: "Invalid Previous Message Id", + }); } - /// Checks if the `IotaDocument` can be added to the auth chain. - /// - /// # Errors - /// - /// Fails if the `IotaDocument` is not a valid addition. - pub fn check_validity(&self, document: &IotaDocument) -> Result<()> { - if self.current.verify_data(document).is_err() { - return Err(Error::ChainError { - error: "Invalid Signature", - }); - } - - if document.message_id().is_none() { - return Err(Error::ChainError { - error: "Invalid Message Id", - }); - } - - if document.previous_message_id().is_none() { - return Err(Error::ChainError { - error: "Invalid Previous Message Id", - }); - } - - if self.current_message_id() != document.previous_message_id() { - return Err(Error::ChainError { - error: "Invalid Previous Message Id", - }); - } - - Ok(()) - } + Ok(()) + } } diff --git a/identity-iota/src/chain/diff.rs b/identity-iota/src/chain/diff.rs index 23dcd04523..043d356b14 100644 --- a/identity-iota/src/chain/diff.rs +++ b/identity-iota/src/chain/diff.rs @@ -4,143 +4,143 @@ use core::slice::Iter; use crate::{ - chain::{AuthChain, DocumentChain}, - did::{DocumentDiff, IotaDID}, - error::{Error, Result}, - tangle::{Message, MessageId, MessageIndex, TangleRef as _}, + chain::{AuthChain, DocumentChain}, + did::{DocumentDiff, IotaDID}, + error::{Error, Result}, + tangle::{Message, MessageId, MessageIndex, TangleRef as _}, }; #[derive(Debug)] pub struct DiffChain { - inner: Vec, + inner: Vec, } impl DiffChain { - /// Constructs a new `DiffChain` for the given `AuthChain` from a slice of `Message`s. - pub fn try_from_messages(auth: &AuthChain, messages: &[Message]) -> Result { - if messages.is_empty() { - return Ok(Self::new()); - } + /// Constructs a new `DiffChain` for the given `AuthChain` from a slice of `Message`s. + pub fn try_from_messages(auth: &AuthChain, messages: &[Message]) -> Result { + if messages.is_empty() { + return Ok(Self::new()); + } - let did: &IotaDID = auth.current().id(); + let did: &IotaDID = auth.current().id(); - let mut index: MessageIndex = messages - .iter() - .flat_map(|message| message.try_extract_diff(did)) - .collect(); + let mut index: MessageIndex = messages + .iter() + .flat_map(|message| message.try_extract_diff(did)) + .collect(); - let mut this: Self = Self::new(); + let mut this: Self = Self::new(); - while let Some(mut list) = index.remove(DocumentChain::__diff_message_id(auth, &this)) { - 'inner: while let Some(next) = list.pop() { - if auth.current().verify_data(&next).is_ok() { - this.inner.push(next); - break 'inner; - } - } + while let Some(mut list) = index.remove(DocumentChain::__diff_message_id(auth, &this)) { + 'inner: while let Some(next) = list.pop() { + if auth.current().verify_data(&next).is_ok() { + this.inner.push(next); + break 'inner; } - - Ok(this) - } - - /// Creates a new `DiffChain`. - pub fn new() -> Self { - Self { inner: Vec::new() } - } - - /// Returns the total number of diffs in the chain. - pub fn len(&self) -> usize { - self.inner.len() - } - - /// Returns `true` if the diff chain is empty. - pub fn is_empty(&self) -> bool { - self.inner.is_empty() + } } - /// Empties the diff chain, removing all diffs. - pub fn clear(&mut self) { - self.inner.clear(); + Ok(this) + } + + /// Creates a new `DiffChain`. + pub fn new() -> Self { + Self { inner: Vec::new() } + } + + /// Returns the total number of diffs in the chain. + pub fn len(&self) -> usize { + self.inner.len() + } + + /// Returns `true` if the diff chain is empty. + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + /// Empties the diff chain, removing all diffs. + pub fn clear(&mut self) { + self.inner.clear(); + } + + /// Returns an iterator yielding references to `DocumentDiff`s. + pub fn iter(&self) -> Iter<'_, DocumentDiff> { + self.inner.iter() + } + + /// Returns the `MessageId` of the latest diff if the chain, if any. + pub fn current_message_id(&self) -> Option<&MessageId> { + self.inner.last().map(|diff| diff.message_id()) + } + + /// Adds a new diff to the diff chain. + /// + /// # Errors + /// + /// Fails if the diff signature is invalid or the Tangle message + /// references within the diff are invalid. + pub fn try_push(&mut self, auth: &AuthChain, diff: DocumentDiff) -> Result<()> { + self.check_validity(auth, &diff)?; + + // SAFETY: we performed the necessary validation in `check_validity`. + unsafe { + self.push_unchecked(diff); } - /// Returns an iterator yielding references to `DocumentDiff`s. - pub fn iter(&self) -> Iter<'_, DocumentDiff> { - self.inner.iter() + Ok(()) + } + + /// Adds a new diff to the diff chain with performing validation checks. + /// + /// # Safety + /// + /// This function is unsafe because it does not check the validity of + /// the signature or Tangle references of the `DocumentDiff`. + pub unsafe fn push_unchecked(&mut self, diff: DocumentDiff) { + self.inner.push(diff); + } + + /// Returns `true` if the `DocumentDiff` can be added to the diff chain. + pub fn is_valid(&self, auth: &AuthChain, diff: &DocumentDiff) -> bool { + self.check_validity(auth, diff).is_ok() + } + + /// Checks if the `DocumentDiff` can be added to the diff chain. + /// + /// # Errors + /// + /// Fails if the `DocumentDiff` is not a valid addition. + pub fn check_validity(&self, auth: &AuthChain, diff: &DocumentDiff) -> Result<()> { + if auth.current().verify_data(diff).is_err() { + return Err(Error::ChainError { + error: "Invalid Signature", + }); } - /// Returns the `MessageId` of the latest diff if the chain, if any. - pub fn current_message_id(&self) -> Option<&MessageId> { - self.inner.last().map(|diff| diff.message_id()) + if diff.message_id().is_none() { + return Err(Error::ChainError { + error: "Invalid Message Id", + }); } - /// Adds a new diff to the diff chain. - /// - /// # Errors - /// - /// Fails if the diff signature is invalid or the Tangle message - /// references within the diff are invalid. - pub fn try_push(&mut self, auth: &AuthChain, diff: DocumentDiff) -> Result<()> { - self.check_validity(auth, &diff)?; - - // SAFETY: we performed the necessary validation in `check_validity`. - unsafe { - self.push_unchecked(diff); - } - - Ok(()) + if diff.previous_message_id().is_none() { + return Err(Error::ChainError { + error: "Invalid Previous Message Id", + }); } - /// Adds a new diff to the diff chain with performing validation checks. - /// - /// # Safety - /// - /// This function is unsafe because it does not check the validity of - /// the signature or Tangle references of the `DocumentDiff`. - pub unsafe fn push_unchecked(&mut self, diff: DocumentDiff) { - self.inner.push(diff); + if diff.previous_message_id() != DocumentChain::__diff_message_id(auth, self) { + return Err(Error::ChainError { + error: "Invalid Previous Message Id", + }); } - /// Returns `true` if the `DocumentDiff` can be added to the diff chain. - pub fn is_valid(&self, auth: &AuthChain, diff: &DocumentDiff) -> bool { - self.check_validity(auth, diff).is_ok() - } - - /// Checks if the `DocumentDiff` can be added to the diff chain. - /// - /// # Errors - /// - /// Fails if the `DocumentDiff` is not a valid addition. - pub fn check_validity(&self, auth: &AuthChain, diff: &DocumentDiff) -> Result<()> { - if auth.current().verify_data(diff).is_err() { - return Err(Error::ChainError { - error: "Invalid Signature", - }); - } - - if diff.message_id().is_none() { - return Err(Error::ChainError { - error: "Invalid Message Id", - }); - } - - if diff.previous_message_id().is_none() { - return Err(Error::ChainError { - error: "Invalid Previous Message Id", - }); - } - - if diff.previous_message_id() != DocumentChain::__diff_message_id(auth, self) { - return Err(Error::ChainError { - error: "Invalid Previous Message Id", - }); - } - - Ok(()) - } + Ok(()) + } } impl Default for DiffChain { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } diff --git a/identity-iota/src/chain/document.rs b/identity-iota/src/chain/document.rs index be42695121..7a61143590 100644 --- a/identity-iota/src/chain/document.rs +++ b/identity-iota/src/chain/document.rs @@ -2,151 +2,151 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - chain::{AuthChain, DiffChain}, - did::{DocumentDiff, IotaDID, IotaDocument}, - error::Result, - tangle::MessageId, + chain::{AuthChain, DiffChain}, + did::{DocumentDiff, IotaDID, IotaDocument}, + error::Result, + tangle::MessageId, }; #[derive(Debug)] pub struct DocumentChain { - auth_chain: AuthChain, - diff_chain: DiffChain, - document: Option, + auth_chain: AuthChain, + diff_chain: DiffChain, + document: Option, } impl DocumentChain { - pub(crate) fn __diff_message_id<'a>(auth: &'a AuthChain, diff: &'a DiffChain) -> &'a MessageId { - diff.current_message_id().unwrap_or_else(|| auth.current_message_id()) - } - - pub(crate) fn __fold(auth_chain: &AuthChain, diff_chain: &DiffChain) -> Result { - let mut this: IotaDocument = auth_chain.current.clone(); - - for diff in diff_chain.iter() { - this.merge(diff)?; - } - - Ok(this) - } - - /// Creates a new `DocumentChain` from given the `AuthChain`. - pub fn new(auth_chain: AuthChain) -> Self { - Self { - auth_chain, - diff_chain: DiffChain::new(), - document: None, - } - } - - /// Creates a new `DocumentChain` from given the `AuthChain` and `DiffChain`. - pub fn with_diff_chain(auth_chain: AuthChain, diff_chain: DiffChain) -> Result { - let document: Option = if diff_chain.is_empty() { - None - } else { - Some(Self::__fold(&auth_chain, &diff_chain)?) - }; - - Ok(Self { - auth_chain, - diff_chain, - document, - }) - } - - /// Returns a reference to the DID identifying the document chain. - pub fn id(&self) -> &IotaDID { - self.auth_chain.current.id() - } - - /// Returns a reference to the `AuthChain`. - pub fn auth(&self) -> &AuthChain { - &self.auth_chain - } - - /// Returns a mutable reference to the `AuthChain`. - pub fn auth_mut(&mut self) -> &mut AuthChain { - &mut self.auth_chain - } - - /// Returns a reference to the `DiffChain`. - pub fn diff(&self) -> &DiffChain { - &self.diff_chain - } - - /// Returns a mutable reference to the `DiffChain`. - pub fn diff_mut(&mut self) -> &mut DiffChain { - &mut self.diff_chain - } - - pub fn fold(mut self) -> Result { - for diff in self.diff_chain.iter() { - self.auth_chain.current.merge(diff)?; - } - - Ok(self.auth_chain.current) - } - - /// Returns a reference to the latest document in the chain. - pub fn current(&self) -> &IotaDocument { - self.document.as_ref().unwrap_or_else(|| self.auth_chain.current()) - } - - /// Returns a mutable reference to the latest document in the chain. - pub fn current_mut(&mut self) -> &mut IotaDocument { - if let Some(document) = self.document.as_mut() { - document - } else { - self.auth_chain.current_mut() - } - } - - /// Returns the Tangle message Id of the latest auth document. - pub fn auth_message_id(&self) -> &MessageId { - self.auth_chain.current_message_id() - } - - /// Returns the Tangle message Id of the latest diff or auth document. - pub fn diff_message_id(&self) -> &MessageId { - Self::__diff_message_id(&self.auth_chain, &self.diff_chain) - } - - /// Adds a new auth document to the chain. - /// - /// # Errors - /// - /// Fails if the document is not a valid auth document. - pub fn try_push_auth(&mut self, document: IotaDocument) -> Result<()> { - self.auth_chain.try_push(document)?; - self.diff_chain.clear(); - - self.document = None; - - Ok(()) - } - - /// Adds a new diff to the current diff chain. - /// - /// # Errors - /// - /// Fails if the document diff is invalid. - pub fn try_push_diff(&mut self, diff: DocumentDiff) -> Result<()> { - self.diff_chain.check_validity(&self.auth_chain, &diff)?; - - let mut document: IotaDocument = self - .document - .take() - .unwrap_or_else(|| self.auth_chain.current().clone()); - - document.merge(&diff)?; - - self.document = Some(document); - - // SAFETY: we performed the necessary validation in `DiffChain::check_validity`. - unsafe { - self.diff_chain.push_unchecked(diff); - } - - Ok(()) - } + pub(crate) fn __diff_message_id<'a>(auth: &'a AuthChain, diff: &'a DiffChain) -> &'a MessageId { + diff.current_message_id().unwrap_or_else(|| auth.current_message_id()) + } + + pub(crate) fn __fold(auth_chain: &AuthChain, diff_chain: &DiffChain) -> Result { + let mut this: IotaDocument = auth_chain.current.clone(); + + for diff in diff_chain.iter() { + this.merge(diff)?; + } + + Ok(this) + } + + /// Creates a new `DocumentChain` from given the `AuthChain`. + pub fn new(auth_chain: AuthChain) -> Self { + Self { + auth_chain, + diff_chain: DiffChain::new(), + document: None, + } + } + + /// Creates a new `DocumentChain` from given the `AuthChain` and `DiffChain`. + pub fn with_diff_chain(auth_chain: AuthChain, diff_chain: DiffChain) -> Result { + let document: Option = if diff_chain.is_empty() { + None + } else { + Some(Self::__fold(&auth_chain, &diff_chain)?) + }; + + Ok(Self { + auth_chain, + diff_chain, + document, + }) + } + + /// Returns a reference to the DID identifying the document chain. + pub fn id(&self) -> &IotaDID { + self.auth_chain.current.id() + } + + /// Returns a reference to the `AuthChain`. + pub fn auth(&self) -> &AuthChain { + &self.auth_chain + } + + /// Returns a mutable reference to the `AuthChain`. + pub fn auth_mut(&mut self) -> &mut AuthChain { + &mut self.auth_chain + } + + /// Returns a reference to the `DiffChain`. + pub fn diff(&self) -> &DiffChain { + &self.diff_chain + } + + /// Returns a mutable reference to the `DiffChain`. + pub fn diff_mut(&mut self) -> &mut DiffChain { + &mut self.diff_chain + } + + pub fn fold(mut self) -> Result { + for diff in self.diff_chain.iter() { + self.auth_chain.current.merge(diff)?; + } + + Ok(self.auth_chain.current) + } + + /// Returns a reference to the latest document in the chain. + pub fn current(&self) -> &IotaDocument { + self.document.as_ref().unwrap_or_else(|| self.auth_chain.current()) + } + + /// Returns a mutable reference to the latest document in the chain. + pub fn current_mut(&mut self) -> &mut IotaDocument { + if let Some(document) = self.document.as_mut() { + document + } else { + self.auth_chain.current_mut() + } + } + + /// Returns the Tangle message Id of the latest auth document. + pub fn auth_message_id(&self) -> &MessageId { + self.auth_chain.current_message_id() + } + + /// Returns the Tangle message Id of the latest diff or auth document. + pub fn diff_message_id(&self) -> &MessageId { + Self::__diff_message_id(&self.auth_chain, &self.diff_chain) + } + + /// Adds a new auth document to the chain. + /// + /// # Errors + /// + /// Fails if the document is not a valid auth document. + pub fn try_push_auth(&mut self, document: IotaDocument) -> Result<()> { + self.auth_chain.try_push(document)?; + self.diff_chain.clear(); + + self.document = None; + + Ok(()) + } + + /// Adds a new diff to the current diff chain. + /// + /// # Errors + /// + /// Fails if the document diff is invalid. + pub fn try_push_diff(&mut self, diff: DocumentDiff) -> Result<()> { + self.diff_chain.check_validity(&self.auth_chain, &diff)?; + + let mut document: IotaDocument = self + .document + .take() + .unwrap_or_else(|| self.auth_chain.current().clone()); + + document.merge(&diff)?; + + self.document = Some(document); + + // SAFETY: we performed the necessary validation in `DiffChain::check_validity`. + unsafe { + self.diff_chain.push_unchecked(diff); + } + + Ok(()) + } } diff --git a/identity-iota/src/client/client.rs b/identity-iota/src/client/client.rs index 993f67a9ce..78ec7c7b36 100644 --- a/identity-iota/src/client/client.rs +++ b/identity-iota/src/client/client.rs @@ -4,233 +4,235 @@ use core::slice::from_ref; use identity_core::{common::Url, convert::ToJson}; use iota::{ - client::{FindTransactionsResponse, GetTrytesResponse, Transfer}, - transaction::bundled::{Address, BundledTransaction, BundledTransactionField as _}, + client::{FindTransactionsResponse, GetTrytesResponse, Transfer}, + transaction::bundled::{Address, BundledTransaction, BundledTransactionField as _}, }; use crate::{ - chain::{AuthChain, DiffChain, DocumentChain}, - client::{ClientBuilder, Network, TxnPrinter}, - did::{DocumentDiff, IotaDID, IotaDocument}, - error::{Error, Result}, - tangle::{Message, MessageId}, - utils::{bundles_from_trytes, create_address_from_trits, encode_trits, txn_hash_trytes}, + chain::{AuthChain, DiffChain, DocumentChain}, + client::{ClientBuilder, Network, TxnPrinter}, + did::{DocumentDiff, IotaDID, IotaDocument}, + error::{Error, Result}, + tangle::{Message, MessageId}, + utils::{bundles_from_trytes, create_address_from_trits, encode_trits, txn_hash_trytes}, }; #[derive(Clone, Debug)] pub struct Client { - pub(crate) client: iota::Client, - pub(crate) network: Network, + pub(crate) client: iota::Client, + pub(crate) network: Network, } impl Client { - /// Creates a new `Client` with default settings. - pub fn new() -> Result { - Self::from_builder(Self::builder()) - } + /// Creates a new `Client` with default settings. + pub fn new() -> Result { + Self::from_builder(Self::builder()) + } + + /// Creates a `ClientBuilder` to configure a new `Client`. + /// + /// This is the same as `ClientBuilder::new()`. + pub fn builder() -> ClientBuilder { + ClientBuilder::new() + } + + /// Creates a new `Client` with default settings for the given `Network`. + pub fn from_network(network: Network) -> Result { + Self::builder() + .node(network.node_url().as_str()) + .network(network) + .build() + } + + /// Creates a new `Client` based on the `ClientBuilder` configuration. + pub fn from_builder(builder: ClientBuilder) -> Result { + let mut client: iota::ClientBuilder = iota::ClientBuilder::new(); + + if builder.nodes.is_empty() { + client = client.node(builder.network.node_url().as_str())?; + } else { + for node in builder.nodes { + client = client.node(&node)?; + } + } + + client = client.network(builder.network.into()); + + Ok(Self { + client: client.build()?, + network: builder.network, + }) + } - /// Creates a `ClientBuilder` to configure a new `Client`. - /// - /// This is the same as `ClientBuilder::new()`. - pub fn builder() -> ClientBuilder { - ClientBuilder::new() - } + /// Returns the `Client` Tangle network. + pub fn network(&self) -> Network { + self.network + } - /// Creates a new `Client` with default settings for the given `Network`. - pub fn from_network(network: Network) -> Result { - Self::builder() - .node(network.node_url().as_str()) - .network(network) - .build() - } + /// Returns the default node URL of the `Client` network. + pub fn default_node_url(&self) -> &'static Url { + self.network.node_url() + } - /// Creates a new `Client` based on the `ClientBuilder` configuration. - pub fn from_builder(builder: ClientBuilder) -> Result { - let mut client: iota::ClientBuilder = iota::ClientBuilder::new(); + /// Returns the web explorer URL of the `Client` network. + pub fn explorer_url(&self) -> &'static Url { + self.network.explorer_url() + } - if builder.nodes.is_empty() { - client = client.node(builder.network.node_url().as_str())?; - } else { - for node in builder.nodes { - client = client.node(&node)?; - } - } + /// Returns the web explorer URL of the given `transaction`. + pub fn transaction_url(&self, transaction: &BundledTransaction) -> Url { + let hash: TxnPrinter<_> = TxnPrinter::hash(transaction); + let mut url: Url = self.network.explorer_url().clone(); - client = client.network(builder.network.into()); + url + .path_segments_mut() + .unwrap() + .push("transaction") + .push(&hash.to_string()); - Ok(Self { - client: client.build()?, - network: builder.network, - }) - } + url + } - /// Returns the `Client` Tangle network. - pub fn network(&self) -> Network { - self.network - } + /// Returns the hash of the Tangle transaction as a tryte-encoded `String`. + pub fn transaction_hash(&self, transaction: &BundledTransaction) -> String { + txn_hash_trytes(transaction) + } - /// Returns the default node URL of the `Client` network. - pub fn default_node_url(&self) -> &'static Url { - self.network.node_url() - } + /// Publishes an `IotaDocument` to the Tangle. + /// + /// Note: The only validation performed is to ensure the correct Tangle + /// network is selected. + pub async fn publish_document(&self, document: &IotaDocument) -> Result { + trace!("Publish Document: {}", document.id()); + trace!("Tangle Address: {}", document.id().address()); - /// Returns the web explorer URL of the `Client` network. - pub fn explorer_url(&self) -> &'static Url { - self.network.explorer_url() - } + self.check_network(document.id())?; - /// Returns the web explorer URL of the given `transaction`. - pub fn transaction_url(&self, transaction: &BundledTransaction) -> Url { - let hash: TxnPrinter<_> = TxnPrinter::hash(transaction); - let mut url: Url = self.network.explorer_url().clone(); + let address: String = document.id().address(); + let transfer: Transfer = create_transfer(&address, document)?; - url.path_segments_mut() - .unwrap() - .push("transaction") - .push(&hash.to_string()); + self.send_transfer(transfer).await + } - url - } - - /// Returns the hash of the Tangle transaction as a tryte-encoded `String`. - pub fn transaction_hash(&self, transaction: &BundledTransaction) -> String { - txn_hash_trytes(transaction) - } + /// Publishes a `DocumentDiff` to the Tangle. + /// + /// Note: The only validation performed is to ensure the correct Tangle + /// network is selected. + pub async fn publish_diff(&self, message_id: &MessageId, diff: &DocumentDiff) -> Result { + trace!("Publish Diff: {}", diff.id()); + trace!("Tangle Address: {}", IotaDocument::diff_address(message_id)?); - /// Publishes an `IotaDocument` to the Tangle. - /// - /// Note: The only validation performed is to ensure the correct Tangle - /// network is selected. - pub async fn publish_document(&self, document: &IotaDocument) -> Result { - trace!("Publish Document: {}", document.id()); - trace!("Tangle Address: {}", document.id().address()); + self.check_network(diff.id())?; - self.check_network(document.id())?; + let address: String = IotaDocument::diff_address(message_id)?; + let transfer: Transfer = create_transfer(&address, diff)?; - let address: String = document.id().address(); - let transfer: Transfer = create_transfer(&address, document)?; + self.send_transfer(transfer).await + } - self.send_transfer(transfer).await - } + pub async fn read_document(&self, did: &IotaDID) -> Result { + self.read_document_chain(did).await.and_then(DocumentChain::fold) + } - /// Publishes a `DocumentDiff` to the Tangle. - /// - /// Note: The only validation performed is to ensure the correct Tangle - /// network is selected. - pub async fn publish_diff(&self, message_id: &MessageId, diff: &DocumentDiff) -> Result { - trace!("Publish Diff: {}", diff.id()); - trace!("Tangle Address: {}", IotaDocument::diff_address(message_id)?); + pub async fn read_document_chain(&self, did: &IotaDID) -> Result { + trace!("Read Document Chain: {}", did); + trace!("Auth Chain Address: {}", did.address()); - self.check_network(diff.id())?; + // Fetch all messages for the auth chain. + let address: String = did.address(); + let messages: Vec = self.read_messages(&address).await?; - let address: String = IotaDocument::diff_address(message_id)?; - let transfer: Transfer = create_transfer(&address, diff)?; + let auth: AuthChain = AuthChain::try_from_messages(did, &messages)?; - self.send_transfer(transfer).await - } + let diff: DiffChain = if auth.current().immutable() { + DiffChain::new() + } else { + // Fetch all messages for the diff chain. + let address: String = IotaDocument::diff_address(auth.current_message_id())?; + let messages: Vec = self.read_messages(&address).await?; - pub async fn read_document(&self, did: &IotaDID) -> Result { - self.read_document_chain(did).await.and_then(DocumentChain::fold) - } + trace!("Tangle Messages: {:?}", messages); - pub async fn read_document_chain(&self, did: &IotaDID) -> Result { - trace!("Read Document Chain: {}", did); - trace!("Auth Chain Address: {}", did.address()); + DiffChain::try_from_messages(&auth, &messages)? + }; - // Fetch all messages for the auth chain. - let address: String = did.address(); - let messages: Vec = self.read_messages(&address).await?; + DocumentChain::with_diff_chain(auth, diff) + } - let auth: AuthChain = AuthChain::try_from_messages(did, &messages)?; + pub async fn read_messages(&self, address: &str) -> Result> { + let address: Address = create_address_from_trits(address)?; - let diff: DiffChain = if auth.current().immutable() { - DiffChain::new() - } else { - // Fetch all messages for the diff chain. - let address: String = IotaDocument::diff_address(auth.current_message_id())?; - let messages: Vec = self.read_messages(&address).await?; + trace!("Read Transactions: {}", encode_trits(address.to_inner())); - trace!("Tangle Messages: {:?}", messages); + // Fetch all transaction hashes containing the tangle address. + let response: FindTransactionsResponse = self + .client + .find_transactions() + .addresses(from_ref(&address)) + .send() + .await?; - DiffChain::try_from_messages(&auth, &messages)? - }; + trace!("Transactions Found: {:?}", __dbg_transactions(&response)); - DocumentChain::with_diff_chain(auth, diff) + if response.hashes.is_empty() { + return Ok(Vec::new()); } - pub async fn read_messages(&self, address: &str) -> Result> { - let address: Address = create_address_from_trits(address)?; - - trace!("Read Transactions: {}", encode_trits(address.to_inner())); - - // Fetch all transaction hashes containing the tangle address. - let response: FindTransactionsResponse = self - .client - .find_transactions() - .addresses(from_ref(&address)) - .send() - .await?; - - trace!("Transactions Found: {:?}", __dbg_transactions(&response)); + // Fetch the content of all transactions. + let content: GetTrytesResponse = self.client.get_trytes(&response.hashes).await?; - if response.hashes.is_empty() { - return Ok(Vec::new()); - } + trace!("Transaction Trytes: {:?}", __dbg_trytes(&content)); - // Fetch the content of all transactions. - let content: GetTrytesResponse = self.client.get_trytes(&response.hashes).await?; + if content.trytes.is_empty() { + return Err(Error::InvalidTransactionTrytes); + } - trace!("Transaction Trytes: {:?}", __dbg_trytes(&content)); + // Re-build the fragmented messages stored in the bundle. + bundles_from_trytes(content.trytes) + .into_iter() + .map(Message::try_from_bundle) + .collect() + } - if content.trytes.is_empty() { - return Err(Error::InvalidTransactionTrytes); - } + pub async fn send_transfer(&self, transfer: Transfer) -> Result { + trace!("Sending Transfer: {:?}", transfer.message); - // Re-build the fragmented messages stored in the bundle. - bundles_from_trytes(content.trytes) - .into_iter() - .map(Message::try_from_bundle) - .collect() - } + self + .client + .send(None) + .transfers(vec![transfer]) + .send() + .await? + .into_iter() + .find(BundledTransaction::is_tail) + .ok_or(Error::InvalidBundleTail) + } - pub async fn send_transfer(&self, transfer: Transfer) -> Result { - trace!("Sending Transfer: {:?}", transfer.message); - - self.client - .send(None) - .transfers(vec![transfer]) - .send() - .await? - .into_iter() - .find(BundledTransaction::is_tail) - .ok_or(Error::InvalidBundleTail) + pub fn check_network(&self, did: &IotaDID) -> Result<()> { + if !self.network.matches_did(did) { + return Err(Error::InvalidDIDNetwork); } - pub fn check_network(&self, did: &IotaDID) -> Result<()> { - if !self.network.matches_did(did) { - return Err(Error::InvalidDIDNetwork); - } - - Ok(()) - } + Ok(()) + } } fn create_transfer(address: &str, data: &T) -> Result where - T: ToJson, + T: ToJson, { - Ok(Transfer { - address: create_address_from_trits(address)?, - value: 0, - message: Some(data.to_json()?), - tag: None, - }) + Ok(Transfer { + address: create_address_from_trits(address)?, + value: 0, + message: Some(data.to_json()?), + tag: None, + }) } fn __dbg_transactions(response: &FindTransactionsResponse) -> Vec { - response.hashes.iter().map(|hash| encode_trits(hash)).collect() + response.hashes.iter().map(|hash| encode_trits(hash)).collect() } fn __dbg_trytes(response: &GetTrytesResponse) -> Vec { - response.trytes.iter().map(TxnPrinter::full).collect() + response.trytes.iter().map(TxnPrinter::full).collect() } diff --git a/identity-iota/src/client/client_builder.rs b/identity-iota/src/client/client_builder.rs index 6ce09e68ca..4f9aeb79e7 100644 --- a/identity-iota/src/client/client_builder.rs +++ b/identity-iota/src/client/client_builder.rs @@ -2,54 +2,54 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - client::{Client, Network}, - error::Result, + client::{Client, Network}, + error::Result, }; /// A `ClientBuilder` is used to generated a customized `Client`. #[derive(Clone, Debug)] pub struct ClientBuilder { - pub(crate) network: Network, - pub(crate) nodes: Vec, + pub(crate) network: Network, + pub(crate) nodes: Vec, } impl ClientBuilder { - /// Creates a new `ClientBuilder`. - pub const fn new() -> Self { - Self { - network: Network::Mainnet, - nodes: Vec::new(), - } - } - - /// Sets the network of the generated `Client`. - #[must_use] - pub fn network(mut self, network: Network) -> Self { - self.network = network; - self - } - - /// Adds an IOTA node to the generated `Client`. - #[must_use] - pub fn node(mut self, node: impl Into) -> Self { - self.nodes.push(node.into()); - self - } - - /// Adds an iterator of IOTA nodes to the generated `Client`. - pub fn nodes(mut self, nodes: impl IntoIterator>) -> Self { - self.nodes.extend(nodes.into_iter().map(Into::into)); - self - } - - /// Creates a new `Client` based on the `ClientBuilder` configuration. - pub fn build(self) -> Result { - Client::from_builder(self) + /// Creates a new `ClientBuilder`. + pub const fn new() -> Self { + Self { + network: Network::Mainnet, + nodes: Vec::new(), } + } + + /// Sets the network of the generated `Client`. + #[must_use] + pub fn network(mut self, network: Network) -> Self { + self.network = network; + self + } + + /// Adds an IOTA node to the generated `Client`. + #[must_use] + pub fn node(mut self, node: impl Into) -> Self { + self.nodes.push(node.into()); + self + } + + /// Adds an iterator of IOTA nodes to the generated `Client`. + pub fn nodes(mut self, nodes: impl IntoIterator>) -> Self { + self.nodes.extend(nodes.into_iter().map(Into::into)); + self + } + + /// Creates a new `Client` based on the `ClientBuilder` configuration. + pub fn build(self) -> Result { + Client::from_builder(self) + } } impl Default for ClientBuilder { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } diff --git a/identity-iota/src/client/network.rs b/identity-iota/src/client/network.rs index 0695153c60..fd960f72be 100644 --- a/identity-iota/src/client/network.rs +++ b/identity-iota/src/client/network.rs @@ -7,115 +7,115 @@ use iota::client::builder; use crate::did::IotaDID; lazy_static! { - static ref EXPLORER_MAIN: Url = Url::parse("https://explorer.iota.org/mainnet").unwrap(); - static ref EXPLORER_DEV: Url = Url::parse("https://explorer.iota.org/devnet").unwrap(); - static ref EXPLORER_COM: Url = Url::parse("https://comnet.thetangle.org").unwrap(); - static ref NODE_MAIN: Url = Url::parse("https://nodes.iota.org:443").unwrap(); - static ref NODE_DEV: Url = Url::parse("https://nodes.devnet.iota.org:443").unwrap(); - static ref NODE_COM: Url = Url::parse("https://nodes.comnet.thetangle.org:443").unwrap(); + static ref EXPLORER_MAIN: Url = Url::parse("https://explorer.iota.org/mainnet").unwrap(); + static ref EXPLORER_DEV: Url = Url::parse("https://explorer.iota.org/devnet").unwrap(); + static ref EXPLORER_COM: Url = Url::parse("https://comnet.thetangle.org").unwrap(); + static ref NODE_MAIN: Url = Url::parse("https://nodes.iota.org:443").unwrap(); + static ref NODE_DEV: Url = Url::parse("https://nodes.devnet.iota.org:443").unwrap(); + static ref NODE_COM: Url = Url::parse("https://nodes.comnet.thetangle.org:443").unwrap(); } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum Network { - Mainnet, - Devnet, - Comnet, + Mainnet, + Devnet, + Comnet, } impl Network { - pub fn from_name(string: &str) -> Self { - match string { - "dev" => Self::Devnet, - "com" => Self::Comnet, - _ => Self::Mainnet, - } + pub fn from_name(string: &str) -> Self { + match string { + "dev" => Self::Devnet, + "com" => Self::Comnet, + _ => Self::Mainnet, } - - pub fn matches_did(self, did: &IotaDID) -> bool { - did.network() == self.as_str() + } + + pub fn matches_did(self, did: &IotaDID) -> bool { + did.network() == self.as_str() + } + + /// Returns the default node URL of the Tangle network. + pub fn node_url(self) -> &'static Url { + match self { + Self::Mainnet => &*NODE_MAIN, + Self::Devnet => &*NODE_DEV, + Self::Comnet => &*NODE_COM, } - - /// Returns the default node URL of the Tangle network. - pub fn node_url(self) -> &'static Url { - match self { - Self::Mainnet => &*NODE_MAIN, - Self::Devnet => &*NODE_DEV, - Self::Comnet => &*NODE_COM, - } + } + + /// Returns the web explorer URL of the Tangle network. + pub fn explorer_url(self) -> &'static Url { + match self { + Self::Mainnet => &*EXPLORER_MAIN, + Self::Devnet => &*EXPLORER_DEV, + Self::Comnet => &*EXPLORER_COM, } - - /// Returns the web explorer URL of the Tangle network. - pub fn explorer_url(self) -> &'static Url { - match self { - Self::Mainnet => &*EXPLORER_MAIN, - Self::Devnet => &*EXPLORER_DEV, - Self::Comnet => &*EXPLORER_COM, - } - } - - /// Returns the name of the network as a static `str`. - pub const fn as_str(self) -> &'static str { - match self { - Self::Mainnet => "main", - Self::Devnet => "dev", - Self::Comnet => "com", - } + } + + /// Returns the name of the network as a static `str`. + pub const fn as_str(self) -> &'static str { + match self { + Self::Mainnet => "main", + Self::Devnet => "dev", + Self::Comnet => "com", } + } } impl Default for Network { - fn default() -> Self { - Network::Mainnet - } + fn default() -> Self { + Network::Mainnet + } } impl From for Network { - fn from(other: builder::Network) -> Network { - match other { - builder::Network::Mainnet => Self::Mainnet, - builder::Network::Devnet => Self::Devnet, - builder::Network::Comnet => Self::Comnet, - } + fn from(other: builder::Network) -> Network { + match other { + builder::Network::Mainnet => Self::Mainnet, + builder::Network::Devnet => Self::Devnet, + builder::Network::Comnet => Self::Comnet, } + } } impl From for builder::Network { - fn from(other: Network) -> builder::Network { - match other { - Network::Mainnet => Self::Mainnet, - Network::Devnet => Self::Devnet, - Network::Comnet => Self::Comnet, - } + fn from(other: Network) -> builder::Network { + match other { + Network::Mainnet => Self::Mainnet, + Network::Devnet => Self::Devnet, + Network::Comnet => Self::Comnet, } + } } #[cfg(test)] mod tests { - use super::*; - - #[test] - fn test_from_name() { - assert_eq!(Network::from_name("com"), Network::Comnet); - assert_eq!(Network::from_name("dev"), Network::Devnet); - assert_eq!(Network::from_name("main"), Network::Mainnet); - assert_eq!(Network::from_name("anything"), Network::Mainnet); - } - - #[test] - fn test_matches_did() { - let did: IotaDID = IotaDID::new(b"").unwrap(); - assert!(Network::matches_did(Network::Mainnet, &did)); - assert!(!Network::matches_did(Network::Comnet, &did)); - assert!(!Network::matches_did(Network::Devnet, &did)); - - let did: IotaDID = IotaDID::with_network(b"", "com").unwrap(); - assert!(Network::matches_did(Network::Comnet, &did)); - assert!(!Network::matches_did(Network::Mainnet, &did)); - assert!(!Network::matches_did(Network::Devnet, &did)); - - let did: IotaDID = IotaDID::with_network(b"", "dev").unwrap(); - assert!(Network::matches_did(Network::Devnet, &did)); - assert!(!Network::matches_did(Network::Mainnet, &did)); - assert!(!Network::matches_did(Network::Comnet, &did)); - } + use super::*; + + #[test] + fn test_from_name() { + assert_eq!(Network::from_name("com"), Network::Comnet); + assert_eq!(Network::from_name("dev"), Network::Devnet); + assert_eq!(Network::from_name("main"), Network::Mainnet); + assert_eq!(Network::from_name("anything"), Network::Mainnet); + } + + #[test] + fn test_matches_did() { + let did: IotaDID = IotaDID::new(b"").unwrap(); + assert!(Network::matches_did(Network::Mainnet, &did)); + assert!(!Network::matches_did(Network::Comnet, &did)); + assert!(!Network::matches_did(Network::Devnet, &did)); + + let did: IotaDID = IotaDID::with_network(b"", "com").unwrap(); + assert!(Network::matches_did(Network::Comnet, &did)); + assert!(!Network::matches_did(Network::Mainnet, &did)); + assert!(!Network::matches_did(Network::Devnet, &did)); + + let did: IotaDID = IotaDID::with_network(b"", "dev").unwrap(); + assert!(Network::matches_did(Network::Devnet, &did)); + assert!(!Network::matches_did(Network::Mainnet, &did)); + assert!(!Network::matches_did(Network::Comnet, &did)); + } } diff --git a/identity-iota/src/client/resolver.rs b/identity-iota/src/client/resolver.rs index f62cb9934a..a359767443 100644 --- a/identity-iota/src/client/resolver.rs +++ b/identity-iota/src/client/resolver.rs @@ -3,40 +3,40 @@ use async_trait::async_trait; use identity_core::{ - convert::SerdeInto as _, - did_url::DID, - error::{Error, Result}, - resolver::{DocumentMetadata, InputMetadata, MetaDocument, ResolverMethod}, + convert::SerdeInto as _, + did_url::DID, + error::{Error, Result}, + resolver::{DocumentMetadata, InputMetadata, MetaDocument, ResolverMethod}, }; use crate::{ - client::Client, - did::{IotaDID, IotaDocument}, + client::Client, + did::{IotaDID, IotaDocument}, }; #[async_trait(?Send)] impl ResolverMethod for Client { - fn is_supported(&self, did: &DID) -> bool { - IotaDID::try_from_borrowed(did) - .map(|did| self.check_network(did).is_ok()) - .unwrap_or(false) - } + fn is_supported(&self, did: &DID) -> bool { + IotaDID::try_from_borrowed(did) + .map(|did| self.check_network(did).is_ok()) + .unwrap_or(false) + } - async fn read(&self, did: &DID, _input: InputMetadata) -> Result> { - let did: &IotaDID = IotaDID::try_from_borrowed(did).map_err(err)?; - let document: IotaDocument = self.read_document(&did).await.map_err(err)?; + async fn read(&self, did: &DID, _input: InputMetadata) -> Result> { + let did: &IotaDID = IotaDID::try_from_borrowed(did).map_err(err)?; + let document: IotaDocument = self.read_document(&did).await.map_err(err)?; - let mut meta: DocumentMetadata = DocumentMetadata::new(); - meta.created = Some(document.created()); - meta.updated = Some(document.updated()); + let mut meta: DocumentMetadata = DocumentMetadata::new(); + meta.created = Some(document.created()); + meta.updated = Some(document.updated()); - Ok(Some(MetaDocument { - data: document.serde_into()?, - meta, - })) - } + Ok(Some(MetaDocument { + data: document.serde_into()?, + meta, + })) + } } fn err(error: crate::error::Error) -> Error { - Error::ResolutionError(error.into()) + Error::ResolutionError(error.into()) } diff --git a/identity-iota/src/client/txn_printer.rs b/identity-iota/src/client/txn_printer.rs index 323467322c..a15865eff0 100644 --- a/identity-iota/src/client/txn_printer.rs +++ b/identity-iota/src/client/txn_printer.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use core::{ - fmt::{Debug, Display, Formatter, Result}, - marker::PhantomData, + fmt::{Debug, Display, Formatter, Result}, + marker::PhantomData, }; use iota::transaction::bundled::{BundledTransaction, BundledTransactionField as _}; @@ -18,58 +18,58 @@ pub enum __Hash {} pub struct TxnPrinter<'a, T = __Full>(&'a BundledTransaction, PhantomData); impl<'a> TxnPrinter<'a, __Full> { - pub fn full(transaction: &'a BundledTransaction) -> Self { - Self(transaction, PhantomData) - } + pub fn full(transaction: &'a BundledTransaction) -> Self { + Self(transaction, PhantomData) + } } impl<'a> TxnPrinter<'a, __Mini> { - pub fn mini(transaction: &'a BundledTransaction) -> Self { - Self(transaction, PhantomData) - } + pub fn mini(transaction: &'a BundledTransaction) -> Self { + Self(transaction, PhantomData) + } } impl<'a> TxnPrinter<'a, __Hash> { - pub fn hash(transaction: &'a BundledTransaction) -> Self { - Self(transaction, PhantomData) - } + pub fn hash(transaction: &'a BundledTransaction) -> Self { + Self(transaction, PhantomData) + } } impl Debug for TxnPrinter<'_, __Full> { - fn fmt(&self, f: &mut Formatter) -> Result { - f.debug_struct("BundledTransaction") - .field("hash", &txn_hash_trytes(self.0)) - .field("address", &encode_trits(self.0.address().to_inner())) - .field("value", &self.0.value().to_inner()) - .field("index", &self.0.index().to_inner()) - .field("last_index", &self.0.last_index().to_inner()) - .field("bundle", &encode_trits(self.0.bundle())) - .field("tag", &encode_trits(self.0.tag().to_inner())) - .field("attachment_ts", &self.0.attachment_ts().to_inner()) - .field("attachment_lbts", &self.0.attachment_lbts().to_inner()) - .field("attachment_ubts", &self.0.attachment_ubts().to_inner()) - .field("nonce", &encode_trits(self.0.nonce().to_inner())) - .finish() - } + fn fmt(&self, f: &mut Formatter) -> Result { + f.debug_struct("BundledTransaction") + .field("hash", &txn_hash_trytes(self.0)) + .field("address", &encode_trits(self.0.address().to_inner())) + .field("value", &self.0.value().to_inner()) + .field("index", &self.0.index().to_inner()) + .field("last_index", &self.0.last_index().to_inner()) + .field("bundle", &encode_trits(self.0.bundle())) + .field("tag", &encode_trits(self.0.tag().to_inner())) + .field("attachment_ts", &self.0.attachment_ts().to_inner()) + .field("attachment_lbts", &self.0.attachment_lbts().to_inner()) + .field("attachment_ubts", &self.0.attachment_ubts().to_inner()) + .field("nonce", &encode_trits(self.0.nonce().to_inner())) + .finish() + } } impl Debug for TxnPrinter<'_, __Mini> { - fn fmt(&self, f: &mut Formatter) -> Result { - f.debug_struct("BundledTransaction") - .field("hash", &txn_hash_trytes(self.0)) - .field("address", &encode_trits(self.0.address().to_inner())) - .finish() - } + fn fmt(&self, f: &mut Formatter) -> Result { + f.debug_struct("BundledTransaction") + .field("hash", &txn_hash_trytes(self.0)) + .field("address", &encode_trits(self.0.address().to_inner())) + .finish() + } } impl Debug for TxnPrinter<'_, __Hash> { - fn fmt(&self, f: &mut Formatter) -> Result { - write!(f, "{}", txn_hash_trytes(self.0)) - } + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "{}", txn_hash_trytes(self.0)) + } } impl Display for TxnPrinter<'_, __Hash> { - fn fmt(&self, f: &mut Formatter) -> Result { - write!(f, "{}", txn_hash_trytes(self.0)) - } + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "{}", txn_hash_trytes(self.0)) + } } diff --git a/identity-iota/src/credential/validator.rs b/identity-iota/src/credential/validator.rs index 9b1b48518f..9d621ef41a 100644 --- a/identity-iota/src/credential/validator.rs +++ b/identity-iota/src/credential/validator.rs @@ -2,148 +2,149 @@ // SPDX-License-Identifier: Apache-2.0 use identity_core::{ - common::Object, - convert::FromJson as _, - credential::{VerifiableCredential, VerifiablePresentation}, - error::Error, + common::Object, + convert::FromJson as _, + credential::{VerifiableCredential, VerifiablePresentation}, + error::Error, }; use serde::{de::DeserializeOwned, Serialize}; use std::collections::BTreeMap; use crate::{ - client::Client, - did::{IotaDID, IotaDocument}, - error::Result, + client::Client, + did::{IotaDID, IotaDocument}, + error::Result, }; #[derive(Clone, Debug, PartialEq, Serialize)] pub struct CredentialValidation { - pub credential: VerifiableCredential, - pub issuer: DocumentValidation, - pub subjects: BTreeMap, - pub verified: bool, + pub credential: VerifiableCredential, + pub issuer: DocumentValidation, + pub subjects: BTreeMap, + pub verified: bool, } #[derive(Clone, Debug, PartialEq, Serialize)] pub struct PresentationValidation { - pub presentation: VerifiablePresentation, - pub holder: DocumentValidation, - pub credentials: Vec>, - pub verified: bool, + pub presentation: VerifiablePresentation, + pub holder: DocumentValidation, + pub credentials: Vec>, + pub verified: bool, } #[derive(Clone, Debug, PartialEq, Serialize)] pub struct DocumentValidation { - pub did: IotaDID, - pub document: IotaDocument, - pub metadata: Object, - pub verified: bool, + pub did: IotaDID, + pub document: IotaDocument, + pub metadata: Object, + pub verified: bool, } #[derive(Clone, Copy, Debug)] pub struct CredentialValidator<'a> { - client: &'a Client, + client: &'a Client, } impl<'a> CredentialValidator<'a> { - /// Creates a new `CredentialValidator`. - pub const fn new(client: &'a Client) -> Self { - Self { client } + /// Creates a new `CredentialValidator`. + pub const fn new(client: &'a Client) -> Self { + Self { client } + } + + /// Deserializes the given JSON-encoded `VerifiableCredential` and validates + /// all associated DID documents. + pub async fn check(&self, data: &str) -> Result> + where + T: DeserializeOwned + Serialize, + { + self.validate_credential(VerifiableCredential::from_json(data)?).await + } + + /// Deserializes the given JSON-encoded `VerifiablePresentation` and + /// validates all associated DID documents/`VerifiableCredential`s. + pub async fn check_presentation(&self, data: &str) -> Result> + where + T: Clone + DeserializeOwned + Serialize, + U: Clone + DeserializeOwned + Serialize, + { + self + .validate_presentation(VerifiablePresentation::from_json(data)?) + .await + } + + /// Validates the `VerifiableCredential` proof and all relevant DID documents. + /// + /// Note: The credential is expected to have a proof created by the issuing party. + /// Note: The credential issuer URL is expected to be a valid DID. + /// Note: Credential subject IDs are expected to be valid DIDs (if present). + pub async fn validate_credential(&self, credential: VerifiableCredential) -> Result> + where + T: Serialize, + { + let issuer: DocumentValidation = self.validate_document(credential.issuer.url().as_str()).await?; + let verified: bool = issuer.document.verify_data(&credential).is_ok(); + + let mut subjects: BTreeMap = BTreeMap::new(); + + for subject in credential.credential_subject.iter() { + if let Some(id) = subject.id.as_ref() { + subjects.insert(id.to_string(), self.validate_document(id.as_str()).await?); + } } - /// Deserializes the given JSON-encoded `VerifiableCredential` and validates - /// all associated DID documents. - pub async fn check(&self, data: &str) -> Result> - where - T: DeserializeOwned + Serialize, - { - self.validate_credential(VerifiableCredential::from_json(data)?).await + Ok(CredentialValidation { + credential, + issuer, + subjects, + verified, + }) + } + + /// Validates the `VerifiablePresentation` proof and all relevant DID documents. + /// + /// Note: The presentation holder is expected to be present and a valid DID. + /// Note: The presentation is expected to have a proof created by the holder. + pub async fn validate_presentation( + &self, + presentation: VerifiablePresentation, + ) -> Result> + where + T: Clone + Serialize, + U: Clone + Serialize, + { + let holder: &str = presentation + .holder + .as_ref() + .map(|holder| holder.as_str()) + .ok_or_else(|| Error::InvalidPresentation("Presentation missing `holder`".into()))?; + + let holder: DocumentValidation = self.validate_document(holder).await?; + let verified: bool = holder.document.verify_data(&presentation).is_ok(); + + let mut credentials: Vec> = Vec::new(); + + for credential in presentation.verifiable_credential.iter() { + credentials.push(self.validate_credential(credential.clone()).await?); } - /// Deserializes the given JSON-encoded `VerifiablePresentation` and - /// validates all associated DID documents/`VerifiableCredential`s. - pub async fn check_presentation(&self, data: &str) -> Result> - where - T: Clone + DeserializeOwned + Serialize, - U: Clone + DeserializeOwned + Serialize, - { - self.validate_presentation(VerifiablePresentation::from_json(data)?) - .await - } - - /// Validates the `VerifiableCredential` proof and all relevant DID documents. - /// - /// Note: The credential is expected to have a proof created by the issuing party. - /// Note: The credential issuer URL is expected to be a valid DID. - /// Note: Credential subject IDs are expected to be valid DIDs (if present). - pub async fn validate_credential(&self, credential: VerifiableCredential) -> Result> - where - T: Serialize, - { - let issuer: DocumentValidation = self.validate_document(credential.issuer.url().as_str()).await?; - let verified: bool = issuer.document.verify_data(&credential).is_ok(); - - let mut subjects: BTreeMap = BTreeMap::new(); - - for subject in credential.credential_subject.iter() { - if let Some(id) = subject.id.as_ref() { - subjects.insert(id.to_string(), self.validate_document(id.as_str()).await?); - } - } - - Ok(CredentialValidation { - credential, - issuer, - subjects, - verified, - }) - } - - /// Validates the `VerifiablePresentation` proof and all relevant DID documents. - /// - /// Note: The presentation holder is expected to be present and a valid DID. - /// Note: The presentation is expected to have a proof created by the holder. - pub async fn validate_presentation( - &self, - presentation: VerifiablePresentation, - ) -> Result> - where - T: Clone + Serialize, - U: Clone + Serialize, - { - let holder: &str = presentation - .holder - .as_ref() - .map(|holder| holder.as_str()) - .ok_or_else(|| Error::InvalidPresentation("Presentation missing `holder`".into()))?; - - let holder: DocumentValidation = self.validate_document(holder).await?; - let verified: bool = holder.document.verify_data(&presentation).is_ok(); - - let mut credentials: Vec> = Vec::new(); - - for credential in presentation.verifiable_credential.iter() { - credentials.push(self.validate_credential(credential.clone()).await?); - } - - Ok(PresentationValidation { - presentation, - holder, - credentials, - verified, - }) - } - - async fn validate_document(&self, did: &str) -> Result { - let did: IotaDID = did.parse()?; - let document: IotaDocument = self.client.read_document(&did).await?; - let verified: bool = document.verify().is_ok(); - - Ok(DocumentValidation { - did, - document, - metadata: Object::new(), - verified, - }) - } + Ok(PresentationValidation { + presentation, + holder, + credentials, + verified, + }) + } + + async fn validate_document(&self, did: &str) -> Result { + let did: IotaDID = did.parse()?; + let document: IotaDocument = self.client.read_document(&did).await?; + let verified: bool = document.verify().is_ok(); + + Ok(DocumentValidation { + did, + document, + metadata: Object::new(), + verified, + }) + } } diff --git a/identity-iota/src/did/did.rs b/identity-iota/src/did/did.rs index 040fedef23..b8d6aa1ba9 100644 --- a/identity-iota/src/did/did.rs +++ b/identity-iota/src/did/did.rs @@ -2,23 +2,23 @@ // SPDX-License-Identifier: Apache-2.0 use core::{ - convert::TryFrom, - fmt::{Debug, Display, Formatter, Result as FmtResult}, - ops::Deref, - str::FromStr, + convert::TryFrom, + fmt::{Debug, Display, Formatter, Result as FmtResult}, + ops::Deref, + str::FromStr, }; use identity_core::{ - crypto::KeyPair, - did_url::{Error as DIDError, DID}, - proof::JcsEd25519Signature2020, - utils::{decode_b58, encode_b58}, + crypto::KeyPair, + did_url::{Error as DIDError, DID}, + proof::JcsEd25519Signature2020, + utils::{decode_b58, encode_b58}, }; use multihash::Blake2b256; use crate::{ - did::Segments, - error::{Error, Result}, - utils::utf8_to_trytes, + did::Segments, + error::{Error, Result}, + utils::utf8_to_trytes, }; // The hash size of BLAKE2b-256 (32-bytes) @@ -30,421 +30,421 @@ const BLAKE2B_256_LEN: usize = 32; pub struct IotaDID(DID); impl IotaDID { - /// The DID method name. - pub const METHOD: &'static str = "iota"; - - /// The default Tangle network. - pub const DEFAULT_NETWORK: &'static str = "main"; - - /// Generates an `IotaDID` and `KeyPair` suitable for `ed25519` signatures. - pub fn generate_ed25519<'b, 'c, T, U>(network: T, shard: U) -> Result<(Self, KeyPair)> - where - T: Into>, - U: Into>, - { - let keypair: KeyPair = JcsEd25519Signature2020::new_keypair(); - let public: &[u8] = keypair.public().as_ref(); - - let did: Self = Self::with_network_and_shard(public, network, shard)?; - - Ok((did, keypair)) - } - - /// Converts a borrowed `DID` to an `IotaDID.` - /// - /// # Errors - /// - /// Returns `Err` if the input is not a valid `IotaDID`. - pub fn try_from_borrowed(did: &DID) -> Result<&Self> { - Self::check_validity(did)?; - - // SAFETY: we performed the necessary validation in `check_validity`. - Ok(unsafe { Self::new_unchecked_ref(did) }) - } - - /// Converts an owned `DID` to an `IotaDID.` - /// - /// # Errors - /// - /// Returns `Err` if the input is not a valid `IotaDID`. - pub fn try_from_owned(did: DID) -> Result { - Self::check_validity(&did)?; - - Ok(Self(Self::normalize(did))) - } - - /// Converts a `DID` reference to an `IotaDID` reference without performing - /// validation checks. - /// - /// # Safety - /// - /// This must be guaranteed safe by the caller. - pub unsafe fn new_unchecked_ref(did: &DID) -> &Self { - // SAFETY: This is guaranteed safe by the caller. - &*(did as *const DID as *const IotaDID) - } - - /// Parses an `IotaDID` from the given `input`. - /// - /// # Errors - /// - /// Returns `Err` if the input is not a valid `IotaDID`. - pub fn parse(input: impl AsRef) -> Result { - DID::parse(input).map_err(Into::into).and_then(Self::try_from_owned) - } - - /// Creates a new `IotaDID` with a tag derived from the given `public` key. - /// - /// # Errors - /// - /// Returns `Err` if the input does not form a valid `IotaDID`. - pub fn new(public: &[u8]) -> Result { - Self::with_network_and_shard(public, None, None) - } - - /// Creates a new `IotaDID` for the given `public` key and `network`. - /// - /// # Errors - /// - /// Returns `Err` if the input does not form a valid `IotaDID`. - pub fn with_network<'b, T>(public: &[u8], network: T) -> Result - where - T: Into>, - { - Self::with_network_and_shard(public, network, None) - } - - /// Creates a new `IotaDID` for the given `public` key, `network`, and `shard`. - /// - /// # Errors - /// - /// Returns `Err` if the input does not form a valid `IotaDID`. - pub fn with_network_and_shard<'b, 'c, T, U>(public: &[u8], network: T, shard: U) -> Result - where - T: Into>, - U: Into>, - { - let mut did: String = format!("{}:{}:", DID::SCHEME, Self::METHOD); - - if let Some(network) = network.into() { - did.push_str(network); - did.push(':'); - } - - if let Some(shard) = shard.into() { - did.push_str(shard); - did.push(':'); - } - - did.push_str(&Self::encode_key(public)); - - Self::parse(did) - } - - /// Creates a new [`IotaDID`] by joining `self` with the relative IotaDID `other`. - /// - /// # Errors - /// - /// Returns `Err` if any base or relative DID segments are invalid. - pub fn join(&self, other: impl AsRef) -> Result { - self.0.join(other).map_err(Into::into).and_then(Self::try_from_owned) - } - - /// Checks if the given `DID` has a valid `IotaDID` `method`. - /// - /// # Errors - /// - /// Returns `Err` if the input is not a valid `IotaDID`. - pub fn check_method(did: &DID) -> Result<()> { - if did.method() != Self::METHOD { - Err(Error::InvalidDID(DIDError::InvalidMethodName)) - } else { - Ok(()) - } - } - - /// Checks if the given `DID` has a valid `IotaDID` `method_id`. - /// - /// # Errors - /// - /// Returns `Err` if the input is not a valid `IotaDID`. - pub fn check_method_id(did: &DID) -> Result<()> { - let segments: Vec<&str> = did.method_id().split(':').collect(); - - if segments.is_empty() || segments.len() > 3 { - return Err(Error::InvalidDID(DIDError::InvalidMethodId)); - } - - // We checked if `id_segments` was empty so this should not panic - let mid: &str = segments.last().unwrap(); - let len: usize = decode_b58(mid)?.len(); - - if len == BLAKE2B_256_LEN { - Ok(()) - } else { - Err(Error::InvalidDID(DIDError::InvalidMethodId)) - } - } - - /// Checks if the given `DID` is a valid `IotaDID`. - /// - /// # Errors - /// - /// Returns `Err` if the input is not a valid `IotaDID`. - pub fn check_validity(did: &DID) -> Result<()> { - Self::check_method(did)?; - Self::check_method_id(did)?; - - Ok(()) - } - - /// Returns a `bool` indicating if the given `DID` is a valid `IotaDID`. - pub fn is_valid(did: &DID) -> bool { - Self::check_validity(did).is_ok() - } - - /// Returns the Tangle `network` of the `IotaDID`. - pub fn network(&self) -> &str { - self.segments().network() - } - - /// Returns the Tangle network `shard` of the `IotaDID`. - pub fn shard(&self) -> Option<&str> { - self.segments().shard() - } - - /// Returns the unique Tangle tag of the `IotaDID`. - pub fn tag(&self) -> &str { - self.segments().tag() - } - - pub fn segments(&self) -> Segments<'_> { - Segments(self.method_id()) - } - - /// Returns the Tangle address of the DID auth chain. - pub fn address(&self) -> String { - let mut trytes: String = utf8_to_trytes(self.tag()); - trytes.truncate(iota_constants::HASH_TRYTES_SIZE); - trytes - } - - pub(crate) fn normalize(mut did: DID) -> DID { - let segments: Segments = Segments(did.method_id()); - - if segments.count() == 2 && segments.network() == Self::DEFAULT_NETWORK { - let method_id: String = segments.tag().to_string(); - did.set_method_id(method_id); - } - - did - } - - pub(crate) fn encode_key(key: &[u8]) -> String { - encode_b58(Blake2b256::digest(key).digest()) - } + /// The DID method name. + pub const METHOD: &'static str = "iota"; + + /// The default Tangle network. + pub const DEFAULT_NETWORK: &'static str = "main"; + + /// Generates an `IotaDID` and `KeyPair` suitable for `ed25519` signatures. + pub fn generate_ed25519<'b, 'c, T, U>(network: T, shard: U) -> Result<(Self, KeyPair)> + where + T: Into>, + U: Into>, + { + let keypair: KeyPair = JcsEd25519Signature2020::new_keypair(); + let public: &[u8] = keypair.public().as_ref(); + + let did: Self = Self::with_network_and_shard(public, network, shard)?; + + Ok((did, keypair)) + } + + /// Converts a borrowed `DID` to an `IotaDID.` + /// + /// # Errors + /// + /// Returns `Err` if the input is not a valid `IotaDID`. + pub fn try_from_borrowed(did: &DID) -> Result<&Self> { + Self::check_validity(did)?; + + // SAFETY: we performed the necessary validation in `check_validity`. + Ok(unsafe { Self::new_unchecked_ref(did) }) + } + + /// Converts an owned `DID` to an `IotaDID.` + /// + /// # Errors + /// + /// Returns `Err` if the input is not a valid `IotaDID`. + pub fn try_from_owned(did: DID) -> Result { + Self::check_validity(&did)?; + + Ok(Self(Self::normalize(did))) + } + + /// Converts a `DID` reference to an `IotaDID` reference without performing + /// validation checks. + /// + /// # Safety + /// + /// This must be guaranteed safe by the caller. + pub unsafe fn new_unchecked_ref(did: &DID) -> &Self { + // SAFETY: This is guaranteed safe by the caller. + &*(did as *const DID as *const IotaDID) + } + + /// Parses an `IotaDID` from the given `input`. + /// + /// # Errors + /// + /// Returns `Err` if the input is not a valid `IotaDID`. + pub fn parse(input: impl AsRef) -> Result { + DID::parse(input).map_err(Into::into).and_then(Self::try_from_owned) + } + + /// Creates a new `IotaDID` with a tag derived from the given `public` key. + /// + /// # Errors + /// + /// Returns `Err` if the input does not form a valid `IotaDID`. + pub fn new(public: &[u8]) -> Result { + Self::with_network_and_shard(public, None, None) + } + + /// Creates a new `IotaDID` for the given `public` key and `network`. + /// + /// # Errors + /// + /// Returns `Err` if the input does not form a valid `IotaDID`. + pub fn with_network<'b, T>(public: &[u8], network: T) -> Result + where + T: Into>, + { + Self::with_network_and_shard(public, network, None) + } + + /// Creates a new `IotaDID` for the given `public` key, `network`, and `shard`. + /// + /// # Errors + /// + /// Returns `Err` if the input does not form a valid `IotaDID`. + pub fn with_network_and_shard<'b, 'c, T, U>(public: &[u8], network: T, shard: U) -> Result + where + T: Into>, + U: Into>, + { + let mut did: String = format!("{}:{}:", DID::SCHEME, Self::METHOD); + + if let Some(network) = network.into() { + did.push_str(network); + did.push(':'); + } + + if let Some(shard) = shard.into() { + did.push_str(shard); + did.push(':'); + } + + did.push_str(&Self::encode_key(public)); + + Self::parse(did) + } + + /// Creates a new [`IotaDID`] by joining `self` with the relative IotaDID `other`. + /// + /// # Errors + /// + /// Returns `Err` if any base or relative DID segments are invalid. + pub fn join(&self, other: impl AsRef) -> Result { + self.0.join(other).map_err(Into::into).and_then(Self::try_from_owned) + } + + /// Checks if the given `DID` has a valid `IotaDID` `method`. + /// + /// # Errors + /// + /// Returns `Err` if the input is not a valid `IotaDID`. + pub fn check_method(did: &DID) -> Result<()> { + if did.method() != Self::METHOD { + Err(Error::InvalidDID(DIDError::InvalidMethodName)) + } else { + Ok(()) + } + } + + /// Checks if the given `DID` has a valid `IotaDID` `method_id`. + /// + /// # Errors + /// + /// Returns `Err` if the input is not a valid `IotaDID`. + pub fn check_method_id(did: &DID) -> Result<()> { + let segments: Vec<&str> = did.method_id().split(':').collect(); + + if segments.is_empty() || segments.len() > 3 { + return Err(Error::InvalidDID(DIDError::InvalidMethodId)); + } + + // We checked if `id_segments` was empty so this should not panic + let mid: &str = segments.last().unwrap(); + let len: usize = decode_b58(mid)?.len(); + + if len == BLAKE2B_256_LEN { + Ok(()) + } else { + Err(Error::InvalidDID(DIDError::InvalidMethodId)) + } + } + + /// Checks if the given `DID` is a valid `IotaDID`. + /// + /// # Errors + /// + /// Returns `Err` if the input is not a valid `IotaDID`. + pub fn check_validity(did: &DID) -> Result<()> { + Self::check_method(did)?; + Self::check_method_id(did)?; + + Ok(()) + } + + /// Returns a `bool` indicating if the given `DID` is a valid `IotaDID`. + pub fn is_valid(did: &DID) -> bool { + Self::check_validity(did).is_ok() + } + + /// Returns the Tangle `network` of the `IotaDID`. + pub fn network(&self) -> &str { + self.segments().network() + } + + /// Returns the Tangle network `shard` of the `IotaDID`. + pub fn shard(&self) -> Option<&str> { + self.segments().shard() + } + + /// Returns the unique Tangle tag of the `IotaDID`. + pub fn tag(&self) -> &str { + self.segments().tag() + } + + pub fn segments(&self) -> Segments<'_> { + Segments(self.method_id()) + } + + /// Returns the Tangle address of the DID auth chain. + pub fn address(&self) -> String { + let mut trytes: String = utf8_to_trytes(self.tag()); + trytes.truncate(iota_constants::HASH_TRYTES_SIZE); + trytes + } + + pub(crate) fn normalize(mut did: DID) -> DID { + let segments: Segments = Segments(did.method_id()); + + if segments.count() == 2 && segments.network() == Self::DEFAULT_NETWORK { + let method_id: String = segments.tag().to_string(); + did.set_method_id(method_id); + } + + did + } + + pub(crate) fn encode_key(key: &[u8]) -> String { + encode_b58(Blake2b256::digest(key).digest()) + } } impl Display for IotaDID { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - write!(f, "{}", self.0) - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!(f, "{}", self.0) + } } impl Debug for IotaDID { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - write!(f, "{}", self.0) - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!(f, "{}", self.0) + } } impl Deref for IotaDID { - type Target = DID; + type Target = DID; - fn deref(&self) -> &Self::Target { - self.as_ref() - } + fn deref(&self) -> &Self::Target { + self.as_ref() + } } impl AsRef for IotaDID { - fn as_ref(&self) -> &DID { - &self.0 - } + fn as_ref(&self) -> &DID { + &self.0 + } } impl AsRef for IotaDID { - fn as_ref(&self) -> &str { - self.0.as_str() - } + fn as_ref(&self) -> &str { + self.0.as_str() + } } impl PartialEq for IotaDID { - fn eq(&self, other: &DID) -> bool { - self.0.eq(other) - } + fn eq(&self, other: &DID) -> bool { + self.0.eq(other) + } } impl From for DID { - fn from(other: IotaDID) -> Self { - other.0 - } + fn from(other: IotaDID) -> Self { + other.0 + } } impl TryFrom for IotaDID { - type Error = Error; + type Error = Error; - fn try_from(other: DID) -> Result { - Self::try_from_owned(other) - } + fn try_from(other: DID) -> Result { + Self::try_from_owned(other) + } } impl<'a> TryFrom<&'a DID> for &'a IotaDID { - type Error = Error; + type Error = Error; - fn try_from(other: &'a DID) -> Result { - IotaDID::try_from_borrowed(other) - } + fn try_from(other: &'a DID) -> Result { + IotaDID::try_from_borrowed(other) + } } impl FromStr for IotaDID { - type Err = Error; + type Err = Error; - fn from_str(string: &str) -> Result { - Self::parse(string) - } + fn from_str(string: &str) -> Result { + Self::parse(string) + } } #[cfg(test)] mod tests { - use super::*; - use identity_core::{crypto::KeyPair, did_url::DID, proof::JcsEd25519Signature2020}; - - const TAG: &str = "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"; - - const ADDR_TAG: &str = "HbuRS48djS5PbLQciy6iE9BTdaDTBM3GxcbGdyuv3TWo"; - const ADDR_TRYTES: &str = "RBQCIDACBCYABBSCYCBCZAZBQCVB9CRCXCMD9BXCOBCBLBCCSCPCNBCCLBWBXAQBLDRCQCQBSCMDIDJDX"; - - #[test] - fn test_parse_valid() { - assert!(IotaDID::parse(format!("did:iota:{}", TAG)).is_ok()); - assert!(IotaDID::parse(format!("did:iota:main:{}", TAG)).is_ok()); - assert!(IotaDID::parse(format!("did:iota:com:{}", TAG)).is_ok()); - assert!(IotaDID::parse(format!("did:iota:dev:{}", TAG)).is_ok()); - assert!(IotaDID::parse(format!("did:iota:rainbow:{}", TAG)).is_ok()); - assert!(IotaDID::parse(format!("did:iota:rainbow:shard-1:{}", TAG)).is_ok()); - } - - #[test] - fn test_parse_invalid() { - assert!(IotaDID::parse("did:foo::").is_err()); - assert!(IotaDID::parse("did:::").is_err()); - assert!(IotaDID::parse("did:iota---::").is_err()); - assert!(IotaDID::parse("did:iota:").is_err()); - } - - #[test] - fn test_from_did() { - let key: String = IotaDID::encode_key(b"123"); - - let did: DID = format!("did:iota:{}", key).parse().unwrap(); - assert!(IotaDID::try_from_owned(did).is_ok()); - - let did: DID = "did:iota:123".parse().unwrap(); - assert!(IotaDID::try_from_owned(did).is_err()); - - let did: DID = format!("did:web:{}", key).parse().unwrap(); - assert!(IotaDID::try_from_owned(did).is_err()); - } - - #[test] - fn test_network() { - let key: String = IotaDID::encode_key(b"123"); - - let did: IotaDID = format!("did:iota:dev:{}", key).parse().unwrap(); - assert_eq!(did.network(), "dev"); - - let did: IotaDID = format!("did:iota:{}", key).parse().unwrap(); - assert_eq!(did.network(), "main"); - - let did: IotaDID = format!("did:iota:rainbow:{}", key).parse().unwrap(); - assert_eq!(did.network(), "rainbow"); - } - - #[test] - fn test_shard() { - let key: String = IotaDID::encode_key(b"123"); - - let did: IotaDID = format!("did:iota:dev:{}", key).parse().unwrap(); - assert_eq!(did.shard(), None); - - let did: IotaDID = format!("did:iota:dev:shard:{}", key).parse().unwrap(); - assert_eq!(did.shard(), Some("shard")); - } - - #[test] - fn test_tag() { - let did: IotaDID = format!("did:iota:{}", TAG).parse().unwrap(); - assert_eq!(did.tag(), TAG); - - let did: IotaDID = format!("did:iota:main:{}", TAG).parse().unwrap(); - assert_eq!(did.tag(), TAG); - - let did: IotaDID = format!("did:iota:main:shard:{}", TAG).parse().unwrap(); - assert_eq!(did.tag(), TAG); - } - - #[test] - fn test_address() { - let did: IotaDID = format!("did:iota:com:{}", ADDR_TAG).parse().unwrap(); - assert_eq!(did.address(), ADDR_TRYTES); - } - - #[test] - fn test_new() { - let key: KeyPair = JcsEd25519Signature2020::new_keypair(); - let did: IotaDID = IotaDID::new(key.public().as_ref()).unwrap(); - let tag: String = IotaDID::encode_key(key.public().as_ref()); - - assert_eq!(did.tag(), tag); - assert_eq!(did.network(), IotaDID::DEFAULT_NETWORK); - assert_eq!(did.shard(), None); - } - - #[test] - fn test_with_network() { - let key: KeyPair = JcsEd25519Signature2020::new_keypair(); - let did: IotaDID = IotaDID::with_network(key.public().as_ref(), "foo").unwrap(); - let tag: String = IotaDID::encode_key(key.public().as_ref()); - - assert_eq!(did.tag(), tag); - assert_eq!(did.network(), "foo"); - assert_eq!(did.shard(), None); - } - - #[test] - fn test_with_network_and_shard() { - let key: KeyPair = JcsEd25519Signature2020::new_keypair(); - let did: IotaDID = IotaDID::with_network_and_shard(key.public().as_ref(), "foo", "shard-1").unwrap(); - let tag: String = IotaDID::encode_key(key.public().as_ref()); - - assert_eq!(did.tag(), tag); - assert_eq!(did.network(), "foo"); - assert_eq!(did.shard(), Some("shard-1")); - } - - #[test] - fn test_normalize() { - let key: KeyPair = JcsEd25519Signature2020::new_keypair(); - let tag: String = IotaDID::encode_key(key.public().as_ref()); - - // A DID with "main" as the network can be normalized ("main" removed) - let did1: IotaDID = format!("did:iota:{}", tag).parse().unwrap(); - let did2: IotaDID = format!("did:iota:main:{}", tag).parse().unwrap(); - assert_eq!(did1, did2); - - // A DID with a shard cannot be normalized - let did_str: String = format!("did:iota:main:shard:{}", tag); - let did: IotaDID = did_str.parse().unwrap(); - - assert_eq!(did.as_str(), did_str); - } + use super::*; + use identity_core::{crypto::KeyPair, did_url::DID, proof::JcsEd25519Signature2020}; + + const TAG: &str = "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"; + + const ADDR_TAG: &str = "HbuRS48djS5PbLQciy6iE9BTdaDTBM3GxcbGdyuv3TWo"; + const ADDR_TRYTES: &str = "RBQCIDACBCYABBSCYCBCZAZBQCVB9CRCXCMD9BXCOBCBLBCCSCPCNBCCLBWBXAQBLDRCQCQBSCMDIDJDX"; + + #[test] + fn test_parse_valid() { + assert!(IotaDID::parse(format!("did:iota:{}", TAG)).is_ok()); + assert!(IotaDID::parse(format!("did:iota:main:{}", TAG)).is_ok()); + assert!(IotaDID::parse(format!("did:iota:com:{}", TAG)).is_ok()); + assert!(IotaDID::parse(format!("did:iota:dev:{}", TAG)).is_ok()); + assert!(IotaDID::parse(format!("did:iota:rainbow:{}", TAG)).is_ok()); + assert!(IotaDID::parse(format!("did:iota:rainbow:shard-1:{}", TAG)).is_ok()); + } + + #[test] + fn test_parse_invalid() { + assert!(IotaDID::parse("did:foo::").is_err()); + assert!(IotaDID::parse("did:::").is_err()); + assert!(IotaDID::parse("did:iota---::").is_err()); + assert!(IotaDID::parse("did:iota:").is_err()); + } + + #[test] + fn test_from_did() { + let key: String = IotaDID::encode_key(b"123"); + + let did: DID = format!("did:iota:{}", key).parse().unwrap(); + assert!(IotaDID::try_from_owned(did).is_ok()); + + let did: DID = "did:iota:123".parse().unwrap(); + assert!(IotaDID::try_from_owned(did).is_err()); + + let did: DID = format!("did:web:{}", key).parse().unwrap(); + assert!(IotaDID::try_from_owned(did).is_err()); + } + + #[test] + fn test_network() { + let key: String = IotaDID::encode_key(b"123"); + + let did: IotaDID = format!("did:iota:dev:{}", key).parse().unwrap(); + assert_eq!(did.network(), "dev"); + + let did: IotaDID = format!("did:iota:{}", key).parse().unwrap(); + assert_eq!(did.network(), "main"); + + let did: IotaDID = format!("did:iota:rainbow:{}", key).parse().unwrap(); + assert_eq!(did.network(), "rainbow"); + } + + #[test] + fn test_shard() { + let key: String = IotaDID::encode_key(b"123"); + + let did: IotaDID = format!("did:iota:dev:{}", key).parse().unwrap(); + assert_eq!(did.shard(), None); + + let did: IotaDID = format!("did:iota:dev:shard:{}", key).parse().unwrap(); + assert_eq!(did.shard(), Some("shard")); + } + + #[test] + fn test_tag() { + let did: IotaDID = format!("did:iota:{}", TAG).parse().unwrap(); + assert_eq!(did.tag(), TAG); + + let did: IotaDID = format!("did:iota:main:{}", TAG).parse().unwrap(); + assert_eq!(did.tag(), TAG); + + let did: IotaDID = format!("did:iota:main:shard:{}", TAG).parse().unwrap(); + assert_eq!(did.tag(), TAG); + } + + #[test] + fn test_address() { + let did: IotaDID = format!("did:iota:com:{}", ADDR_TAG).parse().unwrap(); + assert_eq!(did.address(), ADDR_TRYTES); + } + + #[test] + fn test_new() { + let key: KeyPair = JcsEd25519Signature2020::new_keypair(); + let did: IotaDID = IotaDID::new(key.public().as_ref()).unwrap(); + let tag: String = IotaDID::encode_key(key.public().as_ref()); + + assert_eq!(did.tag(), tag); + assert_eq!(did.network(), IotaDID::DEFAULT_NETWORK); + assert_eq!(did.shard(), None); + } + + #[test] + fn test_with_network() { + let key: KeyPair = JcsEd25519Signature2020::new_keypair(); + let did: IotaDID = IotaDID::with_network(key.public().as_ref(), "foo").unwrap(); + let tag: String = IotaDID::encode_key(key.public().as_ref()); + + assert_eq!(did.tag(), tag); + assert_eq!(did.network(), "foo"); + assert_eq!(did.shard(), None); + } + + #[test] + fn test_with_network_and_shard() { + let key: KeyPair = JcsEd25519Signature2020::new_keypair(); + let did: IotaDID = IotaDID::with_network_and_shard(key.public().as_ref(), "foo", "shard-1").unwrap(); + let tag: String = IotaDID::encode_key(key.public().as_ref()); + + assert_eq!(did.tag(), tag); + assert_eq!(did.network(), "foo"); + assert_eq!(did.shard(), Some("shard-1")); + } + + #[test] + fn test_normalize() { + let key: KeyPair = JcsEd25519Signature2020::new_keypair(); + let tag: String = IotaDID::encode_key(key.public().as_ref()); + + // A DID with "main" as the network can be normalized ("main" removed) + let did1: IotaDID = format!("did:iota:{}", tag).parse().unwrap(); + let did2: IotaDID = format!("did:iota:main:{}", tag).parse().unwrap(); + assert_eq!(did1, did2); + + // A DID with a shard cannot be normalized + let did_str: String = format!("did:iota:main:shard:{}", tag); + let did: IotaDID = did_str.parse().unwrap(); + + assert_eq!(did.as_str(), did_str); + } } diff --git a/identity-iota/src/did/did_segments.rs b/identity-iota/src/did/did_segments.rs index 9f39c88bbb..442d6e909e 100644 --- a/identity-iota/src/did/did_segments.rs +++ b/identity-iota/src/did/did_segments.rs @@ -25,39 +25,39 @@ macro_rules! get { pub struct Segments<'a>(pub(crate) &'a str); impl<'a> Segments<'a> { - pub fn is_default_network(&self) -> bool { - match self.count() { - 1 => true, - 2 | 3 => get!(@network self) == IotaDID::DEFAULT_NETWORK, - _ => unreachable!("Segments::is_default_network called for invalid IOTA DID"), - } + pub fn is_default_network(&self) -> bool { + match self.count() { + 1 => true, + 2 | 3 => get!(@network self) == IotaDID::DEFAULT_NETWORK, + _ => unreachable!("Segments::is_default_network called for invalid IOTA DID"), } + } - pub fn network(&self) -> &'a str { - match self.count() { - 1 => IotaDID::DEFAULT_NETWORK, - 2 | 3 => get!(@network self), - _ => unreachable!("Segments::network called for invalid IOTA DID"), - } + pub fn network(&self) -> &'a str { + match self.count() { + 1 => IotaDID::DEFAULT_NETWORK, + 2 | 3 => get!(@network self), + _ => unreachable!("Segments::network called for invalid IOTA DID"), } + } - pub fn shard(&self) -> Option<&'a str> { - match self.count() { - 1 | 2 => None, - 3 => Some(get!(@shard self)), - _ => unreachable!("Segments::shard called for invalid IOTA DID"), - } + pub fn shard(&self) -> Option<&'a str> { + match self.count() { + 1 | 2 => None, + 3 => Some(get!(@shard self)), + _ => unreachable!("Segments::shard called for invalid IOTA DID"), } + } - pub fn tag(&self) -> &'a str { - match self.count() { - 1 => self.0, - 2 | 3 => get!(@tag self), - _ => unreachable!("Segments::tag called for invalid IOTA DID"), - } + pub fn tag(&self) -> &'a str { + match self.count() { + 1 => self.0, + 2 | 3 => get!(@tag self), + _ => unreachable!("Segments::tag called for invalid IOTA DID"), } + } - pub fn count(&self) -> usize { - self.0.split(':').count() - } + pub fn count(&self) -> usize { + self.0.split(':').count() + } } diff --git a/identity-iota/src/did/document.rs b/identity-iota/src/did/document.rs index a4950c17a3..4ea3d7cb12 100644 --- a/identity-iota/src/did/document.rs +++ b/identity-iota/src/did/document.rs @@ -2,29 +2,29 @@ // SPDX-License-Identifier: Apache-2.0 use core::{ - convert::TryFrom, - fmt::{Debug, Display, Formatter, Result as FmtResult}, - ops::Deref, + convert::TryFrom, + fmt::{Debug, Display, Formatter, Result as FmtResult}, + ops::Deref, }; use identity_core::{ - common::{Object, Timestamp, Value}, - convert::{FromJson as _, SerdeInto as _}, - crypto::{KeyPair, SecretKey}, - did_doc::{ - Document, LdSuite, MethodQuery, MethodScope, MethodType, MethodWrap, ResolveMethod, SetSignature, Signature, - SignatureOptions, TrySignature, TrySignatureMut, VerifiableDocument, - }, - did_url::DID, - proof::JcsEd25519Signature2020, + common::{Object, Timestamp, Value}, + convert::{FromJson as _, SerdeInto as _}, + crypto::{KeyPair, SecretKey}, + did_doc::{ + Document, LdSuite, MethodQuery, MethodScope, MethodType, MethodWrap, ResolveMethod, SetSignature, Signature, + SignatureOptions, TrySignature, TrySignatureMut, VerifiableDocument, + }, + did_url::DID, + proof::JcsEd25519Signature2020, }; use serde::Serialize; use crate::{ - client::{Client, Network}, - did::{DocumentDiff, IotaDID, IotaDocumentBuilder, Properties}, - error::{Error, Result}, - tangle::{MessageId, TangleRef}, - utils::utf8_to_trytes, + client::{Client, Network}, + did::{DocumentDiff, IotaDID, IotaDocumentBuilder, Properties}, + error::{Error, Result}, + tangle::{MessageId, TangleRef}, + utils::utf8_to_trytes, }; const AUTH_QUERY: (usize, MethodScope) = (0, MethodScope::Authentication); @@ -38,434 +38,434 @@ type __Document = VerifiableDocument; #[derive(Clone, PartialEq, Deserialize, Serialize)] #[serde(try_from = "Document", into = "__Document")] pub struct IotaDocument { - document: __Document, - message_id: MessageId, + document: __Document, + message_id: MessageId, } impl IotaDocument { - pub fn generate_ed25519<'a, 'b, T, U>(tag: &str, network: T, shard: U) -> Result<(Self, KeyPair)> - where - T: Into>, - U: Into>, - { - let mut builder: IotaDocumentBuilder = IotaDocumentBuilder::new() - .authentication_tag(tag) - .authentication_type(MethodType::Ed25519VerificationKey2018); - - if let Some(value) = network.into() { - builder = builder.did_network(value); - } - - if let Some(value) = shard.into() { - builder = builder.did_shard(value); - } - - builder.build() - } - - /// Converts a generic DID `Document` to an `IotaDocument`. - /// - /// # Errors - /// - /// Returns `Err` if the document is not a valid `IotaDocument`. - pub fn try_from_document(mut document: Document) -> Result { - let did: &IotaDID = IotaDID::try_from_borrowed(document.id())?; - let key: &DID = document.try_resolve(AUTH_QUERY)?.into_method().id(); - - // Ensure the authentication method has an identifying fragment - if key.fragment().is_none() { - return Err(Error::InvalidDocument { error: ERR_AMMF }); - } - - // Ensure the authentication method DID matches the document DID - if key.authority() != did.authority() { - return Err(Error::InvalidDocument { error: ERR_AMIM }); - } - - let proof: Option = document.properties_mut().remove("proof"); - let root: Document = document.try_map(|old| old.serde_into())?; - - if let Some(proof) = proof { - let proof: Signature = Signature::from_json_value(proof)?; - - Ok(Self { - document: VerifiableDocument::with_proof(root, proof), - message_id: MessageId::NONE, - }) - } else { - Ok(Self { - document: VerifiableDocument::new(root), - message_id: MessageId::NONE, - }) - } - } - - /// Creates a `IotaDocumentBuilder` to configure a new `IotaDocument`. - /// - /// This is the same as `IotaDocumentBuilder::new()`. - pub fn builder() -> IotaDocumentBuilder { - IotaDocumentBuilder::new() - } - - /// Returns the DID document `id`. - pub fn id(&self) -> &IotaDID { - // SAFETY: We checked the validity of the DID Document ID in the - // IotaDocument constructors; we don't provide mutable references so - // the value cannot change with typical "safe" Rust. - unsafe { IotaDID::new_unchecked_ref(self.document.id()) } - } - - /// Returns the default authentication method of the DID document. - pub fn authentication(&self) -> MethodWrap { - self.document.resolve(AUTH_QUERY).unwrap() - } - - /// Returns the key bytes of the default DID document authentication method. - pub fn authentication_bytes(&self) -> Result> { - self.document.try_resolve_bytes(AUTH_QUERY).map_err(Into::into) - } - - /// Returns the method type of the default DID document authentication method. - pub fn authentication_type(&self) -> MethodType { - self.authentication().key_type() - } - - /// Returns the timestamp of when the DID document was created. - pub fn created(&self) -> Timestamp { - self.document.properties().created - } - - /// Sets the timestamp of when the DID document was created. - pub fn set_created(&mut self, value: Timestamp) { - self.document.properties_mut().created = value; - } - - /// Sets the DID document "created" timestamp to `Timestamp::now`. - pub fn set_created_now(&mut self) { - self.set_created(Timestamp::now()); - } - - /// Returns the timestamp of the last DID document update. - pub fn updated(&self) -> Timestamp { - self.document.properties().updated - } - - /// Sets the timestamp of the last DID document update. - pub fn set_updated(&mut self, value: Timestamp) { - self.document.properties_mut().updated = value; - } - - /// Sets the DID document "updated" timestamp to `Timestamp::now`. - pub fn set_updated_now(&mut self) { - self.set_updated(Timestamp::now()); - } - - /// Returns the Tangle message id of the previous DID document, if any. - pub fn previous_message_id(&self) -> &MessageId { - &self.document.properties().previous_message_id - } - - /// Sets the Tangle message id the previous DID document. - pub fn set_previous_message_id(&mut self, value: T) - where - T: Into, - { - self.document.properties_mut().previous_message_id = value.into(); - } - - /// Returns true if the `IotaDocument` is flagged as immutable. - pub fn immutable(&self) -> bool { - self.document.properties().immutable - } - - /// Sets the value of the `immutable` flag. - pub fn set_immutable(&mut self, value: bool) { - self.document.properties_mut().immutable = value; - } - - /// Returns a reference to the custom `IotaDocument` properties. - pub fn properties(&self) -> &Object { - &self.document.properties().properties - } - - /// Returns a mutable reference to the custom `IotaDocument` properties. - pub fn properties_mut(&mut self) -> &mut Object { - &mut self.document.properties_mut().properties - } - - /// Returns a reference to the `VerifiableDocument`. - pub fn as_document(&self) -> &__Document { - &self.document - } - - /// Returns a mutable reference to the `VerifiableDocument`. - /// - /// # Safety - /// - /// This function is unsafe because it does not check that modifications - /// made to the `VerifiableDocument` maintain a valid `IotaDocument`. - /// - /// If this constraint is violated, it may cause issues with future uses of - /// the `IotaDocument`. - pub unsafe fn as_document_mut(&mut self) -> &mut __Document { - &mut self.document - } - - /// Signs the DID document with the default authentication method. - /// - /// # Errors - /// - /// Fails if an unsupported verification method is used, document - /// serialization fails, or the signature operation fails. - pub fn sign(&mut self, secret: &SecretKey) -> Result<()> { - match self.authentication_type() { - MethodType::Ed25519VerificationKey2018 => { - let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); - let options: SignatureOptions = self.resolve_options()?; - - suite.sign(&mut self.document, options, secret)?; - } - _ => { - return Err(Error::InvalidDocument { error: ERR_AMNS }); - } - } - - Ok(()) - } - - /// Verifies the signature of the DID document. - /// - /// Note: It is assumed that the signature was created using the default - /// authentication method. - /// - /// # Errors - /// - /// Fails if an unsupported verification method is used, document - /// serialization fails, or the verification operation fails. - pub fn verify(&self) -> Result<()> { - match self.authentication_type() { - MethodType::Ed25519VerificationKey2018 => { - LdSuite::new(JcsEd25519Signature2020).verify(&self.document)?; - } - _ => { - return Err(Error::InvalidDocument { error: ERR_AMNS }); - } - } - - Ok(()) - } - - /// Signs the provided data with the default authentication method. - /// - /// # Errors - /// - /// Fails if an unsupported verification method is used, document - /// serialization fails, or the signature operation fails. - pub fn sign_data(&self, data: &mut T, secret: &SecretKey) -> Result<()> - where - T: Serialize + SetSignature, - { - match self.authentication_type() { - MethodType::Ed25519VerificationKey2018 => { - let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); - let options: SignatureOptions = self.resolve_options()?; - - suite.sign(data, options, secret)?; - } - _ => { - return Err(Error::InvalidDocument { error: ERR_AMNS }); - } - } - - Ok(()) - } - - /// Verfies the signature of the provided data. - /// - /// Note: It is assumed that the signature was created using the default - /// authentication method. - /// - /// # Errors - /// - /// Fails if an unsupported verification method is used, document - /// serialization fails, or the verification operation fails. - pub fn verify_data(&self, data: &T) -> Result<()> - where - T: Serialize + TrySignature, - { - match self.authentication_type() { - MethodType::Ed25519VerificationKey2018 => { - LdSuite::new(JcsEd25519Signature2020).verify_data(data, &self.document)?; - } - _ => { - return Err(Error::InvalidDocument { error: ERR_AMNS }); - } - } - - Ok(()) - } - - pub fn resolve_options(&self) -> Result { - let mut options: SignatureOptions = self.document.resolve_options(AUTH_QUERY)?; - - options.created = Some(Timestamp::now().to_string()); - - Ok(options) - } - - /// Creates a `DocumentDiff` representing the changes between `self` and `other`. - /// - /// The returned `DocumentDiff` will have a digital signature created using the - /// default authentication method and `secret`. - /// - /// # Errors - /// - /// Fails if the diff operation or signature operation fails. - pub fn diff(&self, other: &Self, secret: &SecretKey, previous_message_id: MessageId) -> Result { - let mut diff: DocumentDiff = DocumentDiff::new(self, other, previous_message_id)?; - - self.sign_data(&mut diff, secret)?; - - Ok(diff) - } - - /// Verifies a `DocumentDiff` signature and merges the changes into `self`. - /// - /// If merging fails `self` remains unmodified, otherwise `self` represents - /// the merged document state. - /// - /// # Errors - /// - /// Fails if the merge operation or signature operation fails. - pub fn merge(&mut self, diff: &DocumentDiff) -> Result<()> { - self.verify_data(diff)?; - - *self = diff.merge(self)?; - - Ok(()) - } - - /// Publishes the `IotaDocument` to the Tangle using a default `Client`. - pub async fn publish(&mut self) -> Result<()> { - let network: Network = Network::from_name(self.id().network()); - let client: Client = Client::from_network(network)?; - - self.publish_with_client(&client).await - } - - /// Publishes the `IotaDocument` to the Tangle using the provided `Client`. - pub async fn publish_with_client(&mut self, client: &Client) -> Result<()> { - let transaction: _ = client.publish_document(self).await?; - let message_id: String = client.transaction_hash(&transaction); - - self.set_message_id(message_id.into()); - - Ok(()) - } - - /// Returns the Tangle address of the DID diff chain. - pub fn diff_address(message_id: &MessageId) -> Result { - if message_id.is_none() { - return Err(Error::InvalidDocument { - error: "Invalid Message Id", - }); - } - - let hash: String = IotaDID::encode_key(message_id.as_str().as_bytes()); - - let mut trytes: String = utf8_to_trytes(&hash); - trytes.truncate(iota_constants::HASH_TRYTES_SIZE); - Ok(trytes) - } + pub fn generate_ed25519<'a, 'b, T, U>(tag: &str, network: T, shard: U) -> Result<(Self, KeyPair)> + where + T: Into>, + U: Into>, + { + let mut builder: IotaDocumentBuilder = IotaDocumentBuilder::new() + .authentication_tag(tag) + .authentication_type(MethodType::Ed25519VerificationKey2018); + + if let Some(value) = network.into() { + builder = builder.did_network(value); + } + + if let Some(value) = shard.into() { + builder = builder.did_shard(value); + } + + builder.build() + } + + /// Converts a generic DID `Document` to an `IotaDocument`. + /// + /// # Errors + /// + /// Returns `Err` if the document is not a valid `IotaDocument`. + pub fn try_from_document(mut document: Document) -> Result { + let did: &IotaDID = IotaDID::try_from_borrowed(document.id())?; + let key: &DID = document.try_resolve(AUTH_QUERY)?.into_method().id(); + + // Ensure the authentication method has an identifying fragment + if key.fragment().is_none() { + return Err(Error::InvalidDocument { error: ERR_AMMF }); + } + + // Ensure the authentication method DID matches the document DID + if key.authority() != did.authority() { + return Err(Error::InvalidDocument { error: ERR_AMIM }); + } + + let proof: Option = document.properties_mut().remove("proof"); + let root: Document = document.try_map(|old| old.serde_into())?; + + if let Some(proof) = proof { + let proof: Signature = Signature::from_json_value(proof)?; + + Ok(Self { + document: VerifiableDocument::with_proof(root, proof), + message_id: MessageId::NONE, + }) + } else { + Ok(Self { + document: VerifiableDocument::new(root), + message_id: MessageId::NONE, + }) + } + } + + /// Creates a `IotaDocumentBuilder` to configure a new `IotaDocument`. + /// + /// This is the same as `IotaDocumentBuilder::new()`. + pub fn builder() -> IotaDocumentBuilder { + IotaDocumentBuilder::new() + } + + /// Returns the DID document `id`. + pub fn id(&self) -> &IotaDID { + // SAFETY: We checked the validity of the DID Document ID in the + // IotaDocument constructors; we don't provide mutable references so + // the value cannot change with typical "safe" Rust. + unsafe { IotaDID::new_unchecked_ref(self.document.id()) } + } + + /// Returns the default authentication method of the DID document. + pub fn authentication(&self) -> MethodWrap { + self.document.resolve(AUTH_QUERY).unwrap() + } + + /// Returns the key bytes of the default DID document authentication method. + pub fn authentication_bytes(&self) -> Result> { + self.document.try_resolve_bytes(AUTH_QUERY).map_err(Into::into) + } + + /// Returns the method type of the default DID document authentication method. + pub fn authentication_type(&self) -> MethodType { + self.authentication().key_type() + } + + /// Returns the timestamp of when the DID document was created. + pub fn created(&self) -> Timestamp { + self.document.properties().created + } + + /// Sets the timestamp of when the DID document was created. + pub fn set_created(&mut self, value: Timestamp) { + self.document.properties_mut().created = value; + } + + /// Sets the DID document "created" timestamp to `Timestamp::now`. + pub fn set_created_now(&mut self) { + self.set_created(Timestamp::now()); + } + + /// Returns the timestamp of the last DID document update. + pub fn updated(&self) -> Timestamp { + self.document.properties().updated + } + + /// Sets the timestamp of the last DID document update. + pub fn set_updated(&mut self, value: Timestamp) { + self.document.properties_mut().updated = value; + } + + /// Sets the DID document "updated" timestamp to `Timestamp::now`. + pub fn set_updated_now(&mut self) { + self.set_updated(Timestamp::now()); + } + + /// Returns the Tangle message id of the previous DID document, if any. + pub fn previous_message_id(&self) -> &MessageId { + &self.document.properties().previous_message_id + } + + /// Sets the Tangle message id the previous DID document. + pub fn set_previous_message_id(&mut self, value: T) + where + T: Into, + { + self.document.properties_mut().previous_message_id = value.into(); + } + + /// Returns true if the `IotaDocument` is flagged as immutable. + pub fn immutable(&self) -> bool { + self.document.properties().immutable + } + + /// Sets the value of the `immutable` flag. + pub fn set_immutable(&mut self, value: bool) { + self.document.properties_mut().immutable = value; + } + + /// Returns a reference to the custom `IotaDocument` properties. + pub fn properties(&self) -> &Object { + &self.document.properties().properties + } + + /// Returns a mutable reference to the custom `IotaDocument` properties. + pub fn properties_mut(&mut self) -> &mut Object { + &mut self.document.properties_mut().properties + } + + /// Returns a reference to the `VerifiableDocument`. + pub fn as_document(&self) -> &__Document { + &self.document + } + + /// Returns a mutable reference to the `VerifiableDocument`. + /// + /// # Safety + /// + /// This function is unsafe because it does not check that modifications + /// made to the `VerifiableDocument` maintain a valid `IotaDocument`. + /// + /// If this constraint is violated, it may cause issues with future uses of + /// the `IotaDocument`. + pub unsafe fn as_document_mut(&mut self) -> &mut __Document { + &mut self.document + } + + /// Signs the DID document with the default authentication method. + /// + /// # Errors + /// + /// Fails if an unsupported verification method is used, document + /// serialization fails, or the signature operation fails. + pub fn sign(&mut self, secret: &SecretKey) -> Result<()> { + match self.authentication_type() { + MethodType::Ed25519VerificationKey2018 => { + let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); + let options: SignatureOptions = self.resolve_options()?; + + suite.sign(&mut self.document, options, secret)?; + } + _ => { + return Err(Error::InvalidDocument { error: ERR_AMNS }); + } + } + + Ok(()) + } + + /// Verifies the signature of the DID document. + /// + /// Note: It is assumed that the signature was created using the default + /// authentication method. + /// + /// # Errors + /// + /// Fails if an unsupported verification method is used, document + /// serialization fails, or the verification operation fails. + pub fn verify(&self) -> Result<()> { + match self.authentication_type() { + MethodType::Ed25519VerificationKey2018 => { + LdSuite::new(JcsEd25519Signature2020).verify(&self.document)?; + } + _ => { + return Err(Error::InvalidDocument { error: ERR_AMNS }); + } + } + + Ok(()) + } + + /// Signs the provided data with the default authentication method. + /// + /// # Errors + /// + /// Fails if an unsupported verification method is used, document + /// serialization fails, or the signature operation fails. + pub fn sign_data(&self, data: &mut T, secret: &SecretKey) -> Result<()> + where + T: Serialize + SetSignature, + { + match self.authentication_type() { + MethodType::Ed25519VerificationKey2018 => { + let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); + let options: SignatureOptions = self.resolve_options()?; + + suite.sign(data, options, secret)?; + } + _ => { + return Err(Error::InvalidDocument { error: ERR_AMNS }); + } + } + + Ok(()) + } + + /// Verfies the signature of the provided data. + /// + /// Note: It is assumed that the signature was created using the default + /// authentication method. + /// + /// # Errors + /// + /// Fails if an unsupported verification method is used, document + /// serialization fails, or the verification operation fails. + pub fn verify_data(&self, data: &T) -> Result<()> + where + T: Serialize + TrySignature, + { + match self.authentication_type() { + MethodType::Ed25519VerificationKey2018 => { + LdSuite::new(JcsEd25519Signature2020).verify_data(data, &self.document)?; + } + _ => { + return Err(Error::InvalidDocument { error: ERR_AMNS }); + } + } + + Ok(()) + } + + pub fn resolve_options(&self) -> Result { + let mut options: SignatureOptions = self.document.resolve_options(AUTH_QUERY)?; + + options.created = Some(Timestamp::now().to_string()); + + Ok(options) + } + + /// Creates a `DocumentDiff` representing the changes between `self` and `other`. + /// + /// The returned `DocumentDiff` will have a digital signature created using the + /// default authentication method and `secret`. + /// + /// # Errors + /// + /// Fails if the diff operation or signature operation fails. + pub fn diff(&self, other: &Self, secret: &SecretKey, previous_message_id: MessageId) -> Result { + let mut diff: DocumentDiff = DocumentDiff::new(self, other, previous_message_id)?; + + self.sign_data(&mut diff, secret)?; + + Ok(diff) + } + + /// Verifies a `DocumentDiff` signature and merges the changes into `self`. + /// + /// If merging fails `self` remains unmodified, otherwise `self` represents + /// the merged document state. + /// + /// # Errors + /// + /// Fails if the merge operation or signature operation fails. + pub fn merge(&mut self, diff: &DocumentDiff) -> Result<()> { + self.verify_data(diff)?; + + *self = diff.merge(self)?; + + Ok(()) + } + + /// Publishes the `IotaDocument` to the Tangle using a default `Client`. + pub async fn publish(&mut self) -> Result<()> { + let network: Network = Network::from_name(self.id().network()); + let client: Client = Client::from_network(network)?; + + self.publish_with_client(&client).await + } + + /// Publishes the `IotaDocument` to the Tangle using the provided `Client`. + pub async fn publish_with_client(&mut self, client: &Client) -> Result<()> { + let transaction: _ = client.publish_document(self).await?; + let message_id: String = client.transaction_hash(&transaction); + + self.set_message_id(message_id.into()); + + Ok(()) + } + + /// Returns the Tangle address of the DID diff chain. + pub fn diff_address(message_id: &MessageId) -> Result { + if message_id.is_none() { + return Err(Error::InvalidDocument { + error: "Invalid Message Id", + }); + } + + let hash: String = IotaDID::encode_key(message_id.as_str().as_bytes()); + + let mut trytes: String = utf8_to_trytes(&hash); + trytes.truncate(iota_constants::HASH_TRYTES_SIZE); + Ok(trytes) + } } impl Display for IotaDocument { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - Display::fmt(&self.document, f) - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + Display::fmt(&self.document, f) + } } impl Debug for IotaDocument { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - Debug::fmt(&self.document, f) - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + Debug::fmt(&self.document, f) + } } impl Deref for IotaDocument { - type Target = __Document; + type Target = __Document; - fn deref(&self) -> &Self::Target { - &self.document - } + fn deref(&self) -> &Self::Target { + &self.document + } } impl PartialEq<__Document> for IotaDocument { - fn eq(&self, other: &__Document) -> bool { - self.document.eq(other) - } + fn eq(&self, other: &__Document) -> bool { + self.document.eq(other) + } } impl From<__Document> for IotaDocument { - fn from(other: __Document) -> Self { - Self { - document: other, - message_id: MessageId::NONE, - } + fn from(other: __Document) -> Self { + Self { + document: other, + message_id: MessageId::NONE, } + } } impl From for __Document { - fn from(other: IotaDocument) -> Self { - other.document - } + fn from(other: IotaDocument) -> Self { + other.document + } } impl TryFrom for IotaDocument { - type Error = Error; + type Error = Error; - fn try_from(other: Document) -> Result { - Self::try_from_document(other) - } + fn try_from(other: Document) -> Result { + Self::try_from_document(other) + } } impl TangleRef for IotaDocument { - fn message_id(&self) -> &MessageId { - &self.message_id - } + fn message_id(&self) -> &MessageId { + &self.message_id + } - fn set_message_id(&mut self, message_id: MessageId) { - self.message_id = message_id; - } + fn set_message_id(&mut self, message_id: MessageId) { + self.message_id = message_id; + } - fn previous_message_id(&self) -> &MessageId { - IotaDocument::previous_message_id(self) - } + fn previous_message_id(&self) -> &MessageId { + IotaDocument::previous_message_id(self) + } - fn set_previous_message_id(&mut self, message_id: MessageId) { - IotaDocument::set_previous_message_id(self, message_id) - } + fn set_previous_message_id(&mut self, message_id: MessageId) { + IotaDocument::set_previous_message_id(self, message_id) + } } impl TrySignature for IotaDocument { - fn signature(&self) -> Option<&Signature> { - self.document.proof() - } + fn signature(&self) -> Option<&Signature> { + self.document.proof() + } } impl TrySignatureMut for IotaDocument { - fn signature_mut(&mut self) -> Option<&mut Signature> { - self.document.proof_mut() - } + fn signature_mut(&mut self) -> Option<&mut Signature> { + self.document.proof_mut() + } } impl SetSignature for IotaDocument { - fn set_signature(&mut self, signature: Signature) { - self.document.set_proof(signature) - } + fn set_signature(&mut self, signature: Signature) { + self.document.set_proof(signature) + } } impl ResolveMethod for IotaDocument { - fn resolve_method(&self, query: MethodQuery<'_>) -> Option> { - self.document.resolve(query) - } + fn resolve_method(&self, query: MethodQuery<'_>) -> Option> { + self.document.resolve(query) + } } diff --git a/identity-iota/src/did/document_builder.rs b/identity-iota/src/did/document_builder.rs index 18184ccdbc..2a3691ca80 100644 --- a/identity-iota/src/did/document_builder.rs +++ b/identity-iota/src/did/document_builder.rs @@ -2,119 +2,119 @@ // SPDX-License-Identifier: Apache-2.0 use identity_core::{ - crypto::KeyPair, - did_doc::{DocumentBuilder, Method, MethodBuilder, MethodData, MethodType, VerifiableDocument}, - proof::JcsEd25519Signature2020, + crypto::KeyPair, + did_doc::{DocumentBuilder, Method, MethodBuilder, MethodData, MethodType, VerifiableDocument}, + proof::JcsEd25519Signature2020, }; use crate::{ - did::{IotaDID, IotaDocument, Properties}, - error::Result, + did::{IotaDID, IotaDocument, Properties}, + error::Result, }; #[derive(Clone, Debug)] pub struct IotaDocumentBuilder { - authentication_tag: String, - authentication_type: MethodType, - did_network: Option, - did_shard: Option, - immutable: bool, + authentication_tag: String, + authentication_type: MethodType, + did_network: Option, + did_shard: Option, + immutable: bool, } impl IotaDocumentBuilder { - pub fn new() -> Self { - Self { - authentication_tag: "authentication".into(), - authentication_type: MethodType::Ed25519VerificationKey2018, - did_network: None, - did_shard: None, - immutable: false, - } + pub fn new() -> Self { + Self { + authentication_tag: "authentication".into(), + authentication_type: MethodType::Ed25519VerificationKey2018, + did_network: None, + did_shard: None, + immutable: false, } - - #[must_use] - pub fn authentication_tag(mut self, value: T) -> Self - where - T: Into, - { - self.authentication_tag = value.into(); - self - } - - #[must_use] - pub fn authentication_type(mut self, value: MethodType) -> Self { - self.authentication_type = value; - self - } - - #[must_use] - pub fn did_network(mut self, value: T) -> Self - where - T: Into, - { - self.did_network = Some(value.into()); - self - } - - #[must_use] - pub fn did_shard(mut self, value: T) -> Self - where - T: Into, - { - self.did_shard = Some(value.into()); - self - } - - #[must_use] - pub fn immutable(mut self, value: bool) -> Self { - self.immutable = value; - self - } - - pub fn build(self) -> Result<(IotaDocument, KeyPair)> { - let keypair: KeyPair = Self::default_keypair(self.authentication_type)?; - let public: &[u8] = keypair.public().as_ref(); - - let network: Option<&str> = self.did_network.as_deref(); - let shard: Option<&str> = self.did_shard.as_deref(); - - let did: IotaDID = IotaDID::with_network_and_shard(public, network, shard)?; - let key: IotaDID = did.join(format!("#{}", self.authentication_tag))?; - - let method: Method = MethodBuilder::default() - .id(key.into()) - .controller(did.clone().into()) - .key_type(self.authentication_type) - .key_data(MethodData::new_b58(public)) - .build()?; - - let properties: Properties = Properties { - immutable: self.immutable, - ..Properties::new() - }; - - let document: IotaDocument = DocumentBuilder::new(properties) - .id(did.into()) - .authentication(method) - .build() - .map(VerifiableDocument::new) - .map(Into::into)?; - - Ok((document, keypair)) - } - - fn default_keypair(method: MethodType) -> Result { - match method { - MethodType::Ed25519VerificationKey2018 => Ok(JcsEd25519Signature2020::new_keypair()), - _ => { - todo!("Invalid Method Type") - } - } + } + + #[must_use] + pub fn authentication_tag(mut self, value: T) -> Self + where + T: Into, + { + self.authentication_tag = value.into(); + self + } + + #[must_use] + pub fn authentication_type(mut self, value: MethodType) -> Self { + self.authentication_type = value; + self + } + + #[must_use] + pub fn did_network(mut self, value: T) -> Self + where + T: Into, + { + self.did_network = Some(value.into()); + self + } + + #[must_use] + pub fn did_shard(mut self, value: T) -> Self + where + T: Into, + { + self.did_shard = Some(value.into()); + self + } + + #[must_use] + pub fn immutable(mut self, value: bool) -> Self { + self.immutable = value; + self + } + + pub fn build(self) -> Result<(IotaDocument, KeyPair)> { + let keypair: KeyPair = Self::default_keypair(self.authentication_type)?; + let public: &[u8] = keypair.public().as_ref(); + + let network: Option<&str> = self.did_network.as_deref(); + let shard: Option<&str> = self.did_shard.as_deref(); + + let did: IotaDID = IotaDID::with_network_and_shard(public, network, shard)?; + let key: IotaDID = did.join(format!("#{}", self.authentication_tag))?; + + let method: Method = MethodBuilder::default() + .id(key.into()) + .controller(did.clone().into()) + .key_type(self.authentication_type) + .key_data(MethodData::new_b58(public)) + .build()?; + + let properties: Properties = Properties { + immutable: self.immutable, + ..Properties::new() + }; + + let document: IotaDocument = DocumentBuilder::new(properties) + .id(did.into()) + .authentication(method) + .build() + .map(VerifiableDocument::new) + .map(Into::into)?; + + Ok((document, keypair)) + } + + fn default_keypair(method: MethodType) -> Result { + match method { + MethodType::Ed25519VerificationKey2018 => Ok(JcsEd25519Signature2020::new_keypair()), + _ => { + todo!("Invalid Method Type") + } } + } } impl Default for IotaDocumentBuilder { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } diff --git a/identity-iota/src/did/document_diff.rs b/identity-iota/src/did/document_diff.rs index fdeab166ca..9f4bb9e36e 100644 --- a/identity-iota/src/did/document_diff.rs +++ b/identity-iota/src/did/document_diff.rs @@ -2,124 +2,124 @@ // SPDX-License-Identifier: Apache-2.0 use identity_core::{ - convert::{AsJson as _, SerdeInto as _}, - did_doc::{Document, SetSignature, Signature, TrySignature, TrySignatureMut}, - identity_diff::{did_doc::DiffDocument, Diff}, + convert::{AsJson as _, SerdeInto as _}, + did_doc::{Document, SetSignature, Signature, TrySignature, TrySignatureMut}, + identity_diff::{did_doc::DiffDocument, Diff}, }; use crate::{ - client::{Client, Network}, - did::{IotaDID, IotaDocument}, - error::Result, - tangle::{MessageId, TangleRef}, + client::{Client, Network}, + did::{IotaDID, IotaDocument}, + error::Result, + tangle::{MessageId, TangleRef}, }; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct DocumentDiff { - pub(crate) did: IotaDID, - pub(crate) diff: String, - pub(crate) previous_message_id: MessageId, - pub(crate) proof: Option, - #[serde(skip)] - pub(crate) message_id: MessageId, + pub(crate) did: IotaDID, + pub(crate) diff: String, + pub(crate) previous_message_id: MessageId, + pub(crate) proof: Option, + #[serde(skip)] + pub(crate) message_id: MessageId, } impl DocumentDiff { - pub fn new(current: &IotaDocument, updated: &IotaDocument, previous_message_id: MessageId) -> Result { - let a: Document = current.serde_into()?; - let b: Document = updated.serde_into()?; - let diff: String = Diff::diff(&a, &b)?.to_json()?; - - Ok(Self { - did: current.id().clone(), - previous_message_id, - diff, - proof: None, - message_id: MessageId::NONE, - }) - } - - /// Returns the DID of associated document. - pub fn id(&self) -> &IotaDID { - &self.did - } - - /// Returns the raw contents of the DID document diff. - pub fn diff(&self) -> &str { - &*self.diff - } - - /// Returns the Tangle message id of the previous DID document diff. - pub fn previous_message_id(&self) -> &MessageId { - &self.previous_message_id - } - - /// Returns a reference to the `DocumentDiff` proof. - pub fn proof(&self) -> Option<&Signature> { - self.proof.as_ref() - } - - /// Publishes the `DocumentDiff` to the Tangle using a default `Client`. - pub async fn publish(&mut self, message_id: &MessageId) -> Result<()> { - let network: Network = Network::from_name(self.did.network()); - let client: Client = Client::from_network(network)?; - - self.publish_with_client(&client, message_id).await - } - - /// Publishes the `DocumentDiff` to the Tangle using the provided `Client`. - pub async fn publish_with_client(&mut self, client: &Client, message_id: &MessageId) -> Result<()> { - let transaction: _ = client.publish_diff(message_id, self).await?; - let message_id: String = client.transaction_hash(&transaction); - - self.set_message_id(message_id.into()); - - Ok(()) - } - - pub(crate) fn merge(&self, document: &IotaDocument) -> Result { - let data: DiffDocument = DiffDocument::from_json(&self.diff)?; - - document - .serde_into() - .and_then(|this: Document| Diff::merge(&this, data).map_err(Into::into)) - .and_then(|this: Document| this.serde_into()) - .map_err(Into::into) - } + pub fn new(current: &IotaDocument, updated: &IotaDocument, previous_message_id: MessageId) -> Result { + let a: Document = current.serde_into()?; + let b: Document = updated.serde_into()?; + let diff: String = Diff::diff(&a, &b)?.to_json()?; + + Ok(Self { + did: current.id().clone(), + previous_message_id, + diff, + proof: None, + message_id: MessageId::NONE, + }) + } + + /// Returns the DID of associated document. + pub fn id(&self) -> &IotaDID { + &self.did + } + + /// Returns the raw contents of the DID document diff. + pub fn diff(&self) -> &str { + &*self.diff + } + + /// Returns the Tangle message id of the previous DID document diff. + pub fn previous_message_id(&self) -> &MessageId { + &self.previous_message_id + } + + /// Returns a reference to the `DocumentDiff` proof. + pub fn proof(&self) -> Option<&Signature> { + self.proof.as_ref() + } + + /// Publishes the `DocumentDiff` to the Tangle using a default `Client`. + pub async fn publish(&mut self, message_id: &MessageId) -> Result<()> { + let network: Network = Network::from_name(self.did.network()); + let client: Client = Client::from_network(network)?; + + self.publish_with_client(&client, message_id).await + } + + /// Publishes the `DocumentDiff` to the Tangle using the provided `Client`. + pub async fn publish_with_client(&mut self, client: &Client, message_id: &MessageId) -> Result<()> { + let transaction: _ = client.publish_diff(message_id, self).await?; + let message_id: String = client.transaction_hash(&transaction); + + self.set_message_id(message_id.into()); + + Ok(()) + } + + pub(crate) fn merge(&self, document: &IotaDocument) -> Result { + let data: DiffDocument = DiffDocument::from_json(&self.diff)?; + + document + .serde_into() + .and_then(|this: Document| Diff::merge(&this, data).map_err(Into::into)) + .and_then(|this: Document| this.serde_into()) + .map_err(Into::into) + } } impl TangleRef for DocumentDiff { - fn message_id(&self) -> &MessageId { - &self.message_id - } + fn message_id(&self) -> &MessageId { + &self.message_id + } - fn set_message_id(&mut self, message_id: MessageId) { - self.message_id = message_id; - } + fn set_message_id(&mut self, message_id: MessageId) { + self.message_id = message_id; + } - fn previous_message_id(&self) -> &MessageId { - &self.previous_message_id - } + fn previous_message_id(&self) -> &MessageId { + &self.previous_message_id + } - fn set_previous_message_id(&mut self, message_id: MessageId) { - self.previous_message_id = message_id; - } + fn set_previous_message_id(&mut self, message_id: MessageId) { + self.previous_message_id = message_id; + } } impl TrySignature for DocumentDiff { - fn signature(&self) -> Option<&Signature> { - self.proof.as_ref() - } + fn signature(&self) -> Option<&Signature> { + self.proof.as_ref() + } } impl TrySignatureMut for DocumentDiff { - fn signature_mut(&mut self) -> Option<&mut Signature> { - self.proof.as_mut() - } + fn signature_mut(&mut self) -> Option<&mut Signature> { + self.proof.as_mut() + } } impl SetSignature for DocumentDiff { - fn set_signature(&mut self, value: Signature) { - self.proof = Some(value); - } + fn set_signature(&mut self, value: Signature) { + self.proof = Some(value); + } } diff --git a/identity-iota/src/did/document_properties.rs b/identity-iota/src/did/document_properties.rs index a99fc5b7af..54792a8e57 100644 --- a/identity-iota/src/did/document_properties.rs +++ b/identity-iota/src/did/document_properties.rs @@ -7,29 +7,29 @@ use crate::tangle::MessageId; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct Properties { - pub(crate) created: Timestamp, - pub(crate) updated: Timestamp, - pub(crate) immutable: bool, - #[serde(default, skip_serializing_if = "MessageId::is_none")] - pub(crate) previous_message_id: MessageId, - #[serde(flatten)] - pub(crate) properties: Object, + pub(crate) created: Timestamp, + pub(crate) updated: Timestamp, + pub(crate) immutable: bool, + #[serde(default, skip_serializing_if = "MessageId::is_none")] + pub(crate) previous_message_id: MessageId, + #[serde(flatten)] + pub(crate) properties: Object, } impl Properties { - pub fn new() -> Self { - Self { - created: Timestamp::now(), - updated: Timestamp::now(), - immutable: false, - previous_message_id: MessageId::NONE, - properties: Object::new(), - } + pub fn new() -> Self { + Self { + created: Timestamp::now(), + updated: Timestamp::now(), + immutable: false, + previous_message_id: MessageId::NONE, + properties: Object::new(), } + } } impl Default for Properties { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } diff --git a/identity-iota/src/error.rs b/identity-iota/src/error.rs index 92475830cc..6c33bb3190 100644 --- a/identity-iota/src/error.rs +++ b/identity-iota/src/error.rs @@ -5,32 +5,32 @@ pub type Result = core::result::Result; #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Core Error: {0}")] - CoreError(#[from] identity_core::Error), - #[error("Diff Error: {0}")] - DiffError(#[from] identity_core::identity_diff::Error), - #[error("Invalid DID: {0}")] - InvalidDID(#[from] identity_core::did_url::Error), - #[error("Invalid Document: {0}")] - InvalidDoc(#[from] identity_core::did_doc::Error), - #[error("Client Error: {0}")] - ClientError(#[from] iota::client::error::Error), - #[error("Ternary Error: {0}")] - TernaryError(#[from] iota::ternary::Error), - #[error("Invalid Document: {error}")] - InvalidDocument { error: &'static str }, - #[error("Invalid DID Network")] - InvalidDIDNetwork, - #[error("Invalid Tryte Conversion")] - InvalidTryteConversion, - #[error("Invalid Transaction Bundle")] - InvalidTransactionBundle, - #[error("Invalid Transaction Hashes")] - InvalidTransactionHashes, - #[error("Invalid Transaction Trytes")] - InvalidTransactionTrytes, - #[error("Invalid Bundle Tail")] - InvalidBundleTail, - #[error("Chain Error: {error}")] - ChainError { error: &'static str }, + #[error("Core Error: {0}")] + CoreError(#[from] identity_core::Error), + #[error("Diff Error: {0}")] + DiffError(#[from] identity_core::identity_diff::Error), + #[error("Invalid DID: {0}")] + InvalidDID(#[from] identity_core::did_url::Error), + #[error("Invalid Document: {0}")] + InvalidDoc(#[from] identity_core::did_doc::Error), + #[error("Client Error: {0}")] + ClientError(#[from] iota::client::error::Error), + #[error("Ternary Error: {0}")] + TernaryError(#[from] iota::ternary::Error), + #[error("Invalid Document: {error}")] + InvalidDocument { error: &'static str }, + #[error("Invalid DID Network")] + InvalidDIDNetwork, + #[error("Invalid Tryte Conversion")] + InvalidTryteConversion, + #[error("Invalid Transaction Bundle")] + InvalidTransactionBundle, + #[error("Invalid Transaction Hashes")] + InvalidTransactionHashes, + #[error("Invalid Transaction Trytes")] + InvalidTransactionTrytes, + #[error("Invalid Bundle Tail")] + InvalidBundleTail, + #[error("Chain Error: {error}")] + ChainError { error: &'static str }, } diff --git a/identity-iota/src/tangle/message.rs b/identity-iota/src/tangle/message.rs index ef9c1fc34d..3269b88ca7 100644 --- a/identity-iota/src/tangle/message.rs +++ b/identity-iota/src/tangle/message.rs @@ -4,92 +4,92 @@ use core::fmt::{Debug, Formatter, Result as FmtResult}; use identity_core::convert::FromJson as _; use iota::{ - crypto::ternary::Hash, - ternary::{T1B1Buf, TritBuf}, - transaction::bundled::{BundledTransaction, BundledTransactionField as _, Timestamp}, + crypto::ternary::Hash, + ternary::{T1B1Buf, TritBuf}, + transaction::bundled::{BundledTransaction, BundledTransactionField as _, Timestamp}, }; use crate::{ - did::{DocumentDiff, IotaDID, IotaDocument}, - error::{Error, Result}, - tangle::{MessageId, TangleRef}, - utils::{encode_trits, trytes_to_utf8, txn_hash}, + did::{DocumentDiff, IotaDID, IotaDocument}, + error::{Error, Result}, + tangle::{MessageId, TangleRef}, + utils::{encode_trits, trytes_to_utf8, txn_hash}, }; macro_rules! try_extract { - ($ty:ty, $this:expr, $did:expr) => {{ - let mut resource: $ty = $this - .message_utf8() - .ok() - .and_then(|json| <$ty>::from_json(&json).ok())?; - - if $did.authority() != resource.id().authority() { - return None; - } + ($ty:ty, $this:expr, $did:expr) => {{ + let mut resource: $ty = $this + .message_utf8() + .ok() + .and_then(|json| <$ty>::from_json(&json).ok())?; + + if $did.authority() != resource.id().authority() { + return None; + } - TangleRef::set_message_id(&mut resource, $this.message_id()); + TangleRef::set_message_id(&mut resource, $this.message_id()); - Some(resource) - }}; + Some(resource) + }}; } pub struct Message { - pub address: String, - pub message: TritBuf, - pub tail_hash: Hash, - pub timestamp: Timestamp, + pub address: String, + pub message: TritBuf, + pub tail_hash: Hash, + pub timestamp: Timestamp, } impl Message { - pub fn try_from_bundle(bundle: Vec) -> Result { - let message: TritBuf = bundle - .iter() - .flat_map(|transaction| transaction.payload().to_inner().iter()) - .collect(); - - let tail: &BundledTransaction = bundle - .iter() - .find(|transaction| transaction.is_tail()) - .ok_or(Error::InvalidBundleTail)?; - - Ok(Self { - message, - tail_hash: txn_hash(&tail), - timestamp: tail.timestamp().clone(), - address: encode_trits(tail.address().to_inner()), - }) - } - - /// Returns the contents of the message as a tryte-encoded string. - pub fn message_str(&self) -> String { - encode_trits(&self.message) - } - - /// Returns the contents of the message as a utf8-encoded string. - pub fn message_utf8(&self) -> Result { - trytes_to_utf8(&self.message_str()) - } - - /// Returns the `MessageId` identifying the Tangle message. - pub fn message_id(&self) -> MessageId { - MessageId::new(encode_trits(&self.tail_hash)) - } - - pub fn try_extract_document(&self, did: &IotaDID) -> Option { - try_extract!(IotaDocument, self, did) - } - - pub fn try_extract_diff(&self, did: &IotaDID) -> Option { - try_extract!(DocumentDiff, self, did) - } + pub fn try_from_bundle(bundle: Vec) -> Result { + let message: TritBuf = bundle + .iter() + .flat_map(|transaction| transaction.payload().to_inner().iter()) + .collect(); + + let tail: &BundledTransaction = bundle + .iter() + .find(|transaction| transaction.is_tail()) + .ok_or(Error::InvalidBundleTail)?; + + Ok(Self { + message, + tail_hash: txn_hash(&tail), + timestamp: tail.timestamp().clone(), + address: encode_trits(tail.address().to_inner()), + }) + } + + /// Returns the contents of the message as a tryte-encoded string. + pub fn message_str(&self) -> String { + encode_trits(&self.message) + } + + /// Returns the contents of the message as a utf8-encoded string. + pub fn message_utf8(&self) -> Result { + trytes_to_utf8(&self.message_str()) + } + + /// Returns the `MessageId` identifying the Tangle message. + pub fn message_id(&self) -> MessageId { + MessageId::new(encode_trits(&self.tail_hash)) + } + + pub fn try_extract_document(&self, did: &IotaDID) -> Option { + try_extract!(IotaDocument, self, did) + } + + pub fn try_extract_diff(&self, did: &IotaDID) -> Option { + try_extract!(DocumentDiff, self, did) + } } impl Debug for Message { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - f.debug_struct("Message") - .field("address", &self.address) - .field("message", &self.message_str()) - .field("timestamp", &self.timestamp) - .finish() - } + fn fmt(&self, f: &mut Formatter) -> FmtResult { + f.debug_struct("Message") + .field("address", &self.address) + .field("message", &self.message_str()) + .field("timestamp", &self.timestamp) + .finish() + } } diff --git a/identity-iota/src/tangle/message_id.rs b/identity-iota/src/tangle/message_id.rs index b31cfe110e..14839714d5 100644 --- a/identity-iota/src/tangle/message_id.rs +++ b/identity-iota/src/tangle/message_id.rs @@ -8,77 +8,77 @@ use core::fmt::{Debug, Formatter, Result}; pub struct MessageId(Option); impl MessageId { - pub const NONE: Self = Self(None); - - pub fn new(value: T) -> Self - where - T: Into, - { - let value: String = value.into(); - - if maybe_trytes(&value) { - Self(Some(value)) - } else { - Self(None) - } + pub const NONE: Self = Self(None); + + pub fn new(value: T) -> Self + where + T: Into, + { + let value: String = value.into(); + + if maybe_trytes(&value) { + Self(Some(value)) + } else { + Self(None) } + } - pub const fn is_none(&self) -> bool { - matches!(self, Self(None)) - } + pub const fn is_none(&self) -> bool { + matches!(self, Self(None)) + } - pub const fn is_some(&self) -> bool { - matches!(self, Self(Some(_))) - } + pub const fn is_some(&self) -> bool { + matches!(self, Self(Some(_))) + } - pub fn as_str(&self) -> &str { - self.0.as_deref().unwrap_or_default() - } + pub fn as_str(&self) -> &str { + self.0.as_deref().unwrap_or_default() + } } impl Debug for MessageId { - fn fmt(&self, f: &mut Formatter) -> Result { - f.write_str(self.0.as_deref().unwrap_or_default()) - } + fn fmt(&self, f: &mut Formatter) -> Result { + f.write_str(self.0.as_deref().unwrap_or_default()) + } } impl Default for MessageId { - fn default() -> Self { - Self::NONE - } + fn default() -> Self { + Self::NONE + } } impl From for MessageId { - fn from(other: String) -> Self { - Self::new(other) - } + fn from(other: String) -> Self { + Self::new(other) + } } impl PartialEq for MessageId where - T: AsRef, + T: AsRef, { - fn eq(&self, other: &T) -> bool { - match self.0.as_deref() { - Some(inner) => inner == other.as_ref(), - None => false, - } + fn eq(&self, other: &T) -> bool { + match self.0.as_deref() { + Some(inner) => inner == other.as_ref(), + None => false, } + } } fn maybe_trytes(input: &str) -> bool { - if input.len() != iota_constants::HASH_TRYTES_SIZE { - return false; - } + if input.len() != iota_constants::HASH_TRYTES_SIZE { + return false; + } - input.chars().all(|ch| iota_constants::TRYTE_ALPHABET.contains(&ch)) + input.chars().all(|ch| iota_constants::TRYTE_ALPHABET.contains(&ch)) } #[cfg(test)] mod tests { - use super::*; + use super::*; - #[test] + #[test] #[rustfmt::skip] fn test_new() { // Valid diff --git a/identity-iota/src/tangle/message_index.rs b/identity-iota/src/tangle/message_index.rs index decd057b8c..868bb37bed 100644 --- a/identity-iota/src/tangle/message_index.rs +++ b/identity-iota/src/tangle/message_index.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use core::{ - borrow::Borrow, - iter::FromIterator, - ops::{Deref, DerefMut}, + borrow::Borrow, + iter::FromIterator, + ops::{Deref, DerefMut}, }; use std::collections::BTreeMap; @@ -14,135 +14,135 @@ type __Index = BTreeMap>; #[derive(Clone, Debug)] pub struct MessageIndex { - inner: __Index, + inner: __Index, } impl MessageIndex { - /// Creates a new `MessageIndex`. - pub fn new() -> Self { - Self { inner: BTreeMap::new() } - } - - /// Returns the total size of the index. - pub fn size(&self) -> usize { - self.inner.values().map(Vec::len).sum() - } - - pub fn remove_where(&mut self, key: &U, f: impl Fn(&T) -> bool) -> Option - where - MessageId: Borrow, - U: Ord + ?Sized, - { - if let Some(list) = self.inner.get_mut(key) { - list.iter().position(f).map(|index| list.remove(index)) - } else { - None - } + /// Creates a new `MessageIndex`. + pub fn new() -> Self { + Self { inner: BTreeMap::new() } + } + + /// Returns the total size of the index. + pub fn size(&self) -> usize { + self.inner.values().map(Vec::len).sum() + } + + pub fn remove_where(&mut self, key: &U, f: impl Fn(&T) -> bool) -> Option + where + MessageId: Borrow, + U: Ord + ?Sized, + { + if let Some(list) = self.inner.get_mut(key) { + list.iter().position(f).map(|index| list.remove(index)) + } else { + None } + } } impl MessageIndex where - T: TangleRef, + T: TangleRef, { - pub fn insert(&mut self, element: T) { - let key: &MessageId = element.previous_message_id(); - - if let Some(scope) = self.inner.get_mut(key) { - scope.insert(0, element); - } else { - self.inner.insert(key.clone(), vec![element]); - } - } + pub fn insert(&mut self, element: T) { + let key: &MessageId = element.previous_message_id(); - pub fn extend(&mut self, iter: I) - where - I: IntoIterator, - { - for element in iter.into_iter() { - self.insert(element); - } + if let Some(scope) = self.inner.get_mut(key) { + scope.insert(0, element); + } else { + self.inner.insert(key.clone(), vec![element]); } + } + + pub fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + for element in iter.into_iter() { + self.insert(element); + } + } } impl Default for MessageIndex { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } impl Deref for MessageIndex { - type Target = __Index; + type Target = __Index; - fn deref(&self) -> &Self::Target { - &self.inner - } + fn deref(&self) -> &Self::Target { + &self.inner + } } impl DerefMut for MessageIndex { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } impl FromIterator for MessageIndex where - T: TangleRef, + T: TangleRef, { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let mut this: Self = Self::new(); - this.extend(iter); - this - } + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let mut this: Self = Self::new(); + this.extend(iter); + this + } } #[cfg(test)] mod tests { - use super::*; + use super::*; - #[derive(Debug)] - struct Case { - message_id: MessageId, - previous_message_id: MessageId, - state: bool, - } + #[derive(Debug)] + struct Case { + message_id: MessageId, + previous_message_id: MessageId, + state: bool, + } - impl Case { - fn new(message_id: T, previous_message_id: U, state: bool) -> Self - where - T: Into, - U: Into, - { - Self { - message_id: MessageId::new(message_id.into()), - previous_message_id: MessageId::new(previous_message_id.into()), - state, - } - } + impl Case { + fn new(message_id: T, previous_message_id: U, state: bool) -> Self + where + T: Into, + U: Into, + { + Self { + message_id: MessageId::new(message_id.into()), + previous_message_id: MessageId::new(previous_message_id.into()), + state, + } } + } - impl TangleRef for Case { - fn message_id(&self) -> &MessageId { - &self.message_id - } + impl TangleRef for Case { + fn message_id(&self) -> &MessageId { + &self.message_id + } - fn set_message_id(&mut self, message_id: MessageId) { - self.message_id = message_id; - } + fn set_message_id(&mut self, message_id: MessageId) { + self.message_id = message_id; + } - fn previous_message_id(&self) -> &MessageId { - &self.previous_message_id - } + fn previous_message_id(&self) -> &MessageId { + &self.previous_message_id + } - fn set_previous_message_id(&mut self, message_id: MessageId) { - self.previous_message_id = message_id; - } + fn set_previous_message_id(&mut self, message_id: MessageId) { + self.previous_message_id = message_id; } + } - #[rustfmt::skip] + #[rustfmt::skip] fn setup() -> MessageIndex { let cases: Vec = vec![ Case::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999A", "", true), @@ -158,7 +158,7 @@ mod tests { index } - #[test] + #[test] #[rustfmt::skip] fn test_works() { let index: MessageIndex = setup(); @@ -170,7 +170,7 @@ mod tests { assert_eq!(index[&MessageId::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999C")].len(), 1); } - #[test] + #[test] #[rustfmt::skip] fn test_remove_where() { let mut index: MessageIndex = setup(); diff --git a/identity-iota/src/tangle/traits.rs b/identity-iota/src/tangle/traits.rs index 97453956fe..978d2e0c53 100644 --- a/identity-iota/src/tangle/traits.rs +++ b/identity-iota/src/tangle/traits.rs @@ -4,11 +4,11 @@ use crate::tangle::MessageId; pub trait TangleRef { - fn message_id(&self) -> &MessageId; + fn message_id(&self) -> &MessageId; - fn set_message_id(&mut self, message_id: MessageId); + fn set_message_id(&mut self, message_id: MessageId); - fn previous_message_id(&self) -> &MessageId; + fn previous_message_id(&self) -> &MessageId; - fn set_previous_message_id(&mut self, message_id: MessageId); + fn set_previous_message_id(&mut self, message_id: MessageId); } diff --git a/identity-iota/src/utils.rs b/identity-iota/src/utils.rs index 0ad362a830..cb555a30dc 100644 --- a/identity-iota/src/utils.rs +++ b/identity-iota/src/utils.rs @@ -3,96 +3,96 @@ use core::{cmp::Ordering, iter::once}; use iota::{ - crypto::ternary::{ - sponge::{CurlP81, Sponge as _}, - Hash, - }, - ternary::{raw::RawEncoding, Btrit, T1B1Buf, TritBuf, Trits, TryteBuf, T1B1}, - transaction::bundled::{Address, BundledTransaction, BundledTransactionField as _}, + crypto::ternary::{ + sponge::{CurlP81, Sponge as _}, + Hash, + }, + ternary::{raw::RawEncoding, Btrit, T1B1Buf, TritBuf, Trits, TryteBuf, T1B1}, + transaction::bundled::{Address, BundledTransaction, BundledTransactionField as _}, }; use iota_conversion::trytes_converter; use crate::error::{Error, Result}; pub fn txn_hash(txn: &BundledTransaction) -> Hash { - let mut curl: CurlP81 = CurlP81::new(); - let mut tbuf: TritBuf = TritBuf::zeros(BundledTransaction::trit_len()); + let mut curl: CurlP81 = CurlP81::new(); + let mut tbuf: TritBuf = TritBuf::zeros(BundledTransaction::trit_len()); - txn.into_trits_allocated(&mut tbuf); + txn.into_trits_allocated(&mut tbuf); - Hash::from_inner_unchecked(curl.digest(&tbuf).expect("infallible")) + Hash::from_inner_unchecked(curl.digest(&tbuf).expect("infallible")) } pub fn txn_hash_trytes(txn: &BundledTransaction) -> String { - encode_trits(txn_hash(txn).as_trits()) + encode_trits(txn_hash(txn).as_trits()) } pub fn encode_trits(trits: &Trits) -> String where - T: RawEncoding, + T: RawEncoding, { - trits.iter_trytes().map(char::from).collect() + trits.iter_trytes().map(char::from).collect() } pub fn create_address_from_trits(trits: impl AsRef) -> Result
{ - TryteBuf::try_from_str(trits.as_ref()) - .map_err(Into::into) - .map(|trytes| trytes.as_trits().encode::()) - .map(Address::from_inner_unchecked) + TryteBuf::try_from_str(trits.as_ref()) + .map_err(Into::into) + .map(|trytes| trytes.as_trits().encode::()) + .map(Address::from_inner_unchecked) } pub fn to_tryte(byte: u8) -> impl IntoIterator { - once(iota_constants::TRYTE_ALPHABET[(byte % 27) as usize]) - .chain(once(iota_constants::TRYTE_ALPHABET[(byte / 27) as usize])) + once(iota_constants::TRYTE_ALPHABET[(byte % 27) as usize]) + .chain(once(iota_constants::TRYTE_ALPHABET[(byte / 27) as usize])) } pub fn utf8_to_trytes(input: impl AsRef<[u8]>) -> String { - input.as_ref().iter().copied().flat_map(to_tryte).collect() + input.as_ref().iter().copied().flat_map(to_tryte).collect() } pub fn trytes_to_utf8(string: impl AsRef) -> Result { - trytes_converter::to_string(string.as_ref()).map_err(|_| Error::InvalidTryteConversion) + trytes_converter::to_string(string.as_ref()).map_err(|_| Error::InvalidTryteConversion) } pub fn bundles_from_trytes(mut transactions: Vec) -> Vec> { - transactions.sort_by(|a, b| { - // TODO: impl Ord for Address, Tag, Hash - cmp_trits(a.address().to_inner(), b.address().to_inner()) - .then(cmp_trits(a.tag().to_inner(), b.tag().to_inner())) - // different messages may have the same bundle hash! - .then(cmp_trits(a.bundle().to_inner(), b.bundle().to_inner())) - // reverse order of transactions will be extracted from back with `pop` - .then(a.index().to_inner().cmp(b.index().to_inner()).reverse()) - }); - - let mut bundles: Vec> = Vec::new(); - - if let Some(root) = transactions.pop() { - let mut bundle: Vec = vec![root]; - - loop { - if let Some(transaction) = transactions.pop() { - if cmp_transaction(&bundle[0], &transaction) { - bundle.push(transaction); - } else { - bundles.push(bundle); - bundle = vec![transaction]; - } - } else { - bundles.push(bundle); - break; - } + transactions.sort_by(|a, b| { + // TODO: impl Ord for Address, Tag, Hash + cmp_trits(a.address().to_inner(), b.address().to_inner()) + .then(cmp_trits(a.tag().to_inner(), b.tag().to_inner())) + // different messages may have the same bundle hash! + .then(cmp_trits(a.bundle().to_inner(), b.bundle().to_inner())) + // reverse order of transactions will be extracted from back with `pop` + .then(a.index().to_inner().cmp(b.index().to_inner()).reverse()) + }); + + let mut bundles: Vec> = Vec::new(); + + if let Some(root) = transactions.pop() { + let mut bundle: Vec = vec![root]; + + loop { + if let Some(transaction) = transactions.pop() { + if cmp_transaction(&bundle[0], &transaction) { + bundle.push(transaction); + } else { + bundles.push(bundle); + bundle = vec![transaction]; } + } else { + bundles.push(bundle); + break; + } } + } - // TODO: Check the bundles - bundles + // TODO: Check the bundles + bundles } fn cmp_trits(a: &Trits, b: &Trits) -> Ordering { - a.iter().cmp(b.iter()) + a.iter().cmp(b.iter()) } fn cmp_transaction(a: &BundledTransaction, b: &BundledTransaction) -> bool { - a.address() == b.address() && a.tag() == b.tag() && a.bundle() == b.bundle() + a.address() == b.address() && a.tag() == b.tag() && a.bundle() == b.bundle() } diff --git a/rustfmt.toml b/rustfmt.toml index 657511c75d..d74601ba2f 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -4,4 +4,5 @@ license_template_path = ".license_template" max_width = 120 normalize_comments = false normalize_doc_attributes = false +tab_spaces = 2 wrap_comments = true From a32b2df59d73f6ff14dcdf8f376c83d09d2df76e Mon Sep 17 00:00:00 2001 From: l1h3r Date: Mon, 1 Feb 2021 13:41:54 -0800 Subject: [PATCH 03/28] credential crate --- .github/workflows/build-and-test.yml | 1 + .github/workflows/clippy.yml | 1 + .github/workflows/format.yml | 1 + Cargo.toml | 1 + examples/credential/Cargo.toml | 1 + examples/credential/src/main.rs | 18 +- identity-core/Cargo.toml | 1 - .../src/credential/credential_builder.rs | 255 ----------------- identity-core/src/credential/mod.rs | 22 -- identity-core/src/credential/presentation.rs | 149 ---------- .../src/credential/presentation_builder.rs | 210 -------------- .../src/credential/types/credential_schema.rs | 66 ----- .../credential/types/credential_subject.rs | 123 --------- identity-core/src/credential/types/mod.rs | 18 -- identity-core/src/error.rs | 6 - identity-core/src/lib.rs | 4 - identity-credential/Cargo.toml | 22 ++ identity-credential/src/credential/builder.rs | 261 ++++++++++++++++++ .../src/credential/credential.rs | 120 ++++---- .../src/credential}/evidence.rs | 45 +-- .../src/credential}/issuer.rs | 28 +- identity-credential/src/credential/mod.rs | 28 ++ .../src/credential/policy.rs | 28 +- .../src/credential/refresh.rs | 29 +- identity-credential/src/credential/schema.rs | 72 +++++ .../src/credential/status.rs | 29 +- identity-credential/src/credential/subject.rs | 126 +++++++++ .../src/credential/verifiable.rs | 38 +-- identity-credential/src/error.rs | 27 ++ identity-credential/src/lib.rs | 27 ++ .../src/presentation/builder.rs | 220 +++++++++++++++ identity-credential/src/presentation/mod.rs | 14 + .../src/presentation/presentation.rs | 159 +++++++++++ .../src/presentation/verifiable.rs | 26 +- .../tests/fixtures}/credential-1.json | 0 .../tests/fixtures}/credential-10.json | 0 .../tests/fixtures}/credential-11.json | 0 .../tests/fixtures}/credential-12.json | 0 .../tests/fixtures}/credential-2.json | 0 .../tests/fixtures}/credential-3.json | 0 .../tests/fixtures}/credential-4.json | 0 .../tests/fixtures}/credential-5.json | 0 .../tests/fixtures}/credential-6.json | 0 .../tests/fixtures}/credential-7.json | 0 .../tests/fixtures}/credential-8.json | 0 .../tests/fixtures}/credential-9.json | 0 .../tests/fixtures}/evidence-1.json | 0 .../tests/fixtures}/evidence-2.json | 0 .../tests/fixtures}/issuer-1.json | 0 .../tests/fixtures}/issuer-2.json | 0 .../tests/fixtures/policy-1.json | 0 .../tests/fixtures/policy-2.json | 0 .../tests/fixtures}/presentation-1.json | 0 .../tests/fixtures/refresh-1.json | 0 .../tests/fixtures/schema-1.json | 0 .../tests/fixtures/schema-2.json | 0 .../tests/fixtures/schema-3.json | 0 .../tests/fixtures/status-1.json | 0 .../tests/fixtures/subject-1.json | 0 .../tests/fixtures/subject-10.json | 0 .../tests/fixtures/subject-2.json | 0 .../tests/fixtures/subject-3.json | 0 .../tests/fixtures/subject-4.json | 0 .../tests/fixtures/subject-5.json | 0 .../tests/fixtures/subject-6.json | 0 .../tests/fixtures/subject-7.json | 0 .../tests/fixtures/subject-8.json | 0 .../tests/fixtures/subject-9.json | 0 identity-iota/Cargo.toml | 1 + identity-iota/src/credential/validator.rs | 12 +- identity-iota/src/error.rs | 4 + 71 files changed, 1170 insertions(+), 1023 deletions(-) delete mode 100644 identity-core/src/credential/credential_builder.rs delete mode 100644 identity-core/src/credential/mod.rs delete mode 100644 identity-core/src/credential/presentation.rs delete mode 100644 identity-core/src/credential/presentation_builder.rs delete mode 100644 identity-core/src/credential/types/credential_schema.rs delete mode 100644 identity-core/src/credential/types/credential_subject.rs delete mode 100644 identity-core/src/credential/types/mod.rs create mode 100644 identity-credential/Cargo.toml create mode 100644 identity-credential/src/credential/builder.rs rename {identity-core => identity-credential}/src/credential/credential.rs (65%) rename {identity-core/src/credential/types => identity-credential/src/credential}/evidence.rs (55%) rename {identity-core/src/credential/types => identity-credential/src/credential}/issuer.rs (62%) create mode 100644 identity-credential/src/credential/mod.rs rename identity-core/src/credential/types/terms_of_use.rs => identity-credential/src/credential/policy.rs (79%) rename identity-core/src/credential/types/refresh_service.rs => identity-credential/src/credential/refresh.rs (59%) create mode 100644 identity-credential/src/credential/schema.rs rename identity-core/src/credential/types/credential_status.rs => identity-credential/src/credential/status.rs (58%) create mode 100644 identity-credential/src/credential/subject.rs rename identity-core/src/credential/verifiable_credential.rs => identity-credential/src/credential/verifiable.rs (81%) create mode 100644 identity-credential/src/error.rs create mode 100644 identity-credential/src/lib.rs create mode 100644 identity-credential/src/presentation/builder.rs create mode 100644 identity-credential/src/presentation/mod.rs create mode 100644 identity-credential/src/presentation/presentation.rs rename identity-core/src/credential/verifiable_presentation.rs => identity-credential/src/presentation/verifiable.rs (82%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/credential-1.json (100%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/credential-10.json (100%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/credential-11.json (100%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/credential-12.json (100%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/credential-2.json (100%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/credential-3.json (100%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/credential-4.json (100%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/credential-5.json (100%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/credential-6.json (100%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/credential-7.json (100%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/credential-8.json (100%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/credential-9.json (100%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/evidence-1.json (100%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/evidence-2.json (100%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/issuer-1.json (100%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/issuer-2.json (100%) rename identity-core/tests/fixtures/vc/terms-of-use-1.json => identity-credential/tests/fixtures/policy-1.json (100%) rename identity-core/tests/fixtures/vc/terms-of-use-2.json => identity-credential/tests/fixtures/policy-2.json (100%) rename {identity-core/tests/fixtures/vc => identity-credential/tests/fixtures}/presentation-1.json (100%) rename identity-core/tests/fixtures/vc/refresh-service-1.json => identity-credential/tests/fixtures/refresh-1.json (100%) rename identity-core/tests/fixtures/vc/credential-schema-1.json => identity-credential/tests/fixtures/schema-1.json (100%) rename identity-core/tests/fixtures/vc/credential-schema-2.json => identity-credential/tests/fixtures/schema-2.json (100%) rename identity-core/tests/fixtures/vc/credential-schema-3.json => identity-credential/tests/fixtures/schema-3.json (100%) rename identity-core/tests/fixtures/vc/credential-status-1.json => identity-credential/tests/fixtures/status-1.json (100%) rename identity-core/tests/fixtures/vc/credential-subject-1.json => identity-credential/tests/fixtures/subject-1.json (100%) rename identity-core/tests/fixtures/vc/credential-subject-10.json => identity-credential/tests/fixtures/subject-10.json (100%) rename identity-core/tests/fixtures/vc/credential-subject-2.json => identity-credential/tests/fixtures/subject-2.json (100%) rename identity-core/tests/fixtures/vc/credential-subject-3.json => identity-credential/tests/fixtures/subject-3.json (100%) rename identity-core/tests/fixtures/vc/credential-subject-4.json => identity-credential/tests/fixtures/subject-4.json (100%) rename identity-core/tests/fixtures/vc/credential-subject-5.json => identity-credential/tests/fixtures/subject-5.json (100%) rename identity-core/tests/fixtures/vc/credential-subject-6.json => identity-credential/tests/fixtures/subject-6.json (100%) rename identity-core/tests/fixtures/vc/credential-subject-7.json => identity-credential/tests/fixtures/subject-7.json (100%) rename identity-core/tests/fixtures/vc/credential-subject-8.json => identity-credential/tests/fixtures/subject-8.json (100%) rename identity-core/tests/fixtures/vc/credential-subject-9.json => identity-credential/tests/fixtures/subject-9.json (100%) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ba313b06f2..211bf41b19 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -21,6 +21,7 @@ jobs: project: [ identity-core, + identity-credential, identity-diff, identity-iota, ] diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index b25fdf9862..68941bd6f0 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -21,6 +21,7 @@ jobs: project: [ identity-core, + identity-credential, identity-diff, identity-iota, bindings/wasm, diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index c49591c4b7..82fc83b1c5 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -21,6 +21,7 @@ jobs: project: [ identity-core, + identity-credential, identity-diff, identity-iota, bindings/wasm, diff --git a/Cargo.toml b/Cargo.toml index 92b7abada3..37489549dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "identity-core", + "identity-credential", "identity-diff", "identity-iota", diff --git a/examples/credential/Cargo.toml b/examples/credential/Cargo.toml index bfe7e2c0b7..a4ee5c86c9 100644 --- a/examples/credential/Cargo.toml +++ b/examples/credential/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] identity-core = { path = "../../identity-core" } +identity-credential = { path = "../../identity-credential" } identity-iota = { path = "../../identity-iota" } smol = { version = "0.1.18", features = ["tokio02"] } smol-potat = { version = "0.3" } diff --git a/examples/credential/src/main.rs b/examples/credential/src/main.rs index 90101deb8a..c1ece76ad4 100644 --- a/examples/credential/src/main.rs +++ b/examples/credential/src/main.rs @@ -7,12 +7,12 @@ use identity_core::{ common::{Url, Value}, convert::{FromJson as _, ToJson as _}, - credential::{Credential, CredentialBuilder, CredentialSubject, VerifiableCredential}, crypto::KeyPair, did_doc::MethodScope, did_url::DID, json, }; +use identity_credential::credential::{Builder as CredentialBuilder, Credential, Subject, VerifiableCredential}; use identity_iota::{ client::Client, credential::{CredentialValidation, CredentialValidator}, @@ -41,16 +41,16 @@ async fn document(client: &Client) -> Result<(IotaDocument, KeyPair)> { Ok((document, keypair)) } -fn subject(subject: &DID) -> Result { +fn subject(subject: &DID) -> Result { let json: Value = json!({ - "id": subject.as_str(), - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts" - } + "id": subject.as_str(), + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts" + } }); - CredentialSubject::from_json_value(json).map_err(Into::into) + Subject::from_json_value(json).map_err(Into::into) } #[smol_potat::main] @@ -64,7 +64,7 @@ async fn main() -> Result<()> { let credential: Credential = CredentialBuilder::default() .issuer(Url::parse(doc_iss.id())?) .type_("UniversityDegreeCredential") - .credential_subject(subject(&doc_sub.id())?) + .subject(subject(&doc_sub.id())?) .build()?; // Extract the default verification method from the authentication scope and diff --git a/identity-core/Cargo.toml b/identity-core/Cargo.toml index 48a9a9bd38..fc4ede9260 100644 --- a/identity-core/Cargo.toml +++ b/identity-core/Cargo.toml @@ -20,7 +20,6 @@ did_url = { version = "0.1", default-features = false, features = ["std", "serde digest = { version = "0.9", default-features = false } ed25519-zebra = { version = "2.2", default-features = false } identity-diff = { version = "=0.1.0", path = "../identity-diff", default-features = false } -lazy_static = { version = "1.4", default-features = false } rand = { version = "0.7", default-features = false, features = ["getrandom"] } serde = { version = "1.0", default-features = false, features = ["std", "derive"] } serde_jcs = { version = "0.1", default-features = false } diff --git a/identity-core/src/credential/credential_builder.rs b/identity-core/src/credential/credential_builder.rs deleted file mode 100644 index 9328a57f5a..0000000000 --- a/identity-core/src/credential/credential_builder.rs +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - common::{Context, Object, Timestamp, Url, Value}, - credential::{ - Credential, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, RefreshService, TermsOfUse, - }, - error::Result, -}; - -/// A `CredentialBuilder` is used to create a customized `Credential`. -#[derive(Clone, Debug)] -pub struct CredentialBuilder { - pub(crate) context: Vec, - pub(crate) id: Option, - pub(crate) types: Vec, - pub(crate) credential_subject: Vec, - pub(crate) issuer: Option, - pub(crate) issuance_date: Option, - pub(crate) expiration_date: Option, - pub(crate) credential_status: Vec, - pub(crate) credential_schema: Vec, - pub(crate) refresh_service: Vec, - pub(crate) terms_of_use: Vec, - pub(crate) evidence: Vec, - pub(crate) non_transferable: Option, - pub(crate) properties: T, -} - -impl CredentialBuilder { - /// Creates a new `CredentialBuilder`. - pub fn new(properties: T) -> Self { - Self { - context: vec![Credential::::base_context().clone()], - id: None, - types: vec![Credential::::base_type().into()], - credential_subject: Vec::new(), - issuer: None, - issuance_date: None, - expiration_date: None, - credential_status: Vec::new(), - credential_schema: Vec::new(), - refresh_service: Vec::new(), - terms_of_use: Vec::new(), - evidence: Vec::new(), - non_transferable: None, - properties, - } - } - - /// Adds a value to the `Credential` context set. - #[must_use] - pub fn context(mut self, value: impl Into) -> Self { - self.context.push(value.into()); - self - } - - /// Sets the value of the `Credential` `id`. - #[must_use] - pub fn id(mut self, value: Url) -> Self { - self.id = Some(value); - self - } - - /// Adds a value to the `Credential` type set. - #[must_use] - pub fn type_(mut self, value: impl Into) -> Self { - self.types.push(value.into()); - self - } - - /// Adds a value to the `credentialSubject` set. - #[must_use] - pub fn credential_subject(mut self, value: CredentialSubject) -> Self { - self.credential_subject.push(value); - self - } - - /// Sets the value of the `Credential` `issuer`. - #[must_use] - pub fn issuer(mut self, value: impl Into) -> Self { - self.issuer = Some(value.into()); - self - } - - /// Sets the value of the `Credential` `issuanceDate`. - #[must_use] - pub fn issuance_date(mut self, value: Timestamp) -> Self { - self.issuance_date = Some(value); - self - } - - /// Sets the value of the `Credential` `expirationDate`. - #[must_use] - pub fn expiration_date(mut self, value: Timestamp) -> Self { - self.expiration_date = Some(value); - self - } - - /// Adds a value to the `credentialStatus` set. - #[must_use] - pub fn credential_status(mut self, value: CredentialStatus) -> Self { - self.credential_status.push(value); - self - } - - /// Adds a value to the `credentialSchema` set. - #[must_use] - pub fn credential_schema(mut self, value: CredentialSchema) -> Self { - self.credential_schema.push(value); - self - } - - /// Adds a value to the `refreshService` set. - #[must_use] - pub fn refresh_service(mut self, value: RefreshService) -> Self { - self.refresh_service.push(value); - self - } - - /// Adds a value to the `termsOfUse` set. - #[must_use] - pub fn terms_of_use(mut self, value: TermsOfUse) -> Self { - self.terms_of_use.push(value); - self - } - - /// Adds a value to the `evidence` set. - #[must_use] - pub fn evidence(mut self, value: Evidence) -> Self { - self.evidence.push(value); - self - } - - /// Sets the value of the `Credential` `nonTransferable` property. - #[must_use] - pub fn non_transferable(mut self, value: bool) -> Self { - self.non_transferable = Some(value); - self - } - - /// Returns a new `Credential` based on the `CredentialBuilder` configuration. - pub fn build(self) -> Result> { - Credential::from_builder(self) - } -} - -impl CredentialBuilder { - /// Adds a new custom property to the `Credential`. - #[must_use] - pub fn property(mut self, key: K, value: V) -> Self - where - K: Into, - V: Into, - { - self.properties.insert(key.into(), value.into()); - self - } - - /// Adds a series of custom properties to the `Credential`. - #[must_use] - pub fn properties(mut self, iter: I) -> Self - where - I: IntoIterator, - K: Into, - V: Into, - { - self - .properties - .extend(iter.into_iter().map(|(k, v)| (k.into(), v.into()))); - self - } -} - -impl Default for CredentialBuilder -where - T: Default, -{ - fn default() -> Self { - Self::new(T::default()) - } -} - -#[cfg(test)] -mod tests { - use serde_json::{json, Value}; - - use crate::{ - common::{Object, Timestamp, Url}, - convert::FromJson as _, - credential::{Credential as Credential_, CredentialBuilder, CredentialSubject}, - }; - - type Credential = Credential_; - - fn subject() -> CredentialSubject { - let json: Value = json!({ - "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts" - } - }); - - CredentialSubject::from_json_value(json).unwrap() - } - - fn issuer() -> Url { - Url::parse("did:example:issuer").unwrap() - } - - #[test] - #[rustfmt::skip] - fn test_credential_builder_valid() { - let credential: Credential = CredentialBuilder::default() - .context(Url::parse("https://www.w3.org/2018/credentials/examples/v1").unwrap()) - .id(Url::parse("http://example.edu/credentials/3732").unwrap()) - .type_("UniversityDegreeCredential") - .credential_subject(subject()) - .issuer(issuer()) - .issuance_date(Timestamp::parse("2010-01-01T00:00:00Z").unwrap()) - .build() - .unwrap(); - - assert_eq!(credential.context.len(), 2); - assert_eq!(credential.context.get(0).unwrap(), Credential::base_context()); - assert_eq!(credential.context.get(1).unwrap(), "https://www.w3.org/2018/credentials/examples/v1"); - assert_eq!(credential.id.unwrap(), "http://example.edu/credentials/3732"); - assert_eq!(credential.types.len(), 2); - assert_eq!(credential.types.get(0).unwrap(), Credential::base_type()); - assert_eq!(credential.types.get(1).unwrap(), "UniversityDegreeCredential"); - assert_eq!(credential.credential_subject.len(), 1); - assert_eq!(credential.issuer.url(), "did:example:issuer"); - assert_eq!(credential.issuance_date.to_string(), "2010-01-01T00:00:00Z"); - assert_eq!(credential.credential_subject.get(0).unwrap().id.as_ref().unwrap(), "did:example:ebfeb1f712ebc6f1c276e12ec21"); - assert_eq!(credential.credential_subject.get(0).unwrap().properties["degree"]["type"], "BachelorDegree"); - assert_eq!(credential.credential_subject.get(0).unwrap().properties["degree"]["name"], "Bachelor of Science and Arts"); - } - - #[test] - #[should_panic = "Missing Subject"] - fn test_builder_missing_subjects() { - let _: Credential = CredentialBuilder::default().issuer(issuer()).build().unwrap(); - } - - #[test] - #[should_panic = "Missing Credential Issuer"] - fn test_builder_missing_issuer() { - let _: Credential = CredentialBuilder::default() - .credential_subject(subject()) - .build() - .unwrap(); - } -} diff --git a/identity-core/src/credential/mod.rs b/identity-core/src/credential/mod.rs deleted file mode 100644 index 4cd3ad8d1e..0000000000 --- a/identity-core/src/credential/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! Types and traits for working with Verifiable Credentials. - -#![allow(clippy::module_inception)] - -mod credential; -mod credential_builder; -mod presentation; -mod presentation_builder; -mod types; -mod verifiable_credential; -mod verifiable_presentation; - -pub use credential::*; -pub use credential_builder::*; -pub use presentation::*; -pub use presentation_builder::*; -pub use types::*; -pub use verifiable_credential::*; -pub use verifiable_presentation::*; diff --git a/identity-core/src/credential/presentation.rs b/identity-core/src/credential/presentation.rs deleted file mode 100644 index 7e3252e323..0000000000 --- a/identity-core/src/credential/presentation.rs +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use core::fmt::{Display, Error as FmtError, Formatter, Result as FmtResult}; -use serde::Serialize; - -use crate::{ - common::{Context, Object, OneOrMany, Url}, - convert::ToJson as _, - credential::{Credential, PresentationBuilder, RefreshService, TermsOfUse, VerifiableCredential}, - error::{Error, Result}, -}; - -/// A `Presentation` represents a bundle of one or more `VerifiableCredential`s. -/// -/// `Presentation`s can be signed with `Document`s to create `VerifiablePresentation`s. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct Presentation { - /// The JSON-LD context(s) applicable to the `Presentation`. - #[serde(rename = "@context")] - pub context: OneOrMany, - /// A unique `URI` referencing the subject of the `Presentation`. - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - /// One or more URIs defining the type of the `Presentation`. - #[serde(rename = "type")] - pub types: OneOrMany, - /// Credential(s) expressing the claims of the `Presentation`. - #[serde(default = "Default::default", rename = "verifiableCredential")] - pub verifiable_credential: OneOrMany>, - /// The entity that generated the presentation. - #[serde(skip_serializing_if = "Option::is_none")] - pub holder: Option, - /// Service(s) used to refresh an expired `Presentation`. - #[serde(default, rename = "refreshService", skip_serializing_if = "OneOrMany::is_empty")] - pub refresh_service: OneOrMany, - /// Terms-of-use specified by the `Presentation` holder. - #[serde(default, rename = "termsOfUse", skip_serializing_if = "OneOrMany::is_empty")] - pub terms_of_use: OneOrMany, - /// Miscellaneous properties. - #[serde(flatten)] - pub properties: T, -} - -impl Presentation { - /// Returns the base JSON-LD context for `Presentation`s. - pub fn base_context() -> &'static Context { - Credential::::base_context() - } - - /// Returns the base type for `Presentation`s. - pub const fn base_type() -> &'static str { - "VerifiablePresentation" - } - - /// Creates a `PresentationBuilder` to configure a new `Presentation`. - /// - /// This is the same as `PresentationBuilder::new()`. - pub fn builder(properties: T) -> PresentationBuilder { - PresentationBuilder::new(properties) - } - - /// Returns a new `Presentation` based on the `PresentationBuilder` configuration. - pub fn from_builder(builder: PresentationBuilder) -> Result { - let this: Self = Self { - context: builder.context.into(), - id: builder.id, - types: builder.types.into(), - verifiable_credential: builder.verifiable_credential.into(), - holder: builder.holder, - refresh_service: builder.refresh_service.into(), - terms_of_use: builder.terms_of_use.into(), - properties: builder.properties, - }; - - this.check_structure()?; - - Ok(this) - } - - /// Validates the semantic structure of the `Presentation`. - pub fn check_structure(&self) -> Result<()> { - // Ensure the base context is present and in the correct location - match self.context.get(0) { - Some(context) if context == Self::base_context() => {} - Some(_) | None => return Err(Error::InvalidPresentation("Missing Base Context".into())), - } - - // The set of types MUST contain the base type - if !self.types.iter().any(|type_| type_ == Self::base_type()) { - return Err(Error::InvalidPresentation("Missing Base Type".into())); - } - - // Check all credentials. - for credential in self.verifiable_credential.iter() { - credential.check_structure()?; - } - - Ok(()) - } -} - -impl Display for Presentation -where - T: Serialize, - U: Serialize, -{ - fn fmt(&self, f: &mut Formatter) -> FmtResult { - if f.alternate() { - f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) - } else { - f.write_str(&self.to_json().map_err(|_| FmtError)?) - } - } -} - -#[cfg(test)] -mod tests { - use crate::{ - convert::FromJson as _, - credential::{CredentialSubject, VerifiableCredential, VerifiablePresentation}, - }; - - const JSON: &str = include_str!("../../tests/fixtures/vc/presentation-1.json"); - - #[test] - #[rustfmt::skip] - fn test_from_json() { - let presentation: VerifiablePresentation = VerifiablePresentation::from_json(JSON).unwrap(); - let credential: &VerifiableCredential = presentation.verifiable_credential.get(0).unwrap(); - let subject: &CredentialSubject = credential.credential_subject.get(0).unwrap(); - - assert_eq!(presentation.context.as_slice(), ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"]); - assert_eq!(presentation.id.as_ref().unwrap(), "urn:uuid:3978344f-8596-4c3a-a978-8fcaba3903c5"); - assert_eq!(presentation.types.as_slice(), ["VerifiablePresentation", "CredentialManagerPresentation"]); - assert_eq!(presentation.proof().get(0).unwrap().type_(), "RsaSignature2018"); - - assert_eq!(credential.context.as_slice(), ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"]); - assert_eq!(credential.id.as_ref().unwrap(), "http://example.edu/credentials/3732"); - assert_eq!(credential.types.as_slice(), ["VerifiableCredential", "UniversityDegreeCredential"]); - assert_eq!(credential.issuer.url(), "https://example.edu/issuers/14"); - assert_eq!(credential.issuance_date, "2010-01-01T19:23:24Z".parse().unwrap()); - assert_eq!(credential.proof().get(0).unwrap().type_(), "RsaSignature2018"); - - assert_eq!(subject.id.as_ref().unwrap(), "did:example:ebfeb1f712ebc6f1c276e12ec21"); - assert_eq!(subject.properties["degree"]["type"], "BachelorDegree"); - assert_eq!(subject.properties["degree"]["name"], "Bachelor of Science in Mechanical Engineering"); - } -} diff --git a/identity-core/src/credential/presentation_builder.rs b/identity-core/src/credential/presentation_builder.rs deleted file mode 100644 index 6c108866bd..0000000000 --- a/identity-core/src/credential/presentation_builder.rs +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - common::{Context, Object, Url, Value}, - credential::{Presentation, RefreshService, TermsOfUse, VerifiableCredential}, - error::Result, -}; - -/// A `PresentationBuilder` is used to create a customized `Presentation`. -#[derive(Clone, Debug)] -pub struct PresentationBuilder { - pub(crate) context: Vec, - pub(crate) id: Option, - pub(crate) types: Vec, - pub(crate) verifiable_credential: Vec>, - pub(crate) holder: Option, - pub(crate) refresh_service: Vec, - pub(crate) terms_of_use: Vec, - pub(crate) properties: T, -} - -impl PresentationBuilder { - /// Creates a new `PresentationBuilder`. - pub fn new(properties: T) -> Self { - Self { - context: vec![Presentation::::base_context().clone()], - id: None, - types: vec![Presentation::::base_type().into()], - verifiable_credential: Vec::new(), - holder: None, - refresh_service: Vec::new(), - terms_of_use: Vec::new(), - properties, - } - } - - /// Adds a value to the `Presentation` context set. - #[must_use] - pub fn context(mut self, value: impl Into) -> Self { - self.context.push(value.into()); - self - } - - /// Sets the value of the `Presentation` `id`. - #[must_use] - pub fn id(mut self, value: Url) -> Self { - self.id = Some(value); - self - } - - /// Adds a value to the `Presentation` type set. - #[must_use] - pub fn type_(mut self, value: impl Into) -> Self { - self.types.push(value.into()); - self - } - - /// Adds a value to the `verifiableCredential` set. - #[must_use] - pub fn verifiable_credential(mut self, value: VerifiableCredential) -> Self { - self.verifiable_credential.push(value); - self - } - - /// Sets the value of the `Credential` `holder`. - #[must_use] - pub fn holder(mut self, value: Url) -> Self { - self.holder = Some(value); - self - } - - /// Adds a value to the `refreshService` set. - #[must_use] - pub fn refresh_service(mut self, value: RefreshService) -> Self { - self.refresh_service.push(value); - self - } - - /// Adds a value to the `termsOfUse` set. - #[must_use] - pub fn terms_of_use(mut self, value: TermsOfUse) -> Self { - self.terms_of_use.push(value); - self - } - - /// Returns a new `Presentation` based on the `PresentationBuilder` configuration. - pub fn build(self) -> Result> { - Presentation::from_builder(self) - } -} - -impl PresentationBuilder { - /// Adds a new custom property to the `Presentation`. - #[must_use] - pub fn property(mut self, key: K, value: V) -> Self - where - K: Into, - V: Into, - { - self.properties.insert(key.into(), value.into()); - self - } - - /// Adds a series of custom properties to the `Presentation`. - #[must_use] - pub fn properties(mut self, iter: I) -> Self - where - I: IntoIterator, - K: Into, - V: Into, - { - self - .properties - .extend(iter.into_iter().map(|(k, v)| (k.into(), v.into()))); - self - } -} - -impl Default for PresentationBuilder -where - T: Default, -{ - fn default() -> Self { - Self::new(T::default()) - } -} - -#[cfg(test)] -mod tests { - use did_doc::{Document, DocumentBuilder, Method, MethodBuilder, MethodData, MethodType}; - use did_url::DID; - use serde_json::{json, Value}; - - use crate::{ - common::{Object, Url}, - convert::FromJson as _, - credential::{ - Credential as Credential_, CredentialBuilder, CredentialSubject, Presentation as Presentation_, - PresentationBuilder, VerifiableCredential, - }, - crypto::KeyPair, - proof::JcsEd25519Signature2020, - utils::encode_b58, - }; - - type Credential = Credential_; - type Presentation = Presentation_; - - fn subject() -> CredentialSubject { - let json: Value = json!({ - "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts" - } - }); - - CredentialSubject::from_json_value(json).unwrap() - } - - fn issuer() -> Url { - Url::parse("did:example:issuer").unwrap() - } - - #[test] - #[rustfmt::skip] - fn test_presentation_builder_valid() { - let keypair: KeyPair = JcsEd25519Signature2020::new_keypair(); - let controller: DID = "did:example:1234".parse().unwrap(); - - let method: Method = MethodBuilder::default() - .id(controller.join("#key-1").unwrap()) - .controller(controller.clone()) - .key_type(MethodType::Ed25519VerificationKey2018) - .key_data(MethodData::PublicKeyBase58(encode_b58(keypair.public()))) - .build() - .unwrap(); - - let document: Document = DocumentBuilder::default() - .id(controller) - .verification_method(method) - .build() - .unwrap(); - - let credential: VerifiableCredential = CredentialBuilder::default() - .type_("ExampleCredential") - .credential_subject(subject()) - .issuer(issuer()) - .build() - .unwrap() - .sign(&document, 0, keypair.secret()) - .unwrap(); - - let presentation: Presentation = PresentationBuilder::default() - .type_("ExamplePresentation") - .verifiable_credential(credential) - .build() - .unwrap(); - - assert_eq!(presentation.context.len(), 1); - assert_eq!(presentation.context.get(0).unwrap(), Presentation::base_context()); - assert_eq!(presentation.types.len(), 2); - assert_eq!(presentation.types.get(0).unwrap(), Presentation::base_type()); - assert_eq!(presentation.types.get(1).unwrap(), "ExamplePresentation"); - assert_eq!(presentation.verifiable_credential.len(), 1); - assert_eq!(presentation.verifiable_credential.get(0).unwrap().types.get(0).unwrap(), Credential::base_type()); - assert_eq!(presentation.verifiable_credential.get(0).unwrap().types.get(1).unwrap(), "ExampleCredential"); - } -} diff --git a/identity-core/src/credential/types/credential_schema.rs b/identity-core/src/credential/types/credential_schema.rs deleted file mode 100644 index c99c0e4bc7..0000000000 --- a/identity-core/src/credential/types/credential_schema.rs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use crate::common::{Object, OneOrMany, Url}; - -/// Information used to validate the structure of a `Credential`. -/// -/// [More Info](https://www.w3.org/TR/vc-data-model/#data-schemas) -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct CredentialSchema { - /// A Url identifying the credential schema file. - pub id: Url, - /// The type(s) of the credential schema. - #[serde(rename = "type")] - pub types: OneOrMany, - /// Additional properties of the credential schema. - #[serde(flatten)] - pub properties: Object, -} - -impl CredentialSchema { - /// Creates a new [`CredentialSchema`]. - pub fn new(id: Url, types: T) -> Self - where - T: Into>, - { - Self::with_properties(id, types, Object::new()) - } - - /// Creates a new [`CredentialSchema`] with the given `properties`. - pub fn with_properties(id: Url, types: T, properties: Object) -> Self - where - T: Into>, - { - Self { - id, - types: types.into(), - properties, - } - } -} - -#[cfg(test)] -mod tests { - use crate::{convert::FromJson as _, credential::CredentialSchema}; - - const JSON1: &str = include_str!("../../../tests/fixtures/vc/credential-schema-1.json"); - const JSON2: &str = include_str!("../../../tests/fixtures/vc/credential-schema-2.json"); - const JSON3: &str = include_str!("../../../tests/fixtures/vc/credential-schema-3.json"); - - #[test] - #[rustfmt::skip] - fn test_from_json() { - let schema: CredentialSchema = CredentialSchema::from_json(JSON1).unwrap(); - assert_eq!(schema.id, "https://example.org/examples/degree.json"); - assert_eq!(schema.types.as_slice(), ["JsonSchemaValidator2018"]); - - let schema: CredentialSchema = CredentialSchema::from_json(JSON2).unwrap(); - assert_eq!(schema.id, "https://example.org/examples/degree.zkp"); - assert_eq!(schema.types.as_slice(), ["ZkpExampleSchema2018"]); - - let schema: CredentialSchema = CredentialSchema::from_json(JSON3).unwrap(); - assert_eq!(schema.id, "did:example:cdf:35LB7w9ueWbagPL94T9bMLtyXDj9pX5o"); - assert_eq!(schema.types.as_slice(), ["did:example:schema:22KpkXgecryx9k7N6XN1QoN3gXwBkSU8SfyyYQG"]); - } -} diff --git a/identity-core/src/credential/types/credential_subject.rs b/identity-core/src/credential/types/credential_subject.rs deleted file mode 100644 index d79f37668b..0000000000 --- a/identity-core/src/credential/types/credential_subject.rs +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use crate::common::{Object, Url}; - -/// An entity who is the target of a set of claims. -/// -/// [More Info](https://www.w3.org/TR/vc-data-model/#credential-subject) -#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] -pub struct CredentialSubject { - /// A Url identifying the credential subject. - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - /// Additional properties of the credential subject. - #[serde(flatten)] - pub properties: Object, -} - -impl CredentialSubject { - /// Creates a new [`CredentialSubject`]. - pub fn new() -> Self { - Self::with_properties(Object::new()) - } - - /// Creates a new [`CredentialSubject`] with the given `id`. - pub fn with_id(id: Url) -> Self { - Self::with_id_and_properties(id, Object::new()) - } - - /// Creates a new [`CredentialSubject`] with the given `properties`. - pub fn with_properties(properties: Object) -> Self { - Self { id: None, properties } - } - - /// Creates a new [`CredentialSubject`] with the given `id` and `properties`. - pub fn with_id_and_properties(id: Url, properties: Object) -> Self { - Self { - id: Some(id), - properties, - } - } -} - -#[cfg(test)] -mod tests { - use crate::{convert::FromJson as _, credential::CredentialSubject}; - - const JSON1: &str = include_str!("../../../tests/fixtures/vc/credential-subject-1.json"); - const JSON2: &str = include_str!("../../../tests/fixtures/vc/credential-subject-2.json"); - const JSON3: &str = include_str!("../../../tests/fixtures/vc/credential-subject-3.json"); - const JSON4: &str = include_str!("../../../tests/fixtures/vc/credential-subject-4.json"); - const JSON5: &str = include_str!("../../../tests/fixtures/vc/credential-subject-5.json"); - const JSON6: &str = include_str!("../../../tests/fixtures/vc/credential-subject-6.json"); - const JSON7: &str = include_str!("../../../tests/fixtures/vc/credential-subject-7.json"); - const JSON8: &str = include_str!("../../../tests/fixtures/vc/credential-subject-8.json"); - const JSON9: &str = include_str!("../../../tests/fixtures/vc/credential-subject-9.json"); - const JSON10: &str = include_str!("../../../tests/fixtures/vc/credential-subject-10.json"); - - #[test] - #[rustfmt::skip] - fn test_from_json() { - let subject: CredentialSubject = CredentialSubject::from_json(JSON1).unwrap(); - assert_eq!(subject.id.unwrap(), "did:example:ebfeb1f712ebc6f1c276e12ec21"); - assert_eq!(subject.properties["alumniOf"]["id"], "did:example:c276e12ec21ebfeb1f712ebc6f1"); - assert_eq!(subject.properties["alumniOf"]["name"][0]["value"], "Example University"); - assert_eq!(subject.properties["alumniOf"]["name"][0]["lang"], "en"); - assert_eq!(subject.properties["alumniOf"]["name"][1]["value"], "Exemple d'Université"); - assert_eq!(subject.properties["alumniOf"]["name"][1]["lang"], "fr"); - - let subject: CredentialSubject = CredentialSubject::from_json(JSON2).unwrap(); - assert_eq!(subject.id.unwrap(), "did:example:ebfeb1f712ebc6f1c276e12ec21"); - assert_eq!(subject.properties["degree"]["type"], "BachelorDegree"); - assert_eq!(subject.properties["degree"]["name"], "Bachelor of Science and Arts"); - - let subject: CredentialSubject = CredentialSubject::from_json(JSON3).unwrap(); - assert_eq!(subject.id.unwrap(), "did:example:abcdef1234567"); - assert_eq!(subject.properties["name"], "Jane Doe"); - - let subject: CredentialSubject = CredentialSubject::from_json(JSON4).unwrap(); - assert_eq!(subject.id.unwrap(), "did:example:abcdef1234567"); - assert_eq!(subject.properties["name"], "Jane Doe"); - assert_eq!(subject.properties["favoriteFood"], "Papaya"); - - let subject: CredentialSubject = CredentialSubject::from_json(JSON5).unwrap(); - assert_eq!(subject.properties["givenName"], "Jane"); - assert_eq!(subject.properties["familyName"], "Doe"); - assert_eq!(subject.properties["degree"]["type"], "BachelorDegree"); - assert_eq!(subject.properties["degree"]["name"], "Bachelor of Science and Arts"); - assert_eq!(subject.properties["degree"]["college"], "College of Engineering"); - - let subject: CredentialSubject = CredentialSubject::from_json(JSON6).unwrap(); - assert_eq!(subject.properties["degreeType"], "BachelorDegree"); - assert_eq!(subject.properties["degreeSchool"], "College of Engineering"); - - let subject: CredentialSubject = CredentialSubject::from_json(JSON7).unwrap(); - assert_eq!(subject.id.unwrap(), "http://example.com/credentials/245"); - assert_eq!(subject.properties["currentStatus"], "Disputed"); - assert_eq!(subject.properties["statusReason"]["value"], "Address is out of date."); - assert_eq!(subject.properties["statusReason"]["lang"], "en"); - - let subject: CredentialSubject = CredentialSubject::from_json(JSON8).unwrap(); - assert_eq!(subject.properties["degree"]["type"], "BachelorDegree"); - assert_eq!(subject.properties["degree"]["name"], "Bachelor of Science and Arts"); - - let subject: CredentialSubject = CredentialSubject::from_json(JSON9).unwrap(); - assert_eq!(subject.id.unwrap(), "did:example:ebfeb1f712ebc6f1c276e12ec21"); - assert_eq!(subject.properties["image"], "https://example.edu/images/58473"); - assert_eq!(subject.properties["alumniOf"]["id"], "did:example:c276e12ec21ebfeb1f712ebc6f1"); - assert_eq!(subject.properties["alumniOf"]["name"][0]["value"], "Example University"); - assert_eq!(subject.properties["alumniOf"]["name"][0]["lang"], "en"); - assert_eq!(subject.properties["alumniOf"]["name"][1]["value"], "Exemple d'Université"); - assert_eq!(subject.properties["alumniOf"]["name"][1]["lang"], "fr"); - - let subject: CredentialSubject = CredentialSubject::from_json(JSON10).unwrap(); - assert_eq!(subject.id.unwrap(), "did:example:ebfeb1f712ebc6f1c276e12ec21"); - assert_eq!(subject.properties["image"], "ipfs:/ipfs/QmXfrS3pHerg44zzK6QKQj6JDk8H6cMtQS7pdXbohwNQfK/image"); - assert_eq!(subject.properties["alumniOf"]["id"], "did:example:c276e12ec21ebfeb1f712ebc6f1"); - assert_eq!(subject.properties["alumniOf"]["name"][0]["value"], "Example University"); - assert_eq!(subject.properties["alumniOf"]["name"][0]["lang"], "en"); - assert_eq!(subject.properties["alumniOf"]["name"][1]["value"], "Exemple d'Université"); - assert_eq!(subject.properties["alumniOf"]["name"][1]["lang"], "fr"); - } -} diff --git a/identity-core/src/credential/types/mod.rs b/identity-core/src/credential/types/mod.rs deleted file mode 100644 index e8990cf851..0000000000 --- a/identity-core/src/credential/types/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -mod credential_schema; -mod credential_status; -mod credential_subject; -mod evidence; -mod issuer; -mod refresh_service; -mod terms_of_use; - -pub use credential_schema::*; -pub use credential_status::*; -pub use credential_subject::*; -pub use evidence::*; -pub use issuer::*; -pub use refresh_service::*; -pub use terms_of_use::*; diff --git a/identity-core/src/error.rs b/identity-core/src/error.rs index 7397a255f5..8235ddfdf7 100644 --- a/identity-core/src/error.rs +++ b/identity-core/src/error.rs @@ -33,12 +33,6 @@ pub enum Error { /// Caused by attempting to parse an invalid `Timestamp`. #[error("Invalid Timestamp: {0}")] InvalidTimestamp(#[from] chrono::ParseError), - /// Caused by attempting to perform an invalid `Credential` operation. - #[error("Invalid Credential: {0}")] - InvalidCredential(String), - /// Caused by attempting to perform an invalid `Presentation` operation. - #[error("Invalid Presentation: {0}")] - InvalidPresentation(String), /// Caused by a failure to resolve a DID. #[error("DID Resolution Error: {0}")] ResolutionError(anyhow::Error), diff --git a/identity-core/src/lib.rs b/identity-core/src/lib.rs index e31ec3edbe..d270551d21 100644 --- a/identity-core/src/lib.rs +++ b/identity-core/src/lib.rs @@ -13,9 +13,6 @@ // clippy::missing_errors_doc )] -#[macro_use] -extern crate lazy_static; - #[macro_use] extern crate serde; @@ -27,7 +24,6 @@ pub use serde_json::json; #[macro_use] pub mod common; pub mod convert; -pub mod credential; pub mod crypto; pub mod error; pub mod proof; diff --git a/identity-credential/Cargo.toml b/identity-credential/Cargo.toml new file mode 100644 index 0000000000..909bb99f73 --- /dev/null +++ b/identity-credential/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "identity-credential" +version = "0.1.0" +authors = ["IOTA Identity"] +edition = "2018" +description = "An implementation of the Verfiable Credentials standard." +readme = "../README.md" +repository = "https://github.com/iotaledger/identity.rs" +license = "Apache-2.0" +keywords = ["iota", "tangle", "identity"] +homepage = "https://www.iota.org" + +[dependencies] +did_doc = { git = "https://github.com/l1h3r/did_doc", rev = "bb8bda8f6d39451b08e3cc198f956c212a3b87f8", default-features = false, features = ["std"] } +did_url = { version = "0.1", default-features = false, features = ["std", "serde"] } +identity-core = { version = "=0.1.0", path = "../identity-core" } +lazy_static = { version = "1.4", default-features = false } +serde = { version = "1.0", default-features = false, features = ["std", "derive"] } +thiserror = { version = "1.0", default-features = false } + +[dev-dependencies] +serde_json = { version = "1.0" } diff --git a/identity-credential/src/credential/builder.rs b/identity-credential/src/credential/builder.rs new file mode 100644 index 0000000000..96337e4eaf --- /dev/null +++ b/identity-credential/src/credential/builder.rs @@ -0,0 +1,261 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Context; +use identity_core::common::Object; +use identity_core::common::Timestamp; +use identity_core::common::Url; +use identity_core::common::Value; + +use crate::credential::Credential; +use crate::credential::Evidence; +use crate::credential::Issuer; +use crate::credential::Policy; +use crate::credential::Refresh; +use crate::credential::Schema; +use crate::credential::Status; +use crate::credential::Subject; +use crate::error::Result; + +/// A `Builder` is used to create a customized `Credential`. +#[derive(Clone, Debug)] +pub struct Builder { + pub(crate) context: Vec, + pub(crate) id: Option, + pub(crate) types: Vec, + pub(crate) subject: Vec, + pub(crate) issuer: Option, + pub(crate) issuance_date: Option, + pub(crate) expiration_date: Option, + pub(crate) status: Vec, + pub(crate) schema: Vec, + pub(crate) refresh: Vec, + pub(crate) policy: Vec, + pub(crate) evidence: Vec, + pub(crate) non_transferable: Option, + pub(crate) properties: T, +} + +impl Builder { + /// Creates a new `Builder`. + pub fn new(properties: T) -> Self { + Self { + context: vec![Credential::::base_context().clone()], + id: None, + types: vec![Credential::::base_type().into()], + subject: Vec::new(), + issuer: None, + issuance_date: None, + expiration_date: None, + status: Vec::new(), + schema: Vec::new(), + refresh: Vec::new(), + policy: Vec::new(), + evidence: Vec::new(), + non_transferable: None, + properties, + } + } + + /// Adds a value to the `Credential` context set. + #[must_use] + pub fn context(mut self, value: impl Into) -> Self { + self.context.push(value.into()); + self + } + + /// Sets the value of the `Credential` `id`. + #[must_use] + pub fn id(mut self, value: Url) -> Self { + self.id = Some(value); + self + } + + /// Adds a value to the `Credential` type set. + #[must_use] + pub fn type_(mut self, value: impl Into) -> Self { + self.types.push(value.into()); + self + } + + /// Adds a value to the `credentialSubject` set. + #[must_use] + pub fn subject(mut self, value: Subject) -> Self { + self.subject.push(value); + self + } + + /// Sets the value of the `Credential` `issuer`. + #[must_use] + pub fn issuer(mut self, value: impl Into) -> Self { + self.issuer = Some(value.into()); + self + } + + /// Sets the value of the `Credential` `issuanceDate`. + #[must_use] + pub fn issuance_date(mut self, value: Timestamp) -> Self { + self.issuance_date = Some(value); + self + } + + /// Sets the value of the `Credential` `expirationDate`. + #[must_use] + pub fn expiration_date(mut self, value: Timestamp) -> Self { + self.expiration_date = Some(value); + self + } + + /// Adds a value to the `credentialStatus` set. + #[must_use] + pub fn status(mut self, value: Status) -> Self { + self.status.push(value); + self + } + + /// Adds a value to the `credentialSchema` set. + #[must_use] + pub fn schema(mut self, value: Schema) -> Self { + self.schema.push(value); + self + } + + /// Adds a value to the `refreshService` set. + #[must_use] + pub fn refresh(mut self, value: Refresh) -> Self { + self.refresh.push(value); + self + } + + /// Adds a value to the `termsOfUse` set. + #[must_use] + pub fn policy(mut self, value: Policy) -> Self { + self.policy.push(value); + self + } + + /// Adds a value to the `evidence` set. + #[must_use] + pub fn evidence(mut self, value: Evidence) -> Self { + self.evidence.push(value); + self + } + + /// Sets the value of the `Credential` `nonTransferable` property. + #[must_use] + pub fn non_transferable(mut self, value: bool) -> Self { + self.non_transferable = Some(value); + self + } + + /// Returns a new `Credential` based on the `Builder` configuration. + pub fn build(self) -> Result> { + Credential::from_builder(self) + } +} + +impl Builder { + /// Adds a new custom property to the `Credential`. + #[must_use] + pub fn property(mut self, key: K, value: V) -> Self + where + K: Into, + V: Into, + { + self.properties.insert(key.into(), value.into()); + self + } + + /// Adds a series of custom properties to the `Credential`. + #[must_use] + pub fn properties(mut self, iter: I) -> Self + where + I: IntoIterator, + K: Into, + V: Into, + { + self + .properties + .extend(iter.into_iter().map(|(k, v)| (k.into(), v.into()))); + self + } +} + +impl Default for Builder +where + T: Default, +{ + fn default() -> Self { + Self::new(T::default()) + } +} + +#[cfg(test)] +mod tests { + use identity_core::common::Object; + use identity_core::common::Timestamp; + use identity_core::common::Url; + use identity_core::convert::FromJson; + use serde_json::json; + use serde_json::Value; + + use crate::credential::Builder; + use crate::credential::Credential; + use crate::credential::Subject; + + fn subject() -> Subject { + let json: Value = json!({ + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts" + } + }); + + Subject::from_json_value(json).unwrap() + } + + fn issuer() -> Url { + Url::parse("did:example:issuer").unwrap() + } + + #[test] + #[rustfmt::skip] + fn test_credential_builder_valid() { + let credential: Credential = Builder::default() + .context(Url::parse("https://www.w3.org/2018/credentials/examples/v1").unwrap()) + .id(Url::parse("http://example.edu/credentials/3732").unwrap()) + .type_("UniversityDegreeCredential") + .subject(subject()) + .issuer(issuer()) + .issuance_date(Timestamp::parse("2010-01-01T00:00:00Z").unwrap()) + .build() + .unwrap(); + + assert_eq!(credential.context.len(), 2); + assert_eq!(credential.context.get(0).unwrap(), Credential::::base_context()); + assert_eq!(credential.context.get(1).unwrap(), "https://www.w3.org/2018/credentials/examples/v1"); + assert_eq!(credential.id.unwrap(), "http://example.edu/credentials/3732"); + assert_eq!(credential.types.len(), 2); + assert_eq!(credential.types.get(0).unwrap(), Credential::::base_type()); + assert_eq!(credential.types.get(1).unwrap(), "UniversityDegreeCredential"); + assert_eq!(credential.credential_subject.len(), 1); + assert_eq!(credential.issuer.url(), "did:example:issuer"); + assert_eq!(credential.issuance_date.to_string(), "2010-01-01T00:00:00Z"); + assert_eq!(credential.credential_subject.get(0).unwrap().id.as_ref().unwrap(), "did:example:ebfeb1f712ebc6f1c276e12ec21"); + assert_eq!(credential.credential_subject.get(0).unwrap().properties["degree"]["type"], "BachelorDegree"); + assert_eq!(credential.credential_subject.get(0).unwrap().properties["degree"]["name"], "Bachelor of Science and Arts"); + } + + #[test] + #[should_panic = "MissingSubject"] + fn test_builder_missing_subjects() { + let _: Credential = Builder::default().issuer(issuer()).build().unwrap(); + } + + #[test] + #[should_panic = "MissingIssuer"] + fn test_builder_missing_issuer() { + let _: Credential = Builder::default().subject(subject()).build().unwrap(); + } +} diff --git a/identity-core/src/credential/credential.rs b/identity-credential/src/credential/credential.rs similarity index 65% rename from identity-core/src/credential/credential.rs rename to identity-credential/src/credential/credential.rs index 18400e7c95..9a469d48c9 100644 --- a/identity-core/src/credential/credential.rs +++ b/identity-credential/src/credential/credential.rs @@ -1,21 +1,37 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use core::fmt::{Display, Error as FmtError, Formatter, Result as FmtResult}; -use did_doc::{Document, LdSuite, MethodQuery, MethodType, MethodWrap, SignatureOptions}; +use core::fmt::Display; +use core::fmt::Error as FmtError; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use did_doc::Document; +use did_doc::LdSuite; +use did_doc::MethodQuery; +use did_doc::MethodType; +use did_doc::MethodWrap; +use did_doc::SignatureOptions; +use identity_core::common::Context; +use identity_core::common::Object; +use identity_core::common::OneOrMany; +use identity_core::common::Timestamp; +use identity_core::common::Url; +use identity_core::convert::ToJson; +use identity_core::crypto::SecretKey; +use identity_core::proof::JcsEd25519Signature2020; use serde::Serialize; -use crate::{ - common::{Context, Object, OneOrMany, Timestamp, Url}, - convert::ToJson as _, - credential::{ - CredentialBuilder, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, RefreshService, - TermsOfUse, VerifiableCredential, - }, - crypto::SecretKey, - error::{Error, Result}, - proof::JcsEd25519Signature2020, -}; +use crate::credential::Builder; +use crate::credential::Evidence; +use crate::credential::Issuer; +use crate::credential::Policy; +use crate::credential::Refresh; +use crate::credential::Schema; +use crate::credential::Status; +use crate::credential::Subject; +use crate::credential::VerifiableCredential; +use crate::error::Error; +use crate::error::Result; lazy_static! { static ref BASE_CONTEXT: Context = Context::Url(Url::parse("https://www.w3.org/2018/credentials/v1").unwrap()); @@ -37,7 +53,7 @@ pub struct Credential { pub types: OneOrMany, /// One or more `Object`s representing the `Credential` subject(s). #[serde(rename = "credentialSubject")] - pub credential_subject: OneOrMany, + pub credential_subject: OneOrMany, /// A reference to the issuer of the `Credential`. pub issuer: Issuer, /// A timestamp of when the `Credential` becomes valid. @@ -48,16 +64,16 @@ pub struct Credential { pub expiration_date: Option, /// Information used to determine the current status of the `Credential`. #[serde(default, rename = "credentialStatus", skip_serializing_if = "OneOrMany::is_empty")] - pub credential_status: OneOrMany, + pub credential_status: OneOrMany, /// Information used to assist in the enforcement of a specific `Credential` structure. #[serde(default, rename = "credentialSchema", skip_serializing_if = "OneOrMany::is_empty")] - pub credential_schema: OneOrMany, + pub credential_schema: OneOrMany, /// Service(s) used to refresh an expired `Credential`. #[serde(default, rename = "refreshService", skip_serializing_if = "OneOrMany::is_empty")] - pub refresh_service: OneOrMany, + pub refresh_service: OneOrMany, /// Terms-of-use specified by the `Credential` issuer. #[serde(default, rename = "termsOfUse", skip_serializing_if = "OneOrMany::is_empty")] - pub terms_of_use: OneOrMany, + pub terms_of_use: OneOrMany, /// Human-readable evidence used to support the claims within the `Credential`. #[serde(default, skip_serializing_if = "OneOrMany::is_empty")] pub evidence: OneOrMany, @@ -81,29 +97,27 @@ impl Credential { "VerifiableCredential" } - /// Creates a `CredentialBuilder` to configure a new `Credential`. + /// Creates a new `Builder` to configure a `Credential`. /// - /// This is the same as `CredentialBuilder::new()`. - pub fn builder(properties: T) -> CredentialBuilder { - CredentialBuilder::new(properties) + /// This is the same as `Builder::new()`. + pub fn builder(properties: T) -> Builder { + Builder::new(properties) } - /// Returns a new `Credential` based on the `CredentialBuilder` configuration. - pub fn from_builder(builder: CredentialBuilder) -> Result { + /// Returns a new `Credential` based on the `Builder` configuration. + pub fn from_builder(builder: Builder) -> Result { let this: Self = Self { context: builder.context.into(), id: builder.id, types: builder.types.into(), - credential_subject: builder.credential_subject.into(), - issuer: builder - .issuer - .ok_or_else(|| Error::InvalidCredential("Missing Credential Issuer".into()))?, + credential_subject: builder.subject.into(), + issuer: builder.issuer.ok_or(Error::MissingIssuer)?, issuance_date: builder.issuance_date.unwrap_or_default(), expiration_date: builder.expiration_date, - credential_status: builder.credential_status.into(), - credential_schema: builder.credential_schema.into(), - refresh_service: builder.refresh_service.into(), - terms_of_use: builder.terms_of_use.into(), + credential_status: builder.status.into(), + credential_schema: builder.schema.into(), + refresh_service: builder.refresh.into(), + terms_of_use: builder.policy.into(), evidence: builder.evidence.into(), non_transferable: builder.non_transferable, properties: builder.properties, @@ -119,23 +133,23 @@ impl Credential { // Ensure the base context is present and in the correct location match self.context.get(0) { Some(context) if context == Self::base_context() => {} - Some(_) | None => return Err(Error::InvalidCredential("Missing Base Context".into())), + Some(_) | None => return Err(Error::MissingBaseContext), } // The set of types MUST contain the base type if !self.types.iter().any(|type_| type_ == Self::base_type()) { - return Err(Error::InvalidCredential("Missing Base Type".into())); + return Err(Error::MissingBaseType); } // Credentials MUST have at least one subject if self.credential_subject.is_empty() { - return Err(Error::InvalidCredential("Missing Subject".into())); + return Err(Error::MissingSubject); } // Each subject is defined as one or more properties - no empty objects for subject in self.credential_subject.iter() { if subject.id.is_none() && subject.properties.is_empty() { - return Err(Error::InvalidCredential("Invalid Subject".into())); + return Err(Error::InvalidSubject); } } @@ -149,7 +163,7 @@ impl Credential { document: &Document, query: Q, secret: &SecretKey, - ) -> Result> + ) -> did_doc::Result> where T: Serialize, Q: Into>, @@ -167,7 +181,7 @@ impl Credential { Ok(verifiable) } - _ => Err(Error::InvalidCredential("Verification Method Not Supported".into())), + _ => Err(did_doc::Error::message("Verification Method Not Supported")), } } } @@ -187,20 +201,22 @@ where #[cfg(test)] mod tests { - use crate::{convert::FromJson as _, credential::Credential}; - - const JSON1: &str = include_str!("../../tests/fixtures/vc/credential-1.json"); - const JSON2: &str = include_str!("../../tests/fixtures/vc/credential-2.json"); - const JSON3: &str = include_str!("../../tests/fixtures/vc/credential-3.json"); - const JSON4: &str = include_str!("../../tests/fixtures/vc/credential-4.json"); - const JSON5: &str = include_str!("../../tests/fixtures/vc/credential-5.json"); - const JSON6: &str = include_str!("../../tests/fixtures/vc/credential-6.json"); - const JSON7: &str = include_str!("../../tests/fixtures/vc/credential-7.json"); - const JSON8: &str = include_str!("../../tests/fixtures/vc/credential-8.json"); - const JSON9: &str = include_str!("../../tests/fixtures/vc/credential-9.json"); - const JSON10: &str = include_str!("../../tests/fixtures/vc/credential-10.json"); - const JSON11: &str = include_str!("../../tests/fixtures/vc/credential-11.json"); - const JSON12: &str = include_str!("../../tests/fixtures/vc/credential-12.json"); + use identity_core::convert::FromJson; + + use crate::credential::Credential; + + const JSON1: &str = include_str!("../../tests/fixtures/credential-1.json"); + const JSON2: &str = include_str!("../../tests/fixtures/credential-2.json"); + const JSON3: &str = include_str!("../../tests/fixtures/credential-3.json"); + const JSON4: &str = include_str!("../../tests/fixtures/credential-4.json"); + const JSON5: &str = include_str!("../../tests/fixtures/credential-5.json"); + const JSON6: &str = include_str!("../../tests/fixtures/credential-6.json"); + const JSON7: &str = include_str!("../../tests/fixtures/credential-7.json"); + const JSON8: &str = include_str!("../../tests/fixtures/credential-8.json"); + const JSON9: &str = include_str!("../../tests/fixtures/credential-9.json"); + const JSON10: &str = include_str!("../../tests/fixtures/credential-10.json"); + const JSON11: &str = include_str!("../../tests/fixtures/credential-11.json"); + const JSON12: &str = include_str!("../../tests/fixtures/credential-12.json"); #[test] fn test_from_json() { diff --git a/identity-core/src/credential/types/evidence.rs b/identity-credential/src/credential/evidence.rs similarity index 55% rename from identity-core/src/credential/types/evidence.rs rename to identity-credential/src/credential/evidence.rs index 1b71bfcc09..6a49d5e086 100644 --- a/identity-core/src/credential/types/evidence.rs +++ b/identity-credential/src/credential/evidence.rs @@ -1,7 +1,8 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::common::{Object, OneOrMany}; +use identity_core::common::Object; +use identity_core::common::OneOrMany; /// Information used to increase confidence in the claims of a `Credential` /// @@ -69,28 +70,30 @@ impl Evidence { #[cfg(test)] mod tests { - use crate::{convert::FromJson as _, credential::Evidence}; + use identity_core::convert::FromJson; - const JSON1: &str = include_str!("../../../tests/fixtures/vc/evidence-1.json"); - const JSON2: &str = include_str!("../../../tests/fixtures/vc/evidence-2.json"); + use crate::credential::Evidence; + + const JSON1: &str = include_str!("../../tests/fixtures/evidence-1.json"); + const JSON2: &str = include_str!("../../tests/fixtures/evidence-2.json"); #[test] - #[rustfmt::skip] - fn test_from_json() { - let evidence: Evidence = Evidence::from_json(JSON1).unwrap(); - assert_eq!(evidence.id.unwrap(), "https://example.edu/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d4231"); - assert_eq!(evidence.types.as_slice(), ["DocumentVerification"]); - assert_eq!(evidence.properties["verifier"], "https://example.edu/issuers/14"); - assert_eq!(evidence.properties["evidenceDocument"], "DriversLicense"); - assert_eq!(evidence.properties["subjectPresence"], "Physical"); - assert_eq!(evidence.properties["documentPresence"], "Physical"); + #[rustfmt::skip] + fn test_from_json() { + let evidence: Evidence = Evidence::from_json(JSON1).unwrap(); + assert_eq!(evidence.id.unwrap(), "https://example.edu/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d4231"); + assert_eq!(evidence.types.as_slice(), ["DocumentVerification"]); + assert_eq!(evidence.properties["verifier"], "https://example.edu/issuers/14"); + assert_eq!(evidence.properties["evidenceDocument"], "DriversLicense"); + assert_eq!(evidence.properties["subjectPresence"], "Physical"); + assert_eq!(evidence.properties["documentPresence"], "Physical"); - let evidence: Evidence = Evidence::from_json(JSON2).unwrap(); - assert_eq!(evidence.id.unwrap(), "https://example.edu/evidence/f2aeec97-fc0d-42bf-8ca7-0548192dxyzab"); - assert_eq!(evidence.types.as_slice(), ["SupportingActivity"]); - assert_eq!(evidence.properties["verifier"], "https://example.edu/issuers/14"); - assert_eq!(evidence.properties["evidenceDocument"], "Fluid Dynamics Focus"); - assert_eq!(evidence.properties["subjectPresence"], "Digital"); - assert_eq!(evidence.properties["documentPresence"], "Digital"); - } + let evidence: Evidence = Evidence::from_json(JSON2).unwrap(); + assert_eq!(evidence.id.unwrap(), "https://example.edu/evidence/f2aeec97-fc0d-42bf-8ca7-0548192dxyzab"); + assert_eq!(evidence.types.as_slice(), ["SupportingActivity"]); + assert_eq!(evidence.properties["verifier"], "https://example.edu/issuers/14"); + assert_eq!(evidence.properties["evidenceDocument"], "Fluid Dynamics Focus"); + assert_eq!(evidence.properties["subjectPresence"], "Digital"); + assert_eq!(evidence.properties["documentPresence"], "Digital"); + } } diff --git a/identity-core/src/credential/types/issuer.rs b/identity-credential/src/credential/issuer.rs similarity index 62% rename from identity-core/src/credential/types/issuer.rs rename to identity-credential/src/credential/issuer.rs index a10f134040..2e469c34b5 100644 --- a/identity-core/src/credential/types/issuer.rs +++ b/identity-credential/src/credential/issuer.rs @@ -1,7 +1,8 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::common::{Object, Url}; +use identity_core::common::Object; +use identity_core::common::Url; /// A `Credential` issuer in object form. /// @@ -48,20 +49,21 @@ where #[cfg(test)] mod tests { - use crate::{convert::FromJson as _, credential::Issuer}; + use identity_core::convert::FromJson; - const JSON1: &str = include_str!("../../../tests/fixtures/vc/issuer-1.json"); - const JSON2: &str = include_str!("../../../tests/fixtures/vc/issuer-2.json"); + use crate::credential::Issuer; + + const JSON1: &str = include_str!("../../tests/fixtures/issuer-1.json"); + const JSON2: &str = include_str!("../../tests/fixtures/issuer-2.json"); #[test] - #[rustfmt::skip] - fn test_from_json() { - let issuer: Issuer = Issuer::from_json(JSON1).unwrap(); - assert!(matches!(issuer, Issuer::Url(_))); - assert_eq!(issuer.url(), "https://example.edu/issuers/14"); + fn test_from_json() { + let issuer: Issuer = Issuer::from_json(JSON1).unwrap(); + assert!(matches!(issuer, Issuer::Url(_))); + assert_eq!(issuer.url(), "https://example.edu/issuers/14"); - let issuer: Issuer = Issuer::from_json(JSON2).unwrap(); - assert!(matches!(issuer, Issuer::Obj(_))); - assert_eq!(issuer.url(), "did:example:76e12ec712ebc6f1c221ebfeb1f"); - } + let issuer: Issuer = Issuer::from_json(JSON2).unwrap(); + assert!(matches!(issuer, Issuer::Obj(_))); + assert_eq!(issuer.url(), "did:example:76e12ec712ebc6f1c221ebfeb1f"); + } } diff --git a/identity-credential/src/credential/mod.rs b/identity-credential/src/credential/mod.rs new file mode 100644 index 0000000000..08f0dbf87b --- /dev/null +++ b/identity-credential/src/credential/mod.rs @@ -0,0 +1,28 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! The core types used to create Verifiable Credentials + +#![allow(clippy::module_inception)] + +mod builder; +mod credential; +mod evidence; +mod issuer; +mod policy; +mod refresh; +mod schema; +mod status; +mod subject; +mod verifiable; + +pub use self::builder::Builder; +pub use self::credential::Credential; +pub use self::evidence::Evidence; +pub use self::issuer::Issuer; +pub use self::policy::Policy; +pub use self::refresh::Refresh; +pub use self::schema::Schema; +pub use self::status::Status; +pub use self::subject::Subject; +pub use self::verifiable::VerifiableCredential; diff --git a/identity-core/src/credential/types/terms_of_use.rs b/identity-credential/src/credential/policy.rs similarity index 79% rename from identity-core/src/credential/types/terms_of_use.rs rename to identity-credential/src/credential/policy.rs index bd2bfebc5f..3093571e18 100644 --- a/identity-core/src/credential/types/terms_of_use.rs +++ b/identity-credential/src/credential/policy.rs @@ -1,14 +1,16 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::common::{Object, OneOrMany, Url}; +use identity_core::common::Object; +use identity_core::common::OneOrMany; +use identity_core::common::Url; /// Information used to express obligations, prohibitions, and permissions about /// a `Credential` or `Presentation`. /// /// [More Info](https://www.w3.org/TR/vc-data-model/#terms-of-use) #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] -pub struct TermsOfUse { +pub struct Policy { /// The instance id of the credential terms-of-use. #[serde(skip_serializing_if = "Option::is_none")] pub id: Option, @@ -20,8 +22,8 @@ pub struct TermsOfUse { pub properties: Object, } -impl TermsOfUse { - /// Creates a new [`TermsOfUse`] instance. +impl Policy { + /// Creates a new [`Policy`] instance. pub fn new(types: T) -> Self where T: Into>, @@ -33,7 +35,7 @@ impl TermsOfUse { } } - /// Creates a new [`TermsOfUse`] instance with the given `id`. + /// Creates a new [`Policy`] instance with the given `id`. pub fn with_id(types: T, id: Url) -> Self where T: Into>, @@ -45,7 +47,7 @@ impl TermsOfUse { } } - /// Creates a new [`TermsOfUse`] instance with the given `properties`. + /// Creates a new [`Policy`] instance with the given `properties`. pub fn with_properties(types: T, properties: Object) -> Self where T: Into>, @@ -57,7 +59,7 @@ impl TermsOfUse { } } - /// Creates a new [`TermsOfUse`] instance with the given `id` and `properties`. + /// Creates a new [`Policy`] instance with the given `id` and `properties`. pub fn with_id_and_properties(types: T, id: Url, properties: Object) -> Self where T: Into>, @@ -72,15 +74,17 @@ impl TermsOfUse { #[cfg(test)] mod tests { - use crate::{convert::FromJson as _, credential::TermsOfUse}; + use identity_core::convert::FromJson; - const JSON1: &str = include_str!("../../../tests/fixtures/vc/terms-of-use-1.json"); - const JSON2: &str = include_str!("../../../tests/fixtures/vc/terms-of-use-2.json"); + use crate::credential::Policy; + + const JSON1: &str = include_str!("../../tests/fixtures/policy-1.json"); + const JSON2: &str = include_str!("../../tests/fixtures/policy-2.json"); #[test] #[rustfmt::skip] fn test_from_json() { - let policy: TermsOfUse = TermsOfUse::from_json(JSON1).unwrap(); + let policy: Policy = Policy::from_json(JSON1).unwrap(); assert_eq!(policy.id.unwrap(), "http://example.com/policies/credential/4"); assert_eq!(policy.types.as_slice(), ["IssuerPolicy"]); assert_eq!(policy.properties["profile"], "http://example.com/profiles/credential"); @@ -90,7 +94,7 @@ mod tests { assert_eq!(policy.properties["prohibition"][0]["action"][0], "Archival"); - let policy: TermsOfUse = TermsOfUse::from_json(JSON2).unwrap(); + let policy: Policy = Policy::from_json(JSON2).unwrap(); assert_eq!(policy.id.unwrap(), "http://example.com/policies/credential/6"); assert_eq!(policy.types.as_slice(), ["HolderPolicy"]); assert_eq!(policy.properties["profile"], "http://example.com/profiles/credential"); diff --git a/identity-core/src/credential/types/refresh_service.rs b/identity-credential/src/credential/refresh.rs similarity index 59% rename from identity-core/src/credential/types/refresh_service.rs rename to identity-credential/src/credential/refresh.rs index 267f31dbd8..0f214ebe50 100644 --- a/identity-core/src/credential/types/refresh_service.rs +++ b/identity-credential/src/credential/refresh.rs @@ -1,13 +1,15 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::common::{Object, OneOrMany, Url}; +use identity_core::common::Object; +use identity_core::common::OneOrMany; +use identity_core::common::Url; /// Information used to refresh or assert the status of a `Credential`. /// /// [More Info](https://www.w3.org/TR/vc-data-model/#refreshing) #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct RefreshService { +pub struct Refresh { /// The Url of the credential refresh service. pub id: Url, /// The type(s) of the credential refresh service. @@ -18,8 +20,8 @@ pub struct RefreshService { pub properties: Object, } -impl RefreshService { - /// Creates a new [`RefreshService`]. +impl Refresh { + /// Creates a new [`Refresh`]. pub fn new(id: Url, types: T) -> Self where T: Into>, @@ -27,7 +29,7 @@ impl RefreshService { Self::with_properties(id, types, Object::new()) } - /// Creates a new [`RefreshService`] with the given `properties`. + /// Creates a new [`Refresh`] with the given `properties`. pub fn with_properties(id: Url, types: T, properties: Object) -> Self where T: Into>, @@ -42,15 +44,16 @@ impl RefreshService { #[cfg(test)] mod tests { - use crate::{convert::FromJson as _, credential::RefreshService}; + use identity_core::convert::FromJson; - const JSON: &str = include_str!("../../../tests/fixtures/vc/refresh-service-1.json"); + use crate::credential::Refresh; + + const JSON: &str = include_str!("../../tests/fixtures/refresh-1.json"); #[test] - #[rustfmt::skip] - fn test_from_json() { - let service: RefreshService = RefreshService::from_json(JSON).unwrap(); - assert_eq!(service.id, "https://example.edu/refresh/3732"); - assert_eq!(service.types.as_slice(), ["ManualRefreshService2018"]); - } + fn test_from_json() { + let service: Refresh = Refresh::from_json(JSON).unwrap(); + assert_eq!(service.id, "https://example.edu/refresh/3732"); + assert_eq!(service.types.as_slice(), ["ManualRefreshService2018"]); + } } diff --git a/identity-credential/src/credential/schema.rs b/identity-credential/src/credential/schema.rs new file mode 100644 index 0000000000..8c1ce1bcfc --- /dev/null +++ b/identity-credential/src/credential/schema.rs @@ -0,0 +1,72 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Object; +use identity_core::common::OneOrMany; +use identity_core::common::Url; + +/// Information used to validate the structure of a `Credential`. +/// +/// [More Info](https://www.w3.org/TR/vc-data-model/#data-schemas) +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct Schema { + /// A Url identifying the credential schema file. + pub id: Url, + /// The type(s) of the credential schema. + #[serde(rename = "type")] + pub types: OneOrMany, + /// Additional properties of the credential schema. + #[serde(flatten)] + pub properties: Object, +} + +impl Schema { + /// Creates a new [`Schema`]. + pub fn new(id: Url, types: T) -> Self + where + T: Into>, + { + Self::with_properties(id, types, Object::new()) + } + + /// Creates a new [`Schema`] with the given `properties`. + pub fn with_properties(id: Url, types: T, properties: Object) -> Self + where + T: Into>, + { + Self { + id, + types: types.into(), + properties, + } + } +} + +#[cfg(test)] +mod tests { + use identity_core::convert::FromJson; + + use crate::credential::Schema; + + const JSON1: &str = include_str!("../../tests/fixtures/schema-1.json"); + const JSON2: &str = include_str!("../../tests/fixtures/schema-2.json"); + const JSON3: &str = include_str!("../../tests/fixtures/schema-3.json"); + + #[test] + fn test_from_json() { + let schema: Schema = Schema::from_json(JSON1).unwrap(); + assert_eq!(schema.id, "https://example.org/examples/degree.json"); + assert_eq!(schema.types.as_slice(), ["JsonSchemaValidator2018"]); + + let schema: Schema = Schema::from_json(JSON2).unwrap(); + assert_eq!(schema.id, "https://example.org/examples/degree.zkp"); + assert_eq!(schema.types.as_slice(), ["ZkpExampleSchema2018"]); + + let schema: Schema = Schema::from_json(JSON3).unwrap(); + assert_eq!(schema.id, "did:example:cdf:35LB7w9ueWbagPL94T9bMLtyXDj9pX5o"); + assert_eq!( + schema.types.as_slice(), + ["did:example:schema:22KpkXgecryx9k7N6XN1QoN3gXwBkSU8SfyyYQG"] + ); + } +} diff --git a/identity-core/src/credential/types/credential_status.rs b/identity-credential/src/credential/status.rs similarity index 58% rename from identity-core/src/credential/types/credential_status.rs rename to identity-credential/src/credential/status.rs index 0fc67344e8..98b4d06c85 100644 --- a/identity-core/src/credential/types/credential_status.rs +++ b/identity-credential/src/credential/status.rs @@ -1,13 +1,15 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::common::{Object, OneOrMany, Url}; +use identity_core::common::Object; +use identity_core::common::OneOrMany; +use identity_core::common::Url; /// Information used to determine the current status of a `Credential`. /// /// [More Info](https://www.w3.org/TR/vc-data-model/#status) #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct CredentialStatus { +pub struct Status { /// A Url identifying the credential status. pub id: Url, /// The type(s) of the credential status. @@ -18,8 +20,8 @@ pub struct CredentialStatus { pub properties: Object, } -impl CredentialStatus { - /// Creates a new [`CredentialStatus`]. +impl Status { + /// Creates a new [`Status`]. pub fn new(id: Url, types: T) -> Self where T: Into>, @@ -27,7 +29,7 @@ impl CredentialStatus { Self::with_properties(id, types, Object::new()) } - /// Creates a new [`CredentialStatus`] with the given `properties`. + /// Creates a new [`Status`] with the given `properties`. pub fn with_properties(id: Url, types: T, properties: Object) -> Self where T: Into>, @@ -42,15 +44,16 @@ impl CredentialStatus { #[cfg(test)] mod tests { - use crate::{convert::FromJson as _, credential::CredentialStatus}; + use identity_core::convert::FromJson; - const JSON: &str = include_str!("../../../tests/fixtures/vc/credential-status-1.json"); + use crate::credential::Status; + + const JSON: &str = include_str!("../../tests/fixtures/status-1.json"); #[test] - #[rustfmt::skip] - fn test_from_json() { - let status: CredentialStatus = CredentialStatus::from_json(JSON).unwrap(); - assert_eq!(status.id, "https://example.edu/status/24"); - assert_eq!(status.types.as_slice(), ["CredentialStatusList2017"]); - } + fn test_from_json() { + let status: Status = Status::from_json(JSON).unwrap(); + assert_eq!(status.id, "https://example.edu/status/24"); + assert_eq!(status.types.as_slice(), ["CredentialStatusList2017"]); + } } diff --git a/identity-credential/src/credential/subject.rs b/identity-credential/src/credential/subject.rs new file mode 100644 index 0000000000..e21e737413 --- /dev/null +++ b/identity-credential/src/credential/subject.rs @@ -0,0 +1,126 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Object; +use identity_core::common::Url; + +/// An entity who is the target of a set of claims. +/// +/// [More Info](https://www.w3.org/TR/vc-data-model/#credential-subject) +#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] +pub struct Subject { + /// A Url identifying the credential subject. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + /// Additional properties of the credential subject. + #[serde(flatten)] + pub properties: Object, +} + +impl Subject { + /// Creates a new [`Subject`]. + pub fn new() -> Self { + Self::with_properties(Object::new()) + } + + /// Creates a new [`Subject`] with the given `id`. + pub fn with_id(id: Url) -> Self { + Self::with_id_and_properties(id, Object::new()) + } + + /// Creates a new [`Subject`] with the given `properties`. + pub fn with_properties(properties: Object) -> Self { + Self { id: None, properties } + } + + /// Creates a new [`Subject`] with the given `id` and `properties`. + pub fn with_id_and_properties(id: Url, properties: Object) -> Self { + Self { + id: Some(id), + properties, + } + } +} + +#[cfg(test)] +mod tests { + use identity_core::convert::FromJson; + + use crate::credential::Subject; + + const JSON1: &str = include_str!("../../tests/fixtures/subject-1.json"); + const JSON2: &str = include_str!("../../tests/fixtures/subject-2.json"); + const JSON3: &str = include_str!("../../tests/fixtures/subject-3.json"); + const JSON4: &str = include_str!("../../tests/fixtures/subject-4.json"); + const JSON5: &str = include_str!("../../tests/fixtures/subject-5.json"); + const JSON6: &str = include_str!("../../tests/fixtures/subject-6.json"); + const JSON7: &str = include_str!("../../tests/fixtures/subject-7.json"); + const JSON8: &str = include_str!("../../tests/fixtures/subject-8.json"); + const JSON9: &str = include_str!("../../tests/fixtures/subject-9.json"); + const JSON10: &str = include_str!("../../tests/fixtures/subject-10.json"); + + #[test] + #[rustfmt::skip] + fn test_from_json() { + let subject: Subject = Subject::from_json(JSON1).unwrap(); + assert_eq!(subject.id.unwrap(), "did:example:ebfeb1f712ebc6f1c276e12ec21"); + assert_eq!(subject.properties["alumniOf"]["id"], "did:example:c276e12ec21ebfeb1f712ebc6f1"); + assert_eq!(subject.properties["alumniOf"]["name"][0]["value"], "Example University"); + assert_eq!(subject.properties["alumniOf"]["name"][0]["lang"], "en"); + assert_eq!(subject.properties["alumniOf"]["name"][1]["value"], "Exemple d'Université"); + assert_eq!(subject.properties["alumniOf"]["name"][1]["lang"], "fr"); + + let subject: Subject = Subject::from_json(JSON2).unwrap(); + assert_eq!(subject.id.unwrap(), "did:example:ebfeb1f712ebc6f1c276e12ec21"); + assert_eq!(subject.properties["degree"]["type"], "BachelorDegree"); + assert_eq!(subject.properties["degree"]["name"], "Bachelor of Science and Arts"); + + let subject: Subject = Subject::from_json(JSON3).unwrap(); + assert_eq!(subject.id.unwrap(), "did:example:abcdef1234567"); + assert_eq!(subject.properties["name"], "Jane Doe"); + + let subject: Subject = Subject::from_json(JSON4).unwrap(); + assert_eq!(subject.id.unwrap(), "did:example:abcdef1234567"); + assert_eq!(subject.properties["name"], "Jane Doe"); + assert_eq!(subject.properties["favoriteFood"], "Papaya"); + + let subject: Subject = Subject::from_json(JSON5).unwrap(); + assert_eq!(subject.properties["givenName"], "Jane"); + assert_eq!(subject.properties["familyName"], "Doe"); + assert_eq!(subject.properties["degree"]["type"], "BachelorDegree"); + assert_eq!(subject.properties["degree"]["name"], "Bachelor of Science and Arts"); + assert_eq!(subject.properties["degree"]["college"], "College of Engineering"); + + let subject: Subject = Subject::from_json(JSON6).unwrap(); + assert_eq!(subject.properties["degreeType"], "BachelorDegree"); + assert_eq!(subject.properties["degreeSchool"], "College of Engineering"); + + let subject: Subject = Subject::from_json(JSON7).unwrap(); + assert_eq!(subject.id.unwrap(), "http://example.com/credentials/245"); + assert_eq!(subject.properties["currentStatus"], "Disputed"); + assert_eq!(subject.properties["statusReason"]["value"], "Address is out of date."); + assert_eq!(subject.properties["statusReason"]["lang"], "en"); + + let subject: Subject = Subject::from_json(JSON8).unwrap(); + assert_eq!(subject.properties["degree"]["type"], "BachelorDegree"); + assert_eq!(subject.properties["degree"]["name"], "Bachelor of Science and Arts"); + + let subject: Subject = Subject::from_json(JSON9).unwrap(); + assert_eq!(subject.id.unwrap(), "did:example:ebfeb1f712ebc6f1c276e12ec21"); + assert_eq!(subject.properties["image"], "https://example.edu/images/58473"); + assert_eq!(subject.properties["alumniOf"]["id"], "did:example:c276e12ec21ebfeb1f712ebc6f1"); + assert_eq!(subject.properties["alumniOf"]["name"][0]["value"], "Example University"); + assert_eq!(subject.properties["alumniOf"]["name"][0]["lang"], "en"); + assert_eq!(subject.properties["alumniOf"]["name"][1]["value"], "Exemple d'Université"); + assert_eq!(subject.properties["alumniOf"]["name"][1]["lang"], "fr"); + + let subject: Subject = Subject::from_json(JSON10).unwrap(); + assert_eq!(subject.id.unwrap(), "did:example:ebfeb1f712ebc6f1c276e12ec21"); + assert_eq!(subject.properties["image"], "ipfs:/ipfs/QmXfrS3pHerg44zzK6QKQj6JDk8H6cMtQS7pdXbohwNQfK/image"); + assert_eq!(subject.properties["alumniOf"]["id"], "did:example:c276e12ec21ebfeb1f712ebc6f1"); + assert_eq!(subject.properties["alumniOf"]["name"][0]["value"], "Example University"); + assert_eq!(subject.properties["alumniOf"]["name"][0]["lang"], "en"); + assert_eq!(subject.properties["alumniOf"]["name"][1]["value"], "Exemple d'Université"); + assert_eq!(subject.properties["alumniOf"]["name"][1]["lang"], "fr"); + } +} diff --git a/identity-core/src/credential/verifiable_credential.rs b/identity-credential/src/credential/verifiable.rs similarity index 81% rename from identity-core/src/credential/verifiable_credential.rs rename to identity-credential/src/credential/verifiable.rs index 044e2724c9..76c118f53c 100644 --- a/identity-core/src/credential/verifiable_credential.rs +++ b/identity-credential/src/credential/verifiable.rs @@ -1,18 +1,22 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use core::{ - fmt::{Display, Error as FmtError, Formatter, Result as FmtResult}, - ops::{Deref, DerefMut}, -}; -use serde::{Deserialize, Serialize}; +use core::fmt::Display; +use core::fmt::Error as FmtError; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use core::ops::Deref; +use core::ops::DerefMut; +use did_doc::SetSignature; +use did_doc::Signature; +use did_doc::TrySignature; +use did_doc::TrySignatureMut; +use identity_core::common::Object; +use identity_core::common::OneOrMany; +use identity_core::convert::ToJson; +use serde::Serialize; -use crate::{ - common::{Object, OneOrMany}, - convert::ToJson as _, - credential::Credential, - did_doc::{SetSignature, Signature, TrySignature, TrySignatureMut}, -}; +use crate::credential::Credential; /// A `VerifiableCredential` represents a `Credential` with an associated /// digital proof. @@ -55,6 +59,12 @@ impl Deref for VerifiableCredential { } } +impl DerefMut for VerifiableCredential { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.credential + } +} + impl Display for VerifiableCredential where T: Serialize, @@ -68,12 +78,6 @@ where } } -impl DerefMut for VerifiableCredential { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.credential - } -} - impl TrySignature for VerifiableCredential { fn signature(&self) -> Option<&Signature> { self.proof.get(0) diff --git a/identity-credential/src/error.rs b/identity-credential/src/error.rs new file mode 100644 index 0000000000..b29e2f44a6 --- /dev/null +++ b/identity-credential/src/error.rs @@ -0,0 +1,27 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Errors that may occur when working with Verifiable Credentials. + +/// 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. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Caused when validating a Credential without a valid base context. + #[error("Missing Base Context")] + MissingBaseContext, + /// Caused when validating a Credential without a valid base type. + #[error("Missing Base Type")] + MissingBaseType, + /// Caused when validating a Credential without an issuer. + #[error("Missing Credential Issuer")] + MissingIssuer, + /// Caused when validating a Credential without a subject. + #[error("Missing Credential Subject")] + MissingSubject, + /// Caused when validating a Credential with a malformed subject. + #[error("Invalid Credential Subject")] + InvalidSubject, +} diff --git a/identity-credential/src/lib.rs b/identity-credential/src/lib.rs new file mode 100644 index 0000000000..3323af0bd8 --- /dev/null +++ b/identity-credential/src/lib.rs @@ -0,0 +1,27 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Types and traits for working with Verifiable Credentials/Presentations. + +#![warn( + missing_docs, + missing_crate_level_docs, + broken_intra_doc_links, + private_intra_doc_links, + private_doc_tests, + clippy::missing_safety_doc, + // clippy::missing_errors_doc +)] + +#[macro_use] +extern crate lazy_static; + +#[macro_use] +extern crate serde; + +pub mod credential; +pub mod error; +pub mod presentation; + +pub use self::error::Error; +pub use self::error::Result; diff --git a/identity-credential/src/presentation/builder.rs b/identity-credential/src/presentation/builder.rs new file mode 100644 index 0000000000..0ee4a3fde2 --- /dev/null +++ b/identity-credential/src/presentation/builder.rs @@ -0,0 +1,220 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Context; +use identity_core::common::Object; +use identity_core::common::Url; +use identity_core::common::Value; + +use crate::credential::Policy; +use crate::credential::Refresh; +use crate::credential::VerifiableCredential; +use crate::error::Result; +use crate::presentation::Presentation; + +/// A `Builder` is used to create a customized `Presentation`. +#[derive(Clone, Debug)] +pub struct Builder { + pub(crate) context: Vec, + pub(crate) id: Option, + pub(crate) types: Vec, + pub(crate) credentials: Vec>, + pub(crate) holder: Option, + pub(crate) refresh: Vec, + pub(crate) policy: Vec, + pub(crate) properties: T, +} + +impl Builder { + /// Creates a new `Builder`. + pub fn new(properties: T) -> Self { + Self { + context: vec![Presentation::::base_context().clone()], + id: None, + types: vec![Presentation::::base_type().into()], + credentials: Vec::new(), + holder: None, + refresh: Vec::new(), + policy: Vec::new(), + properties, + } + } + + /// Adds a value to the `Presentation` context set. + #[must_use] + pub fn context(mut self, value: impl Into) -> Self { + self.context.push(value.into()); + self + } + + /// Sets the value of the `Presentation` `id`. + #[must_use] + pub fn id(mut self, value: Url) -> Self { + self.id = Some(value); + self + } + + /// Adds a value to the `Presentation` type set. + #[must_use] + pub fn type_(mut self, value: impl Into) -> Self { + self.types.push(value.into()); + self + } + + /// Adds a value to the `verifiableCredential` set. + #[must_use] + pub fn credential(mut self, value: VerifiableCredential) -> Self { + self.credentials.push(value); + self + } + + /// Sets the value of the `Credential` `holder`. + #[must_use] + pub fn holder(mut self, value: Url) -> Self { + self.holder = Some(value); + self + } + + /// Adds a value to the `refreshService` set. + #[must_use] + pub fn refresh(mut self, value: Refresh) -> Self { + self.refresh.push(value); + self + } + + /// Adds a value to the `termsOfUse` set. + #[must_use] + pub fn policy(mut self, value: Policy) -> Self { + self.policy.push(value); + self + } + + /// Returns a new `Presentation` based on the `Builder` configuration. + pub fn build(self) -> Result> { + Presentation::from_builder(self) + } +} + +impl Builder { + /// Adds a new custom property to the `Presentation`. + #[must_use] + pub fn property(mut self, key: K, value: V) -> Self + where + K: Into, + V: Into, + { + self.properties.insert(key.into(), value.into()); + self + } + + /// Adds a series of custom properties to the `Presentation`. + #[must_use] + pub fn properties(mut self, iter: I) -> Self + where + I: IntoIterator, + K: Into, + V: Into, + { + self + .properties + .extend(iter.into_iter().map(|(k, v)| (k.into(), v.into()))); + self + } +} + +impl Default for Builder +where + T: Default, +{ + fn default() -> Self { + Self::new(T::default()) + } +} + +#[cfg(test)] +mod tests { + use did_doc::Document; + use did_doc::DocumentBuilder; + use did_doc::Method; + use did_doc::MethodBuilder; + use did_doc::MethodData; + use did_doc::MethodType; + use did_url::DID; + use serde_json::json; + use serde_json::Value; + + use identity_core::common::Object; + use identity_core::common::Url; + use identity_core::convert::FromJson; + use identity_core::crypto::KeyPair; + use identity_core::proof::JcsEd25519Signature2020; + use identity_core::utils::encode_b58; + + use crate::credential::Builder as CredentialBuilder; + use crate::credential::Credential; + use crate::credential::Subject; + use crate::credential::VerifiableCredential; + use crate::presentation::Builder; + use crate::presentation::Presentation; + + fn subject() -> Subject { + let json: Value = json!({ + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts" + } + }); + + Subject::from_json_value(json).unwrap() + } + + fn issuer() -> Url { + Url::parse("did:example:issuer").unwrap() + } + + #[test] + #[rustfmt::skip] + fn test_presentation_builder_valid() { + let keypair: KeyPair = JcsEd25519Signature2020::new_keypair(); + let controller: DID = "did:example:1234".parse().unwrap(); + + let method: Method = MethodBuilder::default() + .id(controller.join("#key-1").unwrap()) + .controller(controller.clone()) + .key_type(MethodType::Ed25519VerificationKey2018) + .key_data(MethodData::PublicKeyBase58(encode_b58(keypair.public()))) + .build() + .unwrap(); + + let document: Document = DocumentBuilder::default() + .id(controller) + .verification_method(method) + .build() + .unwrap(); + + let credential: VerifiableCredential = CredentialBuilder::default() + .type_("ExampleCredential") + .subject(subject()) + .issuer(issuer()) + .build() + .unwrap() + .sign(&document, 0, keypair.secret()) + .unwrap(); + + let presentation: Presentation = Builder::default() + .type_("ExamplePresentation") + .credential(credential) + .build() + .unwrap(); + + assert_eq!(presentation.context.len(), 1); + assert_eq!(presentation.context.get(0).unwrap(), Presentation::::base_context()); + assert_eq!(presentation.types.len(), 2); + assert_eq!(presentation.types.get(0).unwrap(), Presentation::::base_type()); + assert_eq!(presentation.types.get(1).unwrap(), "ExamplePresentation"); + assert_eq!(presentation.verifiable_credential.len(), 1); + assert_eq!(presentation.verifiable_credential.get(0).unwrap().types.get(0).unwrap(), Credential::::base_type()); + assert_eq!(presentation.verifiable_credential.get(0).unwrap().types.get(1).unwrap(), "ExampleCredential"); + } +} diff --git a/identity-credential/src/presentation/mod.rs b/identity-credential/src/presentation/mod.rs new file mode 100644 index 0000000000..9053d1b938 --- /dev/null +++ b/identity-credential/src/presentation/mod.rs @@ -0,0 +1,14 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! The core types used to create Verifiable Presentations + +#![allow(clippy::module_inception)] + +mod builder; +mod presentation; +mod verifiable; + +pub use self::builder::Builder; +pub use self::presentation::Presentation; +pub use self::verifiable::VerifiablePresentation; diff --git a/identity-credential/src/presentation/presentation.rs b/identity-credential/src/presentation/presentation.rs new file mode 100644 index 0000000000..2b91e27de0 --- /dev/null +++ b/identity-credential/src/presentation/presentation.rs @@ -0,0 +1,159 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::fmt::Display; +use core::fmt::Error as FmtError; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use identity_core::common::Context; +use identity_core::common::Object; +use identity_core::common::OneOrMany; +use identity_core::common::Url; +use identity_core::convert::ToJson; +use serde::Serialize; + +use crate::credential::Credential; +use crate::credential::Policy; +use crate::credential::Refresh; +use crate::credential::VerifiableCredential; +use crate::error::Error; +use crate::error::Result; +use crate::presentation::Builder; + +/// A `Presentation` represents a bundle of one or more `VerifiableCredential`s. +/// +/// `Presentation`s can be signed with `Document`s to create `VerifiablePresentation`s. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct Presentation { + /// The JSON-LD context(s) applicable to the `Presentation`. + #[serde(rename = "@context")] + pub context: OneOrMany, + /// A unique `URI` referencing the subject of the `Presentation`. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + /// One or more URIs defining the type of the `Presentation`. + #[serde(rename = "type")] + pub types: OneOrMany, + /// Credential(s) expressing the claims of the `Presentation`. + #[serde(default = "Default::default", rename = "verifiableCredential")] + pub verifiable_credential: OneOrMany>, + /// The entity that generated the presentation. + #[serde(skip_serializing_if = "Option::is_none")] + pub holder: Option, + /// Service(s) used to refresh an expired `Presentation`. + #[serde(default, rename = "refreshService", skip_serializing_if = "OneOrMany::is_empty")] + pub refresh_service: OneOrMany, + /// Terms-of-use specified by the `Presentation` holder. + #[serde(default, rename = "termsOfUse", skip_serializing_if = "OneOrMany::is_empty")] + pub terms_of_use: OneOrMany, + /// Miscellaneous properties. + #[serde(flatten)] + pub properties: T, +} + +impl Presentation { + /// Returns the base JSON-LD context for `Presentation`s. + pub fn base_context() -> &'static Context { + Credential::::base_context() + } + + /// Returns the base type for `Presentation`s. + pub const fn base_type() -> &'static str { + "VerifiablePresentation" + } + + /// Creates a `Builder` to configure a new `Presentation`. + /// + /// This is the same as `Builder::new()`. + pub fn builder(properties: T) -> Builder { + Builder::new(properties) + } + + /// Returns a new `Presentation` based on the `Builder` configuration. + pub fn from_builder(builder: Builder) -> Result { + let this: Self = Self { + context: builder.context.into(), + id: builder.id, + types: builder.types.into(), + verifiable_credential: builder.credentials.into(), + holder: builder.holder, + refresh_service: builder.refresh.into(), + terms_of_use: builder.policy.into(), + properties: builder.properties, + }; + + this.check_structure()?; + + Ok(this) + } + + /// Validates the semantic structure of the `Presentation`. + pub fn check_structure(&self) -> Result<()> { + // Ensure the base context is present and in the correct location + match self.context.get(0) { + Some(context) if context == Self::base_context() => {} + Some(_) | None => return Err(Error::MissingBaseContext), + } + + // The set of types MUST contain the base type + if !self.types.iter().any(|type_| type_ == Self::base_type()) { + return Err(Error::MissingBaseType); + } + + // Check all credentials. + for credential in self.verifiable_credential.iter() { + credential.check_structure()?; + } + + Ok(()) + } +} + +impl Display for Presentation +where + T: Serialize, + U: Serialize, +{ + fn fmt(&self, f: &mut Formatter) -> FmtResult { + if f.alternate() { + f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) + } else { + f.write_str(&self.to_json().map_err(|_| FmtError)?) + } + } +} + +#[cfg(test)] +mod tests { + use identity_core::convert::FromJson; + + use crate::credential::Subject; + use crate::credential::VerifiableCredential; + use crate::presentation::VerifiablePresentation; + + const JSON: &str = include_str!("../../tests/fixtures/presentation-1.json"); + + #[test] + #[rustfmt::skip] + fn test_from_json() { + let presentation: VerifiablePresentation = VerifiablePresentation::from_json(JSON).unwrap(); + let credential: &VerifiableCredential = presentation.verifiable_credential.get(0).unwrap(); + let subject: &Subject = credential.credential_subject.get(0).unwrap(); + + assert_eq!(presentation.context.as_slice(), ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"]); + assert_eq!(presentation.id.as_ref().unwrap(), "urn:uuid:3978344f-8596-4c3a-a978-8fcaba3903c5"); + assert_eq!(presentation.types.as_slice(), ["VerifiablePresentation", "CredentialManagerPresentation"]); + assert_eq!(presentation.proof().get(0).unwrap().type_(), "RsaSignature2018"); + + assert_eq!(credential.context.as_slice(), ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"]); + assert_eq!(credential.id.as_ref().unwrap(), "http://example.edu/credentials/3732"); + assert_eq!(credential.types.as_slice(), ["VerifiableCredential", "UniversityDegreeCredential"]); + assert_eq!(credential.issuer.url(), "https://example.edu/issuers/14"); + assert_eq!(credential.issuance_date, "2010-01-01T19:23:24Z".parse().unwrap()); + assert_eq!(credential.proof().get(0).unwrap().type_(), "RsaSignature2018"); + + assert_eq!(subject.id.as_ref().unwrap(), "did:example:ebfeb1f712ebc6f1c276e12ec21"); + assert_eq!(subject.properties["degree"]["type"], "BachelorDegree"); + assert_eq!(subject.properties["degree"]["name"], "Bachelor of Science in Mechanical Engineering"); + } +} diff --git a/identity-core/src/credential/verifiable_presentation.rs b/identity-credential/src/presentation/verifiable.rs similarity index 82% rename from identity-core/src/credential/verifiable_presentation.rs rename to identity-credential/src/presentation/verifiable.rs index 4224036552..76c1bed3e4 100644 --- a/identity-core/src/credential/verifiable_presentation.rs +++ b/identity-credential/src/presentation/verifiable.rs @@ -1,18 +1,22 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use core::{ - fmt::{Display, Error as FmtError, Formatter, Result as FmtResult}, - ops::{Deref, DerefMut}, -}; -use serde::{Deserialize, Serialize}; +use core::fmt::Display; +use core::fmt::Error as FmtError; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use core::ops::Deref; +use core::ops::DerefMut; +use did_doc::SetSignature; +use did_doc::Signature; +use did_doc::TrySignature; +use did_doc::TrySignatureMut; +use identity_core::common::Object; +use identity_core::common::OneOrMany; +use identity_core::convert::ToJson; +use serde::Serialize; -use crate::{ - common::{Object, OneOrMany}, - convert::ToJson as _, - credential::Presentation, - did_doc::{SetSignature, Signature, TrySignature, TrySignatureMut}, -}; +use crate::presentation::Presentation; /// A `VerifiablePresentation` represents a `Presentation` with an associated /// digital proof. diff --git a/identity-core/tests/fixtures/vc/credential-1.json b/identity-credential/tests/fixtures/credential-1.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-1.json rename to identity-credential/tests/fixtures/credential-1.json diff --git a/identity-core/tests/fixtures/vc/credential-10.json b/identity-credential/tests/fixtures/credential-10.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-10.json rename to identity-credential/tests/fixtures/credential-10.json diff --git a/identity-core/tests/fixtures/vc/credential-11.json b/identity-credential/tests/fixtures/credential-11.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-11.json rename to identity-credential/tests/fixtures/credential-11.json diff --git a/identity-core/tests/fixtures/vc/credential-12.json b/identity-credential/tests/fixtures/credential-12.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-12.json rename to identity-credential/tests/fixtures/credential-12.json diff --git a/identity-core/tests/fixtures/vc/credential-2.json b/identity-credential/tests/fixtures/credential-2.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-2.json rename to identity-credential/tests/fixtures/credential-2.json diff --git a/identity-core/tests/fixtures/vc/credential-3.json b/identity-credential/tests/fixtures/credential-3.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-3.json rename to identity-credential/tests/fixtures/credential-3.json diff --git a/identity-core/tests/fixtures/vc/credential-4.json b/identity-credential/tests/fixtures/credential-4.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-4.json rename to identity-credential/tests/fixtures/credential-4.json diff --git a/identity-core/tests/fixtures/vc/credential-5.json b/identity-credential/tests/fixtures/credential-5.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-5.json rename to identity-credential/tests/fixtures/credential-5.json diff --git a/identity-core/tests/fixtures/vc/credential-6.json b/identity-credential/tests/fixtures/credential-6.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-6.json rename to identity-credential/tests/fixtures/credential-6.json diff --git a/identity-core/tests/fixtures/vc/credential-7.json b/identity-credential/tests/fixtures/credential-7.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-7.json rename to identity-credential/tests/fixtures/credential-7.json diff --git a/identity-core/tests/fixtures/vc/credential-8.json b/identity-credential/tests/fixtures/credential-8.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-8.json rename to identity-credential/tests/fixtures/credential-8.json diff --git a/identity-core/tests/fixtures/vc/credential-9.json b/identity-credential/tests/fixtures/credential-9.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-9.json rename to identity-credential/tests/fixtures/credential-9.json diff --git a/identity-core/tests/fixtures/vc/evidence-1.json b/identity-credential/tests/fixtures/evidence-1.json similarity index 100% rename from identity-core/tests/fixtures/vc/evidence-1.json rename to identity-credential/tests/fixtures/evidence-1.json diff --git a/identity-core/tests/fixtures/vc/evidence-2.json b/identity-credential/tests/fixtures/evidence-2.json similarity index 100% rename from identity-core/tests/fixtures/vc/evidence-2.json rename to identity-credential/tests/fixtures/evidence-2.json diff --git a/identity-core/tests/fixtures/vc/issuer-1.json b/identity-credential/tests/fixtures/issuer-1.json similarity index 100% rename from identity-core/tests/fixtures/vc/issuer-1.json rename to identity-credential/tests/fixtures/issuer-1.json diff --git a/identity-core/tests/fixtures/vc/issuer-2.json b/identity-credential/tests/fixtures/issuer-2.json similarity index 100% rename from identity-core/tests/fixtures/vc/issuer-2.json rename to identity-credential/tests/fixtures/issuer-2.json diff --git a/identity-core/tests/fixtures/vc/terms-of-use-1.json b/identity-credential/tests/fixtures/policy-1.json similarity index 100% rename from identity-core/tests/fixtures/vc/terms-of-use-1.json rename to identity-credential/tests/fixtures/policy-1.json diff --git a/identity-core/tests/fixtures/vc/terms-of-use-2.json b/identity-credential/tests/fixtures/policy-2.json similarity index 100% rename from identity-core/tests/fixtures/vc/terms-of-use-2.json rename to identity-credential/tests/fixtures/policy-2.json diff --git a/identity-core/tests/fixtures/vc/presentation-1.json b/identity-credential/tests/fixtures/presentation-1.json similarity index 100% rename from identity-core/tests/fixtures/vc/presentation-1.json rename to identity-credential/tests/fixtures/presentation-1.json diff --git a/identity-core/tests/fixtures/vc/refresh-service-1.json b/identity-credential/tests/fixtures/refresh-1.json similarity index 100% rename from identity-core/tests/fixtures/vc/refresh-service-1.json rename to identity-credential/tests/fixtures/refresh-1.json diff --git a/identity-core/tests/fixtures/vc/credential-schema-1.json b/identity-credential/tests/fixtures/schema-1.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-schema-1.json rename to identity-credential/tests/fixtures/schema-1.json diff --git a/identity-core/tests/fixtures/vc/credential-schema-2.json b/identity-credential/tests/fixtures/schema-2.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-schema-2.json rename to identity-credential/tests/fixtures/schema-2.json diff --git a/identity-core/tests/fixtures/vc/credential-schema-3.json b/identity-credential/tests/fixtures/schema-3.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-schema-3.json rename to identity-credential/tests/fixtures/schema-3.json diff --git a/identity-core/tests/fixtures/vc/credential-status-1.json b/identity-credential/tests/fixtures/status-1.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-status-1.json rename to identity-credential/tests/fixtures/status-1.json diff --git a/identity-core/tests/fixtures/vc/credential-subject-1.json b/identity-credential/tests/fixtures/subject-1.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-subject-1.json rename to identity-credential/tests/fixtures/subject-1.json diff --git a/identity-core/tests/fixtures/vc/credential-subject-10.json b/identity-credential/tests/fixtures/subject-10.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-subject-10.json rename to identity-credential/tests/fixtures/subject-10.json diff --git a/identity-core/tests/fixtures/vc/credential-subject-2.json b/identity-credential/tests/fixtures/subject-2.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-subject-2.json rename to identity-credential/tests/fixtures/subject-2.json diff --git a/identity-core/tests/fixtures/vc/credential-subject-3.json b/identity-credential/tests/fixtures/subject-3.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-subject-3.json rename to identity-credential/tests/fixtures/subject-3.json diff --git a/identity-core/tests/fixtures/vc/credential-subject-4.json b/identity-credential/tests/fixtures/subject-4.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-subject-4.json rename to identity-credential/tests/fixtures/subject-4.json diff --git a/identity-core/tests/fixtures/vc/credential-subject-5.json b/identity-credential/tests/fixtures/subject-5.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-subject-5.json rename to identity-credential/tests/fixtures/subject-5.json diff --git a/identity-core/tests/fixtures/vc/credential-subject-6.json b/identity-credential/tests/fixtures/subject-6.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-subject-6.json rename to identity-credential/tests/fixtures/subject-6.json diff --git a/identity-core/tests/fixtures/vc/credential-subject-7.json b/identity-credential/tests/fixtures/subject-7.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-subject-7.json rename to identity-credential/tests/fixtures/subject-7.json diff --git a/identity-core/tests/fixtures/vc/credential-subject-8.json b/identity-credential/tests/fixtures/subject-8.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-subject-8.json rename to identity-credential/tests/fixtures/subject-8.json diff --git a/identity-core/tests/fixtures/vc/credential-subject-9.json b/identity-credential/tests/fixtures/subject-9.json similarity index 100% rename from identity-core/tests/fixtures/vc/credential-subject-9.json rename to identity-credential/tests/fixtures/subject-9.json diff --git a/identity-iota/Cargo.toml b/identity-iota/Cargo.toml index f127f109b4..98c3dca731 100644 --- a/identity-iota/Cargo.toml +++ b/identity-iota/Cargo.toml @@ -14,6 +14,7 @@ homepage = "https://www.iota.org" anyhow = { version = "1.0", default-features = false, features = ["std"] } async-trait = { version = "0.1", default-features = false } identity-core = { version = "=0.1.0", path = "../identity-core" } +identity-credential = { version = "=0.1.0", path = "../identity-credential" } iota-constants = { version = "0.2", default-features = false } iota-conversion = { version = "0.5", default-features = false } iota-core = { git = "https://github.com/Thoralf-M/iota.rs", rev = "d7c8c64fc3ac2340f0148708a916c245f42fd454" } diff --git a/identity-iota/src/credential/validator.rs b/identity-iota/src/credential/validator.rs index 9d621ef41a..bfd09d4b21 100644 --- a/identity-iota/src/credential/validator.rs +++ b/identity-iota/src/credential/validator.rs @@ -1,19 +1,15 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use identity_core::{ - common::Object, - convert::FromJson as _, - credential::{VerifiableCredential, VerifiablePresentation}, - error::Error, -}; +use identity_core::{common::Object, convert::FromJson as _}; +use identity_credential::{credential::VerifiableCredential, presentation::VerifiablePresentation}; use serde::{de::DeserializeOwned, Serialize}; use std::collections::BTreeMap; use crate::{ client::Client, did::{IotaDID, IotaDocument}, - error::Result, + error::{Error, Result}, }; #[derive(Clone, Debug, PartialEq, Serialize)] @@ -116,7 +112,7 @@ impl<'a> CredentialValidator<'a> { .holder .as_ref() .map(|holder| holder.as_str()) - .ok_or_else(|| Error::InvalidPresentation("Presentation missing `holder`".into()))?; + .ok_or(Error::InvalidPresentationHolder)?; let holder: DocumentValidation = self.validate_document(holder).await?; let verified: bool = holder.document.verify_data(&presentation).is_ok(); diff --git a/identity-iota/src/error.rs b/identity-iota/src/error.rs index 6c33bb3190..3cfed1192a 100644 --- a/identity-iota/src/error.rs +++ b/identity-iota/src/error.rs @@ -9,6 +9,8 @@ pub enum Error { CoreError(#[from] identity_core::Error), #[error("Diff Error: {0}")] DiffError(#[from] identity_core::identity_diff::Error), + #[error("Credential Error: {0}")] + CredError(#[from] identity_credential::Error), #[error("Invalid DID: {0}")] InvalidDID(#[from] identity_core::did_url::Error), #[error("Invalid Document: {0}")] @@ -31,6 +33,8 @@ pub enum Error { InvalidTransactionTrytes, #[error("Invalid Bundle Tail")] InvalidBundleTail, + #[error("Invalid PResentation Holder")] + InvalidPresentationHolder, #[error("Chain Error: {error}")] ChainError { error: &'static str }, } From 930a1386635f7ff91025c3c94b352bbe3e569cfb Mon Sep 17 00:00:00 2001 From: l1h3r Date: Mon, 1 Feb 2021 14:26:45 -0800 Subject: [PATCH 04/28] formatting --- .../src/proof/jcsed25519signature2020.rs | 2 +- identity-iota/src/tangle/message_id.rs | 26 +++--- identity-iota/src/tangle/message_index.rs | 84 +++++++++---------- 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/identity-core/src/proof/jcsed25519signature2020.rs b/identity-core/src/proof/jcsed25519signature2020.rs index 24518f37ae..f891dbd0f1 100644 --- a/identity-core/src/proof/jcsed25519signature2020.rs +++ b/identity-core/src/proof/jcsed25519signature2020.rs @@ -211,7 +211,7 @@ mod tests { const SECRET_B58: &str = "3qsrFcQqVuPpuGrRkU4wkQRvw1tc1C5EmEDPioS1GzQ2pLoThy5TYS2BsrwuzHYDnVqcYhMSpDhTXGst6H5ttFkG"; #[rustfmt::skip] - const SIGNATURE_HELLO: &[u8] = &[12, 203, 235, 144, 80, 6, 163, 39, 181, 17, 44, 123, 250, 162, 165, 145, 135, 132, 32, 152, 24, 168, 55, 80, 84, 139, 153, 101, 102, 27, 157, 29, 70, 124, 64, 120, 250, 172, 186, 163, 108, 27, 208, 248, 134, 115, 3, 154, 222, 165, 31, 93, 33, 108, 212, 92, 191, 14, 21, 40, 251, 103, 241, 10, 104, 101, 108, 108, 111]; + const SIGNATURE_HELLO: &[u8] = &[12, 203, 235, 144, 80, 6, 163, 39, 181, 17, 44, 123, 250, 162, 165, 145, 135, 132, 32, 152, 24, 168, 55, 80, 84, 139, 153, 101, 102, 27, 157, 29, 70, 124, 64, 120, 250, 172, 186, 163, 108, 27, 208, 248, 134, 115, 3, 154, 222, 165, 31, 93, 33, 108, 212, 92, 191, 14, 21, 40, 251, 103, 241, 10, 104, 101, 108, 108, 111]; const SIGNATURE_DOCUMENT: &str = "piKnvB438vWsinW1dqq2EYRzcYFuR7Qm9X8t2S6TPPLDokLwcFBXnnERk6jmS8RXKTJnXKWw1Q9oNhYTwbR7vJkaJT8ZGgwDHNxa6mrMNsQsWkM4rg6EYY99xQko7FnpAMn"; diff --git a/identity-iota/src/tangle/message_id.rs b/identity-iota/src/tangle/message_id.rs index 14839714d5..9dfdcfa8b4 100644 --- a/identity-iota/src/tangle/message_id.rs +++ b/identity-iota/src/tangle/message_id.rs @@ -79,17 +79,17 @@ mod tests { use super::*; #[test] - #[rustfmt::skip] - fn test_new() { - // Valid - assert!(MessageId::new("BCZVFSZPSMLPEQUXVWQQIHHQJJXZPZWRERJVBKKXHKMJAQJUN9OIXDBKMWFSAQIGC9YNXCCFOFKBQZ999").is_some()); - assert!(MessageId::new("999999999999999999999999999999999999999999999999999999999999999999999999999999999").is_some()); - - // Invalid - assert!(MessageId::new("").is_none()); - assert!(MessageId::new(" ").is_none()); - assert!(MessageId::new("- - - - - - -").is_none()); - assert!(MessageId::new("999999999999999999-999999999999999999999999999-9999999999999999999999-99999999999").is_none()); - assert!(MessageId::new("BCZVFSZPSMLPEQUXVWQQIHHQJJXZPZWRERJVBKKXHKMJAQJUN9OIXDBKMWFSAQIGC9YNXCCFOFKBQZ").is_none()); - } + #[rustfmt::skip] + fn test_new() { + // Valid + assert!(MessageId::new("BCZVFSZPSMLPEQUXVWQQIHHQJJXZPZWRERJVBKKXHKMJAQJUN9OIXDBKMWFSAQIGC9YNXCCFOFKBQZ999").is_some()); + assert!(MessageId::new("999999999999999999999999999999999999999999999999999999999999999999999999999999999").is_some()); + + // Invalid + assert!(MessageId::new("").is_none()); + assert!(MessageId::new(" ").is_none()); + assert!(MessageId::new("- - - - - - -").is_none()); + assert!(MessageId::new("999999999999999999-999999999999999999999999999-9999999999999999999999-99999999999").is_none()); + assert!(MessageId::new("BCZVFSZPSMLPEQUXVWQQIHHQJJXZPZWRERJVBKKXHKMJAQJUN9OIXDBKMWFSAQIGC9YNXCCFOFKBQZ").is_none()); + } } diff --git a/identity-iota/src/tangle/message_index.rs b/identity-iota/src/tangle/message_index.rs index 868bb37bed..94d59279ac 100644 --- a/identity-iota/src/tangle/message_index.rs +++ b/identity-iota/src/tangle/message_index.rs @@ -143,55 +143,55 @@ mod tests { } #[rustfmt::skip] - fn setup() -> MessageIndex { - let cases: Vec = vec![ - Case::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999A", "", true), - Case::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999B", "99999999999999999999999999999999999999999999999999999999999999999999999999999999A", false), - Case::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999C", "99999999999999999999999999999999999999999999999999999999999999999999999999999999A", true), - Case::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999D", "99999999999999999999999999999999999999999999999999999999999999999999999999999999C", false), - Case::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999E", "99999999999999999999999999999999999999999999999999999999999999999999999999999999B", false), - Case::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999F", "99999999999999999999999999999999999999999999999999999999999999999999999999999999B", true), - ]; - - let mut index: MessageIndex = MessageIndex::new(); - index.extend(cases); - index - } + fn setup() -> MessageIndex { + let cases: Vec = vec![ + Case::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999A", "", true), + Case::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999B", "99999999999999999999999999999999999999999999999999999999999999999999999999999999A", false), + Case::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999C", "99999999999999999999999999999999999999999999999999999999999999999999999999999999A", true), + Case::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999D", "99999999999999999999999999999999999999999999999999999999999999999999999999999999C", false), + Case::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999E", "99999999999999999999999999999999999999999999999999999999999999999999999999999999B", false), + Case::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999F", "99999999999999999999999999999999999999999999999999999999999999999999999999999999B", true), + ]; + + let mut index: MessageIndex = MessageIndex::new(); + index.extend(cases); + index + } #[test] - #[rustfmt::skip] - fn test_works() { - let index: MessageIndex = setup(); - - assert_eq!(index.size(), 6); - assert_eq!(index[&MessageId::new("")].len(), 1); - assert_eq!(index[&MessageId::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999A")].len(), 2); - assert_eq!(index[&MessageId::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999B")].len(), 2); - assert_eq!(index[&MessageId::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999C")].len(), 1); - } + #[rustfmt::skip] + fn test_works() { + let index: MessageIndex = setup(); + + assert_eq!(index.size(), 6); + assert_eq!(index[&MessageId::new("")].len(), 1); + assert_eq!(index[&MessageId::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999A")].len(), 2); + assert_eq!(index[&MessageId::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999B")].len(), 2); + assert_eq!(index[&MessageId::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999C")].len(), 1); + } #[test] - #[rustfmt::skip] - fn test_remove_where() { - let mut index: MessageIndex = setup(); + #[rustfmt::skip] + fn test_remove_where() { + let mut index: MessageIndex = setup(); - let removed: Case = index.remove_where(&MessageId::new(""), |_| true).unwrap(); - assert_eq!(removed.message_id, "99999999999999999999999999999999999999999999999999999999999999999999999999999999A"); - assert_eq!(removed.previous_message_id, MessageId::NONE); - assert!(index.remove_where(&MessageId::new(""), |_| true).is_none()); + let removed: Case = index.remove_where(&MessageId::new(""), |_| true).unwrap(); + assert_eq!(removed.message_id, "99999999999999999999999999999999999999999999999999999999999999999999999999999999A"); + assert_eq!(removed.previous_message_id, MessageId::NONE); + assert!(index.remove_where(&MessageId::new(""), |_| true).is_none()); - let first: Case = index - .remove_where(&MessageId::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999B"), |case| !case.state) - .unwrap(); + let first: Case = index + .remove_where(&MessageId::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999B"), |case| !case.state) + .unwrap(); - assert_eq!(first.message_id, "99999999999999999999999999999999999999999999999999999999999999999999999999999999E"); - assert_eq!(first.previous_message_id, "99999999999999999999999999999999999999999999999999999999999999999999999999999999B"); + assert_eq!(first.message_id, "99999999999999999999999999999999999999999999999999999999999999999999999999999999E"); + assert_eq!(first.previous_message_id, "99999999999999999999999999999999999999999999999999999999999999999999999999999999B"); - let second: Case = index - .remove_where(&MessageId::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999B"), |_| true) - .unwrap(); + let second: Case = index + .remove_where(&MessageId::new("99999999999999999999999999999999999999999999999999999999999999999999999999999999B"), |_| true) + .unwrap(); - assert_eq!(second.message_id, "99999999999999999999999999999999999999999999999999999999999999999999999999999999F"); - assert_eq!(second.previous_message_id, "99999999999999999999999999999999999999999999999999999999999999999999999999999999B"); - } + assert_eq!(second.message_id, "99999999999999999999999999999999999999999999999999999999999999999999999999999999F"); + assert_eq!(second.previous_message_id, "99999999999999999999999999999999999999999999999999999999999999999999999999999999B"); + } } From 5188f4a557817d74566f2038ebf520ce6567b2c4 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Mon, 1 Feb 2021 14:32:48 -0800 Subject: [PATCH 05/28] Fix cargo run --example NAME --- Cargo.toml | 4 +--- examples/Cargo.toml | 24 +++++++++++++++++++ .../{credential/src/main.rs => credential.rs} | 3 +++ examples/credential/Cargo.toml | 13 ---------- examples/diff-chain/Cargo.toml | 12 ---------- .../{diff-chain/src/main.rs => diff_chain.rs} | 3 +++ .../{resolution/src/main.rs => resolution.rs} | 3 +++ examples/resolution/Cargo.toml | 12 ---------- 8 files changed, 34 insertions(+), 40 deletions(-) create mode 100644 examples/Cargo.toml rename examples/{credential/src/main.rs => credential.rs} (98%) delete mode 100644 examples/credential/Cargo.toml delete mode 100644 examples/diff-chain/Cargo.toml rename examples/{diff-chain/src/main.rs => diff_chain.rs} (99%) rename examples/{resolution/src/main.rs => resolution.rs} (97%) delete mode 100644 examples/resolution/Cargo.toml diff --git a/Cargo.toml b/Cargo.toml index 37489549dc..ba02c4141b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,7 @@ members = [ "identity-diff", "identity-iota", - "examples/credential", - "examples/diff-chain", - "examples/resolution", + "examples", ] exclude = [ diff --git a/examples/Cargo.toml b/examples/Cargo.toml new file mode 100644 index 0000000000..ce7490ed00 --- /dev/null +++ b/examples/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "examples" +version = "0.1.0" +edition = "2018" +publish = false + +[dependencies] +identity-core = { path = "../identity-core" } +identity-credential = { path = "../identity-credential" } +identity-iota = { path = "../identity-iota" } +smol = { version = "0.1.18", features = ["tokio02"] } +smol-potat = { version = "0.3" } + +[[example]] +name = "credential" +path = "credential.rs" + +[[example]] +name = "diff_chain" +path = "diff_chain.rs" + +[[example]] +name = "resolution" +path = "resolution.rs" diff --git a/examples/credential/src/main.rs b/examples/credential.rs similarity index 98% rename from examples/credential/src/main.rs rename to examples/credential.rs index c1ece76ad4..2e459bb603 100644 --- a/examples/credential/src/main.rs +++ b/examples/credential.rs @@ -4,6 +4,9 @@ //! A basic example that generates and publishes subject and issuer DID //! Documents, creates a VerifiableCredential specifying claims about the //! subject, and retrieves information through the CredentialValidator API. +//! +//! cargo run --example credential + use identity_core::{ common::{Url, Value}, convert::{FromJson as _, ToJson as _}, diff --git a/examples/credential/Cargo.toml b/examples/credential/Cargo.toml deleted file mode 100644 index a4ee5c86c9..0000000000 --- a/examples/credential/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "identity-example-credential" -version = "0.1.0" -authors = ["IOTA Identity"] -edition = "2018" -publish = false - -[dependencies] -identity-core = { path = "../../identity-core" } -identity-credential = { path = "../../identity-credential" } -identity-iota = { path = "../../identity-iota" } -smol = { version = "0.1.18", features = ["tokio02"] } -smol-potat = { version = "0.3" } diff --git a/examples/diff-chain/Cargo.toml b/examples/diff-chain/Cargo.toml deleted file mode 100644 index 97a0f810f1..0000000000 --- a/examples/diff-chain/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "identity-example-diff-chain" -version = "0.1.0" -authors = ["IOTA Identity"] -edition = "2018" -publish = false - -[dependencies] -identity-core = { path = "../../identity-core" } -identity-iota = { path = "../../identity-iota" } -smol = { version = "0.1.18", features = ["tokio02"] } -smol-potat = { version = "0.3" } diff --git a/examples/diff-chain/src/main.rs b/examples/diff_chain.rs similarity index 99% rename from examples/diff-chain/src/main.rs rename to examples/diff_chain.rs index 52ac984c4b..ade1bba0f7 100644 --- a/examples/diff-chain/src/main.rs +++ b/examples/diff_chain.rs @@ -3,6 +3,9 @@ //! An example that utilizes a diff and auth chain to publish updates to a //! DID Document. +//! +//! cargo run --example diff_chain + use identity_core::{ crypto::KeyPair, did_doc::{MethodBuilder, MethodData, MethodRef, MethodType}, diff --git a/examples/resolution/src/main.rs b/examples/resolution.rs similarity index 97% rename from examples/resolution/src/main.rs rename to examples/resolution.rs index fa9e8467ad..31c53b33a7 100644 --- a/examples/resolution/src/main.rs +++ b/examples/resolution.rs @@ -3,6 +3,9 @@ //! A basic example that generates a DID Document, publishes it to the Tangle, //! and retrieves information through DID Document resolution/dereferencing. +//! +//! cargo run --example resolution + use identity_core::{ crypto::KeyPair, resolver::{dereference, resolve, Dereference, Resolution}, diff --git a/examples/resolution/Cargo.toml b/examples/resolution/Cargo.toml deleted file mode 100644 index bd42da12f3..0000000000 --- a/examples/resolution/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "identity-example-resolution" -version = "0.1.0" -authors = ["IOTA Identity"] -edition = "2018" -publish = false - -[dependencies] -identity-core = { path = "../../identity-core" } -identity-iota = { path = "../../identity-iota" } -smol = { version = "0.1.18", features = ["tokio02"] } -smol-potat = { version = "0.3" } From 199da87b20febbda1ea6277899fdd4178a4f953d Mon Sep 17 00:00:00 2001 From: l1h3r Date: Mon, 1 Feb 2021 14:36:52 -0800 Subject: [PATCH 06/28] did crate --- .github/workflows/build-and-test.yml | 1 + .github/workflows/clippy.yml | 1 + .github/workflows/format.yml | 1 + Cargo.toml | 1 + identity-core/Cargo.toml | 1 + identity-core/src/error.rs | 3 + .../src/utils/{base58.rs => base_encoding.rs} | 16 + identity-core/src/utils/mod.rs | 5 +- identity-credential/src/error.rs | 2 +- identity-did/Cargo.toml | 17 + identity-did/src/document/builder.rs | 142 ++++++ identity-did/src/document/document.rs | 435 ++++++++++++++++++ identity-did/src/document/mod.rs | 10 + identity-did/src/error.rs | 53 +++ identity-did/src/lib.rs | 21 + identity-did/src/service/builder.rs | 90 ++++ identity-did/src/service/mod.rs | 10 + identity-did/src/service/service.rs | 105 +++++ identity-did/src/signature/ld_suite.rs | 85 ++++ identity-did/src/signature/mod.rs | 20 + identity-did/src/signature/signature.rs | 124 +++++ identity-did/src/signature/signature_data.rs | 68 +++ .../src/signature/signature_options.rs | 46 ++ identity-did/src/signature/signature_value.rs | 74 +++ identity-did/src/signature/traits.rs | 67 +++ identity-did/src/utils/did_key.rs | 157 +++++++ identity-did/src/utils/mod.rs | 8 + identity-did/src/utils/ordered_set.rs | 316 +++++++++++++ identity-did/src/verifiable/document.rs | 113 +++++ identity-did/src/verifiable/mod.rs | 13 + identity-did/src/verifiable/properties.rs | 79 ++++ identity-did/src/verifiable/traits.rs | 90 ++++ identity-did/src/verification/builder.rs | 114 +++++ identity-did/src/verification/method.rs | 127 +++++ identity-did/src/verification/method_data.rs | 49 ++ identity-did/src/verification/method_ident.rs | 54 +++ identity-did/src/verification/method_query.rs | 71 +++ identity-did/src/verification/method_ref.rs | 108 +++++ identity-did/src/verification/method_scope.rs | 53 +++ identity-did/src/verification/method_type.rs | 42 ++ identity-did/src/verification/method_wrap.rs | 45 ++ identity-did/src/verification/mod.rs | 22 + 42 files changed, 2856 insertions(+), 3 deletions(-) rename identity-core/src/utils/{base58.rs => base_encoding.rs} (62%) create mode 100644 identity-did/Cargo.toml create mode 100644 identity-did/src/document/builder.rs create mode 100644 identity-did/src/document/document.rs create mode 100644 identity-did/src/document/mod.rs create mode 100644 identity-did/src/error.rs create mode 100644 identity-did/src/lib.rs create mode 100644 identity-did/src/service/builder.rs create mode 100644 identity-did/src/service/mod.rs create mode 100644 identity-did/src/service/service.rs create mode 100644 identity-did/src/signature/ld_suite.rs create mode 100644 identity-did/src/signature/mod.rs create mode 100644 identity-did/src/signature/signature.rs create mode 100644 identity-did/src/signature/signature_data.rs create mode 100644 identity-did/src/signature/signature_options.rs create mode 100644 identity-did/src/signature/signature_value.rs create mode 100644 identity-did/src/signature/traits.rs create mode 100644 identity-did/src/utils/did_key.rs create mode 100644 identity-did/src/utils/mod.rs create mode 100644 identity-did/src/utils/ordered_set.rs create mode 100644 identity-did/src/verifiable/document.rs create mode 100644 identity-did/src/verifiable/mod.rs create mode 100644 identity-did/src/verifiable/properties.rs create mode 100644 identity-did/src/verifiable/traits.rs create mode 100644 identity-did/src/verification/builder.rs create mode 100644 identity-did/src/verification/method.rs create mode 100644 identity-did/src/verification/method_data.rs create mode 100644 identity-did/src/verification/method_ident.rs create mode 100644 identity-did/src/verification/method_query.rs create mode 100644 identity-did/src/verification/method_ref.rs create mode 100644 identity-did/src/verification/method_scope.rs create mode 100644 identity-did/src/verification/method_type.rs create mode 100644 identity-did/src/verification/method_wrap.rs create mode 100644 identity-did/src/verification/mod.rs diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 211bf41b19..a2694c4a22 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -22,6 +22,7 @@ jobs: [ identity-core, identity-credential, + identity-did, identity-diff, identity-iota, ] diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 68941bd6f0..5df557fe7f 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -22,6 +22,7 @@ jobs: [ identity-core, identity-credential, + identity-did, identity-diff, identity-iota, bindings/wasm, diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 82fc83b1c5..2453f5553d 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -22,6 +22,7 @@ jobs: [ identity-core, identity-credential, + identity-did, identity-diff, identity-iota, bindings/wasm, diff --git a/Cargo.toml b/Cargo.toml index ba02c4141b..20230f44db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "identity-core", "identity-credential", + "identity-did", "identity-diff", "identity-iota", diff --git a/identity-core/Cargo.toml b/identity-core/Cargo.toml index fc4ede9260..3c0d02e094 100644 --- a/identity-core/Cargo.toml +++ b/identity-core/Cargo.toml @@ -19,6 +19,7 @@ did_doc = { git = "https://github.com/l1h3r/did_doc", rev = "bb8bda8f6d39451b08e did_url = { version = "0.1", default-features = false, features = ["std", "serde"] } digest = { version = "0.9", default-features = false } ed25519-zebra = { version = "2.2", default-features = false } +hex = { version = "0.4", default-features = false } identity-diff = { version = "=0.1.0", path = "../identity-diff", default-features = false } rand = { version = "0.7", default-features = false, features = ["getrandom"] } serde = { version = "1.0", default-features = false, features = ["std", "derive"] } diff --git a/identity-core/src/error.rs b/identity-core/src/error.rs index 8235ddfdf7..f093c316e4 100644 --- a/identity-core/src/error.rs +++ b/identity-core/src/error.rs @@ -15,6 +15,9 @@ pub enum Error { /// Caused by a failure to decode Rust types from JSON. #[error("Failed to decode JSON: {0}")] DecodeJSON(serde_json::Error), + /// Caused by a failure to decode base16-encoded data. + #[error("Failed to decode base16 data: {0}")] + DecodeBase16(#[from] hex::FromHexError), /// Caused by a failure to decode base58-encoded data. #[error("Failed to decode base58 data: {0}")] DecodeBase58(#[from] bs58::decode::Error), diff --git a/identity-core/src/utils/base58.rs b/identity-core/src/utils/base_encoding.rs similarity index 62% rename from identity-core/src/utils/base58.rs rename to identity-core/src/utils/base_encoding.rs index e719cda0ac..1748a60302 100644 --- a/identity-core/src/utils/base58.rs +++ b/identity-core/src/utils/base_encoding.rs @@ -21,3 +21,19 @@ where { bs58::encode(data).with_alphabet(bs58::Alphabet::BITCOIN).into_string() } + +/// Decodes the given `data` as base16 (hex). +pub fn decode_b16(data: &T) -> Result> +where + T: AsRef<[u8]> + ?Sized, +{ + hex::decode(data).map_err(Error::DecodeBase16) +} + +/// Encodes the given `data` as base16 (hex). +pub fn encode_b16(data: &T) -> String +where + T: AsRef<[u8]> + ?Sized, +{ + hex::encode(data) +} diff --git a/identity-core/src/utils/mod.rs b/identity-core/src/utils/mod.rs index 7e57237c94..ec8adb5144 100644 --- a/identity-core/src/utils/mod.rs +++ b/identity-core/src/utils/mod.rs @@ -3,7 +3,8 @@ //! Misc. utility functions. -mod base58; +mod base_encoding; mod jcs_sha256; -pub use self::{base58::*, jcs_sha256::*}; +pub use self::base_encoding::*; +pub use self::jcs_sha256::*; diff --git a/identity-credential/src/error.rs b/identity-credential/src/error.rs index b29e2f44a6..bac09f69af 100644 --- a/identity-credential/src/error.rs +++ b/identity-credential/src/error.rs @@ -4,7 +4,7 @@ //! Errors that may occur when working with Verifiable Credentials. /// Alias for a `Result` with the error type [`Error`]. -pub type Result = core::result::Result; +pub type Result = ::core::result::Result; /// This type represents all possible errors that can occur in the library. #[derive(Debug, thiserror::Error)] diff --git a/identity-did/Cargo.toml b/identity-did/Cargo.toml new file mode 100644 index 0000000000..b418c00a22 --- /dev/null +++ b/identity-did/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "identity-did" +version = "0.1.0" +authors = ["IOTA Identity"] +edition = "2018" +description = "An implementation of the Decentralized Identifiers standard." +readme = "../README.md" +repository = "https://github.com/iotaledger/identity.rs" +license = "Apache-2.0" +keywords = ["iota", "tangle", "identity"] +homepage = "https://www.iota.org" + +[dependencies] +did_url = { version = "0.1", default-features = false, features = ["alloc", "serde"] } +identity-core = { version = "=0.1.0", path = "../identity-core" } +serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +thiserror = { version = "1.0", default-features = false } diff --git a/identity-did/src/document/builder.rs b/identity-did/src/document/builder.rs new file mode 100644 index 0000000000..251ee373f3 --- /dev/null +++ b/identity-did/src/document/builder.rs @@ -0,0 +1,142 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use did_url::DID; +use identity_core::common::Url; + +use crate::document::Document; +use crate::error::Result; +use crate::service::Service; +use crate::utils::DIDKey; +use crate::verification::Method; +use crate::verification::MethodRef; + +/// A `Builder` is used to generate a customized `Document`. +#[derive(Clone, Debug)] +pub struct Builder { + pub(crate) id: Option, + pub(crate) controller: Option, + pub(crate) also_known_as: Vec, + pub(crate) verification_method: Vec>>, + pub(crate) authentication: Vec>>, + pub(crate) assertion_method: Vec>>, + pub(crate) key_agreement: Vec>>, + pub(crate) capability_delegation: Vec>>, + pub(crate) capability_invocation: Vec>>, + pub(crate) service: Vec>>, + pub(crate) properties: T, +} + +impl Builder { + /// Creates a new `Builder`. + pub fn new(properties: T) -> Self { + Self { + id: None, + controller: None, + also_known_as: Vec::new(), + verification_method: Vec::new(), + authentication: Vec::new(), + assertion_method: Vec::new(), + key_agreement: Vec::new(), + capability_delegation: Vec::new(), + capability_invocation: Vec::new(), + service: Vec::new(), + properties, + } + } + + /// Sets the `id` value of the generated `Document`. + #[must_use] + pub fn id(mut self, value: DID) -> Self { + self.id = Some(value); + self + } + + /// Sets the `controller` value of the generated `Document`. + #[must_use] + pub fn controller(mut self, value: DID) -> Self { + self.controller = Some(value); + self + } + + /// Adds a value to the `alsoKnownAs` set of the generated `Document`. + #[must_use] + pub fn also_known_as(mut self, value: Url) -> Self { + self.also_known_as.push(value); + self + } + + /// Adds a value to the `verificationMethod` set of the generated `Document`. + #[must_use] + pub fn verification_method(mut self, value: Method) -> Self { + self.verification_method.push(DIDKey::new(value)); + self + } + + /// Adds a value to the `authentication` set of the generated `Document`. + #[must_use] + pub fn authentication(mut self, value: impl Into>) -> Self { + self.authentication.push(DIDKey::new(value.into())); + self + } + + /// Adds a value to the `assertionMethod` set of the generated `Document`. + #[must_use] + pub fn assertion_method(mut self, value: impl Into>) -> Self { + self.assertion_method.push(DIDKey::new(value.into())); + self + } + + /// Adds a value to the `keyAgreement` set of the generated `Document`. + #[must_use] + pub fn key_agreement(mut self, value: impl Into>) -> Self { + self.key_agreement.push(DIDKey::new(value.into())); + self + } + + /// Adds a value to the `capabilityDelegation` set of the generated `Document`. + #[must_use] + pub fn capability_delegation(mut self, value: impl Into>) -> Self { + self.capability_delegation.push(DIDKey::new(value.into())); + self + } + + /// Adds a value to the `capabilityInvocation` set of the generated `Document`. + #[must_use] + pub fn capability_invocation(mut self, value: impl Into>) -> Self { + self.capability_invocation.push(DIDKey::new(value.into())); + self + } + + /// Adds a value to the `service` set of the generated `Document`. + #[must_use] + pub fn service(mut self, value: Service) -> Self { + self.service.push(DIDKey::new(value)); + self + } + + /// Returns a new `Document` based on the `Builder` configuration. + pub fn build(self) -> Result> { + Document::from_builder(self) + } +} + +impl Default for Builder +where + T: Default, +{ + fn default() -> Self { + Self::new(T::default()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic = "InvalidDocumentId"] + fn test_missing_id() { + let _: Document = Builder::default().build().unwrap(); + } +} diff --git a/identity-did/src/document/document.rs b/identity-did/src/document/document.rs new file mode 100644 index 0000000000..4ba779f09a --- /dev/null +++ b/identity-did/src/document/document.rs @@ -0,0 +1,435 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::convert::TryInto as _; +use core::fmt::Display; +use core::fmt::Error as FmtError; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use did_url::DID; +use identity_core::common::Url; +use identity_core::convert::ToJson; +use serde::Serialize; + +use crate::document::Builder; +use crate::error::Error; +use crate::error::Result; +use crate::service::Service; +use crate::signature::SignatureOptions; +use crate::utils::DIDKey; +use crate::utils::OrderedSet; +use crate::verifiable::ResolveMethod; +use crate::verification::Method; +use crate::verification::MethodQuery; +use crate::verification::MethodRef; +use crate::verification::MethodScope; +use crate::verification::MethodWrap; + +/// A DID Document +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[rustfmt::skip] +pub struct Document { + pub(crate) id: DID, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) controller: Option, + #[serde(default = "Default::default", rename = "alsoKnownAs", skip_serializing_if = "Vec::is_empty")] + pub(crate) also_known_as: Vec, + #[serde(default = "Default::default", rename = "verificationMethod", skip_serializing_if = "OrderedSet::is_empty")] + pub(crate) verification_method: OrderedSet>>, + #[serde(default = "Default::default", skip_serializing_if = "OrderedSet::is_empty")] + pub(crate) authentication: OrderedSet>>, + #[serde(default = "Default::default", rename = "assertionMethod", skip_serializing_if = "OrderedSet::is_empty")] + pub(crate) assertion_method: OrderedSet>>, + #[serde(default = "Default::default", rename = "keyAgreement", skip_serializing_if = "OrderedSet::is_empty")] + pub(crate) key_agreement: OrderedSet>>, + #[serde(default = "Default::default", rename = "capabilityDelegation", skip_serializing_if = "OrderedSet::is_empty")] + pub(crate) capability_delegation: OrderedSet>>, + #[serde(default = "Default::default", rename = "capabilityInvocation", skip_serializing_if = "OrderedSet::is_empty")] + pub(crate) capability_invocation: OrderedSet>>, + #[serde(default = "Default::default", skip_serializing_if = "OrderedSet::is_empty")] + pub(crate) service: OrderedSet>>, + #[serde(flatten)] + pub(crate) properties: T, +} + +impl Document { + /// Creates a `Builder` to configure a new `Document`. + /// + /// This is the same as `Builder::new()`. + pub fn builder(properties: T) -> Builder { + Builder::new(properties) + } + + /// Returns a new `Document` based on the `Builder` configuration. + pub fn from_builder(builder: Builder) -> Result { + Ok(Self { + id: builder.id.ok_or(Error::InvalidDocumentId)?, + controller: builder.controller, + also_known_as: builder.also_known_as, + verification_method: builder.verification_method.try_into()?, + authentication: builder.authentication.try_into()?, + assertion_method: builder.assertion_method.try_into()?, + key_agreement: builder.key_agreement.try_into()?, + capability_delegation: builder.capability_delegation.try_into()?, + capability_invocation: builder.capability_invocation.try_into()?, + service: builder.service.try_into()?, + properties: builder.properties, + }) + } + + /// Returns a reference to the `Document` id. + pub fn id(&self) -> &DID { + &self.id + } + + /// Returns a mutable reference to the `Document` id. + pub fn id_mut(&mut self) -> &mut DID { + &mut self.id + } + + /// Returns a reference to the `Document` controller. + pub fn controller(&self) -> Option<&DID> { + self.controller.as_ref() + } + + /// Returns a mutable reference to the `Document` controller. + pub fn controller_mut(&mut self) -> Option<&mut DID> { + self.controller.as_mut() + } + + /// Returns a reference to the `Document` alsoKnownAs set. + pub fn also_known_as(&self) -> &[Url] { + &self.also_known_as + } + + /// Returns a mutable reference to the `Document` alsoKnownAs set. + pub fn also_known_as_mut(&mut self) -> &mut Vec { + &mut self.also_known_as + } + + /// Returns a reference to the `Document` verificationMethod set. + pub fn verification_method(&self) -> &OrderedSet>> { + &self.verification_method + } + + /// Returns a mutable reference to the `Document` verificationMethod set. + pub fn verification_method_mut(&mut self) -> &mut OrderedSet>> { + &mut self.verification_method + } + + /// Returns a reference to the `Document` authentication set. + pub fn authentication(&self) -> &OrderedSet>> { + &self.authentication + } + + /// Returns a mutable reference to the `Document` authentication set. + pub fn authentication_mut(&mut self) -> &mut OrderedSet>> { + &mut self.authentication + } + + /// Returns a reference to the `Document` assertionMethod set. + pub fn assertion_method(&self) -> &OrderedSet>> { + &self.assertion_method + } + + /// Returns a mutable reference to the `Document` assertionMethod set. + pub fn assertion_method_mut(&mut self) -> &mut OrderedSet>> { + &mut self.assertion_method + } + + /// Returns a reference to the `Document` keyAgreement set. + pub fn key_agreement(&self) -> &OrderedSet>> { + &self.key_agreement + } + + /// Returns a mutable reference to the `Document` keyAgreement set. + pub fn key_agreement_mut(&mut self) -> &mut OrderedSet>> { + &mut self.key_agreement + } + + /// Returns a reference to the `Document` capabilityDelegation set. + pub fn capability_delegation(&self) -> &OrderedSet>> { + &self.capability_delegation + } + + /// Returns a mutable reference to the `Document` capabilityDelegation set. + pub fn capability_delegation_mut(&mut self) -> &mut OrderedSet>> { + &mut self.capability_delegation + } + + /// Returns a reference to the `Document` capabilityInvocation set. + pub fn capability_invocation(&self) -> &OrderedSet>> { + &self.capability_invocation + } + + /// Returns a mutable reference to the `Document` capabilityInvocation set. + pub fn capability_invocation_mut(&mut self) -> &mut OrderedSet>> { + &mut self.capability_invocation + } + + /// Returns a reference to the `Document` service set. + pub fn service(&self) -> &OrderedSet>> { + &self.service + } + + /// Returns a mutable reference to the `Document` service set. + pub fn service_mut(&mut self) -> &mut OrderedSet>> { + &mut self.service + } + + /// Returns a reference to the custom `Document` properties. + pub fn properties(&self) -> &T { + &self.properties + } + + /// Returns a mutable reference to the custom `Document` properties. + pub fn properties_mut(&mut self) -> &mut T { + &mut self.properties + } + + /// Maps `Document` to `Document` by applying a function to the custom + /// properties. + pub fn map(self, f: F) -> Document + where + F: FnOnce(T) -> A, + { + Document { + id: self.id, + controller: self.controller, + also_known_as: self.also_known_as, + verification_method: self.verification_method, + authentication: self.authentication, + assertion_method: self.assertion_method, + key_agreement: self.key_agreement, + capability_delegation: self.capability_delegation, + capability_invocation: self.capability_invocation, + service: self.service, + properties: f(self.properties), + } + } + + /// A fallible version of `Document::map(..)`. + /// + /// # Errors + /// + /// `try_map` can fail if the provided function fails. + pub fn try_map(self, f: F) -> Result, E> + where + F: FnOnce(T) -> Result, + { + Ok(Document { + id: self.id, + controller: self.controller, + also_known_as: self.also_known_as, + verification_method: self.verification_method, + authentication: self.authentication, + assertion_method: self.assertion_method, + key_agreement: self.key_agreement, + capability_delegation: self.capability_delegation, + capability_invocation: self.capability_invocation, + service: self.service, + properties: f(self.properties)?, + }) + } + + /// Finds and returns the first verification `Method` matching the provided + ///`MethodQuery`. + pub fn resolve<'a, Q>(&self, query: Q) -> Option> + where + Q: Into>, + { + self.resolve_method(query.into()) + } + + /// Finds and returns the first verification `Method` matching the provided + ///`MethodQuery`. + /// + /// # Errors + /// + /// Fails if no matching verification `Method` is found. + pub fn try_resolve<'a, Q>(&self, query: Q) -> Result> + where + Q: Into>, + { + self.resolve(query).ok_or(Error::QueryMethodNotFound) + } + + pub fn resolve_bytes<'a, Q>(&self, query: Q) -> Option> + where + Q: Into>, + { + self.try_resolve_bytes(query).ok() + } + + pub fn try_resolve_bytes<'a, Q>(&self, query: Q) -> Result> + where + Q: Into>, + { + self.try_resolve(query)?.key_data().try_decode() + } + + pub fn resolve_options<'a, Q>(&self, query: Q) -> Result + where + Q: Into>, + { + let query: MethodQuery<'_> = query.into(); + let method: MethodWrap<'_, U> = self.try_resolve(query)?; + + Ok(SignatureOptions::with_purpose( + method.id.to_string(), + query.scope.as_str().to_string(), + )) + } + + fn resolve_method<'a>(&self, query: MethodQuery<'a>) -> Option> { + let iter = match query.scope { + MethodScope::VerificationMethod => return self.resolve_verification_method(query), + MethodScope::Authentication => self.authentication.iter(), + MethodScope::AssertionMethod => self.assertion_method.iter(), + MethodScope::KeyAgreement => self.key_agreement.iter(), + MethodScope::CapabilityDelegation => self.capability_delegation.iter(), + MethodScope::CapabilityInvocation => self.capability_invocation.iter(), + }; + + iter + .enumerate() + .find(|(index, method)| query.ident == *index || query.ident.matches(method.id())) + .and_then(|(index, method)| match method.as_ref() { + MethodRef::Refer(did) => self.resolve(did.fragment()?), + MethodRef::Embed(method) => Some(MethodWrap::new(method, index, query.scope)), + }) + } + + fn resolve_verification_method(&self, query: MethodQuery<'_>) -> Option> { + self + .verification_method + .iter() + .enumerate() + .find(|(index, method)| query.ident == *index || query.ident.matches(method.id())) + .map(|(index, method)| MethodWrap::new(method, index, MethodScope::VerificationMethod)) + } +} + +impl Display for Document +where + T: Serialize, + U: Serialize, + V: Serialize, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + if f.alternate() { + f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) + } else { + f.write_str(&self.to_json().map_err(|_| FmtError)?) + } + } +} + +impl ResolveMethod for Document { + fn resolve_method(&self, query: MethodQuery<'_>) -> Option> { + Document::resolve_method(self, query) + } +} + +#[cfg(test)] +mod tests { + use did_url::DID; + + use crate::document::Builder; + use crate::document::Document; + use crate::verification::Builder as MethodBuilder; + use crate::verification::Method; + use crate::verification::MethodData; + use crate::verification::MethodScope; + use crate::verification::MethodType; + + fn controller() -> DID { + "did:example:1234".parse().unwrap() + } + + fn method(controller: &DID, fragment: &str) -> Method { + MethodBuilder::default() + .id(controller.join(fragment).unwrap()) + .controller(controller.clone()) + .key_type(MethodType::Ed25519VerificationKey2018) + .key_data(MethodData::new_b58(fragment.as_bytes())) + .build() + .unwrap() + } + + fn document() -> Document { + let controller: DID = controller(); + + Builder::default() + .id(controller.clone()) + .verification_method(method(&controller, "#key-1")) + .verification_method(method(&controller, "#key-2")) + .verification_method(method(&controller, "#key-3")) + .authentication(method(&controller, "#auth-key")) + .authentication(controller.join("#key-3").unwrap()) + .key_agreement(controller.join("#key-4").unwrap()) + .build() + .unwrap() + } + + #[test] + #[rustfmt::skip] + fn test_resolve_fragment_identifier() { + let document: Document = document(); + + // Resolve methods by fragment using the default scope (VerificationMethod) + assert_eq!(document.resolve("#key-1").unwrap().id(), "did:example:1234#key-1"); + assert_eq!(document.resolve("#key-2").unwrap().id(), "did:example:1234#key-2"); + assert_eq!(document.resolve("#key-3").unwrap().id(), "did:example:1234#key-3"); + + // Perfect fine to omit the octothorpe + assert_eq!(document.resolve("key-1").unwrap().id(), "did:example:1234#key-1"); + assert_eq!(document.resolve("key-2").unwrap().id(), "did:example:1234#key-2"); + assert_eq!(document.resolve("key-3").unwrap().id(), "did:example:1234#key-3"); + } + + #[test] + #[rustfmt::skip] + fn test_resolve_index_identifier() { + let document: Document = document(); + + // Resolve methods by index using the default scope once again + assert_eq!(document.resolve(0).unwrap().id(), "did:example:1234#key-1"); + assert_eq!(document.resolve(2).unwrap().id(), "did:example:1234#key-3"); + } + + #[test] + #[rustfmt::skip] + fn test_resolve_explicit_scope() { + let document: Document = document(); + + // Resolve methods by fragment using explicit scopes + assert_eq!(document.resolve(("#key-1", MethodScope::KeyAgreement)), None); + assert_eq!(document.resolve(("#key-2", MethodScope::VerificationMethod)).unwrap().id(), "did:example:1234#key-2"); + } + + #[test] + #[rustfmt::skip] + fn test_resolve_reference_found() { + let document: Document = document(); + + // Resolving a method reference returns the method object + let resolved_ref = document.resolve(("#key-3", MethodScope::Authentication)).unwrap(); + let resolved_obj = document.resolve(("#key-3", MethodScope::VerificationMethod)).unwrap(); + + assert_eq!(resolved_ref.index(), 2); + assert_eq!(resolved_ref.scope(), MethodScope::VerificationMethod); + + // The resolved methods should be identical + assert_eq!(&*resolved_ref, &*resolved_obj); + } + + #[test] + #[rustfmt::skip] + fn test_resolve_reference_missing() { + let document: Document = document(); + + // Resolving an existing reference to a missing method returns None + assert_eq!(document.resolve(("#key-4", MethodScope::KeyAgreement)), None); + } +} diff --git a/identity-did/src/document/mod.rs b/identity-did/src/document/mod.rs new file mode 100644 index 0000000000..8b8e2bc6fc --- /dev/null +++ b/identity-did/src/document/mod.rs @@ -0,0 +1,10 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +#![allow(clippy::module_inception)] + +mod builder; +mod document; + +pub use self::builder::Builder; +pub use self::document::Document; diff --git a/identity-did/src/error.rs b/identity-did/src/error.rs new file mode 100644 index 0000000000..f3c5e3f703 --- /dev/null +++ b/identity-did/src/error.rs @@ -0,0 +1,53 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Errors that may occur when working with Decentralized Identifiers. + +/// 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. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Duplicate Item in Ordered Set")] + OrderedSetDuplicate, + #[error("Verification Method Not Found")] + QueryMethodNotFound, + #[error("Signature Not Found")] + QuerySignatureNotFound, + + #[error("Invalid Document Property: `id`")] + InvalidDocumentId, + + #[error("Invalid Service Property: `id`")] + InvalidServiceId, + #[error("Invalid Service Property: `type`")] + InvalidServiceType, + #[error("Invalid Service Property: `service_endpoint`")] + InvalidServiceEndpoint, + + #[error("Invalid Verification Method Property: `id`")] + InvalidMethodId, + #[error("Invalid Verification Method Fragment")] + InvalidMethodIdFragment, + #[error("Invalid Verification Method Property: `controller`")] + InvalidMethodController, + #[error("Invalid Verification Method Property: `type`")] + InvalidMethodType, + #[error("Invalid Verification Method Property: `data`")] + InvalidMethodData, + + #[error("Unknown Method Scope")] + UnknownMethodScope, + #[error("Unknown Method Type")] + UnknownMethodType, + #[error("Unknown Signature Type")] + UnknownSignatureType, + + #[error("Invalid Key Data")] + InvalidKeyData, + #[error("Invalid Base16 Key Data")] + InvalidKeyDataBase16, + #[error("Invalid Base58 Key Data")] + InvalidKeyDataBase58, +} diff --git a/identity-did/src/lib.rs b/identity-did/src/lib.rs new file mode 100644 index 0000000000..0ad1b8ad5c --- /dev/null +++ b/identity-did/src/lib.rs @@ -0,0 +1,21 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +#[macro_use] +extern crate serde; + +pub mod did { + #[doc(import)] + pub use did_url::*; +} + +pub mod document; +pub mod error; +pub mod service; +pub mod signature; +pub mod utils; +pub mod verifiable; +pub mod verification; + +pub use self::error::Error; +pub use self::error::Result; diff --git a/identity-did/src/service/builder.rs b/identity-did/src/service/builder.rs new file mode 100644 index 0000000000..490cf1191b --- /dev/null +++ b/identity-did/src/service/builder.rs @@ -0,0 +1,90 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use did_url::DID; +use identity_core::common::Url; + +use crate::error::Result; +use crate::service::Service; + +/// A `Builder` is used to generate a customized `Service`. +#[derive(Clone, Debug, Default)] +pub struct Builder { + pub(crate) id: Option, + pub(crate) type_: Option, + pub(crate) service_endpoint: Option, + pub(crate) properties: T, +} + +impl Builder { + /// Creates a new `Builder`. + pub fn new(properties: T) -> Self { + Self { + id: None, + type_: None, + service_endpoint: None, + properties, + } + } + + /// Sets the `id` value of the generated `Service`. + #[must_use] + pub fn id(mut self, value: DID) -> Self { + self.id = Some(value); + self + } + + /// Sets the `type` value of the generated `Service`. + #[must_use] + pub fn type_(mut self, value: impl Into) -> Self { + self.type_ = Some(value.into()); + self + } + + /// Sets the `serviceEndpoint` value of the generated `Service`. + #[must_use] + pub fn service_endpoint(mut self, value: Url) -> Self { + self.service_endpoint = Some(value); + self + } + + /// Returns a new `Service` based on the `Builder` configuration. + pub fn build(self) -> Result> { + Service::from_builder(self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic = "InvalidServiceId"] + fn test_missing_id() { + let _: Service = Builder::default() + .type_("ServiceType") + .service_endpoint("https://example.com".parse().unwrap()) + .build() + .unwrap(); + } + + #[test] + #[should_panic = "InvalidServiceType"] + fn test_missing_type_() { + let _: Service = Builder::default() + .id("did:example:123".parse().unwrap()) + .service_endpoint("https://example.com".parse().unwrap()) + .build() + .unwrap(); + } + + #[test] + #[should_panic = "InvalidServiceEndpoint"] + fn test_missing_service_endpoint() { + let _: Service = Builder::default() + .id("did:example:123".parse().unwrap()) + .type_("ServiceType") + .build() + .unwrap(); + } +} diff --git a/identity-did/src/service/mod.rs b/identity-did/src/service/mod.rs new file mode 100644 index 0000000000..fd6a538b52 --- /dev/null +++ b/identity-did/src/service/mod.rs @@ -0,0 +1,10 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +#![allow(clippy::module_inception)] + +mod builder; +mod service; + +pub use self::builder::Builder; +pub use self::service::Service; diff --git a/identity-did/src/service/service.rs b/identity-did/src/service/service.rs new file mode 100644 index 0000000000..cc7b7902bb --- /dev/null +++ b/identity-did/src/service/service.rs @@ -0,0 +1,105 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::fmt::Display; +use core::fmt::Error as FmtError; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use did_url::DID; +use identity_core::common::Url; +use identity_core::convert::ToJson; +use serde::Serialize; + +use crate::error::Error; +use crate::error::Result; +use crate::service::Builder; + +/// A DID Document Service +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct Service { + pub(crate) id: DID, + #[serde(rename = "type")] + pub(crate) type_: String, + #[serde(rename = "serviceEndpoint")] + pub(crate) service_endpoint: Url, + #[serde(flatten)] + pub(crate) properties: T, +} + +impl Service { + /// Creates a `Builder` to configure a new `Service`. + /// + /// This is the same as `Builder::new()`. + pub fn builder(properties: T) -> Builder { + Builder::new(properties) + } + + /// Returns a new `Service` based on the `Builder` configuration. + pub fn from_builder(builder: Builder) -> Result { + Ok(Self { + id: builder.id.ok_or(Error::InvalidServiceId)?, + type_: builder.type_.ok_or(Error::InvalidServiceType)?, + service_endpoint: builder.service_endpoint.ok_or(Error::InvalidServiceEndpoint)?, + properties: builder.properties, + }) + } + + /// Returns a reference to the `Service` id. + pub fn id(&self) -> &DID { + &self.id + } + + /// Returns a mutable reference to the `Service` id. + pub fn id_mut(&mut self) -> &mut DID { + &mut self.id + } + + /// Returns a reference to the `Service` type. + pub fn type_(&self) -> &str { + &*self.type_ + } + + /// Returns a mutable reference to the `Service` type. + pub fn type_mut(&mut self) -> &mut String { + &mut self.type_ + } + + /// Returns a reference to the `Service` endpoint. + pub fn service_endpoint(&self) -> &Url { + &self.service_endpoint + } + + /// Returns a mutable reference to the `Service` endpoint. + pub fn service_endpoint_mut(&mut self) -> &mut Url { + &mut self.service_endpoint + } + + /// Returns a reference to the custom `Service` properties. + pub fn properties(&self) -> &T { + &self.properties + } + + /// Returns a mutable reference to the custom `Service` properties. + pub fn properties_mut(&mut self) -> &mut T { + &mut self.properties + } +} + +impl Display for Service +where + T: Serialize, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + if f.alternate() { + f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) + } else { + f.write_str(&self.to_json().map_err(|_| FmtError)?) + } + } +} + +impl AsRef for Service { + fn as_ref(&self) -> &DID { + self.id() + } +} diff --git a/identity-did/src/signature/ld_suite.rs b/identity-did/src/signature/ld_suite.rs new file mode 100644 index 0000000000..a64d9e6b6c --- /dev/null +++ b/identity-did/src/signature/ld_suite.rs @@ -0,0 +1,85 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use serde::Serialize; + +use crate::error::Error; +use crate::error::Result; +use crate::signature::Sign; +use crate::signature::Signature; +use crate::signature::SignatureData; +use crate::signature::SignatureOptions; +use crate::signature::SuiteName; +use crate::signature::Verify; +use crate::verifiable::ResolveMethod; +use crate::verifiable::SetSignature; +use crate::verifiable::TrySignature; +use crate::verification::MethodQuery; +use crate::verification::MethodWrap; + +#[derive(Clone, Copy, Debug)] +pub struct LdSuite { + suite: S, +} + +impl LdSuite { + pub fn new(suite: S) -> Self { + Self { suite } + } +} + +impl LdSuite +where + S: Sign + SuiteName, +{ + pub fn sign(&self, message: &mut T, options: SignatureOptions, secret: &K) -> Result<()> + where + T: Serialize + SetSignature, + K: AsRef<[u8]> + ?Sized, + { + message.set_signature(Signature::new(self.suite.name(), options)); + + let value: SignatureData = self.suite.sign(message, secret.as_ref())?; + + message.try_signature_mut()?.set_data(value); + + Ok(()) + } +} + +impl LdSuite +where + S: Verify + SuiteName, +{ + pub fn verify(&self, message: &T) -> Result<()> + where + T: Serialize + TrySignature + ResolveMethod, + M: Serialize, + { + self.verify_data(message, message) + } + + pub fn verify_data(&self, message: &T, resolver: R) -> Result<()> + where + T: Serialize + TrySignature, + R: ResolveMethod, + M: Serialize, + { + let signature: &Signature = message.try_signature()?; + + if signature.type_() != self.suite.name() { + return Err(Error::UnknownSignatureType); + } + + let query: MethodQuery<'_> = signature.to_query()?; + let method: MethodWrap<'_, M> = resolver.try_resolve_method(query)?; + + if !S::METHODS.contains(&method.key_type()) { + return Err(Error::UnknownMethodType); + } + + signature.verify(&self.suite, message, &method.key_data().try_decode()?)?; + + Ok(()) + } +} diff --git a/identity-did/src/signature/mod.rs b/identity-did/src/signature/mod.rs new file mode 100644 index 0000000000..a7d51d6ccb --- /dev/null +++ b/identity-did/src/signature/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +#![allow(clippy::module_inception)] + +mod ld_suite; +mod signature; +mod signature_data; +mod signature_options; +mod signature_value; +mod traits; + +pub use self::ld_suite::LdSuite; +pub use self::signature::Signature; +pub use self::signature_data::SignatureData; +pub use self::signature_options::SignatureOptions; +pub use self::signature_value::SignatureValue; +pub use self::traits::Sign; +pub use self::traits::SuiteName; +pub use self::traits::Verify; diff --git a/identity-did/src/signature/signature.rs b/identity-did/src/signature/signature.rs new file mode 100644 index 0000000000..762cdecdc8 --- /dev/null +++ b/identity-did/src/signature/signature.rs @@ -0,0 +1,124 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::fmt::Debug; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use core::ops::Deref; +use core::ops::DerefMut; +use serde::Serialize; + +use crate::error::Result; +use crate::signature::SignatureData; +use crate::signature::SignatureOptions; +use crate::signature::SignatureValue; +use crate::signature::Verify; +use crate::verification::MethodIdent; +use crate::verification::MethodQuery; + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +pub struct Signature { + #[serde(rename = "type")] + type_: String, + #[serde(flatten, skip_serializing_if = "SignatureValue::is_none")] + data: SignatureValue, + #[serde(flatten)] + options: SignatureOptions, +} + +impl Signature { + pub fn new(type_: impl Into, options: SignatureOptions) -> Self { + Self { + type_: type_.into(), + options, + data: SignatureValue::new(), + } + } + + pub fn type_(&self) -> &str { + &*self.type_ + } + + pub const fn data(&self) -> &SignatureValue { + &self.data + } + + pub fn data_mut(&mut self) -> &mut SignatureValue { + &mut self.data + } + + pub fn set_data(&mut self, value: SignatureData) { + self.data.set(value); + } + + pub fn clear_data(&mut self) { + self.data.clear(); + } + + pub fn hide_value(&self) { + self.data.hide(); + } + + pub fn show_value(&self) { + self.data.show(); + } + + pub fn to_query(&self) -> Result> { + let ident: MethodIdent<'_> = (&*self.verification_method).into(); + + if let Some(scope) = self.proof_purpose.as_deref() { + Ok(MethodQuery::with_scope(ident, scope.parse()?)) + } else { + Ok(MethodQuery::new(ident)) + } + } + + pub fn verify(&self, suite: &S, message: &M, public: &[u8]) -> Result<()> + where + S: Verify, + M: Serialize, + { + self.verifiable(|data| suite.verify(message, data, public)) + } + + pub fn verifiable(&self, f: F) -> T + where + F: FnOnce(&SignatureValue) -> T, + { + self.data.hide(); + + let output: T = f(self.data()); + + self.data.show(); + + output + } +} + +impl Debug for Signature { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_struct("Signature") + .field("type_", &self.type_) + .field("data", &self.data) + .field("verification_method", &self.options.verification_method) + .field("proof_purpose", &self.options.proof_purpose) + .field("created", &self.options.created) + .field("nonce", &self.options.nonce) + .field("domain", &self.options.domain) + .finish() + } +} + +impl Deref for Signature { + type Target = SignatureOptions; + + fn deref(&self) -> &Self::Target { + &self.options + } +} + +impl DerefMut for Signature { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.options + } +} diff --git a/identity-did/src/signature/signature_data.rs b/identity-did/src/signature/signature_data.rs new file mode 100644 index 0000000000..2a6497f9d5 --- /dev/null +++ b/identity-did/src/signature/signature_data.rs @@ -0,0 +1,68 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +pub enum SignatureData { + #[serde(skip)] + None, + #[serde(rename = "jws")] + Jws(String), + #[serde(rename = "proofValue")] + Proof(String), + #[serde(rename = "signatureValue")] + Signature(String), +} + +impl SignatureData { + pub const fn is_none(&self) -> bool { + matches!(self, Self::None) + } + + pub const fn is_jws(&self) -> bool { + matches!(self, Self::Jws(_)) + } + + pub const fn is_proof(&self) -> bool { + matches!(self, Self::Proof(_)) + } + + pub const fn is_signature(&self) -> bool { + matches!(self, Self::Signature(_)) + } + + pub fn as_str(&self) -> &str { + match self { + Self::None => "", + Self::Jws(inner) => &*inner, + Self::Proof(inner) => &*inner, + Self::Signature(inner) => &*inner, + } + } + + pub fn try_jws(&self) -> Option<&str> { + match self { + Self::None => None, + Self::Jws(inner) => Some(&*inner), + Self::Proof(_) => None, + Self::Signature(_) => None, + } + } + + pub fn try_proof(&self) -> Option<&str> { + match self { + Self::None => None, + Self::Jws(_) => None, + Self::Proof(inner) => Some(&*inner), + Self::Signature(_) => None, + } + } + + pub fn try_signature(&self) -> Option<&str> { + match self { + Self::None => None, + Self::Jws(_) => None, + Self::Proof(_) => None, + Self::Signature(inner) => Some(&*inner), + } + } +} diff --git a/identity-did/src/signature/signature_options.rs b/identity-did/src/signature/signature_options.rs new file mode 100644 index 0000000000..6d71973448 --- /dev/null +++ b/identity-did/src/signature/signature_options.rs @@ -0,0 +1,46 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::verification::MethodWrap; + +#[derive(Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +pub struct SignatureOptions { + #[serde(rename = "verificationMethod")] + pub verification_method: String, + #[serde(rename = "proofPurpose", skip_serializing_if = "Option::is_none")] + pub proof_purpose: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub created: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub nonce: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub domain: Option, +} + +impl SignatureOptions { + pub const fn new(verification_method: String) -> Self { + Self { + verification_method, + proof_purpose: None, + created: None, + nonce: None, + domain: None, + } + } + + pub const fn with_purpose(verification_method: String, proof_purpose: String) -> Self { + Self { + verification_method, + proof_purpose: Some(proof_purpose), + created: None, + nonce: None, + domain: None, + } + } +} + +impl From> for SignatureOptions { + fn from(other: MethodWrap<'_, T>) -> Self { + Self::with_purpose(other.id().to_string(), other.scope().as_str().to_string()) + } +} diff --git a/identity-did/src/signature/signature_value.rs b/identity-did/src/signature/signature_value.rs new file mode 100644 index 0000000000..8066e4d5b7 --- /dev/null +++ b/identity-did/src/signature/signature_value.rs @@ -0,0 +1,74 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::cell::Cell; +use core::fmt::Debug; +use core::fmt::Formatter; +use core::fmt::Result; +use core::ops::Deref; +use core::ops::DerefMut; + +use crate::signature::SignatureData; + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +#[serde(transparent)] +pub struct SignatureValue { + data: SignatureData, + #[serde(skip)] + hide: Cell, +} + +impl SignatureValue { + pub const fn new() -> Self { + Self { + data: SignatureData::None, + hide: Cell::new(false), + } + } + + pub fn is_none(&self) -> bool { + self.data.is_none() || self.hide.get() + } + + pub fn set(&mut self, value: SignatureData) { + self.data = value; + } + + pub fn clear(&mut self) { + self.set(SignatureData::None); + } + + pub fn hide(&self) { + self.hide.set(true); + } + + pub fn show(&self) { + self.hide.set(false); + } +} + +impl Debug for SignatureValue { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + Debug::fmt(&self.data, f) + } +} + +impl Default for SignatureValue { + fn default() -> Self { + Self::new() + } +} + +impl Deref for SignatureValue { + type Target = SignatureData; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl DerefMut for SignatureValue { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} diff --git a/identity-did/src/signature/traits.rs b/identity-did/src/signature/traits.rs new file mode 100644 index 0000000000..45e7478430 --- /dev/null +++ b/identity-did/src/signature/traits.rs @@ -0,0 +1,67 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use serde::Serialize; + +use crate::error::Result; +use crate::signature::SignatureData; +use crate::verification::MethodType; + +pub trait SuiteName { + fn name(&self) -> String; +} + +impl<'a, T> SuiteName for &'a T +where + T: SuiteName, +{ + fn name(&self) -> String { + (**self).name() + } +} + +// ============================================================================= +// ============================================================================= + +pub trait Sign { + fn sign(&self, data: &T, secret: &[u8]) -> Result + where + T: Serialize; +} + +impl<'a, T> Sign for &'a T +where + T: Sign, +{ + fn sign(&self, data: &U, secret: &[u8]) -> Result + where + U: Serialize, + { + (**self).sign(data, secret) + } +} + +// ============================================================================= +// ============================================================================= + +pub trait Verify { + const METHODS: &'static [MethodType]; + + fn verify(&self, data: &T, signature: &SignatureData, public: &[u8]) -> Result<()> + where + T: Serialize; +} + +impl<'a, T> Verify for &'a T +where + T: Verify, +{ + const METHODS: &'static [MethodType] = T::METHODS; + + fn verify(&self, data: &U, signature: &SignatureData, public: &[u8]) -> Result<()> + where + U: Serialize, + { + (**self).verify(data, signature, public) + } +} diff --git a/identity-did/src/utils/did_key.rs b/identity-did/src/utils/did_key.rs new file mode 100644 index 0000000000..5b9c498a1e --- /dev/null +++ b/identity-did/src/utils/did_key.rs @@ -0,0 +1,157 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::borrow::Borrow; +use core::cmp::Ordering; +use core::convert::AsMut; +use core::convert::AsRef; +use core::fmt::Debug; +use core::fmt::Display; +use core::fmt::Formatter; +use core::fmt::Result; +use core::hash::Hash; +use core::hash::Hasher; +use core::ops::Deref; +use core::ops::DerefMut; +use did_url::DID; + +/// A helper struct for comparing types only by `DID`. +/// +/// Types are expected to implement `AsRef` which allows access to traits +/// for ordering and comparison. +#[derive(Clone, Copy, Deserialize, Serialize)] +#[repr(transparent)] +#[serde(transparent)] +pub struct DIDKey(T); + +impl DIDKey { + /// Create a new `DIDKey`. + #[inline] + pub const fn new(inner: T) -> Self { + Self(inner) + } + + /// Consumes the `DIDKey` and returns the inner `T`. + #[inline] + pub fn into_inner(self) -> T { + self.0 + } + + /// Returns a reference to the `DID`. + #[inline] + pub fn as_did(&self) -> &DID + where + T: AsRef, + { + self.0.as_ref() + } +} + +impl PartialEq for DIDKey +where + T: AsRef, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + self.as_did().eq(other.as_did()) + } +} + +impl Eq for DIDKey where T: AsRef {} + +impl PartialOrd for DIDKey +where + T: AsRef, +{ + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + self.as_did().partial_cmp(other.as_did()) + } +} + +impl Ord for DIDKey +where + T: AsRef, +{ + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + self.as_did().cmp(other.as_did()) + } +} + +impl Hash for DIDKey +where + T: AsRef, +{ + fn hash(&self, hasher: &mut H) + where + H: Hasher, + { + self.as_did().hash(hasher) + } +} + +impl Deref for DIDKey { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for DIDKey { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl AsRef for DIDKey { + #[inline] + fn as_ref(&self) -> &T { + &self.0 + } +} + +impl AsMut for DIDKey { + #[inline] + fn as_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl Borrow for DIDKey +where + T: AsRef, +{ + #[inline] + fn borrow(&self) -> &DID { + self.as_did() + } +} + +impl From for DIDKey { + #[inline] + fn from(other: T) -> Self { + Self(other) + } +} + +impl Debug for DIDKey +where + T: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + Debug::fmt(&self.0, f) + } +} + +impl Display for DIDKey +where + T: Display, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + Display::fmt(&self.0, f) + } +} diff --git a/identity-did/src/utils/mod.rs b/identity-did/src/utils/mod.rs new file mode 100644 index 0000000000..536f9e511b --- /dev/null +++ b/identity-did/src/utils/mod.rs @@ -0,0 +1,8 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod did_key; +mod ordered_set; + +pub use self::did_key::DIDKey; +pub use self::ordered_set::OrderedSet; diff --git a/identity-did/src/utils/ordered_set.rs b/identity-did/src/utils/ordered_set.rs new file mode 100644 index 0000000000..3027bba757 --- /dev/null +++ b/identity-did/src/utils/ordered_set.rs @@ -0,0 +1,316 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::borrow::Borrow; +use core::convert::TryFrom; +use core::fmt::Debug; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use core::iter::FromIterator; +use core::ops::Deref; +use core::slice::Iter; +use serde::Deserialize; + +use crate::error::Error; +use crate::error::Result; + +/// An ordered set based on a `Vec`. +#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +#[repr(transparent)] +#[serde(bound(deserialize = "T: PartialEq + Deserialize<'de>"), try_from = "Vec")] +pub struct OrderedSet(Vec); + +impl OrderedSet { + /// Creates a new `OrderedSet`. + #[inline] + pub const fn new() -> Self { + Self(Vec::new()) + } + + /// Creates a new `OrderedSet` with the specified capacity. + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Self(Vec::with_capacity(capacity)) + } + + /// Returns the number of elements in the `OrderedSet`. + #[inline] + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns `true` if the `OrderedSet` contains no elements. + #[inline] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Returns an iterator over the slice of elements. + #[inline] + pub fn iter(&self) -> Iter<'_, T> { + self.0.iter() + } + + /// Returns the first element in the set, or `None` if the set is empty. + #[inline] + pub fn head(&self) -> Option<&T> { + self.0.first() + } + + /// Returns a mutable referece to the first element in the set, or `None` if + /// the set is empty. + #[inline] + pub fn head_mut(&mut self) -> Option<&mut T> { + self.0.first_mut() + } + + /// Returns the last element in the set, or `None` if the set is empty. + #[inline] + pub fn tail(&self) -> Option<&T> { + self.0.last() + } + + /// Returns a mutable referece the last element in the set, or `None` if the + /// set is empty. + #[inline] + pub fn tail_mut(&mut self) -> Option<&mut T> { + self.0.last_mut() + } + + /// Returns a slice containing all elements in the `OrderedSet`. + #[inline] + pub fn as_slice(&self) -> &[T] { + &self.0 + } + + /// Consumes the `OrderedSet` and returns the elements as a `Vec`. + #[inline] + pub fn into_vec(self) -> Vec { + self.0 + } + + /// Clears the `OrderedSet`, removing all values. + #[inline] + pub fn clear(&mut self) { + self.0.clear(); + } + + /// Returns `true` if the `OrderedSet` contains the given value. + pub fn contains(&self, item: &U) -> bool + where + T: AsRef, + U: PartialEq + ?Sized, + { + self.0.iter().any(|other| other.as_ref() == item) + } + + /// Adds a new value to the end of the `OrderedSet`; returns `true` if the + /// value was successfully added. + pub fn append(&mut self, item: T) -> bool + where + T: PartialEq, + { + if self.0.contains(&item) { + false + } else { + self.0.push(item); + true + } + } + + /// Adds a new value to the start of the `OrderedSet`; returns `true` if the + /// value was successfully added. + pub fn prepend(&mut self, item: T) -> bool + where + T: PartialEq, + { + if self.0.contains(&item) { + false + } else { + self.0.insert(0, item); + true + } + } + + /// Replaces a `current` value with the given `update` value; returns `true` + /// if the value was successfully replaced. + #[inline] + pub fn replace(&mut self, current: &U, update: T) -> bool + where + T: PartialEq + Borrow, + U: PartialEq + ?Sized, + { + self.change(update, |item, update| item.borrow() == current || item == update) + } + + /// Updates an existing value in the `OrderedSet`; returns `true` if the value + /// was successfully updated. + #[inline] + pub fn update(&mut self, update: T) -> bool + where + T: PartialEq, + { + self.change(update, |item, update| item == update) + } + + fn change(&mut self, data: T, f: F) -> bool + where + F: Fn(&T, &T) -> bool, + { + let index: Option = self.0.iter().position(|item| f(item, &data)); + + if let Some(index) = index { + let keep: Vec = self.0.drain(index..).filter(|item| !f(item, &data)).collect(); + + self.0.extend(keep); + self.0.insert(index, data); + } + + index.is_some() + } +} + +impl Debug for OrderedSet +where + T: Debug, +{ + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_set().entries(self.iter()).finish() + } +} + +impl Deref for OrderedSet { + type Target = [T]; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Default for OrderedSet { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl FromIterator for OrderedSet +where + T: PartialEq, +{ + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let iter: _ = iter.into_iter(); + let size: usize = iter.size_hint().1.unwrap_or(0); + + let mut this: Self = Self::with_capacity(size); + + for item in iter { + this.append(item); + } + + this + } +} + +impl TryFrom> for OrderedSet +where + T: PartialEq, +{ + type Error = Error; + + fn try_from(other: Vec) -> Result { + let mut this: Self = Self::with_capacity(other.len()); + + for item in other { + if !this.append(item) { + return Err(Error::OrderedSetDuplicate); + } + } + + Ok(this) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use did_url::DID; + + use crate::utils::DIDKey; + use crate::verification::MethodRef; + + #[test] + fn test_works() { + let mut set = OrderedSet::new(); + + set.append("a"); + set.append("b"); + set.append("c"); + + assert_eq!(set.as_slice(), &["a", "b", "c"]); + assert_eq!(set.head(), Some(&"a")); + assert_eq!(set.tail(), Some(&"c")); + + set.replace(&"a", "c"); + + assert_eq!(set.as_slice(), &["c", "b"]); + + let mut set = OrderedSet::new(); + + set.prepend("a"); + set.prepend("b"); + set.prepend("c"); + + assert_eq!(set.as_slice(), &["c", "b", "a"]); + assert_eq!(set.head(), Some(&"c")); + assert_eq!(set.tail(), Some(&"a")); + + set.replace(&"a", "c"); + + assert_eq!(set.as_slice(), &["c", "b"]); + } + + #[test] + fn test_from_vec_valid() { + let source: Vec = vec![3, 1, 2, 0]; + let oset: OrderedSet = OrderedSet::try_from(source).unwrap(); + + assert_eq!(&*oset, &[3, 1, 2, 0]); + } + + #[test] + #[should_panic = "OrderedSetDuplicate"] + fn test_from_vec_invalid() { + let source: Vec = vec![1, 2, 2, 5]; + let _: OrderedSet = OrderedSet::try_from(source).unwrap(); + } + + #[test] + fn test_collect() { + let source: Vec = vec![1, 2, 3, 3, 2, 4, 5, 1, 1]; + let oset: OrderedSet = source.into_iter().collect(); + + assert_eq!(&*oset, &[1, 2, 3, 4, 5]); + } + + #[test] + fn test_contains() { + let did1: DID = DID::parse("did:example:123").unwrap(); + let did2: DID = DID::parse("did:example:456").unwrap(); + + let source: Vec> = vec![ + DIDKey::new(MethodRef::Refer(did1.clone())), + DIDKey::new(MethodRef::Refer(did2.clone())), + ]; + + let oset: OrderedSet> = source.into_iter().collect(); + + assert!(oset.contains(&MethodRef::Refer(did1))); + assert!(oset.contains(&MethodRef::Refer(did2))); + } +} diff --git a/identity-did/src/verifiable/document.rs b/identity-did/src/verifiable/document.rs new file mode 100644 index 0000000000..4233e401a4 --- /dev/null +++ b/identity-did/src/verifiable/document.rs @@ -0,0 +1,113 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::fmt::Debug; +use core::fmt::Display; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use core::ops::Deref; +use core::ops::DerefMut; +use serde::Serialize; + +use crate::document::Document as Document_; +use crate::signature::Signature; +use crate::verifiable::ResolveMethod; +use crate::verifiable::SetSignature; +use crate::verifiable::TrySignature; +use crate::verifiable::TrySignatureMut; +use crate::verifiable::Properties; +use crate::verification::MethodQuery; +use crate::verification::MethodWrap; + +#[derive(Clone, PartialEq, Deserialize, Serialize)] +#[repr(transparent)] +#[serde(transparent)] +pub struct Document { + document: Document_, U, V>, +} + +impl Document { + pub fn new(document: Document_) -> Self { + Self { + document: document.map(Properties::new), + } + } + + pub fn with_proof(document: Document_, proof: Signature) -> Self { + Self { + document: document.map(|old| Properties::with_proof(old, proof)), + } + } + + pub fn proof(&self) -> Option<&Signature> { + self.properties().proof() + } + + pub fn proof_mut(&mut self) -> Option<&mut Signature> { + self.properties_mut().proof_mut() + } + + pub fn set_proof(&mut self, signature: Signature) { + self.properties_mut().proof = Some(signature); + } +} + +impl Deref for Document { + type Target = Document_, U, V>; + + fn deref(&self) -> &Self::Target { + &self.document + } +} + +impl DerefMut for Document { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.document + } +} + +impl Debug for Document +where + T: Debug, + U: Debug, + V: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + Debug::fmt(&self.document, f) + } +} + +impl Display for Document +where + T: Serialize, + U: Serialize, + V: Serialize, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + Display::fmt(&self.document, f) + } +} + +impl TrySignature for Document { + fn signature(&self) -> Option<&Signature> { + self.proof() + } +} + +impl TrySignatureMut for Document { + fn signature_mut(&mut self) -> Option<&mut Signature> { + self.proof_mut() + } +} + +impl SetSignature for Document { + fn set_signature(&mut self, signature: Signature) { + self.set_proof(signature) + } +} + +impl ResolveMethod for Document { + fn resolve_method(&self, query: MethodQuery<'_>) -> Option> { + self.document.resolve(query) + } +} diff --git a/identity-did/src/verifiable/mod.rs b/identity-did/src/verifiable/mod.rs new file mode 100644 index 0000000000..b577619059 --- /dev/null +++ b/identity-did/src/verifiable/mod.rs @@ -0,0 +1,13 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod document; +mod properties; +mod traits; + +pub use self::document::Document; +pub use self::properties::Properties; +pub use self::traits::ResolveMethod; +pub use self::traits::SetSignature; +pub use self::traits::TrySignature; +pub use self::traits::TrySignatureMut; diff --git a/identity-did/src/verifiable/properties.rs b/identity-did/src/verifiable/properties.rs new file mode 100644 index 0000000000..b8e6ea0337 --- /dev/null +++ b/identity-did/src/verifiable/properties.rs @@ -0,0 +1,79 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::ops::Deref; +use core::ops::DerefMut; + +use crate::signature::Signature; +use crate::verifiable::SetSignature; +use crate::verifiable::TrySignature; +use crate::verifiable::TrySignatureMut; + +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +pub struct Properties { + #[serde(flatten)] + pub(crate) properties: T, + // TODO: Support multiple signatures (?) + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) proof: Option, +} + +impl Properties { + pub const fn new(properties: T) -> Self { + Self { + properties, + proof: None, + } + } + + pub const fn with_proof(properties: T, proof: Signature) -> Self { + Self { + properties, + proof: Some(proof), + } + } + + pub fn proof(&self) -> Option<&Signature> { + self.proof.as_ref() + } + + pub fn proof_mut(&mut self) -> Option<&mut Signature> { + self.proof.as_mut() + } + + pub fn set_proof(&mut self, signature: Signature) { + self.proof = Some(signature); + } +} + +impl Deref for Properties { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.properties + } +} + +impl DerefMut for Properties { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.properties + } +} + +impl TrySignature for Properties { + fn signature(&self) -> Option<&Signature> { + self.proof() + } +} + +impl TrySignatureMut for Properties { + fn signature_mut(&mut self) -> Option<&mut Signature> { + self.proof_mut() + } +} + +impl SetSignature for Properties { + fn set_signature(&mut self, signature: Signature) { + self.set_proof(signature) + } +} diff --git a/identity-did/src/verifiable/traits.rs b/identity-did/src/verifiable/traits.rs new file mode 100644 index 0000000000..0bc21ea445 --- /dev/null +++ b/identity-did/src/verifiable/traits.rs @@ -0,0 +1,90 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::Error; +use crate::error::Result; +use crate::signature::Signature; +use crate::verification::MethodQuery; +use crate::verification::MethodWrap; + +pub trait TrySignature { + fn signature(&self) -> Option<&Signature>; + + fn try_signature(&self) -> Result<&Signature> { + self.signature().ok_or(Error::QuerySignatureNotFound) + } +} + +impl<'a, T> TrySignature for &'a T +where + T: TrySignature, +{ + fn signature(&self) -> Option<&Signature> { + (**self).signature() + } +} + +impl<'a, T> TrySignature for &'a mut T +where + T: TrySignature, +{ + fn signature(&self) -> Option<&Signature> { + (**self).signature() + } +} + +// ============================================================================= +// ============================================================================= + +pub trait TrySignatureMut: TrySignature { + fn signature_mut(&mut self) -> Option<&mut Signature>; + + fn try_signature_mut(&mut self) -> Result<&mut Signature> { + self.signature_mut().ok_or(Error::QuerySignatureNotFound) + } +} + +impl<'a, T> TrySignatureMut for &'a mut T +where + T: TrySignatureMut, +{ + fn signature_mut(&mut self) -> Option<&mut Signature> { + (**self).signature_mut() + } +} + +// ============================================================================= +// ============================================================================= + +pub trait SetSignature: TrySignatureMut { + fn set_signature(&mut self, signature: Signature); +} + +impl<'a, T> SetSignature for &'a mut T +where + T: SetSignature, +{ + fn set_signature(&mut self, signature: Signature) { + (**self).set_signature(signature); + } +} + +// ============================================================================= +// ============================================================================= + +pub trait ResolveMethod { + fn resolve_method(&self, query: MethodQuery<'_>) -> Option>; + + fn try_resolve_method(&self, query: MethodQuery<'_>) -> Result> { + self.resolve_method(query).ok_or(Error::QueryMethodNotFound) + } +} + +impl<'a, T, M> ResolveMethod for &'a T +where + T: ResolveMethod, +{ + fn resolve_method(&self, query: MethodQuery<'_>) -> Option> { + (**self).resolve_method(query) + } +} diff --git a/identity-did/src/verification/builder.rs b/identity-did/src/verification/builder.rs new file mode 100644 index 0000000000..ea77fb798d --- /dev/null +++ b/identity-did/src/verification/builder.rs @@ -0,0 +1,114 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use did_url::DID; + +use crate::error::Result; +use crate::verification::Method; +use crate::verification::MethodData; +use crate::verification::MethodType; + +/// A `Builder` is used to generate a customized `Method`. +#[derive(Clone, Debug, Default)] +pub struct Builder { + pub(crate) id: Option, + pub(crate) controller: Option, + pub(crate) key_type: Option, + pub(crate) key_data: Option, + pub(crate) properties: T, +} + +impl Builder { + /// Creates a new `Builder`. + pub fn new(properties: T) -> Self { + Self { + id: None, + controller: None, + key_type: None, + key_data: None, + properties, + } + } + + /// Sets the `id` value of the generated verification `Method`. + #[must_use] + pub fn id(mut self, value: DID) -> Self { + self.id = Some(value); + self + } + + /// Sets the `controller` value of the generated verification `Method`. + #[must_use] + pub fn controller(mut self, value: DID) -> Self { + self.controller = Some(value); + self + } + + /// Sets the `type` value of the generated verification `Method`. + #[must_use] + pub fn key_type(mut self, value: MethodType) -> Self { + self.key_type = Some(value); + self + } + + /// Sets the `data` value of the generated verification `Method`. + #[must_use] + pub fn key_data(mut self, value: MethodData) -> Self { + self.key_data = Some(value); + self + } + + /// Returns a new `Method` based on the `Builder` configuration. + pub fn build(self) -> Result> { + Method::from_builder(self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic = "InvalidMethodId"] + fn test_missing_id() { + let _: Method = Builder::default() + .controller("did:example:123".parse().unwrap()) + .key_type(MethodType::Ed25519VerificationKey2018) + .key_data(MethodData::PublicKeyBase58("".into())) + .build() + .unwrap(); + } + + #[test] + #[should_panic = "InvalidMethodType"] + fn test_missing_key_type() { + let _: Method = Builder::default() + .id("did:example:123".parse().unwrap()) + .controller("did:example:123".parse().unwrap()) + .key_data(MethodData::PublicKeyBase58("".into())) + .build() + .unwrap(); + } + + #[test] + #[should_panic = "InvalidMethodData"] + fn test_missing_key_data() { + let _: Method = Builder::default() + .id("did:example:123".parse().unwrap()) + .controller("did:example:123".parse().unwrap()) + .key_type(MethodType::Ed25519VerificationKey2018) + .build() + .unwrap(); + } + + #[test] + #[should_panic = "InvalidMethodController"] + fn test_missing_controller() { + let _: Method = Builder::default() + .id("did:example:123".parse().unwrap()) + .key_type(MethodType::Ed25519VerificationKey2018) + .key_data(MethodData::PublicKeyBase58("".into())) + .build() + .unwrap(); + } +} diff --git a/identity-did/src/verification/method.rs b/identity-did/src/verification/method.rs new file mode 100644 index 0000000000..2df635b7d1 --- /dev/null +++ b/identity-did/src/verification/method.rs @@ -0,0 +1,127 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::fmt::Display; +use core::fmt::Error as FmtError; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use core::iter::once; +use did_url::DID; +use identity_core::convert::ToJson; +use serde::Serialize; + +use crate::error::Error; +use crate::error::Result; +use crate::verification::Builder; +use crate::verification::MethodData; +use crate::verification::MethodType; + +/// A DID Document Verification Method +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct Method { + pub(crate) id: DID, + pub(crate) controller: DID, + #[serde(rename = "type")] + pub(crate) key_type: MethodType, + #[serde(flatten)] + pub(crate) key_data: MethodData, + #[serde(flatten)] + pub(crate) properties: T, +} + +impl Method { + /// Creates a `Builder` to configure a new `Method`. + /// + /// This is the same as `Builder::new()`. + pub fn builder(properties: T) -> Builder { + Builder::new(properties) + } + + /// Returns a new `Method` based on the `Builder` configuration. + pub fn from_builder(builder: Builder) -> Result { + Ok(Method { + id: builder.id.ok_or(Error::InvalidMethodId)?, + controller: builder.controller.ok_or(Error::InvalidMethodController)?, + key_type: builder.key_type.ok_or(Error::InvalidMethodType)?, + key_data: builder.key_data.ok_or(Error::InvalidMethodData)?, + properties: builder.properties, + }) + } + + /// Returns a reference to the verification `Method` id. + pub fn id(&self) -> &DID { + &self.id + } + + /// Returns a mutable reference to the verification `Method` id. + pub fn id_mut(&mut self) -> &mut DID { + &mut self.id + } + + /// Returns a reference to the verification `Method` controller. + pub fn controller(&self) -> &DID { + &self.controller + } + + /// Returns a mutable reference to the verification `Method` controller. + pub fn controller_mut(&mut self) -> &mut DID { + &mut self.controller + } + + /// Returns a reference to the verification `Method` type. + pub fn key_type(&self) -> MethodType { + self.key_type + } + + /// Returns a mutable reference to the verification `Method` type. + pub fn key_type_mut(&mut self) -> &mut MethodType { + &mut self.key_type + } + + /// Returns a reference to the verification `Method` data. + pub fn key_data(&self) -> &MethodData { + &self.key_data + } + + /// Returns a mutable reference to the verification `Method` data. + pub fn key_data_mut(&mut self) -> &mut MethodData { + &mut self.key_data + } + + /// Returns a reference to the custom verification `Method` properties. + pub fn properties(&self) -> &T { + &self.properties + } + + /// Returns a mutable reference to the custom verification `Method` properties. + pub fn properties_mut(&mut self) -> &mut T { + &mut self.properties + } + + pub fn try_into_fragment(&self) -> Result { + self + .id + .fragment() + .ok_or(Error::InvalidMethodIdFragment) + .map(|fragment| once('#').chain(fragment.chars()).collect()) + } +} + +impl Display for Method +where + T: Serialize, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + if f.alternate() { + f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) + } else { + f.write_str(&self.to_json().map_err(|_| FmtError)?) + } + } +} + +impl AsRef for Method { + fn as_ref(&self) -> &DID { + self.id() + } +} diff --git a/identity-did/src/verification/method_data.rs b/identity-did/src/verification/method_data.rs new file mode 100644 index 0000000000..5e0ed95edf --- /dev/null +++ b/identity-did/src/verification/method_data.rs @@ -0,0 +1,49 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Object; +use identity_core::utils::decode_b16; +use identity_core::utils::decode_b58; +use identity_core::utils::encode_b16; +use identity_core::utils::encode_b58; + +use crate::error::Error; +use crate::error::Result; + +/// Supported verification method data formats. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub enum MethodData { + PublicKeyBase58(String), + PublicKeyHex(String), + PublicKeyJwk(Object), +} + +impl MethodData { + /// Creates a new `MethodData` variant with base16-encoded content. + pub fn new_b16(data: impl AsRef<[u8]>) -> Self { + Self::PublicKeyHex(encode_b16(&data)) + } + + /// Creates a new `MethodData` variant with base58-encoded content. + pub fn new_b58(data: impl AsRef<[u8]>) -> Self { + Self::PublicKeyBase58(encode_b58(&data)) + } + + /// Returns a `Vec` containing the decoded bytes of the `MethodData`. + /// + /// This is generally a public key identified by a `MethodType` value. + /// + /// # Errors + /// + /// Decoding can fail if `MethodData` has invalid content or cannot be + /// represented as a vector of bytes. + pub fn try_decode(&self) -> Result> { + match self { + Self::PublicKeyBase58(input) => decode_b58(input).map_err(|_| Error::InvalidKeyDataBase58), + Self::PublicKeyHex(input) => decode_b16(input).map_err(|_| Error::InvalidKeyDataBase16), + Self::PublicKeyJwk(_) => Err(Error::InvalidKeyData), + } + } +} diff --git a/identity-did/src/verification/method_ident.rs b/identity-did/src/verification/method_ident.rs new file mode 100644 index 0000000000..3c663d0c75 --- /dev/null +++ b/identity-did/src/verification/method_ident.rs @@ -0,0 +1,54 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use did_url::DID; + +/// Index or identifier used to identify the target verification method of a +/// `MethodQuery`. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum MethodIdent<'a> { + Index(usize), + Ident(&'a str), +} + +impl<'a> MethodIdent<'a> { + /// Returns a `bool` indicating if the given `DID` matches the identifier. + pub fn matches(&self, did: &DID) -> bool { + match self { + Self::Index(_) => false, + Self::Ident(ident) if ident.starts_with(DID::SCHEME) && !ident.ends_with('#') => ident + .rfind('#') + .map_or(false, |index| Self::matches_fragment(did, &ident[index + 1..])), + Self::Ident(ident) if ident.starts_with('#') => Self::matches_fragment(did, &ident[1..]), + Self::Ident(ident) => Self::matches_fragment(did, *ident), + } + } + + fn matches_fragment(did: &DID, ident: &str) -> bool { + matches!(did.fragment(), Some(fragment) if fragment == ident) + } +} + +impl<'a> From<&'a str> for MethodIdent<'a> { + fn from(other: &'a str) -> Self { + Self::Ident(other) + } +} + +impl From for MethodIdent<'_> { + fn from(other: usize) -> Self { + Self::Index(other) + } +} + +impl PartialEq for MethodIdent<'_> { + fn eq(&self, other: &usize) -> bool { + matches!(self, Self::Index(index) if index == other) + } +} + +impl PartialEq<&'_ str> for MethodIdent<'_> { + fn eq(&self, other: &&'_ str) -> bool { + matches!(self, Self::Ident(ident) if ident == other) + } +} diff --git a/identity-did/src/verification/method_query.rs b/identity-did/src/verification/method_query.rs new file mode 100644 index 0000000000..dd010a896e --- /dev/null +++ b/identity-did/src/verification/method_query.rs @@ -0,0 +1,71 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::verification::MethodIdent; +use crate::verification::MethodScope; + +/// Specifies the conditions of a DID document method resolution query. +/// +/// See `Document::resolve`. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct MethodQuery<'a> { + pub(crate) ident: MethodIdent<'a>, + pub(crate) scope: MethodScope, +} + +impl<'a> MethodQuery<'a> { + /// Creates a new `MethodQuery`. + pub fn new(ident: T) -> Self + where + T: Into>, + { + Self::with_scope(ident, MethodScope::default()) + } + + /// Creates a new `MethodQuery` with the given `MethodScope`. + pub fn with_scope(ident: T, scope: MethodScope) -> Self + where + T: Into>, + { + Self { + ident: ident.into(), + scope, + } + } +} + +impl<'a> From<&'a str> for MethodQuery<'a> { + fn from(other: &'a str) -> Self { + Self::new(other) + } +} + +impl From for MethodQuery<'_> { + fn from(other: usize) -> Self { + Self::new(other) + } +} + +impl<'a> From<(&'a str, MethodScope)> for MethodQuery<'a> { + fn from(other: (&'a str, MethodScope)) -> Self { + Self::with_scope(other.0, other.1) + } +} + +impl From<(usize, MethodScope)> for MethodQuery<'_> { + fn from(other: (usize, MethodScope)) -> Self { + Self::with_scope(other.0, other.1) + } +} + +impl<'a> From<(MethodIdent<'a>, MethodScope)> for MethodQuery<'a> { + fn from(other: (MethodIdent<'a>, MethodScope)) -> Self { + Self::with_scope(other.0, other.1) + } +} + +impl<'a> From for MethodQuery<'a> { + fn from(other: MethodScope) -> Self { + Self::with_scope(0, other) + } +} diff --git a/identity-did/src/verification/method_ref.rs b/identity-did/src/verification/method_ref.rs new file mode 100644 index 0000000000..8366bfde81 --- /dev/null +++ b/identity-did/src/verification/method_ref.rs @@ -0,0 +1,108 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::fmt::Debug; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use did_url::DID; + +use crate::verification::Method; + +/// A reference to a verification method, either a `DID` or embedded `Method`. +#[derive(Clone, PartialEq, Deserialize, Serialize)] +#[serde(untagged)] +pub enum MethodRef { + Embed(Method), + Refer(DID), +} + +impl MethodRef { + /// Returns a reference to the `MethodRef` id. + pub fn id(&self) -> &DID { + match self { + Self::Embed(inner) => inner.id(), + Self::Refer(inner) => inner, + } + } + + /// Returns a reference to the `MethodRef` controller. + pub fn controller(&self) -> Option<&DID> { + match self { + Self::Embed(inner) => Some(inner.controller()), + Self::Refer(_) => None, + } + } + + /// Returns a `bool` indicating if the `MethodRef` is an embedded `Method`. + #[inline] + pub const fn is_embedded(&self) -> bool { + matches!(self, Self::Embed(_)) + } + + /// Returns a `bool` indicating if the `MethodRef` is a `DID` reference. + #[inline] + pub const fn is_referred(&self) -> bool { + matches!(self, Self::Refer(_)) + } + + /// Returns the inner `Method` if this is an embedded `MethodRef`. + /// + /// Note: Returns `Err(self)` as a failure case. + /// + /// # Errors + /// + /// Fails if `MethodRef` is not an embedded method. + pub fn try_into_embedded(self) -> Result, Self> { + match self { + Self::Embed(inner) => Ok(inner), + Self::Refer(_) => Err(self), + } + } + + /// Returns the inner `Method` if this is an referenced `MethodRef`. + /// + /// Note: Returns `Err(self)` as a failure case. + /// + /// # Errors + /// + /// Fails if `MethodRef` is not an referenced method. + pub fn try_into_referenced(self) -> Result { + match self { + Self::Embed(_) => Err(self), + Self::Refer(inner) => Ok(inner), + } + } +} + +impl Debug for MethodRef +where + T: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self { + Self::Embed(inner) => Debug::fmt(inner, f), + Self::Refer(inner) => Debug::fmt(inner, f), + } + } +} + +impl From> for MethodRef { + #[inline] + fn from(other: Method) -> Self { + Self::Embed(other) + } +} + +impl From for MethodRef { + #[inline] + fn from(other: DID) -> Self { + Self::Refer(other) + } +} + +impl AsRef for MethodRef { + #[inline] + fn as_ref(&self) -> &DID { + self.id() + } +} diff --git a/identity-did/src/verification/method_scope.rs b/identity-did/src/verification/method_scope.rs new file mode 100644 index 0000000000..28fc378d34 --- /dev/null +++ b/identity-did/src/verification/method_scope.rs @@ -0,0 +1,53 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::str::FromStr; + +use crate::error::Error; +use crate::error::Result; + +/// Verification method group used to refine the scope of a method query. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum MethodScope { + VerificationMethod, + Authentication, + AssertionMethod, + KeyAgreement, + CapabilityDelegation, + CapabilityInvocation, +} + +impl MethodScope { + pub const fn as_str(&self) -> &'static str { + match self { + Self::VerificationMethod => "VerificationMethod", + Self::Authentication => "Authentication", + Self::AssertionMethod => "AssertionMethod", + Self::KeyAgreement => "KeyAgreement", + Self::CapabilityDelegation => "CapabilityDelegation", + Self::CapabilityInvocation => "CapabilityInvocation", + } + } +} + +impl Default for MethodScope { + fn default() -> Self { + Self::VerificationMethod + } +} + +impl FromStr for MethodScope { + type Err = Error; + + fn from_str(string: &str) -> Result { + match string { + "VerificationMethod" => Ok(Self::VerificationMethod), + "Authentication" => Ok(Self::Authentication), + "AssertionMethod" => Ok(Self::AssertionMethod), + "KeyAgreement" => Ok(Self::KeyAgreement), + "CapabilityDelegation" => Ok(Self::CapabilityDelegation), + "CapabilityInvocation" => Ok(Self::CapabilityInvocation), + _ => Err(Error::UnknownMethodScope), + } + } +} diff --git a/identity-did/src/verification/method_type.rs b/identity-did/src/verification/method_type.rs new file mode 100644 index 0000000000..939d522ffa --- /dev/null +++ b/identity-did/src/verification/method_type.rs @@ -0,0 +1,42 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::str::FromStr; + +use crate::error::Error; +use crate::error::Result; + +/// Supported verification method types. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +#[non_exhaustive] +pub enum MethodType { + JcsEd25519Key2020, + JwsVerificationKey2020, + Ed25519VerificationKey2018, + MerkleKeyCollection2021, +} + +impl MethodType { + pub const fn as_str(self) -> &'static str { + match self { + Self::JcsEd25519Key2020 => "JcsEd25519Key2020", + Self::JwsVerificationKey2020 => "JwsVerificationKey2020", + Self::Ed25519VerificationKey2018 => "Ed25519VerificationKey2018", + Self::MerkleKeyCollection2021 => "MerkleKeyCollection2021", + } + } +} + +impl FromStr for MethodType { + type Err = Error; + + fn from_str(string: &str) -> Result { + match string { + "JcsEd25519Key2020" => Ok(Self::JcsEd25519Key2020), + "JwsVerificationKey2020" => Ok(Self::JwsVerificationKey2020), + "Ed25519VerificationKey2018" => Ok(Self::Ed25519VerificationKey2018), + "MerkleKeyCollection2021" => Ok(Self::MerkleKeyCollection2021), + _ => Err(Error::UnknownMethodType), + } + } +} diff --git a/identity-did/src/verification/method_wrap.rs b/identity-did/src/verification/method_wrap.rs new file mode 100644 index 0000000000..5767f0b06f --- /dev/null +++ b/identity-did/src/verification/method_wrap.rs @@ -0,0 +1,45 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::ops::Deref; + +use crate::verification::Method; +use crate::verification::MethodScope; + +/// A queried `Method` with additional metadata about the query resolution. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct MethodWrap<'a, T = ()> { + pub(crate) method: &'a Method, + pub(crate) index: usize, + pub(crate) scope: MethodScope, +} + +impl<'a, T> MethodWrap<'a, T> { + /// Creates a new `MethodWrap`. + pub(crate) const fn new(method: &'a Method, index: usize, scope: MethodScope) -> Self { + Self { index, method, scope } + } + + /// Returns the index of the method within the verification relationship set. + pub const fn index(&self) -> usize { + self.index + } + + /// Returns the scope of the resolved verification method. + pub const fn scope(&self) -> MethodScope { + self.scope + } + + /// Consumes the `MethodWrap` and returns a reference to the resolved `Method`. + pub const fn into_method(self) -> &'a Method { + self.method + } +} + +impl Deref for MethodWrap<'_, T> { + type Target = Method; + + fn deref(&self) -> &Self::Target { + self.method + } +} diff --git a/identity-did/src/verification/mod.rs b/identity-did/src/verification/mod.rs new file mode 100644 index 0000000000..1eef575936 --- /dev/null +++ b/identity-did/src/verification/mod.rs @@ -0,0 +1,22 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod builder; +mod method; +mod method_data; +mod method_ident; +mod method_query; +mod method_ref; +mod method_scope; +mod method_type; +mod method_wrap; + +pub use self::builder::Builder; +pub use self::method::Method; +pub use self::method_data::MethodData; +pub use self::method_ident::MethodIdent; +pub use self::method_query::MethodQuery; +pub use self::method_ref::MethodRef; +pub use self::method_scope::MethodScope; +pub use self::method_type::MethodType; +pub use self::method_wrap::MethodWrap; From fe2350d4aa29d096112d8b218a45d4593201bf73 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Mon, 1 Feb 2021 16:53:57 -0800 Subject: [PATCH 07/28] enforce rust 2018 idioms --- identity-core/Cargo.toml | 1 + identity-core/src/common/context.rs | 10 ++++--- identity-core/src/common/mod.rs | 12 +++++---- identity-core/src/common/object.rs | 10 +++++++ identity-core/src/common/one_or_many.rs | 21 ++++++++------- identity-core/src/common/timestamp.rs | 24 ++++++++++------- identity-core/src/common/url.rs | 26 ++++++++++--------- identity-core/src/convert/json.rs | 7 +++-- identity-core/src/convert/mod.rs | 5 +++- identity-core/src/convert/serde_into.rs | 7 +++-- identity-core/src/crypto/key_impl.rs | 4 +-- .../src/crypto/merkle_tree/consts.rs | 6 ++--- identity-core/src/crypto/merkle_tree/hash.rs | 2 +- identity-core/src/crypto/merkle_tree/math.rs | 4 +-- .../src/crypto/merkle_tree/merkle.rs | 2 +- identity-core/src/crypto/merkle_tree/node.rs | 2 +- identity-core/src/crypto/merkle_tree/proof.rs | 2 +- identity-core/src/crypto/merkle_tree/tree.rs | 12 ++++----- identity-core/src/error.rs | 2 +- identity-core/src/lib.rs | 6 +++-- identity-core/src/utils/base_encoding.rs | 3 ++- identity-core/src/utils/jcs_sha256.rs | 7 +++-- .../src/credential/credential.rs | 2 +- .../src/credential/verifiable.rs | 2 +- identity-credential/src/lib.rs | 2 ++ .../src/presentation/presentation.rs | 2 +- .../src/presentation/verifiable.rs | 2 +- identity-did/src/lib.rs | 14 ++++++++++ 28 files changed, 124 insertions(+), 75 deletions(-) create mode 100644 identity-core/src/common/object.rs diff --git a/identity-core/Cargo.toml b/identity-core/Cargo.toml index 3c0d02e094..4d79d0e5a0 100644 --- a/identity-core/Cargo.toml +++ b/identity-core/Cargo.toml @@ -28,4 +28,5 @@ serde_json = { version = "1.0", default-features = false, features = ["preserve_ sha2 = { version = "0.9", default-features = false } subtle = { version = "2.4" } thiserror = { version = "1.0", default-features = false } +url = { version = "2.1", default-features = false, features = ["serde"] } zeroize = { version = "1.1", default-features = false } diff --git a/identity-core/src/common/context.rs b/identity-core/src/common/context.rs index 95e8801dcd..16cd5a7153 100644 --- a/identity-core/src/common/context.rs +++ b/identity-core/src/common/context.rs @@ -1,10 +1,12 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use core::fmt::{Debug, Formatter, Result}; -use serde::{Deserialize, Serialize}; +use core::fmt::Debug; +use core::fmt::Formatter; +use core::fmt::Result; -use crate::common::{Object, Url}; +use crate::common::Object; +use crate::common::Url; /// A reference to a JSON-LD context /// @@ -19,7 +21,7 @@ pub enum Context { } impl Debug for Context { - fn fmt(&self, f: &mut Formatter) -> Result { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { match self { Self::Url(inner) => Debug::fmt(inner, f), Self::Obj(inner) => Debug::fmt(inner, f), diff --git a/identity-core/src/common/mod.rs b/identity-core/src/common/mod.rs index e4902dd719..878a5b1418 100644 --- a/identity-core/src/common/mod.rs +++ b/identity-core/src/common/mod.rs @@ -4,12 +4,14 @@ //! Definitions of common types. mod context; +mod object; mod one_or_many; mod timestamp; mod url; -pub use context::Context; -pub use did_doc::{Object, Value}; -pub use one_or_many::OneOrMany; -pub use timestamp::Timestamp; -pub use url::Url; +pub use self::context::Context; +pub use self::object::Object; +pub use self::object::Value; +pub use self::one_or_many::OneOrMany; +pub use self::timestamp::Timestamp; +pub use self::url::Url; diff --git a/identity-core/src/common/object.rs b/identity-core/src/common/object.rs new file mode 100644 index 0000000000..799c291ebd --- /dev/null +++ b/identity-core/src/common/object.rs @@ -0,0 +1,10 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::BTreeMap; + +#[doc(import)] +pub use serde_json::Value; + +/// An alias for an ordered map of key-[value][`Value`] pairs. +pub type Object = BTreeMap; diff --git a/identity-core/src/common/one_or_many.rs b/identity-core/src/common/one_or_many.rs index 18c0c01a39..d78f150911 100644 --- a/identity-core/src/common/one_or_many.rs +++ b/identity-core/src/common/one_or_many.rs @@ -1,13 +1,14 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use core::{ - fmt::{Debug, Display, Formatter, Result as FmtResult}, - hash::Hash, - mem::replace, - ops::Deref, - slice::from_ref, -}; +use core::fmt::Debug; +use core::fmt::Display; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use core::hash::Hash; +use core::mem::replace; +use core::ops::Deref; +use core::slice::from_ref; /// A generic container that stores one or many values of a given type. #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] @@ -101,7 +102,7 @@ impl Debug for OneOrMany where T: Debug, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self { Self::One(inner) => Debug::fmt(inner, f), Self::Many(inner) => Debug::fmt(inner, f), @@ -114,7 +115,7 @@ where T: Display, Vec: Display, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self { Self::One(inner) => Display::fmt(inner, f), Self::Many(inner) => Display::fmt(inner, f), @@ -177,7 +178,7 @@ struct OneOrManyIter<'a, T> { } impl<'a, T> OneOrManyIter<'a, T> { - pub fn new(inner: &'a OneOrMany) -> Self { + fn new(inner: &'a OneOrMany) -> Self { Self { inner, index: 0 } } } diff --git a/identity-core/src/common/timestamp.rs b/identity-core/src/common/timestamp.rs index 6ecde93ccf..24a8515bb9 100644 --- a/identity-core/src/common/timestamp.rs +++ b/identity-core/src/common/timestamp.rs @@ -1,14 +1,18 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use chrono::{DateTime, SecondsFormat, Utc}; -use core::{ - convert::TryFrom, - fmt::{Debug, Display, Formatter, Result as FmtResult}, - str::FromStr, -}; - -use crate::error::{Error, Result}; +use chrono::DateTime; +use chrono::SecondsFormat; +use chrono::Utc; +use core::convert::TryFrom; +use core::fmt::Debug; +use core::fmt::Display; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use core::str::FromStr; + +use crate::error::Error; +use crate::error::Result; /// A parsed Timestamp. #[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] @@ -48,13 +52,13 @@ impl Default for Timestamp { } impl Debug for Timestamp { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!(f, "{:?}", self.to_rfc3339()) } } impl Display for Timestamp { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!(f, "{}", self.to_rfc3339()) } } diff --git a/identity-core/src/common/url.rs b/identity-core/src/common/url.rs index 6b7e76ea7c..698b68cf68 100644 --- a/identity-core/src/common/url.rs +++ b/identity-core/src/common/url.rs @@ -1,25 +1,27 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use core::{ - fmt::{Debug, Display, Formatter, Result as FmtResult}, - ops::{Deref, DerefMut}, - str::FromStr, -}; -use did_doc::url; +use core::fmt::Debug; +use core::fmt::Display; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use core::ops::Deref; +use core::ops::DerefMut; +use core::str::FromStr; -use crate::error::{Error, Result}; +use crate::error::Error; +use crate::error::Result; /// A parsed URL. #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] #[repr(transparent)] #[serde(transparent)] -pub struct Url(url::Url); +pub struct Url(::url::Url); impl Url { /// Parses an absolute [`Url`] from the given input string. pub fn parse(input: impl AsRef) -> Result { - url::Url::parse(input.as_ref()).map_err(Into::into).map(Self) + ::url::Url::parse(input.as_ref()).map_err(Into::into).map(Self) } /// Consumes the [`Url`] and returns the value as a `String`. @@ -34,19 +36,19 @@ impl Url { } impl Debug for Url { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { Debug::fmt(&self.0, f) } } impl Display for Url { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { Display::fmt(&self.0, f) } } impl Deref for Url { - type Target = url::Url; + type Target = ::url::Url; fn deref(&self) -> &Self::Target { &self.0 diff --git a/identity-core/src/convert/json.rs b/identity-core/src/convert/json.rs index 1c099163fb..7f5605d932 100644 --- a/identity-core/src/convert/json.rs +++ b/identity-core/src/convert/json.rs @@ -1,8 +1,11 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::error::{Error, Result}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; +use serde::Serialize; + +use crate::error::Error; +use crate::error::Result; /// A convenience-trait for types that can be serialized as JSON. pub trait ToJson: Serialize { diff --git a/identity-core/src/convert/mod.rs b/identity-core/src/convert/mod.rs index f5a8a98690..f9e24d154f 100644 --- a/identity-core/src/convert/mod.rs +++ b/identity-core/src/convert/mod.rs @@ -6,4 +6,7 @@ mod json; mod serde_into; -pub use self::{json::*, serde_into::*}; +pub use self::json::AsJson; +pub use self::json::FromJson; +pub use self::json::ToJson; +pub use self::serde_into::SerdeInto; diff --git a/identity-core/src/convert/serde_into.rs b/identity-core/src/convert/serde_into.rs index a4e1dc8477..698c831002 100644 --- a/identity-core/src/convert/serde_into.rs +++ b/identity-core/src/convert/serde_into.rs @@ -1,10 +1,9 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::{ - convert::{AsJson, ToJson}, - error::Result, -}; +use crate::convert::AsJson; +use crate::convert::ToJson; +use crate::error::Result; /// An escape-hatch for converting between types that represent the same JSON /// structure. diff --git a/identity-core/src/crypto/key_impl.rs b/identity-core/src/crypto/key_impl.rs index f31f92b74d..fa01024ee5 100644 --- a/identity-core/src/crypto/key_impl.rs +++ b/identity-core/src/crypto/key_impl.rs @@ -33,13 +33,13 @@ macro_rules! impl_bytes { } impl ::core::fmt::Debug for $ident { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.write_str(stringify!($ident)) } } impl ::core::fmt::Display for $ident { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.write_str(stringify!($ident)) } } diff --git a/identity-core/src/crypto/merkle_tree/consts.rs b/identity-core/src/crypto/merkle_tree/consts.rs index 8edf395390..4fe4c39c54 100644 --- a/identity-core/src/crypto/merkle_tree/consts.rs +++ b/identity-core/src/crypto/merkle_tree/consts.rs @@ -3,7 +3,7 @@ use core::mem; -pub const PREFIX_L: &[u8] = &[0x00]; -pub const PREFIX_B: &[u8] = &[0x01]; +pub(super) const PREFIX_L: &[u8] = &[0x00]; +pub(super) const PREFIX_B: &[u8] = &[0x01]; -pub const SIZE_USIZE: usize = mem::size_of::(); +pub(super) const SIZE_USIZE: usize = mem::size_of::(); diff --git a/identity-core/src/crypto/merkle_tree/hash.rs b/identity-core/src/crypto/merkle_tree/hash.rs index a3b24d02e7..12ca0fb61c 100644 --- a/identity-core/src/crypto/merkle_tree/hash.rs +++ b/identity-core/src/crypto/merkle_tree/hash.rs @@ -74,7 +74,7 @@ where } impl Debug for Hash { - fn fmt(&self, f: &mut Formatter) -> Result { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { f.write_str(&encode_b58(self)) } } diff --git a/identity-core/src/crypto/merkle_tree/math.rs b/identity-core/src/crypto/merkle_tree/math.rs index bdfc688a65..ed7311f70d 100644 --- a/identity-core/src/crypto/merkle_tree/math.rs +++ b/identity-core/src/crypto/merkle_tree/math.rs @@ -5,13 +5,13 @@ use crate::crypto::merkle_tree::consts; /// Return true if `value` is a power of two greater than `1`. #[inline(always)] -pub const fn is_pow2(value: usize) -> bool { +pub(super) const fn is_pow2(value: usize) -> bool { value > 1 && value.is_power_of_two() } /// Returns the base-2 logarithm of `value`, rounded up. #[inline(always)] -pub fn log2c(value: usize) -> usize { +pub(super) fn log2c(value: usize) -> usize { (consts::SIZE_USIZE * 8) - value.leading_zeros() as usize - value.is_power_of_two() as usize } diff --git a/identity-core/src/crypto/merkle_tree/merkle.rs b/identity-core/src/crypto/merkle_tree/merkle.rs index 233c6542ca..b09a3fd4ca 100644 --- a/identity-core/src/crypto/merkle_tree/merkle.rs +++ b/identity-core/src/crypto/merkle_tree/merkle.rs @@ -148,7 +148,7 @@ impl Debug for MTree where D: Digest, { - fn fmt(&self, f: &mut Formatter) -> Result { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { let mut f = f.debug_struct("MTree"); let total: usize = self.height(); diff --git a/identity-core/src/crypto/merkle_tree/node.rs b/identity-core/src/crypto/merkle_tree/node.rs index ac075ce02b..00f424dc1a 100644 --- a/identity-core/src/crypto/merkle_tree/node.rs +++ b/identity-core/src/crypto/merkle_tree/node.rs @@ -41,7 +41,7 @@ impl Node { } impl Debug for Node { - fn fmt(&self, f: &mut Formatter) -> Result { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { match self { Self::L(hash) => f.write_fmt(format_args!("L({:?})", hash)), Self::R(hash) => f.write_fmt(format_args!("R({:?})", hash)), diff --git a/identity-core/src/crypto/merkle_tree/proof.rs b/identity-core/src/crypto/merkle_tree/proof.rs index da3354183e..d2665c73a9 100644 --- a/identity-core/src/crypto/merkle_tree/proof.rs +++ b/identity-core/src/crypto/merkle_tree/proof.rs @@ -44,7 +44,7 @@ impl Proof { } impl Debug for Proof { - fn fmt(&self, f: &mut Formatter) -> Result { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { f.debug_struct("Proof").field("nodes", &self.nodes).finish() } } diff --git a/identity-core/src/crypto/merkle_tree/tree.rs b/identity-core/src/crypto/merkle_tree/tree.rs index df5df96562..c4426fe8ed 100644 --- a/identity-core/src/crypto/merkle_tree/tree.rs +++ b/identity-core/src/crypto/merkle_tree/tree.rs @@ -9,35 +9,35 @@ use crate::crypto::merkle_tree::DigestExt; use crate::crypto::merkle_tree::Hash; #[inline(always)] -pub fn height(leaves: usize) -> usize { +pub(super) fn height(leaves: usize) -> usize { math::log2c(leaves) } #[inline(always)] -pub const fn total(leaves: usize) -> usize { +pub(super) const fn total(leaves: usize) -> usize { // 2l - 1 (leaves << 1) - 1 } #[inline(always)] -pub const fn leaves(nodes: usize) -> usize { +pub(super) const fn leaves(nodes: usize) -> usize { // l = (n + 1) / 2 (nodes + 1) >> 1 } #[inline(always)] -pub const fn index_lhs(index: usize) -> usize { +pub(super) const fn index_lhs(index: usize) -> usize { // 2i + 1 (index << 1) + 1 } #[inline(always)] -pub const fn index_rhs(index: usize) -> usize { +pub(super) const fn index_rhs(index: usize) -> usize { // 2i + 2 (index << 1) + 2 } -pub fn compute_nodes(digest: &mut D, leaves: &[Hash]) -> Box<[Hash]> +pub(super) fn compute_nodes(digest: &mut D, leaves: &[Hash]) -> Box<[Hash]> where D: Digest, Output: Copy, diff --git a/identity-core/src/error.rs b/identity-core/src/error.rs index f093c316e4..76b284742f 100644 --- a/identity-core/src/error.rs +++ b/identity-core/src/error.rs @@ -32,7 +32,7 @@ pub enum Error { InvalidDiff(#[from] identity_diff::Error), /// Caused by attempting to parse an invalid `Url`. #[error("Invalid Url: {0}")] - InvalidUrl(#[from] did_doc::url::ParseError), + InvalidUrl(#[from] url::ParseError), /// Caused by attempting to parse an invalid `Timestamp`. #[error("Invalid Timestamp: {0}")] InvalidTimestamp(#[from] chrono::ParseError), diff --git a/identity-core/src/lib.rs b/identity-core/src/lib.rs index d270551d21..45b64a8bb9 100644 --- a/identity-core/src/lib.rs +++ b/identity-core/src/lib.rs @@ -4,6 +4,8 @@ //! Identity Core #![warn( + rust_2018_idioms, + unreachable_pub, missing_docs, missing_crate_level_docs, broken_intra_doc_links, @@ -21,7 +23,6 @@ pub use did_url; pub use identity_diff; pub use serde_json::json; -#[macro_use] pub mod common; pub mod convert; pub mod crypto; @@ -30,4 +31,5 @@ pub mod proof; pub mod resolver; pub mod utils; -pub use error::{Error, Result}; +pub use self::error::Error; +pub use self::error::Result; diff --git a/identity-core/src/utils/base_encoding.rs b/identity-core/src/utils/base_encoding.rs index 1748a60302..8b98e79011 100644 --- a/identity-core/src/utils/base_encoding.rs +++ b/identity-core/src/utils/base_encoding.rs @@ -1,7 +1,8 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::error::{Error, Result}; +use crate::error::Error; +use crate::error::Result; /// Decodes the given `data` as base58-btc. pub fn decode_b58(data: &T) -> Result> diff --git a/identity-core/src/utils/jcs_sha256.rs b/identity-core/src/utils/jcs_sha256.rs index 5f59845bd9..abfe25f1ee 100644 --- a/identity-core/src/utils/jcs_sha256.rs +++ b/identity-core/src/utils/jcs_sha256.rs @@ -1,9 +1,12 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use sha2::{digest::Output, Digest, Sha256}; +use sha2::digest::Digest; +use sha2::digest::Output; +use sha2::Sha256; -use crate::{convert::ToJson, error::Result}; +use crate::convert::ToJson; +use crate::error::Result; /// Returns the given `data` serialized using JSON Canonicalization Scheme and /// hashed using SHA-256. diff --git a/identity-credential/src/credential/credential.rs b/identity-credential/src/credential/credential.rs index 9a469d48c9..9e63c9a246 100644 --- a/identity-credential/src/credential/credential.rs +++ b/identity-credential/src/credential/credential.rs @@ -190,7 +190,7 @@ impl Display for Credential where T: Serialize, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { if f.alternate() { f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) } else { diff --git a/identity-credential/src/credential/verifiable.rs b/identity-credential/src/credential/verifiable.rs index 76c118f53c..78da2b39dd 100644 --- a/identity-credential/src/credential/verifiable.rs +++ b/identity-credential/src/credential/verifiable.rs @@ -69,7 +69,7 @@ impl Display for VerifiableCredential where T: Serialize, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { if f.alternate() { f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) } else { diff --git a/identity-credential/src/lib.rs b/identity-credential/src/lib.rs index 3323af0bd8..b6de868174 100644 --- a/identity-credential/src/lib.rs +++ b/identity-credential/src/lib.rs @@ -4,6 +4,8 @@ //! Types and traits for working with Verifiable Credentials/Presentations. #![warn( + rust_2018_idioms, + unreachable_pub, missing_docs, missing_crate_level_docs, broken_intra_doc_links, diff --git a/identity-credential/src/presentation/presentation.rs b/identity-credential/src/presentation/presentation.rs index 2b91e27de0..f161d0b9ed 100644 --- a/identity-credential/src/presentation/presentation.rs +++ b/identity-credential/src/presentation/presentation.rs @@ -114,7 +114,7 @@ where T: Serialize, U: Serialize, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { if f.alternate() { f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) } else { diff --git a/identity-credential/src/presentation/verifiable.rs b/identity-credential/src/presentation/verifiable.rs index 76c1bed3e4..1a30dec4bd 100644 --- a/identity-credential/src/presentation/verifiable.rs +++ b/identity-credential/src/presentation/verifiable.rs @@ -70,7 +70,7 @@ where T: Serialize, U: Serialize, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { if f.alternate() { f.write_str(&self.to_json_pretty().map_err(|_| FmtError)?) } else { diff --git a/identity-did/src/lib.rs b/identity-did/src/lib.rs index 0ad1b8ad5c..db12812eaa 100644 --- a/identity-did/src/lib.rs +++ b/identity-did/src/lib.rs @@ -1,6 +1,20 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +//! Types and traits for working with Decentralized Identifiers. + +#![warn( + rust_2018_idioms, + unreachable_pub, + // missing_docs, + missing_crate_level_docs, + broken_intra_doc_links, + private_intra_doc_links, + private_doc_tests, + clippy::missing_safety_doc, + // clippy::missing_errors_doc +)] + #[macro_use] extern crate serde; From 37d78e882ab5be2fdfa710359247b311d5c728db Mon Sep 17 00:00:00 2001 From: l1h3r Date: Mon, 1 Feb 2021 18:40:05 -0800 Subject: [PATCH 08/28] Move resolution to identity-did crate --- identity-core/Cargo.toml | 1 - identity-core/src/resolver/mod.rs | 24 ---- identity-did/Cargo.toml | 1 + identity-did/src/document/builder.rs | 2 +- identity-did/src/document/document.rs | 5 +- identity-did/src/error.rs | 20 ++++ identity-did/src/lib.rs | 1 + .../src/resolution}/dereference.rs | 6 +- .../src/resolution}/document_metadata.rs | 4 +- .../src/resolution}/error_kind.rs | 2 - .../src/resolution}/impls.rs | 108 ++++++++++++------ .../src/resolution}/input_metadata.rs | 4 +- identity-did/src/resolution/mod.rs | 32 ++++++ .../src/resolution}/resolution.rs | 7 +- .../src/resolution}/resolution_metadata.rs | 6 +- .../src/resolution}/resource.rs | 30 ++--- .../src/resolution}/traits.rs | 12 +- identity-did/src/service/builder.rs | 2 +- identity-did/src/service/service.rs | 2 +- identity-did/src/utils/did_key.rs | 3 +- identity-did/src/utils/ordered_set.rs | 2 +- identity-did/src/verifiable/document.rs | 2 +- identity-did/src/verification/builder.rs | 3 +- identity-did/src/verification/method.rs | 2 +- identity-did/src/verification/method_ident.rs | 2 +- identity-did/src/verification/method_ref.rs | 2 +- 26 files changed, 170 insertions(+), 115 deletions(-) delete mode 100644 identity-core/src/resolver/mod.rs rename {identity-core/src/resolver => identity-did/src/resolution}/dereference.rs (89%) rename {identity-core/src/resolver => identity-did/src/resolution}/document_metadata.rs (92%) rename {identity-core/src/resolver => identity-did/src/resolution}/error_kind.rs (95%) rename {identity-core/src/resolver => identity-did/src/resolution}/impls.rs (79%) rename {identity-core/src/resolver => identity-did/src/resolution}/input_metadata.rs (94%) create mode 100644 identity-did/src/resolution/mod.rs rename {identity-core/src/resolver => identity-did/src/resolution}/resolution.rs (88%) rename {identity-core/src/resolver => identity-did/src/resolution}/resolution_metadata.rs (93%) rename {identity-core/src/resolver => identity-did/src/resolution}/resource.rs (82%) rename {identity-core/src/resolver => identity-did/src/resolution}/traits.rs (87%) diff --git a/identity-core/Cargo.toml b/identity-core/Cargo.toml index 4d79d0e5a0..ea4831b72d 100644 --- a/identity-core/Cargo.toml +++ b/identity-core/Cargo.toml @@ -12,7 +12,6 @@ homepage = "https://www.iota.org" [dependencies] anyhow = { version = "1.0", default-features = false } -async-trait = { version = "0.1", default-features = false } bs58 = { version = "0.4", default-features = false, features = ["std"] } chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } did_doc = { git = "https://github.com/l1h3r/did_doc", rev = "bb8bda8f6d39451b08e3cc198f956c212a3b87f8", default-features = false, features = ["std"] } diff --git a/identity-core/src/resolver/mod.rs b/identity-core/src/resolver/mod.rs deleted file mode 100644 index 6074bd267c..0000000000 --- a/identity-core/src/resolver/mod.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! Types and traits for supporting DID Document resolution. - -mod dereference; -mod document_metadata; -mod error_kind; -mod impls; -mod input_metadata; -mod resolution; -mod resolution_metadata; -mod resource; -mod traits; - -pub use dereference::*; -pub use document_metadata::*; -pub use error_kind::*; -pub use impls::*; -pub use input_metadata::*; -pub use resolution::*; -pub use resolution_metadata::*; -pub use resource::*; -pub use traits::*; diff --git a/identity-did/Cargo.toml b/identity-did/Cargo.toml index b418c00a22..f77ba00bb4 100644 --- a/identity-did/Cargo.toml +++ b/identity-did/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["iota", "tangle", "identity"] homepage = "https://www.iota.org" [dependencies] +async-trait = { version = "0.1", default-features = false } did_url = { version = "0.1", default-features = false, features = ["alloc", "serde"] } identity-core = { version = "=0.1.0", path = "../identity-core" } serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } diff --git a/identity-did/src/document/builder.rs b/identity-did/src/document/builder.rs index 251ee373f3..06d639fcd0 100644 --- a/identity-did/src/document/builder.rs +++ b/identity-did/src/document/builder.rs @@ -1,9 +1,9 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use did_url::DID; use identity_core::common::Url; +use crate::did::DID; use crate::document::Document; use crate::error::Result; use crate::service::Service; diff --git a/identity-did/src/document/document.rs b/identity-did/src/document/document.rs index 4ba779f09a..8283e19168 100644 --- a/identity-did/src/document/document.rs +++ b/identity-did/src/document/document.rs @@ -6,11 +6,11 @@ use core::fmt::Display; use core::fmt::Error as FmtError; use core::fmt::Formatter; use core::fmt::Result as FmtResult; -use did_url::DID; use identity_core::common::Url; use identity_core::convert::ToJson; use serde::Serialize; +use crate::did::DID; use crate::document::Builder; use crate::error::Error; use crate::error::Result; @@ -333,8 +333,7 @@ impl ResolveMethod for Document { #[cfg(test)] mod tests { - use did_url::DID; - + use crate::did::DID; use crate::document::Builder; use crate::document::Document; use crate::verification::Builder as MethodBuilder; diff --git a/identity-did/src/error.rs b/identity-did/src/error.rs index f3c5e3f703..b54a7236b4 100644 --- a/identity-did/src/error.rs +++ b/identity-did/src/error.rs @@ -9,6 +9,11 @@ pub type Result = ::core::result::Result; /// This type represents all possible errors that can occur in the library. #[derive(Debug, thiserror::Error)] pub enum Error { + #[error("{0}")] + CoreError(#[from] ::identity_core::Error), + #[error("{0}")] + DIDError(#[from] ::did_url::Error), + #[error("Duplicate Item in Ordered Set")] OrderedSetDuplicate, #[error("Verification Method Not Found")] @@ -50,4 +55,19 @@ pub enum Error { InvalidKeyDataBase16, #[error("Invalid Base58 Key Data")] InvalidKeyDataBase58, + + #[error("Missing Resolution DID")] + MissingResolutionDID, + #[error("Missing Resolution Metadata")] + MissingResolutionMetadata, + #[error("Missing Resolution Document")] + MissingResolutionDocument, + #[error("Missing Resolution Document/Metadata")] + MissingResolutionData, + #[error("Invalid DID Resolution Query")] + InvalidDIDQuery, + #[error("Invalid DID Resolution Fragment")] + InvalidDIDFragment, + #[error("Invalid DID Resolution Service")] + InvalidServiceProtocol, } diff --git a/identity-did/src/lib.rs b/identity-did/src/lib.rs index db12812eaa..5082fc8307 100644 --- a/identity-did/src/lib.rs +++ b/identity-did/src/lib.rs @@ -25,6 +25,7 @@ pub mod did { pub mod document; pub mod error; +pub mod resolution; pub mod service; pub mod signature; pub mod utils; diff --git a/identity-core/src/resolver/dereference.rs b/identity-did/src/resolution/dereference.rs similarity index 89% rename from identity-core/src/resolver/dereference.rs rename to identity-did/src/resolution/dereference.rs index 0e8a120707..d97834f329 100644 --- a/identity-core/src/resolver/dereference.rs +++ b/identity-did/src/resolution/dereference.rs @@ -1,9 +1,9 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use serde::{Deserialize, Serialize}; - -use crate::resolver::{DocumentMetadata, ResolutionMetadata, Resource}; +use crate::resolution::DocumentMetadata; +use crate::resolution::ResolutionMetadata; +use crate::resolution::Resource; /// The output returned from [DID URL dereferencing][SPEC]. /// diff --git a/identity-core/src/resolver/document_metadata.rs b/identity-did/src/resolution/document_metadata.rs similarity index 92% rename from identity-core/src/resolver/document_metadata.rs rename to identity-did/src/resolution/document_metadata.rs index fa9ba12917..b7f650424a 100644 --- a/identity-core/src/resolver/document_metadata.rs +++ b/identity-did/src/resolution/document_metadata.rs @@ -1,8 +1,8 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::common::{Object, Timestamp}; -use serde::{Deserialize, Serialize}; +use identity_core::common::Object; +use identity_core::common::Timestamp; /// Metadata associated with a resolved DID Document. #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] diff --git a/identity-core/src/resolver/error_kind.rs b/identity-did/src/resolution/error_kind.rs similarity index 95% rename from identity-core/src/resolver/error_kind.rs rename to identity-did/src/resolution/error_kind.rs index fb90e7e578..b4d7dd431d 100644 --- a/identity-core/src/resolver/error_kind.rs +++ b/identity-did/src/resolution/error_kind.rs @@ -1,8 +1,6 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use serde::{Deserialize, Serialize}; - /// Types of errors that be returned from a [DID resolution][SPEC] process. /// /// [SPEC]: https://www.w3.org/TR/did-core/#dfn-did-resolution diff --git a/identity-core/src/resolver/impls.rs b/identity-did/src/resolution/impls.rs similarity index 79% rename from identity-core/src/resolver/impls.rs rename to identity-did/src/resolution/impls.rs index 8003d86e1f..bd28ce9b5d 100644 --- a/identity-core/src/resolver/impls.rs +++ b/identity-did/src/resolution/impls.rs @@ -1,18 +1,25 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use anyhow::anyhow; -use did_doc::{url::Url, Document}; -use did_url::DID; +use identity_core::common::Url; use std::time::Instant; -use crate::{ - error::{Error, Result}, - resolver::{ - Dereference, DocumentMetadata, ErrorKind, InputMetadata, MetaDocument, PrimaryResource, Resolution, ResolverMethod, - Resource, SecondaryResource, - }, -}; +use crate::did::DID; +use crate::document::Document; +use crate::error::Error; +use crate::error::Result; +use crate::resolution::Dereference; +use crate::resolution::DocumentMetadata; +use crate::resolution::ErrorKind; +use crate::resolution::InputMetadata; +use crate::resolution::MetaDocument; +use crate::resolution::PrimaryResource; +use crate::resolution::Resolution; +use crate::resolution::ResolverMethod; +use crate::resolution::Resource; +use crate::resolution::SecondaryResource; +use crate::utils::DIDKey; +use crate::utils::OrderedSet; /// Resolves a DID into a DID Document by using the "Read" operation of the DID method. /// @@ -83,17 +90,16 @@ where // Extract the document and metadata - Both properties MUST exist as we // checked for resolution errors above. - let (document, metadata): (Document, DocumentMetadata) = resolution - .document - .zip(resolution.document_metadata) - .ok_or_else(|| Error::DereferenceError(anyhow!("Missing Document/Metadata")))?; + let (document, metadata): (Document, DocumentMetadata) = match (resolution.document, resolution.document_metadata) { + (Some(document), Some(metadata)) => (document, metadata), + (Some(_), None) => return Err(Error::MissingResolutionMetadata), + (None, Some(_)) => return Err(Error::MissingResolutionDocument), + (None, None) => return Err(Error::MissingResolutionData), + }; // Extract the parsed DID from the resolution output - It MUST exist as we // checked for resolution errors above. - let did: DID = resolution - .metadata - .resolved - .ok_or_else(|| Error::DereferenceError(anyhow!("Missing Resolved DID")))?; + let did: DID = resolution.metadata.resolved.ok_or(Error::MissingResolutionDID)?; // Add the resolution document metadata to the response. context.set_metadata(metadata); @@ -233,25 +239,51 @@ fn dereference_primary(document: Document, mut did: DID) -> Result Result> { - macro_rules! extract { - ($base:expr, $target:expr, $iter:expr) => { - for object in $iter.iter() { - let did: DID = $base.join(object.id())?; - - if matches!(did.fragment(), Some(fragment) if fragment == $target) { - return Ok(Some(object.clone().into())); - } - } - }; + #[inline] + fn dereference(base: &DID, query: &str, resources: &OrderedSet>) -> Result> + where + T: Clone + AsRef + Into, + { + for object in resources.iter() { + let did: DID = base.join((**object).as_ref())?; + + if matches!(did.fragment(), Some(fragment) if fragment == query) { + return Ok(Some(object.clone().into())); + } } - extract!(document.id(), fragment, document.verification_method()); - extract!(document.id(), fragment, document.authentication()); - extract!(document.id(), fragment, document.assertion_method()); - extract!(document.id(), fragment, document.key_agreement()); - extract!(document.id(), fragment, document.capability_delegation()); - extract!(document.id(), fragment, document.capability_invocation()); - extract!(document.id(), fragment, document.service()); + Ok(None) + } + + let base: &DID = document.id(); + + if let Some(resource) = dereference(base, fragment, document.verification_method())? { + return Ok(Some(resource)); + } + + if let Some(resource) = dereference(base, fragment, document.authentication())? { + return Ok(Some(resource)); + } + + if let Some(resource) = dereference(base, fragment, document.assertion_method())? { + return Ok(Some(resource)); + } + + if let Some(resource) = dereference(base, fragment, document.key_agreement())? { + return Ok(Some(resource)); + } + + if let Some(resource) = dereference(base, fragment, document.capability_delegation())? { + return Ok(Some(resource)); + } + + if let Some(resource) = dereference(base, fragment, document.capability_invocation())? { + return Ok(Some(resource)); + } + + if let Some(resource) = dereference(base, fragment, document.service())? { + return Ok(Some(resource)); + } Ok(None) } @@ -263,18 +295,18 @@ fn service_endpoint_ctor(did: DID, url: &Url) -> Result { // The input DID URL and input service endpoint URL MUST NOT both have a // query component. if did.query().is_some() && url.query().is_some() { - return Err(Error::DereferenceError(anyhow!("Multiple DID Queries"))); + return Err(Error::InvalidDIDQuery); } // The input DID URL and input service endpoint URL MUST NOT both have a // fragment component. if did.fragment().is_some() && url.fragment().is_some() { - return Err(Error::DereferenceError(anyhow!("Multiple DID Fragments"))); + return Err(Error::InvalidDIDFragment); } // The input service endpoint URL MUST be an HTTP(S) URL. if url.scheme() != "https" { - return Err(Error::DereferenceError(anyhow!("Invalid Service Protocol"))); + return Err(Error::InvalidServiceProtocol); } // 1. Initialize a string output service endpoint URL to the value of diff --git a/identity-core/src/resolver/input_metadata.rs b/identity-did/src/resolution/input_metadata.rs similarity index 94% rename from identity-core/src/resolver/input_metadata.rs rename to identity-did/src/resolution/input_metadata.rs index edc4be6334..6944caa0d1 100644 --- a/identity-core/src/resolver/input_metadata.rs +++ b/identity-did/src/resolution/input_metadata.rs @@ -1,9 +1,7 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use serde::{Deserialize, Serialize}; - -use crate::common::Object; +use identity_core::common::Object; /// The content type of a JSON DID Document. pub const MIME_DID: &str = "application/did+json"; diff --git a/identity-did/src/resolution/mod.rs b/identity-did/src/resolution/mod.rs new file mode 100644 index 0000000000..9b6104f0a7 --- /dev/null +++ b/identity-did/src/resolution/mod.rs @@ -0,0 +1,32 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Types and traits for DID Document resolution. + +#![allow(clippy::module_inception)] + +mod dereference; +mod document_metadata; +mod error_kind; +mod impls; +mod input_metadata; +mod resolution; +mod resolution_metadata; +mod resource; +mod traits; + +pub use self::dereference::Dereference; +pub use self::document_metadata::DocumentMetadata; +pub use self::error_kind::ErrorKind; +pub use self::impls::dereference; +pub use self::impls::resolve; +pub use self::input_metadata::InputMetadata; +pub use self::input_metadata::MIME_DID; +pub use self::input_metadata::MIME_DID_LD; +pub use self::resolution::Resolution; +pub use self::resolution_metadata::ResolutionMetadata; +pub use self::resource::PrimaryResource; +pub use self::resource::Resource; +pub use self::resource::SecondaryResource; +pub use self::traits::MetaDocument; +pub use self::traits::ResolverMethod; diff --git a/identity-core/src/resolver/resolution.rs b/identity-did/src/resolution/resolution.rs similarity index 88% rename from identity-core/src/resolver/resolution.rs rename to identity-did/src/resolution/resolution.rs index 8c32a45c27..34433928a0 100644 --- a/identity-core/src/resolver/resolution.rs +++ b/identity-did/src/resolution/resolution.rs @@ -1,10 +1,9 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use did_doc::Document; -use serde::{Deserialize, Serialize}; - -use crate::resolver::{DocumentMetadata, ResolutionMetadata}; +use crate::document::Document; +use crate::resolution::DocumentMetadata; +use crate::resolution::ResolutionMetadata; /// The output returned from [DID resolution][SPEC]. /// diff --git a/identity-core/src/resolver/resolution_metadata.rs b/identity-did/src/resolution/resolution_metadata.rs similarity index 93% rename from identity-core/src/resolver/resolution_metadata.rs rename to identity-did/src/resolution/resolution_metadata.rs index 75400e4086..418ac0e37b 100644 --- a/identity-core/src/resolver/resolution_metadata.rs +++ b/identity-did/src/resolution/resolution_metadata.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use core::time::Duration; -use did_url::DID; -use serde::{Deserialize, Serialize}; +use identity_core::common::Object; -use crate::{common::Object, resolver::ErrorKind}; +use crate::did::DID; +use crate::resolution::ErrorKind; /// Metadata associated with a [DID resolution][SPEC] process. /// diff --git a/identity-core/src/resolver/resource.rs b/identity-did/src/resolution/resource.rs similarity index 82% rename from identity-core/src/resolver/resource.rs rename to identity-did/src/resolution/resource.rs index 67d039f527..df039b525f 100644 --- a/identity-core/src/resolver/resource.rs +++ b/identity-did/src/resolution/resource.rs @@ -1,9 +1,14 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use did_doc::{url::Url, DIDKey, Document, Method, MethodRef, Service}; -use did_url::DID; -use serde::{Deserialize, Serialize}; +use identity_core::common::Url; + +use crate::did::DID; +use crate::document::Document; +use crate::service::Service; +use crate::utils::DIDKey; +use crate::verification::Method; +use crate::verification::MethodRef; /// A resource returned from a [DID URL dereferencing][SPEC] process. /// @@ -88,20 +93,17 @@ impl From for SecondaryResource { } } -impl From> for SecondaryResource { - fn from(other: DIDKey) -> Self { - other.into_inner().into() +impl From for SecondaryResource { + fn from(other: Service) -> Self { + Self::Service(other) } } -impl From> for SecondaryResource { - fn from(other: DIDKey) -> Self { +impl From> for SecondaryResource +where + T: Into, +{ + fn from(other: DIDKey) -> Self { other.into_inner().into() } } - -impl From> for SecondaryResource { - fn from(other: DIDKey) -> Self { - Self::Service(other.into_inner()) - } -} diff --git a/identity-core/src/resolver/traits.rs b/identity-did/src/resolution/traits.rs similarity index 87% rename from identity-core/src/resolver/traits.rs rename to identity-did/src/resolution/traits.rs index f18075dd94..a46c9e89ee 100644 --- a/identity-core/src/resolver/traits.rs +++ b/identity-did/src/resolution/traits.rs @@ -2,14 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use async_trait::async_trait; -use did_doc::Document; -use did_url::DID; -use serde::{Deserialize, Serialize}; -use crate::{ - error::Result, - resolver::{DocumentMetadata, InputMetadata}, -}; +use crate::did::DID; +use crate::document::Document; +use crate::error::Result; +use crate::resolution::DocumentMetadata; +use crate::resolution::InputMetadata; /// A resolved [`Document`] and associated [`DocumentMetadata`]. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] diff --git a/identity-did/src/service/builder.rs b/identity-did/src/service/builder.rs index 490cf1191b..d3545bf237 100644 --- a/identity-did/src/service/builder.rs +++ b/identity-did/src/service/builder.rs @@ -1,9 +1,9 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use did_url::DID; use identity_core::common::Url; +use crate::did::DID; use crate::error::Result; use crate::service::Service; diff --git a/identity-did/src/service/service.rs b/identity-did/src/service/service.rs index cc7b7902bb..a38650ae17 100644 --- a/identity-did/src/service/service.rs +++ b/identity-did/src/service/service.rs @@ -5,11 +5,11 @@ use core::fmt::Display; use core::fmt::Error as FmtError; use core::fmt::Formatter; use core::fmt::Result as FmtResult; -use did_url::DID; use identity_core::common::Url; use identity_core::convert::ToJson; use serde::Serialize; +use crate::did::DID; use crate::error::Error; use crate::error::Result; use crate::service::Builder; diff --git a/identity-did/src/utils/did_key.rs b/identity-did/src/utils/did_key.rs index 5b9c498a1e..aac15ae173 100644 --- a/identity-did/src/utils/did_key.rs +++ b/identity-did/src/utils/did_key.rs @@ -13,7 +13,8 @@ use core::hash::Hash; use core::hash::Hasher; use core::ops::Deref; use core::ops::DerefMut; -use did_url::DID; + +use crate::did::DID; /// A helper struct for comparing types only by `DID`. /// diff --git a/identity-did/src/utils/ordered_set.rs b/identity-did/src/utils/ordered_set.rs index 3027bba757..bf96dc39fe 100644 --- a/identity-did/src/utils/ordered_set.rs +++ b/identity-did/src/utils/ordered_set.rs @@ -239,8 +239,8 @@ where #[cfg(test)] mod tests { use super::*; - use did_url::DID; + use crate::did::DID; use crate::utils::DIDKey; use crate::verification::MethodRef; diff --git a/identity-did/src/verifiable/document.rs b/identity-did/src/verifiable/document.rs index 4233e401a4..3ff169a810 100644 --- a/identity-did/src/verifiable/document.rs +++ b/identity-did/src/verifiable/document.rs @@ -11,11 +11,11 @@ use serde::Serialize; use crate::document::Document as Document_; use crate::signature::Signature; +use crate::verifiable::Properties; use crate::verifiable::ResolveMethod; use crate::verifiable::SetSignature; use crate::verifiable::TrySignature; use crate::verifiable::TrySignatureMut; -use crate::verifiable::Properties; use crate::verification::MethodQuery; use crate::verification::MethodWrap; diff --git a/identity-did/src/verification/builder.rs b/identity-did/src/verification/builder.rs index ea77fb798d..7ea780ef44 100644 --- a/identity-did/src/verification/builder.rs +++ b/identity-did/src/verification/builder.rs @@ -1,8 +1,7 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use did_url::DID; - +use crate::did::DID; use crate::error::Result; use crate::verification::Method; use crate::verification::MethodData; diff --git a/identity-did/src/verification/method.rs b/identity-did/src/verification/method.rs index 2df635b7d1..865128e8a2 100644 --- a/identity-did/src/verification/method.rs +++ b/identity-did/src/verification/method.rs @@ -6,10 +6,10 @@ use core::fmt::Error as FmtError; use core::fmt::Formatter; use core::fmt::Result as FmtResult; use core::iter::once; -use did_url::DID; use identity_core::convert::ToJson; use serde::Serialize; +use crate::did::DID; use crate::error::Error; use crate::error::Result; use crate::verification::Builder; diff --git a/identity-did/src/verification/method_ident.rs b/identity-did/src/verification/method_ident.rs index 3c663d0c75..7f80c6651d 100644 --- a/identity-did/src/verification/method_ident.rs +++ b/identity-did/src/verification/method_ident.rs @@ -1,7 +1,7 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use did_url::DID; +use crate::did::DID; /// Index or identifier used to identify the target verification method of a /// `MethodQuery`. diff --git a/identity-did/src/verification/method_ref.rs b/identity-did/src/verification/method_ref.rs index 8366bfde81..a25b9df185 100644 --- a/identity-did/src/verification/method_ref.rs +++ b/identity-did/src/verification/method_ref.rs @@ -4,8 +4,8 @@ use core::fmt::Debug; use core::fmt::Formatter; use core::fmt::Result as FmtResult; -use did_url::DID; +use crate::did::DID; use crate::verification::Method; /// A reference to a verification method, either a `DID` or embedded `Method`. From 75f096135f9009dce9ac969c90c315c008122b5a Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 08:23:26 -0800 Subject: [PATCH 09/28] Use sealed generics for key types --- identity-core/src/crypto/key/key.rs | 89 +++++++++++++++++++ identity-core/src/crypto/key/mod.rs | 9 ++ .../src/crypto/{key_pair.rs => key/pair.rs} | 24 ++--- identity-core/src/crypto/key_impl.rs | 50 ----------- identity-core/src/crypto/mod.rs | 3 +- 5 files changed, 113 insertions(+), 62 deletions(-) create mode 100644 identity-core/src/crypto/key/key.rs create mode 100644 identity-core/src/crypto/key/mod.rs rename identity-core/src/crypto/{key_pair.rs => key/pair.rs} (62%) delete mode 100644 identity-core/src/crypto/key_impl.rs diff --git a/identity-core/src/crypto/key/key.rs b/identity-core/src/crypto/key/key.rs new file mode 100644 index 0000000000..567c68e0f0 --- /dev/null +++ b/identity-core/src/crypto/key/key.rs @@ -0,0 +1,89 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::fmt::Debug; +use core::fmt::Display; +use core::fmt::Formatter; +use core::fmt::Result; +use core::marker::PhantomData; +use zeroize::Zeroize; + +/// A cryptographic key with `Public` components. +pub type PublicKey = Key; + +/// A cryptographic key with `Secret` components. +pub type SecretKey = Key; + +// ============================================================================= +// ============================================================================= + +mod private { + pub trait Sealed {} +} + +// A marker type for the `Public` components of a cryptographic key. +#[derive(Clone, Copy, Debug)] +pub enum Public {} + +// A marker type for the `Secret` components of a cryptographic key. +#[derive(Clone, Copy, Debug)] +pub enum Secret {} + +impl private::Sealed for Public {} + +impl private::Sealed for Secret {} + +// ============================================================================= +// ============================================================================= + +/// A cryptographic key. +#[derive(Clone)] +pub struct Key { + key: Box<[u8]>, + vis: PhantomData, +} + +impl Debug for Key { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.write_str("Key") + } +} + +impl Display for Key { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.write_str("Key") + } +} + +impl Drop for Key { + fn drop(&mut self) { + self.key.zeroize(); + } +} + +impl Zeroize for Key { + fn zeroize(&mut self) { + self.key.zeroize(); + } +} + +impl AsRef<[u8]> for Key { + fn as_ref(&self) -> &[u8] { + &self.key + } +} + +impl From> for Key { + fn from(other: Box<[u8]>) -> Self { + Self { + key: other, + vis: PhantomData, + } + } +} + +impl From> for Key { + fn from(other: Vec) -> Self { + other.into_boxed_slice().into() + } +} diff --git a/identity-core/src/crypto/key/mod.rs b/identity-core/src/crypto/key/mod.rs new file mode 100644 index 0000000000..a5ac5697a9 --- /dev/null +++ b/identity-core/src/crypto/key/mod.rs @@ -0,0 +1,9 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod key; +mod pair; + +pub use self::key::PublicKey; +pub use self::key::SecretKey; +pub use self::pair::KeyPair; diff --git a/identity-core/src/crypto/key_pair.rs b/identity-core/src/crypto/key/pair.rs similarity index 62% rename from identity-core/src/crypto/key_pair.rs rename to identity-core/src/crypto/key/pair.rs index bf16caad4d..39623ad987 100644 --- a/identity-core/src/crypto/key_pair.rs +++ b/identity-core/src/crypto/key/pair.rs @@ -3,39 +3,43 @@ use zeroize::Zeroize; -use crate::crypto::{PublicKey, SecretKey}; +use crate::crypto::PublicKey; +use crate::crypto::SecretKey; -/// A convenience for storing a pair of public/secret keys +/// A convenience for storing a pair of cryptographic keys #[derive(Clone, Debug)] -pub struct KeyPair(PublicKey, SecretKey); +pub struct KeyPair { + public: PublicKey, + secret: SecretKey, +} impl KeyPair { /// Creates a new [`KeyPair`] from the given keys. pub const fn new(public: PublicKey, secret: SecretKey) -> Self { - Self(public, secret) + Self { public, secret } } /// Returns a reference to the [`PublicKey`] object. pub const fn public(&self) -> &PublicKey { - &self.0 + &self.public } /// Returns a reference to the [`SecretKey`] object. pub const fn secret(&self) -> &SecretKey { - &self.1 + &self.secret } } impl Drop for KeyPair { fn drop(&mut self) { - self.0.zeroize(); - self.1.zeroize(); + self.public.zeroize(); + self.secret.zeroize(); } } impl Zeroize for KeyPair { fn zeroize(&mut self) { - self.0.zeroize(); - self.1.zeroize(); + self.public.zeroize(); + self.secret.zeroize(); } } diff --git a/identity-core/src/crypto/key_impl.rs b/identity-core/src/crypto/key_impl.rs deleted file mode 100644 index fa01024ee5..0000000000 --- a/identity-core/src/crypto/key_impl.rs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -macro_rules! impl_bytes { - ($ident:ident) => { - /// A cryptographic key. - #[derive(Clone)] - pub struct $ident(Vec); - - impl From> for $ident { - fn from(other: Vec) -> $ident { - Self(other) - } - } - - impl AsRef<[u8]> for $ident { - fn as_ref(&self) -> &[u8] { - self.0.as_slice() - } - } - - impl Drop for $ident { - fn drop(&mut self) { - use ::zeroize::Zeroize; - self.0.zeroize(); - } - } - - impl ::zeroize::Zeroize for $ident { - fn zeroize(&mut self) { - self.0.zeroize(); - } - } - - impl ::core::fmt::Debug for $ident { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - f.write_str(stringify!($ident)) - } - } - - impl ::core::fmt::Display for $ident { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - f.write_str(stringify!($ident)) - } - } - }; -} - -impl_bytes!(PublicKey); -impl_bytes!(SecretKey); diff --git a/identity-core/src/crypto/mod.rs b/identity-core/src/crypto/mod.rs index 9560060659..eecf228865 100644 --- a/identity-core/src/crypto/mod.rs +++ b/identity-core/src/crypto/mod.rs @@ -3,8 +3,7 @@ //! Cryptographic Utilities -mod key_impl; -mod key_pair; +mod key; pub mod merkle_tree; pub use self::{key_impl::*, key_pair::*}; From 376a0a23e7a19d45b201ed23d864cd7cce722770 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 08:24:15 -0800 Subject: [PATCH 10/28] improve generated docs --- identity-core/src/common/object.rs | 2 +- identity-core/src/crypto/merkle_tree/digest.rs | 2 +- identity-core/src/lib.rs | 9 ++++----- identity-did/src/lib.rs | 6 ++---- identity-diff/src/lib.rs | 13 +++++++------ 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/identity-core/src/common/object.rs b/identity-core/src/common/object.rs index 799c291ebd..5808402608 100644 --- a/identity-core/src/common/object.rs +++ b/identity-core/src/common/object.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; -#[doc(import)] +#[doc(inline)] pub use serde_json::Value; /// An alias for an ordered map of key-[value][`Value`] pairs. diff --git a/identity-core/src/crypto/merkle_tree/digest.rs b/identity-core/src/crypto/merkle_tree/digest.rs index f2ca4f5861..92e74c49e1 100644 --- a/identity-core/src/crypto/merkle_tree/digest.rs +++ b/identity-core/src/crypto/merkle_tree/digest.rs @@ -1,7 +1,7 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -#[doc(import)] +#[doc(inline)] pub use digest::Digest; use crate::crypto::merkle_tree::consts; diff --git a/identity-core/src/lib.rs b/identity-core/src/lib.rs index 45b64a8bb9..01e3435f84 100644 --- a/identity-core/src/lib.rs +++ b/identity-core/src/lib.rs @@ -18,17 +18,16 @@ #[macro_use] extern crate serde; -pub use did_doc; -pub use did_url; -pub use identity_diff; +#[doc(inline)] pub use serde_json::json; +#[doc(inline)] +pub use identity_diff as diff; + pub mod common; pub mod convert; pub mod crypto; pub mod error; -pub mod proof; -pub mod resolver; pub mod utils; pub use self::error::Error; diff --git a/identity-did/src/lib.rs b/identity-did/src/lib.rs index 5082fc8307..a33c65d348 100644 --- a/identity-did/src/lib.rs +++ b/identity-did/src/lib.rs @@ -18,10 +18,8 @@ #[macro_use] extern crate serde; -pub mod did { - #[doc(import)] - pub use did_url::*; -} +#[doc(inline)] +pub use did_url as did; pub mod document; pub mod error; diff --git a/identity-diff/src/lib.rs b/identity-diff/src/lib.rs index 8b7490bd2b..8be89b2824 100644 --- a/identity-diff/src/lib.rs +++ b/identity-diff/src/lib.rs @@ -1,12 +1,13 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -/// This module implements a `Diff` trait type. The Diff trait gives data structures an ability to compare -/// themselves to another data structure of the same type over time. The library pairs off with `identity_derive` which -/// implements a derive macro for the `Diff` Trait. Types supported include `HashMap`, `Option`, `String`, -/// `serde_json::Value`, `Vec` and primitives such as `i8`/`u8` up to `usize` and `isize` as well as the unit type `()`, -/// `bool`, and `char` types. Structs and Enums are supported via `identity_derive` and can be composed of any number -/// of these types. +//! This module implements a `Diff` trait type. The Diff trait gives data structures an ability to compare +//! themselves to another data structure of the same type over time. The library pairs off with `identity_derive` which +//! implements a derive macro for the `Diff` Trait. Types supported include `HashMap`, `Option`, `String`, +//! `serde_json::Value`, `Vec` and primitives such as `i8`/`u8` up to `usize` and `isize` as well as the unit type `()`, +//! `bool`, and `char` types. Structs and Enums are supported via `identity_derive` and can be composed of any number +//! of these types. + pub mod did_doc; mod error; pub mod hashmap; From e6afd1574f6de96bbe92275aff11a3f645001625 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 08:24:34 -0800 Subject: [PATCH 11/28] Move ed25519 key gen. to utils --- identity-core/src/utils/generate_ed25519.rs | 24 +++++++++++++++++++++ identity-core/src/utils/mod.rs | 2 ++ 2 files changed, 26 insertions(+) create mode 100644 identity-core/src/utils/generate_ed25519.rs diff --git a/identity-core/src/utils/generate_ed25519.rs b/identity-core/src/utils/generate_ed25519.rs new file mode 100644 index 0000000000..eb04233025 --- /dev/null +++ b/identity-core/src/utils/generate_ed25519.rs @@ -0,0 +1,24 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use ed25519_zebra::SigningKey; +use ed25519_zebra::VerificationKey; +use ed25519_zebra::VerificationKeyBytes; +use rand::rngs::OsRng; + +use crate::crypto::KeyPair; +use crate::crypto::PublicKey; +use crate::crypto::SecretKey; +use crate::error::Result; + +/// Generates a new ed25519 [`KeyPair`]. +pub fn generate_ed25519() -> Result { + let secret: SigningKey = SigningKey::new(OsRng); + let public: VerificationKey = (&secret).into(); + let public: VerificationKeyBytes = public.into(); + + let public: PublicKey = public.as_ref().to_vec().into(); + let secret: SecretKey = secret.as_ref().to_vec().into(); + + Ok(KeyPair::new(public, secret)) +} diff --git a/identity-core/src/utils/mod.rs b/identity-core/src/utils/mod.rs index ec8adb5144..0b30a87fbe 100644 --- a/identity-core/src/utils/mod.rs +++ b/identity-core/src/utils/mod.rs @@ -4,7 +4,9 @@ //! Misc. utility functions. mod base_encoding; +mod generate_ed25519; mod jcs_sha256; pub use self::base_encoding::*; +pub use self::generate_ed25519::*; pub use self::jcs_sha256::*; From 8ed985cd3755518a2537eb4af3890a044358c723 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 09:09:12 -0800 Subject: [PATCH 12/28] Extract signature types from identity-did --- .../src/crypto}/signature/mod.rs | 8 ++--- .../src/crypto}/signature/signature.rs | 33 +++++++++---------- .../src/crypto}/signature/signature_data.rs | 13 ++++++++ .../crypto}/signature/signature_options.rs | 27 +++++++-------- .../src/crypto}/signature/signature_value.rs | 9 ++++- .../src/crypto}/signature/traits.rs | 31 ++++++++--------- identity-did/src/document/document.rs | 2 +- identity-did/src/lib.rs | 1 - identity-did/src/verifiable/document.rs | 2 +- .../src/{signature => verifiable}/ld_suite.rs | 28 ++++++++++------ identity-did/src/verifiable/mod.rs | 2 ++ identity-did/src/verifiable/properties.rs | 2 +- identity-did/src/verifiable/traits.rs | 3 +- identity-did/src/verification/method_query.rs | 19 +++++++++++ identity-did/src/verification/method_wrap.rs | 7 ++++ 15 files changed, 121 insertions(+), 66 deletions(-) rename {identity-did/src => identity-core/src/crypto}/signature/mod.rs (73%) rename {identity-did/src => identity-core/src/crypto}/signature/signature.rs (75%) rename {identity-did/src => identity-core/src/crypto}/signature/signature_data.rs (66%) rename {identity-did/src => identity-core/src/crypto}/signature/signature_options.rs (52%) rename {identity-did/src => identity-core/src/crypto}/signature/signature_value.rs (77%) rename {identity-did/src => identity-core/src/crypto}/signature/traits.rs (64%) rename identity-did/src/{signature => verifiable}/ld_suite.rs (72%) diff --git a/identity-did/src/signature/mod.rs b/identity-core/src/crypto/signature/mod.rs similarity index 73% rename from identity-did/src/signature/mod.rs rename to identity-core/src/crypto/signature/mod.rs index a7d51d6ccb..fa47056ff7 100644 --- a/identity-did/src/signature/mod.rs +++ b/identity-core/src/crypto/signature/mod.rs @@ -3,18 +3,16 @@ #![allow(clippy::module_inception)] -mod ld_suite; mod signature; mod signature_data; mod signature_options; mod signature_value; mod traits; -pub use self::ld_suite::LdSuite; pub use self::signature::Signature; pub use self::signature_data::SignatureData; pub use self::signature_options::SignatureOptions; pub use self::signature_value::SignatureValue; -pub use self::traits::Sign; -pub use self::traits::SuiteName; -pub use self::traits::Verify; +pub use self::traits::SigSign; +pub use self::traits::SigName; +pub use self::traits::SigVerify; diff --git a/identity-did/src/signature/signature.rs b/identity-core/src/crypto/signature/signature.rs similarity index 75% rename from identity-did/src/signature/signature.rs rename to identity-core/src/crypto/signature/signature.rs index 762cdecdc8..00a1a06123 100644 --- a/identity-did/src/signature/signature.rs +++ b/identity-core/src/crypto/signature/signature.rs @@ -9,13 +9,12 @@ use core::ops::DerefMut; use serde::Serialize; use crate::error::Result; -use crate::signature::SignatureData; -use crate::signature::SignatureOptions; -use crate::signature::SignatureValue; -use crate::signature::Verify; -use crate::verification::MethodIdent; -use crate::verification::MethodQuery; +use crate::crypto::SignatureData; +use crate::crypto::SignatureOptions; +use crate::crypto::SignatureValue; +use crate::crypto::SigVerify; +/// A DID Document digital signature. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] pub struct Signature { #[serde(rename = "type")] @@ -27,6 +26,7 @@ pub struct Signature { } impl Signature { + /// Creates a new [`Signature`]. pub fn new(type_: impl Into, options: SignatureOptions) -> Self { Self { type_: type_.into(), @@ -35,52 +35,51 @@ impl Signature { } } + /// Returns the `type` property of the signature. pub fn type_(&self) -> &str { &*self.type_ } + /// Returns a reference to the signature `data`. pub const fn data(&self) -> &SignatureValue { &self.data } + /// Returns a mutable reference to the signature `data`. pub fn data_mut(&mut self) -> &mut SignatureValue { &mut self.data } + /// Sets the signature `data` to the given `value`. pub fn set_data(&mut self, value: SignatureData) { self.data.set(value); } + /// Clears the current signature value - all other properties are unchanged. pub fn clear_data(&mut self) { self.data.clear(); } + /// Flag the signature value so it is ignored during serialization pub fn hide_value(&self) { self.data.hide(); } + /// Restore the signature value state so serialization behaves normally pub fn show_value(&self) { self.data.show(); } - pub fn to_query(&self) -> Result> { - let ident: MethodIdent<'_> = (&*self.verification_method).into(); - - if let Some(scope) = self.proof_purpose.as_deref() { - Ok(MethodQuery::with_scope(ident, scope.parse()?)) - } else { - Ok(MethodQuery::new(ident)) - } - } - + /// Verifies `self` with the given signature `suite` and `public` key. pub fn verify(&self, suite: &S, message: &M, public: &[u8]) -> Result<()> where - S: Verify, + S: SigVerify, M: Serialize, { self.verifiable(|data| suite.verify(message, data, public)) } + #[doc(hidden)] pub fn verifiable(&self, f: F) -> T where F: FnOnce(&SignatureValue) -> T, diff --git a/identity-did/src/signature/signature_data.rs b/identity-core/src/crypto/signature/signature_data.rs similarity index 66% rename from identity-did/src/signature/signature_data.rs rename to identity-core/src/crypto/signature/signature_data.rs index 2a6497f9d5..7102ca8495 100644 --- a/identity-did/src/signature/signature_data.rs +++ b/identity-core/src/crypto/signature/signature_data.rs @@ -1,35 +1,45 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/// A DID Document signature with a dynamic JSON field name. #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] pub enum SignatureData { + /// An empty signature value. #[serde(skip)] None, + /// A signature value with the property name `jws`. #[serde(rename = "jws")] Jws(String), + /// A signature value with the property name `proofValue`. #[serde(rename = "proofValue")] Proof(String), + /// A signature value with the property name `signatureValue`. #[serde(rename = "signatureValue")] Signature(String), } impl SignatureData { + /// Returns `true` if the signature data is a `None` type. pub const fn is_none(&self) -> bool { matches!(self, Self::None) } + /// Returns `true` if the signature data is a `Jws` type. pub const fn is_jws(&self) -> bool { matches!(self, Self::Jws(_)) } + /// Returns `true` if the signature data is a `Proof` type. pub const fn is_proof(&self) -> bool { matches!(self, Self::Proof(_)) } + /// Returns `true` if the signature data is a `Signature` type. pub const fn is_signature(&self) -> bool { matches!(self, Self::Signature(_)) } + /// Returns the signature data as a string slice. pub fn as_str(&self) -> &str { match self { Self::None => "", @@ -39,6 +49,7 @@ impl SignatureData { } } + /// Returns the `Jws` type signature data as a string slice. pub fn try_jws(&self) -> Option<&str> { match self { Self::None => None, @@ -48,6 +59,7 @@ impl SignatureData { } } + /// Returns the `Proof` type signature data as a string slice. pub fn try_proof(&self) -> Option<&str> { match self { Self::None => None, @@ -57,6 +69,7 @@ impl SignatureData { } } + /// Returns the `Signature` type signature data as a string slice. pub fn try_signature(&self) -> Option<&str> { match self { Self::None => None, diff --git a/identity-did/src/signature/signature_options.rs b/identity-core/src/crypto/signature/signature_options.rs similarity index 52% rename from identity-did/src/signature/signature_options.rs rename to identity-core/src/crypto/signature/signature_options.rs index 6d71973448..0d7dc7d19e 100644 --- a/identity-did/src/signature/signature_options.rs +++ b/identity-core/src/crypto/signature/signature_options.rs @@ -1,26 +1,31 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::verification::MethodWrap; - +/// Customizable properties of a DID Document signature. #[derive(Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] pub struct SignatureOptions { + /// The unique identifier of the DID method used to create this signature. #[serde(rename = "verificationMethod")] pub verification_method: String, + /// The intended purpose of the signature - See [`MethodScope`] for supported values. #[serde(rename = "proofPurpose", skip_serializing_if = "Option::is_none")] pub proof_purpose: Option, + /// A timestamp of when the signature was created. #[serde(skip_serializing_if = "Option::is_none")] pub created: Option, + /// The signature `nonce` property. #[serde(skip_serializing_if = "Option::is_none")] pub nonce: Option, + /// The signature `domain` property. #[serde(skip_serializing_if = "Option::is_none")] pub domain: Option, } impl SignatureOptions { - pub const fn new(verification_method: String) -> Self { + /// Creates a new [`SignatureOptions`] instance with the given `method`. + pub fn new(method: impl Into) -> Self { Self { - verification_method, + verification_method: method.into(), proof_purpose: None, created: None, nonce: None, @@ -28,19 +33,15 @@ impl SignatureOptions { } } - pub const fn with_purpose(verification_method: String, proof_purpose: String) -> Self { + /// Creates a new [`SignatureOptions`] instance with the given `method` and + /// `purpose`. + pub fn with_purpose(method: impl Into, purpose: impl Into) -> Self { Self { - verification_method, - proof_purpose: Some(proof_purpose), + verification_method: method.into(), + proof_purpose: Some(purpose.into()), created: None, nonce: None, domain: None, } } } - -impl From> for SignatureOptions { - fn from(other: MethodWrap<'_, T>) -> Self { - Self::with_purpose(other.id().to_string(), other.scope().as_str().to_string()) - } -} diff --git a/identity-did/src/signature/signature_value.rs b/identity-core/src/crypto/signature/signature_value.rs similarity index 77% rename from identity-did/src/signature/signature_value.rs rename to identity-core/src/crypto/signature/signature_value.rs index 8066e4d5b7..5dc5beb65a 100644 --- a/identity-did/src/signature/signature_value.rs +++ b/identity-core/src/crypto/signature/signature_value.rs @@ -8,8 +8,9 @@ use core::fmt::Result; use core::ops::Deref; use core::ops::DerefMut; -use crate::signature::SignatureData; +use crate::crypto::SignatureData; +/// A [`SignatureData`] wrapper with a visiblity toggle. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] #[serde(transparent)] pub struct SignatureValue { @@ -19,6 +20,7 @@ pub struct SignatureValue { } impl SignatureValue { + /// Creates a new [`SignatureValue`]. pub const fn new() -> Self { Self { data: SignatureData::None, @@ -26,22 +28,27 @@ impl SignatureValue { } } + /// Returns `true` if the value is empty. pub fn is_none(&self) -> bool { self.data.is_none() || self.hide.get() } + /// Sets the value of the underlying [`SignatureData`]. pub fn set(&mut self, value: SignatureData) { self.data = value; } + /// Clears the value of the underlying [`SignatureData`]. pub fn clear(&mut self) { self.set(SignatureData::None); } + /// Flag the signature value as "hidden". pub fn hide(&self) { self.hide.set(true); } + /// Flag the signature value as "visible". pub fn show(&self) { self.hide.set(false); } diff --git a/identity-did/src/signature/traits.rs b/identity-core/src/crypto/signature/traits.rs similarity index 64% rename from identity-did/src/signature/traits.rs rename to identity-core/src/crypto/signature/traits.rs index 45e7478430..6d27f0572a 100644 --- a/identity-did/src/signature/traits.rs +++ b/identity-core/src/crypto/signature/traits.rs @@ -4,16 +4,17 @@ use serde::Serialize; use crate::error::Result; -use crate::signature::SignatureData; -use crate::verification::MethodType; +use crate::crypto::SignatureData; -pub trait SuiteName { +/// A trait for signature suites identified by a particular name. +pub trait SigName { + /// Returns the name identifying this signature suite. fn name(&self) -> String; } -impl<'a, T> SuiteName for &'a T +impl<'a, T> SigName for &'a T where - T: SuiteName, + T: SigName, { fn name(&self) -> String { (**self).name() @@ -23,15 +24,17 @@ where // ============================================================================= // ============================================================================= -pub trait Sign { +/// A trait for general-purpose signature creation +pub trait SigSign { + /// Signs the given `data` with `secret` and returns a digital signature. fn sign(&self, data: &T, secret: &[u8]) -> Result where T: Serialize; } -impl<'a, T> Sign for &'a T +impl<'a, T> SigSign for &'a T where - T: Sign, + T: SigSign, { fn sign(&self, data: &U, secret: &[u8]) -> Result where @@ -44,20 +47,18 @@ where // ============================================================================= // ============================================================================= -pub trait Verify { - const METHODS: &'static [MethodType]; - +/// A trait for general-purpose signature verification +pub trait SigVerify { + /// Verifies the authenticity of `data` using `signature` and `public`. fn verify(&self, data: &T, signature: &SignatureData, public: &[u8]) -> Result<()> where T: Serialize; } -impl<'a, T> Verify for &'a T +impl<'a, T> SigVerify for &'a T where - T: Verify, + T: SigVerify, { - const METHODS: &'static [MethodType] = T::METHODS; - fn verify(&self, data: &U, signature: &SignatureData, public: &[u8]) -> Result<()> where U: Serialize, diff --git a/identity-did/src/document/document.rs b/identity-did/src/document/document.rs index 8283e19168..321e83f3d7 100644 --- a/identity-did/src/document/document.rs +++ b/identity-did/src/document/document.rs @@ -8,6 +8,7 @@ use core::fmt::Formatter; use core::fmt::Result as FmtResult; use identity_core::common::Url; use identity_core::convert::ToJson; +use identity_core::crypto::SignatureOptions; use serde::Serialize; use crate::did::DID; @@ -15,7 +16,6 @@ use crate::document::Builder; use crate::error::Error; use crate::error::Result; use crate::service::Service; -use crate::signature::SignatureOptions; use crate::utils::DIDKey; use crate::utils::OrderedSet; use crate::verifiable::ResolveMethod; diff --git a/identity-did/src/lib.rs b/identity-did/src/lib.rs index a33c65d348..d57dcdced8 100644 --- a/identity-did/src/lib.rs +++ b/identity-did/src/lib.rs @@ -25,7 +25,6 @@ pub mod document; pub mod error; pub mod resolution; pub mod service; -pub mod signature; pub mod utils; pub mod verifiable; pub mod verification; diff --git a/identity-did/src/verifiable/document.rs b/identity-did/src/verifiable/document.rs index 3ff169a810..aa6685cf33 100644 --- a/identity-did/src/verifiable/document.rs +++ b/identity-did/src/verifiable/document.rs @@ -8,9 +8,9 @@ use core::fmt::Result as FmtResult; use core::ops::Deref; use core::ops::DerefMut; use serde::Serialize; +use identity_core::crypto::Signature; use crate::document::Document as Document_; -use crate::signature::Signature; use crate::verifiable::Properties; use crate::verifiable::ResolveMethod; use crate::verifiable::SetSignature; diff --git a/identity-did/src/signature/ld_suite.rs b/identity-did/src/verifiable/ld_suite.rs similarity index 72% rename from identity-did/src/signature/ld_suite.rs rename to identity-did/src/verifiable/ld_suite.rs index a64d9e6b6c..9d2710497f 100644 --- a/identity-did/src/signature/ld_suite.rs +++ b/identity-did/src/verifiable/ld_suite.rs @@ -1,21 +1,27 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use core::convert::TryInto; +use identity_core::crypto::SigSign; +use identity_core::crypto::Signature; +use identity_core::crypto::SignatureData; +use identity_core::crypto::SignatureOptions; +use identity_core::crypto::SigName; +use identity_core::crypto::SigVerify; use serde::Serialize; use crate::error::Error; use crate::error::Result; -use crate::signature::Sign; -use crate::signature::Signature; -use crate::signature::SignatureData; -use crate::signature::SignatureOptions; -use crate::signature::SuiteName; -use crate::signature::Verify; use crate::verifiable::ResolveMethod; use crate::verifiable::SetSignature; use crate::verifiable::TrySignature; use crate::verification::MethodQuery; use crate::verification::MethodWrap; +use crate::verification::MethodType; + +pub trait LdSignature: SigName { + const METHODS: &'static [MethodType]; +} #[derive(Clone, Copy, Debug)] pub struct LdSuite { @@ -30,7 +36,7 @@ impl LdSuite { impl LdSuite where - S: Sign + SuiteName, + S: SigSign + LdSignature, { pub fn sign(&self, message: &mut T, options: SignatureOptions, secret: &K) -> Result<()> where @@ -49,7 +55,7 @@ where impl LdSuite where - S: Verify + SuiteName, + S: SigVerify + LdSignature, { pub fn verify(&self, message: &T) -> Result<()> where @@ -71,14 +77,16 @@ where return Err(Error::UnknownSignatureType); } - let query: MethodQuery<'_> = signature.to_query()?; + let query: MethodQuery<'_> = signature.try_into()?; let method: MethodWrap<'_, M> = resolver.try_resolve_method(query)?; if !S::METHODS.contains(&method.key_type()) { return Err(Error::UnknownMethodType); } - signature.verify(&self.suite, message, &method.key_data().try_decode()?)?; + let public: Vec = method.key_data().try_decode()?; + + signature.verify(&self.suite, message, &public)?; Ok(()) } diff --git a/identity-did/src/verifiable/mod.rs b/identity-did/src/verifiable/mod.rs index b577619059..a0d4678d57 100644 --- a/identity-did/src/verifiable/mod.rs +++ b/identity-did/src/verifiable/mod.rs @@ -2,10 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 mod document; +mod ld_suite; mod properties; mod traits; pub use self::document::Document; +pub use self::ld_suite::LdSuite; pub use self::properties::Properties; pub use self::traits::ResolveMethod; pub use self::traits::SetSignature; diff --git a/identity-did/src/verifiable/properties.rs b/identity-did/src/verifiable/properties.rs index b8e6ea0337..4f9a21b0dc 100644 --- a/identity-did/src/verifiable/properties.rs +++ b/identity-did/src/verifiable/properties.rs @@ -3,8 +3,8 @@ use core::ops::Deref; use core::ops::DerefMut; +use identity_core::crypto::Signature; -use crate::signature::Signature; use crate::verifiable::SetSignature; use crate::verifiable::TrySignature; use crate::verifiable::TrySignatureMut; diff --git a/identity-did/src/verifiable/traits.rs b/identity-did/src/verifiable/traits.rs index 0bc21ea445..7315e11748 100644 --- a/identity-did/src/verifiable/traits.rs +++ b/identity-did/src/verifiable/traits.rs @@ -1,9 +1,10 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use identity_core::crypto::Signature; + use crate::error::Error; use crate::error::Result; -use crate::signature::Signature; use crate::verification::MethodQuery; use crate::verification::MethodWrap; diff --git a/identity-did/src/verification/method_query.rs b/identity-did/src/verification/method_query.rs index dd010a896e..75af296aa0 100644 --- a/identity-did/src/verification/method_query.rs +++ b/identity-did/src/verification/method_query.rs @@ -1,6 +1,11 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use core::convert::TryFrom; +use identity_core::crypto::Signature; + +use crate::error::Error; +use crate::error::Result; use crate::verification::MethodIdent; use crate::verification::MethodScope; @@ -69,3 +74,17 @@ impl<'a> From for MethodQuery<'a> { Self::with_scope(0, other) } } + +impl<'a> TryFrom<&'a Signature> for MethodQuery<'a> { + type Error = Error; + + fn try_from(other: &'a Signature) -> Result { + let ident: MethodIdent<'a> = (&*other.verification_method).into(); + + if let Some(scope) = other.proof_purpose.as_deref() { + Ok(MethodQuery::with_scope(ident, scope.parse()?)) + } else { + Ok(MethodQuery::new(ident)) + } + } +} diff --git a/identity-did/src/verification/method_wrap.rs b/identity-did/src/verification/method_wrap.rs index 5767f0b06f..a771b52cd4 100644 --- a/identity-did/src/verification/method_wrap.rs +++ b/identity-did/src/verification/method_wrap.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use core::ops::Deref; +use identity_core::crypto::SignatureOptions; use crate::verification::Method; use crate::verification::MethodScope; @@ -43,3 +44,9 @@ impl Deref for MethodWrap<'_, T> { self.method } } + +impl From> for SignatureOptions { + fn from(other: MethodWrap<'_, T>) -> Self { + Self::with_purpose(other.id().as_str(), other.scope().as_str()) + } +} From c34a3d038ff35dbe60b1bf799e254b2669314882 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 09:26:14 -0800 Subject: [PATCH 13/28] Use identity-did for more functionality --- identity-core/Cargo.toml | 2 - identity-core/src/crypto/key/mod.rs | 2 + identity-core/src/crypto/mod.rs | 14 +- .../crypto/proof/jcsed25519signature2020.rs | 293 +++++++++++++++++ identity-core/src/{ => crypto}/proof/mod.rs | 2 +- identity-core/src/crypto/signature/mod.rs | 2 +- .../src/crypto/signature/signature.rs | 4 +- identity-core/src/crypto/signature/traits.rs | 2 +- identity-core/src/error.rs | 15 +- .../src/proof/jcsed25519signature2020.rs | 297 ------------------ identity-credential/Cargo.toml | 3 +- .../src/credential/credential.rs | 18 +- .../src/credential/verifiable.rs | 8 +- identity-credential/src/error.rs | 3 + .../src/presentation/verifiable.rs | 8 +- identity-did/src/error.rs | 2 + identity-did/src/verifiable/document.rs | 2 +- identity-did/src/verifiable/ld_suite.rs | 23 +- 18 files changed, 355 insertions(+), 345 deletions(-) create mode 100644 identity-core/src/crypto/proof/jcsed25519signature2020.rs rename identity-core/src/{ => crypto}/proof/mod.rs (78%) delete mode 100644 identity-core/src/proof/jcsed25519signature2020.rs diff --git a/identity-core/Cargo.toml b/identity-core/Cargo.toml index ea4831b72d..4692fc3777 100644 --- a/identity-core/Cargo.toml +++ b/identity-core/Cargo.toml @@ -14,8 +14,6 @@ homepage = "https://www.iota.org" anyhow = { version = "1.0", default-features = false } bs58 = { version = "0.4", default-features = false, features = ["std"] } chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } -did_doc = { git = "https://github.com/l1h3r/did_doc", rev = "bb8bda8f6d39451b08e3cc198f956c212a3b87f8", default-features = false, features = ["std"] } -did_url = { version = "0.1", default-features = false, features = ["std", "serde"] } digest = { version = "0.9", default-features = false } ed25519-zebra = { version = "2.2", default-features = false } hex = { version = "0.4", default-features = false } diff --git a/identity-core/src/crypto/key/mod.rs b/identity-core/src/crypto/key/mod.rs index a5ac5697a9..c3c07ea998 100644 --- a/identity-core/src/crypto/key/mod.rs +++ b/identity-core/src/crypto/key/mod.rs @@ -1,6 +1,8 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +#![allow(clippy::module_inception)] + mod key; mod pair; diff --git a/identity-core/src/crypto/mod.rs b/identity-core/src/crypto/mod.rs index eecf228865..d967860caf 100644 --- a/identity-core/src/crypto/mod.rs +++ b/identity-core/src/crypto/mod.rs @@ -5,5 +5,17 @@ mod key; pub mod merkle_tree; +mod proof; +mod signature; -pub use self::{key_impl::*, key_pair::*}; +pub use self::key::KeyPair; +pub use self::key::PublicKey; +pub use self::key::SecretKey; +pub use self::proof::JcsEd25519Signature2020; +pub use self::signature::SigName; +pub use self::signature::SigSign; +pub use self::signature::SigVerify; +pub use self::signature::Signature; +pub use self::signature::SignatureData; +pub use self::signature::SignatureOptions; +pub use self::signature::SignatureValue; diff --git a/identity-core/src/crypto/proof/jcsed25519signature2020.rs b/identity-core/src/crypto/proof/jcsed25519signature2020.rs new file mode 100644 index 0000000000..f5bc030de9 --- /dev/null +++ b/identity-core/src/crypto/proof/jcsed25519signature2020.rs @@ -0,0 +1,293 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::convert::TryInto; +use ed25519_zebra::Signature; +use ed25519_zebra::SigningKey; +use ed25519_zebra::VerificationKey; +use serde::Serialize; +use subtle::ConstantTimeEq; + +use crate::crypto::KeyPair; +use crate::crypto::SigName; +use crate::crypto::SigSign; +use crate::crypto::SigVerify; +use crate::crypto::SignatureData; +use crate::error::Error; +use crate::error::Result; +use crate::utils::decode_b58; +use crate::utils::encode_b58; +use crate::utils::generate_ed25519; +use crate::utils::jcs_sha256; + +const SIGNATURE_NAME: &str = "JcsEd25519Signature2020"; +const SIGNATURE_SIZE: usize = 64; +const PUBLIC_KEY_BYTES: usize = 32; +const SECRET_KEY_BYTES: usize = 32; + +/// An implementation of the [JCS Ed25519 Signature 2020][SPEC1] signature suite +/// for [Linked Data Proofs][SPEC2]. +/// +/// [SPEC1]: https://identity.foundation/JcsEd25519Signature2020/ +/// [SPEC2]: https://w3c-ccg.github.io/ld-proofs/ +#[derive(Clone, Copy, Debug)] +pub struct JcsEd25519Signature2020; + +impl JcsEd25519Signature2020 { + /// The name of the signature suite. + pub const NAME: &'static str = SIGNATURE_NAME; + + /// Generates a new [`KeyPair`] appropriate for this signature suite. + pub fn new_keypair() -> KeyPair { + // TODO: Remove unwrap + generate_ed25519().unwrap() + } + + /// Signs the given `data` with `secret` and returns a digital signature. + pub fn sign_data(data: &T, secret: &[u8]) -> Result + where + T: Serialize, + { + jcs_sha256(data) + .and_then(|data| ed25519_sign(&data, secret)) + .map(|data| encode_b58(&data)) + .map(SignatureData::Signature) + } + + /// Verifies the authenticity of `data` using `signature` and `public`. + pub fn verify_data(data: &T, signature: &SignatureData, public: &[u8]) -> Result<()> + where + T: Serialize, + { + let signature: Vec = signature + .try_signature() + .ok_or(Error::InvalidProofFormat) + .and_then(|signature| decode_b58(&signature))?; + + ed25519_verify(&jcs_sha256(data)?, &signature, public)?; + + Ok(()) + } +} + +impl SigName for JcsEd25519Signature2020 { + fn name(&self) -> String { + Self::NAME.to_string() + } +} + +impl SigSign for JcsEd25519Signature2020 { + fn sign(&self, data: &T, secret: &[u8]) -> Result + where + T: Serialize, + { + Self::sign_data(data, secret) + } +} + +impl SigVerify for JcsEd25519Signature2020 { + fn verify(&self, data: &T, signature: &SignatureData, public: &[u8]) -> Result<()> + where + T: Serialize, + { + Self::verify_data(data, signature, public) + } +} + +fn parse_public(slice: &[u8]) -> Option { + if slice.len() < PUBLIC_KEY_BYTES { + return None; + } + + slice[..PUBLIC_KEY_BYTES].try_into().ok() +} + +fn parse_secret(slice: &[u8]) -> Option { + if slice.len() < SECRET_KEY_BYTES { + return None; + } + + slice[..SECRET_KEY_BYTES].try_into().ok() +} + +fn parse_signature(slice: &[u8]) -> Option<(Signature, &[u8])> { + if slice.len() < SIGNATURE_SIZE { + return None; + } + + let (signature, message): (&[u8], &[u8]) = slice.split_at(SIGNATURE_SIZE); + let signature: Signature = signature.try_into().ok()?; + + Some((signature, message)) +} + +// output = +pub(crate) fn ed25519_sign(message: &[u8], secret: &[u8]) -> Result> { + let key: SigningKey = parse_secret(secret).ok_or(Error::InvalidKeyFormat)?; + let sig: [u8; SIGNATURE_SIZE] = key.sign(message).into(); + + Ok([&sig, message].concat()) +} + +// signature = +pub(crate) fn ed25519_verify(message: &[u8], signature: &[u8], public: &[u8]) -> Result<()> { + let key: VerificationKey = parse_public(public).ok_or(Error::InvalidKeyFormat)?; + let (sig, msg): (Signature, &[u8]) = parse_signature(signature).ok_or(Error::InvalidProofFormat)?; + + key.verify(&sig, msg).map_err(|_| Error::InvalidProofFormat)?; + + if message.ct_eq(msg).into() { + Ok(()) + } else { + Err(Error::InvalidProofFormat) + } +} + +#[cfg(test)] +mod tests { + const UNSIGNED: &str = r##" + { + "id": "did:example:123", + "verificationMethod": [ + { + "id": "did:example:123#key-1", + "type": "JcsEd25519Key2020", + "controller": "did:example:123", + "publicKeyBase58": "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c" + } + ], + "service": [ + { + "id": "did:schema:id", + "type": "schema", + "serviceEndpoint": "https://example.com" + } + ] + } + "##; + + const SIGNED: &str = r##" + { + "id": "did:example:123", + "verificationMethod": [ + { + "id": "did:example:123#key-1", + "type": "JcsEd25519Key2020", + "controller": "did:example:123", + "publicKeyBase58": "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c" + } + ], + "service": [ + { + "id": "did:schema:id", + "type": "schema", + "serviceEndpoint": "https://example.com" + } + ], + "proof": { + "verificationMethod": "#key-1", + "type": "JcsEd25519Signature2020", + "signatureValue": "piKnvB438vWsinW1dqq2EYRzcYFuR7Qm9X8t2S6TPPLDokLwcFBXnnERk6jmS8RXKTJnXKWw1Q9oNhYTwbR7vJkaJT8ZGgwDHNxa6mrMNsQsWkM4rg6EYY99xQko7FnpAMn" + } + } + "##; + + // use identity_did::signature::LdSuite; + // use identity_did::signature::VerifiableDocument; + + use super::ed25519_sign; + use super::ed25519_verify; + use crate::convert::FromJson; + use crate::crypto::JcsEd25519Signature2020; + use crate::crypto::SignatureData; + use crate::crypto::SignatureOptions; + use crate::utils::decode_b58; + + const PUBLIC_B58: &str = "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c"; + const SECRET_B58: &str = "3qsrFcQqVuPpuGrRkU4wkQRvw1tc1C5EmEDPioS1GzQ2pLoThy5TYS2BsrwuzHYDnVqcYhMSpDhTXGst6H5ttFkG"; + + #[rustfmt::skip] + const SIGNATURE_HELLO: &[u8] = &[12, 203, 235, 144, 80, 6, 163, 39, 181, 17, 44, 123, 250, 162, 165, 145, 135, 132, 32, 152, 24, 168, 55, 80, 84, 139, 153, 101, 102, 27, 157, 29, 70, 124, 64, 120, 250, 172, 186, 163, 108, 27, 208, 248, 134, 115, 3, 154, 222, 165, 31, 93, 33, 108, 212, 92, 191, 14, 21, 40, 251, 103, 241, 10, 104, 101, 108, 108, 111]; + + const SIGNATURE_DOCUMENT: &str = "piKnvB438vWsinW1dqq2EYRzcYFuR7Qm9X8t2S6TPPLDokLwcFBXnnERk6jmS8RXKTJnXKWw1Q9oNhYTwbR7vJkaJT8ZGgwDHNxa6mrMNsQsWkM4rg6EYY99xQko7FnpAMn"; + + #[test] + fn test_ed25519_can_sign_and_verify() { + let public: Vec = decode_b58(PUBLIC_B58).unwrap(); + let secret: Vec = decode_b58(SECRET_B58).unwrap(); + + let signature: _ = ed25519_sign(b"hello", &secret).unwrap(); + assert_eq!(&signature, SIGNATURE_HELLO); + + let verified: _ = ed25519_verify(b"hello", &signature, &public); + assert!(verified.is_ok()); + } + + // #[test] + // fn test_jcsed25519signature2020_can_sign_and_verify() { + // let secret = decode_b58(SECRET_B58).unwrap(); + // let mut unsigned: VerifiableDocument = VerifiableDocument::from_json(UNSIGNED).unwrap(); + // let signed: VerifiableDocument = VerifiableDocument::from_json(SIGNED).unwrap(); + // let method = unsigned.try_resolve("#key-1").unwrap(); + // let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); + // let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); + + // suite.sign(&mut unsigned, options, &secret).unwrap(); + + // assert!(suite.verify(&unsigned).is_ok()); + // assert_eq!( + // unsigned.properties().proof().unwrap().data().as_str(), + // SIGNATURE_DOCUMENT + // ); + + // assert_eq!( + // serde_jcs::to_vec(&unsigned).unwrap(), + // serde_jcs::to_vec(&signed).unwrap() + // ); + // } + + // #[test] + // fn test_jcsed25519signature2020_fails_when_key_is_mutated() { + // let secret = decode_b58(SECRET_B58).unwrap(); + // let mut document: VerifiableDocument = VerifiableDocument::from_json(UNSIGNED).unwrap(); + // let method = document.try_resolve("#key-1").unwrap(); + // let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); + // let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); + + // suite.sign(&mut document, options, &secret).unwrap(); + + // assert!(suite.verify(&document).is_ok()); + // assert_eq!( + // document.properties().proof().unwrap().data().as_str(), + // SIGNATURE_DOCUMENT + // ); + + // document.proof_mut().unwrap().verification_method = "#key-2".into(); + + // assert!(suite.verify(&document).is_err()); + // } + + // #[test] + // fn test_jcsed25519signature2020_fails_when_signature_is_mutated() { + // let secret = decode_b58(SECRET_B58).unwrap(); + // let mut document: VerifiableDocument = VerifiableDocument::from_json(UNSIGNED).unwrap(); + // let method = document.try_resolve("#key-1").unwrap(); + // let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); + // let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); + + // suite.sign(&mut document, options, &secret).unwrap(); + + // assert!(suite.verify(&document).is_ok()); + // assert_eq!( + // document.properties().proof().unwrap().data().as_str(), + // SIGNATURE_DOCUMENT + // ); + + // document + // .proof_mut() + // .unwrap() + // .set_data(SignatureData::Signature("foo".into())); + + // assert!(suite.verify(&document).is_err()); + // } +} diff --git a/identity-core/src/proof/mod.rs b/identity-core/src/crypto/proof/mod.rs similarity index 78% rename from identity-core/src/proof/mod.rs rename to identity-core/src/crypto/proof/mod.rs index e4c8f0e8a0..7b802227fd 100644 --- a/identity-core/src/proof/mod.rs +++ b/identity-core/src/crypto/proof/mod.rs @@ -6,4 +6,4 @@ mod jcsed25519signature2020; -pub use self::jcsed25519signature2020::*; +pub use self::jcsed25519signature2020::JcsEd25519Signature2020; diff --git a/identity-core/src/crypto/signature/mod.rs b/identity-core/src/crypto/signature/mod.rs index fa47056ff7..5386bd391b 100644 --- a/identity-core/src/crypto/signature/mod.rs +++ b/identity-core/src/crypto/signature/mod.rs @@ -13,6 +13,6 @@ pub use self::signature::Signature; pub use self::signature_data::SignatureData; pub use self::signature_options::SignatureOptions; pub use self::signature_value::SignatureValue; -pub use self::traits::SigSign; pub use self::traits::SigName; +pub use self::traits::SigSign; pub use self::traits::SigVerify; diff --git a/identity-core/src/crypto/signature/signature.rs b/identity-core/src/crypto/signature/signature.rs index 00a1a06123..dd09840c8a 100644 --- a/identity-core/src/crypto/signature/signature.rs +++ b/identity-core/src/crypto/signature/signature.rs @@ -8,11 +8,11 @@ use core::ops::Deref; use core::ops::DerefMut; use serde::Serialize; -use crate::error::Result; +use crate::crypto::SigVerify; use crate::crypto::SignatureData; use crate::crypto::SignatureOptions; use crate::crypto::SignatureValue; -use crate::crypto::SigVerify; +use crate::error::Result; /// A DID Document digital signature. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] diff --git a/identity-core/src/crypto/signature/traits.rs b/identity-core/src/crypto/signature/traits.rs index 6d27f0572a..6187bff2f6 100644 --- a/identity-core/src/crypto/signature/traits.rs +++ b/identity-core/src/crypto/signature/traits.rs @@ -3,8 +3,8 @@ use serde::Serialize; -use crate::error::Result; use crate::crypto::SignatureData; +use crate::error::Result; /// A trait for signature suites identified by a particular name. pub trait SigName { diff --git a/identity-core/src/error.rs b/identity-core/src/error.rs index 76b284742f..ab93fb1a86 100644 --- a/identity-core/src/error.rs +++ b/identity-core/src/error.rs @@ -21,12 +21,6 @@ pub enum Error { /// Caused by a failure to decode base58-encoded data. #[error("Failed to decode base58 data: {0}")] DecodeBase58(#[from] bs58::decode::Error), - /// Caused by attempting to perform an invalid `DID` operation. - #[error("Invalid DID: {0}")] - InvalidDID(#[from] did_url::Error), - /// Caused by attempting to perform an invalid DID `Document` operation. - #[error("Invalid DID Document: {0}")] - InvalidDocument(#[from] did_doc::Error), /// Caused by attempting to perform an invalid `Diff` operation. #[error("Invalid Document Diff: {0}")] InvalidDiff(#[from] identity_diff::Error), @@ -36,10 +30,13 @@ pub enum Error { /// Caused by attempting to parse an invalid `Timestamp`. #[error("Invalid Timestamp: {0}")] InvalidTimestamp(#[from] chrono::ParseError), + /// Caused by attempting to parse an invalid DID proof. + #[error("Invalid Proof Format")] + InvalidProofFormat, + /// Caused by attempting to parse an invalid cryptographic key. + #[error("Invalid Key Format")] + InvalidKeyFormat, /// Caused by a failure to resolve a DID. #[error("DID Resolution Error: {0}")] ResolutionError(anyhow::Error), - /// Caused by a failure to dereference a DID URL. - #[error("DID Dereference Error: {0}")] - DereferenceError(anyhow::Error), } diff --git a/identity-core/src/proof/jcsed25519signature2020.rs b/identity-core/src/proof/jcsed25519signature2020.rs deleted file mode 100644 index f891dbd0f1..0000000000 --- a/identity-core/src/proof/jcsed25519signature2020.rs +++ /dev/null @@ -1,297 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use core::convert::TryInto as _; -use did_doc::{Error, Method, Result, Sign, SignatureData, SuiteName, Verify}; -use ed25519_zebra::{Signature, SigningKey, VerificationKey, VerificationKeyBytes}; -use rand::rngs::OsRng; -use serde::Serialize; -use sha2::{digest::Output, Sha256}; -use subtle::ConstantTimeEq; - -use crate::{ - crypto::KeyPair, - utils::{decode_b58, encode_b58, jcs_sha256}, -}; - -const SIGNATURE_NAME: &str = "JcsEd25519Signature2020"; -const SIGNATURE_SIZE: usize = 64; -const PUBLIC_KEY_BYTES: usize = 32; -const SECRET_KEY_BYTES: usize = 32; - -/// An implementation of the [JCS Ed25519 Signature 2020][SPEC1] signature suite -/// for [Linked Data Proofs][SPEC2]. -/// -/// [SPEC1]: https://identity.foundation/JcsEd25519Signature2020/ -/// [SPEC2]: https://w3c-ccg.github.io/ld-proofs/ -#[derive(Clone, Copy, Debug)] -pub struct JcsEd25519Signature2020; - -impl JcsEd25519Signature2020 { - /// The name of the signature suite. - pub const NAME: &'static str = SIGNATURE_NAME; - - /// Generates a new [`KeyPair`] appropriate for this signature suite. - pub fn new_keypair() -> KeyPair { - let secret: SigningKey = SigningKey::new(OsRng); - let public: VerificationKey = (&secret).into(); - let public: VerificationKeyBytes = public.into(); - - KeyPair::new(public.as_ref().to_vec().into(), secret.as_ref().to_vec().into()) - } - - /// Signs the given `data` with `secret` and returns a digital signature. - pub fn sign_data(data: &T, secret: &[u8]) -> Result - where - T: Serialize, - { - Self::normalize(data) - .and_then(|data| ed25519_sign(&data, secret)) - .map_err(|_| Error::message("Cannot Sign Document")) - .map(|data| encode_b58(&data)) - .map(SignatureData::Signature) - } - - /// Verifies the authenticity of `data` using `signature` and `public`. - pub fn verify_data(data: &T, signature: &SignatureData, public: &[u8]) -> Result<()> - where - T: Serialize, - { - let signature: &str = signature - .try_signature() - .ok_or_else(|| Error::message("Signature Data Not Found"))?; - - let signature: Vec = decode_b58(&signature).map_err(|_| Error::message("Invalid Signature Data"))?; - let verified: Vec = ed25519_verify(&signature, public)?; - let digest: _ = Self::normalize(data)?; - - if digest[..].ct_eq(&verified[..]).into() { - Ok(()) - } else { - Err(Error::message("Invalid Signature Digest")) - } - } - - fn normalize(data: &T) -> Result> - where - T: Serialize, - { - jcs_sha256(data).map_err(|_| Error::message("Cannot Serialize Document")) - } -} - -impl SuiteName for JcsEd25519Signature2020 { - fn name(&self) -> String { - Self::NAME.to_string() - } -} - -impl Sign for JcsEd25519Signature2020 { - fn sign(&self, data: &T, secret: &[u8]) -> Result - where - T: Serialize, - { - Self::sign_data(data, secret) - } -} - -impl Verify for JcsEd25519Signature2020 { - fn verify(&self, data: &T, signature: &SignatureData, method: &Method) -> Result<()> - where - T: Serialize, - U: Serialize, - { - Self::verify_data(data, signature, &method.key_data().try_decode()?) - } -} - -fn pubkey(slice: &[u8]) -> Result { - if slice.len() < PUBLIC_KEY_BYTES { - return Err(Error::message("Invalid Key Format")); - } - - slice[..PUBLIC_KEY_BYTES] - .try_into() - .map_err(|_| Error::message("Invalid Key Format")) -} - -fn seckey(slice: &[u8]) -> Result { - if slice.len() < SECRET_KEY_BYTES { - return Err(Error::message("Invalid Key Format")); - } - - slice[..SECRET_KEY_BYTES] - .try_into() - .map_err(|_| Error::message("Invalid Key Format")) -} - -// output = -fn ed25519_sign(message: &[u8], secret: &[u8]) -> Result> { - let key: SigningKey = seckey(secret)?; - let sig: [u8; SIGNATURE_SIZE] = key.sign(message).into(); - - Ok([&sig, message].concat()) -} - -// signature = -fn ed25519_verify(signature: &[u8], public: &[u8]) -> Result> { - if signature.len() < SIGNATURE_SIZE { - return Err(Error::message("Invalid Key Format")); - } - - let key: VerificationKey = pubkey(public)?; - let (sig, msg): (&[u8], &[u8]) = signature.split_at(SIGNATURE_SIZE); - let sig: Signature = sig.try_into().map_err(|_| Error::message("Invalid Key Format"))?; - - key - .verify(&sig, msg) - .map_err(|_| Error::message("Invalid Key Format"))?; - - Ok(msg.to_vec()) -} - -#[cfg(test)] -mod tests { - const UNSIGNED: &str = r##" - { - "id": "did:example:123", - "verificationMethod": [ - { - "id": "did:example:123#key-1", - "type": "JcsEd25519Key2020", - "controller": "did:example:123", - "publicKeyBase58": "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c" - } - ], - "service": [ - { - "id": "did:schema:id", - "type": "schema", - "serviceEndpoint": "https://example.com" - } - ] - } - "##; - - const SIGNED: &str = r##" - { - "id": "did:example:123", - "verificationMethod": [ - { - "id": "did:example:123#key-1", - "type": "JcsEd25519Key2020", - "controller": "did:example:123", - "publicKeyBase58": "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c" - } - ], - "service": [ - { - "id": "did:schema:id", - "type": "schema", - "serviceEndpoint": "https://example.com" - } - ], - "proof": { - "verificationMethod": "#key-1", - "type": "JcsEd25519Signature2020", - "signatureValue": "piKnvB438vWsinW1dqq2EYRzcYFuR7Qm9X8t2S6TPPLDokLwcFBXnnERk6jmS8RXKTJnXKWw1Q9oNhYTwbR7vJkaJT8ZGgwDHNxa6mrMNsQsWkM4rg6EYY99xQko7FnpAMn" - } - } - "##; - - use super::{ed25519_sign, ed25519_verify, JcsEd25519Signature2020}; - - use crate::{ - convert::FromJson as _, - did_doc::{LdSuite, SignatureData, SignatureOptions, VerifiableDocument}, - utils::decode_b58, - }; - - const PUBLIC_B58: &str = "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c"; - const SECRET_B58: &str = "3qsrFcQqVuPpuGrRkU4wkQRvw1tc1C5EmEDPioS1GzQ2pLoThy5TYS2BsrwuzHYDnVqcYhMSpDhTXGst6H5ttFkG"; - - #[rustfmt::skip] - const SIGNATURE_HELLO: &[u8] = &[12, 203, 235, 144, 80, 6, 163, 39, 181, 17, 44, 123, 250, 162, 165, 145, 135, 132, 32, 152, 24, 168, 55, 80, 84, 139, 153, 101, 102, 27, 157, 29, 70, 124, 64, 120, 250, 172, 186, 163, 108, 27, 208, 248, 134, 115, 3, 154, 222, 165, 31, 93, 33, 108, 212, 92, 191, 14, 21, 40, 251, 103, 241, 10, 104, 101, 108, 108, 111]; - - const SIGNATURE_DOCUMENT: &str = "piKnvB438vWsinW1dqq2EYRzcYFuR7Qm9X8t2S6TPPLDokLwcFBXnnERk6jmS8RXKTJnXKWw1Q9oNhYTwbR7vJkaJT8ZGgwDHNxa6mrMNsQsWkM4rg6EYY99xQko7FnpAMn"; - - #[test] - fn test_ed25519_can_sign_and_verify() { - let public: Vec = decode_b58(PUBLIC_B58).unwrap(); - let secret: Vec = decode_b58(SECRET_B58).unwrap(); - - let signature = ed25519_sign(b"hello", &secret).unwrap(); - let verified = ed25519_verify(&signature, &public).unwrap(); - - assert_eq!(&signature, SIGNATURE_HELLO); - assert_eq!(&verified, b"hello"); - } - - #[test] - fn test_jcsed25519signature2020_can_sign_and_verify() { - let secret = decode_b58(SECRET_B58).unwrap(); - let mut unsigned: VerifiableDocument = VerifiableDocument::from_json(UNSIGNED).unwrap(); - let signed: VerifiableDocument = VerifiableDocument::from_json(SIGNED).unwrap(); - let method = unsigned.try_resolve("#key-1").unwrap(); - let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); - let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); - - suite.sign(&mut unsigned, options, &secret).unwrap(); - - assert!(suite.verify(&unsigned).is_ok()); - assert_eq!( - unsigned.properties().proof().unwrap().data().as_str(), - SIGNATURE_DOCUMENT - ); - - assert_eq!( - serde_jcs::to_vec(&unsigned).unwrap(), - serde_jcs::to_vec(&signed).unwrap() - ); - } - - #[test] - fn test_jcsed25519signature2020_fails_when_key_is_mutated() { - let secret = decode_b58(SECRET_B58).unwrap(); - let mut document: VerifiableDocument = VerifiableDocument::from_json(UNSIGNED).unwrap(); - let method = document.try_resolve("#key-1").unwrap(); - let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); - let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); - - suite.sign(&mut document, options, &secret).unwrap(); - - assert!(suite.verify(&document).is_ok()); - assert_eq!( - document.properties().proof().unwrap().data().as_str(), - SIGNATURE_DOCUMENT - ); - - document.proof_mut().unwrap().verification_method = "#key-2".into(); - - assert!(suite.verify(&document).is_err()); - } - - #[test] - fn test_jcsed25519signature2020_fails_when_signature_is_mutated() { - let secret = decode_b58(SECRET_B58).unwrap(); - let mut document: VerifiableDocument = VerifiableDocument::from_json(UNSIGNED).unwrap(); - let method = document.try_resolve("#key-1").unwrap(); - let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); - let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); - - suite.sign(&mut document, options, &secret).unwrap(); - - assert!(suite.verify(&document).is_ok()); - assert_eq!( - document.properties().proof().unwrap().data().as_str(), - SIGNATURE_DOCUMENT - ); - - document - .proof_mut() - .unwrap() - .set_data(SignatureData::Signature("foo".into())); - - assert!(suite.verify(&document).is_err()); - } -} diff --git a/identity-credential/Cargo.toml b/identity-credential/Cargo.toml index 909bb99f73..33d62abee8 100644 --- a/identity-credential/Cargo.toml +++ b/identity-credential/Cargo.toml @@ -11,9 +11,8 @@ keywords = ["iota", "tangle", "identity"] homepage = "https://www.iota.org" [dependencies] -did_doc = { git = "https://github.com/l1h3r/did_doc", rev = "bb8bda8f6d39451b08e3cc198f956c212a3b87f8", default-features = false, features = ["std"] } -did_url = { version = "0.1", default-features = false, features = ["std", "serde"] } identity-core = { version = "=0.1.0", path = "../identity-core" } +identity-did = { version = "=0.1.0", path = "../identity-did" } lazy_static = { version = "1.4", default-features = false } serde = { version = "1.0", default-features = false, features = ["std", "derive"] } thiserror = { version = "1.0", default-features = false } diff --git a/identity-credential/src/credential/credential.rs b/identity-credential/src/credential/credential.rs index 9e63c9a246..1b5adc6008 100644 --- a/identity-credential/src/credential/credential.rs +++ b/identity-credential/src/credential/credential.rs @@ -5,20 +5,20 @@ use core::fmt::Display; use core::fmt::Error as FmtError; use core::fmt::Formatter; use core::fmt::Result as FmtResult; -use did_doc::Document; -use did_doc::LdSuite; -use did_doc::MethodQuery; -use did_doc::MethodType; -use did_doc::MethodWrap; -use did_doc::SignatureOptions; use identity_core::common::Context; use identity_core::common::Object; use identity_core::common::OneOrMany; use identity_core::common::Timestamp; use identity_core::common::Url; use identity_core::convert::ToJson; +use identity_core::crypto::JcsEd25519Signature2020; use identity_core::crypto::SecretKey; -use identity_core::proof::JcsEd25519Signature2020; +use identity_core::crypto::SignatureOptions; +use identity_did::document::Document; +use identity_did::verifiable::LdSuite; +use identity_did::verification::MethodQuery; +use identity_did::verification::MethodType; +use identity_did::verification::MethodWrap; use serde::Serialize; use crate::credential::Builder; @@ -163,7 +163,7 @@ impl Credential { document: &Document, query: Q, secret: &SecretKey, - ) -> did_doc::Result> + ) -> Result> where T: Serialize, Q: Into>, @@ -181,7 +181,7 @@ impl Credential { Ok(verifiable) } - _ => Err(did_doc::Error::message("Verification Method Not Supported")), + _ => Err(Error::DIDError(identity_did::Error::UnknownMethodType)), } } } diff --git a/identity-credential/src/credential/verifiable.rs b/identity-credential/src/credential/verifiable.rs index 78da2b39dd..196a3d695e 100644 --- a/identity-credential/src/credential/verifiable.rs +++ b/identity-credential/src/credential/verifiable.rs @@ -7,13 +7,13 @@ use core::fmt::Formatter; use core::fmt::Result as FmtResult; use core::ops::Deref; use core::ops::DerefMut; -use did_doc::SetSignature; -use did_doc::Signature; -use did_doc::TrySignature; -use did_doc::TrySignatureMut; use identity_core::common::Object; use identity_core::common::OneOrMany; use identity_core::convert::ToJson; +use identity_core::crypto::Signature; +use identity_did::verifiable::SetSignature; +use identity_did::verifiable::TrySignature; +use identity_did::verifiable::TrySignatureMut; use serde::Serialize; use crate::credential::Credential; diff --git a/identity-credential/src/error.rs b/identity-credential/src/error.rs index bac09f69af..4a74332fff 100644 --- a/identity-credential/src/error.rs +++ b/identity-credential/src/error.rs @@ -9,6 +9,9 @@ pub type Result = ::core::result::Result; /// This type represents all possible errors that can occur in the library. #[derive(Debug, thiserror::Error)] pub enum Error { + /// Caused by errors from the `identity_did` crate. + #[error("{0}")] + DIDError(#[from] identity_did::Error), /// Caused when validating a Credential without a valid base context. #[error("Missing Base Context")] MissingBaseContext, diff --git a/identity-credential/src/presentation/verifiable.rs b/identity-credential/src/presentation/verifiable.rs index 1a30dec4bd..fb69156c6a 100644 --- a/identity-credential/src/presentation/verifiable.rs +++ b/identity-credential/src/presentation/verifiable.rs @@ -7,13 +7,13 @@ use core::fmt::Formatter; use core::fmt::Result as FmtResult; use core::ops::Deref; use core::ops::DerefMut; -use did_doc::SetSignature; -use did_doc::Signature; -use did_doc::TrySignature; -use did_doc::TrySignatureMut; use identity_core::common::Object; use identity_core::common::OneOrMany; use identity_core::convert::ToJson; +use identity_core::crypto::Signature; +use identity_did::verifiable::SetSignature; +use identity_did::verifiable::TrySignature; +use identity_did::verifiable::TrySignatureMut; use serde::Serialize; use crate::presentation::Presentation; diff --git a/identity-did/src/error.rs b/identity-did/src/error.rs index b54a7236b4..ce659c47bf 100644 --- a/identity-did/src/error.rs +++ b/identity-did/src/error.rs @@ -9,8 +9,10 @@ pub type Result = ::core::result::Result; /// This type represents all possible errors that can occur in the library. #[derive(Debug, thiserror::Error)] pub enum Error { + /// Caused by errors from the `identity_core` crate. #[error("{0}")] CoreError(#[from] ::identity_core::Error), + /// Caused by errors from the `did_url` crate. #[error("{0}")] DIDError(#[from] ::did_url::Error), diff --git a/identity-did/src/verifiable/document.rs b/identity-did/src/verifiable/document.rs index aa6685cf33..8c8c664acc 100644 --- a/identity-did/src/verifiable/document.rs +++ b/identity-did/src/verifiable/document.rs @@ -7,8 +7,8 @@ use core::fmt::Formatter; use core::fmt::Result as FmtResult; use core::ops::Deref; use core::ops::DerefMut; -use serde::Serialize; use identity_core::crypto::Signature; +use serde::Serialize; use crate::document::Document as Document_; use crate::verifiable::Properties; diff --git a/identity-did/src/verifiable/ld_suite.rs b/identity-did/src/verifiable/ld_suite.rs index 9d2710497f..b5b42438d0 100644 --- a/identity-did/src/verifiable/ld_suite.rs +++ b/identity-did/src/verifiable/ld_suite.rs @@ -2,12 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use core::convert::TryInto; +use identity_core::crypto::SigName; use identity_core::crypto::SigSign; +use identity_core::crypto::SigVerify; use identity_core::crypto::Signature; use identity_core::crypto::SignatureData; use identity_core::crypto::SignatureOptions; -use identity_core::crypto::SigName; -use identity_core::crypto::SigVerify; use serde::Serialize; use crate::error::Error; @@ -16,27 +16,28 @@ use crate::verifiable::ResolveMethod; use crate::verifiable::SetSignature; use crate::verifiable::TrySignature; use crate::verification::MethodQuery; -use crate::verification::MethodWrap; use crate::verification::MethodType; - -pub trait LdSignature: SigName { - const METHODS: &'static [MethodType]; -} +use crate::verification::MethodWrap; #[derive(Clone, Copy, Debug)] pub struct LdSuite { suite: S, + methods: &'static [MethodType], } impl LdSuite { pub fn new(suite: S) -> Self { - Self { suite } + Self::with_methods(suite, &[]) + } + + pub fn with_methods(suite: S, methods: &'static [MethodType]) -> Self { + Self { suite, methods } } } impl LdSuite where - S: SigSign + LdSignature, + S: SigSign + SigName, { pub fn sign(&self, message: &mut T, options: SignatureOptions, secret: &K) -> Result<()> where @@ -55,7 +56,7 @@ where impl LdSuite where - S: SigVerify + LdSignature, + S: SigVerify + SigName, { pub fn verify(&self, message: &T) -> Result<()> where @@ -80,7 +81,7 @@ where let query: MethodQuery<'_> = signature.try_into()?; let method: MethodWrap<'_, M> = resolver.try_resolve_method(query)?; - if !S::METHODS.contains(&method.key_type()) { + if !self.methods.is_empty() && !self.methods.contains(&method.key_type()) { return Err(Error::UnknownMethodType); } From 067ae1cbf3feefddb84b379a83913e84e70a99cc Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 10:49:59 -0800 Subject: [PATCH 14/28] Restore diff support --- identity-core/src/common/url.rs | 32 + .../src/crypto/signature/signature_options.rs | 2 +- identity-did/Cargo.toml | 2 +- identity-did/src/diff/did_key.rs | 31 + identity-did/src/diff/document.rs | 307 ++++++ identity-did/src/diff/method.rs | 159 +++ identity-did/src/diff/method_data.rs | 65 ++ identity-did/src/diff/method_ref.rs | 65 ++ identity-did/src/diff/method_type.rs | 27 + identity-did/src/diff/mod.rs | 17 + identity-did/src/diff/ordered_set.rs | 39 + identity-did/src/diff/service.rs | 135 +++ identity-did/src/lib.rs | 1 + identity-diff/Cargo.toml | 3 +- identity-diff/derive/src/impls/enums.rs | 4 +- identity-diff/derive/src/impls/structs.rs | 4 +- identity-diff/src/did_doc.rs | 917 ------------------ identity-diff/src/lib.rs | 30 +- identity-diff/src/object.rs | 41 + identity-diff/src/url.rs | 32 + 20 files changed, 977 insertions(+), 936 deletions(-) create mode 100644 identity-did/src/diff/did_key.rs create mode 100644 identity-did/src/diff/document.rs create mode 100644 identity-did/src/diff/method.rs create mode 100644 identity-did/src/diff/method_data.rs create mode 100644 identity-did/src/diff/method_ref.rs create mode 100644 identity-did/src/diff/method_type.rs create mode 100644 identity-did/src/diff/mod.rs create mode 100644 identity-did/src/diff/ordered_set.rs create mode 100644 identity-did/src/diff/service.rs delete mode 100644 identity-diff/src/did_doc.rs create mode 100644 identity-diff/src/object.rs create mode 100644 identity-diff/src/url.rs diff --git a/identity-core/src/common/url.rs b/identity-core/src/common/url.rs index 698b68cf68..908777d6ef 100644 --- a/identity-core/src/common/url.rs +++ b/identity-core/src/common/url.rs @@ -9,6 +9,9 @@ use core::ops::Deref; use core::ops::DerefMut; use core::str::FromStr; +use crate::diff; +use crate::diff::Diff; +use crate::diff::DiffString; use crate::error::Error; use crate::error::Result; @@ -61,6 +64,12 @@ impl DerefMut for Url { } } +impl From<::url::Url> for Url { + fn from(other: ::url::Url) -> Self { + Self(other) + } +} + impl FromStr for Url { type Err = Error; @@ -77,3 +86,26 @@ where self.as_str() == other.as_ref() } } + +impl Diff for Url { + type Type = DiffString; + + fn diff(&self, other: &Self) -> diff::Result { + self.to_string().diff(&other.to_string()) + } + + fn merge(&self, diff: Self::Type) -> diff::Result { + self + .to_string() + .merge(diff) + .and_then(|this| Self::parse(&this).map_err(diff::Error::merge)) + } + + fn from_diff(diff: Self::Type) -> diff::Result { + String::from_diff(diff).and_then(|this| Self::parse(&this).map_err(diff::Error::convert)) + } + + fn into_diff(self) -> diff::Result { + self.to_string().into_diff() + } +} diff --git a/identity-core/src/crypto/signature/signature_options.rs b/identity-core/src/crypto/signature/signature_options.rs index 0d7dc7d19e..de1145f3c3 100644 --- a/identity-core/src/crypto/signature/signature_options.rs +++ b/identity-core/src/crypto/signature/signature_options.rs @@ -7,7 +7,7 @@ pub struct SignatureOptions { /// The unique identifier of the DID method used to create this signature. #[serde(rename = "verificationMethod")] pub verification_method: String, - /// The intended purpose of the signature - See [`MethodScope`] for supported values. + /// The intended purpose of the signature. #[serde(rename = "proofPurpose", skip_serializing_if = "Option::is_none")] pub proof_purpose: Option, /// A timestamp of when the signature was created. diff --git a/identity-did/Cargo.toml b/identity-did/Cargo.toml index f77ba00bb4..de42361806 100644 --- a/identity-did/Cargo.toml +++ b/identity-did/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://www.iota.org" [dependencies] async-trait = { version = "0.1", default-features = false } -did_url = { version = "0.1", default-features = false, features = ["alloc", "serde"] } +did_url = { version = "0.1", default-features = false, features = ["std", "serde"] } identity-core = { version = "=0.1.0", path = "../identity-core" } serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } thiserror = { version = "1.0", default-features = false } diff --git a/identity-did/src/diff/did_key.rs b/identity-did/src/diff/did_key.rs new file mode 100644 index 0000000000..472fe821c7 --- /dev/null +++ b/identity-did/src/diff/did_key.rs @@ -0,0 +1,31 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::diff::Diff; +use identity_core::diff::Result; + +use crate::did::DID; +use crate::utils::DIDKey; + +impl Diff for DIDKey +where + T: AsRef + Diff, +{ + type Type = ::Type; + + fn diff(&self, other: &Self) -> Result { + self.clone().into_inner().diff(&other.clone().into_inner()) + } + + fn merge(&self, diff: Self::Type) -> Result { + self.clone().into_inner().merge(diff).map(Self::new) + } + + fn from_diff(diff: Self::Type) -> Result { + T::from_diff(diff).map(Self::new) + } + + fn into_diff(self) -> Result { + self.into_inner().into_diff() + } +} diff --git a/identity-did/src/diff/document.rs b/identity-did/src/diff/document.rs new file mode 100644 index 0000000000..e6ded725c1 --- /dev/null +++ b/identity-did/src/diff/document.rs @@ -0,0 +1,307 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Url; +use identity_core::diff::Diff; +use identity_core::diff::DiffString; +use identity_core::diff::DiffVec; +use identity_core::diff::Error; +use identity_core::diff::Result; +use serde::Deserialize; +use serde::Serialize; + +use crate::did::DID; +use crate::document::Document; +use crate::service::Service; +use crate::utils::DIDKey; +use crate::utils::OrderedSet; +use crate::verification::Method; +use crate::verification::MethodRef; + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(bound(deserialize = ""))] +pub struct DiffDocument +where + T: Diff + Serialize + for<'__de> Deserialize<'__de>, + U: Diff + Serialize + for<'__de> Deserialize<'__de>, + V: Diff + Serialize + for<'__de> Deserialize<'__de>, +{ + #[serde(skip_serializing_if = "Option::is_none")] + id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + controller: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + also_known_as: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + verification_method: Option>>>, + #[serde(skip_serializing_if = "Option::is_none")] + authentication: Option>>>, + #[serde(skip_serializing_if = "Option::is_none")] + assertion_method: Option>>>, + #[serde(skip_serializing_if = "Option::is_none")] + key_agreement: Option>>>, + #[serde(skip_serializing_if = "Option::is_none")] + capability_delegation: Option>>>, + #[serde(skip_serializing_if = "Option::is_none")] + capability_invocation: Option>>>, + #[serde(skip_serializing_if = "Option::is_none")] + service: Option>>>, + #[serde(skip_serializing_if = "Option::is_none")] + properties: Option<::Type>, +} + +impl Diff for Document +where + T: Diff + Serialize + for<'de> Deserialize<'de>, + U: Diff + Serialize + for<'de> Deserialize<'de>, + V: Diff + Serialize + for<'de> Deserialize<'de>, +{ + type Type = DiffDocument; + + fn diff(&self, other: &Self) -> Result { + Ok(DiffDocument { + id: if self.id() == other.id() { + None + } else { + Some(self.id().diff(other.id())?) + }, + controller: if self.controller() == other.controller() { + None + } else { + match (self.controller(), other.controller()) { + (Some(a), Some(b)) => Some(Some(a.diff(&b)?)), + (None, Some(b)) => Some(Some(b.clone().into_diff()?)), + _ => Some(None), + } + }, + also_known_as: if self.also_known_as() == other.also_known_as() { + None + } else { + Some(self.also_known_as().to_vec().diff(&other.also_known_as().to_vec())?) + }, + verification_method: if self.verification_method() == other.verification_method() { + None + } else { + Some(self.verification_method().diff(other.verification_method())?) + }, + authentication: if self.authentication() == other.authentication() { + None + } else { + Some(self.authentication().diff(other.authentication())?) + }, + assertion_method: if self.assertion_method() == other.assertion_method() { + None + } else { + Some(self.assertion_method().diff(other.assertion_method())?) + }, + key_agreement: if self.key_agreement() == other.key_agreement() { + None + } else { + Some(self.key_agreement().diff(other.key_agreement())?) + }, + capability_delegation: if self.capability_delegation() == other.capability_delegation() { + None + } else { + Some(self.capability_delegation().diff(other.capability_delegation())?) + }, + capability_invocation: if self.capability_invocation() == other.capability_invocation() { + None + } else { + Some(self.capability_invocation().diff(other.capability_invocation())?) + }, + service: if self.service() == other.service() { + None + } else { + Some(self.service().diff(&other.service())?) + }, + properties: if self.properties() == other.properties() { + None + } else { + Some(self.properties().diff(other.properties())?) + }, + }) + } + + fn merge(&self, diff: Self::Type) -> Result { + let id: DID = diff + .id + .map(|value| self.id().merge(value)) + .transpose()? + .unwrap_or_else(|| self.id().clone()); + + let controller: Option = diff + .controller + .flatten() + .and_then(|value| self.controller().map(|controller| controller.merge(value))) + .transpose()?; + + let also_known_as: Vec = diff + .also_known_as + .map(|value| self.also_known_as().to_vec().merge(value)) + .transpose()? + .unwrap_or_else(|| self.also_known_as().to_vec()); + + let verification_method: OrderedSet>> = diff + .verification_method + .map(|value| self.verification_method().merge(value)) + .transpose()? + .unwrap_or_else(|| self.verification_method().clone()); + + let authentication: OrderedSet>> = diff + .authentication + .map(|value| self.authentication().merge(value)) + .transpose()? + .unwrap_or_else(|| self.authentication().clone()); + + let assertion_method: OrderedSet>> = diff + .assertion_method + .map(|value| self.assertion_method().merge(value)) + .transpose()? + .unwrap_or_else(|| self.assertion_method().clone()); + + let key_agreement: OrderedSet>> = diff + .key_agreement + .map(|value| self.key_agreement().merge(value)) + .transpose()? + .unwrap_or_else(|| self.key_agreement().clone()); + + let capability_delegation: OrderedSet>> = diff + .capability_delegation + .map(|value| self.capability_delegation().merge(value)) + .transpose()? + .unwrap_or_else(|| self.capability_delegation().clone()); + + let capability_invocation: OrderedSet>> = diff + .capability_invocation + .map(|value| self.capability_invocation().merge(value)) + .transpose()? + .unwrap_or_else(|| self.capability_invocation().clone()); + + let service: OrderedSet>> = diff + .service + .map(|value| self.service().merge(value)) + .transpose()? + .unwrap_or_else(|| self.service().clone()); + + let properties: T = diff + .properties + .map(|value| self.properties().merge(value)) + .transpose()? + .unwrap_or_else(|| self.properties().clone()); + + Ok(Document { + id, + controller, + also_known_as, + verification_method, + authentication, + assertion_method, + key_agreement, + capability_delegation, + capability_invocation, + service, + properties, + }) + } + + fn from_diff(diff: Self::Type) -> Result { + let id: DID = diff + .id + .map(DID::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `id`"))?; + + let controller: Option = diff + .controller + .map(|diff| match diff { + Some(diff) => Some(DID::from_diff(diff)).transpose(), + None => Ok(None), + }) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `controller`"))?; + + let also_known_as: Vec = diff + .also_known_as + .map(Diff::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `also_known_as`"))?; + + let verification_method: OrderedSet>> = diff + .verification_method + .map(Diff::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `verification_method`"))?; + + let authentication: OrderedSet>> = diff + .authentication + .map(Diff::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `authentication`"))?; + + let assertion_method: OrderedSet>> = diff + .assertion_method + .map(Diff::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `assertion_method`"))?; + + let key_agreement: OrderedSet>> = diff + .key_agreement + .map(Diff::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `key_agreement`"))?; + + let capability_delegation: OrderedSet>> = diff + .capability_delegation + .map(Diff::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `capability_delegation`"))?; + + let capability_invocation: OrderedSet>> = diff + .capability_invocation + .map(Diff::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `capability_invocation`"))?; + + let service: OrderedSet>> = diff + .service + .map(Diff::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `service`"))?; + + let properties: T = diff + .properties + .map(T::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `properties`"))?; + + Ok(Document { + id, + controller, + also_known_as, + verification_method, + authentication, + assertion_method, + key_agreement, + capability_delegation, + capability_invocation, + service, + properties, + }) + } + + fn into_diff(self) -> Result { + Ok(DiffDocument { + id: Some(self.id().clone().into_diff()?), + controller: Some(self.controller().cloned().map(|value| value.into_diff()).transpose()?), + also_known_as: Some(self.also_known_as().to_vec().into_diff()?), + verification_method: Some(self.verification_method().to_vec().into_diff()?), + authentication: Some(self.authentication().to_vec().into_diff()?), + assertion_method: Some(self.assertion_method().to_vec().into_diff()?), + key_agreement: Some(self.key_agreement().to_vec().into_diff()?), + capability_delegation: Some(self.capability_delegation().to_vec().into_diff()?), + capability_invocation: Some(self.capability_invocation().to_vec().into_diff()?), + service: Some(self.service().to_vec().into_diff()?), + properties: Some(self.properties().clone().into_diff()?), + }) + } +} diff --git a/identity-did/src/diff/method.rs b/identity-did/src/diff/method.rs new file mode 100644 index 0000000000..d362635d6b --- /dev/null +++ b/identity-did/src/diff/method.rs @@ -0,0 +1,159 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::diff::Diff; +use identity_core::diff::DiffString; +use identity_core::diff::Error; +use identity_core::diff::Result; +use serde::Deserialize; +use serde::Serialize; + +use crate::did::DID; +use crate::diff::DiffMethodData; +use crate::verification::Method; +use crate::verification::MethodData; +use crate::verification::MethodType; + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct DiffMethod +where + T: Diff, +{ + #[serde(skip_serializing_if = "Option::is_none")] + id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + controller: Option, + #[serde(skip_serializing_if = "Option::is_none")] + key_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + key_data: Option, + #[serde(skip_serializing_if = "Option::is_none")] + properties: Option<::Type>, +} + +impl Diff for Method +where + T: Diff + Serialize + for<'de> Deserialize<'de>, +{ + type Type = DiffMethod; + + fn diff(&self, other: &Self) -> Result { + Ok(DiffMethod { + id: if self.id() == other.id() { + None + } else { + Some(self.id().diff(other.id())?) + }, + controller: if self.controller() == other.controller() { + None + } else { + Some(self.controller().diff(other.controller())?) + }, + key_type: if self.key_type() == other.key_type() { + None + } else { + Some(self.key_type().diff(&other.key_type())?) + }, + key_data: if self.key_data() == other.key_data() { + None + } else { + Some(self.key_data().diff(other.key_data())?) + }, + properties: if self.properties() == other.properties() { + None + } else { + Some(self.properties().diff(other.properties())?) + }, + }) + } + + fn merge(&self, diff: Self::Type) -> Result { + let id: DID = diff + .id + .map(|value| self.id().merge(value)) + .transpose()? + .unwrap_or_else(|| self.id().clone()); + + let controller: DID = diff + .controller + .map(|value| self.controller().merge(value)) + .transpose()? + .unwrap_or_else(|| self.controller().clone()); + + let key_data: MethodData = diff + .key_data + .map(|value| self.key_data().merge(value)) + .transpose()? + .unwrap_or_else(|| self.key_data().clone()); + + let key_type: MethodType = diff + .key_type + .map(|value| self.key_type().merge(value)) + .transpose()? + .unwrap_or_else(|| self.key_type()); + + let properties: T = diff + .properties + .map(|value| self.properties().merge(value)) + .transpose()? + .unwrap_or_else(|| self.properties().clone()); + + Ok(Method { + id, + controller, + key_type, + key_data, + properties, + }) + } + + fn from_diff(diff: Self::Type) -> Result { + let id: DID = diff + .id + .map(DID::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `id`"))?; + + let controller: DID = diff + .controller + .map(DID::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `controller`"))?; + + let key_type: MethodType = diff + .key_type + .map(MethodType::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `key_type`"))?; + + let key_data: MethodData = diff + .key_data + .map(MethodData::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `key_data`"))?; + + let properties: T = diff + .properties + .map(T::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `properties`"))?; + + Ok(Method { + id, + controller, + key_type, + key_data, + properties, + }) + } + + fn into_diff(self) -> Result { + Ok(DiffMethod { + id: Some(self.id().to_string().into_diff()?), + controller: Some(self.controller().to_string().into_diff()?), + key_type: Some(self.key_type().into_diff()?), + key_data: Some(self.key_data().clone().into_diff()?), + properties: Some(self.properties().clone().into_diff()?), + }) + } +} diff --git a/identity-did/src/diff/method_data.rs b/identity-did/src/diff/method_data.rs new file mode 100644 index 0000000000..27a8823bb9 --- /dev/null +++ b/identity-did/src/diff/method_data.rs @@ -0,0 +1,65 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::diff::Diff; +use identity_core::diff::DiffObject; +use identity_core::diff::DiffString; +use identity_core::diff::Result; + +use crate::verification::MethodData; + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub enum DiffMethodData { + PublicKeyBase58(#[serde(skip_serializing_if = "Option::is_none")] Option), + PublicKeyHex(#[serde(skip_serializing_if = "Option::is_none")] Option), + PublicKeyJwk(#[serde(skip_serializing_if = "Option::is_none")] Option), +} + +impl Diff for MethodData { + type Type = DiffMethodData; + + fn diff(&self, other: &Self) -> Result { + match (self, other) { + (Self::PublicKeyBase58(a), Self::PublicKeyBase58(b)) if a == b => Ok(DiffMethodData::PublicKeyBase58(None)), + (Self::PublicKeyBase58(a), Self::PublicKeyBase58(b)) => a.diff(b).map(Some).map(DiffMethodData::PublicKeyBase58), + (Self::PublicKeyHex(a), Self::PublicKeyHex(b)) if a == b => Ok(DiffMethodData::PublicKeyHex(None)), + (Self::PublicKeyHex(a), Self::PublicKeyHex(b)) => a.diff(b).map(Some).map(DiffMethodData::PublicKeyHex), + (Self::PublicKeyJwk(a), Self::PublicKeyJwk(b)) if a == b => Ok(DiffMethodData::PublicKeyJwk(None)), + (Self::PublicKeyJwk(a), Self::PublicKeyJwk(b)) => a.diff(b).map(Some).map(DiffMethodData::PublicKeyJwk), + (_, _) => other.clone().into_diff(), + } + } + + fn merge(&self, diff: Self::Type) -> Result { + match (self, diff) { + (Self::PublicKeyBase58(a), DiffMethodData::PublicKeyBase58(Some(ref b))) => { + a.merge(b.clone()).map(Self::PublicKeyBase58) + } + (Self::PublicKeyBase58(a), DiffMethodData::PublicKeyBase58(None)) => Ok(Self::PublicKeyBase58(a.clone())), + (Self::PublicKeyHex(a), DiffMethodData::PublicKeyHex(Some(ref b))) => a.merge(b.clone()).map(Self::PublicKeyHex), + (Self::PublicKeyHex(a), DiffMethodData::PublicKeyHex(None)) => Ok(Self::PublicKeyHex(a.clone())), + (Self::PublicKeyJwk(a), DiffMethodData::PublicKeyJwk(Some(ref b))) => a.merge(b.clone()).map(Self::PublicKeyJwk), + (Self::PublicKeyJwk(a), DiffMethodData::PublicKeyJwk(None)) => Ok(Self::PublicKeyJwk(a.clone())), + (_, diff) => Self::from_diff(diff), + } + } + + fn from_diff(diff: Self::Type) -> Result { + match diff { + DiffMethodData::PublicKeyBase58(Some(value)) => Diff::from_diff(value).map(Self::PublicKeyBase58), + DiffMethodData::PublicKeyBase58(None) => Ok(Self::PublicKeyBase58(Default::default())), + DiffMethodData::PublicKeyHex(Some(value)) => Diff::from_diff(value).map(Self::PublicKeyHex), + DiffMethodData::PublicKeyHex(None) => Ok(Self::PublicKeyHex(Default::default())), + DiffMethodData::PublicKeyJwk(Some(value)) => Diff::from_diff(value).map(Self::PublicKeyJwk), + DiffMethodData::PublicKeyJwk(None) => Ok(Self::PublicKeyJwk(Default::default())), + } + } + + fn into_diff(self) -> Result { + match self { + Self::PublicKeyBase58(value) => value.into_diff().map(Some).map(DiffMethodData::PublicKeyBase58), + Self::PublicKeyHex(value) => value.into_diff().map(Some).map(DiffMethodData::PublicKeyHex), + Self::PublicKeyJwk(value) => value.into_diff().map(Some).map(DiffMethodData::PublicKeyJwk), + } + } +} diff --git a/identity-did/src/diff/method_ref.rs b/identity-did/src/diff/method_ref.rs new file mode 100644 index 0000000000..d731138a85 --- /dev/null +++ b/identity-did/src/diff/method_ref.rs @@ -0,0 +1,65 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::diff::Diff; +use identity_core::diff::DiffString; +use identity_core::diff::Error; +use identity_core::diff::Result; +use serde::Deserialize; +use serde::Serialize; + +use crate::did::DID; +use crate::diff::DiffMethod; +use crate::verification::MethodRef; + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub enum DiffMethodRef +where + T: Diff, +{ + Embed(#[serde(skip_serializing_if = "Option::is_none")] Option>), + Refer(#[serde(skip_serializing_if = "Option::is_none")] Option), +} + +impl Diff for MethodRef +where + T: Diff + Serialize + for<'de> Deserialize<'de>, +{ + type Type = DiffMethodRef; + + fn diff(&self, other: &Self) -> Result { + match (self, other) { + (Self::Embed(a), Self::Embed(b)) if a == b => Ok(DiffMethodRef::Embed(None)), + (Self::Embed(a), Self::Embed(b)) => a.diff(b).map(Some).map(DiffMethodRef::Embed), + (Self::Refer(a), Self::Refer(b)) if a == b => Ok(DiffMethodRef::Refer(None)), + (Self::Refer(a), Self::Refer(b)) => a.diff(b).map(Some).map(DiffMethodRef::Refer), + (_, _) => other.clone().into_diff(), + } + } + + fn merge(&self, diff: Self::Type) -> Result { + match (self, diff) { + (Self::Embed(a), DiffMethodRef::Embed(Some(b))) => a.merge(b).map(Self::Embed), + (Self::Embed(a), DiffMethodRef::Embed(None)) => Ok(Self::Embed(a.clone())), + (Self::Refer(a), DiffMethodRef::Refer(Some(b))) => a.merge(b).map(Self::Refer), + (Self::Refer(a), DiffMethodRef::Refer(None)) => Ok(Self::Refer(a.clone())), + (_, diff) => Self::from_diff(diff), + } + } + + fn from_diff(diff: Self::Type) -> Result { + match diff { + DiffMethodRef::Embed(Some(value)) => Diff::from_diff(value).map(Self::Embed), + DiffMethodRef::Embed(None) => Err(Error::convert("Invalid MethodRef Diff")), + DiffMethodRef::Refer(Some(value)) => DID::from_diff(value).map(Self::Refer), + DiffMethodRef::Refer(None) => Err(Error::convert("Invalid MethodRef Diff")), + } + } + + fn into_diff(self) -> Result { + match self { + Self::Embed(value) => value.into_diff().map(Some).map(DiffMethodRef::Embed), + Self::Refer(value) => value.to_string().into_diff().map(Some).map(DiffMethodRef::Refer), + } + } +} diff --git a/identity-did/src/diff/method_type.rs b/identity-did/src/diff/method_type.rs new file mode 100644 index 0000000000..389de6e14f --- /dev/null +++ b/identity-did/src/diff/method_type.rs @@ -0,0 +1,27 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::diff::Diff; +use identity_core::diff::Result; + +use crate::verification::MethodType; + +impl Diff for MethodType { + type Type = MethodType; + + fn diff(&self, other: &Self) -> Result { + Ok(*other) + } + + fn merge(&self, diff: Self::Type) -> Result { + Ok(diff) + } + + fn from_diff(diff: Self::Type) -> Result { + Ok(diff) + } + + fn into_diff(self) -> Result { + Ok(self) + } +} diff --git a/identity-did/src/diff/mod.rs b/identity-did/src/diff/mod.rs new file mode 100644 index 0000000000..48dd53658e --- /dev/null +++ b/identity-did/src/diff/mod.rs @@ -0,0 +1,17 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod did_key; +mod document; +mod method; +mod method_data; +mod method_ref; +mod method_type; +mod ordered_set; +mod service; + +pub use self::document::DiffDocument; +pub use self::method::DiffMethod; +pub use self::method_data::DiffMethodData; +pub use self::method_ref::DiffMethodRef; +pub use self::service::DiffService; diff --git a/identity-did/src/diff/ordered_set.rs b/identity-did/src/diff/ordered_set.rs new file mode 100644 index 0000000000..0c4f5c63f7 --- /dev/null +++ b/identity-did/src/diff/ordered_set.rs @@ -0,0 +1,39 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::convert::TryFrom; +use identity_core::diff::Diff; +use identity_core::diff::DiffVec; +use identity_core::diff::Error; +use identity_core::diff::Result; +use serde::Deserialize; +use serde::Serialize; + +use crate::utils::OrderedSet; + +impl Diff for OrderedSet +where + T: Diff + Serialize + for<'de> Deserialize<'de>, +{ + type Type = DiffVec; + + fn diff(&self, other: &Self) -> Result { + self.clone().into_vec().diff(&other.clone().into_vec()) + } + + fn merge(&self, diff: Self::Type) -> Result { + self + .clone() + .into_vec() + .merge(diff) + .and_then(|this| Self::try_from(this).map_err(Error::merge)) + } + + fn from_diff(diff: Self::Type) -> Result { + Vec::from_diff(diff).and_then(|this| Self::try_from(this).map_err(Error::convert)) + } + + fn into_diff(self) -> Result { + self.into_vec().into_diff() + } +} diff --git a/identity-did/src/diff/service.rs b/identity-did/src/diff/service.rs new file mode 100644 index 0000000000..ab466ef7c3 --- /dev/null +++ b/identity-did/src/diff/service.rs @@ -0,0 +1,135 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Url; +use identity_core::diff::Diff; +use identity_core::diff::DiffString; +use identity_core::diff::Error; +use identity_core::diff::Result; +use serde::Deserialize; +use serde::Serialize; + +use crate::did::DID; +use crate::service::Service; + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct DiffService +where + T: Diff, +{ + #[serde(skip_serializing_if = "Option::is_none")] + id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + type_: Option, + #[serde(skip_serializing_if = "Option::is_none")] + service_endpoint: Option, + #[serde(skip_serializing_if = "Option::is_none")] + properties: Option<::Type>, +} + +impl Diff for Service +where + T: Diff + Serialize + for<'de> Deserialize<'de>, +{ + type Type = DiffService; + + fn diff(&self, other: &Self) -> Result { + Ok(DiffService { + id: if self.id() == other.id() { + None + } else { + Some(self.id().diff(other.id())?) + }, + type_: if self.type_() == other.type_() { + None + } else { + Some(self.type_().to_string().diff(&other.type_().to_string())?) + }, + service_endpoint: if self.service_endpoint() == other.service_endpoint() { + None + } else { + Some(self.service_endpoint().diff(other.service_endpoint())?) + }, + properties: if self.properties() == other.properties() { + None + } else { + Some(self.properties().diff(other.properties())?) + }, + }) + } + + fn merge(&self, diff: Self::Type) -> Result { + let id: DID = diff + .id + .map(|value| self.id().merge(value)) + .transpose()? + .unwrap_or_else(|| self.id().clone()); + + let type_: String = diff + .type_ + .map(|value| self.type_().to_string().merge(value)) + .transpose()? + .unwrap_or_else(|| self.type_().to_string()); + + let service_endpoint: Url = diff + .service_endpoint + .map(|value| self.service_endpoint().merge(value)) + .transpose()? + .unwrap_or_else(|| self.service_endpoint().clone()); + + let properties: T = diff + .properties + .map(|value| self.properties().merge(value)) + .transpose()? + .unwrap_or_else(|| self.properties().clone()); + + Ok(Service { + id, + type_, + service_endpoint, + properties, + }) + } + + fn from_diff(diff: Self::Type) -> Result { + let id: DID = diff + .id + .map(DID::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `id`"))?; + + let type_: String = diff + .type_ + .map(String::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `type_`"))?; + + let service_endpoint: Url = diff + .service_endpoint + .map(Url::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `service_endpoint`"))?; + + let properties: T = diff + .properties + .map(T::from_diff) + .transpose()? + .ok_or_else(|| Error::convert("Missing field `properties`"))?; + + Ok(Service { + id, + type_, + service_endpoint, + properties, + }) + } + + fn into_diff(self) -> Result { + Ok(DiffService { + id: Some(self.id().to_string().into_diff()?), + type_: Some(self.type_().to_string().into_diff()?), + service_endpoint: Some(self.service_endpoint().to_string().into_diff()?), + properties: Some(self.properties().clone().into_diff()?), + }) + } +} diff --git a/identity-did/src/lib.rs b/identity-did/src/lib.rs index d57dcdced8..d8fba3adf2 100644 --- a/identity-did/src/lib.rs +++ b/identity-did/src/lib.rs @@ -21,6 +21,7 @@ extern crate serde; #[doc(inline)] pub use did_url as did; +pub mod diff; pub mod document; pub mod error; pub mod resolution; diff --git a/identity-diff/Cargo.toml b/identity-diff/Cargo.toml index ca3c92e6db..557bbb81ef 100644 --- a/identity-diff/Cargo.toml +++ b/identity-diff/Cargo.toml @@ -12,8 +12,7 @@ homepage = "https://www.iota.org" [dependencies] anyhow = { version = "1.0" } -did_doc = { git = "https://github.com/l1h3r/did_doc", rev = "bb8bda8f6d39451b08e3cc198f956c212a3b87f8", default-features = false, features = ["std"] } -did_url = { version = "0.1", default-features = false, features = ["std", "serde"] } +did_url = { version = "0.1", default-features = false, features = ["alloc"] } identity-derive = { version = "=0.1.0", path = "derive" } serde = { version = "1.0", features = [ "derive" ] } serde_json = { version = "1.0" } diff --git a/identity-diff/derive/src/impls/enums.rs b/identity-diff/derive/src/impls/enums.rs index 8b56dc5648..ad2f721719 100644 --- a/identity-diff/derive/src/impls/enums.rs +++ b/identity-diff/derive/src/impls/enums.rs @@ -770,7 +770,7 @@ fn parse_from_into( quote! { #fname: Option::None } } else if f.is_option() { quote! { - #fname: if let identity_diff::option::DiffOption::Some(_) = #fname.clone().into_diff()? { + #fname: if let identity_diff::DiffOption::Some(_) = #fname.clone().into_diff()? { Some(#fname.into_diff()?) } else { None @@ -839,7 +839,7 @@ fn parse_from_into( quote! { Option::None } } else if f.is_option() { quote! { - if #fname.clone().into_diff()? == identity_diff::option::DiffOption::None { + if #fname.clone().into_diff()? == identity_diff::DiffOption::None { None } else { Some(#fname.into_diff()?) diff --git a/identity-diff/derive/src/impls/structs.rs b/identity-diff/derive/src/impls/structs.rs index 8369ba0da1..68c7dfa60c 100644 --- a/identity-diff/derive/src/impls/structs.rs +++ b/identity-diff/derive/src/impls/structs.rs @@ -438,7 +438,7 @@ pub fn diff_impl(input: &InputModel) -> TokenStream { quote! { #fname: Option::None } } else if field.is_option() { quote! { - #fname: if let identity_diff::option::DiffOption::Some(_) = #fname.clone().into_diff()? { + #fname: if let identity_diff::DiffOption::Some(_) = #fname.clone().into_diff()? { Some(#fname.into_diff()?) } else { None @@ -575,7 +575,7 @@ pub fn diff_impl(input: &InputModel) -> TokenStream { quote! { Option::None } } else if field.is_option() { quote! { - if #marker.clone().into_diff()? == identity_diff::option::DiffOption::None { + if #marker.clone().into_diff()? == identity_diff::DiffOption::None { None } else { Some(#marker.into_diff()?) diff --git a/identity-diff/src/did_doc.rs b/identity-diff/src/did_doc.rs deleted file mode 100644 index 4f17f0046d..0000000000 --- a/identity-diff/src/did_doc.rs +++ /dev/null @@ -1,917 +0,0 @@ -// Copyright 2020-2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use core::convert::TryFrom as _; -use did_doc::{ - url::Url, DIDKey, Document, DocumentBuilder, Method, MethodBuilder, MethodData, MethodRef, MethodType, Object, - OrderedSet, Service, ServiceBuilder, -}; -use did_url::DID; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::collections::HashMap; - -use crate::{ - error::{Error, Result}, - hashmap::DiffHashMap, - string::DiffString, - traits::Diff, - vec::DiffVec, -}; - -// ============================================================================= -// ============================================================================= - -impl Diff for DID { - type Type = DiffString; - - fn diff(&self, other: &Self) -> Result { - self.to_string().diff(&other.to_string()) - } - - fn merge(&self, diff: Self::Type) -> Result { - self - .to_string() - .merge(diff) - .and_then(|this| Self::parse(&this).map_err(Error::merge)) - } - - fn from_diff(diff: Self::Type) -> Result { - String::from_diff(diff).and_then(|this| Self::parse(&this).map_err(Error::convert)) - } - - fn into_diff(self) -> Result { - self.to_string().into_diff() - } -} - -// ============================================================================= -// ============================================================================= - -impl Diff for Url { - type Type = DiffString; - - fn diff(&self, other: &Self) -> Result { - self.to_string().diff(&other.to_string()) - } - - fn merge(&self, diff: Self::Type) -> Result { - self - .to_string() - .merge(diff) - .and_then(|this| Self::parse(&this).map_err(Error::merge)) - } - - fn from_diff(diff: Self::Type) -> Result { - String::from_diff(diff).and_then(|this| Self::parse(&this).map_err(Error::convert)) - } - - fn into_diff(self) -> Result { - self.to_string().into_diff() - } -} - -// ============================================================================= -// ============================================================================= - -impl Diff for OrderedSet -where - T: Diff + Serialize + for<'de> Deserialize<'de>, -{ - type Type = DiffVec; - - fn diff(&self, other: &Self) -> Result { - self.clone().into_vec().diff(&other.clone().into_vec()) - } - - fn merge(&self, diff: Self::Type) -> Result { - self - .clone() - .into_vec() - .merge(diff) - .and_then(|this| Self::try_from(this).map_err(Error::merge)) - } - - fn from_diff(diff: Self::Type) -> Result { - Vec::from_diff(diff).and_then(|this| Self::try_from(this).map_err(Error::convert)) - } - - fn into_diff(self) -> Result { - self.into_vec().into_diff() - } -} - -// ============================================================================= -// ============================================================================= - -impl Diff for DIDKey -where - T: AsRef + Diff, -{ - type Type = ::Type; - - fn diff(&self, other: &Self) -> Result { - self.clone().into_inner().diff(&other.clone().into_inner()) - } - - fn merge(&self, diff: Self::Type) -> Result { - self.clone().into_inner().merge(diff).map(Self::new) - } - - fn from_diff(diff: Self::Type) -> Result { - T::from_diff(diff).map(Self::new) - } - - fn into_diff(self) -> Result { - self.into_inner().into_diff() - } -} - -// ============================================================================= -// ============================================================================= - -pub type DiffObject = DiffHashMap; -pub type MapProxy = HashMap; - -impl Diff for Object { - type Type = DiffObject; - - fn diff(&self, other: &Self) -> Result { - let a: MapProxy = self.clone().into_iter().collect(); - let b: MapProxy = other.clone().into_iter().collect(); - - a.diff(&b) - } - - fn merge(&self, diff: Self::Type) -> Result { - let this: MapProxy = self.clone().into_iter().collect(); - let this: MapProxy = this.merge(diff)?; - - Ok(this.into_iter().collect()) - } - - fn from_diff(diff: Self::Type) -> Result { - Ok(MapProxy::from_diff(diff)?.into_iter().collect()) - } - - fn into_diff(self) -> Result { - self.into_iter().collect::().into_diff() - } -} - -// ============================================================================= -// ============================================================================= - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -#[serde(bound(deserialize = ""))] -pub struct DiffDocument -where - T: Diff + Serialize + for<'__de> Deserialize<'__de>, - U: Diff + Serialize + for<'__de> Deserialize<'__de>, - V: Diff + Serialize + for<'__de> Deserialize<'__de>, -{ - #[serde(skip_serializing_if = "Option::is_none")] - id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - controller: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - also_known_as: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - verification_method: Option>>>, - #[serde(skip_serializing_if = "Option::is_none")] - authentication: Option>>>, - #[serde(skip_serializing_if = "Option::is_none")] - assertion_method: Option>>>, - #[serde(skip_serializing_if = "Option::is_none")] - key_agreement: Option>>>, - #[serde(skip_serializing_if = "Option::is_none")] - capability_delegation: Option>>>, - #[serde(skip_serializing_if = "Option::is_none")] - capability_invocation: Option>>>, - #[serde(skip_serializing_if = "Option::is_none")] - service: Option>>>, - #[serde(skip_serializing_if = "Option::is_none")] - properties: Option<::Type>, -} - -impl Diff for Document -where - T: Diff + Serialize + for<'de> Deserialize<'de>, - U: Diff + Serialize + for<'de> Deserialize<'de>, - V: Diff + Serialize + for<'de> Deserialize<'de>, -{ - type Type = DiffDocument; - - fn diff(&self, other: &Self) -> Result { - Ok(DiffDocument { - id: if self.id() == other.id() { - None - } else { - Some(self.id().diff(other.id())?) - }, - controller: if self.controller() == other.controller() { - None - } else { - match (self.controller(), other.controller()) { - (Some(a), Some(b)) => Some(Some(a.diff(&b)?)), - (None, Some(b)) => Some(Some(b.clone().into_diff()?)), - _ => Some(None), - } - }, - also_known_as: if self.also_known_as() == other.also_known_as() { - None - } else { - Some(self.also_known_as().to_vec().diff(&other.also_known_as().to_vec())?) - }, - verification_method: if self.verification_method() == other.verification_method() { - None - } else { - Some(self.verification_method().diff(other.verification_method())?) - }, - authentication: if self.authentication() == other.authentication() { - None - } else { - Some(self.authentication().diff(other.authentication())?) - }, - assertion_method: if self.assertion_method() == other.assertion_method() { - None - } else { - Some(self.assertion_method().diff(other.assertion_method())?) - }, - key_agreement: if self.key_agreement() == other.key_agreement() { - None - } else { - Some(self.key_agreement().diff(other.key_agreement())?) - }, - capability_delegation: if self.capability_delegation() == other.capability_delegation() { - None - } else { - Some(self.capability_delegation().diff(other.capability_delegation())?) - }, - capability_invocation: if self.capability_invocation() == other.capability_invocation() { - None - } else { - Some(self.capability_invocation().diff(other.capability_invocation())?) - }, - service: if self.service() == other.service() { - None - } else { - Some(self.service().diff(&other.service())?) - }, - properties: if self.properties() == other.properties() { - None - } else { - Some(self.properties().diff(other.properties())?) - }, - }) - } - - fn merge(&self, diff: Self::Type) -> Result { - let id: DID = diff - .id - .map(|value| self.id().merge(value)) - .transpose()? - .unwrap_or_else(|| self.id().clone()); - - let controller: Option = diff - .controller - .flatten() - .and_then(|value| self.controller().map(|controller| controller.merge(value))) - .transpose()?; - - let also_known_as: Vec = diff - .also_known_as - .map(|value| self.also_known_as().to_vec().merge(value)) - .transpose()? - .unwrap_or_else(|| self.also_known_as().to_vec()); - - let verification_method: OrderedSet>> = diff - .verification_method - .map(|value| self.verification_method().merge(value)) - .transpose()? - .unwrap_or_else(|| self.verification_method().clone()); - - let authentication: OrderedSet>> = diff - .authentication - .map(|value| self.authentication().merge(value)) - .transpose()? - .unwrap_or_else(|| self.authentication().clone()); - - let assertion_method: OrderedSet>> = diff - .assertion_method - .map(|value| self.assertion_method().merge(value)) - .transpose()? - .unwrap_or_else(|| self.assertion_method().clone()); - - let key_agreement: OrderedSet>> = diff - .key_agreement - .map(|value| self.key_agreement().merge(value)) - .transpose()? - .unwrap_or_else(|| self.key_agreement().clone()); - - let capability_delegation: OrderedSet>> = diff - .capability_delegation - .map(|value| self.capability_delegation().merge(value)) - .transpose()? - .unwrap_or_else(|| self.capability_delegation().clone()); - - let capability_invocation: OrderedSet>> = diff - .capability_invocation - .map(|value| self.capability_invocation().merge(value)) - .transpose()? - .unwrap_or_else(|| self.capability_invocation().clone()); - - let service: OrderedSet>> = diff - .service - .map(|value| self.service().merge(value)) - .transpose()? - .unwrap_or_else(|| self.service().clone()); - - let properties: T = diff - .properties - .map(|value| self.properties().merge(value)) - .transpose()? - .unwrap_or_else(|| self.properties().clone()); - - let mut builder: DocumentBuilder = DocumentBuilder::new(properties); - - builder = builder.id(id); - - if let Some(controller) = controller { - builder = builder.controller(controller); - } - - for element in also_known_as { - builder = builder.also_known_as(element); - } - - for element in verification_method.to_vec() { - builder = builder.verification_method(element.into_inner()); - } - - for element in authentication.to_vec() { - builder = builder.authentication(element.into_inner()); - } - - for element in assertion_method.to_vec() { - builder = builder.assertion_method(element.into_inner()); - } - - for element in key_agreement.to_vec() { - builder = builder.key_agreement(element.into_inner()); - } - - for element in capability_delegation.to_vec() { - builder = builder.capability_delegation(element.into_inner()); - } - - for element in capability_invocation.to_vec() { - builder = builder.capability_invocation(element.into_inner()); - } - - for element in service.to_vec() { - builder = builder.service(element.into_inner()); - } - - builder.build().map_err(Error::convert) - } - - fn from_diff(diff: Self::Type) -> Result { - let id: DID = diff - .id - .map(DID::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `id`"))?; - - let controller: Option = diff - .controller - .map(|diff| match diff { - Some(diff) => Some(DID::from_diff(diff)).transpose(), - None => Ok(None), - }) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `controller`"))?; - - let also_known_as: Vec = diff - .also_known_as - .map(Diff::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `also_known_as`"))?; - - let verification_method: OrderedSet>> = diff - .verification_method - .map(Diff::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `verification_method`"))?; - - let authentication: OrderedSet>> = diff - .authentication - .map(Diff::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `authentication`"))?; - - let assertion_method: OrderedSet>> = diff - .assertion_method - .map(Diff::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `assertion_method`"))?; - - let key_agreement: OrderedSet>> = diff - .key_agreement - .map(Diff::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `key_agreement`"))?; - - let capability_delegation: OrderedSet>> = diff - .capability_delegation - .map(Diff::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `capability_delegation`"))?; - - let capability_invocation: OrderedSet>> = diff - .capability_invocation - .map(Diff::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `capability_invocation`"))?; - - let service: OrderedSet>> = diff - .service - .map(Diff::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `service`"))?; - - let properties: T = diff - .properties - .map(T::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `properties`"))?; - - let mut builder: DocumentBuilder = DocumentBuilder::new(properties); - - builder = builder.id(id); - - if let Some(controller) = controller { - builder = builder.controller(controller); - } - - for element in also_known_as { - builder = builder.also_known_as(element); - } - - for element in verification_method.to_vec() { - builder = builder.verification_method(element.into_inner()); - } - - for element in authentication.to_vec() { - builder = builder.authentication(element.into_inner()); - } - - for element in assertion_method.to_vec() { - builder = builder.assertion_method(element.into_inner()); - } - - for element in key_agreement.to_vec() { - builder = builder.key_agreement(element.into_inner()); - } - - for element in capability_delegation.to_vec() { - builder = builder.capability_delegation(element.into_inner()); - } - - for element in capability_invocation.to_vec() { - builder = builder.capability_invocation(element.into_inner()); - } - - for element in service.to_vec() { - builder = builder.service(element.into_inner()); - } - - builder.build().map_err(Error::convert) - } - - fn into_diff(self) -> Result { - Ok(DiffDocument { - id: Some(self.id().clone().into_diff()?), - controller: Some(self.controller().cloned().map(|value| value.into_diff()).transpose()?), - also_known_as: Some(self.also_known_as().to_vec().into_diff()?), - verification_method: Some(self.verification_method().to_vec().into_diff()?), - authentication: Some(self.authentication().to_vec().into_diff()?), - assertion_method: Some(self.assertion_method().to_vec().into_diff()?), - key_agreement: Some(self.key_agreement().to_vec().into_diff()?), - capability_delegation: Some(self.capability_delegation().to_vec().into_diff()?), - capability_invocation: Some(self.capability_invocation().to_vec().into_diff()?), - service: Some(self.service().to_vec().into_diff()?), - properties: Some(self.properties().clone().into_diff()?), - }) - } -} - -// ============================================================================= -// ============================================================================= - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct DiffMethod -where - T: Diff, -{ - #[serde(skip_serializing_if = "Option::is_none")] - id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - controller: Option, - #[serde(skip_serializing_if = "Option::is_none")] - key_type: Option, - #[serde(skip_serializing_if = "Option::is_none")] - key_data: Option, - #[serde(skip_serializing_if = "Option::is_none")] - properties: Option<::Type>, -} - -impl Diff for Method -where - T: Diff + Serialize + for<'de> Deserialize<'de>, -{ - type Type = DiffMethod; - - fn diff(&self, other: &Self) -> Result { - Ok(DiffMethod { - id: if self.id() == other.id() { - None - } else { - Some(self.id().diff(other.id())?) - }, - controller: if self.controller() == other.controller() { - None - } else { - Some(self.controller().diff(other.controller())?) - }, - key_type: if self.key_type() == other.key_type() { - None - } else { - Some(self.key_type().diff(&other.key_type())?) - }, - key_data: if self.key_data() == other.key_data() { - None - } else { - Some(self.key_data().diff(other.key_data())?) - }, - properties: if self.properties() == other.properties() { - None - } else { - Some(self.properties().diff(other.properties())?) - }, - }) - } - - fn merge(&self, diff: Self::Type) -> Result { - let id: DID = diff - .id - .map(|value| self.id().merge(value)) - .transpose()? - .unwrap_or_else(|| self.id().clone()); - - let controller: DID = diff - .controller - .map(|value| self.controller().merge(value)) - .transpose()? - .unwrap_or_else(|| self.controller().clone()); - - let key_data: MethodData = diff - .key_data - .map(|value| self.key_data().merge(value)) - .transpose()? - .unwrap_or_else(|| self.key_data().clone()); - - let key_type: MethodType = diff - .key_type - .map(|value| self.key_type().merge(value)) - .transpose()? - .unwrap_or_else(|| self.key_type()); - - let properties: T = diff - .properties - .map(|value| self.properties().merge(value)) - .transpose()? - .unwrap_or_else(|| self.properties().clone()); - - MethodBuilder::new(properties) - .id(id) - .controller(controller) - .key_type(key_type) - .key_data(key_data) - .build() - .map_err(Error::merge) - } - - fn from_diff(diff: Self::Type) -> Result { - let id: DID = diff - .id - .map(DID::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `id`"))?; - - let controller: DID = diff - .controller - .map(DID::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `controller`"))?; - - let key_type: MethodType = diff - .key_type - .map(MethodType::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `key_type`"))?; - - let key_data: MethodData = diff - .key_data - .map(MethodData::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `key_data`"))?; - - let properties: T = diff - .properties - .map(T::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `properties`"))?; - - MethodBuilder::new(properties) - .id(id) - .controller(controller) - .key_type(key_type) - .key_data(key_data) - .build() - .map_err(Error::convert) - } - - fn into_diff(self) -> Result { - Ok(DiffMethod { - id: Some(self.id().clone().into_diff()?), - controller: Some(self.controller().clone().into_diff()?), - key_type: Some(self.key_type().into_diff()?), - key_data: Some(self.key_data().clone().into_diff()?), - properties: Some(self.properties().clone().into_diff()?), - }) - } -} - -// ============================================================================= -// ============================================================================= - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub enum DiffMethodRef -where - T: Diff, -{ - Embed(#[serde(skip_serializing_if = "Option::is_none")] Option>), - Refer(#[serde(skip_serializing_if = "Option::is_none")] Option), -} - -impl Diff for MethodRef -where - T: Diff + Serialize + for<'de> Deserialize<'de>, -{ - type Type = DiffMethodRef; - - fn diff(&self, other: &Self) -> Result { - match (self, other) { - (Self::Embed(a), Self::Embed(b)) if a == b => Ok(DiffMethodRef::Embed(None)), - (Self::Embed(a), Self::Embed(b)) => a.diff(b).map(Some).map(DiffMethodRef::Embed), - (Self::Refer(a), Self::Refer(b)) if a == b => Ok(DiffMethodRef::Refer(None)), - (Self::Refer(a), Self::Refer(b)) => a.diff(b).map(Some).map(DiffMethodRef::Refer), - (_, _) => other.clone().into_diff(), - } - } - - fn merge(&self, diff: Self::Type) -> Result { - match (self, diff) { - (Self::Embed(a), DiffMethodRef::Embed(Some(ref b))) => a.merge(b.clone()).map(Self::Embed), - (Self::Embed(a), DiffMethodRef::Embed(None)) => Ok(Self::Embed(a.clone())), - (Self::Refer(a), DiffMethodRef::Refer(Some(ref b))) => a.merge(b.clone()).map(Self::Refer), - (Self::Refer(a), DiffMethodRef::Refer(None)) => Ok(Self::Refer(a.clone())), - (_, diff) => Self::from_diff(diff), - } - } - - fn from_diff(diff: Self::Type) -> Result { - match diff { - DiffMethodRef::Embed(Some(value)) => Diff::from_diff(value).map(Self::Embed), - DiffMethodRef::Embed(None) => Err(Error::convert("Invalid MethodRef Diff")), - DiffMethodRef::Refer(Some(value)) => Diff::from_diff(value).map(Self::Refer), - DiffMethodRef::Refer(None) => Err(Error::convert("Invalid MethodRef Diff")), - } - } - - fn into_diff(self) -> Result { - match self { - Self::Embed(value) => value.into_diff().map(Some).map(DiffMethodRef::Embed), - Self::Refer(value) => value.into_diff().map(Some).map(DiffMethodRef::Refer), - } - } -} - -// ============================================================================= -// ============================================================================= - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub enum DiffMethodData { - PublicKeyBase58(#[serde(skip_serializing_if = "Option::is_none")] Option), - PublicKeyHex(#[serde(skip_serializing_if = "Option::is_none")] Option), - PublicKeyJwk(#[serde(skip_serializing_if = "Option::is_none")] Option), -} - -impl Diff for MethodData { - type Type = DiffMethodData; - - fn diff(&self, other: &Self) -> Result { - match (self, other) { - (Self::PublicKeyBase58(a), Self::PublicKeyBase58(b)) if a == b => Ok(DiffMethodData::PublicKeyBase58(None)), - (Self::PublicKeyBase58(a), Self::PublicKeyBase58(b)) => a.diff(b).map(Some).map(DiffMethodData::PublicKeyBase58), - (Self::PublicKeyHex(a), Self::PublicKeyHex(b)) if a == b => Ok(DiffMethodData::PublicKeyHex(None)), - (Self::PublicKeyHex(a), Self::PublicKeyHex(b)) => a.diff(b).map(Some).map(DiffMethodData::PublicKeyHex), - (Self::PublicKeyJwk(a), Self::PublicKeyJwk(b)) if a == b => Ok(DiffMethodData::PublicKeyJwk(None)), - (Self::PublicKeyJwk(a), Self::PublicKeyJwk(b)) => a.diff(b).map(Some).map(DiffMethodData::PublicKeyJwk), - (_, _) => other.clone().into_diff(), - } - } - - fn merge(&self, diff: Self::Type) -> Result { - match (self, diff) { - (Self::PublicKeyBase58(a), DiffMethodData::PublicKeyBase58(Some(ref b))) => { - a.merge(b.clone()).map(Self::PublicKeyBase58) - } - (Self::PublicKeyBase58(a), DiffMethodData::PublicKeyBase58(None)) => Ok(Self::PublicKeyBase58(a.clone())), - (Self::PublicKeyHex(a), DiffMethodData::PublicKeyHex(Some(ref b))) => a.merge(b.clone()).map(Self::PublicKeyHex), - (Self::PublicKeyHex(a), DiffMethodData::PublicKeyHex(None)) => Ok(Self::PublicKeyHex(a.clone())), - (Self::PublicKeyJwk(a), DiffMethodData::PublicKeyJwk(Some(ref b))) => a.merge(b.clone()).map(Self::PublicKeyJwk), - (Self::PublicKeyJwk(a), DiffMethodData::PublicKeyJwk(None)) => Ok(Self::PublicKeyJwk(a.clone())), - (_, diff) => Self::from_diff(diff), - } - } - - fn from_diff(diff: Self::Type) -> Result { - match diff { - DiffMethodData::PublicKeyBase58(Some(value)) => Diff::from_diff(value).map(Self::PublicKeyBase58), - DiffMethodData::PublicKeyBase58(None) => Ok(Self::PublicKeyBase58(Default::default())), - DiffMethodData::PublicKeyHex(Some(value)) => Diff::from_diff(value).map(Self::PublicKeyHex), - DiffMethodData::PublicKeyHex(None) => Ok(Self::PublicKeyHex(Default::default())), - DiffMethodData::PublicKeyJwk(Some(value)) => Diff::from_diff(value).map(Self::PublicKeyJwk), - DiffMethodData::PublicKeyJwk(None) => Ok(Self::PublicKeyJwk(Default::default())), - } - } - - fn into_diff(self) -> Result { - match self { - Self::PublicKeyBase58(value) => value.into_diff().map(Some).map(DiffMethodData::PublicKeyBase58), - Self::PublicKeyHex(value) => value.into_diff().map(Some).map(DiffMethodData::PublicKeyHex), - Self::PublicKeyJwk(value) => value.into_diff().map(Some).map(DiffMethodData::PublicKeyJwk), - _ => Err(Error::convert("Unknown Method Data Variant")), - } - } -} - -// ============================================================================= -// ============================================================================= - -impl Diff for MethodType { - type Type = MethodType; - - fn diff(&self, other: &Self) -> Result { - Ok(*other) - } - - fn merge(&self, diff: Self::Type) -> Result { - Ok(diff) - } - - fn from_diff(diff: Self::Type) -> Result { - Ok(diff) - } - - fn into_diff(self) -> Result { - Ok(self) - } -} - -// ============================================================================= -// ============================================================================= - -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct DiffService -where - T: Diff, -{ - #[serde(skip_serializing_if = "Option::is_none")] - id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - type_: Option, - #[serde(skip_serializing_if = "Option::is_none")] - service_endpoint: Option, - #[serde(skip_serializing_if = "Option::is_none")] - properties: Option<::Type>, -} - -impl Diff for Service -where - T: Diff + Serialize + for<'de> Deserialize<'de>, -{ - type Type = DiffService; - - fn diff(&self, other: &Self) -> Result { - Ok(DiffService { - id: if self.id() == other.id() { - None - } else { - Some(self.id().diff(other.id())?) - }, - type_: if self.type_() == other.type_() { - None - } else { - Some(self.type_().to_string().diff(&other.type_().to_string())?) - }, - service_endpoint: if self.service_endpoint() == other.service_endpoint() { - None - } else { - Some(self.service_endpoint().diff(other.service_endpoint())?) - }, - properties: if self.properties() == other.properties() { - None - } else { - Some(self.properties().diff(other.properties())?) - }, - }) - } - - fn merge(&self, diff: Self::Type) -> Result { - let id: DID = diff - .id - .map(|value| self.id().merge(value)) - .transpose()? - .unwrap_or_else(|| self.id().clone()); - - let type_: String = diff - .type_ - .map(|value| self.type_().to_string().merge(value)) - .transpose()? - .unwrap_or_else(|| self.type_().to_string()); - - let service_endpoint: Url = diff - .service_endpoint - .map(|value| self.service_endpoint().merge(value)) - .transpose()? - .unwrap_or_else(|| self.service_endpoint().clone()); - - let properties: T = diff - .properties - .map(|value| self.properties().merge(value)) - .transpose()? - .unwrap_or_else(|| self.properties().clone()); - - ServiceBuilder::new(properties) - .id(id) - .type_(type_) - .service_endpoint(service_endpoint) - .build() - .map_err(Error::merge) - } - - fn from_diff(diff: Self::Type) -> Result { - let id: DID = diff - .id - .map(DID::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `id`"))?; - - let type_: String = diff - .type_ - .map(String::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `type_`"))?; - - let service_endpoint: Url = diff - .service_endpoint - .map(Url::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `service_endpoint`"))?; - - let properties: T = diff - .properties - .map(T::from_diff) - .transpose()? - .ok_or_else(|| Error::convert("Missing field `properties`"))?; - - ServiceBuilder::new(properties) - .id(id) - .type_(type_) - .service_endpoint(service_endpoint) - .build() - .map_err(Error::convert) - } - - fn into_diff(self) -> Result { - Ok(DiffService { - id: Some(self.id().clone().into_diff()?), - type_: Some(self.type_().to_string().into_diff()?), - service_endpoint: Some(self.service_endpoint().clone().into_diff()?), - properties: Some(self.properties().clone().into_diff()?), - }) - } -} diff --git a/identity-diff/src/lib.rs b/identity-diff/src/lib.rs index 8be89b2824..73b3cec590 100644 --- a/identity-diff/src/lib.rs +++ b/identity-diff/src/lib.rs @@ -8,19 +8,27 @@ //! `bool`, and `char` types. Structs and Enums are supported via `identity_derive` and can be composed of any number //! of these types. -pub mod did_doc; +#[doc(hidden)] +pub use identity_derive::*; + mod error; -pub mod hashmap; -pub mod hashset; +mod hashmap; +mod hashset; mod macros; -pub mod option; -pub mod string; +mod object; +mod option; +mod string; mod traits; +mod url; mod value; -pub mod vec; +mod vec; -pub use error::{Error, Result}; -pub use traits::Diff; - -#[doc(hidden)] -pub use identity_derive::*; +pub use self::error::Error; +pub use self::error::Result; +pub use self::hashmap::DiffHashMap; +pub use self::hashset::DiffHashSet; +pub use self::object::DiffObject; +pub use self::option::DiffOption; +pub use self::string::DiffString; +pub use self::traits::Diff; +pub use self::vec::DiffVec; diff --git a/identity-diff/src/object.rs b/identity-diff/src/object.rs new file mode 100644 index 0000000000..388dd33254 --- /dev/null +++ b/identity-diff/src/object.rs @@ -0,0 +1,41 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use serde_json::Value; +use std::collections::BTreeMap; +use std::collections::HashMap; + +use crate::error::Result; +use crate::hashmap::DiffHashMap; +use crate::traits::Diff; + +pub type DiffObject = DiffHashMap; + +type ObjectSrc = BTreeMap; +type ObjectDst = HashMap; + +impl Diff for ObjectSrc { + type Type = DiffObject; + + fn diff(&self, other: &Self) -> Result { + let a: ObjectDst = self.clone().into_iter().collect(); + let b: ObjectDst = other.clone().into_iter().collect(); + + a.diff(&b) + } + + fn merge(&self, diff: Self::Type) -> Result { + let this: ObjectDst = self.clone().into_iter().collect(); + let this: ObjectDst = this.merge(diff)?; + + Ok(this.into_iter().collect()) + } + + fn from_diff(diff: Self::Type) -> Result { + Ok(ObjectDst::from_diff(diff)?.into_iter().collect()) + } + + fn into_diff(self) -> Result { + self.into_iter().collect::().into_diff() + } +} diff --git a/identity-diff/src/url.rs b/identity-diff/src/url.rs new file mode 100644 index 0000000000..6df67e3253 --- /dev/null +++ b/identity-diff/src/url.rs @@ -0,0 +1,32 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use did_url::DID; + +use crate::error::Error; +use crate::error::Result; +use crate::string::DiffString; +use crate::traits::Diff; + +impl Diff for DID { + type Type = DiffString; + + fn diff(&self, other: &Self) -> Result { + self.to_string().diff(&other.to_string()) + } + + fn merge(&self, diff: Self::Type) -> Result { + self + .to_string() + .merge(diff) + .and_then(|this| Self::parse(&this).map_err(Error::merge)) + } + + fn from_diff(diff: Self::Type) -> Result { + String::from_diff(diff).and_then(|this| Self::parse(&this).map_err(Error::convert)) + } + + fn into_diff(self) -> Result { + self.to_string().into_diff() + } +} From ce893ea45e1c25a1e1be0ad1d50497fefcaf493d Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 11:44:25 -0800 Subject: [PATCH 15/28] Fix identity-iota and misc. --- bindings/wasm/src/key.rs | 7 +- examples/Cargo.toml | 1 + examples/credential.rs | 32 +++--- examples/diff_chain.rs | 34 +++--- examples/resolution.rs | 18 +-- identity-core/examples/merkle_tree.rs | 7 +- identity-core/src/crypto/key/pair.rs | 7 ++ .../crypto/proof/jcsed25519signature2020.rs | 105 +++++++++--------- .../src/presentation/builder.rs | 22 ++-- identity-diff/Cargo.toml | 1 - identity-diff/src/error.rs | 8 +- identity-iota/Cargo.toml | 2 +- identity-iota/src/chain/auth.rs | 13 ++- identity-iota/src/chain/diff.rs | 16 ++- identity-iota/src/chain/document.rs | 13 ++- identity-iota/src/chain/mod.rs | 6 +- identity-iota/src/client/client.rs | 40 ++++--- identity-iota/src/client/client_builder.rs | 7 +- identity-iota/src/client/mod.rs | 12 +- identity-iota/src/client/resolver.rs | 32 +++--- identity-iota/src/client/txn_printer.rs | 15 ++- identity-iota/src/credential/mod.rs | 5 +- identity-iota/src/credential/validator.rs | 23 ++-- identity-iota/src/did/did.rs | 46 ++++---- identity-iota/src/did/document.rs | 72 +++++++----- identity-iota/src/did/document_builder.rs | 28 ++--- identity-iota/src/did/document_diff.rs | 29 +++-- identity-iota/src/did/document_properties.rs | 3 +- identity-iota/src/did/mod.rs | 15 +-- identity-iota/src/error.rs | 20 ++-- identity-iota/src/tangle/message.rs | 35 +++--- identity-iota/src/tangle/message_id.rs | 4 +- identity-iota/src/tangle/message_index.rs | 12 +- identity-iota/src/tangle/mod.rs | 8 +- 34 files changed, 382 insertions(+), 316 deletions(-) diff --git a/bindings/wasm/src/key.rs b/bindings/wasm/src/key.rs index 01a5897eb8..60b78a76b2 100644 --- a/bindings/wasm/src/key.rs +++ b/bindings/wasm/src/key.rs @@ -4,8 +4,7 @@ use identity_core::{ crypto::{KeyPair, PublicKey, SecretKey}, did_doc::MethodType, - proof::JcsEd25519Signature2020, - utils::{decode_b58, encode_b58}, + utils::{decode_b58, encode_b58, generate_ed25519}, }; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; @@ -29,8 +28,8 @@ impl Key { /// Generates a new `Key` object suitable for ed25519 signatures. #[wasm_bindgen(js_name = generateEd25519)] - pub fn generate_ed25519() -> Key { - Self(JcsEd25519Signature2020::new_keypair()) + pub fn generate_ed25519() -> Result { + generate_ed25519().map_err(js_err).map(Self) } /// Parses a `Key` object from base58-encoded public/private keys. diff --git a/examples/Cargo.toml b/examples/Cargo.toml index ce7490ed00..9d2cc3b083 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] identity-core = { path = "../identity-core" } identity-credential = { path = "../identity-credential" } +identity-did = { path = "../identity-did" } identity-iota = { path = "../identity-iota" } smol = { version = "0.1.18", features = ["tokio02"] } smol-potat = { version = "0.3" } diff --git a/examples/credential.rs b/examples/credential.rs index 2e459bb603..2dfbac8141 100644 --- a/examples/credential.rs +++ b/examples/credential.rs @@ -7,21 +7,23 @@ //! //! cargo run --example credential -use identity_core::{ - common::{Url, Value}, - convert::{FromJson as _, ToJson as _}, - crypto::KeyPair, - did_doc::MethodScope, - did_url::DID, - json, -}; -use identity_credential::credential::{Builder as CredentialBuilder, Credential, Subject, VerifiableCredential}; -use identity_iota::{ - client::Client, - credential::{CredentialValidation, CredentialValidator}, - did::IotaDocument, - error::Result, -}; +use identity_core::common::Url; +use identity_core::common::Value; +use identity_core::convert::FromJson; +use identity_core::convert::ToJson; +use identity_core::crypto::KeyPair; +use identity_core::json; +use identity_credential::credential::Builder as CredentialBuilder; +use identity_credential::credential::Credential; +use identity_credential::credential::Subject; +use identity_credential::credential::VerifiableCredential; +use identity_did::did::DID; +use identity_did::verification::MethodScope; +use identity_iota::client::Client; +use identity_iota::credential::CredentialValidation; +use identity_iota::credential::CredentialValidator; +use identity_iota::did::IotaDocument; +use identity_iota::error::Result; // A helper function to generate and new DID Document/KeyPair, sign the // document, publish it to the Tangle, and return the Document/KeyPair. diff --git a/examples/diff_chain.rs b/examples/diff_chain.rs index ade1bba0f7..b2e7b8cbed 100644 --- a/examples/diff_chain.rs +++ b/examples/diff_chain.rs @@ -6,19 +6,23 @@ //! //! cargo run --example diff_chain -use identity_core::{ - crypto::KeyPair, - did_doc::{MethodBuilder, MethodData, MethodRef, MethodType}, - proof::JcsEd25519Signature2020, -}; -use identity_iota::{ - chain::{AuthChain, DocumentChain}, - client::{Client, ClientBuilder, Network}, - did::{DocumentDiff, IotaDocument}, - error::Result, - tangle::MessageId, -}; -use std::{thread::sleep, time::Duration}; +use identity_core::crypto::KeyPair; +use identity_did::verification::Builder as MethodBuilder; +use identity_did::verification::MethodData; +use identity_did::verification::MethodRef; +use identity_did::verification::MethodScope; +use identity_did::verification::MethodType; +use identity_iota::chain::AuthChain; +use identity_iota::chain::DocumentChain; +use identity_iota::client::Client; +use identity_iota::client::ClientBuilder; +use identity_iota::client::Network; +use identity_iota::did::DocumentDiff; +use identity_iota::did::IotaDocument; +use identity_iota::error::Result; +use identity_iota::tangle::MessageId; +use std::thread::sleep; +use std::time::Duration; #[smol_potat::main] async fn main() -> Result<()> { @@ -54,7 +58,7 @@ async fn main() -> Result<()> { { let mut new: IotaDocument = chain.current().clone(); - let keypair: KeyPair = JcsEd25519Signature2020::new_keypair(); + let keypair: KeyPair = KeyPair::new_ed25519().unwrap(); let authentication: MethodRef = MethodBuilder::default() .id(chain.id().join("#key-2")?.into()) @@ -115,7 +119,7 @@ async fn main() -> Result<()> { { let mut new: IotaDocument = chain.current().clone(); - let keypair: KeyPair = JcsEd25519Signature2020::new_keypair(); + let keypair: KeyPair = KeyPair::new_ed25519().unwrap(); let authentication: MethodRef = MethodBuilder::default() .id(new.id().join("#bad-key")?.into()) diff --git a/examples/resolution.rs b/examples/resolution.rs index 31c53b33a7..86a2b2acbe 100644 --- a/examples/resolution.rs +++ b/examples/resolution.rs @@ -6,15 +6,15 @@ //! //! cargo run --example resolution -use identity_core::{ - crypto::KeyPair, - resolver::{dereference, resolve, Dereference, Resolution}, -}; -use identity_iota::{ - client::Client, - did::{IotaDID, IotaDocument}, - error::Result, -}; +use identity_core::crypto::KeyPair; +use identity_did::resolution::dereference; +use identity_did::resolution::resolve; +use identity_did::resolution::Dereference; +use identity_did::resolution::Resolution; +use identity_iota::client::Client; +use identity_iota::did::IotaDID; +use identity_iota::did::IotaDocument; +use identity_iota::error::Result; #[smol_potat::main] async fn main() -> Result<()> { diff --git a/identity-core/examples/merkle_tree.rs b/identity-core/examples/merkle_tree.rs index d0320ebef5..6a3b2e9515 100644 --- a/identity-core/examples/merkle_tree.rs +++ b/identity-core/examples/merkle_tree.rs @@ -10,15 +10,14 @@ use identity_core::crypto::merkle_tree::MTree; use identity_core::crypto::merkle_tree::Proof; use identity_core::crypto::KeyPair; use identity_core::error::Result; -use identity_core::proof::JcsEd25519Signature2020; use rand::rngs::OsRng; use rand::Rng; use sha2::Sha256; const LEAVES: usize = 1 << 8; -fn generate_leaves(count: usize) -> Vec { - (0..count).map(|_| JcsEd25519Signature2020::new_keypair()).collect() +fn generate_leaves(count: usize) -> Result> { + (0..count).map(|_| KeyPair::new_ed25519()).collect() } fn generate_hashes<'a, D, T, I>(digest: &mut D, leaves: I) -> Vec> @@ -46,7 +45,7 @@ fn main() -> Result<()> { println!("Target Index: {}", index); // Generate a list of keypairs to use for the Merkle tree. - let kpairs: Vec = generate_leaves(LEAVES); + let kpairs: Vec = generate_leaves(LEAVES).unwrap(); // Hash all keypairs with SHA-256. let leaves: _ = kpairs.iter().map(KeyPair::public); diff --git a/identity-core/src/crypto/key/pair.rs b/identity-core/src/crypto/key/pair.rs index 39623ad987..51c4dd831d 100644 --- a/identity-core/src/crypto/key/pair.rs +++ b/identity-core/src/crypto/key/pair.rs @@ -5,6 +5,8 @@ use zeroize::Zeroize; use crate::crypto::PublicKey; use crate::crypto::SecretKey; +use crate::error::Result; +use crate::utils::generate_ed25519; /// A convenience for storing a pair of cryptographic keys #[derive(Clone, Debug)] @@ -14,6 +16,11 @@ pub struct KeyPair { } impl KeyPair { + /// Creates a new Ed25519 [`KeyPair`]. + pub fn new_ed25519() -> Result { + generate_ed25519() + } + /// Creates a new [`KeyPair`] from the given keys. pub const fn new(public: PublicKey, secret: SecretKey) -> Self { Self { public, secret } diff --git a/identity-core/src/crypto/proof/jcsed25519signature2020.rs b/identity-core/src/crypto/proof/jcsed25519signature2020.rs index f5bc030de9..6eb66af0ac 100644 --- a/identity-core/src/crypto/proof/jcsed25519signature2020.rs +++ b/identity-core/src/crypto/proof/jcsed25519signature2020.rs @@ -17,7 +17,6 @@ use crate::error::Error; use crate::error::Result; use crate::utils::decode_b58; use crate::utils::encode_b58; -use crate::utils::generate_ed25519; use crate::utils::jcs_sha256; const SIGNATURE_NAME: &str = "JcsEd25519Signature2020"; @@ -40,7 +39,7 @@ impl JcsEd25519Signature2020 { /// Generates a new [`KeyPair`] appropriate for this signature suite. pub fn new_keypair() -> KeyPair { // TODO: Remove unwrap - generate_ed25519().unwrap() + KeyPair::new_ed25519().unwrap() } /// Signs the given `data` with `secret` and returns a digital signature. @@ -145,62 +144,62 @@ pub(crate) fn ed25519_verify(message: &[u8], signature: &[u8], public: &[u8]) -> #[cfg(test)] mod tests { - const UNSIGNED: &str = r##" - { - "id": "did:example:123", - "verificationMethod": [ - { - "id": "did:example:123#key-1", - "type": "JcsEd25519Key2020", - "controller": "did:example:123", - "publicKeyBase58": "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c" - } - ], - "service": [ - { - "id": "did:schema:id", - "type": "schema", - "serviceEndpoint": "https://example.com" - } - ] - } - "##; - - const SIGNED: &str = r##" - { - "id": "did:example:123", - "verificationMethod": [ - { - "id": "did:example:123#key-1", - "type": "JcsEd25519Key2020", - "controller": "did:example:123", - "publicKeyBase58": "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c" - } - ], - "service": [ - { - "id": "did:schema:id", - "type": "schema", - "serviceEndpoint": "https://example.com" - } - ], - "proof": { - "verificationMethod": "#key-1", - "type": "JcsEd25519Signature2020", - "signatureValue": "piKnvB438vWsinW1dqq2EYRzcYFuR7Qm9X8t2S6TPPLDokLwcFBXnnERk6jmS8RXKTJnXKWw1Q9oNhYTwbR7vJkaJT8ZGgwDHNxa6mrMNsQsWkM4rg6EYY99xQko7FnpAMn" - } - } - "##; + // const UNSIGNED: &str = r##" + // { + // "id": "did:example:123", + // "verificationMethod": [ + // { + // "id": "did:example:123#key-1", + // "type": "JcsEd25519Key2020", + // "controller": "did:example:123", + // "publicKeyBase58": "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c" + // } + // ], + // "service": [ + // { + // "id": "did:schema:id", + // "type": "schema", + // "serviceEndpoint": "https://example.com" + // } + // ] + // } + // "##; + + // const SIGNED: &str = r##" + // { + // "id": "did:example:123", + // "verificationMethod": [ + // { + // "id": "did:example:123#key-1", + // "type": "JcsEd25519Key2020", + // "controller": "did:example:123", + // "publicKeyBase58": "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c" + // } + // ], + // "service": [ + // { + // "id": "did:schema:id", + // "type": "schema", + // "serviceEndpoint": "https://example.com" + // } + // ], + // "proof": { + // "verificationMethod": "#key-1", + // "type": "JcsEd25519Signature2020", + // "signatureValue": "piKnvB438vWsinW1dqq2EYRzcYFuR7Qm9X8t2S6TPPLDokLwcFBXnnERk6jmS8RXKTJnXKWw1Q9oNhYTwbR7vJkaJT8ZGgwDHNxa6mrMNsQsWkM4rg6EYY99xQko7FnpAMn" + // } + // } + // "##; // use identity_did::signature::LdSuite; // use identity_did::signature::VerifiableDocument; use super::ed25519_sign; use super::ed25519_verify; - use crate::convert::FromJson; - use crate::crypto::JcsEd25519Signature2020; - use crate::crypto::SignatureData; - use crate::crypto::SignatureOptions; + // use crate::convert::FromJson; + // use crate::crypto::JcsEd25519Signature2020; + // use crate::crypto::SignatureData; + // use crate::crypto::SignatureOptions; use crate::utils::decode_b58; const PUBLIC_B58: &str = "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c"; @@ -209,7 +208,7 @@ mod tests { #[rustfmt::skip] const SIGNATURE_HELLO: &[u8] = &[12, 203, 235, 144, 80, 6, 163, 39, 181, 17, 44, 123, 250, 162, 165, 145, 135, 132, 32, 152, 24, 168, 55, 80, 84, 139, 153, 101, 102, 27, 157, 29, 70, 124, 64, 120, 250, 172, 186, 163, 108, 27, 208, 248, 134, 115, 3, 154, 222, 165, 31, 93, 33, 108, 212, 92, 191, 14, 21, 40, 251, 103, 241, 10, 104, 101, 108, 108, 111]; - const SIGNATURE_DOCUMENT: &str = "piKnvB438vWsinW1dqq2EYRzcYFuR7Qm9X8t2S6TPPLDokLwcFBXnnERk6jmS8RXKTJnXKWw1Q9oNhYTwbR7vJkaJT8ZGgwDHNxa6mrMNsQsWkM4rg6EYY99xQko7FnpAMn"; + // const SIGNATURE_DOCUMENT: &str = "piKnvB438vWsinW1dqq2EYRzcYFuR7Qm9X8t2S6TPPLDokLwcFBXnnERk6jmS8RXKTJnXKWw1Q9oNhYTwbR7vJkaJT8ZGgwDHNxa6mrMNsQsWkM4rg6EYY99xQko7FnpAMn"; #[test] fn test_ed25519_can_sign_and_verify() { diff --git a/identity-credential/src/presentation/builder.rs b/identity-credential/src/presentation/builder.rs index 0ee4a3fde2..ad227a32b1 100644 --- a/identity-credential/src/presentation/builder.rs +++ b/identity-credential/src/presentation/builder.rs @@ -133,22 +133,20 @@ where #[cfg(test)] mod tests { - use did_doc::Document; - use did_doc::DocumentBuilder; - use did_doc::Method; - use did_doc::MethodBuilder; - use did_doc::MethodData; - use did_doc::MethodType; - use did_url::DID; - use serde_json::json; - use serde_json::Value; - use identity_core::common::Object; use identity_core::common::Url; use identity_core::convert::FromJson; use identity_core::crypto::KeyPair; - use identity_core::proof::JcsEd25519Signature2020; use identity_core::utils::encode_b58; + use identity_did::did::DID; + use identity_did::document::Builder as DocumentBuilder; + use identity_did::document::Document; + use identity_did::verification::Builder as MethodBuilder; + use identity_did::verification::Method; + use identity_did::verification::MethodData; + use identity_did::verification::MethodType; + use serde_json::json; + use serde_json::Value; use crate::credential::Builder as CredentialBuilder; use crate::credential::Credential; @@ -176,7 +174,7 @@ mod tests { #[test] #[rustfmt::skip] fn test_presentation_builder_valid() { - let keypair: KeyPair = JcsEd25519Signature2020::new_keypair(); + let keypair: KeyPair = KeyPair::new_ed25519().unwrap(); let controller: DID = "did:example:1234".parse().unwrap(); let method: Method = MethodBuilder::default() diff --git a/identity-diff/Cargo.toml b/identity-diff/Cargo.toml index 557bbb81ef..fd24adee66 100644 --- a/identity-diff/Cargo.toml +++ b/identity-diff/Cargo.toml @@ -11,7 +11,6 @@ keywords = ["iota", "tangle", "identity"] homepage = "https://www.iota.org" [dependencies] -anyhow = { version = "1.0" } did_url = { version = "0.1", default-features = false, features = ["alloc"] } identity-derive = { version = "=0.1.0", path = "derive" } serde = { version = "1.0", features = [ "derive" ] } diff --git a/identity-diff/src/error.rs b/identity-diff/src/error.rs index 5229828b82..69bb462628 100644 --- a/identity-diff/src/error.rs +++ b/identity-diff/src/error.rs @@ -1,11 +1,11 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use anyhow::Result as AnyhowResult; use core::fmt::Display; -use thiserror::Error as DeriveError; -#[derive(Debug, DeriveError)] +pub type Result = ::core::result::Result; + +#[derive(Debug, thiserror::Error)] pub enum Error { #[error("Diff Error: {0}")] DiffError(String), @@ -37,5 +37,3 @@ impl Error { Self::ConversionError(format!("{}", message)) } } - -pub type Result = AnyhowResult; diff --git a/identity-iota/Cargo.toml b/identity-iota/Cargo.toml index 98c3dca731..5006307ea1 100644 --- a/identity-iota/Cargo.toml +++ b/identity-iota/Cargo.toml @@ -11,10 +11,10 @@ keywords = ["iota", "tangle", "identity"] homepage = "https://www.iota.org" [dependencies] -anyhow = { version = "1.0", default-features = false, features = ["std"] } async-trait = { version = "0.1", default-features = false } identity-core = { version = "=0.1.0", path = "../identity-core" } identity-credential = { version = "=0.1.0", path = "../identity-credential" } +identity-did = { version = "=0.1.0", path = "../identity-did" } iota-constants = { version = "0.2", default-features = false } iota-conversion = { version = "0.5", default-features = false } iota-core = { git = "https://github.com/Thoralf-M/iota.rs", rev = "d7c8c64fc3ac2340f0148708a916c245f42fd454" } diff --git a/identity-iota/src/chain/auth.rs b/identity-iota/src/chain/auth.rs index e10301ab45..1dd3795aa2 100644 --- a/identity-iota/src/chain/auth.rs +++ b/identity-iota/src/chain/auth.rs @@ -3,11 +3,14 @@ use core::mem; -use crate::{ - did::{IotaDID, IotaDocument}, - error::{Error, Result}, - tangle::{Message, MessageId, MessageIndex, TangleRef as _}, -}; +use crate::did::IotaDID; +use crate::did::IotaDocument; +use crate::error::Error; +use crate::error::Result; +use crate::tangle::Message; +use crate::tangle::MessageId; +use crate::tangle::MessageIndex; +use crate::tangle::TangleRef; #[derive(Debug)] pub struct AuthChain { diff --git a/identity-iota/src/chain/diff.rs b/identity-iota/src/chain/diff.rs index 043d356b14..8cf95cf09e 100644 --- a/identity-iota/src/chain/diff.rs +++ b/identity-iota/src/chain/diff.rs @@ -3,12 +3,16 @@ use core::slice::Iter; -use crate::{ - chain::{AuthChain, DocumentChain}, - did::{DocumentDiff, IotaDID}, - error::{Error, Result}, - tangle::{Message, MessageId, MessageIndex, TangleRef as _}, -}; +use crate::chain::AuthChain; +use crate::chain::DocumentChain; +use crate::did::DocumentDiff; +use crate::did::IotaDID; +use crate::error::Error; +use crate::error::Result; +use crate::tangle::Message; +use crate::tangle::MessageId; +use crate::tangle::MessageIndex; +use crate::tangle::TangleRef; #[derive(Debug)] pub struct DiffChain { diff --git a/identity-iota/src/chain/document.rs b/identity-iota/src/chain/document.rs index 7a61143590..f5c3459207 100644 --- a/identity-iota/src/chain/document.rs +++ b/identity-iota/src/chain/document.rs @@ -1,12 +1,13 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::{ - chain::{AuthChain, DiffChain}, - did::{DocumentDiff, IotaDID, IotaDocument}, - error::Result, - tangle::MessageId, -}; +use crate::chain::AuthChain; +use crate::chain::DiffChain; +use crate::did::DocumentDiff; +use crate::did::IotaDID; +use crate::did::IotaDocument; +use crate::error::Result; +use crate::tangle::MessageId; #[derive(Debug)] pub struct DocumentChain { diff --git a/identity-iota/src/chain/mod.rs b/identity-iota/src/chain/mod.rs index fac642769b..1646432c73 100644 --- a/identity-iota/src/chain/mod.rs +++ b/identity-iota/src/chain/mod.rs @@ -5,6 +5,6 @@ mod auth; mod diff; mod document; -pub use auth::*; -pub use diff::*; -pub use document::*; +pub use self::auth::AuthChain; +pub use self::diff::DiffChain; +pub use self::document::DocumentChain; diff --git a/identity-iota/src/client/client.rs b/identity-iota/src/client/client.rs index 78ec7c7b36..369a2545d3 100644 --- a/identity-iota/src/client/client.rs +++ b/identity-iota/src/client/client.rs @@ -2,20 +2,32 @@ // SPDX-License-Identifier: Apache-2.0 use core::slice::from_ref; -use identity_core::{common::Url, convert::ToJson}; -use iota::{ - client::{FindTransactionsResponse, GetTrytesResponse, Transfer}, - transaction::bundled::{Address, BundledTransaction, BundledTransactionField as _}, -}; - -use crate::{ - chain::{AuthChain, DiffChain, DocumentChain}, - client::{ClientBuilder, Network, TxnPrinter}, - did::{DocumentDiff, IotaDID, IotaDocument}, - error::{Error, Result}, - tangle::{Message, MessageId}, - utils::{bundles_from_trytes, create_address_from_trits, encode_trits, txn_hash_trytes}, -}; +use identity_core::common::Url; +use identity_core::convert::ToJson; +use iota::client::FindTransactionsResponse; +use iota::client::GetTrytesResponse; +use iota::client::Transfer; +use iota::transaction::bundled::Address; +use iota::transaction::bundled::BundledTransaction; +use iota::transaction::bundled::BundledTransactionField; + +use crate::chain::AuthChain; +use crate::chain::DiffChain; +use crate::chain::DocumentChain; +use crate::client::ClientBuilder; +use crate::client::Network; +use crate::client::TxnPrinter; +use crate::did::DocumentDiff; +use crate::did::IotaDID; +use crate::did::IotaDocument; +use crate::error::Error; +use crate::error::Result; +use crate::tangle::Message; +use crate::tangle::MessageId; +use crate::utils::bundles_from_trytes; +use crate::utils::create_address_from_trits; +use crate::utils::encode_trits; +use crate::utils::txn_hash_trytes; #[derive(Clone, Debug)] pub struct Client { diff --git a/identity-iota/src/client/client_builder.rs b/identity-iota/src/client/client_builder.rs index 4f9aeb79e7..8f88aa9398 100644 --- a/identity-iota/src/client/client_builder.rs +++ b/identity-iota/src/client/client_builder.rs @@ -1,10 +1,9 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::{ - client::{Client, Network}, - error::Result, -}; +use crate::client::Client; +use crate::client::Network; +use crate::error::Result; /// A `ClientBuilder` is used to generated a customized `Client`. #[derive(Clone, Debug)] diff --git a/identity-iota/src/client/mod.rs b/identity-iota/src/client/mod.rs index fbc81c39c5..cc57adee39 100644 --- a/identity-iota/src/client/mod.rs +++ b/identity-iota/src/client/mod.rs @@ -1,15 +1,15 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -#[allow(clippy::module_inception)] +#![allow(clippy::module_inception)] + mod client; mod client_builder; mod network; mod resolver; mod txn_printer; -pub use client::*; -pub use client_builder::*; -pub use network::*; -pub use resolver::*; -pub use txn_printer::*; +pub use self::client::Client; +pub use self::client_builder::ClientBuilder; +pub use self::network::Network; +pub use self::txn_printer::TxnPrinter; diff --git a/identity-iota/src/client/resolver.rs b/identity-iota/src/client/resolver.rs index a359767443..491d021802 100644 --- a/identity-iota/src/client/resolver.rs +++ b/identity-iota/src/client/resolver.rs @@ -2,17 +2,18 @@ // SPDX-License-Identifier: Apache-2.0 use async_trait::async_trait; -use identity_core::{ - convert::SerdeInto as _, - did_url::DID, - error::{Error, Result}, - resolver::{DocumentMetadata, InputMetadata, MetaDocument, ResolverMethod}, -}; +use identity_core::convert::SerdeInto; +use identity_did::did::DID; +use identity_did::error::Error; +use identity_did::error::Result; +use identity_did::resolution::DocumentMetadata; +use identity_did::resolution::InputMetadata; +use identity_did::resolution::MetaDocument; +use identity_did::resolution::ResolverMethod; -use crate::{ - client::Client, - did::{IotaDID, IotaDocument}, -}; +use crate::client::Client; +use crate::did::IotaDID; +use crate::did::IotaDocument; #[async_trait(?Send)] impl ResolverMethod for Client { @@ -23,8 +24,11 @@ impl ResolverMethod for Client { } async fn read(&self, did: &DID, _input: InputMetadata) -> Result> { - let did: &IotaDID = IotaDID::try_from_borrowed(did).map_err(err)?; - let document: IotaDocument = self.read_document(&did).await.map_err(err)?; + let document: IotaDocument = IotaDID::try_from_borrowed(did) + .map_err(|_| Error::MissingResolutionDID) + .map(|did| self.read_document(&did))? + .await + .map_err(|_| Error::MissingResolutionDocument)?; let mut meta: DocumentMetadata = DocumentMetadata::new(); meta.created = Some(document.created()); @@ -36,7 +40,3 @@ impl ResolverMethod for Client { })) } } - -fn err(error: crate::error::Error) -> Error { - Error::ResolutionError(error.into()) -} diff --git a/identity-iota/src/client/txn_printer.rs b/identity-iota/src/client/txn_printer.rs index a15865eff0..6fc8c24620 100644 --- a/identity-iota/src/client/txn_printer.rs +++ b/identity-iota/src/client/txn_printer.rs @@ -1,13 +1,16 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use core::{ - fmt::{Debug, Display, Formatter, Result}, - marker::PhantomData, -}; -use iota::transaction::bundled::{BundledTransaction, BundledTransactionField as _}; +use core::fmt::Debug; +use core::fmt::Display; +use core::fmt::Formatter; +use core::fmt::Result; +use core::marker::PhantomData; +use iota::transaction::bundled::BundledTransaction; +use iota::transaction::bundled::BundledTransactionField; -use crate::utils::{encode_trits, txn_hash_trytes}; +use crate::utils::encode_trits; +use crate::utils::txn_hash_trytes; pub enum __Full {} diff --git a/identity-iota/src/credential/mod.rs b/identity-iota/src/credential/mod.rs index 835cd4c304..e8fc815fbe 100644 --- a/identity-iota/src/credential/mod.rs +++ b/identity-iota/src/credential/mod.rs @@ -3,4 +3,7 @@ mod validator; -pub use validator::*; +pub use self::validator::CredentialValidation; +pub use self::validator::CredentialValidator; +pub use self::validator::DocumentValidation; +pub use self::validator::PresentationValidation; diff --git a/identity-iota/src/credential/validator.rs b/identity-iota/src/credential/validator.rs index bfd09d4b21..8c40cb95c1 100644 --- a/identity-iota/src/credential/validator.rs +++ b/identity-iota/src/credential/validator.rs @@ -1,19 +1,22 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use identity_core::{common::Object, convert::FromJson as _}; -use identity_credential::{credential::VerifiableCredential, presentation::VerifiablePresentation}; -use serde::{de::DeserializeOwned, Serialize}; +use identity_core::common::Object; +use identity_core::convert::FromJson; +use identity_credential::credential::VerifiableCredential; +use identity_credential::presentation::VerifiablePresentation; +use serde::de::DeserializeOwned; +use serde::Serialize; use std::collections::BTreeMap; -use crate::{ - client::Client, - did::{IotaDID, IotaDocument}, - error::{Error, Result}, -}; +use crate::client::Client; +use crate::did::IotaDID; +use crate::did::IotaDocument; +use crate::error::Error; +use crate::error::Result; #[derive(Clone, Debug, PartialEq, Serialize)] -pub struct CredentialValidation { +pub struct CredentialValidation { pub credential: VerifiableCredential, pub issuer: DocumentValidation, pub subjects: BTreeMap, @@ -21,7 +24,7 @@ pub struct CredentialValidation { } #[derive(Clone, Debug, PartialEq, Serialize)] -pub struct PresentationValidation { +pub struct PresentationValidation { pub presentation: VerifiablePresentation, pub holder: DocumentValidation, pub credentials: Vec>, diff --git a/identity-iota/src/did/did.rs b/identity-iota/src/did/did.rs index b8d6aa1ba9..f666f2ceb9 100644 --- a/identity-iota/src/did/did.rs +++ b/identity-iota/src/did/did.rs @@ -1,25 +1,24 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use core::{ - convert::TryFrom, - fmt::{Debug, Display, Formatter, Result as FmtResult}, - ops::Deref, - str::FromStr, -}; -use identity_core::{ - crypto::KeyPair, - did_url::{Error as DIDError, DID}, - proof::JcsEd25519Signature2020, - utils::{decode_b58, encode_b58}, -}; +use core::convert::TryFrom; +use core::fmt::Debug; +use core::fmt::Display; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use core::ops::Deref; +use core::str::FromStr; +use identity_core::crypto::KeyPair; +use identity_core::utils::decode_b58; +use identity_core::utils::encode_b58; +use identity_did::did::Error as DIDError; +use identity_did::did::DID; use multihash::Blake2b256; -use crate::{ - did::Segments, - error::{Error, Result}, - utils::utf8_to_trytes, -}; +use crate::did::Segments; +use crate::error::Error; +use crate::error::Result; +use crate::utils::utf8_to_trytes; // The hash size of BLAKE2b-256 (32-bytes) const BLAKE2B_256_LEN: usize = 32; @@ -42,7 +41,7 @@ impl IotaDID { T: Into>, U: Into>, { - let keypair: KeyPair = JcsEd25519Signature2020::new_keypair(); + let keypair: KeyPair = KeyPair::new_ed25519()?; let public: &[u8] = keypair.public().as_ref(); let did: Self = Self::with_network_and_shard(public, network, shard)?; @@ -316,7 +315,8 @@ impl FromStr for IotaDID { #[cfg(test)] mod tests { use super::*; - use identity_core::{crypto::KeyPair, did_url::DID, proof::JcsEd25519Signature2020}; + use identity_core::crypto::KeyPair; + use identity_did::did::DID; const TAG: &str = "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"; @@ -400,7 +400,7 @@ mod tests { #[test] fn test_new() { - let key: KeyPair = JcsEd25519Signature2020::new_keypair(); + let key: KeyPair = KeyPair::new_ed25519().unwrap(); let did: IotaDID = IotaDID::new(key.public().as_ref()).unwrap(); let tag: String = IotaDID::encode_key(key.public().as_ref()); @@ -411,7 +411,7 @@ mod tests { #[test] fn test_with_network() { - let key: KeyPair = JcsEd25519Signature2020::new_keypair(); + let key: KeyPair = KeyPair::new_ed25519().unwrap(); let did: IotaDID = IotaDID::with_network(key.public().as_ref(), "foo").unwrap(); let tag: String = IotaDID::encode_key(key.public().as_ref()); @@ -422,7 +422,7 @@ mod tests { #[test] fn test_with_network_and_shard() { - let key: KeyPair = JcsEd25519Signature2020::new_keypair(); + let key: KeyPair = KeyPair::new_ed25519().unwrap(); let did: IotaDID = IotaDID::with_network_and_shard(key.public().as_ref(), "foo", "shard-1").unwrap(); let tag: String = IotaDID::encode_key(key.public().as_ref()); @@ -433,7 +433,7 @@ mod tests { #[test] fn test_normalize() { - let key: KeyPair = JcsEd25519Signature2020::new_keypair(); + let key: KeyPair = KeyPair::new_ed25519().unwrap(); let tag: String = IotaDID::encode_key(key.public().as_ref()); // A DID with "main" as the network can be normalized ("main" removed) diff --git a/identity-iota/src/did/document.rs b/identity-iota/src/did/document.rs index 4ea3d7cb12..f1c11fcb10 100644 --- a/identity-iota/src/did/document.rs +++ b/identity-iota/src/did/document.rs @@ -1,31 +1,47 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use core::{ - convert::TryFrom, - fmt::{Debug, Display, Formatter, Result as FmtResult}, - ops::Deref, -}; -use identity_core::{ - common::{Object, Timestamp, Value}, - convert::{FromJson as _, SerdeInto as _}, - crypto::{KeyPair, SecretKey}, - did_doc::{ - Document, LdSuite, MethodQuery, MethodScope, MethodType, MethodWrap, ResolveMethod, SetSignature, Signature, - SignatureOptions, TrySignature, TrySignatureMut, VerifiableDocument, - }, - did_url::DID, - proof::JcsEd25519Signature2020, -}; +use core::convert::TryFrom; +use core::fmt::Debug; +use core::fmt::Display; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use core::ops::Deref; +use identity_core::common::Object; +use identity_core::common::Timestamp; +use identity_core::common::Value; +use identity_core::convert::FromJson; +use identity_core::convert::SerdeInto; +use identity_core::crypto::JcsEd25519Signature2020; +use identity_core::crypto::KeyPair; +use identity_core::crypto::SecretKey; +use identity_core::crypto::Signature; +use identity_core::crypto::SignatureOptions; +use identity_did::did::DID; +use identity_did::document::Document; +use identity_did::verifiable::Document as VerifiableDocument; +use identity_did::verifiable::LdSuite; +use identity_did::verifiable::ResolveMethod; +use identity_did::verifiable::SetSignature; +use identity_did::verifiable::TrySignature; +use identity_did::verifiable::TrySignatureMut; +use identity_did::verification::MethodQuery; +use identity_did::verification::MethodScope; +use identity_did::verification::MethodType; +use identity_did::verification::MethodWrap; use serde::Serialize; -use crate::{ - client::{Client, Network}, - did::{DocumentDiff, IotaDID, IotaDocumentBuilder, Properties}, - error::{Error, Result}, - tangle::{MessageId, TangleRef}, - utils::utf8_to_trytes, -}; +use crate::client::Client; +use crate::client::Network; +use crate::did::DocumentDiff; +use crate::did::IotaDID; +use crate::did::IotaDocumentBuilder; +use crate::did::Properties; +use crate::error::Error; +use crate::error::Result; +use crate::tangle::MessageId; +use crate::tangle::TangleRef; +use crate::utils::utf8_to_trytes; const AUTH_QUERY: (usize, MethodScope) = (0, MethodScope::Authentication); @@ -36,7 +52,7 @@ const ERR_AMIM: &str = "Authentication Method Id Mismatch"; type __Document = VerifiableDocument; #[derive(Clone, PartialEq, Deserialize, Serialize)] -#[serde(try_from = "Document", into = "__Document")] +#[serde(try_from = "Document", into = "__Document")] pub struct IotaDocument { document: __Document, message_id: MessageId, @@ -68,7 +84,7 @@ impl IotaDocument { /// # Errors /// /// Returns `Err` if the document is not a valid `IotaDocument`. - pub fn try_from_document(mut document: Document) -> Result { + pub fn try_from_document(mut document: Document) -> Result { let did: &IotaDID = IotaDID::try_from_borrowed(document.id())?; let key: &DID = document.try_resolve(AUTH_QUERY)?.into_method().id(); @@ -420,10 +436,10 @@ impl From for __Document { } } -impl TryFrom for IotaDocument { +impl TryFrom> for IotaDocument { type Error = Error; - fn try_from(other: Document) -> Result { + fn try_from(other: Document) -> Result { Self::try_from_document(other) } } @@ -464,7 +480,7 @@ impl SetSignature for IotaDocument { } } -impl ResolveMethod for IotaDocument { +impl ResolveMethod<()> for IotaDocument { fn resolve_method(&self, query: MethodQuery<'_>) -> Option> { self.document.resolve(query) } diff --git a/identity-iota/src/did/document_builder.rs b/identity-iota/src/did/document_builder.rs index 2a3691ca80..77c0fd562f 100644 --- a/identity-iota/src/did/document_builder.rs +++ b/identity-iota/src/did/document_builder.rs @@ -1,16 +1,18 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use identity_core::{ - crypto::KeyPair, - did_doc::{DocumentBuilder, Method, MethodBuilder, MethodData, MethodType, VerifiableDocument}, - proof::JcsEd25519Signature2020, -}; - -use crate::{ - did::{IotaDID, IotaDocument, Properties}, - error::Result, -}; +use identity_core::crypto::KeyPair; +use identity_did::document::Builder as DocumentBuilder; +use identity_did::verifiable::Document as VerifiableDocument; +use identity_did::verification::Builder as MethodBuilder; +use identity_did::verification::Method; +use identity_did::verification::MethodData; +use identity_did::verification::MethodType; + +use crate::did::IotaDID; +use crate::did::IotaDocument; +use crate::did::Properties; +use crate::error::Result; #[derive(Clone, Debug)] pub struct IotaDocumentBuilder { @@ -105,10 +107,8 @@ impl IotaDocumentBuilder { fn default_keypair(method: MethodType) -> Result { match method { - MethodType::Ed25519VerificationKey2018 => Ok(JcsEd25519Signature2020::new_keypair()), - _ => { - todo!("Invalid Method Type") - } + MethodType::Ed25519VerificationKey2018 => KeyPair::new_ed25519().map_err(Into::into), + _ => todo!("Invalid Method Type"), } } } diff --git a/identity-iota/src/did/document_diff.rs b/identity-iota/src/did/document_diff.rs index 9f4bb9e36e..860d028b08 100644 --- a/identity-iota/src/did/document_diff.rs +++ b/identity-iota/src/did/document_diff.rs @@ -1,18 +1,23 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use identity_core::{ - convert::{AsJson as _, SerdeInto as _}, - did_doc::{Document, SetSignature, Signature, TrySignature, TrySignatureMut}, - identity_diff::{did_doc::DiffDocument, Diff}, -}; - -use crate::{ - client::{Client, Network}, - did::{IotaDID, IotaDocument}, - error::Result, - tangle::{MessageId, TangleRef}, -}; +use identity_core::convert::AsJson; +use identity_core::convert::SerdeInto; +use identity_core::crypto::Signature; +use identity_core::diff::Diff; +use identity_did::diff::DiffDocument; +use identity_did::document::Document; +use identity_did::verifiable::SetSignature; +use identity_did::verifiable::TrySignature; +use identity_did::verifiable::TrySignatureMut; + +use crate::client::Client; +use crate::client::Network; +use crate::did::IotaDID; +use crate::did::IotaDocument; +use crate::error::Result; +use crate::tangle::MessageId; +use crate::tangle::TangleRef; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct DocumentDiff { diff --git a/identity-iota/src/did/document_properties.rs b/identity-iota/src/did/document_properties.rs index 54792a8e57..355f8bd6ce 100644 --- a/identity-iota/src/did/document_properties.rs +++ b/identity-iota/src/did/document_properties.rs @@ -1,7 +1,8 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use identity_core::common::{Object, Timestamp}; +use identity_core::common::Object; +use identity_core::common::Timestamp; use crate::tangle::MessageId; diff --git a/identity-iota/src/did/mod.rs b/identity-iota/src/did/mod.rs index 99b07c8731..6767578499 100644 --- a/identity-iota/src/did/mod.rs +++ b/identity-iota/src/did/mod.rs @@ -1,7 +1,8 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -#[allow(clippy::module_inception)] +#![allow(clippy::module_inception)] + mod did; mod did_segments; mod document; @@ -9,9 +10,9 @@ mod document_builder; mod document_diff; mod document_properties; -pub use did::*; -pub use did_segments::*; -pub use document::*; -pub use document_builder::*; -pub use document_diff::*; -pub use document_properties::*; +pub use self::did::IotaDID; +pub use self::did_segments::Segments; +pub use self::document::IotaDocument; +pub use self::document_builder::IotaDocumentBuilder; +pub use self::document_diff::DocumentDiff; +pub use self::document_properties::Properties; diff --git a/identity-iota/src/error.rs b/identity-iota/src/error.rs index 3cfed1192a..2087fc4f74 100644 --- a/identity-iota/src/error.rs +++ b/identity-iota/src/error.rs @@ -5,19 +5,19 @@ pub type Result = core::result::Result; #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Core Error: {0}")] + #[error("{0}")] CoreError(#[from] identity_core::Error), - #[error("Diff Error: {0}")] - DiffError(#[from] identity_core::identity_diff::Error), - #[error("Credential Error: {0}")] + #[error("{0}")] + DiffError(#[from] identity_core::diff::Error), + #[error("{0}")] CredError(#[from] identity_credential::Error), - #[error("Invalid DID: {0}")] - InvalidDID(#[from] identity_core::did_url::Error), - #[error("Invalid Document: {0}")] - InvalidDoc(#[from] identity_core::did_doc::Error), - #[error("Client Error: {0}")] + #[error("{0}")] + InvalidDID(#[from] identity_did::did::Error), + #[error("{0}")] + InvalidDoc(#[from] identity_did::Error), + #[error("{0}")] ClientError(#[from] iota::client::error::Error), - #[error("Ternary Error: {0}")] + #[error("{0}")] TernaryError(#[from] iota::ternary::Error), #[error("Invalid Document: {error}")] InvalidDocument { error: &'static str }, diff --git a/identity-iota/src/tangle/message.rs b/identity-iota/src/tangle/message.rs index 3269b88ca7..0ac7077aec 100644 --- a/identity-iota/src/tangle/message.rs +++ b/identity-iota/src/tangle/message.rs @@ -1,20 +1,27 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use core::fmt::{Debug, Formatter, Result as FmtResult}; -use identity_core::convert::FromJson as _; -use iota::{ - crypto::ternary::Hash, - ternary::{T1B1Buf, TritBuf}, - transaction::bundled::{BundledTransaction, BundledTransactionField as _, Timestamp}, -}; - -use crate::{ - did::{DocumentDiff, IotaDID, IotaDocument}, - error::{Error, Result}, - tangle::{MessageId, TangleRef}, - utils::{encode_trits, trytes_to_utf8, txn_hash}, -}; +use core::fmt::Debug; +use core::fmt::Formatter; +use core::fmt::Result as FmtResult; +use identity_core::convert::FromJson; +use iota::crypto::ternary::Hash; +use iota::ternary::T1B1Buf; +use iota::ternary::TritBuf; +use iota::transaction::bundled::BundledTransaction; +use iota::transaction::bundled::BundledTransactionField; +use iota::transaction::bundled::Timestamp; + +use crate::did::DocumentDiff; +use crate::did::IotaDID; +use crate::did::IotaDocument; +use crate::error::Error; +use crate::error::Result; +use crate::tangle::MessageId; +use crate::tangle::TangleRef; +use crate::utils::encode_trits; +use crate::utils::trytes_to_utf8; +use crate::utils::txn_hash; macro_rules! try_extract { ($ty:ty, $this:expr, $did:expr) => {{ diff --git a/identity-iota/src/tangle/message_id.rs b/identity-iota/src/tangle/message_id.rs index 9dfdcfa8b4..484ba9fcbd 100644 --- a/identity-iota/src/tangle/message_id.rs +++ b/identity-iota/src/tangle/message_id.rs @@ -1,7 +1,9 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use core::fmt::{Debug, Formatter, Result}; +use core::fmt::Debug; +use core::fmt::Formatter; +use core::fmt::Result; #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] #[repr(transparent)] diff --git a/identity-iota/src/tangle/message_index.rs b/identity-iota/src/tangle/message_index.rs index 94d59279ac..9b74c9de04 100644 --- a/identity-iota/src/tangle/message_index.rs +++ b/identity-iota/src/tangle/message_index.rs @@ -1,14 +1,14 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use core::{ - borrow::Borrow, - iter::FromIterator, - ops::{Deref, DerefMut}, -}; +use core::borrow::Borrow; +use core::iter::FromIterator; +use core::ops::Deref; +use core::ops::DerefMut; use std::collections::BTreeMap; -use crate::tangle::{MessageId, TangleRef}; +use crate::tangle::MessageId; +use crate::tangle::TangleRef; type __Index = BTreeMap>; diff --git a/identity-iota/src/tangle/mod.rs b/identity-iota/src/tangle/mod.rs index f1b4753299..2ca6569689 100644 --- a/identity-iota/src/tangle/mod.rs +++ b/identity-iota/src/tangle/mod.rs @@ -6,7 +6,7 @@ mod message_id; mod message_index; mod traits; -pub use message::*; -pub use message_id::*; -pub use message_index::*; -pub use traits::*; +pub use self::message::Message; +pub use self::message_id::MessageId; +pub use self::message_index::MessageIndex; +pub use self::traits::TangleRef; From c18f869f6686adea2e8be3f88a4f107adb9d7244 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 12:14:30 -0800 Subject: [PATCH 16/28] Rename builders --- examples/credential.rs | 2 +- examples/diff_chain.rs | 2 +- identity-credential/src/credential/builder.rs | 22 ++++++++--------- .../src/credential/credential.rs | 14 +++++------ identity-credential/src/credential/mod.rs | 2 +- .../src/presentation/builder.rs | 24 +++++++++---------- identity-credential/src/presentation/mod.rs | 2 +- .../src/presentation/presentation.rs | 14 +++++------ identity-did/src/document/builder.rs | 14 +++++------ identity-did/src/document/document.rs | 20 ++++++++-------- identity-did/src/document/mod.rs | 2 +- identity-did/src/service/builder.rs | 16 ++++++------- identity-did/src/service/mod.rs | 2 +- identity-did/src/service/service.rs | 14 +++++------ identity-did/src/verification/builder.rs | 18 +++++++------- identity-did/src/verification/method.rs | 14 +++++------ identity-did/src/verification/mod.rs | 2 +- identity-iota/src/did/document_builder.rs | 4 ++-- 18 files changed, 94 insertions(+), 94 deletions(-) diff --git a/examples/credential.rs b/examples/credential.rs index 2dfbac8141..8cdf742919 100644 --- a/examples/credential.rs +++ b/examples/credential.rs @@ -13,7 +13,7 @@ use identity_core::convert::FromJson; use identity_core::convert::ToJson; use identity_core::crypto::KeyPair; use identity_core::json; -use identity_credential::credential::Builder as CredentialBuilder; +use identity_credential::credential::CredentialBuilder; use identity_credential::credential::Credential; use identity_credential::credential::Subject; use identity_credential::credential::VerifiableCredential; diff --git a/examples/diff_chain.rs b/examples/diff_chain.rs index b2e7b8cbed..d278ea6d33 100644 --- a/examples/diff_chain.rs +++ b/examples/diff_chain.rs @@ -7,7 +7,7 @@ //! cargo run --example diff_chain use identity_core::crypto::KeyPair; -use identity_did::verification::Builder as MethodBuilder; +use identity_did::verification::MethodBuilder; use identity_did::verification::MethodData; use identity_did::verification::MethodRef; use identity_did::verification::MethodScope; diff --git a/identity-credential/src/credential/builder.rs b/identity-credential/src/credential/builder.rs index 96337e4eaf..41b128cd9d 100644 --- a/identity-credential/src/credential/builder.rs +++ b/identity-credential/src/credential/builder.rs @@ -17,9 +17,9 @@ use crate::credential::Status; use crate::credential::Subject; use crate::error::Result; -/// A `Builder` is used to create a customized `Credential`. +/// A `CredentialBuilder` is used to create a customized `Credential`. #[derive(Clone, Debug)] -pub struct Builder { +pub struct CredentialBuilder { pub(crate) context: Vec, pub(crate) id: Option, pub(crate) types: Vec, @@ -36,8 +36,8 @@ pub struct Builder { pub(crate) properties: T, } -impl Builder { - /// Creates a new `Builder`. +impl CredentialBuilder { + /// Creates a new `CredentialBuilder`. pub fn new(properties: T) -> Self { Self { context: vec![Credential::::base_context().clone()], @@ -148,13 +148,13 @@ impl Builder { self } - /// Returns a new `Credential` based on the `Builder` configuration. + /// Returns a new `Credential` based on the `CredentialBuilder` configuration. pub fn build(self) -> Result> { Credential::from_builder(self) } } -impl Builder { +impl CredentialBuilder { /// Adds a new custom property to the `Credential`. #[must_use] pub fn property(mut self, key: K, value: V) -> Self @@ -181,7 +181,7 @@ impl Builder { } } -impl Default for Builder +impl Default for CredentialBuilder where T: Default, { @@ -199,7 +199,7 @@ mod tests { use serde_json::json; use serde_json::Value; - use crate::credential::Builder; + use crate::credential::CredentialBuilder; use crate::credential::Credential; use crate::credential::Subject; @@ -222,7 +222,7 @@ mod tests { #[test] #[rustfmt::skip] fn test_credential_builder_valid() { - let credential: Credential = Builder::default() + let credential: Credential = CredentialBuilder::default() .context(Url::parse("https://www.w3.org/2018/credentials/examples/v1").unwrap()) .id(Url::parse("http://example.edu/credentials/3732").unwrap()) .type_("UniversityDegreeCredential") @@ -250,12 +250,12 @@ mod tests { #[test] #[should_panic = "MissingSubject"] fn test_builder_missing_subjects() { - let _: Credential = Builder::default().issuer(issuer()).build().unwrap(); + let _: Credential = CredentialBuilder::default().issuer(issuer()).build().unwrap(); } #[test] #[should_panic = "MissingIssuer"] fn test_builder_missing_issuer() { - let _: Credential = Builder::default().subject(subject()).build().unwrap(); + let _: Credential = CredentialBuilder::default().subject(subject()).build().unwrap(); } } diff --git a/identity-credential/src/credential/credential.rs b/identity-credential/src/credential/credential.rs index 1b5adc6008..af12c3f483 100644 --- a/identity-credential/src/credential/credential.rs +++ b/identity-credential/src/credential/credential.rs @@ -21,7 +21,7 @@ use identity_did::verification::MethodType; use identity_did::verification::MethodWrap; use serde::Serialize; -use crate::credential::Builder; +use crate::credential::CredentialBuilder; use crate::credential::Evidence; use crate::credential::Issuer; use crate::credential::Policy; @@ -97,15 +97,15 @@ impl Credential { "VerifiableCredential" } - /// Creates a new `Builder` to configure a `Credential`. + /// Creates a new `CredentialBuilder` to configure a `Credential`. /// - /// This is the same as `Builder::new()`. - pub fn builder(properties: T) -> Builder { - Builder::new(properties) + /// This is the same as `CredentialBuilder::new()`. + pub fn builder(properties: T) -> CredentialBuilder { + CredentialBuilder::new(properties) } - /// Returns a new `Credential` based on the `Builder` configuration. - pub fn from_builder(builder: Builder) -> Result { + /// Returns a new `Credential` based on the `CredentialBuilder` configuration. + pub fn from_builder(builder: CredentialBuilder) -> Result { let this: Self = Self { context: builder.context.into(), id: builder.id, diff --git a/identity-credential/src/credential/mod.rs b/identity-credential/src/credential/mod.rs index 08f0dbf87b..7a415ba185 100644 --- a/identity-credential/src/credential/mod.rs +++ b/identity-credential/src/credential/mod.rs @@ -16,7 +16,7 @@ mod status; mod subject; mod verifiable; -pub use self::builder::Builder; +pub use self::builder::CredentialBuilder; pub use self::credential::Credential; pub use self::evidence::Evidence; pub use self::issuer::Issuer; diff --git a/identity-credential/src/presentation/builder.rs b/identity-credential/src/presentation/builder.rs index ad227a32b1..2033688b1e 100644 --- a/identity-credential/src/presentation/builder.rs +++ b/identity-credential/src/presentation/builder.rs @@ -12,9 +12,9 @@ use crate::credential::VerifiableCredential; use crate::error::Result; use crate::presentation::Presentation; -/// A `Builder` is used to create a customized `Presentation`. +/// A `PresentationBuilder` is used to create a customized `Presentation`. #[derive(Clone, Debug)] -pub struct Builder { +pub struct PresentationBuilder { pub(crate) context: Vec, pub(crate) id: Option, pub(crate) types: Vec, @@ -25,8 +25,8 @@ pub struct Builder { pub(crate) properties: T, } -impl Builder { - /// Creates a new `Builder`. +impl PresentationBuilder { + /// Creates a new `PresentationBuilder`. pub fn new(properties: T) -> Self { Self { context: vec![Presentation::::base_context().clone()], @@ -89,13 +89,13 @@ impl Builder { self } - /// Returns a new `Presentation` based on the `Builder` configuration. + /// Returns a new `Presentation` based on the `PresentationBuilder` configuration. pub fn build(self) -> Result> { Presentation::from_builder(self) } } -impl Builder { +impl PresentationBuilder { /// Adds a new custom property to the `Presentation`. #[must_use] pub fn property(mut self, key: K, value: V) -> Self @@ -122,7 +122,7 @@ impl Builder { } } -impl Default for Builder +impl Default for PresentationBuilder where T: Default, { @@ -139,20 +139,20 @@ mod tests { use identity_core::crypto::KeyPair; use identity_core::utils::encode_b58; use identity_did::did::DID; - use identity_did::document::Builder as DocumentBuilder; + use identity_did::document::DocumentBuilder; use identity_did::document::Document; - use identity_did::verification::Builder as MethodBuilder; + use identity_did::verification::MethodBuilder; use identity_did::verification::Method; use identity_did::verification::MethodData; use identity_did::verification::MethodType; use serde_json::json; use serde_json::Value; - use crate::credential::Builder as CredentialBuilder; + use crate::credential::CredentialBuilder; use crate::credential::Credential; use crate::credential::Subject; use crate::credential::VerifiableCredential; - use crate::presentation::Builder; + use crate::presentation::PresentationBuilder; use crate::presentation::Presentation; fn subject() -> Subject { @@ -200,7 +200,7 @@ mod tests { .sign(&document, 0, keypair.secret()) .unwrap(); - let presentation: Presentation = Builder::default() + let presentation: Presentation = PresentationBuilder::default() .type_("ExamplePresentation") .credential(credential) .build() diff --git a/identity-credential/src/presentation/mod.rs b/identity-credential/src/presentation/mod.rs index 9053d1b938..cf2a23b3d1 100644 --- a/identity-credential/src/presentation/mod.rs +++ b/identity-credential/src/presentation/mod.rs @@ -9,6 +9,6 @@ mod builder; mod presentation; mod verifiable; -pub use self::builder::Builder; +pub use self::builder::PresentationBuilder; pub use self::presentation::Presentation; pub use self::verifiable::VerifiablePresentation; diff --git a/identity-credential/src/presentation/presentation.rs b/identity-credential/src/presentation/presentation.rs index f161d0b9ed..a669302849 100644 --- a/identity-credential/src/presentation/presentation.rs +++ b/identity-credential/src/presentation/presentation.rs @@ -18,7 +18,7 @@ use crate::credential::Refresh; use crate::credential::VerifiableCredential; use crate::error::Error; use crate::error::Result; -use crate::presentation::Builder; +use crate::presentation::PresentationBuilder; /// A `Presentation` represents a bundle of one or more `VerifiableCredential`s. /// @@ -62,15 +62,15 @@ impl Presentation { "VerifiablePresentation" } - /// Creates a `Builder` to configure a new `Presentation`. + /// Creates a `PresentationBuilder` to configure a new `Presentation`. /// - /// This is the same as `Builder::new()`. - pub fn builder(properties: T) -> Builder { - Builder::new(properties) + /// This is the same as `PresentationBuilder::new()`. + pub fn builder(properties: T) -> PresentationBuilder { + PresentationBuilder::new(properties) } - /// Returns a new `Presentation` based on the `Builder` configuration. - pub fn from_builder(builder: Builder) -> Result { + /// Returns a new `Presentation` based on the `PresentationBuilder` configuration. + pub fn from_builder(builder: PresentationBuilder) -> Result { let this: Self = Self { context: builder.context.into(), id: builder.id, diff --git a/identity-did/src/document/builder.rs b/identity-did/src/document/builder.rs index 06d639fcd0..f242c9991e 100644 --- a/identity-did/src/document/builder.rs +++ b/identity-did/src/document/builder.rs @@ -11,9 +11,9 @@ use crate::utils::DIDKey; use crate::verification::Method; use crate::verification::MethodRef; -/// A `Builder` is used to generate a customized `Document`. +/// A `DocumentBuilder` is used to generate a customized `Document`. #[derive(Clone, Debug)] -pub struct Builder { +pub struct DocumentBuilder { pub(crate) id: Option, pub(crate) controller: Option, pub(crate) also_known_as: Vec, @@ -27,8 +27,8 @@ pub struct Builder { pub(crate) properties: T, } -impl Builder { - /// Creates a new `Builder`. +impl DocumentBuilder { + /// Creates a new `DocumentBuilder`. pub fn new(properties: T) -> Self { Self { id: None, @@ -115,13 +115,13 @@ impl Builder { self } - /// Returns a new `Document` based on the `Builder` configuration. + /// Returns a new `Document` based on the `DocumentBuilder` configuration. pub fn build(self) -> Result> { Document::from_builder(self) } } -impl Default for Builder +impl Default for DocumentBuilder where T: Default, { @@ -137,6 +137,6 @@ mod tests { #[test] #[should_panic = "InvalidDocumentId"] fn test_missing_id() { - let _: Document = Builder::default().build().unwrap(); + let _: Document = DocumentBuilder::default().build().unwrap(); } } diff --git a/identity-did/src/document/document.rs b/identity-did/src/document/document.rs index 321e83f3d7..7e4cb2c5f1 100644 --- a/identity-did/src/document/document.rs +++ b/identity-did/src/document/document.rs @@ -12,7 +12,7 @@ use identity_core::crypto::SignatureOptions; use serde::Serialize; use crate::did::DID; -use crate::document::Builder; +use crate::document::DocumentBuilder; use crate::error::Error; use crate::error::Result; use crate::service::Service; @@ -53,15 +53,15 @@ pub struct Document { } impl Document { - /// Creates a `Builder` to configure a new `Document`. + /// Creates a `DocumentBuilder` to configure a new `Document`. /// - /// This is the same as `Builder::new()`. - pub fn builder(properties: T) -> Builder { - Builder::new(properties) + /// This is the same as `DocumentBuilder::new()`. + pub fn builder(properties: T) -> DocumentBuilder { + DocumentBuilder::new(properties) } - /// Returns a new `Document` based on the `Builder` configuration. - pub fn from_builder(builder: Builder) -> Result { + /// Returns a new `Document` based on the `DocumentBuilder` configuration. + pub fn from_builder(builder: DocumentBuilder) -> Result { Ok(Self { id: builder.id.ok_or(Error::InvalidDocumentId)?, controller: builder.controller, @@ -334,9 +334,9 @@ impl ResolveMethod for Document { #[cfg(test)] mod tests { use crate::did::DID; - use crate::document::Builder; + use crate::document::DocumentBuilder; use crate::document::Document; - use crate::verification::Builder as MethodBuilder; + use crate::verification::MethodBuilder; use crate::verification::Method; use crate::verification::MethodData; use crate::verification::MethodScope; @@ -359,7 +359,7 @@ mod tests { fn document() -> Document { let controller: DID = controller(); - Builder::default() + DocumentBuilder::default() .id(controller.clone()) .verification_method(method(&controller, "#key-1")) .verification_method(method(&controller, "#key-2")) diff --git a/identity-did/src/document/mod.rs b/identity-did/src/document/mod.rs index 8b8e2bc6fc..3cb23bca7e 100644 --- a/identity-did/src/document/mod.rs +++ b/identity-did/src/document/mod.rs @@ -6,5 +6,5 @@ mod builder; mod document; -pub use self::builder::Builder; +pub use self::builder::DocumentBuilder; pub use self::document::Document; diff --git a/identity-did/src/service/builder.rs b/identity-did/src/service/builder.rs index d3545bf237..79e13db1b2 100644 --- a/identity-did/src/service/builder.rs +++ b/identity-did/src/service/builder.rs @@ -7,17 +7,17 @@ use crate::did::DID; use crate::error::Result; use crate::service::Service; -/// A `Builder` is used to generate a customized `Service`. +/// A `ServiceBuilder` is used to generate a customized `Service`. #[derive(Clone, Debug, Default)] -pub struct Builder { +pub struct ServiceBuilder { pub(crate) id: Option, pub(crate) type_: Option, pub(crate) service_endpoint: Option, pub(crate) properties: T, } -impl Builder { - /// Creates a new `Builder`. +impl ServiceBuilder { + /// Creates a new `ServiceBuilder`. pub fn new(properties: T) -> Self { Self { id: None, @@ -48,7 +48,7 @@ impl Builder { self } - /// Returns a new `Service` based on the `Builder` configuration. + /// Returns a new `Service` based on the `ServiceBuilder` configuration. pub fn build(self) -> Result> { Service::from_builder(self) } @@ -61,7 +61,7 @@ mod tests { #[test] #[should_panic = "InvalidServiceId"] fn test_missing_id() { - let _: Service = Builder::default() + let _: Service = ServiceBuilder::default() .type_("ServiceType") .service_endpoint("https://example.com".parse().unwrap()) .build() @@ -71,7 +71,7 @@ mod tests { #[test] #[should_panic = "InvalidServiceType"] fn test_missing_type_() { - let _: Service = Builder::default() + let _: Service = ServiceBuilder::default() .id("did:example:123".parse().unwrap()) .service_endpoint("https://example.com".parse().unwrap()) .build() @@ -81,7 +81,7 @@ mod tests { #[test] #[should_panic = "InvalidServiceEndpoint"] fn test_missing_service_endpoint() { - let _: Service = Builder::default() + let _: Service = ServiceBuilder::default() .id("did:example:123".parse().unwrap()) .type_("ServiceType") .build() diff --git a/identity-did/src/service/mod.rs b/identity-did/src/service/mod.rs index fd6a538b52..238c2ed098 100644 --- a/identity-did/src/service/mod.rs +++ b/identity-did/src/service/mod.rs @@ -6,5 +6,5 @@ mod builder; mod service; -pub use self::builder::Builder; +pub use self::builder::ServiceBuilder; pub use self::service::Service; diff --git a/identity-did/src/service/service.rs b/identity-did/src/service/service.rs index a38650ae17..ecebba9e9b 100644 --- a/identity-did/src/service/service.rs +++ b/identity-did/src/service/service.rs @@ -12,7 +12,7 @@ use serde::Serialize; use crate::did::DID; use crate::error::Error; use crate::error::Result; -use crate::service::Builder; +use crate::service::ServiceBuilder; /// A DID Document Service #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] @@ -27,15 +27,15 @@ pub struct Service { } impl Service { - /// Creates a `Builder` to configure a new `Service`. + /// Creates a `ServiceBuilder` to configure a new `Service`. /// - /// This is the same as `Builder::new()`. - pub fn builder(properties: T) -> Builder { - Builder::new(properties) + /// This is the same as `ServiceBuilder::new()`. + pub fn builder(properties: T) -> ServiceBuilder { + ServiceBuilder::new(properties) } - /// Returns a new `Service` based on the `Builder` configuration. - pub fn from_builder(builder: Builder) -> Result { + /// Returns a new `Service` based on the `ServiceBuilder` configuration. + pub fn from_builder(builder: ServiceBuilder) -> Result { Ok(Self { id: builder.id.ok_or(Error::InvalidServiceId)?, type_: builder.type_.ok_or(Error::InvalidServiceType)?, diff --git a/identity-did/src/verification/builder.rs b/identity-did/src/verification/builder.rs index 7ea780ef44..f8196f5313 100644 --- a/identity-did/src/verification/builder.rs +++ b/identity-did/src/verification/builder.rs @@ -7,9 +7,9 @@ use crate::verification::Method; use crate::verification::MethodData; use crate::verification::MethodType; -/// A `Builder` is used to generate a customized `Method`. +/// A `MethodBuilder` is used to generate a customized `Method`. #[derive(Clone, Debug, Default)] -pub struct Builder { +pub struct MethodBuilder { pub(crate) id: Option, pub(crate) controller: Option, pub(crate) key_type: Option, @@ -17,8 +17,8 @@ pub struct Builder { pub(crate) properties: T, } -impl Builder { - /// Creates a new `Builder`. +impl MethodBuilder { + /// Creates a new `MethodBuilder`. pub fn new(properties: T) -> Self { Self { id: None, @@ -57,7 +57,7 @@ impl Builder { self } - /// Returns a new `Method` based on the `Builder` configuration. + /// Returns a new `Method` based on the `MethodBuilder` configuration. pub fn build(self) -> Result> { Method::from_builder(self) } @@ -70,7 +70,7 @@ mod tests { #[test] #[should_panic = "InvalidMethodId"] fn test_missing_id() { - let _: Method = Builder::default() + let _: Method = MethodBuilder::default() .controller("did:example:123".parse().unwrap()) .key_type(MethodType::Ed25519VerificationKey2018) .key_data(MethodData::PublicKeyBase58("".into())) @@ -81,7 +81,7 @@ mod tests { #[test] #[should_panic = "InvalidMethodType"] fn test_missing_key_type() { - let _: Method = Builder::default() + let _: Method = MethodBuilder::default() .id("did:example:123".parse().unwrap()) .controller("did:example:123".parse().unwrap()) .key_data(MethodData::PublicKeyBase58("".into())) @@ -92,7 +92,7 @@ mod tests { #[test] #[should_panic = "InvalidMethodData"] fn test_missing_key_data() { - let _: Method = Builder::default() + let _: Method = MethodBuilder::default() .id("did:example:123".parse().unwrap()) .controller("did:example:123".parse().unwrap()) .key_type(MethodType::Ed25519VerificationKey2018) @@ -103,7 +103,7 @@ mod tests { #[test] #[should_panic = "InvalidMethodController"] fn test_missing_controller() { - let _: Method = Builder::default() + let _: Method = MethodBuilder::default() .id("did:example:123".parse().unwrap()) .key_type(MethodType::Ed25519VerificationKey2018) .key_data(MethodData::PublicKeyBase58("".into())) diff --git a/identity-did/src/verification/method.rs b/identity-did/src/verification/method.rs index 865128e8a2..989521f0bf 100644 --- a/identity-did/src/verification/method.rs +++ b/identity-did/src/verification/method.rs @@ -12,7 +12,7 @@ use serde::Serialize; use crate::did::DID; use crate::error::Error; use crate::error::Result; -use crate::verification::Builder; +use crate::verification::MethodBuilder; use crate::verification::MethodData; use crate::verification::MethodType; @@ -30,15 +30,15 @@ pub struct Method { } impl Method { - /// Creates a `Builder` to configure a new `Method`. + /// Creates a `MethodBuilder` to configure a new `Method`. /// - /// This is the same as `Builder::new()`. - pub fn builder(properties: T) -> Builder { - Builder::new(properties) + /// This is the same as `MethodBuilder::new()`. + pub fn builder(properties: T) -> MethodBuilder { + MethodBuilder::new(properties) } - /// Returns a new `Method` based on the `Builder` configuration. - pub fn from_builder(builder: Builder) -> Result { + /// Returns a new `Method` based on the `MethodBuilder` configuration. + pub fn from_builder(builder: MethodBuilder) -> Result { Ok(Method { id: builder.id.ok_or(Error::InvalidMethodId)?, controller: builder.controller.ok_or(Error::InvalidMethodController)?, diff --git a/identity-did/src/verification/mod.rs b/identity-did/src/verification/mod.rs index 1eef575936..820477aaba 100644 --- a/identity-did/src/verification/mod.rs +++ b/identity-did/src/verification/mod.rs @@ -11,7 +11,7 @@ mod method_scope; mod method_type; mod method_wrap; -pub use self::builder::Builder; +pub use self::builder::MethodBuilder; pub use self::method::Method; pub use self::method_data::MethodData; pub use self::method_ident::MethodIdent; diff --git a/identity-iota/src/did/document_builder.rs b/identity-iota/src/did/document_builder.rs index 77c0fd562f..782cea7d85 100644 --- a/identity-iota/src/did/document_builder.rs +++ b/identity-iota/src/did/document_builder.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use identity_core::crypto::KeyPair; -use identity_did::document::Builder as DocumentBuilder; +use identity_did::document::DocumentBuilder; use identity_did::verifiable::Document as VerifiableDocument; -use identity_did::verification::Builder as MethodBuilder; +use identity_did::verification::MethodBuilder; use identity_did::verification::Method; use identity_did::verification::MethodData; use identity_did::verification::MethodType; From 1e3ec2f38babbe9648ca718c7d38cfa4d04c9820 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 12:15:20 -0800 Subject: [PATCH 17/28] Re-export everything through identity crate --- .github/workflows/build-and-test.yml | 1 + .github/workflows/clippy.yml | 1 + .github/workflows/format.yml | 1 + Cargo.toml | 1 + examples/Cargo.toml | 5 +- examples/credential.rs | 34 ++++++------- examples/diff_chain.rs | 29 ++++++----- examples/resolution.rs | 18 +++---- identity-credential/src/credential/builder.rs | 2 +- .../src/presentation/builder.rs | 8 +-- identity-did/src/document/document.rs | 4 +- identity-iota/src/did/document_builder.rs | 2 +- identity/Cargo.toml | 17 +++++++ identity/src/lib.rs | 50 +++++++++++++++++++ 14 files changed, 120 insertions(+), 53 deletions(-) create mode 100644 identity/Cargo.toml create mode 100644 identity/src/lib.rs diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index a2694c4a22..af86ec8926 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -20,6 +20,7 @@ jobs: matrix: project: [ + identity, identity-core, identity-credential, identity-did, diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 5df557fe7f..299d923eab 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -20,6 +20,7 @@ jobs: matrix: project: [ + identity, identity-core, identity-credential, identity-did, diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 2453f5553d..c146e7ca29 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -20,6 +20,7 @@ jobs: matrix: project: [ + identity, identity-core, identity-credential, identity-did, diff --git a/Cargo.toml b/Cargo.toml index 20230f44db..923777665e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "identity", "identity-core", "identity-credential", "identity-did", diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 9d2cc3b083..504e811308 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -5,10 +5,7 @@ edition = "2018" publish = false [dependencies] -identity-core = { path = "../identity-core" } -identity-credential = { path = "../identity-credential" } -identity-did = { path = "../identity-did" } -identity-iota = { path = "../identity-iota" } +identity = { path = "../identity" } smol = { version = "0.1.18", features = ["tokio02"] } smol-potat = { version = "0.3" } diff --git a/examples/credential.rs b/examples/credential.rs index 8cdf742919..be77118d6b 100644 --- a/examples/credential.rs +++ b/examples/credential.rs @@ -7,23 +7,23 @@ //! //! cargo run --example credential -use identity_core::common::Url; -use identity_core::common::Value; -use identity_core::convert::FromJson; -use identity_core::convert::ToJson; -use identity_core::crypto::KeyPair; -use identity_core::json; -use identity_credential::credential::CredentialBuilder; -use identity_credential::credential::Credential; -use identity_credential::credential::Subject; -use identity_credential::credential::VerifiableCredential; -use identity_did::did::DID; -use identity_did::verification::MethodScope; -use identity_iota::client::Client; -use identity_iota::credential::CredentialValidation; -use identity_iota::credential::CredentialValidator; -use identity_iota::did::IotaDocument; -use identity_iota::error::Result; +use identity::core::json; +use identity::core::FromJson; +use identity::core::ToJson; +use identity::core::Url; +use identity::core::Value; +use identity::credential::Credential; +use identity::credential::CredentialBuilder; +use identity::credential::Subject; +use identity::credential::VerifiableCredential; +use identity::crypto::KeyPair; +use identity::did::MethodScope; +use identity::did::DID; +use identity::iota::Client; +use identity::iota::CredentialValidation; +use identity::iota::CredentialValidator; +use identity::iota::IotaDocument; +use identity::iota::Result; // A helper function to generate and new DID Document/KeyPair, sign the // document, publish it to the Tangle, and return the Document/KeyPair. diff --git a/examples/diff_chain.rs b/examples/diff_chain.rs index d278ea6d33..602de58c1f 100644 --- a/examples/diff_chain.rs +++ b/examples/diff_chain.rs @@ -6,21 +6,20 @@ //! //! cargo run --example diff_chain -use identity_core::crypto::KeyPair; -use identity_did::verification::MethodBuilder; -use identity_did::verification::MethodData; -use identity_did::verification::MethodRef; -use identity_did::verification::MethodScope; -use identity_did::verification::MethodType; -use identity_iota::chain::AuthChain; -use identity_iota::chain::DocumentChain; -use identity_iota::client::Client; -use identity_iota::client::ClientBuilder; -use identity_iota::client::Network; -use identity_iota::did::DocumentDiff; -use identity_iota::did::IotaDocument; -use identity_iota::error::Result; -use identity_iota::tangle::MessageId; +use identity::crypto::KeyPair; +use identity::did::MethodBuilder; +use identity::did::MethodData; +use identity::did::MethodRef; +use identity::did::MethodType; +use identity::iota::AuthChain; +use identity::iota::Client; +use identity::iota::ClientBuilder; +use identity::iota::DocumentChain; +use identity::iota::DocumentDiff; +use identity::iota::IotaDocument; +use identity::iota::MessageId; +use identity::iota::Network; +use identity::iota::Result; use std::thread::sleep; use std::time::Duration; diff --git a/examples/resolution.rs b/examples/resolution.rs index 86a2b2acbe..0d0f819195 100644 --- a/examples/resolution.rs +++ b/examples/resolution.rs @@ -6,15 +6,15 @@ //! //! cargo run --example resolution -use identity_core::crypto::KeyPair; -use identity_did::resolution::dereference; -use identity_did::resolution::resolve; -use identity_did::resolution::Dereference; -use identity_did::resolution::Resolution; -use identity_iota::client::Client; -use identity_iota::did::IotaDID; -use identity_iota::did::IotaDocument; -use identity_iota::error::Result; +use identity::crypto::KeyPair; +use identity::did::resolution::dereference; +use identity::did::resolution::resolve; +use identity::did::resolution::Dereference; +use identity::did::resolution::Resolution; +use identity::iota::Client; +use identity::iota::IotaDID; +use identity::iota::IotaDocument; +use identity::iota::Result; #[smol_potat::main] async fn main() -> Result<()> { diff --git a/identity-credential/src/credential/builder.rs b/identity-credential/src/credential/builder.rs index 41b128cd9d..2f01eee330 100644 --- a/identity-credential/src/credential/builder.rs +++ b/identity-credential/src/credential/builder.rs @@ -199,8 +199,8 @@ mod tests { use serde_json::json; use serde_json::Value; - use crate::credential::CredentialBuilder; use crate::credential::Credential; + use crate::credential::CredentialBuilder; use crate::credential::Subject; fn subject() -> Subject { diff --git a/identity-credential/src/presentation/builder.rs b/identity-credential/src/presentation/builder.rs index 2033688b1e..a7bd2411e3 100644 --- a/identity-credential/src/presentation/builder.rs +++ b/identity-credential/src/presentation/builder.rs @@ -139,21 +139,21 @@ mod tests { use identity_core::crypto::KeyPair; use identity_core::utils::encode_b58; use identity_did::did::DID; - use identity_did::document::DocumentBuilder; use identity_did::document::Document; - use identity_did::verification::MethodBuilder; + use identity_did::document::DocumentBuilder; use identity_did::verification::Method; + use identity_did::verification::MethodBuilder; use identity_did::verification::MethodData; use identity_did::verification::MethodType; use serde_json::json; use serde_json::Value; - use crate::credential::CredentialBuilder; use crate::credential::Credential; + use crate::credential::CredentialBuilder; use crate::credential::Subject; use crate::credential::VerifiableCredential; - use crate::presentation::PresentationBuilder; use crate::presentation::Presentation; + use crate::presentation::PresentationBuilder; fn subject() -> Subject { let json: Value = json!({ diff --git a/identity-did/src/document/document.rs b/identity-did/src/document/document.rs index 7e4cb2c5f1..45a20be11b 100644 --- a/identity-did/src/document/document.rs +++ b/identity-did/src/document/document.rs @@ -334,10 +334,10 @@ impl ResolveMethod for Document { #[cfg(test)] mod tests { use crate::did::DID; - use crate::document::DocumentBuilder; use crate::document::Document; - use crate::verification::MethodBuilder; + use crate::document::DocumentBuilder; use crate::verification::Method; + use crate::verification::MethodBuilder; use crate::verification::MethodData; use crate::verification::MethodScope; use crate::verification::MethodType; diff --git a/identity-iota/src/did/document_builder.rs b/identity-iota/src/did/document_builder.rs index 782cea7d85..9e71cbe14b 100644 --- a/identity-iota/src/did/document_builder.rs +++ b/identity-iota/src/did/document_builder.rs @@ -4,8 +4,8 @@ use identity_core::crypto::KeyPair; use identity_did::document::DocumentBuilder; use identity_did::verifiable::Document as VerifiableDocument; -use identity_did::verification::MethodBuilder; use identity_did::verification::Method; +use identity_did::verification::MethodBuilder; use identity_did::verification::MethodData; use identity_did::verification::MethodType; diff --git a/identity/Cargo.toml b/identity/Cargo.toml new file mode 100644 index 0000000000..a85dec0c8f --- /dev/null +++ b/identity/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "identity" +version = "0.1.0" +authors = ["IOTA Identity"] +edition = "2018" +description = "Tools for working with Self-sovereign Identity." +readme = "../README.md" +repository = "https://github.com/iotaledger/identity.rs" +license = "Apache-2.0" +keywords = ["iota", "tangle", "identity"] +homepage = "https://www.iota.org" + +[dependencies] +identity-core = { version = "=0.1.0", path = "../identity-core" } +identity-credential = { version = "=0.1.0", path = "../identity-credential" } +identity-did = { version = "=0.1.0", path = "../identity-did" } +identity-iota = { version = "=0.1.0", path = "../identity-iota" } diff --git a/identity/src/lib.rs b/identity/src/lib.rs new file mode 100644 index 0000000000..d7382f1cee --- /dev/null +++ b/identity/src/lib.rs @@ -0,0 +1,50 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +pub mod core { + pub use identity_core::common::*; + pub use identity_core::convert::*; + pub use identity_core::error::*; + pub use identity_core::utils::*; + + #[doc(inline)] + pub use identity_core::diff; + + #[doc(inline)] + pub use identity_core::json; +} + +pub mod crypto { + pub use identity_core::crypto::*; +} + +pub mod credential { + pub use identity_credential::credential::*; + pub use identity_credential::error::*; + pub use identity_credential::presentation::*; +} + +pub mod did { + pub use identity_did::document::*; + pub use identity_did::error::*; + pub use identity_did::service::*; + pub use identity_did::utils::*; + pub use identity_did::verification::*; + + pub use identity_did::did::did; + pub use identity_did::did::Error as DIDError; + pub use identity_did::did::DID; + + pub use identity_did::resolution; + pub use identity_did::verifiable; +} + +pub mod iota { + pub use identity_iota::chain::*; + pub use identity_iota::client::*; + pub use identity_iota::credential::*; + pub use identity_iota::did::*; + pub use identity_iota::error::*; + pub use identity_iota::tangle::*; + pub use identity_iota::utils::*; +} From c76b2e97a5379448028d363945367e1d5f72d833 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 12:30:07 -0800 Subject: [PATCH 18/28] rust 2018 idioms --- identity-core/Cargo.toml | 1 - identity-core/src/error.rs | 3 --- identity-diff/derive/src/impls/enums.rs | 2 +- identity-diff/derive/src/impls/structs.rs | 6 ++--- identity-diff/src/hashmap.rs | 4 ++-- identity-diff/src/hashset.rs | 4 ++-- identity-diff/src/lib.rs | 12 ++++++++++ identity-diff/src/macros.rs | 2 +- identity-diff/src/option.rs | 2 +- identity-diff/src/string.rs | 2 +- identity-diff/src/vec.rs | 4 ++-- identity-iota/src/client/client.rs | 4 ++-- identity-iota/src/client/txn_printer.rs | 8 +++---- identity-iota/src/did/did.rs | 6 ++--- identity-iota/src/did/document.rs | 6 ++--- identity-iota/src/lib.rs | 20 ++++++++-------- identity-iota/src/tangle/message.rs | 2 +- identity-iota/src/tangle/message_id.rs | 2 +- identity/src/lib.rs | 28 +++++++++++++++++++++++ 19 files changed, 78 insertions(+), 40 deletions(-) diff --git a/identity-core/Cargo.toml b/identity-core/Cargo.toml index 4692fc3777..2116e3a7c5 100644 --- a/identity-core/Cargo.toml +++ b/identity-core/Cargo.toml @@ -11,7 +11,6 @@ keywords = ["iota", "tangle", "identity"] homepage = "https://www.iota.org" [dependencies] -anyhow = { version = "1.0", default-features = false } bs58 = { version = "0.4", default-features = false, features = ["std"] } chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } digest = { version = "0.9", default-features = false } diff --git a/identity-core/src/error.rs b/identity-core/src/error.rs index ab93fb1a86..ad585876fc 100644 --- a/identity-core/src/error.rs +++ b/identity-core/src/error.rs @@ -36,7 +36,4 @@ pub enum Error { /// Caused by attempting to parse an invalid cryptographic key. #[error("Invalid Key Format")] InvalidKeyFormat, - /// Caused by a failure to resolve a DID. - #[error("DID Resolution Error: {0}")] - ResolutionError(anyhow::Error), } diff --git a/identity-diff/derive/src/impls/enums.rs b/identity-diff/derive/src/impls/enums.rs index ad2f721719..631a2a2eb6 100644 --- a/identity-diff/derive/src/impls/enums.rs +++ b/identity-diff/derive/src/impls/enums.rs @@ -143,7 +143,7 @@ pub fn impl_debug_enum(input: &InputModel) -> TokenStream { impl<#(#param_decls),*> std::fmt::Debug for #diff<#params> #clause { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { #body } diff --git a/identity-diff/derive/src/impls/structs.rs b/identity-diff/derive/src/impls/structs.rs index 68c7dfa60c..b50fb122e7 100644 --- a/identity-diff/derive/src/impls/structs.rs +++ b/identity-diff/derive/src/impls/structs.rs @@ -169,7 +169,7 @@ pub fn debug_impl(input: &InputModel) -> TokenStream { for #diff<#params> #clause { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { const NAME: &str = stringify!(diff); let mut #buf = f.debug_struct(NAME); #mac @@ -227,7 +227,7 @@ pub fn debug_impl(input: &InputModel) -> TokenStream { impl<#(#param_decls),*> std::fmt::Debug for #diff<#params> #clause { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { #mac } } @@ -238,7 +238,7 @@ pub fn debug_impl(input: &InputModel) -> TokenStream { impl<#(#param_decls),*> std::fmt::Debug for #diff<#params> #clause { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct(stringify!(#diff)).finish() } diff --git a/identity-diff/src/hashmap.rs b/identity-diff/src/hashmap.rs index d82f105798..ebb3f0ecef 100644 --- a/identity-diff/src/hashmap.rs +++ b/identity-diff/src/hashmap.rs @@ -147,7 +147,7 @@ where K: Debug + Diff, V: Debug + Diff, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!(f, "DiffHashMap")?; let mut buf = f.debug_list(); @@ -178,7 +178,7 @@ where K: Debug + Diff, V: Debug + Diff, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match &self { Self::Change { key, value } => f .debug_struct("Change") diff --git a/identity-diff/src/hashset.rs b/identity-diff/src/hashset.rs index 11cb0b272e..ab305afe33 100644 --- a/identity-diff/src/hashset.rs +++ b/identity-diff/src/hashset.rs @@ -100,7 +100,7 @@ impl Debug for DiffHashSet where T: Debug + Diff, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!(f, "DiffHashSet")?; let mut buf = f.debug_list(); if let Some(d) = &self.0 { @@ -116,7 +116,7 @@ impl Debug for InnerValue where T: Debug + Diff, { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match &self { Self::Add(val) => f.debug_tuple("Add").field(val).finish(), Self::Remove { remove } => f.debug_tuple("Remove").field(remove).finish(), diff --git a/identity-diff/src/lib.rs b/identity-diff/src/lib.rs index 73b3cec590..faf968c725 100644 --- a/identity-diff/src/lib.rs +++ b/identity-diff/src/lib.rs @@ -8,6 +8,18 @@ //! `bool`, and `char` types. Structs and Enums are supported via `identity_derive` and can be composed of any number //! of these types. +#![warn( + rust_2018_idioms, + unreachable_pub, + // missing_docs, + // missing_crate_level_docs, + // broken_intra_doc_links, + // private_intra_doc_links, + // private_doc_tests, + // clippy::missing_safety_doc, + // clippy::missing_errors_doc, +)] + #[doc(hidden)] pub use identity_derive::*; diff --git a/identity-diff/src/macros.rs b/identity-diff/src/macros.rs index dc2b043381..e2966d081d 100644 --- a/identity-diff/src/macros.rs +++ b/identity-diff/src/macros.rs @@ -46,7 +46,7 @@ macro_rules! impl_diff_on_primitives { impl Debug for $diff { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self.0 { None => write!(f, "{} None", stringify!($diff)), Some(val) => write!(f, "{} => {:#?}", stringify!($diff), val), diff --git a/identity-diff/src/option.rs b/identity-diff/src/option.rs index 79bc9a1444..ceb59d6d8a 100644 --- a/identity-diff/src/option.rs +++ b/identity-diff/src/option.rs @@ -63,7 +63,7 @@ where /// Debug implementation for `DiffOption`. impl std::fmt::Debug for DiffOption { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match &self { Self::Some(d) => write!(f, "DiffOption::Some({:#?})", d), Self::None => write!(f, "DiffOption::None"), diff --git a/identity-diff/src/string.rs b/identity-diff/src/string.rs index dc72cdf764..a855447b64 100644 --- a/identity-diff/src/string.rs +++ b/identity-diff/src/string.rs @@ -52,7 +52,7 @@ impl Diff for String { /// Debug trait implementation for DiffString. impl Debug for DiffString { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match &self.0 { Some(val) => write!(f, "DiffString({:#?})", val), None => write!(f, "DiffString None"), diff --git a/identity-diff/src/vec.rs b/identity-diff/src/vec.rs index ab1d741357..4becd29895 100644 --- a/identity-diff/src/vec.rs +++ b/identity-diff/src/vec.rs @@ -105,7 +105,7 @@ where /// Debug trait for `DiffVec` impl Debug for DiffVec { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!(f, "DiffVec ")?; f.debug_list().entries(self.0.iter()).finish() } @@ -113,7 +113,7 @@ impl Debug for DiffVec { /// Debug trait for `InnerVec` impl Debug for InnerVec { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match &self { Self::Change { index, item } => f .debug_struct("Change") diff --git a/identity-iota/src/client/client.rs b/identity-iota/src/client/client.rs index 369a2545d3..2d58accbb2 100644 --- a/identity-iota/src/client/client.rs +++ b/identity-iota/src/client/client.rs @@ -93,7 +93,7 @@ impl Client { /// Returns the web explorer URL of the given `transaction`. pub fn transaction_url(&self, transaction: &BundledTransaction) -> Url { - let hash: TxnPrinter<_> = TxnPrinter::hash(transaction); + let hash: TxnPrinter<'_, _> = TxnPrinter::hash(transaction); let mut url: Url = self.network.explorer_url().clone(); url @@ -245,6 +245,6 @@ fn __dbg_transactions(response: &FindTransactionsResponse) -> Vec { response.hashes.iter().map(|hash| encode_trits(hash)).collect() } -fn __dbg_trytes(response: &GetTrytesResponse) -> Vec { +fn __dbg_trytes(response: &GetTrytesResponse) -> Vec> { response.trytes.iter().map(TxnPrinter::full).collect() } diff --git a/identity-iota/src/client/txn_printer.rs b/identity-iota/src/client/txn_printer.rs index 6fc8c24620..c4c1f77f23 100644 --- a/identity-iota/src/client/txn_printer.rs +++ b/identity-iota/src/client/txn_printer.rs @@ -39,7 +39,7 @@ impl<'a> TxnPrinter<'a, __Hash> { } impl Debug for TxnPrinter<'_, __Full> { - fn fmt(&self, f: &mut Formatter) -> Result { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { f.debug_struct("BundledTransaction") .field("hash", &txn_hash_trytes(self.0)) .field("address", &encode_trits(self.0.address().to_inner())) @@ -57,7 +57,7 @@ impl Debug for TxnPrinter<'_, __Full> { } impl Debug for TxnPrinter<'_, __Mini> { - fn fmt(&self, f: &mut Formatter) -> Result { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { f.debug_struct("BundledTransaction") .field("hash", &txn_hash_trytes(self.0)) .field("address", &encode_trits(self.0.address().to_inner())) @@ -66,13 +66,13 @@ impl Debug for TxnPrinter<'_, __Mini> { } impl Debug for TxnPrinter<'_, __Hash> { - fn fmt(&self, f: &mut Formatter) -> Result { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { write!(f, "{}", txn_hash_trytes(self.0)) } } impl Display for TxnPrinter<'_, __Hash> { - fn fmt(&self, f: &mut Formatter) -> Result { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { write!(f, "{}", txn_hash_trytes(self.0)) } } diff --git a/identity-iota/src/did/did.rs b/identity-iota/src/did/did.rs index f666f2ceb9..7f188de2e5 100644 --- a/identity-iota/src/did/did.rs +++ b/identity-iota/src/did/did.rs @@ -229,7 +229,7 @@ impl IotaDID { } pub(crate) fn normalize(mut did: DID) -> DID { - let segments: Segments = Segments(did.method_id()); + let segments: Segments<'_> = Segments(did.method_id()); if segments.count() == 2 && segments.network() == Self::DEFAULT_NETWORK { let method_id: String = segments.tag().to_string(); @@ -245,13 +245,13 @@ impl IotaDID { } impl Display for IotaDID { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!(f, "{}", self.0) } } impl Debug for IotaDID { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!(f, "{}", self.0) } } diff --git a/identity-iota/src/did/document.rs b/identity-iota/src/did/document.rs index f1c11fcb10..0f4c532ecc 100644 --- a/identity-iota/src/did/document.rs +++ b/identity-iota/src/did/document.rs @@ -132,7 +132,7 @@ impl IotaDocument { } /// Returns the default authentication method of the DID document. - pub fn authentication(&self) -> MethodWrap { + pub fn authentication(&self) -> MethodWrap<'_> { self.document.resolve(AUTH_QUERY).unwrap() } @@ -396,13 +396,13 @@ impl IotaDocument { } impl Display for IotaDocument { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { Display::fmt(&self.document, f) } } impl Debug for IotaDocument { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { Debug::fmt(&self.document, f) } } diff --git a/identity-iota/src/lib.rs b/identity-iota/src/lib.rs index 481a1bc27d..a6ee51686b 100644 --- a/identity-iota/src/lib.rs +++ b/identity-iota/src/lib.rs @@ -1,15 +1,17 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -// #![warn( -// missing_docs, -// missing_crate_level_docs, -// broken_intra_doc_links, -// private_intra_doc_links, -// private_doc_tests, -// clippy::missing_safety_doc, -// clippy::missing_errors_doc, -// )] +#![warn( + rust_2018_idioms, + unreachable_pub, + // missing_docs, + // missing_crate_level_docs, + // broken_intra_doc_links, + // private_intra_doc_links, + // private_doc_tests, + // clippy::missing_safety_doc, + // clippy::missing_errors_doc, +)] #[macro_use] extern crate lazy_static; diff --git a/identity-iota/src/tangle/message.rs b/identity-iota/src/tangle/message.rs index 0ac7077aec..521b6e7916 100644 --- a/identity-iota/src/tangle/message.rs +++ b/identity-iota/src/tangle/message.rs @@ -92,7 +92,7 @@ impl Message { } impl Debug for Message { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { f.debug_struct("Message") .field("address", &self.address) .field("message", &self.message_str()) diff --git a/identity-iota/src/tangle/message_id.rs b/identity-iota/src/tangle/message_id.rs index 484ba9fcbd..a62d9175ca 100644 --- a/identity-iota/src/tangle/message_id.rs +++ b/identity-iota/src/tangle/message_id.rs @@ -39,7 +39,7 @@ impl MessageId { } impl Debug for MessageId { - fn fmt(&self, f: &mut Formatter) -> Result { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { f.write_str(self.0.as_deref().unwrap_or_default()) } } diff --git a/identity/src/lib.rs b/identity/src/lib.rs index d7382f1cee..a7087c961e 100644 --- a/identity/src/lib.rs +++ b/identity/src/lib.rs @@ -1,7 +1,23 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +//! IOTA Identity + +#![warn( + rust_2018_idioms, + unreachable_pub, + missing_docs, + missing_crate_level_docs, + broken_intra_doc_links, + private_intra_doc_links, + private_doc_tests, + clippy::missing_safety_doc, + clippy::missing_errors_doc +)] + pub mod core { + //! Core Traits and Types + pub use identity_core::common::*; pub use identity_core::convert::*; pub use identity_core::error::*; @@ -15,16 +31,26 @@ pub mod core { } pub mod crypto { + //! Cryptographic Utilities + pub use identity_core::crypto::*; } pub mod credential { + //! Verifiable Credentials + //! + //! [Specification](https://www.w3.org/TR/vc-data-model/) + pub use identity_credential::credential::*; pub use identity_credential::error::*; pub use identity_credential::presentation::*; } pub mod did { + //! Decentralized Identifiers + //! + //! [Specification](https://www.w3.org/TR/did-core/) + pub use identity_did::document::*; pub use identity_did::error::*; pub use identity_did::service::*; @@ -40,6 +66,8 @@ pub mod did { } pub mod iota { + //! IOTA Tangle DID Method + pub use identity_iota::chain::*; pub use identity_iota::client::*; pub use identity_iota::credential::*; From 54be10708bd2c248299ddeea11aa8dd73bdc4b6f Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 12:38:33 -0800 Subject: [PATCH 19/28] update deps --- examples/Cargo.toml | 4 ++-- identity-core/Cargo.toml | 4 ++-- identity-iota/Cargo.toml | 2 +- identity-iota/src/did/did.rs | 3 ++- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 504e811308..ff2f6f8f78 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -6,8 +6,8 @@ publish = false [dependencies] identity = { path = "../identity" } -smol = { version = "0.1.18", features = ["tokio02"] } -smol-potat = { version = "0.3" } +smol = { version = "1.2" } +smol-potat = { version = "1.1" } [[example]] name = "credential" diff --git a/identity-core/Cargo.toml b/identity-core/Cargo.toml index 2116e3a7c5..e2e3de093f 100644 --- a/identity-core/Cargo.toml +++ b/identity-core/Cargo.toml @@ -24,5 +24,5 @@ serde_json = { version = "1.0", default-features = false, features = ["preserve_ sha2 = { version = "0.9", default-features = false } subtle = { version = "2.4" } thiserror = { version = "1.0", default-features = false } -url = { version = "2.1", default-features = false, features = ["serde"] } -zeroize = { version = "1.1", default-features = false } +url = { version = "2.2", default-features = false, features = ["serde"] } +zeroize = { version = "1.2", default-features = false } diff --git a/identity-iota/Cargo.toml b/identity-iota/Cargo.toml index 5006307ea1..444a14c629 100644 --- a/identity-iota/Cargo.toml +++ b/identity-iota/Cargo.toml @@ -20,6 +20,6 @@ iota-conversion = { version = "0.5", default-features = false } iota-core = { git = "https://github.com/Thoralf-M/iota.rs", rev = "d7c8c64fc3ac2340f0148708a916c245f42fd454" } lazy_static = { version = "1.4", default-features = false } log = { version = "0.4", default-features = false } -multihash = { version = "0.11", default-features = false } +multihash = { version = "0.13", default-features = false, features = ["blake2b"] } serde = { version = "1.0", default-features = false, features = ["std", "derive"] } thiserror = { version = "1.0", default-features = false } diff --git a/identity-iota/src/did/did.rs b/identity-iota/src/did/did.rs index 7f188de2e5..db3c26aa1a 100644 --- a/identity-iota/src/did/did.rs +++ b/identity-iota/src/did/did.rs @@ -14,6 +14,7 @@ use identity_core::utils::encode_b58; use identity_did::did::Error as DIDError; use identity_did::did::DID; use multihash::Blake2b256; +use multihash::Hasher; use crate::did::Segments; use crate::error::Error; @@ -240,7 +241,7 @@ impl IotaDID { } pub(crate) fn encode_key(key: &[u8]) -> String { - encode_b58(Blake2b256::digest(key).digest()) + encode_b58(&Blake2b256::digest(key)) } } From e56b8498cdfe73ea4b6b3d29aeaec159779612b2 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 13:57:54 -0800 Subject: [PATCH 20/28] Move additional signature traits to core --- identity-core/src/crypto/mod.rs | 3 + identity-core/src/crypto/signature/mod.rs | 3 + identity-core/src/crypto/signature/traits.rs | 83 +++++++++++++++++++ identity-core/src/error.rs | 3 + .../src/credential/verifiable.rs | 6 +- .../src/presentation/verifiable.rs | 6 +- identity-did/src/error.rs | 2 - identity-did/src/verifiable/document.rs | 6 +- identity-did/src/verifiable/ld_suite.rs | 4 +- identity-did/src/verifiable/mod.rs | 3 - identity-did/src/verifiable/properties.rs | 7 +- identity-did/src/verifiable/traits.rs | 64 -------------- identity-iota/src/did/document.rs | 6 +- identity-iota/src/did/document_diff.rs | 6 +- 14 files changed, 112 insertions(+), 90 deletions(-) diff --git a/identity-core/src/crypto/mod.rs b/identity-core/src/crypto/mod.rs index d967860caf..37465228ed 100644 --- a/identity-core/src/crypto/mod.rs +++ b/identity-core/src/crypto/mod.rs @@ -12,6 +12,7 @@ pub use self::key::KeyPair; pub use self::key::PublicKey; pub use self::key::SecretKey; pub use self::proof::JcsEd25519Signature2020; +pub use self::signature::SetSignature; pub use self::signature::SigName; pub use self::signature::SigSign; pub use self::signature::SigVerify; @@ -19,3 +20,5 @@ pub use self::signature::Signature; pub use self::signature::SignatureData; pub use self::signature::SignatureOptions; pub use self::signature::SignatureValue; +pub use self::signature::TrySignature; +pub use self::signature::TrySignatureMut; diff --git a/identity-core/src/crypto/signature/mod.rs b/identity-core/src/crypto/signature/mod.rs index 5386bd391b..5e45d33547 100644 --- a/identity-core/src/crypto/signature/mod.rs +++ b/identity-core/src/crypto/signature/mod.rs @@ -13,6 +13,9 @@ pub use self::signature::Signature; pub use self::signature_data::SignatureData; pub use self::signature_options::SignatureOptions; pub use self::signature_value::SignatureValue; +pub use self::traits::SetSignature; pub use self::traits::SigName; pub use self::traits::SigSign; pub use self::traits::SigVerify; +pub use self::traits::TrySignature; +pub use self::traits::TrySignatureMut; diff --git a/identity-core/src/crypto/signature/traits.rs b/identity-core/src/crypto/signature/traits.rs index 6187bff2f6..8c3f00fde5 100644 --- a/identity-core/src/crypto/signature/traits.rs +++ b/identity-core/src/crypto/signature/traits.rs @@ -3,7 +3,9 @@ use serde::Serialize; +use crate::crypto::Signature; use crate::crypto::SignatureData; +use crate::error::Error; use crate::error::Result; /// A trait for signature suites identified by a particular name. @@ -66,3 +68,84 @@ where (**self).verify(data, signature, public) } } + +// ============================================================================= +// ============================================================================= + +/// A trait for types that can provide a reference to a [`Signature`]. +pub trait TrySignature { + /// Returns a reference to the [`Signature`] object, if any. + fn signature(&self) -> Option<&Signature>; + + /// Returns a reference to the [`Signature`] object. + /// + /// Errors + /// + /// Fails if the signature is not found. + fn try_signature(&self) -> Result<&Signature> { + self.signature().ok_or(Error::MissingSignature) + } +} + +impl<'a, T> TrySignature for &'a T +where + T: TrySignature, +{ + fn signature(&self) -> Option<&Signature> { + (**self).signature() + } +} + +impl<'a, T> TrySignature for &'a mut T +where + T: TrySignature, +{ + fn signature(&self) -> Option<&Signature> { + (**self).signature() + } +} + +// ============================================================================= +// ============================================================================= + +/// A trait for types that can provide a mutable reference to a [`Signature`]. +pub trait TrySignatureMut: TrySignature { + /// Returns a mutable reference to the [`Signature`] object. + fn signature_mut(&mut self) -> Option<&mut Signature>; + + /// Returns a mutable reference to the [`Signature`] object. + /// + /// Errors + /// + /// Fails if the signature is not found. + fn try_signature_mut(&mut self) -> Result<&mut Signature> { + self.signature_mut().ok_or(Error::MissingSignature) + } +} + +impl<'a, T> TrySignatureMut for &'a mut T +where + T: TrySignatureMut, +{ + fn signature_mut(&mut self) -> Option<&mut Signature> { + (**self).signature_mut() + } +} + +// ============================================================================= +// ============================================================================= + +/// A trait for types that can store a digital [signature][`Signature`]. +pub trait SetSignature: TrySignatureMut { + /// Sets the [`Signature`] object of `self`. + fn set_signature(&mut self, signature: Signature); +} + +impl<'a, T> SetSignature for &'a mut T +where + T: SetSignature, +{ + fn set_signature(&mut self, signature: Signature) { + (**self).set_signature(signature); + } +} diff --git a/identity-core/src/error.rs b/identity-core/src/error.rs index ad585876fc..04639dc34d 100644 --- a/identity-core/src/error.rs +++ b/identity-core/src/error.rs @@ -36,4 +36,7 @@ pub enum Error { /// Caused by attempting to parse an invalid cryptographic key. #[error("Invalid Key Format")] InvalidKeyFormat, + /// Caused by a failed attempt at retrieving a digital signature. + #[error("Signature Not Found")] + MissingSignature, } diff --git a/identity-credential/src/credential/verifiable.rs b/identity-credential/src/credential/verifiable.rs index 196a3d695e..ebd87f9ad9 100644 --- a/identity-credential/src/credential/verifiable.rs +++ b/identity-credential/src/credential/verifiable.rs @@ -10,10 +10,10 @@ use core::ops::DerefMut; use identity_core::common::Object; use identity_core::common::OneOrMany; use identity_core::convert::ToJson; +use identity_core::crypto::SetSignature; use identity_core::crypto::Signature; -use identity_did::verifiable::SetSignature; -use identity_did::verifiable::TrySignature; -use identity_did::verifiable::TrySignatureMut; +use identity_core::crypto::TrySignature; +use identity_core::crypto::TrySignatureMut; use serde::Serialize; use crate::credential::Credential; diff --git a/identity-credential/src/presentation/verifiable.rs b/identity-credential/src/presentation/verifiable.rs index fb69156c6a..2b40cd5e35 100644 --- a/identity-credential/src/presentation/verifiable.rs +++ b/identity-credential/src/presentation/verifiable.rs @@ -10,10 +10,10 @@ use core::ops::DerefMut; use identity_core::common::Object; use identity_core::common::OneOrMany; use identity_core::convert::ToJson; +use identity_core::crypto::SetSignature; use identity_core::crypto::Signature; -use identity_did::verifiable::SetSignature; -use identity_did::verifiable::TrySignature; -use identity_did::verifiable::TrySignatureMut; +use identity_core::crypto::TrySignature; +use identity_core::crypto::TrySignatureMut; use serde::Serialize; use crate::presentation::Presentation; diff --git a/identity-did/src/error.rs b/identity-did/src/error.rs index ce659c47bf..0feada5038 100644 --- a/identity-did/src/error.rs +++ b/identity-did/src/error.rs @@ -20,8 +20,6 @@ pub enum Error { OrderedSetDuplicate, #[error("Verification Method Not Found")] QueryMethodNotFound, - #[error("Signature Not Found")] - QuerySignatureNotFound, #[error("Invalid Document Property: `id`")] InvalidDocumentId, diff --git a/identity-did/src/verifiable/document.rs b/identity-did/src/verifiable/document.rs index 8c8c664acc..44ccbb436d 100644 --- a/identity-did/src/verifiable/document.rs +++ b/identity-did/src/verifiable/document.rs @@ -7,15 +7,15 @@ use core::fmt::Formatter; use core::fmt::Result as FmtResult; use core::ops::Deref; use core::ops::DerefMut; +use identity_core::crypto::SetSignature; use identity_core::crypto::Signature; +use identity_core::crypto::TrySignature; +use identity_core::crypto::TrySignatureMut; use serde::Serialize; use crate::document::Document as Document_; use crate::verifiable::Properties; use crate::verifiable::ResolveMethod; -use crate::verifiable::SetSignature; -use crate::verifiable::TrySignature; -use crate::verifiable::TrySignatureMut; use crate::verification::MethodQuery; use crate::verification::MethodWrap; diff --git a/identity-did/src/verifiable/ld_suite.rs b/identity-did/src/verifiable/ld_suite.rs index b5b42438d0..6310c03355 100644 --- a/identity-did/src/verifiable/ld_suite.rs +++ b/identity-did/src/verifiable/ld_suite.rs @@ -2,19 +2,19 @@ // SPDX-License-Identifier: Apache-2.0 use core::convert::TryInto; +use identity_core::crypto::SetSignature; use identity_core::crypto::SigName; use identity_core::crypto::SigSign; use identity_core::crypto::SigVerify; use identity_core::crypto::Signature; use identity_core::crypto::SignatureData; use identity_core::crypto::SignatureOptions; +use identity_core::crypto::TrySignature; use serde::Serialize; use crate::error::Error; use crate::error::Result; use crate::verifiable::ResolveMethod; -use crate::verifiable::SetSignature; -use crate::verifiable::TrySignature; use crate::verification::MethodQuery; use crate::verification::MethodType; use crate::verification::MethodWrap; diff --git a/identity-did/src/verifiable/mod.rs b/identity-did/src/verifiable/mod.rs index a0d4678d57..3f6434aca2 100644 --- a/identity-did/src/verifiable/mod.rs +++ b/identity-did/src/verifiable/mod.rs @@ -10,6 +10,3 @@ pub use self::document::Document; pub use self::ld_suite::LdSuite; pub use self::properties::Properties; pub use self::traits::ResolveMethod; -pub use self::traits::SetSignature; -pub use self::traits::TrySignature; -pub use self::traits::TrySignatureMut; diff --git a/identity-did/src/verifiable/properties.rs b/identity-did/src/verifiable/properties.rs index 4f9a21b0dc..7b271a9293 100644 --- a/identity-did/src/verifiable/properties.rs +++ b/identity-did/src/verifiable/properties.rs @@ -3,11 +3,10 @@ use core::ops::Deref; use core::ops::DerefMut; +use identity_core::crypto::SetSignature; use identity_core::crypto::Signature; - -use crate::verifiable::SetSignature; -use crate::verifiable::TrySignature; -use crate::verifiable::TrySignatureMut; +use identity_core::crypto::TrySignature; +use identity_core::crypto::TrySignatureMut; #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] pub struct Properties { diff --git a/identity-did/src/verifiable/traits.rs b/identity-did/src/verifiable/traits.rs index 7315e11748..17b9477d0c 100644 --- a/identity-did/src/verifiable/traits.rs +++ b/identity-did/src/verifiable/traits.rs @@ -1,75 +1,11 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use identity_core::crypto::Signature; - use crate::error::Error; use crate::error::Result; use crate::verification::MethodQuery; use crate::verification::MethodWrap; -pub trait TrySignature { - fn signature(&self) -> Option<&Signature>; - - fn try_signature(&self) -> Result<&Signature> { - self.signature().ok_or(Error::QuerySignatureNotFound) - } -} - -impl<'a, T> TrySignature for &'a T -where - T: TrySignature, -{ - fn signature(&self) -> Option<&Signature> { - (**self).signature() - } -} - -impl<'a, T> TrySignature for &'a mut T -where - T: TrySignature, -{ - fn signature(&self) -> Option<&Signature> { - (**self).signature() - } -} - -// ============================================================================= -// ============================================================================= - -pub trait TrySignatureMut: TrySignature { - fn signature_mut(&mut self) -> Option<&mut Signature>; - - fn try_signature_mut(&mut self) -> Result<&mut Signature> { - self.signature_mut().ok_or(Error::QuerySignatureNotFound) - } -} - -impl<'a, T> TrySignatureMut for &'a mut T -where - T: TrySignatureMut, -{ - fn signature_mut(&mut self) -> Option<&mut Signature> { - (**self).signature_mut() - } -} - -// ============================================================================= -// ============================================================================= - -pub trait SetSignature: TrySignatureMut { - fn set_signature(&mut self, signature: Signature); -} - -impl<'a, T> SetSignature for &'a mut T -where - T: SetSignature, -{ - fn set_signature(&mut self, signature: Signature) { - (**self).set_signature(signature); - } -} - // ============================================================================= // ============================================================================= diff --git a/identity-iota/src/did/document.rs b/identity-iota/src/did/document.rs index 0f4c532ecc..a1e37cf354 100644 --- a/identity-iota/src/did/document.rs +++ b/identity-iota/src/did/document.rs @@ -15,16 +15,16 @@ use identity_core::convert::SerdeInto; use identity_core::crypto::JcsEd25519Signature2020; use identity_core::crypto::KeyPair; use identity_core::crypto::SecretKey; +use identity_core::crypto::SetSignature; use identity_core::crypto::Signature; use identity_core::crypto::SignatureOptions; +use identity_core::crypto::TrySignature; +use identity_core::crypto::TrySignatureMut; use identity_did::did::DID; use identity_did::document::Document; use identity_did::verifiable::Document as VerifiableDocument; use identity_did::verifiable::LdSuite; use identity_did::verifiable::ResolveMethod; -use identity_did::verifiable::SetSignature; -use identity_did::verifiable::TrySignature; -use identity_did::verifiable::TrySignatureMut; use identity_did::verification::MethodQuery; use identity_did::verification::MethodScope; use identity_did::verification::MethodType; diff --git a/identity-iota/src/did/document_diff.rs b/identity-iota/src/did/document_diff.rs index 860d028b08..0b6b6c3b7e 100644 --- a/identity-iota/src/did/document_diff.rs +++ b/identity-iota/src/did/document_diff.rs @@ -3,13 +3,13 @@ use identity_core::convert::AsJson; use identity_core::convert::SerdeInto; +use identity_core::crypto::SetSignature; use identity_core::crypto::Signature; +use identity_core::crypto::TrySignature; +use identity_core::crypto::TrySignatureMut; use identity_core::diff::Diff; use identity_did::diff::DiffDocument; use identity_did::document::Document; -use identity_did::verifiable::SetSignature; -use identity_did::verifiable::TrySignature; -use identity_did::verifiable::TrySignatureMut; use crate::client::Client; use crate::client::Network; From b953a6d7d4d270d13c5e61633008a86be33c9090 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 13:58:28 -0800 Subject: [PATCH 21/28] add features --- identity/Cargo.toml | 24 +++++++++++++++++++++--- identity/src/lib.rs | 7 +++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/identity/Cargo.toml b/identity/Cargo.toml index a85dec0c8f..9d3884fbdd 100644 --- a/identity/Cargo.toml +++ b/identity/Cargo.toml @@ -12,6 +12,24 @@ homepage = "https://www.iota.org" [dependencies] identity-core = { version = "=0.1.0", path = "../identity-core" } -identity-credential = { version = "=0.1.0", path = "../identity-credential" } -identity-did = { version = "=0.1.0", path = "../identity-did" } -identity-iota = { version = "=0.1.0", path = "../identity-iota" } +identity-credential = { version = "=0.1.0", path = "../identity-credential", optional = true } +identity-did = { version = "=0.1.0", path = "../identity-did", optional = true } +identity-iota = { version = "=0.1.0", path = "../identity-iota", optional = true } + +[features] +default = ["identifier", "credential", "iota"] + +# Enables support for Decentralized Identifiers +identifier = ["identity-did"] + +# Enables support for Verifiable Credentials/Presentations +credential = ["identifier", "identity-credential"] + +# Enables support for reading/writing from the IOTA Tangle +iota = ["identifier", "credential", "identity-iota"] + +[package.metadata.docs.rs] +# To build locally: +# RUSTDOCFLAGS="--cfg doc_cfg" cargo +nightly doc --all-features --no-deps --open +all-features = true +rustdoc-args = ["--cfg", "doc_cfg"] diff --git a/identity/src/lib.rs b/identity/src/lib.rs index a7087c961e..04baf7f517 100644 --- a/identity/src/lib.rs +++ b/identity/src/lib.rs @@ -14,6 +14,7 @@ clippy::missing_safety_doc, clippy::missing_errors_doc )] +#![cfg_attr(doc_cfg, feature(doc_cfg))] pub mod core { //! Core Traits and Types @@ -36,6 +37,8 @@ pub mod crypto { pub use identity_core::crypto::*; } +#[cfg(feature = "credential")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "credential")))] pub mod credential { //! Verifiable Credentials //! @@ -46,6 +49,8 @@ pub mod credential { pub use identity_credential::presentation::*; } +#[cfg(feature = "identifier")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "identifier")))] pub mod did { //! Decentralized Identifiers //! @@ -65,6 +70,8 @@ pub mod did { pub use identity_did::verifiable; } +#[cfg(feature = "iota")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "iota")))] pub mod iota { //! IOTA Tangle DID Method From 936a99c0ecffc11ea67e20f106e62d1a9d705a42 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 14:36:55 -0800 Subject: [PATCH 22/28] Remove VerifiableDocument type --- identity-did/src/verifiable/document.rs | 83 +++-------------------- identity-did/src/verifiable/ld_suite.rs | 2 +- identity-did/src/verifiable/mod.rs | 1 - identity-iota/src/did/document.rs | 37 ++++------ identity-iota/src/did/document_builder.rs | 4 +- 5 files changed, 26 insertions(+), 101 deletions(-) diff --git a/identity-did/src/verifiable/document.rs b/identity-did/src/verifiable/document.rs index 44ccbb436d..32cbba59cd 100644 --- a/identity-did/src/verifiable/document.rs +++ b/identity-did/src/verifiable/document.rs @@ -1,44 +1,25 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use core::fmt::Debug; -use core::fmt::Display; -use core::fmt::Formatter; -use core::fmt::Result as FmtResult; -use core::ops::Deref; -use core::ops::DerefMut; use identity_core::crypto::SetSignature; use identity_core::crypto::Signature; use identity_core::crypto::TrySignature; use identity_core::crypto::TrySignatureMut; -use serde::Serialize; -use crate::document::Document as Document_; +use crate::document::Document; use crate::verifiable::Properties; -use crate::verifiable::ResolveMethod; -use crate::verification::MethodQuery; -use crate::verification::MethodWrap; - -#[derive(Clone, PartialEq, Deserialize, Serialize)] -#[repr(transparent)] -#[serde(transparent)] -pub struct Document { - document: Document_, U, V>, -} impl Document { - pub fn new(document: Document_) -> Self { - Self { - document: document.map(Properties::new), - } + pub fn into_verifiable(self) -> Document, U, V> { + self.map(Properties::new) } - pub fn with_proof(document: Document_, proof: Signature) -> Self { - Self { - document: document.map(|old| Properties::with_proof(old, proof)), - } + pub fn into_verifiable2(self, proof: Signature) -> Document, U, V> { + self.map(|old| Properties::with_proof(old, proof)) } +} +impl Document, U, V> { pub fn proof(&self) -> Option<&Signature> { self.properties().proof() } @@ -48,66 +29,24 @@ impl Document { } pub fn set_proof(&mut self, signature: Signature) { - self.properties_mut().proof = Some(signature); - } -} - -impl Deref for Document { - type Target = Document_, U, V>; - - fn deref(&self) -> &Self::Target { - &self.document - } -} - -impl DerefMut for Document { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.document + self.properties_mut().set_proof(signature); } } -impl Debug for Document -where - T: Debug, - U: Debug, - V: Debug, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - Debug::fmt(&self.document, f) - } -} - -impl Display for Document -where - T: Serialize, - U: Serialize, - V: Serialize, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - Display::fmt(&self.document, f) - } -} - -impl TrySignature for Document { +impl TrySignature for Document, U, V> { fn signature(&self) -> Option<&Signature> { self.proof() } } -impl TrySignatureMut for Document { +impl TrySignatureMut for Document, U, V> { fn signature_mut(&mut self) -> Option<&mut Signature> { self.proof_mut() } } -impl SetSignature for Document { +impl SetSignature for Document, U, V> { fn set_signature(&mut self, signature: Signature) { self.set_proof(signature) } } - -impl ResolveMethod for Document { - fn resolve_method(&self, query: MethodQuery<'_>) -> Option> { - self.document.resolve(query) - } -} diff --git a/identity-did/src/verifiable/ld_suite.rs b/identity-did/src/verifiable/ld_suite.rs index 6310c03355..40ebf141f3 100644 --- a/identity-did/src/verifiable/ld_suite.rs +++ b/identity-did/src/verifiable/ld_suite.rs @@ -81,7 +81,7 @@ where let query: MethodQuery<'_> = signature.try_into()?; let method: MethodWrap<'_, M> = resolver.try_resolve_method(query)?; - if !self.methods.is_empty() && !self.methods.contains(&method.key_type()) { + if !self.methods.is_empty() || !self.methods.contains(&method.key_type()) { return Err(Error::UnknownMethodType); } diff --git a/identity-did/src/verifiable/mod.rs b/identity-did/src/verifiable/mod.rs index 3f6434aca2..13cf7cc8fd 100644 --- a/identity-did/src/verifiable/mod.rs +++ b/identity-did/src/verifiable/mod.rs @@ -6,7 +6,6 @@ mod ld_suite; mod properties; mod traits; -pub use self::document::Document; pub use self::ld_suite::LdSuite; pub use self::properties::Properties; pub use self::traits::ResolveMethod; diff --git a/identity-iota/src/did/document.rs b/identity-iota/src/did/document.rs index a1e37cf354..b7f0e55f80 100644 --- a/identity-iota/src/did/document.rs +++ b/identity-iota/src/did/document.rs @@ -9,8 +9,6 @@ use core::fmt::Result as FmtResult; use core::ops::Deref; use identity_core::common::Object; use identity_core::common::Timestamp; -use identity_core::common::Value; -use identity_core::convert::FromJson; use identity_core::convert::SerdeInto; use identity_core::crypto::JcsEd25519Signature2020; use identity_core::crypto::KeyPair; @@ -22,8 +20,8 @@ use identity_core::crypto::TrySignature; use identity_core::crypto::TrySignatureMut; use identity_did::did::DID; use identity_did::document::Document; -use identity_did::verifiable::Document as VerifiableDocument; use identity_did::verifiable::LdSuite; +use identity_did::verifiable::Properties as VerifiableProperties; use identity_did::verifiable::ResolveMethod; use identity_did::verification::MethodQuery; use identity_did::verification::MethodScope; @@ -36,7 +34,7 @@ use crate::client::Network; use crate::did::DocumentDiff; use crate::did::IotaDID; use crate::did::IotaDocumentBuilder; -use crate::did::Properties; +use crate::did::Properties as BaseProperties; use crate::error::Error; use crate::error::Result; use crate::tangle::MessageId; @@ -49,7 +47,8 @@ const ERR_AMNS: &str = "Authentication Method Not Supported"; const ERR_AMMF: &str = "Authentication Method Missing Fragment"; const ERR_AMIM: &str = "Authentication Method Id Mismatch"; -type __Document = VerifiableDocument; +type Properties = VerifiableProperties; +type __Document = Document; #[derive(Clone, PartialEq, Deserialize, Serialize)] #[serde(try_from = "Document", into = "__Document")] @@ -84,7 +83,7 @@ impl IotaDocument { /// # Errors /// /// Returns `Err` if the document is not a valid `IotaDocument`. - pub fn try_from_document(mut document: Document) -> Result { + pub fn try_from_document(document: Document) -> Result { let did: &IotaDID = IotaDID::try_from_borrowed(document.id())?; let key: &DID = document.try_resolve(AUTH_QUERY)?.into_method().id(); @@ -98,22 +97,10 @@ impl IotaDocument { return Err(Error::InvalidDocument { error: ERR_AMIM }); } - let proof: Option = document.properties_mut().remove("proof"); - let root: Document = document.try_map(|old| old.serde_into())?; - - if let Some(proof) = proof { - let proof: Signature = Signature::from_json_value(proof)?; - - Ok(Self { - document: VerifiableDocument::with_proof(root, proof), - message_id: MessageId::NONE, - }) - } else { - Ok(Self { - document: VerifiableDocument::new(root), - message_id: MessageId::NONE, - }) - } + Ok(Self { + document: document.serde_into()?, + message_id: MessageId::NONE, + }) } /// Creates a `IotaDocumentBuilder` to configure a new `IotaDocument`. @@ -209,17 +196,17 @@ impl IotaDocument { &mut self.document.properties_mut().properties } - /// Returns a reference to the `VerifiableDocument`. + /// Returns a reference to the underlying `Document`. pub fn as_document(&self) -> &__Document { &self.document } - /// Returns a mutable reference to the `VerifiableDocument`. + /// Returns a mutable reference to the underlying `Document`. /// /// # Safety /// /// This function is unsafe because it does not check that modifications - /// made to the `VerifiableDocument` maintain a valid `IotaDocument`. + /// made to the `Document` maintain a valid `IotaDocument`. /// /// If this constraint is violated, it may cause issues with future uses of /// the `IotaDocument`. diff --git a/identity-iota/src/did/document_builder.rs b/identity-iota/src/did/document_builder.rs index 9e71cbe14b..d406485725 100644 --- a/identity-iota/src/did/document_builder.rs +++ b/identity-iota/src/did/document_builder.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use identity_core::crypto::KeyPair; +use identity_did::document::Document; use identity_did::document::DocumentBuilder; -use identity_did::verifiable::Document as VerifiableDocument; use identity_did::verification::Method; use identity_did::verification::MethodBuilder; use identity_did::verification::MethodData; @@ -99,7 +99,7 @@ impl IotaDocumentBuilder { .id(did.into()) .authentication(method) .build() - .map(VerifiableDocument::new) + .map(Document::into_verifiable) .map(Into::into)?; Ok((document, keypair)) From 1e0e100a4342c961bceb11e36150d006e269b697 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 14:52:08 -0800 Subject: [PATCH 23/28] Restore JcsEd25519 tests --- .../crypto/proof/jcsed25519signature2020.rs | 133 +----------------- identity-did/src/verifiable/ld_suite.rs | 2 +- identity-did/tests/fixtures/jcs_ed25519.rs | 51 +++++++ .../tests/sign_verify_document_ed25519.rs | 85 +++++++++++ 4 files changed, 143 insertions(+), 128 deletions(-) create mode 100644 identity-did/tests/fixtures/jcs_ed25519.rs create mode 100644 identity-did/tests/sign_verify_document_ed25519.rs diff --git a/identity-core/src/crypto/proof/jcsed25519signature2020.rs b/identity-core/src/crypto/proof/jcsed25519signature2020.rs index 6eb66af0ac..04ac6dfb9e 100644 --- a/identity-core/src/crypto/proof/jcsed25519signature2020.rs +++ b/identity-core/src/crypto/proof/jcsed25519signature2020.rs @@ -144,72 +144,19 @@ pub(crate) fn ed25519_verify(message: &[u8], signature: &[u8], public: &[u8]) -> #[cfg(test)] mod tests { - // const UNSIGNED: &str = r##" - // { - // "id": "did:example:123", - // "verificationMethod": [ - // { - // "id": "did:example:123#key-1", - // "type": "JcsEd25519Key2020", - // "controller": "did:example:123", - // "publicKeyBase58": "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c" - // } - // ], - // "service": [ - // { - // "id": "did:schema:id", - // "type": "schema", - // "serviceEndpoint": "https://example.com" - // } - // ] - // } - // "##; - - // const SIGNED: &str = r##" - // { - // "id": "did:example:123", - // "verificationMethod": [ - // { - // "id": "did:example:123#key-1", - // "type": "JcsEd25519Key2020", - // "controller": "did:example:123", - // "publicKeyBase58": "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c" - // } - // ], - // "service": [ - // { - // "id": "did:schema:id", - // "type": "schema", - // "serviceEndpoint": "https://example.com" - // } - // ], - // "proof": { - // "verificationMethod": "#key-1", - // "type": "JcsEd25519Signature2020", - // "signatureValue": "piKnvB438vWsinW1dqq2EYRzcYFuR7Qm9X8t2S6TPPLDokLwcFBXnnERk6jmS8RXKTJnXKWw1Q9oNhYTwbR7vJkaJT8ZGgwDHNxa6mrMNsQsWkM4rg6EYY99xQko7FnpAMn" - // } - // } - // "##; - - // use identity_did::signature::LdSuite; - // use identity_did::signature::VerifiableDocument; - use super::ed25519_sign; use super::ed25519_verify; - // use crate::convert::FromJson; - // use crate::crypto::JcsEd25519Signature2020; - // use crate::crypto::SignatureData; - // use crate::crypto::SignatureOptions; use crate::utils::decode_b58; + const SIGNATURE_HELLO: &[u8] = &[ + 12, 203, 235, 144, 80, 6, 163, 39, 181, 17, 44, 123, 250, 162, 165, 145, 135, 132, 32, 152, 24, 168, 55, 80, 84, + 139, 153, 101, 102, 27, 157, 29, 70, 124, 64, 120, 250, 172, 186, 163, 108, 27, 208, 248, 134, 115, 3, 154, 222, + 165, 31, 93, 33, 108, 212, 92, 191, 14, 21, 40, 251, 103, 241, 10, 104, 101, 108, 108, 111, + ]; + const PUBLIC_B58: &str = "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c"; const SECRET_B58: &str = "3qsrFcQqVuPpuGrRkU4wkQRvw1tc1C5EmEDPioS1GzQ2pLoThy5TYS2BsrwuzHYDnVqcYhMSpDhTXGst6H5ttFkG"; - #[rustfmt::skip] - const SIGNATURE_HELLO: &[u8] = &[12, 203, 235, 144, 80, 6, 163, 39, 181, 17, 44, 123, 250, 162, 165, 145, 135, 132, 32, 152, 24, 168, 55, 80, 84, 139, 153, 101, 102, 27, 157, 29, 70, 124, 64, 120, 250, 172, 186, 163, 108, 27, 208, 248, 134, 115, 3, 154, 222, 165, 31, 93, 33, 108, 212, 92, 191, 14, 21, 40, 251, 103, 241, 10, 104, 101, 108, 108, 111]; - - // const SIGNATURE_DOCUMENT: &str = "piKnvB438vWsinW1dqq2EYRzcYFuR7Qm9X8t2S6TPPLDokLwcFBXnnERk6jmS8RXKTJnXKWw1Q9oNhYTwbR7vJkaJT8ZGgwDHNxa6mrMNsQsWkM4rg6EYY99xQko7FnpAMn"; - #[test] fn test_ed25519_can_sign_and_verify() { let public: Vec = decode_b58(PUBLIC_B58).unwrap(); @@ -221,72 +168,4 @@ mod tests { let verified: _ = ed25519_verify(b"hello", &signature, &public); assert!(verified.is_ok()); } - - // #[test] - // fn test_jcsed25519signature2020_can_sign_and_verify() { - // let secret = decode_b58(SECRET_B58).unwrap(); - // let mut unsigned: VerifiableDocument = VerifiableDocument::from_json(UNSIGNED).unwrap(); - // let signed: VerifiableDocument = VerifiableDocument::from_json(SIGNED).unwrap(); - // let method = unsigned.try_resolve("#key-1").unwrap(); - // let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); - // let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); - - // suite.sign(&mut unsigned, options, &secret).unwrap(); - - // assert!(suite.verify(&unsigned).is_ok()); - // assert_eq!( - // unsigned.properties().proof().unwrap().data().as_str(), - // SIGNATURE_DOCUMENT - // ); - - // assert_eq!( - // serde_jcs::to_vec(&unsigned).unwrap(), - // serde_jcs::to_vec(&signed).unwrap() - // ); - // } - - // #[test] - // fn test_jcsed25519signature2020_fails_when_key_is_mutated() { - // let secret = decode_b58(SECRET_B58).unwrap(); - // let mut document: VerifiableDocument = VerifiableDocument::from_json(UNSIGNED).unwrap(); - // let method = document.try_resolve("#key-1").unwrap(); - // let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); - // let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); - - // suite.sign(&mut document, options, &secret).unwrap(); - - // assert!(suite.verify(&document).is_ok()); - // assert_eq!( - // document.properties().proof().unwrap().data().as_str(), - // SIGNATURE_DOCUMENT - // ); - - // document.proof_mut().unwrap().verification_method = "#key-2".into(); - - // assert!(suite.verify(&document).is_err()); - // } - - // #[test] - // fn test_jcsed25519signature2020_fails_when_signature_is_mutated() { - // let secret = decode_b58(SECRET_B58).unwrap(); - // let mut document: VerifiableDocument = VerifiableDocument::from_json(UNSIGNED).unwrap(); - // let method = document.try_resolve("#key-1").unwrap(); - // let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); - // let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); - - // suite.sign(&mut document, options, &secret).unwrap(); - - // assert!(suite.verify(&document).is_ok()); - // assert_eq!( - // document.properties().proof().unwrap().data().as_str(), - // SIGNATURE_DOCUMENT - // ); - - // document - // .proof_mut() - // .unwrap() - // .set_data(SignatureData::Signature("foo".into())); - - // assert!(suite.verify(&document).is_err()); - // } } diff --git a/identity-did/src/verifiable/ld_suite.rs b/identity-did/src/verifiable/ld_suite.rs index 40ebf141f3..6310c03355 100644 --- a/identity-did/src/verifiable/ld_suite.rs +++ b/identity-did/src/verifiable/ld_suite.rs @@ -81,7 +81,7 @@ where let query: MethodQuery<'_> = signature.try_into()?; let method: MethodWrap<'_, M> = resolver.try_resolve_method(query)?; - if !self.methods.is_empty() || !self.methods.contains(&method.key_type()) { + if !self.methods.is_empty() && !self.methods.contains(&method.key_type()) { return Err(Error::UnknownMethodType); } diff --git a/identity-did/tests/fixtures/jcs_ed25519.rs b/identity-did/tests/fixtures/jcs_ed25519.rs new file mode 100644 index 0000000000..6ba6d7ab29 --- /dev/null +++ b/identity-did/tests/fixtures/jcs_ed25519.rs @@ -0,0 +1,51 @@ +[ + TestVector { + document_unsigned: r##" + { + "id": "did:example:123", + "verificationMethod": [ + { + "id": "did:example:123#key-1", + "type": "JcsEd25519Key2020", + "controller": "did:example:123", + "publicKeyBase58": "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c" + } + ], + "service": [ + { + "id": "did:schema:id", + "type": "schema", + "serviceEndpoint": "https://example.com" + } + ] + } + "##, + document_signed: r##" + { + "id": "did:example:123", + "verificationMethod": [ + { + "id": "did:example:123#key-1", + "type": "JcsEd25519Key2020", + "controller": "did:example:123", + "publicKeyBase58": "6b23ioXQSAayuw13PGFMCAKqjgqoLTpeXWCy5WRfw28c" + } + ], + "service": [ + { + "id": "did:schema:id", + "type": "schema", + "serviceEndpoint": "https://example.com" + } + ], + "proof": { + "verificationMethod": "#key-1", + "type": "JcsEd25519Signature2020", + "signatureValue": "piKnvB438vWsinW1dqq2EYRzcYFuR7Qm9X8t2S6TPPLDokLwcFBXnnERk6jmS8RXKTJnXKWw1Q9oNhYTwbR7vJkaJT8ZGgwDHNxa6mrMNsQsWkM4rg6EYY99xQko7FnpAMn" + } + } + "##, + secret_b58: "3qsrFcQqVuPpuGrRkU4wkQRvw1tc1C5EmEDPioS1GzQ2pLoThy5TYS2BsrwuzHYDnVqcYhMSpDhTXGst6H5ttFkG", + signature: "piKnvB438vWsinW1dqq2EYRzcYFuR7Qm9X8t2S6TPPLDokLwcFBXnnERk6jmS8RXKTJnXKWw1Q9oNhYTwbR7vJkaJT8ZGgwDHNxa6mrMNsQsWkM4rg6EYY99xQko7FnpAMn", + } +] diff --git a/identity-did/tests/sign_verify_document_ed25519.rs b/identity-did/tests/sign_verify_document_ed25519.rs new file mode 100644 index 0000000000..4ca688aa81 --- /dev/null +++ b/identity-did/tests/sign_verify_document_ed25519.rs @@ -0,0 +1,85 @@ +// Copyright 2020-2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Object; +use identity_core::convert::FromJson; +use identity_core::convert::ToJson; +use identity_core::crypto::JcsEd25519Signature2020; +use identity_core::crypto::SignatureData; +use identity_core::crypto::SignatureOptions; +use identity_core::utils::decode_b58; +use identity_did::document; +use identity_did::verifiable::LdSuite; +use identity_did::verifiable::Properties; + +struct TestVector { + document_unsigned: &'static str, + document_signed: &'static str, + secret_b58: &'static str, + signature: &'static str, +} + +const TVS: &'static [TestVector] = &include!("fixtures/jcs_ed25519.rs"); + +type Document = document::Document, (), ()>; + +#[test] +fn test_jcs_ed25519_can_sign_and_verify() { + for tv in TVS { + let secret = decode_b58(tv.secret_b58).unwrap(); + let mut unsigned: Document = Document::from_json(tv.document_unsigned).unwrap(); + let signed: Document = Document::from_json(tv.document_signed).unwrap(); + let method = unsigned.try_resolve("#key-1").unwrap(); + let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); + let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); + + suite.sign(&mut unsigned, options, &secret).unwrap(); + + assert!(suite.verify(&unsigned).is_ok()); + assert_eq!(unsigned.proof().unwrap().data().as_str(), tv.signature); + assert_eq!(unsigned.to_jcs().unwrap(), signed.to_jcs().unwrap()); + } +} + +#[test] +fn test_jcs_ed25519_fails_when_key_is_mutated() { + for tv in TVS { + let secret = decode_b58(tv.secret_b58).unwrap(); + let mut document: Document = Document::from_json(tv.document_unsigned).unwrap(); + let method = document.try_resolve("#key-1").unwrap(); + let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); + let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); + + suite.sign(&mut document, options, &secret).unwrap(); + + assert!(suite.verify(&document).is_ok()); + assert_eq!(document.proof().unwrap().data().as_str(), tv.signature); + + document.proof_mut().unwrap().verification_method = "#key-2".into(); + + assert!(suite.verify(&document).is_err()); + } +} + +#[test] +fn test_jcs_ed25519_fails_when_signature_is_mutated() { + for tv in TVS { + let secret = decode_b58(tv.secret_b58).unwrap(); + let mut document: Document = Document::from_json(tv.document_unsigned).unwrap(); + let method = document.try_resolve("#key-1").unwrap(); + let options: SignatureOptions = SignatureOptions::new(method.try_into_fragment().unwrap()); + let suite: LdSuite<_> = LdSuite::new(JcsEd25519Signature2020); + + suite.sign(&mut document, options, &secret).unwrap(); + + assert!(suite.verify(&document).is_ok()); + assert_eq!(document.proof().unwrap().data().as_str(), tv.signature); + + document + .proof_mut() + .unwrap() + .set_data(SignatureData::Signature("foo".into())); + + assert!(suite.verify(&document).is_err()); + } +} From 6bbdad5d3f0fa9b47b7b0794961a560d409cb203 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 14:56:04 -0800 Subject: [PATCH 24/28] remove panic --- identity-iota/src/did/document_builder.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/identity-iota/src/did/document_builder.rs b/identity-iota/src/did/document_builder.rs index d406485725..aa1e383424 100644 --- a/identity-iota/src/did/document_builder.rs +++ b/identity-iota/src/did/document_builder.rs @@ -12,6 +12,7 @@ use identity_did::verification::MethodType; use crate::did::IotaDID; use crate::did::IotaDocument; use crate::did::Properties; +use crate::error::Error; use crate::error::Result; #[derive(Clone, Debug)] @@ -108,7 +109,7 @@ impl IotaDocumentBuilder { fn default_keypair(method: MethodType) -> Result { match method { MethodType::Ed25519VerificationKey2018 => KeyPair::new_ed25519().map_err(Into::into), - _ => todo!("Invalid Method Type"), + _ => Err(Error::InvalidDocument { error: "Unknown Method Type" }), } } } From c765f153f79198df2dbc23357684838e6f58a596 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 15:10:17 -0800 Subject: [PATCH 25/28] cargo fmt --- identity-iota/src/did/document_builder.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/identity-iota/src/did/document_builder.rs b/identity-iota/src/did/document_builder.rs index aa1e383424..cc58871c09 100644 --- a/identity-iota/src/did/document_builder.rs +++ b/identity-iota/src/did/document_builder.rs @@ -109,7 +109,9 @@ impl IotaDocumentBuilder { fn default_keypair(method: MethodType) -> Result { match method { MethodType::Ed25519VerificationKey2018 => KeyPair::new_ed25519().map_err(Into::into), - _ => Err(Error::InvalidDocument { error: "Unknown Method Type" }), + _ => Err(Error::InvalidDocument { + error: "Unknown Method Type", + }), } } } From 2e540cc539218da9a1365a1ce4d6ff5c42e44cf8 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 15:15:53 -0800 Subject: [PATCH 26/28] clippy --- identity-did/tests/sign_verify_document_ed25519.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/identity-did/tests/sign_verify_document_ed25519.rs b/identity-did/tests/sign_verify_document_ed25519.rs index 4ca688aa81..655e210788 100644 --- a/identity-did/tests/sign_verify_document_ed25519.rs +++ b/identity-did/tests/sign_verify_document_ed25519.rs @@ -19,7 +19,7 @@ struct TestVector { signature: &'static str, } -const TVS: &'static [TestVector] = &include!("fixtures/jcs_ed25519.rs"); +const TVS: &[TestVector] = &include!("fixtures/jcs_ed25519.rs"); type Document = document::Document, (), ()>; From 277b4f7bb9f12557fd4cfc0a0ed0ae589ed53e6e Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 15:32:36 -0800 Subject: [PATCH 27/28] Update wasm --- bindings/wasm/Cargo.toml | 3 +-- bindings/wasm/src/did.rs | 7 +++--- bindings/wasm/src/doc.rs | 48 +++++++++++++++++++++---------------- bindings/wasm/src/iota.rs | 13 +++++----- bindings/wasm/src/key.rs | 16 ++++++------- bindings/wasm/src/lib.rs | 3 +++ bindings/wasm/src/pubkey.rs | 9 ++++--- bindings/wasm/src/vc.rs | 18 ++++++++------ bindings/wasm/src/vp.rs | 16 ++++++++----- 9 files changed, 77 insertions(+), 56 deletions(-) diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 3669f1dcb8..beec44a232 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -15,8 +15,7 @@ crate-type = ["cdylib"] [dependencies] console_error_panic_hook = { version = "0.1" } -identity-core = { version = "=0.1.0", path = "../../identity-core" } -identity-iota = { version = "=0.1.0", path = "../../identity-iota" } +identity = { version = "=0.1.0", path = "../../identity" } serde = { version = "1.0", features = ["derive"] } wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } wasm-bindgen-futures = { version = "0.4", default-features = false } diff --git a/bindings/wasm/src/did.rs b/bindings/wasm/src/did.rs index d88803cacb..c569b85800 100644 --- a/bindings/wasm/src/did.rs +++ b/bindings/wasm/src/did.rs @@ -1,11 +1,12 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use identity_core::utils::decode_b58; -use identity_iota::did::IotaDID; +use identity::core::decode_b58; +use identity::iota::IotaDID; use wasm_bindgen::prelude::*; -use crate::{js_err, key::Key}; +use crate::js_err; +use crate::key::Key; /// @typicalname did #[wasm_bindgen(inspectable)] diff --git a/bindings/wasm/src/doc.rs b/bindings/wasm/src/doc.rs index f07d59dd52..b27c1c0442 100644 --- a/bindings/wasm/src/doc.rs +++ b/bindings/wasm/src/doc.rs @@ -1,22 +1,27 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use identity_core::{ - convert::{FromJson as _, SerdeInto as _}, - did_doc::{DIDKey, Document, DocumentBuilder, MethodIndex, MethodScope, Service, ServiceBuilder, VerifiableDocument}, -}; -use identity_iota::{ - did::{DocumentDiff, IotaDocument, Properties}, - tangle::MessageId, -}; +use identity::core::FromJson; +use identity::core::Object; +use identity::core::SerdeInto; +use identity::did::DIDKey; +use identity::did::Document; +use identity::did::DocumentBuilder; +use identity::did::MethodIdent; +use identity::did::MethodScope; +use identity::did::Service; +use identity::did::ServiceBuilder; +use identity::iota::DocumentDiff; +use identity::iota::IotaDocument; +use identity::iota::MessageId; +use identity::iota::Properties; use wasm_bindgen::prelude::*; -use crate::{ - did::DID, - js_err, - key::Key, - pubkey::{PubKey, DEFAULT_KEY}, -}; +use crate::did::DID; +use crate::js_err; +use crate::key::Key; +use crate::pubkey::PubKey; +use crate::pubkey::DEFAULT_KEY; #[wasm_bindgen(inspectable)] pub struct NewDoc { @@ -48,14 +53,15 @@ impl Doc { let mut did = authentication.0.id().clone(); did.set_fragment(None); - let base: VerifiableDocument = DocumentBuilder::new(Properties::new()) + let base: IotaDocument = DocumentBuilder::new(Properties::new()) .id(did) // Note: We use a reference to the verification method due to // upstream limitations. .authentication(authentication.0.id().clone()) .verification_method(authentication.0.clone()) .build() - .map(VerifiableDocument::new) + .map(Document::into_verifiable) + .map(Into::into) .map_err(js_err)?; IotaDocument::try_from_document(base.serde_into().map_err(js_err)?) @@ -244,11 +250,11 @@ impl Doc { pub fn resolve_key(&mut self, ident: JsValue, scope: Option) -> Result { let borrow: String; - let ident: MethodIndex = if let Some(number) = ident.as_f64() { - MethodIndex::Index(number.to_string().parse().map_err(js_err)?) + let ident: MethodIdent = if let Some(number) = ident.as_f64() { + MethodIdent::Index(number.to_string().parse().map_err(js_err)?) } else if let Some(ident) = ident.as_string() { borrow = ident; - MethodIndex::Ident(&borrow) + MethodIdent::Ident(&borrow) } else { return Err("Invalid Key Identifier".into()); }; @@ -283,8 +289,8 @@ impl Doc { // core DID Document type. // // Uses `serde` for conversions and re-validates the document after mutation. - fn mutate(this: &mut Self, f: impl FnOnce(&mut Document) -> T) -> Result { - let mut document: Document = this.0.serde_into().map_err(js_err)?; + fn mutate(this: &mut Self, f: impl FnOnce(&mut Document) -> T) -> Result { + let mut document: Document = this.0.serde_into().map_err(js_err)?; let output: T = f(&mut document); this.0 = IotaDocument::try_from_document(document).map_err(js_err)?; diff --git a/bindings/wasm/src/iota.rs b/bindings/wasm/src/iota.rs index 80cd83e70b..6f6edbaf67 100644 --- a/bindings/wasm/src/iota.rs +++ b/bindings/wasm/src/iota.rs @@ -1,12 +1,13 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use identity_core::common::Object; -use identity_iota::{ - client::{Client, ClientBuilder, Network, TxnPrinter}, - credential::CredentialValidator, - did::IotaDID, -}; +use identity::core::Object; +use identity::iota::Client; +use identity::iota::ClientBuilder; +use identity::iota::CredentialValidator; +use identity::iota::IotaDID; +use identity::iota::Network; +use identity::iota::TxnPrinter; use serde::Deserialize; use wasm_bindgen::prelude::*; diff --git a/bindings/wasm/src/key.rs b/bindings/wasm/src/key.rs index 60b78a76b2..34fd6716d0 100644 --- a/bindings/wasm/src/key.rs +++ b/bindings/wasm/src/key.rs @@ -1,12 +1,12 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use identity_core::{ - crypto::{KeyPair, PublicKey, SecretKey}, - did_doc::MethodType, - utils::{decode_b58, encode_b58, generate_ed25519}, -}; -use serde::{Deserialize, Serialize}; +use identity::core::decode_b58; +use identity::core::encode_b58; +use identity::crypto::KeyPair; +use identity::crypto::PublicKey; +use identity::crypto::SecretKey; +use identity::did::MethodType; use wasm_bindgen::prelude::*; use crate::js_err; @@ -21,7 +21,7 @@ impl Key { #[wasm_bindgen(constructor)] pub fn new(key_type: &str) -> Result { match key_type.parse().map_err(js_err)? { - MethodType::Ed25519VerificationKey2018 => Ok(Self::generate_ed25519()), + MethodType::Ed25519VerificationKey2018 => Self::generate_ed25519(), _ => Err("Invalid Key Type".into()), } } @@ -29,7 +29,7 @@ impl Key { /// Generates a new `Key` object suitable for ed25519 signatures. #[wasm_bindgen(js_name = generateEd25519)] pub fn generate_ed25519() -> Result { - generate_ed25519().map_err(js_err).map(Self) + KeyPair::new_ed25519().map_err(js_err).map(Self) } /// Parses a `Key` object from base58-encoded public/private keys. diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 96076e6579..a7a1b138e4 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -1,6 +1,9 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +#[macro_use] +extern crate serde; + use wasm_bindgen::prelude::*; pub mod did; diff --git a/bindings/wasm/src/pubkey.rs b/bindings/wasm/src/pubkey.rs index fab974284d..f693190188 100644 --- a/bindings/wasm/src/pubkey.rs +++ b/bindings/wasm/src/pubkey.rs @@ -1,11 +1,14 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use identity_core::did_doc::{Method, MethodBuilder, MethodData}; -use identity_iota::did::IotaDID; +use identity::did::Method; +use identity::did::MethodBuilder; +use identity::did::MethodData; +use identity::iota::IotaDID; use wasm_bindgen::prelude::*; -use crate::{did::DID, js_err}; +use crate::did::DID; +use crate::js_err; pub const DEFAULT_KEY: &str = "Ed25519VerificationKey2018"; pub const DEFAULT_TAG: &str = "authentication"; diff --git a/bindings/wasm/src/vc.rs b/bindings/wasm/src/vc.rs index 17631a1cb5..376e49b2ad 100644 --- a/bindings/wasm/src/vc.rs +++ b/bindings/wasm/src/vc.rs @@ -1,13 +1,17 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use identity_core::{ - common::{OneOrMany, Url}, - credential::{Credential, CredentialBuilder, CredentialSubject, VerifiableCredential as VC}, -}; +use identity::core::OneOrMany; +use identity::core::Url; +use identity::credential::Credential; +use identity::credential::CredentialBuilder; +use identity::credential::Subject; +use identity::credential::VerifiableCredential as VC; use wasm_bindgen::prelude::*; -use crate::{doc::Doc, js_err, key::Key}; +use crate::doc::Doc; +use crate::js_err; +use crate::key::Key; #[wasm_bindgen(inspectable)] #[derive(Clone, Debug, PartialEq)] @@ -23,13 +27,13 @@ impl VerifiableCredential { credential_type: Option, credential_id: Option, ) -> Result { - let subjects: OneOrMany = subject_data.into_serde().map_err(js_err)?; + let subjects: OneOrMany = subject_data.into_serde().map_err(js_err)?; let issuer_url: Url = Url::parse(issuer_doc.id().as_str()).map_err(js_err)?; let mut builder: CredentialBuilder = CredentialBuilder::default().issuer(issuer_url); for subject in subjects.into_vec() { - builder = builder.credential_subject(subject); + builder = builder.subject(subject); } if let Some(credential_type) = credential_type { diff --git a/bindings/wasm/src/vp.rs b/bindings/wasm/src/vp.rs index 5558f06dbb..5bb3aba230 100644 --- a/bindings/wasm/src/vp.rs +++ b/bindings/wasm/src/vp.rs @@ -1,13 +1,17 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use identity_core::{ - common::{OneOrMany, Url}, - credential::{Presentation, PresentationBuilder, VerifiableCredential, VerifiablePresentation as VP}, -}; +use identity::core::OneOrMany; +use identity::core::Url; +use identity::credential::Presentation; +use identity::credential::PresentationBuilder; +use identity::credential::VerifiableCredential; +use identity::credential::VerifiablePresentation as VP; use wasm_bindgen::prelude::*; -use crate::{doc::Doc, js_err, key::Key}; +use crate::doc::Doc; +use crate::js_err; +use crate::key::Key; #[wasm_bindgen(inspectable)] #[derive(Clone, Debug, PartialEq)] @@ -29,7 +33,7 @@ impl VerifiablePresentation { let mut builder: PresentationBuilder = PresentationBuilder::default().holder(holder_url); for credential in credentials.into_vec() { - builder = builder.verifiable_credential(credential); + builder = builder.credential(credential); } if let Some(presentation_type) = presentation_type { From 14bd900da0a8cfbb1f5b09fe8c14bdd320ba73ab Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 2 Feb 2021 15:37:31 -0800 Subject: [PATCH 28/28] enable more warnings --- identity-diff/src/lib.rs | 10 +++++----- identity-iota/src/lib.rs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/identity-diff/src/lib.rs b/identity-diff/src/lib.rs index faf968c725..f37eb930c2 100644 --- a/identity-diff/src/lib.rs +++ b/identity-diff/src/lib.rs @@ -12,11 +12,11 @@ rust_2018_idioms, unreachable_pub, // missing_docs, - // missing_crate_level_docs, - // broken_intra_doc_links, - // private_intra_doc_links, - // private_doc_tests, - // clippy::missing_safety_doc, + missing_crate_level_docs, + broken_intra_doc_links, + private_intra_doc_links, + private_doc_tests, + clippy::missing_safety_doc, // clippy::missing_errors_doc, )] diff --git a/identity-iota/src/lib.rs b/identity-iota/src/lib.rs index a6ee51686b..187d1b5d35 100644 --- a/identity-iota/src/lib.rs +++ b/identity-iota/src/lib.rs @@ -5,11 +5,11 @@ rust_2018_idioms, unreachable_pub, // missing_docs, - // missing_crate_level_docs, - // broken_intra_doc_links, - // private_intra_doc_links, - // private_doc_tests, - // clippy::missing_safety_doc, + missing_crate_level_docs, + broken_intra_doc_links, + private_intra_doc_links, + private_doc_tests, + clippy::missing_safety_doc, // clippy::missing_errors_doc, )]