社交恢復錢包部署完整指南:智慧合約開發、Guardian 網路建置與安全最佳實踐

社交恢復錢包是以太坊錢包安全架構的重大創新,透過引入Guardian概念解決私鑰遺失無法恢復的難題。本文提供從智慧合約開發到Guardian網路建置的完整指南,涵蓋合約架構設計、守護者配置、恢復流程實現、安全審計要點、以及運維監控最佳實踐。

社交恢復錢包部署完整指南:智慧合約開發、Guardian 網路建置與安全最佳實踐

執行摘要

社交恢復錢包是以太坊錢包安全架構的重大創新,透過引入「守護者」(Guardian)概念解決了私鑰遺失無法恢復的千古難題。與傳統錢包不同,社交恢復錢包允許用戶透過預先設定的信任網路來恢復帳戶訪問權限,為數位資產保護提供了前所未有的靈活性與安全性。截至 2026 年第一季度,社交恢復錢包已被全球超過 500 萬用戶採用,管理資產規模超過 200 億美元。本文提供從智慧合約開發到 Guardian 網路建置的完整部署指南,涵蓋合約架構設計、守護者配置、恢復流程實現、安全審計要點、以及運維監控最佳實踐。

第一章:社交恢復機制的理論基礎

1.1 傳統錢包的局限性

在深入社交恢復錢包之前,我們需要理解傳統錢包面臨的根本問題:

私鑰的單一失敗點

傳統以太坊錢包採用外部擁有帳戶(EOA),其安全性完全依賴於私鑰的保密性。一旦私鑰遺失、被盜、或持有者無法訪問,帳戶中的資產將永遠無法恢復。這是區塊鏈「無信任」特性的必然結果——沒有所謂的「忘記密碼」選項。

現有解決方案的不完美

  1. 助記詞備份:用戶需要安全存儲助記詞,但物理存儲有遺失、火災、被盜的風險;數位存儲有被黑客攻擊的風險。
  1. 硬體錢包:提供較好的安全性,但仍存在設備損壞、固件漏洞、甚至供應鏈攻擊的風險。
  1. 多簽名錢包:要求多個私鑰同時簽名才能執行交易,但每次交易都需要多方參與,體驗繁瑣;且無法解決「單一擁有者」場景。
  1. 服務商託管:將資產交給中心化服務商管理,但犧牲了去中心化的核心價值。

1.2 社交恢復的創新設計

社交恢復錢包的設計目標是:平時享受與傳統錢包相同的便利性,同時在緊急情況下可以透過信任網路恢復訪問。

核心設計原則

  1. 分離平時與緊急場景:平時使用單一私鑰,體驗與 EOA 相同;緊急情況下動用守護者網路。
  1. 社會化信任層:利用人際關係建立恢復機制,而非純技術手段。
  1. 延遲與審批:恢復過程需要時間和多方確認,防止盜賊快速控制資產。
  1. 漸進式安全:可配置的安全參數,適應不同用戶的風險偏好。

與 MPC 錢包的比較

社交恢復錢包與 MPC 錢包解決的是不同問題:

特性社交恢復錢包MPC 錢包
日常使用單一私鑰閾值簽名
恢復機制守護者網路重新生成分片
隱私較高依賴服務商
成本較低(智慧合約)較高(多方計算)
適用場景個人用戶機構用戶

第二章:智慧合約架構設計

2.1 合約整體架構

社交恢復錢包的智慧合約通常由多個模組組成,每個模組負責特定功能:

┌─────────────────────────────────────────────────────────────┐
│                    SocialRecoveryWallet                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐       │
│  │   Owner      │  │   Guardian  │  │   Recovery  │       │
│  │   Module     │  │   Module    │  │   Module    │       │
│  └─────────────┘  └─────────────┘  └─────────────┘       │
│                                                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐       │
│  │    ERC-4337 │  │   Security  │  │    Token    │       │
│  │   Entrypoint│  │   Module    │  │   Manager   │       │
│  └─────────────┘  └─────────────┘  └─────────────┘       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

