blog

Suppose you're developing a web application, or running a local service like writefreely. It works great on localhost, now what about publishing it? You install it to a remote server, hide it behind the reverse proxy (like caddy), and lo and behold! It's available to everyone on the internet 24x7. As you modify, update and develop the application, you have to synchronize the remote version, upgrade and maybe restart it.

It can be a bit tedious even if you already have several servers running caddy or nginx. And it's absolutely wrong if you have a reason to share the thing running locally instead of running its snapshot remotely.

Let's start caddy to share a folder with documents:

caddy file-server -l 127.0.0.1:5000 -b -r ~/My_project/Docs

We go to localhost:5000 and see the folders and files. How can we share this without “uploading” anything?

Okay, that's how I do it:

  • on the remote server, I configure my reverse proxy for some domain to go to http://localhost:8080 for content: e.g. reverse_proxy localhost:8080 in Caddyfile.
  • on the local machine, I run ssh -N -R8080:localhost:5000 remote@machine. Whatever I have on localhost:5000 is now fully accessible remotely, as long as SSH is running.

Now the good news:

I offer a service that works exactly this way, only you don't have to configure any remote machine.

The bad news:

It's not free. I charge 1000 satoshi per SSH client key to get your app forwarded to my subdomain. You pay when first logging in with the key, you have a fixed subdomain for that key until the project dies.

Try this (note remote port 80):

ssh -R80:localhost:5000 app@ssh.my-ns.me 

For the first login, a lightning invoice for 1000 sats will be created for you. After you pay the invoice, a random four-letter subdomain will be assigned to your SSH key, let's say it's zzvb. As long as SSH is running, visiting https://zzvb.app.my-ns.me has the same effect as visiting http://localhost:5000.

The country of my citizenship and residence has invaded Ukraine, starting a war. That much I know from my friends and their relatives etc., and no amount of geopolitical bullshit about Biden/NATO/backyard/neonazis will change my mind, so please don't bother to even start.

Russian government has directly prohibited what I'm doing here since 2020, and I was prepared from the start to take a hit for it when I decided to go ahead. It would be kind of funny if I'm imprisoned for a publication or something like this text, which changes almost nothing, instead of the actual, useful anti-state activity of providing flow of funds without verifying anything about the sender or asking anything but the bare minimum about the recipient.

It wouldn't be a personal fun for me and lnurl-pay.me will probably suffer a lot of downtime whenever this happens, but it's nothing compared to living in a bombed city, etc., so it doesn't matter much for now. If you search a person who deserves encouragement and praise for his bravery and support, consider Anton Kumaigorodsky, the author of SBW (https://sbw.app) Lightning wallet, serving in an Ukrainian militia with a purpose to kill as much invaders as possible. Consider helping his country throwing out Russians away as soon as possible, so he can return to developing SBW, Hosted channels and other useful stuff. By doing it, you probably help Lightning Network more than by running a routing node. Consider donating to https://savelife.in.ua/en/donate/ (Bitcoin accepted).

If you are a Reformed Christian like me, consider supporting Gennady Mohnenko's ministry or getting some directions where the help is needed: https://www.facebook.com/gennadiy.mokhnenko (if you're a Reformed Christian from the US, following him might also unfuck your mind about NATO/Biden/Geopolitical shenanigans, if there's any hope for you at all).

Lnurl-pay.me continues to operate in both RUB and UAH directions. As I have no companions or technical personnel to oversee the service and top-up reserves when I'm not present, it can stop any time now. Fortunately I'm not a custodian for anyone's funds, and the invariant of either paying out fiat or getting your LN payment back should persist anyway.

Again, please spare me your warnings about what I'm doing being treason and putting me in a dangerous position. I believe in both grace and sovereignty of our Lord Jesus Christ, and if He decides that some time in prison will improve my weak faith, then I have no objection altogether. If He decides another way, then good luck for petty statist slaves catching me.

Good bye and thanks for all the fish

Introduction

https://lnurl-pay.me is an unidirectional Bitcoin Lightning to fiat exchange, paying out with local Russian and Ukrainian currencies and payment methods (maybe more regions will follow, but there's no such plan yet). If you have a lightning wallet and you want to pay someone who's from Ukraine or Russia, that's probably just what you need.

The back-end (and the author, and the owner) is the same as in @LnToRubBot and its old lnurl-pay generator. The bot is not going anywhere anytime soon, and will continue to serve you.

