openclaw-ndr
v0.0.15
Published
OpenClaw channel plugin for nostr-double-ratchet (forward-secure E2E encryption)
Maintainers
Readme
openclaw-ndr
OpenClaw channel plugin for nostr-double-ratchet - forward-secure end-to-end encrypted messaging over Nostr.
Compatible with chat.iris.to.
Features
- Forward secrecy - Past messages remain secure even if keys are compromised
- Double ratchet encryption - Based on Signal's proven protocol
- Nostr transport - Messages sent via Nostr relays
- Interactive onboarding -
openclaw onboardwalks you through setup - Group chats - Native NDR group transport with shared-channel invites
Prerequisites
Install Rust and the required CLIs:
curl -sSf https://sh.rustup.rs | sh && cargo install ndr hashtree-cli- ndr - Required for double ratchet encryption
- hashtree-cli - Optional, for encrypted media uploads via hashtree
Installation
Note: OpenClaw's plugin system is under active development. These instructions may change.
From GitHub:
openclaw plugins install https://github.com/mmalmi/openclaw-ndrFrom a local clone:
git clone https://github.com/mmalmi/openclaw-ndr
openclaw plugins install -l ./openclaw-ndrSetup
Run the interactive onboarding:
openclaw onboardSelect the NDR channel when prompted. The onboarding will:
- Check if
ndrCLI is installed - Generate a private pairing link (and QR code)
- Wait for you to accept it in chat.iris.to
- Configure your owner pubkey automatically from the accepted invite (no manual
npubinput) - In most cases, you send the first message from chat.iris.to after starting the gateway
Accepting The Pairing Link
- Run
openclaw onboardand select NDR - Copy the pairing URL printed by the onboarding (or scan the QR)
- Open it in your browser (it will take you to chat.iris.to and start the chat)
- After you accept, the agent locks to your pubkey automatically
- Start
openclaw gateway run - Send the first DM from chat.iris.to, then the bot can reply
Start the gateway
openclaw gateway runConfiguration
The onboarding writes config to ~/.openclaw/openclaw.json. You can also edit it manually:
{
channels: {
ndr: {
// Owner's pubkey (npub or hex) - only messages from this key are handled as commands
ownerPubkey: "npub1...",
// Optional: Nostr relays (defaults shown below)
relays: [
"wss://temp.iris.to",
"wss://relay.snort.social",
"wss://relay.primal.net",
"wss://relay.damus.io",
"wss://offchain.pub"
],
// Optional: Path to ndr CLI (default: "ndr" in PATH)
ndrPath: "/path/to/ndr",
// Optional: Custom data directory for ndr (default: ~/.openclaw/ndr-data)
dataDir: "~/.openclaw/ndr-data",
// Optional: Group policy ("open" | "allowlist" | "disabled")
groupPolicy: "open",
// Optional: Allowed senders in groups (npub/hex, "*" allows anyone)
groupAllowFrom: ["npub1...", "abcdef..."],
// Optional: Allowed group IDs (UUIDs). When set, only listed groups are handled.
groups: ["11111111-1111-1111-1111-111111111111"]
}
}
}Authorization:
- Only messages from
ownerPubkeyare handled as agent commands - Messages from other pubkeys are logged but ignored
- If
ownerPubkeyis not set, the first accepted invite/contact is locked as owner
Usage
Check channel status
openclaw channels status --channel ndrList active chats
ndr chat listRelease Smoke Tests
Run these before publishing to verify both plugin and cross-app interop.
1) OpenClaw plugin docker e2e (alice <-> bob)
pnpm test:e2e:docker2) iris-chat interop using local nostr-double-ratchet TS build
This always tests against your local library tree (not the currently published npm version).
# Optional overrides:
# IRIS_CHAT_REPO=~/src/iris-chat
# NDR_TS_REPO=~/src/nostr-double-ratchet/ts
# REPEAT_EACH=8
pnpm test:interop:iris-local-lib3) dockerized OpenClaw bot + iris-chat seen receipt interop
Runs a real OpenClaw gateway bot in Docker with this plugin and verifies in
Playwright that Iris marks the outgoing message as seen.
The harness applies a temporary patch to the copied ndr source so listen
events use inner rumor IDs for receipts.
# Optional overrides:
# IRIS_CHAT_REPO=~/src/iris-chat
# NDR_REPO=~/src/nostr-double-ratchet
# OPENCLAW_REPO=~/src/openclaw
# RELAY_URL=wss://temp.iris.to
pnpm test:e2e:iris-seenGroups (NDR)
NDR groups are managed by the ndr CLI. This plugin listens for group_message
events and can reply in groups when allowed by groupPolicy (default: open).
Group invites from ownerPubkey are auto-accepted; other group invites are
not auto-accepted and the owner is notified to decide.
# Create a group (members are hex pubkeys)
ndr group create --name "My Group" --members <hex_pubkey,hex_pubkey>
# List groups (get group IDs)
ndr group list
# Send a group message
ndr group send <group_id> "hello"How it works
- Listening - Runs
ndr listento receive incoming messages - Receiving - Decrypts messages using the double ratchet session
- Sending - Uses
ndr sendto encrypt and publish messages - Session management - ndr handles key rotation automatically
- Groups - Native
ndr grouptransport with shared-channel invites
Security
- Forward secrecy - Each message uses a unique encryption key
- Session isolation - Each chat has its own ratchet state
- No key exposure - Private keys are managed by the ndr CLI
Troubleshooting
"ndr: command not found" / "hashtree-cli: command not found"
Install both (requires Rust):
curl -sSf https://sh.rustup.rs | sh && cargo install ndr hashtree-cliMedia attachments not working
Make sure hashtree-cli is installed: hashtree-cli --version
"Failed to send message"
Check that:
- You have an active chat session with the recipient
- The relay is reachable
- ndr CLI is working:
ndr chat list
Group messages not showing up
Check that:
groupPolicyisopen(default) orallowlistwith a matchinggroupAllowFrom- The group ID is included in
groupsif you configured a group allowlist - Your NDR client is in the group and has accepted it
