How OpenHuman Uses the Signal Protocol for Agent-to-Agent Encrypted Orchestration
OpenHuman achieves agent-to-agent encrypted orchestration by running the Signal protocol's double-ratchet over a wallet-derived X25519 identity and persisting every session in a ChaCha20-Poly1305 encrypted file store, guaranteeing forward secrecy and at-rest protection.
The tinyhumansai/openhuman repository implements agent-to-agent encrypted orchestration through a Rust-based integration with the Signal protocol. By deriving cryptographic identities directly from the user's wallet seed and layering an encrypted file-backed store underneath the double-ratchet, the system keeps agent messages confidential both in transit and on disk.
Core Components of the Signal Implementation
OpenHuman's Signal integration lives in the src/openhuman/tinyplace/ directory and consists of three tightly coupled components: identity derivation, durable encrypted storage, and the session wrapper.
Wallet-Derived X25519 Identity Keys
Instead of generating and persisting a traditional Signal identity keypair, OpenHuman derives the X25519 keypair from the user's existing wallet seed. In src/openhuman/tinyplace/signal_store.rs, the function ed25519_seed_to_x25519_keypair (lines 9-11) converts the Ed25519 seed into an X25519 keypair. The private key never leaves memory and is never written to disk.
let seed = crate::openhuman::web3::wallet::tinyplace_signer_seed().await?;
let identity = ed25519_seed_to_x25519_keypair(&seed);
FileSessionStore for Encrypted-At-Rest Persistence
The FileSessionStore struct in src/openhuman/tinyplace/signal_store.rs implements the SessionStore trait while encrypting every pre-key, signed-pre-key, and session record. It uses a SecretStore backed by the OS keychain to obtain the ChaCha20-Poly1305 secret. When writing, encrypt_and_write (lines 55-67) prefixes each file with enc2: and encrypts the JSON representation. When reading, read_and_decrypt (lines 89-103) decrypts the payload before deserialization.
The store also enforces atomicity: it writes through tempfile::NamedTempFile and renames the temporary file into place (lines 66-84), preventing corruption if the process crashes mid-write.
SignalSession Wrapper and Double-Ratchet
The tinyplace SDK provides SignalSession, which consumes the in-memory identity key and an Arc<FileSessionStore>. Real-world instantiation appears in src/openhuman/tinyplace/manifest.rs (lines 3660-3815). The session exposes encrypt and decrypt methods that advance the double-ratchet and automatically persist updated state via the store.
Agent-to-Agent Encrypted Orchestration Flow
The end-to-end flow follows four distinct stages.
1. Bootstrap the Global Encrypted Store
At runtime, global_signal_store() (lines 40-42 in signal_store.rs) lazily initializes the FileSessionStore. The identity key remains in memory while the OS keychain protects the store's encryption secret.
let seed = crate::openhuman::web3::wallet::tinyplace_signer_seed().await?;
let identity = ed25519_seed_to_x25519_keypair(&seed);
let secret_store = SecretStore::new(&openhuman_dir, true);
let store = FileSessionStore::new(identity, store_dir, secret_store).await?;
2. Persisted Session State on Every Message
Each call to SignalSession::encrypt or SignalSession::decrypt triggers the SessionStore trait. The FileSessionStore implementation serializes the session to JSON, encrypts it via encrypt_and_write, and flushes it to {workspace_dir}/tinyplace/signal/... (directory layout documented in lines 21-27 of signal_store.rs). On load, read_and_decrypt restores the session from the encrypted file.
3. Message Exchange Between Agents
An agent obtains the global store through global_signal_store_arc() and constructs a SignalSession as shown in manifest.rs:
let store = global_signal_store_arc().await?;
let session = tinyplace::signal::session::SignalSession::new(
identity.public_key,
Arc::clone(&store) as Arc<dyn tinyplace::signal::store::SessionStore>,
);
Outbound payloads are encrypted with session.encrypt, and inbound payloads are decrypted with session.decrypt. Because the store is wrapped in an Arc, multiple agents can share the same encrypted backing state safely.
4. Orchestration Engine Integration
The tiny.place manifest pipeline encrypts agent-to-agent payloads before they leave the local process over RPC. The receiving agent reconstructs its session from the shared encrypted store, decrypts the payload, and advances its ratchet state. This design ensures that even a compromised filesystem cannot reveal session secrets because all records remain encrypted with the keychain-backed secret.
Security Guarantees Enforced by the Source Code
OpenHuman's implementation enforces five specific security properties:
- Identity confidentiality — The X25519 private key lives only in the
identityfield ofFileSessionStore(lines 93-95) and is derived from the wallet seed without filesystem persistence. - At-rest encryption — Every file written by the store carries the
enc2:prefix and is encrypted with ChaCha20-Poly1305 before leaving memory (encrypt_and_write, lines 55-67). - Agent write protection — The
{workspace_dir}/tinyplace/tree is declared insideWORKSPACE_INTERNAL_DIRS(file header, lines 16-18), blocking normal agent tools from tampering with encrypted session files. - Forward secrecy — The
SignalSessiondouble-ratchet evolves cryptographic keys on every message, and the latest ratchet state is always what gets persisted. - Atomic durability — Session files are written to temporary files and renamed into place, so a crash cannot leave a partially written session record.
Code Examples
Initialize the Global Encrypted Store
// Obtain the global store; this creates it on first use.
let store = openhuman::tinyplace::global_signal_store_arc().await?;
Create a SignalSession for an Agent
use tinyplace::signal::session::SignalSession;
// `store` is an `Arc<FileSessionStore>` from the previous step.
let identity = store.identity_x25519_key_pair().await?;
let session = SignalSession::new(identity.public_key, store.clone());
Encrypt a Message for a Peer
let plaintext = b"Hello fellow agent!";
let encrypted = session.encrypt("bob@example.com", plaintext).await?;
println!("Encrypted payload: {}", encrypted); // starts with "enc2:"
Decrypt a Received Message
let received = /* payload from the other agent */;
let decrypted = session.decrypt(received).await?;
println!("Decrypted: {}", String::from_utf8_lossy(&decrypted));
Full Round-Trip Test
The end-to-end suite in src/openhuman/tinyplace/signal_e2e_tests.rs validates the complete flow:
let alice = SignalSession::new(alice_id.public_key, store.clone());
let bob = SignalSession::new(bob_id.public_key, store.clone());
let msg = b"Secret handshake";
let enc = alice.encrypt("bob", msg).await?;
let dec = bob.decrypt(enc).await?;
assert_eq!(msg, &dec[..]);
The "Full Alice ↔ Bob round trip" section appears at lines 204-210 in signal_e2e_tests.rs.
Summary
- OpenHuman implements agent-to-agent encrypted orchestration by layering the Signal protocol on top of a wallet-derived X25519 identity.
- The
FileSessionStoreencrypts every session record with ChaCha20-Poly1305 via a keychain-backed secret, ensuring encrypted-at-rest persistence. - Forward secrecy is maintained because the double-ratchet in
SignalSessionadvances keys on every message and persists updated state atomically. - All session files live under
{workspace_dir}/tinyplace/signal/, are prefixed withenc2:, and are protected from agent modification byWORKSPACE_INTERNAL_DIRS. - The orchestration engine in
src/openhuman/tinyplace/manifest.rsencrypts RPC-bound payloads transparently before they cross process boundaries.
Frequently Asked Questions
How is the Signal identity key generated without storing secrets on disk?
OpenHuman derives the X25519 identity keypair from the user's existing wallet seed through ed25519_seed_to_x25519_keypair in src/openhuman/tinyplace/signal_store.rs (lines 9-11). The resulting private key is held only in the FileSessionStore struct's memory and is never serialized to the filesystem.
What encryption algorithm protects session files at rest?
The store uses ChaCha20-Poly1305 to encrypt every JSON session record before writing. The encrypt_and_write method (lines 55-67) prefixes each file with enc2:, and the symmetric key is managed by the OS keychain through SecretStore.
How does OpenHuman ensure forward secrecy between agents?
Forward secrecy is provided by the Signal protocol's double-ratchet inside SignalSession. Each encrypt or decrypt operation advances the ratchet and immediately persists the new state through the encrypted FileSessionStore. Because old keys cannot be recovered from the evolved state, compromise of a future session does not expose past messages.
Where does the orchestration engine invoke Signal encryption before RPC?
The real-world integration lives in src/openhuman/tinyplace/manifest.rs (lines 3660-3815), where the code builds a SignalSession from the global store and calls encrypt on outbound agent-to-agent payloads. The receiving side calls decrypt after pulling the message from the RPC channel, completing the encrypted orchestration handshake.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →