Skip to content

Commit 37150ed

Browse files
committed
feat!: redo the scheme's interface
Major changes! But confined to the lower level no-std interface, the higher level Async I/O one remains unchanged. There are two big changes which then trickle around the whole library. 1. Cosmetic, but re-name the no-std interface to not include `Reader` and `Writer` suffixes for clarity. These structures are purposely not implementing the standard Read/Write IO traits, so shouldn't follow that naming convention. Those helper wrappers live over in the IO module. 2. Dropped the middle-ground `alloc` feature flag. This change makes the core library just no-std and the I/O module full on std. The inbound and outbound ciphers drop their "alloc" wrapper methods and instead expose helper methods to calculate appropriate buffer lengths, `OutboundCipher::encryption_buffer_len()` and `InboundCipher::decryption_buffer_len()`. These are used by the higher i level IO wrappers.
1 parent 8f2b49f commit 37150ed

File tree

9 files changed

+581
-470
lines changed

9 files changed

+581
-470
lines changed

justfile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,9 @@ _default:
4141

4242
# Test feature flag matrix compatability.
4343
@_test-features:
44-
# Build and test with all features, no features, and some combinations.
44+
# Build and test with all features, no features, and some combinations if required.
4545
cargo +{{STABLE_TOOLCHAIN}} test --package bip324 --lib --all-features
4646
cargo +{{STABLE_TOOLCHAIN}} test --package bip324 --lib --no-default-features
47-
cargo +{{STABLE_TOOLCHAIN}} test --package bip324 --lib --no-default-features --features alloc
4847

4948
# Check code with MSRV compiler.
5049
@_test-msrv:
@@ -70,7 +69,7 @@ _default:
7069
# Test no standard library support.
7170
@_test-no-std:
7271
cargo install cross@0.2.5
73-
$HOME/.cargo/bin/cross build --package bip324 --target thumbv7m-none-eabi --no-default-features --features alloc
72+
$HOME/.cargo/bin/cross build --package bip324 --target thumbv7m-none-eabi --no-default-features
7473

7574
# Run benchmarks.
7675
bench:

protocol/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ default = ["std"]
1515
futures = ["std", "dep:futures"]
1616
# High-level wrappers using tokio traits - may affect MSRV requirements.
1717
tokio = ["std", "dep:tokio"]
18-
std = ["alloc", "bitcoin/std", "bitcoin_hashes/std", "chacha20-poly1305/std", "rand/std", "rand/std_rng"]
19-
alloc = ["chacha20-poly1305/alloc"]
18+
std = ["bitcoin/std", "bitcoin_hashes/std", "chacha20-poly1305/std", "rand/std", "rand/std_rng"]
2019

2120
[dependencies]
2221
futures = { version = "0.3.30", default-features = false, optional = true, features = ["std"] }

protocol/README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@ The library is designed with a bare `no_std` and "Sans I/O" interface to keep it
66

77
The `futures` feature includes the high-level `AsyncProcotol` type which helps create and manage an encrypted channel.
88

9-
The lower-level `Handshake` and `PacketHandler` types can be directly used by applications which require more control. The handshake performs the one-and-a-half round trip dance between the peers in order to generate secret materials. A successful handshake results in a packet handler which performs the encrypt and decrypt operations for the lifetime of the channel.
9+
The lower-level `CipherSession` and `Handshake` types can be directly used by applications which require more control. The handshake performs the one-and-a-half round trip dance between the peers in order to generate secret materials. A successful handshake results in a packet handler which performs the encrypt and decrypt operations for the lifetime of the channel.
1010

1111
## Feature Flags
1212

13-
* `alloc` -- Expose memory allocation dependent features.
14-
* `std` -- Includes the `alloc` memory allocation feature as well as extra standard library dependencies for I/O and random number generators.
13+
* `std` -- Standard library dependencies for I/O, memory allocation, and random number generators.
1514
* `futures` -- High level wrappers for asynchronous read and write runtimes using agnostic futures-rs traits.
1615
* `tokio` -- Same wrappers as `futures`, but using the popular tokio runtime's specific traits instead of futures-rs.
1716

protocol/benches/packet_handler.rs

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
extern crate test;
44

5-
use bip324::{Handshake, Network, PacketHandler, PacketType, Role};
5+
use bip324::{CipherSession, Handshake, InboundCipher, Network, OutboundCipher, PacketType, Role};
66
use test::{black_box, Bencher};
77

