属于 seed/熵审计任务 ArcBlock/arc-wallet-ios#4647 的分项。
问题 1:熵缓冲区是 ASCII-hex,实际熵只有名义的一半
wallet-sdk/src/main/java/io/arcblock/walletkit/bip44/Bip44Utils.java:
private static byte[] getEntropy(String secretCode, String recoverCode) {
// SHA3(SHA3(user_entered_key) + seed)
String result = BaseEncoding.base16()
.encode(ArcKeccakf1600Hasher.sha256(
(BaseEncoding.base16().encode(ArcKeccakf1600Hasher.sha256(secretCode.getBytes(), 1))
+ recoverCode).getBytes(), 1));
return result.substring(0, 32).getBytes();
}
result 是 base16(hex)字符串,.substring(0,32).getBytes() 得到 32 字节,但每字节取值只在 [0-9A-F] 这 16 个值里 —— 32 × 4 = 128 bit 有效熵。
genSeed() 把它交给 bitcoinj new DeterministicSeed(entropy, passphrase, creationTimeSeconds),32 字节熵 → 24 词助记词。
不可利用(recoverCode = genRecoverCode() 的 16 字节 SecureRandom,128 bit 是安全的),但标称 256 bit / 实际 128 bit 对不上。这正是 Coldcard 复盘里那个缺陷类别,只是量级差很远。
问题 2:recoverCode 为 null 时种子会塌缩
调用方 arc-wallet-android 的 CreatePasswordActivity.kt:
@JvmField @Autowired var recoverCode: String? = null
...
:263 BaseEncoding.base64().encode(Bip44Utils.genSeed(secretCode, recoverCode, "").seedBytes ?: ByteArray(0))
recoverCode 是 ARouter 注入的可空字段。Java 里 ... + recoverCode 对 null 会拼成字符串 "null",不抛异常 —— 于是种子退化成只依赖 secretCode(一个用户口令)的确定性函数。
当前不可达:CreatePasswordActivity 的 type 只有 0 和 2 被派发(ImportMnemonicActivity.kt:254 传 withInt("type", 2)),走 genSeed 的 type == 1 分支没有任何调用方。但这是一条只要有人接错路由就会静默生效的路径。
建议
getEntropy 是历史钱包的恢复算法,算法本身不能改(改了老用户恢复不出来),但要加注释写清"输出是 32 字节 ASCII-hex,有效熵 128 bit"。
genSeed(String, String, String) 三个参数加 @NonNull,并在方法入口对 recoverCode 做非空 + 长度校验,让 null 快速失败而不是拼成 "null"。
- 调用侧(arc-wallet-android
CreatePasswordActivity 的 type == 1 分支)已是死代码,建议一并删除。
ArcBlock/walletkit-android 里 walletkit/src/main/java/io/arcblock/walletkit/bip44/Bip44Utils.java 是同一份代码的另一个副本(我逐行 diff 过,除 javadoc 和 creationTimeSeconds 外完全一致)。两份并存本身也值得收敛。
- iOS 侧有逐字对应的实现(
BIP44Utils.getEntropy),已在 ArcBlock/arcblock-ios-sdk 另开 issue。
问题 1:熵缓冲区是 ASCII-hex,实际熵只有名义的一半
wallet-sdk/src/main/java/io/arcblock/walletkit/bip44/Bip44Utils.java:result是 base16(hex)字符串,.substring(0,32).getBytes()得到 32 字节,但每字节取值只在[0-9A-F]这 16 个值里 —— 32 × 4 = 128 bit 有效熵。genSeed()把它交给 bitcoinjnew DeterministicSeed(entropy, passphrase, creationTimeSeconds),32 字节熵 → 24 词助记词。不可利用(
recoverCode=genRecoverCode()的 16 字节 SecureRandom,128 bit 是安全的),但标称 256 bit / 实际 128 bit 对不上。这正是 Coldcard 复盘里那个缺陷类别,只是量级差很远。问题 2:
recoverCode为 null 时种子会塌缩调用方
arc-wallet-android的CreatePasswordActivity.kt:recoverCode是 ARouter 注入的可空字段。Java 里... + recoverCode对 null 会拼成字符串"null",不抛异常 —— 于是种子退化成只依赖secretCode(一个用户口令)的确定性函数。当前不可达:
CreatePasswordActivity的type只有 0 和 2 被派发(ImportMnemonicActivity.kt:254传withInt("type", 2)),走genSeed的type == 1分支没有任何调用方。但这是一条只要有人接错路由就会静默生效的路径。建议
getEntropy是历史钱包的恢复算法,算法本身不能改(改了老用户恢复不出来),但要加注释写清"输出是 32 字节 ASCII-hex,有效熵 128 bit"。genSeed(String, String, String)三个参数加@NonNull,并在方法入口对recoverCode做非空 + 长度校验,让 null 快速失败而不是拼成"null"。CreatePasswordActivity的type == 1分支)已是死代码,建议一并删除。ArcBlock/walletkit-android里walletkit/src/main/java/io/arcblock/walletkit/bip44/Bip44Utils.java是同一份代码的另一个副本(我逐行 diff 过,除 javadoc 和creationTimeSeconds外完全一致)。两份并存本身也值得收敛。BIP44Utils.getEntropy),已在 ArcBlock/arcblock-ios-sdk 另开 issue。