Skip to content

Commit c6b4aad

Browse files
committed
utils: harmonize with noble-hashes
1 parent aade023 commit c6b4aad

File tree

1 file changed

+72
-31
lines changed

1 file changed

+72
-31
lines changed

src/abstract/utils.ts

Lines changed: 72 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
2+
// 100 lines of code in the file are duplicated from noble-hashes (utils).
3+
// This is OK: `abstract` directory does not use noble-hashes.
4+
// User may opt-in into using different hashing library. This way, noble-hashes
5+
// won't be included into their bundle.
26
const _0n = BigInt(0);
37
const _1n = BigInt(1);
48
const _2n = BigInt(2);
59
const u8a = (a: any): a is Uint8Array => a instanceof Uint8Array;
6-
7-
// We accept hex strings besides Uint8Array for simplicity
8-
export type Hex = Uint8Array | string;
9-
// Very few implementations accept numbers, we do it to ease learning curve
10-
export type PrivKey = Hex | bigint;
10+
export type Hex = Uint8Array | string; // hex strings are accepted for simplicity
11+
export type PrivKey = Hex | bigint; // bigints are accepted to ease learning curve
1112
export type CHash = {
1213
(message: Uint8Array | string): Uint8Array;
1314
blockLen: number;
@@ -17,6 +18,9 @@ export type CHash = {
1718
export type FHash = (message: Uint8Array | string) => Uint8Array;
1819

1920
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
21+
/**
22+
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
23+
*/
2024
export function bytesToHex(bytes: Uint8Array): string {
2125
if (!u8a(bytes)) throw new Error('Uint8Array expected');
2226
// pre-caching improves the speed 6x
@@ -38,22 +42,25 @@ export function hexToNumber(hex: string): bigint {
3842
return BigInt(hex === '' ? '0' : `0x${hex}`);
3943
}
4044

41-
// Caching slows it down 2-3x
45+
/**
46+
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
47+
*/
4248
export function hexToBytes(hex: string): Uint8Array {
4349
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
44-
if (hex.length % 2) throw new Error('hex string is invalid: unpadded ' + hex.length);
45-
const array = new Uint8Array(hex.length / 2);
50+
const len = hex.length;
51+
if (len % 2) throw new Error('padded hex string expected, got unpadded hex of length ' + len);
52+
const array = new Uint8Array(len / 2);
4653
for (let i = 0; i < array.length; i++) {
4754
const j = i * 2;
4855
const hexByte = hex.slice(j, j + 2);
4956
const byte = Number.parseInt(hexByte, 16);
50-
if (Number.isNaN(byte) || byte < 0) throw new Error('invalid byte sequence');
57+
if (Number.isNaN(byte) || byte < 0) throw new Error('Invalid byte sequence');
5158
array[i] = byte;
5259
}
5360
return array;
5461
}
5562

56-
// Big Endian
63+
// BE: Big Endian, LE: Little Endian
5764
export function bytesToNumberBE(bytes: Uint8Array): bigint {
5865
return hexToNumber(bytesToHex(bytes));
5966
}
@@ -62,12 +69,26 @@ export function bytesToNumberLE(bytes: Uint8Array): bigint {
6269
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
6370
}
6471

65-
export const numberToBytesBE = (n: bigint, len: number) =>
66-
hexToBytes(n.toString(16).padStart(len * 2, '0'));
67-
export const numberToBytesLE = (n: bigint, len: number) => numberToBytesBE(n, len).reverse();
68-
// Returns variable number bytes (minimal bigint encoding?)
69-
export const numberToVarBytesBE = (n: bigint) => hexToBytes(numberToHexUnpadded(n));
72+
export function numberToBytesBE(n: number | bigint, len: number): Uint8Array {
73+
return hexToBytes(n.toString(16).padStart(len * 2, '0'));
74+
}
75+
export function numberToBytesLE(n: number | bigint, len: number): Uint8Array {
76+
return numberToBytesBE(n, len).reverse();
77+
}
78+
// Unpadded, rarely used
79+
export function numberToVarBytesBE(n: number | bigint): Uint8Array {
80+
return hexToBytes(numberToHexUnpadded(n));
81+
}
7082

83+
/**
84+
* Takes hex string or Uint8Array, converts to Uint8Array.
85+
* Validates output length.
86+
* Will throw error for other types.
87+
* @param title descriptive title for an error e.g. 'private key'
88+
* @param hex hex string or Uint8Array
89+
* @param expectedLength optional, will compare to result array's length
90+
* @returns
91+
*/
7192
export function ensureBytes(title: string, hex: Hex, expectedLength?: number): Uint8Array {
7293
let res: Uint8Array;
7394
if (typeof hex === 'string') {
@@ -89,11 +110,13 @@ export function ensureBytes(title: string, hex: Hex, expectedLength?: number): U
89110
return res;
90111
}
91112

92-
// Copies several Uint8Arrays into one.
93-
export function concatBytes(...arrs: Uint8Array[]): Uint8Array {
94-
const r = new Uint8Array(arrs.reduce((sum, a) => sum + a.length, 0));
113+
/**
114+
* Copies several Uint8Arrays into one.
115+
*/
116+
export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
117+
const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
95118
let pad = 0; // walk through each item, ensure they have proper type
96-
arrs.forEach((a) => {
119+
arrays.forEach((a) => {
97120
if (!u8a(a)) throw new Error('Uint8Array expected');
98121
r.set(a, pad);
99122
pad += a.length;
@@ -111,29 +134,47 @@ export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
111134
// Global symbols in both browsers and Node.js since v11
112135
// See https://github.com/microsoft/TypeScript/issues/31535
113136
declare const TextEncoder: any;
137+
138+
/**
139+
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
140+
*/
114141
export function utf8ToBytes(str: string): Uint8Array {
115-
if (typeof str !== 'string') {
116-
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
117-
}
142+
if (typeof str !== 'string') throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
118143
return new TextEncoder().encode(str);
119144
}
120145

121146
// Bit operations
122147

123-
// Amount of bits inside bigint (Same as n.toString(2).length)
148+
/**
149+
* Calculates amount of bits in a bigint.
150+
* Same as `n.toString(2).length`
151+
*/
124152
export function bitLen(n: bigint) {
125153
let len;
126154
for (len = 0; n > _0n; n >>= _1n, len += 1);
127155
return len;
128156
}
129-
// Gets single bit at position. NOTE: first bit position is 0 (same as arrays)
130-
// Same as !!+Array.from(n.toString(2)).reverse()[pos]
131-
export const bitGet = (n: bigint, pos: number) => (n >> BigInt(pos)) & _1n;
132-
// Sets single bit at position
133-
export const bitSet = (n: bigint, pos: number, value: boolean) =>
134-
n | ((value ? _1n : _0n) << BigInt(pos));
135-
// Return mask for N bits (Same as BigInt(`0b${Array(i).fill('1').join('')}`))
136-
// Not using ** operator with bigints for old engines.
157+
158+
/**
159+
* Gets single bit at position.
160+
* NOTE: first bit position is 0 (same as arrays)
161+
* Same as `!!+Array.from(n.toString(2)).reverse()[pos]`
162+
*/
163+
export function bitGet(n: bigint, pos: number) {
164+
return (n >> BigInt(pos)) & _1n;
165+
}
166+
167+
/**
168+
* Sets single bit at position.
169+
*/
170+
export const bitSet = (n: bigint, pos: number, value: boolean) => {
171+
return n | ((value ? _1n : _0n) << BigInt(pos));
172+
};
173+
174+
/**
175+
* Calculate mask for N bits. Not using ** operator with bigints because of old engines.
176+
* Same as BigInt(`0b${Array(i).fill('1').join('')}`)
177+
*/
137178
export const bitMask = (n: number) => (_2n << BigInt(n - 1)) - _1n;
138179

139180
// DRBG

0 commit comments

Comments
 (0)