btcd源码解析 —— 签名机制(3) —— 源码分析

5. 签名流程——源码层

在开始本篇博客之前,我们首先回顾一下前两篇博客的内容
第一篇博客介绍了比特币签名相关的基础知识,第二篇博客博客介绍了签名的基本原理。本篇博客将从btcd的源码层面分析签名的具体流程。
源码分析所针对的btcd版本为: ed77733ec07dfc8a513741138419b8d9d3de9d2d
签名流程中message的构建主要是由calcSignatureHash函数实现的,其主体代码如下所示:

// calcSignatureHash [script.go]
func calcSignatureHash(script []parsedOpcode, hashType SigHashType, tx *wire.MsgTx, 
	idx int) []byte {
    ...
    if hashType&sigHashMask == SigHashSingle && idx >= len(tx.TxOut) {         // L620
        var hash chainhash.Hash      
        hash[0] = 0x01      
        return hash[:]
    }
    ...
    script = removeOpcode(script, OP_CODESEPARATOR)                            // L627
    ...
    txCopy := shallowCopyTx(tx)                                                // L631
    for i := range txCopy.TxIn {                                               // L632
        if i == idx {                                                          // L633
            ...         
            sigScript, _ := unparseScript(script)                              // L636
            txCopy.TxIn[idx].SignatureScript = sigScript                       // L637
        } else {            
            txCopy.TxIn[i].SignatureScript = nil                               // L639
        }
    }
    
    switch hashType & sigHashMask {                                            // L643
    case SigHashNone:      
        txCopy.TxOut = txCopy.TxOut[0:0] // Empty slice.                       // L645   
        for i := range txCopy.TxIn {                                           // L646
            if i != idx {                  
                txCopy.TxIn[i].Sequence = 0            
            }      
        }                                                                      // L650
        
    case SigHashSingle:     
        // Resize output array to up to and including requested index.      
        txCopy.TxOut = txCopy.TxOut[:idx+1]                                    // L654
        
        // All but current output get zeroed out.      
        for i := 0; i < idx; i++ {                                             // L658
            txCopy.TxOut[i].Value = -1            
            txCopy.TxOut[i].PkScript = nil                                     // L659
        }     
        
        // Sequence on all other inputs is 0, too.      
        for i := range txCopy.TxIn {                                           // L663
            if i != idx {                  
                txCopy.TxIn[i].Sequence = 0            
            }      
        }                                                      			    // L667
        
    default:                                                   				// L669
        // Consensus treats undefined hashtypes like normal SigHashAll      
        // for purposes of hash generation.      
        fallthrough
    case SigHashOld:      
        fallthrough
    case SigHashAll:      
        // Nothing special here.                              				    // L677
    }
    if hashType&SigHashAnyOneCanPay != 0 {                        				// L678
        txCopy.TxIn = txCopy.TxIn[idx : idx+1]
    }                                                                           // L680
    ...
    wbuf := bytes.NewBuffer(make([]byte, 0, txCopy.SerializeSizeStripped()+4))  // L685
    txCopy.SerializeNoWitness(wbuf)
    binary.Write(wbuf, binary.LittleEndian, hashType)
    return chainhash.DoubleHashB(wbuf.Bytes())                         		    // L688
}

以下分小节对calcSignatureHash中的关键部分进行分析。

5.1. 特殊情况的处理

L620行首先对特殊情况进行处理,即:采用了SigHashSingle类型 (不管是有修饰类还是无修饰类),但input的索引值(index)大于TxNew中的output数目。为什么说这种情况是一种特殊情况呢?
回顾上篇博客 4.1.3节的内容。在SigHashSingle的签名类型下,message需要包含对应当前input索引值的output。因此,当input的索引值大于TxNew中的output数目的时候,便出现了特殊情况。
在这种特殊情况下,简单地返回0x010000...0000 (共一个01,31个00)的字节切片。

5.2. 删除OP_CODESEPARATOR操作码

L627行通过调用removeOpcode函数,将script中的OP_CODESEPARATOR操作码全部删除。
这对应于上篇博客图2中的黄色框部分。

5.3. input部分的通用处理

L631行至L641行,对input部分进行了通用的处理。对应于上篇博客中4.1小节的内容:

  1. 在后面的内容中,我们都是以第一个input (vin[0])为例进行介绍,其他input也是同理。
  2. 签名部分填充在vin[0]的的scriptSigLenscriptSig中,也即图中红色框中。
  3. 对于vin[0]而言,签名之前scriptSigLenscriptSig中是没有内容的。其借助于vout[0]中的scriptPubkey进行填充。具体而言,将vout[0]中的scriptPubkey去除OP_CODESEP操作码后,将其内容和长度分别填充到scriptSigscriptSigLen中。如图2中黄色框所示。
  4. 对于其他input(如vin[1])而言,scriptSigLenscriptSig分别填充为0值和空值,如图2中暗绿色框所示。

为方便读者阅读,我们将相应的图片再次展示如下:
图2
需要注意的是, L631行首先将交易(tx)拷贝了一份,因为交易是以传址的方式传入当前函数的,当前函数并不该修改原交易。
当前函数包含一些对交易内容修改的操作,但这些操作只是为了生成message,应该在交易的拷贝(txCopy)上进行。

5.4. 针对无修饰类具体类型的处理

L643行到L676行分别针对具体的签名类型进行处理。

5.4.1. SIGHASH_NONE类型

L644行至L650行,对SIGHASH_NONE类型的签名进行处理。
回顾上篇博客的4.1.2小节(如下图所示),所有的output都被置为空 (L645),除当前input之外的所有inputnSequence被置为0 (L648).
图3

5.4.2. SIGHASH_SINGLE类型

L652行至L667行,对SIGHASH_SINGLE类型的签名进行处理。
回顾上篇博客的4.1.3小节(如下图所示)。
为了只保留对应当前inputoutputcalcSignatureHash函数分两步进行处理:

  1. 首先对当前切片进行截取,只保留索引小于等于当前input的元素 (L654行)
  2. 对索引小于当前input的元素,Value置为-1,PkScript置空。

也就是说,我们在上篇博客的4.1.3小节中描述得并不准确。对于索引小于当前input的元素,并没有直接删除,而是对其进行了置-1置空的处理
对于当前input之外的input,将其nSequnce置为0.

图4

5.4.3. SIGHASH_ALL类型

L669行至L676行,对SIGHASH_ALL类型的签名进行处理。
从代码中可以看出,SIGHASH_OLD类型以及默认处理都采取了和SIGHASH_ALL一样的处理方式。
正如上篇博客的4.1.1小节中所述,SIGHASH_ALL类型签名的处理方式即是5.3节中的input通用处理方式。因而,不需要再做额外的处理。

5.5. 针对有修饰类具体类型的处理

有修饰类和无修饰类最大的不同在于input的处理上。
上篇博客的4.2小节所述,有修饰类即在无修饰类的基础上,将其他input去除。示意图片如下所示,代码如L678至L680所示。
图5

5.6. 生成message

基于前面步骤得到的交易副本 (txCopy),通过序列化、双哈希等方式,即可生成所需的messsage.
该部分实现如代码L685至L699行所示。

发布了51 篇原创文章 · 获赞 23 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/u014633283/article/details/104680712