diff --git a/SECURITY.md b/SECURITY.md index 1e07e9b94..b45ca8893 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -55,16 +55,17 @@ The relay verifies the Schnorr signature and extracts the pubkey. ### Authorization and admission -Channel membership is Buzz's application-level access control mechanism. If a -principal (human or agent) is a member of a channel, application policy may -allow them to read and write it. If they are not a member, the relay rejects -their requests even if they are authenticated. +In the current merged runtime, channel membership is Buzz's only +application-level access-control mechanism. If a principal (human or agent) +is a member of a channel, application policy may allow them to read and write +it. If they are not a member, the relay rejects their requests even if they +are authenticated. Private channels are invisible to non-members: they do not appear in channel listings, and subscription filters for private channel events return nothing unless the subscriber is a member. -[NIP-FI](docs/nips/NIP-FI.md) defines an optional additional admission +[NIP-FI](docs/nips/NIP-FI.md) defines a future optional additional admission authority that combines provider-neutral issuer assertions with fresh Nostr key proof, current durable binding and lifecycle state, and final application admission. When enabled for a domain, one current NIP-FI authority covers every @@ -72,8 +73,9 @@ protected ingress atomically, and both NIP-FI and application authorization must allow each operation. NIP-FI does not replace NIP-42, NIP-98, Nostr signatures, channel membership, or resource policy. -This documentation revision does not include or activate a NIP-FI runtime -adapter. A later exact-head implementation and deployment must pass the +That NIP-FI behavior is not shipped in the current merged runtime. This +documentation revision does not include or activate a NIP-FI runtime adapter. +A later exact-head implementation and deployment must pass the [behavioral evidence matrix](docs/nips/NIP-FI-CONFORMANCE.md) before publishing discovery or enforcing the contract. Source scans, prose, and configuration presence do not establish conformance. See the [threat diff --git a/docs/CORPORATE_IDENTITY.md b/docs/CORPORATE_IDENTITY.md index a26388fac..38a7f5b0c 100644 --- a/docs/CORPORATE_IDENTITY.md +++ b/docs/CORPORATE_IDENTITY.md @@ -20,104 +20,153 @@ public wording alone is not activation or conformance evidence. ## Proposed runtime document The later implementation stack reserves `BUZZ_NIP_FI_V1_CONFIG_JSON` as its -sole identity-configuration input. The `V1` suffix versions this proposed -runtime document; it does not name or enable a legacy transport profile. The -implementation must provide the following modes and reject the -legacy provider-specific variables rather than treating them as aliases. +sole identity-configuration input. `V1` is the reserved input name, while the +document's `configuration_contract_revision` records breaking shape changes; +neither names or enables a legacy transport profile. The implementation must +provide the following modes and reject the legacy provider-specific variables +rather than treating them as aliases. ### Proposed operating modes - **Off:** `BUZZ_NIP_FI_V1_CONFIG_JSON` is unset. Protected composition is not installed and existing unprotected behavior remains available. - **DenyProtected:** the document is `{"deny_protected":true}`. Every - protected route is denied before its handler runs. `deny_protected` takes - precedence over every other field in the document. -- **Enforce:** the complete document below is provided. Missing, unknown, empty, or + protected route is denied before its handler runs. This mode accepts no + other field; an additional field fails startup. +- **Enforce:** `deny_protected` is absent or false and the complete document + below is provided. Missing, unknown, empty, duplicate, contradictory, or invalid fields fail startup. ```json { - "issuer": "https://issuer.example", - "audience": "buzz-relay", - "subject_claim": "sub", - "event_author_claim": "nostr_pubkey", - "clock_skew_seconds": 30, - "maximum_token_lifetime_seconds": 3600, - "jwks": { - "jwks_uri": "https://issuer.example/.well-known/jwks.json" - }, - "lease": { "maximum_seconds": 300 }, - "policy_revision": 1, - "audit": { - "max_events_per_domain": 1000000, - "max_bytes_per_domain": 4294967296, - "max_envelope_bytes": 65536 - }, - "client_status_admission": { - "max_presentations_per_domain": 1000000, - "max_presentations_per_actor": 10000, - "max_presentations_per_peer": 10000 - }, - "transport": { - "kind": "trusted_proxy_hmac_v2", - "active_secrets_base64url": ["replace-with-base64url-secret"], - "maximum_provenance_age_seconds": 60, - "future_skew_seconds": 5 - }, - "enrollment": { "kind": "canonical_admission" }, - "restore": { "kind": "operation_manifest" }, - "delegation": { "enabled": false } + "configuration_contract_revision": 2, + "deny_protected": false, + "domains": [ + { + "id": "community.example.test", + "authorization_domain_id": "00000000-0000-0000-0000-000000000001", + "authorities": ["community.example.test:443"], + "issuers": [ + { + "issuer": "https://issuer.example.test", + "audiences": ["community.example.test"], + "subject_claim": "sub", + "event_author_claim": "nostr_pubkey", + "clock_skew_seconds": 30, + "maximum_token_lifetime_seconds": 3600, + "jwks": { + "jwks_uri": "https://issuer.example.test/.well-known/jwks.json" + } + } + ], + "lease": { "maximum_seconds": 300 }, + "policy_revision": 1, + "audit": { + "max_events_per_domain": 1000000, + "max_bytes_per_domain": 4294967296, + "max_envelope_bytes": 65536 + }, + "denial_observation": { + "maximum_records": 100000, + "maximum_bytes": 268435456, + "maximum_record_bytes": 1024 + }, + "transport": { + "kind": "trusted_proxy_hmac_v2", + "active_secrets_base64url": ["REPLACE_WITH_BASE64URL_SECRET"], + "maximum_provenance_age_seconds": 60, + "future_skew_seconds": 5 + }, + "enrollment": { "kind": "attested-key" }, + "delegation": { "enabled": false } + } + ] } ``` +Revision `2` is an independently reviewed, breaking documentation-first +revision from the earlier flat draft. It does not claim compatibility with a +current parser. The later implementation must update its parser, validation, +fixtures, policy digests, and adapters before claiming this contract. + ### Proposed document rules and bounds -The future parser must reject unknown fields. In Enforce mode: +The future parser must reject unknown and duplicate fields. In Enforce mode: -- `issuer` and `audience` are required non-empty strings of at most 2048 - characters each. +- `configuration_contract_revision` is the unsigned integer `2` and + `domains` is non-empty. +- Domain `id`, `authorization_domain_id`, and every authority are non-empty + and unique across the document. Each domain's authority list is non-empty + and contains no duplicates. +- `authorization_domain_id` is the canonical lowercase UUID text form. Remove + its hyphens and decode the hexadecimal octets from left to right to obtain + the exact 16 opaque bytes used by HMAC-v2; any other spelling or length + fails startup. +- Each authority is the server-configured lowercase ASCII host with an + explicit decimal effective port and brackets around IPv6, matching the + HMAC-v2 canonical input. Non-canonical or ambiguous authority text fails + startup. +- Each domain has at least one issuer. Issuer identifiers are unique within a + domain. Each `issuer` and audience is a non-empty string of at most 2048 + characters, and each issuer's audience list is non-empty and unique. - The implementation's closed asymmetric algorithm set enters verifier-policy identity through deterministic policy construction. If a later document makes that set configurable, adding or removing an algorithm must change the identity and algorithm order must be normalized. -- `subject_claim` defaults to `sub`. It and the optional `event_author_claim` +- Each issuer's `subject_claim` defaults to `sub`. It and the optional `event_author_claim` are limited to 128 characters. -- `clock_skew_seconds` defaults to `0` and is at most 300. -- `maximum_token_lifetime_seconds` is required, positive, and at most 86400. -- `jwks` accepts exactly one HTTPS `jwks_uri` or `discovery_uri`; credentials, +- Each issuer's `clock_skew_seconds` defaults to `0` and is at most 300. +- Each issuer's `maximum_token_lifetime_seconds` is required, positive, and at most 86400. +- Each issuer's `jwks` accepts exactly one HTTPS `jwks_uri` or `discovery_uri`; credentials, fragments, redirects, and private-network targets are rejected. The source kind and normalized authenticated URI enter verifier-policy identity. -- `lease.maximum_seconds` is required, positive, and at most 3600. -- `policy_revision` is required and positive. -- `audit` sets the immutable authorization-evidence capacity +- Each domain's `lease.maximum_seconds` is required, positive, and at most + 3600. Detection bounds derive from this lease limit, immediate version + fencing, and bounded post-commit invalidation behavior. Presentation is + non-authoritative. +- Each domain's `policy_revision` is required and positive. +- Each domain's `audit` sets the immutable authorization-evidence capacity (`max_events_per_domain`, `max_bytes_per_domain`, `max_envelope_bytes`). - There is no online prune, export, reset, or acknowledgement workflow — size - the budget for the installation's lifetime with generous headroom, because - exhaustion denies further authorization-affecting operations instead of - dropping evidence. -- Denied operations never consume that non-reclaimable authorization-evidence - budget and never create authorization receipts. A later implementation must - attempt denial observations through a separate finite-capacity, - non-authoritative channel. Channel exhaustion drops or truncates observation, - emits aggregate saturation signals where possible, and cannot weaken, delay, - or reverse the denial. -- `client_status_admission` limits are positive; - `max_presentations_per_domain` cannot exceed `audit.max_events_per_domain`, - and the per-actor and per-peer limits cannot exceed the per-domain limit. -- `transport`, `enrollment`, and `restore` are required non-empty objects - consumed by matching runtime adapters. The example uses the stock - `trusted_proxy_hmac_v2` adapter. A later implementation may instead select - `client_attached` or one installed registered profile for a bound route and - domain. Selection occurs before listeners open, never from request input, - and failure never falls back to another profile. + Values are positive and finite, validation is overflow safe, and + `max_envelope_bytes` cannot exceed `max_bytes_per_domain`. There is no online + prune, export, reset, acknowledgement, or recovery workflow. Successful or + authorization-affecting operations consume this finite budget; denied + operations do not consume it and never create authorization receipts. + Legitimate exhaustion is an accepted, unrecoverable, domain-wide fail-closed + outage within the installation and domain lineage. Sizing is an irreversible + installation-lifetime decision, so operators monitor consumption and alert + with substantial headroom. See the threat model's [availability and resource + exhaustion](NIP_FI_THREAT_MODEL.md#availability-and-resource-exhaustion) + analysis. +- Each domain's `denial_observation` has positive finite `maximum_records`, + `maximum_bytes`, and `maximum_record_bytes`. Validation is overflow safe and + `maximum_record_bytes` cannot exceed `maximum_bytes`. This capacity is + independent from authorization audit. Exhaustion permits drop or bounded + truncation only and emits aggregate saturation signals where possible; it + cannot weaken, delay, retry, reverse, receipt, bind, replay, or otherwise + mutate a denial. +- `transport` and `enrollment` are required non-empty domain objects consumed + by matching runtime adapters. `enrollment.kind` is exactly `attested-key`, + `provisioned`, or `tofu`; `delegation` remains optional and disabled by + omission as described below. +- `transport.kind` is `client_attached`, `trusted_proxy_hmac_v2`, or a private + registered identifier matching `x---v` and accepted + only when its deployment-installed adapter and profile contract satisfy + NIP-FI. The example remains stock. Selection occurs before listeners open + for each bound route and domain, never from request input, and failure never + falls back to another profile. -The future implementation must validate the `jwks` refresh policy at construction: a fetched document +Issuer-level fields own issuer, audience, claim, time, and JWKS semantics. +Domain-level fields own transport, enrollment, lease, policy revision, audit +capacity, denial-observation capacity, and delegation. Stable verifier policy +identity covers every semantic input, authenticated key-source identity, and +the compiled verifier-contract fingerprint. + +The future implementation must validate each `jwks` refresh policy at construction: a fetched document cannot exceed 4 MiB, a snapshot cannot stay fresh longer than 24 hours, every refresh bound is finite and nonzero, and an accepted key set contains between 1 and 128 keys. Production deployments may use tighter bounds. Key refreshes are -single-flight and stale verification fails closed. Current-status -presentation renews within 120 seconds, so active connections observe -authoritative policy changes within that polling bound. +single-flight and stale verification fails closed. ### Delegation diff --git a/docs/NIP_FI_CONTRIBUTING.md b/docs/NIP_FI_CONTRIBUTING.md index 689bf40f6..5191236fa 100644 --- a/docs/NIP_FI_CONTRIBUTING.md +++ b/docs/NIP_FI_CONTRIBUTING.md @@ -127,6 +127,9 @@ Do not describe a feature as conforming because it compiles, parses configuratio - Every required trace passes at the claim tuple. - Proxy and verifier-parity evidence matches the claim's transport-contract revision and profile-contract digest. - Every `not-applicable` trace has executable absence evidence. +- Denial-oracle evidence uses a fixed iteration count, predeclared bounds and + statistical rule, a pinned isolated runner, and no automatic retry after a + threshold breach. - Artifacts and digests resolve. - Privacy canaries are absent from every public and operational sink. diff --git a/docs/NIP_FI_INTEGRATION.md b/docs/NIP_FI_INTEGRATION.md index 2aca84f64..dd1c5dcbd 100644 --- a/docs/NIP_FI_INTEGRATION.md +++ b/docs/NIP_FI_INTEGRATION.md @@ -156,7 +156,7 @@ Provision, retire, disable, revoke, rotate, recover, re-enable, and administrati Every new target key proves control. Replacement provenance reflects the evidence used for that key. A privileged transition without matching issuer key attestation records provisioned provenance; it never inherits TOFU or attested provenance from another key. -Rotation starts from an active binding and leaves no pending lineage. Recovery consumes exact pending lineage for an enabled identity. A disabled identity uses re-enablement. Retired pairs and revoked keys remain durable. +Rotation starts from an active binding and leaves no pending lineage. Recovery consumes exact pending lineage for an enabled identity. A disabled identity uses re-enablement, which requires an eligible target key with fresh proof and creates the binding while clearing disabled state. There is no clear-only transition: without a target, provisioned mode has nothing to match and TOFU could expose a first-use resurrection window. Retired pairs and revoked keys remain durable. See [runtime operations](NIP_FI_RUNTIME_OPERATIONS.md) for preconditions, postconditions, recovery, and rollback rules. @@ -186,16 +186,61 @@ Private denial observations contain no raw tokens or verbatim unverified claims The later implementation stack must deliver, at one exact head: -1. a route adapter manifest for every protected ingress; -2. one canonical assertion verifier and transport-neutral assertion corpus; -3. serialized lifecycle storage with selector-conflict fixtures; -4. read-only preparation and atomic final admission; -5. direct and delegated lease dependency revalidation; -6. deployed trusted-proxy negative evidence when that profile is supported; -7. an executable adapter mapping for all applicable `FI-TRACE-*` labels; and -8. a conformance report whose revisions and artifact digests match the deployment. +1. Recheck binding version, lifecycle version, invalidation state, every + authenticated policy-snapshot hard deadline, and current snapshot + generation before every protected WebSocket use. JWT evidence revalidates + the retained assertion when JWKS generation changes. +2. Install exactly one server-selected transport for each bound route and + domain, with no request negotiation or fallback. +3. Preserve the HMAC-v2 envelope, canonicalization, time bounds, nonce ledger, + replay behavior, secret rotation, and fixtures unchanged when that stock + profile is selected. +4. Install a registered adapter only after its closed profile contract, + normalized-result mapping, deployment evidence, and profile-contract digest + are reviewed. Permission in these documents does not mean the current + runtime implements such an adapter. +5. Bound every lease by all assertion, provenance, authenticated policy + snapshot, proof, binding, local-policy, and implementation deadlines. +6. Register every issued lease with the invalidation registry, meaning the + implementation's generic lease-cancellation owner, or an equivalent owner + that supplies immediate version fencing and bounded post-commit closure. +7. Preserve evidence for the exact request, actor, proof transport, admission, + adapter, deployment, policy, transport-contract revision, and + profile-contract digest. +8. Demonstrate the selected profile's spoof, replay, cross-request, + verifier-parity, and final-admission properties before claiming + conformance. -The implementation handoff also lists exact commands, required services, fault-injection controls, and cleanup steps. A source grep, documentation link, or claim that code paths are wired is not a substitute. +The same handoff also includes the complete supporting deliverables: + +- a route-adapter manifest for every protected ingress; +- one canonical normalized-result contract and shared authorization corpus; +- serialized lifecycle storage with selector-conflict fixtures; +- read-only preparation and atomic final admission; +- direct and delegated lease dependency revalidation; +- an executable adapter mapping for every applicable `FI-TRACE-*` label; and +- a conformance report whose revisions, profile contracts, and artifact + digests match the exact deployment. + +The configuration migration is explicit: add +`configuration_contract_revision`; replace the flat issuer shape with +`domains[]` and nested `domains[].issuers[]`; add `denial_observation`; remove +the earlier client-presentation-capacity object and restore map; and replace +the earlier nonstandard enrollment value with the `attested-key`, +`provisioned`, and `tofu` enum. Parser behavior, validation, fixtures, policy +digests, and adapter evidence move to that shape together. The implementation +must reject the obsolete shape rather than treat it as an alias. + +These are implementation obligations, not claims about present runtime +behavior. The implementation stack may remain HMAC-v2-only until a deployment +chooses and implements a registered profile. This documentation change does +not require deleting the HMAC envelope, secrets, nonce ledger, +canonicalization, replay machinery, or fixtures. The new configuration and +trace contracts do require a revised implementation plan and evidence. + +The implementation handoff also lists exact commands, required services, +fault-injection controls, and cleanup steps. A source grep, documentation link, +or claim that code paths are wired is not a substitute. ## Related guidance diff --git a/docs/NIP_FI_RUNTIME_OPERATIONS.md b/docs/NIP_FI_RUNTIME_OPERATIONS.md index 76cba9cf6..b61665df4 100644 --- a/docs/NIP_FI_RUNTIME_OPERATIONS.md +++ b/docs/NIP_FI_RUNTIME_OPERATIONS.md @@ -61,6 +61,13 @@ Use the lifecycle state, not the operator's desired outcome, to choose the trans - An identity with no active binding and no lineage uses **Provision** when policy allows it. - A binding blocked only by administrative expiry uses the separately authorized administrative-expiry transition. +Re-enable identity always requires an eligible target key and fresh proof and +creates the new binding in the same transition that clears disabled state. A +clear-only operation would create a resurrection window: provisioned mode +would have no target to match, while TOFU could let the next ordinary +admission capture first use. To re-enable now and provision later, leave the +identity disabled until the target and proof are available. + If selectors are contradictory, versions are unknown, or history and active state disagree, stop. Preserve the evidence, fail closed, and investigate before authorizing a forward repair. ## Recovery procedure diff --git a/docs/NIP_FI_THREAT_MODEL.md b/docs/NIP_FI_THREAT_MODEL.md index da1776cc7..488a77113 100644 --- a/docs/NIP_FI_THREAT_MODEL.md +++ b/docs/NIP_FI_THREAT_MODEL.md @@ -99,6 +99,14 @@ Changing enrollment mode does not repair or reclassify existing bindings. Operat Fail-closed behavior deliberately trades availability for authorization safety. Attackers may amplify issuer refresh, signature verification, replay lookup, authorization-audit writes, denial observations, or policy reads. Implementations bound assertion and header sizes, canonicalization work, clock skew, JWKS refresh, replay retention, concurrency, queues, and observability work. A full or unavailable required authorization-audit or replay store denies instead of silently dropping evidence. +The authorization-audit budget is a non-reclaimable installation-lifetime +capacity. Legitimate exhaustion is an accepted, unrecoverable, domain-wide +fail-closed outage within that installation and domain lineage. Sizing is an +irreversible installation-lifetime decision, so operators monitor consumption +and alert with substantial headroom. Successful or authorization-affecting +operations consume the finite budget; denied operations do not. The base +contract defines no prune, export, reset, acknowledgement, or recovery path. + Denial observations have different failure semantics because a denial is already safe. They use finite capacity separate from the non-reclaimable authorization-audit budget. Saturation or write failure drops or truncates the observation, emits aggregate health signals where possible, and leaves the denial and authoritative stores unchanged. Records minimize attacker control: no raw tokens or verbatim failed claims, only stable reason and correlation identifiers, time, transport class, and bounded or keyed-hashed source coordinates. Authorization, lockout, and rate-limit policy does not consume this best-effort channel. Rate limits cannot replace cryptographic verification, lifecycle selectors, or final-admission serialization. @@ -113,6 +121,7 @@ The protocol cannot eliminate: - compromise of the trusted edge, an active HMAC key, a registered adapter or its authenticated caller, the Buzz process, storage credentials, or privileged operator authority; - first-use theft in risk-labelled TOFU mode; - denial of service caused by required dependencies failing closed; +- unrecoverable domain-wide outage after legitimate authorization-audit budget exhaustion; - correlation visible to systems that legitimately process private identity state; or - an implementation defect that the exact behavioral matrix does not exercise. diff --git a/docs/examples/nip-fi-conformance-report.json.example b/docs/examples/nip-fi-conformance-report.json.example index 68278408f..fb599b197 100644 --- a/docs/examples/nip-fi-conformance-report.json.example +++ b/docs/examples/nip-fi-conformance-report.json.example @@ -1,6 +1,7 @@ { "schema": "nip-fi-conformance-report-v2", "document_role": "shape-example-not-conformance-claim", + "numeric_values_role": "illustrative-shape-values-not-production-defaults", "claim": { "implementation_revision": "REPLACE_WITH_40_HEX_COMMIT", "adapter_revision": "REPLACE_WITH_40_HEX_COMMIT", @@ -166,6 +167,20 @@ }, { "trace_id": "FI-TRACE-DENIAL-ORACLE", + "execution": { + "iteration_count": 1000, + "warm_up_iterations": 100, + "runner_artifact_sha256": "REPLACE_WITH_64_HEX_DIGEST", + "environment_sha256": "REPLACE_WITH_64_HEX_DIGEST", + "public_response_corpus_sha256": "REPLACE_WITH_64_HEX_DIGEST", + "sampling_method": "REPLACE_WITH_PREDECLARED_METHOD_REFERENCE", + "predeclared_bounds": "REPLACE_WITH_IMMUTABLE_BOUNDS_REFERENCE", + "statistical_rule": "REPLACE_WITH_IMMUTABLE_RULE_REFERENCE", + "noise_treatment": "REPLACE_WITH_PREDECLARED_NOISE_TREATMENT", + "acceptance_threshold": "REPLACE_WITH_PREDECLARED_THRESHOLD", + "raw_measurements_sha256": "REPLACE_WITH_64_HEX_DIGEST", + "automatic_retry_on_failure": false + }, "status": "not-run", "adapter_test_ids": [], "evidence": [] diff --git a/docs/nips/NIP-FI-CONFORMANCE.md b/docs/nips/NIP-FI-CONFORMANCE.md index 19a8fb3b3..9a027d40b 100644 --- a/docs/nips/NIP-FI-CONFORMANCE.md +++ b/docs/nips/NIP-FI-CONFORMANCE.md @@ -46,6 +46,14 @@ Stateful tests use an isolated database or namespace. They inspect committed sta When an adapter evaluates denial timing, it freezes the sampling method, production-equivalent environment, warm-up, sample count, statistic, noise treatment, and acceptance threshold before execution. The report retains those values and the raw privacy-safe measurements. An undefined or post-selected threshold cannot pass a trace. +`FI-TRACE-DENIAL-ORACLE` runs a fixed positive iteration count on a pinned, +isolated runner at the exact implementation and adapter head. Before the run, +the operator records the environment, public-response corpus, bounds, +statistical rule, noise treatment, and acceptance threshold. A threshold +breach fails the gate and MUST NOT trigger an automatic retry. The failure is +retained and investigated before a separately authorized new run produces new +evidence. + The following do not satisfy a trace: - searching source, documentation, schemas, or binaries for a token; @@ -78,7 +86,7 @@ The following do not satisfy a trace: | `FI-TRACE-TOFU-THEFT` | Stolen-assertion first use denies in attested and provisioned modes. Only explicitly configured risk-labelled TOFU may create the attacker's proven key. | Same synthetic theft fixture under all three modes, discovery/config witness, provenance result, and no-mutation denials. | `FI-INV-05`, `FI-INV-10` | | `FI-TRACE-DELEGATE-OWNER-ROTATED` | Rotation, retirement, disablement, key revocation, owner-binding version change, or relationship revision makes old delegation non-current. No authority transfers to a replacement key. | Delegated allow baseline; each dependency mutation between preparation and final admission and during lease reuse; exact owner and relationship versions; replacement-key non-inheritance; denial; and bounded closure time. | `FI-INV-10`, `FI-INV-12` | | `FI-TRACE-DELEGATION-EXPIRED` | Missing finite configuration, delegation-expiry equality, owner administrative-expiry equality, and use after either bound deny. | Controlled-clock just-before/equal/after cases for both bounds, configuration omission case, delegate proof, exact owner version, and lease deadline. | `FI-INV-11`, `FI-INV-12`, `FI-INV-14` | -| `FI-TRACE-DENIAL-ORACLE` | Unknown identity, conflicts, tombstones, enrollment posture, and private-policy denials are not distinguishable on the public wire. | Public response corpus normalized by transport, status/prefix comparison, predeclared timing method and threshold with raw measurements, and private-detail scan. | `FI-INV-13` | +| `FI-TRACE-DENIAL-ORACLE` | Unknown identity, conflicts, tombstones, enrollment posture, and private-policy denials are not distinguishable on the public wire. | Public response corpus normalized by transport; fixed iteration count; pinned isolated runner and recorded environment; exact implementation and adapter head; status/prefix comparison; predeclared bounds, timing method, statistical rule, noise treatment, and threshold with raw measurements; private-detail scan; and proof that a breach failed without automatic retry. | `FI-INV-13` | | `FI-TRACE-DEPENDENCY-FAIL-CLOSED` | An unreadable verifier or registered adapter, authenticated key or upstream-policy snapshot, binding, lifecycle, replay, policy, receipt, audit, or invalidation state never allows. | One injected outage per dependency at preparation, final admission, and lease reuse where applicable, including authenticated upstream-policy snapshot retrieval; results and state diffs. | `FI-INV-14` | | `FI-TRACE-MULTI-KEY-SESSION` | A lease for one authenticated key cannot authorize another key on the same connection. | One connection with two keys, per-key operations, lease lookup evidence, and wire results before and after invalidation. | `FI-INV-05`, `FI-INV-11` | | `FI-TRACE-CROSS-DOMAIN-COLLISION` | Equal subjects across issuers and equal pairs across domains remain distinct and inherit no authority. | Two issuers and two domains with controlled collisions, four state snapshots, and cross-use denials. | `FI-INV-01`, `FI-INV-04` | diff --git a/docs/nips/NIP-FI-MODEL.md b/docs/nips/NIP-FI-MODEL.md index 0bc8ae98b..130487a7d 100644 --- a/docs/nips/NIP-FI-MODEL.md +++ b/docs/nips/NIP-FI-MODEL.md @@ -416,6 +416,14 @@ SetAdministrativeExpiry(i, k, old_version, binding_not_after?): create the same pair and provenance at new_version with the supplied bound ``` +`ReenableIdentity` deliberately clears `X_D` only while creating an eligible +target binding under fresh proof. Clearing disabled state without a target +would create a resurrection window: provisioned mode would have no target to +match, while TOFU could let the next ordinary admission capture first use. An +operator that wants to re-enable now and provision later leaves the identity +disabled until the target and proof are available; there is no separate +clear-only transition. + Rotation does not globally revoke `k_old`; revocation does. A retired pair remains retired after rotation, recovery, or re-enablement. Ordinary authorization cannot cross disabled, revoked, retired, pending, or administratively expired state. # Lease model diff --git a/docs/nips/NIP-FI.md b/docs/nips/NIP-FI.md index 8c2749367..69eab975f 100644 --- a/docs/nips/NIP-FI.md +++ b/docs/nips/NIP-FI.md @@ -277,7 +277,7 @@ Every transition reads and rechecks the active relation and all applicable retir - **Revoke key**: records the key as revoked even if it is not active. If active, it removes the binding, retires the exact pair, and records pending lineage. Repeating the same authorized revocation is idempotent and cannot erase lineage. - **Rotate**: replaces one exact active old binding with an eligible new key, retires the old pair, and creates a fresh binding version. Rotation does not globally revoke the old key. - **Recover**: consumes one exact pending-replacement lineage, preserves the retired old pair, and creates a fresh binding version for an eligible new key. A disabled identity uses Re-enable identity instead of Recover. -- **Re-enable identity**: requires the disabled identity and either no prior lineage or one exact pending lineage. It creates an eligible binding, clears the disabled state, and consumes present lineage exactly once. +- **Re-enable identity**: requires the disabled identity, an eligible target key with fresh proof, and either no prior lineage or one exact pending lineage. It creates the new binding, clears the disabled state, and consumes present lineage exactly once. Clearing disabled state without a target would create a resurrection window: provisioned mode would have no target to match, while TOFU could let the next ordinary admission capture first use. An operator that wants to re-enable now and provision later instead leaves the identity disabled until the target and fresh proof are available. - **Set administrative expiry**: requires one exact active binding version and sets, replaces, or clears `binding_not_after` under separate privileged policy. It advances the binding version and cannot change the pair or provenance. Every new target key, including a provisioned key, requires fresh target-bound Nostr proof. When the domain requires issuer attestation for creation or replacement, the transition also requires a current assertion for the same identity with a key claim equal to the target key. Supplied stale, claimless, wrong-identity, or mismatched attestation is rejected; it cannot be treated as absent optional evidence.