Skip to content

Commit c9bcdec

Browse files
committed
Add initial sign/rerandomization impl
1 parent 141b874 commit c9bcdec

File tree

1 file changed

+235
-0
lines changed

1 file changed

+235
-0
lines changed

src/context.rs

+235
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,241 @@ 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 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+
12247
#[cfg(all(feature = "global-context", feature = "std"))]
13248
#[cfg_attr(docsrs, doc(cfg(all(feature = "global-context", feature = "std"))))]
14249
/// Module implementing a singleton pattern for a global `Secp256k1` context.

0 commit comments

Comments
 (0)