the answer

No company in the loop.
Just math, on a public ledger.

Onym replaces the trusted operator with four moving parts: a key ratchet on your phone, a zero-knowledge proof of group membership, a swappable transport, and group rules pinned to a public smart contract. Together they hide message content and sender identity from the network. What they do not hide is named in the threat model.

the math, as it runs

Keys never leave your phone.
The chain only sees a proof.

proof
ratchet
A chain of one-time keys that only walks forward. A leaked key opens nothing before it and nothing after it.
witness on-device secret
Proof you are in the group. Generated when you joined, kept on the phone, never shown to anyone.
prover at send time
When you send, your phone uses the witness to build a tiny proof: “someone in this group sent this.”
verifier on chain
The public contract checks the proof. It learns the proof was valid — and not a single fact more.

A sender proves “a member sent this” without revealing which member. The group never learns who. The transport sees ciphertext and timing, not content and not which member sent it.

the pipe is replaceable

Relays are dumb pipes.
Swap them like cables.

Onym ships with Nostr today and is indifferent to whatever ships next. A relay sees opaque blobs going past — no sender, no recipient, no message. Use the default, run your own, or borrow a friend’s.

nostr.example
default
relay · public
uptime 99.97%
in transit
your-relay.lan
self-hosted
relay · private
last seen 4s
in transit
friend.box
shared
relay · invite-only
uptime 99.4%
in transit
+
add a relay

Every blob looks the same to every relay. Swap the relay; the contents stay opaque. The transport is a courier with a sealed envelope and no return address.

rules, in public

No operator console.
A contract anyone can read.

contract surface
soroban · public
addr CDQA…HZRW flavor sep-* license mit override none
create_group(commitment, …) mint a new group with an initial commitment founder
update_commitment(proof, new_commitment) any state change — admit, remove, vote, role swap zk proof
verify_membership(proof, group_id) contract confirms a proof against the current commitment verify
bump_group_ttl(group_id) keep the group’s storage alive across ledger TTL anyone

The rules are visible. The runtime is the rules. There is no admin who can override them, no console that can suspend you, no version of the app that does something different than what you can read.

the payoff (and the limits)

Hidden by construction.
Honest about what isn't.

message content (always, for everyone outside the group)
which member of a group sent a given message
the human behind an on-chain identity (from the chain alone)
recovery phrase, private keys, message history (stay on device)

What is not hidden — connection timing, group existence, group-size range, on-chain operation timing — is named in the threat model.

the four flows

What happens, in order,
when you press a button.

Identity creation

[device]                          [network]
  |
  | generate BIP-39 (128-bit)
  | derive secp256k1, Ed25519,
  |        X25519, BLS12-381 keys
  | store on device only
  |
  | (no traffic. identity is local
  |  until the first action.)

Group creation

[founder]          [relayer]           [Soroban contract]
  | pick group_id
  | compute commitment
  | sign Soroban auth
  |   for create_group
  |
  |--- submit ---->|
                       |-- Stellar tx+fee -->|
                                           | verify caller auth
                                           | verify ZK proof
                                           | check id unused
                                           | store commitment
                                           | emit GroupCreated
                       |<-- tx confirmed ----|
  |<- confirmed ---|

Message send

[sender]          [Nostr relay]      [recipients]
  | encrypt to members
  | sign with sender key
  |
  |--- publish blob -->|
                          |-- fan out -->|
                                            | decrypt,
                                            | verify sig,
                                            | render

Member action (admit · vote · update)

[member]           [relayer]           [Soroban contract]
  | compute new commitment
  | build ZK proof of
  |   legal transition
  | NO user signature
  |
  |--- submit ----->|
                        |-- Stellar tx+fee -->|
                                            | verify ZK proof
                                            | check c_old matches
                                            | store c_new
                        |<-- tx confirmed ----|
  |<- confirmed ----|

Every flow involves three parties at most: your device, optionally a relayer to pay Stellar fees, and the Soroban contract on chain. The relayer never holds plaintext or signing authority. The contract is the only durable shared state.