Repository navigation
fix: pin the published artifacts, and decode addresses instead of shape-checking them - #77
Merged
Merged
Conversation
moul
force-pushed
the
fix/pin-artifact-and-validate-addresses
branch
from
September 11, 2026 09:45
311ab66 to
5190b1e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two gaps in the verification layer, found while checking counterparty addresses against the sheet. Both are in the class this repository exists to prevent: something that every check calls correct, and isn't.
No artifact changes.
genbalance.txt.gz,balances.txt.gz,README.mdandunrestricted.txtare byte-identical after a full rebuild — total supply still 1,332,999,998.328067 GNOT across 3,262,473 rows. Rebased ontomainafter #73 and #76 landed.1. The published
.gzis not byte-pinned, and macOS silently writes a different onemkgenesis/balances.txt.gzis fetched by raw URL from outside this repo —ABOUT.mdcalls it a public contract. Its bytes depend on whichgzipbuilt it:(measured against the pre-#73 artifact; the committed hash has moved since, the behaviour has not)
Same
gzip -n, same plaintext, different deflate stream. Nothing here could see it: the golden test decompresses before comparing (golden_test.gocomparesbalances.txt, never the container),crosscheckdecompresses too, and CI runsubuntu-latest, where gzip is always GNU.The docs made it worse by asserting the opposite in four places — including a gotchas row that gave the wrong diagnosis (
you are on a build without gzip -n, when the Makefile always passes-n), anddocs/reproduce.mdLevel 1 telling an external verifiergit status # should be cleanfollowed by "If yours differ, that is worth an issue." Someone on macOS doing exactly the independent verification this repo is for would conclude the genesis had been tampered with.This PR:
SHA256SUMS, committed and asserted by a newmake checksums, wired intomake verifyand CI.make supplystill prints the hashes — but printing is not verifying, and nothing failed when they changed. This is also the only check that ever sees the gzip container.make toolsrefuses to run on a non-GNU gzip, naming the problem and the fix instead of letting it surface as a confusing diff.make checksums-updatefor when the artifacts are meant to change.Makefile,docs/reproduce.md(×3) andmkgenesis/ABOUT.md.2. Sheet addresses were shape-checked, never decoded
TestHardcodedAddressesAreValiddoes real validation — but only of the constants, which change rarely and under review. The hand-written sheets, where counterparty addresses arrive by email and get pasted in days before genesis, got:parseRow()— no address validation at all; everything before=was taken as the addressaddrREinunrestricted.go—^g1[0-9a-z]{38}$, shape onlyTestGenesisFileTotal/TestInvestorDistributionsMatchFile— shape onlyThat is backwards from where the risk is. A transposed character preserves the shape and breaks only the checksum. Before this PR, on a branch with a two-character transposition in a real counterparty address:
Full suite green, invalid address in both the shipped artifact and the §126 whitelist. After:
Being precise about severity: this was never silent fund loss.
gnolandrejects it downstream —Balance.Parse→crypto.AddressFromBech32→ checksum. But "downstream" meant at the genesis ceremony rather than in CI seconds after the paste. This moves the failure to where it's cheap.This PR adds
mkgenesis/addr.gowithaddrKey/checkAddr— deliberately the same definition asallocate'saddrKey, since both arepackage mainand the helper can't be shared, but "a valid address" must not fork between the stage that produces rows and the stage that merges them.checkAddralso requires canonicalg1…spelling, so acosmos1…form of the right 20 bytes is rejected too: consumers compare these as strings.Wired into the hand-written sheet readers, the whitelist generator, and
TestGenesisFileTotal.The one deliberate asymmetry
accumulatevalidates the hand-written sheets and not genbalance. Measured, not assumed: decoding all 3.26M rows costs ~3.4s, which would land on every build and every golden test. Those rows are produced byallocate'saddrKeyand are canonical by construction.The shipped artifact is still decoded in full — once, in
TestGenesisFileTotal, which already walks every row. That moves theallocatesuite from ~10s to ~18s, which seemed the right place to spend it.TestGenbalanceIsNotRevalidateddocuments the asymmetry so it doesn't read as an oversight.Test fixtures
The old placeholders (
g1aaa,g1shared, …) are not valid bech32, so they no longer parse — which is the check working. Replaced with real 20-byte payloads (0x11…,0x22…,0x33…repeated) that have correct checksums, plustestAddrTransposedfor the regression tests.Verification
CI green, including the new checksum step. Worth noting how the first CI run failed: I had branched before #73 merged, so
SHA256SUMSdescribed the older artifacts andmake checksumscaught the staleness immediately — which is the check doing exactly its job, on its first outing.SHA256SUMSdoes have to be regenerated (make checksums-update) in any PR that legitimately changes an artifact, and it will conflict on rebase when two such PRs are in flight. That is the intended cost: a conflict is a prompt to re-verify, where today the file just changes.Full rebuild from scratch (
allocate→mkgenesis re→ readme) leaves every committed artifact untouched;git statusshows only the source changes in this PR.go vet ./...clean.Not in this PR
gnolang/gno'smisc/deployments/gnoland1/gen-genesis.sh:45still pinsBALANCES_GZ_URLto9dec38a4(2026-03-12, 27 commits back = 1.002B GNOT vs today's 1.333B) and does no sha256 check on the download. Correct for reproducing gnoland1's historical genesis; a hazard if reused for mainnet.SHA256SUMShere gives that script something to verify against — the other half belongs in agnolang/gnoPR.