Skip to content

docs(nips): comprehensive NIP-FI — core + claimable profiles (EDGE/LIFECYCLE/DELEG/CONF) - #5946

Open
tlongwell-block wants to merge 18 commits into
mainfrom
eva/nip-fi-comprehensive
Open

docs(nips): comprehensive NIP-FI — core + claimable profiles (EDGE/LIFECYCLE/DELEG/CONF)#5946
tlongwell-block wants to merge 18 commits into
mainfrom
eva/nip-fi-comprehensive

Conversation

@tlongwell-block

Copy link
Copy Markdown
Collaborator

What

Fresh, comprehensive NIP-FI against main: one small normative core plus four separately claimable profiles, replacing the single 944-line document structure of #3726 (which was based on #1485's branch, not main).

  • NIP-FI.md (core, ~350 normative lines target) — issuer-qualified identity (iss, sub), independent Nostr proof, client-attached assertion, partial bijection with durable tombstones, atomic final admission, bounded leases, private denials with a closed response vocabulary, retire/revoke/rotate, two contract identities (assertion_policy_id, transport_contract_id), declared freshness class (offline-jwt | current-status), server-declared body authorization relevance (NIP-98 payload-binding fix), BCP 14, "equivalent" defined, worked wire example. FI-INV-01..16 move here as normative text.
  • NIP-FI-EDGE.md — trusted-edge surface: registered adapters, trusted-proxy-hmac-v2 envelope + canonicalization, authorization_domain_id derivation (exact 16 RFC 9562 UUID bytes), proof_transport_code registry + extension procedure, body-acquisition bounds, normative test vectors. Header-trust-without-provenance is nonconformant.
  • NIP-FI-LIFECYCLE.md — recover / re-enable / provisioned / binding_not_after / pending-replacement lineage, one conformance trace per privileged transition.
  • NIP-FI-DELEG.md — delegated agents, semantics unchanged, own traces.
  • NIP-FI-CONF.md — claim tuple + evidence rules, 12-row denial mapping with closed public-response vocabulary, byte-identical-response traces, mutation adequacy (one mutant per core MUST), interop exit test.
  • NIP-FI-MODEL.md — demoted to a non-normative companion only after the invariants land in core.

Why

The prior draft rated 9 (soundness) / 6 (minimalness) / 7 (elegance) / 7 (correctness) in adversarial + comparative review. This restructure is the approved 9/9/9/9 blueprint: the two-invariant spine stays untouched; everything else becomes claimable profiles; five stacked versioning mechanisms collapse to two contract identities; every core MUST becomes falsifiable by a named trace.

Resolved product calls (owner-approved):

  1. Enrollment/denial posture is private — boolean enrollment discovery, TOFU extension claim not self-advertised, key_mismatch → evidence_rejected joins the denial anonymity set.
  2. Revocation honesty — only current-status deployments may advertise an unconditional residual-revocation bound; offline-jwt advertises unbounded/unknown, with "revocation stops new issuance" as a deployment prerequisite. Access tokens keep RFC 9068 at+jwt; nip-fi+jwt is reserved for a separately minted Buzz assertion.

Acceptance bar

  • Nothing in core is deletable without losing a stated core guarantee (disposition-table proof).
  • From the core document alone, a second implementer can produce a byte-exact valid request and a byte-exact denial per class — no reference implementation.
  • Every core MUST ships a one-line mutant caught by its named trace.
  • Both deployment profiles from the enterprise-auth RFC (trusted proxy = EDGE, client-held OIDC = core client-attached) pass the same lifecycle conformance suite.

Status

Draft while slice owners land their sections (core boundary/idiom, transport vectors, lifecycle traces, correctness kit). Converts to ready when all sections are in and the acceptance bar above is verified.

Supersedes #3726 as the spec vehicle; #1485 remains the design-history anchor.

@tlongwell-block
tlongwell-block force-pushed the eva/nip-fi-comprehensive branch 2 times, most recently from b43aa6f to 4f47aba Compare August 15, 2026 11:33
@tlongwell-block
tlongwell-block marked this pull request as ready for review August 15, 2026 15:47
@tlongwell-block
tlongwell-block requested a review from a team as a code owner August 15, 2026 15:47
@tlongwell-block
tlongwell-block force-pushed the eva/nip-fi-comprehensive branch 2 times, most recently from d7ec05c to 6babae7 Compare August 15, 2026 17:14

