diff --git a/identity_core/src/common/ordered_set.rs b/identity_core/src/common/ordered_set.rs index 9cccca1288..96178cf120 100644 --- a/identity_core/src/common/ordered_set.rs +++ b/identity_core/src/common/ordered_set.rs @@ -174,13 +174,16 @@ impl OrderedSet { self.change(update, |item, update| item.key() == update.key()) } - /// Removes all matching items from the set. + /// Removes and returns the matching item from the set, if it exists. #[inline] pub fn remove(&mut self, item: &U) -> bool where T: KeyComparable, U: KeyComparable, { + // self.iter().enumerate().find(|(_, entry)| entry.key() == item.key()).map(|(idx,_)| idx).map(|idx| + // self.0.remove(idx)) + if self.contains(item) { self.0.retain(|this| this.borrow().key() != item.key()); true @@ -312,6 +315,9 @@ where #[cfg(test)] mod tests { use super::*; + use proptest::prelude::Rng; + use proptest::strategy::Strategy; + use proptest::*; #[test] fn test_ordered_set_works() { @@ -383,7 +389,7 @@ mod tests { assert!(!set.contains(&cs4)); } - #[derive(Clone, Copy, PartialEq, Eq)] + #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] struct ComparableStruct { key: u8, value: i32, @@ -456,4 +462,46 @@ mod tests { assert_eq!(set.head().unwrap().key, cs2.key); assert_eq!(set.head().unwrap().value, cs2.value); } + + // =========================================================================================================================== + // Test key uniqueness invariant with randomly generated input + // =========================================================================================================================== + + fn arbitrary_set_comparable_struct() -> impl Strategy> { + proptest::arbitrary::any::>().prop_map(|values| { + values + .into_iter() + .map(|(key, value)| ComparableStruct { key, value }) + .collect() + }) + } + + fn arbitrary_set_u128() -> impl Strategy> { + proptest::arbitrary::any::>().prop_map(|vector| vector.into_iter().collect()) + } + + // construct a set together with a pair of values. Given one of these values there is a 50% chance that it is + // contained in the set. + fn set_with_values(f: F) -> impl Strategy, T, T)> + where + T: KeyComparable + Default + Debug + Clone, + U: Strategy, OrderedSet)>, + F: Fn() -> U, + { + f().prop_perturb(|(s0, s1), mut rng| { + let sets = [&s0, &s1]; + let mut pick_value = || { + let set_idx = usize::from(rng.gen_bool(0.5)); + let set_range = if set_idx == 0 { 0..s0.len() } else { 0..s1.len() }; + if set_range.is_empty() { + T::default() + } else { + let entry_idx = rng.gen_range(set_range); + (sets[set_idx])[entry_idx].clone() + } + }; + let (v0, v1) = (pick_value(), pick_value()); + (s0, v0, v1) + }) + } } diff --git a/identity_core/src/crypto/proof/proof.rs b/identity_core/src/crypto/proof/proof.rs index 9faceceebd..69f6d0ddfc 100644 --- a/identity_core/src/crypto/proof/proof.rs +++ b/identity_core/src/crypto/proof/proof.rs @@ -145,11 +145,11 @@ impl Serialize for Proof { } else { 3 // type + method + value }; - count_fields += if self.created.is_some() { 1 } else { 0 }; - count_fields += if self.expires.is_some() { 1 } else { 0 }; - count_fields += if self.challenge.is_some() { 1 } else { 0 }; - count_fields += if self.domain.is_some() { 1 } else { 0 }; - count_fields += if self.purpose.is_some() { 1 } else { 0 }; + count_fields += usize::from(self.created.is_some()); + count_fields += usize::from(self.expires.is_some()); + count_fields += usize::from(self.challenge.is_some()); + count_fields += usize::from(self.domain.is_some()); + count_fields += usize::from(self.purpose.is_some()); let mut state: S::SerializeMap = serializer.serialize_map(Some(count_fields))?; state.serialize_entry("type", &self.type_)?; diff --git a/identity_credential/src/validator/credential_validator.rs b/identity_credential/src/validator/credential_validator.rs index c65b410119..1cce14f03d 100644 --- a/identity_credential/src/validator/credential_validator.rs +++ b/identity_credential/src/validator/credential_validator.rs @@ -774,7 +774,7 @@ mod tests { // Add a RevocationBitmap service to the issuer. let bitmap: RevocationBitmap = RevocationBitmap::new(); - assert!(issuer_doc.service_mut().append( + assert!(issuer_doc.service_mut_unchecked().append( Service::builder(Object::new()) .id(service_url.clone()) .type_(RevocationBitmap::TYPE) diff --git a/identity_did/Cargo.toml b/identity_did/Cargo.toml index 500439250c..815260a1d5 100644 --- a/identity_did/Cargo.toml +++ b/identity_did/Cargo.toml @@ -23,6 +23,7 @@ strum = { version = "0.24.0", default-features = false, features = ["std", "deri thiserror = { version = "1.0", default-features = false } [dev-dependencies] +criterion = { version = "0.4.0", default-features = false, features = ["cargo_bench_support"] } proptest = { version = "1.0" } serde_json = { version = "1.0", default-features = false } @@ -35,3 +36,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["revocation-bitmap"] revocation-bitmap = ["dataurl", "flate2", "roaring"] + +[[bench]] +name = "deserialize_document" +harness = false diff --git a/identity_did/benches/deserialize_document.rs b/identity_did/benches/deserialize_document.rs new file mode 100644 index 0000000000..ba92a50350 --- /dev/null +++ b/identity_did/benches/deserialize_document.rs @@ -0,0 +1,239 @@ +// Copyright 2020-2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use criterion::Throughput; +use identity_core::convert::FromJson; +// This is a benchmark measuring the time it takes to deserialize a DID document from JSON. +use criterion::criterion_group; +use criterion::criterion_main; +use criterion::BenchmarkId; +use criterion::Criterion; +use identity_did::document::CoreDocument; + +const JSON_DOC_SHORT: &str = r#" + { + "id": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "verificationMethod": [ + { + "id": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#issuerKey", + "controller": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "type": "Ed25519VerificationKey2018", + "publicKeyMultibase": "zFVen3X669xLzsi6N2V91DoiyzHzg1uAgqiT8jZ9nS96Z" + } + ] + }"#; + +const JSON_DOC_DID_KEY: &str = r#"{ + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2020/v1", + "https://w3id.org/security/suites/x25519-2020/v1" + ], + "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", + "verificationMethod": [{ + "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", + "type": "Ed25519VerificationKey2018", + "controller": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", + "publicKeyMultibase": "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK" + }], + "authentication": [ + "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK" + ], + "assertionMethod": [ + "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK" + ], + "capabilityDelegation": [ + "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK" + ], + "capabilityInvocation": [ + "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK" + ], + "keyAgreement": [{ + "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6LSj72tK8brWgZja8NLRwPigth2T9QRiG1uH9oKZuKjdh9p", + "type": "X25519KeyAgreementKey2019", + "controller": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", + "publicKeyMultibase": "z6LSj72tK8brWgZja8NLRwPigth2T9QRiG1uH9oKZuKjdh9p" + }] + }"#; + +// This is not a realistic document in any way. +const JSON_DOCUMENT_LARGE: &str = r#"{ + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "did:example:123", + "verificationMethod": [ + { + "id": "did:example:123#key1", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" + }, + { + "id": "did:example:123#key2", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" + }, + { + "id": "did:example:123#key3", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" + }, + { + "id": "did:example:123#key4", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" + }, + { + "id": "did:example:123#key5", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" + }, + { + "id": "did:example:123#key6", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" + } + ], + "authentication": [ + { + "id": "did:example:123#z6MkecaLyHuYWkayBDLw5ihndj3T1m6zKTGqau3A51G7RBf3EMB1", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "zAKJP3f7BD6W4iWEQ9jwndVTCBq8ua2Utt8EEjJ6Vxsf" + }, + "did:example:123#key1", + { + "id": "did:example:123#z6MkecaLyHuYWkayBDLw5ihndj3T1m6zKTGqau3A51G7RBf3EMB2", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "zAKJP3f7BD6W4iWEQ9jwndVTCBq8ua2Utt8EEjJ6Vxsf" + }, + "did:example:123#key4", + "did:example:123#key2", + { + "id": "did:example:123#z6MkecaLyHuYWkayBDLw5ihndj3T1m6zKTGqau3A51G7RBf3EMB3", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "zAKJP3f7BD6W4iWEQ9jwndVTCBq8ua2Utt8EEjJ6Vxsf" + }, + { + "id": "did:example:123#z6MkecaLyHuYWkayBDLw5ihndj3T1m6zKTGqau3A51G7RBf3EMB4", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "zAKJP3f7BD6W4iWEQ9jwndVTCBq8ua2Utt8EEjJ6Vxsf" + } + ], + "capabilityInvocation": [ + { + "id": "did:example:123#z6MkhdmzFu659ZJ4XKj31vtEDmjvsi5yDZG5L7Caz63oP39k", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "z4BWwfeqdp1obQptLLMvPNgBw48p7og1ie6Hf9p5nTpNN" + }, + "did:example:123#key1", + { + "id": "did:example:123#z6MkhdmzFu659ZJ4XKj31vtEDmjvsi5yDZG5L7Caz63oP39kEMB2", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "z4BWwfeqdp1obQptLLMvPNgBw48p7og1ie6Hf9p5nTpNN" + }, + { + "id": "did:example:123#z6MkhdmzFu659ZJ4XKj31vtEDmjvsi5yDZG5L7Caz63oP39kEMB3", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "z4BWwfeqdp1obQptLLMvPNgBw48p7og1ie6Hf9p5nTpNN" + }, + "did:example:123#key5" + ], + "capabilityDelegation": [ + { + "id": "did:example:123#z6Mkw94ByR26zMSkNdCUi6FNRsWnc2DFEeDXyBGJ5KTzSWyi", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "zHgo9PAmfeoxHG8Mn2XHXamxnnSwPpkyBHAMNF3VyXJCL" + }, + "did:example:123#key6" + ], + "assertionMethod": [ + { + "id": "did:example:123#z6MkiukuAuQAE8ozxvmahnQGzApvtW7KT5XXKfojjwbdEomY", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "z5TVraf9itbKXrRvt2DSS95Gw4vqU3CHAdetoufdcKazA" + }, + { + "id": "did:example:123#z6MkiukuAuQAE8ozxvmahnQGzApvtW7KT5XXKfojjwbdEomYEMB2", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "z5TVraf9itbKXrRvt2DSS95Gw4vqU3CHAdetoufdcKazA" + }, + { + "id": "did:example:123#z6MkiukuAuQAE8ozxvmahnQGzApvtW7KT5XXKfojjwbdEomYEMB3", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "z5TVraf9itbKXrRvt2DSS95Gw4vqU3CHAdetoufdcKazA" + }, + { + "id": "did:example:123#z6MkiukuAuQAE8ozxvmahnQGzApvtW7KT5XXKfojjwbdEomYEMB4", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "z5TVraf9itbKXrRvt2DSS95Gw4vqU3CHAdetoufdcKazA" + }, + { + "id": "did:example:123#z6MkiukuAuQAE8ozxvmahnQGzApvtW7KT5XXKfojjwbdEomYEMB5", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "z5TVraf9itbKXrRvt2DSS95Gw4vqU3CHAdetoufdcKazA" + }, + { + "id": "did:example:123#z6MkiukuAuQAE8ozxvmahnQGzApvtW7KT5XXKfojjwbdEomYEMB6", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "z5TVraf9itbKXrRvt2DSS95Gw4vqU3CHAdetoufdcKazA" + }, + "did:example:123#key4", + { + "id": "did:example:123#z6MkiukuAuQAE8ozxvmahnQGzApvtW7KT5XXKfojjwbdEomYEMB7", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "z5TVraf9itbKXrRvt2DSS95Gw4vqU3CHAdetoufdcKazA" + } + ], + "service": [{ + "id":"did:example:123#linked-domain", + "type": "LinkedDomains", + "serviceEndpoint": "https://bar.example.com" + }] +}"#; + +fn deserialize_json_document(c: &mut Criterion) { + let mut group = c.benchmark_group("deserialize_json_document"); + for (json, name) in [ + (JSON_DOC_SHORT, "short document"), + (JSON_DOC_DID_KEY, "did:key document"), + (JSON_DOCUMENT_LARGE, "large document"), + ] { + group.throughput(Throughput::Bytes(json.as_bytes().len() as u64)); + group.bench_with_input( + BenchmarkId::from_parameter(format!("{name}, document size: {} bytes", json.as_bytes().len())), + json, + |b, json| { + b.iter(|| { + let doc: Result = CoreDocument::from_json(json); + assert!(doc.is_ok(), "bench {name} failed: {:#?}", doc); + }) + }, + ); + } + group.finish(); +} + +criterion_group!(benches, deserialize_json_document); +criterion_main!(benches); diff --git a/identity_did/src/diff/diff_document.rs b/identity_did/src/diff/diff_document.rs index 5adfc9236a..4b3c65f3dd 100644 --- a/identity_did/src/diff/diff_document.rs +++ b/identity_did/src/diff/diff_document.rs @@ -17,6 +17,7 @@ use identity_core::diff::Result; use crate::did::CoreDID; use crate::did::DID; use crate::document::CoreDocument; +use crate::document::CoreDocumentData; use crate::service::Service; use crate::verification::MethodRef; use crate::verification::VerificationMethod; @@ -201,17 +202,19 @@ where .unwrap_or_else(|| self.properties().clone()); Ok(CoreDocument { - id, - controller, - also_known_as, - verification_method, - authentication, - assertion_method, - key_agreement, - capability_delegation, - capability_invocation, - service, - properties, + data: CoreDocumentData { + id, + controller, + also_known_as, + verification_method, + authentication, + assertion_method, + key_agreement, + capability_delegation, + capability_invocation, + service, + properties, + }, }) } @@ -282,36 +285,40 @@ where let properties: T = diff.properties.map(T::from_diff).transpose()?.unwrap_or_default(); Ok(CoreDocument { - id, - controller, - also_known_as, - verification_method, - authentication, - assertion_method, - key_agreement, - capability_delegation, - capability_invocation, - service, - properties, + data: CoreDocumentData { + id, + controller, + also_known_as, + verification_method, + authentication, + assertion_method, + key_agreement, + capability_delegation, + capability_invocation, + service, + properties, + }, }) } fn into_diff(self) -> Result { + let inner = self.data; + Ok(DiffDocument { - id: Some(self.id.into_diff()?), - controller: Some(self.controller.map(|value| value.into_diff()).transpose()?), - also_known_as: Some(self.also_known_as.into_diff()?), - verification_method: Some(self.verification_method.into_diff()?), - authentication: Some(self.authentication.into_diff()?), - assertion_method: Some(self.assertion_method.into_diff()?), - key_agreement: Some(self.key_agreement.into_diff()?), - capability_delegation: Some(self.capability_delegation.into_diff()?), - capability_invocation: Some(self.capability_invocation.into_diff()?), - service: Some(self.service.into_diff()?), - properties: if self.properties == Default::default() { + id: Some(inner.id.into_diff()?), + controller: Some(inner.controller.map(|value| value.into_diff()).transpose()?), + also_known_as: Some(inner.also_known_as.into_diff()?), + verification_method: Some(inner.verification_method.into_diff()?), + authentication: Some(inner.authentication.into_diff()?), + assertion_method: Some(inner.assertion_method.into_diff()?), + key_agreement: Some(inner.key_agreement.into_diff()?), + capability_delegation: Some(inner.capability_delegation.into_diff()?), + capability_invocation: Some(inner.capability_invocation.into_diff()?), + service: Some(inner.service.into_diff()?), + properties: if inner.properties == Default::default() { None } else { - Some(self.properties.into_diff()?) + Some(inner.properties.into_diff()?) }, }) } @@ -386,7 +393,7 @@ mod test { let doc = document(); let mut new = doc.clone(); let new_did = "did:diff:1234"; - *new.id_mut() = new_did.parse().unwrap(); + *new.id_mut_unchecked() = new_did.parse().unwrap(); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); @@ -400,7 +407,7 @@ mod test { let doc: CoreDocument = document(); let mut new: CoreDocument = doc.clone(); let new_controller: CoreDID = "did:diff:1234".parse().unwrap(); - *new.controller_mut() = Some(OneOrSet::new_one(new_controller)); + *new.controller_mut_unchecked() = Some(OneOrSet::new_one(new_controller)); assert_ne!(doc, new); let diff: DiffDocument = doc.diff(&new).unwrap(); @@ -417,7 +424,7 @@ mod test { "did:diff:5678".parse().unwrap(), "did:diff:9012".parse().unwrap(), ]; - *new.controller_mut() = Some(new_controllers.try_into().unwrap()); + *new.controller_mut_unchecked() = Some(new_controllers.try_into().unwrap()); assert_ne!(doc, new); let diff: DiffDocument = doc.diff(&new).unwrap(); @@ -429,7 +436,7 @@ mod test { fn test_controller_unset() { let doc: CoreDocument = document(); let mut new: CoreDocument = doc.clone(); - *new.controller_mut() = None; + *new.controller_mut_unchecked() = None; assert_ne!(doc, new); let diff: DiffDocument = doc.diff(&new).unwrap(); @@ -441,7 +448,9 @@ mod test { fn test_also_known_as() { let doc = document(); let mut new = doc.clone(); - new.also_known_as_mut().append("diff:diff:1234".parse().unwrap()); + new + .also_known_as_mut_unchecked() + .append("diff:diff:1234".parse().unwrap()); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); @@ -455,7 +464,9 @@ mod test { let mut new = doc.clone(); // add new method - assert!(new.verification_method_mut().append(method(&doc.id, "#key-diff"))); + assert!(new + .verification_method_mut_unchecked() + .append(method(&doc.data.id, "#key-diff"))); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -470,7 +481,7 @@ mod test { // update method let first = new.verification_method().first().unwrap().clone(); new - .verification_method_mut() + .verification_method_mut_unchecked() .replace(&first, method(&"did:diff:1234".parse().unwrap(), "#key-diff")); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); @@ -485,7 +496,7 @@ mod test { // remove method let first = new.verification_method().first().unwrap().clone(); - new.verification_method_mut().remove(&first); + new.verification_method_mut_unchecked().remove(&first); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -498,8 +509,8 @@ mod test { let mut new = doc.clone(); // add new method - let method_ref: MethodRef = method(&doc.id, "#key-diff").into(); - assert!(new.authentication_mut().append(method_ref)); + let method_ref: MethodRef = method(&doc.data.id, "#key-diff").into(); + assert!(new.authentication_mut_unchecked().append(method_ref)); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -512,9 +523,9 @@ mod test { let mut new = doc.clone(); // update method - let method_ref: MethodRef = method(&doc.id, "#key-diff").into(); + let method_ref: MethodRef = method(&doc.data.id, "#key-diff").into(); let first = new.authentication().first().unwrap().clone(); - new.authentication_mut().replace(&first, method_ref); + new.authentication_mut_unchecked().replace(&first, method_ref); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -528,7 +539,7 @@ mod test { // remove method let first = new.authentication().first().unwrap().clone(); - new.authentication_mut().remove(&first); + new.authentication_mut_unchecked().remove(&first); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -541,8 +552,8 @@ mod test { let mut new = doc.clone(); // add new method - let method_ref: MethodRef = method(&doc.id, "#key-diff").into(); - assert!(new.assertion_method_mut().append(method_ref)); + let method_ref: MethodRef = method(&doc.data.id, "#key-diff").into(); + assert!(new.assertion_method_mut_unchecked().append(method_ref)); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -555,9 +566,9 @@ mod test { let mut new = doc.clone(); // update method - let method_ref: MethodRef = method(&doc.id, "#key-diff").into(); + let method_ref: MethodRef = method(&doc.data.id, "#key-diff").into(); let first = new.assertion_method().first().unwrap().clone(); - new.assertion_method_mut().replace(&first, method_ref); + new.assertion_method_mut_unchecked().replace(&first, method_ref); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -571,7 +582,7 @@ mod test { // remove method let first = new.assertion_method().first().unwrap().clone(); - new.assertion_method_mut().remove(&first); + new.assertion_method_mut_unchecked().remove(&first); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -584,8 +595,8 @@ mod test { let mut new = doc.clone(); // add new method - let method_ref: MethodRef = method(&doc.id, "#key-diff").into(); - assert!(new.key_agreement_mut().append(method_ref)); + let method_ref: MethodRef = method(&doc.data.id, "#key-diff").into(); + assert!(new.key_agreement_mut_unchecked().append(method_ref)); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -598,9 +609,9 @@ mod test { let mut new = doc.clone(); // update method - let method_ref: MethodRef = method(&doc.id, "#key-diff").into(); + let method_ref: MethodRef = method(&doc.data.id, "#key-diff").into(); let first = new.key_agreement().first().unwrap().clone(); - new.key_agreement_mut().replace(&first, method_ref); + new.key_agreement_mut_unchecked().replace(&first, method_ref); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -614,7 +625,7 @@ mod test { // remove method let first = new.key_agreement().first().unwrap().clone(); - new.key_agreement_mut().remove(&first); + new.key_agreement_mut_unchecked().remove(&first); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -627,8 +638,8 @@ mod test { let mut new = doc.clone(); // add new method - let method_ref: MethodRef = method(&doc.id, "#key-diff").into(); - assert!(new.capability_delegation_mut().append(method_ref)); + let method_ref: MethodRef = method(&doc.data.id, "#key-diff").into(); + assert!(new.capability_delegation_mut_unchecked().append(method_ref)); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -641,9 +652,9 @@ mod test { let mut new = doc.clone(); // update method - let method_ref: MethodRef = method(&doc.id, "#key-diff").into(); + let method_ref: MethodRef = method(&doc.data.id, "#key-diff").into(); let first = new.capability_delegation().first().unwrap().clone(); - new.capability_delegation_mut().replace(&first, method_ref); + new.capability_delegation_mut_unchecked().replace(&first, method_ref); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -657,7 +668,7 @@ mod test { // remove method let first = new.capability_delegation().first().unwrap().clone(); - new.capability_delegation_mut().remove(&first); + new.capability_delegation_mut_unchecked().remove(&first); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -670,8 +681,8 @@ mod test { let mut new = doc.clone(); // add new method - let method_ref: MethodRef = method(&doc.id, "#key-diff").into(); - assert!(new.capability_invocation_mut().append(method_ref)); + let method_ref: MethodRef = method(&doc.data.id, "#key-diff").into(); + assert!(new.capability_invocation_mut_unchecked().append(method_ref)); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -684,9 +695,9 @@ mod test { let mut new = doc.clone(); // update method - let method_ref: MethodRef = method(&doc.id, "#key-diff").into(); + let method_ref: MethodRef = method(&doc.data.id, "#key-diff").into(); let first = new.capability_invocation().first().unwrap().clone(); - new.capability_invocation_mut().replace(&first, method_ref); + new.capability_invocation_mut_unchecked().replace(&first, method_ref); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -700,7 +711,7 @@ mod test { // remove method let first = new.capability_invocation().first().unwrap().clone(); - new.capability_invocation_mut().remove(&first); + new.capability_invocation_mut_unchecked().remove(&first); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -713,8 +724,8 @@ mod test { let mut new = doc.clone(); // Add new service - let service = service(doc.id.to_url().join("#key-diff").unwrap()); - assert!(new.service_mut().append(service)); + let service = service(doc.data.id.to_url().join("#key-diff").unwrap()); + assert!(new.service_mut_unchecked().append(service)); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -727,9 +738,9 @@ mod test { let mut new = doc.clone(); // add new service - let service = service(doc.id.to_url().join("#key-diff").unwrap()); + let service = service(doc.data.id.to_url().join("#key-diff").unwrap()); let first = new.service().first().unwrap().clone(); - new.service_mut().replace(&first, service); + new.service_mut_unchecked().replace(&first, service); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -743,7 +754,7 @@ mod test { // remove method let first = new.service().first().unwrap().clone(); - new.service_mut().remove(&first); + new.service_mut_unchecked().remove(&first); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); let merge = doc.merge(diff).unwrap(); @@ -756,7 +767,7 @@ mod test { let mut new = doc.clone(); // update properties - *new.properties_mut() = BTreeMap::default(); + *new.properties_mut_unchecked() = BTreeMap::default(); assert_ne!(doc, new); let diff = doc.diff(&new).unwrap(); @@ -771,7 +782,7 @@ mod test { // update properties assert!(new - .properties_mut() + .properties_mut_unchecked() .insert("key2".to_string(), "value2".into()) .is_none()); @@ -802,18 +813,18 @@ mod test { let mut new = doc.clone(); let first: CoreDIDUrl = new.capability_invocation().first().unwrap().as_ref().clone(); - new.capability_invocation_mut().remove(&first); + new.capability_invocation_mut_unchecked().remove(&first); let method_ref: MethodRef = MethodBuilder::default() .id(first) - .controller(new.id.clone()) + .controller(new.data.id.clone()) .type_(MethodType::Ed25519VerificationKey2018) .data(MethodData::new_multibase(b"key_material")) .build() .unwrap() .into(); - assert!(new.capability_invocation_mut().append(method_ref)); + assert!(new.capability_invocation_mut_unchecked().append(method_ref)); assert_ne!(doc, new); diff --git a/identity_did/src/document/builder.rs b/identity_did/src/document/builder.rs index 7a29c3306b..e9bedbb0c1 100644 --- a/identity_did/src/document/builder.rs +++ b/identity_did/src/document/builder.rs @@ -142,6 +142,8 @@ where #[cfg(test)] mod tests { use super::*; + use crate::verification::MethodData; + use crate::verification::MethodType; use crate::Error; #[test] @@ -149,4 +151,48 @@ mod tests { let result: Result = DocumentBuilder::default().build(); assert!(matches!(result.unwrap_err(), Error::InvalidDocument(_, None))); } + + #[test] + fn duplicate_id_different_scopes() { + let did: CoreDID = "did:example:1234".parse().unwrap(); + let fragment = "#key1"; + let id = did.clone().to_url().join(fragment).unwrap(); + + let method1: VerificationMethod = VerificationMethod::builder(Default::default()) + .id(id.clone()) + .controller(did.clone()) + .type_(MethodType::Ed25519VerificationKey2018) + .data(MethodData::PublicKeyBase58( + "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J".into(), + )) + .build() + .unwrap(); + + let method2: VerificationMethod = VerificationMethod::builder(Default::default()) + .id(id.clone()) + .controller(did.clone()) + .type_(MethodType::X25519KeyAgreementKey2019) + .data(MethodData::PublicKeyBase58( + "FbQWLPRhTH95MCkQUeFYdiSoQt8zMwetqfWoxqPgaq7x".into(), + )) + .build() + .unwrap(); + + let result: Result = DocumentBuilder::default() + .id(did) + .verification_method(method1) + .key_agreement(method2) + .build(); + // TODO: Remove match once this test passes + match result { + Ok(ref doc) => { + println!( + "{}", + ::to_json_pretty(doc).unwrap() + ); + } + _ => (), + }; + assert!(result.is_err()); + } } diff --git a/identity_did/src/document/core_document.rs b/identity_did/src/document/core_document.rs index 8189fb3771..682c9605ca 100644 --- a/identity_did/src/document/core_document.rs +++ b/identity_did/src/document/core_document.rs @@ -4,6 +4,7 @@ use core::convert::TryInto as _; use core::fmt::Display; use core::fmt::Formatter; +use std::collections::HashMap; use serde::Serialize; @@ -21,6 +22,7 @@ use identity_core::crypto::PrivateKey; use identity_core::crypto::Proof; use identity_core::crypto::ProofPurpose; use identity_core::crypto::Verifier; +use serde::Serializer; use crate::did::CoreDID; use crate::did::DIDUrl; @@ -42,12 +44,9 @@ use crate::verification::MethodUriType; use crate::verification::TryMethod; use crate::verification::VerificationMethod; -/// A DID Document. -/// -/// [Specification](https://www.w3.org/TR/did-core/#did-document-properties) #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[rustfmt::skip] -pub struct CoreDocument +pub(crate) struct CoreDocumentData where D: DID + KeyComparable { @@ -74,12 +73,114 @@ pub struct CoreDocument pub(crate) properties: T, } +impl CoreDocumentData { + fn check_id_constraints(&self) -> Result<()> { + let max_unique_method_ids = self.verification_method.len() + + self.authentication.len() + + self.assertion_method.len() + + self.key_agreement.len() + + self.capability_delegation.len() + + self.capability_invocation.len(); + + // Value = true => the identifier belongs to an embedded method, false means it belongs to a method reference or a + // general purpose verification method + let mut method_identifiers: HashMap<&DIDUrl, bool> = HashMap::with_capacity(max_unique_method_ids); + + for (id, is_embedded) in self + .authentication + .iter() + .chain(self.assertion_method.iter()) + .chain(self.key_agreement.iter()) + .chain(self.capability_delegation.iter()) + .chain(self.capability_invocation.iter()) + .map(|method_ref| match method_ref { + MethodRef::Embed(_) => (method_ref.id(), true), + MethodRef::Refer(_) => (method_ref.id(), false), + }) + { + if let Some(previous) = method_identifiers.insert(id, is_embedded) { + match previous { + // An embedded method with the same id has previously been encountered + true => { + return Err(Error::InvalidDocument( + "attempted to construct document with a duplicated or aliased embedded method", + None, + )); + } + // A method reference to the identifier has previously been encountered + false => { + if is_embedded { + return Err(Error::InvalidDocument( + "attempted to construct document with an aliased embedded method", + None, + )); + } + } + } + } + } + + for method_id in self.verification_method.iter().map(|method| method.id()) { + if method_identifiers + .insert(method_id, false) + .filter(|value| *value) + .is_some() + { + return Err(Error::InvalidDocument( + "attempted to construct document with a duplicated embedded method", + None, + )); + } + } + + for service_id in self.service.iter().map(|service| service.id()) { + if method_identifiers.contains_key(service_id) { + return Err(Error::InvalidDocument( + "attempted to construct document with a service identifier shared with a verification method", + None, + )); + } + } + + Ok(()) + } +} + +/// A DID Document. +/// +/// [Specification](https://www.w3.org/TR/did-core/#did-document-properties) +#[derive(Clone, Debug, PartialEq, Eq, Deserialize)] +#[rustfmt::skip] +#[serde(try_from = "CoreDocumentData")] +pub struct CoreDocument + where + D: DID + KeyComparable +{ + pub(crate) data: CoreDocumentData, +} + +//Forward serialization to inner +impl Serialize for CoreDocument +where + D: DID + KeyComparable + Serialize, + T: Serialize, + U: Serialize, + V: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.data.serialize(serializer) + } +} + // Workaround for lifetime issues with a mutable reference to self preventing closures from being used. macro_rules! method_ref_mut_helper { ($doc:ident, $method: ident, $query: ident) => { - match $doc.$method.query_mut($query.into())? { + match $doc.data.$method.query_mut($query.into())? { MethodRef::Embed(method) => Some(method), - MethodRef::Refer(ref did) => $doc.verification_method.query_mut(did), + MethodRef::Refer(ref did) => $doc.data.verification_method.query_mut(did), } }; } @@ -97,7 +198,7 @@ where /// Returns a new `CoreDocument` based on the [`DocumentBuilder`] configuration. pub fn from_builder(builder: DocumentBuilder) -> Result { - Ok(Self { + Self::try_from(CoreDocumentData { id: builder.id.ok_or(Error::InvalidDocument("missing id", None))?, controller: Some(builder.controller) .filter(|controllers| !controllers.is_empty()) @@ -142,158 +243,197 @@ where /// Returns a reference to the `CoreDocument` id. pub fn id(&self) -> &D { - &self.id + &self.data.id } /// Returns a mutable reference to the `CoreDocument` id. - pub fn id_mut(&mut self) -> &mut D { - &mut self.id + /// + /// # Warning + /// Incorrect use of this method can lead to broken invariants. + pub fn id_mut_unchecked(&mut self) -> &mut D { + &mut self.data.id } /// Returns a reference to the `CoreDocument` controller. pub fn controller(&self) -> Option<&OneOrSet> { - self.controller.as_ref() + self.data.controller.as_ref() } /// Returns a mutable reference to the `CoreDocument` controller. - pub fn controller_mut(&mut self) -> &mut Option> { - &mut self.controller + /// + /// # Warning + /// Incorrect use of this method can lead to broken invariants. + pub fn controller_mut_unchecked(&mut self) -> &mut Option> { + &mut self.data.controller } /// Returns a reference to the `CoreDocument` alsoKnownAs set. pub fn also_known_as(&self) -> &OrderedSet { - &self.also_known_as + &self.data.also_known_as } /// Returns a mutable reference to the `CoreDocument` alsoKnownAs set. - pub fn also_known_as_mut(&mut self) -> &mut OrderedSet { - &mut self.also_known_as + /// # Warning + /// Incorrect use of this method can lead to broken invariants. + pub fn also_known_as_mut_unchecked(&mut self) -> &mut OrderedSet { + &mut self.data.also_known_as } /// Returns a reference to the `CoreDocument` verificationMethod set. pub fn verification_method(&self) -> &OrderedSet> { - &self.verification_method + &self.data.verification_method } /// Returns a mutable reference to the `CoreDocument` verificationMethod set. - pub fn verification_method_mut(&mut self) -> &mut OrderedSet> { - &mut self.verification_method + /// # Warning + /// Incorrect use of this method can lead to broken invariants. + pub fn verification_method_mut_unchecked(&mut self) -> &mut OrderedSet> { + &mut self.data.verification_method } /// Returns a reference to the `CoreDocument` authentication set. pub fn authentication(&self) -> &OrderedSet> { - &self.authentication + &self.data.authentication } /// Returns a mutable reference to the `CoreDocument` authentication set. - pub fn authentication_mut(&mut self) -> &mut OrderedSet> { - &mut self.authentication + /// # Warning + /// Incorrect use of this method can lead to broken invariants. + pub fn authentication_mut_unchecked(&mut self) -> &mut OrderedSet> { + &mut self.data.authentication } /// Returns a reference to the `CoreDocument` assertionMethod set. pub fn assertion_method(&self) -> &OrderedSet> { - &self.assertion_method + &self.data.assertion_method } /// Returns a mutable reference to the `CoreDocument` assertionMethod set. - pub fn assertion_method_mut(&mut self) -> &mut OrderedSet> { - &mut self.assertion_method + /// # Warning + /// Incorrect use of this method can lead to broken invariants. + pub fn assertion_method_mut_unchecked(&mut self) -> &mut OrderedSet> { + &mut self.data.assertion_method } /// Returns a reference to the `CoreDocument` keyAgreement set. pub fn key_agreement(&self) -> &OrderedSet> { - &self.key_agreement + &self.data.key_agreement } /// Returns a mutable reference to the `CoreDocument` keyAgreement set. - pub fn key_agreement_mut(&mut self) -> &mut OrderedSet> { - &mut self.key_agreement + /// # Warning + /// Incorrect use of this method can lead to broken invariants. + pub fn key_agreement_mut_unchecked(&mut self) -> &mut OrderedSet> { + &mut self.data.key_agreement } /// Returns a reference to the `CoreDocument` capabilityDelegation set. pub fn capability_delegation(&self) -> &OrderedSet> { - &self.capability_delegation + &self.data.capability_delegation } /// Returns a mutable reference to the `CoreDocument` capabilityDelegation set. - pub fn capability_delegation_mut(&mut self) -> &mut OrderedSet> { - &mut self.capability_delegation + /// # Warning + /// Incorrect use of this method can lead to broken invariants. + pub fn capability_delegation_mut_unchecked(&mut self) -> &mut OrderedSet> { + &mut self.data.capability_delegation } /// Returns a reference to the `CoreDocument` capabilityInvocation set. pub fn capability_invocation(&self) -> &OrderedSet> { - &self.capability_invocation + &self.data.capability_invocation } /// Returns a mutable reference to the `CoreDocument` capabilityInvocation set. - pub fn capability_invocation_mut(&mut self) -> &mut OrderedSet> { - &mut self.capability_invocation + /// # Warning + /// Incorrect use of this method can lead to broken invariants. + pub fn capability_invocation_mut_unchecked(&mut self) -> &mut OrderedSet> { + &mut self.data.capability_invocation } /// Returns a reference to the `CoreDocument` service set. pub fn service(&self) -> &OrderedSet> { - &self.service + &self.data.service } /// Returns a mutable reference to the `CoreDocument` service set. - pub fn service_mut(&mut self) -> &mut OrderedSet> { - &mut self.service + /// # Warning + /// Incorrect use of this method can lead to broken invariants. Consider using the safer + /// [`Self::insert_service`](CoreDocument::insert_service())/ [`Self::remove_service`](CoreDocument::remove_service) + /// API(s) instead. + pub fn service_mut_unchecked(&mut self) -> &mut OrderedSet> { + &mut self.data.service } /// Returns a reference to the custom `CoreDocument` properties. pub fn properties(&self) -> &T { - &self.properties + &self.data.properties } /// Returns a mutable reference to the custom `CoreDocument` properties. - pub fn properties_mut(&mut self) -> &mut T { - &mut self.properties + /// # Warning + /// Incorrect use of this method can lead to broken invariants. + pub fn properties_mut_unchecked(&mut self) -> &mut T { + &mut self.data.properties } /// Maps `CoreDocument` to `CoreDocument` by applying a function `f` to all [`DID`] fields /// and another function `g` to the custom properties. - pub fn map(self, mut f: F, g: G) -> CoreDocument + /// + /// # Warning + /// Can lead to broken invariants if used incorrectly. See [`Self::try_map`](CoreDocument::try_map()) for a fallible + /// version with additional built-in checks. + pub fn map_unchecked(self, mut f: F, g: G) -> CoreDocument where C: DID + KeyComparable, F: FnMut(D) -> C, G: FnOnce(T) -> S, { - CoreDocument { - id: f(self.id), - controller: self.controller.map(|controller_set| controller_set.map(&mut f)), - also_known_as: self.also_known_as, - verification_method: self - .verification_method - .into_iter() - .map(|method| method.map(&mut f)) - .collect(), - authentication: self - .authentication - .into_iter() - .map(|method_ref| method_ref.map(&mut f)) - .collect(), - assertion_method: self - .assertion_method - .into_iter() - .map(|method_ref| method_ref.map(&mut f)) - .collect(), - key_agreement: self - .key_agreement - .into_iter() - .map(|method_ref| method_ref.map(&mut f)) - .collect(), - capability_delegation: self - .capability_delegation - .into_iter() - .map(|method_ref| method_ref.map(&mut f)) - .collect(), - capability_invocation: self - .capability_invocation - .into_iter() - .map(|method_ref| method_ref.map(&mut f)) - .collect(), - service: self.service.into_iter().map(|service| service.map(&mut f)).collect(), - properties: g(self.properties), + let current_inner = self.data; + CoreDocument:: { + data: CoreDocumentData { + id: f(current_inner.id), + controller: current_inner + .controller + .map(|controller_set| controller_set.map(&mut f)), + also_known_as: current_inner.also_known_as, + verification_method: current_inner + .verification_method + .into_iter() + .map(|method| method.map(&mut f)) + .collect(), + authentication: current_inner + .authentication + .into_iter() + .map(|method_ref| method_ref.map(&mut f)) + .collect(), + assertion_method: current_inner + .assertion_method + .into_iter() + .map(|method_ref| method_ref.map(&mut f)) + .collect(), + key_agreement: current_inner + .key_agreement + .into_iter() + .map(|method_ref| method_ref.map(&mut f)) + .collect(), + capability_delegation: current_inner + .capability_delegation + .into_iter() + .map(|method_ref| method_ref.map(&mut f)) + .collect(), + capability_invocation: current_inner + .capability_invocation + .into_iter() + .map(|method_ref| method_ref.map(&mut f)) + .collect(), + service: current_inner + .service + .into_iter() + .map(|service| service.map(&mut f)) + .collect(), + properties: g(current_inner.properties), + }, } } @@ -301,85 +441,93 @@ where /// /// # Errors /// - /// `try_map` can fail if either of the provided functions fail. - pub fn try_map(self, mut f: F, g: G) -> Result, E> + /// `try_map` can fail if either of the provided functions fail or if the mapping `f` + /// introduces methods referencing embedded method identifiers, or services with identifiers matching method + /// identifiers. In the case of the latter the provided function `h` will be called to construct a compatible error. + pub fn try_map(self, mut f: F, g: G, h: H) -> std::result::Result, E> where C: DID + KeyComparable, F: FnMut(D) -> Result, G: FnOnce(T) -> Result, + H: FnOnce(crate::error::Error) -> E, + E: std::error::Error, { - Ok(CoreDocument { - id: f(self.id)?, - controller: self - .controller - .map(|controller_set| controller_set.try_map(&mut f)) - .transpose()?, - also_known_as: self.also_known_as, - verification_method: self - .verification_method - .into_iter() - .map(|method| method.try_map(&mut f)) - .collect::>()?, - authentication: self - .authentication - .into_iter() - .map(|method_ref| method_ref.try_map(&mut f)) - .collect::>()?, - assertion_method: self - .assertion_method - .into_iter() - .map(|method_ref| method_ref.try_map(&mut f)) - .collect::>()?, - key_agreement: self - .key_agreement - .into_iter() - .map(|method_ref| method_ref.try_map(&mut f)) - .collect::>()?, - capability_delegation: self - .capability_delegation - .into_iter() - .map(|method_ref| method_ref.try_map(&mut f)) - .collect::>()?, - capability_invocation: self - .capability_invocation - .into_iter() - .map(|method_ref| method_ref.try_map(&mut f)) - .collect::>()?, - service: self - .service - .into_iter() - .map(|service| service.try_map(&mut f)) - .collect::>()?, - properties: g(self.properties)?, - }) + let current_inner = self.data; + let helper = || -> Result, E> { + Ok(CoreDocumentData { + id: f(current_inner.id)?, + controller: current_inner + .controller + .map(|controller_set| controller_set.try_map(&mut f)) + .transpose()?, + also_known_as: current_inner.also_known_as, + verification_method: current_inner + .verification_method + .into_iter() + .map(|method| method.try_map(&mut f)) + .collect::>()?, + authentication: current_inner + .authentication + .into_iter() + .map(|method_ref| method_ref.try_map(&mut f)) + .collect::>()?, + assertion_method: current_inner + .assertion_method + .into_iter() + .map(|method_ref| method_ref.try_map(&mut f)) + .collect::>()?, + key_agreement: current_inner + .key_agreement + .into_iter() + .map(|method_ref| method_ref.try_map(&mut f)) + .collect::>()?, + capability_delegation: current_inner + .capability_delegation + .into_iter() + .map(|method_ref| method_ref.try_map(&mut f)) + .collect::>()?, + capability_invocation: current_inner + .capability_invocation + .into_iter() + .map(|method_ref| method_ref.try_map(&mut f)) + .collect::>()?, + service: current_inner + .service + .into_iter() + .map(|service| service.try_map(&mut f)) + .collect::>()?, + properties: g(current_inner.properties)?, + }) + }; + helper().and_then(|data| CoreDocument::try_from(data).map_err(h)) } /// Adds a new [`VerificationMethod`] to the document in the given [`MethodScope`]. /// /// # Errors /// - /// Returns an error if a method with the same fragment already exists. + /// Returns an error if a method or service with the same fragment already exists. pub fn insert_method(&mut self, method: VerificationMethod, scope: MethodScope) -> Result<()> { - if self.resolve_method(method.id(), None).is_some() { - return Err(Error::MethodAlreadyExists); + // check that the method identifier is not already in use by an existing method or service. + if self.resolve_method(method.id(), None).is_some() || self.service().query(method.id()).is_some() { + return Err(Error::MethodInsertionError); } - match scope { - MethodScope::VerificationMethod => self.verification_method.append(method), + MethodScope::VerificationMethod => self.data.verification_method.append(method), MethodScope::VerificationRelationship(MethodRelationship::Authentication) => { - self.authentication.append(MethodRef::Embed(method)) + self.data.authentication.append(MethodRef::Embed(method)) } MethodScope::VerificationRelationship(MethodRelationship::AssertionMethod) => { - self.assertion_method.append(MethodRef::Embed(method)) + self.data.assertion_method.append(MethodRef::Embed(method)) } MethodScope::VerificationRelationship(MethodRelationship::KeyAgreement) => { - self.key_agreement.append(MethodRef::Embed(method)) + self.data.key_agreement.append(MethodRef::Embed(method)) } MethodScope::VerificationRelationship(MethodRelationship::CapabilityDelegation) => { - self.capability_delegation.append(MethodRef::Embed(method)) + self.data.capability_delegation.append(MethodRef::Embed(method)) } MethodScope::VerificationRelationship(MethodRelationship::CapabilityInvocation) => { - self.capability_invocation.append(MethodRef::Embed(method)) + self.data.capability_invocation.append(MethodRef::Embed(method)) } }; @@ -393,12 +541,12 @@ where /// Returns an error if the method does not exist. pub fn remove_method(&mut self, did: &DIDUrl) -> Result<()> { let was_removed: bool = [ - self.authentication.remove(did), - self.assertion_method.remove(did), - self.key_agreement.remove(did), - self.capability_delegation.remove(did), - self.capability_invocation.remove(did), - self.verification_method.remove(did), + self.data.authentication.remove(did), + self.data.assertion_method.remove(did), + self.data.key_agreement.remove(did), + self.data.capability_delegation.remove(did), + self.data.capability_invocation.remove(did), + self.data.verification_method.remove(did), ] .contains(&true); @@ -409,6 +557,36 @@ where } } + /// Adds a new [`Service`] to the document. + /// + /// # Errors + /// + /// Returns an error if there already exists a service or (verification) method with the same identifier. + pub fn insert_service(&mut self, service: Service) -> Result<()> { + let service_id = service.id(); + let id_shared_with_method = self + .verification_relationships() + .map(|method_ref| method_ref.id()) + .chain(self.verification_method().iter().map(|method| method.id())) + .any(|id| id == service_id); + + ((!id_shared_with_method) && self.service_mut_unchecked().append(service)) + .then_some(()) + .ok_or(Error::InvalidServiceInsertion) + } + + /// Removes a [`Service`] from the document. + /// + /// # Errors + /// + /// Returns an error if the service does not exist. + pub fn remove_service(&mut self, id: &DIDUrl) -> Result<()> { + self + .service_mut_unchecked() + .remove(id) + .then_some(()) + .ok_or(Error::ServiceNotFound) + } /// Attaches the relationship to the method resolved by `method_query`. /// /// # Errors @@ -435,11 +613,11 @@ where let method_ref = MethodRef::Refer(method.id().clone()); let was_attached = match relationship { - MethodRelationship::Authentication => self.authentication_mut().append(method_ref), - MethodRelationship::AssertionMethod => self.assertion_method_mut().append(method_ref), - MethodRelationship::KeyAgreement => self.key_agreement_mut().append(method_ref), - MethodRelationship::CapabilityDelegation => self.capability_delegation_mut().append(method_ref), - MethodRelationship::CapabilityInvocation => self.capability_invocation_mut().append(method_ref), + MethodRelationship::Authentication => self.authentication_mut_unchecked().append(method_ref), + MethodRelationship::AssertionMethod => self.assertion_method_mut_unchecked().append(method_ref), + MethodRelationship::KeyAgreement => self.key_agreement_mut_unchecked().append(method_ref), + MethodRelationship::CapabilityDelegation => self.capability_delegation_mut_unchecked().append(method_ref), + MethodRelationship::CapabilityInvocation => self.capability_invocation_mut_unchecked().append(method_ref), }; Ok(was_attached) @@ -471,11 +649,11 @@ where let did_url: DIDUrl = method.id().clone(); let was_detached = match relationship { - MethodRelationship::Authentication => self.authentication_mut().remove(&did_url), - MethodRelationship::AssertionMethod => self.assertion_method_mut().remove(&did_url), - MethodRelationship::KeyAgreement => self.key_agreement_mut().remove(&did_url), - MethodRelationship::CapabilityDelegation => self.capability_delegation_mut().remove(&did_url), - MethodRelationship::CapabilityInvocation => self.capability_invocation_mut().remove(&did_url), + MethodRelationship::Authentication => self.authentication_mut_unchecked().remove(&did_url), + MethodRelationship::AssertionMethod => self.assertion_method_mut_unchecked().remove(&did_url), + MethodRelationship::KeyAgreement => self.key_agreement_mut_unchecked().remove(&did_url), + MethodRelationship::CapabilityDelegation => self.capability_delegation_mut_unchecked().remove(&did_url), + MethodRelationship::CapabilityInvocation => self.capability_invocation_mut_unchecked().remove(&did_url), }; Ok(was_detached) @@ -539,13 +717,14 @@ where } self + .data .verification_method .iter() - .chain(self.authentication.iter().filter_map(__filter_ref)) - .chain(self.assertion_method.iter().filter_map(__filter_ref)) - .chain(self.key_agreement.iter().filter_map(__filter_ref)) - .chain(self.capability_delegation.iter().filter_map(__filter_ref)) - .chain(self.capability_invocation.iter().filter_map(__filter_ref)) + .chain(self.data.authentication.iter().filter_map(__filter_ref)) + .chain(self.data.assertion_method.iter().filter_map(__filter_ref)) + .chain(self.data.key_agreement.iter().filter_map(__filter_ref)) + .chain(self.data.capability_delegation.iter().filter_map(__filter_ref)) + .chain(self.data.capability_invocation.iter().filter_map(__filter_ref)) } /// Returns an iterator over all verification relationships. @@ -553,12 +732,13 @@ where /// This includes embedded and referenced [`VerificationMethods`](VerificationMethod). pub fn verification_relationships(&self) -> impl Iterator> { self + .data .authentication .iter() - .chain(self.assertion_method.iter()) - .chain(self.key_agreement.iter()) - .chain(self.capability_delegation.iter()) - .chain(self.capability_invocation.iter()) + .chain(self.data.assertion_method.iter()) + .chain(self.data.key_agreement.iter()) + .chain(self.data.capability_delegation.iter()) + .chain(self.data.capability_invocation.iter()) } /// Returns the first [`VerificationMethod`] with an `id` property matching the @@ -576,21 +756,27 @@ where let resolve_ref_helper = |method_ref: &'me MethodRef| self.resolve_method_ref(method_ref); match scope { - MethodScope::VerificationMethod => self.verification_method.query(query.into()), - MethodScope::VerificationRelationship(MethodRelationship::Authentication) => { - self.authentication.query(query.into()).and_then(resolve_ref_helper) - } - MethodScope::VerificationRelationship(MethodRelationship::AssertionMethod) => { - self.assertion_method.query(query.into()).and_then(resolve_ref_helper) - } + MethodScope::VerificationMethod => self.data.verification_method.query(query.into()), + MethodScope::VerificationRelationship(MethodRelationship::Authentication) => self + .data + .authentication + .query(query.into()) + .and_then(resolve_ref_helper), + MethodScope::VerificationRelationship(MethodRelationship::AssertionMethod) => self + .data + .assertion_method + .query(query.into()) + .and_then(resolve_ref_helper), MethodScope::VerificationRelationship(MethodRelationship::KeyAgreement) => { - self.key_agreement.query(query.into()).and_then(resolve_ref_helper) + self.data.key_agreement.query(query.into()).and_then(resolve_ref_helper) } MethodScope::VerificationRelationship(MethodRelationship::CapabilityDelegation) => self + .data .capability_delegation .query(query.into()) .and_then(resolve_ref_helper), MethodScope::VerificationRelationship(MethodRelationship::CapabilityInvocation) => self + .data .capability_invocation .query(query.into()) .and_then(resolve_ref_helper), @@ -602,6 +788,9 @@ where /// Returns a mutable reference to the first [`VerificationMethod`] with an `id` property /// matching the provided `query`. + /// + /// # Warning + /// Incorrect use of this method can lead to broken invariants. pub fn resolve_method_mut<'query, 'me, Q>( &'me mut self, query: Q, @@ -612,7 +801,7 @@ where { match scope { Some(scope) => match scope { - MethodScope::VerificationMethod => self.verification_method.query_mut(query.into()), + MethodScope::VerificationMethod => self.data.verification_method.query_mut(query.into()), MethodScope::VerificationRelationship(MethodRelationship::Authentication) => { method_ref_mut_helper!(self, authentication, query) } @@ -637,7 +826,7 @@ where pub fn resolve_method_ref<'a>(&'a self, method_ref: &'a MethodRef) -> Option<&'a VerificationMethod> { match method_ref { MethodRef::Embed(method) => Some(method), - MethodRef::Refer(did) => self.verification_method.query(did), + MethodRef::Refer(did) => self.data.verification_method.query(did), } } @@ -645,29 +834,29 @@ where let mut method: Option<&MethodRef> = None; if method.is_none() { - method = self.authentication.query(query.clone()); + method = self.data.authentication.query(query.clone()); } if method.is_none() { - method = self.assertion_method.query(query.clone()); + method = self.data.assertion_method.query(query.clone()); } if method.is_none() { - method = self.key_agreement.query(query.clone()); + method = self.data.key_agreement.query(query.clone()); } if method.is_none() { - method = self.capability_delegation.query(query.clone()); + method = self.data.capability_delegation.query(query.clone()); } if method.is_none() { - method = self.capability_invocation.query(query.clone()); + method = self.data.capability_invocation.query(query.clone()); } match method { Some(MethodRef::Embed(method)) => Some(method), - Some(MethodRef::Refer(did)) => self.verification_method.query(&did.to_string()), - None => self.verification_method.query(query), + Some(MethodRef::Refer(did)) => self.data.verification_method.query(&did.to_string()), + None => self.data.verification_method.query(query), } } @@ -675,29 +864,29 @@ where let mut method: Option<&mut MethodRef> = None; if method.is_none() { - method = self.authentication.query_mut(query.clone()); + method = self.data.authentication.query_mut(query.clone()); } if method.is_none() { - method = self.assertion_method.query_mut(query.clone()); + method = self.data.assertion_method.query_mut(query.clone()); } if method.is_none() { - method = self.key_agreement.query_mut(query.clone()); + method = self.data.key_agreement.query_mut(query.clone()); } if method.is_none() { - method = self.capability_delegation.query_mut(query.clone()); + method = self.data.capability_delegation.query_mut(query.clone()); } if method.is_none() { - method = self.capability_invocation.query_mut(query.clone()); + method = self.data.capability_invocation.query_mut(query.clone()); } match method { Some(MethodRef::Embed(method)) => Some(method), - Some(MethodRef::Refer(did)) => self.verification_method.query_mut(&did.to_string()), - None => self.verification_method.query_mut(query), + Some(MethodRef::Refer(did)) => self.data.verification_method.query_mut(&did.to_string()), + None => self.data.verification_method.query_mut(query), } } } @@ -834,6 +1023,19 @@ where } } +impl TryFrom> for CoreDocument +where + D: DID + KeyComparable, +{ + type Error = crate::error::Error; + fn try_from(value: CoreDocumentData) -> Result { + match value.check_id_constraints() { + Ok(_) => Ok(Self { data: value }), + Err(err) => Err(err), + } + } +} + #[cfg(feature = "revocation-bitmap")] mod core_document_revocation { use identity_core::common::KeyComparable; @@ -884,7 +1086,7 @@ mod core_document_revocation { Q: Into>, { let service: &mut Service = self - .service_mut() + .service_mut_unchecked() .query_mut(service_query) .ok_or(Error::InvalidService("invalid id - service not found"))?; @@ -934,6 +1136,11 @@ where #[cfg(test)] mod tests { + use identity_core::convert::FromJson; + use identity_core::convert::ToJson; + + use crate::service::ServiceBuilder; + use crate::verification::MethodBuilder; use crate::verification::MethodData; use super::*; @@ -973,10 +1180,10 @@ mod tests { { let mut document: CoreDocument = document(); let expected: CoreDID = CoreDID::parse("did:example:one1234").unwrap(); - *document.controller_mut() = Some(OneOrSet::new_one(expected.clone())); + *document.controller_mut_unchecked() = Some(OneOrSet::new_one(expected.clone())); assert_eq!(document.controller().unwrap().as_slice(), &[expected]); // Unset. - *document.controller_mut() = None; + *document.controller_mut_unchecked() = None; assert!(document.controller().is_none()); } @@ -988,10 +1195,10 @@ mod tests { CoreDID::parse("did:example:many4567").unwrap(), CoreDID::parse("did:example:many8910").unwrap(), ]; - *document.controller_mut() = Some(expected_controllers.clone().try_into().unwrap()); + *document.controller_mut_unchecked() = Some(expected_controllers.clone().try_into().unwrap()); assert_eq!(document.controller().unwrap().as_slice(), &expected_controllers); // Unset. - *document.controller_mut() = None; + *document.controller_mut_unchecked() = None; assert!(document.controller().is_none()); } } @@ -1300,7 +1507,7 @@ mod tests { for index in indices_1.iter() { bitmap.revoke(*index); } - assert!(document.service_mut().append( + assert!(document.service_mut_unchecked().append( Service::builder(Object::new()) .id(service_id.clone()) .type_(crate::revocation::RevocationBitmap::TYPE) @@ -1333,4 +1540,346 @@ mod tests { assert!(!decoded_bitmap.is_revoked(index)); } } + + #[test] + fn test_service_updates() { + let mut document = document(); + let service_id = document.id().to_url().join("#service-update-test").unwrap(); + let service_type = "test"; + let service_endpoint = Url::parse("https://example.com").unwrap(); + + let service: Service = ServiceBuilder::default() + .id(service_id.clone()) + .type_(service_type) + .service_endpoint(service_endpoint) + .build() + .unwrap(); + // inserting a service with an identifier not present in the document should be Ok. + assert!(document.insert_service(service.clone()).is_ok()); + // inserting a service with the same identifier as an already existing service should fail. + let mut service_clone = service.clone(); + *service_clone.service_endpoint_mut() = Url::parse("https://other-example.com").unwrap().into(); + assert!(document.insert_service(service_clone).is_err()); + // removing an existing service should succeed + assert!(document.remove_service(service.id()).is_ok()); + // it should now be possible to insert the service again + assert!(document.insert_service(service.clone()).is_ok()); + + // inserting a method with the same identifier as an existing service should fail + let method: VerificationMethod = MethodBuilder::default() + .type_(MethodType::Ed25519VerificationKey2018) + .data(MethodData::PublicKeyBase58( + "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J".into(), + )) + .id(service.id().clone()) + .controller(document.id().clone()) + .build() + .unwrap(); + + let method_scopes = [ + MethodScope::VerificationMethod, + MethodScope::assertion_method(), + MethodScope::authentication(), + MethodScope::key_agreement(), + MethodScope::capability_delegation(), + MethodScope::capability_invocation(), + ]; + for scope in method_scopes { + let mut document_clone = document.clone(); + assert!(document_clone.insert_method(method.clone(), scope).is_err()); + // should succeed after removing the service + assert!(document_clone.remove_service(service.id()).is_ok()); + assert!(document_clone.insert_method(method.clone(), scope).is_ok()); + } + + // inserting a service with the same identifier as a method should fail + for scope in method_scopes { + let mut doc_clone = document.clone(); + let valid_method_id = document.id().to_url().join("#valid-method-identifier").unwrap(); + let mut valid_method = method.clone(); + valid_method.set_id(valid_method_id.clone()).unwrap(); + // make sure that the method actually gets inserted + assert!(doc_clone.insert_method(valid_method.clone(), scope).is_ok()); + let mut service_clone = service.clone(); + service_clone.set_id(valid_method_id).unwrap(); + assert!(doc_clone.insert_service(service_clone.clone()).is_err()); + // but should work after the method has been removed + assert!(doc_clone.remove_method(valid_method.id()).is_ok()); + assert!(doc_clone.insert_service(service_clone).is_ok()); + } + + //removing a service that does not exist should fail + assert!(document + .remove_service(&service.id().join("#service-does-not-exist").unwrap()) + .is_err()); + } + + #[test] + fn serialize_deserialize_roundtrip() { + let document: CoreDocument = document(); + let doc_json: String = document.to_json().unwrap(); + let doc_json_value: serde_json::Value = document.to_json_value().unwrap(); + let doc_json_vec: Vec = document.to_json_vec().unwrap(); + assert_eq!(document, CoreDocument::from_json(&doc_json).unwrap()); + assert_eq!(document, CoreDocument::from_json_value(doc_json_value).unwrap()); + + assert_eq!(document, CoreDocument::from_json_slice(&doc_json_vec).unwrap()); + } + + #[test] + fn deserialize_valid() { + // The verification method types here are really Ed25519VerificationKey2020, changed to be compatible + // with the current version of this library. + const JSON_DOCUMENT: &str = r#"{ + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "did:example:123", + "authentication": [ + { + "id": "did:example:123#z6MkecaLyHuYWkayBDLw5ihndj3T1m6zKTGqau3A51G7RBf3", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "zAKJP3f7BD6W4iWEQ9jwndVTCBq8ua2Utt8EEjJ6Vxsf" + } + ], + "capabilityInvocation": [ + { + "id": "did:example:123#z6MkhdmzFu659ZJ4XKj31vtEDmjvsi5yDZG5L7Caz63oP39k", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "z4BWwfeqdp1obQptLLMvPNgBw48p7og1ie6Hf9p5nTpNN" + } + ], + "capabilityDelegation": [ + { + "id": "did:example:123#z6Mkw94ByR26zMSkNdCUi6FNRsWnc2DFEeDXyBGJ5KTzSWyi", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "zHgo9PAmfeoxHG8Mn2XHXamxnnSwPpkyBHAMNF3VyXJCL" + } + ], + "assertionMethod": [ + { + "id": "did:example:123#z6MkiukuAuQAE8ozxvmahnQGzApvtW7KT5XXKfojjwbdEomY", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:123", + "publicKeyMultibase": "z5TVraf9itbKXrRvt2DSS95Gw4vqU3CHAdetoufdcKazA" + } + ] + }"#; + let doc: std::result::Result> = + CoreDocument::from_json(JSON_DOCUMENT).map_err(Into::into); + // Print debug representation if the test fails. + dbg!(&doc); + assert!(doc.is_ok()); + } + + #[test] + fn deserialize_duplicate_method_different_scopes() { + const JSON_VERIFICATION_METHOD_KEY_AGREEMENT: &str = r#"{ + "id": "did:example:1234", + "verificationMethod": [ + { + "id": "did:example:1234#key1", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" + } + ], + "keyAgreement": [ + { + "id": "did:example:1234#key1", + "controller": "did:example:1234", + "type": "X25519KeyAgreementKey2019", + "publicKeyBase58": "FbQWLPRhTH95MCkQUeFYdiSoQt8zMwetqfWoxqPgaq7x" + } + ] + }"#; + + const JSON_KEY_AGREEMENT_CAPABILITY_INVOCATION: &str = r#"{ + "id": "did:example:1234", + "capabilityInvocation": [ + { + "id": "did:example:1234#key1", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" + } + ], + "keyAgreement": [ + { + "id": "did:example:1234#key1", + "controller": "did:example:1234", + "type": "X25519KeyAgreementKey2019", + "publicKeyBase58": "FbQWLPRhTH95MCkQUeFYdiSoQt8zMwetqfWoxqPgaq7x" + } + ] + }"#; + + const JSON_ASSERTION_METHOD_CAPABILITY_INVOCATION: &str = r#"{ + "id": "did:example:1234", + "assertionMethod": [ + { + "id": "did:example:1234#key1", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" + } + ], + "capabilityInvocation": [ + { + "id": "did:example:1234#key1", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" + } + ] + }"#; + + const JSON_VERIFICATION_METHOD_AUTHENTICATION: &str = r#"{ + "id": "did:example:1234", + "verificationMethod": [ + { + "id": "did:example:1234#key1", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" + } + ], + "authentication": [ + { + "id": "did:example:1234#key1", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" + } + ] + }"#; + + const JSON_CAPABILITY_DELEGATION_ASSERTION_METHOD: &str = r#"{ + "id": "did:example:1234", + "capabilityDelegation": [ + { + "id": "did:example:1234#key1", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" + } + ], + "assertionMethod": [ + { + "id": "did:example:1234#key1", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" + } + ] + }"#; + + let verifier = |json: &str| { + let result: std::result::Result> = + CoreDocument::from_json(json).map_err(Into::into); + // Print the json if the test fails to aid debugging. + println!("the following non-spec compliant document was deserialized: \n {json}"); + assert!(result.is_err()); + }; + + for json in [ + JSON_VERIFICATION_METHOD_KEY_AGREEMENT, + JSON_KEY_AGREEMENT_CAPABILITY_INVOCATION, + JSON_ASSERTION_METHOD_CAPABILITY_INVOCATION, + JSON_VERIFICATION_METHOD_AUTHENTICATION, + JSON_CAPABILITY_DELEGATION_ASSERTION_METHOD, + ] { + verifier(json); + } + } + + #[test] + fn deserialize_invalid_id_references() { + const JSON_KEY_AGREEMENT_CAPABILITY_INVOCATION: &str = r#"{ + "id": "did:example:1234", + "capabilityInvocation": [ + "did:example:1234#key1" + ], + "keyAgreement": [ + { + "id": "did:example:1234#key1", + "controller": "did:example:1234", + "type": "X25519KeyAgreementKey2019", + "publicKeyBase58": "FbQWLPRhTH95MCkQUeFYdiSoQt8zMwetqfWoxqPgaq7x" + } + ] + }"#; + + const JSON_ASSERTION_METHOD_CAPABILITY_INVOCATION: &str = r#"{ + "id": "did:example:1234", + "assertionMethod": [ + "did:example:1234#key1", + { + "id": "did:example:1234#key2", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" + } + ], + "capabilityInvocation": [ + { + "id": "did:example:1234#key1", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" + } + ] + }"#; + + const JSON_AUTHENTICATION_KEY_AGREEMENT: &str = r#"{ + "id": "did:example:1234", + "keyAgreement": [ + "did:example:1234#key1" + ], + "authentication": [ + { + "id": "did:example:1234#key1", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyMultibase": "zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" + } + ] + }"#; + + const JSON_CAPABILITY_DELEGATION_ASSERTION_METHOD: &str = r#"{ + "id": "did:example:1234", + "capabilityDelegation": [ + "did:example:1234#key1" + ], + "assertionMethod": [ + { + "id": "did:example:1234#key1", + "controller": "did:example:1234", + "type": "X25519KeyAgreementKey2019", + "publicKeyBase58": "FbQWLPRhTH95MCkQUeFYdiSoQt8zMwetqfWoxqPgaq7x" + } + ] + }"#; + + let verifier = |json: &str| { + let result: std::result::Result> = + CoreDocument::from_json(json).map_err(Into::into); + // Print the json if the test fails to aid debugging. + println!("the following non-spec compliant document was deserialized: \n {json}"); + assert!(result.is_err()); + }; + + for json in [ + JSON_KEY_AGREEMENT_CAPABILITY_INVOCATION, + JSON_ASSERTION_METHOD_CAPABILITY_INVOCATION, + JSON_AUTHENTICATION_KEY_AGREEMENT, + JSON_CAPABILITY_DELEGATION_ASSERTION_METHOD, + ] { + verifier(json); + } + } } diff --git a/identity_did/src/document/mod.rs b/identity_did/src/document/mod.rs index c3de65a652..5571f327e1 100644 --- a/identity_did/src/document/mod.rs +++ b/identity_did/src/document/mod.rs @@ -9,6 +9,7 @@ pub use self::builder::DocumentBuilder; pub use self::core_document::CoreDocument; pub use self::traits::Document; +pub(crate) use core_document::CoreDocumentData; mod builder; mod core_document; mod traits; diff --git a/identity_did/src/error.rs b/identity_did/src/error.rs index 076f199621..46cc0dcd93 100644 --- a/identity_did/src/error.rs +++ b/identity_did/src/error.rs @@ -33,14 +33,23 @@ pub enum Error { MissingIdFragment, #[error("Invalid Verification Method Type")] InvalidMethodType, - /// Caused by attempting to add a verification method to a document, where a method with the same fragment already - /// exists. - #[error("verification method already exists")] - MethodAlreadyExists, + /// Caused by attempting to add a verification method to a document, where a method or service with the same fragment + /// already exists. + #[error("unable to insert method: the id is already in use")] + MethodInsertionError, /// Caused by attempting to attach or detach a relationship on an embedded method. #[error("unable to modify relationships on embedded methods, use insert or remove instead")] InvalidMethodEmbedded, + /// Caused by attempting to insert a service whose id overlaps with a (verification) method or an already existing + /// service. + #[error("unable to insert service: the id is already in use")] + InvalidServiceInsertion, + + /// Caused by attempting to remove a service that cannot be found in the document. + #[error("service not found")] + ServiceNotFound, + #[error("Unknown Method Scope")] UnknownMethodScope, #[error("Unknown Method Type")] diff --git a/identity_iota_core/src/document/iota_document.rs b/identity_iota_core/src/document/iota_document.rs index f4932b628a..24c2407743 100644 --- a/identity_iota_core/src/document/iota_document.rs +++ b/identity_iota_core/src/document/iota_document.rs @@ -105,7 +105,7 @@ impl IotaDocument { /// Returns a mutable reference to the `alsoKnownAs` set. pub fn also_known_as_mut(&mut self) -> &mut OrderedSet { - self.document.also_known_as_mut() + self.document.also_known_as_mut_unchecked() } /// Returns a reference to the underlying [`IotaCoreDocument`]. @@ -128,7 +128,7 @@ impl IotaDocument { /// Returns a mutable reference to the custom DID Document properties. pub fn properties_mut(&mut self) -> &mut Object { - self.document.properties_mut() + self.document.properties_mut_unchecked() } // =========================================================================== @@ -147,7 +147,7 @@ impl IotaDocument { if service.id().fragment().is_none() { false } else { - self.core_document_mut().service_mut().append(service) + self.core_document_mut().service_mut_unchecked().append(service) } } @@ -155,7 +155,7 @@ impl IotaDocument { /// /// Returns `true` if a service was removed. pub fn remove_service(&mut self, did_url: &IotaDIDUrl) -> bool { - self.core_document_mut().service_mut().remove(did_url) + self.core_document_mut().service_mut_unchecked().remove(did_url) } // =========================================================================== @@ -334,7 +334,7 @@ mod client_document { Address::Alias(alias_address) => Some(IotaDID::new(alias_address.alias_id(), network_name)), _ => None, }; - *self.core_document_mut().controller_mut() = controller_did.map(OneOrSet::new_one); + *self.core_document_mut().controller_mut_unchecked() = controller_did.map(OneOrSet::new_one); } /// Returns all DID documents of the Alias Outputs contained in the block's transaction payload @@ -453,7 +453,7 @@ impl From for IotaCoreDocument { impl From for CoreDocument { fn from(document: IotaDocument) -> Self { - document.document.map(Into::into, |id| id) + document.document.map_unchecked(Into::into, |id| id) } } diff --git a/identity_iota_core/src/error.rs b/identity_iota_core/src/error.rs index ef1db89e3c..696c051266 100644 --- a/identity_iota_core/src/error.rs +++ b/identity_iota_core/src/error.rs @@ -11,7 +11,7 @@ pub enum Error { #[error("{0}")] DIDSyntaxError(#[from] identity_did::did::DIDError), #[error("{0}")] - InvalidDoc(#[from] identity_did::Error), + InvalidDoc(#[from] identity_did::error::Error), #[cfg(feature = "iota-client")] #[error("DID update: {0}")] DIDUpdateError(&'static str, #[source] Option>), diff --git a/identity_iota_core/src/state_metadata/document.rs b/identity_iota_core/src/state_metadata/document.rs index b0e2c66f37..76113a5799 100644 --- a/identity_iota_core/src/state_metadata/document.rs +++ b/identity_iota_core/src/state_metadata/document.rs @@ -1,6 +1,7 @@ // Copyright 2020-2022 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_did::did::CoreDID; @@ -47,12 +48,14 @@ impl StateMetadataDocument { Ok(original_did.clone()) } else { // TODO: wrap error? - IotaDID::try_from_core(did) + IotaDID::try_from_core(did).map_err(crate::error::Error::DIDSyntaxError) } }, // Do not modify properties. - Ok, + Result::::Ok, + crate::error::Error::InvalidDoc, )?; + Ok(IotaDocument::from((core_document, metadata))) } @@ -62,7 +65,7 @@ impl StateMetadataDocument { // Unset Governor and State Controller Addresses to avoid bloating the payload self.metadata.governor_address = None; self.metadata.state_controller_address = None; - *self.document.controller_mut() = None; + *self.document.controller_mut_unchecked() = None; let encoded_message_data: Vec = match encoding { StateMetadataEncoding::Json => self @@ -155,7 +158,7 @@ impl From for StateMetadataDocument { fn from(document: IotaDocument) -> Self { let IotaDocument { document, metadata } = document; let id: IotaDID = document.id().clone(); - let core_document: CoreDocument = document.map( + let core_document: CoreDocument = document.map_unchecked( // Replace self-referential identifiers with a placeholder, but not others. |did| { if did == id { @@ -255,7 +258,7 @@ mod tests { .append(Url::parse("did:example:xyz").unwrap()); let controllers = OneOrSet::try_from(vec![did_foreign.clone(), did_self.clone()]).unwrap(); - *document.core_document_mut().controller_mut() = Some(controllers); + *document.core_document_mut().controller_mut_unchecked() = Some(controllers); TestSetup { document, @@ -381,7 +384,7 @@ mod tests { let mut state_metadata_doc: StateMetadataDocument = StateMetadataDocument::from(document); let packed: Vec = state_metadata_doc.clone().pack(StateMetadataEncoding::Json).unwrap(); // Controller and State Controller are set to None when packing - *state_metadata_doc.document.controller_mut() = None; + *state_metadata_doc.document.controller_mut_unchecked() = None; state_metadata_doc.metadata.governor_address = None; state_metadata_doc.metadata.state_controller_address = None; let expected_payload: String = format!(