first commit
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
# constantTimeEqual
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## 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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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` — returns `true` iff both buffers have the same length **and** every byte matches. When lengths differ, returns `false` immediately (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.
|
||||
Reference in New Issue
Block a user