UNPKG

@benfen/bcs

Version:

BCS - Canonical Binary Serialization implementation for JavaScript

568 lines (567 loc) 19.6 kB
import { toB64, fromB64 } from './b64'; import { toHEX, fromHEX } from './hex'; declare const toB58: (buffer: Uint8Array) => string; declare const fromB58: (str: string) => Uint8Array; export { toB58, fromB58, toB64, fromB64, fromHEX, toHEX }; /** * Supported encodings. * Used in `Reader.toString()` as well as in `decodeStr` and `encodeStr` functions. */ export type Encoding = 'base58' | 'base64' | 'hex'; /** * Allows for array definitions for names. * @example * ``` * bcs.registerStructType(['vector', BCS.STRING], ...); * // equals * bcs.registerStructType('vector<string>', ...); * ``` */ export type TypeName = string | [string, ...(TypeName | string)[]]; /** * Class used for reading BCS data chunk by chunk. Meant to be used * by some wrapper, which will make sure that data is valid and is * matching the desired format. * * @example * // data for this example is: * // { a: u8, b: u32, c: bool, d: u64 } * * let reader = new BcsReader("647f1a060001ffffe7890423c78a050102030405"); * let field1 = reader.read8(); * let field2 = reader.read32(); * let field3 = reader.read8() === '1'; // bool * let field4 = reader.read64(); * // .... * * Reading vectors is another deal in bcs. To read a vector, you first need to read * its length using {@link readULEB}. Here's an example: * @example * // data encoded: { field: [1, 2, 3, 4, 5] } * let reader = new BcsReader("050102030405"); * let vec_length = reader.readULEB(); * let elements = []; * for (let i = 0; i < vec_length; i++) { * elements.push(reader.read8()); * } * console.log(elements); // [1,2,3,4,5] * * @param {String} data HEX-encoded data (serialized BCS) */ export declare class BcsReader { private dataView; private bytePosition; /** * @param {Uint8Array} data Data to use as a buffer. */ constructor(data: Uint8Array); /** * Shift current cursor position by `bytes`. * * @param {Number} bytes Number of bytes to * @returns {this} Self for possible chaining. */ shift(bytes: number): this; /** * Read U8 value from the buffer and shift cursor by 1. * @returns */ read8(): number; /** * Read U16 value from the buffer and shift cursor by 2. * @returns */ read16(): number; /** * Read U32 value from the buffer and shift cursor by 4. * @returns */ read32(): number; /** * Read U64 value from the buffer and shift cursor by 8. * @returns */ read64(): string; /** * Read U128 value from the buffer and shift cursor by 16. */ read128(): string; /** * Read U128 value from the buffer and shift cursor by 32. * @returns */ read256(): string; /** * Read `num` number of bytes from the buffer and shift cursor by `num`. * @param num Number of bytes to read. */ readBytes(num: number): Uint8Array; /** * Read ULEB value - an integer of varying size. Used for enum indexes and * vector lengths. * @returns {Number} The ULEB value. */ readULEB(): number; /** * Read a BCS vector: read a length and then apply function `cb` X times * where X is the length of the vector, defined as ULEB in BCS bytes. * @param cb Callback to process elements of vector. * @returns {Array<Any>} Array of the resulting values, returned by callback. */ readVec(cb: (reader: BcsReader, i: number, length: number) => any): any[]; } /** * Class used to write BCS data into a buffer. Initializer requires * some size of a buffer to init; default value for this buffer is 1KB. * * Most methods are chainable, so it is possible to write them in one go. * * @example * let serialized = new BcsWriter() * .write8(10) * .write32(1000000) * .write64(10000001000000) * .hex(); */ interface BcsWriterOptions { /** The initial size (in bytes) of the buffer tht will be allocated */ size?: number; /** The maximum size (in bytes) that the buffer is allowed to grow to */ maxSize?: number; /** The amount of bytes that will be allocated whenever additional memory is required */ allocateSize?: number; } export declare class BcsWriter { private dataView; private bytePosition; private size; private maxSize; private allocateSize; constructor({ size, maxSize, allocateSize }?: BcsWriterOptions); private ensureSizeOrGrow; /** * Shift current cursor position by `bytes`. * * @param {Number} bytes Number of bytes to * @returns {this} Self for possible chaining. */ shift(bytes: number): this; /** * Write a U8 value into a buffer and shift cursor position by 1. * @param {Number} value Value to write. * @returns {this} */ write8(value: number | bigint): this; /** * Write a U16 value into a buffer and shift cursor position by 2. * @param {Number} value Value to write. * @returns {this} */ write16(value: number | bigint): this; /** * Write a U32 value into a buffer and shift cursor position by 4. * @param {Number} value Value to write. * @returns {this} */ write32(value: number | bigint): this; /** * Write a U64 value into a buffer and shift cursor position by 8. * @param {bigint} value Value to write. * @returns {this} */ write64(value: number | bigint): this; /** * Write a U128 value into a buffer and shift cursor position by 16. * * @param {bigint} value Value to write. * @returns {this} */ write128(value: number | bigint): this; /** * Write a U256 value into a buffer and shift cursor position by 16. * * @param {bigint} value Value to write. * @returns {this} */ write256(value: number | bigint): this; /** * Write a ULEB value into a buffer and shift cursor position by number of bytes * written. * @param {Number} value Value to write. * @returns {this} */ writeULEB(value: number): this; /** * Write a vector into a buffer by first writing the vector length and then calling * a callback on each passed value. * * @param {Array<Any>} vector Array of elements to write. * @param {WriteVecCb} cb Callback to call on each element of the vector. * @returns {this} */ writeVec(vector: any[], cb: (writer: BcsWriter, el: any, i: number, len: number) => void): this; /** * Adds support for iterations over the object. * @returns {Uint8Array} */ [Symbol.iterator](): Iterator<number, Iterable<number>>; /** * Get underlying buffer taking only value bytes (in case initial buffer size was bigger). * @returns {Uint8Array} Resulting bcs. */ toBytes(): Uint8Array; /** * Represent data as 'hex' or 'base64' * @param encoding Encoding to use: 'base64' or 'hex' */ toString(encoding: Encoding): string; } /** * Set of methods that allows data encoding/decoding as standalone * BCS value or a part of a composed structure/vector. */ export interface TypeInterface { encode: (self: BCS, data: any, options: BcsWriterOptions | undefined, typeParams: TypeName[]) => BcsWriter; decode: (self: BCS, data: Uint8Array, typeParams: TypeName[]) => any; _encodeRaw: (writer: BcsWriter, data: any, typeParams: TypeName[], typeMap: { [key: string]: TypeName; }) => BcsWriter; _decodeRaw: (reader: BcsReader, typeParams: TypeName[], typeMap: { [key: string]: TypeName; }) => any; } /** * Struct type definition. Used as input format in BcsConfig.types * as well as an argument type for `bcs.registerStructType`. */ export type StructTypeDefinition = { [key: string]: TypeName | StructTypeDefinition; }; /** * Enum type definition. Used as input format in BcsConfig.types * as well as an argument type for `bcs.registerEnumType`. * * Value can be either `string` when invariant has a type or `null` * when invariant is empty. * * @example * bcs.registerEnumType('Option<T>', { * some: 'T', * none: null * }); */ export type EnumTypeDefinition = { [key: string]: TypeName | StructTypeDefinition | null; }; /** * Configuration that is passed into BCS constructor. */ export type BcsConfig = { /** * Defines type name for the vector / array type. * In Move: `vector<T>` or `vector`. */ vectorType: string; /** * Address length. Varies depending on a platform and * has to be specified for the `address` type. */ addressLength: number; /** * Custom encoding for address. Supported values are * either 'hex' or 'base64'. */ addressEncoding?: 'hex' | 'base64'; /** * Opening and closing symbol for type parameters. Can be * any pair of symbols (eg `['(', ')']`); default value follows * Rust and Move: `<` and `>`. */ genericSeparators?: [string, string]; /** * Type definitions for the BCS. This field allows spawning * BCS instance from JSON or another prepared configuration. * Optional. */ types?: { structs?: { [key: string]: StructTypeDefinition; }; enums?: { [key: string]: EnumTypeDefinition; }; aliases?: { [key: string]: string; }; }; /** * Whether to auto-register primitive types on launch. */ withPrimitives?: boolean; }; /** * BCS implementation for Move types and few additional built-ins. */ export declare class BCS { static readonly U8: string; static readonly U16: string; static readonly U32: string; static readonly U64: string; static readonly U128: string; static readonly U256: string; static readonly BOOL: string; static readonly VECTOR: string; static readonly ADDRESS: string; static readonly STRING: string; static readonly HEX: string; static readonly BASE58: string; static readonly BASE64: string; /** * Map of kind `TypeName => TypeInterface`. Holds all * callbacks for (de)serialization of every registered type. * * If the value stored is a string, it is treated as an alias. */ types: Map<string, TypeInterface | string>; /** * Stored BcsConfig for the current instance of BCS. */ protected schema: BcsConfig; /** * Count temp keys to generate a new one when requested. */ protected counter: number; /** * Name of the key to use for temporary struct definitions. * Returns a temp key + index (for a case when multiple temp * structs are processed). */ private tempKey; /** * Construct a BCS instance with a prepared schema. * * @param schema A prepared schema with type definitions * @param withPrimitives Whether to register primitive types by default */ constructor(schema: BcsConfig | BCS); /** * Serialize data into bcs. * * @example * bcs.registerVectorType('vector<u8>', 'u8'); * * let serialized = BCS * .set('vector<u8>', [1,2,3,4,5,6]) * .toBytes(); * * console.assert(toHex(serialized) === '06010203040506'); * * @param type Name of the type to serialize (must be registered) or a struct type. * @param data Data to serialize. * @param size Serialization buffer size. Default 1024 = 1KB. * @return A BCS reader instance. Usually you'd want to call `.toBytes()` */ ser(type: TypeName | StructTypeDefinition, data: any, options?: BcsWriterOptions): BcsWriter; /** * Deserialize BCS into a JS type. * * @example * let num = bcs.ser('u64', '4294967295').toString('hex'); * let deNum = bcs.de('u64', num, 'hex'); * console.assert(deNum.toString(10) === '4294967295'); * * @param type Name of the type to deserialize (must be registered) or a struct type definition. * @param data Data to deserialize. * @param encoding Optional - encoding to use if data is of type String * @return Deserialized data. */ de(type: TypeName | StructTypeDefinition, data: Uint8Array | string, encoding?: Encoding): any; /** * Check whether a `TypeInterface` has been loaded for a `type`. * @param type Name of the type to check. * @returns */ hasType(type: string): boolean; /** * Create an alias for a type. * WARNING: this can potentially lead to recursion * @param name Alias to use * @param forType Type to reference * @returns * * @example * ``` * let bcs = new BCS(getSuiMoveConfig()); * bcs.registerAlias('ObjectDigest', BCS.BASE58); * let b58_digest = bcs.de('ObjectDigest', '<digest_bytes>', 'base64'); * ``` */ registerAlias(name: string, forType: string): BCS; /** * Method to register new types for BCS internal representation. * For each registered type 2 callbacks must be specified and one is optional: * * - encodeCb(writer, data) - write a way to serialize data with BcsWriter; * - decodeCb(reader) - write a way to deserialize data with BcsReader; * - validateCb(data) - validate data - either return bool or throw an error * * @example * // our type would be a string that consists only of numbers * bcs.registerType('number_string', * (writer, data) => writer.writeVec(data, (w, el) => w.write8(el)), * (reader) => reader.readVec((r) => r.read8()).join(''), // read each value as u8 * (value) => /[0-9]+/.test(value) // test that it has at least one digit * ); * console.log(Array.from(bcs.ser('number_string', '12345').toBytes()) == [5,1,2,3,4,5]); * * @param name * @param encodeCb Callback to encode a value. * @param decodeCb Callback to decode a value. * @param validateCb Optional validator Callback to check type before serialization. */ registerType(typeName: TypeName, encodeCb: (writer: BcsWriter, data: any, typeParams: TypeName[], typeMap: { [key: string]: TypeName; }) => BcsWriter, decodeCb: (reader: BcsReader, typeParams: TypeName[], typeMap: { [key: string]: TypeName; }) => any, validateCb?: (data: any) => boolean): BCS; /** * Register an address type which is a sequence of U8s of specified length. * @example * bcs.registerAddressType('address', SUI_ADDRESS_LENGTH); * let addr = bcs.de('address', 'c3aca510c785c7094ac99aeaa1e69d493122444df50bb8a99dfa790c654a79af'); * * @param name Name of the address type. * @param length Byte length of the address. * @param encoding Encoding to use for the address type * @returns */ registerAddressType(name: string, length: number, encoding?: Encoding | void): BCS; /** * Register custom vector type inside the bcs. * * @example * bcs.registerVectorType('vector<T>'); // generic registration * let array = bcs.de('vector<u8>', '06010203040506', 'hex'); // [1,2,3,4,5,6]; * let again = bcs.ser('vector<u8>', [1,2,3,4,5,6]).toString('hex'); * * @param name Name of the type to register * @param elementType Optional name of the inner type of the vector * @return Returns self for chaining. */ private registerVectorType; /** * Safe method to register a custom Move struct. The first argument is a name of the * struct which is only used on the FrontEnd and has no affect on serialization results, * and the second is a struct description passed as an Object. * * The description object MUST have the same order on all of the platforms (ie in Move * or in Rust). * * @example * // Move / Rust struct * // struct Coin { * // value: u64, * // owner: vector<u8>, // name // Vec<u8> in Rust * // is_locked: bool, * // } * * bcs.registerStructType('Coin', { * value: bcs.U64, * owner: bcs.STRING, * is_locked: bcs.BOOL * }); * * // Created in Rust with diem/bcs * // let rust_bcs_str = '80d1b105600000000e4269672057616c6c65742047757900'; * let rust_bcs_str = [ // using an Array here as BCS works with Uint8Array * 128, 209, 177, 5, 96, 0, 0, * 0, 14, 66, 105, 103, 32, 87, * 97, 108, 108, 101, 116, 32, 71, * 117, 121, 0 * ]; * * // Let's encode the value as well * let test_set = bcs.ser('Coin', { * owner: 'Big Wallet Guy', * value: '412412400000', * is_locked: false, * }); * * console.assert(Array.from(test_set.toBytes()) === rust_bcs_str, 'Whoopsie, result mismatch'); * * @param name Name of the type to register. * @param fields Fields of the struct. Must be in the correct order. * @return Returns BCS for chaining. */ registerStructType(typeName: TypeName, fields: StructTypeDefinition): BCS; /** * Safe method to register custom enum type where each invariant holds the value of another type. * @example * bcs.registerStructType('Coin', { value: 'u64' }); * bcs.registerEnumType('MyEnum', { * single: 'Coin', * multi: 'vector<Coin>', * empty: null * }); * * console.log( * bcs.de('MyEnum', 'AICWmAAAAAAA', 'base64'), // { single: { value: 10000000 } } * bcs.de('MyEnum', 'AQIBAAAAAAAAAAIAAAAAAAAA', 'base64') // { multi: [ { value: 1 }, { value: 2 } ] } * ) * * // and serialization * bcs.ser('MyEnum', { single: { value: 10000000 } }).toBytes(); * bcs.ser('MyEnum', { multi: [ { value: 1 }, { value: 2 } ] }); * * @param name * @param variants */ registerEnumType(typeName: TypeName, variants: EnumTypeDefinition): BCS; /** * Get a set of encoders/decoders for specific type. * Mainly used to define custom type de/serialization logic. * * @param type * @returns {TypeInterface} */ getTypeInterface(type: string): TypeInterface; /** * Parse a type name and get the type's generics. * @example * let { typeName, typeParams } = parseTypeName('Option<Coin<SUI>>'); * // typeName: Option * // typeParams: [ 'Coin<SUI>' ] * * @param name Name of the type to process * @returns Object with typeName and typeParams listed as Array */ parseTypeName(name: TypeName): { name: string; params: TypeName[]; }; } /** * Encode data with either `hex` or `base64`. * * @param {Uint8Array} data Data to encode. * @param {String} encoding Encoding to use: base64 or hex * @return {String} Encoded value. */ export declare function encodeStr(data: Uint8Array, encoding: Encoding): string; /** * Decode either `base64` or `hex` data. * * @param {String} data Data to encode. * @param {String} encoding Encoding to use: base64 or hex * @return {Uint8Array} Encoded value. */ export declare function decodeStr(data: string, encoding: Encoding): Uint8Array; /** * Register the base set of primitive and common types. * Is called in the `BCS` constructor automatically but can * be ignored if the `withPrimitives` argument is not set. */ export declare function registerPrimitives(bcs: BCS): void; export declare function getRustConfig(): BcsConfig; export declare function getSuiMoveConfig(): BcsConfig; export declare function splitGenericParameters(str: string, genericSeparators?: [string, string]): string[];