use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Serialize, Deserialize)]
pub struct HandshakeHello {
pub client_nonce: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct HandshakeWelcome {
pub agent_id: String,
pub agent_nonce: String,
pub signature_c: String,
pub credentials: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct HandshakeAuth {
pub signature_a: String,
}
pub mod builtin_tools;
pub mod config;
pub mod identity;
pub mod llm;
pub mod logger;
pub mod memory;
pub mod new_agent;
pub mod policy;
pub mod worker;
pub use config::{AgentConfig as Config, LLMConfig, SekuireConfig};
pub use llm::{ChatOptions, ChatResponse, LLMProvider, Message};
pub use new_agent::{get_agent, get_agents, get_system_prompt, get_tools, SekuireAgent};
pub use builtin_tools::{
create_default_tool_registry, CalculatorTool, CreateDirectoryTool, FileReadTool,
HttpRequestTool, ListDirectoryTool, Tool, ToolInput, ToolMetadata, ToolParameter, ToolRegistry,
WebSearchTool, WriteFileTool,
};
pub use memory::{create_memory_storage, InMemoryStorage, MemoryStorage};
pub use llm::{
create_llm_provider, AnthropicProvider, GeminiProvider, OllamaProvider, OpenAIProvider,
};
pub use identity::{calculate_sekuire_id, AgentIdentity};
pub use policy::{ActivePolicy, PolicyClient, PolicyEnforcer};
pub use worker::{create_worker, TaskContext, TaskEvent, TaskHandler, TaskWorker, WorkerConfig};
#[derive(Error, Debug)]
pub enum HandshakeError {
#[error("Network error: {0}")]
Network(#[from] reqwest::Error),
#[error("Protocol error: {0}")]
Protocol(String),
#[error("Crypto error: {0}")]
Crypto(String),
}
pub struct SekuireClient {
signing_key: SigningKey,
registry_url: String, }
impl SekuireClient {
pub fn new(signing_key: SigningKey, registry_url: String) -> Self {
Self {
signing_key,
registry_url,
}
}
pub async fn connect(
&self,
agent_url: &str,
expected_agent_id: Option<&str>,
) -> Result<(), HandshakeError> {
let client = reqwest::Client::new();
let mut csprng = OsRng;
let nonce_c_bytes: [u8; 32] = rand::Rng::gen(&mut csprng);
let nonce_c = hex::encode(nonce_c_bytes);
let hello = HandshakeHello {
client_nonce: nonce_c.clone(),
};
let res = client
.post(format!("{}/sekuire/hello", agent_url))
.json(&hello)
.send()
.await?;
if !res.status().is_success() {
return Err(HandshakeError::Protocol(format!(
"Agent rejected HELLO: {}",
res.status()
)));
}
let welcome: HandshakeWelcome = res.json().await?;
if let Some(expected) = expected_agent_id {
if welcome.agent_id != expected {
return Err(HandshakeError::Protocol(format!(
"Agent ID Mismatch. Expected {}, got {}",
expected, welcome.agent_id
)));
}
}
let agent_pub_key = self.fetch_public_key(&welcome.agent_id).await?;
let sig_c_bytes = hex::decode(&welcome.signature_c)
.map_err(|_| HandshakeError::Crypto("Invalid Signature C hex".into()))?;
let sig_c = Signature::from_bytes(&sig_c_bytes.try_into().unwrap());
if let Err(e) = agent_pub_key.verify(&nonce_c_bytes, &sig_c) {
return Err(HandshakeError::Crypto(format!(
"Agent failed to sign Client Nonce: {}",
e
)));
}
let sig_a = self.signing_key.sign(welcome.agent_nonce.as_bytes());
let auth = HandshakeAuth {
signature_a: hex::encode(sig_a.to_bytes()),
};
let res = client
.post(format!("{}/sekuire/auth", agent_url))
.json(&auth)
.send()
.await?;
if !res.status().is_success() {
return Err(HandshakeError::Protocol("Agent rejected AUTH".into()));
}
Ok(())
}
async fn fetch_public_key(&self, agent_id: &str) -> Result<VerifyingKey, HandshakeError> {
let url = format!("{}/api/v1/agents/{}", self.registry_url, agent_id);
let res = reqwest::get(&url).await?;
if !res.status().is_success() {
return Err(HandshakeError::Protocol(format!(
"Registry lookup failed for {}",
agent_id
)));
}
let agent_data: serde_json::Value = res.json().await?;
let key_hex = agent_data["author_key"]
.as_str()
.ok_or(HandshakeError::Protocol(
"Registry response missing author_key".into(),
))?;
let key_bytes = hex::decode(key_hex)
.map_err(|_| HandshakeError::Crypto("Invalid Public Key Hex from Registry".into()))?;
VerifyingKey::from_bytes(&key_bytes.try_into().unwrap())
.map_err(|_| HandshakeError::Crypto("Invalid Ed25519 Public Key".into()))
}
}