ox0 前言

2008年10月31日,一个名为Satoshi Nakamoto (中本聪)的ID 在cypherpunk 论坛发布了一篇名为《Bitcoin: A Peer-to-Peer Electronic Cash System》的文章,现在此文被看作比特币的白皮书,文中提出了"区块链"的概念。随后在2008年11月,中本聪发布了比特币的第一版代码。2009年1月,中本聪挖出了比特币的第一个区块——创世区块,比特币网络正式开始运行。

现在我们知道这个叫做中本聪的神秘人,凭借这篇文章创造了万亿人民币的比特币市场。

image-20210113091537076

2010年12月12 日,中本聪在比特币论坛中发表了最后一篇文章,随后便不再公开露面,只通过电子邮件与比特币核心开发团队的少数人联系。2011年4月26日,在与比特币核心开发团队领导人加文·安德烈森联系过后,中本聪随即关闭了电子邮件,再也没有与任何人来往过。中本聪十分低调,在网络上可以追溯的行踪很少,他与任何人交流坚持使用PGP加密和Tor网络,而且他在论坛中的发言时间和说话方式也在刻意隐瞒自己的时区,伪装自己的表达习惯。由于他的代码逻辑十分精巧,设计区块链的具体数学细节也十分巧妙,所以有人怀疑中本聪是一个组织公用的ID。但是,时至今日也仍没有确切的证据证明某人或某组织就是中本聪。

关于神秘人中本聪的故事就不过多叙述了,本文主要记述区块链的技术原理,涉及的要点有:

  • 区块链的技术原理
  • p2p交易原理
  • 比特币挖矿
  • HD钱包?

本文是我在了解区块链技术过程中的学习笔记,如果你也希望了解其中的技术原理,本文也许会对你有帮助。本文结合比特币讲解区块链技术的原理和应用方法,比特币只是区块链技术的应用之一,文中讨论的具体方法也比较原始,但就简单理解区块链技术来说足以。

主要摘录整理廖雪峰老师的文章,所有参考文章附在文末。代码以廖雪峰老师给出的javascript 代码为例,每个代码块下方以引用的格式给出运行结果。

0x1 区块链原理

区块链就是一个不断增长的全网总账本,区块链网络中的每个完全节点都拥有完整的区块链,并且,节点总是信任最长的区块链,伪造区块链需要拥有超过51%的全网算力。后面会解释为什么超过51%的全网算力就可以肆意妄为。

区块链的一个重要特性就是不可篡改。为什么区块链不可篡改?我们先来看区块链的结构。

区块链是由一个一个区块构成的有序链表,每一个区块都记录了一系列交易(现在一个比特币区块约记录2000~3000笔交易),并且,每个区块都指向前一个区块,从而形成一个链条:

qukuailian1

如果我们观察某一个区块,就可以看到,每个区块都有一个唯一的哈希标识,被称为区块哈希(下图中父区块哈希),同时,区块通过记录上一个区块的哈希来指向上一个区块:

qukuailian2

每一个区块还有一个Merkle哈希用来确保该区块的所有交易记录无法被篡改。

区块链中的主要数据就是一系列交易,第一条交易通常是Coinbase交易,也就是矿工的挖矿奖励,后续交易都是用户的交易。

不可篡改性

区块链的不可篡改特性是由哈希算法保证的。

常用的哈希算法以及它们的输出长度如下:

哈希算法 输出长度(bit) 输出长度(字节)
MD5 128 bit 16 bytes
RipeMD160 160 bits 20 bytes
SHA-1 160 bits 20 bytes
SHA-256 256 bits 32 bytes
SHA-512 512 bits 64 bytes

比特币使用的哈希算法有两种:SHA-256和RipeMD160

SHA-256的理论碰撞概率是:尝试 21302^{130} 次随机输入,有99.8%的概率碰撞。注意 21302^{130} 是一个非常大的数字,大约是1361万亿亿亿亿。以现有的计算机的计算能力,是不可能在短期内破解的。

比特币使用两种哈希算法,一种是对数据进行两次SHA-256计算,这种算法在比特币协议中通常被称为hash256或者dhash。

另一种算法是先计算SHA-256,再计算RipeMD160,这种算法在比特币协议中通常被称为hash160。

const
    bitcoin = require('bitcoinjs-lib'),
    createHash = require('create-hash');

function standardHash(name, data) {
    let h = createHash(name);
    return h.update(data).digest();
}

function hash160(data) {
    let h1 = standardHash('sha256', data);
    let h2 = standardHash('ripemd160', h1);
    return h2;
}

function hash256(data) {
    let h1 = standardHash('sha256', data);
    let h2 = standardHash('sha256', h1);
    return h2;
}

let s = 'bitcoin is awesome';
console.log('ripemd160 = ' + standardHash('ripemd160', s).toString('hex'));
console.log('  hash160 = ' + hash160(s).toString('hex'));
console.log('   sha256 = ' + standardHash('sha256', s).toString('hex'));
console.log('  hash256 = ' + hash256(s).toString('hex'));

ripemd160 = 46c047bd035afb64dad2293cba29994a95b8b216
hash160 = fe56649aa4f8fdb1edf6b88d2d41f3c1f72cf431
sha256 = 23d4a09295be678b21a5f1dceae1f634a69c1b41775f680ebf8165266471401b
hash256 = 1c78f53758ac96f43b99ed080f36327d2a823c4df4fa094e59b006d945bbb84d

运行上述代码,观察对一个字符串进行SHA-256、RipeMD160、hash256和hash160的结果

Merkle Hash

在区块的头部,有一个Merkle Hash字段,它记录了本区块所有交易的Merkle Hash:

Merkle_Hash0

注意到哈希值也可以看做数据,所以可以把a1a2拼起来,a3a4拼起来,再计算出两个哈希值b1b2

       ┌───────────────┐               ┌───────────────┐
       │b1=dhash(a1+a2)│               │b2=dhash(a3+a4)│
       └───────────────┘               └───────────────┘
               ▲                               ▲
       ┌───────┴───────┐               ┌───────┴───────┐
       │               │               │               │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│a1=dhash(tx1)│ │a2=dhash(tx2)│ │a3=dhash(tx3)│ │a4=dhash(tx4)│
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘

最后,把b1b2这两个哈希值拼起来,计算出最终的哈希值,这个哈希就是Merkle Hash:

                     ┌───────────────────┐
                     │merkle=dhash(b1+b2)│
                     └───────────────────┘
                               ▲
               ┌───────────────┴───────────────┐
               │                               │
       ┌───────────────┐               ┌───────────────┐
       │b1=dhash(a1+a2)│               │b2=dhash(a3+a4)│
       └───────────────┘               └───────────────┘
               ▲                               ▲
       ┌───────┴───────┐               ┌───────┴───────┐
       │               │               │               │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│a1=dhash(tx1)│ │a2=dhash(tx2)│ │a3=dhash(tx3)│ │a4=dhash(tx4)│
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘

如果交易的数量不恰好是4个怎么办?例如,只有3个交易时,第一个和第二个交易的哈希a1a2可以拼起来算出b1,第三个交易只能算出一个哈希a3,这个时候,就把a3直接复制一份,算出b2,这样,我们也能最终计算出Merkle Hash:

                     ┌───────────────────┐
                     │merkle=dhash(b1+b2)│
                     └───────────────────┘
                               ▲
               ┌───────────────┴───────────────┐
               │                               │
       ┌───────────────┐               ┌───────────────┐
       │b1=dhash(a1+a2)│               │b2=dhash(a3+a3)│
       └───────────────┘               └───────────────┘
               ▲                               ▲
       ┌───────┴───────┐               ┌───────┴───────┐
       │               │               │               │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌ ─ ─ ─ ─ ─ ─ ┐
│a1=dhash(tx1)│ │a2=dhash(tx2)│ │a3=dhash(tx3)│
└─────────────┘ └─────────────┘ └─────────────┘ └ ─ ─ ─ ─ ─ ─ ┘

如果有5个交易,我们可以看到,a5被复制了一份,以便计算出b3,随后b3也被复制了一份,以便计算出c2。总之,在每一层计算中,如果有单数,就把最后一份数据复制,最后一定能计算出Merkle Hash:

                  ┌─────────┐
                  │ merkle  │
                  └─────────┘
                       ▲
           ┌───────────┴───────────┐
           │                       │
         ┌───┐                   ┌───┐
         │c1 │                   │c2 │
         └───┘                   └───┘
           ▲                       ▲
     ┌─────┴─────┐           ┌─────┴─────┐
     │           │           │           │
   ┌───┐       ┌───┐       ┌───┐       ┌ ─ ┐
   │b1 │       │b2 │       │b3 │        b3
   └───┘       └───┘       └───┘       └ ─ ┘
     ▲           ▲           ▲
  ┌──┴──┐     ┌──┴──┐     ┌──┴──┐
  │     │     │     │     │     │
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌ ─ ┐
│a1 │ │a2 │ │a3 │ │a4 │ │a5 │  a5
└───┘ └───┘ └───┘ └───┘ └───┘ └ ─ ┘

从Merkle Hash的计算方法可以得出结论:修改任意一个交易哪怕一个字节,或者交换两个交易的顺序,都会导致Merkle Hash验证失败,也就会导致这个区块本身是无效的,所以,Merkle Hash记录在区块头部,它的作用就是保证交易记录永远无法修改。

Block Hash

区块本身用Block Hash——也就是区块哈希来标识。但是,一个区块自己的区块哈希并没有记录在区块头部,而是通过计算区块头部的哈希得到的:

Block_Hash0

区块头部的Prev Hash记录了上一个区块的Block Hash,这样,可以通过Prev Hash追踪到上一个区块。

由于下一个区块的Prev Hash又会指向当前区块,这样,每个区块的Prev Hash都指向自己的上一个区块,这些区块串起来就形成了区块链。

区块链的第一个区块(又称创世区块)并没有上一个区块,因此,它的Prev Hash被设置为00000000...000

如果一个恶意的攻击者修改了一个区块中的某个交易,那么Merkle Hash验证就不会通过。所以,他只能重新计算Merkle Hash,然后把区块头的Merkle Hash也修改了。这时,我们就会发现,这个区块本身的Block Hash就变了,所以,下一个区块指向它的链接就断掉了。

Block_Hash1

由于比特币区块的哈希必须满足一个难度值,因此,攻击者必须先重新计算这个区块的Block Hash,然后,再把后续所有区块全部重新计算并且伪造出来,才能够修改整个区块链。

在后面的挖矿中,我们会看到,修改一个区块的成本就已经非常非常高了,要修改后续所有区块,这个攻击者必须掌握全网51%以上的算力才行,所以,修改区块链的难度是非常非常大的,并且,由于正常的区块链在不断增长,同样一个区块,修改它的难度会随着时间的推移而不断增加。

0x1 区块链原理总结:

区块链依靠安全的哈希算法保证所有区块数据不可更改;

交易数据依靠Merkle Hash确保无法修改,整个区块依靠Block Hash确保区块无法修改;

保证修改区块链的难度非常巨大的机制:工作量证明机制(PoW-Proof of Work 即挖矿)在后面章节介绍

0x2 P2P交易原理

比特币的交易是一种无需信任中介参与的P2P(Peer-to-peer)交易。

传统的电子交易,交易双方必须通过银行这样的信任机构作为中介,这样可以保证交易的安全性,因为银行记录了交易双方的账户资金,能保证在一笔交易中,要么保证成功,要么交易无效,不存在一方到账而另一方没有付款的情况。但是在比特币这种去中心化的P2P网络中,并没有一个类似银行这样的信任机构存在,要想在两个节点之间达成交易,就必须实现一种在零信任的情况下安全交易的机制

一种创建交易的方法是:小明声称他给了小红一万块钱,只要能验证这个声明确实是小明作出的,并且小明真的有1万块钱,那么这笔交易就被认为是有效的:

jiaoyijizhi

数字签名

如何验证这个声明确实是小明作出的呢?数字签名就可以验证这个声明是否是小明做的,并且,一旦验证通过,小明是无法抵赖的。在比特币交易中,付款方就是通过数字签名来证明自己拥有某一笔比特币,并且,要把这笔比特币转移给指定的收款方。使用签名是为了验证某个声明确实是由某个人做出的。例如,在付款合同中签名,可以通过验证笔迹的方式核对身份:

