@noble/curves 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/lib/bls.d.ts +79 -0
- package/lib/bls.js +304 -0
- package/lib/edwards.d.ts +10 -6
- package/lib/edwards.js +16 -11
- package/lib/esm/bls.js +300 -0
- package/lib/esm/edwards.js +17 -12
- package/lib/esm/group.js +2 -2
- package/lib/esm/hashToCurve.js +105 -0
- package/lib/esm/modular.js +131 -50
- package/lib/esm/utils.js +25 -19
- package/lib/esm/weierstrass.js +351 -272
- package/lib/group.js +2 -2
- package/lib/hashToCurve.d.ts +13 -0
- package/lib/hashToCurve.js +112 -0
- package/lib/modular.d.ts +37 -17
- package/lib/modular.js +138 -54
- package/lib/utils.d.ts +28 -10
- package/lib/utils.js +31 -22
- package/lib/weierstrass.d.ts +106 -69
- package/lib/weierstrass.js +352 -272
- package/package.json +23 -44
- package/lib/crypto.d.ts +0 -4
- package/lib/crypto.js +0 -8
- package/lib/cryptoBrowser.d.ts +0 -4
- package/lib/cryptoBrowser.js +0 -7
- package/lib/definitions/_shortw_utils.d.ts +0 -63
- package/lib/definitions/_shortw_utils.js +0 -18
- package/lib/definitions/bn.d.ts +0 -7
- package/lib/definitions/bn.js +0 -23
- package/lib/definitions/ed25519.d.ts +0 -49
- package/lib/definitions/ed25519.js +0 -308
- package/lib/definitions/ed448.d.ts +0 -3
- package/lib/definitions/ed448.js +0 -127
- package/lib/definitions/index.d.ts +0 -0
- package/lib/definitions/index.js +0 -2
- package/lib/definitions/jubjub.d.ts +0 -7
- package/lib/definitions/jubjub.js +0 -55
- package/lib/definitions/p192.d.ts +0 -112
- package/lib/definitions/p192.js +0 -23
- package/lib/definitions/p224.d.ts +0 -112
- package/lib/definitions/p224.js +0 -24
- package/lib/definitions/p256.d.ts +0 -112
- package/lib/definitions/p256.js +0 -23
- package/lib/definitions/p384.d.ts +0 -112
- package/lib/definitions/p384.js +0 -24
- package/lib/definitions/p521.d.ts +0 -113
- package/lib/definitions/p521.js +0 -36
- package/lib/definitions/pasta.d.ts +0 -2
- package/lib/definitions/pasta.js +0 -32
- package/lib/definitions/secp256k1.d.ts +0 -87
- package/lib/definitions/secp256k1.js +0 -245
- package/lib/definitions/stark.d.ts +0 -62
- package/lib/definitions/stark.js +0 -248
- package/lib/esm/crypto.js +0 -5
- package/lib/esm/cryptoBrowser.js +0 -4
- package/lib/esm/definitions/_shortw_utils.js +0 -13
- package/lib/esm/definitions/bn.js +0 -20
- package/lib/esm/definitions/ed25519.js +0 -304
- package/lib/esm/definitions/ed448.js +0 -124
- package/lib/esm/definitions/index.js +0 -2
- package/lib/esm/definitions/jubjub.js +0 -50
- package/lib/esm/definitions/p192.js +0 -20
- package/lib/esm/definitions/p224.js +0 -21
- package/lib/esm/definitions/p256.js +0 -20
- package/lib/esm/definitions/p384.js +0 -21
- package/lib/esm/definitions/p521.js +0 -33
- package/lib/esm/definitions/pasta.js +0 -29
- package/lib/esm/definitions/secp256k1.js +0 -241
- package/lib/esm/definitions/stark.js +0 -227
package/lib/esm/bls.js
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// Barreto-Lynn-Scott Curves. A family of pairing friendly curves, with embedding degree = 12 or 24
|
|
2
|
+
// NOTE: only 12 supported for now
|
|
3
|
+
// Constructed from pair of weierstrass curves, based pairing logic
|
|
4
|
+
import * as mod from './modular.js';
|
|
5
|
+
import { ensureBytes, numberToBytesBE, bytesToNumberBE, bitLen, bitGet } from './utils.js';
|
|
6
|
+
// Types
|
|
7
|
+
import { hexToBytes, bytesToHex } from './utils.js';
|
|
8
|
+
import { stringToBytes, hash_to_field, expand_message_xmd } from './hashToCurve.js';
|
|
9
|
+
import { weierstrassPoints } from './weierstrass.js';
|
|
10
|
+
export function bls(CURVE) {
|
|
11
|
+
// Fields looks pretty specific for curve, so for now we need to pass them with options
|
|
12
|
+
const Fp = CURVE.Fp;
|
|
13
|
+
const Fr = CURVE.Fr;
|
|
14
|
+
const Fp2 = CURVE.Fp2;
|
|
15
|
+
const Fp6 = CURVE.Fp6;
|
|
16
|
+
const Fp12 = CURVE.Fp12;
|
|
17
|
+
const BLS_X_LEN = bitLen(CURVE.x);
|
|
18
|
+
// Pre-compute coefficients for sparse multiplication
|
|
19
|
+
// Point addition and point double calculations is reused for coefficients
|
|
20
|
+
function calcPairingPrecomputes(x, y) {
|
|
21
|
+
// prettier-ignore
|
|
22
|
+
const Qx = x, Qy = y, Qz = Fp2.ONE;
|
|
23
|
+
// prettier-ignore
|
|
24
|
+
let Rx = Qx, Ry = Qy, Rz = Qz;
|
|
25
|
+
let ell_coeff = [];
|
|
26
|
+
for (let i = BLS_X_LEN - 2; i >= 0; i--) {
|
|
27
|
+
// Double
|
|
28
|
+
let t0 = Fp2.square(Ry); // Ry²
|
|
29
|
+
let t1 = Fp2.square(Rz); // Rz²
|
|
30
|
+
let t2 = Fp2.multiplyByB(Fp2.multiply(t1, 3n)); // 3 * T1 * B
|
|
31
|
+
let t3 = Fp2.multiply(t2, 3n); // 3 * T2
|
|
32
|
+
let t4 = Fp2.subtract(Fp2.subtract(Fp2.square(Fp2.add(Ry, Rz)), t1), t0); // (Ry + Rz)² - T1 - T0
|
|
33
|
+
ell_coeff.push([
|
|
34
|
+
Fp2.subtract(t2, t0),
|
|
35
|
+
Fp2.multiply(Fp2.square(Rx), 3n),
|
|
36
|
+
Fp2.negate(t4), // -T4
|
|
37
|
+
]);
|
|
38
|
+
Rx = Fp2.div(Fp2.multiply(Fp2.multiply(Fp2.subtract(t0, t3), Rx), Ry), 2n); // ((T0 - T3) * Rx * Ry) / 2
|
|
39
|
+
Ry = Fp2.subtract(Fp2.square(Fp2.div(Fp2.add(t0, t3), 2n)), Fp2.multiply(Fp2.square(t2), 3n)); // ((T0 + T3) / 2)² - 3 * T2²
|
|
40
|
+
Rz = Fp2.multiply(t0, t4); // T0 * T4
|
|
41
|
+
if (bitGet(CURVE.x, i)) {
|
|
42
|
+
// Addition
|
|
43
|
+
let t0 = Fp2.subtract(Ry, Fp2.multiply(Qy, Rz)); // Ry - Qy * Rz
|
|
44
|
+
let t1 = Fp2.subtract(Rx, Fp2.multiply(Qx, Rz)); // Rx - Qx * Rz
|
|
45
|
+
ell_coeff.push([
|
|
46
|
+
Fp2.subtract(Fp2.multiply(t0, Qx), Fp2.multiply(t1, Qy)),
|
|
47
|
+
Fp2.negate(t0),
|
|
48
|
+
t1, // T1
|
|
49
|
+
]);
|
|
50
|
+
let t2 = Fp2.square(t1); // T1²
|
|
51
|
+
let t3 = Fp2.multiply(t2, t1); // T2 * T1
|
|
52
|
+
let t4 = Fp2.multiply(t2, Rx); // T2 * Rx
|
|
53
|
+
let t5 = Fp2.add(Fp2.subtract(t3, Fp2.multiply(t4, 2n)), Fp2.multiply(Fp2.square(t0), Rz)); // T3 - 2 * T4 + T0² * Rz
|
|
54
|
+
Rx = Fp2.multiply(t1, t5); // T1 * T5
|
|
55
|
+
Ry = Fp2.subtract(Fp2.multiply(Fp2.subtract(t4, t5), t0), Fp2.multiply(t3, Ry)); // (T4 - T5) * T0 - T3 * Ry
|
|
56
|
+
Rz = Fp2.multiply(Rz, t3); // Rz * T3
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return ell_coeff;
|
|
60
|
+
}
|
|
61
|
+
function millerLoop(ell, g1) {
|
|
62
|
+
const Px = g1[0];
|
|
63
|
+
const Py = g1[1];
|
|
64
|
+
let f12 = Fp12.ONE;
|
|
65
|
+
for (let j = 0, i = BLS_X_LEN - 2; i >= 0; i--, j++) {
|
|
66
|
+
const E = ell[j];
|
|
67
|
+
f12 = Fp12.multiplyBy014(f12, E[0], Fp2.multiply(E[1], Px), Fp2.multiply(E[2], Py));
|
|
68
|
+
if (bitGet(CURVE.x, i)) {
|
|
69
|
+
j += 1;
|
|
70
|
+
const F = ell[j];
|
|
71
|
+
f12 = Fp12.multiplyBy014(f12, F[0], Fp2.multiply(F[1], Px), Fp2.multiply(F[2], Py));
|
|
72
|
+
}
|
|
73
|
+
if (i !== 0)
|
|
74
|
+
f12 = Fp12.square(f12);
|
|
75
|
+
}
|
|
76
|
+
return Fp12.conjugate(f12);
|
|
77
|
+
}
|
|
78
|
+
// bls12-381 is a construction of two curves:
|
|
79
|
+
// 1. Fp: (x, y)
|
|
80
|
+
// 2. Fp₂: ((x₁, x₂+i), (y₁, y₂+i)) - (complex numbers)
|
|
81
|
+
//
|
|
82
|
+
// Bilinear Pairing (ate pairing) is used to combine both elements into a paired one:
|
|
83
|
+
// Fp₁₂ = e(Fp, Fp2)
|
|
84
|
+
// where Fp₁₂ = 12-degree polynomial
|
|
85
|
+
// Pairing is used to verify signatures.
|
|
86
|
+
//
|
|
87
|
+
// We are using Fp for private keys (shorter) and Fp2 for signatures (longer).
|
|
88
|
+
// Some projects may prefer to swap this relation, it is not supported for now.
|
|
89
|
+
const htfDefaults = { ...CURVE.htfDefaults };
|
|
90
|
+
function isWithinCurveOrder(num) {
|
|
91
|
+
return 0 < num && num < CURVE.r;
|
|
92
|
+
}
|
|
93
|
+
const utils = {
|
|
94
|
+
hexToBytes: hexToBytes,
|
|
95
|
+
bytesToHex: bytesToHex,
|
|
96
|
+
mod: mod.mod,
|
|
97
|
+
stringToBytes,
|
|
98
|
+
// TODO: do we need to export it here?
|
|
99
|
+
hashToField: (msg, count, options = {}) => hash_to_field(msg, count, { ...CURVE.htfDefaults, ...options }),
|
|
100
|
+
expandMessageXMD: (msg, DST, lenInBytes, H = CURVE.hash) => expand_message_xmd(msg, DST, lenInBytes, H),
|
|
101
|
+
/**
|
|
102
|
+
* Can take 40 or more bytes of uniform input e.g. from CSPRNG or KDF
|
|
103
|
+
* and convert them into private key, with the modulo bias being negligible.
|
|
104
|
+
* As per FIPS 186 B.1.1.
|
|
105
|
+
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
|
106
|
+
* @param hash hash output from sha512, or a similar function
|
|
107
|
+
* @returns valid private key
|
|
108
|
+
*/
|
|
109
|
+
hashToPrivateKey: (hash) => {
|
|
110
|
+
hash = ensureBytes(hash);
|
|
111
|
+
if (hash.length < 40 || hash.length > 1024)
|
|
112
|
+
throw new Error('Expected 40-1024 bytes of private key as per FIPS 186');
|
|
113
|
+
// hashToPrivateScalar(hash, CURVE.r)
|
|
114
|
+
// NOTE: doesn't add +/-1
|
|
115
|
+
const num = mod.mod(bytesToNumberBE(hash), CURVE.r);
|
|
116
|
+
// This should never happen
|
|
117
|
+
if (num === 0n || num === 1n)
|
|
118
|
+
throw new Error('Invalid private key');
|
|
119
|
+
return numberToBytesBE(num, 32);
|
|
120
|
+
},
|
|
121
|
+
randomBytes: (bytesLength = 32) => CURVE.randomBytes(bytesLength),
|
|
122
|
+
// NIST SP 800-56A rev 3, section 5.6.1.2.2
|
|
123
|
+
// https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
|
124
|
+
randomPrivateKey: () => utils.hashToPrivateKey(utils.randomBytes(40)),
|
|
125
|
+
getDSTLabel: () => htfDefaults.DST,
|
|
126
|
+
setDSTLabel(newLabel) {
|
|
127
|
+
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
|
|
128
|
+
if (typeof newLabel !== 'string' || newLabel.length > 2048 || newLabel.length === 0) {
|
|
129
|
+
throw new TypeError('Invalid DST');
|
|
130
|
+
}
|
|
131
|
+
htfDefaults.DST = newLabel;
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
function normalizePrivKey(key) {
|
|
135
|
+
let int;
|
|
136
|
+
if (key instanceof Uint8Array && key.length === 32)
|
|
137
|
+
int = bytesToNumberBE(key);
|
|
138
|
+
else if (typeof key === 'string' && key.length === 64)
|
|
139
|
+
int = BigInt(`0x${key}`);
|
|
140
|
+
else if (typeof key === 'number' && key > 0 && Number.isSafeInteger(key))
|
|
141
|
+
int = BigInt(key);
|
|
142
|
+
else if (typeof key === 'bigint' && key > 0n)
|
|
143
|
+
int = key;
|
|
144
|
+
else
|
|
145
|
+
throw new TypeError('Expected valid private key');
|
|
146
|
+
int = mod.mod(int, CURVE.r);
|
|
147
|
+
if (!isWithinCurveOrder(int))
|
|
148
|
+
throw new Error('Private key must be 0 < key < CURVE.r');
|
|
149
|
+
return int;
|
|
150
|
+
}
|
|
151
|
+
// Point on G1 curve: (x, y)
|
|
152
|
+
const G1 = weierstrassPoints({
|
|
153
|
+
n: Fr.ORDER,
|
|
154
|
+
...CURVE.G1,
|
|
155
|
+
});
|
|
156
|
+
function pairingPrecomputes(point) {
|
|
157
|
+
const p = point;
|
|
158
|
+
if (p._PPRECOMPUTES)
|
|
159
|
+
return p._PPRECOMPUTES;
|
|
160
|
+
p._PPRECOMPUTES = calcPairingPrecomputes(p.x, p.y);
|
|
161
|
+
return p._PPRECOMPUTES;
|
|
162
|
+
}
|
|
163
|
+
function clearPairingPrecomputes(point) {
|
|
164
|
+
const p = point;
|
|
165
|
+
p._PPRECOMPUTES = undefined;
|
|
166
|
+
}
|
|
167
|
+
clearPairingPrecomputes;
|
|
168
|
+
function millerLoopG1(Q, P) {
|
|
169
|
+
return millerLoop(pairingPrecomputes(P), [Q.x, Q.y]);
|
|
170
|
+
}
|
|
171
|
+
// Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i)
|
|
172
|
+
const G2 = weierstrassPoints({
|
|
173
|
+
n: Fr.ORDER,
|
|
174
|
+
...CURVE.G2,
|
|
175
|
+
});
|
|
176
|
+
const { Signature } = CURVE.G2;
|
|
177
|
+
// Calculates bilinear pairing
|
|
178
|
+
function pairing(P, Q, withFinalExponent = true) {
|
|
179
|
+
if (P.equals(G1.Point.ZERO) || Q.equals(G2.Point.ZERO))
|
|
180
|
+
throw new Error('No pairings at point of Infinity');
|
|
181
|
+
P.assertValidity();
|
|
182
|
+
Q.assertValidity();
|
|
183
|
+
// Performance: 9ms for millerLoop and ~14ms for exp.
|
|
184
|
+
const looped = millerLoopG1(P, Q);
|
|
185
|
+
return withFinalExponent ? Fp12.finalExponentiate(looped) : looped;
|
|
186
|
+
}
|
|
187
|
+
function normP1(point) {
|
|
188
|
+
return point instanceof G1.Point ? point : G1.Point.fromHex(point);
|
|
189
|
+
}
|
|
190
|
+
function normP2(point) {
|
|
191
|
+
return point instanceof G2.Point ? point : Signature.decode(point);
|
|
192
|
+
}
|
|
193
|
+
function normP2Hash(point) {
|
|
194
|
+
return point instanceof G2.Point ? point : G2.Point.hashToCurve(point);
|
|
195
|
+
}
|
|
196
|
+
// Multiplies generator by private key.
|
|
197
|
+
// P = pk x G
|
|
198
|
+
function getPublicKey(privateKey) {
|
|
199
|
+
return G1.Point.fromPrivateKey(privateKey).toRawBytes(true);
|
|
200
|
+
}
|
|
201
|
+
function sign(message, privateKey) {
|
|
202
|
+
const msgPoint = normP2Hash(message);
|
|
203
|
+
msgPoint.assertValidity();
|
|
204
|
+
const sigPoint = msgPoint.multiply(normalizePrivKey(privateKey));
|
|
205
|
+
if (message instanceof G2.Point)
|
|
206
|
+
return sigPoint;
|
|
207
|
+
return Signature.encode(sigPoint);
|
|
208
|
+
}
|
|
209
|
+
// Checks if pairing of public key & hash is equal to pairing of generator & signature.
|
|
210
|
+
// e(P, H(m)) == e(G, S)
|
|
211
|
+
function verify(signature, message, publicKey) {
|
|
212
|
+
const P = normP1(publicKey);
|
|
213
|
+
const Hm = normP2Hash(message);
|
|
214
|
+
const G = G1.Point.BASE;
|
|
215
|
+
const S = normP2(signature);
|
|
216
|
+
// Instead of doing 2 exponentiations, we use property of billinear maps
|
|
217
|
+
// and do one exp after multiplying 2 points.
|
|
218
|
+
const ePHm = pairing(P.negate(), Hm, false);
|
|
219
|
+
const eGS = pairing(G, S, false);
|
|
220
|
+
const exp = Fp12.finalExponentiate(Fp12.multiply(eGS, ePHm));
|
|
221
|
+
return Fp12.equals(exp, Fp12.ONE);
|
|
222
|
+
}
|
|
223
|
+
function aggregatePublicKeys(publicKeys) {
|
|
224
|
+
if (!publicKeys.length)
|
|
225
|
+
throw new Error('Expected non-empty array');
|
|
226
|
+
const agg = publicKeys
|
|
227
|
+
.map(normP1)
|
|
228
|
+
.reduce((sum, p) => sum.add(G1.JacobianPoint.fromAffine(p)), G1.JacobianPoint.ZERO);
|
|
229
|
+
const aggAffine = agg.toAffine();
|
|
230
|
+
if (publicKeys[0] instanceof G1.Point) {
|
|
231
|
+
aggAffine.assertValidity();
|
|
232
|
+
return aggAffine;
|
|
233
|
+
}
|
|
234
|
+
// toRawBytes ensures point validity
|
|
235
|
+
return aggAffine.toRawBytes(true);
|
|
236
|
+
}
|
|
237
|
+
function aggregateSignatures(signatures) {
|
|
238
|
+
if (!signatures.length)
|
|
239
|
+
throw new Error('Expected non-empty array');
|
|
240
|
+
const agg = signatures
|
|
241
|
+
.map(normP2)
|
|
242
|
+
.reduce((sum, s) => sum.add(G2.JacobianPoint.fromAffine(s)), G2.JacobianPoint.ZERO);
|
|
243
|
+
const aggAffine = agg.toAffine();
|
|
244
|
+
if (signatures[0] instanceof G2.Point) {
|
|
245
|
+
aggAffine.assertValidity();
|
|
246
|
+
return aggAffine;
|
|
247
|
+
}
|
|
248
|
+
return Signature.encode(aggAffine);
|
|
249
|
+
}
|
|
250
|
+
// https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
|
|
251
|
+
// e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))
|
|
252
|
+
function verifyBatch(signature, messages, publicKeys) {
|
|
253
|
+
if (!messages.length)
|
|
254
|
+
throw new Error('Expected non-empty messages array');
|
|
255
|
+
if (publicKeys.length !== messages.length)
|
|
256
|
+
throw new Error('Pubkey count should equal msg count');
|
|
257
|
+
const sig = normP2(signature);
|
|
258
|
+
const nMessages = messages.map(normP2Hash);
|
|
259
|
+
const nPublicKeys = publicKeys.map(normP1);
|
|
260
|
+
try {
|
|
261
|
+
const paired = [];
|
|
262
|
+
for (const message of new Set(nMessages)) {
|
|
263
|
+
const groupPublicKey = nMessages.reduce((groupPublicKey, subMessage, i) => subMessage === message ? groupPublicKey.add(nPublicKeys[i]) : groupPublicKey, G1.Point.ZERO);
|
|
264
|
+
// const msg = message instanceof PointG2 ? message : await PointG2.hashToCurve(message);
|
|
265
|
+
// Possible to batch pairing for same msg with different groupPublicKey here
|
|
266
|
+
paired.push(pairing(groupPublicKey, message, false));
|
|
267
|
+
}
|
|
268
|
+
paired.push(pairing(G1.Point.BASE.negate(), sig, false));
|
|
269
|
+
const product = paired.reduce((a, b) => Fp12.multiply(a, b), Fp12.ONE);
|
|
270
|
+
const exp = Fp12.finalExponentiate(product);
|
|
271
|
+
return Fp12.equals(exp, Fp12.ONE);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Pre-compute points. Refer to README.
|
|
278
|
+
G1.Point.BASE._setWindowSize(4);
|
|
279
|
+
return {
|
|
280
|
+
CURVE,
|
|
281
|
+
Fr,
|
|
282
|
+
Fp,
|
|
283
|
+
Fp2,
|
|
284
|
+
Fp6,
|
|
285
|
+
Fp12,
|
|
286
|
+
G1,
|
|
287
|
+
G2,
|
|
288
|
+
Signature,
|
|
289
|
+
millerLoop,
|
|
290
|
+
calcPairingPrecomputes,
|
|
291
|
+
pairing,
|
|
292
|
+
getPublicKey,
|
|
293
|
+
sign,
|
|
294
|
+
verify,
|
|
295
|
+
aggregatePublicKeys,
|
|
296
|
+
aggregateSignatures,
|
|
297
|
+
verifyBatch,
|
|
298
|
+
utils,
|
|
299
|
+
};
|
|
300
|
+
}
|
package/lib/esm/edwards.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// 4. Point decompression code is different too (unexpected), now using generalized formula
|
|
9
9
|
// 5. Domain function was no-op for ed25519, but adds some data even with empty context for ed448
|
|
10
10
|
import * as mod from './modular.js';
|
|
11
|
-
import { bytesToHex, concatBytes, ensureBytes, numberToBytesLE, bytesToNumberLE, hashToPrivateScalar, validateOpts as utilOpts,
|
|
11
|
+
import { bytesToHex, concatBytes, ensureBytes, numberToBytesLE, bytesToNumberLE, hashToPrivateScalar, validateOpts as utilOpts, } from './utils.js'; // TODO: import * as u from './utils.js'?
|
|
12
12
|
import { wNAF } from './group.js';
|
|
13
13
|
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
|
|
14
14
|
const _0n = BigInt(0);
|
|
@@ -24,32 +24,37 @@ function validateOpts(curve) {
|
|
|
24
24
|
if (typeof opts[i] !== 'bigint')
|
|
25
25
|
throw new Error(`Invalid curve param ${i}=${opts[i]} (${typeof opts[i]})`);
|
|
26
26
|
}
|
|
27
|
-
for (const fn of ['
|
|
27
|
+
for (const fn of ['randomBytes']) {
|
|
28
|
+
if (typeof opts[fn] !== 'function')
|
|
29
|
+
throw new Error(`Invalid ${fn} function`);
|
|
30
|
+
}
|
|
31
|
+
for (const fn of ['adjustScalarBytes', 'domain', 'uvRatio']) {
|
|
28
32
|
if (opts[fn] === undefined)
|
|
29
33
|
continue; // Optional
|
|
30
34
|
if (typeof opts[fn] !== 'function')
|
|
31
35
|
throw new Error(`Invalid ${fn} function`);
|
|
32
36
|
}
|
|
33
37
|
// Set defaults
|
|
34
|
-
return Object.freeze({
|
|
38
|
+
return Object.freeze({ ...opts });
|
|
35
39
|
}
|
|
36
40
|
// NOTE: it is not generic twisted curve for now, but ed25519/ed448 generic implementation
|
|
37
41
|
export function twistedEdwards(curveDef) {
|
|
38
42
|
const CURVE = validateOpts(curveDef);
|
|
43
|
+
const Fp = CURVE.Fp;
|
|
39
44
|
const CURVE_ORDER = CURVE.n;
|
|
40
|
-
const fieldLen =
|
|
45
|
+
const fieldLen = Fp.BYTES; // 32 (length of one field element)
|
|
41
46
|
if (fieldLen > 2048)
|
|
42
47
|
throw new Error('Field lengths over 2048 are not supported');
|
|
43
48
|
const groupLen = CURVE.nByteLength;
|
|
44
49
|
// (2n ** 256n).toString(16);
|
|
45
50
|
const maxGroupElement = _2n ** BigInt(groupLen * 8); // previous POW_2_256
|
|
46
51
|
// Function overrides
|
|
47
|
-
const {
|
|
48
|
-
const modP =
|
|
52
|
+
const { randomBytes } = CURVE;
|
|
53
|
+
const modP = Fp.create;
|
|
49
54
|
// sqrt(u/v)
|
|
50
55
|
function _uvRatio(u, v) {
|
|
51
56
|
try {
|
|
52
|
-
const value =
|
|
57
|
+
const value = Fp.sqrt(u * Fp.invert(v));
|
|
53
58
|
return { isValid: true, value };
|
|
54
59
|
}
|
|
55
60
|
catch (e) {
|
|
@@ -89,7 +94,7 @@ export function twistedEdwards(curveDef) {
|
|
|
89
94
|
// invert on all of them. invert is very slow operation,
|
|
90
95
|
// so this improves performance massively.
|
|
91
96
|
static toAffineBatch(points) {
|
|
92
|
-
const toInv =
|
|
97
|
+
const toInv = Fp.invertBatch(points.map((p) => p.z));
|
|
93
98
|
return points.map((p, i) => p.toAffine(toInv[i]));
|
|
94
99
|
}
|
|
95
100
|
static normalizeZ(points) {
|
|
@@ -229,7 +234,7 @@ export function twistedEdwards(curveDef) {
|
|
|
229
234
|
const { x, y, z } = this;
|
|
230
235
|
const is0 = this.equals(ExtendedPoint.ZERO);
|
|
231
236
|
if (invZ == null)
|
|
232
|
-
invZ = is0 ? _8n :
|
|
237
|
+
invZ = is0 ? _8n : Fp.invert(z); // 8 was chosen arbitrarily
|
|
233
238
|
const ax = modP(x * invZ);
|
|
234
239
|
const ay = modP(y * invZ);
|
|
235
240
|
const zz = modP(z * invZ);
|
|
@@ -265,7 +270,7 @@ export function twistedEdwards(curveDef) {
|
|
|
265
270
|
// Converts hash string or Uint8Array to Point.
|
|
266
271
|
// Uses algo from RFC8032 5.1.3.
|
|
267
272
|
static fromHex(hex, strict = true) {
|
|
268
|
-
const { d,
|
|
273
|
+
const { d, a } = CURVE;
|
|
269
274
|
hex = ensureBytes(hex, fieldLen);
|
|
270
275
|
// 1. First, interpret the string as an integer in little-endian
|
|
271
276
|
// representation. Bit 255 of this number is the least significant
|
|
@@ -276,7 +281,7 @@ export function twistedEdwards(curveDef) {
|
|
|
276
281
|
const lastByte = hex[fieldLen - 1];
|
|
277
282
|
normed[fieldLen - 1] = lastByte & ~0x80;
|
|
278
283
|
const y = bytesToNumberLE(normed);
|
|
279
|
-
if (strict && y >=
|
|
284
|
+
if (strict && y >= Fp.ORDER)
|
|
280
285
|
throw new Error('Expected 0 < hex < P');
|
|
281
286
|
if (!strict && y >= maxGroupElement)
|
|
282
287
|
throw new Error('Expected 0 < hex < 2**256');
|
|
@@ -510,7 +515,7 @@ export function twistedEdwards(curveDef) {
|
|
|
510
515
|
const utils = {
|
|
511
516
|
getExtendedPublicKey,
|
|
512
517
|
mod: modP,
|
|
513
|
-
invert:
|
|
518
|
+
invert: Fp.invert,
|
|
514
519
|
/**
|
|
515
520
|
* Not needed for ed25519 private keys. Needed if you use scalars directly (rare).
|
|
516
521
|
*/
|
package/lib/esm/group.js
CHANGED
|
@@ -9,8 +9,6 @@ export function wNAF(c, bits) {
|
|
|
9
9
|
return condition ? neg : item;
|
|
10
10
|
};
|
|
11
11
|
const opts = (W) => {
|
|
12
|
-
if (256 % W)
|
|
13
|
-
throw new Error('Invalid precomputation window, must be power of 2');
|
|
14
12
|
const windows = Math.ceil(bits / W) + 1; // +1, because
|
|
15
13
|
const windowSize = 2 ** (W - 1); // -1 because we skip zero
|
|
16
14
|
return { windows, windowSize };
|
|
@@ -59,6 +57,8 @@ export function wNAF(c, bits) {
|
|
|
59
57
|
* @returns real and fake (for const-time) points
|
|
60
58
|
*/
|
|
61
59
|
wNAF(W, precomputes, n) {
|
|
60
|
+
// TODO: maybe check that scalar is less than group order? wNAF will fail otherwise
|
|
61
|
+
// But need to carefully remove other checks before wNAF. ORDER == bits here
|
|
62
62
|
const { windows, windowSize } = opts(W);
|
|
63
63
|
let p = c.ZERO;
|
|
64
64
|
let f = c.BASE;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { concatBytes } from './utils.js';
|
|
2
|
+
import * as mod from './modular.js';
|
|
3
|
+
export function validateHTFOpts(opts) {
|
|
4
|
+
if (typeof opts.DST !== 'string')
|
|
5
|
+
throw new Error('Invalid htf/DST');
|
|
6
|
+
if (typeof opts.p !== 'bigint')
|
|
7
|
+
throw new Error('Invalid htf/p');
|
|
8
|
+
if (typeof opts.m !== 'number')
|
|
9
|
+
throw new Error('Invalid htf/m');
|
|
10
|
+
if (typeof opts.k !== 'number')
|
|
11
|
+
throw new Error('Invalid htf/k');
|
|
12
|
+
if (typeof opts.expand !== 'boolean')
|
|
13
|
+
throw new Error('Invalid htf/expand');
|
|
14
|
+
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
|
|
15
|
+
throw new Error('Invalid htf/hash function');
|
|
16
|
+
}
|
|
17
|
+
// UTF8 to ui8a
|
|
18
|
+
export function stringToBytes(str) {
|
|
19
|
+
const bytes = new Uint8Array(str.length);
|
|
20
|
+
for (let i = 0; i < str.length; i++)
|
|
21
|
+
bytes[i] = str.charCodeAt(i);
|
|
22
|
+
return bytes;
|
|
23
|
+
}
|
|
24
|
+
// Octet Stream to Integer (bytesToNumberBE)
|
|
25
|
+
function os2ip(bytes) {
|
|
26
|
+
let result = 0n;
|
|
27
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
28
|
+
result <<= 8n;
|
|
29
|
+
result += BigInt(bytes[i]);
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
// Integer to Octet Stream
|
|
34
|
+
function i2osp(value, length) {
|
|
35
|
+
if (value < 0 || value >= 1 << (8 * length)) {
|
|
36
|
+
throw new Error(`bad I2OSP call: value=${value} length=${length}`);
|
|
37
|
+
}
|
|
38
|
+
const res = Array.from({ length }).fill(0);
|
|
39
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
40
|
+
res[i] = value & 0xff;
|
|
41
|
+
value >>>= 8;
|
|
42
|
+
}
|
|
43
|
+
return new Uint8Array(res);
|
|
44
|
+
}
|
|
45
|
+
function strxor(a, b) {
|
|
46
|
+
const arr = new Uint8Array(a.length);
|
|
47
|
+
for (let i = 0; i < a.length; i++) {
|
|
48
|
+
arr[i] = a[i] ^ b[i];
|
|
49
|
+
}
|
|
50
|
+
return arr;
|
|
51
|
+
}
|
|
52
|
+
// Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits
|
|
53
|
+
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1
|
|
54
|
+
export function expand_message_xmd(msg, DST, lenInBytes, H) {
|
|
55
|
+
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
|
|
56
|
+
if (DST.length > 255)
|
|
57
|
+
DST = H(concatBytes(stringToBytes('H2C-OVERSIZE-DST-'), DST));
|
|
58
|
+
const b_in_bytes = H.outputLen;
|
|
59
|
+
const r_in_bytes = b_in_bytes * 2;
|
|
60
|
+
const ell = Math.ceil(lenInBytes / b_in_bytes);
|
|
61
|
+
if (ell > 255)
|
|
62
|
+
throw new Error('Invalid xmd length');
|
|
63
|
+
const DST_prime = concatBytes(DST, i2osp(DST.length, 1));
|
|
64
|
+
const Z_pad = i2osp(0, r_in_bytes);
|
|
65
|
+
const l_i_b_str = i2osp(lenInBytes, 2);
|
|
66
|
+
const b = new Array(ell);
|
|
67
|
+
const b_0 = H(concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
|
|
68
|
+
b[0] = H(concatBytes(b_0, i2osp(1, 1), DST_prime));
|
|
69
|
+
for (let i = 1; i <= ell; i++) {
|
|
70
|
+
const args = [strxor(b_0, b[i - 1]), i2osp(i + 1, 1), DST_prime];
|
|
71
|
+
b[i] = H(concatBytes(...args));
|
|
72
|
+
}
|
|
73
|
+
const pseudo_random_bytes = concatBytes(...b);
|
|
74
|
+
return pseudo_random_bytes.slice(0, lenInBytes);
|
|
75
|
+
}
|
|
76
|
+
// hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
|
|
77
|
+
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
|
|
78
|
+
// Inputs:
|
|
79
|
+
// msg - a byte string containing the message to hash.
|
|
80
|
+
// count - the number of elements of F to output.
|
|
81
|
+
// Outputs:
|
|
82
|
+
// [u_0, ..., u_(count - 1)], a list of field elements.
|
|
83
|
+
export function hash_to_field(msg, count, options) {
|
|
84
|
+
// if options is provided but incomplete, fill any missing fields with the
|
|
85
|
+
// value in hftDefaults (ie hash to G2).
|
|
86
|
+
const log2p = options.p.toString(2).length;
|
|
87
|
+
const L = Math.ceil((log2p + options.k) / 8); // section 5.1 of ietf draft link above
|
|
88
|
+
const len_in_bytes = count * options.m * L;
|
|
89
|
+
const DST = stringToBytes(options.DST);
|
|
90
|
+
let pseudo_random_bytes = msg;
|
|
91
|
+
if (options.expand) {
|
|
92
|
+
pseudo_random_bytes = expand_message_xmd(msg, DST, len_in_bytes, options.hash);
|
|
93
|
+
}
|
|
94
|
+
const u = new Array(count);
|
|
95
|
+
for (let i = 0; i < count; i++) {
|
|
96
|
+
const e = new Array(options.m);
|
|
97
|
+
for (let j = 0; j < options.m; j++) {
|
|
98
|
+
const elm_offset = L * (j + i * options.m);
|
|
99
|
+
const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L);
|
|
100
|
+
e[j] = mod.mod(os2ip(tv), options.p);
|
|
101
|
+
}
|
|
102
|
+
u[i] = e;
|
|
103
|
+
}
|
|
104
|
+
return u;
|
|
105
|
+
}
|