@noble/curves 1.2.0 → 1.3.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 +125 -191
- package/abstract/bls.d.ts +27 -10
- package/abstract/bls.d.ts.map +1 -1
- package/abstract/bls.js +58 -8
- package/abstract/bls.js.map +1 -1
- package/abstract/curve.js.map +1 -1
- package/abstract/edwards.js.map +1 -1
- package/abstract/hash-to-curve.d.ts +1 -1
- package/abstract/hash-to-curve.d.ts.map +1 -1
- package/abstract/hash-to-curve.js +8 -8
- package/abstract/hash-to-curve.js.map +1 -1
- package/abstract/modular.js.map +1 -1
- package/abstract/montgomery.d.ts.map +1 -1
- package/abstract/montgomery.js +5 -7
- package/abstract/montgomery.js.map +1 -1
- package/abstract/poseidon.js.map +1 -1
- package/abstract/utils.d.ts +2 -1
- package/abstract/utils.d.ts.map +1 -1
- package/abstract/utils.js +55 -31
- package/abstract/utils.js.map +1 -1
- package/abstract/weierstrass.d.ts +23 -27
- package/abstract/weierstrass.d.ts.map +1 -1
- package/abstract/weierstrass.js +4 -4
- package/abstract/weierstrass.js.map +1 -1
- package/bls12-381.d.ts.map +1 -1
- package/bls12-381.js +137 -82
- package/bls12-381.js.map +1 -1
- package/bn254.d.ts +3 -2
- package/bn254.d.ts.map +1 -1
- package/bn254.js +3 -2
- package/bn254.js.map +1 -1
- package/ed25519.d.ts +4 -2
- package/ed25519.d.ts.map +1 -1
- package/ed25519.js +8 -2
- package/ed25519.js.map +1 -1
- package/ed448.d.ts +4 -2
- package/ed448.d.ts.map +1 -1
- package/ed448.js +10 -1
- package/ed448.js.map +1 -1
- package/esm/abstract/bls.js +58 -8
- package/esm/abstract/bls.js.map +1 -1
- package/esm/abstract/curve.js.map +1 -1
- package/esm/abstract/edwards.js.map +1 -1
- package/esm/abstract/hash-to-curve.js +9 -9
- package/esm/abstract/hash-to-curve.js.map +1 -1
- package/esm/abstract/modular.js.map +1 -1
- package/esm/abstract/montgomery.js +5 -7
- package/esm/abstract/montgomery.js.map +1 -1
- package/esm/abstract/poseidon.js.map +1 -1
- package/esm/abstract/utils.js +53 -30
- package/esm/abstract/utils.js.map +1 -1
- package/esm/abstract/weierstrass.js +4 -4
- package/esm/abstract/weierstrass.js.map +1 -1
- package/esm/bls12-381.js +138 -83
- package/esm/bls12-381.js.map +1 -1
- package/esm/bn254.js +3 -2
- package/esm/bn254.js.map +1 -1
- package/esm/ed25519.js +8 -2
- package/esm/ed25519.js.map +1 -1
- package/esm/ed448.js +10 -1
- package/esm/ed448.js.map +1 -1
- package/esm/jubjub.js.map +1 -1
- package/esm/p256.js +2 -2
- package/esm/p256.js.map +1 -1
- package/esm/p384.js +2 -2
- package/esm/p384.js.map +1 -1
- package/esm/p521.js +3 -3
- package/esm/p521.js.map +1 -1
- package/esm/secp256k1.js +6 -6
- package/esm/secp256k1.js.map +1 -1
- package/jubjub.js.map +1 -1
- package/p256.js +2 -2
- package/p256.js.map +1 -1
- package/p384.js +2 -2
- package/p384.js.map +1 -1
- package/p521.js +3 -3
- package/p521.js.map +1 -1
- package/package.json +5 -5
- package/secp256k1.js +6 -6
- package/secp256k1.js.map +1 -1
- package/src/abstract/bls.ts +117 -19
- package/src/abstract/hash-to-curve.ts +10 -9
- package/src/abstract/montgomery.ts +4 -6
- package/src/abstract/utils.ts +52 -26
- package/src/abstract/weierstrass.ts +16 -7
- package/src/bls12-381.ts +127 -69
- package/src/bn254.ts +3 -2
- package/src/ed25519.ts +10 -2
- package/src/ed448.ts +18 -3
|
@@ -150,17 +150,15 @@ export function montgomery(curveDef: CurveType): CurveFn {
|
|
|
150
150
|
function decodeUCoordinate(uEnc: Hex): bigint {
|
|
151
151
|
// Section 5: When receiving such an array, implementations of X25519
|
|
152
152
|
// MUST mask the most significant bit in the final byte.
|
|
153
|
-
// This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP
|
|
154
|
-
// fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519
|
|
155
153
|
const u = ensureBytes('u coordinate', uEnc, montgomeryBytes);
|
|
156
|
-
|
|
157
|
-
if (fieldLen === montgomeryBytes) u[fieldLen - 1] &= 127; // 0b0111_1111
|
|
154
|
+
if (fieldLen === 32) u[31] &= 127; // 0b0111_1111
|
|
158
155
|
return bytesToNumberLE(u);
|
|
159
156
|
}
|
|
160
157
|
function decodeScalar(n: Hex): bigint {
|
|
161
158
|
const bytes = ensureBytes('scalar', n);
|
|
162
|
-
|
|
163
|
-
|
|
159
|
+
const len = bytes.length;
|
|
160
|
+
if (len !== montgomeryBytes && len !== fieldLen)
|
|
161
|
+
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${len}`);
|
|
164
162
|
return bytesToNumberLE(adjustScalarBytes(bytes));
|
|
165
163
|
}
|
|
166
164
|
function scalarMult(scalar: Hex, u: Hex): Uint8Array {
|
package/src/abstract/utils.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
const _0n = BigInt(0);
|
|
7
7
|
const _1n = BigInt(1);
|
|
8
8
|
const _2n = BigInt(2);
|
|
9
|
-
const u8a = (a: any): a is Uint8Array => a instanceof Uint8Array;
|
|
10
9
|
export type Hex = Uint8Array | string; // hex strings are accepted for simplicity
|
|
11
10
|
export type PrivKey = Hex | bigint; // bigints are accepted to ease learning curve
|
|
12
11
|
export type CHash = {
|
|
@@ -17,6 +16,14 @@ export type CHash = {
|
|
|
17
16
|
};
|
|
18
17
|
export type FHash = (message: Uint8Array | string) => Uint8Array;
|
|
19
18
|
|
|
19
|
+
export function isBytes(a: unknown): a is Uint8Array {
|
|
20
|
+
return (
|
|
21
|
+
a instanceof Uint8Array ||
|
|
22
|
+
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array')
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Array where index 0xf0 (240) is mapped to string 'f0'
|
|
20
27
|
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) =>
|
|
21
28
|
i.toString(16).padStart(2, '0')
|
|
22
29
|
);
|
|
@@ -24,7 +31,7 @@ const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) =>
|
|
|
24
31
|
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
|
|
25
32
|
*/
|
|
26
33
|
export function bytesToHex(bytes: Uint8Array): string {
|
|
27
|
-
if (!
|
|
34
|
+
if (!isBytes(bytes)) throw new Error('Uint8Array expected');
|
|
28
35
|
// pre-caching improves the speed 6x
|
|
29
36
|
let hex = '';
|
|
30
37
|
for (let i = 0; i < bytes.length; i++) {
|
|
@@ -44,20 +51,32 @@ export function hexToNumber(hex: string): bigint {
|
|
|
44
51
|
return BigInt(hex === '' ? '0' : `0x${hex}`);
|
|
45
52
|
}
|
|
46
53
|
|
|
54
|
+
// We use optimized technique to convert hex string to byte array
|
|
55
|
+
const asciis = { _0: 48, _9: 57, _A: 65, _F: 70, _a: 97, _f: 102 } as const;
|
|
56
|
+
function asciiToBase16(char: number): number | undefined {
|
|
57
|
+
if (char >= asciis._0 && char <= asciis._9) return char - asciis._0;
|
|
58
|
+
if (char >= asciis._A && char <= asciis._F) return char - (asciis._A - 10);
|
|
59
|
+
if (char >= asciis._a && char <= asciis._f) return char - (asciis._a - 10);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
47
63
|
/**
|
|
48
64
|
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
|
|
49
65
|
*/
|
|
50
66
|
export function hexToBytes(hex: string): Uint8Array {
|
|
51
67
|
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
60
|
-
|
|
68
|
+
const hl = hex.length;
|
|
69
|
+
const al = hl / 2;
|
|
70
|
+
if (hl % 2) throw new Error('padded hex string expected, got unpadded hex of length ' + hl);
|
|
71
|
+
const array = new Uint8Array(al);
|
|
72
|
+
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
73
|
+
const n1 = asciiToBase16(hex.charCodeAt(hi));
|
|
74
|
+
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
|
|
75
|
+
if (n1 === undefined || n2 === undefined) {
|
|
76
|
+
const char = hex[hi] + hex[hi + 1];
|
|
77
|
+
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
|
78
|
+
}
|
|
79
|
+
array[ai] = n1 * 16 + n2;
|
|
61
80
|
}
|
|
62
81
|
return array;
|
|
63
82
|
}
|
|
@@ -67,7 +86,7 @@ export function bytesToNumberBE(bytes: Uint8Array): bigint {
|
|
|
67
86
|
return hexToNumber(bytesToHex(bytes));
|
|
68
87
|
}
|
|
69
88
|
export function bytesToNumberLE(bytes: Uint8Array): bigint {
|
|
70
|
-
if (!
|
|
89
|
+
if (!isBytes(bytes)) throw new Error('Uint8Array expected');
|
|
71
90
|
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
|
|
72
91
|
}
|
|
73
92
|
|
|
@@ -99,7 +118,7 @@ export function ensureBytes(title: string, hex: Hex, expectedLength?: number): U
|
|
|
99
118
|
} catch (e) {
|
|
100
119
|
throw new Error(`${title} must be valid hex string, got "${hex}". Cause: ${e}`);
|
|
101
120
|
}
|
|
102
|
-
} else if (
|
|
121
|
+
} else if (isBytes(hex)) {
|
|
103
122
|
// Uint8Array.from() instead of hash.slice() because node.js Buffer
|
|
104
123
|
// is instance of Uint8Array, and its slice() creates **mutable** copy
|
|
105
124
|
res = Uint8Array.from(hex);
|
|
@@ -116,21 +135,28 @@ export function ensureBytes(title: string, hex: Hex, expectedLength?: number): U
|
|
|
116
135
|
* Copies several Uint8Arrays into one.
|
|
117
136
|
*/
|
|
118
137
|
export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
|
|
119
|
-
|
|
120
|
-
let
|
|
121
|
-
|
|
122
|
-
if (!
|
|
123
|
-
|
|
138
|
+
let sum = 0;
|
|
139
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
140
|
+
const a = arrays[i];
|
|
141
|
+
if (!isBytes(a)) throw new Error('Uint8Array expected');
|
|
142
|
+
sum += a.length;
|
|
143
|
+
}
|
|
144
|
+
let res = new Uint8Array(sum);
|
|
145
|
+
let pad = 0;
|
|
146
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
147
|
+
const a = arrays[i];
|
|
148
|
+
res.set(a, pad);
|
|
124
149
|
pad += a.length;
|
|
125
|
-
}
|
|
126
|
-
return
|
|
150
|
+
}
|
|
151
|
+
return res;
|
|
127
152
|
}
|
|
128
153
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
154
|
+
// Compares 2 u8a-s in kinda constant time
|
|
155
|
+
export function equalBytes(a: Uint8Array, b: Uint8Array) {
|
|
156
|
+
if (a.length !== b.length) return false;
|
|
157
|
+
let diff = 0;
|
|
158
|
+
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
159
|
+
return diff === 0;
|
|
134
160
|
}
|
|
135
161
|
|
|
136
162
|
// Global symbols in both browsers and Node.js since v11
|
|
@@ -248,7 +274,7 @@ const validatorFns = {
|
|
|
248
274
|
function: (val: any) => typeof val === 'function',
|
|
249
275
|
boolean: (val: any) => typeof val === 'boolean',
|
|
250
276
|
string: (val: any) => typeof val === 'string',
|
|
251
|
-
stringOrUint8Array: (val: any) => typeof val === 'string' || val
|
|
277
|
+
stringOrUint8Array: (val: any) => typeof val === 'string' || isBytes(val),
|
|
252
278
|
isSafeInteger: (val: any) => Number.isSafeInteger(val),
|
|
253
279
|
array: (val: any) => Array.isArray(val),
|
|
254
280
|
field: (val: any, object: any) => (object as any).Fp.isValid(val),
|
|
@@ -123,6 +123,7 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
export type CurvePointsRes<T> = {
|
|
126
|
+
CURVE: ReturnType<typeof validatePointOpts<T>>;
|
|
126
127
|
ProjectivePoint: ProjConstructor<T>;
|
|
127
128
|
normPrivateKeyToScalar: (key: PrivKey) => bigint;
|
|
128
129
|
weierstrassEquation: (x: T) => T;
|
|
@@ -157,7 +158,7 @@ export const DER = {
|
|
|
157
158
|
// parse DER signature
|
|
158
159
|
const { Err: E } = DER;
|
|
159
160
|
const data = typeof hex === 'string' ? h2b(hex) : hex;
|
|
160
|
-
if (!(data
|
|
161
|
+
if (!ut.isBytes(data)) throw new Error('ui8a expected');
|
|
161
162
|
let l = data.length;
|
|
162
163
|
if (l < 2 || data[0] != 0x30) throw new E('Invalid signature tag');
|
|
163
164
|
if (data[1] !== l - 2) throw new E('Invalid signature: incorrect length');
|
|
@@ -187,7 +188,7 @@ export const DER = {
|
|
|
187
188
|
// prettier-ignore
|
|
188
189
|
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4);
|
|
189
190
|
|
|
190
|
-
export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|
191
|
+
export function weierstrassPoints<T>(opts: CurvePointsType<T>): CurvePointsRes<T> {
|
|
191
192
|
const CURVE = validatePointOpts(opts);
|
|
192
193
|
const { Fp } = CURVE; // All curves has same field / group length as for now, but they can differ
|
|
193
194
|
|
|
@@ -237,7 +238,7 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|
|
237
238
|
function normPrivateKeyToScalar(key: PrivKey): bigint {
|
|
238
239
|
const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n } = CURVE;
|
|
239
240
|
if (lengths && typeof key !== 'bigint') {
|
|
240
|
-
if (key
|
|
241
|
+
if (ut.isBytes(key)) key = ut.bytesToHex(key);
|
|
241
242
|
// Normalize to hex string, pad. E.g. P521 would norm 130-132 char hex to 132-char bytes
|
|
242
243
|
if (typeof key !== 'string' || !lengths.includes(key.length)) throw new Error('Invalid key');
|
|
243
244
|
key = key.padStart(nByteLength * 2, '0');
|
|
@@ -269,7 +270,11 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
|
|
|
269
270
|
static readonly BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE);
|
|
270
271
|
static readonly ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO);
|
|
271
272
|
|
|
272
|
-
constructor(
|
|
273
|
+
constructor(
|
|
274
|
+
readonly px: T,
|
|
275
|
+
readonly py: T,
|
|
276
|
+
readonly pz: T
|
|
277
|
+
) {
|
|
273
278
|
if (px == null || !Fp.isValid(px)) throw new Error('x required');
|
|
274
279
|
if (py == null || !Fp.isValid(py)) throw new Error('y required');
|
|
275
280
|
if (pz == null || !Fp.isValid(pz)) throw new Error('z required');
|
|
@@ -763,7 +768,11 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
763
768
|
* ECDSA signature with its (r, s) properties. Supports DER & compact representations.
|
|
764
769
|
*/
|
|
765
770
|
class Signature implements SignatureType {
|
|
766
|
-
constructor(
|
|
771
|
+
constructor(
|
|
772
|
+
readonly r: bigint,
|
|
773
|
+
readonly s: bigint,
|
|
774
|
+
readonly recovery?: number
|
|
775
|
+
) {
|
|
767
776
|
this.assertValidity();
|
|
768
777
|
}
|
|
769
778
|
|
|
@@ -884,7 +893,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
884
893
|
* Quick and dirty check for item being public key. Does not validate hex, or being on-curve.
|
|
885
894
|
*/
|
|
886
895
|
function isProbPub(item: PrivKey | PubKey): boolean {
|
|
887
|
-
const arr = item
|
|
896
|
+
const arr = ut.isBytes(item);
|
|
888
897
|
const str = typeof item === 'string';
|
|
889
898
|
const len = (arr || str) && (item as Hex).length;
|
|
890
899
|
if (arr) return len === compressedLen || len === uncompressedLen;
|
|
@@ -1048,7 +1057,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|
|
1048
1057
|
let _sig: Signature | undefined = undefined;
|
|
1049
1058
|
let P: ProjPointType<bigint>;
|
|
1050
1059
|
try {
|
|
1051
|
-
if (typeof sg === 'string' || sg
|
|
1060
|
+
if (typeof sg === 'string' || ut.isBytes(sg)) {
|
|
1052
1061
|
// Signature can be represented in 2 ways: compact (2*nByteLength) & DER (variable-length).
|
|
1053
1062
|
// Since DER can also be 2*nByteLength bytes, we check for it first.
|
|
1054
1063
|
try {
|
package/src/bls12-381.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
2
2
|
|
|
3
|
-
// bls12-381 pairing-friendly Barreto-Lynn-Scott elliptic curve construction
|
|
4
|
-
// - Construct zk-SNARKs at the
|
|
5
|
-
// -
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// The library uses G1 for public keys and G2 for signatures. Support for G1 signatures is planned.
|
|
9
|
-
// Compatible with Algorand, Chia, Dfinity, Ethereum, FIL, Zcash. Matches specs
|
|
10
|
-
// [pairing-curves-11](https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-11),
|
|
11
|
-
// [bls-sigs-04](https:/cfrg-hash-to/tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04),
|
|
12
|
-
// [hash-to-curve-12](https://tools.ietf.org/html/draft-irtf--curve-12).
|
|
3
|
+
// bls12-381 is pairing-friendly Barreto-Lynn-Scott elliptic curve construction allowing to:
|
|
4
|
+
// - Construct zk-SNARKs at the 120-bit security
|
|
5
|
+
// - Efficiently verify N aggregate signatures with 1 pairing and N ec additions:
|
|
6
|
+
// the Boneh-Lynn-Shacham signature scheme is orders of magnitude more efficient than Schnorr
|
|
13
7
|
//
|
|
14
8
|
// ### Summary
|
|
15
9
|
// 1. BLS Relies on Bilinear Pairing (expensive)
|
|
@@ -25,8 +19,17 @@
|
|
|
25
19
|
// - `S = pk x H(m)` - signing
|
|
26
20
|
// - `e(P, H(m)) == e(G, S)` - verification using pairings
|
|
27
21
|
// - `e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))` - signature aggregation
|
|
28
|
-
//
|
|
29
|
-
//
|
|
22
|
+
//
|
|
23
|
+
// ### Compatibility and notes
|
|
24
|
+
// 1. It is compatible with Algorand, Chia, Dfinity, Ethereum, Filecoin, ZEC
|
|
25
|
+
// Filecoin uses little endian byte arrays for private keys - make sure to reverse byte order.
|
|
26
|
+
// 2. Some projects use G2 for public keys and G1 for signatures. It's called "short signature"
|
|
27
|
+
// 3. Curve security level is about 120 bits as per Barbulescu-Duquesne 2017
|
|
28
|
+
// https://hal.science/hal-01534101/file/main.pdf
|
|
29
|
+
// 4. Compatible with specs:
|
|
30
|
+
// [cfrg-pairing-friendly-curves-11](https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-11),
|
|
31
|
+
// [cfrg-bls-signature-05](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05),
|
|
32
|
+
// [RFC 9380](https://www.rfc-editor.org/rfc/rfc9380).
|
|
30
33
|
import { sha256 } from '@noble/hashes/sha256';
|
|
31
34
|
import { randomBytes } from '@noble/hashes/utils';
|
|
32
35
|
import { bls, CurveFn } from './abstract/bls.js';
|
|
@@ -37,7 +40,6 @@ import {
|
|
|
37
40
|
numberToBytesBE,
|
|
38
41
|
bytesToNumberBE,
|
|
39
42
|
bitLen,
|
|
40
|
-
bitSet,
|
|
41
43
|
bitGet,
|
|
42
44
|
Hex,
|
|
43
45
|
bitMask,
|
|
@@ -1016,11 +1018,41 @@ const htfDefaults = Object.freeze({
|
|
|
1016
1018
|
|
|
1017
1019
|
// Encoding utils
|
|
1018
1020
|
// Point on G1 curve: (x, y)
|
|
1019
|
-
|
|
1020
|
-
const I_BIT_POS = Fp.BITS + 1; // I_bit, point-at-infinity bit for serialization flag
|
|
1021
|
-
const S_BIT_POS = Fp.BITS + 2; // S_bit, sign bit for serialization flag
|
|
1021
|
+
|
|
1022
1022
|
// Compressed point of infinity
|
|
1023
|
-
const COMPRESSED_ZERO = Fp.toBytes(
|
|
1023
|
+
const COMPRESSED_ZERO = setMask(Fp.toBytes(_0n), { infinity: true, compressed: true }); // set compressed & point-at-infinity bits
|
|
1024
|
+
|
|
1025
|
+
function parseMask(bytes: Uint8Array) {
|
|
1026
|
+
// Copy, so we can remove mask data. It will be removed also later, when Fp.create will call modulo.
|
|
1027
|
+
bytes = bytes.slice();
|
|
1028
|
+
const mask = bytes[0] & 0b1110_0000;
|
|
1029
|
+
const compressed = !!((mask >> 7) & 1); // compression bit (0b1000_0000)
|
|
1030
|
+
const infinity = !!((mask >> 6) & 1); // point at infinity bit (0b0100_0000)
|
|
1031
|
+
const sort = !!((mask >> 5) & 1); // sort bit (0b0010_0000)
|
|
1032
|
+
bytes[0] &= 0b0001_1111; // clear mask (zero first 3 bits)
|
|
1033
|
+
return { compressed, infinity, sort, value: bytes };
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function setMask(
|
|
1037
|
+
bytes: Uint8Array,
|
|
1038
|
+
mask: { compressed?: boolean; infinity?: boolean; sort?: boolean }
|
|
1039
|
+
) {
|
|
1040
|
+
if (bytes[0] & 0b1110_0000) throw new Error('setMask: non-empty mask');
|
|
1041
|
+
if (mask.compressed) bytes[0] |= 0b1000_0000;
|
|
1042
|
+
if (mask.infinity) bytes[0] |= 0b0100_0000;
|
|
1043
|
+
if (mask.sort) bytes[0] |= 0b0010_0000;
|
|
1044
|
+
return bytes;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function signatureG1ToRawBytes(point: ProjPointType<Fp>) {
|
|
1048
|
+
point.assertValidity();
|
|
1049
|
+
const isZero = point.equals(bls12_381.G1.ProjectivePoint.ZERO);
|
|
1050
|
+
const { x, y } = point.toAffine();
|
|
1051
|
+
if (isZero) return COMPRESSED_ZERO.slice();
|
|
1052
|
+
const P = Fp.ORDER;
|
|
1053
|
+
const sort = Boolean((y * _2n) / P);
|
|
1054
|
+
return setMask(numberToBytesBE(x, Fp.BYTES), { compressed: true, sort });
|
|
1055
|
+
}
|
|
1024
1056
|
|
|
1025
1057
|
function signatureG2ToRawBytes(point: ProjPointType<Fp2>) {
|
|
1026
1058
|
// NOTE: by some reasons it was missed in bls12-381, looks like bug
|
|
@@ -1032,10 +1064,12 @@ function signatureG2ToRawBytes(point: ProjPointType<Fp2>) {
|
|
|
1032
1064
|
const { re: x0, im: x1 } = Fp2.reim(x);
|
|
1033
1065
|
const { re: y0, im: y1 } = Fp2.reim(y);
|
|
1034
1066
|
const tmp = y1 > _0n ? y1 * _2n : y0 * _2n;
|
|
1035
|
-
const
|
|
1036
|
-
const z1 = bitSet(bitSet(x1, 381, aflag1), S_BIT_POS, true);
|
|
1067
|
+
const sort = Boolean((tmp / Fp.ORDER) & _1n);
|
|
1037
1068
|
const z2 = x0;
|
|
1038
|
-
return concatB(
|
|
1069
|
+
return concatB(
|
|
1070
|
+
setMask(numberToBytesBE(x1, len), { sort, compressed: true }),
|
|
1071
|
+
numberToBytesBE(z2, len)
|
|
1072
|
+
);
|
|
1039
1073
|
}
|
|
1040
1074
|
|
|
1041
1075
|
// To verify curve parameters, see pairing-friendly-curves spec:
|
|
@@ -1074,7 +1108,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
|
|
|
1074
1108
|
),
|
|
1075
1109
|
a: Fp.ZERO,
|
|
1076
1110
|
b: _4n,
|
|
1077
|
-
htfDefaults: { ...htfDefaults, m: 1 },
|
|
1111
|
+
htfDefaults: { ...htfDefaults, m: 1, DST: 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_' },
|
|
1078
1112
|
wrapPrivateKey: true,
|
|
1079
1113
|
allowInfinityPoint: true,
|
|
1080
1114
|
// Checks is the point resides in prime-order subgroup.
|
|
@@ -1116,26 +1150,30 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
|
|
|
1116
1150
|
return isogenyMapG1(x, y);
|
|
1117
1151
|
},
|
|
1118
1152
|
fromBytes: (bytes: Uint8Array): AffinePoint<Fp> => {
|
|
1119
|
-
|
|
1120
|
-
if (
|
|
1153
|
+
const { compressed, infinity, sort, value } = parseMask(bytes);
|
|
1154
|
+
if (value.length === 48 && compressed) {
|
|
1121
1155
|
// TODO: Fp.bytes
|
|
1122
1156
|
const P = Fp.ORDER;
|
|
1123
|
-
const compressedValue = bytesToNumberBE(
|
|
1124
|
-
const bflag = bitGet(compressedValue, I_BIT_POS);
|
|
1157
|
+
const compressedValue = bytesToNumberBE(value);
|
|
1125
1158
|
// Zero
|
|
1126
|
-
if (bflag === _1n) return { x: _0n, y: _0n };
|
|
1127
1159
|
const x = Fp.create(compressedValue & Fp.MASK);
|
|
1160
|
+
if (infinity) {
|
|
1161
|
+
if (x !== _0n) throw new Error('G1: non-empty compressed point at infinity');
|
|
1162
|
+
return { x: _0n, y: _0n };
|
|
1163
|
+
}
|
|
1128
1164
|
const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381.params.G1b)); // y² = x³ + b
|
|
1129
1165
|
let y = Fp.sqrt(right);
|
|
1130
1166
|
if (!y) throw new Error('Invalid compressed G1 point');
|
|
1131
|
-
|
|
1132
|
-
if ((y * _2n) / P !== aflag) y = Fp.neg(y);
|
|
1167
|
+
if ((y * _2n) / P !== BigInt(sort)) y = Fp.neg(y);
|
|
1133
1168
|
return { x: Fp.create(x), y: Fp.create(y) };
|
|
1134
|
-
} else if (
|
|
1169
|
+
} else if (value.length === 96 && !compressed) {
|
|
1135
1170
|
// Check if the infinity flag is set
|
|
1136
|
-
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1171
|
+
const x = bytesToNumberBE(value.subarray(0, Fp.BYTES));
|
|
1172
|
+
const y = bytesToNumberBE(value.subarray(Fp.BYTES));
|
|
1173
|
+
if (infinity) {
|
|
1174
|
+
if (x !== _0n || y !== _0n) throw new Error('G1: non-empty point at infinity');
|
|
1175
|
+
return bls12_381.G1.ProjectivePoint.ZERO.toAffine();
|
|
1176
|
+
}
|
|
1139
1177
|
return { x: Fp.create(x), y: Fp.create(y) };
|
|
1140
1178
|
} else {
|
|
1141
1179
|
throw new Error('Invalid point G1, expected 48/96 bytes');
|
|
@@ -1147,10 +1185,8 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
|
|
|
1147
1185
|
if (isCompressed) {
|
|
1148
1186
|
if (isZero) return COMPRESSED_ZERO.slice();
|
|
1149
1187
|
const P = Fp.ORDER;
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
num = bitSet(num, S_BIT_POS, true);
|
|
1153
|
-
return numberToBytesBE(num, Fp.BYTES);
|
|
1188
|
+
const sort = Boolean((y * _2n) / P);
|
|
1189
|
+
return setMask(numberToBytesBE(x, Fp.BYTES), { compressed: true, sort });
|
|
1154
1190
|
} else {
|
|
1155
1191
|
if (isZero) {
|
|
1156
1192
|
// 2x PUBLIC_KEY_LENGTH
|
|
@@ -1161,6 +1197,30 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
|
|
|
1161
1197
|
}
|
|
1162
1198
|
}
|
|
1163
1199
|
},
|
|
1200
|
+
ShortSignature: {
|
|
1201
|
+
fromHex(hex: Hex): ProjPointType<Fp> {
|
|
1202
|
+
const { infinity, sort, value } = parseMask(ensureBytes('signatureHex', hex, 48));
|
|
1203
|
+
const P = Fp.ORDER;
|
|
1204
|
+
const compressedValue = bytesToNumberBE(value);
|
|
1205
|
+
// Zero
|
|
1206
|
+
if (infinity) return bls12_381.G1.ProjectivePoint.ZERO;
|
|
1207
|
+
const x = Fp.create(compressedValue & Fp.MASK);
|
|
1208
|
+
const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381.params.G1b)); // y² = x³ + b
|
|
1209
|
+
let y = Fp.sqrt(right);
|
|
1210
|
+
if (!y) throw new Error('Invalid compressed G1 point');
|
|
1211
|
+
const aflag = BigInt(sort);
|
|
1212
|
+
if ((y * _2n) / P !== aflag) y = Fp.neg(y);
|
|
1213
|
+
const point = bls12_381.G1.ProjectivePoint.fromAffine({ x, y });
|
|
1214
|
+
point.assertValidity();
|
|
1215
|
+
return point;
|
|
1216
|
+
},
|
|
1217
|
+
toRawBytes(point: ProjPointType<Fp>) {
|
|
1218
|
+
return signatureG1ToRawBytes(point);
|
|
1219
|
+
},
|
|
1220
|
+
toHex(point: ProjPointType<Fp>) {
|
|
1221
|
+
return bytesToHex(signatureG1ToRawBytes(point));
|
|
1222
|
+
},
|
|
1223
|
+
},
|
|
1164
1224
|
},
|
|
1165
1225
|
// G2 is the order-q subgroup of E2(Fp²) : y² = x³+4(1+√−1),
|
|
1166
1226
|
// where Fp2 is Fp[√−1]/(x2+1). #E2(Fp2 ) = h2q, where
|
|
@@ -1232,45 +1292,45 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
|
|
|
1232
1292
|
return Q; // [x²-x-1]P + [x-1]Ψ(P) + Ψ²(2P)
|
|
1233
1293
|
},
|
|
1234
1294
|
fromBytes: (bytes: Uint8Array): AffinePoint<Fp2> => {
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1295
|
+
const { compressed, infinity, sort, value } = parseMask(bytes);
|
|
1296
|
+
if (
|
|
1297
|
+
(!compressed && !infinity && sort) || // 00100000
|
|
1298
|
+
(!compressed && infinity && sort) || // 01100000
|
|
1299
|
+
(sort && infinity && compressed) // 11100000
|
|
1300
|
+
) {
|
|
1301
|
+
throw new Error('Invalid encoding flag: ' + (bytes[0] & 0b1110_0000));
|
|
1239
1302
|
}
|
|
1240
|
-
const bitC = m_byte & 0x80; // compression bit
|
|
1241
|
-
const bitI = m_byte & 0x40; // point at infinity bit
|
|
1242
|
-
const bitS = m_byte & 0x20; // sign bit
|
|
1243
1303
|
const L = Fp.BYTES;
|
|
1244
1304
|
const slc = (b: Uint8Array, from: number, to?: number) => bytesToNumberBE(b.slice(from, to));
|
|
1245
|
-
if (
|
|
1305
|
+
if (value.length === 96 && compressed) {
|
|
1246
1306
|
const b = bls12_381.params.G2b;
|
|
1247
1307
|
const P = Fp.ORDER;
|
|
1248
|
-
|
|
1249
|
-
bytes[0] = bytes[0] & 0x1f; // clear flags
|
|
1250
|
-
if (bitI) {
|
|
1308
|
+
if (infinity) {
|
|
1251
1309
|
// check that all bytes are 0
|
|
1252
|
-
if (
|
|
1310
|
+
if (value.reduce((p, c) => (p !== 0 ? c + 1 : c), 0) > 0) {
|
|
1253
1311
|
throw new Error('Invalid compressed G2 point');
|
|
1254
1312
|
}
|
|
1255
1313
|
return { x: Fp2.ZERO, y: Fp2.ZERO };
|
|
1256
1314
|
}
|
|
1257
|
-
const x_1 = slc(
|
|
1258
|
-
const x_0 = slc(
|
|
1315
|
+
const x_1 = slc(value, 0, L);
|
|
1316
|
+
const x_0 = slc(value, L, 2 * L);
|
|
1259
1317
|
const x = Fp2.create({ c0: Fp.create(x_0), c1: Fp.create(x_1) });
|
|
1260
1318
|
const right = Fp2.add(Fp2.pow(x, _3n), b); // y² = x³ + 4 * (u+1) = x³ + b
|
|
1261
1319
|
let y = Fp2.sqrt(right);
|
|
1262
1320
|
const Y_bit = y.c1 === _0n ? (y.c0 * _2n) / P : (y.c1 * _2n) / P ? _1n : _0n;
|
|
1263
|
-
y =
|
|
1321
|
+
y = sort && Y_bit > 0 ? y : Fp2.neg(y);
|
|
1264
1322
|
return { x, y };
|
|
1265
|
-
} else if (
|
|
1266
|
-
|
|
1267
|
-
|
|
1323
|
+
} else if (value.length === 192 && !compressed) {
|
|
1324
|
+
if (infinity) {
|
|
1325
|
+
if (value.reduce((p, c) => (p !== 0 ? c + 1 : c), 0) > 0) {
|
|
1326
|
+
throw new Error('Invalid uncompressed G2 point');
|
|
1327
|
+
}
|
|
1268
1328
|
return { x: Fp2.ZERO, y: Fp2.ZERO };
|
|
1269
1329
|
}
|
|
1270
|
-
const x1 = slc(
|
|
1271
|
-
const x0 = slc(
|
|
1272
|
-
const y1 = slc(
|
|
1273
|
-
const y0 = slc(
|
|
1330
|
+
const x1 = slc(value, 0, L);
|
|
1331
|
+
const x0 = slc(value, L, 2 * L);
|
|
1332
|
+
const y1 = slc(value, 2 * L, 3 * L);
|
|
1333
|
+
const y0 = slc(value, 3 * L, 4 * L);
|
|
1274
1334
|
return { x: Fp2.fromBigTuple([x0, x1]), y: Fp2.fromBigTuple([y0, y1]) };
|
|
1275
1335
|
} else {
|
|
1276
1336
|
throw new Error('Invalid point G2, expected 96/192 bytes');
|
|
@@ -1283,10 +1343,10 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
|
|
|
1283
1343
|
if (isCompressed) {
|
|
1284
1344
|
if (isZero) return concatB(COMPRESSED_ZERO, numberToBytesBE(_0n, len));
|
|
1285
1345
|
const flag = Boolean(y.c1 === _0n ? (y.c0 * _2n) / P : (y.c1 * _2n) / P);
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1346
|
+
return concatB(
|
|
1347
|
+
setMask(numberToBytesBE(x.c1, len), { compressed: true, sort: flag }),
|
|
1348
|
+
numberToBytesBE(x.c0, len)
|
|
1349
|
+
);
|
|
1290
1350
|
} else {
|
|
1291
1351
|
if (isZero) return concatB(new Uint8Array([0x40]), new Uint8Array(4 * len - 1)); // bytes[0] |= 1 << 6;
|
|
1292
1352
|
const { re: x0, im: x1 } = Fp2.reim(x);
|
|
@@ -1302,17 +1362,15 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
|
|
|
1302
1362
|
Signature: {
|
|
1303
1363
|
// TODO: Optimize, it's very slow because of sqrt.
|
|
1304
1364
|
fromHex(hex: Hex): ProjPointType<Fp2> {
|
|
1305
|
-
|
|
1365
|
+
const { infinity, sort, value } = parseMask(ensureBytes('signatureHex', hex));
|
|
1306
1366
|
const P = Fp.ORDER;
|
|
1307
1367
|
const half = hex.length / 2;
|
|
1308
1368
|
if (half !== 48 && half !== 96)
|
|
1309
1369
|
throw new Error('Invalid compressed signature length, must be 96 or 192');
|
|
1310
|
-
const z1 = bytesToNumberBE(
|
|
1311
|
-
const z2 = bytesToNumberBE(
|
|
1370
|
+
const z1 = bytesToNumberBE(value.slice(0, half));
|
|
1371
|
+
const z2 = bytesToNumberBE(value.slice(half));
|
|
1312
1372
|
// Indicates the infinity point
|
|
1313
|
-
|
|
1314
|
-
if (bflag1 === _1n) return bls12_381.G2.ProjectivePoint.ZERO;
|
|
1315
|
-
|
|
1373
|
+
if (infinity) return bls12_381.G2.ProjectivePoint.ZERO;
|
|
1316
1374
|
const x1 = Fp.create(z1 & Fp.MASK);
|
|
1317
1375
|
const x2 = Fp.create(z2);
|
|
1318
1376
|
const x = Fp2.create({ c0: x2, c1: x1 });
|
|
@@ -1324,7 +1382,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
|
|
|
1324
1382
|
// Choose the y whose leftmost bit of the imaginary part is equal to the a_flag1
|
|
1325
1383
|
// If y1 happens to be zero, then use the bit of y0
|
|
1326
1384
|
const { re: y0, im: y1 } = Fp2.reim(y);
|
|
1327
|
-
const aflag1 =
|
|
1385
|
+
const aflag1 = BigInt(sort);
|
|
1328
1386
|
const isGreater = y1 > _0n && (y1 * _2n) / P !== aflag1;
|
|
1329
1387
|
const isZero = y1 === _0n && (y0 * _2n) / P !== aflag1;
|
|
1330
1388
|
if (isGreater || isZero) y = Fp2.neg(y);
|
package/src/bn254.ts
CHANGED
|
@@ -6,8 +6,9 @@ import { Field } from './abstract/modular.js';
|
|
|
6
6
|
/**
|
|
7
7
|
* bn254 pairing-friendly curve.
|
|
8
8
|
* Previously known as alt_bn_128, when it had 128-bit security.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* Barbulescu-Duquesne 2017 shown it's weaker: just about 100 bits,
|
|
10
|
+
* so the naming has been adjusted to its prime bit count
|
|
11
|
+
* https://hal.science/hal-01534101/file/main.pdf
|
|
11
12
|
*/
|
|
12
13
|
export const bn254 = weierstrass({
|
|
13
14
|
a: BigInt(0),
|
package/src/ed25519.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
numberToBytesLE,
|
|
14
14
|
} from './abstract/utils.js';
|
|
15
15
|
import { createHasher, htfBasicOpts, expand_message_xmd } from './abstract/hash-to-curve.js';
|
|
16
|
-
import { AffinePoint } from './abstract/curve.js';
|
|
16
|
+
import { AffinePoint, Group } from './abstract/curve.js';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* ed25519 Twisted Edwards curve with following addons:
|
|
@@ -343,7 +343,7 @@ function calcElligatorRistrettoMap(r0: bigint): ExtendedPoint {
|
|
|
343
343
|
* but it should work in its own namespace: do not combine those two.
|
|
344
344
|
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448
|
|
345
345
|
*/
|
|
346
|
-
class RistPoint {
|
|
346
|
+
class RistPoint implements Group<RistPoint> {
|
|
347
347
|
static BASE: RistPoint;
|
|
348
348
|
static ZERO: RistPoint;
|
|
349
349
|
// Private property to discourage combining ExtendedPoint + RistrettoPoint
|
|
@@ -471,6 +471,14 @@ class RistPoint {
|
|
|
471
471
|
multiplyUnsafe(scalar: bigint): RistPoint {
|
|
472
472
|
return new RistPoint(this.ep.multiplyUnsafe(scalar));
|
|
473
473
|
}
|
|
474
|
+
|
|
475
|
+
double(): RistPoint {
|
|
476
|
+
return new RistPoint(this.ep.double());
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
negate(): RistPoint {
|
|
480
|
+
return new RistPoint(this.ep.negate());
|
|
481
|
+
}
|
|
474
482
|
}
|
|
475
483
|
export const RistrettoPoint = /* @__PURE__ */ (() => {
|
|
476
484
|
if (!RistPoint.BASE) RistPoint.BASE = new RistPoint(ed25519.ExtendedPoint.BASE);
|