从零手搓一个SHA-256:用C语言一步步实现比特币的‘心脏’算法
当你用比特币转账时,是否好奇过那串看似随机的交易ID是如何生成的?或者当矿工们谈论"算力"时,他们究竟在计算什么?这一切的核心,都离不开一个名为SHA-256的密码学算法。今天,我们将抛开现成的库函数,用C语言从零开始实现这个支撑整个区块链网络的"数字指纹"生成器。
1. 为什么选择SHA-256?
在开始编码之前,我们需要理解SHA-256为何能成为区块链技术的基石。想象你要给朋友发送一封电子邮件,如何确保内容在传输过程中没有被篡改?哈希算法就是解决这个问题的"数字指纹"生成器。
SHA-256的三个关键特性:
- 确定性:相同输入永远产生相同输出
- 雪崩效应:1比特的输入变化会导致约50%的输出比特改变
- 不可逆性:无法从哈希值反推出原始数据
在比特币网络中,每个区块都包含前一个区块的哈希值,形成不可篡改的链条。矿工们竞争的正是找到一个满足特定条件的SHA-256哈希值,这个过程就是我们常说的"挖矿"。
2. 搭建SHA-256的基础结构
2.1 定义基本宏操作
SHA-256的核心是一系列位操作,我们先定义关键的宏:
#define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n)))) #define CH(x, y, z) (((x) & (y)) ^ (~(x) & (z))) #define MAJ(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) #define SIG0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ ((x) >> 3)) #define SIG1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ ((x) >> 10))这些看似复杂的操作实际上是SHA-256实现"混淆"和"扩散"的关键。例如,ROTR实现32位字的循环右移,这是密码学中常用的操作。
2.2 初始化哈希常量
SHA-256使用两组预设的常量值:
// 初始哈希值(前8个质数的平方根小数部分前32位) uint32_t H[8] = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 }; // 64个常量(前64个质数的立方根小数部分前32位) uint32_t K[64] = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, // ... 完整列表见标准文档 };这些"魔法数字"并非随意选择,而是来自数学常数的特定部分,确保了算法的随机性和安全性。
3. 实现消息预处理
3.1 填充消息
SHA-256要求输入长度必须是512位的整数倍。我们的填充规则是:
- 在消息末尾添加一个'1'位
- 添加足够多的'0'位
- 最后64位表示原始消息的位长度
void pad_message(uint8_t *message, uint64_t length) { uint64_t bit_length = length * 8; uint64_t new_length = ((length + 8) / 64 + 1) * 64; // 添加'1'位 message[length] = 0x80; // 添加'0'位 for (uint64_t i = length + 1; i < new_length - 8; i++) { message[i] = 0; } // 添加原始位长度(大端序) for (int i = 0; i < 8; i++) { message[new_length - 8 + i] = (bit_length >> (56 - i * 8)) & 0xFF; } }3.2 消息分块处理
填充后的消息被分割成512位的块,每个块又分为16个32位字:
void process_block(const uint8_t *block, uint32_t *hash) { uint32_t W[64]; uint32_t a, b, c, d, e, f, g, h; // 1. 准备消息调度表W for (int t = 0; t < 16; t++) { W[t] = (block[t*4] << 24) | (block[t*4+1] << 16) | (block[t*4+2] << 8) | block[t*4+3]; } for (int t = 16; t < 64; t++) { W[t] = SIG1(W[t-2]) + W[t-7] + SIG0(W[t-15]) + W[t-16]; } // 2. 初始化工作变量 a = hash[0]; b = hash[1]; c = hash[2]; d = hash[3]; e = hash[4]; f = hash[5]; g = hash[6]; h = hash[7]; // 3. 执行64轮压缩 for (int t = 0; t < 64; t++) { uint32_t T1 = h + EP1(e) + CH(e,f,g) + K[t] + W[t]; uint32_t T2 = EP0(a) + MAJ(a,b,c); h = g; g = f; f = e; e = d + T1; d = c; c = b; b = a; a = T1 + T2; } // 4. 更新哈希值 hash[0] += a; hash[1] += b; hash[2] += c; hash[3] += d; hash[4] += e; hash[5] += f; hash[6] += g; hash[7] += h; }4. 完整SHA-256实现
现在我们将各个部分组合成完整的实现:
#include <stdio.h> #include <stdint.h> #include <string.h> typedef struct { uint8_t data[64]; uint32_t datalen; uint64_t bitlen; uint32_t state[8]; } SHA256_CTX; void sha256_init(SHA256_CTX *ctx) { ctx->datalen = 0; ctx->bitlen = 0; memcpy(ctx->state, H, 8 * sizeof(uint32_t)); } void sha256_update(SHA256_CTX *ctx, const uint8_t *data, size_t len) { for (size_t i = 0; i < len; i++) { ctx->data[ctx->datalen] = data[i]; ctx->datalen++; if (ctx->datalen == 64) { process_block(ctx->data, ctx->state); ctx->bitlen += 512; ctx->datalen = 0; } } } void sha256_final(SHA256_CTX *ctx, uint8_t *hash) { uint32_t i = ctx->datalen; // 填充消息 if (ctx->datalen < 56) { ctx->data[i++] = 0x80; while (i < 56) ctx->data[i++] = 0x00; } else { ctx->data[i++] = 0x80; while (i < 64) ctx->data[i++] = 0x00; process_block(ctx->data, ctx->state); memset(ctx->data, 0, 56); } // 添加位长度 ctx->bitlen += ctx->datalen * 8; for (i = 0; i < 8; i++) { ctx->data[56 + i] = (ctx->bitlen >> (56 - i * 8)) & 0xFF; } process_block(ctx->data, ctx->state); // 生成最终哈希值 for (i = 0; i < 8; i++) { hash[i*4] = (ctx->state[i] >> 24) & 0xFF; hash[i*4+1] = (ctx->state[i] >> 16) & 0xFF; hash[i*4+2] = (ctx->state[i] >> 8) & 0xFF; hash[i*4+3] = ctx->state[i] & 0xFF; } }5. 测试我们的实现
让我们用经典的"abc"字符串测试我们的实现:
int main() { SHA256_CTX ctx; uint8_t hash[32]; char *msg = "abc"; sha256_init(&ctx); sha256_update(&ctx, (uint8_t*)msg, strlen(msg)); sha256_final(&ctx, hash); printf("SHA-256 hash of '%s':\n", msg); for (int i = 0; i < 32; i++) { printf("%02x", hash[i]); } printf("\n"); return 0; }正确输出应该是:
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad6. SHA-256与比特币挖矿
现在你理解了SHA-256的工作原理,就能真正理解比特币挖矿的本质。矿工们实际上是在寻找一个随机数(nonce),使得:
SHA-256(SHA-256(区块头)) < 目标难度值这个过程的计算复杂度正是比特币安全性的基础。我们实现的SHA-256虽然功能完整,但为了实际挖矿,你还需要:
- 优化实现(如使用SIMD指令)
- 添加矿工逻辑(改变nonce并重复计算)
- 连接比特币网络验证区块
在开发过程中,我遇到的一个有趣现象是:即使使用相同的代码,在不同平台上可能会因为字节序问题得到不同的结果。这提醒我们密码学实现中细节的重要性——一个微小的错误就可能完全破坏安全性。