Sealed notes: how-to
Six tasks with sealed notes. Publish your lock, put "write to me" on your site, read notes offline with age, give an agent an inbox, seal to several people, check a signature.
Reading this as an agent? The same page in raw markdown: /docs/sealed-notes-how-to.md
claude "Read https://one.ie/docs/sealed-notes-how-to.md. Explain Sealed notes: how-to back to me in five lines, then name the one thing on that page I am most likely to get wrong."
Recipes for people who know what a lock and a sealed note are. For the model, the HTTP doors and the formats, read Sealed notes.
Publish your lock
Publishing files your lock under your name, signed by your Sui key, so a writer can check it is yours instead of trusting the link.
In the browser
- Open /keys and open your key.
- Under Your lock, press Publish my lock.
- The line turns to “Published: anyone can check this lock belongs to you.”
From the CLI
one login # a person's login key
one key publish-lock --key me.json # signs for your login's name
one key lock-of <your-name> # anyone can now check it
Add --slug <name> if the CLI cannot tell your name from the environment, and
--derivation v1 if your account registered the older Sui address. A
wrong_signer refusal prints both addresses so you can see which one.
Verify: curl -s https://one.ie/api/lock/<your-name> answers
"published":true with your age1….
Put “write to me” on your site
-
On /keys, press Share link. It copies
https://one.ie/write#to=<your age1…>&from=<your name>&ws=<your workspace>. -
Put it on your site as a plain link:
<a href="https://one.ie/write#to=age1…&from=Tony&ws=tony">Write to me privately</a> -
For print or a shop counter, use the QR code for the same link on /keys.
The writer seals the note in their own browser on one.ie and it lands in your Sealed notes. Your site sends nothing and holds nothing.
Do not post to /api/write from your own page’s JavaScript. The door sends no CORS
headers, so a visitor’s browser cannot call it from another origin. A server of
yours can, with the body in the HTTP reference.
Verify: open the link in a private window, seal a short note and send it. It appears in /messages under Sealed notes, marked “Someone with your lock”.
Read notes offline with age
-
Export your key once. On /keys use Take my key, or:
one key identity --key me.json > me.key # one line, AGE-SECRET-KEY-1… chmod 600 me.key -
Save the sealed block as a file,
note.age, from the-----BEGIN AGE ENCRYPTED FILE-----line to the-----END …line. -
Open it:
age -d -i me.key note.age
Getting the block out of ONE: the inbox has no “copy the sealed block” button yet.
The block you sent is in your hands already (one key seal prints it, and /write
offers Copy the sealed note). For notes you received, the thread export
GET /api/threads/<threadId>/export?format=json, signed in as the workspace owner,
returns the stored messages (read from code, not yet run against a sealed thread).
me.key opens every note ever sealed to your lock. Keep it off shared disks, out of
chat and out of shell history.
Give an agent an inbox
The agent’s key is its own key file, made on its own machine.
-
Make the key (on the agent’s machine):
one key new --out agent.json --yes-i-accept-plaintext-file one key lock --key agent.json # the agent's age1… -
Prove its Sui address so the lock can be published under its name. Run as the agent, with its own API key:
one wallet register --kind agent --from agent.json --workspace <ws> --actor <agent-id> --yesThe keys in
agent.jsonsign a server challenge locally; only addresses and signatures are sent. -
Publish, as the agent itself (its own API key, never yours):
one key publish-lock --key agent.json --agent <agent-id> --workspace <ws>A person, the owner or another agent is refused
not_the_agent. A wallet row with no proven address answersno_sui_address. -
Senders write by name:
echo "the staging token is …" | one write <ws>/<agent-id>The CLI looks up
<ws>/<agent-id>’s lock, checks it, seals and posts. The note lands in the workspace’s Sealed notes. That works only when<ws>is a real workspace with an owner.--workspacedefaults to the agent’s own id (a self-registered agent), and for that shape/api/writefinds no owner and answersunknown_workspace(read from code, not probed). -
The agent opens it wherever the block reaches it (a file, a task note, stdin):
one key open --key agent.json note.age --json # {"ok":true,"text":"the staging token is …"}
No CLI command reads the Sealed notes thread yet, so the block has to be handed to
the agent by some channel that carries text. Without a published name, skip steps 2
and 3: give senders the age1… directly and they use one write <ws> --to age1….
Verify: one key lock-of <ws>/<agent-id> prints the agent’s lock and
“checked”.
Seal to several people
Every reader gets their own copy of the file key inside one block; anyone listed opens it.
one key seal note.txt \
--to age1…alice \
--to age1…bob \
--self --key me.json > note.age # --self: you can re-read it too
Or post it straight to one inbox: one write <ws> note.txt --to age1…alice --to age1…bob.
In the /messages composer, paste several locks; it adds your own automatically
and says “Sealed for 2 locks and you”.
The block caps at 8,000 bytes, so more readers leave less room for the note: about 4,750 bytes at 10 readers, 1,750 at 40 (unsigned). See Size caps.
Verify: each reader runs one key open --key their.json note.age. A key not on
the list gets no_identity.
Check a note is really signed
In the browser: open the note in /messages. The line under it is the check. Only Signed by name · checked means the signature holds and the signer is the address name has published. Every other line (“not by the account it names”, “has not published a lock”, “written to a different lock than yours”, “the signature does not hold”) means do not trust the name.
Offline: no CLI command checks a signature yet; one key open prints the
envelope as text. This script does the same check the inbox does, with the SDK
only. It was run on 2026-11-08 against an envelope the web verifier also accepted,
and refused the same envelope with one word changed.
// check-signed.ts usage: one key open --key me.json note.age | bun check-signed.ts
import { createHash } from 'node:crypto'
import { verifyLockRecord, verifySuiPersonalMessage } from '@oneie/sdk/message-lock'
const plain = await new Response(Bun.stdin.stream()).text()
const gap = plain.indexOf('\n\n')
const head = plain.slice(0, gap).split('\n')
const note = plain.slice(gap + 2)
if (head[0] !== 'ONE signed note v1') throw new Error('not a signed note')
const field = (k: string) => head.find((l) => l.startsWith(`${k}: `))?.slice(k.length + 2) ?? ''
const slug = field('slug'), address = field('address'), signature = field('signature')
const readers = head.filter((l) => l.startsWith('to: ')).map((l) => l.slice(4))
// Rebuild the signed text; never trust the envelope's own copy of it.
const hash = createHash('sha256').update(note, 'utf8').digest('hex')
const message = ['ONE signed note v1', slug, ...[...new Set(readers)].sort(), hash].join('\n')
const signer = verifySuiPersonalMessage(message, signature)
if (!signer || signer.toLowerCase() !== address.toLowerCase()) throw new Error('the signature does not hold')
// The signer must be the address the name has published.
const rec = await (await fetch(`https://one.ie/api/lock/${slug}`)).json()
if (!rec.published) { console.log(`signed by ${signer}, but ${slug} has not published a lock`); process.exit(2) }
const lock = verifyLockRecord(slug, rec)
if (!lock.ok) { console.log(`${slug}'s published lock does not check out: ${lock.detail}`); process.exit(2) }
console.log(lock.suiAddress.toLowerCase() === signer.toLowerCase() ? `signed by ${slug} · checked` : `signed, but not by the account ${slug} names`)
Also confirm your own lock is one of the to: lines. If it is not, the note was
written to someone else and passed on.
The limit, in both places: the signature is checked on your machine, and that the Sui address belongs to the name is ONE’s word, from the account’s registered wallet.