credibil_did/key/
operator.rs

1//! # DID Key Operations
2//!
3//! Implements Create, Read, Update, Delete (CRUD) operations for DID Key.
4//!
5//! See <https://w3c-ccg.github.io/did-method-key>
6
7use anyhow::anyhow;
8use base64ct::{Base64UrlUnpadded, Encoding};
9use ed25519_dalek::{PUBLIC_KEY_LENGTH, VerifyingKey};
10use multibase::Base;
11use serde_json::json;
12
13use super::DidKey;
14use crate::core::Kind;
15use crate::document::{CreateOptions, Document, MethodType, PublicKeyFormat, VerificationMethod};
16use crate::error::Error;
17use crate::{DidOperator, ED25519_CODEC, KeyPurpose, X25519_CODEC};
18
19impl DidKey {
20    /// Create a DID Document from the verifying key provided by [`DidOperator`].
21    ///
22    /// # Errors
23    ///
24    /// Returns an error if the supplied verifying key is not found or not a
25    /// valid format.
26    pub fn create(op: &impl DidOperator, options: CreateOptions) -> crate::Result<Document> {
27        let Some(verifying_key) = op.verification(KeyPurpose::VerificationMethod) else {
28            return Err(Error::Other(anyhow!("no verification key")));
29        };
30        let key_bytes = Base64UrlUnpadded::decode_vec(&verifying_key.x)
31            .map_err(|e| Error::InvalidPublicKey(format!("issue decoding key: {e}")))?;
32
33        let mut multi_bytes = ED25519_CODEC.to_vec();
34        multi_bytes.extend_from_slice(&key_bytes);
35        let multikey = multibase::encode(Base::Base58Btc, &multi_bytes);
36
37        let did = format!("did:key:{multikey}");
38
39        let context = if options.public_key_format == PublicKeyFormat::Multikey
40            || options.public_key_format == PublicKeyFormat::Ed25519VerificationKey2020
41        {
42            Kind::String("https://w3id.org/security/data-integrity/v1".into())
43        } else {
44            let verif_type = &options.public_key_format;
45            Kind::Object(json!({
46                "publicKeyJwk": {
47                    "@id": "https://w3id.org/security#publicKeyJwk",
48                    "@type": "@json"
49                },
50                verif_type.to_string(): format!("https://w3id.org/security#{verif_type}"),
51            }))
52        };
53
54        // key agreement
55        // <https://w3c-ccg.github.io/did-method-key/#encryption-method-creation-algorithm>
56        let key_agreement = if options.enable_encryption_key_derivation {
57            let verifier_bytes: [u8; PUBLIC_KEY_LENGTH] = key_bytes.try_into().map_err(|_| {
58                Error::InvalidPublicKey(format!("public key is not {PUBLIC_KEY_LENGTH} bytes"))
59            })?;
60            let verifier = VerifyingKey::from_bytes(&verifier_bytes).map_err(|e| {
61                Error::InvalidPublicKey(format!("public key is not correct size: {e}"))
62            })?;
63            let x25519_bytes = verifier.to_montgomery().to_bytes();
64
65            // base58B encode the raw key
66            let mut multi_bytes = vec![];
67            multi_bytes.extend_from_slice(&X25519_CODEC);
68            multi_bytes.extend_from_slice(&x25519_bytes);
69            let multikey = multibase::encode(Base::Base58Btc, &multi_bytes);
70
71            let method_type = match options.public_key_format {
72                PublicKeyFormat::Multikey => MethodType::Multikey {
73                    public_key_multibase: multikey.clone(),
74                },
75                _ => return Err(Error::InvalidPublicKey("Unsupported public key format".into())),
76            };
77
78            Some(vec![Kind::Object(VerificationMethod {
79                id: format!("{did}#{multikey}"),
80                controller: did.clone(),
81                method_type,
82                ..VerificationMethod::default()
83            })])
84        } else {
85            None
86        };
87
88        let kid = format!("{did}#{multikey}");
89
90        let method_type = match options.public_key_format {
91            PublicKeyFormat::Multikey => MethodType::Multikey {
92                public_key_multibase: multikey,
93            },
94            _ => MethodType::JsonWebKey {
95                public_key_jwk: verifying_key,
96            },
97        };
98
99        Ok(Document {
100            context: vec![Kind::String(options.default_context), context],
101            id: did.clone(),
102            verification_method: Some(vec![VerificationMethod {
103                id: kid.clone(),
104                controller: did,
105                method_type,
106                ..VerificationMethod::default()
107            }]),
108            authentication: Some(vec![Kind::String(kid.clone())]),
109            assertion_method: Some(vec![Kind::String(kid.clone())]),
110            capability_invocation: Some(vec![Kind::String(kid.clone())]),
111            capability_delegation: Some(vec![Kind::String(kid)]),
112            key_agreement,
113            ..Document::default()
114        })
115    }
116}
117
118#[cfg(test)]
119mod test {
120    use credibil_infosec::{Curve, KeyType, PublicKeyJwk};
121    use ed25519_dalek::SigningKey;
122    use rand::rngs::OsRng;
123
124    use super::*;
125
126    #[test]
127    fn create() {
128        let mut options = CreateOptions::default();
129        options.enable_encryption_key_derivation = true;
130
131        let op = Operator;
132        let res = DidKey::create(&op, options).expect("should create");
133
134        let json = serde_json::to_string_pretty(&res).expect("should serialize");
135        println!("{json}");
136    }
137
138    struct Operator;
139    impl DidOperator for Operator {
140        fn verification(&self, purpose: KeyPurpose) -> Option<PublicKeyJwk> {
141            match purpose {
142                KeyPurpose::VerificationMethod => {
143                    let key = generate();
144
145                    Some(PublicKeyJwk {
146                        kty: KeyType::Okp,
147                        crv: Curve::Ed25519,
148                        x: Base64UrlUnpadded::encode_string(&key),
149                        ..PublicKeyJwk::default()
150                    })
151                }
152                _ => panic!("unsupported purpose"),
153            }
154        }
155    }
156
157    // HACK: generate a key pair
158    #[allow(dead_code)]
159    pub fn generate() -> Vec<u8> {
160        // TODO: pass in public key
161        let signing_key = SigningKey::generate(&mut OsRng);
162        let secret = Base64UrlUnpadded::encode_string(signing_key.as_bytes());
163        println!("secret: {secret}");
164
165        signing_key.verifying_key().to_bytes().to_vec()
166    }
167}