#[cfg(feature = "slh-dsa")]
use crate::multicodec::SLH_DSA_SHA2_128S_PUB;
#[cfg(feature = "ml-dsa")]
use crate::multicodec::{
ML_DSA_44_PRIV_SEED, ML_DSA_44_PUB, ML_DSA_65_PRIV_SEED, ML_DSA_65_PUB, ML_DSA_87_PRIV_SEED,
ML_DSA_87_PUB,
};
use crate::{
errors::{Result, SecretsResolverError},
multicodec::{
ED25519_PRIV, ED25519_PUB, MultiEncoded, MultiEncodedBuf, P256_PRIV, P256_PUB, P384_PRIV,
P384_PUB, P521_PRIV, SECP256K1_PRIV, SECP256K1_PUB, X25519_PRIV, X25519_PUB,
},
};
pub use affinidi_crypto::KeyType;
use affinidi_crypto::{JWK, Params};
use base58::ToBase58;
use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use sha2::{Digest, Sha256};
use tracing::warn;
use unsigned_varint::encode as varint_encode;
use x25519_dalek::{PublicKey, StaticSecret};
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Deserialize)]
struct SecretShadow {
id: String,
#[serde(rename = "type")]
type_: SecretType,
#[serde(flatten)]
secret_material: SecretMaterial,
}
#[derive(Debug, Clone, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
#[serde(try_from = "SecretShadow")]
pub struct Secret {
pub id: String,
#[serde(rename = "type")]
pub type_: SecretType,
#[serde(flatten)]
pub secret_material: SecretMaterial,
#[serde(skip)]
pub(crate) private_bytes: Vec<u8>,
#[serde(skip)]
pub(crate) public_bytes: Vec<u8>,
#[serde(skip)]
pub(crate) key_type: KeyType,
}
impl TryFrom<SecretShadow> for Secret {
type Error = SecretsResolverError;
fn try_from(shadow: SecretShadow) -> Result<Self> {
match shadow.secret_material {
SecretMaterial::JWK(jwk) => {
let mut secret = Secret::from_jwk(&jwk)?;
secret.id = shadow.id;
secret.type_ = shadow.type_;
Ok(secret)
}
SecretMaterial::PrivateKeyMultibase(private) => {
Secret::from_multibase(&private, Some(&shadow.id))
}
_ => Err(SecretsResolverError::KeyError(
"Unsupported secret material type".into(),
)),
}
}
}
fn compress_ec_point(
public_bytes: &[u8],
compressed_len: usize,
codec: u64,
) -> Result<MultiEncodedBuf> {
let coord_len = compressed_len - 1;
let required = 1 + 2 * coord_len; if public_bytes.len() < required {
return Err(SecretsResolverError::KeyError(format!(
"public_bytes too short to compress: expected >= {required} bytes, got {}",
public_bytes.len()
)));
}
let last_y_byte = public_bytes[required - 1];
let parity: u8 = if last_y_byte.is_multiple_of(2) {
0x02
} else {
0x03
};
let mut compressed: Vec<u8> = Vec::with_capacity(compressed_len);
compressed.push(parity);
compressed.extend_from_slice(&public_bytes[1..=coord_len]);
Ok(MultiEncodedBuf::encode_bytes(codec, &compressed))
}
impl Secret {
fn convert_to_raw(input: &str) -> Result<Vec<u8>> {
BASE64_URL_SAFE_NO_PAD
.decode(input)
.map_err(|e| SecretsResolverError::KeyError(format!("Failed to decode base64url: {e}")))
}
pub fn from_jwk(jwk: &JWK) -> Result<Self> {
match &jwk.params {
Params::EC(params) => {
let mut x = Secret::convert_to_raw(¶ms.x)?;
let mut y = Secret::convert_to_raw(¶ms.y)?;
x.append(&mut y);
Ok(Secret {
id: jwk.key_id.as_ref().unwrap_or(&"".to_string()).to_string(),
type_: SecretType::JsonWebKey2020,
secret_material: SecretMaterial::JWK(jwk.to_owned()),
private_bytes: Secret::convert_to_raw(params.d.as_ref().ok_or(
SecretsResolverError::KeyError(
"Must have secret key available".to_string(),
),
)?)?,
public_bytes: x,
key_type: KeyType::try_from(params.curve.as_str())?,
})
}
Params::OKP(params) => Ok(Secret {
id: jwk.key_id.as_ref().unwrap_or(&"".to_string()).to_string(),
type_: SecretType::JsonWebKey2020,
secret_material: SecretMaterial::JWK(jwk.to_owned()),
private_bytes: Secret::convert_to_raw(params.d.as_ref().ok_or(
SecretsResolverError::KeyError("Must have secret key available".to_string()),
)?)?,
public_bytes: Secret::convert_to_raw(¶ms.x)?,
key_type: KeyType::try_from(params.curve.as_str())?,
}),
}
}
pub fn from_str(key_id: &str, jwk: &Value) -> Result<Self> {
let mut jwk: JWK = serde_json::from_value(jwk.to_owned())
.map_err(|e| SecretsResolverError::KeyError(format!("Failed to parse JWK: {e}")))?;
jwk.key_id = Some(key_id.to_string());
Self::from_jwk(&jwk)
}
pub fn from_multibase(private: &str, kid: Option<&str>) -> Result<Self> {
let private_bytes = multibase::decode(private).map_err(|e| {
SecretsResolverError::KeyError(format!("Failed to decode private key: {e}"))
})?;
let private_bytes = MultiEncoded::new(private_bytes.1.as_slice())?;
match private_bytes.codec() {
ED25519_PRIV => {
if private_bytes.data().len() != 32 {
return Err(SecretsResolverError::KeyError(
"Invalid ED25519 private key length".into(),
));
}
let mut pb: [u8; 32] = [0; 32];
pb.copy_from_slice(private_bytes.data());
let secret = Secret::generate_ed25519(kid, Some(&pb));
pb.zeroize();
Ok(secret)
}
X25519_PRIV => {
if private_bytes.data().len() != 32 {
return Err(SecretsResolverError::KeyError(
"Invalid X25519 private key length".into(),
));
}
let mut pb: [u8; 32] = [0; 32];
pb.copy_from_slice(private_bytes.data());
let secret = Secret::generate_x25519(kid, Some(&pb));
pb.zeroize();
secret
}
P256_PRIV => {
if private_bytes.data().len() != 32 {
return Err(SecretsResolverError::KeyError(
"Invalid P256 private key length".into(),
));
}
Secret::generate_p256(kid, Some(private_bytes.data()))
}
P384_PRIV => Secret::generate_p384(kid, Some(private_bytes.data())),
SECP256K1_PRIV => Secret::generate_secp256k1(kid, Some(private_bytes.data())),
#[cfg(feature = "ml-dsa")]
ML_DSA_44_PRIV_SEED => {
if private_bytes.data().len() != 32 {
return Err(SecretsResolverError::KeyError(
"Invalid ML-DSA-44 seed length".into(),
));
}
let mut pb: [u8; 32] = [0; 32];
pb.copy_from_slice(private_bytes.data());
let s = Secret::generate_ml_dsa_44(kid, Some(&pb));
pb.zeroize();
Ok(s)
}
#[cfg(feature = "ml-dsa")]
ML_DSA_65_PRIV_SEED => {
if private_bytes.data().len() != 32 {
return Err(SecretsResolverError::KeyError(
"Invalid ML-DSA-65 seed length".into(),
));
}
let mut pb: [u8; 32] = [0; 32];
pb.copy_from_slice(private_bytes.data());
let s = Secret::generate_ml_dsa_65(kid, Some(&pb));
pb.zeroize();
Ok(s)
}
#[cfg(feature = "ml-dsa")]
ML_DSA_87_PRIV_SEED => {
if private_bytes.data().len() != 32 {
return Err(SecretsResolverError::KeyError(
"Invalid ML-DSA-87 seed length".into(),
));
}
let mut pb: [u8; 32] = [0; 32];
pb.copy_from_slice(private_bytes.data());
let s = Secret::generate_ml_dsa_87(kid, Some(&pb));
pb.zeroize();
Ok(s)
}
_ => Err(SecretsResolverError::KeyError(
"Unsupported key type in from_multibase".into(),
)),
}
}
pub fn decode_multikey(key: &str) -> Result<Vec<u8>> {
let bytes = multibase::decode(key).map_err(|e| {
SecretsResolverError::KeyError(format!("Failed to multibase.decode key: {e}"))
})?;
let bytes = MultiEncoded::new(bytes.1.as_slice()).map_err(|e| {
SecretsResolverError::KeyError(format!("Failed to load decoded key: {e}"))
})?;
Ok(bytes.data().to_vec())
}
pub fn get_public_keymultibase(&self) -> Result<String> {
let encoded = match self.key_type {
KeyType::Ed25519 => MultiEncodedBuf::encode_bytes(ED25519_PUB, &self.public_bytes),
KeyType::X25519 => MultiEncodedBuf::encode_bytes(X25519_PUB, &self.public_bytes),
KeyType::P256 => compress_ec_point(&self.public_bytes, 33, P256_PUB)?,
KeyType::P384 => compress_ec_point(&self.public_bytes, 49, P384_PUB)?,
KeyType::P521 => {
return Err(SecretsResolverError::KeyError(
"P-521 is not supported".to_string(),
));
}
KeyType::Secp256k1 => compress_ec_point(&self.public_bytes, 33, SECP256K1_PUB)?,
#[cfg(feature = "ml-dsa")]
KeyType::MlDsa44 => MultiEncodedBuf::encode_bytes(ML_DSA_44_PUB, &self.public_bytes),
#[cfg(feature = "ml-dsa")]
KeyType::MlDsa65 => MultiEncodedBuf::encode_bytes(ML_DSA_65_PUB, &self.public_bytes),
#[cfg(feature = "ml-dsa")]
KeyType::MlDsa87 => MultiEncodedBuf::encode_bytes(ML_DSA_87_PUB, &self.public_bytes),
#[cfg(feature = "slh-dsa")]
KeyType::SlhDsaSha2_128s => {
MultiEncodedBuf::encode_bytes(SLH_DSA_SHA2_128S_PUB, &self.public_bytes)
}
_ => {
return Err(SecretsResolverError::KeyError(
"Unsupported key type".into(),
));
}
};
Ok(multibase::encode(
multibase::Base::Base58Btc,
encoded.into_bytes(),
))
}
pub fn get_public_keymultibase_hash(&self) -> Result<String> {
let key = self.get_public_keymultibase()?;
Secret::base58_hash_string(&key)
}
pub fn base58_hash_string(key: &str) -> Result<String> {
let hash = Sha256::digest(key.as_bytes());
let mut code_buf = varint_encode::u64_buffer();
let code_varint = varint_encode::u64(0x12, &mut code_buf);
let mut len_buf = varint_encode::u64_buffer();
let len_varint = varint_encode::u64(hash.len() as u64, &mut len_buf);
let mut bytes = Vec::with_capacity(code_varint.len() + len_varint.len() + hash.len());
bytes.extend_from_slice(code_varint);
bytes.extend_from_slice(len_varint);
bytes.extend_from_slice(&hash);
Ok(bytes.to_base58())
}
pub fn get_private_keymultibase(&self) -> Result<String> {
let encoded = match self.key_type {
KeyType::Ed25519 => MultiEncodedBuf::encode_bytes(ED25519_PRIV, &self.private_bytes),
KeyType::X25519 => MultiEncodedBuf::encode_bytes(X25519_PRIV, &self.private_bytes),
KeyType::P256 => MultiEncodedBuf::encode_bytes(P256_PRIV, &self.private_bytes),
KeyType::P384 => MultiEncodedBuf::encode_bytes(P384_PRIV, &self.private_bytes),
KeyType::P521 => MultiEncodedBuf::encode_bytes(P521_PRIV, &self.private_bytes),
KeyType::Secp256k1 => {
MultiEncodedBuf::encode_bytes(SECP256K1_PRIV, &self.private_bytes)
}
#[cfg(feature = "ml-dsa")]
KeyType::MlDsa44 => {
MultiEncodedBuf::encode_bytes(ML_DSA_44_PRIV_SEED, &self.private_bytes)
}
#[cfg(feature = "ml-dsa")]
KeyType::MlDsa65 => {
MultiEncodedBuf::encode_bytes(ML_DSA_65_PRIV_SEED, &self.private_bytes)
}
#[cfg(feature = "ml-dsa")]
KeyType::MlDsa87 => {
MultiEncodedBuf::encode_bytes(ML_DSA_87_PRIV_SEED, &self.private_bytes)
}
#[cfg(feature = "slh-dsa")]
KeyType::SlhDsaSha2_128s => {
return Err(SecretsResolverError::KeyError(
"SLH-DSA has no private-key multicodec registered; persist raw private_bytes \
instead of encoding as multikey"
.into(),
));
}
_ => {
return Err(SecretsResolverError::KeyError(
"Unsupported key type".into(),
));
}
};
Ok(multibase::encode(
multibase::Base::Base58Btc,
encoded.into_bytes(),
))
}
pub fn get_public_bytes(&self) -> &[u8] {
self.public_bytes.as_slice()
}
pub fn get_private_bytes(&self) -> &[u8] {
self.private_bytes.as_slice()
}
pub fn get_key_type(&self) -> KeyType {
self.key_type
}
pub fn to_x25519(&self) -> Result<Secret> {
if self.key_type != KeyType::Ed25519 {
warn!(
"Can only convert ED25519 to X25519! Current key type is {:#?}",
self.key_type
);
Err(SecretsResolverError::KeyError(format!(
"Can only convert ED25519 to X25519! Current key type is {:#?}",
self.key_type
)))
} else {
let x25519_secret = affinidi_crypto::ed25519::ed25519_private_to_x25519(
self.private_bytes.first_chunk::<32>().unwrap(),
);
let x25519_sk = StaticSecret::from(x25519_secret);
let x25519_pk = PublicKey::from(&x25519_sk);
let secret = BASE64_URL_SAFE_NO_PAD.encode(x25519_sk.as_bytes());
let public = BASE64_URL_SAFE_NO_PAD.encode(x25519_pk.as_bytes());
let jwk = json!({
"crv": "X25519",
"d": secret,
"kty": "OKP",
"x": public
});
Secret::from_str(&self.id, &jwk)
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Zeroize)]
pub enum SecretType {
JsonWebKey2020,
X25519KeyAgreementKey2019,
X25519KeyAgreementKey2020,
Ed25519VerificationKey2018,
Ed25519VerificationKey2020,
EcdsaSecp256k1VerificationKey2019,
Multikey,
Other,
}
#[derive(Debug, Clone, Deserialize, Serialize, Zeroize)]
#[serde(rename_all = "camelCase")]
pub enum SecretMaterial {
#[serde(rename = "privateKeyJwk")]
JWK(JWK),
PrivateKeyMultibase(String),
Base58 {
private_key_base58: String,
},
Multibase {
private_key_multibase: String,
},
}
#[cfg(test)]
mod tests {
use super::Secret;
use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD};
use serde_json::json;
#[test]
fn check_hash() {
let input = "z6MkgfFvvWA7sw8WkNWyK3y74kwNVvWc7Qrs5tWnsnqMfLD3";
let output = Secret::base58_hash_string(input).expect("Hash of input");
assert_eq!(&output, "QmY1kaguPMgjndEh1sdDZ8kdjX4Uc1SW4vziMfgWC6ndnJ")
}
#[test]
fn check_hash_bad() {
let input = "z6MkgfFvvWA7sw8WkNWyK3y74kwNVvWc7Qrs5tWnsnqMfLD4";
let output = Secret::base58_hash_string(input).expect("Hash of input");
assert_ne!(&output, "QmY1kaguPMgjndEh1sdDZ8kdjX4Uc1SW4vziMfgWC6ndnJ")
}
#[test]
fn check_x25519() {
let x25519_sk_bytes: [u8; 32] = [
200, 255, 64, 61, 17, 52, 112, 33, 205, 71, 186, 13, 131, 12, 241, 136, 223, 5, 152,
40, 95, 187, 83, 168, 142, 10, 234, 215, 70, 210, 148, 104,
];
let jwk = json!({
"crv": "Ed25519",
"d": "ymjvUTVuUPzGF5ui12LfreO8bjZ_LbnOrh0sk0xCxMM",
"kty": "OKP",
"x": "d17TbZmkoYHZUQpzJTcuOtq0tjWYm8CKvKGYHDW6ZaE"
});
let ed25519 = Secret::from_str("test", &jwk).unwrap();
let x25519 = ed25519
.to_x25519()
.expect("Couldn't convert ed25519 to x25519");
assert_eq!(x25519.private_bytes.as_slice(), x25519_sk_bytes);
}
#[test]
fn check_secret_deserialize() {
let txt = r#"{
"id": "did:web:localhost%3A7037:mediator:v1:.well-known#key-2",
"type": "JsonWebKey2020",
"privateKeyJwk": {
"crv": "secp256k1",
"d": "Cs5xn7WCkUWEua5vGxjP9_wBzIzMtEwjQ4KWKHHQR14",
"kty": "EC",
"x": "Lk1FY8MmyLjBswU4KbLoBQ_1THZJBMx2n6aIBXt1uXo",
"y": "tEv7EQHj4g4njOfrsjjDJBPKOI9RGWWMS8NYClo2cqo"
}
}"#;
let secret = serde_json::from_str::<Secret>(txt);
assert!(secret.is_ok());
}
#[test]
fn from_multiencode_ed25519() {
let seed = BASE64_URL_SAFE_NO_PAD
.decode("oihAhqs-h9V9rq6KYEhiEWwdBDpTI7xL0EEiwC9heFg")
.expect("Couldn't decode ed25519 BASE64 encoding");
let public_bytes = BASE64_URL_SAFE_NO_PAD
.decode("eC1vNebw6IJ8SJ4Tg9g2Q9W-Zy8xIS80byxTZXlPaHk")
.expect("Couldn't BASE64 decode ed25519 public bytes");
assert_eq!(seed.len(), 32);
let mut private_bytes: [u8; 32] = [0; 32];
private_bytes.copy_from_slice(seed.as_slice());
let secret = Secret::generate_ed25519(None, Some(&private_bytes));
assert_eq!(
secret.get_private_keymultibase().unwrap(),
"z3u2c8oS2oKgATvakQzVF66EAcZWJqPUzGQzWMUTKnFkv5DR"
);
assert_eq!(secret.get_public_bytes(), public_bytes.as_slice());
assert_eq!(secret.get_private_bytes(), private_bytes.as_slice());
let secret2 =
Secret::from_multibase("z3u2c8oS2oKgATvakQzVF66EAcZWJqPUzGQzWMUTKnFkv5DR", None)
.expect("Failed to transform ed25519 to secret");
assert_eq!(
secret2.get_public_keymultibase().unwrap(),
secret.get_public_keymultibase().unwrap()
);
assert_eq!(secret2.get_public_bytes(), secret.get_public_bytes());
assert_eq!(secret2.get_private_bytes(), secret.get_private_bytes());
assert_eq!(
secret2.get_private_keymultibase().unwrap(),
secret.get_private_keymultibase().unwrap()
);
}
#[test]
fn from_multiencode_x25519() {
let seed = BASE64_URL_SAFE_NO_PAD
.decode("eYN37ZX0ij4TYdklZax2jiRiyHYMNOzwW2bvNauAzKk")
.expect("Couldn't decode x25519 BASE64 encoding");
let public_bytes = BASE64_URL_SAFE_NO_PAD
.decode("Ephwf5xVmhVnDj2KtIPDKcGYBG9CQR_mZKlRqETZ62U")
.expect("Couldn't BASE64 decode x25519 public bytes");
assert_eq!(seed.len(), 32);
let mut private_bytes: [u8; 32] = [0; 32];
private_bytes.copy_from_slice(seed.as_slice());
let secret = Secret::generate_x25519(None, Some(&private_bytes))
.expect("x25519 generate secret failed");
assert_eq!(
secret.get_private_keymultibase().unwrap(),
"z3weexK9erGUKF41d3tJoDu2Fetx1xnsC7WhFWnjuCJXJGxp"
);
assert_eq!(secret.get_public_bytes(), public_bytes.as_slice());
assert_eq!(secret.get_private_bytes(), private_bytes.as_slice());
let secret2 =
Secret::from_multibase("z3weexK9erGUKF41d3tJoDu2Fetx1xnsC7WhFWnjuCJXJGxp", None)
.expect("Failed to transform x25519 to secret");
assert_eq!(
secret2.get_public_keymultibase().unwrap(),
secret.get_public_keymultibase().unwrap()
);
assert_eq!(secret2.get_public_bytes(), secret.get_public_bytes());
assert_eq!(secret2.get_private_bytes(), secret.get_private_bytes());
assert_eq!(
secret2.get_private_keymultibase().unwrap(),
secret.get_private_keymultibase().unwrap()
);
}
#[test]
fn from_multiencode_p256() {
let seed = BASE64_URL_SAFE_NO_PAD
.decode("B5ZIiXYkpEPczVbyWP85H75wrBifiRcFgtqYvI5I9AI")
.expect("Couldn't decode P-256 BASE64 encoding");
let pub_x = BASE64_URL_SAFE_NO_PAD
.decode("Iy3cHBWCRhcjohhS-iSucYMUNjH77DIQRSdn-NylcCw")
.expect("Couldn't BASE64 decode P-256 X public bytes");
let pub_y = BASE64_URL_SAFE_NO_PAD
.decode("p9MikGh-O3nbLWA-6tP4Oanch5AF3ZhRD907tQojH3k")
.expect("Couldn't BASE64 decode P-256 Y public bytes");
let public_bytes = [vec![4], pub_x, pub_y].concat();
assert_eq!(seed.len(), 32);
let mut private_bytes: [u8; 32] = [0; 32];
private_bytes.copy_from_slice(seed.as_slice());
let secret =
Secret::generate_p256(None, Some(&private_bytes)).expect("P256 secret generate failed");
assert_eq!(
secret.get_private_keymultibase().unwrap(),
"z42tiPvqM1uFz2QxbF7wTsQkfAf3hCsq1Uf9JbUMRaRiV1yb"
);
assert_eq!(secret.get_public_bytes(), public_bytes.as_slice());
assert_eq!(secret.get_private_bytes(), private_bytes.as_slice());
let secret2 =
Secret::from_multibase("z42tiPvqM1uFz2QxbF7wTsQkfAf3hCsq1Uf9JbUMRaRiV1yb", None)
.expect("Failed to transform P256 to secret");
assert_eq!(
secret2.get_public_keymultibase().unwrap(),
secret.get_public_keymultibase().unwrap()
);
assert_eq!(secret2.get_public_bytes(), secret.get_public_bytes());
assert_eq!(secret2.get_private_bytes(), secret.get_private_bytes());
assert_eq!(
secret2.get_private_keymultibase().unwrap(),
secret.get_private_keymultibase().unwrap()
);
}
#[test]
fn from_multiencode_p384() {
let seed = BASE64_URL_SAFE_NO_PAD
.decode("nka5zKVpVpOdCKdZZgnZ-VaSXk6V_ovYibzr2nf-mKAgct6wBdvWCXWLaNr80zY0")
.expect("Couldn't decode P-384 BASE64 encoding");
let pub_x = BASE64_URL_SAFE_NO_PAD
.decode("uitQkpTA3Vw8t_qOGrdLlbIzdzF0K9NsScgsVgmpQdQJgshCifOCUehxeazzL-Ow")
.expect("Couldn't BASE64 decode P-384 X public bytes");
let pub_y = BASE64_URL_SAFE_NO_PAD
.decode("4BIcrueQfhxfnrqToZEOujOfJOmwEsWJAdFNZ9dksIBCnWiCLBEn2HnR7ikyyPMJ")
.expect("Couldn't BASE64 decode P-384 Y public bytes");
let public_bytes = [vec![4], pub_x, pub_y].concat();
assert_eq!(seed.len(), 48);
let mut private_bytes: [u8; 48] = [0; 48];
private_bytes.copy_from_slice(seed.as_slice());
let secret =
Secret::generate_p384(None, Some(&private_bytes)).expect("P384 secret generate failed");
assert_eq!(
secret.get_private_keymultibase().unwrap(),
"z2fapqKp6mPoQCwkQzvL9Ns35Y57R4LRRfVwbXoSTQjTHdjD4MqFZnw5PueieuTWG4pN5q"
);
assert_eq!(secret.get_public_bytes(), public_bytes.as_slice());
assert_eq!(secret.get_private_bytes(), private_bytes.as_slice());
let secret2 = Secret::from_multibase(
"z2fapqKp6mPoQCwkQzvL9Ns35Y57R4LRRfVwbXoSTQjTHdjD4MqFZnw5PueieuTWG4pN5q",
None,
)
.expect("Failed to transform P384 to secret");
assert_eq!(
secret2.get_public_keymultibase().unwrap(),
secret.get_public_keymultibase().unwrap()
);
assert_eq!(secret2.get_public_bytes(), secret.get_public_bytes());
assert_eq!(secret2.get_private_bytes(), secret.get_private_bytes());
assert_eq!(
secret2.get_private_keymultibase().unwrap(),
secret.get_private_keymultibase().unwrap()
);
}
#[cfg(feature = "ml-dsa")]
#[test]
fn from_multiencode_ml_dsa_44() {
let seed = [7u8; 32];
let secret = Secret::generate_ml_dsa_44(Some("k-44"), Some(&seed));
let mb = secret.get_private_keymultibase().expect("encode priv");
let pub_mb = secret.get_public_keymultibase().expect("encode pub");
let secret2 = Secret::from_multibase(&mb, Some("k-44")).expect("decode");
assert_eq!(secret2.get_public_bytes(), secret.get_public_bytes());
assert_eq!(secret2.get_private_bytes(), secret.get_private_bytes());
assert_eq!(
secret2.get_public_keymultibase().unwrap(),
pub_mb,
"public multikey roundtrip"
);
}
#[cfg(any(feature = "ml-dsa", feature = "slh-dsa"))]
fn varint_prefix(codec: u64) -> Vec<u8> {
let mut buf = [0u8; 10];
use unsigned_varint::encode;
let slice = encode::u64(codec, &mut buf);
slice.to_vec()
}
#[cfg(feature = "ml-dsa")]
#[test]
fn ml_dsa_multikey_uses_registered_codecs() {
use crate::multicodec::{
ML_DSA_44_PRIV_SEED, ML_DSA_44_PUB, ML_DSA_65_PRIV_SEED, ML_DSA_65_PUB,
ML_DSA_87_PRIV_SEED, ML_DSA_87_PUB,
};
use affinidi_crypto::KeyType;
let cases: &[(KeyType, u64, u64, usize, usize)] = &[
(
KeyType::MlDsa44,
ML_DSA_44_PUB,
ML_DSA_44_PRIV_SEED,
1312,
32,
),
(
KeyType::MlDsa65,
ML_DSA_65_PUB,
ML_DSA_65_PRIV_SEED,
1952,
32,
),
(
KeyType::MlDsa87,
ML_DSA_87_PUB,
ML_DSA_87_PRIV_SEED,
2592,
32,
),
];
for (kt, pub_code, priv_code, pub_len, priv_len) in cases {
let s = match kt {
KeyType::MlDsa44 => Secret::generate_ml_dsa_44(None, Some(&[1u8; 32])),
KeyType::MlDsa65 => Secret::generate_ml_dsa_65(None, Some(&[1u8; 32])),
KeyType::MlDsa87 => Secret::generate_ml_dsa_87(None, Some(&[1u8; 32])),
_ => unreachable!(),
};
let pub_mb = s.get_public_keymultibase().unwrap();
let (_, pub_raw) = multibase::decode(&pub_mb).unwrap();
let expected = varint_prefix(*pub_code);
assert_eq!(
&pub_raw[..expected.len()],
expected.as_slice(),
"{kt:?} pub codec prefix mismatch (expected {pub_code:#06x})"
);
assert_eq!(pub_raw.len() - expected.len(), *pub_len);
let priv_mb = s.get_private_keymultibase().unwrap();
let (_, priv_raw) = multibase::decode(&priv_mb).unwrap();
let expected = varint_prefix(*priv_code);
assert_eq!(
&priv_raw[..expected.len()],
expected.as_slice(),
"{kt:?} priv-seed codec prefix mismatch (expected {priv_code:#06x})"
);
assert_eq!(priv_raw.len() - expected.len(), *priv_len);
}
}
#[cfg(feature = "slh-dsa")]
#[test]
fn slh_dsa_multikey_uses_registered_public_codec() {
use crate::multicodec::SLH_DSA_SHA2_128S_PUB;
let s = Secret::generate_slh_dsa_sha2_128s(None);
let pub_mb = s.get_public_keymultibase().unwrap();
let (_, raw) = multibase::decode(&pub_mb).unwrap();
let expected = varint_prefix(SLH_DSA_SHA2_128S_PUB);
assert_eq!(&raw[..expected.len()], expected.as_slice());
assert_eq!(raw.len() - expected.len(), 32);
}
#[test]
fn get_public_keymultibase_bounds_check_p256() {
let mut s = Secret::generate_p256(None, Some(&[1u8; 32])).unwrap();
s.public_bytes.clear(); let err = s.get_public_keymultibase().unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("too short"),
"expected bounds-check error, got: {msg}"
);
}
#[test]
fn get_public_keymultibase_bounds_check_p384() {
let mut s = Secret::generate_p384(None, Some(&[1u8; 48])).unwrap();
s.public_bytes.truncate(10);
assert!(s.get_public_keymultibase().is_err());
}
#[cfg(feature = "slh-dsa")]
#[test]
fn slh_dsa_private_multibase_unsupported() {
let secret = Secret::generate_slh_dsa_sha2_128s(Some("k-slh"));
assert!(secret.get_private_keymultibase().is_err());
assert!(secret.get_public_keymultibase().is_ok());
}
#[test]
fn from_multiencode_secp256k1() {
let seed = BASE64_URL_SAFE_NO_PAD
.decode("CzR8XKYmrxbeEeUKojSgXUskLmGjbLXFf4CoJd6he6A")
.expect("Couldn't decode secp256k1 BASE64 encoding");
let pub_x = BASE64_URL_SAFE_NO_PAD
.decode("jcGMDsxKBME8GmaN_-XTaAEKk2ET6ajWe_8-2RsU-is")
.expect("Couldn't BASE64 decode secp256k1 X public bytes");
let pub_y = BASE64_URL_SAFE_NO_PAD
.decode("9ECTinCwW9bA36fmUBg0_iu0oyLR-Tn54guX8exrUjM")
.expect("Couldn't BASE64 decode secp256k1 Y public bytes");
let public_bytes = [vec![4], pub_x, pub_y].concat();
assert_eq!(seed.len(), 32);
let mut private_bytes: [u8; 32] = [0; 32];
private_bytes.copy_from_slice(seed.as_slice());
let secret = Secret::generate_secp256k1(None, Some(&private_bytes))
.expect("secp256k1 secret generate failed");
assert_eq!(
secret.get_private_keymultibase().unwrap(),
"z3vLUkda21MTbdECEEyjUWEQmJ8r1CKekvRLqQbZXxfLieL7"
);
assert_eq!(secret.get_public_bytes(), public_bytes.as_slice());
assert_eq!(secret.get_private_bytes(), private_bytes.as_slice());
let secret2 =
Secret::from_multibase("z3vLUkda21MTbdECEEyjUWEQmJ8r1CKekvRLqQbZXxfLieL7", None)
.expect("Failed to transform secp256k1 to secret");
assert_eq!(
secret2.get_public_keymultibase().unwrap(),
secret.get_public_keymultibase().unwrap()
);
assert_eq!(secret2.get_public_bytes(), secret.get_public_bytes());
assert_eq!(secret2.get_private_bytes(), secret.get_private_bytes());
assert_eq!(
secret2.get_private_keymultibase().unwrap(),
secret.get_private_keymultibase().unwrap()
);
}
}