上一文,我们介绍了Bitcoin Core中哈希相关的代码实现中的一部分

主要是存储哈希函数映射结果——哈希值的数据结构uint256、uint160及他们的基类base_blob

这篇文章我们继续来探讨其他哈希相关的代码,主要是:

  • Bitcoin Core对于Hash的封装
  • SHA-256的底层实现
  • RIPEMD-160的底层实现

Bitcoin Core对于Hash的封装

比特币核心对于哈希相关的封装位于hash.h

其中提供了进行SHA256dHash160的相关接口

其中:

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-256RIPEMD-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.huint32_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相关推荐

  1. 探索比特币源码7-哈希

    在之前的两篇文章中,我们已经大致了解了私钥和公钥相关的代码: 探索比特币源码5-私钥 探索比特币源码6-公钥 接下来,我们打算阅读生成比特币地址的相关代码. 比特币地址的生成过程中,涉及了哈希函数的计 ...

  2. 探索比特币源码4-JSON-RPC接口的其他调用方法

    探索比特币源码4-JSON-RPC接口的其他调用方法 上一文探索比特币源码3-熟悉RPC接口中练习了使用bitcoin-cli客户端调用JSON-RPC接口的方法. 本文探索JSON-RPC接口的其他 ...

  3. 探索比特币源码3-熟悉RPC接口

    探索比特币源码3-熟悉RPC接口 比特币核心客户端实现了JSON-RPC接口,这个接口可以通过命令行帮助程序bitcoin-cli访问,也可以通过编程的形式在程序中访问. 本文主要探索3点: * 什么 ...

  4. 探索比特币源码2-配置Bitcoin Core节点

    探索比特币源码2-配置Bitcoin Core节点 回顾上文探索比特币源码1-运行比特币核心 我们已经运行上了Bitcoin Core,开始同步区块链数据 这个过程会持续一两天,我也不能干等着,便开始 ...

  5. 探索比特币源码1-运行 Bitcoin Core Node

    探索比特币源码1-运行 Bitcoin Core Node 上一文我们已经编译好了Bitcoin Core的可执行文件 本文,我们来尝试运行一个Bitcoin Core 注:本文是在学习Masteri ...

  6. 探索比特币源码0-bitcoin的编译

    探索比特币源码0-bitcoin的编译 前言 区块链领域作为一个新兴的热点,迭代的实在是太快了,刚刚入门,必须要抓紧追赶. 在阅读了一些优秀的文档之后,已经对比特币及其背后的实现技术--区块链有了一定 ...

  7. 探索比特币源码5-私钥

    经过一段时间的积累,终于来到了比特币源码阅读的环节.还是按照之前的节奏,我们就比对着精通比特币一书的进度,进行源码的阅读. 对于此文,只需你对比特币系统中私钥-公钥-地址的产生及关系有最基本的了解 因 ...

  8. 探索比特币源码6-公钥

    继续源码的阅读,本文将对比特币源码中的公钥相关部分进行梳理. 在阅读代码前,先明确一个概念:公钥是如何定义和产生的? 公钥如何产生 我们已经知道,比特币的私钥就是一个256位二进制数字. 通过椭圆曲线 ...

  9. 探索比特币源码9-顺藤摸瓜之通过RPC读源码

    前言 阅读像比特币这样规模的源码,理清脉络很重要,一个文件一个文件阅读肯定不现实. 从比特币系统的RPC接口入手,不失为一个学习源码的好方法. 具体做法是,按照想要学习的功能,找到相应的RPC命令,然 ...

最新文章

  1. Ubantu安装maven
  2. 检测移动端内存敏感数据方法(安卓)
  3. 搭建glusterfs集群
  4. 以一致的体验交付和管理云原生多集群应用
  5. 收藏 | 500页阿里、滴滴、快手等公司的大数据最佳实践!PDF限时下载
  6. sql 关联使用id还是code_R语言实例:用glue批量生成SQL语句
  7. vue 得到枚举个数_Package - vue-enum
  8. Azure 中国篇之网络服务—(2)Azure虚拟机使用公网ip(PIP)
  9. C++指针编程你要小心的陷阱——如何优雅的释放指针void*
  10. 黑马程序员—选择黑马,是我前进的方向
  11. 解析Excel(xls、xlsx两种格式)
  12. MTK 平台Android系统遥控器映射关系
  13. windows安装gem和fastlane
  14. 怎么打开产品原型是html,如何打开产品原型图(axure)
  15. vmware您无权输入许可证密钥,请请使用系统管理员账户重试
  16. int、long、long long、unsigned int、_int64的取值范围(与不同位数的编译器有关)
  17. swift 代码加载xib storyboard
  18. 为什么网页无法正常显示
  19. 国内在线学习网站汇总
  20. 微信小程序怎么样取代传统收款设备的流程

热门文章

  1. ReentrantLock学习
  2. Tomcat安装与环境变量的配置
  3. 莫名的证书错误...ERROR ITMS-90035:Invalid Signature.
  4. bzoj1532: [POI2005]Kos-Dicing
  5. 数据库事务(Database Transaction)
  6. Android开发中依赖注入的应用
  7. 数据库基础知识——流程控制结构
  8. 计算机网络(十一)-数据链路层-流量控制
  9. php mysql会员注册_PHP实现会员注册系统
  10. vscode写java没有提示_VS Code报错Language Support for Java(TM)插件显示需要JDK11 写java代码没有提示...