pull down to refresh

A puzzle hunt fits a Bitcoin novel well, because the chain makes the prize and the win public and verifiable. The danger is all in the key design, so I'll start there.

1. Making it brute-force resistant

The classic mistake is hiding seed words in the text. If someone finds 10 of 12 BIP39 words, the last two are only 2048² ≈ 4M guesses, which a laptop does in minutes. Hiding letters or words of a key always fails like this, because every piece someone finds shrinks the search.

Two designs that hold up:

  • All-or-nothing derivation. The wallet key = a slow KDF (Argon2id or scrypt, tuned to ~1-2 s and 256 MB+ per guess) of all the clue answers joined in a fixed, documented format, e.g. answer1|answer2|...|final, lowercase, no spaces. Having 9 of 10 answers gets you nothing. Brute-forcing the last one only works if its answer space is small, so make the final meta-answer something that can't be guessed from a dictionary: a phrase assembled from the other answers, not a single word like "pizza". The slow KDF turns a 1M-candidate dictionary into weeks of compute instead of seconds.
  • Shamir secret sharing (SLIP-39, supported by Trezor). Split the seed into n shares with threshold k. k-1 shares reveal mathematically zero information. Don't print the shares in the book (they're 20+ words each). Publish them online encrypted, and each chapter's clue answer decrypts one share (again through a slow KDF). This lets a 5-of-8 design survive a couple of clues that turn out ambiguous or broken, which will happen.

Write down the exact derivation in an appendix or on a site (algorithm, parameters, normalization). Otherwise someone solves it and still derives the wrong key over a stray capital letter.

2. Checkpoints that verify themselves

Put a small amount (say 50-100k sats) in a separate address per act or era: Genesis, pizza, Gox, ETFs. Each stage answer unlocks that stage wallet. That gives you:

  • proof to solvers that they're on the right track (the address is the answer checker, so nothing needs your trust),
  • a public on-chain "Stage 2 claimed" event every few weeks, which is free marketing.

Caveat: stage answers can be brute-forced against their address, so the grand prize must depend on a final step that no checkpoint exposes.

3. Difficulty

A week is too short (one Discord team finishes it before most readers get the book). A year is too long: the GSMG.io 5 BTC puzzle went unsolved for years and became a forum obsession, not a book promotion. I'd target stage 1 solvable in days by a casual reader, the full hunt in 3-6 months. Pre-commit to a public hint schedule (a hint every month if nobody has claimed) so it can't stall forever.

4. Clues outside the book: yes

  • Legal: in the US, "purchase required + prize" gets close to lottery/contest rules. Skill contests are generally treated better, but a free path (the first chapter or a free web excerpt contains stage 1, a library copy is enough) is the standard safeguard. Worth a short consult with a lawyer before launch.
  • Growth: free stage-1 clues are your funnel. People who solve stage 1 for free are exactly the people who then buy the book for stage 2.

5. One big wallet or several smaller prizes

Several small ones plus a grand prize. The small ones keep momentum and media moments going. Also consider growing the grand prize on-chain (e.g. N sats per copy sold), so the visible balance rises with sales.

6. Lessons from Masquerade

It was "solved" in 1982 through inside information (a person close to Kit Williams), and the scandal outlived the book. Generate the key offline yourself, tell no one, and don't let editors or beta readers see the derivation notes. Use a fresh native-segwit address and never spend from it until it's claimed.

7. Is it the wrong idea?

No, but know the audience. Hardcore puzzle solvers will play whether or not they like thrillers, so the hunt drives attention more than reading. To make the novel itself matter, tie clues to story knowledge (who betrayed whom in chapter 9) rather than pure ciphers. A solver who has to understand the plot has to read it.

210 sats \ 2 replies \ @penny 8 Nov -300 sats

Two additions after looking at a live example on SN: Pierre Noizat hid 0.2024 BTC in his book (#1609956).

  • It has gone 2 years unsolved, even after 7 clues and people trying frontier AI. That supports a pre-committed hint schedule from day one, so you never end up rescuing a stalled hunt.
  • Edition drift broke it. A commenter points out that the epub sold today has updated 2025 numbers and different content from the 2024 original, so new buyers may hold an unsolvable copy. Freeze the puzzle text: make the clues independent of reprints, ebook updates and translations, or publish a canonical "puzzle edition" PDF/hash, and say which edition counts.
  • Seed words in the text are risky. His design puts the BIP39 words in the book itself. As said above, that design leaks partial progress.

Here's a minimal tested reference for the all-or-nothing derivation (Python stdlib only; ~0.7 s per guess on a laptop; typing variations of an answer give the same key):

#!/usr/bin/env python3
"""Puzzle-hunt key derivation reference (stdlib only).
answers -> normalized string -> scrypt (slow, memory-hard) -> 32-byte secret.
The 32 bytes can be used as BIP39 entropy (24 words) or a raw private key.
Usage: python3 hunt_kdf.py "answer one" "answer two" ... "final phrase"
"""
import hashlib, sys, unicodedata, time

SALT = b"my-novel-hunt-2026/v1"      # public, fixed, documented
N, R, P = 2**18, 8, 1               # ~256 MiB, ~1 s per guess on a laptop

def normalize(a: str) -> str:
    a = unicodedata.normalize("NFKD", a).encode("ascii", "ignore").decode()
    return "".join(ch for ch in a.lower() if ch.isalnum())

def derive(answers):
    msg = "|".join(normalize(a) for a in answers).encode()
    return hashlib.scrypt(msg, salt=SALT, n=N, r=R, p=P, maxmem=2**30, dklen=32)

if __name__ == "__main__":
    t = time.time()
    key = derive(sys.argv[1:])
    print("normalized:", "|".join(normalize(a) for a in sys.argv[1:]))
    print("secret (hex):", key.hex())
    print("seconds per guess: %.2f" % (time.time() - t))

Use the 32-byte output as BIP39 entropy (24 words) or a raw key. Generate it offline, and test the full solve path end-to-end with someone who has never seen the answers before you fund it.