Skip to content

Commit 8cb222f

Browse files
committed
Add initial sign/rerandomization impl
1 parent be2999a commit 8cb222f

File tree

1 file changed

+251
-0
lines changed

1 file changed

+251
-0
lines changed

src/context.rs

+251
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,257 @@ use crate::ffi::types::{c_uint, c_void, AlignedType};
99
use crate::ffi::{self, CPtr};
1010
use crate::{Error, Secp256k1};
1111

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 semaphore, 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_SEM: 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+
///
81+
/// We still have to use atomics because multiple mutable accesses is undefined behavior in Rust.
82+
static GLOBAL_SEED: [AtomicU8; 32] = init_seed_buffer();
83+
84+
/// Rerandomizes inactive context using first half of `seed` and stores the second half in the
85+
/// global seed buffer used for later rerandomizations.
86+
pub fn reseed(seed: &[u8; 64]) {
87+
if rerand_lock() {
88+
let last = sign_contexts_inc();
89+
let other = 1 - active_context(last);
90+
91+
_rerandomize(other, <&[u8; 32]>::try_from(&seed[0..32]).expect("32 bytes"));
92+
clear_context_dirty(other);
93+
rerand_unlock();
94+
95+
sign_contexts_dec();
96+
97+
// We unlock before setting the swap bit so that soon as another
98+
// reader sees the swap bit set they can grab the rand lock.
99+
sign_contexts_set_swap_bit();
100+
}
101+
write_global_seed(<&[u8; 32]>::try_from(&seed[32..64]).expect("32 bytes"));
102+
}
103+
104+
/// Perform function using the current active global verification context.
105+
///
106+
/// # Safety
107+
///
108+
/// TODO: Write safety docs.
109+
pub unsafe fn with_global_verify_context<F: FnOnce(*const ffi::Context) -> R, R>(f: F) -> R {
110+
f(GLOBAL_VERIFY_CONTEXT.ctx.as_ptr())
111+
}
112+
113+
/// Perform function using the current active global signing context.
114+
///
115+
/// # Safety
116+
///
117+
/// TODO: Write safety docs.
118+
pub unsafe fn with_global_signing_context<F: FnOnce(*const ffi::Context) -> R, R>(f: F) -> R {
119+
let last = sign_contexts_inc();
120+
121+
// Shift 2 for the 2 flag bits.
122+
if last >= usize::MAX >> 2 {
123+
// Having this many threads should be impossible so if this happens it's because of a bug.
124+
panic!("too many readers");
125+
}
126+
127+
let active = active_context(last);
128+
129+
let res = f(GLOBAL_SIGN_CONTEXTS[active].ctx.as_ptr());
130+
set_context_dirty(active);
131+
132+
let last = sign_contexts_dec();
133+
134+
// No readers and needs swap.
135+
if last & !1 == 0b10 {
136+
if let Some(ctx) = sign_contexts_swap(last) {
137+
rerandomize_with_global_seed(ctx);
138+
}
139+
}
140+
res
141+
}
142+
143+
/// Returns the index (into GLOBAL_SIGN_CONTEXTS) of the active context.
144+
fn active_context(sem: usize) -> usize { sem & 1 }
145+
146+
/// Attempts to lock the rerand lock.
147+
///
148+
/// # Returns
149+
///
150+
/// `true` if lock was acquired, false otherwise.
151+
fn rerand_lock() -> bool {
152+
RERAND_LOCK.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_ok()
153+
}
154+
155+
/// Attempts to unlock the rerand lock.
156+
///
157+
/// # Returns
158+
///
159+
/// `true` if the lock was unlocked by this operation.
160+
fn rerand_unlock() -> bool {
161+
RERAND_LOCK.compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed).is_ok()
162+
}
163+
164+
/// Increments the sign-contexts reader semaphore.
165+
// FIXME: What happens if we have more than usize::MAX >> 2 readers i.e., overflow?
166+
fn sign_contexts_inc() -> usize { SIGN_CONTEXTS_SEM.fetch_add(4, Ordering::Acquire) }
167+
168+
/// Decrements the sign-contexts reader semaphore.
169+
fn sign_contexts_dec() -> usize { SIGN_CONTEXTS_SEM.fetch_sub(4, Ordering::Acquire) }
170+
171+
/// Swap the active context and clear the swap bit.
172+
///
173+
/// # Panics
174+
///
175+
/// If `lock` has count > 0.
176+
///
177+
/// # Returns
178+
///
179+
/// The now-inactive context index (ie, the index of the context swapped out).
180+
fn sign_contexts_swap(sem: usize) -> Option<usize> {
181+
assert!(sem & !0b11 == 0); // reader count == 0
182+
let new = (sem & !0b10) ^ 0b01; // turn off swap bit, toggle active bit.
183+
match SIGN_CONTEXTS_SEM.compare_exchange(sem, new, Ordering::Relaxed, Ordering::Relaxed) {
184+
Ok(last) => Some(active_context(last)),
185+
// Another reader signaled before we had a chance to swap.
186+
Err(_) => None,
187+
}
188+
}
189+
190+
/// Unconditionally turns on the "needs swap" bit.
191+
fn sign_contexts_set_swap_bit() { SIGN_CONTEXTS_SEM.fetch_or(0b10, Ordering::Relaxed); }
192+
193+
fn set_context_dirty(ctx: usize) {
194+
assert!(ctx < 2);
195+
SIGN_CONTEXTS_DIRTY[ctx].store(true, Ordering::Relaxed);
196+
}
197+
198+
fn clear_context_dirty(ctx: usize) {
199+
assert!(ctx < 2);
200+
SIGN_CONTEXTS_DIRTY[ctx].store(true, Ordering::Relaxed);
201+
}
202+
203+
fn write_global_seed(seed: &[u8; 32]) {
204+
for (i, b) in seed.iter().enumerate() {
205+
GLOBAL_SEED[i].store(*b, Ordering::Relaxed);
206+
}
207+
}
208+
209+
/// Rerandomize the global signing context using randomness in the global seed.
210+
fn rerandomize_with_global_seed(ctx: usize) {
211+
let mut buf = [0_u8; 32];
212+
for (i, b) in buf.iter_mut().enumerate() {
213+
let atomic = &GLOBAL_SEED[i];
214+
*b = atomic.load(Ordering::Relaxed);
215+
}
216+
rerandomize(ctx, &buf)
217+
}
218+
219+
/// Rerandomize global context index `ctx` using randomness in `seed`.
220+
fn rerandomize(ctx: usize, seed: &[u8; 32]) {
221+
assert!(ctx < 2);
222+
if rerand_lock() {
223+
_rerandomize(ctx, seed);
224+
clear_context_dirty(ctx);
225+
rerand_unlock();
226+
227+
// We unlock before setting the swap bit so that soon as another
228+
// reader sees the swap bit set they can grab the rand lock.
229+
sign_contexts_set_swap_bit();
230+
}
231+
}
232+
233+
/// Should be called with the RERAND_LOCK held.
234+
fn _rerandomize(ctx: usize, seed: &[u8; 32]) {
235+
let secp = GLOBAL_SIGN_CONTEXTS[ctx];
236+
unsafe {
237+
let err = ffi::secp256k1_context_randomize(secp.ctx, seed.as_c_ptr());
238+
// This function cannot fail; it has an error return for future-proofing.
239+
// We do not expose this error since it is impossible to hit, and we have
240+
// precedent for not exposing impossible errors (for example in
241+
// `PublicKey::from_secret_key` where it is impossible to create an invalid
242+
// secret key through the API.)
243+
// However, if this DOES fail, the result is potentially weaker side-channel
244+
// resistance, which is deadly and undetectable, so we take out the entire
245+
// thread to be on the safe side.
246+
assert_eq!(err, 1);
247+
}
248+
}
249+
250+
// TODO: Find better way to do this.
251+
#[rustfmt::skip]
252+
const fn init_seed_buffer() -> [AtomicU8; 32] {
253+
let buf: [AtomicU8; 32] = [
254+
AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0),
255+
AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0),
256+
AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0),
257+
AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0),
258+
];
259+
buf
260+
}
261+
}
262+
12263
#[cfg(all(feature = "global-context", feature = "std"))]
13264
#[cfg_attr(docsrs, doc(cfg(all(feature = "global-context", feature = "std"))))]
14265
/// Module implementing a singleton pattern for a global `Secp256k1` context.

0 commit comments

Comments
 (0)