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.
Resumen
Sección titulada «Resumen»Debido a que BCS no es un formato autodescriptivo, el lector debe conocer el formato de los bytes de antemano.
Tipos Primitivos
Sección titulada «Tipos Primitivos»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.
Bool (booleano)
Sección titulada «Bool (booleano)»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.
Valor | Bytes |
---|---|
true | 0x01 |
false | 0x00 |
#[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); }}
// Serializarlet val: bool = true;let bytes: Vec<u8> = bcs::to_bytes(&val).unwrap();assert_eq!(bytes, vec![0x01]);
// Deserializarlet val_des = bcs::from_bytes::<bool>(&bytes).unwrap();assert_eq!(val_des, true);
import { Serializer, Deserializer } from "@aptos-labs/ts-sdk";
// Serializarconst ser = new Serializer();ser.serializeBool(true);const bytes = ser.toUint8Array();console.log(bytes == Uint8Array.from([1]));
// Deserializarconst des = new Deserializer(bytes);const val = des.deserializeBool();console.log(val == true);
import ( "github.com/aptos-labs/aptos-go-sdk")
func main() { // Serializar ser := bcs.Serializer{} ser.Bool(true) trueBytes := ser.ToBytes() trueBytes == []byte{0x01}
// Deserializar des := bcs.NewDeserializer(trueBytes) val := des.Bool() val == true}
U8 (unsigned 8-bit integer)
Sección titulada «U8 (unsigned 8-bit integer)»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); }}
// Serializarlet val: u8 = 1;let bytes: Vec<u8> = bcs::to_bytes(&val).unwrap();assert_eq!(bytes, vec![0x01]);
// Deserializarlet val_des = bcs::from_bytes::<u8>(&bytes).unwrap();assert_eq!(val_des, 1);
import { Serializer, Deserializer } from "@aptos-labs/ts-sdk";
// Serializarconst ser = new Serializer();ser.serializeU8(1);const bytes = ser.toUint8Array();console.log(bytes == Uint8Array.from([1]));
// Deserializarconst des = new Deserializer(bytes);const val = des.deserializeU8();console.log(val == 1);
import ( "github.com/aptos-labs/aptos-go-sdk")
func main() { // Serializar ser := bcs.Serializer{} ser.U8(1) trueBytes := ser.ToBytes() trueBytes == []byte{0x01}
// Deserializar des := bcs.NewDeserializer(trueBytes) val := des.U8() val == 1}
U16 (unsigned 16-bit integer)
Sección titulada «U16 (unsigned 16-bit integer)»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); }}
// Serializarlet val: u16 = 1000;let bytes: Vec<u8> = bcs::to_bytes(&val).unwrap();assert_eq!(bytes, vec![0xe8, 0x03]);
// Deserializarlet val_des = bcs::from_bytes::<u16>(&bytes).unwrap();assert_eq!(val_des, 1000);
import { Serializer, Deserializer } from "@aptos-labs/ts-sdk";
// Serializarconst ser = new Serializer();ser.serializeU16(1000);const bytes = ser.toUint8Array();console.log(bytes == Uint8Array.from([0xe8, 0x03]));
// Deserializarconst des = new Deserializer(bytes);const val = des.deserializeU16();console.log(val == 1000);
import ( "github.com/aptos-labs/aptos-go-sdk")
func main() { // Serializar ser := bcs.Serializer{} ser.U16(1000) bytes := ser.ToBytes() bytes == []byte{0xe8, 0x03}
// Deserializar des := bcs.NewDeserializer(bytes) val := des.U16() val == 1000}
U32 (unsigned 32-bit integer)
Sección titulada «U32 (unsigned 32-bit integer)»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); }}
// Serializarlet val: u32 = 1000000000;let bytes: Vec<u8> = bcs::to_bytes(&val).unwrap();assert_eq!(bytes, vec![0x00, 0xca, 0x9a, 0x3b]);
// Deserializarlet val_des = bcs::from_bytes::<u32>(&bytes).unwrap();assert_eq!(val_des, 1000000000);
import { Serializer, Deserializer } from "@aptos-labs/ts-sdk";
// Serializarconst ser = new Serializer();ser.serializeU32(1000000000);const bytes = ser.toUint8Array();console.log(bytes == Uint8Array.from([0x00, 0xca, 0x9a, 0x3b]));
// Deserializarconst des = new Deserializer(bytes);const val = des.deserializeU32();console.log(val == 1000000000);
import ( "github.com/aptos-labs/aptos-go-sdk")
func main() { // Serializar ser := bcs.Serializer{} ser.U32(1000000000) bytes := ser.ToBytes() bytes == []byte{0x00, 0xca, 0x9a, 0x3b}
// Deserializar des := bcs.NewDeserializer(bytes) val := des.U32() val == 1000000000}
U64 (unsigned 64-bit integer)
Sección titulada «U64 (unsigned 64-bit integer)»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); }}
// Serializarlet val: u64 = 10000000000000000;let bytes: Vec<u8> = bcs::to_bytes(&val).unwrap();assert_eq!(bytes, vec![0x00, 0x40, 0x9c, 0x4f, 0x2c, 0x68, 0x00, 0x00]);
// Deserializarlet val_des = bcs::from_bytes::<u64>(&bytes).unwrap();assert_eq!(val_des, 10000000000000000);
import { Serializer, Deserializer } from "@aptos-labs/ts-sdk";
// Serializarconst ser = new Serializer();ser.serializeU64(10000000000000000n);const bytes = ser.toUint8Array();console.log(bytes == Uint8Array.from([0x00, 0x40, 0x9c, 0x4f, 0x2c, 0x68, 0x00, 0x00]));
// Deserializarconst des = new Deserializer(bytes);const val = des.deserializeU64();console.log(val == 10000000000000000n);
import ( "github.com/aptos-labs/aptos-go-sdk")
func main() { // Serializar ser := bcs.Serializer{} ser.U64(10000000000000000) bytes := ser.ToBytes() bytes == []byte{0x00, 0x40, 0x9c, 0x4f, 0x2c, 0x68, 0x00, 0x00}
// Deserializar des := bcs.NewDeserializer(bytes) val := des.U64() val == 10000000000000000}
U128 (unsigned 128-bit integer)
Sección titulada «U128 (unsigned 128-bit integer)»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); }}
// Serializarlet val: u128 = 10000000000000000;let bytes: Vec<u8> = bcs::to_bytes(&val).unwrap();assert_eq!(bytes, vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x9c, 0x4f, 0x2c, 0x68, 0x00, 0x00]);
// Deserializarlet val_des = bcs::from_bytes::<u128>(&bytes).unwrap();assert_eq!(val_des, 10000000000000000);
import { Serializer, Deserializer } from "@aptos-labs/ts-sdk";
// Serializarconst ser = new Serializer();ser.serializeU128(10000000000000000n);const bytes = ser.toUint8Array();console.log(bytes == Uint8Array.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x9c, 0x4f, 0x2c, 0x68, 0x00, 0x00]));
// Deserializarconst des = new Deserializer(bytes);const val = des.deserializeU128();console.log(val == 10000000000000000n);
import ( "github.com/aptos-labs/aptos-go-sdk" "math/big")
func main() { // Serializar ser := bcs.Serializer{} val := new(big.Int) val.SetString("10000000000000000", 10) ser.U128(val) bytes := ser.ToBytes() bytes == []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x9c, 0x4f, 0x2c, 0x68, 0x00, 0x00}
// Deserializar des := bcs.NewDeserializer(bytes) val_des := des.U128() val_des.String() == "10000000000000000"}
U256 (unsigned 256-bit integer)
Sección titulada «U256 (unsigned 256-bit integer)»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); }}
// Serializarlet val: U256 = U256::from(10000000000000000u64);let bytes: Vec<u8> = bcs::to_bytes(&val).unwrap();assert_eq!(bytes, vec![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]);
// Deserializarlet val_des = bcs::from_bytes::<U256>(&bytes).unwrap();assert_eq!(val_des, U256::from(10000000000000000u64));
import { Serializer, Deserializer } from "@aptos-labs/ts-sdk";
// Serializarconst ser = new Serializer();ser.serializeU256(10000000000000000n);const bytes = ser.toUint8Array();console.log(bytes == Uint8Array.from([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]));
// Deserializarconst des = new Deserializer(bytes);const val = des.deserializeU256();console.log(val == 10000000000000000n);
import ( "github.com/aptos-labs/aptos-go-sdk" "math/big")
func main() { // Serializar ser := bcs.Serializer{} val := new(big.Int) val.SetString("10000000000000000", 10) ser.U256(val) bytes := ser.ToBytes() bytes == []byte{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 des := bcs.NewDeserializer(bytes) val_des := des.U256() val_des.String() == "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
// Currently not supported by itself in Rust with serde
import { Serializer, Deserializer } from "@aptos-labs/ts-sdk";
// Serializarconst ser = new Serializer();ser.serializeU32AsUleb128(127);const bytes = ser.toUint8Array();console.log(bytes == Uint8Array.from([0x7f]));
const ser = new Serializer();ser.serializeU32AsUleb128(128);const bytes2 = ser.toUint8Array();console.log(bytes2 == Uint8Array.from([0x80, 0x01]));
// Deserializarconst des = new Deserializer(bytes2);const val = des.deserializeUleb128AsU32();console.log(val == 128);
import ( "github.com/aptos-labs/aptos-go-sdk" "math/big")
func main() { // Serializar ser := bcs.Serializer{} val := new(big.Int) val.SetInt64(127) ser.Uleb128(val) bytes := ser.ToBytes() bytes == []byte{0x7f}
// Deserializar des := bcs.NewDeserializer(bytes) val_des := des.Uleb128() val_des.Int64() == 127}
Sequence and FixedSequence
Sección titulada «Sequence and FixedSequence»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]); }}
// Serializarlet val = vec![1u8, 2u8, 3u8];let bytes = bcs::to_bytes(&val).unwrap();assert_eq!(bytes, vec![3, 1, 2, 3]);
// Deserializarlet val_des = bcs::from_bytes::<Vec<u8>>(&bytes).unwrap();assert_eq!(val_des, vec![1, 2, 3]);
import { Serializer, Deserializer } from "@aptos-labs/ts-sdk";
// Serializarconst ser = new Serializer();ser.serializeVector([1, 2, 3], (s, item) => s.serializeU8(item));const bytes = ser.toUint8Array();console.log(bytes == Uint8Array.from([3, 1, 2, 3]));
// Deserializarconst des = new Deserializer(bytes);const val = des.deserializeVector((d) => d.deserializeU8());console.log(val == [1, 2, 3]);
import ( "github.com/aptos-labs/aptos-go-sdk")
func main() { // Serializar ser := bcs.Serializer{} ser.SerializeVector([]uint8{1, 2, 3}, func(s *bcs.Serializer, item uint8) { s.U8(item) }) bytes := ser.ToBytes() bytes == []byte{3, 1, 2, 3}
// Deserializar des := bcs.NewDeserializer(bytes) val := des.DeserializeVector(func(d *bcs.Deserializer) uint8 { return d.U8() }) val == []uint8{1, 2, 3}}
Tipos Complejos
Sección titulada «Tipos Complejos»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 24let 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
Sección titulada «AccountAddress»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)?);