Skip to content

Commit

Permalink
noise_sv2: is a optional no_std crate, std by default
Browse files Browse the repository at this point in the history
- std::ptr -> core::ptr
- std::boxed::Box -> alloc::boxed::Box
- std::vec::Vec -> alloc::vec::Vec
- std::string::{String, ToString} -> alloc::string::{String, ToString}
- std::convert::TryInto -> core::convert::TryInto
- std::fmt::{Debug, Formatter, Result} -> core::fmt::{Debug, Formatter, Result}
- std::time::Duration -> core::time::Duration

To have a `no-std` version, the `--no-default-features` must be used
public API changed to be able to be `no_std` (delegate the choice of the Ramdom Number Generator and the current System Time to the caller, instead of assuming using the ones from `std`) :
- `Initiator::new()`, `Initiator::from_raw_k()`, `Initiator::without_pk()`, `Responder::new()`, `Responder::from_authority_kp()` and `Responder::generate_key()` take an additional argument implementing rand::Rng + ?Sized
- `Responder::step_1()` and `SignatureNoiseMessage::sign()` take an additional argument implementing rand::Rng + rand::CryptoRng
- `Initiator::step_2()`, `Responder::step_1()`, `Responder::step_2()` and `SignatureNoiseMessage::verify()` take an additional argument for the current system time epoch
  • Loading branch information
Georges Palauqui committed Oct 29, 2024
1 parent 0a8c17e commit 2375b22
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 65 deletions.
11 changes: 8 additions & 3 deletions protocols/v2/noise-sv2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@ homepage = "https://stratumprotocol.org"
keywords = ["stratum", "mining", "bitcoin", "protocol"]

[dependencies]
secp256k1 = { version = "0.28.2", default-features = false, features =["hashes", "alloc","rand","rand-std"] }
rand = {version = "0.8.5", default-features = false, features = ["std","std_rng"] }
secp256k1 = { version = "0.28.2", default-features = false, features = ["hashes", "alloc", "rand"] }
rand = {version = "0.8.5", default-features = false }
aes-gcm = "0.10.2"
chacha20poly1305 = "0.10.1"
rand_chacha = "0.3.1"
rand_chacha = { version = "0.3.1", default-features = false }
const_sv2 = { version = "^2.0.0", path = "../../../protocols/v2/const-sv2"}

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

[dev-dependencies]
quickcheck = "1.0.3"
quickcheck_macros = "1"
rand = {version = "0.8.5", default-features = false, features = ["std", "std_rng"] }

[package.metadata.docs.rs]
all-features = true
33 changes: 25 additions & 8 deletions protocols/v2/noise-sv2/examples/handshake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,35 +25,52 @@ const PARITY: Parity = Parity::Even;
const RESPONDER_CERT_VALIDITY: u32 = 3600;