核心合約組件

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/**
 * @title SocialRecoveryWallet
 * @dev 社交恢復錢包核心合約
 */
contract SocialRecoveryWallet {
    
    // ═══════════════════════════════════════════════════════
    // 事件定義
    // ═══════════════════════════════════════════════════════
    
    event OwnerChanged(address indexed oldOwner, address indexed newOwner);
    event GuardianAdded(address indexed guardian);
    event GuardianRemoved(address indexed guardian);
    event RecoveryInitiated(
        address indexed guardian,
        address indexed newOwner,
        uint256 executeAfter
    );
    event RecoveryExecuted(address indexed newOwner);
    event RecoveryCancelled(address indexed guardian);
    event TransactionExecuted(
        address indexed to,
        uint256 value,
        bytes data,
        uint256 nonce
    );
    
    // ═══════════════════════════════════════════════════════
    // 狀態變量
    // ═══════════════════════════════════════════════════════
    
    // 當前所有者
    address public owner;
    
    // 守護者集合
    mapping(address => bool) public guardians;
    address[] public guardianList;
    uint256 public guardianCount;
    
    // 恢復請求
    struct RecoveryRequest {
        address newOwner;
        uint256 confirmationCount;
        uint256 executeAfter;
        mapping(address => bool) confirmedBy;
    }
    mapping(bytes32 => RecoveryRequest) public recoveryRequests;
    
    // 安全參數
    uint256 public constant RECOVERY_DELAY = 2 days;
    uint256 public constant GUARDIAN_DELAY = 1 days;
    uint256 public constant MIN_GUARDIANS = 3;
    uint256 public constant RECOVERY_THRESHOLD = 2;
    
    // nonce 用於防重放攻擊
    uint256 public nonce;
    
    // ERC-4337 EntryPoint
    address public immutable entryPoint;
    
    // ═══════════════════════════════════════════════════════
    // 修飾符
    // ═══════════════════════════════════════════════════════
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }
    
    modifier onlyGuardian() {
        require(guardians[msg.sender], "Not guardian");
        _;
    }
    
    // ═══════════════════════════════════════════════════════
    // 建構函數
    // ═══════════════════════════════════════════════════════
    
    constructor(
        address _owner,
        address[] memory _guardians,
        address _entryPoint
    ) {
        require(_owner != address(0), "Invalid owner");
        require(_guardians.length >= MIN_GUARDIANS, "Too few guardians");
        
        owner = _owner;
        entryPoint = _entryPoint;
        
        // 添加初始守護者
        for (uint256 i = 0; i < _guardians.length; i++) {
            _addGuardian(_guardians[i]);
        }
    }
    
    // ═══════════════════════════════════════════════════════
    // 所有者功能
    // ═══════════════════════════════════════════════════════
    
    /**
     * @dev 執行交易
     */
    function execute(
        address to,
        uint256 value,
        bytes calldata data,
        uint256 gasLimit
    ) external onlyOwner returns (bytes memory) {
        require(to != address(0), "Invalid target");
        
        nonce++;
        
        (bool success, bytes memory result) = to.call{value: value, gas: gasLimit}(data);
        
        require(success, "Transaction failed");
        
        emit TransactionExecuted(to, value, data, nonce);
        
        return result;
    }
    
    /**
     * @dev 批量執行交易
     */
    function executeBatch(
        address[] calldata tos,
        uint256[] calldata values,
        bytes[] calldata datas
    ) external onlyOwner {
        require(tos.length == values.length, "Length mismatch");
        require(tos.length == datas.length, "Length mismatch");
        
        for (uint256 i = 0; i < tos.length; i++) {
            (bool success, ) = tos[i].call{value: values[i]}(datas[i]);
            require(success, "Batch transaction failed");
        }
    }
    
    /**
     * @dev 添加守護者(需要延遲生效)
     */
    function addGuardian(address guardian) external onlyOwner {
        require(guardian != address(0), "Invalid guardian");
        require(guardian != owner, "Owner cannot be guardian");
        require(!guardians[guardian], "Already guardian");
        
        _addGuardian(guardian);
        
        emit GuardianAdded(guardian);
    }
    
    /**
     * @dev 移除守護者(立即生效)
     */
    function removeGuardian(address guardian) external onlyOwner {
        require(guardians[guardian], "Not guardian");
        require(guardianCount > MIN_GUARDIANS, "Too few guardians");
        
        guardians[guardian] = false;
        guardianCount--;
        
        // 從列表中移除
        for (uint256 i = 0; i < guardianList.length; i++) {
            if (guardianList[i] == guardian) {
                guardianList[i] = guardianList[guardianList.length - 1];
                guardianList.pop();
                break;
            }
        }
        
        emit GuardianRemoved(guardian);
    }
    
    /**
     * @dev 內部添加守護者
     */
    function _addGuardian(address guardian) internal {
        guardians[guardian] = true;
        guardianList.push(guardian);
        guardianCount++;
    }
    
    // ═══════════════════════════════════════════════════════
    // 守護者功能
    // ═══════════════════════════════════════════════════════
    
    /**
     * @dev 發起恢復請求
     */
    function initiateRecovery(address newOwner) external onlyGuardian {
        require(newOwner != address(0), "Invalid new owner");
        require(newOwner != owner, "Already owner");
        
        bytes32 requestId = keccak256(abi.encodePacked(newOwner, block.timestamp));
        
        RecoveryRequest storage request = recoveryRequests[requestId];
        request.newOwner = newOwner;
        request.confirmationCount = 1;
        request.executeAfter = block.timestamp + RECOVERY_DELAY;
        request.confirmedBy[msg.sender] = true;
        
        emit RecoveryInitiated(msg.sender, newOwner, request.executeAfter);
    }
    
    /**
     * @dev 確認恢復請求
     */
    function confirmRecovery(bytes32 requestId) external onlyGuardian {
        RecoveryRequest storage request = recoveryRequests[requestId];
        require(request.executeAfter > 0, "No pending request");
        require(!request.confirmedBy[msg.sender], "Already confirmed");
        require(block.timestamp < request.executeAfter, "Request expired");
        
        request.confirmationCount++;
        request.confirmedBy[msg.sender] = true;
    }
    
    /**
     * @dev 執行恢復
     */
    function executeRecovery(bytes32 requestId) external {
        RecoveryRequest storage request = recoveryRequests[requestId];
        require(request.executeAfter > 0, "No pending request");
        require(
            block.timestamp >= request.executeAfter,
            "Recovery not ready"
        );
        require(
            request.confirmationCount >= RECOVERY_THRESHOLD,
            "Insufficient confirmations"
        );
        
        address oldOwner = owner;
        owner = request.newOwner;
        
        // 清除請求
        delete recoveryRequests[requestId];
        
        emit RecoveryExecuted(owner);
        emit OwnerChanged(oldOwner, owner);
    }
    
    /**
     * @dev 取消恢復請求
     */
    function cancelRecovery(bytes32 requestId) external onlyOwner {
        RecoveryRequest storage request = recoveryRequests[requestId];
        require(request.executeAfter > 0, "No pending request");
        
        delete recoveryRequests[requestId];
        
        emit RecoveryCancelled(msg.sender);
    }
    
    // ═══════════════════════════════════════════════════════
    // 查詢功能
    // ═══════════════════════════════════════════════════════
    
    /**
     * @dev 獲取守護者列表
     */
    function getGuardians() external view returns (address[] memory) {
        return guardianList;
    }
    
    /**
     * @dev 檢查地址是否是守護者
     */
    function isGuardian(address addr) external view returns (bool) {
        return guardians[addr];
    }
    
    /**
     * @dev 計算恢復請求 ID
     */
    function getRecoveryId(address newOwner) external pure returns (bytes32) {
        return keccak256(abi.encodePacked(newOwner, block.timestamp));
    }
    
    // ═══════════════════════════════════════════════════════
    // ERC-4337 支持
    // ═══════════════════════════════════════════════════════
    
    /**
     * @dev 驗證用戶操作
     */
    function validateUserOp(
        UserOperation calldata userOp,
        bytes32 userOpHash,
        uint256 missingWalletFunds
    ) external view returns (uint256) {
        require(userOp.sender == address(this), "Wrong wallet");
        
        bytes32 hash = keccak256(
            abi.encode(
                userOpHash,
                nonce,
                uint256(0),
                uint256(0),
                block.chainid
            )
        );
        
        if (owner != ecrecover(hash, userOp.signature[0], 
                bytes32(userOp.signature[1:33]), 
                bytes32(userOp.signature[33:65]))) {
            return 1;
        }
        
        return 0;
    }
    
    receive() external payable {}
}

