欢迎来到 wabc.cc 官方网站!

探讨更简单的以太坊多重签名合约

来源:推荐文章 / 时间:2025-12-20

翻译:西溪明月

作者:Christian Lundkvist

 

几周前,多个广泛使用的以太坊多重签名钱包被黑了,被盗以太坊资产达3200万美元。另有1.6亿美元的资产被一群白帽黑客抢先保护起来。

 

这起事件突显了编写智能合约代码所面临的挑战。没有什么环境比公有链更具对抗性了:你的代码对所有人公开,任何人都可以研究你公开的函数。再加上那些用来保护数千万美元资产的合约,所有这些使你置身的环境对黑客极具吸引力,他们往往会动力十足地寻找你合约中的漏洞并加以利用。

 

在多重签名钱包遭到黑客攻击这件事上,讽刺的是,它们本应比只有一把私钥的钱包更安全。理论上,多重签名这一属性增加了被黑的难度,因为黑客需要侵袭多人才能获得私钥。然而,多重签名技术在执行此安全功能时存在一个逻辑漏洞,这个漏洞使其安全系数比单个秘钥的安全系数还低。

 

简单性vs安全性

 

这里出现了一个有趣的权衡现象:智能合约中,运用的逻辑越多,可实现的安全功能越多:超时设定、支付限制、多重签名、金库(vault)等。然而,运用的逻辑越多,智能合约的攻击面也越大,漏洞也更有可能带来破坏安全功能的风险。

 

我们可以考虑一系列资产管理工具,不管是简单的还是复杂的。但要保护以太币的安全,最简单的方法是使用与以太坊地址相对应的单个私钥,有时也称为“终端用户账户”。采用这种方法,根本不存在智能合约逻辑,也就无需为此费神,这样便消除了这一风险。然而,只使用一把秘钥也意味着存在单点故障。

 

另一方面,你也可以创建复杂的钱包合约来管理自己的资金。以太坊基金会有一个他们经常使用的钱包合约,且最近Gnosis引入了一个精巧的多重签名钱包,该钱包支持支付限制、管理控制,并采用了这样一种工作流程,即(钱包)所有者可以确认由其他人提交的交易。

 

简单的多重签名合约

 

我们想探讨一下最简单的多重签名合约。在该合约下,秘钥持有者应能够聚集起来转移资金,但为了保持其简单性,我们不想要更高级的功能,比如支付限制或更新签名者等。

 

这个灵感来源于Bitcoin的多重签名方式,这种方式中,脚本语言的OP_CHECKMULTISIG操作码可以直接支持多重签名。据我所知,迄今为止,由于错误的Bitcoin多重签名脚本而致使Bitcoin被盗或丢失的事件还从未发生过。

                                                                                                             

最后我们得到的是一个将多数逻辑踢下链(即仅运用少数逻辑)的合约,在此合约中,多重签名的每一个所有者都负责创建授权交易的签名,然后利用单个函数向合约提交所有签名以完成验证。我们使用的是ERC191规范,该规范旨在促进签名格式的标准化。同时,我们还采用单一整数随机数来防止重放攻击。

 

在用户界面方面,我们的想法是多重签名的秘钥持有者都有用户界面,他们可以在此输入自己想要发送(最好在离线计算机上)的交易详情。随后,“操作员”将在一台在线计算机上收集持有者的所有签名,并将包含所有签名的实际交易发送出去。该操作员不需要对资金进行任何实际控制,持有多重签名秘钥的人才是最终有权执行交易的人。

 

完整的代码为:

pragma solidity 0.4.14;

contract SimpleMultiSig {

 

  uint publicnonce;                // (only) mutablestate

  uint publicthreshold;            // immutable state

  mapping(address => bool) isOwner; // immutable state

  address[]public ownersArr;        // immutablestate

 

  function SimpleMultiSig(uint threshold_,address[] owners_) {

    if(owners_.length > 10 || threshold_ > owners_.length || threshold_ == 0){throw;}

 

    for (uinti=0; i

     isOwner[owners_[i]] = true;

    }

    ownersArr= owners_;

    threshold= threshold_;

  }

 

  // Notethat address recovered from signatures must be strictly increasing

  functionexecute(uint8[] sigV, bytes32[] sigR, bytes32[] sigS, address destination, uintvalue, bytes data) {

    if(sigR.length != threshold) {throw;}

    if(sigR.length != sigS.length || sigR.length != sigV.length) {throw;}

 

    //Follows ERC191 signature scheme: https://github.com/ethereum/EIPs/issues/191

    bytes32txHash = sha3(byte(0x19), byte(0), this, destination, value, data, nonce);

 

    addresslastAdd = address(0); // cannot have address(0) as an owner

    for (uinti = 0; i < threshold; i++) {

       address recovered = ecrecover(txHash, sigV[i], sigR[i], sigS[i]);

        if(recovered <= lastAdd || !isOwner[recovered]) throw;

       lastAdd = recovered;

    }

 

    // If wemake it here all signatures are accounted for

    nonce =nonce + 1;

    if(!destination.call.value(value)(data)) {throw;}

  }

 

  function ()payable {}

}

此代码也可以在github中找到。

 

效益

 

此合约的有益属性包括:

最小代码库:只有40行代码

最少可变状态:唯一可变的数据是,每一次执行都会增加一个单元(unit)。

最小界面:由单一功能构成的界面.

能够发送任意交易,故而支持代币(token)

 

由于缺乏复杂的状态转换,在没有资金的情况下,该合约便无法转入“冻结”状态。而因为唯一可能的状态转换是简单的增量计数器,所以该合约永远处于正确状态。且由于使用了32字节的整数,计数器便不可能溢出。同时,测试也变得更简单了,因为事实上(除构造函数外)只有一个函数需要测试。

 

但由于我们尽可能地简化了链上逻辑,因而增加了链下工作流程的复杂性,这便导致了一些缺陷与不足,比如:

需要用户给非交易签名,而这可能妨碍某些硬件钱包的使用

为了发送交易,需要终端用户进行链下协调。

 

未来工作:形式化验证

 

合约的简单性使之成为创建形式规范并用这种规范进行形式验证的理想选择——数学上可以证明代码遵循了规范。对EVM(以太坊)编译也已经做过形式化验证了,需要形式化验证语言来重写合约。随着EVM形式化语义的发布,近来EVM的形式化验证得到了越来越多的关注。

 

下一步还要用低阶语言(LLL)或纯EVM字节代码来编写简单的多重签名合约,以便限制Solidity编译器的风险。

 

总结

 

本文探讨了一种更加简单的多重签名以太坊合约,在此合约下,用一个交易发送所有分离签名前,会先在链下汇总这些签名。这样做简化了链上智能合约代码,方便审查,且为将来进行形式化验证提供了可能性。

 

 

作者简介 Christian Lundkvist

区块链爱好者,以太坊探索者,神秘的数学家。


相关产品

在线客服
微信联系
客服
扫码加微信(手机同号)
电话咨询
返回顶部