第八章. 数字签名

  • berry
  • 发布于 2025-02-09 13:24
  • 阅读 10

第八章. 数字签名

综合介绍

\ 目前比特币中使用了两种签名算法,分别是Schnorr签名算法和椭圆曲线数字签名算法(ECDSA)。这些算法基于椭圆曲线私钥/公钥对进行数字签名,如第56页的“椭圆曲线加密解释”中所描述的。它们用于花费SegWit v0 P2WPKH输出、SegWit v1 P2TR路径花费,以及脚本函数OP_CHECKSIG、OP_CHECKSIGVERIFY、OP_CHECKMULTISIG、OP_CHECKMULTISIGVERIFY和OP_CHECKSIGADD。每当其中一个被执行时,都必须提供一个签名。

在比特币中,数字签名有三个作用。首先,签名证明了私钥的控制者,也就是资金的所有者,已经授权使用这些资金。其次,授权的证据是不可否认的(不可否认性)。第三,被授权的交易不能被未经身份验证的第三方更改,其完整性得到保证。

注意:每个交易输入及其可能包含的任何签名都与其他输入或签名完全独立。多方可以合作构建交易,每个人只需对一个输入进行签名。几种协议利用了这一点来创建用于隐私的多方交易。

在本章中,我们将介绍数字签名的工作原理,以及如何通过它们提供私钥控制的证明,而不暴露私钥。

数字签名的工作原理

数字签名由两部分组成。第一部分是使用私钥(签名密钥)为消息(交易)创建签名的算法。第二部分是一个算法,允许任何人在给定消息和相应的公钥的情况下验证签名。

生成数字签名

在比特币中使用的数字签名算法中,被签名的“消息”是交易,或更准确地说是交易数据的特定子集的哈希值,称为承诺哈希(参见“签名哈希类型(SIGHASH)”第185页)。签名密钥是用户的私钥。结果是签名:

$$ Sig = F{sig}(F{hash}(m),x) $$

其中:

  • x 是签名的私钥
  • m 是要签名的消息,即承诺哈希(例如交易的部分)
  • Fhash 是哈希函数
  • Fsig 是签名算法
  • Sig 是生成的签名

你可以在“Schnorr Signatures”第187页和“ECDSA Signatures”第197页找到有关schnorr和ECDSA签名数学的更多详细信息。

在schnorr和ECDSA签名中,函数Fsig 生成一个由两个值组成的签名 Sig。这两个值在不同算法中存在差异,我们稍后会详细探讨。计算出这两个值后,它们被序列化成一个字节流。对于ECDSA签名,编码使用称为Distinguished Encoding Rules或DER的国际标准编码方案。对于schnorr签名,使用了更简单的序列化格式。

验证签名

签名验证算法接受消息(通常是交易和相关数据的哈希)、签名者的公钥和签名,并且如果该签名对于此消息和公钥是有效的,则返回TRUE。 要验证签名,必须具备以下组件:

  1. 签名本身。
  2. 序列化的交易。
  3. 有关要花费的输出的相关数据。
  4. 与用于生成签名的私钥相对应的公钥。

总的来说,签名验证过程确认只有生成给定交易消息的公钥对应的私钥的个人或实体才能在给定交易上产生此签名。

签名哈希类型(SIGHASH)

数字签名适用于消息,在比特币中,这些消息就是交易本身。签名证明了签名者对特定交易数据的承诺。在最简单的形式中,签名适用于几乎整个交易,因此承诺了所有输入、输出和其他交易字段。然而,签名也可以仅适用于交易数据的子集,这对于许多情况都是有用的,正如我们将在本节中看到的那样。

比特币签名有一种方法来指示由私钥使用 SIGHASH 标志签名的交易数据的哪部分被包含在哈希中。SIGHASH 标志是附加到签名的单个字节。每个签名都有一个明确或隐含的 SIGHASH 标志,而且该标志可能从一个输入到另一个输入不同。一个包含三个已签名输入的交易可能有三个带有不同 SIGHASH 标志的签名,每个签名都签署(承诺)交易的不同部分。

请记住,每个输入可能包含一个或多个签名。因此,一个输入可能有具有不同 SIGHASH 标志的签名,这些签名承诺交易的不同部分。还要注意,比特币交易可能包含来自不同“所有者”的输入,这些所有者可能仅在部分构建的交易中签署一个输入,与其他人合作以收集所有必要的签名以使交易有效。 许多 SIGHASH 标志类型只有在考虑到多个参与者在比特币网络之外协作并更新部分签名的交易时才有意义。

SIGHASH 标志有三种类型:ALL、NONE 和 SINGLE,如表 8-1 所示。

表 8-1. SIGHASH 类型及其含义

<table><thead><tr><th width="148">SIGHASH标志</th><th width="73">值</th><th>描述</th></tr></thead><tbody><tr><td>ALL</td><td>0x01</td><td>签名适用于所有的输入和输出</td></tr><tr><td>NONE</td><td>0x02</td><td>签名适用于所有的输入,但不适用于任何输出</td></tr><tr><td>SINGLE</td><td>0x03</td><td>签名适用于所有输入,但仅适用于与签名输入具有相同索引号的一个输出</td></tr></tbody></table>

