比特币脚本

比特币脚本(Bitcoin Script)是一种用于控制交易的简单编程语言。在比特币网络中,交易需要满足一定条件才能被确认,比特币脚本就是用来规定这些条件的。比特币脚本允许用户定义各种复杂的交易条件,例如多重签名(Multi-Sig)、时间锁(Time Locks)、条件支付(Conditional Payments)等。

欧易
欧易(OKX)

全球三大交易所之一,注册领50U数币盲盒,币圈常用的交易平台!

币安
币安(Binance)

币安交易所是世界领先的数字货币交易平台,注册领100U。



什么是比特币脚本

比特币脚本(Bitcoin Script)是一种用于控制交易的简单编程语言。在比特币网络中,交易需要满足一定条件才能被确认,比特币脚本就是用来规定这些条件的。比特币脚本允许用户定义各种复杂的交易条件,例如多重签名(Multi-Sig)、时间锁(Time Locks)、条件支付(Conditional Payments)等。

比特币脚本的设计初衷是为了增加交易的安全性和灵活性。通过比特币脚本,用户可以定义多种不同的支付条件,例如只有在特定时间内才能花费比特币、需要多个私钥签名才能完成交易等。这为用户提供了更多的选择和控制权,从而增强了交易的安全性。

比特币脚本是基于堆栈的脚本语言,类似于编程语言中的逆波兰表达式。比特币脚本由一系列操作码(Opcode)组成,每个操作码执行不同的操作,例如将数据推入堆栈、对堆栈中的数据进行运算等。通过组合不同的操作码,用户可以创建各种不同的支付条件。

比特币脚本的执行是在比特币网络中的每个节点上进行的,节点需要验证交易是否满足脚本中定义的条件。如果交易符合脚本规定的条件,节点就会将其纳入区块中,从而完成交易确认。

比特币交易概览

一个比特币交易的实例:来源

比特币脚本

这个交易的JSON:

{
  "txid": "c07534fe61e1d7a0cfa81326f5efa8533c8a93ad7cfcba3767123ff0b4d2e7c3",
  "size": 371,
  "version": 1,
  "locktime": 0,
  "fee": 188100,
  "inputs": [
    {
      "coinbase": false,
      "txid": "a53788308b127d44eaf5d549b7626e2fb5b4ee34df581d65b508e8f5271cd6c0",
      "output": 2,
      "sigscript": "",
      "sequence": 4294967295,
      "pkscript": "0014e70986d7a0401b2c9ac354487d56c7e82b89b4b5",
      "value": 33023251,
      "address": "bc1quuycd4aqgqdjexkr23y864k8aq4cnd94jy6u8u",
      "witness": [
        "304502210084a4f4277134913ce3d8fcceafc771e32bd9fdcb7b8b43314ec337ab901ad9f502201f06c6d683fc7f3fb4d04848730bd9e09a3f800cbe833b21ebf4feb8c2d41a3501",
        "038a1997fd115368187cee688d285398e74714e7e2f42a65480f0f8960fa6a02ff"
      ]
    },
    {
      "coinbase": false,
      "txid": "ef3775c685181ee24d6274c25e359e4002cafba35d76fbd4459a8107b16c1f64",
      "output": 1,
      "sigscript": "",
      "sequence": 4294967295,
      "pkscript": "00147fe8e29b2835fdaf2efde03f0c8d6ef480594858",
      "value": 63767807,
      "address": "bc1q0l5w9xegxh767thauqlsertw7jq9jjzcj69lw2",
      "witness": [
        "304402207eed4a2709c62f2916bbb3c24b7f9f4cac239a89b6de99e8012033396f8897bd0220566573564fe7f9b11b0d9ff7d9eb2cbc40fd97f92a6ef9f2f5acffe870638c2601",
        "027ebe84f812272370a3b210eeb6f488c67d178dbf18a94e41622edafe333846ce"
      ]
    }
  ],
  "outputs": [
    {
      "address": "bc1qh0a08hs7ytqhq5dhv3p4fug2pfq36zncnnf4v9",
      "pkscript": "0014bbfaf3de1e22c17051b7644354f10a0a411d0a78",
      "value": 30343663,
      "spent": true,
      "spender": {
        "txid": "b832b21f9078d07906ce233589a0f0764f52b44e230df0797a05d2040b6f6aac",
        "input": 0
      }
    },
    {
      "address": "bc1qyd9rar2ngmffersqahj3z4lc8lvkw00s5e30e3",
      "pkscript": "0014234a3e8d5346d29c8e00ede51157f83fd9673df0",
      "value": 66259295,
      "spent": true,
      "spender": {
        "txid": "bc20fd44855f7861b08a4f26e1f0b0838a99f87e9caf8d0b09110a29b5232383",
        "input": 50
      }
    }
  ],
  "block": {
    "height": 792216,
    "position": 1
  },
  "deleted": false,
  "time": 1685518790,
  "rbf": false,
  "weight": 833
}

