identity_credential/revocation/revocation_bitmap_2022/
document_ext.rs

1// Copyright 2020-2023 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use super::RevocationBitmap;
5use identity_document::document::CoreDocument;
6use identity_document::service::Service;
7use identity_document::utils::DIDUrlQuery;
8use identity_document::utils::Queryable;
9
10use crate::revocation::RevocationError;
11use crate::revocation::RevocationResult;
12
13/// Extension trait providing convenience methods to update a `RevocationBitmap2022` service
14/// in a [`CoreDocument`](::identity_document::document::CoreDocument).   
15pub trait RevocationDocumentExt: private::Sealed {
16  /// If the document has a [`RevocationBitmap`] service identified by `service_query`,
17  /// revoke all specified `indices`.
18  fn revoke_credentials<'query, 'me, Q>(&'me mut self, service_query: Q, indices: &[u32]) -> RevocationResult<()>
19  where
20    Q: Into<DIDUrlQuery<'query>>;
21
22  /// If the document has a [`RevocationBitmap`] service identified by `service_query`,
23  /// unrevoke all specified `indices`.
24  fn unrevoke_credentials<'query, 'me, Q>(&'me mut self, service_query: Q, indices: &[u32]) -> RevocationResult<()>
25  where
26    Q: Into<DIDUrlQuery<'query>>;
27
28  /// Extracts the `RevocationBitmap` from the referenced service in the DID Document.
29  ///
30  /// # Errors
31  ///
32  /// Fails if the referenced service is not found, or is not a
33  /// valid `RevocationBitmap2022` service.
34  fn resolve_revocation_bitmap(&self, query: DIDUrlQuery<'_>) -> RevocationResult<RevocationBitmap>;
35}
36
37mod private {
38  use super::CoreDocument;
39
40  pub trait Sealed {}
41  impl Sealed for CoreDocument {}
42}
43
44impl RevocationDocumentExt for CoreDocument {
45  fn revoke_credentials<'query, 'me, Q>(&'me mut self, service_query: Q, indices: &[u32]) -> RevocationResult<()>
46  where
47    Q: Into<DIDUrlQuery<'query>>,
48  {
49    update_revocation_bitmap(self, service_query, |revocation_bitmap| {
50      for credential in indices {
51        revocation_bitmap.revoke(*credential);
52      }
53    })
54  }
55
56  fn unrevoke_credentials<'query, 'me, Q>(&mut self, service_query: Q, indices: &[u32]) -> RevocationResult<()>
57  where
58    Q: Into<DIDUrlQuery<'query>>,
59  {
60    update_revocation_bitmap(self, service_query, |revocation_bitmap| {
61      for credential in indices {
62        revocation_bitmap.unrevoke(*credential);
63      }
64    })
65  }
66
67  fn resolve_revocation_bitmap(&self, query: DIDUrlQuery<'_>) -> RevocationResult<RevocationBitmap> {
68    self
69      .resolve_service(query)
70      .ok_or(RevocationError::InvalidService("revocation bitmap service not found"))
71      .and_then(RevocationBitmap::try_from)
72  }
73}
74
75fn update_revocation_bitmap<'query, 'me, F, Q>(
76  document: &'me mut CoreDocument,
77  service_query: Q,
78  f: F,
79) -> RevocationResult<()>
80where
81  F: FnOnce(&mut RevocationBitmap),
82  Q: Into<DIDUrlQuery<'query>>,
83{
84  let service: &mut Service = document
85    .service_mut_unchecked()
86    .query_mut(service_query)
87    .ok_or(RevocationError::InvalidService("invalid id - service not found"))?;
88
89  let mut revocation_bitmap: RevocationBitmap = RevocationBitmap::try_from(&*service)?;
90  f(&mut revocation_bitmap);
91
92  std::mem::swap(service.service_endpoint_mut(), &mut revocation_bitmap.to_endpoint()?);
93
94  Ok(())
95}
96
97#[cfg(test)]
98mod tests {
99  use super::*;
100  use identity_core::convert::FromJson;
101  use identity_did::DID;
102
103  const START_DOCUMENT_JSON: &str = r#"{
104        "id": "did:example:1234",
105        "verificationMethod": [
106          {
107            "id": "did:example:1234#key-1",
108            "controller": "did:example:1234",
109            "type": "Ed25519VerificationKey2018",
110            "publicKeyMultibase": "zJdzr2UvC"
111          },
112          {
113            "id": "did:example:1234#key-2",
114            "controller": "did:example:1234",
115            "type": "Ed25519VerificationKey2018",
116            "publicKeyMultibase": "zJdzr2UvD"
117          },
118          {
119            "id": "did:example:1234#key-3",
120            "controller": "did:example:1234",
121            "type": "Ed25519VerificationKey2018",
122            "publicKeyMultibase": "zJdzr2UvE"
123          }
124        ],
125        "authentication": [
126          {
127            "id": "did:example:1234#auth-key",
128            "controller": "did:example:1234",
129            "type": "Ed25519VerificationKey2018",
130            "publicKeyMultibase": "zT7yhPEwJZL4G"
131          },
132          "did:example:1234#key-3"
133        ],
134        "keyAgreement": [
135          "did:example:1234#key-4"
136        ]
137      }
138      "#;
139
140  #[test]
141  fn test_revocation() {
142    let mut document: CoreDocument = CoreDocument::from_json(&START_DOCUMENT_JSON).unwrap();
143
144    let indices_1 = [3, 9, 254, 65536];
145    let indices_2 = [2, 15, 1337, 1000];
146
147    let service_id = document.id().to_url().join("#revocation-service").unwrap();
148
149    // The methods error if the service doesn't exist.
150    assert!(document.revoke_credentials(&service_id, &indices_2).is_err());
151    assert!(document.unrevoke_credentials(&service_id, &indices_2).is_err());
152
153    // Add service with indices_1 already revoked.
154    let mut bitmap: crate::revocation::RevocationBitmap = crate::revocation::RevocationBitmap::new();
155    for index in indices_1.iter() {
156      bitmap.revoke(*index);
157    }
158
159    assert!(document
160      .insert_service(bitmap.to_service(service_id.clone()).unwrap())
161      .is_ok());
162
163    // Revoke indices_2.
164    document.revoke_credentials(&service_id, &indices_2).unwrap();
165    let service: &Service = document.resolve_service(&service_id).unwrap();
166    let decoded_bitmap: crate::revocation::RevocationBitmap = service.try_into().unwrap();
167
168    // We expect all indices to be revoked now.
169    for index in indices_1.iter().chain(indices_2.iter()) {
170      assert!(decoded_bitmap.is_revoked(*index));
171    }
172
173    // Unrevoke indices_1.
174    document.unrevoke_credentials(&service_id, &indices_1).unwrap();
175
176    let service: &Service = document.resolve_service(&service_id).unwrap();
177    let decoded_bitmap: crate::revocation::RevocationBitmap = service.try_into().unwrap();
178
179    // Expect indices_2 to be revoked, but not indices_1.
180    for index in indices_2 {
181      assert!(decoded_bitmap.is_revoked(index));
182    }
183    for index in indices_1 {
184      assert!(!decoded_bitmap.is_revoked(index));
185    }
186  }
187}