UNPKG

20.3 kBPlain TextView Raw
1import { jsSHABase, packedLEConcat, sha_variant_error, mac_rounds_error, TWO_PWR_32, parseInputOption } from "./common";
2import {
3 packedValue,
4 CSHAKEOptionsNoEncodingType,
5 CSHAKEOptionsEncodingType,
6 SHAKEOptionsNoEncodingType,
7 SHAKEOptionsEncodingType,
8 KMACOptionsNoEncodingType,
9 KMACOptionsEncodingType,
10 FixedLengthOptionsEncodingType,
11 FixedLengthOptionsNoEncodingType,
12 FormatNoTextType,
13 ResolvedCSHAKEOptionsNoEncodingType,
14 ResolvedKMACOptionsNoEncodingType,
15} from "./custom_types";
16import { getStrConverter } from "./converters";
17import { Int_64, rotl_64, xor_64_2, xor_64_5 } from "./primitives_64";
18
19type FixedLengthVariantType = "SHA3-224" | "SHA3-256" | "SHA3-384" | "SHA3-512" | "SHAKE128" | "SHAKE256";
20
21type VariantType = FixedLengthVariantType | "SHAKE128" | "SHAKE256" | "CSHAKE128" | "CSHAKE256" | "KMAC128" | "KMAC256";
22
23const rc_sha3 = [
24 new Int_64(0x00000000, 0x00000001),
25 new Int_64(0x00000000, 0x00008082),
26 new Int_64(0x80000000, 0x0000808a),
27 new Int_64(0x80000000, 0x80008000),
28 new Int_64(0x00000000, 0x0000808b),
29 new Int_64(0x00000000, 0x80000001),
30 new Int_64(0x80000000, 0x80008081),
31 new Int_64(0x80000000, 0x00008009),
32 new Int_64(0x00000000, 0x0000008a),
33 new Int_64(0x00000000, 0x00000088),
34 new Int_64(0x00000000, 0x80008009),
35 new Int_64(0x00000000, 0x8000000a),
36 new Int_64(0x00000000, 0x8000808b),
37 new Int_64(0x80000000, 0x0000008b),
38 new Int_64(0x80000000, 0x00008089),
39 new Int_64(0x80000000, 0x00008003),
40 new Int_64(0x80000000, 0x00008002),
41 new Int_64(0x80000000, 0x00000080),
42 new Int_64(0x00000000, 0x0000800a),
43 new Int_64(0x80000000, 0x8000000a),
44 new Int_64(0x80000000, 0x80008081),
45 new Int_64(0x80000000, 0x00008080),
46 new Int_64(0x00000000, 0x80000001),
47 new Int_64(0x80000000, 0x80008008),
48];
49
50const r_sha3 = [
51 [0, 36, 3, 41, 18],
52 [1, 44, 10, 45, 2],
53 [62, 6, 43, 15, 61],
54 [28, 55, 25, 21, 56],
55 [27, 20, 39, 8, 14],
56];
57
58/**
59 * Gets the state values for the specified SHA-3 variant.
60 *
61 * @param _variant Unused for this family.
62 * @returns The initial state values.
63 */
64function getNewState(_variant: VariantType): Int_64[][] {
65 let i;
66 const retVal = [];
67
68 for (i = 0; i < 5; i += 1) {
69 retVal[i] = [new Int_64(0, 0), new Int_64(0, 0), new Int_64(0, 0), new Int_64(0, 0), new Int_64(0, 0)];
70 }
71
72 return retVal;
73}
74
75/**
76 * Returns a clone of the given SHA3 state.
77 *
78 * @param state The state to be cloned.
79 * @returns The cloned state.
80 */
81function cloneSHA3State(state: Int_64[][]): Int_64[][] {
82 let i;
83 const clone = [];
84 for (i = 0; i < 5; i += 1) {
85 clone[i] = state[i].slice();
86 }
87
88 return clone;
89}
90
91/**
92 * Performs a round of SHA-3 hashing over a block. This clobbers `state`.
93 *
94 * @param block The binary array representation of the block to hash.
95 * @param state Hash state from a previous round.
96 * @returns The resulting state value.
97 */
98function roundSHA3(block: number[] | null, state: Int_64[][]): Int_64[][] {
99 let round, x, y, B;
100 const C = [],
101 D = [];
102
103 if (null !== block) {
104 for (x = 0; x < block.length; x += 2) {
105 state[(x >>> 1) % 5][((x >>> 1) / 5) | 0] = xor_64_2(
106 state[(x >>> 1) % 5][((x >>> 1) / 5) | 0],
107 new Int_64(block[x + 1], block[x])
108 );
109 }
110 }
111
112 for (round = 0; round < 24; round += 1) {
113 /* Any SHA-3 variant name will do here */
114 B = getNewState("SHA3-384");
115
116 /* Perform theta step */
117 for (x = 0; x < 5; x += 1) {
118 C[x] = xor_64_5(state[x][0], state[x][1], state[x][2], state[x][3], state[x][4]);
119 }
120 for (x = 0; x < 5; x += 1) {
121 D[x] = xor_64_2(C[(x + 4) % 5], rotl_64(C[(x + 1) % 5], 1));
122 }
123 for (x = 0; x < 5; x += 1) {
124 for (y = 0; y < 5; y += 1) {
125 state[x][y] = xor_64_2(state[x][y], D[x]);
126 }
127 }
128
129 /* Perform combined ro and pi steps */
130 for (x = 0; x < 5; x += 1) {
131 for (y = 0; y < 5; y += 1) {
132 B[y][(2 * x + 3 * y) % 5] = rotl_64(state[x][y], r_sha3[x][y]);
133 }
134 }
135
136 /* Perform chi step */
137 for (x = 0; x < 5; x += 1) {
138 for (y = 0; y < 5; y += 1) {
139 state[x][y] = xor_64_2(
140 B[x][y],
141 new Int_64(
142 ~B[(x + 1) % 5][y].highOrder & B[(x + 2) % 5][y].highOrder,
143 ~B[(x + 1) % 5][y].lowOrder & B[(x + 2) % 5][y].lowOrder
144 )
145 );
146 }
147 }
148
149 /* Perform iota step */
150 state[0][0] = xor_64_2(state[0][0], rc_sha3[round]);
151 }
152
153 return state;
154}
155
156/**
157 * Finalizes the SHA-3 hash. This clobbers `remainder` and `state`.
158 *
159 * @param remainder Any leftover unprocessed packed ints that still need to be processed.
160 * @param remainderBinLen The number of bits in `remainder`.
161 * @param _processedBinLen Unused for this family.
162 * @param state The state from a previous round.
163 * @param blockSize The block size/rate of the variant in bits
164 * @param delimiter The delimiter value for the variant
165 * @param outputLen The output length for the variant in bits
166 * @returns The array of integers representing the SHA-3 hash of message.
167 */
168function finalizeSHA3(
169 remainder: number[],
170 remainderBinLen: number,
171 _processedBinLen: number,
172 state: Int_64[][],
173 blockSize: number,
174 delimiter: number,
175 outputLen: number
176): number[] {
177 let i,
178 state_offset = 0,
179 temp;
180 const retVal = [],
181 binaryStringInc = blockSize >>> 5,
182 remainderIntLen = remainderBinLen >>> 5;
183
184 /* Process as many blocks as possible, some may be here for multiple rounds
185 with SHAKE
186 */
187 for (i = 0; i < remainderIntLen && remainderBinLen >= blockSize; i += binaryStringInc) {
188 state = roundSHA3(remainder.slice(i, i + binaryStringInc), state);
189 remainderBinLen -= blockSize;
190 }
191
192 remainder = remainder.slice(i);
193 remainderBinLen = remainderBinLen % blockSize;
194
195 /* Pad out the remainder to a full block */
196 while (remainder.length < binaryStringInc) {
197 remainder.push(0);
198 }
199
200 /* Find the next "empty" byte for the 0x80 and append it via an xor */
201 i = remainderBinLen >>> 3;
202 remainder[i >> 2] ^= delimiter << (8 * (i % 4));
203
204 remainder[binaryStringInc - 1] ^= 0x80000000;
205 state = roundSHA3(remainder, state);
206
207 while (retVal.length * 32 < outputLen) {
208 temp = state[state_offset % 5][(state_offset / 5) | 0];
209 retVal.push(temp.lowOrder);
210 if (retVal.length * 32 >= outputLen) {
211 break;
212 }
213 retVal.push(temp.highOrder);
214 state_offset += 1;
215
216 if (0 === (state_offset * 64) % blockSize) {
217 roundSHA3(null, state);
218 state_offset = 0;
219 }
220 }
221
222 return retVal;
223}
224
225/**
226 * Performs NIST left_encode function returned with no extra garbage bits. `x` is limited to <= 9007199254740991.
227 *
228 * @param x 32-bit number to to encode.
229 * @returns The NIST specified output of the function.
230 */
231function left_encode(x: number): packedValue {
232 let byteOffset,
233 byte,
234 numEncodedBytes = 0;
235 /* JavaScript numbers max out at 0x1FFFFFFFFFFFFF (7 bytes) so this will return a maximum of 7 + 1 = 8 bytes */
236 const retVal = [0, 0],
237 x_64 = [x & 0xffffffff, (x / TWO_PWR_32) & 0x1fffff];
238
239 for (byteOffset = 6; byteOffset >= 0; byteOffset--) {
240 /* This will surprisingly work for large shifts because JavaScript masks the shift amount by 0x1F */
241 byte = (x_64[byteOffset >> 2] >>> (8 * byteOffset)) & 0xff;
242
243 /* Starting from the most significant byte of a 64-bit number, start recording the first non-0 byte and then
244 every byte thereafter */
245 if (byte !== 0 || numEncodedBytes !== 0) {
246 retVal[(numEncodedBytes + 1) >> 2] |= byte << ((numEncodedBytes + 1) * 8);
247 numEncodedBytes += 1;
248 }
249 }
250 numEncodedBytes = numEncodedBytes !== 0 ? numEncodedBytes : 1;
251 retVal[0] |= numEncodedBytes;
252
253 return { value: numEncodedBytes + 1 > 4 ? retVal : [retVal[0]], binLen: 8 + numEncodedBytes * 8 };
254}
255
256/**
257 * Performs NIST right_encode function returned with no extra garbage bits. `x` is limited to <= 9007199254740991.
258 *
259 * @param x 32-bit number to to encode.
260 * @returns The NIST specified output of the function.
261 */
262function right_encode(x: number): packedValue {
263 let byteOffset,
264 byte,
265 numEncodedBytes = 0;
266 /* JavaScript numbers max out at 0x1FFFFFFFFFFFFF (7 bytes) so this will return a maximum of 7 + 1 = 8 bytes */
267 const retVal = [0, 0],
268 x_64 = [x & 0xffffffff, (x / TWO_PWR_32) & 0x1fffff];
269
270 for (byteOffset = 6; byteOffset >= 0; byteOffset--) {
271 /* This will surprisingly work for large shifts because JavaScript masks the shift amount by 0x1F */
272 byte = (x_64[byteOffset >> 2] >>> (8 * byteOffset)) & 0xff;
273
274 /* Starting from the most significant byte of a 64-bit number, start recording the first non-0 byte and then
275 every byte thereafter */
276 if (byte !== 0 || numEncodedBytes !== 0) {
277 retVal[numEncodedBytes >> 2] |= byte << (numEncodedBytes * 8);
278 numEncodedBytes += 1;
279 }
280 }
281 numEncodedBytes = numEncodedBytes !== 0 ? numEncodedBytes : 1;
282 retVal[numEncodedBytes >> 2] |= numEncodedBytes << (numEncodedBytes * 8);
283
284 return { value: numEncodedBytes + 1 > 4 ? retVal : [retVal[0]], binLen: 8 + numEncodedBytes * 8 };
285}
286
287/**
288 * Performs NIST encode_string function.
289 *
290 * @param input Packed array of integers.
291 * @returns NIST encode_string output.
292 */
293function encode_string(input: packedValue): packedValue {
294 return packedLEConcat(left_encode(input["binLen"]), input);
295}
296
297/**
298 * Performs NIST byte_pad function.
299 *
300 * @param packed Packed array of integers.
301 * @param outputByteLen Desired length of the output in bytes, assumed to be a multiple of 4.
302 * @returns NIST byte_pad output.
303 */
304function byte_pad(packed: packedValue, outputByteLen: number): number[] {
305 let encodedLen = left_encode(outputByteLen),
306 i;
307
308 encodedLen = packedLEConcat(encodedLen, packed);
309 const outputIntLen = outputByteLen >>> 2,
310 intsToAppend = (outputIntLen - (encodedLen["value"].length % outputIntLen)) % outputIntLen;
311
312 for (i = 0; i < intsToAppend; i++) {
313 encodedLen["value"].push(0);
314 }
315
316 return encodedLen["value"];
317}
318
319/**
320 * Parses/validate constructor options for a CSHAKE variant
321 *
322 * @param options Option given to constructor
323 */
324function resolveCSHAKEOptions(options: CSHAKEOptionsNoEncodingType): ResolvedCSHAKEOptionsNoEncodingType {
325 const resolvedOptions = options || {};
326
327 return {
328 funcName: parseInputOption("funcName", resolvedOptions["funcName"], 1, { value: [], binLen: 0 }),
329 customization: parseInputOption("Customization", resolvedOptions["customization"], 1, { value: [], binLen: 0 }),
330 };
331}
332
333/**
334 * Parses/validate constructor options for a KMAC variant
335 *
336 * @param options Option given to constructor
337 */
338function resolveKMACOptions(options: KMACOptionsNoEncodingType): ResolvedKMACOptionsNoEncodingType {
339 const resolvedOptions = options || {};
340
341 return {
342 kmacKey: parseInputOption("kmacKey", resolvedOptions["kmacKey"], 1),
343 /* This is little-endian packed "KMAC" */
344 funcName: { value: [0x43414d4b], binLen: 32 },
345 customization: parseInputOption("Customization", resolvedOptions["customization"], 1, { value: [], binLen: 0 }),
346 };
347}
348
349export default class jsSHA extends jsSHABase<Int_64[][], VariantType> {
350 intermediateState: Int_64[][];
351 variantBlockSize: number;
352 bigEndianMod: -1 | 1;
353 outputBinLen: number;
354 isVariableLen: boolean;
355 HMACSupported: boolean;
356
357 /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
358 converterFunc: (input: any, existingBin: number[], existingBinLen: number) => packedValue;
359 roundFunc: (block: number[], H: Int_64[][]) => Int_64[][];
360 finalizeFunc: (
361 remainder: number[],
362 remainderBinLen: number,
363 processedBinLen: number,
364 H: Int_64[][],
365 outputLen: number
366 ) => number[];
367 stateCloneFunc: (state: Int_64[][]) => Int_64[][];
368 newStateFunc: (variant: VariantType) => Int_64[][];
369 getMAC: ((options: { outputLen: number }) => number[]) | null;
370
371 constructor(variant: FixedLengthVariantType, inputFormat: "TEXT", options?: FixedLengthOptionsEncodingType);
372 constructor(
373 variant: FixedLengthVariantType,
374 inputFormat: FormatNoTextType,
375 options?: FixedLengthOptionsNoEncodingType
376 );
377 constructor(variant: "SHAKE128" | "SHAKE256", inputFormat: "TEXT", options?: SHAKEOptionsEncodingType);
378 constructor(variant: "SHAKE128" | "SHAKE256", inputFormat: FormatNoTextType, options?: SHAKEOptionsNoEncodingType);
379 constructor(variant: "CSHAKE128" | "CSHAKE256", inputFormat: "TEXT", options?: CSHAKEOptionsEncodingType);
380 constructor(variant: "CSHAKE128" | "CSHAKE256", inputFormat: FormatNoTextType, options?: CSHAKEOptionsNoEncodingType);
381 constructor(variant: "KMAC128" | "KMAC256", inputFormat: "TEXT", options: KMACOptionsEncodingType);
382 constructor(variant: "KMAC128" | "KMAC256", inputFormat: FormatNoTextType, options: KMACOptionsNoEncodingType);
383 // eslint-disable-next-line @typescript-eslint/no-explicit-any
384 constructor(variant: any, inputFormat: any, options?: any) {
385 let delimiter = 0x06,
386 variantBlockSize = 0;
387 super(variant, inputFormat, options);
388 const resolvedOptions = options || {};
389
390 /* In other variants, this was done after variable initialization but need to do it earlier here becaue we want to
391 avoid KMAC initialization */
392 if (this.numRounds !== 1) {
393 if (resolvedOptions["kmacKey"] || resolvedOptions["hmacKey"]) {
394 throw new Error(mac_rounds_error);
395 } else if (this.shaVariant === "CSHAKE128" || this.shaVariant === "CSHAKE256") {
396 throw new Error("Cannot set numRounds for CSHAKE variants");
397 }
398 }
399
400 this.bigEndianMod = 1;
401 this.converterFunc = getStrConverter(this.inputFormat, this.utfType, this.bigEndianMod);
402 this.roundFunc = roundSHA3;
403 this.stateCloneFunc = cloneSHA3State;
404 this.newStateFunc = getNewState;
405 this.intermediateState = getNewState(variant);
406
407 this.isVariableLen = false;
408 switch (variant) {
409 case "SHA3-224":
410 this.variantBlockSize = variantBlockSize = 1152;
411 this.outputBinLen = 224;
412 this.HMACSupported = true;
413 // eslint-disable-next-line @typescript-eslint/unbound-method
414 this.getMAC = this._getHMAC;
415 break;
416 case "SHA3-256":
417 this.variantBlockSize = variantBlockSize = 1088;
418 this.outputBinLen = 256;
419 this.HMACSupported = true;
420 // eslint-disable-next-line @typescript-eslint/unbound-method
421 this.getMAC = this._getHMAC;
422 break;
423 case "SHA3-384":
424 this.variantBlockSize = variantBlockSize = 832;
425 this.outputBinLen = 384;
426 this.HMACSupported = true;
427 // eslint-disable-next-line @typescript-eslint/unbound-method
428 this.getMAC = this._getHMAC;
429 break;
430 case "SHA3-512":
431 this.variantBlockSize = variantBlockSize = 576;
432 this.outputBinLen = 512;
433 this.HMACSupported = true;
434 // eslint-disable-next-line @typescript-eslint/unbound-method
435 this.getMAC = this._getHMAC;
436 break;
437 case "SHAKE128":
438 delimiter = 0x1f;
439 this.variantBlockSize = variantBlockSize = 1344;
440 /* This will be set in getHash */
441 this.outputBinLen = -1;
442 this.isVariableLen = true;
443 this.HMACSupported = false;
444 this.getMAC = null;
445 break;
446 case "SHAKE256":
447 delimiter = 0x1f;
448 this.variantBlockSize = variantBlockSize = 1088;
449 /* This will be set in getHash */
450 this.outputBinLen = -1;
451 this.isVariableLen = true;
452 this.HMACSupported = false;
453 this.getMAC = null;
454 break;
455 case "KMAC128":
456 delimiter = 0x4;
457 this.variantBlockSize = variantBlockSize = 1344;
458 this._initializeKMAC(options);
459 /* This will be set in getHash */
460 this.outputBinLen = -1;
461 this.isVariableLen = true;
462 this.HMACSupported = false;
463 // eslint-disable-next-line @typescript-eslint/unbound-method
464 this.getMAC = this._getKMAC;
465 break;
466 case "KMAC256":
467 delimiter = 0x4;
468 this.variantBlockSize = variantBlockSize = 1088;
469 this._initializeKMAC(options);
470 /* This will be set in getHash */
471 this.outputBinLen = -1;
472 this.isVariableLen = true;
473 this.HMACSupported = false;
474 // eslint-disable-next-line @typescript-eslint/unbound-method
475 this.getMAC = this._getKMAC;
476 break;
477 case "CSHAKE128":
478 this.variantBlockSize = variantBlockSize = 1344;
479 delimiter = this._initializeCSHAKE(options);
480 /* This will be set in getHash */
481 this.outputBinLen = -1;
482 this.isVariableLen = true;
483 this.HMACSupported = false;
484 this.getMAC = null;
485 break;
486 case "CSHAKE256":
487 this.variantBlockSize = variantBlockSize = 1088;
488 delimiter = this._initializeCSHAKE(options);
489 /* This will be set in getHash */
490 this.outputBinLen = -1;
491 this.isVariableLen = true;
492 this.HMACSupported = false;
493 this.getMAC = null;
494 break;
495 default:
496 throw new Error(sha_variant_error);
497 }
498
499 /* This needs to be down here as CSHAKE can change its delimiter */
500 this.finalizeFunc = function (remainder, remainderBinLen, processedBinLen, state, outputBinLen): number[] {
501 return finalizeSHA3(
502 remainder,
503 remainderBinLen,
504 processedBinLen,
505 state,
506 variantBlockSize,
507 delimiter,
508 outputBinLen
509 );
510 };
511
512 if (resolvedOptions["hmacKey"]) {
513 this._setHMACKey(parseInputOption("hmacKey", resolvedOptions["hmacKey"], this.bigEndianMod));
514 }
515 }
516
517 /**
518 * Initialize CSHAKE variants.
519 *
520 * @param options Options containing CSHAKE params.
521 * @param funcNameOverride Overrides any "funcName" present in `options` (used with KMAC)
522 * @returns The delimiter to be used
523 */
524 protected _initializeCSHAKE(options?: CSHAKEOptionsNoEncodingType, funcNameOverride?: packedValue): number {
525 const resolvedOptions = resolveCSHAKEOptions(options || {});
526 if (funcNameOverride) {
527 resolvedOptions["funcName"] = funcNameOverride;
528 }
529 const packedParams = packedLEConcat(
530 encode_string(resolvedOptions["funcName"]),
531 encode_string(resolvedOptions["customization"])
532 );
533
534 /* CSHAKE is defined to be a call to SHAKE iff both the customization and function-name string are both empty. This
535 can be accomplished by processing nothing in this step. */
536 if (resolvedOptions["customization"]["binLen"] !== 0 || resolvedOptions["funcName"]["binLen"] !== 0) {
537 const byte_pad_out = byte_pad(packedParams, this.variantBlockSize >>> 3);
538 for (let i = 0; i < byte_pad_out.length; i += this.variantBlockSize >>> 5) {
539 this.intermediateState = this.roundFunc(
540 byte_pad_out.slice(i, i + (this.variantBlockSize >>> 5)),
541 this.intermediateState
542 );
543 this.processedLen += this.variantBlockSize;
544 }
545 return 0x04;
546 } else {
547 return 0x1f;
548 }
549 }
550
551 /**
552 * Initialize KMAC variants.
553 *
554 * @param options Options containing KMAC params.
555 */
556 protected _initializeKMAC(options: KMACOptionsNoEncodingType): void {
557 const resolvedOptions = resolveKMACOptions(options || {});
558
559 this._initializeCSHAKE(options, resolvedOptions["funcName"]);
560 const byte_pad_out = byte_pad(encode_string(resolvedOptions["kmacKey"]), this.variantBlockSize >>> 3);
561 for (let i = 0; i < byte_pad_out.length; i += this.variantBlockSize >>> 5) {
562 this.intermediateState = this.roundFunc(
563 byte_pad_out.slice(i, i + (this.variantBlockSize >>> 5)),
564 this.intermediateState
565 );
566 this.processedLen += this.variantBlockSize;
567 }
568 this.macKeySet = true;
569 }
570
571 /**
572 * Returns the the KMAC in the specified format.
573 *
574 * @param options Hashmap of extra outputs options. `outputLen` must be specified.
575 * @returns The KMAC in the format specified.
576 */
577 // eslint-disable-next-line @typescript-eslint/no-explicit-any
578 protected _getKMAC(options: { outputLen: number }): number[] {
579 const concatedRemainder = packedLEConcat(
580 { value: this.remainder.slice(), binLen: this.remainderLen },
581 right_encode(options["outputLen"])
582 );
583
584 return this.finalizeFunc(
585 concatedRemainder["value"],
586 concatedRemainder["binLen"],
587 this.processedLen,
588 this.stateCloneFunc(this.intermediateState),
589 options["outputLen"]
590 );
591 }
592}
593
\No newline at end of file