Web3安全开发指南

Web3安全开发指南

Web3安全是区块链应用开发中最重要的环节之一。本章将介绍智能合约安全、前端安全、钱包安全等关键内容。

智能合约安全

1. 常见漏洞

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// 1. 重入攻击
contract Vulnerable {
    mapping(address => uint) public balances;

    function withdraw() public {
        uint balance = balances[msg.sender];
        require(balance > 0);

        // 危险:在更新状态前发送以太币
        (bool success, ) = msg.sender.call{value: balance}("");
        require(success);

        balances[msg.sender] = 0;
    }
}

// 安全版本
contract Secure {
    mapping(address => uint) public balances;

    function withdraw() public {
        uint balance = balances[msg.sender];
        require(balance > 0);

        // 先更新状态
        balances[msg.sender] = 0;

        // 再发送以太币
        (bool success, ) = msg.sender.call{value: balance}("");
        require(success);
    }
}

// 2. 整数溢出
contract Vulnerable {
    mapping(address => uint8) public balances;

    function transfer(address to, uint8 amount) public {
        // 危险:可能发生整数溢出
        balances[to] += amount;
    }
}

// 安全版本
contract Secure {
    using SafeMath for uint256;
    mapping(address => uint256) public balances;

    function transfer(address to, uint256 amount) public {
        // 使用SafeMath防止溢出
        balances[to] = balances[to].add(amount);
    }
}

// 3. 访问控制
contract Vulnerable {
    address public owner;

    function changeOwner(address newOwner) public {
        // 危险:没有访问控制
        owner = newOwner;
    }
}

// 安全版本
contract Secure {
    address public owner;

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    function changeOwner(address newOwner) public onlyOwner {
        require(newOwner != address(0), "Invalid address");
        owner = newOwner;
    }
}

2. 安全开发模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// 1. 检查-生效-交互模式
contract CheckEffectInteraction {
    mapping(address => uint) public balances;

    function withdraw() public {
        // 检查
        uint balance = balances[msg.sender];
        require(balance > 0, "Insufficient balance");

        // 生效
        balances[msg.sender] = 0;

        // 交互
        (bool success, ) = msg.sender.call{value: balance}("");
        require(success, "Transfer failed");
    }
}

// 2. 紧急停止模式
contract EmergencyStop {
    bool public stopped;
    address public owner;

    modifier stopInEmergency {
        require(!stopped, "Contract is paused");
        _;
    }

    modifier onlyInEmergency {
        require(stopped, "Contract is not paused");
        _;
    }

    function toggleContractState() public {
        require(msg.sender == owner, "Not owner");
        stopped = !stopped;
    }

    function deposit() public payable stopInEmergency {
        // 存款逻辑
    }

    function withdraw() public onlyInEmergency {
        // 紧急取款逻辑
    }
}

// 3. 速率限制模式
contract RateLimiter {
    uint public constant RATE_LIMIT = 1 ether;
    uint public constant TIME_PERIOD = 1 days;

    mapping(address => uint) public lastWithdrawTime;
    mapping(address => uint) public withdrawnAmount;

    function withdraw(uint amount) public {
        require(amount <= RATE_LIMIT, "Exceeds rate limit");

        uint currentPeriod = block.timestamp / TIME_PERIOD;
        uint lastPeriod = lastWithdrawTime[msg.sender] / TIME_PERIOD;

        if (currentPeriod > lastPeriod) {
            withdrawnAmount[msg.sender] = 0;
        }

        require(withdrawnAmount[msg.sender] + amount <= RATE_LIMIT, 
                "Exceeds period limit");

        withdrawnAmount[msg.sender] += amount;
        lastWithdrawTime[msg.sender] = block.timestamp;

        // 执行提款
    }
}

3. 安全工具和审计

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 1. Mythril安全分析
const { analyze } = require('mythril-classic');

async function analyzeSecurity(contractSource) {
    try {
        const issues = await analyze(contractSource);
        return issues.map(issue => ({
            title: issue.title,
            description: issue.description,
            severity: issue.severity,
            location: issue.location
        }));
    } catch (error) {
        console.error('Analysis failed:', error);
    }
}

// 2. Slither静态分析
const { slither } = require('slither-analyzer');