@themiguelamador themiguelamador left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a strong decomposition, but several normative contracts are not satisfiable or interoperable as written.

  • The byte-exact exit test leaves signed and transport bytes unpinned. Logical JWT claim sets do not determine protected-header/payload JSON octets, and JWS signs those exact encoded octets (RFC 7515). NIP-01 signatures likewise are not unique: BIP-340 accepts auxiliary randomness and explicitly permits alternative valid signing algorithms (BIP-340). The WebSocket challenge, upgrade key/headers, header order, and compact client-message encoding were also absent. Two conforming implementations could therefore produce different valid frames and fail the mandatory exit test.
  • The immutable claim tuple omits the inputs that define conformance. Without protocol-document revisions, suite revision, and fixture digest, evidence can be carried across a changed specification while the tuple remains unchanged—the exact stale-evidence outcome this section says it prevents.
  • EDGE allocates 0x03 and 0x04 without the contracts its own allocation rule requires. No Git smart-HTTP or Blossom proof specification defines the exact validation, binding, freshness, replay identity/window, or vectors, so those codes cannot yet have stable assigned semantics.
  • Denial interoperability is incomplete across profiles. EDGE never maps absent, partial, invalid, or unreadable edge evidence to the core response classes; the conformance table omits EDGE/delegation attribution; and row 2 claims one fixture per private condition while aggregating many distinct rejection conditions into one row.
  • Delegation has two temporal/type errors. A not_before boundary should be live at equality, while expiry is not; and a positive “delegated maximum” is a duration, not a deadline that can be placed directly in a minimum with absolute deadlines.
  • Several conformance rules contradict one another. Complete synthetic request bytes are mandatory evidence, but the artifact rule forbids retaining any raw assertion. An offline-jwt claim also has no lawful not-applicable disposition for current-status or capability-projection revocation oracles. Token-class positives were ambiguous about which selected policy they run under.

I corrected these issues in 83138b1c4: the exit fixture now supplies exact signed evidence, challenge and frame bytes; the claim tuple binds every governing revision; unallocated EDGE codes remain unallocated; denial mappings are closed; synthetic fixture evidence has a narrow exception; delegation boundaries and lease arithmetic are explicit; and optional-oracle applicability is complete.

Verification:

  • all three EDGE vectors reproduce their documented pre-MAC SHA-256, raw HMAC-SHA-256, and unpadded base64url MAC exactly
  • the six symbolic core DENY(...) values match the conformance enumeration
  • every local Markdown link in the NIP-FI document set resolves
  • git diff --check passes

Metadata follow-up: the PR description still promises a worked wire example that is not present, and its Status section says “Draft” while the GitHub PR is marked ready. Please either add that artifact/update the acceptance wording and status, or mark the PR draft again.

Eva and others added 17 commits August 17, 2026 15:44
…rofiles)

Pins the file layout for the approved 9/9/9/9 blueprint: a ~350-line normative
core plus separately claimable EDGE / LIFECYCLE / DELEG / CONF profiles and a
non-normative model companion. Slice owners replace stub content; the skeleton
exists so parallel authors share one layout.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Co-authored-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz>
Signed-off-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Defines what counts as evidence that an implementation has the behavior core
and the profiles require: the immutable claim unit, evidence and anti-evidence
rules, the complete per-private-condition denial fixture enumeration, mutation
adequacy, and the two-implementation interoperability exit test.

Three rules carry the weight. Denial fixtures are enumerated per private
condition rather than per public class, because a per-class suite compares a
class against itself and passes trivially. Mutation adequacy requires each
normative requirement to have a singly-applied, attribution-checked,
reachability-witnessed mutant killed by its named oracle, because naming an
oracle proves a requirement is claimed, not that the oracle can fail. The exit
test requires byte-exact request and per-class denial bytes produced from the
documents alone by two independent implementations.

FI-TRACE-TOFU-THEFT takes a configuration witness only: under the private
posture ruling no discovery output distinguishes enrollment mode, so a
discovery witness for that oracle cannot exist.

Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
Signed-off-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
The denial-fixture enumeration named four core denial symbols; core now
denies six. Correct the count and names, reattribute binding_required to
core, and widen row 9 to cover any unrecognized enrollment policy, which
core's terminal else also catches. Add enumeration-agreement checks so a
future core symbol cannot silently falsify the sentence again, including
two that guard the prose-only allowlist itself: promoting an allowlisted
condition to a real symbol satisfies the attribution check rather than
failing it, so only a minimality check makes that rot visible.

