bitcoin_hpke/lib.rs
1//! # bitcoin_hpke
2//! **WARNING:** This code has not been audited. Use at your own discretion.
3//!
4//! This is a pure Rust implementation of the
5//! [HPKE](https://datatracker.ietf.org/doc/rfc9180/) hybrid encryption scheme (RFC 9180). The
6//! purpose of hybrid encryption is to use allow someone to send secure messages to an entity whose
7//! public key they know. Here's an example of Alice and Bob, where Alice knows Bob's public key:
8//!
9//! ```
10//! # #[cfg(any(feature = "alloc", feature = "std"))] {
11//! # #[cfg(feature = "secp")]
12//! # {
13//! # use rand::{rngs::StdRng, SeedableRng};
14//! # use bitcoin_hpke::{
15//! # aead::ChaCha20Poly1305,
16//! # kdf::HkdfSha256,
17//! # kem::SecpK256HkdfSha256,
18//! # Kem as KemTrait, OpModeR, OpModeS, setup_receiver, setup_sender,
19//! # };
20//! // These types define the ciphersuite Alice and Bob will be using
21//! type Kem = SecpK256HkdfSha256;
22//! type Aead = ChaCha20Poly1305;
23//! type Kdf = HkdfSha256;
24//!
25//! let mut csprng = StdRng::from_entropy();
26//! # let (bob_sk, bob_pk) = Kem::gen_keypair(&mut csprng);
27//!
28//! // This is a description string for the session. Both Alice and Bob need to know this value.
29//! // It's not secret.
30//! let info_str = b"Alice and Bob's weekly chat";
31//!
32//! // Alice initiates a session with Bob. OpModeS::Base means that Alice is not authenticating
33//! // herself at all. If she had a public key herself, or a pre-shared secret that Bob also
34//! // knew, she'd be able to authenticate herself. See the OpModeS and OpModeR types for more
35//! // detail.
36//! let (encapsulated_key, mut encryption_context) =
37//! bitcoin_hpke::setup_sender::<Aead, Kdf, Kem, _>(&OpModeS::Base, &bob_pk, info_str, &mut csprng)
38//! .expect("invalid server pubkey!");
39//!
40//! // Alice encrypts a message to Bob. `aad` is authenticated associated data that is not
41//! // encrypted.
42//! let msg = b"fronthand or backhand?";
43//! let aad = b"a gentleman's game";
44//! // To seal without allocating:
45//! // let auth_tag = encryption_context.seal_in_place_detached(&mut msg, aad)?;
46//! // To seal with allocating:
47//! let ciphertext = encryption_context.seal(msg, aad).expect("encryption failed!");
48//!
49//! // ~~~
50//! // Alice sends the encapsulated key, message ciphertext, AAD, and auth tag to Bob over the
51//! // internet. Alice doesn't care if it's an insecure connection, because only Bob can read
52//! // her ciphertext.
53//! // ~~~
54//!
55//! // Somewhere far away, Bob receives the data and makes a decryption session
56//! let mut decryption_context =
57//! bitcoin_hpke::setup_receiver::<Aead, Kdf, Kem>(
58//! &OpModeR::Base,
59//! &bob_sk,
60//! &encapsulated_key,
61//! info_str,
62//! ).expect("failed to set up receiver!");
63//! // To open without allocating:
64//! // decryption_context.open_in_place_detached(&mut ciphertext, aad, &auth_tag)
65//! // To open with allocating:
66//! let plaintext = decryption_context.open(&ciphertext, aad).expect("invalid ciphertext!");
67//!
68//! assert_eq!(&plaintext, b"fronthand or backhand?");
69//! # }
70//! # }
71//! ```
72
73// The doc_cfg feature is only available in nightly. It lets us mark items in documentation as
74// dependent on specific features.
75#![cfg_attr(docsrs, feature(doc_cfg))]
76//-------- no_std stuff --------//
77#![no_std]
78
79#[cfg(feature = "std")]
80#[allow(unused_imports)]
81#[macro_use]
82extern crate std;
83
84#[cfg(feature = "std")]
85pub(crate) use std::vec::Vec;
86
87#[cfg(all(feature = "alloc", not(feature = "std")))]
88#[allow(unused_imports)]
89#[macro_use]
90extern crate alloc;
91
92#[cfg(all(feature = "alloc", not(feature = "std")))]
93pub(crate) use alloc::vec::Vec;
94
95#[macro_use]
96mod util;
97
98//-------- Testing stuff --------//
99
100// kat_tests tests all the implemented ciphersuites, and thus needs all the dependencies. It also
101// needs std for file IO.
102#[cfg(all(test, feature = "std", feature = "secp"))]
103mod kat_tests;
104
105#[cfg(test)]
106mod test_util;
107
108//-------- Modules and exports--------//
109
110// Re-export our versions of generic_array and rand_core, since their traits and types are exposed
111// in this crate
112pub use generic_array;
113pub use rand_core;
114
115pub mod aead;
116mod dhkex;
117pub mod kdf;
118pub mod kem;
119mod op_mode;
120mod setup;
121mod single_shot;
122
123#[doc(inline)]
124pub use kem::Kem;
125#[doc(inline)]
126pub use op_mode::{OpModeR, OpModeS, PskBundle};
127#[doc(inline)]
128pub use setup::{setup_receiver, setup_sender};
129#[doc(inline)]
130pub use single_shot::{single_shot_open_in_place_detached, single_shot_seal_in_place_detached};
131
132#[doc(inline)]
133#[cfg(any(feature = "alloc", feature = "std"))]
134pub use single_shot::{single_shot_open, single_shot_seal};
135
136//-------- Top-level types --------//
137
138use generic_array::{typenum::marker_traits::Unsigned, ArrayLength, GenericArray};
139
140/// Describes things that can go wrong in the HPKE protocol
141#[derive(Clone, Copy, Debug, PartialEq, Eq)]
142pub enum HpkeError {
143 /// The allowed number of message encryptions has been reached
144 MessageLimitReached,
145 /// An error occurred while opening a ciphertext
146 OpenError,
147 /// An error occured while sealing a plaintext
148 SealError,
149 /// The KDF was asked to output too many bytes
150 KdfOutputTooLong,
151 /// An invalid input value was encountered
152 ValidationError,
153 /// Encapsulation failed
154 EncapError,
155 /// Decapsulation failed
156 DecapError,
157 /// An input isn't the right length. First value is the expected length, second is the given
158 /// length.
159 IncorrectInputLength(usize, usize),
160}
161
162impl core::fmt::Display for HpkeError {
163 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
164 match self {
165 HpkeError::MessageLimitReached => write!(f, "Message limit reached"),
166 HpkeError::OpenError => write!(f, "Failed to open ciphertext"),
167 HpkeError::SealError => write!(f, "Failed to seal plaintext"),
168 HpkeError::KdfOutputTooLong => write!(f, "Too many bytes requested from KDF"),
169 HpkeError::ValidationError => write!(f, "Input value is invalid"),
170 HpkeError::EncapError => write!(f, "Encapsulation failed"),
171 HpkeError::DecapError => write!(f, "Decapsulation failed"),
172 HpkeError::IncorrectInputLength(expected, given) => write!(
173 f,
174 "Incorrect input length. Expected {} bytes. Got {}.",
175 expected, given
176 ),
177 }
178 }
179}
180
181/// Implemented by types that have a fixed-length byte representation
182pub trait Serializable {
183 /// Serialized size in bytes
184 type OutputSize: ArrayLength<u8>;
185
186 /// Serializes `self` to the given slice. `buf` MUST have length equal to `Self::size()`.
187 ///
188 /// Panics
189 /// ======
190 /// Panics if `buf.len() != Self::size()`.
191 fn write_exact(&self, buf: &mut [u8]);
192
193 /// Serializes `self` to a new array
194 fn to_bytes(&self) -> GenericArray<u8, Self::OutputSize> {
195 // Make a buffer of the correct size and write to it
196 let mut buf = GenericArray::default();
197 self.write_exact(&mut buf);
198 // Return the buffer
199 buf
200 }
201
202 /// Returns the size (in bytes) of this type when serialized
203 fn size() -> usize {
204 Self::OutputSize::to_usize()
205 }
206}
207
208/// Implemented by types that can be deserialized from byte representation
209pub trait Deserializable: Serializable + Sized {
210 fn from_bytes(encoded: &[u8]) -> Result<Self, HpkeError>;
211}
212
213// An Error type is just something that's Debug and Display
214#[cfg(feature = "std")]
215impl std::error::Error for HpkeError {}