此外,还有一个修饰标志,SIGHASH_ANYONECANPAY,可以与前面的每个标志结合使用。当设置了ANYONECANPAY时,只有一个输入被签名,其余的(及其序列号)则保持开放以进行修改。ANYONECANPAY的值为0x80,通过按位或运算应用于组合标志,如表8-2所示。

表8-2. 带有修饰符的SIGHASH 类型及其含义

SIGHASH标志 描述
ALL|ANYONECANPAY 0x81 签名适用于一个输入和所有输出
NONE|ANYONECANPAY 0x82 签名适用于一个输入,但不适用于任何输出
SINGLE|ANYONECANPAY 0x83 签名适用于一个输入,以及具有相同索引号的输出

在签名和验证过程中应用SIGHASH标志的方式是,首先复制交易,然后将其中某些字段省略或截断(设置为空长度)。结果交易进行序列化。SIGHASH标志包含在序列化的交易数据中,然后对结果进行哈希。哈希摘要本身就是被签名的“消息”。根据使用的SIGHASH标志不同,将包括交易的不同部分。通过包含SIGHASH标志本身,签名也承诺了SIGHASH类型,因此它不能被更改(例如,由矿工更改)。

在“ECDSA签名的序列化(DER)”中,我们将看到DER编码签名的最后部分是01,这是ECDSA签名的SIGHASH_ALL标志。这将锁定交易数据,因此Alice的签名将承诺所有输入和输出的状态。这是最常见的签名形式。

现在让我们看看其他一些SIGHASH类型及其在实践中的使用方式:

\ ALL|ANYONECANPAY

这种类型的构建可用于创建“众筹”风格的交易。试图筹集资金的人可以构建一个只有一个输出的交易。单个输出支付“目标”金额给筹款人。这种交易显然是无效的,因为它没有输入。然而,其他人现在可以通过添加他们自己的输入作为捐款来修改它。他们使用ALL|ANYONECANPAY对自己的输入进行签名。除非收集到足够的输入达到输出值,否则该交易是无效的。每笔捐款都是一笔“承诺”,在筹集到整个目标金额之前,筹款人不能收取。不幸的是,该协议可以被筹款人添加他们自己的输入(或来自借给他们资金的人),即使他们没有达到指定的价值,也允许他们收取捐款。

NONE

这种类型的构建可用于创建特定金额的“持票支票”或“空白支票”。它承诺了所有的输入,但允许输出被更改。任何人都可以将自己的比特币地址写入输出脚本中。单独使用这种方式允许任何矿工更改输出目的地并将资金据为己有,但如果交易中的其他必要签名使用了SIGHASH_ALL或另一种承诺输出的类型,那么它允许这些花费者更改目的地,而不允许任何第三方(如矿工)修改输出。

NONE|ANYONECANPAY

这种类型的构建可用于构建一个“尘埃收集器”。在他们的钱包中有微小UTXO的用户,无法在费用超过UTXO价值时进行支出;请参阅“经济不划算的输出和不允许的尘埃”页。有了这种类型的签名,经济不划算的UTXO可以捐赠给任何人在他们想要的时候进行聚合和支出。

\ 有一些提议修改或扩展SIGHASH系统。截至目前,最广泛讨论的提议是BIP118,该提议提议添加两个新的sighash标志。使用SIGHASH_ANYPREVOUT的签名不会承诺输入的outpoint字段,从而允许其用于花费特定witness程序的任何先前输出。例如,如果Alice收到了两个相同金额的输出到相同的witness程序(例如,需要她钱包的单个签名),则用于花费其中一个输出的SIGHASH_ANYPREVOUT签名可以被复制并用于花费另一个输出到相同的目标。

使用SIGHASH_ANYPREVOUTANYSCRIPT的签名不会承诺输出点、金额、见证程序或taproot merkle树(脚本树)中的特定叶子,因此可以花费任何之前的输出,只要签名可以满足条件。例如,如果Alice收到了两个不同金额和不同见证程序的输出(例如,一个需要单个签名,另一个需要她的签名加上一些其他数据),则用于花费其中一个输出的SIGHASH_ANYPREVOUTANYSCRIPT签名可以被复制并用于花费另一个输出到相同的目标(假设第二个输出的额外数据已知)。

两个SIGHASH_ANYPREVOUT操作码的主要预期用途是改进的支付通道,例如闪电网络(LN)中使用的通道,尽管还描述了几种其他用途。

注意:在用户的钱包应用程序中,很少会看到SIGHASH标志作为选项呈现。简单的钱包应用程序使用SIGHASH_ALL标志进行签名。更复杂的应用程序,例如LN节点,可能使用替代的SIGHASH标志,但它们使用已经广泛审查的协议来理解替代标志的影响。

施乃尔(Schnorr)签名

1989年,克劳斯·施乃尔发表了一篇论文,描述了他的同名签名算法。该算法并不特定于椭圆曲线密码学(ECC),尽管它如今可能与ECC联系最为密切,ECC是比特币和许多其他应用程序使用的密码学。施乃尔签名具有许多良好的特性:

\ 可证明安全性

对施乃尔签名安全性的数学证明仅依赖于解决离散对数问题(DLP)的难度,特别是对于比特币所使用的椭圆曲线(EC),以及哈希函数(比特币中使用的SHA256函数)产生不可预测的值的能力,称为随机预言模型(ROM)。其他签名算法可能具有额外的依赖项,或者需要更大的公钥或签名才能获得与ECC-Schnorr相同的安全性(当威胁定义为经典计算机时;其他算法可能提供更有效的安全性,针对量子计算机)。

线性性

施乃尔签名具有数学家称为线性性的属性,该属性适用于具有两个特定属性的函数。第一个属性是将两个或多个变量相加,然后对该总和运行函数将产生与分别对每个变量运行函数然后将结果相加相同的值,例如,f(x + y + z) == f(x) + f(y) + f(z);这种属性称为可加性。第二个属性是将一个变量乘以一个数,然后对该乘积运行函数将产生与对变量运行函数然后乘以相同量的值相同的值,例如,f(a × x) == a × f(x);这种属性称为一次齐次性。

在密码学操作中,一些函数可能是私有的(例如涉及私钥或秘密随机数的函数),因此能够在函数内外执行操作获得相同结果,使得多个方可协调合作而无需分享其秘密。我们将在“基于施乃尔的无脚本多重签名”(第193页)和“基于施乃尔的无脚本阈值签名”(第195页)中看到线性性的具体好处。

批量验证

当以某种方式使用(比特币即如此)时,施乃尔的线性性的一个后果是,相对而言,可以比独立验证每个签名所需的时间更简单地同时验证多个施乃尔签名。在一个批次中验证的签名越多,加速度越大。对于一个块中的典型签名数量,可以在大约一半的时间内批量验证它们,而这将花费验证每个签名的时间。

在本章的后面,我们将精确描述施乃尔签名算法,就像它在比特币中使用的那样,但我们将从一个简化版本开始,并逐步逼近实际的协议。

Alice首先选择一个大的随机数(x),我们称之为她的私钥。她还知道比特币椭圆曲线上的一个公共点称为生成器(G)(参见“公钥”)。Alice使用椭圆曲线乘法将G乘以她的私钥x,其中x被称为标量,因为它对G进行了缩放。结果是xG,我们称之为Alice的公钥。Alice将她的公钥提供给Bob。尽管Bob也知道G,但离散对数问题阻止Bob能够通过G将xG除以G来推导出Alice的私钥。

在以后的某个时间,Bob希望Alice通过证明她知道用于前面接收的公钥(xG)的标量x来证明自己的身份。Alice不能直接给Bob x,因为那样会使他能够向其他人证明自己是她,因此她需要在不向Bob透露x的情况下证明她对x的知识,这称为零知识证明。为此,我们开始施乃尔身份验证过程:

  1. Alice选择另一个大的随机数(k),我们称之为私密nonce。她再次将其用作标量,将其与G相乘以产生kG,我们称之为公共nonce。她将公共nonce提供给Bob。
  2. Bob选择自己的一个大随机数e,我们称之为挑战标量。我们称之为“挑战”,因为它用于挑战Alice证明她知道用于前面向Bob提供的公钥(xG)的私钥x;我们称之为“标量”,因为它稍后将用于乘以一个椭圆曲线点。
  3. Alice现在拥有数(标量)x、k和e。她将它们组合在一起,使用公式s = k + ex来生成最终的标量s。她将s提供给Bob。
  4. Bob现在知道标量s和e,但不知道x或k。但是,Bob知道xG和kG,并且他可以自己计算sG和exG。这意味着他可以检查Alice执行的操作的放大版本的相等性:sG == kG + exG。如果相等,那么Bob可以确信Alice在生成s时知道x。

Schnorr身份验证协议中的整数替代椭圆曲线点

如果我们可以通过使用简单的整数代替椭圆曲线上的点来创建一个不安全的过度简化版本,可能更容易理解,交互式Schnorr身份验证协议。例如,我们将使用从3开始的质数: 设置:Alice选择x = 3作为她的私钥。她将其与生成器G = 5相乘,以获得她的公钥xG = 15。她将15提供给Bob。

  1. Alice选择私密nonce k = 7,并生成公共nonce kG = 35。她将35提供给Bob。
  2. Bob选择e = 11,并将其提供给Alice。
  3. Alice生成 s = 40 = 7 + 11 × 3。她把40给了Bob。
  4. Bob推导出 sG = 200 = 40 × 5 和 exG = 165 = 11 × 15。然后他验证 200 == 35 + 165。注意,这是Alice执行的相同操作,但所有值都被放大了5倍(G的值)。

当然,这是一个过度简化的例子。当使用简单整数时,我们可以通过生成器G来除以产品,以获取基础标量,但这并不安全。这就是比特币中使用的椭圆曲线加密的一个关键特性,即乘法容易,但除以曲线上的点是不切实际的。此外,对于这么小的数字,通过穷举法找到基础值(或有效的替代值)是很容易的;比特币中使用的数字要大得多。

