1#[cfg(feature = "slh-dsa")]
7use crate::multicodec::SLH_DSA_SHA2_128S_PUB;
8#[cfg(feature = "ml-dsa")]
9use crate::multicodec::{
10 ML_DSA_44_PRIV_SEED, ML_DSA_44_PUB, ML_DSA_65_PRIV_SEED, ML_DSA_65_PUB, ML_DSA_87_PRIV_SEED,
11 ML_DSA_87_PUB,
12};
13use crate::{
14 errors::{Result, SecretsResolverError},
15 multicodec::{
16 ED25519_PRIV, ED25519_PUB, MultiEncoded, MultiEncodedBuf, P256_PRIV, P256_PUB, P384_PRIV,
17 P384_PUB, P521_PRIV, SECP256K1_PRIV, SECP256K1_PUB, X25519_PRIV, X25519_PUB,
18 },
19};
20pub use affinidi_crypto::KeyType;
21use affinidi_crypto::{JWK, Params};
22use base58::ToBase58;
23use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD};
24use serde::{Deserialize, Serialize};
25use serde_json::{Value, json};
26use sha2::{Digest, Sha256};
27use tracing::warn;
28use unsigned_varint::encode as varint_encode;
29use x25519_dalek::{PublicKey, StaticSecret};
30use zeroize::{Zeroize, ZeroizeOnDrop};
31
32#[derive(Deserialize)]
35struct SecretShadow {
36 id: String,
37 #[serde(rename = "type")]
38 type_: SecretType,
39 #[serde(flatten)]
40 secret_material: SecretMaterial,
41}
42
43#[derive(Debug, Clone, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
45#[serde(try_from = "SecretShadow")]
46pub struct Secret {
47 pub id: String,
49
50 #[serde(rename = "type")]
52 pub type_: SecretType,
53
54 #[serde(flatten)]
56 pub secret_material: SecretMaterial,
57
58 #[serde(skip)]
60 pub(crate) private_bytes: Vec<u8>,
61
62 #[serde(skip)]
64 pub(crate) public_bytes: Vec<u8>,
65
66 #[serde(skip)]
68 pub(crate) key_type: KeyType,
69}
70
71impl TryFrom<SecretShadow> for Secret {
74 type Error = SecretsResolverError;
75
76 fn try_from(shadow: SecretShadow) -> Result<Self> {
77 match shadow.secret_material {
78 SecretMaterial::JWK(jwk) => {
79 let mut secret = Secret::from_jwk(&jwk)?;
80 secret.id = shadow.id;
81 secret.type_ = shadow.type_;
82 Ok(secret)
83 }
84 SecretMaterial::PrivateKeyMultibase(private) => {
85 Secret::from_multibase(&private, Some(&shadow.id))
86 }
87 _ => Err(SecretsResolverError::KeyError(
88 "Unsupported secret material type".into(),
89 )),
90 }
91 }
92}
93
94fn compress_ec_point(
99 public_bytes: &[u8],
100 compressed_len: usize,
101 codec: u64,
102) -> Result<MultiEncodedBuf> {
103 let coord_len = compressed_len - 1;
104 let required = 1 + 2 * coord_len; if public_bytes.len() < required {
106 return Err(SecretsResolverError::KeyError(format!(
107 "public_bytes too short to compress: expected >= {required} bytes, got {}",
108 public_bytes.len()
109 )));
110 }
111 let last_y_byte = public_bytes[required - 1];
112 let parity: u8 = if last_y_byte.is_multiple_of(2) {
113 0x02
114 } else {
115 0x03
116 };
117 let mut compressed: Vec<u8> = Vec::with_capacity(compressed_len);
118 compressed.push(parity);
119 compressed.extend_from_slice(&public_bytes[1..=coord_len]);
120 Ok(MultiEncodedBuf::encode_bytes(codec, &compressed))
121}
122
123impl Secret {
124 fn convert_to_raw(input: &str) -> Result<Vec<u8>> {
126 BASE64_URL_SAFE_NO_PAD
127 .decode(input)
128 .map_err(|e| SecretsResolverError::KeyError(format!("Failed to decode base64url: {e}")))
129 }
130
131 pub fn from_jwk(jwk: &JWK) -> Result<Self> {
133 match &jwk.params {
134 Params::EC(params) => {
135 let mut x = Secret::convert_to_raw(¶ms.x)?;
136 let mut y = Secret::convert_to_raw(¶ms.y)?;
137
138 x.append(&mut y);
139 Ok(Secret {
140 id: jwk.key_id.as_ref().unwrap_or(&"".to_string()).to_string(),
141 type_: SecretType::JsonWebKey2020,
142 secret_material: SecretMaterial::JWK(jwk.to_owned()),
143 private_bytes: Secret::convert_to_raw(params.d.as_ref().ok_or(
144 SecretsResolverError::KeyError(
145 "Must have secret key available".to_string(),
146 ),
147 )?)?,
148 public_bytes: x,
149 key_type: KeyType::try_from(params.curve.as_str())?,
150 })
151 }
152 Params::OKP(params) => Ok(Secret {
153 id: jwk.key_id.as_ref().unwrap_or(&"".to_string()).to_string(),
154 type_: SecretType::JsonWebKey2020,
155 secret_material: SecretMaterial::JWK(jwk.to_owned()),
156 private_bytes: Secret::convert_to_raw(params.d.as_ref().ok_or(
157 SecretsResolverError::KeyError("Must have secret key available".to_string()),
158 )?)?,
159 public_bytes: Secret::convert_to_raw(¶ms.x)?,
160 key_type: KeyType::try_from(params.curve.as_str())?,
161 }),
162 }
163 }
164
165 pub fn from_str(key_id: &str, jwk: &Value) -> Result<Self> {
183 let mut jwk: JWK = serde_json::from_value(jwk.to_owned())
184 .map_err(|e| SecretsResolverError::KeyError(format!("Failed to parse JWK: {e}")))?;
185
186 jwk.key_id = Some(key_id.to_string());
187 Self::from_jwk(&jwk)
188 }
189
190 pub fn from_multibase(private: &str, kid: Option<&str>) -> Result<Self> {
197 let private_bytes = multibase::decode(private).map_err(|e| {
198 SecretsResolverError::KeyError(format!("Failed to decode private key: {e}"))
199 })?;
200
201 let private_bytes = MultiEncoded::new(private_bytes.1.as_slice())?;
202
203 match private_bytes.codec() {
204 ED25519_PRIV => {
205 if private_bytes.data().len() != 32 {
206 return Err(SecretsResolverError::KeyError(
207 "Invalid ED25519 private key length".into(),
208 ));
209 }
210 let mut pb: [u8; 32] = [0; 32];
211 pb.copy_from_slice(private_bytes.data());
212
213 let secret = Secret::generate_ed25519(kid, Some(&pb));
214 pb.zeroize();
215 Ok(secret)
216 }
217 X25519_PRIV => {
218 if private_bytes.data().len() != 32 {
219 return Err(SecretsResolverError::KeyError(
220 "Invalid X25519 private key length".into(),
221 ));
222 }
223 let mut pb: [u8; 32] = [0; 32];
224 pb.copy_from_slice(private_bytes.data());
225
226 let secret = Secret::generate_x25519(kid, Some(&pb));
227 pb.zeroize();
228 secret
229 }
230 P256_PRIV => {
231 if private_bytes.data().len() != 32 {
232 return Err(SecretsResolverError::KeyError(
233 "Invalid P256 private key length".into(),
234 ));
235 }
236
237 Secret::generate_p256(kid, Some(private_bytes.data()))
238 }
239 P384_PRIV => Secret::generate_p384(kid, Some(private_bytes.data())),
240 SECP256K1_PRIV => Secret::generate_secp256k1(kid, Some(private_bytes.data())),
241 #[cfg(feature = "ml-dsa")]
242 ML_DSA_44_PRIV_SEED => {
243 if private_bytes.data().len() != 32 {
244 return Err(SecretsResolverError::KeyError(
245 "Invalid ML-DSA-44 seed length".into(),
246 ));
247 }
248 let mut pb: [u8; 32] = [0; 32];
249 pb.copy_from_slice(private_bytes.data());
250 let s = Secret::generate_ml_dsa_44(kid, Some(&pb));
251 pb.zeroize();
252 Ok(s)
253 }
254 #[cfg(feature = "ml-dsa")]
255 ML_DSA_65_PRIV_SEED => {
256 if private_bytes.data().len() != 32 {
257 return Err(SecretsResolverError::KeyError(
258 "Invalid ML-DSA-65 seed length".into(),
259 ));
260 }
261 let mut pb: [u8; 32] = [0; 32];
262 pb.copy_from_slice(private_bytes.data());
263 let s = Secret::generate_ml_dsa_65(kid, Some(&pb));
264 pb.zeroize();
265 Ok(s)
266 }
267 #[cfg(feature = "ml-dsa")]
268 ML_DSA_87_PRIV_SEED => {
269 if private_bytes.data().len() != 32 {
270 return Err(SecretsResolverError::KeyError(
271 "Invalid ML-DSA-87 seed length".into(),
272 ));
273 }
274 let mut pb: [u8; 32] = [0; 32];
275 pb.copy_from_slice(private_bytes.data());
276 let s = Secret::generate_ml_dsa_87(kid, Some(&pb));
277 pb.zeroize();
278 Ok(s)
279 }
280 _ => Err(SecretsResolverError::KeyError(
281 "Unsupported key type in from_multibase".into(),
282 )),
283 }
284 }
285
286 pub fn decode_multikey(key: &str) -> Result<Vec<u8>> {
288 let bytes = multibase::decode(key).map_err(|e| {
289 SecretsResolverError::KeyError(format!("Failed to multibase.decode key: {e}"))
290 })?;
291 let bytes = MultiEncoded::new(bytes.1.as_slice()).map_err(|e| {
292 SecretsResolverError::KeyError(format!("Failed to load decoded key: {e}"))
293 })?;
294 Ok(bytes.data().to_vec())
295 }
296
297 pub fn get_public_keymultibase(&self) -> Result<String> {
299 let encoded = match self.key_type {
300 KeyType::Ed25519 => MultiEncodedBuf::encode_bytes(ED25519_PUB, &self.public_bytes),
301 KeyType::X25519 => MultiEncodedBuf::encode_bytes(X25519_PUB, &self.public_bytes),
302 KeyType::P256 => compress_ec_point(&self.public_bytes, 33, P256_PUB)?,
303 KeyType::P384 => compress_ec_point(&self.public_bytes, 49, P384_PUB)?,
304 KeyType::P521 => {
305 return Err(SecretsResolverError::KeyError(
306 "P-521 is not supported".to_string(),
307 ));
308 }
309 KeyType::Secp256k1 => compress_ec_point(&self.public_bytes, 33, SECP256K1_PUB)?,
310 #[cfg(feature = "ml-dsa")]
311 KeyType::MlDsa44 => MultiEncodedBuf::encode_bytes(ML_DSA_44_PUB, &self.public_bytes),
312 #[cfg(feature = "ml-dsa")]
313 KeyType::MlDsa65 => MultiEncodedBuf::encode_bytes(ML_DSA_65_PUB, &self.public_bytes),
314 #[cfg(feature = "ml-dsa")]
315 KeyType::MlDsa87 => MultiEncodedBuf::encode_bytes(ML_DSA_87_PUB, &self.public_bytes),
316 #[cfg(feature = "slh-dsa")]
317 KeyType::SlhDsaSha2_128s => {
318 MultiEncodedBuf::encode_bytes(SLH_DSA_SHA2_128S_PUB, &self.public_bytes)
319 }
320 _ => {
321 return Err(SecretsResolverError::KeyError(
322 "Unsupported key type".into(),
323 ));
324 }
325 };
326 Ok(multibase::encode(
327 multibase::Base::Base58Btc,
328 encoded.into_bytes(),
329 ))
330 }
331
332 pub fn get_public_keymultibase_hash(&self) -> Result<String> {
335 let key = self.get_public_keymultibase()?;
336
337 Secret::base58_hash_string(&key)
338 }
339
340 pub fn base58_hash_string(key: &str) -> Result<String> {
343 let hash = Sha256::digest(key.as_bytes());
344 let mut code_buf = varint_encode::u64_buffer();
347 let code_varint = varint_encode::u64(0x12, &mut code_buf);
348 let mut len_buf = varint_encode::u64_buffer();
349 let len_varint = varint_encode::u64(hash.len() as u64, &mut len_buf);
350 let mut bytes = Vec::with_capacity(code_varint.len() + len_varint.len() + hash.len());
351 bytes.extend_from_slice(code_varint);
352 bytes.extend_from_slice(len_varint);
353 bytes.extend_from_slice(&hash);
354 Ok(bytes.to_base58())
355 }
356
357 pub fn get_private_keymultibase(&self) -> Result<String> {
359 let encoded = match self.key_type {
360 KeyType::Ed25519 => MultiEncodedBuf::encode_bytes(ED25519_PRIV, &self.private_bytes),
361 KeyType::X25519 => MultiEncodedBuf::encode_bytes(X25519_PRIV, &self.private_bytes),
362 KeyType::P256 => MultiEncodedBuf::encode_bytes(P256_PRIV, &self.private_bytes),
363 KeyType::P384 => MultiEncodedBuf::encode_bytes(P384_PRIV, &self.private_bytes),
364 KeyType::P521 => MultiEncodedBuf::encode_bytes(P521_PRIV, &self.private_bytes),
365 KeyType::Secp256k1 => {
366 MultiEncodedBuf::encode_bytes(SECP256K1_PRIV, &self.private_bytes)
367 }
368 #[cfg(feature = "ml-dsa")]
369 KeyType::MlDsa44 => {
370 MultiEncodedBuf::encode_bytes(ML_DSA_44_PRIV_SEED, &self.private_bytes)
371 }
372 #[cfg(feature = "ml-dsa")]
373 KeyType::MlDsa65 => {
374 MultiEncodedBuf::encode_bytes(ML_DSA_65_PRIV_SEED, &self.private_bytes)
375 }
376 #[cfg(feature = "ml-dsa")]
377 KeyType::MlDsa87 => {
378 MultiEncodedBuf::encode_bytes(ML_DSA_87_PRIV_SEED, &self.private_bytes)
379 }
380 #[cfg(feature = "slh-dsa")]
381 KeyType::SlhDsaSha2_128s => {
382 return Err(SecretsResolverError::KeyError(
383 "SLH-DSA has no private-key multicodec registered; persist raw private_bytes \
384 instead of encoding as multikey"
385 .into(),
386 ));
387 }
388 _ => {
389 return Err(SecretsResolverError::KeyError(
390 "Unsupported key type".into(),
391 ));
392 }
393 };
394 Ok(multibase::encode(
395 multibase::Base::Base58Btc,
396 encoded.into_bytes(),
397 ))
398 }
399
400 pub fn get_public_bytes(&self) -> &[u8] {
402 self.public_bytes.as_slice()
403 }
404
405 pub fn get_private_bytes(&self) -> &[u8] {
407 self.private_bytes.as_slice()
408 }
409
410 pub fn get_key_type(&self) -> KeyType {
412 self.key_type
413 }
414
415 pub fn to_x25519(&self) -> Result<Secret> {
416 if self.key_type != KeyType::Ed25519 {
417 warn!(
418 "Can only convert ED25519 to X25519! Current key type is {:#?}",
419 self.key_type
420 );
421 Err(SecretsResolverError::KeyError(format!(
422 "Can only convert ED25519 to X25519! Current key type is {:#?}",
423 self.key_type
424 )))
425 } else {
426 let x25519_secret = affinidi_crypto::ed25519::ed25519_private_to_x25519(
428 self.private_bytes.first_chunk::<32>().unwrap(),
429 );
430
431 let x25519_sk = StaticSecret::from(x25519_secret);
432 let x25519_pk = PublicKey::from(&x25519_sk);
433
434 let secret = BASE64_URL_SAFE_NO_PAD.encode(x25519_sk.as_bytes());
435 let public = BASE64_URL_SAFE_NO_PAD.encode(x25519_pk.as_bytes());
436
437 let jwk = json!({
438 "crv": "X25519",
439 "d": secret,
440 "kty": "OKP",
441 "x": public
442 });
443
444 Secret::from_str(&self.id, &jwk)
445 }
446 }
447}
448
449#[derive(Debug, Clone, Deserialize, Serialize, Zeroize)]
451pub enum SecretType {
452 JsonWebKey2020,
453 X25519KeyAgreementKey2019,
454 X25519KeyAgreementKey2020,
455 Ed25519VerificationKey2018,
456 Ed25519VerificationKey2020,
457 EcdsaSecp256k1VerificationKey2019,
458 Multikey,
459 Other,
460}
461
462#[derive(Debug, Clone, Deserialize, Serialize, Zeroize)]
466#[serde(rename_all = "camelCase")]
467pub enum SecretMaterial {
468 #[serde(rename = "privateKeyJwk")]
469 JWK(JWK),
470
471 PrivateKeyMultibase(String),
472
473 Base58 {
474 private_key_base58: String,
475 },
476
477 Multibase {
480 private_key_multibase: String,
481 },
482}
483
484#[cfg(test)]
485mod tests {
486 use super::Secret;
487 use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD};
488 use serde_json::json;
489
490 #[test]
491 fn check_hash() {
492 let input = "z6MkgfFvvWA7sw8WkNWyK3y74kwNVvWc7Qrs5tWnsnqMfLD3";
493 let output = Secret::base58_hash_string(input).expect("Hash of input");
494 assert_eq!(&output, "QmY1kaguPMgjndEh1sdDZ8kdjX4Uc1SW4vziMfgWC6ndnJ")
495 }
496
497 #[test]
498 fn check_hash_bad() {
499 let input = "z6MkgfFvvWA7sw8WkNWyK3y74kwNVvWc7Qrs5tWnsnqMfLD4";
500 let output = Secret::base58_hash_string(input).expect("Hash of input");
501 assert_ne!(&output, "QmY1kaguPMgjndEh1sdDZ8kdjX4Uc1SW4vziMfgWC6ndnJ")
502 }
503
504 #[test]
505 fn check_x25519() {
506 let x25519_sk_bytes: [u8; 32] = [
514 200, 255, 64, 61, 17, 52, 112, 33, 205, 71, 186, 13, 131, 12, 241, 136, 223, 5, 152,
515 40, 95, 187, 83, 168, 142, 10, 234, 215, 70, 210, 148, 104,
516 ];
517
518 let jwk = json!({
520 "crv": "Ed25519",
521 "d": "ymjvUTVuUPzGF5ui12LfreO8bjZ_LbnOrh0sk0xCxMM",
522 "kty": "OKP",
523 "x": "d17TbZmkoYHZUQpzJTcuOtq0tjWYm8CKvKGYHDW6ZaE"
524 });
525
526 let ed25519 = Secret::from_str("test", &jwk).unwrap();
527
528 let x25519 = ed25519
529 .to_x25519()
530 .expect("Couldn't convert ed25519 to x25519");
531
532 assert_eq!(x25519.private_bytes.as_slice(), x25519_sk_bytes);
533 }
534
535 #[test]
536 fn check_secret_deserialize() {
537 let txt = r#"{
538 "id": "did:web:localhost%3A7037:mediator:v1:.well-known#key-2",
539 "type": "JsonWebKey2020",
540 "privateKeyJwk": {
541 "crv": "secp256k1",
542 "d": "Cs5xn7WCkUWEua5vGxjP9_wBzIzMtEwjQ4KWKHHQR14",
543 "kty": "EC",
544 "x": "Lk1FY8MmyLjBswU4KbLoBQ_1THZJBMx2n6aIBXt1uXo",
545 "y": "tEv7EQHj4g4njOfrsjjDJBPKOI9RGWWMS8NYClo2cqo"
546 }
547 }"#;
548
549 let secret = serde_json::from_str::<Secret>(txt);
550
551 assert!(secret.is_ok());
552 }
553
554 #[test]
555 fn from_multiencode_ed25519() {
556 let seed = BASE64_URL_SAFE_NO_PAD
557 .decode("oihAhqs-h9V9rq6KYEhiEWwdBDpTI7xL0EEiwC9heFg")
558 .expect("Couldn't decode ed25519 BASE64 encoding");
559
560 let public_bytes = BASE64_URL_SAFE_NO_PAD
561 .decode("eC1vNebw6IJ8SJ4Tg9g2Q9W-Zy8xIS80byxTZXlPaHk")
562 .expect("Couldn't BASE64 decode ed25519 public bytes");
563
564 assert_eq!(seed.len(), 32);
565 let mut private_bytes: [u8; 32] = [0; 32];
566 private_bytes.copy_from_slice(seed.as_slice());
567
568 let secret = Secret::generate_ed25519(None, Some(&private_bytes));
569
570 assert_eq!(
571 secret.get_private_keymultibase().unwrap(),
572 "z3u2c8oS2oKgATvakQzVF66EAcZWJqPUzGQzWMUTKnFkv5DR"
573 );
574 assert_eq!(secret.get_public_bytes(), public_bytes.as_slice());
575 assert_eq!(secret.get_private_bytes(), private_bytes.as_slice());
576
577 let secret2 =
578 Secret::from_multibase("z3u2c8oS2oKgATvakQzVF66EAcZWJqPUzGQzWMUTKnFkv5DR", None)
579 .expect("Failed to transform ed25519 to secret");
580
581 assert_eq!(
582 secret2.get_public_keymultibase().unwrap(),
583 secret.get_public_keymultibase().unwrap()
584 );
585 assert_eq!(secret2.get_public_bytes(), secret.get_public_bytes());
586 assert_eq!(secret2.get_private_bytes(), secret.get_private_bytes());
587
588 assert_eq!(
589 secret2.get_private_keymultibase().unwrap(),
590 secret.get_private_keymultibase().unwrap()
591 );
592 }
593
594 #[test]
595 fn from_multiencode_x25519() {
596 let seed = BASE64_URL_SAFE_NO_PAD
597 .decode("eYN37ZX0ij4TYdklZax2jiRiyHYMNOzwW2bvNauAzKk")
598 .expect("Couldn't decode x25519 BASE64 encoding");
599
600 let public_bytes = BASE64_URL_SAFE_NO_PAD
601 .decode("Ephwf5xVmhVnDj2KtIPDKcGYBG9CQR_mZKlRqETZ62U")
602 .expect("Couldn't BASE64 decode x25519 public bytes");
603
604 assert_eq!(seed.len(), 32);
605 let mut private_bytes: [u8; 32] = [0; 32];
606 private_bytes.copy_from_slice(seed.as_slice());
607
608 let secret = Secret::generate_x25519(None, Some(&private_bytes))
609 .expect("x25519 generate secret failed");
610
611 assert_eq!(
612 secret.get_private_keymultibase().unwrap(),
613 "z3weexK9erGUKF41d3tJoDu2Fetx1xnsC7WhFWnjuCJXJGxp"
614 );
615 assert_eq!(secret.get_public_bytes(), public_bytes.as_slice());
616 assert_eq!(secret.get_private_bytes(), private_bytes.as_slice());
617
618 let secret2 =
619 Secret::from_multibase("z3weexK9erGUKF41d3tJoDu2Fetx1xnsC7WhFWnjuCJXJGxp", None)
620 .expect("Failed to transform x25519 to secret");
621
622 assert_eq!(
623 secret2.get_public_keymultibase().unwrap(),
624 secret.get_public_keymultibase().unwrap()
625 );
626 assert_eq!(secret2.get_public_bytes(), secret.get_public_bytes());
627 assert_eq!(secret2.get_private_bytes(), secret.get_private_bytes());
628
629 assert_eq!(
630 secret2.get_private_keymultibase().unwrap(),
631 secret.get_private_keymultibase().unwrap()
632 );
633 }
634
635 #[test]
636 fn from_multiencode_p256() {
637 let seed = BASE64_URL_SAFE_NO_PAD
638 .decode("B5ZIiXYkpEPczVbyWP85H75wrBifiRcFgtqYvI5I9AI")
639 .expect("Couldn't decode P-256 BASE64 encoding");
640
641 let pub_x = BASE64_URL_SAFE_NO_PAD
642 .decode("Iy3cHBWCRhcjohhS-iSucYMUNjH77DIQRSdn-NylcCw")
643 .expect("Couldn't BASE64 decode P-256 X public bytes");
644
645 let pub_y = BASE64_URL_SAFE_NO_PAD
646 .decode("p9MikGh-O3nbLWA-6tP4Oanch5AF3ZhRD907tQojH3k")
647 .expect("Couldn't BASE64 decode P-256 Y public bytes");
648
649 let public_bytes = [vec![4], pub_x, pub_y].concat();
650
651 assert_eq!(seed.len(), 32);
652 let mut private_bytes: [u8; 32] = [0; 32];
653 private_bytes.copy_from_slice(seed.as_slice());
654
655 let secret =
656 Secret::generate_p256(None, Some(&private_bytes)).expect("P256 secret generate failed");
657
658 assert_eq!(
659 secret.get_private_keymultibase().unwrap(),
660 "z42tiPvqM1uFz2QxbF7wTsQkfAf3hCsq1Uf9JbUMRaRiV1yb"
661 );
662 assert_eq!(secret.get_public_bytes(), public_bytes.as_slice());
663 assert_eq!(secret.get_private_bytes(), private_bytes.as_slice());
664
665 let secret2 =
666 Secret::from_multibase("z42tiPvqM1uFz2QxbF7wTsQkfAf3hCsq1Uf9JbUMRaRiV1yb", None)
667 .expect("Failed to transform P256 to secret");
668
669 assert_eq!(
670 secret2.get_public_keymultibase().unwrap(),
671 secret.get_public_keymultibase().unwrap()
672 );
673 assert_eq!(secret2.get_public_bytes(), secret.get_public_bytes());
674 assert_eq!(secret2.get_private_bytes(), secret.get_private_bytes());
675
676 assert_eq!(
677 secret2.get_private_keymultibase().unwrap(),
678 secret.get_private_keymultibase().unwrap()
679 );
680 }
681
682 #[test]
683 fn from_multiencode_p384() {
684 let seed = BASE64_URL_SAFE_NO_PAD
685 .decode("nka5zKVpVpOdCKdZZgnZ-VaSXk6V_ovYibzr2nf-mKAgct6wBdvWCXWLaNr80zY0")
686 .expect("Couldn't decode P-384 BASE64 encoding");
687
688 let pub_x = BASE64_URL_SAFE_NO_PAD
689 .decode("uitQkpTA3Vw8t_qOGrdLlbIzdzF0K9NsScgsVgmpQdQJgshCifOCUehxeazzL-Ow")
690 .expect("Couldn't BASE64 decode P-384 X public bytes");
691
692 let pub_y = BASE64_URL_SAFE_NO_PAD
693 .decode("4BIcrueQfhxfnrqToZEOujOfJOmwEsWJAdFNZ9dksIBCnWiCLBEn2HnR7ikyyPMJ")
694 .expect("Couldn't BASE64 decode P-384 Y public bytes");
695
696 let public_bytes = [vec![4], pub_x, pub_y].concat();
697
698 assert_eq!(seed.len(), 48);
699 let mut private_bytes: [u8; 48] = [0; 48];
700 private_bytes.copy_from_slice(seed.as_slice());
701
702 let secret =
703 Secret::generate_p384(None, Some(&private_bytes)).expect("P384 secret generate failed");
704
705 assert_eq!(
706 secret.get_private_keymultibase().unwrap(),
707 "z2fapqKp6mPoQCwkQzvL9Ns35Y57R4LRRfVwbXoSTQjTHdjD4MqFZnw5PueieuTWG4pN5q"
708 );
709 assert_eq!(secret.get_public_bytes(), public_bytes.as_slice());
710 assert_eq!(secret.get_private_bytes(), private_bytes.as_slice());
711
712 let secret2 = Secret::from_multibase(
713 "z2fapqKp6mPoQCwkQzvL9Ns35Y57R4LRRfVwbXoSTQjTHdjD4MqFZnw5PueieuTWG4pN5q",
714 None,
715 )
716 .expect("Failed to transform P384 to secret");
717
718 assert_eq!(
719 secret2.get_public_keymultibase().unwrap(),
720 secret.get_public_keymultibase().unwrap()
721 );
722 assert_eq!(secret2.get_public_bytes(), secret.get_public_bytes());
723 assert_eq!(secret2.get_private_bytes(), secret.get_private_bytes());
724
725 assert_eq!(
726 secret2.get_private_keymultibase().unwrap(),
727 secret.get_private_keymultibase().unwrap()
728 );
729 }
730
731 #[cfg(feature = "ml-dsa")]
732 #[test]
733 fn from_multiencode_ml_dsa_44() {
734 let seed = [7u8; 32];
735 let secret = Secret::generate_ml_dsa_44(Some("k-44"), Some(&seed));
736
737 let mb = secret.get_private_keymultibase().expect("encode priv");
738 let pub_mb = secret.get_public_keymultibase().expect("encode pub");
739
740 let secret2 = Secret::from_multibase(&mb, Some("k-44")).expect("decode");
741 assert_eq!(secret2.get_public_bytes(), secret.get_public_bytes());
742 assert_eq!(secret2.get_private_bytes(), secret.get_private_bytes());
743 assert_eq!(
744 secret2.get_public_keymultibase().unwrap(),
745 pub_mb,
746 "public multikey roundtrip"
747 );
748 }
749
750 #[cfg(any(feature = "ml-dsa", feature = "slh-dsa"))]
752 fn varint_prefix(codec: u64) -> Vec<u8> {
753 let mut buf = [0u8; 10];
754 use unsigned_varint::encode;
755 let slice = encode::u64(codec, &mut buf);
756 slice.to_vec()
757 }
758
759 #[cfg(feature = "ml-dsa")]
764 #[test]
765 fn ml_dsa_multikey_uses_registered_codecs() {
766 use crate::multicodec::{
767 ML_DSA_44_PRIV_SEED, ML_DSA_44_PUB, ML_DSA_65_PRIV_SEED, ML_DSA_65_PUB,
768 ML_DSA_87_PRIV_SEED, ML_DSA_87_PUB,
769 };
770 use affinidi_crypto::KeyType;
771
772 let cases: &[(KeyType, u64, u64, usize, usize)] = &[
773 (
775 KeyType::MlDsa44,
776 ML_DSA_44_PUB,
777 ML_DSA_44_PRIV_SEED,
778 1312,
779 32,
780 ),
781 (
782 KeyType::MlDsa65,
783 ML_DSA_65_PUB,
784 ML_DSA_65_PRIV_SEED,
785 1952,
786 32,
787 ),
788 (
789 KeyType::MlDsa87,
790 ML_DSA_87_PUB,
791 ML_DSA_87_PRIV_SEED,
792 2592,
793 32,
794 ),
795 ];
796
797 for (kt, pub_code, priv_code, pub_len, priv_len) in cases {
798 let s = match kt {
799 KeyType::MlDsa44 => Secret::generate_ml_dsa_44(None, Some(&[1u8; 32])),
800 KeyType::MlDsa65 => Secret::generate_ml_dsa_65(None, Some(&[1u8; 32])),
801 KeyType::MlDsa87 => Secret::generate_ml_dsa_87(None, Some(&[1u8; 32])),
802 _ => unreachable!(),
803 };
804
805 let pub_mb = s.get_public_keymultibase().unwrap();
806 let (_, pub_raw) = multibase::decode(&pub_mb).unwrap();
807 let expected = varint_prefix(*pub_code);
808 assert_eq!(
809 &pub_raw[..expected.len()],
810 expected.as_slice(),
811 "{kt:?} pub codec prefix mismatch (expected {pub_code:#06x})"
812 );
813 assert_eq!(pub_raw.len() - expected.len(), *pub_len);
814
815 let priv_mb = s.get_private_keymultibase().unwrap();
816 let (_, priv_raw) = multibase::decode(&priv_mb).unwrap();
817 let expected = varint_prefix(*priv_code);
818 assert_eq!(
819 &priv_raw[..expected.len()],
820 expected.as_slice(),
821 "{kt:?} priv-seed codec prefix mismatch (expected {priv_code:#06x})"
822 );
823 assert_eq!(priv_raw.len() - expected.len(), *priv_len);
824 }
825 }
826
827 #[cfg(feature = "slh-dsa")]
828 #[test]
829 fn slh_dsa_multikey_uses_registered_public_codec() {
830 use crate::multicodec::SLH_DSA_SHA2_128S_PUB;
831 let s = Secret::generate_slh_dsa_sha2_128s(None);
832 let pub_mb = s.get_public_keymultibase().unwrap();
833 let (_, raw) = multibase::decode(&pub_mb).unwrap();
834 let expected = varint_prefix(SLH_DSA_SHA2_128S_PUB);
835 assert_eq!(&raw[..expected.len()], expected.as_slice());
836 assert_eq!(raw.len() - expected.len(), 32);
837 }
838
839 #[test]
840 fn get_public_keymultibase_bounds_check_p256() {
841 let mut s = Secret::generate_p256(None, Some(&[1u8; 32])).unwrap();
845 s.public_bytes.clear(); let err = s.get_public_keymultibase().unwrap_err();
847 let msg = format!("{err}");
848 assert!(
849 msg.contains("too short"),
850 "expected bounds-check error, got: {msg}"
851 );
852 }
853
854 #[test]
855 fn get_public_keymultibase_bounds_check_p384() {
856 let mut s = Secret::generate_p384(None, Some(&[1u8; 48])).unwrap();
857 s.public_bytes.truncate(10);
858 assert!(s.get_public_keymultibase().is_err());
859 }
860
861 #[cfg(feature = "slh-dsa")]
862 #[test]
863 fn slh_dsa_private_multibase_unsupported() {
864 let secret = Secret::generate_slh_dsa_sha2_128s(Some("k-slh"));
867 assert!(secret.get_private_keymultibase().is_err());
868 assert!(secret.get_public_keymultibase().is_ok());
870 }
871
872 #[test]
873 fn from_multiencode_secp256k1() {
874 let seed = BASE64_URL_SAFE_NO_PAD
875 .decode("CzR8XKYmrxbeEeUKojSgXUskLmGjbLXFf4CoJd6he6A")
876 .expect("Couldn't decode secp256k1 BASE64 encoding");
877
878 let pub_x = BASE64_URL_SAFE_NO_PAD
879 .decode("jcGMDsxKBME8GmaN_-XTaAEKk2ET6ajWe_8-2RsU-is")
880 .expect("Couldn't BASE64 decode secp256k1 X public bytes");
881
882 let pub_y = BASE64_URL_SAFE_NO_PAD
883 .decode("9ECTinCwW9bA36fmUBg0_iu0oyLR-Tn54guX8exrUjM")
884 .expect("Couldn't BASE64 decode secp256k1 Y public bytes");
885
886 let public_bytes = [vec![4], pub_x, pub_y].concat();
887
888 assert_eq!(seed.len(), 32);
889 let mut private_bytes: [u8; 32] = [0; 32];
890 private_bytes.copy_from_slice(seed.as_slice());
891
892 let secret = Secret::generate_secp256k1(None, Some(&private_bytes))
893 .expect("secp256k1 secret generate failed");
894
895 assert_eq!(
896 secret.get_private_keymultibase().unwrap(),
897 "z3vLUkda21MTbdECEEyjUWEQmJ8r1CKekvRLqQbZXxfLieL7"
898 );
899 assert_eq!(secret.get_public_bytes(), public_bytes.as_slice());
900 assert_eq!(secret.get_private_bytes(), private_bytes.as_slice());
901
902 let secret2 =
903 Secret::from_multibase("z3vLUkda21MTbdECEEyjUWEQmJ8r1CKekvRLqQbZXxfLieL7", None)
904 .expect("Failed to transform secp256k1 to secret");
905
906 assert_eq!(
907 secret2.get_public_keymultibase().unwrap(),
908 secret.get_public_keymultibase().unwrap()
909 );
910 assert_eq!(secret2.get_public_bytes(), secret.get_public_bytes());
911 assert_eq!(secret2.get_private_bytes(), secret.get_private_bytes());
912
913 assert_eq!(
914 secret2.get_private_keymultibase().unwrap(),
915 secret.get_private_keymultibase().unwrap()
916 );
917 }
918}