交易脚本结构

交易脚本整体结构:

{
  "txid": "c07534f.....d2e7c3",  // 交易的Hash值
  "size": 371,  // 交易的大小
  "version": 1,  // 使用的BTC协议的版本号
  "locktime": 0,  // 指用来设置交易的生效时间。绝大多数时候该值都为0,表示立即生效。如果设置为非0值,表示需要等一段时间才能生效,例如等10个区块之后才能写入区块链。
  "fee": 188100,
  "inputs": [ ........ ],  // 输入部分
  "outputs": [ ........ ],  // 输出部分
  "block": {  // 该交易所在区块
    "height": 792216,
    "position": 1
  },
  "deleted": false,
  "time": 1685518790,  // 该交易产生的时间
  "rbf": false,  // Replace-by-fee,用一笔交易替代另一笔交易
  "weight": 833
}

交易的输入:

{
    "inputs": [   // 输入是一个数组,可以有多个输入
    {
      "coinbase": false,
        // 表示本次交易该输入的币的来自txid交易的第output个输出
      "txid": "a53788308...cd6c0",  // 提供币的来源的交易Hash值
      "output": 2,  // 提供币的来源的交易中,对应的第几个输出(下标从0开始)。
      "sigscript": "", // 签名(解锁脚本),本笔交易采用的隔离见证,sigscript为空
      "sequence": 4294967295,
      "pkscript": "0014...9b4b5",  // 从币的来源output复制的pkscript加锁脚本。
      "value": 33023251, // 金额,单位为“聪”,即0.33023251 BTC
      "address": "bc1q...6u8u",  // A账户的地址(即A的公钥)
      "witness": [  // 隔离见证
        "3045...a3501",
        "038a....02ff"
      ]
    },
    {
      "coinbase": false,
      "txid": "ef37....1f64",
      "output": 1,
      "sigscript": "",
      "sequence": 4294967295,
      "pkscript": "0014....4858",
      "value": 63767807,
      "address": "bc1q....9lw2",
      "witness": [
        "3044....2601",
        "027e....46ce"
      ]
    }
  ]
}

“聪”是比特币中最小的单位,也写作Satoshi(中本聪的名字)、SAT,等于一个比特币的一亿分之一:1 聪 = 0.00000001 BTC

交易的输出:

{
    "outputs": [  // 输出是一个数组,一个交易可以有多个输出
    {
      "address": "bc1q....f4v9",   // B账户的地址(B的公钥)
      "pkscript": "0014....d0a78",  // 加锁脚本。加锁脚本在交易验证过程中非常重要。它的作用是在用户花费这笔钱的时候,需要证明这笔钱是他的。它其实是一个栈结构,里面是一条条的命令,验证过程就是依次执行这些命令,都通过了则验证通过。在花费这笔UTXO时,各个矿工会验证该加锁脚本,验证通过后才允许交易。
      "value": 30343663,  // 金额,单位“聪”
      "spent": true,  // 该笔输出是否已经被花掉了
      "spender": {  // 花掉这笔输出的交易
        "txid": "b832...6aac",  // 花掉这笔输出的交易hash值
        "input": 0    // 花掉这笔输出的交易中的第几个输入
      }
    },
    {
      "address": "bc1q...30e3",
      "pkscript": "001...3df0",
      "value": 66259295,
      "spent": true,
      "spender": {
        "txid": "bc20....2383",
        "input": 50
      }
    }
  ]
}

交易的具体操作:

比特币脚本

A转账给B,B再转给C。

B转给C时,输入的来源对应A转给B时的交易的输出。

验证B转给C这个交易合法性时,将B转给C的输入脚本和A转给B的输出脚本拼接在一起,执行没有出错即可。早期的比特币协议中,会将两个脚本拼接在一起之后,作为一个脚本一次执行;后来的比特币协议中,会将两个脚本分别执行,先执行B转给C的输入脚本,再执行A转给B的输出脚本,最后的执行结果没有出错,且栈顶的结果为非0值(即true)则验证通过。

如果一个交易中包含多个输入、输出,则每个输入和它对应来源的输出的脚本都验证通过后,才证明这个交易是合法的。

