Line data Source code
1 : #define _GNU_SOURCE
2 : #include "../tiles.h"
3 :
4 : #include <linux/futex.h>
5 : #include "generated/fd_sign_tile_seccomp.h"
6 :
7 : #include "../keyguard/fd_keyguard.h"
8 : #include "../keyguard/fd_keyguard_bls.h"
9 : #include "../keyguard/fd_keyload.h"
10 : #include "../keyguard/fd_keyswitch.h"
11 : #include "../../discof/admin/fd_adminctl.h"
12 : #include "../../ballet/base58/fd_base58.h"
13 : #include "../metrics/fd_metrics.h"
14 : #include "../../ballet/bls/fd_bls.h"
15 :
16 : #include "../../util/hist/fd_histf.h"
17 :
18 : #include <errno.h>
19 : #include <sys/mman.h>
20 :
21 0 : #define MAX_IN (32UL)
22 :
23 0 : #define FD_KEYGUARD_BLS_KEY_MAX (FD_KEYGUARD_AUTH_VOTERS_MAX+1UL) /* identity plus authorized voters */
24 :
25 : FD_STATIC_ASSERT( sizeof(fd_keyguard_bls_key_t)*FD_KEYGUARD_BLS_KEY_MAX<=4096UL, bls_keys_fit_protected_page );
26 :
27 : /* fd_sign_in_ctx_t is a context object for each in (producer) mcache
28 : connected to the sign tile. */
29 :
30 : struct fd_sign_out_ctx {
31 : fd_wksp_t * out_mem;
32 : ulong out_chunk0;
33 : ulong out_wmark;
34 : ulong out_chunk;
35 : };
36 : typedef struct fd_sign_out_ctx fd_sign_out_ctx_t;
37 :
38 : struct fd_sign_in_ctx {
39 : int role;
40 : fd_wksp_t * mem;
41 : ulong chunk0;
42 : ulong wmark;
43 : ulong mtu;
44 : };
45 : typedef struct fd_sign_in_ctx fd_sign_in_ctx_t;
46 :
47 : typedef struct {
48 : uchar _data[ FD_KEYGUARD_SIGN_REQ_MTU ];
49 :
50 : /* Pre-staged with the public key base58 encoded, followed by "-" in the first bytes */
51 : ulong public_key_base58_sz;
52 : uchar concat[ FD_BASE58_ENCODED_32_SZ+1UL+9UL ];
53 :
54 : fd_sign_in_ctx_t in[ MAX_IN ];
55 : fd_sign_out_ctx_t out[ MAX_IN ];
56 :
57 : fd_sha512_t sha512 [ 1 ];
58 :
59 : fd_keyswitch_t * keyswitch;
60 :
61 : fd_keyswitch_t * av_keyswitch; /* authorized voters */
62 :
63 : uchar * public_key;
64 : uchar * private_key;
65 :
66 : uchar const * failover_junk_key; /* the junk key that failover installs after it hands away its staked identity, NULL if none */
67 : uchar * failover_key; /* the staked identity failover hands off */
68 :
69 : fd_keyguard_bls_key_t * bls_keys;
70 :
71 : uchar tip_payment_program [32];
72 : uchar tip_distribution_program[32];
73 :
74 : ulong authorized_voters_cnt;
75 : uchar authorized_voter_pubkeys[ FD_KEYGUARD_AUTH_VOTERS_MAX ][ 32UL ];
76 : uchar (* authorized_voter_private_keys)[ 32UL ];
77 :
78 : fd_histf_t sign_duration[1];
79 : } fd_sign_ctx_t;
80 :
81 : FD_FN_CONST static inline ulong
82 0 : scratch_align( void ) {
83 0 : return alignof( fd_sign_ctx_t );
84 0 : }
85 :
86 : FD_FN_PURE static inline ulong
87 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
88 0 : (void)tile;
89 0 : ulong l = FD_LAYOUT_INIT;
90 0 : l = FD_LAYOUT_APPEND( l, alignof( fd_sign_ctx_t ), sizeof( fd_sign_ctx_t ) );
91 0 : return FD_LAYOUT_FINI( l, scratch_align() );
92 0 : }
93 :
94 : static void FD_FN_SENSITIVE
95 0 : derive_fields( fd_sign_ctx_t * ctx ) {
96 0 : uchar check_public_key[ 32 ];
97 0 : fd_ed25519_public_from_private( check_public_key, ctx->private_key, ctx->sha512 );
98 0 : if( FD_UNLIKELY( memcmp( check_public_key, ctx->public_key, 32UL ) ) )
99 0 : FD_LOG_EMERG(( "The public key in the identity key file does not match the public key derived from the private key. "
100 0 : "Firedancer will not use the key pair to sign as it might leak the private key." ));
101 :
102 0 : fd_base58_encode_32( ctx->public_key, &ctx->public_key_base58_sz, (char *)ctx->concat );
103 0 : ctx->concat[ ctx->public_key_base58_sz ] = '-';
104 :
105 0 : fd_keyguard_bls_key_derive( &ctx->bls_keys[ 0 ], ctx->public_key, ctx->private_key, ctx->sha512 );
106 0 : }
107 :
108 : /* A failover switch passes only a public key, we copy the junk key or
109 : the failover identity into the identity key. */
110 :
111 : static void FD_FN_SENSITIVE
112 0 : failover_select( fd_sign_ctx_t * ctx ) {
113 0 : uchar const * selected = NULL;
114 0 : if( fd_memeq( ctx->keyswitch->bytes, ctx->failover_junk_key+32UL, 32UL ) ) selected = ctx->failover_junk_key;
115 0 : else if( fd_memeq( ctx->keyswitch->bytes, ctx->failover_key+32UL, 32UL ) ) selected = ctx->failover_key;
116 0 : if( FD_UNLIKELY( !selected ) ) FD_LOG_ERR(( "identity switch selects a key that is neither the junk key nor the failover identity" ));
117 0 : fd_memzero_explicit( ctx->keyswitch->bytes, 64UL );
118 0 : fd_memcpy( ctx->private_key, selected, 32UL );
119 0 : fd_memcpy( ctx->public_key, selected+32UL, 32UL );
120 0 : derive_fields( ctx );
121 0 : fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
122 0 : }
123 :
124 : static int
125 : failover_identity_allowed( fd_sign_ctx_t const * ctx,
126 0 : uchar const * pubkey ) {
127 0 : for( ulong i=0UL; i<ctx->authorized_voters_cnt; i++ ) {
128 0 : if( fd_memeq( pubkey, ctx->authorized_voter_pubkeys[ i ], 32UL ) ) return 0;
129 0 : }
130 0 : return 1;
131 0 : }
132 :
133 : /* Handles the failover keyswitch params (prepare, check, pubkey select)
134 : and returns 1 if fully handled. Returns 0 for a normal keypair switch. */
135 :
136 : static int FD_FN_SENSITIVE
137 0 : failover_keyswitch( fd_sign_ctx_t * ctx ) {
138 0 : if( FD_LIKELY( fd_keyswitch_state_query( ctx->keyswitch )!=FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) return 1;
139 0 : ulong param = fd_keyswitch_param_query( ctx->keyswitch );
140 0 : if( FD_UNLIKELY( param==FD_KEYSWITCH_PARAM_IDENTITY_PREPARE ) ) {
141 0 : int allowed = failover_identity_allowed( ctx, ctx->keyswitch->bytes+32UL );
142 0 : if( FD_LIKELY( allowed ) ) fd_memcpy( ctx->failover_key, ctx->keyswitch->bytes, 64UL );
143 0 : fd_memzero_explicit( ctx->keyswitch->bytes, 64UL );
144 0 : ctx->keyswitch->result = allowed ? FD_ADMINCTL_RESULT_SUCCESS : FD_SET_IDENTITY_RESULT_AUTHORIZED_VOTER;
145 0 : fd_keyswitch_state( ctx->keyswitch, allowed ? FD_KEYSWITCH_STATE_COMPLETED : FD_KEYSWITCH_STATE_FAILED );
146 0 : return 1;
147 0 : }
148 0 : if( FD_UNLIKELY( param==FD_KEYSWITCH_PARAM_IDENTITY_CHECK ) ) {
149 0 : int allowed = failover_identity_allowed( ctx, ctx->keyswitch->bytes );
150 0 : fd_memzero_explicit( ctx->keyswitch->bytes, 64UL );
151 0 : ctx->keyswitch->result = allowed ? FD_ADMINCTL_RESULT_SUCCESS : FD_SET_IDENTITY_RESULT_AUTHORIZED_VOTER;
152 0 : fd_keyswitch_state( ctx->keyswitch, allowed ? FD_KEYSWITCH_STATE_COMPLETED : FD_KEYSWITCH_STATE_FAILED );
153 0 : return 1;
154 0 : }
155 0 : if( FD_UNLIKELY( param==FD_KEYSWITCH_PARAM_IDENTITY_PUBKEY ) ) {
156 0 : failover_select( ctx );
157 0 : return 1;
158 0 : }
159 0 : FD_TEST( param==FD_KEYSWITCH_PARAM_IDENTITY_KEYPAIR );
160 0 : fd_memcpy( ctx->failover_key, ctx->keyswitch->bytes, 64UL );
161 0 : return 0;
162 0 : }
163 :
164 : static void FD_FN_SENSITIVE
165 0 : during_housekeeping_sensitive( fd_sign_ctx_t * ctx ) {
166 0 : int identity_handled = ctx->failover_junk_key && failover_keyswitch( ctx );
167 :
168 0 : if( FD_UNLIKELY( !identity_handled && fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
169 0 : memcpy( ctx->private_key, ctx->keyswitch->bytes, 32UL );
170 0 : fd_memzero_explicit( ctx->keyswitch->bytes, 32UL );
171 0 : FD_COMPILER_MFENCE();
172 0 : memcpy( ctx->public_key, ctx->keyswitch->bytes+32UL, 32UL );
173 :
174 0 : derive_fields( ctx );
175 0 : fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
176 0 : }
177 :
178 : /* firedancer only */
179 :
180 0 : if( FD_UNLIKELY( ctx->av_keyswitch && fd_keyswitch_state_query( ctx->av_keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
181 0 : ulong param = fd_keyswitch_param_query( ctx->av_keyswitch );
182 0 : if( FD_LIKELY( param==FD_KEYSWITCH_PARAM_AV_ADD ) ) {
183 0 : if( FD_UNLIKELY( ctx->authorized_voters_cnt==FD_KEYGUARD_AUTH_VOTERS_MAX ) ) {
184 0 : FD_LOG_WARNING(( "keyswitch failed: maximum number of authorized voters reached" ));
185 0 : fd_memzero_explicit( ctx->av_keyswitch->bytes, 64UL );
186 0 : ctx->av_keyswitch->result = FD_ADD_AUTHORIZED_VOTER_RESULT_MAX_AUTH_VOTERS;
187 0 : fd_keyswitch_state( ctx->av_keyswitch, FD_KEYSWITCH_STATE_FAILED );
188 0 : return;
189 0 : }
190 0 : for( ulong i=0UL; i<ctx->authorized_voters_cnt; i++ ) {
191 0 : if( FD_UNLIKELY( !memcmp( ctx->authorized_voter_pubkeys[ i ], ctx->av_keyswitch->bytes+32UL, 32UL ) ) ) {
192 0 : FD_BASE58_ENCODE_32_BYTES( ctx->authorized_voter_pubkeys[ i ], pubkey_b58 );
193 0 : FD_LOG_WARNING(( "keyswitch failed: authorized voter key duplicate (%s)", pubkey_b58 ));
194 0 : fd_memzero_explicit( ctx->av_keyswitch->bytes, 64UL );
195 0 : ctx->av_keyswitch->result = FD_ADD_AUTHORIZED_VOTER_RESULT_DUPLICATE_AUTH_VOTER;
196 0 : fd_keyswitch_state( ctx->av_keyswitch, FD_KEYSWITCH_STATE_FAILED );
197 0 : return;
198 0 : }
199 0 : }
200 :
201 0 : memcpy( ctx->authorized_voter_private_keys[ ctx->authorized_voters_cnt ], ctx->av_keyswitch->bytes, 32UL );
202 0 : fd_memzero_explicit( ctx->av_keyswitch->bytes, 32UL );
203 0 : FD_COMPILER_MFENCE();
204 0 : memcpy( ctx->authorized_voter_pubkeys[ ctx->authorized_voters_cnt ], ctx->av_keyswitch->bytes + 32UL, 32UL );
205 0 : fd_keyguard_bls_key_derive( &ctx->bls_keys[ ctx->authorized_voters_cnt+1UL ],
206 0 : ctx->authorized_voter_pubkeys[ ctx->authorized_voters_cnt ],
207 0 : ctx->authorized_voter_private_keys[ ctx->authorized_voters_cnt ],
208 0 : ctx->sha512 );
209 0 : ctx->authorized_voters_cnt++;
210 0 : } else if( FD_LIKELY( param==FD_KEYSWITCH_PARAM_AV_CLEAR ) ) {
211 0 : fd_memzero_explicit( ctx->authorized_voter_private_keys, 32UL*FD_KEYGUARD_AUTH_VOTERS_MAX );
212 0 : fd_memzero_explicit( ctx->authorized_voter_pubkeys, sizeof( ctx->authorized_voter_pubkeys ) );
213 0 : fd_memzero_explicit( ctx->bls_keys+1, sizeof(fd_keyguard_bls_key_t)*(FD_KEYGUARD_BLS_KEY_MAX-1UL) );
214 0 : ctx->authorized_voters_cnt = 0UL;
215 0 : } else {
216 0 : FD_LOG_CRIT(( "keyswitch: unexpected authorized voter operation %lu", param ));
217 0 : }
218 0 : fd_keyswitch_state( ctx->av_keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
219 0 : }
220 0 : }
221 :
222 : static inline void
223 0 : during_housekeeping( fd_sign_ctx_t * ctx ) {
224 0 : during_housekeeping_sensitive( ctx );
225 0 : }
226 :
227 : static inline void
228 0 : metrics_write( fd_sign_ctx_t * ctx ) {
229 0 : FD_MHIST_COPY( SIGN, SIGN_DURATION_NANOS, ctx->sign_duration );
230 0 : }
231 :
232 : /* during_frag is called between pairs for sequence number checks, as
233 : we are reading incoming frags. We don't actually need to copy the
234 : fragment here, see fd_dedup.c for why we do this.*/
235 :
236 : static void FD_FN_SENSITIVE
237 : during_frag_sensitive( void * _ctx,
238 : ulong in_idx,
239 : ulong seq,
240 : ulong sig,
241 : ulong chunk,
242 0 : ulong sz ) {
243 0 : (void)seq;
244 0 : (void)sig;
245 :
246 0 : fd_sign_ctx_t * ctx = (fd_sign_ctx_t *)_ctx;
247 0 : FD_TEST( in_idx<MAX_IN );
248 :
249 0 : int role = ctx->in[ in_idx ].role;
250 0 : ulong mtu = ctx->in[ in_idx ].mtu;
251 :
252 0 : if( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>mtu ) {
253 0 : FD_LOG_EMERG(( "oversz or out of bounds signing request (role=%d chunk=%lu sz=%lu mtu=%lu, chunk0=%lu, wmark=%lu)", role, chunk, sz, mtu, ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
254 0 : }
255 :
256 0 : void * src = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
257 0 : fd_memcpy( ctx->_data, src, sz );
258 0 : }
259 :
260 :
261 : static void
262 : during_frag( void * _ctx,
263 : ulong in_idx,
264 : ulong seq,
265 : ulong sig,
266 : ulong chunk,
267 : ulong sz,
268 0 : ulong ctl FD_PARAM_UNUSED ) {
269 0 : during_frag_sensitive( _ctx, in_idx, seq, sig, chunk, sz );
270 0 : }
271 :
272 : /* The failover identity signs the admission proofs and the TLS
273 : handshakes of the failover channel. */
274 :
275 : static void FD_FN_SENSITIVE
276 : failover_sign( fd_sign_ctx_t * ctx,
277 : uchar * dst,
278 0 : ulong sz ) {
279 0 : fd_ed25519_sign( dst, ctx->_data, sz, ctx->failover_key+32UL, ctx->failover_key, ctx->sha512 );
280 0 : }
281 :
282 : static void FD_FN_SENSITIVE
283 : after_frag_sensitive( void * _ctx,
284 : ulong in_idx,
285 : ulong seq,
286 : ulong sig,
287 : ulong sz,
288 : ulong tsorig,
289 : ulong tspub,
290 0 : fd_stem_context_t * stem ) {
291 0 : (void)seq;
292 0 : (void)tspub;
293 :
294 0 : fd_sign_ctx_t * ctx = (fd_sign_ctx_t *)_ctx;
295 :
296 : /* The lower 32 bits are used to specify the sign type.
297 :
298 : If the frag is coming from the repair tile, then the upper 32 bits
299 : contain the repair tile nonce to identify the request.
300 :
301 : If the frag is coming from the send tile, then the upper 32 bits
302 : contain the index of the authorized voter that needs to sign the
303 : vote transaction. The least significant bit of the upper 32 is
304 : used to indicate if a second signature is needed. The next 4 least
305 : significant bits are used to encode the index of the authorized
306 : voter that a signature is needed from.
307 :
308 : BLS requests from the votor tile encode the upper 32 bits the same
309 : way, except that a set least significant bit selects the authorized
310 : voter's key in place of the identity's instead of adding a second
311 : signature. */
312 0 : int sign_type = (int)(uint)(sig);
313 0 : int needs_second_sign = ctx->in[ in_idx ].role==FD_KEYGUARD_ROLE_TXSEND && ((sig>>32) & 1UL);
314 :
315 0 : FD_TEST( in_idx<MAX_IN );
316 :
317 0 : int role = ctx->in[ in_idx ].role;
318 :
319 0 : fd_keyguard_authority_t authority = {0};
320 0 : memcpy( authority.identity_pubkey, ctx->public_key, 32UL );
321 0 : memcpy( authority.tip_payment_program, ctx->tip_payment_program, 32UL );
322 0 : memcpy( authority.tip_distribution_program, ctx->tip_distribution_program, 32UL );
323 :
324 0 : if( FD_UNLIKELY( !fd_keyguard_payload_authorize( &authority, ctx->_data, sz, role, sign_type ) ) ) {
325 0 : FD_LOG_EMERG(( "fd_keyguard_payload_authorize failed (role=%d sign_type=%d)", role, sign_type ));
326 0 : }
327 :
328 0 : long sign_duration = -fd_tickcount();
329 :
330 0 : uchar * dst = fd_chunk_to_laddr( ctx->out[ in_idx ].out_mem, ctx->out[ in_idx ].out_chunk );
331 0 : ulong out_sz = 64UL;
332 :
333 0 : switch( sign_type ) {
334 0 : case FD_KEYGUARD_SIGN_TYPE_ED25519: {
335 0 : if( FD_UNLIKELY( role==FD_KEYGUARD_ROLE_FAILOV ) ) { failover_sign( ctx, dst, sz ); break; }
336 0 : fd_ed25519_sign( dst, ctx->_data, sz, ctx->public_key, ctx->private_key, ctx->sha512 );
337 0 : if( needs_second_sign ) {
338 0 : ulong authority_idx = (sig >> 33) & 0xFUL;
339 0 : if( FD_UNLIKELY( authority_idx>=ctx->authorized_voters_cnt ) ) {
340 0 : FD_LOG_CRIT(( "invalid sign request from in_idx=%lu: authority_idx=%lu out of range (authorized_voters_cnt=%lu)", in_idx, authority_idx, ctx->authorized_voters_cnt ));
341 0 : }
342 0 : fd_ed25519_sign( dst+64UL, ctx->_data, sz, ctx->authorized_voter_pubkeys[ authority_idx ], ctx->authorized_voter_private_keys[ authority_idx ], ctx->sha512 );
343 0 : out_sz = 128UL;
344 0 : }
345 0 : break;
346 0 : }
347 0 : case FD_KEYGUARD_SIGN_TYPE_SHA256_ED25519: {
348 0 : uchar hash[ 32 ];
349 0 : fd_sha256_hash( ctx->_data, sz, hash );
350 0 : fd_ed25519_sign( dst, hash, 32UL, ctx->public_key, ctx->private_key, ctx->sha512 );
351 0 : break;
352 0 : }
353 0 : case FD_KEYGUARD_SIGN_TYPE_PUBKEY_CONCAT_ED25519: {
354 0 : memcpy( ctx->concat+ctx->public_key_base58_sz+1UL, ctx->_data, 9UL );
355 0 : fd_ed25519_sign( dst, ctx->concat, ctx->public_key_base58_sz+1UL+9UL, ctx->public_key, ctx->private_key, ctx->sha512 );
356 0 : break;
357 0 : }
358 0 : case FD_KEYGUARD_SIGN_TYPE_BLS: {
359 0 : ulong key_idx = 0UL;
360 0 : if( (sig>>32) & 1UL ) {
361 0 : ulong authority_idx = (sig >> 33) & 0xFUL;
362 0 : if( FD_UNLIKELY( authority_idx>=ctx->authorized_voters_cnt ) ) {
363 0 : FD_LOG_CRIT(( "invalid sign request from in_idx=%lu: authority_idx=%lu out of range (authorized_voters_cnt=%lu)", in_idx, authority_idx, ctx->authorized_voters_cnt ));
364 0 : }
365 0 : key_idx = authority_idx+1UL;
366 0 : }
367 :
368 0 : fd_bls_sig_t bls_sig[1];
369 0 : fd_bls_sec_sign( &ctx->bls_keys[ key_idx ].secret_key, ctx->_data, sz, bls_sig );
370 0 : fd_bls_sig_ser( bls_sig, dst );
371 0 : out_sz = FD_BLS_SIG_SZ;
372 0 : break;
373 0 : }
374 0 : case FD_KEYGUARD_SIGN_TYPE_BLS_PUBKEY: {
375 0 : if( FD_UNLIKELY( sig!=FD_KEYGUARD_SIGN_TYPE_BLS_PUBKEY ) ) {
376 0 : FD_LOG_EMERG(( "invalid BLS public key request (sig=%lu)", sig ));
377 0 : }
378 0 : ulong authority_idx = FD_LOAD( ulong, ctx->_data );
379 0 : if( FD_UNLIKELY( authority_idx!=ULONG_MAX && authority_idx>=ctx->authorized_voters_cnt ) ) {
380 0 : FD_LOG_CRIT(( "invalid BLS public key request from in_idx=%lu: authority_idx=%lu out of range (authorized_voters_cnt=%lu)", in_idx, authority_idx, ctx->authorized_voters_cnt ));
381 0 : }
382 0 : ulong key_idx = authority_idx==ULONG_MAX ? 0UL : authority_idx+1UL;
383 0 : memcpy( dst, ctx->bls_keys[ key_idx ].public_key, FD_BLS_PUB_COMPRESSED_SZ );
384 0 : out_sz = FD_BLS_PUB_COMPRESSED_SZ;
385 0 : break;
386 0 : }
387 0 : default:
388 0 : FD_LOG_EMERG(( "invalid sign type: %d", sign_type ));
389 0 : }
390 :
391 0 : if( FD_LIKELY( sign_type!=FD_KEYGUARD_SIGN_TYPE_BLS_PUBKEY ) ) {
392 0 : sign_duration += fd_tickcount();
393 0 : fd_histf_sample( ctx->sign_duration, (ulong)sign_duration );
394 0 : }
395 :
396 0 : fd_stem_publish( stem, in_idx, sig, ctx->out[ in_idx ].out_chunk, out_sz, 0UL, tsorig, 0UL );
397 0 : ctx->out[ in_idx ].out_chunk = fd_dcache_compact_next( ctx->out[ in_idx ].out_chunk, out_sz, ctx->out[ in_idx ].out_chunk0, ctx->out[ in_idx ].out_wmark );
398 0 : }
399 :
400 : static void
401 : after_frag( void * _ctx,
402 : ulong in_idx,
403 : ulong seq,
404 : ulong sig,
405 : ulong sz,
406 : ulong tsorig,
407 : ulong tspub,
408 0 : fd_stem_context_t * stem ) {
409 0 : after_frag_sensitive( _ctx, in_idx, seq, sig, sz, tsorig, tspub, stem );
410 0 : }
411 :
412 : static void FD_FN_SENSITIVE
413 : load_keys( fd_sign_ctx_t * ctx,
414 : fd_topo_t const * topo,
415 0 : fd_topo_tile_t const * tile ) {
416 0 : ctx->failover_junk_key = NULL;
417 0 : ctx->failover_key = NULL;
418 0 : int standby = !strcmp( tile->sign.identity_key_path, FD_KEYLOAD_FAILOVER_JUNK );
419 0 : if( FD_UNLIKELY( tile->sign.failover_enabled ) ) {
420 0 : ulong failov_idx = fd_topo_find_tile( topo, "failov", 0UL );
421 0 : FD_TEST( failov_idx!=ULONG_MAX );
422 0 : ctx->failover_junk_key = fd_keyload_load( FD_KEYLOAD_FAILOVER_JUNK, /* pubkey only: */ 0 );
423 0 : ctx->failover_key = fd_keyload_mprotect_wr( fd_keyload_load( topo->tiles[ failov_idx ].failov.target_key_path, /* pubkey only: */ 0 ), /* public_key_only: */ 0 );
424 0 : if( FD_UNLIKELY( fd_memeq( ctx->failover_junk_key+32UL, ctx->failover_key+32UL, 32UL ) ) ) {
425 0 : FD_LOG_ERR(( "the failover junk key must differ from the [paths.identity_key] key" ));
426 0 : }
427 :
428 0 : fd_sha512_t sha512[ 1 ];
429 0 : FD_TEST( fd_sha512_join( fd_sha512_new( sha512 ) ) );
430 0 : uchar check_public_key[ 32 ];
431 0 : fd_ed25519_public_from_private( check_public_key, ctx->failover_key, sha512 );
432 0 : fd_memzero_explicit( sha512, sizeof(sha512) );
433 0 : if( FD_UNLIKELY( !fd_memeq( check_public_key, ctx->failover_key+32UL, 32UL ) ) ) {
434 0 : FD_LOG_EMERG(( "The public key in the failover identity key file does not match the public key derived from the private key. "
435 0 : "Firedancer will not use the key pair to sign as it might leak the private key." ));
436 0 : }
437 0 : } else {
438 0 : FD_TEST( !standby );
439 0 : }
440 :
441 0 : uchar * identity_key;
442 0 : if( FD_UNLIKELY( standby ) ) {
443 0 : identity_key = fd_keyload_alloc_protected_pages( 1UL, 2UL );
444 0 : fd_memcpy( identity_key, ctx->failover_junk_key, 64UL );
445 0 : } else {
446 0 : identity_key = fd_keyload_mprotect_wr( fd_keyload_load( tile->sign.identity_key_path, /* pubkey only: */ 0 ), /* public_key_only: */ 0 );
447 0 : }
448 0 : ctx->private_key = identity_key;
449 0 : ctx->public_key = identity_key + 32UL;
450 :
451 0 : ctx->bls_keys = fd_keyload_alloc_protected_pages( 1UL, 2UL );
452 0 : ctx->authorized_voter_private_keys = fd_keyload_alloc_protected_pages( 1UL, 2UL );
453 :
454 0 : FD_TEST( tile->sign.authorized_voter_paths_cnt<FD_KEYGUARD_BLS_KEY_MAX );
455 0 : ctx->authorized_voters_cnt = tile->sign.authorized_voter_paths_cnt;
456 0 : for( ulong i=0UL; i<tile->sign.authorized_voter_paths_cnt; i++ ) {
457 0 : uchar const * authorized_voter_key = fd_keyload_load( tile->sign.authorized_voter_paths[ i ], /* pubkey only: */ 0 );
458 0 : memcpy( ctx->authorized_voter_private_keys[ i ], authorized_voter_key, 32UL );
459 0 : memcpy( ctx->authorized_voter_pubkeys[ i ], authorized_voter_key + 32UL, 32UL );
460 0 : fd_keyload_unload( authorized_voter_key, /* public_key_only: */ 0 );
461 0 : }
462 0 : }
463 :
464 : static void FD_FN_SENSITIVE
465 : privileged_init_sensitive( fd_topo_t const * topo,
466 0 : fd_topo_tile_t const * tile ) {
467 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
468 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
469 0 : fd_sign_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_sign_ctx_t ), sizeof( fd_sign_ctx_t ) );
470 :
471 0 : load_keys( ctx, topo, tile );
472 :
473 : /* The stack can be taken over and reorganized by under AddressSanitizer,
474 : which causes this code to fail. */
475 : #if FD_HAS_ASAN
476 : FD_LOG_WARNING(( "!!! SECURITY WARNING !!! YOU ARE RUNNING THE SIGNING TILE "
477 : "WITH ADDRESS SANITIZER ENABLED. THIS CAN LEAK SENSITIVE "
478 : "DATA INCLUDING YOUR PRIVATE KEYS INTO CORE DUMPS IF THIS "
479 : "PROCESS ABORTS. IT IS HIGHLY ADVISED TO NOT TO RUN IN THIS "
480 : "MODE IN PRODUCTION!" ));
481 : #else
482 : /* Prevent the stack from showing up in core dumps just in case the
483 : private key somehow ends up in there. */
484 0 : FD_TEST( fd_tile_stack0() );
485 0 : FD_TEST( fd_tile_stack_sz() );
486 0 : if( FD_UNLIKELY( madvise( (void*)fd_tile_stack0(), fd_tile_stack_sz(), MADV_DONTDUMP ) ) )
487 0 : FD_LOG_ERR(( "madvise failed (%i-%s)", errno, fd_io_strerror( errno ) ));
488 0 : #endif
489 0 : }
490 :
491 : static void
492 : privileged_init( fd_topo_t const * topo,
493 0 : fd_topo_tile_t const * tile ) {
494 0 : privileged_init_sensitive( topo, tile );
495 0 : }
496 :
497 : static void FD_FN_SENSITIVE
498 : unprivileged_init_sensitive( fd_topo_t const * topo,
499 0 : fd_topo_tile_t const * tile ) {
500 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
501 :
502 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
503 0 : fd_sign_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_sign_ctx_t ), sizeof( fd_sign_ctx_t ) );
504 0 : FD_TEST( fd_sha512_join( fd_sha512_new( ctx->sha512 ) ) );
505 :
506 0 : FD_TEST( tile->in_cnt<=MAX_IN );
507 0 : FD_TEST( tile->in_cnt==tile->out_cnt );
508 :
509 0 : fd_histf_join( fd_metrics_histf_new_ns( ctx->sign_duration, FD_MHIST_EDGES_NS( SIGN, SIGN_DURATION_NANOS ) ) );
510 :
511 0 : ctx->keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) );
512 0 : derive_fields( ctx );
513 0 : for( ulong i=0UL; i<ctx->authorized_voters_cnt; i++ ) {
514 0 : fd_keyguard_bls_key_derive( &ctx->bls_keys[ i+1UL ],
515 0 : ctx->authorized_voter_pubkeys[ i ],
516 0 : ctx->authorized_voter_private_keys[ i ],
517 0 : ctx->sha512 );
518 0 : }
519 :
520 0 : if( FD_LIKELY( tile->av_keyswitch_obj_id!=ULONG_MAX ) ) {
521 0 : ctx->av_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->av_keyswitch_obj_id ) );
522 0 : FD_TEST( ctx->av_keyswitch );
523 0 : } else {
524 0 : ctx->av_keyswitch = NULL;
525 0 : }
526 :
527 0 : memcpy( ctx->tip_payment_program, tile->sign.bundle.tip_payment_program_addr, 32UL );
528 0 : memcpy( ctx->tip_distribution_program, tile->sign.bundle.tip_distribution_program_addr, 32UL );
529 :
530 0 : for( ulong i=0UL; i<MAX_IN; i++ ) ctx->in[ i ].role = -1;
531 :
532 0 : for( ulong i=0UL; i<tile->in_cnt; i++ ) {
533 0 : fd_topo_link_t const * in_link = &topo->links[ tile->in_link_id[ i ] ];
534 0 : fd_topo_link_t const * out_link = &topo->links[ tile->out_link_id[ i ] ];
535 :
536 0 : if( in_link->mtu > FD_KEYGUARD_SIGN_REQ_MTU ) FD_LOG_CRIT(( "oversz link[%lu].mtu=%lu", i, in_link->mtu ));
537 0 : ctx->in[ i ].mem = fd_wksp_containing( in_link->dcache );
538 0 : ctx->in[ i ].mtu = in_link->mtu;
539 0 : ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, in_link->dcache );
540 0 : ctx->in[ i ].wmark = fd_dcache_compact_wmark( ctx->in[ i ].mem, in_link->dcache, in_link->mtu );
541 :
542 0 : ctx->out[ i ].out_mem = fd_wksp_containing( out_link->dcache );
543 0 : ctx->out[ i ].out_chunk0 = fd_dcache_compact_chunk0( ctx->out[ i ].out_mem, out_link->dcache );
544 0 : ctx->out[ i ].out_wmark = fd_dcache_compact_wmark( ctx->out[ i ].out_mem, out_link->dcache, out_link->mtu );
545 0 : ctx->out[ i ].out_chunk = ctx->out[ i ].out_chunk0;
546 :
547 0 : if( !strcmp( in_link->name, "shred_sign" ) ) {
548 0 : ctx->in[ i ].role = FD_KEYGUARD_ROLE_LEADER;
549 0 : FD_TEST( !strcmp( out_link->name, "sign_shred" ) );
550 0 : FD_TEST( in_link->mtu==32UL );
551 0 : FD_TEST( out_link->mtu==64UL );
552 0 : } else if ( !strcmp( in_link->name, "gossip_sign" ) ) {
553 0 : ctx->in[ i ].role = FD_KEYGUARD_ROLE_GOSSIP;
554 0 : FD_TEST( !strcmp( out_link->name, "sign_gossip" ) );
555 0 : FD_TEST( in_link->mtu==2048UL );
556 0 : FD_TEST( out_link->mtu==64UL );
557 0 : } else if ( !strcmp( in_link->name, "repair_sign" ) ) {
558 0 : ctx->in[ i ].role = FD_KEYGUARD_ROLE_REPAIR;
559 0 : FD_TEST( !strcmp( out_link->name, "sign_repair" ) );
560 0 : FD_TEST( in_link->mtu==124UL ); // FD_REPAIR_SIG_SER_MAX
561 0 : FD_TEST( out_link->mtu==64UL );
562 0 : } else if( !strcmp(in_link->name, "bundle_sign" ) ) {
563 0 : ctx->in[ i ].role = FD_KEYGUARD_ROLE_BUNDLE;
564 0 : FD_TEST( !strcmp( out_link->name, "sign_bundle" ) );
565 0 : FD_TEST( in_link->mtu==9UL );
566 0 : FD_TEST( out_link->mtu==64UL );
567 0 : } else if( !strcmp(in_link->name, "event_sign" ) ) {
568 0 : ctx->in[ i ].role = FD_KEYGUARD_ROLE_EVENT;
569 0 : FD_TEST( !strcmp( out_link->name, "sign_event" ) );
570 0 : FD_TEST( in_link->mtu==317UL );
571 0 : FD_TEST( out_link->mtu==64UL );
572 0 : } else if( !strcmp(in_link->name, "pack_sign" ) ) {
573 0 : ctx->in[ i ].role = FD_KEYGUARD_ROLE_BUNDLE_CRANK;
574 0 : FD_TEST( !strcmp( out_link->name, "sign_pack" ) );
575 0 : FD_TEST( in_link->mtu==1232UL );
576 0 : FD_TEST( out_link->mtu==64UL );
577 0 : } else if( !strcmp(in_link->name, "rserve_sign" ) ) {
578 0 : ctx->in[ i ].role = FD_KEYGUARD_ROLE_RSERVE;
579 0 : FD_TEST( !strcmp( out_link->name, "sign_rserve" ) );
580 0 : FD_TEST( in_link->mtu==32UL );
581 0 : FD_TEST( out_link->mtu==64UL );
582 0 : } else if( !strcmp(in_link->name, "votor_sign" ) ) {
583 0 : ctx->in[ i ].role = FD_KEYGUARD_ROLE_VOTOR;
584 0 : FD_TEST( !strcmp( out_link->name, "sign_votor" ) );
585 0 : FD_TEST( in_link->mtu==FD_KEYGUARD_SIGN_REQ_MTU );
586 0 : FD_TEST( out_link->mtu==FD_BLS_SIG_SZ );
587 0 : } else if( !strcmp(in_link->name, "failov_sign" ) ) {
588 0 : ctx->in[ i ].role = FD_KEYGUARD_ROLE_FAILOV;
589 0 : FD_TEST( !strcmp( out_link->name, "sign_failov" ) );
590 0 : FD_TEST( in_link->mtu==130UL );
591 0 : FD_TEST( out_link->mtu==64UL );
592 0 : FD_TEST( ctx->failover_junk_key && ctx->failover_key );
593 0 : } else {
594 0 : FD_LOG_CRIT(( "unexpected link %s", in_link->name ));
595 0 : }
596 0 : }
597 :
598 0 : ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
599 0 : if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
600 0 : FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
601 0 : }
602 :
603 : static void
604 : unprivileged_init( fd_topo_t const * topo,
605 0 : fd_topo_tile_t const * tile ) {
606 0 : unprivileged_init_sensitive( topo, tile );
607 0 : }
608 :
609 : static ulong
610 : populate_allowed_seccomp( fd_topo_t const * topo,
611 : fd_topo_tile_t const * tile,
612 : ulong out_cnt,
613 0 : struct sock_filter * out ) {
614 0 : (void)topo;
615 0 : (void)tile;
616 :
617 0 : populate_sock_filter_policy_fd_sign_tile( out_cnt, out, (uint)fd_log_private_logfile_fd() );
618 0 : return sock_filter_policy_fd_sign_tile_instr_cnt;
619 0 : }
620 :
621 : static ulong
622 : populate_allowed_fds( fd_topo_t const * topo,
623 : fd_topo_tile_t const * tile,
624 : ulong out_fds_cnt,
625 0 : int * out_fds ) {
626 0 : (void)topo;
627 0 : (void)tile;
628 :
629 0 : if( FD_UNLIKELY( out_fds_cnt<2UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
630 :
631 0 : ulong out_cnt = 0;
632 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
633 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
634 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
635 0 : return out_cnt;
636 0 : }
637 :
638 0 : #define STEM_BURST (1UL)
639 :
640 : /* See explanation in fd_pack */
641 0 : #define STEM_LAZY (128L*3000L)
642 :
643 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_sign_ctx_t
644 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_sign_ctx_t)
645 :
646 0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
647 0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
648 0 : #define STEM_CALLBACK_DURING_FRAG during_frag
649 0 : #define STEM_CALLBACK_AFTER_FRAG after_frag
650 :
651 : #include "../../disco/stem/fd_stem.c"
652 :
653 : fd_topo_run_tile_t fd_tile_sign = {
654 : .name = "sign",
655 : .populate_allowed_seccomp = populate_allowed_seccomp,
656 : .populate_allowed_fds = populate_allowed_fds,
657 : .scratch_align = scratch_align,
658 : .scratch_footprint = scratch_footprint,
659 : .privileged_init = privileged_init,
660 : .unprivileged_init = unprivileged_init,
661 : .run = stem_run,
662 : };
|