Europe/London
BlogDecember 20, 2022

BORSH — Binary Serializer for NEAR Protocol

Alex Filatov
BORSH — Binary Serializer for NEAR Protocol
BORSH (Binary Object Representation Serializer for Hashing) is a binary serialization format designed specifically for the NEAR blockchain protocol. In this article, I'll explain what BORSH is, why it exists, and how to use it. Serialization is the process of converting data structures into a format that can be stored or transmitted. Common formats include:
  • JSON: Human-readable, widely supported, but verbose
  • Protocol Buffers: Efficient binary format by Google
  • MessagePack: Binary JSON alternative
  • BORSH: Deterministic binary format for blockchain
NEAR Protocol needed a serialization format with specific properties:
  • Deterministic: The same data always produces the same binary output
  • Efficient: Minimal overhead for blockchain storage
  • Simple: Easy to implement in multiple languages
  • Self-describing: No schema negotiation required
JSON fails the determinism test (key ordering varies), and other formats had unnecessary complexity. | Type | Encoding | |------|----------| | u8 | 1 byte | | u16 | 2 bytes, little-endian | | u32 | 4 bytes, little-endian | | u64 | 8 bytes, little-endian | | u128 | 16 bytes, little-endian | | bool | 1 byte (0 or 1) | | string | 4-byte length + UTF-8 bytes | Arrays: Length prefix (4 bytes) + elements
[1, 2, 3] → 03 00 00 00 01 02 03
Structs: Fields serialized in order
Rust
struct Point { x: u32, y: u32 }
Point { x: 10, y: 20 } → 0A 00 00 00 14 00 00 00
Enums: Variant index (1 byte) + variant data BORSH has implementations in multiple languages:
Rust
use borsh::{BorshSerialize, BorshDeserialize};

#[derive(BorshSerialize, BorshDeserialize)]
struct Account {
    balance: u128,
    nonce: u64,
}
Javascript
const borsh = require('borsh');

class Account {
    constructor(balance, nonce) {
        this.balance = balance;
        this.nonce = nonce;
    }
}

const schema = new Map([
    [Account, { kind: 'struct', fields: [
        ['balance', 'u128'],
        ['nonce', 'u64']
    ]}]
]);

const account = new Account(1000n, 1);
const encoded = borsh.serialize(schema, account);
I created an Elixir implementation: github.com/alexfilatov/borsh
Elixir
defmodule Account do
  @derive Borsh.Encoder
  defstruct [:balance, :nonce]
end

account = %Account{balance: 1000, nonce: 1}
encoded = Borsh.encode(account)
BORSH is used throughout NEAR for:
  • Transaction serialization: Signing and submitting transactions
  • State storage: Contract state is BORSH-encoded
  • Cross-contract calls: Arguments and return values
Javascript
const nearAPI = require('near-api-js');
const borsh = require('borsh');

// Transaction is serialized with BORSH before signing
const serializedTx = borsh.serialize(
    nearAPI.transactions.SCHEMA,
    transaction
);
const hash = sha256(serializedTx);
const signature = sign(hash, privateKey);
  • Byte order: BORSH uses little-endian; don't assume big-endian
  • String encoding: Always UTF-8, with 4-byte length prefix
  • Optional fields: Encoded as 0 (None) or 1 + value (Some)
  • Enum indices: Start at 0, must match definition order
BORSH provides a simple, deterministic binary format perfect for blockchain applications. Its implementations in Rust, JavaScript, Elixir, and other languages make it accessible for building NEAR Protocol applications. For more details, check out:
Share this post: