Skip to main content

agent_phone/
did.rs

1//! DID + key handling: Ed25519 keypairs, did:key codec, Ed25519↔X25519 conversion.
2
3use crate::error::{Error, Result};
4use ed25519_dalek::{SigningKey, VerifyingKey};
5use rand::rngs::OsRng;
6use sha2::{Digest, Sha512};
7
8pub const MULTICODEC_ED25519_PUB: [u8; 2] = [0xED, 0x01];
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct KeyPair {
12    pub public_key: [u8; 32],
13    pub private_key: [u8; 32],
14}
15
16pub fn generate_key_pair() -> KeyPair {
17    let signing = SigningKey::generate(&mut OsRng);
18    let verifying: VerifyingKey = signing.verifying_key();
19    KeyPair {
20        public_key: verifying.to_bytes(),
21        private_key: signing.to_bytes(),
22    }
23}
24
25pub fn encode_did_key(public_key: &[u8]) -> Result<String> {
26    if public_key.len() != 32 {
27        return Err(Error::InvalidDid(format!(
28            "Ed25519 pubkey must be 32 bytes, got {}",
29            public_key.len()
30        )));
31    }
32    let mut prefixed = Vec::with_capacity(34);
33    prefixed.extend_from_slice(&MULTICODEC_ED25519_PUB);
34    prefixed.extend_from_slice(public_key);
35    let encoded = bs58::encode(&prefixed).into_string();
36    Ok(format!("did:key:z{}", encoded))
37}
38
39pub fn decode_did_key(did: &str) -> Result<[u8; 32]> {
40    let prefix = "did:key:z";
41    if !did.starts_with(prefix) {
42        return Err(Error::InvalidDid("not a did:key identifier".into()));
43    }
44    let decoded = bs58::decode(&did[prefix.len()..])
45        .into_vec()
46        .map_err(|e| Error::InvalidDid(format!("base58 decode failed: {e}")))?;
47    if decoded.len() < 34
48        || decoded[0] != MULTICODEC_ED25519_PUB[0]
49        || decoded[1] != MULTICODEC_ED25519_PUB[1]
50    {
51        return Err(Error::InvalidDid(
52            "did:key is not an Ed25519 key (wrong multicodec prefix or truncated)".into(),
53        ));
54    }
55    let mut out = [0u8; 32];
56    out.copy_from_slice(&decoded[2..34]);
57    Ok(out)
58}
59
60/// Convert an Ed25519 public key to an X25519 public key
61/// (Edwards y → Montgomery u = (1+y)/(1-y) mod p).
62pub fn ed25519_pub_to_x25519(ed_pub: &[u8; 32]) -> [u8; 32] {
63    let verifying = VerifyingKey::from_bytes(ed_pub).expect("valid Edwards point");
64    let edwards = verifying.to_edwards();
65    let montgomery = edwards.to_montgomery();
66    montgomery.to_bytes()
67}
68
69/// Convert an Ed25519 private key (32-byte seed) to an X25519 private key.
70/// Matches `edwardsToMontgomeryPriv` from `@noble/curves`:
71/// take SHA-512(seed)[0..32] and clamp.
72pub fn ed25519_priv_to_x25519(ed_priv: &[u8; 32]) -> [u8; 32] {
73    let mut h = Sha512::new();
74    h.update(ed_priv);
75    let digest = h.finalize();
76    let mut out = [0u8; 32];
77    out.copy_from_slice(&digest[..32]);
78    // RFC 7748 clamping.
79    out[0] &= 248;
80    out[31] &= 127;
81    out[31] |= 64;
82    out
83}