morphit/docs/VERIFY-YOUR-DOWNLOAD.md

216 lines
8.2 KiB
Markdown

# Verify your Morphit download
Morphit's source is public and pushed to **three independent git hosts**
— the project's own Forgejo, plus GitHub and Codeberg (Forgejo mirrors
every commit and signed tag to them automatically) — so no single host
can quietly disappear or tamper with it. This page shows you how to
**prove** that the code you got is the genuine, unmodified release.
Pick whichever matches how you got the code:
| You have… | Verify with | Proves |
| --- | --- | --- |
| a **git clone** (from any mirror) | `git verify-tag vX.Y.Z` | the release tag was GPG-signed by Morphit's key |
| the **source tarball** (from the release page) | `verify-download.mjs` (+ `git verify-tag`) | the bytes match what `@morphit` anchored on-chain, tied to the GPG-signed release tag |
Both trace back to the **same GPG key**, whose fingerprint `@morphit`
publishes on the Blurt chain (a `morphit_release_v1` operation with a
`distribution` block). The expected fingerprint and hash come from the
**blockchain**, not from the host you downloaded from — so a malicious
mirror can't serve you a bad file *and* a matching "expected" value on
its own web page.
---
## Option A — verify a git clone (the signed tag)
If you cloned the repo from **any** of the three mirrors, verify the
release tag's signature. First import Morphit's public key (once) and
confirm its fingerprint against the one on the project's `/security`
page:
```sh
gpg --keyserver keyserver.ubuntu.com --recv-keys <FINGERPRINT>
```
Then, in your clone:
```sh
git fetch --tags
git verify-tag vX.Y.Z
git checkout vX.Y.Z
```
You want **`Good signature`** from Morphit's key. Because the commit and
the signed tag are byte-identical across Forgejo, GitHub, and Codeberg,
a tag that verifies is the genuine release no matter which mirror you
cloned from. That's the fully decentralized path — it needs no release
assets and no single host.
---
## Option B — verify the source tarball
The release page carries the source tarball, its checksum, and the
anchor CI recorded on-chain:
- `morphit-vX.Y.Z.tar.gz` — the source
- `morphit-vX.Y.Z.tar.gz.sha256` — its SHA-256
- `distribution-anchor.env` — the anchor CI wrote (the SHA-256 and the
signing-key fingerprint that also went on-chain)
- `morphit-vX.Y.Z.tar.gz.asc` — a detached GPG signature, **present only
if the project signs tarballs in CI**. The release *tag* is always
GPG-signed regardless (Option A), so tarball signing is optional.
---
### Step 1 — cross-check the on-chain anchor (always available)
`@morphit` publishes each release's hash, signing-key fingerprint, and
mirror list onto the Blurt chain (a `morphit_release_v1` operation with
a `distribution` block). The bundled verifier reads that anchor
**directly from a Blurt node** and compares it to your file:
```sh
node scripts/verify-download.mjs morphit-vX.Y.Z.tar.gz
```
or pin the version you expect:
```sh
node scripts/verify-download.mjs morphit-vX.Y.Z.tar.gz --version X.Y.Z
```
It prints your file's SHA-256, fetches the on-chain anchor, and tells
you plainly whether they **match**. On a match it also shows the **GPG
fingerprint** the release was signed with (feeds Step 2), the mirror
repos, and — **if the release was pinned to IPFS** — a content-addressed
**IPFS CID** (the CID *is* the hash, so no gateway can serve altered
bytes under it).
Because the expected hash and fingerprint come from the **blockchain**,
not from the host you downloaded from, a malicious mirror can't serve
you a bad file *and* a matching "expected" value on its own web page.
Note the two artifacts differ: this checks the **canonical release
tarball** from the release page. A source archive auto-generated by
GitHub or Codeberg from the tag has different bytes and won't match this
hash — for those, use **Option A** (`git verify-tag`) instead.
If it reports a **mismatch**, do not trust the download.
The verifier is deliberately tiny and dependency-free (only Node
built-ins). Read it — it's `scripts/verify-download.mjs`, about two
hundred lines — so you don't have to take even *it* on trust.
---
### Step 2 — confirm the signing key
Step 1 ties your tarball's bytes to the fingerprint `@morphit` anchored
on-chain. To confirm that fingerprint really is Morphit's key, verify
the signed **tag**: clone any mirror and run **Option A**
(`git verify-tag vX.Y.Z`). A `Good signature` from that same fingerprint
closes the loop — the bytes match the chain, and the chain's key signed
the tag.
If the release page also carries a `.asc`, you can additionally check a
signature directly on the bytes:
```sh
# import the key once, and confirm its fingerprint against /security
gpg --keyserver keyserver.ubuntu.com --recv-keys <FINGERPRINT>
gpg --fingerprint <FINGERPRINT>
# then verify the tarball
gpg --verify morphit-vX.Y.Z.tar.gz.asc morphit-vX.Y.Z.tar.gz
sha256sum -c morphit-vX.Y.Z.tar.gz.sha256
```
A "Good signature" warning about the key not being *certified* is fine —
that just means you haven't personally signed the key; the fingerprint
match is what matters. If any check fails, **stop** — the file is not
what Morphit published.
### Pick a Blurt node
By default it tries a few public nodes. To choose your own:
```sh
MORPHIT_RPC=https://rpc.beblurt.com node scripts/verify-download.mjs morphit-vX.Y.Z.tar.gz
```
---
## If a host is unreachable
The code lives on three git hosts, so if one is down, clone from
another and use **Option A** (`git verify-tag`) — the signed tag is
identical everywhere:
```sh
git clone https://codeberg.org/agorise/morphit.git # or the GitHub / Forgejo URL
cd morphit && git verify-tag vX.Y.Z
```
If the release was also pinned to IPFS, the verifier prints its CID. That
CID names a small **release directory** — the signed tarball plus its
`.sha256`/`.asc`, the release notes, and a `metadata.json` — so you can
browse it, read the notes, or pull the exact bytes and re-run the Option B
checks:
```sh
# list what's in the release directory
ipfs ls <CID>
# fetch the signed tarball out of it (its bytes are content-addressed, so
# this is the same file the on-chain SHA-256 covers)
ipfs get <CID>/morphit-vX.Y.Z.tar.gz -o morphit-vX.Y.Z.tar.gz
```
No `ipfs` installed? A raw CID resolves on **any** public gateway (the CID is
the hash, so the gateway cannot serve altered bytes):
```sh
curl -fsSLo morphit-vX.Y.Z.tar.gz https://ipfs.io/ipfs/<CID>/morphit-vX.Y.Z.tar.gz
curl -fsSL https://ipfs.io/ipfs/<CID>/metadata.json # version + sha256 + notes
```
Every Morphit instance re-hosts the current release over its own IPFS (Kubo)
node, so this content stays reachable without depending on any commercial
pinning service.
That CID is immutable — it only ever names *that* release. To always fetch
the **latest** release over IPFS, Morphit also publishes a stable **IPNS
name** (a `k51…` string, shown on the on-chain anchor as `ipns_name` and on
the download page). It resolves through a **w3name-aware** gateway —
`dweb.link` or `w3s.link` — to the newest release directory, which always
contains a stable-named `morphit-latest.tar.gz`:
```sh
# always the latest signed tarball, by name instead of by CID
curl -fsSLo morphit-latest.tar.gz https://dweb.link/ipns/<name>/morphit-latest.tar.gz
# ...then run the same Option B checks on it (the on-chain SHA-256 tells you
# which version you actually got). Browse https://dweb.link/ipns/<name>/ for
# the versioned filename, release notes, and metadata.json.
```
Use `dweb.link` or `w3s.link` for the **IPNS name** — not `ipfs.io/ipns/…`. The
name is published via w3name, which those gateways resolve; the DHT-only
`ipfs.io/ipns` path does not (a raw `ipfs.io/ipfs/<CID>` fetch works fine — this
caveat is only for the `k51…` name).
IPNS is a convenience for *discovery* only — it is a mutable pointer, so a
copy fetched this way is still only trustworthy once it passes the
`source_sha256` + GPG checks above. The immutable `ipfs_cid` and the signed
tag remain the verification anchors.
Because the signed tag, the GPG signature, and the on-chain hash are all
host-independent, code that passes verification is the genuine release
no matter where you pulled it from.
---
## Exit codes (for scripting `verify-download.mjs`)
`0` verified · `1` MISMATCH (do not trust) · `2` usage error ·
`3` couldn't reach any Blurt node · `4` no anchor found on chain.