输入输出脚本形式

比特币使用的脚本是非常简单的,唯一能够访问的内存空间就是一个堆栈,不像其他语言那样有全局变量、局部变量、可动态分配的内存空间。所以比特币脚本语言也称为基于栈的语言。

比特币中使用的脚本语言是比较简单的,甚至连固定的名字都没有,被称为BitCoin Script Language。

比特币脚本语言不支持循环等功能。不支持循环也就不会有死循环,不用担心停机问题(halting problem)。而像以太坊使用的智能合约语言,是图灵完备的,表达能力很强,就需要使用“汽油费机制”来防止程序陷入死循环。

比特币脚本语言虽然在有些方面功能很有限,但是在和密码学相关的功能是很强大的。例如使用一条语句OP_CHECKMULTISIG就可以完成检查多重签名。

下面的示例中,操作符省略了OP_前缀,例如OP_DUP写成了DUP、OP_CHECKSIG写成了CHECKSIG。

P2PK

P2PK(Pay to Public Key):最简的形式,直接在输出脚本中给出收款人公钥

输入脚本:

●PUSHDATA(Sig):将Sig入栈。Sig是付款人用自己的私钥对整个交易加密生成的签名

输出脚本:

●PUSHDATA(PubKey):将PubKey入栈。PubKey是收款人的公钥

●CHECKSIG:检查签名

那么,A转给B,B再转给C时,验证B转给C交易合法性的脚本形式就是,将本交易的输入脚本、上一个交易的输出脚本拼接在一起:

1.PUSHDATA(Sig):将B再转给C交易的付款人B的签名压入栈

2.PUSHDATA(PubKey):将A转给B交易的收款人(还是B)的公钥压入栈

3.CHECKSIG:弹出栈顶的两个元素(公钥、签名),验证签名是否合法。如果相等则入栈TRUE

4.如果程序全程无报错,且栈顶的结果为非0值(即true),验证通过

比特币脚本

P2PKH

P2PKH(Pay to Public Key Hash):输出脚本中没有直接给出收款人的公钥,而是给出了收款人公钥的Hash,在下一次交易时在输入脚本中给出公钥。最常用的形式。

输入脚本:

●PUSHDATA(Sig)

●PUSHDATA(PubKey)

输出脚本:

●DUP

●HASH160

●PUSHDATA(PubKeyHash)

●EQUALVERIFY

●CHECKSIG

将本交易的输入脚本、上一个交易的输出脚本拼接在一起:

1.PUSHDATA(Sig):将签名压入栈

2.PUSHDATA(PubKey):将公钥压入栈

3.DUP:将栈顶的元素复制一份

4.HASH160:将栈顶元素弹出来求hash,然后将得到的hash再压入栈

5.PUSHDATA(PubKeyHash):将输出脚本中提供的公钥哈希压入栈

6.EQUALVERIFY:弹出栈顶的两个元素,比较是否相等。即验证栈顶的两个哈希是否相等。如果不等则直接退出

7.CHECKSIG:弹出栈顶的两个元素(公钥、签名),验证签名是否合法。如果相等则入栈TRUE

8.如果程序全程无报错,且栈顶的结果为非0值(即true),验证通过

比特币脚本

实例:

比特币脚本

P2SH

P2SH(Pay to Script Hash):输出脚本不是收款人公钥或收款人公钥哈希,而是收款人提供的一个脚本的哈希,这个脚本叫redeemScript(赎回脚本)。将来要花这笔钱时,输入中需要给出赎回脚本的内容,同时还要给出让这个赎回脚本可以正确运行的签名。最复杂的一个。

P2SH在早起的比特币版本中没有该功能,后来通过软分叉的形式添加了进去。它的最常见的场景是对多重签名的支持。

输入脚本:

●……

●PUSHDATA(Sig)

●…….

●PUSHDATA(serialized redeemScript)

输出脚本:

●HASH160

●PUSHDATA(redeemScriptHash)

●EQUAL

输入脚本要给出一些签名(数目不定),以及一段序列化的redeemScript。验证分为两步:

1.验证序列化的redeemScript是否与输出脚本中的哈希值相匹配

2.反序列化redeemScript,将其当做操作指令执行一遍,验证输入脚本给出的签名是否正确

redeemScript的形式:

●P2PK形式

●P2PKH形式

●多重签名形式

用P2SH来实现P2PK

赎回脚本(redeemScript):

●PUSHDATA(PubKey)

●CHECKSIG

输入脚本:

●PUSHDATA(Sig)

●PUSHDATA(serialized redeemScript)

