Skip to content

S0.6 tinycloud-threshold: durable share store (candidate persistence, positive-proof retirement) - #539

Merged
samgbafa merged 8 commits into
mainfrom
skgbafa/tc-2221-share-store
Nov 10, 2026
Merged

samgbafa merged 8 commits into
mainfrom
skgbafa/tc-2221-share-store

Conversation

@samgbafa

@samgbafa samgbafa commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Linear: TC-2221 residual — node-side persistence of candidate shares and retirement of old shares. PR only: no merge, tag or release is requested, and no production crate depends on tinycloud-threshold (the same unwired shape #525 shipped in).

Clean-room statement. tinycloud-threshold is written from published papers and standards only: Shamir 1979; Feldman 1987; Pedersen 1991; Gennaro–Jarecki–Krawczyk–Rabin 1999/2007; Boneh–Franklin 2001/2003; Fujisaki–Okamoto 1999; Desmedt–Jajodia 1997; Wong–Wang–Wing 2002; Herzberg–Jarecki–Krawczyk–Yung 1995; CHURP (ePrint 2019/017); vetKeys (ePrint 2023/616); RFC 9380; RFC 9180; the IRTF pairing-friendly-curves draft; and TinyCloud's own design and plan. No source code, tests, issues or other material from TACo, nucypher, nucypher-core, ferveo, umbral or taco-web (GPL-3.0/AGPL-3.0) was opened, fetched, cloned, searched or used. Dependencies are permissively licensed and listed in the PR. Each PR keeps a provenance log of the sources consulted.

What changes

1. CandidateShare::to_bytes/from_bytes (src/cert.rs). The one crate-API addition the durable store needs: version:u8=1 || transcript_hash:32 || index:u16 || scalar:32 (67 bytes). Decode is canonical only — exact length, version 1, index 1..=16, scalar < r and non-zero. Encoding writes directly into the guarded output; the raw scalar and every read-back buffer travel inside Zeroizing. KeyShare::bind re-verifies the reloaded scalar against the certificate's public share before use. This is a secret-export surface.

2. store module (src/store.rs). One member's file-volume share store for one network: share-<cert-digest> (the 67-byte candidate), proofs/<cert-digest> (signed certificate plus, once activated, the owner activation statement and receipts), dealt/<ceremony-id> markers, a versioned state record, and an exclusive lock file. Semantics:

  • One opener per network: open holds an exclusive advisory lock for the store's lifetime; a second opener gets Busy. member (this node's DID) scopes every acceptance.
  • Verified-only chain: every activation flag in proofs/ — genesis included — is verified through verify_activation (AllHolders) before it can extend the pin or count toward a fork; a flag alone is never trusted. A recorded state pin must be a verified activated certificate (chain member or fork child); anything else is Corrupt.
  • Ceremony binding: accept_ceremony requires the ceremony's anchor to match this store's owner/network, this member to be among its recipients, and current to equal the pin. install_candidate requires the ceremony id to have been accepted, then reads the persisted file back and re-binds it (and checks the index) before the candidate can receive a receipt.
  • State commits before unlinks: every removal (R1, C1, C2, S1) is committed to state before the file unlink, so a crash is always recovered as "erasure owed", never as a missing promised share. A receipt-bearing candidate whose share is still needed is the only missing-file Corrupt; a candidate already retired by durable proof converges even when its share is gone.
  • Fork freeze is durable: a verified activated certificate naming an earlier chain member as predecessor is persisted as fork evidence first, then the store freezes — and a frozen store refuses export (current_share), install, accept, and further recordings, across restarts.
  • Write hygiene: temp files are uniquely named *.tmp-<pid>-<seq>, created with create_new + 0600, and zeroed + unlinked on any failure; open sweeps leftovers. Parent directories are fsynced after hierarchy creation. Erasure is zero-overwrite + fsync + unlink + dir-fsync (best-effort; journal/CoW residue is documented).
  • record_activation is idempotent per verified chain digest, so concurrent retirement converges.

3. Certificate accessors (src/cert.rs): CertificateBody::previous(), epoch(), rekey(). No behavior change.

4. SPEC/README. "Implemented surface" bumped to S0.6; S0.6 appendix records the layout, formats, and rules above.

Tests (test-first)

Nineteen store tests plus the codec refusal table, including: unverified activation flag never trusted; second opener refused while the lock is held; foreign-anchor, member-excluding, and stale-pin ceremonies rejected with the candidate untouched; forked activation persisted before freeze and freeze survives restart; receipted candidate converges when its share is missing but the retirement proof is durable; leftover temp files swept; restart between every transition; concurrent retirement.

Not in this PR

Volume mount and backup exclusion, DB/peer pin sources, boot freshness (F1–F4), audit rows, instance_id binding, dstack/compose hardening — all node-runtime wiring, out of scope until the wiring decision.

Verification run here

  • cargo test --locked -p tinycloud-threshold --release with PROPTEST_CASES=1024: 245/245 (see check results on this PR).
  • cargo clippy --locked -p tinycloud-threshold --all-targets -- -D warnings and cargo fmt --all -- --check: clean.
  • No dependency added or changed (Cargo.toml, Cargo.lock untouched).

Notes for the crypto review

  • to_bytes is the secret-export surface: canonical 67-byte encoding, encode writes directly into the Zeroizing output with a guarded raw-scalar intermediate; decode takes guarded fixed buffers; bind re-checks the scalar before any use.
  • No at-rest encryption: the gate-A decision was one 0600 file per generation on a backup-excluded volume; a per-epoch key was ruled to add no property.
  • Freshness against volume rollback needs the peer/DB pin sources — explicitly deferred with node wiring; within this store alone the pin is self-authenticating.
  • Erasure is best-effort: on journaling/CoW filesystems plaintext may survive in snapshots or journals (design v5 §5.9).

Provenance log

Sources consulted: tinycloud-threshold/SPEC.md (§3.11–3.12); Linear TC-2221, TC-2224 (gate A design v5, /var/tmp/tc2092-gates/TC-2224-design-v5.md), TC-2226; the crate's own source and tests; PR #525's body as house format; Bobbie's security review of the first revision (P1/P2 findings, all addressed in the second revision). No dependency was added or changed. No TACo, nucypher, nucypher-core, ferveo, umbral or taco-web material was opened, fetched, cloned, searched, recalled or requested from a model.

Details: TC-2221

…ony binding, state-before-unlink retirement; Details: TC-2221
…fork descendants, strict temp sweep, full parent fsync; Details: TC-2221
…use install and activation, state mutates after write; Details: TC-2221
…s, persist live forks, bound+compact retirement with predecessors; state v3; Details: TC-2221

- open: chain tip that is also a retired digest freezes (P3-1)
- erase_share_file: unlink names with nlink>1, refuse links/symlinks before zeroing (P3-2)
- live second activated genesis persists the proof and freezes (P3-3)
- state v3: retired carries predecessor; dead entries compact at the 65536 bound, over-bound refuses (P3-4)
- model: save/restore around open-path damage, recover-and-continue refusals, member-drop, pin-share and proof-loss faults, read-only dir, byte-frozen outcome check, op accounting
… evidence cannot thaw the store; model resync reconciles rollback timelines; Details: TC-2221
…s assign after durable commit, wide seeded-harness ceremony ids, compact-bounded recovery retirement; Details: TC-2221
@samgbafa
samgbafa marked this pull request as ready for review November 10, 2026 13:40
@samgbafa
samgbafa merged commit 7ddd53d into main Nov 10, 2026
27 checks passed
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