比特币源码解析:认证与交易

写在前面

    花了一周多的时间看比特币0.1.0的源码,到现在基本是都看完了,虽然不能保证所有地方都统统了解,但至少能够理论界和实际。从书本上看得理论,基本都能在源码中的到印证。

    这篇文章是只针对比特币交易部分的解析。因为结合着源码讲述,读者观看请自备源码。源码内容也不是特别详细,基本只列出关键函数和关键步骤。后面马上要做别的项目了,比特币的其他部分解析不知道还有没有机会补上。文档是在word中写好的,直接粘过来。

1.密钥与比特币地址的生成

公钥加密,私钥解密;私钥签名,公钥验签。

上面是比特币加密与认证的本质,然而本文不讲述公私钥原理,只讲述比特币原码具体是如何使用这种机制实现加密与认证的。欲了解请先查看相关资料。

1.1公私钥的生成

首先比特币用户的公私钥信息存储在globle 变量keyUser中:
 

CKey keyUser; // 当前用户公私钥对信息

CKey类有以下几个函数: 


MakeNewKey();//生成一对公私钥

GetPubKey();//返回公钥

GetPrivKey()//返回私钥

 

公钥和私钥都是256位的数字串。私钥是用某种安全的方式随机获得的,本质就是一个256位的随机数。公钥是将私钥通过一个椭圆曲线乘法(K = k * G ,其中k是私钥,G是被称为生成点的常数点,而K是所得公钥))的算法计算得来,这些都会在MakeNewKey()这个函数中实现,我们只需要知道,这种运算不可逆。

即由私钥可以推出公钥,反之不然。

一个私钥可以生成多个公钥。

比特币程序运行后,程序会先从wallet.dat数据文件中加载钱包数据信息,其中就包括密钥信息,将密钥信息放入两个map表中:

map, CPrivKey> mapKeys;

// 公钥和私钥对应的映射关系,其中key为公钥,value为私钥

map > mapPubKeys;

// 公钥的hash值和公钥的关系,其中key为公钥的hash值,value为公钥

 

 

1.2比特币地址的生成

比特币的钱包地址由公钥进行hash160加密生成,同样可以有多个,公钥得到钱包地址的过程如下

Inline string PubKeyToAddress

(constvector& vchPubKey)

{

    return Hash160ToAddress(Hash160(vchPubKey));

}

Hash160加密“先SHA256加密后RIPEMD160加密”的简称,也就是进行可两次加密。之后得到一个160位的数字。

然而这还没完,为了更加简洁方便的表示长串数字,比特币又调用Hash160ToAddress对该数字进行base58编码,从而获得了我们常常看到的比特币地址,例如:

1JyShDpyqafQ88EaLvUQdhajKCzYG4zxd9

总结比特币地址的生成过程如下图所示

比特币源码解析:认证与交易_第1张图片

2交易的生成与校验

2.1比特币交易的原理

比特币的交易方式与传统的交易方式相比有着非常明显的不同。传统的交易方式是数据库式的。比如你从网上买了一件衣服,则从你的支付宝账户中减去这个衣服的金额。支付宝账户代表数据库。而比特币交易方式则不同。比特币的交易是记账式的。比特币值记录每笔交易历史,而不专门存储余额数据。

所以比特币的本质就是一个分布式账本,每个人的账本都相同,都记录着所有的交易历史。

然而只有交易的记录,比特币是如何将确定金钱的归属的呢?

下面就要讲述比特币交易账本的实现方式。如下如图

比特币源码解析:认证与交易_第2张图片

 

比特币的每笔交易包含输入和输出两部分,输入和输出都可以使多个。每笔交易的输入都来自其他交易的输出。输入表明了这笔交易金额的具体来源,而输出表明的了金额的去向。

特别的,创币交易,也叫币基交易,就是矿工打包块所得奖励的交易的输入为空,因为这笔交易的金额是“凭空产生”的。

除去币基交易,每笔交易的输入金额之和都应等于输出金额之和。

那么,比特币究竟是怎么保证属于我们的比特币只有我们可以使用呢?

