哈希函数就像一个冲压机器,不管多长的信息,它都输出一个固定大小的字符串。更神奇的是,输入不同输出就不同,哪怕两本红楼梦,只有一个字不同,冲压结果也将大相径庭。
#
<!--StartFragment-->
以比特币在用的SHA256哈希函数为例,我们看看不同的输入,得到的输出有什么不同。我们用Python得到hello world的哈希值:
import hashlib
# 要计算SHA-256哈希的原始数据
data = "hello world"
# 创建SHA-256哈希对象
sha256_hash = hashlib.sha256()
# 更新哈希对象,传入数据(需要编码为字节)
sha256_hash.update(data.encode('utf-8'))
# 哈希结果
print(sha256_hash.hexdigest())
# 输出
b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
给hello world加一个感叹号,哈希结果为:
7c0bf498f86df605bc1716e74f375c62708510829f22078d5abb04124833cead
给50个连续的hello world!超长字符串求哈希,结果为:
b62cbce3594235e3551370ce4d55951f20476a0f1aa9bc6e2712eec9a3cce16e
结果表明,三次哈希结果长度相同,肉眼看上去毫无关系。这就是哈希这个冲压机器的特点。就算输入几百万几千万字符,输出依然是64个字母或数字。
直觉上,既然不管输入什么,输出都是64位字符串,那多个输入总会对应同一个输出的吧?
是的,这就是哈希碰撞,不过SHA256出现哈希碰撞的概率极低,应用中忽略不计。有些反直觉,毕竟随意长度的字符串太多了,而64个字符串不管怎么样都是有限的。数学上可以证明概率极低,但是学习区块链这个证明可以跳过,不妨碍区块链的安全性。
哈希的这个特性,使其成为验证信息完整性和检测任何更改的优秀工具。
譬如:大多数软件都会在官网展示其程序安装文件的SHA256结果。这样,不管你从哪个代理商购买的软件,或者担心下载过程中被拦截植入病毒,只需要在安装前求文件的SHA256结果,跟官网对比下。一样的结果证明程序文件未被更改过,可放心安装。
常见的哈希函数有MD5、SHA1、SHA256和SHA3。MD5和SHA1的安全性较低,安全领域不建议使用。目前,比特币使用了SHA256,以太坊使用了SHA3。
这些哈希函数的计算过程有以下通用的几个主要步骤:
填充数据
进行哈希计算的原始数据长度是参差不齐的,需要按照一定要求补齐长度。通常在数据末尾添加一个1位,接着添加若干0位,直到数据长度取模(如512位)运算等于特定值(如448位)。最后,添加一个固定的(如64位)的表示原始数据长度的结尾,这步操作是为了后续分块的时候,没有不足一块的残余。
分块处理
将整个原始数据划分为固定长度的块,如512位,这也是为什么要填充数据的一个原因。对每个块进行几十次甚至上百次的数学运算,如位操作、模运算、压缩等...
拼接或压缩输出固定长度
在所有数据块处理完成后,输出最终的哈希值,通常是将状态值连接起来形成固定长度的输出。SHA3的输出长度是可变的。
细节上差异很大,一般而言数学计算步骤较多,安全性较高;生成长度越长,防碰撞的能力越强。根据实际需要,在计算效率和安全性上做平衡。
#
区块链是去中心化的,每个全节点保存了全部的交易信息,不同节点之间对比数据差异,保证数据一致性,就必须使用哈希这一工具。\
如果没有哈希,想象下对比两个巨量的数据集,逐一对比,既需要漫长的时间,也消耗巨量的网络带宽。可以这么说,没有哈希,区块链无法成为现实。
比特币充分利用了哈希,区块之间,后一个区块会将前一个区块的哈希值纳入区块哈希计算中,这样将不同的区块链接起来,形成了区块链;在一个区块内,将所有的交易信息进行哈希,方便节点之间校验区块数据,只需要计算出默克尔树,默克尔树一致则区块内交易信息完全一致。
比特币每个区块都有一个区块哈希值,表示这个区块的唯一性。它的计算逻辑如下:
构建区块头
区块哈希是通过对区块头进行哈希计算得到的。区块头包含以下字段:
版本号(version) 前一个区块的哈希(previous block hash) 默克尔根(merkle root) 时间戳(time) 难度目标(bits) 随机数(nonce)
序列化区块头
将区块头的各个字段按照特定顺序进行序列化,通常是以小端字节序(little-endian)排列。
两次哈希计算生成区块哈希值
使用 SHA-256 算法对序列化后的区块头进行两次哈希计算
因为每个区块哈希计算,都使用了前一个区块哈希值,所以区块之间就有了向前的唯一指向,形成一条链。我们可以取区块头信息和前一个区块哈希值做校验,判断当前区块的合法性。
我们以区块#856960为例子,自己计算区块哈希值,看是否和比特币主网浏览器查到的哈希一致:
import hashlib
import struct
def sha256d(data):
"""计算双重SHA-256哈希"""
return hashlib.sha256(hashlib.sha256(data).digest()).digest()
def create_block_header(version, prev_hash, merkle_root, timestamp, bits, nonce):
"""创建区块头"""
header = (
struct.pack("<L", version) + # 版本号
bytes.fromhex(prev_hash)[::-1] + # 前一个区块的哈希(小端序)
bytes.fromhex(merkle_root)[::-1] + # 默克尔根(小端序)
struct.pack("<L", timestamp) + # 时间戳
struct.pack("<L", bits) + # 难度目标
struct.pack("<L", nonce) # 随机数
)
return header
def validate_block_hash(version, prev_hash, merkle_root, timestamp, bits, nonce, expected_hash):
"""验证区块哈希的合法性"""
block_header = create_block_header(version, prev_hash, merkle_root, timestamp, bits, nonce)
block_hash = sha256d(block_header)
print("计算哈希值为:"+block_hash[::-1].hex())
print("主网哈希值为:"+expected_hash)
return block_hash[::-1].hex() == expected_hash # 转换为小端序并比较
# block id #856960 的信息 取自比特币主网 prev_hash为前一个区块#856959的哈希值
version = 537452544
prev_hash = "00000000000000000000fdcb6d2e07cc883ab259e8e6c7f9d7fbcbe47ea44274"
merkle_root = "5a39abc218ee9d95b393ecdbc407725fe2577d506208c5c5da4ef8d0d6578828"
timestamp = 1723766311
bits = 386088310
nonce = 2189834309
expected_hash = "00000000000000000002521858364734ccd35e3cdcf443f89a630395bc89e8b9"
# 验证哈希
is_valid = validate_block_hash(version, prev_hash, merkle_root, timestamp, bits, nonce, expected_hash)
print("区块哈希合法性:", is_valid)
验证结果为:
计算哈希值为:00000000000000000002521858364734ccd35e3cdcf443f89a630395bc89e8b9
主网哈希值为:00000000000000000002521858364734ccd35e3cdcf443f89a630395bc89e8b9
区块哈希合法性: True
比特币中区块的默克尔根(Merkle Root)是通过对区块内所有交易的哈希值进行一系列哈希计算得到的。以下是计算默克尔根的步骤:
从区块中提取所有交易,计算每个交易的 SHA-256 哈希值。
将所有交易哈希放入一个列表。若交易数量为奇数,重复最后一个哈希以确保每一层都是偶数个哈希。
将相邻的哈希值组合在一起(连接),然后对组合后的字符串进行 SHA-256 哈希。继续这个过程,直到只剩下一个哈希值,这个值就是默克尔根。
每个节点可以校验区块内的交易信息,计算默克尔树的合法性。节点之间校验区块内交易信息时,可以仅对比默克尔树即可,非常高效。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!