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