惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
有赞技术团队
有赞技术团队
J
Java Code Geeks
H
Hackread – Cybersecurity News, Data Breaches, AI and More
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Hugging Face - Blog
Hugging Face - Blog
人人都是产品经理
人人都是产品经理
酷 壳 – CoolShell
酷 壳 – CoolShell
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
Check Point Blog
博客园 - 【当耐特】
The GitHub Blog
The GitHub Blog
Recent Announcements
Recent Announcements
The Cloudflare Blog
Microsoft Azure Blog
Microsoft Azure Blog
腾讯CDC
Vercel News
Vercel News
IT之家
IT之家
MyScale Blog
MyScale Blog
博客园_首页
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
罗磊的独立博客

博客园 - cac2020

QA之三 - 变异测试 -- PITest QA之二 - 单元测试-- JaCoCo QA之二 - 单元测试-- JUnit5 + Mockito QA之二 - 单元测试--Mockito QA之二 - 单元测试--JUnit5 QA之二 -- 测试用例 QA之一 -- 常见测试类型和测试环境 密码学之二 —— GPG、SSH、KMS 密码学之一 —— 三类算法 记录一次在线word转PDF的问题处理过程 分析Java堆Dump Java GC日志分析 Java线程Dump分析(TDA-Thread Dump Analyzer) Java可视化故障处理工具-VisualVM Java基础故障处理工具 记录Arthas在一次性能调优过程中实践 中小型系统必要可行的性能测试实践--ArtHas调优实战 中小型系统必要可行的性能测试实践--jmeter落地实践 中小型系统必要可行的性能测试实践--性能测试理论基础
密码学之三 —— MPC(TSS)、多签、时间锁
cac2020 · 2026-02-03 · via 博客园 - cac2020

一、MPC
MPC 全称 Secure Multi-Party Computation(多方安全计算),让多个参与方在「不泄露各自私密数据」的前提下,共同完成计算并得到正确结果,全程数据不共享、不落地、不暴露原始信息。在区块链 / 智能合约开发场景中,MPC 最核心的落地应用是MPC钱包(多方签名),解决了「私钥单点泄露」的致命问题。

  • 官方定义
    MPC 是一种密码学协议,允许多个互不信任的参与方,各自持有私密数据,在不向任何第三方(包括其他参与方)泄露自身数据的情况下,协同完成指定计算,最终仅输出计算结果,全程原始数据始终处于加密 / 分片状态。

  • 核心本质:「数据可用不可见,计算协同不共享」

    • 数据:每个参与方的私密信息(如区块链私钥、交易金额、个人数据)绝不暴露给任何人;
    • 计算:多个参与方通过密码学协议协作,完成计算(如签名、求和、验证);
    • 结果:仅输出最终计算结果(如签名后的交易、总和),无任何原始数据泄露。
  • MPC 核心工作原理
    MPC 的核心底层是秘密分享(Secret Sharing) + 同态加密 / 混淆电路,以区块链最常用的MPC 签名(如 ECDSA/Ed25519 签名)为例,原理极简拆解:

    • 核心前提:私钥分片(Sharding)
      MPC 不会生成「完整私钥」,而是将私钥拆分成 N 个加密分片(Shard),分发给 N 个参与方(如你的手机、电脑、平板、信任的朋友),任何单个分片都无法还原完整私钥,也无法单独签名。
    • 签名流程(多方协同,无完整私钥)
      以「3 个参与方,2 个即可签名(2-of-3 MPC)」为例:
    • 发起签名:你发起一笔区块链交易(如合约部署、转账),生成待签名的哈希值;
    • 分片协同计算:
      • 参与方 1、2(任意 2 个)各自用自己的私钥分片,对哈希值进行局部签名计算;
      • 计算过程中,仅交换「加密的中间结果」,不交换私钥分片,也不暴露原始数据;
    • 汇总签名:两个参与方的局部计算结果汇总,生成完整的交易签名(RSV);
    • 上链验证:将签名后的交易发送到链上,链上验证签名有效(和普通私钥签名结果一致)。
  • 主流 MPC 签名协议(区块链专用)
    区块链 MPC 签名有成熟的标准化协议,目前主流的是:

    • GG18/GG20:最早的 ECDSA-MPC 协议,支持 2-of-N 多签,兼容以太坊 / 比特币;
    • FROST:新一代轻量 MPC 协议,效率更高、开销更小,支持 Ed25519/ECDSA,是当前个人 / 企业首选;
    • CMP:针对企业级的 MPC 协议,支持更多参与方,安全性更强。
  • 个人 / 智能合约开发者适用的 MPC 方案

    • 个人 MPC 钱包(直接使用,无需开发)
      适合个人管理资产、合约签名,开箱即用:
      Coinbase Wallet MPC:Coinbase 推出的 MPC 钱包,支持 EVM/Solana,2-of-3 多签(手机 + 邮箱 + 信任联系人),社交恢复,无硬件,适合新手 / 开发者;
      Argent X:以太坊生态主流 MPC 钱包,2-of-2 多签(手机 + guardian),支持合约部署、DApp 交互,Gas 优化;
      Safe {Wallet}(原 Gnosis Safe)MPC 版:多签钱包的 MPC 升级,支持 1-of-1 到 N-of-N,适合开发者 / 团队管理合约部署私钥;
      Web3Auth MPC:支持社交登录(谷歌 / 邮箱)+ MPC 分片,私钥分片在用户设备 + Web3Auth 节点,易用性极高,适合 DApp 用户。

    • 开发者 MPC SDK(集成到项目 / 脚本)
      适合智能合约开发者,将 MPC 签名集成到部署脚本、DApp 中:
      Coinbase MPC SDK:开源 SDK,支持 ECDSA/Ed25519,可自定义分片数量,集成到合约部署脚本;
      Fireblocks MPC SDK:企业级 MPC SDK,支持多链,适合批量合约部署、资产操作;
      FROST SDK:开源 FROST 协议实现,轻量高效,适合个人开发者自定义 MPC 方案;
      Web3Auth Core Kit:开源 MPC SDK,支持社交登录 + 分片管理,适合 DApp 前端集成。

    • 开源 MPC 库(自主搭建,极客 / 开发者)
      适合想深入研究、自主搭建 MPC 方案的开发者:
      mpc-crypto:开源 MPC 密码学库,支持 ECDSA/Ed25519 签名;
      frost-ed25519:Ed25519 的 FROST 协议实现,适配区块链签名;
      gg20-ecdsa:GG20 协议的 ECDSA 实现,兼容以太坊 / 比特币。

