Saltearse al contenido

Serialización Canónica Binaria (BCS)

La Serialización Canónica Binaria (BCS) es el formato de serialización usado en la blockchain de Aptos. Es un formato de serialización binario canónico no autodescriptivo que se usa para serializar estructuras de datos. BCS se usa para serializar todos los datos en la cadena, proporcionar respuestas binarias en la API REST, y codificar argumentos de entrada a transacciones.

Debido a que BCS no es un formato autodescriptivo, el lector debe conocer el formato de los bytes de antemano.

Se soportan enteros sin signo de 8-bit, 16-bit, 32-bit, 64-bit, 128-bit y 256-bit. Se serializan en orden de bytes little-endian.

Los booleanos se serializan como un solo byte. true se serializa como 0x01 y false se serializa como 0x00. Todos los otros valores son inválidos.

ValorBytes
true0x01
false0x00
#[test_only]
module 0x42::example {
use std::bcs;
use std::from_bcs;
#[test]
fun test_bool() {
// Serializar
let val: bool = true;
let bytes: vector<u8> = bcs::to_bytes(&val);
assert!(bytes == vector[0x01]);
// Deserializar
let val_des = from_bcs::to_bool(bytes);
assert!(val_des == true);
}
}

Unsigned 8-bit integers are serialized as a single byte.

#[test_only]
module 0x42::example {
use std::bcs;
use std::from_bcs;
#[test]
fun test_u8() {
// Serializar
let val: u8 = 1;
let bytes: vector<u8> = bcs::to_bytes(&val);
assert!(bytes == vector[0x01]);
// Deserializar
let val_des = from_bcs::to_u8(bytes);
assert!(val_des == 1);
}
}

Unsigned 16-bit integers are serialized as 2 bytes in little-endian byte order.

#[test_only]
module 0x42::example {
use std::bcs;
use std::from_bcs;
#[test]
fun test_u16() {
// Serializar
let val: u16 = 1000;
let bytes: vector<u8> = bcs::to_bytes(&val);
assert!(bytes == vector[0xe8, 0x03]);
// Deserializar
let val_des = from_bcs::to_u16(bytes);
assert!(val_des == 1000);
}
}

Unsigned 32-bit integers are serialized as 4 bytes in little-endian byte order.

#[test_only]
module 0x42::example {
use std::bcs;
use std::from_bcs;
#[test]
fun test_u32() {
// Serializar
let val: u32 = 1000000000;
let bytes: vector<u8> = bcs::to_bytes(&val);
assert!(bytes == vector[0x00, 0xca, 0x9a, 0x3b]);
// Deserializar
let val_des = from_bcs::to_u32(bytes);
assert!(val_des == 1000000000);
}
}

Unsigned 64-bit integers are serialized as 8 bytes in little-endian byte order.

#[test_only]
module 0x42::example {
use std::bcs;
use std::from_bcs;
#[test]
fun test_u64() {
// Serializar
let val: u64 = 10000000000000000;
let bytes: vector<u8> = bcs::to_bytes(&val);
assert!(bytes == vector[0x00, 0x40, 0x9c, 0x4f, 0x2c, 0x68, 0x00, 0x00]);
// Deserializar
let val_des = from_bcs::to_u64(bytes);
assert!(val_des == 10000000000000000);
}
}

Unsigned 128-bit integers are serialized as 16 bytes in little-endian byte order.

#[test_only]
module 0x42::example {
use std::bcs;
use std::from_bcs;
#[test]
fun test_u128() {
// Serializar
let val: u128 = 10000000000000000;
let bytes: vector<u8> = bcs::to_bytes(&val);
assert!(bytes == vector[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x9c, 0x4f, 0x2c, 0x68, 0x00, 0x00]);
// Deserializar
let val_des = from_bcs::to_u128(bytes);
assert!(val_des == 10000000000000000);
}
}

Unsigned 256-bit integers are serialized as 32 bytes in little-endian byte order.

#[test_only]
module 0x42::example {
use std::bcs;
use std::from_bcs;
#[test]
fun test_u256() {
// Serializar
let val: u256 = 10000000000000000;
let bytes: vector<u8> = bcs::to_bytes(&val);
assert!(bytes == vector[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x9c, 0x4f, 0x2c, 0x68, 0x00, 0x00]);
// Deserializar
let val_des = from_bcs::to_u256(bytes);
assert!(val_des == 10000000000000000);
}
}

Uleb128 (unsigned 128-bit variable length integer)

Sección titulada «Uleb128 (unsigned 128-bit variable length integer)»

Unsigned 128-bit variable length integers are serialized as a variable number of bytes. The most significant bit of each byte is used to indicate if there are more bytes to read. The remaining 7 bits are used to store the value.

This is common used for variable lengths of vectors, or for enums.

// Currently not supported by itself in Move

Sequences are serialized as a variable length vector of an item. The length of the vector is serialized as a Uleb128 followed by repeated items. FixedSequences are serialized without the leading size byte. The reader must know the number of bytes prior to deserialization.

#[test_only]
module 0x42::example {
use std::bcs;
use std::from_bcs;
#[test]
fun test_vector() {
// Serializar
let val = vector[1u8, 2u8, 3u8];
let bytes = bcs::to_bytes(&val);
assert!(bytes == vector[3, 1, 2, 3]);
// Deserializar, solo soporta bytes por ahora
let val_des = from_bcs::to_bytes(bytes);
assert!(val_des == vector[1, 2, 3]);
}
}

Strings are serialized as a vector of bytes, however the bytes are encoded as UTF-8.

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

AccountAddress is serialized as a fixed 32 byte vector of bytes.

@0x1 => [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01]

Structs are serialized as an ordered set of fields. The fields are serialized in the order they are defined in the struct.

struct Color {
r: u8 = 1,
g: u8 = 2,
b: u8 = 3,
} => [0x01, 0x02, 0x03]

Options are serialized as a single byte to determine whether it’s filled. If the option is None, the byte is 0x00. If the option is Some, the byte is 0x01 followed by the serialized value.

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

Enums are serialized as a uleb128 to determine which variant is being used. The size is followed by the serialized value of the variant.

#[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']);

Maps are stored as a sequence of tuples. The length of the map is serialized as a Uleb128 followed by repeated key-value pairs.

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