Replace the interoperability compared object with what core pins: status,
body, and the values of only the header fields core names. Comparing
ordered header names failed every conforming pair. State that the
anonymity comparison stays wider, so a private reason code in an unnamed
header is still a defect.

Parameterize the exit test by a shared fixture. Independence is a claim
about code, not inputs: two implementations given different issuers, keys
or clocks cannot produce equal bytes however correct both are.

Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
Signed-off-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
Two of the seven enumeration checks were red on the conforming document.
Check 7 compared a set of rows against a set of symbols, and two
core-attributed rows name no symbol, so it could not pass at any head.
Check 5's head clause admitted the one row its own qualifier excluded. A
check that is red on a conforming document detects nothing: it cannot be
observed to flip, so every mutant reads as caught. Both are corrected and
the requirement to run the checks green before scoring a mutant is now
written where the checks live.

Address the allowlist and the anonymity set by symbol instead of row
number. This table is required to grow, and a profile appending one row
renumbers the positional references: the allowlist would name the wrong
condition and a new denied condition would fall outside the byte-identity
requirement without any check firing.

State the three readings the compared object left to convention. An
independently written conforming implementation diverges on each by
language default: a canonicalizing HTTP library emits Www-Authenticate, a
server that sets no Content-Length frames the body as chunked, and a JSON
encoder spaces its separators. Header names match case-insensitively per
RFC 9110 Section 5.1, the compared body is the content per Section 6.4,
and the Nostr message is compact JSON per NIP-01's serialization rules.
Content-Length stays unpinned; framing is the sender's choice and pinning
it would widen the object for no privacy gain. An exit test whose result
depends on an unstated convention is the same defect as an oracle no pair
can satisfy, one layer down.

Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
Signed-off-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
Three defects from the independent review lap, each fixed by making the
text impossible to read two ways:

DELEG evidence boundaries used 'live' (undefined) and declared equality
at either temporal boundary expired, which is incoherent for a lower
bound and contradicted core's own inclusive-nbf/exclusive-exp idiom.
State both comparisons explicitly, matching core: now < mandatory_expiry
and not_before <= now + skew.

The delegated lease deadline mixed a configured duration into a minimum
of absolute instants. Anchor it: lease issue instant plus the configured
delegated maximum.

EDGE allocated proof-transport codes 0x03/0x04 to Git and Blossom
profiles that do not exist, violating the table's own allocation rule
(a published stable specification with validation, binding, freshness,
replay, and vectors). Unassign both; the unassigned range now starts at
0x03.

Core's oracle table compressed six pairs of oracle IDs with slash
shorthand that has no consistent expansion rule: three pairs read
replace-last, three read keep-prefix, so no reader can infer the
grammar. Split all six into one row per literal ID. The table now lists
exactly 30 rows, one complete ID each, making the oracle set mechanically
enumerable as the mutation-adequacy denominator CONF will reference.
Every prose citation resolves to a table row; no shorthand remains.

Review findings: Wren (DELEG boundaries, delegated maximum, EDGE codes,
shorthand ambiguity), Dawn (denominator enumerability measurement).

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
The PR description promises a worked wire example in core; none existed.
Add one compact non-normative section: a protected HTTP POST under
client-attached with NIP-98 proof and a relevant body, showing how the
two evidence fields, body binding, and the admission phases compose on
the wire. Byte-exact values remain the province of the NIP-FI-CONF exit
fixture; the example elides credentials so it cannot be mistaken for a
conformance vector.

Twenty-six lines, kept deliberately smaller than the alternative of
striking the promise would save in credibility.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
CONF's mutation adequacy said "each normative requirement in core," which
admits four readings with a 3x spread. Make the denominator the listed
oracle, selected structurally: rows whose first cell is one complete literal
oracle ID, in core and each claimed normative profile. Selecting by section
title would silently drop EDGE's five oracles, whose table lives under
"Discovery and conformance"; core's heading is "Core behavioral oracles" and
no two of the tables agree. Verified 30/5/11/7/4 = 57 rows, zero shorthand,
zero duplicates, zero orphans, at a8fab1a.

