ZYCORD docs
English
ZycordDocsVerifying a download

Verifying a download

A reproducible build asserts this came from this source, and anyone can check — which is the assurance an anonymously published project can offer in place of a code-signing certificate. Here is how to collect on it.

Three checks, and they answer different questions. Run them in this order; each one is worth less without the one before it.

CheckWhat it provesWhat it does not
ChecksumThe file was not altered in transit.Anything about who produced it, or what is in it.
SignatureThe checksum list came from the holder of the project key.That the key belongs to anyone you have reason to trust.
RebuildThe binary contains this source and nothing else.That the source is honest — read it, or read the reviews.

The checksum#

curl -fsSLO https://zycord.com/releases//download/v<version>/SHA256SUMS.randomx
sha256sum --check --ignore-missing SHA256SUMS.randomx

# on macOS:
shasum -a 256 --check --ignore-missing SHA256SUMS.randomx
--ignore-missing is not optional, and not leniency

Releases now ship only the -randomx archives, so SHA256SUMS.randomx is the list that covers what you downloaded; SHA256SUMS.deb and SHA256SUMS.desktop cover the Debian packages and the desktop wallet. Each covers several files and you downloaded one. Without the flag, the five you do not have are reported as FAILED open or read and the command exits non-zero for a perfectly good download, on the very page that tells you a mismatch means a compromised binary. A check whose normal result is a failure is a check people learn to ignore.

The signature#

No signature is published yet, and this section describes the procedure for when one is

Releases currently ship zycord-release-key.asc — the public key — but nothing signed with it beyond the updater's manifest: none of the checksum files has a detached signature, and none is clearsigned. The gpg --verify below therefore fails today with a missing file, and that is the release's gap rather than your mistake. Until it is closed, the check that actually carries the trust argument is the rebuild: it needs no signature at all, because you produce the bytes yourself and compare.

When a checksum signature is published it is made with the project key. The full fingerprint is:

E724 39CE DD85 11F9 D607 550B 87FD 60D5 EB4A 0B29

It is published in the whitepaper header, in the archived deposit, and in the genesis announcement. It never rotates silently: a key that changes without a signed statement from the old one is indistinguishable from a compromise, and should be treated as one.

The fingerprint is the anchor, not the key file

Get the key from wherever is convenient and then check what you got against the line above. A key file whose fingerprint is that one is the project key no matter which host handed it to you, and one whose fingerprint is anything else is worthless no matter how trustworthy the host looked.

Three places carry it. Any of them will do:

# 1. The repository, if you have a clone. No network, no keyserver.
gpg --import packaging/zycord-release-key.asc

# 2. The release page, as an asset beside the archives.
curl -fsSLO https://zycord.com/releases//download/v<version>/zycord-release-key.asc
gpg --import zycord-release-key.asc

# 3. A keyserver. Use this one.
gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys E72439CEDD8511F9D607550B87FD60D5EB4A0B29

Then confirm what entered your keyring, before you rely on it:

gpg --fingerprint E72439CEDD8511F9D607550B87FD60D5EB4A0B29
Do not use keys.openpgp.org for this key

It is the default keyserver in several GnuPG builds, and it serves a stripped copy: the bare public-key packet, with no user ID and no self-signature. GnuPG refuses that copy — gpg: key 87FD60D5EB4A0B29: new key but contains no user ID - skipped — and the key never enters the keyring, so the verify below fails with what looks like a bad signature and is in fact a missing key. That server strips user IDs by policy until an address is confirmed through it, which is not something a pseudonymous project can do.

gpg --verify SHA256SUMS.randomx.asc SHA256SUMS.randomx

Today this reports No such file or directory, because no release has published that file yet. See the note at the top of this section.

gpg will add that the key is not certified with a trusted signature. That is expected and it is not a failure. It means you have not told your keyring that you personally vouch for this key, which is exactly what the fingerprint is there to settle. What matters is that the signature verifies, and that the key it verified against is the fingerprint printed above.

The whitepaper is signed too#

The PDF served from this site has a detached signature beside it, made by the same key:

curl -fsSLO https://zycord.com/assets/zycord-whitepaper.pdf
curl -fsSLO https://zycord.com/assets/zycord-whitepaper.pdf.asc
gpg --verify zycord-whitepaper.pdf.asc zycord-whitepaper.pdf

The rebuild — the check that actually replaces a certificate#

A hash tells you the file was not altered in transit. It says nothing about what the publisher put in it. This does:

git clone https://gitlab.com/zycord-group/zycord-node.git
cd zycord-node
git checkout v<version>
make build
sha256sum bin/zcd bin/zycordd

Compare against SHA256SUMS.binaries from the release. They must match exactly. If they do, the binary you downloaded contains this source and nothing else — a claim no certificate has ever made about anything.

The Go toolchain is part of what you are reproducing#

It is go1.26.2. The same source compiled by two different Go releases is two different binaries; that is not a subtlety, it is measurable — this repository's own release workflow once recorded three separate SHA-256 values for one commit and one set of flags, one per Go version. So make build pins GOTOOLCHAIN=go1.26.2 and stops with an explanation if it cannot use that toolchain, instead of producing a mismatch that looks like the tampering this whole page is here to detect.

Two consequences worth having:

  • You do not have to trust the version written above. The released binary states it: go version -m zcd prints the toolchain that built it, next to the module and the build flags. If that line and the Makefile's pin ever disagree, the binary was not built the way this page says.
  • Checking out an older tag checks out its pin along with its source, so rebuilding an old release keeps working after the project moves to a newer Go.

Line endings are part of the source here#

wallet/webui embeds the wallet frontend with //go:embed, so those files' line endings are bytes in the binary: a checkout converted to CRLF builds a different zcd from the same commit — a mismatch with an innocent cause and a frightening appearance. A .gitattributes carrying * text=auto eol=lf gives LF in the working tree on every platform and every git configuration, so a fresh clone gives the same answer everywhere.

Two cases it does not cover: rebuilding a tag older than that file (set core.autocrlf=input before checking out), and an existing clone made with core.autocrlf=true, which does not heal on pull.

The -randomx archives are not reproducible, by construction

RandomX is C++, so a cgo build carries a system C toolchain into the output and nobody can rebuild it byte for byte. Those archives are checksummed by SHA256SUMS.randomx and are deliberately absent from SHA256SUMS.binaries, which lists what a build you make from source should hash to. This is the one place where the tier that joins a network and the tier that is attested do not overlap — see the two-tiers table under Installation. Stating it is the point: it is your decision rather than an assumption made for you.

Check the implementation against the protocol#

Separately from checking a binary's provenance, you can check that it implements the protocol. The golden vectors are the protocol; an independent implementation that passes them is a peer, not a fork.

zcd vectors            # check this build against spec/vectors
zcd genesis --testnet  # rebuild block 0 and print its id
zcd params --testnet   # print the frozen parameters

zcd genesis is the command that matters most: a launch announcement commits, weeks in advance, to the code tag, the parameter hash and the genesis id — and this rebuilds all of them in milliseconds, from source, on any machine. The public testnet's values are on the testnet page; a build that prints different ones is a build on a different chain, and it will not connect.

Independent attestations#

One person rebuilding a tag proves the tag is reproducible. Several strangers rebuilding it and signing the result proves rather more, and it is the pattern serious projects converged on for exactly this reason. Rebuild a tag, then post the digest you got and the tag you built in the announcement thread — including when it does not match, which is the case worth hearing about.