探索比特币源码8-哈希2
上一文,我们介绍了Bitcoin Core中哈希相关的代码实现中的一部分
主要是存储哈希函数映射结果——哈希值的数据结构uint256、uint160及他们的基类base_blob
这篇文章我们继续来探讨其他哈希相关的代码,主要是:
- Bitcoin Core对于Hash的封装
- SHA-256的底层实现
- RIPEMD-160的底层实现
Bitcoin Core对于Hash的封装
比特币核心对于哈希相关的封装位于hash.h
中
其中提供了进行SHA256d
和Hash160
的相关接口
其中:
SHA256d(x) = SHA256(SHA256(x))
Hash160(x) = RIPEMD160(SHA256(x))
直接来看源码(只给出了前半部分主要内容):
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.#ifndef BITCOIN_HASH_H
#define BITCOIN_HASH_H#include <crypto/ripemd160.h> // ripemd160的底层实现
#include <crypto/sha256.h> // sha256的底层实现
#include <prevector.h>
#include <serialize.h>
#include <uint256.h>
#include <version.h>#include <vector>typedef uint256 ChainCode;/** A hasher class for Bitcoin's 256-bit hash (double SHA-256).
* 封装了比特币 256-bit 哈希算法的类型
* 注意!这个类计算的 Bitcoin 256 hash 通常被称为 SHA256d
* SHA256d != SHA-256, 实际上,SHA256d(x) = SHA256(SHA256(x))
*/
class CHash256 {
private:CSHA256 sha; // 定义于`crypto/sha256.h`中
public:static const size_t OUTPUT_SIZE = CSHA256::OUTPUT_SIZE;// 完成HASH映射,需配合Write()函数使用void Finalize(unsigned char hash[OUTPUT_SIZE]) {unsigned char buf[CSHA256::OUTPUT_SIZE]; // 存储hash结果sha.Finalize(buf); // 完成第一次SHA256计算sha.Reset().Write(buf, CSHA256::OUTPUT_SIZE).Finalize(hash); // 重置后,使用第一次的结果作为输入,再进行一个SHA256,得到SHA256d的最终结果}// 写入待hash的数据CHash256& Write(const unsigned char *data, size_t len) {sha.Write(data, len);return *this;}// 重置类的实例(使用同一CHash256的实例进行多次SHA256d计算前,需要先使用本方法重置)CHash256& Reset() {sha.Reset();return *this;}
};/** A hasher class for Bitcoin's 160-bit hash (SHA-256 + RIPEMD-160).
* 封装了比特币 160-bit 哈希算法的类型
* Hash160(x) = RIPEMD160(SHA256(x))
*/
class CHash160 {
private:CSHA256 sha; // 定义于`crypto/ripemd160.h`中
public:static const size_t OUTPUT_SIZE = CRIPEMD160::OUTPUT_SIZE;// 完成HASH映射,需配合Write()函数使用void Finalize(unsigned char hash[OUTPUT_SIZE]) {unsigned char buf[CSHA256::OUTPUT_SIZE]; // 存储hash结果sha.Finalize(buf); // 完成第一次SHA256计算CRIPEMD160().Write(buf, CSHA256::OUTPUT_SIZE).Finalize(hash); // 重置后,使用第一次的结果作为输入,再进行一个RIPEMD160,得到Hash160的最终结果}// 写入待hash的数据CHash160& Write(const unsigned char *data, size_t len) {sha.Write(data, len);return *this;}// 重置类的实例(使用同一CHash256的实例进行多次SHA256d计算前,需要先使用本方法重置)CHash160& Reset() {sha.Reset();return *this;}
};/** Compute the 256-bit hash of an object.
* 该函数提供了计算SHA256d的简便方法。否则你需要人为构造CHash256的实例,并调用Write()和Finalize()函数
* 注意! SHA256d(x) = SHA256(SHA256(x))
*/
template<typename T1>
inline uint256 Hash(const T1 pbegin, const T1 pend)
{static const unsigned char pblank[1] = {};uint256 result;CHash256().Write(pbegin == pend ? pblank : (const unsigned char*)&pbegin[0], (pend - pbegin) * sizeof(pbegin[0])).Finalize((unsigned char*)&result);return result;
}/** Compute the 256-bit hash of the concatenation of two objects. */
template<typename T1, typename T2>
inline uint256 Hash(const T1 p1begin, const T1 p1end,const T2 p2begin, const T2 p2end) {static const unsigned char pblank[1] = {};uint256 result;CHash256().Write(p1begin == p1end ? pblank : (const unsigned char*)&p1begin[0], (p1end - p1begin) * sizeof(p1begin[0])).Write(p2begin == p2end ? pblank : (const unsigned char*)&p2begin[0], (p2end - p2begin) * sizeof(p2begin[0])).Finalize((unsigned char*)&result);return result;
}/** Compute the 160-bit hash an object.
* 该函数提供了计算Hash160的简便方法。否则你需要人为构造CHash160的实例,并调用Write()和Finalize()函数
* 注意! Hash160(x) = RIPEMD160(SHA256(x))
*/
template<typename T1>
inline uint160 Hash160(const T1 pbegin, const T1 pend)
{static unsigned char pblank[1] = {};uint160 result;CHash160().Write(pbegin == pend ? pblank : (const unsigned char*)&pbegin[0], (pend - pbegin) * sizeof(pbegin[0])).Finalize((unsigned char*)&result);return result;
}/** Compute the 160-bit hash of a vector. */
inline uint160 Hash160(const std::vector<unsigned char>& vch)
{return Hash160(vch.begin(), vch.end());
}/** Compute the 160-bit hash of a vector. */
template<unsigned int N>
inline uint160 Hash160(const prevector<N, unsigned char>& vch)
{return Hash160(vch.begin(), vch.end());
}
SHA-256的底层实现
进行SHA-256
和RIPEMD-160
哈希映射的真正底层实现位于crypto/
目录下
// Copyright (c) 2014-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.#ifndef BITCOIN_CRYPTO_SHA256_H
#define BITCOIN_CRYPTO_SHA256_H#include <stdint.h>
#include <stdlib.h>
#include <string>/** A hasher class for SHA-256. */
class CSHA256
{
private:uint32_t s[8]; // 8个哈希初值,Initialize中进行初始化// 缓冲区(SHA256以512bit为单位,一个block一个block的进行hash映射,因此缓冲区为64byte)// 将待哈希的序列添加进缓冲区,每当缓冲区填满,进行一次变换,将缓冲区清空unsigned char buf[64];// 记录曾经加入到缓冲区的总字节数// 因此当全部数据写入后,bytes代表了待Hash的数据的总字节数// 还可以通过对64取余可以判断当前缓冲区中存在的剩余数据的字节数uint64_t bytes;// 这两个私有成员的含义不太好理解,可以通过阅读我写的博客更好的理解源代码// https://blog.csdn.net/u011583927/article/details/80905740
public:static const size_t OUTPUT_SIZE = 32;CSHA256();CSHA256& Write(const unsigned char* data, size_t len);void Finalize(unsigned char hash[OUTPUT_SIZE]);CSHA256& Reset();
};/** Autodetect the best available SHA256 implementation.* Returns the name of the implementation.*/
std::string SHA256AutoDetect();/** Compute multiple double-SHA256's of 64-byte blobs.* output: pointer to a blocks*32 byte output buffer* input: pointer to a blocks*64 byte input buffer* blocks: the number of hashes to compute.*/
void SHA256D64(unsigned char* output, const unsigned char* input, size_t blocks);#endif // BITCOIN_CRYPTO_SHA256_H
其中Write()
和Finalize()
是两个核心方法,其源代码为:
// 将制定数据写入缓冲区中并处理
// 对数据进行处理的方法为:每填满一个缓冲区,进行一次hash变换,未能填满缓冲区不进行额外操作
CSHA256& CSHA256::Write(const unsigned char* data, size_t len)
{const unsigned char* end = data + len;size_t bufsize = bytes % 64; // 缓冲区中已有的byte数(需一直维护使得该定义持续成立)// 考虑到Write可能被多次调用,因此缓冲区中的可能存在剩余数据,即 bufsize!=0if (bufsize && bufsize + len >= 64) { // 缓冲区中已有一部分数据,且加上待拷贝的len个byte将会超出缓冲区大小// Fill the buffer, and process it.// 将缓冲区填满(缓冲区现有byte字节,因此填充 64 - bufsize个字节进来就填满了)memcpy(buf + bufsize, data, 64 - bufsize);bytes += 64 - bufsize;data += 64 - bufsize;Transform(s, buf, 1);bufsize = 0;}// 执行到这里有两种情况:// 1.缓冲区有数据,但加上待写入的len个byte也填充不满缓冲区// 2.缓冲区内为空(因为即使缓冲区不为空,也已在上一个if语句块中处理,将缓冲区填满并进行变换)if (end - data >= 64) {// 有多少个 512-bits,就进行多少次Hash变换size_t blocks = (end - data) / 64; // 计算写入的数据有多少个完整的512bits块Transform(s, data, blocks); // 进行blocks次hash映射data += 64 * blocks;bytes += 64 * blocks;}// 执行到这里,data中可能有少部分剩余数据待写入,但剩余数据不会超过64byteif (end > data) {// Fill the buffer with what remains. 将剩余数据加入缓冲区就好// 由于bufsize代表缓冲区中已存在的字节数,将剩余数据从 buf + bufsize 指向的位置开始拷贝memcpy(buf + bufsize, data, end - data);bytes += end - data;}return *this;
}
// Finalize 函数最终确定SHA-256的结果
// 由于SHA-256算法最后需要完成填充数据的操作
// 因此需要 Write + Finalize 的调用方法来计算SHA-256算法
void CSHA256::Finalize(unsigned char hash[OUTPUT_SIZE])
{static const unsigned char pad[64] = {0x80}; // 第一步待填充的附加比特{1000 0000 0000 0000 ...}unsigned char sizedesc[8]; // 第二步待填充的数据长度值WriteBE64(sizedesc, bytes << 3); // 长度值为bytes*8Write(pad, 1 + ((119 - (bytes % 64)) % 64)); // 填充附加比特Write(sizedesc, 8); // 填充长度值// 将哈希结果写入数组hash中WriteBE32(hash, s[0]);WriteBE32(hash + 4, s[1]);WriteBE32(hash + 8, s[2]);WriteBE32(hash + 12, s[3]);WriteBE32(hash + 16, s[4]);WriteBE32(hash + 20, s[5]);WriteBE32(hash + 24, s[6]);WriteBE32(hash + 28, s[7]);
}
其中WriteBE32()
函数的含义尚不明确,其源码位于crypto/common.h
中
void static inline WriteBE32(unsigned char* ptr, uint32_t x)
{uint32_t v = htobe32(x); // 四字节整数的大小端转换memcpy(ptr, (char*)&v, 4);
}
而htobe32()
的代码又位于compat/endian.h
中
// ... 此处省略若干代码
#if defined(WORDS_BIGENDIAN)
// ... 此处省略若干代码
#if HAVE_DECL_HTOBE32 == 0
inline uint32_t htobe32(uint32_t host_32bits)
{return host_32bits;
}
#endif // HAVE_DECL_HTOBE32
// ... 此处省略若干代码
#else // WORDS_BIGENDIAN
// ... 此处省略若干代码
#if HAVE_DECL_HTOBE32 == 0
inline uint32_t htobe32(uint32_t host_32bits)
{return bswap_32(host_32bits);
}
#endif // HAVE_DECL_HTOBE32
// ... 此处省略若干代码
#endif // WORDS_BIGENDIAN
因此可以看到,htobe32()
根据WORDS_BIGENDIAN标识进行不同的操作
再探究bswap_32
的实现最终追溯到compat/byteswap.h
的uint32_t bswap_32()
函数中
inline uint32_t bswap_32(uint32_t x) // 四字节整数的大小端转换
{return (((x & 0xff000000U) >> 24) | ((x & 0x00ff0000U) >> 8) |((x & 0x0000ff00U) << 8) | ((x & 0x000000ffU) << 24));
}
因此最终确定WriteBE32(unsigned char* ptr, uint32_t x)
函数的功能是将指定的整数x按照大端格式,写入ptr指向的内存上
RIPEMD-160的底层实现
对于RIPEMD-160,接口完全一致,详细实现这里就不做过多分析了
RIPEMD-160定义部分的代码如下:
// Copyright (c) 2014-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.#ifndef BITCOIN_CRYPTO_RIPEMD160_H
#define BITCOIN_CRYPTO_RIPEMD160_H#include <stdint.h>
#include <stdlib.h>/** A hasher class for RIPEMD-160. */
class CRIPEMD160
{
private:uint32_t s[5];unsigned char buf[64];uint64_t bytes;public:static const size_t OUTPUT_SIZE = 20;CRIPEMD160();CRIPEMD160& Write(const unsigned char* data, size_t len);void Finalize(unsigned char hash[OUTPUT_SIZE]);CRIPEMD160& Reset();
};#endif // BITCOIN_CRYPTO_RIPEMD160_H
探索比特币源码8-哈希2相关推荐
- 探索比特币源码7-哈希
在之前的两篇文章中,我们已经大致了解了私钥和公钥相关的代码: 探索比特币源码5-私钥 探索比特币源码6-公钥 接下来,我们打算阅读生成比特币地址的相关代码. 比特币地址的生成过程中,涉及了哈希函数的计 ...
- 探索比特币源码4-JSON-RPC接口的其他调用方法
探索比特币源码4-JSON-RPC接口的其他调用方法 上一文探索比特币源码3-熟悉RPC接口中练习了使用bitcoin-cli客户端调用JSON-RPC接口的方法. 本文探索JSON-RPC接口的其他 ...
- 探索比特币源码3-熟悉RPC接口
探索比特币源码3-熟悉RPC接口 比特币核心客户端实现了JSON-RPC接口,这个接口可以通过命令行帮助程序bitcoin-cli访问,也可以通过编程的形式在程序中访问. 本文主要探索3点: * 什么 ...
- 探索比特币源码2-配置Bitcoin Core节点
探索比特币源码2-配置Bitcoin Core节点 回顾上文探索比特币源码1-运行比特币核心 我们已经运行上了Bitcoin Core,开始同步区块链数据 这个过程会持续一两天,我也不能干等着,便开始 ...
- 探索比特币源码1-运行 Bitcoin Core Node
探索比特币源码1-运行 Bitcoin Core Node 上一文我们已经编译好了Bitcoin Core的可执行文件 本文,我们来尝试运行一个Bitcoin Core 注:本文是在学习Masteri ...
- 探索比特币源码0-bitcoin的编译
探索比特币源码0-bitcoin的编译 前言 区块链领域作为一个新兴的热点,迭代的实在是太快了,刚刚入门,必须要抓紧追赶. 在阅读了一些优秀的文档之后,已经对比特币及其背后的实现技术--区块链有了一定 ...
- 探索比特币源码5-私钥
经过一段时间的积累,终于来到了比特币源码阅读的环节.还是按照之前的节奏,我们就比对着精通比特币一书的进度,进行源码的阅读. 对于此文,只需你对比特币系统中私钥-公钥-地址的产生及关系有最基本的了解 因 ...
- 探索比特币源码6-公钥
继续源码的阅读,本文将对比特币源码中的公钥相关部分进行梳理. 在阅读代码前,先明确一个概念:公钥是如何定义和产生的? 公钥如何产生 我们已经知道,比特币的私钥就是一个256位二进制数字. 通过椭圆曲线 ...
- 探索比特币源码9-顺藤摸瓜之通过RPC读源码
前言 阅读像比特币这样规模的源码,理清脉络很重要,一个文件一个文件阅读肯定不现实. 从比特币系统的RPC接口入手,不失为一个学习源码的好方法. 具体做法是,按照想要学习的功能,找到相应的RPC命令,然 ...
最新文章
- Ubantu安装maven
- 检测移动端内存敏感数据方法(安卓)
- 搭建glusterfs集群
- 以一致的体验交付和管理云原生多集群应用
- 收藏 | 500页阿里、滴滴、快手等公司的大数据最佳实践!PDF限时下载
- sql 关联使用id还是code_R语言实例:用glue批量生成SQL语句
- vue 得到枚举个数_Package - vue-enum
- Azure 中国篇之网络服务—(2)Azure虚拟机使用公网ip(PIP)
- C++指针编程你要小心的陷阱——如何优雅的释放指针void*
- 黑马程序员—选择黑马,是我前进的方向
- 解析Excel(xls、xlsx两种格式)
- MTK 平台Android系统遥控器映射关系
- windows安装gem和fastlane
- 怎么打开产品原型是html,如何打开产品原型图(axure)
- vmware您无权输入许可证密钥,请请使用系统管理员账户重试
- int、long、long long、unsigned int、_int64的取值范围(与不同位数的编译器有关)
- swift 代码加载xib storyboard
- 为什么网页无法正常显示
- 国内在线学习网站汇总
- 微信小程序怎么样取代传统收款设备的流程
热门文章
- ReentrantLock学习
- Tomcat安装与环境变量的配置
- 莫名的证书错误...ERROR ITMS-90035:Invalid Signature.
- bzoj1532: [POI2005]Kos-Dicing
- 数据库事务(Database Transaction)
- Android开发中依赖注入的应用
- 数据库基础知识——流程控制结构
- 计算机网络(十一)-数据链路层-流量控制
- php mysql会员注册_PHP实现会员注册系统
- vscode写java没有提示_VS Code报错Language Support for Java(TM)插件显示需要JDK11 写java代码没有提示...