输出脚本:

●HASH160

●PUSHDATA(redeemScriptHash)

●EQUAL

第一阶段的验证:(同样的,将输入脚本和输出脚本拼接在一起)

1.PUSHDATA(Sig):将输入脚本的签名入栈

2.PUSHDATA(serialized redeemScript):将输入脚本中的赎回脚本入栈

3.HASH160:将赎回脚本取哈希

4.PUSHDATA(redeemScriptHash):将输出脚本中给出的赎回脚本哈希值入栈

5.EQUAL:对比两个哈希值是否相等。如果不相等则直接退出

比特币脚本

第二阶段验证:

6.反序列化serialized redeemScript,还原成可执行脚本,然后执行该脚本

7.PUSHDATA(PubKey):将输入脚本的公钥入栈

8.CHECKSIG:验证签名

比特币脚本

比特币多重签名

比特币的一个输出中,可能有些场景需要有多个人的签名才可以进行交易。多重签名是P2SH形式的脚本最常见的一种使用场景。

例如一个公司账户,需要进行转账时需要5个高管中任意3个高管的签名才可以交易。

这样也是一种对私钥泄漏的保护,假如一个高管的私钥被泄漏了,也不能把账户中的资产盗走,还需要2个高管的私钥才可以。

同时也是对私钥丢失的一种冗余,假如5个高管中有2个人忘记了自己的私钥,使用剩余3个人的私钥依然可以进行交易,将资产转到一个新的安全的账户。

早期多重签名

最早的多重签名(已不推荐使用)。

输入脚本:

●x:往栈中压入一个没用的元素。

●PUSHDATA(Sig_1):给出第一个签名

●PUSHDATA(Sig_2):给出第二个签名

●……

●PUSHDATA(Sig_M):给出第M个签名

多重签名时,需要往栈中压入一个没用的元素(即第一步的x):比特币中CHECKMULTISIG的代码实现有一个bug,执行时会从堆栈中多弹出一个元素。因为比特币是一个去中心化系统,如果想通过软件升级的方式修复该bug,需要做硬分叉,代价是很大的,所以该bug无法进行修复。为了迁就代码中的这个bug,所以需要往堆栈上多压入一个没有实际作用的元素。

输出脚本:

●M:花掉该输出时需要的签名个数

●PUSHDATA(pubKey_1):第一个公钥

●PUSHDATA(pubkey_2):第二个公钥

●……

●PUSHDATA(pubkey_N):第N个公钥

●N:公钥的个数

●CHECKMULTISIG:检查多重签名

多重签名时,输入脚本中给出的这M个签名的顺序,需要和它们在输出脚本中的公钥顺序保持一致。例如3个签名中给2个即可交易的脚本中,输出脚本中的公钥顺序为:pubkey_1、pubkey_2、pubkey_3,输入脚本中给出1和3的签名时,顺序就需要保持为:Sig_1、Sig_3,不能颠倒。

将本交易的输入脚本、上一个交易的输出脚本拼接在一起,进行验证。

示例3个签名中给出2个签名即可交易(N=3,M=2):

1.FALSE:将FLASE(可以为任意值)压入栈,无实际意义,只是为了迁就CHECKMULTISIG代码的bug

2.PUSHDATA(Sig_1):将账户1的签名压入栈

3.PUSHDATA(Sig_2):将账户2的签名压入栈

4.2:将花掉该输出需要的签名个数2压入栈

5.PUSHDATA(pubkey_1):将账户1的公钥压入栈

6.PUSHDATA(pubkey_2):将账户2的公钥压入栈

7.PUSHDATA(pubkey_3):将账户3的公钥压入栈

8.3:将公钥数量3压入栈

9.CHECKMULTISIG:检查多重签名

比特币脚本

该种签名在实际中不常用,因为把复杂性暴露给了用户:当用户需要交易购买商品时,有的商户需要的是3个签名检查2个,有的商户需要6个签名检查3个,这样对用户生成转账交易非常不方便。

使用P2SH实现多重签名

使用P2SH实现多重签名,它的本质是将复杂度从输出脚本转移到了输入脚本。

输出脚本中只需要给出赎回脚本的哈希即可,原来的复杂度转移到了赎回脚本中。赎回脚本需要给出M的值、N的值、N个公钥。这个赎回脚本是在输入脚本中提供的,也就是说这个赎回脚本是由收款人商家提供的。

这样,店商在网站上公布自己的赎回脚本的哈希值,用户付款时,只需要将这个赎回脚本的哈希值放到自己的输出脚本中即可。

