@noble/curves 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +295 -258
- package/abstract/bls.d.ts +27 -10
- package/abstract/bls.d.ts.map +1 -1
- package/abstract/bls.js +60 -10
- package/abstract/bls.js.map +1 -1
- package/abstract/curve.js.map +1 -1
- package/abstract/edwards.js.map +1 -1
- package/abstract/hash-to-curve.d.ts +2 -2
- package/abstract/hash-to-curve.d.ts.map +1 -1
- package/abstract/hash-to-curve.js +22 -16
- package/abstract/hash-to-curve.js.map +1 -1
- package/abstract/modular.d.ts +51 -11
- package/abstract/modular.d.ts.map +1 -1
- package/abstract/modular.js +79 -21
- package/abstract/modular.js.map +1 -1
- package/abstract/montgomery.d.ts.map +1 -1
- package/abstract/montgomery.js +5 -7
- package/abstract/montgomery.js.map +1 -1
- package/abstract/poseidon.d.ts.map +1 -1
- package/abstract/poseidon.js +39 -41
- package/abstract/poseidon.js.map +1 -1
- package/abstract/utils.d.ts +3 -1
- package/abstract/utils.d.ts.map +1 -1
- package/abstract/utils.js +56 -31
- package/abstract/utils.js.map +1 -1
- package/abstract/weierstrass.d.ts +25 -28
- package/abstract/weierstrass.d.ts.map +1 -1
- package/abstract/weierstrass.js +17 -15
- package/abstract/weierstrass.js.map +1 -1
- package/bls12-381.d.ts.map +1 -1
- package/bls12-381.js +142 -88
- package/bls12-381.js.map +1 -1
- package/bn254.d.ts +3 -2
- package/bn254.d.ts.map +1 -1
- package/bn254.js +3 -2
- package/bn254.js.map +1 -1
- package/ed25519.d.ts +5 -2
- package/ed25519.d.ts.map +1 -1
- package/ed25519.js +17 -8
- package/ed25519.js.map +1 -1
- package/ed448.d.ts +53 -2
- package/ed448.d.ts.map +1 -1
- package/ed448.js +216 -29
- package/ed448.js.map +1 -1
- package/esm/abstract/bls.js +61 -11
- package/esm/abstract/bls.js.map +1 -1
- package/esm/abstract/curve.js.map +1 -1
- package/esm/abstract/edwards.js.map +1 -1
- package/esm/abstract/hash-to-curve.js +23 -17
- package/esm/abstract/hash-to-curve.js.map +1 -1
- package/esm/abstract/modular.js +75 -20
- package/esm/abstract/modular.js.map +1 -1
- package/esm/abstract/montgomery.js +5 -7
- package/esm/abstract/montgomery.js.map +1 -1
- package/esm/abstract/poseidon.js +39 -41
- package/esm/abstract/poseidon.js.map +1 -1
- package/esm/abstract/utils.js +54 -30
- package/esm/abstract/utils.js.map +1 -1
- package/esm/abstract/weierstrass.js +17 -15
- package/esm/abstract/weierstrass.js.map +1 -1
- package/esm/bls12-381.js +143 -89
- package/esm/bls12-381.js.map +1 -1
- package/esm/bn254.js +3 -2
- package/esm/bn254.js.map +1 -1
- package/esm/ed25519.js +17 -8
- package/esm/ed25519.js.map +1 -1
- package/esm/ed448.js +218 -32
- package/esm/ed448.js.map +1 -1
- package/esm/jubjub.js +1 -1
- package/esm/jubjub.js.map +1 -1
- package/esm/p256.js +2 -2
- package/esm/p256.js.map +1 -1
- package/esm/p384.js +2 -2
- package/esm/p384.js.map +1 -1
- package/esm/p521.js +3 -3
- package/esm/p521.js.map +1 -1
- package/esm/package.json +1 -4
- package/esm/secp256k1.js +6 -6
- package/esm/secp256k1.js.map +1 -1
- package/jubjub.js.map +1 -1
- package/p256.js +2 -2
- package/p256.js.map +1 -1
- package/p384.js +2 -2
- package/p384.js.map +1 -1
- package/p521.js +3 -3
- package/p521.js.map +1 -1
- package/package.json +7 -6
- package/secp256k1.js +6 -6
- package/secp256k1.js.map +1 -1
- package/src/abstract/bls.ts +120 -22
- package/src/abstract/hash-to-curve.ts +24 -17
- package/src/abstract/modular.ts +81 -22
- package/src/abstract/montgomery.ts +4 -6
- package/src/abstract/poseidon.ts +39 -40
- package/src/abstract/utils.ts +55 -26
- package/src/abstract/weierstrass.ts +29 -18
- package/src/bls12-381.ts +132 -75
- package/src/bn254.ts +3 -2
- package/src/ed25519.ts +19 -8
- package/src/ed448.ts +267 -34
- package/src/jubjub.ts +1 -1
package/src/abstract/modular.ts
CHANGED
|
@@ -75,9 +75,14 @@ export function invert(number: bigint, modulo: bigint): bigint {
|
|
|
75
75
|
return mod(x, modulo);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Tonelli-Shanks square root search algorithm.
|
|
80
|
+
* 1. https://eprint.iacr.org/2012/685.pdf (page 12)
|
|
81
|
+
* 2. Square Roots from 1; 24, 51, 10 to Dan Shanks
|
|
82
|
+
* Will start an infinite loop if field order P is not prime.
|
|
83
|
+
* @param P field order
|
|
84
|
+
* @returns function that takes field Fp (created from P) and number n
|
|
85
|
+
*/
|
|
81
86
|
export function tonelliShanks(P: bigint) {
|
|
82
87
|
// Legendre constant: used to calculate Legendre symbol (a | p),
|
|
83
88
|
// which denotes the value of a^((p-1)/2) (mod p).
|
|
@@ -198,7 +203,7 @@ export function FpSqrt(P: bigint) {
|
|
|
198
203
|
// Little-endian check for first LE bit (last BE bit);
|
|
199
204
|
export const isNegativeLE = (num: bigint, modulo: bigint) => (mod(num, modulo) & _1n) === _1n;
|
|
200
205
|
|
|
201
|
-
// Field is not always over prime
|
|
206
|
+
// Field is not always over prime: for example, Fp2 has ORDER(q)=p^m
|
|
202
207
|
export interface IField<T> {
|
|
203
208
|
ORDER: bigint;
|
|
204
209
|
BYTES: number;
|
|
@@ -228,7 +233,8 @@ export interface IField<T> {
|
|
|
228
233
|
sqrN(num: T): T;
|
|
229
234
|
|
|
230
235
|
// Optional
|
|
231
|
-
// Should be same as sgn0 function in
|
|
236
|
+
// Should be same as sgn0 function in
|
|
237
|
+
// [RFC9380](https://www.rfc-editor.org/rfc/rfc9380#section-4.1).
|
|
232
238
|
// NOTE: sgn0 is 'negative in LE', which is same as odd. And negative in LE is kinda strange definition anyway.
|
|
233
239
|
isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2
|
|
234
240
|
// legendre?(num: T): T;
|
|
@@ -260,6 +266,11 @@ export function validateField<T>(field: IField<T>) {
|
|
|
260
266
|
}
|
|
261
267
|
|
|
262
268
|
// Generic field functions
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Same as `pow` but for Fp: non-constant-time.
|
|
272
|
+
* Unsafe in some contexts: uses ladder, so can expose bigint bits.
|
|
273
|
+
*/
|
|
263
274
|
export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
|
|
264
275
|
// Should have same speed as pow for bigints
|
|
265
276
|
// TODO: benchmark!
|
|
@@ -276,7 +287,10 @@ export function FpPow<T>(f: IField<T>, num: T, power: bigint): T {
|
|
|
276
287
|
return p;
|
|
277
288
|
}
|
|
278
289
|
|
|
279
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Efficiently invert an array of Field elements.
|
|
292
|
+
* `inv(0)` will return `undefined` here: make sure to throw an error.
|
|
293
|
+
*/
|
|
280
294
|
export function FpInvertBatch<T>(f: IField<T>, nums: T[]): T[] {
|
|
281
295
|
const tmp = new Array(nums.length);
|
|
282
296
|
// Walk from first to last, multiply them by each other MOD p
|
|
@@ -319,12 +333,12 @@ export function nLength(n: bigint, nBitLength?: number) {
|
|
|
319
333
|
|
|
320
334
|
type FpField = IField<bigint> & Required<Pick<IField<bigint>, 'isOdd'>>;
|
|
321
335
|
/**
|
|
322
|
-
* Initializes a
|
|
323
|
-
* Do not init in loop: slow. Very fragile: always run a benchmark on change.
|
|
324
|
-
* Major performance
|
|
325
|
-
* a)
|
|
326
|
-
* b)
|
|
327
|
-
* c)
|
|
336
|
+
* Initializes a finite field over prime. **Non-primes are not supported.**
|
|
337
|
+
* Do not init in loop: slow. Very fragile: always run a benchmark on a change.
|
|
338
|
+
* Major performance optimizations:
|
|
339
|
+
* * a) denormalized operations like mulN instead of mul
|
|
340
|
+
* * b) same object shape: never add or remove keys
|
|
341
|
+
* * c) Object.freeze
|
|
328
342
|
* @param ORDER prime positive bigint
|
|
329
343
|
* @param bitLen how many bits the field consumes
|
|
330
344
|
* @param isLE (def: false) if encoding / decoding should be in little-endian
|
|
@@ -336,7 +350,7 @@ export function Field(
|
|
|
336
350
|
isLE = false,
|
|
337
351
|
redef: Partial<IField<bigint>> = {}
|
|
338
352
|
): Readonly<FpField> {
|
|
339
|
-
if (ORDER <= _0n) throw new Error(`Expected
|
|
353
|
+
if (ORDER <= _0n) throw new Error(`Expected Field ORDER > 0, got ${ORDER}`);
|
|
340
354
|
const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen);
|
|
341
355
|
if (BYTES > 2048) throw new Error('Field lengths over 2048 bytes are not supported');
|
|
342
356
|
const sqrtP = FpSqrt(ORDER);
|
|
@@ -400,15 +414,10 @@ export function FpSqrtEven<T>(Fp: IField<T>, elm: T) {
|
|
|
400
414
|
}
|
|
401
415
|
|
|
402
416
|
/**
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
|
408
|
-
* @param hash hash output from SHA3 or a similar function
|
|
409
|
-
* @param groupOrder size of subgroup - (e.g. curveFn.CURVE.n)
|
|
410
|
-
* @param isLE interpret hash bytes as LE num
|
|
411
|
-
* @returns valid private scalar
|
|
417
|
+
* "Constant-time" private key generation utility.
|
|
418
|
+
* Same as mapKeyToField, but accepts less bytes (40 instead of 48 for 32-byte field).
|
|
419
|
+
* Which makes it slightly more biased, less secure.
|
|
420
|
+
* @deprecated use mapKeyToField instead
|
|
412
421
|
*/
|
|
413
422
|
export function hashToPrivateScalar(
|
|
414
423
|
hash: string | Uint8Array,
|
|
@@ -423,3 +432,53 @@ export function hashToPrivateScalar(
|
|
|
423
432
|
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
|
|
424
433
|
return mod(num, groupOrder - _1n) + _1n;
|
|
425
434
|
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Returns total number of bytes consumed by the field element.
|
|
438
|
+
* For example, 32 bytes for usual 256-bit weierstrass curve.
|
|
439
|
+
* @param fieldOrder number of field elements, usually CURVE.n
|
|
440
|
+
* @returns byte length of field
|
|
441
|
+
*/
|
|
442
|
+
export function getFieldBytesLength(fieldOrder: bigint): number {
|
|
443
|
+
if (typeof fieldOrder !== 'bigint') throw new Error('field order must be bigint');
|
|
444
|
+
const bitLength = fieldOrder.toString(2).length;
|
|
445
|
+
return Math.ceil(bitLength / 8);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Returns minimal amount of bytes that can be safely reduced
|
|
450
|
+
* by field order.
|
|
451
|
+
* Should be 2^-128 for 128-bit curve such as P256.
|
|
452
|
+
* @param fieldOrder number of field elements, usually CURVE.n
|
|
453
|
+
* @returns byte length of target hash
|
|
454
|
+
*/
|
|
455
|
+
export function getMinHashLength(fieldOrder: bigint): number {
|
|
456
|
+
const length = getFieldBytesLength(fieldOrder);
|
|
457
|
+
return length + Math.ceil(length / 2);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* "Constant-time" private key generation utility.
|
|
462
|
+
* Can take (n + n/2) or more bytes of uniform input e.g. from CSPRNG or KDF
|
|
463
|
+
* and convert them into private scalar, with the modulo bias being negligible.
|
|
464
|
+
* Needs at least 48 bytes of input for 32-byte private key.
|
|
465
|
+
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
|
466
|
+
* FIPS 186-5, A.2 https://csrc.nist.gov/publications/detail/fips/186/5/final
|
|
467
|
+
* RFC 9380, https://www.rfc-editor.org/rfc/rfc9380#section-5
|
|
468
|
+
* @param hash hash output from SHA3 or a similar function
|
|
469
|
+
* @param groupOrder size of subgroup - (e.g. secp256k1.CURVE.n)
|
|
470
|
+
* @param isLE interpret hash bytes as LE num
|
|
471
|
+
* @returns valid private scalar
|
|
472
|
+
*/
|
|
473
|
+
export function mapHashToField(key: Uint8Array, fieldOrder: bigint, isLE = false): Uint8Array {
|
|
474
|
+
const len = key.length;
|
|
475
|
+
const fieldLen = getFieldBytesLength(fieldOrder);
|
|
476
|
+
const minLen = getMinHashLength(fieldOrder);
|
|
477
|
+
// No small numbers: need to understand bias story. No huge numbers: easier to detect JS timings.
|
|
478
|
+
if (len < 16 || len < minLen || len > 1024)
|
|
479
|
+
throw new Error(`expected ${minLen}-1024 bytes of input, got ${len}`);
|
|
480
|
+
const num = isLE ? bytesToNumberBE(key) : bytesToNumberLE(key);
|
|
481
|
+
// `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0
|
|
482
|
+
const reduced = mod(num, fieldOrder - _1n) + _1n;
|
|
483
|
+
return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
|
|
484
|
+
}
|
|
@@ -150,17 +150,15 @@ export function montgomery(curveDef: CurveType): CurveFn {
|
|
|
150
150
|
function decodeUCoordinate(uEnc: Hex): bigint {
|
|
151
151
|
// Section 5: When receiving such an array, implementations of X25519
|
|
152
152
|
// MUST mask the most significant bit in the final byte.
|
|
153
|
-
// This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP
|
|
154
|
-
// fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519
|
|
155
153
|
const u = ensureBytes('u coordinate', uEnc, montgomeryBytes);
|
|
156
|
-
|
|
157
|
-
if (fieldLen === montgomeryBytes) u[fieldLen - 1] &= 127; // 0b0111_1111
|
|
154
|
+
if (fieldLen === 32) u[31] &= 127; // 0b0111_1111
|
|
158
155
|
return bytesToNumberLE(u);
|
|
159
156
|
}
|
|
160
157
|
function decodeScalar(n: Hex): bigint {
|
|
161
158
|
const bytes = ensureBytes('scalar', n);
|
|
162
|
-
|
|
163
|
-
|
|
159
|
+
const len = bytes.length;
|
|
160
|
+
if (len !== montgomeryBytes && len !== fieldLen)
|
|
161
|
+
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${len}`);
|
|
164
162
|
return bytesToNumberLE(adjustScalarBytes(bytes));
|
|
165
163
|
}
|
|
166
164
|
function scalarMult(scalar: Hex, u: Hex): Uint8Array {
|
package/src/abstract/poseidon.ts
CHANGED
|
@@ -15,34 +15,36 @@ export type PoseidonOpts = {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export function validateOpts(opts: PoseidonOpts) {
|
|
18
|
-
const { Fp } = opts;
|
|
18
|
+
const { Fp, mds, reversePartialPowIdx: rev, roundConstants: rc } = opts;
|
|
19
|
+
const { roundsFull, roundsPartial, sboxPower, t } = opts;
|
|
20
|
+
|
|
19
21
|
validateField(Fp);
|
|
20
22
|
for (const i of ['t', 'roundsFull', 'roundsPartial'] as const) {
|
|
21
23
|
if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
|
|
22
24
|
throw new Error(`Poseidon: invalid param ${i}=${opts[i]} (${typeof opts[i]})`);
|
|
23
25
|
}
|
|
24
|
-
if (opts.reversePartialPowIdx !== undefined && typeof opts.reversePartialPowIdx !== 'boolean')
|
|
25
|
-
throw new Error(`Poseidon: invalid param reversePartialPowIdx=${opts.reversePartialPowIdx}`);
|
|
26
|
-
// Default is 5, but by some reasons stark uses 3
|
|
27
|
-
let sboxPower = opts.sboxPower;
|
|
28
|
-
if (sboxPower === undefined) sboxPower = 5;
|
|
29
|
-
if (typeof sboxPower !== 'number' || !Number.isSafeInteger(sboxPower))
|
|
30
|
-
throw new Error(`Poseidon wrong sboxPower=${sboxPower}`);
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
// MDS is TxT matrix
|
|
28
|
+
if (!Array.isArray(mds) || mds.length !== t) throw new Error('Poseidon: wrong MDS matrix');
|
|
29
|
+
const _mds = mds.map((mdsRow) => {
|
|
30
|
+
if (!Array.isArray(mdsRow) || mdsRow.length !== t)
|
|
31
|
+
throw new Error(`Poseidon MDS matrix row: ${mdsRow}`);
|
|
32
|
+
return mdsRow.map((i) => {
|
|
33
|
+
if (typeof i !== 'bigint') throw new Error(`Poseidon MDS matrix value=${i}`);
|
|
34
|
+
return Fp.create(i);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (rev !== undefined && typeof rev !== 'boolean')
|
|
39
|
+
throw new Error(`Poseidon: invalid param reversePartialPowIdx=${rev}`);
|
|
37
40
|
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
const rounds = opts.roundsFull + opts.roundsPartial;
|
|
41
|
+
if (roundsFull % 2 !== 0) throw new Error(`Poseidon roundsFull is not even: ${roundsFull}`);
|
|
42
|
+
const rounds = roundsFull + roundsPartial;
|
|
41
43
|
|
|
42
|
-
if (!Array.isArray(
|
|
44
|
+
if (!Array.isArray(rc) || rc.length !== rounds)
|
|
43
45
|
throw new Error('Poseidon: wrong round constants');
|
|
44
|
-
const roundConstants =
|
|
45
|
-
if (!Array.isArray(rc) || rc.length !==
|
|
46
|
+
const roundConstants = rc.map((rc) => {
|
|
47
|
+
if (!Array.isArray(rc) || rc.length !== t)
|
|
46
48
|
throw new Error(`Poseidon wrong round constants: ${rc}`);
|
|
47
49
|
return rc.map((i) => {
|
|
48
50
|
if (typeof i !== 'bigint' || !Fp.isValid(i))
|
|
@@ -50,18 +52,16 @@ export function validateOpts(opts: PoseidonOpts) {
|
|
|
50
52
|
return Fp.create(i);
|
|
51
53
|
});
|
|
52
54
|
});
|
|
53
|
-
|
|
54
|
-
if (!
|
|
55
|
-
throw new Error(
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
});
|
|
64
|
-
return Object.freeze({ ...opts, rounds, sboxFn, roundConstants, mds });
|
|
55
|
+
|
|
56
|
+
if (!sboxPower || ![3, 5, 7].includes(sboxPower))
|
|
57
|
+
throw new Error(`Poseidon wrong sboxPower=${sboxPower}`);
|
|
58
|
+
const _sboxPower = BigInt(sboxPower);
|
|
59
|
+
let sboxFn = (n: bigint) => FpPow(Fp, n, _sboxPower);
|
|
60
|
+
// Unwrapped sbox power for common cases (195->142μs)
|
|
61
|
+
if (sboxPower === 3) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(n), n);
|
|
62
|
+
else if (sboxPower === 5) sboxFn = (n: bigint) => Fp.mul(Fp.sqrN(Fp.sqrN(n)), n);
|
|
63
|
+
|
|
64
|
+
return Object.freeze({ ...opts, rounds, sboxFn, roundConstants, mds: _mds });
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
export function splitConstants(rc: bigint[], t: number) {
|
|
@@ -80,18 +80,17 @@ export function splitConstants(rc: bigint[], t: number) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
export function poseidon(opts: PoseidonOpts) {
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
83
|
+
const _opts = validateOpts(opts);
|
|
84
|
+
const { Fp, mds, roundConstants, rounds, roundsPartial, sboxFn, t } = _opts;
|
|
85
|
+
const halfRoundsFull = _opts.roundsFull / 2;
|
|
86
|
+
const partialIdx = _opts.reversePartialPowIdx ? t - 1 : 0;
|
|
86
87
|
const poseidonRound = (values: bigint[], isFull: boolean, idx: number) => {
|
|
87
|
-
values = values.map((i, j) => Fp.add(i,
|
|
88
|
+
values = values.map((i, j) => Fp.add(i, roundConstants[idx][j]));
|
|
88
89
|
|
|
89
90
|
if (isFull) values = values.map((i) => sboxFn(i));
|
|
90
91
|
else values[partialIdx] = sboxFn(values[partialIdx]);
|
|
91
92
|
// Matrix multiplication
|
|
92
|
-
values =
|
|
93
|
-
i.reduce((acc, i, j) => Fp.add(acc, Fp.mulN(i, values[j])), Fp.ZERO)
|
|
94
|
-
);
|
|
93
|
+
values = mds.map((i) => i.reduce((acc, i, j) => Fp.add(acc, Fp.mulN(i, values[j])), Fp.ZERO));
|
|
95
94
|
return values;
|
|
96
95
|
};
|
|
97
96
|
const poseidonHash = function poseidonHash(values: bigint[]) {
|
|
@@ -105,7 +104,7 @@ export function poseidon(opts: PoseidonOpts) {
|
|
|
105
104
|
// Apply r_f/2 full rounds.
|
|
106
105
|
for (let i = 0; i < halfRoundsFull; i++) values = poseidonRound(values, true, round++);
|
|
107
106
|
// Apply r_p partial rounds.
|
|
108
|
-
for (let i = 0; i <
|
|
107
|
+
for (let i = 0; i < roundsPartial; i++) values = poseidonRound(values, false, round++);
|
|
109
108
|
// Apply r_f/2 full rounds.
|
|
110
109
|
for (let i = 0; i < halfRoundsFull; i++) values = poseidonRound(values, true, round++);
|
|
111
110
|
|
|
@@ -114,6 +113,6 @@ export function poseidon(opts: PoseidonOpts) {
|
|
|
114
113
|
return values;
|
|
115
114
|
};
|
|
116
115
|
// For verification in tests
|
|
117
|
-
poseidonHash.roundConstants =
|
|
116
|
+
poseidonHash.roundConstants = roundConstants;
|
|
118
117
|
return poseidonHash;
|
|
119
118
|
}
|
package/src/abstract/utils.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
const _0n = BigInt(0);
|
|
7
7
|
const _1n = BigInt(1);
|
|
8
8
|
const _2n = BigInt(2);
|
|
9
|
-
const u8a = (a: any): a is Uint8Array => a instanceof Uint8Array;
|
|
10
9
|
export type Hex = Uint8Array | string; // hex strings are accepted for simplicity
|
|
11
10
|
export type PrivKey = Hex | bigint; // bigints are accepted to ease learning curve
|
|
12
11
|
export type CHash = {
|
|
@@ -17,12 +16,22 @@ export type CHash = {
|
|
|
17
16
|
};
|
|
18
17
|
export type FHash = (message: Uint8Array | string) => Uint8Array;
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
export function isBytes(a: unknown): a is Uint8Array {
|
|
20
|
+
return (
|
|
21
|
+
a instanceof Uint8Array ||
|
|
22
|
+
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array')
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Array where index 0xf0 (240) is mapped to string 'f0'
|
|
27
|
+
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) =>
|
|
28
|
+
i.toString(16).padStart(2, '0')
|
|
29
|
+
);
|
|
21
30
|
/**
|
|
22
31
|
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
|
|
23
32
|
*/
|
|
24
33
|
export function bytesToHex(bytes: Uint8Array): string {
|
|
25
|
-
if (!
|
|
34
|
+
if (!isBytes(bytes)) throw new Error('Uint8Array expected');
|
|
26
35
|
// pre-caching improves the speed 6x
|
|
27
36
|
let hex = '';
|
|
28
37
|
for (let i = 0; i < bytes.length; i++) {
|
|
@@ -42,20 +51,32 @@ export function hexToNumber(hex: string): bigint {
|
|
|
42
51
|
return BigInt(hex === '' ? '0' : `0x${hex}`);
|
|
43
52
|
}
|
|
44
53
|
|
|
54
|
+
// We use optimized technique to convert hex string to byte array
|
|
55
|
+
const asciis = { _0: 48, _9: 57, _A: 65, _F: 70, _a: 97, _f: 102 } as const;
|
|
56
|
+
function asciiToBase16(char: number): number | undefined {
|
|
57
|
+
if (char >= asciis._0 && char <= asciis._9) return char - asciis._0;
|
|
58
|
+
if (char >= asciis._A && char <= asciis._F) return char - (asciis._A - 10);
|
|
59
|
+
if (char >= asciis._a && char <= asciis._f) return char - (asciis._a - 10);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
45
63
|
/**
|
|
46
64
|
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
|
|
47
65
|
*/
|
|
48
66
|
export function hexToBytes(hex: string): Uint8Array {
|
|
49
67
|
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
if (
|
|
58
|
-
|
|
68
|
+
const hl = hex.length;
|
|
69
|
+
const al = hl / 2;
|
|
70
|
+
if (hl % 2) throw new Error('padded hex string expected, got unpadded hex of length ' + hl);
|
|
71
|
+
const array = new Uint8Array(al);
|
|
72
|
+
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
73
|
+
const n1 = asciiToBase16(hex.charCodeAt(hi));
|
|
74
|
+
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
|
|
75
|
+
if (n1 === undefined || n2 === undefined) {
|
|
76
|
+
const char = hex[hi] + hex[hi + 1];
|
|
77
|
+
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
|
78
|
+
}
|
|
79
|
+
array[ai] = n1 * 16 + n2;
|
|
59
80
|
}
|
|
60
81
|
return array;
|
|
61
82
|
}
|
|
@@ -65,7 +86,7 @@ export function bytesToNumberBE(bytes: Uint8Array): bigint {
|
|
|
65
86
|
return hexToNumber(bytesToHex(bytes));
|
|
66
87
|
}
|
|
67
88
|
export function bytesToNumberLE(bytes: Uint8Array): bigint {
|
|
68
|
-
if (!
|
|
89
|
+
if (!isBytes(bytes)) throw new Error('Uint8Array expected');
|
|
69
90
|
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
|
|
70
91
|
}
|
|
71
92
|
|
|
@@ -97,7 +118,7 @@ export function ensureBytes(title: string, hex: Hex, expectedLength?: number): U
|
|
|
97
118
|
} catch (e) {
|
|
98
119
|
throw new Error(`${title} must be valid hex string, got "${hex}". Cause: ${e}`);
|
|
99
120
|
}
|
|
100
|
-
} else if (
|
|
121
|
+
} else if (isBytes(hex)) {
|
|
101
122
|
// Uint8Array.from() instead of hash.slice() because node.js Buffer
|
|
102
123
|
// is instance of Uint8Array, and its slice() creates **mutable** copy
|
|
103
124
|
res = Uint8Array.from(hex);
|
|
@@ -114,21 +135,28 @@ export function ensureBytes(title: string, hex: Hex, expectedLength?: number): U
|
|
|
114
135
|
* Copies several Uint8Arrays into one.
|
|
115
136
|
*/
|
|
116
137
|
export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
|
|
117
|
-
|
|
118
|
-
let
|
|
119
|
-
|
|
120
|
-
if (!
|
|
121
|
-
|
|
138
|
+
let sum = 0;
|
|
139
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
140
|
+
const a = arrays[i];
|
|
141
|
+
if (!isBytes(a)) throw new Error('Uint8Array expected');
|
|
142
|
+
sum += a.length;
|
|
143
|
+
}
|
|
144
|
+
let res = new Uint8Array(sum);
|
|
145
|
+
let pad = 0;
|
|
146
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
147
|
+
const a = arrays[i];
|
|
148
|
+
res.set(a, pad);
|
|
122
149
|
pad += a.length;
|
|
123
|
-
}
|
|
124
|
-
return
|
|
150
|
+
}
|
|
151
|
+
return res;
|
|
125
152
|
}
|
|
126
153
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
154
|
+
// Compares 2 u8a-s in kinda constant time
|
|
155
|
+
export function equalBytes(a: Uint8Array, b: Uint8Array) {
|
|
156
|
+
if (a.length !== b.length) return false;
|
|
157
|
+
let diff = 0;
|
|
158
|
+
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
159
|
+
return diff === 0;
|
|
132
160
|
}
|
|
133
161
|
|
|
134
162
|
// Global symbols in both browsers and Node.js since v11
|
|
@@ -246,6 +274,7 @@ const validatorFns = {
|
|
|
246
274
|
function: (val: any) => typeof val === 'function',
|
|
247
275
|
boolean: (val: any) => typeof val === 'boolean',
|
|
248
276
|
string: (val: any) => typeof val === 'string',
|
|
277
|
+
stringOrUint8Array: (val: any) => typeof val === 'string' || isBytes(val),
|
|
249
278
|
isSafeInteger: (val: any) => Number.isSafeInteger(val),
|
|
250
279
|
array: (val: any) => Array.isArray(val),
|
|
251
280
|
field: (val: any, object: any) => (object as any).Fp.isValid(val),
|
|
@@ -123,6 +123,7 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
export type CurvePointsRes<T> = {
|
|
126
|
+
CURVE: ReturnType<typeof validatePointOpts<T>>;
|
|
126
127
|
ProjectivePoint: ProjConstructor<T>;
|
|
127
128
|
normPrivateKeyToScalar: (key: PrivKey) => bigint;
|
|
128
129
|
weierstrassEquation: (x: T) => T;
|
|
@@ -157,7 +158,7 @@ export const DER = {
|
|
|
157
158
|
// parse DER signature
|
|
158
159
|
const { Err: E } = DER;
|
|
159
160
|
const data = typeof hex === 'string' ? h2b(hex) : hex;
|
|
160
|
-
if (!(data
|
|
161
|
+
if (!ut.isBytes(data)) throw new Error('ui8a expected');
|
|
161
162
|
let l = data.length;
|
|
162
163
|
if (l < 2 || data[0] != 0x30) throw new E('Invalid signature tag');
|
|
163
164
|
if (data[1] !== l - 2) throw new E('Invalid signature: incorrect length');
|
|
@@ -187,13 +188,13 @@ export const DER = {
|
|
|
187
188
|
// prettier-ignore
|
|
188
189
|
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4);
|
|
189
190
|
|
|
190
|
-
export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|
191
|
+
export function weierstrassPoints<T>(opts: CurvePointsType<T>): CurvePointsRes<T> {
|
|
191
192
|
const CURVE = validatePointOpts(opts);
|
|
192
193
|
const { Fp } = CURVE; // All curves has same field / group length as for now, but they can differ
|
|
193
194
|
|
|
194
195
|
const toBytes =
|
|
195
196
|
CURVE.toBytes ||
|
|
196
|
-
((
|
|
197
|
+
((_c: ProjConstructor<T>, point: ProjPointType<T>, _isCompressed: boolean) => {
|
|
197
198
|
const a = point.toAffine();
|
|
198
199
|
return ut.concatBytes(Uint8Array.from([0x04]), Fp.toBytes(a.x), Fp.toBytes(a.y));
|
|
199
200
|
});
|
|
@@ -237,7 +238,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|
|
237
238
|
function normPrivateKeyToScalar(key: PrivKey): bigint {
|
|
238
239
|
const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n } = CURVE;
|
|
239
240
|
if (lengths && typeof key !== 'bigint') {
|
|
240
|
-
if (key
|
|
241
|
+
if (ut.isBytes(key)) key = ut.bytesToHex(key);
|
|
241
242
|
// Normalize to hex string, pad. E.g. P521 would norm 130-132 char hex to 132-char bytes
|
|
242
243
|
if (typeof key !== 'string' || !lengths.includes(key.length)) throw new Error('Invalid key');
|
|
243
244
|
key = key.padStart(nByteLength * 2, '0');
|
|
@@ -269,7 +270,11 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|
|
269
270
|
static readonly BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE);
|
|
270
271
|
static readonly ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO);
|
|
271
272
|
|
|
272
|
-
constructor(
|
|
273
|
+
constructor(
|
|
274
|
+
readonly px: T,
|
|
275
|
+
readonly py: T,
|
|
276
|
+
readonly pz: T
|
|
277
|
+
) {
|
|
273
278
|
if (px == null || !Fp.isValid(px)) throw new Error('x required');
|
|
274
279
|
if (py == null || !Fp.isValid(py)) throw new Error('y required');
|
|
275
280
|
if (pz == null || !Fp.isValid(pz)) throw new Error('z required');
|
|
@@ -333,9 +338,11 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|
|
333
338
|
|
|
334
339
|
// A point on curve is valid if it conforms to equation.
|
|
335
340
|
assertValidity(): void {
|
|
336
|
-
// Zero is valid point too!
|
|
337
341
|
if (this.is0()) {
|
|
338
|
-
|
|
342
|
+
// (0, 1, 0) aka ZERO is invalid in most contexts.
|
|
343
|
+
// In BLS, ZERO can be serialized, so we allow it.
|
|
344
|
+
// (0, 0, 0) is wrong representation of ZERO and is always invalid.
|
|
345
|
+
if (CURVE.allowInfinityPoint && !Fp.is0(this.py)) return;
|
|
339
346
|
throw new Error('bad point: ZERO');
|
|
340
347
|
}
|
|
341
348
|
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
|
|
@@ -707,7 +714,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
707
714
|
isWithinCurveOrder,
|
|
708
715
|
} = weierstrassPoints({
|
|
709
716
|
...CURVE,
|
|
710
|
-
toBytes(
|
|
717
|
+
toBytes(_c, point, isCompressed: boolean): Uint8Array {
|
|
711
718
|
const a = point.toAffine();
|
|
712
719
|
const x = Fp.toBytes(a.x);
|
|
713
720
|
const cat = ut.concatBytes;
|
|
@@ -761,7 +768,11 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
761
768
|
* ECDSA signature with its (r, s) properties. Supports DER & compact representations.
|
|
762
769
|
*/
|
|
763
770
|
class Signature implements SignatureType {
|
|
764
|
-
constructor(
|
|
771
|
+
constructor(
|
|
772
|
+
readonly r: bigint,
|
|
773
|
+
readonly s: bigint,
|
|
774
|
+
readonly recovery?: number
|
|
775
|
+
) {
|
|
765
776
|
this.assertValidity();
|
|
766
777
|
}
|
|
767
778
|
|
|
@@ -845,13 +856,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
845
856
|
normPrivateKeyToScalar: normPrivateKeyToScalar,
|
|
846
857
|
|
|
847
858
|
/**
|
|
848
|
-
* Produces cryptographically secure private key from random of size
|
|
849
|
-
*
|
|
859
|
+
* Produces cryptographically secure private key from random of size
|
|
860
|
+
* (groupLen + ceil(groupLen / 2)) with modulo bias being negligible.
|
|
850
861
|
*/
|
|
851
862
|
randomPrivateKey: (): Uint8Array => {
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
return ut.numberToBytesBE(num, CURVE.nByteLength);
|
|
863
|
+
const length = mod.getMinHashLength(CURVE.n);
|
|
864
|
+
return mod.mapHashToField(CURVE.randomBytes(length), CURVE.n);
|
|
855
865
|
},
|
|
856
866
|
|
|
857
867
|
/**
|
|
@@ -883,7 +893,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
883
893
|
* Quick and dirty check for item being public key. Does not validate hex, or being on-curve.
|
|
884
894
|
*/
|
|
885
895
|
function isProbPub(item: PrivKey | PubKey): boolean {
|
|
886
|
-
const arr = item
|
|
896
|
+
const arr = ut.isBytes(item);
|
|
887
897
|
const str = typeof item === 'string';
|
|
888
898
|
const len = (arr || str) && (item as Hex).length;
|
|
889
899
|
if (arr) return len === compressedLen || len === uncompressedLen;
|
|
@@ -964,7 +974,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
964
974
|
if (ent != null) {
|
|
965
975
|
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
|
|
966
976
|
const e = ent === true ? randomBytes(Fp.BYTES) : ent; // generate random bytes OR pass as-is
|
|
967
|
-
seedArgs.push(ensureBytes('extraEntropy', e
|
|
977
|
+
seedArgs.push(ensureBytes('extraEntropy', e)); // check for being bytes
|
|
968
978
|
}
|
|
969
979
|
const seed = ut.concatBytes(...seedArgs); // Step D of RFC6979 3.2
|
|
970
980
|
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!
|
|
@@ -1047,7 +1057,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
1047
1057
|
let _sig: Signature | undefined = undefined;
|
|
1048
1058
|
let P: ProjPointType<bigint>;
|
|
1049
1059
|
try {
|
|
1050
|
-
if (typeof sg === 'string' || sg
|
|
1060
|
+
if (typeof sg === 'string' || ut.isBytes(sg)) {
|
|
1051
1061
|
// Signature can be represented in 2 ways: compact (2*nByteLength) & DER (variable-length).
|
|
1052
1062
|
// Since DER can also be 2*nByteLength bytes, we check for it first.
|
|
1053
1063
|
try {
|
|
@@ -1170,7 +1180,8 @@ export function SWUFpSqrtRatio<T>(Fp: mod.IField<T>, Z: T) {
|
|
|
1170
1180
|
return sqrtRatio;
|
|
1171
1181
|
}
|
|
1172
1182
|
/**
|
|
1173
|
-
*
|
|
1183
|
+
* Simplified Shallue-van de Woestijne-Ulas Method
|
|
1184
|
+
* https://www.rfc-editor.org/rfc/rfc9380#section-6.6.2
|
|
1174
1185
|
*/
|
|
1175
1186
|
export function mapToCurveSimpleSWU<T>(
|
|
1176
1187
|
Fp: mod.IField<T>,
|