|
| 1 | +import { BlogPostLayout } from '@/components/BlogPostLayout' |
| 2 | +import {ThemeImage} from '@/components/ThemeImage' |
| 3 | + |
| 4 | +export const post = { |
| 5 | + draft: false, |
| 6 | + author: 'Rüdiger Klaehn', |
| 7 | + date: '2025-04-08', |
| 8 | + title: 'The new BLAKE3 hazmat API', |
| 9 | + description: |
| 10 | + "The new BLAKE3 hazmat API, or why we can retire our BLAKE3 fork", |
| 11 | +} |
| 12 | + |
| 13 | +export const metadata = { |
| 14 | + title: post.title, |
| 15 | + description: post.description, |
| 16 | + openGraph: { |
| 17 | + title: post.title, |
| 18 | + description: post.description, |
| 19 | + images: [{ |
| 20 | + url: `/api/og?title=Blog&subtitle=${post.title}`, |
| 21 | + width: 1200, |
| 22 | + height: 630, |
| 23 | + alt: post.title, |
| 24 | + type: 'image/png', |
| 25 | + }], |
| 26 | + type: 'article' |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +export default (props) => <BlogPostLayout article={post} {...props} /> |
| 31 | + |
| 32 | +In [iroh-blobs], we are using the [BLAKE3] hash function for content-addressing. To allow verified streaming, we need to access some internals of the hash function. For the last 18 months we have maintaned a [fork] of the BLAKE3 crate to allow us to access some internals of the hash function. With the latest release of BLAKE3, there is a new *hazmat* API that allows us to retire this fork. |
| 33 | + |
| 34 | +This blog post goes into a lot of detail on the previous *guts* API, the new *hazmat* API, and how we use these APIs in blobs. It will be of interest to other advanced users of the BLAKE3 hash function. |
| 35 | + |
| 36 | +# Short summary of BLAKE3 |
| 37 | + |
| 38 | +The BLAKE3 hash function is a *tree* hash function. Briefly, the data gets divided into 1024 byte chunks, and each chunk is then hashed individually. |
| 39 | + |
| 40 | +To compute a root hash, pairs of hashes are combined with a binary combine function until just a single hash remains. To provide [domain separation], ***only*** the final root hash gets computed with a special flag is_root set to true. |
| 41 | + |
| 42 | +<div className="not-prose"> |
| 43 | +<ThemeImage |
| 44 | + lightSrc='/blog/blake3-hazmat-api/blake-tree-6.png' |
| 45 | + darkSrc='/blog/blake3-hazmat-api/blake-tree-6-dark.png' |
| 46 | + alt='BLAKE3 forks' |
| 47 | + width={800} |
| 48 | + height={600} |
| 49 | + /> |
| 50 | +</div> |
| 51 | + |
| 52 | +# Why does iroh-blobs need internals access |
| 53 | + |
| 54 | +The `iroh-blobs` protocol allows for verified streaming of data, just like the [bao] crate. In order to do so we need to be able to compute *intermediate results* of the computation described above. |
| 55 | + |
| 56 | +<div className="not-prose"> |
| 57 | +<ThemeImage |
| 58 | + lightSrc='/blog/blake3-hazmat-api/blake-tree-21.png' |
| 59 | + darkSrc='/blog/blake3-hazmat-api/blake-tree-21-dark.png' |
| 60 | + alt='BLAKE3 forks' |
| 61 | + width={800} |
| 62 | + height={600} |
| 63 | + /> |
| 64 | +</div> |
| 65 | + |
| 66 | +<Note> |
| 67 | +Most users are only interested in the root hash, and won't *ever* have to deal with intermediate results. |
| 68 | +</Note> |
| 69 | + |
| 70 | +# Minimal internals API |
| 71 | + |
| 72 | +At a minimum, we need the ability to hash individual chunks, as well as the ability to combine chunks to build the binary tree up to the root. |
| 73 | + |
| 74 | +Previously, the BLAKE3 crate provided exactly this functionality in an undocumented and unstable *guts* API that was also used by the [bao] crate. |
| 75 | + |
| 76 | +A function `parent_cv` provided the ability to combine two non-root hashes (also called continuation values) to another continuation value or root hash. |
| 77 | + |
| 78 | +```rust |
| 79 | +pub fn parent_cv(left_child: &Hash, right_child: &Hash, is_root: bool) -> Hash |
| 80 | +``` |
| 81 | + |
| 82 | +And the `ChunkState` struct provided a builder-like API to compute a (root or non-root) hash for any chunk. |
| 83 | + |
| 84 | +# Why the fork? |
| 85 | + |
| 86 | +One reason for the very good performance of the BLAKE3 hash function is the ability to compute chunk hashes in parallel. BLAKE3 is using instruction level parallelism (SIMD) to the greatest extent possible, and on top of that optionally uses thread level parallelism using the [rayon] crate. |
| 87 | + |
| 88 | +When computing a hash from a large blob, the [Hasher](https://docs.rs/blake3/latest/blake3/struct.Hasher.html) can perform chunk hash computations in any order, and it makes use of this to use SIMD. |
| 89 | + |
| 90 | +But if you only ever ask the poor thing for hashes of *individual* chunks, there is nothing it can do! |
| 91 | + |
| 92 | +So to get the benefit of this awesomeness, we need to give the hash function multiple chunks to work with, even when computing subtree hashes. |
| 93 | + |
| 94 | +`iroh-blobs` works with *chunk groups* of 16 chunks. When sending or receiving data, the most expensive hashing related computation going on in `iroh-blobs` is computing the hash of a subtree consisting of 16 chunks. |
| 95 | + |
| 96 | +You can of course compute this sequentially using the primitives exposed by the guts API. But you only benefit from the parallelism of BLAKE3 if you give all chunks to the hasher all at once. This is exactly what our fork does. it added a fn to the guts api to hash an entire subtree: |
| 97 | + |
| 98 | +```rust |
| 99 | +pub fn hash_subtree(start_chunk: u64, data: &[u8], is_root: bool) -> Hash |
| 100 | +``` |
| 101 | + |
| 102 | +But using and maintaining this fork caused quite some complexity. There were build issues due to symbol collisions when people had both the original BLAKE3 crate and our fork in their dependencies. And we did not stay up to date with the improvements of the upstream crate. |
| 103 | + |
| 104 | +# The new hazmat API |
| 105 | + |
| 106 | +The biggest change of the new hazmat API compared to the guts API is that this is a [public API](https://docs.rs/blake3/latest/blake3/hazmat/index.html) that has the same stability guarantees as anything else in a 1.x rust crate. |
| 107 | + |
| 108 | +<Note> |
| 109 | +There is a convention in the rust-crypto collection of cryptography crates to name such useful but potentially dangerous APIs hazmat APIs, hence the name. |
| 110 | +</Note> |
| 111 | + |
| 112 | +To hash subtrees, the hazmat API has an extension trait [HasherExt](https://docs.rs/blake3/latest/blake3/hazmat/trait.HasherExt.html) that allows [setting the input offset](https://docs.rs/blake3/latest/blake3/hazmat/trait.HasherExt.html#tymethod.set_input_offset) on the standard [Hasher](https://docs.rs/blake3/latest/blake3/struct.Hasher.html). In addition, it adds the ability to finalize the computation to a *non-root* hash or chaining value. |
| 113 | + |
| 114 | +The ability to combine two non-root hashes or chaining values is given using two functions [merge_subtree_root](https://docs.rs/blake3/latest/blake3/hazmat/fn.merge_subtrees_root.html) and [merge_subtree_non_root](https://docs.rs/blake3/latest/blake3/hazmat/fn.merge_subtrees_non_root.html). |
| 115 | + |
| 116 | +A change from the old guts API is that chaining values are now a separate [type alias](https://docs.rs/blake3/latest/blake3/hazmat/type.ChainingValue.html) to distinguish them from root hashes. |
| 117 | + |
| 118 | +There are also a number of functions to use a BLAKE3 hash as an [extendable output function], which we don't use in iroh-blobs. |
| 119 | + |
| 120 | +<Note> |
| 121 | +Despite this attempt to make the distinction between chaining values and root hashes more clear, it is still very easy to use the hazmat API in a way that does not result in correct hashes. Hence the large disclaimers in the docs. So be careful! |
| 122 | +</Note> |
| 123 | + |
| 124 | +# Other forks |
| 125 | + |
| 126 | +Due to it's useful tree structure, high performance and built in parallelism, the BLAKE3 hashing function is quite popular. Several other projects like [fleek] or [fluence] also forked the BLAKE3 crate, often for very similar reasons. |
| 127 | + |
| 128 | +<div className="not-prose"> |
| 129 | +<ThemeImage |
| 130 | + lightSrc='/blog/blake3-hazmat-api/crates.png' |
| 131 | + darkSrc='/blog/blake3-hazmat-api/crates.png' |
| 132 | + alt='BLAKE3 forks' |
| 133 | + width={800} |
| 134 | + height={600} |
| 135 | + /> |
| 136 | +</div> |
| 137 | +
|
| 138 | +These are just the public forks from [crates.io](https://crates.io/search?q=blake3), there are probably more private or less visible forks. For example, the [s5 project] also uses BLAKE3 with chunk groups, but with a larger chunk group size than iroh-blobs. |
| 139 | +
|
| 140 | +I hope that some of these other projects will take a look at the new *hazmat* API and will possibly be able to retire their forks as well! |
| 141 | +
|
| 142 | +<Note> |
| 143 | +Thanks to [Jack O'Connor] for BLAKE3 and for providing the new *hazmat* API! |
| 144 | +</Note> |
| 145 | + |
| 146 | +[iroh-blobs]: https://github.com/n0-computer/iroh-blobs |
| 147 | +[BLAKE3]: https://github.com/BLAKE3-team/BLAKE3 |
| 148 | +[bao]: https://crates.io/crates/bao |
| 149 | +[fork]: https://github.com/n0-computer/iroh-blake3 |
| 150 | +[rayon]: https://docs.rs/rayon/latest/rayon/ |
| 151 | +[domain separation]: https://en.wikipedia.org/wiki/Domain_separation |
| 152 | +[extendable output function]: https://en.wikipedia.org/wiki/Extendable-output_function |
| 153 | +[fluence]: https://www.fluence.network/ |
| 154 | +[fleek]: https://fleek.network/ |
| 155 | +[s5 project]: https://docs.sfive.net/ |
| 156 | +[Jack O'Connor]: https://jacko.io/ |
0 commit comments