可编程支付原理

廖雪峰
资深软件开发工程师,业余马拉松选手。

重要提示

此区块链教程为技术教程,教程的所有内容均不构成任何投资比特币或其他数字货币的意见和建议,也不赞成个人炒作任何数字货币!

比特币的所有交易的信息都被记录在比特币的区块链中,任何用户都可以通过公钥查询到某个交易的输入和输出金额。当某个用户希望花费一个输出时,例如,小明想要把某个公钥地址的输出支付给小红,他就需要使用自己的私钥对这笔交易进行签名,而矿工验证这笔交易的签名是有效的之后,就会把这笔交易打包到区块中,从而使得这笔交易被确认。

但比特币的支付实际上并不是直接支付到对方的地址,而是一个脚本,这个脚本的意思是:谁能够提供另外一个脚本,让这两个脚本能顺利执行通过,谁就能花掉这笔钱:

FROM: UTXO Hash#index
AMOUNT: 0.5 btc
TO: OP_DUP OP_HASH160 <address> OP_EQUALVERIFY OP_CHECKSIG

所以,比特币交易的输出是一个锁定脚本,而下一个交易的输入是一个解锁脚本。必须提供一个解锁脚本,让锁定脚本正确运行,那么该输入有效,就可以花费该输出。

我们以真实的比特币交易为例,某个交易的某个输出脚本是76a914dc...489c88ac这样的二进制数据,注意这里的二进制数据是用十六进制表示的,而花费该输出的某个交易的输入脚本是48304502...14cf740f这样的二进制数据,也是十六进制表示的:

   ┌─────────────────────────────────────────────────────────────────────┐
   │tx:ada3f1f426ad46226fdce0ec8f795dcbd05780fd17f76f5dcf67cfbfd35d54de  │
   ├──────────────────────────────────┬──────────────────────────────────┤
   │                                  │1M6Bzo23yqad8YwzTeRapGXQ76Pb9RRJYJ│──┐
   │                                  ├──────────────────────────────────┤  │
   │                                  │18gJ3jeLdMnr9g3EcbRzXwNssYEN5yFHKE│  │
   │3JXRVxhrk2o9f4w3cQchBLwUeegJBj6BEp├──────────────────────────────────┤  │
   │                                  │1A5Mp8jHcMJEqZUmcsbmtqXfsiGdWYmp6y│  │
   │                                  ├──────────────────────────────────┤  │
   │                                  │3JXRVxhrk2o9f4w3cQchBLwUeegJBj6BEp│  │
   └──────────────────────────────────┴──────────────────────────────────┘  │
┌───────────────────────────────────────────────────────────────────────────┘
│   script: 76a914dc5dc65c7e6cc3c404c6dd79d83b22b2fe9f489c88ac
│
│  ┌─────────────────────────────────────────────────────────────────────┐
│  │tx:55142366a67beda9d3ba9bfbd6166e8e95c4931a2b44e5b44b5685597e4c8774  │
│  ├──────────────────────────────────┬──────────────────────────────────┤
└─>│1M6Bzo23yqad8YwzTeRapGXQ76Pb9RRJYJ│13Kb2ykVGpNTJbxwnrfoyZAwgd4ZpXHv2q│
   └──────────────────────────────────┴──────────────────────────────────┘
    script: 4830450221008ecb5ab06e62a67e320880db70ee8a7020503a055d7c45b7
            3dcc41adf01ea9f602203a0d8f4314342636a6a473fc0b4dd4e49b62be28
            8f0a4d5a23a8f488a768fa9b012103dd8763f8c3db6b77bee743ddafd33c
            969a99cde9278deb441b09ad7c14cf740f

我们先来看锁定脚本,锁定脚本的第一个字节76翻译成比特币脚本的字节码就是OP_DUPa9翻译成比特币脚本的字节码就是OP_HASH16014表示这是一个20字节的数据,注意十六进制的14换算成十进制是20,于是我们得到20字节的数据。最后两个字节,88表示字节码OP_EQUALVERIFYac表示字节码OP_CHECKSIG,所以整个锁定脚本是:

        OP_DUP 76
    OP_HASH160 a9
          DATA 14 (dc5dc65c...fe9f489c)
OP_EQUALVERIFY 88
   OP_CHECKSIG ac

我们再来看解锁脚本。解锁脚本的第一个字节48表示一个72字节长度的数据,因为十六进制的48换算成十进制是72。接下来的字节21表示一个33字节长度的数据。因此,该解锁脚本实际上只有两个数据。

DATA 48 (30450221...68fa9b01)
DATA 21 (03dd8763...14cf740f)

接下来,我们就需要验证这个交易是否有效。要验证这个交易,首先,我们要把解锁脚本和锁定脚本拼到一块,然后,开始执行这个脚本:

          DATA 48 (30450221...68fa9b01)
          DATA 21 (03dd8763...14cf740f)
        OP_DUP 76
    OP_HASH160 a9
          DATA 14 (dc5dc65c...fe9f489c)
OP_EQUALVERIFY 88
   OP_CHECKSIG ac

比特币脚本是一种基于栈结构的编程语言,所以,我们要先准备一个空栈,用来执行比特币脚本。然后,我们执行第一行代码,由于第一行代码是数据,所以直接把数据压栈:

│                   │
│                   │
│                   │
│                   │
│                   │
│                   │
├───────────────────┤
│30450221...68fa9b01│
└───────────────────┘

紧接着执行第二行代码,第二行代码也是数据,所以直接把数据压栈:

│                   │
│                   │
│                   │
│                   │
├───────────────────┤
│03dd8763...14cf740f│
├───────────────────┤
│30450221...68fa9b01│
└───────────────────┘

接下来执行OP_DUP指令,这条指令会把栈顶的元素复制一份,因此,我们现在的栈里面一共有3份数据:

│                   │
│                   │
├───────────────────┤
│03dd8763...14cf740f│
├───────────────────┤
│03dd8763...14cf740f│
├───────────────────┤
│30450221...68fa9b01│
└───────────────────┘

然后,执行OP_HASH160指令,这条指令会计算栈顶数据的hash160,也就是先计算SHA-256,再计算RipeMD160。对十六进制数据03dd8763f8c3db6b77bee743ddafd33c969a99cde9278deb441b09ad7c14cf740f计算hash160后得到结果dc5dc65c7e6cc3c404c6dd79d83b22b2fe9f489c,然后用结果替换栈顶数据:

│                   │
│                   │
├───────────────────┤
│dc5dc65c...fe9f489c│
├───────────────────┤
│03dd8763...14cf740f│
├───────────────────┤
│30450221...68fa9b01│
└───────────────────┘

接下来的指令是一条数据,所以直接压栈:

├───────────────────┤
│dc5dc65c...fe9f489c│
├───────────────────┤
│dc5dc65c...fe9f489c│
├───────────────────┤
│03dd8763...14cf740f│
├───────────────────┤
│30450221...68fa9b01│
└───────────────────┘

然后,执行OP_EQUALVERIFY指令,它比较栈顶两份数据是否相同,如果相同,则验证通过,脚本将继续执行,如果不同,则验证失败,整个脚本就执行失败了。在这个脚本中,栈顶的两个元素是相同的,所以验证通过,脚本将继续执行:

│                   │
│                   │
│                   │
│                   │
├───────────────────┤
│03dd8763...14cf740f│
├───────────────────┤
│30450221...68fa9b01│
└───────────────────┘

最后,执行OP_CHECKSIG指令,它使用栈顶的两份数据,第一份数据被看作公钥,第二份数据被看作签名,这条指令就是用公钥来验证签名是否有效。根据验证结果,成功存入1,失败存入0

│                   │
│                   │
│                   │
│                   │
│                   │
│                   │
├───────────────────┤
│1                  │
└───────────────────┘

最后,当整个脚本执行结束后,检查栈顶元素是否为0,如果不为0,那么整个脚本就执行成功,这笔交易就被验证为有效的。

上述代码执行过程非常简单,因为比特币的脚本不含条件判断、循环等复杂结构。上述脚本就是对输入的两个数据视作签名和公钥,然后先验证公钥哈希是否与地址相同,再根据公钥验证签名,这种标准脚本称之为P2PKH(Pay to Public Key Hash)脚本。

输出

当小明给小红支付一笔比特币时,实际上小明创建了一个锁定脚本,该锁定脚本中引入了小红的地址。要想通过解锁脚本花费该输出,只有持有对应私钥的小红才能创建正确的解锁脚本(因为解锁脚本包含的签名只有小红的私钥才能创建),因此,小红事实上拥有了花费该输出的权利。

使用钱包软件创建的交易都是标准的支付脚本,但是,比特币的交易本质是成功执行解锁脚本和锁定脚本,所以,可以编写各种符合条件的脚本。比如,有人创建了一个交易,它的锁定脚本像这样:

OP_HASH256
      DATA 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000
  OP_EQUAL

这有点像一个数学谜题。它的意思是说,谁能够提供一个数据,它的hash256等于6fe28c0a...,谁就可以花费这笔输出。所以,解锁脚本实际上只需要提供一个正确的数据,就可以花费这笔输出。点这里查看谁花费了该输出。

比特币的脚本通过不同的指令还可以实现更灵活的功能。例如,多重签名可以让一笔交易只有在多数人同意的情况下才能够进行。最常见的多重签名脚本可以提供3个签名,只要任意两个签名被验证成功,这笔交易就可以成功。

FROM: UTXO Hash#index
AMOUNT: 10.5 btc
TO: P2SH: OP_2 pk1 pk2 pk3 OP_3 OP_CHECKMULTISIG

也就是说,3个人中,只要任意两个人同意用他们的私钥提供签名,就可以完成交易。这种方式也可以一定程度上防止丢失私钥的风险。3个人中如果只有一个人丢失了私钥,仍然可以保证这笔输出是可以被花费的。

支付的本质

从比特币支付的脚本可以看出,比特币支付的本质是由程序触发的数字资产转移。这种支付方式无需信任中介的参与,可以在零信任的基础上完成数字资产的交易,这也是为什么数字货币又被称为可编程的货币。

由此催生出了智能合约:当一个预先编好的条件被触发时,智能合约可以自动执行相应的程序,自动完成数字资产的转移。保险、贷款等金融活动在将来都可以以智能合约的形式执行。智能合约以程序来替代传统的纸质文件条款,并由计算机强制执行,将具有更低的信任成本和运营成本。

小结

比特币采用脚本的方式进行可编程支付:通过执行解锁脚本确认某个UTXO的资产可以被私钥持有人转移给其他人。



Comments

Loading comments...