Line data Source code
1 : #include "fd_bls12_381.h"
2 : #include "../bigint/fd_uint256.h"
3 :
4 : #include <blst.h>
5 :
6 : /* Scalar */
7 :
8 : typedef blst_scalar fd_bls12_381_scalar_t;
9 :
10 : static inline fd_bls12_381_scalar_t *
11 : fd_bls12_381_scalar_frombytes( fd_bls12_381_scalar_t * n,
12 : uchar const in[ 32 ],
13 0 : int big_endian ) {
14 : /* https://github.com/filecoin-project/blstrs/blob/v0.7.1/src/scalar.rs#L551-L569 */
15 0 : if( big_endian ) {
16 0 : blst_scalar_from_bendian( n, in );
17 0 : } else {
18 0 : blst_scalar_from_lendian( n, in );
19 0 : }
20 0 : if( FD_UNLIKELY( !blst_scalar_fr_check( n ) ) ) {
21 0 : return NULL;
22 0 : }
23 0 : return n;
24 0 : }
25 :
26 : /* G1 serde */
27 :
28 : typedef blst_p1_affine fd_bls12_381_g1aff_t;
29 : typedef blst_p1 fd_bls12_381_g1_t;
30 :
31 : static inline void
32 : fd_bls12_381_g1_bswap( uchar out[ 96 ], /* out can be in */
33 0 : uchar const in [ 96 ] ) {
34 : /* copy into aligned memory */
35 0 : ulong e[ 96/sizeof(ulong) ];
36 0 : memcpy( e, in, 96 );
37 :
38 : /* bswap X, Y independently (48 bytes each) */
39 0 : fd_ulong_n_bswap( e+0, 6 );
40 0 : fd_ulong_n_bswap( e+6, 6 );
41 :
42 : /* copy to out */
43 0 : memcpy( out, e, 96 );
44 0 : }
45 :
46 : static inline uchar *
47 : fd_bls12_381_g1_tobytes( uchar out[ 96 ],
48 : fd_bls12_381_g1_t const * a,
49 0 : int big_endian ) {
50 0 : blst_p1_serialize( out, a );
51 0 : if( !big_endian ) {
52 0 : fd_bls12_381_g1_bswap( out, out );
53 0 : }
54 0 : return out;
55 0 : }
56 :
57 : static inline fd_bls12_381_g1aff_t *
58 : fd_bls12_381_g1_frombytes_unchecked( fd_bls12_381_g1aff_t * r,
59 : uchar const _in[ 96 ],
60 0 : int big_endian ) {
61 0 : ulong be[ 96/sizeof(ulong) ];
62 0 : uchar const * in = _in;
63 0 : if( !big_endian ) {
64 0 : fd_bls12_381_g1_bswap( (uchar *)be, _in );
65 0 : in = (uchar *)be;
66 0 : }
67 :
68 : /* Reject the point if the compressed or parity flag is set.
69 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.2/bls12-381/src/encoding.rs#L57-L60 */
70 0 : if( FD_UNLIKELY( in[ 0 ] & 0xA0 ) ) {
71 0 : return NULL;
72 0 : }
73 :
74 0 : if( FD_UNLIKELY( blst_p1_deserialize( r, in )!=BLST_SUCCESS ) ) {
75 0 : return NULL;
76 0 : }
77 0 : return r;
78 0 : }
79 :
80 : static inline fd_bls12_381_g1aff_t *
81 : fd_bls12_381_g1_frombytes( fd_bls12_381_g1aff_t * r,
82 : uchar const in[ 96 ],
83 0 : int big_endian ) {
84 0 : if( FD_UNLIKELY( !fd_bls12_381_g1_frombytes_unchecked( r, in, big_endian ) ) ) {
85 0 : return NULL;
86 0 : }
87 0 : if( FD_UNLIKELY( !blst_p1_affine_in_g1( r ) ) ) {
88 0 : return NULL;
89 0 : }
90 0 : return r;
91 0 : }
92 :
93 : /* G1 syscalls */
94 :
95 : int
96 : fd_bls12_381_g1_decompress_syscall( uchar _r[ 96 ],
97 : uchar const _a[ 48 ],
98 0 : int big_endian ) {
99 : /* blst expects input in big endian. if little endian, bswap. */
100 0 : ulong be[ 48/sizeof(ulong) ];
101 0 : uchar const * in = _a;
102 0 : if( !big_endian ) {
103 0 : in = (uchar *)be;
104 0 : memcpy( be, _a, 48 );
105 0 : fd_ulong_n_bswap( be, 6 );
106 0 : }
107 :
108 : /* decompress and serialize */
109 0 : fd_bls12_381_g1aff_t r[1];
110 0 : if( FD_UNLIKELY( blst_p1_uncompress( r, in )!=BLST_SUCCESS ) ) {
111 0 : return -1;
112 0 : }
113 0 : if( FD_UNLIKELY( !blst_p1_affine_in_g1( r ) ) ) {
114 0 : return -1;
115 0 : }
116 0 : blst_p1_affine_serialize( _r, r );
117 :
118 : /* blst output is big endian. if we want little endian, bswap. */
119 0 : if( !big_endian ) {
120 0 : fd_bls12_381_g1_bswap( _r, _r );
121 0 : }
122 0 : return 0;
123 0 : }
124 :
125 : int
126 : fd_bls12_381_g1_validate_syscall( uchar const _a[ 96 ],
127 0 : int big_endian ) {
128 0 : fd_bls12_381_g1aff_t a[1];
129 0 : return !!fd_bls12_381_g1_frombytes( a, _a, big_endian );
130 0 : }
131 :
132 : int
133 : fd_bls12_381_g1_add_syscall( uchar _r[ 96 ],
134 : uchar const _a[ 96 ],
135 : uchar const _b[ 96 ],
136 0 : int big_endian ) {
137 : /* points a, b are unchecked per SIMD-0388 */
138 0 : fd_bls12_381_g1aff_t a[1], b[1];
139 0 : if( FD_UNLIKELY( fd_bls12_381_g1_frombytes_unchecked( a, _a, big_endian )==NULL ) ) {
140 0 : return -1;
141 0 : }
142 0 : if( FD_UNLIKELY( fd_bls12_381_g1_frombytes_unchecked( b, _b, big_endian )==NULL ) ) {
143 0 : return -1;
144 0 : }
145 :
146 0 : fd_bls12_381_g1_t r[1], p[1];
147 0 : blst_p1_from_affine( p, a );
148 0 : blst_p1_add_or_double_affine( r, p, b );
149 :
150 0 : fd_bls12_381_g1_tobytes( _r, r, big_endian );
151 0 : return 0;
152 0 : }
153 :
154 : int
155 : fd_bls12_381_g1_sub_syscall( uchar _r[ 96 ],
156 : uchar const _a[ 96 ],
157 : uchar const _b[ 96 ],
158 0 : int big_endian ) {
159 : /* points a, b are unchecked per SIMD-0388 */
160 0 : fd_bls12_381_g1aff_t a[1], b[1];
161 0 : if( FD_UNLIKELY( fd_bls12_381_g1_frombytes_unchecked( a, _a, big_endian )==NULL ) ) {
162 0 : return -1;
163 0 : }
164 0 : if( FD_UNLIKELY( fd_bls12_381_g1_frombytes_unchecked( b, _b, big_endian )==NULL ) ) {
165 0 : return -1;
166 0 : }
167 :
168 0 : fd_bls12_381_g1_t r[1], p[1];
169 0 : blst_p1_from_affine( p, a );
170 0 : blst_fp_cneg( &b->y, &b->y, 1 ); /* -b, it works also with b=0 */
171 0 : blst_p1_add_or_double_affine( r, p, b );
172 :
173 0 : fd_bls12_381_g1_tobytes( _r, r, big_endian );
174 0 : return 0;
175 0 : }
176 :
177 : int
178 : fd_bls12_381_g1_mul_syscall( uchar _r[ 96 ],
179 : uchar const _n[ 32 ],
180 : uchar const _a[ 96 ],
181 0 : int big_endian ) {
182 : /* point a, scalar n are validated per SIMD-0388 */
183 0 : fd_bls12_381_g1aff_t a[1];
184 0 : fd_bls12_381_scalar_t n[1];
185 0 : if( FD_UNLIKELY( fd_bls12_381_g1_frombytes( a, _a, big_endian )==NULL ) ) {
186 0 : return -1;
187 0 : }
188 0 : if( FD_UNLIKELY( fd_bls12_381_scalar_frombytes( n, _n, big_endian )==NULL ) ) {
189 0 : return -1;
190 0 : }
191 :
192 0 : fd_bls12_381_g1_t r[1], p[1];
193 0 : blst_p1_from_affine( p, a );
194 : /* https://github.com/filecoin-project/blstrs/blob/v0.7.1/src/g1.rs#L578-L580 */
195 0 : blst_p1_mult( r, p, n->b, 255 );
196 :
197 0 : fd_bls12_381_g1_tobytes( _r, r, big_endian );
198 0 : return 0;
199 0 : }
200 :
201 : /* G2 serde */
202 :
203 : typedef blst_p2_affine fd_bls12_381_g2aff_t;
204 : typedef blst_p2 fd_bls12_381_g2_t;
205 :
206 : static inline void
207 : fd_bls12_381_g2_bswap( uchar out[ 96*2 ], /* out can be in */
208 0 : uchar const in [ 96*2 ] ) {
209 : /* copy into aligned memory */
210 0 : ulong e[ 96*2/sizeof(ulong) ];
211 0 : memcpy( e, in, 96*2 );
212 :
213 : /* bswap X, Y independently (96 bytes each) */
214 0 : fd_ulong_n_bswap( e+00, 12 );
215 0 : fd_ulong_n_bswap( e+12, 12 );
216 :
217 : /* copy to out */
218 0 : memcpy( out, e, 96*2 );
219 0 : }
220 :
221 : static inline uchar *
222 : fd_bls12_381_g2_tobytes( uchar out[ 96*2 ],
223 : fd_bls12_381_g2_t const * a,
224 0 : int big_endian ) {
225 0 : blst_p2_serialize( out, a );
226 0 : if( !big_endian ) {
227 0 : fd_bls12_381_g2_bswap( out, out );
228 0 : }
229 0 : return out;
230 0 : }
231 :
232 : static inline fd_bls12_381_g2aff_t *
233 : fd_bls12_381_g2_frombytes_unchecked( fd_bls12_381_g2aff_t * r,
234 : uchar const _in[ 96*2 ],
235 0 : int big_endian ) {
236 0 : ulong be[ 96*2/sizeof(ulong) ];
237 0 : uchar const * in = _in;
238 0 : if( !big_endian ) {
239 0 : fd_bls12_381_g2_bswap( (uchar *)be, _in );
240 0 : in = (uchar *)be;
241 0 : }
242 :
243 : /* Reject the point if the compressed or parity flag is set.
244 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.2/bls12-381/src/encoding.rs#L103-L106 */
245 0 : if( FD_UNLIKELY( in[ 0 ] & 0xA0 ) ) {
246 0 : return NULL;
247 0 : }
248 :
249 0 : if( FD_UNLIKELY( blst_p2_deserialize( r, in )!=BLST_SUCCESS ) ) {
250 0 : return NULL;
251 0 : }
252 0 : return r;
253 0 : }
254 :
255 : static inline fd_bls12_381_g2aff_t *
256 : fd_bls12_381_g2_frombytes( fd_bls12_381_g2aff_t * r,
257 : uchar const in[ 96*2 ],
258 0 : int big_endian ) {
259 0 : if( FD_UNLIKELY( !fd_bls12_381_g2_frombytes_unchecked( r, in, big_endian ) ) ) {
260 0 : return NULL;
261 0 : }
262 0 : if( FD_UNLIKELY( !blst_p2_affine_in_g2( r ) ) ) {
263 0 : return NULL;
264 0 : }
265 0 : return r;
266 0 : }
267 :
268 : /* G2 syscalls */
269 :
270 : int
271 : fd_bls12_381_g2_decompress_syscall( uchar _r[ 96*2 ],
272 : uchar const _a[ 48*2 ],
273 0 : int big_endian ) {
274 : /* blst expects input in big endian. if little endian, bswap. */
275 0 : ulong be[ 48*2/sizeof(ulong) ];
276 0 : uchar const * in = _a;
277 0 : if( !big_endian ) {
278 0 : in = (uchar *)be;
279 0 : memcpy( be, _a, 48*2 );
280 0 : fd_ulong_n_bswap( be, 6*2 );
281 0 : }
282 :
283 : /* decompress and serialize */
284 0 : fd_bls12_381_g2aff_t r[1];
285 0 : if( FD_UNLIKELY( blst_p2_uncompress( r, in )!=BLST_SUCCESS ) ) {
286 0 : return -1;
287 0 : }
288 0 : if( FD_UNLIKELY( !blst_p2_affine_in_g2( r ) ) ) {
289 0 : return -1;
290 0 : }
291 0 : blst_p2_affine_serialize( _r, r );
292 :
293 : /* blst output is big endian. if we want little endian, bswap. */
294 0 : if( !big_endian ) {
295 0 : fd_bls12_381_g2_bswap( _r, _r );
296 0 : }
297 0 : return 0;
298 0 : }
299 :
300 : int
301 : fd_bls12_381_g2_validate_syscall( uchar const _a[ 96*2 ],
302 0 : int big_endian ) {
303 0 : fd_bls12_381_g2aff_t a[1];
304 0 : return !!fd_bls12_381_g2_frombytes( a, _a, big_endian );
305 0 : }
306 :
307 : int
308 : fd_bls12_381_g2_add_syscall( uchar _r[ 96*2 ],
309 : uchar const _a[ 96*2 ],
310 : uchar const _b[ 96*2 ],
311 0 : int big_endian ) {
312 : /* points a, b are unchecked per SIMD-0388 */
313 0 : fd_bls12_381_g2aff_t a[1], b[1];
314 0 : if( FD_UNLIKELY( fd_bls12_381_g2_frombytes_unchecked( a, _a, big_endian )==NULL ) ) {
315 0 : return -1;
316 0 : }
317 0 : if( FD_UNLIKELY( fd_bls12_381_g2_frombytes_unchecked( b, _b, big_endian )==NULL ) ) {
318 0 : return -1;
319 0 : }
320 :
321 0 : fd_bls12_381_g2_t r[1], p[1];
322 0 : blst_p2_from_affine( p, a );
323 0 : blst_p2_add_or_double_affine( r, p, b );
324 :
325 0 : fd_bls12_381_g2_tobytes( _r, r, big_endian );
326 0 : return 0;
327 0 : }
328 :
329 : int
330 : fd_bls12_381_g2_sub_syscall( uchar _r[ 96*2 ],
331 : uchar const _a[ 96*2 ],
332 : uchar const _b[ 96*2 ],
333 0 : int big_endian ) {
334 : /* points a, b are unchecked per SIMD-0388 */
335 0 : fd_bls12_381_g2aff_t a[1], b[1];
336 0 : if( FD_UNLIKELY( fd_bls12_381_g2_frombytes_unchecked( a, _a, big_endian )==NULL ) ) {
337 0 : return -1;
338 0 : }
339 0 : if( FD_UNLIKELY( fd_bls12_381_g2_frombytes_unchecked( b, _b, big_endian )==NULL ) ) {
340 0 : return -1;
341 0 : }
342 :
343 0 : fd_bls12_381_g2_t r[1], p[1];
344 0 : blst_p2_from_affine( p, a );
345 0 : blst_fp2_cneg( &b->y, &b->y, 1 ); /* -b, it works also with b=0 */
346 0 : blst_p2_add_or_double_affine( r, p, b );
347 :
348 0 : fd_bls12_381_g2_tobytes( _r, r, big_endian );
349 0 : return 0;
350 0 : }
351 :
352 : int
353 : fd_bls12_381_g2_mul_syscall( uchar _r[ 96*2 ],
354 : uchar const _n[ 32 ],
355 : uchar const _a[ 96*2 ],
356 0 : int big_endian ) {
357 : /* point a, scalar n are validated per SIMD-0388 */
358 0 : fd_bls12_381_g2aff_t a[1];
359 0 : fd_bls12_381_scalar_t n[1];
360 0 : if( FD_UNLIKELY( fd_bls12_381_g2_frombytes( a, _a, big_endian )==NULL ) ) {
361 0 : return -1;
362 0 : }
363 0 : if( FD_UNLIKELY( fd_bls12_381_scalar_frombytes( n, _n, big_endian )==NULL ) ) {
364 0 : return -1;
365 0 : }
366 :
367 0 : fd_bls12_381_g2_t r[1], p[1];
368 0 : blst_p2_from_affine( p, a );
369 : /* https://github.com/filecoin-project/blstrs/blob/v0.7.1/src/g2.rs#L545-L547 */
370 0 : blst_p2_mult( r, p, n->b, 255 );
371 :
372 0 : fd_bls12_381_g2_tobytes( _r, r, big_endian );
373 0 : return 0;
374 0 : }
375 :
376 : int
377 : fd_bls12_381_pairing_syscall( uchar _r[ 48*12 ],
378 : uchar const _a[], /* 96*n */
379 : uchar const _b[], /* 96*2*n */
380 : ulong const _n,
381 0 : int big_endian ) {
382 :
383 0 : if( FD_UNLIKELY( _n>FD_BLS12_381_PAIRING_BATCH_SZ ) ) {
384 0 : return -1;
385 0 : }
386 :
387 0 : fd_bls12_381_g1aff_t a[ FD_BLS12_381_PAIRING_BATCH_SZ ];
388 0 : fd_bls12_381_g2aff_t b[ FD_BLS12_381_PAIRING_BATCH_SZ ];
389 0 : fd_bls12_381_g1aff_t const * aptr[ FD_BLS12_381_PAIRING_BATCH_SZ ];
390 0 : fd_bls12_381_g2aff_t const * bptr[ FD_BLS12_381_PAIRING_BATCH_SZ ];
391 0 : for( ulong j=0; j<_n; j++ ) {
392 0 : if( FD_UNLIKELY( fd_bls12_381_g1_frombytes( &a[ j ], _a+96*j, big_endian )==NULL ) ) {
393 0 : return -1;
394 0 : }
395 0 : if( FD_UNLIKELY( fd_bls12_381_g2_frombytes( &b[ j ], _b+96*2*j, big_endian )==NULL ) ) {
396 0 : return -1;
397 0 : }
398 : /* blst wants an array of pointers (not necessarily a compact array) */
399 0 : aptr[ j ] = &a[ j ];
400 0 : bptr[ j ] = &b[ j ];
401 0 : }
402 :
403 0 : blst_fp12 r[1];
404 0 : memcpy( r, blst_fp12_one(), sizeof(blst_fp12) );
405 :
406 0 : if( FD_LIKELY ( _n>0 ) ) {
407 0 : blst_miller_loop_n( r, bptr, aptr, _n );
408 0 : blst_final_exp( r, r );
409 0 : }
410 :
411 0 : if( big_endian ) {
412 0 : for( ulong j=0; j<12; j++ ) {
413 0 : blst_bendian_from_fp( _r+48*(12-1-j), &r[ 0 ].fp6[ j/6 ].fp2[ (j/2)%3 ].fp[ j%2 ] );
414 0 : }
415 0 : } else {
416 0 : for( ulong j=0; j<12; j++ ) {
417 0 : blst_lendian_from_fp( _r+48*j, &r[ 0 ].fp6[ j/6 ].fp2[ (j/2)%3 ].fp[ j%2 ] );
418 0 : }
419 0 : }
420 :
421 0 : return 0;
422 0 : }
423 :
424 : /* Proof of possession */
425 :
426 : #define FD_BLS_SIG_DOMAIN_NUL "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"
427 0 : #define FD_BLS_SIG_DOMAIN_POP "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"
428 0 : #define FD_BLS_SIG_DOMAIN_SZ (43UL)
429 :
430 : /* fd_bls12_381_core_verify verifies a BLS signature in the mathematical
431 : sense, i.e. computes a pairing to check that the signature is correct.
432 : This is the core computation both for "real world" signatures and proofs
433 : of possession. In both cases, the difference between the math paper and
434 : the RFC implementation is an additional domain separator that's used
435 : in computing the hash to G2.
436 :
437 : See also:
438 : https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-06#name-coreverify
439 :
440 : We use a1, a2 for points in G1, b1, b2 for points in G2.
441 : We have to check that e( pk, H(msg) ) == e( g1, sig ), or equivalently
442 : e( pk, H(msg) ) * e( -g1, sig ) == 1.
443 :
444 : Replacing the variables we get:
445 : - a1 <- public_key, input needs to be decompressed in G1
446 : - b1 <- msg, input needs to be hashed to G2
447 : - a2 <- -g1, the const generator of G1, negated
448 : - b2 <- signature, input needs to be decompressed in G2
449 : */
450 : static inline int
451 : fd_bls12_381_core_verify( uchar const msg[], /* msg_sz */
452 : ulong msg_sz,
453 : uchar const signature[ 96 ],
454 : uchar const public_key[ 48 ],
455 0 : char const * domain ) {
456 0 : fd_bls12_381_g1aff_t a1[1]; /* a2 is const, we don't need a var */
457 0 : fd_bls12_381_g2aff_t b1[1], b2[1];
458 :
459 : /* decompress public_key into a1 and check that it's a valid point in G1 */
460 0 : if( FD_UNLIKELY( blst_p1_uncompress( a1, public_key )!=BLST_SUCCESS ) ) {
461 0 : return -1;
462 0 : }
463 0 : if( FD_UNLIKELY( !blst_p1_affine_in_g1( a1 ) ) ) {
464 0 : return -1;
465 0 : }
466 :
467 : /* hash msg into b1. the check that it's a valid point in G2 is implicit/guaranteed */
468 0 : fd_bls12_381_g2_t _b1[1];
469 0 : blst_hash_to_g2( _b1, msg, msg_sz, (uchar const *)domain, FD_BLS_SIG_DOMAIN_SZ, NULL, 0UL );
470 0 : blst_p2_to_affine( b1, _b1 );
471 :
472 : /* decompress signature into b2 and check that it's a valid point in G2 */
473 0 : if( FD_UNLIKELY( blst_p2_uncompress( b2, signature )!=BLST_SUCCESS ) ) {
474 0 : return -1;
475 0 : }
476 0 : if( FD_UNLIKELY( !blst_p2_affine_in_g2( b2 ) ) ) {
477 0 : return -1;
478 0 : }
479 :
480 : /* prepare pairing input: blst needs 2 arrays of pointers, and the result
481 : needs to be initialized to 1. */
482 0 : fd_bls12_381_g1aff_t const * aptr[ 2 ] = { a1, &BLS12_381_NEG_G1 };
483 0 : fd_bls12_381_g2aff_t const * bptr[ 2 ] = { b1, b2 };
484 0 : blst_fp12 r[1];
485 0 : memcpy( r, blst_fp12_one(), sizeof(blst_fp12) );
486 :
487 : /* compute the actual pairing and check that it's 1 */
488 0 : blst_miller_loop_n( r, bptr, aptr, 2 );
489 0 : if( FD_LIKELY( blst_fp12_finalverify( r, blst_fp12_one() ) ) ) {
490 0 : return 0; /* success */
491 0 : }
492 0 : return -1;
493 0 : }
494 :
495 : int
496 : fd_bls12_381_proof_of_possession_verify( uchar const msg[], /* msg_sz */
497 : ulong msg_sz,
498 : uchar const proof[ 96 ],
499 0 : uchar const public_key[ 48 ] ) {
500 : /* Agave supports the case of empty msg, where the public key is used
501 : instead (i.e. the plain RFC proof of possession). But that's not really
502 : used anywhere, and probably shouldn't be used for security reasons.
503 : In order to avoid accidental future changes, we prefer to not implement
504 : the case msg_sz==0 and instead explicitly throw an error.
505 : Since the public key must be part of the message, we check that
506 : msg_sz >= public key size, again to avoid accidental mistakes. */
507 0 : if( FD_UNLIKELY( msg_sz<48 ) ) {
508 0 : return -1;
509 0 : }
510 :
511 0 : return fd_bls12_381_core_verify( msg, msg_sz, proof, public_key, FD_BLS_SIG_DOMAIN_POP );
512 0 : }
|