查看原文
其他

干货 | 以太坊上发送交易的九种办法

Aodhgan Gleeson 以太坊爱好者 2020-02-12

 

本文的目的是为在以太坊生态中发送交易使用的各类技术,模式和机制提供一个指南。由于新技术层出不穷,本文也会随之更新,所以可以认为是未完待续的一个状态。

针对这个公认的大课题,本文将包含以下内容:

  • 以太坊交易的简明介绍

  • Gas 的用途和 gas 代币

  • 元交易

  • 潜艇交易

  • 反事实合约实例化

  • 零确认交易

  • 批量转账

  • 基于短信的付款

  • 订阅付款

  • 通过预言机合约为批量交易节省 Gas

  • 使用一次性地址进行多笔付款


背景知识


以太坊是一个基于账户的系统,目前有两种账户:普通账户和合约账户。这两种账户都有自己的以太坊地址,交易计数器 Nonce 和余额。合约账户还额外拥有不可变的代码以及相应的存储空间。这里有一篇介绍这些基本概念的好文章。

一个以太坊交易包含以下关键字段:

  • Nonce,或者说交易计数器,即该账户主动发起的交易数量,从0开始计数

  • gas Price,决定了该笔交易需要支付的 Ether 数量

  • gas Limit,即处理该交易所允许的最大 gas 数量

  • 目标地址,即接受该笔交易的对象,如为空,则该交易会创建一个新的合约

  • 交易金额,即发送的 Ether 数量

  • 数据,即可以是任意的一条文本消息,也可以是某合约的一次调用或者创建新合约的一段代码

请注意,以上关键点在于没有“交易发起”地址,因为该地址可以从生成该笔交易哈希值签名的公钥-私钥对推导出来,其中交易字段采用了适当的 RLP(递归长度前缀)编码。


Gas 用途和 Gas 代币


站在一个很通俗的角度,区块链可以看成是一个共享数据库。每次从该数据库读取或者写入数据都需要花费 gas 以防止类似垃圾邮件的恶意攻击。具体来说,以太坊上执行的每个计算步骤都需要花费 gas,以避免可能导致以太坊停摆的恶意攻击。每个操作码的 gas 开销都在以太坊黄皮书中有说明。但操作码的 gas 开销仍是一个热烈讨论的活跃话题,以太坊的社区成员们正在研究引入存储租金机制的可能性,甚至是 gas 和操作码的动态定价方案。

在以太坊区块链中写入数据很贵,比如创建一个非空的存储单元需要花费 20000 gas,几乎与一次简单的 Ether 转账交易(即当交易结构中的数据字段为空时)花费相当(21000 gas)。作为缓解区块链数据存储暴涨的一种激励方案,以太坊协议会为清空不再使用的旧存储单元退还 10000 个 gas。

这个 Ether 的退还机制最多可以返还合约交易花费的一半 gas(普通转账交易是无法获得退款的,因为它们已经使用了 gas 的最低消费;但是针对合约的批量调用是可以享受这个退款机制的)。Gas Token 允许开发者简单而高效地利用这个退款机制,即通过 gas 的代币化,在 gas 价格低的时候囤货,然后在 gas 价格高的时候花掉之前储存的 gas 代币。

最近确实有在一些交易所发现了一个没有正确设定交易 gas上限的漏洞。攻击方法很简单:在交易所申请提现,然后将提现交易目标地址设置成一个攻击者部署的恶意合约,其默认 fallback 函数(发送 Ether 到该合约会触发该函数的调用)就会趁机铸造新的 gas Token(囤货 gas)。(校对注:详情可见文末超链接《深处的蚁穴》)


元交易


元交易是这样一种发送交易的模式:发送方先对一个合法的以太坊交易签名,然后把该交易和签名通过链下传递的方式转交给一个中继方,该中继方愿意承担该笔交易的 gas 开销并最终发送交易到以太坊网络中。

-以太坊的技术宅们都喜欢抽象化-


