6.5 数字签名(ECDSA)

到目前为止,我们还没有深入了解“数字签名”的细节。在本节中,我们将研究数字签名的工作原理,以及如何在不出示私钥的情况下提供私钥的所有权证明。

比特币中使用的数字签名算法是椭圆曲线数字签名算法(Elliptic Curve Digital Signature Algorithm)ECDSA。 ECDSA是基于椭圆曲线私钥/公钥对用于数字签名的算法,如【4.1.5 椭圆曲线加密(Elliptic Curve Cryptography)解释】所述。ECDSA用于脚本函数OP_CHECKSIG,OP_CHECKSIGVERIFY,OP_CHECKMULTISIG和OP_CHECKMULTISIGVERIFY。每当在锁定脚本中看到这些时,解锁脚本都必须包含一个ECDSA签名。

数字签名在比特币中有三种用途(请参阅下面的侧栏)。第一,签名证明私钥的所有者,即资金所有者,有权花费这些资金。第二,授权证明是不可拒绝的(不可否认性)。第三,签名证明交易(或交易的具体部分)在签字之后没有也不能被任何人修改。

请注意,交易的每笔输入都是独立签名的。这一点至关重要,因为不管是签名还是输入都不必属于同一“所有者”或者被其使用。事实上,一个名为 “CoinJoin” 的特定交易方案就使用这个特性来创建多方私密交易。

注意: 每个交易输入和它包含的任何签名完全独立于任何其他输入或签名。 多方可以协作构建交易,但是每方只能签名一个输入。

维基百科对 “数字签名 ”的定义:

数字签名是用于证明数字消息或文档的真实性的数学方案。 有效的数字签名使收件人有理由相信消息是由已知的发件人创建的(身份验证),发件人不能否认已发送的消息(不可否认性),并且消息在传输过程中没有更改(完整性)。

来源: https://en.wikipedia.org/wiki/Digital_signature*

6.5.1 数字签名如何工作

数字签名是一种数学方案mathematical scheme,由两部分组成的:第一部分是使用私钥(签名密钥)从消息(交易)创建签名的算法; 第二部分是允许任何人给定依据给定的消息和公钥验证签名的算法。

6.5.1.1 创建数字签名

在比特币的ECDSA算法的实现中,被签名的“消息”是交易,或更确切地说是交易中特定数据子集的哈希值(参见【6.5.3签名哈希类型(SIGHASH)】)。签名密钥是用户的私钥,结果就是签名:

((Sig = F{sig}(F{hash}(m), dA)))这里的:

  • dA 是签名私钥
  • m 是交易(或其部分)
  • Fhash 是散列函数
  • Fsig 是签名算法
  • Sig 是结果签名

ECDSA数学运算的更多细节可以在【6.5.4 ECDSA数学】找到。

函数Fsig 产生由两个值组成的签名Sig,通常称为R和S:

  1. Sig = (R, S)

现在已经计算了两个值R和S,它们就使用一种称为可分辨编码规则Distinguished Encoding RulesDER的国际标准编码方案,序列化为字节流。

6.5.1.2 签名序列化(DER)

我们再来看看Alice创建的交易。 在交易输入中有一个解锁脚本,其中包含Alice的钱包中的以下DER编码签名:

  1. 3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e381301

该签名是Alice的钱包生成的R和S值的序列化字节流,证明她拥有授权花费该输出的私钥。 序列化格式包含以下9个元素:

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

看看您是否可以使用此列表解码 Alice 的序列化(DER编码)签名。 重要的数字是R和S; 数据的其余部分是DER编码方案的一部分。

6.5.2 验证签名

要验证签名,必须有签名(RS)、序列化交易和公钥(对应于用于创建签名的私钥)。本质上,签名的验证意味着“只有生成此公钥的私钥的所有者,才能在此交易上产生此签名。”

签名验证算法采用消息(交易或其部分的哈希值)、签名者的公钥和签名(R和S值),如果签名对该消息和公钥有效,则返回 TRUE 值。

6.5.3 签名哈希类型(SIGHASH)

