Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
13 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions tinycloud-threshold/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ Preconditions: an `AuthorizedCeremony` of kind `genesis` (§3.8 step 0 applies t
**`verify_certificate(anchor, signed, verifier)` → `VerifiedCertificate`:**
- owner DID = `anchor.expected_owner` and network = `anchor.network` (an otherwise valid self-signed certificate naming another owner is rejected);
- the owner signature verifies through the injected verifier (principle 2's allow-list lives behind it, in tinycloud-auth);
- validation of the **decoded** object, not just of constructors: 2 ≤ t ≤ n ≤ 16; genesis t ≥ 2; indices 1..=n, distinct, sorted; member DIDs distinct (no weighted membership); commitments length t; commitment[0] = mpk; commitment[t−1] ≠ identity; every evaluated public share valid and non-identity; field combinations: genesis ⇒ no previous digest, no previous mpk, epoch 0, re-key counter 0; reshare successor ⇒ previous digest, no previous mpk; re-key ⇒ previous digest and previous mpk.
- validation of the **decoded** object, not just of constructors: 2 ≤ t ≤ n ≤ 16; genesis t ≥ 2; indices 1..=n, distinct, sorted; member DIDs distinct (no weighted membership); commitments length t; commitment[0] = mpk; commitment[t−1] ≠ identity; every evaluated public share valid and non-identity; field combinations: genesis ⇒ no previous digest, no previous mpk, epoch 0, re-key counter 0, genesis t = t and a §3.0 genesis shape (n ≥ 2t−1, or exactly 3-of-4); reshare successor ⇒ previous digest, no previous mpk; re-key ⇒ previous digest and previous mpk, previous mpk ≠ mpk, re-key counter ≥ 1, genesis t = t and a §3.0 genesis shape; every successor (previous digest present) ⇒ epoch ≥ 1 and re-key counter ≤ epoch.

**`verify_successor(pinned: &VerifiedCertificate, next, verifier)`:** same owner and network (re-checked against the anchor); next.previous digest = pinned digest; epoch = pinned + 1 (checked); and either (reshare/refresh) the same mpk, re-key counter and genesis t, or (re-key) re-key counter + 1, previous mpk = pinned mpk, new mpk, genesis t = new t. Rollback and alternate lineages are rejected relative to the pinned certificate; which certificate is pinned as current comes from the trusted log head (gate B), not from the crate.

Expand Down Expand Up @@ -246,6 +246,10 @@ S0.4 adds the §3.8 redistribution for reshare and refresh: dealer and recipient

The dealer remains test-only. Durable storage/retirement and node-runtime integration are separate slices. The crate remains an unwired workspace member.

## Verifier conformance

Two verifiers of the same raw transcript or certificate conform when they agree on the **outcome**: accept or reject; QUAL (genesis) or D (redistribution); the final mpk, commitments and public shares; and which dealers are disqualified or excluded. These outcomes are normative. Typed error codes, reason codes and the order in which checks run (and so which code a multiply-defective input reports) are informative; an implementation may report a different code for an input it rejects, provided it rejects it.

## S0.2 canonical byte appendix

All integers below are unsigned big-endian. `lp(x)` is `len(x):u32 || x`; strings use exact UTF-8 without normalization. `option(x)` is `00` when absent or `01 || x` when present; other markers are rejected. Symbolic tag names refer to the literal domain strings in §3.3, and `TH` is defined in §3.2.
Expand Down Expand Up @@ -319,7 +323,7 @@ The canonical certificate body is:

The certificate digest is `TH(NETWORK_CERT, body)`. The owner signs `TH(CERT_STATEMENT, certificate_digest:32)`.

Certificates contain no HPKE keys. The commitment count equals `t`; the constant equals `mpk`; constant/top coefficients and evaluated public shares are non-identity. Genesis has absent predecessor fields and zero counters. A pinned successor has the exact previous digest and checked `epoch+1`. Reshare/refresh preserve `mpk`, `rekey` and `genesis_t`; re-key changes `mpk`, names the previous `mpk`, increments `rekey` and sets `genesis_t` to the new `t`.
Certificates contain no HPKE keys. The commitment count equals `t`; the constant equals `mpk`; constant/top coefficients and evaluated public shares are non-identity. Genesis has absent predecessor fields, zero counters, `genesis_t = t` and a §3.0 genesis shape (`n >= 2t-1`, or exactly `t = 3, n = 4`). Any certificate with a previous digest has `epoch >= 1` and `rekey <= epoch`. A pinned successor has the exact previous digest and checked `epoch+1`. Reshare/refresh preserve `mpk`, `rekey` and `genesis_t`; re-key changes `mpk` (previous `mpk` ≠ `mpk`), names the previous `mpk`, has `rekey >= 1`, increments `rekey`, sets `genesis_t` to the new `t` (`genesis_t = t`) and satisfies the same genesis shape rule.

Verification uses the caller's expected owner/network. The caller's authorization-log integration selects the current pinned certificate. Candidate binding checks transcript and member index, then `scalar*g2` against the certificate's evaluated public share.

Expand Down Expand Up @@ -405,7 +409,7 @@ R5 extraction = 01 || count:u16 || [(dealer:u16 || s:32 || s′:32)]
R6 values = 01 || count:u16 || [(dealer:u16 || s:32 || s′:32)]
```

`sealed_dealing` is the §3.10 envelope `01 || enc:32 || lp(ciphertext)`. Every count is at most 16 and is checked before allocation; index lists are strictly increasing member indices 1..=16; point coefficients are canonical G2 encodings and individual coefficients may be the identity (§3.2 role table); scalars are canonical. Trailing bytes and other versions are rejected. A payload that fails this decoding is *malformed*.
`sealed_dealing` is the §3.10 envelope `01 || enc:32 || lp(ciphertext)`; the ciphertext is at least 16 bytes (the AEAD tag), and a shorter one makes that R1 malformed. Every count is at most 16 and is checked before allocation; index lists are strictly increasing member indices 1..=16; point coefficients are canonical G2 encodings and individual coefficients may be the identity (§3.2 role table); scalars are canonical. Trailing bytes and other versions are rejected. A payload that fails this decoding is *malformed*.

### Classifying a round

Expand Down Expand Up @@ -456,7 +460,7 @@ This appendix records the byte layouts and deterministic rules that members and

### Authorization and refresh

Every entry point (`reshare_deal`, `reshare_start_receiver`, `reshare_verify_transcript`) first requires an `AuthorizedCeremony` of kind reshare or refresh (`InvalidCeremony` otherwise) whose current-certificate digest equals the supplied `VerifiedCertificate` (`ContextMismatch` otherwise). Refresh additionally requires t′ = t, n′ = n and a directory whose index→DID map equals the current certificate's members (`InvalidCeremony` otherwise). A dealer's `KeyShare` must carry the current certificate's key context (`ContextMismatch`). A recipient's ceremony key must derive the directory key at its index (`ContextMismatch`; unknown index `InvalidIndex`). These checks precede any randomness, opening or emitted message.
Every entry point (`reshare_deal`, `reshare_start_receiver`, `reshare_verify_transcript`) first requires an `AuthorizedCeremony` of kind reshare or refresh (`InvalidCeremony` otherwise) authorized against the pinned current certificate: its current-certificate digest must be present and equal the supplied `VerifiedCertificate` (`InvalidCeremony` otherwise, for a stale or absent digest). Refresh additionally requires t′ = t, n′ = n and a directory whose index→DID map equals the current certificate's members (`InvalidCeremony` otherwise). A dealer's `KeyShare` must carry the current certificate's key context (`ContextMismatch`). A recipient's ceremony key must derive the directory key at its index (`ContextMismatch`; unknown index `InvalidIndex`). These checks precede any randomness, opening or emitted message.

### Initial link and transcript

Expand Down
72 changes: 62 additions & 10 deletions tinycloud-threshold/vectors/check/README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,73 @@
# Primitive vector checker
# Independent vector and transcript checker

Independent TypeScript computation for `../v1/primitives.json`, using
`@noble/curves` 2.4.0 and Bun 1.4.4. All scalar inputs are public test values.
Independent TypeScript computation for `../v1/primitives.json`,
`../v1/dkg-genesis.json` and `../v1/reshare.json`, using `@noble/curves` 2.4.0
and Bun 1.4.4. All scalar inputs are public test values.

```sh
bun install --frozen-lockfile && bun run check
```

`check` runs the Bun tests, TypeScript checking, and the real vector CLI. The
existing `threshold` CI job invokes this command. To check another JSON file:
`check` runs the Bun tests, TypeScript checking, and the real vector CLI over
all three committed documents. The existing `threshold` CI job invokes this
command. To check one JSON file (primitive, DKG or redistribution; the kind is
read from its `spec_sections`):

```sh
bun run vectors /absolute/path/to/primitives.json
bun run vectors /absolute/path/to/document.json
```

The CLI reports each computed record and exits nonzero on a schema, byte,
equation, or expected-error disagreement. Its default path is relative to the
equation, or expected-error disagreement. Its default paths are relative to the
CLI module, not the calling directory.

## Transcript verification (S0.5)

`dkg.ts` (`dkgVerifyTranscript`) and `reshare.ts` (`reshareVerifyTranscript`)
recompute a whole genesis DKG or reshare/refresh outcome from the raw signed
records, using `ceremony.ts` for the S0.2 layer. Check order:

1. Byte-length sum of every closure body/signature and member envelope/signature
above 4194304 → `LimitExceeded`, before any decoding or signature.
2. Ceremony statement: decode (`InvalidEncoding`), owner/network against the
anchor, owner signature, kind and generation rules; a genesis shape other
than `n >= 2t−1` or exactly 3-of-4 → `UnsupportedFaultBudget`.
3. Redistribution only: the current certificate (decode, anchor, owner
signature, evaluated public shares), then ceremony/certificate binding
(`InvalidCeremony`), successor epoch/re-key and refresh shape.
4. Each present record k as closed round k from the start link H0: closure
ceremony/round/previous hash, owner closure signature, each envelope
(decode, ceremony/round, signer in its signed cohort, strict RFC 8032
signature), then the ordered-list commitment.
5. The S0.3/S0.4 classification rules (missing, conflicting, malformed), QUAL
or D, R4–R6 extraction and reconstruction, then the output identity checks.
An abort (`InsufficientQualified`, `InsufficientRecovery`) must be the last
record; missing or additional records are `InvalidRound`.

Per SPEC "Verifier conformance", outcomes (accept/reject, QUAL or D, mpk,
commitments, public shares, excluded dealers) are normative; error codes and
their precedence are informative. Conditions SPEC describes without naming an
error use the defaults in `DEFAULT_ERROR` (`ceremony.ts`), ruled by Naomi on
2026-10-30.

`testkit.ts` builds signed transcripts from the published fixture seeds in
`../v1/cert-core.json`, with payload encoders written separately from the
decoders; expected outputs are computed in the scalar domain. `dkg.test.ts` and
`reshare.test.ts` cover every disqualification/exclusion reason and its
precedence, extraction and reconstruction, identity rejections, caps, entry
checks and strict signatures. `probes.ts` and `transcript-corruption.test.ts`
flip one byte in each field class of every committed record and pin the
checker-order reason codes (informative); re-signed flips reach the rule-level
outcome.
`transcript-vectors.test.ts` perturbs expected outputs and runs the real CLI.

The checker does not open sealed dealings (it holds no recipient keys); a
sealed dealing is decoded by layout, and a ciphertext shorter than the 16-byte
AEAD tag makes that R1 malformed (SPEC S0.3 appendix).
`s02.test.ts` runs the `../v1/cert-core.json` certificate, ceremony and
closed-round records through the same S0.2 layer and covers the §3.9
decoded-certificate rules.

## Computations

- H1 and Pedersen h use the specified RFC 9380 custom DSTs.
Expand Down Expand Up @@ -71,9 +121,11 @@ Dependency implementation files were not opened or used as design inputs.
| Consulted source | Use |
| --- | --- |
| TinyCloud approved design/plan and `../../SPEC.md` §§3.1–3.6, S0.1 appendix | Equations, DSTs, encodings, error ordering and RequestDigest amendment |
| `../v1/primitives.json` and JSON-only schema/byte-layout handoff | Public inputs, expected bytes and current record format |
| `../../SPEC.md` §§3.0, 3.7–3.12, S0.2, S0.3 and S0.4 appendices; plan r2 §§3.0–3.11, §5, §6 | Ceremony, certificate, envelope, closure and transcript layouts; DKG and redistribution rules (TC-2223) |
| `../v1/primitives.json`, `../v1/dkg-genesis.json`, `../v1/reshare.json`, `../v1/cert-core.json` seeds | Public inputs, expected bytes and fixture signing seeds |
| [RFC 9380 §§5.2–5.3.1](https://www.rfc-editor.org/rfc/rfc9380#section-5.2) | Scalar-field reduction and SHA-256 XMD algorithm |
| [noble-curves 2.4.0 API documentation](https://github.com/paulmillr/noble-curves/blob/2.4.0/README.md) | Group operations, hash-to-curve, pairings and field API |
| [RFC 8032 §5.1](https://www.rfc-editor.org/rfc/rfc8032#section-5.1) | Ed25519 encodings for the strict-verification tests |
| [noble-curves 2.4.0 API documentation](https://github.com/paulmillr/noble-curves/blob/2.4.0/README.md) | Group operations, hash-to-curve, pairings, field API and Ed25519 `zip215: false` |
| npm registry version metadata | Version, integrity and license declarations |

## Dependencies
Expand All @@ -85,7 +137,7 @@ Development dependencies are TypeScript 5.9.3 (Apache-2.0), `@types/bun` 1.3.1,
`@types/react` 19.3.0 and `csstype` 3.2.3 (all MIT). License declarations were
read from the npm registry metadata for those exact versions.

Details: TC-2219
Details: TC-2219, TC-2223

## Certificate-core checker portion

Expand Down
Loading
Loading