Skip to main content

anda_brain/
lib.rs

1use anda_core::BoxError;
2use ic_auth_types::ByteBufB64;
3use ic_cose_types::cose::{CoseKey, ed25519::VerifyingKey, get_cose_key_public};
4use std::str::FromStr;
5
6pub mod agents;
7pub mod assess;
8pub(crate) mod authz;
9pub mod eval;
10pub mod handler;
11pub(crate) mod ledger;
12#[cfg(feature = "mcp")]
13pub mod mcp;
14pub mod payload;
15pub mod space;
16#[cfg(test)]
17pub(crate) mod testkit;
18pub mod types;
19#[cfg(feature = "wiki")]
20pub(crate) mod wiki;
21
22pub fn parse_ed25519_pubkeys(input: &str) -> Result<Vec<VerifyingKey>, BoxError> {
23    if input.is_empty() {
24        return Ok(vec![]);
25    }
26
27    input
28        .split(',')
29        .map(|item| match parse_ed25519_pubkey(item.trim()) {
30            Some(key) => Ok(key),
31            None => Err("invalid ED25519_PUBKEYS entry".into()),
32        })
33        .collect::<Result<Vec<_>, _>>()
34}
35
36fn parse_ed25519_pubkey(input: &str) -> Option<VerifyingKey> {
37    let data = ByteBufB64::from_str(input).ok()?;
38
39    if data.len() == 32 {
40        let mut bytes = [0u8; 32];
41        bytes.copy_from_slice(&data);
42        return VerifyingKey::from_bytes(&bytes).ok();
43    }
44
45    let cose_key = CoseKey::from_slice(data.as_slice()).ok()?;
46    let public_key = get_cose_key_public(cose_key).ok()?;
47    let bytes: [u8; 32] = public_key.try_into().ok()?;
48    VerifyingKey::from_bytes(&bytes).ok()
49}
50
51#[cfg(test)]
52mod tests {
53    use super::parse_ed25519_pubkeys;
54    use cose2::{Key as CoseKey, iana};
55    use ic_auth_types::ByteBufB64;
56
57    fn ed25519_basepoint_bytes() -> [u8; 32] {
58        let mut bytes = [0x66; 32];
59        bytes[0] = 0x58;
60        bytes
61    }
62
63    #[test]
64    fn parse_ed25519_pubkeys_allows_empty_input() {
65        let keys = parse_ed25519_pubkeys("").unwrap();
66
67        assert!(keys.is_empty());
68    }
69
70    #[test]
71    fn parse_ed25519_pubkeys_accepts_raw_keys_and_trims_items() {
72        let key_bytes = ed25519_basepoint_bytes();
73        let encoded = ByteBufB64(key_bytes.to_vec()).to_string();
74        let keys = parse_ed25519_pubkeys(&format!(" {encoded} , {encoded} ")).unwrap();
75
76        assert_eq!(keys.len(), 2);
77        assert_eq!(keys[0].to_bytes(), key_bytes);
78        assert_eq!(keys[1].to_bytes(), key_bytes);
79    }
80
81    #[test]
82    fn parse_ed25519_pubkeys_accepts_cose_key_entries() {
83        let key_bytes = ed25519_basepoint_bytes();
84        let mut cose_key = CoseKey::new();
85        cose_key.set_kty(iana::KeyTypeOKP);
86        cose_key.insert(iana::OKPKeyParameterX, key_bytes.to_vec());
87        let encoded = ByteBufB64(cose_key.to_vec().unwrap()).to_string();
88
89        let keys = parse_ed25519_pubkeys(&encoded).unwrap();
90
91        assert_eq!(keys.len(), 1);
92        assert_eq!(keys[0].to_bytes(), key_bytes);
93    }
94
95    #[test]
96    fn parse_ed25519_pubkeys_rejects_invalid_entries() {
97        let short_key = ByteBufB64(vec![1, 2, 3]).to_string();
98
99        assert!(parse_ed25519_pubkeys("not base64").is_err());
100        assert!(parse_ed25519_pubkeys(&short_key).is_err());
101        assert!(parse_ed25519_pubkeys(" ").is_err());
102    }
103}