Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 2 additions & 21 deletions secp256k1-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ pub type EcdhHashFn = unsafe extern "C" fn(
x: *const c_uchar,
y: *const c_uchar,
data: *mut c_void,
);
) -> c_int;

/// A Secp256k1 context, containing various precomputed values and such
/// needed to do elliptic curve computations. If you create one of these
Expand Down Expand Up @@ -134,25 +134,6 @@ impl Default for Signature {
}
}

/// Library-internal representation of an ECDH shared secret
#[repr(C)]
pub struct SharedSecret([c_uchar; 32]);
impl_array_newtype!(SharedSecret, c_uchar, 32);
impl_raw_debug!(SharedSecret);

impl SharedSecret {
/// Create a new (zeroed) signature usable for the FFI interface
pub fn new() -> SharedSecret { SharedSecret([0; 32]) }
/// Create a new (uninitialized) signature usable for the FFI interface
#[deprecated(since = "0.15.3", note = "Please use the new function instead")]
pub unsafe fn blank() -> SharedSecret { SharedSecret::new() }
}

impl Default for SharedSecret {
fn default() -> Self {
SharedSecret::new()
}
}

#[cfg(not(feature = "fuzztarget"))]
extern "C" {
Expand Down Expand Up @@ -296,7 +277,7 @@ extern "C" {
#[cfg_attr(not(feature = "external-symbols"), link_name = "rustsecp256k1_v0_1_0_ecdh")]
pub fn secp256k1_ecdh(
cx: *const Context,
output: *mut SharedSecret,
output: *mut c_uchar,
pubkey: *const PublicKey,
privkey: *const c_uchar,
hashfp: EcdhHashFn,
Expand Down
1 change: 1 addition & 0 deletions secp256k1-sys/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ macro_rules! impl_array_newtype {
}
}

#[macro_export]
macro_rules! impl_raw_debug {
($thing:ident) => {
impl ::core::fmt::Debug for $thing {
Expand Down
164 changes: 115 additions & 49 deletions src/ecdh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Copy link
Copy Markdown
Member

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

}
}

#[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);
Comment thread
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>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 set_len (or just copying into a slice and truncating) on the result.

ss.set_len(res as usize);
ss
}
}

Expand Down
17 changes: 17 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,23 @@ macro_rules! impl_pretty_debug {
}
}

macro_rules! impl_from_array_len {
($thing:ident, $capacity:tt, ($($N:tt)+)) => {
$(
impl From<[u8; $N]> for $thing {
fn from(arr: [u8; $N]) -> Self {
let mut data = [0u8; $capacity];
data[..$N].copy_from_slice(&arr);
$thing {
data,
len: $N,
}
}
}
)+
}
}

#[cfg(feature="serde")]
/// Implements `Serialize` and `Deserialize` for a type `$t` which represents
/// a newtype over a byte-slice over length `$len`. Type `$t` must implement
Expand Down