46

基于Java语言构建区块链(七)—— 交易脚本(智能合约)

 7 years ago
source link: https://wangwei.one/posts/643232a4.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

6bmyEnF.jpg!web

上一篇 文章我们引入 UTXOset 和 Merkle Tree 对交易流程做了些许优化,本篇文章我们将介绍比特币另一个更加重要的机制 —— 交易脚本。

在介绍UTXO的文章 中,我们已经了解到比特币的交易输出由锁定脚本锁定,它只能被交易输出所被指向的交易输入中的解锁脚本所解锁,今天让我们来详细讨论一下它们的实现机制。

交易详情

如今,大多数比特币网络处理的交易是以“Alice付给Bob”的形式存在的。同时,它们是以一种称为“P2PKH”(Pay-to-Public-Key-Hash)脚本为基础的。然而,通过使用脚本来锁定输出和解锁输入意味着通过使用编程语言,比特币交易可以包含无限数量的条件。当然,比特币交易并不限于“Alice付给Bob” 的形式和模式。”

这只是这个脚本语言可以表达的可能性的冰山一角。稍后, 我们将会全面展示比特币交易脚本语言的各个组成部分;同时,我们也会演示如何使用它去表达复杂的使用条件以及解锁脚本如何去满足这些花费条件。

比特币交易验证并不基于一个不变的模式,而是通过运行脚本语言来实现。这种语言可以表达出多到数不尽的条件变种。这也是比特币作为一种“可编程的货币”所拥有的权力。

我们以 《精通比特币(第二版)》第二章节 中 Alice向Bob购买咖啡为例,点击查看该笔 交易详情

交易输入:0.1000 BTC

手续费用:0.0005 BTC

支付费用:0.0150 BTC

找 零: 0.0845 BTC

bYrMfqz.png!web

该笔交易的数据如下:

{
    "hash": "0627052b6f28912f2703066a912ea577f2ce4da4caa5a5fbd8a57286c345c2f2",
    "locktime": 0,
    "size": 258,
    "txid": "0627052b6f28912f2703066a912ea577f2ce4da4caa5a5fbd8a57286c345c2f2",
    "version": 1,
    "vin": [
        {
            "scriptSig": {
                "asm": "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
                "hex": "483045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e381301410484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf"
            },
            "sequence": 4294967295,
            "txid": "7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
            "vout": 0
        }
    ],
    "vout": [
        {
            "n": 0,
            "scriptPubKey": {
                "addresses": [
                    "1GdK9UzpHBzqzX2A9JFP3Di4weBwqgmoQA"
                ],
                "asm": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG",
                "hex": "76a914ab68025513c3dbd2f7b92a94e0581f5d50f654e788ac",
                "reqSigs": 1,
                "type": "pubkeyhash"
            },
            "value": 0.015
        },
        {
            "n": 1,
            "scriptPubKey": {
                "addresses": [
                    "1Cdid9KFAaatwczBwBttQcwXYCpvK8h7FK"
                ],
                "asm": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG",
                "hex": "76a9147f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a888ac",
                "reqSigs": 1,
                "type": "pubkeyhash"
            },
            "value": 0.0845
        }
    ],
    "vsize": 258
}

交易输入

"vin": [
    {
        "scriptSig": {
            "asm": "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
            "hex": "483045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e381301410484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf"
        },
        "sequence": 4294967295,
        "txid": "7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
        "vout": 0
    }
]

它所包含的信息:

  • 交易ID。包含了它所指向的UTXO的交易的Hash值。
  • UTXO下标。定义了它所指向的UTXO在上一笔交易中交易输出数组的位置(下标值)。
  • 签名。用于满足它所指向的UTXO上所设定的花费条件。

交易输出

"vout": [
    {
        "n": 0,
        "scriptPubKey": {
            "addresses": [
                "1GdK9UzpHBzqzX2A9JFP3Di4weBwqgmoQA"
            ],
            "asm": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG",
            "hex": "76a914ab68025513c3dbd2f7b92a94e0581f5d50f654e788ac",
            "reqSigs": 1,
            "type": "pubkeyhash"
        },
        "value": 0.015
    },
    {
        "n": 1,
        "scriptPubKey": {
            "addresses": [
                "1Cdid9KFAaatwczBwBttQcwXYCpvK8h7FK"
            ],
            "asm": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG",
            "hex": "76a9147f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a888ac",
            "reqSigs": 1,
            "type": "pubkeyhash"
        },
        "value": 0.0845
    }
]

它所包含的信息:

  • 比特币的数量。单位:satoshis(聪)

  • 比特币地址。交易输出所绑定的地址。

  • 锁定脚本。定义了花费这笔交易输出所需要满足的限制条件。其中包含了一些字符串,例如: OP_DUPOP_HASH160OP_EQUALVERIFYOP_CHECKSIG ,这些叫操作码,后面会做介绍。

那么,交易输入中的 script 是如何满足交易输出中 script_string 的限制条件的呢?接下来,我们来一起看下比特币的交易脚本是如何工作的。首先,我们来了解一下比特币所用到脚本语言的特性以及它的工作原理。

比特币脚本语言

特性

脚本是一种类似Forth的基于堆栈的逆波兰表示法的图灵非完备语言。接下来,让我们逐个解释一下:

图灵非完备( Turing Incomplete

什么是图灵完备

可计算性理论 里,如果一系列操作数据的规则(如 指令集编程语言细胞自动机 )可以用来模拟 单带图灵机 ,那么它是 图灵完备的 。这个词源于引入图灵机概念的数学家 艾倫·图灵

虽然 图灵机 会受到储存能力的 物理 限制,图灵完全性通常指「具有无限存储能力的通用物理机器或编程语言」。

来源: 维基百科

https://en.wikipedia.org/wiki/Turing_completeness

图灵非完备语言将会有有限的功能,不能进行跳转或/和循环。因此它们不能进入无线循环。图灵完备就意味着,在给定的计算资源和内存下,图灵完备程序,能够解决任何问题。 Solidity 就是其中一种图灵完备语言。

为什么比特币脚本是图灵非完备的

因为没有必要。比特币脚本没有必要做到像以太坊智能合约那样复杂。事实上,如果一个脚本是图灵完备的,它会给恶意的人以机会去随意创造复杂的交易,这将会吃掉比特币网络的哈希率并降低整个系统的性能。

哈希率是比特币网络的处理能力的衡量单位。为了安全,比特币网络必须进行高强度的数学运算。网络的哈希率达到10TH/s,意味着这个网络每秒能处理10亿次计算。

逆波兰表示法( Reverse Polish notation )

逆波兰表示法( Reverse Polish notationRPN ,或 逆波兰记法 ),是一种是由 波兰 数学家 扬·武卡谢维奇 1920年引入的数学表达式方式,在逆波兰记法中,所有 操作符 置于 操作数 的后面,因此也被称为 后缀表示法 。逆波兰记法不需要括号来标识操作符的优先级。

来源: 维基百科

例如:

解释 常规表示 逆波兰表示法 三加四 3 + 4 34+ 先3减去4,再加上5 3 - 4 + 5 3 4 - 5 + 先3减去4,再乘以5 (3 - 4)*5 3 4 - 5 *

基于堆栈

这是一种具有 LIFO(Last In First Out)特性的数据结构,熟悉数据结构的应该非常清楚,这里不多做介绍。想深入了解的朋友,可以查看我的另一篇文章: https://wangwei.one/posts/d1e0a844.html

buMZ3yF.png!web

类Forth脚本语言

比特币脚本恰好类似于编程语言“Forth”,它也恰好是基于堆栈的一种编程语言。

查看: Forth编程语言

工作原理

比特币的脚本语言非常简单,这种语言的代码无非就是一系列数据和操作符。脚本语言通过从左至右地处理每个项目的方式执行脚本。数字(常数)被推送至堆栈,操作符向堆栈推送(或移除)一个或多个参数,对它们进行处理,甚至可能会向堆栈推送一个结果。例如,OP_ADD将从堆栈移除两个项目,将二者相加,然后再将二者相加之和推送到堆栈。

条件操作符评估一项条件,产生一个真或假的结果。例如,OP_EQUAL从堆栈移除两个项目,假如二者相等则推送真(表示为1),假如二者不等则推送为假(表示为0)。比特币交易脚本常含条件操作符,当一笔交易有效时,就会产生True的结果。

我们以一个简单的脚本来进行演示:

2 3 OP_ADD 5 OP_EQUAL

从左至右,依次执行,过程如下:

BrMNnym.png!web

弄明白这个过程之后,你会发现其中所蕴含的堆栈特性以及逆波兰表示法特性。接下来,我们来看下比特币脚本的锁定与解锁逻辑。

锁定与解锁逻辑

比特币的交易验证引擎依赖于两类脚本来验证比特币交易:一个锁定脚本和一个解锁脚本。

锁定脚本是放置在输出上的消费条件:它指定将来要花费输出必须满足的条件。锁定脚本常被称为 scriptPubKey ,因为它通常包含公钥或比特币地址(公钥哈希)。

解锁脚本是通过“解决”或满足锁定脚本上的交易输出条件并允许交易输出花费的脚本。 解锁脚本是每个交易输入的一部分。大多数情况下,它包含了由用户私钥所产生的数字签名。解锁脚本常被称为 scriptSig ,因为它通常包含数字签名。

每当要验证一笔交易的有效性时,解锁脚本和锁定脚本会随着堆栈的传递被分别执行。首先,使用堆栈执行引擎执行解锁脚本。如果解锁脚本在执行过程中未报错(例如:没有“悬挂”操作码),则复制 主堆栈(而不是备用堆栈),并执行锁定脚本。如果从解锁脚本中复制而来的堆栈数据执行锁定脚本的结果 为“TRUE”,那么解锁脚本就成功地满足了锁定脚本所设置的条件,因此,该输入是一个能使用该UTXO的有效授 权。如果在合并脚本后的结果不是”TRUE“以外的任何结果,输入都是无效的,因为它不能满足UTXO中所设置的使 用该笔资金的条件。

下图所示是最为常见类型的比特币交易(向公钥哈希进行一笔支付)的解锁和锁定脚本样本,该样本展示了在脚本验证之前将解锁脚本和锁定脚本串联而成的组合脚本。

iummyqy.png!web

这是比特币脚本中使用最为常见的一种形式,名叫 Pay to Public Key Hash (P2PKH)。基于前面 2 + 3 = 5 的验证过程,我们可以得到 P2PKH 脚本在堆栈引擎中的验证过程如下所示:

Fj67ray.jpg!web

好了, 到此为止,你已经对比特币的交易脚本以及它的工作原理已经有了一个非常清楚的理解与认识。下面我们开始用代码来实现它的工作机制。

代码实现

// TODO

参考资料


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK