@@ -9,6 +9,241 @@ use crate::ffi::types::{c_uint, c_void, AlignedType};
9
9
use crate :: ffi:: { self , CPtr } ;
10
10
use crate :: { Error , Secp256k1 } ;
11
11
12
+ /// TODO: Rename to global and remove the other one.
13
+ pub mod _global {
14
+ use core:: convert:: TryFrom ;
15
+ use core:: sync:: atomic:: { AtomicBool , AtomicU8 , AtomicUsize , Ordering } ;
16
+ use std:: ops:: Deref ;
17
+ use std:: sync:: Once ;
18
+
19
+ use super :: alloc_only:: { SignOnly , VerifyOnly } ;
20
+ use crate :: ffi:: CPtr ;
21
+ use crate :: { ffi, Secp256k1 } ;
22
+
23
+ struct GlobalVerifyContext {
24
+ __private : ( ) ,
25
+ }
26
+
27
+ impl Deref for GlobalVerifyContext {
28
+ type Target = Secp256k1 < VerifyOnly > ;
29
+
30
+ fn deref ( & self ) -> & Self :: Target {
31
+ static ONCE : Once = Once :: new ( ) ;
32
+ static mut CONTEXT : Option < Secp256k1 < VerifyOnly > > = None ;
33
+ ONCE . call_once ( || unsafe {
34
+ let ctx = Secp256k1 :: verification_only ( ) ;
35
+ CONTEXT = Some ( ctx) ;
36
+ } ) ;
37
+ unsafe { CONTEXT . as_ref ( ) . unwrap ( ) }
38
+ }
39
+ }
40
+
41
+ struct GlobalSignContext {
42
+ __private : ( ) ,
43
+ }
44
+
45
+ impl Deref for GlobalSignContext {
46
+ type Target = Secp256k1 < SignOnly > ;
47
+
48
+ fn deref ( & self ) -> & Self :: Target {
49
+ static ONCE : Once = Once :: new ( ) ;
50
+ static mut CONTEXT : Option < Secp256k1 < SignOnly > > = None ;
51
+ ONCE . call_once ( || unsafe {
52
+ let ctx = Secp256k1 :: signing_only ( ) ;
53
+ CONTEXT = Some ( ctx) ;
54
+ } ) ;
55
+ unsafe { CONTEXT . as_ref ( ) . unwrap ( ) }
56
+ }
57
+ }
58
+
59
+ static GLOBAL_VERIFY_CONTEXT : & GlobalVerifyContext = & GlobalVerifyContext { __private : ( ) } ;
60
+
61
+ static GLOBAL_SIGN_CONTEXTS : [ & GlobalSignContext ; 2 ] =
62
+ [ & GlobalSignContext { __private : ( ) } , & GlobalSignContext { __private : ( ) } ] ;
63
+
64
+ static SIGN_CONTEXTS_DIRTY : [ AtomicBool ; 2 ] = [ AtomicBool :: new ( false ) , AtomicBool :: new ( false ) ] ;
65
+
66
+ // The sign contexts lock, stores two flags in the lowest bits and the reader count
67
+ // in the remaining bits. Thus adding or subtracting 4 increments/decrements the counter.
68
+ //
69
+ // The two flags are:
70
+ // * Active context bit - least significant (0b1)
71
+ // * Swap bit - second least significant (0b10) (see [`needs_swap`]).
72
+ static SIGN_CONTEXTS_LOCK : AtomicUsize = AtomicUsize :: new ( 0 ) ;
73
+
74
+ // Re-randomization lock, true==locked, false==unlocked.
75
+ static RERAND_LOCK : AtomicBool = AtomicBool :: new ( false ) ;
76
+
77
+ // Stores the seed for RNG. Notably it doesn't matter that a thread may read "inconsistent"
78
+ // content because it's all random data. If the array is being overwritten while being read it
79
+ // cannot worsen entropy and the exact data doesn't matter.
80
+ static GLOBAL_SEED : [ AtomicU8 ; 32 ] = init_seed_buffer ( ) ;
81
+
82
+ /// Rerandomizes inactive context using first half of `seed` and stores the second half in the
83
+ /// global seed buffer used for later rerandomizations.
84
+ pub fn reseed ( seed : & [ u8 ; 64 ] ) {
85
+ if rerand_lock ( ) {
86
+ let last_lock = sign_contexts_lock ( ) ;
87
+ let other = 1 - active_context ( last_lock) ;
88
+ _rerandomize ( other, <& [ u8 ; 32 ] >:: try_from ( & seed[ 0 ..32 ] ) . expect ( "32 bytes" ) ) ;
89
+ sign_contexts_unlock ( ) ;
90
+ rerand_unlock ( ) ;
91
+ // We unlock before setting the swap bit so that soon as another
92
+ // reader sees the swap bit set they can grab the rand lock.
93
+ sign_contexts_set_swap_bit ( ) ;
94
+ }
95
+ write_global_seed ( <& [ u8 ; 32 ] >:: try_from ( & seed[ 32 ..64 ] ) . expect ( "32 bytes" ) ) ;
96
+ }
97
+
98
+ /// Perform function using the current active global verification context.
99
+ ///
100
+ /// # Safety
101
+ ///
102
+ /// TODO: Write safety docs.
103
+ pub unsafe fn with_global_verify_context < F : FnOnce ( * const ffi:: Context ) -> R , R > ( f : F ) -> R {
104
+ f ( GLOBAL_VERIFY_CONTEXT . ctx . as_ptr ( ) )
105
+ }
106
+
107
+ /// Perform function using the current active global signing context.
108
+ ///
109
+ /// # Safety
110
+ ///
111
+ /// TODO: Write safety docs.
112
+ pub unsafe fn with_global_signing_context < F : FnOnce ( * const ffi:: Context ) -> R , R > ( f : F ) -> R {
113
+ let last_lock = sign_contexts_lock ( ) ;
114
+
115
+ // TODO(Tobin): Understand this block, is this to do with UB if atomics overflow?
116
+ if last_lock >= usize:: MAX / 2 {
117
+ // having usize::MAX threads should be impossible so if this happens it's because of a bug
118
+ panic ! ( "last_lock is too big" ) ;
119
+ }
120
+
121
+ let active_context = last_lock & 1 ;
122
+
123
+ let res = f ( GLOBAL_SIGN_CONTEXTS [ active_context] . ctx . as_ptr ( ) ) ;
124
+ set_context_dirty ( active_context) ;
125
+
126
+ let last_lock = sign_contexts_unlock ( ) ;
127
+
128
+ // No readers and needs swap.
129
+ if last_lock & !1 == 0b10 {
130
+ if let Some ( ctx) = sign_contexts_swap ( last_lock) {
131
+ rerandomize_with_global_seed ( ctx) ;
132
+ }
133
+ }
134
+ res
135
+ }
136
+
137
+ /// Returns the index (into GLOBAL_SIGN_CONTEXTS) of the active context.
138
+ fn active_context ( lock : usize ) -> usize { lock & 1 }
139
+
140
+ /// Returns `true` if lock was acquired, false otherwise.
141
+ fn rerand_lock ( ) -> bool {
142
+ RERAND_LOCK . compare_exchange ( false , true , Ordering :: Acquire , Ordering :: Relaxed ) . is_ok ( )
143
+ }
144
+
145
+ /// Attempts to unlock the rerand lock.
146
+ ///
147
+ /// # Returns
148
+ ///
149
+ /// `true` if the lock was unlocked by this operation.
150
+ fn rerand_unlock ( ) -> bool {
151
+ RERAND_LOCK . compare_exchange ( true , false , Ordering :: Acquire , Ordering :: Relaxed ) . is_ok ( )
152
+ }
153
+
154
+ fn sign_contexts_lock ( ) -> usize { SIGN_CONTEXTS_LOCK . fetch_add ( 4 , Ordering :: Acquire ) }
155
+
156
+ fn sign_contexts_unlock ( ) -> usize { SIGN_CONTEXTS_LOCK . fetch_sub ( 4 , Ordering :: Acquire ) }
157
+
158
+ /// Swap the active context and clear the swap bit.
159
+ ///
160
+ /// # Panics
161
+ ///
162
+ /// If `lock` has count > 0.
163
+ ///
164
+ /// # Returns
165
+ ///
166
+ /// The now-inactive context index (ie, the index of the context swapped out).
167
+ fn sign_contexts_swap ( lock : usize ) -> Option < usize > {
168
+ assert ! ( lock & !0b11 == 0 ) ; // reader count == 0
169
+ let new = ( lock & !0b10 ) ^ 0b01 ; // turn off swap bit, toggle active bit.
170
+ match SIGN_CONTEXTS_LOCK . compare_exchange ( lock, new, Ordering :: Relaxed , Ordering :: Relaxed ) {
171
+ Ok ( last_lock) => Some ( active_context ( last_lock) ) ,
172
+ Err ( _) => None ,
173
+ }
174
+ }
175
+
176
+ /// Unconditionally turns on the "needs swap" bit.
177
+ fn sign_contexts_set_swap_bit ( ) { SIGN_CONTEXTS_LOCK . fetch_or ( 0b10 , Ordering :: Relaxed ) ; }
178
+
179
+ fn set_context_dirty ( ctx : usize ) {
180
+ assert ! ( ctx < 2 ) ;
181
+ SIGN_CONTEXTS_DIRTY [ ctx] . store ( true , Ordering :: Relaxed ) ;
182
+ }
183
+
184
+ fn clear_context_dirty ( ctx : usize ) {
185
+ assert ! ( ctx < 2 ) ;
186
+ SIGN_CONTEXTS_DIRTY [ ctx] . store ( true , Ordering :: Relaxed ) ;
187
+ }
188
+
189
+ fn write_global_seed ( seed : & [ u8 ; 32 ] ) {
190
+ for ( i, b) in seed. iter ( ) . enumerate ( ) {
191
+ GLOBAL_SEED [ i] . store ( * b, Ordering :: Relaxed ) ;
192
+ }
193
+ }
194
+
195
+ /// Rerandomize the global signing context using randomness in the global seed.
196
+ fn rerandomize_with_global_seed ( ctx : usize ) {
197
+ let mut buf = [ 0_u8 ; 32 ] ;
198
+ for ( i, b) in buf. iter_mut ( ) . enumerate ( ) {
199
+ let atomic = & GLOBAL_SEED [ i] ;
200
+ * b = atomic. load ( Ordering :: Relaxed ) ;
201
+ }
202
+ rerandomize ( ctx, & buf)
203
+ }
204
+
205
+ /// Rerandomize global context index `ctx` using randomness in `seed`.
206
+ fn rerandomize ( ctx : usize , seed : & [ u8 ; 32 ] ) {
207
+ if rerand_lock ( ) {
208
+ _rerandomize ( ctx, seed) ;
209
+ clear_context_dirty ( ctx) ;
210
+ rerand_unlock ( ) ;
211
+ // We unlock before setting the swap bit so that soon as another
212
+ // reader sees the swap bit set they can grab the rand lock.
213
+ sign_contexts_set_swap_bit ( ) ;
214
+ }
215
+ }
216
+
217
+ /// Should be called with the RERAND_LOCK held.
218
+ fn _rerandomize ( ctx : usize , seed : & [ u8 ; 32 ] ) {
219
+ let secp = GLOBAL_SIGN_CONTEXTS [ ctx] ;
220
+ unsafe {
221
+ let err = ffi:: secp256k1_context_randomize ( secp. ctx , seed. as_c_ptr ( ) ) ;
222
+ // This function cannot fail; it has an error return for future-proofing.
223
+ // We do not expose this error since it is impossible to hit, and we have
224
+ // precedent for not exposing impossible errors (for example in
225
+ // `PublicKey::from_secret_key` where it is impossible to create an invalid
226
+ // secret key through the API.)
227
+ // However, if this DOES fail, the result is potentially weaker side-channel
228
+ // resistance, which is deadly and undetectable, so we take out the entire
229
+ // thread to be on the safe side.
230
+ assert_eq ! ( err, 1 ) ;
231
+ }
232
+ }
233
+
234
+ // TODO: Find better way to do this.
235
+ #[ rustfmt:: skip]
236
+ const fn init_seed_buffer ( ) -> [ AtomicU8 ; 32 ] {
237
+ let buf: [ AtomicU8 ; 32 ] = [
238
+ AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) ,
239
+ AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) ,
240
+ AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) ,
241
+ AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) , AtomicU8 :: new ( 0 ) ,
242
+ ] ;
243
+ buf
244
+ }
245
+ }
246
+
12
247
#[ cfg( all( feature = "global-context" , feature = "std" ) ) ]
13
248
#[ cfg_attr( docsrs, doc( cfg( all( feature = "global-context" , feature = "std" ) ) ) ) ]
14
249
/// Module implementing a singleton pattern for a global `Secp256k1` context.
0 commit comments