让我们讨论一下交互式Schnorr身份验证协议的一些使其安全的特性:

\ 随机数(k):

在第1步中,爱丽丝选择一个鲍勃不知道且无法猜测的数字,并将该数字的缩放形式kG交给他。此时,鲍勃已经拥有她的公钥(xG),这是她的私钥x的缩放形式。这意味着当鲍勃在处理最终方程式(sG = kG + exG)时,有两个他不知道的独立变量(x和k)。使用简单的代数可以解决一个未知变量的方程,但不能解决两个独立的未知变量,因此爱丽丝的nonce的存在防止了鲍勃能够推导出她的私钥。值得注意的是,此保护取决于nonce在任何方面都不可预测。如果爱丽丝的nonce有可预测的任何东西,鲍勃可能会利用这一点来推断出爱丽丝的私钥。有关更多详细信息,请参阅“签名中随机性的重要性”(第200页)。

挑战标量(e):

鲍勃等待收到爱丽丝的公共nonce,然后在第2步中继续给她一个数字(挑战标量),爱丽丝之前不知道并且无法猜测。重要的是,鲍勃只有在她承诺了她的公共nonce之后才会给她挑战标量。想象一下,如果一个不知道x的人想要冒充爱丽丝,而鲍勃在他们告诉他公共nonce kG之前意外地给了他们挑战标量e,会发生什么情况。这使得冒充者可以更改鲍勃将用于验证的方程两侧的参数,sG == kG + exG;具体来说,他们可以同时更改sG和kG。考虑到方程式的简化形式:x = y + a。如果你可以同时更改x和y,你可以使用x' = (x – a) + a来消除a。现在,你选择的任何x值都将满足方程式。对于冒充者而言,实际方程式很简单,他们只需为s选择一个随机数,生成sG,然后使用EC减法选择一个等于kG的kG = sG – exG。他们将给出他们计算出的kG以及稍后的随机sG,而鲍勃会认为这是有效的,因为sG ==(sG – exG) + exG。这解释了协议中操作顺序的重要性:鲍勃必须在爱丽丝承诺她的公共nonce之后才能给她挑战标量。

\ 在这里描述的交互式身份验证协议与克劳斯·施诺尔的原始描述部分匹配,但缺少了我们在去中心化比特币网络中需要的两个关键特性。其中之一是它依赖于鲍勃等待爱丽丝承诺她的公共nonce,然后鲍勃给她一个随机的挑战标量。在比特币中,每个交易的支出者都需要由数千个比特币全节点进行身份验证,包括尚未启动但其运营商将来会希望确保他们收到的比特币来自每个交易都有效的传输链的未来节点。无论今天还是将来,任何无法与爱丽丝通信的比特币节点都将无法对其进行身份验证,并且将与对其进行身份验证的每个其他节点不一致。对于像比特币这样的共识系统来说,这是不可接受的。为了让比特币运作,我们需要一个不需要爱丽丝与每个想要对她进行身份验证的节点进行交互的协议。

一种简单的技术,称为Fiat-Shamir变换,可以将Schnorr交互式身份验证协议转换为非交互式数字签名方案。回想一下步骤1和2的重要性,包括它们按顺序执行。爱丽丝必须承诺一个不可预测的nonce;鲍勃必须在收到她的承诺后才给爱丽丝一个不可预测的挑战标量。还记得我们在本书其他部分使用的安全加密哈希函数的属性:当给定相同的输入时,它将始终产生相同的输出,但当给定不同的输入时,它将产生与随机数据无法区分的值。这使得爱丽丝可以选择她的私有nonce,推导出她的公共nonce,然后对公共nonce进行哈希以获得挑战标量。因为爱丽丝无法预测哈希函数的输出(挑战),并且对于相同的输入(nonce),它总是相同的,这确保了爱丽丝即使选择了nonce并对其进行哈希也能得到一个随机的挑战。我们不再需要鲍勃的互动。她只需发布她的公共nonce kG和标量s,每个数千个全节点(过去和未来)都可以对kG进行哈希以产生e,使用它来产生exG,然后验证sG == kG + exG。明确写出,验证方程变为sG == kG + hash(kG) × xG。

我们需要另一个东西来完成将交互式Schnorr身份验证协议转换为对比特币有用的数字签名协议。我们不仅希望爱丽丝证明她知道自己的私钥,还希望她能够承诺一个消息。具体来说,我们希望她承诺与她想要发送的比特币交易相关的数据。有了Fiat-Shamir变换,我们已经有了一种承诺,因此我们可以简单地使其额外承诺消息。现在,我们不仅使用hash(kG),还使用hash(kG || m)来承诺消息,其中||表示串联。