总结:硬件钱包是「离线保险柜」,MPC 钱包是「分布式密码箱」,KMS 是「云保险柜」,三者各有侧重。

二、TSS

是什么?
门限签名(TSS)是一种分布式数字签名方案,核心是将单个私钥拆分成多个密钥分片,由多个参与方分别持有;只有当至少达到指定门限数量(t-of-n) 的参与方协同合作时,才能生成有效签名,单个 / 不足门限数量的参与方无法单独签名,且全程不会出现完整私钥。
TSS 是MPC(多方安全计算)在数字签名领域的核心落地应用(属于 MPC 子集),也是当前区块链、交易所、钱包解决「私钥单点泄露」的主流技术,和传统「链上多签(Multisig)」是完全不同的技术路径。

  • TSS 遵循t-of-n门限规则:

    • n:总参与方数量(如 3 个设备 / 服务器);
    • t:签名所需的最小参与方数量(如 2 个);
    • 规则:≥t 个参与方协同 → 生成有效签名;<t 个参与方 → 无法签名;
    • 核心:完整私钥永不生成、永不落地,仅以「分片」形式存在,签名通过多方密码学协同完成。
  • 关键特性(区块链 / 交易所核心价值)

    • 无完整私钥:私钥始终分片存储,任何参与方都无法获取完整私钥,杜绝单点泄露;
    • 链下签名:签名过程在链下完成,链上仅验证签名结果,无额外 Gas 费;
    • 链上兼容:生成的签名和「普通单私钥签名」完全一致,兼容所有区块链(无需修改合约 / 链上逻辑);
    • 门限灵活:可动态调整 t/n(如 2-of-3 → 3-of-5),无需链上操作;
    • 隐私保护:参与方身份、分片信息不暴露,比链上多签更隐私;
    • 容错性:部分参与方离线 / 失效,只要达到门限数量,仍可正常签名。
  • TSS 核心工作原理
    以区块链最常用的 ECDSA-secp256k1 TSS(适配以太坊 / 比特币),2-of-3为例

    • 阶段 1:分布式密钥生成(DKG)

      • TSS 不会生成「完整私钥」,而是通过分布式密钥生成(DKG) 协议,由 3 个参与方协同生成私钥分片:
      • 3 个参与方各自生成随机数,通过 MPC 协议交换加密的中间值;
      • 协同计算出私钥分片(Share1、Share2、Share3),分别由 3 个参与方持有;
      • 同时生成唯一公钥(和普通私钥的公钥格式一致,可公开);
      • 关键:完整私钥仅存在于数学逻辑中,从未被任何一方生成或存储。
    • 阶段 2:分布式签名(链下协同)

      • 发起交易 / 合约部署时,需 2 个参与方协同签名(满足 2-of-3 门限):
      • 参与方 A、B 各自用自己的私钥分片,对交易哈希值进行局部签名计算;
      • 双方交换「加密的局部签名结果」(不暴露私钥分片);
      • 协同汇总局部结果,生成完整的 ECDSA 签名(RSV);
      • 关键:签名过程中,完整私钥仍未出现,仅输出签名结果。
    • 阶段 3:链上验证

      • 将签名后的交易发送到区块链,链上验证逻辑和普通签名完全一致:
      • 用公钥验证签名(RSV)是否有效;
      • 验证通过 → 交易上链;验证失败 → 拒绝交易;
      • 关键:区块链无法区分是 TSS 签名还是普通单私钥签名,完全兼容。
  • TSS分类

