-
Notifications
You must be signed in to change notification settings - Fork 47
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
blip-0042: Bolt 12 Contacts #42
base: master
Are you sure you want to change the base?
Conversation
9925dac
to
a5199f7
Compare
One downside here is it relies on the recipient using a static offer signing pubkey, which by default they almost certainly should not do. A radically different approach would be to use something like bLIP #31 to use separate key material from the offer id to do a mutual handshake without using static keys. |
It is very important in my opinion that "adding a contact" is as simple as entering a BIP 353 address or scanning a QR code and clicking a "store contact" button. It is also very important to make it easy to add to any wallet to guarantee compatibility across the ecosystem. The simplest way I can see to achieve that is to use key material from the Bolt 12 offer directly. This is why I chose to use a key that already needs to be in any valid offer, even though the drawback is that it's a bit of a layering violation (by deriving a key used for paying from a key used to get paid). It seems to me that alternatives will:
|
I don't think bLIP 32 breaks either of those?
There's two issues here, not just the one - (A) IMO the re-use of the offer signing pubkey as the key to use for this authentication is not just a layering violation but breaks potentially important use-cases (eg custodial services can't use this, doubly so if they have more than one node). But that's not to say you can't by default use the offer signing key as the authentication key... For example, when you pay an offer, if the recipient is willing to be authenticated, the invoice can specify an alternative "authentication key"/BP which is used to identify this recipient. Then, if the server is willing to be authenticated they can send an onion message to the node and share their authentication key. Because this authentication should only ever be used if both ends have the other one in their contact list, just using a commonly available key is not sufficient. (B) but much more importantly, the use of the offer signing key as the authentication key completely breaks BOLT 12. You must not use the same offer signing key for all of your offers. A static public offer is one thing but for this to work when I scan an offer you also have to reuse the same signing key on every offer, allowing someone to match a fresh offer against my public BIP 353 one. Further, I'm very afraid of this design because people are absolutely going to implement it in the naive way - always reuse the payer-key no matter who the recipient is - which is very very much not okay. |
I don't understand, that's not at all what I'm specifying here? When you'd pay one of your trusted contact, you would use the offer signing key of the current offer you're using in your BIP 353 address (which you can rotate with a different signing key). But previous offers or offers you're using for other purposes have no reason to use the same key?
You mean bLIP 31? It doesn't break those assumptions, but I don't see how you fetch/configure the "list of acceptable public keys they wish to exchange messages with" you're mentioning in there if those aren't included in Bolt 12 offers. Or do you mean that you'd like to use bLIP 31, but use the offer signing keys of your contacts as that "list of acceptable public keys" so that they are directly contained in the Bolt 12 offers or your contacts and in their BIP 353 address? Because if the keys from that "list of acceptable public keys they wish to exchange messages with" does not come from Bolt 12 offers, then we need a new protocol to exchange those keys and somehow include them in BIP 353, which can quickly get messy and hard to get widespread compatibility... So overall, I'm not at all against using bLIP 31 instead of what I'm proposing here, but I'd like to see a more detailed design that shows how to use it for contact authentication if you could write it down. |
Sure they do? Having a "contact list" where I can pay someone in person and it doesn't show up as the person in my contacts list is weird? Or, inversely, having a "contact list" where I won't show up as the sender if you've paid you in person before but not via BIP 353 is weird? More generally, I may occasionally change my BIP 353 for various reasons, and I don't want my entire contact feature to break just because I changed it? That seems like a weird user footgun.
Sorry, yes, bLIP 31. Indeed, bLIP 31 doesn't solve the issue here, it just gives us a building block for doing this in a more mutual and private way. We still have to solve key distribution. We could pick from a few different key distribution methods, depending on what properties we want, for example, if we assume that users know their own BIP 353 (or can figure it out in a reasonable way): (a) when receiving an This allows payments to be identified even if they scan a QR code after storing a contact, but it does rely on using BIP 353 for the contact list (maybe that's the intended use anyway? Just means you can't store a contact from an offer directly). It also means recipients can't track senders unless they are mutually in each others' contacts, which I think is an important property. It also allows for multiple "identities" for one node, though the assumption that a node knows its BIP 353 may be strong. |
That's a good point. I'm biased because in Phoenix we deterministically derive a "default" offer from the user's seed (which allows seamlessly restoring it to a different device), but I agree that this is only one out of many possible strategies for mobile wallets, and being able to rotate your default offer without breaking your contact lists is important. It could also make sense to give different offers to different people to more easily account for payments from different use-cases. As you note, this does force us to decouple the "contact key" from offers themselves and thus may require re-thinking the UX for adding contacts a bit. Would it make sense to simply include a new "lnck" (LN contact key) field in BIP 21 URIs that contains our contact pubkey? This way we separate it from the offer itself, but our BIP 353 address can include it in the returned URI, and the QR code for receiving Bolt 12 payments shown by wallets could use this BIP 21 URI as well (instead encoding just the Bolt 12 offer). |
Based on the discussions we had in this PR so far, I think it's worth discussing the various options before actually specifying a bLIP. I've started the discussion here, please take a look and tell me what you think: https://delvingbitcoin.org/t/bolt-12-trusted-contacts/1046 |
Add support for contacts as specified in bLIP 42. Contacts are mutually authenticated using a 32-bytes random secret generated when first adding a node to our contacts. When paying contacts, we include our own payment information to allow them to pay us back and us to their contacts. The benefit of this design is that offers stay private by default (they don't include any contact information). It's only when we pay someone we trust that we reveal contact information (which they are free to ignore). The drawback of this design is that if when both nodes independently add each other to their contacts list, they generate a different contact secret: users must manually associate incoming payments to an existing contact to correctly identify incoming payments (by storing multiple secrets for such contacts). This also happens when contacts use multiple wallets, which will all use different contact secrets. I think this is an acceptable trade-off to preserve privacy by default. More details in the bLIP: lightning/blips#42
Add support for contacts as specified in bLIP 42. Contacts are mutually authenticated using a 32-bytes random secret generated when first adding a node to our contacts. When paying contacts, we include our own payment information to allow them to pay us back and us to their contacts. The benefit of this design is that offers stay private by default (they don't include any contact information). It's only when we pay someone we trust that we reveal contact information (which they are free to ignore). The drawback of this design is that if when both nodes independently add each other to their contacts list, they generate a different contact secret: users must manually associate incoming payments to an existing contact to correctly identify incoming payments (by storing multiple secrets for such contacts). This also happens when contacts use multiple wallets, which will all use different contact secrets. I think this is an acceptable trade-off to preserve privacy by default. More details in the bLIP: lightning/blips#42
Add support for contacts as specified in bLIP 42. Contacts are mutually authenticated using a 32-bytes random secret generated when first adding a node to our contacts. When paying contacts, we include our own payment information to allow them to pay us back and us to their contacts. The benefit of this design is that offers stay private by default (they don't include any contact information). It's only when we pay someone we trust that we reveal contact information (which they are free to ignore). The drawback of this design is that if when both nodes independently add each other to their contacts list, they generate a different contact secret: users must manually associate incoming payments to an existing contact to correctly identify incoming payments (by storing multiple secrets for such contacts). This also happens when contacts use multiple wallets, which will all use different contact secrets. I think this is an acceptable trade-off to preserve privacy by default. More details in the bLIP: lightning/blips#42
Bolt 12 introduces offers, which are static lightning "addresses". An offer can be stored and reused to pay the same node many times. It then becomes natural to associate Bolt 12 offers to your friends and contacts. When sending payments to contacts, you may want them to know that the payment came from you. We propose a scheme to optionally include contact information in outgoing payments to allow the recipient to: - detect that the payment is coming from one of their known contacts - otherwise, be able to add the payer to their contacts list - send funds back to the payer without additional interaction This feature provides a better UX for lightning wallets, by making payments between contacts look very similar to fiat payment applications.
a5199f7
to
5c0a128
Compare
Add support for contacts as specified in bLIP 42. Contacts are mutually authenticated using a 32-bytes random secret generated when first adding a node to our contacts. When paying contacts, we include our own payment information to allow them to pay us back and us to their contacts. The benefit of this design is that offers stay private by default (they don't include any contact information). It's only when we pay someone we trust that we reveal contact information (which they are free to ignore). The drawback of this design is that if when both nodes independently add each other to their contacts list, they generate a different contact secret: users must manually associate incoming payments to an existing contact to correctly identify incoming payments (by storing multiple secrets for such contacts). This also happens when contacts use multiple wallets, which will all use different contact secrets. I think this is an acceptable trade-off to preserve privacy by default. More details in the bLIP: lightning/blips#42
@jklein24 @TheBlueMatt I have completely re-written this bLIP based on the whiteboard session we had during the lightning summit with Rusty, this is now ready for review as I'm chasing concept ACK before adding this to Phoenix! We don't use |
- SHOULD NOT include `invreq_payer_offer`. | ||
- SHOULD include `invreq_payer_bip_353_name` instead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably make this verifiable somehow, no? IMO it should not be the case that I can pretend to be someone else when paying. We could include a signature by the offer's signing key, I guess?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that would fix anything: if I'm pretending to be someone else when paying you, the offer I would include in invreq_payer_offer
would also be one of mine, so I'd be able to sign the invalid bip_353_name
and you wouldn't be able to tell?
I think this scheme has to rely on information obtained outside of the protocol: whenever you add someone to your contacts list based on a payment you received from them, you must have verified somehow (e.g. in real-life or through some secure messaging) that this was indeed their payment, in which case you can trust that the invreq_payer_offer
and inreq_payer_bip_353_name
correctly belong to them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that would fix anything: if I'm pretending to be someone else when paying you, the offer I would include in invreq_payer_offer would also be one of mine, so I'd be able to sign the invalid bip_353_name and you wouldn't be able to tell?
No I was thinking you'd have to sign using the offer signing key from the BIP 353 entry. That'd be verifiable.
We don't need to make the offer verifiable, necessarily, because people don't identify themselves by the offer signing key, though it would be kinda nice imo if they were.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No I was thinking you'd have to sign using the offer signing key from the BIP 353 entry. That'd be verifiable.
Got it, that would make sense, as it would allow reconciling payments based on BIP 353 HRNs.
What do you think of the following proposal: if the sender of invoice_request
includes invreq_bip_353_name
, it MUST set invreq_payer_id
to the signing key of the BIP 353 offer. This way the invoice_request
is signed by the BIP 353 offer signing key. Since the payer wants to reveal their identity, I don't see any drawback in using the payer_id
for this?
The reason I'd like to avoid adding an additional signature field is because:
- we don't want to resolve the BIP 353 DNS record when receiving
invoice_request
: we haven't been paid yet, so it would be a DoS vector (anyone could flood us withinvoice_request
and ignore theinvoice
), we only want to resolve it after receiving the payment - if we added a new signature field, we would then need to include it in the
path_id
in the invoice returned, to be able to verify it when receiving the payment, which uses an additional 64 bytes in a potentially already largepath_id
(constrained by the 1300 bytes onion size) - whereas we already need to include the
invreq_payer_id
in thatpath_id
(to allow the payer to later provide a PoP), so we only need to check that it matches the BIP 353 offer signing key after receiving the payment
If you think we should still create a new field for that signature, then we may need to require BIP 353 names to be short, probably not more than 200 characters: in practice it should already be the case, but in theory it can be 2*255 characters long...am I over-thinking this when considering that we need to handle "large" BIP 353 names?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@TheBlueMatt could you take a look at my last comments? I'd like to finalize a prototype version of this, and this seems to be the main blocking point that would create backwards-compat issues!
blip-0042.md
Outdated
create a new contact. | ||
- If it creates a new contact based on this received payment, the | ||
received `contact_secret` MUST be used when paying that contact. | ||
- MAY associate the received `contact_secret` with an existing contact. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How? Might want to specify "via BIP 353" because you generally can't based on the payer_offer
unless it happens to exactly match one you already paid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was unclear indeed: I think we shouldn't automatically reconcile, that won't be helpful in practice and opens the door for exploitation if contact secrets leak. I updated this in e24d241 to mention that this is a manual process when the user has out-of-protocol knowledge that a payment came from a specific contact.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we shouldn't automatically reconcile, that won't be helpful in practice and opens the door for exploitation if contact secrets leak.
I disagree. If we can verify a BIP 353 match (using the signing key in the offer in the DNS) then auto-reconciling would be a really nice UX compared to forcing users into a manual flow. It'll be really confusing UX when a user has a payment from a HRN and sees a different contact for sent payments to the same HRN.
I'm not all that worried about contact secret leaks, fwiw. Yes, we should make it so that you can't trivially steal money if they do, but at least on the BIP 353 end if you can edit someone's BIP 353 you can presumably steal some of their money anyway, soo....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree. If we can verify a BIP 353 match (using the signing key in the offer in the DNS)
I agree, as discussed in #42 (comment), if the BIP 353 name is signed, then we can reconcile using it.
blip-0042.md
Outdated
- If it creates a new contact based on this received payment, the | ||
received `contact_secret` MUST be used when paying that contact. | ||
- MAY associate the received `contact_secret` with an existing contact. | ||
- MUST ignore `invreq_payer_offer` and `invreq_bip_353_name` if it already |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we allow the offer to be updated if its using the same signing key? That way the blinded paths can be updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is dangerous: if the contact_secret
was leaked, Mallory can create an offer that uses blinded paths to herself with the offer_issuer_id
of Bob and include that in a payment to Alice. Then Alice would try to pay Mallory instead of Bob. Mallory wouldn't be able to receive the payments because she cannot sign invoices with Bob's offer_issuer_id
, but she would see that Alice is trying to pay Bob, and she would prevent Alice from paying Bob.
I think the only way to refresh offers should be:
- manually by the user (when they know that the offer they're adding is really from their contact)
- through BIP 353 by resolving the DNS record
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't say I'm that worried about it? Lightning wallets have to store secret(s) that, if leaked, can lead to loss of funds. The fact that we have to store (or derive) a new one and then keep that secret doesn't seem like a particularly huge burden.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO a wallet's state should contain the following:
- the wallet seed: this is the most important thing to secure to protect against an attacker stealing your funds
- the channels state: this is important to secure (and back-up) to restore channels on a different device, but an attacker with access to this data cannot steal your funds, as this shouldn't contain private keys or fully signed transactions
- the payments history: this is not critical and optional to backup, an attacker with access to this learns preimages, but cannot steal funds
- the contacts list: this is not critical and optional to backup, and shouldn't allow an attacker to steal funds
Of course, all those DBs should be encrypted, but the only highly sensitive entry should be the seed. The others can only be exploited if they're stolen by someone who colludes with your channel peer(s), which is IMO a different security assumption.
If we don't automatically update offers, the contact_secret
cannot let attackers steal funds even if it leaks. I don't understand why we'd want to take more risks for a feature that's IMO not useful at all? Offers can be refreshed using BIP 353 or real-world interaction, would we really gain something by automatically updating them here?
- If the offer contains `offer_issuer_id`: | ||
- `offer_node_id = offer_issuer_id`. | ||
- Otherwise, the offer must contain `offer_paths`: | ||
- `offer_node_id` is set to the last `blinded_node_id` of the first | ||
`path`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just say the key used to sign the invoice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That wouldn't cover all cases, because when you have multiple blinded paths and no offer_issuer_id
, the key you use to sign the invoice depends on the blinded path that was used for the invoice_request
. So if Alice is trying to pay Bob, and Alice has multiple blinded paths in her own offer, she wouldn't know which local key to use in the ECDH (since she's the one sending invoice_request
, not receiving it)?
That's why I'm explicitly specifying that when multiple blinded paths are used, we only use the first one for this contact_secret
derivation. Does that make sense?
- Detail normative section to clarify that `contact_secret`s are bidirectional and set by the first payer. - Remove automatic matching of incoming payments using distinct secrets. - Expand the contact secrets section with UX examples.
And feedback from @robbiehanson.
Bolt 12 introduces offers, which are static lightning "addresses". An offer can be stored and reused to pay the same node many times. It then becomes natural to associate Bolt 12 offers to your friends and contacts.
When sending payments to contacts, you may want them to know that the payment came from you. We propose a scheme to optionally include contact information in outgoing payments to allow the recipient to:
This feature provides a better UX for lightning wallets, by making payments between contacts look very similar to fiat payment
applications.