Inspiration
I like to go outside sometimes with just my phone and keys in my pocket. And I love to tip at restaurants. The problem is, to tip someone without my wallet means asking for their phone number so I can send them a UPI (Indian govt fiat payment service) payment. I was daydreaming about a card that I could load up INR on via UPI and hand over to servers. UPI is unfortunately proprietary software and getting an SDK requires paying an unholy amount of money to a big bank. Plus I would have to hold the funds in escrow somehow and a host of other problems.
Enter Bitcoin. I don't need anyone's permission to send money. But how do you give someone without a BTC wallet Bitcoin?
What it does
Orange Ticket is a physical Bitcoin gift card that can be redeemed by someone who doesn't already have a Bitcoin wallet.
You create a voucher, which is a card with a random secret barcode printed inside and a handwritten phrase + address QR on the outside. The secret key is derived from a combination of the two.
- You control handwritten phrase => Printer can't redeem
- Printer controls random secret => You can't redeem
- Recipient has both => They can redeem!
A little cryptography magic (a dash of Argon2id) means that even if someone like your friendly print shop guy or the incredibly nice receptionist at PrideInn Azure Nairobi Westlands discovers the hidden secret printed inside the card, guessing the remaining half is computationally expensive.
The BTC lives "inside" the card until the recipient is ready to redeem - they tear it open, derive the key with the app, and sweep the BTC to any wallet they set up.
Verifiability is important - which is why the card is physically destroyed on opening. It's meant to be cheap and disposable. This is not a cold wallet for long term storage - it's a gift card for your nephew who's into tech, or for your server at the restaurant you really like, or for a cabbie who gave you a late night drive and a lovely conversation.
Orange Ticket only works because Bitcoin itself is open. More importantly, Orange Ticket itself must be open source. The entire trust model depends on users being able to verify that the app never uploads K_a, never uploads derived private keys, and derives addresses exactly as documented. A closed-source implementation would fundamentally break the premise. Yet more proof of the undeniable superiority of open source.
boring cryptography details follow
Let's imagine for a second I'm a guy that prints these vouchers for a living.
A wants to give B some Bitcoin. B doesn't have a Bitcoin wallet. A picks some secret K_a, a two-word passphrase. The app suggests two random words from the EFF Long wordlist (for around 25 bits of entropy), but it's done client-side, so I (the printer) never see the passphrase. And of course, the sender can override the passphrase as they please. I generate K_x, a random secret, then I do F(K_x + K_a) to generate an address client side in my app, where F is some function that generates a secret (specifically, a BIP-32 seed) and + is some way of combining the keys. It's kind of like BIP-39 on steroids. The app will send the pubkey to my server so I can print it, then I fold and seal the card and send it to A. They write the passphrase on the outside of the card (it's stored in their browser, I'm not completely inhuman), fund the card via the pubkey QR on the outside, and hand it to B.
Now, B can get the secret back by scanning K_x and entering K_a into the app, and then do stuff with the Bitcoin. The app could guide them to set up their first bitcoin wallet and receive the BTC... but I'm still on vacation.
Notice how B got the Bitcoin and I never saw the key! Isn't it neat?
How we built it
I initially wanted to do a split BIP-39 seed phrase, but the problem is that it's way too easy to brute force the remaining 1 or 2 words. Doing BIP-39 would've meant the funds can be swept into any wallet - unfortunately, I had to settle for the awkward two-step redemption process. I guess I could build a wallet into Orange Ticket, but I'm on vacation.
I didn't want to do ecash because it needs mints. And it's not a great UX for a first-time Bitcoiner. You may say, "just print a seed phrase on a piece of paper and hand it to them", but that would mean I and the printer would know the secret key. I wouldn't rug myself, but the printer would just have to wait for a txn to appear on the card and steal all the BTC.
So instead of BIP-39, we do Argon2id. We use K_x as the password and K_a as the salt. An attacker who knows Kx must perform a full Argon2id evaluation for every candidate Ka and for every card independently. Argon2id reduces attacker throughput by several orders of magnitude compared to PBKDF2.
The rest is glue and scissors and patience.
Challenges we ran into
- Glovo had all the arts and crafts supplies I needed, except for glue
- I knew almost nothing about Bitcoin until I started building this, but I didn't want to cop out and make a Nostr app
Accomplishments that I'm proud of
- learning enough Bitcoin in one night to pull this off
- finding glue in the middle of the night to make the cards
What I learned
- Bitcoin is super freaking cool
- How BIP-39 works
- a crash course in how to take a small, insecure value and make it into a big, secure value
What's next for Orange Ticket
Once I get off vacation, I'd like to learn more about Bitcoin and do the coinjoin stuff for sending txns, as well as make it easier to sweep/offramp funds. And I have heard good things about multisigs but I am far too smoothbrained to implement those properly. Time to develop some wrinkles.
Built With
- dokku
- typescript
Log in or sign up for Devpost to join the conversation.