问题
遇到一个与地址相关的问题:新生成的合约地址本质上与其它地址一样,都是 32 字节的byte
数组;但是,当试图将地址转化为适合人类阅读的字符串时,转换出的结果却是乱码。
原因分析
每个字节是通过 8 位二进制数来表示的,这 8 位二进制数具体表示的 256 种可能的字符在 ASCII 码中给出了明确的定义。
问题就在于这 256 种字符中只有一部分是我们平时理解中的字符:
- [48~57]:0-9 十个数字;
- [65~90]:26 个大些英文字母;
- [97~122]:26 个小写英文字母。
因此,将地址转化为字符串显示时,超出以上范围的值显示的就只能是我们所不能理解的形式了,即乱码。
解决思路
既然显示乱码是因为 ASCII 码超出了所需的范围,那么就可以通过某种编码方式,将二进制数值进行编码,使其落到能够正常显示的区间内就可以了。
而编码方式的具体实现,早就有人已经做好了,我们不再需要重复造轮子,拿过来直接使用即可。
具体的实现有 Base 编码系列:Base64、Base32、Base16。
这里采用的是 Base32 编码:
使用32个可打印字符(字母A-Z和数字2-7)对任意字节数据进行编码,编码后的字符串不用区分大小写并排除了容易混淆的字符,可以方便地由人类使用并由计算机处理。
其特点有:
- 不区分大小写,便于人类口语交流或记忆
- 结果可用作文件名,因为不包含路径分隔符
/
等符号 - 排除了视觉上容易混淆的字符:忽略了数字
1、8、0
,因为它们可能与字母I、B、O
混淆)。
那么来实践一下:
byte[] before = ...
byte[] after = new Base32().encode(before);
得到的结果是 56 字节的byte
数组,超出了我们需要的 32 字节的长度,为什么会这样呢?
深入研究
仔细看一下 Base32 编码的原理:
将输入的
byte
数组的每个字节对应的二进制值(不足 8 位的,通过补 0 补成 8 位)串联起来;按照 5 位一组进行切分,将每组二进制值按照规则转换成 Base32 字母表中的字符(又扩展为 8 位)。
值 | 符号 | 值 | 符号 | 值 | 符号 | 值 | 符号 |
---|---|---|---|---|---|---|---|
0 | A | 8 | I | 16 | Q | 24 | Y |
1 | B | 9 | J | 17 | R | 25 | Z |
2 | C | 10 | K | 18 | S | 26 | 2 |
3 | D | 11 | L | 19 | T | 27 | 3 |
4 | E | 12 | M | 20 | U | 28 | 4 |
5 | F | 13 | N | 21 | V | 29 | 5 |
6 | G | 14 | O | 22 | W | 30 | 6 |
7 | H | 15 | P | 23 | X | 31 | 7 |
在这个编码的过程中无疑会放大字节数组的长度,经过计算,32 字节的byte
数组被放大为了 56 字节的byte
数组;而如果要求最终的结果为 32 字节的byte
数组的话,要求输入值为 20 字节的byte
数组!
获得 20 字节的byte
数组的方式很简单:
- 从 32 字节的
byte
数组中截取 - 对 32 字节的
byte
数组做一次RIPEMD160
哈希运算(一种单向函数,接收任意长度的输入产生数据指纹摘要),得到 20 字节的byte
数组
我们选择的是第二种方式,理由是:经过哈希运算产生的结果很难出现重复,而截取的话就不能保证了。
对结果进行验证:
byte[] before = ...
byte[] ripHash = RIPEMD160(before)
byte[] after = new Base32().encode(ripHash);
============================================
address is: IIYCWKDXVBC4OYQ7KC36TEWOE32C5YS6
如此,就得到了我们所需的能够正常显示的字符串地址了。
网友评论