One drawn glyph is one byte.
Draw the first bytes of a nostr event id or pubkey on any surface with any pen, chalk or mower. Each byte is a square frame holding one of sixteen upright icons and up to four corner dots: the icon is the first hex digit, the dots are the second. A phone reads the row back on the device, and a relay resolves the bytes to the event or the profile they name.
How it works
-
Draw
Take the first six bytes of an id or a pubkey. For each byte draw a square frame, the icon for its first hex digit upright in the middle, and the dots for its second hex digit in the corners. Draw them left to right, underline the whole row, and put a fat dot at the start of the line.
e8ed3798c6ffas the reference renderer draws it, one byte per frame:e8star, dot top-leftedstar, dots top-left, top-right, bottom-left37moon, dots top-right, bottom-right, bottom-left98tree, dot top-leftc6x, dots top-right, bottom-rightfffish, dots top-left, top-right, bottom-right, bottom-left
-
Read
Open the reader on a phone and take a photo. It finds the frames, the underline and the dot, straightens each frame, and reads the icon and each corner dot with a small neural network, all in JavaScript on the device. The underline tells it which way is up, and the icons check it: each has a top, so a photo taken sideways or upside down reads the same, even without an underline.
Where a glyph is unclear the reader keeps the alternatives instead of guessing, and returns the most probable whole readings, ranked. A missing glyph is never dropped silently, because that would shift every byte after it.
-
Look up
The bytes are a prefix. The reader asks a relay for every candidate reading at once, as an event id prefix and as a pubkey prefix, and shows what comes back with the author's profile. You confirm which one you meant. A wrong candidate matches nothing, which is why there is no checksum.
How well does it work
On the synthetic benchmark in BENCH.md (200 scenes of pen and marker drawings with random handwriting severity, perspective, any camera roll, lighting, blur and JPEG on photographed backdrops) the reference reader finds 89% of the frames, reads 95% of the bytes correctly where the frame count matched, and gets the whole row right as its first answer 63% of the time; the smaller model that runs in the browser gets 62%. With chalk on pavement and paths in crop fields mixed in, whole rows drop to 45%: grainy strokes still break frames apart. Real handwriting so far: rows in coloured chalk on a sidewalk read exactly, found by their colour where in gray they barely differ from the concrete. Faint chalk whose colour differs from the surface but whose brightness does not, and rows drawn in two colours, are still weak, and that is the next thing to improve.
The alphabet
| hex | icon | hex | icon |
|---|---|---|---|
| 0 | house | 8 | pie |
| 1 | heart | 9 | tree |
| 2 | drop | a | plus |
| 3 | moon | b | flag |
| 4 | crown | c | x |
| 5 | arrow | d | bolt |
| 6 | box | e | star |
| 7 | triangle | f | fish |
The second hex digit is the sum of the corner dots, clockwise from the top-left: 8 top-left, 4 top-right, 2 bottom-right, 1 bottom-left. So e8 is a star with one dot top-left, 50 an arrow with no dots, ff a fish with all four. Sixteen icons times sixteen dot patterns is 256: every byte is exactly one drawing.
Rows in the first alphabet (format 1, 2026-09-22: sixteen pictograms, rotated, outline or filled, in square or circle frames) still read: the reader tries both formats on every photo and says which one it found. The reader's glyph switch still draws format 1, and its sheet is here.
Draw a row
- Frame first. Draw a square. Keep the frames about the same size along the row, with a gap of roughly a third of a frame between them.
- Icon in the middle, upright, about half the frame wide, touching nothing. Keep the box and the x small.
- Corner dots are filled blobs about a sixth of the frame across. A dot must touch neither the frame nor the icon, or it becomes part of that line.
- Keep points pointy. The house's roof, the crown's points and the triangle's corners are what separate them from the box.
- Underline the row with one stroke a little below the frames, extending past the first and last frame. The line tells the reader which way is up.
- Fat start dot: a solid dot at the start end of the underline, about a quarter of a frame across. The dot tells the reader where to begin; without it the row has two readings, forward and reversed.
- Six bytes is the recommended length for an event id or pubkey prefix; eight for something meant to stay unambiguous for years; four only where the relay is small.
Look it up
The reader sends every candidate reading in one request, as an ids prefix and as an authors prefix for kind 0 profiles:
["REQ", "gb", {"ids": ["e8ed3798c6ff"]}, {"authors": ["e8ed3798c6ff"], "kinds": [0]}]
Prefix matching in ids and authors was part of the first NIP-01 and was later removed from the text, so it needs a relay that still supports it. As of 2026-09 grain honours prefixes of any length in both fields; strfry and khatru currently do not (strfry answers CLOSED, khatru returns nothing). The reader defaults to wss://wheat.oslim.dev, a grain relay, and tests whichever relay you enter once, by fetching one event and asking for it again by an 8-character prefix, then tells you when a relay rejects or ignores partial queries.
Results show the author's profile and which candidate they matched, and the person confirms which one they meant. A prefix is a pointer, not a proof: the resolved full id or pubkey is the reference, never the drawn bytes.
Get it
- The specification, with the 256 test vectors, the icon strokes and the printable sheet.
- github.com/arkin0x/glyphbyte: the Python reference implementation, the JavaScript reader and this site.
- JavaScript:
glyphbyteon npm, dependency-free.npm install glyphbytegives you the reader, the row renderer, the relay lookup, and cairn publishing. - Python:
pip install git+https://github.com/arkin0x/glyphbytefor now (numpy, OpenCV, onnxruntime).glyphbyte decode photo.jpgprints the bytes;glyphbyte encode HEX --out row.pngrenders a row to copy from.
The whole reader runs in the browser: the detector, the network weights and the icon shapes are inline in one page, and nothing leaves the device except the relay queries you can see on the page, and what you publish. The same file is also a napplet (NIP-5D) that a nostr shell can load in a sandboxed iframe.