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(feature = "rand-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    #[inline]
68    pub fn from_slice(bytes: &[u8]) -> Result<SharedSecret, Error> {
69        match bytes.len() {
70            SHARED_SECRET_SIZE => {
71                let mut ret = [0u8; SHARED_SECRET_SIZE];
72                ret[..].copy_from_slice(bytes);
73                Ok(SharedSecret(ret))
74            }
75            _ => Err(Error::InvalidSharedSecret),
76        }
77    }
78}
79
80impl str::FromStr for SharedSecret {
81    type Err = Error;
82    fn from_str(s: &str) -> Result<SharedSecret, Error> {
83        let mut res = [0u8; SHARED_SECRET_SIZE];
84        match crate::from_hex(s, &mut res) {
85            Ok(SHARED_SECRET_SIZE) => Ok(SharedSecret::from_bytes(res)),
86            _ => Err(Error::InvalidSharedSecret),
87        }
88    }
89}
90
91impl Borrow<[u8]> for SharedSecret {
92    fn borrow(&self) -> &[u8] { &self.0 }
93}
94
95impl AsRef<[u8]> for SharedSecret {
96    fn as_ref(&self) -> &[u8] { &self.0 }
97}
98
99/// Creates a shared point from public key and secret key.
100///
101/// **Important: use of a strong cryptographic hash function may be critical to security! Do NOT use
102/// unless you understand cryptographical implications.** If not, use SharedSecret instead.
103///
104/// Can be used like `SharedSecret` but caller is responsible for then hashing the returned buffer.
105/// This allows for the use of a custom hash function since `SharedSecret` uses SHA256.
106///
107/// # Returns
108///
109/// 64 bytes representing the (x,y) co-ordinates of a point on the curve (32 bytes each).
110///
111/// # Examples
112/// ```
113/// # #[cfg(all(feature = "hashes-std", feature = "rand-std"))] {
114/// # use secp256k1::{ecdh, rand, Secp256k1, PublicKey, SecretKey};
115/// # use secp256k1::hashes::{Hash, sha512};
116///
117/// let s = Secp256k1::new();
118/// let (sk1, pk1) = s.generate_keypair(&mut rand::thread_rng());
119/// let (sk2, pk2) = s.generate_keypair(&mut rand::thread_rng());
120///
121/// let point1 = ecdh::shared_secret_point(&pk2, &sk1);
122/// let secret1 = sha512::Hash::hash(&point1);
123/// let point2 = ecdh::shared_secret_point(&pk1, &sk2);
124/// let secret2 = sha512::Hash::hash(&point2);
125/// assert_eq!(secret1, secret2)
126/// # }
127/// ```
128pub fn shared_secret_point(point: &PublicKey, scalar: &SecretKey) -> [u8; 64] {
129    let mut xy = [0u8; 64];
130
131    let res = unsafe {
132        ffi::secp256k1_ecdh(
133            ffi::secp256k1_context_no_precomp,
134            xy.as_mut_ptr(),
135            point.as_c_ptr(),
136            scalar.as_c_ptr(),
137            Some(c_callback),
138            ptr::null_mut(),
139        )
140    };
141    // Our callback *always* returns 1.
142    // The scalar was verified to be valid (0 > scalar > group_order) via the type system.
143    debug_assert_eq!(res, 1);
144    xy
145}
146
147unsafe extern "C" fn c_callback(
148    output: *mut c_uchar,
149    x: *const c_uchar,
150    y: *const c_uchar,
151    _data: *mut c_void,
152) -> c_int {
153    ptr::copy_nonoverlapping(x, output, 32);
154    ptr::copy_nonoverlapping(y, output.offset(32), 32);
155    1
156}
157
158#[cfg(feature = "serde")]
159impl ::serde::Serialize for SharedSecret {
160    fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
161        if s.is_human_readable() {
162            let mut buf = [0u8; SHARED_SECRET_SIZE * 2];
163            s.serialize_str(crate::to_hex(&self.0, &mut buf).expect("fixed-size hex serialization"))
164        } else {
165            s.serialize_bytes(self.as_ref())
166        }
167    }
168}
169
170#[cfg(feature = "serde")]
171impl<'de> ::serde::Deserialize<'de> for SharedSecret {
172    fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
173        if d.is_human_readable() {
174            d.deserialize_str(super::serde_util::FromStrVisitor::new(
175                "a hex string representing 32 byte SharedSecret",
176            ))
177        } else {
178            d.deserialize_bytes(super::serde_util::BytesVisitor::new(
179                "raw 32 bytes SharedSecret",
180                SharedSecret::from_slice,
181            ))
182        }
183    }
184}
185
186#[cfg(test)]
187#[allow(unused_imports)]
188mod tests {
189    #[cfg(target_arch = "wasm32")]
190    use wasm_bindgen_test::wasm_bindgen_test as test;
191
192    use super::SharedSecret;
193    use crate::Secp256k1;
194
195    #[test]
196    #[cfg(feature = "rand-std")]
197    fn ecdh() {
198        let s = Secp256k1::signing_only();
199        let (sk1, pk1) = s.generate_keypair(&mut rand::thread_rng());
200        let (sk2, pk2) = s.generate_keypair(&mut rand::thread_rng());
201
202        let sec1 = SharedSecret::new(&pk2, &sk1);
203        let sec2 = SharedSecret::new(&pk1, &sk2);
204        let sec_odd = SharedSecret::new(&pk1, &sk1);
205        assert_eq!(sec1, sec2);
206        assert!(sec_odd != sec2);
207    }
208
209    #[test]
210    fn test_c_callback() {
211        let x = [5u8; 32];
212        let y = [7u8; 32];
213        let mut output = [0u8; 64];
214        let res = unsafe {
215            super::c_callback(output.as_mut_ptr(), x.as_ptr(), y.as_ptr(), core::ptr::null_mut())
216        };
217        assert_eq!(res, 1);
218        let mut new_x = [0u8; 32];
219        let mut new_y = [0u8; 32];
220        new_x.copy_from_slice(&output[..32]);
221        new_y.copy_from_slice(&output[32..]);
222        assert_eq!(x, new_x);
223        assert_eq!(y, new_y);
224    }
225
226    #[test]
227    #[cfg(not(secp256k1_fuzz))]
228    #[cfg(all(feature = "hashes-std", feature = "rand-std"))]
229    fn hashes_and_sys_generate_same_secret() {
230        use hashes::{sha256, Hash, HashEngine};
231
232        use crate::ecdh::shared_secret_point;
233
234        let s = Secp256k1::signing_only();
235        let (sk1, _) = s.generate_keypair(&mut rand::thread_rng());
236        let (_, pk2) = s.generate_keypair(&mut rand::thread_rng());
237
238        let secret_sys = SharedSecret::new(&pk2, &sk1);
239
240        let xy = shared_secret_point(&pk2, &sk1);
241
242        // Mimics logic in `bitcoin-core/secp256k1/src/module/main_impl.h`
243        let version = (xy[63] & 0x01) | 0x02;
244        let mut engine = sha256::HashEngine::default();
245        engine.input(&[version]);
246        engine.input(&xy.as_ref()[..32]);
247        let secret_bh = sha256::Hash::from_engine(engine);
248
249        assert_eq!(secret_bh.as_byte_array(), secret_sys.as_ref());
250    }
251
252    #[test]
253    #[cfg(all(feature = "serde", feature = "alloc"))]
254    fn serde() {
255        use serde_test::{assert_tokens, Configure, Token};
256        #[rustfmt::skip]
257        static BYTES: [u8; 32] = [
258            1, 1, 1, 1, 1, 1, 1, 1,
259            0, 1, 2, 3, 4, 5, 6, 7,
260            0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0,
261            99, 99, 99, 99, 99, 99, 99, 99
262        ];
263        static STR: &str = "01010101010101010001020304050607ffff0000ffff00006363636363636363";
264
265        let secret = SharedSecret::from_slice(&BYTES).unwrap();
266
267        assert_tokens(&secret.compact(), &[Token::BorrowedBytes(&BYTES[..])]);
268        assert_tokens(&secret.compact(), &[Token::Bytes(&BYTES)]);
269        assert_tokens(&secret.compact(), &[Token::ByteBuf(&BYTES)]);
270
271        assert_tokens(&secret.readable(), &[Token::BorrowedStr(STR)]);
272        assert_tokens(&secret.readable(), &[Token::Str(STR)]);
273        assert_tokens(&secret.readable(), &[Token::String(STR)]);
274    }
275}
276
277#[cfg(bench)]
278#[cfg(feature = "rand-std")] // Currently only a single bench that requires "rand-std".
279mod benches {
280    use test::{black_box, Bencher};
281
282    use super::SharedSecret;
283    use crate::Secp256k1;
284
285    #[bench]
286    pub fn bench_ecdh(bh: &mut Bencher) {
287        let s = Secp256k1::signing_only();
288        let (sk, pk) = s.generate_keypair(&mut rand::thread_rng());
289
290        bh.iter(|| {
291            let res = SharedSecret::new(&pk, &sk);
292            black_box(res);
293        });
294    }
295}