作者:SHINOBI
来源:https://bitcoinmagazine.com/technical/using-dns-to-coordinate-bitcoin-payments
Matt Corallo 在几周前提出了一项用于协调比特币支付的 BIP。出于许多原因,不论是链上支付还是链下支付(比如使用闪电网络这样的协议),发起比特币支付在协调上始终有一些难点。在使用电子系统,比如电子邮件和 Paypal、Cashapp 这样的支付系统时,人们非常习惯于有一个静态的标识符。如果你想要给 John 发送一封 email,你只需要给 “john@【邮箱域名】” 发送即可。或者,如果你想在 Cashapp 上给 John 转一些钱,你只需在 Cashapp 中找到 John 的账户,然后发送支付就可以了。
这就是人们熟悉的用户体验,而且,在涉及这样根深蒂固的用户行为和期待的时候,要让他们立即改变行为会更加困难。如果你给他们的工具要求他们的行为有实质上的变化,它会产生大量的摩擦,而且很有可能直接劝退了大部分人。
链上支付与这种期待相悖,不是因为无法拥有一个静态的标识符(比如地址),而是因为只使用一个链上地址、让跟你有关系的每个人都使用它会带来严重的隐私问题。这会让你的所有支付记录和资金所有权都暴露给跟你交互过的人。如果你只是偶尔收款,比如用比特币来接收工资或收账,这就不是什么问题,你只要打开钱包软件,生成一个新地址就好。但如果你要频繁收款,尤其是在你不会直接请求支付的时候,这会变成一个沉重的负担。
这就是 BTCPay Server 这样的工具诞生的原因:为了降低人们搭建所需的基础设施来自动化收款的门槛,同时保证不犯低级错误,例如给每个要支付给你的人出示同一个地址。然而,这还是需要用户运行一个持续联网的服务端。虽然 BTCPay Server 已经极大地降低了理解的门槛,这对只是希望能够被动收款的用户来说依然是很大的负担。
闪电网络也是一样的(只会更糟)。一张闪电发票只能用在一次收款中。链上地址是可以复用的,只不过复用是个糟糕的习惯;而闪电发票直接是不能复用的。一旦发票被支付过了,或者过期了,签发这张发票的闪电节点会直接拒绝凭此发票发起的支付尝试。这个机理导致了 LNURL 规范(以及在它基础上的 Lightning Address)的建立。LNURL 是一套通过一个静态 IP 来连接一个 HTTP 服务端的协议;这个静态的 IP 只需分享一次,就可以从目标服务器拉取一张真实的闪电发票,然后就可以按发票发起支付了。在此基础上,Lightning Address(“闪电地址”)是一套命名方案,结构上类似于邮件地址:John@【LNURL 服务端域名】。
所有这些解决方案都有缺点。除了运行你的比特币钱包软件和闪电节点,你还需要运行额外的软件(一个 HTTP 服务端)、保证它全时在线;向接收方的 BTCPay/LNURL 服务端请求支付也会泄露支付方的 IP 地址;此外,还需依赖于 TLS 证书权威。
像 LNURL 这样的 HTTP 服务端工具,在解析闪电地址时,会使用域名(domains)来解析连接到 HTTP 服务端的连接。类似地,BTCPay Server 也全部配置呈使用域名,而不是使用裸的 IP 地址。Matt 的洞见是,为什么我们不减少对 HTTP 的依赖,而直接使用“动态域名系统(DNS)” 本身呢?
DNS 允许你将一段 TXT 记录跟一个给定的域名关联起来,因此可以从 DNS 服务器请求小体积的、人类(或者机器)可读的记录。再结合 “动态域名系统安全插件(DNSSEC)”,DNS TXT 记录提供了一项机制,可以用来查询支付信息,而无需运行一个 HTTP 服务端,同时也提供了多一些灵活性和开放性。DNSSEC 为密码学签名的 DNS 条目提供了许多工具,包括 TXT 记录,同时 DNS 的层级式架构中还内置了 DNS 公钥。这保证了你查到的 TXT 记录就是被 根服务器/公钥 签名过的、分发到低层级 DNS 服务器的记录。
这就是使用 SND 作为一种拉取支付数据的手段的真正好处:跟运行一个 HTTP 服务端的要求说再见。TXT 记录可以编码一个链上的比特币地址(虽然该 BIP 明确反对这样做,如果你不能定期轮换新地址以避免地址复用的话),但更重要的是,它还可以包含一个 BOLT12 闪电要约。
这些记录可以从任何 DNS 服务器、你自己的本地服务器、你的互联网服务商、甚至是谷歌和 Cloudflare 这样的公开服务器上拉取得到。从这个基本角度看,基于 HTTP 的解决方案的一个缺点就已经解决了;你不会再向自己尝试支付的人泄露自己的 IP 地址。现在,在使用你的互联网服务商的 DNS 或者谷歌、Cloudflare 的公开服务器时,如果你不使用虚拟私人网络或者 Tor,你就会向它们泄露你的 IP 地址;也正因此,该 BIP 建议支持通过一个虚拟私人网络或者 Tor 来运行 DNS 解析。
将这个提议与 BOLT12 相结合,你就不再需要运行附属软件了(这些软件对于业余用户来说是非常真实的安全性问题),并且仅凭域名的所有权就获得用户所需的一切:用一段简单的、肉眼可读的标识符来定位支付信息。BOLT12 不要求 HTTP 服务端,它在直接通过闪电网络的洋葱路由连接中发放真正的发票;而且,它支持 “要约(Offer)”,这种静态的标识符可以用来找出通达这个闪电节点的洋葱路径。问题在于要约是被编码成大量看起来随机的字符串的,就跟发票一样,所以它不是一个易读的标识符,而只适合于扫码或者 复制-粘贴。
通过在一个 DNS TXT 记录里存储一个要约,要向该用户支付的人只需将 TA 的域名输入自己的钱包软件,然后钱包软件就可以拉取 TXT 记录、拉取 BOLT12 要约,然后发起支付。除了闪电节点,不需要运行任何服务端、任何额外的软件,DNS 系统会为用户处理好一切,只需托管一个想要给该用户支付的人能找到的 BOLT12 腰围。
这是一个完美的免信任的系统嘛?不是。但它是不是比基于 HTTP 的系统更好?显然。这些问题的根结在于,大部分人对周围的电子系统有一个印象,这使他们对用户体验和可行操作产生了特定的期待。如果不复制这样的用户体验,大部分人都会直接使用能够满足这种用户体验期待的方案(而不是适应原生的体验)。给定这个现实,要把比特币塞进这个用户体验期待的框框内,设计目标就应该是以最少的信任因素、最小的用户负担、最少的潜在隐私性损失,满足用户的需要。我认为,与现有的解决方案相比,Matt 的 BIP 最贴合这个框框。
(完)
]]>作者:ACINQ
来源:https://acinq.co/blog/phoenix-swaproot
摘要:在链上向 Phoenix 钱包存入资金现在变得更便宜(*),也更隐私,这都得益于过去几年添加到比特币和闪电网络上的强大新功能的组合。
(*)在 swap 交易只有一个输入时便宜了 16%,有两个输入时便宜了 23%,而在三个输入时便宜了 27%。
去年,我们为 Phoenix 钱包软件发布了一个重大升级(中文译本):用户可以只使用一条通道,通道的容量可以按需扩大和缩小。
在那次升级中,一项关键的特性就是免信任的、即时的 “swap-in”:当资金被发送到你的钱包的链上地址(swap-in 地址)时,这些资金会通过 swap-in 交易 “拼接” 进入你已有的闪电通道。
但那一套 swap-in 协议还不能尽如人意:
使用 Taproot、MuSig2 以及描述符,我们设计并实现了一种新的 swap-in 协议:swap-in 交易现在变得更加便宜,也更难跟踪,而且 Phoenix 钱包会在每次你想要链上收款时为你生成一个新的 swap-in 地址。
一笔 swap-in
交易就是一笔链上交易,可以用来为一条闪电通道增加资金。Phoenix 使用了一套基于 “swap-in potentiam”(中文译本)的免信任的、即时的 swap-in 协议。
这套协议跟 “零确认”的理念是兼容的,而 “零确认” 是 Phoenix 钱包使用体验的一个关键:在 swap-in 交易还在等待链上确认时,通道依然是可用的;一旦交易获得确认, Phoenix 钱包就会触发零确认的通道拼接(默认拼入通道的资金已经可用)。
之前,我们使用的是一种 pay-to-script 构造,脚本是 用户公钥 + 服务商公钥 OR 用户公钥 + 时延
。这里,“用户” 指的是 Phoenix 钱包的一个用户,而 “服务商” 指的是 ACINQ 闪电节点。
有两种办法可以花费这个脚本:
但这种设计有一些缺点:
“Taproot”(详见 BIP 340 和 BIP 341)是比特币的一个重大升级,为比特币协议带来了许多提升,包括:
最后一项对我们的新的 swap-in 协议非常关键。Taproot 引入了 key-path spending
(使用公钥来花费)和 script-path spending
(使用脚本来花费)的概念。
在以往的比特币交易中,你只能在 pay-to-script 和 pay-to-public-key 之间选择一种,但有了 Taproot,你可以两者都要。你可以实现 pay-to-public-key OR pay-to-script
这样的协议,这样,当你在使用 pay-to-public-key 条件时,你的花费交易将跟其它 pay-to-public-key 构造的花费交易没有区别。
所以,回到我们的 swap-in 脚本:用户公钥 + 服务商公钥 OR 用户公钥 + 时延
。为了在合作情形中使用 key-path spending
,我们需要把 用户公钥 + 服务商公钥
换成一把公钥。这是怎么做到的呢?
有了 Schnorr 签名,加总公钥和签名就变得非常简单。事实上,如果你用 N 个私钥对同一条消息生成了 N 条签名,你可以把这 N 条签名全部加起来(成为 “聚合签名”),然后把对应的 N 个公钥全部加起来(成为 “聚合公钥”),这个聚合签名就是这个聚合公钥的有效签名。
非常非常简单 …… 而且根本不安全!
事实证明,因为加总非常简单,所以在你使用公钥 P
的时候,攻击者可以使用 -P
,把你的公钥 “取消掉”。
因此,我们需要 “MuSig2”,这是一种用来聚合签名和公钥的算法,可以证明是安全的,而且已经在生产环境中就绪了。
所以,有了 MuSig2,我们可以真的把用户公钥和服务商公钥聚合成一个公钥,然后用在 key-path
花费中,然后我们的脚本就变成了:用户-服务商 聚合公钥 OR 用户公钥 + 时延
:
在合作情形中,使用 swap-in 资金的通道拼入交易会变得更加便宜,而且跟其它的 pay-to-taproot-address 交易没什么区别。很棒!
但 swap-in 地址能不能换呢?如何既能轮换地址,又能提供一种通用的资金复原流程、保证可以扫描所有可能的 swap-in 地址?
“描述符” 是一种简单的语言,可以用来描述标准钱包的范式,包括 BIP32 密钥生成的模式。
描述符可以使用 “miniscript” 来描述大部分标准的脚本范式。Miniscript 支持 Taproot,但(还)不支持 MuSig2:它可以显式地说明 key-spend 公钥以及 script-path 脚本(包括地址的生成)。
为了让我们的 swap-in 协议与 miniscript 描述符兼容,我们只需要为合作情形使用一个固定的用户公钥(*),而在退款分支中可以使用不同的退款公钥(可以使用 BIP32 方案),从而:
这就是 swap-in 复原描述符的样子:
tr( // 使用固定的 key-path 花费密钥 1fc559d9c96c5953895d3150e64ebf3dd696a0b0...48ff6251d7e60d1, // 下面则是 secipt-path 花费脚本 and_v( // the xprv with the derivation path v:pk(xprvA1EfxcCy5HJnYBfPmwi9iXAyCktUSN...tvYsWqFTu29/...), // CLTC timeout older(2590) )// checksum)#sv8ug44m
(*)每一个 Phoenix 钱包都有一个唯一的用户公钥以及一个唯一的服务商公钥(两个都是从你的 12 词种子词中推导出来的)
我们的 swap-in 协议站在了巨人的肩膀上:
所有这些特性和协议乍看起来都暗淡无光,可能也很难看出它们对终端用户有什么好处。但现在,清楚起来了:它们是开发更好、更便宜、更隐私也更易用的协议的工具。这正是 Phoenix 的使命!
(完)
]]>作者:thunderbiscuit
来源:https://delvingbitcoin.org/t/building-intuition-for-the-cashu-blind-signature-scheme/506
本文以更加平实易懂的方式分解了当前已被 Cashu 采用的 ecash 盲签名方案的工作流程;Ruben Somsen 和 David Wagner 此前也对同一方案作了解释。(译者注:David Wagner 是这一方案的原创作者;Ruben Somsen 解释的中文译本见此处。)
前提 1:Diffie-Hellman 点。给定两位用户 Alice 和 Bob,各自有自己的公私钥对(分别是 $A = aG$ 和 $B = bG$ ),那么,任一方都可以使用对方的公钥独立地推导出椭圆曲线上的同一个 Diffie-Hellman 点。假定 Alice 知道 Bob 的公钥,Bob 也知道 Alice 的公钥,那么两人都可以使用下列公式得出一个共有且互相知晓的点(公钥)$C$:
$$C = aB = bA$$
$$C = a(bG) = b(aG) = abG$$
上述等式表明,Alice 可以用自己的私钥 $a$ 乘以 Bob 的公钥 $B$ 来获得点 $C$;而 Bob 也可以用自己的私钥 $b$ 乘以 Alice 的公钥 $A$ 来得到完全相同的点 $C$。
这种推导出共享秘密值 $C$ 的能力,已经用在今天网络生活的方方面面;凭借它,即使信息通道可能不安全,人们也只需在其中交换公钥,就可以得出 只有他们俩知道 的共享秘密值,然后用这个秘密值来加密两人之间的进一步通信。
注意,虽然他们能够独立得出相同的点,但双方都需要这两件东西:
你可以认为,Diffie-Hellman 点是一个可以从两端趋近的位置,只要两端都有自己的私钥:
Alice's private key ---> D-H Point <--- Bob's private key
任何一方只要没有自己的私钥,就不能知道这个 D-H 点。
(译者注:Whitfield Diffie 和 Whitfield Diffie 是两位最早公开提出这种密钥交换方法的密码学家。)
前提 2:你可以 “证明” 自己不知道某个点的私钥。通过展示一个点的坐标是如何从某一条消息的哈希值中推导出来的,你可以证明自己并不知道这个点的私钥。为了帮助我们“证明”,我们将使用一个函数:
hash_to_curve(message) = PointCoordinates哈希到曲线(消息) = 点坐标
如果某人从一个私钥创建曲线上的一个点(即用私钥乘以椭圆曲线上的生成元,从而得出一个点,例如 $A = aG$ ),而该点的坐标是 Coord1
。那么,找出一条消息,可以哈希得到相同的坐标,例如:
hash(message) = Coord1
将需要攻破这个哈希函数的原像抗碰撞性 1(也即找出一个能够得出特定输出的输入)。因为(能够打破抗碰撞性)是一个不可靠的假设,所以,我们可以假设一个知道某个点的私钥的人不可能 也知道 某条消息能哈希出同一个点的坐标。我们也知道,椭圆曲线的一个特性是给定曲线上的一个点,我们无法便利地计算出其对应的私钥。综合这两点,就意味着:
如果你知道某个点的私钥,你就不知道能够哈希出相同点坐标的原像;反过来,如果你知道一组坐标的原像(消息),你不会知道这个点的私钥。
我们先从协议的一个 “幼稚” 版本开始,在这里,token 不会盲化。这本身是如何在铸币厂和用户两个角色中运行一套 token 系统的介绍(我们会在 Part 3 中加入隐私性)。
假设有一个铸币厂 Mike
,承诺向拥有 token 的用户提供自己的 API 服务。为了简化,我们假设 Mike 只提供单体价值 10 美元的 token。你可以跟 Mike 购买 token(我们管这叫做 Mike “铸造” 了一个 token),用协议外的方式给他转移 10 美元。那么这些 token 会长什么样?Mike 如何铸造它们?
一种办法是 Mike 公布自己的公钥 $K$ 。他需要一种办法,证明 token 真的是由自己发行的;所以他在自己的网站上公开这样的方案:
只要你给我发送一个公钥,并可以证明你自己不知道它的私钥,我将知道我可以独自生成我的公钥与你公钥之间的 Diffie-hellman 点 $C$。我只会在收到 10 美元的支付之后才把 $C$ 交给你。每个这样的 Diffie-hellman 点都价值 10 美元,你可以随时从我这里取回。
这里的重点是,任何人都可以用一个公钥得出与 Mike 的 Diffie-Hellman 点,只要 TA 知道这个公钥背后的私钥(就是 $aK = C$ 嘛)。Mike 也知道这一点,所以不能接受 任意的 公钥与其 $K$ 的 Diffie-Hellman 点。要让 Mike 认为一个点 $C$ 是一个有效的 token,用户必须证明自己并不知道用来生成它的私钥。如此一来,Mike 就知道他们并不能自己推导出点 $C$,而只能 等待 Mike 先生成一个然后交给他们(因此,他可以保证绝不会在没有收到支付之前就放出 D-H 点)(这个点也成了一个有效的证据,证明曾在过去给 Mike 支付过 10 美元)。
这个方案的步骤可以理解如下:
Alice 创建一个公钥,且该公钥是可证自己并不知道其私钥的:
hash_to_curve(message) = PointA
她把这个公钥交给 Mike 并发送 10 美元
Mike 看到这笔支付以及这个公钥(点)之后,就放出对应的 Diffie-Hellman 点 $C$:
$$kA = C$$
Alice 现在得到了点 $C$。
当她要兑现这个 token 的时候,她知道点 $C$ 和点 $A$ 。Mike 会说,“没问题,但你可以证明这个点是我给你的,而不是由你自己推导出来的吗?”Alice 通过提供用来生成点 $A$ 的秘密值 message
来证明她无法推导出 $C$。
Mike 计算:
$$hash_to_curve(message) = PointA$$
$$kA = C$$
然后就知道 Alice 无法独自计算出点 $C$ ,因此自己必定曾给过 TA 这个点。他会把这个点当成有效的,而 Alice 也可以用这个 token 买到她想要的东西。
从上述流程中,我们可以看出,Alice 需要两件东西才能兑现这个 token:
所以,可以认为一对数据 $(C, message)$ 就是一个 token 。
因此,这个系统允许 Mike 提供一种电子 token,并且这个电子 token 可以在日后在他这里兑现。不过,他完全知道哪个 token 被兑现了(谁购买了它)、在什么时候兑现的,因为要发行 $C$ 就得知道 $A$,而他可以将 $A$ 与 Alice 联系起来,在 Alice 最初给他支付的时候。这意味着,在 Alice 兑现这个 token 的时候,他也会知道 Alice 就是兑现它的人。Part 3 会解释 Alice 如何可以通过稍微调整 $C$ 和 $A$ 来获得对铸币厂的隐私性,同时保留 Mike 验证自己是 token 发行人的能力。
回顾一下,这里的 “幼稚” token 方案大体是这样的:
Alice 找到铸币厂 Mike 并提出,“这里是 10 美元,请把你的公钥和这个公钥的 D-H 点给我。我没法自己生成这个点,因为我不知道这个公钥背后的私钥”。下一周,Alice 找回 Mike,并提出,“这里是 一个 D-H 点,是你的公钥与我拥有的这个公钥的。你可以验证自己正是发行它的人,因为我可以证明我不知道这个公钥背后的私钥,所以它必定是你创建的。”
在 Part 2 中,我们罗列了铸币厂 Mike 和用户 Alice 可以用来创建和兑现 token 的通用方案。不过,迄今为止,我们的方案还面临一个严重的问题:所有 token 都可以跟请求 token 的人联系起来,所以 Mike 可以分辨出:
两者加起来,就是 token 用户的隐私性的严重问题,也让这个 “幼稚” 的方案在现实中完全不可用。
更好的方案是任何人都能兑现 Mike 所发现的任何一个 token,不让 Mike 知道谁是最初请求这个 token 的人、这个 token 是否被交换过、从创建到兑现隔了多久。这就遇上了一个问题,因为 Alice 要兑现自己的 token(Mike 给她的 DH 点)的时候,Mike 完全记得给她的是哪一个点!
实用的办法是,Alice 给 Mike 发送一个她已经 盲化 的密钥,以交换一个签名(计算 DH 点);日后她可以 解盲 这个密钥。
我们把这个密钥称为 盲化消息
,记作 B_
。它是由 Alice 真正想用的一个公钥 加上 一个随机公钥组成的(还记得吧,我们可以在椭圆曲线上运行点加法)。Alice 可以实用她在 Part 2 里 的公钥(A = hash_to_curve(x)
),并加入公钥 R
(R = rG
)。Alice 只需选择一个随机秘密值 r
就可以生成出 R
,保存下来即可。这个 盲化信息
可以通过这两个点的加法来定义:
盲化消息 = A + R盲化消息 = A + rG
她给 Mike 发送盲化消息(依然只是椭圆曲线上的一个点),Mike 生成这个点与其公钥 K
的 DH 点:
C_ = k(盲化消息)
我们管这个点 C_
叫 盲化签名。Mike 将盲化签名发回给 Alice,Alice 可以通过下列步骤来解盲:
C_ = k(A + rG)C_ = kA + krGC_ = kA + rKC_ - rK = kA + rK - rKC = kA
注意,在 Part 2 中,Mike 给 Alice 的 DH 点是用私钥 k
乘以 Alice 所提供的公钥 A
得出的(D-H = kA
)。而在这里,Alice 是通过一种迂回的方式得到它的:她并不 直接 给 Mike 提供 A
,这也意味着在她兑现的时候,Mike 不会知道点 A 跟盲化消息有什么关系,也不能把 A 跟 Alice 关联起来。
这个点 C
叫做 解盲密钥,也正是 Alice 如果直接给 Mike 提供公钥 A
会得到的密钥。
日后,Alice 可以找到 Mike 并出示 (C, x)
。Mike 可以通过运算 hash_to_curve(x) == A
来见证 x 产生了点 A
(确认 Alice 并不知道这个点的私钥),而且 C
是 A
与其公钥 K
的 DH 点(C == kA
)。基于这两个事实,只有他能够给出这个点。他再验证这个点 C
还未被兑现过,如果真的没有,那就认为这个 token 是有效的。
因为这个 token 是有效的,而且有很好的隐私性,它可能会被其它人接受。Alice 可以在给 Bob 支付时使用这个 token (C, message)
,Bob 可以跟 Mike 兑现这个 token。Mike 无法知道这两个人发生过交易。
1. 抗碰撞性 是指一个哈希函数是难以反算的:给定哈希函数的一个输出,找出能够产生这个输出的输入是计算上不可行的。 ↩
]]>作者:Anony
在前面的文章中,我们一直在讨论,如何优化闪电网络,并通过技术的进步来为用户提供更好的体验。但有一个 “简单的” 问题我们一直没有触及:在闪电钱包用户的日常生活中,可能既需要发起闪电支付,也需要发起 “链上支付” —— 具体来说,就是让一笔交易获得区块确认,或者说,在比特币网络中创建一个新的 UTXO;既需要利用闪电网络来收款,也需要接收链上支付。
最初的闪电网络客户端实现(包括:Eclair、Core Lightning 和 LND)都同时管理链上资金和闪电通道资金。而且那时候,“链上资金” 与 “闪电通道资金” 是泾渭分明的,互相不能直接用于对方的支付:如果你要发起一笔链上支付,你无法直接动用闪电通道内的资金,你必须先退出通道,然后再发起链上支付(这其中的时延高得令人发指)。链上资金自然更无法直接用于闪电支付。也正因此,当时的用户界面(第三方的钱包软件以及操作工具)也往往将链上余额与通道内余额分开展示。
显然,这很不便利,是不理想的。它会在用户的同一个钱包(使用同一批私钥可控制的资金)内部形成流动性的分割、影响用户的支付能力,并且制造很大的困惑 —— 这世界上难道存在两种比特币吗?为什么它们都在我的钱包里,我却不能用它们来支付?
但是,如何打破这种区隔呢?如何让闪电通道内的资金也能发起链上支付?如何用链上资金触发闪电网络内的支付?
在本文中,我们会介绍一系列的技术和解决方案,它们改变了 “两种资金有区隔” 的不完善状态,有力地证明了闪电钱包不必因为这种认识上的人为区别而裹足不前。最终,它们允许闪电钱包只保留一种形式的资金、只展示一种余额,同时保留跟一切钱包交互的能力。
第一种使我们可以不必顾虑资金的形式、径直发起支付的技术就是上一篇文章里我们介绍过的 “潜水艇互换”。正向的潜水艇互换使我们可以用通道内的资金创建链上输出(也即发起链上支付),而反向的潜水艇互换使我们可以用链上输出换取闪电支付的能力。
不过它也有些不便利之处。在潜水艇互换中,接收者的钱包软件必须支持这种脚本,而且必须在一段时间内领取支付。这都会对接收方提出一些要求。
“通道拼接” 则比潜水艇互换更进一步。它利用了一种根本上的洞见:所谓的 “链上资金”,不过是 “放在只有自己能控制的 UTXO 中的资金”;而 “通道资金”,不过是 “放在与其他人共同分享的 UTXO 中的资金” —— 它们只是操作方法不同,本质并没有什么分别。只要通道双方达成一致意见,他们就能以通道 UTXO 作为输入,发起链上交易(“拼出”),并在同一笔交易中创建一条新的通道,不必先关闭通道、完成支付之后又开启;同样地,只要双方遵循一定的程序,同样能将当前的 UTXO 以及其它资金作为输入,形成新的通道(“拼入”),而不必先关闭通道然后再开启。
并且,通道拼接还能保证,在等待拼出或拼入交易获得区块确认的过程中,通道的功能可以不受影响,依然能即使确认支付。这是怎么做到的呢?
以通道拼出为例,我们假设双方要用通道资金发起一笔链上支付,其输入和输出如下:
当前通道 UTXO(前身 UTXO) --------- 新通道 UTXO | —--- 接收者 UTXO
在这笔交易等待区块确认期间,双方可以对 “前身 UTXO” 和 “新通道 UTXO” 同步签名意义相同的承诺交易,区别只在于对 “前身 UTXO” 签名的交易将始终有一个交给接收者的输出。也即,如果拼出交易得到了确认,这些对前身 UTXO 签名的承诺交易就自动作废,而基于新通道 UTXO 的承诺交易如实地反映了双方的交互;如果拼出交易得不到确认,基于前身 UTXO 的承诺交易也如实地反映了双方的余额变化,将最新的承诺交易广播到比特币网络中依然会触发对接收者的链上支付,而不会允许通道任何一方占便宜。拼入交易也是同样的道理。
通道拼接大大改变了闪电通道发起链上支付的能力和体验。并且,它使我们可以在维持闪电支付能力的同时调整通道的大小,而不必经历关闭又开启的繁琐操作。甚至有人提出可以让通道也参与 coinjoin(一种混淆支付真正收发者的交易) 1。可想而知它有多么强大。
相比于潜水艇互换,通道拼出不会对接受者的钱包提出任何要求,最普通、最常见的钱包软件就可以用来收款。不过,通道拼出无法用来获得入账流动性。
截至本文撰写之时,Eclair 客户端已经支持通道拼接,Core Lightning 客户端也提供了实验性的支持。详细解释和支持进度可见这个页面 2。
除了优化通道资金发起链上支付的能力,人们也在思考如何加快链上资金进入闪电网络的速度。上一篇文章介绍的 “零确认通道”,也是这方面的努力。“Swap-in Potentiam” 则提议闪电网络的用户应该用这样一种脚本来接收链上支付:该脚本有两种花费分支,一是用户与某个闪电网络服务商(LSP)的 2-of-2 多签名;二是用户单签名 + 一个相对时间锁。
这种脚本有非常有趣的特性:(1)时间锁分支决定了其中的资金的最终归属;假设其双签名分支不被动用,在相对时间锁过期后,用户就可以单凭自己的公钥和签名来花费它;(2)在时间锁还未解锁的时候,不论 LSP 还是用户都无法单方面使用其中的资金;当用户使用其中的资金给 LSP 支付(以承诺交易的形式),LSP 是可以立即就确认的。—— 没错,在时间锁解锁之前,可以把它当成是用户对 LSP 的单向支付通道。在这样的单向通道内,用户给 LSP 的支付,与闪电通道内的支付没有什么区别 —— 所以它可以立即用来闪电支付。
对用户来说,这是一种免信任,但又可以获得 LSP 服务的资金形式。在收到链上支付之后,只需资金得到区块确认,立即就可以进入闪电网络,而无需再经历开启通道的流程。
“Swap-in Potentiam” 提议的原始文本可见这里 3。
截至本文撰写之时,移动端钱包 Phoenix 已经实现了 Swap-in Potentiam。
Swap-in Potentiam 已经走到这条路的终点了吗?不。从通道拼接的角度看,最佳效率的做法是支付者、闪电钱包用户、LSP 三方一起构造、签名交易,使得支付者的链上支付在单笔交易中直接拼入通道,而不是进入 Swap-in Potentiam 脚本之后再拼入。
显然,这需要支付者的钱包采用一种更为抽象的支付的概念 —— 为一笔交易贡献一定数量的输入,然后确保该交易有一个归属于自己的找零输出,并且输入和输出的差额恰为(支付额 - 应负担的手续费额) —— 而不再要求交易完全由自身来构造、不再要求交易的输入仅有自身可控制的资金。在这种情况下,接收者仅提供自己的收款脚本是不足够的,两方可能要经过多次通信往返,才能安全地构造出最终的交易并广播出去。
当前,以 payjoin 为名的技术,最契合我们这里谈到的概念 4。
接下来,我们要分析两种钱包的用户体验。它们各自使用上述的技术,达成了 “向用户展示一个余额” 的目标。
Muun 钱包可能是最早尝试克服 “两套余额” 问题的钱包。它的做法是只维护链上资金,然后以潜水艇互换来获得 闪电支付/闪电收款 的能力。
这样做的结果是简洁得惊人的架构和用户体验 —— 它将 闪电通道/闪电网络 完全抽象掉了。软件无需实现完整的闪电网络协议、钱包里没有通道、没有流动性问题,也没有在线要求。用户也完全不必了解闪电通道。无论是常规的链上支付还是闪电支付,体验都相当一致。而且,其安全保管操作与常规的 “比特币钱包” 没有什么区别。
但这样做也有一个明显的缺点:它并不能享受到闪电网络的所有好处,主要体现在手续费上。即使在支付闪电发票,实际上其操作也是在链上发起交易,也要支付链上确认的手续费;反之,接收闪电支付的时候也是如此,同样涉及链上交易。一旦比特币网络的交易确认需求高涨、手续费率提高,用户就会发现自己要支付的手续费高得可怕,一点也不像在使用闪电钱包。
Phoenix 则跟 Muun 钱包朝着完全相反的方向走 —— 它激进地要让用户的钱包里只有通道资金,并且只有一笔。它是这样做的:
Phoenix 综合了我们在这两篇里提到的许多技术,持之以恒地打造了最友好的移动端自主保管钱包体验。
不论是 Muun 钱包,还是 Phoenix 钱包,它们所应用的技术并没有抹去 “区块确认” 和 “通道确认” 在速度(和经济开销)上的区别,也不可能抹除,但它们已经是两个容易解释得多的概念 5,而且只要保证了支付能力,这些细节就不会太让用户困扰。它们都为钱包软件的设计提供了许多启发和应该思考的问题。Muun 基于跟常规的 “比特币钱包” 完全相同的架构,提供了支付闪电发票的能力;Phoenix 则激进到让用户所有的资金都进入通道,但完全不影响用户的链上支付能力与体验。它们都在抹除 “比特币钱包” 和 “闪电钱包” 的界限,逼迫所有人正视技术和基础设施的进步所带来的潜能,并要求钱包的开发者们为用户释放这种潜能。
最终,用户也会知道,世界上有且只有一种比特币,它每一天都会比昨天更容易在网络中穿梭。
1. https://www.btcstudy.org/2023/03/01/lightning-privacy-research-channel-coinjoins/ ↩
2. https://bitcoinops.org/en/topics/splicing/ ↩
3. https://www.btcstudy.org/2023/03/06/swap-in-potentiam-moving-onchain-funds-instantly-to-lightning/ ↩
4. https://www.btcstudy.org/2023/11/10/payjoin-for-a-better-bitcoin-future/ ↩
]]>作者:Anony
在本篇中,我们将介绍让节点获得入账流动性(收款额度)的方案,并介绍这些方案在闪电网络终端用户所用的实现上的演化。
早在本系列第一篇中,我们就提到,出于 “通道(共有资金)” 的特性,“收款额度” 的概念是无法逃避的。而每个用户都有可能遇上想要收款却没有额度的情形。
这个问题因为闪电网络早期实现的一个设计而变得更加严峻 —— 单向注资。
虽然在我们之前使用的例子中,都把通道双方都有余额作为通道的初始状态,但实际上,最早实现的通道注资方法是 “单向注资” —— 在参与通道的双方中,仅有一方为通道提供资金。如此一来,在一开始,双方得到的都是不平衡的通道:一方仅有支付能力(而无收款能力),而另一方又仅有收款能力(没有支付能力)。
这种设计有一些优点:易于实现、安全性检查更加简单(双向注资所需的额外安全性检查可见 1);尤为重要的是,它更适合网络的启动。在一个新节点要加入网络时,它可能会选择跟一个已知的老节点开设通道,然而,对后者来说,却难以决定要不要在其中锁入流动性:如果这个新节点只是尝试一下,很少在线,那么,锁入其中的资金就无法获得收益。而单向注资使得该节点根本不必有此担忧:新节点会自己锁入资金,而不要求老节点提供任何初始资金。
但是,这也意味着,收款额度的获取成了所有新节点的难题 —— 不论你想做一个转发节点,还是将闪电网络用于日常使用。
截至本文撰写之时(2024 年 2 月),已经有两个闪电网络客户端(Core Lightning 和 Eclair)实现了 “双向注资(dual funding)”,也即在创建通道时,双方都可以为通道提供资金。在一些条件下,这可以节约一个节点要创建平衡的通道的步骤。
“双向注资” 的详细介绍可见这个页面 2。
接下来我们要介绍的是获得入账流动性的方案。概要来说,这样的方案可以分成两大类:一类的原理是向外支付;另一类的原理是请求他人向自己开设不平衡的通道。
“潜水艇互换(submarine swap)” 是第一类方案的代表。其想法是,需要入账流动性的节点将自己在闪电通道中的资金通过 HTLC 置换成链上资金;在向外支付的同时,该节点也就获得了入账流动性。
用户在闪电网络中给互换服务商支付,互换服务商在链上给用户支付,这就是潜水艇互换;而 HTLC 可以保证互换的免信任性。不过,由于这个过程涉及链上支付,它在实现中会遇到一些难题,从而一定程度上需要一些信任。我们用详细的互换流程来分析:
从第二步开始,服务商需要信任用户,因为 TA 需要创建一个链上输出。如果这时候用户突然反悔,或本来就有意作恶,不在链上释放原像,那么服务商就亏掉了为创建这个链上输出而需支付的手续费。也正因此,在一些服务中,服务商会要求用户预付一定的费用,在完成互换之后退款,但从原理上说,这又变成了用户需要信任服务商。
潜水艇互换在实践中的另一个困难是,它需要用户的钱包也有相应的支持。用于给用户支付的 HTLC 并不是常见的单签名输出,用户的钱包必须能够理解 HTLC 的脚本、懂得为其构造见证数据(witness),才能花费其中的资金。当前,将自身定位为 “比特币钱包” 的钱包软件几乎都无法提供这种支持,但一些 “闪电钱包” 则可以做到。未来,这需要依靠 Miniscript 这样的技术来提供更好的互通性 3。
潜水艇互换可能是最早出现的入账流动性获取方案,也启发了许多后来者。其中一种是 “PeerSwap”,直接跟你的通道对手实施潜水艇互换 4。还有一种叫 “环路支付(circular payment)”,就是规划出一条环状的路径,将自己在 A 通道中的余额转移到 B 通道 5。
潜水艇互换还有一种有趣的用法:你可以用闪电网络中的资金给他人支付链上比特币。此外,它也可以反向运行:将链上的比特币 “充值” 到自己的闪电通道中,以增加支付能力,无需 关闭通道-重新打开通道。
当前,如果你使用 LND 客户端来运行闪电节点,你可以使用搭配的 Loop
客户端来使用潜水艇互换功能。
获得入账流动性的另一种办法是租赁通道:请求他人用自己的资金与你开设一条通道(当然,你需要支付一些费用)。对方投入的资金即是你立即获得的收款额度。
请注意 —— 你并没有借他的钱,钱依然在他手上,只不过形式从链上资金变成了链下(某一条通道中的)资金;当你在这条通道中收到支付时,他必定在另一条通道中得到了 相同数额+转发费用 的支付。
显然,这种通道租赁需要让通道保持一段时间,否则出租方就可以收钱不办事 —— 在收到租金之后立即关闭通道。但是前面介绍的闪电通道构造似乎并没有对通道存续时间的保证。难道又只能引入信任了?
事实上,如果你了解比特币的脚本,你会发现,有一种很简单的办法可以保证出租方会让通道持续一段时间。假设租赁方 Alice 要求出租方 Bob 与自己开设一条通道,那么,Alice 可以在自己签名的承诺交易中,为所有给 Bob 支付的输出(包括普通的输出和 HTLC 输出的相应花费分支)都加入绝对时间锁,时间锁的过期时间是双方约定的合约结束时间。也即,即使 Bob 拿着最新的承诺交易退出通道,如果未过合约期限,这些资金也会一直锁定、无法动用。这就完全打消了 Bob 提前关闭通道的激励 —— 资金放在通道中还有可能产生收益,但关闭通道会导致字面意义上的 “冷藏”。早在 2018 年,就有开发者提出了这样的想法 6。
通道租赁的想法简单又直接。甚至移动端的自主保管钱包 Breez 也在 App 中提供了向著名的服务商租赁通道的入口。
最初,提供此类服务的都是著名的公司,而且流程中很可能需要信任。于是,人们想出了更多方法来降低参与的门槛、增加市场的竞争。
Lightning Pool 是 Lightning Labs 推出的一种通道租赁拍卖市场。用户可以使用 LND 客户端和搭配的 Pool
客户端来参与。其基本特性是:
Lightning Pool 是一种有趣的尝试。它用一个半公开的市场解决了流动性供需匹配的问题,还为比特币资金提供了一种按区块计量的利息率(当然,所有的通道租赁解决方案都有这个效果)。
Lightning Pool 的技术详述可见他们的论文 7。
“流动性广告(Liquidity Advertisement)” 是 Blockstream 提出的一种技术,其核心是让流动性的供方可以用闪电节点的 gossip 消息(本来用于宣布新通道、新节点的消息,相当于整个闪电网络的公开频道)来播报自己的要价以及通道的持续时间。接受这个价格的闪电节点可以与之用双向注资方法开启通道、获得入账流动性。租赁方为通道提供的输入会立即用来支付租赁费。
当前,仅有 Core Lightning 客户端为 Liquidity Advertisement 提供了实验性支持。
上述两种方案,显然更适合于网络中的转发节点,以及有经验有技能的用户,却不适合于移动设备的用户(可以操作的界面更为有限),更不适合刚解除闪电网络、对技术了解不多的用户。因此,我们要专门一些适合刚入门用户的方案(并且假设他们会使用移动端 App,而不是电脑软件)。
“零确认通道(Zero-conf channel)”(也称 “零配置通道”、“涡轮通道”)的想法是,通过引入有限的信任,一举解决入门用户的两大问题:(1)创建通道需要等待;(2)新建通道没有入账额度。
办法如下:用户将比特币资金发送给服务商,服务商负责对用户开启通道(单向注资),并在通道内将扣除服务费之后的剩余资金转移给用户;并且,双方都视这条通道为立即可用,而无需等待通道注资交易得到区块确认。
显然,这个过程需要用户信任服务商:假定用户将资金转移给服务商之后,服务商拒绝执行后续步骤,那么用户并没有什么办法(只能到社交媒体上哭诉);其次,在通道注资交易获得区块确认之前,用户在通道内获得的支付都是不可靠的,因为服务商可以重复花费注资交易的输入。
但是,它解决了刚进入闪电通道的用户的一些痛点:首先,常规的闪电通道,在创建时需要让注资交易获得 3 次区块确认,用户才能开始支付,而零确认通道可以让用户立即开始闪电支付(用户的闪电支付,只要能获得收据,是可靠的);其次,这种通道由服务商创建(可能还加入了服务商自己的资金),所以用户从一开始就可以获得收款额度。因此,还是可以给用户带来显著的便利的。
而且,一旦零确认通道的注资交易获得区块确认,它就会逐渐转变成常规的闪电通道:用户拥有承诺交易所提供的保护,不再需要信任服务商。
显然,要求用户先将资金交给服务商,是双向注资技术缺位之时的无奈之举。有了双向注资之后,我们就可以优化它,让服务商和用户直接执行双向注资程序。这并不能缩减用户创建闪电钱包的时延,只是降低了所需的信任。至于 “零确认” 这段时间所要求的信任,则是无法消除的 —— 但我们都知道,这个环节所要求的信任,是有限的。
更富细节的描述可见此篇 8。
许多移动端的闪电钱包都实现过零确认通道。不过现在,它已逐渐让位于一种更精致的零确认通道 —— JIT 通道。
“JIT 通道”,顾名思义,是一种 “按需开设的通道”,它在零确认通道的基础上更进一步,但又可以说更加简单。
其思想是:仅当用户要接收支付、现有的收款额度又不够时,由服务商向用户开设新的通道,用户在新的通道里领取支付;并且,这条新通道也是 “零确认通道”,可以立即使用。
其技术实现也非常直接:当一个 HTLC 即将经由服务商到达用户,而用户的收款额度不够时;服务商就跟用户创建一个新通道,并用这个新通道内的资金来创建所需的 HTLC;此外,服务商还可以要求用户在某一条通道中创建一个使用相同哈希值的 HTLC,以支付服务费。一旦用户在新通道中用原像领取支付,服务商就不仅能获得该笔支付的转发费(像常规的闪电通道一样),还能获得用户支付的服务费。
相比于原本的零确认通道,JIT 通道的流程大大减少,但又保留了零确认通道对服务商的好处:甄别用户。因为需要在通道中锁定资金、为用户提供收款额度,服务商最担心的就是遇上试水的、不真实的用户。或者说,服务商需要甄别用户,以决定自己锁定的流动性的数量。而用户预先付出的资金数量、即将收取的资金数量,都为服务商提供了重要的参考。
除去 “零确认” 过程中的信任,JIT 通道也未完全消除信任需要,但它跟潜水艇互换一样,更偏向要求服务商信任用户:服务商必须垫付创建通道的区块确认费,但如果这笔支付是虚假的、用户不会放出原像,服务商就面临亏损。
在业内,有一个 “闪电网络服务商(LSP)” 的概念。其意思是,当用户与这样的服务商建立通道时,服务商可以自动地帮助用户管理通道(包括备份)和流动性,无需用户为之烦恼;此外,由于 LSP 时刻在线,也能为用户异步接收支付提供帮助 9 。即使这个概念不是由 JIT 通道激发的,JIT 通道也必定极大地完善了这个概念。
当前,许多自主保管钱包都提供了 LSP,包括 Phoenix 钱包和 Breez 钱包。其中,Phoenix 钱包还有专门的一个页面,让用户自己配置为接收一笔支付而愿意支付的服务费上限。当开设通道所需的实际费用超过这个限度时,LSP 将不会行动。
现如今,人们已经在讨论为 LSP 制定一套技术规范 10,从而允许人们使用相同的软件来获取不同 LSP 的服务,以强化 LSP 的市场竞争、保证用户的体验。
目前,大部分移动端闪电网络用户的选择依然是托管钱包 —— 用户不掌握私钥,资金也得不到真实通道的保护,但无需关心收款额度、定期在线要求以及通道资料备份等所有事情的,闪电支付服务。显然,自主保管钱包的体验还没有说服足够多的人。但是,我们有理由相信它会继续进步。上一篇文章所述的收款码的进化是一个例子。本篇所述的零确认通道的进化也是一个例子。
在下一篇文章,我们将介绍让用户能够更自由地应对支付和收款需要,不必关心资金形式的技术。它们改善了用户的支付能力,并且直观地展现了形式不同、本质相同的资金的深层一致性 —— 只有一种比特币。
(未完)
1. https://bitcoinops.org/zh/newsletters/2024/02/07/#txid-segwit ↩
2. https://bitcoinops.org/en/topics/dual-funding/ ↩
3. https://www.btcstudy.org/2022/06/26/hidden-power-of-bitcoin/ ↩
4. https://www.btcstudy.org/2022/04/27/peerswap-a-p2p-btc-ln-balancing-protocol/ ↩
5. https://www.btcstudy.org/2022/06/14/rebalancing-in-the-lightning-network-circular-payments-fee-management-and-splices/ ↩
6. https://lists.linuxfoundation.org/pipermail/lightning-dev/2018-November/001555.html ↩
7. https://github.com/lightninglabs/pool-paper/blob/main/liquidity.pdf ↩
8. https://www.btcstudy.org/2022/08/25/what-are-turbo-channels/ ↩
9. https://www.btcstudy.org/2024/02/02/the-past-present-and-future-of-offline-payments/ ↩
10. https://www.btcstudy.org/2023/12/01/lightnings-future-a-new-era-of-interoperability-with-lsp-specs/ ↩
]]>作者:Anony
在上一篇文章中,我们了解到,在最初设想的闪电网络支付中,收款方应该向付款方发送一个 “闪电网络发票(invoice)”,使后者能够在闪电网络中找出收款方的位置并通过 HTLC 和中间节点送达支付。作为一段数据,闪电网络可以有各种各样的形式,它可以是一串字符,也可以编码成一个 QR 码。
在支付方和接收方能够面对面的情形中,将闪电发票编码成 QR 码会更加便利,而且跟当今用户习惯的互联网支付没有什么分别 —— 支付方拿出手机,打开闪电 App,然后扫描接收方的 QR 码,App 在后台处理之后,点击确认,完成支付。
但是,这样的 “收款码” 却有一个奇怪的特点:它只能用一次。闪电发票本质上是一次性的,一旦支付成功,或者超时失败,就不能再次使用。
对于做生意的商家来说,这有一点点麻烦,就是每次都必须为用户生成一个新的收款码,但还不算是不能接受。但是,对另一些场景来说,它就很不便利了。比如说:互联网打赏;熟人间的多次来回支付;捐赠。在这些场景中,让接收方手动一一为支付方生成并提供发票,要么在时机上不现实,要么在规模上不现实。
为了实现 “静态的收款码”,人们提出了许多方案。我们先来看第一种。
“Keysend” 的想法是:因为节点的 node_id
是不会改变的,而且在给出发票之后就会向支付方暴露,所以,可以用它来作为一个静态的端点。
具体来说,当一个支付方在知道接收方的 node_id
之后,想要再次给 TA 支付,又不便于获取发票时,就这样做:支付方自己生成一个秘密值,并计算出其哈希值;然后,将这个秘密值放在给接收方的支付转发消息中,同时,使用其哈希值来构造传递支付的 HTLC。当加密的消息被一跳一跳传递给接收者节点时,TA 会解密消息,然后知晓其中的秘密值,然后就可以像常规的闪电网络支付那样回传原像。(支付转发消息在闪电网络中传递的详情,请见上一篇文章。)这样一来,支付方无需接收方出示原像,就能在闪电网络中完成一次多跳支付。
Keysend 有许多有趣的用法。比如,你可以用它来实现对一些通道的内部余额的打探(probing):发送方向接收方发送一次 Keysend,并要求接收方不要接收支付,而回传错误;当这样的支付逐步加大额度,从而在某一跳断开时,发送方也就知道了该通道在该方向上的余额。打探可以变成对隐私性的攻击,但是,也可以用来提前打探路径,从而避免支付失败。还有人尝试用 Keysend 来发送消息,也即借助闪电网络实现加密的即时通讯 1。
Keysend 还有一个优点:它完全不依赖其它协议,而只依赖闪电网络自身。
不过,作为一种支付,它还是有一个令人难以接受的缺点:它不能得到收据。由于用来构造 HTLC 的哈希值是由支付方自己指定的(而发票中的哈希值是由接收方指定的,并具有接收方的签名),他从一开始就知道这个哈希值背后的原像,所以,即使全部 HTLC 顺利结算,也不能认为自己得到了收据。
另一个同样不容小视的缺点是,如果以 node_id
来接收支付(比如在捐赠或打赏场景中),接收方的隐私会有非常大的缺陷。接收方的节点、通道、通道 UTXO,都会暴露。
当前,大多数闪电网络客户端都已经实现了 Keysend 的功能,不过,在运行的时候可能需要用户手动打开这个功能。
Keysend 的介绍还可见这篇文章 2。
在上一篇文章中,我们介绍了一种叫做 “AMP(原子化多路径支付)” 的技术,并表示它其实更像一种 Keysend 方案。其中的缘由,在了解 Keysend 是什么之后,当不难理解。读者可以看看 Lighting Labs 为 AMP 编写的介绍 3,看看其特性与 Keysend 有多么相似。
从 Keysend 的案例中我们可以知道,尽管发票令人觉得麻烦,却是不可或缺的一种东西,因为它可以让支付方获得收据(支付证据)。换言之,合理的方案应该是能够自动化地请求发票,而不是完全绕过发票(像 Keysend 那样)。
而这就需要一种方式,能够触达接收者节点、请求一个闪电发票,并且发票能恰当地回传到支付方处。
那么,如果接收者节点有一个可被外部访问的网络端点(比如,一个可以访问的网络域名或者 IP 地址),那么支付方就可以先访问这个网络端点、请求接收者的发票;获得发票之后,再启动常规的支付流程。
LNURL 就是在这个想法上产生的解决方案。接收者节点额外运行一个 LNURL 服务端,并将该服务端的网络端口(例如 https://lnurliscool.com/receiver
)编码成一个 QR 码。然后,在支付中:
由于 QR 码所编码的不是发票,而是一个网络端口,当然可以是稳定不变的:支付者可以向服务端多次请求发票并发起支付。这种收款码的一种有趣的形式是 XXXX@YY.com
这样看起来像电子邮件地址的 “Lightning Address(闪电地址)” 4,前缀是一个自定义的标识符,后缀则是 LNURL 服务端的域名。支付者的闪电 App 会根据 LNURL 规范 #16 5,组合出一个网络端口。
LNURL 是一套多特性的协议,除了上述特性(称为 “LNURL-Pay”)之外,还有:
到目前,规范基本上已经稳定下来了 8。你可以在他们的规范库中看到哪些钱包已经支持了 LNURL。
值得一提的是,在当前,你会发现,实现 LNURL 的钱包大多是托管钱包,它们会给每一个用户分配一个 Lightning Address,以允许使用 Lighting Address 收取支付,使用 LNURL 的其它功能更不在话下。但自主保管钱包往往只允许用户向 Lightning Address 发送支付,而不会为用户提供 Lightning Address。
其原因可能是,LNURL 自身其实并不解决用户的可访问性问题 —— 用户必须先有一个可访问的互联网端口,然后才能运行 LNURL 的服务端。为用户提供这样的互联网服务(“内网穿透”),超出了自主保管钱包的开发目标,也面临许多用户体验上的挑战 —— 最好的办法还是让有技能的用户在自己的设备上自主选择或搭建一些互联网服务。而托管钱包就不存在这些困扰,其用户并没有真实的闪电网络客户端,也没有复杂的网络问题要处理。
托管钱包,顾名思义,资金被托管在服务商处,只是用户可以使用这些名义余额来发起闪电支付。它在各个方面都更接近于如今大多数人接触到的互联网支付产品(比如支付宝、微信支付乃至 PayPal),体验也相似。但是,因为用户和服务商之间并没有真实的通道,因此也无法得到密码学和比特币网络的保护 —— 用户需要信任服务商。在使用这样的服务商时,用户无需考虑在线要求,也无需担心收支额度的问题,一切皆由服务商在后台处理。
自主保管钱包则依据我们系列第二篇中所述的原理开发出来,因此用户无需信任对手方。
不管怎么说,LNURL 代表了使用其它网络协议来改进闪电网络用户体验的尝试,这可以说是一种特性,也可以说是一种缺点,完全看你持有哪一种视角。因为使用的是额外的网络协议,它有巨大的开发空间。但因为利用了其它协议(而不是仅依赖于闪电网络本身),自主保管的闪电钱包无法直接运用其所有特性,尤其是移动端的用户,几乎只能在托管钱包上享受其所有特性。
接下来,我们进入一种仅依赖于闪电网络,但所实现的最终功能与 LNURL 非常相似的协议。
如何能够仅依赖闪电网络,来提供网络触达、往返通信的功能呢?
在前面关于 Keysend 的介绍中,我们已经提过,我们可以通过用来传递支付的 “洋葱路由” 协议,向一个节点递送消息;消息就藏在给该节点的加密数据包中。在常规的、成功的支付中,接收者节点会回传一个原像,这个原像是一个 32 字节的随机数,并通过支付路径上各通道的更新,一路回传到支付者节点。而在专门为了传递消息的 Keysend 中,支付者可以在加密数据包中放置消息,并要求接收者节点回传错误(表示支付失败),支付路径上的各通道也会更新,但不返回原像。
如果,我是说如果 —— 如果 A 节点传给 B 节点的是一张发票呢?那么 B 节点就可以依据这个发票给 A 节点支付了!如果 A 节点传给 B 节点的是自身的位置信息呢?那么 B 节点就可以通过 Keysend 给 A 节点发送发票了!
这就是 “BOLT12 Offer” 协议背后的洞见。给定我们可以用闪电网络触达一个节点,自然就能在此基础上实现通讯。BOLT12 增加了一种数据格式,称为 “Offer(要约)”,还有一种叫做 invoice_request
的消息类型。
在支付场景中:
接收方出示 Offer(作为收款码);
支付方根据 Offer 的信息向接收者节点发送 invoice_request
,其中包含了愿意接受的支付条件以及 reply_path(回复路径)
;
接收方节点构造发票之后,通过 reply_path
发送给支付方节点;
支付方节点根据发票,给接收方支付。
同理,Offer 也可以用于退款:要求退款的一方根据退款方的 Offer 发送发票,退款方就根据发票发起支付。当交互流程反过来(支付者出示 Offer,接收者发送发票)时,它就变成了一种 “付款码”。
由于 Offer 所提供的主要是自身位置的信息,以及其它一些跟支付条件有关的信息,但自身并不是发票,所以,它可以长期使用 —— 支付者可以多次向接收方请求发票。
在 2019 年刚刚提出的时候,BOLT12 就使用上述类似于 Keysend 的做法来传递消息。但随后就有开发者提出 9,这种 “messages-over-payments(通过转发支付的协议来转发消息)” 的做法有很大的开销,沿途的节点必须更新通道状态,还必须保存关于这些承诺交易的信息,而且在回传失败消息时还必须加以处理 —— 与其如此,还不如设计一套新的协议,让支持这种协议的闪电节点能够直接通信,这就是 “洋葱消息” 在闪电网络语境下的含义 10。
在使用洋葱消息之后,节点就可以直接转发消息,转发消息的节点也无需再记忆跟这些消息相关的信息。这也是上述 reply_path
的由来 —— 响应 Offer 的一方规划好从自身出发、触达 Offer 方然后再触达自身的环状路径,并将后半段路径放在 reply_path
中告诉 Offer 方。
在 2023 年 8 月 11,“洋葱消息” 合并进入了 BOLT7,这意味着以后所有的闪电节点客户端都会实现这种特性。而洋葱消息也会使用我们在上一篇文章中介绍的 “盲化路径”。原本在原理上,BOLT12 是不依赖于盲化路径的,但如今在实现中,变成了需要盲化路径作为前置技术。不过,这也意味着,出示 Offer 的接收方将默认具有更好的隐私性。
BOLT12 尚未合并到 BOLT 中,但这个想法得到了大多数开发者的支持。关于 BOLT12 的特性,这个页面提供了基本的介绍 12。至于其起源和变革,Optech 的主题界面提供非常详尽的参考 13。
相比于 LNURL,BOLT12 最大的特点是,它可以在闪电网络协议内实现,而不需要依赖于其它网络协议和通讯方式。这有它的好处,但是,它也意味着要使用闪电网络中的资源。如今,因为洋葱消息的采用,其开销被进一步降低。我们有理由期望,BOLT12 会给我们带来用户体验的变革。届时,自主保管的钱包,也能提供收款码、付款码这样当前专属于(支持 LNURL 的)托管钱包的体验。
在这篇文章中,我们介绍了实现可复用的闪电网络收款信息的尝试。最初的 Keysend 虽然直接,却无法提供支付证据。LNURL 有很大的灵活性,在自主保管的闪电钱包中却难以做到 “开箱即用”。而 BOLT12,通过利用闪电网络自身的特性,最有可能革新自主保管的闪电网络用户的体验。
在下一篇文章中,我们将介绍帮助用户应对通道用户体验最难解问题 —— 收款额度 —— 的解决方案。
(未完)
1. https://www.btcstudy.org/2022/03/09/on-lightning-messaging-apps-emerge-as-growing-use-case/ ↩
2. https://www.btcstudy.org/2022/01/14/technical-series-what-is-keysend/ ↩
3. https://docs.lightning.engineering/lightning-network-tools/lnd/amp ↩
4. https://www.btcstudy.org/2022/12/22/how-lightning-address-works/ ↩
5. https://github.com/lnurl/luds/blob/luds/16.md ↩
6. https://www.btcstudy.org/2022/11/30/lightning-authentication-lnurl-auth/ ↩
7. https://www.btcstudy.org/2024/02/02/the-past-present-and-future-of-offline-payments/#LNURL-Withdraw ↩
8. https://github.com/lnurl/luds ↩
9. https://bitcoinops.org/en/newsletters/2020/02/26/#ln-direct-messages ↩
10. https://bitcoinops.org/en/newsletters/2020/04/08/#onion-messages ↩
11. https://bitcoinops.org/en/newsletters/2023/08/09/#bolts-759 ↩
12. https://bolt12.org/ ↩
]]>作者:Annoy
在上一篇文章中,我们了解了如何借助比特币的脚本和交易特性,实现双方可以无限次更新状态的共有资金(“通道”);并在此基础上实现可以获得收据的 “支付”。与链上支付相比,这种基于通道的支付具有低手续费、确认速度快的特点,因此扩大了比特币的吞吐量(“可扩展性”)。
但是,这只表明,通道是可以实现的,跨通道的多跳支付也是可以实现的。具体来说,在一个网络中,到底如何运用这些技术,向另一个节点支付呢?本篇将解决这个问题。
本篇所述的内容,将使读者对闪电网络的 “网络” 特性有更直观的理解,也将意识到,这里有多么大的改进空间。本文也将解释一些已经发生和正在发生的改进。同时,其中的关键事实,也解释了闪电网络当前的一些用户体验。
“路由(routing)” 的字面意思是 “寻找路径”。
假设你要去一个从没去过的地方,你需要哪些信息?首先,你需要一张地图;其次,你需要在地图上找出你身处的位置以及目的地的位置;最后,在两个点之间找出一条可以走得通的线路。互联网导航软件在做的也是同样的事情。
在闪电网络中找出由通道前后连接而成的、给特定对象支付的路径,也是一样的道理。为此我们要借助两部分信息。
整个闪电网络的公开通道的连接情形,也即网络的拓扑图,在闪电网络中属于公开的信息。任何一个节点都可以从其它节点处获得该节点所掌握的拓扑信息。每一个节点在新建一条公开通道时,也都会向自己所知的其它对等节点播报(channel_announment
)1,从而更新这些公开的信息。
闪电网络中有两种通道,一种是公开的通道,另一种则是不公开的(unannounced channel)。如此处所述,前者的许多信息属于全网可得的公开信息。后者的这些信息并不向整个网络公开,但有可能被第三方知晓,比如,节点出于收款的需要而主动暴露给支付方。
另一种公开的信息,是这些公开通道的容量(双方共有的资金总额)。新的公开通道在播报出来的时候,也会公开该通道所使用的 UTXO(被称为 “channel point”),该 UTXO 的面额自然也就是这条通道的容量。通道容量的信息在规划支付路径时显然是有用的,它使我们可以不必尝试那些容量显然不够的通道,或者说,可以不在一条通道上尝试超过该通道容量的支付。
但是,这一信息也常常被诟病为对隐私性的侵害:能够同时观察链上数据和闪电网络拓扑信息的人,将知道哪一个节点在使用哪一条通道(哪一个 UTXO),并猜测汇入这个 UTXO 的哪一部分资金属于这个节点,甚至能从该 UTXO 的后续变化中了解该节点的情况,例如,该节点广播一笔非合作式结算该通道的交易之后,使用自己的另一个 UTXO 来为这笔交易追加手续费。
如果有一种技术,可以在不曝光通道 UTXO 的前提下可信地证明其容量,将是对闪电网络隐私性的重大改进。
还有一种公开的信息,是通道在转发特定方向的支付时的收费条件,以及允许 HTLC 留存的时间。由于一条通道有两个端点,所以它可以媒介两种方向的支付。每一种方向的支付的收费条件,都是由负责的节点自己定义的(通过 channel_update
消息来更新)2。显然,这也是对寻路有用的信息,支付方可以优先选择收费更便宜的路线。
值得一提的是,有一种信息并不是全网公开的信息:每一条通道的双方实时余额。显然,如果有这种信息,寻路问题将变得非常容易解决:一条路径到底能不能支付成功,只需观察路径上的通道的实时余额就可以知道了。但是,每个人都知道,这对隐私性来说是多么大的灾难:第三方观察者将能轻松地从通道余额的变动中找出哪个节点在给哪个节点支付、支付额是多少。这是闪电网络作出的取舍,牺牲支付的效率来提高隐私性。我认为这种取舍是值得的。
除了这些公开信息,我们还需借助一些私人信息。
为了发起支付,我们需要知道自己在给哪一个节点支付 —— 这就是我们需要接收者提供的信息:接收方节点在网络中的位置。
在最常规的情形中, 这就是收款方要向支付方出示 “闪电发票(invoice)”3 的原因。发票中包含了接收方节点的签名,可用于复原出接收方节点的公钥,也即其 node_id
,从而允许我们定位到闪电网络中的具体一个节点。发票中也包含了支付额以及支付哈希值,允许支付方使用这个哈希值来建立 HTLC。发票还可以包含要求支付方写明的备注、支付过期时间等信息。
依据公开信息(地图)和接收方提供的私人信息,我们就可以尝试在网络中找出一条路径,给接收方支付一定的数额。
在网络中找出一条令人满意的路径始终是一个有挑战性的事情,因为网络的情形是不断变化的。具备足够容量的通道可能恰好有节点下线了,或者在支付者希望传递支付的方向上没有足够多的余额。或者,虽然能找出路径,但所需的转发费用超出了支付者愿意负担的程度。
当前闪电网络的两个主要客户端实现 LND 和 Core Lightning 都使用 Dijkstra 算法4 的某个变种5 6。该算法常常用于在固定一个起点时寻找触达其它点的最短路径。
不过,闪电网络规范(BOLT)并不规定寻路算法,也就是说人们可以按自己的想法尝试各种不同的算法,这不会导致你的节点无法参与闪电网络。
当前,改进路由算法最新的一些尝试包括:假设通道总是不平衡的(大部分余额会位于一端),以此来猜测并获得已尝试过的通道的一些信息;在支付之前用失败支付来 “打探” 一条路径是否能成功;等等。
最后一个问题是,假定一个节点掌握了网络的拓扑图,并从接收者传来的发票中得知了接收者节点的 id,以及本次支付的支付额以及支付哈希值;然后,它使用路由算法,在网络中找出了很有可能支付成功的一条路径;那么,它该怎么告诉路径上的节点、请求他们使用 HTLC 来转发支付呢?
就像接力传递 HTLC 一样,这样的指令也是接力传递的。假设现在 Alice 在收到 Diana 的发票之后,找出了一条给 Diana 支付的路径:
Alice --> Bob --> Carol --> Diana
那么:
可见,一个节点在转发支付时要做什么,完全是由发送者在加密消息中指定的。(转发节点为什么愿意遵从这些指令呢?因为他们期待从中收到转发费用。)
发送者在一开始计算好路径之后,就构造出给每一个转发节点的指令,然后以加密的方式,确保相应的指令只能被相应的节点知晓。因此,每一个节点都不知道后续的节点收到的是什么样的指令。
其次,因为每个节点收到的加密数据包中,都没有此前节点的信息遗存,所以每个节点也都不知道自己之前的节点收到的指令。
最后,因为每个节点在解密、删去自己可读的消息之后,往下一个节点传递加密数据包时,会用垃圾数据将它填充到自己所获得的加密数据包的长度,所以,每一个节点收到的加密数据包的长度都是相同的。所以,没有任何一个节点能从这些数据中知道自己前面有几个节点、后面有几个节点 —— 它只知道自己的上一个节点和下一个节点,而无法知晓整条支付路径。这就实现了相当好的隐私性。
这是使用一种叫做 “Sphinx” 的消息构造技术实现的。其关键是按支付路径的倒序构造支付指令和加密包,并且层层加密:Alice 先构造给 Diana 的指令,填充垃圾数据成特定的长度(1300 字节),执行加密;然后在结果的前面加入给 Carol 的指令、删去后面多余的字节使数据成特定的长度(1300 字节),再次加密;再加入给 Bob 的指令、删去多余的字节,再次加密。
如果我们用括号表示加密的话,最终 Alice 发给 Bob 的加密数据包相当于是这样的:
(给 Bob 的指令 + (给 Carol 的指令 + (给 Diana 的指令)))
。每个人都只能解开一层加密,了解一些信息,然后将剩余的加密数据包(在填充垃圾数据成 1300 字节后)转发给下一个节点。与加密包伴随的是一段元数据,可被对应的节点用来解密(但对其他人是无用的)。给下一个节点的元数据总能依据本节点得到的元数据构造出来,所以 Alice 只需构造给 Bob 的元数据就可以了。
最终,每个转发节点收到的消息都是 1366 字节,其中 1300 字节是加密数据包;而剩余的 66 字节则是元数据。
这种在构造时层层加密、读取时层层解密的消息,非常类似于洋葱(onion),这就是为什么整套协议被命名为 “洋葱路由”。但它与 “洋葱消息” 是两种独立的特性。
“洋葱消息” 在闪电网络的语境下有特殊的含义,指的是一种 “闪电网络节点间直接通信” 的协议,我们会在下一篇文章中介绍。
此处所说的加密消息包的构造过程图示,可见这篇文章 8。
除此之外,由于给每一个中间节点的指令中都包含了其应在通道中保留 HTLC 的时间以及相应的面额,而这两个数值是逐跳递减的(以保证资金安全性并支付转发费用),为了避免中间节点从中猜出接收者节点与自身的距离,还应该在这两个数额中加入一些冗余,或使用多余的跳,以优化接收者节点的隐私性。
虽然发送者节点需要在拓扑图上运行路由算法、构造给路径上每一个节点的指令、每一个节点都要解密消息并给自己的通道对手更新状态,但是,整个过程的带宽和计算负担都很小。每两个节点之间只需传递两次消息,只需更新两次通道状态(一次是增加 HTLC 输出,一次是结算或表示失败),更新通道状态只需要生成一些签名并发送给对方(而无需实际上发送承诺交易,因为承诺交易的构造是确定性的)。所以,闪电网络的用户会发现,在成功的支付中,它的速度总是相当快,一般几秒之内就能确认支付成功。
接下来,我们探讨一些优化措施。这些优化措施分别优化了支付的成功率、发送者的负担和接收者的隐私性。
在上面的例子中,Alice 找出了一条路径给 Diana 支付。但仔细想想,如果支付只能走一条路径,显然是不够理想的。因为闪电网络是一个网络 —— 这意味着 Alice 有多条路径可以触达 Diana,每一条都在所需要的方向上都有一定的支付能力。如果只能走一条路径,就没有用尽闪电网络的潜能,可允许的支付额度会更小,或只能采用更远的路径而使费用变高。
我们仍以第一篇中的例子举例:
甲 <--------> 乙 <--------> 丙 <--------> 丁500 300 200 300 500 500甲 <----------------> 戊 <----------------> 丁500 200 300 500
如果只能采用一条路径,甲最多只能给丁支付 300 聪;如果能同时运用两条路径,就能支付 500 聪。
幸运的是,闪电网络的支付形式 HTLC 并不妨碍我们通过多条路径送达支付,反正,每一条路径上的每一个节点,要采取什么样的行动,都是发送者节点可以指定的!发送者可以将一笔支付额拆成多笔更小额的支付,每一笔都使用相同的哈希值建构 HTLC,然后使用不同的支付路径送达。这就是所谓的 “多路径支付(MPP)”。
事实上,在发送者给接收者构造的消息中,有两个字段,分别指明当前这条消息将送达的支付数额,以及发送者意图支付的总数额,这就可以告知接收者,发送者是否在使用多路径支付。
敏锐的读者会立即意识到一个问题:拆开的多笔支付不一定能同时送达,甚至可能只有一些会送达,而另一些会失败。这该怎么办呢?如果接收者在只收到一部分支付碎片时就回传原像,岂不就遭遇了损失?答案是,不怎么办 —— 只要接收者节点不在所有支付碎片都到达之前回传原像,就不会遭遇经济损失,而这是接收者节点出于自己的利益,会自然而然采取的行为。当然,在这个过程中,由于成功的路径不能获得回传的原像,各个通道中都会有一些资金被锁定一段时间,但这是可以优化的,发送者可以及时回传支付失败的信息,从而解除资金占用。
LND 客户端实现了一种叫做 “原子化多路径支付” 的技术。顾名思义,就是可以保证所有支付碎片要么一起成功,要么一起失败 —— 尽管仍可能遭遇一些支付碎片无法送达的情形,但接收者却一定不会遭遇损失,不会在只收到部分碎片的时候就返回原像。这是因为,用来构建 HTLC 的哈希值并不是由接收者给出的,而是由发送者指定的,并且发送者将原像的信息分散在不同路径的支付转发消息中。只有所有消息都到达,发送者才能抽取完整的原像信息,并让所有支付路径都回传原像。
这种技术听起来很美妙,但实际上,它却缺乏一个关键的元素:发送者无法收到支付证据。跟这些支付相关的原像信息,是发送者早就已经知道的。这使它更像一种高级的 “Keysend” 功能 —— 我们会在下一篇文章中介绍。
当前,所有主要的闪电网络客户端(Core Lightning、Eclair、LND)都已经实现了多路径支付。
在上一篇文章中我们提到,PTLC 可以替代 HTLC 并获得更好的隐私性。但 PTLC 能做的还不止如此。
PTLC 与 HTLC 的关键区别在于,它使用了另一种检查秘密值的方式:椭圆曲线点乘法(私钥与公钥的对应)。而它是 “可加的”。即:
a.G + b.G = (a + b).G = A + B
这就意味着,当支付发送者要用 PTLC 交换秘密值(私钥)a
的时候,也完全可以使用公钥 A + B
来检查,只要发送者以某种方式,将 b
告诉支付接收者 —— 而这正是支付转发消息可以做到的事。
因此,发送者可以创建一种基于 PTLC 的、可以收到支付证据的、原子化的多路径支付:生成 n 个秘密值 s_i
,这些秘密值的和为 s
;在不同支付碎片的支付转发消息中告诉接收者不同的 s_i
,并在支付路径的最后一跳中要求检查 S + A
的秘密值,也即 s + a
。 仅当所有的支付转发消息(支付碎片)都送达的时候,接收者才能知道 s
,然后才能在所有支付路径中回传秘密值。而发送者也可以收到支付证据 a
。
在上文的例子中,支付路径是由支付的发送者 Alice 根据自己所知的网络拓扑图构造的。这意味着,支付者要先有这个拓扑图。但是,拓扑图既需要存储空间,也会时时变化。到了闪电网络用户这里,就意味着,每次上线,都需要先拨出一个时间来获取拓扑图的更新并完成处理,然后才能支付。对于闪电网络的目标用户(使用移动设备的小额支付用户)来说,这种时延可能会构成很大的困扰。
“蹦床路由(Trampoline payments)” 就是一种解决这个问题的技术,其思路是:假定 Franky 具有完整的拓扑图,而发送者 Alice 没有,但是知道 Franky 的闪电网络位置;那么,Alice 先找出触达蹦床节点 Franky 的路径,然后再由 Franky 找出触达接收者 Diana 的路径。这就把保存和更新拓扑图、规划路径的工作外包给了 Franky。
因为 Franky 要寻找触达 Diana 的路径,Alice 当然要把 Diana 的闪电网络位置告诉他,这就影响了接收者的隐私性。对应的一种解决办法是,使用多个蹦床节点,也即,Alice 指示 Franky 寻找的,不是最终接收者 Diana,而是另一个蹦床节点 Gloria。当需要寻找的节点既可能是蹦床节点、也可能是最终接收者的时候,Franky 自然就不能肯定到底是哪一种情况。
别忘了,在支付路径上,一个中间节点要做什么,能够了解什么信息,完全是由发送者决定的。这给了我们相当大的自由来实现蹦床路由这样的技术。
蹦床路由的另一个缺点是,它可能会使用更长的路径,因此支付者需要付出更高的手续费,但这是可以接受的。
迄今,所有主要的闪电网络客户端都已经实现了蹦床路由。值得一提的是,接收者节点并不需要支持蹦床路由,就能接收支付。
最后一个我们要介绍的改进,与接收者的隐私性有关。
如前所述,因为洋葱路由的采用,闪电支付的发送方享有非常好的隐私性:支付路径上的转发节点并不知道自己前面有多少跳,也就难以确定发送方的位置。但是,接收者的隐私性,相对来说就没有那么好。转发节点可以通过 HTLC 的持续时间和数额猜测自己跟最终接收者的距离;接收者的节点位置首先要暴露给发送者,还有可能被发送者暴露给第三方(蹦床节点)。这些都是接收者隐私性的瑕疵。
那么,有没有一种办法可以不暴露接收者节点的位置,而依然能接收支付呢?
“路径盲化(route blinding)” 就是旨在解决这个问题的尝试。其想法是,接收者向发送者暴露的不是自己的闪电网络位置(node_id
),而是一条可以触达自身的路径;并且,这条路径所经过的节点和通道,是不向发送者暴露的(“盲化”);发送者能知道的仅有这条路径的 “入口节点”,即第一个节点。
实质上,这意味着,支付路径不再是全部由发送者决定的了;相反,接收者选择了一段路径,并将使用这条路径的必要信息(比如到达入口节点时应该为 HTLC 设置的存续时间和面额)告诉发送者。发送者只需用常规的支付转发消息触达入口节点,并递送接收者提前构造的加密消息,就能触达接收者;从入口节点开始,盲化路径上的节点会逐一解密安排给自己的消息,并依据其中的信息转发支付。
这再一次体现了闪电网络的灵活性!
关于 “路径盲化” 的详细描述,可见这份提议 11。
当前,路径盲化的详述已经在 2023 年 4 月进入了闪电网络规范(BOLT)#4 12。主要的闪电网络客户端都在致力于实现路径盲化。
值得一提的是,蹦床路由和路径盲化实际上可以相结合。Eclair 客户端就实现了将两者相结合的特性 13。
在本文中,我们介绍了一种标准的闪电支付流程所涉及的网络通信步骤:支付的发送者需要依据网络的拓扑图以及接收者所暴露的信息,创建支付路径,并借助节点之间的网络连接逐步转发消息并接力支付。这个过程具备相当好的隐私性,也尽可能降低了节点对稳定的互联网位置的依赖。
然后,我们介绍了几种优化措施:多路径支付、蹦床路由以及路径盲化。它们分别优化了支付的成功率、发送者节点的启动负担以及接收者的隐私性。这些优化措施都围绕着闪电网络的 “网络” 特性而展开。
在下一篇文章中,我们将回归用户体验问题,介绍一个普通用户感知很明显的事物:收款码。
闪电网络的发票本身是一次性的,它包含了一次支付的哈希值及其面额,在支付成功或者超时之后就会作废。这意味着,如果仅有发票这个工具,接收者将无法提供一个可重复使用的收款码。那么,我们该怎么解决这个问题呢?
(未完)
1. https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#the-channel_announcement-message ↩
2. https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#the-channel_update-message ↩
3. https://github.com/lightning/bolts/blob/master/11-payment-encoding.md ↩
4. https://zh.wikipedia.org/zh-hans/%E6%88%B4%E5%85%8B%E6%96%AF%E7%89%B9%E6%8B%89%E7%AE%97%E6%B3%95 ↩
5. https://docs.lightning.engineering/the-lightning-network/pathfinding/finding-routes-in-the-lightning-network ↩
6. https://medium.com/@rusty_lightning/routing-dijkstra-bellman-ford-and-bfg-7715840f004 ↩
7. https://bitcoin.stackexchange.com/questions/89542/single-hop-payment-vs-multi-hop-payments ↩
8. https://www.btcstudy.org/2023/05/18/what-is-onion-routing-how-does-it-work/ ↩
9. https://www.btcstudy.org/2022/03/29/outsourcing-route-computation-with-trampoline-payments/ ↩
10. https://bitcoinops.org/en/topics/trampoline-payments/ ↩
11. https://www.btcstudy.org/2023/03/10/route-blinding-proposal-by-bastien-teinturier/ ↩
12. https://bitcoinops.org/en/newsletters/2023/04/05/#bolts-765 ↩
13. https://bitcoinops.org/zh/newsletters/2024/01/31/#eclair-2811 ↩
]]>作者:Annoy
在上一篇文章中,我们介绍了闪电网络用户体验的基本元素,包括:在线收款、收款额度以及支付成功率。这些概念都可以从我们对闪电网络基本概念的分析中得出,也是对入门用户最有用的概念。
在这一篇文章中,我们将具体解释,在当前的比特币上,闪电通道是如何构造的、通道内的交易是如何实现的、这样的 “支付” 有什么样的特点。要而言之,我们要解释的是,为什么闪电通道是一种 “免信任” 的构造:你不必信任你的通道对手,也可以确定对方给你的支付是真实的、可以花费的。也可以认为,这是在讨论闪电通道的 “安全性” 概念:一个闪电通道的内部状态,是如何安全由比特币协议来表达,从而可以保证它符合人们的使用预期的。在我们解释完所有这一切之后,读者自然也会明白,为什么闪电网络是一种 “扩容方案(扩大比特币吞吐量的方案)”。
最后,我们也会指出,构造闪电通道的技术还有哪些进步空间。
本文绝大部分内容跟技术有关,如果你对它不感兴趣,可以跳过这一篇。但如果你对比特币的可编程性感兴趣,迄今为止,闪电通道依然是最好的思维材料。
一些读者可能已经知道,比特币的存在形式是 “未花费的交易输出(UTXO)”1。一种形象的理解方式是把它当成某种金属块:每一块都是相互独立的,但它们可以一起熔铸;熔铸之后可以切分成不同的块;然而一旦被熔铸,就不可能再找到原来那一块2。而比特币交易,就是这样的熔铸过程,或者说使用票据而签发新票据的过程。
每个 UTXO 都附带了两种信息:(1)面额,即其比特币价值(以 “聪” 为单位);(2)锁定脚本,也称脚本公钥,为花费该笔资金设置条件。我们可以用一种编程语言 Bitcoin Script 3 来编程锁定脚本。
在最常见的单签名钱包中,锁定脚本中放置的是一个公钥,用来检查花费交易所提供的签名是不是一个有效签名。除了签名检查之外,Bitcoin Script 还可以提供哈希原像检查(根据哈希值检查原像)、花费时间检查。
关于 UTXO 结构以及比特币脚本的工作,笔者的这篇文章 4 提供了更细致的介绍。这里仅着重指出的是:Bitcoin Script 中还存在一种流程控制操作码,例如: OP_IF
。其作用是将一笔资金的多种花费方式并置,例如,我们可以让一笔资金既可以被某两个公钥一起花费,也可以被另一个公钥以及一个哈希原像花费。这些不同的花费方式,我们称为 “花费分支”。每一个分支都是花费资金的充分不必要条件。这意味着,我们可以这些流程控制操作码来组合花费条件。
一般来说,比特币交易总是花费一些 UTXO,然后形成新的 UTXO —— 这就意味着,比特币交易所给出的不是关于操作的指令,而是处理的结果。这是比特币交易的一个重要特点。同时,因为每个 UTXO 都有自身的锁定脚本,通过对锁定脚本的编程,我们就可以反过来约束一笔交易的意义。
这里还要介绍的是 “承诺交易” 的概念:在一定条件下,我们可以用比特币交易来表达一种可信的承诺 —— 即使这笔交易没有得到比特币区块链的确认,但因为它是有效的(随时可以得到确认),所以,该交易(它所指明的结果)也是有意义的。
举个例子:两个好朋友 Alice 和 Bob 一起用各自的公钥控制一笔资金;当 Alice 签名这笔资金的花费交易,向 Bob 支付一笔钱(产生由 Bob 能够独自支配的 UTXO)时,这笔交易对 Bob 来说就是一个可信的承诺 —— 虽然它还没有得到区块确认,但因为它是有效的,而且 Alice 没有办法独自把钱转走,所以 Bob 可以保证这笔交易是随时可以得到区块确认的(只需加上自己的签名就是有效的比特币交易),所以,其支付效果是真实的。
接下来,我们要结合上述洞见,构造一种比特币上的免信任双向支付通道。免信任,意味着每一次支付都有自身的安全性保证,你不必信任对手,就可以确定所得的支付是真实的。这就是让上一篇文章中的 “算珠轴” 成为现实的东西。
假定 Alice 和 Bob 有一笔共同控制的资金 —— 这个 UTXO 的锁定脚本包含一个 2-of-2 多签名检查,必须是 Alice 和 Bob 都提供签名才能花费它。Alice 和 Bob 用来签名花费交易的公钥,记为 A
和 B
;而各自用来收款的公钥,记为 Alice
和 Bob
。他们还需生成一对长期使用的密钥,分别记为 SA
和 SB
,这个公钥需要分享给对方。
假定现在,Alice 和 Bob 在通道中各有 5 BTC,而现在 Alice 要给 Bob 转移 2 BTC。这时,Alice 向 Bob 提供请求一个新公钥 P1B
,然后以自己的公钥 SA
构造一个新公钥:
RA1 = SA * SHA256(SA|P1B) + P1B * SHA256(P1B|SA)
然后用 A
签名这样一笔交易并提供给 Bob:
输入 #0,10 BTC: A-B,2-of-2 多签名输出(即通道) 输出 #0,3 BTC: Alice 单签名 输出 #1,7 BTC: 要么,RA1 单签名 要么,两周后,Bob 单签名
可以看出,这里的 RA1
暗藏机关。
假设这笔交易得到区块链确认,它会熔掉双方共同控制的资金,立即将 3 BTC 交给 Alice;但剩余的 7 BTC,则会在两周以后才交给 Bob —— 前提是没有人知道 RA1
的私钥。如果 Alice 知道了 RA1
的私钥 —— 只需 Bob 向 Alice 暴露了 P1B
的私钥 —— 那么,Alice 就有两周的时间窗口,可以取走这 7 BTC。
对 Bob 来说,尽管这笔交易看起来对他不利,但它却是一个可信的承诺:(1)Alice 为这笔交易提供了有效的签名;(2)Bob 知道 SA
和自己的 P1B
,可以验证 RA1
的构成;他知道,只要自己还未暴露 P1B
的私钥给 Alice,Alice 就无法动用这个分支;(3)Alice 无法独自花费通道资金。也即,它实在地支付了原本属于 Alice 的 2 BTC 给 Bob。而这笔交易的两个输出的面额,表达的是支付完成之后的结果(Alice 3 BTC、Bob 7 BTC)。
同理,Bob 也向 Alice 请求一个新公钥 P1A
,然后用 SB
构造一个新公钥:
RB1 = SB * SHA256(SB|P1A) + P1A * SHA256(P1A|SB)
然后用 B
签名一笔不对称的交易并交给 Alice:
输入 #0,10 BTC: A-B,2-of-2 多签名输出(即通道) 输出 #0,7 BTC: Bob 单签名 输出 #1,3 BTC: 要么,RB1 单签名 要么,两周后,Alice 单签名
对 Alice 来说,这同样是一笔虽然有不利条件,但可信的承诺。
那么,我们要问的是,在这两笔承诺交易的输出 #1 中加入这样的公钥,是为了什么呢?答案是,它们是为了让这样的承诺交易变得 “可以撤销”!
现在,假设 Bob 要给 Alice 支付,那么,Bob 就向 Alice 请求一个新的公钥 P2A
,构造新的撤销公钥 RB2
,然后向 Alice 提供新的一笔承诺交易。 这笔承诺交易将同样使用双方的共同资金作为输入,而输出将表达此次支付完成之后的结果(比如,Alice 4 BTC,Bob 6 BTC,表示 Bob 支付了 1 BTC 给 Alice)。Alice 得到新的承诺交易之后,就给出 P1A
的私钥给 Bob;同理,当 Alice 签名新的、不对称的承诺交易给 Bob 之后,Bob 交出 P1B
的私钥。
如此一来,双方就安全地交换了一个私钥,从而 “撤销” 了上一笔承诺交易。这些承诺交易,从表面上看,依然是有效的比特币交易,但持有这些承诺交易的相关方,却再也不敢让这笔交易得到区块确认。比如,假设 Bob 将上一笔承诺交易提交到区块链,该交易将立即支付 3 BTC 给 Alice,同时,打开为期两周的时间窗口,允许 Alice 动用 RA1
的私钥来取走输出 #1 的 7 BTC —— 而 Alice 此时确实已经知道了 RA1
的私钥!
回顾一下我们前面提到的概念:UTXO 的锁定脚本可以使用多个花费分支;在一定条件下,有效的比特币交易可以成为可信的承诺;同时,交易的输出又携带了锁定脚本。闪电通道的洞见在于:通过在交易的输出中埋设特殊构造的公钥,并让双方签名意思一致但不对称交易,将这样的承诺交易变成了 “可撤销的”。也即,现在,双方可以几乎无限次地更新通道的内部状态(不断使用通道中的资金给对方支付),既不需要等待区块链的确认,也不需要信任对方,因为他们拥有密码学和比特币网络的保护。双方都可以随时以最新一笔承诺交易(通道的最新状态)退出通道;而一旦对方发布旧的承诺交易(即尝试欺诈),他们有一段时间窗口可以发动反击(惩罚),将所有通道资金全部收入囊中。
关于闪电承诺交易的特性,笔者的的另一篇文章 5 提供了更细致的分析。但是,这还不够,上述构造仅表明,我们拥有一种机制来更新一笔资金(一个合约)的内部状态,它跟现实的 “支付” 还有差距 —— 支付方应该能得到某种证据,证明自己已经支付了。
闪电通道承诺交易的脚本分析也解释了上一篇文章提到的一个细节:闪电网络的用户不能无限期离线。这是因为,旧的承诺交易依然是有效的、可以得到区块链确认的交易(否则它们就不是可信的承诺),没有什么能从技术上阻止你的通道对手将旧承诺交易提交上链。欺诈发生时,你唯一的反制措施就是在脚本允许的时间窗口内让你的惩罚交易得到区块链确认。也就是说,你需要观察到对手的举动,然后反击。但如果你长期离线,你可能不知道对手在尝试发布旧承诺交易。
一种可延长离线时间及帮助优化反击响应速度的解决方案叫做 “瞭望塔”。简单来说,就是将过期承诺交易及其对应惩罚交易的信息交给一个全时在线的节点,由后者在观察到链上出现某一过期承诺交易时就向区块链提交惩罚交易。
还值得一提的是,参与一条通道的双方的第一笔承诺交易,不是在他们为通道注入资金之后才签名的,而是在注入资金之前就签名的,这就规避了进入通道之后对方不再响应、资金卡死的风险 —— 这就是承诺交易的另一个作用:规避合约失控的风险。你肯定想问,这是怎么做到的?被花费的那笔资金不是还不存在吗?这还是跟 UTXO 的特性有关。UTXO 的位置是 “输出点(outpoint)”:它是某一笔交易的某个输出。给定参与通道的双方愿意为通道提供的输入,交易 ID 就是确定的,从而,通道 UTXO 的输出点也是确定的。双方可以用这个输出点来构造交易并签名。这是比特币在 2017 年的隔离见证升级后才明确具备的特性。
幸运的是,Bitcoin Script 恰好可以编程出 “原子化交换” 的一种形式:哈希时间锁合约(HTLC)。HTLC 是一种带有两个花费分支的锁定脚本,协助一方向另一方购买一个秘密值。假设 Bob 要向 Alice 购买一个秘密值,Alice 表示该秘密值的哈希值是 H,那么,为了一手交钱一手交货,Bob 可以向 Alice 提供一个这样的 HTLC:
要么,Alice 可通过揭晓 H 的原像以及自己的签名花费这笔资金要么,24 小时后,Bob 可独自取回资金
当 Alice 使用第一个花费分支时,Bob 就知道了 H 的原像(目标秘密值);如果 Alice 不愿意揭晓 H 的原像,24 小时之后,Bob 就可以独自取回资金。
在通道中,我们可以为承诺交易增加带有 HTLC 的输出。其作用也很简单:让支付的一方可以获得支付成功的证据 —— “我不是不愿意给你支付,只求给个收据”。以上文 Alice 给 Bob 支付为例,Alice 可以请求 Bob 给出一个哈希值 HB1
,然后在承诺交易中提供一个价值 2 BTC 的 HTLC 输出:
输入 #0,10 BTC: A-B,2-of-2 多签名输出(即通道) 输出 #0,3 BTC: Alice 单签名 输出 #1,2 BTC: HTLC 输出 #2,5 BTC: 要么,RA1 单签名 要么,两周后,Bob 单签名
此处没有给出 “输出 #1” 的锁定脚本构造、仅以 “HTLC” 代指,是因为,在实践中,其形式并不像上文表示的那么简单。笔者会在注释中提供一些说明,但对技术细节不太感兴趣的读者来说,不了解这些细节并不影响理解 HTLC 的功能 6。
当 Bob 给出 HB1 的原像之后,双方就可以使用新的一笔承诺交易来表达支付完成之后的状态:Bob 7 BTC、Alice 3 BTC。区别在于,Alice 不仅完成了支付,还获得了收据 —— HB1 的原像。
更有趣的是,HTLC 还可以将多个通道内发生的支付 “粘合” 在一起:给定在每一条通道内,都使用相同的哈希值来构造 HTLC,那么,这些支付只会一起成功,或者一起失败。
假设 Alice 要通过 Bob、Carol 给 Daniel 支付,那么,Alice 先向 Daniel 请求一个哈希值,然后让每一条通道都创建一个使用相同哈希值的 HTLC:
Alice -- HTLC --> Bob -- HTLC --> Carol -- HTLC --> Daniel
每一个中间节点(Bob、Carol)都会在自己的一条通道中收到一个 HTLC,并需要在自己的另一条通道中给出一个 HTLC,而两者面额的差值,就是支付成功时该节点可以获得的转发费收入。
当 Daniel 获得 Carol 给出的 HTLC 之后,就揭晓原像,从而领取 HTLC 的资金;每一条通道都发生相同的事情,原像便一路回传交给 Alice:
Alice <-- 原像 -- Bob <-- 原像 -- Carol <-- 原像 -- Daniel
这就是上一篇文章中我们提到的 “转发支付”(也称 “多跳支付”、“路由支付”)的基础,也是闪电网络能成为一个网络的原因。由支付的接收者给出一个秘密值的承诺(在这里是哈希值)、支付的发送者据以构造原子化互换合约,并通过不同的通道一路转发的方法,定义了闪电网络中的 “支付” 的概念:它是可以转发的、免信任的,此外,还可以让支付者获得支付证据(收据)。
在后续文章中,我们还会了解到一种无法获得支付证据的支付,其基本概念叫做 “Keysend”。比较之下,我们将理解,为何我们此处介绍的用法是一种 “标准用法”,是一个我们希望继续开发下去的方法。
如上所述,闪电通道是免信任的:每一种操作的每一步,都可以由比特币协议来表达,并获得密码学和比特币网络的保护。这同时也意味着,比特币协议自身的进步(例如,脚本编程能力的改进),也将为闪电通道带来改进的空间。
2021 年,比特币网络激活了 Taproot 升级 7,它为比特币带来了两项重要升级:可以验证 Schnorr 签名;将完整的锁定脚本转化为默克尔树(MAST),在花费时仅暴露需要用到的花费分支(而无需曝光所有分支)。
相比于 ECDSA 签名,Schnorr 具备一系列的优点:体积更小、验证起来更快、安全性证明更清晰、易于实现聚合签名。“聚合签名” 意味着,你可以将来自多个公钥的 Schnorr 签名聚合成一个恰当构造的公钥的 Schnorr 签名 8。再换句话说,(举例而言),原本的 2-of-2 ECDSA 多签名脚本,可以替换成 Schnorr 单签名脚本 —— 只需这个公钥是恰当构造的、双方共有的公钥。
如前所述,闪电通道就是双方共同控制的资金,并使用多签名检查来保证每一步操作都经过了一致的同意。有了 Schnorr 签名之后,我们可以在保证这一点的同时,让链上需要暴露的公钥和签名都只有一个。
这带来了效率和隐私性的双重提升。在需要将交易提交到区块链的情形中,原本的 “两个公钥、两个签名” 都可以被替换成 “一个公钥、一个签名”,体积缩减了,对区块空间的占用更少了。此外,在 “合作式关闭通道”(双方同时在线,一致决定关闭通道,立即分割资金,而不为交易输出设置时间锁)中,交易在链上的表现就像一个人作了一笔普通的支付,而不是两个人在分割资金。这提高了隐私性。
这是否意味着,使用 Taproot 之后,闪电通道的关闭将跟普通的支付完全无法分别?答,仅在双方合作式关闭通道时,仅在只观察链上数据时,是的。但是,
- 在非合作式关闭的情形中,一方会提交最新的承诺交易,而这笔交易可能会暴露相当多信息,从而表明这是一条通道,而非个人资金。
- 如果一条通道在闪电网络中公开宣告了,那么,它也会公开自己的通道 UTXO,从而让闪电网络中的第三方观察者知晓这个 UTXO 不是一笔个人资金,而是一条闪电通道。
这两者都会打消签名聚合带来的隐私性好处。
幸运的是,从上一篇文章中我们可以知道,闪电网络中有两类节点:转发节点和用户节点。对于无意参与支付转发的用户节点来说,完全不必公开自己的通道,也能正常使用闪电网络,他们将有很大概率,能享受到聚合签名带来的隐私性改进。
MAST 也同样提高了效率和隐私性。闪电承诺交易的输出可能会带有多个分支,这些分支的体积甚至很大(比如 HTLC 输出)。MAST 允许我们仅暴露一个真正用到的分支,而不暴露其它分支。这就减少了花费交易的体积。此外,它也掩盖了完整脚本的全貌、模糊了不同脚本的区别,使人们更难辨别一个输出是否来自闪电通道(或是其它类型的合约)。
关于使用 Taproot 构造闪电通道脚本的详情,可见这篇文章 9。
截至本文撰写之时(2024 年 2 月),LND 客户端已支持 “简单的 taproot 通道”。
Taproot 所带来的一种更大胆的升级可能性,是以 PTLC(点时间锁合约)来替代 HTLC。它可以带来隐私性和安全性的提升。
在原本的比特币脚本中,我们可以使用哈希值来检查秘密值(其原像);而有了 Schnorr 签名之后,我们可以用一种叫做 “适配器签名(Signature Adaptor)” 的技术 10,用一个椭圆曲线点(公钥)来检查秘密值(其私钥)。
在基于 HTLC 的转发支付中,整条路径上的每一个通道的相关 HTLC 都使用同一个哈希值,这意味着,如果路径上有某两个节点属于同一个控制者,他将有更大概率推断出谁在给谁支付;此外,当原像回传到其离接收方较近的节点时,他完全可以跳过中间的节点,由离发送方较近的节点继续回传原像,从而 “盗取” 一笔与这个 HTLC 相同的价值。
而有了 PTLC,我们可以让每一条通道中的支付都需要揭晓不同的私钥,从而让这些支付完全无法关联起来,但依然能保证原子性。
PTLC 的构造方法可见这篇文章 11,此处不述。
截至本文撰写之时,PTLC 尚出于规范的讨论阶段,尚未听闻有客户端开始实现。
我们在上面介绍的机制被称为 “LN-Penalty”,它是一套基于惩罚、允许更新一个合约内部状态的机制。但它也有一个明显的缺点:为了保证惩罚能力,节点必须保存过去每一次更新通道状态时候的资料:自己签给对方的承诺交易的交易 ID,该交易所对应的 惩罚 私钥。这就构成了一种存储负担。
此外,LN-Penalty 还意味着,双方每次都要签名不对称的交易,这给输出脚本的设计带来了复杂性,使其更难分析。
到目前为止,人们其实已经找出了在当前的比特币上改进后者 —— 允许双方都签名完全相同的承诺交易 —— 的办法 12。该方法同样只需要 “适配器签名” 元件。但是,这种方法无法改进前者 —— 存储负担。
然而,早在 2018 年,人们就已经提出了一种比特币协议可能的升级(SIGHASH_ANYPREVOUT),来实现一种叫做 “eltoo” 的方案,以同时消除存储负担和不对称的交易。其基本思想是,让更新的承诺交易(称为 “状态更新交易”)总能花费较旧的承诺交易(而反之不行),然后用 “结算交易” 来真正触发资金分割。由于新的承诺交易总能花费旧的,因此,用户只需保存最新一笔承诺交易及其结算交易,即可;即使对手尝试欺诈,也只需发布最新一笔承诺交易,便可使用最新状态来结算通道。
Eltoo 的更详细介绍可见此文 13。
截至本文撰写之时,SIGHASH_ANYPREVOUT 还未激活;而且,人们已经指出,有其它提议 14 可以模拟 SIGHASH_ANYPREVOUT 的特性,从而实现 eltoo。这是关于 “限制条款(covenant)” 的讨论的一部分。也印证了我们前面说的:闪电通道是可以完全由比特币协议表达的机制,因此,比特币协议自身的进步,也将带来闪电通道的进步。
在这方面的改进上,我们依然处于方法的讨论阶段。
值得指出的是,人们发现仅需适配器签名就可以构造出对称的闪电承诺交易是在 2020 年。而人们也早已经发现,ECDSA 同样能实现适配器签名(虽然 Schnorr 签名依然有自身的优势)。但是,迄今为止(Taproot 升级带来 Schnorr 签名的两年后),似乎依然没有人尝试以此路径实现使用对称承诺交易的闪电通道。也许是因为,要实现它并保证兼容现在的闪电网络规范,是一件需要大量努力而收效又不够吸引人的事情。
但是,eltoo 又持续地吸引人们,甚至影响了人们在比特币协议改进中的想法和态度。
如前所述,在当前的闪电通道实现中,每一笔支付都使用一个单独的 HTLC 输出来表达。这就意味着,在一条通道中,每多一笔正在发送的支付,其承诺交易都会多一个交易输出 —— 其体积会增大。闪电通道的免信任性来自其承诺交易总是可以得到区块链的确认,这就意味着承诺交易的体积不能超过一定的限度 —— 如果它大到无法塞进区块内,就不可能得到区块确认了。这就意味着,交易输出的数量不能超过一定的限度,也意味着正在发送的支付的数量不能超过这个限度。同时,它还意味着,恶意攻击者可以通过要求一条通道转发支付(本就不会成功的支付)来阻塞掉一条通道:假定一条通道同时最多只能转发 16 笔支付,那么,最小只需 8672 聪(16 * 542),就可以让这条通道不再能转发其他人的支付 —— 这被称为 “占位阻塞攻击(slot jamming attack)”。
但是,我们已经知道了,一个输出可以使用多种花费条件,这意味着,我们可以将多个 HTLC 合并到一个输出中;每揭晓一个原像,就能取走相应的数额;这种对数量和取款之后剩余资金的花费条件的约束,也是可以通过承诺交易来实现的。
这样的聚合 HTLC 输出有许多好处。一方面,它意味着,当一名用户同时取走多笔 HTLC 资金时,无论是通过原像分支还是通过超时分支,可以节省需要进入区块的交易的数量,从而节约手续费;另一方面,占位阻塞攻击将被大大缓解。
然而,这种 “多个 HTLC 在一个输出中并存” 的结构,将要求我们为所有的资金提取顺序安排可花费的脚本/交易。举个例子,假设我们聚合了 A、B、C 三个 HTLC 到一个输出中,那么,我们需要考虑 74 种取款可能性(例如,在 A 先被揭晓原像并取走的情形中,剩下的资金应该进入一个输出,允许 B、C 任意一个被先行揭晓原像取走,也允许 B、C 同时取走);如果我们要用承诺交易的方式来实现这样的聚合 HTLC,我们就需要预先签名 30 笔交易。可以看出,随着同时聚合的 HTLC 数量增加,需要考虑的取款顺序可能性会爆炸式增长。为了避免这方面的开销,需要有合适的操作码来帮我们实现更精巧的计算。
截至目前为止,“聚合 HTLC 输出” 同样仍处于方法的讨论阶段。详细介绍可见这篇文章 15。
本文至此就要告一段落了。我们从基础的概念出发,解释了闪电通道的构造,闪电支付的概念,以及,其构造方法如何可以随着比特币协议的升级而升级。我们还介绍了尚在讨论阶段,无法当下就实现的可能性。
本文覆盖了一般的闪电网络概述的内容,但是,它依然未全面展现闪电网络的 “网络” 特性 —— 没错,HTLC 可以将多条通道内的支付 “粘合” 在一起,但问题是,发送者根据什么信息,找出这样的路径?这些节点,依据什么指令,开始向通道对手提供 HTLC?这些问题,我们将在下一篇解答。它们的答案,也将真正体现 “网络” 的特性,并越过一般的闪电网络概述止步之处。
(未完)
1. https://www.btcstudy.org/2020/08/25/bitcoins-utxo-model-by-river-finance/ ↩
2. https://www.btcstudy.org/2022/07/19/the-words-we-use-in-bitcoin/。又或者,可以理解成一种票据:每一张票据都是相互独立的,而一旦被使用,该票据就会作废,但可能形成数量不定的新票据 —— 它不是一种 “账户”。 ↩
3. https://www.btcstudy.org/2022/09/24/script-a-mini-programming-language-by-Greg-Walker/ ↩
4. https://www.btcstudy.org/2023/04/18/interesting-bitcoin-scripts-and-its-use-cases-part-1-introduction/ ↩
5. https://www.btcstudy.org/2023/04/24/interesting-bitcoin-scripts-and-its-use-cases-part-5-lightning-channels-and-lightning-network/ ↩
6. 通道内 HTLC 输出的锁定脚本:(1)也带有 RA1 单签名
这样的 惩罚 花费分支;(2)在剩余的两个分支中,其中一个分支(对于给出承诺交易的一方来说是对方取款的分支,而对获得承诺交易的一方来说是给自己取款的分支)将需要双方的签名(并且要求双方提前签名承诺交易),而不是仅一方的签名。比如,在这里,Alice 给 Bob 支付,那么,在 Alice 签名的承诺交易中,HTLC 输出的哈希锁花费分支就需要双方的签名,而非仅需 Bob 的签名;预先签名的、花费该哈希锁分支的交易称为 “HTLC-成功” 交易。而在 Bob 签名的交易中,HTLC 输出的超时分支就要求双方的签名,而非仅需 Alice 的签名;预先签名的、花费该超时分支的交易就称为 “HTLC-超时” 交易。这些交易都会将资金转移到一个带有相同 惩罚 花费分支的输出中,要求资金的名义主人(比如,在成功揭晓哈希原像的场景中是 Bob,而在超时撤回资金时是 Alice)在一个时间窗口之后才能取走资金。使用这样复杂、不对称的构造,是为了贯彻惩罚的时间窗口一致性:无论一笔旧的承诺交易在得到确认时,其 HTLC 允许该欺诈方取款的分支是否可以使用,被欺诈的一方都有相同的时间窗口可以发起反制。更细致的推理见此篇:https://ellemouton.com/posts/htlc-deep-dive/ 。 ↩
7. https://www.btcstudy.org/2021/09/29/bitcoin-taproot-a-technical-explanation/ ↩
8. https://www.btcstudy.org/2021/11/29/schnorr-applications-musig/ ↩
9. https://www.btcstudy.org/2023/05/12/taproot-channel-translations-how-it-works/ ↩
10. https://www.btcstudy.org/2021/12/02/schnorr-applications-scriptless-scripts/ ↩
11. https://www.btcstudy.org/2021/10/26/payment-points-part-1-replacing-HTLC/ ↩
12. https://www.btcstudy.org/2023/05/04/an-introduction-to-generalized-bitcoin-channels/ ↩
13. https://www.btcstudy.org/2022/01/27/breaking-down-the-bitcoin-lightning-network-eltoo/ ↩
14. https://delvingbitcoin.org/t/lnhance-bips-and-implementation/376/ ↩
15. https://www.btcstudy.org/2023/11/15/htlc-output-aggregation-as-a-mitigation-for-tx-recycling-jamming-and-on-chain-efficicency/↩
]]>作者:Roy Sheinfeld
来源:https://medium.com/breez-technology/the-past-present-and-future-of-offline-payments-1ddb46054e11
对闪电网络,爱之深者责之切。我和 Ben Carman 都如此。在最近一篇 Stacker 帖子(中文译本)中,Ben 将流动性和离线支付列为闪电网络最影响用户体验、阻碍自主保管的方面。这似乎只是另一种 “未来怎么不像我们想的那样” 的抱怨。我们承诺会作出会飞的汽车,但我们只得到了互联网奇迹。一切都很神奇,但没有人觉得开心。
但是,看得更仔细一些,Ben 其实说对了一些东西。我一直在讨论闪电网络的用户体验,好几年了,也包括离线支付的困难。我甚至在 2019 年就为离线支付问题提出了一个解决方案(我下文会解释为什么我们从未实现它)。
这可能会让人觉得,我们承诺的未来永远也不会来了,但开发闪电网络就像登山。登山者自始至终看不到山顶在哪,直到他们真的到达山顶。他们甚至常常连下一段山脊在哪也看不见。计划路线当然很重要,但大多数时候你只能关心下一步踏在哪儿。但功夫不负有心人。就像在山顶俯瞰万物是对所有攀登努力的奖赏,更光明的未来货币也将是我们一点一点打造这个网络的奖赏。这也是闪电网络从一个想法变成一个持续运行的比特币支付网络的原因,也是我们走向未来的方式。
- 与登山相似的另一点是,没人能独来独往(图源:Sylvain Mauroux) -
因为离线支付是一个长期存在的使用体验挑战,我们要回顾一下问题出在哪里、当前有什么技术可以处理这个问题,以及未来还有什么可能。(我会在后续文章中讨论 Ben 提出的另一个问题 —— 通道流动性。)
只体验过法币、链上比特币和托管式钱包可能不会意识到 “离线支付” 的困难。法币和托管式钱包通过完全控制用户的资金来解决这个问题。银行/托管商 从其他用户的 银行/托管商 处接收 资金/借条,也在自己的用户要求的时候发送 资金/借条。如果用户从头到尾都没有真正保管过自己的资金,那他们在不在线就无关紧要。只有真正控制资金的人才需要在线。至于链上比特币,发送者只需要接收者的地址就可以了。
自主保管的闪电钱包面临更大的挑战,因为一笔交易所涉及的两方都需要同时在线。接收者需要给发送者提供一个闪电发票;发送者需要创建支付,接收者也要在线签名确认支付。这种安排回复了用户对自己的钱的控制权,但也可能成为用户体验中的痛点。
在 Norgay 和 Hillary 最终成功以前,有记录的攀登珠穆朗玛峰的尝试共有 11 次。Breez 应对离线支付问题的第一次尝试叫做 “Connect to pay”,它会提醒发送者通知收款者,支付即将发送,收款者应该把闪电钱包应用打开。这背后的用户体验理念类似于打电话:让双方都同时专注于同一件事情。但是,不需要操心过程就能收到支付的心理预期实在是太根深蒂固了。没办法,这是我们都想要的体验,差一点都不行。(人们甚至不喜欢打电话。他们编了歌曲来嘲笑它。)
我们的下一个想法要更加复杂一些,我们称之为 “Lightning Rod”。这个想法是使用 “暂缓兑付发票”,让发送者和接收者之间的一个路由节点中断支付,直到接收者回到线上。因此,它很像 Zeus 的 Zaplocker。
虽然 Lightning Rod 可以工作,我们从未让它进入实际使用,因为暂缓兑付发票对路由节点来说是不可扩容的。他们的资金会被冻结。当一个路由节点扣住一笔支付时,基本上相当于给接收方提供一笔免息贷款。但死水必腐。通过在路由中冻结流动性来解决异步支付问题,只会让流动性问题恶化。
我们遇上了一座对的山,但选择了错误的路线。
好消息是,处理离线支付的技术在不断进步。现有的方法中没有一个是十全十美的,但每一种都在某个地方很有用。
第一种是 “LNURL-Withdraw”。接收者可以扫描一个 QR 码或者输入一个 URL、指示自己的 app 请求来自一个发送者的资金。举个例子,希望从某个交易所取出资金的用户可以随时 “拉取” 自己的资金,而不是让交易所 “推送” 给自己(他们可能不在线)。
这种办法由两个重大缺点。第一,它要求发送者拥有一个节点,运行在一个持续在线的服务端上,所以对非托管的移动客户端和网页客户端来说,是不合适的。其次,“拉取” 模式仅在一种非常具体的情形中才有用 —— 你知道自己有钱可取。举个例子,它很难用在自发的打赏中。
手机通知是另一种支持离线支付的方法。在 iOS 和 Android 系统中,触发通知甚至可以给客户端 app 足够多的 CPU 时间来收取支付。使用手机通知来处理进入的支付,不需要接收方主动介入,也为这种同时性难题提供了一种自然的、不扰人的解决方案。这也是为什么我们在 Breez SDK 中添加了一个新特性,使用推送通知来协助离线支付。这对 SDK 的用户来说是一种重大的用户体验提升,而且只要求开发者付出少量工作。
Breez SDK 的方法是这样工作的:首先,开发者创建一个 webhook,互联网服务商可以在一笔支付还在路由中的时候调用它。只要一笔支付将这个 LSP 作为触达接收者的最后一跳,这个 LSP 就可以通过 webhook 调用一次通知分发服务(Notification Delivery Service,NDS),然后 NDS 会给用户的手机发送一条推送通知,并且带有指令。这只需要开发者做一些跑腿工作(建立一个 NDS),但结果是更好的用户体验,因为用户不需要将 app 保持在后台以接收支付。它也让移动端用户可以使用一种静态的地址(例如 Lightning address、LNURL-Pay 和 BOLT12)来接收支付。
手机通知是一项提升,但不是万灵丹。首先,它只能用在移动设备上;而且,要是设备关机了,或者用户禁用了通知,那就没法工作。而且,谷歌和苹果可以通过改变他们的操作系统处理通知的方式来削弱其作用。这就是为什么我们需要一种内置在闪电网络协议中的解决方案。
更高级的离线支付背后的想法很简单:让支付变成异步的。因为同时性问题对自主保管的移动端和网页端用户影响最大,而现实中几乎所有这些用户都会通过 LSP 连接到闪电网络,那么,为什么不利用这些永远在线的 LSP、在发送者或接收者离线时同步支付呢?
LSP 可以通过拦截 HTLC,适时地分解支付流,这就消除了同时性问题,或者说,将问题转移到了 LSP 层面 —— 对他们来说这个问题并不存在。一切将从嵌入发票中的一条消息开始:它表示接收者不在线,但连接到了一个 LSP。发送者将支付以及一条消息发送给自己的 LSP,让后者扣住它,在一段很长的超时窗口内等待进一步的指令。然后,发送者给接收者的 LSP 发送消息,请求 TA 在接收方回到线上时通知自己的 LSP(即依然扣住支付的那一个)。这时候,怎么做就取决于 LSP 了。当接收方回到线上时,他的 LSP 会给发送者的 LSP 发送信号、请求完成支付。
这种模式并不会牺牲网络在整体上的流动性,因为唯一会被冻住流动性的节点就只有发送者自己的 LSP,这也是用户实际上希望的。(译者注:一定程度上,本就使用 LSP 的发送者,因为可能经常不在线,而且只有跟 LSP 一条通道,本身就无法为网络的整体流动性作贡献。)
虽然听起来很简单,但这种办法需要更多的技术投入才能理想工作:静态发票、洋葱消息、盲化路由、蹦床支付,还有 PTLC。它是复杂的,但希望深入了解的读者可以看看我在 Honeybadger 2023 上的演讲。虽然一些闪电实现已经支持其中一些特性,但要让整个网络都采用它们需要时间,而采用率决定了互通性。
明天太阳还会升起,但今天已有光明。我的意思是,我们已经 拥有不同、可用的办法来处理闪电网络中的离线支付,而不需要牺牲保管特性,每一种都适合不同的应用场景。LNURL-Withdraw 已经可以在一些商业环境中使用了,而新的 Breez SDK 特性则为移动端用户启用了离线支付。
这很酷!我们不是一直都有这些东西,但至少现在已经有了。我们已经生活在昨天的未来中!
基于协议的解决方案还在开发中。许多实现,比如 Eclair、LDK、lndk 和 Core Lightning 都在所需特性的开发上取得了进展(没错,我看着你呢 lnd)。一旦实现,异步支付就会带来巨大的用户体验提升。这是值得追求的未来。
我确信,在我们到达那里之后,又会有其它挑战出现,需要我们的关注、挑战我们的耐心,但永远不要忘记,我们已经攀登了多高。
(完)
]]>作者:Anony
本系列文章旨在填补关于闪电网络的文献资料的一些空白。对闪电网络和闪电通道的整体性介绍一般都撰写于几年前 1,因此,它们往往只及于闪电通道和闪电网络的基本技术概念,而:
有鉴于此,本系列文章希望给出一份新的介绍,将闪电网络(钱包)的使用体验(尤其是其挑战)与技术概念关联起来,并基于这些概念以及闪电网络的 “网络” 特性,介绍闪电网络上已经发生的技术改进,以及还可以采用的改进。
本文是这个系列的第一篇文章。与一般的介绍不同,本文不会涉及构造闪电通道的技术以及形成闪电网络的元素(这是后续篇章的内容),相反,我们将先了解闪电网络的使用体验(准确来说,是自主保管的移动端闪电钱包的体验)。我们将从闪电网络的基本想法出发,推理出它的使用体验中的关键部分。往后,我们将通过 “体验-技术” 的视角不断解释形成这些体验的技术原因,并相应呈现改进的空间。
本文不要求读者对比特币的可编程性有任何理解,但要求读者至少了解并能够辨识出比特币的使用体验 —— 在比特币网络上发送交易并使之得到区块链确认 —— 中的关键元素:地址、手续费、确认时间。
闪电网络的想法非常简单:
假设有一笔资金,是由甲乙双方共同支配的,也即只有双方一致同意,才能花费它;那么,当其中的甲方希望用这笔资金中属于自己的部分来给乙方支付时,这就不需要获得其他任何人的见证 —— 只需要乙方确认即可。甲乙双方可以在很长一段时间之后结算双方的余额,完成资金分割。
假设一些人不止有一条通道,那么,他们就可以为其他人 “转发支付”。比如,甲与丙没有通道,但是甲跟乙有通道、乙跟丙也有通道,那么,甲就可以借助乙,给丙支付。例如,甲把钱交给乙,乙再把钱交给丙,就像接力跑一样。只需要每一条通道的双方彼此确认,就可以完成支付,也不需要其他任何人的见证。
这就是闪电网络的想法:用一对一的通道来实现即时的支付确认;用通道组成的网络来最大化单个节点的支付能力。
这里,我们暂不讨论如何安全地实现 “通道” 和 “转发支付”,这会是后续章节的内容。我们需要知道的仅仅是,用户最常接触的比特币的保管形式是 “单签名钱包”,也即只需一把私钥就能花费的资金,显然是不能用来充当通道的,因为其控制是排他的。
重点是,光凭这段概述,我们就可以发现闪电网络使用体验中的几个关键元素。在使用闪电钱包之前,必须先理解这几个元素,才能顺利使用。
在闪电网络中,当你要收款的时候,你必须在线。从上面的推理来看,这是很容易理解的:收款涉及更改通道中的资金的状态,而通道是你与他人共享的,所以你必须在线,与对方一同更改资金的状态。
这既不同于比特币的使用体验(只要他人知道你的一个比特币地址,就随时可以给你支付,不需要你在线),也不同于绝大部分支付系统(比如银行、互联网电子支付系统)的使用体验。
不过,在当前的移动端闪电钱包中,这一点已经不会让用户产生太多感知:联网的手机都有通知系统,钱包的服务端可以向你发送通知,要求你打开 App,也即上线,以接收付款。但是,如果太长时间不处理这样的通知,可能会导致支付退回。
对这种特性的一种误解是,认为闪电网络钱包的用户必须全天候在线。这是不对的,因为不在线仅仅意味着你无法收款,并不代表你在通道中的资金立即变得不安全。但另一种正确的理解是,闪电网络钱包的用户不能无限期离线,你必须隔三岔五上线检查自己的资金状况。具体可容许的离线时间视用户所用的软件实现而定,一般来说,离线三天五天是没有问题的。其中的缘由我们会在后续章节中解释。
一定程度上,在线收款的特性也意味着,发送支付和收取支付的双方必须同时在线。但如上所述,这不难解决,关键是让接收支付的一方即时上线。在未来,这可以通过增加 “异步支付” 技术来解决这种同时性问题。
类似地,因为收款涉及通道,所以,它的收款能力是有上限的。
我们以上面的那张图来举例。现在,算珠都位于 Alice 这边,因此,Alice 无法再用这条通道来接收支付了 —— 因为 Bob 在这条通道中已经没有余额了!但与此同时,Bob 使用这条通道来接收支付的能力,则是 Alice 在这条通道中的所有算珠。
这就是 “收款额度(inbound liquidity,入账流动性)” 的概念。这也是一个迥异于比特币和其它支付系统的概念。在其它支付系统中,接收支付的能力在大多数时候是没有限制的。
这种特性给闪电网络的用户带来了大量的不解和困扰。当你没有足够多的收款额度时,要么你的收款会失败(他人无法给你支付),要么,会导致新通道的创建(因此你需要支付在比特币链上确认一笔交易的手续费,这时候的手续费会远远高于你平时发起闪电支付的手续费)。
这些问题在刚入门的用户这里往往会更加严重,这跟闪电网络当前的实现方式有关:在当前的闪电网络中,往往仅由发起通道开设请求的一方向通道注入资金(这被称为 “单向注资”),但这就导致了,在刚刚创建好的通道中,一方(比如用户)将没有任何收款额度 —— 在你要接收支付之前,你必须先花掉一些钱。
现在,关于终端用户的收款额度问题,一种解决方式已经逐渐获得采用:安排一种服务商(称为 “LSP”),每当用户要收取支付、收款额度又不足时,就主动与用户开设新的通道,为用户提供收款额度(当然,也要收取开启通道的手续费)。这种办法同样也可以解决上述新用户的收款额度问题:当一名新用户第一次接收闪电支付时,LSP 与该用户打开通道,让该用户能够收取支付。唯一的问题是,它将对用户的第一笔闪电收款的数额提出要求,但无疑已经方便很多。
因此,本文推荐新用户在上手使用闪电网络钱包时,总是使用闪电钱包(而不是比特币地址)来接收第一笔支付。这样会更加便利。
此外,“双向注资” 技术也已经在主要的闪电网络客户端软件中实现,有望为进一步解决新用户的收款额度问题提供帮助。
对于非移动端用户,比如全时在线节点的运营者来说,有更多的方法可以获得收款额度,比如:“潜水艇互换”,我们将在后续篇章中介绍。这些方法在一定程度上也可以为移动端用户所用,但从便利性来说,都不及 LSP 的上述服务。
但总的来说,“收款额度” 会始终伴随你的闪电网络使用历程,并且也是闪电网络与其它支付系统最显著的区别。它造成了许多困惑,这也意味着,如果我们能理解它,就能少去很多困惑。
与 “收款额度” 相对的是 “支付额度(outbound liquidity)”。但是,这个词并不能体现闪电网络的特殊性,因为我们的每一种支付工具都有余额(支付能力的上限)。
我们回到上面的转发支付的例子。我们扩展成四方,并假设他们在彼此的通道中的余额如下图所示:
甲 <--------> 乙 <--------> 丙 <--------> 丁500 300 200 300 500 500
甲在 “甲-乙” 通道中有余额 500 聪,乙则有 300 聪。其余通道以此类推。在这里,虽然甲拥有 500 聪,但如果他要给丙或者丁支付,则支付额无法超过 200 聪。
无法给丁支付超过 200 聪的数额,显然不能归因于丁的收款额度不够 —— 在 “丙-丁” 通道中,丙还有 500 聪。问题在哪儿呢?在于 “乙-丙” 通道中,乙只有 200 聪。
也就是说,成功的支付,要求在支付所途径的每一条通道中,转发支付的一方都有足够多的余额(支付额度)。在甲给丁支付的过程中,如果支付数额超过 200 聪,这笔支付将无法通过 “乙-丙” 通道。
这就是为什么本文要使用 “支付成功率” 的概念。这也是为什么人们常常说闪电网络比较适合小额支付而不适合大额支付 —— 成功的支付不仅要找出一条由通道连成的路径,将支付者与接收者连起来,还要求组成通道的每一条通道,在传递支付的方向上都有足够多的余额。
“聪(satoshi)”是比特币的最小单位。
在后面的章节中,我们会了解如何可以优化支付的成功率。简单来说,我们需要做的是将一笔支付打散成多个碎片,让每一部分途径不同的路径,从而最大限度利用不同路径的支付额度。
在本文中,我们了解了闪电网络的基本思想,并从这些基本思想出发,解释了闪电网络用户体验中的一些关键元素。这些描述无法涵盖今时今日移动端自主保管闪电钱包的全部体验,但勾勒出了其中的一部分。在往后的章节中,我们将越来越多地了解这些体验,也越来越多地解释其背后的技术或设计成因,并指出哪些技术进步已经改变了或有望改变这些体验。
闪电网络是一个网络 —— 对这个事实,再怎么强调都不过分。这种 “网络” 特性,既形成了其使用体验中的一些明显的特点(也许有人将它们视为一些缺点),也指明了这些体验的优化空间。
在下一章,我们将介绍闪电通道在比特币上的构造方法。
(未完)
1. https://www.btcstudy.org/2020/08/23/understanding-the-lightning-network-part-building-a-bidirectional-payment-channel/ ↩
2. https://medium.com/breez-technology/understanding-lightning-network-using-an-abacus-daad8dc4cf4b ↩
]]>作者:Che Kohler
来源:https://thebitcoinmanual.com/articles/jit-channel/
译者注:“JIT 通道” 已经广泛用在配有 LSP 的自主保管闪电钱包中。
将比特币推广给下一个 10 亿人并不是一件简单的事,但随着成长的痛苦,我们也能学到教训。在媒介某一些价值转移,比如你的小额电子支付、打赏以及向结算层的更高层级流式支付时,出于许多理由,链上交易是不实用的,其经济代价和交易确认时间无法满足要求。
闪电网络这样的二层解决方案在持续成熟, 而且每天都路由几百万笔交易,减少了上链确认交易的需要,但这离不开单体节点的孜孜不倦的管理。
闪电网络可以运作,但它要求每个用户都管理自己的支付系统 —— 运行一个节点、建立通道、保证资本量以及不断再平衡通道。虽然这对于普通的比特币业余人士,以及那些希望通过运行路由节点来赚取额外收入的人来说可能很有趣,但普通用户不会为了路由 69 聪而搞懂这些东西。
付出跟回报根本不成正比,这也是为什么许多闪电网络的用户都选择使用托管钱包,或者跟一个闪电网络服务商一起保管自己的闪电资金。
闪电网络用户体验的一个痛点是启动成本;在迁移到二层的过程中,你需要一个全节点来广播发送到链上的交易、建立一条通道,还需要在收款之前先获得收款额度。这跟比特币的用户体验是很不相同的:从你上手使用比特币钱包开始,你就随时可以接收付款,而且可以接收任意大的数额。
为了免去用户的辛劳,异步支付和 JIT 通道这样的技术被发明出来。这些方法是为了彻底改变用户进入闪电网络以及在闪电网络中交互的体验而提出来的。
“按需使用(Just-In-Time,JIT)”,是从投资管理借用来的一个概念,指的是在闪电支付到达时才创建通道。“JIT 通道” 最初是一条虚拟的支付通道;一旦这个虚拟通道收到一笔支付,通道的一方(闪电网络服务商)就广播一笔链上交易,将该通道锚定到链上(使之成为一条常规的通道)。
也就是说,“JIT 通道” 是一种在来自公开网络的支付进入时,由 LSP 对一个客户响应式开启的通道。这使得没有闪电通道的客户也能立即开始接收闪电支付,而他们获得入账流动性(收款额度)的成本会从这第一笔支付的支付额中扣除。
这种技术与传统的方法大相径庭。在传统的模式中,用户必须自己提前开设通道,并且是自己要准备好开启通道的资金。
注意:JIT 通道不应该跟 “JIT 路由” 相混淆,后者是一种用于再平衡现有的通道、以接受原本可能会被拒绝的支付的技术。
换句话说,JIT 通道的工作流允许一个客户在即使没有任何收款额度时,也能通过闪电网络接收支付。服务该客户的 LSP 开启一条零确认通道,来路由支付,同时扣减掉开启通道的手续费。在通道开启之后,客户端就能领取支付。
JIT 工作流中的关键词:
JIT 通道对闪电网络来说是关键的,理由如下:
不幸的是,因为链上交易和闪电支付的结算速度有差别,JIT 通道有一个内在的假设,就是铆定这条通道的 UTXO 最终会在链上确认,但路由到客户端闪电支付是即时结算的。
虽然 JIT 通道减少了对通道构造和处理速度较慢的区块链层的依赖,但它也引入了自己的信任假设。LSP 承担了转发支付的风险,需要信任客户;客户也需要信任 LSP。
LSP 将需要决定自己愿意承担多少风险,并相应评估客户;如果客户能够提供 LSAT、节点 ID 或者可以承担名誉损失的 Nostr 公钥,那可能会有所帮助。
那么,没有使用经历的用户可能会在 JIT 通道的支付规模上受到限制。使用限制更宽泛的 LSP 可能会受到攻击,但也可以将损失视作一种获客成本(实际上,只会损失一些链上手续费,以及在不会得到支付的通道中锁定资本的机会成本),并且希望将来可以从可信任的客户上获得回报来覆盖损失。
如果客户和 LSP 都不信任彼此,那他们就会陷入死锁。不愿意信任客户的 LSP 会扣住通道注资交易而不广播,直到自己看到支付原像;而不信任 LSP 的客户会扣住支付原像,直到看到注资交易;这就跟 JIT 通道的目的违背了。JIT 通道需要双方的信任,以协助及时的流动性部署。
打破这种死锁而不引入信任的唯一办法就是使用区块链来确认合约,以保证注资交易当且仅当原像提供给 LSP 时就会广播。
这可以通过使用一个 HTLC 来做到:其哈希锁分支由 LSP 和客户一起签名,并且 LSP 提供从哈希锁分支花费到通道注资输出点的的见证,而客户提供自己的签名以及原像,以让通道注资输出点得到确认。
(译者注:这种合约并不是标准的哈希时间锁合约,但原理上相似。哈希锁分支要求双方的签名,而不是只有一方的签名;并且哈希锁是用进入支付的同一个原像构造的。LSP 给客户提供将资金花费到通道注资点的签名,客户一旦加上自己的签名和原像,就能让通道得到区块的确认。当然,通道内的承诺交易,双方要提前构造。)
但总的来说,从结算的角度看,这跟标准的支付通道创建并没有什么区别。
虽然有这些潜在的缺点,显然 JIT 通道有很大希望,让闪电网络变得更加用户友好,而且高效。就像我们这个领域的所有开发,一定有需要考虑的取舍;推出之后,市场会确定这些取舍是否值得、这种方法能否继续进步,以及还有什么取舍需要解决。
不管怎么说,在引导入门和流动性管理上的好处,让 JIT 通道成了闪电网络演化中的巨大进步。
(完)
]]>作者:sdaftuar
来源:https://delvingbitcoin.org/t/an-overview-of-the-cluster-mempool-proposal/393
去年春天,@sipa 和我第一次向一群 Bitcoin Core 贡献者提出了一种新的交易池设计的概念,后来我写了一个 github isue(Proposal for a new mempool design · Issue #27677 · bitcoin/bitcoin · GitHub)。一整年过去,这个想法不断浓缩,我将在这篇帖子中为提议整体提供一个更新之后的抽象总结 —— 包括它的动机以及它会产生的影响 —— 让每个人都能跟上这个主题。
这里是对草案实现中的交易池策略(mempool policy)变更的总结(https://github.com/bitcoin/bitcoin/pull/28676)—— 注意,所有这些术语和想法都进一步解释如下:
注意,对交易图添加一种新的限制,会立即让现有的 “CPFP carve out” 规则失去意义,所以它也会被移除。见下文的讨论。
此外,通过限制族群的体积,我们可以实现更优的交易池内交易排序以便挖矿(下文将解释),也因此,两种额外的行为将被本提议修改:
当前的交易池设计围绕着两种不同的排序,我们跟踪的每一笔交易都有自身在 “祖先费率排序” 和 “后代费率排序” 两种序列中的位置。
注:一笔交易的 “祖先费率”,指的是这笔交易及其未确认的祖先交易的总手续费除以它们的总体积(以 vbyte 计),这些祖先交易是在区块中包含这笔交易时也应该包含的最小集合。而 “后代费率” 是类似的概念,是一笔交易及其在交易池内的后代(的总手续费除以它们的总体积),这些后代交易代表的是要从交易池中驱逐这笔交易时也应该一并驱逐的集合,从而避免在交易池中保存孤儿交易。
我们使用这两个序列来实现交易池驱逐和交易(挖矿)选择。无论什么时候,交易池的体积要超过指定限制时,我们就驱逐具有最低后代费率的交易,及其所有的后代交易。如此重复,直到交易池的体积回到指定限制之下。
当我们在构造区块模板时挑选交易时,我们就选择具有最高祖先费率的交易,并让其所有祖先交易进入区块。然后,更新交易池内剩余交易的祖先分数,以反映这些选择,再继续运行根据祖先费率挑选交易的算法,直至区块满载。
这些算法在几个方面存在缺陷。尤其是,驱逐算法并不保证被驱逐的第一笔交易总是最差的挖矿选择,而且存在一些情况,我们会将很可能进入下一个区块的交易驱逐出去。(下文有解释)。
此外,挖矿算法比我们维持的祖先费率序列要更加复杂,而且,仅仅查找我们的祖先费率索引是不足以确认两笔交易中的那一笔应该先从交易池中挖出的。缺乏对交易池内交易的全面排序,也让 RBF 计算难以跟矿工的激励一致,并且创造了扭曲 —— 有时候被我们用 RBF 替代掉的交易其实对矿工更加有利可图。
为了解决所有这些问题,我们可以转为维护一种对交易池交易的全面排序,以实现刚好相反的挖矿算法和驱逐算法,并提供更好的 RBF 规则,以决定什么时候允许替换。
考虑这个交易图:
具有最低后代费率的是交易 A,5 聪/vB。但是,具有最高祖先费率的是交易 E,6 聪/vB。这意味着我们的挖矿算法会选择交易 E(以及交易 A)出现在下一个区块中,但如果我们的交易池恰好满了,我们的驱逐算法会选择交易 A,然后把整个完全驱逐出去。
注:一个更复杂的例子夸大了这两种费率之间的差别,可以在这里找到。
当 Bitcoin Core 收到一笔跟池内现有(至少一笔)交易冲突的交易时,它会使用(大致上)由 BIP 125 描述的交易池规则来确定是否应该接纳这笔新的交易(这将要求驱逐池内与之冲突的交易)。然而,这些规则中有许多形式的激励不兼容:
为了展示一些具体的激励兼容问题,我们来看看这个例子:A 和 B 已经在交易池中了,而 A’ 是跟 A 冲突的交易。
在这里,交易 B 的祖先费率是 50 聪/vB,而交易 A’ 只支付 3 聪/vB。虽然在 BIP125 规则下,我们将把 A’ 评估对 [A, b] 的一笔成果的替代,因为它比 A 支付了更高的手续费(3 聪/vB vs. 1 聪/vB),也比 A+B 整体支付了更高的手续费(30000 聪 vs. 10000 聪)。
没有采纳激励兼容的替代交易的情形也可能发生。考虑这个例子:A 和 B 已经在交易池中,而 B’ 是 B 的潜在替代交易:
B’ 无法替代 B,因为在 BIP125 规则之下,它所支付的手续费率比 B 更低,但是,B’ 的祖先费率是 25 聪/vB,而 B 的祖先费率仅仅不到 2 聪/vB !
还存在许多别的例子。
为了解决跟驱逐和挖矿相关的问题,我们开始意识到,它们应该是对交易池的相反操作 —— 当我们需要驱逐交易时,我们应该总是驱逐对挖矿最没有吸引力的交易。在当前的交易池设计中实现这一点的困难在于,我们无法容易地计算最不可能被挖出的交易 —— 挖矿算法的时间复杂度为 $O(dn \log n)$,其中 $n$ 是交易池内存在的交易数量,而 $d$ 是对后代交易的数量限制,所以,在当前,要通过对整个交易池运行算法来决定先驱逐哪一笔交易,并不可行。
但是,这正是我们想要做的事情 —— 所以,一个问题油然而生:我们可以向交易池增加什么限制,来确保一套全面排序是可行的呢?找到答案的一种办法是思考:在一笔交易要加入交易池时,池内有多少交易的挖矿分数可能会改变?如果我们总是在交易池内维护一种完整序列,那么这就给了我们一个在添加一笔交易时,更新交易池需要多少工作量的概念。
这个问题的答案是:需要更新跟这笔新交易相关的每一笔交易的挖矿分数,不论是父交易还是子交易。考虑这个例子(感谢 @sipa):
使用一些计算,我们可以看出,交易 B 有最高的祖先费率,是 73 聪/vB。一旦它和交易 A 被选入下一个区块,交易 D 就将有最高的祖先费率,是 72 聪/vB 。顺序是交易 D、交易 F、交易 F、交易 J 和交易 L。
如果一笔新的交易 M 到达:
那么,祖先费率就完全反过来了!祖先费率最高的交易变成了 M(80聪/vB),然后是 L(79聪/vB,一旦交易 K 被选入的话)、然后是 J、H、F、D 以及 B,跟交易 M 到达之前完全相反。
这解释了,为了在一笔新交易到达时约束更新挖矿分数所需的工作量,我们必须约束在交易图中相关连接的元素的规模。我们将这些相互连接的元素称为 “族群(cluster)”。
我们可以保证交易池随时都有全面排序
要对交易池全面排序,只需要(a)在每一个集群内使用某种 排序/交易选择 算法(例如现有的祖先费率);以及(b)设计一种方式,能够合并排好序的族群。
关于(a),我们现在意识到,我们也许可以做得更好,不止于使用一种祖先费率算法来排序一个集群。对于非常小的族群,我们可以使用指数运行时算法,在所有可能的交易拓扑有效排序中选出最优的序列。对于更大的集群,我们依然可以回到使用我们的祖先费率算法,或者考虑更高级的策略,只要能将 CPU 使用量约束在合理范围内。
注:我们使用 “线性化(linerization)” 一词来指代在一个族群内产生一个拓扑有效排序的过程。
关于(b),合并已经排好序的多个族群:这是非常直接的。假设我们有两个族群:$C_1 = [tx_{a_1}, …, tx_{a_n}]$ 以及 $C_2 = [tx_{b_1}, …, tx_{b_m}]$ 。为了确定将这两个族群合并成一个序列的最优方法,我们先找出 $C_1$ 族群中最高手续费率的一串交易,以及 $C_2$ 族群中最高手续费率的一串交易,两相比较;最佳的一串将是我们将包含在区块中的第一组交易。然后,我们从它所在的集群中移除这串交易,然后重复,直到所有交易都被选入区块。
注:我们将一个族群内的连续、最高手续费率的交易串的计算称为族群的 “分家(chunk)”。可以可以在线性时间内计算一个已经线性化的族群的分家。
要了解这些话题的更多信息,以及其它相关的概念,见 @sipa 的文章。
我们只需按照分家手续费率的降序迭代族群,然后从每一个分家中选择交易,直到区块满载。
在交易池满之时,要运行驱逐算法,我们只需按分家手续费率的升序迭代分家,并选择要驱逐的分家,直到交易池回落到体积限制之下。
首先要问的是,“激励兼容” 应该意味着什么。要考虑这个问题,先看看这个例子,交易池的起始状态是这些交易(假定所有的交易都是 100 vB 的体积):
然后,考虑一笔潜在的替换交易 C’,它跟交易 C 冲突,而且将产生这样的交易池:
我们如何知道要不要接纳交易 C’ 呢?使用 BIP125 的规则,我们可能会说,应该拒绝 C’,因为它带来了一个新的未确认的父交易;或者,我们可能会说,这个未确认父交易规则很傻,因为我们只是使用另一笔 手续费/体积 都相同的父交易来替代一笔父交易,而交易 C’ 比 C 支付了严格更高的手续费和手续费率,所以我们显然应该接纳它。
事实证明,无论哪一个解释,都无法完全让人满意。那么,考虑 “旧” 交易池(包含了交易 C)的全面排序,以及 “新” 交易池(包含了交易 C’)的全面排序。
“旧” 交易池的最优排序是:[A, B, C]、 [D](方括号表示分家)。仅挖掘旧交易池的第一个分家的矿工将使用 300 vB 的体积、得到 950 聪的手续费;而挖掘整个旧交易池的矿工将使用 400 vB 的体积,获得 1050 聪的手续费。
同时,新的交易池的最优排序是:[D, C’]、[A, B] 。打包第一个分家的矿工将使用 200 vB 的体积获得 700 聪;而打包两个分家的矿工将使用 400 vB 的体积,得到 1150 聪的手续费。
我们可以将这些数据画成图,称为每一个交易池选择的 “手续费图”,其中 x 轴是矿工要包含的交易的体积,而 y 轴是矿工可以用一定的体积实现的最高手续费。我们填入 “旧” 交易池和 “新” 交易池的数据,就可以得到这样的手续费率图:
注意,来自旧交易池的点(300, 950)在新交易的线之上!这告诉我们的是,在某一个体积上,旧交易池会比新的交易池更好;类似地,某一些体积上,新交易池会比旧交易池更好。
我们要说的是,这些手续费图,是 “不可比较的”,意思是没有一者严格优于另一者,同时它们也不是等价的。如果红线总是高于蓝线、或重合于且至少在一个点上严格更好,那么我们就说新的交易池 “严格好于” 旧的交易池。这就意味着,无论要从交易池中选出多大体积的交易 —— 先不管区块接近满载时候的长尾效应 —— 矿工都总是会对 “新” 交易池更满意。
这张图展示了为什么许多判断是否应该接纳一笔替代交易的启发式方法不能令人满意。相反,为了确保一笔替代交易总是对矿工更好,我们提议,将 RBF 验证规则定义为交易池必须使用这种费率图得到 “严格更好” 的答案。
注:为了防止免费转发(一种 DoS 顾虑),我们依然要求新交易的总手续费必须超过与其冲突的交易的总手续费,而且差值必须至少等于这笔新交易的体积 * 最小转发费率。不过这只有微小的作用,因为上述激励兼容规则已经要求总手续费不能下降 —— 这条额外的规则只是让数值增加一点。
CPFP carve out 规则不再被支持。
当前 Bitcoin Core 中的 CPFP carve out 规则允许一笔交易的后代交易的数量超出交易池限制,在这笔交易跟闪电协议有关的时候。
相关的背景是,默认情况下,我们的交易池策略会拒绝一笔进入的交易,当它将导致交易池内现有的一些交易拥有超过 25 个后代交易的时候。而在闪电网络的场景中(就我的理解),两方可能都在相同的父交易中有一些可以花费的输出。那么其中一方可能有激励用低手续费将交易 “钉” 在交易池中(使之无法被挖出),办法就是创建子交易来填满这个 25 笔后代交易的限制,但并不实质上追加大量手续费。这时候,另一方可能希望使用 CPFP 来为父交易追加手续费,但做不到,因为已经触及了后代交易的数量限制。
CPFP carve out 规则允许交易池超出这个后代交易数量限制,正好一笔,这笔进入的交易应该仅有 1 个交易池内的祖先(而且满足一些比较小的体积限制,以 vbyte 计量)。它的作用是,在两方协议中,当每一方在共享的交易中都有一个可以花费的输出时,任何一方都总是能够添加一笔子交易来为父交易追加手续费。要了解更多的背景和思考,请看这个邮件组帖子。
但是,这个 CPFP carve out 规则仅在只有后代数量限制、没有其它拓扑限制的时候才是有意义的。有了提议添加的对族群体积的限制,这个 CPFP 规则就没有用了 —— 一个敌手可以利用族群的体积限制来阻止另一方向共享的父交易添加子交易。而且,因为族群限制不是具体一笔交易的特性(而后代交易的数量限制是),就没有直接的办法可以引入一种新的 carve out 规则,能够允许以受限制的方式绕过族群体积限制。
所以这里的结论是,carve out 规则必须被放弃,而闪电通道这样的当前依赖于它的项目需要使用别的机制来绕过这个问题。“v3 交易提议” 是提供这样的机制的尝试。
一个公平的问题是,网络上是否有某些使用模式跟限制族群体积的想法不相容?具体来说,网络中的其它参与者可以向一笔交易附加子交易、干预某人向该未确认的父交易附加自己的子交易的能力,是公平的吗?还是一种糟糕的设计呢?
对于这种具体的担忧,我不认为对族群体积的限制跟现在的后代交易体积限制的角色有实质上的不同。具体来说,任何时候,如果一个敌手可以向一笔交易附加子交易、触及族群体积限制(从而阻止额外的花费交易),那么也能以相同的方式触及后代交易的体积限制。
所以,我认为,基本上,族群体积限制不会比后代交易体积限制更糟(只要我们可以将族群体积限制设置得跟当前默认得后代交易总体积限制一样,我预期应该这样做)。
这跟我们限制祖先交易和后代交易的体积的理由完全相同 —— 它为交易选择算法的工作表现设置了一个下界,在我们需要使用贪婪算法来解决背包问题的时候。具体来说,通过将祖先交易的总体积设置为区块体积的大约 10%,我们的贪婪交易选择算法将总能获得我们用别的背包解法可获得的最大手续费的至少 90%。
类似地,通过限制后代交易的体积,我们可以在驱逐交易时限制可以发生的免费转发的数量。
为了在族群交易池种实现相同的效果,我们需要限制分家的体积。最简单的办法就是限制一个族群的体积(因为一个族群可能仅由一个分家组成)。虽然理论上有可能将分家的体积限制到一个(比族群限制)更小的数值,但这会带来更多需要解决的理论问题,可能不值得花费这个力气和复杂性(因为一个族群内的各分家的手续费率并不是单调递减的)。
族群交易池方法的一个好处在于,我们不再需要维护祖先费率和后代费率状态,这就意味着我们不再需要在 向交易池添加交易/从交易池移除交易/发现新区块 时迭代 祖先交易/后代交易。这意味着我们不再需要为了限制 CPU 用量而强制适用对祖先交易和后代交易的统计限制。
一份对应最近版本的 Bitcoin Core 主分支的草案实现已经开启。实现的细节依然在优化,而且族群体积限制和 RBF 的参数选择还需要在进一步的代码基准测试以及对历史交易数据运行本逻辑的效果分支之后决定。
向前看,这里引入的、用于分析交易池(以及 RBF)的框架,可能对于分析交易包验证和交易包 RBF 是有帮助的,虽然那些工作依然在进行中。
]]>作者:SHINOBI
来源:https://bitcoinmagazine.com/technical/mercury-layer-a-massive-improvement-on-statechains
CoimmerceBlock 今天推出了 “Mercury Layer”,这是他们的 statechain 变种的升级版本。你可以通过这篇长文来了解 Mercury Statechain 是如何工作的。而本次的 Mercury Layer 升级代表了对最初的 statechain 实现的巨大提升,但是,不像最初的 Mercury Wallet,它还没打包成可以完全面向消费者的钱包。它是作为其它钱包可以集成的一个库和命令行工具而发布的。本文简单总结了它的工作原理:
Statechain 在许多方面都类似于支付通道,即,它是一个集体共享的 UTXO,将预先签名的交易作为保证人们可以强制执行所有权的手段。Statechain 跟闪电通道的主要区别是可以参与共享一个 UTXO 的参与者数量,以及一个可以强制执行的追索权转移给另一个参与者的方式。
在闪电通道中,一个 UTXO 是由静态的两方创建并共享的,而一个 Statechain 是由一个运营者开启的,而且可以在任何两个愿意相信运营者的诚实的人之间自由转移,完全是链下的。希望创建一个 Statechain 的人跟运营者一起创建一个公钥,这个创建者和运营者都只拥有该公钥背后的私钥的一个碎片,没有任何一方拥有完整的私钥。然后,他们预先签名一笔交易,允许这个创建者在一个时间锁到期后单方面取回自己的资金。
要转移一个 Statechain 的时候,现在的所有权人就跟接收者以及运营者合作,使用他们的碎片私钥签名一个密码学证据,证明他们要转移这笔钱,然后接收者跟运营者生成一对新的碎片私钥;这对新的碎片合起来依然是原来那个私钥;然后就可以签名一笔带有时间锁的交易,并且让新的所有权人的时间锁比原所有权人的更短(从而保证新的所有权人总是能比旧所有权人更快取走资金)。这个过程可以一直重复,直到时间锁无法再缩短,这时候这个 Statechain 就要在链上关闭了。
所有权人也要转移过往每一次转移的整个历史链条,这样用户可以验证时间锁被恰当地缩短了、运营者使用 Mainstay 协议(Opentimestamp 协议的一个变种)给这些记录加上了时间戳;每一段数据都在一棵默克尔树上有自己的唯一 “空间”,从而保证只有一个版本的数据是有时间戳的。这让每个人都能审计一个 Statechain 的整个转账历史。
Mercury Layer 给原版的 statechain 所带来的最大改变就是盲化(blinding)。Statechain 服务的运营者将不再能够知晓被转移的任何东西:即,相关的 TXID、相关的公钥、甚至它跟用户为预签名的交易合作生成的签名。
引入了 Schnorr MuSig2 的一个盲化的变种,Mercury 可以协助备用交易的签名流程,而无需知晓被签名的东西的任何细节。这使得有必要改变一些设计,以适应这个运营者不再能看到和发布一个 Statechain 的转移历史的完整性的事实。他们甚至不再能验证自己所签名的交易。
在上一版中,一个 Statechain 的 当前所有权人/交易集合 的唯一性,是由运营者通过 mainstay 发布这个 Statechain 的完整的转移历史来见证的。现在,这是不可能的了,因为,在这个盲化版本中,运营者完全不知道这些交易。这使得我们有必要找出一种新的办法,来让运营者见证一个 Statechain 的最新所有权。所有这些数据都会完全由一种 “客户端验证模式” 来传递。运营者只需跟踪他为一个 Statechain 签名一些东西的次数,然后告诉一个用户需要这个数字,即可。用户从支付方接收这个 Statechain 的过往交易历史,然后完全自己验证交易的数量跟运营者的声明一致,然后完全验证所有签名都是有效的、时间锁每次都递减了合适的量。不再将完整的 statechain 交易以及转账顺序发布到 Mainstay,因为运营者被设计成根本不知道所有这些信息,他只向最新的用户发布他自己跟以前每一个用户合作时候的公钥碎片(而不是完整的聚合公钥)。这使得任何收到一个 Statechain 的用户都能(通过由发送者提供的交易数据)验证转账历史以及当前状态是有效的。
运营者服务端需要跟踪每一个 Statechain 的过往签名次数,办法是在创建时为每一个 Statechain 分配一个随机的标识符、并存储相关的面额、碎片私钥和碎片公钥(而不是完整的聚合公钥)。在新的私钥分割及再分割方案中,服务端传递自己的碎片公钥给用户,而执行再分割所需的数据是盲化的,这样服务端就无法知晓用户的完整碎片公钥,因此也不再能生成完整的聚合公钥、在链上辨识资金的位置。
这种设计甚至不允许运营者知晓他是跟最新的所有权人签名了合作关闭,还是跟一个新的所有权人签名了一笔新的预签名交易;他不能通过任何细节来分辨这两者。但是,如果有人尝试通过提供一笔假的、无法被结算的交易来 “重复花费” 一个 Statechain,接收方是能够发现的。首先,接收方将看到为这个 Statechain 提供价值的 UTXO 在链上被花掉的。其次,因为运营者必须签名所有的状态变更,在过往交易的历史中,将只有一个清晰的合作式关闭解决。两者都可以让接收方发觉这是无效的,应该拒绝。
Statechain 构造也允许闪电通道 “架设在” 一个 Statechain 上,办法是将这个 Statechain 转移给两人的一个多签名地址,然后这两人可以在这基础上协商一组传统的闪电通道承诺交易。如果这两人需要关闭这个闪电通道,他们需要先在链上关闭这个 Statechain,并且,也因此,他们也不能在闪电交易中使用长于 statechain 寿命的时间锁,但其它方面,跟常规的闪电网络是完全一样的。
总的来说,有了新版 statechian 的巨大隐私性提升,以及它跟闪电网络的可组合性,比特币上的二层交易机制的经济可行性和灵活性的许多前景都已开启。尤其考虑到最近交易池动态的巨大变化以及最终的手续费压力。
这套机制提供跟 Ark 相同类型的流动性优势,即,可以自由转移而无需考虑收款额度,但跟 Ark 不同的是,它已经上线了,现在就可以使用了。不可否认,它跟闪电通道这样的东西由不同的信任模式,但考虑到它在灵活性以及可扩展性上的巨大收获,它毫无疑问是一个应该探索的领域。
(完)
]]>作者:benthecarman
来源:https://stacker.news/items/379225
过去几个月来,我感觉比特币社区对闪电网络越来越感到厌倦。说实在的,这完全说得过去。在 2017 年,我们得到的承诺是,这会是一个去中心化的支付网络、永远能提供便宜的支付、而且每个人都能运行自己的节点。今天,闪电网络的普通用户实际上并不使用闪电网络,他们只使用一款托管钱包;而其中少数运行闪电节点的用户常常发现这是一个繁重的任务。我们 Mutiny Wallet 的同仁,一直在通过尝试开发轻量的自主保管钱包来改变这一切,而且,我认为,我们已经很好地实现了这个梦想。在本文中,我将分析这些问题,并提出一种新的理解闪电网络的视角,然后论辩这对比特币的未来意味着什么。
闪电网络的用户体验挑战中的第一个也是最艰巨的一个,就是通道流动性。(译者注:这意味着,用户不仅有余额即支付额度,还有收款额度。)今天,除了闪电网络,没有其它支付系统存在这样的问题,所以这常常会让许多用户感到困惑。更糟糕的是,我们没有任何实用的诀窍可以解决这个问题。Muun Wallet 使用 “一个链上钱包 + 潜水艇互换” 的模式绕过了这个通道流动性问题,这个解决方案在平时工作得非常好,直到手续费涨起来的那天,每个用户都会意识到它并不是一个真正的闪电钱包。(译者注:在这种模式下,用户并不跟任何人创建通道,只通过链上资金与链下资金的互换来实现闪电支付;每当用户需要发起闪电支付时,都要做一笔链上交易。所以用户的支付成本是跟网络手续费率挂钩的,在费率上涨的时候就会暴露出来。)更好的解决方案是 JIT 流动性,就像我们在 Mutiny 的做法,或者是通道拼接技术(Phoenix 已经实现了)。(译者注:“JIT” 是 “Just in time” 的缩写,意为 “随时获取”,大致做法是,当超出收款额度的支付到达时,会导致新通道的建立,从而使用户获得足够的收款额度。)这些解决方案部分地抽象掉了流动性问题,但还不够,我们常常会遇到用户在客户支持频道中提问,为什么某一些支付有手续费,而另一些没有。事实就是,通道流动性对于绝大部分终端用户来说不是一种可用的用户体验。
闪电网络的另一个主要痛点是离线收款问题。根本上,你必须在线,使用你的私钥来签名并申领一笔支付。技术上来说,有一个持续进展的规范提议可以解决这个问题(本质上是创建一个通知系统,告知用户何时应该上线收款),但也并不能解决根本的问题,也依然有局限性。也有一些解决这个问题的尝试,最著名的当属 Zeus Pay 的闪电地址。本质上,它只是创建一笔卡住的支付,然后等待收款方上线来接收支付,这给人们带来了不计其数的问题,甚至迫使我们 Mutiny 禁止用户向他们支付,因为它导致了许许多多的强制关闭通道事件。这是一个难题,因为其余所有的 比特币/密码货币 生态的工作模式都是你只需 复制-粘贴 一个地址,然后就可以随时给这个地址支付,不会有任何警告要求你提醒自己的朋友记得打开钱包。闪电地址这样的东西甚至让它进一步恶化,因为它从一开始就需要一个网络服务器来使你能够获得发票。
我认为,通道流动性问题和离线收款问题,是自主保管的闪电钱包没法流行起来的两个最显著的理由。绝大部分用户听到这两个问题中的任何一个,都会想 “算了”,然后转去使用一款托管钱包,因为那实在是容易太多了。如果我们只有这两个问题,我认为自主保管的闪电钱包还是好的,也许不会成为人们使用闪电网络的主流方式,但我们可以让用户体验足够好,好到能让大一部分人群以自我主权的方式使用闪电网络。然而,表象之下还有更多的问题。
通道流动性是一个问题,但它也有欺骗性。在你拥有 10 万聪的收款额度时,你会认为自己最多可以收到 10 万聪的支付,但不是这样的,你常常收不到任何支付。这是因为链上手续费。在闪电通道中,每当你发生一笔支付时,你都需要创建一笔新的预先签名交易,而这笔交易需要为每一笔正在处理(还未完成)的支付安排一个输出,这些输出会影响交易的体积,因此需要为之提供手续费;网络手续费率越高,你的流动性就越少(被分配给了手续费)。在我们解决了 Mutiny 的大部分导致强制关闭通道的问题时,这个问题就成了数量最多的客服请求。即使你做对了一切事情、理解了流动性并你的支付准备了足够多的流动性,支付依然可能不成功,因为链上手续费太高了。这总是让人失望,因为,闪电网络的全部意义不就在于你不必非得支付链上手续费吗?基本上,当前所有的闪电通道,都可以因为链上手续费率变得足够高而沦为无用之物,因为一次支付需要太多准备金。显然这只是一种夸张,但我希望我表达清楚了:链上手续费不仅影响开启和关闭通道的代价,即时你是一个勤奋的节点运营者,仅在手续费率低的时候开启通道,那也不够,你的通道需要足够大,大到足以在未来的任何时候、链上手续费率的任何水平、为你的每一个 HTLC 支付链上手续费。随着链上手续费不断上涨,这个问题只会变得更加严重。
为这个保证金问题而提议的解决方案是 “锚点通道”、“交易包转发”、“一次性锚点”,等等。这些想法都有价值,也不错,但一定程度上只是掩盖了问题。它们确实能够让手续费保证金变得非常低,甚至可以是零,然而,其取舍在于,你需要可以使用的链上资金来为你强制关闭通道的操作追加手续费,这样交易才能得到区块确认。(译者注:这里解决方案的核心是 “CPFP”,使用高费率的子交易提高父交易的吸引力;因此,父交易,在这里是通道的承诺交易,可以不准备任何手续费,也就没有资金占用问题。但取舍如作者所述。)这又一次打破了自主保管钱包用户的使用体验,因为他们 必须 在闪电网络资金之外持有链上资金,这样他们才可以追加手续费。而需要准备的链上资金的规模依然动态地取决于链上手续费率。解决这个问题的方法包括让其他人来帮你追加手续费,但这会引入一个受信任的第三方,所以并不理想。
当我们列出一个闪电节点需要做的所有权衡、尤其是高手续费环境对它的影响时,我不禁思考,我们到底在干什么?我们走的路是错的吗?闪电网络依然从头到尾是一个极为强大的支付协议,但它的局限性在于它需要 扩大规模。基本上我列举的每一个问题,在你拥有一个大闪电节点时都会消失不见 —— 你有大量的流动性和高运行时间。我们应该优化这一切。市场一直在教育我们,这么多年了,90% 以上的闪电用户都在使用托管式钱包,因为它在扩大规模上就是做得更好。那么,我们如何能够不使用托管钱包而用上大规模的闪电节点?
遗憾的是,结合现有的大体量的闪电网络基础设施以及自主保管解决方案,依然力有不逮。到目前为止,做到这点的唯一真实的方法是我们前面提到的 Muun Wallet,但它并不真的解决了问题,因为所有一切都只是链上交易。但是,Muun 做对了一些事情。设计一个通向闪电网络的更简单的协议接口的架构,是一种天才的想法,而且给了我们两个世界里最好的东西。我们可以发起快速而便宜的支付,并让那个大男孩通过运行闪电节点来收取手续费。刚刚启动的 Aqua Wallet 本质上也是一个 Muun Wallet,只不过是在 Liquid 上,这是一个很好的权宜之计,但并不能从根本上解决问题。
在我们继续前进之前,我们应该后退一步,分析一下我们要尝试解决什么问题。比特币有一个根本上的扩容限制:其区块大小。如果我们可以拥有无限的区块大小,那我们就不需要任何 layer 2 解决方案了,因为我们只需要链上支付。然而,我们生活在真实世界中,而且有 1 MB 的区块体积限制,它限制了我们可以在链上确认的交易数量。闪电网络是对比特币的巨大提升,因为我们不需要将 每一笔 交易都发布到链上,我们只需要开启一条通道,然后就可以发起几乎无限笔支付。那么,闪电网络怎么没有一剑封喉?因为闪电网络让我们可以将支付转移到链下,但没有做到能让我们将所有权转移到链下。根本上,闪电网络依然依赖于,最终的最终,一个 UTXO 会归属于某一个用户。所以,即使链上的每一笔交易都属于某一条闪电通道,我们依然会撞上一个限制 —— 能够拥有自己的通道的人的数量终究是有限的。我们需要的是另一种 layer 2,可以扩展 utxo 的所有权,并且可以跟闪电网络交互,这样我们就有办法在扩大支付容量的同时扩大所有权的容量。
那么,我们如何扩大所有权的容量呢?简单来说,今天的答案是托管(custody),无论是纯粹的托管商(比如 Wallet of Satoshi)或者处于灰色地带的(比如 fedimint 和 Liquid),今天我们能用的唯一办法就是托管或者联盟桥。在比特币上,唯一能够将一个 UTXO 的所有权委托给多方的办法就是多签名,然而,它要求任何一个用户希望交互时,每一个用户都在线,而且,当你沿着这条路走得足够远,最终你只会重新发明闪电网络。
那么我们注定要失败吗?没有能够以自我主权的方式扩展比特币的方法吗?幸运的是,答案是否定的,但我们需要一些软分叉。限制条款(covenant)就是扩大所有权容量的办法。有许许多多的限制条款提议,但归根到底,它们所提议的都是添加一种办法,让你可以拥有一种比特币地址,可以限制其中的资金被花到哪里去、怎么花。这看起来似乎很奇怪,但我们今天的比特币上已经有这种东西了,OP_CTLV(CheckLockTimeVeiry),是在 2016 年的软分叉中激活的,它仅允许你使用具有给定 locktime 数值的交易来花费一个比特币地址中的资金,所以它让你可以把关可以花费一个 UTXO 的 时间。当前的限制条款提议所做的是让你可以把关可以花费一个 UTXO 去哪里。有这个简单的元件,我们就可以开发出许多不同的协议,允许扩大所有权的容量。
不过,未来并不灰暗,即使没有限制条款,我们依然可以扩容比特币,只不过不是以理想的方式。在 Mutiny,我们正在权力推进在钱包中实现 fedimint,我个人(以及我们团队中的其他人也)认为,这是比特币当前最好的扩容解决方案。Fedimint 给了我们可以动态地跟一群人分享所有权的能力,而且可以通过网关跟闪电网络交互。这是当前技术下比特币扩容梦想的顶峰,我们将不遗余力帮助它成为现实。
(完)
]]>作者:Peter Todd
来源:https://petertodd.org/2023/v3-transactions-review
“V3 交易” 是一组提议中的交易池策略(mempool policies)集,旨在允许合约式协议(例如闪电通道)中的交易使用 “子为父偿(CPFP)”、锚点输出和交易包转发作为支付手续费的首要方法。在本文中,我们会了解对 V3 交易以及锚点输出的期待是什么样的,以及它们在闪电通道这样的协议中的作用。最终我们会看到,它们对 CPFP 的依赖,使得它们在区块空间(以及手续费)上昂贵得多,相比于 “手续费替换(RBF)” 技术,在绝大多数情况下都将强制关闭通道的成本提高了大约 2 倍。我们也会解释何以 V3 交易形式会成为对挖矿中心化的一个潜在威胁,因为协议外(out-of-band)的手续费支付手段可能会比锚点交易便宜很多。
感谢 Fulgur Ventures 资助这项研究。他们对本文的内容无任何编辑权限,也未在出版之前审核。
闪电通道以及类似的协议依赖于交换预先签名的交易来工作,除非出现了意外,这些交易本无意于被挖出。意外,就是指参与者之间不能达成一致意见的时候。在这些意外情形中 —— 比如,通道的某一方下线了 —— 资金会通过签名并广播必要的交易来复原。举个例子,在典型的、没有待处理的 HTLC 的情况下,一条通道的两方都拥有一笔对应着这个通道最新状态的承诺交易,它花费持有通道资金的 2-of-2 多签名输出,将资金发回给各方控制的公钥。
问题在于,为得到区块确认而需支付的手续费率是经常变化的,而且会大幅变化。其次,Bitcoin Core 当前要求交易的手续费率必须在某个门槛之上,才能被本地节点的交易池接纳。这个手续费率门槛也会随着网络的确认需求而变化,因为节点的交易池的容量上限是固定的。
虽然闪电协议有一种让参与者能供协商预签名交易的手续费率的机制,但这种机制也无法预测未来。因此,经常出现这样的情况:预签名交易的手续费要么太低,因此无法被及时确认;要么太高,因此浪费了一些资金。
在特定情况下,网络确认需求的意外增加甚至会让通道状态的解决变得不可能,因为进入交易池的手续费门槛已经高于预签名交易的手续费率。这在还有 HTLC 待处理的时候是尤其有害的,因为 HTLC 可能具备严格的时间窗口,如果不能在窗口期结束之前处理,应该得到资金的人就可能遭受损失。
闪电通道已经实现了一部分的解决方案,就是 “锚点通道”。锚点通道使用了两项关键的技术:
在锚点通道中,承诺交易加入了两个面额为粉尘门槛的锚点输入,目的是允许 本地 以及/或者 远端 的参与者使用 CPFP 来提高承诺交易的实质手续费,在双方所选的手续费太低、无法在合理时间内被挖出时,能够提高交易的确认优先级。
两个锚点输出的脚本都是这样的形式:
<local_funding_pubkey/remote_funding_pubkey> OP_CHECKSIG OP_IFDUPOP_NOTIF OP_16 OP_CHECKSEQUENCEVERIFYOP_ENDIF
你可能会好奇,为什么这样本质上没有价值的输出,要使用 OP_CHECKSIG
(检查签名) 操作码。为什么不使用一种裸露的、最小化的 OP_TRUE
输出? 不管怎么说,如果别人花费这个输出,不也意味着是在支付手续费、加速我们的承诺交易被挖出吗?
因为,不幸的是,我们必须处理 “交易钉死攻击”:它会让 RBF 手续费追加法变得极为昂贵,甚至完全不可行。基本上,如果这个输出只是一个 OP_TRUE
,那么一个攻击者可以广播一笔交易、花费这个 OP_TRUE
锚点输出,使得我们使用 RBF 来追加手续费变得昂贵,甚至完全不可行,因此这笔承诺交易也难以在合理时间内得到确认。
因此, OP_CHECK
保证了只有实际参与这条通道的各方才能花费锚点输出,从而阻止任何钉死攻击。这同样是设置两个锚点输出的原因:我们不希望一方能对另一方发动钉死攻击。
实际上,两个锚点输出是冗余的: to_remote
输出就可以作为 to_remote_anchor
输出,这是我在撰写本文时发现的。只需一个锚点输出就够了。1
当前,Bitcoin Core 会孤立地判定是否要将一笔交易纳入本地交易池 —— 不考虑该交易的任何子交易。因此,即使有了锚点通道,承诺交易依然有可能因为网络手续费率上涨得太多而无法进入任何一个交易池(因为交易池的容量都是有限制的)。这是用,锚点输出就无法使用 CPFP 机制了,因为父交易根本就无法进入交易池,因此无从开始。
“交易包转发” 机制旨在通过评估交易 包(将相互关联的多笔交易作为一个整体)来解决这个问题。有了交易包转发,即使零费率的父交易也可以进入交易池,只要这个交易包包含了(通过 CPFP)为父交易支付手续费的子交易。
如果交易包转发能够实现,锚点通道就能在所有环境下使用了。相比于 RBF,它们依然不够高效 —— 我们下文会讨论到 —— 但至少是能够工作、不必顾虑交易池的条件的。
“V3 交易” 提议结合了交易包转发、一次性锚点以及新的交易池接纳规则,以尝试优化锚点通道和锚点输出。值得注意的是,“V3 交易” 中的 “V3” 指的是一种新的、非共识层面的 nVersion
数值 3, 矿工和节点被期望会 利他主义 地区别对待它,从而防止 V3 交易的输出被某一些方式花费掉。
因此,V3 交易提议有三个部分:
OP_TRUE
或等价脚本的锚点输出,同时,交易池规则允许一个交易包在既创建又花费掉面额低于粉尘门槛的输出时,能够无视粉尘规则。重要的是,这不会让小于粉尘面额的输出进入 UTXO 集,因为它会在同一个区块中创建然后花掉。OP_TRUE
锚点是无法抵抗钉死攻击的;而 V3 花费限制就是为了防止这些攻击。我使用 “非共识限制” 的意思是,实际上的区块有效性规则并没有被改变。相反,仅仅是 Bitcoin Core 将拒绝尝试花费 V3 交易输出的交易 —— 即使它们是有效的,并且对矿工来说有利可图 —— 只要这些交易没有满足特定的要求,其中主要的一条是:花费未确认的 V3 输出的交易的重量必须小于 1000vB,否则就会被拒绝。这种限制阻止了重大的交易钉死攻击,反过来使得锚点输出可以是简单的、任何人都可以花费的 OP_TRUE
。因为花费这个输出的交易无法被钉死,如果第三方尝试以低费率交易花费这个输出,任何人都可以直接用更高费率的交易替换掉第三方的交易。
至少,想法上是这样的。不幸的是,因为 1000vB 的限制依然显著大于标准的承诺交易以及一次性锚点的花费交易,攻击者依然可以对第三方进行便宜而有效的钉死攻击。具体有多便宜、有多高效,取决于交易池内的环境。但一个攻击攻击者只需花费少许资金(甚至不需要花费资金)就可以让受害者为一次强制关闭通道的操作支付大约 1.5 倍的手续费,最高甚至可达约 3.7 倍。
对相同问题的一个显然的解决方案是,直接预签名不同费率的承诺交易。虽然乍看起来这是低效的,但计算机很快,带宽也便宜:secp256k1 曲线上的一次(MuSig2)签名操作的计算时间仅在 $100us$ (100 微秒)级别(一个公钥)2 3,然后生成一个 64 字节的签名。因为交易的变体可以确定性地生成,因此只需要传递用于确定性生成的参数,以及签名本身。并不需要传输完整的交易。而且闪电网络已经在使用这种技术了:交易的确切形式是由闪电网络规范确定的。
那么需要多少个挡位的手续费呢?根据 mempool.space 的数据,在过去的六年中,进入下一个区块所需的手续费率从 $1 sat/vB$ 到 $500 sat/vB$ 不等。
如果我们的手续费范围是从 $a$ 到 $b$,而递进的比例是 $r$,我们很容易算出需要几次递进:
$$b = ar^n \implies N = \frac{ln(\frac{b}{a})}{ln(r)} + 1$$
举个例子,如果你希望覆盖 $10 sat/vB$ 到 $1000 sat/vB$ 的费率,递进的比例是 10 %,那么只需要 50 个挡位,(需要传输的的)总体积是 3200 字节,只需要 $5 ms$(5 毫秒)来签名(单核计算机)。给定闪电通道的非常好的可扩展性,这个开销是合理的。
更新:事实证明,AICNQ 的 Phoenix 钱包最近已经为通道实现了部分的 RBF,在底层的 lightning-kmq 库中。
现有的闪电通道实际上需要 两笔 承诺交易,remote(“对方”)承诺交易和 local(“己方”)承诺交易,因为双方手上的承诺交易有些许区别;上述手续费挡位的计算指的是每一方需要准备的数量,不是总数量。每一笔承诺交易都要由对方签名,这就引入了为不同参与者分配手续费的能力。在绝大部分情况下,让广播承诺交易的一方支付手续费是合理的;因此,每一方都签名交易、给予对方用 自己 的钱来充当手续费的能力,在他们愿意的时候,就可以补上自己的签名 4 并广播交易。
只要手续费是由选择广播承诺交易的一方来支付的,最高手续费率就可以是该方在通道中的全部余额;这样一方也将不能通过广播高手续费的承诺交易来骚扰另一方,因为他们只能广播用 自己 的钱来支付手续费的承诺交易。这种机制有个好处:不会陷入某一方没有足够的手续费的情形中。如果让一笔承诺交易被挖出需要支付己方在该通道中的全部余额,那么己方尝试让这笔交易挖出是没有意义的。可以等待手续费率下降,或者直接放弃通道中的资金。
闪电承诺交易的 HTLC 输出只能被预签名的 HTLC-超时 或者 HTLC-成功 交易花费。之所以需要这些专门的交易,是因为一方可能广播已经撤销的承诺交易然后创建出 HTLC 输出,而我们需要让另一方有机会使用撤销公钥。在锚点通道中,这些 HTLC 是使用 SIGHASH_SINGLE|SIGHASH_ANYONECANPAY
签名的,所以任何人都能够通过添加额外的输入和输出来为这些交易追加手续费。
因为可以加入额外的输入和输出, 所以转发费率门槛 不是 一个问题:我们总是可以添加足够大的 输入/输出 来够上这个门槛。因此使用 RBF 承诺交易的一种办法是直接为每一种手续费挡位预签名(一笔) HTLC 超时/成功 交易,然后继续使用这种 “添加一个输出” 的机制。这会让我们需要为每一个 HTLC 做的工作乘以 $N$,但这里也一样,因为这些交易是确定性地制作出来的,所以很便宜,也很容易验证。
为同一个 HTLC 超时/成功 操作签名多个手续费挡位的交易也是可以做到的。虽然在最糟糕的情况下,总工作量会变成 $N^2$ 级别。但只要我们意识到,通常来说,HTLC 交易通常需要接近于对应那笔承诺交易的手续费,就可以节省下来。注意,这也说明了为什么对于 Phoenix 这样的终端用户钱包来说,手续费挡位是更好的选项 —— 终端用户钱包希望每个用户只有一个 UTXO,所以不会有额外的 UTXO 能够用来为 HTLC 输出追加手续费。
最后,SIGHASH_ANYPREVOUT
可以解决这个 $N^2$ 复杂度问题,因为它可以让任意的 HTLC 交易变体都能花费任何承诺交易的变体。
混合两者的方法也是可行的。让多笔变体交易只负责一部分的手续费范围,然后让一笔携带了锚点输出的变体负责最高的手续费。
RBF 要比锚点输出高效得多,因为它只需要一笔交易。一笔标准的、使用 V3 形式的闪电通道承诺交易是 163vB 5,然后需要一笔体积为 152vB 的一次性输出交易来支付手续费,总计 315vB。
因为使用 RBF 的承诺交易不需要锚点输出,所以体积只有 154vB,而且不需要第二笔交易。V3 交易在体积上 至少 是两倍,因此手续费也将会是两倍。
但 V3 其实比这还要差:V3 交易需要保留额外的、纯粹只是为追加手续费而需保留的输出。管理这些额外的输出对许多闪电节点来说都是困难而且昂贵的,尤其是更小的 节点/钱包。很难确定要为这些输出分配多大比例的资金,而且,显然,需要消耗区块空间来创建它们。
绝大多数对比特币的究极扩展容量的分析都忽略了这一点:使用锚点输出,每一个闪电网络的用户都至少需要创建 两个 UTXO,而不是一个输出,从而将闪电网络可能容纳的全球用户总量砍半。
这个问题的一个特别糟糕的例子可以在面向用户的钱包(比如 Phoenix 7)上看到:Phoenix 希望让一个用户的所有资金都放在一条闪电通道中,而使用通道拼接技术来加入和取出链上资金。
像 RGB 闪电通道和 LND 的 Taproot Assets 这样的协议旨在让非比特币的协议也能利用闪电通道形式的通道,但可能被迫绑定更多的 BTC 以使用 RBF 技术,因为双方尝试交易的资产并不能直接用来支付手续费。对于大体量的通道来说,这可能不是一个问题,因为手续费的 BTC 价值相对于通道的价值来说是小的。但更小的通道可能需要使用其它技术。
举个例子,SIGHASH_ANYPREVOUT
让承诺交易可以用 SIGHASH_ANYONECANPAY
来签名,从而可以在有需要的时候通过添加额外的输入来使用 RBF 手续费追加。当然,这可能会引入一些问题,比如说交易钉死攻击。
感谢 Brandon Black 指出这个问题。
因为锚点输出比 RBF 要低效得多 —— 代价大约是 2 倍 —— 它带来了显著的挖矿中心化威胁,因为用户可能尝试使用协议外(out-of-band)的手续费支付手段。问题在于,如果你拥有使用锚点输出的交易,你需要确认它,使用这个机制(锚点输出)来挖出交易,比起让矿工挖出目标交易而不花费锚点输出,要消耗大概是两倍的区块空间。
一些大矿工已经接受了协议外的支付手段,允许你通过直接支付给他们来让你的交易被挖出;仅有 大体量的矿工可以合理地提供这项服务,因为只有大矿工才能足够频繁地找出区块,来让这种协议外的手续费物有所值。去中心化的矿池协议比如 P2Pool 和 Braidpool 完全没有任何希望能够提供这样的服务。
因为使用锚点输出的标准闪电通道需要花费 2 倍的区块空间,一个大矿工可以容易地以一定的折扣(比如 25%)提供协议外的手续费支付,这也实质上给了他们 25% 的溢价(相比于他们的更小的竞争对手)。给定矿池的手续费是高度竞争的,量级在 1% 到 2%,如果能够赚取 25% 的溢价,那会是一种巨大的竞争优势。有了闪电网络,可以自动化、便利地实现这样的服务。
我们不能开发让去中心化挖矿反而处于不利地位的协议。单凭这一点,就是 V3 交易和临时锚点输出不应被实现的好理由。
作为一个社区,我们应该追问,这样一个低效的提议,伴随去挖矿去中心化的巨大潜在伤害,为何走了如此之远。V3 交易的 PR 已经以各种形式存在了超过一年的时间,尚未有一份 BIP 或者任何真正的设计文档。尤其是,作者最近承认 8 他们对标准的闪电承诺交易的体积的认识是显著错误的,由此产生了前面提到的 V3 交易钉死问题。
在考虑这样复杂的协议决策时,我们不应该急着实现代码。我们应该定位真实的应用场景并解决这些场景的效率和隐藏后果。到目前为之,通过 PR 而公开的关于 V3 交易的文档没有提供这一类的分析;就我所知,我是第一个合理地比较了它于其它解决方案的人。
1. [Lightning-dev] 锚点通道的对方锚点是多余的, Peter Todd, lightning-dev 邮件组, 2023-12-13 ↩
2. https://github.com/jonasnick/musig-benchmark ↩
3. https://github.com/tarcieri/rust-secp256k1-ecdsa-bench ↩
4. 记住,在交互式联合签名协议(比如 MuSig2),“联合签名” 仅仅意味着补完签名流程的最后一步。 ↩
5. 假设 1 个 taproot 输出(musig),2 个 taproot 输出,以及 1 个一次性锚点输出 ↩
6. 假设 1 个一次性锚点、一个 taproot 输入,以及 1 个 taproot 输出 ↩
]]>作者:Optech
来源:https://bitcoinops.org/en/newsletters/2023/12/20/
本译本由 “Optech 中文翻译小组(BitcoinOptechCN)” 提供。
本期为 Optech Newsletter 的特刊, 总结了 2023 年全年比特币开发中的重要进展。 它接续了我们的年度总结传统:2018、2019、2020、2021,以及 2022。
Anthony Towns 推出 了 Bitcoin Inquisition,一款复刻了 Bitcoin Core 的软件,设计目标是在默认的 signet 上运行,以测试人们提出的软分叉和其它重大的协议变更。截至今年年底,Bitcoin Inquisition 已经支持了多项提案:SIGHASH_ANYPREVOUT、OP_CHECKTEMPLATEVERIFY 以及基本的 “临时锚点”;并且,旨在为 OP_CAT、OP_VAULT
以及限制 64 字节交易而增加支持的 PR 已经提交到其代码库。
ZmnSCPxj 以及 Jesse Posner 提出 了 “swap-in-potentiam”,这是一种可以开启闪电通道的非交互式方法,用于解决经常离线的钱包(比如移动设备上的钱包)所面临的挑战。客户端可以在自己离线时以链上交易的形式接收资金。一旦这样的交易得到足够多的确认,当客户端再次回到线上时,就可以立即安全地跟预先选定的对等节点开启一条通道 —— 无需信任该对等节点。在这个提案提出的几个月内,至少一个热门的闪电钱包已经用上了这个想法的实现。
一种用于导出和导入钱包内部标签的标准格式被分配了标识符BIP329。这个标准不仅让用户更容易备份无法通过 BIP32 种子 来复原的重要钱包数据,还大大方便了复制交易元数据到非钱包程序(比如记账软件)中。截至今年年底,已经有多款钱包实现了 BIP329 导出功能。
二月份开始出现贯穿了全年的关于 Ordinals 和 “铭文(Inscriptions)” 的讨论,这两个相关的协议旨在为交易输出附加含义和数据。Andrew Poelstra 总结了许多协议开发者的立场:“没有一种实用的办法能够在阻止人们在交易的见证字段存储任意数据的同时,不吸引他们尝试更加糟糕的行为 以及/或者 打破合理的用途。”鉴于铭文所用的方法允许存储大体积的数据,Christopher Allen 建议提高 Bitcoin Core 对以 OP_RETURN
为前缀的输出可存储的数据体积限制(83 字节);同年晚些时候,Peter Todd 也支持这样的提议。
BitcoinSearch.xyz 在今年初启动了,它为比特币的技术文档和讨论提供了一个搜索引擎。截至今年年底,该网站也提供了一个聊天界面,以及最新讨论的总结。
Core Lightning 添加了对 “对等节点存储备份” 的实验性支持;该特性允许一个节点为其对等节点存储一个小体积的加密备份文件。如果一个对等节点需要重新建立连接,可能已经弄丢了数据,那么可以请求这个备份文件。请求节点可以使用从自己的钱包种子词中派生的密钥来解密这个文件,然后用其中的内容来复原其所有通道的最新状态。
Joost Jager 提议给闪电通道增加一种 “高可用性” 标记,以允许一条通道表示自己可以提供可靠的支付转发服务。Christian Decker 指出创建声誉系统会面临的挑战,比如同一个节点可能不会被同一个支付发起方频繁使用。此前出现的一种替代性方法也被提及:“带有复原机制的超额支付”(曾用名 “回旋镖”、“可退款的超额支付”),其中支付会被分切并通过多条路径来发送,从而减少对高可用通道的依赖。
来自去年出版的一篇论文的想法成了 2023 年缓解 “闪电通道阻塞” 问题的焦点。二月份,Carla Kirk-Cohen 以及论文的联合作者 Clara Shikhelman 开始征求对实现论文的其中一个想法 “HTLC 背书” 时应使用的建议参数的反馈。四月份,他们为自己的测试计划提出了一份规范草案。六月份,论文中的想法和提议在闪电网络开发者会议上得到了讨论,从而引起了邮件组内关于一种替代性方法的讨论;该替代方法希望让攻击者和诚实用户所支付的费用都能反映节点运营者提供服务所需付出的真实成本,如此一来,能够凭借向诚实用户提供服务来赚取合理回报的节点,也将能在攻击者尝试使用其服务时持续获得合理的回报。八月份,公开消息称 Eclair、Core Lightning 和 LND 的开发者都实现了部分的 HTLC 背书协议,以开始收集与之相关的数据。
Russell O’Connor 和 Andrew Poelstra 为备份和存储 BIP32 助记词提出了一个新的 BIP,称为 “codex32”。类似于 SLIP39,codex32 允许使用 Shamir 私钥分割方案将一个种子词分成多个碎片,并允许配置复原种子词的阈值要求。攻击者所得的碎片数量若小于阈值,就无法得出完整的种子词。但是,codex32 不像其它复原码使用一张词表,它使用跟 bech32 地址相同的字母表。与现有的方案相比,codex32 的主要优势在于,用户可以使用纸、笔、指令和剪纸,手动运行所有操作,包括生成一个编码过的助记词(使用骰子)、用一个校验和来保护它、创建带有校验和的碎片、验证校验和以及复原种子词。这使得用户可以周期性地验证单个碎片的完整性,而无需依赖于可信任的计算设备。
三月份,化名开发者 John Law 出版了一篇论文,介绍了一种可以仅使用单笔链上交易就为多个用户创建一种层级式通道的方法。这种设计允许即使在部分参与者离线时,在线的用户也能花费自己的资金,而这在当前的闪电通道中是做不到的。这种优化使得总是在线的用户能够更高效地使用自己的资金,从而也可能降低其它闪电网络用户的成本。本提议基于 Law 的可调整惩罚协议,而该协议从 2022 年提出以来,还未看到任何公开的软件开发。
- - -
James O’Beirne 在一月提出了一种新的 OP_VAULT
操作码,随后在二月为之编写了一份 BIP 草案以及提交到 Bitcoin Inquisition 的实现。几周后,Gregory Sanders 提出了 OP_VAULT
的一种替代性设计。
“默克尔化一切(MATT)” 提议首次亮相是在去年,而在今年再次有了动静。Salvatore Ingala 展示了 MATT 如何能够提供拟议的 OP_VAULT
操作码的绝大部分特性。Johan Torås Halseth 进一步展示来自 MATT 提议的其中一个操作码如何能够复制拟议的 OP_CHECKTEMPLATEVERIFY
操作码的关键功能,虽然 MATT 的做法在空间效率上稍差。Halseth 还利用这个机会,向读者介绍了一种他开发的工具 “tapsim”,可以用来调试比特币交易和 tapscripts。
六月份,Robin Linus 描述了用户如何能够在当下用时间锁锁定资金、在侧链上长期使用这些资金、允许侧链上的资金接收者在未来从比特币网络上取出资金 —— 但仅在比特币用户最终决定以某种方式改变共识规则时才可以。这将允许愿意承担财务风险的用户立即开始在自己的资金上尝试想要的新共识规则,同时提供一种办法,让这些资金日后能够回到比特币主链上。
八月份,Brandon Black 提出 了一种 OP_TXHASH
,结合 OP_CHECKSIGFROMSTACK,就能提供 OP_CHECKTEMPLATEVERIFY(CTV)和 SIGHASH_ANYPREVOUT(APO)的绝大部分特性,而且链上开销相比这两个单独的提议不会高太多。
九月份,John Law 提议使用限制条款来加强闪电网络的可扩展性。他使用了一种类似于 “通道工厂” 和拟议的 Ark 协议的构造,以在链下同时为大量的通道注入资金;这些资金在一定时间之后,可以被工厂的注资者收回,因此用户需要提前通过闪电网络取出自己的资金。这种模式允许资金在多个工厂之间移动,而无需用户的交互,从而降低工厂生命末期的链上阻塞风险和交易费。Anthony Towns 提出了对 “强制过期洪水” 问题的担忧,也就是一个大体量的用户的故障可能迫使许多时间敏感型链上交易同时出现。Law 回应称他正在研究一种解决方案,以在高手续费时期推迟过期。
十月份,Steven Roost 为新的 OP_TXHASH 操作码发布了一份 BIP 草稿。这个操作码的想法,前面已经提到过,但这份草稿是其第一份规范。除了准确地描述这个操作码会如何工作,该规范也探究了其一些缺点,例如,每次涉及这个操作码时,全节点可能都需要哈希高达几 MB 的数据。该 BIP 草案也包含了该操作码的一个实现样本。
同样在十月,Rusty Russell 研究了如何以对比特币脚本语言的尽可能小的变更实现通用的限制条款,而 Ethan Heilman 发布了一份 BIP 草稿,提议加入一种 OP_CAT 操作码,以拼接堆栈中的两个元素。对这两个主题的讨论都持续到了十一月。
在年底,Johan Torås Halseth 也提出,限制条款类型的软分叉可以聚合多个 HTLCs 成单个输出;如果某一方知道所有的原像,则可以一次性花费;如果 TA 只知道部分原像,则只能取走一部分的资金,而剩余资金将由另一方收回。这将具有更高的链上空间效率,而且更难对其执行特性类型的通道阻塞攻击。
- - -
Sergi Delgado Segura 提议了一种问责机制,以应对瞭望塔未能对其应该检测到的协议违规行为做出回应的情况。例如,Alice 为瞭望塔提供了用于检测和响应旧 LN 通道状态确认的数据;后来,该状态得到了确认,但瞭望塔未能作出反应。Alice 希望能够通过公开证明瞭望塔未能适当回应来追究瞭望塔运营商的责任。Delgado 建议使用一种基于密码学累加器的机制,瞭望塔可以用其来创建承诺,如果发生违规,用户随后可以用它来产生问责证明。
路径盲化在三年前被首次提出,在今年四月被添加到 LN 规范当中。它允许收款方向付款方提供特定转发节点的标识符和从该节点到付款方自己节点的洋葱加密路径。付款方将支付和加密的路径信息转发到选定的转发节点;转发节点为下一跳解密信息;下一跳为其下一跳解密;依此类推,直到支付到达收款方节点,付款方和任何转发节点都永远不会(确定地)知道哪个节点属于收款方。它显著提高了使用 LN 收钱的隐私性。
BIP327 在四月份被分配给了用于创建无脚本多重签名的 MuSig2 协议。该协议将在一年内在多个程序和系统中实施,其中包括 LND 的 signrpc RPC、 闪电实验室的 Loop 服务、BitGo 的多重签名服务、LND 的试验性简单 taproot 通道和一个BIP 草案 以用来扩展 PSBTs。
Maxim Orlovsky 在四月宣布了 RGB v0.10 版本,这是 RGB 协议的一个新版本,允许使用链外定义和客户端验证的合约创建和转移代币(以及其他一些东西)。与典型交易相比,合约状态的改变(例如转账)与链上交易相关联,这种方式不需要额外的区块空间,且能够完全保护合约状态(包括合约的存在)的所有信息对第三方完全保密。今年晚些时候,Taproot Assets 协议(部分源自 RGB)发布了旨在成为 BIP 的规范。
四月,针对已提议的通道拼接协议,出现了大量的讨论,该协议允许节点在向通道添加资金或从通道中取出资金的同时继续使用该通道。这对于在允许即时链上支付的同时保持通道中的资金尤其有用,以允许钱包用户界面向用户显示可用于链上或链下支付的统一余额。到今年年底,Core Lightning 和 Eclair 都将支持通道拼接。
五月发布了一套针对闪电网络服务商(LSP)的规范草案。这些标准使客户端可以轻松连接多个 LSP,这将防止供应商锁定并改善隐私性。首个发布的规范描述了一个 API,以允许客户端从 LSP 购买一个通道,实现了类似于流动性广告(liquidity ads)的功能。第二个规范描述了一个用于建立和管理即时(JIT)通道的API。
Dan Gould 在今年的大部分时间致力于增强 payjoin 协议,这是一种提高隐私的技术,使第三方很难可靠地将交易中的输入和输出与发送方或接收方相关联。在二月,他提议了一个无服务器版 payjoin 协议,即使接收方不在公共网络接口上运行始终在线的 HTTPS 服务器,也可以使用。在五月,他讨论了使用 payjoin 的几种高级应用,包括支付合并(payment cut-through)的变体;例如,Alice 不是向 Bob 付款,而是向 Bob 的供应商 (Carol) 付款,从而减少他欠她的债务(或预付将来的预期账单)——这节省了区块空间,并进一步改善了标准的 payjoin 的隐私。在八月,他发布了一个无服务器版 BIP 草案,即不要求发送方和接收方同时在线(尽管他们每个人在交易发起后至少需要上线一次才能广播交易)。一整年里,他是 payjoin 开发工具包 (PDK)以及为 Bitcoin Core 创建 payjoin 提供插件的 payjoin-cli 项目的头号贡献者。
Burak Keceli 提出了一种新的 joinpool 式协议,名为 Ark。基于此协议,比特币所有者可以选择在特定时间段内使用对手方作为所有交易的共同签名人。所有者可以在时间锁过期后在链上取回比特币,或者在时间锁过期前即时且免信任地将比特币在链下转移给对手方。该协议为所有者向交易对手方提供了免信任的单跳、单向原子转账协议,可用于混币、进行内部转账和支付 LN 发票等用途。与 LN 相比,链上足迹较大且运营商需要在热钱包中持有大量资金引起了些许担忧。然而,几位开发人员对该提议协议及其为用户提供简单且免信任的体验的潜力仍然保持热情。
Josie Baker 和 Ruben Somsen 发布了一份用于静默支付的 BIP 草案,这是一种可复用的支付代码,每次使用时都会产生一个唯一的链上地址,从而防止输出关联(地址复用)。输出关联可以显著降低用户(包括未直接参与交易的用户)的隐私。该草案详细介绍了该提案的好处、它的权衡以及软件如何有效地使用它。此外,在 Bitcoin Core PR 审核俱乐部会议期间还讨论了正在进行的为 Bitcoin Core 实施静默支付的工作。
- - -
Optech 今年报告了三个重大安全漏洞:
bx
中的 Milk Sad 漏洞:用于创建钱包的命令中存在广泛未记录的熵缺失,最终导致多个钱包中的大量比特币被盗。- - -
验证闪电签名者(VLS)项目于七月发布了他们的第一个 beta 版本。该项目允许将 LN 节点与控制其资金的密钥分离。使用 VLS 运行的 LN 节点将签名请求路由到远程签名设备,而不是本地密钥。Beta 版支持 CLN 和 LDK、layer-1 和 layer-2 验证规则、备份和恢复功能,并提供了参考实现。
七月举行的 LN 开发者峰会讨论了各种话题,包括可靠的主链交易确认数量、taproot 和 MuSig2 通道、 更新的通道公告、PTLC 和冗余超额支付、缓解通道阻塞攻击提议、简化承诺和规范流程。同时期关于 LN 的其他讨论包括整理LN 规范以删除未使用的传统功能和简化的闪电通道关闭协议。
八月,LN 规范中添加并支持了洋葱消息。洋葱消息允许通过网络发送单向消息。与支付(HTLC)一样,消息使用洋葱加密,这样每个转发节点只知道它从哪个对等节点接收消息和哪个对等节点应该下一个接收消息。消息净荷也是加密的,所以只有最终接收者可以读取它。洋葱消息使用了四月添加到 LN 规范中的盲化路径,洋葱消息本身也在拟议的要约(offers)协议中使用。
Thomas Voegtlin 提出了一项协议,该协议允许对提供给用户过期备份状态的服务商进行惩罚。该服务涉及一个简单的机制,即用户 Alice 使用版本号和签名将备份的数据给 Bob。Bob 添加一个随机数,并使用带时间戳的签名对完整的数据承诺。如果 Bob 提供过时数据,Alice 可以生成一个欺诈证明,显示 Bob 曾经签署过一个更高版本号。这个机制并不是比特币专用的,但是结合特定的比特币操作码可以使其在链上使用。在闪电网络(LN)通道中,如果 Bob 提供了一个过期的备份,Alice 可以要求索取所有通道资金,从而降低了 Bob 欺骗 Alice 并窃取她余额的风险。该提议引发了重要的讨论。Peter Todd 指出了其超越了 LN 的通用性,并提出一个更简单的没有欺诈证明的机制,而 Ghost43 强调了在与匿名对等节点打交道时这些证明的重要性。
LND 添加了对“简单 taproot 通道”的实验性支持,允许 LN 的注资和承诺交易使用P2TR,并在双方合作时支持 MuSig2 风格的无脚本多重签名。这在通道合作关闭时减少了交易重量并提高了隐私。LND 继续专门使用 HTLC,允许在 taproot 通道中开始的支付可以继续通过其他不支持 taproot 通道的 LN 节点进行转发。
今年九月,Tom Briar 发布了一份压缩比特币交易的规范和实现草案。该提案解决了压缩比特币交易中均匀分布数据的难题,用变长整数来表示整数,使用区块高度和位置来引用交易而不是其输出点 txid,并省略了 P2WPKH 交易中的公钥。虽然压缩格式节省了空间,但与处理常规序列化的交易相比,将其转换回可用格式需要更多的 CPU、内存和 I/O,在卫星广播或隐秘传输等情况下,这是可以接受的权衡。
- - -
scanblocks
RPC,简化了 bitcoin-cli
的使用,为 finalizepsbt
RPC 添加了 miniscript 支持,使用 blocksonly
配置选项减少了默认内存的使用,并在启用致密区块过滤器时加快了钱包重新扫描的速度。- - -
Gijs van Dam 发布了有关 支付拆分与切换 (PSS)的研究成果和代码。他的代码允许节点将正在接收的付款分成多个部分,这些部分在到达最终收款人之前可以通过不同的路径。 例如,Alice 支付给 Bob 款项的一部分可以通过 Carol 来路由。这种技术可以显著抑制余额发现攻击,即攻击者通过探测通道余额来追踪整个网络的支付情况。Van Dam 的研究表明,使用 PSS,攻击者获得的信息减少了 62%。此外,PSS 要约提高了闪电网络的吞吐量,并有助于减轻通道阻塞攻击。
开发者 ZmnSCPxj 提出了一个名为 sidepools 的概念,旨在加强闪电网络的流动性管理。Sidepools 涉及多个转发节点将资金存入类似于闪电网络通道的多方链外状态合约。这样,资金就可以在参与者之间进行链外再分配。例如,如果 Alice、Bob 和 Carol 开始时各有 1 BTC,则可以更新状态,使 Alice 拥有 2 BTC,Bob 拥有 0 BTC,Carol 拥有 1 BTC。参与者仍将使用和广告常规的闪电网络通道,如果这些通道失衡,则可以通过状态合约内的链外点对点互换进行重新平衡。这种方法对参与者来说是私密的,需要的链上空间更少,而且很可能省去链外再平衡费用,从而提高转发节点的收入潜力和闪电网络支付的可靠性。不过,它需要多方状态合约,而这在生产中尚未经过测试。ZmnSCPxj 建议可以建在对称闪电网络(LN-Symmetry)或双工支付通道上,这两者各有利弊。
assumeUTXO 项目的第一阶段已于十月份完成,其中包含使用假定有效的链状态快照和在后台进行一次完整验证同步所需的所有剩余更改。它使 UTXO 快照可以通过 RPC 加载。虽然经验少的用户还无法直接使用该功能集,但此次合并标志着多年努力的最终成果。该项目于 2018 年提出,2019 年正式确定,将显著改善新的全节点首次进入网络时的用户体验。
Bitcoin Core 项目在十月份还完成了对 BIP324 中规定的第 2 版加密 P2P 传输的支持。该功能目前默认为禁用,但可以使用 -v2transport
选项启用。加密传输可以防止被动观察者(如互联网服务提供商)直接确定节点向其对等节点转发了哪些交易,从而有助于提高比特币用户的隐私保护。还可以使用加密传输通过比较会话标识符来检测主动的中间观察者。未来,增加其他功能可能会使轻量级客户端更方便地通过 P2P 加密连接安全地连接到受信任节点。
在这一年中,Bitcoin Core 对 Miniscript 描述符的支持又有了一些改进。二月,为 P2WSH 输出脚本创建 miniscript 描述符的能力得以实现。十月,对 miniscript 的支持进行了更新以支持 taproot ,包括 tapscript 的 miniscript 描述符。
Robin Linus 和 Lukas George 在今年五月描述了一种在比特币中使用零知识有效性证明进行状态压缩的方法。这极大地减少了客户端为无需信任地验证系统中未来的操作而需要下载的状态量,例如,只需相对较少的有效性证明即可启动一个新的全节点,而无需验证区块链上每一笔已确认交易。今年十月,Robin Linus 介绍了 BitVM,这是一种能够以任意程序的成功执行为条件支付比特币的方法,而不需要改变比特币的共识。BitVM 需要大量的链外数据交换,但只需要单个链上交易来达成一致。如果有争议,也只需要少量的链上交易。BitVM 使复杂的无需信任的合约成为可能,即使在有敌手的场景下也是如此。这引起了一些开发者的关注。
随着盲化路径和洋葱信息的规范定稿及其在多个流行的闪电网络节点中的实施,依赖于它们的要约协议在今年的开发取得了重大进展。要约允许接收者的钱包生成简短的 要约,并分享给花费者的钱包。花费者的钱包可以使用要约通过闪电网络协议来联系接收者的钱包,要求开具特定发票,然后再以常规方式支付。这样就可以创建可重复使用的要约,每个要约可以产生不同的发票,发票可以在支付前几秒更新当前信息(如汇率),要约可以由同一个钱包支付多次(如订阅)等等。在这一年中,Core Lightning 和 Eclair 更新了现有的实验性要约实现,LDK 也增加了对要约的支持。此外,十一月还讨论了创建一个兼容要约的更新版本的 Lightning Addresses 的问题。
十一月还有流动性广告规范的更新,允许节点向新建立的双向注资通道宣布提供部分资金以获得手续费的意愿,从而使请求的节点能够迅速开始接收闪电网络的付款。这些更新大部分都是次要的,但关于流动性广告创建的通道是否应包含时间锁的讨论一直持续到十二月。时间锁可以向买方提供激励性保证,即他们将实际收到所支付的流动性,但时间锁也可能被恶意或不考虑他人的买方用来对提供者锁定额外资本。
- - -
在 Optech 的第六年,我们出版了 51 期周报,发表了一个关于交易池政策 10 篇的系列文章,并为我们的主题索引增加了 15 个新页面。今年,Optech 总共发表了超过 86,000 个英文单词的比特币软件研究和开发文章,大致相当于一本 250 页的书。
此外,今年的每期周报都配有一集播客,音频总时长超过 50 小时,文字稿超过 45 万字。比特币的许多顶级贡献者都是节目嘉宾。2023 年共有 62 位不同的特别来宾——其中有些人不止参加一集:
Optech 还发表了两篇来自业界的田野调查报告:一篇来自 BitGo 公司的 Brandon Black,介绍了如何实施 MuSig2 以降低费用成本和提高隐私保护;另一篇来自 Wizardsardine 公司的 Antoine Poinsot,介绍了如何使用 miniscript 构建软件。
- - -
几位 Bitcoin Core 开发者开始研究一种新的聚类交易池的设计,以简化交易池操作,同时保持必要的交易排序,即父交易必须先于子交易确认。交易被分组到群组中,然后被分割成按费率排序的交易块,同时会确保高费率的交易块首先被确认。这样就可以通过简单地选择交易池中费率最高的交易块来创建块模板,并通过放弃费率最低的交易块来剔除交易。这解决了一些现有令人不满意的行为(例如,矿工可能会因为次优的剔除策略而损失手续费收入),并可能在未来改善交易池管理和交易中继的其他方面。他们讨论的归档内容已于十二月初发布。
十二月还有一个新工具公布,用于启动大量比特币节点。节点之间(通常在测试网络上)会有一组定义好的连接。这可以用来测试用少量节点难以复现的行为,或者在公共网络上测试会造成问题的行为,比如已知的攻击和 gossip 信息的传播。使用该工具的一个公开示例是测量 Bitcoin Core 在一项提议变更前后的内存消耗。
我们感谢所有上面提到的比特币贡献者及其他很多贡献者。他们的工作同样重要。感谢他们为比特币的发展又贡献了不可思议的一年。Optech 周报将于 1 月 3 日恢复正常的每周三发布安排。
(完)
]]>作者:jamesob
来源:https://github.com/jamesob/assumeutxo-docs/tree/2019-04-proposal/proposal
本文档撰写于 2019 年 4 月。
Bitcoin Core #27596 完成了 assumeutxo 项目的第一阶段,包括了同时使用一个假定有效(assumedvalid)的链状态快照和在后台进行完整验证同步所必需的所有余下变更。它通过 RPC(
loadtxoutset
)使 UTXO 快照可加载,并在链参数(chainparams)中添加了assumeutxo
参数。尽管该功能集在激活前在主链上都不可用,但这个合并标志着到达了多年努力的顶点。该项目在 2018 年提出并在 2019 年正式确定,将显著改善首次接入网络的新的全节点的用户体验。后续合并包括 Bitcoin Core #28590、#28562和#28589。
本文为 assumeutxo
提议了一种实现和部署计划。AssumeUTXO 使用序列化的 UTXO 集,以持续地减少冷启动一个可用的比特币节点所需的时间,同时保证安全性上的变更是可以接受的。
这个特性可以分成两步(或者三步)来部署:
assumeutxo
哈希值将确定哪个快照被认为是有效的。如果有什么部分难以理解,请继续阅读以了解更多的细节。
现在,你可以帮助审核这个提议以及代码草稿。
资源
已经熟悉这些想法了?
如果你一直在跟踪相关的讨论、已经理解了 assumeutxo
的基本原理,你可以直接跳到下文的 “安全性” 章节。
致谢
我希望对帮助这个提议的人们表示感谢,虽然他们不应该为我在这份文档以及相关的代码中可能犯的任何愚蠢的错误负责。他们是:
Suhas Daftuar、Pieter Wuille、Greg Maxwell、Matt Corallo、Alex Morcos、Dave Harding、AJ Towns、Sjors Provoost、Marco Falke、Russ Yanofsky,以及 Jim Posen。
“UTXO 快照” 是在区块链的某个特定高度上的 “未花费的交易输出(UTXO)” 集合的一个序列化版本。这些序列化的 UTXO 会被打包并带上一些元数据,例如:
等等。你可以在 assumeutxo pull request 中看到完整的数据结构(形式可能会变化)。
assumeutxo
?它是嵌入软代码中的一片数据,承诺了一个序列化的 UTXO 集的哈希值,这个 UTXO 集被认为是在特定区块高度下形成的真实集合。这个承诺的最终形式还有待讨论,因为生成它要耗费大量的计算,而且它的结构也会影响我们如何存储序列化 UTXO 集合以及在对等节点间传输它。但当前来看,它就是使用现有的 GetUTXOStats()
功能所生成的 UTXO 集内容的一个基于 SHA-256 的哈希值。
我们可以使用 UTXO 快照以及 assumeutxo
承诺,从而极大地减少冷启动一个可用的比特币节点所需的时间,并且其安全模式是可以接受的。
当前,初始化区块下载程序所需花费的时间会随着区块链历史的增加而线性增加。不管在哪里,一个新安装的 bitcoind 总要花费至少 4 个小时甚至几天来走完流程,具体时间取决于你的硬件和网络带宽。这个过程会劝退想要运行全节点的用户,促使他们转向安全模式更差的客户端。
在加载一个快照的时候,这个快照会解序列化、变成一个完整的链状态(chainsate)数据结构,该数据结构包含了对区块链(chainActive
)的一种表示以及 UTXO 集(既存放在硬盘中,也缓存在内存中)。加载快照所得的 chainstate 会跟加载快照之前的原 chainstate 同时存在。在接受一个被加载的快照之前,必须先从对等节点网络中检索得到一条区块头链,并且该区块头链应该包含该快照所压缩的最新区块(其 “基础”)的区块哈希值。
加载好快照之后,快照 chainstate 会执行初始化区块下载,从快照状态开始,一直下载的网络的链顶端。然后,系统将允许操作,就像 IBD 已经完成了一样,并且这个假定有效的快照 chainstate 会被视为 chainActive
/pcoinsTip
/等等。
在快照 chainstate 追上网络的链顶端之后,原来的 chainstate 会在后台恢复因为加载快照而中断的区块初始化下载。这个 “后台验证” 流程跟激活的(快照)chainstate 是异步的,所以系统可以如常服务(比如,运行钱包操作)。这个后台验证的目的是检索所有的区块文件并完全验证整条链,直到快照的起点(即其 “基础” 高度)。
在后台验证完成之前,我们会拒绝加载任何 bestblock
标记低于快照基础高度的钱包,因为我们没有执行重新扫描所需的区块数据。
一旦后台验证完成,我们就可以丢弃原先的 chainstate,因为快照 chainstate 已经被证明是完全有效的了。如果因为某些原因,后台验证产生了一个 UTXO 集哈希值,跟快照所声称的不同,我们会给出明确的警告并使程序停止运行。
assumeutxo
是否改变了安全模型?如果我们讨论的是用户是否需要在辨别什么是 有效的/无效的 比特币状态时信任某一些开发者(及其程度),那么:不,使用 assumeutxo 并不会在实质上改变现在比特币 “信任开发者” 的程度。
现在,比特币软件也带有硬编码的 assumevalid 数值。这个数值定义了,如果你在区块头链上看到了某个区块,那么该区块以前的区块的签名检查就可以全都跳过,作为一种性能优化措施。
使用 “assumevalid” 模式,你假设平等审核你所用软件的人可以运行一个全节点、验证所有区块直至(包括)某一个区块。并不需要进一步信任(假设)平等审核你所用软件的人懂得这个软件的编程语言、懂得共识规则;实质上,可以说需要的信任更少了,因为 没有人 完全懂得 C++ 这样的复杂语言,而且 没有人 可能懂得公式规则的每一种可能的细微差异 —— 然而,几乎每个懂技术的人都可以使用
-noassumevalid
启动一个节点,并等待几个小时,然后检查bitcoin-cli getblock $assume_valid_hash
所返回的"confirmations(确认次数)"
字段不为-1
。David Harding
Assumeutxo 的想法也是类似的,而且会以一种更严格的方式来指定(不能通过 CLI 来指定)。硬编码的 assumeutxo 数值将以跟 assumevalid 数值完全相同的方式得到提议和审核(通过 pull request),而且会在提议和合并之间留出足够长的时间,从而让任何感兴趣的人可以自己重新生成相同的数值(即验证)。
这个数值跟开发者所提议的其它任何代码变更没有区别;实际上,在另一个更加模糊的代码部分中偷藏某种实现了扭曲的有效性的反向逻辑,会容易得多,比如, CCoinsViewDB
可以修改成在某些特殊条件下总是承认一笔钱的存在性,或者网络也可以修改成仅跟特定的对等节点沟通。
assumevalid/assumeutxo 数值的无遮掩特性,实际上让用户的参与更加直接,因为如何审核这种优化是显而易见的。
同样值得指出的是,存在 assumevalid/utxo 数值并不阻止其它链被认为有效,它只是说 “软件以前验证过 这一条链”。
是的,两者有一个实践上的安全性差异。当前,如果我想要欺骗别人,让他们认为我拥有某一笔钱(而诚实的网络会看穿我),我需要这样所:
-assumevalid=
参数的 bitcoind,这显然要付出许多精力,因为攻击者需要生产一条具备有效 PoW 的区块链。
然而,在 assumeutxo 中,只要我让用户接受一个恶意的 assumeutxo
成熟数值,大部分工作就完成了。修改和序列化错误的 UTXO 快照是非常简单的 —— 并不需要工作量证明。
没错,确实如此。
因此,assumeutxo 数值需要嵌进源代码中,并且我们不会开发一种通过运用命令行来指定 assumeutxo
数值的机制了;这在实践中的风险太高了。如果用户希望指定另一个数值(不推荐这样做),他们可以修改源代码然后重新编译。
也许未来可以这么做,但不是现在。在我们获得使用 UTXO 快照的实际经验之前,我们不知道正确的承诺结构是什么样的。改变共识是一个非常昂贵的过程,而且除非我们绝对确定希望承诺它,不然就不该尝试。
沿着这条路一直走下去,也许我们会在共识关键的地方引入这样的承诺,但目前,我们应该将 assumeutxo 设计成不需要这样的假设也是安全的。
assumeutxo
数值,我们还会运行别的验证吗?是的。在 UTXO 快照加载完成、同步到网络的链顶端之后,我们会在后台运用单独的数据结构(即一个单独的 chainstate)启动一次初始化区块下载。这个后台的 IBD 会一直下载和验证所有区块,直至被快照假设有效的最后一个区块(即该快照的 “基础”)。
一旦后台的 IBD 完成,我们就验证完了在我们加载快照时候假定有效的所有历史区块。然后,我们就可以丢弃后台验证后得到的 chainstate
数据了。
没错,因为我们要维护两个完全独立的 UTXO 集,以支持后台 IBD(这会同时使用快照假定有效的链条以及快照基础高度后的链条),我们必须拥有一个额外的 CCoinsView*
层级。这意味着要在硬盘上临时保存一个额外的 chainsate
(leveldb)目录,而且需要根据每个 -dbcache
分割内存、分配给需要放在内存内的 UTXO 缓存。
我不认为这是一个很大的问题,因为这基本上意味着(在当前来说)额外的 3.2GB 的存储空间。我们可以按照大约 80/20 的比例给 后台 IBD 所用的 CCoinsViewCache
vs. 假定有效的 chainActive
分配指定的 -dbcache
内存,因为较大的 dacache 仅在 IBD 期间才提供了重大的性能好处。
如果我们引入 assumeutxo 和快照而不在后台运行 IBD,那么容易想象几乎每个建立节点的人都会使用 UTXO 快照(因为它比传统的 IBD 快得多)、假定某一段区块链是有效的,然后将自身作为剪枝节点(pruned node)呈现给网络。在极端情况下,这将导致缺乏节点为网络提供历史区块。这显然不是我们想要的,因此,将后台 IBD 作为默认设置似乎是有意义的。
硬件受限的用户无疑可以在剪枝模式下使用 assumeutxo 。
Assumeutxo 是一项性能优化。如果我们移除 IBD 模式、换成它,就改变了比特币的安全模型。在未来,我们可能会分割区块下载与 连接/验证 程序,从而 assumeutxo 节点依然可以为对等节点提供区块,而无需花费计算资源来执行 IBD 式的验证。
当前,计算特定高度的 UTXO 集的哈希值可以使用 gettxoutsetinfo
RPC 命令来做到(即 GetUTXOStats()
)。这会花掉几分钟来计算;如果你要对某一个具体的高度计算这个值,你需要调用 invalidateblock
来回滚到那个高度;计算完成后,你可以使用 reconsiderblock
快速切换回来。显然,这会打断正常的操作。
这不方便,但我们可以修改 gettxoutsetinfo
的行为,使之可以接受一个高度作为参数,然后至少抽象掉通过 invalidateblock/reconsiderblock
手动回滚和切换的过程。
未来,可以想象我们可以使用一个节点本地的滚动 UTXO 集哈希值以让特定高度的 UTXO 哈希值随时可用。但是,滚动的 UTXO 集哈希值跟 assuemeutxo 承诺方案是不兼容的,因为后者涉及到分块快照(下文有述),并且因此最终的 assumeutxo 值可能必须是 (rolling_set_hash, split_snapshot_chunks_merkle_root)
这样的元组。
最终,用户将通过对等节点网络来获得 UTXO 快照。但在这个状态实现之前,用户需要从其他人或者 CDN 那里说得 UTXO 快照,然后用硬编码的 assumeutxo 哈希值来验证它(见下文的 “如何分步部署?”)。
因为快照的体积是非常大的,而且因为恶意的对等节点可能会谎称自己提供的一个大文件是一个有效的快照,所以我们需要分块存储及传输。应该做到容易验证每一块。
粗糙地说,分切的一种办法是将快照平均分成 n 块。我们可以用每一块的哈希值构造一棵默克尔树,然后将树的根植作为嵌入源代码的 assumeutxo
承诺数值。每一个对等节点都会选择随机的数值对 k 求模(这里 k <= n),以确定自己要复杂存储哪一部分数据块。在初始化的时候,想要获得快照的对等节点将需要找出 k 个不同的对等节点,每一个对等节点都提供快照数据的独特一 “段”,从而获得全部 n 个数据块。
没错。这种方法的问题在于:
相应的,我们可以使用纠删编码,生成 n+m 个数据块(其中 m 就是额外编码的数据块的数量),并让冷启动的节点才对等节点处检索任意 n + 𝛼 个不同数据块;这里的 𝛼 的大小取决于我们所用的具体的编码方案。每一个节点都依然只存储和提供快照全部分块的子集。
为了保证运行旧版本软件的邻居,一个节点只存储最新的快照是不够的。具体要存储多少套可以讨论,但我认为,为两套历史快照以及最新的一套快照存储一些数据,是合理的。
对于每一套快照,每个节点都只需存储一部分数据(大概是 1/8 左右),取决于我们为纠删编码方案选择的具体参数。
一个参考,当前的一套 UTXO 快照大概是 3.2 GB。如果我们不做任何改进、快照保持这个规模,那么预计一个节点要存储 1.2GB(= 1/8 * 3 套快照 * 3.2GB/每套快照)的快照数据(假定需要 8 个对等节点来使用快照冷启动,并且每个节点都存储 3 套快照)。
好问题,我还没有想得很清楚。可以假设,我们可以(比如说)让代码每 6 个月生成一套快照。为了能在运行时生成快照而不影响正常过的操作(比如新区块的响应),我们可能必须重构 “从状态到磁盘” 的刷新,使之变成异步的。
Sjors 指出,我们可以基于区块高度,周期性地生成快照,这可能会派上传输给对等节点以外的用场:
如果按固定的区块间隔生成,这些快照可以【马上】派上用场,即便(其承诺)还没有进入新发行的软件。它可以用于本地备份,从区块以及 chainstate 的数据中恢复。每个节点都可以用简单的 text 文件来存储哈希值。对于剪枝节点来说,重新编制索引可以通过回顾快照来实现,而不必再一路回溯到创世区块。类似地,这在一个节点需要撤销剪枝以扫描旧钱包的使用也是有用的。
这是一个吸引人的想法,但在实践中有一些缺点。
首先,实现 assumeutxo 方法比起在 Bitcoin Core 中开发一个 SPV 模式,所需的代码要少得多。当前,Bitcoin Core 并没有任何作为 SPV 客户端的代码,而且许多子系统也都假设了一个 chainstate 的持久存在(即一个 CChain
区块链对象以及对 UTXO 集的视图)。
也许有些让人意外,但 assumeutxo 方法让我们可以复用大量 Bitcoin Core 中已经存在的代码。它的实现相当于一系列的重构(即使没有 assumeutxo,本来也是要做的,会让编写测试变得更加容易),再加上少量新的小块逻辑,以处理在初始化和网络通信期间多个 chainstate 并存的问题。
需要使用大量的新代码(比如 Reed-Solomon 纠删编码)来分割快照以存储和对等传输,但这类代码可以容易地放在系统的外围,而构筑 SPV 模式的代码则必须对软件的 “核心” 作不计其数的修改。
更多新代码就意味着更大的工程量和审核工作,而且最终会有更多风险。而且,使用 SPV 模式,也意味着在完整的链被下载和验证之前,节点无法对新进入的区块作全面的验证。
通过 Bitcoin Core 软件以外的方式来分发这类数据在实践中有一系列的缺点。如果个人和团体被鼓励下载一定程度上未经软件本身验证的 chainstate 数据,会引入许多安全风险。用户将不仅需要信任 bitcoind,还需要信任数据的提供方。除了软件本身以外,这些数据的提供方也需要挑剔。
此外,现有的方案,比如 FastSync,使用 PGP 来见证他们所提供的数据的有效性。这个签名常常被忽略,而且说服用户可靠地验证它们不是个容易的事。
Assumeutxo 或者类似方案的用户应该是 默认安全的,而且最终来说,用户不应该需要采取额外的步骤,才能从这样的优化中收益(同时保持安全性)。
dumptxoutset
和 loadtxoutset
实现 UTXO 快照的创建和使用,以及一个硬编码的 assumeutxo
哈希值。(见 PR)在这里审核代码。因为变更的规模较大,一部分代码可能会被分割,大家会感谢你的投入。
如果其它更节约空间的 UTXO 集表示方式可用,它们会跟 assumeutxo 相得益彰。容易想象 assumeutxo 的值变成 utreexo 森林的默克尔根值,或者一个累加器数值。UTXO 快照将缩减成几千字节,而不是几 GB。后台 IBD 也依然会很有用,因为我们将依然希望在后台执行完全验证。
-dbcache
)呢?来自 Sjors:
我认为,应该分配绝大部分内存给快照状态,使之追上链顶端。因为快照可能代表的是 6 个月以前的链状态。一旦追上,就刷新并分配绝大部分内存给后台验证(从创世区块同步到快照基础区块)。如果节点在此期间重启,那就同步区块头,如果落后超过 24 小时,那就再次分配大部分资源以追上链顶端,否则分配大部分资源以追上快照基础区块。
在最初的版本中,至少我们需要确保两个 UTXO 集对内存的用量不超过
-dbcache + -maxpool
。
此时,实现草稿会在后台验证与顶端追赶之间按 70/30 的比例分配内存。
将快照加载作为启动参数,的确可以简化许多东西(比如链状态数据结构的管理),但如果我们最终要走向可以通过 P2P 网络来传输快照,我们就需要准备好让快照能在启动之后加载的逻辑。我认为,我们应该在将这个特性作为默认配置之前确保充分测试,因此,loadtxoutset
似乎是在 RPC 阶段应该使用的方法。
(完)
]]>作者:Brandon Black
来源:https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2023-August/021907.html
我正在为这份提议(下附全文)征求反馈。该提议以综合的方式实现了 BIP118 和 BIP119 的支持者们希望的功能,同时保持了跟它俩任意一个提议同样的低风险。创建这个提议的部分目的跟我提出 BIP PR#1472 以及我的 “限制条款比较表” 的目的相似,是帮助进一步讨论这些提议,并让它们的相似性和区别变得更加明显。
对我来说,显然的是,两种提议的支持者之间的分歧很大程度上都来自于对提议的特性缺乏全面的理解。我们希望这个工作可以帮助厘清我们对它们两者(单个也好,一起也好)的思考,并对启用更好的闪电网络、保险柜以及其它类似的使用比特币的神奇方式的道路达成共识。
本提议是对 bip119 和 bip118 的替代;它提供了这两种提议的功能,而且在大部分场景下没有额外的开销;同时,它能辩驳对上述两种提议的某些反对意见,从而开启清晰的升级的路径。
实质上,本提议是 Russel O’Connor 的 OP_TXHASH+OP_CSFS 提议的一个初级的、受限的版本。
我们定义了三种新的专属于 Tapscript 的操作码,将 OP_SUCCESS80
、OP_SUCCESS187
和 OP_SUCCESS188
分别替换成 OP_TXHASH
、OP_CHECKSIGFROMSTACK
和 OP_CHECKSIGFROMSTACKVERIFY
。
对 OP_TXHASH
,我们定义了 5 种哈希交易的方法,取决于从堆栈中弹出的、最小化编码的数字参数:
参数 | 行为 |
---|---|
0 | 如同 bip119 |
1 | 如同使用 sighash 标签 0x41 的 bip118 |
2 | 如同使用 sighash 标签 0xc1 的 bip118 |
3 | 如同使用 sighash 标签 0x43 的 bip118 |
4 | 如同使用 sighash 标签 0xc3 的 bip118 |
OP_CHECKSIGFROMSTACK(VERIFY)
的定义类似于它在 Elements 项目中的实现,但在内部求 SHA256 哈希值时并不包含数据参数。因为 bip340 定义了对任意长度的消息的签名,而且OP_CHECKSIGFROMSTACK(VERIFY)
仅可用于 Tapscript,内部哈希是没有必要的限制。用户可能希望使用预先哈希的数值(就像在本提议中一样),或者可以在脚本中使用非 SHA256 的哈希值。
人们花了很多笔墨在讨论比特币脚本编程的未来上。两个最接近于取得共识的提议是 bip118 和 bip119,但两个协议的支持者在两者的相对优先级和对方的优点上存在分歧。这里,我们会简要列举这样的一些反对意见,并展示本提议如何避免这些批评。我们不会讨论对引入限制条款或者 “递归限制条款” 的担忧。
OP_CHECKSIGFROMSTACK
结合的时候)OP_NOPx
的语义(尽管 OP_SUCCESSx
已经可用)OP_EQUALVERIFY
中)OP_SUCCESSx
升级语义OP_TXHASH
在验证 Tapscript 的时候,OP_SUCCESS80
的行为修改如下:
OP_0
、OP_1
或者 OP_2
,那么立即成功退出 2哈希模式
哈希模式
为 0:哈希模式
为 1:sighash_type=0x41
的定义哈希模式
为 2:sighash_type=0xc1
的定义哈希模式
为 3:sighash_type=0x43
的定义哈希模式
为 4:sighash_type=0xc3
的定义OP_CHECKSIGFROMSTACK(VERIFY)
在验证 Tapscript 时,OP_SUCCESS187
和 OP_SUCCESS188
的行为修改如下:
公钥
、消息
和 签名
结果
等于根据 bip340 验证 签名
对 消息
和 公钥
有效的结果结果
为 true
,则推入堆栈,否则将 false
推入堆栈OP_CHECKSIGFROMSTACKVERIFY
检查值
检查值
不是 true
,则执行失败SIGHASH_ANYPREVOUT
:
<64 字节的签名>||<1 字节的 sighash 类型> <33 字节的公钥> OP_CHECKSIG(VERIFY)加上 PUSH 操作符: 64+1+1 + 33+1 + 1 = 101 witness bytes (25.25vBytes)
本提议:
<64 字节的签名> <1 字节的参数> OP_TXHASH <32 字节的公钥> OP_CHECKSIGFROMSTACK(VERIFY)加上 PUSH 操作符: 64+1 + 1 + 1 + 32+1 + 1 = 101 witness bytes (25.25vBytes)
都放在 Tapscript 内
单独使用 OP_CHECKTEMPLATEVERIFY
:
<32 字节的哈希值> OP_CHECKTEMPLATEVERIFY OP_DROP OP_TRUE加上 PUSH 操作符: 32+1 + 1 + 1 + 1 = 36 witness bytes (9vBytes)
OP_CHECKTEMPLATEVERIFY
加上后续的检查:
<32 字节的哈希值> OP_CHECKTEMPLATEVERIFY OP_DROP <...>加上 PUSH 操作符: 32+1 + 1 + 1 = 35 witness bytes (8.75vBytes)
本提议:
<1 字节的参数> OP_TXHASH <32 字节的哈希值> OP_EQUAL(VERIFY)加上 PUSH 操作符: 1 + 1 + 32+1 + 1 = 36 witness bytes (9 vBytes)
与非 Tapscript 的 CTV 比较
裸的 OP_CHECKTEMPLATEVERIFY
:
锁定: <32 字节的哈希值> OP_CHECKTEMPLATEVERIFY OP_DROP OP_TRUE加上 PUSH 操作符: 32+1 + 1 + 1 + 1 = 36 bytes (36vBytes)解锁: <empty>总计: 36 + 0 = 36vBytes
Witness v0 CTV:
锁定: OP_0 <32 字节的哈希值>加上 PUSH 操作符: 1 + 32+1 = 34 bytes (34 vBytes)解锁:<36 字节的见证脚本>加上大小: 36+1 = 37 witness bytes (9.25vBytes)总计: 34 + 9.25 = 43.25vBytes
本提议:
锁定: OP_1 <32 字节的哈希值>加上 PUSH 操作符: 1 + 32+1 = 34 bytes (34 vBytes)解锁:<36 字节的叶子脚本> <33 字节的控制块>加上大小: 36+1 + 33+1 = 71 witness bytes (17.75vBytes)总计: 34 + 17.75 = 51.75vBytes
相比于裸 CTV,本提议要贵 15.75 个虚拟字节。如果 CTV 的用例得到流行,可能需要为裸 CTV 专门安排一个升级,无论是像 bip119 定义的那样作为一个单独的见证版本,还是别的方法。
考虑 bip119 提到的风险,手续费敏感的用户可以在使用 OP_TXHASH
和 OP_EQUAL(VERIFY)
时加入 OP_RIPEMD160
以节约 2.75 虚拟字节,这将让本提议相比裸 CTV 的额外开销降低到 13 虚拟字节。
可能由于我们缺乏想象力,我们看不到它在不签名任何输入以及任何输出(或者只签名一个输入脚本而不签名任何输出)时候的用途。
可以,它完全兼容 N-Symmetry。它使用了另一种脚本,但跟出于这个目的而使用 bip118 时的体积和行为都是相同的。
可以,它完全兼容 PTLC。它使用了另一种脚本,但跟出于这个目的而使用 bip118 时的体积和行为都是相同的。
可以,也完全兼容 OP_VAULT。它使用了另一种脚本,但跟出于这个目的而使用 bip119 时的体积和行为都是相同的。
字段模式 | CTV(0) | APO/ALL(1) | APOAS/ALL(2) | APO/SINGLE(3) | APOAS/SINGLE(4) |
---|---|---|---|---|---|
哈希类型 | x | x | x | x | |
交易版本号/锁定时间 | x | x | x | x | x |
本输入 UTXO | |||||
其它输入 UTXOs | |||||
本脚本公钥/面额 | x | x | |||
其它脚本公钥/面额 | |||||
本脚本签名 | x | ||||
其它脚本签名 | x | ||||
输入的个数 | x | ||||
本输入的 sequence | x | x | x | x | x |
其它输入的 sequences | x | ||||
花费类型/annex | x | x | x | x | |
叶子脚本 | x | x | |||
公钥版本/codesep pos | x | x | x | x | |
tap 默克尔路径 | |||||
对应输出的脚本/面额 | x | x | x | x | x |
其它输出的脚本/面额 | x | x | x | ||
输出的个数 | x |
1. 在堆栈元素的长度无效时执行失败,可以保证攻击者无法跳过验证。 ↩ ↩
2. 我们在遇到未定义的 TXHASH 模式或公钥长度时执行成功,是为了允许未来的插件。 ↩ ↩
3. 我们在公钥长度检查 之后 遇到无效的签名长度时失败,就可以让 64 字节的签名仅能使用 32 字节的公钥,但允许未来的公钥类型也能使用另一种长度的签名。 ↩
4. 迄今为止,就我们所知,除了在 bip118 中定义的 sighash 类型, 其它类型在本提议中没有用处,因为其它类型要么会使操作降格为 OP_CHECKSIG(VERIFY)
,要么会创建无限的哈希循环。 ↩
作者:Brandon Black
来源:https://bitcoinmagazine.com/technical/the-witness-discount-why-some-bytes-are-cheaper-than-others
今年我们看到人们对比特币区块的有限空间的需求大大上升,这导致交易为了得到区块确认必须付出更高的手续费。大部分需求都来自于要用交易来揭晓 “铭文”。这些铭文的内容会作为比特币交易的 “witness(见证)” 字段 1 内的数据的一部分而得到公开。而见证字段内的数据 1 相比于交易其它字段的数据,会折扣成 25% 的体积。为什么要给这些铭文一个折扣呢?我们应该通过软分叉,将见证数据的折扣取消掉码?
为什么一些字节要比另一些字节更便宜?
一般来说,货币,尤其是比特币,是基于对人类的激励而运行的。比特币通过使用原生的比特币来给矿工支付将特定交易纳入他们所构造的区块内的费用,使得矿工和交易者的激励得以兼容。但并不因此就能让节点运营者的激励跟矿工和交易者的激励一致,也不能因此让交易的发送者和接收者的激励一致。
迄今为之,在比特币的激励兼容上,有过三种重大的优化措施:
交易者希望确认大量的交易,矿工也希望收取大量的交易费;但节点运营者不得不转发、验证并存储所有的交易数据,而且他们并不能像矿工那样因此得到补偿。在比特币历史的早期,中本聪通过加入一个固定的区块大小限制(由节点强制执行)来解决这个问题。这个限制是,一个区块最多只能使用 1 百万字节,从而为节点在一定时间段内需要下载和验证的数据设置上限。那时候,中本聪写道:“我们可以在日后需要的时候逐步改变它”。但后来,在提到一个旨在提高这个限制的补丁时,他指出:“不要使用这个补丁,这会让你跟整个网络不兼容”,意思是提高区块大小限制是一种硬分叉变更,比起软分叉需要更多的协调工作。后来的日子里,比特币小心翼翼地避免采用这样不兼容的硬分叉变更,因此也意味着 1 百万字节的区块体积限制得到了保留。
因为作为资金的比特币是靠锁定脚本来保护的,因此总是由可能采用更高级的脚本(比如多签名)来锁定资金。在最初的设计中,一笔比特币交易的发送者需要将接收者的完整锁定脚本放在交易中,并因此需要为在区块内包含这个锁定脚本而支付手续费。开发者们意识到,随着手续费率的提升,发送者可能会犹豫是否要给这种更大体积的锁定脚本的用户支付,因为手续费会更高。这些复杂的锁定脚本也给编码地址、通过低带宽的机制(例如 QR 码)来分享带来了挑战。
为解决这个问题,P2SH 作为一个软分叉,被加入比特币。在这个分叉的规则中,不再需要将接收者的完整锁定脚本放在交易的输出信息中,而只需要放入该脚本的哈希值。当接收者最终要花费这个输出时,他们自己会在花费交易中曝光完整的脚本;脚本的真实性由锁定资金的哈希值来验证。在这一变更之后,任意大小的 “赎回脚本” (即实际编码花费条件的完整脚本)都可以用一段固定长度的锁定脚本来代表,因此发送者也不再需要(也不再能够)根据花费条件的复杂性来区分接收者。
节点对比特币交易执行的最根本的验证是:这些交易尝试花费的比特币,是真实存在的。为此,每个节点都要保存一套包含了每一笔可花费的比特币(未花费的交易输出,UTXO)的索引。这个索引越大,运行一个节点、验证未来交易的开销 2 就越大。因此,提高这套索引的体积的交易(输出多于输入的交易)的长期代价,会比相同体积但减少了这套索引的体积的交易更大。
大部分比特币解锁脚本中的绝大部分都是密码学签名。这些签名的体积大概是对应公钥的两倍,这就意味着,解锁脚本的体积(即使没有 P2SH 也)会比锁定脚本更大。
花费 UTXO vs. 创建 UTXO 的显著更高的成本,在节点运营者和交易者之间产生了一种激励冲突。交易者被反激励花费自己的小额 UTXO(尤其在手续费率高企的时候),相反,花费更大的 UTXO 并创建更多小面额的找零 UTXO 对他们来说更便宜。与此同时,节点运营者要为这些数量日渐积累的小额 UTXO 支付代价:所有交易的验证成本会升高。
虽然乍看起来有点奇怪,但是,验证区块链历史上的每一笔交易所花费的每一个 UTXO 的锁定脚本都被解锁脚本满足了,是显然不那么根本的。因此,运行 Bitcoin Core 26.x 默认配置的节点将不再为区块高度 804000 (预计是 2023 年 8 月 19 日)以前的交易验证完整的锁定脚本执行。
(译者注:作者在此处值的可能是 “Assume Valid” 模式,这是一种用于初始化区块同步的模式,节点可以设置不验证一定区块高度以前的交易,从而加速同步。这是可以手动配置的。)
上述所有,意味着区块链的不同部分给比特币节点带来了不同的开销。用来确认每一笔交易的实质的数据必须得到每一个从创世区块 3 以来同步了所有数据的节点的验证,而长期来看,交易的输出会比交易的输入带来更大的开销(尤其在这些输出会长期存在的情况下),而且除了最近的交易,许多的见证数据甚至不会被检查。
“隔离见证(SegWit)” 是比特币迄今为止最大胆的变更。这个变更的最大动机是解决比特币中长期存在 TXID 4 熔融性 5 问题。为了修复熔融性问题,解锁脚本会放在新设立的交易 “见证” 字段。通过不再让授权数据(通常可以被第三方改变而不影响交易的实质)参与 TXID 的计算,依赖于不可改变的 TXID 的协议(例如闪电通道)成为可能。
既然授权数据不再放置在原本的交易结构中,它也就不再参与区块大小的统计。因此,我们需要一种新的限制。那时候人们讨论过许多限制被隔离的见证数据的方法:单独限制见证数据的体积 6、小于 1 百万字节的整体限制 7,或是加权的综合限制。最终,人们选择了加权的综合限制,被隔离的见证数据字节的权重是 1 单位,而交易数据字节的权重是 4 单位,而整个区块的字节不得超过 4 百万单位。在用于手续费计算时,每个权重单位会被视作 1/4 的虚拟字节(vByte)。
为什么要使用这样的权重?我们来看看使用和不使用隔离见证的交易输入和输出的开销:
资金的类型 8 | 作为输出的字节量 | 作为输入的字节量 | 见证字节量 | 作为输入的 vBytes |
---|---|---|---|---|
P2PK | 44 | 112 | 0 | 112 |
裸 2-of-3 多签名 | 106 | 186 | 0 | 186 |
P2PKH | 34 | 146 | 0 | 146 |
P2SH(2-of-3 多签名) | 32 | 293 | 0 | 293 |
P2WPKH | 31 | 41 | 107 | 67.75 |
P2WSH(2-of-3 多签名) | 43 | 41 | 252 | 104 |
关于这张表格,第一个要指出的事情是,这些见证脚本类型(P2WPKH、P2WSH)作为输入和输出时的字节量(两种都按完整的虚拟字节量)相差无几。一个见证脚本的花费者,在计算交易的体积时,授权她花费的数据会按字节量的 1/4 计算虚拟字节,这些数据中的绝大部分都只会在交易发生的一段时间内被验证(过了这段时间就不再被验证),并且这些数据都不会给 UTXO 索引造成持续的负担。还有一个值得指出的事情是,与升级之前相比,更安全的 2-of-3 多签名与单签名的使用成本差距,从 147 虚拟字节降低到了 36.25 虚拟字节。
就像我一开始说得,比特币运行在人类激励之上,而且,我们可以看到这些年来比特币上的变更如何优化了使用网络的各方之间的激励兼容。
Taproot 自身 “只是” 另一种使用隔离见证来锁定比特币的方法。它并没有显著改变这些激励。伴随 Taproot 的一项变更是移除了对脚本大小的明确限制。这是为了降低给比特币脚本设计分析工具的复杂性,也是对不同类型的数据的相对成本的承认。移除这个限制让铭文的实现比 Taproot 激活之前更简单,但并没有显著改变网络的激励结构。
现在,进入问题的关键。铭文是在见证数据中揭晓的,所以铭文数据的每个字节只会被统计为 1/4 虚拟字节。这是对见证数据折扣的滥用码?真相是,铭文这样的数据,对网络中的节点来说是验证起来最便宜的数据之一。铭文所用的脚本结构会显式地跳过对铭文数据的执行,所以对它运行的唯一验证就是一次哈希检查(保证实际揭晓的铭文正是计划揭晓的铭文)。这部分数据只会被哈希一次,然后就不会再被节点回顾。所以计算成本是非常低的(比起相同体积的多签名脚本的验证,低了几个数量级)。
但是铭文推高了手续费、挤出了其他用户。
没错!使用当前可用于跟比特币网络互动的软件,铭文兄弟比其他不得不使用其它类型交易的人更有经济激励让铭文上链。
这凸显了增加比特币交易的经济密度的机制的价值。闪电网络通过让几百笔、几千笔甚至几百万笔交易可以被打包成一笔链上交易,向这个目标迈出了一大步。一笔链上交易的每一个字节的经济密度越大,意味着这些经济活动付出的手续费越低。随着比特币交易的经济密度提升,区块空间的其它用法已经被淘汰,也必将继续被淘汰 9。
值得指出的是,如果 链下的多签名协议(比如 MuSig2 和 FROST,还有适配器签名)变得普遍,削减甚至消除见证数据折扣 可能 是有意义的。这些协议可以让原本需要耗费大量空间的花费条件只需使用一个签名。也就是说,它结合 Taproot 的高效密钥路径花费,可以让使用几乎任意复杂条件的输入都只需占用 105 字节。
应对铭文所导致的高手续费的措施,跟比特币历史上出现过的其它让人担心天要塌下来的情形一样:耐心地开发、更加耐心地开发。我们还可以做很多事情来提高比特币交易的经济密度,从开发更好的闪电钱包,到 Ark、谨慎日志合约,等等。(过早地)移除见证数据的折扣、取消 taproot,或者其它类似的具有反作用的措施,都只会减少当前比特币交易的经济密度,并且使情况进一步恶化。
保持谦虚,存好你的聪,然后努力开发。
1. 这个被比特币世界采用的术语 “见证(witness)” 实际上来自密码学,它指的是高效地验证一个密码学陈述所需的数据。BIP141 将它定义为 “检查交易的有效性所需的、但对确定交易的实质无用的数据”。密码学家的灵感可能来自于制造业的 “witness” 标记,它被用来高效地验证各组件的公差。 ↩
2. “Utreexo” 项目希望能让部分比特币节点不再需要这样做。Utrexxo 让这些节点可以高效地累加 UTXO 包含根,然后在这些 UTXO 被花费时通过接收它们的包含路径来验证它们。如果这变成了使用比特币的通行办法,它就将更多 UTXO 的负担聪节点转移到了这些 UTXO 的持有者手上。 ↩
3. “ZeroSync” 希望帮助一些节点在某些环境下改变这一点。 ↩
4. 交易 ID:前隔离见证格式的交易连续运行两次 SHA256 的哈希值的倒序字节。 ↩
5. 多笔使用相同输入和输出的有效交易却具有不同的交易 ID,原因可能是它们的签名方式不同,或是签名被第三方改动了。 ↩
6. 可以是任意数值,不会引发硬分叉,因为旧的节点将不知道隔离的见证数据。 ↩
7. 1 百万,或者更少,从而保持与旧节点的兼容,而不会引发 硬分叉。 ↩
8. 假设使用压缩公钥以及 71 字节的 低-R/S DER 格式 ECDSA 签名。 ↩
]]>作者:Kevin Loaec
本文为 Kevin Loaec 在 Advancing Bitcoin 2023 大会上的演讲,由参与 BTC transcript Review 项目的 pgsdesign 将录音转写成文字稿。
我的演讲围绕比特币中的时间锁。我会先非常简略地介绍一下比特币中的不同类型的时间锁。此外,我们还会非常简略地讲讲我们今天在比特币中使用时间锁的理由。然后,是过去几个月间发生的非常令人激动的新东西,它会完全改变我们使用时间锁的用法。最后,我希望我能说服你:在未来的比特币上,时间锁会无处不在。这就是我的演讲的大纲。
首先,我们快速回顾一下时间锁的历史。我想,你们中的大部分人应该都已经听过它了,所以我只准备快速回顾一下它是什么、它是如何工作的。
最早在比特币上使用的一种时间锁是 nLocktime
。它工作的方式使之得名 “绝对时间锁”。从一开始,比特币交易就有这个 nLocktime
字段,而你在这个字段中填入的数值将是你的交易可以开始在网络中传播的最小区块高度。这曾经只是一项转发策略,而不是一个共识规则,因此,并不是非常智能。这就意味着,你可以用交易来阻止你自己花费某一笔资金,或者说,要到某个时间点,你的花费才能添加到区块链上。但如果一个矿工提前得到了它,那依然能提前让它进入区块链。这也是为什么,人们很快改变了它,使之成为一个共识规则。所以现在,如果一个区块包含了一笔交易、其 nLocktime
字段的数值是大于该区块的高度的,那么这就是一个无效的区块。
这很酷,但是还不够,因为这是交易层面的,意思是,只要你拥有私钥,你依然可以签名另一笔交易、不使用这个时间锁。所以,你可以绕过你自己,或者说,从交易中移除时间锁。
有趣的地方在于,在你想要强制执行一些条件时,比特币脚本就能派上用场。你可以用一个操作码来执行。这就是 CLTV
,CheckLockTimeVerify 。当你在自己的脚本中加入这个操作码时,基本上,它会强制要求花费这个 UTXO 的交易的 nLocktime
字段的数值大于等于你在脚本中设定的数值。这就意味着 —— 在收款时,我会给你一个比特币地址,你大概不会知道它对应的脚本是什么,而我创建的脚本中可能包含了一个 OP_CLTV,这将阻止我在一定的区块高度(比如 80 0000)之前花费你给我的资金。我们现在还没到 80 0000 区块高度,对吧?这就是说,在你给我支付的时候,我是不能立即花费这笔钱的,要等到区块高度达到 80 0000 之后才行。
我们也有一些稍微不同的机制(或者说专门添加了这样的机制),以允许使用实际时间而非区块高度,因为,这对于某一些应用场景或者某一些人群来说更为实用。所以,具体取决于你在 nLocktime
字段中填入的数值,它可以被解释日历上的一个日期。这是用 Unix 时间来表达的。因此,你不是非得设置区块高度,你可以单纯设置成:这些资金在 2130 年之前是无法动用的。当然,实际上,你不会设置这么大的数值。所以,就假设是 2030 吧,你会把自己的资金一直锁定,到 2030 年。
然后,我们加入了相对时间锁。CLTV 大概是 2015 年加入的,而 2016 年加入了等价的相对时间锁。相对时间锁不会设置一个具体的时间点或者区块高度,而是从一个 UTXO 被创建的时间点开始计时。也就是说,当你的收款交易得到区块确认的时候,它就开始计时。你可以设置区块的数量 —— 必须经过多少区块,这个输出才能被花费。也有等价的时间形式。以 512 秒为一个单位,你可以用递增的数值来设置你的资金需要经过多少秒(而不是多少个区块)才能花费。这就是 CLTV 的对等操作码,叫做 CSV
。CSV 同样只是一个操作码,用来强制执行交易层面的相对时间锁。只是从技术上来说,有一点点不同。nLocktime
是交易层面的,而(用于表达相对时间锁的) sequence
是输入层面的。这不是很重要。而且 sequence
字段也被用来做别的事情。我们不会在这里全面讲解。你只要知道就行。我们在这里只讲关于时间锁的部分。
好了,这就是非常、非常简单的介绍。你可能已经听过这些东西了。我猜我这里的数字都是对的,但我也不是非常确定。如果我哪里说错了,不要客气,请纠正我。不过我觉得我应该不会差得太远。
不过,的确有一些关于时间锁的 FUD(害怕、犹豫和怀疑)。我的意思是,一直都有这样一种 FUD:你不该使用时间锁,因为你会让自己的钱永远锁死。没错,事实上确实是可以做到的,如果你做了非常愚蠢的事情的话,但总会有一些相对安全的用法,对不对?假设你实使用 OP_CSV,你可以锁定自己的资金的时间是有一个上限的。如果你以区块数量为单位,那么大概是一年多的时间,455 天。如果你用秒数为单位,那么最长只能锁定 388 天。所以,如果你使用的是 CSV ,即使你犯了非常严重的错误,也没什么大问题。比起你使用 nLocktime、让自己的钱锁定 9500 年以上,那是好得多了。在你做这些事的时候,你可能并不清楚它的后果,但你知道了,就没问题了。我的意思是,用什么方式来触发它并不重要,它总是有限制的。如果是 5 亿及以上的数值,它会被解释成 Unix 时间;而如果是小于 5 亿的数值,则会解释成区块数量。这就是为什么绝对时间锁可以如此之长,因为我们还接近 5 亿的区块高度。而在 sequence 中,会稍微复杂一些。因为不同的比特有不同的含义。如果设置了比特 22,那么时间锁的长度就是这个数值乘以 512 秒。如果没有设置这个比特,那么数值就会被解释成区块的数量。实际时间长度差不多,但你也知道,512 秒并不等于 10 分钟。
那么,它有什么用呢?现在我们都把时间锁用在什么地方?用得并不多。我的意思,大概率你不会在你个人的资金保管上使用时间锁。确实有一些应用场景,但非常少。人们可能想要使用时间锁的一个理由是想阻止自己花钱。为什么要阻止自己花钱呢?
我不知道。也许你有点强迫症,只想长时间守住你的钱。因此你要自己创建一个地址,对吧?(你当然要自己动手,没有人能强迫你使用时间锁)。因此你创建一个地址,可以保证你存入其中的钱在 10 年内无法移动。这就是一种强制储蓄策略。只要你想,你是可以做到的。这在德国很有用,现在在葡萄牙也一样,税法的要求是如果你存币时间超过一年,就不用收资本利得税;但如果你在一年内转手,你就要支付资本利得税。那么,与其一笔笔记录你买入和卖出的比特币,为什么不直接创建能够阻止你在一年内花费的地址呢,对吧。这样一来,你就知道,你永远不必为自己的比特币纳税。这可能是一种应用场景。
现在真正使用时间锁的地方基本上是多方合约的逃生舱(escape hatch)。还有一些用法是,比如说,来自 Blockstream 的 Green 钱包。在你使用 Green 钱包的时候,你可以使用 Blockstream,或者说,将他们当成双因子中的第二个。这样一来,基本上,你发起的每一笔交易都是得到 Blockstream 的联合签名的。当然,如果他们的服务器离线了、宕机了或他们弄丢私钥了,你也不希望自己的钱被永远锁定。所以它带有一个时间锁。你不会注意到它。但它可以帮助你在一段时间后独自花费你的资金。我感觉 Green 钱包应该一直是这样的。
然后,还有更高级的东西。比如你可能听说过的,我们开发的 Revolt。Revolt 基本上就是加入时间锁,或者说与时间锁和预先签名的交易玩耍,从而能够在比特币交易上强制执行花费策略。这在一些,比如说,多方参与的场景中是有用的。比如说,你是一个团体的医院,你想要委托一些资金给其他人,让他们可以发起支付。你希望确保他们一天之内无法花费超过 10 K 的资金。你可以运用比特币上的智能合约,使用时间锁和预先签名的交易,来强制执行这种构造。如果你感兴趣的话,看看 Revolt。
闪电网络也使用时间锁。它使用时间锁来保证你可以从你的通道(多方合约)中退出。如果你的通道对手消失了,你也不希望你的资金被永远算定。所以你们可以使用一笔带有时间锁的逃生交易。时间锁也用于惩罚尝试偷窃你的钱的通道对手。如果你的通道对手尝试用一个旧的状态来关闭通道,时间锁制造了一个时间窗口,允许你先一步惩罚 TA 并拿走你们通道中的资金。这是今天就在用的东西,但非常专门,对吧?
所以,时间锁在普通用户这里并不常用。但是,未来会改变的!…… 我的意思是,现在没有得到广泛使用,主要是由于缺乏工具。
我们没有真正好用的工具。比特币上出现时间锁已经 14 年了。但问题在于,它依然要使用高度定制化的脚本,而大家的想法都是,在制作你的个人比特币钱包时,你不该使用定制化的脚本。你可能会弄丢资金,因为你的钱包软件和其它钱包软件不兼容、因为硬件签名器无法处理你的脚本、等等,对吧?这就是为什么没有人真正探索过如何在日常使用的钱包中使用时间锁。
但是最近,我指的是过去几个月里,输出描述符,以及(具体来说) Miniscript,已经彻底改变了这一切。现在,即使使用复杂的比特币脚本(包括时间锁),我们也可以实现钱包之间的兼容了。Bitcoin Core 也已经在上个 11 月为观察钱包(watch-only)添加了 Miniscript 支持;而且刚刚合并了用于签名的代码。这就意味着,你可以使用 Miniscript,向 Bitcoin Core 导入一个带有时间锁的输出描述符,而且如果这些描述符名下有资金,你是可以花费它的,这非常酷。还有一些代码库可以完成同样的事情:BDK,还有 Rust Miniscript,它们都已经推出了可用的版本,所以你可以试用一下。你可以用它们开发出一款钱包软件,并不需要从头开发所有东西。而且这就不再需要手写比特币脚本了。而且,我们已经有了可以持处理 Miniscript 的签名设备;这是最后一块拼图。
Salvatore 可能在房间里,也可能不在 : ) 我没去听他上午的演讲,可能他也跳过了我的。基本上,Ledger 已经在他们的 Bitcoin app 里支持了 Miniscript。所以,如果你有 Ledger,而且更新了软件,你就拥有了能支持 Miniscript 的 app。它可以理解脚本,也可以使用相同的脚本生成找零地址。它还可以签名交易。非常酷。另一款现在可以处理 Miniscript 的硬件签名器是 Spectre DIY。今年会有更多的硬件钱包集成 Miniscript。非常非常让人兴奋,我也真的相信我们可以改变这个局面。
然后是软件钱包。我们开始看到一些钱包支持 Miniscript。我提到了 Bitcoin Core。然后是我们正在开发的 Liana 钱包。我认为 Sparrow 也能处理 Miniscript,虽然它并没有时间锁功能,但可以理解脚本,也能花费。还有一款钱包叫做 MyCitadel。所以支持 Miniscript 的钱包还真不少,它们之间都是兼容的。这也是非常棒的事情。(有了 Miniscript,)我们讨论的就不是定制化的脚本了。你可以在其中一款钱包上生成钱包,然后导入到其它钱包中花费。这是大变局。所有这些都发生在过去几个月里。
接下来是我的演讲的主体。我准备谈谈更安全的自主保管(safer self-custody)。许多人都谈过自主保管。人们的想法要么是:这实在是太简单了,每个人都能在纸上写下 12 或者 24 个词;要么是另一个极端:人们没法做到安全的自主保管,一定会弄丢自己的钱的。这两者我都不信。我的想法在两者的中间:自主保管既不只是在纸上写下 12 个词,你还要安全地保管这张纸(所以不是那么简单);但也不是不可克服的难,所以解决方案不该是把你所有的钱都交给 Coinbase。
但现在我们可以使用一些很酷的东西了。我准备跟你一起探究一下,或者,至少,给你一些基本的概念:我们可以用这些工作开发出什么样的东西。我们不会讨论构造的细节,比如手动构造交易或其它复杂的东西。我们就讲,比如说,图形用户界面、钱包兼容性、跟 Bitcoin Core 的兼容性,这些。这些是你现在就可以用到的,对吧?我只是尝试让你想象一下,我们现在可以用时间锁做什么样的事。
其中一个我们可以想象的事情是,现在,我们可以制作一种你不需要备份其中一部分(我称为 “主要路径”)(它是不带有时间锁的日常密钥)的钱包。它的日常使用体验跟常规的单公钥钱包没有区别。但它有一个时间锁,比如说是一年的。那么,在一年之后,你的第二个密钥就可以独自花费这笔资金。所以,你不用备份主要路径中的密钥(“主要密钥”)的 12 字词,你要备份的是需要时间锁解锁后才能使用的第二个密钥。为什么要这样做呢?说实在的,理由还真不少。其中一个理由是,人们天生不擅长好好保管东西。你要把自己的 12 词种子词或者 24 词种子词放在哪里呢?不是真的要问你们哈。只是说,你的保管位置可能不是非常安全。其他人是否知道它在那里?其他人能不能拿到它?也许有时候你会离开它、不能亲自看着它(比如来参加大会),对吧?也许有的人就等这个时候动手。要是他们找到了你的种子词,你就完蛋了。你会丢失自己所有的钱。但现在,你有了一种对抗这种风险的办法。要是别人拿到了你记录种子词的纸,他们也花不了你的钱,除非你一直不移动你的钱、长达一年。有趣的地方就在这里,你可以使用 “防拆包(tamper evident bag)” 这样的基本工具(来保存你的种子词)。只要你经常检查,比如说,参加完大会后你回到家里,看看有没有人打开过你的防拆包,你就知道自己的资金安不安全。只要防拆包没有人打开过,你的钱就是安全的。这非常酷。这跟你单单保存种子词在某个地方、祈祷没有人发现它们完全不一样。这是非常基本的用法。我认为这应该从现在开始成为每一款钱包的默认配置。当然,也不是不能备份主要路径。我在这里只是过分了一点。你可以为主要路径制作一个非常非常安全的备份。比如说非常难以发现、非常安全的备份,但你也有更容易访问的、带有时间锁的路径,以备不时之需。对我来说,这是一种非常好的选择,也可能是未来每一种钱包都会提供的默认选项。
你还可以尝试更奇怪的东西。比如,OpenDime 这样的默认不保存私钥的东西。OpenDime 是有意设计成这样的。它就像一个小的设备一样,把钱放进去之后,花费这些钱的唯一方式就是摧毁这个设备。它可以签名一笔交易,或者把私钥给你,就这样。它是一次性的。要是这个设备坏掉了,你的钱就丢了,没有任何办法能复原。所以,你需要完全信任这个设备,在你转移其中的钱之前不会损坏。你完全能够想象一个在(比如说)10 年后,一个 OpenDime 的芯片坏掉的时候,你有一个退出通道,对吧?但你也假设,在这 10 年中,就会有人使用它,所以这并不会创建一种在 OpenDime 之外抽取你的资金的办法。这就像一种复原办法。你也可以看看 HSM(硬件签名模块),它就是一个能够签名交易的机器,你也不能备份这个 HSM 中的私钥,这就是它的设计目的。它被假设会充当一个飞地,是外部不可访问的,对吧?那么,(有了时间锁)你可以设置一个退出通道,在你的 HSM 坏掉或者拒绝签名等等情况下,起作用。在 HSM 无法操作的很长时间之后,你可以用另一种途径来花费资金或者复原资金。
你也可以再次尝试以前的事物,比如 “脑钱包”。脑钱包指的是你单纯把种子词背下来而不留其它备份形式。你不应该这样做(而应该留下备份),因为这会导致你的资金控制权完全依赖于你的记忆。如果你忘记了,钱就完全丢了。但以后就不必有这个担心了,因为现在你可以使用脑钱包,但在你真的忘记了它的时候,你知道还有一个办法可以在几年之后复原你的资金。所以非常酷。使用口令来保护的钱包也是一样的道理。如果你的种子词之上还有一套口令,而你没有备份这个口令,那么你是有风险忘掉它的。那么,为什么你不设置一个在很长时间后可以启用的退出通道,来应对你真的忘记事情的风险呢?
(译者注:此处的 “脑钱包” 的含义是 “单凭人脑来记忆种子词,不留其它形式的备份”;但也有人在另一种含义上使用 “脑钱包” 的概念:“凭借人脑(以及其它工具)来生成熵,进而生成种子词(私钥)”。前者更多是指备份(记忆)种子词的形式,而后者指的是生成种子词的方法。两者不应混淆。)
接下来是另一个想法。你可能才刚刚从单签名钱包换成多签名钱包,但一些人已经讨论了钱包的 “社交恢复” 很久了。我不是很喜欢这个想法,但我可以聊一聊。社交恢复的意思是,假设我把事情搞砸了,我弄丢了自己的私钥,我可以请我的一些朋友来帮我复原我的钱包。他们可能彼此互不认识,但只要我能找到足够多的朋友(或者说找到足够多的密钥),我就可以复原我的资金。现在,在你要这样做的时候,你会面临一种风险:他们认识彼此,因此可以串谋欺骗你、偷走你的钱。而有了时间锁,他们就没法这样做了。只要你没有弄丢你的密钥,你就知道他们绝没有可能串通。所以你晚上睡觉也能安稳。你不用成天疑心,他们真的是我的朋友吗?你可以不必担心。
这也可以是一种服务,不必由你的朋友来提供。你可以获得多个不同公司的服务(如果这样的市场会出现的话)。这也一样,你不用担心,老天爷,FBI 会不会给他们施压、没收我的钱?他们可以叫齐所有的公司并强迫这些公司一起签名。不会的,因为根本做不到,除非时间锁过期。这也减轻了第三方的风险,因为现在第三方不会受到威胁了 —— 威胁他们也没法花费你的千克。这是非常大的不同。在你把一个密钥交给别人的时候,他们就有了保证这个密钥安全的责任,这是有成本的。如果某人在一把密钥里放了 100 万美元甚至 10 亿美元,然后把这个密钥分享给别人,那得到这个密钥的人也会成为目标。所以有一个时间锁也可以降低这个第三方的风险。
你可能也听过 “递减的多签名”。这个概念是说,你可以设置一个非常严格的多签名装置,而不是一个阈值多签名装置。比如说你设置一个 3-of-3 的多签名;然后,在很长时间之后,将可以使用 2-of-3 多签名,从而可以应对丢失一个密钥的情形。那么,为什么不再设置一个更长的时间锁,然后启用 1-of-3 多签名呢?所以,你可以看出,这里不止一个时间锁。你可以想设置多少个,就设置多少个。你可以设置一种时间锁,每过两种就改变条件。这非常酷。而且,这也不再是一种定制化的脚本,也只是你定义自己的花费条件而已。你可以将它导入 Bitcoin Core,也可以导入别的软件钱包,都是一样的。
你还可以尝试一种 稍微不同的东西,就是每隔一段时间就添加额外的密钥。假设你出于某些理由,不想降低阈值,那你可以设置在一段时间之后就可以使用额外的一个密钥。这个密钥可以是律师的,对吧?也许你会添加放在公司的保险柜里的一个密钥。但这些密钥在时间锁过期之前都是无法使用的。所以其安全模型就跟单纯的 3-of-5 不一样:它在一开始是一个 3-of-3 的多签名装置;如果出了问题,就会变成 3-of-4 的多签名;如果还没转移资金,就会变成一个 3-of-5 的多签名。
而且,你还可以从第三方那里获得逃生舱。今天已经有这样的产品了,像 Casa、Unchained、BitGo 这样的公司,都推出了这样的产品。许多公司都提供这样的付费的联合签名服务。在你想花费自己的资金的时候,他们会代替你执行花费策略。但与此同时,他们不想承担这样的风险,或者说你不想承担这样的风险 —— 如果他们消失,或者把事情搞砸,你的钱就全都丢了。现在,应对这种风险的方式是 2-of-3 多签名,你自己保管两个密钥,其中一个是非常安全的备份,你假定自己永远不会使用它;假设联合签名服务商真的出了问题,你也可以放心,因为你还拥有两个密钥,所以可以花费你的资金。这种技术的一个问题是,如果你不设置时间锁,那么任何知道你在使用这个服务的攻击者,只要得到了你的两个密钥,就可以绕开服务商。所以服务并不是自始至终都非用不可。如果我是一个坏人,我知道你在使用这样的服务,那我就可以威胁你,我知道你有两个密钥,虽然我不知道它们在哪儿,但我知道你一定有,我用不着去攻击服务商,因为不是一定要经过他们。而现在,有了时间锁,你就可以真正执行你的花费策略。你可以设置一个严格的 2-of-2 多签名,使我无法绕过签名服务商,而你自己又没有两个密钥。但如果这个签名服务商弄丢了自己的密钥,或者消失了,那么你可以等到时间锁过期,然后启用别的路径。这样的路径可以是递减的,所以你只需一个密钥就可以复原你的资金了。但这也有风险,也许你不想要这个风险,如我们所知,这是人身安全风险。也许你想添加另一个服务商的公钥。所以,我们假设你有一个 Unchained 的公钥,当 Unchained 停止响应的时候,时间锁过期后,你将可以跟 Casa 一起签名。如果 Casa 也不在线,那么,再过一段时间,你可以跟另一个服务商一起签名,等等。所以,你可以制作出可以退出的服务,但又不让他们可以被绕过,对吧。你还跟得上吗?真棒!我希望你可以想出我现在还没想到的东西,对吧。
反向出口是我们发现的一个非常非常酷的东西。所以,哈哈,这是一个剧透警告。我认为未来我们会尝试用这个功能来创收。这跟人们至今还在使用托管商的原因有关。人们担心自己会弄丢自己的密钥。他们不想承担自己保管密钥的责任。但每个人都知道,成为自己的托管商 —— 也就是自主保管 ——— 是非常棒的,对吧?那为什么不双管齐下呢?为什么不让人们能完全保管自己的钱呢?他们将使用自己的密钥,保管自己的钱。但是,当且仅当他们真的把事情搞砸、弄丢密钥、弄丢备份、弄丢一切的时候,他们依然有一个紧急按钮。比如说,我忘记了自己的口令,但在一个时间锁之后,资金会实质上进入托管,因此可以被你信任的人花费。谁能成为这样的你信任的人我不知道,也许是 Casa、Unchained,等等。你可以想象这个第三方并不掌控你的钱,仅在你弄丢了所有东西的时候,才能有所作为。这里的时间锁是相对时间锁。只要你还在使用你的钱包、只要你依然拥有你的密钥,他们是无法动用你的钱的,对吧?仅在你弄丢密钥时,这些服务才能生效。我想说的是,我觉得它会改变一切。我觉得它会改变跟自主保管相关的所有东西。你不必再非此即彼,要么是自主保管然后面临一些风险,要么是使用托管商、让托管商帮你承担所有风险。你可以获得两者的综合,只要你还没倒霉,你就完成控制着自己的资金。
当然,也不一定需要是第三方托管商,你可以制作类似的东西,但让你的母亲或者你的家人来帮助你。作为比特币人,你可能已经做过类似的事情。你曾经帮助他们设置硬件签名器以及所有东西,但再你的内心深处,你觉得他们一定会忘记。所以你帮他们保管了一份助记词。我非常确定,你们中间一定有不少人有这样的助记词副本,你知道自己不该多留一份,但你担心万一。那么,为什么不多给他们一些信任,或者说期望呢?你应该让他们完全保管自己的资金。他们也应该这样做,对吧?仅当他们真的忘记了,并且又过了很长时间,当他们跟你求救的时候,你知道自己还可以帮助他们。我们是能做出这样的东西的。
这是一张截图。【听众提问】是的。它可以是三个不同服务商中的任意两个,所以你不必信任任何一个第三方。它是一个可以组合的东西。【听众提问】没错,没错。你当然可以使用多签名,比如,再前一个逃生舱中。不是只能使用一方。也不必是一个第三方。可以是任何东西。这只是 Liana 钱包当前的模式的一个截图。Liana 是一个处理时间锁、Miniscript、一切、描述符的钱包。它是为时间锁而设计的。所以在你建立钱包的时候,也就是第一次打开它的时候,用户界面会有一些不同。这就是它的样子。一旦你开始使用,它就又像一个常规的钱包了。正常收款、正常花费。这时候你不必考虑时间锁。实际上,我们选择了一个非常复杂的例子。我应该先简化一下。但不管怎么说,假设我希望设置一个使用一个 HSM 的花费策略,也就是用一个机器来强制执行我设定的规则。假设我的每日花费不应超过 1000 欧元,而这个 HSM 可以强制执行这个规则。我不希望自己能绕过这个规则,因为我很懂技术,而且,也可能我想要的新电脑是 2000 欧元,因此我心痒痒;总之,我不希望自己能绕过这个 HSM。因此,我设置一个 2-of-2 多签名,总是要求这个 HSM 跟我一起签名交易。但与此同时,这个 HSM 是一个我没法备份的机器。我又不希望自己能够绕过它,但我也不能备份它,所以我需要一种办法,可以在它坏掉的时候有办法复原资金。如果发生了什么事情、我又弄丢了我的密钥,但依然可以复原我的资金,那该多好。所以,我加入了一个长达 1 年的时间锁,并将我的妈妈加入作为多签名的一个签名人。这是一个 2-of-3 的门限多签名装置。这就意味着,如果那个 HSM 坏掉了,我可以在一年之后拜访我妈妈,然后复原我的资金。又或者,如果我弄丢了自己的密钥,但 HSM 还没坏,我也可以复原我的资金。如果我死了,我妈妈可以和 HSM 一起签名交易,然后拿出我的钱。这是非常强大的,除非等待一年,否则我无法绕过这个 HSM,对吧?现在,这样的用法可以成为现实了。只需点几下鼠标,就可以建立。而它的描述符,你可以导入到 Bitcoin Core,等等。
我的演讲就到这里了。我想说的是,对我来说,2023 年极大地改变了我们可以用比特币做的事,就是因为 Miniscript。没错,就是这样,点几下鼠标,你就可以获得自己的钱包的描述符,你可以将它导入任何钱包,只要它支持描述符。而硬件钱包现在也开始支持它了。所以我们的进展不错。试想一下你可以在自己的钱包里加入什么、你想要什么,因为人们可以为你开发出来。而且我们可以让比特币比起今天更加安全、更能容错。对我来说,这真是开天辟地的大事。感谢每一个人开发 Miniscript、将它集成到硬件签名器和软件库中的每一个人。
谢谢。
MC:非常棒的演讲,当你提到是否还有其他人知道你的种子词的时候,人群里响起了紧张的笑声。现在是问答时间。
问:根据你对这些时间锁的描述,假设它们不使用某种限制条款(covenant)方案或某种预先签名交易然后删除私钥的方案,你是不是需要经常轮换你的 UTXO 以重置时间锁?Liana 是否会自动为你重置?或者,它会提醒你应该定期上线检查并准备好签名的硬件签名器吗?
答:很棒的问题。问题的核心是,如何刷新时间锁?在 Liana 钱包中,我们使用 OP_CSV,所以,时间锁的起始点是一个输出得到区块确认的时间。如果你长期不移动自己的资金,那么,没错,时间锁会过期。所以你需要保证经常使用自己的钱包。这是一个 UI 问题,而不是一个脚本或者限制条款或者预签名交易的问题。我的意思是,没错,时间锁当然有可能过期。所以,因为 OP_CSV,它不应该作为你的长期储蓄钱包,因为 CSV 只能持续一年。所以你每年至少需要花费一次,以刷新你的时间锁。从 UI 的角度来说,这将成为钱包之间的一个区别因素,因为 Miniscript 允许你做一些非常奇怪的事情。而钱包,或者说开发钱包的人,没办法穷举人们可以使用 Miniscript 可以做的所有事情。所以,从技术的角度看,一款钱包只要兼容 Miniscript,就能花费你的资金。它将能理解如何构造交易并花费你的资金。但不一定能够给你展示关于这个脚本实际上做了什么的所有信息。所以,在 Liana 钱包里面,你会看到一个计时器。它会告诉你还剩多少个区块 —— 再过多少个区块,第二条路径就能动用了。每个 UTXO 都会有一个计时器,当时间锁快要过期的时候,它会告诉你,要求你轮换你的 UTXO。我们还没有自动轮换机制,但未来会有的。我认为这应该已经回答你的问题了,对吧?
MC:很好。这里有一个问题。
问:您好。之前我曾经想要在比特币钱包中实现这种时间锁。按照我的理解,我需要 —— 为了备份它,我既需要备份助记词,也需要备份这个交易的时间锁。我该如何解决这个 UX 问题呢?
答:你的意思是,你要备份什么东西,对吗?
问:是的。我希望用时间锁交易来复原我的钱包。
答:在任何比特币钱包种,你都需要两个东西。首先,你需要私钥(或者说秘密值),从而能够花费钱包中的资金。然后,你需要能够找出你能够花费多少钱,对吧?从一个 UI/UX 的角度看,我认为这是一个错误,但不管怎么说,这么长时间下来,人们主要使用的钱包类型就是单签名钱包。因为钱包只包含一个密钥,所以你只需备份一套助记词,基本上就是这样。而今天的钱包的做法是,助记词中不包含任何能让你找出自己能花多少钱的信息。这很疯狂。虽然你拥有私钥,但你也只拥有私钥;然后,猜测你能花费多少资金的工作就交给了钱包。这种办法对常规钱包是有用的,它可以暴力穷举最常用的密钥派生路径。钱包会尝试分辨:这个私钥的主人使用 SegWit 输出吗?TA 使用 Taproot 输出吗?TA 到底使用哪种地址呢?钱包会穷举,这就是它的工作方法。我认为,这是非常糟糕的。但不管怎么说,这就是为什么我们今天不需要备份很多信息 —— 因为你让钱包通过穷举来确定你能花多少钱。
但是,你应该总是有一张地图,可以标示出你的钱在哪儿,这就是我们今天说的 “描述符”(全称 “输出描述符”)的作用。基本上,如果你创建过多签名钱包,你应该已经注意到了这个东西。基本上,Miniscript 做的事情也差不多,对吧?所以,任何钱包,只要兼容 Miniscript,就能为你生成输出描述符。我没有放案例在我的幻灯片上,但如果你听了今天上午关于 Miniscript 的演讲,你应该已经看过了一些。它就是一堆字符,解释应该如何寻找你的资金。如果你创建的是多签名钱包,它就会包含关于你的多签名钱包的信息,比如第一个公钥是什么、第二个公钥是什么,等等。
所以你需要备份这两个东西。首先是助记词,然后是描述符。
问:你认为现在应该推出一种新的助记词标准吗?
答:我并不这么认为。我认为人们应该,怎么说呢,助记词本身也是一堆烂泥,但重点是让人们理解自己到底备份了什么东西。描述符表示应该如何寻找你的资金。然后你要保存秘密材料,基本上就是私钥,不管它的形式是什么样的,对吧?即使是助记词,也只是另一种可以导出的私钥形式,这不是重点。重点是:一,你要能找到自己的资金;二,你要能够签名从而花费它们。这是两个不同的东西,所以我们总是需要两种东西。这不是我现在才提出的新问题。
MC:很好。我看到台前还有一个提问。
问:我应该没忘记我要问的问题。稍等,嗯,是这样的。假设我们制作了这样一种装置,比如说我可以动用自己的比特币,然后,一年之后,其他人,比如说我妈妈,可以动用我的比特币。等等。那么,我猜测我们两个人都需要备份这个描述符。那这不就让其他人也能看到我的交易历史以及所有东西了吗?
答:没错,描述符就像标记你的资金存放地点的地图。它不是私钥这样的秘密材料,但它确实完全反映了你的一切,因为它会展示你所有的资金以及你所有的交易历史,对吧?这看起来是同一个问题。
问:密钥会轮换吗?或者说,是否你每次更换密钥,都需要改变描述符呢?
答:不不不,不需要,这就是重点。描述符描述的是你建立钱包的方法。然后,你知道,所有的地址和找零地址都在其中了,所以你不需要考虑这个问题。在你使用的时候,你不需要考虑时间锁。仅在你第一次建立钱包的时候,你才需要考虑它。在你设置好时间锁之后,就能创建自己的钱包描述符。然后你每次收取交易、发送交易,都跟现在的常规钱包是一样的。所以只有一个描述符,你保管好它就可以了。
MC:很好。那我们还能回答问题吗?看起来可以。
问:那我接着往下问。假设我的妈妈是一个联合签名人,我怎么向她隐藏呢?你现在有什么办法,能向 5 美元扳手先生隐藏这个描述符吗?因为不给出这些信息是个好事。你是否认为我们需要用每一个私钥来加密,然后把加密的描述符藏起来?
答:在 “5 美元扳手攻击”(译者注:人身威胁)中,我不认为真的有工具能帮上忙。我不认为这是一个重点。但从保存和复制这些描述符的角度看,是的,你可以加密它们。而且,一旦你使用每一个密钥来加密它,你就知道它不会丢了。因为基本上你需要至少一个密钥来花费它。所以如果你还有至少一个密钥,你就能解密那个描述符。没错,描述符可以备份在云端服务器,如果加密了,那是更好的,这样就不会有泄露信息的风险。
问:我再追问一下。所以,我猜,这在你使用这些服务商作为自己的应急备份的时候,加密描述符也是有用的。因为你不希望他们跟踪你,除非你真的要用到他们。他们应该能在恰当的时候得到描述符,但不应该提前得到。
答:是的。但你也会发现,我妈妈也差不多,我也不希望我妈妈知道我有多少比特币。
问:但她没法使用你的描述符呀。
答:这话是什么意思呢?
问:我不知道你妈妈有多了解比特币钱包 : )
答:哈哈。但基本上,我可以跟别人分享这个描述符。描述符跟秘密值不一样。我可以用我妈妈的密钥来加密。只要他们不能串谋,我妈妈就不知道我有多少钱。她只能在我死后才能动用我的钱,这非常好。
(完)
]]>