shuziqianming0

而在计算机中,用密码学理论设计的数字签名算法比验证笔迹更加可信。使用数字签名时,每个人都可以自己生成一个秘钥对,这个秘钥对包含一个私钥和一个公钥:私钥被称为Secret Key或者Private Key,私钥必须严格保密,不能泄漏给其他人;公钥被称为Public Key,可以公开给任何人。这种使用一对公私密钥进行加密解密的方法就是非对称加密,早已经普遍应用于现实世界中,如果具备非对称加密的基础知识理解后面的内容会相当容易,推荐非对称加密算法RSA原理一RSA原理二

当私钥持有人,例如,小明希望对某个消息签名的时候,他可以用自己的私钥对消息进行签名,然后,把消息、签名和自己的公钥发送出去:

shuziqianming1

其他任何人都可以通过小明的公钥对这个签名进行验证,如果验证通过,可以肯定,该消息是小明发出的。这里的签名和验证就是指非对称加密中的加密和解密过程。

这里要解释一下非对称加密应用的时候公钥和私钥到底谁负责加密谁负责解密?

第一种用法:公钥加密,私钥解密。——用于加解密
第二种用法:私钥签名,公钥验签。——用于签名

有点混乱,不要去硬记,总结一下:
你只要想:
既然是加密,那肯定是不希望别人知道我的消息,所以只有我才能解密,所以可得出公钥负责加密,私钥负责解密;
既然是签名,那肯定是不希望有人冒充我发消息,只有我才能发布这个签名,所以可得出私钥负责签名,公钥负责验证。

ref:https://blog.csdn.net/qq_23167527/article/details/80614454

数字签名算法在电子商务、在线支付这些领域有非常重要的作用:

首先,签名不可伪造,因为私钥只有签名人自己知道,所以其他人无法伪造签名。

其次,消息不可篡改,如果原始消息被人篡改了,那么对签名进行验证将失败。

最后,签名不可抵赖。如果对签名进行验证通过了,那么,该消息肯定是由签名人自己发出的,他不能抵赖自己曾经发过这一条消息。

简单地说来,数字签名的三个作用:防伪造,防篡改,防抵赖。

签名和验证过程

上面我们说了在p2p网络中数字签名的大致定义、作用和效果,我们来稍微具体讲一下区块链数字签名的签名过程和验证过程:

签名

对消息进行签名,实际上是对消息的哈希进行签名,这样可以使任意长度的消息在签名前先转换为固定长度的哈希数据。对哈希进行签名相当于保证了原始消息的不可伪造性。

我们来看看使用ECDSA如何通过私钥对消息进行签名。关键代码是通过sign()方法签名,并获取一个ECSignature对象表示签名:

const bitcoin = require('bitcoinjs-lib');
let
    message = 'a secret message!', // 原始消息
    hash = bitcoin.crypto.sha256(message), // 消息哈希
    wif = 'KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617',
    keyPair = bitcoin.ECPair.fromWIF(wif);
// 用私钥签名:
let signature = keyPair.sign(hash).toDER(); // ECSignature对象
// 打印签名:
console.log('signature = ' + signature.toString('hex'));
// 打印公钥以便验证签名:
console.log('public key = ' + keyPair.getPublicKeyBuffer().toString('hex'));

signature = 304402205d0b6e817e01e22ba6ab19c0ab9cdbb2dbcd0612c5b8f990431dd0634f5a96530220188b989017ee7e830de581d4e0d46aa36bbe79537774d56cbe41993b3fd66686
public key = 02d0de0aaeaefad02b8bdc8a01a1b8b11c696bd3d66a2c5f10780d95b7df42645c

ECSignature对象可序列化为十六进制表示的字符串。

验证

在获得签名、原始消息和公钥的基础上,可以对签名进行验证。验证签名需要先构造一个不含私钥的ECPair,然后调用verify()方法验证签名:

const bitcoin = require('bitcoinjs-lib');
let signAsStr = '304402205d0b6e817e01e22ba6ab19c0'
              + 'ab9cdbb2dbcd0612c5b8f990431dd063'
              + '4f5a96530220188b989017ee7e830de5'
              + '81d4e0d46aa36bbe79537774d56cbe41'
              + '993b3fd66686'

let
    signAsBuffer = Buffer.from(signAsStr, 'hex'),
    signature = bitcoin.ECSignature.fromDER(signAsBuffer), // ECSignature对象
    message = 'a secret message!', // 原始消息
    hash = bitcoin.crypto.sha256(message), // 消息哈希
    pubKeyAsStr = '02d0de0aaeaefad02b8bdc8a01a1b8b11c696bd3d66a2c5f10780d95b7df42645c',
    pubKeyAsBuffer = Buffer.from(pubKeyAsStr, 'hex'),
    pubKeyOnly = bitcoin.ECPair.fromPublicKeyBuffer(pubKeyAsBuffer); // 从public key构造ECPair

// 验证签名:
let result = pubKeyOnly.verify(hash, signature);
console.log('Verify result: ' + result);

Verify result: true

注意上述代码只引入了公钥,并没有引入私钥。

修改signAsStrmessagepubKeyAsStr的任意一个变量的任意一个字节,再尝试验证签名,则不能通过。

数字签名的验证实际上就是非对称加密中的解密过程,如果交易创造者使用私钥加密交易数据的hash得到的签名值(密文)能顺利被网络中其他人手中对应该私钥的公钥解密,即解密值==hash(交易数据),就算是签名验证成功。数字签名算法,私钥和公钥的生成方式以及公钥和地址的对应关系我们在下一节数字签名算法中叙述。

比特币对交易数据进行签名和对消息进行签名的原理是一样的,只是格式更加复杂。通过私钥可以对消息进行签名,签名可以保证消息防伪造,防篡改,防抵赖。对交易签名确保了只有持有私钥的人才能够花费对应地址的资金。

数字签名算法

这一小节讲介绍比特币采用的:

  • 签名算法
  • 私钥和公钥的生成方式
  • 公钥和地址的对应关系。

签名算法

常用的数字签名算法有:RSA算法,DSA算法和ECDSA算法。比特币采用的签名算法是椭圆曲线签名算法:ECDSA,使用的椭圆曲线是一个已经定义好的标准曲线secp256k1:

