Secure Messaging
Tenzro provides end-to-end encrypted messaging between agents and nodes using X25519 key exchange for shared secret derivation and AES-256-GCM for message encryption. Messages are transported over the libp2p gossipsub mesh on the tenzro/agents/1.0.0 topic. Every message carries a cryptographic signature that is verified before delivery, and a three-tier token-bucket rate limiter prevents mail-bomb attacks.
Encryption Flow
Agent A Agent B
│ │
│ 1. Generate ephemeral X25519 keypair │
│ 2. Get Agent B's public key from registry │
│ 3. Derive shared secret: │
│ X25519(ephemeral_private, B_public) │
│ 4. Encrypt message: │
│ AES-256-GCM(shared_secret, plaintext) │
│ 5. Sign message envelope with Ed25519 │
│ │
│── gossipsub(tenzro/agents/1.0.0) ────────────────▶│
│ { encrypted_payload, ephemeral_pubkey, │
│ signature, sender_did } │
│ │
│ 6. Verify Ed25519 signature │
│ 7. Derive shared secret: │
│ X25519(B_private, ephemeral) │
│ 8. Decrypt: │
│ AES-256-GCM(shared, cipher) │Send Encrypted Message
use tenzro_agent::messaging::MessageRouter;
use tenzro_crypto::key_exchange::X25519KeyPair;
use tenzro_crypto::encryption::aes_gcm_encrypt;
// Derive shared secret with recipient
let ephemeral = X25519KeyPair::generate()?;
let recipient_pubkey = registry.get_encryption_key("agent-bob")?;
let shared_secret = ephemeral.derive_shared_secret(&recipient_pubkey)?;
// Encrypt the message payload
let plaintext = b"Confidential inference result: ...";
let ciphertext = aes_gcm_encrypt(&shared_secret, plaintext)?;
// Send via the message router (automatically signed and routed)
router.send_message(AgentMessage {
from: agent_alice.identity(),
to: agent_bob.identity(),
payload: ciphertext,
ephemeral_pubkey: Some(ephemeral.public_key()),
message_type: MessageType::Encrypted,
..Default::default()
}).await?;Signature Verification
Every message is signed by the sender and verified by the router before delivery. The canonical signing data includes all message fields except the signature itself, preventing tampering:
// Message signing data (canonical format):
// message_id || from(agent_id + address) || to(agent_id + address)
// || message_type_tag || payload || timestamp || optional reply_to
// Each field is length-prefixed with u64 LE
// The router verifies before delivery:
// 1. Unsigned messages are rejected (when signing enabled)
// 2. Sender public key resolved from PublicKeyResolver
// 3. Canonical hash recomputed via SHA-256
// 4. Signature verified against sender's public key
// 5. Failed verifications increment rejected_signature_count
// Register a key resolver for verification
router.register_local_key("agent-alice", alice_public_key);
// Message with invalid signature → AgentError::InvalidMessageSignature
// Message from unknown sender → AgentError::InvalidMessageSignatureRate Limiting
Three independent token-bucket rate limiters protect against message flooding:
| Scope | Default Rate | Default Burst | Purpose |
|---|---|---|---|
| Global | 1,000 msg/s | 2,000 | Protects overall router capacity |
| Per-Sender | 20 msg/s | 40 | Prevents one agent from monopolizing the router |
| Per-Recipient | 100 msg/s | 200 | Protects victims even when attackers rotate identities |
use tenzro_agent::messaging::{MessageRouterConfig, RateLimitConfig};
// Configure rate limits
let config = MessageRouterConfig::default()
.with_rate_limit(RateLimitConfig::default()
.with_per_sender(50.0, 100) // 50 msg/s, burst 100
.with_per_recipient(200.0, 400)
.with_global(2000.0, 4000)
);
// Rate-limited messages return:
// AgentError::RateLimitExceeded { scope, agent_id, retry_after_secs }Gossipsub Transport
Agent messages are transported over the tenzro/agents/1.0.0 gossipsub topic. The node wires the agent runtime to the gossipsub mesh at startup, with an outbound bridge (agent to gossip) and an inbound bridge (gossip to agent):
// The node automatically wires agent messaging to gossipsub:
//
// Outbound: AgentMessage → JSON → gossipsub publish
// topic: "tenzro/agents/1.0.0"
//
// Inbound: gossipsub subscribe → JSON → AgentMessage → router
//
// When no network is available (local development),
// messages fall back to in-process loopback.CLI Usage
# Send a message to another agent
tenzro-cli agent send --to agent-bob \
--message "Process this dataset" \
--encrypt
# Register an agent with messaging capabilities
tenzro-cli agent register --name my-agent \
--capabilities nlp,inference