// Bitcoin secp256k1 bindings // Written in 2015 by // Andrew Poelstra // // To the extent possible under law, the author(s) have dedicated all // copyright and related and neighboring rights to this software to // the public domain worldwide. This software is distributed without // any warranty. // // You should have received a copy of the CC0 Public Domain Dedication // along with this software. // If not, see . // //! Support for shared secret computations. //! use core::ptr; use core::borrow::Borrow; use key::{SecretKey, PublicKey}; use ffi::{self, CPtr}; use secp256k1_sys::types::{c_int, c_uchar, c_void}; use constants; // The logic for displaying shared secrets relies on this (see `secret.rs`). const SHARED_SECRET_SIZE: usize = constants::SECRET_KEY_SIZE; /// Enables two parties to create a shared secret without revealing their own secrets. /// /// # Examples /// /// ``` /// # #[cfg(all(feature = "std", feature = "rand-std"))] { /// # use secp256k1::Secp256k1; /// # use secp256k1::ecdh::SharedSecret; /// # use secp256k1::rand::thread_rng; /// let s = Secp256k1::new(); /// let (sk1, pk1) = s.generate_keypair(&mut thread_rng()); /// let (sk2, pk2) = s.generate_keypair(&mut thread_rng()); /// let sec1 = SharedSecret::new(&pk2, &sk1); /// let sec2 = SharedSecret::new(&pk1, &sk2); /// assert_eq!(sec1, sec2); /// # } // ``` #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SharedSecret([u8; SHARED_SECRET_SIZE]); impl_display_secret!(SharedSecret); impl SharedSecret { /// Creates a new shared secret from a pubkey and secret key. #[inline] pub fn new(point: &PublicKey, scalar: &SecretKey) -> SharedSecret { let mut buf = [0u8; SHARED_SECRET_SIZE]; let res = unsafe { ffi::secp256k1_ecdh( ffi::secp256k1_context_no_precomp, buf.as_mut_ptr(), point.as_c_ptr(), scalar.as_c_ptr(), ffi::secp256k1_ecdh_hash_function_default, ptr::null_mut(), ) }; debug_assert_eq!(res, 1); SharedSecret(buf) } /// Returns the shared secret as a byte value. #[inline] pub fn secret_bytes(&self) -> [u8; SHARED_SECRET_SIZE] { self.0 } } impl Borrow<[u8]> for SharedSecret { fn borrow(&self) -> &[u8] { &self.0 } } impl AsRef<[u8]> for SharedSecret { fn as_ref(&self) -> &[u8] { &self.0 } } /// Creates a shared point from public key and secret key. /// /// **Important: use of a strong cryptographic hash function may be critical to security! Do NOT use /// unless you understand cryptographical implications.** If not, use SharedSecret instead. /// /// Can be used like `SharedSecret` but caller is responsible for then hashing the returned buffer. /// This allows for the use of a custom hash function since `SharedSecret` uses SHA256. /// /// # Returns /// /// 64 bytes representing the (x,y) co-ordinates of a point on the curve (32 bytes each). /// /// # Examples /// ``` /// # #[cfg(all(feature = "bitcoin_hashes", feature = "rand-std", feature = "std"))] { /// # use secp256k1::{ecdh, Secp256k1, PublicKey, SecretKey}; /// # use secp256k1::hashes::{Hash, sha512}; /// # use secp256k1::rand::thread_rng; /// /// let s = Secp256k1::new(); /// let (sk1, pk1) = s.generate_keypair(&mut thread_rng()); /// let (sk2, pk2) = s.generate_keypair(&mut thread_rng()); /// /// let point1 = ecdh::shared_secret_point(&pk2, &sk1); /// let secret1 = sha512::Hash::hash(&point1); /// let point2 = ecdh::shared_secret_point(&pk1, &sk2); /// let secret2 = sha512::Hash::hash(&point2); /// assert_eq!(secret1, secret2) /// # } /// ``` pub fn shared_secret_point(point: &PublicKey, scalar: &SecretKey) -> [u8; 64] { let mut xy = [0u8; 64]; let res = unsafe { ffi::secp256k1_ecdh( ffi::secp256k1_context_no_precomp, xy.as_mut_ptr(), point.as_ptr(), scalar.as_ptr(), Some(c_callback), ptr::null_mut(), ) }; // Our callback *always* returns 1. // The scalar was verified to be valid (0 > scalar > group_order) via the type system. debug_assert_eq!(res, 1); xy } unsafe extern "C" fn c_callback(output: *mut c_uchar, x: *const c_uchar, y: *const c_uchar, _data: *mut c_void) -> c_int { ptr::copy_nonoverlapping(x, output, 32); ptr::copy_nonoverlapping(y, output.offset(32), 32); 1 } #[cfg(test)] #[allow(unused_imports)] 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; #[test] #[cfg(all(feature="rand-std", any(feature = "alloc", feature = "std")))] fn ecdh() { let s = Secp256k1::signing_only(); let (sk1, pk1) = s.generate_keypair(&mut thread_rng()); let (sk2, pk2) = s.generate_keypair(&mut thread_rng()); let sec1 = SharedSecret::new(&pk2, &sk1); let sec2 = SharedSecret::new(&pk1, &sk2); let sec_odd = SharedSecret::new(&pk1, &sk1); assert_eq!(sec1, sec2); assert!(sec_odd != sec2); } #[test] fn test_c_callback() { let x = [5u8; 32]; let y = [7u8; 32]; let mut output = [0u8; 64]; let res = unsafe { super::c_callback(output.as_mut_ptr(), x.as_ptr(), y.as_ptr(), ptr::null_mut()) }; assert_eq!(res, 1); let mut new_x = [0u8; 32]; let mut new_y = [0u8; 32]; new_x.copy_from_slice(&output[..32]); new_y.copy_from_slice(&output[32..]); assert_eq!(x, new_x); assert_eq!(y, new_y); } #[test] #[cfg(not(fuzzing))] #[cfg(all(feature="rand-std", feature = "std", feature = "bitcoin_hashes"))] fn bitcoin_hashes_and_sys_generate_same_secret() { use hashes::{sha256, Hash, HashEngine}; let s = Secp256k1::signing_only(); let (sk1, _) = s.generate_keypair(&mut thread_rng()); let (_, pk2) = s.generate_keypair(&mut thread_rng()); let secret_sys = SharedSecret::new(&pk2, &sk1); let xy = shared_secret_point(&pk2, &sk1); // Mimics logic in `bitcoin-core/secp256k1/src/module/main_impl.h` let version = (xy[63] & 0x01) | 0x02; let mut engine = sha256::HashEngine::default(); engine.input(&[version]); engine.input(&xy.as_ref()[..32]); let secret_bh = sha256::Hash::from_engine(engine); assert_eq!(secret_bh.as_inner(), secret_sys.as_ref()); } } #[cfg(all(test, feature = "unstable"))] mod benches { use rand::thread_rng; use test::{Bencher, black_box}; use super::SharedSecret; use super::super::Secp256k1; #[bench] pub fn bench_ecdh(bh: &mut Bencher) { let s = Secp256k1::signing_only(); let (sk, pk) = s.generate_keypair(&mut thread_rng()); bh.iter( || { let res = SharedSecret::new(&pk, &sk); black_box(res); }); } }