diff options
| author | skdltmxn <supershop@naver.com> | 2020-06-05 04:14:36 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-06-04 15:14:36 -0400 |
| commit | e05ed537130eff116b48af02d1eb6ce65953e7d8 (patch) | |
| tree | 0bd1ca235ad9ed89ca602c384ab39301346afe24 /std/hash/_sha3/sponge.ts | |
| parent | 430beebc46be1dc87a993c2f7442c5c22076f458 (diff) | |
feat(std/hash): add sha3 (#5558)
Diffstat (limited to 'std/hash/_sha3/sponge.ts')
| -rw-r--r-- | std/hash/_sha3/sponge.ts | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/std/hash/_sha3/sponge.ts b/std/hash/_sha3/sponge.ts new file mode 100644 index 000000000..a5705e13e --- /dev/null +++ b/std/hash/_sha3/sponge.ts @@ -0,0 +1,111 @@ +// 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"); + } + } +} |
