2.4 KiB
constantTimeEqual
(no submodules)
Purpose
Byte-for-byte comparison of two Uint8Array buffers that takes time dependent only on the input length, not on where they first differ. Use it — and never the built-in ===, Array.prototype.every, or Buffer.equals — for comparing authentication tags, MAC digests, password hashes, and any other value where an attacker might use wall-clock time to learn a secret byte-by-byte.
Features
- No early return on mismatch within the byte loop.
- Constant-time in buffer contents; length check is the only short-circuit (lengths of MACs/tags are public constants).
- Zero allocations; pure loop over the inputs.
- No dependencies.
Usage: Basic Example
import { constantTimeEqual } from "@suaveplan/crypto-utils/constant-time";
const tagReceived = new Uint8Array(16);
const tagExpected = new Uint8Array(16);
if (!constantTimeEqual(tagReceived, tagExpected)) {
throw new Error("tag mismatch");
}
Usage: Advanced Example
import { constantTimeEqual } from "@suaveplan/crypto-utils/constant-time";
// Verify a computed MAC against a header-supplied expected MAC in one step,
// without ever comparing via `===`, `for-of`, or `Buffer.equals`.
function verifyMessageAuth(
macImpl: (msg: Uint8Array) => Uint8Array,
message: Uint8Array,
expected: Uint8Array
): boolean {
const computed = macImpl(message);
return constantTimeEqual(computed, expected);
}
API Reference
constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean— returnstrueiff both buffers have the same length and every byte matches. When lengths differ, returnsfalseimmediately (length is not secret).
Implementation Notes
The loop computes diff |= a[i] ^ b[i], accumulating a nonzero value iff any pair of bytes differs; the final check diff === 0 is also constant time because it inspects a single integer. JavaScript engines aggressively inline this pattern, so the generated machine code is effectively identical to the hand-written one found in @noble/hashes and libsodium's sodium_memcmp. The length short-circuit exists because returning false on length mismatch does not leak secret information — MAC and tag lengths are public protocol constants — and because continuing the loop with a shorter buffer would require masking that adds complexity with no security benefit.