@noble/curves 1.8.2 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -24
- package/abstract/bls.js +1 -1
- package/abstract/bls.js.map +1 -1
- package/abstract/curve.d.ts +1 -1
- package/abstract/curve.d.ts.map +1 -1
- package/abstract/curve.js +13 -4
- package/abstract/curve.js.map +1 -1
- package/abstract/edwards.d.ts.map +1 -1
- package/abstract/edwards.js +17 -3
- package/abstract/edwards.js.map +1 -1
- package/abstract/fft.d.ts +120 -0
- package/abstract/fft.d.ts.map +1 -0
- package/abstract/fft.js +439 -0
- package/abstract/fft.js.map +1 -0
- package/abstract/hash-to-curve.d.ts +10 -5
- package/abstract/hash-to-curve.d.ts.map +1 -1
- package/abstract/hash-to-curve.js +31 -23
- package/abstract/hash-to-curve.js.map +1 -1
- package/abstract/modular.d.ts +13 -12
- package/abstract/modular.d.ts.map +1 -1
- package/abstract/modular.js +158 -158
- package/abstract/modular.js.map +1 -1
- package/abstract/montgomery.d.ts +4 -9
- package/abstract/montgomery.d.ts.map +1 -1
- package/abstract/montgomery.js +70 -90
- package/abstract/montgomery.js.map +1 -1
- package/abstract/poseidon.d.ts +39 -2
- package/abstract/poseidon.d.ts.map +1 -1
- package/abstract/poseidon.js +183 -4
- package/abstract/poseidon.js.map +1 -1
- package/abstract/tower.d.ts.map +1 -1
- package/abstract/tower.js +4 -5
- package/abstract/tower.js.map +1 -1
- package/abstract/utils.d.ts +1 -0
- package/abstract/utils.d.ts.map +1 -1
- package/abstract/utils.js +2 -0
- package/abstract/utils.js.map +1 -1
- package/abstract/weierstrass.d.ts +31 -9
- package/abstract/weierstrass.d.ts.map +1 -1
- package/abstract/weierstrass.js +67 -48
- package/abstract/weierstrass.js.map +1 -1
- package/bls12-381.d.ts.map +1 -1
- package/bls12-381.js +9 -23
- package/bls12-381.js.map +1 -1
- package/bn254.d.ts +1 -0
- package/bn254.d.ts.map +1 -1
- package/bn254.js +10 -0
- package/bn254.js.map +1 -1
- package/ed25519.d.ts +19 -5
- package/ed25519.d.ts.map +1 -1
- package/ed25519.js +29 -18
- package/ed25519.js.map +1 -1
- package/ed448.d.ts +21 -5
- package/ed448.d.ts.map +1 -1
- package/ed448.js +46 -34
- package/ed448.js.map +1 -1
- package/esm/abstract/bls.js +1 -1
- package/esm/abstract/bls.js.map +1 -1
- package/esm/abstract/curve.d.ts +1 -1
- package/esm/abstract/curve.d.ts.map +1 -1
- package/esm/abstract/curve.js +13 -4
- package/esm/abstract/curve.js.map +1 -1
- package/esm/abstract/edwards.d.ts.map +1 -1
- package/esm/abstract/edwards.js +19 -5
- package/esm/abstract/edwards.js.map +1 -1
- package/esm/abstract/fft.d.ts +120 -0
- package/esm/abstract/fft.d.ts.map +1 -0
- package/esm/abstract/fft.js +426 -0
- package/esm/abstract/fft.js.map +1 -0
- package/esm/abstract/hash-to-curve.d.ts +10 -5
- package/esm/abstract/hash-to-curve.d.ts.map +1 -1
- package/esm/abstract/hash-to-curve.js +32 -24
- package/esm/abstract/hash-to-curve.js.map +1 -1
- package/esm/abstract/modular.d.ts +13 -12
- package/esm/abstract/modular.d.ts.map +1 -1
- package/esm/abstract/modular.js +158 -158
- package/esm/abstract/modular.js.map +1 -1
- package/esm/abstract/montgomery.d.ts +4 -9
- package/esm/abstract/montgomery.d.ts.map +1 -1
- package/esm/abstract/montgomery.js +71 -91
- package/esm/abstract/montgomery.js.map +1 -1
- package/esm/abstract/poseidon.d.ts +39 -2
- package/esm/abstract/poseidon.d.ts.map +1 -1
- package/esm/abstract/poseidon.js +180 -5
- package/esm/abstract/poseidon.js.map +1 -1
- package/esm/abstract/tower.d.ts.map +1 -1
- package/esm/abstract/tower.js +4 -5
- package/esm/abstract/tower.js.map +1 -1
- package/esm/abstract/utils.d.ts +1 -0
- package/esm/abstract/utils.d.ts.map +1 -1
- package/esm/abstract/utils.js +2 -0
- package/esm/abstract/utils.js.map +1 -1
- package/esm/abstract/weierstrass.d.ts +31 -9
- package/esm/abstract/weierstrass.d.ts.map +1 -1
- package/esm/abstract/weierstrass.js +69 -50
- package/esm/abstract/weierstrass.js.map +1 -1
- package/esm/bls12-381.d.ts.map +1 -1
- package/esm/bls12-381.js +9 -23
- package/esm/bls12-381.js.map +1 -1
- package/esm/bn254.d.ts +1 -0
- package/esm/bn254.d.ts.map +1 -1
- package/esm/bn254.js +10 -0
- package/esm/bn254.js.map +1 -1
- package/esm/ed25519.d.ts +19 -5
- package/esm/ed25519.d.ts.map +1 -1
- package/esm/ed25519.js +29 -18
- package/esm/ed25519.js.map +1 -1
- package/esm/ed448.d.ts +21 -5
- package/esm/ed448.d.ts.map +1 -1
- package/esm/ed448.js +47 -35
- package/esm/ed448.js.map +1 -1
- package/esm/jubjub.d.ts +11 -1
- package/esm/jubjub.d.ts.map +1 -1
- package/esm/jubjub.js +11 -1
- package/esm/jubjub.js.map +1 -1
- package/esm/misc.d.ts +8 -2
- package/esm/misc.d.ts.map +1 -1
- package/esm/misc.js +10 -4
- package/esm/misc.js.map +1 -1
- package/esm/nist.d.ts +30 -0
- package/esm/nist.d.ts.map +1 -0
- package/esm/nist.js +121 -0
- package/esm/nist.js.map +1 -0
- package/esm/p256.d.ts +7 -9
- package/esm/p256.d.ts.map +1 -1
- package/esm/p256.js +6 -44
- package/esm/p256.js.map +1 -1
- package/esm/p384.d.ts +9 -10
- package/esm/p384.d.ts.map +1 -1
- package/esm/p384.js +7 -46
- package/esm/p384.js.map +1 -1
- package/esm/p521.d.ts +7 -8
- package/esm/p521.d.ts.map +1 -1
- package/esm/p521.js +6 -46
- package/esm/p521.js.map +1 -1
- package/esm/pasta.d.ts +9 -1
- package/esm/pasta.d.ts.map +1 -1
- package/esm/pasta.js +9 -1
- package/esm/pasta.js.map +1 -1
- package/esm/secp256k1.d.ts +3 -3
- package/esm/secp256k1.d.ts.map +1 -1
- package/esm/secp256k1.js +8 -9
- package/esm/secp256k1.js.map +1 -1
- package/jubjub.d.ts +11 -1
- package/jubjub.d.ts.map +1 -1
- package/jubjub.js +12 -5
- package/jubjub.js.map +1 -1
- package/misc.d.ts +8 -2
- package/misc.d.ts.map +1 -1
- package/misc.js +11 -5
- package/misc.js.map +1 -1
- package/nist.d.ts +30 -0
- package/nist.d.ts.map +1 -0
- package/nist.js +124 -0
- package/nist.js.map +1 -0
- package/p256.d.ts +7 -9
- package/p256.d.ts.map +1 -1
- package/p256.js +5 -49
- package/p256.js.map +1 -1
- package/p384.d.ts +9 -10
- package/p384.d.ts.map +1 -1
- package/p384.js +6 -51
- package/p384.js.map +1 -1
- package/p521.d.ts +7 -8
- package/p521.d.ts.map +1 -1
- package/p521.js +5 -51
- package/p521.js.map +1 -1
- package/package.json +117 -8
- package/pasta.d.ts +9 -1
- package/pasta.d.ts.map +1 -1
- package/pasta.js +9 -3
- package/pasta.js.map +1 -1
- package/secp256k1.d.ts +3 -3
- package/secp256k1.d.ts.map +1 -1
- package/secp256k1.js +9 -10
- package/secp256k1.js.map +1 -1
- package/src/abstract/bls.ts +1 -1
- package/src/abstract/curve.ts +11 -6
- package/src/abstract/edwards.ts +26 -12
- package/src/abstract/fft.ts +508 -0
- package/src/abstract/hash-to-curve.ts +44 -36
- package/src/abstract/modular.ts +154 -153
- package/src/abstract/montgomery.ts +78 -109
- package/src/abstract/poseidon.ts +208 -13
- package/src/abstract/tower.ts +4 -5
- package/src/abstract/utils.ts +2 -0
- package/src/abstract/weierstrass.ts +109 -61
- package/src/bls12-381.ts +11 -27
- package/src/bn254.ts +10 -0
- package/src/ed25519.ts +32 -19
- package/src/ed448.ts +91 -75
- package/src/jubjub.ts +12 -5
- package/src/misc.ts +10 -4
- package/src/nist.ts +155 -0
- package/src/p256.ts +6 -50
- package/src/p384.ts +8 -56
- package/src/p521.ts +6 -65
- package/src/pasta.ts +9 -1
- package/src/secp256k1.ts +12 -11
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
7
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
8
|
-
import { mod
|
|
8
|
+
import { mod } from './modular.ts';
|
|
9
9
|
import {
|
|
10
10
|
aInRange,
|
|
11
11
|
bytesToNumberLE,
|
|
@@ -16,19 +16,15 @@ import {
|
|
|
16
16
|
|
|
17
17
|
const _0n = BigInt(0);
|
|
18
18
|
const _1n = BigInt(1);
|
|
19
|
+
const _2n = BigInt(2);
|
|
19
20
|
type Hex = string | Uint8Array;
|
|
20
21
|
|
|
21
22
|
export type CurveType = {
|
|
22
23
|
P: bigint; // finite field prime
|
|
23
|
-
|
|
24
|
-
adjustScalarBytes
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
montgomeryBits: number;
|
|
28
|
-
powPminus2?: (x: bigint) => bigint;
|
|
29
|
-
xyToU?: (x: bigint, y: bigint) => bigint;
|
|
30
|
-
Gu: bigint;
|
|
31
|
-
randomBytes?: (bytesLength?: number) => Uint8Array;
|
|
24
|
+
type: 'x25519' | 'x448';
|
|
25
|
+
adjustScalarBytes: (bytes: Uint8Array) => Uint8Array;
|
|
26
|
+
powPminus2: (x: bigint) => bigint;
|
|
27
|
+
randomBytes: (bytesLength?: number) => Uint8Array;
|
|
32
28
|
};
|
|
33
29
|
|
|
34
30
|
export type CurveFn = {
|
|
@@ -41,66 +37,87 @@ export type CurveFn = {
|
|
|
41
37
|
};
|
|
42
38
|
|
|
43
39
|
function validateOpts(curve: CurveType) {
|
|
44
|
-
validateObject(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
montgomeryBits: 'isSafeInteger',
|
|
51
|
-
nByteLength: 'isSafeInteger',
|
|
52
|
-
adjustScalarBytes: 'function',
|
|
53
|
-
domain: 'function',
|
|
54
|
-
powPminus2: 'function',
|
|
55
|
-
Gu: 'bigint',
|
|
56
|
-
}
|
|
57
|
-
);
|
|
58
|
-
// Set defaults
|
|
40
|
+
validateObject(curve, {
|
|
41
|
+
adjustScalarBytes: 'function',
|
|
42
|
+
powPminus2: 'function',
|
|
43
|
+
});
|
|
59
44
|
return Object.freeze({ ...curve } as const);
|
|
60
45
|
}
|
|
61
46
|
|
|
62
|
-
// Uses only one coordinate instead of two
|
|
63
47
|
export function montgomery(curveDef: CurveType): CurveFn {
|
|
64
48
|
const CURVE = validateOpts(curveDef);
|
|
65
|
-
const { P } = CURVE;
|
|
49
|
+
const { P, type, adjustScalarBytes, powPminus2 } = CURVE;
|
|
50
|
+
const is25519 = type === 'x25519';
|
|
51
|
+
if (!is25519 && type !== 'x448') throw new Error('invalid type');
|
|
52
|
+
|
|
53
|
+
const montgomeryBits = is25519 ? 255 : 448;
|
|
54
|
+
const fieldLen = is25519 ? 32 : 56;
|
|
55
|
+
const Gu = is25519 ? BigInt(9) : BigInt(5);
|
|
56
|
+
// RFC 7748 #5:
|
|
57
|
+
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519 and
|
|
58
|
+
// (156326 - 2) / 4 = 39081 for curve448/X448
|
|
59
|
+
// const a = is25519 ? 156326n : 486662n;
|
|
60
|
+
const a24 = is25519 ? BigInt(121665) : BigInt(39081);
|
|
61
|
+
// RFC: x25519 "the resulting integer is of the form 2^254 plus
|
|
62
|
+
// eight times a value between 0 and 2^251 - 1 (inclusive)"
|
|
63
|
+
// x448: "2^447 plus four times a value between 0 and 2^445 - 1 (inclusive)"
|
|
64
|
+
const minScalar = is25519 ? _2n ** BigInt(254) : _2n ** BigInt(447);
|
|
65
|
+
const maxAdded = is25519
|
|
66
|
+
? BigInt(8) * _2n ** BigInt(251) - _1n
|
|
67
|
+
: BigInt(4) * _2n ** BigInt(445) - _1n;
|
|
68
|
+
const maxScalar = minScalar + maxAdded + _1n; // (inclusive)
|
|
66
69
|
const modP = (n: bigint) => mod(n, P);
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
const GuBytes = encodeU(Gu);
|
|
71
|
+
function encodeU(u: bigint): Uint8Array {
|
|
72
|
+
return numberToBytesLE(modP(u), fieldLen);
|
|
73
|
+
}
|
|
74
|
+
function decodeU(u: Hex): bigint {
|
|
75
|
+
const _u = ensureBytes('u coordinate', u, fieldLen);
|
|
76
|
+
// RFC: When receiving such an array, implementations of X25519
|
|
77
|
+
// (but not X448) MUST mask the most significant bit in the final byte.
|
|
78
|
+
if (is25519) _u[31] &= 127; // 0b0111_1111
|
|
79
|
+
// RFC: Implementations MUST accept non-canonical values and process them as
|
|
80
|
+
// if they had been reduced modulo the field prime. The non-canonical
|
|
81
|
+
// values are 2^255 - 19 through 2^255 - 1 for X25519 and 2^448 - 2^224
|
|
82
|
+
// - 1 through 2^448 - 1 for X448.
|
|
83
|
+
return modP(bytesToNumberLE(_u));
|
|
84
|
+
}
|
|
85
|
+
function decodeScalar(scalar: Hex): bigint {
|
|
86
|
+
return bytesToNumberLE(adjustScalarBytes(ensureBytes('scalar', scalar, fieldLen)));
|
|
87
|
+
}
|
|
88
|
+
function scalarMult(scalar: Hex, u: Hex): Uint8Array {
|
|
89
|
+
const pu = montgomeryLadder(decodeU(u), decodeScalar(scalar));
|
|
90
|
+
// Some public keys are useless, of low-order. Curve author doesn't think
|
|
91
|
+
// it needs to be validated, but we do it nonetheless.
|
|
92
|
+
// https://cr.yp.to/ecdh.html#validate
|
|
93
|
+
if (pu === _0n) throw new Error('invalid private or public key received');
|
|
94
|
+
return encodeU(pu);
|
|
95
|
+
}
|
|
96
|
+
// Computes public key from private. By doing scalar multiplication of base point.
|
|
97
|
+
function scalarMultBase(scalar: Hex): Uint8Array {
|
|
98
|
+
return scalarMult(scalar, GuBytes);
|
|
99
|
+
}
|
|
72
100
|
|
|
73
|
-
// cswap from RFC7748
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
x_3 = x_3 XOR dummy
|
|
79
|
-
Return (x_2, x_3)
|
|
80
|
-
Where mask(swap) is the all-1 or all-0 word of the same length as x_2
|
|
81
|
-
and x_3, computed, e.g., as mask(swap) = 0 - swap.
|
|
82
|
-
*/
|
|
83
|
-
function cswap(swap: bigint, x_2: bigint, x_3: bigint): [bigint, bigint] {
|
|
101
|
+
// cswap from RFC7748 "example code"
|
|
102
|
+
function cswap(swap: bigint, x_2: bigint, x_3: bigint): { x_2: bigint; x_3: bigint } {
|
|
103
|
+
// dummy = mask(swap) AND (x_2 XOR x_3)
|
|
104
|
+
// Where mask(swap) is the all-1 or all-0 word of the same length as x_2
|
|
105
|
+
// and x_3, computed, e.g., as mask(swap) = 0 - swap.
|
|
84
106
|
const dummy = modP(swap * (x_2 - x_3));
|
|
85
|
-
x_2 = modP(x_2 - dummy);
|
|
86
|
-
x_3 = modP(x_3 + dummy);
|
|
87
|
-
return
|
|
107
|
+
x_2 = modP(x_2 - dummy); // x_2 = x_2 XOR dummy
|
|
108
|
+
x_3 = modP(x_3 + dummy); // x_3 = x_3 XOR dummy
|
|
109
|
+
return { x_2, x_3 };
|
|
88
110
|
}
|
|
89
111
|
|
|
90
|
-
// x25519 from 4
|
|
91
|
-
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
|
|
92
|
-
const a24 = (CURVE.a - BigInt(2)) / BigInt(4);
|
|
93
112
|
/**
|
|
94
|
-
*
|
|
113
|
+
* Montgomery x-only multiplication ladder.
|
|
95
114
|
* @param pointU u coordinate (x) on Montgomery Curve 25519
|
|
96
115
|
* @param scalar by which the point would be multiplied
|
|
97
116
|
* @returns new Point on Montgomery curve
|
|
98
117
|
*/
|
|
99
118
|
function montgomeryLadder(u: bigint, scalar: bigint): bigint {
|
|
100
119
|
aInRange('u', u, _0n, P);
|
|
101
|
-
aInRange('scalar', scalar,
|
|
102
|
-
// Section 5: Implementations MUST accept non-canonical values and process them as
|
|
103
|
-
// if they had been reduced modulo the field prime.
|
|
120
|
+
aInRange('scalar', scalar, minScalar, maxScalar);
|
|
104
121
|
const k = scalar;
|
|
105
122
|
const x_1 = u;
|
|
106
123
|
let x_2 = _1n;
|
|
@@ -108,16 +125,11 @@ export function montgomery(curveDef: CurveType): CurveFn {
|
|
|
108
125
|
let x_3 = u;
|
|
109
126
|
let z_3 = _1n;
|
|
110
127
|
let swap = _0n;
|
|
111
|
-
let sw: [bigint, bigint];
|
|
112
128
|
for (let t = BigInt(montgomeryBits - 1); t >= _0n; t--) {
|
|
113
129
|
const k_t = (k >> t) & _1n;
|
|
114
130
|
swap ^= k_t;
|
|
115
|
-
|
|
116
|
-
x_2 =
|
|
117
|
-
x_3 = sw[1];
|
|
118
|
-
sw = cswap(swap, z_2, z_3);
|
|
119
|
-
z_2 = sw[0];
|
|
120
|
-
z_3 = sw[1];
|
|
131
|
+
({ x_2, x_3 } = cswap(swap, x_2, x_3));
|
|
132
|
+
({ x_2: z_2, x_3: z_3 } = cswap(swap, z_2, z_3));
|
|
121
133
|
swap = k_t;
|
|
122
134
|
|
|
123
135
|
const A = x_2 + z_2;
|
|
@@ -136,53 +148,10 @@ export function montgomery(curveDef: CurveType): CurveFn {
|
|
|
136
148
|
x_2 = modP(AA * BB);
|
|
137
149
|
z_2 = modP(E * (AA + modP(a24 * E)));
|
|
138
150
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// (z_2, z_3) = cswap(swap, z_2, z_3)
|
|
144
|
-
sw = cswap(swap, z_2, z_3);
|
|
145
|
-
z_2 = sw[0];
|
|
146
|
-
z_3 = sw[1];
|
|
147
|
-
// z_2^(p - 2)
|
|
148
|
-
const z2 = powPminus2(z_2);
|
|
149
|
-
// Return x_2 * (z_2^(p - 2))
|
|
150
|
-
return modP(x_2 * z2);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function encodeUCoordinate(u: bigint): Uint8Array {
|
|
154
|
-
return numberToBytesLE(modP(u), montgomeryBytes);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function decodeUCoordinate(uEnc: Hex): bigint {
|
|
158
|
-
// Section 5: When receiving such an array, implementations of X25519
|
|
159
|
-
// MUST mask the most significant bit in the final byte.
|
|
160
|
-
const u = ensureBytes('u coordinate', uEnc, montgomeryBytes);
|
|
161
|
-
if (fieldLen === 32) u[31] &= 127; // 0b0111_1111
|
|
162
|
-
return bytesToNumberLE(u);
|
|
163
|
-
}
|
|
164
|
-
function decodeScalar(n: Hex): bigint {
|
|
165
|
-
const bytes = ensureBytes('scalar', n);
|
|
166
|
-
const len = bytes.length;
|
|
167
|
-
if (len !== montgomeryBytes && len !== fieldLen) {
|
|
168
|
-
let valid = '' + montgomeryBytes + ' or ' + fieldLen;
|
|
169
|
-
throw new Error('invalid scalar, expected ' + valid + ' bytes, got ' + len);
|
|
170
|
-
}
|
|
171
|
-
return bytesToNumberLE(adjustScalarBytes(bytes));
|
|
172
|
-
}
|
|
173
|
-
function scalarMult(scalar: Hex, u: Hex): Uint8Array {
|
|
174
|
-
const pointU = decodeUCoordinate(u);
|
|
175
|
-
const _scalar = decodeScalar(scalar);
|
|
176
|
-
const pu = montgomeryLadder(pointU, _scalar);
|
|
177
|
-
// The result was not contributory
|
|
178
|
-
// https://cr.yp.to/ecdh.html#validate
|
|
179
|
-
if (pu === _0n) throw new Error('invalid private or public key received');
|
|
180
|
-
return encodeUCoordinate(pu);
|
|
181
|
-
}
|
|
182
|
-
// Computes public key from private. By doing scalar multiplication of base point.
|
|
183
|
-
const GuBytes = encodeUCoordinate(CURVE.Gu);
|
|
184
|
-
function scalarMultBase(scalar: Hex): Uint8Array {
|
|
185
|
-
return scalarMult(scalar, GuBytes);
|
|
151
|
+
({ x_2, x_3 } = cswap(swap, x_2, x_3));
|
|
152
|
+
({ x_2: z_2, x_3: z_3 } = cswap(swap, z_2, z_3));
|
|
153
|
+
const z2 = powPminus2(z_2); // `Fp.pow(x, P - _2n)` is much slower equivalent
|
|
154
|
+
return modP(x_2 * z2); // Return x_2 * (z_2^(p - 2))
|
|
186
155
|
}
|
|
187
156
|
|
|
188
157
|
return {
|
|
@@ -190,7 +159,7 @@ export function montgomery(curveDef: CurveType): CurveFn {
|
|
|
190
159
|
scalarMultBase,
|
|
191
160
|
getSharedSecret: (privateKey: Hex, publicKey: Hex) => scalarMult(privateKey, publicKey),
|
|
192
161
|
getPublicKey: (privateKey: Hex): Uint8Array => scalarMultBase(privateKey),
|
|
193
|
-
utils: { randomPrivateKey: () => CURVE.randomBytes!(
|
|
194
|
-
GuBytes: GuBytes,
|
|
162
|
+
utils: { randomPrivateKey: () => CURVE.randomBytes!(fieldLen) },
|
|
163
|
+
GuBytes: GuBytes.slice(),
|
|
195
164
|
};
|
|
196
165
|
}
|
package/src/abstract/poseidon.ts
CHANGED
|
@@ -7,19 +7,127 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
10
|
-
import { FpPow, type IField, validateField } from './modular.ts';
|
|
10
|
+
import { FpInvertBatch, FpPow, type IField, validateField } from './modular.ts';
|
|
11
|
+
import { bitGet } from './utils.ts';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
// Grain LFSR (Linear-Feedback Shift Register): https://eprint.iacr.org/2009/109.pdf
|
|
14
|
+
function grainLFSR(state: number[]): () => boolean {
|
|
15
|
+
let pos = 0;
|
|
16
|
+
if (state.length !== 80) throw new Error('grainLFRS: wrong state length, should be 80 bits');
|
|
17
|
+
const getBit = (): boolean => {
|
|
18
|
+
const r = (offset: number) => state[(pos + offset) % 80];
|
|
19
|
+
const bit = r(62) ^ r(51) ^ r(38) ^ r(23) ^ r(13) ^ r(0);
|
|
20
|
+
state[pos] = bit;
|
|
21
|
+
pos = ++pos % 80;
|
|
22
|
+
return !!bit;
|
|
23
|
+
};
|
|
24
|
+
for (let i = 0; i < 160; i++) getBit();
|
|
25
|
+
return () => {
|
|
26
|
+
// https://en.wikipedia.org/wiki/Shrinking_generator
|
|
27
|
+
while (true) {
|
|
28
|
+
const b1 = getBit();
|
|
29
|
+
const b2 = getBit();
|
|
30
|
+
if (!b1) continue;
|
|
31
|
+
return b2;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type PoseidonBasicOpts = {
|
|
13
37
|
Fp: IField<bigint>;
|
|
14
|
-
t: number;
|
|
38
|
+
t: number; // t = rate + capacity
|
|
15
39
|
roundsFull: number;
|
|
16
40
|
roundsPartial: number;
|
|
41
|
+
isSboxInverse?: boolean;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function validateBasicOpts(opts: PoseidonBasicOpts) {
|
|
45
|
+
const { Fp, roundsFull } = opts;
|
|
46
|
+
validateField(Fp);
|
|
47
|
+
for (const i of ['t', 'roundsFull', 'roundsPartial'] as const) {
|
|
48
|
+
if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
|
|
49
|
+
throw new Error('invalid number ' + i);
|
|
50
|
+
}
|
|
51
|
+
if (opts.isSboxInverse !== undefined && typeof opts.isSboxInverse !== 'boolean')
|
|
52
|
+
throw new Error(`Poseidon: invalid param isSboxInverse=${opts.isSboxInverse}`);
|
|
53
|
+
if (roundsFull & 1) throw new Error('roundsFull is not even' + roundsFull);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function poseidonGrain(opts: PoseidonBasicOpts) {
|
|
57
|
+
validateBasicOpts(opts);
|
|
58
|
+
const { Fp } = opts;
|
|
59
|
+
const state = Array(80).fill(1);
|
|
60
|
+
let pos = 0;
|
|
61
|
+
const writeBits = (value: bigint, bitCount: number) => {
|
|
62
|
+
for (let i = bitCount - 1; i >= 0; i--) state[pos++] = Number(bitGet(value, i));
|
|
63
|
+
};
|
|
64
|
+
const _0n = BigInt(0);
|
|
65
|
+
const _1n = BigInt(1);
|
|
66
|
+
writeBits(_1n, 2); // prime field
|
|
67
|
+
writeBits(opts.isSboxInverse ? _1n : _0n, 4); // b2..b5
|
|
68
|
+
writeBits(BigInt(Fp.BITS), 12); // b6..b17
|
|
69
|
+
writeBits(BigInt(opts.t), 12); // b18..b29
|
|
70
|
+
writeBits(BigInt(opts.roundsFull), 10); // b30..b39
|
|
71
|
+
writeBits(BigInt(opts.roundsPartial), 10); // b40..b49
|
|
72
|
+
|
|
73
|
+
const getBit = grainLFSR(state);
|
|
74
|
+
return (count: number, reject: boolean): bigint[] => {
|
|
75
|
+
const res: bigint[] = [];
|
|
76
|
+
for (let i = 0; i < count; i++) {
|
|
77
|
+
while (true) {
|
|
78
|
+
let num = _0n;
|
|
79
|
+
for (let i = 0; i < Fp.BITS; i++) {
|
|
80
|
+
num <<= _1n;
|
|
81
|
+
if (getBit()) num |= _1n;
|
|
82
|
+
}
|
|
83
|
+
if (reject && num >= Fp.ORDER) continue; // rejection sampling
|
|
84
|
+
res.push(Fp.create(num));
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return res;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export type PoseidonGrainOpts = PoseidonBasicOpts & {
|
|
17
93
|
sboxPower?: number;
|
|
18
|
-
reversePartialPowIdx?: boolean; // Hack for stark
|
|
19
|
-
mds: bigint[][];
|
|
20
|
-
roundConstants: bigint[][];
|
|
21
94
|
};
|
|
22
95
|
|
|
96
|
+
type PoseidonConstants = { mds: bigint[][]; roundConstants: bigint[][] };
|
|
97
|
+
|
|
98
|
+
// NOTE: this is not standard but used often for constant generation for poseidon
|
|
99
|
+
// (grain LFRS-like structure)
|
|
100
|
+
export function grainGenConstants(opts: PoseidonGrainOpts, skipMDS: number = 0): PoseidonConstants {
|
|
101
|
+
const { Fp, t, roundsFull, roundsPartial } = opts;
|
|
102
|
+
const rounds = roundsFull + roundsPartial;
|
|
103
|
+
const sample = poseidonGrain(opts);
|
|
104
|
+
const roundConstants: bigint[][] = [];
|
|
105
|
+
for (let r = 0; r < rounds; r++) roundConstants.push(sample(t, true));
|
|
106
|
+
if (skipMDS > 0) for (let i = 0; i < skipMDS; i++) sample(2 * t, false);
|
|
107
|
+
const xs = sample(t, false);
|
|
108
|
+
const ys = sample(t, false);
|
|
109
|
+
// Construct MDS Matrix M[i][j] = 1 / (xs[i] + ys[j])
|
|
110
|
+
const mds: bigint[][] = [];
|
|
111
|
+
for (let i = 0; i < t; i++) {
|
|
112
|
+
const row: bigint[] = [];
|
|
113
|
+
for (let j = 0; j < t; j++) {
|
|
114
|
+
const xy = Fp.add(xs[i], ys[j]);
|
|
115
|
+
if (Fp.is0(xy))
|
|
116
|
+
throw new Error(`Error generating MDS matrix: xs[${i}] + ys[${j}] resulted in zero.`);
|
|
117
|
+
row.push(xy);
|
|
118
|
+
}
|
|
119
|
+
mds.push(FpInvertBatch(Fp, row));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { roundConstants, mds };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export type PoseidonOpts = PoseidonBasicOpts &
|
|
126
|
+
PoseidonConstants & {
|
|
127
|
+
sboxPower?: number;
|
|
128
|
+
reversePartialPowIdx?: boolean; // Hack for stark
|
|
129
|
+
};
|
|
130
|
+
|
|
23
131
|
export function validateOpts(opts: PoseidonOpts): Readonly<{
|
|
24
132
|
rounds: number;
|
|
25
133
|
sboxFn: (n: bigint) => bigint;
|
|
@@ -32,15 +140,10 @@ export function validateOpts(opts: PoseidonOpts): Readonly<{
|
|
|
32
140
|
sboxPower?: number;
|
|
33
141
|
reversePartialPowIdx?: boolean; // Hack for stark
|
|
34
142
|
}> {
|
|
143
|
+
validateBasicOpts(opts);
|
|
35
144
|
const { Fp, mds, reversePartialPowIdx: rev, roundConstants: rc } = opts;
|
|
36
145
|
const { roundsFull, roundsPartial, sboxPower, t } = opts;
|
|
37
146
|
|
|
38
|
-
validateField(Fp);
|
|
39
|
-
for (const i of ['t', 'roundsFull', 'roundsPartial'] as const) {
|
|
40
|
-
if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
|
|
41
|
-
throw new Error('invalid number ' + i);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
147
|
// MDS is TxT matrix
|
|
45
148
|
if (!Array.isArray(mds) || mds.length !== t) throw new Error('Poseidon: invalid MDS matrix');
|
|
46
149
|
const _mds = mds.map((mdsRow) => {
|
|
@@ -68,7 +171,7 @@ export function validateOpts(opts: PoseidonOpts): Readonly<{
|
|
|
68
171
|
});
|
|
69
172
|
});
|
|
70
173
|
|
|
71
|
-
if (!sboxPower || ![3, 5, 7].includes(sboxPower)) throw new Error('invalid sboxPower');
|
|
174
|
+
if (!sboxPower || ![3, 5, 7, 17].includes(sboxPower)) throw new Error('invalid sboxPower');
|
|
72
175
|
const _sboxPower = BigInt(sboxPower);
|
|
73
176
|
let sboxFn = (n: bigint) => FpPow(Fp, n, _sboxPower);
|
|
74
177
|
// Unwrapped sbox power for common cases (195->142μs)
|
|
@@ -134,3 +237,95 @@ export function poseidon(opts: PoseidonOpts): {
|
|
|
134
237
|
poseidonHash.roundConstants = roundConstants;
|
|
135
238
|
return poseidonHash;
|
|
136
239
|
}
|
|
240
|
+
|
|
241
|
+
export class PoseidonSponge {
|
|
242
|
+
private Fp: IField<bigint>;
|
|
243
|
+
readonly rate: number;
|
|
244
|
+
readonly capacity: number;
|
|
245
|
+
readonly hash: ReturnType<typeof poseidon>;
|
|
246
|
+
private state: bigint[]; // [...capacity, ...rate]
|
|
247
|
+
private pos = 0;
|
|
248
|
+
private isAbsorbing = true;
|
|
249
|
+
|
|
250
|
+
constructor(
|
|
251
|
+
Fp: IField<bigint>,
|
|
252
|
+
rate: number,
|
|
253
|
+
capacity: number,
|
|
254
|
+
hash: ReturnType<typeof poseidon>
|
|
255
|
+
) {
|
|
256
|
+
this.Fp = Fp;
|
|
257
|
+
this.hash = hash;
|
|
258
|
+
this.rate = rate;
|
|
259
|
+
this.capacity = capacity;
|
|
260
|
+
this.state = new Array(rate + capacity);
|
|
261
|
+
this.clean();
|
|
262
|
+
}
|
|
263
|
+
private process(): void {
|
|
264
|
+
this.state = this.hash(this.state);
|
|
265
|
+
}
|
|
266
|
+
absorb(input: bigint[]): void {
|
|
267
|
+
for (const i of input)
|
|
268
|
+
if (typeof i !== 'bigint' || !this.Fp.isValid(i)) throw new Error('invalid input: ' + i);
|
|
269
|
+
for (let i = 0; i < input.length; ) {
|
|
270
|
+
if (!this.isAbsorbing || this.pos === this.rate) {
|
|
271
|
+
this.process();
|
|
272
|
+
this.pos = 0;
|
|
273
|
+
this.isAbsorbing = true;
|
|
274
|
+
}
|
|
275
|
+
const chunk = Math.min(this.rate - this.pos, input.length - i);
|
|
276
|
+
for (let j = 0; j < chunk; j++) {
|
|
277
|
+
const idx = this.capacity + this.pos++;
|
|
278
|
+
this.state[idx] = this.Fp.add(this.state[idx], input[i++]);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
squeeze(count: number): bigint[] {
|
|
283
|
+
const res: bigint[] = [];
|
|
284
|
+
while (res.length < count) {
|
|
285
|
+
if (this.isAbsorbing || this.pos === this.rate) {
|
|
286
|
+
this.process();
|
|
287
|
+
this.pos = 0;
|
|
288
|
+
this.isAbsorbing = false;
|
|
289
|
+
}
|
|
290
|
+
const chunk = Math.min(this.rate - this.pos, count - res.length);
|
|
291
|
+
for (let i = 0; i < chunk; i++) res.push(this.state[this.capacity + this.pos++]);
|
|
292
|
+
}
|
|
293
|
+
return res;
|
|
294
|
+
}
|
|
295
|
+
clean(): void {
|
|
296
|
+
this.state.fill(this.Fp.ZERO);
|
|
297
|
+
this.isAbsorbing = true;
|
|
298
|
+
this.pos = 0;
|
|
299
|
+
}
|
|
300
|
+
clone(): PoseidonSponge {
|
|
301
|
+
const c = new PoseidonSponge(this.Fp, this.rate, this.capacity, this.hash);
|
|
302
|
+
c.pos = this.pos;
|
|
303
|
+
c.state = [...this.state];
|
|
304
|
+
return c;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export type PoseidonSpongeOpts = Omit<PoseidonOpts, 't'> & {
|
|
309
|
+
rate: number;
|
|
310
|
+
capacity: number;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* The method is not defined in spec, but nevertheless used often.
|
|
315
|
+
* Check carefully for compatibility: there are many edge cases, like absorbing an empty array.
|
|
316
|
+
* We cross-test against:
|
|
317
|
+
* - https://github.com/ProvableHQ/snarkVM/tree/staging/algorithms
|
|
318
|
+
* - https://github.com/arkworks-rs/crypto-primitives/tree/main
|
|
319
|
+
*/
|
|
320
|
+
export function poseidonSponge(opts: PoseidonSpongeOpts): () => PoseidonSponge {
|
|
321
|
+
for (const i of ['rate', 'capacity'] as const) {
|
|
322
|
+
if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
|
|
323
|
+
throw new Error('invalid number ' + i);
|
|
324
|
+
}
|
|
325
|
+
const { rate, capacity } = opts;
|
|
326
|
+
const t = opts.rate + opts.capacity;
|
|
327
|
+
// Re-use hash instance between multiple instances
|
|
328
|
+
const hash = poseidon({ ...opts, t });
|
|
329
|
+
const { Fp } = opts;
|
|
330
|
+
return () => new PoseidonSponge(Fp, rate, capacity, hash);
|
|
331
|
+
}
|
package/src/abstract/tower.ts
CHANGED
|
@@ -88,7 +88,7 @@ export function psiFrobenius(
|
|
|
88
88
|
PSI2_X: Fp2;
|
|
89
89
|
PSI2_Y: Fp2;
|
|
90
90
|
} {
|
|
91
|
-
//
|
|
91
|
+
// GLV endomorphism Ψ(P)
|
|
92
92
|
const PSI_X = Fp2.pow(base, (Fp.ORDER - _1n) / _3n); // u^((p-1)/3)
|
|
93
93
|
const PSI_Y = Fp2.pow(base, (Fp.ORDER - _1n) / _2n); // u^((p-1)/2)
|
|
94
94
|
function psi(x: Fp2, y: Fp2): [Fp2, Fp2] {
|
|
@@ -167,7 +167,6 @@ export function tower12(opts: Tower12Opts): {
|
|
|
167
167
|
// Fp
|
|
168
168
|
const Fp = mod.Field(ORDER);
|
|
169
169
|
const FpNONRESIDUE = Fp.create(opts.NONRESIDUE || BigInt(-1));
|
|
170
|
-
const FpLegendre = mod.FpLegendre(ORDER);
|
|
171
170
|
const Fpdiv2 = Fp.div(Fp.ONE, _2n); // 1/2
|
|
172
171
|
|
|
173
172
|
// Fp2
|
|
@@ -265,14 +264,14 @@ export function tower12(opts: Tower12Opts): {
|
|
|
265
264
|
const { c0, c1 } = num;
|
|
266
265
|
if (Fp.is0(c1)) {
|
|
267
266
|
// if c0 is quadratic residue
|
|
268
|
-
if (
|
|
267
|
+
if (mod.FpLegendre(Fp, c0) === 1) return Fp2.create({ c0: Fp.sqrt(c0), c1: Fp.ZERO });
|
|
269
268
|
else return Fp2.create({ c0: Fp.ZERO, c1: Fp.sqrt(Fp.div(c0, FpNONRESIDUE)) });
|
|
270
269
|
}
|
|
271
270
|
const a = Fp.sqrt(Fp.sub(Fp.sqr(c0), Fp.mul(Fp.sqr(c1), FpNONRESIDUE)));
|
|
272
271
|
let d = Fp.mul(Fp.add(a, c0), Fpdiv2);
|
|
273
|
-
const legendre = FpLegendre(Fp, d);
|
|
272
|
+
const legendre = mod.FpLegendre(Fp, d);
|
|
274
273
|
// -1, Quadratic non residue
|
|
275
|
-
if (
|
|
274
|
+
if (legendre === -1) d = Fp.sub(d, a);
|
|
276
275
|
const a0 = Fp.sqrt(d);
|
|
277
276
|
const candidateSqrt = Fp2.create({ c0: a0, c1: Fp.div(Fp.mul(c1, Fpdiv2), a0) });
|
|
278
277
|
if (!Fp2.eql(Fp2.sqr(candidateSqrt), num)) throw new Error('Cannot find square root');
|
package/src/abstract/utils.ts
CHANGED
|
@@ -32,6 +32,7 @@ export function abool(title: string, value: boolean): void {
|
|
|
32
32
|
if (typeof value !== 'boolean') throw new Error(title + ' boolean expected, got ' + value);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
// Used in weierstrass, der
|
|
35
36
|
export function numberToHexUnpadded(num: number | bigint): string {
|
|
36
37
|
const hex = num.toString(16);
|
|
37
38
|
return hex.length & 1 ? '0' + hex : hex;
|
|
@@ -217,6 +218,7 @@ export function aInRange(title: string, n: bigint, min: bigint, max: bigint): vo
|
|
|
217
218
|
/**
|
|
218
219
|
* Calculates amount of bits in a bigint.
|
|
219
220
|
* Same as `n.toString(2).length`
|
|
221
|
+
* TODO: merge with nLength in modular
|
|
220
222
|
*/
|
|
221
223
|
export function bitLen(n: bigint): number {
|
|
222
224
|
let len;
|