identity_storage/storage/
did_jwk_document_ext.rs

1// Copyright 2020-2025 IOTA Stiftung, Fondazione Links
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::JwkGenOutput;
5use crate::JwkStorage;
6#[cfg(feature = "jpt-bbs-plus")]
7use crate::JwkStorageBbsPlusExt;
8use crate::JwkStorageDocumentError as Error;
9#[cfg(feature = "pqc")]
10use crate::JwkStoragePQ;
11#[cfg(feature = "hybrid")]
12use crate::KeyId;
13use crate::KeyIdStorage;
14use crate::KeyType;
15use crate::MethodDigest;
16use async_trait::async_trait;
17#[cfg(feature = "hybrid")]
18use identity_did::DIDCompositeJwk;
19use identity_did::DIDJwk;
20use identity_document::document::CoreDocument;
21#[cfg(feature = "hybrid")]
22use identity_verification::jwk::CompositeAlgId;
23#[cfg(feature = "hybrid")]
24use identity_verification::jwk::CompositeJwk;
25use identity_verification::jws::JwsAlgorithm;
26use identity_verification::jwu::encode_b64_json;
27#[cfg(feature = "jpt-bbs-plus")]
28use jsonprooftoken::jpa::algs::ProofAlgorithm;
29
30use super::Storage;
31use super::StorageResult;
32
33/// Extension trait for creating JWK-based DID documents for traditional, zk, PQ and hybrid keys
34#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
35#[cfg_attr(feature = "send-sync-storage", async_trait)]
36pub trait DidJwkDocumentExt {
37  /// Create a JWK-based DID documents with traditional keys. Returns the DID document and the fragment
38  async fn new_did_jwk<K, I>(
39    storage: &Storage<K, I>,
40    key_type: KeyType,
41    alg: JwsAlgorithm,
42  ) -> StorageResult<(CoreDocument, String)>
43  where
44    K: JwkStorage,
45    I: KeyIdStorage;
46  /// Create a JWK-based DID documents with PQ keys. Returns the DID document and the fragment
47  #[cfg(feature = "pqc")]
48  async fn new_did_jwk_pqc<K, I>(
49    storage: &Storage<K, I>,
50    key_type: KeyType,
51    alg: JwsAlgorithm,
52  ) -> StorageResult<(CoreDocument, String)>
53  where
54    K: JwkStoragePQ,
55    I: KeyIdStorage;
56  /// Create a JWK-based DID documents with zk keys. Returns the DID document and the fragment
57  #[cfg(feature = "jpt-bbs-plus")]
58  async fn new_did_jwk_zk<K, I>(
59    storage: &Storage<K, I>,
60    key_type: KeyType,
61    alg: ProofAlgorithm,
62  ) -> StorageResult<(CoreDocument, String)>
63  where
64    K: JwkStorageBbsPlusExt,
65    I: KeyIdStorage;
66
67  /// Create a JWK-based DID documents with hybrid keys. Returns the DID document and the fragment
68  #[cfg(feature = "hybrid")]
69  async fn new_did_compositejwk<K, I>(
70    storage: &Storage<K, I>,
71    alg: identity_verification::jwk::CompositeAlgId,
72  ) -> StorageResult<(CoreDocument, String)>
73  where
74    K: JwkStorage + JwkStoragePQ,
75    I: KeyIdStorage;
76}
77
78#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
79#[cfg_attr(feature = "send-sync-storage", async_trait)]
80impl DidJwkDocumentExt for CoreDocument {
81  async fn new_did_jwk<K, I>(
82    storage: &Storage<K, I>,
83    key_type: KeyType,
84    alg: JwsAlgorithm,
85  ) -> StorageResult<(CoreDocument, String)>
86  where
87    K: JwkStorage,
88    I: KeyIdStorage,
89  {
90    let JwkGenOutput { key_id, jwk } = K::generate(storage.key_storage(), key_type, alg)
91      .await
92      .map_err(Error::KeyStorageError)?;
93
94    let b64 = encode_b64_json(&jwk).map_err(|err| Error::EncodingError(Box::new(err)))?;
95
96    let did =
97      DIDJwk::parse(format!("{}{}", "did:jwk:", b64).as_str()).map_err(|err| Error::EncodingError(Box::new(err)))?;
98
99    let document = CoreDocument::expand_did_jwk(did).map_err(|err| Error::EncodingError(Box::new(err)))?;
100
101    let fragment = "0";
102
103    let verification_method = document
104      .resolve_method(fragment, None)
105      .ok_or(identity_verification::Error::MissingIdFragment)
106      .map_err(Error::VerificationMethodConstructionError)?;
107
108    let method_digest = MethodDigest::new(verification_method).map_err(Error::MethodDigestConstructionError)?;
109
110    I::insert_key_id(storage.key_id_storage(), method_digest, key_id.clone())
111      .await
112      .map_err(Error::KeyIdStorageError)?;
113
114    Ok((document, fragment.to_string()))
115  }
116
117  #[cfg(feature = "pqc")]
118  async fn new_did_jwk_pqc<K, I>(
119    storage: &Storage<K, I>,
120    key_type: KeyType,
121    alg: JwsAlgorithm,
122  ) -> StorageResult<(CoreDocument, String)>
123  where
124    K: JwkStoragePQ,
125    I: KeyIdStorage,
126  {
127    let JwkGenOutput { key_id, jwk } = K::generate_pq_key(storage.key_storage(), key_type, alg)
128      .await
129      .map_err(Error::KeyStorageError)?;
130
131    let b64 = encode_b64_json(&jwk).map_err(|err| Error::EncodingError(Box::new(err)))?;
132
133    let did =
134      DIDJwk::parse(format!("{}{}", "did:jwk:", b64).as_str()).map_err(|err| Error::EncodingError(Box::new(err)))?;
135
136    let document = CoreDocument::expand_did_jwk(did).map_err(|err| Error::EncodingError(Box::new(err)))?;
137
138    let fragment = "0";
139
140    let verification_method = document
141      .resolve_method(fragment, None)
142      .ok_or(identity_verification::Error::MissingIdFragment)
143      .map_err(Error::VerificationMethodConstructionError)?;
144
145    let method_digest = MethodDigest::new(verification_method).map_err(Error::MethodDigestConstructionError)?;
146
147    I::insert_key_id(storage.key_id_storage(), method_digest, key_id.clone())
148      .await
149      .map_err(Error::KeyIdStorageError)?;
150
151    Ok((document, fragment.to_string()))
152  }
153
154  #[cfg(feature = "jpt-bbs-plus")]
155  async fn new_did_jwk_zk<K, I>(
156    storage: &Storage<K, I>,
157    key_type: KeyType,
158    alg: ProofAlgorithm,
159  ) -> StorageResult<(CoreDocument, String)>
160  where
161    K: JwkStorageBbsPlusExt,
162    I: KeyIdStorage,
163  {
164    let JwkGenOutput { key_id, jwk } = K::generate_bbs(storage.key_storage(), key_type, alg)
165      .await
166      .map_err(Error::KeyStorageError)?;
167
168    let b64 = encode_b64_json(&jwk).map_err(|err| Error::EncodingError(Box::new(err)))?;
169
170    let did =
171      DIDJwk::parse(format!("{}{}", "did:jwk:", b64).as_str()).map_err(|err| Error::EncodingError(Box::new(err)))?;
172
173    let document = CoreDocument::expand_did_jwk(did).map_err(|err| Error::EncodingError(Box::new(err)))?;
174
175    let fragment = "0";
176
177    let verification_method = document
178      .resolve_method(fragment, None)
179      .ok_or(identity_verification::Error::MissingIdFragment)
180      .map_err(Error::VerificationMethodConstructionError)?;
181
182    let method_digest = MethodDigest::new(verification_method).map_err(Error::MethodDigestConstructionError)?;
183
184    I::insert_key_id(storage.key_id_storage(), method_digest, key_id.clone())
185      .await
186      .map_err(Error::KeyIdStorageError)?;
187
188    Ok((document, fragment.to_string()))
189  }
190
191  #[cfg(feature = "hybrid")]
192  async fn new_did_compositejwk<K, I>(
193    storage: &Storage<K, I>,
194    alg: CompositeAlgId,
195  ) -> StorageResult<(CoreDocument, String)>
196  where
197    K: JwkStorage + JwkStoragePQ,
198    I: KeyIdStorage,
199  {
200    use identity_verification::jwk::PostQuantumJwk;
201    use identity_verification::jwk::TraditionalJwk;
202
203    let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg {
204      CompositeAlgId::IdMldsa44Ed25519 => (
205        KeyType::from_static_str("AKP"),
206        JwsAlgorithm::ML_DSA_44,
207        KeyType::from_static_str("Ed25519"),
208        JwsAlgorithm::EdDSA,
209      ),
210      CompositeAlgId::IdMldsa65Ed25519 => (
211        KeyType::from_static_str("AKP"),
212        JwsAlgorithm::ML_DSA_65,
213        KeyType::from_static_str("Ed25519"),
214        JwsAlgorithm::EdDSA,
215      ),
216      _ => {
217        return Err(Error::InvalidJwsAlgorithm);
218      }
219    };
220
221    let JwkGenOutput {
222      key_id: t_key_id,
223      jwk: t_jwk,
224    } = K::generate(storage.key_storage(), trad_key_type, trad_alg)
225      .await
226      .map_err(Error::KeyStorageError)?;
227
228    let JwkGenOutput {
229      key_id: pq_key_id,
230      jwk: pq_jwk,
231    } = K::generate_pq_key(storage.key_storage(), pq_key_type, pq_alg)
232      .await
233      .map_err(Error::KeyStorageError)?;
234
235    let key_id = KeyId::new(format!("{}~{}", t_key_id.as_str(), pq_key_id.as_str()));
236
237    let pq_jwk = PostQuantumJwk::try_from(pq_jwk).map_err(|err| Error::EncodingError(Box::new(err)))?;
238
239    let traditional_jwk = TraditionalJwk::try_from(t_jwk).map_err(|err| Error::EncodingError(Box::new(err)))?;
240
241    let composite_pk = CompositeJwk::new(alg, traditional_jwk, pq_jwk);
242
243    let b64 = encode_b64_json(&composite_pk).map_err(|err| Error::EncodingError(Box::new(err)))?;
244
245    let did = DIDCompositeJwk::parse(format!("{}{}", "did:compositejwk:", b64).as_str())
246      .map_err(|err| Error::EncodingError(Box::new(err)))?;
247
248    let document = CoreDocument::expand_did_compositejwk(did).map_err(|err| Error::EncodingError(Box::new(err)))?;
249
250    let fragment = "0";
251
252    let verification_method = document
253      .resolve_method(fragment, None)
254      .ok_or(identity_verification::Error::MissingIdFragment)
255      .map_err(Error::VerificationMethodConstructionError)?;
256
257    let method_digest = MethodDigest::new(verification_method).map_err(Error::MethodDigestConstructionError)?;
258
259    I::insert_key_id(storage.key_id_storage(), method_digest, key_id.clone())
260      .await
261      .map_err(Error::KeyIdStorageError)?;
262
263    Ok((document, fragment.to_string()))
264  }
265}