这种元交易模式很有用,因为发送方不再需要在发送账户中存有 Ether,从用户体验角度这很有益处。我之前在这篇文章中提到过元交易及其在 UX 上的影响。

元交易最终的目标地址一般都是某个以太坊合约,且在某种程度上,该合约知道,交易的签名方并不是交易实际的发送者。以太坊交易 API 的 msg.sender 字段会返回中继方的地址,但其很可能并没有代表签名方操作的权利,所以在这个场景下(仅仅查看 msg.sender 字段)并没有太大意义。因此,许多元交易依赖链上的签名校验(通过以太坊 API 的 ecRecover 函数)来保证签名方账户的确是在一份合适的白名单里面(有权限操作该交易想要执行的指令)。


潜艇交易(Submarine Send)


(别跟这个潜艇交易网站<https://submarineswaps.org/> 混淆了)

矿工抢跑(frontruning)现象在基于区块链经济的交易市场中是一个很难杜绝的老问题,即矿工可以对交易重新排序,随意裁剪或者让他们自己的交易插队来获利。潜艇交易试图通过极强的保密特性来提供矿工抢跑问题的一个解决方案。不仅仅是隐藏交易金额,潜艇交易会尝试完全隐藏该笔交易的存在。当然,如果一笔交易永远都被隐藏着,那也就没啥意思了。潜艇交易允许发送方在未来的某个时刻公开该笔交易,这也是称其为“潜艇”交易的缘由。

-通过使用潜艇交易,用户的交易就不会被矿工抢跑-


用户提交一个包含加密承诺的交易,其中包含了用户希望发送给目标合约的若干应用数据,并在潜艇地址中锁定交易涉及的 Ether 或者代币,其中潜艇地址与一个全新的地址无异(因此难以被矿工识别)。锁定在该地址中的 Ether 或者代币只有目标合约可以解锁。通过在承诺交易中附加货币价值(除非用户完成承诺的公开,否则附加货币价值其实是被燃烧掉了,无法找回),我们保证了有效的经济约束来防止某些恶意用户选择性地公开承诺(即防止用户可以无代价地提交任意承诺)。只要承诺交易被成功打包并经过足够区块确认,用户就可以向目标合约公开其加密承诺,然后合约(成功校验后)便会执行该笔交易中包含的应用逻辑。


反事实合约实例化


Counterfactual(反事实)一词源于哲学和思辩中的一个概念。一条反事实陈述是一连串有理有据的推理以及相应的结论,但是该陈述的前提是有意与事实相反的。除开这个与事实不符的前提,整个推理链条是合理的,所以如果前提正确,最终结论也会是正确的。应用到区块链交易场景,Conterfactual 的逻辑不光会考虑区块链当前的状态,还会考虑如果某合约部署完成后,区块链的状态。

更具体来说,在合约部署之前就获取它的地址,这种模式被称作反事实合约实例化,这个理论由 L4 发表在他们的“反事实状态通道”论文中,并受到以太坊社区的广泛欢迎。

目前,新的合约地址由以太坊操作码 CREATE 生成,并可通过合约的创建者账户地址(sender字段)以及创建者已经发出去的交易数量(nonce字段)来明确决定,即 sender 和 nonce 字段会通过 RLP 编码然后经过 Keccak256 哈希算法生成新的合约地址。

EIP1014 引入的 Skinny CREATE2 操作码更进一步,允许用户与链上尚不存在的地址进行交互;虽然该地址上还没有代码,但可以保证它最终只可能包含通过一段特定的初始代码生成的合约逻辑。与 CREATE 操作码一般使用 sender-nonce 然后哈希得到合约地址不同,CREATE2 操作码使用的是如下地址生成公式: keccak256( 0xff ++ address ++ salt ++ keccak256(init_code)))[12:] 

