1 个不稳定版本

0.1.3 2022年11月6日

#4#diem


2 crate 中使用

Apache-2.0

64KB
1K SLoC

Build Status License bcs on crates.io Documentation (latest release) Documentation (master)

二进制规范序列化 (BCS)

BCS(以前称为“Libra规范序列化”或LCS)是在Diem区块链环境中开发的一种序列化格式。

BCS的以下主要目标被考虑在内

  • 提供良好的性能和简洁(二进制)表示;
  • 支持Rust中常用的丰富数据类型集;
  • 强制执行规范序列化,意味着给定类型的每个值都应该有唯一的有效表示。

BCS还旨在通过在(反)序列化期间强制对大型或嵌套容器定义良好的限制来减轻恶意输入的后果。

Rust 实现

此crate提供了BCS的Rust实现,作为Serde库的编码格式。因此,此实现涵盖了Serde支持的几乎所有数据类型,包括用户定义的结构体、标记变体(Rust枚举)、元组和映射,但不包括浮点数、单个Unicode字符(char)和集合。

由于单独的项目serde-reflection,BCS也适用于其他编程语言。

在密码学中的应用

BCS格式保证了规范序列化,这意味着对于任何给定的数据类型,内存值和有效字节表示之间存在一对一的对应关系。

在密码学应用的背景下,规范序列化具有以下好处

  • 它提供了一种自然且可靠的方式来将内存值与密码学散列相关联。
  • 它允许将消息的签名定义为序列化字节的签名或内存值的签名。

请注意,BCS为每个数据类型分别确保了规范序列化。序列化值的类型必须由应用程序本身强制执行。通常使用每种数据类型的唯一哈希种子来满足此要求。(参见Diem的密码学库中的示例。)

向后兼容性

BCS的设计不提供隐式版本或前后兼容性,因此应用程序必须提前仔细规划临时扩展点。

  • 枚举可以用于显式版本和向后兼容(例如可扩展查询接口)。
  • 在某些情况下,可能还会添加类型为 Vec<u8> 的数据字段,以便在序列化形式中允许(未来的)未知负载。

详细规范

BCS支持以下数据类型

  • 布尔值
  • 有符号8位、16位、32位、64位和128位整数
  • 无符号8位、16位、32位、64位和128位整数
  • Option
  • Unit(一个空值)
  • 固定和可变长度序列
  • UTF-8编码字符串
  • 元组
  • 结构体(也称为“structs”)
  • 外部标记的枚举(也称为“枚举”)
  • 映射

BCS不是自描述的格式。因此,为了反序列化消息,必须提前知道消息类型和布局。

除非指定,否则所有数字都以小端、二进制补码格式存储。

BCS数据的递归和深度

递归数据结构(例如树)是被允许的。然而,由于(反)序列化过程中可能发生栈溢出,任何有效BCS数据的 容器深度 不能超过常量 MAX_CONTAINER_DEPTH。正式来说,我们定义 容器深度 为(反)序列化过程中遍历的结构和枚举的数量。

此定义旨在最小化操作次数,同时确保已知BCS格式的(反)序列化不会导致任意大的栈分配。

例如,如果 v1v2 是深度为 n1n2 的值,

  • 一个结构值 Foo { v1, v2 } 的深度为 1 + max(n1, n2)
  • 一个枚举值 E::Foo { v1, v2 } 的深度为 1 + max(n1, n2)
  • 一个对 (v1, v2) 的深度为 max(n1, n2)
  • Some(v1) 的深度为 n1

所有字符串和整数值的深度为 0

布尔值和整数

类型 原始数据 十六进制表示 序列化字节
布尔值 真/假 0x01 / 0x00 01 / 00
8位有符号整数 -1 0xFF FF
8位无符号整数 1 0x01 01
16位有符号整数 -4660 0xEDCC CC ED
16位无符号整数 4660 0x1234 34 12
32位有符号整数 -305419896 0xEDCBA988 88 A9 CB ED
32位无符号整数 305419896 0x12345678 78 56 34 12
64位有符号整数 -1311768467750121216 0xEDCBA98754321100 00 11 32 54 87 A9 CB ED
64位无符号整数 1311768467750121216 0x12345678ABCDEF00 00 EF CD AB 78 56 34 12

ULEB128编码的整数

BCS 格式也使用 ULEB128 编码 在内部表示无符号 32 位整数,在通常期望小值的情况下使用两种情况:(1) 可变长度序列的长度和(2) 枚举值的标记(请参阅下文相关部分)。

类型 原始数据 十六进制表示 序列化字节
ULEB128 编码的 u32 整数 2^0 = 1 0x00000001 01
2^7 = 128 0x00000080 80 01
2^14 = 16384 0x00004000 80 80 01
2^21 = 2097152 0x00200000 80 80 80 01
2^28 = 268435456 0x10000000 80 80 80 80 01
9487 0x0000250f 8f 4a

一般来说,ULEB128 编码由一系列基数为 128(7 位)的数字组成。每个数字通过将最高位设置为 1 来完成为字节,除了最后一个(最高有效位)数字,其最高位设置为 0。

在 BCS 中,解码 ULEB128 字节的结果必须适合 32 位无符号整数,并且是规范形式。例如,以下值被拒绝

  • 80 80 80 80 80 01 (2^36) 太大了。
  • 80 80 80 80 10 (2^33) 太大了。
  • 80 00 不是 0 的最小编码。

可选数据

可选或可为空的数据要么以完整表示存在,要么不存在。BCS 将其表示为表示数据存在或不存在的一个字节,即 0x010x00。如果数据存在,则随后跟随着该数据的序列化形式。例如

let some_data: Option<u8> = Some(8);
assert_eq!(to_bytes(&some_data)?, vec![1, 8]);

let no_data: Option<u8> = None;
assert_eq!(to_bytes(&no_data)?, vec![0]);

固定和可变长度序列

序列可以由 BCS 支持的任何类型(甚至是复杂结构)组成,但序列中的所有元素必须是同一类型。如果序列的长度是固定的且已知,那么 BCS 将其表示为序列中每个单独元素序列化的简单连接。如果序列的长度是可变的,那么序列化的序列将使用一个 ULEB128 编码的无符号整数作为长度前缀,表示序列中的元素数量。所有可变长度序列必须小于或等于 MAX_SEQUENCE_LENGTH 个元素。

let fixed: [u16; 3] = [1, 2, 3];
assert_eq!(to_bytes(&fixed)?, vec![1, 0, 2, 0, 3, 0]);

let variable: Vec<u16> = vec![1, 2];
assert_eq!(to_bytes(&variable)?, vec![2, 1, 0, 2, 0]);

let large_variable_length: Vec<()> = vec![(); 9_487];
assert_eq!(to_bytes(&large_variable_length)?, vec![0x8f, 0x4a]);

字符串

只支持有效的 UTF-8 字符串。BCS 将此类字符串序列化为可变长度的字节序列,即以 ULEB128 编码的无符号整数作为长度前缀,后跟字符串的字节表示。

// Note that this string has 10 characters but has a byte length of 24
let utf8_str = "çå∞≠¢õß∂ƒ∫";
let expecting = vec![
    24, 0xc3, 0xa7, 0xc3, 0xa5, 0xe2, 0x88, 0x9e, 0xe2, 0x89, 0xa0, 0xc2,
    0xa2, 0xc3, 0xb5, 0xc3, 0x9f, 0xe2, 0x88, 0x82, 0xc6, 0x92, 0xe2, 0x88, 0xab,
];
assert_eq!(to_bytes(&utf8_str)?, expecting);

元组

元组是对象的类型化组合:(Type0, Type1)

元组被视为一个固定长度序列,其中序列中的每个元素可以是 BCS 支持的任何不同类型。元组的每个元素按在元组中定义的顺序序列化,即 [tuple.0, tuple.2]。

let tuple = (-1i8, "diem");
let expecting = vec![0xFF, 4, b'd', b'i', b'e', b'm'];
assert_eq!(to_bytes(&tuple)?, expecting);

结构体

结构体是由可能具有不同类型的字段组成的固定长度序列。结构体中的每个字段按照规范结构定义中指定的顺序序列化。结构体可以存在于其他结构体中,因此 BCS 会递归到每个结构体中并按顺序序列化它们。序列化格式中没有标签,结构体顺序定义了序列化流中的组织。

#[derive(Serialize)]
struct MyStruct {
    boolean: bool,
    bytes: Vec<u8>,
    label: String,
}

#[derive(Serialize)]
struct Wrapper {
    inner: MyStruct,
    name: String,
}

let s = MyStruct {
    boolean: true,
    bytes: vec![0xC0, 0xDE],
    label: "a".to_owned(),
};
let s_bytes = to_bytes(&s)?;
let mut expecting = vec![1, 2, 0xC0, 0xDE, 1, b'a'];
assert_eq!(s_bytes, expecting);

let w = Wrapper {
    inner: s,
    name: "b".to_owned(),
};
let w_bytes = to_bytes(&w)?;
assert!(w_bytes.starts_with(&s_bytes));

expecting.append(&mut vec![1, b'b']);
assert_eq!(w_bytes, expecting);

外部标记枚举

枚举通常表示为可以取许多不同变体的类型。在 BCS 中,每个变体都映射到一个变体索引,这是一个 ULEB128 编码的 32 位无符号整数,后跟如果有关联值的话,序列化的数据。关联类型可以是任何 BCS 支持的类型。变体索引是根据规范枚举定义中变体的顺序确定的,其中第一个变体索引为 0,第二个为 1,等等。

#[derive(Serialize)]
enum E {
    Variant0(u16),
    Variant1(u8),
    Variant2(String),
}

let v0 = E::Variant0(8000);
let v1 = E::Variant1(255);
let v2 = E::Variant2("e".to_owned());

assert_eq!(to_bytes(&v0)?, vec![0, 0x40, 0x1F]);
assert_eq!(to_bytes(&v1)?, vec![1, 0xFF]);
assert_eq!(to_bytes(&v2)?, vec![2, 1, b'e']);

如果您需要序列化 C 风格的枚举,应使用原始整数类型。

映射(键/值存储)

地图表示为一系列长度可变的、排序的(键,值)元组。键必须是唯一的,元组按照每个键的BCS字节按字典顺序递增排序。表示方式与可变长度序列类似。特别是,它以ULEB128编码的元组数量开头。

let mut map = HashMap::new();
map.insert(b'e', b'f');
map.insert(b'a', b'b');
map.insert(b'c', b'd');

let expecting = vec![(b'a', b'b'), (b'c', b'd'), (b'e', b'f')];

assert_eq!(to_bytes(&map)?, to_bytes(&expecting)?);

贡献

有关如何帮助的详细信息,请参阅贡献文件。

许可证

本项目可在Apache 2.0许可证的条款下使用。Apache 2.0许可证

依赖关系

~0.4–1MB
~23K SLoC