Skip to content

Commit 06d0f16

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

File tree

1 file changed

+234
-0
lines changed

1 file changed

+234
-0
lines changed

src/context.rs

+234
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,240 @@ 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+
if last_lock & !1 == 0b10 {
128+
// No readers and needs swap.
129+
if let Some(ctx) = sign_contexts_swap(last_lock) {
130+
rerandomize_with_global_seed(ctx);
131+
}
132+
}
133+
res
134+
}
135+
136+
// Returns the index (into GLOBAL_SIGN_CONTEXTS) of the active context.
137+
fn active_context(lock: usize) -> usize { lock & 1 }
138+
139+
/// Returns `true` if lock was acquired, false otherwise.
140+
fn rerand_lock() -> bool {
141+
RERAND_LOCK.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_ok()
142+
}
143+
144+
/// Attempts to unlock the rerand lock.
145+
///
146+
/// # Returns
147+
///
148+
/// Whether `true` if the lock was unlocked by this operation.
149+
fn rerand_unlock() -> bool {
150+
RERAND_LOCK.compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed).is_ok()
151+
}
152+
153+
fn sign_contexts_lock() -> usize { SIGN_CONTEXTS_LOCK.fetch_add(4, Ordering::Acquire) }
154+
155+
fn sign_contexts_unlock() -> usize { SIGN_CONTEXTS_LOCK.fetch_sub(4, Ordering::Acquire) }
156+
157+
/// Swap the active context and clear the swap bit.
158+
///
159+
/// # Panics
160+
///
161+
/// If `lock` has count > 0.
162+
///
163+
/// # Returns
164+
///
165+
/// The now-inactive context index (ie, the index of the context swapped out).
166+
fn sign_contexts_swap(lock: usize) -> Option<usize> {
167+
assert!(lock & !0b11 == 0); // reader count == 0
168+
let new = (lock & !0b10) ^ 0b01; // turn off swap bit, toggle active bit.
169+
match SIGN_CONTEXTS_LOCK.compare_exchange(lock, new, Ordering::Relaxed, Ordering::Relaxed) {
170+
Ok(last_lock) => Some(active_context(last_lock)),
171+
Err(_) => None,
172+
}
173+
}
174+
175+
/// Unconditionally turns on the "needs swap" bit.
176+
fn sign_contexts_set_swap_bit() { SIGN_CONTEXTS_LOCK.fetch_or(0b10, Ordering::Relaxed); }
177+
178+
fn set_context_dirty(ctx: usize) {
179+
assert!(ctx < 2);
180+
SIGN_CONTEXTS_DIRTY[ctx].store(true, Ordering::Relaxed);
181+
}
182+
183+
fn clear_context_dirty(ctx: usize) {
184+
assert!(ctx < 2);
185+
SIGN_CONTEXTS_DIRTY[ctx].store(true, Ordering::Relaxed);
186+
}
187+
188+
fn write_global_seed(seed: &[u8; 32]) {
189+
for (i, b) in seed.iter().enumerate() {
190+
GLOBAL_SEED[i].store(*b, Ordering::Relaxed);
191+
}
192+
}
193+
194+
/// Rerandomize the global signing context using randomness in the global seed.
195+
fn rerandomize_with_global_seed(ctx: usize) {
196+
let mut buf = [0_u8; 32];
197+
for (i, b) in buf.iter_mut().enumerate() {
198+
let atomic = &GLOBAL_SEED[i];
199+
*b = atomic.load(Ordering::Relaxed);
200+
}
201+
rerandomize(ctx, &buf)
202+
}
203+
204+
/// Rerandomize global context index `ctx` using randomness in `seed`.
205+
fn rerandomize(ctx: usize, seed: &[u8; 32]) {
206+
if rerand_lock() {
207+
_rerandomize(ctx, seed);
208+
clear_context_dirty(ctx);
209+
rerand_unlock();
210+
// We unlock before setting the swap bit so that soon as another
211+
// reader sees the swap bit set they can grab the rand lock.
212+
sign_contexts_set_swap_bit();
213+
}
214+
}
215+
216+
/// Should be called with the RERAND_LOCK held.
217+
fn _rerandomize(ctx: usize, seed: &[u8; 32]) {
218+
let secp = GLOBAL_SIGN_CONTEXTS[ctx];
219+
unsafe {
220+
let err = ffi::secp256k1_context_randomize(secp.ctx, seed.as_c_ptr());
221+
// This function cannot fail; it has an error return for future-proofing.
222+
// We do not expose this error since it is impossible to hit, and we have
223+
// precedent for not exposing impossible errors (for example in
224+
// `PublicKey::from_secret_key` where it is impossible to create an invalid
225+
// secret key through the API.)
226+
// However, if this DOES fail, the result is potentially weaker side-channel
227+
// resistance, which is deadly and undetectable, so we take out the entire
228+
// thread to be on the safe side.
229+
assert_eq!(err, 1);
230+
}
231+
}
232+
233+
// TODO: Find better way to do this.
234+
#[rustfmt::skip]
235+
const fn init_seed_buffer() -> [AtomicU8; 32] {
236+
let buf: [AtomicU8; 32] = [
237+
AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0),
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+
];
242+
buf
243+
}
244+
}
245+
12246
#[cfg(all(feature = "global-context", feature = "std"))]
13247
#[cfg_attr(docsrs, doc(cfg(all(feature = "global-context", feature = "std"))))]
14248
/// Module implementing a singleton pattern for a global `Secp256k1` context.

0 commit comments

Comments
 (0)