affinidi_did_common/did_method/
key.rs1use 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#[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#[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#[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#[derive(Deserialize)]
61struct KeyMaterialShadow {
62 id: String,
63 #[serde(rename = "type")]
64 type_: KeyMaterialType,
65 #[serde(flatten)]
66 format: KeyMaterialFormat,
67}
68
69#[derive(Debug, Clone, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
75#[serde(try_from = "KeyMaterialShadow")]
76pub struct KeyMaterial {
77 pub id: String,
79
80 #[serde(rename = "type")]
82 pub type_: KeyMaterialType,
83
84 #[serde(flatten)]
86 pub format: KeyMaterialFormat,
87
88 #[serde(skip)]
90 pub(crate) private_bytes: Vec<u8>,
91
92 #[serde(skip)]
94 pub(crate) public_bytes: Vec<u8>,
95
96 #[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 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 #[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 #[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 #[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 #[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 #[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 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 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 pub fn from_jwk(jwk: &JWK) -> Result<Self, KeyError> {
215 match &jwk.params {
216 Params::EC(params) => {
217 let mut x = Self::decode_base64url(¶ms.x)?;
218 let mut y = Self::decode_base64url(¶ms.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(¶ms.x)?,
246 key_type: KeyType::try_from(params.curve.as_str())?,
247 }),
248 }
249 }
250
251 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 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 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 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 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 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 pub fn public_bytes(&self) -> &[u8] {
335 &self.public_bytes
336 }
337
338 pub fn private_bytes(&self) -> &[u8] {
340 &self.private_bytes
341 }
342
343 pub fn key_type(&self) -> KeyType {
345 self.key_type
346 }
347
348 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}