DMs and Group Chats
Pipoke direct messages and group messages are end-to-end encrypted. The ciphertext lives on chain in an inbox shard or a group shard. The keys live in your wallet. The platform never sees the plaintext.
Pipoke runs on Octra Devnet today. Any fee, price, or limit referred to here is a contract setting chosen for testing. Every one is owner-settable, and mainnet values will be different. These docs describe how the mechanics work, not what the numbers are.
#DMs: the crypto
Pipoke DMs use NaCl box (X25519 + XSalsa20-Poly1305). Each participant has an X25519 box keypair derived deterministically from their wallet signature, published once via set_followers_only_pk.
For each DM, the app encrypts the plaintext twice:
| Ciphertext | Encrypted to | Read by |
|---|---|---|
ct_to |
recipient's box pubkey | the recipient |
ct_from |
your own box pubkey | you (so your outbox is readable) |
Both ciphertexts ship in the same on-chain tx. The shard stores both.
#DM method surface
| Method | Signature | Purpose |
|---|---|---|
send_message |
send_message(recipient, ct_to, ct_from) |
Standard DM. |
send_message_ext |
send_message_ext(recipient, ct_to, ct_from, reply_to, expires_at_epoch) |
DM with optional reply_to and per-message expiry. |
forward_message |
forward_message(recipient, ct_to, ct_from, forward_of_origin) |
Forward another DM, with attribution to the original. |
mark_read |
mark_read(pair_key, msg_idx) |
Mark a thread read on your side. |
set_dm_allowlist_required |
toggle | Only allow DMs from wallets on your allowlist. |
set_dm_allowlist_member |
(addr, on) |
Add or remove a wallet from your allowlist. |
set_dm_default_expiry |
(epochs) |
Default expiry for DMs you send. |
DMs always require a wallet signature. The session key cannot derive your X25519 secret, so DM sends are explicitly outside the session-key scope.
#16 inbox shards: pair routing
The inbox is sharded across 16 shards. Any two wallets always land on the same shard because the shard index is derived from the pair, not from the sender alone:
pair_inbox_id_of_call(my_addr, their_addr) -> shard_id (0..15)
The function is symmetric in (my_addr, their_addr): order does not matter. No matter who sends first, the conversation lives in one place.
The on-chain message record carries:
| Field | What it is |
|---|---|
pair_key |
Symmetric hash of the two wallets. |
msg_idx |
Per-pair sequential index. |
sender, recipient |
Wallets. |
ct_to, ct_from |
Sealed ciphertexts. |
reply_to |
Index of the message being replied to, if any. |
forward_of_origin |
Pointer to the original message, if a forward. |
expires_at |
Epoch after which the message is considered expired. |
epoch |
Send epoch. |
tx_hash |
The on-chain tx. |
#Groups: the crypto
A group uses a symmetric key (NaCl secretbox) shared across N members. The group shard stores the ciphertext of each message. The symmetric key is distributed to members through sealed wraps: one wrap per member, each sealed to that member's box pubkey.
| Method | Signature | Effect |
|---|---|---|
create_group |
create_group(name, my_key_ct, principal) |
Creates the group. The creator's own copy of the group key is my_key_ct. |
add_member |
add_member(group_id, member_addr, member_key_ct, principal) |
Adds a member. member_key_ct is the group key sealed to that member's box pubkey. |
remove_member |
remove_member(group_id, member_addr, principal) |
Removes a member from the on-chain roster. |
leave_group |
leave_group(group_id, principal) |
You leave. |
rename_group |
rename_group(group_id, new_name, principal) |
Rename. |
close_group |
close_group(group_id, principal) |
Close. |
reopen_group |
reopen_group(group_id, principal) |
Reopen. |
send_group_message |
send_group_message(group_id, text_ct, attachment_ct, principal) |
Send a group message. |
There are 16 group shards. A group is assigned to one at create-time.
#Group rekey (roadmap)
When a member leaves or is removed, the previous symmetric key is technically still readable to them (they have it in their browser). Group rekey rotates to a new symmetric key sealed only to the remaining members. Pipoke V1 does not yet implement automatic group rekey; it is on the roadmap.
#Why on chain
Permanent record (encrypted). Your conversations are not at a service provider's mercy. The bytes are on chain.
No metadata leak about message content. The chain holds ciphertext only. The platform sees who-sent-what-when and who-is-in-a-group-with-whom, but not what they said.