import { timingSafeEqual } from "./deps.ts";import { encodeBase64Safe, importKey, sign } from "./util.ts";import type { Data, Key } from "./types.d.ts";
async function compare(a: Data, b: Data): Promise<boolean> { const key = new Uint8Array(32); globalThis.crypto.getRandomValues(key); const cryptoKey = await importKey(key); const ah = await sign(a, cryptoKey); const bh = await sign(b, cryptoKey); return timingSafeEqual(ah, bh);}
export class KeyStack { #cryptoKeys = new Map<Key, CryptoKey>(); #keys: Key[];
async #toCryptoKey(key: Key): Promise<CryptoKey> { if (!this.#cryptoKeys.has(key)) { this.#cryptoKeys.set(key, await importKey(key)); } return this.#cryptoKeys.get(key)!; }
get length(): number { return this.#keys.length; }
constructor(keys: Key[]) { if (!(0 in keys)) { throw new TypeError("keys must contain at least one value"); } this.#keys = keys; }
async sign(data: Data): Promise<string> { const key = await this.#toCryptoKey(this.#keys[0]); return encodeBase64Safe(await sign(data, key)); }
async verify(data: Data, digest: string): Promise<boolean> { return (await this.indexOf(data, digest)) > -1; }
async indexOf(data: Data, digest: string): Promise<number> { for (let i = 0; i < this.#keys.length; i++) { const cryptoKey = await this.#toCryptoKey(this.#keys[i]); if ( await compare(digest, encodeBase64Safe(await sign(data, cryptoKey))) ) { return i; } } return -1; }
[Symbol.for("Deno.customInspect")](inspect: (value: unknown) => string) { const { length } = this; return `${this.constructor.name} ${inspect({ length })}`; }
[Symbol.for("nodejs.util.inspect.custom")]( depth: number, options: any, inspect: (value: unknown, options?: unknown) => string, ) { if (depth < 0) { return options.stylize(`[${this.constructor.name}]`, "special"); }
const newOptions = Object.assign({}, options, { depth: options.depth === null ? null : options.depth - 1, }); const { length } = this; return `${options.stylize(this.constructor.name, "special")} ${ inspect({ length }, newOptions) }`; }}