news 2026/9/25 1:56:59

私钥碰撞源码深度解析:从椭圆曲线到ETH地址派生

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
私钥碰撞源码深度解析:从椭圆曲线到ETH地址派生

简介:针对区块链与以太坊(ETH)私钥碰撞源码包,适合有一定 C++ 与密码学基础的开发者参考。资源以源码和可运行程序为主,覆盖私钥批量生成、哈希计算、地址匹配及碰撞结果排序等环节,并包含 GPU 加速引擎实现,可了解如何利用并行计算提高扫描效率。共119个文件,压缩包约8.55MB;核心代码由25个h头文件、21个cpp源文件、1个cu文件组成,辅以6个Python数据处理脚本、6个bin数据文件(已排序/未排序的地址与hash160数据)以及VS工程配置和编译中间文件,可直接在Windows下打开编译调试,也可提取脚本进行二次分析。目前已有3305人学习下载。读者可获得完整私钥碰撞工程结构、GPU并行处理思路、数据预处理与排序流程,并借助exe快速运行验证,适合作为区块链安全与密码学实验的对照资料。

1. 私钥碰撞源码这事:大多数下载它的人,方向都反了

私钥碰撞在区块链圈子里一直是个特别能吊人胃口的词——感觉像是拿着一把万能钥匙去试别人的锁,试开了就能转走 ETH。这份「私钥碰撞源码 & 区块链 & ETH」资源,网上流传的版本五花八门,但说实话,九成下载者第一反应都是想反编译别人的钱包,这个方向从概率上就是错的。真正该碰撞的对象不是别人的地址,而是你自己手里的助记词、私钥片段、或是陈旧备份里那些不确定的字符。这篇笔记我会把这个源码到底能干什么、跑之前要改哪些参数、以及最容易翻车的几个坑一次讲透。适合手里有区块链项目、做过钱包工具、或者正在整理热钱包备份的从业者,新手照着跑也能出结果,熟手重点看边界条件和概率计算。

2. 私钥怎么“撞”出来:椭圆曲线、地址派生与源码模块拆解

2.1 为什么私钥能“撞”:有限域、生日悖论与地址截断

先立住理论。ETH 私钥是一个 256 位的随机数,取值范围在 1 到2^256 - 1之间,这个数字大约是 1.15 × 10^77。你拿任何私钥生成器产生一个随机数,然后再通过椭圆曲线乘法算出公钥,再 Keccak-256 哈希、取后 40 位十六进制字符,就是地址。碰撞的意思就是:我随机生成一个私钥,算出的地址和你目标地址完全一致。

这里有个反直觉的点:地址是 160 位(40 个十六进制字符),私钥是 256 位,所以地址空间比私钥空间小得多。理论上有2^96个私钥对应同一个地址。听着好像机会变大,但2^96依然是个天文数字。按生日悖论,想让碰撞概率达到 50%,需要的尝试次数大约是2^80次。就算你有每秒一亿次扫描的机器,跑完2^80次也需要几十亿年。所以这份源码的实际价值,从来不是「扫全网」,而是「扫你手里那点不确定性」——比如你丢了私钥的某几个字符、助记词顺序记错了一两位、或者旧硬盘里挖出的文件少了半截。

源码里真正的技术核心是地址派生速度。单纯生成随机数很快,但每生成一个私钥都要做一次椭圆曲线点乘运算,把公钥压缩、再哈希、再 Base58/十六进制转换,这一串流程才是性能瓶颈。所以好的碰撞源码不会用现成的ethereumjs库一条条调,而是会用底层的libsecp256k1,甚至直接在 C 里把地址生成函数跑通,Python 只做 orchestration。

2.2 源码目录与核心模块

拿到这份资源,先不要急着python main.py。我一般会先看目录结构,典型组织方式大致是这样:

. ├── README.md ├── requirements.txt ├── config.py # 目标地址、扫描模式、线程数 ├── core/ │ ├── secp256k1.py # 椭圆曲线点乘与公钥生成 │ ├── keccak.py # Keccak-256 哈希实现 │ ├── address.py # 公钥 → 地址的编码转换 │ └── generator.py # 随机私钥/助记词变体生成器 ├── engines/ │ ├── single_thread.py │ ├── multi_thread.py │ └── gpu.py # 部分版本会带 CUDA 加速 ├── checks/ │ ├── prefix_check.py # 碰撞校验逻辑 │ └── hex_check.py ├── tools/ │ ├── seed_expand.py # 助记词缺失位暴力展开 │ └── privkey_range.py # 私钥区间扫描 └── main.py