1)按签名算法分类(区块链主流)

算法类型 适配场景 主流协议 代表应用
ECDSA TSS 以太坊 / 比特币 / BSC 等 EVM 链 GG18/GG20、CMP、FROST-ECDSA 交易所热钱包、硬件钱包、MPC 钱包
EdDSA TSS(Ed25519) Solana/Aptos 等非 EVM 链 FROST-Ed25519 新公链钱包、分布式节点签名
Schnorr TSS 比特币 Taproot、新公链 FROST、MuSig2 比特币多签、轻量级签名场景

2)按门限类型分类
固定门限 TSS:t/n 提前设定(如 2-of-3),签名前无法修改,最常用;
动态门限 TSS:可实时调整 t/n(如 2-of-3 → 3-of-5),适合企业级灵活权限控制;
分层门限 TSS:多层门限嵌套(如核心层 3-of-5 + 操作层 2-of-3),交易所大额资产专用。

  • 主流TSS协议

    • GG18/GG20(最成熟的 ECDSA TSS)
      开发者:Goldfeder 等密码学家,2018/2020 年发布;
      特点:抗恶意节点、兼容 ECDSA,支持 2-of-n 门限;
      应用:Coinbase、Kraken 等交易所,早期 MPC 钱包;
      局限:签名需 2-3 轮通信,效率略低。

    • FROST(新一代轻量 TSS)
      全称:Flexible Round-Optimized Schnorr Threshold Signatures;
      特点:单轮签名、效率极高、支持 Ed25519/ECDSA,容错性强;
      应用:新公链、去中心化交易所、个人 MPC 钱包(如 Argent X);
      优势:比 GG20 快 50%+,适合移动端 / 轻量级设备。

    • MPC-CMP(Fireblocks 自研 TSS)
      全称:MPC-Coordinated Multiparty Protocol;
      特点:单轮签名、速度提升 800%,支持冷 / 热钱包混合;
      应用:Bitfinex、中小交易所、金融机构;
      优势:开源免费,适配高频交易场景。

    • MuSig2(比特币专用 Schnorr TSS)
      特点:适配比特币 Taproot,签名结果和单签一致,隐私性极强;
      应用:比特币多签钱包、冷存储方案。

  • TSS 核心应用场景

    • 交易所资产安全(核心场景)
      热钱包:采用 2-of-3/3-of-5 TSS,私钥分片在多服务器,防止单点泄露;
      冷钱包:离线设备 + 在线服务器 TSS 混合,大额资产离线保护,小额资产 TSS 快速流转;
      代表:Binance、Coinbase、Fireblocks 客户均采用 TSS 作为核心安全方案。

    • 个人 / 开发者 MPC 钱包
      2-of-3 门限:私钥分片在手机、电脑、云备份,任意 2 个设备即可签名;
      无助记词:通过分片恢复,替代传统助记词,避免备份泄露;
      代表:Argent X、Coinbase Wallet MPC、Web3Auth。

    • 智能合约开发与部署
      合约部署私钥:采用 TSS 分片管理,防止部署私钥泄露导致合约被攻击;
      DApp 交互:用户用 TSS 钱包签名,无需暴露私钥,提升 DApp 安全性;
      批量签名:TSS 支持批量交易签名,适合合约批量部署 / 调用。

    • 企业级多签管理
      团队权限:2-of-3 门限(2 个员工 + 1 个管理员),防止单人操作风险;
      审计合规:签名操作全程可追溯,满足金融 / 政务监管要求。

  • TSS vs 链上多签(Multisig):核心区别(必懂)
    这是最易混淆的点,TSS 是链下分布式签名,Multisig 是链上合约多签,二者目标一致(多方授权),但技术路径、成本、兼容性差异极大:

对比维度 TSS(门限签名) 链上多签(Multisig) 核心差异
私钥状态 永不完整(分片) 各签名方持有完整私钥 TSS 无单点泄露风险,Multisig 有
签名位置 链下多方协同 链上合约验证 TSS 无 Gas 费,Multisig 需支付合约 Gas
链上兼容性 100% 兼容所有链 需部署多签合约,部分链不支持 TSS 适配所有公链,Multisig 依赖链上功能
门限调整 链下动态修改,无成本 需重新部署合约,高成本 TSS 灵活性远高于 Multisig
隐私性 参与方身份不暴露 签名方地址公开,可追踪 TSS 隐私性更好
签名效率 链下秒级完成 链上验证,延迟高 TSS 适合高频交易

关键结论
个人 / 高频场景:首选 TSS(无 Gas、便捷、兼容所有链);
大额资产 / 强监管场景:可 TSS + Multisig 混合(如交易所热钱包 TSS + 冷钱包 Multisig)。

简单说:链上多签是「合约管权限」,TSS 是「密码学管私钥」,TSS 更安全、更灵活、更适配所有区块链场景。

三、多签(Multisig)

  • 多签(Multisignature)是一种需要多个私钥持有者共同授权才能完成交易 / 操作的安全机制,核心遵循「t-of-n 门限规则」:

    • n:授权人总数量(如 3 个管理员);
    • t:完成操作所需的最小授权数量(如 2 个);
    • 只有收集到≥t 个授权人的签名,交易 / 操作才能执行;单个 / 不足 t 个授权人无法单独完成操作。
  • 多签的核心类型(链上 vs 链下)
    区块链多签分两类,核心差异在于「签名验证位置」:

    • 链上多签(Multisig):智能合约实现授权逻辑,区块链上(合约验证),Gnosis Safe、ERC-20 多签合约,中高(依赖合约安全),大额资产管理、合约权限控制;
    • 链下多签(TSS):MPC/TSS 密码学实现,区块链下(链上仅验证最终签名),Fireblocks MPC、Coinbase MPC,高(无完整私钥),高频交易、交易所热钱包
      关键区别:
    • 链上多签:多个私钥分别签名,合约收集签名后执行(有完整私钥,合约管权限);
    • 链下多签(TSS):私钥分片,多方协同生成一个签名(无完整私钥,密码学管私钥);

为什么?

  • 链上多签核心工作原理(以太坊 EVM 链举例)
    以「2-of-2 多签合约」为例,核心流程:

    • 部署多签合约:指定 2 个授权人地址(如 AddrA、AddrB),门限设为 2;
    • 发起交易:任意授权人提交交易(如转账 ETH、调用合约)到多签合约;
    • 收集签名:2 个授权人分别用自己的私钥对该交易签名,将签名提交到合约;
    • 验证执行:合约验证签名数量≥门限(2 个),且签名合法,执行交易;
    • 失败场景:仅 1 个授权人签名→合约拒绝执行。
  • 多签合约示例代码

