比特币交易脚本签名生成,解锁UTXO的密钥与数学艺术
摘要:比特币作为首个成功的去中心化数字货币,其安全性和交易有效性很大程度上依赖于一种精巧的机制——交易脚本签名生成,这个过程并非简单的“加密签名”,而是一套遵循特定规则、结合加密数学与脚本逻辑的复杂交互,确...
比特币作为首个成功的去中心化数字货币,其安全性和交易有效性很大程度上依赖于一种精巧的机制——交易脚本签名生成,这个过程并非简单的“加密签名”,而是一套遵循特定规则、结合加密数学与脚本逻辑的复杂交互,确保只有真正拥有资产所有权的用户才能花费其比特币,理解这一过程,是深入掌握比特币核心原理的关键。
脚本签名生成的背景:UTXO 与脚本锁
比特币交易的本质是UTXO(未花费交易输出)的转移,每个UTXO都附带一个“锁定脚本”(ScriptPubKey),它像一把锁,规定了未来谁能花费这笔UTXO,最常见的P2PKH(Pay-to-Public-Key-Hash)锁定脚本会要求:提供与某个公哈希对应的公钥,并提供该公钥对应的有效签名。
而交易中用于花费UTXO的部分则称为“解锁脚本”(ScriptSig),它试图提供打开这把锁的“钥匙”,脚本签名生成,正是构建这把“钥匙”中最核心、最关键的部分——签名的过程。
签名生成的核心目标:证明所有权与授权花费
脚本签名生成的核心目标有两个:
- 证明私钥所有权:签名者必须拥有与锁定脚本中指定的公钥(或公钥哈希)相对应的私钥。
- 授权特定交易:签名必须精确针对当前要花费的UTXO所在的特定交易进行生成,防止签名被重放用于其他交易。
为了实现这两个目标,比特币采用了椭圆曲线数字签名算法(ECDSA),基于其底层的椭圆曲线密码学(SECP256k1)。
签名生成的详细步骤:从交易数据到数字指纹
一个完整的比特币交易脚本签名生成过程(以P2PKH为例)通常包含以下关键步骤:
-
构建待签名数据(Sighash):
- 这是签名生成的基础,签名并非对交易内容本身直接签名,而是对一个经过特定格式化、哈希处理后的“交易数据摘要”进行签名。
- 构建原始交易副本:创建一个包含所有输入、输出、序列号、版本号等完整交易信息的副本。
- 清空输入脚本:将每个输入的
ScriptSig字段清空(填充为0字节)。 - 设置当前输入的序列号:在清空脚本后,将当前正在签名的输入的
sequence字段设置回其原始值(在签名过程中,它被临时清空)。 - 添加Sighash类型标志:在交易副本的末尾,添加一个4字节的
nHashType标志(如SIGHASH_ALL,表示签名覆盖所有输入和输出;SIGHASH_SINGLE等),这个标志定义了签名的范围和有效性。 - 双重SHA-256哈希:对上述构建好的交易副本进行两次SHA-256哈希运算,得到一个32字节的哈希值,这就是最终的待签名数据(Sighash),它唯一地标识了当前输入要花费的特定UTXO以及交易的整体结构(由Sighash类型决定)。
-
使用私钥对Sighash进行签名:
- 签名者使用其与锁定脚本目标公钥对应的私钥,对上一步生成的32字节Sighash值执行ECDSA签名运算。
- ECDSA签名生成两个值:
r:一个大的整数,代表签名的一部分。s:另一个大的整数,代表签名的另一部分。
- 在比特币中,这两个值被序列化并组合成一个紧凑的二进制格式,称为DER编码签名,通常还会添加一个前缀字节(0x30)标识DER格式,并包含
r和s的长度信息。
-
构建完整的解锁脚本(ScriptSig):
- 仅仅有签名是不够的,因为验证节点需要知道这个签名是用哪个公钥生成的,才能验证其有效性。
- 解锁脚本(
ScriptSig)通常包含两部分:- 签名(Signature):上一步生成的DER编码ECDSA签名。注意:为了满足脚本执行要求,签名中通常还会嵌入
Sighash类型标志(作为最后一个字节)。 - 公钥(Public Key):与签名所用的私钥相对应的原始公钥(未压缩或压缩格式)。
- 签名(Signature):上一步生成的DER编码ECDSA签名。注意:为了满足脚本执行要求,签名中通常还会嵌入
- 对于P2PKH,解锁脚本的典型结构就是:
<Signature> <Public Key>。
签名验证:脚本执行与锁的开启
签名生成的最终目的是为了验证,当一笔交易被广播到比特币网络后,每个验证节点都会执行以下操作:
- 提取解锁脚本:获取当前输入的
ScriptSig(即<Signature> <Public Key>)。 - 提取锁定脚本:获取当前输入要花费的UTXO所附加的
ScriptPubKey(如P2PKH:<OP_DUP> <OP_HASH160> <PubKeyHash> <OP_EQUALVERIFY> <OP_CHECKSIG>)。 - 组合脚本并执行:将
ScriptSig和ScriptPubKey按特定顺序连接起来(通常是ScriptSig+ScriptPubKey),形成一个完整的脚本,一个虚拟机(Script Interpreter)会逐条执行这些操作码:ScriptSig中的<Signature>和<Public Key>被压入栈中。ScriptPubKey中的操作码开始执行:OP_DUP:复制栈顶元素(公钥)。OP_HASH160:对复制的公钥进行RIPEMD160(SHA256())哈希,得到PubKeyHash。OP_EQUALVERIFY:将栈顶的PubKeyHash与ScriptPubKey中硬编码的<PubKeyHash>进行比较(OP_EQUAL),如果不相等则验证失败;如果相等,则继续执行。OP_CHECKSIG:这是验证的核心!它执行以下操作:- 从栈中弹出两个元素:顶部是
<Signature>,下面是<Public Key>。 - 使用
<Public Key>和<Signature>对该输入对应的Sighash(使用相同的Sighash类型重新构建)进行ECDSA验证。 - 如果验证通过(签名确实是由该公钥对应的私钥对正确的Sighash生成的),则返回
TRUE(1)压入栈顶;否则返回FALSE(0)。
- 从栈中弹出两个元素:顶部是
- 脚本执行结果栈顶必须是
TRUE(非零),交易输入才被视为有效。
签名类型(Sighash)的重要性
nHashType标志在签名生成和验证中扮演着至关重要的角色,它定义了签名的范围:
- SIGHASH_ALL (0x01):最常用,签名覆盖所有输入和所有输出,这意味着任何修改交易输入(添加/删除/修改)或输出(修改金额/接收者)都会导致签名无效,提供者必须完全信任接收者不会改变交易的其他部分。
- SIGHASH_NONE (0x02):签名覆盖所有输入,但不覆盖任何输出(输出部分被清零),这意味着接收者可以自由修改输出金额和接收者,但输入列表不能改变,常用于需要接收者指定输出金额的场景(如通道)。
- SIGHASH_SINGLE (0x03):签名覆盖第N个输入和第N个输出(输入和输出数量必须相同),每个输入单独签名,只对应其同序号的输出,允许修改其他输入的输出,但不能改变当前输入对应的输出。
- SIGHASH_ANYONECANPAY (0x80):可以与上述类型组合(如
SIGHASH_ALL | SIGHASH_ANYONECANPAY),表示签名仅覆盖当前输入,其他输入可以被任意添加或移除,这在需要多方共同签名一个交易,但各自只负责自己输入的场景中非常有用。
进阶:更复杂的脚本与签名(如P2SH, P2WPKH)
- P2SH (Pay-to-ScriptHash):用户发送到哈希值(代表一个赎回脚本),签名生成和解锁过程需要用户提供完整的赎回脚本以及其中要求的签名和其他数据(如多重签名的多个签名),签名数据是赎回脚本的一部分。
- SegWit (P2WPKH, P2WSH):隔离见证引入了签名数据的分离,签名生成过程本身类似,但签名数据(以及公钥)被放在交易输入的
witness字段中,而不是ScriptSig中。ScriptSig变得非常简单(对于P2WPKH,通常只是<0>),而ScriptPubKey则指向见证程序(如公
