1、16进制转换成58进制。
将50位(2位版本号+40位HASH160结果+8位校验码)16进制地址换成base58编码的时候,对于base58编码后的地址,理论上应该是最高位能表达的数值数量占总体数值数量的57/58=0.9827586206896552。
例如对于10进制,0-99中2位数占比90/100=9/10,0-999中3位数占比为900/1000=9/10;
对于16进制,0-FF中2位数占比240/256=15/16,0-FFF中3位数占比3840/4096=15/16;
同理,对于58进制的base58编码,最高位能表达的数值数量占总体数值数量的57/58=0.9827586206896552。
2、程序测试。
bitcoin硬核玩家@迦楼罗王2010 ,用js编了一个程序,对1开头的bitcoin地址进行测试。通过检测循环58^i与58^(i-1)的临界点(即每位满58进位,跟其他进制一样),来计算base58编码后的地址中,34位、33位……地址数量占总体地址数量的百分比。
测试结果发现:
34=0.9999869219,
33=0.0000130780,
32=0.0000000001,
31=0.0000000000,
30=0.0000000000,
……
34位地址占比远高于理论计算结果,非34地址占比只有10万分之一。
3、深入分析。
问题出现:按说从最低位开始,按理i每增大一个,地址长度加1, 但实际上不是的,而是好几个i对应的地址长度是一样的。
根本原因:base58转换前的16进制数值,进行了前导补0,每补一个0x00,base58转换就会多生成1个,从而会占多占1位。如果不进行前导补零。那么就会符合57/58=0.9827586207。
base58编码最前面的1(在base58编码中,1就代表没有大小的0),等同于其他进制最前面的0,对数值的大小没影响,但却占了位。
base58编码的前导1,导致多个i对应一个长度,说白了就是被前导1硬凑齐的。就如同0x00ff与0x01ff,理论上来说,分别是2位和3位数,但被对数值大小不起任何作用的前导0补齐都成了4位数。
4、手工练习。
可以用我写的脑钱包程序,练习16进制转换成58进制的base58编码。网页链接
4、测试程序源代码。
(未经网友授权,就发布了
。感兴趣的网友,可以测试一下)
const bs58 = require(‘bs58’);
function caculateAddressPercent() {
let ss5 = “”;
for (let i = 0; i < 50; i++) {
if (i == 0 || i == 1) {
ss5 += “0”;
} else {
ss5 += “f”;
}
}
let map = new Map();
let bbbb = BigInt(“0x” + ss5);
for (let i = 1; i <= 50; i++) {
let ele = BigInt(58) ** BigInt(i);
let ele_pre = null;
if (i == 1) {
ele_pre = BigInt(0);
} else {
ele_pre = BigInt(58) ** BigInt(i – 1);
}
if (ele > bbbb) {
console.error(“max 35 hex: ” + bbbb.toString(16));
console.error(“max 34 hex: ” + (BigInt(58) ** BigInt(i)).toString(16));
console.error(“max 35 num: ” + (bbbb – BigInt(58) ** BigInt(i)).toString(16));
let len1 = bs58.encode(Buffer.from(padString(ele.toString(16), 50), ‘hex’)).length;
map.set(len1.toString(), bbbb – ele_pre);
break;
}
console.error(“——-iii=” + i.toString() + “———ele=” + ele.toString());
let len1 = bs58.encode(Buffer.from(padString((ele – BigInt(1)).toString(16), 50), ‘hex’)).length;
let len2 = bs58.encode(Buffer.from(padString(ele.toString(16), 50), ‘hex’)).length;
console.error(“len1=” + len1.toString());
console.error(“len2=” + len2.toString());
let key = len1.toString();
let add = ele – ele_pre;
let num = map.get(key);
if (num == null) {
map.set(key, add);
} else {
num += add;
map.set(key, add);
}
console.error(“—-ele end—“);
}
console.error(map);
let total = BigInt(0);
for (let value of map.values()) {
total += value;
}
let total_deci = new Decimal(total.toString());
console.error(“total: ” + total.toString(16));
let perMap = new Map();
for (let [key, value] of map.entries()) {
let value_deci = new Decimal(value.toString());
let per = value_deci.dividedBy(total_deci).toFixed(10);
perMap.set(key, per);
}
console.error(perMap);
}
function padString(input, length) {
if (input.length >= length) {
return input.substring(0, length);
}
let output = “”;
for (let i = 0; i < length – input.length; i++) {
output += “0”;
}
output += input;
return output;
}