2.2 合約升級機制

智慧合約需要支持升級以修復可能的漏洞和添加新功能:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/proxy/Proxy.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

/**
 * @title WalletFactory
 * @dev 錢包工廠合約
 */
contract WalletFactory {
    
    address public immutable implementation;
    address public owner;
    
    mapping(address => bool) public wallets;
    mapping(address => address[]) public userWallets;
    
    event WalletCreated(address indexed wallet, address indexed owner);
    
    constructor(address _implementation) {
        implementation = _implementation;
        owner = msg.sender;
    }
    
    /**
     * @dev 創建錢包
     */
    function createWallet(
        address[] calldata guardians,
        bytes calldata initData
    ) external returns (address) {
        ERC1967Proxy proxy = new ERC1967Proxy(
            implementation,
            initData
        );
        
        address wallet = address(proxy);
        wallets[wallet] = true;
        userWallets[msg.sender].push(wallet);
        
        emit WalletCreated(wallet, msg.sender);
        
        return wallet;
    }
    
    /**
     * @dev 獲取用戶的所有錢包
     */
    function getUserWallets(address user) 
        external view returns (address[] memory) {
        return userWallets[user];
    }
}

2.3 安全模組設計

安全模組負責額外的安全檢查:

/**
 * @title SecurityModule
 * @dev 安全模組合約
 */
contract SecurityModule {
    
    // 每日交易限額
    mapping(address => uint256) public dailyLimits;
    mapping(address => uint256) public dailySpent;
    mapping(address => uint256) public lastResetDay;
    
    // 地址白名單
    mapping(address => mapping(address => bool)) public whitelist;
    
    // 緊急暫停
    bool public paused;
    address public securityAdmin;
    
    modifier whenNotPaused() {
        require(!paused, "Paused");
        _;
    }
    
    modifier onlySecurityAdmin() {
        require(msg.sender == securityAdmin, "Not admin");
        _;
    }
    
    /**
     * @dev 檢查交易是否在限額內
     */
    function checkDailyLimit(
        address wallet,
        uint256 amount
    ) internal returns (bool) {
        uint256 today = block.timestamp / 1 days;
        
        if (lastResetDay[wallet] != today) {
            dailySpent[wallet] = 0;
            lastResetDay[wallet] = today;
        }
        
        require(
            dailySpent[wallet] + amount <= dailyLimits[wallet],
            "Daily limit exceeded"
        );
        
        dailySpent[wallet] += amount;
        
        return true;
    }
    
    /**
     * @dev 檢查目標地址是否在白名單
     */
    function checkWhitelist(
        address wallet,
        address to
    ) internal view returns (bool) {
        if (whitelist[wallet][to]) {
            return true;
        }
        
        // 允許與已知 DeFi 合約交互
        return isKnownContract(to);
    }
    
    /**
     * @dev 緊急暫停
     */
    function pause() external onlySecurityAdmin {
        paused = true;
    }
    
    /**
     * @dev 恢復正常
     */
    function unpause() external onlySecurityAdmin {
        paused = false;
    }
}

第三章:Guardian 網路建置

3.1 Guardian 選擇策略

Guardian 的選擇直接影響錢包的安全性:

數量建議

守護者類型

  1. 個人 Guardian:家人、朋友、自己控制的其他錢包
  2. 專業服務:法律機構、審計公司、專業託管服務
  3. 硬體設備:自己控制的硬體錢包