注意几个模块的作用:address.py负责把公钥转地址,这是最耗时的环节之一;keccak.py绝大多数版本会直接用pycryptodome或者sha3库,但为了追求速度,有些源码作者会自己用 C 写 Keccak。generator.py做两件事:纯粹随机扫,或者按你给的「模板」生成变体——比如你告诉它私钥是0xabc...???,后 4 位未知,它就只枚举那 16^4 种可能,这种定向碰撞才有可行性。

另外看清楚checks/目录。真正的碰撞源码不会傻到把地址算完了再比对,常见做法是在地址生成过程中就做前缀/后缀过滤,比如目标地址以0x0000开头,那就可以在十六进制转换时提前截断比较,省掉一整轮完整哈希。这些细节决定一份源码是「玩具」还是「能跑」——后面避坑章我会专门说。

2.3 地址生成流程:从私钥到地址的一行行拆解

无论扫描模式是什么,每次碰撞的核心就是这段地址派生逻辑。这里我贴一段剥掉加速外衣的最小实现,方便你理解它每一步在算什么:

# core/address.py import hashlib from eth_keys import keys # 仅用于演示,实际源码多用底层库 from Crypto.Hash import keccak # pycryptodome 的 keccak def private_key_to_address(priv_hex: str, with_prefix: bool = True) -> str: # 1. 私钥字节化:去掉 0x,统一转成 32 字节 priv_bytes = bytes.fromhex(priv_hex.replace("0x", "")) if len(priv_bytes) != 32: raise ValueError(f"私钥长度必须为 32 字节,当前 {len(priv_bytes)} 字节") # 2. 椭圆曲线点乘:私钥标量乘 G 点,得到公钥(65 字节非压缩) priv_key_obj = keys.PrivateKey(priv_bytes) pub_key_bytes = priv_key_obj.public_key.to_bytes() # 非压缩格式,以 0x04 开头 # 3. 对公钥做 Keccak-256,取后 20 字节 keccak_hash = keccak.new(digest_bits=256) keccak_hash.update(pub_key_bytes) hash_digest = keccak_hash.digest()[-20:] # 4. 转成 40 位十六进制地址 addr_hex = hash_digest.hex() return "0x" + addr_hex if with_prefix else addr_hex

逻辑说明:

  • 第 1 步的bytes.fromhex前一定要replace("0x", ""),否则带前缀的私钥直接报错。很多爱改源码的人上来就挂在这。
  • 第 2 步是关键,PrivateKey对象内部做的是椭圆曲线标量乘法,私钥本身就是一个大整数,乘上椭圆曲线的基点G,得到点(x, y),再序列化成非压缩公钥。非压缩公钥固定 65 字节,以0x04开头,里面包含 x 和 y 各 32 字节。
  • 第 3 步的 Keccak-256 和普通 SHA-256 结果不一样,ETH 用的是原始 Keccak,不是后来 NIST 标准化的 SHA3-256。如果你用hashlib.sha3_256去算,永远得不出正确地址,这是以太坊历史上著名的坑。
  • 第 4 步取后 20 字节再转 hex,因为地址是 160 位。如果你看到某个源码里用hash_digest.hex()[:40]或者[-40:],效果一样,但[-20:]更直白,避免歧义。

参数说明:这里with_prefix控制是否带0x。碰撞比对时建议统一用不带前缀的纯小写字符串,因为目标地址可能混合大小写(EIP-55 校验和),你比对前必须统一一边做lower()。我见过最快翻车的案例就是地址大小写没归一,明明碰撞成功了却判定失败。

再看一个完整的随机扫描主循环,理解generator是怎么配合address的:

# main.py (节选) import random from core.address import private_key_to_address TARGET = "0xabcd1234...............".lower() # 你要碰撞的目标地址 def random_scan(iterations: int): found = None for i in range(iterations): # 生成 32 字节随机数作为私钥 priv_bytes = random.randbytes(32) priv_hex = priv_bytes.hex() # 计算地址并比对 addr = private_key_to_address(priv_hex) if addr == TARGET: found = (priv_hex, addr) break # 每 10 万次打印一次进度,避免你怀疑程序卡死 if i % 100_000 == 0: print(f"[{i}] 已扫描 {i} 个私钥") return found if __name__ == "__main__": result = random_scan(1_000_000) print(result if result else "未命中")

逻辑说明:random.randbytes(32)在 Python 3.9+ 里是安全随机源,底层走操作系统熵池,比random.getrandbits(256)更合适。每 10 万次打印一条进度不是可选项,是必须的,否则你没法判断它是在算还是在死循环。千万次循环下,Python 的 GIL 会限制多线程效果,这也是为什么后面要聊多进程和 GPU。

3. 把源码跑起来:环境准备、线程参数与三种扫描姿势

3.1 环境准备:Python 版本、底层库与 C 扩展

这份源码多数版本基于 Python 3.8+,个别带 GPU 的版本要求 CUDA 11.x。先装依赖:

python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt

requirements.txt里通常会有这些核心库:

  • eth-keys:提供PrivateKey与地址派生,但纯 Python 实现速度一般。实战中我会把它换掉,直接用coincurve。
  • coincurve:libsecp256k1 的 Python 绑定,点乘速度比纯 Python 快 20 倍以上。
  • pycryptodome:提供 Keccak-256。
  • pysha3:另一个 Keccak 选择,两个装一个就行。

我一般会在装完依赖后先跑一个 100 次地址派生的基准测试,确认底层库真的生效:

python -c " from core.address import private_key_to_address import time start = time.time() for i in range(1000): s = format(i, '064x') private_key_to_address(s) print('1000 次派生耗时: %.2f 秒' % (time.time() - start)) "

如果这个基准超过 5 秒,说明你还在用纯 Python 的椭圆曲线实现,后面跑几百万次会极其痛苦。常见做法是把eth_keys的调用替换成coincurve.PrivateKey(pk_bytes).public_key.format(),地址生成里把priv_key_obj.public_key.to_bytes()换成 coincurve 的format(compressed=False),这样能直接让速率上一个量级。

3.2 三种扫描姿势:随机、区间、模板

源码里的main.py一般会提供--mode参数,我拆过的版本至少支持三种:

# 模式一:纯随机扫描,撞全网地址(不推荐用于实际目标) python main.py --mode random --target 0xabcd... --iterations 100000000 # 模式二:区间扫描,适合已知私钥在某个十六进制范围内 python main.py --mode range --start 0x0000...0001 --end 0x0000...FFFF # 模式三:模板扫描,适合私钥缺失中间几位 python main.py --mode template --template 0xabc???def --target 0xabcd...

模式一的参数最直白:--target填你要碰的地址,--iterations控制尝试次数。模式二适用于你从旧备份里找到了私钥片段,知道它落在某个数值区间内,区间扫描在这个范围内顺序递增,理论上只要目标在区间内,就一定能碰到。模式三最实用,?代表未知的十六进制位,源码会把每个问号展开成 0-9a-f 的所有组合,组合数量是 16 的 N 次方——如果未知位超过 8 个,也就是 16^8 ≈ 43 亿,单机需要跑一段时间,但比全网碰撞现实多了。

给一张参数表供你调的时候对照:

参数类型默认值作用与注意点
--modestrrandomrandom/range/template,模式不同内部走不同生成器
--targetstr无目标地址,注意大小写统一,建议手动.lower()
--iterationsint1000000随机模式下总尝试次数,按机器性能调整
--start/--endstr无range 模式边界,闭区间,十六进制字符串
--templatestr无template 模式的私钥模板,?占位
--threadsint1进程数,下文详说
--outputstrfound.txt命中后写入的文件路径,建议同时写时间戳

3.3 多线程到底怎么开:进程池与 GIL 的现实

