Skip to content

Commit

Permalink
Merge branch 'nyonson-feature/no-std'
Browse files Browse the repository at this point in the history
  • Loading branch information
rustaceanrob committed Mar 19, 2024
2 parents 00af8e8 + 5ed951e commit 16479af
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 41 deletions.
17 changes: 12 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Cargo Build & Test
name: Build & Test

on:
push:
Expand All @@ -7,8 +7,7 @@ on:
pull_request:

jobs:
build:
name: Rust - latest
build-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
Expand All @@ -19,7 +18,11 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Update Toolchain
run: rustup default ${{ matrix.toolchain }} && rustup component add --toolchain ${{ matrix.toolchain }} rustfmt && rustup component add --toolchain ${{ matrix.toolchain }} clippy && rustup update ${{ matrix.toolchain }}
run: |
rustup default ${{ matrix.toolchain }}
rustup component add --toolchain ${{ matrix.toolchain }} rustfmt
rustup component add --toolchain ${{ matrix.toolchain }} clippy
rustup update ${{ matrix.toolchain }}
- name: Build
run: cargo build --verbose
- name: Lint
Expand All @@ -28,5 +31,9 @@ jobs:
run: cargo fmt --all -- --check
- name: Test
run: cargo test --verbose

- name: Check No Standard Library Support
run: |
rustup target add --toolchain ${{ matrix.toolchain }} thumbv7m-none-eabi
cargo install cross
cross build --target thumbv7m-none-eabi --no-default-features
10 changes: 7 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ repository = "https://github.com/rustaceanrob/bip324"
readme = "README.md"
rust-version = "1.56.1"

[features]
default = ["std"]
std = ["secp256k1/std", "rand/std", "rand/std_rng"]

[dependencies]
secp256k1 = { version="0.28.2" }
rand = "0.8.4"
bitcoin_hashes = "0.13.0"
secp256k1 = { version="0.28.2", default-features = false}
rand = { version = "0.8.4", default-features = false }
bitcoin_hashes = { version = "0.13.0", default-features = false }

[dev-dependencies]
hex = "0.4.3"
Expand Down
5 changes: 3 additions & 2 deletions src/chachapoly.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::chacha::ChaCha20;
use crate::error;
use crate::poly1305::Poly1305;
use error::ChaCha20Poly1305EncryptionError;
extern crate alloc;
pub use error::ChaCha20Poly1305DecryptionError;
use error::ChaCha20Poly1305EncryptionError;

use alloc::string::ToString;

#[derive(Debug)]
pub struct ChaCha20Poly1305 {
Expand Down
32 changes: 17 additions & 15 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::error::Error;
extern crate alloc;
use core::fmt;
// use alloc::string::String;

use alloc::string::String;

/// An error occured responding to an inbound handshake.
#[derive(Debug)]
Expand All @@ -12,8 +11,8 @@ pub enum ResponderHandshakeError {
EncryptionError(String),
}

impl std::fmt::Display for ResponderHandshakeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Display for ResponderHandshakeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ResponderHandshakeError::ECC(e) => write!(f, "ECC error: {}", e),
ResponderHandshakeError::IncorrectMessage(s) => write!(f, "Version error: {}", s),
Expand All @@ -22,8 +21,9 @@ impl std::fmt::Display for ResponderHandshakeError {
}
}

impl Error for ResponderHandshakeError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
#[cfg(feature = "std")]
impl std::error::Error for ResponderHandshakeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ResponderHandshakeError::ECC(e) => Some(e),
ResponderHandshakeError::IncorrectMessage(_) => None,
Expand All @@ -41,8 +41,8 @@ pub enum HandshakeCompletionError {
DecryptionError(String),
}

impl std::fmt::Display for HandshakeCompletionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Display for HandshakeCompletionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HandshakeCompletionError::MessageTooShort(s) => write!(f, "Handshake error: {}", s),
HandshakeCompletionError::TooMuchGarbage(s) => write!(f, "Handshake error: {}", s),
Expand All @@ -52,8 +52,9 @@ impl std::fmt::Display for HandshakeCompletionError {
}
}

impl Error for HandshakeCompletionError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
#[cfg(feature = "std")]
impl std::error::Error for HandshakeCompletionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
HandshakeCompletionError::MessageTooShort(_s) => None,
HandshakeCompletionError::TooMuchGarbage(_s) => None,
Expand All @@ -71,8 +72,8 @@ pub enum FSChaChaError {
Poly1305Decryption(String),
}

impl std::fmt::Display for FSChaChaError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Display for FSChaChaError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FSChaChaError::StreamEncryption(s) => write!(f, "Cipher error: {}", s),
FSChaChaError::StreamDecryption(s) => write!(f, "Cipher error: {}", s),
Expand All @@ -82,8 +83,9 @@ impl std::fmt::Display for FSChaChaError {
}
}

impl Error for FSChaChaError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
#[cfg(feature = "std")]
impl std::error::Error for FSChaChaError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
FSChaChaError::StreamEncryption(_s) => None,
FSChaChaError::StreamDecryption(_s) => None,
Expand Down
1 change: 1 addition & 0 deletions src/hkdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ impl fmt::Display for InvalidLength {
}
}

