Skip to content

feat: shares and secrets written on paper, shown once and typed back - #219

Merged
lomigmegard merged 1 commit into
mainfrom
feat/paper-shares
Sep 29, 2026
Merged

lomigmegard merged 1 commit into
mainfrom
feat/paper-shares

Conversation

@lomigmegard

Copy link
Copy Markdown
Contributor

Adds the human side of secret sharing: a share (or any byte value) leaves the machine once, on a paper sheet, and comes back by being typed from that sheet.

reveal shows a value in a window of its own. The window says the value is coming and who it is for, shows it on Enter with its rows numbered as on the sheet, and closes only on a yes to "every row written down and checked?". The transcript records the message and the acknowledgement, never the value. The console asks the same way and says to clear the terminal; a headless run acknowledges in a dry run and stops otherwise. length: makes the sheet exact, and a value of another size is refused before it is shown.

paper32 (rite_model::paper32) is the encoding: rows of 28 base-32 characters plus 4 of parity, Crockford's alphabet (no I, L, O, U, either case, O read as 0 and I/L as 1), each row a Reed-Solomon codeword over GF(32). A wrong character in a row is corrected and the row named; two unreadable ones (typed as ? or U) are recovered; a row that needs more is refused by name. Every row but the last is full, so a correct sheet decodes by hand by dropping the last four characters of each row. format: hex is available as a plain alternative.

Worksheets. rite script now also writes <name>.worksheets.html beside the script when a step shows a value: one page per reveal step, rows of boxes in groups of four with the parity cells shaded, the encoding in small print. It is a separate document because a sheet is printed once and kept with the value, while the script is copied for everyone. --worksheets <path> moves it.

enter_share types a share back. A new EnterRows prompt takes the value row by row; the TUI and the console check each row as it is typed, so a slip is corrected and named at once, and a short row where more are due is refused. Rows that are not a share, or a share of another size, are asked for again (Reporter::prompt_checked). The share arrives as a set of one that combine_shares reads directly. The transcript records the share's index and which rows were repaired, never the rows. enter_secret also takes format: paper32; enter_value refuses it, since a value on a sheet is a secret.

Examples and tests. split_and_combine now splits a 32-byte secret and hands share 3 over on paper. The new recover_from_paper recovers it from two fixed sheets and checks the result against a digest. It is left out of the dry-run test (no made-up value is a share); instead a test in rite-stdlib types the published sheets back and must recover secret.txt, which pins the alphabet, bit order, parity, row size and share header to fixed text. ReporterHarness now fails a er instead of blocking forever.

reveal shows a share or any byte artifact for a person to write down. A
window over the log says the value is coming and that it is not written
to the transcript, shows it on Enter with its rows numbered as on the
sheet, and closes only on a yes to "every row written down and
checked?". The prompt is recorded without the value and the fact says
only that it was shown. The console asks the same way and says to clear
the terminal; a headless run acknowledges in a dry run and stops
otherwise.

The encoding is paper32, in rite_model::paper32: rows of 28 base-32
characters and 4 of parity, the alphabet without I, L, O and U, each row
a Reed-Solomon codeword over GF(32) evaluated at all 32 field elements.
One wrong character in a row is corrected and the row named, two
unreadable ones (typed as ? or U) are recovered, and a row that needs
more is refused by name. A correct sheet decodes by dropping the last
four characters of each row. A share is its wire bytes in these rows, 64
characters for a 32-byte secret. format: hex shows two characters per
byte instead.

rite script writes the sheets beside the script, in
<name>.worksheets.html or where --worksheets says, one page per reveal
step: the ceremony in the header, the step's message and note, rows of
boxes in cells of four with the parity cells shaded, the encoding in
small print. length: makes the rows exact, and the step refuses a value
of another size before showing it.

enter_share types a share back from its sheet. A new prompt, EnterRows,
takes a value row by row; the TUI and the console check each row as it
is typed, so a slip is corrected and its row named at once. Every row
but the last is full, so a short row where more are due is refused and
a short row ends an entry of no set length. Rows that
are not a share, or a share of another size than length: says, are
asked for again through the reporter's new prompt_checked. The share is
held as a set of one, which combine_shares names without a property.
The transcript records the share's index and the rows repaired, never
the rows. enter_secret takes format: paper32 through the same prompt,
which carries the step's rule so the runtime checks it and a rehearsal
can build a stand-in; enter_value refuses paper32.

split_secret no longer logs how its shares are named. The
split_and_combine example splits a 32-byte secret and hands share 3 over
on paper; recover_from_paper recovers a secret from two fixed sheets
typed back and checks it against a digest. Examples with an enter_share
step are left out of the dry-run test, since no made-up value is a
share; instead a test types the published sheets back through
enter_share and combine_shares and must get secret.txt, which pins the
alphabet, bit order, parity, row size and share header to fixed text.
ReporterHarness hands a reporter only the answers queued when it was
built, so a prompt left without one fails instead of waiting forever. docs/secret-sharing.md and docs/typed-entry.md cover the actions,
the paper format and the sheet, and the transcript schema covers the
two new prompts.
@lomigmegard lomigmegard self-assigned this Sep 27, 2026
@lomigmegard
lomigmegard merged commit a27fad3 into main Sep 29, 2026
11 checks passed
@lomigmegard
lomigmegard deleted the feat/paper-shares branch September 29, 2026 19:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant