@noble/curves 1.9.0 → 1.9.2
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 +78 -34
- package/_shortw_utils.d.ts +7 -5
- package/_shortw_utils.d.ts.map +1 -1
- package/_shortw_utils.js +2 -8
- package/_shortw_utils.js.map +1 -1
- package/abstract/bls.d.ts +60 -24
- package/abstract/bls.d.ts.map +1 -1
- package/abstract/bls.js +158 -109
- package/abstract/bls.js.map +1 -1
- package/abstract/curve.d.ts +44 -9
- package/abstract/curve.d.ts.map +1 -1
- package/abstract/curve.js +99 -11
- package/abstract/curve.js.map +1 -1
- package/abstract/edwards.d.ts +112 -25
- package/abstract/edwards.d.ts.map +1 -1
- package/abstract/edwards.js +141 -92
- package/abstract/edwards.js.map +1 -1
- package/abstract/fft.d.ts +122 -0
- package/abstract/fft.d.ts.map +1 -0
- package/abstract/fft.js +438 -0
- package/abstract/fft.js.map +1 -0
- package/abstract/hash-to-curve.d.ts +25 -11
- package/abstract/hash-to-curve.d.ts.map +1 -1
- package/abstract/hash-to-curve.js +17 -14
- package/abstract/hash-to-curve.js.map +1 -1
- package/abstract/modular.d.ts +28 -17
- package/abstract/modular.d.ts.map +1 -1
- package/abstract/modular.js +156 -139
- package/abstract/modular.js.map +1 -1
- package/abstract/montgomery.d.ts +3 -8
- package/abstract/montgomery.d.ts.map +1 -1
- package/abstract/montgomery.js +73 -93
- package/abstract/montgomery.js.map +1 -1
- package/abstract/poseidon.d.ts +5 -13
- package/abstract/poseidon.d.ts.map +1 -1
- package/abstract/poseidon.js +12 -7
- package/abstract/poseidon.js.map +1 -1
- package/abstract/tower.d.ts +20 -46
- package/abstract/tower.d.ts.map +1 -1
- package/abstract/tower.js +10 -4
- package/abstract/tower.js.map +1 -1
- package/abstract/utils.d.ts +1 -115
- package/abstract/utils.d.ts.map +1 -1
- package/abstract/utils.js +17 -371
- package/abstract/utils.js.map +1 -1
- package/abstract/weierstrass.d.ts +152 -73
- package/abstract/weierstrass.d.ts.map +1 -1
- package/abstract/weierstrass.js +487 -404
- package/abstract/weierstrass.js.map +1 -1
- package/bls12-381.d.ts +2 -0
- package/bls12-381.d.ts.map +1 -1
- package/bls12-381.js +504 -480
- package/bls12-381.js.map +1 -1
- package/bn254.d.ts +2 -0
- package/bn254.d.ts.map +1 -1
- package/bn254.js +44 -32
- package/bn254.js.map +1 -1
- package/ed25519.d.ts +25 -9
- package/ed25519.d.ts.map +1 -1
- package/ed25519.js +89 -65
- package/ed25519.js.map +1 -1
- package/ed448.d.ts +29 -10
- package/ed448.d.ts.map +1 -1
- package/ed448.js +116 -81
- package/ed448.js.map +1 -1
- package/esm/_shortw_utils.d.ts +7 -5
- package/esm/_shortw_utils.d.ts.map +1 -1
- package/esm/_shortw_utils.js +2 -8
- package/esm/_shortw_utils.js.map +1 -1
- package/esm/abstract/bls.d.ts +60 -24
- package/esm/abstract/bls.d.ts.map +1 -1
- package/esm/abstract/bls.js +158 -109
- package/esm/abstract/bls.js.map +1 -1
- package/esm/abstract/curve.d.ts +44 -9
- package/esm/abstract/curve.d.ts.map +1 -1
- package/esm/abstract/curve.js +96 -12
- package/esm/abstract/curve.js.map +1 -1
- package/esm/abstract/edwards.d.ts +112 -25
- package/esm/abstract/edwards.d.ts.map +1 -1
- package/esm/abstract/edwards.js +141 -94
- package/esm/abstract/edwards.js.map +1 -1
- package/esm/abstract/fft.d.ts +122 -0
- package/esm/abstract/fft.d.ts.map +1 -0
- package/esm/abstract/fft.js +425 -0
- package/esm/abstract/fft.js.map +1 -0
- package/esm/abstract/hash-to-curve.d.ts +25 -11
- package/esm/abstract/hash-to-curve.d.ts.map +1 -1
- package/esm/abstract/hash-to-curve.js +17 -14
- package/esm/abstract/hash-to-curve.js.map +1 -1
- package/esm/abstract/modular.d.ts +28 -17
- package/esm/abstract/modular.d.ts.map +1 -1
- package/esm/abstract/modular.js +155 -138
- package/esm/abstract/modular.js.map +1 -1
- package/esm/abstract/montgomery.d.ts +3 -8
- package/esm/abstract/montgomery.d.ts.map +1 -1
- package/esm/abstract/montgomery.js +74 -94
- package/esm/abstract/montgomery.js.map +1 -1
- package/esm/abstract/poseidon.d.ts +5 -13
- package/esm/abstract/poseidon.d.ts.map +1 -1
- package/esm/abstract/poseidon.js +12 -7
- package/esm/abstract/poseidon.js.map +1 -1
- package/esm/abstract/tower.d.ts +20 -46
- package/esm/abstract/tower.d.ts.map +1 -1
- package/esm/abstract/tower.js +10 -4
- package/esm/abstract/tower.js.map +1 -1
- package/esm/abstract/utils.d.ts +1 -115
- package/esm/abstract/utils.d.ts.map +1 -1
- package/esm/abstract/utils.js +3 -344
- package/esm/abstract/utils.js.map +1 -1
- package/esm/abstract/weierstrass.d.ts +152 -73
- package/esm/abstract/weierstrass.d.ts.map +1 -1
- package/esm/abstract/weierstrass.js +485 -406
- package/esm/abstract/weierstrass.js.map +1 -1
- package/esm/bls12-381.d.ts +2 -0
- package/esm/bls12-381.d.ts.map +1 -1
- package/esm/bls12-381.js +503 -479
- package/esm/bls12-381.js.map +1 -1
- package/esm/bn254.d.ts +2 -0
- package/esm/bn254.d.ts.map +1 -1
- package/esm/bn254.js +41 -29
- package/esm/bn254.js.map +1 -1
- package/esm/ed25519.d.ts +25 -9
- package/esm/ed25519.d.ts.map +1 -1
- package/esm/ed25519.js +84 -60
- package/esm/ed25519.js.map +1 -1
- package/esm/ed448.d.ts +29 -10
- package/esm/ed448.d.ts.map +1 -1
- package/esm/ed448.js +113 -78
- package/esm/ed448.js.map +1 -1
- package/esm/jubjub.d.ts +4 -0
- package/esm/jubjub.d.ts.map +1 -1
- package/esm/jubjub.js +4 -0
- package/esm/jubjub.js.map +1 -1
- package/esm/misc.d.ts.map +1 -1
- package/esm/misc.js +31 -26
- package/esm/misc.js.map +1 -1
- package/esm/nist.d.ts +8 -16
- package/esm/nist.d.ts.map +1 -1
- package/esm/nist.js +87 -97
- package/esm/nist.js.map +1 -1
- package/esm/p256.d.ts +3 -3
- package/esm/p384.d.ts +3 -3
- package/esm/p521.d.ts +3 -3
- package/esm/pasta.d.ts +4 -0
- package/esm/pasta.d.ts.map +1 -1
- package/esm/pasta.js +4 -0
- package/esm/pasta.js.map +1 -1
- package/esm/secp256k1.d.ts +6 -6
- package/esm/secp256k1.d.ts.map +1 -1
- package/esm/secp256k1.js +44 -41
- package/esm/secp256k1.js.map +1 -1
- package/esm/utils.d.ts +96 -0
- package/esm/utils.d.ts.map +1 -0
- package/esm/utils.js +279 -0
- package/esm/utils.js.map +1 -0
- package/jubjub.d.ts +4 -0
- package/jubjub.d.ts.map +1 -1
- package/jubjub.js +4 -0
- package/jubjub.js.map +1 -1
- package/misc.d.ts.map +1 -1
- package/misc.js +35 -30
- package/misc.js.map +1 -1
- package/nist.d.ts +8 -16
- package/nist.d.ts.map +1 -1
- package/nist.js +87 -97
- package/nist.js.map +1 -1
- package/p256.d.ts +3 -3
- package/p384.d.ts +3 -3
- package/p521.d.ts +3 -3
- package/package.json +26 -8
- package/pasta.d.ts +4 -0
- package/pasta.d.ts.map +1 -1
- package/pasta.js +4 -0
- package/pasta.js.map +1 -1
- package/secp256k1.d.ts +6 -6
- package/secp256k1.d.ts.map +1 -1
- package/secp256k1.js +47 -44
- package/secp256k1.js.map +1 -1
- package/src/_shortw_utils.ts +5 -15
- package/src/abstract/bls.ts +260 -145
- package/src/abstract/curve.ts +125 -18
- package/src/abstract/edwards.ts +282 -127
- package/src/abstract/fft.ts +519 -0
- package/src/abstract/hash-to-curve.ts +51 -27
- package/src/abstract/modular.ts +156 -143
- package/src/abstract/montgomery.ts +81 -111
- package/src/abstract/poseidon.ts +22 -18
- package/src/abstract/tower.ts +37 -68
- package/src/abstract/utils.ts +3 -378
- package/src/abstract/weierstrass.ts +752 -461
- package/src/bls12-381.ts +542 -507
- package/src/bn254.ts +47 -35
- package/src/ed25519.ts +104 -76
- package/src/ed448.ts +156 -105
- package/src/jubjub.ts +4 -0
- package/src/misc.ts +39 -34
- package/src/nist.ts +138 -126
- package/src/p256.ts +3 -3
- package/src/p384.ts +3 -3
- package/src/p521.ts +3 -3
- package/src/pasta.ts +5 -1
- package/src/secp256k1.ts +59 -47
- package/src/utils.ts +328 -0
- package/utils.d.ts +96 -0
- package/utils.d.ts.map +1 -0
- package/utils.js +313 -0
- package/utils.js.map +1 -0
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Short Weierstrass curve methods. The formula is: y² = x³ + ax + b.
|
|
3
3
|
*
|
|
4
|
-
* ### Parameters
|
|
5
|
-
*
|
|
6
|
-
* To initialize a weierstrass curve, one needs to pass following params:
|
|
7
|
-
*
|
|
8
|
-
* * a: formula param
|
|
9
|
-
* * b: formula param
|
|
10
|
-
* * Fp: finite Field over which we'll do calculations. Can be complex (Fp2, Fp12)
|
|
11
|
-
* * n: Curve prime subgroup order, total count of valid points in the field
|
|
12
|
-
* * Gx: Base point (x, y) aka generator point x coordinate
|
|
13
|
-
* * Gy: ...y coordinate
|
|
14
|
-
* * h: cofactor, usually 1. h*n = curve group order (n is only subgroup order)
|
|
15
|
-
* * lowS: whether to enable (default) or disable "low-s" non-malleable signatures
|
|
16
|
-
*
|
|
17
4
|
* ### Design rationale for types
|
|
18
5
|
*
|
|
19
6
|
* * Interaction between classes from different curves should fail:
|
|
@@ -38,29 +25,76 @@
|
|
|
38
25
|
* @module
|
|
39
26
|
*/
|
|
40
27
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
41
|
-
|
|
28
|
+
import { hmac } from '@noble/hashes/hmac.js';
|
|
42
29
|
import {
|
|
43
|
-
|
|
44
|
-
|
|
30
|
+
_validateObject,
|
|
31
|
+
abool,
|
|
32
|
+
abytes,
|
|
33
|
+
aInRange,
|
|
34
|
+
bitMask,
|
|
35
|
+
bytesToHex,
|
|
36
|
+
bytesToNumberBE,
|
|
37
|
+
concatBytes,
|
|
38
|
+
createHmacDrbg,
|
|
39
|
+
ensureBytes,
|
|
40
|
+
hexToBytes,
|
|
41
|
+
inRange,
|
|
42
|
+
isBytes,
|
|
43
|
+
memoized,
|
|
44
|
+
numberToHexUnpadded,
|
|
45
|
+
randomBytes,
|
|
46
|
+
type CHash,
|
|
47
|
+
type Hex,
|
|
48
|
+
type PrivKey,
|
|
49
|
+
} from '../utils.ts';
|
|
50
|
+
import {
|
|
51
|
+
_createCurveFields,
|
|
52
|
+
mulEndoUnsafe,
|
|
53
|
+
negateCt,
|
|
54
|
+
normalizeZ,
|
|
55
|
+
pippenger,
|
|
56
|
+
wNAF,
|
|
57
|
+
type AffinePoint,
|
|
58
|
+
type BasicCurve,
|
|
59
|
+
type Group,
|
|
60
|
+
type GroupConstructor,
|
|
45
61
|
} from './curve.ts';
|
|
46
|
-
// prettier-ignore
|
|
47
62
|
import {
|
|
48
63
|
Field,
|
|
49
64
|
FpInvertBatch,
|
|
50
|
-
|
|
65
|
+
getMinHashLength,
|
|
66
|
+
mapHashToField,
|
|
67
|
+
validateField,
|
|
68
|
+
type IField,
|
|
69
|
+
type NLength,
|
|
51
70
|
} from './modular.ts';
|
|
52
|
-
// prettier-ignore
|
|
53
|
-
import {
|
|
54
|
-
type CHash, type Hex, type PrivKey,
|
|
55
|
-
aInRange, abool,
|
|
56
|
-
bitMask,
|
|
57
|
-
bytesToHex, bytesToNumberBE, concatBytes, createHmacDrbg, ensureBytes, hexToBytes,
|
|
58
|
-
inRange, isBytes, memoized, numberToBytesBE, numberToHexUnpadded, validateObject
|
|
59
|
-
} from './utils.ts';
|
|
60
71
|
|
|
61
72
|
export type { AffinePoint };
|
|
62
|
-
type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array;
|
|
63
|
-
|
|
73
|
+
export type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array;
|
|
74
|
+
/**
|
|
75
|
+
* When Weierstrass curve has `a=0`, it becomes Koblitz curve.
|
|
76
|
+
* Koblitz curves allow using **efficiently-computable GLV endomorphism ψ**.
|
|
77
|
+
* Endomorphism uses 2x less RAM, speeds up precomputation by 2x and ECDH / key recovery by 20%.
|
|
78
|
+
* For precomputed wNAF it trades off 1/2 init time & 1/3 ram for 20% perf hit.
|
|
79
|
+
*
|
|
80
|
+
* Endomorphism consists of beta, lambda and splitScalar:
|
|
81
|
+
*
|
|
82
|
+
* 1. GLV endomorphism ψ transforms a point: `P = (x, y) ↦ ψ(P) = (β·x mod p, y)`
|
|
83
|
+
* 2. GLV scalar decomposition transforms a scalar: `k ≡ k₁ + k₂·λ (mod n)`
|
|
84
|
+
* 3. Then these are combined: `k·P = k₁·P + k₂·ψ(P)`
|
|
85
|
+
* 4. Two 128-bit point-by-scalar multiplications + one point addition is faster than
|
|
86
|
+
* one 256-bit multiplication.
|
|
87
|
+
*
|
|
88
|
+
* where
|
|
89
|
+
* * beta: β ∈ Fₚ with β³ = 1, β ≠ 1
|
|
90
|
+
* * lambda: λ ∈ Fₙ with λ³ = 1, λ ≠ 1
|
|
91
|
+
* * splitScalar decomposes k ↦ k₁, k₂, by using reduced basis vectors.
|
|
92
|
+
* Gauss lattice reduction calculates them from initial basis vectors `(n, 0), (-λ, 0)`
|
|
93
|
+
*
|
|
94
|
+
* Check out `test/misc/endomorphism.js` and
|
|
95
|
+
* [gist](https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066).
|
|
96
|
+
*/
|
|
97
|
+
export type EndomorphismOpts = {
|
|
64
98
|
beta: bigint;
|
|
65
99
|
splitScalar: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };
|
|
66
100
|
};
|
|
@@ -72,7 +106,7 @@ export type BasicWCurve<T> = BasicCurve<T> & {
|
|
|
72
106
|
// Optional params
|
|
73
107
|
allowedPrivateKeyLengths?: readonly number[]; // for P521
|
|
74
108
|
wrapPrivateKey?: boolean; // bls12-381 requires mod(n) instead of rejecting keys >= n
|
|
75
|
-
endo?: EndomorphismOpts;
|
|
109
|
+
endo?: EndomorphismOpts;
|
|
76
110
|
// When a cofactor != 1, there can be an effective methods to:
|
|
77
111
|
// 1. Determine whether a point is torsion-free
|
|
78
112
|
isTorsionFree?: (c: ProjConstructor<T>, point: ProjPointType<T>) => boolean;
|
|
@@ -82,36 +116,70 @@ export type BasicWCurve<T> = BasicCurve<T> & {
|
|
|
82
116
|
|
|
83
117
|
export type Entropy = Hex | boolean;
|
|
84
118
|
export type SignOpts = { lowS?: boolean; extraEntropy?: Entropy; prehash?: boolean };
|
|
85
|
-
export type VerOpts = {
|
|
119
|
+
export type VerOpts = {
|
|
120
|
+
lowS?: boolean;
|
|
121
|
+
prehash?: boolean;
|
|
122
|
+
format?: 'compact' | 'der' | 'js' | undefined;
|
|
123
|
+
};
|
|
86
124
|
|
|
87
125
|
function validateSigVerOpts(opts: SignOpts | VerOpts) {
|
|
88
126
|
if (opts.lowS !== undefined) abool('lowS', opts.lowS);
|
|
89
127
|
if (opts.prehash !== undefined) abool('prehash', opts.prehash);
|
|
90
128
|
}
|
|
91
129
|
|
|
92
|
-
|
|
130
|
+
/** Instance methods for 3D XYZ points. */
|
|
93
131
|
export interface ProjPointType<T> extends Group<ProjPointType<T>> {
|
|
132
|
+
/** projective x coordinate. Note: different from .x */
|
|
94
133
|
readonly px: T;
|
|
134
|
+
/** projective y coordinate. Note: different from .y */
|
|
95
135
|
readonly py: T;
|
|
136
|
+
/** projective z coordinate */
|
|
96
137
|
readonly pz: T;
|
|
138
|
+
/** affine x coordinate */
|
|
97
139
|
get x(): T;
|
|
140
|
+
/** affine y coordinate */
|
|
98
141
|
get y(): T;
|
|
99
|
-
toAffine(iz?: T): AffinePoint<T>;
|
|
100
|
-
toHex(isCompressed?: boolean): string;
|
|
101
|
-
toRawBytes(isCompressed?: boolean): Uint8Array;
|
|
102
|
-
|
|
103
142
|
assertValidity(): void;
|
|
104
|
-
|
|
143
|
+
clearCofactor(): ProjPointType<T>;
|
|
144
|
+
is0(): boolean;
|
|
145
|
+
isTorsionFree(): boolean;
|
|
105
146
|
multiplyUnsafe(scalar: bigint): ProjPointType<T>;
|
|
147
|
+
/**
|
|
148
|
+
* Massively speeds up `p.multiply(n)` by using wnaf precompute tables (caching).
|
|
149
|
+
* Table generation takes 30MB of ram and 10ms on high-end CPU, but may take
|
|
150
|
+
* much longer on slow devices.
|
|
151
|
+
* Actual generation will happen on first call of `.multiply()`.
|
|
152
|
+
* By default, BASE point is precomputed.
|
|
153
|
+
* @param windowSize - table window size
|
|
154
|
+
* @param isLazy - (default true) allows to defer generation
|
|
155
|
+
*/
|
|
156
|
+
precompute(windowSize?: number, isLazy?: boolean): ProjPointType<T>;
|
|
157
|
+
|
|
158
|
+
/** Converts 3D XYZ projective point to 2D xy affine coordinates */
|
|
159
|
+
toAffine(invertedZ?: T): AffinePoint<T>;
|
|
160
|
+
/** Encodes point using IEEE P1363 (DER) encoding. First byte is 2/3/4. Default = isCompressed. */
|
|
161
|
+
toBytes(isCompressed?: boolean): Uint8Array;
|
|
162
|
+
toHex(isCompressed?: boolean): string;
|
|
163
|
+
|
|
164
|
+
/** @deprecated use `toBytes` */
|
|
165
|
+
toRawBytes(isCompressed?: boolean): Uint8Array;
|
|
166
|
+
/** @deprecated use `multiplyUnsafe` */
|
|
106
167
|
multiplyAndAddUnsafe(Q: ProjPointType<T>, a: bigint, b: bigint): ProjPointType<T> | undefined;
|
|
107
|
-
|
|
108
|
-
|
|
168
|
+
/** @deprecated use `p.y % 2n === 0n` */
|
|
169
|
+
hasEvenY(): boolean;
|
|
170
|
+
/** @deprecated use `p.precompute(windowSize)` */
|
|
109
171
|
_setWindowSize(windowSize: number): void;
|
|
110
172
|
}
|
|
111
|
-
|
|
173
|
+
|
|
174
|
+
/** Static methods for 3D XYZ points. */
|
|
112
175
|
export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
|
|
176
|
+
Fp: IField<T>;
|
|
177
|
+
Fn: IField<bigint>;
|
|
178
|
+
/** Does NOT validate if the point is valid. Use `.assertValidity()`. */
|
|
113
179
|
new (x: T, y: T, z: T): ProjPointType<T>;
|
|
180
|
+
/** Does NOT validate if the point is valid. Use `.assertValidity()`. */
|
|
114
181
|
fromAffine(p: AffinePoint<T>): ProjPointType<T>;
|
|
182
|
+
fromBytes(encodedPoint: Uint8Array): ProjPointType<T>;
|
|
115
183
|
fromHex(hex: Hex): ProjPointType<T>;
|
|
116
184
|
fromPrivateKey(privateKey: PrivKey): ProjPointType<T>;
|
|
117
185
|
normalizeZ(points: ProjPointType<T>[]): ProjPointType<T>[];
|
|
@@ -119,57 +187,104 @@ export interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
|
|
|
119
187
|
}
|
|
120
188
|
|
|
121
189
|
export type CurvePointsType<T> = BasicWCurve<T> & {
|
|
122
|
-
// Bytes
|
|
123
190
|
fromBytes?: (bytes: Uint8Array) => AffinePoint<T>;
|
|
124
191
|
toBytes?: (c: ProjConstructor<T>, point: ProjPointType<T>, isCompressed: boolean) => Uint8Array;
|
|
125
192
|
};
|
|
126
193
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
>;
|
|
130
|
-
|
|
131
|
-
function validatePointOpts<T>(curve: CurvePointsType<T>): CurvePointsTypeWithLength<T> {
|
|
132
|
-
const opts = validateBasic(curve);
|
|
133
|
-
validateObject(
|
|
134
|
-
opts,
|
|
135
|
-
{
|
|
136
|
-
a: 'field',
|
|
137
|
-
b: 'field',
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
allowedPrivateKeyLengths: 'array',
|
|
141
|
-
wrapPrivateKey: 'boolean',
|
|
142
|
-
isTorsionFree: 'function',
|
|
143
|
-
clearCofactor: 'function',
|
|
144
|
-
allowInfinityPoint: 'boolean',
|
|
145
|
-
fromBytes: 'function',
|
|
146
|
-
toBytes: 'function',
|
|
147
|
-
}
|
|
148
|
-
);
|
|
149
|
-
const { endo, Fp, a } = opts;
|
|
150
|
-
if (endo) {
|
|
151
|
-
if (!Fp.eql(a, Fp.ZERO)) {
|
|
152
|
-
throw new Error('invalid endomorphism, can only be defined for Koblitz curves that have a=0');
|
|
153
|
-
}
|
|
154
|
-
if (
|
|
155
|
-
typeof endo !== 'object' ||
|
|
156
|
-
typeof endo.beta !== 'bigint' ||
|
|
157
|
-
typeof endo.splitScalar !== 'function'
|
|
158
|
-
) {
|
|
159
|
-
throw new Error('invalid endomorphism, expected beta: bigint and splitScalar: function');
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return Object.freeze({ ...opts } as const);
|
|
163
|
-
}
|
|
194
|
+
// LegacyWeierstrassOpts
|
|
195
|
+
export type CurvePointsTypeWithLength<T> = Readonly<CurvePointsType<T> & Partial<NLength>>;
|
|
164
196
|
|
|
197
|
+
// LegacyWeierstrass
|
|
165
198
|
export type CurvePointsRes<T> = {
|
|
166
|
-
CURVE
|
|
199
|
+
/** @deprecated import individual CURVE params */
|
|
200
|
+
CURVE: CurvePointsType<T>;
|
|
201
|
+
Point: ProjConstructor<T>;
|
|
202
|
+
/** @deprecated use `Point` */
|
|
167
203
|
ProjectivePoint: ProjConstructor<T>;
|
|
204
|
+
/** @deprecated */
|
|
168
205
|
normPrivateKeyToScalar: (key: PrivKey) => bigint;
|
|
206
|
+
/** @deprecated */
|
|
169
207
|
weierstrassEquation: (x: T) => T;
|
|
208
|
+
/** @deprecated use `Point.Fn.isValidNot0(num)` */
|
|
170
209
|
isWithinCurveOrder: (num: bigint) => boolean;
|
|
171
210
|
};
|
|
172
211
|
|
|
212
|
+
// Aliases to legacy types
|
|
213
|
+
// export type CurveType = LegacyECDSAOpts;
|
|
214
|
+
// export type CurveFn = LegacyECDSA;
|
|
215
|
+
// export type CurvePointsRes<T> = LegacyWeierstrass<T>;
|
|
216
|
+
// export type CurvePointsType<T> = LegacyWeierstrassOpts<T>;
|
|
217
|
+
// export type CurvePointsTypeWithLength<T> = LegacyWeierstrassOpts<T>;
|
|
218
|
+
// export type BasicWCurve<T> = LegacyWeierstrassOpts<T>;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Weierstrass curve options.
|
|
222
|
+
*
|
|
223
|
+
* * p: prime characteristic (order) of finite field, in which arithmetics is done
|
|
224
|
+
* * n: order of prime subgroup a.k.a total amount of valid curve points
|
|
225
|
+
* * h: cofactor, usually 1. h*n is group order; n is subgroup order
|
|
226
|
+
* * a: formula param, must be in field of p
|
|
227
|
+
* * b: formula param, must be in field of p
|
|
228
|
+
* * Gx: x coordinate of generator point a.k.a. base point
|
|
229
|
+
* * Gy: y coordinate of generator point
|
|
230
|
+
*/
|
|
231
|
+
export type WeierstrassOpts<T> = Readonly<{
|
|
232
|
+
p: bigint;
|
|
233
|
+
n: bigint;
|
|
234
|
+
h: bigint;
|
|
235
|
+
a: T;
|
|
236
|
+
b: T;
|
|
237
|
+
Gx: T;
|
|
238
|
+
Gy: T;
|
|
239
|
+
}>;
|
|
240
|
+
|
|
241
|
+
// When a cofactor != 1, there can be an effective methods to:
|
|
242
|
+
// 1. Determine whether a point is torsion-free
|
|
243
|
+
// 2. Clear torsion component
|
|
244
|
+
// wrapPrivateKey: bls12-381 requires mod(n) instead of rejecting keys >= n
|
|
245
|
+
export type WeierstrassExtraOpts<T> = Partial<{
|
|
246
|
+
Fp: IField<T>;
|
|
247
|
+
Fn: IField<bigint>;
|
|
248
|
+
// TODO: remove
|
|
249
|
+
allowedPrivateKeyLengths: readonly number[]; // for P521
|
|
250
|
+
allowInfinityPoint: boolean;
|
|
251
|
+
endo: EndomorphismOpts;
|
|
252
|
+
wrapPrivateKey: boolean;
|
|
253
|
+
isTorsionFree: (c: ProjConstructor<T>, point: ProjPointType<T>) => boolean;
|
|
254
|
+
clearCofactor: (c: ProjConstructor<T>, point: ProjPointType<T>) => ProjPointType<T>;
|
|
255
|
+
fromBytes: (bytes: Uint8Array) => AffinePoint<T>;
|
|
256
|
+
toBytes: (c: ProjConstructor<T>, point: ProjPointType<T>, isCompressed: boolean) => Uint8Array;
|
|
257
|
+
}>;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Options for ECDSA signatures over a Weierstrass curve.
|
|
261
|
+
*/
|
|
262
|
+
export type ECDSAOpts = {
|
|
263
|
+
hash: CHash;
|
|
264
|
+
hmac?: HmacFnSync;
|
|
265
|
+
randomBytes?: (bytesLength?: number) => Uint8Array;
|
|
266
|
+
lowS?: boolean;
|
|
267
|
+
bits2int?: (bytes: Uint8Array) => bigint;
|
|
268
|
+
bits2int_modN?: (bytes: Uint8Array) => bigint;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/** ECDSA is only supported for prime fields, not Fp2 (extension fields). */
|
|
272
|
+
export interface ECDSA {
|
|
273
|
+
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
|
|
274
|
+
getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array;
|
|
275
|
+
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => RecoveredSignatureType;
|
|
276
|
+
verify: (signature: Hex | SignatureLike, msgHash: Hex, publicKey: Hex, opts?: VerOpts) => boolean;
|
|
277
|
+
Point: ProjConstructor<bigint>;
|
|
278
|
+
Signature: SignatureConstructor;
|
|
279
|
+
utils: {
|
|
280
|
+
isValidPrivateKey(privateKey: PrivKey): boolean;
|
|
281
|
+
randomPrivateKey: () => Uint8Array;
|
|
282
|
+
// TODO: deprecate those two
|
|
283
|
+
normPrivateKeyToScalar: (key: PrivKey) => bigint;
|
|
284
|
+
/** @deprecated */
|
|
285
|
+
precompute: (windowSize?: number, point?: ProjPointType<bigint>) => ProjPointType<bigint>;
|
|
286
|
+
};
|
|
287
|
+
}
|
|
173
288
|
export class DERErr extends Error {
|
|
174
289
|
constructor(m = '') {
|
|
175
290
|
super(m);
|
|
@@ -292,74 +407,171 @@ export const DER: IDER = {
|
|
|
292
407
|
// prettier-ignore
|
|
293
408
|
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4);
|
|
294
409
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const { Fp } = CURVE; // All curves has same field / group length as for now, but they can differ
|
|
298
|
-
const Fn = Field(CURVE.n, CURVE.nBitLength);
|
|
299
|
-
|
|
300
|
-
const toBytes =
|
|
301
|
-
CURVE.toBytes ||
|
|
302
|
-
((_c: ProjConstructor<T>, point: ProjPointType<T>, _isCompressed: boolean) => {
|
|
303
|
-
const a = point.toAffine();
|
|
304
|
-
return concatBytes(Uint8Array.from([0x04]), Fp.toBytes(a.x), Fp.toBytes(a.y));
|
|
305
|
-
});
|
|
306
|
-
const fromBytes =
|
|
307
|
-
CURVE.fromBytes ||
|
|
308
|
-
((bytes: Uint8Array) => {
|
|
309
|
-
// const head = bytes[0];
|
|
310
|
-
const tail = bytes.subarray(1);
|
|
311
|
-
// if (head !== 0x04) throw new Error('Only non-compressed encoding is supported');
|
|
312
|
-
const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES));
|
|
313
|
-
const y = Fp.fromBytes(tail.subarray(Fp.BYTES, 2 * Fp.BYTES));
|
|
314
|
-
return { x, y };
|
|
315
|
-
});
|
|
316
|
-
|
|
410
|
+
// TODO: remove
|
|
411
|
+
export function _legacyHelperEquat<T>(Fp: IField<T>, a: T, b: T): (x: T) => T {
|
|
317
412
|
/**
|
|
318
413
|
* y² = x³ + ax + b: Short weierstrass curve formula. Takes x, returns y².
|
|
319
414
|
* @returns y²
|
|
320
415
|
*/
|
|
321
416
|
function weierstrassEquation(x: T): T {
|
|
322
|
-
const { a, b } = CURVE;
|
|
323
417
|
const x2 = Fp.sqr(x); // x * x
|
|
324
|
-
const x3 = Fp.mul(x2, x); //
|
|
325
|
-
return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); //
|
|
326
|
-
}
|
|
327
|
-
// Validate whether the passed curve params are valid.
|
|
328
|
-
// We check if curve equation works for generator point.
|
|
329
|
-
// `assertValidity()` won't work: `isTorsionFree()` is not available at this point in bls12-381.
|
|
330
|
-
// ProjectivePoint class has not been initialized yet.
|
|
331
|
-
if (!Fp.eql(Fp.sqr(CURVE.Gy), weierstrassEquation(CURVE.Gx)))
|
|
332
|
-
throw new Error('bad generator point: equation left != right');
|
|
333
|
-
|
|
334
|
-
// Valid group elements reside in range 1..n-1
|
|
335
|
-
function isWithinCurveOrder(num: bigint): boolean {
|
|
336
|
-
return inRange(num, _1n, CURVE.n);
|
|
418
|
+
const x3 = Fp.mul(x2, x); // x² * x
|
|
419
|
+
return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x³ + a * x + b
|
|
337
420
|
}
|
|
421
|
+
return weierstrassEquation;
|
|
422
|
+
}
|
|
423
|
+
export function _legacyHelperNormPriv(
|
|
424
|
+
Fn: IField<bigint>,
|
|
425
|
+
allowedPrivateKeyLengths?: readonly number[],
|
|
426
|
+
wrapPrivateKey?: boolean
|
|
427
|
+
): (key: PrivKey) => bigint {
|
|
428
|
+
const { BYTES: expected } = Fn;
|
|
338
429
|
// Validates if priv key is valid and converts it to bigint.
|
|
339
|
-
// Supports options allowedPrivateKeyLengths and wrapPrivateKey.
|
|
340
430
|
function normPrivateKeyToScalar(key: PrivKey): bigint {
|
|
341
|
-
const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n: N } = CURVE;
|
|
342
|
-
if (lengths && typeof key !== 'bigint') {
|
|
343
|
-
if (isBytes(key)) key = bytesToHex(key);
|
|
344
|
-
// Normalize to hex string, pad. E.g. P521 would norm 130-132 char hex to 132-char bytes
|
|
345
|
-
if (typeof key !== 'string' || !lengths.includes(key.length))
|
|
346
|
-
throw new Error('invalid private key');
|
|
347
|
-
key = key.padStart(nByteLength * 2, '0');
|
|
348
|
-
}
|
|
349
431
|
let num: bigint;
|
|
350
|
-
|
|
351
|
-
num =
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
432
|
+
if (typeof key === 'bigint') {
|
|
433
|
+
num = key;
|
|
434
|
+
} else {
|
|
435
|
+
let bytes = ensureBytes('private key', key);
|
|
436
|
+
if (allowedPrivateKeyLengths) {
|
|
437
|
+
if (!allowedPrivateKeyLengths.includes(bytes.length * 2))
|
|
438
|
+
throw new Error('invalid private key');
|
|
439
|
+
const padded = new Uint8Array(expected);
|
|
440
|
+
padded.set(bytes, padded.length - bytes.length);
|
|
441
|
+
bytes = padded;
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
num = Fn.fromBytes(bytes);
|
|
445
|
+
} catch (error) {
|
|
446
|
+
throw new Error(
|
|
447
|
+
`invalid private key: expected ui8a of size ${expected}, got ${typeof key}`
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (wrapPrivateKey) num = Fn.create(num); // disabled by default, enabled for BLS
|
|
452
|
+
if (!Fn.isValidNot0(num)) throw new Error('invalid private key: out of range [1..N-1]');
|
|
453
|
+
return num;
|
|
454
|
+
}
|
|
455
|
+
return normPrivateKeyToScalar;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export function weierstrassN<T>(
|
|
459
|
+
CURVE: WeierstrassOpts<T>,
|
|
460
|
+
curveOpts: WeierstrassExtraOpts<T> = {}
|
|
461
|
+
): ProjConstructor<T> {
|
|
462
|
+
const { Fp, Fn } = _createCurveFields('weierstrass', CURVE, curveOpts);
|
|
463
|
+
const { h: cofactor, n: CURVE_ORDER } = CURVE;
|
|
464
|
+
_validateObject(
|
|
465
|
+
curveOpts,
|
|
466
|
+
{},
|
|
467
|
+
{
|
|
468
|
+
allowInfinityPoint: 'boolean',
|
|
469
|
+
clearCofactor: 'function',
|
|
470
|
+
isTorsionFree: 'function',
|
|
471
|
+
fromBytes: 'function',
|
|
472
|
+
toBytes: 'function',
|
|
473
|
+
endo: 'object',
|
|
474
|
+
wrapPrivateKey: 'boolean',
|
|
475
|
+
}
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
const { endo } = curveOpts;
|
|
479
|
+
if (endo) {
|
|
480
|
+
// validateObject(endo, { beta: 'bigint', splitScalar: 'function' });
|
|
481
|
+
if (
|
|
482
|
+
!Fp.is0(CURVE.a) ||
|
|
483
|
+
typeof endo.beta !== 'bigint' ||
|
|
484
|
+
typeof endo.splitScalar !== 'function'
|
|
485
|
+
) {
|
|
486
|
+
throw new Error('invalid endo: expected "beta": bigint and "splitScalar": function');
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function assertCompressionIsSupported() {
|
|
491
|
+
if (!Fp.isOdd) throw new Error('compression is not supported: Field does not have .isOdd()');
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Implements IEEE P1363 point encoding
|
|
495
|
+
function pointToBytes(
|
|
496
|
+
_c: ProjConstructor<T>,
|
|
497
|
+
point: ProjPointType<T>,
|
|
498
|
+
isCompressed: boolean
|
|
499
|
+
): Uint8Array {
|
|
500
|
+
const { x, y } = point.toAffine();
|
|
501
|
+
const bx = Fp.toBytes(x);
|
|
502
|
+
abool('isCompressed', isCompressed);
|
|
503
|
+
if (isCompressed) {
|
|
504
|
+
assertCompressionIsSupported();
|
|
505
|
+
const hasEvenY = !Fp.isOdd!(y);
|
|
506
|
+
return concatBytes(pprefix(hasEvenY), bx);
|
|
507
|
+
} else {
|
|
508
|
+
return concatBytes(Uint8Array.of(0x04), bx, Fp.toBytes(y));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function pointFromBytes(bytes: Uint8Array) {
|
|
512
|
+
abytes(bytes);
|
|
513
|
+
const L = Fp.BYTES;
|
|
514
|
+
const LC = L + 1; // length compressed, e.g. 33 for 32-byte field
|
|
515
|
+
const LU = 2 * L + 1; // length uncompressed, e.g. 65 for 32-byte field
|
|
516
|
+
const length = bytes.length;
|
|
517
|
+
const head = bytes[0];
|
|
518
|
+
const tail = bytes.subarray(1);
|
|
519
|
+
// No actual validation is done here: use .assertValidity()
|
|
520
|
+
if (length === LC && (head === 0x02 || head === 0x03)) {
|
|
521
|
+
const x = Fp.fromBytes(tail);
|
|
522
|
+
if (!Fp.isValid(x)) throw new Error('bad point: is not on curve, wrong x');
|
|
523
|
+
const y2 = weierstrassEquation(x); // y² = x³ + ax + b
|
|
524
|
+
let y: T;
|
|
525
|
+
try {
|
|
526
|
+
y = Fp.sqrt(y2); // y = y² ^ (p+1)/4
|
|
527
|
+
} catch (sqrtError) {
|
|
528
|
+
const err = sqrtError instanceof Error ? ': ' + sqrtError.message : '';
|
|
529
|
+
throw new Error('bad point: is not on curve, sqrt error' + err);
|
|
530
|
+
}
|
|
531
|
+
assertCompressionIsSupported();
|
|
532
|
+
const isYOdd = Fp.isOdd!(y); // (y & _1n) === _1n;
|
|
533
|
+
const isHeadOdd = (head & 1) === 1; // ECDSA-specific
|
|
534
|
+
if (isHeadOdd !== isYOdd) y = Fp.neg(y);
|
|
535
|
+
return { x, y };
|
|
536
|
+
} else if (length === LU && head === 0x04) {
|
|
537
|
+
// TODO: more checks
|
|
538
|
+
const x = Fp.fromBytes(tail.subarray(L * 0, L * 1));
|
|
539
|
+
const y = Fp.fromBytes(tail.subarray(L * 1, L * 2));
|
|
540
|
+
if (!isValidXY(x, y)) throw new Error('bad point: is not on curve');
|
|
541
|
+
return { x, y };
|
|
542
|
+
} else {
|
|
356
543
|
throw new Error(
|
|
357
|
-
|
|
544
|
+
`bad point: got length ${length}, expected compressed=${LC} or uncompressed=${LU}`
|
|
358
545
|
);
|
|
359
546
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const toBytes = curveOpts.toBytes || pointToBytes;
|
|
550
|
+
const fromBytes = curveOpts.fromBytes || pointFromBytes;
|
|
551
|
+
const weierstrassEquation = _legacyHelperEquat(Fp, CURVE.a, CURVE.b);
|
|
552
|
+
|
|
553
|
+
// TODO: move top-level
|
|
554
|
+
/** Checks whether equation holds for given x, y: y² == x³ + ax + b */
|
|
555
|
+
function isValidXY(x: T, y: T): boolean {
|
|
556
|
+
const left = Fp.sqr(y); // y²
|
|
557
|
+
const right = weierstrassEquation(x); // x³ + ax + b
|
|
558
|
+
return Fp.eql(left, right);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Validate whether the passed curve params are valid.
|
|
562
|
+
// Test 1: equation y² = x³ + ax + b should work for generator point.
|
|
563
|
+
if (!isValidXY(CURVE.Gx, CURVE.Gy)) throw new Error('bad curve params: generator point');
|
|
564
|
+
|
|
565
|
+
// Test 2: discriminant Δ part should be non-zero: 4a³ + 27b² != 0.
|
|
566
|
+
// Guarantees curve is genus-1, smooth (non-singular).
|
|
567
|
+
const _4a3 = Fp.mul(Fp.pow(CURVE.a, _3n), _4n);
|
|
568
|
+
const _27b2 = Fp.mul(Fp.sqr(CURVE.b), BigInt(27));
|
|
569
|
+
if (Fp.is0(Fp.add(_4a3, _27b2))) throw new Error('bad curve params: a or b');
|
|
570
|
+
|
|
571
|
+
/** Asserts coordinate is valid: 0 <= n < Fp.ORDER. */
|
|
572
|
+
function acoord(title: string, n: T, banZero = false) {
|
|
573
|
+
if (!Fp.isValid(n) || (banZero && Fp.is0(n))) throw new Error(`bad point coordinate ${title}`);
|
|
574
|
+
return n;
|
|
363
575
|
}
|
|
364
576
|
|
|
365
577
|
function aprjpoint(other: unknown) {
|
|
@@ -370,7 +582,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>): CurvePointsRes<T
|
|
|
370
582
|
|
|
371
583
|
// Converts Projective point to affine (x, y) coordinates.
|
|
372
584
|
// Can accept precomputed Z^-1 - for example, from invertBatch.
|
|
373
|
-
// (
|
|
585
|
+
// (X, Y, Z) ∋ (x=X/Z, y=Y/Z)
|
|
374
586
|
const toAffineMemo = memoized((p: Point, iz?: T): AffinePoint<T> => {
|
|
375
587
|
const { px: x, py: y, pz: z } = p;
|
|
376
588
|
// Fast-path for normalized points
|
|
@@ -393,51 +605,63 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>): CurvePointsRes<T
|
|
|
393
605
|
// (0, 1, 0) aka ZERO is invalid in most contexts.
|
|
394
606
|
// In BLS, ZERO can be serialized, so we allow it.
|
|
395
607
|
// (0, 0, 0) is invalid representation of ZERO.
|
|
396
|
-
if (
|
|
608
|
+
if (curveOpts.allowInfinityPoint && !Fp.is0(p.py)) return;
|
|
397
609
|
throw new Error('bad point: ZERO');
|
|
398
610
|
}
|
|
399
611
|
// Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
|
|
400
612
|
const { x, y } = p.toAffine();
|
|
401
|
-
|
|
402
|
-
if (!
|
|
403
|
-
const left = Fp.sqr(y); // y²
|
|
404
|
-
const right = weierstrassEquation(x); // x³ + ax + b
|
|
405
|
-
if (!Fp.eql(left, right)) throw new Error('bad point: equation left != right');
|
|
613
|
+
if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error('bad point: x or y not field elements');
|
|
614
|
+
if (!isValidXY(x, y)) throw new Error('bad point: equation left != right');
|
|
406
615
|
if (!p.isTorsionFree()) throw new Error('bad point: not in prime-order subgroup');
|
|
407
616
|
return true;
|
|
408
617
|
});
|
|
409
618
|
|
|
619
|
+
function finishEndo(
|
|
620
|
+
endoBeta: EndomorphismOpts['beta'],
|
|
621
|
+
k1p: Point,
|
|
622
|
+
k2p: Point,
|
|
623
|
+
k1neg: boolean,
|
|
624
|
+
k2neg: boolean
|
|
625
|
+
) {
|
|
626
|
+
k2p = new Point(Fp.mul(k2p.px, endoBeta), k2p.py, k2p.pz);
|
|
627
|
+
k1p = negateCt(k1neg, k1p);
|
|
628
|
+
k2p = negateCt(k2neg, k2p);
|
|
629
|
+
return k1p.add(k2p);
|
|
630
|
+
}
|
|
631
|
+
|
|
410
632
|
/**
|
|
411
|
-
* Projective Point works in 3d / projective (homogeneous) coordinates:
|
|
412
|
-
* Default Point works in 2d / affine coordinates: (x, y)
|
|
633
|
+
* Projective Point works in 3d / projective (homogeneous) coordinates:(X, Y, Z) ∋ (x=X/Z, y=Y/Z).
|
|
634
|
+
* Default Point works in 2d / affine coordinates: (x, y).
|
|
413
635
|
* We're doing calculations in projective, because its operations don't require costly inversion.
|
|
414
636
|
*/
|
|
415
637
|
class Point implements ProjPointType<T> {
|
|
638
|
+
// base / generator point
|
|
416
639
|
static readonly BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE);
|
|
640
|
+
// zero / infinity / identity point
|
|
417
641
|
static readonly ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO); // 0, 1, 0
|
|
642
|
+
// fields
|
|
643
|
+
static readonly Fp = Fp;
|
|
644
|
+
static readonly Fn = Fn;
|
|
645
|
+
|
|
418
646
|
readonly px: T;
|
|
419
647
|
readonly py: T;
|
|
420
648
|
readonly pz: T;
|
|
421
649
|
|
|
650
|
+
/** Does NOT validate if the point is valid. Use `.assertValidity()`. */
|
|
422
651
|
constructor(px: T, py: T, pz: T) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
this.px = px;
|
|
427
|
-
this.py = py;
|
|
428
|
-
this.pz = pz;
|
|
652
|
+
this.px = acoord('x', px);
|
|
653
|
+
this.py = acoord('y', py, true);
|
|
654
|
+
this.pz = acoord('z', pz);
|
|
429
655
|
Object.freeze(this);
|
|
430
656
|
}
|
|
431
657
|
|
|
432
|
-
|
|
433
|
-
// Use fromHex instead, or call assertValidity() later.
|
|
658
|
+
/** Does NOT validate if the point is valid. Use `.assertValidity()`. */
|
|
434
659
|
static fromAffine(p: AffinePoint<T>): Point {
|
|
435
660
|
const { x, y } = p || {};
|
|
436
661
|
if (!p || !Fp.isValid(x) || !Fp.isValid(y)) throw new Error('invalid affine point');
|
|
437
662
|
if (p instanceof Point) throw new Error('projective point not allowed');
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if (is0(x) && is0(y)) return Point.ZERO;
|
|
663
|
+
// (0, 0) would've produced (0, 0, 1) - instead, we need (0, 1, 0)
|
|
664
|
+
if (Fp.is0(x) && Fp.is0(y)) return Point.ZERO;
|
|
441
665
|
return new Point(x, y, Fp.ONE);
|
|
442
666
|
}
|
|
443
667
|
|
|
@@ -448,59 +672,67 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>): CurvePointsRes<T
|
|
|
448
672
|
return this.toAffine().y;
|
|
449
673
|
}
|
|
450
674
|
|
|
451
|
-
/**
|
|
452
|
-
* Takes a bunch of Projective Points but executes only one
|
|
453
|
-
* inversion on all of them. Inversion is very slow operation,
|
|
454
|
-
* so this improves performance massively.
|
|
455
|
-
* Optimization: converts a list of projective points to a list of identical points with Z=1.
|
|
456
|
-
*/
|
|
457
675
|
static normalizeZ(points: Point[]): Point[] {
|
|
458
|
-
|
|
459
|
-
Fp,
|
|
460
|
-
points.map((p) => p.pz)
|
|
461
|
-
);
|
|
462
|
-
return points.map((p, i) => p.toAffine(toInv[i])).map(Point.fromAffine);
|
|
676
|
+
return normalizeZ(Point, 'pz', points);
|
|
463
677
|
}
|
|
464
678
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
679
|
+
static fromBytes(bytes: Uint8Array): Point {
|
|
680
|
+
abytes(bytes);
|
|
681
|
+
return Point.fromHex(bytes);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/** Converts hash string or Uint8Array to Point. */
|
|
469
685
|
static fromHex(hex: Hex): Point {
|
|
470
686
|
const P = Point.fromAffine(fromBytes(ensureBytes('pointHex', hex)));
|
|
471
687
|
P.assertValidity();
|
|
472
688
|
return P;
|
|
473
689
|
}
|
|
474
690
|
|
|
475
|
-
|
|
691
|
+
/** Multiplies generator point by privateKey. */
|
|
476
692
|
static fromPrivateKey(privateKey: PrivKey) {
|
|
693
|
+
const normPrivateKeyToScalar = _legacyHelperNormPriv(
|
|
694
|
+
Fn,
|
|
695
|
+
curveOpts.allowedPrivateKeyLengths,
|
|
696
|
+
curveOpts.wrapPrivateKey
|
|
697
|
+
);
|
|
477
698
|
return Point.BASE.multiply(normPrivateKeyToScalar(privateKey));
|
|
478
699
|
}
|
|
479
700
|
|
|
480
|
-
|
|
701
|
+
/** Multiscalar Multiplication */
|
|
481
702
|
static msm(points: Point[], scalars: bigint[]): Point {
|
|
482
703
|
return pippenger(Point, Fn, points, scalars);
|
|
483
704
|
}
|
|
484
705
|
|
|
485
|
-
|
|
486
|
-
|
|
706
|
+
/**
|
|
707
|
+
*
|
|
708
|
+
* @param windowSize
|
|
709
|
+
* @param isLazy true will defer table computation until the first multiplication
|
|
710
|
+
* @returns
|
|
711
|
+
*/
|
|
712
|
+
precompute(windowSize: number = 8, isLazy = true): Point {
|
|
487
713
|
wnaf.setWindowSize(this, windowSize);
|
|
714
|
+
if (!isLazy) this.multiply(_3n); // random number
|
|
715
|
+
return this;
|
|
488
716
|
}
|
|
489
717
|
|
|
490
|
-
|
|
718
|
+
/** "Private method", don't use it directly */
|
|
719
|
+
_setWindowSize(windowSize: number) {
|
|
720
|
+
this.precompute(windowSize);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// TODO: return `this`
|
|
724
|
+
/** A point on curve is valid if it conforms to equation. */
|
|
491
725
|
assertValidity(): void {
|
|
492
726
|
assertValidMemo(this);
|
|
493
727
|
}
|
|
494
728
|
|
|
495
729
|
hasEvenY(): boolean {
|
|
496
730
|
const { y } = this.toAffine();
|
|
497
|
-
if (Fp.isOdd)
|
|
498
|
-
|
|
731
|
+
if (!Fp.isOdd) throw new Error("Field doesn't support isOdd");
|
|
732
|
+
return !Fp.isOdd(y);
|
|
499
733
|
}
|
|
500
734
|
|
|
501
|
-
/**
|
|
502
|
-
* Compare one point to another.
|
|
503
|
-
*/
|
|
735
|
+
/** Compare one point to another. */
|
|
504
736
|
equals(other: Point): boolean {
|
|
505
737
|
aprjpoint(other);
|
|
506
738
|
const { px: X1, py: Y1, pz: Z1 } = this;
|
|
@@ -510,9 +742,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>): CurvePointsRes<T
|
|
|
510
742
|
return U1 && U2;
|
|
511
743
|
}
|
|
512
744
|
|
|
513
|
-
/**
|
|
514
|
-
* Flips point to one corresponding to (x, -y) in Affine coordinates.
|
|
515
|
-
*/
|
|
745
|
+
/** Flips point to one corresponding to (x, -y) in Affine coordinates. */
|
|
516
746
|
negate(): Point {
|
|
517
747
|
return new Point(this.px, Fp.neg(this.py), this.pz);
|
|
518
748
|
}
|
|
@@ -618,48 +848,10 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>): CurvePointsRes<T
|
|
|
618
848
|
return this.add(other.negate());
|
|
619
849
|
}
|
|
620
850
|
|
|
621
|
-
is0() {
|
|
851
|
+
is0(): boolean {
|
|
622
852
|
return this.equals(Point.ZERO);
|
|
623
853
|
}
|
|
624
854
|
|
|
625
|
-
private wNAF(n: bigint): { p: Point; f: Point } {
|
|
626
|
-
return wnaf.wNAFCached(this, n, Point.normalizeZ);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
/**
|
|
630
|
-
* Non-constant-time multiplication. Uses double-and-add algorithm.
|
|
631
|
-
* It's faster, but should only be used when you don't care about
|
|
632
|
-
* an exposed private key e.g. sig verification, which works over *public* keys.
|
|
633
|
-
*/
|
|
634
|
-
multiplyUnsafe(sc: bigint): Point {
|
|
635
|
-
const { endo, n: N } = CURVE;
|
|
636
|
-
aInRange('scalar', sc, _0n, N);
|
|
637
|
-
const I = Point.ZERO;
|
|
638
|
-
if (sc === _0n) return I;
|
|
639
|
-
if (this.is0() || sc === _1n) return this;
|
|
640
|
-
|
|
641
|
-
// Case a: no endomorphism. Case b: has precomputes.
|
|
642
|
-
if (!endo || wnaf.hasPrecomputes(this))
|
|
643
|
-
return wnaf.wNAFCachedUnsafe(this, sc, Point.normalizeZ);
|
|
644
|
-
|
|
645
|
-
// Case c: endomorphism
|
|
646
|
-
let { k1neg, k1, k2neg, k2 } = endo.splitScalar(sc);
|
|
647
|
-
let k1p = I;
|
|
648
|
-
let k2p = I;
|
|
649
|
-
let d: Point = this;
|
|
650
|
-
while (k1 > _0n || k2 > _0n) {
|
|
651
|
-
if (k1 & _1n) k1p = k1p.add(d);
|
|
652
|
-
if (k2 & _1n) k2p = k2p.add(d);
|
|
653
|
-
d = d.double();
|
|
654
|
-
k1 >>= _1n;
|
|
655
|
-
k2 >>= _1n;
|
|
656
|
-
}
|
|
657
|
-
if (k1neg) k1p = k1p.negate();
|
|
658
|
-
if (k2neg) k2p = k2p.negate();
|
|
659
|
-
k2p = new Point(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz);
|
|
660
|
-
return k1p.add(k2p);
|
|
661
|
-
}
|
|
662
|
-
|
|
663
855
|
/**
|
|
664
856
|
* Constant time multiplication.
|
|
665
857
|
* Uses wNAF method. Windowed method may be 10% faster,
|
|
@@ -670,20 +862,19 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>): CurvePointsRes<T
|
|
|
670
862
|
* @returns New point
|
|
671
863
|
*/
|
|
672
864
|
multiply(scalar: bigint): Point {
|
|
673
|
-
const { endo
|
|
674
|
-
|
|
865
|
+
const { endo } = curveOpts;
|
|
866
|
+
if (!Fn.isValidNot0(scalar)) throw new Error('invalid scalar: out of range'); // 0 is invalid
|
|
675
867
|
let point: Point, fake: Point; // Fake point is used to const-time mult
|
|
868
|
+
const mul = (n: bigint) => wnaf.wNAFCached(this, n, Point.normalizeZ);
|
|
869
|
+
/** See docs for {@link EndomorphismOpts} */
|
|
676
870
|
if (endo) {
|
|
677
871
|
const { k1neg, k1, k2neg, k2 } = endo.splitScalar(scalar);
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
k2p = new Point(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz);
|
|
683
|
-
point = k1p.add(k2p);
|
|
684
|
-
fake = f1p.add(f2p);
|
|
872
|
+
const { p: k1p, f: k1f } = mul(k1);
|
|
873
|
+
const { p: k2p, f: k2f } = mul(k2);
|
|
874
|
+
fake = k1f.add(k2f);
|
|
875
|
+
point = finishEndo(endo.beta, k1p, k2p, k1neg, k2neg);
|
|
685
876
|
} else {
|
|
686
|
-
const { p, f } =
|
|
877
|
+
const { p, f } = mul(scalar);
|
|
687
878
|
point = p;
|
|
688
879
|
fake = f;
|
|
689
880
|
}
|
|
@@ -692,60 +883,88 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>): CurvePointsRes<T
|
|
|
692
883
|
}
|
|
693
884
|
|
|
694
885
|
/**
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
* @returns non-zero affine point
|
|
886
|
+
* Non-constant-time multiplication. Uses double-and-add algorithm.
|
|
887
|
+
* It's faster, but should only be used when you don't care about
|
|
888
|
+
* an exposed private key e.g. sig verification, which works over *public* keys.
|
|
699
889
|
*/
|
|
890
|
+
multiplyUnsafe(sc: bigint): Point {
|
|
891
|
+
const { endo } = curveOpts;
|
|
892
|
+
const p = this;
|
|
893
|
+
if (!Fn.isValid(sc)) throw new Error('invalid scalar: out of range'); // 0 is valid
|
|
894
|
+
if (sc === _0n || p.is0()) return Point.ZERO;
|
|
895
|
+
if (sc === _1n) return p; // fast-path
|
|
896
|
+
if (wnaf.hasPrecomputes(this)) return this.multiply(sc);
|
|
897
|
+
if (endo) {
|
|
898
|
+
const { k1neg, k1, k2neg, k2 } = endo.splitScalar(sc);
|
|
899
|
+
// `wNAFCachedUnsafe` is 30% slower
|
|
900
|
+
const { p1, p2 } = mulEndoUnsafe(Point, p, k1, k2);
|
|
901
|
+
return finishEndo(endo.beta, p1, p2, k1neg, k2neg);
|
|
902
|
+
} else {
|
|
903
|
+
return wnaf.wNAFCachedUnsafe(p, sc);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
700
907
|
multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined {
|
|
701
|
-
const
|
|
702
|
-
const mul = (
|
|
703
|
-
P: Point,
|
|
704
|
-
a: bigint // Select faster multiply() method
|
|
705
|
-
) => (a === _0n || a === _1n || !P.equals(G) ? P.multiplyUnsafe(a) : P.multiply(a));
|
|
706
|
-
const sum = mul(this, a).add(mul(Q, b));
|
|
908
|
+
const sum = this.multiplyUnsafe(a).add(Q.multiplyUnsafe(b));
|
|
707
909
|
return sum.is0() ? undefined : sum;
|
|
708
910
|
}
|
|
709
911
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
912
|
+
/**
|
|
913
|
+
* Converts Projective point to affine (x, y) coordinates.
|
|
914
|
+
* @param invertedZ Z^-1 (inverted zero) - optional, precomputation is useful for invertBatch
|
|
915
|
+
*/
|
|
916
|
+
toAffine(invertedZ?: T): AffinePoint<T> {
|
|
917
|
+
return toAffineMemo(this, invertedZ);
|
|
715
918
|
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Checks whether Point is free of torsion elements (is in prime subgroup).
|
|
922
|
+
* Always torsion-free for cofactor=1 curves.
|
|
923
|
+
*/
|
|
716
924
|
isTorsionFree(): boolean {
|
|
717
|
-
const {
|
|
718
|
-
if (cofactor === _1n) return true;
|
|
925
|
+
const { isTorsionFree } = curveOpts;
|
|
926
|
+
if (cofactor === _1n) return true;
|
|
719
927
|
if (isTorsionFree) return isTorsionFree(Point, this);
|
|
720
|
-
|
|
928
|
+
return wnaf.wNAFCachedUnsafe(this, CURVE_ORDER).is0();
|
|
721
929
|
}
|
|
930
|
+
|
|
722
931
|
clearCofactor(): Point {
|
|
723
|
-
const {
|
|
932
|
+
const { clearCofactor } = curveOpts;
|
|
724
933
|
if (cofactor === _1n) return this; // Fast-path
|
|
725
934
|
if (clearCofactor) return clearCofactor(Point, this) as Point;
|
|
726
|
-
return this.multiplyUnsafe(
|
|
935
|
+
return this.multiplyUnsafe(cofactor);
|
|
727
936
|
}
|
|
728
937
|
|
|
729
|
-
|
|
938
|
+
toBytes(isCompressed = true): Uint8Array {
|
|
730
939
|
abool('isCompressed', isCompressed);
|
|
731
940
|
this.assertValidity();
|
|
732
941
|
return toBytes(Point, this, isCompressed);
|
|
733
942
|
}
|
|
734
943
|
|
|
944
|
+
/** @deprecated use `toBytes` */
|
|
945
|
+
toRawBytes(isCompressed = true): Uint8Array {
|
|
946
|
+
return this.toBytes(isCompressed);
|
|
947
|
+
}
|
|
948
|
+
|
|
735
949
|
toHex(isCompressed = true): string {
|
|
736
|
-
|
|
737
|
-
|
|
950
|
+
return bytesToHex(this.toBytes(isCompressed));
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
toString() {
|
|
954
|
+
return `<Point ${this.is0() ? 'ZERO' : this.toHex()}>`;
|
|
738
955
|
}
|
|
739
956
|
}
|
|
740
|
-
const
|
|
741
|
-
const wnaf = wNAF(Point,
|
|
742
|
-
return
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
};
|
|
957
|
+
const bits = Fn.BITS;
|
|
958
|
+
const wnaf = wNAF(Point, curveOpts.endo ? Math.ceil(bits / 2) : bits);
|
|
959
|
+
return Point;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// _legacyWeierstrass
|
|
963
|
+
/** @deprecated use `weierstrassN` */
|
|
964
|
+
export function weierstrassPoints<T>(c: CurvePointsTypeWithLength<T>): CurvePointsRes<T> {
|
|
965
|
+
const { CURVE, curveOpts } = _weierstrass_legacy_opts_to_new(c);
|
|
966
|
+
const Point = weierstrassN(CURVE, curveOpts);
|
|
967
|
+
return _weierstrass_new_output_to_legacy(c, Point);
|
|
749
968
|
}
|
|
750
969
|
|
|
751
970
|
// Instance
|
|
@@ -760,57 +979,44 @@ export interface SignatureType {
|
|
|
760
979
|
recoverPublicKey(msgHash: Hex): ProjPointType<bigint>;
|
|
761
980
|
toCompactRawBytes(): Uint8Array;
|
|
762
981
|
toCompactHex(): string;
|
|
763
|
-
toDERRawBytes(
|
|
764
|
-
toDERHex(
|
|
982
|
+
toDERRawBytes(): Uint8Array;
|
|
983
|
+
toDERHex(): string;
|
|
984
|
+
// toBytes(format?: string): Uint8Array;
|
|
765
985
|
}
|
|
766
986
|
export type RecoveredSignatureType = SignatureType & {
|
|
767
987
|
readonly recovery: number;
|
|
768
988
|
};
|
|
769
989
|
// Static methods
|
|
770
990
|
export type SignatureConstructor = {
|
|
771
|
-
new (r: bigint, s: bigint): SignatureType;
|
|
991
|
+
new (r: bigint, s: bigint, recovery?: number): SignatureType;
|
|
772
992
|
fromCompact(hex: Hex): SignatureType;
|
|
773
993
|
fromDER(hex: Hex): SignatureType;
|
|
774
994
|
};
|
|
775
|
-
type SignatureLike = { r: bigint; s: bigint };
|
|
776
|
-
|
|
995
|
+
export type SignatureLike = { r: bigint; s: bigint };
|
|
777
996
|
export type PubKey = Hex | ProjPointType<bigint>;
|
|
778
997
|
|
|
779
998
|
export type CurveType = BasicWCurve<bigint> & {
|
|
780
999
|
hash: CHash; // CHash not FHash because we need outputLen for DRBG
|
|
781
|
-
hmac
|
|
782
|
-
randomBytes
|
|
1000
|
+
hmac?: HmacFnSync;
|
|
1001
|
+
randomBytes?: (bytesLength?: number) => Uint8Array;
|
|
783
1002
|
lowS?: boolean;
|
|
784
1003
|
bits2int?: (bytes: Uint8Array) => bigint;
|
|
785
1004
|
bits2int_modN?: (bytes: Uint8Array) => bigint;
|
|
786
1005
|
};
|
|
787
1006
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
const opts = validateBasic(curve);
|
|
792
|
-
validateObject(
|
|
793
|
-
opts,
|
|
794
|
-
{
|
|
795
|
-
hash: 'hash',
|
|
796
|
-
hmac: 'function',
|
|
797
|
-
randomBytes: 'function',
|
|
798
|
-
},
|
|
799
|
-
{
|
|
800
|
-
bits2int: 'function',
|
|
801
|
-
bits2int_modN: 'function',
|
|
802
|
-
lowS: 'boolean',
|
|
803
|
-
}
|
|
804
|
-
);
|
|
805
|
-
return Object.freeze({ lowS: true, ...opts } as const);
|
|
1007
|
+
// Points start with byte 0x02 when y is even; otherwise 0x03
|
|
1008
|
+
function pprefix(hasEvenY: boolean): Uint8Array {
|
|
1009
|
+
return Uint8Array.of(hasEvenY ? 0x02 : 0x03);
|
|
806
1010
|
}
|
|
807
1011
|
|
|
808
1012
|
export type CurveFn = {
|
|
809
|
-
CURVE:
|
|
1013
|
+
CURVE: CurvePointsType<bigint>;
|
|
810
1014
|
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
|
|
811
1015
|
getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array;
|
|
812
1016
|
sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => RecoveredSignatureType;
|
|
813
1017
|
verify: (signature: Hex | SignatureLike, msgHash: Hex, publicKey: Hex, opts?: VerOpts) => boolean;
|
|
1018
|
+
Point: ProjConstructor<bigint>;
|
|
1019
|
+
/** @deprecated use `Point` */
|
|
814
1020
|
ProjectivePoint: ProjConstructor<bigint>;
|
|
815
1021
|
Signature: SignatureConstructor;
|
|
816
1022
|
utils: {
|
|
@@ -821,80 +1027,30 @@ export type CurveFn = {
|
|
|
821
1027
|
};
|
|
822
1028
|
};
|
|
823
1029
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
function invN(a: bigint) {
|
|
841
|
-
return invert(a, CURVE_ORDER);
|
|
842
|
-
}
|
|
1030
|
+
export function ecdsa(
|
|
1031
|
+
Point: ProjConstructor<bigint>,
|
|
1032
|
+
ecdsaOpts: ECDSAOpts,
|
|
1033
|
+
curveOpts: WeierstrassExtraOpts<bigint> = {}
|
|
1034
|
+
): ECDSA {
|
|
1035
|
+
_validateObject(
|
|
1036
|
+
ecdsaOpts,
|
|
1037
|
+
{ hash: 'function' },
|
|
1038
|
+
{
|
|
1039
|
+
hmac: 'function',
|
|
1040
|
+
lowS: 'boolean',
|
|
1041
|
+
randomBytes: 'function',
|
|
1042
|
+
bits2int: 'function',
|
|
1043
|
+
bits2int_modN: 'function',
|
|
1044
|
+
}
|
|
1045
|
+
);
|
|
843
1046
|
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
} =
|
|
850
|
-
|
|
851
|
-
toBytes(_c, point, isCompressed: boolean): Uint8Array {
|
|
852
|
-
const a = point.toAffine();
|
|
853
|
-
const x = Fp.toBytes(a.x);
|
|
854
|
-
const cat = concatBytes;
|
|
855
|
-
abool('isCompressed', isCompressed);
|
|
856
|
-
if (isCompressed) {
|
|
857
|
-
return cat(Uint8Array.from([point.hasEvenY() ? 0x02 : 0x03]), x);
|
|
858
|
-
} else {
|
|
859
|
-
return cat(Uint8Array.from([0x04]), x, Fp.toBytes(a.y));
|
|
860
|
-
}
|
|
861
|
-
},
|
|
862
|
-
fromBytes(bytes: Uint8Array) {
|
|
863
|
-
const len = bytes.length;
|
|
864
|
-
const head = bytes[0];
|
|
865
|
-
const tail = bytes.subarray(1);
|
|
866
|
-
// this.assertValidity() is done inside of fromHex
|
|
867
|
-
if (len === compressedLen && (head === 0x02 || head === 0x03)) {
|
|
868
|
-
const x = bytesToNumberBE(tail);
|
|
869
|
-
if (!inRange(x, _1n, Fp.ORDER)) throw new Error('Point is not on curve');
|
|
870
|
-
const y2 = weierstrassEquation(x); // y² = x³ + ax + b
|
|
871
|
-
let y: bigint;
|
|
872
|
-
try {
|
|
873
|
-
y = Fp.sqrt(y2); // y = y² ^ (p+1)/4
|
|
874
|
-
} catch (sqrtError) {
|
|
875
|
-
const suffix = sqrtError instanceof Error ? ': ' + sqrtError.message : '';
|
|
876
|
-
throw new Error('Point is not on curve' + suffix);
|
|
877
|
-
}
|
|
878
|
-
const isYOdd = (y & _1n) === _1n;
|
|
879
|
-
// ECDSA
|
|
880
|
-
const isHeadOdd = (head & 1) === 1;
|
|
881
|
-
if (isHeadOdd !== isYOdd) y = Fp.neg(y);
|
|
882
|
-
return { x, y };
|
|
883
|
-
} else if (len === uncompressedLen && head === 0x04) {
|
|
884
|
-
const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES));
|
|
885
|
-
const y = Fp.fromBytes(tail.subarray(Fp.BYTES, 2 * Fp.BYTES));
|
|
886
|
-
return { x, y };
|
|
887
|
-
} else {
|
|
888
|
-
const cl = compressedLen;
|
|
889
|
-
const ul = uncompressedLen;
|
|
890
|
-
throw new Error(
|
|
891
|
-
'invalid Point, expected length of ' + cl + ', or uncompressed ' + ul + ', got ' + len
|
|
892
|
-
);
|
|
893
|
-
}
|
|
894
|
-
},
|
|
895
|
-
});
|
|
896
|
-
const numToNByteHex = (num: bigint): string =>
|
|
897
|
-
bytesToHex(numberToBytesBE(num, CURVE.nByteLength));
|
|
1047
|
+
const randomBytes_ = ecdsaOpts.randomBytes || randomBytes;
|
|
1048
|
+
const hmac_: HmacFnSync =
|
|
1049
|
+
ecdsaOpts.hmac ||
|
|
1050
|
+
(((key, ...msgs) => hmac(ecdsaOpts.hash, key, concatBytes(...msgs))) satisfies HmacFnSync);
|
|
1051
|
+
|
|
1052
|
+
const { Fp, Fn } = Point;
|
|
1053
|
+
const { ORDER: CURVE_ORDER, BITS: fnBits } = Fn;
|
|
898
1054
|
|
|
899
1055
|
function isBiggerThanHalfOrder(number: bigint) {
|
|
900
1056
|
const HALF = CURVE_ORDER >> _1n;
|
|
@@ -902,10 +1058,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
902
1058
|
}
|
|
903
1059
|
|
|
904
1060
|
function normalizeS(s: bigint) {
|
|
905
|
-
return isBiggerThanHalfOrder(s) ?
|
|
1061
|
+
return isBiggerThanHalfOrder(s) ? Fn.neg(s) : s;
|
|
1062
|
+
}
|
|
1063
|
+
function aValidRS(title: string, num: bigint) {
|
|
1064
|
+
if (!Fn.isValidNot0(num))
|
|
1065
|
+
throw new Error(`invalid signature ${title}: out of range 1..CURVE.n`);
|
|
906
1066
|
}
|
|
907
|
-
// slice bytes num
|
|
908
|
-
const slcNum = (b: Uint8Array, from: number, to: number) => bytesToNumberBE(b.slice(from, to));
|
|
909
1067
|
|
|
910
1068
|
/**
|
|
911
1069
|
* ECDSA signature with its (r, s) properties. Supports DER & compact representations.
|
|
@@ -915,8 +1073,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
915
1073
|
readonly s: bigint;
|
|
916
1074
|
readonly recovery?: number;
|
|
917
1075
|
constructor(r: bigint, s: bigint, recovery?: number) {
|
|
918
|
-
|
|
919
|
-
|
|
1076
|
+
aValidRS('r', r); // r in [1..N-1]
|
|
1077
|
+
aValidRS('s', s); // s in [1..N-1]
|
|
920
1078
|
this.r = r;
|
|
921
1079
|
this.s = s;
|
|
922
1080
|
if (recovery != null) this.recovery = recovery;
|
|
@@ -925,9 +1083,9 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
925
1083
|
|
|
926
1084
|
// pair (bytes of r, bytes of s)
|
|
927
1085
|
static fromCompact(hex: Hex) {
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
return new Signature(
|
|
1086
|
+
const L = Fn.BYTES;
|
|
1087
|
+
const b = ensureBytes('compactSignature', hex, L * 2);
|
|
1088
|
+
return new Signature(Fn.fromBytes(b.subarray(0, L)), Fn.fromBytes(b.subarray(L, L * 2)));
|
|
931
1089
|
}
|
|
932
1090
|
|
|
933
1091
|
// DER encoded ECDSA signature
|
|
@@ -947,19 +1105,34 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
947
1105
|
return new Signature(this.r, this.s, recovery) as RecoveredSignature;
|
|
948
1106
|
}
|
|
949
1107
|
|
|
1108
|
+
// ProjPointType<bigint>
|
|
950
1109
|
recoverPublicKey(msgHash: Hex): typeof Point.BASE {
|
|
1110
|
+
const FIELD_ORDER = Fp.ORDER;
|
|
951
1111
|
const { r, s, recovery: rec } = this;
|
|
952
|
-
const h = bits2int_modN(ensureBytes('msgHash', msgHash)); // Truncate hash
|
|
953
1112
|
if (rec == null || ![0, 1, 2, 3].includes(rec)) throw new Error('recovery id invalid');
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1113
|
+
|
|
1114
|
+
// ECDSA recovery is hard for cofactor > 1 curves.
|
|
1115
|
+
// In sign, `r = q.x mod n`, and here we recover q.x from r.
|
|
1116
|
+
// While recovering q.x >= n, we need to add r+n for cofactor=1 curves.
|
|
1117
|
+
// However, for cofactor>1, r+n may not get q.x:
|
|
1118
|
+
// r+n*i would need to be done instead where i is unknown.
|
|
1119
|
+
// To easily get i, we either need to:
|
|
1120
|
+
// a. increase amount of valid recid values (4, 5...); OR
|
|
1121
|
+
// b. prohibit non-prime-order signatures (recid > 1).
|
|
1122
|
+
const hasCofactor = CURVE_ORDER * _2n < FIELD_ORDER;
|
|
1123
|
+
if (hasCofactor && rec > 1) throw new Error('recovery id is ambiguous for h>1 curve');
|
|
1124
|
+
|
|
1125
|
+
const radj = rec === 2 || rec === 3 ? r + CURVE_ORDER : r;
|
|
1126
|
+
if (!Fp.isValid(radj)) throw new Error('recovery id 2 or 3 invalid');
|
|
1127
|
+
const x = Fp.toBytes(radj);
|
|
1128
|
+
const R = Point.fromHex(concatBytes(pprefix((rec & 1) === 0), x));
|
|
1129
|
+
const ir = Fn.inv(radj); // r^-1
|
|
1130
|
+
const h = bits2int_modN(ensureBytes('msgHash', msgHash)); // Truncate hash
|
|
1131
|
+
const u1 = Fn.create(-h * ir); // -hr^-1
|
|
1132
|
+
const u2 = Fn.create(s * ir); // sr^-1
|
|
1133
|
+
// (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1). unsafe is fine: there is no private data.
|
|
1134
|
+
const Q = Point.BASE.multiplyUnsafe(u1).add(R.multiplyUnsafe(u2));
|
|
1135
|
+
if (Q.is0()) throw new Error('point at infinify');
|
|
963
1136
|
Q.assertValidity();
|
|
964
1137
|
return Q;
|
|
965
1138
|
}
|
|
@@ -970,27 +1143,39 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
970
1143
|
}
|
|
971
1144
|
|
|
972
1145
|
normalizeS() {
|
|
973
|
-
return this.hasHighS() ? new Signature(this.r,
|
|
1146
|
+
return this.hasHighS() ? new Signature(this.r, Fn.neg(this.s), this.recovery) : this;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
toBytes(format: 'compact' | 'der') {
|
|
1150
|
+
if (format === 'compact') return concatBytes(Fn.toBytes(this.r), Fn.toBytes(this.s));
|
|
1151
|
+
if (format === 'der') return hexToBytes(DER.hexFromSig(this));
|
|
1152
|
+
throw new Error('invalid format');
|
|
974
1153
|
}
|
|
975
1154
|
|
|
976
1155
|
// DER-encoded
|
|
977
1156
|
toDERRawBytes() {
|
|
978
|
-
return
|
|
1157
|
+
return this.toBytes('der');
|
|
979
1158
|
}
|
|
980
1159
|
toDERHex() {
|
|
981
|
-
return
|
|
1160
|
+
return bytesToHex(this.toBytes('der'));
|
|
982
1161
|
}
|
|
983
1162
|
|
|
984
1163
|
// padded bytes of r, then padded bytes of s
|
|
985
1164
|
toCompactRawBytes() {
|
|
986
|
-
return
|
|
1165
|
+
return this.toBytes('compact');
|
|
987
1166
|
}
|
|
988
1167
|
toCompactHex() {
|
|
989
|
-
return
|
|
1168
|
+
return bytesToHex(this.toBytes('compact'));
|
|
990
1169
|
}
|
|
991
1170
|
}
|
|
992
1171
|
type RecoveredSignature = Signature & { recovery: number };
|
|
993
1172
|
|
|
1173
|
+
const normPrivateKeyToScalar = _legacyHelperNormPriv(
|
|
1174
|
+
Fn,
|
|
1175
|
+
curveOpts.allowedPrivateKeyLengths,
|
|
1176
|
+
curveOpts.wrapPrivateKey
|
|
1177
|
+
);
|
|
1178
|
+
|
|
994
1179
|
const utils = {
|
|
995
1180
|
isValidPrivateKey(privateKey: PrivKey) {
|
|
996
1181
|
try {
|
|
@@ -1007,22 +1192,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
1007
1192
|
* (groupLen + ceil(groupLen / 2)) with modulo bias being negligible.
|
|
1008
1193
|
*/
|
|
1009
1194
|
randomPrivateKey: (): Uint8Array => {
|
|
1010
|
-
const
|
|
1011
|
-
return mapHashToField(
|
|
1195
|
+
const n = CURVE_ORDER;
|
|
1196
|
+
return mapHashToField(randomBytes_(getMinHashLength(n)), n);
|
|
1012
1197
|
},
|
|
1013
1198
|
|
|
1014
|
-
/**
|
|
1015
|
-
* Creates precompute table for an arbitrary EC point. Makes point "cached".
|
|
1016
|
-
* Allows to massively speed-up `point.multiply(scalar)`.
|
|
1017
|
-
* @returns cached point
|
|
1018
|
-
* @example
|
|
1019
|
-
* const fast = utils.precompute(8, ProjectivePoint.fromHex(someonesPubKey));
|
|
1020
|
-
* fast.multiply(privKey); // much faster ECDH now
|
|
1021
|
-
*/
|
|
1022
1199
|
precompute(windowSize = 8, point = Point.BASE): typeof Point.BASE {
|
|
1023
|
-
point.
|
|
1024
|
-
point.multiply(BigInt(3)); // 3 is arbitrary, just need any number here
|
|
1025
|
-
return point;
|
|
1200
|
+
return point.precompute(windowSize, false);
|
|
1026
1201
|
},
|
|
1027
1202
|
};
|
|
1028
1203
|
|
|
@@ -1033,20 +1208,25 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
1033
1208
|
* @returns Public key, full when isCompressed=false; short when isCompressed=true
|
|
1034
1209
|
*/
|
|
1035
1210
|
function getPublicKey(privateKey: PrivKey, isCompressed = true): Uint8Array {
|
|
1036
|
-
return Point.fromPrivateKey(privateKey).
|
|
1211
|
+
return Point.fromPrivateKey(privateKey).toBytes(isCompressed);
|
|
1037
1212
|
}
|
|
1038
1213
|
|
|
1039
1214
|
/**
|
|
1040
1215
|
* Quick and dirty check for item being public key. Does not validate hex, or being on-curve.
|
|
1041
1216
|
*/
|
|
1042
|
-
function isProbPub(item: PrivKey | PubKey): boolean {
|
|
1043
|
-
|
|
1044
|
-
const str = typeof item === 'string';
|
|
1045
|
-
const len = (arr || str) && (item as Hex).length;
|
|
1046
|
-
if (arr) return len === compressedLen || len === uncompressedLen;
|
|
1047
|
-
if (str) return len === 2 * compressedLen || len === 2 * uncompressedLen;
|
|
1217
|
+
function isProbPub(item: PrivKey | PubKey): boolean | undefined {
|
|
1218
|
+
if (typeof item === 'bigint') return false;
|
|
1048
1219
|
if (item instanceof Point) return true;
|
|
1049
|
-
|
|
1220
|
+
const arr = ensureBytes('key', item);
|
|
1221
|
+
const length = arr.length;
|
|
1222
|
+
const L = Fp.BYTES;
|
|
1223
|
+
const LC = L + 1; // e.g. 33 for 32
|
|
1224
|
+
const LU = 2 * L + 1; // e.g. 65 for 32
|
|
1225
|
+
if (curveOpts.allowedPrivateKeyLengths || Fn.BYTES === LC) {
|
|
1226
|
+
return undefined;
|
|
1227
|
+
} else {
|
|
1228
|
+
return length === LC || length === LU;
|
|
1229
|
+
}
|
|
1050
1230
|
}
|
|
1051
1231
|
|
|
1052
1232
|
/**
|
|
@@ -1060,10 +1240,10 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
1060
1240
|
* @returns shared public key
|
|
1061
1241
|
*/
|
|
1062
1242
|
function getSharedSecret(privateA: PrivKey, publicB: Hex, isCompressed = true): Uint8Array {
|
|
1063
|
-
if (isProbPub(privateA)) throw new Error('first arg must be private key');
|
|
1064
|
-
if (
|
|
1243
|
+
if (isProbPub(privateA) === true) throw new Error('first arg must be private key');
|
|
1244
|
+
if (isProbPub(publicB) === false) throw new Error('second arg must be public key');
|
|
1065
1245
|
const b = Point.fromHex(publicB); // check for being on-curve
|
|
1066
|
-
return b.multiply(normPrivateKeyToScalar(privateA)).
|
|
1246
|
+
return b.multiply(normPrivateKeyToScalar(privateA)).toBytes(isCompressed);
|
|
1067
1247
|
}
|
|
1068
1248
|
|
|
1069
1249
|
// RFC6979: ensure ECDSA msg is X bytes and < N. RFC suggests optional truncating via bits2octets.
|
|
@@ -1071,30 +1251,30 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
1071
1251
|
// bits2int can produce res>N, we can do mod(res, N) since the bitLen is the same.
|
|
1072
1252
|
// int2octets can't be used; pads small msgs with 0: unacceptatble for trunc as per RFC vectors
|
|
1073
1253
|
const bits2int =
|
|
1074
|
-
|
|
1254
|
+
ecdsaOpts.bits2int ||
|
|
1075
1255
|
function (bytes: Uint8Array): bigint {
|
|
1076
|
-
// Our custom check "just in case"
|
|
1256
|
+
// Our custom check "just in case", for protection against DoS
|
|
1077
1257
|
if (bytes.length > 8192) throw new Error('input is too large');
|
|
1078
1258
|
// For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m)
|
|
1079
1259
|
// for some cases, since bytes.length * 8 is not actual bitLength.
|
|
1080
1260
|
const num = bytesToNumberBE(bytes); // check for == u8 done here
|
|
1081
|
-
const delta = bytes.length * 8 -
|
|
1261
|
+
const delta = bytes.length * 8 - fnBits; // truncate to nBitLength leftmost bits
|
|
1082
1262
|
return delta > 0 ? num >> BigInt(delta) : num;
|
|
1083
1263
|
};
|
|
1084
1264
|
const bits2int_modN =
|
|
1085
|
-
|
|
1265
|
+
ecdsaOpts.bits2int_modN ||
|
|
1086
1266
|
function (bytes: Uint8Array): bigint {
|
|
1087
|
-
return
|
|
1267
|
+
return Fn.create(bits2int(bytes)); // can't use bytesToNumberBE here
|
|
1088
1268
|
};
|
|
1089
1269
|
// NOTE: pads output with zero as per spec
|
|
1090
|
-
const ORDER_MASK = bitMask(
|
|
1270
|
+
const ORDER_MASK = bitMask(fnBits);
|
|
1091
1271
|
/**
|
|
1092
1272
|
* Converts to bytes. Checks if num in `[0..ORDER_MASK-1]` e.g.: `[0..2^256-1]`.
|
|
1093
1273
|
*/
|
|
1094
1274
|
function int2octets(num: bigint): Uint8Array {
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
return
|
|
1275
|
+
// IMPORTANT: the check ensures working for case `Fn.BYTES != Fn.BITS * 8`
|
|
1276
|
+
aInRange('num < 2^' + fnBits, num, _0n, ORDER_MASK);
|
|
1277
|
+
return Fn.toBytes(num);
|
|
1098
1278
|
}
|
|
1099
1279
|
|
|
1100
1280
|
// Steps A, D of RFC6979 3.2
|
|
@@ -1105,7 +1285,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
1105
1285
|
function prepSig(msgHash: Hex, privateKey: PrivKey, opts = defaultSigOpts) {
|
|
1106
1286
|
if (['recovered', 'canonical'].some((k) => k in opts))
|
|
1107
1287
|
throw new Error('sign() legacy options not supported');
|
|
1108
|
-
const { hash
|
|
1288
|
+
const { hash } = ecdsaOpts;
|
|
1109
1289
|
let { lowS, prehash, extraEntropy: ent } = opts; // generates low-s sigs by default
|
|
1110
1290
|
if (lowS == null) lowS = true; // RFC6979 3.2: we skip step A, because we already provide hash
|
|
1111
1291
|
msgHash = ensureBytes('msgHash', msgHash);
|
|
@@ -1113,7 +1293,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
1113
1293
|
if (prehash) msgHash = ensureBytes('prehashed msgHash', hash(msgHash));
|
|
1114
1294
|
|
|
1115
1295
|
// We can't later call bits2octets, since nested bits2int is broken for curves
|
|
1116
|
-
// with
|
|
1296
|
+
// with fnBits % 8 !== 0. Because of that, we unwrap it here as int2octets call.
|
|
1117
1297
|
// const bits2octets = (bits) => int2octets(bits2int_modN(bits))
|
|
1118
1298
|
const h1int = bits2int_modN(msgHash);
|
|
1119
1299
|
const d = normPrivateKeyToScalar(privateKey); // validate private key, convert to bigint
|
|
@@ -1121,24 +1301,25 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
1121
1301
|
// extraEntropy. RFC6979 3.6: additional k' (optional).
|
|
1122
1302
|
if (ent != null && ent !== false) {
|
|
1123
1303
|
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
|
|
1124
|
-
const e = ent === true ?
|
|
1304
|
+
const e = ent === true ? randomBytes_(Fp.BYTES) : ent; // generate random bytes OR pass as-is
|
|
1125
1305
|
seedArgs.push(ensureBytes('extraEntropy', e)); // check for being bytes
|
|
1126
1306
|
}
|
|
1127
1307
|
const seed = concatBytes(...seedArgs); // Step D of RFC6979 3.2
|
|
1128
1308
|
const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!
|
|
1129
1309
|
// Converts signature params into point w r/s, checks result for validity.
|
|
1310
|
+
// Can use scalar blinding b^-1(bm + bdr) where b ∈ [1,q−1] according to
|
|
1311
|
+
// https://tches.iacr.org/index.php/TCHES/article/view/7337/6509. We've decided against it:
|
|
1312
|
+
// a) dependency on CSPRNG b) 15% slowdown c) doesn't really help since bigints are not CT
|
|
1130
1313
|
function k2sig(kBytes: Uint8Array): RecoveredSignature | undefined {
|
|
1131
1314
|
// RFC 6979 Section 3.2, step 3: k = bits2int(T)
|
|
1315
|
+
// Important: all mod() calls here must be done over N
|
|
1132
1316
|
const k = bits2int(kBytes); // Cannot use fields methods, since it is group element
|
|
1133
|
-
if (!
|
|
1134
|
-
const ik =
|
|
1317
|
+
if (!Fn.isValidNot0(k)) return; // Valid scalars (including k) must be in 1..N-1
|
|
1318
|
+
const ik = Fn.inv(k); // k^-1 mod n
|
|
1135
1319
|
const q = Point.BASE.multiply(k).toAffine(); // q = Gk
|
|
1136
|
-
const r =
|
|
1320
|
+
const r = Fn.create(q.x); // r = q.x mod n
|
|
1137
1321
|
if (r === _0n) return;
|
|
1138
|
-
|
|
1139
|
-
// https://tches.iacr.org/index.php/TCHES/article/view/7337/6509. We've decided against it:
|
|
1140
|
-
// a) dependency on CSPRNG b) 15% slowdown c) doesn't really help since bigints are not CT
|
|
1141
|
-
const s = modN(ik * modN(m + r * d)); // Not using blinding here
|
|
1322
|
+
const s = Fn.create(ik * Fn.create(m + r * d)); // Not using blinding here, see comment above
|
|
1142
1323
|
if (s === _0n) return;
|
|
1143
1324
|
let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); // recovery bit (2 or 3, when q.x > n)
|
|
1144
1325
|
let normS = s;
|
|
@@ -1150,8 +1331,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
1150
1331
|
}
|
|
1151
1332
|
return { seed, k2sig };
|
|
1152
1333
|
}
|
|
1153
|
-
const defaultSigOpts: SignOpts = { lowS:
|
|
1154
|
-
const defaultVerOpts: VerOpts = { lowS:
|
|
1334
|
+
const defaultSigOpts: SignOpts = { lowS: ecdsaOpts.lowS, prehash: false };
|
|
1335
|
+
const defaultVerOpts: VerOpts = { lowS: ecdsaOpts.lowS, prehash: false };
|
|
1155
1336
|
|
|
1156
1337
|
/**
|
|
1157
1338
|
* Signs message hash with a private key.
|
|
@@ -1168,14 +1349,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
1168
1349
|
*/
|
|
1169
1350
|
function sign(msgHash: Hex, privKey: PrivKey, opts = defaultSigOpts): RecoveredSignature {
|
|
1170
1351
|
const { seed, k2sig } = prepSig(msgHash, privKey, opts); // Steps A, D of RFC6979 3.2.
|
|
1171
|
-
const
|
|
1172
|
-
const drbg = createHmacDrbg<RecoveredSignature>(C.hash.outputLen, C.nByteLength, C.hmac);
|
|
1352
|
+
const drbg = createHmacDrbg<RecoveredSignature>(ecdsaOpts.hash.outputLen, Fn.BYTES, hmac_);
|
|
1173
1353
|
return drbg(seed, k2sig); // Steps B, C, D, E, F, G
|
|
1174
1354
|
}
|
|
1175
1355
|
|
|
1176
1356
|
// Enable precomputes. Slows down first publicKey computation by 20ms.
|
|
1177
|
-
Point.BASE.
|
|
1178
|
-
// utils.precompute(8, ProjectivePoint.BASE)
|
|
1357
|
+
Point.BASE.precompute(8);
|
|
1179
1358
|
|
|
1180
1359
|
/**
|
|
1181
1360
|
* Verifies a signature against message hash and public key.
|
|
@@ -1199,13 +1378,16 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
1199
1378
|
const sg = signature;
|
|
1200
1379
|
msgHash = ensureBytes('msgHash', msgHash);
|
|
1201
1380
|
publicKey = ensureBytes('publicKey', publicKey);
|
|
1202
|
-
const { lowS, prehash, format } = opts;
|
|
1203
1381
|
|
|
1204
|
-
// Verify opts
|
|
1382
|
+
// Verify opts
|
|
1205
1383
|
validateSigVerOpts(opts);
|
|
1384
|
+
const { lowS, prehash, format } = opts;
|
|
1385
|
+
|
|
1386
|
+
// TODO: remove
|
|
1206
1387
|
if ('strict' in opts) throw new Error('options.strict was renamed to lowS');
|
|
1207
|
-
|
|
1208
|
-
|
|
1388
|
+
|
|
1389
|
+
if (format !== undefined && !['compact', 'der', 'js'].includes(format))
|
|
1390
|
+
throw new Error('format must be "compact", "der" or "js"');
|
|
1209
1391
|
const isHex = typeof sg === 'string' || isBytes(sg);
|
|
1210
1392
|
const isObj =
|
|
1211
1393
|
!isHex &&
|
|
@@ -1216,14 +1398,31 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
1216
1398
|
typeof sg.s === 'bigint';
|
|
1217
1399
|
if (!isHex && !isObj)
|
|
1218
1400
|
throw new Error('invalid signature, expected Uint8Array, hex string or Signature instance');
|
|
1219
|
-
|
|
1220
1401
|
let _sig: Signature | undefined = undefined;
|
|
1221
1402
|
let P: ProjPointType<bigint>;
|
|
1403
|
+
|
|
1404
|
+
// deduce signature format
|
|
1222
1405
|
try {
|
|
1223
|
-
if (
|
|
1406
|
+
// if (format === 'js') {
|
|
1407
|
+
// if (sg != null && !isBytes(sg)) _sig = new Signature(sg.r, sg.s);
|
|
1408
|
+
// } else if (format === 'compact') {
|
|
1409
|
+
// _sig = Signature.fromCompact(sg);
|
|
1410
|
+
// } else if (format === 'der') {
|
|
1411
|
+
// _sig = Signature.fromDER(sg);
|
|
1412
|
+
// } else {
|
|
1413
|
+
// throw new Error('invalid format');
|
|
1414
|
+
// }
|
|
1415
|
+
if (isObj) {
|
|
1416
|
+
if (format === undefined || format === 'js') {
|
|
1417
|
+
_sig = new Signature(sg.r, sg.s);
|
|
1418
|
+
} else {
|
|
1419
|
+
throw new Error('invalid format');
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1224
1422
|
if (isHex) {
|
|
1225
|
-
//
|
|
1226
|
-
//
|
|
1423
|
+
// TODO: remove this malleable check
|
|
1424
|
+
// Signature can be represented in 2 ways: compact (2*Fn.BYTES) & DER (variable-length).
|
|
1425
|
+
// Since DER can also be 2*Fn.BYTES bytes, we check for it first.
|
|
1227
1426
|
try {
|
|
1228
1427
|
if (format !== 'compact') _sig = Signature.fromDER(sg);
|
|
1229
1428
|
} catch (derError) {
|
|
@@ -1237,27 +1436,118 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
1237
1436
|
}
|
|
1238
1437
|
if (!_sig) return false;
|
|
1239
1438
|
if (lowS && _sig.hasHighS()) return false;
|
|
1240
|
-
|
|
1439
|
+
// todo: optional.hash => hash
|
|
1440
|
+
if (prehash) msgHash = ecdsaOpts.hash(msgHash);
|
|
1241
1441
|
const { r, s } = _sig;
|
|
1242
1442
|
const h = bits2int_modN(msgHash); // Cannot use fields methods, since it is group element
|
|
1243
|
-
const is =
|
|
1244
|
-
const u1 =
|
|
1245
|
-
const u2 =
|
|
1246
|
-
const R = Point.BASE.
|
|
1247
|
-
if (
|
|
1248
|
-
const v =
|
|
1443
|
+
const is = Fn.inv(s); // s^-1
|
|
1444
|
+
const u1 = Fn.create(h * is); // u1 = hs^-1 mod n
|
|
1445
|
+
const u2 = Fn.create(r * is); // u2 = rs^-1 mod n
|
|
1446
|
+
const R = Point.BASE.multiplyUnsafe(u1).add(P.multiplyUnsafe(u2));
|
|
1447
|
+
if (R.is0()) return false;
|
|
1448
|
+
const v = Fn.create(R.x); // v = r.x mod n
|
|
1249
1449
|
return v === r;
|
|
1250
1450
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1451
|
+
// TODO: clarify API for cloning .clone({hash: sha512}) ? .createWith({hash: sha512})?
|
|
1452
|
+
// const clone = (hash: CHash): ECDSA => ecdsa(Point, { ...ecdsaOpts, ...getHash(hash) }, curveOpts);
|
|
1453
|
+
return Object.freeze({
|
|
1253
1454
|
getPublicKey,
|
|
1254
1455
|
getSharedSecret,
|
|
1255
1456
|
sign,
|
|
1256
1457
|
verify,
|
|
1257
|
-
ProjectivePoint: Point,
|
|
1258
|
-
Signature,
|
|
1259
1458
|
utils,
|
|
1459
|
+
Point,
|
|
1460
|
+
Signature,
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
export type WsPointComposed<T> = {
|
|
1465
|
+
CURVE: WeierstrassOpts<T>;
|
|
1466
|
+
curveOpts: WeierstrassExtraOpts<T>;
|
|
1467
|
+
};
|
|
1468
|
+
export type WsComposed = {
|
|
1469
|
+
CURVE: WeierstrassOpts<bigint>;
|
|
1470
|
+
curveOpts: WeierstrassExtraOpts<bigint>;
|
|
1471
|
+
ecdsaOpts: ECDSAOpts;
|
|
1472
|
+
};
|
|
1473
|
+
function _weierstrass_legacy_opts_to_new<T>(c: CurvePointsType<T>): WsPointComposed<T> {
|
|
1474
|
+
const CURVE: WeierstrassOpts<T> = {
|
|
1475
|
+
a: c.a,
|
|
1476
|
+
b: c.b,
|
|
1477
|
+
p: c.Fp.ORDER,
|
|
1478
|
+
n: c.n,
|
|
1479
|
+
h: c.h,
|
|
1480
|
+
Gx: c.Gx,
|
|
1481
|
+
Gy: c.Gy,
|
|
1260
1482
|
};
|
|
1483
|
+
const Fp = c.Fp;
|
|
1484
|
+
const Fn = Field(CURVE.n, c.nBitLength);
|
|
1485
|
+
const curveOpts: WeierstrassExtraOpts<T> = {
|
|
1486
|
+
Fp,
|
|
1487
|
+
Fn,
|
|
1488
|
+
allowedPrivateKeyLengths: c.allowedPrivateKeyLengths,
|
|
1489
|
+
allowInfinityPoint: c.allowInfinityPoint,
|
|
1490
|
+
endo: c.endo,
|
|
1491
|
+
wrapPrivateKey: c.wrapPrivateKey,
|
|
1492
|
+
isTorsionFree: c.isTorsionFree,
|
|
1493
|
+
clearCofactor: c.clearCofactor,
|
|
1494
|
+
fromBytes: c.fromBytes,
|
|
1495
|
+
toBytes: c.toBytes,
|
|
1496
|
+
};
|
|
1497
|
+
return { CURVE, curveOpts };
|
|
1498
|
+
}
|
|
1499
|
+
function _ecdsa_legacy_opts_to_new(c: CurveType): WsComposed {
|
|
1500
|
+
const { CURVE, curveOpts } = _weierstrass_legacy_opts_to_new(c);
|
|
1501
|
+
const ecdsaOpts: ECDSAOpts = {
|
|
1502
|
+
hash: c.hash,
|
|
1503
|
+
hmac: c.hmac,
|
|
1504
|
+
randomBytes: c.randomBytes,
|
|
1505
|
+
lowS: c.lowS,
|
|
1506
|
+
bits2int: c.bits2int,
|
|
1507
|
+
bits2int_modN: c.bits2int_modN,
|
|
1508
|
+
};
|
|
1509
|
+
return { CURVE, curveOpts, ecdsaOpts };
|
|
1510
|
+
}
|
|
1511
|
+
function _weierstrass_new_output_to_legacy<T>(
|
|
1512
|
+
c: CurvePointsType<T>,
|
|
1513
|
+
Point: ProjConstructor<T>
|
|
1514
|
+
): CurvePointsRes<T> {
|
|
1515
|
+
const { Fp, Fn } = Point;
|
|
1516
|
+
// TODO: remove
|
|
1517
|
+
function isWithinCurveOrder(num: bigint): boolean {
|
|
1518
|
+
return inRange(num, _1n, Fn.ORDER);
|
|
1519
|
+
}
|
|
1520
|
+
const weierstrassEquation = _legacyHelperEquat(Fp, c.a, c.b);
|
|
1521
|
+
const normPrivateKeyToScalar = _legacyHelperNormPriv(
|
|
1522
|
+
Fn,
|
|
1523
|
+
c.allowedPrivateKeyLengths,
|
|
1524
|
+
c.wrapPrivateKey
|
|
1525
|
+
);
|
|
1526
|
+
return Object.assign(
|
|
1527
|
+
{},
|
|
1528
|
+
{
|
|
1529
|
+
CURVE: c,
|
|
1530
|
+
Point: Point,
|
|
1531
|
+
ProjectivePoint: Point,
|
|
1532
|
+
normPrivateKeyToScalar,
|
|
1533
|
+
weierstrassEquation,
|
|
1534
|
+
isWithinCurveOrder,
|
|
1535
|
+
}
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
function _ecdsa_new_output_to_legacy(c: CurveType, ecdsa: ECDSA): CurveFn {
|
|
1539
|
+
return Object.assign({}, ecdsa, {
|
|
1540
|
+
ProjectivePoint: ecdsa.Point,
|
|
1541
|
+
CURVE: c,
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// _ecdsa_legacy
|
|
1546
|
+
export function weierstrass(c: CurveType): CurveFn {
|
|
1547
|
+
const { CURVE, curveOpts, ecdsaOpts } = _ecdsa_legacy_opts_to_new(c);
|
|
1548
|
+
const Point = weierstrassN(CURVE, curveOpts);
|
|
1549
|
+
const signs = ecdsa(Point, ecdsaOpts, curveOpts);
|
|
1550
|
+
return _ecdsa_new_output_to_legacy(c, signs);
|
|
1261
1551
|
}
|
|
1262
1552
|
|
|
1263
1553
|
/**
|
|
@@ -1353,30 +1643,31 @@ export function mapToCurveSimpleSWU<T>(
|
|
|
1353
1643
|
}
|
|
1354
1644
|
): (u: T) => { x: T; y: T } {
|
|
1355
1645
|
validateField(Fp);
|
|
1356
|
-
|
|
1646
|
+
const { A, B, Z } = opts;
|
|
1647
|
+
if (!Fp.isValid(A) || !Fp.isValid(B) || !Fp.isValid(Z))
|
|
1357
1648
|
throw new Error('mapToCurveSimpleSWU: invalid opts');
|
|
1358
|
-
const sqrtRatio = SWUFpSqrtRatio(Fp,
|
|
1359
|
-
if (!Fp.isOdd) throw new Error('
|
|
1649
|
+
const sqrtRatio = SWUFpSqrtRatio(Fp, Z);
|
|
1650
|
+
if (!Fp.isOdd) throw new Error('Field does not have .isOdd()');
|
|
1360
1651
|
// Input: u, an element of F.
|
|
1361
1652
|
// Output: (x, y), a point on E.
|
|
1362
1653
|
return (u: T): { x: T; y: T } => {
|
|
1363
1654
|
// prettier-ignore
|
|
1364
1655
|
let tv1, tv2, tv3, tv4, tv5, tv6, x, y;
|
|
1365
1656
|
tv1 = Fp.sqr(u); // 1. tv1 = u^2
|
|
1366
|
-
tv1 = Fp.mul(tv1,
|
|
1657
|
+
tv1 = Fp.mul(tv1, Z); // 2. tv1 = Z * tv1
|
|
1367
1658
|
tv2 = Fp.sqr(tv1); // 3. tv2 = tv1^2
|
|
1368
1659
|
tv2 = Fp.add(tv2, tv1); // 4. tv2 = tv2 + tv1
|
|
1369
1660
|
tv3 = Fp.add(tv2, Fp.ONE); // 5. tv3 = tv2 + 1
|
|
1370
|
-
tv3 = Fp.mul(tv3,
|
|
1371
|
-
tv4 = Fp.cmov(
|
|
1372
|
-
tv4 = Fp.mul(tv4,
|
|
1661
|
+
tv3 = Fp.mul(tv3, B); // 6. tv3 = B * tv3
|
|
1662
|
+
tv4 = Fp.cmov(Z, Fp.neg(tv2), !Fp.eql(tv2, Fp.ZERO)); // 7. tv4 = CMOV(Z, -tv2, tv2 != 0)
|
|
1663
|
+
tv4 = Fp.mul(tv4, A); // 8. tv4 = A * tv4
|
|
1373
1664
|
tv2 = Fp.sqr(tv3); // 9. tv2 = tv3^2
|
|
1374
1665
|
tv6 = Fp.sqr(tv4); // 10. tv6 = tv4^2
|
|
1375
|
-
tv5 = Fp.mul(tv6,
|
|
1666
|
+
tv5 = Fp.mul(tv6, A); // 11. tv5 = A * tv6
|
|
1376
1667
|
tv2 = Fp.add(tv2, tv5); // 12. tv2 = tv2 + tv5
|
|
1377
1668
|
tv2 = Fp.mul(tv2, tv3); // 13. tv2 = tv2 * tv3
|
|
1378
1669
|
tv6 = Fp.mul(tv6, tv4); // 14. tv6 = tv6 * tv4
|
|
1379
|
-
tv5 = Fp.mul(tv6,
|
|
1670
|
+
tv5 = Fp.mul(tv6, B); // 15. tv5 = B * tv6
|
|
1380
1671
|
tv2 = Fp.add(tv2, tv5); // 16. tv2 = tv2 + tv5
|
|
1381
1672
|
x = Fp.mul(tv1, tv3); // 17. x = tv1 * tv3
|
|
1382
1673
|
const { isValid, value } = sqrtRatio(tv2, tv6); // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6)
|