至于这个店商是使用5选3还是6选4或其他的签名规则,对于用户是不可见的,用户无需关心。从用户角度看,使用这种方式,和使用P2PKH没有多大区别,只是将原本公钥的哈希值换成了赎回脚本的哈希值而已。虽然输出脚本的内容和P2PKH稍有不同,但不是本质性的。

输入脚本是店商在花掉这笔输出时提供的,其中包含:赎回脚本的序列化版本、让这个赎回脚本验证通过所需的M个签名。

将来如果这个店商的签名验证策略从6选4变成了5选2,只需要修改店商自己的输入脚本、赎回脚本,然后将新的赎回脚本的哈希值公布出去即可。对用户来说,只不过需要修改包含的赎回脚本的哈希值,其他的无需知道。

输入脚本:

●X

●PUSHDATA(Sig_1)

●PUSHDATA(Sig_2)

●……

●PUSHDATA(Sig_M)

●PUSHDATA(serialized RedeemScript)

输出脚本:

●HASH160

●PUSHDATA(RedeemScriptHash)

●EQUAL

赎回脚本RedeemScript:

●M

●PUSHDATA(pubkey_1)

●PUSHDATA(pubkey_2)

●…….

●PUSHDATA(pubkey_N)

●N

●CHECKMULTISIG

3个公钥选2个签名的最终的验证过程示例(输入脚本、反序列化执行赎回脚本、输出脚本):

第一阶段验证:

1.FALSE

2.PUSHDATA(Sig_1)

3.PUSHDATA(Sig_2)

4.PUSHDATA(serialized RedeemScript)

5.HASH160

6.PUSHDATA(RedeemScriptHash)

7.EQUAL

比特币脚本

第二阶段验证(赎回脚本展开后验证):

8.2

9.PUSHDATA(pubkey_1)

10.PUSHDATA(pubkey_2)

11.PUSHDATA(pubkey_3)

12.3

13.CHECKMULTISIG

比特币脚本

Proof of Burn销毁比特币

输出脚本:

●…….

●RETURN

●……..

这种形式的输出脚本被称为:Provably Unspendable/Prunable Outputs。

在这个输出脚本中,会包含一个RETURN操作,而RETURN操作会无条件的返回错误。所以无论该输出脚本的RETURN后面还有什么指令,都不会进行执行。如果输入脚本指向这个输出,则不论输入脚本如何设计,都会因为该输出脚本中的RETURN出错而返回false,所以这个输出无法再被花出去,其对应的UTXO也就可以被剪枝了,无需保存。

这种脚本是证明销毁比特币的一种方法。一般有两种使用场景:

●有一些小币种,要求销毁一定数量的比特币才能得到该币种。这种小币种被称为Alt Coin(Alternative Coin)。除了比特币之外的其他加密货币都可以被称为Alt Coin,例如有些币种规定销毁一个比特币可以得到1000个该种加密货币。需要用这种销毁比特币的方式来证明自己付出的代价。

●可以向RETURN后面写入一些永久保存的内容。因为RETURN后面的内容不会被执行,且写入区块链中的内容无法被篡改,所以可以向RETURN后面写入一些自己想写的内容。例如可以当做digital commitment用来保护自己的知识产权,将自己需要保护的知识的内容取哈希值,然后写到RETURN语句的后面,将来如果发生了知识产权纠纷,可以将自己的知识内容再次取哈希和RETURN后面的哈希值对比,来证明自己曾经在这个时间已经发现了这个知识。

区块链的区块中,铸币交易的CoinBase域内也可以当做digital commitment写入任意内容。但是想要在CoinBase中写内容,必须要挖矿得到记账权。

而只要有比特币的账户,都可以通过销毁很少一点比特币的形式在输出脚本中的RETURN后面写入任意内容。

销毁0个比特币

有些交易虽然写了RETURN,但是实际上没有销毁比特币(销毁比特币的金额为0),而是支付了交易费。

示例1:CoinBase Transaction销毁0个比特币。

该交易有两个输出:

●第一个输出是正常的block reward+transaction

●第二个输出的金额是0,形式就是RETURN+任意自定义内容,第二个输出的作用就是向区块链中写入一些内容。

比特币脚本

示例2:转账交易销毁0个比特币

该交易的输入金额是0.05 BTC,输出金额是0 BTC,说明输入的金额全部用于支付交易费了。

该交易的输出脚本是RETURN+任意自定义内容,但是这笔交易其实销毁比特币的个数为0,只不过将输入中的比特币作为交易费全部转给了挖到矿的矿工了。

比特币脚本

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...