数字签名被应用于消息,在比特币中,就是交易本身。签名意味着签名者对特定交易数据的承诺commitment。最简单的形式是,签名应用于整个交易,从而承诺所有输入,输出和其他交易字段。但是,一个签名也可以只承诺交易数据的子集,这对于我们将在本节中看到的许多场景是有用的。

比特币签名使用 SIGHASH 标志显示,交易数据的哪一部分包含在私钥签名的哈希中。 SIGHASH 标志是附加到签名的单个字节。每个签名都有一个SIGHASH标志,该标志在随输入不同而不同。一笔交易如果有三个签名输入,就会有不同SIGHASH标志的三个签名,每个签名签署(承诺)交易的不同部分。

记住,每个输入可能在其解锁脚本中包含一个签名。因此,包含多个输入的交易可以拥有具有不同SIGHASH标志的多个签名,这些标志在每个输入中承诺交易的不同部分。还要注意,比特币交易可能包含来自不同“所有者”的输入,他们在部分构建(和无效)的交易中可能仅签名一个输入,需要与他人协作收集所有必要的签名后才能使交易生效。许多SIGHSASH标志类型,只有考虑到由许多参与者在比特币网络之外共同协作去更新仅部分签名了的交易,才具有意义。

有三个SIGHASH标志:ALL,NONE和SINGLE,如下表6-3所示。

表6-3 SIGHASH类型和意义

SIGHASH flag Value Description
ALL 0x01 签名应用到所有输出输入
NONE 0x02 签名只应用到所有输入,不包括任何输出
SINGLE 0x03 签名应用到所有输入和与签名输入具有相同索引号的那个输出

另外还有一个修饰符标志SIGHASH_ANYONECANPAY,它可以与前面的每个标志组合使用。 当设置ANYONECANPAY时,只有一个输入被签名,其余的(及其序列号)保持开放以进行修改。 ANYONECANPAY的值为0x80,并通过按位OR运算,得到如下表6-4所示的组合标志:

表6-4 带修饰符的SIGHASH类型及其含义

SIGHASH flag Value Description
ALL|ANYONECANPAY 0x81 签名应用到一个输入和所有输出
NONE|ANYONECANPAY 0x82 签名应用到一个输入,不包括任何输出
SINGLE|ANYONECANPAY 0x83 签名应用到一个输入和具有相同索引号的输出

这些标志组合总结如下图6-7。

图6-7

图6-7 不同sighash组合的总结

签名和验证期间应用sighash标志的方式是生成交易的副本并截断其中的某些字段(设置为零长度并清空)。继而交易被序列化,SIGHASH标志被添加到序列化交易的结尾,并将结果哈希,得到的哈希值即是被签名的“消息”。 根据使用的SIGHASH标志,交易的不同部分会被截断。 所得到的哈希值取决于交易中数据的不同子集。 在进行哈希前的最后一步,借助于包含SIGHASH,签名提交了SIGHASH类型,因此不能再更改(例如,被矿工)。

小贴士: 所有SIGHASH类型都签署到交易的nLocktime字段(请参阅【7.5.1交易锁定时间(nLocktime)】)。 此外,SIGHASH类型本身在交易签名之前就被附加到交易中了,因此一旦签名就不能再修改它。

在Alice的交易(参见【6.5.1.2 签名序列化(DER)】)的例子中,我们看到DER编码签名的最后一部分是01,这是SIGHASH_ALL标志。这会导致锁定交易数据,因此Alice的签名承诺的是所有的输入和输出状态。 这是最常见的签名形式。

我们来看看其他一些SIGHASH类型,以及如何在实践中使用它们:

ALL | ANYONECANPAY

这种结构可以用来做“众筹”交易,试图筹集资金的人可以用单笔输出来构建一个交易,单笔输出将“目标”金额付给众筹发起人。这样的交易显然是无效的,因为它没有输入。但是现在其他人可以添加自己的输入来修改它,实现捐赠。他们用ALL | ANYONECANPAY签名自己的输入,除非收集到足够的输入以达到输出的价值,否则交易无效,每次捐赠是一项“抵押”,直到募集到整个目标金额,筹款人才能收取。

