@noble/curves 1.9.3 → 1.9.5
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/abstract/curve.d.ts +47 -46
- package/abstract/curve.d.ts.map +1 -1
- package/abstract/curve.js +9 -6
- package/abstract/curve.js.map +1 -1
- package/abstract/edwards.d.ts +33 -29
- package/abstract/edwards.d.ts.map +1 -1
- package/abstract/edwards.js +123 -122
- package/abstract/edwards.js.map +1 -1
- package/abstract/modular.d.ts +1 -1
- package/abstract/modular.d.ts.map +1 -1
- package/abstract/modular.js +4 -4
- package/abstract/modular.js.map +1 -1
- package/abstract/montgomery.d.ts +2 -6
- package/abstract/montgomery.d.ts.map +1 -1
- package/abstract/montgomery.js +13 -10
- package/abstract/montgomery.js.map +1 -1
- package/abstract/weierstrass.d.ts +161 -91
- package/abstract/weierstrass.d.ts.map +1 -1
- package/abstract/weierstrass.js +340 -267
- package/abstract/weierstrass.js.map +1 -1
- package/bls12-381.d.ts.map +1 -1
- package/bls12-381.js +4 -4
- package/bls12-381.js.map +1 -1
- package/ed25519.d.ts +15 -15
- package/ed25519.d.ts.map +1 -1
- package/ed25519.js +41 -38
- package/ed25519.js.map +1 -1
- package/ed448.d.ts +13 -13
- package/ed448.d.ts.map +1 -1
- package/ed448.js +43 -35
- package/ed448.js.map +1 -1
- package/esm/abstract/curve.d.ts +47 -46
- package/esm/abstract/curve.d.ts.map +1 -1
- package/esm/abstract/curve.js +9 -6
- package/esm/abstract/curve.js.map +1 -1
- package/esm/abstract/edwards.d.ts +33 -29
- package/esm/abstract/edwards.d.ts.map +1 -1
- package/esm/abstract/edwards.js +124 -123
- package/esm/abstract/edwards.js.map +1 -1
- package/esm/abstract/modular.d.ts +1 -1
- package/esm/abstract/modular.d.ts.map +1 -1
- package/esm/abstract/modular.js +4 -4
- package/esm/abstract/modular.js.map +1 -1
- package/esm/abstract/montgomery.d.ts +2 -6
- package/esm/abstract/montgomery.d.ts.map +1 -1
- package/esm/abstract/montgomery.js +14 -11
- package/esm/abstract/montgomery.js.map +1 -1
- package/esm/abstract/weierstrass.d.ts +161 -91
- package/esm/abstract/weierstrass.d.ts.map +1 -1
- package/esm/abstract/weierstrass.js +342 -270
- package/esm/abstract/weierstrass.js.map +1 -1
- package/esm/bls12-381.d.ts.map +1 -1
- package/esm/bls12-381.js +5 -5
- package/esm/bls12-381.js.map +1 -1
- package/esm/ed25519.d.ts +15 -15
- package/esm/ed25519.d.ts.map +1 -1
- package/esm/ed25519.js +40 -37
- package/esm/ed25519.js.map +1 -1
- package/esm/ed448.d.ts +13 -13
- package/esm/ed448.d.ts.map +1 -1
- package/esm/ed448.js +42 -34
- package/esm/ed448.js.map +1 -1
- package/esm/misc.js +2 -2
- package/esm/misc.js.map +1 -1
- package/esm/nist.d.ts +6 -0
- package/esm/nist.d.ts.map +1 -1
- package/esm/nist.js +6 -0
- package/esm/nist.js.map +1 -1
- package/esm/secp256k1.d.ts +2 -6
- package/esm/secp256k1.d.ts.map +1 -1
- package/esm/secp256k1.js +8 -11
- package/esm/secp256k1.js.map +1 -1
- package/esm/utils.d.ts +14 -0
- package/esm/utils.d.ts.map +1 -1
- package/esm/utils.js +43 -0
- package/esm/utils.js.map +1 -1
- package/misc.js +2 -2
- package/misc.js.map +1 -1
- package/nist.d.ts +6 -0
- package/nist.d.ts.map +1 -1
- package/nist.js +7 -1
- package/nist.js.map +1 -1
- package/package.json +1 -1
- package/secp256k1.d.ts +2 -6
- package/secp256k1.d.ts.map +1 -1
- package/secp256k1.js +7 -10
- package/secp256k1.js.map +1 -1
- package/src/abstract/curve.ts +131 -68
- package/src/abstract/edwards.ts +162 -164
- package/src/abstract/modular.ts +4 -4
- package/src/abstract/montgomery.ts +16 -16
- package/src/abstract/weierstrass.ts +510 -394
- package/src/bls12-381.ts +5 -4
- package/src/ed25519.ts +51 -46
- package/src/ed448.ts +46 -44
- package/src/misc.ts +2 -2
- package/src/nist.ts +7 -0
- package/src/secp256k1.ts +10 -12
- package/src/utils.ts +48 -0
- package/utils.d.ts +14 -0
- package/utils.d.ts.map +1 -1
- package/utils.js +47 -0
- package/utils.js.map +1 -1
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
* @module
|
|
26
26
|
*/
|
|
27
27
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
28
|
-
import { hmac } from '@noble/hashes/hmac.js';
|
|
28
|
+
import { hmac as nobleHmac } from '@noble/hashes/hmac.js';
|
|
29
29
|
import { ahash } from '@noble/hashes/utils';
|
|
30
|
-
import { _validateObject, abool, abytes, aInRange, bitLen, bitMask, bytesToHex, bytesToNumberBE, concatBytes, createHmacDrbg, ensureBytes, hexToBytes, inRange, isBytes, memoized, numberToHexUnpadded, randomBytes, } from "../utils.js";
|
|
30
|
+
import { _validateObject, _abool2 as abool, _abytes2 as abytes, aInRange, bitLen, bitMask, bytesToHex, bytesToNumberBE, concatBytes, createHmacDrbg, ensureBytes, hexToBytes, inRange, isBytes, memoized, numberToHexUnpadded, randomBytes as wcRandomBytes, } from "../utils.js";
|
|
31
31
|
import { _createCurveFields, mulEndoUnsafe, negateCt, normalizeZ, pippenger, wNAF, } from "./curve.js";
|
|
32
|
-
import { Field, FpInvertBatch, getMinHashLength, mapHashToField, validateField, } from "./modular.js";
|
|
32
|
+
import { Field, FpInvertBatch, getMinHashLength, mapHashToField, nLength, validateField, } from "./modular.js";
|
|
33
33
|
// We construct basis in such way that den is always positive and equals n, but num sign depends on basis (not on secret value)
|
|
34
34
|
const divNearest = (num, den) => (num + (num >= 0 ? den : -den) / _2n) / den;
|
|
35
35
|
/**
|
|
@@ -60,11 +60,22 @@ export function _splitEndoScalar(k, basis, n) {
|
|
|
60
60
|
}
|
|
61
61
|
return { k1neg, k1, k2neg, k2 };
|
|
62
62
|
}
|
|
63
|
-
function
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
function validateSigFormat(format) {
|
|
64
|
+
if (!['compact', 'recovered', 'der'].includes(format))
|
|
65
|
+
throw new Error('Signature format must be "compact", "recovered", or "der"');
|
|
66
|
+
return format;
|
|
67
|
+
}
|
|
68
|
+
function validateSigOpts(opts, def) {
|
|
69
|
+
const optsn = {};
|
|
70
|
+
for (let optName of Object.keys(def)) {
|
|
71
|
+
// @ts-ignore
|
|
72
|
+
optsn[optName] = opts[optName] === undefined ? def[optName] : opts[optName];
|
|
73
|
+
}
|
|
74
|
+
abool(optsn.lowS, 'lowS');
|
|
75
|
+
abool(optsn.prehash, 'prehash');
|
|
76
|
+
if (optsn.format !== undefined)
|
|
77
|
+
validateSigFormat(optsn.format);
|
|
78
|
+
return optsn;
|
|
68
79
|
}
|
|
69
80
|
export class DERErr extends Error {
|
|
70
81
|
constructor(m = '') {
|
|
@@ -185,19 +196,6 @@ export const DER = {
|
|
|
185
196
|
// Be friendly to bad ECMAScript parsers by not using bigint literals
|
|
186
197
|
// prettier-ignore
|
|
187
198
|
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4);
|
|
188
|
-
// TODO: remove
|
|
189
|
-
export function _legacyHelperEquat(Fp, a, b) {
|
|
190
|
-
/**
|
|
191
|
-
* y² = x³ + ax + b: Short weierstrass curve formula. Takes x, returns y².
|
|
192
|
-
* @returns y²
|
|
193
|
-
*/
|
|
194
|
-
function weierstrassEquation(x) {
|
|
195
|
-
const x2 = Fp.sqr(x); // x * x
|
|
196
|
-
const x3 = Fp.mul(x2, x); // x² * x
|
|
197
|
-
return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x³ + a * x + b
|
|
198
|
-
}
|
|
199
|
-
return weierstrassEquation;
|
|
200
|
-
}
|
|
201
199
|
export function _normFnElement(Fn, key) {
|
|
202
200
|
const { BYTES: expected } = Fn;
|
|
203
201
|
let num;
|
|
@@ -217,10 +215,29 @@ export function _normFnElement(Fn, key) {
|
|
|
217
215
|
throw new Error('invalid private key: out of range [1..N-1]');
|
|
218
216
|
return num;
|
|
219
217
|
}
|
|
220
|
-
|
|
221
|
-
|
|
218
|
+
/**
|
|
219
|
+
* Creates weierstrass Point constructor, based on specified curve options.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
```js
|
|
223
|
+
const opts = {
|
|
224
|
+
p: BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff'),
|
|
225
|
+
n: BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551'),
|
|
226
|
+
h: BigInt(1),
|
|
227
|
+
a: BigInt('0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc'),
|
|
228
|
+
b: BigInt('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b'),
|
|
229
|
+
Gx: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'),
|
|
230
|
+
Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'),
|
|
231
|
+
};
|
|
232
|
+
const p256_Point = weierstrass(opts);
|
|
233
|
+
```
|
|
234
|
+
*/
|
|
235
|
+
export function weierstrassN(params, extraOpts = {}) {
|
|
236
|
+
const validated = _createCurveFields('weierstrass', params, extraOpts);
|
|
237
|
+
const { Fp, Fn } = validated;
|
|
238
|
+
let CURVE = validated.CURVE;
|
|
222
239
|
const { h: cofactor, n: CURVE_ORDER } = CURVE;
|
|
223
|
-
_validateObject(
|
|
240
|
+
_validateObject(extraOpts, {}, {
|
|
224
241
|
allowInfinityPoint: 'boolean',
|
|
225
242
|
clearCofactor: 'function',
|
|
226
243
|
isTorsionFree: 'function',
|
|
@@ -229,13 +246,14 @@ export function weierstrassN(CURVE, curveOpts = {}) {
|
|
|
229
246
|
endo: 'object',
|
|
230
247
|
wrapPrivateKey: 'boolean',
|
|
231
248
|
});
|
|
232
|
-
const { endo } =
|
|
249
|
+
const { endo } = extraOpts;
|
|
233
250
|
if (endo) {
|
|
234
251
|
// validateObject(endo, { beta: 'bigint', splitScalar: 'function' });
|
|
235
252
|
if (!Fp.is0(CURVE.a) || typeof endo.beta !== 'bigint' || !Array.isArray(endo.basises)) {
|
|
236
253
|
throw new Error('invalid endo: expected "beta": bigint and "basises": array');
|
|
237
254
|
}
|
|
238
255
|
}
|
|
256
|
+
const lengths = getWLengths(Fp, Fn);
|
|
239
257
|
function assertCompressionIsSupported() {
|
|
240
258
|
if (!Fp.isOdd)
|
|
241
259
|
throw new Error('compression is not supported: Field does not have .isOdd()');
|
|
@@ -244,7 +262,7 @@ export function weierstrassN(CURVE, curveOpts = {}) {
|
|
|
244
262
|
function pointToBytes(_c, point, isCompressed) {
|
|
245
263
|
const { x, y } = point.toAffine();
|
|
246
264
|
const bx = Fp.toBytes(x);
|
|
247
|
-
abool(
|
|
265
|
+
abool(isCompressed, 'isCompressed');
|
|
248
266
|
if (isCompressed) {
|
|
249
267
|
assertCompressionIsSupported();
|
|
250
268
|
const hasEvenY = !Fp.isOdd(y);
|
|
@@ -255,15 +273,13 @@ export function weierstrassN(CURVE, curveOpts = {}) {
|
|
|
255
273
|
}
|
|
256
274
|
}
|
|
257
275
|
function pointFromBytes(bytes) {
|
|
258
|
-
abytes(bytes);
|
|
259
|
-
const
|
|
260
|
-
const LC = L + 1; // length compressed, e.g. 33 for 32-byte field
|
|
261
|
-
const LU = 2 * L + 1; // length uncompressed, e.g. 65 for 32-byte field
|
|
276
|
+
abytes(bytes, undefined, 'Point');
|
|
277
|
+
const { public: comp, publicUncompressed: uncomp } = lengths; // e.g. for 32-byte: 33, 65
|
|
262
278
|
const length = bytes.length;
|
|
263
279
|
const head = bytes[0];
|
|
264
280
|
const tail = bytes.subarray(1);
|
|
265
281
|
// No actual validation is done here: use .assertValidity()
|
|
266
|
-
if (length ===
|
|
282
|
+
if (length === comp && (head === 0x02 || head === 0x03)) {
|
|
267
283
|
const x = Fp.fromBytes(tail);
|
|
268
284
|
if (!Fp.isValid(x))
|
|
269
285
|
throw new Error('bad point: is not on curve, wrong x');
|
|
@@ -283,21 +299,26 @@ export function weierstrassN(CURVE, curveOpts = {}) {
|
|
|
283
299
|
y = Fp.neg(y);
|
|
284
300
|
return { x, y };
|
|
285
301
|
}
|
|
286
|
-
else if (length ===
|
|
302
|
+
else if (length === uncomp && head === 0x04) {
|
|
287
303
|
// TODO: more checks
|
|
288
|
-
const
|
|
289
|
-
const
|
|
304
|
+
const L = Fp.BYTES;
|
|
305
|
+
const x = Fp.fromBytes(tail.subarray(0, L));
|
|
306
|
+
const y = Fp.fromBytes(tail.subarray(L, L * 2));
|
|
290
307
|
if (!isValidXY(x, y))
|
|
291
308
|
throw new Error('bad point: is not on curve');
|
|
292
309
|
return { x, y };
|
|
293
310
|
}
|
|
294
311
|
else {
|
|
295
|
-
throw new Error(`bad point: got length ${length}, expected compressed=${
|
|
312
|
+
throw new Error(`bad point: got length ${length}, expected compressed=${comp} or uncompressed=${uncomp}`);
|
|
296
313
|
}
|
|
297
314
|
}
|
|
298
|
-
const
|
|
299
|
-
const
|
|
300
|
-
|
|
315
|
+
const encodePoint = extraOpts.toBytes || pointToBytes;
|
|
316
|
+
const decodePoint = extraOpts.fromBytes || pointFromBytes;
|
|
317
|
+
function weierstrassEquation(x) {
|
|
318
|
+
const x2 = Fp.sqr(x); // x * x
|
|
319
|
+
const x3 = Fp.mul(x2, x); // x² * x
|
|
320
|
+
return Fp.add(Fp.add(x3, Fp.mul(x, CURVE.a)), CURVE.b); // x³ + a * x + b
|
|
321
|
+
}
|
|
301
322
|
// TODO: move top-level
|
|
302
323
|
/** Checks whether equation holds for given x, y: y² == x³ + ax + b */
|
|
303
324
|
function isValidXY(x, y) {
|
|
@@ -360,7 +381,7 @@ export function weierstrassN(CURVE, curveOpts = {}) {
|
|
|
360
381
|
// (0, 1, 0) aka ZERO is invalid in most contexts.
|
|
361
382
|
// In BLS, ZERO can be serialized, so we allow it.
|
|
362
383
|
// (0, 0, 0) is invalid representation of ZERO.
|
|
363
|
-
if (
|
|
384
|
+
if (extraOpts.allowInfinityPoint && !Fp.is0(p.Y))
|
|
364
385
|
return;
|
|
365
386
|
throw new Error('bad point: ZERO');
|
|
366
387
|
}
|
|
@@ -393,6 +414,9 @@ export function weierstrassN(CURVE, curveOpts = {}) {
|
|
|
393
414
|
this.Z = acoord('z', Z);
|
|
394
415
|
Object.freeze(this);
|
|
395
416
|
}
|
|
417
|
+
static CURVE() {
|
|
418
|
+
return CURVE;
|
|
419
|
+
}
|
|
396
420
|
/** Does NOT validate if the point is valid. Use `.assertValidity()`. */
|
|
397
421
|
static fromAffine(p) {
|
|
398
422
|
const { x, y } = p || {};
|
|
@@ -405,45 +429,19 @@ export function weierstrassN(CURVE, curveOpts = {}) {
|
|
|
405
429
|
return Point.ZERO;
|
|
406
430
|
return new Point(x, y, Fp.ONE);
|
|
407
431
|
}
|
|
408
|
-
get x() {
|
|
409
|
-
return this.toAffine().x;
|
|
410
|
-
}
|
|
411
|
-
get y() {
|
|
412
|
-
return this.toAffine().y;
|
|
413
|
-
}
|
|
414
|
-
// TODO: remove
|
|
415
|
-
get px() {
|
|
416
|
-
return this.X;
|
|
417
|
-
}
|
|
418
|
-
get py() {
|
|
419
|
-
return this.X;
|
|
420
|
-
}
|
|
421
|
-
get pz() {
|
|
422
|
-
return this.Z;
|
|
423
|
-
}
|
|
424
|
-
static normalizeZ(points) {
|
|
425
|
-
return normalizeZ(Point, points);
|
|
426
|
-
}
|
|
427
432
|
static fromBytes(bytes) {
|
|
428
|
-
abytes(bytes);
|
|
429
|
-
return Point.fromHex(bytes);
|
|
430
|
-
}
|
|
431
|
-
/** Converts hash string or Uint8Array to Point. */
|
|
432
|
-
static fromHex(hex) {
|
|
433
|
-
const P = Point.fromAffine(fromBytes(ensureBytes('pointHex', hex)));
|
|
433
|
+
const P = Point.fromAffine(decodePoint(abytes(bytes, undefined, 'point')));
|
|
434
434
|
P.assertValidity();
|
|
435
435
|
return P;
|
|
436
436
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
return Point.BASE.multiply(_normFnElement(Fn, privateKey));
|
|
437
|
+
static fromHex(hex) {
|
|
438
|
+
return Point.fromBytes(ensureBytes('pointHex', hex));
|
|
440
439
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
return pippenger(Point, Fn, points, scalars);
|
|
440
|
+
get x() {
|
|
441
|
+
return this.toAffine().x;
|
|
444
442
|
}
|
|
445
|
-
|
|
446
|
-
this.
|
|
443
|
+
get y() {
|
|
444
|
+
return this.toAffine().y;
|
|
447
445
|
}
|
|
448
446
|
/**
|
|
449
447
|
*
|
|
@@ -592,7 +590,7 @@ export function weierstrassN(CURVE, curveOpts = {}) {
|
|
|
592
590
|
* @returns New point
|
|
593
591
|
*/
|
|
594
592
|
multiply(scalar) {
|
|
595
|
-
const { endo } =
|
|
593
|
+
const { endo } = extraOpts;
|
|
596
594
|
if (!Fn.isValidNot0(scalar))
|
|
597
595
|
throw new Error('invalid scalar: out of range'); // 0 is invalid
|
|
598
596
|
let point, fake; // Fake point is used to const-time mult
|
|
@@ -619,7 +617,7 @@ export function weierstrassN(CURVE, curveOpts = {}) {
|
|
|
619
617
|
* an exposed secret key e.g. sig verification, which works over *public* keys.
|
|
620
618
|
*/
|
|
621
619
|
multiplyUnsafe(sc) {
|
|
622
|
-
const { endo } =
|
|
620
|
+
const { endo } = extraOpts;
|
|
623
621
|
const p = this;
|
|
624
622
|
if (!Fn.isValid(sc))
|
|
625
623
|
throw new Error('invalid scalar: out of range'); // 0 is valid
|
|
@@ -654,7 +652,7 @@ export function weierstrassN(CURVE, curveOpts = {}) {
|
|
|
654
652
|
* Always torsion-free for cofactor=1 curves.
|
|
655
653
|
*/
|
|
656
654
|
isTorsionFree() {
|
|
657
|
-
const { isTorsionFree } =
|
|
655
|
+
const { isTorsionFree } = extraOpts;
|
|
658
656
|
if (cofactor === _1n)
|
|
659
657
|
return true;
|
|
660
658
|
if (isTorsionFree)
|
|
@@ -662,7 +660,7 @@ export function weierstrassN(CURVE, curveOpts = {}) {
|
|
|
662
660
|
return wnaf.unsafe(this, CURVE_ORDER).is0();
|
|
663
661
|
}
|
|
664
662
|
clearCofactor() {
|
|
665
|
-
const { clearCofactor } =
|
|
663
|
+
const { clearCofactor } = extraOpts;
|
|
666
664
|
if (cofactor === _1n)
|
|
667
665
|
return this; // Fast-path
|
|
668
666
|
if (clearCofactor)
|
|
@@ -674,13 +672,9 @@ export function weierstrassN(CURVE, curveOpts = {}) {
|
|
|
674
672
|
return this.multiplyUnsafe(cofactor).is0();
|
|
675
673
|
}
|
|
676
674
|
toBytes(isCompressed = true) {
|
|
677
|
-
abool(
|
|
675
|
+
abool(isCompressed, 'isCompressed');
|
|
678
676
|
this.assertValidity();
|
|
679
|
-
return
|
|
680
|
-
}
|
|
681
|
-
/** @deprecated use `toBytes` */
|
|
682
|
-
toRawBytes(isCompressed = true) {
|
|
683
|
-
return this.toBytes(isCompressed);
|
|
677
|
+
return encodePoint(Point, this, isCompressed);
|
|
684
678
|
}
|
|
685
679
|
toHex(isCompressed = true) {
|
|
686
680
|
return bytesToHex(this.toBytes(isCompressed));
|
|
@@ -688,26 +682,44 @@ export function weierstrassN(CURVE, curveOpts = {}) {
|
|
|
688
682
|
toString() {
|
|
689
683
|
return `<Point ${this.is0() ? 'ZERO' : this.toHex()}>`;
|
|
690
684
|
}
|
|
685
|
+
// TODO: remove
|
|
686
|
+
get px() {
|
|
687
|
+
return this.X;
|
|
688
|
+
}
|
|
689
|
+
get py() {
|
|
690
|
+
return this.X;
|
|
691
|
+
}
|
|
692
|
+
get pz() {
|
|
693
|
+
return this.Z;
|
|
694
|
+
}
|
|
695
|
+
toRawBytes(isCompressed = true) {
|
|
696
|
+
return this.toBytes(isCompressed);
|
|
697
|
+
}
|
|
698
|
+
_setWindowSize(windowSize) {
|
|
699
|
+
this.precompute(windowSize);
|
|
700
|
+
}
|
|
701
|
+
static normalizeZ(points) {
|
|
702
|
+
return normalizeZ(Point, points);
|
|
703
|
+
}
|
|
704
|
+
static msm(points, scalars) {
|
|
705
|
+
return pippenger(Point, Fn, points, scalars);
|
|
706
|
+
}
|
|
707
|
+
static fromPrivateKey(privateKey) {
|
|
708
|
+
return Point.BASE.multiply(_normFnElement(Fn, privateKey));
|
|
709
|
+
}
|
|
691
710
|
}
|
|
692
711
|
// base / generator point
|
|
693
712
|
Point.BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE);
|
|
694
713
|
// zero / infinity / identity point
|
|
695
714
|
Point.ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO); // 0, 1, 0
|
|
696
|
-
//
|
|
715
|
+
// math field
|
|
697
716
|
Point.Fp = Fp;
|
|
717
|
+
// scalar field
|
|
698
718
|
Point.Fn = Fn;
|
|
699
719
|
const bits = Fn.BITS;
|
|
700
|
-
const wnaf = new wNAF(Point,
|
|
720
|
+
const wnaf = new wNAF(Point, extraOpts.endo ? Math.ceil(bits / 2) : bits);
|
|
701
721
|
return Point;
|
|
702
722
|
}
|
|
703
|
-
// _legacyWeierstrass
|
|
704
|
-
// TODO: remove
|
|
705
|
-
/** @deprecated use `weierstrassN` */
|
|
706
|
-
export function weierstrassPoints(c) {
|
|
707
|
-
const { CURVE, curveOpts } = _weierstrass_legacy_opts_to_new(c);
|
|
708
|
-
const Point = weierstrassN(CURVE, curveOpts);
|
|
709
|
-
return _weierstrass_new_output_to_legacy(c, Point);
|
|
710
|
-
}
|
|
711
723
|
// Points start with byte 0x02 when y is even; otherwise 0x03
|
|
712
724
|
function pprefix(hasEvenY) {
|
|
713
725
|
return Uint8Array.of(hasEvenY ? 0x02 : 0x03);
|
|
@@ -836,8 +848,122 @@ export function mapToCurveSimpleSWU(Fp, opts) {
|
|
|
836
848
|
return { x, y };
|
|
837
849
|
};
|
|
838
850
|
}
|
|
851
|
+
function getWLengths(Fp, Fn) {
|
|
852
|
+
return {
|
|
853
|
+
secret: Fn.BYTES,
|
|
854
|
+
public: 1 + Fp.BYTES,
|
|
855
|
+
publicUncompressed: 1 + 2 * Fp.BYTES,
|
|
856
|
+
publicKeyHasPrefix: true,
|
|
857
|
+
signature: 2 * Fn.BYTES,
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Sometimes users only need getPublicKey, getSharedSecret, and secret key handling.
|
|
862
|
+
* This helper ensures no signature functionality is present. Less code, smaller bundle size.
|
|
863
|
+
*/
|
|
864
|
+
export function ecdh(Point, ecdhOpts = {}) {
|
|
865
|
+
const { Fn } = Point;
|
|
866
|
+
const randomBytes_ = ecdhOpts.randomBytes || wcRandomBytes;
|
|
867
|
+
const lengths = Object.assign(getWLengths(Point.Fp, Fn), { seed: getMinHashLength(Fn.ORDER) });
|
|
868
|
+
function isValidSecretKey(secretKey) {
|
|
869
|
+
try {
|
|
870
|
+
return !!_normFnElement(Fn, secretKey);
|
|
871
|
+
}
|
|
872
|
+
catch (error) {
|
|
873
|
+
return false;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
function isValidPublicKey(publicKey, isCompressed) {
|
|
877
|
+
const { public: comp, publicUncompressed } = lengths;
|
|
878
|
+
try {
|
|
879
|
+
const l = publicKey.length;
|
|
880
|
+
if (isCompressed === true && l !== comp)
|
|
881
|
+
return false;
|
|
882
|
+
if (isCompressed === false && l !== publicUncompressed)
|
|
883
|
+
return false;
|
|
884
|
+
return !!Point.fromBytes(publicKey);
|
|
885
|
+
}
|
|
886
|
+
catch (error) {
|
|
887
|
+
return false;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Produces cryptographically secure secret key from random of size
|
|
892
|
+
* (groupLen + ceil(groupLen / 2)) with modulo bias being negligible.
|
|
893
|
+
*/
|
|
894
|
+
function randomSecretKey(seed = randomBytes_(lengths.seed)) {
|
|
895
|
+
return mapHashToField(abytes(seed, lengths.seed, 'seed'), Fn.ORDER);
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Computes public key for a secret key. Checks for validity of the secret key.
|
|
899
|
+
* @param isCompressed whether to return compact (default), or full key
|
|
900
|
+
* @returns Public key, full when isCompressed=false; short when isCompressed=true
|
|
901
|
+
*/
|
|
902
|
+
function getPublicKey(secretKey, isCompressed = true) {
|
|
903
|
+
return Point.BASE.multiply(_normFnElement(Fn, secretKey)).toBytes(isCompressed);
|
|
904
|
+
}
|
|
905
|
+
function keygen(seed) {
|
|
906
|
+
const secretKey = randomSecretKey(seed);
|
|
907
|
+
return { secretKey, publicKey: getPublicKey(secretKey) };
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Quick and dirty check for item being public key. Does not validate hex, or being on-curve.
|
|
911
|
+
*/
|
|
912
|
+
function isProbPub(item) {
|
|
913
|
+
if (typeof item === 'bigint')
|
|
914
|
+
return false;
|
|
915
|
+
if (item instanceof Point)
|
|
916
|
+
return true;
|
|
917
|
+
if (Fn.allowedLengths || lengths.secret === lengths.public)
|
|
918
|
+
return undefined;
|
|
919
|
+
const l = ensureBytes('key', item).length;
|
|
920
|
+
return l === lengths.public || l === lengths.publicUncompressed;
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* ECDH (Elliptic Curve Diffie Hellman).
|
|
924
|
+
* Computes shared public key from secret key A and public key B.
|
|
925
|
+
* Checks: 1) secret key validity 2) shared key is on-curve.
|
|
926
|
+
* Does NOT hash the result.
|
|
927
|
+
* @param isCompressed whether to return compact (default), or full key
|
|
928
|
+
* @returns shared public key
|
|
929
|
+
*/
|
|
930
|
+
function getSharedSecret(secretKeyA, publicKeyB, isCompressed = true) {
|
|
931
|
+
if (isProbPub(secretKeyA) === true)
|
|
932
|
+
throw new Error('first arg must be private key');
|
|
933
|
+
if (isProbPub(publicKeyB) === false)
|
|
934
|
+
throw new Error('second arg must be public key');
|
|
935
|
+
const s = _normFnElement(Fn, secretKeyA);
|
|
936
|
+
const b = Point.fromHex(publicKeyB); // checks for being on-curve
|
|
937
|
+
return b.multiply(s).toBytes(isCompressed);
|
|
938
|
+
}
|
|
939
|
+
const utils = {
|
|
940
|
+
isValidSecretKey,
|
|
941
|
+
isValidPublicKey,
|
|
942
|
+
randomSecretKey,
|
|
943
|
+
// TODO: remove
|
|
944
|
+
isValidPrivateKey: isValidSecretKey,
|
|
945
|
+
randomPrivateKey: randomSecretKey,
|
|
946
|
+
normPrivateKeyToScalar: (key) => _normFnElement(Fn, key),
|
|
947
|
+
precompute(windowSize = 8, point = Point.BASE) {
|
|
948
|
+
return point.precompute(windowSize, false);
|
|
949
|
+
},
|
|
950
|
+
};
|
|
951
|
+
return Object.freeze({ getPublicKey, getSharedSecret, keygen, Point, utils, lengths });
|
|
952
|
+
}
|
|
839
953
|
/**
|
|
840
|
-
* Creates ECDSA for given elliptic curve Point and hash function.
|
|
954
|
+
* Creates ECDSA signing interface for given elliptic curve `Point` and `hash` function.
|
|
955
|
+
* We need `hash` for 2 features:
|
|
956
|
+
* 1. Message prehash-ing. NOT used if `sign` / `verify` are called with `prehash: false`
|
|
957
|
+
* 2. k generation in `sign`, using HMAC-drbg(hash)
|
|
958
|
+
*
|
|
959
|
+
* ECDSAOpts are only rarely needed.
|
|
960
|
+
*
|
|
961
|
+
* @example
|
|
962
|
+
* ```js
|
|
963
|
+
* const p256_Point = weierstrass(...);
|
|
964
|
+
* const p256_sha256 = ecdsa(p256_Point, sha256);
|
|
965
|
+
* const p256_sha224 = ecdsa(p256_Point, sha224);
|
|
966
|
+
* ```
|
|
841
967
|
*/
|
|
842
968
|
export function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
843
969
|
ahash(hash);
|
|
@@ -848,19 +974,19 @@ export function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
848
974
|
bits2int: 'function',
|
|
849
975
|
bits2int_modN: 'function',
|
|
850
976
|
});
|
|
851
|
-
const randomBytes_ = ecdsaOpts.randomBytes ||
|
|
977
|
+
const randomBytes_ = ecdsaOpts.randomBytes || wcRandomBytes;
|
|
852
978
|
const hmac_ = ecdsaOpts.hmac ||
|
|
853
|
-
((key, ...msgs) =>
|
|
979
|
+
((key, ...msgs) => nobleHmac(hash, key, concatBytes(...msgs)));
|
|
854
980
|
const { Fp, Fn } = Point;
|
|
855
981
|
const { ORDER: CURVE_ORDER, BITS: fnBits } = Fn;
|
|
856
|
-
const
|
|
857
|
-
const
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
seed: seedLen,
|
|
982
|
+
const { keygen, getPublicKey, getSharedSecret, utils, lengths } = ecdh(Point, ecdsaOpts);
|
|
983
|
+
const defaultSigOpts = {
|
|
984
|
+
prehash: false,
|
|
985
|
+
lowS: typeof ecdsaOpts.lowS === 'boolean' ? ecdsaOpts.lowS : false,
|
|
986
|
+
format: undefined, //'compact' as ECDSASigFormat,
|
|
987
|
+
extraEntropy: false,
|
|
863
988
|
};
|
|
989
|
+
const defaultSigOpts_format = 'compact';
|
|
864
990
|
function isBiggerThanHalfOrder(number) {
|
|
865
991
|
const HALF = CURVE_ORDER >> _1n;
|
|
866
992
|
return number > HALF;
|
|
@@ -868,37 +994,41 @@ export function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
868
994
|
function normalizeS(s) {
|
|
869
995
|
return isBiggerThanHalfOrder(s) ? Fn.neg(s) : s;
|
|
870
996
|
}
|
|
871
|
-
function
|
|
997
|
+
function validateRS(title, num) {
|
|
872
998
|
if (!Fn.isValidNot0(num))
|
|
873
|
-
throw new Error(`invalid signature ${title}: out of range 1..
|
|
999
|
+
throw new Error(`invalid signature ${title}: out of range 1..Point.Fn.ORDER`);
|
|
1000
|
+
return num;
|
|
874
1001
|
}
|
|
875
1002
|
/**
|
|
876
|
-
* ECDSA signature with its (r, s) properties. Supports
|
|
1003
|
+
* ECDSA signature with its (r, s) properties. Supports compact, recovered & DER representations.
|
|
877
1004
|
*/
|
|
878
1005
|
class Signature {
|
|
879
1006
|
constructor(r, s, recovery) {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
this.r = r;
|
|
883
|
-
this.s = s;
|
|
1007
|
+
this.r = validateRS('r', r); // r in [1..N-1];
|
|
1008
|
+
this.s = validateRS('s', s); // s in [1..N-1];
|
|
884
1009
|
if (recovery != null)
|
|
885
1010
|
this.recovery = recovery;
|
|
886
1011
|
Object.freeze(this);
|
|
887
1012
|
}
|
|
888
|
-
static fromBytes(bytes, format =
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
const r = bytes.subarray(0, L);
|
|
893
|
-
const s = bytes.subarray(L, L * 2);
|
|
894
|
-
return new Signature(Fn.fromBytes(r), Fn.fromBytes(s));
|
|
895
|
-
}
|
|
1013
|
+
static fromBytes(bytes, format = defaultSigOpts_format) {
|
|
1014
|
+
validateSigFormat(format);
|
|
1015
|
+
const size = lengths.signature;
|
|
1016
|
+
let recid;
|
|
896
1017
|
if (format === 'der') {
|
|
897
|
-
abytes(bytes);
|
|
898
|
-
const { r, s } = DER.toSig(bytes);
|
|
1018
|
+
const { r, s } = DER.toSig(abytes(bytes));
|
|
899
1019
|
return new Signature(r, s);
|
|
900
1020
|
}
|
|
901
|
-
|
|
1021
|
+
if (format === 'recovered') {
|
|
1022
|
+
abytes(bytes, size + 1);
|
|
1023
|
+
recid = bytes[0];
|
|
1024
|
+
format = 'compact';
|
|
1025
|
+
bytes = bytes.subarray(1);
|
|
1026
|
+
}
|
|
1027
|
+
abytes(bytes, size);
|
|
1028
|
+
const L = size / 2;
|
|
1029
|
+
const r = bytes.subarray(0, L);
|
|
1030
|
+
const s = bytes.subarray(L, L * 2);
|
|
1031
|
+
return new Signature(Fn.fromBytes(r), Fn.fromBytes(s), recid);
|
|
902
1032
|
}
|
|
903
1033
|
static fromHex(hex, format) {
|
|
904
1034
|
return this.fromBytes(hexToBytes(hex), format);
|
|
@@ -906,7 +1036,6 @@ export function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
906
1036
|
addRecoveryBit(recovery) {
|
|
907
1037
|
return new Signature(this.r, this.s, recovery);
|
|
908
1038
|
}
|
|
909
|
-
// ProjPointType<bigint>
|
|
910
1039
|
recoverPublicKey(msgHash) {
|
|
911
1040
|
const FIELD_ORDER = Fp.ORDER;
|
|
912
1041
|
const { r, s, recovery: rec } = this;
|
|
@@ -927,7 +1056,7 @@ export function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
927
1056
|
if (!Fp.isValid(radj))
|
|
928
1057
|
throw new Error('recovery id 2 or 3 invalid');
|
|
929
1058
|
const x = Fp.toBytes(radj);
|
|
930
|
-
const R = Point.
|
|
1059
|
+
const R = Point.fromBytes(concatBytes(pprefix((rec & 1) === 0), x));
|
|
931
1060
|
const ir = Fn.inv(radj); // r^-1
|
|
932
1061
|
const h = bits2int_modN(ensureBytes('msgHash', msgHash)); // Truncate hash
|
|
933
1062
|
const u1 = Fn.create(-h * ir); // -hr^-1
|
|
@@ -943,15 +1072,18 @@ export function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
943
1072
|
hasHighS() {
|
|
944
1073
|
return isBiggerThanHalfOrder(this.s);
|
|
945
1074
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
}
|
|
949
|
-
toBytes(format = 'compact') {
|
|
950
|
-
if (format === 'compact')
|
|
951
|
-
return concatBytes(Fn.toBytes(this.r), Fn.toBytes(this.s));
|
|
1075
|
+
toBytes(format = defaultSigOpts_format) {
|
|
1076
|
+
validateSigFormat(format);
|
|
952
1077
|
if (format === 'der')
|
|
953
1078
|
return hexToBytes(DER.hexFromSig(this));
|
|
954
|
-
|
|
1079
|
+
const r = Fn.toBytes(this.r);
|
|
1080
|
+
const s = Fn.toBytes(this.s);
|
|
1081
|
+
if (format === 'recovered') {
|
|
1082
|
+
if (this.recovery == null)
|
|
1083
|
+
throw new Error('recovery bit must be present');
|
|
1084
|
+
return concatBytes(Uint8Array.of(this.recovery), r, s);
|
|
1085
|
+
}
|
|
1086
|
+
return concatBytes(r, s);
|
|
955
1087
|
}
|
|
956
1088
|
toHex(format) {
|
|
957
1089
|
return bytesToHex(this.toBytes(format));
|
|
@@ -964,6 +1096,9 @@ export function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
964
1096
|
static fromDER(hex) {
|
|
965
1097
|
return Signature.fromBytes(ensureBytes('sig', hex), 'der');
|
|
966
1098
|
}
|
|
1099
|
+
normalizeS() {
|
|
1100
|
+
return this.hasHighS() ? new Signature(this.r, Fn.neg(this.s), this.recovery) : this;
|
|
1101
|
+
}
|
|
967
1102
|
toDERRawBytes() {
|
|
968
1103
|
return this.toBytes('der');
|
|
969
1104
|
}
|
|
@@ -977,86 +1112,6 @@ export function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
977
1112
|
return bytesToHex(this.toBytes('compact'));
|
|
978
1113
|
}
|
|
979
1114
|
}
|
|
980
|
-
function isValidSecretKey(privateKey) {
|
|
981
|
-
try {
|
|
982
|
-
return !!_normFnElement(Fn, privateKey);
|
|
983
|
-
}
|
|
984
|
-
catch (error) {
|
|
985
|
-
return false;
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
function isValidPublicKey(publicKey, isCompressed) {
|
|
989
|
-
try {
|
|
990
|
-
const l = publicKey.length;
|
|
991
|
-
if (isCompressed === true && l !== lengths.public)
|
|
992
|
-
return false;
|
|
993
|
-
if (isCompressed === false && l !== lengths.publicUncompressed)
|
|
994
|
-
return false;
|
|
995
|
-
return !!Point.fromBytes(publicKey);
|
|
996
|
-
}
|
|
997
|
-
catch (error) {
|
|
998
|
-
return false;
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
/**
|
|
1002
|
-
* Produces cryptographically secure secret key from random of size
|
|
1003
|
-
* (groupLen + ceil(groupLen / 2)) with modulo bias being negligible.
|
|
1004
|
-
*/
|
|
1005
|
-
function randomSecretKey(seed = randomBytes_(seedLen)) {
|
|
1006
|
-
return mapHashToField(seed, CURVE_ORDER);
|
|
1007
|
-
}
|
|
1008
|
-
const utils = {
|
|
1009
|
-
isValidSecretKey,
|
|
1010
|
-
isValidPublicKey,
|
|
1011
|
-
randomSecretKey,
|
|
1012
|
-
// TODO: remove
|
|
1013
|
-
isValidPrivateKey: isValidSecretKey,
|
|
1014
|
-
randomPrivateKey: randomSecretKey,
|
|
1015
|
-
normPrivateKeyToScalar: (key) => _normFnElement(Fn, key),
|
|
1016
|
-
precompute(windowSize = 8, point = Point.BASE) {
|
|
1017
|
-
return point.precompute(windowSize, false);
|
|
1018
|
-
},
|
|
1019
|
-
};
|
|
1020
|
-
/**
|
|
1021
|
-
* Computes public key for a secret key. Checks for validity of the secret key.
|
|
1022
|
-
* @param isCompressed whether to return compact (default), or full key
|
|
1023
|
-
* @returns Public key, full when isCompressed=false; short when isCompressed=true
|
|
1024
|
-
*/
|
|
1025
|
-
function getPublicKey(secretKey, isCompressed = true) {
|
|
1026
|
-
return Point.BASE.multiply(_normFnElement(Fn, secretKey)).toBytes(isCompressed);
|
|
1027
|
-
}
|
|
1028
|
-
/**
|
|
1029
|
-
* Quick and dirty check for item being public key. Does not validate hex, or being on-curve.
|
|
1030
|
-
*/
|
|
1031
|
-
function isProbPub(item) {
|
|
1032
|
-
// TODO: remove
|
|
1033
|
-
if (typeof item === 'bigint')
|
|
1034
|
-
return false;
|
|
1035
|
-
// TODO: remove
|
|
1036
|
-
if (item instanceof Point)
|
|
1037
|
-
return true;
|
|
1038
|
-
if (Fn.allowedLengths || lengths.secret === lengths.public)
|
|
1039
|
-
return undefined;
|
|
1040
|
-
const l = ensureBytes('key', item).length;
|
|
1041
|
-
return l === lengths.public || l === lengths.publicUncompressed;
|
|
1042
|
-
}
|
|
1043
|
-
/**
|
|
1044
|
-
* ECDH (Elliptic Curve Diffie Hellman).
|
|
1045
|
-
* Computes shared public key from secret key A and public key B.
|
|
1046
|
-
* Checks: 1) secret key validity 2) shared key is on-curve.
|
|
1047
|
-
* Does NOT hash the result.
|
|
1048
|
-
* @param isCompressed whether to return compact (default), or full key
|
|
1049
|
-
* @returns shared public key
|
|
1050
|
-
*/
|
|
1051
|
-
function getSharedSecret(secretKeyA, publicKeyB, isCompressed = true) {
|
|
1052
|
-
if (isProbPub(secretKeyA) === true)
|
|
1053
|
-
throw new Error('first arg must be private key');
|
|
1054
|
-
if (isProbPub(publicKeyB) === false)
|
|
1055
|
-
throw new Error('second arg must be public key');
|
|
1056
|
-
const s = _normFnElement(Fn, secretKeyA);
|
|
1057
|
-
const b = Point.fromHex(publicKeyB); // checks for being on-curve
|
|
1058
|
-
return b.multiply(s).toBytes(isCompressed);
|
|
1059
|
-
}
|
|
1060
1115
|
// RFC6979: ensure ECDSA msg is X bytes and < N. RFC suggests optional truncating via bits2octets.
|
|
1061
1116
|
// FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits, which matches bits2int.
|
|
1062
1117
|
// bits2int can produce res>N, we can do mod(res, N) since the bitLen is the same.
|
|
@@ -1086,31 +1141,33 @@ export function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
1086
1141
|
aInRange('num < 2^' + fnBits, num, _0n, ORDER_MASK);
|
|
1087
1142
|
return Fn.toBytes(num);
|
|
1088
1143
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1144
|
+
/**
|
|
1145
|
+
* Steps A, D of RFC6979 3.2.
|
|
1146
|
+
* Creates RFC6979 seed; converts msg/privKey to numbers.
|
|
1147
|
+
* Used only in sign, not in verify.
|
|
1148
|
+
*
|
|
1149
|
+
* Warning: we cannot assume here that message has same amount of bytes as curve order,
|
|
1150
|
+
* this will be invalid at least for P521. Also it can be bigger for P224 + SHA256.
|
|
1151
|
+
*/
|
|
1152
|
+
function prepSig(message, privateKey, opts) {
|
|
1095
1153
|
if (['recovered', 'canonical'].some((k) => k in opts))
|
|
1096
1154
|
throw new Error('sign() legacy options not supported');
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
msgHash = ensureBytes('msgHash', msgHash);
|
|
1101
|
-
validateSigVerOpts(opts);
|
|
1155
|
+
const { lowS, prehash, extraEntropy } = validateSigOpts(opts, defaultSigOpts);
|
|
1156
|
+
// RFC6979 3.2: we skip step A, because we already provide hash
|
|
1157
|
+
message = abytes(message, undefined, 'message');
|
|
1102
1158
|
if (prehash)
|
|
1103
|
-
|
|
1159
|
+
message = abytes(hash(message), undefined, 'prehashed message');
|
|
1104
1160
|
// We can't later call bits2octets, since nested bits2int is broken for curves
|
|
1105
1161
|
// with fnBits % 8 !== 0. Because of that, we unwrap it here as int2octets call.
|
|
1106
1162
|
// const bits2octets = (bits) => int2octets(bits2int_modN(bits))
|
|
1107
|
-
const h1int = bits2int_modN(
|
|
1163
|
+
const h1int = bits2int_modN(message);
|
|
1108
1164
|
const d = _normFnElement(Fn, privateKey); // validate secret key, convert to bigint
|
|
1109
1165
|
const seedArgs = [int2octets(d), int2octets(h1int)];
|
|
1110
1166
|
// extraEntropy. RFC6979 3.6: additional k' (optional).
|
|
1111
|
-
if (
|
|
1167
|
+
if (extraEntropy != null && extraEntropy !== false) {
|
|
1112
1168
|
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
|
|
1113
|
-
|
|
1169
|
+
// gen random bytes OR pass as-is
|
|
1170
|
+
const e = extraEntropy === true ? randomBytes_(lengths.secret) : extraEntropy;
|
|
1114
1171
|
seedArgs.push(ensureBytes('extraEntropy', e)); // check for being bytes
|
|
1115
1172
|
}
|
|
1116
1173
|
const seed = concatBytes(...seedArgs); // Step D of RFC6979 3.2
|
|
@@ -1126,7 +1183,7 @@ export function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
1126
1183
|
function k2sig(kBytes) {
|
|
1127
1184
|
// RFC 6979 Section 3.2, step 3: k = bits2int(T)
|
|
1128
1185
|
// Important: all mod() calls here must be done over N
|
|
1129
|
-
const k = bits2int(kBytes); //
|
|
1186
|
+
const k = bits2int(kBytes); // mod n, not mod p
|
|
1130
1187
|
if (!Fn.isValidNot0(k))
|
|
1131
1188
|
return; // Valid scalars (including k) must be in 1..N-1
|
|
1132
1189
|
const ik = Fn.inv(k); // k^-1 mod n
|
|
@@ -1147,49 +1204,48 @@ export function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
1147
1204
|
}
|
|
1148
1205
|
return { seed, k2sig };
|
|
1149
1206
|
}
|
|
1150
|
-
const defaultSigOpts = { lowS: ecdsaOpts.lowS, prehash: false };
|
|
1151
|
-
const defaultVerOpts = { lowS: ecdsaOpts.lowS, prehash: false };
|
|
1152
1207
|
/**
|
|
1153
1208
|
* Signs message hash with a secret key.
|
|
1209
|
+
*
|
|
1154
1210
|
* ```
|
|
1155
|
-
* sign(m, d
|
|
1211
|
+
* sign(m, d) where
|
|
1212
|
+
* k = rfc6979_hmac_drbg(m, d)
|
|
1156
1213
|
* (x, y) = G × k
|
|
1157
1214
|
* r = x mod n
|
|
1158
|
-
* s = (m + dr)/k mod n
|
|
1215
|
+
* s = (m + dr) / k mod n
|
|
1159
1216
|
* ```
|
|
1160
1217
|
*/
|
|
1161
|
-
function sign(
|
|
1162
|
-
|
|
1218
|
+
function sign(message, secretKey, opts = {}) {
|
|
1219
|
+
message = ensureBytes('message', message);
|
|
1220
|
+
const { seed, k2sig } = prepSig(message, secretKey, opts); // Steps A, D of RFC6979 3.2.
|
|
1163
1221
|
const drbg = createHmacDrbg(hash.outputLen, Fn.BYTES, hmac_);
|
|
1164
|
-
|
|
1222
|
+
const sig = drbg(seed, k2sig); // Steps B, C, D, E, F, G
|
|
1223
|
+
return sig;
|
|
1165
1224
|
}
|
|
1166
1225
|
// Enable precomputes. Slows down first publicKey computation by 20ms.
|
|
1167
1226
|
Point.BASE.precompute(8);
|
|
1168
1227
|
/**
|
|
1169
|
-
* Verifies a signature against message
|
|
1170
|
-
* Rejects lowS signatures by default:
|
|
1171
|
-
*
|
|
1228
|
+
* Verifies a signature against message and public key.
|
|
1229
|
+
* Rejects lowS signatures by default: see {@link ECDSAVerifyOpts}.
|
|
1230
|
+
* Implements section 4.1.4 from https://www.secg.org/sec1-v2.pdf:
|
|
1172
1231
|
*
|
|
1173
1232
|
* ```
|
|
1174
1233
|
* verify(r, s, h, P) where
|
|
1175
|
-
*
|
|
1176
|
-
*
|
|
1177
|
-
* R =
|
|
1234
|
+
* u1 = hs^-1 mod n
|
|
1235
|
+
* u2 = rs^-1 mod n
|
|
1236
|
+
* R = u1⋅G + u2⋅P
|
|
1178
1237
|
* mod(R.x, n) == r
|
|
1179
1238
|
* ```
|
|
1180
1239
|
*/
|
|
1181
|
-
function verify(signature,
|
|
1240
|
+
function verify(signature, message, publicKey, opts = {}) {
|
|
1182
1241
|
const sg = signature;
|
|
1183
|
-
|
|
1242
|
+
message = ensureBytes('msgHash', message);
|
|
1184
1243
|
publicKey = ensureBytes('publicKey', publicKey);
|
|
1185
|
-
|
|
1186
|
-
validateSigVerOpts(opts);
|
|
1187
|
-
const { lowS, prehash, format } = opts;
|
|
1188
|
-
// TODO: remove
|
|
1189
|
-
if ('strict' in opts)
|
|
1190
|
-
throw new Error('options.strict was renamed to lowS');
|
|
1244
|
+
const { lowS, prehash, format } = validateSigOpts(opts, defaultSigOpts);
|
|
1191
1245
|
let _sig = undefined;
|
|
1192
1246
|
let P;
|
|
1247
|
+
if ('strict' in opts)
|
|
1248
|
+
throw new Error('options.strict was renamed to lowS');
|
|
1193
1249
|
if (format === undefined) {
|
|
1194
1250
|
// Try to deduce format
|
|
1195
1251
|
const isHex = typeof sg === 'string' || isBytes(sg);
|
|
@@ -1230,11 +1286,6 @@ export function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
1230
1286
|
throw new Error('"der" / "compact" format expects Uint8Array signature');
|
|
1231
1287
|
_sig = Signature.fromBytes(ensureBytes('sig', sg), format);
|
|
1232
1288
|
}
|
|
1233
|
-
else if (format === 'js') {
|
|
1234
|
-
if (!(sg instanceof Signature))
|
|
1235
|
-
throw new Error('"js" format expects Signature instance');
|
|
1236
|
-
_sig = sg;
|
|
1237
|
-
}
|
|
1238
1289
|
else {
|
|
1239
1290
|
throw new Error('format must be "compact", "der" or "js"');
|
|
1240
1291
|
}
|
|
@@ -1247,13 +1298,13 @@ export function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
1247
1298
|
return false;
|
|
1248
1299
|
// todo: optional.hash => hash
|
|
1249
1300
|
if (prehash)
|
|
1250
|
-
|
|
1301
|
+
message = hash(message);
|
|
1251
1302
|
const { r, s } = _sig;
|
|
1252
|
-
const h = bits2int_modN(
|
|
1253
|
-
const is = Fn.inv(s); // s^-1
|
|
1303
|
+
const h = bits2int_modN(message); // mod n, not mod p
|
|
1304
|
+
const is = Fn.inv(s); // s^-1 mod n
|
|
1254
1305
|
const u1 = Fn.create(h * is); // u1 = hs^-1 mod n
|
|
1255
1306
|
const u2 = Fn.create(r * is); // u2 = rs^-1 mod n
|
|
1256
|
-
const R = Point.BASE.multiplyUnsafe(u1).add(P.multiplyUnsafe(u2));
|
|
1307
|
+
const R = Point.BASE.multiplyUnsafe(u1).add(P.multiplyUnsafe(u2)); // u1⋅G + u2⋅P
|
|
1257
1308
|
if (R.is0())
|
|
1258
1309
|
return false;
|
|
1259
1310
|
const v = Fn.create(R.x); // v = r.x mod n
|
|
@@ -1263,23 +1314,34 @@ export function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
1263
1314
|
return false;
|
|
1264
1315
|
}
|
|
1265
1316
|
}
|
|
1266
|
-
function
|
|
1267
|
-
const
|
|
1268
|
-
|
|
1317
|
+
function recoverPublicKey(signature, message, opts = {}) {
|
|
1318
|
+
const prehash = opts.prehash !== undefined ? opts.prehash : defaultSigOpts.prehash;
|
|
1319
|
+
abool(prehash, 'prehash');
|
|
1320
|
+
message = abytes(message, undefined, 'message');
|
|
1321
|
+
if (prehash)
|
|
1322
|
+
message = abytes(hash(message), undefined, 'prehashed message');
|
|
1323
|
+
return Signature.fromBytes(signature, 'recovered').recoverPublicKey(message).toBytes();
|
|
1269
1324
|
}
|
|
1270
1325
|
return Object.freeze({
|
|
1271
1326
|
keygen,
|
|
1272
1327
|
getPublicKey,
|
|
1273
|
-
sign,
|
|
1274
|
-
verify,
|
|
1275
1328
|
getSharedSecret,
|
|
1276
1329
|
utils,
|
|
1330
|
+
lengths,
|
|
1277
1331
|
Point,
|
|
1332
|
+
sign,
|
|
1333
|
+
verify,
|
|
1334
|
+
recoverPublicKey,
|
|
1278
1335
|
Signature,
|
|
1279
|
-
|
|
1336
|
+
hash,
|
|
1280
1337
|
});
|
|
1281
1338
|
}
|
|
1282
|
-
|
|
1339
|
+
/** @deprecated use `weierstrass` in newer releases */
|
|
1340
|
+
export function weierstrassPoints(c) {
|
|
1341
|
+
const { CURVE, curveOpts } = _weierstrass_legacy_opts_to_new(c);
|
|
1342
|
+
const Point = weierstrassN(CURVE, curveOpts);
|
|
1343
|
+
return _weierstrass_new_output_to_legacy(c, Point);
|
|
1344
|
+
}
|
|
1283
1345
|
function _weierstrass_legacy_opts_to_new(c) {
|
|
1284
1346
|
const CURVE = {
|
|
1285
1347
|
a: c.a,
|
|
@@ -1297,7 +1359,7 @@ function _weierstrass_legacy_opts_to_new(c) {
|
|
|
1297
1359
|
const Fn = Field(CURVE.n, {
|
|
1298
1360
|
BITS: c.nBitLength,
|
|
1299
1361
|
allowedLengths: allowedLengths,
|
|
1300
|
-
|
|
1362
|
+
modFromBytes: c.wrapPrivateKey,
|
|
1301
1363
|
});
|
|
1302
1364
|
const curveOpts = {
|
|
1303
1365
|
Fp,
|
|
@@ -1322,10 +1384,20 @@ function _ecdsa_legacy_opts_to_new(c) {
|
|
|
1322
1384
|
};
|
|
1323
1385
|
return { CURVE, curveOpts, hash: c.hash, ecdsaOpts };
|
|
1324
1386
|
}
|
|
1325
|
-
|
|
1387
|
+
export function _legacyHelperEquat(Fp, a, b) {
|
|
1388
|
+
/**
|
|
1389
|
+
* y² = x³ + ax + b: Short weierstrass curve formula. Takes x, returns y².
|
|
1390
|
+
* @returns y²
|
|
1391
|
+
*/
|
|
1392
|
+
function weierstrassEquation(x) {
|
|
1393
|
+
const x2 = Fp.sqr(x); // x * x
|
|
1394
|
+
const x3 = Fp.mul(x2, x); // x² * x
|
|
1395
|
+
return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x³ + a * x + b
|
|
1396
|
+
}
|
|
1397
|
+
return weierstrassEquation;
|
|
1398
|
+
}
|
|
1326
1399
|
function _weierstrass_new_output_to_legacy(c, Point) {
|
|
1327
1400
|
const { Fp, Fn } = Point;
|
|
1328
|
-
// TODO: remove
|
|
1329
1401
|
function isWithinCurveOrder(num) {
|
|
1330
1402
|
return inRange(num, _1n, Fn.ORDER);
|
|
1331
1403
|
}
|
|
@@ -1339,11 +1411,11 @@ function _weierstrass_new_output_to_legacy(c, Point) {
|
|
|
1339
1411
|
isWithinCurveOrder,
|
|
1340
1412
|
});
|
|
1341
1413
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
return Object.assign({},
|
|
1345
|
-
ProjectivePoint:
|
|
1346
|
-
CURVE: c,
|
|
1414
|
+
function _ecdsa_new_output_to_legacy(c, _ecdsa) {
|
|
1415
|
+
const Point = _ecdsa.Point;
|
|
1416
|
+
return Object.assign({}, _ecdsa, {
|
|
1417
|
+
ProjectivePoint: Point,
|
|
1418
|
+
CURVE: Object.assign({}, c, nLength(Point.Fn.ORDER, Point.Fn.BITS)),
|
|
1347
1419
|
});
|
|
1348
1420
|
}
|
|
1349
1421
|
// _ecdsa_legacy
|