作者:李雪愚
开源 Bitcoin P2P电子货币是点对点的电子现金系统,创建于2009年[http://en.wikipedia.org/wiki/Bitcoin]。无需金融机构直接点对点支付。该电子货币系统的特色是无需信托中间人,能够方便的进行互联网上的汇款。第三方不能够控制或者阻止您的交易。Bitcoin 交易几乎免费, 而信用卡的网上在线支付系统通常收取 1-5% 的交易费用,加上其他各种费用高达数百美元。避免了中央储备银行的不良政策和不稳定性所造成的安全隐患. Bitcoin系统的有限货币通胀是均匀分布(由CPU决定)于整个网络, 而不是由银行垄断。
这被某些人认为是最危险的开源项目,但我觉得恰恰相反,这是有史以来最令人兴奋的开源项目。这样发行的币才是真正的“人 民 币”,人民才是被服务,而不是被管理。P2P必将从商业、政治、生活各个方面重新定义新的更加公平公正的意识形态。
名词解释以及背后的技术
Bitcoin涉及到很多有意思的技术,要想搞懂bitcoin首先必须对这些技术理解和把握然后是对Bitcoin术语的理解,bitcoin最重要的技术支撑是P2P,数字签名(EC DSA),散列(SHA256, RIPEMD-160), POW,和HashCash。而术语则是:transaction, block, address, Merkle Tree.
工作证明 POW(Proof-Of-Work)机制
工作证明POW系统是要求对方服务前,必须要出据某种工作证明的机制。主要用于防止拒绝服务攻击和反垃圾信息。通常这种“工作证明”会花费一定的时间计算才能得到。最常见的例子是CAPTCHA。另外用于防止DoS和垃圾信息的机制是HashCash,bitcoin使用的原理就类似于HashCash.
HashCash 机制
hashcash 的灵感来自于这样一个想法,即一些数学结果难于发现而易于校验。一个众所周知的例子是因数分解一个大的数字(尤其是因数较少的数字)。将数字相乘来获得它们的积的代价是低廉的,但首先找到那些因数的代价却要高得多。
对交互式质询来说,因数分解足以胜任。比如,希望客户端能象征性地为其付出代价方能访问在线资源。这个时候可以定义协议,首先服务器向客户端发送一个消息,说“只要您能因数分解这个数,我将让您得到这个资源”。没有诚意的客户端将无法得到我的资源,只有那些能够证明自己有足够的兴趣、付出一些 CPU 周期来回答这个质询的才能得到这个资源。
不过,有一些资源无法很方便地进行交互式协商。比如电子邮件反垃圾或者支付交易,怎么才能避免邮箱不被垃圾邮件所占据?“我并不介意陌生人给我写信,但是,我希望他们能以稍微认真的态度,亲自通过对我有价值的邮件与我取得联系。至少,我不希望他们是垃圾邮件制造者,那些人向我和上百万的其他人发送包含同样消息的邮件(double-spending),期望我们中的某些人能购买某种产品或者落入一个骗局。”而对于电子货币,内容的复制几乎是没有代价的,如何保证电子货币(内容)没有被交易(发送)多次?这和反垃圾邮件是同样的问题。
hashcash的解决之道就是:在电子邮件的消息头中,增加一个 hashcash 戳记(hashcash stamp)散列值;该散列中包含收件人地址,发送时间,salt,该散列值特别之处在于它至少前20位必须是0才是一个合法的hashcash戳记。为了得到合法的散列值,发送者必须经过许多次尝试(改变salt值)才能获得。一旦生成戳记,不希望每一个给我发送邮件的垃圾邮件制造者都能重复使用它。所以,hashcash 戳记要带一个日期。这样可以指定时间更早的戳记是非法的。另外 hashcash 的接收端要实现一个double-spending数据库,用来记录戳记的历史信息。
Bitcoin中的术语解释
散列:bitcoin在计算散列时一般会计算2次。第一轮总是使用SHA-256散列,而第二次在大多数情况下,也是使用SHA-256散列,但用于生成较短的散列(例如生成 bitcoin接收地址的时候)会用RIPEMD-160散列。
hello
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 ( sha-256)
9595c9df90075148eb06860365df33584b75bff782a510c6cd4883a419833d50 (sha-256)生成比特币地址时(RIPEMD-160):
hello
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 (sha-256)
b6a9c8c230722b7c748331a8b450f05566dc7d0f (ripemd-160)Addresses: Bitcoin Address是ECDSA公钥(public key)的散列,计算方法如下:
if 正式网络
Version=0x00
else if 测试网络
Version=0x6F
end
key_hash=Version+RIPEMD-160(SHA-256(public_key))
Checksum = SHA-256(SHA-256(Key hash))[0..3] #取两次散列值后的前4位
Bitcoin_Address = Base58Encode(Key_hash+Checksum)
#Base58编码做了特殊处理,与通用的base58有一些区别:转换的时候 #前面的0被作为单个0保留。
Bitcoin 电子货币系统的核心功能是面对面(点对点)支付,就像真实货币一样,无需中间人,几乎不需要交易费。它背后的技术实现是很巧妙的通过将造币(Mint),交易(transaction,这里提到的交易都是特指的支付),交易验证(transaction verifiction)交织在一起,形成一个完美的圆。所以要想把它的机理理解清楚,就必须同时理解bitcoin的电子货币,交易,造币的确切含义。
首先从电子货币谈起。
电子货币和交易单(Transaction)
Bitcoin电子货币解决的是:
任意造币的问题:通过“挖矿”机制保证了不能任意造币。
重复花钱的问题(Double-Spending):利用P2P网络,通过HashCash的机制。
事实上Bitcoin系统中不存在独立的电子货币,货币值是依附于交易单存在的,所以在bitcoin中的电子货币的实质就是交易单,确切的说是货币交易(Transactions)的 数字签名链,它的数字签名算法使用的是ECDSA(椭圆曲线数字签名算法 secp256k1曲线)进行签名的。
交易单的数据如下:
In:
Previous tx: f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd0
4470b9a6
Index: 0
scriptSig: 304502206e21798a42fae0e854281abd38bacd1aeed3ee3738d9e1446
618c4571d1090db022100e2ac980643b0b82c0e88ffdfec6b64e3e6ba35e7ba5fd
d7d5d6cc8d25c6b241501
Out:
Value: 5000000000
scriptPubKey: OP_DUP OP_HASH160 404371705fa9bd789a2fcd52d2c580b65d35
549d OP_EQUALVERIFY OP_CHECKSIG
交易单记录的是本次交易的收入来源(in)和支出(out)。当你支出(给)一笔钱的时候,首先在交易单中就要描述清楚你要支出(out)的钱的收入来源(in),然后在支出(out)项中,指明要支出的金额,以及通过脚本的形式写明接收者的公钥,然后用自己的私钥签名(scriptSig)认可该笔交易,最后将交易单广播到网络。
收入来源(in):
Previous tx: 为收入来源交易单的散列值,也就是待支付的钱是谁给你的,经常会有多个收入来源被列在交易单中
index: 指明是收入来源交易单中具体哪一个out,也就是Previous tx交易单中的out索引值(因为out也可以有多个)。
scriptSig: 拥有者对该交易的ECDSA签名认可。
接收对象(out):
Value: 发送的币值,以Satoshi 为单位,1BTC = 100,000,000 Satoshi
scriptPubKey: 接收方的公钥脚本。
in与out的关系:
每一笔交易,out的总额应该等于in的总额。但是,在这个交易单里,只会有out的Value,没有in的Value,而是通过in的Pervious与index,追溯到上一个交易单的某一个out,获得Value。
一次send bitcoin,剩下的钱,应该out给自己,否则这个钱就丢了。
情况列举:
我有10个BTC,是某一次交易获得的,我要送给朋友A,10个BTC。这时候,有一个in,一个out。
我有10个BTC,是某一次交易获得的,我要送给朋友A,5个BTC,这时候,有一个in,两个out,一个指向朋友5个BTC,一个指向我自己,得到剩下的5个BTC。
我有10个BTC,是以前的两次交易获得的,我要送给朋友A,10个BTC,这时候,有两个in,一个out。
我有10个BTC,是以前的两次交易获得的,其中一次获得了6个BTC,另一次获得了4个BTC,我要送给我的朋友7个BTC,这时候,有两个in,两个out。
//
// An input of a transaction. It contains the location of the previous
// transaction's output that it claims and a signature that matches the
// output's public key.
//
class CTxIn
{
public:
COutPoint prevout;
CScript scriptSig;
unsigned int nSequence;
....
}
//
// An output of a transaction. It contains the public key that the next input
// must be able to sign with to claim it.
//
class CTxOut
{
public:
int64 nValue;
CScript scriptPubKey;
....
}
//
// The basic transaction that is broadcasted on the network and contained in
// blocks. A transaction can contain multiple inputs and outputs.
//
class CTransaction
{
public:
int nVersion;
std::vector
vin;
std::vector vout;
unsigned int nLockTime;
....
}
每一笔交易单验证追查到最后,第一笔总是“挖矿”所得,这被称为coinbase。
如果是第一次“挖矿”所得,电子货币的内容用JSON格式表示如下:
{
"hash":"c9e0c111366cad02a123e84736fda6619bc95edace36dc57ed3ef33843fd7adb",
"ver":1,
"vin_sz":1,
"vout_sz":2,
"lock_time":0,
"size":257,
"in":[
{
"prev_out":{
"hash":"374f2b746630f2d5310af4d040215a2aa3abe74d1f5a5f5d2345e656149ff177",
"n":0
},
"scriptSig":"30440220347e400427945dbfe70fc42c5518d41c5ffffd79dbc60e037992d2073f9accdc022073a d05a381c84db99cd4147f98717606d79c213a206f1790522f4a0dd688d2b201 04e81438beeb99f709b4c7396e93db588a3db8463d6da8e31f82097650e2002cd634e7836336e53 00f9ba25b488c6565cbcae7e10eaf4bdd3f78d648d6818fc0ca"
}
],
"out":[
{
"value":"309.15753855",
"scriptPubKey":"OP_DUP OP_HASH160 6cee814064fe6548cc8201532a0bf64dff08fcf8 OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value":"0.04000000",
"scriptPubKey":"OP_DUP OP_HASH160 e8168f1fc661511db105709d281d588eaa26a486 OP_EQUALVERIFY OP_CHECKSIG"
}
]
}
每一笔交易都会向整个P2P网络广播该货币的交易记录。通过投票机制,来决定该支付交易是否正常。如节点认为该交易记录是正常的那么就通过CPU计算POW(Proof-of-Work),然后广播,其它节点收到这个POW可以继续投票,形成Block 链(见挖矿)。如果节点收到不一致的两个交易记录,那么只信任链最长的。如果一笔Bitcoin被支出两次的情况广播出来,那么某些节点将先看到它第一次发生的支付交易,其他节点则看到的是它第二次发生的支付交易。究竟是哪一个支付交易“赢”了,则是由恰好创建了下一个block的那个节点来决定 —— 无论是哪个节点找到了“小的散列值”, 它的block中包含的那个支付交易被判断为有效的,其他的支付交易被视为无效。
前面讲了交易单,接下来讲讲交易单的类型和交易验证的实现细节。
交易的类型与验证
交易单(Transaction)目前被bitcoin分为3种:
按IP地址转账(Transfer)
按接收地址转账(Transfer)
造币(Generation)
通过前面对交易单的描述,我想大家应该已经知道,交易的验证是通过不断的追溯交易单来完成的。那么交易验证的细节是如何实现的,bitcoin的交易验证的实现细节是很意思的,它是通过脚本来实现验证的,这样bitcoin就实现了一个可被脚本扩展的行为,更加广义的”交易”,这可以保证足够的灵活性和可扩展,可以想象也许以后会有真正的分布式计算任务跑在上面作为一笔交易。因此要想讲清楚验证的细节,就不得不从脚本系统讲起了。
脚本系统
Bitcoin的脚本系统是使用类FORTH语言(FORTH在嵌入式设备中是我最喜欢的一种语言)实现的,FORTH系统是面向栈的“高级机器”语言,它兼具了高级语言的特性(面向WORD)和机器语言的高性能与小尺寸,唯一的缺点就是可读性不高,基本上可以把FORTH当一种高级的机器语言吧。Bitcoin在实现上做了许多简化和特殊实现,所以只能算是类FORTH的系统。
在FORTH系统中所有的命令(函数)都被称为单词(WORD),所有数据的处理都要压入数据栈上进行,所以FORTH被称为面向栈的语言。
比如 1和2相加,在FORTH系统中则是 1 2 +,数字 1,2 表示将它们压入到数据栈中(在FORTH解释器执行后实际指令会有OP_PUSH),+的操作则是将栈顶和次栈顶的数字取出并相加,并将结果压回数据栈。
Bitcoin的脚本系统的所有的操作指令(单词)见后面的附录,不是所有的操作指令都能被执行支持,因为安全上的考虑有的操作指令被禁止掉了,比如大多数的字符串操作和乘除运算。
在FORTH系统中一般会定义该系统是8位FORTH, 16位FORTH还是32/64位FORTH,这和数据栈的值长有关。在Bitcoin中的脚本系统使用的堆栈是用(vector >)来模拟的,它在数据栈的每一项都是以字符串的形式存放,它的数值是统一用CBigNum来运算的(读到这里应该明白为啥bitcoin会限制乘除运算了),每一次从数据栈中压入(CBigNum.getvch())或者取出数值(CastToBigNum(stacktop[-1]))都要进行一次转换。看着这样的实现,让我有点发怵,本能的不喜欢,怎么能把高效的FORTH搞成这个样子。Bitcoin的脚本系统与FORTH一样,有两个堆栈,一个数据栈,在Bitcoin中被称为main stack,一个返回栈,在Bitcoin中被称为alt stack(事实上在Bitcoin中将alt stack作为数据栈的中转而还没有作为FORTH意义上的返回栈). Bitcoin的脚本系统没有实现单词定义,流程跳转,循环控制(FOR, WHILE),对IF的实现非常丑陋。事实上我对Bitcoin的脚本实现不是十分认可,堆栈采用变长的数据值,直接将big number 作为opcode,导致每一次opcode执行开销无法控制,由于没有完整实现FORTH的控制指令,导致用trick的方式来实现IF指令,在bitcoin中也因此无法定义新的WORD。总之我很不喜欢bitcoin的这个脚本引擎实现(参见script.cpp)。
下一篇将接着介绍下和交易验证相关的几个操作符:OP_DUP, OP_HASH160, OP_EQUALVERIFY , OP_CHECKSIG 的功能,现在有点倦了。
附录Bitcoin脚本系统单词列表
参见:https://en.bitcoin.it/wiki/Script
Ok,接着描述交易验证的具体的过程。
涉及的脚本命令(操作符)
首先讲讲涉及到的脚本命令(操作符),描述栈顶项的变化方式如下:(n1 n2 — ) 用 “–”分隔操作符执行前后的栈中元素的变化,左边表示操作前,右边表示操作后。最右边的项表示栈顶元素。这里例子中,n2 表示栈顶,n1表示次栈顶,操作后n1,n2均被弹出。
OP_EQUAL(n1 n2 — true/false): 判断栈顶两项是否相等的脚本命令,如果相等则压入true,否则false
OP_VERIFY(true/false — NOTHING/false): 如果栈顶项为true,标记改交易单为有效,否则失败,压入false并返回
OP_CODESEPARATOR( — ): 将当前执行PC(ProgramCount)保存到变量: pbegincodehash
OP_CHECKSIG( – true/false): 校验收入来源(in)的签名是否有效。栈顶是公开密钥(PubKey),次栈顶是签名(Sig)。如果验证通过会压入True,否则压入False。
OP_EQUALVERIFY(n1 n2 — NOTHING/false): 等于 OP_EQUAL OP_VERIFY 两个操作符的联合。
交易验证
以最常见的接收地址交易转账为例描述交易验证的过程(SendMoneyToBitcoinAddress)
首先示例交易如下:
{
"hash":"c9e0c111366cad02a123e84736fda6619bc95edace36dc57ed3ef33843fd7adb",
"ver":1,
"vin_sz":1,
"vout_sz":2,
"lock_time":0,
"size":257,
"in":[
{
"prev_out":{
"hash":"374f2b746630f2d5310af4d040215a2aa3abe74d1f5a5f5d2345e656149ff177",
"n":0
},
"scriptSig":"30440220347e400427945dbfe70fc42c5518d41c5ffffd79dbc60e037992d2073f9accdc022073a d05a381c84db99cd4147f98717606d79c213a206f1790522f4a0dd688d2b201 04e81438beeb99f709b4c7396e93db588a3db8463d6da8e31f82097650e2002cd634e7836336e53 00f9ba25b488c6565cbcae7e10eaf4bdd3f78d648d6818fc0ca"
}
],
"out":[
{
"value":"309.15753855",
"scriptPubKey":"OP_DUP OP_HASH160 6cee814064fe6548cc8201532a0bf64dff08fcf8 OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value":"0.04000000",
"scriptPubKey":"OP_DUP OP_HASH160 e8168f1fc661511db105709d281d588eaa26a486 OP_EQUALVERIFY OP_CHECKSIG"
}
]
}
ScriptSig( ): 中有两个值,一是当前拥有者的该交易单的签名,二是拥有者的公开密钥(用来验证签名)。
Sig:
30440220347e400427945dbfe70fc42c5518d41c5ffffd79dbc60e037992d2073f9accdc022073a d05a381c84db99cd4147f98717606d79c213a206f1790522f4a0dd688d2b201
Pubkey:
04e81438beeb99f709b4c7396e93db588a3db8463d6da8e31f82097650e2002cd634e7836336e53 00f9ba25b488c6565cbcae7e10eaf4bdd3f78d648d6818fc0ca
脚本执行验证过程:
执行 scriptSig:
压入 和 :
执行 scriptPubKey:
OP_DUP 复制栈顶:
OP_HASH160 对栈顶项做HASH160(RIPEMD-160(SHA256())散列:
: 压入
OP_EQUALVERIFY: 对栈顶两个元素 做是否相等校验,如果不相等那么标记交易单无效并返回,如果等于继续:
OP_CHECKSIG: 校验收入来源的In的签名是否有效,根据pubKey去校验签名,签名的内容为交易单的Ins, Outs和脚本(从最近一次的 OP_CODESEPARATOR 到脚本结束的位置)的散列。 PubKey.Verify(SignatureHash(scriptCode, nIn, Outs), Sig)
代码略.
挖矿与块(Blocks)
接下来讲的是什么是挖矿,以及交易单在P2P网络上的存放,其实这个过程也是挖矿的过程。
所有的交易单是以Block的形式在Bitcoin P2P网络上存放的。每一个Block包含了最近所有的有效交易单。Block中最为重要数据为:
最近交易单集合
Nonce随机数
前一个块的散列值
在造币的Node总在侦听网络,接收信息,然后各自将接收到的新交易打包到一个新block(CBlock Class in main.h)。在块交易单集合中的第一个交易单总是“造币单”:给创建该块的人以新的货币,节点只需要校验货币金额是否合法即可:形成一个合法的新块得到的回报是初始金额为50BTC,每产生210,000块的时候这个金额减半(大约每隔4年会发生一次)。产生合法的新块是不容易的,需要通过无数次的计算尝试得到,另外如果大家都在计算同一个新块,那么还存在竞争关系,这个时候只有最困难(Difficulty)的那个块(指整个块链的困难度值加起来最大的)才会被网络认可。另外如果块中的某笔交易size大于该块中交易单的平均size,那么该笔交易单会被收取一笔小的交易费用。
块的数据结构
块的数据结构可以分为块头(block header)和块体(block body)
块头(Block Header)
块头的数据是用于校验块、产生块ID(256位散列值),这个块ID也是决定块是否合法的数字。计算块ID的公式如下:
?1 BlockID = SHA256(SHA256(Block_Header))
块头(Block Header)的内容如下:
ver: 版本号(4字节)
prev_block: 前一个块的256位散列值
mrkl_root: 所有交易单的256位散列
time: 时间戳(4字节)
Bits: 一个紧凑格式的4字节的数字,它表示一个256位的当前目标(Target)数值,这也表示困难度(Difficulty),最后产生块的ID值必须不大于目标值才能被接受(如果有多人同时产生同一个块,那么最困难的将被接受)。这个数值会每隔2016个block(网络大约每小时创建6个块,创建2016块大约2周)调整一次,当产生前一个2016块的时间大于2周的时候,难度值将会被调低,当小于两周的时候难度值将被调高(关于Target和Difficulty详见后述)。
nonce: 32位随机数。节点在产生Block的时候,会增量尝试改变nonce的值,直到产生的块的散列值符合bits的要求
计算目标(Target)和困难度(Difficulty)
计算目标(Target)
计算目标(Target)是一个256位的数值,块的散列值ID必须小于或等于该目标数值,放可被接受。块头中的BITS是一个用紧凑的4字节格式表示的目标(Target)数值,它表示一个256位的当前目标数值,方式如下:
//假设BITS为:
BITS = 0x1B0406CC
//那么target的数值则是:
Target = 0x0406CC * 2^(8*(0x1B-3))= 0x00000000000406CC000000000000000000000000000000000000000000000000
当前网络的目标值为:Current target。
困难度(Difficulty)
困难度(Difficulty)表示该块ID的计算难度,值越大表示越困难,1表示最容易。
1困难度(Difficulty)的Target被定义为:
0x1D00FFFF(BITS)
因此困难度的计算公式为:1 困难度(Target) / 当前目标
我们可以在这里查看当前网络的困难度:Current difficulty(BitCoin的 getDifficulty 接口输出)。也可以通过这个网站查看困难度值的变化情况图:Graphs。
块体(Block Body)
Block Body 实际上就是交易单的集合。
你可以在这个网站上看到当前网络所有的交易单(Blocks): https://blockexplorer.com/