Files
2026-07-20 20:17:22 -04:00

26 KiB
Raw Permalink Blame History

GPT-5.6 Audit Mitigation and Redesign Plan

Companion to: audit.md
Purpose: turn the current research prototype into a defensible post-quantum key pre-commitment/link system and define the additional work required for a complete Nostr migration.

1. Target outcome

The immediate target should not be marketed as “Nostr is now quantum-resistant.” The achievable first production milestone is:

A client can prove that, before a verified Bitcoin block, an existing Nostr identity authorized and demonstrated possession of a canonical set of PQ keys; all clients deterministically resolve competing claims to the same earliest valid authorization; future PQ protocols can use that key set as their migration root.

A complete migration is a later milestone requiring standardized PQ-signed events, PQ encryption, identity mapping, revocation, rotation, recovery, and client adoption.

2. Non-negotiable security invariants

Implement these before addressing UI polish. Every design and test should trace back to at least one invariant.

Invariant Required property
I-01 Canonical authorization All authenticators sign one versioned, domain-separated deterministic byte representation—not free-form prose or partially overlapping objects.
I-02 Strict policy Missing, duplicated, malformed, downgraded, or unknown evidence cannot accidentally count as successful evidence.
I-03 Identity binding Query author, wrapper author, embedded author, and old-identity field are equal.
I-04 Immutable history An attacker cannot hide the initial candidate by publishing a newer replaceable event.
I-05 Earliest-anchor resolution Every conforming client chooses the same earliest fully valid Bitcoin anchor under defined tie-break rules.
I-06 PQ possession Each PQ signature key proves possession; KEM ownership is either proven by a specified mechanism or described only as authorization.
I-07 Post-break authority After migration, secp-only claims cannot replace or revoke the canonical PQ root.
I-08 Availability The canonical authorization and proof can be exported, mirrored, and verified without the original website or one relay.
I-09 Trust transparency “Bitcoin verified,” “API checked,” and “structurally present” are separate states.
I-10 Secure key boundary Production seed derivation and PQ signing occur in a dedicated signer/hardware boundary where feasible.

This redesign should precede patching the existing two-event format, because several critical flaws arise from format semantics.

3.1 Define a canonical authorization object

Add a protocol module—for example, a new www/js/nip-qr-schema.mjs file—rather than embedding schema logic in pq-crypto.mjs.

Use deterministic CBOR or an explicitly specified canonical JSON encoding. A conceptual structure is:

{
  protocol: "nostr-pq-link",
  version: 1,
  network: "nostr-main",
  old_pubkey: <32 bytes>,
  successor_pubkey: <optional 32-byte seed-derived Schnorr key>,
  created_at: <integer>,
  nonce: <32 random bytes>,
  keys: [
    { algorithm: "ml-dsa-44", public_key: <bytes>, role: "sign" },
    { algorithm: "ml-dsa-65", public_key: <bytes>, role: "sign" },
    { algorithm: "slh-dsa-sha2-128s", public_key: <bytes>, role: "sign" },
    { algorithm: "falcon-512", public_key: <bytes>, role: "sign" },
    { algorithm: "ml-kem-768", public_key: <bytes>, role: "encrypt" }
  ],
  policy: {
    required_signature_algorithms: [...],
    rotation_threshold: <defined rule>
  }
}

Normative signing input:

UTF8("NOSTR-PQ-LINK\0") || protocol_version || canonical_encode(object)

Requirements:

  • Sort key entries by registered algorithm ID.
  • Reject duplicate algorithms.
  • Pin exact public-key and signature lengths by version.
  • PQ signatures sign the complete object, including all sibling keys and policy.
  • The old secp identity signs a Nostr event committing to the exact canonical object digest.
  • If common seed origin/recoverability is claimed, a seed-derived successor key signs the same object.
  • Human-readable content is generated from the object and is explicitly non-normative.

3.2 Separate immutable authorization from mutable proof transport

Do not make the canonical authorization only a replaceable kind 11112 event.

Recommended structure:

  1. Immutable authorization event: a regular, non-replaceable event containing or committing to the canonical authorization and all possession proofs.
  2. OTS proof-carrier event: may be parameterized/replaceable by authorization ID, but every version must reference the same immutable authorization event ID and target digest.
  3. Archive package: downloadable canonical object, signed authorization event, OTS proof, and verification metadata.

If kind allocation is not standardized, use an experimental range and clearly version it. Do not overload kind 1 as the sole normative migration record merely for feed visibility; a separate optional kind 1 announcement can reference the immutable authorization.

3.3 Define canonical conflict resolution

