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, Secp256k1};
25/// # use secp256k1::ecdh::SharedSecret;
26/// let s = Secp256k1::new();
27/// let (sk1, pk1) = s.generate_keypair(&mut rand::thread_rng());
28/// let (sk2, pk2) = s.generate_keypair(&mut rand::thread_rng());
29/// let sec1 = SharedSecret::new(&pk2, &sk1);
30/// let sec2 = SharedSecret::new(&pk1, &sk2);
31/// assert_eq!(sec1, sec2);
32/// # }
33// ```
34#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
35pub struct SharedSecret([u8; SHARED_SECRET_SIZE]);
36impl_display_secret!(SharedSecret);
37impl_non_secure_erase!(SharedSecret, 0, [0u8; SHARED_SECRET_SIZE]);
38
39impl SharedSecret {
40    /// Creates a new shared secret from a pubkey and secret key.
41    #[inline]
42    pub fn new(point: &PublicKey, scalar: &SecretKey) -> SharedSecret {
43        let mut buf = [0u8; SHARED_SECRET_SIZE];
44        let res = unsafe {
45            ffi::secp256k1_ecdh(
46                ffi::secp256k1_context_no_precomp,
47                buf.as_mut_ptr(),
48                point.as_c_ptr(),
49                scalar.as_c_ptr(),
50                ffi::secp256k1_ecdh_hash_function_default,
51                ptr::null_mut(),
52            )
53        };
54        debug_assert_eq!(res, 1);
55        SharedSecret(buf)
56    }
57
58    /// Returns the shared secret as a byte value.
59    #[inline]
60    pub fn secret_bytes(&self) -> [u8; SHARED_SECRET_SIZE] { self.0 }
61
62    /// Creates a shared secret from `bytes` array.
63    #[inline]
64    pub fn from_bytes(bytes: [u8; SHARED_SECRET_SIZE]) -> SharedSecret { SharedSecret(bytes) }
65
66    /// Creates a shared secret from `bytes` slice.
67    #[deprecated(since = "TBD", note = "Use `from_bytes` instead.")]
68    #[inline]
69    pub fn from_slice(bytes: &[u8]) -> Result<SharedSecret, Error> {
70        match bytes.len() {
71            SHARED_SECRET_SIZE => {
72                let mut ret = [0u8; SHARED_SECRET_SIZE];
73                ret[..].copy_from_slice(bytes);
74                Ok(SharedSecret(ret))
75            }
76            _ => Err(Error::InvalidSharedSecret),
77        }
78    }
79}
80
81impl str::FromStr for SharedSecret {
82    type Err = Error;
83    fn from_str(s: &str) -> Result<SharedSecret, Error> {
84        let mut res = [0u8; SHARED_SECRET_SIZE];
85        match crate::from_hex(s, &mut res) {
86            Ok(SHARED_SECRET_SIZE) => Ok(SharedSecret::from_bytes(res)),
87            _ => Err(Error::InvalidSharedSecret),
88        }
89    }
90}
91
92impl Borrow<[u8]> for SharedSecret {
93    fn borrow(&self) -> &[u8] { &self.0 }
94}
95
96impl AsRef<[u8]> for SharedSecret {
97    fn as_ref(&self) -> &[u8] { &self.0 }
98}
99
100/// Creates a shared point from public key and secret key.
101///
102/// **Important: use of a strong cryptographic hash function may be critical to security! Do NOT use
103/// unless you understand cryptographical implications.** If not, use SharedSecret instead.
104///
105/// Can be used like `SharedSecret` but caller is responsible for then hashing the returned buffer.
106/// This allows for the use of a custom hash function since `SharedSecret` uses SHA256.
107///
108/// # Returns
109///
110/// 64 bytes representing the (x,y) co-ordinates of a point on the curve (32 bytes each).
111///
112/// # Examples
113/// ```
114/// # #[cfg(all(feature = "hashes", feature = "rand", feature = "std"))] {
115/// # use secp256k1::{ecdh, rand, Secp256k1, PublicKey, SecretKey};
116/// # use secp256k1::hashes::{Hash, sha512};
117///
118/// let s = Secp256k1::new();
119/// let (sk1, pk1) = s.generate_keypair(&mut rand::thread_rng());
120/// let (sk2, pk2) = s.generate_keypair(&mut rand::thread_rng());
121///
122/// let point1 = ecdh::shared_secret_point(&pk2, &sk1);
123/// let secret1 = sha512::Hash::hash(&point1);
124/// let point2 = ecdh::shared_secret_point(&pk1, &sk2);
125/// let secret2 = sha512::Hash::hash(&point2);
126/// assert_eq!(secret1, secret2)
127/// # }
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                SharedSecret::from_slice,
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    use crate::Secp256k1;
195
196    #[test]
197    #[cfg(all(feature = "rand", feature = "std"))]
198    fn ecdh() {
199        let s = Secp256k1::signing_only();
200        let (sk1, pk1) = s.generate_keypair(&mut rand::thread_rng());
201        let (sk2, pk2) = s.generate_keypair(&mut rand::thread_rng());
202
203        let sec1 = SharedSecret::new(&pk2, &sk1);
204        let sec2 = SharedSecret::new(&pk1, &sk2);
205        let sec_odd = SharedSecret::new(&pk1, &sk1);
206        assert_eq!(sec1, sec2);
207        assert!(sec_odd != sec2);
208    }
209
210    #[test]
211    fn test_c_callback() {
212        let x = [5u8; 32];
213        let y = [7u8; 32];
214        let mut output = [0u8; 64];
215        let res = unsafe {
216            super::c_callback(output.as_mut_ptr(), x.as_ptr(), y.as_ptr(), core::ptr::null_mut())
217        };
218        assert_eq!(res, 1);
219        let mut new_x = [0u8; 32];
220        let mut new_y = [0u8; 32];
221        new_x.copy_from_slice(&output[..32]);
222        new_y.copy_from_slice(&output[32..]);
223        assert_eq!(x, new_x);
224        assert_eq!(y, new_y);
225    }
226
227    #[test]
228    #[cfg(not(secp256k1_fuzz))]
229    #[cfg(all(feature = "hashes", feature = "rand", feature = "std"))]
230    fn hashes_and_sys_generate_same_secret() {
231        use hashes::{sha256, Hash, HashEngine};
232
233        use crate::ecdh::shared_secret_point;
234
235        let s = Secp256k1::signing_only();
236        let (sk1, _) = s.generate_keypair(&mut rand::thread_rng());
237        let (_, pk2) = s.generate_keypair(&mut rand::thread_rng());
238
239        let secret_sys = SharedSecret::new(&pk2, &sk1);
240
241        let xy = shared_secret_point(&pk2, &sk1);
242
243        // Mimics logic in `bitcoin-core/secp256k1/src/module/main_impl.h`
244        let version = (xy[63] & 0x01) | 0x02;
245        let mut engine = sha256::HashEngine::default();
246        engine.input(&[version]);
247        engine.input(&xy.as_ref()[..32]);
248        let secret_bh = sha256::Hash::from_engine(engine);
249
250        assert_eq!(secret_bh.as_byte_array(), secret_sys.as_ref());
251    }
252
253    #[test]
254    #[cfg(all(feature = "serde", feature = "alloc"))]
255    fn serde() {
256        use serde_test::{assert_tokens, Configure, Token};
257        #[rustfmt::skip]
258        static BYTES: [u8; 32] = [
259            1, 1, 1, 1, 1, 1, 1, 1,
260            0, 1, 2, 3, 4, 5, 6, 7,
261            0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0,
262            99, 99, 99, 99, 99, 99, 99, 99
263        ];
264        static STR: &str = "01010101010101010001020304050607ffff0000ffff00006363636363636363";
265
266        let secret = SharedSecret::from_slice(&BYTES).unwrap();
267
268        assert_tokens(&secret.compact(), &[Token::BorrowedBytes(&BYTES[..])]);
269        assert_tokens(&secret.compact(), &[Token::Bytes(&BYTES)]);
270        assert_tokens(&secret.compact(), &[Token::ByteBuf(&BYTES)]);
271
272        assert_tokens(&secret.readable(), &[Token::BorrowedStr(STR)]);
273        assert_tokens(&secret.readable(), &[Token::Str(STR)]);
274        assert_tokens(&secret.readable(), &[Token::String(STR)]);
275    }
276}
277
278#[cfg(bench)]
279#[cfg(all(feature = "rand", feature = "std"))] // Currently only a single bench that requires "rand" + "std".
280mod benches {
281    use test::{black_box, Bencher};
282
283    use super::SharedSecret;
284    use crate::Secp256k1;
285
286    #[bench]
287    pub fn bench_ecdh(bh: &mut Bencher) {
288        let s = Secp256k1::signing_only();
289        let (sk, pk) = s.generate_keypair(&mut rand::thread_rng());
290
291        bh.iter(|| {
292            let res = SharedSecret::new(&pk, &sk);
293            black_box(res);
294        });
295    }
296}