NONE

该结构可用于创建特定金额的“不记名支票”或“空白支票”。它对输入进行承诺,但允许更改输出锁定脚本。任何人都可以将自己的比特币地址写入输出锁定脚本并兑换交易。不过,输出值本身被签名锁定。

NONE | ANYONECANPAY

这种结构可以用来建造一个“吸尘器”。用户的钱包中拥有微小UTXO,因为微小UTXO的面值还不够支付交易费,所以用户无法花费这些费用。借助这种类型的签名,微小UTXO可以捐赠给任何人,收集起来随时消费。

有一些修改或扩展SIGHASH系统的建议。其中一个是Blockstream的Glenn Willen作为Elements项目的一部分提议的Bitmask Sighash模式。其目的是创建一个灵活的SIGHASH类型的替代方案,允许使用“输入和输出的任意的,矿工可以重写的位掩码”来表示“更复杂的合同预承诺方案,例如分布式资产交易所中有变更的已签名的报价”。

注释: 您不会在用户的钱包应用程序中看到SIGHASH标志作为一个功能呈现。 有极少数例外,钱包会构建P2PKH脚本,并使用SIGHASH_ALL标志进行签名。 要使用不同的SIGHASH标志,必须编写软件来构造和签名交易。 更重要的是,SIGHASH标志可以被特殊用途的比特币应用程序使用,实现新的用途。

6.5.4 ECDSA数学

如前所述,签名由数学函数Fsig 创建,该函数生成了由RS两个值组成的签名。在本节中,我们将更详细查看Fsig 函数。

签名算法首先生成一个 短暂的ephemeral(临时的)私公钥对。 该临时密钥对用于在涉及签名私钥和交易哈希的转换之后计算RS值。

临时密钥对基于随机数k,后者用作临时私钥。 从k,生成相应的临时公钥P(以P = k G计算,与派生比特币公钥相同);参见【4.1.4 公钥】部分)。数字签名的R值则是临时公钥P*的x坐标。

在此基础上,算法计算签名的S值,使得:

S = k-1 (Hash(m) + dA * R) mod n

其中:

  • k是临时私钥
  • R是临时公钥的x坐标
  • dA是签名私钥
  • m是交易数据
  • n是椭圆曲线的阶

验证是签名生成函数的倒数,使用R,S值和公钥来计算值P,该值是椭圆曲线上的一个点(签名创建时使用的临时公钥):

P = S-1 Hash(m) G + S-1 R Qa

其中:

  • RS是签名值
  • Qa是Alice的公钥
  • m是签名的交易数据
  • G是椭圆曲线生成点

如果计算出的点Px坐标等于R,则验证者就得出结论,签名是有效的。

请注意,在验证签名时,私钥既不知道也不会泄露。

小贴士: ECDSA是一个相当复杂的数学问题,完整的解释超出了本书的范围。 网上有一些很棒的指南会指导你一步步学习:搜索“ECDSA解释”或尝试这个:http://bit.ly/2r0HhGB。

6.5.5 随机性在签名中的重要性

如我们在【6.5.4 ECDSA数学】中所看到的,签名生成算法使用随机密钥k作为临时私有公钥对的基础。 k 的值不重要,只要它是随机的。如果使用相同的k值用在不同的消息(交易)上生成两个签名,则任何人都可以计算签名私钥。在签名算法中使用相同的 k 值会导致私钥泄露!

警告 如果在两个不同的交易中,签名算法使用相同的 k值,则私钥就能被计算出来,暴露给全世界!

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

为了避免此漏洞,行业最佳实践是,不使用以熵为种子的随机数生成器生成k,而是使用以交易数据本身为种子的确定性随机流程。这确保每个交易产生不同的 k 值。在互联网工程任务组(Internet Engineering Task Force)发布的RFC 6979中定义了 k 值的确定性初始化的行业标准算法。

如果您正在部署在比特币中签名交易的算法,则必须使用RFC 6979或类似的确定性随机算法来确保为每个交易生成不同的 k 值。