点击查看代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @title Simple2of2Multisig
 * @dev 简化版2-of-2链上多签合约,支持ETH转账和合约调用
 * 核心功能:提交交易→收集2个签名→执行交易
 */
contract Simple2of2Multisig {
    // 授权人地址(2个)
    address public immutable owner1;
    address public immutable owner2;

    // 交易结构体:存储待执行的交易信息
    struct Transaction {
        address to;       // 接收方地址
        uint256 value;    // 转账ETH金额(wei)
        bytes data;       // 调用合约的calldata(空则为纯ETH转账)
        bool executed;    // 是否已执行
        uint8 confirmCount; // 已收集的签名数量
    }

    // 交易ID → 交易信息
    mapping(uint256 => Transaction) public transactions;
    // 交易ID → 授权人地址 → 是否已签名
    mapping(uint256 => mapping(address => bool)) public isConfirmed;
    // 交易计数器(生成唯一交易ID)
    uint256 public txCount;

    // 事件:提交交易
    event TransactionSubmitted(uint256 indexed txId, address indexed submitter, address to, uint256 value);
    // 事件:确认交易(签名)
    event TransactionConfirmed(uint256 indexed txId, address indexed confirmer);
    // 事件:执行交易
    event TransactionExecuted(uint256 indexed txId, address indexed executor);

    // 修饰器:仅授权人可调用
    modifier onlyOwner() {
        require(msg.sender == owner1 || msg.sender == owner2, "Not an owner");
        _;
    }

    // 修饰器:交易未执行
    modifier notExecuted(uint256 txId) {
        require(!transactions[txId].executed, "Transaction already executed");
        _;
    }

    // 构造函数:初始化2个授权人
    constructor(address _owner1, address _owner2) {
        require(_owner1 != address(0) && _owner2 != address(0) && _owner1 != _owner2, "Invalid owners");
        owner1 = _owner1;
        owner2 = _owner2;
    }

    /**
     * @dev 提交交易(任意授权人可提交)
     * @param _to 接收方地址
     * @param _value 转账ETH金额(wei)
     * @param _data 合约调用数据(空则为纯ETH转账)
     * @return txId 生成的交易ID
     */
    function submitTransaction(
        address _to,
        uint256 _value,
        bytes calldata _data
    ) external onlyOwner returns (uint256 txId) {
        require(_to != address(0), "Invalid to address");
        
        txId = txCount;
        transactions[txId] = Transaction({
            to: _to,
            value: _value,
            data: _data,
            executed: false,
            confirmCount: 0
        });

        txCount++;
        emit TransactionSubmitted(txId, msg.sender, _to, _value);
    }

    /**
     * @dev 确认交易(签名,任意授权人可确认)
     * @param txId 交易ID
     */
    function confirmTransaction(uint256 txId) external onlyOwner notExecuted(txId) {
        Transaction storage txn = transactions[txId];
        require(!isConfirmed[txId][msg.sender], "Already confirmed");

        isConfirmed[txId][msg.sender] = true;
        txn.confirmCount++;

        emit TransactionConfirmed(txId, msg.sender);
    }

    /**
     * @dev 执行交易(需收集2个签名,任意授权人可执行)
     * @param txId 交易ID
     */
    function executeTransaction(uint256 txId) external onlyOwner notExecuted(txId) {
        Transaction storage txn = transactions[txId];
        // 验证签名数量≥2(2-of-2门限)
        require(txn.confirmCount >= 2, "Not enough confirmations");

        // 标记交易已执行
        txn.executed = true;
        // 执行交易:转账ETH或调用合约
        (bool success, ) = txn.to.call{value: txn.value}(txn.data);
        require(success, "Transaction execution failed");

        emit TransactionExecuted(txId, msg.sender);
    }

    /**
     * @dev 接收ETH(合约可存储ETH)
     */
    receive() external payable {}
}
  • 调用流程:
    • 步骤 1:部署合约
      输入2个授权人地址(如 Owner1: 0xAAA...,Owner2: 0xBBB...);
      部署合约(执行Constructor初始化合约 校验授权人地址合法性,存储 2 个授权人),记录合约地址(如 0xCCC...);

    • 步骤 2:提交交易
      用Owner1的地址调用submitTransaction,提交交易,生成交易ID,存储交易信息,触发提交事件;
      记录返回的txId(如 0);

    • 步骤 3:确认交易(收集签名)
      confirmTransaction 确认交易(签名) 校验授权人身份→校验未重复签名→增加签名数→触发确认事件
      用 Owner1 的地址调用confirmTransaction(0)(第一个签名);
      用 Owner2 的地址调用confirmTransaction(0)(第二个签名,达到 2-of-2 门限)。

    • 步骤 4:执行交易
      用任意授权人(如 Owner1)调用executeTransaction(0)执行交易;
      查看区块链浏览器:合约向 0xDDD... 转账 0.005 ETH,交易执行成功。