8-
fn create_packet_handler_pair() -> (PacketHandler, PacketHandler) {
8+
fn create_packet_handler_pair() -> (CipherSession, CipherSession) {
99
// Create a proper handshake between Alice and Bob.
1010
let mut alice_init_buffer = vec![0u8; 64];
1111
let mut alice_handshake = Handshake::new(
@@ -45,11 +45,12 @@ fn create_packet_handler_pair() -> (PacketHandler, PacketHandler) {
4545
.unwrap();
4646

4747
// Authenticate.
48+
let mut packet_buffer = vec![0u8; 4096];
4849
alice_handshake
49-
.authenticate_garbage_and_version(&bob_init_buffer[64..])
50+
.authenticate_garbage_and_version(&bob_init_buffer[64..], &mut packet_buffer)
5051
.unwrap();
5152
bob_handshake
52-
.authenticate_garbage_and_version(&alice_response_buffer)
53+
.authenticate_garbage_and_version(&alice_response_buffer, &mut packet_buffer)
5354
.unwrap();
5455

5556
let alice = alice_handshake.finalize().unwrap();
@@ -65,20 +66,31 @@ fn bench_round_trip_small_packet(b: &mut Bencher) {
6566

6667
b.iter(|| {
6768
// Encrypt the packet.
68-
let encrypted = alice
69-
.writer()
70-
.encrypt_packet(black_box(plaintext), None, PacketType::Genuine)
69+
let packet_len = OutboundCipher::encryption_buffer_len(plaintext.len());
70+
let mut encrypted = vec![0u8; packet_len];
71+
alice
72+
.outbound()
73+
.encrypt(
74+
black_box(plaintext),
75+
&mut encrypted,
76+
PacketType::Genuine,
77+
None,
78+
)
7179
.unwrap();
7280

7381
// Decrypt the length from first 3 bytes (real-world step).
7482
let packet_length = bob
75-
.reader()
76-
.decypt_len(black_box(encrypted[0..3].try_into().unwrap()));
83+
.inbound()
84+
.decrypt_packet_len(black_box(encrypted[0..3].try_into().unwrap()));
7785

7886
// Decrypt the payload using the decrypted length.
79-
let decrypted = bob
80-
.reader()
81-
.decrypt_payload(black_box(&encrypted[3..3 + packet_length]), None)
87+
let mut decrypted = vec![0u8; InboundCipher::decryption_buffer_len(packet_length)];
88+
bob.inbound()
89+
.decrypt(
90+
black_box(&encrypted[3..3 + packet_length]),
91+
&mut decrypted,
92+
None,
93+
)
8294
.unwrap();
8395

8496
// Ensure the final result isn't optimized away.
@@ -93,20 +105,31 @@ fn bench_round_trip_large_packet(b: &mut Bencher) {
93105

94106
b.iter(|| {
95107
// Encrypt the packet.
96-
let encrypted = alice
97-
.writer()
98-
.encrypt_packet(black_box(&plaintext), None, PacketType::Genuine)
108+
let packet_len = OutboundCipher::encryption_buffer_len(plaintext.len());
109+
let mut encrypted = vec![0u8; packet_len];
110+
alice
111+
.outbound()
112+
.encrypt(
113+
black_box(&plaintext),
114+
&mut encrypted,
115+
PacketType::Genuine,
116+
None,
117+
)
99118
.unwrap();
100119

101120
// Decrypt the length from first 3 bytes (real-world step).
102121
let packet_length = bob
103-
.reader()
104-
.decypt_len(black_box(encrypted[0..3].try_into().unwrap()));
122+
.inbound()
123+
.decrypt_packet_len(black_box(encrypted[0..3].try_into().unwrap()));
105124

106125
// Decrypt the payload using the decrypted length.
107-
let decrypted = bob
108-
.reader()
109-
.decrypt_payload(black_box(&encrypted[3..3 + packet_length]), None)
126+
let mut decrypted = vec![0u8; InboundCipher::decryption_buffer_len(packet_length)];
127+
bob.inbound()
128+
.decrypt(
129+
black_box(&encrypted[3..3 + packet_length]),
130+
&mut decrypted,
131+
None,
132+
)
110133
.unwrap();
111134

112135
// Ensure the final result isn't optimized away.

protocol/fuzz/fuzz_targets/handshake.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![no_main]
2-
use bip324::{Handshake, Network, Role};
2+
use bip324::{Handshake, Network, Role, NUM_INITIAL_HANDSHAKE_BUFFER_BYTES};
33
use libfuzzer_sys::fuzz_target;
44

55
fuzz_target!(|data: &[u8]| {
@@ -52,6 +52,7 @@ fuzz_target!(|data: &[u8]| {
5252
// Exercising malformed public key handling.
5353
let _ = handshake.complete_materials(fuzzed_responder_pubkey, &mut garbage_and_version, None);
5454
// Check how a broken handshake is handled.
55-
let _ = handshake.authenticate_garbage_and_version(&garbage_and_version);
55+
let mut packet_buffer = vec![0u8; NUM_INITIAL_HANDSHAKE_BUFFER_BYTES]; // Initial buffer for decoy and version packets
56+
let _ = handshake.authenticate_garbage_and_version(&garbage_and_version, &mut packet_buffer);
5657
let _ = handshake.finalize();
5758
});

protocol/src/io.rs

Lines changed: 73 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
66
use core::fmt;
77

8-
#[cfg(feature = "alloc")]
9-
use alloc::vec;
10-
#[cfg(feature = "alloc")]
11-
use alloc::vec::Vec;
8+
#[cfg(feature = "std")]
9+
use std::vec;
10+
#[cfg(feature = "std")]
11+
use std::vec::Vec;
1212

1313
use bitcoin::Network;
1414

@@ -20,11 +20,38 @@ use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
2020
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
2121

2222
use crate::{
23-
Error, Handshake, PacketReader, PacketType, PacketWriter, Payload, Role,
24-
NUM_ELLIGATOR_SWIFT_BYTES, NUM_GARBAGE_TERMINTOR_BYTES, NUM_INITIAL_HANDSHAKE_BUFFER_BYTES,
25-
VERSION_CONTENT,
23+
Error, Handshake, InboundCipher, OutboundCipher, PacketType, Role, NUM_ELLIGATOR_SWIFT_BYTES,
24+
NUM_GARBAGE_TERMINTOR_BYTES, NUM_INITIAL_HANDSHAKE_BUFFER_BYTES, VERSION_CONTENT,
2625
};
2726

27+
/// A decrypted BIP324 payload with its packet type.
28+
#[cfg(feature = "std")]
29+
pub struct Payload {
30+
contents: Vec<u8>,
31+
packet_type: PacketType,
32+
}
33+
34+
#[cfg(feature = "std")]
35+
impl Payload {
36+
/// Create a new payload.
37+
pub fn new(contents: Vec<u8>, packet_type: PacketType) -> Self {
38+
Self {
39+
contents,
40+
packet_type,
41+
}
42+
}
43+
44+
/// Access the decrypted payload contents.
45+
pub fn contents(&self) -> &[u8] {
46+
&self.contents
47+
}
48+
49+
/// Access the packet type.
50+
pub fn packet_type(&self) -> PacketType {
51+
self.packet_type
52+
}
53+
}
54+
2855
/// High level error type for the protocol interface.
2956
#[cfg(feature = "std")]
3057
#[derive(Debug)]
@@ -159,11 +186,11 @@ impl AsyncProtocol {
159186
let mut remote_ellswift_buffer = [0u8; 64];
160187
reader.read_exact(&mut remote_ellswift_buffer).await?;
161188

162-
let num_version_packet_bytes = PacketWriter::required_packet_allocation(&VERSION_CONTENT);
189+
let num_version_packet_bytes = OutboundCipher::encryption_buffer_len(VERSION_CONTENT.len());
163190
let num_decoy_packets_bytes: usize = match decoys {
164191
Some(decoys) => decoys
165192
.iter()
166-
.map(|decoy| PacketWriter::required_packet_allocation(decoy))
193+
.map(|decoy| OutboundCipher::encryption_buffer_len(decoy.len()))
167194
.sum(),
168195
None => 0,
169196
};
@@ -187,6 +214,8 @@ impl AsyncProtocol {
187214
// Keep pulling bytes from the buffer until the garbage is flushed.
188215
let mut remote_garbage_and_version_buffer =
189216
Vec::with_capacity(NUM_INITIAL_HANDSHAKE_BUFFER_BYTES);
217+
let mut packet_buffer = vec![0u8; NUM_INITIAL_HANDSHAKE_BUFFER_BYTES];
218+
190219
loop {
191220
let mut temp_buffer = [0u8; NUM_INITIAL_HANDSHAKE_BUFFER_BYTES];
192221
match reader.read(&mut temp_buffer).await {
@@ -197,12 +226,17 @@ impl AsyncProtocol {
197226
Ok(bytes_read) => {
198227
remote_garbage_and_version_buffer.extend_from_slice(&temp_buffer[..bytes_read]);
199228

200-
match handshake
201-
.authenticate_garbage_and_version(&remote_garbage_and_version_buffer)
202-
{
229+
match handshake.authenticate_garbage_and_version(
230+
&remote_garbage_and_version_buffer,
231+
&mut packet_buffer,
232+
) {
203233
Ok(()) => break,
204234
// Not enough data, continue reading.
205235
Err(Error::CiphertextTooSmall) => continue,
236+
Err(Error::BufferTooSmall { required_bytes }) => {
237+
packet_buffer.resize(required_bytes, 0);
238+
continue;
239+
}
206240
Err(e) => return Err(ProtocolError::Internal(e)),
207241
}
208242
}
@@ -216,15 +250,15 @@ impl AsyncProtocol {
216250
}
217251
}
218252

219-
let packet_handler = handshake.finalize()?;
220-
let (packet_reader, packet_writer) = packet_handler.into_split();
253+
let cipher_session = handshake.finalize()?;
254+
let (inbound_cipher, outbound_cipher) = cipher_session.into_split();
221255

222256
Ok(Self {
223257
reader: AsyncProtocolReader {
224-
packet_reader,
258+
inbound_cipher,
225259
state: DecryptState::init_reading_length(),
226260
},
227-
writer: AsyncProtocolWriter { packet_writer },
261+
writer: AsyncProtocolWriter { outbound_cipher },
228262
})
229263
}
230264

@@ -280,7 +314,7 @@ impl DecryptState {
280314
/// Manages an async buffer to automatically decrypt contents of received packets.
281315
#[cfg(any(feature = "futures", feature = "tokio"))]
282316
pub struct AsyncProtocolReader {
283-
packet_reader: PacketReader,
317+
inbound_cipher: InboundCipher,
284318
state: DecryptState,
285319
}
286320

@@ -297,7 +331,7 @@ impl AsyncProtocolReader {
297331
/// # Returns
298332
///
299333
/// A `Result` containing:
300-
/// * `Ok(Payload)`: A decrypted payload.
334+
/// * `Ok(Payload)`: A decrypted payload with packet type.
301335
/// * `Err(ProtocolError)`: An error that occurred during the read or decryption.
302336
pub async fn read_and_decrypt<R>(&mut self, buffer: &mut R) -> Result<Payload, ProtocolError>
303337
where
@@ -314,7 +348,7 @@ impl AsyncProtocolReader {
314348
*bytes_read += buffer.read(&mut length_bytes[*bytes_read..]).await?;
315349
}
316350

317-
let packet_bytes_len = self.packet_reader.decypt_len(*length_bytes);
351+
let packet_bytes_len = self.inbound_cipher.decrypt_packet_len(*length_bytes);
318352
self.state = DecryptState::init_reading_payload(packet_bytes_len);
319353
}
320354
DecryptState::ReadingPayload {
@@ -325,24 +359,28 @@ impl AsyncProtocolReader {
325359
*bytes_read += buffer.read(&mut packet_bytes[*bytes_read..]).await?;
326360
}
327361

328-
let payload = self.packet_reader.decrypt_payload(packet_bytes, None)?;
362+
let plaintext_len = InboundCipher::decryption_buffer_len(packet_bytes.len());
363+
let mut plaintext_buffer = vec![0u8; plaintext_len];
364+
let packet_type =
365+
self.inbound_cipher
366+
.decrypt(packet_bytes, &mut plaintext_buffer, None)?;
329367
self.state = DecryptState::init_reading_length();
330-
return Ok(payload);
368+
return Ok(Payload::new(plaintext_buffer, packet_type));
331369
}
332370
}
333371
}
334372
}
335373

336-
/// Consume the protocol reader in exchange for the underlying packet decoder.
337-
pub fn decoder(self) -> PacketReader {
338-
self.packet_reader
374+
/// Consume the protocol reader in exchange for the underlying inbound cipher.
375+
pub fn into_cipher(self) -> InboundCipher {
376+
self.inbound_cipher
339377
}
340378
}
341379

342380
/// Manages an async buffer to automatically encrypt and send contents in packets.
343381
#[cfg(any(feature = "futures", feature = "tokio"))]
344382
pub struct AsyncProtocolWriter {
345-
packet_writer: PacketWriter,
383+
outbound_cipher: OutboundCipher,
346384
}
347385

348386
#[cfg(any(feature = "futures", feature = "tokio"))]
@@ -366,16 +404,19 @@ impl AsyncProtocolWriter {
366404
where
367405
W: AsyncWrite + Unpin + Send,
368406
{
369-
let write_bytes =
370-
self.packet_writer
371-
.encrypt_packet(plaintext, None, PacketType::Genuine)?;
372-
buffer.write_all(&write_bytes[..]).await?;
407+
let packet_len = OutboundCipher::encryption_buffer_len(plaintext.len());
408+
let mut packet_buffer = vec![0u8; packet_len];
409+
410+
self.outbound_cipher
411+
.encrypt(plaintext, &mut packet_buffer, PacketType::Genuine, None)?;
412+
413+
buffer.write_all(&packet_buffer).await?;
373414
buffer.flush().await?;
374415
Ok(())
375416
}
376417

377-
/// Consume the protocol writer in exchange for the underlying packet encoder.
378-
pub fn encoder(self) -> PacketWriter {
379-
self.packet_writer
418+
/// Consume the protocol writer in exchange for the underlying outbound cipher.
419+
pub fn into_cipher(self) -> OutboundCipher {
420+
self.outbound_cipher
380421
}
381422
}

0 commit comments

Comments
 (0)