2 个不稳定版本

0.1.0 2020年11月17日
0.0.0 2019年10月11日

#1737 in 神奇豆子

Apache-2.0

60KB
968

Libra 正规序列化(LCS)

LCS 定义了一种确定性的方法,用于将消息或数据结构转换为字节序列,不论平台、架构或编程语言。

背景

在 Libra 中,参与者传递消息或数据结构,这些消息或数据结构通常需要由证明者签名并由一个或多个验证者验证。在此背景下,序列化是指将消息转换为字节数组的过程。许多序列化方法支持宽松的标准,使得两个实现可以产生两个不同的字节流,它们可以表示相同的消息。虽然对于许多应用来说,非确定性的序列化不会引起问题,但它对使用序列化进行加密目的的应用来说却是一个问题。例如,给定一个签名和一个消息,验证者可能无法产生与证明者在签名消息时构建的相同的序列化字节数组,导致无法验证的消息。换句话说,为了在使用非确定性序列化时确保消息的可验证性,参与者必须保留原始序列化字节或冒着失去验证消息的能力的风险。这给参与者带来了一项负担,要求他们维护序列化字节和反序列化消息的副本,这往往导致对安全和正确性的混淆。虽然存在一些现有的确定性序列化格式,但没有明显的选择。为了解决这个问题,我们提出了 Libra 正规序列化,它定义了一种将消息转换为字节序列并再次转换回的方法。

规范

LCS支持以下数据类型

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

通用结构

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

除非特别说明,所有数字都以小端、二进制补码格式存储。

LCS数据递归和深度

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

此定义旨在尽量减少操作次数,同时确保已知LCS格式的(反)序列化不会引起任意大的栈分配。

例如,如果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 [CCED]
16位无符号整数 4660 0x1234 [3412]
32位有符号整数 -305419896 0xEDCBA988 [88A9CBED]
32位无符号整数 305419896 0x12345678 [78563412]
64位有符号整数 -1311768467750121216 0xEDCBA98754321100 [0011325487A9CBED]
64位无符号整数 1311768467750121216 0x12345678ABCDEF00 [00EFCDAB78563412]

ULEB128编码整数

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

类型 原始数据 十六进制表示 序列化格式
ULEB128编码的u32整数 2^0 = 1 0x00000001 [01]
2^7 = 128 0x00000080 [8001]
2^14 = 16384 0x00004000 [808001]
2^21 = 2097152 0x00200000 [80808001]
2^28 = 268435456 0x10000000 [8080808001]
9487 0x0000250f [8f4a]

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

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

  • [808080808001](2^36)太大。
  • [8080808010](2^33)太大。
  • [8000]不是0的最小编码。

可选数据

可选或可空数据要么以完整表示存在,要么不存在。LCS将其表示为一个单字节,表示数据的存在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]);

固定和可变长度序列

序列可以由LCS支持的任何类型(甚至是复杂结构)组成,但序列中的所有元素必须具有相同的类型。如果序列的长度是固定的并且是已知的,则LCS将其表示为序列中每个单独元素序列化形式的串联。如果序列的长度是可变的,则序列化的序列是以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字符串。LCS将这些字符串序列化为可变长度字节序列,即以一个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)

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

let tuple = (-1i8, "libra");
let expecting = vec![0xFF, 5, b'l', b'i', b'b', b'r', b'a'];
assert_eq!(to_bytes(&tuple)?, expecting);

结构

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

struct MyStruct {
    boolean: bool,
    bytes: Vec<u8>,
    label: String,
}

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);

外部标记枚举

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

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样式枚举,应使用原始整数类型。

映射(键/值存储)

映射表示为可变长度、排序的(键,值)元组序列。键必须是唯一的,并且元组根据每个键的LCS字节按字典顺序排序。表示方式与其他可变长度序列类似。特别是,它以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)?);

向后兼容性

复杂类型依赖于它们所使用的规范。LCS没有提供直接针对版本或前后兼容性的规定。对象结构的变更可能阻止历史客户端理解新客户端,反之亦然。

贡献

有关如何帮助的说明,请参阅CONTRIBUTING文件。

许可证

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

依赖项

~0.4–1MB
~24K SLoC