選擇原則

3.2 Guardian 客戶端配置

Guardian 需要運行專門的客戶端軟體來參與恢復流程:

# guardian_client/client.py

import asyncio
import logging
from typing import Optional, Dict, Any
from dataclasses import dataclass
from enum import Enum
import web3
from eth_account import Account

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class GuardianState(Enum):
    IDLE = "idle"
    MONITORING = "monitoring"
    RECOVERY_REQUESTED = "recovery_requested"
    RECOVERY_CONFIRMED = "recovery_confirmed"

@dataclass
class GuardianConfig:
    private_key: str
    wallet_address: str
    rpc_url: str
    wallet_contract: str
    notification_endpoint: Optional[str] = None
    auto_confirm: bool = False

class GuardianClient:
    def __init__(self, config: GuardianConfig):
        self.config = config
        self.w3 = web3.Web3(web3.HTTPProvider(config.rpc_url))
        self.account = Account.from_key(config.private_key)
        self.state = GuardianState.IDLE
        
        # 錢包合約ABI
        self.wallet_abi = [...]
        
    async def start_monitoring(self):
        """開始監控錢包狀態"""
        
        logger.info(f"Guardian {self.account.address} starting monitoring")
        
        self.state = GuardianState.MONITORING
        
        # 監控區塊新區塊
        while True:
            try:
                await self._check_recovery_requests()
                await asyncio.sleep(60)  # 每分鐘檢查一次
                
            except Exception as e:
                logger.error(f"Monitoring error: {e}")
                await asyncio.sleep(5)
    
    async def _check_recovery_requests(self):
        """檢查是否有待處理的恢復請求"""
        
        # 查詢合約事件
        contract = self.w3.eth.contract(
            address=self.config.wallet_contract,
            abi=self.wallet_abi
        )
        
        # 獲取最新事件
        current_block = self.w3.eth.block_number
        
        # 過濾 RecoveryInitiated 事件
        events = contract.events.RecoveryInitiated.getLogs(
            fromBlock=current_block - 1000,
            argument_filters={'guardian': self.account.address}
        )
        
        for event in events:
            await self._handle_recovery_event(event)
    
    async def _handle_recovery_event(self, event):
        """處理恢復事件"""
        
        request_id = event['args']['requestId']
        new_owner = event['args']['newOwner']
        execute_after = event['args']['executeAfter']
        
        logger.info(
            f"Recovery request detected: "
            f"newOwner={new_owner}, executeAfter={execute_after}"
        )
        
        self.state = GuardianState.RECOVERY_REQUESTED
        
        # 發送通知
        if self.config.notification_endpoint:
            await self._send_notification(new_owner, execute_after)
        
        # 自動確認或等待手動確認
        if self.config.auto_confirm:
            await self._confirm_recovery(request_id)
        else:
            logger.info("Waiting for manual confirmation")
            await self._wait_for_manual_confirmation(request_id)
    
    async def _confirm_recovery(self, request_id: str):
        """確認恢復請求"""
        
        logger.info(f"Confirming recovery request: {request_id}")
        
        contract = self.w3.eth.contract(
            address=self.config.wallet_contract,
            abi=self.wallet_abi
        )
        
        # 構建交易
        tx = contract.functions.confirmRecovery(request_id).buildTransaction({
            'from': self.account.address,
            'nonce': self.w3.eth.get_transaction_count(self.account.address),
            'gas': 100000,
            'gasPrice': self.w3.eth.gas_price
        })
        
        # 簽名並發送
        signed_tx = self.account.sign_transaction(tx)
        tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction)
        
        receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
        
        logger.info(f"Recovery confirmed: {receipt['transactionHash'].hex()}")
        
        self.state = GuardianState.RECOVERY_CONFIRMED
    
    async def _wait_for_manual_confirmation(self, request_id: str):
        """等待手動確認"""
        
        # 這裡可以實現用戶界面交互
        # 簡化版本:等待輸入
        logger.info("Press 'y' to confirm recovery...")
        
        # 實際實現中需要用戶界面
        user_input = input()
        
        if user_input.lower() == 'y':
            await self._confirm_recovery(request_id)
        else:
            logger.info("Recovery not confirmed")
            self.state = GuardianState.MONITORING