Specify the algorithm before coding it:

  1. Gather all immutable authorization candidates for old_pubkey from all configured relays, local archives, and user-provided packages.
  2. Validate schema, old secp signature, identity equality, PQ proofs, algorithm policy, and OTS target binding.
  3. Require a Bitcoin attestation with configured confirmation depth.
  4. Sort valid candidates by:
    • lowest verified Bitcoin block height,
    • then lowest authorization digest lexicographically for deterministic same-block ties.
  5. The first candidate becomes the genesis PQ root.
  6. Later rotation is valid only if authorized under the canonical root’s PQ rotation policy.
  7. A later secp-only authorization never supersedes the genesis root after the defined transition policy activates.

Document the limitation that a previously unknown earlier proof can change resolution. Mitigate this with archive discovery, checkpointing, and proof gossip. Consider defining a stabilization period before treating a candidate as final.

3.4 Define PQ rotation and future event authentication

A linked key with no use protocol is only a commitment. Create separate specifications for:

  • PQ authorization envelopes for future Nostr events;
  • replay/domain separation and binding to the NIP-01 event ID;
  • key rotation and revocation thresholds;
  • algorithm deprecation/emergency removal;
  • recovery after seed loss;
  • PQ encryption and KEM key rotation;
  • how clients map the old npub to a successor key without splitting the social graph.

A practical compatibility design is a normal NIP-01 event plus a standardized tag or companion event containing a PQ signature over a domain-separated NIP-01 event ID. Relays can remain unaware initially; PQ-aware clients enforce the companion proof. This needs independent protocol review before adoption.

4. Staged remediation plan

Phase 0 — Freeze unsafe production claims

Goal: prevent real-user harm while redesign proceeds.

  1. Add a prominent research-prototype warning to index.html and verify.html.
  2. Replace “Make your Nostr identity quantum-resistant” with “Create an experimental pre-quantum PQ key commitment.”
  3. Disable or gate real relay publication behind an explicit experimental acknowledgement.
  4. Warn users never to enter a valuable existing mnemonic into the web page.
  5. Restore the 12-word PQ-strength warning and default new generation to 24 words.
  6. Change every success state to distinguish:
    • event signatures valid,
    • PQ policy sufficient,
    • OTS pending,
    • OTS API-checked,
    • canonical earliest candidate selected.
  7. Mark README.md, nip_proposal.md, and research documents with implementation-status banners.

Release gate: the public deployment cannot imply complete quantum resistance.

Phase 1 — Replace the verifier with strict schema/policy validation

Findings addressed: G56-01, G56-03, G56-07, G56-17, G56-19, part of G56-20.

Implementation instructions

  1. Create a schema validator that checks before cryptography:
    • object type and exact required fields,
    • integer ranges,
    • exact kind values,
    • arrays and tag element types,
    • exact lowercase hex lengths,
    • strict canonical base64 with decoded byte limits,
    • maximum content/event/proof sizes,
    • exact tag cardinality,
    • no duplicate recognized tags/algorithms.
  2. Refactor verifyNostrEvent() to require id, sig, pubkey, created_at, kind, tags, and content by default. Return { valid, errors }; do not throw on untrusted malformed input.
  3. Replace the fail-open branch at verifyNIPQRContent():
    • only ml-kem-768 may omit a signature;
    • all signature algorithms require signatures;
    • unknown algorithms are ignored evidence, never successful evidence;
    • mandatory algorithms are evaluated after parsing all tags.
  4. Pass the full outer event and expected query author into the verifier. Require:
    • outer.kind is the expected wrapper kind,
    • outer.pubkey === embedded.pubkey,
    • outer.pubkey === expectedAuthor when supplied,
    • e and SHA-256 tags are unique and exact.
  5. Return a structured result:
{
  structurallyValid,
  secpValid,
  pqProofsValid,
  policySufficient,
  otsTargetBound,
  otsBitcoinStatus,
  canonicalSelectionStatus,
  validForMigration,
  errors,
  warnings
}
  1. Make verify.html display these separate states and never infer overall migration validity from results.every(...).
  2. Validate the claimed block height against the verified OTS height or remove it from the normative authorization.

Required tests

Add tests for:

  • zero algorithm tags,
  • each missing mandatory algorithm,
  • missing signature on every signature algorithm,
  • arbitrary unknown no-signature algorithm,
  • duplicate algorithm tags,
  • duplicate e, SHA-256, and OTS tags in both orders,
  • wrong decoded key/signature lengths,
  • malformed base64/hex,
  • null/wrong-shaped objects,
  • missing event ID,
  • wrong wrapper kind,
  • wrapper/embedded/query-author mismatch,
  • oversized event and proof,
  • valid optional unknown algorithm ignored without policy credit,
  • exact valid fixture passing all layers.

