Skip to content

Commit

Permalink
Randomize context on creation
Browse files Browse the repository at this point in the history
Currently it is easy for users to mis-use our API because they may not
know that `randomize()` should be called after context creation for
maximum defence against side channel attacks.

We can better assist users by making APIs that are hard to mis-use.

Add an enum to the context constructor that forces the use to make a
decision on the additional side channel attack protection we provide.
This makes the API a little harder to use because users must now read
the docs but this cost offsets the likely-hood of users ignoring this
important feature.

This is quite an invasive change because every user of the secp256k1
library will have to update the context constructor call sites and read
what this enum does. Is this worth it?

Resolves: rust-bitcoin#225
  • Loading branch information
tcharding committed Jan 26, 2022
1 parent ea8eb12 commit 7d772d3
Show file tree
Hide file tree
Showing 12 changed files with 240 additions and 117 deletions.
2 changes: 1 addition & 1 deletion examples/generate_keys.rs
Expand Up @@ -4,7 +4,7 @@ use secp256k1::rand::rngs::OsRng;
use secp256k1::{PublicKey, Secp256k1, SecretKey};

fn main() {
let secp = Secp256k1::new();
let secp = Secp256k1::new_randomize();
let mut rng = OsRng::new().unwrap();
// First option:
let (seckey, pubkey) = secp.generate_keypair(&mut rng);
Expand Down
2 changes: 1 addition & 1 deletion examples/sign_verify.rs
Expand Up @@ -21,7 +21,7 @@ fn sign<C: Signing>(secp: &Secp256k1<C>, msg: &[u8], seckey: [u8; 32]) -> Result
}

fn main() {
let secp = Secp256k1::new();
let secp = Secp256k1::new_no_randomize();

let seckey = [59, 148, 11, 85, 134, 130, 61, 253, 2, 174, 59, 70, 27, 180, 51, 107, 94, 203, 174, 253, 102, 39, 170, 146, 46, 252, 4, 143, 236, 12, 136, 28];
let pubkey = [2, 29, 21, 35, 7, 198, 183, 43, 14, 208, 65, 139, 14, 112, 205, 128, 231, 245, 41, 91, 141, 134, 245, 114, 45, 63, 82, 19, 251, 210, 57, 79, 54];
Expand Down
2 changes: 1 addition & 1 deletion examples/sign_verify_recovery.rs
Expand Up @@ -22,7 +22,7 @@ fn sign_recovery<C: Signing>(secp: &Secp256k1<C>, msg: &[u8], seckey: [u8; 32])
}