四、时间锁(Timelock)核心定义

是什么?
时间锁是区块链 / 智能合约中强制延迟执行操作的安全机制,核心逻辑是:当用户提交一笔操作(如转账、合约升级、多签交易)后,必须等待预设的延迟时间(如 24 小时),且当前区块时间超过该延迟时间阈值时,操作才能被执行;未到时间则无法执行。
本质是给关键操作增加「冷却期 / 反悔期」,防止恶意操作、误操作或内部攻击,是智能合约安全的核心组件(尤其适配多签、DAO、合约升级场景)。

  • 关键术语

    • 提交时间(proposalTime):操作被提交到合约的区块时间;
    • 延迟时间(delay):预设的最小等待时间(如 86400 秒 = 24 小时);
    • 可执行时间(executionTime):proposalTime + delay,只有≥该时间才能执行操作;
    • 过期时间(expirationTime):可选,超过该时间操作失效(防止长期未执行的废操作)。
  • 时间锁的核心作用

    • 防误操作 / 恶意操作(多签 + 时间锁核心价值)
      多签合约中添加时间锁:授权人提交大额转账后,有 24 小时反悔期 —— 若发现是误操作 / 恶意提交,可及时撤销,避免资产损失。
    • 防闪电贷攻击 / 套利
      DeFi 合约中,关键操作(如调整费率、清算规则)加时间锁,防止黑客利用闪电贷在操作生效前套利。
    • DAO 提案生效延迟
      DAO 投票通过提案后,需等待时间锁到期才执行(如 7 天),给社区成员足够时间理解提案、撤离风险资产。
    • 合约升级安全
      合约升级前,时间锁延迟生效(如 48 小时),用户可在窗口期将资产转出,避免升级漏洞导致损失。
    • 合规与审计
      延迟期内可对操作进行审计,确认无漏洞后再执行,满足金融 / 监管合规要求。
  • 基础时间锁合约示例

点击查看代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @title GenericTimelock
 * @dev 通用型相对时间锁合约
 * 核心逻辑:提交操作→等待delay秒→执行操作(支持ETH转账/合约调用)
 */
