news 2026/9/21 15:21:06

web3.js web3-eth-accounts 使用指南:Ethereum 账户管理与交易签名

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
web3.js web3-eth-accounts 使用指南:Ethereum 账户管理与交易签名

web3.js web3-eth-accounts 使用指南:Ethereum 账户管理与交易签名

【免费下载链接】web3.jsCollection of comprehensive TypeScript libraries for Interaction with the Ethereum JSON RPC API and utility functions.项目地址: https://gitcode.com/gh_mirrors/we/web3.js

web3-eth-accounts是 web3.js 的官方子包,专门负责 Ethereum 账户的生成、管理与数据/交易签名。本文基于当前仓库中该包的 README 展开,并结合其源码(account.tswallet.tstx/目录等)深入讲解账户创建、消息签名、三类 Typed Transaction 签名、V3 Keystore 加解密与内存钱包的完整用法,读者学完后可以直接在 Node.js 或浏览器应用中落地账户管理功能。

包定位与功能总览

web3-eth-accounts是 web3.js 的模块化子包之一,其职责定义在源码注释中:

"The web3.eth.accounts contains functions to generate Ethereum accounts and sign transactions and data."

即:生成 Ethereum 账户签名交易签名任意数据。它同时提供:

  • 账户生成:createprivateKeyToAccount
  • 地址/公钥推导:privateKeyToAddressprivateKeyToPublicKey
  • 消息签名与验签:hashMessagesignsignRawrecoverrecoverTransaction
  • 交易签名:signTransaction(支持 legacy、EIP-2930、EIP-1559)
  • Keystore:encryptdecrypt(V3 JSON Keystore,scrypt / pbkdf2)
  • 钱包:Wallet内存钱包(create/add/get/remove/clear/encrypt/decrypt/save/load)

从源码结构看,包的公共导出入口在 packages/web3-eth-accounts/src/index.ts,它统一导出walletaccounttypesschemas以及common(链参数、EIP、硬分叉定义)和tx(交易类型实现)两大内部模块。tx/目录中的 tx/index.ts 明确标注其交易实现源自@ethereumjs/txv4.1.1 的思路,提供了Transaction(legacy)、AccessListEIP2930TransactionFeeMarketEIP1559Transaction以及TransactionFactory工厂。

安全提示(源自源码官方注释):该包尚未经过审计(NOT been audited),在生产环境使用前必须妥善清理内存、安全保管私钥,并充分测试交易收发功能。请务必阅读本文最后一节的注意事项。

安装与前置条件

安装

推荐使用 NPM 或 Yarn 安装:

# NPM npm install web3-eth-accounts # Yarn yarn add web3-eth-accounts

环境要求

依据包的 package.json:

项目要求
Node.js>=14
npm>=6.12.0
ECMAScriptES2020(tsconfig目标)
包管理器(开发)Yarn / Lerna

包同时提供lib/commonjs(CJS)与lib/esm(ESM)双格式产物,exports字段中require指向 CJS、import指向 ESM,可无缝用于 Node 与打包工具。

依赖关系

该包依赖以下运行时库(见 package.json):

  • ethereum-cryptography:提供 secp256k1 曲线、AES、scrypt/pbkdf2 等密码学原语
  • @ethereumjs/rlp:RLP 编码
  • crc-32:校验计算
  • web3-errors/web3-types/web3-utils/web3-validator:错误类型、TS 类型、工具函数与 JSON Schema 校验

两种使用方式

方式一:通过web3主包访问(推荐)

安装web3主包后,账户功能挂载在web3.eth.accounts下:

import Web3 from 'web3'; const web3 = new Web3(Web3.givenProvider || 'ws://some.local-or-remote.node:8546'); const account = web3.eth.accounts.create(); const result = web3.eth.accounts.hashMessage('Test Message');

