Skip to content

docs(nip-fi): state, don't argue — CONF −29%, rationale section, four pins (alt to #5946) - #6437

Open
tlongwell-block wants to merge 8 commits into
eva/nip-fi-comprehensivefrom
meli/nip-fi-ideal
Open

docs(nip-fi): state, don't argue — CONF −29%, rationale section, four pins (alt to #5946)#6437
tlongwell-block wants to merge 8 commits into
eva/nip-fi-comprehensivefrom
meli/nip-fi-ideal

Conversation

@tlongwell-block

@tlongwell-block tlongwell-block commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

What this is

An alternative shape for the NIP-FI document family, built on top of #5946 at its current head 772ba7a72 (so it carries R1–R12 and diffs cleanly against it). Thesis: the current text argues where a standard should state, and is silent where a reader needs one paragraph of why. This PR makes every section either a rule or labeled as not one, and fixes four correctness items found in review.

Base: 772ba7a72 (= eva/nip-fi-comprehensive). Eight FF commits, no rebase. Three files. Oracle census unchanged at 58 (30/6/4/11/7).

Review lap (Wren, Dawn, Eva) folded in as FF commits be72f0a5ddb35013c1 — see "Review findings applied" below.

Changes

NIP-FI-CONF — 4,325 → 3,088 words (−29%), no obligation lost

  • Denial fixtures 1,516 → ~740 words. Same 16-row table. Enumeration-agreement checks 4 → 3 (checks 1 and 2 quantified over the same set). Design history ("Two of these checks shipped red…", the Www-Authenticate canonicalization paragraph, "one layer down", "a slow way to discover…") removed from normative text.
  • Interoperability exit test 934 → ~620 words. Same fixture contents, same compared objects, same deferral rule, same negative control.
  • Release gate restructured as a list; identical conjuncts.
  • BCP 14 keyword census 37 → 36. Every MUST/MUST NOT/MAY/REQUIRED sentence in the old text was fuzzy-matched to the new text; the two that don't map are (a) "Their public responses MUST compare byte-identical to each other, not merely equal in prefix or status" — fully subsumed by the anonymity comparison one paragraph later, and (b) the standalone "The suite MUST include a negative control:" whose obligation is retained in the Negative control paragraph. Reviewers: please try to find a third.