y2=x3+7y^2 = x^3 + 7

这条曲线的图像长这样:

secp256k1

比特币采用的ECDSA签名算法需要一个私钥和公钥组成的秘钥对:私钥本质上就是一个122561~2^{256}的随机数,公钥是由私钥根据ECDSA算法推算出来的,通过私钥可以很容易推算出公钥,所以不必保存公钥,但是,通过公钥无法反推私钥,只能暴力破解。

私钥

比特币的私钥是一个随机的非常大的22562^{256}位整数。它的上限,确切地说,比22562^{256}要稍微小一点:

0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4140

而比特币的公钥是根据私钥推算出的两个256位整数。

如果用银行卡作比较的话,比特币的公钥相当于银行卡卡号,它是两个256位整数:

bitconcard

比特币的私钥相当于银行卡密码,它是一个256位整数:

18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725

银行卡的卡号由银行指定,银行卡的密码可以由用户随时修改。而比特币“卡”和银行卡的不同点在于:密码(实际上是私钥)由用户先确定下来,然后计算出“卡号”(实际上是公钥),即卡号是由密码通过ECDSA算法推导出来的,不能更换密码。

由于比特币账本是全网公开的,所以,任何人都可以根据公钥查询余额,但是,不知道持卡人是谁。这就是比特币的匿名特性。

如果丢失了私钥,就永远无法花费对应公钥的比特币!

在比特币的P2P网络中不存在中央节点,私钥只有持有人自己知道,因此,丢失了私钥,对应的比特币就永远无法花费。如果私钥被盗,黑客就可以花费对应公钥的比特币,并且这是无法追回的。

我们以JavaScript为例,演示如何创建比特币私钥。在JavaScript中,内置的Number类型使用56位表示整数和浮点数,最大可表示的整数最大只有9007199254740991。其他语言如Java一般也仅提供64位的整数类型。要表示一个256位的整数,只能使用数组来模拟。bitcoinjs使用bigi这个库来表示任意大小的整数。

下面的代码演示了使用一个随机生成的数字,通过ECPair创建一个新的私钥后,表示私钥的整数就是字段d,我们把它打印出来:

const bitcoin = require('bitcoinjs-lib');
let keyPair = bitcoin.ECPair.makeRandom();
// 打印私钥:
console.log('private key = ' + keyPair.d);
// 以十六进制打印:
console.log('hex = ' + keyPair.d.toHex());
// 补齐32位:
console.log('hex = ' + keyPair.d.toHex(32));

private key = 81014991225650123107594446292560463009575145732738301659501234189040494639354
hex = b31cdc2d854be1d2047b0290d7b3039977bd29c036a62b63b2550c87d28cf8fa
hex = b31cdc2d854be1d2047b0290d7b3039977bd29c036a62b63b2550c87d28cf8fa

想要记住一个256位的整数是非常困难的,并且,如果记错了其中某些位,这个记错的整数仍然是一个有效的私钥,因此,比特币有一种对私钥进行编码的方式,这种编码方式就是带校验的Base58编码

对私钥进行Base58编码有两种方式,一种是非压缩的私钥格式,一种是压缩的私钥格式,它们分别对应非压缩的公钥格式和压缩的公钥格式。

具体地来说,非压缩的私钥格式是指在32字节的私钥前添加一个0x80字节前缀,得到33字节的数据,对其计算4字节的校验码,附加到最后,一共得到37字节的数据:

0x80           256bit             check
┌─┬──────────────────────────────┬─────┐
│1│              32              │  4  │
└─┴──────────────────────────────┴─────┘

计算校验码非常简单,对其进行两次SHA256,取开头4字节作为校验码。

对这37字节的数据进行Base58编码,得到总是以5开头的字符串编码,这个字符串就是我们需要非常小心地保存的私钥地址,又称为钱包导入格式:WIF(Wallet Import Format)。可以使用wif这个库实现WIF编码:

const wif = require('wif');
// 十六进制表示的私钥:
let privateKey = '0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d';
// 对私钥编码:
let encoded = wif.encode(
        0x80, // 0x80前缀
        Buffer.from(privateKey, 'hex'), // 转换为字节
        false // 非压缩格式
);
console.log(encoded);

5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ

(如果你去验证了这个算法发现5HueC…解不出来0c28…,或者800c28…aa1d507a5b8d编码结果不是5HueC…,是因为你用的base58工具把你拼接的38字节数据看作字符串解析的,而实际上应该是看作16进制数值解析。私钥地址总是以5开头就是因为十六进制私钥以0x80开头base58结果总是5)

另一种压缩格式的私钥编码方式,与非压缩格式不同的是,压缩的私钥格式会在32字节的私钥前后各添加一个0x80字节前缀和0x01字节后缀,共34字节的数据,对其计算4字节的校验码,附加到最后,一共得到38字节的数据:

0x80           256bit           0x01 check
┌─┬──────────────────────────────┬─┬─────┐
│1│              32              │1│  4  │
└─┴──────────────────────────────┴─┴─────┘

对这38字节的数据进行Base58编码,得到总是以KL开头的字符串编码。通过代码实现压缩格式的WIF编码如下:

const wif = require('wif');
// 十六进制表示的私钥:
let privateKey = '0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d';
// 对私钥编码:
let encoded = wif.encode(
        0x80, // 0x80前缀
        Buffer.from(privateKey, 'hex'), // 转换为字节
        true // 压缩格式
);
console.log(encoded);

KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617

目前,非压缩的格式几乎已经不使用了。bitcoinjs提供的ECPair总是使用压缩格式的私钥表示:

const
    bitcoin = require('bitcoinjs-lib'),
    BigInteger = require('bigi');
let
    priv = '0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d',
    d = BigInteger.fromBuffer(Buffer.from(priv, 'hex')),
    keyPair = new bitcoin.ECPair(d);
// 打印WIF格式的私钥:
console.log(keyPair.toWIF());

KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617

私钥小结:

比特币的私钥本质上就是一个256位整数,对私钥进行WIF格式编码可以得到一个带校验的字符串。

使用非压缩格式的WIF是以5开头的字符串,使用压缩格式的WIF是以KL开头的字符串。

公钥

比特币的公钥是根据私钥计算出来的。