async function runSlitherAnalysis(contractPath) {
    try {
        const results = await slither.analyze(contractPath);
        return results.detectors.map(detector => ({
            check: detector.check,
            impact: detector.impact,
            confidence: detector.confidence,
            description: detector.description
        }));
    } catch (error) {
        console.error('Slither analysis failed:', error);
    }
}

// 3. 自动化测试套件
contract("SecurityTests", function(accounts) {
    let contract;

    beforeEach(async () => {
        contract = await SecureContract.new();
    });

    it("should prevent reentrancy attacks", async () => {
        const attacker = await MockAttacker.new();
        await expectRevert(
            attacker.attack(contract.address),
            "ReentrancyGuard: reentrant call"
        );
    });

    it("should handle integer overflow safely", async () => {
        const maxUint256 = new BN(2).pow(new BN(256)).sub(new BN(1));
        await expectRevert(
            contract.add(maxUint256, 1),
            "SafeMath: addition overflow"
        );
    });
});

前端安全

1. 钱包连接安全

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 1. 安全的钱包连接
async function connectWallet() {
    try {
        // 检查环境
        if (!window.ethereum) {
            throw new Error("Please install MetaMask");
        }

        // 请求账户访问
        const accounts = await window.ethereum.request({
            method: 'eth_requestAccounts'
        });

        // 验证返回的账户
        if (!accounts || accounts.length === 0) {
            throw new Error("No accounts found");
        }

        // 验证网络
        const chainId = await window.ethereum.request({
            method: 'eth_chainId'
        });

        if (chainId !== expectedChainId) {
            await switchNetwork(expectedChainId);
        }

        return accounts[0];
    } catch (error) {
        console.error('Wallet connection failed:', error);
        throw error;
    }
}

// 2. 网络切换处理
async function switchNetwork(targetChainId) {
    try {
        await window.ethereum.request({
            method: 'wallet_switchEthereumChain',
            params: [{ chainId: targetChainId }]
        });
    } catch (error) {
        if (error.code === 4902) {
            await addNetwork(targetChainId);
        } else {
            throw error;
        }
    }
}

// 3. 交易签名验证
async function verifySignature(message, signature, address) {
    try {
        const recoveredAddress = ethers.utils.verifyMessage(message, signature);
        return recoveredAddress.toLowerCase() === address.toLowerCase();
    } catch (error) {
        console.error('Signature verification failed:', error);
        return false;
    }
}

2. 数据安全

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 1. 安全的本地存储
const secureStorage = {
    // 加密存储
    setItem: async (key, value) => {
        const encrypted = await encryptData(value);
        localStorage.setItem(key, encrypted);
    },

    // 解密读取
    getItem: async (key) => {
        const encrypted = localStorage.getItem(key);
        if (!encrypted) return null;
        return await decryptData(encrypted);
    },

    // 安全删除
    removeItem: (key) => {
        localStorage.removeItem(key);
    }
};

