比特币源码分析之四:签名验证

比特币源码分析之四:签名验证

在《比特币源码分析之三:交易脚本》文中最后以比特币系统中最简单的交易脚本为例子介绍了比特币的脚本指令系统,其中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使用私钥,通过secp256k1_ecdsa_sign函数对msg32做签名生成,生成signature,用户B使用公钥,通过secp256k1_ecdsa_verify对同样的数据msg32做sig验证,以此来证明用户B的公钥和用户A的私钥是一对。

签名验证的源码封装

在《交易脚本》文中提到了CKey和CPubKey两个类是比特币源码中代表私钥和公钥的两个类,而这两个类又提供了签名生成和验证的封装。

CKey::Sign用于生成签名

函数原型:

bool CKey::Sign(const uint256 &hash,std::vector<unsigned char>&vchSig,uint32_t test_case)

参数说明:

1)hash 代表交易的hash值,下文会详细介绍

2)vchSig 输出参数,代表生成的签名

功能介绍:

该函数是调用secp256k1_ecdsa_sign 使用ckey代表的私钥对hash(数据)运算生成签名

CPubKey::Verify 用于签名验证

函数原型:

bool CPubKey::Verify(const uint256 &hash, const std::vector<unsigned char>& vchSig)

参数说明:

Hash 代表交易hash

vchSig CKey::Sign函数生成的签名

返回值:

验证成功返回true否则返回false

OP_CHECKSIG逻辑

该指令的执行逻辑主要是从栈中取sig和pubkey调用TransactionSignatureChecker::CheckSig函数对签名进行验证

TransactionSignatureChecker::CheckSig 的执行逻辑如下

1、调用SignatureHash 对交易做hash运算

2、调用VerifySignature对第一步算出来的交易的hash做签名验证

VerifySignature 这个函数比较简单就是调用上文中的pubkey的Verify函数

SignatureHash

函数原型:

uint256 SignatureHash(const CScript& scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache)

参数说明:

1)scriptCode 输出脚本,这个对应的就是本次需要验证的交易的输出脚本(锁,ps:有时会是输出脚本的一段,这个逻辑暂时不考虑)

2)txTo 交易,也就是输入脚本(提供sig的脚本,钥匙)对应的交易(花钱的交易)

3)amount 花多少钱

4)sigversion,nHashType分别是交易结构组织方式和hash计算方式,这里先不讨论,以最简单的方式讲解,这里只需要知道这两个参数决定了hash的计算方式

函数功能:

该函数把花钱的交易的一些字段和输出脚本(出钱交易)一起做了一个hash处理,计算出了hash

可以简单理解为

Hash(tx+pretx.outscript)

其中tx就是花钱的交易,也就是需要做脚本验证的交易,验证该脚本的输入是否合法(钥匙是否合法)

Pretx就是这个tx对应的上一笔tx,也就是待验证交易对应的出钱的交易

Outscript表示输出脚本,也就是pretx的锁

意义:

上面的解释有点绕,所以还是单独说一下这个设计的意义

还记得前面几篇关于交易的文章中提到的交易,其中交易如下图

本篇所谓的签名验证就是针对TxB做验证,验证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)B的公钥

2)B的私钥生成的签名

3、用户B使用私钥生成了TxB,对TxB签名,并且提供了自己的公钥,把签名和公钥放入到输入脚本,满足了解开TxA的条件,也就是花了这笔钱

细心的读者可能还会有疑惑

1、输出脚本中为什么不是直接给一个pubkey,而是给了一个hash(pubkey)?

这个是为了保密考虑,直接把公钥提供出来不利于保密,而做一次hash,就可以在你不使用这笔钱的时候别人永远不知道你的公钥。

2、对整个tx做hash,但是hash后的签名又要写入到输入脚本内,这个是怎么做到的?

这个是上文为了简化模型做的错误描述,这里做hash是tx中除了输入脚本(sig和pubkey)之外的其他字段做hash,进而做签名。

3、为什么是对tx的除了输入脚本的其他所有字段做hash,而不是某个单一字段?单一字段做签名验证也能证明用户是持有私钥的。

举个例子

Tx中有一个字段是表述花费数量的,对应到上图就是TxB花费了2个比特币,如果我们在做hash的时候没有把2个比特币信息带进去,那么这个交易发布到网上的时候,矿主为了多赚手续费,可以把2个换成1个,那么手续费就从1个比特币变成了2个。所以对整个tx进行hash是为了保护一些字段不被串改。

Ps:其实也不是整个tx,有些字段为了灵活性是没有被hash的,尤其是一些特殊的场景,为了灵活性,故意不把字段做hash,但是这种情况的讲解需要对应场景,不利于理解,暂时不介绍。

下一篇会介绍另一个核心的概念:区块

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏IMWeb前端团队

Nodejs进阶:使用DiffieHellman密钥交换算法

本文作者:IMWeb 陈映平 原文出处:IMWeb社区 未经同意,禁止转载 简介 Diffie-Hellman(简称DH)是密钥交换算法之一,它的作用...

1997
来自专栏Golang语言社区

【golang】调优工具 pprof

Golang 提供了 pprof 包(runtime/pprof)用于输出运行时的 profiling 数据,这些数据可以被 pprof 工具(或者 go to...

1013
来自专栏利炳根的专栏

学习笔记CB011:lucene搜索引擎库、IKAnalyzer中文切词工具、检索服务、word2vec

影视剧字幕聊天语料库特点,把影视剧说话内容一句一句以回车换行罗列三千多万条中国话,相邻第二句很可能是第一句最好回答。一个问句有很多种回答,可以根据相关程度以及历...

4468
来自专栏数据结构与算法

51NOD 1185 威佐夫游戏 V2(威佐夫博弈)

1185 威佐夫游戏 V2 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 有2堆石子。A B两个人轮流拿,A先拿。每次可以从一...

3496
来自专栏Kubernetes

Kubernetes Resource QoS机制解读

Kubernetes Resource QoS Classes介绍 Kubernetes根据Pod中Containers Resource的request和li...

35312
来自专栏沈唁志

文本处理,第2部分:OH,倒排索引

这是我的文本处理系列的第二部分。在这篇博客中,我们将研究如何将文本文档存储在可以通过查询轻松检索的表单中。我将使用流行的开源Apache Lucene索引进行说...

1454
来自专栏大数据挖掘DT机器学习

利用Python绘制MySQL数据图实现数据可视化

第1步:确保MySQL已安装且在运行 安装教程: 亲测:MySQL安装与python下的MySQLdb使用(附软件与模块包) 第2步:使用Python连接...

5875
来自专栏FreeBuf

Office”组合”式漏洞攻击样本分析

by hcl, nine8 of code audit labs of vulnhunt.com 1 概述 网上公开一个疑似CVE-2014-1761的RTF样...

2169
来自专栏小樱的经验随笔

简易版DES加密和解密详解

在DES密码里,是如何进行加密和解密的呢?这里采用DES的简易版来进行说明。 二进制数据的变换 由于不仅仅是DES密码,在其它的现代密码中也应用了二进制数据,所...

3086
来自专栏ml

ijg库解码超大型jpeg图片

1. ijg库解码超大型jpeg图片(>100M)的时候,如何避免内存溢出。        采用边解码边压缩的策略,每次解码一行或者若干行图片数据,然后对于这些...

3928

扫码关注云+社区