// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import * as hex from "../../encoding/hex.ts"; type SpongePermutator = (data: Uint8Array) => void; /** Sponge construction option */ export interface SpongeOption { bitsize: number; rate: number; dsbyte: number; permutator: SpongePermutator; } export type Message = string | ArrayBuffer; const STATE_SIZE = 200; const TYPE_ERROR_MSG = "sha3: `data` is invalid type"; /** Sponge construction */ export class Sponge { #option: SpongeOption; #state: Uint8Array; #rp: number; #absorbing: boolean; constructor(option: SpongeOption) { this.#option = option; this.#state = new Uint8Array(STATE_SIZE); this.#rp = 0; this.#absorbing = true; } /** Applies padding to internal state */ private pad(): void { this.#state[this.#rp] ^= this.#option.dsbyte; this.#state[this.#option.rate - 1] ^= 0x80; } /** Squeezes internal state */ protected squeeze(length: number): Uint8Array { if (length < 0) { throw new Error("sha3: length cannot be negative"); } this.pad(); const hash = new Uint8Array(length); let pos = 0; while (length > 0) { const r = length > this.#option.rate ? this.#option.rate : length; this.#option.permutator(this.#state); hash.set(this.#state.slice(0, r), pos); length -= r; pos += r; } this.#absorbing = false; return hash; } /** Updates internal state by absorbing */ update(data: Message): this { if (!this.#absorbing) { throw new Error("sha3: cannot update already finalized hash"); } let msg: Uint8Array; if (typeof data === "string") { msg = new TextEncoder().encode(data as string); } else if (typeof data === "object") { if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) { msg = new Uint8Array(data); } else { throw new Error(TYPE_ERROR_MSG); } } else { throw new Error(TYPE_ERROR_MSG); } let rp = this.#rp; for (let i = 0; i < msg.length; ++i) { this.#state[rp++] ^= msg[i]; if (rp >= this.#option.rate) { this.#option.permutator(this.#state); rp = 0; } } this.#rp = rp; return this; } /** Returns the hash in ArrayBuffer */ digest(): ArrayBuffer { return this.squeeze(this.#option.bitsize >> 3); } /** Returns the hash in given format */ toString(format: "hex" = "hex"): string { const rawOutput = this.squeeze(this.#option.bitsize >> 3); switch (format) { case "hex": return hex.encodeToString(rawOutput); default: throw new Error("sha3: invalid output format"); } } }