Skip to content

Commit a68e545

Browse files
committed
WIP: Add initial sign/rerandomization impl
Add new sign/re-randomization API using atomics. Includes a few examples of how we could move forward with a separate API for "std" builds to non-std builds.
1 parent 7c8270a commit a68e545

File tree

9 files changed

+441
-82
lines changed

9 files changed

+441
-82
lines changed

examples/generate_keys.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1+
#![cfg(feature = "std")]
2+
13
extern crate secp256k1;
24

3-
use secp256k1::{PublicKey, Secp256k1, SecretKey};
5+
use secp256k1::{PublicKey, SecretKey};
46

57
fn main() {
6-
let secp = Secp256k1::new();
78
let mut rng = rand::thread_rng();
89
// First option:
9-
let (seckey, pubkey) = secp.generate_keypair(&mut rng);
10+
let (seckey, pubkey) = secp256k1::generate_keypair(&mut rng);
1011

11-
assert_eq!(pubkey, PublicKey::from_secret_key(&secp, &seckey));
12+
assert_eq!(pubkey, PublicKey::from_secret_key(&seckey));
1213

1314
// Second option:
1415
let seckey = SecretKey::new(&mut rng);
15-
let _pubkey = PublicKey::from_secret_key(&secp, &seckey);
16+
let _pubkey = PublicKey::from_secret_key(&seckey);
1617
}

src/context.rs

+252
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,258 @@ use crate::ffi::types::{c_uint, c_void, AlignedType};
1010
use crate::ffi::{self, CPtr};
1111
use crate::{Error, Secp256k1};
1212

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

src/ecdh.rs

+4-6
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,8 @@ mod tests {
195195
#[test]
196196
#[cfg(feature = "rand-std")]
197197
fn ecdh() {
198-
let s = Secp256k1::signing_only();
199-
let (sk1, pk1) = s.generate_keypair(&mut rand::thread_rng());
200-
let (sk2, pk2) = s.generate_keypair(&mut rand::thread_rng());
198+
let (sk1, pk1) = crate::generate_keypair(&mut rand::thread_rng());
199+
let (sk2, pk2) = crate::generate_keypair(&mut rand::thread_rng());
201200

202201
let sec1 = SharedSecret::new(&pk2, &sk1);
203202
let sec2 = SharedSecret::new(&pk1, &sk2);
@@ -231,9 +230,8 @@ mod tests {
231230

232231
use crate::ecdh::shared_secret_point;
233232

234-
let s = Secp256k1::signing_only();
235-
let (sk1, _) = s.generate_keypair(&mut rand::thread_rng());
236-
let (_, pk2) = s.generate_keypair(&mut rand::thread_rng());
233+
let (sk1, _) = crate::generate_keypair(&mut rand::thread_rng());
234+
let (_, pk2) = crate::generate_keypair(&mut rand::thread_rng());
237235

238236
let secret_sys = SharedSecret::new(&pk2, &sk1);
239237

src/ecdsa/global.rs

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//! Drop in replacement for all the methods currently implemented on the global context (SECP256K1).
2+
3+
use core::ptr;
4+
5+
use super::Signature;
6+
use crate::ffi::CPtr;
7+
use crate::{ffi, Error, Message, PublicKey, SecretKey};
8+
9+
/// Constructs a signature for `msg` using the secret key `sk` and RFC6979 nonce
10+
pub fn sign_ecdsa(msg: &Message, sk: &SecretKey) -> Signature {
11+
sign_ecdsa_with_noncedata_pointer(msg, sk, None)
12+
}
13+
14+
/// Constructs a signature for `msg` using the secret key `sk` and RFC6979 nonce
15+
/// and includes 32 bytes of noncedata in the nonce generation via inclusion in
16+
/// one of the hash operations during nonce generation. This is useful when multiple
17+
/// signatures are needed for the same Message and SecretKey while still using RFC6979.
18+
/// Requires a signing-capable context.
19+
pub fn sign_ecdsa_with_noncedata(msg: &Message, sk: &SecretKey, noncedata: &[u8; 32]) -> Signature {
20+
sign_ecdsa_with_noncedata_pointer(msg, sk, Some(noncedata))
21+
}
22+
23+
/// Checks that `sig` is a valid ECDSA signature for `msg` using the public
24+
/// key `pubkey`. Returns `Ok(())` on success. Note that this function cannot
25+
/// be used for Bitcoin consensus checking since there may exist signatures
26+
/// which OpenSSL would verify but not libsecp256k1, or vice-versa. Requires a
27+
/// verify-capable context.
28+
///
29+
/// ```rust
30+
/// # #[cfg(feature = "rand-std")] {
31+
/// # use secp256k1::{rand, Secp256k1, Message, Error};
32+
/// #
33+
/// # let secp = Secp256k1::new();
34+
/// # let (secret_key, public_key) = secp.generate_keypair(&mut rand::thread_rng());
35+
/// #
36+
/// let message = Message::from_slice(&[0xab; 32]).expect("32 bytes");
37+
/// let sig = secp.sign_ecdsa(&message, &secret_key);
38+
/// assert_eq!(secp.verify_ecdsa(&message, &sig, &public_key), Ok(()));
39+
///
40+
/// let message = Message::from_slice(&[0xcd; 32]).expect("32 bytes");
41+
/// assert_eq!(secp.verify_ecdsa(&message, &sig, &public_key), Err(Error::IncorrectSignature));
42+
/// # }
43+
/// ```
44+
#[inline]
45+
pub fn verify_ecdsa(msg: &Message, sig: &Signature, pk: &PublicKey) -> Result<(), Error> {
46+
unsafe {
47+
crate::context::_global::with_global_verify_context(|ctx| {
48+
if ffi::secp256k1_ecdsa_verify(ctx, sig.as_c_ptr(), msg.as_c_ptr(), pk.as_c_ptr()) == 0
49+
{
50+
Err(Error::IncorrectSignature)
51+
} else {
52+
Ok(())
53+
}
54+
})
55+
}
56+
}
57+
58+
fn sign_ecdsa_with_noncedata_pointer(
59+
msg: &Message,
60+
sk: &SecretKey,
61+
noncedata: Option<&[u8; 32]>,
62+
) -> Signature {
63+
unsafe {
64+
let mut ret = ffi::Signature::new();
65+
let noncedata_ptr = match noncedata {
66+
Some(arr) => arr.as_c_ptr() as *const _,
67+
None => ptr::null(),
68+
};
69+
crate::context::_global::with_global_signing_context(|ctx| {
70+
// We can assume the return value because it's not possible to construct
71+
// an invalid signature from a valid `Message` and `SecretKey`
72+
assert_eq!(
73+
ffi::secp256k1_ecdsa_sign(
74+
ctx,
75+
&mut ret,
76+
msg.as_c_ptr(),
77+
sk.as_c_ptr(),
78+
ffi::secp256k1_nonce_function_rfc6979,
79+
noncedata_ptr
80+
),
81+
1
82+
);
83+
});
84+
Signature::from(ret)
85+
}
86+
}

0 commit comments

Comments
 (0)