上一篇博客的最后,我们讲到 AddAllInputScripts
函数体中针对每一个非witness
的input
调用了SignTxOutput
函数。该函数主要用来对当前input
构造解锁脚本。 解锁脚本构造的最关键部分在于签名的构造,因而该函数中是用了Sign
这个词。 此外,SignTxOutput
函数名中虽然写的是output
,但这个output
是UTXO
的意思,也就是当前交易的input
。
在继续本篇博客的阅读之前,强烈建议读者先阅读关于签名机制的三篇博客签名机制(1) —— 基础知识, 签名机制(2) ——签名流程和签名机制(3) —— 源码分析,其中介绍了各种签名哈希类型下message
的内容。
IV. 构造解锁脚本
A. SignTxOutput
函数
SignTxOutput
函数的主题内容如下所示:
1 | // SignTxOutput [sign.go] |
ScriptHashTy
类型;2)其他类。 其他类调用了sign
函数一次(L437行),scripthash
类则调用了sign
函数两次(L437行和L445行)。 此外,L461行调用mergeScripts
函数将得到的sigScript
和previousScript
进行合并。老实说,该函数的实现我没看懂(/捂脸哭)。看了一圈注释,好像也没写清楚,而且这个previousScript
貌似一般情况下为空值。因此,这里我们就不对mergeScripts
函数展开讲解了。
以下分IV.A和IV.B两小节分别介绍其他类中的PubKeyHashTy
类型和ScriptHashTy
类型。
B. PubKeyHashTy
类型的解锁脚本
如SignTxOutput
中的L437行代码所示,对于PubKeyHashTy
类型的脚本,主要通过调用sign
函数构造解锁脚本。 sign
函数的主体内容如下所示:
1 | // SignTxOutput [sign.go] -> sign |
sign
首先在L160行调用ExtractPkScriptAddrs
函数对锁定脚本(subScript
)进行解析,得到脚本类型(class
)和地址(addresses
). 然后根据脚本类型,进行不同方式的处理。本小节,我们主要关注L181行的PubKeyHashTy
类型。 对于该类型,首先在L183行调用GetKey
函数获取到本地数据库保存的私钥(key
),然后在L188行调用SignatureScript
函数构造解锁脚本。 以下分别介绍ExtractPkScriptAddrs
函数和SignatureScript
函数。
1. ExtractPkScriptAddrs
函数
ExtractPkScriptAddrs
函数的主体内容如下所示:
1 | // SignTxOutput [sign.go] -> sign -> ExtractPkScriptAddrs [standard.go] |
pkScript
是一个字节码切片,首先在L530行调用parseScript
函数将字节码切片解析成操作码切片(pops
)。 基于pops
,可以调用typeOfScript
函数判断出锁定脚本的类型 (L535行)。 针对PubKeyHashTy
类型的脚本,调用NewAddressPubKeyHash
函数生成所需的地址(addr
). 以下分别介绍typeOfScript
函数和NewAddressPubKeyHash
函数
1) typeOfScript
函数
typeOfScript
函数主要是一些判断语句,如下所示:
1 | // SignTxOutput [sign.go] -> sign -> ExtractPkScriptAddrs [standard.go] -> typeOfScript |
isPubkey
,isPubkeyHash
等)主要是对其中的特定操作码进行判断。以isPubkeyHash
函数为例,其主体代码如下所示: 1 | // SignTxOutput [sign.go] -> sign -> ExtractPkScriptAddrs [standard.go] -> typeOfScript -> isPubkeyHash |
2) NewAddressPubKeyHash
函数
NewAddressPubKeyHash
函数如下所示。
1 | // SignTxOutput [sign.go] -> sign -> ExtractPkScriptAddrs [standard.go] -> |
NewAddressPubKeyHash
函数主要是调用了newAddressPubKeyHash
函数,后者的主体如下: 1 | // SignTxOutput [sign.go] -> sign -> ExtractPkScriptAddrs [standard.go] -> |
newAddressPubKeyHash
函数也比较简单,主要是对锁定脚本中的hash
值做了一些判断和封装。
2. SignatureScript函数
回到IV.B小节sign
函数的L188行。其主要是调用了SignatureScript
函数针对当前的input
构造解锁脚本。 SignatureScript
函数主体如下所示:
1 | // SignTxOutput [sign.go] -> sign -> SignatureScript |
SignatureScript
在L98行调用RawTxInSignature
函数对当前input
进行签名,得到签名数据(sig
)。 在L103行到L109行构建公钥数据(pkData
). L111行将签名数据和公钥数据相结合,即得到解锁脚本,其与我们熟悉的解锁脚本形式(如下所示)是完全一致的。
<sig> <public key>
以下我们来关注RawTxInSignature
函数
1) RawTxInSignature
函数
1 | // SignTxOutput [sign.go] -> sign -> SignatureScript -> RawTxInSignature |
我们知道,构建签名的过程一般包含两个阶段:1)构建用于签名的消息(message
);2)使用私钥对该消息进行签名。 L77行即调用CalcSignatureHash
来生成message
。 CalcSignatureHash
函数内部主要调用了calcSignatureHash
函数,而calcSignatureHash
函数是比较复杂的。 如本篇博客开篇所述,笔者已经写了三篇博客来介绍这个message
的构建过程,其中第三篇博客签名机制(3) —— 源码分析便详细分析了calcSignatureHash
函数的实现细节。感兴趣的读者,可以去看看。 没有时间去看这几篇博客的读者,只需记住CalcSignatureHash
和calcSignatureHash
函数的功能:即生成签名所需的message
.
L81行调用Sign
函数对该message
进行签名,得到签名结果signature
。
L86行将signature
序列化并添加上签名哈希类型返回,其中签名哈希类型也在开篇推荐的三篇博客中予以了介绍。
C. ScriptHashTy
类型的解锁脚本
在IV.B小节中,我们介绍了PubKeyHashTy
类型解锁脚本的构造过程。本小节,我们介绍ScriptHashTy
类型解锁脚本的构造。 回顾IV.A小节中的SignTxOutput
函数,当处理PubKeyHashTy
类型时,只执行了L437行的sign
调用;而这里处理ScriptHashTy
类型时,除了执行L437行的sign
调用,还需要执行L445行的sign
调用。
1. 第一次执行sign
函数
sign
函数主体如下所示:
1 | // SignTxOutput [sign.go] -> sign |
ExtractPkScriptAddrs
函数已经在上一篇博客中进行过介绍。在该函数中,ScriptHashTy
类型的处理流程和PubKeyHashTy
类型相似,这里略过。
L195行至L201行对ScriptHashTy
类型进行处理,该处的处理流程非常简单。L196行调用GetScript
函数从本地数据库中读取地址所对应的脚本 (script
).
此处,我们需要岔开一笔来介绍一下ScriptHashTy
类型的脚本格式。 ScriptHashTy
类型的锁定脚本(locking script
)和解锁脚本(unlocking script
)格式分别如下所示:
Locking script: HASH160 <redeem script hash> EQUALVERIFY
Unlocking script: <sigscript> <redeem script>
L196行读取的script
即为unlocking script
中的redeem script
. 获得该script
后,再对该script
进行签名,因而需要重新调用一次sign
函数。 因此,在sign
函数中处理ScriptHashTy
类型时,采取了直接返回该script
的方式。
2. 第二次执行sign
函数
回顾IV.A小节中的SignTxOutput
函数,其L445行对IV.C.1小节查询得到的script
(也即返回值sigScript
)进行签名。 此时的script
可以看作是普通的其他类解锁脚本 (如PubKeyHashTy
类型),因而在L445行进入到sign
函数后,完全按照IV.B小节的流程进行。
签名得到realSigScript
脚本后,在SignTxOutput
函数的L452至L456对realSigScript
脚本和IV.C.1小节得到的sigScript
进行拼接,最后得到unlocking script
格式的解锁脚本。