sekuire 0.1.0

The official SDK for the Sekuire Agent Identity Protocol
Documentation
use ed25519_dalek::{Signer, SigningKey};
use std::env;
use std::path::Path;
use tokio::fs;

#[derive(Debug)]
pub struct AgentIdentity {
    pub name: String,
    pub sekuire_id: String,
    private_key: Option<SigningKey>,
}

pub fn calculate_sekuire_id(model: &str, system_prompt: &str, tools: &str) -> String {
    let prompt_hash = blake3::hash(system_prompt.trim().as_bytes());

    let canonical_tools = match serde_json::from_str::<serde_json::Value>(tools) {
        Ok(parsed) => serde_json::to_string(&parsed).unwrap_or_else(|_| tools.to_string()),
        Err(_) => tools.to_string(),
    };
    let tools_hash = blake3::hash(canonical_tools.as_bytes());

    let fingerprint = format!(
        "model:{}|prompt:{}|tools:{}",
        model, prompt_hash, tools_hash
    );
    blake3::hash(fingerprint.as_bytes()).to_string()
}

fn parse_signing_key(hex_str: &str) -> Option<SigningKey> {
    let bytes = hex::decode(hex_str.trim()).ok()?;
    match bytes.len() {
        64 => {
            let seed: [u8; 32] = bytes[..32].try_into().ok()?;
            Some(SigningKey::from_bytes(&seed))
        }
        32 => {
            let seed: [u8; 32] = bytes.try_into().ok()?;
            Some(SigningKey::from_bytes(&seed))
        }
        _ => None,
    }
}

impl AgentIdentity {
    pub async fn load() -> Result<Self, Box<dyn std::error::Error>> {
        let manifest_path = Path::new("sekuire.json");

        let mut name = "Unknown".to_string();
        let mut sekuire_id = "8a6ecffc...".to_string();

        if manifest_path.exists() {
            let content = fs::read_to_string(manifest_path).await?;
            let v: serde_json::Value = serde_json::from_str(&content)?;
            if let Some(n) = v.get("name").and_then(|v| v.as_str()) {
                name = n.to_string();
            }
        } else if let Ok(n) = env::var("SEKUIRE_AGENT_NAME") {
            name = n;
            if let Ok(id) = env::var("SEKUIRE_AGENT_ID") {
                sekuire_id = id;
            }
        } else {
            return Err("sekuire.json not found and env vars missing".into());
        }

        let mut private_key_hex = env::var("SEKUIRE_PRIVATE_KEY").ok();

        if private_key_hex.is_none() {
            let key_path = Path::new(".sekuire/secret.key");
            if key_path.exists() {
                private_key_hex = Some(fs::read_to_string(key_path).await?.trim().to_string());
            }
        }

        let private_key = private_key_hex.and_then(|h| parse_signing_key(&h));

        Ok(Self {
            name,
            sekuire_id,
            private_key,
        })
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn sekuire_id(&self) -> &str {
        &self.sekuire_id
    }

    pub fn sign(&self, payload: &[u8]) -> String {
        match &self.private_key {
            Some(key) => {
                let signature = key.sign(payload);
                hex::encode(signature.to_bytes())
            }
            None => String::new(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde::Deserialize;
    use std::path::PathBuf;

    #[derive(Deserialize)]
    struct IdentityTestFile {
        vectors: Vec<IdentityVector>,
    }

    #[derive(Deserialize)]
    struct IdentityVector {
        name: String,
        input: IdentityInput,
        expected: String,
    }

    #[derive(Deserialize)]
    #[serde(rename_all = "camelCase")]
    struct IdentityInput {
        model: String,
        system_prompt: String,
        tools: String,
    }

    #[derive(Deserialize)]
    #[serde(rename_all = "camelCase")]
    struct SigningTestFile {
        key_pair: SigningKeyPair,
        vectors: Vec<SigningVector>,
    }

    #[derive(Deserialize)]
    #[serde(rename_all = "camelCase")]
    struct SigningKeyPair {
        public_key: String,
        secret_key: String,
    }

    #[derive(Deserialize)]
    struct SigningVector {
        name: String,
        message: String,
        signature: String,
    }

    fn read_test_vector_file(file_name: &str) -> String {
        let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        let mut candidates = Vec::new();

        if let Ok(override_dir) = std::env::var("SEKUIRE_TEST_VECTORS_DIR") {
            candidates.push(PathBuf::from(override_dir).join(file_name));
        }

        candidates.push(
            manifest_dir
                .join("../platform/packages/protocol/test-vectors")
                .join(file_name),
        );
        candidates.push(manifest_dir.join("../oags/specs/test-vectors").join(file_name));
        candidates.push(
            manifest_dir
                .join("../vision/oss/specs/test-vectors")
                .join(file_name),
        );

        let mut attempted_paths = Vec::new();
        for path in candidates {
            let display_path = path.display().to_string();
            attempted_paths.push(display_path.clone());
            if path.exists() {
                return std::fs::read_to_string(&path).unwrap_or_else(|err| {
                    panic!(
                        "failed to read {} at {}: {}",
                        file_name,
                        display_path,
                        err
                    )
                });
            }
        }

        panic!(
            "failed to locate {}. attempted paths:\n{}",
            file_name,
            attempted_paths.join("\n")
        );
    }

    #[test]
    fn test_identity_vectors() {
        let content = read_test_vector_file("identity.json");
        let test_file: IdentityTestFile =
            serde_json::from_str(&content).expect("failed to parse identity test vectors");

        for vector in &test_file.vectors {
            let result = calculate_sekuire_id(
                &vector.input.model,
                &vector.input.system_prompt,
                &vector.input.tools,
            );
            assert_eq!(
                result, vector.expected,
                "identity vector '{}' failed: got {}, expected {}",
                vector.name, result, vector.expected
            );
        }
    }

    #[test]
    fn test_signing_vectors() {
        let content = read_test_vector_file("signing.json");
        let test_file: SigningTestFile =
            serde_json::from_str(&content).expect("failed to parse signing test vectors");

        let signing_key =
            parse_signing_key(&test_file.key_pair.secret_key).expect("failed to parse secret key");
        let public_key_hex = hex::encode(signing_key.verifying_key().as_bytes());
        assert_eq!(
            public_key_hex, test_file.key_pair.public_key,
            "derived public key does not match expected"
        );

        let identity = AgentIdentity {
            name: "test".to_string(),
            sekuire_id: "test".to_string(),
            private_key: Some(signing_key),
        };

        for vector in &test_file.vectors {
            let sig = identity.sign(vector.message.as_bytes());
            assert_eq!(
                sig, vector.signature,
                "signing vector '{}' failed: got {}, expected {}",
                vector.name, sig, vector.signature
            );
        }
    }
}