比特币开发系列 - #1 序列化

比特币开发系列 - 1 序列化

理论够了,我们来写一些真正的代码吧!在处理完整的实体之前,你需要学习如何通过一小组基本数据类型来序列化通用数据。想想 C 变量,我们肯定希望在定义自定义struct之前知道intchar是什么意思。

请记住,小端序是默认的字节顺序。代码示例可能包括 common.h 用于通用例程和 endian.h 用于朴素的端序转换。哈希函数在 hash.h 中定义,借助于 OpenSSL。从现在开始,我希望你不会在指针算术中遇到麻烦。

整数

首先,在区块链中没有负整数的用处。整数始终是无符号的,它们可以保存 8 位、16 位、32 位或 64 位的值。在 ex-integers.c 中:

    uint8_t n8 = 0x01;
    uint16_t n16 = 0x4523;
    uint32_t n32 = 0xcdab8967;
    uint64_t n64 = 0xdebc9a78563412ef;

我们将“n8 + n16 + n32 + n64”序列化为(1 + 2 + 4 + 8) = 15 字节。在存储单个字节时,我们不关心字节顺序,但在所有其他情况下必须关心。这就是为什么多字节值必须强制使用小端序:

    uint8_t ser[15];

    *ser = n8;
    *(uint16_t *)(ser + 1) = bbp_eint16(BBP_LITTLE, n16);
    *(uint32_t *)(ser + 3) = bbp_eint32(BBP_LITTLE, n32);
    *(uint64_t *)(ser + 7) = bbp_eint64(BBP_LITTLE, n64);

如果机器是小端的,数字将被存储而无需额外操作。如果不是,则它们的字节将被颠倒。

生成的ser数组(15 字节):

    01
    23 45
    67 89 ab cd
    ef 12 34 56 78 9a bc de

固定长度数据

固定长度数据是指其长度事先已知,因此不需要附加长度。在实际代码中,memcpy是序列化二进制数据所需的全部。

以空字符填充的字符串

固定长度字符串以 UTF-8 编码,并用\0字符填充到所需长度。比特币 p2p 协议就是这种情况,其中消息由人类可读的名称标识,如versiontxgetblocks等,最大长度为 12 个字符。在 ex-fixed-strings.c 中:

    uint32_t n32 = 0x68f7a38b;
    char str[] = "FooBar";
    size_t str_len = 10;
    uint16_t n16 = 0xee12;

我们将“n32 + str + n16”序列化为(4 + 10 + 2) = 16 字节。可以安全地假设 ASCII 字符串免费编码为原始字节。需要实际字符串长度来计算填充:

    size_t str_real_len = strlen(str);
    size_t str_pad_len = str_len - str_real_len;

最终打包:

    uint8_t ser[16];

    *(uint32_t *)(ser) = bbp_eint32(BBP_LITTLE, n32);
    memcpy(ser + 4, str, str_real_len);
    if (str_pad_len > 0) {
        memset(ser + 4 + str_real_len, '\0', str_pad_len);
    }
    *(uint16_t *)(ser + 4 + str_len) = bbp_eint16(BBP_LITTLE, n16);

生成的ser数组(16 字节):

    8b a3 f7 68
    46 6f 6f 42 61 72 00 00 00 00
    12 ee

哈希值

哈希值是固定长度数据的另一个典型示例。在 ex-hashes.c 中(需要 OpenSSL):

    char message[] = "Hello Bitcoin!";
    uint16_t prefix = 0xd17f;
    uint8_t suffix = 0x8c;
    uint8_t digest[32];

我们将“prefix + hash256(message) + suffix”序列化为(2 + 32 + 1) = 35 字节。下面我们首先计算消息的 SHA-256 摘要:

    bbp_sha256(digest, (uint8_t *)message, strlen(message));

SHA-256 算法产生一个 256 位的摘要,因此我们提前分配一个 32 字节的数组。这里等效于SHA256_DIGEST_LENGTH,但我想尽可能明确。对于字符串“Hello Bitcoin!”的 SHA-256 摘要是:

    51 8a d5 a3 75 fa 52 f8
    4b 2b 3d f7 93 3a d6 85
    eb 62 cf 69 86 9a 96 73
    15 61 f9 4d 10 82 6b 5c

通过再次进行哈希:

    bbp_sha256(digest, digest, 32);

我们得到 hash256 摘要:

    90 98 6e a4 e2 8b 84 7c
    c7 f9 be ba 87 ea 81 b2
    21 ca 6e af 98 28 a8 b0
    4c 29 0c 21 d8 91 bc da

其中90是 MSB,因为 SHA-256 按大端工作。最终打包:

    uint8_t ser[35];

    *(uint16_t *)(ser) = bbp_eint16(BBP_LITTLE, prefix);
    memcpy(ser + 2, digest, 32);
    *(ser + 2 + 32) = suffix;

生成的ser数组(35 字节):

    7f d1
    90 98 6e a4 e2 8b 84 7c
    c7 f9 be ba 87 ea 81 b2
    21 ca 6e af 98 28 a8 b0
    4c 29 0c 21 d8 91 bc da
    8c

获取代码

完整源代码在 GitHub 上。

下一篇

你已经学会了如何为区块链序列化固定长度数据。

本系列序列化第二部分中,我们将处理可变长度数据。如果你喜欢这篇文章,请分享 。

我是 AI 翻译官,为大家转译优秀英文文章,如有翻译不通的地方,在这里修改,还请包涵~

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Davide De Rosa
Davide De Rosa
江湖只有他的大名,没有他的介绍。