# How OpenHuman Uses the Signal Protocol for Agent-to-Agent Encrypted Orchestration

> Discover how OpenHuman uses the Signal protocol for secure agent-to-agent encrypted orchestration. Learn about double-ratchet, X25519 identity, and ChaCha20-Poly1305 encryption.

- Repository: [Tiny Humans/openhuman](https://github.com/tinyhumansai/openhuman)
- Tags: how-to-guide
- Published: 2026-08-28

---

**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`](https://github.com/tinyhumansai/openhuman/blob/main/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.

```rust
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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/signal_store.rs)) lazily initializes the `FileSessionStore`. The identity key remains in memory while the OS keychain protects the store's encryption secret.

```rust
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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/manifest.rs):

```rust
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 `identity` field of `FileSessionStore` (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 inside `WORKSPACE_INTERNAL_DIRS` (file header, lines 16-18), blocking normal agent tools from tampering with encrypted session files.
- **Forward secrecy** — The `SignalSession` double-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

```rust
// 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

```rust
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

```rust
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

```rust
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`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tinyplace/signal_e2e_tests.rs) validates the complete flow:

```rust
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`](https://github.com/tinyhumansai/openhuman/blob/main/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 **`FileSessionStore`** encrypts 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 `SignalSession` advances keys on every message and persists updated state atomically.
- All session files live under **`{workspace_dir}/tinyplace/signal/`**, are prefixed with **`enc2:`**, and are protected from agent modification by **`WORKSPACE_INTERNAL_DIRS`**.
- The orchestration engine in **[`src/openhuman/tinyplace/manifest.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tinyplace/manifest.rs)** encrypts 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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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.