现在我们已经定义了schnorr签名协议的一个版本,但还有一件事情我们需要做来解决比特币特定的问题。在BIP32密钥派生中,如在第92页的“公共子密钥派生”中描述的,非硬化派生的算法将一个公钥和一个非秘密值相加,以产生一个派生的公钥。这意味着也可以将那个非秘密值添加到一个密钥的有效签名中,以产生一个相关密钥的签名。这个相关签名是有效的,但它不是由持有私钥的人授权的,这是一个重大的安全失败。为了保护BIP32非硬化派生,并且支持人们希望在schnorr签名之上构建的几个协议,比特币的schnorr签名版本,称为secp256k1的BIP340 schnorr签名,还承诺要使用的公钥,除了公共nonce和消息之外。这使得完整的承诺哈希(kG || xG || m)。 现在我们已经描述了BIP340 schnorr签名算法的每个部分,并解释了它为我们做了什么,我们可以定义该协议了。整数的乘法是模p执行的,表示操作的结果被数字p除(如在secp256k1标准中定义的)并且余数被使用。数字p非常大,但如果它是3并且操作的结果是5,则我们实际使用的数字是2(即,5除以3的余数为2)。 设置:爱丽丝选择一个大的随机数(x)作为她的私钥(直接选择或使用BIP32等协议从大的随机种子值确定性地生成私钥)。她使用secp256k1中定义的参数(参见第56页的“椭圆曲线密码学解释”)将生成器G乘以她的标量x,产生xG(她的公钥)。她将她的公钥提供给以后将验证她的比特币交易的所有人(例如,通过在交易输出中包含xG)。当她准备花费时,她开始生成她的签名:

  • Alice选择一个大的随机私用nonce k 并派生公共nonce kG。
  • 她选择她的消息 m(例如,交易数据)并生成挑战标量 e = hash(kG || xG || m)。
  • 她生成标量 s = k + ex。两个值 kG 和 s 是她的签名。她将这个签名提供给所有想要验证该签名的人;她还需要确保每个人都收到她的消息 m。在比特币中,这是通过将她的签名包含在她的支付交易的见证结构中,然后将该交易中继到完整节点来完成的。
  • 验证者(例如完整节点)使用 s 派生 sG,然后验证 sG == kG + hash(kG || xG || m) × xG。如果等式成立,则Alice证明了她知道自己的私钥 x(而没有泄露它),并承诺了消息 m(包含交易数据)。

Schnorr签名序列化

Schnorr签名的序列化包括两个值,kG 和 s。值 kG 是比特币椭圆曲线(称为 secp256k1)上的一个点,通常由两个32字节的坐标表示,例如 (x, y)。然而,只需要 x 坐标,因此只包含该值。当你在比特币的Schnorr签名中看到 kG 时,请注意它只是该点的 x 坐标。

值 s 是一个标量(意思是用来乘以其他数字的数字)。对于比特币的 secp256k1 曲线,它的长度永远不会超过32字节。

虽然 kG 和 s 可能有时是可以用较少于32字节表示的值,但它们很少会比32字节小得多,因此它们被序列化为两个32字节的值(即小于32字节的值具有前导零)。它们按照 kG 然后是 s 的顺序进行序列化,生成确切的64字节。

Taproot软分叉,也称为v1隔离见证,将Schnorr签名引入了比特币,并且截至目前,这是比特币中使用Schnorr签名的唯一方式。当与taproot keypath或scriptpath spending一起使用时,64字节的Schnorr签名被认为使用默认的签名哈希(sighash),即SIGHASH_ALL。如果使用了替代的sighash,或者如果花费者想要浪费空间来显式指定SIGHASH_ALL,那么将在签名后附加一个额外的字节,指定签名哈希,使签名成为65字节。 正

如我们将看到的那样,无论是64字节还是65字节,都比描述在“ECDSA签名序列化(DER)”中的序列化效率要高得多。

基于Schnorr的无脚本多重签名

在“Schnorr签名”页上描述的单签名Schnorr协议中,Alice使用签名(kG,s)公开证明了她对私钥的知识,这种情况下我们将其称为y。想象一下,如果Bob也有一个私钥(z),并且他愿意与Alice合作,以证明他们共同知道x = y + z,而不是他们中的任何一个向对方或其他人透露他们的私钥。让我们再次通过BIP340 schnorr签名协议进行说明。

特别注意:我们即将描述的简单协议是不安全的,原因我们将很快解释。我们只是使用它来演示Schnorr多签名的机制,然后再描述与之相关的被认为是安全的协议。

Alice和Bob需要推导出x的公钥,即xG。由于可以使用椭圆曲线运算将两个EC点相加,因此他们从Alice派生yG和Bob派生zG开始。然后他们将它们相加在一起,创建xG = yG + zG。

该协议中的点xG是它们的聚合公钥。为了创建签名,他们开始简单的多签名协议:

  1. 他们各自选择一个大的随机私有nonce,对于Alice是a,对于Bob是b。他们还各自派生相应的公共nonce aG 和 bG。他们一起生成聚合公共nonce kG = aG + bG。
  2. 他们就要签名的消息m达成一致(例如,一笔交易),每个人都生成一个挑战标量的副本:e = hash(kG || xG || m)。
  3. Alice生成标量 q = a + ey。Bob生成标量 r = b + ez。他们将这些标量相加得到 s = q + r。他们的签名是两个值 kG 和 s。
  4. 验证者使用正常的方程检查他们的公钥和签名:sG == kG + hash(kG || xG || m) × xG。

