主页 > 怎么看出来imtoken真伪 > 比特币源码分析:认证与交易

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

怎么看出来imtoken真伪 2023-10-07 05:09:37

写在前面

比特币源码0.1.0花了一个多星期看完,到现在基本都看完了,虽然不能保证所有地方都是我懂的比特币密钥多长,但是至少我在理论上和实践上都可以。从书上看,理论基本上可以在源码中得到证实。

本文只是对比特币交易部分的分析。由于是结合源代码进行描述的,读者请自行准备源代码以供查看。源码的内容不是特别详细,基本上只列出了关键功能和关键步骤。我很快就会做其他项目。不知道有没有机会弥补对比特币其他部分的分析。文档是word写的,直接粘贴。

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

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

以上就是比特币加密和认证的本质。但是,本文没有描述公钥和私钥的原理,而只是描述了比特币原始代码是如何利用这种机制来实现加密和认证的。欲了解更多信息,请先查看相关信息。

1.生成1个公钥和私钥

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

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

CKey类有以下功能:


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

公钥和私钥都是 256 位数字字符串。私钥是以安全的方式随机获取的,本质上是一个 256 位的随机数。公钥是通过椭圆曲线乘法的算法计算出来的(K = k * G,其中k是私钥,G是一个叫做生成点的常数点,K是得到的公钥),它们都是在 MakeNewKey() 函数中实现,我们只需要知道这个操作是不可逆的。

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

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

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

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编码,从而得到我们经常看到的比特币地址,例如:

p>

1JyShDpyqafQ88EaLvUQdhajKCzYG4zxd9

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

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

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

比特币的交易方式与传统的交易方式有很大不同。传统的事务方法是数据库式的。例如,如果您在网上购买一件衣服,则该衣服的金额将从您的支付宝账户中扣除。支付宝账户代表数据库。比特币交易是不同的。比特币交易是基于账本的。比特币价值记录了每笔交易的历史,没有专门存储余额数据。

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

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

下面将描述比特币交易账本的实现。如下图

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

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

具体来说,创币交易,也称为币基交易,是指矿工打包区块奖励的交易输入是空的,因为这笔交易的金额是“凭空产生的”。

排除coinbase交易,每笔交易的输入金额之和应等于输出金额之和。

那么,比特币究竟是如何确保只有我们才能使用属于我们的比特币呢?

当一笔交易刚刚生成时,我们可以将其输出与钱箱进行比较。这些保险箱被主人的锁锁住。这个锁只能通过交易的输出目标来开锁,也就是说只有拥有开锁钥匙的人才能打开盒子,相当于这个人拥有了财产

如果一笔交易的某个输出没有作为另一笔交易的输入,也就是没有被打开,那么这个交易就是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; // 交易对应的第几个输出

p>

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

CTxOut 的内容:

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

2.3 事务发起

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

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

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

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

p>

遍历之前收集的交易集合,将哪笔交易的具体输出填入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。

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

上一节描述了交易发起的整体流程,但是细节我们不是很清楚,尤其是scriptSig和scriptPubKey是如何配对脚本的。制作和工作原理。

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

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

可以理解为scriptPubKey是所有者给交易的锁,scriptSig是key。如果用户想要使用交易的输出,他需要提供一个交易来解锁 scriptPubKey 供大家验证。

那么 scriptSig 是如何得到它的呢? scriptSig 实际上是比特币用户使用自己的私钥签署交易。

主要调用SignatureSignature函数:

首先在这个函数中执行:

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.push_back(vchPushValue);

先将scriptSig部分压栈,再执行scriptPubKey部分:

scriptPubKey 中的每个操作符都被读取,执行一次。可以看成是解释型语言的形式,读一读一执行,具体流程如下:

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