← Timeline
Avatar placeholder
Anton Kovalenko
LNURL: история

Сначала были только lightning invoices (bolt11). Это запрос на одноразовый платёж, подписанный узлом-получателем, у которого есть (опциональная) сумма (можно переплачивать), иногда описание, иногда полезная инфа для маршрутизации и ещё много всякого "иногда". Для любителей криптографии: плюс хэш, прообраз которого служит доказательством платежа (по протоколу невозможно забрать платёж и не предоставить прообраз -- ни один узел в цепочке маршрутизации не может так поступить; поэтому "пользовательский кошелёк знает прообраз" -- идеальный прокси-признак для "пользователь оплатил инвойс", кошельки это знают и как минимум умеют показать preimage при необходимости).

Инвойсы (1) одноразовые, (2) всегда идут от получателя к отправителю. Свой штатный сценарий использования (отсканировал QR с чужого экрана, или там скопировал строчку с сайта, и сразу оплатил) они поддерживают нормально. Но, например, вывесить инвойс в блоге в качестве "адреса" для донатов уже не получится -- нужно что-то, что может сгенерериновать новый инвойс на ходу. Это может быть веб-страница или, к примеру, "магазин" на базе btcpayserver (аккаунты в чужом инстансе btcpayserver раздают на btcpayjungle.com, при этом можно подключить и lightning-узел, который у вас есть где-то ещё (и это единственный способ подключить лайтнинг-узел к тамошнему инстансу, то есть узлы они не раздают)).

Разработчики C-Lightning (второй по популярности реализации lightning-узла) довольно давно придумали сущность более высокого уровня поверх инвойсов: bolt12 offer (см. bolt12.org). Но только в последние недели офферы начинают вводиться в эксплуатацию, с примерно нулевой поддержкой в кошельках и сервисах. Bolt12 -- "тяжёлое" предложение, которое пытается решить кучу проблем сразу, и оно ориентировано на "lightning-only" мир, в котором нет https и прочих бумерских централизованных изобретений. Есть lightning-узлы, они как-то связаны, всё интересное происходит в onion-сети поверх этих узлов. Это само по себе не недостаток проекта (просто такой design decision, и авторы в итоге могут быть правы в долгосрочном периоде), но есть ещё и техническое "расхождение" с текущей практикой в LN: в базовом протоколе lightning onion routing чужие сообщения передаются только вместе с платежами или статусами платежей. Это не означает, что за любую передачу надо платить: бывают неуспешные попытки платежа и ответы на них, так что "злоупотребить" протоколом для халявной сигнализации вполне возможно. Но даже неуспешные попытки не вполне "бесплатны" для отправителя платежа, потому что он рискует "подвисанием" платежа на некоторый срок (от часов до дней). Bolt12 же ориентируется на предварительный диалог с узлом-партнёром ещё до всяких платежей, который сейчас возможен только между узлами, соединёнными напрямую (не обязательно соединёнными платёжным каналом, но подключенными в качестве peer'ов через IP, tor или как там они предпочли это делать). Поэтому взаимодействие с bolt12 сейчас требует найти целевой узел в публичном графе и установить с ним прямое соединение, что само по себе проблематично (исключает узлы, которых нет в публичном графе, и полагается на аккуратность анонса узлами собственных адресов -- каковая аккуратность может оставлять желать лучшего как случайно, так и намеренно, в качестве меры безопасности).

Два-три года назад двое разработчиков "сервисов поверх лайтнинга" придумали LNURL: намного более простую штуку, чем bolt12 offers. Распространённые практики взаимодействия с инвойсами вроде "чтобы забрать баланс с сайта, сходите на сайт, вставьте туда свой инвойс, и он его оплатит" просто превращаются в обмен GET-запросами и кусками JSON между клиентом и сервером (поверх https или http+Tor/onion). Упомянутые авторы -- @fiatjaf, присутствующий здесь в виде заброшенного аккаунта, и Антон Кумайгородский (автор BLW, SBW и библиотеки IMMORTAN, всё это вокруг кодовой базы lightning-узла eclair на scala, но с нетривиальными низкоуровневыми доработками в том числе на уровне протокола).

Первым "взлетевшим" видом LNURL был lnurl-withdraw: способ забрать баланс на свой кошелёк без ручного копи-пастинга своего инвойса. Эта задача до lnurl-withdraw была в диапазоне от несколько неудобной (копипастинг на десктопе и/или в браузере) до абсолютно зверски неудобной (когда кошелёк на мобильнике, а сайт открыт на компе), а теперь "худший случай" выглядит так: видишь QR -- отсканировал кошельком -- забрал баланс. Разница с предыдущим опытом по удобству была настолько велика, что успех lnurl-withdraw, на мой взгляд, был предрешён: у протокола могло быть ещё в несколько раз больше недостатков, и всё равно его перенимали бы все подряд.

LNURL в изначальной спецификации -- закодированная в bech32 строка URL, либо https, либо http://...onion; вся нужная на первом шаге информация передаётся в параметрах GET-запроса. Сервер возвращает JSON, из которого следует "тип" данного lnurl (какую операцию он предлагает); чаще всего в этом JSON'е присутствует callback url, на который клиент делает ещё один GET-запрос, добавляя уточнённые параметры. Bech32-кодирование (под которое легко заглянуть, https://lnurl.fiatjaf.com/codec/) ничего особо полезного на самом деле не добавляет, просто хорошо сочетается с lightning: схемой, которая использовалась для инвойсов (lightning:lnbc... это инвойс, lightning:lnurl1.. это lnurl), ну и побочный эффект -- никаких "лишних" символов, которые могут преобразоваться в что-то не то при пересылке в мессенджерах и других подобным местах (типа как скайп превращает куски кода в смайлики иногда).

Lnurl-auth, который позволяет логиниться через подпись challenge открытым ключом своего кошелька (точнее, его HD-потомком, выведенным согласно домену сайта, на который логинишься) тоже стал популярен довольно быстро. Логин без регистрации -- вообще давняя всеобщая мечта, осталось только решить мааленькую подзадачу; как уговорить юзеров создать ключевую пару, о которой они будут тщательно заботиться и не потеряют. Для пользователей lightning-кошельков эта подзадача решена заранее. В остальном, кстати, lnurl-auth не имеет отношения к протоколам lightning, ему достаточно любого способа вывести уникальный pubkey/privkey для сайта, а это мог бы делать и простой bitcoin-кошелек (даже описанным в rfc способом), и вообще любой девайс, который справится с ecdsa + secp256k1.

Lnurl-channel, протокол для запроса открытия канала у чужого узла, пригодился и сервисам, продающим услугу входящего канала, и self-hosted мобильным кошелькам, для которых первый шаг "получить ёмкость для приёма платежей" одновременно важен и был довольно неудобен без LNURL. Впрочем, и сейчас есть self-hosted мобильные отщепенцы, которые этот протокол не умеют (Muun).

Lnurl-pay осенью 2019 года был эдаким черновиком, в заголовке которого было написано "не пытайтесь имплементировать, тут ещё недопридумано", и для этого были веские причины. Задумано было просто: кошелёк делает один GET-запрос и получает сразу инвойс, максимум и минимум того, что можно заплатить, описание платежа (метаданные, хэш которых попадают инвойсу в description_hash), дальше уже можно платить (в заданных пределах). Ключевой проблемой был инвойс, для которого штатно предполагается переплата: мало того, что тогда это было небезопасно на уровне протокола (routing nodes могли произвольно откусить себе любую комиссию, если они догадаются, что инвойс переплачен -- теперь так не сделать, поскольку полная оплаченная клиентом сумма приезжает получателю в tlv-onion с каждым кусочком платежа), но ещё и подрывает идею доказательства платежа (preimage доказывает оплату инвойса, а вот сумму переплаты установить невозможно). В таком недоделанном виде lnurl-pay провалялся довольно долго, поскольку авторам было не до него и вообще было неочевидно, полезный это протокол или нет. Это сейчас LNTXBOT немыслим без обработки lnurl-pay на вход и выход.

Я тогда пилил t.me/LnToRubBot, который уже во всю работал, и хотел сделать к нему вебморду, чтобы им можно было пользоваться без телеграма. Lnurl-pay пригодился бы идеально, но пришлось сначала превратить его в двухшаговый протокол, в котором инвойс выставляется под точную запрошенную сумму. Моё предложение особо не оспаривали, поскольку на lnurl-pay всем было наплевать. Таким образом и получилось, что первый в мире работающий сервер lnurl-pay оказался у меня раньше, чем его начал поддерживать хотя бы один клиент 🙂

Fiatjaf и Антон поддержали lnurl-pay довольно быстро, но остальные не спешили подтягиваться. С точки зрения обычного юзера lnurl-pay -- это прежде всего такой кривой недоинвойс, который умеют оплачивать не все кошельки. Уникальная возможность его сохранить и переиспользовать не бросалась в глаза (она стала более заметной, когда по флагу disposable: false кошелёк BLW стал сохранять "оплаченные" lnurl-pay в "платёжных закладках" для переиспользования). В какой-то момент появилась lnurl-страница для пользователей t.me/LNTXBOT (пример), можно стало также скормить LNURL самому LNTXBOT для немедленной оплаты, другие кошельки тоже начали шевелиться в эту сторону, в общем, оно постепенно завоёвывало мир, но не быстро. Протокол тоже дорабатывался, например, появилась возможность передать пользователю секрет, который он расшифрует с помощью preimage после оплаты инвойса (многие кошельки это поддерживают, но живого сервиса с этой фичей я ещё не встречал).

До недавнего времени lnurl был прежде всего фишкой opensource-кошельков, что в общем неудивительно: fiatjaf просто ходил и патчил всё, что движется, чтобы добавить поддержку. Понятно, что некастодиальные closed-source-кошельки никому не интересны: свои ключи доверять неизвестной закрытой вундервафле дураков нет. Однако есть несколько проектов (кастодиальных + интеграция с фиатными платежами), которые довольно хорошо распространены и профинансированы: прежде всего strike.me, потом Fold, официальный сальвадорский chivo wallet, и так далее. На большинство из них я даже посмотреть толком не могу, поскольку они активно не хотят пользователей извне "поддерживаемых регионов". Из таких проектов только zebedee.io влезает сейчас в lnurl (zebedee хотя и не связан с фиатными платежами, а просто кастодиальный кошелёк с прицелом на геймерскую аудиторию, всё равно залочен географически и мне официально недоступен, впрочем, хотя бы обязательного KYC у него нет -- так что с помощью aurora store и такой-то матери посмотреть на него я могу).

История приобрела неожиданный поворот, когда параллельно с обсуждениями, как нам похоронить bech32-encoding в LNURL (отсоветовать в новых документах можно, но стимулов-то переключаться на новый формат нет примерно никому, и будем ждать phase out до полной поддержки нового варианта в кошельках), несколько авторов придумали записать lnurl-pay в виде user@domain, с тем чтобы заинтересованный софт превратил это в domain/.well-known/lnurlp/user (да, я знаю, что юзер не должен быть частью url path, к сожалению, я поздно включился в эту историю и не успел воспрепятствовать). Такой вариант позволяет разом избавиться от bech32 и заодно, к примеру, набрать "адрес" вручную, если вы его просто знаете, безо всякого QR-кода. Lnurl-тусовка так разрослась, что и патчи, и "рекламный" lightningaddress.com, и всякие демонстрашки запилили очень быстро. Мне предложили добавить "адресный" вариант к моему сервису (по мнению fiatjaf'а, если оно заработает для моих use cases, про все остальные можно не беспокоиться), что я и сделал (можно слать сатоши на "адреса" вроде 79555555555@mobile.lnurl-pay.me, 38055555555@uamobile.lnurl-pay.me, 41111@uacard.lnurl-pay.me), andreneves@twitter торжественно пожертвовал 70 рублей на Sci-Hub (https://twitter.com/andreneves/status/1425909205593174022?ref_src=twsrc%5Etfw), и так далее и так далее. Всё это стало настолько обсуждаемым, что разработчики bolt12 срочно занялись своим вариантом поддержки user@domain и привязки таких "адресов" к офферам.

Кстати, уже после этого я прикрутил возможность указания суммы для моих "псевдоадресов" прямо в "имени юзера": 100rub-79555555555@mobile.lnurl-pay.me, 2.50usd-akovalenko@skype.lnurl-pay.me и так далее. История ходит кругами: когда-то ещё до lnurl-pay мы с fiatjaf интегрировали "рублевые платежи" в LNTXBOT через кустарый json-rpc, а теперь это можно просто взять и сделать из самого lntxbot (и ещё из множества мест), просто набрав такой вот адрес.

👍💡💯4
To react or comment  View in Web Client