Acceptance criterion: malformed input never throws, and no event without the full configured PQ policy can be labeled migration-valid.

Phase 2 — Implement immutable candidate discovery and earliest-anchor selection

Findings addressed: G56-02, G56-12, G56-16.

Implementation instructions

  1. Replace limit: 1 queries at verify.html and index.html with candidate collection until EOSE from each relay.
  2. Do not select by created_at.
  3. Verify every candidate independently and retain relay provenance.
  4. Introduce a pure function such as:
selectCanonicalAuthorization(candidates, policy)

It must sort only fully valid confirmed candidates by verified block height and deterministic digest tie-break. 5. Store immutable complete artifacts locally:

  • old identity,
  • authorization event JSON/bytes,
  • canonical authorization digest,
  • OTS target digest,
  • pending/confirmed proof,
  • proof-carrier event(s),
  • relay receipts.
  1. Replace the overloaded eventId metadata in savePendingOts() with explicit authorizationEventId, wrapperEventId, and targetDigest.
  2. On resume, require every persisted/fetched binding to match before signing or publishing.
  3. Add export/import of a self-contained archive package.

Required tests

  • later secp-valid forged candidate loses to earlier valid anchor,
  • earlier invalid PQ candidate is rejected,
  • earlier proof with wrong target is rejected,
  • same-block tie resolves identically regardless of relay order,
  • malicious relay sends events outside subscription/filter,
  • one relay hides the earlier candidate but another supplies it,
  • resume rejects a different event ID/digest/owner,
  • upgraded proof preserves immutable authorization target.

Acceptance criterion: candidate input ordering and relay response ordering cannot change the selected canonical root.

Phase 3 — Validate signer output and bind seed continuity

Findings addressed: G56-05, G56-06, G56-09, G56-10.

Implementation instructions

  1. Add a single signer-validation helper used after every window.nostr.signEvent() call:
    • verify returned object has exact fields,
    • verify no approved template field changed,
    • verify returned pubkey equals authenticated identity,
    • recompute ID,
    • verify Schnorr signature,
    • reject extra/missing semantic fields.
  2. Never reconstruct an event from a mixture of signer-returned and local fields. Validate and retain one exact signed object.
  3. Split onboarding into explicit modes:
    • Existing mnemonic identity: entered seed must derive the connected NIP-06 pubkey; otherwise stop.
    • Raw-nsec identity: generate a new 24-word seed and require a successor proof over the canonical authorization.
  4. Run a recovery self-test before publication: discard derived objects, rederive from mnemonic, and compare all public keys/deterministic identifiers.
  5. Prefer moving derivation/signing into a dedicated signer API. Define capabilities such as:
    • pq_get_public_keys(protocolVersion, account),
    • pq_sign_authorization(protocolVersion, digest, algorithms),
    • pq_rotate_keys(...).
  6. Until signer support exists:
    • scope secrets locally rather than globals,
    • do not derive the unused secp key except for continuity/proof,
    • overwrite secret typed arrays where possible,
    • clear seed DOM/input immediately after confirmation,
    • avoid clipboard as the primary backup path,
    • warn about extensions/devtools/clipboard managers.

Required tests

  • signer changes content/tag/timestamp/pubkey,
  • signer returns wrong ID,
  • signer returns invalid signature,
  • connected pubkey changes between requests,
  • existing mnemonic derives wrong identity,
  • raw-nsec successor proof missing/invalid,
  • recovery rederivation mismatch,
  • cancellation at each signer prompt leaves no publishable partial state.

Acceptance criterion: nothing is persisted or published unless the exact signer-returned object is locally valid and bound to the intended identity/template.

Phase 4 — Make OTS and Bitcoin verification trust-explicit and robust

Findings addressed: G56-04, G56-13, G56-17, part of G56-08.

Parser hardening

In OtsReader and parseOtsFile():

  1. Set maximum detached-proof size, for example 1 MiB unless evidence supports another bound.
  2. Track and cap:
    • recursion depth,
    • operation count,
    • branches,
    • cumulative append/prepend bytes,
    • cumulative hash input bytes,
    • attestations.
  3. Replace 32-bit bitwise varuint parsing at readVaruint() with safe arithmetic/BigInt plus maximum value and canonical-encoding checks.
  4. Require complete stream consumption.
  5. Reject unsupported operation/hash types explicitly.
  6. Fuzz with random, truncated, deeply branched, and overlong inputs.

Bitcoin trust modes

