Skip to main content

二進位規範序列化 (Binary Canonical Serialization, BCS)

二進位規範序列化 (BCS) 是一種用於結構化資料的二進位編碼格式。它最初是為 Diem 設計的,後來成為 Move 的標準序列化格式。BCS 簡單、高效、具有確定性,且易於在任何程式語言中實作。

完整的格式規範可在 BCS 儲存庫 中找到。

格式

BCS 是一種支援高達 256 位元的無符號整數、選項 (options)、布林值、單元 (unit,空值)、固定和可變長度序列以及映射 (maps) 的二進位格式。該格式旨在具有確定性,這意味著相同的資料將始終被序列化為相同的位元組。

「BCS 不是一種自帶描述 (self-describing) 的格式。因此,為了反序列化訊息,必須提前知道訊息類型和佈局。」 —— 摘自 README

整數以小端 (little-endian) 格式存儲,可變長度整數使用可變長度編碼方案進行編碼。序列會以 ULEB128 編碼的長度作為前綴,列舉 (enumerations) 存儲為變體的索引後接資料,而映射則存儲為鍵值對的有序序列。

結構 (Structs) 被視為欄位序列,欄位按其在結構中定義的順序進行序列化。欄位使用與頂層資料相同的規則進行序列化。

使用 BCS

Sui 框架 包含用於編碼和解碼資料的 sui::bcs 模組。編碼函式在 VM 中原生實作,解碼函式則是在 Move 中實作。

編碼 (Encoding)

若要編碼資料,請使用 bcs::to_bytes 函式,它將資料參考轉換為位元組向量。此函式支援編碼任何類型,包括結構。

module std::bcs;

public native fun to_bytes<T>(t: &T): vector<u8>;

以下範例展示了如何使用 BCS 編碼結構。to_bytes 函式可以接收任何數值並將其編碼為位元組向量。

use sui::bcs;

// 0x01 - 單個位元組,值為 1(或 false 為 0)
let bool_bytes = bcs::to_bytes(&true);
assert_eq!(bool_bytes, x"01");

// 0x2a - 只是單個位元組
let u8_bytes = bcs::to_bytes(&42u8);
assert_eq!(u8_bytes, x"2A");

// 0x2a00000000000000 - 8 個位元組
let u64_bytes = bcs::to_bytes(&42u64);
assert_eq!(u64_bytes, x"2A00000000000000");

// 位址是 32 個位元組的固定序列
// 0x0000000000000000000000000000000000000000000000000000000000000002
let addr = bcs::to_bytes(&@sui);
assert_eq!(addr, x"0000000000000000000000000000000000000000000000000000000000000002");

編碼結構

結構的編碼方式與簡單類型相似。以下是如何使用 BCS 編碼結構:

let data = CustomData {
num: 42,
string: b"hello, world!".to_string(),
value: true
};

let struct_bytes = bcs::to_bytes(&data);

let mut custom_bytes = vector[];
custom_bytes.append(bcs::to_bytes(&42u8));
custom_bytes.append(bcs::to_bytes(&b"hello, world!".to_string()));
custom_bytes.append(bcs::to_bytes(&true));

// 結構只是欄位的序列,所以位元組應該相同!
assert_eq!(struct_bytes, custom_bytes);

解碼 (Decoding)

由於 BCS 不是自帶描述的格式,解碼需要預先知道資料類型。sui::bcs 模組提供了多種函式來協助此過程。

封裝 API (Wrapper API)

BCS 在 Move 中是以封裝器 (wrapper) 的形式實作的。解碼器按值接收位元組,然後允許呼叫者透過呼叫以 peel_* 為前綴的不同解碼函式來「剝離 (peel off)」資料。資料從位元組中提取,剩餘的位元組保留在封裝器中,直到呼叫 into_remainder_bytes 函式。

use sui::bcs;

// BCS 實例應該總是宣告為可變
let mut bcs = bcs::new(x"010000000000000000");

// 相同的位元組可以以不同方式讀取,例如:Option<u64>
let value: Option<u64> = bcs.peel_option_u64();

assert_eq!(value.is_some(), true);
assert_eq!(*value.borrow(), 0);

let remainder = bcs.into_remainder_bytes();

assert_eq!(remainder.length(), 0);

一種常見的實作是在解碼過程中的單個 let 語句中使用多個變數。這使得程式碼更具可讀性,並有助於避免不必要的資料複製。

let mut bcs = bcs::new(x"0101010F0000000000F00000000000");

// 注意順序!!!
// 提取多個值的便利方式
let (bool_value, u8_value, u64_value) = (
bcs.peel_bool(),
bcs.peel_u8(),
bcs.peel_u64()
);

解碼向量 (Decoding Vectors)

雖然大多數原始類型都有專用的解碼函式,但向量需要特殊處理,這取決於元素的類型。對於向量,首先需要解碼向量的長度,然後在迴圈中解碼每個元素。

let mut bcs = bcs::new(x"0101010F0000000000F00000000000");

// bcs.peel_vec_length() 提取向量的長度 :)
let mut len = bcs.peel_vec_length();
let mut vec = vector[];

// 然後根據資料型別迭代
while (len > 0) {
vec.push_back(bcs.peel_u64()); // 或任何其他型別
len = len - 1;
};

assert_eq!(vec.length(), 1);

// 上面的 `while` 可以使用 `巨集` 簡化並使其更易讀。
// bcs.peel_vec!(|bcs| bcs.peel_u64()) 等效於上面的 `while` 迴圈。

此功能由函式庫提供,表現為巨集 peel_vec!。它根據向量長度重複呼叫內部表達式,並將結果匯總到單個向量中。

let u64_vec = bcs.peel_vec!(|bcs| bcs.peel_u64());
let address_vec = bcs.peel_vec!(|bcs| bcs.peel_address());

// 注意:僅當 `MyStruct` 定義在目前模組中時才可行!
let my_struct = bcs.peel_vec!(|bcs| MyStruct {
user_addr: bcs.peel_address(),
age: bcs.peel_u8(),
});

解碼選項 (Decoding Option)

Move 中的 Option 被表示為具有 0 或 1 個元素的向量。要讀取選項,您可以像處理向量一樣處理它,並檢查其長度(第一個位元組 —— 不是 1 就是 0)。

let mut bcs = bcs::new(x"00");
let is_some = bcs.peel_bool();

assert_eq!(is_some, false);

let mut bcs = bcs::new(x"0101");
let is_some = bcs.peel_bool();
let value = bcs.peel_u8();

assert_eq!(is_some, true);
assert_eq!(value, 1);

向量 類似,這裡也有一個封裝巨集 peel_option!,它會檢查變體索引,並在底層數值為 some 時評估表達式。

let u8_opt = bcs.peel_option!(|bcs| bcs.peel_u8());
let bool_opt = bcs.peel_option!(|bcs| bcs.peel_bool());

解碼結構

結構是逐個欄位解碼的,目前沒有自動將位元組解碼為 Move 結構的方法。要將位元組解析為結構,您需要解碼每個欄位並實例化該類型。

let mut bcs = bcs::new(x"0101010F0000000000F00000000000");

// 注意:順序很重要!
let user = User {
age: bcs.peel_u8(),
is_active: bcs.peel_bool(),
name: bcs.peel_vec_u8().to_string()
};

總結

二進位規範序列化是一種高效的結構化資料二進位格式,確保了跨平台的一致序列化。Sui 框架提供了全面的工具來處理 BCS,透過內建函式實作廣泛的功能。