简介:这是一份面向Java开发者与区块链技术研究者的以太坊地址生成与余额查询工程,基于web3j直连自建或免费以太坊节点,围绕助记词遍历、地址派生与余额记录展开。其核心在于依据助记词生成规则做部分反推判断,将原本约4.8亿种单词组合压缩至约0.3亿种,缩减约16倍,再逐组生成地址并与节点交互查询余额、落库记录,相当于一套可直接运行的硬破解流程。压缩包共53个文件,约19.79MB,以29个jar依赖为主,涵盖web3j、bitcoinj、spongycastle等加密与链交互库,另有7个java源码、7个class及properties、cofig、txt等配置与词表文件,便于二次编译调试。目前已有4184人学习下载,适合想研究助记词派生逻辑、节点交互与批量查询实现的中高级开发者参考,可从中获取依赖组织方式、遍历优化思路与查询记录模块的完整工程结构。
1. 从助记词到地址:java web3j 直连以太坊节点查余额到底在做什么
很多做 Java 后端的同学第一次接触链上数据,场景往往很朴素:手里有一串 12 或 24 个单词的助记词,想知道它对应的以太坊地址里到底有没有余额,能不能用 Java 直接查,而不是打开某个网页钱包手动粘贴。这个需求听起来简单,背后其实串起了四件事:助记词到私钥的派生、私钥到地址的推导、通过 web3j 直连以太坊节点、以及用 JSON-RPC 查余额。标题里说的「硬破解」,在工程语境下不是去暴力穷举别人的助记词——那既不现实也不合法——而是指把这条链路用代码硬编码跑通,自己掌控派生路径和节点连接,不依赖任何第三方钱包界面。
web3j 是 Java 生态里最成熟的以太坊交互库,它把 JSON-RPC 调用、ABI 编解码、交易签名都封装成了 Java 对象。你要做的,是用它把「助记词 → 地址 → 余额」这条线在本地跑起来。适合谁?适合需要做钱包归集、地址监控、充值回调校验的 Java 工程师,也适合想搞懂 HD 钱包派生逻辑、不想被「八股文」式面试题糊弄的开发者。下面从依赖、派生、连接、查询一路拆到踩坑。
2. 助记词派生地址:BIP39 与 BIP44 在 web3j 里怎么落地
2.1 先搞清楚助记词不是私钥,中间隔着两层标准
助记词本身只是一串人类可读的单词,它要变成能签名的私钥,需要经过 BIP39 的种子派生和 BIP44 的路径派生。BIP39 负责把助记词加盐(可选 passphrase)用 PBKDF2-HMAC-SHA512 迭代 2048 次,生成一个 64 字节的种子。BIP44 则定义了一条派生路径,以太坊常用的是m/44'/60'/0'/0/0,其中 44 是 BIP44 的标识,60 是以太坊的 coin type,后面三个数字分别代表账户、外部链、地址索引。
这里有个容易翻车的点:同一个助记词,派生路径不同,得到的地址完全不同。很多钱包默认用m/44'/60'/0'/0/0,但有些老钱包用m/44'/60'/0'/0,还有用m/44'/60'/0'的。你查余额查出来是 0,第一反应不该是「这助记词没钱」,而是先确认派生路径对不对。web3j 的Bip39Wallet和WalletUtils默认走的是标准路径,但如果你要兼容多钱包,就得自己控制路径。
2.2 用 web3j 从助记词生成地址的最小可运行代码
先看依赖。Maven 项目里引入 web3j 的核心包,版本用 4.9.x 以上比较稳,低版本在 BIP39 的 NFKD 规范化上有过 bug。
<dependency> <groupId>org.web3j</groupId> <artifactId>core</artifactId> <version>4.9.8</version> </dependency>然后是生成地址的代码。注意这里我故意把派生路径显式写出来,方便你改。
import org.web3j.crypto.*; import org.web3j.crypto.MnemonicUtils; import java.nio.charset.StandardCharsets; public class MnemonicToAddress { public static void main(String[] args) throws Exception { // 12 个单词的助记词,实际使用中从安全存储读取,不要硬编码在源码里 String mnemonic = "test test test test test test test test test test test junk"; // 可选 passphrase,大多数钱包为空字符串 String passphrase = ""; // 1. 助记词 -> 种子(BIP39) byte[] seed = MnemonicUtils.generateSeed(mnemonic, passphrase); // 2. 种子 -> 主私钥(BIP32) Bip32ECKeyPair masterKeypair = Bip32ECKeyPair.generateKeyPair(seed); // 3. 按 BIP44 路径派生:m/44'/60'/0'/0/0 // 注意 44、60、0 这些带撇号的要加 HARDENED 标志 int[] path = { 44 | Bip32ECKeyPair.HARDENED_BIT, 60 | Bip32ECKeyPair.HARDENED_BIT, 0 | Bip32ECKeyPair.HARDENED_BIT, 0, // 外部链,不硬化 0 // 地址索引,不硬化 }; Bip32ECKeyPair childKeypair = Bip32ECKeyPair.deriveKeyPair(masterKeypair, path); // 4. 从派生出的私钥拿到地址 Credentials credentials = Credentials.create(childKeypair); String address = credentials.getAddress(); System.out.println("派生地址: " + address); System.out.println("私钥(十六进制): " + childKeypair.getPrivateKey().toString(16)); } }逻辑说明:MnemonicUtils.generateSeed内部做的是 PBKDF2,盐是"mnemonic" + passphrase,迭代 2048 次,输出 512 位。Bip32ECKeyPair.generateKeyPair把种子转成 BIP32 主密钥对。deriveKeyPair按路径逐层派生,带HARDENED_BIT的层级用的是硬化派生,索引值要按位或上0x80000000。最后Credentials.create从私钥算出公钥,再取 Keccak-256 哈希的后 20 字节作为地址。
参数说明:mnemonic必须是合法的 BIP39 词表单词,数量和校验和都要对,否则generateSeed不报错但派生出的地址是错的——这是最阴的坑。passphrase如果钱包设过,必须一致,否则地址完全不同。path数组里前三个带硬化,后两个不带,这是 BIP44 的规定,写反了地址就错。
2.3 派生路径写错时怎么快速定位
如果你手头有一个已知地址和对应助记词,但代码派生出来的地址对不上,按这个顺序排查:先确认助记词单词拼写和顺序,再确认 passphrase 是否为空,然后逐个试常见路径。常见路径除了m/44'/60'/0'/0/0,还有m/44'/60'/0'/0(少一层索引)、m/44'/60'/0'(账户层直接取)、以及m/44'/60'/0'/0/1这种索引偏移。写个小循环把索引 0 到 9 都打出来,往往能对上。
提示:不要把真实助记词写进单元测试或日志。生产环境里助记词应该来自加密的配置中心或硬件模块,代码里只持有派生后的私钥或直接签名。
3. 直连以太坊节点:web3j 的三种连接方式和选型
3.1 HTTP、WebSocket、IPC 三种连接各适合什么场景
web3j 连节点有三种方式。HTTP 最简单,每次请求建一次连接,适合低频查询余额、发交易这种场景。WebSocket 是长连接,适合订阅新区块、监听事件日志,比如你要做充值监控,用 WebSocket 订阅newHeads比轮询省资源。IPC 是本地进程间通信,只有节点和 Java 程序在同一台机器上才能用,延迟最低,但部署耦合度高。
对「查余额」这个需求,HTTP 就够了。但如果你后面要做地址监控,建议直接上 WebSocket,省得以后重构。选型时还要考虑节点来源:自建全节点、第三方节点服务、还是轻节点。自建全节点同步慢、占磁盘,但数据自主;第三方节点服务接入快,但有速率限制和隐私顾虑。做余额查询这种只读操作,第三方节点通常够用,但要注意别把助记词或私钥发给节点——查余额只需要地址,不需要私钥。
3.2 用 HTTP 直连节点并查余额的完整代码
下面这段代码演示从连接节点到查余额的全过程。节点地址用占位符,你换成自己的节点 RPC 地址。
import org.web3j.protocol.Web3j; import org.web3j.protocol.http.HttpService; import org.web3j.protocol.core.DefaultBlockParameterName; import org.web3j.protocol.core.methods.response.EthGetBalance; import java.math.BigDecimal; import java.math.BigInteger; public class QueryBalance { public static void main(String[] args) throws Exception { // 节点 RPC 地址,替换成你自己的 String rpcUrl = "http://your-node-host:8545"; Web3j web3j = Web3j.build(new HttpService(rpcUrl)); // 要查询的地址 String address = "0x0000000000000000000000000000000000000000"; // 查最新区块的余额 EthGetBalance balanceWei = web3j.ethGetBalance( address, DefaultBlockParameterName.LATEST ).send(); BigInteger wei = balanceWei.getBalance(); // 1 ETH = 10^18 wei BigDecimal eth = new BigDecimal(wei).divide(new BigDecimal("1000000000000000000")); System.out.println("余额(wei): " + wei); System.out.println("余额(ETH): " + eth.toPlainString()); // 用完关闭,HTTP 连接池会释放 web3j.shutdown(); } }逻辑说明:Web3j.build(new HttpService(rpcUrl))创建一个 web3j 实例,底层用 OkHttp 发 JSON-RPC 请求。ethGetBalance对应 JSON-RPC 的eth_getBalance方法,第二个参数是区块参数,LATEST表示最新区块,也可以传具体区块号或PENDING。返回的EthGetBalance里getBalance()是BigInteger,单位是 wei。除以 10^18 得到 ETH,但要注意用BigDecimal而不是double,否则大数会丢精度。
参数说明:rpcUrl的端口常见是 8545(HTTP)和 8546(WebSocket)。DefaultBlockParameterName有LATEST、EARLIEST、PENDING三个枚举值,查历史余额要用DefaultBlockParameter.valueOf(BigInteger blockNumber)。web3j.shutdown()在长时间运行的服务里不是必须每次调用,但短生命周期脚本里调用能避免连接泄漏。
3.3 节点连不上或返回超时的排查顺序
连不上节点时,先确认 RPC 地址和端口对不对,用 curl 直接打一发 JSON-RPC 看返回。如果 curl 通但 Java 不通,多半是代理或防火墙问题。如果返回 401 或 403,说明节点需要认证,HTTP 头里要带 token。如果返回method not found,说明节点没开eth模块,或者你连的是个不完整的节点。超时的话,先看节点是否在同步中,同步中的节点查余额可能返回旧数据或超时。第三方节点服务通常有速率限制,短时间内大量请求会被限流,返回 429,这时候要加退避重试。
注意:查余额是只读操作,不需要私钥,也不消耗 gas。任何要你提供私钥才能「查余额」的服务都值得警惕。
4. 批量查余额与性能:从单地址到多地址的工程化
4.1 批量查询为什么要用异步和连接池
单地址查询用同步send()没问题,但如果你要查几百上千个地址,同步串行会慢到无法接受。web3j 提供了CompletableFuture的异步接口,配合 HTTP 连接池可以并发发请求。但并发数不是越大越好,节点和第三方服务都有速率限制,盲目开 100 个并发只会触发限流。我一般会把并发控制在 10 到 20,再加一个信号量控制。
另一个思路是用eth_getBalance的批量请求,JSON-RPC 支持一次发多个请求,web3j 里可以用Web3j的批量接口,但 web3j 对批量请求的封装不算特别顺手,实际项目里更多人还是用异步加限流。
4.2 异步批量查余额的代码骨架
import org.web3j.protocol.Web3j; import org.web3j.protocol.http.HttpService; import org.web3j.protocol.core.DefaultBlockParameterName; import java.util.List; import java.util.concurrent.*; import java.util.stream.Collectors; public class BatchBalance { public static void main(String[] args) throws Exception { Web3j web3j = Web3j.build(new HttpService("http://your-node-host:8545")); List<String> addresses = List.of( "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000001" ); // 控制并发数,避免触发节点限流 Semaphore semaphore = new Semaphore(10); ExecutorService pool = Executors.newFixedThreadPool(10); List<CompletableFuture<String>> futures = addresses.stream() .map(addr -> CompletableFuture.supplyAsync(() -> { try { semaphore.acquire(); try { var resp = web3j.ethGetBalance( addr, DefaultBlockParameterName.LATEST).send(); return addr + " -> " + resp.getBalance(); } finally { semaphore.release(); } } catch (Exception e) { return addr + " -> ERROR: " + e.getMessage(); } }, pool)) .collect(Collectors.toList()); // 等待全部完成 CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); futures.forEach(f -> System.out.println(f.getNow("timeout"))); pool.shutdown(); web3j.shutdown(); } }逻辑说明:用Semaphore限制同时进行的请求数,CompletableFuture.supplyAsync把每个查询丢到线程池。allOf().join()等所有任务结束。每个任务内部捕获异常,避免一个失败拖垮整批。getNow拿结果,超时给默认值。
参数说明:Semaphore(10)的 10 是并发上限,根据节点承受能力调。newFixedThreadPool(10)的线程数和信号量保持一致,避免线程空转。实际项目里线程池应该复用,不要每次查询都新建。
4.3 批量查询时余额对不上的常见原因
批量查余额时,如果发现某些地址余额和钱包显示不一致,先确认区块参数。LATEST在节点同步延迟时可能不是真正的链头,尤其是第三方节点,不同节点看到的「最新」可能差几个块。做对账类业务时,应该固定一个区块号查询,保证同一批数据在同一区块高度上。另外,如果地址是合约地址,eth_getBalance返回的是合约的 ETH 余额,不是代币余额,代币余额要用eth_call调合约的balanceOf方法,这是两码事。
5. 避坑与排查:助记词派生和节点查询的 5 个血泪教训
5.1 现象:派生地址和钱包对不上,余额查出来是 0
原因:派生路径不一致,或者助记词有隐藏字符。很多从网页复制的助记词末尾带空格或换行,MnemonicUtils对空白字符的处理在不同版本有差异。另外 passphrase 如果钱包设过而你没传,地址也会完全不同。
解决:先把助记词trim()并统一空格,再确认 passphrase。然后写个循环把常见派生路径和索引 0 到 9 都打出来,和钱包地址比对。如果还是对不上,检查助记词语言,BIP39 有英文、日文、中文等词表,用错词表派生出的种子是错的。
5.2 现象:节点返回invalid sender或签名相关错误
原因:这种情况通常不是查余额引起的,而是你在用派生出的私钥发交易时,链 ID 或 nonce 不对。查余额本身不涉及签名,但如果你的代码里混了发交易的逻辑,私钥派生错了就会报这个。
解决:先隔离问题,只跑查余额的代码。确认地址派生正确后,再检查发交易部分的 chainId 和 nonce。以太坊主网 chainId 是 1,测试网不同,签名时用错 chainId 会导致交易被拒。
5.3 现象:批量查询时大量请求超时或返回 429
原因:并发太高触发节点或第三方服务的速率限制。HTTP 连接池默认配置可能不够,或者每次请求都新建连接。
解决:降低并发数,加信号量控制。给 OkHttp 配置连接池和超时,web3j 的HttpService可以传入自定义的 OkHttpClient。加重试逻辑,遇到 429 时退避几秒再试,不要立即重试。
5.4 现象:余额精度丢失,大额余额显示成科学计数法
原因:用double或float处理 wei 转 ETH。wei 是 10^18 量级,double只有 53 位有效数字,大额余额会丢精度。
解决:全程用BigInteger和BigDecimal。转 ETH 时用BigDecimal.divide并指定精度和舍入模式,展示时用toPlainString()避免科学计数法。
5.5 现象:节点同步中,查到的余额是旧的
原因:连的节点还没同步到链头,LATEST返回的是它本地认为的最新块,可能落后主网很多块。
解决:查节点同步状态,用web3j.ethSyncing().send()看isSyncing()。如果节点在同步,换一个已同步的节点,或者接受延迟。对时间敏感的业务,固定区块号查询并记录区块高度,方便对账。
6. 进阶技巧:用固定区块号做可复现的余额快照
前面几章把链路跑通了,但真正在生产里做余额对账,光会查LATEST是不够的。链上数据是不断变化的,同一批地址在不同时间查,余额可能不同。要做可复现的对账,必须固定区块号。web3j 里用DefaultBlockParameter.valueOf(BigInteger.valueOf(blockNumber))就能查指定区块的余额。这个技巧在空投快照、分红计算、审计取证里都是刚需。
具体做法是:先拿到当前区块号web3j.ethBlockNumber().send().getBlockNumber(),记下来,然后用这个区块号查所有地址的余额。这样无论什么时候重跑,只要区块号一样,结果就一样。如果节点做了归档裁剪,查历史区块可能返回missing trie node错误,这时候需要连归档节点。第三方节点服务里,免费档通常不提供归档数据,查历史区块会失败,这是选型时要提前确认的。
另一个进阶点是多链。同样的助记词,在以太坊、BSC、Polygon 上派生出的地址是一样的,因为 coin type 都是 60(有些链用 60 的变体,但主流 EVM 链都用 60)。但余额要分别查各自的节点。你可以把Web3j实例按链配置成多个,用同一个地址去查不同链的余额。这里有个坑:不同链的 chainId 不同,如果你要发交易,签名时 chainId 必须对;但查余额不需要 chainId,只需要节点地址。
验证方法上,我习惯用「已知答案」做回归:拿一个公开的、余额已知的地址(比如某个交易所的热钱包),用你的代码查一遍,和区块浏览器上的数字比对。如果对得上,说明派生和查询链路没问题;对不上,就按第 5 章的排查顺序走。这个习惯帮我省了很多后悔药——有次批量查询结果全偏,最后发现是节点连到了测试网,地址在主网上根本没余额。
最后说个我自己的教训:早期做地址监控时,我把助记词直接写在了application.yml里,觉得内网环境没事。后来做安全审计被指出来,才改成从加密配置中心拉取,并且只在内存里派生,用完立即清零。链上代码一旦涉及私钥,安全边界就要比普通业务高一档。希望帮到你。
本文还有配套的精品资源,点击获取