Alice和Bob已经证明他们知道他们私钥的总和,而不让其中任何一方向另一方或其他任何人透露他们的私钥。该协议可以扩展到任意数量的参与者(例如,百万人可以证明他们知道他们百万个不同密钥的总和)。

前述的协议存在几个安全问题。最值得注意的是,一方可能在承诺自己的公钥之前学习到其他方的公钥。例如,Alice诚实地生成了她的公钥yG,并与Bob分享了它。Bob使用zG - yG生成了他的公钥。当他们的两个密钥结合在一起时(yG + zG - yG),正负yG项会相互抵消,因此公钥只代表z的私钥(即Bob的私钥)。现在Bob可以在没有任何帮助的情况下创建有效的签名。这称为密钥取消攻击。

有各种方法可以解决密钥取消攻击。最简单的方案是要求每个参与者在与其他所有参与者分享有关该密钥的任何内容之前,都要承诺其公钥的一部分。例如,Alice和Bob各自对其公钥进行哈希处理,并将其摘要与彼此分享。当他们都拥有对方的摘要时,他们可以分享他们的密钥。他们各自检查对方的密钥是否哈希为先前提供的摘要,然后正常进行协议。这可以防止他们中的任何一个选择会抵消其他参与者密钥的公钥。然而,要正确实施此方案是很容易失败的,例如,将其用于未硬化的BIP32公钥派生。此外,这增加了参与者之间的通信额外步骤,在许多情况下可能不理想。已经提出了更复杂的方案来解决这些缺陷。

除了密钥取消攻击之外,还存在许多可能针对随机数的攻击。请回忆一下,随机数的目的是防止任何人利用对签名验证方程中其他值的了解来解出你的私钥,从而确定其值。为了有效实现这一点,你必须每次签署不同消息或更改其他签名参数时都使用不同的随机数。这些不同的随机数不能以任何方式相关联。对于多重签名,每个参与者都必须遵循这些规则,否则可能会危及其他参与者的安全性。此外,还需要防止取消和其他攻击。实现这些目标的不同协议会做出不同的权衡,因此没有一种单一的多重签名协议适用于所有情况。相反,我们将注意到 MuSig 协议族中的三个协议:

MuSig

也称为 MuSig1,此协议在签署过程中需要三轮通信,类似于我们刚刚描述的过程。MuSig1 的最大优势在于其简单性。

MuSig2

此协议只需要两轮通信,有时可以将其中一轮与密钥交换合并。这可以显着加快某些协议的签名速度,例如计划在 LN 中使用无脚本多重签名。MuSig2 在 BIP327 中有规定(截至本文撰写时,这是唯一一个具有 BIP 的无脚本多重签名协议)。

MuSig-DN

DN 代表确定性随机数,消除了一个称为重复会话攻击的问题。它无法与密钥交换结合使用,且实现起来比 MuSig 或 MuSig2 要复杂得多。

对于大多数应用程序来说,MuSig2 是撰写本文时可用的最佳多重签名协议。

基于Schnorr的无脚本门限签名

无脚本多重签名协议仅适用于 k-of-k 签名。每个具有成为聚合公钥一部分的部分公钥的人都必须为最终签名贡献部分签名和部分随机数。然而,有时候,参与者希望允许其中的子集进行签名,例如 t-of-k,其中门限(t)数量的参与者可以为由 k 个参与者构建的密钥进行签名。这种类型的签名称为门限签名。

我们在“脚本多重签名”第150页看到了基于脚本的门限签名。 但就像无脚本多重签名相比脚本多重签名节省空间并增加隐私一样,无脚本门限签名相比脚本门限签名也节省空间并增加隐私。对于不参与签名的任何人来说,无脚本门限签名看起来就像任何其他可能由单一签名用户或通过无脚本多重签名协议创建的签名一样。

\ 已知有各种方法可用于生成无脚本门限签名,其中最简单的是对之前创建的无脚本多重签名稍作修改。此协议还依赖于可验证秘密共享(其本身依赖于安全秘密共享)。

基本的秘密共享可以通过简单的分割来实现。Alice有一个秘密数字,她将其分成三个长度相等的部分并与Bob、Carol和Dan分享。这三个人可以按正确顺序组合他们收到的部分数字(称为份额),以重构Alice的秘密。更复杂的方案涉及Alice向每个份额添加一些附加信息,称为修正码,允许其中任意两个人恢复该数字。这种方案并不安全,因为每个份额都使其持有者对Alice的秘密具有部分了解,使得参与者比没有份额的非参与者更容易猜测Alice的秘密。

安全的秘密共享方案阻止参与者在组合最低门限数量的份额之前了解有关秘密的任何信息。例如,如果Alice希望Bob、Carol和Dan中的任意两人能够重构她的秘密,她可以选择门限值为2。已知的最佳安全秘密共享算法是Shamir的秘密共享方案,通常缩写为SSSS,以其发现者的名字命名,他也是我们在“Schnorr Signatures”第187页看到的Fiat-Shamir变换的发现者之一。

