对 CVE-2022-40769 的可证明检查:某个 EOA 的私钥是否落在 Profanity 靓号地址生成器可达的密钥空间内?如果是,该密钥可被恢复,该账户(及其控制的任何东西)可被任何人清空。
没有启发式方法。结果是一个布尔值,由对精确密钥空间的完整搜索支撑,可通过重新运行同一分片复现。
Dispatcher.cpp,createSeed():
std::random_device rd;
std::mt19937_64 eng(rd()); // seeded with only 32 bits <- CVE-2022-40769
std::uniform_int_distribution<cl_ulong> distr;
cl_ulong4 r;
r.s[0] = distr(eng); r.s[1] = distr(eng); // 256-bit seed, used directly
r.s[2] = distr(eng); r.s[3] = distr(eng); // as the private key
随后 OpenCL 内核对该密钥取 G^k 并向前遍历,每轮将密钥加一,直到地址匹配所请求的模式。因此整个可达密钥空间为:
key(x, i) = mt19937_64(x) as a 256-bit scalar + i
x ∈ [0, 2^32) the 32-bit seed (four billion possibilities)
i ∈ [0, depth] rounds the generator ran
j ≤ depth 计算 P_j = P − j·G。如果密钥由 Profanity 生成,这些点中有一个是种子公钥。x 推导出 mt19937_64(x) 及其地址。P_j 是种子公钥,因此候选者的私钥为 seed(x) + j —— 可恢复,因而可被清空。匹配即为证明。在完整种子范围内无匹配即为相反结论的证明,上限为所选深度。
std::mt19937_64 抽取结果,与使用 std::mt19937_64 + std::uniform_int_distribution<unsigned long long> 的 g++ 程序逐 limb 比较;完全一致。k256(纯 Rust)和 secp256k1(libsecp256k1 绑定)生成:种子 0 → 0xfef2583edde5637dad990bc5d05d52c8247019cf,种子 12345 → 0x7712c45360f5dfa3a622a815b6073f0351050f13。--selftest 断言推导、地址、链遍历、向下遍历恢复,以及一个能找到已知种子的端到端种子扫描。自行运行:
cargo run --release -- --selftest
profanity-verifier --address 0x... [--pubkey 0x...] --shard i/n [--depth 16777216]
--pubkey —— 候选者的未压缩公钥(64 字节的 x||y,带或不带 0x04 前缀)。可从该地址签署过的任意交易中恢复。没有它,只能测试链位置 0。--shard i/n —— 要搜索的 32 位种子空间的切片,以便将工作并行分配到多台机器。--depth —— 沿生成器链查看的深度。2^24 足以覆盖六字符靓号且有余量;更深则消耗内存和时间。输出为单行 JSON,例如:
{"address":"0x...","shard":"0/64","depth":16777216,"seeds_checked":67108864,"seconds":833.4,"match":true}
种子和私钥从不被打印或存储。 只输出布尔值、地址和分片,因此结果可以发布而无需将密钥交给任何人。
.github/workflows/verify.yml 将搜索展开为公共 runner 上 (candidate, shard) 作业的矩阵。
实测吞吐量:2 核上约 20,000 种子/秒(libsecp256k1)。完整的 2^32 种子空间约 59 核心小时,因此 64 分片 × 4 核 ≈ 每个候选者 14 分钟挂钟时间 —— 在公共仓库上免费。
用候选者列表进行调度:
gh workflow run profanity-verify -f candidates='[{"address":"0x...","pubkey":"0x..."}]' -f shards=64
std::mt19937_64,逐 limb 比较已覆盖此点。针对不同标准库构建的版本会有不同的映射。depth 限定了搜索范围。在异常长的搜索(非常长的靓号模式)之后生成的候选者可能位于其之外。