mint: enforce the admin lever's loopback bind and refuse the placeholder pool key #42
Loading…
Reference in a new issue
No description provided.
Delete branch "agent/admin-bind-and-pool-key"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Two startup gaps in the mint's epoch machinery are closed: the epoch admin lever can no longer be bound off loopback by a config typo, and the generator-point placeholder is refused as a pool identity while the prod config ships no key at all. Every refusal was witnessed by starting the real binary against the bad state, before any file is created. The third issue in this group, #36 (epoch store durability), is superseded by vnprc/hashpool#41 moving the store into the mint database and is not part of this change.
#34 — admin lever binding
admin_listenmust be a loopback address or the mint refuses to start, naming the setting and the value.admin_allow_non_loopback = trueis the explicit opt-in and logs a warning at startup that the unauthenticated lever is exposed.just rotate-epochdid not exist on master (the earlier PR described it but the recipe was lost). It is added and readsadmin_listenfrom the dev config (either quote style, leading whitespace allowed), so moving the port never needs a recipe edit; it exits non-zero when the lever refuses (409 while the current epoch is provisional) or is unreachable, with the JSON body still printed.#35 — placeholder pool key
validate_pool_pubkeyrefuses the secp256k1 generator point by value, and the prod config shipspool_pubkeycommented out so an unedited prod file fails on the missing key. The validator catches a copied dev value; the missing key catches an untouched prod file. The error names the config file it loaded.sha256("hashpool dev pool identity"), pinned by a test that derives it and compares against the committed file.receive_addressstays derived from the generator point; it is an address, not an identity, and has its own pinning test.[hashpool_mint]validation runs before the mint database is opened, so a refused start creates nothing.Risk
R2: startup validation and a justfile recipe; no persistent-state handling, no money path. The worst credible failure is refusing a valid configuration at startup, which is loud and names the setting. Rollback is a revert.
Validation
cargo test -p mint: 69 + 5 pass on top of #41's suite, including the admin validator (loopback v4 and v6,0.0.0.0refused naming the key, opt-in accepted, unparseable refused) and the generator-point refusal plus the dev-key derivation test.pool_pubkey; prod with the key unset refused on the missing key;admin_listen = "0.0.0.0:3339"refused with the opt-in named; nothing created in the database directory by any refusal.just rotate-epochrotates on the default port and, after changingadmin_listento 3340 and restarting, on the new port with the recipe untouched; the recipe exits 22 on a 409 and 7 on a refused connection against a throwaway responder.Closes vnprc/hashpool#34. Closes vnprc/hashpool#35. Refs vnprc/hashpool#13. Rebased onto master after vnprc/hashpool#41 merged.
config/prod/mint.config.toml shipped pool_pubkey set to G, a structurally valid compressed pubkey that namespaces every epoch under a well-known constant instead of a real pool identity. Both halves, for the two ways this goes wrong: - naming::validate_pool_pubkey now rejects G by value, so a copied dev value is caught even if it is otherwise well-formed. - the prod config ships pool_pubkey commented out with a TODO(operator), so an unedited prod file fails on the pre-existing "pool_pubkey is required" error instead of ever reaching the generator-point check. The dev config needed a replacement key that isn't G: pubkey of sha256("hashpool dev pool identity"), pinned by a naming.rs test that derives it independently and compares against the committed config. Updated every test constant that used G as a pool pubkey to the new dev key. receive_address is untouched — it is an address, not an identity, and stays derived from G per its own existing test. pool_pubkey (and every other [hashpool_mint] setting) is now validated before setup_mint opens the database, wrapped with the config file path it loaded, so a refused start never leaves a freshly created mint.sqlite behind. Refs vnprc/hashpool#35.Verification
Verifier: vnprc-verifier on rust-dev, scratch clone /home/vnprc/v42 (cloned into the session scratchpad, moved to this short path because bitcoind's unix socket path exceeded the length limit under the scratchpad)
Head:
a03ba32736Base: master ata5b56100e4(branch point92dddda2; master gained one unrelated commit since, no file overlap;git merge-tree --write-treeis clean:74657759)Score: R2 — the worst credible failure after validation is a loud startup refusal of a valid config (for example an
admin_listenwritten as a hostname, which the new socket-address parse rejects), recovered by a straight revert; all validation runs before the mint database opens, so a refusal writes nothing. The security-boundary signal is a reason to walk, and the change only tightens the boundary. PR body says R2.Rung: R2 on vnprc/hashpool is agent merge, veto window waived (vnprc/hashpool#49, owner comment 2026-10-21)
Checks: no feature map and no README check list, so the checks are the PR body's own:
devenv shell -- cargo test --manifest-path roles/Cargo.toml -p mint --no-fail-fastata03ba327— pass, 69 + 5, output belowCoverage (no feature map: the PR body's Validation commands rerun at the head):
cargo test -p mintat headpool_pubkey, empty db dirpool_pubkey; nothing createdError: loading <file>/Caused by: [hashpool_mint] pool_pubkey is the secp256k1 generator point…; db dir still emptypool_pubkeyunset, empty db dir[hashpool_mint] pool_pubkey is required; db dir still emptyadmin_listen = "0.0.0.0:3339", empty db diradmin_listen 0.0.0.0:3339 is not a loopback address… Set [hashpool_mint] admin_allow_non_loopback = true to opt in.; db dir still emptyadmin_allow_non_loopback = trueadmin_allow_non_loopback is set… reachable from the network addr=0.0.0.0:3339, thenUsing database path, db files created; killed while waiting on bitcoindgenesis epoch opened unit=hash_034dcb73…e66a_17 height=17;/v1/keysetsand/v1/infonut4/nut5 show that unitjust rotate-epochon the default port{"height":17,…,"unit":"hash_034dcb…_17_1"}; logepoch opened … source=Manual; nut4 now lists_17_1onlyadmin_listenset to 3340, mint restarted, recipe untouchedEpoch admin listening on 127.0.0.1:3340, 3339 closed;just rotate-epochexit 0 with unit_17_2; persisted epoch_17_1resumed across the restartcurl: (22) … 409, body{"error":"current epoch is provisional; manual rotation refused"}, recipe exit 22curl: (7) Failed to connect to 127.0.0.1:3339, recipe exit 7cargo test -p minton the merged tree (mastera5b56100+ head, local commit of tree74657759)Gaps: none. Not rerun: the cold-review bullet (a review, not a command).
Observation for the author, not a stop: an existing dev checkout's
.devenv/stateholds epochs named under the old generator-point key; after this change the mint resumes those records as-is and names new epochs under the new dev key, sojust clean cashuis the way to a consistent dev state. Disposable state only.Decision: merge — score R2 is on the agent-merge rung with the veto window waived; every Validation command reran green at the head and the checks pass on the merged tree.
Block 1 —
devenv shell -- cargo test --manifest-path roles/Cargo.toml -p mint --no-fail-fastata03ba327(devenv 2.1.2, rustc 1.86.0):Block 2 —
roles/target/debug/mint -c <config> -g config/shared/pool.tomlata03ba327, each db dir empty before and after unless noted:Block 3 — live dev stack (
devenv up) ata03ba327in /home/vnprc/v42:Block 4 — recipe exit status against a throwaway responder (socat returning 409) and a closed port:
Block 5 —
cargo test -p minton the merged tree (local commit 3e214890 of tree74657759=git merge-tree --write-tree a5b56100 a03ba327, never pushed):Merged as
3547740516. Revert: git revert a5b56100e4e3712b8439e96409fa47510ff7a799..3547740516278a624fbbccf303e82de2f2b9f54b