在一些密码协议中,例如我们正在努力实现的无脚本门限签名方案,Bob、Carol和Dan知道Alice是否正确遵循了协议的一面至关重要。他们需要知道她创建的所有份额都来自同一个秘密,她使用了她声称的门限值,并且她给了他们每个人一个不同的份额。一个可以实现所有这些目标,并且仍然是一个安全的秘密共享方案的协议是可验证秘密共享方案。

要了解多重签名和可验证秘密共享如何对Alice、Bob和Carol起作用,想象一下他们每个人都希望接收可以由其中任意两人支配的资金。他们按照“基于 Schnorr 的无脚本多重签名”第193页所描述的方式进行合作,以生成接受资金的常规多重签名公钥(k-of-k)。然后,每个参与者从他们的私钥派生出两个秘密份额,一个用于其他两个参与者中的每一个。这些份额允许他们中的任意两个人重构多重签名的原始部分私钥。每个参与者将他们的一个秘密份额分发给其他两个参与者,导致每个参与者存储自己的部分私钥和其他每个参与者的一个份额。随后,每个参与者验证他们收到的份额的真实性和唯一性,与其他参与者给出的份额进行比较。

之后,当(例如)Alice和Bob希望在没有Carol参与的情况下生成一个无脚本门限签名时,他们交换他们拥有的两个份额给Carol。这使他们能够重构出Carol的部分私钥。Alice和Bob也有他们的私钥,使他们能够创建具有所有三个必要密钥的无脚本多重签名。

换句话说,刚刚描述的无脚本门限签名方案与无脚本多重签名方案相同,只是门限数量的参与者有能力重构任何其他无法或不愿签名的参与者的部分私钥。 在考虑无脚本门限签名协议时,需要注意几点:\ \ 没有问责制

由于Alice和Bob重构了Carol的部分私钥,因此通过涉及Carol和没有涉及Carol的过程生成的无脚本多重签名之间基本上没有区别。即使Alice、Bob或Carol声称他们没有签名,他们也没有确凿的方法证明他们没有帮助生成签名。如果重要的是知道组中的哪些成员签署了,那么你将需要使用脚本。

操纵攻击

想象一下,Bob告诉Alice说Carol不可用,所以他们一起重构Carol的部分私钥。然后Bob告诉Carol说Alice不可用,所以他们一起重构了Alice的部分私钥。现在Bob拥有自己的部分私钥以及Alice和Carol的私钥,使他能够在没有他们参与的情况下花费资金。如果所有参与者都同意只使用一种方案进行通信,该方案允许其中任何一个人看到其他所有消息(例如,如果Bob告诉Alice说Carol不可用,那么Carol在开始与Bob合作之前能够看到该消息),那么可以解决此攻击。在撰写本书时,针对此问题的其他解决方案,可能是更健壮的解决方案,正在进行研究。

尽管多位比特币贡献者已经对该主题进行了大量研究,并且我们预计在本书出版后将会有经过同行评审的解决方案问世,但尚未提出任何作为BIP的无脚本门限签名协议。

ECDSA签名

很不幸对于比特币和许多其他应用的未来发展,克劳斯·施纳尔申请了他发现的算法的专利,并阻止了它在开放标准和开源软件中的使用近20年。在1990年代初被禁止使用施纳尔签名方案的密码学家们开发了一种另一种构造,称为数字签名算法(DSA),其中包括一种适用于椭圆曲线的版本,称为ECDSA。

\ ECDSA方案以及建议曲线的标准参数在比特币开发于2007年之前已被广泛实现于加密库中。这几乎可以肯定是为什么ECDSA是比特币从首个发布版本直到2021年的taproot软分叉激活之前唯一支持的数字签名协议。ECDSA至今仍然支持所有非taproot交易。与施纳尔签名相比,一些不同之处包括:

更复杂

正如我们将看到的,ECDSA需要更多的操作来创建或验证签名,而不只是施纳尔签名协议。从实现的角度来看,它并没有显著的更复杂性,但这种额外的复杂性使ECDSA的灵活性更差、性能更差,而且更难以证明其安全性。

安全性难以证明

交互式施纳尔签名识别协议仅依赖于椭圆曲线离散对数问题(ECDLP)的强度。比特币中使用的非交互式身份验证协议还依赖于随机预言模型(ROM)。然而,ECDSA的额外复杂性阻碍了对其安全性的完整证明的发布(据我们所知)。我们不是加密算法的证明专家,但在30年之后,ECDSA可能只需要与施纳尔相同的两个假设来证明其安全性似乎不太可能。

非线性

ECDSA签名不能轻松地组合成无脚本多重签名,也不能用于相关的高级应用,比如多方签名适配器。虽然有针对这个问题的解决方法,但它们涉及额外的复杂性,会显著减慢操作速度,并且在某些情况下可能导致软件意外泄露私钥。

ECDSA算法

让我们来看看ECDSA的数学原理。签名是通过一个数学函数Fsig生成的,它产生由两个值组成的签名。在ECDSA中,这两个值是R和s。