contract GenericTimelock {
    // 管理员:有权提交/执行/撤销操作(生产环境可替换为多签地址)
    address public immutable admin;
    // 延迟时间(秒):提交后需等待该时间才能执行
    uint256 public immutable delay;

    // 操作结构体:存储待执行的操作信息
    struct Operation {
        address to;          // 接收方地址
        uint256 value;       // 转账ETH金额(wei)
        bytes data;          // 合约调用calldata
        uint256 proposalTime;// 提交时间(区块时间)
        bool executed;       // 是否已执行
        bool canceled;       // 是否已撤销
    }

    // 操作ID → 操作信息(操作ID=keccak256(操作关键参数),避免重复)
    mapping(bytes32 => Operation) public operations;

    // 事件:提交操作
    event OperationProposed(bytes32 indexed opId, address indexed proposer, address to, uint256 value);
    // 事件:执行操作
    event OperationExecuted(bytes32 indexed opId, address indexed executor);
    // 事件:撤销操作
    event OperationCanceled(bytes32 indexed opId, address indexed canceler);

    // 修饰器:仅管理员可调用
    modifier onlyAdmin() {
        require(msg.sender == admin, "Not admin");
        _;
    }

    // 修饰器:操作未执行且未撤销
    modifier validOperation(bytes32 opId) {
        Operation storage op = operations[opId];
        require(op.proposalTime > 0, "Operation does not exist");
        require(!op.executed, "Operation already executed");
        require(!op.canceled, "Operation canceled");
        _;
    }

    // 修饰器:操作已到可执行时间
    modifier timeLockPassed(bytes32 opId) {
        Operation storage op = operations[opId];
        require(block.timestamp >= op.proposalTime + delay, "Time lock not passed");
        _;
    }

    /**
     * @dev 构造函数
     * @param _admin 管理员地址(生产环境建议设为多签合约地址)
     * @param _delay 延迟时间(秒),建议≥86400(24小时)
     */
    constructor(address _admin, uint256 _delay) {
        require(_admin != address(0), "Invalid admin");
        require(_delay > 0, "Delay must be > 0");
        admin = _admin;
        delay = _delay;
    }

    /**
     * @dev 生成操作ID(唯一标识,防止重复提交)
     * @param to 接收方
     * @param value 转账金额
     * @param data 调用数据
     * @return opId 操作ID
     */
    function getOperationId(
        address to,
        uint256 value,
        bytes calldata data
    ) public pure returns (bytes32 opId) {
        opId = keccak256(abi.encodePacked(to, value, data));
    }

    /**
     * @dev 提交操作(提案)
     * @param to 接收方地址
     * @param value 转账ETH金额
     * @param data 合约调用数据(空则为纯ETH转账)
     * @return opId 操作ID
     */
    function proposeOperation(
        address to,
        uint256 value,
        bytes calldata data
    ) external onlyAdmin returns (bytes32 opId) {
        require(to != address(0), "Invalid to address");
        opId = getOperationId(to, value, data);
        require(operations[opId].proposalTime == 0, "Operation already proposed");

        operations[opId] = Operation({
            to: to,
            value: value,
            data: data,
            proposalTime: block.timestamp,
            executed: false,
            canceled: false
        });

        emit OperationProposed(opId, msg.sender, to, value);
        return opId;
    }

    /**
     * @dev 执行操作(需等待时间锁到期)
     * @param to 接收方地址(需与提交时一致)
     * @param value 转账金额(需与提交时一致)
     * @param data 调用数据(需与提交时一致)
     */
    function executeOperation(
        address to,
        uint256 value,
        bytes calldata data
    ) external onlyAdmin validOperation(getOperationId(to, value, data)) timeLockPassed(getOperationId(to, value, data)) {
        bytes32 opId = getOperationId(to, value, data);
        Operation storage op = operations[opId];

        // 标记操作已执行
        op.executed = true;
        // 执行转账/合约调用
        (bool success, ) = op.to.call{value: op.value}(op.data);
        require(success, "Operation execution failed");

        emit OperationExecuted(opId, msg.sender);
    }

    /**
     * @dev 撤销操作(仅未执行/未到期时可撤销)
     * @param to 接收方地址
     * @param value 转账金额
     * @param data 调用数据
     */
    function cancelOperation(
        address to,
        uint256 value,
        bytes calldata data
    ) external onlyAdmin validOperation(getOperationId(to, value, data)) {
        bytes32 opId = getOperationId(to, value, data);
        operations[opId].canceled = true;

        emit OperationCanceled(opId, msg.sender);
    }

    /**
     * @dev 接收ETH(合约可存储ETH)
     */
    receive() external payable {}
}
  • 调用流程

    • 部署合约:管理员地址填你的钱包地址,延迟时间设为3600(1 小时);
    • 提交操作:调用proposeOperation,to 填测试地址,value 填1000000000000000(0.001 ETH),data 留空;
    • 等待 1 小时:期间调用executeOperation会提示 “Time lock not passed”;
    • 执行操作:1 小时后调用executeOperation,参数与提交时一致,合约向测试地址转账 0.001 ETH;
    • 撤销测试:若提交后未到 1 小时,调用cancelOperation可撤销操作,撤销后无法执行。
  • 多签 + 时间锁整合合约(生产级核心场景)
    多签合约仅解决 “多人授权”,但缺少 “反悔期”;时间锁仅解决 “延迟执行”,但缺少 “多人授权”—— 二者结合是生产环境资产安全的黄金组合(如 Gnosis Safe 的时间锁模块)。

