⽐特币之难度调整算法推导及实现
⽐特币⼯作证明基本原理:对⽐特币 整个区块头进⾏hash 再进⾏ ⽐较 是否在 ⼀个特定单位内 ⼀个has
h 的取值范围是 次⽅⼤⼩。难度值(difficulty)是矿⼯们在挖矿时候的重要参考指标,它决定了矿⼯⼤约需要经过多少次哈希运算才能产⽣⼀个合法的区块。⽐特币的区块⼤约每10分钟⽣成⼀个,如果要在不同的全⽹算⼒条件下,新区块的产⽣保持都基本这个速率,难度值必须根据全⽹算⼒的变化进⾏调整。简单地说,难度值被设定在⽆论挖矿能⼒如何,新区块产⽣速率都保持在10分钟⼀个。
难度的调整是在每个完整节点中独⽴⾃动发⽣的。每2016个区块,所有节点都会按统⼀的公式⾃动调整难度,这个公式是由最新2016个区块的花费时长与期望时长(期望时长为20160分钟即两周,是按每10分钟⼀个区块的产⽣速率计算出的总时长)⽐较得出的,根据实际时长与期望时长的⽐值,进⾏相应调整(或变难或变易)。也就是说,如果区块产⽣的速率⽐10分钟快则增加难度,⽐10分钟慢则降低难度。
2256
⽐特币是怎么⾃动调整挖矿难度的呢?
⽐特币是通过将交易头 hash256 ⽣成64 位的hash 值 满⾜是否在这个区间来判断当前 矿⼯是否挖到矿的。假设 有0 ~ 15 的10个数 你要猜到 ⼩于 3 的概率 是 那么学过计算机的⼩伙伴 应该知道16进制表⽰法 16 =f 就相当于2/F的概率 那如果是 256=
=ff
如上图, 假定⼀个范围 为 0~30 概率为 30/256 = 11.54% 如果要随机猜测的话 的话 平均 要 9次左右 才能 得到 想要的hash值 ⼩于等于 0-30这个区间内。
但对于计算机来说 1GHZ主频⼤约就是每秒10亿次运算 来说 ⼏百以内的猜数字 当然是⼩意思了 那怎么办 其实很简单 把数字范围加⼤不就好了吗
sha 256 ⽣成的是 1.157920892373162e+77 这么⼤的范围区间 当你要在 ⼀个 位的数字中 ⼀个特定的值 的概率是 ⼤概是8.636168555094445e-78 也⼏乎是不可能的。 假设⼀台计算机每秒能猜 100亿个数字 1e10 那么要猜完全部的数字 ⼤概是
3.67e+59 年 与此相⽐宇宙存在150亿年(1.5e10) 。
⽽ ⽐特币 就是通过⽣成 这个
位的hash 值去计算 的⼩于都是正确的值 此时 可以取到的正确值的总数为 样本空间点数: 概率为 按照古典概型计算:样本空间点/全样本空间点 = =2.938735877055719e-39 可以看出 这个范围 越⼩ 难度就越⼤。
假定初始的难度0x00000000FFFF0000000000000000000000000000000000000000000000000000 =
2.695953529101131e+67
那 概率是多⼤呢 有上⾯的公式 可以计算 打个⽐⽅ 假如你要买 你要中奖概率 是100万分之1(概率)
那么 你就要买 100万次(次数) 假设 假设你买 100万次 需要 100年 那么你的买效率 是 100万次/100年 = 1万次/年
由上⾯可以得出公式: 计算次数 * 概率 = 1
计算次数 = 算⼒ * 时间
=16281
162256222561
22562128212822562128
≈22562.695953529101131e +67  2.3282709094019083e −10
⽐特币⼀般是10分钟1块 这个值是固定的,这样的话 每四年衰减⼀般,到2140 就全部挖完了。 算⼒ = 计算次数 / 时间
已知了概率 那么计算次数 = =4295032833.000015
算⼒为 : 4295032833.000015 / 60/ 60 / 10 (化为10分钟) =119306.46758333375 次/分钟
但是随着矿机的添加 算⼒就会提升 算⼒提升了 那么 在时间不变的情况下 根据公式 计算次数 就会提升 计算次数 和 概率 ⼜是成 反⽐的 那么 计算次数增加 那么概率 就会 变⼩ 那么假设 全⽹ 的算⼒ 由 119306.46758333375到达了 219306.46758333375 那么 概率要怎么调整呢。
由公式得: 概率 = 1 / 计算次数
计算次数 = 219306.46758333375 * 10 * 60 * 60 = 7895032833.000015
那么可以反向推出 概率 = 1/ 计算次数 = 1.266619178352438e-10
那么剩下的问题就是 从 空间中选则的哪个样本空间是这个概率呢。
样本空间/全空间= 概率样本空间 = 概率 * 全空间 = 1.266619178352438e-10 *  = 1.4666448092948163e+67 转成16进制:
0X000000000ded374e381d4b800000000000000000000000000000000000000000 算⼒提升后的难度
0x00000000FFFF0000000000000000000000000000000000000000000000000000 初始难度
0X000000000ded374e381d4b8 = 1.4666448092948163e+67
0x00000000FFFF =2.695953529101131e+67
为了 更直观 我们舍去后⾯的 0 可以看到 调整后 的值明显变⼩了 ⽽如果画在数轴上
就相当于范围变⼩了 范围变⼩了 就相当于 原来 你能买⼀百次 的 现在 只能买50次 那中奖概率肯定就会降低。假设 有个很能买的⼈⼈家 每年2万/次每年 那么这种⽅法 就相当于 达到修改概率 还是 100 年中奖。
基于以上推理我们得到以下公式 :
概率 = 1 / 计算次数
计算次数 = 算⼒ * 时间
⽬标难度 = 样本空间 = 概率 * 全空间
⽬标难度 = 样本空间 = 1 / 算⼒ * 时间 * 全空间
全空间为:时间为:10 * 60 * 60 = 10分钟 (固定)
初始 的难度值 tt= 0x00000000FFFF0000000000000000000000000000000000000000000000000000 实际计算是获取前 n(⽐特币⾥是第前2016个block的bints 存储了 当时的难度值)个区块中 。
算⼒ = 计算次数/时间
计算次数 = 1/概率
得:算⼒ = 1/概率 /时间 = 1/概率*时间
2.3282709094019083e −10
≈2.3282709094019083e −101
225622562256
=  =  = 所以得出:= 改变后⽬标难度 = 算⼒改变后时间 就是前n个 ⽐特币⾥设定2周 每 10分钟⼀个 就
是2016个块实际运⽤时间
算⼒改变前时间控制在10分钟这个时间
所以总结就是 只要知道 前特定次 的实际花费时间 和 期望需要花费的时间 做个⽐值 再乘以 第那⼀次的 困难度 就可以了。
以下是代码:
public  static  void  main (String [] args ) {
public  final  static  Integer nTargetTimespan = 14 * 24 * 60 * 60; //过去两周时间
public  final  static  Integer nTargetSpacing = 10 * 60; //期望挖矿时间
public  final  static  Integer nInterval = nTargetTimespan / nTargetSpacing ; //过去2周期望达到的区块数 2016个
public  final  static  String num2 =new  BigInteger ("0FF0000000000000000000000000000000000000000000000000000000000000", 16).toString ();//初始难度
BigDecimal numb2 = new  BigDecimal (num2);
//实际时间
float  timeInterval = 1;
float  scale = timeInterval /nTargetTimespan ;
System .out .println ("缩放⽐例:"+scale +"如果当前 的算⼒越⼤ 花费时间越⼩ 和 预期时间不变并成反⽐ 缩放⽐例也就越⼩ ");
//假设
int  precision = 0; // 0位⼩数
BigDecimal NEWb = new  BigDecimal (numb2.multiply (new  BigDecimal (scale )).toString ()).setScale (precision , RoundingMode .DOWN );
String toHex = String .format ("%#x",NEWb .toBigInteger ());
StringBuilder zeroHeader = new  StringBuilder ();
/**
* 下⾯代码⾃动 补⾜长度为64位 如 0x00028ccccbd80000000000000000000000000000000000000000000000000000
*/
for (int  i =0;i <64 - toHex .replace ("0x","").length () +1;i ++){
zeroHeader .append ("0");
}
for (int  m =2;m < toHex .length () -1;m ++){
zeroHeader .append (toHex .charAt (m ));
}
toHex = "0x" + zeroHeader .toString ();
System .out .println (toHex + "\n"+toHex .length () +"0x".length ());
}
⽐特币⾥ nbits字段 ⽤来存放区块难度
Bits:0x18013ce9(以⼗六进制显⽰)
上⾯这个其实 是 0x0000000000000000013ce9000000000000000000000000000000000000000000 压缩格式。主要就是将⼀长串00省去了。
⽐特币 难度字符串反压缩算法:target = coefficient * 0x02^(0x08 * (exponent – 0x03))
=改变前⽬标难度改变后⽬标难度1/改变前算⼒∗时间∗全空间1/改变后算⼒∗时间∗全空间改变前算⼒改变后算⼒1/调整前概率∗算⼒改变前时间1/调整前概率∗算⼒改变后时间算⼒改变前时间
算⼒改变后时间改变前⽬标难度改变后⽬标难度算⼒改变前时间算⼒改变后时间改变前⽬标难度∗算⼒改变前时间
算⼒改变后时间
public static String decodeBits(String nbits){
if(nbits.startsWith("0x"))
nbits = nbits.substring(2,nbits.length());
int end=0;
int begin =0;
for(int i=0;i<nbits.length()-1;i++){
char ch =nbits.charAt(i);
if(ch !='0'&& begin ==0){
begin= i;//开始标记
}
//如果当前字符是 0且前⼏次都是零
if(ch =='0'&& begin ==0)
continue;
if(ch !='0')
//记录最后⼀个⾮0 char的index
end = i;
}
String co ="0"+nbits.substring(begin,end+1);
BigDecimal traget =new BigDecimal(new BigInteger(nbits,16));
BigDecimal coefficient =new BigDecimal(new BigInteger(co,16));
Double xx = traget.divide(coefficient).doubleValue();
System.out.println(traget.divide(coefficient));
Double mm =Math.log(xx)/Math.log(2)/0x08+0x03;
return"0x"+ HexString(mm.intValue())+ co;
}
⽐特币 难度字符串压缩算法 :log(traget/coefficient ,0x02) /0x08 + 0x03
public static String unDecodeBits(String nbits){
String a ="0x1903a30c";
String ex = a.substring(2,4);
String co = a.substring(4,a.length());
BigInteger exponent =new BigInteger(ex,16);
BigInteger coefficient =new BigInteger(co,16);
Double xx =exponent.subtract(new BigInteger("3")).multiply(new BigInteger("8")).doubleValue();
System.out.println(xx);
Double ax = Math.pow(2,xx);
BigDecimal res =new BigDecimal(coefficient).multiply(new BigDecimal(ax));
System.out.BigInteger());
String toHex = String.format("%#x.",BigInteger());
System.out.println(autoformat(toHex));
return autoformat(toHex);
}
public static String autoformat(String toHex){
StringBuilder zeroHeader =new StringBuilder();
/**
* 下⾯代码⾃动补⾜长度为64位如 0x00028ccccbd80000000000000000000000000000000000000000000000000000        */
for(int i =0;i<place("0x","").length()+1;i++){
zeroHeader.append("0");
bigdecimal取值范围
}
for(int m =2;m< toHex.length()-1;m++){
zeroHeader.append(toHex.charAt(m));
}
toHex ="0x"+ String();
UpperCase();
}