一笔交易刚刚生成时,我们可以把它的输出比作存钱的保险箱。这些保险箱被主人的锁给锁住了。这个锁只有交易的输出目标能够解锁,也就代表只有拥有解开锁的钥匙的人可以打开这个箱子,相当于这个人拥有这笔财产

如果一笔交易的某个输出没有作为另一个交易的输入,也就是没有被打开过,则这笔交易就是UTXO(未花费交易输出)。

一个比特币用户的钱包中会存放若干个钥匙,这把钥匙能打开的锁的交易输出都属于该用户。

以上就是交易的基本原理,下面结合代码具体看比特币如何实现上述过程的。

2.2 CTransaction类

比特的交易信息存储在CTransaction类中,其内容是:

 int nVersion; // 交易的版本号,用于升级

 vector vin; // 交易对应的输入

 vector vout; // 交易对应的输出

 int nLockTime; // 交易对应的锁定时间

重要的是vin和vout两个成员,分别记录了这笔交易对应的输入与输出信息。一笔交易的输入和输出都可能是多个,所以用vector定义。

CTxIn的内容:

 COutPoint prevout; // 该输入的来源

 CScript scriptSig; // 输入脚本对应的签名

 unsigned int nSequence;// 主要是用于判断相同输入的交易哪一个更新,值越大越新

其中COutPoint的定义如下:

 uint256 hash; // 交易对应的hash

 unsigned int n; // 交易对应的第几个输出

也就说交易中包含了输入究竟来哪个交易的哪个输出的具体信息。

CTxOut的内容:

 int64 nValue; // 交易输出对应的金额

 CScript scriptPubKey; // 交易对应的公钥

 

2.3交易的发起

比特币源码解析:认证与交易_第3张图片

 

 

比特币源码解析:认证与交易_第4张图片

上面是比特币钱包软件的界面。当点击第一张图中的Send Coin按钮就会弹出第二张图所示的窗口。填写目标地址和余额后即可发起交易。在源码中,对应发起交易的函数是:

bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew);

这个函数的第一个参数是有比特币钱包地址构成的脚本数据,后续会给出具体描述。第二个参数是这笔转账的金额,第三个变量是个返回值变量。

这个函数的核心就是运行下面这个函数

 

前三个参数继承自上面的函数,第四个函数也是个返回值变量,代表的是交易费。

CreateTransaction主要内容是搜索可用币,填写输出信息和输入信息。

搜索可用币:

SelectCoins(int64 nTargetValue, set& setCoinsRet)

根据参数一的金额,去寻找能够凑够该金额的相关交易

该函数的内容就会从头检索mapWallet表,这个表中记录着所有输出包括自己的交易。从这交易中挑选出可用的交易输出并且加起来大于目标金额。

 

填写交易输出:

wtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey));

这部分很简单,就是把输出的金额和接收方提供的锁作为参数填写输出。不过有时候我们搜集到的输入币值总和可能大于要发送的金额,这就需要找零。也就在输出中加一条目标是自己的输出:

wtxNew.vout.push_back(CTxOut(nValueIn - nValue, scriptPubKey));

填写交易输入:

foreach(CWalletTx* pcoin, setCoins)

     for (int nOut = 0; nOut < pcoin->vout.size(); nOut++)

          if (pcoin->vout[nOut].IsMine())

             wtxNew.vin.push_back(CTxIn(pcoin->GetHash(), nOut));

遍历之前搜集到的交易的集合,将具体的哪个交易的哪个输出填写到vin里面去。

交易签名:

调用

SignSignature(*pcoin, wtxNew, nIn++);//对每笔输入交易进行签名

在这个函数中会调用

Solver(txout.scriptPubKey, hash, nHashType, txin.scriptSig)
//验证自己是否具有打开这个公钥的私钥

如果成功,就会获得这个输入的签名txin.scriptSig。

下面是整体流程:

 

2.4签名与认证

 