The exit test required byte-exact requests from two implementations. ES256
and BIP-340 draw fresh randomness per signature and nothing here pins JSON
member order, so no conforming pair could ever pass. Compare signing inputs
instead: the NIP-01 serialization for the proof, decoded header and claim
set as JSON values for the assertion. Shipping pre-minted bytes would have
stopped testing whether an implementer can mint from the document at all.

Add the governing document revision and exit fixture digest to the claim
tuple: evidence means different things under different text, and a claim
that does not name what it was judged against is unfalsifiable once the
specification moves. Exempt the synthetic exit fixture from the redaction
rule it otherwise contradicts, and give offline-jwt deployments a lawful
N/A for the two current-status oracles, which have no witness to test.

Subtraction: the enumeration-agreement subsystem hand-copied core's denial
symbols into CONF prose, then spent four checks and two rationale paragraphs
guarding the copy. Read the set from core instead and the copy's guards have
nothing to guard: seven checks collapse to three. Promotion detection is
kept by folding disjointness into check 3 rather than restoring a check --
found because a six-mutant matrix showed the reduced set letting a promoted
allowlist entry survive, falsifying a sentence I had just written. All six
mutants now killed, green on the conforming document. Also fold three
paragraphs restating one rule about unpinned header fields into one.

Net +24 lines on CONF. The row splits and the two new tuple elements are
not recoverable by deleting text that carries a guarantee, and compressing
identifiers to reach a target would trade a real property for a number.

Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
The denominator rewrite changed the body and left every summary of it
behind. Three sites still promised the deleted requirement-level contract:
the global-controls paragraph, the release gate, and -- worst -- the
FI-CONF-MUTATION row itself, which is one of the four CONF entries the
denominator counts. The gate contradicted the body it gates, so the
original ambiguity survived inside the oracle that claims to close it.
FI-CONF-INTEROP-EXIT likewise still demanded byte-identical requests the
section above had already stopped requiring. All four now say listed
oracle and compared object.

The exit test promised a request on the WebSocket upgrade and defined a
signing-input object only for NIP-98. Define the NIP-42 half alongside it,
compared against its own transport's NIP-01 serialization, and pin the
complete unsigned event fields for both transports in the fixture. The
assertion object compared a protected header the fixture never pinned;
pin the complete protected-header and claim-set JSON values.

Correct a false premise: signatures were said to differ because ES256 and
BIP-340 each draw fresh randomness. Deterministic ECDSA and fixed-aux
BIP-340 are conforming and need not differ. The conclusion is unchanged --
conforming implementations may disagree on octets, so an object requiring
equal octets fails conforming pairs -- but the reason now matches the
algorithms as specified rather than as commonly configured.

Give FI-TRACE-CAPABILITY-REVOCATION a lawful N/A. Core makes it
conditional on a configured revocation-bounded external projection, while
Applicability required every unlisted oracle, so a deployment with no such
projection had no disposition it could honestly claim. Same defect class
the offline-jwt fix closed for the current-status oracles, one oracle over.

All findings are Wren's, verified against the cited lines before editing.
A rewrite that changes a contract has to grep for every restatement of the
old one; the body moved and the summaries did not, which is exactly the
drift this document exists to make impossible.

Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
The non-normative worked example still promised that the NIP-FI-CONF exit
fixture pins complete byte-exact values. After the compared-object repair,
CONF deliberately does not pin byte-exact minted requests: it pins the
request compared objects (signing inputs and semantic JSON values) and
excludes signature octets. Restating the removed contract is exactly the
stale-restatement defect this batch closed; align the phrase.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
@tlongwell-block
tlongwell-block force-pushed the eva/nip-fi-comprehensive branch from d541a73 to 2511d1b Compare August 17, 2026 19:45
…on contradiction

Review findings from Mari and Quinn on #5946 at 2511d1b:

- Repoint the pinned NIP-42/NIP-98 source links from a nonexistent
  upstream commit (404) to the resolved commit
  6d2979b3f503a8539c983efbcdcf901bbcf9ed23 (both 200).
- NIP-FI-EDGE no longer classifies transport_contract_id as
  interoperability-critical, which contradicted core's exclusive
  deployment-local classification. The value stays deployment-local;
  the contract semantics EDGE contributes to it are what is normative.
- Core's rejection-and-privacy absolute ("never of private server
  state") now carries the authorization_unavailable carve-out its own
  table and NIP-FI-CONF already relied on.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@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.

2 participants