Expose one of these explicit modes:

  • local-node-verified: query a user-controlled Bitcoin node and validate block data;
  • header-chain-verified: validate raw headers, PoW, linkage, difficulty, chainwork, and checkpoints;
  • multi-explorer-checked: compare independent APIs and reject disagreement;
  • single-explorer-checked: current behavior, clearly labeled trusted API;
  • structural-only: no Bitcoin validity claim.

Do not use “cryptographically verified on Bitcoin” for explorer-only modes. Modify verifyOtsProof() to return the trust mode and evidence source.

Upgrade helper

  1. Add the /ots-upgrade server source and deployment config to the repository.
  2. Enforce body size, content type, timeouts, process isolation, rate limits, no arbitrary URI access, and sanitized output.
  3. The browser must still bind and validate returned proofs; never trust the helper’s confirmed boolean alone.
  4. Add server unit/integration tests and deployment documentation.

Acceptance criterion: the UI accurately states the Bitcoin trust model, malicious proofs stay within resource budgets, and a helper/API compromise cannot validate a proof for the wrong digest.

Phase 5 — Fix publishing, relay, and recovery operations

Findings addressed: G56-11, G56-12, G56-18, G56-20.

Implementation instructions

  1. Validate relay URLs with URL; allow only wss: in production and ws://localhost under explicit development mode.
  2. Cap relay count, URL length, event size, and concurrent sockets.
  3. Require relay OK acceptance for both immutable authorization and proof carrier.
  4. Set a publication policy, for example:
    • at least two independent relays accept the authorization,
    • at least two accept the proof carrier,
    • at least one read-back query returns each exact event.
  5. Keep retry controls after partial failure; never disable publication as “complete” with zero acceptance.
  6. Bind subscription IDs and verify returned events match requested authors/kinds.
  7. Test representative 30–50 KiB events against target relays and document limits.
  8. Export the archive package before/after publish and encourage independent storage.
  9. Add multi-tab locking using BroadcastChannel or Web Locks so two tabs cannot race upgrades/publication.
  10. Prevent overlapping OTS polls with an in-flight guard or recursive timeout.
  11. Replace origin-wide localStorage.clear()/sessionStorage.clear() with deletion of application-owned keys only.

Acceptance criterion: the app cannot report completion without durable, read-back-confirmed publication and a user-exportable verification package.

Phase 6 — Harden the browser deployment and supply chain

Findings addressed: G56-08, G56-10, G56-14, G56-15.

Application/deployment instructions

  1. Move inline JavaScript from index.html and verify.html into reviewed module files.
  2. Enforce a CSP without script 'unsafe-inline'; use hashes/nonces only where unavoidable.
  3. Add:
    • frame-ancestors 'none',
    • HSTS with an appropriate rollout,
    • X-Content-Type-Options: nosniff,
    • strict Referrer-Policy,
    • restrictive Permissions-Policy,
    • appropriate COOP/CORP where compatible.
  4. Vendor and audit nostr-login-lite assets or pin reviewed immutable files with release hashes/SRI.
  5. Remove unnecessary permissive CORS headers from static pages/helper responses.
  6. Stop exposing avoidable server version information.
  7. Add deployment configuration and a security-header integration test.

Dependency instructions

  1. Remove package-lock.json from .gitignore, regenerate it so root version matches package.json, and commit it.
  2. Use npm ci in CI/releases.
  3. Pin security-sensitive dependencies and review upgrades deliberately.
  4. Generate an SBOM and signed provenance with bundle SHA-256.
  5. Repair .gitmodules for resources/javascript-opentimestamps/ or replace the broken gitlink with a documented immutable test-vector snapshot.
  6. Run tests and advisories in CI; do not claim the old OTS package is integrated when it is only a fixture source.

Acceptance criterion: a clean clone can reproduce the bundle and tests from tracked metadata, and the deployed asset hashes can be tied to a source revision.

Phase 7 — Documentation and protocol standardization

Findings addressed: G56-16 and conceptual completeness.

  1. Rewrite nip_proposal.md around the canonical authorization, immutable history, exact verification state machine, trust modes, and rotation semantics.
  2. Clearly separate:
    • implemented,
    • experimental,
    • design-only,
    • required companion standards.
  3. Replace “identity is quantum-resistant” with precise claims about linked keys and anchor status.
  4. Do not claim pending calendar proofs establish an external time.
  5. Publish test vectors:
    • canonical object bytes/digest,
    • BIP39/BIP32 child seeds,
    • PQ public keys/signatures,
    • valid Nostr authorization event,
    • pending and confirmed OTS proof,
    • candidate conflict-resolution examples.
  6. Obtain at least two independent implementations for canonical encoding and verification before standardization.
  7. Seek independent review from Nostr protocol experts, Bitcoin/OTS experts, and PQ cryptographers.

Acceptance criterion: two independent implementations produce identical vectors and canonical candidate selection.

5. Finding-to-work mapping

Finding Primary mitigation phase Release-blocking?
G56-01 Phase 1 Yes
G56-02 Phase 2 / protocol redesign Yes
G56-03 Phase 1 Yes
G56-04 Phase 4 Yes for strong Bitcoin claims
G56-05 Phase 3 Yes
G56-06 Protocol redesign / Phase 3 Yes
G56-07 Phase 1 Yes
G56-08 Phase 0 and Phase 6 Yes for real seeds
G56-09 Phase 0 / Phase 3 Yes for PQ-strength claim
G56-10 Phase 3 / Phase 6 Yes for production web custody
G56-11 Phase 5 Yes
G56-12 Phase 2 / Phase 5 Yes
G56-13 Phase 4 Yes for untrusted proofs
G56-14 Phase 6 Yes for reproducible release
G56-15 Phase 6 Required for clean clone/tests
G56-16 Phase 0 / Phase 7 Yes for public claims
G56-17 Phase 1 / Phase 4 No if removed from claims
G56-18 Phase 5 No for local research; yes for production
G56-19 Phase 1 Yes
G56-20 Phase 5 / protocol redesign Yes if target relays reject events

6. Test strategy

Unit tests

  • canonical encoding and rejection of non-canonical forms,
  • strict event/schema validation,
  • exact algorithm policy,
  • signer-return validation,
  • candidate sorting/tie-breaks,
  • persistence binding,
  • OTS parser limits and varints,
  • trust-mode labeling.

Property and fuzz tests

  • arbitrary JSON/tag shapes never throw,
  • arbitrary OTS bytes stay within CPU/memory/time budgets,
  • candidate ordering does not alter selection,
  • encode/decode round trips preserve canonical bytes,
  • mutation of any signed canonical field invalidates all applicable proofs.

Integration tests

  • mock signer approves/cancels/mutates events,
  • mock relays reorder, hide, duplicate, reject, and inject events,
  • mock calendars partially fail and return malformed fragments,
  • mock upgrade helper returns wrong-target or oversized proofs,
  • explorer disagreement and outage behavior,
  • resume after every workflow checkpoint,
  • multiple browser tabs.

End-to-end tests

Use throwaway identities against controlled relays:

  1. create 24-word seed,
  2. derive and recover keys,
  3. sign canonical authorization,
  4. publish and read back,
  5. timestamp and upgrade,
  6. verify through each trust mode,
  7. inject a later forged candidate,
  8. confirm earliest valid anchor remains canonical,
  9. rotate using PQ authorization,
  10. verify archive import with the website offline.

Independent vectors

At least one second implementation in another language must reproduce the canonical encoding, derivation, signatures where deterministic, event IDs, and selection result. Self-comparison is insufficient.

7. Release gates

Gate A — Research demo

  • Phase 0 warnings complete.
  • No real-security claims.
  • Existing and adversarial tests pass.
  • Throwaway identities only.

Gate B — Experimental key commitment

  • Protocol redesign, Phases 1–5 complete.
  • Strict verification and earliest-anchor selection independently tested.
  • Archive export/import works.
  • Explorer trust is labeled accurately.
  • No valuable mnemonic entry into the web origin.

Gate C — Production pre-commitment registry

  • Dedicated signer/hardware integration.
  • Phase 6 reproducibility/deployment hardening.
  • Independent implementation and security audit.
  • Kind/format coordination with Nostr community.
  • Durable relay/archive strategy.

Gate D — Complete PQ migration

All Gate C requirements plus standardized and deployed:

  • PQ authentication for future events,
  • PQ key rotation/revocation/recovery,
  • client identity mapping/social-graph behavior,
  • PQ encryption protocol,
  • algorithm deprecation governance,
  • broad client adoption.

Only at Gate D should the project claim to make ongoing Nostr identity use post-quantum secure.

8. Conceptual conclusion

The project is not doomed by its premise. Pre-quantum authorization plus independently ordered timestamping is a reasonable bootstrap for migration away from a signature algorithm that may later become forgeable. Multi-algorithm commitments can provide useful hedging.

The original implementation does, however, conflate three different achievements:

  1. generating PQ keys,
  2. historically linking those keys to an old identity,
  3. making the identity’s future Nostr activity post-quantum secure.

Only the first is currently strong; the second is incomplete; the third requires companion protocols. Implementing this document would make the second defensible and create a valid foundation for the third. It would not eliminate the need to standardize and deploy future PQ event, rotation, encryption, availability, and client identity semantics.