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 functions that make explicit the randomization of the context during
construction.

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 5d063c3 commit e35b02c
Show file tree
Hide file tree
Showing 12 changed files with 248 additions and 125 deletions.
2 changes: 1 addition & 1 deletion examples/generate_keys.rs
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
120 changes: 103 additions & 17 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ 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 {
Expand All @@ -40,7 +40,7 @@ pub mod global {
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 @@ -167,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 @@ -181,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-std")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand-std")))]
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-std")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand-std")))]
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-std")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand-std")))]
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-std")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand-std")))]
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
26 changes: 17 additions & 9 deletions src/ecdh.rs
Original file line number Diff line number Diff line change
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-std")]
use rand::thread_rng;

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

#[test]
#[cfg(all(feature = "rand-std", 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-std", 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-std", 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 Expand Up @@ -235,7 +243,7 @@ mod tests {
}
}

#[cfg(all(test, feature = "unstable", feature = "rand", any(features = "alloc", feature = "std")))]
#[cfg(all(test, feature = "unstable", feature = "rand-std", any(features = "alloc", feature = "std")))]
mod benches {
use rand::thread_rng;
use test::{Bencher, black_box};
Expand All @@ -245,7 +253,7 @@ mod benches {

#[bench]
pub fn bench_ecdh(bh: &mut Bencher) {
let s = Secp256k1::signing_only();
let s = Secp256k1::signing_only_randomize();
let (sk, pk) = s.generate_keypair(&mut thread_rng());

bh.iter( || {
Expand Down
8 changes: 4 additions & 4 deletions src/ecdsa/mod.rs
Original file line number Diff line number Diff line change
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
25 changes: 13 additions & 12 deletions src/ecdsa/recovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,11 @@ mod tests {
use wasm_bindgen_test::wasm_bindgen_test as test;

#[test]
#[cfg(all(feature = "rand-std", any(features = "alloc", feature = "std")))]
fn capabilities() {
let sign = Secp256k1::signing_only();
let vrfy = Secp256k1::verification_only();
let full = Secp256k1::new();
let sign = Secp256k1::signing_only_randomize();
let vrfy = Secp256k1::verification_only_randomize();
let full = Secp256k1::new_randomize();

let mut msg = [0u8; 32];
thread_rng().fill_bytes(&mut msg);
Expand Down Expand Up @@ -243,9 +244,9 @@ mod tests {

#[test]
#[cfg(not(fuzzing))] // fixed sig vectors can't work with fuzz-sigs
#[cfg(all(feature = "rand-std", any(features = "alloc", feature = "std")))]
fn sign() {
let mut s = Secp256k1::new();
s.randomize(&mut thread_rng());
let s = Secp256k1::new_randomize();
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 @@ -266,9 +267,9 @@ mod tests {
}

#[test]
#[cfg(all(feature = "rand-std", any(features = "alloc", feature = "std")))]
fn sign_and_verify_fail() {
let mut s = Secp256k1::new();
s.randomize(&mut thread_rng());
let s = Secp256k1::new_randomize();

let mut msg = [0u8; 32];
thread_rng().fill_bytes(&mut msg);
Expand All @@ -289,9 +290,9 @@ mod tests {
}

#[test]
#[cfg(all(feature = "rand-std", any(features = "alloc", feature = "std")))]
fn sign_with_recovery() {
let mut s = Secp256k1::new();
s.randomize(&mut thread_rng());
let s = Secp256k1::new_randomize();

let mut msg = [0u8; 32];
thread_rng().fill_bytes(&mut msg);
Expand All @@ -305,9 +306,9 @@ mod tests {
}

#[test]
#[cfg(all(feature = "rand-std", any(features = "alloc", feature = "std")))]
fn bad_recovery() {
let mut s = Secp256k1::new();
s.randomize(&mut thread_rng());
let s = Secp256k1::new_randomize();

let msg = Message::from_slice(&[0x55; 32]).unwrap();

Expand Down Expand Up @@ -379,7 +380,7 @@ mod benches {

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

0 comments on commit e35b02c

Please sign in to comment.