Giter Club home page Giter Club logo

borsh's Introduction

Borsh Build Status Hex pm hex.pm downloads

BORSH, binary serializer for security-critical projects.

Borsh stands for "Binary Object Representation Serializer for Hashing". It is meant to be used in security-critical projects as it prioritizes consistency, safety, speed; and comes with a strict specification. In short, Borsh is a non self-describing binary serialization format. It is designed to serialize any objects to canonical and deterministic set of bytes.

General principles of Borsh serialization:

  • Integers are encoded in little-endian format.
  • The size of dynamic containers (such as hash maps and hash sets) is written as a 32-bit unsigned integer before the values.
  • All unordered containers are ordered lexicographically by key, with a tie breaker of the value.
  • Structs are serialized in the order of their fields.
  • Enums are serialized by storing the ordinal as an 8-bit unsigned integer, followed by the data contained within the enum value (if present).

This is Elixir implementation of the Borsh serializer and deserializer. Official specification: https://github.com/near/borsh#specification

A little article on Medium about Borsh serializer in more details: https://medium.com/@alexfilatov/borsh-binary-serialiser-for-near-protocol-eed79a1638f4

Installation

If available in Hex, the package can be installed by adding borsh to your list of dependencies in mix.exs:

def deps do
  [
    {:borsh, "~> 0.1"}
  ]
end

Usage

  use Borsh,
      schema: [
        signer_id: :string,
        public_key: {:borsh, PublicKey},
        nonce: :u64,
        receiver_id: :string,
        block_hash: [32],
        actions: [
          {:borsh, ActionOne}, 
          {:borsh, ActionTwo}
        ]
      ]

In this example ActionOne, ActionTwo and PublicKey are structs that implement Borsh protocol.

Options

schema: Borsh schema itself, structure of fields for serialisation with serialisation formats described below.

Borsh literal formats

String literals

:string - string, encoded as utf-8 bytes

[32] and [64] - A string with 32/64 chars length.

Number literals

:u8 - unsigned 8-bit integer

:u16 - unsigned 16-bit integer

:u32 - unsigned 32-bit integer

:u64 - unsigned 64-bit integer

:i8 - signed 8-bit integer

:i16 - signed 16-bit integer

:i32 - signed 32-bit integer

:i64 - signed 64-bit integer

:f32 - 32-bit float

:f64 - 64-bit float

Borsh-typed literals

To define custom types for serialization, we can use the syntax {:borsh, StructModule} in a parent struct, when we want to serialize another struct within it. There are single and arrays of borsh types.

{:borsh, Module} - The syntax represents a single struct of a borsh-encoded module. When this struct is passed to the serializer, the serializer will execute the .borsh_encode method of the struct's module on the struct.

:borsh - has the same effect as {:borsh, Module}, but the resulting serialized data cannot be decoded back into the original struct. Using :borsh for serialization is safe for sending transactions to the NEAR blockchain, as the main concern is just the serialization itself.

[{:borsh, Module}] - represents an enumeration of borsh-encoded structs, where each element of the list must have a Borsh schema.

[:borsh] - has the same effect as [{:borsh, Module}], but the resulting serialized data cannot be decoded back into the original structs. It can only be used for encoding, not decoding.

[{:borsh, Module1}, {:borsh, Module2}] - represents an enumeration of borsh-encoded structs, where each element of the list must have a Borsh schema. Each element in the list can belong to a different module, and the sequence of elements is important. This syntax can be used for both encoding and decoding.

License

Copyright © 2021-present Alex Filatov <[email protected]>

This work is free. You can redistribute it and/or modify it under the
terms of the MIT License. See the LICENSE file for more details.

borsh's People

Contributors

alexfilatov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

flmel

borsh's Issues

Refactor {:borsh, Module} serialiser and deserialiser

WHY

Currently, when we find {:borsh, PublicKey} we treat it as a Borsh structure and serialize/deserialize according to the scheme inside that structure.
I believe we can even more simplify, and create scheme like this e.g:

old schema:

use Borsh,
      schema: [
        signer_id: :string,
        public_key: {:borsh, PublicKey},
        nonce: :u64,
        receiver_id: :string,
        block_hash: [32],
        actions: [
          {:borsh, ActionOne}, 
          {:borsh, ActionTwo}
        ]
      ]

new improved schema:

use Borsh,
      schema: [
        signer_id: :string,
        public_key: PublicKey,
        nonce: :u64,
        receiver_id: :string,
        block_hash: [32],
        actions: [
          ActionOne, 
          ActionTwo
        ]
      ]

WHAT

so if the value is just a module - treat this as a module with borsh schema.

IMPORTANT

Allow backward compatibility with the {:borsh, ActionOne}

Implement decode functionality

This library lacks decode functionality, going to implement it.

Should work like the following:

defmodule DummyStruct do
 @type t() :: %__MODULE__{first_name: String.t(), last_name: String.t(), age: integer}

 defstruct [
   :first_name,
   :last_name,
   :age
 ]

 use Borsh,
   schema: [
     first_name: :string,
     last_name: :string,
     age: :u8
   ]
end

bitstr = <<5, 0, 0, 0, 66, 111, 114, 105, 115, 7, 0, 0, 0, 74, 111, 104, 110, 115, 111, 110, 58>>

Borsh.Decode.borsh_decode(bitstr, DummyStruct)
%DummyStruct{first_name: "Boris", last_name: "Johnson", age: 58}

[IDEA] Implement `Borsh.EnumItem`

An idea - instead of enum_map create Borsh.EnumItem with required field that represent the index of the item in the list.
This field will come first in the Borsh schema and and will be required for all structs that uses the Borsh.EnumItem, e.g.:

  use Borsh.EnumItem, schema: [
    index: :u8,
    field1: :string,
    field2: :u32
  ]

the index here will be required to have an integer unsigned little endian value.

So parent Borsh schema will have only a field with enum_field: [:borsh] and Serializer will be checking enum items in the field whether they are Borsh.EnumItem with the required index.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.