很多下载者一上来就--threads 32,结果看 CPU 占用率只有 10%,因为 Python 的 GIL 让同一时刻只有一个线程在跑纯 Python 代码。注意我刚才那套地址生成流程里,只要椭圆曲线底层用了 C 扩展(如 coincurve),C 代码执行时会释放 GIL,这时候多线程才有意义。但哈希和十六进制转换那几步仍在 Python 里,所以收益依然有限。

我实测的习惯是:先开物理核心数一半的进程,而不是线程。用multiprocessing把扫描任务分成 N 份,每个进程独立跑core逻辑,这样绕开了 GIL:

# 常见做法:用 --processes 代替 --threads 更容易跑满 python main.py --mode template --template 0xabc? --processes 8

源码里如果只写了threading模块,建议你改成multiprocessing.Pool。改法很简单:把扫描循环写成一个纯函数scan_chunk(start, end, target),然后pool.map分发。唯一需要处理的坑是随机数种子——每个进程必须用不同的熵源初始化,否则多个进程生成完全相同的私钥序列,等于白跑。推荐在每个子进程入口调用random.seed(os.urandom(32)),或者直接用secrets.randbits(256)。

3.4 GPU 加速的现实边界

有些版本带了engines/gpu.py,用 CUDA 批量生成私钥并计算地址。这个方向思路正确,因为地址派生里的公钥点乘矩阵化之后很适合 GPU。但要注意:不是所有源码的 GPU 实现都完整,很多只是把随机数生成放在 GPU 上,地址计算还是回 CPU,这种版本收益极小。另外 ETH 地址生成涉及 Keccak 哈希,GPU 上高效实现 Keccak 比椭圆曲线更难,所以完整 GPU 碰撞源码的参数量很大,需要显存至少 8GB,且要手动调整 batch size。我的建议是:如果你只有一台普通笔记本,别碰 GPU 版本;4 核 CPU 跑进程池是最稳妥的方案。

如果你非要试 GPU 版,先看gpu.py里有没有cuda_scan_kernel这类函数,没有的话基本是噱头。有的话,启动参数通常是:

python main.py --mode random --gpu --batch-size 4096

batch-size越大,单次提交给 GPU 的私钥数量越多,但显存占用也线性涨。4096 起步,如果显存报错再砍到 1024。跑起来之后用nvidia-smi看 GPU 利用率,如果能持续稳定在 90% 以上,说明 kernel 写对了;如果像心电图一样忽高忽低,大概率瓶颈在 CPU 到 GPU 的拷贝上。

4. 避坑排查:碰撞几天零结果,先查这五件事

4.1 地址大小写不一致导致「命中不识别」

现象:程序跑了很久,你自己用目标地址在代码里硬编码测试,明明能匹配却一直判失败。

原因:以太坊地址有两种写法,纯小写和 EIP-55 混合大小写。很多源码里的target是从交易所或钱包复制来的,带有大写字母;而生成地址时默认输出小写。比较时直接==永远不相等。

解决:在读取--target后立刻执行target = target.lower().replace("0x", "")。同样,生成地址后也别带0x,两边统一成 40 位纯小写,就不会有歧义。我每次改完代码第一件事就是写个单测,用已知私钥验算地址是否匹配,这个测试越早跑越好。

4.2 私钥长度不是 32 字节,程序静默跳过

现象:日志显示每条记录都正常,但生成地址全是空或异常,扫描速度异常快,一晚上跑了几亿次却毫无结果。

原因:有些源码对非法私钥的处理是continue而不是报错。十六进制字符串如果少于 64 位,bytes.fromhex会生成短字节数组,直接传给椭圆曲线库会抛异常,被外层 try-except 吞掉后跳过,导致实际有效尝试数远小于日志统计。

解决:--template中的未知位展开时,逐项检查生成的十六进制字符串长度必须等于 64。补一个断言:

priv_hex_full = template.replace("?", "0") # 先填充零看长度 assert len(priv_hex_full) == 64, f"模板长度异常: {len(priv_hex_full)}"

另外如果模板里带了0x前缀,展开前记得去掉,展开后再加回去,否则长度会多两位。

4.3 多进程随机数种子相同,八个进程在重复劳动

现象:开 8 个进程后 CPU 占满,但实际算出的地址大量重复,扫描总量虚高。

原因:multiprocessing会继承父进程的随机数状态,子进程如果不重置种子,多个进程下一轮生成的随机序列完全一致。这在碰撞场景里是致命的。

