did_utils/methods/key/
method.rs

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 to consider during DID expansion into a DID document
16    key_format: PublicKeyFormat,
17
18    /// Derive key agreement on expanding did:key address
19    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    /// Creates a new DidKey resolver instance.
30    pub fn new() -> Self {
31        Self::new_full(false, PublicKeyFormat::default())
32    }
33
34    /// Creates a new DidKey resolver with optional encryption key derivation and a specific key format.
35    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    /// Generates did:key address ex nihilo, off self-generated Ed25519 key pair
43    pub fn generate() -> Result<String, CryptoError> {
44        let keypair = Ed25519KeyPair::new()?;
45        Self::from_ed25519_keypair(&keypair)
46    }
47
48    /// Computes did:key address corresponding to Ed25519 key pair
49    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    /// Computes did:key address corresponding to raw public key bytes
59    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    /// Expands did:key address into DID document
72    ///
73    /// See [Create a did key](https://w3c-ccg.github.io/did-method-key/#create)
74    pub fn expand(&self, did: &str) -> Result<DIDDocument, DIDResolutionError> {
75        if !did.starts_with("did:key:") {
76            return Err(DIDResolutionError::InvalidDid);
77        }
78
79        // See https://w3c-ccg.github.io/did-method-key/#format
80        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        // Run algorithm for signature verification method expansion
84        let signature_verification_method = self.derive_signature_verification_method(alg, multibase_value, &raw_public_key_bytes)?;
85
86        // Build DID document
87        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(), //
95            )]),
96            assertion_method: Some(vec![didcore::VerificationMethodType::Reference(
97                signature_verification_method.id.clone(), //
98            )]),
99            capability_delegation: Some(vec![didcore::VerificationMethodType::Reference(
100                signature_verification_method.id.clone(), //
101            )]),
102            capability_invocation: Some(vec![didcore::VerificationMethodType::Reference(
103                signature_verification_method.id.clone(), //
104            )]),
105            key_agreement: None,
106            service: None,
107            additional_properties: None,
108            proof: None,
109        };
110
111        if self.enable_encryption_key_derivation {
112            // Run algorithm for encryption verification method derivation if opted in
113            let encryption_verification_method = self.derive_encryption_verification_method(alg, multibase_value, &raw_public_key_bytes)?;
114
115            // Amend DID document accordingly
116            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(), //
120            )]);
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    // See https://w3c-ccg.github.io/did-method-key/#signature-method-creation-algorithm
148    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) //
175                        .map_err(|_| DIDResolutionError::InternalError)?,
176                ),
177            }),
178            ..Default::default()
179        })
180    }
181
182    // See https://w3c-ccg.github.io/did-method-key/#encryption-method-creation-algorithm
183    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(),   //
310            json_canon::to_string(&expected).unwrap(), //
311        );
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(),   //
348            json_canon::to_string(&expected).unwrap(), //
349        );
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(),   //
392            json_canon::to_string(&expected).unwrap(), //
393        );
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}