-
Notifications
You must be signed in to change notification settings - Fork 778
/
Copy pathnoble.ts
345 lines (293 loc) · 11.2 KB
/
noble.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
import {
BIGINT_0,
bigIntToBytes,
bytesToBigInt,
concatBytes,
equalsBytes,
setLengthLeft,
} from '@ethereumjs/util'
import { bls12_381 } from '@noble/curves/bls12-381'
import { ERROR, EvmError } from '../../exceptions.js'
import {
BLS_FIELD_MODULUS,
BLS_G1_INFINITY_POINT_BYTES,
BLS_G1_POINT_BYTE_LENGTH,
BLS_G2_INFINITY_POINT_BYTES,
BLS_G2_POINT_BYTE_LENGTH,
BLS_ONE_BUFFER,
BLS_ZERO_BUFFER,
} from './constants.js'
import type { EVMBLSInterface } from '../../types.js'
import type { AffinePoint, ProjPointType } from '@noble/curves/abstract/weierstrass'
// Copied from @noble/curves/bls12-381 (only local declaration)
type Fp2 = {
c0: bigint
c1: bigint
}
/**
* Converts an Uint8Array to a Noble G1 point. Raises errors if the point is not on the curve
* and (if activated) if the point is in the subgroup / order check.
* @param input Input Uint8Array. Should be 128 bytes
* @returns Noble G1 point
*/
function BLS12_381_ToG1Point(input: Uint8Array) {
if (equalsBytes(input, BLS_G1_INFINITY_POINT_BYTES)) {
return bls12_381.G1.ProjectivePoint.ZERO
}
const x = bytesToBigInt(input.subarray(16, BLS_G1_POINT_BYTE_LENGTH / 2))
const y = bytesToBigInt(input.subarray(80, BLS_G1_POINT_BYTE_LENGTH))
const G1 = bls12_381.G1.ProjectivePoint.fromAffine({
x,
y,
})
return G1
}
// input: a Noble G1 point
// output: a 128-byte Uint8Array
function BLS12_381_FromG1Point(input: AffinePoint<bigint>): Uint8Array {
const xBytes = setLengthLeft(bigIntToBytes(input.x), 64)
const yBytes = setLengthLeft(bigIntToBytes(input.y), 64)
return concatBytes(xBytes, yBytes)
}
/**
* Converts an Uint8Array to a Noble G2 point. Raises errors if the point is not on the curve
* and (if activated) if the point is in the subgroup / order check.
* @param input Input Uint8Array. Should be 256 bytes
* @returns Noble G2 point
*/
function BLS12_381_ToG2Point(input: Uint8Array) {
if (equalsBytes(input, BLS_G2_INFINITY_POINT_BYTES)) {
return bls12_381.G2.ProjectivePoint.ZERO
}
const p_x_1 = input.subarray(0, 64)
const p_x_2 = input.subarray(64, BLS_G2_POINT_BYTE_LENGTH / 2)
const p_y_1 = input.subarray(128, 192)
const p_y_2 = input.subarray(192, BLS_G2_POINT_BYTE_LENGTH)
const Fp2X = BLS12_381_ToFp2Point(p_x_1, p_x_2)
const Fp2Y = BLS12_381_ToFp2Point(p_y_1, p_y_2)
const pG2 = bls12_381.G2.ProjectivePoint.fromAffine({
x: Fp2X,
y: Fp2Y,
})
return pG2
}
// input: a Noble G1 point
// output: a 128-byte Uint8Array
function BLS12_381_FromG2Point(input: AffinePoint<Fp2>): Uint8Array {
const xBytes1 = setLengthLeft(bigIntToBytes(input.x.c0), 64)
const xBytes2 = setLengthLeft(bigIntToBytes(input.x.c1), 64)
const yBytes1 = setLengthLeft(bigIntToBytes(input.y.c0), 64)
const yBytes2 = setLengthLeft(bigIntToBytes(input.y.c1), 64)
return concatBytes(xBytes1, xBytes2, yBytes1, yBytes2)
}
// input: a 32-byte hex scalar Uint8Array
// output: a Noble Fr point
function BLS12_381_ToFrPoint(input: Uint8Array): bigint {
const Fr = bls12_381.fields.Fr.fromBytes(input)
// TODO: This fixes the following two failing tests:
// bls_g1mul_random*g1_unnormalized_scalar
// bls_g1mul_random*p1_unnormalized_scalar
// It should be nevertheless validated if this is (fully) correct,
// especially if ">" or ">=" should be applied.
//
// Unfortunately the skalar in both test vectors is significantly
// greater than the ORDER threshold, here are th values from both tests:
//
// Skalar / Order
// 69732848789442042582239751384143889712113271203482973843852656394296700715236n
// 52435875175126190479447740508185965837690552500527637822603658699938581184513n
//
// There should be 4 test cases added to the official test suite:
// 1. bls_g1mul_random*g1_unnormalized_scalar within threshold (ORDER (?))
// 2. bls_g1mul_random*g1_unnormalized_scalar outside threshold (ORDER + 1 (?))
// 3. bls_g1mul_random*p1_unnormalized_scalar within threshold (ORDER (?))
// 4. bls_g1mul_random*p1_unnormalized_scalar outside threshold (ORDER + 1 (?))
//
if (Fr > bls12_381.fields.Fr.ORDER) {
return Fr % bls12_381.fields.Fr.ORDER
}
return Fr
}
// input: a 64-byte buffer
// output: a Noble Fp point
function BLS12_381_ToFpPoint(fpCoordinate: Uint8Array) {
// check if point is in field
if (bytesToBigInt(fpCoordinate) >= BLS_FIELD_MODULUS) {
throw new EvmError(ERROR.BLS_12_381_FP_NOT_IN_FIELD)
}
const FP = bls12_381.fields.Fp.fromBytes(fpCoordinate.slice(16))
return FP
}
function BLS12_381_ToFp2Point(fpXCoordinate: Uint8Array, fpYCoordinate: Uint8Array) {
// check if the coordinates are in the field
if (bytesToBigInt(fpXCoordinate) >= BLS_FIELD_MODULUS) {
throw new EvmError(ERROR.BLS_12_381_FP_NOT_IN_FIELD)
}
if (bytesToBigInt(fpYCoordinate) >= BLS_FIELD_MODULUS) {
throw new EvmError(ERROR.BLS_12_381_FP_NOT_IN_FIELD)
}
const fpBytes = concatBytes(fpXCoordinate.subarray(16), fpYCoordinate.subarray(16))
const FP = bls12_381.fields.Fp2.fromBytes(fpBytes)
return FP
}
/**
* Implementation of the `EVMBLSInterface` using the `@noble/curves` JS library,
* see https://github.com/paulmillr/noble-curves.
*
* This is the EVM default implementation.
*/
export class NobleBLS implements EVMBLSInterface {
addG1(input: Uint8Array): Uint8Array {
const p1 = BLS12_381_ToG1Point(input.subarray(0, BLS_G1_POINT_BYTE_LENGTH))
const p2 = BLS12_381_ToG1Point(
input.subarray(BLS_G1_POINT_BYTE_LENGTH, BLS_G1_POINT_BYTE_LENGTH * 2),
)
const p = p1.add(p2)
const result = BLS12_381_FromG1Point(p)
return result
}
mulG1(input: Uint8Array): Uint8Array {
// convert input to G1 points, add them, and convert the output to a Uint8Array.
const p = BLS12_381_ToG1Point(input.subarray(0, BLS_G1_POINT_BYTE_LENGTH))
const skalar = BLS12_381_ToFrPoint(input.subarray(BLS_G1_POINT_BYTE_LENGTH, 160))
if (skalar === BIGINT_0) {
return BLS_G1_INFINITY_POINT_BYTES
}
const result = p.multiply(skalar)
return BLS12_381_FromG1Point(result)
}
addG2(input: Uint8Array): Uint8Array {
const p1 = BLS12_381_ToG2Point(input.subarray(0, BLS_G2_POINT_BYTE_LENGTH))
const p2 = BLS12_381_ToG2Point(
input.subarray(BLS_G2_POINT_BYTE_LENGTH, BLS_G2_POINT_BYTE_LENGTH * 2),
)
const p = p1.add(p2)
const result = BLS12_381_FromG2Point(p)
return result
}
mulG2(input: Uint8Array): Uint8Array {
// convert input to G2 point/Fr point, add them, and convert the output to a Uint8Array.
const p = BLS12_381_ToG2Point(input.subarray(0, BLS_G2_POINT_BYTE_LENGTH))
const skalar = BLS12_381_ToFrPoint(input.subarray(BLS_G2_POINT_BYTE_LENGTH, 288))
if (skalar === BIGINT_0) {
return BLS_G2_INFINITY_POINT_BYTES
}
const result = p.multiply(skalar)
return BLS12_381_FromG2Point(result)
}
mapFPtoG1(input: Uint8Array): Uint8Array {
// convert input to Fp1 point
const FP = BLS12_381_ToFpPoint(input.subarray(0, 64))
const result = bls12_381.G1.mapToCurve([FP]).toAffine()
const resultBytes = BLS12_381_FromG1Point(result)
return resultBytes
}
mapFP2toG2(input: Uint8Array): Uint8Array {
// convert input to Fp2 point
const Fp2Point = BLS12_381_ToFp2Point(input.subarray(0, 64), input.subarray(64, 128))
const result = bls12_381.G2.mapToCurve([Fp2Point.c0, Fp2Point.c1]).toAffine()
const resultBytes = BLS12_381_FromG2Point(result)
return resultBytes
}
msmG1(input: Uint8Array): Uint8Array {
// Note: This implementation is using the naive "algorithm" of just doing
// p1G1*v1F1 + p2G1*v1F1 + ... while the EIP is suggesting to use an optimized
// algorithm (Pippenger's algorithm, see https://eips.ethereum.org/EIPS/eip-2537#g1g2-msm).
//
// While this functionally works the approach is not "gas-cost-competitive" and an
// optimization should be considered in the future.
const pairLength = 160
const numPairs = input.length / pairLength
let pRes = bls12_381.G1.ProjectivePoint.ZERO
for (let k = 0; k < numPairs; k++) {
const pairStart = pairLength * k
const G1 = BLS12_381_ToG1Point(
input.subarray(pairStart, pairStart + BLS_G1_POINT_BYTE_LENGTH),
)
const Fr = BLS12_381_ToFrPoint(
input.subarray(pairStart + BLS_G1_POINT_BYTE_LENGTH, pairStart + pairLength),
)
let pMul
if (Fr === BIGINT_0) {
pMul = bls12_381.G1.ProjectivePoint.ZERO
} else {
pMul = G1.multiply(Fr)
}
pRes = pRes.add(pMul)
}
return BLS12_381_FromG1Point(pRes)
}
msmG2(input: Uint8Array): Uint8Array {
// Note: This implementation is using the naive "algorithm" of just doing
// p1G1*v1F1 + p2G1*v1F1 + ... while the EIP is suggesting to use an optimized
// algorithm (Pippenger's algorithm, see https://eips.ethereum.org/EIPS/eip-2537#g1g2-msm).
//
// While this functionally works the approach is not "gas-cost-competitive" and an
// optimization should be considered in the future.
const pairLength = 288
const numPairs = input.length / pairLength
let pRes = bls12_381.G2.ProjectivePoint.ZERO
for (let k = 0; k < numPairs; k++) {
const pairStart = pairLength * k
const G2 = BLS12_381_ToG2Point(
input.subarray(pairStart, pairStart + BLS_G2_POINT_BYTE_LENGTH),
)
const Fr = BLS12_381_ToFrPoint(
input.subarray(pairStart + BLS_G2_POINT_BYTE_LENGTH, pairStart + pairLength),
)
let pMul
if (Fr === BIGINT_0) {
pMul = bls12_381.G2.ProjectivePoint.ZERO
} else {
pMul = G2.multiply(Fr)
}
pRes = pRes.add(pMul)
}
return BLS12_381_FromG2Point(pRes)
}
pairingCheck(input: Uint8Array): Uint8Array {
const pairLength = 384
const pairs = []
for (let k = 0; k < input.length / pairLength; k++) {
const pairStart = pairLength * k
const G1 = BLS12_381_ToG1Point(
input.subarray(pairStart, pairStart + BLS_G1_POINT_BYTE_LENGTH),
)
const g2start = pairStart + BLS_G1_POINT_BYTE_LENGTH
const G2 = BLS12_381_ToG2Point(input.subarray(g2start, g2start + BLS_G2_POINT_BYTE_LENGTH))
// EIP: "If any input is the infinity point, pairing result will be 1"
if (G1 === bls12_381.G1.ProjectivePoint.ZERO || G2 === bls12_381.G2.ProjectivePoint.ZERO) {
return BLS_ONE_BUFFER
}
pairs.push([G1, G2])
}
// run the pairing check
// reference (Nethermind): https://github.com/NethermindEth/nethermind/blob/374b036414722b9c8ad27e93d64840b8f63931b9/src/Nethermind/Nethermind.Evm/Precompiles/Bls/Mcl/PairingPrecompile.cs#L93
let GT: any // Fp12 type not exported, eventually too complex
for (let index = 0; index < pairs.length; index++) {
const pair = pairs[index]
const G1 = pair[0] as ProjPointType<bigint>
const G2 = pair[1] as ProjPointType<Fp2>
if (index === 0) {
GT = bls12_381.pairing(G1, G2)
} else {
GT = bls12_381.fields.Fp12.mul(GT!, bls12_381.pairing(G1, G2))
}
}
const FP12 = bls12_381.fields.Fp12.finalExponentiate(GT!)
if (bls12_381.fields.Fp12.eql(FP12, bls12_381.fields.Fp12.ONE)) {
return BLS_ONE_BUFFER
} else {
return BLS_ZERO_BUFFER
}
}
}
export {
BLS12_381_FromG1Point,
BLS12_381_FromG2Point,
BLS12_381_ToFp2Point,
BLS12_381_ToFpPoint,
BLS12_381_ToFrPoint,
BLS12_381_ToG1Point,
BLS12_381_ToG2Point,
}