#[cfg(feature = "std")]
impl std::error::Error for InvalidLength {}

/// HMAC-based Extract-and-Expand Key Derivation Function (HKDF).
Expand Down
94 changes: 78 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
//! assert_eq!(message, secret_message);
//! ```
#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]

extern crate alloc;

mod chacha;
mod chachapoly;
mod error;
Expand All @@ -45,12 +49,19 @@ mod types;

use chacha::ChaCha20;
use chachapoly::ChaCha20Poly1305;

use alloc::vec;
use alloc::vec::Vec;

use alloc::string::ToString;

use error::FSChaChaError;
pub use error::{HandshakeCompletionError, ResponderHandshakeError};
use hkdf::Hkdf;
use rand::Rng;
use secp256k1::{
ellswift::{ElligatorSwift, ElligatorSwiftParty},
ffi::types::AlignedType,
PublicKey, Secp256k1, SecretKey,
};
pub use types::SessionKeyMaterial;
Expand Down Expand Up @@ -360,22 +371,21 @@ impl FSChaCha20 {
}
}

fn gen_key() -> Result<SecretKey, secp256k1::Error> {
let mut rnd = rand::thread_rng();
fn gen_key(rng: &mut impl Rng) -> Result<SecretKey, secp256k1::Error> {
let mut buffer: Vec<u8> = vec![0; 32];
rnd.fill(&mut buffer[..]);
rng.fill(&mut buffer[..]);
let sk = SecretKey::from_slice(&buffer)?;
Ok(sk)
}

fn new_elligator_swift(sk: SecretKey) -> ElligatorSwift {
let curve = Secp256k1::new();
let mut buf_ful = vec![AlignedType::zeroed(); Secp256k1::preallocate_size()];
let curve = Secp256k1::preallocated_new(&mut buf_ful).unwrap();
let pk = PublicKey::from_secret_key(&curve, &sk);
ElligatorSwift::from_pubkey(pk)
}

fn gen_garbage(garbage_len: u32) -> Vec<u8> {
let mut rng = rand::thread_rng();
fn gen_garbage(garbage_len: u32, rng: &mut impl Rng) -> Vec<u8> {
let buffer: Vec<u8> = (0..garbage_len).map(|_| rng.gen()).collect();
buffer
}
Expand Down Expand Up @@ -449,13 +459,37 @@ fn initialize_session_key_material(ikm: &[u8]) -> SessionKeyMaterial {
/// # Errors
///
/// Fails if their was an error generating the keypair.
#[cfg(feature = "std")]
pub fn initialize_v2_handshake(
garbage_len: Option<u32>,
) -> Result<InitiatorHandshake, secp256k1::Error> {
let sk = gen_key()?;
let mut rng = rand::thread_rng();
initialize_v2_handshake_with_rng(garbage_len, &mut rng)
}

/// Initialize a V2 transport handshake with a peer. The `InitiatorHandshake` contains a message ready to be sent over the wire,
/// and the information necessary for completing ECDH when the peer responds.
///
/// # Arguments
///
/// `garbage_len` - The length of the additional garbage to be sent along with the encoded public key.
/// `rng` - supplied Random Number Generator.
///
/// # Returns
///
/// A partial handshake.
///
/// # Errors
///
/// Fails if their was an error generating the keypair.
pub fn initialize_v2_handshake_with_rng(
garbage_len: Option<u32>,
rng: &mut impl Rng,
) -> Result<InitiatorHandshake, secp256k1::Error> {
let sk = gen_key(rng)?;
let es = new_elligator_swift(sk);
let garbage_len = garbage_len.unwrap_or(MAX_GARBAGE_LEN);
let garbage = gen_garbage(garbage_len);
let garbage = gen_garbage(garbage_len, rng);
let point = EcdhPoint {
secret_key: sk,
elligator_swift: es,
Expand All @@ -482,8 +516,31 @@ pub fn initialize_v2_handshake(
/// # Errors
///
/// Fails if the packet was not prepared properly.
#[cfg(feature = "std")]
pub fn receive_v2_handshake(
message: Vec<u8>,
) -> Result<ResponderHandshake, ResponderHandshakeError> {
let mut rng = rand::thread_rng();
receive_v2_handshake_with_rng(message, &mut rng)
}

/// Receive a V2 handshake over the wire. The `ResponderHandshake` contains the message ready to be sent over the wire and a struct for parsing packets.
///
/// # Arguments
///
/// `message` - The message received over the wire.
/// `rng` - Supplied Random Number Generator.
///
/// # Returns
///
/// A completed handshake containing a `PacketHandler`.
///
/// # Errors
///
/// Fails if the packet was not prepared properly.
pub fn receive_v2_handshake_with_rng(
message: Vec<u8>,
rng: &mut impl Rng,
) -> Result<ResponderHandshake, ResponderHandshakeError> {
let mut network_magic = NETWORK_MAGIC.to_vec();
let mut version_bytes = "version".as_bytes().to_vec();
Expand All @@ -496,7 +553,7 @@ pub fn receive_v2_handshake(
))
} else {
let mut response: Vec<u8> = Vec::new();
let sk = gen_key().map_err(ResponderHandshakeError::ECC)?;
let sk = gen_key(rng).map_err(ResponderHandshakeError::ECC)?;
let es = new_elligator_swift(sk);
response.extend(&es.to_array());
let elliswift_message = &message[..64];
Expand All @@ -507,7 +564,7 @@ pub fn receive_v2_handshake(
let session_keys = get_shared_secrets(theirs, es, sk, ElligatorSwiftParty::B);
let initiator_garbage = message[64..].to_vec();
let initiator_garbage_len = initiator_garbage.len() as u32;
let response_garbage = gen_garbage(initiator_garbage_len);
let response_garbage = gen_garbage(initiator_garbage_len, rng);
if initiator_garbage_len > MAX_GARBAGE_LEN {
return Err(ResponderHandshakeError::IncorrectMessage(
"Garbage length is too large.".to_string(),
Expand Down Expand Up @@ -649,7 +706,8 @@ mod tests {

#[test]
fn test_sec_keygen() {
gen_key().unwrap();
let mut rng = rand::thread_rng();
gen_key(&mut rng).unwrap();
}

#[test]
Expand Down Expand Up @@ -765,6 +823,7 @@ mod tests {

#[test]
fn test_fuzz_packets() {
let mut rng = rand::thread_rng();
let alice =
SecretKey::from_str("61062ea5071d800bbfd59e2e8b53d47d194b095ae5a4df04936b49772ef0d4d7")
.unwrap();
Expand All @@ -780,7 +839,7 @@ mod tests {
PacketHandler::new(session_keys.clone(), HandshakeRole::Initiator);
let mut bob_packet_handler = PacketHandler::new(session_keys, HandshakeRole::Responder);
for _ in 0..REKEY_INTERVAL + 100 {
let message = gen_garbage(4095);
let message = gen_garbage(4095, &mut rng);
let enc_packet = alice_packet_handler
.prepare_v2_packet(message.clone(), None, false)
.unwrap();
Expand All @@ -789,7 +848,7 @@ mod tests {
.unwrap();
let secret_message = dec_packet.first().unwrap().message.clone().unwrap();
assert_eq!(message, secret_message);
let message = gen_garbage(420);
let message = gen_garbage(420, &mut rng);
let enc_packet = bob_packet_handler
.prepare_v2_packet(message.clone(), None, false)
.unwrap();
Expand All @@ -803,6 +862,7 @@ mod tests {

#[test]
fn test_authenticated_garbage() {
let mut rng = rand::thread_rng();
let alice =
SecretKey::from_str("61062ea5071d800bbfd59e2e8b53d47d194b095ae5a4df04936b49772ef0d4d7")
.unwrap();
Expand All @@ -817,7 +877,7 @@ mod tests {
let mut alice_packet_handler =
PacketHandler::new(session_keys.clone(), HandshakeRole::Initiator);
let mut bob_packet_handler = PacketHandler::new(session_keys, HandshakeRole::Responder);
let auth_garbage = gen_garbage(200);
let auth_garbage = gen_garbage(200, &mut rng);
let enc_packet = alice_packet_handler
.prepare_v2_packet(Vec::new(), Some(auth_garbage.clone()), false)
.unwrap();
Expand Down Expand Up @@ -885,6 +945,7 @@ mod tests {

#[test]
fn test_fuzz_decode_multiple_messages() {
let mut rng = rand::thread_rng();
let handshake_init = initialize_v2_handshake(None).unwrap();
let mut handshake_response = receive_v2_handshake(handshake_init.message.clone()).unwrap();
let alice_completion =
Expand All @@ -899,7 +960,7 @@ mod tests {
let mut bob = handshake_response.packet_handler;
let mut message_to_bob = Vec::new();
for _ in 0..REKEY_INTERVAL + 100 {
let message = gen_garbage(420);
let message = gen_garbage(420, &mut rng);
let enc_packet = alice
.prepare_v2_packet(message.clone(), None, false)
.unwrap();
Expand All @@ -912,6 +973,7 @@ mod tests {

#[test]
fn test_vector_1() {
let mut rng = rand::thread_rng();
let alice =
SecretKey::from_str("61062ea5071d800bbfd59e2e8b53d47d194b095ae5a4df04936b49772ef0d4d7")
.unwrap();
Expand All @@ -926,7 +988,7 @@ mod tests {
let mut alice_packet_handler =
PacketHandler::new(session_keys.clone(), HandshakeRole::Initiator);
let mut bob_packet_handler = PacketHandler::new(session_keys, HandshakeRole::Responder);
let first = gen_garbage(100);
let first = gen_garbage(100, &mut rng);
let enc = alice_packet_handler
.prepare_v2_packet(first.clone(), None, false)
.unwrap();
Expand Down
1 change: 1 addition & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::PacketHandler;
use alloc::vec::Vec;
use secp256k1::{ellswift::ElligatorSwift, SecretKey};

/// A point on the curve used to complete the handshake.
Expand Down

0 comments on commit 16479af

Please sign in to comment.