Skip to content

Commit 03dd349

Browse files
authored
common: add sha3. (#655)
1 parent 908f7f0 commit 03dd349

File tree

4 files changed

+499
-8
lines changed

4 files changed

+499
-8
lines changed

packages/common/mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export {
5151

5252
export { hmac } from "./src/hash/hmac.ts";
5353
export { sha256, sha384, sha512 } from "./src/hash/sha2.ts";
54+
export { sha3_256, sha3_512, shake128, shake256 } from "./src/hash/sha3.ts";
5455

5556
export { mod, pow2 } from "./src/curve/modular.ts";
5657
export { montgomery, type MontgomeryECDH } from "./src/curve/montgomery.ts";

packages/common/src/hash/sha3.ts

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
/**
2+
* This file is based on noble-hashes (https://github.com/paulmillr/noble-hashes).
3+
*
4+
* noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com)
5+
*
6+
* The original file is located at:
7+
* https://github.com/paulmillr/noble-hashes/blob/4e358a46d682adfb005ae6314ec999f2513086b9/src/sha3.ts
8+
*/
9+
10+
/**
11+
* SHA3 (keccak) hash function, based on a new "Sponge function" design.
12+
* Different from older hashes, the internal state is bigger than output size.
13+
*
14+
* Check out [FIPS-202](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf),
15+
* [Website](https://keccak.team/keccak.html),
16+
* [the differences between SHA-3 and Keccak](https://crypto.stackexchange.com/questions/15727/what-are-the-key-differences-between-the-draft-sha-3-standard-and-the-keccak-sub).
17+
*
18+
* Check out `sha3-addons` module for cSHAKE, k12, and others.
19+
* @module
20+
*/
21+
import { rotlBH, rotlBL, rotlSH, rotlSL, split } from "./u64.ts";
22+
import {
23+
abytes,
24+
aexists,
25+
anumber,
26+
aoutput,
27+
type CHash,
28+
type CHashXOF,
29+
clean,
30+
createHasher,
31+
type Hash,
32+
type HashInfo,
33+
type HashXOF,
34+
oidNist,
35+
swap32IfBE,
36+
u32,
37+
} from "../utils/noble.ts";
38+
39+
// No __PURE__ annotations in sha3 header:
40+
// EVERYTHING is in fact used on every export.
41+
// Various per round constants calculations
42+
const _0n = BigInt(0);
43+
const _1n = BigInt(1);
44+
const _2n = BigInt(2);
45+
const _7n = BigInt(7);
46+
const _256n = BigInt(256);
47+
const _0x71n = BigInt(0x71);
48+
const SHA3_PI: number[] = [];
49+
const SHA3_ROTL: number[] = [];
50+
const _SHA3_IOTA: bigint[] = []; // no pure annotation: var is always used
51+
for (let round = 0, R = _1n, x = 1, y = 0; round < 24; round++) {
52+
// Pi
53+
[x, y] = [y, (2 * x + 3 * y) % 5];
54+
SHA3_PI.push(2 * (5 * y + x));
55+
// Rotational
56+
SHA3_ROTL.push((((round + 1) * (round + 2)) / 2) % 64);
57+
// Iota
58+
let t = _0n;
59+
for (let j = 0; j < 7; j++) {
60+
R = ((R << _1n) ^ ((R >> _7n) * _0x71n)) % _256n;
61+
if (R & _2n) t ^= _1n << ((_1n << BigInt(j)) - _1n);
62+
}
63+
_SHA3_IOTA.push(t);
64+
}
65+
const IOTAS = split(_SHA3_IOTA, true);
66+
const SHA3_IOTA_H = IOTAS[0];
67+
const SHA3_IOTA_L = IOTAS[1];
68+
69+
// Left rotation (without 0, 32, 64)
70+
const rotlH = (
71+
h: number,
72+
l: number,
73+
s: number,
74+
) => (s > 32 ? rotlBH(h, l, s) : rotlSH(h, l, s));
75+
const rotlL = (
76+
h: number,
77+
l: number,
78+
s: number,
79+
) => (s > 32 ? rotlBL(h, l, s) : rotlSL(h, l, s));
80+
81+
/** `keccakf1600` internal function, additionally allows to adjust round count. */
82+
export function keccakP(s: Uint32Array, rounds: number = 24): void {
83+
const B = new Uint32Array(5 * 2);
84+
// NOTE: all indices are x2 since we store state as u32 instead of u64 (bigints to slow in js)
85+
for (let round = 24 - rounds; round < 24; round++) {
86+
// Theta θ
87+
for (let x = 0; x < 10; x++) {
88+
B[x] = s[x] ^ s[x + 10] ^ s[x + 20] ^ s[x + 30] ^ s[x + 40];
89+
}
90+
for (let x = 0; x < 10; x += 2) {
91+
const idx1 = (x + 8) % 10;
92+
const idx0 = (x + 2) % 10;
93+
const B0 = B[idx0];
94+
const B1 = B[idx0 + 1];
95+
const Th = rotlH(B0, B1, 1) ^ B[idx1];
96+
const Tl = rotlL(B0, B1, 1) ^ B[idx1 + 1];
97+
for (let y = 0; y < 50; y += 10) {
98+
s[x + y] ^= Th;
99+
s[x + y + 1] ^= Tl;
100+
}
101+
}
102+
// Rho (ρ) and Pi (π)
103+
let curH = s[2];
104+
let curL = s[3];
105+
for (let t = 0; t < 24; t++) {
106+
const shift = SHA3_ROTL[t];
107+
const Th = rotlH(curH, curL, shift);
108+
const Tl = rotlL(curH, curL, shift);
109+
const PI = SHA3_PI[t];
110+
curH = s[PI];
111+
curL = s[PI + 1];
112+
s[PI] = Th;
113+
s[PI + 1] = Tl;
114+
}
115+
// Chi (χ)
116+
for (let y = 0; y < 50; y += 10) {
117+
for (let x = 0; x < 10; x++) B[x] = s[y + x];
118+
for (let x = 0; x < 10; x++) {
119+
s[y + x] ^= ~B[(x + 2) % 10] & B[(x + 4) % 10];
120+
}
121+
}
122+
// Iota (ι)
123+
s[0] ^= SHA3_IOTA_H[round];
124+
s[1] ^= SHA3_IOTA_L[round];
125+
}
126+
clean(B);
127+
}
128+
129+
/** Keccak sponge function. */
130+
export class Keccak implements Hash<Keccak>, HashXOF<Keccak> {
131+
protected state: Uint8Array;
132+
protected pos = 0;
133+
protected posOut = 0;
134+
protected finished = false;
135+
protected state32: Uint32Array;
136+
protected destroyed = false;
137+
138+
public blockLen: number;
139+
public suffix: number;
140+
public outputLen: number;
141+
protected enableXOF = false;
142+
protected rounds: number;
143+
144+
// NOTE: we accept arguments in bytes instead of bits here.
145+
constructor(
146+
blockLen: number,
147+
suffix: number,
148+
outputLen: number,
149+
enableXOF = false,
150+
rounds: number = 24,
151+
) {
152+
this.blockLen = blockLen;
153+
this.suffix = suffix;
154+
this.outputLen = outputLen;
155+
this.enableXOF = enableXOF;
156+
this.rounds = rounds;
157+
// Can be passed from user as dkLen
158+
anumber(outputLen, "outputLen");
159+
// 1600 = 5x5 matrix of 64bit. 1600 bits === 200 bytes
160+
// 0 < blockLen < 200
161+
if (!(0 < blockLen && blockLen < 200)) {
162+
throw new Error("only keccak-f1600 function is supported");
163+
}
164+
this.state = new Uint8Array(200);
165+
this.state32 = u32(this.state);
166+
}
167+
clone(): Keccak {
168+
return this._cloneInto();
169+
}
170+
protected keccak(): void {
171+
swap32IfBE(this.state32);
172+
keccakP(this.state32, this.rounds);
173+
swap32IfBE(this.state32);
174+
this.posOut = 0;
175+
this.pos = 0;
176+
}
177+
update(data: Uint8Array): this {
178+
aexists(this);
179+
abytes(data);
180+
const { blockLen, state } = this;
181+
const len = data.length;
182+
for (let pos = 0; pos < len;) {
183+
const take = Math.min(blockLen - this.pos, len - pos);
184+
for (let i = 0; i < take; i++) state[this.pos++] ^= data[pos++];
185+
if (this.pos === blockLen) this.keccak();
186+
}
187+
return this;
188+
}
189+
protected finish(): void {
190+
if (this.finished) return;
191+
this.finished = true;
192+
const { state, suffix, pos, blockLen } = this;
193+
// Do the padding
194+
state[pos] ^= suffix;
195+
if ((suffix & 0x80) !== 0 && pos === blockLen - 1) this.keccak();
196+
state[blockLen - 1] ^= 0x80;
197+
this.keccak();
198+
}
199+
protected writeInto(out: Uint8Array): Uint8Array {
200+
aexists(this, false);
201+
abytes(out);
202+
this.finish();
203+
const bufferOut = this.state;
204+
const { blockLen } = this;
205+
for (let pos = 0, len = out.length; pos < len;) {
206+
if (this.posOut >= blockLen) this.keccak();
207+
const take = Math.min(blockLen - this.posOut, len - pos);
208+
out.set(bufferOut.subarray(this.posOut, this.posOut + take), pos);
209+
this.posOut += take;
210+
pos += take;
211+
}
212+
return out;
213+
}
214+
xofInto(out: Uint8Array): Uint8Array {
215+
// Sha3/Keccak usage with XOF is probably mistake, only SHAKE instances can do XOF
216+
if (!this.enableXOF) {
217+
throw new Error("XOF is not possible for this instance");
218+
}
219+
return this.writeInto(out);
220+
}
221+
xof(bytes: number): Uint8Array {
222+
anumber(bytes);
223+
return this.xofInto(new Uint8Array(bytes));
224+
}
225+
digestInto(out: Uint8Array): Uint8Array {
226+
aoutput(out, this);
227+
if (this.finished) throw new Error("digest() was already called");
228+
this.writeInto(out);
229+
this.destroy();
230+
return out;
231+
}
232+
digest(): Uint8Array {
233+
return this.digestInto(new Uint8Array(this.outputLen));
234+
}
235+
destroy(): void {
236+
this.destroyed = true;
237+
clean(this.state);
238+
}
239+
_cloneInto(to?: Keccak): Keccak {
240+
const { blockLen, suffix, outputLen, rounds, enableXOF } = this;
241+
to ||= new Keccak(blockLen, suffix, outputLen, enableXOF, rounds);
242+
to.state32.set(this.state32);
243+
to.pos = this.pos;
244+
to.posOut = this.posOut;
245+
to.finished = this.finished;
246+
to.rounds = rounds;
247+
// Suffix can change in cSHAKE
248+
to.suffix = suffix;
249+
to.outputLen = outputLen;
250+
to.enableXOF = enableXOF;
251+
to.destroyed = this.destroyed;
252+
return to;
253+
}
254+
}
255+
256+
const genKeccak = (
257+
suffix: number,
258+
blockLen: number,
259+
outputLen: number,
260+
info: HashInfo = {},
261+
) => createHasher(() => new Keccak(blockLen, suffix, outputLen), info);
262+
263+
// /** SHA3-224 hash function. */
264+
// export const sha3_224: CHash = /* @__PURE__ */ genKeccak(
265+
// 0x06,
266+
// 144,
267+
// 28,
268+
// /* @__PURE__ */ oidNist(0x07),
269+
// );
270+
271+
/** SHA3-256 hash function. Different from keccak-256. */
272+
export const sha3_256: CHash = /* @__PURE__ */ genKeccak(
273+
0x06,
274+
136,
275+
32,
276+
/* @__PURE__ */ oidNist(0x08),
277+
);
278+
279+
// /** SHA3-384 hash function. */
280+
// export const sha3_384: CHash = /* @__PURE__ */ genKeccak(
281+
// 0x06,
282+
// 104,
283+
// 48,
284+
// /* @__PURE__ */ oidNist(0x09),
285+
// );
286+
287+
/** SHA3-512 hash function. */
288+
export const sha3_512: CHash = /* @__PURE__ */ genKeccak(
289+
0x06,
290+
72,
291+
64,
292+
/* @__PURE__ */ oidNist(0x0a),
293+
);
294+
295+
/** keccak-224 hash function. */
296+
export const keccak_224: CHash = /* @__PURE__ */ genKeccak(0x01, 144, 28);
297+
/** keccak-256 hash function. Different from SHA3-256. */
298+
export const keccak_256: CHash = /* @__PURE__ */ genKeccak(0x01, 136, 32);
299+
/** keccak-384 hash function. */
300+
export const keccak_384: CHash = /* @__PURE__ */ genKeccak(0x01, 104, 48);
301+
/** keccak-512 hash function. */
302+
export const keccak_512: CHash = /* @__PURE__ */ genKeccak(0x01, 72, 64);
303+
304+
export type ShakeOpts = { dkLen?: number };
305+
306+
const genShake = (
307+
suffix: number,
308+
blockLen: number,
309+
outputLen: number,
310+
info: HashInfo = {},
311+
) =>
312+
createHasher<Keccak, ShakeOpts>(
313+
(opts: ShakeOpts = {}) =>
314+
new Keccak(
315+
blockLen,
316+
suffix,
317+
opts.dkLen === undefined ? outputLen : opts.dkLen,
318+
true,
319+
),
320+
info,
321+
);
322+
323+
/** SHAKE128 XOF with 128-bit security. */
324+
export const shake128: CHashXOF<Keccak, ShakeOpts> =
325+
/* @__PURE__ */
326+
genShake(0x1f, 168, 16, /* @__PURE__ */ oidNist(0x0b));
327+
/** SHAKE256 XOF with 256-bit security. */
328+
export const shake256: CHashXOF<Keccak, ShakeOpts> =
329+
/* @__PURE__ */
330+
genShake(0x1f, 136, 32, /* @__PURE__ */ oidNist(0x0c));
331+
332+
// /** SHAKE128 XOF with 256-bit output (NIST version). */
333+
// export const shake128_32: CHashXOF<Keccak, ShakeOpts> =
334+
// /* @__PURE__ */
335+
// genShake(0x1f, 168, 32, /* @__PURE__ */ oidNist(0x0b));
336+
// /** SHAKE256 XOF with 512-bit output (NIST version). */
337+
// export const shake256_64: CHashXOF<Keccak, ShakeOpts> =
338+
// /* @__PURE__ */
339+
// genShake(0x1f, 136, 64, /* @__PURE__ */ oidNist(0x0c));

0 commit comments

Comments
 (0)