digest: constant-time comparison (rest of WEB F7) #96
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
std/digestcan compare two byte sequences (a MAC, a digest, a token) without the running time depending on where they first differ, so signed cookies, CSRF tokens and snapshots (WEB.md sections 3.6 and 6.4) can verify aHmacresult safely. Today SHA-256, HMAC and PBKDF2 exist instd/digest;Digest ==is the derived list equality, which is not specified as constant-time.Source (design record)
std/cryptoand new natives. In the tree the primitives arestd/digest, ordinary TorbScript with no native (docs/standard-library/digest.md), so this issue follows the code, not the F7 wording.Acceptance criteria
std/digestcomparing twoBytesfor equality that reads every byte of the shorter-or-equal common length and combines differences by XOR/OR accumulation, with no early exit on the first difference; unequal lengths answerfalseDigestcan be compared the same way (sohmac.finished()against a received MAC needs no conversion)std/digesttest file): equal inputs, a difference in the first byte, in the last byte, in a middle byte, unequal length, empty against empty, empty against non-empty; a pure-function check that the result matches==on all of themdocs/standard-library/digest.mddocuments the function with atrb checkexample and states what is promised (no data-dependent early exit in the source) and what is not (no guarantee against a compiler or CPU timing effect)Seed
Does not touch the seed, provided it stays ordinary TorbScript in
std/digest: no syntax change, no new native, no driver flag, and no std name the compiler looks up by string. If the implementation turns out to need a native, stop and re-plan (two commits, seed refresh).Size
One PR, one Conventional Commit (
feat(digest): constant-time comparison), docs and tests in the same commit.Dependencies
None.
Open owner question
The public name and shape of the new API is not settled in a design record; see the
owner decisioncomment.[loop:torbscript] intake: in scope (author orion, label loop). Matches WEB.md F7 (constant-time comparison); SHA-256, HMAC and random already exist. No collision with a design record or CONCEPT "Open Questions". Likely a new native in
std/digest, hence the two-commit seed rule; to settle in refine. next: refine.[loop:torbscript] claim refine by session:loop-torbscript until 2026-10-06T06:58:30Z
[loop:torbscript] owner decision: name and shape of the constant-time comparison API (library design choice, not in a design record).
Options:
Digest.equalsConstantTime(other: Digest): Boolplus a free functionconstantTimeEqual(a: Bytes, b: Bytes): Boolinstd/digest.constantTimeEqual(a: Bytes, b: Bytes): Bool; callers usedigest.bytesorBytes.std/cryptopackage as F7 words it, with the comparison there (later home for the random source).Recommendation: 1. It is where HMAC lives and where WEB.md callers need it, adds one name and no package;
std/cryptocan still re-export it later. The implementation is plain TorbScript, so no seed refresh.Until answered the issue is not labelled
ready.[loop:torbscript] refine: body rewritten to the definition of ready (goal, criteria, F7 record, no seed touch, one commit); open owner question on the API name/shape,
readywithheld, next: wait for Torben's decision comment, then labelreadyand implement.[loop:torbscript] owner decision recorded (torben, 2026-10-06): option (a). The primary public API is the method
Digest.equalsConstantTime(other)instd/digest; the free function stays as a thin wrapper over the method. Owner direction for the codebase: prefer methods over free functions where they make sense (many free functions predate the current syntax), so implementation, docs and examples lead with the method. Labelledready.[loop:torbscript] claim implement by session:loop-torbscript-20261007 until 2026-10-07T05:32:48Z
[loop:torbscript] implement: PR #149 (
Digest.equalsConstantTimeas the primary method,constantTimeEqualas a thin wrapper, tests, docs, regenerated skills, WEB.md F7 updated; one Conventional Commit, no seed touch). Localsh tools/gates.sh awas red on five gates (format, std index, skills, one doc example), all fixed in the commit and re-verified; forge CI on the PR head was green (state success). The merge gate (argus) approved and landed it ascd9c5500on main;mainpush CI was still running when I stopped, so the close stage still has to confirm it. next: close (main CI green oncd9c5500, then close the issue)Bytes(slice of #146) #175MarkdownDocument(slice of #146) #178