Earn Your Stripes
Authors
Published
August 13, 2026
You can also read this blog post on commonware.xyz’s blog.
A blockchain can only finalize transactions as fast as it can disseminate them between validators. At high TPS, that means moving big blocks across the network.
Sending a full copy to every validator turns the leader’s upload into the bottleneck while everyone else’s bandwidth sits idle. Deliver Us in Pieces showed how erasure coding could be seamlessly integrated with Simplex to spread that load across the network: the leader sends a different shard to each validator, validators relay what they receive, and everyone reconstructs the full block once enough pieces arrive.
Those bandwidth savings come with a computational tradeoff: the leader must encode each block before sending it, and every validator must reconstruct it and verify its commitment before certification. Until recently, Reed-Solomon recovery bottlenecked on a single core. We removed that bottleneck by cutting every shard into matching stripes and recovering them in parallel.
Parallel Hashing, Serial Recovery
Reconstruction was already parallelized, but only while hashing missing shards to rebuild the Merkle root. Reed-Solomon recovery itself remained a single decoding pass. Vector instructions accelerated that pass on supported CPUs, but it still ran on one core.
Benchmarks on an Apple M5 Pro (8 MiB block, 250 chunks) show where recovery stops scaling. The all-original case does no recovery. The full-recovery case reconstructs every original:
From 1 to 16 workers, all-original end-to-end latency dropped from 34.96 ms to 7.90 ms. Full recovery improved through 8 workers, then changed little at 16, flattening near 26 ms. Because the cases hash different sets of missing shards, the gap between them does not isolate recovery time. The relevant signal is how they scale: the all-original path keeps improving, while the recovery-heavy path barely changes beyond 8 workers.
From One Decoder to Many
Reed-Solomon encoding represents the block as equal-length shards, with original shards and recovery shards. When original shards are missing, any shards can recover the original block. Before this change, recovery handed the full shard width to one Reed-Solomon decoder.
To recover one 16-bit symbol inside a missing shard, the decoder only needs the corresponding symbol column from the available shards. We turn that into parallel work by cutting the shard width into contiguous byte ranges, or stripes. Each stripe covers the same range across all shards and runs as its own Reed-Solomon job. The jobs run concurrently.
Let be the codeword index of supplied shard , for , and write . All supplied shards have the same even byte length. Split them into stripes at identical boundaries. Every non-final boundary is aligned to 64 bytes, so the original partial tail, if any, remains wholly in the final stripe. For every stripe , the decoder receives the indexed slices , preserving each shard’s original codeword index:
Reed-Solomon recovery works independently at each symbol position. Stripe therefore recovers only stripe of the missing shard :
No stripe reads or writes another stripe. The stripes can therefore be recovered in parallel, and concatenating their outputs produces the same as one full-width decode.
Figure 1: Recovering missing original shard used to run as one full-width Reed-Solomon job. After striping, each aligned range becomes its own job. The jobs run in parallel, then the recovered ranges concatenate into the same full-width .
Commonware’s authenticated Reed-Solomon codec uses the novel-polynomial-basis FFT construction to evaluate polynomials over a binary extension field with a radix-2-style butterfly. Write the shard matrix as , where is the vector formed by symbol column across the shards. If is the encoding or recovery transform, the butterfly acts independently on each column:
In theory, a stripe can therefore end at any symbol boundary.
The optimized arithmetic processes 32 columns as one batch. Modern CPUs can apply the same operation to several values with one vector instruction, so this layout lets each butterfly step update several columns at once:
A short final batch is padded. Cutting an interior stripe through a batch would turn that slice into a different padded tail, so every non-final stripe ends between complete batches. This implementation constraint preserves the column-wise decomposition above.
Recovery Scales
We reran the same worst-case decode before and after striping:
With one worker, one stripe follows the original full-width decode, so no parallel speedup is expected. With eight workers, parallel recovery cuts worst-case decode from 26.85 ms to 10.22 ms, a 2.63x speedup. With 16 workers, it falls to 7.67 ms, a 3.38x speedup. The recovery work that stayed on one core now scales with the available workers.
Removing the Second Transform
Splitting each shard into independent stripes made recovery parallel, but the verification path still ran Reed-Solomon twice. After it decoded missing originals, it re-encoded every recovery shard, compared any provided recoveries with that output, and rebuilt the Merkle root over the complete codeword. As explained in Deliver Us in Pieces, checking a shard against the commitment does not by itself show that all committed shards form one valid Reed-Solomon codeword.
By vendoring reed-solomon-simd, we could make the decoder return missing recovery positions and remove the second pass whenever an original shard was missing. The expensive decode transform had already evaluated those positions, but only missing originals were exposed.
Write the systematic generator matrix as
where contains the original shards, generates the recovery shards, and is the complete codeword. In Figure 2, take the ordered row lists and . Let and select rows of in those orders. Any codeword rows determine , so one decode determines both missing rows:
The old decoder returned only . Verification then rebuilt the original vector and ran the encoder:
That re-encode repeated the expensive codeword transform to materialize canonical . Decoding had already evaluated the position. Let be the intermediate value at a missing position after the IFFT, formal derivative, and FFT. The canonical codeword value differs only by the known erasure-locator scale :
Previously, this unscale was applied only to missing originals. Decode-reveal applies it to every symbol of each missing recovery shard, then normalizes a partial final 64-byte batch. Algebraically, revealing missing recovery shards of symbols each adds field multiplications. Because the arithmetic runs in 32-symbol batches, the implementation touches
lanes, including final-block padding. Re-encoding would instead rerun the encoder’s IFFT/FFT pipeline across every symbol column to recompute all recovery shards.
The recovery path now feeds exactly shards to the decoder. If more arrive, surplus recovery shards are treated as missing and reconstructed too. Let be the committed root of the binary Merkle tree (BMT). The acceptance condition is unchanged:
Checked positions reuse their verified digests, while reconstructed positions are hashed before the tree is rebuilt. We skip the encode, not the verification.
When the supplied set contains all original shards, no inverse decode runs and there are no hidden recovery rows to reveal. That path still computes .
The block itself only needs the missing originals. Missing recovery positions matter here because decode also verifies the commitment: rebuilding the BMT root requires a digest at every shard position.
Figure 2: Both paths derive and . The old decoder hid , so the encoder derived and again before verification. Decode-reveal returns the already-derived with , eliminating the second transform while preserving the full-root check. When all originals are supplied there is nothing to decode, so the re-encode path remains.
Revealed recovery shards are byte-identical to the encoder’s output, including shard widths that are not 64-byte aligned.
Running the recovery-heavy benchmark first with striping alone and then with decode-reveal isolates the improvement:
These measurements cover the entire recovery-heavy verification path, not just the transforms themselves.
The final pull request combines striping with decode-reveal:
Finding Bugs (and Bottlenecks?)
Sunghyeon used QED’s research agent to find this optimization. The agent read the coding stack and noticed that hashing the missing shards in decode was parallelized, but Reed-Solomon recovery was not. It then tested whether recovery could be split into independent stripes and run in parallel.
The loop resembles the one QED uses for security audits. It scans the code, gathers repository evidence, forms hypotheses, and verifies them with tests and benchmarks. Repository context separates a useful finding from a known issue or an intentional design choice. QED had built that context by continuously auditing Commonware.
Tuning to the Limit
It is more practical than ever to tune low-level primitives to run at the limits of modern hardware. We are continuing to invest in low-level tuning across the Commonware Library, from our recent SHA-256 optimizations to ongoing work on faster Ed25519 signature verification.