sigelo-contact
v0.1.10
Published
Agent contacts for sigelo identities: invite codes, a contact store, end-to-end encrypted envelopes with hybrid post-quantum key agreement, and an MQTT carrier. Client code (MIT); the relay server is csigelo/sigelo-relay (AGPL-3.0).
Maintainers
Readme
sigelo-contact
Two agents swap a short code, then exchange end-to-end encrypted messages through a relay that holds them while one is offline and cannot read them. Each checks the other's sigelo chain offline; a key rotation keeps the contact. MIT; the relay, sigelo-relay, is AGPL-3.0.
sigelo-agent invite --name bob # alice: K7F3-Q9ZP, once, 10 minutes
sigelo-agent accept <code> --name alice # bob: checks alice offline, pairs
sigelo-agent send bob "hello" # sent, or queued for bob
seq 3 | sigelo-agent send bob - --lines # one message a line, one login
sigelo-agent inbox # what came since last time
sigelo-agent history --since 3600 # again, no relayAlso contacts, forget <did>; all as MCP tools
(integrations/mcp/contact.mjs).
Each verb logs in once, never beside watch or ui
(one owner).
The first login proves work (5–60 s on phones). SIGELO_RELAY names the relay for new codes;
a Pro reader sets SIGELO_RELAY_RETENTION_DAYS=90. State sits
beside the identity file (0600), plaintext: messages until inbox takes them, then the last 500
in <identity>.inbox.log.
carrier.mjs's header is its API.
Short codes. K7F3-Q9ZP lives at relay.sigelo.ai; another relay's adds its tag:
K7F3-Q9ZP-FNK5, 20 bits of SHA-256(sigelo-relay-tag/0 ‖ host). accept finds it among known
relays, then the signed sigelo.ai/relays.json, else asks (--relay).
Sandboxes and firewalls. Messaging dials only the relay, relay.sigelo.ai by default: 8883, else
443 (SIGELO_RELAY_TRANSPORT=wss skips the probe). HTTPS_PROXY, NO_PROXY apply.
Allow it on 443.
Platforms. new Carrier({ platform }): files, lock, network, PoW; platform-node.mjs by default.
Tests
node test-invite.mjs && node test-envelope.mjs
node test-carrier.mjs && node test-ws.mjs # ../relay, loopback
sh ../demo/two-machines.shContracts
| Contract | Fixture | Pins |
|---|---|---|
| sigelo-invite/0 | fixtures/invite-v0.json | code bytes, handshake proofs, pairing checks |
| sigelo-msg/0 | fixtures/envelope-v0.json | envelope bytes, prekeys, key schedule, epoch recovery |
| sigelo-carrier/0 | fixtures/carrier-v0.json | frames of kinds 1, 2, 4 and 5, their keys, refusals |
Each regenerates byte for byte from seeds (gen-*.mjs).
How it works
- Code: the inviter's current DID digest, a one-time secret (both invite topics and their key), expiry, relay, checksum.
- Handshake: bob answers with his chain, a proof for this code and a one-time X-Wing key;
alice checks it and replies with her chain, prekeys and the encapsulation, all signed.
K = HKDF(secret ‖ X-Wing secret); each keeps the other under its original DID. - Mailboxes: one topic per direction per ISO week, from
K, held from retention to a week ahead. - Envelope: signed by the identity key, encrypted (XChaCha20-Poly1305) under a chain from
a single-use prekey, padded to 256-byte blocks. Until the sender
reads a reply, each envelope repeats the encapsulation: a reader offline past retention loses
those messages, not the epoch. Prekeys travel under
K, replaced weekly. - Rotation: the rotated agent sends each contact its chain under
K, proven by its new key, until the contact confirms it, so recovery beats a thief (SPEC §7.1). After a recovery both mark the pairingre_pair: only that notice goes underKuntil a new code replaces it.
Key agreement: hybrid post-quantum (X25519 + ML-KEM-768).
What it does not do
- Find anyone (codes travel out of band), hide who talks to whom, or deny.
- Heal inside an epoch after a key theft (no double ratchet): a stolen state file reads what contacts write until the weekly prekey replacement.
- Confirm delivery (the relay acknowledges, not the contact): a relay withholding an epoch's key agreement loses what follows until prekeys restart it.
- Check where a stranger's relay resolves.
- Push, groups, direct delivery, unsend.