私钥本质上是一个256位整数,记作k。根据比特币采用的ECDSA算法,可以推导出两个256位整数,记作(x, y)这两个256位整数即为非压缩格式的公钥

由于ECC曲线的特点,根据非压缩格式的公钥(x, y)x实际上也可推算出y,但需要知道y的奇偶性,因此,可以根据(x, y)推算出x',作为压缩格式的公钥。

压缩格式的公钥实际上是,根据y的奇偶性在x前面添加0203前缀,y为偶数时添加02,否则添加03,这样,得到一个1+32=33字节的压缩格式的公钥数据,记作x'这个284位的整数作为压缩格式的公钥

注意压缩格式的公钥和非压缩格式的公钥是可以互相转换的,但均不可反向推导出私钥。

非压缩格式的公钥目前已很少使用,原因是非压缩格式的公钥签名脚本数据会更长。

我们来看看如何根据私钥推算出公钥:

const bitcoin = require('bitcoinjs-lib');
let
    wif = 'KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617',
    ecPair = bitcoin.ECPair.fromWIF(wif); // 导入私钥
// 计算公钥:
let pubKey = ecPair.getPublicKeyBuffer(); // 返回Buffer对象
console.log(pubKey.toString('hex')); // 02或03开头的压缩公钥

02d0de0aaeaefad02b8bdc8a01a1b8b11c696bd3d66a2c5f10780d95b7df42645c

构造出ECPair对象后,即可通过getPublicKeyBuffer()Buffer对象返回公钥数据。

地址

要特别注意,比特币的地址并不是公钥,而是公钥的哈希,即从公钥能推导出地址,但从地址不能反推公钥,因为哈希函数是单向函数。

以压缩格式的公钥为例,从公钥计算地址的方法是,首先对1+32=33字节的公钥数据进行Hash160(即先计算SHA256,再计算RipeMD160),得到20字节的哈希。然后,添加0x00前缀,得到1+20=21字节数据,再计算4字节校验码,拼在一起,总计得到1+20+4=25字节数据:

0x00      hash160         check
┌─┬──────────────────────┬─────┐
│1│          20          │  4  │
└─┴──────────────────────┴─────┘

对上述25字节数据进行Base58编码,得到总是以1开头的字符串,该字符串即为比特币地址。使用JavaScript实现公钥到地址的编码如下:

const bitcoin = require('bitcoinjs-lib');
let
    publicKey = '02d0de0aaeaefad02b8bdc8a01a1b8b11c696bd3d66a2c5f10780d95b7df42645c',
    ecPair = bitcoin.ECPair.fromPublicKeyBuffer(Buffer.from(publicKey, 'hex')); // 导入公钥
// 计算地址:
let address = ecPair.getAddress();
console.log(address); // 1开头的地址

1LoVGDgRs9hTfTNJNuXKSpywcbdvwRXpmK

计算地址的时候,不必知道私钥,可以直接从公钥计算地址,即通过ECPair.fromPublicKeyBuffer构造一个不带私钥的ECPair即可计算出地址。

要注意,对非压缩格式的公钥和压缩格式的公钥进行哈希编码得到的地址,都是以1开头的,因此,从地址本身并无法区分出使用的是压缩格式还是非压缩格式的公钥。

1开头的字符串地址即为比特币收款地址,可以安全地公开给任何人。

仅提供地址并不能让其他人得知公钥。通常来说,公开公钥并没有安全风险。实际上,如果某个地址上有对应的资金,要花费该资金,就需要提供公钥。如果某个地址的资金被花费过至少一次,该地址的公钥实际上就公开了。

私钥、公钥以及地址的推导关系如下:

┌───────────┐      ┌───────────┐
│Private Key│─────▶│Public Key │
└───────────┘      └───────────┘
      ▲                  │
      │                  │
      ▼                  ▼
┌───────────┐      ┌───────────┐
│    WIF    │      │  Address  │
└───────────┘      └───────────┘

对数字签名这一节做一个总结:

1.数字签名应用于p2p交易过程,目的是对交易发起者(付款人)进行验证。数字签名的三个作用:防伪造,防篡改,防抵赖。

2.比特币使用的数字签名算法是ECDSA

3.比特币私钥本质上是一个256位随机整数,对私钥进行WIF格式编码可以得到一个带校验的字符串。私钥由持有者自行保管,一旦丢失不可找回,钱包再也无法找回

4.比特币公钥根据私钥计算的来,算法是ECDSA(即非对称加密私钥计算公钥),本质上是两个256位整数,公钥有压缩和非压缩两种表示方法,可互相转换

5.比特币的地址是公钥哈希的编码,并不是公钥本身,通过公钥可推导出地址;通过地址不可推导出公钥,通过公钥不可推导出私钥。

比特币钱包

比特币钱包实际上就是帮助用户管理私钥的软件。因为比特币的钱包是给普通用户使用的,它有几种分类:

  • 本地钱包:是把私钥保存在本地计算机硬盘上的钱包软件,如Electrum
  • 手机钱包:和本地钱包类似,但可以直接在手机上运行,如Bitpay
  • 在线钱包:是把私钥委托给第三方在线服务商保存;
  • 纸钱包:是指把私钥打印出来保存在纸上;
  • 脑钱包:是指把私钥记在自己脑袋里。

对大多数普通用户来说,想要记住私钥非常困难,所以强烈不建议使用脑钱包

作为用户,可以生成任意数量的私钥-公钥对,公钥是接收别人转账的地址,而私钥是花费比特币的唯一手段,钱包程序可以帮助用户管理私钥-公钥对。

交易

我们再来看记录在区块链上的交易。每个区块都记录了至少一笔交易,一笔交易就是把一定金额的比特币从一个输入转移到一个输出:

deal1

例如,小明把两个比特币转移给小红,这笔交易的输入是小明,输出就是小红。实际记录的是双方的公钥地址

如果小明有50个比特币,他要转给小红两个比特币,那么剩下的48个比特币应该记录在哪?比特币协议规定一个输出必须一次性花完,所以,小明给小红的两个比特币的交易必须表示成:

deal2

小明给小红2个比特币,同时小明又给自己48个比特币,这48个比特币就是找零。所以,一个交易中,一个输入可以对应多个输出。

当小红有两笔收入时,一笔2.0,一笔1.5,她想给小白转3.5比特币时,就不能单用一笔输出,她必须把两笔钱合起来再花掉,这种情况就是一个交易对应多个输入和1个输出:

deal3

果存在找零,这笔交易就既包含多个输入也包含多个输出:

deal4

在实际的交易中,输入比输出要稍微大一点点,这个差额就是隐含的交易费用,交易费用会算入当前区块的矿工收入中作为矿工奖励的一部分

deal5

计算出的交易费用:

交易费用 = 输入 - 输出 = (2.0 + 1.5) - (2.99 + 0.49) = 3.5 - 3.48 = 0.02

比特币实际的交易记录是由一系列交易构成,每一个交易都包含一个或多个输入,以及一个或多个输出。未花费的输出被称为UTXO(Unspent Transaction Ouptut)。

当我们要简单验证某个交易的时候,例如,对于交易f36abd,它记录的输入是3f96ab,索引号是1(索引号从0开始,0表示第一个输出,1表示第二个输出,以此类推),我们就根据3f96ab找到前面已发生的交易,再根据索引号找到对应的输出是0.5个比特币,所以,交易f36abd的输入总计是0.5个比特币,输出分别是0.4个比特币和0.09个比特币,隐含的交易费用是0.01个比特币:

deal6

再来解释一下上图,防止大家看不懂。上图中每一行都是一次交易,一次交易中input 必须来自过去发生的交易的output (直到追溯到Coinbase交易,即挖矿者创建的铸币交易),UTXO 的值索引过去的某一笔交易中的某个输出地址和钱数,比如说,f36abd此次交易的input 是交易3f96ab中输出给1mPvuPA的0.5个比特币,此次交易输出给16Gr9nB1vg47TL各0.4,0.09个比特币,总输出比输入少了0.01个比特币,这些钱就是交易费用,给矿工了。

交易小结

比特币使用数字签名保证零信任的可靠P2P交易:

  • 私钥是花费比特币的唯一手段;
  • 钱包软件是用来帮助用户管理私钥;
  • 所有交易被记录在区块链中,可以通过公钥查询所有交易信息。

0x3 挖矿原理

在比特币的P2P网络中,有一类节点,它们时刻不停地进行计算,试图把新的交易打包成新的区块并附加到区块链上,这类节点就是矿工。因为每打包一个新的区块,打包该区块的矿工就可以获得一笔比特币作为奖励。所以,打包新区块就被称为挖矿

比特币的挖矿原理就是一种工作量证明机制。工作量证明POW是英文Proof of Work的缩写。

在讨论POW之前,我们先思考一个问题:在一个新区块中,凭什么是小明得到50个币的奖励,而不是小红或者小军?

pow1

当小明成功地打包了一个区块后,除了用户的交易,小明会在第一笔交易记录里写上一笔“挖矿”奖励的交易,从而给自己的地址添加50个比特币。为什么比特币的P2P网络会承认小明打包的区块,并且认可小明得到的区块奖励呢?

因为比特币的挖矿使用了工作量证明机制,小明的区块被认可,是因为他在打包区块的时候,做了一定的工作,而P2P网络的其他节点可以验证小明的工作量。

工作量证明

比特币的工作量证明需要归结为计算机计算,也就是数学问题。工作量证明问题转为如何构造一个数学问题来实现工作量证明。

在比特币网络中,矿工的挖矿也是一种工作量证明,要让计算机实现工作量证明,必须找到一种工作量算法,让计算机无法在短时间内算出来。这种算法就是哈希算法。

通过改变区块头部的一个nonce字段的值,计算机可以计算出不同的区块哈希值:

pow5

直到计算出某个特定的哈希值的时候,计算结束。这个哈希和其他的哈希相比,它的特点是前面有好几个0

hash256(block data, nonce=0) = 291656f37cdcf493c4bb7b926e46fee5c14f9b76aff28f9d00f5cca0e54f376f
hash256(block data, nonce=1) = f7b2c15c4de7f482edee9e8db7287a6c5def1c99354108ef33947f34d891ea8d
hash256(block data, nonce=2) = b6eebc5faa4c44d9f5232631f39ddf4211443d819208da110229b644d2a99e12
hash256(block data, nonce=3) = 00aeaaf01166a93a2217fe01021395b066dd3a81daffcd16626c308c644c5246
hash256(block data, nonce=4) = 26d33671119c9180594a91a2f1f0eb08bdd0b595e3724050acb68703dc99f9b5
hash256(block data, nonce=5) = 4e8a3dcab619a7ce5c68e8f4abdc49f98de1a71e58f0ce9a0d95e024cce7c81a
hash256(block data, nonce=6) = 185f634d50b17eba93b260a911ba6dbe9427b72f74f8248774930c0d8588c193
hash256(block data, nonce=7) = 09b19f3d32e3e5771bddc5f0e1ee3c1bac1ba4a85e7b2cc30833a120e41272ed
...
hash256(block data, nonce=124709132) = 00000000fba7277ef31c8ecd1f3fef071cf993485fe5eab08e4f7647f47be95c

比特币挖矿的工作量证明原理就是,不断尝试计算区块的哈希,直到计算出一个特定的哈希值,它比难度值要小。这里的小是说哈希值看作数值比目标小,举例来说如果目标的是前5位是零,那你找到的哈希值需要小于0x00000fffffff…这就是比特币挖矿的工作量证明方法。

比特币使用的SHA-256算法可以看作对随机输入产生随机输出,这个随机很重要,就是说hash值的每一位都是随机的。例如,我们对字符串Hello再加上一个数字计算两次SHA-256,根据数字的不同,得到的哈希是完全无规律的256位随机数:

hash256("Hello?") = ????????????????????????????????????????????????????????????????

大约计算16次,我们可以在得到的哈希中找到首位是0的哈希值,因为首位是0出现的概率是1/16:

hash256("Hello1") = ffb7a43d629d363026b3309586233ab7ffc1054c4f56f43a92f0054870e7ddc9
hash256("Hello2") = e085bf19353eb3bd1021661a17cee97181b0b369d8e16c10ffb7b01287a77173
...
hash256("Hello15") = 0442e1c38b810f5d3c022fc2820b1d7999149460b83dc680abdebc9c7bd65cae

