@noble/curves 2.0.1 → 2.2.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 +214 -122
- package/abstract/bls.d.ts +299 -16
- package/abstract/bls.d.ts.map +1 -1
- package/abstract/bls.js +82 -22
- package/abstract/bls.js.map +1 -1
- package/abstract/curve.d.ts +274 -27
- package/abstract/curve.d.ts.map +1 -1
- package/abstract/curve.js +177 -23
- package/abstract/curve.js.map +1 -1
- package/abstract/edwards.d.ts +166 -30
- package/abstract/edwards.d.ts.map +1 -1
- package/abstract/edwards.js +221 -86
- package/abstract/edwards.js.map +1 -1
- package/abstract/fft.d.ts +322 -10
- package/abstract/fft.d.ts.map +1 -1
- package/abstract/fft.js +154 -12
- package/abstract/fft.js.map +1 -1
- package/abstract/frost.d.ts +293 -0
- package/abstract/frost.d.ts.map +1 -0
- package/abstract/frost.js +704 -0
- package/abstract/frost.js.map +1 -0
- package/abstract/hash-to-curve.d.ts +173 -24
- package/abstract/hash-to-curve.d.ts.map +1 -1
- package/abstract/hash-to-curve.js +170 -31
- package/abstract/hash-to-curve.js.map +1 -1
- package/abstract/modular.d.ts +429 -37
- package/abstract/modular.d.ts.map +1 -1
- package/abstract/modular.js +414 -119
- package/abstract/modular.js.map +1 -1
- package/abstract/montgomery.d.ts +83 -12
- package/abstract/montgomery.d.ts.map +1 -1
- package/abstract/montgomery.js +32 -7
- package/abstract/montgomery.js.map +1 -1
- package/abstract/oprf.d.ts +164 -91
- package/abstract/oprf.d.ts.map +1 -1
- package/abstract/oprf.js +88 -29
- package/abstract/oprf.js.map +1 -1
- package/abstract/poseidon.d.ts +138 -7
- package/abstract/poseidon.d.ts.map +1 -1
- package/abstract/poseidon.js +178 -15
- package/abstract/poseidon.js.map +1 -1
- package/abstract/tower.d.ts +122 -3
- package/abstract/tower.d.ts.map +1 -1
- package/abstract/tower.js +323 -139
- package/abstract/tower.js.map +1 -1
- package/abstract/weierstrass.d.ts +339 -76
- package/abstract/weierstrass.d.ts.map +1 -1
- package/abstract/weierstrass.js +395 -205
- package/abstract/weierstrass.js.map +1 -1
- package/bls12-381.d.ts +16 -2
- package/bls12-381.d.ts.map +1 -1
- package/bls12-381.js +199 -209
- package/bls12-381.js.map +1 -1
- package/bn254.d.ts +11 -2
- package/bn254.d.ts.map +1 -1
- package/bn254.js +93 -38
- package/bn254.js.map +1 -1
- package/ed25519.d.ts +125 -14
- package/ed25519.d.ts.map +1 -1
- package/ed25519.js +202 -40
- package/ed25519.js.map +1 -1
- package/ed448.d.ts +108 -14
- package/ed448.d.ts.map +1 -1
- package/ed448.js +194 -42
- package/ed448.js.map +1 -1
- package/index.js +7 -1
- package/index.js.map +1 -1
- package/misc.d.ts +106 -7
- package/misc.d.ts.map +1 -1
- package/misc.js +141 -32
- package/misc.js.map +1 -1
- package/nist.d.ts +112 -11
- package/nist.d.ts.map +1 -1
- package/nist.js +139 -17
- package/nist.js.map +1 -1
- package/package.json +11 -6
- package/secp256k1.d.ts +92 -15
- package/secp256k1.d.ts.map +1 -1
- package/secp256k1.js +211 -28
- package/secp256k1.js.map +1 -1
- package/src/abstract/bls.ts +350 -67
- package/src/abstract/curve.ts +327 -44
- package/src/abstract/edwards.ts +367 -143
- package/src/abstract/fft.ts +369 -36
- package/src/abstract/frost.ts +1092 -0
- package/src/abstract/hash-to-curve.ts +255 -56
- package/src/abstract/modular.ts +591 -144
- package/src/abstract/montgomery.ts +114 -30
- package/src/abstract/oprf.ts +383 -194
- package/src/abstract/poseidon.ts +235 -35
- package/src/abstract/tower.ts +428 -159
- package/src/abstract/weierstrass.ts +710 -312
- package/src/bls12-381.ts +239 -236
- package/src/bn254.ts +107 -46
- package/src/ed25519.ts +227 -55
- package/src/ed448.ts +227 -57
- package/src/index.ts +7 -1
- package/src/misc.ts +154 -35
- package/src/nist.ts +143 -20
- package/src/secp256k1.ts +284 -41
- package/src/utils.ts +583 -81
- package/src/webcrypto.ts +302 -73
- package/utils.d.ts +457 -24
- package/utils.d.ts.map +1 -1
- package/utils.js +410 -53
- package/utils.js.map +1 -1
- package/webcrypto.d.ts +167 -25
- package/webcrypto.d.ts.map +1 -1
- package/webcrypto.js +165 -58
- package/webcrypto.js.map +1 -1
package/src/bls12-381.ts
CHANGED
|
@@ -91,6 +91,8 @@ import {
|
|
|
91
91
|
hexToBytes,
|
|
92
92
|
numberToBytesBE,
|
|
93
93
|
randomBytes,
|
|
94
|
+
type TArg,
|
|
95
|
+
type TRet,
|
|
94
96
|
} from './utils.ts';
|
|
95
97
|
// Types
|
|
96
98
|
import { isogenyMap } from './abstract/hash-to-curve.ts';
|
|
@@ -112,7 +114,8 @@ const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n =
|
|
|
112
114
|
// To verify math:
|
|
113
115
|
// https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-11
|
|
114
116
|
|
|
115
|
-
// The BLS parameter x (seed) for BLS12-381.
|
|
117
|
+
// The BLS parameter x (seed) for BLS12-381. The stored constant is `|x|`; call
|
|
118
|
+
// sites that need the signed parameter apply the minus sign themselves.
|
|
116
119
|
// x = -2^63 - 2^62 - 2^60 - 2^57 - 2^48 - 2^16
|
|
117
120
|
const BLS_X = BigInt('0xd201000000010000');
|
|
118
121
|
// t = x (called differently in different places)
|
|
@@ -127,7 +130,7 @@ const BLS_X_LEN = bitLen(BLS_X);
|
|
|
127
130
|
// where r is order of prime subgroup and h is cofactor.
|
|
128
131
|
// r = t⁴-t²+1
|
|
129
132
|
// r = (t**4n - t**2n + 1n)
|
|
130
|
-
// cofactor h of G1: (t - 1)²/3
|
|
133
|
+
// cofactor h of G1: (t - 1)²/3, with the signed convention `t = -x`
|
|
131
134
|
// cofactorG1 = (t-1n)**2n/3n
|
|
132
135
|
// x = 3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507
|
|
133
136
|
// y = 1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569
|
|
@@ -149,23 +152,28 @@ const bls12_381_CURVE_G1: WeierstrassOpts<bigint> = {
|
|
|
149
152
|
|
|
150
153
|
// CURVE FIELDS
|
|
151
154
|
// r = z⁴ − z² + 1; CURVE.n from other curves
|
|
152
|
-
/**
|
|
153
|
-
|
|
155
|
+
/**
|
|
156
|
+
* bls12-381 Fr (Fn) field.
|
|
157
|
+
* `fromBytes()` reduces modulo `q` instead of rejecting non-canonical encodings.
|
|
158
|
+
*/
|
|
159
|
+
export const bls12_381_Fr: TRet<IField<bigint>> = Field(bls12_381_CURVE_G1.n, {
|
|
154
160
|
modFromBytes: true,
|
|
155
|
-
})
|
|
161
|
+
}) as TRet<IField<bigint>>;
|
|
156
162
|
const { Fp, Fp2, Fp6, Fp12 } = tower12({
|
|
157
163
|
ORDER: bls12_381_CURVE_G1.p,
|
|
158
164
|
X_LEN: BLS_X_LEN,
|
|
159
165
|
// Finite extension field over irreducible polynominal.
|
|
160
166
|
// Fp(u) / (u² - β) where β = -1
|
|
167
|
+
// Public `Fp2.NONRESIDUE` below is the sextic-tower value `(1, 1) = u + 1`;
|
|
168
|
+
// the quadratic non-residue for the base Fp2 construction is still `-1`.
|
|
161
169
|
FP2_NONRESIDUE: [_1n, _1n],
|
|
162
|
-
Fp2mulByB: ({ c0, c1 }) => {
|
|
170
|
+
Fp2mulByB: ({ c0, c1 }: Fp2) => {
|
|
163
171
|
const t0 = Fp.mul(c0, _4n); // 4 * c0
|
|
164
172
|
const t1 = Fp.mul(c1, _4n); // 4 * c1
|
|
165
173
|
// (T0-T1) + (T0+T1)*i
|
|
166
174
|
return { c0: Fp.sub(t0, t1), c1: Fp.add(t0, t1) };
|
|
167
175
|
},
|
|
168
|
-
Fp12finalExponentiate: (num) => {
|
|
176
|
+
Fp12finalExponentiate: (num: Fp12) => {
|
|
169
177
|
const x = BLS_X;
|
|
170
178
|
// this^(q⁶) / this
|
|
171
179
|
const t0 = Fp12.div(Fp12.frobeniusMap(num, 6), num);
|
|
@@ -186,15 +194,31 @@ const { Fp, Fp2, Fp6, Fp12 } = tower12({
|
|
|
186
194
|
},
|
|
187
195
|
});
|
|
188
196
|
|
|
189
|
-
// GLV endomorphism Ψ(P), for fast cofactor clearing
|
|
190
|
-
|
|
197
|
+
// GLV endomorphism Ψ(P), for fast cofactor clearing. `Fp2.NONRESIDUE` here is
|
|
198
|
+
// the tower value `u + 1`, so the Frobenius base passed to psiFrobenius is
|
|
199
|
+
// `1 / (u + 1)`, and psi2 derives the published `1 / 2^((p - 1) / 3)` constant internally.
|
|
200
|
+
let frob: ReturnType<typeof psiFrobenius> | undefined;
|
|
201
|
+
const getFrob = () => frob || (frob = psiFrobenius(Fp, Fp2, Fp2.div(Fp2.ONE, Fp2.NONRESIDUE)));
|
|
202
|
+
// Eager psiFrobenius setup now dominates `bls12-381.js` import, so defer it to
|
|
203
|
+
// first use. After that these locals are rewritten to the direct helper refs.
|
|
204
|
+
let G2psi: ReturnType<typeof psiFrobenius>['G2psi'] = (c, P) => {
|
|
205
|
+
const fn = getFrob().G2psi;
|
|
206
|
+
G2psi = fn;
|
|
207
|
+
return fn(c, P);
|
|
208
|
+
};
|
|
209
|
+
let G2psi2: ReturnType<typeof psiFrobenius>['G2psi2'] = (c, P) => {
|
|
210
|
+
const fn = getFrob().G2psi2;
|
|
211
|
+
G2psi2 = fn;
|
|
212
|
+
return fn(c, P);
|
|
213
|
+
};
|
|
191
214
|
|
|
192
215
|
/**
|
|
193
216
|
* Default hash_to_field / hash-to-curve for BLS.
|
|
194
217
|
* m: 1 for G1, 2 for G2
|
|
195
218
|
* k: target security level in bits
|
|
196
219
|
* hash: any function, e.g. BBS+ uses BLAKE2: see [github](https://github.com/hyperledger/aries-framework-go/issues/2247).
|
|
197
|
-
*
|
|
220
|
+
* Field/hash parameters come from [section 8.8.2 of RFC 9380](https://www.rfc-editor.org/rfc/rfc9380#section-8.8.2),
|
|
221
|
+
* but the `DST` / `encodeDST` strings below are the BLS-signature-suite override.
|
|
198
222
|
*/
|
|
199
223
|
const hasher_opts = Object.freeze({
|
|
200
224
|
DST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_',
|
|
@@ -207,7 +231,7 @@ const hasher_opts = Object.freeze({
|
|
|
207
231
|
});
|
|
208
232
|
|
|
209
233
|
// a=0, b=4
|
|
210
|
-
// cofactor h of G2
|
|
234
|
+
// cofactor h of G2, derived with the signed convention `t = -x`
|
|
211
235
|
// (t^8 - 4t^7 + 5t^6 - 4t^4 + 6t^3 - 4t^2 - 4t + 13)/9
|
|
212
236
|
// cofactorG2 = (t**8n - 4n*t**7n + 5n*t**6n - 4n*t**4n + 6n*t**3n - 4n*t**2n - 4n*t+13n)/9n
|
|
213
237
|
// x = 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758*u + 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160
|
|
@@ -239,11 +263,102 @@ const bls12_381_CURVE_G2 = {
|
|
|
239
263
|
};
|
|
240
264
|
|
|
241
265
|
// Encoding utils
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
266
|
+
const sortBit = (parts: bigint[], p: bigint) => {
|
|
267
|
+
for (const part of parts) {
|
|
268
|
+
if (part !== _0n) return Boolean((part * _2n) / p);
|
|
269
|
+
}
|
|
270
|
+
return false;
|
|
271
|
+
};
|
|
272
|
+
const fp2 = {
|
|
273
|
+
// Generic tower bytes use `c0 || c1`, but the BLS12-381 G2 point/signature wire encoding uses
|
|
274
|
+
// `c1 || c0`, so keep this local wrapper instead of changing generic field serialization.
|
|
275
|
+
encode({ c0, c1 }: Fp2): TRet<Uint8Array> {
|
|
276
|
+
const { BYTES: L } = Fp;
|
|
277
|
+
return concatBytes(numberToBytesBE(c1, L), numberToBytesBE(c0, L)) as TRet<Uint8Array>;
|
|
278
|
+
},
|
|
279
|
+
decode(bytes: TArg<Uint8Array>) {
|
|
280
|
+
const { BYTES: L } = Fp;
|
|
281
|
+
return Fp2.create({
|
|
282
|
+
c0: Fp.create(bytesToNumberBE(bytes.subarray(L))),
|
|
283
|
+
c1: Fp.create(bytesToNumberBE(bytes.subarray(0, L))),
|
|
284
|
+
});
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
const BaseFp = Fp;
|
|
288
|
+
type Mask = { compressed: boolean; infinity: boolean; sort: boolean };
|
|
289
|
+
// Keep BLS12-381 point/signature codecs on one control-flow skeleton: the G1/G2
|
|
290
|
+
// and point/signature variants differ only in field packing, subgroup bytes, and
|
|
291
|
+
// whether uncompressed form is allowed. Copy-paste decoders were diverging.
|
|
292
|
+
const coder = <T>(
|
|
293
|
+
name: 'G1' | 'G2',
|
|
294
|
+
Fp: TArg<IField<T>>,
|
|
295
|
+
b: T,
|
|
296
|
+
encode: TArg<(v: T) => TRet<Uint8Array>>,
|
|
297
|
+
decode: TArg<(bytes: TArg<Uint8Array>) => T>,
|
|
298
|
+
yparts: (y: T) => bigint[]
|
|
299
|
+
) => {
|
|
300
|
+
const F = Fp as IField<T>;
|
|
301
|
+
const enc = encode as (v: T) => TRet<Uint8Array>;
|
|
302
|
+
const dec = decode as (bytes: TArg<Uint8Array>) => T;
|
|
303
|
+
const W = F.BYTES;
|
|
304
|
+
return (allowUncompressed: boolean) => ({
|
|
305
|
+
encode(point: WeierstrassPoint<T>, compressed = true): TRet<Uint8Array> {
|
|
306
|
+
if (!compressed && !allowUncompressed)
|
|
307
|
+
throw new Error('invalid signature: expected compressed encoding');
|
|
308
|
+
const infinity = point.is0();
|
|
309
|
+
const { x, y } = point.toAffine();
|
|
310
|
+
const bytes = compressed ? enc(x) : concatBytes(enc(x), enc(y));
|
|
311
|
+
let sort;
|
|
312
|
+
if (compressed && !infinity) sort = sortBit(yparts(y), BaseFp.ORDER);
|
|
313
|
+
return setMask(bytes, { compressed, infinity, sort }) as TRet<Uint8Array>;
|
|
314
|
+
},
|
|
315
|
+
decode(bytes: TArg<Uint8Array>): AffinePoint<T> {
|
|
316
|
+
const raw = allowUncompressed
|
|
317
|
+
? abytes(bytes, undefined, 'point')
|
|
318
|
+
: abytes(bytes, W, 'signature');
|
|
319
|
+
const { compressed, infinity, sort, value } = parseMask(raw);
|
|
320
|
+
if (!allowUncompressed && !compressed)
|
|
321
|
+
throw new Error('invalid signature: expected compressed encoding');
|
|
322
|
+
const len = compressed ? W : 2 * W;
|
|
323
|
+
if (value.length !== len) throw new Error(`invalid ${name} point: expected ${len} bytes`);
|
|
324
|
+
if (infinity) {
|
|
325
|
+
// Infinity canonicality has to be checked on raw bytes before decode()
|
|
326
|
+
// reduces coordinates modulo p and turns non-empty payloads into zero.
|
|
327
|
+
for (const b of value) {
|
|
328
|
+
if (b) throw new Error(`invalid ${name} point: non-canonical zero`);
|
|
329
|
+
}
|
|
330
|
+
return { x: F.ZERO, y: F.ZERO };
|
|
331
|
+
}
|
|
332
|
+
const x = dec(compressed ? value : value.subarray(0, W));
|
|
333
|
+
let y;
|
|
334
|
+
if (compressed) {
|
|
335
|
+
y = F.sqrt(F.add(F.pow(x, _3n), b));
|
|
336
|
+
if (!y) throw new Error(`invalid ${name} point: compressed`);
|
|
337
|
+
if (sortBit(yparts(y), BaseFp.ORDER) !== sort) y = F.neg(y);
|
|
338
|
+
} else {
|
|
339
|
+
y = dec(value.subarray(W));
|
|
340
|
+
}
|
|
341
|
+
// Noble keeps the permissive coordinate reduction path here, but an
|
|
342
|
+
// omitted infinity flag must not still decode to ZERO afterwards.
|
|
343
|
+
if (!compressed && F.is0(x) && F.is0(y))
|
|
344
|
+
throw new Error(`invalid ${name} point: uncompressed`);
|
|
345
|
+
return { x, y };
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
};
|
|
245
349
|
|
|
246
|
-
|
|
350
|
+
// Internal helper only: it copies before clearing the top flag bits. The
|
|
351
|
+
// pairing-friendly-curves draft C.2 step 1 rejects 0x20 / 0x60 / 0xe0 because
|
|
352
|
+
// S_bit must be zero for infinity and for all uncompressed encodings.
|
|
353
|
+
function validateMask({ compressed, infinity, sort }: Mask) {
|
|
354
|
+
if (
|
|
355
|
+
(!compressed && !infinity && sort) || // 0010_0000 = 0x20
|
|
356
|
+
(!compressed && infinity && sort) || // 0110_0000 = 0x60
|
|
357
|
+
(compressed && infinity && sort) // 1110_0000 = 0xe0
|
|
358
|
+
)
|
|
359
|
+
throw new Error('invalid encoding flag');
|
|
360
|
+
}
|
|
361
|
+
function parseMask(bytes: TArg<Uint8Array>) {
|
|
247
362
|
// Copy, so we can remove mask data.
|
|
248
363
|
// It will be removed also later, when Fp.create will call modulo.
|
|
249
364
|
bytes = copyBytes(bytes);
|
|
@@ -251,222 +366,62 @@ function parseMask(bytes: Uint8Array) {
|
|
|
251
366
|
const compressed = !!((mask >> 7) & 1); // compression bit (0b1000_0000)
|
|
252
367
|
const infinity = !!((mask >> 6) & 1); // point at infinity bit (0b0100_0000)
|
|
253
368
|
const sort = !!((mask >> 5) & 1); // sort bit (0b0010_0000)
|
|
369
|
+
validateMask({ compressed, infinity, sort });
|
|
254
370
|
bytes[0] &= 0b0001_1111; // clear mask (zero first 3 bits)
|
|
255
371
|
return { compressed, infinity, sort, value: bytes };
|
|
256
372
|
}
|
|
257
373
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
) {
|
|
374
|
+
// Internal helper only: mutates a non-empty fresh buffer in place and just
|
|
375
|
+
// sets bits. Keep the same invalid-flag guard as parseMask() so encoders cannot
|
|
376
|
+
// manufacture states that decoders already reject.
|
|
377
|
+
function setMask(bytes: TArg<Uint8Array>, mask: Partial<Mask>) {
|
|
262
378
|
if (bytes[0] & 0b1110_0000) throw new Error('setMask: non-empty mask');
|
|
379
|
+
validateMask({ compressed: !!mask.compressed, infinity: !!mask.infinity, sort: !!mask.sort });
|
|
263
380
|
if (mask.compressed) bytes[0] |= 0b1000_0000;
|
|
264
381
|
if (mask.infinity) bytes[0] |= 0b0100_0000;
|
|
265
382
|
if (mask.sort) bytes[0] |= 0b0010_0000;
|
|
266
383
|
return bytes;
|
|
267
384
|
}
|
|
268
385
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const sort = Boolean((y * _2n) / P);
|
|
280
|
-
return setMask(numberToBytesBE(x, L), { compressed: true, sort });
|
|
281
|
-
} else {
|
|
282
|
-
if (is0) {
|
|
283
|
-
return concatBytes(Uint8Array.of(0x40), new Uint8Array(2 * L - 1));
|
|
284
|
-
} else {
|
|
285
|
-
return concatBytes(numberToBytesBE(x, L), numberToBytesBE(y, L));
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function signatureG1ToBytes(point: WeierstrassPoint<Fp>) {
|
|
386
|
+
const g1coder = coder(
|
|
387
|
+
'G1',
|
|
388
|
+
Fp,
|
|
389
|
+
Fp.create(bls12_381_CURVE_G1.b),
|
|
390
|
+
(x: Fp) => numberToBytesBE(x, Fp.BYTES),
|
|
391
|
+
(bytes: TArg<Uint8Array>) => Fp.create(bytesToNumberBE(bytes) & bitMask(Fp.BITS)),
|
|
392
|
+
(y: Fp) => [y]
|
|
393
|
+
);
|
|
394
|
+
const g1 = { point: g1coder(true), sig: g1coder(false) };
|
|
395
|
+
const signatureG1ToBytes = (point: WeierstrassPoint<Fp>): TRet<Uint8Array> => {
|
|
291
396
|
point.assertValidity();
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const sort = Boolean((y * _2n) / P);
|
|
296
|
-
return setMask(numberToBytesBE(x, L), { compressed: true, sort });
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function pointG1FromBytes(bytes: Uint8Array): AffinePoint<Fp> {
|
|
300
|
-
const { compressed, infinity, sort, value } = parseMask(bytes);
|
|
301
|
-
const { BYTES: L, ORDER: P } = Fp;
|
|
302
|
-
if (value.length === 48 && compressed) {
|
|
303
|
-
const compressedValue = bytesToNumberBE(value);
|
|
304
|
-
// Zero
|
|
305
|
-
const x = Fp.create(compressedValue & bitMask(Fp.BITS));
|
|
306
|
-
if (infinity) {
|
|
307
|
-
if (x !== _0n) throw new Error('invalid G1 point: non-empty, at infinity, with compression');
|
|
308
|
-
return { x: _0n, y: _0n };
|
|
309
|
-
}
|
|
310
|
-
const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381_CURVE_G1.b)); // y² = x³ + b
|
|
311
|
-
let y = Fp.sqrt(right);
|
|
312
|
-
if (!y) throw new Error('invalid G1 point: compressed point');
|
|
313
|
-
if ((y * _2n) / P !== BigInt(sort)) y = Fp.neg(y);
|
|
314
|
-
return { x: Fp.create(x), y: Fp.create(y) };
|
|
315
|
-
} else if (value.length === 96 && !compressed) {
|
|
316
|
-
// Check if the infinity flag is set
|
|
317
|
-
const x = bytesToNumberBE(value.subarray(0, L));
|
|
318
|
-
const y = bytesToNumberBE(value.subarray(L));
|
|
319
|
-
if (infinity) {
|
|
320
|
-
if (x !== _0n || y !== _0n) throw new Error('G1: non-empty point at infinity');
|
|
321
|
-
return bls12_381.G1.Point.ZERO.toAffine();
|
|
322
|
-
}
|
|
323
|
-
return { x: Fp.create(x), y: Fp.create(y) };
|
|
324
|
-
} else {
|
|
325
|
-
throw new Error('invalid G1 point: expected 48/96 bytes');
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function signatureG1FromBytes(bytes: Uint8Array): WeierstrassPoint<Fp> {
|
|
330
|
-
const { infinity, sort, value } = parseMask(abytes(bytes, 48, 'signature'));
|
|
331
|
-
const P = Fp.ORDER;
|
|
397
|
+
return g1.sig.encode(point);
|
|
398
|
+
};
|
|
399
|
+
function signatureG1FromBytes(bytes: TArg<Uint8Array>): WeierstrassPoint<Fp> {
|
|
332
400
|
const Point = bls12_381.G1.Point;
|
|
333
|
-
const
|
|
334
|
-
// Zero
|
|
335
|
-
if (infinity) return Point.ZERO;
|
|
336
|
-
const x = Fp.create(compressedValue & bitMask(Fp.BITS));
|
|
337
|
-
const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381_CURVE_G1.b)); // y² = x³ + b
|
|
338
|
-
let y = Fp.sqrt(right);
|
|
339
|
-
if (!y) throw new Error('invalid G1 point: compressed');
|
|
340
|
-
const aflag = BigInt(sort);
|
|
341
|
-
if ((y * _2n) / P !== aflag) y = Fp.neg(y);
|
|
342
|
-
const point = Point.fromAffine({ x, y });
|
|
401
|
+
const point = Point.fromAffine(g1.sig.decode(bytes));
|
|
343
402
|
point.assertValidity();
|
|
344
403
|
return point;
|
|
345
404
|
}
|
|
346
405
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
)
|
|
352
|
-
|
|
353
|
-
const is0 = point.is0();
|
|
354
|
-
const { x, y } = point.toAffine();
|
|
355
|
-
if (isComp) {
|
|
356
|
-
if (is0) return concatBytes(COMPZERO, numberToBytesBE(_0n, L));
|
|
357
|
-
const flag = Boolean(y.c1 === _0n ? (y.c0 * _2n) / P : (y.c1 * _2n) / P);
|
|
358
|
-
return concatBytes(
|
|
359
|
-
setMask(numberToBytesBE(x.c1, L), { compressed: true, sort: flag }),
|
|
360
|
-
numberToBytesBE(x.c0, L)
|
|
361
|
-
);
|
|
362
|
-
} else {
|
|
363
|
-
if (is0) return concatBytes(Uint8Array.of(0x40), new Uint8Array(4 * L - 1));
|
|
364
|
-
const { re: x0, im: x1 } = Fp2.reim(x);
|
|
365
|
-
const { re: y0, im: y1 } = Fp2.reim(y);
|
|
366
|
-
return concatBytes(
|
|
367
|
-
numberToBytesBE(x1, L),
|
|
368
|
-
numberToBytesBE(x0, L),
|
|
369
|
-
numberToBytesBE(y1, L),
|
|
370
|
-
numberToBytesBE(y0, L)
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function signatureG2ToBytes(point: WeierstrassPoint<Fp2>) {
|
|
406
|
+
const g2coder = coder('G2', Fp2, bls12_381_CURVE_G2.b, fp2.encode, fp2.decode, (y: Fp2) => [
|
|
407
|
+
y.c1,
|
|
408
|
+
y.c0,
|
|
409
|
+
]);
|
|
410
|
+
const g2 = { point: g2coder(true), sig: g2coder(false) };
|
|
411
|
+
const signatureG2ToBytes = (point: WeierstrassPoint<Fp2>): TRet<Uint8Array> => {
|
|
376
412
|
point.assertValidity();
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
const { re: x0, im: x1 } = Fp2.reim(x);
|
|
381
|
-
const { re: y0, im: y1 } = Fp2.reim(y);
|
|
382
|
-
const tmp = y1 > _0n ? y1 * _2n : y0 * _2n;
|
|
383
|
-
const sort = Boolean((tmp / Fp.ORDER) & _1n);
|
|
384
|
-
const z2 = x0;
|
|
385
|
-
return concatBytes(
|
|
386
|
-
setMask(numberToBytesBE(x1, L), { sort, compressed: true }),
|
|
387
|
-
numberToBytesBE(z2, L)
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function pointG2FromBytes(bytes: Uint8Array): AffinePoint<Fp2> {
|
|
392
|
-
const { BYTES: L, ORDER: P } = Fp;
|
|
393
|
-
const { compressed, infinity, sort, value } = parseMask(bytes);
|
|
394
|
-
if (
|
|
395
|
-
(!compressed && !infinity && sort) || // 00100000
|
|
396
|
-
(!compressed && infinity && sort) || // 01100000
|
|
397
|
-
(sort && infinity && compressed) // 11100000
|
|
398
|
-
) {
|
|
399
|
-
throw new Error('invalid encoding flag: ' + (bytes[0] & 0b1110_0000));
|
|
400
|
-
}
|
|
401
|
-
const slc = (b: Uint8Array, from: number, to?: number) => bytesToNumberBE(b.slice(from, to));
|
|
402
|
-
if (value.length === 96 && compressed) {
|
|
403
|
-
if (infinity) {
|
|
404
|
-
// check that all bytes are 0
|
|
405
|
-
if (value.reduce((p, c) => (p !== 0 ? c + 1 : c), 0) > 0) {
|
|
406
|
-
throw new Error('invalid G2 point: compressed');
|
|
407
|
-
}
|
|
408
|
-
return { x: Fp2.ZERO, y: Fp2.ZERO };
|
|
409
|
-
}
|
|
410
|
-
const x_1 = slc(value, 0, L);
|
|
411
|
-
const x_0 = slc(value, L, 2 * L);
|
|
412
|
-
const x = Fp2.create({ c0: Fp.create(x_0), c1: Fp.create(x_1) });
|
|
413
|
-
const right = Fp2.add(Fp2.pow(x, _3n), bls12_381_CURVE_G2.b); // y² = x³ + 4 * (u+1) = x³ + b
|
|
414
|
-
let y = Fp2.sqrt(right);
|
|
415
|
-
const Y_bit = y.c1 === _0n ? (y.c0 * _2n) / P : (y.c1 * _2n) / P ? _1n : _0n;
|
|
416
|
-
y = sort && Y_bit > 0 ? y : Fp2.neg(y);
|
|
417
|
-
return { x, y };
|
|
418
|
-
} else if (value.length === 192 && !compressed) {
|
|
419
|
-
if (infinity) {
|
|
420
|
-
if (value.reduce((p, c) => (p !== 0 ? c + 1 : c), 0) > 0) {
|
|
421
|
-
throw new Error('invalid G2 point: uncompressed');
|
|
422
|
-
}
|
|
423
|
-
return { x: Fp2.ZERO, y: Fp2.ZERO };
|
|
424
|
-
}
|
|
425
|
-
const x1 = slc(value, 0 * L, 1 * L);
|
|
426
|
-
const x0 = slc(value, 1 * L, 2 * L);
|
|
427
|
-
const y1 = slc(value, 2 * L, 3 * L);
|
|
428
|
-
const y0 = slc(value, 3 * L, 4 * L);
|
|
429
|
-
return { x: Fp2.fromBigTuple([x0, x1]), y: Fp2.fromBigTuple([y0, y1]) };
|
|
430
|
-
} else {
|
|
431
|
-
throw new Error('invalid G2 point: expected 96/192 bytes');
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function signatureG2FromBytes(bytes: Uint8Array) {
|
|
436
|
-
const { ORDER: P } = Fp;
|
|
437
|
-
// TODO: Optimize, it's very slow because of sqrt.
|
|
438
|
-
const { infinity, sort, value } = parseMask(abytes(bytes));
|
|
413
|
+
return g2.sig.encode(point);
|
|
414
|
+
};
|
|
415
|
+
function signatureG2FromBytes(bytes: TArg<Uint8Array>) {
|
|
439
416
|
const Point = bls12_381.G2.Point;
|
|
440
|
-
const
|
|
441
|
-
if (half !== 48 && half !== 96)
|
|
442
|
-
throw new Error('invalid compressed signature length, expected 96/192 bytes');
|
|
443
|
-
const z1 = bytesToNumberBE(value.slice(0, half));
|
|
444
|
-
const z2 = bytesToNumberBE(value.slice(half));
|
|
445
|
-
// Indicates the infinity point
|
|
446
|
-
if (infinity) return Point.ZERO;
|
|
447
|
-
const x1 = Fp.create(z1 & bitMask(Fp.BITS));
|
|
448
|
-
const x2 = Fp.create(z2);
|
|
449
|
-
const x = Fp2.create({ c0: x2, c1: x1 });
|
|
450
|
-
const y2 = Fp2.add(Fp2.pow(x, _3n), bls12_381_CURVE_G2.b); // y² = x³ + 4
|
|
451
|
-
// The slow part
|
|
452
|
-
let y = Fp2.sqrt(y2);
|
|
453
|
-
if (!y) throw new Error('Failed to find a square root');
|
|
454
|
-
|
|
455
|
-
// Choose the y whose leftmost bit of the imaginary part is equal to the a_flag1
|
|
456
|
-
// If y1 happens to be zero, then use the bit of y0
|
|
457
|
-
const { re: y0, im: y1 } = Fp2.reim(y);
|
|
458
|
-
const aflag1 = BigInt(sort);
|
|
459
|
-
const isGreater = y1 > _0n && (y1 * _2n) / P !== aflag1;
|
|
460
|
-
const is0 = y1 === _0n && (y0 * _2n) / P !== aflag1;
|
|
461
|
-
if (isGreater || is0) y = Fp2.neg(y);
|
|
462
|
-
const point = Point.fromAffine({ x, y });
|
|
417
|
+
const point = Point.fromAffine(g2.sig.decode(bytes));
|
|
463
418
|
point.assertValidity();
|
|
464
419
|
return point;
|
|
465
420
|
}
|
|
466
421
|
|
|
467
422
|
const signatureCoders = {
|
|
468
423
|
ShortSignature: {
|
|
469
|
-
fromBytes(bytes: Uint8Array) {
|
|
424
|
+
fromBytes(bytes: TArg<Uint8Array>) {
|
|
470
425
|
return signatureG1FromBytes(abytes(bytes));
|
|
471
426
|
},
|
|
472
427
|
fromHex(hex: string): WeierstrassPoint<Fp> {
|
|
@@ -475,6 +430,7 @@ const signatureCoders = {
|
|
|
475
430
|
toBytes(point: WeierstrassPoint<Fp>) {
|
|
476
431
|
return signatureG1ToBytes(point);
|
|
477
432
|
},
|
|
433
|
+
// Historical alias: BLS signatures have a single compressed byte format here.
|
|
478
434
|
toRawBytes(point: WeierstrassPoint<Fp>) {
|
|
479
435
|
return signatureG1ToBytes(point);
|
|
480
436
|
},
|
|
@@ -483,7 +439,7 @@ const signatureCoders = {
|
|
|
483
439
|
},
|
|
484
440
|
},
|
|
485
441
|
LongSignature: {
|
|
486
|
-
fromBytes(bytes: Uint8Array): WeierstrassPoint<Fp2> {
|
|
442
|
+
fromBytes(bytes: TArg<Uint8Array>): WeierstrassPoint<Fp2> {
|
|
487
443
|
return signatureG2FromBytes(abytes(bytes));
|
|
488
444
|
},
|
|
489
445
|
fromHex(hex: string): WeierstrassPoint<Fp2> {
|
|
@@ -492,6 +448,7 @@ const signatureCoders = {
|
|
|
492
448
|
toBytes(point: WeierstrassPoint<Fp2>) {
|
|
493
449
|
return signatureG2ToBytes(point);
|
|
494
450
|
},
|
|
451
|
+
// Historical alias: BLS signatures have a single compressed byte format here.
|
|
495
452
|
toRawBytes(point: WeierstrassPoint<Fp2>) {
|
|
496
453
|
return signatureG2ToBytes(point);
|
|
497
454
|
},
|
|
@@ -509,10 +466,16 @@ const fields = {
|
|
|
509
466
|
Fr: bls12_381_Fr,
|
|
510
467
|
};
|
|
511
468
|
const G1_Point = weierstrass(bls12_381_CURVE_G1, {
|
|
469
|
+
// Public point APIs still accept infinity, even though the Zcash proof
|
|
470
|
+
// encoding rules cited above only define nonzero point encodings.
|
|
512
471
|
allowInfinityPoint: true,
|
|
513
472
|
Fn: bls12_381_Fr,
|
|
514
|
-
fromBytes:
|
|
515
|
-
toBytes:
|
|
473
|
+
fromBytes: g1.point.decode,
|
|
474
|
+
toBytes: (
|
|
475
|
+
_c: WeierstrassPointCons<Fp>,
|
|
476
|
+
point: WeierstrassPoint<Fp>,
|
|
477
|
+
isComp: boolean
|
|
478
|
+
): TRet<Uint8Array> => g1.point.encode(point, isComp) as TRet<Uint8Array>,
|
|
516
479
|
// Checks is the point resides in prime-order subgroup.
|
|
517
480
|
// point.isTorsionFree() should return true for valid points
|
|
518
481
|
// It returns false for shitty points.
|
|
@@ -537,10 +500,16 @@ const G1_Point = weierstrass(bls12_381_CURVE_G1, {
|
|
|
537
500
|
});
|
|
538
501
|
const G2_Point = weierstrass(bls12_381_CURVE_G2, {
|
|
539
502
|
Fp: Fp2,
|
|
503
|
+
// Public point APIs still accept infinity, even though the Zcash proof
|
|
504
|
+
// encoding rules cited above only define nonzero point encodings.
|
|
540
505
|
allowInfinityPoint: true,
|
|
541
506
|
Fn: bls12_381_Fr,
|
|
542
|
-
fromBytes:
|
|
543
|
-
toBytes:
|
|
507
|
+
fromBytes: g2.point.decode,
|
|
508
|
+
toBytes: (
|
|
509
|
+
_c: WeierstrassPointCons<Fp2>,
|
|
510
|
+
point: WeierstrassPoint<Fp2>,
|
|
511
|
+
isComp: boolean
|
|
512
|
+
): TRet<Uint8Array> => g2.point.encode(point, isComp) as TRet<Uint8Array>,
|
|
544
513
|
// https://eprint.iacr.org/2021/1130.pdf
|
|
545
514
|
// Older version: https://eprint.iacr.org/2019/814.pdf
|
|
546
515
|
isTorsionFree: (c, P): boolean => {
|
|
@@ -569,7 +538,15 @@ const bls12_hasher_opts = {
|
|
|
569
538
|
mapToG1: mapToG1,
|
|
570
539
|
mapToG2: mapToG2,
|
|
571
540
|
hasherOpts: hasher_opts,
|
|
572
|
-
|
|
541
|
+
// RFC 9380 Appendix J defines distinct G1/G2 RO and NU suite IDs, and
|
|
542
|
+
// draft-irtf-cfrg-bls-signature-06 §4.2.1 gives separate G1/G2 `_NUL_` DSTs.
|
|
543
|
+
// Keep G1 encode-to-curve on the G1 domain instead of inheriting G2's `encodeDST`.
|
|
544
|
+
hasherOptsG1: {
|
|
545
|
+
...hasher_opts,
|
|
546
|
+
m: 1,
|
|
547
|
+
DST: 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_',
|
|
548
|
+
encodeDST: 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_',
|
|
549
|
+
},
|
|
573
550
|
hasherOptsG2: { ...hasher_opts },
|
|
574
551
|
} as const;
|
|
575
552
|
|
|
@@ -583,6 +560,16 @@ const bls12_params = {
|
|
|
583
560
|
/**
|
|
584
561
|
* bls12-381 pairing-friendly curve construction.
|
|
585
562
|
* Provides both longSignatures and shortSignatures.
|
|
563
|
+
* @example
|
|
564
|
+
* bls12-381 pairing-friendly curve construction.
|
|
565
|
+
*
|
|
566
|
+
* ```ts
|
|
567
|
+
* const bls = bls12_381.longSignatures;
|
|
568
|
+
* const { secretKey, publicKey } = bls.keygen();
|
|
569
|
+
* const msg = bls.hash(new TextEncoder().encode('hello noble'));
|
|
570
|
+
* const sig = bls.sign(msg, secretKey);
|
|
571
|
+
* const isValid = bls.verify(sig, msg, publicKey);
|
|
572
|
+
* ```
|
|
586
573
|
*/
|
|
587
574
|
export const bls12_381: BlsCurvePairWithSignatures = bls(
|
|
588
575
|
fields,
|
|
@@ -594,6 +581,8 @@ export const bls12_381: BlsCurvePairWithSignatures = bls(
|
|
|
594
581
|
);
|
|
595
582
|
|
|
596
583
|
// 3-isogeny map from E' to E https://www.rfc-editor.org/rfc/rfc9380#appendix-E.3
|
|
584
|
+
// Coefficients stay in ascending `k_(?,0)`..`k_(?,d)` order; isogenyMap()
|
|
585
|
+
// reverses them internally for Horner evaluation.
|
|
597
586
|
const isogenyMapG2 = isogenyMap(
|
|
598
587
|
Fp2,
|
|
599
588
|
[
|
|
@@ -670,7 +659,8 @@ const isogenyMapG2 = isogenyMap(
|
|
|
670
659
|
Fp2[],
|
|
671
660
|
]
|
|
672
661
|
);
|
|
673
|
-
// 11-isogeny map from E' to E
|
|
662
|
+
// 11-isogeny map from E' to E. Coefficients stay in ascending
|
|
663
|
+
// `k_(?,0)`..`k_(?,d)` order; isogenyMap() reverses them for Horner evaluation.
|
|
674
664
|
const isogenyMapG1 = isogenyMap(
|
|
675
665
|
Fp,
|
|
676
666
|
[
|
|
@@ -744,32 +734,45 @@ const isogenyMapG1 = isogenyMap(
|
|
|
744
734
|
].map((i) => i.map((j) => BigInt(j))) as [Fp[], Fp[], Fp[], Fp[]]
|
|
745
735
|
);
|
|
746
736
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
});
|
|
767
|
-
|
|
737
|
+
let G1_SWU: ((u: bigint) => { x: bigint; y: bigint }) | undefined;
|
|
738
|
+
let G2_SWU: ((u: Fp2) => { x: Fp2; y: Fp2 }) | undefined;
|
|
739
|
+
// SWU setup validates the pre-isogeny curve parameters and builds sqrt-ratio helpers.
|
|
740
|
+
// Doing that eagerly adds about 10ms to `bls12-381.js` import here, so keep it lazy; after the
|
|
741
|
+
// first map call the cached mapper is reused directly.
|
|
742
|
+
const getG1_SWU = () =>
|
|
743
|
+
G1_SWU ||
|
|
744
|
+
(G1_SWU = mapToCurveSimpleSWU(Fp, {
|
|
745
|
+
A: Fp.create(
|
|
746
|
+
BigInt(
|
|
747
|
+
'0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d'
|
|
748
|
+
)
|
|
749
|
+
),
|
|
750
|
+
B: Fp.create(
|
|
751
|
+
BigInt(
|
|
752
|
+
'0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0'
|
|
753
|
+
)
|
|
754
|
+
),
|
|
755
|
+
Z: Fp.create(BigInt(11)),
|
|
756
|
+
}));
|
|
757
|
+
const getG2_SWU = () =>
|
|
758
|
+
G2_SWU ||
|
|
759
|
+
(G2_SWU = mapToCurveSimpleSWU(Fp2, {
|
|
760
|
+
// SWU map for the RFC 9380 §8.8.2 pre-isogeny G2 curve E':
|
|
761
|
+
// y² = x³ + 240i * x + 1012 + 1012i
|
|
762
|
+
A: Fp2.create({ c0: Fp.create(_0n), c1: Fp.create(BigInt(240)) }), // A' = 240 * I
|
|
763
|
+
B: Fp2.create({ c0: Fp.create(BigInt(1012)), c1: Fp.create(BigInt(1012)) }), // B' = 1012 * (1 + I)
|
|
764
|
+
Z: Fp2.create({ c0: Fp.create(BigInt(-2)), c1: Fp.create(BigInt(-1)) }), // Z: -(2 + I)
|
|
765
|
+
}));
|
|
766
|
+
|
|
767
|
+
// Internal hash-to-curve step: G1 uses `m = 1`, so only `scalars[0]` is read,
|
|
768
|
+
// and the result is the isogeny image on E before the subgroup clear.
|
|
768
769
|
function mapToG1(scalars: bigint[]) {
|
|
769
|
-
const { x, y } =
|
|
770
|
+
const { x, y } = getG1_SWU()(Fp.create(scalars[0]));
|
|
770
771
|
return isogenyMapG1(x, y);
|
|
771
772
|
}
|
|
773
|
+
// Internal hash-to-curve step: G2 expects the RFC `m = 2` pair, and the result
|
|
774
|
+
// is the isogeny image on E before the subgroup clear.
|
|
772
775
|
function mapToG2(scalars: bigint[]) {
|
|
773
|
-
const { x, y } =
|
|
776
|
+
const { x, y } = getG2_SWU()(Fp2.fromBigTuple(scalars as BigintTuple));
|
|
774
777
|
return isogenyMapG2(x, y);
|
|
775
778
|
}
|