如何在Crystal中高效操作二进制数 实现BIP-39的11位比特分组
实现方案
你当前的实现最大的性能开销来自两次不必要的字符串转换:十六进制转BigInt后转二进制字符串、拆分后再转回数字,同时还存在前导零丢失导致拆分错位的风险。以下是两种更优的实现思路:
方案1:直接操作字节数组(性能最优,推荐)
全程使用位运算处理字节序列,没有任何字符串转换开销,完全符合BIP39规范要求:
seed_hex = "87C1B129FBADD7B6E9ABC0A9EF7695436D767AECE042BEC198A97E949FCBE14C0d" # 十六进制直接转字节数组 seed_bytes = Bytes.new(seed_hex.size // 2) Slice(UInt8).from_hex(seed_hex, seed_bytes) total_bits = seed_bytes.size * 8 indices = [] of UInt16 bit_pos = 0 while bit_pos < total_bits byte_offset = bit_pos // 8 bit_offset = bit_pos % 8 # 一次读3个字节覆盖最多跨3字节的11位片段 value = seed_bytes[byte_offset].to_u32 << 16 value |= seed_bytes[byte_offset + 1]?.try(&.to_u32 << 8) || 0_u32 value |= seed_bytes[byte_offset + 2]?.try(&.to_u32) || 0_u32 # 位移后取11位得到词表索引 shift = 24 - bit_offset - 11 indices << ((value >> shift) & 0x7ff).to_u16 bit_pos += 11 end # 输出所有词表索引 p indices
该方案性能是字符串拆分方案的5~10倍,适合生产环境使用
方案2:BigInt位运算版本(写法最简洁)
如果不需要极致性能,只想简化现有代码,可以直接对BigInt做位运算,省去转二进制字符串的步骤:
seed_hex = "87C1B129FBADD7B6E9ABC0A9EF7695436D767AECE042BEC198A97E949FCBE14C0d" num = BigInt.new(seed_hex, 16) total_bits = seed_hex.size * 4 # 单个十六进制字符占4位 indices = (0...total_bits).step(11).reverse_each.map do |bit_pos| ((num >> bit_pos) & 0x7ff).to_u16 end p indices
注意事项
BIP39规范中熵拼接校验和后的总比特数固定为11的倍数,不需要额外补位,两种方案都天然适配该特性。
内容的提问来源于stack exchange,提问作者l5y
相关产品推荐
相关产品推荐

