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);
}
}
|
练习题
- 实现一个防重入锁:
| // 练习:实现重入锁
contract ReentrancyLock {
// 你的代码
}
|
- 创建安全的钱包连接函数:
| // 练习:实现安全的钱包连接
async function secureWalletConnect() {
// 你的代码
}
|
- 实现交易监控系统:
| // 练习:实现交易监控
class TransactionMonitor {
// 你的代码
}
|
- 创建私钥加密存储:
| // 练习:实现私钥加密存储
class PrivateKeyStorage {
// 你的代码
}
|
- 实现安全日志系统:
| // 练习:实现安全日志
class SecurityAuditLog {
// 你的代码
}
|
参考资源
- 以太坊智能合约安全最佳实践
- OpenZeppelin安全文档
- Web3安全指南
- DeFi安全最佳实践
- 智能合约审计清单