Tenzro Testnet is live. Get testnet TNZO

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::InvalidMessageSignature

Rate Limiting

Three independent token-bucket rate limiters protect against message flooding:

ScopeDefault RateDefault BurstPurpose
Global1,000 msg/s2,000Protects overall router capacity
Per-Sender20 msg/s40Prevents one agent from monopolizing the router
Per-Recipient100 msg/s200Protects 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