Line data Source code
1 : #ifndef HEADER_fd_src_disco_shred_fd_rnonce_ss_h
2 : #define HEADER_fd_src_disco_shred_fd_rnonce_ss_h
3 : #include "../../util/fd_util.h"
4 :
5 : /* fd_rnonce_ss_t is a strongly typed version of the 64 byte shared
6 : secret used to generate and verify nonces for repair requests and
7 : responses. */
8 : union fd_rnonce_ss {
9 : uchar bytes[64];
10 : struct {
11 : ulong ss0[3];
12 : ulong slot;
13 : ulong ss1[1];
14 : uint shred_idx;
15 : uint ss2[2];
16 : uint time;
17 : ulong ss3[1];
18 : } private;
19 : };
20 : typedef union fd_rnonce_ss fd_rnonce_ss_t;
21 : FD_STATIC_ASSERT( sizeof(fd_rnonce_ss_t)==64, rnonce_ss );
22 :
23 : FD_PROTOTYPES_BEGIN
24 :
25 : /* fd_rnonce_ss_{compute,verify} compute and verify, respectively, the
26 : nonce for the specified repair request issued or received at time_ns.
27 : slot and shred_idx specify the slot and shred index of the
28 : requested/received shred. slot_complete must be non-zero if the
29 : received shred is the last in the slot. normal_repair must be
30 : non-zero if the request is a "normal" repair request, i.e., one for a
31 : specific shred index. If normal_repair is zero, shred_idx is
32 : ignored, slot is adjusted (see below), and slot_complete must be
33 : non-zero in the verify function. ss is a pointer to the shared secret
34 : value. ss_compute returns the value of the nonce. ss_verify takes
35 : the supposed value of the nonce in the nonce parameter. ss_verify
36 : returns 1 if the nonce is correct and 0 otherwise.
37 :
38 : These satisfy:
39 : 1==ss_verify( ss_v, ss_compute( ss_c, 1, slot_c, shred_idx_c, rq_time ), slot_v, shred_idx_v, slot_complete, rs_time )
40 : when
41 : ss_v == ss_c,
42 : slot_v == slot_c,
43 : shred_idx_v == shred_idx_c, AND
44 : rq_time <= rs_time < rq_time + 1.02 seconds.
45 : When any of these of these conditions is false, it should return 0
46 : with high probability, approx (1 - 2^25).
47 :
48 : And
49 : 1==ss_verify( ss_v, ss_compute( ss_c, 0, slot_c, shred_idx_c, rq_time ), slot_v, shred_idx_v, slot_complete, rs_time )
50 : when
51 : ss_v == ss_c,
52 : slot_complete != 0,
53 : -1 <= floor(slot_v/128) - floor(slot_c/128) <= 0, which is looser than slot_c - 128 <= slot_v <= slot_c, AND
54 : rq_time <= rs_time < rq_time + 1.02 seconds.
55 : When any of these of these conditions is false, it should return 0
56 : with high probability, approx (1 - 2^25).
57 : */
58 : static inline uint
59 : fd_rnonce_ss_compute( fd_rnonce_ss_t const * ss,
60 : int normal_repair,
61 : ulong slot,
62 : uint shred_idx,
63 66000000 : long time_ns ) {
64 66000000 : fd_rnonce_ss_t temp[1] = { *ss };
65 : /* truncate time down to intervals of 2^32 ns, which is ~4 seconds. */
66 66000000 : temp->private.time = (uint)(time_ns>>32);
67 66000000 : temp->private.slot = fd_ulong_if( normal_repair, slot, slot/128UL );
68 66000000 : temp->private.shred_idx = fd_uint_if ( normal_repair, shred_idx, 0U );
69 : /* seed is fractional part of sqrt(17) */
70 : /* Then we add back in time_ns>>24 (truncated to 16ms intervals).
71 : This is kind of surprising, but it means that we can generate a new
72 : nonce when we re-request a specific shred, but we don't need to
73 : compute a ton of hashes. */
74 66000000 : return (uint)(
75 66000000 : fd_ulong_if( normal_repair, 0x80000000UL, 0UL ) |
76 66000000 : (0x7FFFFFFFUL & (fd_hash( 2270897969802886507UL, temp, sizeof(temp) ) + (((ulong)time_ns)>>24) ) ) );
77 66000000 : }
78 :
79 : static inline int
80 : fd_rnonce_ss_verify( fd_rnonce_ss_t const * ss,
81 : uint nonce,
82 : ulong slot,
83 : uint shred_idx,
84 : int slot_complete,
85 99000000 : long time_ns ) {
86 99000000 : fd_rnonce_ss_t temp[1] = { *ss };
87 99000000 : int normal_repair = !!(nonce>>31);
88 :
89 : /* If it's not "normal" repair, then the shred must have slot
90 : complete. Technically this is not required by the repair protocol,
91 : so we may occasionally reject an honest response here when the
92 : responder doesn't have the last shred in the slot, but this is good
93 : defense in depth. If none of the repair peers have the last shred
94 : in the slot, then it's unlikely the cluster has confirmed it. */
95 99000000 : if( FD_UNLIKELY( (!normal_repair) & (!slot_complete) ) ) return 0;
96 :
97 96000000 : temp->private.time = (uint)(time_ns>>32);
98 96000000 : temp->private.slot = fd_ulong_if( normal_repair, slot, slot/128UL );
99 96000000 : temp->private.shred_idx = fd_uint_if ( normal_repair, shred_idx, 0U );
100 96000000 : #define ALLOWED_TIME_DELTA ((uint)((1000000000UL + (1UL<<24) - 1UL)/(1UL<<24))) /* == 60 */
101 :
102 156467385 : #define CHECKN( temp ) do{ if( FD_LIKELY( \
103 156467385 : ( (0x7FFFFFFFUL & (fd_hash( 2270897969802886507UL, temp, sizeof(temp) ) + (((ulong)time_ns)>>24) )) - \
104 156467385 : (0x7FFFFFFFUL & nonce) ) <= ALLOWED_TIME_DELTA ) ) \
105 156467385 : return 1; \
106 156467385 : } while( 0 )
107 :
108 :
109 96000000 : CHECKN( temp );
110 :
111 92015550 : int try_prev_time = ((time_ns-1000000000L)>>32) != (time_ns>>32);
112 92015550 : if( try_prev_time ) {
113 : /* If that doesn't match, check the previous time bucket. */
114 21825834 : temp->private.time--;
115 21825834 : CHECKN( temp );
116 21298338 : temp->private.time++;
117 21298338 : }
118 :
119 91488054 : if( FD_UNLIKELY( !normal_repair ) ) {
120 : /* Check the next slot bucket */
121 31488054 : temp->private.slot++;
122 31488054 : CHECKN( temp );
123 30174024 : if( try_prev_time ) {
124 : /* And check it with the prev time bucket */
125 7153497 : temp->private.time--;
126 7153497 : CHECKN( temp );
127 7153497 : }
128 30174024 : }
129 89999997 : #undef CHECKN
130 89999997 : #undef ALLOWED_TIME_DELTA
131 89999997 : return 0;
132 91488054 : }
133 :
134 : FD_PROTOTYPES_END
135 :
136 : #endif /* HEADER_fd_src_disco_shred_fd_rnonce_ss_h */
|