Lnurl-pay.me: Encrypting the Destination

Until 2022-01-07, every LNURL and lightning address generated by https://lnurl-pay.me had a destination fiat payment system account visible in plain text. Sometimes it's fine: if you're collecting mass donations to a bank card whose number you've already published anyway. Sometimes it's even good: when you call for lightning donations to a public person who has published his fiat account, everyone can see that you aren't scamming the donors, as exactly that account is visible in the address.

Sometimes, however, this is not what you want. When you're charging a random person for your services using LNURL, or when you're collecting donations for yourself, there's no point in sharing your fiat account to anyone but the source of fiat payment (i.e., me). I'm adding optional account encryption today so you don't have to do it anymore.

It's just a checkbox labelled “Encrypt”: you may turn it on before (or after) entering the destination, and both the LNURL link and the lightning address cease to reveal the specific account in the destination payment system.

Every other thing remains visible: fiat currency, pre-specified fiat amount, and the payment direction (is it a Russian bank card or an Ukrainian mobile phone balance?). The exact amount of a successful fiat payment continues to be displayed in LUD-09-compliant wallets. The recipient's account, however, is not revealed even to the payer after a successful payment. First characters of the encrypted address are included in the message (they cover the whole AES-GCM MAC for the encrypted account, so I don't believe you can make it collide for different addresses to construct a fake LNURL-style “proof of payment”).

Anticipated FAQ

Q: Why are encrypted addresses so long? A: I've chosen an asymmetric encryption scheme to ensure that my server would know nothing about your account until someone actually pays to an encrypted address (you can create 100500 donation links, and I'll only know target accounts for those that are actually paid to). Unfortunately, the ECIES authenticated encryption scheme is rather heavy, requiring 33 bytes for an ephemeral public key + 16 bytes for MAC + the original length of plain text.

Q: Why not encrypt payment direction, currency etc.? A: With the current service implementation, these things may leak easily anyway, and I'm trying hard to avoid any illusion of privacy. Payment size limits, payment provider outages, and countless other things may reveal what system you're paying to. If it's interesting to anyone, I'll consider a special kind of encrypted addresses in the future, where the satoshi payment range or amount is user-specified, and no feedback on fiat amount is given during payment.

Q: How's encryption implemented? A: Code is published at https://git.int.sw4me.com/akovalenko/lnurl-pay.me/src/branch/master/src/ecies.js. The result of ecEncrypt is always prefixed with “0g” fixed prefix for some obscure legacy technical reasons. The bytes are encoded using bech32 alphabet, but without a hrp or a checksum to save some space (checksum makes no sense here as long as authenticated encryption is used). I use https://github.com/ecies/go for decryption, jumping through a few hoops to save space: passing a compressed representation of an ephemeral public key, plus assuming zero nonce (which I consider to be safe here because each ephemeral key is only used once).

Q: No padding? Doesn't it reveal account length? Isn't that bad? A: The calling code of ecEncrypt pads very short account names/numbers to 8 characters, preventing cases where the leak could be most harmful.

Q: Can the government see where my payment goes? A: Please assume it can. Surveillance of my outgoing payment rails is entirely possible, though not always easy. A government official can donate to an address and observe outgoing fiat payments, it's rather easy. I console myself that at least it's not free, and it's a bit harder to correlate third party payments to encrypted addresses this way.