比特币原理及其代码实践6-交易签名和解锁


实现签名

交易必须被签名,因为这是比特币里面保证发送方不会花费属于其他人的币的唯一方式。如果一个签名是无效的,那么这笔交易就会被认为是无效的,因此,这笔交易也就无法被加到区块链中。

思考:哪些数据需要签名?

  1. 之前交易的某个输出的公钥hash,也就是我们钱的源头
  2. 当前交易的全部输出,也就是我们把钱转给了谁

签名在哪个地方?

签名在当前交易的每一个输入

伪代码:

func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) {
    ...
    //针对每一个输入Vin
    for inID, vin := range txCopy.Vin {
        prevVout := 获得之前交易的输出
        hashValue := hash(prevVout的公钥hash+当前交易的全部输出)
        //使用私钥对hash值进行数字签名 ecdsa.Sign
        signature := 数字签名(privKey, hashValue)
        //赋值
        vin.Signature = signature
        //把自己的公钥也带上为了方便别人验证
        //vin.Pubkey = Pubkey
    }
}

验证交易:

func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
    ...
    //针对每一个vin都需要验证
    for inID, vin := range tx.Vin {
        prevVout := 获得之前交易的输出
        hashValue := hash(prevVout的公钥hash+当前交易的全部输出)
        //获得公钥
        PubKey := vin.Pubkey
        //验证 ecdsa.Verify
        Verify(PubKey,hashValue)
    }
    ...
}

理解解锁和上锁

上锁

我们从对方的address中提取到对方的公钥hash

在output中带上对方的公钥hash就意味着对这比output上锁

解锁

对方在新的交易中,引用我们的output,指向一个新的input

并对新的input用自己的私钥进行签名

这就是解锁这笔钱为自己所用

总结:

用对方的公钥hash上锁输出,对方用自己的私钥签名公钥解锁这比输出作为自己的输入

验证

矿工在打包区块的时候,会对每一笔input进行验证

func (bc *Blockchain) MineBlock(transactions []*Transaction) {
    var lastHash []byte

    for _, tx := range transactions {
        if bc.VerifyTransaction(tx) != true {
            log.Panic("ERROR: Invalid transaction")
        }
    }
    ...
}

代码演示

真实的比特币是如何做的?

在比特币中,锁定/解锁逻辑被存储在脚本中,它们被分别存储在输入和输出的 ScriptSigScriptPubKey 字段。

比特币的脚本语言:

  • 基于堆栈的编程语言
  • 非图灵完备
  • 一个脚本能在任何系统上以相同的方式执行

锁定脚本是一个放在一个输出值上的“障碍”,同时它明确了今后花费这笔输出的条件。由于锁定脚本往往含有一个公钥(即比特币地址),它也曾被称作脚本公钥代码

解锁脚本是一个“解决”或满足被锁定脚本在一个输出上设定的花费条件的脚本,同时它将允许输出被消费。解锁脚本是每一笔比特币交易输出的一部分,而且往往含有一个被用户的比特币钱包(通过用户的私钥)生成的数字签名。由于解锁脚本常常包含一个数字签名,因此它曾被称作ScriptSig。但是并非所有的解锁脚本都会包含签名。

基于堆栈的脚本:

从脚本的角度来解释交易,交易就是:解锁前一个锁定脚本,建一个新的锁定脚本。