Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 5 additions & 3 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
# Macroscope, not by us; prettier rewrites its double-quoted YAML scalars
.macroscope/

# KAT manifest and vector fixtures - written only by the committed generator
# (cargo run -p cipherbox-core --example kat_gen); the CI freshness gate
# diffs them byte-for-byte against generator output
# KAT manifests and vector fixtures - written only by the committed generators
# (cargo run -p cipherbox-{core,engine} --example kat_gen); the CI freshness
# gates diff them byte-for-byte against generator output, so a prettier rewrite
# would fail the gate it cannot fix
crates/core/kat/
crates/engine/kat/

# Generated OpenAPI docs artifact - byte-stability is owned by the generator
# (CI openapi-freshness diffs a regeneration against the committed file)
Expand Down
9 changes: 5 additions & 4 deletions blueprint/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,11 @@ ownerPseudonymPk, [(tag, permission, pseudonymPk)]}`), owner blob, the optional
fail-closed at decode and
encode — `historyLinks` at 256, `grantBlobs` and the commitment's `entries`
both at 1024 (`too-many-structures`) — and two history links may not carry
equal sealed bytes (`duplicate-history-link`): the gate verifies one signature
per structure per committed pseudonym, so an unbounded collection on **either**
side of that product is a reader-CPU amplifier, and each epoch mints one link
under a fresh nonce, so a repeat is an authored anomaly. The two 1024 ceilings
equal sealed bytes (`duplicate-history-link`): the gate's stage-3 work is
`pseudonyms + structures` (engine.md "One section, one signer"), so an
unbounded collection on **either** side of that sum is a reader-CPU amplifier,
and each epoch mints one link under a fresh nonce, so a repeat is an authored
anomaly. The two 1024 ceilings
are one number: the ledger must match the committed set exactly and a re-seal
wraps one blob per ledger row, so a commitment past the ceiling could only mint
a section its own encoder refuses. `historyLinks` is ordered **oldest epoch
Expand Down
40 changes: 33 additions & 7 deletions blueprint/engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,18 +163,44 @@ the #33 pipeline with the #39 D3 seal-auth stage and the D4 floor law:
against the contact-code-anchored owner identity (#34 D6, #39 D1).
3. **Grant-section authentication** (scope roots) — every seed-bearing
structure (grant blobs, owner blob, the optional owner-write-blob, ascent
link, history links, write-body) verifies under a committed write-capable
pseudonym via core's pure per-structure checks; any failure rejects the
**whole record** as a trust violation (#39 D3). The owner-write-blob is
optional on the wire, but a **present** one with a missing or invalid
structure signature is a whole-record trust violation, never staleness (its
signature is recomputed at the authenticated envelope epoch like every other
structure, though its sealed AAD binds the write epoch).
link, history links, write-body) verifies under **one** committed
write-capable pseudonym via core's pure per-structure checks; any failure
rejects the **whole record** as a trust violation (#39 D3). The
owner-write-blob is optional on the wire, but a **present** one with a
missing or invalid structure signature is a whole-record trust violation,
never staleness (its signature is recomputed at the authenticated envelope
epoch like every other structure, though its sealed AAD binds the write
epoch).
4. **Sequence** — strictly newer than the durable per-name floor.
5. **Epoch** — epoch tag at or above the scope's durable epoch floor.
6. **Unseal** — success required; core's trust-violation error class carries
through fail-closed.

**One section, one signer** (stage 3). A section is a single rotator's work: it
re-seals and detached-signs every structure with its own writer pseudonym,
re-signing at the record's read epoch even the history links it carries forward
verbatim (`rotation/reseal.rs`). The gate therefore **pins** the pseudonym that
authenticated the section's first structure and requires every later structure
to verify under that key alone; a section signed by two committed pseudonyms is
unadoptable, not merely unusual.

It closes a **structure splice**: a structure lifted verbatim out of a different
record at the same scope and epoch, authored by a different committed writer,
recomputes an identical signed input — `scope`, `epoch`, `structTag`,
`recipientTag` and `H(ciphertext)` all match — so per-structure trial-verify
adopted it. It is also what bounds stage 3's work at `pseudonyms + structures`
rather than their product: without it an accepted contact commits 1024 write
pseudonyms of their own and spreads a section's signatures across them, buying
~1000x reader-CPU amplification for ~1284 signatures. The produce side runs the
same predicate release-active (`net/author.rs::check_scope_root`), so this build
never signs a section its own gate rejects.

The pinned signer may be **any** committed write-capable pseudonym, not the
owner's specifically: the commitment is epoch-free so that grantee-triggered
rotation needs no owner signature (`CONTEXT.md`). Per-structure signers would
need a per-structure signer index on the wire, since the gate cannot otherwise
avoid the product — a format change, not a relaxation of this rule.

A gate failure is never mere staleness: the engine pins last-known-good,
raises the withheld-update escalation where applicable, and never renders the
rejected record. Duplicate `id`s and duplicate `ipnsName`s within a scope
Expand Down
8 changes: 8 additions & 0 deletions blueprint/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ Adversarial cases are first-class: the harness can replay, transplant, and
re-sign records with any key it holds; every crypto-review finding (#35)
gets a pinned regression scenario.

The engine also ships **its own KAT vectors**, under core's regime but for the
formats and predicates core cannot reach: the content-DAG root, and the
adoption gate's stage-3 verdict over whole scope-root head blocks — including
the **one section, one signer** reject. They are written only by
`cargo run -p cipherbox-engine --example kat_gen`, and the **Engine Tests** gate
regenerates all of `crates/engine/kat` and diffs it before running the suites,
so a verdict change that is not a deliberate re-freeze fails there.

### The contract suite — the live API gate

The sdk-e2e descendant (#28 D6), and it inherits sdk-e2e's most valuable v1
Expand Down
11 changes: 6 additions & 5 deletions crates/core/src/seal/section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,12 @@ pub struct GrantSection {
pub unknown: PreservedFields,
}

/// The frozen bound on a section's history links. The gate verifies one
/// signature per structure per committed pseudonym, so an unbounded collection
/// lets one record dictate another reader's CPU budget. Producers prune to a far
/// smaller retained window (`rotation/reseal.rs`), leaving this a
/// malformed-input guard an honest rotator never approaches.
/// The frozen bound on a section's history links: the gate's stage-3 work is
/// `pseudonyms + structures` (blueprint/engine.md "One section, one signer"), so
/// an unbounded collection on either side lets one record dictate another
/// reader's CPU budget. Producers prune to a far smaller retained window
/// (`rotation/reseal.rs`), leaving this a malformed-input guard an honest
/// rotator never approaches.
pub const MAX_HISTORY_LINKS: usize = 256;

/// The frozen bound on a section's grant blobs — one per committed grantee.
Expand Down
Loading