Skip to content

以太坊无密钥执行 - Nick的方法 #8

Open

Description

@SpaceStation09

本篇意在通过简单的描述总结这篇博客:《Nick 的方法 - 以太坊无密钥执行/部署方法》。它描述了,如何使用无密钥的方式去构建一个交易,并使其顺利执行, 我们可以在一些特殊的使用场景中,利用这样的机制,帮我们绕过一些问题。

Activity

  1. SpaceStation09 commented on Feb 14, 2025

    @SpaceStation09
    OwnerAuthor

    工作原理

    Tx的构造

    一个在以太坊上valid的交易(签名后),会包含以下字段:

    {  
      nonce: "0x00",
      gasPrice: "0x09184e72a000",  
      gasLimit: "0x27100",  
      to: "0x0000000000000000000000000000000000000000",  
      value: "0x01",  
      data: "0x7f7465737432000000000000000000000000000000000000000000000000000000600057",  
      v: "0x26",  
      r: "0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e",  
      s: "0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663,  
    }
    

    通过以上的例子,我们会发现,交易中并没有“from”的字段(即,并没有描述交易的签署者)。那么,节点如何知道谁在执行交易呢?A:使用ecrecover从签名中恢复出地址。

    Aha!

    如果我们随意修改这个交易的签名(v,r,s的值)会发生什么呢?交易并不会被视为无效。只要签名恢复出的地址所对应的账户,符合交易的condition(例如,足够支付交易费用或者调用合约时,满足合约规定的条件)。那么,我们就可以:

    将(v,r,s)的值改为随机生成的值,此时,我们可以通过我们build的tx,以及随机生成的签名来恢复出一个地址,随后我们为这个地址手动提供一些Ether 以cover 交易的费用,并将我们build的tx广播到网络中,就可以使得交易从我们恢复的地址成功执行。

    总的来说,这样我们就可以无密钥得构造一个符合我们需求的可执行的交易,不过这个交易地址是一次性的,是个单次使用的不可控的地址。

  2. SpaceStation09 commented on Feb 14, 2025

    @SpaceStation09
    OwnerAuthor

    Use Case

    如何像11,440人发送Ether

    这个方法当时被用于在DAO黑客事件之后,向链上的11,440个地址发送Ether。

    困境:资金由多重签名控制,每次需要从多签账户转账到某个地址时,要求多个签名是很麻烦且消耗资源的(时间,gas cost)。

    利用以上section描述的机制,我们有一个新方法:

    • 创建一个MultiSend合约,该合约将一组的资金发送给一组接收者,合约代码如下:
      contract MultiSend {
        function MultiSend(address[] recipients, uint[] amounts, address remainder) {
            if(recipients.length != amounts.length)
                throw;
            
            for(uint i = 0; i < recipients.length; i++) {
                recipients[i].send(amounts[i]);
            }
            
            selfdestruct(remainder);
        }
      }
    • 但出于gas cost的限制的考量,我们也没办法一次性通过这个合约完成所有地址的投送。因此我们将11,440个地址分成104组。这也就相当于,我们需要向MultiSend合约发起104次交易。
    • 利用以上的机制,我们可以将整个发送任务拆解分配出去 ,如下图:

    Image

    这个图查看的时候,我们可以这样理解,真正执行流程的时候,顺序是自顶向下的,而当我们作为开发者build的时候,是自下向上的。

    • 首先,我们需要build出那104个给最终收款的11,440个地址的交易(实际交易内容是,调用 MultiSend)。我们生成原始的Tx后,为104个交易分别生成随机的(r, s, v), 并计算出,它们对应的104个branch addresses。
    • 接着,就要build出一个交易,这个交易的内容是,调用MultiSend来发送对应的资金给104个branch addresses。构建出原始的Tx后,我们随机生成(r, s, v), 并计算出该交易对应的Root address。
    • 多签账户通过正常的多签交易流程,给上一步中计算出的Root address转账对应的资金。

    这样以来,我们要用多签账户给11,440个地址发送资金,只需要一个签名就可以完成。

    在同一地址上进行多链部署

    提到这个使用场景,想必,我们脑子里已经有现成的解决方案了,比如人工控制部署的EOA地址相同,nonce相同;或者,利用合约工厂+ CREATE2。上述机制,为我们提供了另一个有趣的解决方案: 我们可以使用一个不受控制的地址作为from,这样它的的nonce极大概率就是0,然后我们可以在不同的链上发起这个交易,这样被部署的合约地址就是一样的了。

    但必须要强调的是,这个思路只是一个看起来有趣的方案,但实际存在不少问题,所以放在这里作为一个趣味脑洞。 存在的问题有:

    • EIP-155 引入的chainId防止在多链上重放交易的机制,我们签名中的v就是与chainID有关的,这意味着,我们换了一条链时,v也会随之改变。进而导致签名,生成地址的改变和被部署合约地址的改变。
    • 我们在交易中还有gas 相关的字段,每条链的值都会不同,所以我们很难通过简单的重放交易完成多链的部署。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions