Line data Source code
1 : #include "../fd_bls.h"
2 :
3 : /* --- secret keys --- */
4 :
5 : void
6 : fd_bls_sec_to_pub( fd_bls_sec_t const * sec,
7 0 : fd_bls_pub_t * pub ) {
8 0 : blst_sk_to_pk_in_g1( pub, sec );
9 0 : }
10 :
11 : void
12 : fd_bls_sec_derive( fd_bls_sec_t * sec,
13 : uchar const * ikm,
14 0 : ulong ikm_sz ) {
15 0 : FD_TEST( ikm_sz>=32UL );
16 0 : blst_keygen( sec, ikm, ikm_sz, NULL, 0UL );
17 0 : }
18 :
19 : void
20 : fd_bls_sec_sign( fd_bls_sec_t const * sec,
21 : uchar const * msg,
22 : ulong msg_sz,
23 0 : fd_bls_sig_t * sig ) {
24 0 : blst_p2 hash[1];
25 0 : blst_hash_to_g2( hash, msg, msg_sz, (uchar const *)FD_BLS_DST,
26 0 : FD_BLS_DST_SZ, NULL, 0UL );
27 0 : blst_sign_pk_in_g1( sig, hash, sec );
28 0 : }
29 :
30 :
31 : /* --- public keys (G1) --- */
32 :
33 : void
34 : fd_bls_pub_compress( fd_bls_pub_t const * pub,
35 0 : uchar buf[ static FD_BLS_PUB_COMPRESSED_SZ ] ) {
36 0 : blst_p1_affine a[1];
37 0 : blst_p1_to_affine( a, pub );
38 0 : blst_p1_affine_compress( buf, a );
39 0 : }
40 :
41 : static int
42 : fd_bls_pub_from_bytes( blst_p1_affine * out,
43 : uchar const * in,
44 0 : ulong in_sz ) {
45 0 : BLST_ERROR err;
46 0 : switch( in_sz ) {
47 0 : case FD_BLS_PUB_COMPRESSED_SZ: err = blst_p1_uncompress( out, in ); break;
48 0 : case FD_BLS_PUB_SZ:
49 0 : if( FD_UNLIKELY( in[0]&0xA0U ) ) return 0;
50 0 : err = blst_p1_deserialize( out, in );
51 0 : break;
52 0 : default: return 0;
53 0 : }
54 0 : return err==BLST_SUCCESS &&
55 0 : !blst_p1_affine_is_inf( out ) &&
56 0 : blst_p1_affine_in_g1( out );
57 0 : }
58 :
59 : int
60 : fd_bls_pub_de( fd_bls_pub_t * pub,
61 : uchar const * buf,
62 0 : ulong buf_sz ) {
63 0 : blst_p1_affine a[1];
64 0 : if( FD_UNLIKELY( !fd_bls_pub_from_bytes( a, buf, buf_sz ) ) ) return -1;
65 0 : blst_p1_from_affine( pub, a );
66 0 : return 0;
67 0 : }
68 :
69 : int
70 : fd_bls_pub_de_unchecked( fd_bls_pub_t * pub,
71 0 : uchar const buf[ static FD_BLS_PUB_SZ ] ) {
72 0 : blst_p1_affine a[1];
73 0 : if( FD_UNLIKELY( blst_p1_deserialize( a, buf )!=BLST_SUCCESS ) ) return -1;
74 0 : blst_p1_from_affine( pub, a );
75 0 : return 0;
76 0 : }
77 :
78 : void
79 : fd_bls_pub_add( fd_bls_pub_t * out,
80 : fd_bls_pub_t const * a,
81 0 : fd_bls_pub_t const * b ) {
82 0 : blst_p1_add_or_double( out, a, b );
83 0 : }
84 :
85 : void
86 : fd_bls_pub_neg( fd_bls_pub_t * out,
87 0 : fd_bls_pub_t const * a ) {
88 0 : *out = *a;
89 0 : blst_p1_cneg( out, 1 );
90 0 : }
91 :
92 : int
93 0 : fd_bls_pub_is_inf( fd_bls_pub_t const * pub ) {
94 0 : return !!blst_p1_is_inf( pub );
95 0 : }
96 :
97 : int
98 : fd_bls_pub_eq( fd_bls_pub_t const * a,
99 0 : fd_bls_pub_t const * b ) {
100 0 : return !!blst_p1_is_equal( a, b );
101 0 : }
102 :
103 : static void
104 : fd_bls_pub_add_many( fd_bls_pub_t * out,
105 : fd_bls_pub_t const * const * pubs,
106 0 : ulong pub_cnt ) {
107 0 : fd_bls_pub_t const * finite[ FD_BLS_SET_MAX ];
108 0 : ulong finite_cnt = 0UL;
109 0 : for( ulong i=0UL; i<pub_cnt; i++ )
110 0 : if( FD_LIKELY( !fd_bls_pub_is_inf( pubs[ i ] ) ) ) finite[ finite_cnt++ ] = pubs[ i ];
111 0 : if( FD_UNLIKELY( !finite_cnt ) ) { fd_memset( out, 0, sizeof(fd_bls_pub_t) ); return; }
112 :
113 0 : blst_p1_affine affine [ FD_BLS_SET_MAX ];
114 0 : blst_p1_affine const * affine_ptr[ FD_BLS_SET_MAX ];
115 0 : blst_p1s_to_affine( affine, finite, finite_cnt );
116 0 : for( ulong i=0UL; i<finite_cnt; i++ ) affine_ptr[ i ] = affine+i;
117 0 : blst_p1s_add( out, affine_ptr, finite_cnt );
118 0 : }
119 :
120 : void
121 : fd_bls_pub_sum( fd_bls_pub_t * out,
122 : fd_bls_pub_t const * pubs,
123 0 : fd_bls_set_t const * set ) {
124 0 : fd_bls_pub_t const * pub_ptr[ FD_BLS_SET_MAX ];
125 0 : ulong pub_cnt = 0UL;
126 0 : for( ulong rank = fd_bls_set_const_iter_init( set );
127 0 : !fd_bls_set_const_iter_done( rank );
128 0 : rank = fd_bls_set_const_iter_next( set, rank ) ) {
129 0 : pub_ptr[ pub_cnt++ ] = pubs+rank;
130 0 : }
131 0 : fd_bls_pub_add_many( out, pub_ptr, pub_cnt );
132 0 : }
133 :
134 : void
135 : fd_bls_pub_sum_cached( fd_bls_pub_t * out,
136 : fd_bls_pub_t const * pubs,
137 : ulong pub_cnt,
138 : fd_bls_set_t const * set,
139 0 : fd_bls_pub_t const * full ) {
140 0 : if( FD_LIKELY( !full || 2UL*fd_bls_set_cnt( set )<=pub_cnt ) ) {
141 0 : fd_bls_pub_sum( out, pubs, set );
142 0 : return;
143 0 : }
144 :
145 : /* For a dense signer set, normalize and sum only the complement, then
146 : subtract that from the cached epoch total. */
147 0 : fd_bls_pub_t const * missing[ FD_BLS_SET_MAX ];
148 0 : ulong missing_cnt = 0UL;
149 0 : for( ulong rank=0UL; rank<pub_cnt; rank++ )
150 0 : if( !fd_bls_set_test( set, rank ) ) missing[ missing_cnt++ ] = pubs+rank;
151 0 : fd_bls_pub_add_many( out, missing, missing_cnt );
152 0 : fd_bls_pub_neg( out, out );
153 0 : fd_bls_pub_add( out, full, out );
154 0 : }
155 :
156 : /* --- signatures (G2) --- */
157 :
158 : void
159 : fd_bls_sig_ser( fd_bls_sig_t const * sig,
160 0 : uchar buf[ static FD_BLS_SIG_SZ ] ) {
161 0 : blst_p2_affine a[1];
162 0 : blst_p2_to_affine( a, sig );
163 0 : blst_p2_affine_serialize( buf, a );
164 0 : }
165 :
166 : void
167 : fd_bls_sig_compress( fd_bls_sig_t const * sig,
168 0 : uchar buf[ static FD_BLS_SIG_COMPRESSED_SZ ] ) {
169 0 : blst_p2_affine a[1];
170 0 : blst_p2_to_affine( a, sig );
171 0 : blst_p2_affine_compress( buf, a );
172 0 : }
173 :
174 : int
175 : fd_bls_sig_de( fd_bls_sig_t * sig,
176 0 : uchar const buf[ static FD_BLS_SIG_SZ ] ) {
177 0 : blst_p2_affine a[1];
178 0 : if( FD_UNLIKELY( buf[0]&0xA0U ) ) return -1;
179 0 : if( FD_UNLIKELY( blst_p2_deserialize( a, buf )!=BLST_SUCCESS ) ) return -1;
180 0 : if( FD_UNLIKELY( !blst_p2_affine_in_g2( a ) ) ) return -1;
181 0 : blst_p2_from_affine( sig, a );
182 0 : return 0;
183 0 : }
184 :
185 : int
186 : fd_bls_sig_de_unchecked( fd_bls_sig_t * sig,
187 0 : uchar const buf[ static FD_BLS_SIG_SZ ] ) {
188 0 : blst_p2_affine a[1];
189 0 : if( FD_UNLIKELY( buf[0]&0xA0U ) ) return -1;
190 0 : if( FD_UNLIKELY( blst_p2_deserialize( a, buf )!=BLST_SUCCESS ) ) return -1;
191 0 : blst_p2_from_affine( sig, a );
192 0 : return 0;
193 0 : }
194 :
195 : int
196 : fd_bls_sig_de_compressed( fd_bls_sig_t * sig,
197 0 : uchar const buf[ static FD_BLS_SIG_COMPRESSED_SZ ] ) {
198 0 : blst_p2_affine a[1];
199 0 : if( FD_UNLIKELY( blst_p2_uncompress( a, buf )!=BLST_SUCCESS ) ) return -1;
200 0 : if( FD_UNLIKELY( !blst_p2_affine_in_g2( a ) ) ) return -1;
201 0 : blst_p2_from_affine( sig, a );
202 0 : return 0;
203 0 : }
204 :
205 : void
206 : fd_bls_sig_add( fd_bls_sig_t * out,
207 : fd_bls_sig_t const * a,
208 0 : fd_bls_sig_t const * b ) {
209 0 : blst_p2_add_or_double( out, a, b );
210 0 : }
211 :
212 : void
213 : fd_bls_sig_neg( fd_bls_sig_t * out,
214 0 : fd_bls_sig_t const * a ) {
215 0 : *out = *a;
216 0 : blst_p2_cneg( out, 1 );
217 0 : }
218 :
219 : int
220 0 : fd_bls_sig_validate( fd_bls_sig_t const * sig ) {
221 0 : return !!blst_p2_in_g2( sig );
222 0 : }
223 :
224 : int
225 0 : fd_bls_sig_is_inf( fd_bls_sig_t const * sig ) {
226 0 : return !!blst_p2_is_inf( sig );
227 0 : }
228 :
229 : int
230 : fd_bls_sig_eq( fd_bls_sig_t const * a,
231 0 : fd_bls_sig_t const * b ) {
232 0 : return !!blst_p2_is_equal( a, b );
233 0 : }
234 :
235 : static void
236 : fd_bls_sig_add_many( fd_bls_sig_t * out,
237 : fd_bls_sig_t const * const * sigs,
238 0 : ulong sig_cnt ) {
239 0 : fd_bls_sig_t const * finite[ FD_BLS_SET_MAX ];
240 0 : ulong finite_cnt = 0UL;
241 0 : for( ulong i=0UL; i<sig_cnt; i++ )
242 0 : if( FD_LIKELY( !fd_bls_sig_is_inf( sigs[ i ] ) ) ) finite[ finite_cnt++ ] = sigs[ i ];
243 0 : if( FD_UNLIKELY( !finite_cnt ) ) { fd_memset( out, 0, sizeof(fd_bls_sig_t) ); return; }
244 :
245 0 : blst_p2_affine affine [ FD_BLS_SET_MAX ];
246 0 : blst_p2_affine const * affine_ptr[ FD_BLS_SET_MAX ];
247 0 : blst_p2s_to_affine( affine, finite, finite_cnt );
248 0 : for( ulong i=0UL; i<finite_cnt; i++ ) affine_ptr[ i ] = affine+i;
249 0 : blst_p2s_add( out, affine_ptr, finite_cnt );
250 0 : }
251 :
252 : /* --- hash-to-curve --- */
253 :
254 : void
255 : fd_bls_hash_msg( uchar const * msg,
256 : ulong msg_sz,
257 0 : fd_bls_hash_t * hash ) {
258 0 : blst_p2 point[1];
259 0 : blst_hash_to_g2( point, msg, msg_sz, (uchar const *)FD_BLS_DST,
260 0 : FD_BLS_DST_SZ, NULL, 0UL );
261 0 : blst_p2_to_affine( hash, point );
262 0 : }
263 :
264 : int
265 : fd_bls_hash_eq( fd_bls_hash_t const * a,
266 0 : fd_bls_hash_t const * b ) {
267 0 : return !!blst_p2_affine_is_equal( a, b );
268 0 : }
269 :
270 : static int
271 0 : fd_bls_hash_is_inf( fd_bls_hash_t const * hash ) {
272 0 : return !!blst_p2_affine_is_inf( hash );
273 0 : }
274 :
275 : fd_bls_agg_t *
276 0 : fd_bls_agg_null( fd_bls_agg_t * agg ) {
277 0 : fd_memset( &agg->pub, 0, sizeof(fd_bls_pub_t) );
278 0 : fd_memset( &agg->sig, 0, sizeof(fd_bls_sig_t) );
279 0 : fd_bls_set_null( agg->set );
280 0 : return agg;
281 0 : }
282 :
283 : fd_bls_agg_t *
284 : fd_bls_agg_construct( fd_bls_agg_t * agg,
285 : fd_bls_pub_t const * pub,
286 : fd_bls_sig_t const * sig,
287 0 : fd_bls_set_t const * set ) {
288 0 : fd_bls_pub_t const * pub_ptr[ FD_BLS_SET_MAX ];
289 0 : fd_bls_sig_t const * sig_ptr[ FD_BLS_SET_MAX ];
290 0 : ulong cnt = 0UL;
291 0 : fd_bls_set_copy( agg->set, set );
292 0 : for( ulong rank = fd_bls_set_const_iter_init( set );
293 0 : !fd_bls_set_const_iter_done( rank );
294 0 : rank = fd_bls_set_const_iter_next( set, rank ) ) {
295 0 : pub_ptr[ cnt ] = pub+rank;
296 0 : sig_ptr[ cnt++ ] = sig+rank;
297 0 : }
298 0 : fd_bls_pub_add_many( &agg->pub, pub_ptr, cnt );
299 0 : fd_bls_sig_add_many( &agg->sig, sig_ptr, cnt );
300 0 : return agg;
301 0 : }
302 :
303 : static int
304 : fd_bls_verify_hash_pair( fd_bls_hash_t const * h,
305 : fd_bls_pub_t const * pub,
306 : fd_bls_hash_t const * h_fallback,
307 : fd_bls_pub_t const * pub_fallback,
308 0 : fd_bls_sig_t const * sig ) {
309 0 : ulong pair_cnt = 1UL + !!pub_fallback;
310 0 : if( FD_UNLIKELY( fd_bls_hash_is_inf( h ) || fd_bls_pub_is_inf( pub ) ||
311 0 : (pub_fallback && (fd_bls_hash_is_inf( h_fallback ) || fd_bls_pub_is_inf( pub_fallback ))) ||
312 0 : fd_bls_sig_is_inf( sig ) ) ) return 0;
313 :
314 0 : blst_p1_affine p[3];
315 0 : blst_p2_affine q[3];
316 0 : blst_p1_to_affine( p, pub ); q[0] = *h;
317 0 : if( pub_fallback ) { blst_p1_to_affine( p+1, pub_fallback ); q[1] = *h_fallback; }
318 0 : p[pair_cnt] = BLS12_381_NEG_G1;
319 0 : blst_p2_to_affine( q+pair_cnt, sig );
320 0 : blst_p1_affine const * pp[3] = { p, p+1, p+2 };
321 0 : blst_p2_affine const * qq[3] = { q, q+1, q+2 };
322 0 : blst_fp12 product[1];
323 0 : blst_miller_loop_n( product, qq, pp, pair_cnt+1UL );
324 0 : return !!blst_fp12_finalverify( product, blst_fp12_one() );
325 0 : }
326 :
327 : int
328 : fd_bls_agg_verify_hashed( fd_bls_hash_t const * h,
329 : fd_bls_pub_t const * pub,
330 0 : fd_bls_sig_t const * sig ) {
331 0 : return fd_bls_verify_hash_pair( h, pub, NULL, NULL, sig );
332 0 : }
333 :
334 : int
335 : fd_bls_agg_verify( uchar const * msg,
336 : ulong msg_sz,
337 : fd_bls_pub_t const * pub,
338 0 : fd_bls_sig_t const * sig ) {
339 0 : fd_bls_hash_t h[1];
340 0 : fd_bls_hash_msg( msg, msg_sz, h );
341 0 : return fd_bls_agg_verify_hashed( h, pub, sig );
342 0 : }
343 :
344 : int
345 : fd_bls_agg_verify_pair( uchar const * msg,
346 : ulong msg_sz,
347 : fd_bls_pub_t const * pub,
348 : uchar const * msg_fallback,
349 : ulong msg_fallback_sz,
350 : fd_bls_pub_t const * pub_fallback,
351 0 : fd_bls_sig_t const * sig ) {
352 0 : if( FD_UNLIKELY( !msg_fallback || !pub_fallback ) ) return 0;
353 0 : fd_bls_hash_t h[1], h_fallback[1];
354 0 : fd_bls_hash_msg( msg, msg_sz, h );
355 0 : fd_bls_hash_msg( msg_fallback, msg_fallback_sz, h_fallback );
356 0 : return fd_bls_verify_hash_pair( h, pub, h_fallback, pub_fallback, sig );
357 0 : }
358 :
359 : static void
360 : fd_bls_agg_gt( blst_fp12 * gt,
361 : fd_bls_hash_t const * h,
362 : fd_bls_pub_t const * pub,
363 0 : fd_bls_sig_t const * sig ) {
364 0 : blst_p1_affine p[2]; blst_p2_affine q[2];
365 0 : blst_p1_affine const * pp[2]; blst_p2_affine const * qq[2]; ulong cnt=0UL;
366 0 : if( FD_LIKELY( !fd_bls_pub_is_inf( pub ) ) ) {
367 0 : blst_p1_to_affine( p+cnt,pub );
368 0 : q[cnt]=*h; pp[cnt]=p+cnt; qq[cnt]=q+cnt;
369 0 : cnt++;
370 0 : }
371 0 : if( FD_LIKELY( !fd_bls_sig_is_inf( sig ) ) ) {
372 0 : p[cnt]=BLS12_381_NEG_G1;
373 0 : blst_p2_to_affine( q+cnt,sig );
374 0 : pp[cnt]=p+cnt; qq[cnt]=q+cnt;
375 0 : cnt++;
376 0 : }
377 0 : if( FD_UNLIKELY( !cnt ) ) {
378 0 : *gt = *blst_fp12_one();
379 0 : return;
380 0 : }
381 0 : blst_fp12 miller;
382 0 : blst_miller_loop_n( &miller, qq, pp, cnt );
383 0 : blst_final_exp( gt, &miller );
384 0 : }
385 :
386 : /* Compute the left GT value and derive right=parent/left. */
387 : static void
388 : fd_bls_agg_bisect_run( fd_bls_hash_t const * h,
389 : fd_bls_pub_t const * const pub[],
390 : fd_bls_sig_t const * const sig[],
391 : ushort const * rank,
392 : ulong lo,
393 : ulong hi,
394 : fd_bls_pub_t const * sum_pub,
395 : fd_bls_sig_t const * sum_sig,
396 : blst_fp12 const * gt,
397 0 : fd_bls_set_t * bad ) {
398 0 : if( FD_UNLIKELY( hi-lo==1UL ) ) { fd_bls_set_insert(bad,rank[lo]); return; }
399 0 : ulong mid=lo+(hi-lo)/2UL;
400 :
401 0 : fd_bls_pub_t lpub,hpub;
402 0 : fd_bls_sig_t lsig,hsig;
403 0 : fd_bls_pub_add_many( &lpub,pub+lo,mid-lo );
404 0 : fd_bls_sig_add_many( &lsig,sig+lo,mid-lo );
405 0 : fd_bls_pub_neg( &hpub,&lpub ); fd_bls_pub_add( &hpub,&hpub,sum_pub );
406 0 : fd_bls_sig_neg( &hsig,&lsig ); fd_bls_sig_add( &hsig,&hsig,sum_sig );
407 :
408 0 : blst_fp12 lgt,hgt;
409 0 : fd_bls_agg_gt( &lgt,h,&lpub,&lsig );
410 0 : hgt = lgt;
411 0 : blst_fp12_conjugate( &hgt );
412 0 : blst_fp12_mul( &hgt, &hgt, gt );
413 :
414 0 : int l_ok=blst_fp12_is_one(&lgt) && !fd_bls_pub_is_inf(&lpub) && !fd_bls_sig_is_inf(&lsig);
415 0 : int h_ok=blst_fp12_is_one(&hgt) && !fd_bls_pub_is_inf(&hpub) && !fd_bls_sig_is_inf(&hsig);
416 0 : if( FD_UNLIKELY(!l_ok) ) fd_bls_agg_bisect_run( h,pub,sig,rank,lo,mid,&lpub,&lsig,&lgt,bad );
417 0 : if( FD_UNLIKELY(!h_ok) ) fd_bls_agg_bisect_run( h,pub,sig,rank,mid,hi,&hpub,&hsig,&hgt,bad );
418 0 : }
419 :
420 : static fd_bls_set_t *
421 : fd_bls_agg_verify_bisect_hashed( fd_bls_agg_t const * agg,
422 : fd_bls_hash_t const * h,
423 : blst_fp12 const * gt,
424 : fd_bls_pub_t const * pub,
425 : fd_bls_sig_t const * sig,
426 0 : fd_bls_set_t * bad ) {
427 0 : fd_bls_set_null(bad);
428 0 : ulong cnt = fd_bls_set_cnt(agg->set);
429 0 : if( FD_UNLIKELY(!cnt) ) return bad;
430 0 : if( FD_LIKELY( blst_fp12_is_one(gt) && !fd_bls_pub_is_inf(&agg->pub) && !fd_bls_sig_is_inf(&agg->sig) ) ) return bad;
431 :
432 0 : fd_bls_pub_t const * pubs[FD_BLS_SET_MAX];
433 0 : fd_bls_sig_t const * sigs[FD_BLS_SET_MAX];
434 0 : ushort ranks[FD_BLS_SET_MAX];
435 0 : ulong n=0UL;
436 0 : for( ulong r=fd_bls_set_const_iter_init(agg->set); !fd_bls_set_const_iter_done(r); r=fd_bls_set_const_iter_next(agg->set,r) ) {
437 0 : pubs[n] = pub+r;
438 0 : sigs[n] = sig+r;
439 0 : ranks[n] = (ushort)r;
440 0 : n++;
441 0 : }
442 0 : fd_bls_agg_bisect_run( h,pubs,sigs,ranks,0UL,cnt,&agg->pub,&agg->sig,gt,bad );
443 0 : return bad;
444 0 : }
445 :
446 : int
447 : fd_bls_agg_verify_subtract( fd_bls_agg_t * agg,
448 : uchar const * msg,
449 : ulong msg_sz,
450 : fd_bls_pub_t const * pub,
451 : fd_bls_sig_t const * sig,
452 0 : fd_bls_set_t * bad ) {
453 0 : fd_bls_hash_t h; blst_fp12 gt;
454 0 : fd_bls_hash_msg( msg, msg_sz, &h );
455 0 : fd_bls_agg_gt( >, &h, &agg->pub, &agg->sig );
456 0 : if( FD_LIKELY( blst_fp12_is_one( > ) && !fd_bls_pub_is_inf( &agg->pub ) && !fd_bls_sig_is_inf( &agg->sig ) ) ) {
457 0 : fd_bls_set_null( bad );
458 0 : return 0;
459 0 : }
460 :
461 0 : fd_bls_agg_verify_bisect_hashed( agg, &h, >, pub, sig, bad );
462 0 : fd_bls_agg_t sub = { 0 };
463 0 : fd_bls_agg_construct( &sub, pub, sig, bad );
464 0 : fd_bls_pub_neg( &sub.pub, &sub.pub ); fd_bls_pub_add( &agg->pub, &agg->pub, &sub.pub );
465 0 : fd_bls_sig_neg( &sub.sig, &sub.sig ); fd_bls_sig_add( &agg->sig, &agg->sig, &sub.sig );
466 0 : fd_bls_set_subtract( agg->set, agg->set, bad );
467 0 : if( FD_UNLIKELY( fd_bls_pub_is_inf( &agg->pub ) ) ) return -1;
468 0 : return 0;
469 0 : }
|