-
Notifications
You must be signed in to change notification settings - Fork 316
Alternative: Passing custom hash functions to ECDH #180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
9759cb0
af8fa21
f804282
ca8ea92
124c1f3
5619f2a
92c42ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,83 +16,149 @@ | |
| //! Support for shared secret computations | ||
| //! | ||
|
|
||
| use core::{ops, ptr}; | ||
| use core::ptr; | ||
| use core::ops::{FnMut, Deref}; | ||
|
|
||
| use key::{SecretKey, PublicKey}; | ||
| use ffi::{self, CPtr}; | ||
| use secp256k1_sys::types::{c_int, c_uchar, c_void}; | ||
|
|
||
| /// A tag used for recovering the public key from a compact signature | ||
| #[derive(Copy, Clone, PartialEq, Eq, Debug)] | ||
| pub struct SharedSecret(ffi::SharedSecret); | ||
| #[derive(Copy, Clone)] | ||
| pub struct SharedSecret { | ||
| data: [u8; 256], | ||
| len: usize, | ||
| } | ||
| impl_raw_debug!(SharedSecret); | ||
|
|
||
|
|
||
| // This implementes `From<N>` for all `[u8; N]` arrays from 128bits(16 byte) to 2048bits allowing known hash lengths. | ||
| // Lower than 128 bits isn't resistant to collisions any more. | ||
| impl_from_array_len!(SharedSecret, 256, (16 20 28 32 48 64 96 128 256)); | ||
|
|
||
| impl SharedSecret { | ||
| /// Creates a new shared secret from a pubkey and secret key | ||
| #[inline] | ||
| pub fn new(point: &PublicKey, scalar: &SecretKey) -> SharedSecret { | ||
| unsafe { | ||
| let mut ss = ffi::SharedSecret::new(); | ||
| let res = ffi::secp256k1_ecdh( | ||
| ffi::secp256k1_context_no_precomp, | ||
| &mut ss, | ||
| point.as_c_ptr(), | ||
| scalar.as_c_ptr(), | ||
| ffi::secp256k1_ecdh_hash_function_default, | ||
| ptr::null_mut(), | ||
| ); | ||
| debug_assert_eq!(res, 1); | ||
| SharedSecret(ss) | ||
|
|
||
| /// Create an empty SharedSecret | ||
| pub(crate) fn empty() -> SharedSecret { | ||
| SharedSecret { | ||
| data: [0u8; 256], | ||
| len: 0, | ||
| } | ||
| } | ||
|
|
||
| /// Obtains a raw pointer suitable for use with FFI functions | ||
| #[inline] | ||
| pub fn as_ptr(&self) -> *const ffi::SharedSecret { | ||
| &self.0 as *const _ | ||
| /// Get a pointer to the underlying data with the specified capacity. | ||
| pub(crate) fn get_data_mut_ptr(&mut self) -> *mut u8 { | ||
| self.data.as_mut_ptr() | ||
| } | ||
| } | ||
|
|
||
| /// Creates a new shared secret from a FFI shared secret | ||
| impl From<ffi::SharedSecret> for SharedSecret { | ||
| #[inline] | ||
| fn from(ss: ffi::SharedSecret) -> SharedSecret { | ||
| SharedSecret(ss) | ||
| /// Get the capacity of the underlying data buffer. | ||
| pub fn capacity(&self) -> usize { | ||
| self.data.len() | ||
| } | ||
| } | ||
|
|
||
| /// Get the len of the used data. | ||
| pub fn len(&self) -> usize { | ||
| self.len | ||
| } | ||
|
|
||
| impl ops::Index<usize> for SharedSecret { | ||
| type Output = u8; | ||
| /// Set the length of the object. | ||
| pub(crate) fn set_len(&mut self, len: usize) { | ||
| self.len = len; | ||
| } | ||
| } | ||
|
|
||
| #[inline] | ||
| fn index(&self, index: usize) -> &u8 { | ||
| &self.0[index] | ||
| impl PartialEq for SharedSecret { | ||
| fn eq(&self, other: &SharedSecret) -> bool { | ||
| self.as_ref() == other.as_ref() | ||
| } | ||
| } | ||
|
|
||
| impl ops::Index<ops::Range<usize>> for SharedSecret { | ||
| type Output = [u8]; | ||
| impl AsRef<[u8]> for SharedSecret { | ||
| fn as_ref(&self) -> &[u8] { | ||
| &self.data[..self.len] | ||
| } | ||
| } | ||
|
|
||
| #[inline] | ||
| fn index(&self, index: ops::Range<usize>) -> &[u8] { | ||
| &self.0[index] | ||
| impl Deref for SharedSecret { | ||
| type Target = [u8]; | ||
| fn deref(&self) -> &[u8] { | ||
| &self.data[..self.len] | ||
| } | ||
| } | ||
|
|
||
| impl ops::Index<ops::RangeFrom<usize>> for SharedSecret { | ||
| type Output = [u8]; | ||
|
|
||
| #[inline] | ||
| fn index(&self, index: ops::RangeFrom<usize>) -> &[u8] { | ||
| &self.0[index.start..] | ||
| } | ||
| unsafe extern "C" fn hash_callback<F>(output: *mut c_uchar, x: *const c_uchar, y: *const c_uchar, data: *mut c_void) -> c_int | ||
| where F: FnMut([u8; 32], [u8; 32]) -> SharedSecret { | ||
| let callback: &mut F = &mut *(data as *mut F); | ||
|
|
||
| let mut x_arr = [0; 32]; | ||
| let mut y_arr = [0; 32]; | ||
| ptr::copy_nonoverlapping(x, x_arr.as_mut_ptr(), 32); | ||
| ptr::copy_nonoverlapping(y, y_arr.as_mut_ptr(), 32); | ||
|
elichai marked this conversation as resolved.
|
||
|
|
||
| let secret = callback(x_arr, y_arr); | ||
| ptr::copy_nonoverlapping(secret.as_ptr(), output as *mut u8, secret.len()); | ||
|
|
||
| secret.len() as c_int | ||
| } | ||
|
|
||
| impl ops::Index<ops::RangeFull> for SharedSecret { | ||
| type Output = [u8]; | ||
|
|
||
| impl SharedSecret { | ||
| /// Creates a new shared secret from a pubkey and secret key | ||
| #[inline] | ||
| fn index(&self, _: ops::RangeFull) -> &[u8] { | ||
| &self.0[..] | ||
| pub fn new(point: &PublicKey, scalar: &SecretKey) -> SharedSecret { | ||
| let mut ss = SharedSecret::empty(); | ||
| let res = unsafe { | ||
| ffi::secp256k1_ecdh( | ||
| ffi::secp256k1_context_no_precomp, | ||
| ss.get_data_mut_ptr(), | ||
| point.as_c_ptr(), | ||
| scalar.as_c_ptr(), | ||
| ffi::secp256k1_ecdh_hash_function_default, | ||
| ptr::null_mut(), | ||
| ) | ||
| }; | ||
| debug_assert_eq!(res, 1); // The default `secp256k1_ecdh_hash_function_default` should always return 1. | ||
| ss.set_len(32); // The default hash function is SHA256, which is 32 bytes long. | ||
| ss | ||
| } | ||
|
|
||
| /// Creates a new shared secret from a pubkey and secret key with applied custom hash function | ||
| /// # Examples | ||
| /// ``` | ||
| /// # 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 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); | ||
| /// | ||
| /// let secret = SharedSecret::new_with_hash(&public_key, &secret_key, |x,y| { | ||
| /// 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 | ||
| { | ||
| let mut ss = SharedSecret::empty(); | ||
| let hashfp: ffi::EcdhHashFn = hash_callback::<F>; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is really a fascinating use of Rust's type system. I did not know that generics and FFI would be allowed to interact like this. |
||
|
|
||
| let res = unsafe { | ||
| ffi::secp256k1_ecdh( | ||
| ffi::secp256k1_context_no_precomp, | ||
| ss.get_data_mut_ptr(), | ||
| point.as_ptr(), | ||
| scalar.as_ptr(), | ||
| hashfp, | ||
| &mut hash_function as *mut F as *mut c_void, | ||
| ) | ||
| }; | ||
| debug_assert!(res >= 16); // 128 bit is the minimum for a secure hash function and the minimum we let users. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like this, though of course users are welcome to bypass it by calling |
||
| ss.set_len(res as usize); | ||
| ss | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this should be marked
unsafe. It is not technically unsafe, in that we're using safe code so it won't allow access to uninitialized memory or OOB access, but it seems a bit footgunny