Verification
The call
import { verifyPuzzle, requirePuzzle } from "@agntn/puzzles";
const result = verifyPuzzle(await requirePuzzle("b1000/1"));
result.verified; // true
result.derivedAddress; // "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH"
result.expectedAddress; // the same
result.privateKey; // "00…01", the hex the derivation started from
verifyPuzzle(puzzle) is synchronous. It reads secretOf(puzzle.keyData()), turns the secret into a raw key, derives the address the way the chain does it, and compares. addressesEqual lowercases Ethereum and compares everything else byte for byte.
Collections have the asynchronous form, b1000.verify(1) and b1000.verifyById("b1000/1"). They import the signing crypto on first use. That keeps secp256k1 out of the CLI's start: puzzles collections doesn't need it, so it doesn't pay for it.
Failures are values
const result = verifyPuzzle(await requirePuzzle("b1000/71"));
result.verified; // false
result.unavailable; // true
result.error; // "Puzzle has no private key"
Three answers, one shape. verified: true with the derived address. verified: false, unavailable: true when there was nothing to derive: no key, an encrypted key without its passphrase, a mini key. And verified: false, unavailable: false with an error when a key exists and derives the wrong address. That last one is what the data gate exists to catch. Nothing throws for a bad record. Why not? Because puzzles verify --all would crash on the first unsolved puzzle.
| Secret | Verified how |
|---|---|
hex | The key as written, with the public key format the record declares, compressed by default |
wif | Decoded first. The WIF says whether the key is compressed, so the record doesn't have to |
seed with a phrase | BIP39 to BIP32 along the record's path. No path, or a Required passphrase, means unavailable |
encrypted | Unavailable here. The data gate decrypts BIP38 payloads with the passphrase when the record has one, so an encrypted key with a known passphrase is still checked, just not at runtime |
mini | Unavailable. Nobody has asked yet |
Bitcoin and Litecoin derive P2PKH and P2WPKH, Ethereum its Keccak address, Decred its BLAKE-256 P2PKH. Arweave and Monero have no key to address derivation here. A key there stays unavailable too, and the data gate treats "nothing to contradict" as a pass, not a failure.
What this isn't
A verified puzzle is one whose published key derives its stored address. It says the record is internally consistent. It doesn't say the address holds anything, that the key was the solver's, or that the puzzle is worth your CPU. b1000/1 verifies and holds nothing. It was solved in 2013.
CLI and tools
puzzles verify b1000/1 # OK b1000/1
puzzles verify --all --quiet # exit 1 when any puzzle with a key fails, nothing printed
puzzles verify --all prints one line per puzzle, OK, SKIP or FAIL. It exits 1 only on a FAIL. The CI gate runs it with --quiet. puzzles_verify is the same answer for a model: b1000/1: verified, derives 1BgG…, or unverifiable (Puzzle has no private key). isError stays unset either way.
Lookups and views
get and requirePuzzle by identifier. selectPuzzles by collection and status. stats and the dataset snapshot with its deterministic data version.
Balances
puzzle.balance() through the explorers provider for its chain. The one call that leaves the process. Base units as bigint. API keys never reach an error.