3.3 分布式 Guardian 協調

多個 Guardian 需要協調工作:

# guardian_client/coordinator.py

import asyncio
import logging
from typing import List, Dict
from dataclasses import dataclass

@dataclass
class RecoverySession:
    request_id: str
    new_owner: str
    confirmed_guardians: List[str]
    threshold: int
    status: str

class GuardianCoordinator:
    """多個 Guardian 之間的協調器"""
    
    def __init__(self, guardians: List[str], threshold: int):
        self.guardians = guardians
        self.threshold = threshold
        self.sessions: Dict[str, RecoverySession] = {}
    
    async def start_recovery(
        self,
        request_id: str,
        new_owner: str
    ) -> RecoverySession:
        """開始新的恢復會話"""
        
        session = RecoverySession(
            request_id=request_id,
            new_owner=new_owner,
            confirmed_guardians=[],
            threshold=self.threshold,
            status="pending"
        )
        
        self.sessions[request_id] = session
        
        return session
    
    async def add_confirmation(
        self,
        request_id: str,
        guardian: str
    ) -> bool:
        """添加確認"""
        
        if request_id not in self.sessions:
            return False
        
        session = self.sessions[request_id]
        
        if guardian in session.confirmed_guardians:
            return False
        
        session.confirmed_guardians.append(guardian)
        
        # 檢查是否達到閾值
        if len(session.confirmed_guardians) >= session.threshold:
            session.status = "ready"
            return True
        
        return False
    
    async def broadcast_request(
        self,
        request_id: str,
        new_owner: str
    ):
        """廣播恢復請求給所有 Guardian"""
        
        # 實際實現中通過消息隊列或 P2P 網路廣播
        for guardian in self.guardians:
            await self._send_to_guardian(guardian, {
                'type': 'recovery_request',
                'request_id': request_id,
                'new_owner': new_owner
            })

第四章:前端整合與用戶體驗

4.1 錢包連接與認證

錢包需要與 DApp 整合:

// wallet-client/index.ts

import { ethers } from 'ethers';
import { ERC4337EthersProvider } from '@account-abstraction/sdk';

export class SocialRecoveryWallet {
  private provider: ethers.providers.JsonRpcProvider;
  private wallet: ethers.Contract;
  private entryPoint: string;
  
  constructor(
    walletAddress: string,
    rpcUrl: string,
    entryPoint: string = '0x5FF137D4b0FD9CDe4383A0a2d4E4D0f5D5a8f5F3'
  ) {
    this.provider = new ethers.providers.JsonRpcProvider(rpcUrl);
    this.entryPoint = entryPoint;
    
    this.wallet = new ethers.Contract(
      walletAddress,
      WALLET_ABI,
      this.provider
    );
  }
  
  /**
   * 連接錢包(使用瀏�器錢包)
   */
  async connect(signer: ethers.Signer): Promise<string> {
    const address = await signer.getAddress();
    return address;
  }
  
  /**
   * 發送交易
   */
  async sendTransaction(
    to: string,
    value: ethers.BigNumber,
    data: string,
    signer: ethers.Signer
  ): Promise<ethers.TransactionReceipt> {
    const walletAddress = this.wallet.address;
    
    // 構建交易
    const tx = {
      to,
      value,
      data
    };
    
    // 通過錢包合約執行
    const txResponse = await this.wallet
      .connect(signer)
      .execute(
        tx.to,
        tx.value,
        tx.data,
        50000 // gas limit
      );
    
    return txResponse.wait();
  }
  
  /**
   * 發起恢復(需要錢包所有權)
   */
  async initiateRecovery(
    newOwner: string,
    signer: ethers.Signer
  ): Promise<string> {
    const tx = await this.wallet
      .connect(signer)
      .initiateRecovery(newOwner);
    
    const receipt = await tx.wait();
    return receipt.transactionHash;
  }
  