点击查看代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @title MultisigWithTimelock
 * @dev 2-of-2多签 + 时间锁整合合约
 * 核心流程:提交交易→收集2个签名→等待delay秒→执行交易
 */
contract MultisigWithTimelock {
    // 多签授权人
    address public immutable owner1;
    address public immutable owner2;
    // 时间锁延迟(秒)
    uint256 public immutable delay;

    // 交易结构体:整合多签+时间锁
    struct Transaction {
        address to;
        uint256 value;
        bytes data;
        uint256 proposalTime; // 提交时间(时间锁起点)
        bool executed;
        bool canceled;
        uint8 confirmCount; // 多签签名数
    }

    mapping(uint256 => Transaction) public transactions;
    mapping(uint256 => mapping(address => bool)) public isConfirmed;
    uint256 public txCount;

    // 事件
    event TransactionProposed(uint256 indexed txId, address indexed proposer);
    event TransactionConfirmed(uint256 indexed txId, address indexed confirmer);
    event TransactionExecuted(uint256 indexed txId, address indexed executor);
    event TransactionCanceled(uint256 indexed txId, address indexed canceler);

    // 修饰器:仅授权人
    modifier onlyOwner() {
        require(msg.sender == owner1 || msg.sender == owner2, "Not owner");
        _;
    }

    // 修饰器:交易有效(未执行/未撤销)
    modifier validTx(uint256 txId) {
        Transaction storage txn = transactions[txId];
        require(txn.proposalTime > 0, "Tx does not exist");
        require(!txn.executed, "Tx executed");
        require(!txn.canceled, "Tx canceled");
        _;
    }

    // 修饰器:时间锁到期
    modifier timelockPassed(uint256 txId) {
        Transaction storage txn = transactions[txId];
        require(block.timestamp >= txn.proposalTime + delay, "Timelock not passed");
        _;
    }

    // 修饰器:多签签名数≥2
    modifier enoughConfirmations(uint256 txId) {
        require(transactions[txId].confirmCount >= 2, "Not enough confirmations");
        _;
    }

    constructor(address _owner1, address _owner2, uint256 _delay) {
        require(_owner1 != _owner2 && _owner1 != address(0), "Invalid owners");
        require(_delay > 0, "Delay > 0");
        owner1 = _owner1;
        owner2 = _owner2;
        delay = _delay;
    }

    /**
     * @dev 提交交易(多签授权人提交,启动时间锁)
     */
    function proposeTx(address to, uint256 value, bytes calldata data) external onlyOwner returns (uint256 txId) {
        require(to != address(0), "Invalid to");
        txId = txCount;
        transactions[txId] = Transaction({
            to: to,
            value: value,
            data: data,
            proposalTime: block.timestamp,
            executed: false,
            canceled: false,
            confirmCount: 0
        });
        txCount++;
        emit TransactionProposed(txId, msg.sender);
    }

    /**
     * @dev 确认交易(多签签名)
     */
    function confirmTx(uint256 txId) external onlyOwner validTx(txId) {
        Transaction storage txn = transactions[txId];
        require(!isConfirmed[txId][msg.sender], "Already confirmed");
        isConfirmed[txId][msg.sender] = true;
        txn.confirmCount++;
        emit TransactionConfirmed(txId, msg.sender);
    }

    /**
     * @dev 执行交易(需:2个签名+时间锁到期)
     */
    function executeTx(uint256 txId) external onlyOwner validTx(txId) enoughConfirmations(txId) timelockPassed(txId) {
        Transaction storage txn = transactions[txId];
        txn.executed = true;
        (bool success, ) = txn.to.call{value: txn.value}(txn.data);
        require(success, "Tx failed");
        emit TransactionExecuted(txId, msg.sender);
    }

    /**
     * @dev 撤销交易(仅未执行/未到期时)
     */
    function cancelTx(uint256 txId) external onlyOwner validTx(txId) {
        transactions[txId].canceled = true;
        emit TransactionCanceled(txId, msg.sender);
    }

    receive() external payable {}
}

学习技术不是用来写HelloWorld和Demo的,而是要用来解决线上系统的真实问题的.