Skip to content

M6: Revision Provenance (diff, rollback, audit export) + FROST go/no-go - #413

Merged
QSchlegel merged 3 commits into
preprodfrom
claude/m6-provenance-frost
Oct 16, 2026
Merged

QSchlegel merged 3 commits into
preprodfrom
claude/m6-provenance-frost

Conversation

@QSchlegel

Copy link
Copy Markdown
Collaborator

Completes Quirin's MRP Month 6 (September 2026) items in ROADMAP.md: Document Sign-Off v1 — Revision Provenance, and the FROST research go/no-go (#220). Builds on PRD-001 (Document Sign-Off).

Revision Provenance (Document Sign-Off v1)

History was already covered by the chained platform attestations (#375). This PR adds the other three pieces, all off-chain.

  • Diff (diffVersions, src/lib/documents/diff.ts): compares any two versions of a document.
    • The metadata diff always works, including hash-only versions whose bytes never left the signer's machine.
    • The line diff runs when both versions are inline UTF-8 text. When it can't run, the response says why.
    • Cost is bounded: 4M LCS cells after the common prefix and suffix are trimmed.
  • Rollback (restoreVersion): re-publishes an earlier version's exact content as version N+1.
    • Append-only: it is logged as version.restored and attested.
    • Approvals never come back. The restored version starts a fresh round at zero.
    • It is refused when the head already has that content, and when the caller's expectedHeadVersionId is stale.
    • Only wallet members can restore.
  • Audit export (exportAudit / public verifyAudit): new format mesh-multisig.document-signoff.audit.v1.
    • Contains every version with its lineage and its standalone proof.v1, the full event log, and the attestation chain with its keys.
    • verifyAuditPackage re-runs every check offline and cross-checks the signed material against the database records.
    • It is a new format rather than more fields on proof.v1, because the proof.v1 field names are a contract that existing verifiers parse.
  • UI: each version card on the document page has Changes and Restore buttons, and the actions menu has Export audit trail.

Refactor: uploadVersion, publishDraft and restoreVersion now share one appendVersion helper. Event metadata is unchanged. exportProof builds through the shared buildProofPackage and now carries partyId/partyRole for contract-party reviews. For threshold proofs the output is byte-identical.

FROST / PQ research (#220)

The findings are in docs/research/frost-go-no-go.md.

  • NO-GO on replacing native scripts with FROST. It saves 206 B or 371 B per transaction (0.009 or 0.016 ADA for 2-of-3 or 3-of-5). The cost:
    • The signer set and threshold leave the chain, and the DRep and CIP-1854 features read them there.
    • CIP-30 and hardware-wallet support are lost.
    • Nonce reuse leaks a signer's key share.
  • CONDITIONAL GO on an opt-in FROST signer as one leaf inside a native script.
  • NO-GO on PQ multisig until the ledger can verify a PQ signature. The roadmap's "Lemour" is Lemur (ePrint 2026/1161).
  • PoC in scripts/research/frost-ed25519/ (research only, trusted dealer):
    • Reproduces the RFC 9591 Appendix E.1 test vector byte for byte.
    • Verifies 2-of-3 and 3-of-5 aggregate signatures as ordinary Cardano vkey witnesses with the CSL fork Mesh ships.
    • Recovers a signer's key share from a reused nonce.

Roadmap

Testing

  • Unit tests: documentDiff, documentAudit (tamper cases, malformed input, bounds) and frostResearchPoc. The full unit run has 1632 tests passing.
  • documentProvenance runs against real Postgres with real CIP-8 signatures and a real attestation key. It covers diff, rollback (including that the approval stays on v1), audit export and offline verification. The document* and contractParties suites pass: 13 tests.
  • tsc --noEmit is clean. Prettier has been applied.
  • ESLint crashes repo-wide on its config, independently of this change.
  • An independent review found no blockers. Its should-fix items are addressed in de89bac.

Known limits / follow-ups

  • The event log, statuses and each round's threshold are database records, not signed data. verifyAuditPackage cross-checks them against the signatures and the chain, and AUDIT_INSTRUCTIONS says so plainly.
  • Parties mode: startReview and submitSignerAction do not yet write partyId. A proof where one person signs in two roles would still trip the verifier's duplicate-signer check, which is keyed on address. This PR doesn't change that.

🤖 Generated with Claude Code

https://claude.ai/code/session_016DwjSXosdJg8rATTa7pQEn


Generated by Claude Code

QSchlegel and others added 3 commits October 16, 2026 19:02
Document Sign-Off v1 (ROADMAP M6). History already had chained platform
attestations (#375); this adds the other three pieces, all off-chain.

Diff: diffVersions compares any two versions of a document. The metadata
diff always works, including hash-only versions whose bytes never left the
signer's machine; the line diff runs when both versions carry their bytes
inline as UTF-8 text, and says why when it cannot. Bounded at 4M LCS cells
after trimming the common prefix and suffix.

Rollback: restoreVersion re-publishes an earlier version's exact content as
version N+1. Append-only — nothing in between is deleted, the restore is
logged (version.restored, with its source) and attested, and approvals never
come back: the restored version starts a fresh round at zero. Restoring the
current head is refused. Wallet members only.

Audit export: mesh-multisig.document-signoff.audit.v1 — every version with
its lineage and its standalone proof.v1, the whole event log, and the
attestation chain with its keys. verifyAuditPackage re-runs every check
offline and cross-checks the two records (each version attested once at its
own hash, each restore carrying the hash it names, Approved only with the
approvals to back it). A new format rather than more fields on proof.v1.

uploadVersion, publishDraft and restoreVersion now share one appendVersion
helper instead of three copies of the supersede logic; event metadata is
unchanged. exportProof builds through the same buildProofPackage as the
audit export, and now carries partyId/partyRole for contract-party reviews,
which the verifier binds against.

Also: ROADMAP M5/M6 PR mapping rows, Revision Provenance vault note, public
roadmap data (Sign-Off MVP and v1 history marked done).

Tests: documentDiff, documentAudit (tamper cases), and a real-DB/real-CIP-8
documentProvenance suite; full unit run 1620 passing; tsc clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016DwjSXosdJg8rATTa7pQEn
…220)

Closes the ROADMAP M6 research item.

Verdict: no-go on replacing native scripts with FROST; conditional go on an
opt-in FROST signer as one leaf inside a native script; no-go on post-quantum
multi-sig until the ledger can verify a PQ signature.

FROST(Ed25519, SHA-512) works on Cardano with no ledger change: RFC 9591's
Ed25519 ciphersuite emits plain RFC 8032 signatures, so a t-of-n aggregate is
one ordinary 101-byte vkey witness under one key hash. The research PoC
(scripts/research/frost-ed25519, trusted dealer, not for production)
reproduces the RFC 9591 Appendix E.1 vector byte for byte and verifies 2-of-3
and 3-of-5 aggregates with noble and with the CSL fork Mesh ships, as an
enterprise address and as a sig leaf in an atLeast script. It also recovers a
signer's share from a reused nonce, which is the operational hazard.

The saving is 206 / 371 bytes per tx (0.009 / 0.016 ADA at 44 lovelace/byte);
the cost is the on-chain signer set and threshold this app's DRep and
CIP-1854 features read, plus CIP-30 and hardware-wallet support.

"Lemour" is Lemur (ePrint 2026/1161), a lattice aggregate multi-signature with
no public implementation; no Plutus builtin verifies any PQ signature.

ROADMAP M6 proof-of-completion rows for Quirin's tasks; Andre's three M6 rows
marked merged (#399, released in #393). FROST and PQC vault notes and the
public roadmap research item updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016DwjSXosdJg8rATTa7pQEn
Audit verifier
- Proof-level errors (format, hash shape, requiredSigners < 1) are reported,
  not dropped; proof wallet and attestation wallet are cross-checked.
- Lineage is re-derived from the event log and must match what the package
  states, so a restore cannot be relabelled as an upload. Events now carry
  their versionId.
- A version whose log shows review.started must carry its proof, and the
  proof's threshold must match the one the round started with — the
  threshold is in no signed payload, so this is the only check on it.
- An emptied event log is an error; requireAttestation turns a stripped
  chain into an error. AUDIT_INSTRUCTIONS now say plainly what is signed
  and what is a database record.

Public verifyAudit
- auditPackageProblem: structural, bounded shape check (versions, reviews,
  chain, events capped) so malformed input is a 400, not a TypeError 500,
  and nobody can buy unbounded CIP-8 work. Verification wrapped likewise.
- No Math.min spread over the chain; a deployment with no attestation key
  falls back to the package's keys (flagged) instead of failing every chain.

restoreVersion
- Refused when the head already has the source's content (not only when it
  is the same row), so a restore cannot silently end a round over the same
  bytes.
- Optional expectedHeadVersionId: a stale view of the head is a CONFLICT.
  The UI sends it, hides Restore for same-content versions, and warns when
  restoring would end an in-review round with signatures.
- A unique-constraint race (P2002) is a CONFLICT, not a 500.

exportAudit reads versions, events and attestations in one RepeatableRead
transaction and no longer loads inline bytes.

Diff
- Split on \n only: a line-ending change is visible, and a
  trailing-newline-only change explains itself instead of an empty diff.
- UTF-8 decoded with fatal + ignoreBOM, so BOM files diff.
- toHunks is linear; LCS table is Uint16 (8MB at the cap); removals before
  additions, as in unified diff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016DwjSXosdJg8rATTa7pQEn
@vercel

vercel Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
multisig Ready Ready Preview Oct 16, 2026 5:44pm UTC

Request Review

@QSchlegel
QSchlegel marked this pull request as ready for review October 16, 2026 19:52
@QSchlegel
QSchlegel merged commit 2718e09 into preprod Oct 16, 2026
8 checks passed

This branch was successfully deployed

1 active deployment
Preview — de89bac6 Deployed Oct 16, 2026 by vercel[bot]
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