Skip to main content

affinidi_did_common/did_method/
key.rs

1//! Key material for did:key and related cryptographic operations
2//!
3//! This module contains types for managing cryptographic key material
4//! associated with DIDs, including generation, serialization, and encoding.
5
6use affinidi_crypto::{JWK, KeyType, Params};
7use affinidi_encoding::{
8    ED25519_PRIV, ED25519_PUB, P256_PRIV, P256_PUB, P384_PRIV, P384_PUB, SECP256K1_PRIV,
9    SECP256K1_PUB, X25519_PRIV, X25519_PUB,
10};
11use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD};
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use thiserror::Error;
15use zeroize::{Zeroize, ZeroizeOnDrop};
16
17/// Errors related to key material operations
18#[derive(Error, Debug)]
19pub enum KeyError {
20    #[error("Key error: {0}")]
21    Key(String),
22
23    #[error("Unsupported key type: {0}")]
24    UnsupportedKeyType(String),
25
26    #[error("Encoding error: {0}")]
27    Encoding(#[from] affinidi_encoding::EncodingError),
28
29    #[error("Crypto error: {0}")]
30    Crypto(#[from] affinidi_crypto::CryptoError),
31}
32
33/// Type of key material for DID Document verification methods
34#[derive(Debug, Clone, Deserialize, Serialize, Zeroize, PartialEq, Eq)]
35pub enum KeyMaterialType {
36    JsonWebKey2020,
37    Multikey,
38    X25519KeyAgreementKey2019,
39    X25519KeyAgreementKey2020,
40    Ed25519VerificationKey2018,
41    Ed25519VerificationKey2020,
42    EcdsaSecp256k1VerificationKey2019,
43    Other,
44}
45
46/// Serialization format for key material
47#[derive(Debug, Clone, Deserialize, Serialize, Zeroize)]
48pub enum KeyMaterialFormat {
49    #[serde(rename = "privateKeyJwk", rename_all = "camelCase")]
50    JWK(JWK),
51
52    #[serde(rename_all = "camelCase")]
53    Multibase { private_key_multibase: String },
54
55    #[serde(rename_all = "camelCase")]
56    Base58 { private_key_base58: String },
57}
58
59/// Shadow struct for deserialization
60#[derive(Deserialize)]
61struct KeyMaterialShadow {
62    id: String,
63    #[serde(rename = "type")]
64    type_: KeyMaterialType,
65    #[serde(flatten)]
66    format: KeyMaterialFormat,
67}
68
69/// Key material associated with a DID
70///
71/// Contains both public and private key bytes along with metadata.
72/// This type securely manages cryptographic key material and supports
73/// various serialization formats (JWK, multibase).
74#[derive(Debug, Clone, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
75#[serde(try_from = "KeyMaterialShadow")]
76pub struct KeyMaterial {
77    /// Key ID (typically a DID URL like `did:key:z6Mk...#z6Mk...`)
78    pub id: String,
79
80    /// Type for DID Document verification methods
81    #[serde(rename = "type")]
82    pub type_: KeyMaterialType,
83
84    /// Serialized form of the key
85    #[serde(flatten)]
86    pub format: KeyMaterialFormat,
87
88    /// Raw private key bytes
89    #[serde(skip)]
90    pub(crate) private_bytes: Vec<u8>,
91
92    /// Raw public key bytes
93    #[serde(skip)]
94    pub(crate) public_bytes: Vec<u8>,
95
96    /// Cryptographic algorithm
97    #[serde(skip)]
98    pub(crate) key_type: KeyType,
99}
100
101impl TryFrom<KeyMaterialShadow> for KeyMaterial {
102    type Error = KeyError;
103
104    fn try_from(shadow: KeyMaterialShadow) -> Result<Self, Self::Error> {
105        match shadow.format {
106            KeyMaterialFormat::JWK(jwk) => {
107                let mut key = KeyMaterial::from_jwk(&jwk)?;
108                key.id = shadow.id;
109                key.type_ = shadow.type_;
110                Ok(key)
111            }
112            _ => Err(KeyError::Key("Unsupported key material format".into())),
113        }
114    }
115}
116
117impl KeyMaterial {
118    // ========== Generation Methods ==========
119
120    /// Generate a new key pair of the specified type
121    pub fn generate(key_type: KeyType) -> Result<Self, KeyError> {
122        match key_type {
123            KeyType::Ed25519 => Ok(Self::generate_ed25519(None)),
124            KeyType::X25519 => Self::generate_x25519(None),
125            KeyType::P256 => Self::generate_p256(None),
126            KeyType::P384 => Self::generate_p384(None),
127            KeyType::Secp256k1 => Self::generate_secp256k1(None),
128            _ => Err(KeyError::UnsupportedKeyType(format!("{key_type:?}"))),
129        }
130    }
131
132    /// Generate a random Ed25519 signing key pair
133    #[cfg(feature = "ed25519")]
134    pub fn generate_ed25519(seed: Option<&[u8; 32]>) -> Self {
135        let kp = affinidi_crypto::ed25519::generate(seed);
136        Self::from_parts(kp.key_type, kp.private_bytes, kp.public_bytes, kp.jwk)
137    }
138
139    /// Generate a random X25519 key agreement key pair
140    #[cfg(feature = "ed25519")]
141    pub fn generate_x25519(seed: Option<&[u8; 32]>) -> Result<Self, KeyError> {
142        let kp = affinidi_crypto::ed25519::generate_x25519(seed);
143        Ok(Self::from_parts(
144            kp.key_type,
145            kp.private_bytes,
146            kp.public_bytes,
147            kp.jwk,
148        ))
149    }
150
151    /// Generate a random P-256 key pair
152    #[cfg(feature = "p256")]
153    pub fn generate_p256(seed: Option<&[u8]>) -> Result<Self, KeyError> {
154        let kp = affinidi_crypto::p256::generate(seed)?;
155        Ok(Self::from_parts(
156            kp.key_type,
157            kp.private_bytes,
158            kp.public_bytes,
159            kp.jwk,
160        ))
161    }
162
163    /// Generate a random P-384 key pair
164    #[cfg(feature = "p384")]
165    pub fn generate_p384(seed: Option<&[u8]>) -> Result<Self, KeyError> {
166        let kp = affinidi_crypto::p384::generate(seed)?;
167        Ok(Self::from_parts(
168            kp.key_type,
169            kp.private_bytes,
170            kp.public_bytes,
171            kp.jwk,
172        ))
173    }
174
175    /// Generate a random secp256k1 key pair
176    #[cfg(feature = "k256")]
177    pub fn generate_secp256k1(seed: Option<&[u8]>) -> Result<Self, KeyError> {
178        let kp = affinidi_crypto::secp256k1::generate(seed)?;
179        Ok(Self::from_parts(
180            kp.key_type,
181            kp.private_bytes,
182            kp.public_bytes,
183            kp.jwk,
184        ))
185    }
186
187    /// Create KeyMaterial from raw key parts
188    fn from_parts(
189        key_type: KeyType,
190        private_bytes: Vec<u8>,
191        public_bytes: Vec<u8>,
192        jwk: JWK,
193    ) -> Self {
194        KeyMaterial {
195            id: String::new(),
196            type_: KeyMaterialType::JsonWebKey2020,
197            format: KeyMaterialFormat::JWK(jwk),
198            private_bytes,
199            public_bytes,
200            key_type,
201        }
202    }
203
204    // ========== Conversion Methods ==========
205
206    /// Helper function to decode base64url to raw bytes
207    fn decode_base64url(input: &str) -> Result<Vec<u8>, KeyError> {
208        BASE64_URL_SAFE_NO_PAD
209            .decode(input)
210            .map_err(|e| KeyError::Key(format!("Failed to decode base64url: {e}")))
211    }
212
213    /// Creates KeyMaterial from a JWK
214    pub fn from_jwk(jwk: &JWK) -> Result<Self, KeyError> {
215        match &jwk.params {
216            Params::EC(params) => {
217                let mut x = Self::decode_base64url(&params.x)?;
218                let mut y = Self::decode_base64url(&params.y)?;
219                x.append(&mut y);
220
221                Ok(KeyMaterial {
222                    id: jwk.key_id.as_ref().unwrap_or(&String::new()).clone(),
223                    type_: KeyMaterialType::JsonWebKey2020,
224                    format: KeyMaterialFormat::JWK(jwk.clone()),
225                    private_bytes: Self::decode_base64url(
226                        params
227                            .d
228                            .as_ref()
229                            .ok_or_else(|| KeyError::Key("Missing private key".into()))?,
230                    )?,
231                    public_bytes: x,
232                    key_type: KeyType::try_from(params.curve.as_str())?,
233                })
234            }
235            Params::OKP(params) => Ok(KeyMaterial {
236                id: jwk.key_id.as_ref().unwrap_or(&String::new()).clone(),
237                type_: KeyMaterialType::JsonWebKey2020,
238                format: KeyMaterialFormat::JWK(jwk.clone()),
239                private_bytes: Self::decode_base64url(
240                    params
241                        .d
242                        .as_ref()
243                        .ok_or_else(|| KeyError::Key("Missing private key".into()))?,
244                )?,
245                public_bytes: Self::decode_base64url(&params.x)?,
246                key_type: KeyType::try_from(params.curve.as_str())?,
247            }),
248        }
249    }
250
251    /// Creates KeyMaterial from a JWK JSON value
252    pub fn from_jwk_value(key_id: &str, jwk: &Value) -> Result<Self, KeyError> {
253        let mut jwk: JWK = serde_json::from_value(jwk.clone())
254            .map_err(|e| KeyError::Key(format!("Failed to parse JWK: {e}")))?;
255        jwk.key_id = Some(key_id.to_string());
256        Self::from_jwk(&jwk)
257    }
258
259    /// Get the public key as multibase (Base58btc) encoded string
260    pub fn public_multibase(&self) -> Result<String, KeyError> {
261        let codec = Self::public_codec(self.key_type);
262        let bytes = self.compress_public_key()?;
263        Ok(affinidi_encoding::encode_multikey(codec, &bytes))
264    }
265
266    /// Get the private key as multibase (Base58btc) encoded string
267    pub fn private_multibase(&self) -> Result<String, KeyError> {
268        let codec = Self::private_codec(self.key_type);
269        Ok(affinidi_encoding::encode_multikey(
270            codec,
271            &self.private_bytes,
272        ))
273    }
274
275    /// Map KeyType to public key codec
276    fn public_codec(key_type: KeyType) -> u64 {
277        match key_type {
278            KeyType::Ed25519 => ED25519_PUB,
279            KeyType::X25519 => X25519_PUB,
280            KeyType::P256 => P256_PUB,
281            KeyType::P384 => P384_PUB,
282            KeyType::Secp256k1 => SECP256K1_PUB,
283            _ => 0,
284        }
285    }
286
287    /// Map KeyType to private key codec
288    fn private_codec(key_type: KeyType) -> u64 {
289        match key_type {
290            KeyType::Ed25519 => ED25519_PRIV,
291            KeyType::X25519 => X25519_PRIV,
292            KeyType::P256 => P256_PRIV,
293            KeyType::P384 => P384_PRIV,
294            KeyType::Secp256k1 => SECP256K1_PRIV,
295            _ => 0,
296        }
297    }
298
299    /// Compress public key for EC curves (returns as-is for OKP)
300    fn compress_public_key(&self) -> Result<Vec<u8>, KeyError> {
301        match self.key_type {
302            KeyType::Ed25519 | KeyType::X25519 => Ok(self.public_bytes.clone()),
303            KeyType::P256 | KeyType::Secp256k1 => {
304                if self.public_bytes.len() < 65 {
305                    return Err(KeyError::Key("Invalid public key length".into()));
306                }
307                let parity: u8 = if self.public_bytes[64].is_multiple_of(2) {
308                    0x02
309                } else {
310                    0x03
311                };
312                let mut compressed = vec![parity];
313                compressed.extend_from_slice(&self.public_bytes[1..33]);
314                Ok(compressed)
315            }
316            KeyType::P384 => {
317                if self.public_bytes.len() < 97 {
318                    return Err(KeyError::Key("Invalid public key length".into()));
319                }
320                let parity: u8 = if self.public_bytes[96].is_multiple_of(2) {
321                    0x02
322                } else {
323                    0x03
324                };
325                let mut compressed = vec![parity];
326                compressed.extend_from_slice(&self.public_bytes[1..49]);
327                Ok(compressed)
328            }
329            _ => Err(KeyError::UnsupportedKeyType(format!("{:?}", self.key_type))),
330        }
331    }
332
333    /// Get raw public key bytes
334    pub fn public_bytes(&self) -> &[u8] {
335        &self.public_bytes
336    }
337
338    /// Get raw private key bytes
339    pub fn private_bytes(&self) -> &[u8] {
340        &self.private_bytes
341    }
342
343    /// Get the key type
344    pub fn key_type(&self) -> KeyType {
345        self.key_type
346    }
347
348    /// Convert Ed25519 key to X25519 for key agreement
349    pub fn to_x25519(&self) -> Result<Self, KeyError> {
350        if self.key_type != KeyType::Ed25519 {
351            return Err(KeyError::Key(format!(
352                "Can only convert Ed25519 to X25519, got {:?}",
353                self.key_type
354            )));
355        }
356
357        let x25519_private = affinidi_crypto::ed25519::ed25519_private_to_x25519(
358            self.private_bytes
359                .first_chunk::<32>()
360                .ok_or_else(|| KeyError::Key("Invalid Ed25519 private key length".into()))?,
361        );
362
363        let x25519_sk = x25519_dalek::StaticSecret::from(x25519_private);
364        let x25519_pk = x25519_dalek::PublicKey::from(&x25519_sk);
365
366        let jwk = JWK {
367            key_id: None,
368            params: Params::OKP(affinidi_crypto::OctectParams {
369                curve: "X25519".to_string(),
370                x: BASE64_URL_SAFE_NO_PAD.encode(x25519_pk.as_bytes()),
371                d: Some(BASE64_URL_SAFE_NO_PAD.encode(x25519_sk.as_bytes())),
372            }),
373        };
374
375        let mut key = Self::from_jwk(&jwk)?;
376        key.id = self.id.clone();
377        Ok(key)
378    }
379}
380
381#[cfg(test)]
382mod tests {
383    use super::*;
384    use serde_json::json;
385
386    #[test]
387    fn test_from_jwk_ed25519() {
388        let jwk = json!({
389            "crv": "Ed25519",
390            "d": "ymjvUTVuUPzGF5ui12LfreO8bjZ_LbnOrh0sk0xCxMM",
391            "kty": "OKP",
392            "x": "d17TbZmkoYHZUQpzJTcuOtq0tjWYm8CKvKGYHDW6ZaE"
393        });
394
395        let key = KeyMaterial::from_jwk_value("test", &jwk).expect("Failed to parse JWK");
396        assert_eq!(key.key_type, KeyType::Ed25519);
397        assert!(!key.private_bytes.is_empty());
398        assert!(!key.public_bytes.is_empty());
399    }
400
401    #[test]
402    fn test_to_x25519() {
403        let jwk = json!({
404            "crv": "Ed25519",
405            "d": "ymjvUTVuUPzGF5ui12LfreO8bjZ_LbnOrh0sk0xCxMM",
406            "kty": "OKP",
407            "x": "d17TbZmkoYHZUQpzJTcuOtq0tjWYm8CKvKGYHDW6ZaE"
408        });
409
410        let ed25519 = KeyMaterial::from_jwk_value("test", &jwk).expect("Failed to parse JWK");
411        let x25519 = ed25519.to_x25519().expect("Failed to convert to X25519");
412
413        assert_eq!(x25519.key_type, KeyType::X25519);
414    }
415}