7个版本
0.1.6 | 2023年10月13日 |
---|---|
0.1.5 | 2023年2月16日 |
0.1.4 | 2022年10月6日 |
0.1.3 | 2021年5月19日 |
0.0.0 | 2020年12月11日 |
#75 在 编码
73,818 每月下载量
用于 235 个crate(109 个直接使用)
71KB
1K SLoC
二进制规范序列化 (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 位整数
- 选项
- 单元(一个空值)
- 固定和可变长度序列
- UTF-8 编码字符串
- 元组
- 结构体(即“结构”)
- 外部标记的枚举(即“枚举”)
- 映射
BCS不是一个自描述的格式。因此,为了反序列化消息,必须事先知道消息类型和布局。
除非指定,所有数字都存储在小端、二进制补码格式中。
BCS数据的递归和深度
允许递归数据结构(例如树)。然而,由于(反)序列化过程中可能发生栈溢出,任何有效BCS数据的 容器深度 不能超过常量 MAX_CONTAINER_DEPTH
。正式地,我们定义 容器深度 为(反)序列化过程中遍历的结构和枚举的数量。
此定义旨在最大限度地减少操作次数,同时确保已知BCS格式的(反)序列化不会导致任意大的栈分配。
例如,如果 v1
和 v2
是深度为 n1
和 n2
的值,
- 结构值
{ 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将其表示为一个单字节,表示数据的存在(0x01
)或不存在(0x00
)。如果数据存在,则随后的序列化形式跟随。例如
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编码的元组数 precedes。
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)?);
贡献
有关如何帮助的信息,请参阅CONTRIBUTING文件。
许可证
本项目可在Apache 2.0许可证的条款下使用。Apache 2.0许可证。
依赖
~0.4–1MB
~22K SLoC