主页 > imtoken苹果版testflight > 比特币源码解析四:签名校验
比特币源码解析四:签名校验
比特币源码解析四:签名验证
在《比特币源码解析三:交易脚本》中,文章最后以比特币系统中最简单的交易脚本为例介绍了比特币的脚本命令系统,其中OP_CHECKSIG命令是命令系统的核心命令,用于验证交易。 签名,本文重点介绍其原理。
ECDSA 基函数
椭圆曲线数字签名算法 (ECDSA) 是使用椭圆曲线密码术 (ECC) 的数字签名算法 (DSA) 的模拟。
源码中有几个关键函数,这里简单介绍一下,方便下面理解:
1. secp256k1_ecdsa_verify 用于使用公钥验证签名
函数原型:
int secp256k1_ecdsa_verify(const secp256k1_context* ctx,const secp256k1_ecdsa_signature *sig,const unsigned char *msg32,const secp256k1_pubkey *pubkey)
关键参数说明:
1)msg32数据
2)sig为msg32生成的签名(由secp256k1_ecdsa_sign生成)
3)pubkey公钥
返回值:
pubkey表示的公钥如果通过数据msg32的sig签名验证则返回true,否则返回false
2. secp256k1_ecdsa_sign 用于使用私钥生成签名
函数原型:
int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature *signature, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata)
关键参数说明:
1)msg32数据(对应secp256k1_ecdsa_verify中的msg32)
2)signature输出参数生成的签名
3)seckey公钥(与secp256k1_ecdsa_verify中的pubkey组成非对称加密的公私钥对)
调用逻辑为:用户A使用私钥在msg32上通过secp256k1_ecdsa_sign函数生成签名,用户B使用公钥通过secp256k1_ecdsa_verify对同一数据msg32进行sig验证,证明公钥用户B和用户A的私钥是一对。
签名校验源码包
在《交易脚本》一文中提到,比特币源码中CKey和CPubKey这两个类分别代表私钥和公钥,这两个类提供了签名生成和验证的封装。
CKey::Sign 用于生成签名
函数原型:
bool CKey::Sign(const uint256 &hash,std::vector&vchSig,uint32_t test_case)
参数说明:
1)hash表示交易的哈希值,下面会详细介绍
2)vchSig输出参数,代表生成的签名
特征:
该函数是调用secp256k1_ecdsa_sign使用ckey代表的私钥进行hash(数据)运算生成签名
CPubKey::Verify 用于签名验证
函数原型:
bool CPubKey::Verify(const uint256 &hash, const std::vector& vchSig)
参数说明:
Hash 代表交易哈希
vchSig CKey::Sign 函数生成的签名
返回值:
如果验证成功,则返回true,否则返回false
OP_CHECKSIG 逻辑
该指令的执行逻辑主要是从栈中取出sig和pubkey,调用TransactionSignatureChecker::CheckSig函数来验证签名
TransactionSignatureChecker::CheckSig的执行逻辑如下
1.调用SignatureHash对交易进行哈希运算
2.调用VerifySignature对第一步计算出的交易哈希值进行签名验证
VerifySignature 这个函数比较简单就是调用上面pubkey的Verify函数
签名哈希
函数原型:
uint256 SignatureHash(const CScript& scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* 缓存)
参数说明:
1)scriptCode输出脚本,这个对应本次需要验证的交易的输出脚本(锁,ps:有时候是一段输出脚本,这个逻辑暂时不考虑)
2)txTo交易,即输入脚本(提供sig的脚本,key)对应的交易(花钱的交易)
3)金额需要多少
4)sigversion、nHashType分别是交易结构组织方式和hash计算方式,这里不做赘述,以最简单的方式进行说明。 这里只需要知道这两个参数决定了hash的计算方式
函数功能:
该函数对消费交易和输出脚本(支付交易)的部分字段进行hash处理,计算出hash
可以简单理解为
哈希(tx+pretx.outscript)
其中tx是需要花钱的交易,即需要通过脚本验证的交易,验证脚本的输入是否合法(key是否合法)
Pretx为本次tx对应的最后一个tx,即待验证交易对应的支付交易
outscript表示输出脚本,是pretx的锁
意义:
上面的解释有点绕,下面单独说说这个设计的意义
还记得之前关于交易的文章中提到的事务吗,这里的事务如下图所示
本文中所谓的签名验证比特币源码修改,就是验证TxB是否有权花费TxA提供的3个比特币(是否有私钥)
上面提到的Hash(tx+pretx.outscript)公式中的tx为TxB,pretx为TxA,outscript为花费3个比特币对应的输出脚本
具体场景是这样的
1、用户B将自己的公钥做成一个地址(上一篇文章介绍过,这里简单理解为hash(pubkey))提供给用户A,让A给他打钱
2、用户A生成一笔交易TxA,在交易中注入3个比特币,并在输出脚本中填写用户B的公钥地址(hash(pubkey)),并表示如果有人要花费这3个比特币1个比特币必须提供两个数据
1)B的公钥
2)B的私钥生成的签名
3、用户B使用私钥生成TxB,对TxB进行签名,并提供自己的公钥,将签名和公钥放入输入脚本中,满足解锁TxA的条件,即花钱
细心的读者可能还有疑惑
1、为什么在输出脚本中不直接给出一个hash(pubkey)而是一个pubkey?
这是为了保密,直接提供公钥不利于保密,而且在你不用钱的时候做hash会让别人永远不知道你的公钥。
2. 对整个 tx 进行哈希处理,但必须将哈希签名写入输入脚本。 这是怎么做到的?
这是上面为了简化模型所做的错误描述。 这里的hash是tx中除输入脚本(sig和pubkey)外其他字段的hash,然后是签名。
3、为什么你对tx的除输入脚本以外的所有字段进行hash,而不是对单个字段进行hash? 用于签名验证的单个字段也可以证明用户持有私钥。
例如
Tx 中有一个字段表示花费的金额。 对应上图,TxB花费了2个比特币。 如果我们在做hash的时候没有带入2个比特币的信息比特币源码修改,那么当交易上线的时候,为了赚取更多的服务费,矿主可以用2个比特币换取1个比特币,那么服务费就会从1 比特币到 2 比特币。 因此,对整个交易进行哈希处理是为了保护某些字段不被更改。
ps:其实不是整个tx。 有些字段为了灵活性没有进行哈希处理,特别是在一些特殊场景下。 为了灵活性,字段有意不做hash,但是这种情况的解释需要和场景对应,不利于理解,暂不介绍。