Skip to main content

agent_ask/
identity.rs

1//! Ed25519 keypair + did:key helpers (mirror of `src/identity.ts`).
2
3use base64::{engine::general_purpose::STANDARD as B64, Engine as _};
4use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey, SECRET_KEY_LENGTH};
5use rand::rngs::OsRng;
6
7use crate::error::Error;
8
9const ED25519_MULTICODEC: [u8; 2] = [0xED, 0x01];
10
11#[derive(Debug, Clone)]
12pub struct Keypair {
13    pub private_key: [u8; SECRET_KEY_LENGTH],
14    pub public_key: [u8; 32],
15    pub did: String,
16}
17
18pub fn generate_keypair() -> Keypair {
19    let signing = SigningKey::generate(&mut OsRng);
20    let verifying = signing.verifying_key();
21    let pub_key = verifying.to_bytes();
22    Keypair {
23        private_key: signing.to_bytes(),
24        public_key: pub_key,
25        did: did_from_pubkey(&pub_key).expect("32-byte pubkey"),
26    }
27}
28
29pub fn did_from_pubkey(pubkey: &[u8]) -> Result<String, Error> {
30    if pubkey.len() != 32 {
31        return Err(Error::Invalid("pubkey must be 32 bytes".into()));
32    }
33    let mut buf = Vec::with_capacity(34);
34    buf.extend_from_slice(&ED25519_MULTICODEC);
35    buf.extend_from_slice(pubkey);
36    Ok(format!("did:key:z{}", bs58::encode(&buf).into_string()))
37}
38
39pub fn pubkey_from_did(did: &str) -> Result<[u8; 32], Error> {
40    if !did.starts_with("did:key:") {
41        return Err(Error::Invalid("not a did:key".into()));
42    }
43    let multibase = &did["did:key:".len()..];
44    let Some(rest) = multibase.strip_prefix('z') else {
45        return Err(Error::Invalid("did:key must use base58btc (z-prefix)".into()));
46    };
47    let decoded = bs58::decode(rest)
48        .into_vec()
49        .map_err(|e| Error::Invalid(format!("base58: {e}")))?;
50    if decoded.len() != 34 || decoded[0] != 0xED || decoded[1] != 0x01 {
51        return Err(Error::Invalid("did:key multicodec is not ed25519-pub".into()));
52    }
53    let mut out = [0u8; 32];
54    out.copy_from_slice(&decoded[2..]);
55    Ok(out)
56}
57
58pub fn sign(message: &[u8], private_key: &[u8; SECRET_KEY_LENGTH]) -> [u8; 64] {
59    SigningKey::from_bytes(private_key).sign(message).to_bytes()
60}
61
62pub fn verify_sig(sig: &[u8], message: &[u8], public_key: &[u8; 32]) -> bool {
63    let Ok(vk) = VerifyingKey::from_bytes(public_key) else {
64        return false;
65    };
66    let Ok(sig_arr) = <[u8; 64]>::try_from(sig) else {
67        return false;
68    };
69    vk.verify(message, &ed25519_dalek::Signature::from_bytes(&sig_arr))
70        .is_ok()
71}
72
73pub fn to_base64(bytes: &[u8]) -> String {
74    B64.encode(bytes)
75}
76
77pub fn from_base64(s: &str) -> Result<Vec<u8>, Error> {
78    B64.decode(s).map_err(|e| Error::Invalid(format!("base64: {e}")))
79}