在上一篇文章中我们评论道:闪电网络是一个丰富的生态,将来里面会有各种各样的角色参与其中;目前来看,如何注入足够Inbound Capacity,保持闪电网络有充裕的流动性似乎是个棘手问题;而且不少人攻击这最终会导致比特币运营中心化;
为了研究如何解决Inbound Capacity的问题,我们还需要复习并深化闪电网络的一些基础知识;即RSMC 和HTLC;这些基础我们曾在之前的文章中简要介绍过,但只是对网上的资料做了一番整理,人云亦云而已,实在是辜负了闪电网络;
如果把闪电网络比作一支跌宕起伏的乐章,那RSMC和HTLC就是其中最华彩的两个小节,围绕这两个基础技术所衍生的种种细节Tooltips就是其中的伴奏,整个乐章美不胜收。那么,让我们沉下心来仔细体味吧;
让我们再回忆一下链下(off-chain)交易的双方要解决的信任问题:Alice和Bob想要实现公正的,双方都无法作弊的多次交易,他们需要做什么;
首先他们需要向对方展示一下自己有支付的能力,放到现实世界里面做个类比,我们买房摇号时要冻结一笔保证金,这样双方能够放心;
买房者的保证金放到银行账户上,银行来确保开发商不会挪用,但在去中心化的世界里面,如何向对方证明自己的资金实力呢?毕竟,比特币世界里面,可没有中心化的银行;
这需要双方各自将一笔保证金打到一个2-2 多重签名地址当中,这样只要这笔资金不被挪用,就会在这段时间内表明双方的资金实力;
这笔交易被称之为Funding Tx,一般翻译为保证金交易;Funding Tx交易输出的资金需要Alice和Bob两个人的签名同意才能动用;
Funding Tx构造之后,最终资金如何花费需要取得双方的同意,当双方产生分歧时,这笔钱就有可能冻结在这个地址永远无法花费,所以此时还不能广播上链;所以支付通道建立初期, 状况是这样的:
Funding Tx需要解决几个问题,才能让人放心大胆的广播出去:
损人不利己
– 永远不同意释放这笔资金,然后这笔钱就一直冻结在这里了;于是我们产生了一个初步的方案:
Alice构造了C1a交易,这笔交易的Input是Funing Tx的output,但是输出上做了限制,它将资金分配到了两个output:
这样的交易输出保证Bob不会吃亏,在任何情况下,Alice解约,Bob会立即收到自己的0.5BTC;
但是Alice呢?Alice的0.5BTC怎么办呢? Alice会对Bob说:我够意思吧,任何情况下,你都能拿回自己的0.5BTC,那么,你如果能对 Alice2&Bob的Output提供签名,那就太好了,这样我们就能达成交易了,这样任何时候都不会让你吃亏;
同样的,Bob也会构造C1b交易,他会说:好吧,Alice,你确实是个诚信的人;你的做法启发了我,为了能随时解约,我也同样构造了一笔交易,无论何时,你都能拿回自己应得的0.5个BTC,而这笔交易也需要你签署一下;
双方的这两笔交易我们称之为 Commitment Tx(承诺交易);
我们可以想象,Alice和Bob两个人高高兴兴的互相为对方的交易签名,并为合作双方不用依靠别人就达成了如此巧妙而公平的交易而洋洋自得;
此时,无论是Funding TX,还是双方的Commitment Tx,都还没有广播出去; 但是似乎在达成了Commitment Tx之后,可以广播Funding Tx了;
但是且慢!! 我们建立资金通道的目的是什么?
当然是为了解决资金的链下双向流通问题,目前双方在不依靠第三方公证人的前提下,成功冻结了一笔保证金,可是怎么让这笔保证金流动起来呢?
聪明的Alice和Bob再次对他们的通道交易做了升级,现在通道里面的交易变成了这样:
在Alice一方, 又增加了一笔交易, 即RD1a;
RD1a消费了C1a的第一个输出,即 Alice2 & Bob 0.5btc
, 而RD1a直接输出给Alice,但是这笔交易有一个限制条件,即seq=1000,即如果广播C1a的话,要等1000个block之后,RD1a才会生效;
C1a, C1b两笔交易花费的是同一个输出,故他们两个交易只有一个能进块。若Alice广播C1a,则Bob立即拿到0.5BTC(C1a的第二个输出),而Alice需要等C1a得到1000个确认,才能通过RD1a的输出拿到0.5BTC。另一方,若Bob广播C1b,则Alice立即拿到0.5BTC,Bob等待C1b得到1000个确认,才能通过RD1b拿到0.5BTC。也就是说,单方广播交易终止合约的那一方会延迟拿到币,而另一放则立即拿币。
上述过程以及结构图的描述,就是创建RSMC的全部过程。
Alice和Bob各自0.5BTC的余额,此时Alice从Bob处购买了一件商品,价格为0.1BTC,那么余额应该变为Alice 0.4BTC,Bob 0.6BTC。
于是创建新的Commitment Tx,对于Alice来说是C2a 和RD2a,对于Bob来说是C2b和RD2b,过程与上面类似。
交易更新时的交易结构–此时两个状态均是有效的,那么最核心的问题来了,如何才能彻底废弃掉C1a和C1b呢?
RSMC采用了一个非常巧妙的方法,在C1a的第一个输出中,采用了Alice2和Bob的多重签名,Alice将Alice2 的私钥交给Bob,即表示Alice放弃C1a,承认C2a。
Alice交出Alice2的私钥给Bob,那么Bob就可以修改RD1a的输出给他自己,形成新的交易BR1a。
若Alice破坏合约存在C2a的情况下依然广播出C1a,那么Alice的惩罚就是失去她全部的币。
Alice交出Alice2的私钥,或者对交易BR1a进行签名,两者是等同的,都是对C1a的放弃。反之亦然,Bob交出Bob2的私钥给Alice即意味放弃C1b,而仅能认可C2b。
引入sequence的目的是,阻止后续交易进块(RD1a),给出一个实施惩罚窗口期,当发现对方破坏合约时,可以有1000个块确认的时间去实施惩罚交易,即广播BR1a代替RD1a。若错过1000个块时间窗口,则无法再实施惩罚了(RD1a进块了)。
Alice和Bob两人通过不断的协商和推敲,最终建立了这样一个通道:
在达成了Funding Tx、C1a, C2a, RD1a, RD2a 这些交易之后,Alice和Bob两人就可以广播Funding Tx了,这是唯一需要广播的交易,建立通道后,双方所有的交易就是更新Commitment Tx的过程了,这些更新都可以通过链下完成,交易速度理论上只取决于Alice及Bob两方的网络和机器性能,可以很轻易的提升至数千TPS;
RSMC要求交易的双方一定要都缴纳一笔保证金,我每天都跟不同的商家打交道,不能跟每个人都去建立RSMC,存入一笔资金吧。而且通道的建立和关闭都是需要链上广播的,如果要建立多个支付通道,交易费用也不容小觑,这有点本末倒置了吧。
为了解决这个问题,闪电网络又引入了HTLC ( Hashed Timelock Contract ),中文意思是“哈希的带时钟的合约”。这个其实就是限时转账。理解起来也很简单,通过智能合约,双方约定转账方先冻结一笔钱,并提供一个哈希值,如果在一定时间内有人能提出一个字符串,使得它哈希后的值跟已知值匹配(实际上意味着转账方授权了接收方来提现),则这笔钱转给接收方。
推广一步,甲想转账给丙,丙先发给甲一个哈希值。甲可以先跟乙签订一个合同,如果你在一定时间内能告诉我一个暗语,我就给你多少钱。乙于是跑去跟丙签订一个合同,如果你告诉我那个暗语,我就给你多少钱。丙于是告诉乙暗语,拿到乙的钱,乙又从甲拿到钱。最终达到结果是甲转账给丙。这样甲和丙之间似乎构成了一条完整的虚拟的“支付通道”。而乙就做了中转节点。
Alice想要支付0.5BTC给Bob,但她并没有一个渠道来和他进行交易。幸运的是,她和Charlie有一个交易渠道,而Charlie正好和Bob有一个交易渠道。这样Alice就能借助Charlie的交易渠道,通过哈希时间锁定合约(HTLC)来和Bob进行交易了。
为了完成这次交易,Alice就会给Bob发短信说:“嘿!我要给你付笔款。”这时Bob自己将收到一个随机数字(R),接着Bob便会回一个被哈希的数字(H)(你可以认为被哈希的数字R是随机数字的一种加密形式)给Alice。
然后Alice的钱包紧接着就会联系Charlie说:“嘿,Charlie。如果你给我生成(H)的未加密值(R),那么我就同意更新我们渠道的支付分配,这样你就可以得到的就会比0.5BTC多一点,我得的比0.5少一点。”
尽管Charlie并不知道R,但他也会同意。之后Charlie便会去找Bob说:“嘿,Bob。如果你给我那个能生成H的未加密的值R,我将同意更新我们渠道的支付分配,这样你就可以得到的会比0.5BTC多一点,我得到的比0.5少一点。”因为R就是从Bob这里生成的,所以他肯定知道。接着他马上将R告诉Charlie,并更新了其渠道的支付分配。然后Charlie将R告诉给了Alice之后也更新他们的渠道,最后交易完成,Alice以脱链的形式付给Bob0.5BTC。
交易中转说起来很简单,但它也要解决一些工程细节问题:
让我们用一个更复杂的例子来好好理清这个交易过程吧!
在这个例子中,我们有五个参与者:Alice、Bob、Carol、Diana和Eric。这五名参与者彼此之间已经开设了支付通道。Alice和Bob有支付通道。Bob连接Carol,Carol连接到Diana,Diana连接Eric。为了简单起见,我们假设每个通道每个参与者都注资2个比特币资金,每个通道的总容量为4个比特币。
现在Alice想要给Eric支付1个比特币,通过我们的交易中转机制,她发现了一条支付通道:Alice->Bob->Carol->Diana->Eric;然后支付步骤如下:
在没有向Eric打开支付通道的情况下,Alice已经支付给Eric 1比特币。付款路线中的中间方无须互相信任。在他们的通道内做一个短时间的资金承诺,他们就可以赚取一小笔费用,唯一的风险是,如果通道关闭或路由付款失败,退款有段短的延迟时间。
LN节点之间的所有通信都是点对点加密的。另外,节点有一个长期公钥,它们用作标识符并且彼此认证对方;
在前面的例子中,Alice的节点使用这些路由发现机制之一来查找将她的节点连接到Eric的节点的一个或多个路径。一旦Alice的节点构建了路径,她将通过网络初始化该路径,传播一系列加密和嵌套的指令来连接每个相邻的支付通道。
重要的是,这个路径只有Alice的节点才知道。付款路线上的所有其他参与者只能看到相邻的节点。从Carol的角度来看,这看起来像是从Bob到Diana的付款。Carol不知道Bob实际上是中继转发Alice的汇款。她也不知道Diana将会向Eric中继转发付款。
这是闪电网络的一个重要特征,因为它确保了付款的隐私,并且使得很难对其应用监控、审查以及黑名单机制。但是,Alice如何建立这种付款路径,而不向中间节点透露任何内容呢?
闪电网络实现了一种基于Sphinx 方案的洋葱路由协议。该路由协议确保支付发送者可以通过闪电网络构建和通信路径,使得:
使用这种洋葱路由协议,Alice将路径的每个节点信息封装在一层加密中,从尾端开始倒过来运算。她用Eric的公钥加密了Eric的消息。该消息封装在加密到Diana的消息中,并将Eric标识为下一个收件人。给Diana的消息封装在加密到Carol的公钥的消息中,并将Diana识别为下一个收件人。对Carol的消息被Bob的密钥加密。这样一来,Alice已经构建了这个加密的多层“洋葱”的消息。她发送给Bob,他只能解密和解开外层。在里面,Bob发现了一封给Carol的消息,他可以转发给Carol,但不能自己破译。按照路径,消息被转发、解密、转发等,一路到Eric那里。每个参与者只知道各自这一跳的前一个和下一个节点。
路径中的每个节点都包含有关HTLC必须扩展到下一个跳的信息,HTLC中的要发送的数量,要包括的费用以及CLTV锁定到期时间(以块为单位)。随着路由信息的传播,节点将HTLC承诺转发到下一跳。
此时,你可能想知道节点如何不知道路径的长度及其在该路径中的位置。毕竟,它们收到一个消息,并将其转发到下一跳。难道它不会将路径缩短,或者允许他们推断出路径长短和位置?为了防止这种情况,路径总是固定在20跳,并用随机数据填充。每个节点都会看到下一跳和一个要转发的固定长度的加密消息。只有最终的收件人看得到没有下一跳。对于其他人来说,似乎总是有20多跳要走。
中转交易和路由加密技术解决了回退交易以及隐私保护的问题;
那么,剩下的就是最重要的问题,就是如何在双方的支付通道中,在Commitment Tx的基础上,构造HTLC交易,同时不会影响原有Commitment Tx的所有功能?
我们即将探究闪电网络最后的,也是最精细的一个部分,就是HTLC的细节实现,堪称这支交响乐中最细腻的部分;
让我们再从头想想吧, HTLC本质上也是一个双方资金分配的问题,只不过这笔资金的解锁需要一个符合散列H值的原像R值,这就是一笔P2SH交易而已;
但普通的P2SH交易并不能满足这个场景的所有需求,因为我们还需要在交易失败的时候设置回退机制,资金还要能回退回来;
回到我们上图的例子中,在路由的第一跳,我们想象中的Alice付给Bob的HTLC交易逻辑是这样的:
1 2 3 4 5 6 | OP IF OP HASH160 <Hash160(R)> OP EQUALVERIFY 2 <Alice2> <Bob2> OP CHECKMULTISIG OP ELSE 2 < Alice1> <Bob1> OP CHECKMULTISIG OP ENDIF |
---|
这个脚本从单一的 HTLC 输出花费有两种可能的路径:
那么让我们再次继续RSMC中的例子,假设Alice和Bob已经在Funding Tx的基础上建立了Commitment Tx,并且每个人都注入了0.5BTC,现在Alice想要为Bob构造一个输出为0.1BTC的HTLC,细节是这样的:
这个图跟我们在更新Commitment Tx的时候没有多大区别,最大的不同就是我们多了一个Output: 即C2a,C2b中的Output2;
这个输出就代表着0.1BTC的HTLC的完整逻辑;要花费它可以有两种方式,就是我们上面所定义的两种路径:
我们以Alice的视角,来观察C2a交易中的HTLC输出,拆分为HT1a和HED1a;
HED1a的输出很简单,就是只需要Bob2签名以及原像R,Bob就能立即得到0.1BTC;
HT1a就复杂了,它解决了交易失败时,如何交易回退的问题:
让我们仔细再想想,支付通道更新的过程中,C1a,C2a, C1b, C2b等等交易完全是不上链的,如果Bob没有在指定时间内提供原像R,Alice如何收回自己的0.1BTC呢?
等等,这个问题好熟悉的感觉!! 没错,这个问题其实与我们如何花费Funding Tx如出一辙! 对于HTLC交易的处理跟Funding Tx的处理可以采取一样的方法:
经历了漫长的探索,我们最终得到了闪电网络白皮书中这样一张大图:
对于付款人(Alice),“中转”交易作为 HTLC 执行交付交易(HED1a)被发送,其不受阻于 RSMC。假定支付路径成立,而且 Alice 证明公布的承诺交易C2a是最近的话。如果 Bob 可以产生原像 R,他将能够在该承诺交易C2a在 blockchain 上公布之后赎回资金。当然只有 Alice 给 Bob她的 HED1a 签名Alice2,Bob 才可以公布 HED1a。
如果交易失败, Alice 公布她的承诺交易HT1a,本次交易需要multisig(Alice1,Bob1)。但是,需要三天的锁定期,Alice才可以公布HTLC Timeout交易(HT1a)。这项交易是一个 RSMC。HT1a的输出HTRD1a需要Alice3和Bob3的 multisig,并且1000个block之后才能入块; 这样为后面的再次调整HTLC金额留出了余地,即本次交易可以撤销,当另一个使用 multisig(PAlice4,PBob4)的交易取代 HTRD1a,它没有对任何区块的成熟度要求。所以Bob也可以放心的签署HTRD1a;
对于收款者(Bob),收到的“Timeout” 作为 HTLC Timeout 交付交易(HTD1b)被退还。本次交易直接返还资金给原始发件人(Alice),并不受 RSMC 的阻碍。假定该 HTLC从未被 Off-chain 终止,因为 Bob 证明公布的承诺交易(C2b)是最新的。如果 3 天已经过去,Alice 可以公布 HTD1b 并拿到退款。如果 Bob 公布 C2b,本次交易需要 multisig(PAlice5,PAlice5)。只有 Alice 可以公布 HTD1b,因为 Bob 给了 Alice 他 HTD1b 交易的签名。
但是,如果 HTD1b 没有被公布(没有经过 3 天时间)并且 Bob 知道原像 R,如果他能产生R,则 Bob 将能够公布 HTLC 执行交易(HE1b)。这项交易是一个 RSMC。如果 Bob 公布C2b,它需要输出 multisig(Alice6,Bob6),并要求披露 R。此交易的输出是一个有 1000个区块相对成熟的 multisig(Alice7,Bob7)的 RSMC,和不需要区块确认成熟的 multisig(Alice8,Bob8)。只有 Alice 给 Bob 她 HT1a 的签名,Bob 才可以公布 HT1a。HT1A 进入 blockchain 并且 1000 次确认完成后,一个 HTLC Timeout 撤销交付交易(HERD1b)可以由 Bob 通过消耗 multisig(Alice7,Bob7)公布。只有 Alice 给 Bob 他 HERD1b 的签名,Bob 可以在公布 HE1b 1000 区块后公布 HERD1b。本次交易可以撤销,当另一个使用multisig(Alice8,Bob8)的交易取代 HERD1b,它没有对任何区块的成熟度要求。
HTLC 构造之后,为了终止 HTLC Off-chain 需要双方同意渠道的状态。如果收件人可以向对方证明 R 的信息,证明他们能够立即关闭比特币 blockchain 上的渠道并且接收资金。在这一点上,如果双方都希望保持渠道打开,就应终止 HTLC Off-chain,并创建一个新的承诺交易反应新的平衡。
由于 Bob 向 Alice 证明,以告诉 Alice R 的有关信息来告诉 Alice,Alice 愿意用新的承诺交易更新平衡。此时不管公布 C2 或 C3,支付将是相同的。
同样,如果收件人不能够通过公开 R 来证明 R 的信息,双方应同意终止 HTLC 并创建一个新的承诺交易, HTLC 中的余额退还给发件人。
如果交易对手不能达成协议或不回应,他们应该通过在比特币 blockchain 公布必需的渠道交易来关闭渠道。
但是,如果他们合作,他们可以通过首先生成具有新的平衡的承诺交易,然后通过交换违约补救交易(BR2a / BR2b)使先前承诺失效。此外,如果他们终止特定的 HTLC,也要交换一些在 HTLC 交易中使用的自己的私钥。
为了研究解决Inbound Capacity的问题,我们不可避免的又把闪电网络的细节回顾了一遍;我们需要理解HTLC;但文章也因此拉长了,我们把解决方案的探讨留给下一篇文章。
https://github.com/lightningnetwork/paper
https://blog.lightning.engineering/posts/2018/05/30/routing.html
https://blog.lightning.engineering/technical/posts/2019/04/15/loop-out-in-depth.html