RPE2E Protocol
RPE2E stands for Repartee End-to-End. Version 1 is the protocol currently implemented in native form by repartee and in companion scripts for WeeChat and irssi-compatible clients such as erssi.
This page describes the protocol as implemented today, not an aspirational design draft.
Scope and goals
RPE2E v1 adds end-to-end encryption for IRC channels and private conversations without changing IRC transport. The server still routes traffic, but compatible clients exchange keys out of band and send encrypted payloads over normal IRC messages.
The design goal is pragmatic interoperability across three environments:
- native Rust in repartee
- Python script for WeeChat
- Perl script for irssi and erssi
Version identifiers
RPE2E v1 currently uses these identifiers:
| Item | Value |
|---|---|
| Protocol string | RPE2E01 |
| Encrypted message prefix | +RPE2E01 |
| Handshake CTCP tag | RPEE2E |
| Handshake version field | v=1 |
The protocol name is RPE2E. The handshake tag is RPEE2E.
Security model
RPE2E encrypts message bodies end-to-end, but it does not hide IRC metadata. The server can still observe:
- nicknames and
ident@host - channel names or PM targets
- timing and approximate message sizes
- that a handshake took place
Trust is TOFU-oriented: the first contact can require manual accept, can be auto-accepted, or can be silently ignored depending on channel mode. Fingerprint verification is still required if you want protection against active impersonation.
RPE2E v1 also intentionally does not implement a Signal-style double ratchet. Session keys rotate on handshake and REKEY, not on every message.
Handshake transport
Handshake traffic is carried inside CTCP framing sent via NOTICE.
RPEE2E KEYREQ v=1 c=#x p=<b64u32> e=<b64u32> n=<b64u16> s=<b64u64>
RPEE2E KEYRSP v=1 c=#x p=<b64u32> e=<b64u32> wn=<b64u24> w=<b64u> n=<b64u16> s=<b64u64>
RPEE2E REKEY v=1 c=#x p=<b64u32> e=<b64u32> wn=<b64u24> w=<b64u> n=<b64u16> s=<b64u64>
Field summary:
c: channel or PM pseudochannel contextp: long-term Ed25519 identity public keye: ephemeral X25519 public keywn: wrap nonce for AEAD-wrapped session keyw: wrapped session key ciphertextn: anti-replay nonce included in the signed payloads: Ed25519 signature
KEYREQ starts the exchange. KEYRSP returns a wrapped session key. REKEY is the unsolicited distribution form used to rotate an outgoing session key to already trusted peers.
Cryptographic construction
RPE2E v1 uses:
- Ed25519 for long-term identity keys and handshake signatures
- X25519 for ECDH during key exchange
- HKDF-SHA256 for wrap-key derivation
- XChaCha20-Poly1305 for message encryption and wrapped session keys
For REKEY, repartee maps the peer's Ed25519 identity public key to X25519 using the RFC 7748 Appendix A birational map, matching libsodium-compatible clients.
Encrypted message wire format
Encrypted channel or PM traffic is sent as a normal IRC line body:
+RPE2E01 <msgid> <ts> <part>/<total> <nonce_b64>:<ct_b64>
Each chunk is an independent cryptographic unit:
msgid: random 8-byte message identifier, hex-encodedts: unix timestamp in secondspart/total: chunk index withtotal <= 16nonce_b64: 24-byte XChaCha20 noncect_b64: ciphertext plus Poly1305 tag
Receivers decrypt chunks immediately. There is no stateful reassembly buffer in the protocol.
AAD and replay window
The current implementation authenticates this AAD layout:
PROTO
|| be16(channel.len) || channel
|| be16(8) || msgid
|| be16(8) || ts_be
|| be16(1) || part
|| be16(1) || total
Notes:
- the sender handle is not included in AAD
- sender binding is enforced at the keyring lookup layer using
(handle_from_IRC_prefix, context) - the default timestamp tolerance is
300seconds - fields are length-prefixed to prevent delimiter ambiguity
Context model
For real channels, the context key is the channel itself, for example #repartee.
For private messages, RPE2E uses a pseudochannel:
@<peer_handle>
Example:
@~alice@static.example.org
This keeps session storage consistent across channels and PMs while avoiding nick collisions.
Channel modes and trust states
RPE2E v1 supports three per-context modes:
| Mode | Behavior |
|---|---|
auto-accept | first contact is trusted automatically |
normal | pending request with explicit accept/decline |
quiet | unknown peers are ignored until manually handled |
Peer trust state is tracked as pending, trusted, or revoked.
Client scripts and downloads
Official companion scripts are published in the repartee repository:
WeeChat requirements
- WeeChat with Python scripting enabled
- Python 3
PyNaClinstalled, for examplepip install pynacl- load
scripts/weechat/rpe2e.py
irssi and erssi requirements
- irssi or erssi with Perl scripting enabled
libsodiumavailable on the system- Perl modules used by the script, including
Crypt::NaCl::Sodium,MIME::Base64,JSON::PP,Digest::SHA,FFI::Platypus, andFFI::Platypus::Buffer - load
scripts/irssi/rpe2e.pl
Current v1 limitations
- no metadata hiding beyond encrypted message bodies
- no per-message forward secrecy / double ratchet
- only compatible clients can read
+RPE2E01payloads - export/import is operationally useful, but exported key material is plaintext and must be protected accordingly
Relationship to repartee
repartee is the reference implementation at the moment. The protocol page here follows the current Rust code in src/e2e/ and the matching client scripts in scripts/.