签名算法首先生成一个私密nonce(k),并从中派生一个公共nonce(K)。数字签名的R值然后是nonce K的x坐标。

然后,算法计算签名的s值。就像我们在schnorr签名中所做的那样,涉及整数的操作都是模p进行的:

$$ s = k^{-1}(Hash(m)+x*R) $$

其中:

  • k 是私密nonce
  • R 是公共nonce的x坐标
  • x 是Alice的私钥
  • m 是消息(交易数据)

验证是签名生成函数的逆函数,使用R、s值和公钥计算一个值K,它是椭圆曲线上的一个点(在签名创建中使用的公共nonce):

$$ K = s^{-1} Hash(m) G + s^{-1} R X $$

其中:

  • R 和 s 是签名值
  • X 是 Alice 的公钥
  • m 是消息(被签名的交易数据)
  • G 是椭圆曲线的生成点

如果计算得到的点 K 的 x 坐标等于 R,则验证者可以得出签名是有效的结论。

注意:ECDSA 的数学过程相当复杂;本书的范围之外无法完整解释。有许多在线优秀指南会逐步带领你了解它:搜索“ECDSA 解释”。

ECDSA签名序列化(DER)

让我们来看下面的 DER 编码签名:

3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204 b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e381301

该签名是由签名者生成的 R 和 s 值的序列化字节流,用于证明对用于花费输出的私钥的控制权。序列化格式包括以下九个元素:

  • 0x30,表示 DER 序列的开始
  • 0x45,序列的长度(69 字节)
  • 0x02,后跟一个整数值
  • 0x21,整数的长度(33 字节)
  • R,00884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb
  • 0x02,后跟另一个整数
  • 0x20,整数的长度(32 字节)
  • S,4b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813
  • 一个后缀(0x01),表示所使用的哈希类型(SIGHASH_ALL)

在签名中随机性的重要性

正如我们在“Schnorr Signatures”(第187页)和“ECDSA Signatures”(第197页)中所看到的,签名生成算法使用一个随机数k作为私有/公有nonce对的基础。k的值并不重要,只要它是随机的。如果来自同一个私钥的签名使用了私有nonce k并且对不同的消息(交易)进行签名,那么签名私钥可以被任何人计算出来。在签名算法中重复使用相同的值k会导致私钥曝光!

特别注意: 如果在两笔不同交易上的签名算法中使用相同的值k,那么私钥就可以被计算并暴露给世界!

这不仅仅是一种理论可能性。我们曾经看到这个问题导致比特币中几种不同实现的交易签名算法暴露私钥。由于意外重用k值,人们的资金被盗。重用k值的最常见原因是随机数生成器未正确初始化。

为了避免这种漏洞,业界最佳实践是不仅使用熵进行种子初始化的随机数生成器生成k,而是使用部分由交易数据本身加上正在使用的私钥进行种子初始化的过程。这确保了每个交易产生不同的k。确定k的行业标准算法是在RFC6979中定义的,由互联网工程任务组发布。对于schnorr签名,BIP340建议了一个默认的签名算法。

\ BIP340和RFC6979可以完全确定性地生成k,这意味着相同的交易数据将始终产生相同的k。许多钱包采用这种方法,因为这样可以轻松编写测试来验证其关键安全签名代码是否正确生成k值。BIP340和RFC6979都允许在计算中包含附加数据。如果该数据是熵,则即使签署了完全相同的交易数据,也会产生不同的k。这可以增加对侧信道和故障注入攻击的防护。

如果你正在实现比特币交易签名算法,你必须使用BIP340、RFC6979或类似的算法,以确保为每个交易生成不同的k。

\

隔离见证的新签名算法

\ 比特币交易中的签名是应用于承诺哈希上的,该哈希是从交易数据计算而来的,锁定了指示签署者对这些值的承诺的特定部分数据。例如,在简单的SIGHASH_ALL类型签名中,承诺哈希包括所有输入和输出。

不幸的是,遗留承诺哈希的计算方式引入了一种可能性,即验证签名的节点可能被迫执行大量的哈希计算。具体而言,哈希操作与交易中的输入数量呈大致二次方增长。因此,攻击者可以创建一个包含大量签名操作的交易,导致整个比特币网络必须执行数百甚至数千次的哈希操作来验证该交易。

隔离见证提供了解决这个问题的机会,方法是改变承诺哈希的计算方式。对于隔离见证版本0的见证程序,签名验证使用了BIP143规定的改进的承诺哈希算法。

新算法允许哈希操作的数量以更渐进的O(n)方式增加到签名操作的数量,减少了使用过于复杂交易创建拒绝服务攻击的机会。

在本章中,我们了解了比特币的Schnorr和ECDSA签名。这解释了完整节点如何验证交易,以确保只有控制比特币接收的密钥的人才能花费这些比特币。我们还研究了签名的几个高级应用,例如可以用于提高比特币效率和隐私的无脚本多签名和无脚本阈值签名。在过去的几章中,我们学会了如何创建交易,如何使用授权和认证来保护它们,并学会了如何对其进行签名。接下来,我们将学习如何通过向我们创建的交易添加费用来鼓励矿工确认它们。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论