如果我们要找出前两位是0的哈希值,理论上需要计算256次,因为00出现的概率是16^2=256,实际计算44次:

hash256("Hello44") = 00e477f95283a544ffac7a8efc7decb887f5c073e0f3b43b3797b5dafabb49b5

如果我们要找出前6位是0的哈希值,理论上需要计算16^6=1677万次,实际计算1558万次:

hash256("Hello15583041") = 0000009becc5cf8c9e6ba81b1968575a1d15a93112d3bd67f4546f6172ef7e76

每增加一个0,理论计算量将增加16倍。

对于比特币挖矿来说,就是先给定一个难度值,然后不断变换nonce,计算Block Hash,直到找到一个比给定难度值低的Block Hash,就算成功挖矿。

我们用简化的方法来说明难度,例如,必须计算出连续17个0开头的哈希值,矿工先确定Prev Hash,Merkle Hash,Timestamp,bits,然后,不断变化nonce来计算哈希,直到找出连续17个0开头的哈希值。我们可以大致推算一下,17个十六进制的0相当于计算了1617次,大约需要计算2.9万亿亿次。

17个0 = 16^17 = 295147905179352825856 = 2.9万亿亿次

实际的难度是根据bits由一个公式计算出来,比特币协议要求计算出的区块的哈希值比难度值要小,这个区块才算有效。例如:

Difficulty = 402937298
           = 0x180455d2
           = 0x0455d2 * 2^(8 * (0x18 - 3))
           = 106299667504289830835845558415962632664710558339861315584
           = 0x00000000000000000455d2000000000000000000000000000000000000000000

上述计算过程,0x180455d2是压缩的难度标记,末3字节作为底,前面0x18作为幂,做上述运算得到难度值。

注意,难度值越小,说明哈希值前面的0越多,计算难度越大。

难度调整

比特币网络的难度值是不断变化的,它的难度值保证大约每10分钟产生一个区块,而难度值在每2015个区块调整一次:如果区块平均生成时间小于10分钟,说明全网算力增加,难度值也会增加,如果区块平均生成时间大于10分钟,说明全网算力减少,难度值也会减少。因此,难度值随着全网算力的增减会动态调整。

关于比特币挖矿难度与收益计算可以参考:https://zhuanlan.zhihu.com/p/28805231

比特币设计时本来打算每2016个区块调整一次难度,也就是两周一次,但是由于第一版代码的一个bug,实际调整周期是2015个区块。

根据比特币每个区块的难度值和产出时间,就可以推算出整个比特币网络的全网算力。

比特币网络的全网算力一直在迅速增加。目前,全网算力已经超过了100EH/每秒,也就是大约每秒钟计算1万亿亿次哈希:

wakuang

所以比特币的工作量证明被通俗地称之为挖矿。在同一时间,所有矿工都在努力计算下一个区块的哈希。而挖矿难度取决于全网总算力的百分比。举个例子,假设小明拥有全网总算力的百分之一,那么他挖到下一个区块的可能性就是1%,或者说,每挖出100个区块,大约有1个就是小明挖的。

由于目前全网算力超过了100EH/s,而单机CPU算力不过几M,GPU算力也不过1G,所以,单机挖矿的成功率几乎等于0。比特币挖矿已经从早期的CPU、GPU发展到专用的ASIC芯片构建的矿池挖矿。

wakuang1

当某个矿工成功找到特定哈希的新区块后,他会立刻向全网广播该区块。其他矿工在收到新区块后,会对新区块进行验证,如果有效,就把它添加到区块链的尾部。同时说明,在本轮工作量证明的竞争中,这个矿工胜出,而其他矿工都失败了。失败的矿工会抛弃自己当前正在计算还没有算完的区块,转而开始计算下一个区块,进行下一轮工作量证明的竞争。

为什么区块可以安全广播?因为Merkle Hash锁定了该区块的所有交易,而该区块的第一个Coinbase交易输出地址是该矿工地址。每个矿工在挖矿时产生的区块数据都是不同的,所以无法窃取别人的工作量。

比特币总量被限制为约2100万个比特币,初始挖矿奖励为每个区块50个比特币,以后每4年减半

共识算法

如果两个矿工在同一时间各自找到了有效区块,注意,这两个区块是不同的,因为coinbase交易不同,所以Merkle Hash不同,区块哈希也不同。但它们只要符合难度值,就都是有效的。这个时候,网络上的其他矿工应该接收哪个区块并添加到区块链的末尾呢?答案是,都有可能。

通常,矿工接收先收到的有效区块,由于P2P网络广播的顺序是不确定的,不同的矿工先收到的区块是有可能的不同的。这个时候,我们说区块发生了分叉:

gongshisuanfa1

在分叉的情况下,有的矿工在绿色的分叉上继续挖矿,有的矿工在蓝色的分叉上继续挖矿:

gongshisuanfa2

但是最终,总有一个分叉首先挖到后续区块,这个时候,由于比特币网络采用最长分叉的共识算法,绿色分叉胜出,蓝色分叉被废弃,整个网络上的所有矿工又会继续在最长的链上继续挖矿。

由于区块链虽然最终会保持数据一致,但是,一个交易可能被打包到一个后续被孤立的区块中。所以,要确认一个交易被永久记录到区块链中,需要对交易进行确认。如果后续的区块被追加到区块链上,实际上就会对原有的交易进行确认,因为链越长,修改的难度越大。一般来说,经过6个区块确认的交易几乎是不可能被修改的。

gongshisuanfa2

0x3 挖矿原理小结

比特币挖矿是一种带经济激励的工作量证明机制

工作量证明保证了修改区块链需要极高的成本,从而使得区块链的不可篡改特性得到保护

比特币的网络安全实际上就是依靠强大的算力保障的

比特币网络的难度值是不断变化的,它的难度值保证大约每10分钟产生一个区块

最长分叉共识算法保证了区块链网络中只有一条合法链存在

0x4 更多细节

通过上面的叙述,相信你已经理解了区块链的基本原理和比特币的交易方式。本节讲笼统的叙述一些细节上的概念和原理,不再展开感兴趣的话可以去廖雪峰老师的网站上了解细节。

可编程支付原理

比特币的所有交易的信息都被记录在比特币的区块链中,任何用户都可以通过公钥查询到某个交易的输入和输出金额。当某个用户希望花费一个输出时,例如,小明想要把某个公钥地址的输出支付给小红,他就需要使用自己的私钥对这笔交易进行签名,而矿工验证这笔交易的签名是有效的之后,就会把这笔交易打包到区块中,从而使得这笔交易被确认。

但比特币的支付实际上并不是直接支付到对方的地址,而是一个脚本,这个脚本的意思是:谁能够提供另外一个脚本,让这两个脚本能顺利执行通过,谁就能花掉这笔钱。

比特币的脚本通过不同的指令还可以实现更灵活的功能。例如,多重签名可以让一笔交易只有在多数人同意的情况下才能够进行。最常见的多重签名脚本可以提供3个签名,只要任意两个签名被验证成功,这笔交易就可以成功。

支付的本质

从比特币支付的脚本可以看出,比特币支付的本质是由程序触发的数字资产转移。这种支付方式无需信任中介的参与,可以在零信任的基础上完成数字资产的交易,这也是为什么数字货币又被称为可编程的货币。

由此催生出了智能合约:当一个预先编好的条件被触发时,智能合约可以自动执行相应的程序,自动完成数字资产的转移。保险、贷款等金融活动在将来都可以以智能合约的形式执行。智能合约以程序来替代传统的纸质文件条款,并由计算机强制执行,将具有更高的更低的信任成本和运营成本。

多重签名

由比特币的签名机制可知,如果丢失了私钥,没有任何办法可以花费对应地址的资金。

这样就使得因为丢失私钥导致资金丢失的风险会很高。为了避免一个私钥的丢失导致地址的资金丢失,比特币引入了多重签名机制,可以实现分散风险的功能。

具体来说,就是假设N个人分别持有N个私钥,只要其中M个人同意签名就可以动用某个“联合地址”的资金。

以3开头的地址就是比特币的多重签名地址,但从地址本身无法得知签名所需的M/N。

UTXO模型

比特币的区块链由一个个区块串联构成,而每个区块又包含一个或多个交易。

如果我们观察任何一个交易,它总是由若干个输入(Input)和若干个输出(Output)构成,一个Input指向的是前面区块的某个Output,只有Coinbase交易(矿工奖励的铸币交易)没有输入,只有凭空输出。所以,任何交易,总是可以由Input溯源到Coinbase交易。

还没有被下一个交易花费的Output被称为UTXO:Unspent TX Output,即未花费交易输出。给定任何一个区块,计算当前所有的UXTO金额之和,等同于自创世区块到给定区块的挖矿奖励之和。

钱包的当前余额总是钱包地址关联的所有UTXO金额之和。

如果刚装了一个新钱包,导入了一组私钥,在钱包扫描完整个比特币区块之前,是无法得知当前管理的地址余额的。

那么,给定一个地址,要查询该地址的余额,难道要从头扫描几百GB的区块链数据?

当然不是。

要做到瞬时查询,我们知道,使用关系数据库的主键进行查询,由于用了索引,速度极快。

因此,对区块链进行查询之前,首先要扫描整个区块链,重建一个类似关系数据库的地址-余额映射表。这个表的结构如下:

address balance lastUpdatedAtBlock
address-1 50.0 0

一开始,这是一个空表。每当扫描一个区块的所有交易后,某些地址的余额增加,另一些地址的余额减少,两者之差恰好为区块奖励:

address balance lastUpdatedAtBlock
address-1 50.0 0
address-2 40.0 3
address-3 50.0 3
address-4 10.0 3

这样,扫描完所有区块后,我们就得到了整个区块链所有地址的完整余额记录,查询的时候,并不是从区块链查询,而是从本地数据库查询。大多数钱包程序使用LevelDB来存储这些信息,手机钱包程序则是请求服务器,由服务器查询数据库后返回结果。

总而言之,重建整个地址-余额数据库需要扫描整个区块链,并按每个交易依次更新记录,即可得到当前状态,每当新的块加入区块链,数据库也同时更新。此后,查询余额只需要从数据库查询即可。

0x5 Q&A

  • Q:区块链的安全性由哪些因素决定?

    我认为区块链的安全性主要由以下几部分决定:

    数字签名安全性:采取的ECDSA或其他非对称加密算法的安全性

    工作量证明机制安全性:比如比特币网络中,hash算法的防止碰撞的强度

    区块链网络安全性:存在足够多的“矿工”节点,保证区块不断更新

  • Q:区块链中每个区块都包含那些元素,这些元素是怎么得来的,作用分别是什么?

    以比特币网络为例主要包括:

    父区块hash:=hash(父区块)

    Merkle根:按序hash区块体中的记录

    难度目标:工作量证明机制的目标,计算方法参考0x3-工作量证明

    时间戳:做工作量证明之前估计一个值就可以,要比上一个区块大

    nonce:需要不断变化,计算区块hash,以证明工作量

    区块大小:如名

    区块体:交易记录

  • Q:p2p交易如比特币交易,每个块能存储多少笔交易?

    目前一个比特币区块中约有2000-3000笔交易。我的理解是:具体多少是由比特币网络规定的保证约10分钟打包一个区块,这10分钟产生的交易数量决定的。

  • Q:挖矿的工作量整明,实际上是怎么做的?

    实际上就是通过变换区块头中的nonce的值,计算整个区块的hash值,要求hash值前n位是0,如果计算出来了,你就获得了这个区块的打包权力,就可以向区块链网络发出广播,你挖了一个新区块,这个区块中有一笔交易是给你的钱包添加一笔挖矿奖励,来源就是是挖矿。

    n是难度的象征,比特币网络每挖出2015个区块进行一次难度调整,维持10分钟一个区块的难度。

参考文章:

廖雪峰区块链教程:https://www.liaoxuefeng.com/wiki/1207298049439968

Satoshi Nakamoto:Bitcoin: A Peer-to-Peer Electronic Cash System https://bitcoin.org/bitcoin.pdf

https://www.bitmain.com/

阮一峰数字签名是什么:http://www.ruanyifeng.com/blog/2011/08/what_is_a_digital_signature.html

比特币挖矿难度与收益计算:https://zhuanlan.zhihu.com/p/28805231

https://www.528btc.com/college/58842.html