pub fn ed25519_multibase_pubkey(public_key_bytes: &[u8; 32]) -> String {
let mut buf = Vec::with_capacity(34);
buf.extend_from_slice(&[0xed, 0x01]);
buf.extend_from_slice(public_key_bytes);
multibase::encode(multibase::Base::Base58Btc, &buf)
}
const ED25519_PRIV_CODEC: [u8; 2] = [0x80, 0x26];
pub fn decode_private_key_multibase(mb: &str) -> Result<[u8; 32], DidKeyError> {
let (_, raw) = multibase::decode(mb).map_err(|e| DidKeyError::Multibase(e.to_string()))?;
let seed_bytes = if raw.len() == 34 && raw[..2] == ED25519_PRIV_CODEC {
&raw[2..]
} else {
&raw[..]
};
seed_bytes
.try_into()
.map_err(|_| DidKeyError::InvalidSeedLength)
}
#[cfg(feature = "didcomm")]
pub struct DidKeySecrets {
pub signing: affinidi_tdk::secrets_resolver::secrets::Secret,
pub key_agreement: affinidi_tdk::secrets_resolver::secrets::Secret,
}
#[cfg(feature = "didcomm")]
pub fn secrets_from_did_key(did: &str, seed: &[u8; 32]) -> Result<DidKeySecrets, DidKeyError> {
use affinidi_tdk::secrets_resolver::secrets::Secret;
let ed_pub_mb = did
.strip_prefix("did:key:")
.ok_or(DidKeyError::InvalidDidKey)?;
let mut signing = Secret::generate_ed25519(None, Some(seed));
signing.id = format!("{did}#{ed_pub_mb}");
let mut key_agreement = signing
.to_x25519()
.map_err(|e| DidKeyError::X25519Conversion(e.to_string()))?;
let x_pub_mb = key_agreement
.get_public_keymultibase()
.map_err(|e| DidKeyError::X25519Conversion(e.to_string()))?;
key_agreement.id = format!("{did}#{x_pub_mb}");
Ok(DidKeySecrets {
signing,
key_agreement,
})
}
#[derive(Debug)]
pub enum DidKeyError {
Multibase(String),
InvalidSeedLength,
InvalidDidKey,
#[cfg(feature = "didcomm")]
X25519Conversion(String),
}
impl std::fmt::Display for DidKeyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Multibase(e) => write!(f, "invalid private key multibase: {e}"),
Self::InvalidSeedLength => write!(f, "private key seed must be 32 bytes"),
Self::InvalidDidKey => write!(f, "invalid did:key format"),
#[cfg(feature = "didcomm")]
Self::X25519Conversion(e) => write!(f, "X25519 conversion failed: {e}"),
}
}
}
impl std::error::Error for DidKeyError {}
#[cfg(feature = "client")]
pub fn secret_from_key_response(
resp: &crate::client::GetKeySecretResponse,
) -> Result<affinidi_tdk::secrets_resolver::secrets::Secret, DidKeyError> {
affinidi_tdk::secrets_resolver::secrets::Secret::from_multibase(
&resp.private_key_multibase,
None,
)
.map_err(|e| DidKeyError::Multibase(e.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ed25519_multibase_pubkey_format() {
let key = [0u8; 32];
let result = ed25519_multibase_pubkey(&key);
assert!(result.starts_with('z'));
let (_, decoded) = multibase::decode(&result).unwrap();
assert_eq!(decoded.len(), 34);
assert_eq!(decoded[0], 0xed);
assert_eq!(decoded[1], 0x01);
assert_eq!(&decoded[2..], &key);
}
#[test]
fn test_decode_private_key_multibase_roundtrip() {
let seed = [42u8; 32];
let encoded = multibase::encode(multibase::Base::Base58Btc, seed);
let decoded = decode_private_key_multibase(&encoded).unwrap();
assert_eq!(decoded, seed);
}
#[test]
fn test_decode_private_key_multibase_with_codec_prefix() {
let seed = [42u8; 32];
let mut prefixed = Vec::with_capacity(34);
prefixed.extend_from_slice(&ED25519_PRIV_CODEC);
prefixed.extend_from_slice(&seed);
let encoded = multibase::encode(multibase::Base::Base58Btc, &prefixed);
let decoded = decode_private_key_multibase(&encoded).unwrap();
assert_eq!(decoded, seed);
}
#[test]
fn test_decode_private_key_multibase_invalid() {
let result = decode_private_key_multibase("!!!bad!!!");
assert!(result.is_err());
}
#[test]
fn test_decode_private_key_multibase_wrong_length() {
let encoded = multibase::encode(multibase::Base::Base58Btc, [1u8; 16]);
let result = decode_private_key_multibase(&encoded);
assert!(matches!(result, Err(DidKeyError::InvalidSeedLength)));
}
}