1use multibase::Base::Base58Btc;
2
3use crate::{
4 crypto::{
5 alg::decode_multikey,
6 Algorithm, Ed25519KeyPair, Error as CryptoError, PublicKeyFormat, {Generate, KeyMaterial},
7 },
8 didcore::{self, Document as DIDDocument, KeyFormat, VerificationMethod},
9 ldmodel::Context,
10 methods::{errors::DIDResolutionError, traits::DIDMethod},
11};
12
13#[derive(Default)]
14pub struct DidKey {
15 key_format: PublicKeyFormat,
17
18 enable_encryption_key_derivation: bool,
20}
21
22impl DIDMethod for DidKey {
23 fn name() -> String {
24 "did:key".to_string()
25 }
26}
27
28impl DidKey {
29 pub fn new() -> Self {
31 Self::new_full(false, PublicKeyFormat::default())
32 }
33
34 pub fn new_full(enable_encryption_key_derivation: bool, key_format: PublicKeyFormat) -> Self {
36 Self {
37 enable_encryption_key_derivation,
38 key_format,
39 }
40 }
41
42 pub fn generate() -> Result<String, CryptoError> {
44 let keypair = Ed25519KeyPair::new()?;
45 Self::from_ed25519_keypair(&keypair)
46 }
47
48 pub fn from_ed25519_keypair(keypair: &Ed25519KeyPair) -> Result<String, CryptoError> {
50 let multibase_value = multibase::encode(
51 Base58Btc,
52 [&Algorithm::Ed25519.muticodec_prefix(), keypair.public_key_bytes()?.as_slice()].concat(),
53 );
54
55 Ok(format!("did:key:{}", multibase_value))
56 }
57
58 pub fn from_raw_public_key(alg: Algorithm, bytes: &[u8]) -> Result<String, CryptoError> {
60 if let Some(required_length) = alg.public_key_length() {
61 if required_length != bytes.len() {
62 return Err(CryptoError::InvalidKeyLength);
63 }
64 }
65
66 let multibase_value = multibase::encode(Base58Btc, [&alg.muticodec_prefix(), bytes].concat());
67
68 Ok(format!("did:key:{}", multibase_value))
69 }
70
71 pub fn expand(&self, did: &str) -> Result<DIDDocument, DIDResolutionError> {
75 if !did.starts_with("did:key:") {
76 return Err(DIDResolutionError::InvalidDid);
77 }
78
79 let multibase_value = did.strip_prefix("did:key:").unwrap();
81 let (alg, raw_public_key_bytes) = decode_multikey(multibase_value).map_err(|_| DIDResolutionError::InvalidDid)?;
82
83 let signature_verification_method = self.derive_signature_verification_method(alg, multibase_value, &raw_public_key_bytes)?;
85
86 let mut diddoc = DIDDocument {
88 context: Context::SetOfString(self.guess_context_property(&alg)),
89 id: did.to_string(),
90 controller: None,
91 also_known_as: None,
92 verification_method: Some(vec![signature_verification_method.clone()]),
93 authentication: Some(vec![didcore::VerificationMethodType::Reference(
94 signature_verification_method.id.clone(), )]),
96 assertion_method: Some(vec![didcore::VerificationMethodType::Reference(
97 signature_verification_method.id.clone(), )]),
99 capability_delegation: Some(vec![didcore::VerificationMethodType::Reference(
100 signature_verification_method.id.clone(), )]),
102 capability_invocation: Some(vec![didcore::VerificationMethodType::Reference(
103 signature_verification_method.id.clone(), )]),
105 key_agreement: None,
106 service: None,
107 additional_properties: None,
108 proof: None,
109 };
110
111 if self.enable_encryption_key_derivation {
112 let encryption_verification_method = self.derive_encryption_verification_method(alg, multibase_value, &raw_public_key_bytes)?;
114
115 let verification_method = diddoc.verification_method.as_mut().unwrap();
117 verification_method.push(encryption_verification_method.clone());
118 diddoc.key_agreement = Some(vec![didcore::VerificationMethodType::Reference(
119 encryption_verification_method.id.clone(), )]);
121 }
122
123 Ok(diddoc)
124 }
125
126 fn guess_context_property(&self, alg: &Algorithm) -> Vec<String> {
127 let mut context = vec!["https://www.w3.org/ns/did/v1"];
128
129 match self.key_format {
130 PublicKeyFormat::Multikey => match alg {
131 Algorithm::Ed25519 => {
132 context.push("https://w3id.org/security/suites/ed25519-2020/v1");
133 if self.enable_encryption_key_derivation {
134 context.push("https://w3id.org/security/suites/x25519-2020/v1");
135 }
136 }
137 Algorithm::X25519 => context.push("https://w3id.org/security/suites/x25519-2020/v1"),
138 _ => (),
139 },
140
141 PublicKeyFormat::Jwk => context.push("https://w3id.org/security/suites/jws-2020/v1"),
142 }
143
144 context.iter().map(|x| x.to_string()).collect()
145 }
146
147 fn derive_signature_verification_method(
149 &self,
150 alg: Algorithm,
151 multibase_value: &str,
152 raw_public_key_bytes: &[u8],
153 ) -> Result<VerificationMethod, DIDResolutionError> {
154 if let Some(required_length) = alg.public_key_length() {
155 if required_length != raw_public_key_bytes.len() {
156 return Err(DIDResolutionError::InvalidPublicKeyLength);
157 }
158 }
159
160 Ok(VerificationMethod {
161 id: format!("did:key:{multibase_value}#{multibase_value}"),
162 key_type: String::from(match self.key_format {
163 PublicKeyFormat::Multikey => match alg {
164 Algorithm::Ed25519 => "Ed25519VerificationKey2020",
165 Algorithm::X25519 => "X25519KeyAgreementKey2020",
166 _ => "Multikey",
167 },
168 PublicKeyFormat::Jwk => "JsonWebKey2020",
169 }),
170 controller: format!("did:key:{multibase_value}"),
171 public_key: Some(match self.key_format {
172 PublicKeyFormat::Multikey => KeyFormat::Multibase(String::from(multibase_value)),
173 PublicKeyFormat::Jwk => KeyFormat::Jwk(
174 alg.build_jwk(raw_public_key_bytes) .map_err(|_| DIDResolutionError::InternalError)?,
176 ),
177 }),
178 ..Default::default()
179 })
180 }
181
182 fn derive_encryption_verification_method(
184 &self,
185 alg: Algorithm,
186 multibase_value: &str,
187 raw_public_key_bytes: &[u8],
188 ) -> Result<VerificationMethod, DIDResolutionError> {
189 if alg != Algorithm::Ed25519 {
190 return Err(DIDResolutionError::InternalError);
191 }
192
193 let raw_public_key_bytes: [u8; 32] = raw_public_key_bytes.try_into().map_err(|_| DIDResolutionError::InvalidPublicKeyLength)?;
194 let ed25519_keypair = Ed25519KeyPair::from_public_key(&raw_public_key_bytes).map_err(|_| DIDResolutionError::InternalError)?;
195 let x25519_keypair = ed25519_keypair.get_x25519().map_err(|_| DIDResolutionError::InternalError)?;
196
197 let alg = Algorithm::X25519;
198 let encryption_raw_public_key_bytes = &x25519_keypair.public_key_bytes().unwrap()[..];
199 let encryption_multibase_value = multibase::encode(Base58Btc, [&alg.muticodec_prefix(), encryption_raw_public_key_bytes].concat());
200
201 Ok(VerificationMethod {
202 id: format!("did:key:{multibase_value}#{encryption_multibase_value}"),
203 key_type: String::from(match self.key_format {
204 PublicKeyFormat::Multikey => "X25519KeyAgreementKey2020",
205 PublicKeyFormat::Jwk => "JsonWebKey2020",
206 }),
207 controller: format!("did:key:{multibase_value}"),
208 public_key: Some(match self.key_format {
209 PublicKeyFormat::Multikey => KeyFormat::Multibase(encryption_multibase_value),
210 PublicKeyFormat::Jwk => KeyFormat::Jwk(
211 alg.build_jwk(encryption_raw_public_key_bytes)
212 .map_err(|_| DIDResolutionError::InternalError)?,
213 ),
214 }),
215 ..Default::default()
216 })
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223 use crate::jwk::Jwk;
224 use serde_json::Value;
225
226 #[test]
227 fn test_did_key_generation() {
228 let did = DidKey::generate();
229 assert!(did.unwrap().starts_with("did:key:z6Mk"));
230 }
231
232 #[test]
233 fn test_did_key_generation_from_given_jwk() {
234 let jwk: Jwk = serde_json::from_str(
235 r#"{
236 "kty": "OKP",
237 "crv": "Ed25519",
238 "x": "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik"
239 }"#,
240 )
241 .unwrap();
242 let keypair: Ed25519KeyPair = jwk.try_into().unwrap();
243
244 let did = DidKey::from_ed25519_keypair(&keypair);
245 assert_eq!(did.unwrap(), "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp");
246 }
247
248 #[test]
249 fn test_did_key_generation_from_given_raw_public_key_bytes() {
250 let entries = [
251 (
252 Algorithm::Ed25519,
253 hex::decode("3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29").unwrap(),
254 "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
255 ),
256 (
257 Algorithm::X25519,
258 hex::decode("2fe57da347cd62431528daac5fbb290730fff684afc4cfc2ed90995f58cb3b74").unwrap(),
259 "did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F",
260 ),
261 (
262 Algorithm::Secp256k1,
263 hex::decode("03874c15c7fda20e539c6e5ba573c139884c351188799f5458b4b41f7924f235cd").unwrap(),
264 "did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme",
265 ),
266 (
267 Algorithm::P521,
268 hex::decode("020125073ccca272143441b1d9f687cdc7f978cbb96e9dc9f97de28ba373a92769d26d9a02ee67dfa258f9bb2eece8a48a5c59a7356c46278d883ab8d9e3baaac2ac92").unwrap(),
269 "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7",
270 )
271 ];
272
273 for entry in entries {
274 let (alg, bytes, expected) = entry;
275 let did = DidKey::from_raw_public_key(alg, &bytes);
276 assert_eq!(did.unwrap(), expected);
277 }
278 }
279
280 #[test]
281 fn test_did_key_expansion_multikey() {
282 let did_method = DidKey::new();
283
284 let did = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
285 let expected: Value = serde_json::from_str(
286 r#"{
287 "@context": [
288 "https://www.w3.org/ns/did/v1",
289 "https://w3id.org/security/suites/ed25519-2020/v1"
290 ],
291 "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
292 "verificationMethod": [{
293 "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
294 "type": "Ed25519VerificationKey2020",
295 "controller": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
296 "publicKeyMultibase": "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
297 }],
298 "authentication": ["did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
299 "assertionMethod": ["did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
300 "capabilityDelegation": ["did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
301 "capabilityInvocation": ["did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"]
302 }"#,
303 )
304 .unwrap();
305
306 let diddoc = did_method.expand(did).unwrap();
307
308 assert_eq!(
309 json_canon::to_string(&diddoc).unwrap(), json_canon::to_string(&expected).unwrap(), );
312 }
313
314 #[test]
315 fn test_did_key_expansion_jsonwebkey() {
316 let did_method = DidKey::new_full(false, PublicKeyFormat::Jwk);
317
318 let did = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
319 let expected: Value = serde_json::from_str(
320 r#"{
321 "@context": [
322 "https://www.w3.org/ns/did/v1",
323 "https://w3id.org/security/suites/jws-2020/v1"
324 ],
325 "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
326 "verificationMethod": [{
327 "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
328 "type": "JsonWebKey2020",
329 "controller": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
330 "publicKeyJwk": {
331 "kty": "OKP",
332 "crv": "Ed25519",
333 "x": "Lm_M42cB3HkUiODQsXRcweM6TByfzEHGO9ND274JcOY"
334 }
335 }],
336 "authentication": ["did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
337 "assertionMethod": ["did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
338 "capabilityDelegation": ["did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
339 "capabilityInvocation": ["did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"]
340 }"#,
341 )
342 .unwrap();
343
344 let diddoc = did_method.expand(did).unwrap();
345
346 assert_eq!(
347 json_canon::to_string(&diddoc).unwrap(), json_canon::to_string(&expected).unwrap(), );
350 }
351
352 #[test]
353 fn test_did_key_expansion_multikey_with_encryption_derivation() {
354 let did_method = DidKey::new_full(true, PublicKeyFormat::default());
355
356 let did = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
357 let expected: Value = serde_json::from_str(
358 r#"{
359 "@context": [
360 "https://www.w3.org/ns/did/v1",
361 "https://w3id.org/security/suites/ed25519-2020/v1",
362 "https://w3id.org/security/suites/x25519-2020/v1"
363 ],
364 "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
365 "verificationMethod": [
366 {
367 "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
368 "type": "Ed25519VerificationKey2020",
369 "controller": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
370 "publicKeyMultibase": "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
371 },
372 {
373 "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6LSj72tK8brWgZja8NLRwPigth2T9QRiG1uH9oKZuKjdh9p",
374 "type": "X25519KeyAgreementKey2020",
375 "controller": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
376 "publicKeyMultibase": "z6LSj72tK8brWgZja8NLRwPigth2T9QRiG1uH9oKZuKjdh9p"
377 }
378 ],
379 "authentication": ["did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
380 "assertionMethod": ["did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
381 "capabilityDelegation": ["did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
382 "capabilityInvocation": ["did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
383 "keyAgreement": ["did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6LSj72tK8brWgZja8NLRwPigth2T9QRiG1uH9oKZuKjdh9p"]
384 }"#,
385 )
386 .unwrap();
387
388 let diddoc = did_method.expand(did).unwrap();
389
390 assert_eq!(
391 json_canon::to_string(&diddoc).unwrap(), json_canon::to_string(&expected).unwrap(), );
394 }
395
396 #[test]
397 fn test_did_key_expansion_fails_as_expected() {
398 let did_method = DidKey::new();
399
400 let did = "did:key:Z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
401 assert_eq!(did_method.expand(did).unwrap_err(), DIDResolutionError::InvalidDid);
402
403 let did = "did:key:z6MkhaXgBZDvotDkL5257####tiGiC2QtKLGpbnnEGta2doK";
404 assert_eq!(did_method.expand(did).unwrap_err(), DIDResolutionError::InvalidDid);
405
406 let did = "did:key:zQebt6zPwbE4Vw5GFAjjARHrNXFALofERVv4q6Z4db8cnDRQm";
407 assert_eq!(did_method.expand(did).unwrap_err(), DIDResolutionError::InvalidPublicKeyLength);
408 }
409}