Skip to content

Commit a7c54b4

Browse files
authored
common: refine utils/noble. (#659)
* common: separate hash related definitions from noble.ts to hash.ts. * common: add __PURE__ just in case. * common: define common BigInt constants. * common: remove prettier-ignore.
1 parent 0a63ba5 commit a7c54b4

File tree

17 files changed

+245
-186
lines changed

17 files changed

+245
-186
lines changed

packages/common/mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,6 @@ export { hmac } from "./src/hash/hmac.ts";
5353
export { sha256, sha384, sha512 } from "./src/hash/sha2.ts";
5454
export { sha3_256, sha3_512, shake128, shake256 } from "./src/hash/sha3.ts";
5555

56+
export type { CHash, CHashXOF } from "./src/hash/hash.ts";
5657
export { mod, pow2 } from "./src/curve/modular.ts";
5758
export { montgomery, type MontgomeryECDH } from "./src/curve/montgomery.ts";

packages/common/src/consts.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,13 @@ export const INFO_LENGTH_LIMIT = 65536;
77
export const MINIMUM_PSK_LENGTH = 32;
88

99
// b""
10-
export const EMPTY: Uint8Array = new Uint8Array(0);
10+
export const EMPTY: Uint8Array = /* @__PURE__ */ new Uint8Array(0);
11+
12+
// Common BigInt constants
13+
export const N_0 = /* @__PURE__ */ BigInt(0);
14+
export const N_1 = /* @__PURE__ */ BigInt(1);
15+
export const N_2 = /* @__PURE__ */ BigInt(2);
16+
export const N_7 = /* @__PURE__ */ BigInt(7);
17+
export const N_32 = /* @__PURE__ */ BigInt(32);
18+
export const N_256 = /* @__PURE__ */ BigInt(256);
19+
export const N_0x71 = /* @__PURE__ */ BigInt(0x71);

packages/common/src/curve/modular.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,20 @@
1515
*/
1616
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
1717

18+
import { N_0 } from "../consts.ts";
19+
1820
// Numbers aren't used in x25519 / x448 builds
19-
// prettier-ignore
20-
const _0n = /* @__PURE__ */ BigInt(0);
2121

2222
// Calculates a modulo b
2323
export function mod(a: bigint, b: bigint): bigint {
2424
const result = a % b;
25-
return result >= _0n ? result : b + result;
25+
return result >= N_0 ? result : b + result;
2626
}
2727

2828
/** Does `x^(2^power)` mod p. `pow2(30, 4)` == `30^(2^4)` */
2929
export function pow2(x: bigint, power: bigint, modulo: bigint): bigint {
3030
let res = x;
31-
while (power-- > _0n) {
31+
while (power-- > N_0) {
3232
res *= res;
3333
res %= modulo;
3434
}

packages/common/src/curve/montgomery.ts

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,7 @@ import {
2626
} from "../utils/noble.ts";
2727
import { createKeygen, type CurveLengths } from "./curve.ts";
2828
import { mod } from "./modular.ts";
29-
30-
const _0n = BigInt(0);
31-
const _1n = BigInt(1);
32-
const _2n = BigInt(2);
29+
import { N_0, N_1, N_2 } from "../consts.ts";
3330

3431
export type CurveType = {
3532
P: bigint; // finite field prime
@@ -83,11 +80,11 @@ export function montgomery(curveDef: CurveType): MontgomeryECDH {
8380
// RFC: x25519 "the resulting integer is of the form 2^254 plus
8481
// eight times a value between 0 and 2^251 - 1 (inclusive)"
8582
// x448: "2^447 plus four times a value between 0 and 2^445 - 1 (inclusive)"
86-
const minScalar = is25519 ? _2n ** BigInt(254) : _2n ** BigInt(447);
83+
const minScalar = is25519 ? N_2 ** BigInt(254) : N_2 ** BigInt(447);
8784
const maxAdded = is25519
88-
? BigInt(8) * _2n ** BigInt(251) - _1n
89-
: BigInt(4) * _2n ** BigInt(445) - _1n;
90-
const maxScalar = minScalar + maxAdded + _1n; // (inclusive)
85+
? BigInt(8) * N_2 ** BigInt(251) - N_1
86+
: BigInt(4) * N_2 ** BigInt(445) - N_1;
87+
const maxScalar = minScalar + maxAdded + N_1; // (inclusive)
9188
const modP = (n: bigint) => mod(n, P);
9289
const GuBytes = encodeU(Gu);
9390
function encodeU(u: bigint): Uint8Array {
@@ -114,7 +111,7 @@ export function montgomery(curveDef: CurveType): MontgomeryECDH {
114111
// Some public keys are useless, of low-order. Curve author doesn't think
115112
// it needs to be validated, but we do it nonetheless.
116113
// https://cr.yp.to/ecdh.html#validate
117-
if (pu === _0n) throw new Error("invalid private or public key received");
114+
if (pu === N_0) throw new Error("invalid private or public key received");
118115
return encodeU(pu);
119116
}
120117
// Computes public key from private. By doing scalar multiplication of base point.
@@ -146,17 +143,17 @@ export function montgomery(curveDef: CurveType): MontgomeryECDH {
146143
* @returns new Point on Montgomery curve
147144
*/
148145
function montgomeryLadder(u: bigint, scalar: bigint): bigint {
149-
aInRange("u", u, _0n, P);
146+
aInRange("u", u, N_0, P);
150147
aInRange("scalar", scalar, minScalar, maxScalar);
151148
const k = scalar;
152149
const x_1 = u;
153-
let x_2 = _1n;
154-
let z_2 = _0n;
150+
let x_2 = N_1;
151+
let z_2 = N_0;
155152
let x_3 = u;
156-
let z_3 = _1n;
157-
let swap = _0n;
158-
for (let t = BigInt(montgomeryBits - 1); t >= _0n; t--) {
159-
const k_t = (k >> t) & _1n;
153+
let z_3 = N_1;
154+
let swap = N_0;
155+
for (let t = BigInt(montgomeryBits - 1); t >= N_0; t--) {
156+
const k_t = (k >> t) & N_1;
160157
swap ^= k_t;
161158
({ x_2, x_3 } = cswap(swap, x_2, x_3));
162159
({ x_2: z_2, x_3: z_3 } = cswap(swap, z_2, z_3));
@@ -180,7 +177,7 @@ export function montgomery(curveDef: CurveType): MontgomeryECDH {
180177
}
181178
({ x_2, x_3 } = cswap(swap, x_2, x_3));
182179
({ x_2: z_2, x_3: z_3 } = cswap(swap, z_2, z_3));
183-
const z2 = powPminus2(z_2); // `Fp.pow(x, P - _2n)` is much slower equivalent
180+
const z2 = powPminus2(z_2); // `Fp.pow(x, P - N_2)` is much slower equivalent
184181
return modP(x_2 * z2); // Return x_2 * (z_2^(p - 2))
185182
}
186183
const lengths = {

packages/common/src/hash/hash.ts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/**
2+
* This file is based on noble-curves (https://github.com/paulmillr/noble-curves).
3+
*
4+
* noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com)
5+
*
6+
* The original file is located at:
7+
* https://github.com/paulmillr/noble-curves/blob/b9d49d2b41d550571a0c5be443ecb62109fa3373/src/utils.ts
8+
*/
9+
10+
/**
11+
* Hash utilities and type definitions extracted from noble.ts
12+
* @module
13+
*/
14+
15+
import { anumber } from "../utils/noble.ts";
16+
17+
export interface Hash<T> {
18+
blockLen: number; // Bytes per block
19+
outputLen: number; // Bytes in output
20+
update(buf: Uint8Array): this;
21+
digestInto(buf: Uint8Array): void;
22+
digest(): Uint8Array;
23+
destroy(): void;
24+
_cloneInto(to?: T): T;
25+
clone(): T;
26+
}
27+
28+
export interface HashInfo {
29+
/** DER encoded OID in bytes */
30+
oid?: Uint8Array;
31+
}
32+
33+
/**
34+
* Hash function interface with callable signature and properties
35+
* @template T - The Hash implementation type
36+
* @template Opts - Optional parameters type (undefined for simple hashes)
37+
*
38+
* Note: Default type parameter uses `any` due to TypeScript's limitations with
39+
* F-bounded polymorphism in self-referential type constraints.
40+
* This is a necessary compromise for the circular type dependency in Hash<T>.
41+
*/
42+
// deno-lint-ignore no-explicit-any
43+
export interface CHash<T extends Hash<T> = Hash<any>, Opts = undefined>
44+
extends HashInfo {
45+
/** Output length in bytes */
46+
readonly outputLen: number;
47+
/** Block length in bytes */
48+
readonly blockLen: number;
49+
50+
/**
51+
* Hash a message
52+
* @param msg - Message to hash
53+
* @param opts - Optional parameters (only for hashes that support options)
54+
*/
55+
(msg: Uint8Array, opts?: Opts): Uint8Array;
56+
57+
/**
58+
* Create a new hash instance
59+
* @param opts - Optional parameters (only for hashes that support options)
60+
*/
61+
create(opts?: Opts): T;
62+
}
63+
64+
/**
65+
* XOF: streaming API to read digest in chunks.
66+
* Same as 'squeeze' in keccak/k12 and 'seek' in blake3, but more generic name.
67+
* When hash used in XOF mode it is up to user to call '.destroy' afterwards, since we cannot
68+
* destroy state, next call can require more bytes.
69+
* @template T - The Hash implementation type
70+
*/
71+
export interface HashXOF<T extends Hash<T>> extends Hash<T> {
72+
/** Read 'bytes' bytes from digest stream */
73+
xof(bytes: number): Uint8Array;
74+
/** Read buf.length bytes from digest stream into buf */
75+
xofInto(buf: Uint8Array): Uint8Array;
76+
}
77+
78+
/**
79+
* Hash constructor function type
80+
* @template T - The Hash implementation type
81+
* @template Opts - Optional parameters type (undefined for simple hashes)
82+
*/
83+
export type HasherCons<T, Opts = undefined> = Opts extends undefined ? () => T
84+
: (opts?: Opts) => T;
85+
86+
/**
87+
* XOF (eXtendable Output Function) interface
88+
* Extended hash function that can produce output of arbitrary length
89+
*
90+
* Note: Default type parameter uses `any` due to TypeScript's limitations with
91+
* F-bounded polymorphism in self-referential type constraints.
92+
* This is a necessary compromise for the circular type dependency in HashXOF<T>.
93+
*/
94+
// deno-lint-ignore no-explicit-any
95+
export interface CHashXOF<T extends HashXOF<T> = HashXOF<any>, Opts = undefined>
96+
extends CHash<T, Opts> {
97+
}
98+
99+
/** Asserts something is hash */
100+
export function ahash(h: CHash): void {
101+
if (typeof h !== "function" || typeof h.create !== "function") {
102+
throw new Error("Hash must wrapped by utils.createHasher");
103+
}
104+
anumber(h.outputLen);
105+
anumber(h.blockLen);
106+
}
107+
108+
export function createHasher<T extends Hash<T>, Opts = undefined>(
109+
hashCons: HasherCons<T, Opts>,
110+
info: HashInfo = {},
111+
): CHash<T, Opts> {
112+
const hashFn = (msg: Uint8Array, opts?: Opts) =>
113+
hashCons(opts).update(msg).digest();
114+
115+
const tmp = hashCons(undefined);
116+
117+
const hashC = Object.assign(hashFn, {
118+
outputLen: tmp.outputLen,
119+
blockLen: tmp.blockLen,
120+
create: (opts?: Opts) => hashCons(opts),
121+
...info,
122+
}) as CHash<T, Opts>;
123+
124+
return Object.freeze(hashC);
125+
}

packages/common/src/hash/hmac.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,8 @@
1111
* HMAC: RFC2104 message authentication code.
1212
* @module
1313
*/
14-
import {
15-
abytes,
16-
aexists,
17-
ahash,
18-
type CHash,
19-
clean,
20-
type Hash,
21-
} from "../utils/noble.ts";
14+
import { abytes, aexists, clean } from "../utils/noble.ts";
15+
import { ahash, type CHash, type Hash } from "./hash.ts";
2216

2317
export class _HMAC<T extends Hash<T>> implements Hash<_HMAC<T>> {
2418
oHash: T;

packages/common/src/hash/md.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,8 @@
1111
* Internal Merkle-Damgard hash utils.
1212
* @module
1313
*/
14-
import {
15-
abytes,
16-
aexists,
17-
aoutput,
18-
clean,
19-
createView,
20-
type Hash,
21-
} from "../utils/noble.ts";
14+
import { abytes, aexists, aoutput, clean, createView } from "../utils/noble.ts";
15+
import type { Hash } from "./hash.ts";
2216

2317
/** Choice: a ? b : c */
2418
export function Chi(a: number, b: number, c: number): number {

packages/common/src/hash/sha2.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,13 @@
1515
*/
1616
import { Chi, HashMD, Maj, SHA256_IV, SHA384_IV, SHA512_IV } from "./md.ts";
1717
import * as u64 from "./u64.ts";
18-
import {
19-
type CHash,
20-
clean,
21-
createHasher,
22-
oidNist,
23-
rotr,
24-
} from "../utils/noble.ts";
18+
import { clean, oidNist, rotr } from "../utils/noble.ts";
19+
import { type CHash, createHasher } from "./hash.ts";
2520

2621
/**
2722
* Round constants:
2823
* First 32 bits of fractional parts of the cube roots of the first 64 primes 2..311)
2924
*/
30-
// prettier-ignore
3125
const SHA256_K = /* @__PURE__ */ Uint32Array.from([
3226
0x428a2f98,
3327
0x71374491,
@@ -127,7 +121,6 @@ abstract class SHA2_32B<T extends SHA2_32B<T>> extends HashMD<T> {
127121
const { A, B, C, D, E, F, G, H } = this;
128122
return [A, B, C, D, E, F, G, H];
129123
}
130-
// prettier-ignore
131124
protected set(
132125
A: number,
133126
B: number,
@@ -216,7 +209,6 @@ export class _SHA256 extends SHA2_32B<_SHA256> {
216209

217210
// Round contants
218211
// First 32 bits of the fractional parts of the cube roots of the first 80 primes 2..409
219-
// prettier-ignore
220212
const K512 = /* @__PURE__ */ (() =>
221213
u64.split([
222214
"0x428a2f98d728ae22",
@@ -332,7 +324,6 @@ abstract class SHA2_64B<T extends SHA2_64B<T>> extends HashMD<T> {
332324
constructor(outputLen: number) {
333325
super(128, outputLen, 16, false);
334326
}
335-
// prettier-ignore
336327
protected get(): [
337328
number,
338329
number,
@@ -355,7 +346,6 @@ abstract class SHA2_64B<T extends SHA2_64B<T>> extends HashMD<T> {
355346
this;
356347
return [Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl];
357348
}
358-
// prettier-ignore
359349
protected set(
360350
Ah: number,
361351
Al: number,
@@ -437,7 +427,6 @@ abstract class SHA2_64B<T extends SHA2_64B<T>> extends HashMD<T> {
437427
const CHIh = (Eh & Fh) ^ (~Eh & Gh);
438428
const CHIl = (El & Fl) ^ (~El & Gl);
439429
// T1 = H + sigma1 + Chi(E, F, G) + SHA512_K[i] + SHA512_W[i]
440-
// prettier-ignore
441430
const T1ll = u64.add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]);
442431
const T1h = u64.add5H(
443432
T1ll,

packages/common/src/hash/sha3.ts

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,45 +24,42 @@ import {
2424
aexists,
2525
anumber,
2626
aoutput,
27+
clean,
28+
oidNist,
29+
swap32IfBE,
30+
u32,
31+
} from "../utils/noble.ts";
32+
import {
2733
type CHash,
2834
type CHashXOF,
29-
clean,
3035
createHasher,
3136
type Hash,
3237
type HashInfo,
3338
type HashXOF,
34-
oidNist,
35-
swap32IfBE,
36-
u32,
37-
} from "../utils/noble.ts";
39+
} from "./hash.ts";
40+
import { N_0, N_0x71, N_1, N_2, N_256, N_7 } from "../consts.ts";
3841

3942
// No __PURE__ annotations in sha3 header:
4043
// EVERYTHING is in fact used on every export.
4144
// 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);
4845
const SHA3_PI: number[] = [];
4946
const SHA3_ROTL: number[] = [];
5047
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++) {
48+
for (let round = 0, R = N_1, x = 1, y = 0; round < 24; round++) {
5249
// Pi
5350
[x, y] = [y, (2 * x + 3 * y) % 5];
5451
SHA3_PI.push(2 * (5 * y + x));
5552
// Rotational
5653
SHA3_ROTL.push((((round + 1) * (round + 2)) / 2) % 64);
5754
// Iota
58-
let t = _0n;
55+
let t = N_0;
5956
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);
57+
R = ((R << N_1) ^ ((R >> N_7) * N_0x71)) % N_256;
58+
if (R & N_2) t ^= N_1 << ((N_1 << BigInt(j)) - N_1);
6259
}
6360
_SHA3_IOTA.push(t);
6461
}
65-
const IOTAS = split(_SHA3_IOTA, true);
62+
const IOTAS = /* @__PURE__ */ split(_SHA3_IOTA, true);
6663
const SHA3_IOTA_H = IOTAS[0];
6764
const SHA3_IOTA_L = IOTAS[1];
6865

0 commit comments

Comments
 (0)