Study & contribute to bitcoin and lightning open source
Interactive AI chat to learn about bitcoin technology and its history
Technical bitcoin search engine
Daily summary of key bitcoin tech development discussions and updates
Engaging bitcoin dev intro for coders using technical texts and code challenges
Review technical bitcoin transcripts and earn sats
我非常高兴地宣布我们今天的第一位演讲者,来自 Specter 的 Stepan Snigirev,他是 Specter Solutions 的 CTO,有 3 年的开发比特币软件钱包(soft wallets)和硬件签名器(hard wallets)的经历。欢迎 Stepan。
我今天的演讲主题是 “在硬件签名器上支持 Taproot”。我们刚刚激活了 Taproot,非常棒,是在去年 11 月激活的。一些软件钱包已经开始集成了,而且甚至一些硬件签名器也开始集成了。现在大部分人用的都是单调的 “单密钥、单签名” 方案。我想讲讲我们可以用 Taproot 做什么。我觉得应该大家都知道了,所以我会讲快一点,然后我会讨论为什么在硬件签名器中集成 Taproot 是非常困难的、难点在哪里。如果我们无法在硬件钱包中集成,我们还有什么办法?
Taproot 非常出色。首先是它给了用户隐私性。在你观察区块链的时候,如果你看到一个单签名和单公钥的 taproot 地址,它里面可能实际上是一个公钥和一个脚本树。然后,这个公钥自身也可能凝结了一组公钥,而这棵脚本树可能非常高,是许许多多脚本的复杂集合。在里面你可以放置任意类型的时间锁,然后备份平时不会用到的私钥、仅在紧急情况下才启用它们。这意味着,所有复杂的花费条件,在链上看起来都是一样的。这是非常棒的事。甚至放在脚本中的公钥也可以代表着一组公钥,这就像是无限阶的密钥聚合。非常酷。
我个人会使用它的第一个理由是,它支持更好的明文备份。为什么现在没有人使用 Miniscript 或者复杂的比特币脚本?首先是因为比特币脚本复杂而不容易编写(在 Miniscript 出现之前)。其次是所有人都不使用它。这是一个鸡生蛋还是蛋生鸡的问题:每个人(90%)都使用单签名脚本,10% 的人使用多签名脚本,只有 0.3% 的人使用定制化的脚本。如果你使用一些定制化的脚本,你就暴露在了这 0.3% 里面。所有的链分析公司都知道,要是使用这样的脚本,那很有可能是同一个人。这样的隐私性非常糟,这就是障碍之一。
花费条件:or(HW, and(backup, timelock))
描述符:tr(HW, {and_v(v:pk(backup), older(timelock))})
Tapscript:<backup> OP_CHECKSIGVERIFY <timelock> OP_CHECKSEQUENCEVERIFY
(译者注:这段花费条件的意思是:一个硬件签名器随时可以花费这笔钱;同时,时间锁过期后,后备私钥也可以花费这笔钱。)
我个人会使用,我非常害怕把明文的钱包复原词(recovery phrases,应指种子词)放在我家里。如果有人得到了它,那我的钱就全部丢了。我个人的做法是使用一个不备份的硬件钱包,然后设置一个备用脚本,这个备用脚本带有时间锁,加上复原词就可以花费我的钱。然后,如果我遇到了什么意外,或者我的硬件钱包坏了,那么等待一段时间(也许半年)我就能拿回我的钱。但是,如果我的复原词被盗了,他们是没法立即偷走我的钱的(只要硬件钱包还在我手上的话)。我有足够多的时间,将资金迁移到一个新的装置上。但是,想想硬件签名器和 Miniscript 实现,现在还没有一个东西真正支持这个功能。太糟糕了。但实际上这并不是很难。在我给我们的硬件签名器集成 Miniscript 的时候,基本上我只花了一周的时间。我只要坐下来就可以开始开发了,因为它的说明真的写得非常好。Miniscript 有两个元素,其中一个你可以忽略,另一个是,如果你有一段可读的 policy 表达式,你就可以把它转成钱包的描述符。这有点复杂,但你不需要在硬件签名器里完成操作。第二部分是将钱包的描述符编译成实际的比特币脚本。这基本上只是把这些记号替换成比特币脚本的操作码,然后把派生出来的密钥放在正确的位置上。非常简单。然后硬件签名器就可以确定哪个输出是找零,并验证找零输出是从相同的描述符中派生出来的。这就行了。我想提一句,Ledger 团体最近做了很多工作来升级他们的比特币应用。他们在设计的时候就采用了 Miniscript 方法。虽然现在仅支持多签名功能,但很容易就能升级到支持定制化的 Miniscript,所以我很期待。至于硬件签名器,我不知道他们的计划。但至少会有两种硬件签名器将支持 Miniscript。
xpub = {c, P}
{c, P} = {h1(c, P, i) , P + h2(c, P, i)}
另一个应用场景是,假定你开了一家合伙的托管公司。你的客户可以在自己的多签名装置中使用你的公钥。举个例子,他们可以制作 2-of-3 的多签名装置,其中 2 个密钥由自己控制,但还有 1 个密钥是你们公司的。你不想让这个密钥出现单点故障,所以你虽然给出了一个公钥,但你不希望它只是一个公钥。你有一个办法:交互式的多签名。也就是将多个公钥聚合成一个公钥。遵循了特定的签名流程,你就只需向用户给出一个 xpub(公钥)。这是兼容 BIP32 以及 xpub 的派生方法的。它只是用一个基于哈希值的标量来调整你的公钥而已。你只需要提前把多个公钥组合起来就好。在你构造这个 xpub 时,你要取得 “链码(chaincode,用在 BIP32 密钥派生中的数据)”,然后运行 XOR 运算,将这个公钥与一个常规的 MuSig 或你用的其它协议的公钥结合起来;在你需要派生一个新的子私钥时,只需这个聚合链码以及公钥,就可以派生出下一个公钥。你能够使用多个设备执行交互式签名,来生成所需的正确签名。这是非常棒的应用。
但是,就像我说的,你需要依赖这种交互式的多签名流程。如果你读过论文,你可以数一数,建立交互式的多签名有多少种方法。就我所知,至少 5 种:MuSig、MuSig2、FROST、MuSig-DN、GKMN21 。这意味着,每一种方法都有自己的取舍。每一种都有自己的安全边界。我想在整体上介绍一下它们,并讲讲实现它们的难处在哪里。我应该先帮大家回顾一下 Schnorr 签名。
Schnorr 签名:
选取 nonce
r
,R = rG
sig(签名) = {R, d.hash(P, R, m) + r} x G = P. hash(P, R, m) + R
聚合签名:
P = sum of a_k P_k
R = sum of R_k = sum of r_k.G
sig_k = {R, a_k.d_k . hash(P, R, m) + r_k} sig = {R, d.hash(P, R, m) + r} xG P.hash(P, R, m) + R
(译者注:此处的记号有一些混乱。但下文的介绍是清楚的。)
如果你要签名,那么你先要选出一个随机的 nonce;你将这个 nonce 的公钥点跟签名公钥和待签名消息一起哈希,然后与你的签名私钥相乘,最后加上这个随机 nonce 值,以在签名中隐藏你的私钥,这样就没有人能计算出你的私钥。然后,验证者只需要得到你的签名,将它与生成器点相乘,就可以验证等式是否成立。如果你要在此基础上建立多签名,流程也是一样的,但会遇到一些问题。
第一个问题是放在哈希函数中的 nonce 值,每个联合签名人都要生成自己的 nonce 值。第一个要求是,他们要相互通信,告知他人自己要使用什么 nonce 值。只有每个人都得到聚合的 nonce 值之后,才能各自生成碎片签名。然后,举个例子,软件钱包可以将它们(所有碎片签名)加在一起,就得到了最终的签名。这个方案还不算是非常复杂,你只需要额外的一轮通信,来沟通 nonce。
那为什么会有这么多论文呢?先说 MuSig,它可能是人们提出的第一篇论文。它实现了 n-of-n 条件下的密钥聚合。要么是 2-of-2,要么是 3-of-3、5-of-5,你不能做 2-of-3 或者 3-of-5。它需要 3 轮通信。第一轮是选择 nonce 值(R_i = r_i.G
),你哈希它(hash(R_i)
)并把它发送给其他人。这是在承诺 nonce 值,就像在说:“这是我所用的 nonce 值的哈希值,我先放出来,我待会就会使用它所对应的 nonce 值,我不能再换用别的 nonce 值。”然后,人们发送 nonce 值本身(R_i
),从而得到所有联合签名人的 nonce 值,并生成聚合 nonce 值(R = sum of R_i
)。三轮通信很可怕,假设你有三个分散在不同地方的硬件签名器,那么,你需要跑 3 趟。而且,在此期间,每一个签名器都要保存状态,需要记得所有的承诺,等等。但当前的硬件签名器并不是这样工作的。这些签名器的设计目标是成为无状态的东西,不希望有交互。
R’_i = r’_i x G, R’’_i = r’’_i x G
R’_i, R’’_i
R = sum of (R’_i + b_i.R’’_i)
sig_i
然后是 MuSig2,是 MuSig2 的升级版,移除了第一轮承诺通信,但是,你就不是只生成一个 nonce 值了,你生成了两个。然后,你将这个 nonce 值跟其他人分享,并将它与某个基于哈希值的系数相乘。我不想讲太多细节,但这样一来,它的安全假设就多加了一条。如果你要让它变得更安全,你要生成 4 个 nonce 值。这跟一些麻烦的密码学有关,比如分叉引理(Foring Lemma)、时间机器什么的。如果你想搞清楚细节,我建议你读读论文。基本上,通过生成一个额外的 nonce 值来制作安全性,你就可以减少一轮通信。两轮已经好很多了。你可以提前生成 nonce 值。你可以从每一个硬件签名器中生成 100 个 nonce 值,然后在你需要的时候使用。然后你只需要为每个签名器跑一趟就行了。再说一遍,它也需要签名器保存状态。硬件签名器需要知道自己所生成的 nonce 值,并验证没有重复使用相同的 nonce 。如果你用了相同的 nonce ,那你的私钥会被人计算出来。
最后是 FROST。FROST 很有趣,因为它并不集中在 nonce 生成上,它瞄准的是密钥的聚合。你可以实现 2-of-3、3-of-5 的聚合密钥。这里的想法是使用 Shamir 的可验证私钥分割方案(verifiable secret sharing scheme)。如果你想实现 2-of-3,假定你有 3 个私钥,并且这 3 个私钥在同一条线上(on the same line),那你只需要其中 2 个,就可以派生出这个 结合私钥/公钥,以及签名。这三个点中的任意两个,都可以帮助你重新构造最终的签名。这意味着,唯一的问题在于,如何确保我们随机生成的私钥最终会在同一条线上。而 FROST 论文的主要想法就是如何在多个签名者之间沟通,使得他们最终会在同一条线上。这是一个交互式的方案,但非常好,因为在第一次交互式启动之后,你就可以将它抛在脑后,只需要 2 轮通信就可以了。但是,再说一遍,这 3 种方案都要求 nonce 值不能复用。这意味着,为了生成新的 nonce 值,要么你需要一个计数器,要么需要一个随机数生成器(RNG)。这两种东西在硬件签名器中都是问题,因为我可以黑了计数器,也可以劫持随机数生成器。我给你们看看怎么做到。
优点:非常简单就能实现,2 轮通信,接近于不需要交互。
缺点:重度依赖于 RNG 或者计数器,需要保存状态。
第一种想法是使用计数器。也就是一个只增不减、永不重置的数字;永远不会使用相同的数值,你将这个数值与你的私钥一起运行哈希函数,就可以得到 nonce 值。
递增型计数器:counter ++ ; r = hash(d, counter)
每次你使用的时候,就增加一下计数器的值。理论上它是很好用的。但在现实中有许多攻击,比如 Fraunhofer Institute 解锁了一个微控制器,然后发动了攻击。他们将一束激光照射进控制器,要么计数器会归零,要么激光照到正确的位置,它会减一次,从而导致相同的 nonce 值被再一次使用。这里的问题是,签名中的 nonce 值会进入区块链,而且你的联合签名人也会知道,软件钱包也会知道。这意味着,要是 nonce 值没有足够的熵,或者被重复使用,他们就可以从签名中计算出你的私钥。至于随机数生成器,DEF Con 上有一个非常棒的演讲,用 45 分钟讲了随机数生成器的问题。这意味着,即使你使用了一个有证书(闭源的)随机数生成器,它一般来说也是不够用的。这个演讲大部分的内容都关于作为一个开发者,你可能会怎样搞砸随机数生成器。甚至还没讲到随机数生成器被黑的问题。
所以,你能怎么搞砸随机数生成器呢?首先,你无法控制随机数生成器会面临的环境。索尼的 Playstation 2 就被黑了,因为索尼重复使用了相同的 nonce,导致私钥被泄露,然后你就可以拿这个索尼私钥在家里自制 Playstation 2 了。Yubikey,当他们想要通过认证流程时,他们确实通过了,但搞砸了随机数生成器的初始化。只需要 3 个签名,就可以计算出他们的私钥。就像我在这个演讲中说的,有人分析了来自两家微控制器的随机数生成器的输出。他们说,有时候,你会意外得到一堆全是 0 的 nonce,这就完蛋了。有时候,它会多次给你相同的数值,如果你请求随机数过于频繁的话。它就是不能生成一个新的随机数。还有一些时候,RNG 会出错,因为电压故障或者一些神秘的原因(比如太阳烤热了微控制器)。另外说一句,这样的事情在计数器上也可能发生。要是这发生在你的使用了计数器的硬件签名器上,你就真的遇到很大一件事了。这就是搬起石头砸自己的脚。
我们再假设有人就想砸你的脚。那你会如何中招呢?这是最常见的随机数生成器架构,一个环形震荡器(ring oscillator)。基本上,它使用了标准的 “非” 门,这是很容易用半导体实现的。非门的作用是将 0 变成 1,将 1 变成 0 。你将 3 个非门连接在一起,这样你会得到一些时延。然后,你又将第三个非门的输出当成第一个非门的输入。这就成了一个荒谬的逻辑电路,它会不断在 0 和 1 之间跳来跳去,而切换的时机将高度依赖于环境、制造缺陷、半导体的杂质,等等。所以它基本上会给你一个完全无法预测的输出。为了得到更加随机的输出,你可以把一堆这样的振荡器放在一起,运行 XOR 操作。现在,你回忆一下你在高中或者初中上过的物理课,如果你把多个钟摆放在一根杆子上,会怎么样?不会怎么样,因为它是一根杆子,这些钟摆都会以自己的频率摆动,但是如果把它们放在一根绳子上呢?绳子会让能量在这些钟摆间传递。所以一段时间之后,这些钟摆会同步 —— 摆动的频率变得相同。你在 YouTube 上搜索一下视频吧,看看是怎么回事。问题来了,如果你将多个振荡器放在一起,那么最终它们会同步,然后你的随机输出就将不再随机。在得到了认证的、设计良好的 RNG 中,会有一些应对措施来检查输出是否良好。但是,如果你只是把自己的微控制器放在 PCB 板上,而且设计很差,有一个路径可以通过所有这些控制器,那会怎么样?它会把这种耦合重新带回来。有一种攻击是盗取你的设备、拆解它、在里面放一些电线,也是一样的道理 —— 引入耦合。然后你就完蛋了。
另一种随机数生成也不是完美的。如果你使用的是一个依赖于温度的东西,那我们可以把它冻起来。你还可以调低随机数生成器的电压,然后它就会输出更多的 0,输出更少的 1,或者产生一些奇怪的东西。这些奇怪的东西也是低熵的。没有足够的熵,你的 nonce 就会被暴力破解,然后你就完了。还有各种各样的错误注入,我可以拿一个电磁表,迫使这些振荡器或者说随机数生成器都失灵。所以随机数生成器是糟糕的,至少不完美。
有没有一种解决方案呢?可以看到有 5 篇论文。在第二列中,有两篇论文不需要随机数生成器。他们使用确定性的 nonce,而且不止是确定性的,还是可验证的确定性 nonce。这意味着,你的硬件签名器可以生成 nonce 值并向他人证明它是使用某一种算法确定性地生成出来的。MuSig-DN 是使用了确定性 nonce 的 MuSig。GKMN21(Garillot、Kondi、Mohassel、Nikolaenko)来自 Facebook,他们确实出版了这篇论文,而且写得非常好。看起来有一个非常棒的解决方案。唯一的问题是,生成这些证据(证明你得 nonce 是确定性得)是非常复杂的。比如在 MuSig-DN 的基准测试中,如果你在一个英特尔的 i7 、频率为 3Ghz 的核心计算器上运行(这是家用计算机的配置),它需要 1 秒来生成证据。考虑到硬件签名器一般只有 100 MHz,而且是 32 位的,不是 64 位的,你可能要乘以 100。
这里作一个比较。前面三种方案(MuSig、MuSgi2 和 FROST)依赖于 RNG,所以我们先不管基准测试,它肯定更快。但后面的两种(MuSig-DN,GKMN21),MuSig-DN 在微控制器上可能需要 100 秒。如果你有一笔 5 个输入的交易,你可能需要等待 10 分钟,这就不舒服了。而且内存要求也很高。我认为可以优化,但依然需要微控制器有 10 MB 的内存。现在的签名器一般来说是 100 KB,左右吧。也许在高端设备上可以有 MB 级别的内存。比如 Keystone 是基于 Android 系统的,它有大量的内存,但他们在安全芯片(security element)上运行的安全代码也不是非常高效。证据的大小还行,1KB 。我喜欢 QR 码,所以我不喜欢通过 QR 码来传输 1KB,那会很复杂,但还好。最后,第二篇论文(GKMN21)使用了零知识证明,所以它会快很多,而且在微控制器上也可以运行。内存要求我不确定,但我认为也要用到几 MB。证据的体积是 1MB。相当于一整个比特币区块的大小。但你不需要广播它,只需要在签名人之间传播。
这就是我对所有的多签名方案的总结。每一种都有特定的取舍。我会说,如果你使用了多个硬件签名器,而且不想让它们在同一时间连接同一台电脑,比如它们是分开保管的,那么别用交互式多签名方案。使用常规的多签名就好,知道我们得到更合理的 MuSig 实现。但是,确实有一些用途,是非常有用的。
比如说闪电网络,它有完全不同的安全模型,你的私钥一直是触网的。无论如何你都需要保存状态。所以使用 MuSig 并不会增加你的攻击界面。所以用在闪电通道中就很好。原子化互换,可以在热钱包中完成,所以情况也非常类似。也没问题。然后是服务端是签名者之一的情况。比如 Blockstream Green 钱包,使用 2-of-2 或者 1-of-2 加上时间锁的装置。使用 Taproot 也完全是一种优化。Muun 钱包使用 2-of-2 多签名,Square 也在使用服务端做一件跟移动钱包和安全私钥管理相关的有趣的事。都是非常好的应用场景。最后是我最喜欢的东西,我的梦想、我的激情、我的宝贝。我梦想了 3 年了,一直没有时间去实现。一个 paranoid HSM(硬件安全模块),将多个芯片组合在一个设备上,使用完全开源的 RISC-V PGA 板,使用基于 NDA 的安全芯片(比如英飞凌的),还有一些其它的基于 RAM 的微控制器。每一个芯片都有一个私钥,每一个都必须签名才能得到一个完整的签名。然后,如果攻击者希望黑掉这个东西,他们需要黑掉 3 种不同的微控制器。这非常非常棒,尤其是对 HSM 企业用户来说。企业也可以将 HSM 放在一个法拉第笼(Faraday cage)中,并使用一根导线,来检测所有篡改的尝试,等等。这非常酷。我觉得 Taproot 真是太令人惊讶了。我们可以翘首等待它的演化,太棒了。
问:闪电钱包现在就可以用到 Taproot 多签名了吗?
答:现在应该连规范都还没有,所以应该也还没有软件实现。c-lightning 应该在往这个方向开发,但最好问问 Christian Decker,他也在这里。应该还在编写规范的阶段。但总的来说,它意味着,你的开启通道和共同关闭通道的操作,在链上都只会表现为一个单签名以及单公钥。仅在单方面关闭通道的时候,才会暴露你的 Taptree 上有一个时间锁。
问:你认为上面的合作式多签名方案中,哪一种能得到主流采用?我们现在的情况很尴尬,已经有 14 种标准了,我们需要制定一个最终的标准,结果是出现了 15 种标准。
答:我不知道哪一种是每个人都会用的。我个人喜欢 MuSig,可能还有它跟 FROST 的某种结合,因为它实现了门限签名。从我的理解来说,FROST 会有交叉输入集成(cross input integration)的问题,Sanket 已经提到了。坦白说我也不知道。我认为从 MuSig 开始理解这个领域是对的,因为它可能也是第一种被采用的。最好问问开发 libsecp256k1 的人。我没有一个确定的答案。
问:哪一种硬件签名器允许你自己为 nonce 提供熵吗?如果没有,为什么没有呢?
答:在 Taproot 以前,比特币社区讨论硬件签名器时,讨论的是选择 nonce 攻击(chosen nonce attacks)、侧信道攻击(side channel)的验证和缓解,这些会在硬件签名器被劫持时导致你的私钥泄露。我知道两种硬件签名器在签名种实现了额外熵的混合,是 Jade 和 Bitbox 。就我所知,迄今为止没有别人做了这个。但这对 MuSig 的第一步来说非常重要。我认为,我们可以为 MuSig 复用相同的规范和协议。只要其它硬件钱包开始了,当他们开始实现 MuSig 的时候,他们就需要这个 API 来混合额外的熵。
问:你提到了 RISC-V。你认为是否有某种定制化指令,可以让这些变得非常快吗?基本上你必须在软件实现种计算所有东西。
答:在 FPGA 上的 RISC-V 吗?我推进你看看来自 Bunnie 的 Precursor 项目,供记者使用的安全通讯设备以及别的东西正在众筹。现在他们正在制造。他们非常了解硬件,他们使用 FPGA,并在 FPGA 上使用 RISC-V 核心,所以你可以看看。他们也有一套操作系统,以及一些安全操作。理论上来说他们也是完全开源的。我们可以做的就是把他们做的一切都拿过来然后为我们所用。我认为开源的 RISC-V 核心发展得非常快,而且更接近量产状态了。在这个设置中,比如说,你并不需要真正到达可量产状态,因为你的安全性还依赖于其它芯片。有一些 FPGA 制造商位于中国,我不知道你信不信得过。它们非常便宜,而且可以在最小实现上运行 RISC-V。总的来说,有了 FPGA,你想要运行 RISC-V 并获得良好的速度,你需要支付一些溢价,因为 FPGA 更贵。可能几百美元吧。
Community-maintained archive to unlocking knowledge from technical bitcoin transcripts