这种模式,对于涉及与尚不存在的合约进行交互的状态通道场景,尤其重要。它让以太坊主链可以成为争议(解决)层,并且不需要考虑合约部署的真实开销。类似地,这种模式在已知功能将创建新地址的场景也可以使用,比如这里的借贷还款地址。


零确认交易


零确认交易源于 Bitcoin Cash 社区,目前仍是一个有趣但尚未经过证明的研究领域,在这样的一个区块链网络中,出块时间实质上可能更加不利于用户体验(UX-inhibiting)。零确认交易的发送方需要提交一个保证金,如果有双花行为,发送方就会损失掉该保证金。在比特币现金中,双花行为可以通过 UTXO 的输入项重用被检测到。任何人(一般假设是矿工)都可以提交找到的两笔双花交易,然后得到保证金的奖励。

在以太坊的账户网络中,不同于使用类似比特币的UTXO,我们可以检查同一发送方是否重用了同一个 nonce。比如一个已部署合约提供一个 reportDoubleSpend 方法,该方法接受两个待完成的已签名交易,然后合约会检查其发送方和 nonce,如果相等,就会把保证金奖励给方法调用者。原理很简单:如果保证金数额足够大,这对于交易发送方而言,就是防止其作弊(双花)的一个有力武器,因为他们有可能损失缴纳的保证金。这种交易类型被认为最适合用于小额一次性的单笔支付场景,因为有一系列针对该场景的潜在攻击模式存在。


批量转账


跟 ERC20 代币交互的一个主要问题在于,一般需要两次不同的交易:一次是调用代币合约的 approve 方法,另一次才是真正调用目标合约(该目标合约内部会调用 transferFrom 方法)使用代币完成特定逻辑(doSomethingForTokens 方法)。这种模式就会产生非原子性交易的一系列问题。最简单的情况就是,如果 doSomethingForTokens 调用交易失败了,之前的 approve 调用不会回滚,即 approve 方法允许合约支配的代币额度(allowance)仍然成立。

- ERC20 代币合约的 approve 和 transferFrom 方法是非原子性的-


Limechain 实现了一种特殊的批量转账方法。借鉴元交易中的链上签名校验原理,失败的 doSomethingForTokens 调用交易会回滚相应的 approve 调用,从而改善了 ERC20 代币原本 approve 和 transferFrom 方法的非原子性。


基于短信的付款


CoinText 可能是最有名的基于短信的密码学货币支付服务商,目前专注提供比特币现金的交易。这种付款机制对于发展中国家和地区的移动设备尤其有用。Eth2 也已经在以太坊上部署了类似的技术,它可以通过传统的基于移动应用的以太坊钱包(比如 Trust)来工作。

- eth2.io 的基于短信的加密数字付款方案-


这个特定方案采用了一个托管合约。交易发送方生成一个临时的公私钥对,然后往托管合约 存入 Ether,该笔转账中附带之前生成的临时公钥。私钥则通过随机生成的对称密钥加密,然后密文通过链下方式(邮件,短信,Whatsapp 等)发送给某个中心化的校验服务器。提现时,如果接收方手机号码校验成功,校验服务器就会把密文发给接收方,接收方可以解密(即拿到临时私钥),然后对提现交易消息签名,托管合约随后可以对该签名进行校验,确认是由临时私钥完成的签名。

中心化服务器用来对手机号做验证并传递秘钥,但是 Eth2 的服务器无法控制锁定在托管合约中的 Ether。如果中心化服务器被攻击了,付款交易会失败,但是 Ether 仍在托管合约中。如果此时想拿回锁定的 Ether,发送方可以通过调用托管合约取消该笔付款。


订阅付款


基于可选择退出的订阅服务付款是 Web2.0 时代互联网服务的主流变现方式,比如 Spotify,Netflix,Headspace 和 Tinder 都是基于订阅付款构建其商业收入模型。

