#![allow(missing_docs)]
use ant_quic::{
derive_peer_id_from_public_key, MlDsaPublicKey, MlDsaSecretKey, PeerId as AntQuicPeerId,
};
use hex;
use serde::{Deserialize, Serialize};
pub const PEER_ID_LENGTH: usize = 32;
pub type PeerId = AntQuicPeerId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MachineId(pub [u8; PEER_ID_LENGTH]);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct AgentId(pub [u8; PEER_ID_LENGTH]);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct UserId(pub [u8; PEER_ID_LENGTH]);
impl MachineId {
#[inline]
pub fn from_public_key(pubkey: &MlDsaPublicKey) -> Self {
let peer_id = derive_peer_id_from_public_key(pubkey);
Self(peer_id.0)
}
#[inline]
#[must_use]
pub fn as_bytes(&self) -> &[u8; PEER_ID_LENGTH] {
&self.0
}
#[inline]
#[must_use]
pub fn to_vec(&self) -> Vec<u8> {
self.0.to_vec()
}
pub fn verify(&self, pubkey: &MlDsaPublicKey) -> Result<(), crate::error::IdentityError> {
let derived = Self::from_public_key(pubkey);
if *self == derived {
Ok(())
} else {
Err(crate::error::IdentityError::PeerIdMismatch)
}
}
}
impl AgentId {
#[inline]
pub fn from_public_key(pubkey: &MlDsaPublicKey) -> Self {
let peer_id = derive_peer_id_from_public_key(pubkey);
Self(peer_id.0)
}
#[inline]
#[must_use]
pub fn as_bytes(&self) -> &[u8; PEER_ID_LENGTH] {
&self.0
}
#[inline]
#[must_use]
pub fn to_vec(&self) -> Vec<u8> {
self.0.to_vec()
}
pub fn verify(&self, pubkey: &MlDsaPublicKey) -> Result<(), crate::error::IdentityError> {
let derived = Self::from_public_key(pubkey);
if *self == derived {
Ok(())
} else {
Err(crate::error::IdentityError::PeerIdMismatch)
}
}
}
impl UserId {
#[inline]
pub fn from_public_key(pubkey: &MlDsaPublicKey) -> Self {
let peer_id = derive_peer_id_from_public_key(pubkey);
Self(peer_id.0)
}
#[inline]
#[must_use]
pub fn as_bytes(&self) -> &[u8; PEER_ID_LENGTH] {
&self.0
}
#[inline]
#[must_use]
pub fn to_vec(&self) -> Vec<u8> {
self.0.to_vec()
}
pub fn verify(&self, pubkey: &MlDsaPublicKey) -> Result<(), crate::error::IdentityError> {
let derived = Self::from_public_key(pubkey);
if *self == derived {
Ok(())
} else {
Err(crate::error::IdentityError::PeerIdMismatch)
}
}
}
impl std::fmt::Display for UserId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "UserId(0x{})", hex::encode(&self.0[..8]))
}
}
impl std::fmt::Display for MachineId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MachineId(0x{})", hex::encode(&self.0[..8]))
}
}
impl std::fmt::Display for AgentId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "AgentId(0x{})", hex::encode(&self.0[..8]))
}
}
pub struct MachineKeypair {
public_key: MlDsaPublicKey,
secret_key: MlDsaSecretKey,
}
impl std::fmt::Debug for MachineKeypair {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MachineKeypair")
.field("public_key", &self.public_key)
.field("secret_key", &"<REDACTED>")
.finish()
}
}
impl MachineKeypair {
pub fn generate() -> Result<Self, crate::error::IdentityError> {
let (public_key, secret_key) = ant_quic::generate_ml_dsa_keypair()
.map_err(|e| crate::error::IdentityError::KeyGeneration(format!("{:?}", e)))?;
Ok(Self {
public_key,
secret_key,
})
}
#[inline]
#[must_use]
pub fn public_key(&self) -> &MlDsaPublicKey {
&self.public_key
}
#[inline]
#[must_use]
pub fn machine_id(&self) -> MachineId {
MachineId::from_public_key(&self.public_key)
}
#[inline]
#[must_use]
pub fn secret_key(&self) -> &MlDsaSecretKey {
&self.secret_key
}
pub fn from_bytes(
public_key_bytes: &[u8],
secret_key_bytes: &[u8],
) -> Result<Self, crate::error::IdentityError> {
let public_key = MlDsaPublicKey::from_bytes(public_key_bytes).map_err(|_| {
crate::error::IdentityError::InvalidPublicKey("failed to parse public key".to_string())
})?;
let secret_key = MlDsaSecretKey::from_bytes(secret_key_bytes).map_err(|_| {
crate::error::IdentityError::InvalidSecretKey("failed to parse secret key".to_string())
})?;
Ok(Self {
public_key,
secret_key,
})
}
#[must_use]
pub fn to_bytes(&self) -> (Vec<u8>, Vec<u8>) {
(
self.public_key.as_bytes().to_vec(),
self.secret_key.as_bytes().to_vec(),
)
}
}
pub struct AgentKeypair {
public_key: MlDsaPublicKey,
secret_key: MlDsaSecretKey,
}
impl std::fmt::Debug for AgentKeypair {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AgentKeypair")
.field("public_key", &self.public_key)
.field("secret_key", &"<REDACTED>")
.finish()
}
}
impl AgentKeypair {
pub fn generate() -> Result<Self, crate::error::IdentityError> {
let (public_key, secret_key) = ant_quic::generate_ml_dsa_keypair()
.map_err(|e| crate::error::IdentityError::KeyGeneration(format!("{:?}", e)))?;
Ok(Self {
public_key,
secret_key,
})
}
#[inline]
#[must_use]
pub fn public_key(&self) -> &MlDsaPublicKey {
&self.public_key
}
#[inline]
#[must_use]
pub fn agent_id(&self) -> AgentId {
AgentId::from_public_key(&self.public_key)
}
#[inline]
#[must_use]
pub fn secret_key(&self) -> &MlDsaSecretKey {
&self.secret_key
}
pub fn from_bytes(
public_key_bytes: &[u8],
secret_key_bytes: &[u8],
) -> Result<Self, crate::error::IdentityError> {
let public_key = MlDsaPublicKey::from_bytes(public_key_bytes).map_err(|_| {
crate::error::IdentityError::InvalidPublicKey("failed to parse public key".to_string())
})?;
let secret_key = MlDsaSecretKey::from_bytes(secret_key_bytes).map_err(|_| {
crate::error::IdentityError::InvalidSecretKey("failed to parse secret key".to_string())
})?;
Ok(Self {
public_key,
secret_key,
})
}
#[must_use]
pub fn to_bytes(&self) -> (Vec<u8>, Vec<u8>) {
(
self.public_key.as_bytes().to_vec(),
self.secret_key.as_bytes().to_vec(),
)
}
}
pub struct UserKeypair {
public_key: MlDsaPublicKey,
secret_key: MlDsaSecretKey,
}
impl std::fmt::Debug for UserKeypair {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UserKeypair")
.field("public_key", &self.public_key)
.field("secret_key", &"<REDACTED>")
.finish()
}
}
impl UserKeypair {
pub fn generate() -> Result<Self, crate::error::IdentityError> {
let (public_key, secret_key) = ant_quic::generate_ml_dsa_keypair()
.map_err(|e| crate::error::IdentityError::KeyGeneration(format!("{:?}", e)))?;
Ok(Self {
public_key,
secret_key,
})
}
#[inline]
#[must_use]
pub fn public_key(&self) -> &MlDsaPublicKey {
&self.public_key
}
#[inline]
#[must_use]
pub fn user_id(&self) -> UserId {
UserId::from_public_key(&self.public_key)
}
#[inline]
#[must_use]
pub fn secret_key(&self) -> &MlDsaSecretKey {
&self.secret_key
}
pub fn from_bytes(
public_key_bytes: &[u8],
secret_key_bytes: &[u8],
) -> Result<Self, crate::error::IdentityError> {
let public_key = MlDsaPublicKey::from_bytes(public_key_bytes).map_err(|_| {
crate::error::IdentityError::InvalidPublicKey("failed to parse public key".to_string())
})?;
let secret_key = MlDsaSecretKey::from_bytes(secret_key_bytes).map_err(|_| {
crate::error::IdentityError::InvalidSecretKey("failed to parse secret key".to_string())
})?;
Ok(Self {
public_key,
secret_key,
})
}
#[must_use]
pub fn to_bytes(&self) -> (Vec<u8>, Vec<u8>) {
(
self.public_key.as_bytes().to_vec(),
self.secret_key.as_bytes().to_vec(),
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AgentCertificate {
user_public_key: Vec<u8>,
agent_public_key: Vec<u8>,
signature: Vec<u8>,
issued_at: u64,
}
impl AgentCertificate {
const CERT_PREFIX: &'static [u8] = b"x0x-agent-cert-v1";
pub fn issue(
user_kp: &UserKeypair,
agent_kp: &AgentKeypair,
) -> Result<Self, crate::error::IdentityError> {
let issued_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| {
crate::error::IdentityError::CertificateVerification(format!(
"system time error: {}",
e
))
})?
.as_secs();
let user_pub_bytes = user_kp.public_key().as_bytes().to_vec();
let agent_pub_bytes = agent_kp.public_key().as_bytes().to_vec();
let message = Self::build_message(&user_pub_bytes, &agent_pub_bytes, issued_at);
let signature = ant_quic::crypto::raw_public_keys::pqc::sign_with_ml_dsa(
user_kp.secret_key(),
&message,
)
.map_err(|e| {
crate::error::IdentityError::CertificateVerification(format!("signing failed: {:?}", e))
})?;
Ok(Self {
user_public_key: user_pub_bytes,
agent_public_key: agent_pub_bytes,
signature: signature.as_bytes().to_vec(),
issued_at,
})
}
pub fn verify(&self) -> Result<(), crate::error::IdentityError> {
let user_pubkey = MlDsaPublicKey::from_bytes(&self.user_public_key).map_err(|_| {
crate::error::IdentityError::CertificateVerification(
"invalid user public key in certificate".to_string(),
)
})?;
let signature =
ant_quic::crypto::raw_public_keys::pqc::MlDsaSignature::from_bytes(&self.signature)
.map_err(|e| {
crate::error::IdentityError::CertificateVerification(format!(
"invalid signature format: {:?}",
e
))
})?;
let message = Self::build_message(
&self.user_public_key,
&self.agent_public_key,
self.issued_at,
);
ant_quic::crypto::raw_public_keys::pqc::verify_with_ml_dsa(
&user_pubkey,
&message,
&signature,
)
.map_err(|e| {
crate::error::IdentityError::CertificateVerification(format!(
"signature verification failed: {:?}",
e
))
})
}
pub fn user_id(&self) -> Result<UserId, crate::error::IdentityError> {
let pubkey = MlDsaPublicKey::from_bytes(&self.user_public_key).map_err(|_| {
crate::error::IdentityError::CertificateVerification(
"invalid user public key in certificate".to_string(),
)
})?;
Ok(UserId::from_public_key(&pubkey))
}
pub fn agent_id(&self) -> Result<AgentId, crate::error::IdentityError> {
let pubkey = MlDsaPublicKey::from_bytes(&self.agent_public_key).map_err(|_| {
crate::error::IdentityError::CertificateVerification(
"invalid agent public key in certificate".to_string(),
)
})?;
Ok(AgentId::from_public_key(&pubkey))
}
#[must_use]
pub fn issued_at(&self) -> u64 {
self.issued_at
}
fn build_message(user_pubkey: &[u8], agent_pubkey: &[u8], timestamp: u64) -> Vec<u8> {
let mut message = Vec::with_capacity(
Self::CERT_PREFIX.len() + user_pubkey.len() + agent_pubkey.len() + 8,
);
message.extend_from_slice(Self::CERT_PREFIX);
message.extend_from_slice(user_pubkey);
message.extend_from_slice(agent_pubkey);
message.extend_from_slice(×tamp.to_le_bytes());
message
}
}
pub struct Identity {
machine_keypair: MachineKeypair,
agent_keypair: AgentKeypair,
user_keypair: Option<UserKeypair>,
agent_certificate: Option<AgentCertificate>,
}
impl std::fmt::Debug for Identity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Identity")
.field("machine_keypair", &self.machine_keypair)
.field("agent_keypair", &self.agent_keypair)
.field("user_keypair", &self.user_keypair)
.field("agent_certificate", &self.agent_certificate)
.finish()
}
}
impl Identity {
#[inline]
pub fn new(machine_keypair: MachineKeypair, agent_keypair: AgentKeypair) -> Self {
Self {
machine_keypair,
agent_keypair,
user_keypair: None,
agent_certificate: None,
}
}
#[inline]
pub fn new_with_user(
machine_keypair: MachineKeypair,
agent_keypair: AgentKeypair,
user_keypair: UserKeypair,
agent_certificate: AgentCertificate,
) -> Self {
Self {
machine_keypair,
agent_keypair,
user_keypair: Some(user_keypair),
agent_certificate: Some(agent_certificate),
}
}
pub fn generate() -> Result<Self, crate::error::IdentityError> {
Ok(Self {
machine_keypair: MachineKeypair::generate()?,
agent_keypair: AgentKeypair::generate()?,
user_keypair: None,
agent_certificate: None,
})
}
#[inline]
#[must_use]
pub fn machine_id(&self) -> MachineId {
self.machine_keypair.machine_id()
}
#[inline]
#[must_use]
pub fn agent_id(&self) -> AgentId {
self.agent_keypair.agent_id()
}
#[inline]
#[must_use]
pub fn user_id(&self) -> Option<UserId> {
self.user_keypair.as_ref().map(|kp| kp.user_id())
}
#[inline]
#[must_use]
pub fn machine_keypair(&self) -> &MachineKeypair {
&self.machine_keypair
}
#[inline]
#[must_use]
pub fn agent_keypair(&self) -> &AgentKeypair {
&self.agent_keypair
}
#[inline]
#[must_use]
pub fn user_keypair(&self) -> Option<&UserKeypair> {
self.user_keypair.as_ref()
}
#[inline]
#[must_use]
pub fn agent_certificate(&self) -> Option<&AgentCertificate> {
self.agent_certificate.as_ref()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ServiceEntry {
pub name: String,
pub description: String,
pub min_trust: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IntroductionCard {
pub agent_id: AgentId,
pub machine_id: MachineId,
pub user_id: Option<UserId>,
pub certificate: Option<AgentCertificate>,
pub display_name: Option<String>,
pub identity_words: String,
pub services: Vec<ServiceEntry>,
pub signature: Vec<u8>,
}
impl IntroductionCard {
pub fn from_identity(
identity: &Identity,
display_name: Option<String>,
services: Vec<ServiceEntry>,
) -> Self {
let agent_id = identity.agent_id();
let machine_id = identity.machine_id();
let user_id = identity.user_id();
let certificate = identity.agent_certificate().cloned();
let encoder = four_word_networking::IdentityEncoder::new();
let identity_words = if let Some(uid) = user_id {
encoder
.encode_full(agent_id.as_bytes(), uid.as_bytes())
.map(|w| w.to_string())
.unwrap_or_default()
} else {
encoder
.encode_agent(agent_id.as_bytes())
.map(|w| w.to_string())
.unwrap_or_default()
};
let signature = identity.machine_keypair().public_key().as_bytes().to_vec();
Self {
agent_id,
machine_id,
user_id,
certificate,
display_name,
identity_words,
services,
signature,
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn test_machine_id_from_public_key() {
let keypair = MachineKeypair::generate().unwrap();
let machine_id = MachineId::from_public_key(keypair.public_key());
assert_eq!(machine_id.as_bytes().len(), PEER_ID_LENGTH);
}
#[test]
fn test_machine_id_verification() {
let keypair = MachineKeypair::generate().unwrap();
let machine_id = MachineId::from_public_key(keypair.public_key());
machine_id.verify(keypair.public_key()).unwrap();
}
#[test]
fn test_agent_id_from_public_key() {
let keypair = AgentKeypair::generate().unwrap();
let agent_id = AgentId::from_public_key(keypair.public_key());
assert_eq!(agent_id.as_bytes().len(), PEER_ID_LENGTH);
}
#[test]
fn test_identity_generation() {
let identity = Identity::generate().unwrap();
assert!(identity.machine_id().as_bytes().len() == PEER_ID_LENGTH);
assert!(identity.agent_id().as_bytes().len() == PEER_ID_LENGTH);
assert!(identity.user_id().is_none());
assert!(identity.user_keypair().is_none());
assert!(identity.agent_certificate().is_none());
}
#[test]
fn test_user_id_from_public_key() {
let keypair = UserKeypair::generate().unwrap();
let user_id = UserId::from_public_key(keypair.public_key());
assert_eq!(user_id.as_bytes().len(), PEER_ID_LENGTH);
}
#[test]
fn test_user_id_verification() {
let keypair = UserKeypair::generate().unwrap();
let user_id = UserId::from_public_key(keypair.public_key());
user_id.verify(keypair.public_key()).unwrap();
}
#[test]
fn test_user_id_verification_fails_with_wrong_key() {
let keypair1 = UserKeypair::generate().unwrap();
let keypair2 = UserKeypair::generate().unwrap();
let user_id = UserId::from_public_key(keypair1.public_key());
assert!(user_id.verify(keypair2.public_key()).is_err());
}
#[test]
fn test_user_keypair_generation() {
let keypair = UserKeypair::generate().unwrap();
let user_id = keypair.user_id();
assert_ne!(user_id.as_bytes(), &[0u8; PEER_ID_LENGTH]);
}
#[test]
fn test_user_keypair_serialization_roundtrip() {
let keypair = UserKeypair::generate().unwrap();
let (pub_bytes, sec_bytes) = keypair.to_bytes();
let restored = UserKeypair::from_bytes(&pub_bytes, &sec_bytes).unwrap();
assert_eq!(keypair.user_id(), restored.user_id());
}
#[test]
fn test_user_id_display() {
let keypair = UserKeypair::generate().unwrap();
let user_id = keypair.user_id();
let display = format!("{}", user_id);
assert!(display.starts_with("UserId(0x"));
assert_eq!(display.len(), "UserId(0x)".len() + 16);
}
#[test]
fn test_agent_certificate_issue_and_verify() {
let user_kp = UserKeypair::generate().unwrap();
let agent_kp = AgentKeypair::generate().unwrap();
let cert = AgentCertificate::issue(&user_kp, &agent_kp).unwrap();
cert.verify().unwrap();
assert_eq!(cert.user_id().unwrap(), user_kp.user_id());
assert_eq!(cert.agent_id().unwrap(), agent_kp.agent_id());
assert!(cert.issued_at() > 0);
}
#[test]
fn test_agent_certificate_wrong_key_fails() {
let user_kp = UserKeypair::generate().unwrap();
let agent_kp = AgentKeypair::generate().unwrap();
let mut cert = AgentCertificate::issue(&user_kp, &agent_kp).unwrap();
let other_user = UserKeypair::generate().unwrap();
cert.user_public_key = other_user.public_key().as_bytes().to_vec();
assert!(cert.verify().is_err());
}
#[test]
fn test_agent_certificate_tampered_agent_key_fails() {
let user_kp = UserKeypair::generate().unwrap();
let agent_kp = AgentKeypair::generate().unwrap();
let mut cert = AgentCertificate::issue(&user_kp, &agent_kp).unwrap();
let other_agent = AgentKeypair::generate().unwrap();
cert.agent_public_key = other_agent.public_key().as_bytes().to_vec();
assert!(cert.verify().is_err());
}
#[test]
fn test_identity_with_user() {
let machine_kp = MachineKeypair::generate().unwrap();
let agent_kp = AgentKeypair::generate().unwrap();
let user_kp = UserKeypair::generate().unwrap();
let cert = AgentCertificate::issue(&user_kp, &agent_kp).unwrap();
let expected_user_id = user_kp.user_id();
let identity = Identity::new_with_user(machine_kp, agent_kp, user_kp, cert);
assert_eq!(identity.user_id(), Some(expected_user_id));
assert!(identity.user_keypair().is_some());
assert!(identity.agent_certificate().is_some());
}
}