216 lines
8.2 KiB
Markdown
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.
|