@noble/curves 0.5.2 → 0.6.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 +49 -5
- package/lib/_shortw_utils.d.ts +10 -21
- package/lib/abstract/bls.d.ts +39 -32
- package/lib/abstract/bls.js +74 -73
- package/lib/abstract/{group.d.ts → curve.d.ts} +31 -1
- package/lib/abstract/{group.js → curve.js} +39 -2
- package/lib/abstract/edwards.d.ts +30 -72
- package/lib/abstract/edwards.js +197 -375
- package/lib/abstract/hash-to-curve.d.ts +25 -6
- package/lib/abstract/hash-to-curve.js +40 -12
- package/lib/abstract/modular.d.ts +20 -7
- package/lib/abstract/modular.js +61 -35
- package/lib/abstract/montgomery.js +4 -5
- package/lib/abstract/poseidon.d.ts +29 -0
- package/lib/abstract/poseidon.js +115 -0
- package/lib/abstract/utils.d.ts +5 -36
- package/lib/abstract/utils.js +23 -71
- package/lib/abstract/weierstrass.d.ts +51 -74
- package/lib/abstract/weierstrass.js +455 -628
- package/lib/bls12-381.js +63 -58
- package/lib/bn.js +1 -1
- package/lib/ed25519.d.ts +7 -5
- package/lib/ed25519.js +82 -79
- package/lib/ed448.d.ts +3 -0
- package/lib/ed448.js +86 -83
- package/lib/esm/abstract/bls.js +75 -74
- package/lib/esm/abstract/{group.js → curve.js} +37 -1
- package/lib/esm/abstract/edwards.js +196 -374
- package/lib/esm/abstract/hash-to-curve.js +38 -11
- package/lib/esm/abstract/modular.js +58 -34
- package/lib/esm/abstract/montgomery.js +5 -6
- package/lib/esm/abstract/poseidon.js +109 -0
- package/lib/esm/abstract/utils.js +21 -66
- package/lib/esm/abstract/weierstrass.js +454 -627
- package/lib/esm/bls12-381.js +75 -70
- package/lib/esm/bn.js +1 -1
- package/lib/esm/ed25519.js +80 -78
- package/lib/esm/ed448.js +84 -82
- package/lib/esm/jubjub.js +1 -1
- package/lib/esm/p256.js +11 -9
- package/lib/esm/p384.js +11 -9
- package/lib/esm/p521.js +13 -12
- package/lib/esm/secp256k1.js +115 -151
- package/lib/esm/stark.js +104 -40
- package/lib/jubjub.d.ts +2 -2
- package/lib/jubjub.js +1 -1
- package/lib/p192.d.ts +20 -42
- package/lib/p224.d.ts +20 -42
- package/lib/p256.d.ts +23 -42
- package/lib/p256.js +13 -10
- package/lib/p384.d.ts +23 -42
- package/lib/p384.js +13 -10
- package/lib/p521.d.ts +23 -42
- package/lib/p521.js +15 -13
- package/lib/secp256k1.d.ts +25 -37
- package/lib/secp256k1.js +115 -151
- package/lib/stark.d.ts +36 -19
- package/lib/stark.js +107 -40
- package/package.json +13 -8
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import { concatBytes } from './utils.js';
|
|
3
|
-
|
|
4
|
-
export function validateHTFOpts(opts) {
|
|
1
|
+
import { mod } from './modular.js';
|
|
2
|
+
import { concatBytes, ensureBytes } from './utils.js';
|
|
3
|
+
export function validateOpts(opts) {
|
|
5
4
|
if (typeof opts.DST !== 'string')
|
|
6
5
|
throw new Error('Invalid htf/DST');
|
|
7
6
|
if (typeof opts.p !== 'bigint')
|
|
@@ -15,13 +14,11 @@ export function validateHTFOpts(opts) {
|
|
|
15
14
|
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
|
|
16
15
|
throw new Error('Invalid htf/hash function');
|
|
17
16
|
}
|
|
18
|
-
// UTF8 to ui8a
|
|
19
|
-
// TODO: looks broken, ASCII only, why not TextEncoder/TextDecoder? it is in hashes anyway
|
|
20
17
|
export function stringToBytes(str) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return
|
|
18
|
+
if (typeof str !== 'string') {
|
|
19
|
+
throw new TypeError(`utf8ToBytes expected string, got ${typeof str}`);
|
|
20
|
+
}
|
|
21
|
+
return new TextEncoder().encode(str);
|
|
25
22
|
}
|
|
26
23
|
// Octet Stream to Integer (bytesToNumberBE)
|
|
27
24
|
function os2ip(bytes) {
|
|
@@ -120,7 +117,7 @@ export function hash_to_field(msg, count, options) {
|
|
|
120
117
|
for (let j = 0; j < options.m; j++) {
|
|
121
118
|
const elm_offset = L * (j + i * options.m);
|
|
122
119
|
const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L);
|
|
123
|
-
e[j] = mod
|
|
120
|
+
e[j] = mod(os2ip(tv), options.p);
|
|
124
121
|
}
|
|
125
122
|
u[i] = e;
|
|
126
123
|
}
|
|
@@ -136,3 +133,33 @@ export function isogenyMap(field, map) {
|
|
|
136
133
|
return { x, y };
|
|
137
134
|
};
|
|
138
135
|
}
|
|
136
|
+
export function hashToCurve(Point, mapToCurve, def) {
|
|
137
|
+
validateOpts(def);
|
|
138
|
+
if (typeof mapToCurve !== 'function')
|
|
139
|
+
throw new Error('hashToCurve: mapToCurve() has not been defined');
|
|
140
|
+
return {
|
|
141
|
+
// Encodes byte string to elliptic curve
|
|
142
|
+
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
|
|
143
|
+
hashToCurve(msg, options) {
|
|
144
|
+
if (!mapToCurve)
|
|
145
|
+
throw new Error('CURVE.mapToCurve() has not been defined');
|
|
146
|
+
msg = ensureBytes(msg);
|
|
147
|
+
const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options });
|
|
148
|
+
const P = Point.fromAffine(mapToCurve(u[0]))
|
|
149
|
+
.add(Point.fromAffine(mapToCurve(u[1])))
|
|
150
|
+
.clearCofactor();
|
|
151
|
+
P.assertValidity();
|
|
152
|
+
return P;
|
|
153
|
+
},
|
|
154
|
+
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3
|
|
155
|
+
encodeToCurve(msg, options) {
|
|
156
|
+
if (!mapToCurve)
|
|
157
|
+
throw new Error('CURVE.mapToCurve() has not been defined');
|
|
158
|
+
msg = ensureBytes(msg);
|
|
159
|
+
const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options });
|
|
160
|
+
const P = Point.fromAffine(mapToCurve(u[0])).clearCofactor();
|
|
161
|
+
P.assertValidity();
|
|
162
|
+
return P;
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
2
|
-
// TODO: remove circular imports
|
|
3
|
-
import * as utils from './utils.js';
|
|
4
2
|
// Utilities for modular arithmetics and finite fields
|
|
3
|
+
import { bitMask, numberToBytesBE, numberToBytesLE, bytesToNumberBE, bytesToNumberLE, ensureBytes, } from './utils.js';
|
|
5
4
|
// prettier-ignore
|
|
6
5
|
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3);
|
|
7
6
|
// prettier-ignore
|
|
@@ -91,7 +90,7 @@ export function tonelliShanks(P) {
|
|
|
91
90
|
const p1div4 = (P + _1n) / _4n;
|
|
92
91
|
return function tonelliFast(Fp, n) {
|
|
93
92
|
const root = Fp.pow(n, p1div4);
|
|
94
|
-
if (!Fp.
|
|
93
|
+
if (!Fp.eql(Fp.sqr(root), n))
|
|
95
94
|
throw new Error('Cannot find square root');
|
|
96
95
|
return root;
|
|
97
96
|
};
|
|
@@ -100,26 +99,26 @@ export function tonelliShanks(P) {
|
|
|
100
99
|
const Q1div2 = (Q + _1n) / _2n;
|
|
101
100
|
return function tonelliSlow(Fp, n) {
|
|
102
101
|
// Step 0: Check that n is indeed a square: (n | p) should not be ≡ -1
|
|
103
|
-
if (Fp.pow(n, legendreC) === Fp.
|
|
102
|
+
if (Fp.pow(n, legendreC) === Fp.neg(Fp.ONE))
|
|
104
103
|
throw new Error('Cannot find square root');
|
|
105
104
|
let r = S;
|
|
106
105
|
// TODO: will fail at Fp2/etc
|
|
107
106
|
let g = Fp.pow(Fp.mul(Fp.ONE, Z), Q); // will update both x and b
|
|
108
107
|
let x = Fp.pow(n, Q1div2); // first guess at the square root
|
|
109
108
|
let b = Fp.pow(n, Q); // first guess at the fudge factor
|
|
110
|
-
while (!Fp.
|
|
111
|
-
if (Fp.
|
|
109
|
+
while (!Fp.eql(b, Fp.ONE)) {
|
|
110
|
+
if (Fp.eql(b, Fp.ZERO))
|
|
112
111
|
return Fp.ZERO; // https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm (4. If t = 0, return r = 0)
|
|
113
112
|
// Find m such b^(2^m)==1
|
|
114
113
|
let m = 1;
|
|
115
|
-
for (let t2 = Fp.
|
|
116
|
-
if (Fp.
|
|
114
|
+
for (let t2 = Fp.sqr(b); m < r; m++) {
|
|
115
|
+
if (Fp.eql(t2, Fp.ONE))
|
|
117
116
|
break;
|
|
118
|
-
t2 = Fp.
|
|
117
|
+
t2 = Fp.sqr(t2); // t2 *= t2
|
|
119
118
|
}
|
|
120
119
|
// NOTE: r-m-1 can be bigger than 32, need to convert to bigint before shift, otherwise there will be overflow
|
|
121
120
|
const ge = Fp.pow(g, _1n << BigInt(r - m - 1)); // ge = 2^(r-m-1)
|
|
122
|
-
g = Fp.
|
|
121
|
+
g = Fp.sqr(ge); // g = ge * ge
|
|
123
122
|
x = Fp.mul(x, ge); // x *= ge
|
|
124
123
|
b = Fp.mul(b, g); // b *= g
|
|
125
124
|
r = m;
|
|
@@ -141,7 +140,7 @@ export function FpSqrt(P) {
|
|
|
141
140
|
return function sqrt3mod4(Fp, n) {
|
|
142
141
|
const root = Fp.pow(n, p1div4);
|
|
143
142
|
// Throw if root**2 != n
|
|
144
|
-
if (!Fp.
|
|
143
|
+
if (!Fp.eql(Fp.sqr(root), n))
|
|
145
144
|
throw new Error('Cannot find square root');
|
|
146
145
|
return root;
|
|
147
146
|
};
|
|
@@ -155,7 +154,7 @@ export function FpSqrt(P) {
|
|
|
155
154
|
const nv = Fp.mul(n, v);
|
|
156
155
|
const i = Fp.mul(Fp.mul(nv, _2n), v);
|
|
157
156
|
const root = Fp.mul(nv, Fp.sub(i, Fp.ONE));
|
|
158
|
-
if (!Fp.
|
|
157
|
+
if (!Fp.eql(Fp.sqr(root), n))
|
|
159
158
|
throw new Error('Cannot find square root');
|
|
160
159
|
return root;
|
|
161
160
|
};
|
|
@@ -189,9 +188,9 @@ export function FpSqrt(P) {
|
|
|
189
188
|
export const isNegativeLE = (num, modulo) => (mod(num, modulo) & _1n) === _1n;
|
|
190
189
|
// prettier-ignore
|
|
191
190
|
const FIELD_FIELDS = [
|
|
192
|
-
'create', 'isValid', '
|
|
193
|
-
'
|
|
194
|
-
'addN', 'subN', 'mulN', '
|
|
191
|
+
'create', 'isValid', 'is0', 'neg', 'inv', 'sqrt', 'sqr',
|
|
192
|
+
'eql', 'add', 'sub', 'mul', 'pow', 'div',
|
|
193
|
+
'addN', 'subN', 'mulN', 'sqrN'
|
|
195
194
|
];
|
|
196
195
|
export function validateField(field) {
|
|
197
196
|
for (const i of ['ORDER', 'MASK']) {
|
|
@@ -222,7 +221,7 @@ export function FpPow(f, num, power) {
|
|
|
222
221
|
while (power > _0n) {
|
|
223
222
|
if (power & _1n)
|
|
224
223
|
p = f.mul(p, d);
|
|
225
|
-
d = f.
|
|
224
|
+
d = f.sqr(d);
|
|
226
225
|
power >>= 1n;
|
|
227
226
|
}
|
|
228
227
|
return p;
|
|
@@ -231,16 +230,16 @@ export function FpInvertBatch(f, nums) {
|
|
|
231
230
|
const tmp = new Array(nums.length);
|
|
232
231
|
// Walk from first to last, multiply them by each other MOD p
|
|
233
232
|
const lastMultiplied = nums.reduce((acc, num, i) => {
|
|
234
|
-
if (f.
|
|
233
|
+
if (f.is0(num))
|
|
235
234
|
return acc;
|
|
236
235
|
tmp[i] = acc;
|
|
237
236
|
return f.mul(acc, num);
|
|
238
237
|
}, f.ONE);
|
|
239
238
|
// Invert last element
|
|
240
|
-
const inverted = f.
|
|
239
|
+
const inverted = f.inv(lastMultiplied);
|
|
241
240
|
// Walk from last to first, multiply them by inverted each other MOD p
|
|
242
241
|
nums.reduceRight((acc, num, i) => {
|
|
243
|
-
if (f.
|
|
242
|
+
if (f.is0(num))
|
|
244
243
|
return acc;
|
|
245
244
|
tmp[i] = f.mul(acc, tmp[i]);
|
|
246
245
|
return f.mul(acc, num);
|
|
@@ -248,20 +247,27 @@ export function FpInvertBatch(f, nums) {
|
|
|
248
247
|
return tmp;
|
|
249
248
|
}
|
|
250
249
|
export function FpDiv(f, lhs, rhs) {
|
|
251
|
-
return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.
|
|
250
|
+
return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.inv(rhs));
|
|
252
251
|
}
|
|
253
252
|
// This function returns True whenever the value x is a square in the field F.
|
|
254
253
|
export function FpIsSquare(f) {
|
|
255
254
|
const legendreConst = (f.ORDER - _1n) / _2n; // Integer arithmetic
|
|
256
255
|
return (x) => {
|
|
257
256
|
const p = f.pow(x, legendreConst);
|
|
258
|
-
return f.
|
|
257
|
+
return f.eql(p, f.ZERO) || f.eql(p, f.ONE);
|
|
259
258
|
};
|
|
260
259
|
}
|
|
260
|
+
// CURVE.n lengths
|
|
261
|
+
export function nLength(n, nBitLength) {
|
|
262
|
+
// Bit size, byte size of CURVE.n
|
|
263
|
+
const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length;
|
|
264
|
+
const nByteLength = Math.ceil(_nBitLength / 8);
|
|
265
|
+
return { nBitLength: _nBitLength, nByteLength };
|
|
266
|
+
}
|
|
261
267
|
export function Fp(ORDER, bitLen, isLE = false, redef = {}) {
|
|
262
268
|
if (ORDER <= _0n)
|
|
263
269
|
throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`);
|
|
264
|
-
const { nBitLength: BITS, nByteLength: BYTES } =
|
|
270
|
+
const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen);
|
|
265
271
|
if (BYTES > 2048)
|
|
266
272
|
throw new Error('Field lengths over 2048 bytes are not supported');
|
|
267
273
|
const sqrtP = FpSqrt(ORDER);
|
|
@@ -269,41 +275,41 @@ export function Fp(ORDER, bitLen, isLE = false, redef = {}) {
|
|
|
269
275
|
ORDER,
|
|
270
276
|
BITS,
|
|
271
277
|
BYTES,
|
|
272
|
-
MASK:
|
|
278
|
+
MASK: bitMask(BITS),
|
|
273
279
|
ZERO: _0n,
|
|
274
280
|
ONE: _1n,
|
|
275
281
|
create: (num) => mod(num, ORDER),
|
|
276
282
|
isValid: (num) => {
|
|
277
283
|
if (typeof num !== 'bigint')
|
|
278
284
|
throw new Error(`Invalid field element: expected bigint, got ${typeof num}`);
|
|
279
|
-
return _0n <= num && num < ORDER;
|
|
285
|
+
return _0n <= num && num < ORDER; // 0 is valid element, but it's not invertible
|
|
280
286
|
},
|
|
281
|
-
|
|
287
|
+
is0: (num) => num === _0n,
|
|
282
288
|
isOdd: (num) => (num & _1n) === _1n,
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
289
|
+
neg: (num) => mod(-num, ORDER),
|
|
290
|
+
eql: (lhs, rhs) => lhs === rhs,
|
|
291
|
+
sqr: (num) => mod(num * num, ORDER),
|
|
286
292
|
add: (lhs, rhs) => mod(lhs + rhs, ORDER),
|
|
287
293
|
sub: (lhs, rhs) => mod(lhs - rhs, ORDER),
|
|
288
294
|
mul: (lhs, rhs) => mod(lhs * rhs, ORDER),
|
|
289
295
|
pow: (num, power) => FpPow(f, num, power),
|
|
290
296
|
div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER),
|
|
291
297
|
// Same as above, but doesn't normalize
|
|
292
|
-
|
|
298
|
+
sqrN: (num) => num * num,
|
|
293
299
|
addN: (lhs, rhs) => lhs + rhs,
|
|
294
300
|
subN: (lhs, rhs) => lhs - rhs,
|
|
295
301
|
mulN: (lhs, rhs) => lhs * rhs,
|
|
296
|
-
|
|
302
|
+
inv: (num) => invert(num, ORDER),
|
|
297
303
|
sqrt: redef.sqrt || ((n) => sqrtP(f, n)),
|
|
298
304
|
invertBatch: (lst) => FpInvertBatch(f, lst),
|
|
299
305
|
// TODO: do we really need constant cmov?
|
|
300
306
|
// We don't have const-time bigints anyway, so probably will be not very useful
|
|
301
307
|
cmov: (a, b, c) => (c ? b : a),
|
|
302
|
-
toBytes: (num) => isLE ?
|
|
308
|
+
toBytes: (num) => (isLE ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES)),
|
|
303
309
|
fromBytes: (bytes) => {
|
|
304
310
|
if (bytes.length !== BYTES)
|
|
305
311
|
throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes.length}`);
|
|
306
|
-
return isLE ?
|
|
312
|
+
return isLE ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes);
|
|
307
313
|
},
|
|
308
314
|
});
|
|
309
315
|
return Object.freeze(f);
|
|
@@ -312,11 +318,29 @@ export function FpSqrtOdd(Fp, elm) {
|
|
|
312
318
|
if (!Fp.isOdd)
|
|
313
319
|
throw new Error(`Field doesn't have isOdd`);
|
|
314
320
|
const root = Fp.sqrt(elm);
|
|
315
|
-
return Fp.isOdd(root) ? root : Fp.
|
|
321
|
+
return Fp.isOdd(root) ? root : Fp.neg(root);
|
|
316
322
|
}
|
|
317
323
|
export function FpSqrtEven(Fp, elm) {
|
|
318
324
|
if (!Fp.isOdd)
|
|
319
325
|
throw new Error(`Field doesn't have isOdd`);
|
|
320
326
|
const root = Fp.sqrt(elm);
|
|
321
|
-
return Fp.isOdd(root) ? Fp.
|
|
327
|
+
return Fp.isOdd(root) ? Fp.neg(root) : root;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility.
|
|
331
|
+
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
|
|
332
|
+
* and convert them into private scalar, with the modulo bias being neglible.
|
|
333
|
+
* Needs at least 40 bytes of input for 32-byte private key.
|
|
334
|
+
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
|
335
|
+
* @param hash hash output from SHA3 or a similar function
|
|
336
|
+
* @returns valid private scalar
|
|
337
|
+
*/
|
|
338
|
+
export function hashToPrivateScalar(hash, groupOrder, isLE = false) {
|
|
339
|
+
hash = ensureBytes(hash);
|
|
340
|
+
const hashLen = hash.length;
|
|
341
|
+
const minLen = nLength(groupOrder).nByteLength + 8;
|
|
342
|
+
if (minLen < 24 || hashLen < minLen || hashLen > 1024)
|
|
343
|
+
throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`);
|
|
344
|
+
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
|
|
345
|
+
return mod(num, groupOrder - _1n) + _1n;
|
|
322
346
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
2
|
-
import
|
|
3
|
-
import { ensureBytes, numberToBytesLE, bytesToNumberLE
|
|
2
|
+
import { mod, pow } from './modular.js';
|
|
3
|
+
import { ensureBytes, numberToBytesLE, bytesToNumberLE } from './utils.js';
|
|
4
4
|
const _0n = BigInt(0);
|
|
5
5
|
const _1n = BigInt(1);
|
|
6
6
|
function validateOpts(curve) {
|
|
@@ -11,7 +11,7 @@ function validateOpts(curve) {
|
|
|
11
11
|
for (const i of ['montgomeryBits', 'nByteLength']) {
|
|
12
12
|
if (curve[i] === undefined)
|
|
13
13
|
continue; // Optional
|
|
14
|
-
if (!
|
|
14
|
+
if (!Number.isSafeInteger(curve[i]))
|
|
15
15
|
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
|
16
16
|
}
|
|
17
17
|
for (const fn of ['adjustScalarBytes', 'domain', 'powPminus2']) {
|
|
@@ -27,7 +27,6 @@ function validateOpts(curve) {
|
|
|
27
27
|
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
|
28
28
|
}
|
|
29
29
|
// Set defaults
|
|
30
|
-
// ...nLength(curve.n, curve.nBitLength),
|
|
31
30
|
return Object.freeze({ ...curve });
|
|
32
31
|
}
|
|
33
32
|
// NOTE: not really montgomery curve, just bunch of very specific methods for X25519/X448 (RFC 7748, https://www.rfc-editor.org/rfc/rfc7748)
|
|
@@ -35,12 +34,12 @@ function validateOpts(curve) {
|
|
|
35
34
|
export function montgomery(curveDef) {
|
|
36
35
|
const CURVE = validateOpts(curveDef);
|
|
37
36
|
const { P } = CURVE;
|
|
38
|
-
const modP = (a) => mod
|
|
37
|
+
const modP = (a) => mod(a, P);
|
|
39
38
|
const montgomeryBits = CURVE.montgomeryBits;
|
|
40
39
|
const montgomeryBytes = Math.ceil(montgomeryBits / 8);
|
|
41
40
|
const fieldLen = CURVE.nByteLength;
|
|
42
41
|
const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes) => bytes);
|
|
43
|
-
const powPminus2 = CURVE.powPminus2 || ((x) =>
|
|
42
|
+
const powPminus2 = CURVE.powPminus2 || ((x) => pow(x, P - BigInt(2), P));
|
|
44
43
|
/**
|
|
45
44
|
* Checks for num to be in range:
|
|
46
45
|
* For strict == true: `0 < num < max`.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
2
|
+
// Poseidon Hash: https://eprint.iacr.org/2019/458.pdf, https://www.poseidon-hash.info
|
|
3
|
+
import { validateField, FpPow } from './modular.js';
|
|
4
|
+
export function validateOpts(opts) {
|
|
5
|
+
const { Fp } = opts;
|
|
6
|
+
validateField(Fp);
|
|
7
|
+
for (const i of ['t', 'roundsFull', 'roundsPartial']) {
|
|
8
|
+
if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
|
|
9
|
+
throw new Error(`Poseidon: invalid param ${i}=${opts[i]} (${typeof opts[i]})`);
|
|
10
|
+
}
|
|
11
|
+
if (opts.reversePartialPowIdx !== undefined && typeof opts.reversePartialPowIdx !== 'boolean')
|
|
12
|
+
throw new Error(`Poseidon: invalid param reversePartialPowIdx=${opts.reversePartialPowIdx}`);
|
|
13
|
+
// Default is 5, but by some reasons stark uses 3
|
|
14
|
+
let sboxPower = opts.sboxPower;
|
|
15
|
+
if (sboxPower === undefined)
|
|
16
|
+
sboxPower = 5;
|
|
17
|
+
if (typeof sboxPower !== 'number' || !Number.isSafeInteger(sboxPower))
|
|
18
|
+
throw new Error(`Poseidon wrong sboxPower=${sboxPower}`);
|
|
19
|
+
const _sboxPower = BigInt(sboxPower);
|
|
20
|
+
let sboxFn = (n) => FpPow(Fp, n, _sboxPower);
|
|
21
|
+
// Unwrapped sbox power for common cases (195->142μs)
|
|
22
|
+
if (sboxPower === 3)
|
|
23
|
+
sboxFn = (n) => Fp.mul(Fp.sqrN(n), n);
|
|
24
|
+
else if (sboxPower === 5)
|
|
25
|
+
sboxFn = (n) => Fp.mul(Fp.sqrN(Fp.sqrN(n)), n);
|
|
26
|
+
if (opts.roundsFull % 2 !== 0)
|
|
27
|
+
throw new Error(`Poseidon roundsFull is not even: ${opts.roundsFull}`);
|
|
28
|
+
const rounds = opts.roundsFull + opts.roundsPartial;
|
|
29
|
+
if (!Array.isArray(opts.roundConstants) || opts.roundConstants.length !== rounds)
|
|
30
|
+
throw new Error('Poseidon: wrong round constants');
|
|
31
|
+
const roundConstants = opts.roundConstants.map((rc) => {
|
|
32
|
+
if (!Array.isArray(rc) || rc.length !== opts.t)
|
|
33
|
+
throw new Error(`Poseidon wrong round constants: ${rc}`);
|
|
34
|
+
return rc.map((i) => {
|
|
35
|
+
if (typeof i !== 'bigint' || !Fp.isValid(i))
|
|
36
|
+
throw new Error(`Poseidon wrong round constant=${i}`);
|
|
37
|
+
return Fp.create(i);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
// MDS is TxT matrix
|
|
41
|
+
if (!Array.isArray(opts.mds) || opts.mds.length !== opts.t)
|
|
42
|
+
throw new Error('Poseidon: wrong MDS matrix');
|
|
43
|
+
const mds = opts.mds.map((mdsRow) => {
|
|
44
|
+
if (!Array.isArray(mdsRow) || mdsRow.length !== opts.t)
|
|
45
|
+
throw new Error(`Poseidon MDS matrix row: ${mdsRow}`);
|
|
46
|
+
return mdsRow.map((i) => {
|
|
47
|
+
if (typeof i !== 'bigint')
|
|
48
|
+
throw new Error(`Poseidon MDS matrix value=${i}`);
|
|
49
|
+
return Fp.create(i);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
return Object.freeze({ ...opts, rounds, sboxFn, roundConstants, mds });
|
|
53
|
+
}
|
|
54
|
+
export function splitConstants(rc, t) {
|
|
55
|
+
if (typeof t !== 'number')
|
|
56
|
+
throw new Error('poseidonSplitConstants: wrong t');
|
|
57
|
+
if (!Array.isArray(rc) || rc.length % t)
|
|
58
|
+
throw new Error('poseidonSplitConstants: wrong rc');
|
|
59
|
+
const res = [];
|
|
60
|
+
let tmp = [];
|
|
61
|
+
for (let i = 0; i < rc.length; i++) {
|
|
62
|
+
tmp.push(rc[i]);
|
|
63
|
+
if (tmp.length === t) {
|
|
64
|
+
res.push(tmp);
|
|
65
|
+
tmp = [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return res;
|
|
69
|
+
}
|
|
70
|
+
export function poseidon(opts) {
|
|
71
|
+
const { t, Fp, rounds, sboxFn, reversePartialPowIdx } = validateOpts(opts);
|
|
72
|
+
const halfRoundsFull = Math.floor(opts.roundsFull / 2);
|
|
73
|
+
const partialIdx = reversePartialPowIdx ? t - 1 : 0;
|
|
74
|
+
const poseidonRound = (values, isFull, idx) => {
|
|
75
|
+
values = values.map((i, j) => Fp.add(i, opts.roundConstants[idx][j]));
|
|
76
|
+
if (isFull)
|
|
77
|
+
values = values.map((i) => sboxFn(i));
|
|
78
|
+
else
|
|
79
|
+
values[partialIdx] = sboxFn(values[partialIdx]);
|
|
80
|
+
// Matrix multiplication
|
|
81
|
+
values = opts.mds.map((i) => i.reduce((acc, i, j) => Fp.add(acc, Fp.mulN(i, values[j])), Fp.ZERO));
|
|
82
|
+
return values;
|
|
83
|
+
};
|
|
84
|
+
const poseidonHash = function poseidonHash(values) {
|
|
85
|
+
if (!Array.isArray(values) || values.length !== t)
|
|
86
|
+
throw new Error(`Poseidon: wrong values (expected array of bigints with length ${t})`);
|
|
87
|
+
values = values.map((i) => {
|
|
88
|
+
if (typeof i !== 'bigint')
|
|
89
|
+
throw new Error(`Poseidon: wrong value=${i} (${typeof i})`);
|
|
90
|
+
return Fp.create(i);
|
|
91
|
+
});
|
|
92
|
+
let round = 0;
|
|
93
|
+
// Apply r_f/2 full rounds.
|
|
94
|
+
for (let i = 0; i < halfRoundsFull; i++)
|
|
95
|
+
values = poseidonRound(values, true, round++);
|
|
96
|
+
// Apply r_p partial rounds.
|
|
97
|
+
for (let i = 0; i < opts.roundsPartial; i++)
|
|
98
|
+
values = poseidonRound(values, false, round++);
|
|
99
|
+
// Apply r_f/2 full rounds.
|
|
100
|
+
for (let i = 0; i < halfRoundsFull; i++)
|
|
101
|
+
values = poseidonRound(values, true, round++);
|
|
102
|
+
if (round !== rounds)
|
|
103
|
+
throw new Error(`Poseidon: wrong number of rounds: last round=${round}, total=${rounds}`);
|
|
104
|
+
return values;
|
|
105
|
+
};
|
|
106
|
+
// For verification in tests
|
|
107
|
+
poseidonHash.roundConstants = opts.roundConstants;
|
|
108
|
+
return poseidonHash;
|
|
109
|
+
}
|
|
@@ -1,41 +1,16 @@
|
|
|
1
1
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
2
|
-
import * as mod from './modular.js';
|
|
3
2
|
const _0n = BigInt(0);
|
|
4
3
|
const _1n = BigInt(1);
|
|
5
4
|
const _2n = BigInt(2);
|
|
6
|
-
|
|
7
|
-
export function isPositiveInt(num) {
|
|
8
|
-
return typeof num === 'number' && Number.isSafeInteger(num) && num > 0;
|
|
9
|
-
}
|
|
10
|
-
export function validateOpts(curve) {
|
|
11
|
-
mod.validateField(curve.Fp);
|
|
12
|
-
for (const i of ['n', 'h']) {
|
|
13
|
-
const val = curve[i];
|
|
14
|
-
if (typeof val !== 'bigint')
|
|
15
|
-
throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`);
|
|
16
|
-
}
|
|
17
|
-
if (!curve.Fp.isValid(curve.Gx))
|
|
18
|
-
throw new Error('Invalid generator X coordinate Fp element');
|
|
19
|
-
if (!curve.Fp.isValid(curve.Gy))
|
|
20
|
-
throw new Error('Invalid generator Y coordinate Fp element');
|
|
21
|
-
for (const i of ['nBitLength', 'nByteLength']) {
|
|
22
|
-
const val = curve[i];
|
|
23
|
-
if (val === undefined)
|
|
24
|
-
continue; // Optional
|
|
25
|
-
if (!isPositiveInt(val))
|
|
26
|
-
throw new Error(`Invalid curve param ${i}=${val} (${typeof val})`);
|
|
27
|
-
}
|
|
28
|
-
// Set defaults
|
|
29
|
-
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve });
|
|
30
|
-
}
|
|
5
|
+
const u8a = (a) => a instanceof Uint8Array;
|
|
31
6
|
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
|
|
32
|
-
export function bytesToHex(
|
|
33
|
-
if (!(
|
|
7
|
+
export function bytesToHex(bytes) {
|
|
8
|
+
if (!u8a(bytes))
|
|
34
9
|
throw new Error('Expected Uint8Array');
|
|
35
10
|
// pre-caching improves the speed 6x
|
|
36
11
|
let hex = '';
|
|
37
|
-
for (let i = 0; i <
|
|
38
|
-
hex += hexes[
|
|
12
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
13
|
+
hex += hexes[bytes[i]];
|
|
39
14
|
}
|
|
40
15
|
return hex;
|
|
41
16
|
}
|
|
@@ -44,17 +19,15 @@ export function numberToHexUnpadded(num) {
|
|
|
44
19
|
return hex.length & 1 ? `0${hex}` : hex;
|
|
45
20
|
}
|
|
46
21
|
export function hexToNumber(hex) {
|
|
47
|
-
if (typeof hex !== 'string')
|
|
48
|
-
throw new
|
|
49
|
-
}
|
|
22
|
+
if (typeof hex !== 'string')
|
|
23
|
+
throw new Error('hexToNumber: expected string, got ' + typeof hex);
|
|
50
24
|
// Big Endian
|
|
51
25
|
return BigInt(`0x${hex}`);
|
|
52
26
|
}
|
|
53
27
|
// Caching slows it down 2-3x
|
|
54
28
|
export function hexToBytes(hex) {
|
|
55
|
-
if (typeof hex !== 'string')
|
|
56
|
-
throw new
|
|
57
|
-
}
|
|
29
|
+
if (typeof hex !== 'string')
|
|
30
|
+
throw new Error('hexToBytes: expected string, got ' + typeof hex);
|
|
58
31
|
if (hex.length % 2)
|
|
59
32
|
throw new Error('hexToBytes: received invalid unpadded hex ' + hex.length);
|
|
60
33
|
const array = new Uint8Array(hex.length / 2);
|
|
@@ -72,24 +45,31 @@ export function hexToBytes(hex) {
|
|
|
72
45
|
export function bytesToNumberBE(bytes) {
|
|
73
46
|
return hexToNumber(bytesToHex(bytes));
|
|
74
47
|
}
|
|
75
|
-
export function bytesToNumberLE(
|
|
76
|
-
if (!(
|
|
48
|
+
export function bytesToNumberLE(bytes) {
|
|
49
|
+
if (!u8a(bytes))
|
|
77
50
|
throw new Error('Expected Uint8Array');
|
|
78
|
-
return
|
|
51
|
+
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
|
|
79
52
|
}
|
|
80
53
|
export const numberToBytesBE = (n, len) => hexToBytes(n.toString(16).padStart(len * 2, '0'));
|
|
81
54
|
export const numberToBytesLE = (n, len) => numberToBytesBE(n, len).reverse();
|
|
55
|
+
// Returns variable number bytes (minimal bigint encoding?)
|
|
56
|
+
export const numberToVarBytesBE = (n) => {
|
|
57
|
+
let hex = n.toString(16);
|
|
58
|
+
if (hex.length & 1)
|
|
59
|
+
hex = '0' + hex;
|
|
60
|
+
return hexToBytes(hex);
|
|
61
|
+
};
|
|
82
62
|
export function ensureBytes(hex, expectedLength) {
|
|
83
63
|
// Uint8Array.from() instead of hash.slice() because node.js Buffer
|
|
84
64
|
// is instance of Uint8Array, and its slice() creates **mutable** copy
|
|
85
|
-
const bytes = hex
|
|
65
|
+
const bytes = u8a(hex) ? Uint8Array.from(hex) : hexToBytes(hex);
|
|
86
66
|
if (typeof expectedLength === 'number' && bytes.length !== expectedLength)
|
|
87
67
|
throw new Error(`Expected ${expectedLength} bytes`);
|
|
88
68
|
return bytes;
|
|
89
69
|
}
|
|
90
70
|
// Copies several Uint8Arrays into one.
|
|
91
71
|
export function concatBytes(...arrays) {
|
|
92
|
-
if (!arrays.every((b) => b
|
|
72
|
+
if (!arrays.every((b) => u8a(b)))
|
|
93
73
|
throw new Error('Uint8Array list expected');
|
|
94
74
|
if (arrays.length === 1)
|
|
95
75
|
return arrays[0];
|
|
@@ -102,31 +82,6 @@ export function concatBytes(...arrays) {
|
|
|
102
82
|
}
|
|
103
83
|
return result;
|
|
104
84
|
}
|
|
105
|
-
// CURVE.n lengths
|
|
106
|
-
export function nLength(n, nBitLength) {
|
|
107
|
-
// Bit size, byte size of CURVE.n
|
|
108
|
-
const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length;
|
|
109
|
-
const nByteLength = Math.ceil(_nBitLength / 8);
|
|
110
|
-
return { nBitLength: _nBitLength, nByteLength };
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility.
|
|
114
|
-
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
|
|
115
|
-
* and convert them into private scalar, with the modulo bias being neglible.
|
|
116
|
-
* Needs at least 40 bytes of input for 32-byte private key.
|
|
117
|
-
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
|
118
|
-
* @param hash hash output from SHA3 or a similar function
|
|
119
|
-
* @returns valid private scalar
|
|
120
|
-
*/
|
|
121
|
-
export function hashToPrivateScalar(hash, groupOrder, isLE = false) {
|
|
122
|
-
hash = ensureBytes(hash);
|
|
123
|
-
const hashLen = hash.length;
|
|
124
|
-
const minLen = nLength(groupOrder).nByteLength + 8;
|
|
125
|
-
if (minLen < 24 || hashLen < minLen || hashLen > 1024)
|
|
126
|
-
throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`);
|
|
127
|
-
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
|
|
128
|
-
return mod.mod(num, groupOrder - _1n) + _1n;
|
|
129
|
-
}
|
|
130
85
|
export function equalBytes(b1, b2) {
|
|
131
86
|
// We don't care about timing attacks here
|
|
132
87
|
if (b1.length !== b2.length)
|