OpenZeppelin 多重签名账户

本文介绍了多重签名账户,这是一种需要多个授权签名者批准操作才能执行的智能合约账户。文章详细讲解了 ERC-7913 标准及其在 OpenZeppelin 中的实现,包括 SignerERC7913、MultiSignerERC7913 和 MultiSignerERC7913Weighted 合约,以及如何具有不同类型签名者和权重的多重签名账户。

多重签名账户

多重签名 (multisig) 账户是一种智能账户,它需要多个授权签名者批准操作才能执行。与由单个私钥控制的传统账户不同,多重签名将控制权分配给多个参与方,从而消除单点故障。例如,一个 2-of-3 的多重签名需要 3 个可能的签名者中至少 2 个的签名。

Safe(前身为 Gnosis Safe)等流行的实现已成为保护有价值资产的标准。多重签名通过集体授权、所有权和阈值的可自定义控制以及在不更改帐户地址的情况下轮换签名者的能力来提供增强的安全性。

超越标准签名验证

正如在账户部分中讨论的那样,智能合约验证签名的标准方法是 ERC-1271,它定义了一个 isValidSignature(hash, signature)。但是,它在两个重要方面受到限制:

  1. 它假设签名者拥有一个 EVM 地址

  2. 它将签名者视为一个单独的身份

当实现多重签名账户时,这会变得有问题,因为:

  • 你可能想使用没有 EVM 地址的签名者(例如来自硬件设备的密钥)

  • 每个签名者需要单独验证,而不是被视为一个集体身份

  • 你需要一个阈值系统来确定何时存在足够的有效签名

SignatureChecker 库对于验证 EOA 和 ERC-1271 签名很有用,但它并非为更复杂的安排(如基于阈值的多重签名)而设计。

ERC-7913 签名者

ERC-7913 扩展了签名者表示的概念,以包括没有 EVM 地址的密钥,从而解决了这一限制。OpenZeppelin 通过三个合约来实现此标准:

SignerERC7913

SignerERC7913 合约允许单个 ERC-7913 格式的签名者控制一个账户。签名者表示为一个 bytes 对象,该对象连接一个验证者地址和一个密钥:verifier || key

// contracts/MyAccountERC7913.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import {Account} from "@openzeppelin/community-contracts/account/Account.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import {ERC7739} from "@openzeppelin/community-contracts/utils/cryptography/ERC7739.sol";
import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/ERC7821.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {SignerERC7913} from "@openzeppelin/community-contracts/utils/cryptography/SignerERC7913.sol";

contract MyAccountERC7913 is Account, SignerERC7913, ERC7739, ERC7821, ERC721Holder, ERC1155Holder, Initializable {
    constructor() EIP712("MyAccount7913", "1") {}

    function initialize(bytes memory signer) public initializer {
        _setSigner(signer);
    }

    /// @dev 允许入口点作为授权执行者。
    function _erc7821AuthorizedExecutor(
        address caller,
        bytes32 mode,
        bytes calldata executionData
    ) internal view virtual override returns (bool) {
        return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
    }
}

警告:帐户在未初始化状态可能会使其无法使用,因为没有公钥与其关联。 |

MultiSignerERC7913

MultiSignerERC7913 合约扩展了这个概念,以支持具有基于阈值的签名验证系统的多个签名者。

// contracts/MyAccountERC7913.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.27;

import {Account} from "@openzeppelin/community-contracts/account/Account.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import {ERC7739} from "@openzeppelin/community-contracts/utils/cryptography/ERC7739.sol";
import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/ERC7821.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {MultiSignerERC7913} from "@openzeppelin/community-contracts/utils/cryptography/MultiSignerERC7913.sol";

contract MyAccountMultiSigner is
    Account,
    MultiSignerERC7913,
    ERC7739,
    ERC7821,
    ERC721Holder,
    ERC1155Holder,
    Initializable
{
    constructor() EIP712("MyAccountMultiSigner", "1") {}

    function initialize(bytes[] memory signers, uint256 threshold) public initializer {
        _addSigners(signers);
        _setThreshold(threshold);
    }

    function addSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
        _addSigners(signers);
    }

    function removeSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
        _removeSigners(signers);
    }

    function setThreshold(uint256 threshold) public onlyEntryPointOrSelf {
        _setThreshold(threshold);
    }

    /// @dev 允许入口点作为授权执行者。
    function _erc7821AuthorizedExecutor(
        address caller,
        bytes32 mode,
        bytes calldata executionData
    ) internal view virtual override returns (bool) {
        return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
    }
}

此实现非常适合标准多重签名设置,其中每个签名者具有同等的权限,并且需要固定数量的批准。

MultiSignerERC7913 合约提供了几个用于管理多重签名账户的关键功能。它维护一组授权签名者,并实现一个基于阈值的系统,该系统需要最少数量的签名才能批准操作。该合约包括一个用于管理签名者的内部接口,允许添加和删除授权方。

MultiSignerERC7913 采取保护措施,以确保阈值基于当前活动的签名者数量保持可实现,从而防止操作可能无法执行的情况。

该合约还提供了公共 isSigner(bytes memory signer) 函数来检查给定的签名者是否已获得授权,这在验证签名或实施自定义访问控制逻辑时非常有用。

MultiSignerERC7913Weighted

对于更复杂的治理结构,MultiSignerERC7913Weighted 合约通过为每个签名者分配不同的权重来扩展 MultiSignerERC7913

