-
Notifications
You must be signed in to change notification settings - Fork 126
(ios) Playground: Bolt Card + Phoenix Wallet #665
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
9bf63de to
e50f888
Compare
1ab6a94 to
ec112e5
Compare
7f1e602 to
d972a95
Compare
9faea14 to
e85329c
Compare
09c40b3 to
2827f1b
Compare
Bolt Card - Bolt 12 SpecificationAbstractThe Bolt Card allows for bitcoin payments over the lightning network using a contactless payment card. The original specification uses lnurl-withdraw. The problem with this is that an HTTP server is required, which creates a significant problem for self-custodial wallets. This document provides an additional specification that does not require an HTTP server, and uses native lightning messaging. MotivationIn the original version, a merchant who scans a bolt card will read a URL, which is an address for lnurl-withdraw. The merchant then generates a Bolt 11 invoice, and sends it to the HTTP server, which is then trusted to forward it to the self-custodial wallet. The wallet user must fully trust the HTTP provider, because it would be very easy to replace the merchant's invoice and steal funds. Providing this HTTP service may also introduce possible legal complications. But to put it simply: there's technically no reason to use lnurl-withdraw anymore. With Bolt 12 standardized we now have all the tools we need. A merchant can send the invoice directly to the wallet via an onion message, routed over the lightning network itself. This removes the need for an HTTP server, while also increasing privacy for the wallet.
OverviewWhen a merchant scans a bolt card, the card will output a dynamic value that changes everytime the card is read.
For example, the value may look like this: This can be broken down into the static part, and the dynamic part:
The static part tells the merchant how to communicate with the associated wallet. The dynamic part contains encrypted data that can only be read by the wallet. The general idea for payments is:
The original specification (hereby referred to as bolt-card-lnurl) states that the static part must be a lnurl-withdraw URL. This additional specification (hereby referred to as bolt-card-b12) states that the static part may also be a Bolt 12 offer. Which allows the merchant to directly send the invoice & dynamic parameters directly to the wallet over the lightning network. Dual SpecificationThis is an additional specification. As in, a bolt card could have either a lnurl-withdraw url, or a bolt 12 offer. And a merchant should support both. Bolt Card ContentFor For (The language identifier will be ignored. It's recommended to use "en".) The text content can either be:
Bolt 12 offerIf writing an offer to the card, the text must start with "lno". Thus the format of the read value should be:
While an offer is generally preferable to a bip-353 address, it might not always be possible. Specifically, the NTAG 424 DNA card only has 256 bytes of storage for the NDEF file (where the template string goes). But you'll need 2 bytes for the NDEF filesytem header, and 7 bytes for the NDEF message header. Plus you'll also need space for the BIP-353 addressIf writing a bip-353 address to the card, the text must start with "₿". Thus the format of the read value should be:
Query parametersThe "query parameters" must not be an empty string (must have at least one character). But other than that, this specification does not enforce the format of that data. It's up to the wallet to decide. For example, you could follow the example from above: or change the names: or even just squish it all together without using the common "query parameters" format: Scanning a Bolt CardWhen the merchant scans the bolt card, they should expect to find either For the b12 version, the TEXT must either start with "lno" or "₿". Otherwise the card must be treated as a non-bolt-card. Hybrid versionFor wallets that already support The
For example, from the URL above: General IdeaA Bolt 12 offer encodes a way to send a message to the associated wallet directly over the lightning network. Thus, when you write an offer onto a card (or an address that resolves to an offer), you make it possible for the merchant to send a message directly to the wallet. The merchant is going to send a
Merchant processingIf the value is a BIP-353 address, then the first step will be to resolve that address to a Bolt 12 offer. Once the merchant has the offer, it will then generate an unsolicited Bolt 12 invoice. That is, an invoice not based on an
One additional TLV is added to unsolited invoice:
Where the value is the extracted query parameters. E.g.: Note that this value should be treated as a generic string. The merchant shouldn't make any assumptions concerning it's format. The data must be encoded as UTF-8. The merchant then generates an onion message, where the destination is derived from the scanned offer. The
That is, the value for the final hop is the unsolicted invoice, including the At this point, the merchant expects one of the following to happen:
Merchant RecommendationsCard payments are expected to be fast, and complete within a few seconds. So it's recommended that the generated invoice has a short timeout period. 30 seconds is the recommended value. This allows the merchant UI to display a countdown, which helps manage expectations. It's recommended to display the countdown about 10 or 15 seconds after the payment request has been sent. If a payment is received, it's recommended that the merchant software store the base value that was read from the card (either the offer or bip-353 address), and associate this information with the received payment. This allows the merchant software to issue a refund without requiring any interaction from the payer. Thus alleviating a common pain point for merchants (and upset customers). Client processingWhen the client receives the
If all checks pass, it can send the payment for the Bolt 12 invoice. If the payment succeeds, the client should NOT respond to the invoice request in any other way. However, if the client is unable to send the payment, it must respond with a Error MessagesThe error message, sent from the client to the merchant, allows the merchant to immediately know the payment failed, without having to wait for a timeout. Each error message should contain an error code and a message. The standardized error codes are listed below.
The client MAY send an error code not listed above, and the software should be equipped to handle such a case. Note that care should be taken to avoid leaking too much information. For example, the wallet software may allow the user to set limits such as:
However, if one of these limits is breached, it's not advisable to tell the merchant which one exactly. Doing so would simply make it easier for a thief to steal more. The client uses the merchant's offer (received via the The client generates an onion message, where the
That is, the content is a TLV stream containing a standard
For example:
Merchant RecommendationsWhen sending the Cliff Notes: Bolt 12Bolt 12 is an upgrade to the lightning network that includes general "onion messages" and "reusable payment requests". It's not just a proposal - it was standardized and approved in 2024, and is already available & shipping in many lightning products. Onion message routingOriginally, the lightning network only routed payments. But now you can send generalized "onion messages", which are routed through the lightning network, but are specifically designed for communication that is separate from the actual payment itself. OffersAn "offer" (standaridized as part of Bolt 12) can be thought of as a reusable payment address. Let's say that Bob's wallet supports Bolt 12 offers. Bob can send a single offer to all his friends. He can post it on the Internet. And everybody can use that single offer to securely pay him. As many times as they want. So how does an offer work? It's quite simple:
So both the The Technical Background: NTAG 424 DNAThe physical "bolt card" is actually a NFC card, spefically NTAG 424 DNA. This type of card can be used for many different things. But it also has the attributes needed to perform card payments. In particular, it has AES encryption plus a built-in counter that gets incremented everytime the card is read. In other words, the card can output a dynamic value that changes everytime the card is read. For example, if you tap the card to your phone, it might read: And if you tap it again, the output will be slightly different: Here's the cliff notes version of how that works: When you program the card, you write:
Then when the card is read, it will:
Next time the output will be different because the counter value will be different. |
2827f1b to
f8ec284
Compare
7b37215 to
980a6f7
Compare
980a6f7 to
5b4acb1
Compare
The Bolt Card allows for bitcoin payments over the lightning network using a contactless payment card.
This PR:
This is a playground / draft PR, with the goal of developing and testing Bolt Card b12 - an additional specification that uses modern lightning network communication.
There's a LOT to explain here, so I've broken it down into sections.
User Experience
It's super easy to link a card to your wallet. Just tap the "create new debit card" button, and then tap the card to the upper-half of the iPhone.
nfc_write_720p.mov
After that the user is free to manage their card however they want:
When they make a payment with the card, they will see a notification on their phone:
NTAG 424 DNA
The NFC card that's used is called NTAG 424 DNA
This type of card can be used for many different things. But it also has the attributes needed to perform card payments. In particular, it has AES encryption plus a built-in counter that gets incremented everytime the card is read.
Here's the cliff notes version of how it works:
When you program the card, you write:
Then when the card is read, it will:
countervariablepiccdata:cmac(message authentication code):Then general idea is:
Common misunderstanding:
The card does NOT have a static value. The card has a built-in counter that gets incremented automatically. So everytime the card is read, it will output a different value. And within the encrypted value is an incremented counter. This protects against replay attacks.
Lnurl-Withdraw
The Bolt Card was initially released several years ago. Long before Bolt 12 was standardized and widely deployed. Thus it's completely understandable that they opted to use lnurl-withdraw.
However, the use of lnurl-withdraw means:
There's not many problems with this design if you're operating a custodial wallet service. But if you're designing a non-custodial wallet, then there are lots of problems. Thus the desire for an updated version that takes advantage of modern lightning technologies.
(Similar to how BIP-353 is replacing lnurl-pay for lightning addresses.)
Host Card Emulation (like Apple Pay)
It is my understanding that we do NOT need any special permission from Apple to allow either reading NFC cards, or writing to them within our app.
This is in stark contrast to doing Host Card Emulation, where the phone itself acts as an NFC card, and sends data to a reader (i.e. like when using Apple Pay)
However, you must obtain special permission from Apple to use this technology. Here's the details for obtaining permission in the European Economic Area. And here's the details for obtaining permission in the USA.
However, note that even if Apple decides to give you permission to use the technology, it can only be used in an "eligible territory", which Apple decides. And there are more people in the world living outside these "eligible territories" than inside.
Task List: