Skip to content

Instantly share code, notes, and snippets.

@NCrashed
Created May 18, 2022 20:36
Show Gist options
  • Save NCrashed/630e0d643af14ade593f70babe38075b to your computer and use it in GitHub Desktop.
Save NCrashed/630e0d643af14ade593f70babe38075b to your computer and use it in GitHub Desktop.

Не так давно @lamed опубликовал здесь введение в bitcoin. Опираясь на него, попробую рассказать про Bitcoin Lightning Network. Едва ли у меня получится так же хорошо, но я буду ориентироваться на ту же пропорцию технической корректности, доходчивости и занудства, которую вижу там; также я буду опираться на изложенные там вещи как на общеизвестные.

Участники сети bitcoin могут участвовать в генерации новых блоков ("майнинге"), а могут следить за наращиванием цепочки блоков, всего лишь проверяя соблюдение правил майнерами. И те, и другие называются "полными узлами сети" (full node). Не-майнящий полный узел помогает прежде всего своему владельцу, удостоверяя, что полученные данные о транзакциях "настоящие" (если сама запущенная реализация полного узла настоящая). Также владелец узла может запрашивать баланс своих адресов, не боясь, что чужой сервер зафиксирует его интерес конкретно к этим адресам (даже если я скрываю от чужого сервера свой IP-адрес с помощью Tor, сама связь нескольких адресов друг с другом и их принадлежность к одному "кошельку" -- не то, чем я хотел бы делиться с кем попало).

Полные узлы полезны и друг другу: они получают друг от друга свежие блоки (и по желанию могут предоставить старые блоки для свежеустановленных узлов, только начинающих синхронизироваться), распространяют транзакции-кандидаты на включение в новый блок (опять же с проверкой допустимости каждой из них) и так далее. Поведение узла настраивается, поэтому, например, совсем "альтруистичные" движения вроде отдачи старых блоков владелец часто ограничивает ("отдавать не более 100 мегабайт в сутки"); с другой стороны, если узел полезен тем, кто к нему подключён, от него будут реже отключаться, а это полезно и ему с точки зрения быстрого, надёжного и своевременного оповещения о новых блоках и новых транзакциях-кандидатах.

Возможность держать полный узел на скромном "железе" очень важна для сети, и она требует, чтобы общемировая "бухгалтерская книга" росла не слишком быстро. Сейчас типичный блок, добываемый в среднем раз в 10 минут, имеет размер в полтора мегабайта. Размер простенькой транзакции с одним входом и одним выходом -- около 200 байт; соответсвенно, предел скорости сети -- около 10 простеньких транзакций в секунду. Можно было бы поменять параметры сети, например, в сторону увеличения блока, и один раз (2017) это было сделано: хитрым обратно-совместимым методом, когда узлы, использующие только старый тип адресов, видят часть "настоящего" блока и не проверяют движения по "новым" типам адресов, а обновившиеся узлы видят увеличенный блок целиком. Можно было бы увеличить частоту блоков, но только наплевав на обратную совместимость (этого не делали и скорее всего не будут). Можно ухищряться и извращаться со склеиванием множества простеньких транзакций в большие и сложные, на этом в общем случае экономится место, а заодно улучшается приватность. Подобные меры могут дать ускорение в разы, но не на порядки (а если на порядки, то придётся попрощаться с полными узлами на скромном железе). Мир так не захватить и даже не обогнать жалкий paypal или долбаный сбербанк. А нам довольно-таки обидно, поскольку захватить мир таки весьма желательно. Сия депрессивная реальность называется "проблемой масштабирования".

Ещё, разумеется, можно хранить bitcoin у доверенных посредников, которые совершают операции от нашего имени: вместо собственной коллекции приватных ключей мы имеем логин на веб-кошелёк или аккаунт на бирже; если два контрагента пользуются одним посредником, расчёты между ними никак не отражаются в цепочке блоков; расчёты же между посредниками тоже не обязаны быть мгновенными, если они в какой-то степени друг другу доверяют и ни у кого не кончаются резервы. Это фактически повторение истории с частными банками, со всеми её преимуществами и недостатками. Но при текущем размере блоков, например, ежедневно рассчитываться между собой по принципу "каждый с каждым" могли бы лишь чуть больше тысячи таких "банков" во всём мире.

Сеть Lightning -- одно из решений "проблемы масштабирования". Обходится ли она без доверенных посредников? С одной стороны, ни один участник сети не может просто взять и сбежать с деньгами другого; с другой стороны, злонамеренный или ненадёжный контрагент может доставить участникам много неприятных часов, минут или дней, "подвесив" идущий через него платёж или потребовав неожиданно большую комиссию; с третьей стороны, гадить контрагентам невыгодно, а выгодно пропускать платежи побыстрее и подешевле (но от внезапной аварии не застрахован никто).

Базовая конструкция сети Lightning -- так называемый "платёжный канал". В другом месте я уже писал, что проверка допустимости траты может быть сложнее, чем сопоставление одной электронной подписи одному публичному ключу; можно, например, построить адрес, осуществить трату с которого можем только я и @lamed вместе (договорившись о получателе), Предположим, я собираюсь регулярно платить @lamed; почему бы мне не начать с того, чтобы перевести, скажем, миллион сатоши (0.01 биткойна) на адрес, которым мы управляем совместно, договорившись с ним, что "вот сейчас там всё моё", потом передоговорившись, что "а теперь там 900000 сатоши моих, а 100000 сатоши его", и так далее?

В таком виде предложение выглядит бредятиной: если контрагент решит подзабить на биткойн и уйдёт выращивать кабачки, как Эркюль Пуаро, мне мало поможет то, о чём мы раньше договорились -- деньги застрянут на общем адресе навсегда. Если же у нас возникнет спор, сколько на общем адресе моих сатоши, а сколько его -- забрать каждому своё с совместно управляемого адреса не получится.

Добавим ещё один элемент в нашу конструкцию. Предположим, до того как вносить деньги на "общий" адрес, я обзаведусь подписанной @lamed транзакцией, которая мне возвращает мою часть, а ему -- остальное. Эта транзакция никуда не транслируется, я держу её "про запас", но если контрагент сбежит, я в любой момент могу выложить её в сеть, и рано или поздно её включат в блок. Если у контрагента есть такая же транзакция с моей подписью, то и он будет чувствовать себя спокойнее: пропаду ли я или заартачусь, всегда можно забрать свою часть назад.

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

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

Вот теперь мы сконструировали настоящий платёжный канал. Его создание потребовало одной транзакции, включаемой в цепочку блоков; я могу платить контрагенту сколь угодно мелкими порциями, договариваясь с ним о новом балансе, а он может платить мне; если один из нас исчезнет, второй сможет оттранслировать запасённую транзакцию и получить свою долю обратно (через некоторое время), вернув мне мою (сразу); если же мы просто договоримся, что канал нам больше не нужен, мы вдвоём можем оттранслировать обычную совместную трату наших доль на наши адреса (нам выгодно "закрывать" канал именно таким образом, потому что это единственный сценарий, когда ничьи деньги не "подвисают" и их можем сразу использовать мы оба). Таким образом, жёстий, неповоротливый и дорогой механизм биткойновой цепочки блоков (on-chain) выступает (1) в качестве "нотариуса", у которого мы регистриуем "совместное предприятие" и "разрыв совместного предприятия", и (2) в качестве "судьи", которого мы привлекаем к делу, если партнёр исчез или задумал нас облапошить.

Однако платёжный канал -- это ещё не сеть. Хорошо, когда можно открыть канал к телефонной компании, или к кафешке, или к табачному ларьку, и потратиться на on-chain комиссию пару раз вместо пары тысяч раз -- но учитывая, что со многими контрагентами я взаимодействую нерегулярно и что их в принципе довольно много, наше решение проблемы масштабирования ещё не впечатляет. Сеть же начинается с такой идеи: если у меня есть канал до телефонной компании, а у @lamed есть канал до меня, почему бы ему не заплатить мне и не попросить меня заплатить телефонной компании от его имени? Но тут у нас опять же вылезает проблема доверия (о которой, как водится, есть рассказ Аверченко.

Как мы видели выше, грубую идею, в которой вылезает проблема доверия, не обязательно сразу выбрасывать: её можно попытаться усовершенствовать. Пересылка платежей сейчас решается с помощью ещё одной хитрой конструкции bitcoin script, которая называется hashed time-locked contract (HTLC).

Транзакцию "за пазухой", раздающую каждому свою долю, можно сконструировать с дополнительным условием: если я знаю прообраз некоего криптографического хэша SHA256 и успеваю предъявить его за какое-то время, мне причитается немножко больше денег, чем если я его не знаю (а контрагент обязан подождать до дедлайна, чтобы выяснилось, успел ли я его узнать или не успел). С таким дополнением оказывается, что передача платежа через посредника за некоторый срок и некоторый процент комиссии уже не требует полного доверия:

  1. Телефонная компания присылает моему контрагенту счёт на 500000 сатоши, в котором указан SHA256-хэш от секретного прообраза (его знает только компания)
  2. Контрагент знает, что у нас с ним есть канал, где ему принадлежит 600000 сатоши, и что у меня есть канал с телефонной компанией, и что за 0.1% я готов передать его платёж
  3. Контрагент подписывает "усложнённую" транзакцию для хранения у меня за пазухой, по которой я могу разорвать контракт с текущим балансом через 5 часов, а могу за 5 часов предъявить прообраз хэша и разорвать контракт с более выгодным мне балансом (где мне причитаются лишние 500050 сатоши). Он также сообщает мне, что я должен передать телефонной компании 500000 сатоши.
  4. Я подписываю "усложнённую" транзакцию для телефонной компании, по которой она может либо разорвать контракт через 4 часа, либо предъявить прообраз хэша и разорвать его более выгодно, забрав 500000 сатоши премии.
  5. В норме телефонная компания "опознаёт" прообраз хэша из своего счёта, который она же и выставила, предоставляет его мне (при этом мы "передоговориваемся" о новом балансе канала уже штатным способом, без "усложнённого адреса-отстойника"), я предоставляю его моему контрагенту (при этом мы опять же "передоговариваемся" с контрагентом о новом балансе), в результате телефонная компания получает 500000 сатоши, мой контрагент имеет на руках прообраз хэша, который может служить доказательством платежа, а я получаю 50 сатоши за маршрутизацию.

Когда я получил от телефонной компании прообраз хэша, могло оказаться, что у моего контрагента умер сервер или он уехал в отпуск выращивать кабачки. Благодаря тому, что у меня за пазухой лежит транзакция с HTLC, я могу всё-таки получить компенсацию своего платежа и комиссии даже от исчезнувшего контрагента, просто оттранслировав её в сеть для включения в блок. При этом в блокчейне окажется опубликован прообраз хэша, благодаря чему вернувшийся из отпуска контрагент всё равно получит доказательство платежа, хотя он и вёл себя, как свинья (он будет наказан тем, что у нас с ним теперь нет прямого канала).

Коварная телефонная компания, получив от меня HTLC для хранения "за пазухой", может почему-то решить оттранслировать её on-chain, вместо того чтобы договариваться со мной о новом балансе. Но при этом ей придётся опубликовать прообраз хэша, которым я воспользуюсь в своих расчётах с контрагентом (и если контрагент не коварен и не исчез, наш канал с контрагентом останется в живых -- а вот наш канал с телефонной компанией накроется, ну и ладно, она вела себя как свинья, а кому нужны каналы со свиньями).

Я могу обнаружить, что в нашем платёжном канале с телефонной компанией мне принадлежит меньше 500000 сатоши и поэтому я не могу маршрутизировать платёж (об этом не мог заранее знать мой контрагент, потому что баланс конкретного канала в норме знают две его стороны и больше никто). Тогда я не должен передавать ей HTLC, а должен, напротив, сказать контрагенту, что посредничество не состоялось. При этом я отказываюсь от своего входящего HTLC, тем же способом, которым обычно отказываюсь от других устаревших "транзакций за пазухой" (отдавая контрагенту приватный ключ от старого адреса-отстойника). Таким образом, отмена платежа по желанию левой пятки посредника -- действие быстрое, нерискованное и бесплатное для отправителя, который может тут же попытаться отправить платёж другим маршрутом.

Пойти не так может многое, и худший случай довольно неудобен. Если я приму HTLC и тут же отправлюсь выращивать кабачки, контрагент 5 часов будет в неведении, дошёл его платёж или нет и есть ли у него вообще шансы. Если телефонная компания задумается после принятия своего HTLC, в неведении будем мы оба с контрагентом. Если я решу, что теперь моя комиссия целых 5%, а у контрагента всего один канал -- он может решить послать меня на фиг и канал закрыть, а я могу не кооперироваться при закрытии, так что ему придётся ждать несколько дней, чтобы увести свой баланс с "адреса-отстойника". Но за счёт того, что гадить не выгодно, а драть много неконкурентоспособно, худшие случаи бывают очень редко.

Мы не обсудили ни коммуникацию между узлами сети, ни построение маршрута, ни доставку платежа частями, ни стандартный формат инвойса с прообразом хэша -- но это уже технические детали; мне кажется, что цель "держаться вровень с текстами @lamed[Shmuel Leib Melamud] по детализации" противоречит тому, чтобы в них углубляться.

Решает ли Lightning проблему масштабирования? При всех оговорках и недостатках, это именно искомое повышение количества платежей не в разы, а на порядки. И всё-таки... открыть восемь миллиардов каналов можно лет за 30, если весь блокчейн будет заполнен открытиями каналов -- это слишком долго, да и по одному каналу на нос всё равно как-то маловато. Но "узким местом" в деле захвата мира пропускная способность ещё долго не будет.

P.S. Самое неаккуратное упрощение, допущенное мною выше -- предположение, что можно заранее подписать транзакцию, которая тратит некий адрес ("весь баланс адреса"). На самом деле заранее подписать можно только трату конкретного выхода другой конкретной транзакции. Тем не менее, с учётом общепринятой рекомендации не переиспользовать адреса, и с учётом того, что для внутренностей протокола lightning это правило жёстко соблюдается софтом -- в каком-то смысле "адреса" и "выходы" можно считать взаимозаменяемыми, и такое упрощение допустимо.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment