borsh: a canonical binary format so the same object always hashes the same
Binary Object Representation Serializer for Hashing
At a glance
- What is it?
- borsh is a serialization format whose selling point is not speed but determinism: there is a bijective mapping between an object and its byte representation, so a hash computed over it is reproducible in any language. The repository is a specification and an index of implementations rather than a library.
- Who is it for?
- borsh suits anyone computing hashes or signatures over serialised state across languages, especially in blockchain and distributed systems where two runtimes must agree byte for byte on what a record looks like. It does not suit someone who only needs to move data between processes that already agree on a format, and it does not suit a team that needs a rich type system, since the format favours a small, fixed set of primitives.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- GitHub does not report a main language for this repository.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A format designed around the hash, not the parser
borsh stands for Binary Object Representation Serializer for Hashing, and the name tells you where the design pressure comes from. The README opens by asking why the ecosystem needs yet another serialization format, and answers that borsh is the first serializer that prioritises the qualities that matter for security-critical projects.
The context is visible in the repository itself. The links at the top of the README point to a website at borsh.io, a community chat, an implementations list, benchmarks and a specification. The repository contents are correspondingly thin: `.editorconfig`, `.gitattributes`, a `CNAME` file, `CODEOWNERS`, the README and a `docs/` directory. There is no library code here, and no build system. This is the specification repository, and the `CNAME` file is what a GitHub Pages site uses to claim a custom domain, which is how borsh.io is served.
The problem Borsh addresses shows up in distributed systems. If a protocol serialises a record and then hashes the bytes, every participant must produce identical bytes from identical state. Serialisation formats that allow more than one valid encoding for the same value, through optional fields, omitted defaults or non-canonical integer widths, break that property. A hash over a record that two nodes encoded differently produces two different hashes, and the protocol sees a fork that is not really a fork.
That is why the format's central property is a bijective mapping between objects and their binary representations, and why the README points out that there are no two binary representations that deserialize into the same object. For an application that computes a hash over a binary representation, that is the property that matters, and it is the one that most general purpose formats do not promise.
Consistency comes with a written specification
The second stated quality is that borsh comes with a full specification that can be used for implementations in other languages. This is the part that makes a cross-language hashing protocol possible, and it is also the part that distinguishes Borsh from a format that exists as one library plus a hope that others will copy the behaviour.
A specification is a different kind of artefact from an implementation. It can be implemented independently, reviewed by people who do not use the reference language, and argued about in terms of bytes rather than in terms of API calls. It also means the ecosystem can have competing implementations without the format itself being owned by whoever wrote the first one.
The implementation table in the README makes the scale of that effort visible. There are entries for Rust, TypeScript in several flavours, the JVM languages, Go, Python, AssemblyScript, C#, C++, Elixir, Ruby and Swift. Some of those are marked as work in progress, which is worth noting: the table distinguishes between a shipped release and an in-progress effort, so a team choosing an implementation should read that column rather than assume every row is equally ready.
The docs directory of this repository is where the specification itself lives, and for anyone implementing Borsh in a new language, that is the document to read rather than the reference implementation's source.
Safe code, with one deliberate exception
The safety claim is stated in specific terms. Borsh implementations use safe coding practices, and in Rust the format uses almost only safe code, with one exception: an `unsafe` block used to avoid an exhaustion attack.
That exception is the interesting part, because it says the design was checked against adversarial input rather than only against correct input. A serializer that allocates based on a length field taken from untrusted bytes is a classic memory exhaustion vector, and a format used for hashing untrusted network data has to survive that. Using one small `unsafe` region to bound the work, in a crate whose claim is that it is otherwise safe Rust, is a defensible trade, and the README is upfront that the exception exists rather than implying the crate is entirely safe.
The reason to care about the `unsafe` count at all is that a serialization format sits on the trust boundary. It parses bytes that arrived from somewhere else, and it hands back structures that the rest of the program will treat as trusted data. A format that can be implemented entirely in safe code in any language makes that boundary much easier to argue about, because there is no memory-safety argument to make about the parser itself.
The other side of the trade is that a canonical format constrains what you can encode. With one representation per object, there is no room for a field that is sometimes present and sometimes absent, and the format is deliberately small in the set of types it covers. That is fine for a protocol, and inconvenient for a general purpose object serialiser.
Dropping Serde for speed in the Rust implementation
The performance claim in the README is narrow and specific, and worth reading carefully rather than paraphrasing. In Rust, borsh achieves high performance by opting out from Serde, and the README states that this makes it faster than bincode in some cases, and also reduces the code size.
Three things follow from that sentence. First, the comparison is against bincode, the general Rust serialisation format, not against a protocol format, so the comparison is between two different kinds of tool. Second, the qualification is in some cases, so this is not a blanket statement that Borsh is the fastest option available. Third, the code size effect is not an accident but a consequence of not carrying a derive-based generic framework into the binary.
That last point is the real trade. Serde is the dominant serialisation ecosystem in Rust, and opting out of it means the integration story is different. There is no shared derive macro, no `serde_json` interop, and no way to reuse a type that already derives Serde traits without writing a separate Borsh implementation of it. In exchange, the generated code is smaller and the serialisation path is direct.
For a protocol implementation, where the schema is fixed and shared across languages, that trade is usually worth it. For an application that already has a large Serde-based domain model and serialises it in several ways, it is not, and a project in that position will find the friction is in the types rather than in the bytes.
What the benchmarks measure, and what they leave out
The benchmarks section describes its method precisely, which is refreshing. The objects measured are the ones blockchain projects care about most: blocks, block headers, transactions and accounts, and the object structure is taken from the NEAR Protocol blockchain. The graphs were built with Criterion, and they were run on Google Cloud, on an n1-standard-2 machine with 2 vCPUs and 7.5 GB of memory.
The four graphs are block header serialisation speed against header size, block header deserialisation speed against header size, block serialisation speed against block size, and block deserialisation speed against block size. The README notes that size only roughly corresponds to serialisation complexity, which is why the graphs are not smooth.
Two limits are worth naming. The first is that these are throughput measurements of one implementation on one machine on one object model, and the README does not present a table of numbers to compare implementations against each other, so the graphs show shape and scaling rather than a ranking. The second is that the benchmark answers the question of how fast the format moves bytes, and says nothing about the property the format was designed for, which is that the bytes are the same everywhere. A format can be fast and non-canonical, or canonical and slow, and the two concerns are independent.
So the benchmarks are useful for understanding scaling behaviour on realistic blockchain objects, and they are not evidence about cross-runtime agreement.
What lives in this repository and what does not
It is worth being explicit about the shape of the project, because the repository name suggests a crate and does not contain one.
What is here is the README, which serves as the front page and the index of implementations, the `docs/` directory with the specification, and a handful of project governance files. The `CODEOWNERS` file indicates that parts of the repository have designated owners, which is a reasonable convention for a specification that different teams maintain in parallel, and `.editorconfig` and `.gitattributes` are the usual hygiene files.
What is not here is any implementation, in any language. Those live in the separate repositories listed in the table, including borsh-rs for Rust, borsh-go, borsh-js, zorsh for TypeScript, borsh-construct-py for Python, borshj for the JVM, borsh.rb for Ruby, borsh-ex for Elixir, Hexarc.Borsh for C#, and the C++ entries borsh-cpp and borsh-cpp20, which are marked work in progress alongside the Swift implementation SwiftBorsh.
The licence question deserves attention. The project files in this repository do not state a licence, so before copying the specification text or the tables into your own documentation or a derived specification, check with the project. The specification itself is the durable asset, and an implementer who relies on it needs to know the terms it comes with.
On maintenance, the repository is not archived and the last push was on 2026-08-01, so the specification is still being touched. The README links to a separate specification repository, a benchmarks section and a community chat, which are the places to follow for changes that matter to an implementer.
Editorial conclusion
borsh suits anyone computing hashes or signatures over serialised state across languages, especially in blockchain and distributed systems where two runtimes must agree byte for byte on what a record looks like. It does not suit someone who only needs to move data between processes that already agree on a format, and it does not suit a team that needs a rich type system, since the format favours a small, fixed set of primitives. Before adopting, read the specification in the docs directory of this repository, confirm that a maintained implementation exists in your language from the implementations table, and check that your project can accept a format where there is no optional field encoding, since the last push was on 2026-08-01.
Frequently asked questions
What problem does the borsh serialization format solve?
It guarantees a bijective mapping between an object and its binary representation, so there are never two binary representations that deserialize into the same object. That makes a hash computed over the bytes reproducible, which is what applications that hash their serialised state need.
Is borsh related to Serde?
The Rust implementation deliberately opts out of Serde, and the README says this makes it faster than bincode in some cases and reduces the code size. The trade is that the format does not share Serde derives with the rest of a Rust project.
Where is the borsh specification and which languages have implementations?
The specification lives in the docs directory of this repository and is linked from the README, which also carries a table of implementations for Rust, TypeScript, the JVM, Go, Python, AssemblyScript, C#, C++, Elixir, Ruby and Swift, with some entries marked work in progress.
Does borsh use unsafe code?
The README states that implementations use safe coding practices and that the Rust implementation uses almost only safe code, with one exception, an `unsafe` usage to avoid an exhaustion attack.
What do the borsh benchmarks measure?
They measure serialisation and deserialisation speed against object size for blocks, block headers, transactions and accounts, using object structure from the NEAR Protocol and graphs built with Criterion on a Google Cloud n1-standard-2 instance. They show scaling on one implementation rather than a comparison of formats.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/near-borsh)Was this useful?