比特币有时也被称为
可编程货币。它的数字性质,使得用户在设置如何使用资金的条件时拥有很大的灵活性
讨论比特币时,我们会提到
钱包和
代币。但我们也可以
把钱包看作钥匙,
把代币看作支票,把
区块链看作一排又一排带锁的保险柜。每个保险柜都有一条细小的插槽,这样任何人都可以存入支票或查看保险柜里有多少价值。然而,只有钥匙持有人才能给保险柜开锁。
如果钥匙持有人想把钱给其他人,就会打开保险柜。他们会开一张引用旧支票的新支票(旧支票随后会被销毁),并将其重新锁在收款人可以打开的箱子中。为了花掉这笔钱,新的收款人需要重复这个过程。
在本文中,我们将更深入地了解脚本,这是一种由比特币网络上的
节点进行解释的编程语言。脚本管理前面提到的保险柜的上锁/开锁机制。
采用上述类比,可以说每笔交易都有两个部分 — 钥匙(用于给保险箱开锁)和锁。您需要用钥匙打开包含要发送的支票的箱子,然后将新的支票添加至另一个使用不同锁的新箱子。要花掉新箱子里面的钱,您需要另一把钥匙。
就这么简单。系统中的锁的类型可能会有一些变化。有些保险箱需要您提供多个
多个钥匙,其他保险箱则需要您证明您知道某个密码,总之可以设定很多条件。
我们的钥匙就是我们所说的scriptSig,锁则是我们所说的scriptPubKey。如果我们更详细地观察这些组件,我们会发现它们实际上由数据位和代码块组成。当它们结合在一起时,就形成了一个小程序。
当您进行交易时,您就是在向网络广播该组合。每个接收到这笔交易的节点都将检查这个程序,该程序将告知节点这比交易是否有效。如果无效,交易就会被废弃,您将无法使用锁定的资金。
您持有的支票(代币)被称为
未花费的交易输出(UTXO)。只要能提供与这把锁相配的钥匙,任何人都可以使用这笔资金。具体来说,
钥匙是scriptSig,锁是scriptPubKey。
如果UTXO在您的钱包内,它们可能会有一个条件,
即只有能证明该公钥所有权的人才能解锁这些资金。如需解锁资金,您
需要提供一个包含数字签名的scriptSig,
使用映射到scriptPubKey中指定公钥的私钥。一切很快就会变得清晰起来。
脚本是一种基于堆栈的语言。这就意味着,当我们阅读一组指令时,我们会将它们放在被认为是垂直的列中。例如,列表A、B、C将产生一个堆栈,A位于底部,C位于顶部。当指令告诉我们要做某件事时,我们就会从堆栈的顶部开始操作一个或多个元素。
元素A、B和C被添加并从堆栈中“弹出”。
我们可以区分数据(例如
签名、
哈希和公钥)和指令(或
操作码)。指令删除数据并对其进行处理。以下就是一个非常简单的脚本示例:
<xyz> <md5 hasher> <d16fb36f0911f878998c136191af705e> <check if equal>
红色部分表示数据,蓝色部分表示操作码。我们从左到右读取,因此我们首先把字符串<xyz>放到堆栈上。接下来是<md5 hasher>操作码。这个操作码在比特币中并不存在,但我们假设它删除了堆栈顶部的元素(<xyz>),并使用MD5算法进行了哈希。随后将输出添加回堆栈。这里的输出恰好是d16fb36f0911f878998c136191af705e。
太巧了!我们要添加的下一个元素是<d16fb36f0911f878998c136191af705e>,因此,现在我们的堆栈有两个相同的元素。最后,<check if equal>在顶部弹出两个元素,并检查它们是否相等。如果相等,则将<1>添加至堆栈。如果不相等,则将<0>添加至堆栈。
我们已经到了指令列表的末尾。我们的脚本可能会以两种方式失败 — 如果剩余元素是零,或如果某些条件不满足时,其中一个运算符将导致其失败。在这个例子中,我们没有任何这样的运算符,最终得到一个非0元素(<1>),因此我们的脚本是有效的。这些规则也适用于真正的比特币交易。
上述示例只是一个虚构的程序。现在我们来看一些实际的示例。
支付到公钥(P2PK)非常简单。它设计将资金锁定至特定的公钥。如果您想以这种方式接收资金,就需要向发送者提供您的公钥,而不是比特币地址。
中本聪和Hal Finney在2009年的
第一笔交易就是P2PK交易。这种结构在比特币早期被大量使用,但如今,支付到公钥哈希(P2PKH)已经在很大程度上取代了它。
P2PK交易的锁定脚本遵循<public key> OP_CHECKSIG的格式。就这么简单。您可能已经猜到,OP_CHECKSIG会根据提供的公钥检查签名。因此,我们的scriptSig将是一个简单的<signature>。记住,scriptSig是开锁的钥匙。
没有比这更简单的了。签名被添加到堆栈中,随后是公钥。OP_CHECKSIG同时弹出签名和公钥,并根据公钥验证签名。如果二者匹配,则向堆栈中添加<1>。否则添加<0>。
出于我们即将在下一节详细说明的原因,P2PK实际上已经不再使用。
支付到公钥哈希(P2PKH)是现在最常见的交易类型。除非您特地去下载过时的软件,否则您的钱包很可能会默认采取这种交易。
P2PKH的scriptPubKey如下:
OP_DUP OP_HASH160 <public key hash> OP_EQUALVERIFY OP_CHECKSIG
在我们介绍scriptSig之前,我们首先来分析一下新的操作码的作用:
OP_DUP
OP_DUP弹出第一个元素,并复制这个元素。随后,它将二者同时添加回堆栈。通常,这样做是为了在不影响原始元素的情况下对副本进行操作。
OP_HASH160
这将弹出第一个元素,并进行两次哈希。第一轮将使用SHA-256算法进行哈希。随后使用RIPEMD-160算法对SHA-256输出进行哈希。结果输出被添加回堆栈。
OP_EQUALVERIFY
OP_EQUALVERIFY由另外两个运算符OP_EQUAL和OP_VERIFY组合而成。OP_EQUAL弹出两个元素,并检查其是否相同。如果相同,则将1添加到堆栈。如果不相同,则添加0。OP_VERIFY弹出顶部元素,并检查其是否为True(亦即非0)。如果不是,则交易失败。综合起来,如果顶部两个元素不匹配,则OP_EQUALVERIFY会导致交易失败。
这一次,scriptSig如下所示:
<signature> <public key>
您需要提供签名以及相应的公钥来解锁P2PKH输出。
您可以通过上面的GIF详细了解。它与P2PK脚本并没有太大的不同。我们只是添加了一个额外的步骤,来检查公钥是否与脚本中的哈希匹配。
不过,有一点需要注意。在P2PKH锁定脚本中,公钥是不可见的 — 我们只能看到它的哈希。如果我们前往
区块链浏览器并查看尚未被花掉的P2PKH输出,我们将无法确定其公钥。只有当收款人决定转移资金时才会被披露。
这有几个好处。首先就是,公钥哈希比完整的公钥更容易传递。正是出于这个原因,中本聪在2009年推出了公钥哈希。公钥哈希就是我们现在所知道的比特币地址。
第二个好处是,公钥哈希可以为
量子计算提供额外的安全层。因为我们的公钥只有在我们花费资金之后才会被知道,因此其他人就更难计算私钥。他们必须逆转两轮哈希(RIPEMD-160和SHA-256),才能得到私钥。
支付到脚本哈希(P2SH)对于
比特币来说是一个非常有趣的发展。它允许发送者将资金锁定到脚本的哈希 — 而无需知道脚本实际做了什么。以下面的SHA-256哈希为例:
e145fe9ed5c23aa71fdb443de00c7d9b4a69f8a27a2e4fbb1fe1d0dbfb6583f1
您不需要知道哈希的输入来锁定资金。然而,花费资金的人需要提供用于哈希它的脚本,并需要满足该脚本的条件。
上面的哈希就是通过下面的脚本创建:
<multiply by 2> <4> <check if equal>
如果您想花费绑定到这个scriptPubKey的代币,您不仅要提供这些命令。您还需要一个scriptSig,使完成的脚本的值为True。在这个示例中,即为您<multiply by 2>以得到<4>结果的元素。当然,这意味着我们的脚本只是<2>。
在实际情况中,P2SH输出的scriptPubKey为:
OP_HASH160 <redeemScript hash> OP_EQUAL
此处没有新的运算符。但是,我们确实有<redeemScript hash>作为一个新元素。顾名思义,这就是我们需要提供用于赎回资金额脚本哈希(称之为redeemScript)。scriptSig将根据redeemScript中的内容而变化。不过,通常情况下,您会发现它是签名和附加公钥的某种组合,随后是(强制性的)redeemScript:
<signature> <public key> <redeemScript>
我们的计算与目前所看到的堆栈执行略有不同。它分为两个部分。第一部分仅检查您是否提供了正确的哈希。
您会注意到,我们没有对redeemScript前面的元素执行任何操作。它们在这个时候还不会被用到。我们已经到了这个小程序的末尾,顶部元素为非0。也就是说,它是有效的。
但我们还没有完成。网络
节点将这个结构识别为P2SH,因此它们实际上已经让scriptSig的元素在另一个堆栈中等候。这就是要用到签名和公钥的地方。
到目前为止,我们一直将redeemScript视为一个元素。但现在,它会被解释为指令,这可以是任何东西。我们以P2PKH锁定脚本为例,我们必须向其提供与<redeemScript>中的<public key hash>匹配的<signature>和<public key>。
一旦您的redeemScript被扩展,就可以看到我们的情况看起来与常规P2PKH交易完全一样。之后,您只需要像正常程序一样运行它。
我们已经演示了所谓的P2SH(P2PKH)脚本,但您不太可能在自然环境中找到这样的脚本。没有什么可以阻止您制作一个,但却不会为您带来任何好处,而且最终会占用更多的区块空间(因此也成本更高)。
P2SH通常适用于
多重签名或
SegWit兼容交易之类的情况。多重签名交易的规模可能非常之大,因为它们需要多个密钥。在实施付费到脚本哈希之前,发送者必须在其锁定脚本中列出所有可能的公钥。
但对于P2SH,无论消费条件多么复杂,它都不会产生影响。redeemScript的哈希始终为固定大小。因此,成本会转移给想要解锁锁定脚本的用户。
SegWit兼容性是P2SH可以派上用场的另一个示例(我们将在下一节详细讨论交易结构的不同)。SegWit是一个软分叉,它导致了区块/交易格式的改变。由于它是一项可选择的升级,因此并非所有钱包软件都能识别这些变化。
如果客户端将SegWit脚本哈希封装在P2SH中,这一点无足轻重。和所有这种类型的交易一样,它们不需要知道解锁的redeemScript是什么。
如需理解SegWit中的交易格式,您只需要知道我们不再只有scriptSig和scriptPubKey。现在,我们还有一个被称之为“见证”的新领域。我们之前保存在scriptSig中的数据被移到见证,因此scriptSig为空。
如果您遇到过以“bc1”开头的地址,那么,这些地址就是我们所说的 SegWit原生(而不是SegWit兼容,因为它们是P2SH地址,因此它们以“3”开头)。
支付到见证公钥哈希(P2PKH)
支付到见证公钥哈希(P2PKH)是P2PKH的SegWit版本。我们的见证如下所示:
<signature> <public key>
您会注意到,这与P2PKH中的scriptSig相同。在这里,scriptSig是空的。同时,scriptPubKey类似如下:
<OP_0> <public key hash>
这看起很奇怪,对吧?让我们比较签名、公钥及其哈希的运算码在哪里?
我们并没有在这里展示额外的运算符,因为接收交易的节点根据<public key hash>的长度知道如何处理它。它们会计算长度,并理解它必须以与传统P2PKH交易相同的方式运行。
未升级的节点不知道如何以这种方式解释交易,但这无关紧要。在旧的规则下没有见证,因此它们会读取一个空的scriptSig和一些数据。它们会对其进行评估,并将其标记为有效 — 在它们看来,任何人都可以使用这些输出。这就是为什么SegWit会被认为是向后兼容的
软分叉。
支付到公钥哈希(P2PKH)
支付到公钥哈希(P2PKH)是新的P2SH。如果您已进展至这一步,或许就能弄清楚它的运作方式了,但无论如何,我们都会过一遍。我们的见证是我们通常放在scriptSig中的内容。例如,在打包P2PKH交易的P2WSH中,它可能看起来是这样子的:
<signature> <public key>
下面是我们的scriptPubKey:
<OP_0> <public key hash>
这里采用同样的规则。SegWit节点读取脚本哈希的长度并确定它是P2WSH输出,其评估方式类似于P2SH。与此同时,旧节点只不过将其视为任何人可以花费的输出。
在这篇文章中,我们对比特币的构建区块有了一定了解。我们来快速总结一下:
脚本类型 | 描述 |
---|
支付到公钥(P2PK) | 将资金锁定至特定公钥 |
支付到公钥哈希(P2PKH) | 将资金锁定到特定公钥哈希(即地址) |
支付到脚本哈希(P2SH) | 将资金锁定到收款人可以提供的脚本的哈希 |
支付到见证公钥哈希(P2PKH) | P2PK的SegWit版本 |
支付到公钥哈希(P2PKH) | P2SH的SegWit版本 |
深入研究比特币后,您就会开始理解为何它具有如此大的潜力。交易可能有多个不同的组成部分构成。通过操纵这些构建区块,用户可极其灵活地为使用资金的方式及时间设定条件。