You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We'd like to add a first-class abstraction for hashing N independent messages at once, so an implementation can place one message per SIMD lane and run the compression function across all lanes simultaneously:
Given N independent byte messages, produce their N digests in a single call.
This is the classic "multi-buffer" SHA construction (Gueron–Krasnov, Intel isa-l sha512_mb/sha256_mb). It exploits the fact that the round function has no cross-message dependency, even though it's fully serial within one message — so it's a different axis of parallelism from everything RustCrypto has today.
Why it's missing today (prior art)
As far as we can tell this capability doesn't exist anywhere in RustCrypto, and the existing parallelism is all a different axis:
ParBlocksSizeUser / ParBlocks (crypto-common, used by cipher) is the closest structural match — "process LANES units at once + scalar tail," via encrypt_par_blocks/encrypt_tail_blocks over inout::InOutBuf. But it means multiple blocks of one stream, not independent messages.
BLAKE2bp/sp, BLAKE3 tree mode, the sha2 AVX2 backend (elliptic-curve v0.6.4 #327) are all single-message internal SIMD.
The hashing core API (digest/src/block_api.rs) has no par-blocks concept; update_blocks is serial over one message.
So this would be a genuinely new capability on the hashing side, ideally borrowing the cipher-side conventions.
Motivating use case
Parallel Merkle-tree leaf hashing: the leaves are independent, equal-length messages — the ideal multi-buffer workload. (Our concrete driver is a parallel, order-independent launch-measurement for AMD SEV-SNP confidential VMs, but the primitive is general: tree/dedup/storage/packet hashing all want it.) On AMD in particular, SHA-384/512 has no hardware engine, so multi-buffer is the only way to use the SIMD datapath — and it's substantial on the Zen 5 / Turin native 512-bit path.
Design options
A — High-level one-shot trait on top of Digest, runtime dispatch internal (sha2-style):
pubtraitMultiDigestBackend:OutputSizeUser{typeLanes:ArraySize;// typenum, like ParBlocksSizefndigest_lanes(msgs:&Array<&[u8],Lanes>,out:&mutArray<Output<Self>,Lanes>);fndigest_tail(..);// len < Lanes}// + a driver that splits into Lanes-chunks then a scalar tail
C — Streaming multi-lane context (isa-l manager-style): N independent incremental states with submit/flush, handling unequal-length messages that finish at different times. Most general, closest to isa-l, most complex.
Does a multi-message hashing trait belong in digest (or crypto-common, or a new crate), or would you prefer inherent batch methods on individual hashes rather than a shared trait?
Width as a typenum associated type (type Lanes: ArraySize, matching ParBlocksSize) vs kept internal with runtime cpufeatures dispatch (matching how sha2 selects backends)? These two precedents point in different directions here.
Is a one-shot, equal-length primitive an acceptable MVP, or do you want the general (unequal-length / streaming) shape from the start?
I/O convention: caller-provided output slice (à la inout/InOutBuf), and should inputs reuse InOutBuf or just &[&[u8]]?
Naming: MultiDigest + Lanes (deliberately avoiding Par*, which already means intra-message)?
Happy to prototype whichever direction you prefer — we have a multi-buffer SHA-512/384 (AVX2 4-lane, AVX-512 8-lane) ready to shape to your guidance, with all SHA-2 variants to follow.
The core primitive
We'd like to add a first-class abstraction for hashing N independent messages at once, so an implementation can place one message per SIMD lane and run the compression function across all lanes simultaneously:
This is the classic "multi-buffer" SHA construction (Gueron–Krasnov, Intel isa-l
sha512_mb/sha256_mb). It exploits the fact that the round function has no cross-message dependency, even though it's fully serial within one message — so it's a different axis of parallelism from everything RustCrypto has today.Why it's missing today (prior art)
As far as we can tell this capability doesn't exist anywhere in RustCrypto, and the existing parallelism is all a different axis:
ParBlocksSizeUser/ParBlocks(crypto-common, used bycipher) is the closest structural match — "process LANES units at once + scalar tail," viaencrypt_par_blocks/encrypt_tail_blocksoverinout::InOutBuf. But it means multiple blocks of one stream, not independent messages.digest/src/block_api.rs) has no par-blocks concept;update_blocksis serial over one message.So this would be a genuinely new capability on the hashing side, ideally borrowing the cipher-side conventions.
Motivating use case
Parallel Merkle-tree leaf hashing: the leaves are independent, equal-length messages — the ideal multi-buffer workload. (Our concrete driver is a parallel, order-independent launch-measurement for AMD SEV-SNP confidential VMs, but the primitive is general: tree/dedup/storage/packet hashing all want it.) On AMD in particular, SHA-384/512 has no hardware engine, so multi-buffer is the only way to use the SIMD datapath — and it's substantial on the Zen 5 / Turin native 512-bit path.
Design options
A — High-level one-shot trait on top of
Digest, runtime dispatch internal (sha2-style):B — Backend + driver split mirroring
cipher'sParBlocks(compile-time width):C — Streaming multi-lane context (isa-l manager-style): N independent incremental states with submit/flush, handling unequal-length messages that finish at different times. Most general, closest to isa-l, most complex.
type Lanes: ArraySizecpufeatures(like sha2)ParBlocksOpen questions for @tarcieri
digest(orcrypto-common, or a new crate), or would you prefer inherent batch methods on individual hashes rather than a shared trait?typenumassociated type (type Lanes: ArraySize, matchingParBlocksSize) vs kept internal with runtimecpufeaturesdispatch (matching howsha2selects backends)? These two precedents point in different directions here.inout/InOutBuf), and should inputs reuseInOutBufor just&[&[u8]]?MultiDigest+Lanes(deliberately avoidingPar*, which already means intra-message)?Happy to prototype whichever direction you prefer — we have a multi-buffer SHA-512/384 (AVX2 4-lane, AVX-512 8-lane) ready to shape to your guidance, with all SHA-2 variants to follow.