# NULL — Reproducible builds & release transparency

Every crypto artifact this server serves is a **logged, signed fact**:

- `GET /svc/api/messages/v2/transparency` — sha256 of every served artifact, ML-DSA-65-signed
  over the canon `null-release/v1\n<file>:<sha256>...` by the same issuer key as `/svc/api/messages/v2/prov/issuer`.
- Every hash **change** is appended to the append-only witness chain under slot `release:<file>`
  (`GET /svc/api/messages/v2/witness/range` — replay it). A swapped build is permanent, provable history.

**Honesty:** this is tamper-*evidence*, not prevention. A malicious server could serve a targeted
page once — but any third party polling `/transparency` (do it — the endpoint is public and cheap)
turns that into a provable event. The more independent monitors, the stronger the guarantee.

## Verify what you were served

```
curl -s https://nullchat.me/svc/anygas-web.js | sha256sum
curl -s https://nullchat.me/svc/api/messages/v2/transparency        # canon (name:sha256 per line; sig rides its own route, SLH-DSA countersig at /api/trust/roots)
curl -s https://nullchat.me/svc/api/messages/v2/transparency-sig    # { sig, canonSha256 }
# 1. compare your hashes against the canon lines
# 2. check sha256(canon) equals canonSha256 from the sig route (pairs the two replies)
# 3. verify sig.mlSig (ML-DSA-65, FIPS 204) over the exact canon string with the pinned
#    issuer key (GET /svc/api/messages/v2/prov/issuer - pin it once, out of band)
# The split exists so BOTH replies fit the shaped private-lane budget: monitoring what code
# is served is itself IP-blind.
```

The four HTML pages additionally pin `anygas-web.js` via subresource integrity (sha384) —
view-source and compare the `integrity=` attribute against your own hash of the bundle.

## Rebuild the bundle yourself

The browser bundle is built from `public/anygas-web.src.js` (served, readable) with esbuild:

```
curl -sO https://nullchat.me/svc/build-package.json && mv build-package.json package.json
curl -sO https://nullchat.me/svc/build-lock.json && mv build-lock.json package-lock.json
npm ci
# npm ci installs the EXACT dependency tree the attested bundles were built from - TRANSITIVES
# included (top-level pins alone left @noble/ciphers, nested @noble/hashes 2.3.x/1.4.x, nested
# @noble/curves 2.3.x and @scure/base floating, so any upstream patch release changed your hash
# without any tampering). Both files are themselves attested in the transparency canon, so the
# dependency tree you rebuild from is part of the signed release. ethers is NOT bundled - the
# pages load ethers.umd.min.js separately (itself attested).
npx esbuild anygas-web.src.js --bundle --format=iife --platform=browser --target=es2020 --minify --outfile=anygas-web.js
npx esbuild anygas-web.src.js --bundle --format=esm --platform=browser --target=es2020 --minify --outfile=anygas-web.esm.js   # the ESM twin verify.html imports for in-page ML-DSA verification (R11)
sha256sum anygas-web.js anygas-web.esm.js   # compare both to /transparency
```

Minifier output is byte-stable for a given esbuild version; if your hash differs, diff the
unminified bundle (`--minify` omitted) against the served source before assuming tampering.

The Node agent SDK (`msg2-sdk.mjs`, `msg2-fs.mjs`) is served **unbuilt** — what you read is what runs.
