2026-08-11 · testnet4 · everything below is publicly verifiable
Yesterday a browser game and a dice house — separate sites, separate repos, zero communication between their servers — moved money between themselves correctly. The player carried it. A Bitcoin block rolled the dice. The whole history is signed by one key and notarized on-chain, and you can check every claim in this post yourself.
This is a write-up of the pieces and why the shape matters.
Everything runs on a single secp256k1 keypair — the same curve nostr and taproot share. One key is, simultaneously and demonstrably:
| role | mechanism |
|---|---|
| identity | did:nostr:47779362…1a03 (the x-only pubkey) |
| authentication | NIP-98 signed requests |
| a Bitcoin address | the pubkey is a P2TR witness program: tb1pgamexch…827x |
| a game account | the game (Tideholm) keys players by the did |
| a state signer | every balance move is a Schnorr-signed transition |
| a transaction signer | the anchors below were signed in the browser |
No conversion, no derivation paths, no second credential. Funding the address is the entire provisioning step.
The game lets a player peg gold out of its economy into a sealed balance:
a linear chain of signed transitions — {did, prev, delta, next, sig} — the
trail. The server stores it but cannot forge it; the signature covers the
exact bytes. The player's real balance is wherever the trail says it is, and
any app that can verify a Schnorr signature can honor it.
The player's actual trail, as of this writing (six transitions, one day):
[0] 0 +100 → 100 sealed
[1] 100 −100 → 0 pegged back ⚓ anchor 1
[2] 0 +100 → 100 sealed
[3] 100 +100 → 200 sealed ⚓ anchor 2
[4] 200 −100 → 100 staked at the tavern ⚓ anchor 3
[5] 100 +196 → 296 won — block 147,862 rolled 84
The trail is anchored to Bitcoin with
BlockTrails: each state tweaks the key —
tᵢ = SHA256(stateᵢ) mod n, Pᵢ = P₀ + Σtⱼ·G — and spending the output at
Pᵢ₋₁ to create Pᵢ is the commitment. No OP_RETURN, no data on chain at all:
just addresses, each one a cryptographic consequence of the state history.
Three real anchors, signed non-custodially by a button in the browser:
| spends | creates | tx | |
|---|---|---|---|
| 1 | base (259,951 sat) | P(1) + change home | 3ae578e4… |
| 2 | P(1) (10,000 sat) | P(2) | c1bb1326… |
| 3 | P(2) (9,889 sat) | P(3) | 9de26665… |
Anchors 2 and 3 are the interesting ones: chained advances, where the signing key and destination were reconstructed from nothing but the public key and the trail itself. The recovery story is exactly one line: npub + trail ⇒ every address, forever. Only a small float rides the tweaked chain (~10k sats); the treasury stays at the recognizable base address. Each anchor costs ~111 sats.
The tavern is a static gh-pages page — no server. Its classic mode is commit–reveal, which a client-only page famously cannot run honestly (it holds its own seed; its README says so in bold). The Tide mode fixes that with a seed nobody holds:
bet at chain height H, with a player-chosen mark
seed = hash of block H+1 ← does not exist yet; nobody can choose it
roll = 1 + int(sha256(seed | mark)[..13]) mod 100
The entropy doesn't exist at bet time, so there is nothing to commit to and nothing to grind. The mark salts the roll so bets riding the same block get independent dice. The bet in trail entry [5]: stake 100, target "above 50", block 147,862 rolled 84 → payout 196 (1.96×, 2% house edge, priced by a ~230-line pure module with no dependencies, no clock, no network). The bettor holds the mark and can publish it; with it, anyone recomputes the roll from public chain data.
The part we think is genuinely new — or rather, genuinely old (bearer instruments, Chaum, "the browser is the wallet") and finally small:
- The game links out:
tavern/?did=…&seal=…&return=…— a query string. - The tavern signs stakes and payouts with the player's key (entered once per origin, never leaving the browser) onto a slip — a chained segment of signed transitions in localStorage.
- "Settle up" links home:
game/?tavern=<base64 slip>— a query string. - The game's single sync endpoint verifies every Schnorr signature server-side, checks the chain (starts at the current balance, never below zero, key must be the did), skips any move already on the trail (idempotent — a slip can be re-presented safely), and applies the rest.
The two servers never exchanged a byte. The player is the transport; the
mathematics is the trust. Total integration surface on the game's side: one
<a href> and one ~40-line endpoint.
- The address is the pubkey: bech32m-encode the x-only key
47779362…1a03as a v1 witness program, hrptb→tb1pgamexch…827x. - Walk the anchors: each tx above spends the previous trail tip. Derive
Pᵢyourself:state = {"app":"tideholm","did":…,"seq":n,"tip":"sha256:<hash of the canonical trail prefix>"}, tweak, add, encode. - Recheck the roll: block 147,862's hash + the bettor's mark →
sha256(hash|mark)→ first 13 hex chars mod 100, +1 → must be 84. The tavern's verifier does it offline — leave the commitment blank for a block-seed round.
- Payout authorization. The sync endpoint accepts any self-signed move, so a player could credit themselves — fine for a two-player testnet, disclosed and tracked. The elegant fix is that Tide payouts are provable from public data, so the server can verify the game itself rather than trust a signature. Alternatively apps countersign their payouts.
- One writer per trail. Linear chains want a single writer; two apps writing concurrently need a rule (or CAS on the coming pod store).
- Durability. Trails currently live in browsers and one server; the next step moves them to the owner's pod (the store seam is already built), which changes where state lives, not any of the shapes above.
- Testnet. All of it. That's the point, for now.
| piece | what | size |
|---|---|---|
| tidegate | seal, stores, NIP-98, taproot derivation, browser anchorer | a handful of small ESM files, gh-pages |
| tavern | wager maths (pure) + static demo/verifier + Tide + sealed mode | ~230-line core, 93 tests |
| blocktrails | the reference chained-tweak crypto | used as a library |
| Tideholm | the game: one link out, one endpoint in | ~2 lines UI + ~40 lines server |
No frameworks, no build steps, no dependencies beyond noble/scure crypto. Static pages, one small game server, one keypair, one chain.
Player guide: how to do all of this yourself.