@noble/curves 0.5.2 → 0.6.1
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 +115 -41
- package/lib/_shortw_utils.d.ts +13 -24
- package/lib/abstract/bls.d.ts +39 -32
- package/lib/abstract/bls.js +74 -73
- package/lib/abstract/{group.d.ts → curve.d.ts} +30 -1
- package/lib/abstract/{group.js → curve.js} +33 -2
- package/lib/abstract/edwards.d.ts +30 -72
- package/lib/abstract/edwards.js +206 -389
- 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 +21 -8
- package/lib/abstract/modular.js +72 -48
- package/lib/abstract/montgomery.js +23 -68
- package/lib/abstract/poseidon.d.ts +29 -0
- package/lib/abstract/poseidon.js +115 -0
- package/lib/abstract/utils.d.ts +9 -37
- package/lib/abstract/utils.js +61 -87
- package/lib/abstract/weierstrass.d.ts +58 -81
- package/lib/abstract/weierstrass.js +485 -679
- 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} +31 -1
- package/lib/esm/abstract/edwards.js +204 -387
- package/lib/esm/abstract/hash-to-curve.js +38 -11
- package/lib/esm/abstract/modular.js +69 -47
- package/lib/esm/abstract/montgomery.js +24 -69
- package/lib/esm/abstract/poseidon.js +109 -0
- package/lib/esm/abstract/utils.js +58 -82
- package/lib/esm/abstract/weierstrass.js +484 -678
- 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/p224.js +1 -1
- package/lib/esm/p256.js +11 -9
- package/lib/esm/p384.js +11 -9
- package/lib/esm/p521.js +12 -23
- package/lib/esm/secp256k1.js +124 -162
- package/lib/esm/stark.js +105 -41
- package/lib/jubjub.d.ts +2 -2
- package/lib/jubjub.js +1 -1
- package/lib/p192.d.ts +26 -48
- package/lib/p224.d.ts +26 -48
- package/lib/p224.js +1 -1
- package/lib/p256.d.ts +29 -48
- package/lib/p256.js +13 -10
- package/lib/p384.d.ts +29 -48
- package/lib/p384.js +13 -10
- package/lib/p521.d.ts +37 -57
- package/lib/p521.js +14 -24
- package/lib/secp256k1.d.ts +37 -46
- package/lib/secp256k1.js +124 -162
- package/lib/stark.d.ts +39 -22
- package/lib/stark.js +108 -41
- package/package.json +15 -10
|
@@ -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 Error(`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, validateObject, } 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
|
|
@@ -35,7 +34,6 @@ export function pow(num, power, modulo) {
|
|
|
35
34
|
return res;
|
|
36
35
|
}
|
|
37
36
|
// Does x ^ (2 ^ power) mod p. pow2(30, 4) == 30 ^ (2 ^ 4)
|
|
38
|
-
// TODO: Fp version?
|
|
39
37
|
export function pow2(x, power, modulo) {
|
|
40
38
|
let res = x;
|
|
41
39
|
while (power-- > _0n) {
|
|
@@ -91,7 +89,7 @@ export function tonelliShanks(P) {
|
|
|
91
89
|
const p1div4 = (P + _1n) / _4n;
|
|
92
90
|
return function tonelliFast(Fp, n) {
|
|
93
91
|
const root = Fp.pow(n, p1div4);
|
|
94
|
-
if (!Fp.
|
|
92
|
+
if (!Fp.eql(Fp.sqr(root), n))
|
|
95
93
|
throw new Error('Cannot find square root');
|
|
96
94
|
return root;
|
|
97
95
|
};
|
|
@@ -100,26 +98,26 @@ export function tonelliShanks(P) {
|
|
|
100
98
|
const Q1div2 = (Q + _1n) / _2n;
|
|
101
99
|
return function tonelliSlow(Fp, n) {
|
|
102
100
|
// Step 0: Check that n is indeed a square: (n | p) should not be ≡ -1
|
|
103
|
-
if (Fp.pow(n, legendreC) === Fp.
|
|
101
|
+
if (Fp.pow(n, legendreC) === Fp.neg(Fp.ONE))
|
|
104
102
|
throw new Error('Cannot find square root');
|
|
105
103
|
let r = S;
|
|
106
104
|
// TODO: will fail at Fp2/etc
|
|
107
105
|
let g = Fp.pow(Fp.mul(Fp.ONE, Z), Q); // will update both x and b
|
|
108
106
|
let x = Fp.pow(n, Q1div2); // first guess at the square root
|
|
109
107
|
let b = Fp.pow(n, Q); // first guess at the fudge factor
|
|
110
|
-
while (!Fp.
|
|
111
|
-
if (Fp.
|
|
108
|
+
while (!Fp.eql(b, Fp.ONE)) {
|
|
109
|
+
if (Fp.eql(b, Fp.ZERO))
|
|
112
110
|
return Fp.ZERO; // https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm (4. If t = 0, return r = 0)
|
|
113
111
|
// Find m such b^(2^m)==1
|
|
114
112
|
let m = 1;
|
|
115
|
-
for (let t2 = Fp.
|
|
116
|
-
if (Fp.
|
|
113
|
+
for (let t2 = Fp.sqr(b); m < r; m++) {
|
|
114
|
+
if (Fp.eql(t2, Fp.ONE))
|
|
117
115
|
break;
|
|
118
|
-
t2 = Fp.
|
|
116
|
+
t2 = Fp.sqr(t2); // t2 *= t2
|
|
119
117
|
}
|
|
120
118
|
// NOTE: r-m-1 can be bigger than 32, need to convert to bigint before shift, otherwise there will be overflow
|
|
121
119
|
const ge = Fp.pow(g, _1n << BigInt(r - m - 1)); // ge = 2^(r-m-1)
|
|
122
|
-
g = Fp.
|
|
120
|
+
g = Fp.sqr(ge); // g = ge * ge
|
|
123
121
|
x = Fp.mul(x, ge); // x *= ge
|
|
124
122
|
b = Fp.mul(b, g); // b *= g
|
|
125
123
|
r = m;
|
|
@@ -141,7 +139,7 @@ export function FpSqrt(P) {
|
|
|
141
139
|
return function sqrt3mod4(Fp, n) {
|
|
142
140
|
const root = Fp.pow(n, p1div4);
|
|
143
141
|
// Throw if root**2 != n
|
|
144
|
-
if (!Fp.
|
|
142
|
+
if (!Fp.eql(Fp.sqr(root), n))
|
|
145
143
|
throw new Error('Cannot find square root');
|
|
146
144
|
return root;
|
|
147
145
|
};
|
|
@@ -155,7 +153,7 @@ export function FpSqrt(P) {
|
|
|
155
153
|
const nv = Fp.mul(n, v);
|
|
156
154
|
const i = Fp.mul(Fp.mul(nv, _2n), v);
|
|
157
155
|
const root = Fp.mul(nv, Fp.sub(i, Fp.ONE));
|
|
158
|
-
if (!Fp.
|
|
156
|
+
if (!Fp.eql(Fp.sqr(root), n))
|
|
159
157
|
throw new Error('Cannot find square root');
|
|
160
158
|
return root;
|
|
161
159
|
};
|
|
@@ -189,23 +187,22 @@ export function FpSqrt(P) {
|
|
|
189
187
|
export const isNegativeLE = (num, modulo) => (mod(num, modulo) & _1n) === _1n;
|
|
190
188
|
// prettier-ignore
|
|
191
189
|
const FIELD_FIELDS = [
|
|
192
|
-
'create', 'isValid', '
|
|
193
|
-
'
|
|
194
|
-
'addN', 'subN', 'mulN', '
|
|
190
|
+
'create', 'isValid', 'is0', 'neg', 'inv', 'sqrt', 'sqr',
|
|
191
|
+
'eql', 'add', 'sub', 'mul', 'pow', 'div',
|
|
192
|
+
'addN', 'subN', 'mulN', 'sqrN'
|
|
195
193
|
];
|
|
196
194
|
export function validateField(field) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
195
|
+
const initial = {
|
|
196
|
+
ORDER: 'bigint',
|
|
197
|
+
MASK: 'bigint',
|
|
198
|
+
BYTES: 'isSafeInteger',
|
|
199
|
+
BITS: 'isSafeInteger',
|
|
200
|
+
};
|
|
201
|
+
const opts = FIELD_FIELDS.reduce((map, val) => {
|
|
202
|
+
map[val] = 'function';
|
|
203
|
+
return map;
|
|
204
|
+
}, initial);
|
|
205
|
+
return validateObject(field, opts);
|
|
209
206
|
}
|
|
210
207
|
// Generic field functions
|
|
211
208
|
export function FpPow(f, num, power) {
|
|
@@ -222,7 +219,7 @@ export function FpPow(f, num, power) {
|
|
|
222
219
|
while (power > _0n) {
|
|
223
220
|
if (power & _1n)
|
|
224
221
|
p = f.mul(p, d);
|
|
225
|
-
d = f.
|
|
222
|
+
d = f.sqr(d);
|
|
226
223
|
power >>= 1n;
|
|
227
224
|
}
|
|
228
225
|
return p;
|
|
@@ -231,16 +228,16 @@ export function FpInvertBatch(f, nums) {
|
|
|
231
228
|
const tmp = new Array(nums.length);
|
|
232
229
|
// Walk from first to last, multiply them by each other MOD p
|
|
233
230
|
const lastMultiplied = nums.reduce((acc, num, i) => {
|
|
234
|
-
if (f.
|
|
231
|
+
if (f.is0(num))
|
|
235
232
|
return acc;
|
|
236
233
|
tmp[i] = acc;
|
|
237
234
|
return f.mul(acc, num);
|
|
238
235
|
}, f.ONE);
|
|
239
236
|
// Invert last element
|
|
240
|
-
const inverted = f.
|
|
237
|
+
const inverted = f.inv(lastMultiplied);
|
|
241
238
|
// Walk from last to first, multiply them by inverted each other MOD p
|
|
242
239
|
nums.reduceRight((acc, num, i) => {
|
|
243
|
-
if (f.
|
|
240
|
+
if (f.is0(num))
|
|
244
241
|
return acc;
|
|
245
242
|
tmp[i] = f.mul(acc, tmp[i]);
|
|
246
243
|
return f.mul(acc, num);
|
|
@@ -248,20 +245,27 @@ export function FpInvertBatch(f, nums) {
|
|
|
248
245
|
return tmp;
|
|
249
246
|
}
|
|
250
247
|
export function FpDiv(f, lhs, rhs) {
|
|
251
|
-
return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.
|
|
248
|
+
return f.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, f.ORDER) : f.inv(rhs));
|
|
252
249
|
}
|
|
253
250
|
// This function returns True whenever the value x is a square in the field F.
|
|
254
251
|
export function FpIsSquare(f) {
|
|
255
252
|
const legendreConst = (f.ORDER - _1n) / _2n; // Integer arithmetic
|
|
256
253
|
return (x) => {
|
|
257
254
|
const p = f.pow(x, legendreConst);
|
|
258
|
-
return f.
|
|
255
|
+
return f.eql(p, f.ZERO) || f.eql(p, f.ONE);
|
|
259
256
|
};
|
|
260
257
|
}
|
|
258
|
+
// CURVE.n lengths
|
|
259
|
+
export function nLength(n, nBitLength) {
|
|
260
|
+
// Bit size, byte size of CURVE.n
|
|
261
|
+
const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length;
|
|
262
|
+
const nByteLength = Math.ceil(_nBitLength / 8);
|
|
263
|
+
return { nBitLength: _nBitLength, nByteLength };
|
|
264
|
+
}
|
|
261
265
|
export function Fp(ORDER, bitLen, isLE = false, redef = {}) {
|
|
262
266
|
if (ORDER <= _0n)
|
|
263
267
|
throw new Error(`Expected Fp ORDER > 0, got ${ORDER}`);
|
|
264
|
-
const { nBitLength: BITS, nByteLength: BYTES } =
|
|
268
|
+
const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen);
|
|
265
269
|
if (BYTES > 2048)
|
|
266
270
|
throw new Error('Field lengths over 2048 bytes are not supported');
|
|
267
271
|
const sqrtP = FpSqrt(ORDER);
|
|
@@ -269,41 +273,41 @@ export function Fp(ORDER, bitLen, isLE = false, redef = {}) {
|
|
|
269
273
|
ORDER,
|
|
270
274
|
BITS,
|
|
271
275
|
BYTES,
|
|
272
|
-
MASK:
|
|
276
|
+
MASK: bitMask(BITS),
|
|
273
277
|
ZERO: _0n,
|
|
274
278
|
ONE: _1n,
|
|
275
279
|
create: (num) => mod(num, ORDER),
|
|
276
280
|
isValid: (num) => {
|
|
277
281
|
if (typeof num !== 'bigint')
|
|
278
282
|
throw new Error(`Invalid field element: expected bigint, got ${typeof num}`);
|
|
279
|
-
return _0n <= num && num < ORDER;
|
|
283
|
+
return _0n <= num && num < ORDER; // 0 is valid element, but it's not invertible
|
|
280
284
|
},
|
|
281
|
-
|
|
285
|
+
is0: (num) => num === _0n,
|
|
282
286
|
isOdd: (num) => (num & _1n) === _1n,
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
287
|
+
neg: (num) => mod(-num, ORDER),
|
|
288
|
+
eql: (lhs, rhs) => lhs === rhs,
|
|
289
|
+
sqr: (num) => mod(num * num, ORDER),
|
|
286
290
|
add: (lhs, rhs) => mod(lhs + rhs, ORDER),
|
|
287
291
|
sub: (lhs, rhs) => mod(lhs - rhs, ORDER),
|
|
288
292
|
mul: (lhs, rhs) => mod(lhs * rhs, ORDER),
|
|
289
293
|
pow: (num, power) => FpPow(f, num, power),
|
|
290
294
|
div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER),
|
|
291
295
|
// Same as above, but doesn't normalize
|
|
292
|
-
|
|
296
|
+
sqrN: (num) => num * num,
|
|
293
297
|
addN: (lhs, rhs) => lhs + rhs,
|
|
294
298
|
subN: (lhs, rhs) => lhs - rhs,
|
|
295
299
|
mulN: (lhs, rhs) => lhs * rhs,
|
|
296
|
-
|
|
300
|
+
inv: (num) => invert(num, ORDER),
|
|
297
301
|
sqrt: redef.sqrt || ((n) => sqrtP(f, n)),
|
|
298
302
|
invertBatch: (lst) => FpInvertBatch(f, lst),
|
|
299
303
|
// TODO: do we really need constant cmov?
|
|
300
304
|
// We don't have const-time bigints anyway, so probably will be not very useful
|
|
301
305
|
cmov: (a, b, c) => (c ? b : a),
|
|
302
|
-
toBytes: (num) => isLE ?
|
|
306
|
+
toBytes: (num) => (isLE ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES)),
|
|
303
307
|
fromBytes: (bytes) => {
|
|
304
308
|
if (bytes.length !== BYTES)
|
|
305
309
|
throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes.length}`);
|
|
306
|
-
return isLE ?
|
|
310
|
+
return isLE ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes);
|
|
307
311
|
},
|
|
308
312
|
});
|
|
309
313
|
return Object.freeze(f);
|
|
@@ -312,11 +316,29 @@ export function FpSqrtOdd(Fp, elm) {
|
|
|
312
316
|
if (!Fp.isOdd)
|
|
313
317
|
throw new Error(`Field doesn't have isOdd`);
|
|
314
318
|
const root = Fp.sqrt(elm);
|
|
315
|
-
return Fp.isOdd(root) ? root : Fp.
|
|
319
|
+
return Fp.isOdd(root) ? root : Fp.neg(root);
|
|
316
320
|
}
|
|
317
321
|
export function FpSqrtEven(Fp, elm) {
|
|
318
322
|
if (!Fp.isOdd)
|
|
319
323
|
throw new Error(`Field doesn't have isOdd`);
|
|
320
324
|
const root = Fp.sqrt(elm);
|
|
321
|
-
return Fp.isOdd(root) ? Fp.
|
|
325
|
+
return Fp.isOdd(root) ? Fp.neg(root) : root;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* FIPS 186 B.4.1-compliant "constant-time" private key generation utility.
|
|
329
|
+
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
|
|
330
|
+
* and convert them into private scalar, with the modulo bias being neglible.
|
|
331
|
+
* Needs at least 40 bytes of input for 32-byte private key.
|
|
332
|
+
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
|
333
|
+
* @param hash hash output from SHA3 or a similar function
|
|
334
|
+
* @returns valid private scalar
|
|
335
|
+
*/
|
|
336
|
+
export function hashToPrivateScalar(hash, groupOrder, isLE = false) {
|
|
337
|
+
hash = ensureBytes(hash);
|
|
338
|
+
const hashLen = hash.length;
|
|
339
|
+
const minLen = nLength(groupOrder).nByteLength + 8;
|
|
340
|
+
if (minLen < 24 || hashLen < minLen || hashLen > 1024)
|
|
341
|
+
throw new Error(`hashToPrivateScalar: expected ${minLen}-1024 bytes of input, got ${hashLen}`);
|
|
342
|
+
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
|
|
343
|
+
return mod(num, groupOrder - _1n) + _1n;
|
|
322
344
|
}
|
|
@@ -1,33 +1,20 @@
|
|
|
1
1
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
2
|
-
import
|
|
3
|
-
import { ensureBytes, numberToBytesLE,
|
|
2
|
+
import { mod, pow } from './modular.js';
|
|
3
|
+
import { bytesToNumberLE, ensureBytes, numberToBytesLE, validateObject } from './utils.js';
|
|
4
4
|
const _0n = BigInt(0);
|
|
5
5
|
const _1n = BigInt(1);
|
|
6
6
|
function validateOpts(curve) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
for (const fn of ['adjustScalarBytes', 'domain', 'powPminus2']) {
|
|
18
|
-
if (curve[fn] === undefined)
|
|
19
|
-
continue; // Optional
|
|
20
|
-
if (typeof curve[fn] !== 'function')
|
|
21
|
-
throw new Error(`Invalid ${fn} function`);
|
|
22
|
-
}
|
|
23
|
-
for (const i of ['Gu']) {
|
|
24
|
-
if (curve[i] === undefined)
|
|
25
|
-
continue; // Optional
|
|
26
|
-
if (typeof curve[i] !== 'string')
|
|
27
|
-
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
|
28
|
-
}
|
|
7
|
+
validateObject(curve, {
|
|
8
|
+
a24: 'bigint',
|
|
9
|
+
}, {
|
|
10
|
+
montgomeryBits: 'isSafeInteger',
|
|
11
|
+
nByteLength: 'isSafeInteger',
|
|
12
|
+
adjustScalarBytes: 'function',
|
|
13
|
+
domain: 'function',
|
|
14
|
+
powPminus2: 'function',
|
|
15
|
+
Gu: 'string',
|
|
16
|
+
});
|
|
29
17
|
// Set defaults
|
|
30
|
-
// ...nLength(curve.n, curve.nBitLength),
|
|
31
18
|
return Object.freeze({ ...curve });
|
|
32
19
|
}
|
|
33
20
|
// NOTE: not really montgomery curve, just bunch of very specific methods for X25519/X448 (RFC 7748, https://www.rfc-editor.org/rfc/rfc7748)
|
|
@@ -35,37 +22,13 @@ function validateOpts(curve) {
|
|
|
35
22
|
export function montgomery(curveDef) {
|
|
36
23
|
const CURVE = validateOpts(curveDef);
|
|
37
24
|
const { P } = CURVE;
|
|
38
|
-
const modP = (a) => mod
|
|
25
|
+
const modP = (a) => mod(a, P);
|
|
39
26
|
const montgomeryBits = CURVE.montgomeryBits;
|
|
40
27
|
const montgomeryBytes = Math.ceil(montgomeryBits / 8);
|
|
41
28
|
const fieldLen = CURVE.nByteLength;
|
|
42
29
|
const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes) => bytes);
|
|
43
|
-
const powPminus2 = CURVE.powPminus2 || ((x) =>
|
|
44
|
-
|
|
45
|
-
* Checks for num to be in range:
|
|
46
|
-
* For strict == true: `0 < num < max`.
|
|
47
|
-
* For strict == false: `0 <= num < max`.
|
|
48
|
-
* Converts non-float safe numbers to bigints.
|
|
49
|
-
*/
|
|
50
|
-
function normalizeScalar(num, max, strict = true) {
|
|
51
|
-
if (!max)
|
|
52
|
-
throw new TypeError('Specify max value');
|
|
53
|
-
if (typeof num === 'number' && Number.isSafeInteger(num))
|
|
54
|
-
num = BigInt(num);
|
|
55
|
-
if (typeof num === 'bigint' && num < max) {
|
|
56
|
-
if (strict) {
|
|
57
|
-
if (_0n < num)
|
|
58
|
-
return num;
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
if (_0n <= num)
|
|
62
|
-
return num;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
throw new TypeError('Expected valid scalar: 0 < scalar < max');
|
|
66
|
-
}
|
|
67
|
-
// cswap from RFC7748
|
|
68
|
-
// NOTE: cswap is not from RFC7748!
|
|
30
|
+
const powPminus2 = CURVE.powPminus2 || ((x) => pow(x, P - BigInt(2), P));
|
|
31
|
+
// cswap from RFC7748. But it is not from RFC7748!
|
|
69
32
|
/*
|
|
70
33
|
cswap(swap, x_2, x_3):
|
|
71
34
|
dummy = mask(swap) AND (x_2 XOR x_3)
|
|
@@ -81,6 +44,11 @@ export function montgomery(curveDef) {
|
|
|
81
44
|
x_3 = modP(x_3 + dummy);
|
|
82
45
|
return [x_2, x_3];
|
|
83
46
|
}
|
|
47
|
+
function assertFieldElement(n) {
|
|
48
|
+
if (typeof n === 'bigint' && _0n <= n && n < P)
|
|
49
|
+
return n;
|
|
50
|
+
throw new Error('Expected valid scalar 0 < scalar < CURVE.P');
|
|
51
|
+
}
|
|
84
52
|
// x25519 from 4
|
|
85
53
|
/**
|
|
86
54
|
*
|
|
@@ -89,11 +57,10 @@ export function montgomery(curveDef) {
|
|
|
89
57
|
* @returns new Point on Montgomery curve
|
|
90
58
|
*/
|
|
91
59
|
function montgomeryLadder(pointU, scalar) {
|
|
92
|
-
const
|
|
93
|
-
const u = normalizeScalar(pointU, P);
|
|
60
|
+
const u = assertFieldElement(pointU);
|
|
94
61
|
// Section 5: Implementations MUST accept non-canonical values and process them as
|
|
95
62
|
// if they had been reduced modulo the field prime.
|
|
96
|
-
const k =
|
|
63
|
+
const k = assertFieldElement(scalar);
|
|
97
64
|
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
|
|
98
65
|
const a24 = CURVE.a24;
|
|
99
66
|
const x_1 = u;
|
|
@@ -146,11 +113,11 @@ export function montgomery(curveDef) {
|
|
|
146
113
|
return numberToBytesLE(modP(u), montgomeryBytes);
|
|
147
114
|
}
|
|
148
115
|
function decodeUCoordinate(uEnc) {
|
|
149
|
-
const u = ensureBytes(uEnc, montgomeryBytes);
|
|
150
116
|
// Section 5: When receiving such an array, implementations of X25519
|
|
151
117
|
// MUST mask the most significant bit in the final byte.
|
|
152
118
|
// This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP
|
|
153
119
|
// fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519
|
|
120
|
+
const u = ensureBytes(uEnc, montgomeryBytes);
|
|
154
121
|
u[fieldLen - 1] &= 127; // 0b0111_1111
|
|
155
122
|
return bytesToNumberLE(u);
|
|
156
123
|
}
|
|
@@ -160,13 +127,6 @@ export function montgomery(curveDef) {
|
|
|
160
127
|
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`);
|
|
161
128
|
return bytesToNumberLE(adjustScalarBytes(bytes));
|
|
162
129
|
}
|
|
163
|
-
/**
|
|
164
|
-
* Computes shared secret between private key "scalar" and public key's "u" (x) coordinate.
|
|
165
|
-
* We can get 'y' coordinate from 'u',
|
|
166
|
-
* but Point.fromHex also wants 'x' coordinate oddity flag,
|
|
167
|
-
* and we cannot get 'x' without knowing 'v'.
|
|
168
|
-
* Need to add generic conversion between twisted edwards and complimentary curve for JubJub.
|
|
169
|
-
*/
|
|
170
130
|
function scalarMult(scalar, u) {
|
|
171
131
|
const pointU = decodeUCoordinate(u);
|
|
172
132
|
const _scalar = decodeScalar(scalar);
|
|
@@ -177,12 +137,7 @@ export function montgomery(curveDef) {
|
|
|
177
137
|
throw new Error('Invalid private or public key received');
|
|
178
138
|
return encodeUCoordinate(pu);
|
|
179
139
|
}
|
|
180
|
-
|
|
181
|
-
* Computes public key from private.
|
|
182
|
-
* Executes scalar multiplication of curve's base point by scalar.
|
|
183
|
-
* @param scalar private key
|
|
184
|
-
* @returns new public key
|
|
185
|
-
*/
|
|
140
|
+
// Computes public key from private. By doing scalar multiplication of base point.
|
|
186
141
|
function scalarMultBase(scalar) {
|
|
187
142
|
return scalarMult(scalar, CURVE.Gu);
|
|
188
143
|
}
|
|
@@ -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 { FpPow, validateField } 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
|
+
}
|