use crate::error::{Error, Result};
use ed25519_dalek::{SigningKey, VerifyingKey};
use rand::rngs::OsRng;
use sha2::{Digest, Sha512};
pub const MULTICODEC_ED25519_PUB: [u8; 2] = [0xED, 0x01];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyPair {
pub public_key: [u8; 32],
pub private_key: [u8; 32],
}
pub fn generate_key_pair() -> KeyPair {
let signing = SigningKey::generate(&mut OsRng);
let verifying: VerifyingKey = signing.verifying_key();
KeyPair {
public_key: verifying.to_bytes(),
private_key: signing.to_bytes(),
}
}
pub fn encode_did_key(public_key: &[u8]) -> Result<String> {
if public_key.len() != 32 {
return Err(Error::InvalidDid(format!(
"Ed25519 pubkey must be 32 bytes, got {}",
public_key.len()
)));
}
let mut prefixed = Vec::with_capacity(34);
prefixed.extend_from_slice(&MULTICODEC_ED25519_PUB);
prefixed.extend_from_slice(public_key);
let encoded = bs58::encode(&prefixed).into_string();
Ok(format!("did:key:z{}", encoded))
}
pub fn decode_did_key(did: &str) -> Result<[u8; 32]> {
let prefix = "did:key:z";
if !did.starts_with(prefix) {
return Err(Error::InvalidDid("not a did:key identifier".into()));
}
let decoded = bs58::decode(&did[prefix.len()..])
.into_vec()
.map_err(|e| Error::InvalidDid(format!("base58 decode failed: {e}")))?;
if decoded.len() < 34
|| decoded[0] != MULTICODEC_ED25519_PUB[0]
|| decoded[1] != MULTICODEC_ED25519_PUB[1]
{
return Err(Error::InvalidDid(
"did:key is not an Ed25519 key (wrong multicodec prefix or truncated)".into(),
));
}
let mut out = [0u8; 32];
out.copy_from_slice(&decoded[2..34]);
Ok(out)
}
pub fn ed25519_pub_to_x25519(ed_pub: &[u8; 32]) -> [u8; 32] {
let verifying = VerifyingKey::from_bytes(ed_pub).expect("valid Edwards point");
let edwards = verifying.to_edwards();
let montgomery = edwards.to_montgomery();
montgomery.to_bytes()
}
pub fn ed25519_priv_to_x25519(ed_priv: &[u8; 32]) -> [u8; 32] {
let mut h = Sha512::new();
h.update(ed_priv);
let digest = h.finalize();
let mut out = [0u8; 32];
out.copy_from_slice(&digest[..32]);
out[0] &= 248;
out[31] &= 127;
out[31] |= 64;
out
}