Lightning invoices are one-time, non-reusable, and subject to expiration (that's what the bot gives you for each deal, and you have 10 minutes to make a payment). Lnurl-pay codes, on the other hand, are reusable: They're just entry points for getting a new invoice from a predefined place, with some auxiliary negotiations around it.

It took a while for lnurl-pay to gain traction and support in major wallets, but it's mostly happened by now. (See awesome-lnurl).

Generation

When generating a code with lnurl-pay.me, you can either specify a precise fiat amount of payments, or leave it up to the payer's decision. In the first case, the lightning wallet will offer, during each payment, the exact amount of satoshi that is enough to be exchanged for the given fiat value. In the second case, the wallet will offer a range for the payment's value in satoshi. This latter arrangement makes it almost impossible to pay some precise amount in fiat: your wallet's opinion of the exchange rate and my backend's opinion would necessarily differ.

lnurl-pay.me offers to specify a memo for the payment. That's what the payer's lightning wallet will probably show in payment history. More precisely, if there's a user-specified memo, it will show up as “MEMO: <your text here>”; otherwise, my server will provide description according to the payment details, like “1200 rub to mobile +75551234567” or “some sats to card 2022202244445555”. For payment links coming from my domain, I hereby declare that anything prefixed with “MEMO:” is a non-authoritative user-specified comment, while anything not prefixed with “MEMO” reflects real payment details used by the backend.

Once generated, lnurl-pay codes may be shared, printed out, kept for private use, sent to a friend. A wallet, like SBW, might retain the link after first payment, so it's available for reuse (there's a flag for it in the spec, and I set it to true).

A lightning address is generated together with each lnurl-pay code. As support for addresses is rather widespread, chance are that you can use it where you can use lnurl-pay. Fiat amount is brought into it when specified, resulting in addresses like 200rub-79175555555@mobile.lnurl-pay.me. The Memo field is the only thing that is not retained in the address: you have to use lnurl-pay link for it.

Safety

My backend takes great care not to finalize a lightning payment until the reciprocal fiat payment is complete. Many things could go wrong in the payment business, however, my main principle is that it's better to refund erroneously than to be liable for other people's money. During the 2+ years of the project's operation, there indeed were a couple of cases where a user got both a refund and a payout; the opposite thing, where I held user's satoshi without paying back, has never happened.

The only realistic way to lose your payment's value is to send it to wrong, but valid, fiat destination. That is, to top-up a wrong phone or skype account due to a typo, or to be tricked into sending funds to a banking card belonging to an impostor. As far as I know it has never happened to my users, but if it would, too bad: reversing my payouts is nontrivial enough to assume it's impossible. Double-check the receiver's account before making payment. Consider this operation as irreversible as a lightning payment itself.

Fortunately it doesn't hurt too much to split up a payment and send it in small parts, checking in between that the previous part has arrived to the right person.

Privacy

Generated lnurl-pay link contains its payment details in clear text. They aren't visually prominent in LNURL1... string because of a peculiar encoding, but it is not an encryption (see this codec). Never publish a QR code or corresponding text to an audience where you'd not share your payment details as well. Setting the MEMO field suppresses showing payment detail in the wallet's history, but it's all purely visual and cosmetic: don't count on it to protect your data.

Update: now in 2022, you can generate encrypted lightning addresses and lnurl-pay links, so publishing them won't reveal your card numbers, wallets, accounts, etc., even to those who pay with them.

Perhaps the most sensitive kind of information I work with is a Visa/MC card number. There are indeed certain risks in publishing it, but it's not really considered a secret anyway, at least not in Russia or Ukraine: card numbers are routinely published for fundraising, and some small shops provide the owner's card to settle payments in p2p.

As of data logging and retention, I'm not committed to do anything special in that regard: my web server keeps some logs that might include payment details, and they're removed when they get older. Payment details are visible to my payout providers anyway. What they don't see is the Lightning half, and LN onion routing prevents even me from any reliable guesses on the payment's source.

Lnurl-pay.me will happily serve clients and wallets coming through tor network. Note that the real risk of correlated IP+payment details disclosure is not when you generate the link (in this step, everything happens in your browser, and the server gets no data), but when the wallet makes a payment. Tor-supporting wallet could help here.

Probably illegal. Feel free to go elsewhere if it's too important to you.

As of my native jurisdiction, we've got a “cryptocurrency law” (259-ФЗ) prohibiting, among other things, any spread of information that a “cryptocurrency” may be used as a payment method for goods and services. That's exactly what I do for destinations like mobile phone top-up, and the rest is probably more severe, somewhere between unauthorized entrepreneurship and unlawful banking.

I regard exercising civil disobedience on this particular point as my duty before God.

As of potential risk for payment recipients, I believe it to be exactly the same as if they got the same money from p2p card payments (which are way popular here).

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.