// 2. API请求安全
async function secureApiRequest(url, method, data) {
    try {
        const response = await fetch(url, {
            method,
            headers: {
                'Content-Type': 'application/json',
                'X-API-Key': process.env.API_KEY
            },
            body: data ? JSON.stringify(data) : undefined,
            credentials: 'same-origin'
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        const result = await response.json();
        return result;
    } catch (error) {
        console.error('API request failed:', error);
        throw error;
    }
}

// 3. 输入验证
function validateInput(input) {
    // 地址验证
    if (input.type === 'address') {
        return ethers.utils.isAddress(input.value);
    }

    // 金额验证
    if (input.type === 'amount') {
        const amount = new BigNumber(input.value);
        return amount.isGreaterThan(0) && amount.isFinite();
    }

    // 交易数据验证
    if (input.type === 'txData') {
        return /^0x[0-9a-fA-F]*$/.test(input.value);
    }

    return false;
}

3. 错误处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 1. 全局错误处理
window.addEventListener('unhandledrejection', function(event) {
    console.error('Unhandled promise rejection:', event.reason);
    // 处理未捕获的Promise错误
});

// 2. 交易错误处理
async function handleTransaction(tx) {
    try {
        const receipt = await tx.wait();
        return receipt;
    } catch (error) {
        if (error.code === 'INSUFFICIENT_FUNDS') {
            throw new Error('余额不足');
        } else if (error.code === 'UNPREDICTABLE_GAS_LIMIT') {
            throw new Error('Gas估算失败');
        } else if (error.code === 4001) {
            throw new Error('用户拒绝交易');
        } else {
            console.error('Transaction failed:', error);
            throw new Error('交易失败');
        }
    }
}

// 3. 错误恢复机制
class ErrorRecovery {
    constructor() {
        this.retryCount = 0;
        this.maxRetries = 3;
    }

    async execute(operation) {
        try {
            return await operation();
        } catch (error) {
            if (this.retryCount < this.maxRetries) {
                this.retryCount++;
                console.log(`Retrying... (${this.retryCount}/${this.maxRetries})`);
                return await this.execute(operation);
            } else {
                throw error;
            }
        }
    }
}

钱包安全

1. 私钥管理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// 1. 安全的私钥存储
class SecureKeyStorage {
    constructor() {
        this.encryptionKey = null;
    }

    // 初始化加密密钥
    async initialize(password) {
        const salt = crypto.getRandomValues(new Uint8Array(16));
        const key = await crypto.subtle.deriveKey(
            {
                name: 'PBKDF2',
                salt: salt,
                iterations: 100000,
                hash: 'SHA-256'
            },
            password,
            { name: 'AES-GCM', length: 256 },
            false,
            ['encrypt', 'decrypt']
        );
        this.encryptionKey = key;
    }

    // 加密私钥
    async encryptPrivateKey(privateKey) {
        const iv = crypto.getRandomValues(new Uint8Array(12));
        const encrypted = await crypto.subtle.encrypt(
            {
                name: 'AES-GCM',
                iv: iv
            },
            this.encryptionKey,
            new TextEncoder().encode(privateKey)
        );
        return {
            encrypted: Array.from(new Uint8Array(encrypted)),
            iv: Array.from(iv)
        };
    }

    // 解密私钥
    async decryptPrivateKey(encryptedData) {
        const decrypted = await crypto.subtle.decrypt(
            {
                name: 'AES-GCM',
                iv: new Uint8Array(encryptedData.iv)
            },
            this.encryptionKey,
            new Uint8Array(encryptedData.encrypted)
        );
        return new TextDecoder().decode(decrypted);
    }
}

// 2. 助记词管理
class MnemonicManager {
    // 生成助记词
    static generateMnemonic() {
        return ethers.Wallet.createRandom().mnemonic.phrase;
    }

    // 验证助记词
    static validateMnemonic(mnemonic) {
        try {
            return ethers.utils.isValidMnemonic(mnemonic);
        } catch (error) {
            return false;
        }
    }

    // 从助记词恢复钱包
    static async recoverWallet(mnemonic) {
        if (!this.validateMnemonic(mnemonic)) {
            throw new Error('Invalid mnemonic');
        }
        return ethers.Wallet.fromMnemonic(mnemonic);
    }
}

2. 交易安全

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// 1. 交易签名验证
async function verifyTransaction(tx) {
    // 验证基本参数
    if (!tx.to || !ethers.utils.isAddress(tx.to)) {
        throw new Error('Invalid recipient address');
    }

    if (tx.value && tx.value.lte(0)) {
        throw new Error('Invalid transaction value');
    }

    // 验证Gas设置
    const gasPrice = await provider.getGasPrice();
    if (tx.gasPrice && tx.gasPrice.lt(gasPrice)) {
        throw new Error('Gas price too low');
    }

    // 验证nonce
    const nonce = await provider.getTransactionCount(tx.from);
    if (tx.nonce !== nonce) {
        throw new Error('Invalid nonce');
    }

    return true;
}

// 2. 交易确认监控
class TransactionMonitor {
    constructor(provider) {
        this.provider = provider;
    }

    async waitForConfirmations(txHash, confirmations = 1) {
        const tx = await this.provider.getTransaction(txHash);
        const receipt = await tx.wait(confirmations);

        return {
            status: receipt.status,
            confirmations: receipt.confirmations,
            blockNumber: receipt.blockNumber
        };
    }

    async checkRevert(receipt) {
        if (receipt.status === 0) {
            const tx = await this.provider.getTransaction(receipt.transactionHash);
            try {
                await this.provider.call(tx, receipt.blockNumber);
            } catch (error) {
                return error.reason || 'Transaction reverted';
            }
        }
        return null;
    }
}

// 3. 交易限额控制
class TransactionLimiter {
    constructor() {
        this.dailyLimit = ethers.utils.parseEther('1.0');
        this.transactions = new Map();
    }

    async checkLimit(address, value) {
        const today = new Date().toDateString();
        const dailyTxs = this.transactions.get(today) || new Map();
        const userTotal = dailyTxs.get(address) || ethers.constants.Zero;

        if (userTotal.add(value).gt(this.dailyLimit)) {
            throw new Error('Daily limit exceeded');
        }

        dailyTxs.set(address, userTotal.add(value));
        this.transactions.set(today, dailyTxs);

        return true;
    }
}

3. 安全最佳实践

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// 1. 钱包状态检查
class WalletHealthCheck {
    constructor(wallet) {
        this.wallet = wallet;
    }

    async checkHealth() {
        const checks = {
            balance: await this.checkBalance(),
            network: await this.checkNetwork(),
            permissions: await this.checkPermissions(),
            pendingTxs: await this.checkPendingTransactions()
        };

        return checks;
    }

    async checkBalance() {
        const balance = await this.wallet.getBalance();
        return {
            status: balance.gt(0),
            value: ethers.utils.formatEther(balance)
        };
    }

    async checkNetwork() {
        const network = await this.wallet.provider.getNetwork();
        return {
            status: network.chainId === expectedChainId,
            name: network.name
        };
    }

    async checkPermissions() {
        // 检查钱包权限
        const permissions = await ethereum.request({
            method: 'wallet_getPermissions'
        });

        return {
            status: permissions.length > 0,
            permissions: permissions
        };
    }

    async checkPendingTransactions() {
        const nonce = await this.wallet.getTransactionCount();
        const pendingNonce = await this.wallet.getTransactionCount('pending');

        return {
            status: nonce === pendingNonce,
            pending: pendingNonce - nonce
        };
    }
}

// 2. 定期备份提醒
class BackupReminder {
    constructor() {
        this.lastBackup = localStorage.getItem('lastBackup');
        this.backupInterval = 30 * 24 * 60 * 60 * 1000; // 30天
    }

    checkBackupNeeded() {
        if (!this.lastBackup) return true;

        const lastBackupDate = new Date(this.lastBackup);
        const now = new Date();

        return now - lastBackupDate > this.backupInterval;
    }

    async performBackup() {
        // 执行备份操作
        // ...

        // 更新备份时间
        this.lastBackup = new Date().toISOString();
        localStorage.setItem('lastBackup', this.lastBackup);
    }
}

// 3. 安全日志记录
class SecurityLogger {
    constructor() {
        this.logs = [];
    }

    log(event) {
        const logEntry = {
            timestamp: new Date().toISOString(),
            event: event,
            metadata: {
                userAgent: navigator.userAgent,
                network: window.ethereum?.networkVersion,
                account: window.ethereum?.selectedAddress
            }
        };

        this.logs.push(logEntry);
        this.persistLogs();
    }

    persistLogs() {
        // 安全地存储日志
        const encryptedLogs = encryptData(JSON.stringify(this.logs));
        localStorage.setItem('securityLogs', encryptedLogs);
    }

    async getLogs() {
        const encryptedLogs = localStorage.getItem('securityLogs');
        if (!encryptedLogs) return [];

        const decryptedLogs = await decryptData(encryptedLogs);
        return JSON.parse(decryptedLogs);
    }
}

练习题

  1. 实现一个防重入锁:
1
2
3
4
// 练习:实现重入锁
contract ReentrancyLock {
    // 你的代码
}
  1. 创建安全的钱包连接函数:
1
2
3
4
// 练习:实现安全的钱包连接
async function secureWalletConnect() {
    // 你的代码
}
  1. 实现交易监控系统:
1
2
3
4
// 练习:实现交易监控
class TransactionMonitor {
    // 你的代码
}
  1. 创建私钥加密存储:
1
2
3
4
// 练习:实现私钥加密存储
class PrivateKeyStorage {
    // 你的代码
}
  1. 实现安全日志系统:
1
2
3
4
// 练习:实现安全日志
class SecurityAuditLog {
    // 你的代码
}

参考资源

  1. 以太坊智能合约安全最佳实践
  2. OpenZeppelin安全文档
  3. Web3安全指南
  4. DeFi安全最佳实践
  5. 智能合约审计清单