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
I am using libsodium.js (the sumo build) to end-to-end encrypt file transfers that run entirely in the browser. For big payloads: tens of GB up to 1 TB , secretstream became the bottleneck: since every chunk has to be processed in order on one thread.
And it also makes "resume after the tab or connection dropped" painful, since you can't jump to chunk N without replaying everything before it.
So I made each chunk an independent XChaCha20-Poly1305 seal:
nonce = fileNonce ‖ u64be(index)
aad = u64be(index)
subkey = BLAKE2b("vitalsend:{chunk,meta,header}:v3", key=rootKey)
rootKey = linkKey (32B, URL #fragment only)
| BLAKE2b(linkKey, key=Argon2id(pw)) // when a password is set
The header is itself authenticated and pins chunkSize, totalChunks, fileNonce and the Argon2id params. Because the index is welded into both the nonce and the AAD, reordering / truncation / duplication / extension / cross-file substitution should all fail to authenticate. The payoff: chunks are independent, so a worker pool can encrypt in parallel and a stalled upload resumes from any offset.
Where a second opinion would help:
Does explicit index-binding (nonce + AAD) actually recover everything secretstream's sequential chaining guarantees, or is there a boundary/ordering case that slips through once chunks are independent?
Any nonce-management trap once chunk counts get huge (TB-scale, millions of chunks)?
Anything about the header layout or the BLAKE2b / Argon2id schedule you'd structure differently?
Full construction, wire format, and a deliberately honest SECURITY.md: https://github.com/vitalsend/crypto — primitives are all stock libsodium, the only novel part is the framing.
P.S. ran the whole thing past Fable 5 and it didn't flag anything here, but it's an LLM, hence asking humans. :-)
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
I am using libsodium.js (the sumo build) to end-to-end encrypt file transfers that run entirely in the browser. For big payloads: tens of GB up to 1 TB , secretstream became the bottleneck: since every chunk has to be processed in order on one thread.
And it also makes "resume after the tab or connection dropped" painful, since you can't jump to chunk N without replaying everything before it.
So I made each chunk an independent XChaCha20-Poly1305 seal:
nonce = fileNonce ‖ u64be(index)
aad = u64be(index)
subkey = BLAKE2b("vitalsend:{chunk,meta,header}:v3", key=rootKey)
rootKey = linkKey (32B, URL #fragment only)
| BLAKE2b(linkKey, key=Argon2id(pw)) // when a password is set
The header is itself authenticated and pins chunkSize, totalChunks, fileNonce and the Argon2id params. Because the index is welded into both the nonce and the AAD, reordering / truncation / duplication / extension / cross-file substitution should all fail to authenticate. The payoff: chunks are independent, so a worker pool can encrypt in parallel and a stalled upload resumes from any offset.
Where a second opinion would help:
Full construction, wire format, and a deliberately honest SECURITY.md: https://github.com/vitalsend/crypto — primitives are all stock libsodium, the only novel part is the framing.
P.S. ran the whole thing past Fable 5 and it didn't flag anything here, but it's an LLM, hence asking humans. :-)
Beta Was this translation helpful? Give feedback.
All reactions