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

写在前面

花了一个多星期的时间来查看Bitcoin0.1.0的源代码,现在我基本上已经阅读了。尽管不能保证理解所有内容,但至少在理论上和实践上都是可以的。这些书中的理论基本上可以在源代码中得到证实。

本文仅用于分析比特币交易。因为它与源代码结合在一起,所以读者应该准备源代码以供查看。源代码的内容没有特别详细,基本上只列出了关键功能和关键步骤。不久将有其他项目要做。我不知道是否有机会弥补对比特币其他部分的分析。该文档是用文字写的并直接粘贴。

1.密钥和比特币地址生成

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

以上是比特币加密和身份验证的本质。但是,本文没有描述公钥和私钥的原理。它仅描述原始比特币代码如何使用此机制来实现加密和身份验证。要了解,请先检查相关信息。

1.1生成公钥和私钥

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

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

CKey类具有以下功能:


MakeNewKey();//生成一对公私钥
GetPubKey();//返回公钥
GetPrivKey()//返回私钥

公钥和私钥均为256位数字字符串。私钥以安全的方式随机获得,并且本质上是256位随机数。公钥通过椭圆曲线乘法计算(K = k * G,其中k是私钥,G是称为生成点的常数点,而K是生成的公钥)。这些将在功能MakeNewKey()中实现,我们只需要知道此操作是不可逆的即可。

公钥可以从私钥派生,反之亦然。

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

Bitcoin程序运行后,该程序将首先从wallet.dat数据文件中加载钱包数据信息(包括密钥信息),并将密钥信息放入两个映射表中:

map, CPrivKey> mapKeys;

//公钥与私钥之间的映射关系,其中key为公钥,value为私钥

map > mapPubKeys;

//公钥和公钥的哈希值之间的关系,其中key是公钥的哈希值,value是公钥

1.2生成比特币地址

比特币的钱包地址是通过使用公钥进行hash160加密生成的。也可以有多个。通过公钥获取钱包地址的过程如下

Inline string PubKeyToAddress
(constvector& vchPubKey)
{
    return Hash160ToAddress(Hash160(vchPubKey));
}

Hash160加密是“首先进行SHA256加密和RIPEMD160加密”的缩写,这意味着可以将其加密两次。然后获取一个160位数字。

但是,这还没有结束。为了更简洁,方便地表示一长串数字,比特币将Hash160ToAddress调用为base58对数字进行编码,从而获得我们经常看到的比特币地址,例如:

1JyShDpyqafQ88EaLvUQdhajKCzYG4zxd9

下图显示了比特币地址生成过程的摘要

比特币交易网站源码

2交易的生成和验证2.1比特币交易的原理

比特币的交易方式与传统交易方式有很大不同。传统的事务处理方法是数据库类型。例如,如果您在线购买一件衣服,那么衣服的金额将从您的支付宝帐户中扣除。支付宝帐户代表数据库。比特币交易方法不同。比特币交易是簿记。比特币价值记录每笔交易的历史记录,而无需专门存储余额数据。

因此,比特币的本质是分布式分类帐,每个人的分类帐都相同,并且记录了所有交易历史。

但是,只有交易记录,比特币如何确定货币所有权?

以下将描述比特币交易分类账的实现。如下图所示

比特币交易网站源码

每个比特币交易都包含两个部分:输入和输出。输入和输出都可以是多个。每个事务的输入来自其他事务的输出。输入指示交易金额的特定来源,输出指示交易金额的目的地。

特别是,造币交易(也称为基于货币的交易)是矿工打包的区块和奖励没有输入的交易,因为该交易的金额是“凭空产生的”。

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

那么,比特币如何保证属于我们的比特币只能使用?

当刚刚生成交易时,我们可以将其输出与可以存钱的保险箱进行比较。这些保险箱被所有者的锁锁住了。只能通过交易的输出目标来解锁该锁,这意味着只有具有解锁锁的钥匙的人才能打开盒子,这等效于拥有该财产的人

如果某个事务的输出未用作另一事务的输入,即尚未打开,则该事务为UTXO(未使用的事务输出)。

一个比特币用户的钱包将存储多个钥匙,并且该钥匙可以打开的锁的交易输出属于该用户。

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

2.2CTransaction类

位交易信息存储在CTransaction类中,其内容为:

 int nVersion; // 交易的版本号,用于升级
 vector vin; // 交易对应的输入
 vector vout; // 交易对应的输出
 int nLockTime; // 交易对应的锁定时间

重要的是,两个成员vin和vout记录了与此交易相对应的输入和输出信息。交易可能有多个输入和输出,因此由向量定义。

CTxIn的内容:

 COutPoint prevout; // 该输入的来源
 CScript scriptSig; // 输入脚本对应的签名
 unsigned int nSequence;// 主要是用于判断相同输入的交易哪一个更新,值越大越新

COutPoint的定义如下:

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

换句话说,交易包含有关哪个交易和哪个输出来自的特定信息。

CTxOut的内容:

 int64 nValue; // 交易输出对应的金额
 CScript scriptPubKey; // 交易对应的公钥

2.3交易开始

比特币交易网站源码

比特币交易网站源码

以上是比特币钱包软件的界面。单击第一张图片中的“发送硬币”按钮时,将弹出第二张图片中所示的窗口。填写目标地址和余额后,即可开始交易。在源代码中,与启动事务相对应的函数是:

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

此函数的第一个参数是由比特币钱包地址组成的脚本数据,稍后将给出详细说明。第二个参数是转账金额,第三个变量是返回值变量。

此功能的核心是运行以下功能

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

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

搜索可用硬币:

SelectCoins(int64 nTargetValue, set& setCoinsRet)

根据参数1的金额,查找可以构成金额的相关交易

此函数的内容将从头开始检索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想要将密文x发送给B,然后A将使用私钥m对密文进行签名,并将签名和密文发送给B。在收到密文后,B将使用公钥来处理签名。这意味着签名者是A。

比特币交易网站源码

最后一部分描述了事务启动的总体过程,但是我们仍不太清楚细节,尤其是如何生成和操作scriptSig和scriptPubKey。

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);

此函数仅对某些输入的交易数据执行哈希操作并返回哈希值(在这里感觉很奇怪)

随后执行:

Solver(txout.scriptPubKey, hash, nHashType, txin.scriptSig)
{
......
scriptSigRet << vchSig << vchPubKey;
// 除了 sig 外 还要把 pubkey 也添加进入scriptsig中 // 这里就是生成答案的地方
......
}

首先验证您是否具有与公钥对应的私钥,如果有,请使用私钥对上面获得的哈希进行签名,然后在txin成员中填写签名。

这是获取scriptSig的方式。

对于签名验证,主要功能是执行VerifySignature功能

此功能主要执行

EvalScript(txin.scriptSig + CScript(OP_CODESEPARATOR) +
txout.scriptPubKey, txTo, nIn, nHashType)

那是解析脚本。

EvalScript函数的操作如下:

首先创建一个堆栈,该堆栈实际上是一个向量。

stack.push_back(vchPushValue);

首先将scriptSig部分推入堆栈,然后首先执行scriptPubKey部分:

每次读取scriptPubKey中的运算符时,都会执行一次。它可以看作是一种解释性语言,可以阅读并执行一种,具体过程如下:

比特币交易网站源码