TL;DR: NostrDice is a provably fair game built on Nostr and Lightning.

Why build NostrDice

Nostr has been around for a while now, but we’ve only used it as a social network. We had not had a chance to look under the hood to see how things work and what you can do with it.
We wanted to get our hands dirty. But we didn’t just want to follow some tutorials; we wanted to build something fun and see how far Nostr could take us.
After some brainstorming, we landed on the legendary game SatoshiDICE. Why not bring the OG of all Bitcoin games to Lightning using Nostr?
We are going to build our own SatoshiDICE, with blackjack and hookers
NostrDice is SathoshiDICE on steroids. I mean, it’s built with Nostr and Lightning.

Design Goals of NostrDice

Before we jump into it, these are our design goals for the game.
  1. Provably fair. The NostrDice server must not be able to control the outcome of a die roll. Players must be able to verify that the NostrDice server is not cheating.
  2. Easy to use. The game should be compatible with any Nostr client (which supports Zaps). No new clients should be required to play the game.
  3. Have fun building it! This was the main goal if we’re honest :D
Looking into how Zaps work we quickly figure out a way to use them to place bets and pay out lucky winners.

How NostrDice Works

We set up 3 accounts on Nostr:
  1. Commitment to a Secret Nonce
    A new round starts when the NostrDice server posts a commitment to a nonce on NostrDice Nonces. The server does this before receiving any information from the player, so it can choose the nonce based on the player’s input.
  2. Placing a Bet
    The player chooses their multiplier and wager by zapping one of 11 notes on NostrDice Game. The multiplier also determines the target the die roll needs to hit. For example, to win a 2x multiplier, the player must roll a number lower than 31784 (out of 65535 possibilities).
  3. Determining the Outcome
    The server calculates the player’s die roll based on the secret nonce, the player’s npub and the zap memo. Player’s are encouraged to use the zap memo to make it impossible for the server to anticipate their input to the die roll.
  4. Immediate Payouts
    If the player hits the target, the NostrDice server will pay out instantly. The server zaps the player’s npub to credit them with their winnings. To this end, the player must have a valid LNURL configured if they want to get paid!
  5. House Edge
    The server retains a small house edge on every bet. For a specific target, the payout is slightly less than the true odds to account for this edge.

NostrDice as a Provably Fair Game

The key to NostrDice being fair is that the outcome of a die roll is random and verifiable. For an in-depth explanation on this, check out the rules of the game in our repository. But here is a summary of the main ideas.
Commitment Scheme
NostrDice commits to a nonce before the round starts by hashing it and publishing the hash on Nostr for everyone to see:
nonce := gen_32_bytes() commitment := sha256(nonce)
Roll Formula
When the player places their bet, the nonce is combined with the player's npub and zap memo to generate the die roll:
roll = bytes_to_decimal(first_2_bytes(sha256(nonce | player_npub | zap_memo | index)))
To prevent the server from predicting outcomes based on the player's npub, the player submits their own randomness via the zap memo.
Additionally, a roll index is included to allow the player to roll more than once with the same nonce.
Verification
The player can independently compute their roll after the nonce is revealed. They can also verify that the revealed nonce matches the original commitment:
original_commitment == sha256(revealed_nonce)

Lessons learned

Building NostrDice was very fun and not that hard! It’s easy to see why the Nostr ecosystem is flourishing: the Nostr protocol is super simple.
  • Dev environment: Building a Nostr client is super straight forward. There are plenty of libraries available, but we went with the rust-nostr stack. However, setting up a dev environment was not so easy. We ended up with an involved docker-compose file and a just file to spin up a dev environment consisting of:
    • bitcoind.
    • Two lnd nodes: one for the player and one for the server.
    • A Nostr relay via nostr-rs-relay.
    • nostr-wallet-connect-lnd so that the player can zap a multiplier note with their LND node.
    • An LNURL pay server for the player to receive payouts.
    • nostrdice: the game server itself, acting as a modified LNURL server.
  • Note limits: Our nonce account needs to publish notes regularly and we quickly ran into the limits of publicly available relays: we got banned. Because of this we decided to post less and run our own relay which again, was super simple. Since Rust is our first language, we decided to go with https://github.com/scsibug/nostr-rs-relay.
  • Production testing: This is the biggest hurdle we encountered. While for “normal” social media usage clients like Primal, Snort or Damus work well, when wanting to jump between different accounts, we quickly ran into UX issues. The setup which worked best for us was using the Alby browser extension with different profiles. I believe things can be improved here. For example, a browser extension supporting multiple Nostr profiles, where profiles are locked to one tab/website only. This way we can be logged into multiple accounts on different profiles at the same time. We also ended up using noStrudel as our favorite browser-based client.
  • No signet support on web clients. Zaps are always assumed to be on mainnet.
Overall it was great fun to build on Nostr for the first time, and we learned a lot. I hope you also enjoy this nostalgic tribute to SatoshiDICE on Nostr.
cheers,
the 10101 team
Love it!
reply
Nice write-up but how do I verify it myself?