1use super::*;
2
3use crate::data::auth::Authentication;
4use crate::data::{
5 did::Did,
6 validation::{ValidationIssueSeverity as Severity, ValidationResult},
7};
8
9#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)]
10pub enum Right {
11 #[serde(rename = "update")]
12 Update,
13 #[serde(rename = "impersonate")]
14 Impersonation,
15}
16
17impl Display for Right {
18 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
19 let value =
20 serde_json::to_value(self).expect("Implementation error: Right is not serializable");
21 match value {
22 serde_json::Value::String(s) => write!(f, "{}", s),
23 _ => panic!("Implementation error: unexpected Right serialization"),
24 }
25 }
26}
27
28impl FromStr for Right {
29 type Err = anyhow::Error;
30 fn from_str(s: &str) -> Result<Self, Self::Err> {
31 Ok(serde_json::from_value(serde_json::Value::String(s.to_owned()))?)
32 }
33}
34
35impl Right {
36 pub fn map_all<T>(f: impl Fn(&Right) -> T) -> HashMap<Right, T> {
37 vec![Self::Update, Self::Impersonation].drain(..).map(|r| (r, f(&r))).collect()
38 }
39}
40
41pub fn is_in_opt_range(
42 height: BlockHeight, from_inc: Option<BlockHeight>, until_exc: Option<BlockHeight>,
43) -> bool {
44 if let Some(from) = from_inc {
45 if height < from {
46 return false;
47 }
48 }
49 if let Some(until) = until_exc {
50 if until <= height {
51 return false;
52 }
53 }
54 true
55}
56
57pub fn is_between(height: BlockHeight, after: BlockHeight, until_exc: BlockHeight) -> bool {
58 is_in_opt_range(height, Some(after + 1), Some(until_exc))
59}
60
61#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
62pub struct KeyState {
63 #[serde(rename = "auth")]
64 pub authentication: Authentication,
65 #[serde(rename = "validFromHeight")]
66 pub valid_from_block: Option<BlockHeight>, #[serde(rename = "validUntilHeight")]
68 pub valid_until_block: Option<BlockHeight>, }
70
71#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
72pub struct KeyDataDerived {
73 pub index: usize,
74 pub valid: bool,
75}
76
77#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
78pub struct KeyData {
79 #[serde(flatten)]
80 pub state: KeyState,
81 #[serde(flatten)]
82 pub derived: KeyDataDerived,
83}
84
85impl KeyData {
86 fn from_auth(authentication: Authentication) -> Self {
87 let state = KeyState { authentication, valid_from_block: None, valid_until_block: None };
88 let derived = KeyDataDerived { index: 0, valid: true };
89 Self { state, derived }
90 }
91
92 fn is_valid_at(&self, height: BlockHeight) -> bool {
93 is_in_opt_range(height, self.state.valid_from_block, self.state.valid_until_block)
94 }
95}
96
97#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)]
98pub struct KeyRightHistoryItem {
99 pub height: Option<BlockHeight>,
100 pub valid: bool,
101}
102
103#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)]
104pub struct KeyRightState {
105 pub history: Vec<KeyRightHistoryItem>,
106}
107
108#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)]
109pub struct KeyRightDerived {
110 #[serde(rename = "keyLink")]
111 pub key_link: String, pub valid: bool,
113}
114
115#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)]
116pub struct KeyRightHistory {
117 #[serde(flatten)]
118 pub state: KeyRightState,
119 #[serde(flatten)]
120 pub derived: KeyRightDerived,
121}
122
123impl KeyRightHistory {
124 fn ensure_valid_history(&self) -> Result<()> {
125 let heights: Vec<_> =
126 self.state.history.iter().map(|item| item.height.unwrap_or_default()).collect();
127 let mut sorted = heights.clone();
128 sorted.sort_unstable(); ensure!(heights == sorted, "Height of key history items must be strictly increasing");
130 Ok(())
131 }
132
133 fn is_true_at(&self, height: BlockHeight) -> Result<bool> {
134 self.ensure_valid_history()?;
136
137 let last_state_before_height =
138 self.state.history.iter().rev().find(|item| item.height.unwrap_or_default() <= height);
139 let valid = match last_state_before_height {
140 None => false,
141 Some(item) => item.valid,
142 };
143 Ok(valid)
144 }
145}
146
147#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)]
148pub enum ServiceType {
149 }
151
152#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)]
153pub struct Service {
154 #[serde(rename = "type")]
155 pub type_: ServiceType,
156 pub name: String,
157 pub service_endpoint: String, }
159
160#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
161pub struct DidDocument {
162 #[serde(with = "serde_str")]
163 pub did: Did,
164 pub keys: Vec<KeyData>,
165 #[serde(skip_serializing_if = "HashMap::is_empty", default)]
166 pub rights: HashMap<Right, Vec<KeyRightHistory>>,
167 #[serde(skip_serializing_if = "Vec::is_empty", default)]
168 pub services: Vec<Service>,
169 #[serde(rename = "tombstonedAtHeight")]
170 pub tombstoned_at_height: Option<BlockHeight>,
171 pub tombstoned: bool,
172 #[serde(rename = "queriedAtHeight")]
173 pub queried_at_height: BlockHeight,
174}
175
176impl DidDocument {
177 pub fn implicit(did: &Did) -> Self {
178 let default_key = KeyData::from_auth(Authentication::KeyId(did.default_key_id()));
179 Self {
180 did: did.to_owned(),
181 keys: vec![default_key],
182 rights: Default::default(),
183 services: Default::default(),
184 tombstoned_at_height: Default::default(),
185 tombstoned: Default::default(),
186 queried_at_height: Default::default(),
187 }
188 }
189
190 fn key(&self, key_link: &str) -> Result<KeyData> {
191 ensure!(key_link.starts_with('#'), "Key links for remote DIDs are not supported yet");
192 let idx_str: String = key_link.chars().skip(1).collect();
193 let idx: usize = idx_str.parse()?;
194 let key =
195 self.keys.get(idx).ok_or_else(|| anyhow!("No key found for link {}", key_link))?;
196 Ok(key.to_owned())
197 }
198
199 fn ensure_known_height(&self, height: BlockHeight) -> Result<()> {
200 if self.queried_at_height < height {
201 bail!("Queried future height {}, present is {}", height, self.queried_at_height);
202 }
203 Ok(())
204 }
205
206 pub fn has_right_at(
207 &self, auth: &Authentication, right: Right, height: BlockHeight,
208 ) -> Result<bool> {
209 self.ensure_known_height(height)?;
210
211 if let Some(tombstoned_at_height) = self.tombstoned_at_height {
212 if tombstoned_at_height <= height {
213 return Ok(false);
214 }
215 }
216 let keys_with_right = match self.rights.get(&right) {
217 Some(key) => key,
218 None => return Ok(false),
219 };
220
221 for key_right in keys_with_right.iter() {
222 let key = self.key(&key_right.derived.key_link)?;
223 if !key.is_valid_at(height) {
224 continue;
225 }
226 if key.state.authentication != *auth {
227 continue;
228 }
229
230 return key_right.is_true_at(height);
231 }
232
233 Ok(false)
234 }
235
236 pub fn is_tombstoned_at(&self, height: BlockHeight) -> Result<bool> {
237 self.ensure_known_height(height)?;
238
239 if let Some(tombstone_height) = self.tombstoned_at_height {
240 return Ok(tombstone_height <= height);
241 }
242
243 Ok(false)
244 }
245
246 pub fn validate_right(
248 &self, auth: &Authentication, right: Right, from: BlockHeight, until: BlockHeight,
249 ) -> Result<ValidationResult> {
250 ensure!(1 <= from, "Range must not predate genesis block");
251 ensure!(from < until, "Invalid block range {}-{}", from, until);
252 self.ensure_known_height(until)?;
253
254 let mut result: ValidationResult = Default::default();
255
256 if self.is_tombstoned_at(from)? {
257 result.add_issue(Severity::Error, "DID was tombstoned before given period");
258 }
259 if let Some(tombstone_height) = self.tombstoned_at_height {
260 if is_between(tombstone_height, from, until) {
261 result.add_issue(Severity::Warning, "DID was tombstoned during given period");
262 }
263 }
264
265 let keys_with_right = match self.rights.get(&right) {
266 Some(entries) => entries,
267 None => {
268 result
269 .add_issue(Severity::Error, "Right was never granted to given authentication");
270 return Ok(result);
271 }
272 };
273
274 let key_history_opt = keys_with_right.iter().find_map(|right_entry| {
275 let key_data = match self.key(&right_entry.derived.key_link) {
276 Ok(key_entry) => key_entry,
277 Err(e) => {
278 result.add_issue(Severity::Error, &e.to_string());
280 return None;
281 }
282 };
283 if key_data.state.authentication != *auth {
284 return None;
285 }
286 Some((key_data, right_entry))
287 });
288
289 let (key_data, key_right) = match key_history_opt {
290 Some(key_history) => key_history,
291 None => {
292 result.add_issue(Severity::Error, "No matching authentication found in DID");
293 return Ok(result);
294 }
295 };
296
297 if let Some(key_valid_from) = key_data.state.valid_from_block {
298 if until < key_valid_from {
299 result.add_issue(Severity::Error, "Key was enabled only after given period");
300 }
301 if is_between(key_valid_from, from, until) {
302 result.add_issue(Severity::Warning, "Key was enabled during given period");
303 }
304 }
305
306 if let Some(key_valid_until) = key_data.state.valid_until_block {
307 if key_valid_until < from {
308 result.add_issue(Severity::Error, "Key expired before given period");
309 }
310 if is_between(key_valid_until, from, until) {
311 result.add_issue(Severity::Warning, "Key expired during given period");
312 }
313 }
314
315 let history = &key_right.state.history;
316 ensure!(! history.is_empty(), "Implementation error: key related to rights were already filtered, right must be present here");
317
318 let mut right_changes_in_range =
319 history.iter().filter(|item| is_between(item.height.unwrap_or_default(), from, until));
320
321 if !key_right.is_true_at(from)? {
322 if right_changes_in_range.next().is_none() {
323 result.add_issue(Severity::Error, "Required right was never granted for key");
324 } else {
325 result.add_issue(Severity::Warning, "Required right changed during given period");
326 }
327 }
328
329 Ok(result)
330 }
331}
332
333#[cfg(test)]
334mod test {
335 use super::*;
336 use crate::data::validation::ValidationStatus;
337
338 #[test]
339 fn pretty_json() -> Result<()> {
340 test_parsed_did_document(
341 r##"{
342 "did": "did:morpheus:ezbeWGSY2dqcUBqT8K7R14xr",
343 "keys": [
344 {
345 "index": 0,
346 "auth": "iezbeWGSY2dqcUBqT8K7R14xr",
347 "valid": true
348 },
349 {
350 "index": 1,
351 "auth": "iez25N5WZ1Q6TQpgpyYgiu9gTX",
352 "valid": true,
353 "validFromHeight": 120
354 }
355 ],
356 "rights": {
357 "impersonate": [
358 {
359 "keyLink": "#0",
360 "history": [
361 {
362 "height": null,
363 "valid": true
364 }
365 ],
366 "valid": true
367 },
368 {
369 "keyLink": "#1",
370 "history": [
371 {
372 "height": null,
373 "valid": false
374 },
375 {
376 "height": 126,
377 "valid": true
378 }
379 ],
380 "valid": true
381 }
382 ],
383 "update": [
384 {
385 "keyLink": "#0",
386 "history": [
387 {
388 "height": null,
389 "valid": true
390 }
391 ],
392 "valid": true
393 },
394 {
395 "keyLink": "#1",
396 "history": [
397 {
398 "height": null,
399 "valid": false
400 }
401 ],
402 "valid": false
403 }
404 ]
405 },
406 "tombstonedAtHeight": null,
407 "tombstoned": false,
408 "queriedAtHeight": 126
409 }"##,
410 )
411 }
412
413 #[test]
414 fn terse_json() -> Result<()> {
415 test_parsed_did_document(
416 r##"{"did":"did:morpheus:ezbeWGSY2dqcUBqT8K7R14xr","keys":[{"index":0,"auth":"iezbeWGSY2dqcUBqT8K7R14xr","validFromHeight":null,"validUntilHeight":null,"valid":true},{"index":1,"auth":"iez25N5WZ1Q6TQpgpyYgiu9gTX","validFromHeight":120,"validUntilHeight":null,"valid":true}],"rights":{"impersonate":[{"keyLink":"#0","history":[{"height":null,"valid":true}],"valid":true},{"keyLink":"#1","history":[{"height":null,"valid":false},{"height":126,"valid":true}],"valid":true}],"update":[{"keyLink":"#0","history":[{"height":null,"valid":true}],"valid":true},{"keyLink":"#1","history":[{"height":null,"valid":false}],"valid":false}]},"tombstoned":false,"tombstonedAtHeight":null,"queriedAtHeight":126}"##,
417 )
418 }
419
420 fn test_parsed_did_document(s: &str) -> Result<()> {
421 let doc: DidDocument = serde_json::from_str(s)?;
422
423 assert_eq!(doc.did, "did:morpheus:ezbeWGSY2dqcUBqT8K7R14xr".parse()?);
424 assert_eq!(doc.tombstoned_at_height, None);
425 assert_eq!(doc.queried_at_height, 126);
426 assert_eq!(doc.tombstoned, false);
427
428 let first_key = &doc.keys[0].state.authentication;
429 let second_key = &doc.keys[1].state.authentication;
430
431 assert!(doc.has_right_at(first_key, Right::Impersonation, 1)?);
432 assert!(doc.has_right_at(first_key, Right::Impersonation, 2)?);
433 assert!(doc.has_right_at(first_key, Right::Impersonation, 125)?);
434 assert!(doc.has_right_at(first_key, Right::Impersonation, 126)?);
435 assert!(doc.has_right_at(first_key, Right::Impersonation, 127).is_err());
436
437 assert!(!doc.has_right_at(second_key, Right::Impersonation, 1)?);
438 assert!(!doc.has_right_at(second_key, Right::Impersonation, 2)?);
439 assert!(!doc.has_right_at(second_key, Right::Impersonation, 125)?);
440 assert!(doc.has_right_at(second_key, Right::Impersonation, 126)?);
441 assert!(doc.has_right_at(second_key, Right::Impersonation, 127).is_err());
442
443 assert!(doc.has_right_at(first_key, Right::Update, 1)?);
444 assert!(doc.has_right_at(first_key, Right::Update, 2)?);
445 assert!(doc.has_right_at(first_key, Right::Update, 125)?);
446 assert!(doc.has_right_at(first_key, Right::Update, 126)?);
447 assert!(doc.has_right_at(first_key, Right::Update, 127).is_err());
448
449 assert!(!doc.has_right_at(second_key, Right::Update, 1)?);
450 assert!(!doc.has_right_at(second_key, Right::Update, 2)?);
451 assert!(!doc.has_right_at(second_key, Right::Update, 125)?);
452 assert!(!doc.has_right_at(second_key, Right::Update, 126)?);
453 assert!(doc.has_right_at(second_key, Right::Update, 127).is_err());
454
455 Ok(())
456 }
457
458 #[test]
459 #[allow(clippy::cognitive_complexity)]
460 fn has_right_between() -> Result<()> {
461 let did_doc_str = r##"{
462 "did": "did:morpheus:ezbeWGSY2dqcUBqT8K7R14xr",
463 "keys": [
464 {
465 "index": 0,
466 "auth": "iezbeWGSY2dqcUBqT8K7R14xr",
467 "valid": true
468 },
469 {
470 "index": 1,
471 "auth": "iez25N5WZ1Q6TQpgpyYgiu9gTX",
472 "valid": true,
473 "validFromHeight": 10,
474 "validUntilHeight": 90
475 }
476 ],
477 "rights": {
478 "impersonate": [
479 {
480 "keyLink": "#0",
481 "history": [
482 { "height": null, "valid": true }
483 ],
484 "valid": true
485 },
486 {
487 "keyLink": "#1",
488 "history": [
489 { "height": null, "valid": false },
490 { "height": 20, "valid": true },
491 { "height": 80, "valid": false }
492 ],
493 "valid": false
494 }
495 ],
496 "update": [
497 {
498 "keyLink": "#0",
499 "history": [
500 { "height": null, "valid": true }
501 ],
502 "valid": true
503 },
504 {
505 "keyLink": "#1",
506 "history": [
507 { "height": null, "valid": false },
508 { "height": 90, "valid": true }
509 ],
510 "valid": true
511 }
512 ]
513 },
514 "tombstonedAtHeight": 100,
515 "tombstoned": true,
516 "queriedAtHeight": 200
517 }"##;
518
519 let doc: DidDocument = serde_json::from_str(did_doc_str)?;
520
521 assert_eq!(doc.did, "did:morpheus:ezbeWGSY2dqcUBqT8K7R14xr".parse()?);
522 assert_eq!(doc.tombstoned_at_height, Some(100));
523 assert_eq!(doc.queried_at_height, 200);
524 assert_eq!(doc.tombstoned, true);
525
526 let first_key = &doc.keys[0].state.authentication;
527 let second_key = &doc.keys[1].state.authentication;
528 assert_eq!(*first_key, Authentication::KeyId("iezbeWGSY2dqcUBqT8K7R14xr".parse()?));
529 assert_eq!(*second_key, Authentication::KeyId("iez25N5WZ1Q6TQpgpyYgiu9gTX".parse()?));
530
531 use Right::*;
532 use ValidationStatus::*;
533
534 assert_eq!(doc.validate_right(first_key, Impersonation, 1, 100)?.status(), Valid);
535 assert_eq!(doc.validate_right(first_key, Update, 1, 100)?.status(), Valid);
536 assert_eq!(doc.validate_right(first_key, Impersonation, 10, 90)?.status(), Valid);
537 assert_eq!(doc.validate_right(first_key, Update, 10, 90)?.status(), Valid);
538 assert_eq!(doc.validate_right(first_key, Impersonation, 101, 200)?.status(), Invalid);
539 assert_eq!(doc.validate_right(first_key, Update, 101, 200)?.status(), Invalid);
540 assert_eq!(doc.validate_right(first_key, Impersonation, 1, 200)?.status(), MaybeValid);
541 assert_eq!(doc.validate_right(first_key, Update, 1, 200)?.status(), MaybeValid);
542 assert_eq!(doc.validate_right(first_key, Impersonation, 50, 150)?.status(), MaybeValid);
543 assert_eq!(doc.validate_right(first_key, Update, 50, 150)?.status(), MaybeValid);
544
545 assert_eq!(doc.validate_right(second_key, Impersonation, 20, 80)?.status(), Valid);
546 assert_eq!(doc.validate_right(second_key, Impersonation, 30, 70)?.status(), Valid);
547 assert_eq!(doc.validate_right(second_key, Impersonation, 1, 80)?.status(), MaybeValid);
548 assert_eq!(doc.validate_right(second_key, Impersonation, 20, 200)?.status(), MaybeValid);
549 assert_eq!(doc.validate_right(second_key, Impersonation, 1, 20)?.status(), Invalid);
550 assert_eq!(doc.validate_right(second_key, Impersonation, 80, 200)?.status(), Invalid);
551
552 assert_eq!(doc.validate_right(second_key, Update, 90, 100)?.status(), Valid);
553 assert_eq!(doc.validate_right(second_key, Update, 1, 90)?.status(), Invalid);
554 assert_eq!(doc.validate_right(second_key, Update, 100, 200)?.status(), Invalid);
555 assert_eq!(doc.validate_right(second_key, Update, 80, 110)?.status(), MaybeValid);
556
557 Ok(())
558 }
559}