From 622d688ac88a2b14d64b6da3263caf0a488f21f1 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 24 Jun 2026 10:01:00 +0000 Subject: [PATCH] content: add article the-committed-weights-scalable-llm-fingerprinting-and-the-economics-of-zk-model-ip Article on scalable LLM fingerprinting (perinucleus sampling, FP-VEC, Hot-Swap MarkBoard) and ZK-based on-chain IP registration with Poseidon commitment + Groth16 proof. Includes interactive artifact model-fingerprint-lifecycle: 5-step flow diagram with click-to-explore mechanics and attack scenario overlay (fine-tuning / quantization / merging). Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01LdkWes428e7YMAzV9RY426 --- content/_index.json | 56 ++- .../index.mdx | 111 +++++ .../model-fingerprint-lifecycle/data.json | 95 +++++ .../model-fingerprint-lifecycle/manifest.json | 20 + .../artifacts/model-fingerprint-lifecycle.ts | 378 ++++++++++++++++++ 5 files changed, 655 insertions(+), 5 deletions(-) create mode 100644 content/articles/2026/06/the-committed-weights-scalable-llm-fingerprinting-and-the-economics-of-zk-model-ip/index.mdx create mode 100644 content/artifacts/model-fingerprint-lifecycle/data.json create mode 100644 content/artifacts/model-fingerprint-lifecycle/manifest.json create mode 100644 src/islands/artifacts/model-fingerprint-lifecycle.ts diff --git a/content/_index.json b/content/_index.json index 8013526..aa5164d 100644 --- a/content/_index.json +++ b/content/_index.json @@ -1,14 +1,14 @@ { "counts": { - "articles": 134, - "artifacts": 135 + "articles": 135, + "artifacts": 136 }, "topicCounts": { "agents": 45, "ai": 14, "blockchain": 33, - "cryptography": 22, - "data": 19, + "cryptography": 23, + "data": 20, "defi": 32, "infrastructure": 46, "llm": 23, @@ -19,9 +19,39 @@ "tokenomics": 18, "tooling": 1, "web3": 14, - "zero-knowledge": 18 + "zero-knowledge": 19 }, "articles": [ + { + "slug": "the-committed-weights-scalable-llm-fingerprinting-and-the-economics-of-zk-model-ip", + "path": "content/articles/2026/06/the-committed-weights-scalable-llm-fingerprinting-and-the-economics-of-zk-model-ip/index.mdx", + "url": "/articles/the-committed-weights-scalable-llm-fingerprinting-and-the-economics-of-zk-model-ip", + "title": "The Committed Weights: Scalable LLM Fingerprinting and the Economics of ZK Model IP", + "description": "Training Llama-3.1-8B costs ~$6M. The weights are free to clone. Perinucleus sampling embeds 24,576 distinct fingerprints per model with no quality loss — and a Poseidon ZK commitment registers ownership on-chain for $0.017, enforceable by smart contract.", + "pubDate": "2026-06-24", + "topics": [ + "zero-knowledge", + "cryptography", + "data" + ], + "tags": [ + "model-fingerprinting", + "watermarking", + "perinucleus", + "fp-vec", + "poseidon", + "groth16", + "ip-registry", + "model-ip", + "llm-ip", + "zklora" + ], + "artifacts": [ + "model-fingerprint-lifecycle" + ], + "difficulty": "intermediate", + "draft": false + }, { "slug": "the-biased-draft-speculative-decodings-trust-gap-in-decentralized-inference", "path": "content/articles/2026/06/the-biased-draft-speculative-decodings-trust-gap-in-decentralized-inference/index.mdx", @@ -3680,6 +3710,22 @@ } ], "artifacts": [ + { + "slug": "model-fingerprint-lifecycle", + "path": "content/artifacts/model-fingerprint-lifecycle/manifest.json", + "module": "src/islands/artifacts/model-fingerprint-lifecycle.ts", + "url": "/artifacts/model-fingerprint-lifecycle", + "title": "Model Fingerprint Lifecycle", + "description": "Interactive diagram of the five-step LLM IP protocol: embed a statistical fingerprint, commit it with Poseidon, register on-chain, prove ownership with a ZK proof, enforce via smart contract. Select an attack scenario to see which steps are vulnerable.", + "pubDate": "2026-06-24", + "type": "dom", + "archetype": "diagram", + "topics": [ + "zero-knowledge", + "cryptography" + ], + "draft": false + }, { "slug": "the-draft-race", "path": "content/artifacts/the-draft-race/manifest.json", diff --git a/content/articles/2026/06/the-committed-weights-scalable-llm-fingerprinting-and-the-economics-of-zk-model-ip/index.mdx b/content/articles/2026/06/the-committed-weights-scalable-llm-fingerprinting-and-the-economics-of-zk-model-ip/index.mdx new file mode 100644 index 0000000..0192690 --- /dev/null +++ b/content/articles/2026/06/the-committed-weights-scalable-llm-fingerprinting-and-the-economics-of-zk-model-ip/index.mdx @@ -0,0 +1,111 @@ +--- +slug: the-committed-weights-scalable-llm-fingerprinting-and-the-economics-of-zk-model-ip +title: "The Committed Weights: Scalable LLM Fingerprinting and the Economics of ZK Model IP" +description: "Training Llama-3.1-8B costs ~$6M. The weights are free to clone. Perinucleus sampling embeds 24,576 distinct fingerprints per model with no quality loss — and a Poseidon ZK commitment registers ownership on-chain for $0.017, enforceable by smart contract." +pubDate: 2026-06-24T09:50:19.256Z +author: blokz +topics: [zero-knowledge, cryptography, data] +tags: [model-fingerprinting, watermarking, perinucleus, fp-vec, poseidon, groth16, ip-registry, model-ip, llm-ip, zklora] +artifacts: [model-fingerprint-lifecycle] +difficulty: intermediate +sources: + - label: "Scalable Fingerprinting of Large Language Models (Nasery et al. 2025)" + url: "https://arxiv.org/abs/2502.07760" + - label: "FP-VEC: Fingerprinting LLMs via Efficient Vector Addition (Xu et al. 2024)" + url: "https://arxiv.org/abs/2409.08846" + - label: "Hot-Swap MarkBoard: Black-box Watermarking for Large-scale Distribution (Zhang et al. 2025)" + url: "https://arxiv.org/abs/2507.20650" + - label: "Copyright Protection for LLMs: A Survey (Xu et al. 2025)" + url: "https://arxiv.org/abs/2508.11548" +--- + +Training Llama-3.1-8B requires roughly 6M GPU-hours and millions of dollars. The resulting weights sit on Hugging Face, free to clone, fine-tune, and deploy commercially without attribution. Traditional IP law barely reaches model weights in most jurisdictions, and even where it does, enforcement requires litigation, discovery, and a court order. The enforcement problem is fundamentally cryptographic, and two recent research threads — scalable LLM fingerprinting and ZK commitment schemes — have arrived at the same point from opposite directions. + +## The IP Vacuum in Open-Weight Models + +The core issue isn't copying; it's unverifiable lineage. When a derivative model appears in the wild, there is no mechanism to prove it descends from a specific base model, let alone to enforce a license programmatically. Without that primitive, the economics of open-weight releases depend entirely on social norms and reputational incentives — which break down at commercial scale. + +Copyright registration gives you a timestamped claim but no enforcement mechanism a computer can check. A patent covers algorithms, not the specific learned weight values that constitute a trained model. Neither produces a machine-readable assertion that a smart contract can act on. + +Fingerprinting is the missing cryptographic primitive. + +## Scalable LLM Fingerprinting: Three Approaches + +Classical model watermarking embeds a backdoor trigger — a specific input that produces a predictable output only the watermarked model generates. The weakness: any fine-tuning run that touches the affected weights erases the trigger. The field has moved past this. + +**Perinucleus sampling** (Nasery et al., 2025) operates on the statistical periphery of the token distribution — the "perinuclear" zone around the nucleus of high-probability tokens. Each user receives a 512-bit seed that shifts the tail distribution of the model's token probabilities in a user-specific way. Detection runs a hypothesis test: sample ~1,000 tokens from a suspect model, check whether the tail distribution matches the claimed seed at statistical significance. The scheme embeds 24,576 distinct user fingerprints into Llama-3.1-8B — two orders of magnitude more than prior work — with zero measurable degradation in model utility. Critically, the fingerprints survive standard supervised post-training fine-tuning on new data, because the signal is distributed broadly across weights rather than concentrated at backdoor-activating positions. + +**FP-VEC** (Xu et al., 2024) encodes a fingerprint as a weight perturbation vector — literally added to a base model's weights via vector addition. One training run produces a master delta; every subsequent deployment copies that delta into any base model, CPU-side, in seconds. The scheme is lightweight (no GPU required for fingerprinting), scalable (one training produces unlimited fingerprinted copies), and preserves model utility by construction. + +**Hot-Swap MarkBoard** (Zhang et al., 2025) adapts the LoRA architecture for distribution-scale attribution. It encodes an *n*-bit binary signature into a multi-branch LoRA module attached to the base model; each branch carries one bit. Changing the embedded signature requires only swapping branches, not retraining. The critical property for decentralized deployment: verification is purely black-box — it tests model behavior through an API, not weight values. When a deployer only exposes an inference endpoint, MarkBoard is the scheme that works. + + + +## The ZK Bridge: Registering a Secret Without Exposing It + +Fingerprinting solves detection. The publication problem is harder. + +A distributor who registers their fingerprint plainly creates an attack target. Knowing the exact seed or perturbation vector lets an adversary craft a fine-tuning run that erases precisely that pattern. The right primitive is a cryptographic commitment. + +The scheme: + +``` +commitment = Poseidon(fingerprint ∥ salt) +``` + +**Poseidon** is the ZK-friendly hash function used throughout the SNARK ecosystem today — Groth16, PLONK, Halo2 circuits all use it natively. The key property is constraint efficiency: a Poseidon evaluation requires roughly 200 R1CS constraints, versus ~25,000 for SHA-256 inside a circuit. That gap matters for proof generation speed and cost. + +The commitment is 32 bytes. It is computationally hiding (it reveals nothing about the fingerprint or the salt) and binding (any change to either input produces a different commitment). The distributor publishes this commitment on-chain — one SSTORE operation. + +**Proving ownership** later requires demonstrating knowledge of `(fingerprint, salt)` such that `Poseidon(fingerprint ∥ salt)` equals the registered value, without revealing either. This is a standard ZK preimage proof. A Groth16 circuit over the 200-constraint Poseidon hash takes on the order of milliseconds to prove with modern tooling (e.g., Circom + snarkjs, or SP1). On-chain Groth16 verification costs roughly 300,000 gas. + +When fingerprint secrecy matters less — say, when a license dispute already requires disclosing the seed to an auditor — a simpler path works: the fingerprint is revealed directly to a smart contract that hashes it on-chain and checks against the registered commitment. This costs ~50,000 gas at the price of exposing the fingerprint in the transaction data. + +## The Economics: Gas vs. Traditional IP Filing + +At current Ethereum mainnet conditions (0.5 gwei gas price, ETH at ~$1,654): + +| Action | Gas | Cost | +|---|---|---| +| Register commitment (SSTORE) | 20,000 | **$0.017** | +| Verify via ZK proof (Groth16) | 300,000 | **$0.25** | +| Verify via direct hash check | 50,000 | **$0.041** | +| US copyright registration | — | $65 | +| USPTO utility patent (small entity) | — | $1,000–$8,000 | + +The $0.017 SSTORE is the prior art claim. It costs 3,800× less than the cheapest centralized IP registration and settles in ~12 seconds. The ZK verification proof — the enforcement mechanism — costs $0.25 and is callable by any smart contract, from any inference marketplace, without trusted intermediaries. + +Compare this to the traditional path: copyright registration provides a timestamped record but no smart-contract-readable assertion. Enforcement requires civil litigation, which can cost $100,000+ before discovery. The on-chain registry is three orders of magnitude cheaper and produces a machine-enforceable claim. + +## Attack Vectors: Where the Scheme Breaks + +Three attacks threaten the fingerprint layer. The commitment/registration/proof layer is cryptographically sound and none of these attacks touch it. + +**Fine-tuning erasure.** Standard supervised fine-tuning on new data rewrites weight perturbations. Perinucleus was explicitly designed to survive standard post-training by distributing its signal broadly. Backdoor-based schemes — not covered here but still prevalent — are not resistant. The key engineering lesson: scheme choice matters, and the fingerprint's attack resistance is a property of the embedding algorithm, not of the ZK layer. + +**Quantization degradation.** 4-bit quantization maps continuous weight values to discrete levels, truncating subtle perturbations. Schemes that test behavioral output through an API (MarkBoard) survive this because quantization doesn't change *which* tokens the model strongly prefers. Weight-statistics schemes that measure perturbation magnitudes directly are more vulnerable. Layering: use API-level detection when the model will be quantized for deployment. + +**Model merging dilution.** This is the unresolved hard case. SLERP interpolation between two models — one fingerprinted with seed A, one with seed B — at 50% weighting attenuates both signals. Two licensees who collude to merge their copies produce a model attributable to neither. The survey by Xu et al. (2025) identifies this as the primary open problem in model fingerprinting. No current scheme is provably merge-resistant. + +The practical engineering response is layered defense: fingerprinting establishes attribution evidence, not an unbreakable lock. Wire it with behavioral monitoring (does this deployed model behave like a fingerprinted model?) and API-rate-based anomaly detection. + +## What This Enables: Composable Model IP + +The primitive unlocks composable attribution. Today, a LoRA adapter trained on proprietary data has no trustless claim on any derivative revenue. With fingerprint commitments as the base layer: + +1. The base model owner registers `Poseidon(base_fp ∥ salt_b)` on-chain. +2. A fine-tuner builds a LoRA adapter. Perinucleus fingerprints persist through standard fine-tuning; the adapter can also embed its own fingerprint for the adapter-specific contribution. +3. Both commitments are registered independently. An inference marketplace verifies ZK proofs of both before routing a query. +4. Revenue splits execute in the same contract — 10% to base model registrant, 90% to adapter owner — without any party revealing their fingerprint. + +This connects to the [TOPLOC activation hash](/articles/the-activation-hash-toploc-and-the-1-000x-receipt-for-non-interactive-verifiable-inference), which proves *what model was run* during a specific inference. Fingerprint commitment proves *who owns* the model that was run. The two proofs compose: a single inference call can produce a TOPLOC receipt proving model identity and a ZK fingerprint proof establishing ownership, giving inference marketplaces both verification dimensions in one round trip. + +[ZKLoRA](/articles/prove-it-without-showing-it-zero-knowledge-verification-for-lora-adapter-marketplaces) proves adapter *compatibility* with a base model without revealing adapter weights — but not ownership. Fingerprint commitments handle the ownership side, closing the remaining gap. + +## Takeaways + +- Scalable LLM fingerprinting has reached 24,576 per-user identities in an 8B-parameter model with no utility degradation. The detection problem is effectively solved for standard fine-tuning attacks. +- The deployment problem is cryptographic: publishing a fingerprint creates an attack surface. Poseidon commitment plus Groth16 ZK preimage proof registers ownership on-chain for $0.017 and enforces it for $0.25 — machine-readable, trustless, composable. +- On-chain IP registration is 3,800× cheaper than US copyright filing and produces enforcement-ready smart contract state versus a PDF certificate. +- Model merging remains the primary unresolved attack. Treat fingerprint-based IP as strong attribution evidence, not an unbreakable lock. +- The architecture for composable model IP markets exists today: fingerprint commitment on Ethereum, ZK proof of lineage, automated revenue splits. What's missing is the registry contract and marketplace integrations. diff --git a/content/artifacts/model-fingerprint-lifecycle/data.json b/content/artifacts/model-fingerprint-lifecycle/data.json new file mode 100644 index 0000000..9bc47cd --- /dev/null +++ b/content/artifacts/model-fingerprint-lifecycle/data.json @@ -0,0 +1,95 @@ +{ + "ethPriceUSD": 1654, + "gasGwei": 0.50, + "model": { + "name": "Llama-3.1-8B", + "paramsBillions": 8, + "numFingerprints": 24576, + "fingerprintBits": 512 + }, + "steps": [ + { + "id": "embed", + "label": "Embed", + "icon": "⊕", + "title": "Fingerprint Embedding", + "detail": "Perinucleus sampling injects a user-specific 512-bit seed into the model's weight perturbation distribution. Each of 24,576 users receives a distinct seed that shifts the statistical tail of token probabilities — invisible to casual inspection, detectable with a hypothesis test over ~1,000 sampled tokens.", + "stat": "24,576 unique fingerprints per 8B model", + "gasUnits": null, + "attacks": { "finetune": "partial", "quantize": "partial", "merge": "vulnerable" } + }, + { + "id": "commit", + "label": "Commit", + "icon": "H(·)", + "title": "Cryptographic Commitment", + "detail": "Poseidon(fingerprint ∥ salt) yields a 32-byte digest. Poseidon is a ZK-friendly hash: ~200 R1CS constraints per call versus ~25,000 for SHA-256. The commitment is computationally hiding (reveals nothing about the fingerprint) and binding (any change invalidates the digest).", + "stat": "32 bytes · ~200 ZK constraints", + "gasUnits": null, + "attacks": { "finetune": "resistant", "quantize": "resistant", "merge": "resistant" } + }, + { + "id": "register", + "label": "Register", + "icon": "⛓", + "title": "On-Chain Registration", + "detail": "One SSTORE writes the 32-byte commitment to a public IP registry contract. Block number provides immutable timestamping for establishing prior art. Anyone can verify the registration exists; nobody can learn the underlying fingerprint.", + "stat": "20,000 gas · $0.017 at 0.5 gwei / $1,654 ETH", + "gasUnits": 20000, + "attacks": { "finetune": "resistant", "quantize": "resistant", "merge": "resistant" } + }, + { + "id": "prove", + "label": "Prove", + "icon": "π", + "title": "ZK Ownership Proof", + "detail": "A Groth16 proof over the 200-constraint Poseidon circuit proves knowledge of (fingerprint, salt) such that Poseidon(fingerprint ∥ salt) equals the registered commitment — without revealing either value. Alternatively, a simpler 50,000-gas on-chain hash check works when fingerprint secrecy is less critical.", + "stat": "300,000 gas ZK ($0.25) · 50,000 gas simple ($0.041)", + "gasUnits": 300000, + "attacks": { "finetune": "resistant", "quantize": "resistant", "merge": "resistant" } + }, + { + "id": "enforce", + "label": "Enforce", + "icon": "✓", + "title": "License Enforcement", + "detail": "An inference marketplace contract verifies the ZK proof on each request and gates model access or revenue splits to verified fingerprint holders. No litigation, no intermediaries — the smart contract is the enforcement mechanism, composable with any on-chain payment system.", + "stat": "Trustless · automatic · composable", + "gasUnits": null, + "attacks": { "finetune": "partial", "quantize": "partial", "merge": "vulnerable" } + } + ], + "attacks": [ + { + "id": "none", + "label": "No Attack", + "color": "#5b8cff", + "description": "Baseline: fingerprints intact, all five protocol steps function as designed." + }, + { + "id": "finetune", + "label": "Fine-tuning", + "color": "#f59e0b", + "description": "Standard supervised fine-tuning rewrites weight perturbations. Perinucleus distributes signal broadly and survives standard post-training fine-tuning (Nasery et al. 2025). Backdoor-based watermarks are erased by large-dataset fine-tuning runs." + }, + { + "id": "quantize", + "label": "Quantization", + "color": "#f59e0b", + "description": "4-bit quantization truncates weight values, potentially erasing subtle perturbations. API-level behavioral schemes (Hot-Swap MarkBoard) survive. Weight-statistics schemes are more vulnerable to precision reduction (Zhang et al. 2025)." + }, + { + "id": "merge", + "label": "Model Merging", + "color": "#ef4444", + "description": "SLERP or TIES interpolation between two differently-fingerprinted models attenuates both signals below detection threshold. Coordinated merging by two licensees nullifies attribution for both. No current scheme is provably merge-resistant (Xu et al. 2025 survey)." + } + ], + "costCompare": [ + { "label": "SSTORE registration", "costUSD": 0.017, "type": "onchain" }, + { "label": "Simple hash verify", "costUSD": 0.041, "type": "onchain" }, + { "label": "Groth16 ZK verify", "costUSD": 0.25, "type": "onchain" }, + { "label": "US copyright filing", "costUSD": 65, "type": "traditional" }, + { "label": "USPTO utility patent", "costUSD": 2000, "type": "traditional" } + ] +} diff --git a/content/artifacts/model-fingerprint-lifecycle/manifest.json b/content/artifacts/model-fingerprint-lifecycle/manifest.json new file mode 100644 index 0000000..2a90916 --- /dev/null +++ b/content/artifacts/model-fingerprint-lifecycle/manifest.json @@ -0,0 +1,20 @@ +{ + "slug": "model-fingerprint-lifecycle", + "title": "Model Fingerprint Lifecycle", + "description": "Interactive diagram of the five-step LLM IP protocol: embed a statistical fingerprint, commit it with Poseidon, register on-chain, prove ownership with a ZK proof, enforce via smart contract. Select an attack scenario to see which steps are vulnerable.", + "pubDate": "2026-06-24T09:50:28.675Z", + "type": "dom", + "archetype": "diagram", + "topics": [ + "zero-knowledge", + "cryptography" + ], + "aspect": "16/9", + "featured": false, + "controls": ["click a step node to see its mechanics", "select an attack scenario to highlight vulnerable steps"], + "dataSource": { + "label": "Nasery et al. 2025 · Xu et al. 2024 · Zhang et al. 2025", + "url": "https://arxiv.org/abs/2502.07760" + }, + "dataAsOf": "2026-06-24" +} diff --git a/src/islands/artifacts/model-fingerprint-lifecycle.ts b/src/islands/artifacts/model-fingerprint-lifecycle.ts new file mode 100644 index 0000000..01a6b47 --- /dev/null +++ b/src/islands/artifacts/model-fingerprint-lifecycle.ts @@ -0,0 +1,378 @@ +/** + * Artifact: model-fingerprint-lifecycle — Model Fingerprint Lifecycle + * + * Interactive diagram of the five-step LLM fingerprint protocol: + * Embed → Commit → Register → Prove → Enforce + * + * Click any step to see its mechanics and attack-resistance in the panel. + * Use the attack selector to see which steps are vulnerable (red) or + * partial (amber) under fine-tuning, quantization, or model merging. + * + * Stage: SVG flow diagram (5 step nodes + arrows). + * Panel: selected step detail + attack-resistance badges. + * Controls: attack selector (None / Fine-tuning / Quantization / Merging). + * Caption: primary sources. + */ +import data from '../../../content/artifacts/model-fingerprint-lifecycle/data.json'; +import { createArtifactLayout } from '@/lib/artifact-layout'; + +type Step = typeof data.steps[number]; +type Attack = typeof data.attacks[number]; + +const NS = 'http://www.w3.org/2000/svg'; + +/* ── Design tokens (mirror src/styles/global.css) ─────────────────────────── */ +const C = { + void: '#05070d', + panel: '#0d1322', + line: 'rgba(124,140,255,.18)', + lineBright: 'rgba(124,140,255,.38)', + text: '#e7eaf3', + muted: '#8d95ad', + faint: '#4a5268', + accent: '#5b8cff', + violet: '#8b5cf6', + cyan: '#22d3ee', + green: '#22c55e', + amber: '#f59e0b', + red: '#ef4444', +}; + +const ATTACK_COLOR: Record = { + resistant: C.green, + partial: C.amber, + vulnerable: C.red, +}; + +/* ── SVG helpers ───────────────────────────────────────────────────────────── */ +function svgEl( + tag: T, + attrs: Record = {}, +): SVGElementTagNameMap[T] { + const el = document.createElementNS(NS, tag); + for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, String(v)); + return el; +} + +function buildFlowDiagram( + svg: SVGSVGElement, + selectedId: string | null, + attackId: string, + onStep: (id: string) => void, +): void { + svg.innerHTML = ''; + + const VW = 720, VH = 260; + svg.setAttribute('viewBox', `0 0 ${VW} ${VH}`); + + /* Background */ + svg.appendChild(svgEl('rect', { x: 0, y: 0, width: VW, height: VH, fill: C.void })); + + /* Defs: arrowhead marker */ + const defs = svgEl('defs'); + const marker = svgEl('marker', { + id: 'arrowhead', markerWidth: 8, markerHeight: 6, + refX: 7, refY: 3, orient: 'auto', + }); + const poly = svgEl('polygon', { points: '0 0, 8 3, 0 6', fill: C.faint }); + marker.appendChild(poly); + defs.appendChild(marker); + svg.appendChild(defs); + + const N = data.steps.length; // 5 + const BOX_W = 106, BOX_H = 92; + const GAP = 34; + const TOTAL = N * BOX_W + (N - 1) * GAP; + const LEFT = (VW - TOTAL) / 2; // horizontal centering + const TOP = (VH - BOX_H) / 2 - 8; // a little above center (room for label below) + const LABEL_Y = TOP + BOX_H + 22; + const ICON_CY = TOP + BOX_H * 0.38; + const STEP_CY = TOP + BOX_H * 0.70; + + data.steps.forEach((step, i) => { + const x = LEFT + i * (BOX_W + GAP); + const cx = x + BOX_W / 2; + + /* Arrow to next step */ + if (i < N - 1) { + const ax0 = x + BOX_W + 3; + const ax1 = x + BOX_W + GAP - 3; + const ay = TOP + BOX_H / 2; + svg.appendChild(svgEl('line', { + x1: ax0, y1: ay, x2: ax1, y2: ay, + stroke: C.faint, 'stroke-width': 1.5, + 'marker-end': 'url(#arrowhead)', + })); + } + + /* Determine box border color based on attack + selection */ + const attackState = attackId !== 'none' + ? (step.attacks as Record)[attackId] + : null; + + const borderColor = attackState + ? ATTACK_COLOR[attackState] + : (selectedId === step.id ? C.accent : C.line); + + const glowAlpha = (selectedId === step.id || attackState) ? 0.18 : 0; + const glowColor = attackState + ? ATTACK_COLOR[attackState] + : C.accent; + + /* Glow filter (approximate via shadow rect) */ + if (glowAlpha > 0) { + svg.appendChild(svgEl('rect', { + x: x - 4, y: TOP - 4, width: BOX_W + 8, height: BOX_H + 8, + rx: 14, ry: 14, fill: glowColor, + opacity: glowAlpha, filter: 'blur(6px)', + })); + } + + /* Box fill */ + svg.appendChild(svgEl('rect', { + x, y: TOP, width: BOX_W, height: BOX_H, rx: 10, ry: 10, + fill: C.panel, stroke: borderColor, 'stroke-width': 1.5, + })); + + /* Icon / symbol */ + const iconEl = svgEl('text', { + x: cx, y: ICON_CY, + 'text-anchor': 'middle', + 'dominant-baseline': 'central', + fill: selectedId === step.id ? C.accent : (attackState ? ATTACK_COLOR[attackState] : C.violet), + 'font-size': 18, + 'font-family': 'monospace', + 'font-weight': 700, + }); + iconEl.textContent = step.icon; + svg.appendChild(iconEl); + + /* Step label inside box */ + const stepEl = svgEl('text', { + x: cx, y: STEP_CY, + 'text-anchor': 'middle', + 'dominant-baseline': 'central', + fill: C.muted, + 'font-size': 10, + 'font-family': 'monospace', + 'letter-spacing': 1.5, + }); + stepEl.textContent = step.label.toUpperCase(); + svg.appendChild(stepEl); + + /* Label below box */ + if (step.gasUnits) { + const gEl = svgEl('text', { + x: cx, y: LABEL_Y, + 'text-anchor': 'middle', + fill: C.faint, + 'font-size': 9.5, + 'font-family': 'monospace', + }); + gEl.textContent = `${(step.gasUnits / 1000).toFixed(0)}k gas`; + svg.appendChild(gEl); + } + + /* Invisible click target */ + const hit = svgEl('rect', { + x, y: TOP - 10, width: BOX_W, height: BOX_H + 20, + fill: 'transparent', cursor: 'pointer', + }); + hit.addEventListener('click', () => onStep(step.id)); + svg.appendChild(hit); + }); + + /* Title row at top */ + const titleEl = svgEl('text', { + x: VW / 2, y: 16, + 'text-anchor': 'middle', + fill: C.faint, 'font-size': 10, + 'font-family': 'monospace', + 'letter-spacing': 1.5, + }); + titleEl.textContent = 'MODEL FINGERPRINT LIFECYCLE · CLICK A STEP TO EXPLORE'; + svg.appendChild(titleEl); +} + +/* ── Panel ────────────────────────────────────────────────────────────────── */ +function renderPanel( + panel: HTMLElement, + step: Step | null, + attackId: string, + attacks: typeof data.attacks, +): void { + panel.innerHTML = ''; + + const attack = attacks.find(a => a.id === attackId) ?? attacks[0]; + + if (!step) { + /* Default state */ + const empty = document.createElement('div'); + empty.className = 'mfl-empty'; + empty.innerHTML = ` +

← Click any step to see mechanics & attack resistance

+ ${attackId !== 'none' ? `
${attack.description}
` : ''} + `; + panel.appendChild(empty); + return; + } + + const attackState = attackId !== 'none' + ? (step.attacks as Record)[attackId] + : null; + + const badge = (key: string, label: string) => { + const state = (step.attacks as Record)[key]; + const color = ATTACK_COLOR[state] ?? C.muted; + return `${label}: ${state}`; + }; + + const div = document.createElement('div'); + div.className = 'mfl-detail'; + div.innerHTML = ` +

${step.title}

+

${step.detail}

+
${step.stat}
+
+ ${badge('finetune', 'Fine-tune')} + ${badge('quantize', 'Quantize')} + ${badge('merge', 'Merge')} +
+ ${attackState && attackId !== 'none' ? ` +
+ ${attack.label}: ${attack.description} +
` : ''} + `; + panel.appendChild(div); +} + +/* ── Main mount ────────────────────────────────────────────────────────────── */ +export default function mount(container: HTMLElement): () => void { + const layout = createArtifactLayout(container, { + wideTemplate: 'rail', + stageAspect: '720/260', + }); + const { stage, panel, controls, caption } = layout; + + /* ── State ── */ + let selectedId: string | null = null; + let attackId = 'none'; + + /* ── Styles ── */ + const style = document.createElement('style'); + style.textContent = ` + .mfl-svg { width: 100%; height: 100%; display: block; cursor: default; } + + .mfl-empty { padding: 12px 4px; } + .mfl-empty-hint { + color: ${C.muted}; font-size: .82rem; font-style: italic; + line-height: 1.5; margin: 0 0 10px; + } + + .mfl-detail { display: flex; flex-direction: column; gap: 10px; padding: 4px; } + .mfl-title { font-size: .95rem; font-weight: 600; color: ${C.text}; margin: 0; } + .mfl-text { font-size: .80rem; color: ${C.muted}; line-height: 1.55; margin: 0; } + .mfl-stat { font-family: monospace; font-size: .75rem; color: ${C.cyan}; + background: rgba(34,211,238,.07); border: 1px solid rgba(34,211,238,.18); + border-radius: 6px; padding: 5px 8px; } + .mfl-badges { display: flex; flex-wrap: wrap; gap: 6px; } + .mfl-badge { + font-family: monospace; font-size: .70rem; padding: 3px 8px; + border-radius: 99px; border: 1px solid var(--bc); + color: var(--bc); background: color-mix(in srgb, var(--bc) 12%, transparent); + white-space: nowrap; flex: none; + } + .mfl-attack-note { + font-size: .76rem; color: ${C.muted}; line-height: 1.5; + border-left: 2px solid var(--ac); padding-left: 8px; + } + .mfl-attack-label { color: var(--ac); font-weight: 600; } + .mfl-attack-desc { + font-size: .80rem; color: ${C.muted}; line-height: 1.5; + border-left: 2px solid ${C.amber}; padding-left: 8px; + margin-top: 8px; + } + + .mfl-controls { display: flex; flex-wrap: wrap; gap: 7px; } + .mfl-ctl-label { font-size: .72rem; color: ${C.faint}; font-family: monospace; + letter-spacing: .08em; text-transform: uppercase; margin-bottom: 6px; + display: block; } + .mfl-btn { + font-family: monospace; font-size: .72rem; padding: 4px 11px; + border-radius: 6px; cursor: pointer; border: 1px solid ${C.line}; + color: ${C.muted}; background: transparent; transition: border-color .15s, color .15s; + white-space: nowrap; flex: none; + } + .mfl-btn:hover { border-color: ${C.lineBright}; color: ${C.text}; } + .mfl-btn.is-active { border-color: var(--ab); color: var(--ab); + background: color-mix(in srgb, var(--ab) 10%, transparent); } + .mfl-caption { font-size: .70rem; color: ${C.faint}; line-height: 1.5; } + `; + stage.appendChild(style); + + /* ── SVG stage ── */ + const svg = document.createElementNS(NS, 'svg') as SVGSVGElement; + svg.className.baseVal = 'mfl-svg'; + stage.appendChild(svg); + + /* ── Controls ── */ + const ctrlLabel = document.createElement('span'); + ctrlLabel.className = 'mfl-ctl-label'; + ctrlLabel.textContent = 'Attack scenario'; + controls.appendChild(ctrlLabel); + + const btnsRow = document.createElement('div'); + btnsRow.className = 'mfl-controls'; + controls.appendChild(btnsRow); + + const ATTACK_COLORS: Record = { + none: C.accent, + finetune: C.amber, + quantize: C.amber, + merge: C.red, + }; + + const attackBtns: HTMLButtonElement[] = []; + data.attacks.forEach(a => { + const btn = document.createElement('button'); + btn.className = 'mfl-btn' + (a.id === attackId ? ' is-active' : ''); + btn.textContent = a.label; + btn.style.setProperty('--ab', ATTACK_COLORS[a.id] ?? C.accent); + btn.addEventListener('click', () => { + attackId = a.id; + attackBtns.forEach((b, bi) => { + const isActive = data.attacks[bi].id === a.id; + b.classList.toggle('is-active', isActive); + }); + redraw(); + }); + btnsRow.appendChild(btn); + attackBtns.push(btn); + }); + + /* ── Caption ── */ + const cap = document.createElement('p'); + cap.className = 'mfl-caption'; + cap.innerHTML = + 'Sources: Nasery et al. 2025 (arXiv:2502.07760) · Xu et al. 2024 (arXiv:2409.08846) · Zhang et al. 2025 (arXiv:2507.20650) · Xu et al. 2025 (arXiv:2508.11548)'; + caption.appendChild(cap); + + /* ── Redraw ── */ + function onStepClick(id: string) { + selectedId = selectedId === id ? null : id; + redraw(); + } + + function redraw() { + buildFlowDiagram(svg, selectedId, attackId, onStepClick); + const step = selectedId ? data.steps.find(s => s.id === selectedId) ?? null : null; + renderPanel(panel, step, attackId, data.attacks); + } + + redraw(); + + return () => { + layout.dispose(); + style.remove(); + }; +}