NIP-FI core

  • New non-normative section "Relationship to other work" (~190 words): DPoP / mTLS cnf with the NIP-42/98 event as the proof; durable binding vs per-token claim; why 1:1 vs WebAuthn's N credentials; why two contract IDs plus dependency versions; RFC 6750 error codes; RFC 9421 / SigV4 family for the edge MAC. The "Thus two contract identities…" argument paragraph moved here from the normative snapshot section. Three source links added.
  • Pin: skew, maximum_assertion_age, maximum_status_age declared as configured finite values (missing → deny). Previously used at :177-178/:266/:278 and never declared; EDGE already did this for its own.
  • Pin: NIP-98 payload hashes one defined referent — "body bytes: the complete content after transfer decoding and before any content decoding" — the same stage EDGE MACs as payload_octets. Previously :315 said "bytes consumed by the application" and :320 said "body bytes received", which differ under Content-Encoding. This is a semantic shift, not a clarification (Eva's A3): under the old "consumed" reading a Content-Encoding flip in flight was caught by hash mismatch; under this pin the same octets hash equal, so the defense moves onto the server-resolved-body-semantics rule (core:305-308, EDGE:176-178) and the EDGE Metadata negative row (:367), which are now load-bearing for it. All four body-wording sites say "substitution of the protected octets", not "transformation", so content decoding after validation is permitted.
  • Pin: a denial decided on a WebSocket upgrade, before any NIP-42 proof exists, is the HTTP table row sent instead of 101; post-connect denials are the Nostr text. FI-TRACE-DENIAL-ORACLE requires each row "on every surface where its condition can be decided" (a private-state row needing the proof is not decidable at upgrade — Wren). CONF exit list aligned.

NIP-FI-EDGE

  • R12 exception class-scoped (Dawn's finding): "except an independent Nostr proof validated on its own signature, such as the NIP-98 event in Authorization or the NIP-42 event after connect" instead of naming the field. Members are illustrative ("such as") so 0x03/0x04 can conform once their contracts publish. Prohibition widened to "request or connection component" — Dawn's model shows this closes 6144 unprotected-connection derivations to 0. Requirement 3 gets the matching carve-out.
  • Authorization byte-identity gains an oracle — as a two-point witness, not a denial. EDGE:62-65 makes altering it nonconformant; no oracle named it. First attempt said "altered … denies"; Eva (A1) and Dawn's a1_oracle.py showed that's false as a universal — an edge substituting a fresh, valid NIP-98 event from the same actor admits, because Authorization is outside the MAC by R12's design. FI-TRACE-PROXY-CROSS-REQUEST now requires the bytes at final admission to equal the client-sent bytes, witnessed at both points, and names the mutant that must fail it (valid-proof substitution). The Request matrix row no longer claims a denial for it.

Review findings applied (all FF commits on dcd0b2ff2)

# Finder Finding Fix
W1 Wren Denial oracle demanded every private row on all three surfaces; undecidable pre-proof core:586 "on every surface where its condition can be decided"; CONF:235-237 aligned
W2 Wren EDGE:168 and :372 still forbade any post-snapshot "transformation" "substitution of the protected octets denies" at all four sites (core:315, :573; EDGE:168, :372)
W3/A2 Wren, Eva CONF dropped exit-evidence obligation (old :393) CONF:287 "Exit evidence includes the exchanged artifacts and each implementation's statement of independence."
W4/A6 Wren Two-device sentence prescribed custody outside the standard core:605 "additional devices do not create additional active bindings."
D1/A4 Dawn, Eva Em-dash members read exhaustive; 0x03 couldn't conform "such as"
D2 Dawn "a missing value denies" had no detecting oracle FI-TRACE-ASSERTION-VALIDATION gains "missing-configuration" negative
A1 Eva, Dawn Authorization oracle asserted a false denial universal two-point byte witness + valid-substitution mutant (above)
A5/W5 Eva, Wren Promotion guard retargeted by 4→3 collapse, now inert sentence deleted; check 2's disjointness already fails promotion until the allowlist entry is removed, check 1 validates the class. CONF MUST NOT 9→8, deliberate
D3 Dawn Replacement sentence said "only check 2's disjointness fails" — false in 2/4 promotion cases (check 1 fails too when classes disagree); "then" implied an ordering the independent conjuncts don't have both words dropped (624429bd8)

Not taken this lap (predate this PR): EDGE:180 maximum_provenance_age missing-config oracle; 0x04 Blossom row says "authorization event" not "Nostr", so the class exception can't reach it until its contract is written.

Not changed

LIFECYCLE, DELEG, MODEL: zero bytes. Oracle tables, HMAC vectors, negative matrix, denial table: zero bytes beyond the two cells named above.

Verification

  • git rev-parse HEAD == 624429bd8 in the shell that produced the census/keyword numbers below; section word counts above are from dcd0b2ff2 (CONF now 3,1k).
  • Oracle census: rg -c '^\| \FI-[A-Z0-9-]+` |'` per file → 30/6/4/11/7.
  • Distribution markers: 0 hits across all six docs and the commit message.
  • Keyword census and sentence mapping: script in the PR thread on request.

Open questions

  1. Two-device sentence — closed by Wren's wording.
  2. R12 widening — Dawn's model: net stricter than 772ba7a72.
  3. CONF floor — Eva measured Evidence rules at 317 words, every sentence an obligation. Floor reached.
  4. A3 body-referent shift — accepted by Wren and Eva at db35013c1: the defense is a listed oracle row, hence inside the mutation regime.

Verdicts

At 624429bd8: Wren 9/9/9/9 · Eva 9/9/9/9 · Meli 9/9/9/9, each re-pinned at this exact head. Dawn's three instruments (conf/a1/r12) rc=0 at this head. CI 14 pass / 0 fail / 9 skipped. Zero known falsehoods in the text.

Census partition note (Dawn): "58" is unique oracle IDs per document across FI-TRACE/CONF/LC/DELEG prefixes; partitioned per prefix across all docs including FI-INV the same corpus totals 75. Quote the partition with the number.

… four pins

CONF is rewritten so every section is either a rule or labeled as not
one: the Denial-fixtures and Interoperability-exit sections carried
their own design history and code review inline (1,516 and 934 words).
Every BCP 14 obligation in the previous text maps to a surviving
sentence; the only net loss is one MUST that restated the anonymity
comparison and one that restated check-green-before-mutants. The four
enumeration-agreement checks collapse to three because checks 1 and 2
quantified over the same set. Keyword census 37 -> 36.

Core gains a non-normative "Relationship to other work" section (DPoP
and mTLS cnf, WebAuthn multi-credential, RFC 6750 error codes, RFC
9421 and SigV4) so a reader with OAuth background sees the shape in one
pass; the contract-identity argument moves there from the normative
snapshot section. Three pins: skew, maximum_assertion_age, and
maximum_status_age are now declared as configured finite values; the
NIP-98 payload hashes one defined referent (post-transfer-decoding,
pre-content-decoding content, the same octets EDGE MACs); a denial
decided on a WebSocket upgrade is the HTTP table row sent instead of
101, and FI-TRACE-DENIAL-ORACLE now covers all three surfaces.

EDGE: the pre-MAC derivation exception is class-scoped to any
independent Nostr proof validated on its own signature rather than to
the Authorization field, so 0x01 routes are covered and the undefined
phrase "request component" no longer decides satisfiability; the
Authorization byte-identity mandate at :62-65 gains an oracle via
FI-TRACE-PROXY-CROSS-REQUEST and the Request negative-matrix row.

Oracle census unchanged at 58 (30/6/4/11/7). Family 15,279 -> 14,378
words.

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
@tlongwell-block
tlongwell-block requested a review from a team as a code owner August 20, 2026 22:09
@tlongwell-block
tlongwell-block changed the base branch from main to eva/nip-fi-comprehensive August 20, 2026 22:09
Meli added 7 commits August 20, 2026 18:11
Pinning the payload referent to pre-content-decoding content made the
adjacent "post-validation transformation denies" forbid content
decoding itself. The obligation is that the application consume the
validated bytes, so the sentence now denies substitution of the body
bytes after validation. Oracle census unchanged at 58.

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
…ution; restore exit independence evidence

Three wording repairs from review at 37356d8:

1. FI-TRACE-DENIAL-ORACLE required every private row on HTTP, upgrade, and
   after connect, but a private-state row needing the NIP-42 proof cannot be
   decided at upgrade. The cell now reads "on every surface where its
   condition can be decided"; the rule sentence no longer enumerates
   conditions, only the when: before any NIP-42 proof = HTTP table response
   instead of 101, after connect = Nostr text. CONF exit list aligned
   ("on each transport where the class can be decided").

2. EDGE Payload still said "Any transformation after the protected snapshot
   is forbidden", which read literally forbade content decoding that core
   now permits. Now: "Substitution of the protected octets after the
   snapshot denies" - same shape as core body binding.

3. The CONF rewrite dropped the exit-evidence obligation (old :393 "produced
   bytes and a statement of independence"). Restored as one sentence:
   "Exit evidence includes the exchanged artifacts and each implementation's
   statement of independence."

Oracle census unchanged 58 (30/6/4/11/7). CONF keyword census unchanged
from 37356d8 (MUST 35, MUST NOT 9, MAY 3).

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
…tive gets an oracle

Two one-word repairs from formal review at dcd0b2f:

1. EDGE derivation exception listed its two members after an em dash, which
   reads as exhaustive and leaves the reserved 0x03 (and 0x04) routes unable
   to conform once their contracts publish. Now "such as": the class is the
   rule, the members are examples.

2. Core :176-178 pins "a missing value denies" for skew/maximum_assertion_age/
   maximum_status_age, but FI-TRACE-ASSERTION-VALIDATION enumerated no
   negative that detects it. Added "missing-configuration" to the cell.

Oracle census unchanged 58 (30/6/4/11/7).

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
…decidable

be72f0a's message claimed the CONF exit list was aligned with the
decidable-surface scoping; the edit was reverted in the working tree before
the commit and the message was not corrected. This commit carries the edit.

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
…device sentence states only the invariant

Staged diff, three hunks:

- EDGE:372 negative matrix Body row: "any post-snapshot transform denies"
  -> "any post-snapshot substitution of the protected octets denies".
  Third site of the transform/substitution wording; :168 and core:315
  were fixed earlier, this row still forbade permitted content decoding.
- core:573 FI-TRACE-BODY-BINDING: "transformed" -> "substituted" in the
  negative list, so the oracle names the same act the text denies.
  `rg -i transform docs/nips/NIP-FI*.md` -> 0.
- core:605-606 Relationship to other work: replaced the custody/rotation
  sentence, which prescribed behavior outside the standard, with the fact
  the invariants establish: "additional devices do not create additional
  active bindings."

Oracle census unchanged 58 (30/6/4/11/7).

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
… a denial; drop the retargeted promotion guard

Staged diff, three hunks:

- EDGE FI-TRACE-PROXY-CROSS-REQUEST: "An Authorization field altered ...
  denies" was false as a universal - an edge substituting a fresh, valid
  NIP-98 event from the same actor admits, because Authorization is
  outside the MAC by design. The cell now requires the bytes at final
  admission to equal the client-sent bytes, witnessed at both points, and
  names the mutant that must fail it: valid-proof substitution.
- EDGE Request negative-matrix row: removed the same "mutating
  Authorization ... denies" clause. The matrix enumerates denials; this
  property is a witness, and it now lives in the oracle cell above.
- CONF Enumeration agreement: deleted "dependency_unreadable MUST NOT be
  promoted without also leaving the set check 1 quantifies over". The
  4->3 collapse retargeted that guard from old check 2 (symbolic set) to
  new check 1 (every named symbol, class-compared), where a promoted
  dependency_unreadable passes anyway. Check 2 equality+disjointness
  already fails promotion until the allowlist entry is removed. Net
  CONF MUST NOT 9 -> 8, deliberate.

Oracle census unchanged 58 (30/6/4/11/7).

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Staged diff, one hunk, CONF:143-145.

"only check 2's disjointness fails" was false in 2 of 4 promotion cases:
when the promoted symbol's class disagrees with the CONF row, check 1
fails together with check 2 (the checks are independent conjuncts, not
ordered). "then" implied an order the checks do not have. Now:

  check 2's disjointness fails until the allowlist entry is deleted,
  and check 1 validates the promoted symbol's class.

Descriptive prose, no keyword; CONF census unchanged (MUST 34, MUST NOT 8,
MAY 3). Oracle census unchanged 58.

Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
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