  /**
   * 添加守護者
   */
  async addGuardian(
    guardian: string,
    signer: ethers.Signer
  ): Promise<ethers.TransactionReceipt> {
    const tx = await this.wallet
      .connect(signer)
      .addGuardian(guardian);
    
    return tx.wait();
  }
  
  /**
   * 獲取守護者列表
   */
  async getGuardians(): Promise<string[]> {
    return this.wallet.getGuardians();
  }
  
  /**
   * 獲取錢包餘額
   */
  async getBalance(): Promise<ethers.BigNumber> {
    return this.provider.getBalance(this.wallet.address);
  }
}

4.2 恢復流程用戶界面

恢復流程的 UI 設計至關重要:

// wallet-ui/RecoveryFlow.tsx

import React, { useState, useEffect } from 'react';
import { useWallet } from './hooks/useWallet';
import { GuardianClient } from './guardian-client';

export const RecoveryFlow: React.FC = () => {
  const { wallet } = useWallet();
  const [recoveryState, setRecoveryState] = useState<string>('idle');
  const [newOwner, setNewOwner] = useState<string>('');
  const [guardians, setGuardians] = useState<string[]>([]);
  const [confirmations, setConfirmations] = useState<number>(0);
  const [timeRemaining, setTimeRemaining] = useState<number>(0);
  
  useEffect(() => {
    loadRecoveryState();
  }, []);
  
  const loadRecoveryState = async () => {
    const guardianList = await wallet.getGuardians();
    setGuardians(guardianList);
    
    // 檢查是否有待處理的恢復
    // ...
  };
  
  const initiateRecovery = async () => {
    if (!ethers.utils.isAddress(newOwner)) {
      alert('Invalid address');
      return;
    }
    
    setRecoveryState('initiating');
    
    try {
      await wallet.initiateRecovery(newOwner, signer);
      setRecoveryState('initiated');
      
      // 啟動計時器
      startCountdown();
    } catch (error) {
      console.error('Recovery initiation failed:', error);
      setRecoveryState('idle');
    }
  };
  
  const confirmRecovery = async () => {
    setRecoveryState('confirming');
    
    try {
      // 這裡調用 Guardian 確認
      await GuardianClient.confirm(requestId);
      setConfirmations(prev => prev + 1);
    } catch (error) {
      console.error('Confirmation failed:', error);
    }
  };
  
  return (
    <div className="recovery-flow">
      {recoveryState === 'idle' && (
        <div className="initiate-section">
          <h2>Initiate Account Recovery</h2>
          <p>Enter the address that will become the new owner:</p>
          <input
            type="text"
            value={newOwner}
            onChange={(e) => setNewOwner(e.target.value)}
            placeholder="0x..."
          />
          <button onClick={initiateRecovery}>
            Start Recovery
          </button>
        </div>
      )}
      
      {recoveryState === 'initiated' && (
        <div className="confirmation-section">
          <h2>Waiting for Guardian Confirmations</h2>
          <div className="progress">
            {confirmations} / {guardians.length} confirmations
          </div>
          
          <div className="countdown">
            Time remaining: {formatTime(timeRemaining)}
          </div>
          
          <div className="guardians-status">
            {guardians.map((guardian, index) => (
              <GuardianStatus
                key={index}
                address={guardian}
                confirmed={index < confirmations}
              />
            ))}
          </div>
        </div>
      )}
      
      {recoveryState === 'ready' && (
        <div className="execute-section">
          <h2>Recovery Ready to Execute</h2>
          <p>The recovery will change ownership to:</p>
          <code>{newOwner}</code>
          
          <button onClick={executeRecovery}>
            Execute Recovery
          </button>
        </div>
      )}
    </div>
  );
};

第五章:安全審計與測試

5.1 合約審計清單

部署前必須完成以下審計項目:

訪問控制審計

邏輯漏洞檢查

邊界條件測試

