
# secureCompare()

> Constant-time equality. The trusted value goes first, and anything odd from the wire is a mismatch rather than an exception.

```ts
import { secureCompare } from "unsecure/compare";

secureCompare(storedToken, submittedToken); // boolean
```

## Signature

```ts
function secureCompare(
  expected: string | BytesSource | undefined,
  received: string | BytesSource | null | undefined,
  options?: { strict?: boolean },
): boolean;
```

## Why it exists

`===` on strings stops at the first differing character. The time that takes is measurable, and an attacker who can measure it recovers a secret one character at a time. `secureCompare` reads the same number of bytes whatever the input, so the duration carries no information about where the values diverged.

## The argument order is the contract

The **first** argument determines the loop length.

```ts
// Leaks the length of the attacker's input
secureCompare(userInput, serverToken);

// The trusted value first
secureCompare(serverToken, userInput);
```

## What each argument accepts

`expected` is yours, so a wrong type is your bug and throws:

```ts
secureCompare(12_345 as any, "x");
// UnsecureError INVALID_TYPE: secureCompare: expected a string, ArrayBuffer or ArrayBuffer view, got number.
```

`received` is untrusted, so anything that is not text or bytes is simply a mismatch:

```ts
secureCompare("expected", undefined); // false — a missing database column
secureCompare("expected", null); // false — a missing header
secureCompare("expected", 12_345 as any); // false — a number out of a JSON body
secureCompare("expected", [1, 2, 3] as any); // false — an array out of a JSON body
```

Both sides take a `string` or any `BytesSource`, and mixing them is fine — a string is compared as its UTF-8 bytes.

```ts
secureCompare("match", "match"); // true
secureCompare("hello", new TextEncoder().encode("hello")); // true
secureCompare(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3])); // true
```

## Empty and missing `expected`

By default an empty or `undefined` `expected` returns `false`, the same answer a mismatch gives:

```ts
secureCompare("", "something"); // false
secureCompare(undefined, undefined); // false — never "empty matches empty"
```

`{ strict: true }` throws instead:

```ts
secureCompare(undefined, "x", { strict: true });
// UnsecureError OUT_OF_RANGE
```

## Options

| Option   | Type      | Default | What it does                                                                                |
| -------- | --------- | ------- | ------------------------------------------------------------------------------------------- |
| `strict` | `boolean` | `false` | Throw `OUT_OF_RANGE` when `expected` is empty or `undefined`, instead of returning `false`. |

## Pitfall: `strict: true` in a request handler

`strict: true` turns a missing server-side value into a throw, which your framework turns into a 500 while a wrong signature stays a 403. An attacker can tell those apart, which makes the option a side channel in any code path they can reach.

```ts
// Distinguishable by status code
const valid = secureCompare(serverSecret, userInput, { strict: true });

// Default mode, plus an explicit check at boot
if (!serverSecret) throw new Error("BOOT: serverSecret is not configured");
const valid = secureCompare(serverSecret, userInput);
```

Keep `strict` for start-up assertions and tests.

## Note

[`hmacVerify`](/crypto/hashing/hmac), [`hotpVerify`, `totpVerify`](/crypto/otp) and [`argon2Verify`](/crypto/password-hashing) all use `secureCompare` internally. You do not need to call it yourself when you are using those.