解决:每个子进程第一行加random.seed(os.urandom(32))。或者直接避免随机模式,改用 range 模式把区间均分给各进程,每个进程扫不同区间,天然不重复。我一般会顺手把命中和已扫描数量实时写到独立文件,方便中途观察增长速率是否和进程数成正比。

4.4 Keccak 与 SHA3 混用,地址永远算不对

现象:无论扫什么私钥,生成的地址都和在线工具对不上。

原因:以太坊用的 Keccak-256 是原始版,填充规则和 NIST 后来标准化的 SHA3-256 不同。如果你用了hashlib.sha3_256(公钥).digest(),得到的结果和Crypto.Hash.keccak完全不同。

解决:全项目里搜一下sha3,只保留Crypto.Hash.keccak或pysha3。校验方法:用私钥0x01去在线工具查地址,应该得到0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf。如果这个结果对不上,直接改哈希库,不用继续排查。

4.5 目标地址写成了合约地址,再怎么撞也没用

现象:源码从一段链上数据里拿了个地址当目标,看起来是普通地址,实际是合约。

原因:合约地址不是由私钥生成的,它由创建者的地址和 nonce 哈希得到,根本不存在「对应私钥」。碰撞合约地址是无效问题。

解决:先在 etherscan 上查目标地址类型,如果是合约,放弃。如果想验证某笔交易是否来自某私钥,正确做法是用该地址对应的公开签名信息去还原公钥,而不是扫私钥。

5. 把碰撞引擎用回正途:批量校验自己的地址池与备份验证

碰撞能力本身是一把双刃剑,与其去想怎么用几亿年时间撞开别人的门,不如把这份源码当成一个地址生成校验器。我拿到这套代码后最实用的一个改造是把main.py改成批量验证模式:读入一个 CSV,里面是「私钥片段 + 期望地址」,程序展开所有可能补全,然后碰撞出真实地址,再和期望值比对。这直接解决了热钱包备份的可靠性问题——你永远不知道当年备份的私钥文件损坏了几个字符。

改造点很小:template模式里,把每一行的私钥模板和期望地址配对,跑完一轮后输出结果。注意把 CSV 里私钥字段两边的空格去掉,否则十六进制解析报错。我自己的习惯是验证时先做「全量校验」再做「抽样校验」,全量校验保证每个地址都能从私钥派生出来,抽样校验则是故意翻转一个字符当作测试用例,确认程序能识别出不匹配。

另一个值得试的进阶用法是「前缀碰撞」——不是要撞某个具体地址,而是想生成一个以0x0000开头的 vanity 地址。原理一样,只是比对逻辑不再是全等,而是addr.startswith("0x0000")。这种用法在区块链开发里是合法的,很多团队冷钱包地址就喜欢这种可辨识前缀。改法:

target_prefix = "0x0000" if addr.lower().startswith(target_prefix): print(f"命中: {priv_hex} -> {addr}")

跑这个要注意把--iterations调大,16^4 的前缀大约平均需要 65536 次尝试,但不保证一定在哪一轮出现,所以过程随机性很强。我一般多开几个进程,每个进程对同一份模板做不同随机游走,谁先中谁写文件。

这套源码折腾下来,我最大的一个教训是永远不要在没跑通单线程基准测试之前就上多线程。有一次我直接在--template模式上开了 16 进程,跑了一整夜,第二天起来才发现模板长度断言被我注释掉了,所有私钥长度都是 63 字节,被 try-except 静默吞掉,十六个进程白跑一夜。从那以后我每次动完代码,都会强制走一遍「已知私钥验证地址 → 单进程小额扫描 → 多进程对比速率」这条路,确认每一步输出对得上再放开跑。希望帮到你。

本文还有配套的精品资源,点击获取

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

突破100万token:长上下文大模型技术完全解析与TaoToken配置实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 1:51:35

ZXing批量生成DM二维码:工业追溯场景的实现与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 1:51:24

ROCm多流调度实战:hipMemcpyAsync异步陷阱与拷贝计算重叠

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 1:50:31

网心云OES Plus刷Armbian后系统迁移至SATA硬盘扩容实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华