5.2 自動化測試套件

// test/SocialRecoveryWallet.js

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("SocialRecoveryWallet", function() {
  let wallet;
  let owner;
  let guardian1, guardian2, guardian3;
  let addrs;
  
  beforeEach(async function() {
    [owner, guardian1, guardian2, guardian3, ...addrs] = await ethers.getSigners();
    
    const Wallet = await ethers.getContractFactory("SocialRecoveryWallet");
    
    wallet = await Wallet.deploy(
      owner.address,
      [guardian1.address, guardian2.address, guardian3.address],
      "0x5FF137D4b0FD9CDe4383A0a2d4E4D0f5D5a8f5F3"
    );
  });
  
  describe("Ownership", function() {
    it("should set correct owner", async function() {
      expect(await wallet.owner()).to.equal(owner.address);
    });
    
    it("should allow owner to execute transactions", async function() {
      const [_, addr1] = await ethers.getSigners();
      
      await owner.sendTransaction({
        to: wallet.address,
        value: ethers.utils.parseEther("1")
      });
      
      const balance = await wallet.provider.getBalance(wallet.address);
      expect(balance).to.equal(ethers.utils.parseEther("1"));
    });
  });
  
  describe("Guardians", function() {
    it("should set initial guardians", async function() {
      expect(await wallet.guardians(guardian1.address)).to.be.true;
      expect(await wallet.guardians(guardian2.address)).to.be.true;
      expect(await wallet.guardians(guardian3.address)).to.be.true;
      expect(await wallet.guardianCount()).to.equal(3);
    });
    
    it("should allow adding new guardian", async function() {
      const newGuardian = addrs[0];
      
      await wallet.addGuardian(newGuardian.address);
      
      expect(await wallet.guardians(newGuardian.address)).to.be.true;
      expect(await wallet.guardianCount()).to.equal(4);
    });
    
    it("should prevent owner from being guardian", async function() {
      await expect(
        wallet.addGuardian(owner.address)
      ).to.be.revertedWith("Owner cannot be guardian");
    });
  });
  
  describe("Recovery", function() {
    it("should allow guardian to initiate recovery", async function() {
      const newOwner = addrs[1].address;
      
      await wallet.connect(guardian1).initiateRecovery(newOwner);
      
      // 檢查事件
      // ...
    });
    
    it("should require threshold confirmations to execute", async function() {
      const newOwner = addrs[1].address;
      
      // 只有一個 Guardian 確認
      await wallet.connect(guardian1).initiateRecovery(newOwner);
      await wallet.connect(guardian1).confirmRecovery(
        ethers.utils.keccak256(ethers.utils.toUtf8Bytes(newOwner + Date.now().toString()))
      );
      
      // 嘗試執行應該失敗
      await expect(
        wallet.executeRecovery(
          ethers.utils.keccak256(ethers.utils.toUtf8Bytes(newOwner + Date.now().toString()))
        )
      ).to.be.revertedWith("Insufficient confirmations");
    });
  });
});

5.3 滲透測試要點

智能合約滲透測試

  1. 重入攻擊:檢查所有外部調用
  2. 整數溢位:使用 SafeMath 或 Solidity 0.8+
  3. 訪問控制:繞過所有權限檢查
  4. DoS 攻擊:嘗試使合約無法使用

前端滲透測試

  1. XSS 攻擊:注入惡意腳本
  2. CSRF 攻擊:偽造用戶操作
  3. 中間人攻擊:攔截通信

結論

社交恢復錢包代表了以太坊錢包安全的重大進步,通過引入守護者網路實現了安全性與便利性的平衡。本文提供了從智慧合約開發到 Guardian 網路建置的完整指南。

正確部署社交恢復錢包需要:

  1. 安全的合約架構設計
  2. 合理的守護者配置
  3. 完善的監控與警報系統
  4. 全面的審計與測試

遵循這些最佳實踐,用戶可以獲得比傳統錢包更高的安全性,同時保持良好的使用體驗。

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。

目前尚無評論,成為第一個發表評論的人吧!