这种模式下web3.eth.accounts.signTransaction是有状态的:web3主包在 packages/web3/src/accounts.ts 中通过initAccountsForContext(context)将账户模块与Web3Context绑定,签名前会先调用web3-ethprepareTransactionForSigning(transaction, context)自动补齐noncechainId等网络相关信息。该上下文绑定逻辑在 packages/web3/src/web3.ts 中通过initAccountsForContext(this)注入。

方式二:独立使用子包(轻量应用)

只安装web3-eth-accounts,按需导入函数,适合对包体积敏感的应用:

import { create, hashMessage, signTransaction, Transaction } from 'web3-eth-accounts'; const account = create(); const result = hashMessage('Test Message');

⚠️重要区别:独立导入时signTransaction是无状态的。源码 account.ts 明确说明:由于没有网络访问能力去获取账户noncechainId,函数依赖调用方传入完整的交易对象;如需签名不完整的交易对象,应使用web3.eth.accounts.sign。同样,privateKeyToAccount返回的signTransaction会直接抛出TransactionSigningError('Do not have network access to sign the transaction')(见 account.ts)。

账户的创建与导入

create:生成全新账户

const account = web3.eth.accounts.create(); // { // address: '0xbD504f977021b5E5DdccD8741A368b147B3B38bB', // privateKey: '0x964ced1c69ad27a311c432fdc0d8211e987595f7eb34ab405a5f16bdc9563ec5', // signTransaction: [Function], // sign: [Function], // encrypt: [AsyncFunction] // }

源码 account.ts 显示create使用secp256k1.utils.randomPrivateKey()(来自经审计的ethereum-cryptography包,基于密码学安全随机数)生成 32 字节私钥,再交给privateKeyToAccount组装账户对象。

privateKeyToAccount:从私钥导入账户

const account = web3.eth.accounts.privateKeyToAccount( '0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709', ); // 返回 { address: '0xb8CE9ab6943e0eCED004cDe8e3bBed6568B2Fa01', privateKey: '0x348c...', sign, signTransaction, encrypt }

privateKeyToAddress:私钥推导地址

web3.eth.accounts.privateKeyToAddress( '0xbe6383dad004f233317e46ddb46ad31b16064d14447a95cc1d8c8d4bc61c3728', ); // > '0xEB014f8c8B418Db6b45774c326A0E64C78914dC0'

privateKeyToPublicKey:私钥推导公钥

web3.eth.accounts.privateKeyToPublicKey( '0x1e046a882bb38236b646c9f135cf90ad90a140810f439875f2a6dd8e50fa261f', true, // isCompressed: true 返回 33 字节压缩公钥,false 返回 65 字节非压缩公钥 );

底层原理:私钥校验与地址推导

上述函数都经过parseAndValidatePrivateKey(account.ts)做统一校验:

  • 输入为stringUint8Array
  • 十六进制字符串长度必须为 66(0x+ 64 位),否则抛PrivateKeyLengthError
  • 字节长度必须为 32 字节,否则抛PrivateKeyLengthError
  • 转换失败抛InvalidPrivateKeyError

地址推导过程(见privateKeyToAddress,account.ts):

  1. 由私钥经 secp256k1 得到非压缩公钥(前缀0x04);
  2. 去掉前缀字节后对剩余部分做 Keccak-256 哈希(sha3Raw,注意这里是 Ethereum 的 Keccak 而非 NIST SHA3);
  3. 取哈希后 20 字节(后 40 个十六进制字符)即为地址;
  4. toChecksumAddress生成 EIP-55 校验和格式地址。

消息签名与验签

hashMessage:生成 Ethereum 签名消息哈希

web3.eth.accounts.hashMessage('Hello world'); // > '0x8144a6fa26be252b86456491fbcd43c1de7e022241845ffea1c3df066f7cfede' // 传入 UTF8 Hex 编码的消息也会被解码后再哈希,结果一致: web3.eth.accounts.hashMessage(web3.utils.utf8ToHex('Hello world')); // > '0x8144a6fa26be252b86456491fbcd43c1de7e022241845ffea1c3df066f7cfede' // skipPrefix=true 时不添加 Ethereum 前缀: web3.eth.accounts.hashMessage('Hello world', true); // > '0xed6c11b0b5b808960df26f5bfc471d04c1995b0ffd2055925ad1be28d6baadfd'

源码 account.ts 揭示其实现:消息按"\x19Ethereum Signed Message:\n" + message.length + message包装后,用 Keccak-256(sha3Raw)哈希。skipPrefix参数(默认false)控制是否跳过该前缀。

sign:签名任意数据(带 Ethereum 前缀)

web3.eth.accounts.sign( 'Some data', '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318', ); // > { // message: 'Some data', // messageHash: '0x1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655', // v: '0x1c', // r: '0xb91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd', // s: '0x6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a029', // signature: '0xb91467...f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c' // }

signRaw:签名原始数据(无前缀)

signRaw使用hashMessage(data, true)跳过 Ethereum 前缀,直接对原始数据哈希签名(account.ts),适合需要与其他链或自定义协议兼容的场景。

signMessageWithPrivateKey:底层签名组件

signsignRaw最终都调用signMessageWithPrivateKey(hash, privateKey)(account.ts),它使用 secp256k1 对消息哈希签名,返回{ messageHash, v, r, s, signature }。其中v = recovery + 27signaturer || s || v的拼接。

recover:从签名恢复地址

const data = 'Some data'; const sigObj = web3.eth.accounts.sign( data, '0xbe6383dad004f233317e46ddb46ad31b16064d14447a95cc1d8c8d4bc61c3728', ); // 方式一:传入签名对象 web3.eth.accounts.recover(sigObj); // 方式二:传入 v / r / s web3.eth.accounts.recover(data, sigObj.v, sigObj.r, sigObj.s); // > '0xEB014f8c8B418Db6b45774c326A0E64C78914dC0'

recover支持三种入参形态(签名对象、完整签名串、(data, v, r, s)分量形式),并从签名中解析v(大于 26 时减去 27)恢复公钥再推导地址(account.ts)。

recoverTransaction:从 RLP 交易恢复签名地址

web3.eth.accounts.recoverTransaction( '0xf869808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a0c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895a0727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68', ); // > '0x2c7536E3605D9C16a7a3D7b1898e529396a65c23'

实现上先通过TransactionFactory.fromSerializedData解码 RLP 数据得到交易对象,再取getSenderAddress()(account.ts)。

交易签名:支持三种交易类型

signTransaction接受一个TypedTransaction(即Transaction/AccessListEIP2930Transaction/FeeMarketEIP1559Transaction三者之一,见 types.ts),签名后返回{ messageHash, v, r, s, rawTransaction, transactionHash }

签名 Legacy 交易

import { signTransaction, Transaction } from 'web3-eth-accounts'; signTransaction( new Transaction({ to: '0x118C2E5F57FD62C2B5b46a5ae9216F4FF4011a07', value: '0x186A0', gasLimit: '0x520812', gasPrice: '0x09184e72a000', data: '', chainId: 1, nonce: 0, }), '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318', );

签名 EIP-1559 交易

signTransaction( new Transaction({ to: '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55', maxPriorityFeePerGas: '0x3B9ACA00', maxFeePerGas: '0xB2D05E00', gasLimit: '0x6A4012', value: '0x186A0', data: '', chainId: 1, nonce: 0, }), '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318', );

签名 EIP-2930 交易(带 Access List)

signTransaction( new Transaction({ chainId: 1, nonce: 0, gasPrice: '0x09184e72a000', gasLimit: '0x2710321', to: '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55', value: '0x186A0', data: '', accessList: [ { address: '0x0000000000000000000000000000000000000101', storageKeys: [ '0x0000000000000000000000000000000000000000000000000000000000000000', '0x00000000000000000000000000000000000000000000000000000000000060a7', ], }, ], }), '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318', );

签名返回结构示例

{ "messageHash": "0x28b7b75f7ba48d588a902c1ff4d5d13cc0ca9ac0aaa39562368146923fb853bf", "v": "0x25", "r": "0x601b0017b0e20dd0eeda4b895fbc1a9e8968990953482214f880bae593e71b5", "s": "0x690d984493560552e3ebdcc19a65b9c301ea9ddc82d3ab8cfde60485fd5722ce", "rawTransaction": "0xf869808609184e72a0008352081294118c2e5f57fd62c2b5b46a5ae9216f4ff4011a07830186a08025a00601b0017b0e20dd0eeda4b895fbc1a9e8968990953482214f880bae593e71b5a0690d984493560552e3ebdcc19a65b9c301ea9ddc82d3ab8cfde60485fd5722ce", "transactionHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" }

签名实现细节

signTransaction(account.ts)的核心流程:

  1. 调用transaction.sign(hexToBytes(privateKey))生成签名;
  2. 校验v/r/s是否存在(否则抛TransactionSigningError);
  3. 调用signedTx.validate(true)做严格校验,任何错误都会汇总抛出;
  4. rawTransactionsignedTx.serialize()的十六进制;
  5. transactionHash为 raw 交易数据的 Keccak-256 哈希。

TransactionFactory(transactionFactory.ts)负责按类型分发:type字段为 0 时创建 legacy 交易、1 时创建 EIP-2930、2 时创建 EIP-1559,无type字段时默认 legacy;还提供fromSerializedData(按首字节判断类型)与fromBlockBodyData(区分 Uint8Array 与数组),并可通过registerTransactionType注册自定义交易类型。此外src/common/目录内置了 mainnet/goerli/sepolia 等链参数、20+ EIP 定义与全部硬分叉配置(如 common/chains/mainnet.ts、common/eips、common/hardforks),保证交易签名时的参数正确性。

V3 Keystore:加密与解密

encrypt将私钥加密为 Ethereum V3 JSON Keystore(Web3 Secret Storage),decrypt反向还原账户。

使用 scrypt 加密

web3.eth.accounts .encrypt( '0x67f476289210e3bef3c1c75e4de993ff0a00663df00def84e73aa7411eac18a6', '123', { n: 8192, iv: web3.utils.hexToBytes('0xbfb43120ae00e9de110f8325143a2709'), salt: web3.utils.hexToBytes('0x210d0ec956787d865358ac45716e6dd42e68d48e346d795746509523aeb477dd'), }, ) .then(console.log);

输出示例:

{ "version": 3, "id": "c0cb0a94-4702-4492-b6e6-eb2ac404344a", "address": "cda9a91875fc35c8ac1320e098e584495d66e47c", "crypto": { "ciphertext": "cb3e13e3281ff3861a3f0257fad4c9a51b0eb046f9c7821825c46b210f040b8f", "cipherparams": { "iv": "bfb43120ae00e9de110f8325143a2709" }, "cipher": "aes-128-ctr", "kdf": "scrypt", "kdfparams": { "n": 8192, "r": 8, "p": 1, "dklen": 32, "salt": "210d0ec956787d865358ac45716e6dd42e68d48e346d795746509523aeb477dd" }, "mac": "efbf6d3409f37c0084a79d5fdf9a6f5d97d11447517ef1ea8374f51e581b7efd" } }

使用 pbkdf2 加密

web3.eth.accounts .encrypt('0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709', '123', { iv: 'bfb43120ae00e9de110f8325143a2709', salt: '210d0ec956787d865358ac45716e6dd42e68d48e346d795746509523aeb477dd', c: 262144, kdf: 'pbkdf2', }) .then(console.log);

输出中kdfparams{ dklen: 32, salt, c: 262144, prf: 'hmac-sha256' }

解密 Keystore 还原账户

web3.eth.accounts .decrypt( { version: 3, id: 'c0cb0a94-4702-4492-b6e6-eb2ac404344a', address: 'cda9a91875fc35c8ac1320e098e584495d66e47c', crypto: { ciphertext: 'cb3e13e3281ff3861a3f0257fad4c9a51b0eb046f9c7821825c46b210f040b8f', cipherparams: { iv: 'bfb43120ae00e9de110f8325143a2709' }, cipher: 'aes-128-ctr', kdf: 'scrypt', kdfparams: { n: 8192, r: 8, p: 1, dklen: 32, salt: '210d0ec956787d865358ac45716e6dd42e68d48e346d795746509523aeb477dd', }, mac: 'efbf6d3409f37c0084a79d5fdf9a6f5d97d11447517ef1ea8374f51e581b7efd', }, }, '123', ) .then(console.log); // > { address: '0xcdA9A91875fc35c8Ac1320E098e584495d66e47c', privateKey: '67f4...', sign, signTransaction, encrypt }

参数说明与底层逻辑

encrypt的可选参数(CipherOptions,见 account.ts):

参数默认值说明
kdf'scrypt'密钥派生函数,可选'scrypt''pbkdf2',其他值抛InvalidKdfError
n8192scrypt 的 CPU/内存成本参数
r8scrypt 块大小参数
p1scrypt 并行化参数
c262144pbkdf2 迭代次数,小于 1000 抛PBKDF2IterationsError
dklen32派生密钥长度
iv随机 16 字节初始化向量,长度必须为 16 字节,否则抛IVLengthError
salt随机 32 字节盐值

加密链路:私钥 → 派生密钥(scrypt/pbkdf2)→ 取派生密钥前 16 字节作为 AES-128-CTR 密钥加密私钥得到ciphertextmac = Keccak(derivedKey[16:32] || ciphertext)decrypt则校验 JSON Schema(schemas.ts 中的keyStoreSchema,要求包含crypto/id/version/address字段)、校验version === 3(否则抛KeyStoreVersionError)、按 KDF 派生密钥并比对mac(不一致抛KeyDerivationError),最后用 AES-CTR 解密得到私钥。

Wallet:内存钱包管理多账户

Wallet继承自Web3BaseWallet(wallet.ts),是内存中的多账户容器,其账户可直接被web3.eth.sendTransaction()或合约方法send()内部使用:

import { Web3 } from 'web3'; const web3 = new Web3('http://127.0.0.1:7545'); const wallet = await web3.eth.accounts.wallet.create(2); const signature = wallet.at(0).sign('Test Data'); // 使用钱包内账户 // 先给账户充值,再发送交易(内部用钱包账户签名): const receipt = await web3.eth.sendTransaction({ from: wallet.at(0).address, to: '0xdAC17F958D2ee523a2206206994597C13D831ec7', value: 1, // ... });

核心方法速查

方法说明
create(numberOfAccounts)批量生成账户并加入钱包(不会覆盖已有账户),返回钱包本身
add(account \| privateKey)用账户对象或私钥字符串添加账户;地址重复时打印警告并使用原索引
get(addressOrIndex)按地址(不区分大小写)或索引获取账户,不存在返回undefined
remove(addressOrIndex)移除账户,成功返回true,找不到返回false
clear()安全清空钱包所有账户(清空_addressMap并将数组长度置 0)
encrypt(password, options?)用密码加密钱包内所有账户,返回 Keystore V3 对象数组
decrypt(encryptedWallets, password)批量解密 Keystore 数组并逐个add回钱包
save(password, keyName?)仅浏览器:将加密后的钱包写入localStorage,默认 key 为'web3js_wallet'
load(password, keyName?)仅浏览器:从localStorage读取并解密钱包
// 添加账户 web3.eth.accounts.wallet.add('0xbce9b59981303e76c4878b1a6d7b088ec6b9dd5c966b7d5f54d7a749ff683387'); // 移除账户 web3.eth.accounts.wallet.remove('0x85D70633b90e03e0276B98880286D0D055685ed7'); // > true // 整体加密/解密 await web3.eth.accounts.wallet.create(1); await web3.eth.accounts.wallet.encrypt('abc').then(console.log); // 浏览器持久化 await web3.eth.accounts.wallet.save('test#!$'); // > true await web3.eth.accounts.wallet.load('test#!$');

save/load通过静态方法Wallet.getStorage()(wallet.ts)探测window.localStorage可用性,并识别QuotaExceededError(Firefox 为NS_ERROR_DOM_QUOTA_REACHED)等存储异常;无可用存储时抛出'Local storage not available.'。内部用_addressMap(地址小写 → 索引)维护 O(1) 的地址查找,_defaultKeyName默认存储键为'web3js_wallet'

开发与测试脚本

包内package.json提供完整的工程化脚本(见 package.json):

脚本说明
cleanrimraf清理dist/lib/
build并行构建 CJS、ESM 与类型声明(build:cjs/build:esm/build:types
lint/lint:fixeslint检查 / 自动修复
formatprettier格式化代码
test/test:unit运行单元测试(jest,配置于test/unit/jest.config.js
test:integration运行集成测试(test/integration/jest.config.js
test:ciCI 环境运行带覆盖率输出的测试
test:coverage:unit/test:coverage:integration输出单元 / 集成测试覆盖率

仓库中对应测试位于 packages/web3-eth-accounts/test/unit(含account.test.tswallet.test.ts以及common/tx/子目录的交易与链参数测试)和 packages/web3-eth-accounts/test/integration,fixtures 则提供 EIP-1559/EIP-2930 交易与各链配置的 JSON 样本,可供深入验证签名与解码行为。

安全与生产环境注意事项

结合源码官方注释(account.ts)与包特性,以下几点在生产环境中务必重视:

  1. 本包未经安全审计:私钥生成、签名、加解密等路径应在充分评估后使用;
  2. 私钥妥善保管:避免硬编码在源码中,Keystore 密码应使用强口令并独立保存;
  3. 内存清理:使用完毕后及时清空私钥相关的临时变量与Uint8Array
  4. 无状态签名的局限:独立使用web3-eth-accountssignTransaction不访问网络,需自行补齐nonce/chainId;在web3主包内使用则会通过 packages/web3/src/accounts.ts 自动补齐;
  5. 交易签名前充分测试:尤其是收款地址与金额,防止误签。

总结

web3-eth-accounts以模块化的方式提供了 Ethereum 账户体系的完整能力:从create/privateKeyToAccount的账户生成与导入,到sign/signRaw/recover的消息签名验签,再到覆盖 legacy、EIP-2930、EIP-1559 三类交易的signTransaction,以及 V3 Keystore 的encrypt/decrypt和可持久化的Wallet内存钱包。它既可独立安装用于轻量应用,也可通过web3.eth.accounts集成进主包以获得网络感知的交易签名能力。相关源码、测试与链参数定义均可在 packages/web3-eth-accounts 目录下继续深入研究。

【免费下载链接】web3.jsCollection of comprehensive TypeScript libraries for Interaction with the Ethereum JSON RPC API and utility functions.项目地址: https://gitcode.com/gh_mirrors/we/web3.js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/21 15:10:36

Ubuntu 20.04离线安装Realtek b852无线网卡驱动全攻略

1. 一块网卡引发的折腾:为什么离线装驱动比想象中麻烦Realtek b852 这块无线网卡,最近两年在不少轻薄本和迷你主机上出现得挺频繁。它本身是 RTL8852BE 系列的衍生型号,支持 Wi-Fi 6 和蓝牙 5.2,纸面参数不差。但问题在于&#xf…

作者头像 李华
网站建设 2026/9/21 14:48:38

xmake单元测试实践:提升C/C++开发效率

1. 为什么选择xmake进行单元测试在C/C项目开发中,单元测试一直是个令人头疼的问题。传统做法要么依赖第三方框架(如Google Test),要么需要手动编写大量胶水代码。而xmake作为国产构建工具的后起之秀,其内置的测试框架让…

作者头像 李华