// contracts/MyAccountERC7913.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.27;

import {Account} from "@openzeppelin/community-contracts/account/Account.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import {ERC7739} from "@openzeppelin/community-contracts/utils/cryptography/ERC7739.sol";
import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/ERC7821.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {MultiSignerERC7913Weighted} from "@openzeppelin/community-contracts/utils/cryptography/MultiSignerERC7913Weighted.sol";

contract MyAccountMultiSignerWeighted is
    Account,
    MultiSignerERC7913Weighted,
    ERC7739,
    ERC7821,
    ERC721Holder,
    ERC1155Holder,
    Initializable
{
    constructor() EIP712("MyAccountMultiSignerWeighted", "1") {}

    function initialize(bytes[] memory signers, uint256[] memory weights, uint256 threshold) public initializer {
        _addSigners(signers);
        _setSignerWeights(signers, weights);
        _setThreshold(threshold);
    }

    function addSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
        _addSigners(signers);
    }

    function removeSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
        _removeSigners(signers);
    }

    function setThreshold(uint256 threshold) public onlyEntryPointOrSelf {
        _setThreshold(threshold);
    }

    function setSignerWeights(bytes[] memory signers, uint256[] memory weights) public onlyEntryPointOrSelf {
        _setSignerWeights(signers, weights);
    }

    /// @dev 允许入口点作为授权执行者。
    function _erc7821AuthorizedExecutor(
        address caller,
        bytes32 mode,
        bytes calldata executionData
    ) internal view virtual override returns (bool) {
        return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
    }
}

此实现非常适合不同签名者应具有不同级别的权限的场景,例如:

  • 具有不同投票权的董事会成员

  • 具有分层决策的组织结构

  • 结合核心团队和社区成员的混合治理系统

  • 像“社交恢复”这样的执行设置,在这种设置中,你比其他人更信任特定的监护人

MultiSignerERC7913Weighted 合约使用加权系统扩展了 MultiSignerERC7913。每个签名者都可以具有自定义权重,并且操作需要签名参与者的总权重达到或超过阈值。没有明确权重的签名者默认为权重 1。

设置加权多重签名时,请确保阈值与用于签名者权重的比例相匹配。例如,如果签名者的权重为 1、2 或 3,则阈值 4 将至少需要两个签名者(例如,一个权重为 1,一个权重为 3)。

设置多重签名账户

要创建多重签名账户,你需要:

  1. 定义你的签名者

  2. 确定你的阈值

  3. 使用这些参数初始化你的帐户

下面的示例演示了如何设置具有不同类型签名者的 2-of-3 多重签名账户:

// Example setup code
function setupMultisigAccount() external {
    // Create signers using different types of keys
    bytes memory ecdsaSigner = alice; // EOA address (20 bytes)

    // P256 signer with format: verifier || pubKey
    bytes memory p256Signer = abi.encodePacked(
        p256Verifier,
        bobP256PublicKeyX,
        bobP256PublicKeyY
    );

    // RSA signer with format: verifier || pubKey
    bytes memory rsaSigner = abi.encodePacked(
        rsaVerifier,
        abi.encode(charlieRSAPublicKeyE, charlieRSAPublicKeyN)
    );

    // Create array of signers
    bytes[] memory signers = new bytes[](3);
    signers[0] = ecdsaSigner;
    signers[1] = p256Signer;
    signers[2] = rsaSigner;

    // Set threshold to 2 (2-of-3 multisig)
    uint256 threshold = 2;

    // Initialize the account
    myMultisigAccount.initialize(signers, threshold);
}

对于加权多重签名,你还需要指定权重:

// Example setup for weighted multisig
function setupWeightedMultisigAccount() external {
    // Create array of signers (same as above)
    bytes[] memory signers = new bytes[](3);
    signers[0] = ecdsaSigner;
    signers[1] = p256Signer;
    signers[2] = rsaSigner;

    // Assign weights to signers (Alice:1, Bob:2, Charlie:3)
    uint256[] memory weights = new uint256[](3);
    weights[0] = 1;
    weights[1] = 2;
    weights[2] = 3;

    // Set threshold to 4 (requires at least Bob+Charlie or all three)
    uint256 threshold = 4;

    // Initialize the weighted account
    myWeightedMultisigAccount.initialize(signers, weights, threshold);
}

_validateReachableThreshold 函数确保所有活动签名者的权重总和达到或超过阈值。建立在多重签名合约之上的任何自定义都必须确保阈值始终可达到。

对于多重签名账户,签名是一个复杂的结构,其中包含签名者及其各自的签名。该格式遵循 ERC-7913 的规范,并且必须正确编码。

签名格式

多重签名编码为:

abi.encode(
    bytes[] signers,   // Array of signers sorted by `keccak256`
    bytes[] signatures // Array of signatures corresponding to each signer
)

其中:

  • signers 是参与此特定签名的签名者数组

  • signatures 是与每个签名者相对应的各个签名数组

为了避免重复的签名者,该合约使用 keccak256 为每个签名者生成一个唯一的 ID。当提供多重签名时,signers 数组必须按 keccak256 以升序排序,并且 signatures 数组必须与其相应的签名者的顺序匹配。

  • 原文链接: docs.openzeppelin.com/co...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 2
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
OpenZeppelin
OpenZeppelin
江湖只有他的大名,没有他的介绍。