交易是由外部账户发出的经过签名的消息,通过以太坊的网络传播,由矿工记录在区块链上。在这些基本定义背后,包含了很多令人称奇的细节。从另一个角度来看,交易是唯一能够触发区块链状态改变,或触发EVM上的合约执行的东西。以太坊是一个全局的单体状态机,交易是唯一能够让这台状态机向前推进并改变状态的东西。合约并不会自动运行。以太坊也不会在“后台”运行。所有这一切,都是由交易触发的。
首先我们来看交易在以太坊网络上打包和传输的基本结构。每个以太坊客户端或应用程序在收到这个打包的交易后都会把它保存在内存里,并且使用应用内部的格式,例如:增加原来交易数据包中没有的一些元数据。只有网络上传输的交易数据包的格式才是唯一通用标准的交易格式。
交易是一串打包在一起的二进制数据,
nonce
一个序列编号,由构建这个交易的外部账号提供,用于防止交易的重放攻击
gas price
交易发起方愿意支付的gas(单位:wei)价格
gas limit
交易发起方愿意为这笔交易支付的最大的gas数量
recipient
目标以太坊地址
value
发送给目标地址的以太币的数量
data
附在交易中的可变长度的数据
v,r,s
由构建交易的外部账户提供的椭圆曲线签名的三个组成部分
交易内容的结构采用递归长度前缀(RLP)编码标准。这个标准是为以太坊专门创建的,主要是为了打包准确且字节合适的数据。以太坊交易中所有的数字都采用大端模式编码,长度是8比特的倍数。
请注意,上面这些结构的名字(to,gaslimit等)都是为了清楚表达而列出的,但这些并不是以太坊交易数据包的内容,数据包中的RLP编码已经包含了字段的定义信息。总体来说,RLP编码中不会包含任何字段标识符或者标签。RLP的长度偏移量用来表示每一个字段的长度。任何超过定义的长度,就自然属于结构体中下一个字段的内容了。
尽管这就是以太坊上传输的交易的真实结构,但多数应用内部或用户界面的展现形态会有些不同,应用会在交易结构基础上增加一些额外的信息。这些信息是从交易数据包或区块链上衍生计算而来的。
例如:你会注意到交易数据包的格式中,并不包含发起这个交易的外部账户的所谓"from"地址。这是因为发起交易的外部账户的公钥,可以经由椭圆曲线数字签名算法v,r,s这三个组件计算得出。对应的外部账号的地址,也可以经由公钥推算得出。当你在界面中看到一个交易的"from"字段时,这是由钱包软件计算后得出的,用来显示交易的情况。其他由软件计算后添加到交易上的元数据包括区块编号(在挖矿后获得)和交易ID(交易的哈希)。这些数据都是从交易数据包推算出来的,并不包含在交易数据包之内。
nonce是交易中最重要却最难理解的一个概念。以太坊黄皮书中对Nonce的定义
Nonce:一个数值,等于这个地址发出的交易数量,当这个地址与合约关联时,是这个地址所常见的合约数量、。
严格来说,nonce是发起方地址的一个属性,也就是说它只在发送地址的上下文中才有意义。而且nonce也不会作为账户状态的一部分显式地保存在区块链上。相反,它是通过计算发送方地址已经确认的交易数量而动态计算的。
在两种情况下,交易数量随机数的存在是非常重要的:包含在交易创建顺序中的可用性特征。以及交易重复保护的重要特征。让我们看一下每个场景的示例:
1.你想进行两笔交易,其中一笔是需要支付6 ether的重要付款,另一笔交易需要支付 8 ether。
首先,你签名并广播了那笔6 ether的交易。因为它比较重要。
然后,你又签名并广播了第二笔8 ether的交易。遗憾的是,你忽略了一个事实,即你的账户只有10ether。所以(以太坊)网络不可能同时接受这两笔交易:其中一笔会失败。因为你首先发送了那笔更加重要的6ether的交易,于是你理所应当的认为它会被接受。而8ether的交易会被拒绝。但是,在像以太坊这样的去中心化系统中,节点会以任意顺序接收交易;没有任何方法能保证某个节点在另外一个节点之前接收到某笔交易。因此,几乎可以肯定的是,某些节点会先接收到 6ethrt的交易,而一些节点会先接收到8ether的交易。在没有随机数的情况下,接收与否完全是随机的。相反,如果包含了随机数,那么你发送的第一笔交易将具有一个随机数,假设是3,而8ether 的交易将具有下一个随机数(如图 4)。因此,这笔交易将被忽略,直到 nonce值为 0到 3的交易已经被处理,即便它是先接收到的。
2.你有一个包含100ether的账户,太好了。你在网上找到了出售你非常想要的 mcguffin挂件的卖家,并且他们接受以太币支付。你付给他们2ether。他们吧 mcguffin挂件发给了你。为了完成这笔2 ether的支付,你签名了一笔交易,将账户里的2 ether发送到 他们的账户,然后将这笔交易广播到以太坊网络,使其能够被验证并包含到区块链中。现在,在交易没有Nonce的情况下,再次将2 ether发送给同一地址的交易与第一次交易看起来完全相同。这就意味着任何能在以太坊网络上看见这笔交易的人(包括收件人和你的敌人),都能简单地通过复制粘贴你的原始交易一次又一次的重放该交易,直到你的以太币消耗殆尽。但是,如果加以数据中包含nonce值,那么每一笔交易都是唯一的,即便是多次向同一个收件人地址发送相同数量的以太币交易也是如此。因此,通过将递增的随机数作为交易的一部分,任何人都没有办法“复制“你已经完成的付款
总之,值得注意的是,与比特币协议使用的 “未花费输出”(UTXO)机制相比,使用随机数对于基于账户的区块链协议实际上是至关重要的。
从实践的角度而言,nonce就是发起交易那个地址目前位置所有确认(即链上)交易的计数器。为了获得nonce,你可以对区块链发起查询,例如;通过 web3接口。打开一个运行着 MetaMask的 JavAsCRIPT 控制台,或者使用 truffle控制台命令访问 Java Script web 3 库,然后输入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cogt25ue-1585488614904)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585394043566.png)]
nonce 是从零开始计数的,这意味着外部地址的第一笔交易的nonce是 0,上面的代码返回的Nonce是 40,这意味着在区块链上已经发现了这个地址从0到39的交易,下一个交易中应该写入的nonce值就是 40.
你的钱包软件会针对每一个地址跟踪和管理他们所对应的nonce值,这相对容易实现,因为只有你会使用这些地址发起交易。假设你在编写自己的钱包软件,或者其他某些可能发起以太坊交易的应用,你应该如何跟踪外部地址的Nonce?
创建一个新的交易时,你会按照记录把下一个Nonce值分配给新交易。但是要知道这笔交易被以太坊网络确认之前,交易中的nonce值都不会被计入getTransactionCount的返回值中。
糟糕的是,当我们一次提交几个交易时,这个getTranscactionCount函数可能会发生一些问题。这是getTransactionCount的一个已知的bug,因为它无法正确的计算待确认的交易。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4d2jAjYV-1585488614906)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585395122208.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-olztTeGj-1585488614907)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585395143648.png)]
如你所见,我们发送的第一个消息把nonce增加到了41,并显示为待确认交易。但是当我们很快再发送三个交易之后,getTranscationCount调用并没有正确的计算他们。即使待确认交易池中有三个交易,他也只记录了一个。我们得等待几秒钟。当这个区块被矿工写入区块链之后,getTranscationCount调用才会返回正确的值。但是当初在交易待确认的中间状态时,如果有超过一个交易处在这个状态,那么getTranscationCount就无法返回正确的Nonce值。
当你的应用程序需要构建和发布交易时,不能依赖GetTranscationCount这个函数来计算Nonce值,只有当待确认交易和确认交易相等(所有的待确认交易都确认了)的时候,你才可以把getTranscationCount作为Nonce计数,在此之后,在应用程序中保持对Nonce的追踪,直到每一笔交易都得到确认
Parity 的JSONRPC 接口提供了一个 parity_nextNonce函数,它返回了可供下一个交易使用的Nonce值。parity_nextNonce这个函数的计算方式是正确的。即使同时构建多个交易并且这些交易都没有得到确认,nonce的值也不会被计算错误。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vatjnIE3-1585488614909)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585395852878.png)]
Parity有一个供JSONRPC接口调用的web界面,但是这里我们使用命令行发起 HTTP访问。
如果你通过编程的方式创建交易,那么正确的跟踪记录Nonce的值就非常重要,特别是在你的应用可能会使用相同的外部账户地址同时提交多个交易的情况下。
以太坊网络是根据Nonce数值的顺序处理交易的。这意味着如果你发出了一个Nonce值为0的交易,紧接着又发出了一个Nonce的值为2的交易,那么第二个交易不会被包含在任何区块中。他会被保存在待确认交易内存池中,以太坊网络会一直等待Nonce值为1的交易出现。所有的节点都假设那个nonce值为1的交易因为网络延迟还没有传送过来,而Nonce为2的交易却先被接收到了。
如果这时你在创建一个Nonce值为1的交易,那么这两个交易(nonce值为1和2)都会被确认并写入区块链。这就是说,当你填补了nonce计数中缺少的部分之后,后续在待确认内存池中的交易都会得到以太坊的处理,并最终写入区块链。
这也意味着,如果你按顺序创建了一系列交易,但其中一个没有得到确认,那么之后的所有交易都会“堵住”,等待这个缺失的交易,如果某个交易的nonce值不对,或者没有足够得gas
,就很可能会导致这样的堵车现象,为了疏通堵车,你必须创建一个正确的交易,并使用缺失得那个nonce值。同样值得注意的是,一旦网络验证了“缺失”的交易,那么具有后续随机数的所有交易都会被广播,并逐渐变得有效;而且无法撤回任何交易。
另一方面,如果你不小心创建了重复的交易。例如:发出了两个具有相同nonce值的交易,但是收款地址或交易金额不同,那么其中一个会被确认,而另外一个会被拒绝。最先到达以太坊网络中确认节点的那个交易会被确认(这会相当随机)
如你所见,追踪nonce的值是非常有必要的,如果你的应用程序没有正确的管理Nonce,那么你就会遇到麻烦。糟糕的是,在并发交易的情况下,这个问题会变得更加困难。
处理并发是计算机科学中的一个难题,有时会在意想不到的情况下出现并发问题,尤其是去中心化的,分布式的,实时处理的系统中。如以太坊
简单的说,并发就是指多个独立的系统同时进行一项计算任务,可以是在同一个程序中(例如多线程),在同一颗CPU上(例如多进程),或者在不同的计算节点上(分布式系统),以太坊是一个允许并发操作(包含节点,客户端,去中心化的应用)。但通过共识强制维持单体状态的系统。
假设我们有多个独立的钱包软件,正在使用相同的外部账户地址生成交易。这种情况的例子可以是交易所处理从热钱包中提币的操作(热钱包中的密钥是在线存储的,而冷钱包则不联网)。理想情况下,你一般会有不止一台计算机用于处理这些提币申请,这样不至于出现处理瓶颈或者单点故障。然而,这很快会引发另一个问题。使用多于一台计算机处理提币申请时,会导致一些令人头疼的并发问题,不仅仅是如何设定Nonce的问题。如何使用多台计算机,针对同一个热钱包进行地址生成签名和广播交易呢?
你应该使用一台计算机来分配Nonce,以先到先得的方式给那些需要对交易进行签名的计算机。然而,这台计算机就会成为单点故障。更糟糕的是,如果分配了多个nonce,但是由于某些原因,有一个nonce没有被使用(例如:处理哪个Nonce的计算机正好出故障),那么随后所有的交易都会堵住
你也可先生成交易,但是不签名,或分配Nonce(由于nonce是交易数据的一部分,因此必须包含在用于认证交易的数字签名中)。然后把这些交易传送到一台计算机上排队处理,逐一给他们分配nonce,并对签名交易。不过,这也会导致单点故障的存在。当处理量比较大的时候,进行签名和分配nonce的操作就会成为一个拥堵的节点。尽管这些没有被签名的交易并非一定需要并发处理。我们虽然名义上使用了并发,但是在关键环节上并没有带来任何用处。
最终,这些并发问题引发的在独立进程中追踪账户余额和交易确认的难题,迫使多数软件实现过程放弃使用并发,而不得不使用可能导致瓶颈的单一进程来处理所有的交易所提币操作,或者使用完全独立处理提币操作只需间歇性重新计算余额的热钱包
gas就是以太坊的燃料,gas并不是以太币,它是一种独立的虚拟货币,跟以太币之间存在汇率关系。以太坊使用gas来控制交易对资源的使用。因为交易会在数以千计的以太坊节点上被处理。开放型(图灵完备)的计算模式需要一种计量单位,以确保避免拒绝服务式攻击或过度消耗资源的交易。
gas独立于以太币,是为了在以太币价格大幅度波动的情况下,仍旧保护系统的灵活性,同时,对于各种消耗gas的资源(比如,计算,内存和存储),gas能够管理他们之间重要而敏感的汇率关系
交易中的gasPrice字段允许交易的发起方设定针对gas的汇率。gas价格的计量单位是 wei/gas,例如,我们刚才在本书中创建的交易示例,钱包软件把gasPrice设定为 3gwei(等于30亿wei)
知名网站https://ethgasstation.info提供了当前gas的兑换价格,以及其他用于以太坊网络的gas相关指标。
钱包软件可以调整它们所发起的交易中的gasPrice值,这样就可以让交易更快得到区块链的确认(更快被矿工打包)。gasPrice越高,交易被确认的速度也越来越快。与之对应的是,相对低优先级的交易可以支付比平均值低的gas费用,这样它们被确认的时间也会较长。gasPrice最低可以为零,这意味着没有手续费的交易。如果区块中尚有多余的空间,那么这样的交易也会被矿工打包并提交到区块链上。
gasPrice最低可接受的值是零,这意味着钱包软件可以生成完全不包含任何手续费的交易,取决于区块的容量。这样的交易有可能永远也不会在区块链上被确认,但是以太坊协议本身并没有禁止无手续费交易。你可以在目前以太坊的区块链中找到好几笔类似的无手续费交易,这些交易都已经成功完成。
通过计算最近几个区块中的价格中值,web3接口提供了一个建议的gasPrice(我们可以使用 truffile控制台或者任何javaScript Web3控制台)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-taXe3sn2-1585488614910)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585406097082.png)]
与gas相关的第二个重要字段是 gasLimit.简单说,gasLimit表示这个交易的发起方为了完成交易所愿意支付的最大GAS数量。对于简单的以太币转账来说,从一个外部账户转账到另一个外部账户,所需要的gas数量是固定的 21000单位个 gas.为了算出这些 gas对应的以太币数量,只需要使用 21000乘以gasPrice.比如:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YEOSnbix-1585488614911)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585406316211.png)]
如果你的交易目标地址是一个合约,那么需要gas的数量可以估计,但是很难精确算出,因为合约可以根据不同的初始条件选择不同的执行路径,这样会导致不同的gas开销。这意味着合约可能只执行一个简单的计算,也可能是更复杂的计算。这些可能取决于一些你无法控制或预判的外部因素。为了解释清楚这个概念,我们举个例子:
假设有这样一个智能合约,每次被调用时,它的计数器都会加一,并且执行一个特殊的循环,执行次数等于调用次数。有可能在第100次调用是,会出现一个特殊的奖励(像中彩票一样).但是需要额外的计算量才能算出奖励,如果你调用了合约99次,都只会发生一件事情,但是第100次调用却完全不同。你所支付的gas数量取决于在你的交易被包含进区块之前,这个合约已经被调用了多少次。可能你所预估的gas是基于99次调用的。但是恰好在你的交易被矿工确认之前,其他人发起了第99次调用。现在你变成了发起第100次调用的那个人,这次调用所对应的计算量,也就是你需要支付的ga开销相比之前会大很多。
借助以太坊常用的比喻,你可以把gaslimit字段想象为汽车的油箱(这里把交易比喻为汽车)。你会在启程前加够你认为这一路所需要的汽油(完成交易所需要的计算量).你可以适当地估计油量,但是由于路上的突发情况,或者临时的线路更改(合约转入一个更复杂的计算分支),这些都可能增加汽车的油耗。
也许油耗的比喻有一些误导,这更像一个用于加油站的信用账户·。你可以在旅程结束后支付,基于实际的gas开销。当你发出交易时,验证交易的第一个步骤就是确保发起交易的账户中有足够的以太币来支付对应的gas费用(即gasPrice*gas).但是这时并不会从账户中直接扣除gas,而是直到交易执行结束后才会扣除。你只会被扣除交易所执行实际发生的gas.但是账户中必须有高于你在交易中指定的最高的gas费用对应的以太币,这个交易才能通过验证。
交易的接收方在to字段中指定,这个字段包含了一个20字节的以太坊地址。这个地址既可以是外部账户,也可以是合约的地址
以太坊不会进一步验证这个地址,任何一个20字节的值都被认为是正确的。如果这20字节是一个没有私钥的地址,或者没有对应的合约。这个交易依然合法。以太坊无法判断这个地址是否是从已知的公钥(因而这个地址会有一个可以解锁的私钥)衍生而来的
以太坊协议也不会验证接收方地址的正确性,你可以向一个没有对应私钥的“地址’'发送以太币。这就相当于是销毁以太币。接收方地址的验证工作需要在用户应用这一层完成。
向一个错误的地址发送交易导致以太币销毁,使其无法再次被访问(不可被花费),这是因为大部分地址没有已知的私钥,也就无法生成签名去使用它,通常认为验证地址的工作应该在用户应用这一层。实际上,也存在一些合理的销毁以太币的场景,如支付通道和其他合约存在欺诈行为时,由于以太币是有限的,销毁以太币能有效地将价值传到所有以太币持有者手上。
交易数据包的核心是两个字段:value和 data.交易可以同时包括value和data,只有value只有data或既没有value也没有data这几种情况都是正确的且合法的
只包含value的交易是支付操作。只包含data的交易是针对合约的调用。既没有value也没data的交易也许只是为了浪费gas,但是也是允许的
我们来尝试所有以上这些情况。首先,为了让演示更加直观,我们使用两个变量来设定钱包软件的发起地址和目标地址:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-czhRTguZ-1585488614912)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585409730273.png)]
我们的第一笔交易只包含付款,不包含数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nxqPF74I-1585488614914)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585409774082.png)]
钱包软件会显示一个确认页面,包含发送的以太币的值,同时显示交易中的data字段没有内容:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rBZfgoQF-1585488614915)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585409849953.png)]
接下来这笔交易同时包含付款和数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rI4AirBC-1585488614917)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585409879149.png)]
钱包软件会显示一个确认页面,显示本次交易发送的以太币为零,同时显示交易中data字段的内容;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iha4bfh8-1585488614918)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585410112580.png)]
最后一笔交易既不包含付款,也不包含数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KKuvLWDB-1585488614919)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585410158360.png)]
钱包软件会显示一个确认页面,显示本次交易发送的以太币为零,同时显示交易中附加的data字段没有内容
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2dD8v9Cm-1585488614919)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585410257447.png)]
当你构建的以太坊交易中包含value,也就是转账支付的以太币数量时,交易的行为模式取决于你的目的地址是另一个外部账户,还是一个合约
对于外部账户,或者任何在区块上没有注册为合约的地址,以太坊会记录这一次的状态变化,在目的账户余额中增加你所指定的以太币。如果你这个地址之前不存在,那么以太坊会在区块链上创建这个地址,把它的余额初始化你在支付交易中指定的value值
如果目标地址(to)是一个合约,那么EVM会执行这个合约,并尝试调用在交易的data字段中指定的函数。如果交易中的data字段是空的,那么EVM会指定目标合约的回退函数,如果这个会退函数是可支付的,那么根据函数的代码来决定下一步的执行动作。如果没有回退函数,那么交易的效果就是增加合约的余额,如同向钱包支付一样
合约可以在可支付函数被调用时,立刻通过抛出异常的方式拒绝转入的支付,也可以根据可支付回退函数的逻辑做出决定。如果可支付回退函数正常执行(没有异常0,那么合约的状态就会被更新,以此反映出合约账户余额的变化。
当交易的data字段含有内容时,多数情况下这个交易的目标是一个合约。这并不意味着你不能通过交易向外部账户发送data字段。实际上,你完全可以这么做。然而,在这种情况下,对data字段内容的解读取决于目标外部账户的主人所使用的钱包软件。以太坊协议并未对此作出规定。大部分钱包软件会忽略针对外部账户交易中包含在 data字段中的内容。在将来,也许会有一些标准来规范化钱包软件如何使用交易中包含在data字段中的内容,这样也许可能允许用户调用运行在用户钱包内部的函数,非常重要的一点区别在于,外部账户对交易中的data内容的解读和使用,都不属于以太坊区块链共识的一部分,这是完全不同于合约执行的。
现在我们假设你的交易包含data字段的内容,目标是一个合约地址,在这样的情况下,data字段的内容会被EVM解读为针对合约的函数调用,调用data中指定的函数,并把需要的参数传递给这个函数。
发送给合约的data字段内容是通过十六进制编码的:
函数选择器:
被调函数原型的Keccak-256哈希值的前4个字节。这允许EVM准确无误的识别被调函数
函数参数
函数的参数,根据EVM多种实现规则定义的编码结构
在代码2-1中,我们定义了一个用于提币的函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GYXDjvey-1585488614920)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585450146356.png)]
这个方法的原型定义为包含函数名称和圆括号内每一个参数类型的字符串。函数的名称withdraw,它接收一个参数,类型是 unit(也就是unit256).所以这个函数的定义就是
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rKAXXpJi-1585488614921)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585450231247.png)]
我们来计算它的Keccak-256哈希值:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IbHm0L2l-1585488614922)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585450266549.png)]
这个哈希值的前4个字节是 0x2ela7d4d.这是“函数选择器”的值,用于告诉EVM调用的哪一个函数
。接着我们来计算作为参数传递给 withdraw-amount的那个值。我们希望提取0.01ether.因此把这个值编码为十六进制大端字节序无符号256位整数,采用wei为单位:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nzTR7S1O-1585488614923)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585450453748.png)]
现在,增加函数选择器之后的调用内容如下(填满了32字节)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PKwEIjbj-1585488614923)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585450492385.png)]
这就是我们这个交易中data字段的内容,调用withdraw方法并通过withdraw-amount参数请求提取0.01ether
有一种特殊的交易值得我们关注,即在区块链上创建新合约的交易。这些合约用于未来有需求的场合。合约注册交易的目的地址是一个特殊的地址----零地址。简单的说,合约注册交易的to字段包含的是 0x0.这个地址既不是一个外部的账户地址(没有与之对应的私钥或者公钥),也不是一个合约地址。这个地址永远都不能用来支付以太币或者触发交易。它只是作为一个目标,一个带有特殊的’注册合约’含义的目标地址。
尽管全零地址仅用来进行合约注册,但有时候这个地址也会收到来自其他账户的以太币转账。对这个情况有两种解释:一种情况是由于误操作,这就导致了以太币的丢失;另一种情况是有意的销毁以太币(故意把以太币发到永远不会花费他的地址0.如果你希望有意的销毁一些以太币,那么需要向网络明确表示你的意图,使用这个特殊的销毁地址。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M6N6Vjz3-1585488614924)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585451615367.png)]
任何发送合约注册地址或专用销毁地址的以太币都无法继续使用,并且被永久性地锁定在了这些地址上。
合约注册交易中唯一需要的就是在data字段中包含经过编译的合约字节码。这个交易的唯一用处就是把合约注册到以太坊区块链上。你可以在value字段中包含以太币,从而为新的合约设置起始余额,但这完全是可选的。如果你向合约注册地址发送了一笔只有value(以太币),而没有ata数据字段的交易,那么这笔交易的效果和销毁以太币一样:没有新的合约可用,以太币却丢失了。
举个例子:我们可以手动向零地址发送包含data值的交易,注册2中的 Faucet.so合约。合约需要被编译成字节码表示。这可以通过solidity编译成完成:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U3zKFIaT-1585488614925)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585453072916.png)]
同样的结果也可以从Remix在线编译器获得
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CxPMPG1P-1585488614928)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585453124531.png)]
始终为to 指定参数是良好的做法,即便是在向零地址创建合约的情况下,因为不小心将你的以太币发送给 0x0而导致其丢失的成本太高了。同样,你应该为 gasPrice和 gasLImit指定值
一旦这个创建合约的交易被矿工打包,我们可以在以太坊区块浏览器Etherscan 中查看到它:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5DC6l6TG-1585488614930)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585453297708.png)]
我们可以查看交易收据以获得合约相关的信息:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ketpTzGi-1585488614931)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585453425972.png)]
其中包含合约的地址,我们像之前讲到的那样向合约发送资金或者从合约中获得资金
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hZqrqVNp-1585488614932)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585456272768.png)]
不久之后,所有的交易都可以在 Etherscan 中被查看到。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gkGaDfLi-1585488614933)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585456307548.png)]
目前为止,我们还没又深入讨论过数字签名。在这一节,我们来看看数字签名的工作方式,以及它们如何在证明私钥的同时不需要对外公布私钥
以太坊中使用的数字签名称为椭圆曲线数字签名算法,简称ECDSA。ECDSA是一套基于椭圆曲线私钥和公钥密钥对的数字签名算法,
以太坊中的数字签名起到了三个作用。第一,数字签名用来证明签名方式私钥的持有人。因此也就是对应以太坊账户的主人,用于授权以太坊的转账或合约的执行。第二,用于证明这个’授权’是不可否认的。第三,用于确保交易数据在经过签名之后没有也不能被任何人修改
维基百科上关于数字签名的定义
数字签名是一种数学标准,用于证明某个数字消息或文档的真实性。一个合法的数字签名可以让接收方相信消息来自于已知的发送方(身份认证),发送方不能否认发送过这个消息(不可否认),同时这个消息在传输过程中是无法被修改的(完整性)
数字签名的数学标准中包含两个部分
第一部分适用于创建签名的算法,针对一个消息(例如我们的以太坊交易数据包)使用私钥(签名私钥)进行签名
第二部分是一个允许任何人使用消息和公钥进行签名验证的算法
在以太坊的ECDSA中,被签名的’消息’是交易数据包,或者更准确的说,是经过RLP编码的交易数据包的 Kecak-256哈希值。签名密钥是外部账户的私钥,这个算法的输出结果是一个数字签名
Sig=Fsig(Fkeccak256(m) , k)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aYikRJ2y-1585488614934)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585457454591.png)]
其中:
K是用于签名的私钥
m是经过RLP编码的交易数据包
Fkeccak256是 Keccak-256哈希函数
Fsig是签名算法
Sig是输出的数字签名
函数Fsig生成了一个签名,这个签名包含两部分内容,通常被称为 r和s
Sig=(r,s)
为了验证签名,我们必须有签名本身(即r和s),交易数据包和公钥(对应着生成这个签名的私钥).
验证签名意味着:只有私钥的持有者才能够针对交易生成这样的签名
签名验证算法的输入包括交易数据包(其实是交易的哈希值的一部分),签名方的公钥和签名(r和 s值0,如果针对消息和公钥的签名验证成功,算法会返回true
如之前提到的,签名是通过数学算法 Fsig生成的,这个签名包含r 和 s 两个值,
签名算法首先以密码学安全的方式生成一个临时的私钥。这个临时的密钥对用于计算r和s的值,以确保攻击者无法通过在以太坊上查看已签名的交易来计算发送者的真实密钥
临时私钥是由以下两个输入决定的:
一个密码学安全的随机数q,作为临时私钥
从q生成的临时公钥Q,以及椭圆曲线上的生成点 G
数字签名中的 r值就是临时公钥Q的x轴的值
在此基础上,算法计算数字签名的s值,通过如下方式
s≡q-1(Keccak256(m)+r*k)(mod p)
其中;
q是临时私钥
r是临时公钥对应的x轴坐标
k是用于签名的私钥(外部账户持有人的私钥)
m是被签名的交易数据包(的哈希值)
p是椭圆曲线上的素数阶
验证签名是一个相反的操作,使用r,s和公钥的值来计算Q,
Q是椭圆曲线上的一个点(用于本次签名创建的临时公钥),具体步骤如下;
1.检查所有的输入都是正确的形式
2.计算w=s-1mod p
3.计算u1=Keccak256(m) * w mod p
4.计算u2=r * w mod p
5.计算椭圆曲线上的点Q≡u1* _ G + u2* K (mod p)
其中:
r和s是数字签名的值
k是签名方(外部账户持有人)的公钥
m是被签名的交易数据包
G是椭圆曲线上的生成点
p是椭圆曲线上的素数阶
如果计算得到的Q点对应的x轴坐标等于r,那么就可以认定这个签名是合法的
注意在整个签名验证过程中,并不需要对外提供私钥
ECDSA的数学实现是非常复杂的,远远超出了本书的范围。网上有一些非常详细的关于椭圆曲线的介绍,请搜索“ECDSA Explained",或访问https://www.instructables.com/id/Understanding-how-ECDSA-protects-your-data/。
为了生成一个正确的交易,交易的发起方必须在交易的数据包中附带上使用椭圆曲线数字签名算法生成的数字签名。当我们说“对某个交易签名’'时,实际上是指”对采用RLP编码的交易数据包Keccak-256哈希值“进行签名。签名是针对交易数据包的哈希值进行的,而不是针对交易数据包本身
在以太坊中签名一个交易,发起方需要:
1.生成一个交易数据包,包括九个字段:nonce,gasPrice,gasLimit,to,value,data,chainID,0,0
2.把交易数据包进行RLP格式编码
3.计算这个交易的Keccak-256哈希值
4.计算ECDSA签名,使用交易发起方的私钥进行签名
5.在交易中插入经过ECDSA签名获得的v,r和s的值
特殊签名变量v代表两件事情:链ID,以及帮助ECDSArecover函数检查签名的恢复标识符。它被计算为27或28,或者是将链ID加倍后再加上35或36.
恢复标识符(在“老式”签名中是 27或者28,在完整的SpuriousDragon式签名中是35或者36),用来表明公钥的y分量的奇偶性
以太坊在区块高度2675000处实施了名为“Spurious Dragon"的硬分叉,伴随着其他变化,这次硬分叉还引入了包括交易重放保护在内的新签名方案(阻止在同一网络中重放他人交易的意图)。这个新的签名方案在EIP-155中被引入。这次更改会影响交易的形式及其签名,因此必须注意三个签名变量中的第一个(即v),它采用两种形式表明交易中的数据字段应该如何被哈希。
这一节我们来创建一个原生交易并对它进行签名,使用 ethereumjs-tx这个程序库。这个例子展示了钱包中的常用功能,以及应用程序如何为用户签署交易。例子的源代码位于GITHub仓库
(https://github.com/ethereumbook/ethereumbook/blob/develop/code/web3js/raw_tx/raw_tx_demo.js)的raw_tx_demo.js文件中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JjnKB1eA-1585488614935)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585482658699.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H4bEoZWF-1585488614937)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585482682615.png)]
运行上述示例代码会产生输出结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-brL0RU9A-1585488614937)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585482721174.png)]
名为“简单重放攻击保护”的EIP-155标准制定了带有重放攻击保护能力的交易的编码格式,这个标准在交易签名前包含了一个链标识符。这确保在一个以太坊区块链(如以太坊主网)上创建的交易在其他以太坊区块链(如以太坊经典或者Ropsten测试网)上是不合法的。因此,在一个网络上广播的交易被重放到另一个网络,这就是重放攻击保护名字的来源。
EIP-155在包含在六个字段的交易的数据结构中添加了三个字段,分别是链标识符,0,和0.这三个值在交易被编码和哈希之前加入数据结构中。因此这三个值会改变交易的哈希值,这个哈希值之后是用来被签名的。通过在交易数据中插入链标识符,交易的签名可以防止对链标识符的更改,因为如果更改了链标识符,那么签名也就失效了。因此,EIP-155可以确保交易无法在其他以太坊网络上进行重放,原因是交易的验证依赖于未经修改的链标识符。
链标识符会根据交易处在的以太坊网络取不同的值,见表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GiDY6MLB-1585488614938)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585483366419.png)]
产生的交易数据经过RLP编码,哈希并签名。这个签名算法经过少量的修改,以对v前缀中的链标识符进行编码
更多细节请参考EIP-155规范(http://bit.ly/2CQUgne)。
在本章”交易的结构“一节中我们曾提到,交易消息中并没有包括任何from字段。这是因为交易发起方的公钥可以通过ECDSA的签名计算得到。获得了公钥之后,就很容易计算出发起方的以太坊地址。这个过程被称为公钥恢复
对于给定的供椭圆曲线进行计算的r和 s,我们可以算出两个可能的公钥
首先,我们通过签名中r的值的x坐标,计算椭圆曲线上的点R和R’。因为椭圆曲线是关于x轴对称的,所以对于x值,在椭圆曲线上有两个满足的点,分列在x轴的两侧
通过r,我们也可以计算出r-1,这是r的模乘法逆运算
最终我们可以计算得到z,这是交易哈希值低位的第n位,其中n是椭圆曲线的阶
K1=r-1(sR-zG)
和
K2=r1(sR-zG)
其中:
K1和·K2是交易发起方的两个可能的公钥
r-1是签名中r值的模乘法逆运算
s是签名中的s值
R和R是临时公钥的两个可能的值
z是交易哈希值低位的第n位
G是椭圆曲线的生成点
为了让事情更高效,交易签名中包含了一个v字段,用于告诉我们哪一个R值是临时公钥。如果v是偶数,那么R就是正确的值。如果v是奇数,那么R就是临时公钥。通过这样的方式,我们只需要计算R就可以得到k的值
当交易被签名后,它就可以被广播到以太坊网络。通常交易的创建签名和广播都是在一个操作中完成的。例如; web3.eth.sendTranscation.然而,如我们在本章“原生交易创建和签名”一节中所见,你可以把创建和签名交易的动作分成两步来执行,签名完成后,你可以使用 web3.eth.sendSignedTransaction广播交易,这个方法接受十六进制编码和经过签名的交易数据包,并把这个数据包广播到以太坊网络上
为什么我们想要把交易的签名和广播分开处理呢?主要原因是安全。用于签名的计算机必须保存在外部账户的私钥,用于广播交易的计算机必须连接在互联网上,并且运行在以太坊客户端。如果这两个操作在用一台计算机上完成,那么就相当于把私钥放置在了一台联网的计算机上,这是非常危险的。把交易的签名和广播分开处理的做法称为离线签名(联网的设备和未联网的设备分别处理),这是一种非常常见的安全实践
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XtAQsh13-1585488614939)(C:\Users\xiaoweifeng\AppData\Roaming\Typora\typora-user-images\1585485620839.png)]
1.为当前状态的账户通过在线的计算机创建一个未签名的交易,可以检索该账户当前的Nonce和余额
2.将未签名的交易转移到’空气隔离’的离线设备以便进行签名,如通过QR码或USB闪存设备
3.将已经签名的交易转移回在线设备,以便在以太坊区块链上进行广播,如通过QR码或USB闪存设备
取决于我们所需要的安全等级,用于“离线签名’'的计算机可以有不同等级的安全防护,可以是处在防火墙之后的专用子网中的计算机,也可以是一台完全离线的计算机(我们称之为空气隔离)在”空气隔离“的系统中根本就没有网络连接,计算机完全隔离于任何网络环境。在隔离环境万恒交易签名之后,你需要通过介质存储,或者网络摄像头和QR码的方式把签名后的数据包转移到在线计算机。这也意味着每一笔交易都需要这样的方式才能转移到在线计算机,这太不方便了
很多情况下完全空气隔离的系统时不可用的,但是即使是一定程度的隔离,也会带来极大的安全优势。例如,一个在防火墙之后的专用子网,只允许消息队列协议通过,这相比在网上系统出了签名而言,攻击面更小,更安全。很多公司在交易签名计算机使用 ZeroMQ(0MQ)消息队列,他的攻击面很小。使用这样的设置,交易通过消息队列完成签名。消息队列协议采用类似TCP数据包的方式把交易数据包发送给签名计算机。签名计算机从消息队列中读取交易数据包,使用恰当的密钥对交易进行签名,然后把签名之后的交易数据包发回到回复消息队列。这个回复消息队列接着把交易发送给带有以太坊客户端的联网计算机,进行交易广播
以太坊网络使用“洪泛”路由协议。每一个以太坊客户端都是P2P网络上的一个节点,这些节点构成了一个网状的结构。没有任何节点是特殊的,他们之间的身份是对等的。我们会使用“节点”来代表连接在P2P网络上的以太坊客户端
交易的广播从一个发起以太坊节点的交易创建(或从离线设备接收)和签名开始。交易通过验证后,会传送给所有跟这个发起节点直接相连的以太坊节点。平均而言,每一个以太坊节点大约维护了至少13个跟他直接相连的其他节点,称为邻居。每一个邻居节点都会在收到交易数据包后立刻进行验证。如果他们确认交易数据包时合法的,这些节点就会保留一份交易副本,然后把交易数据包广播给与他们相连的邻居(除去消息来源的那个上游节点)。因此交易就像水中的波纹一样,在整个网络中迅速传播开,直到所有节点都收到了一份交易的副本。节点可以对所广播的消息进行过滤,但默认情况下·,节点会广播收到所有的验证消息。
在短短的几秒钟时间内,一个以太坊交易就能到达全球范围内所有的以太坊节点。从单个节点的角度而言,它无从知晓这个消息的来源。向这个节点发送交易的邻居,可能只是扮演了“二传手’'的角色,但也可能是交易的真正发起方。为了追踪到交易的最初发起方,或者影响交易在以太坊中传播的形式,攻击者必须要控制网络中的大部分节点。这是P2P网络安全和隐私性的设计,对于区块链而言尤为重要。
尽管以太坊网络上的所有节点都是平等的,但有些节点会扮演矿工的角色,这些矿工把交易打包成区块链,投入到具备高性能GPU的矿场中,这些挖矿用的节点把一些交易打包成候选区块,使用“工作量证明’'算法完成挖矿并达成全网共识。
不需要深入细节,合法验证过的交易最终会被包含在一个区块中,并且记录在以太坊区块链上。计入区块链之后,交易就会更改以太坊网络的单体,修改账户余额(对于转账类交易而言),或者调用改变内部状态的合约。这些变化在交易进行的同时被记录,称之为交易的收据,也可能会包含一些事件
一笔交易完成了他从创建,由外部账户私钥签名,广播最终写入区块链的旅程。它会触发以太坊单体状态的修改,并在区块链上留下自己的印记
如果你对比特币脚本的功能很熟悉的话,就会知道创建多重账户是可能的,该账户只有在多方共同签名的情况下才能花费其中的资金(比如2或者3或者4个签名中的2个)以太坊基本的外部账户值交易并没有提供多重签名的功能,但是,任何有关签名的限制都可以通过智能合约来强制执行,包括你能想到的关于交易以太币和其他通证的任何条件。
为了利用这种能力,以太币必须被转移到’钱包合约’,该合约对支出规则进行了编程,例如:多重签名或者要求支出限额(或者两者的结合)。一旦满足支出条件,钱包合约将在外部账户的授权提示下进行支出。所以,要想在多重条件下保护你的以太币,请将以太币转移到多重签名合约。每当你想讲资金发送到另外一个账户时,所有必要的用户都需要通过常规的钱包应用向合约发送交易,从而有效授权合约执行最终的交易
这些合约还可以被设计为在执行本地代码之前需要多个签名或者触发其他合约。这个方案的安全性最终由多重签名合约的代码所决定
能够为只能合约实现多重签名的能力证明了以太坊的灵活性。但是,这是一把双刃剑,额外的灵活性可能会导致bug,从而破坏多重签名方案的安全性。实际上,有许多提案建议在EVM中添加多重签名命令,以消除对智能合约的要求,至少对于简单的 M-OF-N多重签名方案来说是这样的。这就相当于比特币的多重签名系统,它是核心共识规则的一部分,并且已被证明时健壮和安全的。
者4个签名中的2个)以太坊基本的外部账户值交易并没有提供多重签名的功能,但是,任何有关签名的限制都可以通过智能合约来强制执行,包括你能想到的关于交易以太币和其他通证的任何条件。
为了利用这种能力,以太币必须被转移到’钱包合约’,该合约对支出规则进行了编程,例如:多重签名或者要求支出限额(或者两者的结合)。一旦满足支出条件,钱包合约将在外部账户的授权提示下进行支出。所以,要想在多重条件下保护你的以太币,请将以太币转移到多重签名合约。每当你想讲资金发送到另外一个账户时,所有必要的用户都需要通过常规的钱包应用向合约发送交易,从而有效授权合约执行最终的交易
这些合约还可以被设计为在执行本地代码之前需要多个签名或者触发其他合约。这个方案的安全性最终由多重签名合约的代码所决定
能够为只能合约实现多重签名的能力证明了以太坊的灵活性。但是,这是一把双刃剑,额外的灵活性可能会导致bug,从而破坏多重签名方案的安全性。实际上,有许多提案建议在EVM中添加多重签名命令,以消除对智能合约的要求,至少对于简单的 M-OF-N多重签名方案来说是这样的。这就相当于比特币的多重签名系统,它是核心共识规则的一部分,并且已被证明时健壮和安全的。
交易是以太坊系统进行每项活动的起点。交易作为’输入’,可以导致以太坊虚拟机执行合约,更新余额以及更改以太坊区块链的状态。