首先我们要知道一个签名和验签的流程,A有一对密钥,公钥为m,私钥为n,私钥只有A持有,公钥则所有人拥有。A要给B发送密文x,那么A会用私钥m对密文进行签名,并向B发送签名和密文,B收到后用公钥对签名进行处理,若处理后与密文相同则代表签名者为A。

 

比特币源码解析:认证与交易_第5张图片

 

上节讲述了交易发起的整体流程,但是对细节,我们还不是很清楚,尤其是scriptSigscriptPubKey这对脚本究竟是怎么产生和怎么运作的。

scriptPubKey << OP_DUP << OP_HASH160 << hash160 << OP_EQUALVERIFY << OP_CHECKSIG;

在发送比特币的代码中,scriptPubKey 被这样填充。其中hash160是解码后的比特币钱包地址。而OP_DUP这些代表的是操作。

可以理解为,这个scriptPubKey就是归属者给交易上的锁,scriptSig就是钥匙。用户想要使用一笔交易的某个输出,就需要提供能够解锁scriptPubKey的交易来供所有人验证。

那么scriptSig究竟是怎么得到呢?scriptSig实际上是比特币用户使用自己的私钥对交易进行的签名。

主要调用SignSignature这个函数:

在这个函数中首先执行:

uint256 hash =SignatureHash(scriptPrereq + txout.scriptPubKey, txTo, nIn, nHashType);

这个函数对只有某个输入的交易数据进行hash运算并返回hash值(感觉这里很奇怪)

之后执行:

Solver(txout.scriptPubKey, hash, nHashType, txin.scriptSig)

{

......

scriptSigRet << vchSig << vchPubKey;

// 除了 sig 外 还要把 pubkey 也添加进入scriptsig中 // 这里就是生成答案的地方

......

}

 

先验证自己是否拥有该对应该公钥的私钥,若有,则用私钥对上面得到的hash进行签名,并将该签名填入到txin成员里。

这就是scriptSig的获得方式。

 

对于签名的认证,主要是执行VerifySignature这个函数

而该函数主要执行的是

EvalScript(txin.scriptSig + CScript(OP_CODESEPARATOR) +

txout.scriptPubKey, txTo, nIn, nHashType)

也就是解析脚本。

EvalScript函数的运行方式是这样的:

首先创建一个stack(栈),其实就是个vector。

stack.push_back(vchPushValue);

先将scriptSig部分压入栈中,然后首先执行scriptPubKey部分:

每读取scriptPubKey中的一个操作符,就执行一次。可以把其当作解释形语言的形式,读取一条执行一条,具体流程如下:

 

比特币源码解析:认证与交易_第6张图片

上面图片摘自链接https://zhuanlan.zhihu.com/p/27512347,多谢!

公钥验证可以初步验证身份

 由上图我们就知道,当执行到OP_CHECKSIG操作符时,代表对签名进行验证。

查看EvalScript中的代码,其中主要是swith case结构,根据操作符对应操作。

EvalScript(...)

{

Switch(opcode)

{

...

Case OP_CHECKSIG:

case OP_CHECKSIGVERIFY:

{

...

bool fSuccess = CheckSig(vchSig, vchPubKey, scriptCode, txTo, nIn, nHashType);

...

}



...

}

}

CheckSig就是认证函数,它的参数分别是对交易hash的签名,私钥对应的公钥与公钥脚本...进入该函数我们看到:

 

key.Verify(SignatureHash(scriptCode, txTo, nIn, nHashType), vchSig)

 

这里我们再次看到了SignatureHash,这个函数,这个函数会返回交易对应的hash值,而Verify便会验证,验证过程是,用公钥对签名进行运算得到的hash值是否和这里用SignatureHash运算得到的hash相同,如果相同,则代表签名有效。

我们假定私钥签名为函数S,公钥验签函数为V,交易为tx,hash函数为H有:

V(S(H(tx))) = H(tx)

下图可直观看出整个交易的签名和验证过程

 

需要指出的是,这里面的tx只是代指,但并不是整个交易,一个交易有很多输入,tx只是有某个输入的被拆分后的交易。

比特币源码解析:认证与交易_第7张图片

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(比特币,c++)