// Generates a secp256k1 public/private key pair for the responder.
fn generate_key() -> Keypair {
fn generate_key<R: rand::Rng + ?Sized>(rng: &mut R) -> Keypair {

Check warning on line 28 in protocols/v2/noise-sv2/examples/handshake.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/noise-sv2/examples/handshake.rs#L28

Added line #L28 was not covered by tests
let secp = Secp256k1::new();
let (secret_key, _) = secp.generate_keypair(&mut rand::thread_rng());
let (secret_key, _) = secp.generate_keypair(rng);

Check warning on line 30 in protocols/v2/noise-sv2/examples/handshake.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/noise-sv2/examples/handshake.rs#L30

Added line #L30 was not covered by tests
let kp = Keypair::from_secret_key(&secp, &secret_key);
if kp.x_only_public_key().1 == PARITY {
kp
} else {
generate_key()
generate_key(rng)

Check warning on line 35 in protocols/v2/noise-sv2/examples/handshake.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/noise-sv2/examples/handshake.rs#L35

Added line #L35 was not covered by tests
}
}

fn main() {
let mut secret_message = "Ciao, Mondo!".as_bytes().to_vec();

let responder_key_pair = generate_key();
let responder_key_pair = generate_key(&mut rand::thread_rng());

let mut initiator = Initiator::new(Some(responder_key_pair.public_key().into()));
let mut responder = Responder::new(responder_key_pair, RESPONDER_CERT_VALIDITY);
let mut initiator = Initiator::new(
Some(responder_key_pair.public_key().into()),
&mut rand::thread_rng(),
);
let mut responder = Responder::new(
responder_key_pair,
RESPONDER_CERT_VALIDITY,
&mut rand::thread_rng(),
);

let first_message = initiator
.step_0()
.expect("Initiator failed first step of handshake");

let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as u32;

let (second_message, mut responder_state) = responder
.step_1(first_message)
.step_1(first_message, now, &mut rand::thread_rng())
.expect("Responder failed second step of handshake");

let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as u32;

let mut initiator_state = initiator
.step_2(second_message)
.step_2(second_message, now)
.expect("Initiator failed third step of handshake");

initiator_state
Expand Down
2 changes: 1 addition & 1 deletion protocols/v2/noise-sv2/src/cipher_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
// within the Noise protocol, ensuring secure data handling, key management, and nonce tracking
// throughout the communication session.

use std::ptr;
use core::ptr;

use crate::aed_cipher::AeadCipher;
use aes_gcm::Aes256Gcm;
Expand Down
2 changes: 2 additions & 0 deletions protocols/v2/noise-sv2/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
//
// Defines error types and utilities for handling errors in the `noise_sv2` module.

use alloc::vec::Vec;

use aes_gcm::Error as AesGcm;

/// Noise protocol error handling.
Expand Down
15 changes: 9 additions & 6 deletions protocols/v2/noise-sv2/src/handshake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
// mandatory for communication across external networks (e.g., between a local mining proxy and a
// remote pool).

use alloc::{string::String, vec::Vec};

use crate::{aed_cipher::AeadCipher, cipher_state::CipherState, NOISE_HASHED_PROTOCOL_NAME_CHACHA};
use chacha20poly1305::ChaCha20Poly1305;
use secp256k1::{
Expand Down Expand Up @@ -99,14 +101,14 @@ pub trait HandshakeOp<Cipher: AeadCipher>: CipherState<Cipher> {
// Generates a fresh key pair, consisting of a secret key and a corresponding public key,
// using the [`Secp256k1`] elliptic curve. If the generated public key does not match the
// expected parity, a new key pair is generated to ensure consistency.
fn generate_key() -> Keypair {
fn generate_key<R: rand::Rng + ?Sized>(rng: &mut R) -> Keypair {
let secp = Secp256k1::new();
let (secret_key, _) = secp.generate_keypair(&mut rand::thread_rng());
let (secret_key, _) = secp.generate_keypair(rng);
let kp = Keypair::from_secret_key(&secp, &secret_key);
if kp.x_only_public_key().1 == crate::PARITY {
kp
} else {
Self::generate_key()
Self::generate_key(rng)
}
}

Expand Down Expand Up @@ -274,10 +276,11 @@ pub trait HandshakeOp<Cipher: AeadCipher>: CipherState<Cipher> {
#[cfg(test)]
mod test {
use super::*;
use alloc::string::ToString;
use core::convert::TryInto;
use quickcheck::{Arbitrary, TestResult};
use quickcheck_macros;
use secp256k1::{ecdh::SharedSecret, SecretKey, XOnlyPublicKey};
use std::convert::TryInto;

struct TestHandShake {
k: Option<[u8; 32]>,
Expand Down Expand Up @@ -466,8 +469,8 @@ mod test {

#[test]
fn test_ecdh() {
let key_pair_1 = TestHandShake::generate_key();
let key_pair_2 = TestHandShake::generate_key();
let key_pair_1 = TestHandShake::generate_key(&mut rand::thread_rng());
let key_pair_2 = TestHandShake::generate_key(&mut rand::thread_rng());

let secret_1 = key_pair_1.secret_bytes();
let secret_2 = key_pair_2.secret_bytes();
Expand Down
28 changes: 18 additions & 10 deletions protocols/v2/noise-sv2/src/initiator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@
// The [`Drop`] trait is implemented to automatically trigger secure erasure when the [`Initiator`]
// instance goes out of scope, preventing potential misuse or leakage of cryptographic material.

use std::{convert::TryInto, ptr};
use alloc::{
boxed::Box,
string::{String, ToString},
};
use core::{convert::TryInto, ptr};

use crate::{
cipher_state::{Cipher, CipherState, GenericCipher},
Expand Down Expand Up @@ -93,8 +97,8 @@ pub struct Initiator {
c2: Option<GenericCipher>,
}

impl std::fmt::Debug for Initiator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl core::fmt::Debug for Initiator {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Initiator").finish()
}
}
Expand Down Expand Up @@ -164,14 +168,14 @@ impl Initiator {
/// If the responder public key is provided, the initiator uses this key to authenticate the
/// responder during the handshake. The initial initiator state is instantiated with the
/// ephemeral key pair and handshake hash.
pub fn new(pk: Option<XOnlyPublicKey>) -> Box<Self> {
pub fn new<R: rand::Rng + ?Sized>(pk: Option<XOnlyPublicKey>, rng: &mut R) -> Box<Self> {
let mut self_ = Self {
handshake_cipher: None,
k: None,
n: 0,
ck: [0; 32],
h: [0; 32],
e: Self::generate_key(),
e: Self::generate_key(rng),
responder_authority_pk: pk,
c1: None,
c2: None,
Expand All @@ -187,19 +191,22 @@ impl Initiator {
/// valid [`XOnlyPublicKey`], an [`Error::InvalidRawPublicKey`] error is returned.
///
/// Typically used when the initiator is aware of the responder's public key in advance.
pub fn from_raw_k(key: [u8; 32]) -> Result<Box<Self>, Error> {
pub fn from_raw_k<R: rand::Rng + ?Sized>(
key: [u8; 32],
rng: &mut R,
) -> Result<Box<Self>, Error> {
let pk =
secp256k1::XOnlyPublicKey::from_slice(&key).map_err(|_| Error::InvalidRawPublicKey)?;
Ok(Self::new(Some(pk)))
Ok(Self::new(Some(pk), rng))

Check warning on line 200 in protocols/v2/noise-sv2/src/initiator.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/noise-sv2/src/initiator.rs#L200

Added line #L200 was not covered by tests
}

/// Creates a new [`Initiator`] without requiring the responder's authority public key.
/// This function initializes the [`Initiator`] with a default empty state and is intended
/// for use when both the initiator and responder are within the same network. In this case,
/// the initiator does not validate the responder's static key from a certificate. However,
/// the connection remains encrypted.
pub fn without_pk() -> Result<Box<Self>, Error> {
Ok(Self::new(None))
pub fn without_pk<R: rand::Rng + ?Sized>(rng: &mut R) -> Result<Box<Self>, Error> {
Ok(Self::new(None, rng))

Check warning on line 209 in protocols/v2/noise-sv2/src/initiator.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/noise-sv2/src/initiator.rs#L208-L209

Added lines #L208 - L209 were not covered by tests
}

/// Executes the initial step of the Noise NX protocol handshake.
Expand Down Expand Up @@ -244,6 +251,7 @@ impl Initiator {
pub fn step_2(
&mut self,
message: [u8; INITIATOR_EXPECTED_HANDSHAKE_MESSAGE_SIZE],
now: u32,
) -> Result<NoiseCodec, Error> {
// 2. interprets first 64 bytes as ElligatorSwift encoding of x-coordinate of public key
// from this is derived the 32-bytes remote ephemeral public key `re.public_key`
Expand Down Expand Up @@ -308,7 +316,7 @@ impl Initiator {
.0
.serialize();
let rs_pk_xonly = XOnlyPublicKey::from_slice(&rs_pub_key).unwrap();
if signature_message.verify(&rs_pk_xonly, &self.responder_authority_pk) {
if signature_message.verify(&rs_pk_xonly, &self.responder_authority_pk, now) {
let (temp_k1, temp_k2) = Self::hkdf_2(self.get_ck(), &[]);
let c1 = ChaCha20Poly1305::new(&temp_k1.into());
let c2 = ChaCha20Poly1305::new(&temp_k2.into());
Expand Down
9 changes: 7 additions & 2 deletions protocols/v2/noise-sv2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
//! used to authenticate messages and validate the identities of the Sv2 roles, ensuring that
//! critical messages like job templates and share submissions originate from legitimate sources.
#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]

#[macro_use]
extern crate alloc;

use aes_gcm::aead::Buffer;
pub use aes_gcm::aead::Error as AeadError;
use cipher_state::GenericCipher;
Expand Down Expand Up @@ -66,8 +71,8 @@ pub struct NoiseCodec {
decryptor: GenericCipher,
}

impl std::fmt::Debug for NoiseCodec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl core::fmt::Debug for NoiseCodec {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("NoiseCodec").finish()
}
}
Expand Down
46 changes: 28 additions & 18 deletions protocols/v2/noise-sv2/src/responder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
// The [`Drop`] trait is implemented to automatically trigger secure erasure when the [`Responder`]
// instance goes out of scope, preventing potential misuse or leakage of cryptographic material.

use std::{ptr, time::Duration};
use core::{ptr, time::Duration};

use crate::{
cipher_state::{Cipher, CipherState, GenericCipher},
Expand All @@ -44,6 +44,11 @@ use crate::{
NoiseCodec,
};
use aes_gcm::KeyInit;
use alloc::{
boxed::Box,
string::{String, ToString},
vec::Vec,
};
use chacha20poly1305::ChaCha20Poly1305;
use const_sv2::{
ELLSWIFT_ENCODING_SIZE, ENCRYPTED_ELLSWIFT_ENCODING_SIZE,
Expand Down Expand Up @@ -97,8 +102,8 @@ pub struct Responder {
cert_validity: u32,
}

impl std::fmt::Debug for Responder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl core::fmt::Debug for Responder {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Responder").finish()
}
}
Expand Down Expand Up @@ -170,15 +175,15 @@ impl Responder {
/// Constructs a new [`Responder`] with the necessary cryptographic state for the Noise NX protocol
/// handshake. It generates ephemeral and static key pairs for the responder and prepares the
/// handshake state. The authority keypair and certificate validity period are also configured.
pub fn new(a: Keypair, cert_validity: u32) -> Box<Self> {
pub fn new<R: rand::Rng + ?Sized>(a: Keypair, cert_validity: u32, rng: &mut R) -> Box<Self> {
let mut self_ = Self {
handshake_cipher: None,
k: None,
n: 0,
ck: [0; 32],
h: [0; 32],
e: Self::generate_key(),
s: Self::generate_key(),
e: Self::generate_key(rng),
s: Self::generate_key(rng),
a,
c1: None,
c2: None,
Expand All @@ -194,17 +199,18 @@ impl Responder {
/// the responder's authority credentials. It verifies that the provided public key matches the
/// corresponding private key, ensuring the authenticity of the authority key pair. The
/// certificate validity duration is also set here. Fails if the key pair is mismatched.
pub fn from_authority_kp(
pub fn from_authority_kp<R: rand::Rng + ?Sized>(
public: &[u8; 32],
private: &[u8; 32],
cert_validity: Duration,
rng: &mut R,
) -> Result<Box<Self>, Error> {
let secp = Secp256k1::new();
let secret = SecretKey::from_slice(private).map_err(|_| Error::InvalidRawPrivateKey)?;
let kp = Keypair::from_secret_key(&secp, &secret);
let pub_ = kp.x_only_public_key().0.serialize();
if public == &pub_[..] {
Ok(Self::new(kp, cert_validity.as_secs() as u32))
Ok(Self::new(kp, cert_validity.as_secs() as u32, rng))

Check warning on line 213 in protocols/v2/noise-sv2/src/responder.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/noise-sv2/src/responder.rs#L213

Added line #L213 was not covered by tests
} else {
Err(Error::InvalidRawPublicKey)
}
Expand All @@ -225,9 +231,11 @@ impl Responder {
///
/// On failure, the method returns an error if there is an issue during encryption, decryption,
/// or any other step of the handshake process.
pub fn step_1(
pub fn step_1<R: rand::Rng + rand::CryptoRng>(
&mut self,
elligatorswift_theirs_ephemeral_serialized: [u8; ELLSWIFT_ENCODING_SIZE],
now: u32,
rng: &mut R,
) -> Result<([u8; INITIATOR_EXPECTED_HANDSHAKE_MESSAGE_SIZE], NoiseCodec), aes_gcm::Error> {
// 4.5.1.2 Responder
Self::mix_hash(self, &elligatorswift_theirs_ephemeral_serialized[..]);
Expand Down Expand Up @@ -286,13 +294,9 @@ impl Responder {
Self::mix_key(self, &ecdh_static[..]);

// 7. appends `EncryptAndHash(SIGNATURE_NOISE_MESSAGE)` to the buffer
let valid_from = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let not_valid_after = valid_from as u32 + self.cert_validity;
let signature_noise_message =
self.get_signature(VERSION, valid_from as u32, not_valid_after);
let valid_from = now;

Check warning on line 297 in protocols/v2/noise-sv2/src/responder.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/noise-sv2/src/responder.rs#L297

Added line #L297 was not covered by tests
let not_valid_after = now + self.cert_validity;
let signature_noise_message = self.get_signature(VERSION, valid_from, not_valid_after, rng);

Check warning on line 299 in protocols/v2/noise-sv2/src/responder.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/noise-sv2/src/responder.rs#L299

Added line #L299 was not covered by tests
let mut signature_part = Vec::with_capacity(ENCRYPTED_SIGNATURE_NOISE_MESSAGE_SIZE);
signature_part.extend_from_slice(&signature_noise_message[..]);
Self::encrypt_and_hash(self, &mut signature_part)?;
Expand Down Expand Up @@ -328,7 +332,13 @@ impl Responder {
// certificate validity period, and a cryptographic signature. The signature is created using
// the responder's static public key and authority keypair, ensuring that the responder's
// identity and certificate validity are cryptographically verifiable.
fn get_signature(&self, version: u16, valid_from: u32, not_valid_after: u32) -> [u8; 74] {
fn get_signature<R: rand::Rng + rand::CryptoRng>(
&self,
version: u16,
valid_from: u32,
not_valid_after: u32,
rng: &mut R,
) -> [u8; 74] {
let mut ret = [0; 74];
let version = version.to_le_bytes();
let valid_from = valid_from.to_le_bytes();
Expand All @@ -343,7 +353,7 @@ impl Responder {
ret[7] = not_valid_after[1];
ret[8] = not_valid_after[2];
ret[9] = not_valid_after[3];
SignatureNoiseMessage::sign(&mut ret, &self.s.x_only_public_key().0, &self.a);
SignatureNoiseMessage::sign(&mut ret, &self.s.x_only_public_key().0, &self.a, rng);
ret
}

Expand Down
Loading

0 comments on commit 2375b22

Please sign in to comment.