密码学货币中的订阅付款概念也不是新东西,在比特币中,nLocktime 字段就可以用来保证一笔已签名的交易在指定的区块高度之前不会被打包(即消费掉)。但在以太坊上,用于未来支付的预签名交易意义不大,因为账户的 nonce 会随着该账户不停发出交易而增长,会导致预签名时所用 nonce 偏小,进而导致交易无效。

幸好,以太坊的图灵完备性提供了一个解决办法:有一些针对重复订阅类型交易的架构方案。这些架构在保证金(流动性),用户体验复杂性,可选功能,gas 开销和可延展性方面有不同的权衡取舍。


基于预言机的方法调用


另一种更加特殊的交易发送方式是使用预言机服务,比如 Oraclize,以期通过适当的中心化来换取gas使用量的减少,可以参见此文。

-使用 Oraclize 减少常量合约调用的 gas 使用量-


这种类型适用于非交易型(返回常量)的方法调用。已经与以太坊主网同步的节点,可以通过以太坊 JSON-RPC 的 eth_call 接口来调用上述方法。只要继承了 usingOraclize,在你的合约中就可以使用 Oraclize 的 oraclize_query 方法进行常量查询。另外,你的合约里面还必须定义一个 __callback(bytes32 queryId, string results) 的回调函数,Oraclize 查询会调用该函数并保存查询结果。与调用 Oraclize 相比,直接进行链上查询来获取和计算这些状态常量可能更加昂贵。


使用一次性地址进行多笔付款


如背景知识中介绍的,交易字段中并没有“发起地址”。这个地址可以通过 ecRecover 函数计算得出。那么问题来了:我们能不能在交易签名中任意填入我们想要的数据?事实表明,有一半的签名是正确的,即 ecRecover 仍然返回一个合法的公钥(因此也对应着以太坊地址)。由于我们无法控制生成的地址,那么我们通过设置字段值,其实是在构建这样一个交易:该交易可以花费看上去是一个随机生成的地址中的余额。

如果我们创建了这样一笔交易(发送方是我们想要生成的地址),并给生成的地址充值了若干Ether,那么该笔交易就可以像一般交易一样执行。这样我们实质上创建了一个一次性的地址,因为其中的余额只能被一笔交易使用。如果我们以某种可预测的方式选择交易签名中的字段值并公布该笔交易,我们就可以向任何人证明,发给交易发送方地址的金额,只能被该笔交易使用,而不能被其它任何交易使用。

如上图所述,该场景尝试发送交易至11140个目标地址,由一系列发送 Ether 至多个地址的交易组成,每个交易发送到 110 个地址,其中发送方的地址通过上述方法生成。对于签名字段,我们填入‘0xDA0DA0DA0…’ ,这是一个可预测的值,这样我们确定,没有人能拿到这些签名所对应地址的私钥。

这就创建了一批拥有“一次性地址”的交易,这些地址可以用来给相应交易提供所需交易金额。但 104 个要签名的交易对于受托自然人而言还是太多了,所以我们重复一次上述过程,形成一个级联结构:我们先构造 104 笔交易,每笔交易都有其对应的唯一地址,然后再构造一笔发送 Ether到对应的 104 个地址的转账。通过验证,代码确实可以按照预期运行,那么任何人就可以这些构建好的交易发送到以太坊网络中,整个过程就像多米诺骨牌一样自动进行了:名单上的 11400 个地址都会收到 Ether,但我们仅仅用了一次人工签名。




以太坊上完成交易居然有这么多不同的方式!!!

如果有什么遗漏,请告诉我,我会时不时地更新本文。欢迎在推特上关注我:https://twitter.com/gawnieg



原文链接: 

https://medium.com/coinmonks/the-business-of-sending-transactions-on-ethereum-e79fd9a2b88

作者: Aodhgan Gleeson

翻译&校对: Ray & 阿剑


你可能还会喜欢:

科普 | 深处的蚁穴:与 Gas 相关的三种安全问题
引介 | Linkdrops:以太坊上发红包的开源标准
干货 | Solidity 安全,Part-5:未检查的 Call 返回值,条件竞争




    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存