secp256k1/
ecdh.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Support for shared secret computations.
4//!
5
6use core::borrow::Borrow;
7use core::{ptr, str};
8
9use secp256k1_sys::types::{c_int, c_uchar, c_void};
10
11use crate::ffi::{self, CPtr};
12use crate::key::{PublicKey, SecretKey};
13use crate::{constants, Error};
14
15// The logic for displaying shared secrets relies on this (see `secret.rs`).
16const SHARED_SECRET_SIZE: usize = constants::SECRET_KEY_SIZE;
17
18/// Enables two parties to create a shared secret without revealing their own secrets.
19///
20/// # Examples
21///
22/// ```
23/// # #[cfg(all(feature = "rand", feature = "std"))] {
24/// # use secp256k1::{rand, ecdh::SharedSecret};
25/// let (sk1, pk1) = secp256k1::generate_keypair(&mut rand::rng());
26/// let (sk2, pk2) = secp256k1::generate_keypair(&mut rand::rng());
27/// let sec1 = SharedSecret::new(&pk2, &sk1);
28/// let sec2 = SharedSecret::new(&pk1, &sk2);
29/// assert_eq!(sec1, sec2);
30/// # }
31// ```
32#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
33pub struct SharedSecret([u8; SHARED_SECRET_SIZE]);
34impl_display_secret!(SharedSecret);
35impl_non_secure_erase!(SharedSecret, 0, [0u8; SHARED_SECRET_SIZE]);
36
37impl SharedSecret {
38    /// Creates a new shared secret from a pubkey and secret key.
39    #[inline]
40    pub fn new(point: &PublicKey, scalar: &SecretKey) -> SharedSecret {
41        let mut buf = [0u8; SHARED_SECRET_SIZE];
42        let res = unsafe {
43            ffi::secp256k1_ecdh(
44                ffi::secp256k1_context_no_precomp,
45                buf.as_mut_ptr(),
46                point.as_c_ptr(),
47                scalar.as_c_ptr(),
48                ffi::secp256k1_ecdh_hash_function_default,
49                ptr::null_mut(),
50            )
51        };
52        debug_assert_eq!(res, 1);
53        SharedSecret(buf)
54    }
55
56    /// Returns the shared secret as a byte value.
57    #[inline]
58    pub fn to_secret_bytes(&self) -> [u8; SHARED_SECRET_SIZE] { self.0 }
59
60    /// Returns the shared secret as a byte value.
61    #[deprecated(since = "TBD", note = "Use `to_secret_bytes` instead.")]
62    #[inline]
63    pub fn secret_bytes(&self) -> [u8; SHARED_SECRET_SIZE] { self.to_secret_bytes() }
64
65    /// Creates a shared secret from `bytes` array.
66    #[inline]
67    pub fn from_bytes(bytes: [u8; SHARED_SECRET_SIZE]) -> SharedSecret { SharedSecret(bytes) }
68
69    /// Creates a shared secret from `bytes` slice.
70    #[deprecated(since = "0.31.0", note = "Use `from_bytes` instead.")]
71    #[inline]
72    pub fn from_slice(bytes: &[u8]) -> Result<SharedSecret, Error> {
73        match bytes.len() {
74            SHARED_SECRET_SIZE => {
75                let mut ret = [0u8; SHARED_SECRET_SIZE];
76                ret[..].copy_from_slice(bytes);
77                Ok(SharedSecret(ret))
78            }
79            _ => Err(Error::InvalidSharedSecret),
80        }
81    }
82}
83
84impl str::FromStr for SharedSecret {
85    type Err = Error;
86    fn from_str(s: &str) -> Result<SharedSecret, Error> {
87        let mut res = [0u8; SHARED_SECRET_SIZE];
88        match crate::from_hex(s, &mut res) {
89            Ok(SHARED_SECRET_SIZE) => Ok(SharedSecret::from_bytes(res)),
90            _ => Err(Error::InvalidSharedSecret),
91        }
92    }
93}
94
95impl Borrow<[u8]> for SharedSecret {
96    fn borrow(&self) -> &[u8] { &self.0 }
97}
98
99impl AsRef<[u8]> for SharedSecret {
100    fn as_ref(&self) -> &[u8] { &self.0 }
101}
102
103/// Creates a shared point from public key and secret key.
104///
105/// **Important: use of a strong cryptographic hash function may be critical to security! Do NOT use
106/// unless you understand cryptographical implications.** If not, use SharedSecret instead.
107///
108/// Can be used like `SharedSecret` but caller is responsible for then hashing the returned buffer.
109/// This allows for the use of a custom hash function since `SharedSecret` uses SHA256.
110///
111/// # Returns
112///
113/// 64 bytes representing the (x,y) co-ordinates of a point on the curve (32 bytes each).
114///
115/// # Examples
116/// ```ignore
117/// use bitcoin_hashes::{Hash, sha512};
118/// use secp256k1::{ecdh, rand, PublicKey, SecretKey};
119///
120/// let (sk1, pk1) = crate::generate_keypair(&mut rand::rng());
121/// let (sk2, pk2) = crate::generate_keypair(&mut rand::rng());
122///
123/// let point1 = ecdh::shared_secret_point(&pk2, &sk1);
124/// let secret1 = sha512::Hash::hash(&point1);
125/// let point2 = ecdh::shared_secret_point(&pk1, &sk2);
126/// let secret2 = sha512::Hash::hash(&point2);
127/// assert_eq!(secret1, secret2)
128/// ```
129pub fn shared_secret_point(point: &PublicKey, scalar: &SecretKey) -> [u8; 64] {
130    let mut xy = [0u8; 64];
131
132    let res = unsafe {
133        ffi::secp256k1_ecdh(
134            ffi::secp256k1_context_no_precomp,
135            xy.as_mut_ptr(),
136            point.as_c_ptr(),
137            scalar.as_c_ptr(),
138            Some(c_callback),
139            ptr::null_mut(),
140        )
141    };
142    // Our callback *always* returns 1.
143    // The scalar was verified to be valid (0 > scalar > group_order) via the type system.
144    debug_assert_eq!(res, 1);
145    xy
146}
147
148unsafe extern "C" fn c_callback(
149    output: *mut c_uchar,
150    x: *const c_uchar,
151    y: *const c_uchar,
152    _data: *mut c_void,
153) -> c_int {
154    ptr::copy_nonoverlapping(x, output, 32);
155    ptr::copy_nonoverlapping(y, output.offset(32), 32);
156    1
157}
158
159#[cfg(feature = "serde")]
160impl ::serde::Serialize for SharedSecret {
161    fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
162        if s.is_human_readable() {
163            let mut buf = [0u8; SHARED_SECRET_SIZE * 2];
164            s.serialize_str(crate::to_hex(&self.0, &mut buf).expect("fixed-size hex serialization"))
165        } else {
166            s.serialize_bytes(self.as_ref())
167        }
168    }
169}
170
171#[cfg(feature = "serde")]
172impl<'de> ::serde::Deserialize<'de> for SharedSecret {
173    fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
174        if d.is_human_readable() {
175            d.deserialize_str(super::serde_util::FromStrVisitor::new(
176                "a hex string representing 32 byte SharedSecret",
177            ))
178        } else {
179            d.deserialize_bytes(super::serde_util::BytesVisitor::new(
180                "raw 32 bytes SharedSecret",
181                |x| x.try_into().map(SharedSecret::from_bytes),
182            ))
183        }
184    }
185}
186
187#[cfg(test)]
188#[allow(unused_imports)]
189mod tests {
190    #[cfg(target_arch = "wasm32")]
191    use wasm_bindgen_test::wasm_bindgen_test as test;
192
193    use super::SharedSecret;
194
195    #[test]
196    fn ecdh() {
197        let (sk1, pk1) = crate::test_random_keypair();
198        let (sk2, pk2) = crate::test_random_keypair();
199
200        let sec1 = SharedSecret::new(&pk2, &sk1);
201        let sec2 = SharedSecret::new(&pk1, &sk2);
202        let sec_odd = SharedSecret::new(&pk1, &sk1);
203        assert_eq!(sec1, sec2);
204        assert!(sec_odd != sec2);
205    }
206
207    #[test]
208    fn test_c_callback() {
209        let x = [5u8; 32];
210        let y = [7u8; 32];
211        let mut output = [0u8; 64];
212        let res = unsafe {
213            super::c_callback(output.as_mut_ptr(), x.as_ptr(), y.as_ptr(), core::ptr::null_mut())
214        };
215        assert_eq!(res, 1);
216        let mut new_x = [0u8; 32];
217        let mut new_y = [0u8; 32];
218        new_x.copy_from_slice(&output[..32]);
219        new_y.copy_from_slice(&output[32..]);
220        assert_eq!(x, new_x);
221        assert_eq!(y, new_y);
222    }
223
224    #[test]
225    #[cfg(all(feature = "serde", feature = "alloc"))]
226    fn serde() {
227        use serde_test::{assert_tokens, Configure, Token};
228        #[rustfmt::skip]
229        static BYTES: [u8; 32] = [
230            1, 1, 1, 1, 1, 1, 1, 1,
231            0, 1, 2, 3, 4, 5, 6, 7,
232            0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0,
233            99, 99, 99, 99, 99, 99, 99, 99
234        ];
235        static STR: &str = "01010101010101010001020304050607ffff0000ffff00006363636363636363";
236
237        let secret = SharedSecret::from_bytes(BYTES);
238
239        assert_tokens(&secret.compact(), &[Token::BorrowedBytes(&BYTES[..])]);
240        assert_tokens(&secret.compact(), &[Token::Bytes(&BYTES)]);
241        assert_tokens(&secret.compact(), &[Token::ByteBuf(&BYTES)]);
242
243        assert_tokens(&secret.readable(), &[Token::BorrowedStr(STR)]);
244        assert_tokens(&secret.readable(), &[Token::Str(STR)]);
245        assert_tokens(&secret.readable(), &[Token::String(STR)]);
246    }
247}
248
249#[cfg(bench)]
250#[cfg(all(feature = "rand", feature = "std"))] // Currently only a single bench that requires "rand" + "std".
251mod benches {
252    use test::{black_box, Bencher};
253
254    use super::SharedSecret;
255
256    #[bench]
257    pub fn bench_ecdh(bh: &mut Bencher) {
258        let (sk, pk) = s.generate_keypair(&mut rand::rng());
259
260        bh.iter(|| {
261            let res = SharedSecret::new(&pk, &sk);
262            black_box(res);
263        });
264    }
265}