fn main() {
let secp = Secp256k1::new();
let secp = Secp256k1::new_no_randomize();

let seckey = [
59, 148, 11, 85, 134, 130, 61, 253, 2, 174, 59, 70, 27, 180, 51, 107,
Expand Down
2 changes: 1 addition & 1 deletion no_std_test/src/main.rs
Expand Up @@ -135,7 +135,7 @@ fn start(_argc: isize, _argv: *const *const u8) -> isize {

#[cfg(feature = "alloc")]
{
let secp_alloc = Secp256k1::new();
let secp_alloc = Secp256k1::new_no_randomize();
let public_key = PublicKey::from_secret_key(&secp_alloc, &secret_key);
let message = Message::from_slice(&[0xab; 32]).expect("32 bytes");

Expand Down
121 changes: 104 additions & 17 deletions src/context.rs
Expand Up @@ -29,17 +29,18 @@ pub mod global {
/// A global, static context to avoid repeatedly creating contexts where one can't be passed
///
/// If the global-context feature is enabled (and not just the global-context-less-secure),
/// this will have been randomized.
/// this will have been randomized for additional defense-in-depth side channel protection.
pub static SECP256K1: &GlobalContext = &GlobalContext { __private: () };

impl Deref for GlobalContext {
type Target = Secp256k1<All>;

#[allow(unused_mut)] // mut is unused when "global-context" is not enabled.
fn deref(&self) -> &Self::Target {
static ONCE: Once = Once::new();
static mut CONTEXT: Option<Secp256k1<All>> = None;
ONCE.call_once(|| unsafe {
let mut ctx = Secp256k1::new();
let mut ctx = Secp256k1::new_no_randomize();
#[cfg(feature = "global-context")]
{
ctx.randomize(&mut rand::thread_rng());
Expand Down Expand Up @@ -166,8 +167,8 @@ mod alloc_only {
}

impl<C: Context> Secp256k1<C> {
/// Lets you create a context in a generic manner(sign/verify/all)
pub fn gen_new() -> Secp256k1<C> {
/// Helper function only intended to be called by other gen_new_* functions.
fn _gen_new() -> Secp256k1<C> {
#[cfg(target_arch = "wasm32")]
ffi::types::sanity_checks_for_wasm();

Expand All @@ -180,32 +181,118 @@ mod alloc_only {
size,
}
}

/// Lets you create a context in a generic manner(sign/verify/all).
///
/// Context is randomized using `thread_rng` for additional defense-in-depth side channel
/// protection.
#[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
pub fn gen_new_randomize() -> Secp256k1<C> {
let mut secp = Secp256k1::_gen_new();
secp.randomize(&mut rand::thread_rng());
secp
}

/// Lets you create a context in a generic manner(sign/verify/all).
///
/// No randomization is done, this context is perfectly safe but for additional
/// defense-in-depth side channel protection consider using `gen_new_seeded_randomize`.
pub fn gen_new_no_randomize() -> Secp256k1<C> {
Secp256k1::_gen_new()
}

/// Lets you create a context in a generic manner(sign/verify/all).
///
/// Context is randomized using `seed` for additional defense-in-depth side channel
/// protection.
pub fn gen_new_seeded_randomize(seed: &[u8; 32]) -> Secp256k1<C> {
let mut secp = Secp256k1::_gen_new();
secp.seeded_randomize(seed);
secp
}
}

impl Secp256k1<All> {
/// Creates a new Secp256k1 context with all capabilities
pub fn new() -> Secp256k1<All> {
Secp256k1::gen_new()
/// Creates a new Secp256k1 context with all capabilities.
///
/// Context is randomized using `thread_rng` for additional defense-in-depth side channel
/// protection.
#[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
pub fn new_randomize() -> Secp256k1<All> {
Secp256k1::gen_new_randomize()
}
/// Creates a new Secp256k1 context with all capabilities.
///
/// No randomization is done, this context is perfectly safe but for additional
/// defense-in-depth side channel protection consider using `new_seeded_randomize`.
pub fn new_no_randomize() -> Secp256k1<All> {
Secp256k1::gen_new_no_randomize()
}

/// Creates a new Secp256k1 context with all capabilities.
///
/// Context is randomized using `seed` for additional defense-in-depth side channel
/// protection.
pub fn new_seeded_randomize(seed: &[u8; 32]) -> Secp256k1<All> {
Secp256k1::gen_new_seeded_randomize(seed)
}
}

impl Secp256k1<SignOnly> {
/// Creates a new Secp256k1 context that can only be used for signing
pub fn signing_only() -> Secp256k1<SignOnly> {
Secp256k1::gen_new()
/// Creates a new Secp256k1 context that can only be used for signing.
///
/// Context is randomized using `thread_rng` for additional defense-in-depth side channel
/// protection.
#[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
pub fn signing_only_randomize() -> Secp256k1<SignOnly> {
Secp256k1::gen_new_randomize()
}

/// Creates a new Secp256k1 context that can only be used for signing.
///
/// No randomization is done, this context is perfectly safe but for additional
/// defense-in-depth side channel protection consider using `signing_only_seeded_randomize`.
pub fn signing_only_no_randomize() -> Secp256k1<SignOnly> {
Secp256k1::gen_new_no_randomize()
}

/// Creates a new Secp256k1 context that can only be used for signing.
///
/// Context is randomized using `seed` for additional defense-in-depth side channel
/// protection.
pub fn signing_only_seeded_randomize(seed: &[u8; 32]) -> Secp256k1<SignOnly> {
Secp256k1::gen_new_seeded_randomize(seed)
}
}

impl Secp256k1<VerifyOnly> {
/// Creates a new Secp256k1 context that can only be used for verification
pub fn verification_only() -> Secp256k1<VerifyOnly> {
Secp256k1::gen_new()
/// Creates a new Secp256k1 context that can only be used for verifying.
///
/// Context is randomized using `thread_rng` for additional defense-in-depth side channel
/// protection.
#[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
pub fn verification_only_randomize() -> Secp256k1<VerifyOnly> {
Secp256k1::gen_new_randomize()
}

/// Creates a new Secp256k1 context that can only be used for verifying.
///
/// No randomization is done, this context is perfectly safe but for additional
/// defense-in-depth side channel protection consider using `verification_only_seeded_randomize`.
pub fn verification_only_no_randomize() -> Secp256k1<VerifyOnly> {
Secp256k1::gen_new_no_randomize()
}
}

impl Default for Secp256k1<All> {
fn default() -> Self {
Self::new()
/// Creates a new Secp256k1 context that can only be used for verifying.
///
/// Context is randomized using `seed` for additional defense-in-depth side channel
/// protection.
pub fn verification_only_seeded_randomize(seed: &[u8; 32]) -> Secp256k1<VerifyOnly> {
Secp256k1::gen_new_seeded_randomize(seed)
}
}

Expand Down
22 changes: 15 additions & 7 deletions src/ecdh.rs
Expand Up @@ -127,10 +127,11 @@ impl SharedSecret {
/// `SharedSecret` can be easily created via the `From` impl from arrays.
/// # Examples
/// ```
/// # #[cfg(any(features = "alloc", feature = "std"))] {
/// # use secp256k1::ecdh::SharedSecret;
/// # use secp256k1::{Secp256k1, PublicKey, SecretKey};
/// # fn sha2(_a: &[u8], _b: &[u8]) -> [u8; 32] {[0u8; 32]}
/// # let secp = Secp256k1::signing_only();
/// # let secp = Secp256k1::signing_only_no_randomize();
/// # let secret_key = SecretKey::from_slice(&[3u8; 32]).unwrap();
/// # let secret_key2 = SecretKey::from_slice(&[7u8; 32]).unwrap();
/// # let public_key = PublicKey::from_secret_key(&secp, &secret_key2);
Expand All @@ -139,7 +140,7 @@ impl SharedSecret {
/// let hash: [u8; 32] = sha2(&x,&y);
/// hash.into()
/// });
///
/// # }
/// ```
pub fn new_with_hash<F>(point: &PublicKey, scalar: &SecretKey, mut hash_function: F) -> SharedSecret
where F: FnMut([u8; 32], [u8; 32]) -> SharedSecret {
Expand Down Expand Up @@ -170,15 +171,20 @@ impl SharedSecret {
#[cfg(test)]
mod tests {
use super::*;
use rand::thread_rng;
use super::super::Secp256k1;

#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test as test;

#[cfg(feature = "rand")]
use rand::thread_rng;

#[cfg(all(feature = "rand", any(features = "alloc", feature = "std")))]
use super::super::Secp256k1;

#[test]
#[cfg(all(feature = "rand", any(features = "alloc", feature = "std")))]
fn ecdh() {
let s = Secp256k1::signing_only();
let s = Secp256k1::signing_only_randomize();
let (sk1, pk1) = s.generate_keypair(&mut thread_rng());
let (sk2, pk2) = s.generate_keypair(&mut thread_rng());

Expand All @@ -190,8 +196,9 @@ mod tests {
}

#[test]
#[cfg(all(feature = "rand", any(features = "alloc", feature = "std")))]
fn ecdh_with_hash() {
let s = Secp256k1::signing_only();
let s = Secp256k1::signing_only_randomize();
let (sk1, pk1) = s.generate_keypair(&mut thread_rng());
let (sk2, pk2) = s.generate_keypair(&mut thread_rng());

Expand All @@ -203,8 +210,9 @@ mod tests {
}

#[test]
#[cfg(all(feature = "rand", any(features = "alloc", feature = "std")))]
fn ecdh_with_hash_callback() {
let s = Secp256k1::signing_only();
let s = Secp256k1::signing_only_randomize();
let (sk1, pk1) = s.generate_keypair(&mut thread_rng());
let expect_result: [u8; 64] = [123; 64];
let mut x_out = [0u8; 32];
Expand Down
8 changes: 4 additions & 4 deletions src/ecdsa/mod.rs
Expand Up @@ -431,11 +431,11 @@ impl<C: Verification> Secp256k1<C> {
/// verify-capable context.
///
/// ```rust
/// # #[cfg(feature="rand")] {
/// # #[cfg(all(feature="rand", any(feature = "alloc", feature = "std")))] {
/// # use secp256k1::rand::rngs::OsRng;
/// # use secp256k1::{Secp256k1, Message, Error};
/// #
/// # let secp = Secp256k1::new();
/// # let secp = Secp256k1::new_randomize();
/// # let mut rng = OsRng::new().expect("OsRng");
/// # let (secret_key, public_key) = secp.generate_keypair(&mut rng);
/// #
Expand All @@ -460,11 +460,11 @@ impl<C: Verification> Secp256k1<C> {
/// verify-capable context.
///
/// ```rust
/// # #[cfg(feature="rand")] {
/// # #[cfg(all(feature="rand", any(feature = "alloc", feature = "std")))] {
/// # use secp256k1::rand::rngs::OsRng;
/// # use secp256k1::{Secp256k1, Message, Error};
/// #
/// # let secp = Secp256k1::new();
/// # let secp = Secp256k1::new_randomize();
/// # let mut rng = OsRng::new().expect("OsRng");
/// # let (secret_key, public_key) = secp.generate_keypair(&mut rng);
/// #
Expand Down
12 changes: 6 additions & 6 deletions src/ecdsa/recovery.rs
Expand Up @@ -213,7 +213,7 @@ mod tests {
fn capabilities() {
let sign = Secp256k1::signing_only();
let vrfy = Secp256k1::verification_only();
let full = Secp256k1::new();
let full = Secp256k1::new_no_randomize();

let mut msg = [0u8; 32];
thread_rng().fill_bytes(&mut msg);
Expand Down Expand Up @@ -244,7 +244,7 @@ mod tests {
#[test]
#[cfg(not(fuzzing))] // fixed sig vectors can't work with fuzz-sigs
fn sign() {
let mut s = Secp256k1::new();
let mut s = Secp256k1::new_no_randomize();
s.randomize(&mut thread_rng());
let one: [u8; 32] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
Expand All @@ -267,7 +267,7 @@ mod tests {

#[test]
fn sign_and_verify_fail() {
let mut s = Secp256k1::new();
let mut s = Secp256k1::new_no_randomize();
s.randomize(&mut thread_rng());

let mut msg = [0u8; 32];
Expand All @@ -290,7 +290,7 @@ mod tests {

#[test]
fn sign_with_recovery() {
let mut s = Secp256k1::new();
let mut s = Secp256k1::new_no_randomize();
s.randomize(&mut thread_rng());

let mut msg = [0u8; 32];
Expand All @@ -306,7 +306,7 @@ mod tests {

#[test]
fn bad_recovery() {
let mut s = Secp256k1::new();
let mut s = Secp256k1::new_no_randomize();
s.randomize(&mut thread_rng());

let msg = Message::from_slice(&[0x55; 32]).unwrap();
Expand Down Expand Up @@ -379,7 +379,7 @@ mod benches {

#[bench]
pub fn bench_recover(bh: &mut Bencher) {
let s = Secp256k1::new();
let s = Secp256k1::new_no_randomize();
let mut msg = [0u8; 32];
thread_rng().fill_bytes(&mut msg);
let msg = Message::from_slice(&msg).unwrap();
Expand Down

0 comments on commit 7d772d3

Please sign in to comment.