├── fuzz ├── src │ ├── lib.rs │ └── bin │ │ ├── network_frame.rs │ │ ├── noise.rs │ │ ├── connection_encryptor.rs │ │ ├── setup_connection.rs │ │ └── mining_messages.rs ├── .gitignore ├── Cargo.toml └── README.md ├── .gitignore ├── Cargo.toml ├── stratumv2 ├── src │ ├── macro_message │ │ ├── mod.rs │ │ ├── setup_connection_success.rs │ │ ├── setup_connection_error.rs │ │ ├── message.rs │ │ └── setup_connection.rs │ ├── common │ │ ├── mod.rs │ │ ├── setup_connection_error_code.rs │ │ ├── channel_endpoint_changed.rs │ │ └── setup_connection.rs │ ├── codec │ │ ├── mod.rs │ │ ├── serialize.rs │ │ ├── deserialize.rs │ │ ├── parser.rs │ │ └── frame.rs │ ├── job_negotiation │ │ ├── setup_connection_error.rs │ │ ├── setup_connection_success.rs │ │ ├── mod.rs │ │ └── setup_connection.rs │ ├── network │ │ ├── mod.rs │ │ ├── message_handler.rs │ │ ├── channel.rs │ │ ├── peer.rs │ │ ├── config.rs │ │ └── encryptor.rs │ ├── types │ │ ├── mod.rs │ │ ├── flags.rs │ │ ├── unix_timestamp.rs │ │ ├── error_code.rs │ │ ├── message_type.rs │ │ ├── primitives.rs │ │ ├── fixed.rs │ │ ├── strings.rs │ │ └── bytes.rs │ ├── mining │ │ ├── setup_connection_error.rs │ │ ├── open_standard_mining_channel_error.rs │ │ ├── open_extended_mining_channel_error.rs │ │ ├── setup_connection_success.rs │ │ ├── mod.rs │ │ ├── update_channel.rs │ │ ├── setup_connection.rs │ │ ├── open_standard_mining_channel_success.rs │ │ ├── open_extended_mining_channel_success.rs │ │ ├── open_mining_channel_error.rs │ │ ├── open_standard_mining_channel.rs │ │ └── open_extended_mining_channel.rs │ ├── noise │ │ ├── noise_session.rs │ │ ├── signed_certificate.rs │ │ ├── types.rs │ │ ├── signature_noise_message.rs │ │ ├── certificate_format.rs │ │ └── mod.rs │ ├── lib.rs │ └── error.rs ├── nx-noise │ ├── Cargo.toml │ ├── src │ │ ├── macros.rs │ │ ├── lib.rs │ │ ├── utils.rs │ │ ├── error.rs │ │ ├── prims.rs │ │ ├── consts.rs │ │ ├── noisesession.rs │ │ └── state.rs │ └── README.md ├── Cargo.toml ├── benches │ └── mining_benchmark.rs └── README.md ├── examples ├── README.md ├── Cargo.toml ├── setup_connection.rs └── noise_handshake.rs ├── README.md └── .github └── workflows └── rust.yml /fuzz/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | hfuzz_target 2 | hfuzz_workspace 3 | target 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | Cargo.lock 3 | TODO.md 4 | *.swp 5 | *.swo 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "stratumv2", 5 | 6 | # Internal 7 | "examples", 8 | ] 9 | -------------------------------------------------------------------------------- /stratumv2/src/macro_message/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod message; 2 | pub(crate) mod setup_connection; 3 | pub(crate) mod setup_connection_error; 4 | pub(crate) mod setup_connection_success; 5 | -------------------------------------------------------------------------------- /fuzz/src/bin/network_frame.rs: -------------------------------------------------------------------------------- 1 | use honggfuzz::fuzz; 2 | use stratumv2::codec::{deserialize, Deserializable, Message}; 3 | 4 | fn main() { 5 | fuzz!(|data: &[u8]| { 6 | deserialize::(&data); 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /fuzz/src/bin/noise.rs: -------------------------------------------------------------------------------- 1 | use honggfuzz::fuzz; 2 | use stratumv2::{ 3 | codec::{deserialize, Deserializable}, 4 | noise::SignatureNoiseMessage, 5 | }; 6 | 7 | fn main() { 8 | fuzz!(|data: &[u8]| { 9 | deserialize::(&data); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /stratumv2/src/common/mod.rs: -------------------------------------------------------------------------------- 1 | mod channel_endpoint_changed; 2 | mod setup_connection; 3 | mod setup_connection_error_code; 4 | 5 | pub use channel_endpoint_changed::ChannelEndpointChanged; 6 | pub use setup_connection::{Protocol, SetupConnection}; 7 | pub use setup_connection_error_code::SetupConnectionErrorCode; 8 | -------------------------------------------------------------------------------- /stratumv2/src/codec/mod.rs: -------------------------------------------------------------------------------- 1 | mod deserialize; 2 | mod frame; 3 | mod parser; 4 | mod serialize; 5 | 6 | pub use deserialize::{deserialize, Deserializable}; 7 | pub(crate) use frame::CHANNEL_BIT_MASK; 8 | pub use frame::{frame, unframe, Frameable, Message}; 9 | pub use parser::ByteParser; 10 | pub use serialize::{serialize, Serializable}; 11 | -------------------------------------------------------------------------------- /stratumv2/nx-noise/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "noiseexplorer_nx" 3 | version = "1.0.2" 4 | authors = ["Symbolic Software "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | blake2-rfc = "0.2.18" 9 | constant_time_eq = "0.1.3" 10 | hacl-star = "0.1.0" 11 | hex = "0.3.2" 12 | rand = "0.6.5" 13 | zeroize = "1.2.0" 14 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ## Examples of how to use Stratum-V2 2 | 3 | This directory contains examples on how to use the `stratumv2` crate. 4 | 5 | All examples can be run: 6 | 7 | ``` 8 | cargo run --example 9 | ``` 10 | 11 | These examples are the bare minimum implementations to demonstrate how to call 12 | into and use the library. They should not be used for real implementations. 13 | -------------------------------------------------------------------------------- /stratumv2/src/job_negotiation/setup_connection_error.rs: -------------------------------------------------------------------------------- 1 | use crate::impl_setup_connection_error; 2 | use crate::job_negotiation::SetupConnectionFlags; 3 | 4 | impl_setup_connection_error!(SetupConnectionFlags); 5 | 6 | #[cfg(test)] 7 | mod tests { 8 | use super::*; 9 | use crate::impl_setup_connection_error_tests; 10 | 11 | impl_setup_connection_error_tests!(SetupConnectionFlags); 12 | } 13 | -------------------------------------------------------------------------------- /stratumv2/nx-noise/src/macros.rs: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------- * 2 | * MACROS * 3 | * ---------------------------------------------------------------- */ 4 | 5 | macro_rules! copy_slices { 6 | ($inslice:expr, $outslice:expr) => { 7 | $outslice[..$inslice.len()].clone_from_slice(&$inslice[..]) 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /stratumv2/nx-noise/README.md: -------------------------------------------------------------------------------- 1 | # NX-Noise 2 | 3 | This code was generated using [Noise Explorer](https://noiseexplorer.com/) for 4 | the `NX` pattern. 5 | 6 | The only modifications made to the generated code is for non-cryptographic 7 | purposes, such as applying the `Clone` trait for certain structs. Any changes 8 | to this generated code should be reviewed very closely and should not edit 9 | any cryptographic operations. 10 | -------------------------------------------------------------------------------- /stratumv2/src/network/mod.rs: -------------------------------------------------------------------------------- 1 | mod channel; 2 | mod config; 3 | mod encryptor; 4 | mod message_handler; 5 | mod peer; 6 | 7 | pub use channel::{new_channel_id, ChanID, Channel, ChannelManager}; 8 | pub use config::{NetworkConfig, NoiseConfig, ServerConfig}; 9 | pub use encryptor::{ConnectionEncryptor, Encryptor}; 10 | pub use message_handler::{JobNegotiationInitiator, MiningInitiator, NewConnReceiver}; 11 | pub use peer::Peer; 12 | -------------------------------------------------------------------------------- /stratumv2/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | mod bytes; 2 | pub(crate) mod error_code; 3 | mod fixed; 4 | pub(crate) mod flags; 5 | mod message_type; 6 | mod primitives; 7 | mod strings; 8 | pub mod unix_timestamp; 9 | 10 | pub use bytes::{B0_16M, B0_255, B0_31, B0_32, B0_64K}; 11 | pub use fixed::{U24, U256}; 12 | pub use message_type::MessageType; 13 | pub use strings::{STR0_255, STR0_32}; 14 | pub use unix_timestamp::system_unix_time_to_u32; 15 | -------------------------------------------------------------------------------- /stratumv2/src/mining/setup_connection_error.rs: -------------------------------------------------------------------------------- 1 | use crate::common::SetupConnectionErrorCode; 2 | use crate::impl_setup_connection_error; 3 | use crate::mining::SetupConnectionFlags; 4 | 5 | impl_setup_connection_error!(SetupConnectionFlags); 6 | 7 | #[cfg(test)] 8 | mod tests { 9 | use super::*; 10 | use crate::impl_setup_connection_error_tests; 11 | 12 | impl_setup_connection_error_tests!(SetupConnectionFlags); 13 | } 14 | -------------------------------------------------------------------------------- /stratumv2/src/mining/open_standard_mining_channel_error.rs: -------------------------------------------------------------------------------- 1 | use crate::impl_message; 2 | use crate::impl_open_mining_channel_error; 3 | use crate::types::MessageType; 4 | 5 | impl_open_mining_channel_error!(OpenStandardMiningChannelError); 6 | 7 | #[cfg(test)] 8 | mod test { 9 | use super::*; 10 | use crate::impl_open_mining_channel_error_tests; 11 | 12 | impl_open_mining_channel_error_tests!(OpenStandardMiningChannelError); 13 | } 14 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples" 3 | version = "0.0.0" 4 | publish = false 5 | edition = "2018" 6 | 7 | [dependencies] 8 | stratumv2 = { version = "0.1.0", path = "../stratumv2" } 9 | rand = "0.7.3" 10 | 11 | [dev-dependencies] 12 | tokio = { version = "1.0.0", features = ["full"] } 13 | 14 | [[example]] 15 | name = "setup_connection" 16 | path = "setup_connection.rs" 17 | 18 | [[example]] 19 | name = "noise_handshake" 20 | path = "noise_handshake.rs" 21 | -------------------------------------------------------------------------------- /fuzz/src/bin/connection_encryptor.rs: -------------------------------------------------------------------------------- 1 | use honggfuzz::fuzz; 2 | use stratumv2::network::{ConnectionEncryptor, Encryptor}; 3 | 4 | fn main() { 5 | let mut receiver = ConnectionEncryptor::new_inbound(None); 6 | fuzz!(|data: &[u8]| { 7 | receiver.recv_handshake(&mut data.to_vec()); 8 | }); 9 | 10 | let mut initiator = ConnectionEncryptor::new_outbound(); 11 | fuzz!(|data: &[u8]| { 12 | initiator.recv_handshake(&mut data.to_vec()); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /stratumv2/src/mining/open_extended_mining_channel_error.rs: -------------------------------------------------------------------------------- 1 | use crate::impl_message; 2 | use crate::impl_open_mining_channel_error; 3 | use crate::mining::OpenMiningChannelErrorCode; 4 | use crate::types::MessageType; 5 | 6 | impl_open_mining_channel_error!(OpenExtendedMiningChannelError); 7 | 8 | #[cfg(test)] 9 | mod test { 10 | use super::*; 11 | use crate::impl_open_mining_channel_error_tests; 12 | 13 | impl_open_mining_channel_error_tests!(OpenExtendedMiningChannelError); 14 | } 15 | -------------------------------------------------------------------------------- /fuzz/src/bin/setup_connection.rs: -------------------------------------------------------------------------------- 1 | use honggfuzz::fuzz; 2 | use stratumv2::{ 3 | codec::{deserialize, Deserializable}, 4 | mining, 5 | }; 6 | 7 | fn main() { 8 | fuzz!(|data: &[u8]| { 9 | deserialize::(&data); 10 | }); 11 | 12 | fuzz!(|data: &[u8]| { 13 | deserialize::(&data); 14 | }); 15 | 16 | fuzz!(|data: &[u8]| { 17 | deserialize::(&data); 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /stratumv2/nx-noise/src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | NX: 3 | -> e 4 | <- e, ee, s, es 5 | -> 6 | <- 7 | */ 8 | 9 | /* ---------------------------------------------------------------- * 10 | * PARAMETERS * 11 | * ---------------------------------------------------------------- */ 12 | 13 | #[macro_use] 14 | pub(crate) mod macros; 15 | 16 | pub(crate) mod prims; 17 | pub(crate) mod state; 18 | pub(crate) mod utils; 19 | 20 | pub mod consts; 21 | pub mod error; 22 | pub mod noisesession; 23 | pub mod types; -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzz" 3 | version = "0.1.0" 4 | authors = ["Christopher Coverdale "] 5 | publish = false 6 | edition = "2018" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | [package.metadata] 10 | cargo-fuzz = true 11 | 12 | [features] 13 | honggfuzz_fuzz = ["honggfuzz"] 14 | 15 | [dependencies] 16 | honggfuzz = { version = "0.5", optional = true } 17 | stratumv2 = { path = "../stratumv2" } 18 | 19 | [workspace] 20 | members = ["."] 21 | 22 | [lib] 23 | name = "stratumv2_fuzz" 24 | -------------------------------------------------------------------------------- /stratumv2/src/codec/serialize.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use std::io; 3 | 4 | /// Trait for encoding and serializing messages and objects according to the 5 | /// Stratum V2 protocol. 6 | pub trait Serializable { 7 | fn serialize(&self, writer: &mut W) -> Result; 8 | } 9 | 10 | /// Helper utility function to serialize a type that implements the Serializable 11 | /// trait and returns the serialized result. 12 | pub fn serialize(val: &T) -> Result> { 13 | let mut buffer = vec![]; 14 | val.serialize(&mut buffer)?; 15 | 16 | Ok(buffer) 17 | } 18 | -------------------------------------------------------------------------------- /stratumv2/src/codec/deserialize.rs: -------------------------------------------------------------------------------- 1 | use crate::{codec::ByteParser, error::Result}; 2 | 3 | /// Trait for deserializing bytes to most Stratum V2 messages. 4 | pub trait Deserializable { 5 | fn deserialize(parser: &mut ByteParser) -> Result 6 | where 7 | Self: std::marker::Sized; 8 | } 9 | 10 | /// Helper utility function to deserialize a byte-stream into a type that 11 | /// implements the Serializable trait and returns the deserialized result. 12 | pub fn deserialize(bytes: &[u8]) -> Result { 13 | let mut parser = ByteParser::new(bytes, 0); 14 | T::deserialize(&mut parser) 15 | } 16 | -------------------------------------------------------------------------------- /stratumv2/nx-noise/src/utils.rs: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------- * 2 | * UTILITY FUNCTIONS * 3 | * ---------------------------------------------------------------- */ 4 | use crate::consts::{EMPTY_HASH, HASHLEN, NONCE_LENGTH}; 5 | 6 | pub(crate) fn from_slice_hashlen(bytes: &[u8]) -> [u8; HASHLEN] { 7 | let mut array = EMPTY_HASH; 8 | let bytes = &bytes[..array.len()]; 9 | array.copy_from_slice(bytes); 10 | array 11 | } 12 | pub(crate) fn prep_nonce(n: u64) -> [u8;12] { 13 | let mut nonce: [u8; NONCE_LENGTH] = [0_u8; NONCE_LENGTH]; 14 | nonce[4..].copy_from_slice(&n.to_le_bytes()); 15 | nonce 16 | } 17 | -------------------------------------------------------------------------------- /stratumv2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stratumv2" 3 | version = "0.1.0" 4 | authors = [ 5 | "Christopher Coverdale ", 6 | "Christopher Patton ", 7 | ] 8 | edition = "2018" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | noiseexplorer_nx = { version = "1.0.2", path = "./nx-noise" } 14 | rand = "0.7.3" 15 | bitflags = "1.2.1" 16 | thiserror = "1.0.24" 17 | bitcoin = "0.26.0" 18 | 19 | [dev-dependencies] 20 | criterion = "0.3" 21 | 22 | [[bench]] 23 | name = "mining_benchmark" 24 | harness = false 25 | 26 | [dependencies.ed25519-dalek] 27 | version = "1" 28 | -------------------------------------------------------------------------------- /stratumv2/src/job_negotiation/setup_connection_success.rs: -------------------------------------------------------------------------------- 1 | use crate::{impl_bitflags_serde, impl_setup_connection_success}; 2 | 3 | bitflags!( 4 | /// Feature flags for the SetupConnectionSuccess message from the Server to 5 | /// the Client for the JobNegotiation Protocol. 6 | pub struct SetupConnectionSuccessFlags: u32 { 7 | const NONE = 0; 8 | } 9 | ); 10 | 11 | impl_bitflags_serde!(SetupConnectionSuccessFlags); 12 | impl_setup_connection_success!(SetupConnectionSuccessFlags); 13 | 14 | #[cfg(test)] 15 | mod tests { 16 | use super::*; 17 | use crate::impl_setup_connection_success_tests; 18 | 19 | impl_setup_connection_success_tests!(SetupConnectionSuccessFlags); 20 | } 21 | -------------------------------------------------------------------------------- /stratumv2/src/job_negotiation/mod.rs: -------------------------------------------------------------------------------- 1 | //! The sub protocol allows a Job Negotiator to intermediate between Upstream 2 | //! Nodes (Mining Pools), Bitcoind and Proxies/Mining Devices. The protocol 3 | //! allows a block template to be negotiated with a mining pool, including the 4 | //! transaction set. 5 | //! 6 | //! The results of the job negotiation with a Mining Pool means downstream 7 | //! nodes (Mining Farms/Devices) can use the same negotiation result on all 8 | //! their connections. 9 | 10 | mod setup_connection; 11 | mod setup_connection_error; 12 | mod setup_connection_success; 13 | 14 | pub use setup_connection::{SetupConnection, SetupConnectionFlags}; 15 | pub use setup_connection_error::SetupConnectionError; 16 | pub use setup_connection_success::{SetupConnectionSuccess, SetupConnectionSuccessFlags}; 17 | -------------------------------------------------------------------------------- /fuzz/README.md: -------------------------------------------------------------------------------- 1 | # Fuzzing 2 | 3 | 4 | ## Install 5 | 6 | - Install dependencies on Debian based systems: 7 | 8 | ``` 9 | sudo apt install build-essential binutils-dev libunwind-dev 10 | ``` 11 | 12 | - Install honggfuzz 13 | 14 | ``` 15 | cargo install --force honggfuzz 16 | ``` 17 | 18 | ## Run 19 | 20 | ``` 21 | export CPU_COUNT=1 # replace as needed 22 | export HFUZZ_BUILD_ARGS="--features honggfuzz_fuzz" 23 | export HFUZZ_RUN_ARGS="-n $CPU_COUNT -N 100000 --exit_upon_crash" 24 | export HFUZZ_DEBUGGER=rust-gdb 25 | ``` 26 | 27 | View all the fuzzing targets: 28 | 29 | ``` 30 | ls ./src/bin 31 | ``` 32 | 33 | Run a fuzzing target: 34 | 35 | ``` 36 | cargo hfuzz run 37 | ``` 38 | 39 | Run in debug environment to view the crash: 40 | 41 | ``` 42 | cargo hfuzz run-debug hfuzz_workspace/*/*.fuzz 43 | ``` 44 | -------------------------------------------------------------------------------- /stratumv2/src/codec/parser.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | 3 | /// A custom iterator-like struct. It's used to extract segments 4 | /// from a slice using by providing an offset to return the bytes from start 5 | /// to step. 6 | pub struct ByteParser<'a> { 7 | bytes: &'a [u8], 8 | start: usize, 9 | } 10 | 11 | impl<'a> ByteParser<'a> { 12 | pub fn new(bytes: &'a [u8], start: usize) -> ByteParser { 13 | ByteParser { bytes, start } 14 | } 15 | 16 | pub fn next_by(&mut self, step: usize) -> Result<&'a [u8]> { 17 | let offset = self.start + step; 18 | 19 | let b = self.bytes.get(self.start..offset); 20 | if b.is_none() { 21 | return Err(Error::ParseError("out of bounds error".into())); 22 | } 23 | 24 | self.start = offset; 25 | Ok(b.unwrap()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /fuzz/src/bin/mining_messages.rs: -------------------------------------------------------------------------------- 1 | use honggfuzz::fuzz; 2 | use stratumv2::{ 3 | codec::{deserialize, Deserializable}, 4 | mining, 5 | }; 6 | 7 | fn main() { 8 | fuzz!(|data: &[u8]| { 9 | deserialize::(&data); 10 | }); 11 | 12 | fuzz!(|data: &[u8]| { 13 | deserialize::(&data); 14 | }); 15 | 16 | fuzz!(|data: &[u8]| { 17 | deserialize::(&data); 18 | }); 19 | 20 | fuzz!(|data: &[u8]| { 21 | deserialize::(&data); 22 | }); 23 | 24 | fuzz!(|data: &[u8]| { 25 | deserialize::(&data); 26 | }); 27 | 28 | fuzz!(|data: &[u8]| { 29 | deserialize::(&data); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /stratumv2/src/noise/noise_session.rs: -------------------------------------------------------------------------------- 1 | use crate::noise::types::StaticKeyPair; 2 | use noiseexplorer_nx::types::Keypair; 3 | 4 | /// NoiseSession is a struct that contains all the state required to handle a 5 | /// key exchange and subsequent encrypted communication. 6 | pub type NoiseSession = noiseexplorer_nx::noisesession::NoiseSession; 7 | 8 | /// Creates a NoiseSession for a responder, this will be the Upstream Node (Server) 9 | /// with the option of using a pre-determined StaticKeyPair. 10 | pub fn new_noise_responder(static_keypair: Option) -> NoiseSession { 11 | let key = match static_keypair { 12 | Some(k) => k, 13 | None => Keypair::default(), 14 | }; 15 | 16 | NoiseSession::init_session(false, &[], key) 17 | } 18 | 19 | /// Creates a NoiseSession for an initiator, this will be the Downstream Node (Client). 20 | pub fn new_noise_initiator() -> NoiseSession { 21 | NoiseSession::init_session(true, &[], Keypair::default()) 22 | } 23 | -------------------------------------------------------------------------------- /stratumv2/src/types/flags.rs: -------------------------------------------------------------------------------- 1 | pub mod macro_prelude { 2 | pub use crate::codec::{ByteParser, Deserializable, Serializable}; 3 | pub use crate::error::{Error, Result}; 4 | } 5 | 6 | /// Implemenation of all the ser/de traits for bitflags. 7 | #[doc(hidden)] 8 | #[macro_export] 9 | macro_rules! impl_bitflags_serde { 10 | ($name:ident) => { 11 | impl_bitflags_serde!($name, u32); 12 | }; 13 | 14 | ($name:ident, $underlying:ident) => { 15 | use crate::types::flags::macro_prelude::*; 16 | 17 | impl Serializable for $name { 18 | fn serialize(&self, writer: &mut W) -> Result { 19 | Ok(self.bits().serialize(writer)?) 20 | } 21 | } 22 | 23 | impl Deserializable for $name { 24 | fn deserialize(parser: &mut ByteParser) -> Result<$name> { 25 | $name::from_bits($underlying::deserialize(parser)?).ok_or(Error::UnknownFlags()) 26 | } 27 | } 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /stratumv2/src/common/setup_connection_error_code.rs: -------------------------------------------------------------------------------- 1 | use crate::impl_error_code_enum; 2 | 3 | /// Contains the error codes for the [SetupConnectionError](struct.SetupConnectionError.html) message. 4 | /// Each error code has a default STR0_255 message. 5 | #[derive(Debug, PartialEq, Clone, Copy)] 6 | pub enum SetupConnectionErrorCode { 7 | /// Indicates the server has received a feature flag from a client that 8 | /// the server does not support. 9 | UnsupportedFeatureFlags, 10 | 11 | /// Indicates the server has received a connection request using a protcol 12 | /// the server does not support. 13 | UnsupportedProtocol, 14 | 15 | // TODO: What is the difference between protocol version mismatch 16 | // and unsupported protocol? 17 | ProtocolVersionMismatch, 18 | } 19 | 20 | impl_error_code_enum!( 21 | SetupConnectionErrorCode, 22 | SetupConnectionErrorCode::UnsupportedFeatureFlags => "unsupported-feature-flags", 23 | SetupConnectionErrorCode::UnsupportedProtocol => "unsupported-protocol", 24 | SetupConnectionErrorCode::ProtocolVersionMismatch => "protocol-version-mismatch" 25 | ); 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![workflow](https://github.com/ccdle12/rust-stratum-v2/actions/workflows/rust.yml/badge.svg) 2 | 3 | # Rust Stratum V2 4 | 5 | A WIP library implementation of the [Stratum V2 Protocol](https://braiins.com/stratum-v2). 6 | 7 | The detailed spec can be found [here](https://docs.google.com/document/d/1FadCWj-57dvhxsnFM_7X806qyvhR0u3i85607bGHxvg/edit#heading=h.we2r5emgsjcx). 8 | 9 | ## Building 10 | 11 | The library can be built and tested using [`cargo`](https://github.com/rust-lang/cargo/): 12 | 13 | ``` 14 | git clone git@github.com:ccdle12/rust-stratum-v2.git 15 | cd rust-stratum-v2 16 | cargo build 17 | ``` 18 | 19 | ## Tests 20 | 21 | You can run tests with: 22 | 23 | ``` 24 | cargo test 25 | ``` 26 | 27 | Please refer to the [`cargo` documentation](https://doc.rust-lang.org/stable/cargo/) for more detailed instructions. 28 | 29 | See [fuzz tests](/fuzz/README.md) for instructions on how to run fuzzing. 30 | 31 | ## Documentation 32 | 33 | Documentation can be built locally using cargo: 34 | 35 | ``` 36 | cargo doc --no-deps --open 37 | ``` 38 | 39 | ## Examples 40 | 41 | Examples can be found [here](/examples) and the [README](/examples/README.md) 42 | contains instructions on how to run the examples. 43 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | HFUZZ_BUILD_ARGS: "--features honggfuzz_fuzz" 12 | HFUZZ_RUN_ARGS: "-n 1 -N 10000 --exit_upon_crash" 13 | HFUZZ_DEBUGGER: rust-gdb 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Check out source code 20 | uses: actions/checkout@v2 21 | - name: Build 22 | run: cargo build --verbose 23 | - name: Run tests 24 | run: cargo test --verbose 25 | 26 | fuzz: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Check out source code 30 | uses: actions/checkout@v2 31 | - name: Install dependencies 32 | run: sudo apt install build-essential binutils-dev libunwind-dev -y 33 | - name: Install Honggfuzz 34 | run: cargo install --force honggfuzz 35 | - name: Fuzz test SetupConnection 36 | run: cd fuzz && cargo hfuzz run setup_connection 37 | - name: Fuzz test Mining Protocol Messages 38 | run: cd fuzz && cargo hfuzz run mining_messages 39 | - name: Fuzz test Network Frame 40 | run: cd fuzz && cargo hfuzz run network_frame 41 | - name: Fuzz test Noise 42 | run: cd fuzz && cargo hfuzz run noise 43 | - name: Fuzz test ConnectionEncryptor 44 | run: cd fuzz && cargo hfuzz run connection_encryptor 45 | -------------------------------------------------------------------------------- /stratumv2/src/job_negotiation/setup_connection.rs: -------------------------------------------------------------------------------- 1 | use crate::{impl_bitflags_serde, impl_setup_connection}; 2 | 3 | bitflags!( 4 | /// Feature flags that can be passed to a SetupConnection message for the 5 | /// job negotiation protocol. Each flag corresponds to a set bit. 6 | pub struct SetupConnectionFlags: u32 { 7 | // TODO: Add hyperlinks to all everything between `` 8 | /// Flag indicating that the `mining_job_token` from `AllocateMiningJobToken.Success` 9 | /// can be used immediately on a mining connection in `SetCustomMiningJob` 10 | /// message. 11 | const REQUIRES_ASYNC_JOB_MINING = (1 << 0); 12 | } 13 | ); 14 | 15 | impl_bitflags_serde!(SetupConnectionFlags); 16 | impl_setup_connection!(SetupConnectionFlags); 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | use super::*; 21 | use crate::codec::{deserialize, serialize}; 22 | use crate::impl_setup_connection_tests; 23 | 24 | #[test] 25 | fn flags_serialize() { 26 | assert_eq!( 27 | serialize(&SetupConnectionFlags::REQUIRES_ASYNC_JOB_MINING).unwrap(), 28 | 0x01u32.to_le_bytes() 29 | ); 30 | } 31 | 32 | #[test] 33 | fn flags_deserialize() { 34 | assert_eq!( 35 | deserialize::(&0x01u32.to_le_bytes()).unwrap(), 36 | SetupConnectionFlags::REQUIRES_ASYNC_JOB_MINING, 37 | ); 38 | } 39 | 40 | impl_setup_connection_tests!(SetupConnectionFlags); 41 | } 42 | -------------------------------------------------------------------------------- /stratumv2/src/network/message_handler.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | common::SetupConnection, 3 | error::Result, 4 | job_negotiation, mining, 5 | network::{Encryptor, Peer}, 6 | }; 7 | 8 | /// A trait that should be applied to upstream devices such as a Mining Pool Server 9 | /// that can handle [SetupConnection Messages](../common/setup_connection/enum.SetupConnection.html). 10 | pub trait NewConnReceiver { 11 | fn handle_new_conn( 12 | &self, 13 | server_flags: &mining::SetupConnectionFlags, 14 | new_conn: SetupConnection, 15 | peer: &mut Peer, 16 | ) -> Result<()>; 17 | } 18 | 19 | /// A trait that should be applied to downstream devices such as Mining Devices 20 | /// and proxies that can handle responses after attempting to open a New Mining 21 | /// Connection. 22 | pub trait MiningInitiator { 23 | fn handle_mining_conn_success(conn_success: mining::SetupConnectionSuccess); 24 | fn handle_mining_conn_error(conn_error: mining::SetupConnectionError); 25 | } 26 | 27 | /// A trait that should be applied to downstream devices such as Mining 28 | /// Proxies and Job Negotiators to handle responses from upstream nodes when 29 | /// attempting to open a Job Negotiation connection. 30 | pub trait JobNegotiationInitiator { 31 | fn handle_jn_conn_success(conn_success: job_negotiation::SetupConnectionSuccess); 32 | fn handle_jn_conn_error(conn_error: job_negotiation::SetupConnectionError); 33 | } 34 | -------------------------------------------------------------------------------- /stratumv2/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A library implementation of the Stratum V2 Protocol. 2 | //! 3 | //! Stratum V2 sources: 4 | //! - [Stratum V2 Overview](https://braiins.com/stratum-v2) 5 | //! - [Stratum V2 Specification](https://docs.google.com/document/d/1FadCWj-57dvhxsnFM_7X806qyvhR0u3i85607bGHxvg/edit) 6 | 7 | #[macro_use] 8 | extern crate bitflags; 9 | extern crate thiserror; 10 | 11 | pub extern crate bitcoin; 12 | 13 | /// Common messages and flags for all sub protocols. 14 | pub mod common; 15 | 16 | /// Errors returned in the library. 17 | pub mod error; 18 | 19 | /// Job Negotiation is a sub protocol of Stratum V2. 20 | pub mod job_negotiation; 21 | mod macro_message; 22 | 23 | /// Mining is the main sub protocol of Stratum V2. 24 | pub mod mining; 25 | 26 | /// Codec contains all the functionality to serialize, deserialize and frame network messages. 27 | /// This also includes the required Serializable, Deserializable and Frameable traits. 28 | pub mod codec; 29 | 30 | /// Types used in all Stratum V2 Protocols. 31 | pub mod types; 32 | 33 | /// Noise contains all the required messages and functions to perform the Noise 34 | /// Handshake, creating a symmetric key to perform secure communication. 35 | /// This module contains functions to verify and generate signatures 36 | /// for both Client and Server to attest to the authenticty of an Upstream Node. 37 | pub mod noise; 38 | 39 | /// Structs and Traits required for a networked implementation 40 | pub mod network; 41 | -------------------------------------------------------------------------------- /stratumv2/benches/mining_benchmark.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | use stratumv2::{ 3 | mining::{ 4 | OpenExtendedMiningChannel, OpenExtendedMiningChannelError, OpenMiningChannelErrorCode, 5 | }, 6 | parse::{deserialize, serialize}, 7 | types::U256, 8 | }; 9 | 10 | fn criterion_benchmark(c: &mut Criterion) { 11 | c.bench_function("OpenExtendedMiningChannel serde", |b| { 12 | b.iter(|| { 13 | let msg = OpenExtendedMiningChannel::new( 14 | black_box(1u32), 15 | black_box("user id"), 16 | black_box(3.0f32), 17 | black_box(U256([4u8; 32])), 18 | black_box(5u16), 19 | ) 20 | .unwrap(); 21 | 22 | let ser = serialize(&msg).unwrap(); 23 | deserialize::(&ser).unwrap(); 24 | }) 25 | }); 26 | 27 | c.bench_function("OpenExtendedMiningChannelError serde", |b| { 28 | b.iter(|| { 29 | let msg = OpenExtendedMiningChannelError::new( 30 | black_box(1), 31 | black_box(OpenMiningChannelErrorCode::UnknownUser), 32 | ) 33 | .unwrap(); 34 | 35 | let ser = serialize(&msg).unwrap(); 36 | deserialize::(&ser).unwrap(); 37 | }) 38 | }); 39 | } 40 | 41 | criterion_group!(benches, criterion_benchmark); 42 | criterion_main!(benches); 43 | -------------------------------------------------------------------------------- /stratumv2/src/common/channel_endpoint_changed.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | codec::{ByteParser, Deserializable, Frameable, Serializable}, 3 | error::Result, 4 | impl_message, 5 | types::MessageType, 6 | }; 7 | use std::io; 8 | 9 | impl_message!( 10 | /// When a channel’s upstream or downstream endpoint changes and that channel had previously sent 11 | /// messages with channel_msg bitset of unknown extension_type, the intermediate proxy MUST send a 12 | /// ChannelEndpointChanged message. Upon receipt thereof, any extension state (including version 13 | /// negotiation and the presence of support for a given extension) MUST be reset and 14 | /// version/presence negotiation must begin again. 15 | ChannelEndpointChanged, 16 | 17 | /// The channel which has changed endpoint. 18 | channel_id u32 19 | ); 20 | 21 | impl ChannelEndpointChanged { 22 | pub fn new(channel_id: u32) -> Result { 23 | Ok(ChannelEndpointChanged { channel_id }) 24 | } 25 | } 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | use super::*; 30 | use crate::impl_message_tests; 31 | 32 | fn make_deserialized_channel_endpoint_changed() -> ChannelEndpointChanged { 33 | ChannelEndpointChanged::new(5u32).unwrap() 34 | } 35 | 36 | fn make_serialized_channel_endpoint_changed() -> Vec { 37 | return vec![0x05, 0x00, 0x00, 0x00]; 38 | } 39 | 40 | impl_message_tests!( 41 | ChannelEndpointChanged, 42 | make_serialized_channel_endpoint_changed, 43 | make_deserialized_channel_endpoint_changed 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /stratumv2/src/mining/setup_connection_success.rs: -------------------------------------------------------------------------------- 1 | use crate::{impl_bitflags_serde, impl_setup_connection_success}; 2 | 3 | bitflags!( 4 | /// Feature flags for the SetupConnectionSuccess message from the Server to 5 | /// the Client for the Mining Protocol. 6 | pub struct SetupConnectionSuccessFlags: u32 { 7 | const NONE = 0; 8 | const REQUIRES_FIXED_VERSION = (1 << 0); 9 | const REQUIRES_EXTENDED_CHANNELS = (1 << 1); 10 | } 11 | ); 12 | 13 | impl_bitflags_serde!(SetupConnectionSuccessFlags); 14 | impl_setup_connection_success!(SetupConnectionSuccessFlags); 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use super::*; 19 | use crate::codec::{deserialize, frame, serialize, unframe, Message}; 20 | use crate::impl_setup_connection_success_tests; 21 | 22 | #[test] 23 | fn flags_serialize() { 24 | assert_eq!( 25 | serialize(&SetupConnectionSuccessFlags::REQUIRES_FIXED_VERSION).unwrap(), 26 | 0x01u32.to_le_bytes() 27 | ); 28 | assert_eq!( 29 | serialize(&SetupConnectionSuccessFlags::REQUIRES_EXTENDED_CHANNELS).unwrap(), 30 | 0x02u32.to_le_bytes() 31 | ); 32 | } 33 | 34 | #[test] 35 | fn flags_deserialize() { 36 | assert_eq!( 37 | deserialize::(&0x01u32.to_le_bytes()).unwrap(), 38 | SetupConnectionSuccessFlags::REQUIRES_FIXED_VERSION, 39 | ); 40 | assert_eq!( 41 | deserialize::(&0x02u32.to_le_bytes()).unwrap(), 42 | SetupConnectionSuccessFlags::REQUIRES_EXTENDED_CHANNELS, 43 | ); 44 | } 45 | 46 | impl_setup_connection_success_tests!(SetupConnectionSuccessFlags); 47 | } 48 | -------------------------------------------------------------------------------- /stratumv2/src/network/channel.rs: -------------------------------------------------------------------------------- 1 | use crate::mining::{OpenExtendedMiningChannel, OpenStandardMiningChannel}; 2 | use std::{collections::HashMap, sync::Mutex}; 3 | 4 | use rand::Rng; 5 | 6 | /// ChanID is a type assigned to identify channels on a connection. 7 | /// The type de/serialzing for messaging is already handled using the native 8 | /// type u32. 9 | pub type ChanID = u32; 10 | 11 | /// Generate a new random channel_id. 12 | pub fn new_channel_id() -> ChanID { 13 | rand::thread_rng().gen_range(0, ChanID::MAX) 14 | } 15 | 16 | /// Represents a Channel opened on a connection. The Channel holds stateful 17 | /// information about the Channel for both Upstream and Downstream devices. 18 | #[derive(Debug, Clone)] 19 | pub enum Channel { 20 | StandardMiningChannel { 21 | id: ChanID, 22 | channel: OpenStandardMiningChannel, 23 | }, 24 | ExtendedMiningChannel { 25 | id: ChanID, 26 | channel: OpenExtendedMiningChannel, 27 | }, 28 | } 29 | 30 | // TODO: Replace the u32 with a ConnID type. 31 | /// Holds a collection of [Channels](./enum.Channel.html) according to the ChanID and linked to a 32 | /// ConnID, representing the networked connection. 33 | pub struct ChannelManager { 34 | /// Contains multiple channels that belong to a certain connection according 35 | /// to the Connection ID. 36 | pub channels: Mutex>>, 37 | } 38 | 39 | impl ChannelManager { 40 | pub fn new() -> Self { 41 | ChannelManager { 42 | channels: Mutex::new(HashMap::new()), 43 | } 44 | } 45 | } 46 | 47 | #[cfg(test)] 48 | mod tests { 49 | use super::*; 50 | 51 | #[test] 52 | fn channel_id_generate() { 53 | assert!(new_channel_id() <= u32::MAX) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /stratumv2/src/types/unix_timestamp.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use std::time::SystemTime; 3 | 4 | /// Convert SystemTime to a Unix timestamp as a u32. 5 | pub fn system_unix_time_to_u32(time: &SystemTime) -> Result { 6 | Ok(time 7 | .duration_since(SystemTime::UNIX_EPOCH) 8 | .map(|duration| duration.as_secs() as u32)?) 9 | } 10 | 11 | /// A helper function that returns the current unix time in seconds as a u32. 12 | pub fn unix_u32_now() -> Result { 13 | system_unix_time_to_u32(&SystemTime::now()) 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use super::*; 19 | 20 | use crate::error::Error; 21 | use std::time::Duration; 22 | 23 | #[test] 24 | fn system_unix_time_epoch() { 25 | let five_after = SystemTime::UNIX_EPOCH 26 | .checked_add(Duration::new(5, 0)) 27 | .unwrap(); 28 | assert_eq!(system_unix_time_to_u32(&five_after).unwrap(), 5u32); 29 | } 30 | 31 | #[test] 32 | fn system_unix_time_resolution() { 33 | let five_after = SystemTime::UNIX_EPOCH 34 | .checked_add(Duration::new(5, 3)) 35 | .unwrap(); 36 | assert_eq!(system_unix_time_to_u32(&five_after).unwrap(), 5u32); 37 | } 38 | 39 | #[test] 40 | fn system_unix_time_error() { 41 | let five_till = SystemTime::UNIX_EPOCH 42 | .checked_sub(Duration::new(5, 3)) 43 | .unwrap(); 44 | assert!(matches!( 45 | system_unix_time_to_u32(&five_till), 46 | Err(Error::SystemTimeError { .. }) 47 | )); 48 | } 49 | 50 | #[test] 51 | fn unix_u32_now_sequence() { 52 | let first = unix_u32_now().unwrap(); 53 | let second = unix_u32_now().unwrap(); 54 | assert!(first <= second); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /stratumv2/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | /// The main error type for this library. 4 | #[derive(Error, Debug)] 5 | pub enum Error { 6 | #[error(transparent)] 7 | Base58Error(#[from] bitcoin::util::base58::Error), 8 | 9 | #[error("`{0}`")] 10 | VersionError(String), 11 | 12 | #[error(transparent)] 13 | IOError(#[from] std::io::Error), 14 | 15 | #[error(transparent)] 16 | Utf8Error(#[from] std::str::Utf8Error), 17 | 18 | #[error(transparent)] 19 | FromUtf8Error(#[from] std::string::FromUtf8Error), 20 | 21 | #[error("`{0}`")] 22 | ProtocolMismatchError(String), 23 | 24 | #[error("`{0}`")] 25 | RequirementError(String), 26 | 27 | #[error("`{0}`")] 28 | DeserializationError(String), 29 | 30 | #[error("`{0}`")] 31 | ParseError(String), 32 | 33 | #[error(transparent)] 34 | AuthorityKeyError(#[from] ed25519_dalek::ed25519::Error), 35 | 36 | #[error(transparent)] 37 | SystemTimeError(#[from] std::time::SystemTimeError), 38 | 39 | #[error(transparent)] 40 | TryFromSliceError(#[from] std::array::TryFromSliceError), 41 | 42 | #[error("Unimplemented")] 43 | Unimplemented(), 44 | 45 | #[error("The error code is invalid")] 46 | UnknownErrorCode(), 47 | 48 | #[error("the received message type is unknown")] 49 | UnknownMessageType(), 50 | 51 | #[error("the received flags are unknown")] 52 | UnknownFlags(), 53 | 54 | #[error("parsed message type `{0}/{1}` does not match expected message")] 55 | UnexpectedMessageType(u16, u8), 56 | 57 | #[error("parsed channel bit `{0}` does not match expected message")] 58 | UnexpectedChannelBit(bool), 59 | 60 | #[error(transparent)] 61 | NoiseError(#[from] noiseexplorer_nx::error::NoiseError), 62 | } 63 | 64 | /// Alias Result type for the library. 65 | pub type Result = std::result::Result; 66 | -------------------------------------------------------------------------------- /stratumv2/src/network/peer.rs: -------------------------------------------------------------------------------- 1 | use crate::{codec::Message, common::SetupConnection, network::Encryptor}; 2 | use std::{mem, sync::Mutex}; 3 | 4 | // TODO: Come up with a better name than Peer. 5 | /// Peer is not really a Peer since this is a Client-Server architecture but 6 | /// for a lack of a better term, this "Peer" struct will hold state information 7 | /// about a Connection. It distinctly does NOT hold any network structs such as 8 | /// TCPStreams, only state and logic required to execute the business logic 9 | /// of the device. 10 | pub struct Peer { 11 | /// An encryptor used to de/encrypt messages on this connection. 12 | pub encryptor: E, 13 | 14 | /// The required SetupConnection message on this connection. If this message 15 | /// doesn't exist then we'll assume as a Server, we are waiting to receive 16 | /// one and won't process any further stratumv2 messages. If as a Client, 17 | /// we are assuming that we are waiting to send one to initiate a stratumv2 18 | /// connection. 19 | pub setup_conn_msg: Option, 20 | 21 | /// Outgoing message buffer used to queue messages to be sent to the 22 | /// counterparty on this connection. This would typically messages queued 23 | /// by message handlers receiving and processing a message and requiring 24 | /// to send a response. 25 | pub pending_msg_buffer: Mutex>, 26 | } 27 | 28 | impl Peer 29 | where 30 | E: Encryptor, 31 | { 32 | pub fn new(encryptor: E) -> Self { 33 | Peer { 34 | encryptor, 35 | setup_conn_msg: None, 36 | pending_msg_buffer: Mutex::new(Vec::new()), 37 | } 38 | } 39 | 40 | /// Drains the Messages from the pending_msg_buffer in order them to be sent 41 | /// over the wire. An empty buffer is left in it's place. 42 | pub fn get_pending_msgs(&self) -> Vec { 43 | let mut result = Vec::new(); 44 | mem::swap(&mut *self.pending_msg_buffer.lock().unwrap(), &mut result); 45 | result 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /stratumv2/nx-noise/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | #[derive(Debug)] 4 | pub enum NoiseError { 5 | DecryptionError, 6 | UnsupportedMessageLengthError, 7 | ExhaustedNonceError, 8 | InvalidKeyError, 9 | InvalidPublicKeyError, 10 | EmptyKeyError, 11 | InvalidInputError, 12 | DerivePublicKeyFromEmptyKeyError, 13 | Hex(hex::FromHexError), 14 | MissingnsError, 15 | MissingneError, 16 | MissingHsMacError, 17 | MissingrsError, 18 | MissingreError, 19 | } 20 | 21 | impl std::fmt::Display for NoiseError { 22 | #[inline] 23 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 24 | match *self { 25 | NoiseError::DecryptionError => write!(f, "Unsuccesful decryption."), 26 | NoiseError::UnsupportedMessageLengthError => write!(f, "Unsupported Message Length."), 27 | NoiseError::ExhaustedNonceError => write!( 28 | f, 29 | "Reached maximum number of messages that can be sent for this session." 30 | ), 31 | NoiseError::DerivePublicKeyFromEmptyKeyError => { 32 | write!(f, "Unable to derive PublicKey.") 33 | } 34 | NoiseError::InvalidKeyError => write!(f, "Invalid Key."), 35 | NoiseError::InvalidPublicKeyError => write!(f, "Invalid Public Key."), 36 | NoiseError::EmptyKeyError => write!(f, "Empty Key."), 37 | NoiseError::InvalidInputError => write!(f, "Invalid input length."), 38 | NoiseError::MissingnsError => write!(f, "Invalid message length."), 39 | NoiseError::MissingHsMacError => write!(f, "Invalid message length."), 40 | NoiseError::MissingneError => write!(f, "Invalid message length."), 41 | NoiseError::MissingrsError => write!(f, "Invalid message length."), 42 | NoiseError::MissingreError => write!(f, "Invalid message length."), 43 | NoiseError::Hex(ref e) => e.fmt(f), 44 | } 45 | } 46 | } 47 | 48 | impl Error for NoiseError { 49 | fn source(&self) -> Option<&(dyn Error + 'static)> { 50 | Some(self) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /stratumv2/src/mining/mod.rs: -------------------------------------------------------------------------------- 1 | //! This protocol is the direct successor to Stratum V1 and is the only required 2 | //! sub protocol. Mining devices use this protocol to communicate with Upstream 3 | //! Nodes (Proxies or Mining Pools). 4 | //! 5 | //! The protocol has three types of communication channels: 6 | //! - `Standard Channels`: A communication channel with upstream nodes where the 7 | //! coinbase transaction and merkle path are not manipulated. 8 | //! 9 | //! - `Extended Channels`: A communication channel allowing more advanced use cases 10 | //! such as translation between v1 to v2, difficulty aggregation, 11 | //! and `custom search space splitting` 12 | //! 13 | //! - `Group Channels`: A communication channel that is a collection of `Standard Channels` 14 | //! opened to a particular connection. The group is addressable 15 | //! through a common communication channel. 16 | 17 | mod open_extended_mining_channel; 18 | mod open_extended_mining_channel_error; 19 | mod open_extended_mining_channel_success; 20 | mod open_mining_channel_error; 21 | mod open_standard_mining_channel; 22 | mod open_standard_mining_channel_error; 23 | mod open_standard_mining_channel_success; 24 | mod setup_connection; 25 | mod setup_connection_error; 26 | mod setup_connection_success; 27 | mod update_channel; 28 | 29 | pub use open_extended_mining_channel::OpenExtendedMiningChannel; 30 | pub use open_extended_mining_channel_error::OpenExtendedMiningChannelError; 31 | pub use open_extended_mining_channel_success::OpenExtendedMiningChannelSuccess; 32 | pub use open_mining_channel_error::OpenMiningChannelErrorCode; 33 | pub use open_standard_mining_channel::OpenStandardMiningChannel; 34 | pub use open_standard_mining_channel_error::OpenStandardMiningChannelError; 35 | pub use open_standard_mining_channel_success::OpenStandardMiningChannelSuccess; 36 | pub use setup_connection::{SetupConnection, SetupConnectionFlags}; 37 | pub use setup_connection_error::SetupConnectionError; 38 | pub use setup_connection_success::{SetupConnectionSuccess, SetupConnectionSuccessFlags}; 39 | pub use update_channel::UpdateChannel; 40 | -------------------------------------------------------------------------------- /stratumv2/src/mining/update_channel.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::Result, impl_message, types::U256}; 2 | 3 | impl_message!( 4 | /// UpdateChannel is sent from the Client to a Server. This message is used by 5 | /// the Client to notify the server about specific changes to a channel. 6 | UpdateChannel, 7 | 8 | /// The unique identifier of the channel. 9 | channel_id u32, 10 | 11 | /// The expected [h/s] (hash rate/per second) of the device or the 12 | /// cumulative rate on the channel if multiple devices are connected 13 | /// downstream. Proxies MUST send 0.0f when there are no mining devices 14 | /// connected yet. 15 | nominal_hash_rate f32, 16 | 17 | /// The Max Target that can be accepted by the connected device or 18 | /// multiple devices downstream. In this case, if the max_target of 19 | /// the channel is smaller than the current max target, the Server MUST 20 | /// respond with a SetTarget message. 21 | max_target U256 22 | ); 23 | 24 | impl UpdateChannel { 25 | pub fn new>( 26 | channel_id: u32, 27 | nominal_hash_rate: f32, 28 | max_target: U, 29 | ) -> Result { 30 | Ok(UpdateChannel { 31 | channel_id, 32 | nominal_hash_rate, 33 | max_target: max_target.into(), 34 | }) 35 | } 36 | } 37 | 38 | #[cfg(test)] 39 | mod test { 40 | use super::*; 41 | use crate::impl_message_tests; 42 | 43 | fn make_deserialized_update_channel() -> UpdateChannel { 44 | UpdateChannel::new(1, 12.3, [0; 32]).unwrap() 45 | } 46 | 47 | fn make_serialized_update_channel() -> Vec { 48 | return vec![ 49 | 0x01, 0x00, 0x00, 0x00, // channel_id 50 | 0xcd, 0xcc, 0x44, 0x41, // nominal_hash_rate 51 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 52 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 53 | 0x00, 0x00, 0x00, 0x00, // max_target 54 | ]; 55 | } 56 | 57 | impl_message_tests!( 58 | UpdateChannel, 59 | make_serialized_update_channel, 60 | make_deserialized_update_channel 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /stratumv2/src/noise/signed_certificate.rs: -------------------------------------------------------------------------------- 1 | use crate::codec::Serializable; 2 | use crate::error::{Error, Result}; 3 | use crate::noise::types::AuthorityKeyPair; 4 | use crate::noise::types::{Signature, StaticPublicKey}; 5 | use ed25519_dalek::Signer; 6 | use std::io; 7 | 8 | /// A SignedCertificate represents the signed part of a SignatureNoiseMessage. 9 | /// This struct is signed by the Mining Pool's AuthorityKeyPair, attesting to 10 | /// the identity of the StaticPublicKey used in the Noise Diffie-Hellman exchange 11 | /// of the Upstream Node. 12 | pub struct SignedCertificate<'a> { 13 | pub version: u16, 14 | pub valid_from: u32, 15 | pub not_valid_after: u32, 16 | pub public_key: &'a StaticPublicKey, 17 | } 18 | 19 | impl<'a> SignedCertificate<'a> { 20 | pub fn new( 21 | version: u16, 22 | valid_from: u32, 23 | not_valid_after: u32, 24 | public_key: &'a StaticPublicKey, 25 | ) -> Result> { 26 | if valid_from >= not_valid_after { 27 | return Err(Error::RequirementError( 28 | "the valid_from time cannot be greater than or equal to the not_valid_after time" 29 | .into(), 30 | )); 31 | } 32 | 33 | Ok(SignedCertificate { 34 | version, 35 | valid_from, 36 | not_valid_after, 37 | public_key, 38 | }) 39 | } 40 | } 41 | impl Serializable for SignedCertificate<'_> { 42 | fn serialize(&self, writer: &mut W) -> Result { 43 | Ok([ 44 | self.version.serialize(writer)?, 45 | self.valid_from.serialize(writer)?, 46 | self.not_valid_after.serialize(writer)?, 47 | self.public_key.serialize(writer)?, 48 | ] 49 | .iter() 50 | .sum()) 51 | } 52 | } 53 | 54 | /// Signs a [SignedCertificate](struct.SignedCertificate.html) using the Mining Pools 55 | /// [AuthorityKeyPair](struct.AuthorityKeyPair.html), authorizing the Upstream Node 56 | /// to operate on behalf of the Mining Pool. 57 | pub fn authority_sign_cert( 58 | keypair: &AuthorityKeyPair, 59 | cert: &SignedCertificate, 60 | ) -> Result { 61 | let mut signed_cert = Vec::new(); 62 | cert.serialize(&mut signed_cert)?; 63 | 64 | Ok(keypair.sign(&signed_cert)) 65 | } 66 | -------------------------------------------------------------------------------- /stratumv2/src/noise/types.rs: -------------------------------------------------------------------------------- 1 | use crate::codec::{ByteParser, Deserializable, Serializable}; 2 | use crate::error::Result; 3 | use rand::rngs::OsRng; 4 | use std::convert::TryInto; 5 | use std::io; 6 | 7 | /// AuthorityKeyPair is an ed25519_dalek::Keypair used as the Authentication Authority 8 | /// Keypair for the Mining Pool. 9 | pub type AuthorityKeyPair = ed25519_dalek::Keypair; 10 | 11 | pub fn generate_authority_keypair() -> AuthorityKeyPair { 12 | AuthorityKeyPair::generate(&mut OsRng {}) 13 | } 14 | 15 | /// AuthorityPublicKey is the publicly known key of the 16 | /// [AuthorityKeyPair](struct.AuthorityKeyPair.html) of the Mining Pool. 17 | /// This will be used by the Client to verify the and authenticate the Upstream 18 | /// Node is authorised by the Mining Pool. 19 | pub type AuthorityPublicKey = ed25519_dalek::PublicKey; 20 | 21 | /// StaticPublicKey is used as the Noise Diffie-Hellman static public. The key 22 | /// will be signed by the AuthorityKeyPair, to attest to the authenticity of 23 | /// the Mining Pool Server. 24 | pub type StaticPublicKey = noiseexplorer_nx::types::PublicKey; 25 | 26 | impl Serializable for StaticPublicKey { 27 | fn serialize(&self, writer: &mut W) -> Result { 28 | let public_key = self.as_bytes(); 29 | writer.write(&public_key)?; 30 | 31 | Ok(public_key.len()) 32 | } 33 | } 34 | 35 | /// StaticKeyPair is a Keypair used by the responder (Server) as a pre-determined 36 | /// static key that will be signed by the AuthorityKeyPair and used in the 37 | /// [NoiseSession](struct.NoiseSession.html). 38 | /// 39 | /// # Examples 40 | /// 41 | /// ```rust 42 | /// use stratumv2::noise::StaticKeyPair; 43 | /// 44 | /// 45 | /// let static_keypair = StaticKeyPair::default(); 46 | /// ``` 47 | pub type StaticKeyPair = noiseexplorer_nx::types::Keypair; 48 | 49 | // TODO: DOC STRING 50 | pub type Signature = ed25519_dalek::Signature; 51 | 52 | impl Serializable for Signature { 53 | fn serialize(&self, writer: &mut W) -> Result { 54 | let public_key = self.to_bytes(); 55 | writer.write(&public_key)?; 56 | 57 | Ok(public_key.len()) 58 | } 59 | } 60 | 61 | impl Deserializable for Signature { 62 | fn deserialize(parser: &mut ByteParser) -> Result { 63 | let signature_bytes = parser.next_by(64)?; 64 | Ok(Signature::new(signature_bytes.try_into()?)) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /stratumv2/src/mining/setup_connection.rs: -------------------------------------------------------------------------------- 1 | use crate::{impl_bitflags_serde, impl_setup_connection}; 2 | 3 | bitflags!( 4 | /// Feature flags that can be passed to a SetupConnection message in the Mining 5 | /// Protocol. Each flag corresponds to a set bit. 6 | pub struct SetupConnectionFlags: u32 { 7 | /// Flag indicating the Client requires Standard Jobs. The Client doesn't 8 | /// understand group channels and extended jobs. 9 | const REQUIRES_STANDARD_JOBS = (1 << 0); 10 | 11 | /// Flag indicating that the Client will send the Server a SetCustomMiningJob 12 | /// message on this connection. 13 | const REQUIRES_WORK_SELECTION = (1 << 1); 14 | 15 | /// Flag indicating the Client requires version rolling. The Server MUST NOT 16 | /// send jobs which do not allow version rolling. 17 | const REQUIRES_VERSION_ROLLING = (1 << 2); 18 | } 19 | ); 20 | 21 | impl_bitflags_serde!(SetupConnectionFlags); 22 | impl_setup_connection!(SetupConnectionFlags); 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use super::*; 27 | use crate::codec::{deserialize, serialize}; 28 | use crate::impl_setup_connection_tests; 29 | 30 | #[test] 31 | fn flags_serialize() { 32 | assert_eq!( 33 | serialize(&SetupConnectionFlags::REQUIRES_STANDARD_JOBS).unwrap(), 34 | 0x01u32.to_le_bytes() 35 | ); 36 | assert_eq!( 37 | serialize(&SetupConnectionFlags::REQUIRES_WORK_SELECTION).unwrap(), 38 | 0x02u32.to_le_bytes() 39 | ); 40 | assert_eq!( 41 | serialize(&SetupConnectionFlags::REQUIRES_VERSION_ROLLING).unwrap(), 42 | 0x04u32.to_le_bytes() 43 | ); 44 | } 45 | 46 | #[test] 47 | fn flags_deserialize() { 48 | assert_eq!( 49 | deserialize::(&0x01u32.to_le_bytes()).unwrap(), 50 | SetupConnectionFlags::REQUIRES_STANDARD_JOBS, 51 | ); 52 | assert_eq!( 53 | deserialize::(&0x02u32.to_le_bytes()).unwrap(), 54 | SetupConnectionFlags::REQUIRES_WORK_SELECTION, 55 | ); 56 | assert_eq!( 57 | deserialize::(&0x04u32.to_le_bytes()).unwrap(), 58 | SetupConnectionFlags::REQUIRES_VERSION_ROLLING, 59 | ); 60 | 61 | assert!(matches!( 62 | deserialize::(&0xffu32.to_le_bytes()), 63 | Err(Error::UnknownFlags { .. }) 64 | )); 65 | } 66 | 67 | impl_setup_connection_tests!(SetupConnectionFlags); 68 | } 69 | -------------------------------------------------------------------------------- /stratumv2/nx-noise/src/prims.rs: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------- * 2 | * PRIMITIVES * 3 | * ---------------------------------------------------------------- */ 4 | 5 | use crate::{consts::{BLOCKLEN, DHLEN, EMPTY_HASH, HASHLEN, MAC_LENGTH}, 6 | utils::{from_slice_hashlen, prep_nonce}}; 7 | use blake2_rfc::blake2s::Blake2s; 8 | use hacl_star::chacha20poly1305; 9 | 10 | pub(crate) fn encrypt(k: [u8; DHLEN], n: u64, ad: &[u8], in_out: &mut [u8], mac: &mut [u8; MAC_LENGTH]) { 11 | chacha20poly1305::key(&k).nonce(&prep_nonce(n)).encrypt(ad, in_out, mac) 12 | } 13 | 14 | pub(crate) fn decrypt(k: [u8; DHLEN], n: u64, ad: &[u8], in_out: &mut [u8], mac: &[u8; MAC_LENGTH]) -> bool { 15 | chacha20poly1305::key(&k).nonce(&prep_nonce(n)).decrypt(ad, in_out, mac) 16 | } 17 | 18 | pub(crate) fn hash(data: &[u8]) -> [u8; HASHLEN] { 19 | let mut context = Blake2s::new(HASHLEN); 20 | context.update(data); 21 | let hash = context.finalize(); 22 | from_slice_hashlen(&hash.as_bytes()[..]) 23 | } 24 | 25 | pub(crate) fn hash_with_context(con: &[u8], data: &[u8]) -> [u8; HASHLEN] { 26 | let mut context = Blake2s::new(HASHLEN); 27 | context.update(con); 28 | context.update(data); 29 | let hash = context.finalize(); 30 | from_slice_hashlen(&hash.as_bytes()[..]) 31 | } 32 | 33 | pub(crate) fn hmac(key: &[u8], data: &[u8], out: &mut [u8]) { 34 | let mut context = Blake2s::new(HASHLEN); 35 | let mut ipad = [0x36_u8; BLOCKLEN]; 36 | let mut opad = [0x5c_u8; BLOCKLEN]; 37 | for count in 0..key.len() { 38 | ipad[count] ^= key[count]; 39 | opad[count] ^= key[count]; 40 | } 41 | context.update(&ipad[..BLOCKLEN]); 42 | context.update(data); 43 | let inner_output = context.finalize(); 44 | context = Blake2s::new(HASHLEN); 45 | context.update(&opad[..BLOCKLEN]); 46 | context.update(&inner_output.as_bytes()[..HASHLEN]); 47 | out.copy_from_slice(context.finalize().as_bytes()); 48 | } 49 | 50 | pub(crate) fn hkdf( 51 | chaining_key: &[u8], input_key_material: &[u8], outputs: usize, out1: &mut [u8], 52 | out2: &mut [u8], out3: &mut [u8], 53 | ) { 54 | let mut temp_key = EMPTY_HASH; 55 | hmac(chaining_key, input_key_material, &mut temp_key); 56 | hmac(&temp_key, &[1u8,], out1); 57 | if outputs == 1 { 58 | return; 59 | } 60 | let mut in2 = [0_u8; HASHLEN + 1]; 61 | copy_slices!(&out1[0..HASHLEN], &mut in2); 62 | in2[HASHLEN] = 2; 63 | hmac(&temp_key, &in2[..=HASHLEN], out2); 64 | if outputs == 2 { 65 | return; 66 | } 67 | let mut in3 = [0_u8; HASHLEN + 1]; 68 | copy_slices!(&out2[0..HASHLEN], &mut in3); 69 | in3[HASHLEN] = 3; 70 | hmac(&temp_key, &in3[..=HASHLEN], out3); 71 | } 72 | -------------------------------------------------------------------------------- /stratumv2/src/types/error_code.rs: -------------------------------------------------------------------------------- 1 | pub mod macro_prelude { 2 | pub use crate::codec::{ByteParser, Deserializable, Serializable}; 3 | pub use crate::error::{Error, Result}; 4 | pub use crate::types::STR0_255; 5 | pub use std::convert::TryFrom; 6 | pub use std::fmt; 7 | pub use std::io; 8 | pub use std::str::FromStr; 9 | } 10 | 11 | /// Implemenation of all the common traits for ErrorCode enums. 12 | #[doc(hidden)] 13 | #[macro_export] 14 | macro_rules! impl_error_code_enum { 15 | ($name:ident, $($variant:path => $str:expr),*) => { 16 | use crate::types::error_code::macro_prelude::*; 17 | 18 | impl fmt::Display for $name { 19 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 20 | match *self { 21 | $($variant => write!(f, $str)),* 22 | } 23 | } 24 | } 25 | 26 | impl FromStr for $name { 27 | type Err = Error; 28 | 29 | fn from_str(s: &str) -> Result { 30 | match s { 31 | $($str => Ok($variant)),*, 32 | _ => Err(Error::UnknownErrorCode()), 33 | } 34 | } 35 | } 36 | 37 | impl TryFrom for $name { 38 | type Error = Error; 39 | 40 | fn try_from(s: String) -> Result { 41 | Self::from_str(s.as_str()) 42 | } 43 | } 44 | 45 | impl TryFrom for $name { 46 | type Error = Error; 47 | 48 | fn try_from(s: STR0_255) -> Result { 49 | Self::try_from(s.data) 50 | } 51 | } 52 | 53 | impl From<&$name> for String { 54 | fn from(error_code: &$name) -> Self { 55 | match error_code { 56 | $($variant => $str.into()),* 57 | } 58 | } 59 | } 60 | 61 | impl From<&$name> for STR0_255 { 62 | fn from(error_code: &$name) -> Self { 63 | let data: String = error_code.into(); 64 | STR0_255{ 65 | length: data.len() as u8, 66 | data: data, 67 | } 68 | } 69 | } 70 | 71 | impl Serializable for $name { 72 | fn serialize(&self, writer: &mut W) -> Result { 73 | Ok(STR0_255::from(self).serialize(writer)?) 74 | } 75 | } 76 | 77 | impl Deserializable for $name { 78 | fn deserialize(parser: &mut ByteParser) -> Result<$name> { 79 | let error_code = STR0_255::deserialize(parser)?; 80 | 81 | $name::try_from(error_code) 82 | } 83 | } 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /stratumv2/nx-noise/src/consts.rs: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------- * 2 | * CONSTANTS * 3 | * ---------------------------------------------------------------- */ 4 | 5 | #![allow(non_snake_case, non_upper_case_globals)] 6 | use hacl_star::{chacha20poly1305, curve25519}; 7 | 8 | pub const DHLEN: usize = curve25519::SECRET_LENGTH; 9 | pub(crate) const HASHLEN: usize = 32; 10 | pub(crate) const BLOCKLEN: usize = 64; 11 | pub(crate) const EMPTY_HASH: [u8; DHLEN] = [0_u8; HASHLEN]; 12 | pub(crate) const EMPTY_KEY: [u8; DHLEN] = [0_u8; DHLEN]; 13 | pub const MAC_LENGTH: usize = chacha20poly1305::MAC_LENGTH; 14 | pub(crate) const MAX_MESSAGE: usize = 0xFFFF; 15 | pub(crate) const MAX_NONCE: u64 = u64::max_value(); 16 | pub(crate) const NONCE_LENGTH: usize = chacha20poly1305::NONCE_LENGTH; 17 | pub(crate) const ZEROLEN: [u8; 0] = [0_u8; 0]; 18 | pub(crate) const forbidden_curve_values: [[u8; 32]; 12] = [ 19 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 20 | [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 21 | [224, 235, 122, 124, 59, 65, 184, 174, 22, 86, 227, 250, 241, 159, 196, 106, 218, 9, 141, 235, 156, 50, 177, 253, 134, 98, 5, 22, 95, 73, 184, 0], 22 | [95, 156, 149, 188, 163, 80, 140, 36, 177, 208, 177, 85, 156, 131, 239, 91, 4, 68, 92, 196, 88, 28, 142, 134, 216, 34, 78, 221, 208, 159, 17, 87], 23 | [236, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127], 24 | [237, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127], 25 | [238, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127], 26 | [205, 235, 122, 124, 59, 65, 184, 174, 22, 86, 227, 250, 241, 159, 196, 106, 218, 9, 141, 235, 156, 50, 177, 253, 134, 98, 5, 22, 95, 73, 184, 128], 27 | [76, 156, 149, 188, 163, 80, 140, 36, 177, 208, 177, 85, 156, 131, 239, 91, 4, 68, 92, 196, 88, 28, 142, 134, 216, 34, 78, 221, 208, 159, 17, 215], 28 | [217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], 29 | [218, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], 30 | [219, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255] 31 | ]; -------------------------------------------------------------------------------- /stratumv2/src/mining/open_standard_mining_channel_success.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use crate::impl_message; 3 | use crate::types::{MessageType, B0_32, U256}; 4 | 5 | impl_message!( 6 | /// A message sent by the Server to the Client in response to a successful 7 | /// opening of a standard mining channel. 8 | OpenStandardMiningChannelSuccess, 9 | 10 | /// The request_id received in the [OpenStandardMiningChannel](struct.OpenStandardMiningChannel.html) message. 11 | /// This is returned to the Client so that they can pair the responses with the 12 | /// initial request. 13 | request_id u32, 14 | 15 | /// Assigned by the Server to uniquely identify the channel, the id is stable 16 | /// for the whole lifetime of the connection. 17 | channel_id u32, 18 | 19 | /// The initial target difficulty target for the mining channel. 20 | target U256, 21 | 22 | // TODO: I don't understand the purpose of the extranonce_prefix. 23 | extranonce_prefix B0_32, 24 | 25 | /// Group channel that the channel belongs to. 26 | group_channel_id u32 27 | ); 28 | 29 | impl OpenStandardMiningChannelSuccess { 30 | pub fn new>, U: Into>( 31 | request_id: u32, 32 | channel_id: u32, 33 | target: U, 34 | extranonce_prefix: T, 35 | group_channel_id: u32, 36 | ) -> Result { 37 | Ok(OpenStandardMiningChannelSuccess { 38 | request_id, 39 | channel_id, 40 | target: target.into(), 41 | extranonce_prefix: B0_32::new(extranonce_prefix)?, 42 | group_channel_id, 43 | }) 44 | } 45 | } 46 | 47 | #[cfg(test)] 48 | mod test { 49 | use super::*; 50 | use crate::impl_message_tests; 51 | 52 | fn make_deserialized_open_standard_mining_channel_success() -> OpenStandardMiningChannelSuccess 53 | { 54 | OpenStandardMiningChannelSuccess::new(1u32, 2u32, [3u8; 32], [4u8; 4], 5u32).unwrap() 55 | } 56 | 57 | fn make_serialized_open_standard_mining_channel_success() -> Vec { 58 | return vec![ 59 | 0x01, 0x00, 0x00, 0x00, // request_id, 60 | 0x02, 0x00, 0x00, 0x00, // channel_id, 61 | 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 62 | 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 63 | 0x03, 0x03, 0x03, 0x03, // target 64 | 0x04, 0x04, 0x04, 0x04, 0x04, // extranonce_prefix 65 | 0x05, 0x00, 0x00, 0x00, // group_channel_id 66 | ]; 67 | } 68 | 69 | impl_message_tests!( 70 | OpenStandardMiningChannelSuccess, 71 | make_serialized_open_standard_mining_channel_success, 72 | make_deserialized_open_standard_mining_channel_success 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /stratumv2/src/noise/signature_noise_message.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | codec::{ByteParser, Deserializable, Serializable}, 3 | error::Result, 4 | noise::{ 5 | signed_certificate::{authority_sign_cert, SignedCertificate}, 6 | types::AuthorityKeyPair, 7 | types::Signature, 8 | }, 9 | }; 10 | use std::io; 11 | 12 | /// SignatureNoiseMessage is sent by the Server after the NX Noise 13 | /// Handshake has completed. The message is used by the Client to reconstruct 14 | /// the full certificate and validate the remote static public key ("s") has 15 | /// been signed by the AuthorityKeyPair of the Mining Pool. 16 | #[derive(Clone, Debug, PartialEq)] 17 | pub struct SignatureNoiseMessage { 18 | pub version: u16, 19 | pub valid_from: u32, 20 | pub not_valid_after: u32, 21 | pub signature: Signature, 22 | } 23 | 24 | impl SignatureNoiseMessage { 25 | pub fn new(cert: &SignedCertificate, signature: Signature) -> SignatureNoiseMessage { 26 | SignatureNoiseMessage { 27 | version: cert.version, 28 | valid_from: cert.valid_from, 29 | not_valid_after: cert.not_valid_after, 30 | signature, 31 | } 32 | } 33 | 34 | /// from_auth_key generates a signature from an AuthorityKeyPair over the 35 | /// SignedCertificate and returns a SignatureNoiseMessage. 36 | // TODO: EXAMPLE 37 | pub fn from_auth_key( 38 | authority_keypair: &AuthorityKeyPair, 39 | cert: &SignedCertificate, 40 | ) -> Result { 41 | Ok(SignatureNoiseMessage { 42 | version: cert.version, 43 | valid_from: cert.valid_from, 44 | not_valid_after: cert.not_valid_after, 45 | signature: authority_sign_cert(&authority_keypair, &cert)?, 46 | }) 47 | } 48 | } 49 | 50 | impl Serializable for SignatureNoiseMessage { 51 | fn serialize(&self, writer: &mut W) -> Result { 52 | Ok([ 53 | self.version.serialize(writer)?, 54 | self.valid_from.serialize(writer)?, 55 | self.not_valid_after.serialize(writer)?, 56 | self.signature.serialize(writer)?, 57 | ] 58 | .iter() 59 | .sum()) 60 | } 61 | } 62 | 63 | impl Deserializable for SignatureNoiseMessage { 64 | fn deserialize(parser: &mut ByteParser) -> Result { 65 | let version = u16::deserialize(parser)?; 66 | let valid_from = u32::deserialize(parser)?; 67 | let not_valid_after = u32::deserialize(parser)?; 68 | let signature = Signature::deserialize(parser)?; 69 | 70 | Ok({ 71 | SignatureNoiseMessage { 72 | version, 73 | valid_from, 74 | not_valid_after, 75 | signature, 76 | } 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /stratumv2/src/mining/open_extended_mining_channel_success.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use crate::impl_message; 3 | use crate::types::{MessageType, B0_32, U256}; 4 | 5 | impl_message!( 6 | /// OpenExtendedMiningChannelSuccess is a message sent by the Server to the Client 7 | /// in response to a successful opening of a standard mining channel. 8 | OpenExtendedMiningChannelSuccess, 9 | 10 | /// The request_id received in the 11 | /// [OpenExtendedMiningChannel](struct.OpenExtendedMiningChannel.html) message. 12 | /// This is returned to the Client so that they can pair the responses with the 13 | /// initial request. 14 | request_id u32, 15 | 16 | /// Assigned by the Server to uniquely identify the channel, the id is stable 17 | /// for the whole lifetime of the connection. 18 | channel_id u32, 19 | 20 | /// The initial target difficulty target for the mining channel. 21 | target U256, 22 | 23 | // TODO: I don't understand the purpose of the extranonce size. 24 | extranonce_size u16, 25 | 26 | // TODO: I don't understand the purpose of the extranonce prefix. 27 | extranonce_prefix B0_32 28 | ); 29 | 30 | impl OpenExtendedMiningChannelSuccess { 31 | pub fn new>, U: Into>( 32 | request_id: u32, 33 | channel_id: u32, 34 | target: U, 35 | extranonce_size: u16, 36 | extranonce_prefix: T, 37 | ) -> Result { 38 | Ok(OpenExtendedMiningChannelSuccess { 39 | request_id, 40 | channel_id, 41 | target: target.into(), 42 | extranonce_size, 43 | extranonce_prefix: B0_32::new(extranonce_prefix)?, 44 | }) 45 | } 46 | } 47 | 48 | #[cfg(test)] 49 | mod test { 50 | use super::*; 51 | use crate::impl_message_tests; 52 | 53 | fn make_deserialized_open_extended_mining_channel_success() -> OpenExtendedMiningChannelSuccess 54 | { 55 | OpenExtendedMiningChannelSuccess::new(1u32, 2u32, [3u8; 32], 4u16, [5u8; 4]).unwrap() 56 | } 57 | 58 | fn make_serialized_open_extended_mining_channel_success() -> Vec { 59 | return vec![ 60 | 0x01, 0x00, 0x00, 0x00, // request_id, 61 | 0x02, 0x00, 0x00, 0x00, // channel_id, 62 | 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 63 | 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 64 | 0x03, 0x03, 0x03, 0x03, // target 65 | 0x04, 0x00, // extranonce_size 66 | 0x04, 0x05, 0x05, 0x05, 0x05, // extranonce_prefix 67 | ]; 68 | } 69 | 70 | impl_message_tests!( 71 | OpenExtendedMiningChannelSuccess, 72 | make_serialized_open_extended_mining_channel_success, 73 | make_deserialized_open_extended_mining_channel_success 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /stratumv2/src/network/config.rs: -------------------------------------------------------------------------------- 1 | use crate::noise::{SignatureNoiseMessage, StaticKeyPair}; 2 | 3 | /// NoiseConfig contains the configuration for devices to assign a pre-defined 4 | /// StaticKeyPair and SignatureNoiseMessage signed by the Certificate Authority 5 | /// of the Mining Pool. This Config would usually only be used by an Upstream Server 6 | /// (Mining Pool Server). 7 | #[derive(Clone)] 8 | pub struct NoiseConfig { 9 | /// The SignatureNoiseMessage is intended to be read from disk so that a 10 | /// Mining Pool Server can send this message at the end of a noise handshake. 11 | /// If the SignatureNoiseMessage does not exist on disk, the intention is 12 | /// that after generating it for the first time, it will be stored in the 13 | /// datadir of the device. 14 | pub sig_noise_msg: SignatureNoiseMessage, 15 | 16 | /// The StaticKeyPair is the keypair used by the Upstream Device 17 | /// (Mining Pool Server or Mining Proxy) for all noise sessions. If the 18 | /// Upstream Device is a Mining Pool Server, then the StaticKeyPair will 19 | /// be used to generate the SignatureNoiseMessage. The intention is for 20 | /// the StaticKeyPair to be read from disk. If it is not available on disk, 21 | /// then after generating it for the first time, it will be persisted 22 | /// in the datadir of the device. 23 | pub static_key: StaticKeyPair, 24 | } 25 | 26 | impl NoiseConfig { 27 | pub fn new(sig_noise_msg: SignatureNoiseMessage, static_key: StaticKeyPair) -> Self { 28 | NoiseConfig { 29 | sig_noise_msg, 30 | static_key, 31 | } 32 | } 33 | } 34 | 35 | /// NetworkConfig contains the configuration networking for all networked devices. 36 | /// This maybe separated into Upstream or Downstream configs depending on how 37 | /// each device requirements begin to diverge. Equally this maybe later moved 38 | /// into an upstream networked crate if it makes sense to do so. 39 | #[derive(Clone)] 40 | pub struct NetworkConfig { 41 | /// The public networked listening address of this device. 42 | pub listening_addr: String, 43 | 44 | /// TODO: A flag determining whether this device will accept insecure communication 45 | /// on a local network. 46 | pub local_network_encryption: bool, 47 | } 48 | 49 | impl NetworkConfig { 50 | pub fn new(listening_addr: String, local_network_encryption: bool) -> Self { 51 | NetworkConfig { 52 | listening_addr, 53 | local_network_encryption, 54 | } 55 | } 56 | } 57 | 58 | /// ServerConfig contains the configurations for state and decision making logic 59 | /// for a Mining Pool Server. 60 | pub struct ServerConfig { 61 | pub mining_flags: crate::mining::SetupConnectionFlags, 62 | } 63 | 64 | impl ServerConfig { 65 | pub fn new(mining_flags: crate::mining::SetupConnectionFlags) -> Self { 66 | ServerConfig { mining_flags } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /stratumv2/src/macro_message/setup_connection_success.rs: -------------------------------------------------------------------------------- 1 | pub mod macro_prelude { 2 | pub use crate::error::{Error, Result}; 3 | pub use crate::impl_message; 4 | pub use crate::types::MessageType; 5 | pub use std::io; 6 | } 7 | 8 | #[doc(hidden)] 9 | #[macro_export] 10 | macro_rules! impl_setup_connection_success { 11 | ($flags_type:ident) => { 12 | use crate::macro_message::setup_connection_success::macro_prelude::*; 13 | 14 | impl_message!( 15 | /// One of the required responses from a Server to a Client when a 16 | /// connection is accepted. 17 | /// 18 | /// # Examples 19 | /// 20 | /// ```rust 21 | /// use stratumv2::mining; 22 | /// 23 | /// let conn_success = mining::SetupConnectionSuccess::new( 24 | /// 2, 25 | /// mining::SetupConnectionSuccessFlags::REQUIRES_FIXED_VERSION, 26 | /// ).unwrap(); 27 | /// 28 | /// assert_eq!( 29 | /// conn_success.flags, 30 | /// mining::SetupConnectionSuccessFlags::REQUIRES_FIXED_VERSION 31 | /// ); 32 | /// ``` 33 | SetupConnectionSuccess, 34 | 35 | /// Version proposed by the connecting node as one of the verions supported 36 | /// by the upstream node. The version will be used during the lifetime of 37 | /// the connection. 38 | used_version u16, 39 | 40 | /// Indicates the optional features the server supports. 41 | flags $flags_type 42 | ); 43 | 44 | impl SetupConnectionSuccess { 45 | /// Constructor for the SetupConnectionSuccess message. 46 | pub fn new(used_version: u16, flags: $flags_type) -> Result { 47 | Ok(SetupConnectionSuccess { 48 | used_version, 49 | flags, 50 | }) 51 | } 52 | } 53 | }; 54 | } 55 | 56 | #[cfg(test)] 57 | pub mod test_macro_prelude { 58 | pub use crate::impl_message_tests; 59 | } 60 | 61 | #[cfg(test)] 62 | #[doc(hidden)] 63 | #[macro_export] 64 | macro_rules! impl_setup_connection_success_tests { 65 | ($flags_type:ident) => { 66 | use crate::macro_message::setup_connection_success::test_macro_prelude::*; 67 | 68 | fn make_deserialized_setup_connection_success() -> SetupConnectionSuccess { 69 | SetupConnectionSuccess::new(2, $flags_type::all()).unwrap() 70 | } 71 | 72 | fn make_serialized_setup_connection_success() -> Vec { 73 | let mut serialized = vec![ 74 | 0x02, 0x00, // used_version 75 | ]; 76 | serialized.extend($flags_type::all().bits().to_le_bytes().iter()); // flags 77 | 78 | serialized 79 | } 80 | 81 | impl_message_tests!( 82 | SetupConnectionSuccess, 83 | make_serialized_setup_connection_success, 84 | make_deserialized_setup_connection_success 85 | ); 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /stratumv2/src/mining/open_mining_channel_error.rs: -------------------------------------------------------------------------------- 1 | use crate::impl_error_code_enum; 2 | 3 | /// Contains the error codes for the [OpenStandardMiningChannelError](struct.OpenStandardMiningChannelError.html) 4 | /// and [OpenExtendedMiningChannelError](struct.OpenExtendedMiningChannelError.html) 5 | /// message. Each error code is serialized according to constraints of a 6 | /// [STR0_32](../types/struct.STR0_32.html). 7 | #[derive(Debug, Clone, PartialEq)] 8 | pub enum OpenMiningChannelErrorCode { 9 | UnknownUser, 10 | MaxTargetOutOfRange, 11 | } 12 | 13 | impl_error_code_enum!( 14 | OpenMiningChannelErrorCode, 15 | OpenMiningChannelErrorCode::UnknownUser => "unknown-user", 16 | OpenMiningChannelErrorCode::MaxTargetOutOfRange => "max-target-out-of-range" 17 | 18 | ); 19 | 20 | pub mod macro_prelude { 21 | pub use super::OpenMiningChannelErrorCode; 22 | pub use crate::error::Result; 23 | } 24 | 25 | #[doc(hidden)] 26 | #[macro_export] 27 | macro_rules! impl_open_mining_channel_error { 28 | ($struct_name:ident) => { 29 | use crate::mining::open_mining_channel_error::macro_prelude::*; 30 | 31 | impl_message!( 32 | /// An implementation of the OpenMiningChannelError. This message applies to both 33 | /// Standard Mining Channels and Extended Mining Channels. 34 | $struct_name, 35 | 36 | /// A client specified request ID from the original OpenMiningChannel message. 37 | request_id u32, 38 | 39 | /// Pre-determined human readable error codes for the OpenMiningChannel message. 40 | error_code OpenMiningChannelErrorCode 41 | 42 | ); 43 | 44 | impl $struct_name { 45 | pub fn new(request_id: u32, error_code: OpenMiningChannelErrorCode) -> Result<$struct_name> { 46 | Ok($struct_name { 47 | request_id, 48 | error_code, 49 | }) 50 | } 51 | } 52 | }; 53 | } 54 | 55 | #[cfg(test)] 56 | pub mod test_macro_prelude { 57 | pub use crate::impl_message_tests; 58 | } 59 | 60 | #[cfg(test)] 61 | #[doc(hidden)] 62 | #[macro_export] 63 | macro_rules! impl_open_mining_channel_error_tests { 64 | ($struct_name:ident) => { 65 | use crate::mining::open_mining_channel_error::test_macro_prelude::*; 66 | 67 | fn make_deserialized_open_mining_channel_error() -> $struct_name { 68 | $struct_name::new(0x01, OpenMiningChannelErrorCode::UnknownUser).unwrap() 69 | } 70 | 71 | fn make_serialized_open_mining_channel_error() -> Vec { 72 | let mut serialized = vec![0x01, 0x00, 0x00, 0x00]; // request_id 73 | serialized.extend(vec![ 74 | // error_code 75 | 0x0c, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x2d, 0x75, 0x73, 0x65, 0x72, 76 | ]); 77 | 78 | serialized 79 | } 80 | 81 | impl_message_tests!( 82 | $struct_name, 83 | make_serialized_open_mining_channel_error, 84 | make_deserialized_open_mining_channel_error 85 | ); 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /stratumv2/src/mining/open_standard_mining_channel.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use crate::impl_message; 3 | use crate::types::{MessageType, STR0_255, U256}; 4 | 5 | impl_message!( 6 | /// A message sent by the Client to the Server after a [SetupConnection.Success](struct.SetupConnectionSuccess.html) 7 | /// is sent from the Server. This message is used to request opening a standard 8 | /// channel to the upstream server. A standard mining channel indicates `header-only` 9 | /// mining. 10 | OpenStandardMiningChannel, 11 | 12 | /// A Client-specified unique identifier across all client connections. 13 | /// The request_id is not interpreted by the Server. 14 | request_id u32, 15 | 16 | /// A sequence of bytes that identifies the node to the Server, e.g. 17 | /// "braiintest.worker1". 18 | user_identity STR0_255, 19 | 20 | /// The expected [h/s] (hash rate/per second) of the 21 | /// device or the cumulative on the channel if multiple devices are connected 22 | /// downstream. Proxies MUST send 0.0f when there are no mining devices 23 | /// connected yet. 24 | nominal_hash_rate f32, 25 | 26 | /// The Maximum Target that can be acceptd by the connected device or 27 | /// multiple devices downstream. The Server MUST accept the maximum 28 | /// target or respond by sending a 29 | /// [OpenStandardMiningChannel.Error](struct.OpenStandardMiningChannelError.html) 30 | /// or [OpenExtendedMiningChannel.Error](struct.OpenExtendedMiningChannelError.html) 31 | max_target U256 32 | ); 33 | 34 | impl OpenStandardMiningChannel { 35 | pub fn new, U: Into>( 36 | request_id: u32, 37 | user_identity: T, 38 | nominal_hash_rate: f32, 39 | max_target: U, 40 | ) -> Result { 41 | Ok(OpenStandardMiningChannel { 42 | request_id, 43 | user_identity: STR0_255::new(user_identity)?, 44 | nominal_hash_rate, 45 | max_target: max_target.into(), 46 | }) 47 | } 48 | } 49 | 50 | #[cfg(test)] 51 | mod test { 52 | use super::*; 53 | use crate::impl_message_tests; 54 | 55 | fn make_deserialized_open_standard_mining_channel() -> OpenStandardMiningChannel { 56 | OpenStandardMiningChannel::new(1u32, "user id", 3.0f32, [4u8; 32]).unwrap() 57 | } 58 | 59 | fn make_serialized_open_standard_mining_channel() -> Vec { 60 | return vec![ 61 | 0x01, 0x00, 0x00, 0x00, // request_id, 62 | 0x07, 0x75, 0x73, 0x65, 0x72, 0x20, 0x69, 0x64, // user_identity 63 | 0x00, 0x00, 0x40, 0x40, // nominal_hash_rate 64 | 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 65 | 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 66 | 0x04, 0x04, 0x04, 0x04, // max_target 67 | ]; 68 | } 69 | 70 | impl_message_tests!( 71 | OpenStandardMiningChannel, 72 | make_serialized_open_standard_mining_channel, 73 | make_deserialized_open_standard_mining_channel 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /stratumv2/src/mining/open_extended_mining_channel.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use crate::impl_message; 3 | use crate::types::MessageType; 4 | use crate::types::{STR0_255, U256}; 5 | 6 | impl_message!( 7 | /// A message sent by the Client to the Server to open a mining channel that 8 | /// has additional capabilities such as difficulty aggregatation and custom 9 | /// search space splitting. 10 | OpenExtendedMiningChannel, 11 | 12 | /// A Client-specified unique identifier across all client connections. 13 | /// The request_id is not interpreted by the Server. 14 | request_id u32, 15 | 16 | /// A sequence of bytes that identifies the node to the Server, e.g. 17 | /// "braiintest.worker1". 18 | user_identity STR0_255, 19 | 20 | /// The expected [h/s] (hash rate/per second) of the 21 | /// device or the cumulative on the channel if multiple devices are connected 22 | /// downstream. Proxies MUST send 0.0f when there are no mining devices 23 | /// connected yet. 24 | nominal_hash_rate f32, 25 | 26 | /// The Maximum Target that can be acceptd by the connected device or 27 | /// multiple devices downstream. The Server MUST accept the maximum 28 | /// target or respond by sending a 29 | /// [OpenStandardMiningChannel.Error](struct.OpenStandardMiningChannelError.html) 30 | /// or [OpenExtendedMiningChannel.Error](struct.OpenExtendedMiningChannelError.html) 31 | max_target U256, 32 | 33 | /// The minimum size of extranonce space required by the Downstream node. 34 | min_extranonce_size u16 35 | ); 36 | 37 | impl OpenExtendedMiningChannel { 38 | pub fn new, U: Into>( 39 | request_id: u32, 40 | user_identity: T, 41 | nominal_hash_rate: f32, 42 | max_target: U, 43 | min_extranonce_size: u16, 44 | ) -> Result { 45 | Ok(OpenExtendedMiningChannel { 46 | request_id, 47 | user_identity: STR0_255::new(user_identity)?, 48 | nominal_hash_rate, 49 | max_target: max_target.into(), 50 | min_extranonce_size, 51 | }) 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod test { 57 | use super::*; 58 | use crate::impl_message_tests; 59 | 60 | fn make_deserialized_open_extended_mining_channel() -> OpenExtendedMiningChannel { 61 | OpenExtendedMiningChannel::new(1u32, "user id", 3.0f32, [4u8; 32], 5u16).unwrap() 62 | } 63 | 64 | fn make_serialized_open_extended_mining_channel() -> Vec { 65 | return vec![ 66 | 0x01, 0x00, 0x00, 0x00, // request_id, 67 | 0x07, 0x75, 0x73, 0x65, 0x72, 0x20, 0x69, 0x64, // user_identity 68 | 0x00, 0x00, 0x40, 0x40, // nominal_hash_rate 69 | 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 70 | 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 71 | 0x04, 0x04, 0x04, 0x04, // max_target 72 | 0x05, 0x00, // min_extranonce_size 73 | ]; 74 | } 75 | 76 | impl_message_tests!( 77 | OpenExtendedMiningChannel, 78 | make_serialized_open_extended_mining_channel, 79 | make_deserialized_open_extended_mining_channel 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /stratumv2/src/noise/certificate_format.rs: -------------------------------------------------------------------------------- 1 | use crate::codec::Serializable; 2 | use crate::error::{Error, Result}; 3 | use crate::noise::signature_noise_message::SignatureNoiseMessage; 4 | use crate::noise::types::{AuthorityPublicKey, StaticPublicKey}; 5 | use crate::types::unix_timestamp::unix_u32_now; 6 | use bitcoin::util::base58; 7 | use ed25519_dalek::Verifier; 8 | use std::convert::TryInto; 9 | use std::io; 10 | 11 | /// CertificateFormat is used to reconstruct a message to verify a signature 12 | /// given a [SignatureNoiseMessage](struct.SignatureNoiseMessage.html). 13 | /// 14 | /// The `verison`, `valid_from` and `not_valid_after` fields are 15 | /// taken from the [SignatureNoiseMessage](struct.SignatureNoiseMessage.html) 16 | /// and the static key of the server to verify the signature was signed with 17 | /// the correct Authority Key. 18 | pub struct CertificateFormat<'a> { 19 | authority_public_key: AuthorityPublicKey, 20 | static_public_key: &'a StaticPublicKey, 21 | signature_noise_message: &'a SignatureNoiseMessage, 22 | } 23 | 24 | impl<'a> CertificateFormat<'a> { 25 | pub fn new( 26 | authority_public_key: &'a str, 27 | static_public_key: &'a StaticPublicKey, 28 | signature_noise_message: &'a SignatureNoiseMessage, 29 | ) -> Result> { 30 | // Convert the base58 encoded String into an AuthorityPublicKey object. 31 | let key_bytes: [u8; 32] = base58::from(authority_public_key)? 32 | .try_into() 33 | .map_err(|_| Error::ParseError("Failed to deserialize the base58 public key".into()))?; 34 | 35 | Ok(CertificateFormat { 36 | authority_public_key: AuthorityPublicKey::from_bytes(&key_bytes)?, 37 | static_public_key, 38 | signature_noise_message, 39 | }) 40 | } 41 | 42 | /// Verify the certificate, specifically the validity of the certificate time 43 | /// limits and whether the static public key was signed by the AuthorityKeyPair 44 | /// identifying the Mining Pool. 45 | pub fn verify(&self) -> Result<()> { 46 | if unix_u32_now()? >= self.signature_noise_message.not_valid_after { 47 | return Err(Error::RequirementError( 48 | "the signature noise message is expired".into(), 49 | )); 50 | } 51 | 52 | let mut certificate = Vec::new(); 53 | self.serialize(&mut certificate)?; 54 | 55 | Ok(self 56 | .authority_public_key 57 | .verify(&certificate, &self.signature_noise_message.signature)?) 58 | } 59 | } 60 | 61 | // This implementation of Serializable, intentionally omits the signature field 62 | // in the signature_noise_message. We only need the bytes of the version, valid_from 63 | // and not_valid after to check against the signature and the counter parties 64 | // static public key. 65 | impl Serializable for CertificateFormat<'_> { 66 | fn serialize(&self, writer: &mut W) -> Result { 67 | Ok([ 68 | self.signature_noise_message.version.serialize(writer)?, 69 | self.signature_noise_message.valid_from.serialize(writer)?, 70 | self.signature_noise_message 71 | .not_valid_after 72 | .serialize(writer)?, 73 | self.static_public_key.serialize(writer)?, 74 | ] 75 | .iter() 76 | .sum()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /stratumv2/src/network/encryptor.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::Result, 3 | noise::{ 4 | new_noise_initiator, new_noise_responder, NoiseSession, StaticKeyPair, StaticPublicKey, 5 | }, 6 | }; 7 | 8 | /// The Encryptor trait can be used to apply a noise framework encryption implementation 9 | /// for a connection. 10 | pub trait Encryptor { 11 | fn is_handshake_complete(&self) -> bool; 12 | fn recv_handshake(&mut self, bytes: &mut [u8]) -> Result>; 13 | fn init_handshake(&mut self) -> Result>; 14 | fn encrypt_message(bytes: &[u8]) -> Vec; 15 | fn decrypt_message(bytes: &[u8]) -> Vec; 16 | } 17 | 18 | /// ConnectionEncryptor implements Encryptor providing a common interface to 19 | /// to perform the noise handshake and de/encrypt messsages. 20 | pub struct ConnectionEncryptor { 21 | noise_session: NoiseSession, 22 | } 23 | 24 | impl ConnectionEncryptor { 25 | /// Initialize a ChannelEncryptor as the receiver of an inbound noise handshake 26 | /// session. This would typically be upstream devices such as Mining Pool Server. 27 | pub fn new_inbound(static_key: Option) -> Self { 28 | ConnectionEncryptor { 29 | noise_session: new_noise_responder(static_key), 30 | } 31 | } 32 | 33 | /// Initialize a ChannelEncryptor as the initiator of an outbound noise handshake. 34 | /// This would typically be downstream nodes such as Mining Devices or Mining Proxies. 35 | pub fn new_outbound() -> Self { 36 | ConnectionEncryptor { 37 | noise_session: new_noise_initiator(), 38 | } 39 | } 40 | 41 | /// Returns the StaticPublicKey of the counter party of the noise session. 42 | /// This method return None if the noise handshake is not complete. 43 | pub fn get_remote_pubkey(&self) -> Option { 44 | self.noise_session.get_remote_static_public_key() 45 | } 46 | } 47 | 48 | impl Encryptor for ConnectionEncryptor { 49 | /// Checks if the noise handshake has completed, meaning the sender and receiver 50 | /// can communicate securely. 51 | fn is_handshake_complete(&self) -> bool { 52 | self.noise_session.is_transport() 53 | } 54 | 55 | /// Receives bytes and update the noise handshake state. Will also advance 56 | /// the handshake state and return the bytes required to send back to 57 | /// the counter-party. 58 | fn recv_handshake(&mut self, bytes: &mut [u8]) -> Result> { 59 | self.noise_session.recv_message(bytes)?; 60 | self.noise_session.send_message(bytes)?; 61 | 62 | Ok(bytes.to_vec()) 63 | } 64 | 65 | /// Initialize the handshake state as the initiator. Will return the bytes 66 | /// required to send to the receiver. 67 | fn init_handshake(&mut self) -> Result> { 68 | let mut buf = [0u8; 1024]; 69 | self.noise_session.send_message(&mut buf)?; 70 | 71 | Ok(buf.to_vec()) 72 | } 73 | 74 | // TODO: 75 | /// Encrypt an outbound message. 76 | fn encrypt_message(bytes: &[u8]) -> Vec { 77 | vec![] 78 | } 79 | 80 | // TODO: 81 | /// Decrypt an inbound message. 82 | fn decrypt_message(bytes: &[u8]) -> Vec { 83 | vec![] 84 | } 85 | } 86 | 87 | #[cfg(test)] 88 | mod test { 89 | use super::*; 90 | 91 | #[test] 92 | fn basic_handshake() { 93 | let mut initiator = ConnectionEncryptor::new_outbound(); 94 | let mut receiver = ConnectionEncryptor::new_inbound(None); 95 | 96 | let mut x = initiator.init_handshake().unwrap(); 97 | let mut y = receiver.recv_handshake(&mut x).unwrap(); 98 | initiator.recv_handshake(&mut y).unwrap(); 99 | 100 | assert!(initiator.is_handshake_complete() && receiver.is_handshake_complete()); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /stratumv2/src/types/message_type.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | 3 | /// MessageType contains all the variations for the byte representation of 4 | /// messages used in message frames. 5 | #[derive(Debug, PartialEq, Clone, Copy)] 6 | pub enum MessageType { 7 | // Common messages 8 | SetupConnection, 9 | SetupConnectionSuccess, 10 | SetupConnectionError, 11 | ChannelEndpointChanged, 12 | // Mining protocol messages 13 | OpenStandardMiningChannel, 14 | OpenStandardMiningChannelSuccess, 15 | OpenStandardMiningChannelError, 16 | OpenExtendedMiningChannel, 17 | OpenExtendedMiningChannelSuccess, 18 | OpenExtendedMiningChannelError, 19 | UpdateChannel, 20 | UpdateChannelError, 21 | CloseChannel, 22 | SetExtranoncePrefix, 23 | SubmitSharesStandard, 24 | SubmitSharesExtended, 25 | SubmitSharesSuccess, 26 | SubmitSharesError, 27 | NewMiningJob, 28 | NewExtendedMiningJob, 29 | SetNewPrevHash, 30 | SetTarget, 31 | SetCustomMiningJob, 32 | SetCustomMiningJobSuccess, 33 | SetCustomMiningJobError, 34 | Reconnect, 35 | SetGroupChannel, 36 | // TODO(chpatton013): Job negotiation protocol messages 37 | // TODO(chpatton013): Template distribution protocol messages 38 | // Testing messages 39 | TestMessage1, 40 | TestMessage2, 41 | } 42 | 43 | macro_rules! impl_enum_message_types { 44 | ($type:ident, $($variant:path => ($ext_type:expr, $msg_type:expr, $channel_bit:expr)),*) => { 45 | impl $type { 46 | pub fn new(ext_type: u16, msg_type: u8) -> Result<$type> { 47 | match (ext_type, msg_type) { 48 | $(($ext_type, $msg_type) => Ok($variant)),*, 49 | _ => Err(Error::UnknownMessageType()), 50 | } 51 | } 52 | 53 | pub fn ext_type(&self) -> u16 { 54 | match self { 55 | $($variant => $ext_type),* 56 | } 57 | } 58 | 59 | pub fn msg_type(&self) -> u8 { 60 | match self { 61 | $($variant => $msg_type),* 62 | } 63 | } 64 | 65 | pub fn channel_bit(&self) -> bool { 66 | match self { 67 | $($variant => $channel_bit),* 68 | } 69 | } 70 | } 71 | }; 72 | } 73 | 74 | impl_enum_message_types!( 75 | MessageType, 76 | MessageType::SetupConnection => (0x0000, 0x00, false), 77 | MessageType::SetupConnectionSuccess => (0x0000, 0x01, false), 78 | MessageType::SetupConnectionError => (0x0000, 0x02, false), 79 | MessageType::ChannelEndpointChanged => (0x0000, 0x03, true), 80 | MessageType::OpenStandardMiningChannel => (0x0000, 0x10, false), 81 | MessageType::OpenStandardMiningChannelSuccess => (0x0000, 0x11, false), 82 | MessageType::OpenStandardMiningChannelError => (0x0000, 0x12, false), 83 | MessageType::OpenExtendedMiningChannel => (0x0000, 0x13, false), 84 | MessageType::OpenExtendedMiningChannelSuccess => (0x0000, 0x14, false), 85 | MessageType::OpenExtendedMiningChannelError => (0x0000, 0x15, false), 86 | MessageType::UpdateChannel => (0x0000, 0x16, true), 87 | MessageType::UpdateChannelError => (0x0000, 0x17, true), 88 | MessageType::CloseChannel => (0x0000, 0x18, true), 89 | MessageType::SetExtranoncePrefix => (0x0000, 0x19, true), 90 | MessageType::SubmitSharesStandard => (0x0000, 0x1a, true), 91 | MessageType::SubmitSharesExtended => (0x0000, 0x1b, true), 92 | MessageType::SubmitSharesSuccess => (0x0000, 0x1c, true), 93 | MessageType::SubmitSharesError => (0x0000, 0x1d, true), 94 | MessageType::NewMiningJob => (0x0000, 0x1e, true), 95 | MessageType::NewExtendedMiningJob => (0x0000, 0x1f, true), 96 | MessageType::SetNewPrevHash => (0x0000, 0x20, true), 97 | MessageType::SetTarget => (0x0000, 0x21, true), 98 | MessageType::SetCustomMiningJob => (0x0000, 0x22, false), 99 | MessageType::SetCustomMiningJobSuccess => (0x0000, 0x23, false), 100 | MessageType::SetCustomMiningJobError => (0x0000, 0x24, false), 101 | MessageType::Reconnect => (0x0000, 0x25, false), 102 | MessageType::SetGroupChannel => (0x0000, 0x26, false), 103 | MessageType::TestMessage1 => (0x0000, 0xfe, false), 104 | MessageType::TestMessage2 => (0x0000, 0xff, true) 105 | ); 106 | -------------------------------------------------------------------------------- /stratumv2/README.md: -------------------------------------------------------------------------------- 1 | # stratumv2 2 | 3 | This crate contains a library implemenation of the messages and functions 4 | required to build a networked implementation for each component in the Stratum V2 5 | protocol. 6 | 7 | ## Project Structure 8 | 9 | 10 | The library has the following structure: 11 | 12 | - `benches` - Contains the bench marking tests for the library. 13 | 14 | - `nx-noise` - A noise frame work crate generated by [Noise Explorer](https://noiseexplorer.com/). 15 | 16 | - `src/codec` - This module contains traits and functions to serialize and deserialize StratumV2 messages as well as frame and unframe network messages. 17 | 18 | - `src/common` - This module contains common functions and structs used in all 19 | Stratum V2 sub-protocols. 20 | 21 | - `src/job_negotiation` - This module contains functions and structs specific to 22 | ONLY the `job_negotiation` sub-protocol. 23 | 24 | - `src/macro_message` - This module contains internal macros to generate 25 | Stratum V2 `Messages` and test macros for `Messages`. 26 | 27 | - `src/mining` - This module contains functions and structs specific to 28 | ONLY the `mining` sub-protocol. 29 | 30 | - `src/network` - This module contains structs and traits to allow an upstream 31 | project to build a networked implementation. 32 | 33 | - `src/noise` - This module contains functions and structs to enable encrypted 34 | communication using the `Noise Framework` and to validate the certificates of the 35 | `Mining Pool Server`. 36 | 37 | - `src/types` - This module contains all the custom types used in Stratum V2. 38 | 39 | - `src/error` - Contains the custom error types for this crate. 40 | 41 | ## Development 42 | 43 | All messages should be constructed using the macro [impl_message](./src/macro_message/message.rs). 44 | The only exceptions are for `Messages` that don't have a [MessageType](./src/types/message_type.rs) such as the messages 45 | found in `src/noise`, since the `noise protocol` is a precursor to subsequent Stratum V2 communication. 46 | 47 | Creating a Stratum V2 `Message`: 48 | 49 | ```rust 50 | use crate::impl_message; 51 | 52 | impl_message!( 53 | /// Struct docstring 54 | StructName, 55 | 56 | /// Field Docstring 57 | foo u32, 58 | 59 | /// Field Docstring 60 | bar u16, 61 | ); 62 | 63 | impl StructName { 64 | pub fn new(foo: u32, bar: u16) -> Result { 65 | Ok(StructName { foo, bar }) 66 | } 67 | } 68 | ``` 69 | 70 | ## Tests 71 | 72 | The current testing strategy takes the approach of heavily unit testing the 73 | smallest types in the library. By relying on the smallest types being tested, 74 | we assert that when those types are used to construct larger types, all the 75 | components in a larger type are correct. 76 | 77 | - All the native types such as `u8, u16, u32 etc...` and custom types such as 78 | `B0_255` and `B0_32` have been heavily tested to assert that they will 79 | de/serialize correctly according to the Stratum V2 spec. 80 | 81 | - Stratum V2 `Messages` are constructed using only these native and custom types. 82 | We don't need to test the de/serialization of the individual fields. Instead, 83 | we can test the rules around de/serializing the `Messages` as well as any other 84 | cases at this level. 85 | 86 | Testing a `Message` should use the macro [impl_message_tests](./src/macro_message/message.rs). 87 | This macro will generate the standard tests for a Stratum V2 `Message`. 88 | 89 | Testing a Stratum V2 `Message`: 90 | 91 | ```rust 92 | use crate::impl_message; 93 | 94 | impl_message!( 95 | /// Struct docstring 96 | StructName, 97 | 98 | /// Field Docstring 99 | foo u32, 100 | 101 | /// Field Docstring 102 | bar u16, 103 | ); 104 | 105 | impl StructName { 106 | pub fn new(foo: u32, bar: u16) -> Result { 107 | Ok(StructName { foo, bar }) 108 | } 109 | } 110 | 111 | #[cfg(test)] 112 | mod test { 113 | use super::*; 114 | use crate::impl_message_tests; 115 | 116 | fn expected_deserialized_state() -> StructName { 117 | StructName::new(1u32, 1u16).unwrap() 118 | } 119 | 120 | fn expected_serialized_state() -> Vec { 121 | return vec![ 122 | 0x01, 0x00, 0x00, 0x00, 123 | 0x01, 0x00, 124 | ]; 125 | } 126 | 127 | impl_message_tests!( 128 | StructName, 129 | expected_deserialized_state, 130 | expected_serialized_state, 131 | ); 132 | } 133 | 134 | ``` 135 | 136 | ## Benchmarking 137 | 138 | To run the benchmarking tests, run the following command: 139 | 140 | ``` 141 | cargo bench 142 | ``` 143 | -------------------------------------------------------------------------------- /stratumv2/src/macro_message/setup_connection_error.rs: -------------------------------------------------------------------------------- 1 | pub mod macro_prelude { 2 | pub use crate::common::SetupConnectionErrorCode; 3 | pub use crate::error::{Error, Result}; 4 | pub use crate::impl_message; 5 | pub use crate::types::{MessageType, STR0_255}; 6 | pub use std::io; 7 | } 8 | 9 | /// Implementation of the SetupConnectionError message for each sub protocol. 10 | #[doc(hidden)] 11 | #[macro_export] 12 | macro_rules! impl_setup_connection_error { 13 | ($flags_type:ident) => { 14 | use crate::macro_message::setup_connection_error::macro_prelude::*; 15 | 16 | impl_message!( 17 | /// One of the required responses from a Server to a Client when a 18 | /// new connection has failed. The server is required to send this 19 | /// message with an error code before closing the connection. 20 | /// 21 | /// If the error is a variant of [UnsupportedFeatureFlags](../common/enum.SetupConnectionErrorCode.html), 22 | /// the server MUST respond with all the feature flags that it does NOT support. 23 | /// 24 | /// If the flag is 0, then the error is some condition aside from unsupported 25 | /// flags. 26 | /// 27 | /// # Examples 28 | /// 29 | /// ```rust 30 | /// use stratumv2::mining; 31 | /// use stratumv2::common::SetupConnectionErrorCode; 32 | /// 33 | /// let conn_error = mining::SetupConnectionError::new( 34 | /// mining::SetupConnectionFlags::REQUIRES_VERSION_ROLLING, 35 | /// SetupConnectionErrorCode::UnsupportedFeatureFlags 36 | /// ); 37 | /// 38 | /// assert!(conn_error.is_ok()); 39 | /// assert_eq!( 40 | /// conn_error.unwrap().error_code, 41 | /// SetupConnectionErrorCode::UnsupportedFeatureFlags 42 | /// ); 43 | SetupConnectionError, 44 | 45 | /// Indicates all the flags that the server does NOT support. 46 | flags $flags_type, 47 | 48 | /// Error code is a predefined STR0_255 error code. 49 | error_code SetupConnectionErrorCode 50 | ); 51 | 52 | impl SetupConnectionError { 53 | pub fn new( 54 | flags: $flags_type, 55 | error_code: SetupConnectionErrorCode, 56 | ) -> Result { 57 | if flags.is_empty() 58 | && error_code == SetupConnectionErrorCode::UnsupportedFeatureFlags 59 | { 60 | return Err(Error::RequirementError( 61 | "a full set of unsupported flags MUST be returned to the client".into(), 62 | )); 63 | } 64 | 65 | Ok(SetupConnectionError { flags, error_code }) 66 | } 67 | } 68 | }; 69 | } 70 | 71 | #[cfg(test)] 72 | pub mod test_macro_prelude { 73 | pub use crate::impl_message_tests; 74 | } 75 | 76 | #[cfg(test)] 77 | #[doc(hidden)] 78 | #[macro_export] 79 | macro_rules! impl_setup_connection_error_tests { 80 | ($flags_type:ident) => { 81 | use crate::macro_message::setup_connection_error::test_macro_prelude::*; 82 | 83 | fn make_deserialized_setup_connection_error() -> SetupConnectionError { 84 | SetupConnectionError::new( 85 | $flags_type::all(), 86 | SetupConnectionErrorCode::UnsupportedFeatureFlags, 87 | ) 88 | .unwrap() 89 | } 90 | 91 | fn make_serialized_setup_connection_error() -> Vec { 92 | let mut serialized = vec![]; 93 | serialized.extend($flags_type::all().bits().to_le_bytes().iter()); // flags 94 | serialized.extend(vec![ 95 | // error_code 96 | 0x19, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x2d, 0x66, 97 | 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, 0x66, 0x6c, 0x61, 0x67, 0x73, 98 | ]); 99 | 100 | serialized 101 | } 102 | 103 | impl_message_tests!( 104 | SetupConnectionError, 105 | make_serialized_setup_connection_error, 106 | make_deserialized_setup_connection_error 107 | ); 108 | 109 | #[test] 110 | fn empty_feature_flags_error() { 111 | assert!(matches!( 112 | SetupConnectionError::new( 113 | $flags_type::empty(), 114 | SetupConnectionErrorCode::UnsupportedFeatureFlags 115 | ), 116 | Err(Error::RequirementError { .. }) 117 | )); 118 | } 119 | }; 120 | } 121 | -------------------------------------------------------------------------------- /stratumv2/src/types/primitives.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | codec::{ByteParser, Deserializable, Serializable}, 3 | error::Result, 4 | }; 5 | use std::io; 6 | 7 | impl Serializable for bool { 8 | fn serialize(&self, writer: &mut W) -> Result { 9 | let buffer = if *self { vec![1u8] } else { vec![0u8] }; 10 | writer.write(&buffer)?; 11 | Ok(buffer.len()) 12 | } 13 | } 14 | 15 | impl Serializable for u8 { 16 | fn serialize(&self, writer: &mut W) -> Result { 17 | let buffer = self.to_le_bytes(); 18 | writer.write(&buffer)?; 19 | Ok(buffer.len()) 20 | } 21 | } 22 | 23 | impl Serializable for u16 { 24 | fn serialize(&self, writer: &mut W) -> Result { 25 | let buffer = self.to_le_bytes(); 26 | writer.write(&buffer)?; 27 | Ok(buffer.len()) 28 | } 29 | } 30 | 31 | impl Serializable for u32 { 32 | fn serialize(&self, writer: &mut W) -> Result { 33 | let buffer = self.to_le_bytes(); 34 | writer.write(&buffer)?; 35 | Ok(buffer.len()) 36 | } 37 | } 38 | 39 | impl Serializable for f32 { 40 | fn serialize(&self, writer: &mut W) -> Result { 41 | let buffer = self.to_le_bytes(); 42 | writer.write(&buffer)?; 43 | Ok(buffer.len()) 44 | } 45 | } 46 | 47 | // TODO: The specs states that any bits OUTSIDE of the LSB MUST NOT be interpreted 48 | // for the meaning of the bool. 49 | // 50 | // It DOES suggest that the outside bits maybe reused to be interpreted with 51 | // additional meaning in the future. So if we want to be able to allow for this, 52 | // ser/der and initialization of the BOOL type may need to be redesigned. 53 | impl Deserializable for bool { 54 | fn deserialize(parser: &mut ByteParser) -> Result { 55 | let mut buffer: [u8; 1] = [0; 1]; 56 | buffer.clone_from_slice(parser.next_by(1)?); 57 | 58 | Ok(buffer[0] & 1 == 1) 59 | } 60 | } 61 | 62 | impl Deserializable for u8 { 63 | fn deserialize(parser: &mut ByteParser) -> Result { 64 | let mut buffer: [u8; 1] = [0; 1]; 65 | buffer.clone_from_slice(parser.next_by(1)?); 66 | 67 | Ok(u8::from_le_bytes(buffer)) 68 | } 69 | } 70 | 71 | impl Deserializable for u16 { 72 | fn deserialize(parser: &mut ByteParser) -> Result { 73 | let mut buffer: [u8; 2] = [0; 2]; 74 | buffer.clone_from_slice(parser.next_by(2)?); 75 | 76 | Ok(u16::from_le_bytes(buffer)) 77 | } 78 | } 79 | 80 | impl Deserializable for u32 { 81 | fn deserialize(parser: &mut ByteParser) -> Result { 82 | let mut buffer: [u8; 4] = [0; 4]; 83 | buffer.clone_from_slice(parser.next_by(4)?); 84 | 85 | Ok(u32::from_le_bytes(buffer)) 86 | } 87 | } 88 | 89 | impl Deserializable for f32 { 90 | fn deserialize(parser: &mut ByteParser) -> Result { 91 | let mut buffer: [u8; 4] = [0; 4]; 92 | buffer.clone_from_slice(parser.next_by(4)?); 93 | 94 | Ok(f32::from_le_bytes(buffer)) 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use crate::codec::{deserialize, serialize}; 101 | 102 | #[test] 103 | fn u8_serde() { 104 | assert_eq!(serialize(&0b10000001u8).unwrap(), vec![0b10000001u8]); 105 | assert_eq!(deserialize::(&[0b10000001u8]).unwrap(), 0b10000001u8); 106 | } 107 | 108 | #[test] 109 | fn u16_serde() { 110 | assert_eq!( 111 | serialize(&0b01000010_10000001u16).unwrap(), 112 | vec![0b10000001u8, 0b01000010u8] 113 | ); 114 | assert_eq!( 115 | deserialize::(&[0b10000001u8, 0b01000010u8]).unwrap(), 116 | 0b01000010_10000001u16 117 | ); 118 | } 119 | 120 | #[test] 121 | fn u32_serde() { 122 | assert_eq!( 123 | serialize(&0b00011000_00100100_01000010_10000001u32).unwrap(), 124 | vec![0b10000001u8, 0b01000010u8, 0b00100100u8, 0b00011000u8] 125 | ); 126 | assert_eq!( 127 | deserialize::(&[0b10000001u8, 0b01000010u8, 0b00100100u8, 0b00011000u8]).unwrap(), 128 | 0b00011000_00100100_01000010_10000001u32 129 | ); 130 | } 131 | 132 | #[test] 133 | fn f32_serde() { 134 | // Binary representation of PI in 32-bit floating-point: 135 | // 0 10000000 10010010000111111011011 136 | assert_eq!( 137 | serialize(&3.14159274101257324f32).unwrap(), 138 | vec![0b11011011, 0b00001111, 0b01001001u8, 0b01000000u8] 139 | ); 140 | assert_eq!( 141 | deserialize::(&[0b11011011, 0b00001111, 0b01001001u8, 0b01000000u8]).unwrap(), 142 | 3.14159274101257324f32 143 | ); 144 | } 145 | 146 | #[test] 147 | fn bool_serde() { 148 | assert_eq!(serialize(&true).unwrap(), vec![1u8]); 149 | assert_eq!(serialize(&false).unwrap(), vec![0u8]); 150 | assert_eq!(deserialize::(&vec![1u8]).unwrap(), true); 151 | assert_eq!(deserialize::(&vec![0u8]).unwrap(), false); 152 | 153 | // Deserialize other values that should ONLY interpret the set or 154 | // unset LSB. 155 | assert_eq!(deserialize::(&vec![2u8]).unwrap(), false); 156 | assert_eq!(deserialize::(&vec![3u8]).unwrap(), true); 157 | assert_eq!(deserialize::(&vec![4u8]).unwrap(), false); 158 | assert_eq!(deserialize::(&vec![u8::MAX]).unwrap(), true); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /stratumv2/src/codec/frame.rs: -------------------------------------------------------------------------------- 1 | use crate::codec::{serialize, ByteParser, Deserializable, Serializable}; 2 | use crate::error::{Error, Result}; 3 | use crate::types::{MessageType, U24}; 4 | use std::io; 5 | 6 | /// The CHANNEL_BIT_MASK is used to mask out the MSB to identify if a message 7 | /// type has a channel id in a message frame. 8 | pub(crate) const CHANNEL_BIT_MASK: u16 = 0x8000; 9 | 10 | /// The EXTENSION_TYPE_MASK disables the MSB so the u16 representation of the 11 | /// extension type in a message frame has the same value as the u15 representation. 12 | const EXTENSION_TYPE_MASK: u16 = 0x7FFF; 13 | 14 | /// Used to deserialize a received network frame. The payload would be further 15 | /// deserialized according to the received MessageTypes. Message can also be 16 | /// used to store messages on an outgoing buffer before being processed 17 | /// and sent over the wire. 18 | #[derive(Debug, Clone, PartialEq)] 19 | pub struct Message { 20 | pub message_type: MessageType, 21 | pub payload: Vec, 22 | } 23 | 24 | impl Message { 25 | pub fn new>>(message_type: MessageType, payload: T) -> Message { 26 | Message { 27 | message_type, 28 | payload: payload.into(), 29 | } 30 | } 31 | } 32 | 33 | impl Serializable for Message { 34 | fn serialize(&self, writer: &mut W) -> Result { 35 | let message_type: u8 = self.message_type.msg_type(); 36 | let mut extension_type: u16 = self.message_type.ext_type(); 37 | 38 | // Enable the MSB to indicate if this message type has a channel id. 39 | if self.message_type.channel_bit() { 40 | extension_type |= CHANNEL_BIT_MASK; 41 | } 42 | 43 | let message_length = U24::new(self.payload.len() as u32)?; 44 | 45 | Ok([ 46 | extension_type.serialize(writer)?, 47 | message_type.serialize(writer)?, 48 | message_length.serialize(writer)?, 49 | writer.write(self.payload.as_slice())?, 50 | ] 51 | .iter() 52 | .sum()) 53 | } 54 | } 55 | 56 | impl Deserializable for Message { 57 | fn deserialize(parser: &mut ByteParser) -> Result { 58 | let mut extension_type = u16::deserialize(parser)?; 59 | let channel_bit: bool = (extension_type & CHANNEL_BIT_MASK) != 0; 60 | extension_type &= EXTENSION_TYPE_MASK; 61 | 62 | let message_type = MessageType::new(extension_type, u8::deserialize(parser)?)?; 63 | 64 | if message_type.channel_bit() != channel_bit { 65 | return Err(Error::UnexpectedChannelBit(channel_bit)); 66 | } 67 | 68 | let message_length = U24::deserialize(parser)?; 69 | let payload = parser.next_by(message_length.0 as usize)?; 70 | 71 | // TODO(chpatton013): Make this operation zero-copy by taking the contents of the parser. 72 | Ok(Message::new(message_type, payload.to_vec())) 73 | } 74 | } 75 | 76 | /// Trait for wrapping and unwrapping messages with a network frame. 77 | pub trait Frameable: Deserializable + Serializable { 78 | fn message_type() -> MessageType; 79 | } 80 | 81 | /// Utility function to create a network frame message according to a type 82 | /// that implements the Frameable trait. 83 | pub fn frame(payload: &T) -> Result { 84 | Ok(Message::new(T::message_type(), serialize(payload)?)) 85 | } 86 | 87 | /// Utility function to convert a network frame message into a type that implements 88 | /// the Frameable trait. 89 | pub fn unframe(message: &Message) -> Result { 90 | let expected_message_type = T::message_type(); 91 | if expected_message_type != message.message_type { 92 | return Err(Error::UnexpectedMessageType( 93 | expected_message_type.ext_type(), 94 | expected_message_type.msg_type(), 95 | )); 96 | } 97 | 98 | let mut parser = ByteParser::new(message.payload.as_slice(), 0); 99 | 100 | T::deserialize(&mut parser) 101 | } 102 | 103 | #[cfg(test)] 104 | mod tests { 105 | use super::*; 106 | use crate::codec::{deserialize, serialize}; 107 | use crate::impl_message; 108 | 109 | impl_message!(TestMessage1, x u8); 110 | 111 | impl TestMessage1 { 112 | fn new(x: u8) -> Result { 113 | Ok(TestMessage1 { x }) 114 | } 115 | } 116 | 117 | impl_message!(TestMessage2, x u8); 118 | 119 | impl TestMessage2 { 120 | fn new(x: u8) -> Result { 121 | Ok(TestMessage2 { x }) 122 | } 123 | } 124 | 125 | #[test] 126 | fn frame_test_message() { 127 | let unframed = TestMessage1::new(5u8).unwrap(); 128 | let framed = Message::new(MessageType::TestMessage1, vec![0x05]); 129 | assert_eq!(frame(&unframed).unwrap(), framed); 130 | assert_eq!(unframe::(&framed).unwrap(), unframed); 131 | } 132 | 133 | #[test] 134 | fn test_message_1_frame_serde() { 135 | let deserialized = Message::new(MessageType::TestMessage1, vec![0x05]); 136 | let serialized = vec![ 137 | 0x00, 0x00, // extension type & channel bit (MSB=0) 138 | 0xfe, // message type 139 | 0x01, 0x00, 0x00, // message length 140 | 0x05, // message payload 141 | ]; 142 | assert_eq!(serialize(&deserialized).unwrap(), serialized); 143 | assert_eq!( 144 | deserialize::(serialized.as_slice()).unwrap(), 145 | deserialized 146 | ); 147 | } 148 | 149 | #[test] 150 | fn test_message_2_frame_serde() { 151 | let deserialized = Message::new(MessageType::TestMessage2, vec![0x05]); 152 | let serialized = vec![ 153 | 0x00, 0x80, // extension type & channel bit (MSB=1) 154 | 0xff, // message type 155 | 0x01, 0x00, 0x00, // message length 156 | 0x05, // message payload 157 | ]; 158 | assert_eq!(serialize(&deserialized).unwrap(), serialized); 159 | assert_eq!( 160 | deserialize::(serialized.as_slice()).unwrap(), 161 | deserialized 162 | ); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /stratumv2/src/types/fixed.rs: -------------------------------------------------------------------------------- 1 | use crate::codec::{ByteParser, Deserializable, Serializable}; 2 | use crate::error::{Error, Result}; 3 | use std::convert::{TryFrom, TryInto}; 4 | use std::io; 5 | 6 | /// U24 is an unsigned integer type of 24-bits in little endian. This will 7 | /// usually be used to represent the length of a variable-length string or 8 | /// byte-stream. 9 | #[derive(Debug, Clone, PartialEq)] 10 | pub struct U24(pub u32); 11 | 12 | impl U24 { 13 | pub const MIN: u32 = 0; 14 | pub const MAX: u32 = 2u32.pow(24) - 1; 15 | pub const BITS: u32 = 24; 16 | 17 | pub fn new(value: u32) -> Result { 18 | value.try_into() 19 | } 20 | } 21 | 22 | impl PartialEq for U24 { 23 | fn eq(&self, other: &u32) -> bool { 24 | self.0 == *other 25 | } 26 | } 27 | 28 | impl PartialEq for u32 { 29 | fn eq(&self, other: &U24) -> bool { 30 | *self == other.0 31 | } 32 | } 33 | 34 | impl From for u32 { 35 | fn from(u: U24) -> Self { 36 | u.0 37 | } 38 | } 39 | 40 | impl TryFrom for U24 { 41 | type Error = Error; 42 | 43 | fn try_from(value: u32) -> Result { 44 | U24::try_from(value as usize) 45 | } 46 | } 47 | 48 | impl From for usize { 49 | fn from(u: U24) -> Self { 50 | u.0 as usize 51 | } 52 | } 53 | 54 | impl TryFrom for U24 { 55 | type Error = Error; 56 | 57 | fn try_from(value: usize) -> Result { 58 | if value > usize::pow(2, 24) - 1 { 59 | return Err(Error::RequirementError( 60 | "U24 cannot be greater than 2^24-1".into(), 61 | )); 62 | } 63 | 64 | Ok(U24(value as u32)) 65 | } 66 | } 67 | 68 | impl Deserializable for U24 { 69 | fn deserialize(parser: &mut ByteParser) -> Result { 70 | // Pad the 3-byte slice up to a 4-byte array. 71 | let mut buffer: [u8; 4] = [0; 4]; 72 | buffer[..3].clone_from_slice(parser.next_by(3)?); 73 | 74 | U24::new(u32::from_le_bytes(buffer)) 75 | } 76 | } 77 | 78 | impl Serializable for U24 { 79 | fn serialize(&self, writer: &mut W) -> Result { 80 | writer.write(&self.0.to_le_bytes()[0..3])?; 81 | Ok(3) 82 | } 83 | } 84 | 85 | /// U256 is an unsigned integer type of 256-bits in little endian. This will 86 | /// usually be used to represent a raw SHA256 byte output. 87 | #[derive(Debug, Clone, PartialEq)] 88 | pub struct U256(pub [u8; 32]); 89 | 90 | impl Deserializable for U256 { 91 | fn deserialize(parser: &mut ByteParser) -> Result { 92 | let mut buffer: [u8; 32] = [0; 32]; 93 | buffer[..].clone_from_slice(parser.next_by(32)?); 94 | Ok(U256(buffer)) 95 | } 96 | } 97 | 98 | impl Serializable for U256 { 99 | fn serialize(&self, writer: &mut W) -> Result { 100 | writer.write(&self.0)?; 101 | Ok(32) 102 | } 103 | } 104 | 105 | impl PartialEq<[u8; 32]> for U256 { 106 | fn eq(&self, other: &[u8; 32]) -> bool { 107 | self.0 == *other 108 | } 109 | } 110 | 111 | impl PartialEq for [u8; 32] { 112 | fn eq(&self, other: &U256) -> bool { 113 | *self == other.0 114 | } 115 | } 116 | 117 | impl From for [u8; 32] { 118 | fn from(u: U256) -> Self { 119 | u.0 120 | } 121 | } 122 | 123 | impl From<[u8; 32]> for U256 { 124 | fn from(a: [u8; 32]) -> Self { 125 | U256(a) 126 | } 127 | } 128 | 129 | #[cfg(test)] 130 | mod tests { 131 | use super::*; 132 | use crate::codec::{deserialize, serialize}; 133 | 134 | fn make_encoded_u24(value: u32) -> Vec { 135 | value.to_le_bytes()[..3].to_vec() 136 | } 137 | 138 | #[test] 139 | fn u24_new() { 140 | assert_eq!(U24::new(0).unwrap(), U24(0)); 141 | assert_eq!(U24::new(1).unwrap(), U24(1)); 142 | assert_eq!(U24::new(2u32.pow(24) - 1).unwrap(), U24(U24::MAX)); 143 | assert!(matches!( 144 | U24::new(2u32.pow(24)), 145 | Err(Error::RequirementError { .. }) 146 | )); 147 | assert!(matches!( 148 | U24::new(2u32.pow(24) + 1), 149 | Err(Error::RequirementError { .. }) 150 | )); 151 | } 152 | 153 | #[test] 154 | fn u24_serde_ok() { 155 | let encoded = make_encoded_u24(5); 156 | let decoded = U24::new(5).unwrap(); 157 | 158 | assert_eq!(deserialize::(&encoded).unwrap(), decoded); 159 | assert_eq!(serialize(&decoded).unwrap(), encoded); 160 | } 161 | 162 | #[test] 163 | fn u24_deserialize_err() { 164 | let encoded: [u8; 2] = [0; 2]; 165 | assert!(matches!( 166 | deserialize::(&encoded), 167 | Err(Error::ParseError { .. }) 168 | )); 169 | } 170 | 171 | #[test] 172 | fn u24_serialize_err() { 173 | // Since we are performing bounds-checking in the U24::new factory, we will not be 174 | // performing any bounds-checking on serialization. 175 | let encoded = make_encoded_u24(0); 176 | assert_eq!(serialize(&U24(2u32.pow(24))).unwrap(), encoded); 177 | } 178 | 179 | fn make_encoded_u256(a: u64, b: u64, c: u64, d: u64) -> Vec { 180 | let mut buffer = vec![]; 181 | buffer.extend_from_slice(&a.to_le_bytes()); 182 | buffer.extend_from_slice(&b.to_le_bytes()); 183 | buffer.extend_from_slice(&c.to_le_bytes()); 184 | buffer.extend_from_slice(&d.to_le_bytes()); 185 | return buffer; 186 | } 187 | 188 | #[test] 189 | fn u256_serde_ok() { 190 | let encoded = make_encoded_u256(1, 2, 3, 4); 191 | let decoded = U256([ 192 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1 193 | 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2 194 | 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 3 195 | 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 4 196 | ]); 197 | assert_eq!(deserialize::(&encoded).unwrap(), decoded); 198 | assert_eq!(serialize(&decoded).unwrap(), encoded); 199 | } 200 | 201 | #[test] 202 | fn u256_deserialize_err() { 203 | let encoded: [u8; 31] = [0; 31]; 204 | assert!(matches!( 205 | deserialize::(&encoded), 206 | Err(Error::ParseError { .. }) 207 | )); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /examples/setup_connection.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use stratumv2::{ 3 | codec::{deserialize, frame, serialize, unframe, Frameable, Message}, 4 | common::SetupConnection, 5 | mining::{SetupConnectionFlags, SetupConnectionSuccess, SetupConnectionSuccessFlags}, 6 | types::MessageType, 7 | }; 8 | use tokio::net::{TcpListener, TcpStream}; 9 | 10 | // Addreses and ports for the example. 11 | const POOL_ADDR: &str = "127.0.0.1:8080"; 12 | const MINER_ADDR: &str = "127.0.0.1:8545"; 13 | 14 | #[tokio::main] 15 | async fn main() { 16 | tokio::spawn(async move { 17 | println!("Pool: mining pool now listening for connections"); 18 | Pool::new(&POOL_ADDR).listen().await; 19 | }); 20 | 21 | println!("Miner: sending SetupConnection for new Mining Connection"); 22 | let miner = Miner::new(&MINER_ADDR); 23 | 24 | let setup_conn = SetupConnection::new_mining( 25 | 2, 26 | 2, 27 | SetupConnectionFlags::REQUIRES_STANDARD_JOBS, 28 | "0.0.0.0", 29 | 8545, 30 | "Bitmain", 31 | "S9i 13.5", 32 | "braiins-os-2018-09-22-1-hash", 33 | "some-uuid", 34 | ) 35 | .unwrap(); 36 | 37 | miner 38 | .send_message(&TcpStream::connect(&POOL_ADDR).await.unwrap(), &setup_conn) 39 | .await; 40 | 41 | miner.listen().await; 42 | } 43 | 44 | /// Pool is a convenience struct to demonstrate simple behaviour of a Mining Pool. 45 | struct Pool<'a> { 46 | /// Listening address of the Mining Pool to accept incoming connections. 47 | listening_addr: &'a str, 48 | 49 | /// The required feature flags for the mining sub protocol. These flags 50 | /// should be sent on a SetupConnectionSuccess. 51 | required_mining_feature_flags: SetupConnectionSuccessFlags, 52 | } 53 | 54 | impl<'a> Pool<'a> { 55 | fn new(listening_addr: &'a str) -> Pool<'a> { 56 | Pool { 57 | listening_addr, 58 | required_mining_feature_flags: SetupConnectionSuccessFlags::REQUIRES_FIXED_VERSION, 59 | } 60 | } 61 | 62 | /// Listen on the port and handle the messages. 63 | async fn listen(&self) { 64 | let listener = TcpListener::bind(&self.listening_addr).await.unwrap(); 65 | let mut buffer = [0u8; 1024]; 66 | 67 | match listener.accept().await { 68 | Ok((socket, _)) => loop { 69 | match socket.try_read(&mut buffer) { 70 | Ok(_) => { 71 | &self.handle_recv_bytes(&buffer).await; 72 | break; 73 | } 74 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => { 75 | continue; 76 | } 77 | _ => continue, 78 | } 79 | }, 80 | Err(e) => println!("failed to accept client {:?}", e), 81 | } 82 | } 83 | 84 | async fn handle_recv_bytes(&self, buffer: &[u8]) { 85 | let network_message = deserialize::(&buffer).unwrap(); 86 | 87 | match network_message.message_type { 88 | MessageType::SetupConnection => { 89 | let setup_conn = unframe::(&network_message).unwrap(); 90 | 91 | match setup_conn { 92 | SetupConnection::Mining(v) => { 93 | let conn_success = SetupConnectionSuccess::new( 94 | v.min_version, 95 | self.required_mining_feature_flags, 96 | ) 97 | .unwrap(); 98 | 99 | println!("Pool: sending SetupConnectionSuccess message"); 100 | let network_message = frame(&conn_success).unwrap(); 101 | let buffer = serialize(&network_message).unwrap(); 102 | 103 | TcpStream::connect(&MINER_ADDR) 104 | .await 105 | .unwrap() 106 | .try_write(&buffer) 107 | .unwrap(); 108 | } 109 | _ => (), 110 | } 111 | } 112 | _ => (), 113 | } 114 | } 115 | } 116 | 117 | /// Miner is a convenience struct to demonstrate simple behaviour of a Miner. 118 | struct Miner<'a> { 119 | /// Listening address of the miner to accept incoming connections. 120 | listening_addr: &'a str, 121 | } 122 | 123 | impl<'a> Miner<'a> { 124 | fn new(listening_addr: &'a str) -> Miner<'a> { 125 | Miner { listening_addr } 126 | } 127 | 128 | async fn listen(&self) { 129 | let listener = TcpListener::bind(&self.listening_addr).await.unwrap(); 130 | let mut buffer = [0u8; 1024]; 131 | 132 | match listener.accept().await { 133 | Ok((socket, _)) => loop { 134 | match socket.try_read(&mut buffer) { 135 | Ok(_) => { 136 | println!("Miner: received message from Pool"); 137 | &self.handle_recv_bytes(&buffer).await; 138 | break; 139 | } 140 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => { 141 | continue; 142 | } 143 | _ => continue, 144 | } 145 | }, 146 | Err(e) => println!("failed to accept client {:?}", e), 147 | } 148 | } 149 | 150 | async fn send_message(&self, stream: &TcpStream, msg: &T) { 151 | let network_message = frame(msg).unwrap(); 152 | let buffer = serialize(&network_message).unwrap(); 153 | 154 | stream.try_write(&buffer).unwrap(); 155 | } 156 | 157 | async fn handle_recv_bytes(&self, buffer: &[u8]) { 158 | let network_message = deserialize::(&buffer).unwrap(); 159 | 160 | match network_message.message_type { 161 | MessageType::SetupConnectionSuccess => { 162 | let setup_conn_success = 163 | unframe::(&network_message).unwrap(); 164 | 165 | println!("Miner: Received a SetupConnectionSuccess message with feature flags supported by the Mining Pool: {:?}", setup_conn_success.flags) 166 | } 167 | _ => (), 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /examples/noise_handshake.rs: -------------------------------------------------------------------------------- 1 | use rand::rngs::OsRng; 2 | use std::io; 3 | use std::time::SystemTime; 4 | use stratumv2::{ 5 | bitcoin::util::base58, 6 | codec::{deserialize, serialize, Deserializable, Serializable}, 7 | noise::{ 8 | new_noise_initiator, new_noise_responder, AuthorityKeyPair, AuthorityPublicKey, 9 | CertificateFormat, NoiseSession, SignatureNoiseMessage, SignedCertificate, StaticKeyPair, 10 | }, 11 | types::unix_timestamp::{system_unix_time_to_u32, unix_u32_now}, 12 | }; 13 | use tokio::net::{TcpListener, TcpStream}; 14 | use tokio::time::{sleep, Duration}; 15 | 16 | const POOL_ADDR: &str = "127.0.0.1:8085"; 17 | const MINER_ADDR: &str = "127.0.0.1:8545"; 18 | 19 | #[tokio::main] 20 | async fn main() { 21 | let authority_keypair = AuthorityKeyPair::generate(&mut OsRng {}); 22 | let authority_public_key = base58::encode_slice(&authority_keypair.public.to_bytes()); 23 | 24 | tokio::spawn(async move { 25 | Pool::new(&POOL_ADDR, &authority_keypair).listen().await; 26 | }); 27 | 28 | sleep(Duration::from_secs(2)).await; 29 | 30 | let mut miner = Miner::new(&MINER_ADDR, &authority_public_key); 31 | 32 | miner 33 | .send_message( 34 | &TcpStream::connect(&POOL_ADDR).await.unwrap(), 35 | &mut [0u8; 1024], 36 | ) 37 | .await; 38 | 39 | miner.listen().await; 40 | } 41 | 42 | /// Pool is a convenience struct to demonstrate simple behaviour of a Mining Pool. 43 | struct Pool<'a> { 44 | /// Listening address of the Mining Pool to accept incoming connections. 45 | listening_addr: &'a str, 46 | authority_keypair: &'a AuthorityKeyPair, 47 | // TODO: Pass the static keypair on constructor as option 48 | static_keypair: StaticKeyPair, 49 | noise_session: NoiseSession, 50 | } 51 | 52 | impl<'a> Pool<'a> { 53 | fn new(listening_addr: &'a str, authority_keypair: &'a AuthorityKeyPair) -> Pool<'a> { 54 | let static_keypair = StaticKeyPair::default(); 55 | 56 | Pool { 57 | listening_addr, 58 | authority_keypair, 59 | static_keypair: static_keypair.clone(), 60 | noise_session: new_noise_responder(Some(static_keypair)), 61 | } 62 | } 63 | 64 | /// Listen on the port and handle the messages. 65 | async fn listen(&mut self) { 66 | let listener = TcpListener::bind(&self.listening_addr).await.unwrap(); 67 | let mut buffer = [0u8; 1024]; 68 | 69 | match listener.accept().await { 70 | Ok((socket, _)) => loop { 71 | match socket.try_read(&mut buffer) { 72 | Ok(_) => { 73 | &self.handle_recv_bytes(&mut buffer).await; 74 | break; 75 | } 76 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => { 77 | continue; 78 | } 79 | _ => continue, 80 | } 81 | }, 82 | Err(e) => println!("failed to accept client {:?}", e), 83 | } 84 | } 85 | 86 | async fn handle_recv_bytes(&mut self, buffer: &mut [u8]) { 87 | // Receive the noise handshake messages and return. 88 | self.noise_session.recv_message(buffer).unwrap(); 89 | self.send_message(&TcpStream::connect(&MINER_ADDR).await.unwrap(), buffer) 90 | .await; 91 | 92 | // Construct and send the SignatureNoiseMessage. 93 | let valid_from = unix_u32_now().unwrap(); 94 | let not_valid_after = 95 | system_unix_time_to_u32(&(SystemTime::now() + Duration::from_secs(5))).unwrap(); 96 | 97 | let key = self.static_keypair.get_public_key(); 98 | let cert = SignedCertificate::new(0, valid_from, not_valid_after, &key).unwrap(); 99 | 100 | let signature_noise_msg = 101 | SignatureNoiseMessage::from_auth_key(&self.authority_keypair, &cert).unwrap(); 102 | 103 | let serialized_msg = serialize(&signature_noise_msg).unwrap(); 104 | 105 | let mut buf = [0u8; 1024]; 106 | buf[..serialized_msg.len()].copy_from_slice(&serialized_msg); 107 | 108 | self.send_message(&TcpStream::connect(&MINER_ADDR).await.unwrap(), &mut buf) 109 | .await; 110 | } 111 | 112 | // TODO: Update this to use Frameable trait. 113 | async fn send_message(&mut self, stream: &TcpStream, msg: &mut [u8]) { 114 | self.noise_session.send_message(msg).unwrap(); 115 | stream.try_write(&msg).unwrap(); 116 | } 117 | } 118 | 119 | /// Miner is a convenience struct to demonstrate simple behaviour of a Miner. 120 | struct Miner<'a> { 121 | /// Listening address of the miner to accept incoming connections. 122 | listening_addr: &'a str, 123 | /// Base58 encoded string of the authority public key. 124 | authority_public_key: &'a str, 125 | noise_session: NoiseSession, 126 | } 127 | 128 | impl<'a> Miner<'a> { 129 | pub fn new(listening_addr: &'a str, authority_public_key: &'a str) -> Miner<'a> { 130 | Miner { 131 | listening_addr, 132 | authority_public_key, 133 | noise_session: new_noise_initiator(), 134 | } 135 | } 136 | 137 | async fn listen(&mut self) { 138 | let listener = TcpListener::bind(&self.listening_addr).await.unwrap(); 139 | let mut buffer = [0u8; 1024]; 140 | 141 | loop { 142 | match listener.accept().await { 143 | Ok((socket, _)) => loop { 144 | match socket.try_read(&mut buffer) { 145 | Ok(_) => { 146 | &self.handle_recv_bytes(&mut buffer).await; 147 | break; 148 | } 149 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => { 150 | continue; 151 | } 152 | _ => continue, 153 | } 154 | }, 155 | Err(e) => println!("failed to accept client {:?}", e), 156 | } 157 | } 158 | } 159 | 160 | async fn handle_recv_bytes(&mut self, buffer: &mut [u8]) { 161 | // TODO: Rethink this logic, after calling `recv_message` the state 162 | // will be transistioned into transport stage but won't be a 163 | // SignatureNoiseMessage. 164 | if self.noise_session.is_transport() { 165 | self.noise_session.recv_message(buffer).unwrap(); 166 | 167 | // Deserialize and recreate the certificate format. Validate the 168 | // signature is valid over the counter parties static key. 169 | let msg = deserialize::(buffer).unwrap(); 170 | let remote_static_key = self.noise_session.get_remote_static_public_key().unwrap(); 171 | 172 | println!( 173 | "Is the SignatureNoiseMessage valid? - {:?}", 174 | CertificateFormat::new(&self.authority_public_key, &remote_static_key, &msg) 175 | .unwrap() 176 | .verify() 177 | .is_ok() 178 | ); 179 | } else { 180 | self.noise_session.recv_message(buffer).unwrap(); 181 | } 182 | } 183 | 184 | // TODO: Update this to use Frameable trait. 185 | async fn send_message(&mut self, stream: &TcpStream, msg: &mut [u8]) { 186 | self.noise_session.send_message(msg).unwrap(); 187 | stream.try_write(&msg).unwrap(); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /stratumv2/src/noise/mod.rs: -------------------------------------------------------------------------------- 1 | mod certificate_format; 2 | mod noise_session; 3 | mod signature_noise_message; 4 | mod signed_certificate; 5 | mod types; 6 | 7 | pub use certificate_format::CertificateFormat; 8 | pub use noise_session::{new_noise_initiator, new_noise_responder, NoiseSession}; 9 | pub use signature_noise_message::SignatureNoiseMessage; 10 | pub use signed_certificate::SignedCertificate; 11 | pub use types::{ 12 | generate_authority_keypair, AuthorityKeyPair, AuthorityPublicKey, Signature, StaticKeyPair, 13 | StaticPublicKey, 14 | }; 15 | 16 | #[cfg(test)] 17 | mod test { 18 | use crate::{ 19 | codec::{deserialize, Serializable}, 20 | error::Error, 21 | noise::certificate_format::CertificateFormat, 22 | noise::noise_session::{new_noise_initiator, new_noise_responder}, 23 | noise::signature_noise_message::SignatureNoiseMessage, 24 | noise::signed_certificate::{authority_sign_cert, SignedCertificate}, 25 | noise::types::{AuthorityKeyPair, StaticKeyPair, StaticPublicKey}, 26 | types::unix_timestamp::system_unix_time_to_u32, 27 | types::unix_timestamp::unix_u32_now, 28 | }; 29 | use bitcoin::util::base58; 30 | use noiseexplorer_nx::types::Keypair; 31 | use rand::rngs::OsRng; 32 | use std::thread::sleep; 33 | use std::time::{Duration, SystemTime}; 34 | 35 | // Helper function to generate timestamps for SignedCertificates. 36 | fn setup_timestamps(valid_until: u32) -> (u32, u32) { 37 | ( 38 | unix_u32_now().unwrap(), 39 | system_unix_time_to_u32(&(SystemTime::now() + Duration::from_secs(valid_until as u64))) 40 | .unwrap(), 41 | ) 42 | } 43 | 44 | #[test] 45 | fn init_signed_certificate() { 46 | let (valid_from, not_valid_after) = setup_timestamps(5); 47 | 48 | let pubkey = Keypair::default().get_public_key(); 49 | let cert = SignedCertificate::new(0, valid_from, not_valid_after, &pubkey); 50 | assert!(cert.is_ok()); 51 | } 52 | 53 | #[test] 54 | fn invalid_time_signed_certificate() { 55 | let (valid_from, not_valid_after) = setup_timestamps(5); 56 | 57 | // Should return an error since valid_from time is greater than not_valid_after time. 58 | let pubkey = Keypair::default().get_public_key(); 59 | let cert = SignedCertificate::new(0, not_valid_after, valid_from, &pubkey); 60 | assert!(cert.is_err()); 61 | } 62 | 63 | // Helper function to setup the keys and a signature noise message for certificate 64 | // verification. 65 | fn setup_keys_and_signature() -> (AuthorityKeyPair, StaticPublicKey, SignatureNoiseMessage) { 66 | let authority_keypair = AuthorityKeyPair::generate(&mut OsRng {}); 67 | 68 | let static_keypair = StaticKeyPair::default(); 69 | let static_pub_key = static_keypair.get_public_key(); 70 | 71 | let (valid_from, not_valid_after) = setup_timestamps(1); 72 | let cert = SignedCertificate::new(0, valid_from, not_valid_after, &static_pub_key).unwrap(); 73 | 74 | let signature = authority_sign_cert(&authority_keypair, &cert).unwrap(); 75 | let signature_noise_message = SignatureNoiseMessage::new(&cert, signature); 76 | 77 | (authority_keypair, static_pub_key, signature_noise_message) 78 | } 79 | 80 | #[test] 81 | fn expired_certficate_format() { 82 | let (authority_keypair, static_pub_key, signature_noise_message) = 83 | setup_keys_and_signature(); 84 | 85 | let key = &base58::encode_slice(&authority_keypair.public.to_bytes()); 86 | let certificate = 87 | CertificateFormat::new(&key, &static_pub_key, &signature_noise_message).unwrap(); 88 | 89 | // TODO: It would be better if we could mock the system time. 90 | sleep(Duration::new(1, 0)); 91 | assert!(certificate.verify().is_err()) 92 | } 93 | 94 | #[test] 95 | fn invalid_pubkey() { 96 | let (authority_keypair, static_pub_key, signature_noise_message) = 97 | setup_keys_and_signature(); 98 | let invalid_pubkey = "jg9QygGzKSVyxExPrj6bSCDq93c17Krj9yq5kNQnM3GP65"; 99 | 100 | let certificate = 101 | CertificateFormat::new(invalid_pubkey, &static_pub_key, &signature_noise_message); 102 | 103 | assert!(matches!(certificate, Err(Error::ParseError { .. }))); 104 | } 105 | 106 | #[test] 107 | fn noise_nx() { 108 | // This test contains a simulated lifecycle of the noise handshake 109 | // including validating the SignatureNoiseMessage. 110 | let server_static_keypair = StaticKeyPair::default(); 111 | 112 | let mut server = new_noise_responder(Some(server_static_keypair.clone())); 113 | let mut client = new_noise_initiator(); 114 | 115 | let mut read_buf = [0u8; 1024]; 116 | 117 | // -> e - First half of the handshake 118 | client.send_message(&mut read_buf).unwrap(); 119 | server.recv_message(&mut read_buf).unwrap(); 120 | 121 | // <- e... - Second half of the handshake 122 | server.send_message(&mut read_buf).unwrap(); 123 | client.recv_message(&mut read_buf).unwrap(); 124 | 125 | assert!(server.is_transport() && client.is_transport()); 126 | assert_eq!(server.get_handshake_hash(), client.get_handshake_hash()); 127 | 128 | // Server generates the SignatureNoiseMessage with a signature over the 129 | // StaticPublicKey. 130 | let (valid_from, not_valid_after) = setup_timestamps(100); 131 | let public_key = &server_static_keypair.get_public_key(); 132 | let cert = SignedCertificate::new(0, valid_from, not_valid_after, public_key).unwrap(); 133 | 134 | let authority_keypair = AuthorityKeyPair::generate(&mut OsRng {}); 135 | let signature_noise_msg = 136 | SignatureNoiseMessage::from_auth_key(&authority_keypair, &cert).unwrap(); 137 | 138 | let mut serialized_signature_msg = Vec::new(); 139 | signature_noise_msg 140 | .serialize(&mut serialized_signature_msg) 141 | .unwrap(); 142 | 143 | // Copy the serialized signature message into the buffer to simulate 144 | // sending over the wire. 145 | let mut buf = [0u8; 1024]; 146 | buf[..serialized_signature_msg.len()].copy_from_slice(&serialized_signature_msg); 147 | 148 | let plain_text = buf.clone(); 149 | server.send_message(&mut buf).unwrap(); 150 | 151 | let cipher_text = buf.clone(); 152 | assert!( 153 | plain_text[..serialized_signature_msg.len()] 154 | != cipher_text[..serialized_signature_msg.len()] 155 | ); 156 | 157 | // Client reads and decrypts the SignatureNoiseMessage into buf. 158 | client.recv_message(&mut buf).unwrap(); 159 | 160 | assert_eq!( 161 | buf[..serialized_signature_msg.len()], 162 | plain_text[..serialized_signature_msg.len()] 163 | ); 164 | 165 | // Client deseializes the SignatureNoiseMessage, builds a CertificateFormat 166 | // and verifies the signature is from the Mining Pools Authority Keypair. 167 | let signature_noise_message = deserialize::(&buf).unwrap(); 168 | let remote_static_key = client.get_remote_static_public_key().unwrap(); 169 | 170 | // By Base58 encoding the public authority key, it mimicks the behaviour 171 | // of the client downloading this from the server pools website or some 172 | // other public forum. 173 | let key = &base58::encode_slice(&authority_keypair.public.to_bytes()); 174 | let cert = 175 | CertificateFormat::new(&key, &remote_static_key, &signature_noise_message).unwrap(); 176 | 177 | assert!(cert.verify().is_ok()); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /stratumv2/src/types/strings.rs: -------------------------------------------------------------------------------- 1 | use crate::codec::{ByteParser, Deserializable, Serializable}; 2 | use crate::error::{Error, Result}; 3 | use std::io; 4 | 5 | /// An internal macro that implements a STR0 type that is restricted according to a MAX_LENGTH. 6 | macro_rules! impl_sized_STR0 { 7 | ($type:ident, $max_length:expr) => { 8 | #[derive(Debug, Clone, PartialEq)] 9 | pub struct $type { 10 | pub length: u8, 11 | pub data: String, 12 | } 13 | 14 | impl $type { 15 | const MAX_LENGTH: usize = $max_length; 16 | 17 | /// The constructor enforces the String input size as the MAX_LENGTH. A RequirementError 18 | /// will be returned if the input byte size is greater than the MAX_LENGTH. 19 | pub fn new>(value: T) -> Result<$type> { 20 | let value = value.into(); 21 | if value.len() > Self::MAX_LENGTH { 22 | return Err(Error::RequirementError( 23 | "string size cannot be greater than MAX_LENGTH".into(), 24 | )); 25 | } 26 | 27 | let length = value.len() as u8; 28 | Ok($type { 29 | length: length, 30 | data: value, 31 | }) 32 | } 33 | } 34 | 35 | /// PartialEq implementation allowing direct comparison between the STR0 type and String. 36 | impl PartialEq for $type { 37 | fn eq(&self, other: &String) -> bool { 38 | self.data == *other 39 | } 40 | } 41 | 42 | /// PartialEq implementation allowing direct comparison between String and the STR0 type. 43 | impl PartialEq<$type> for String { 44 | fn eq(&self, other: &$type) -> bool { 45 | *self == other.data 46 | } 47 | } 48 | 49 | /// From trait implementation that allows a STR0 to be converted into a String. 50 | impl From<$type> for String { 51 | fn from(s: $type) -> Self { 52 | s.data 53 | } 54 | } 55 | 56 | /// Deserialize trait implementation that allows a STR0 to be deserialized from a 57 | /// ByteParser. 58 | impl Deserializable for $type { 59 | fn deserialize(parser: &mut ByteParser) -> Result<$type> { 60 | // Parse the length header before the buffer. 61 | let header_length = u8::deserialize(parser)?; 62 | 63 | // Then parse the byte buffer. 64 | let mut data_buffer = vec![]; 65 | data_buffer.extend_from_slice(parser.next_by(header_length.into())?); 66 | 67 | $type::new(String::from_utf8(data_buffer)?) 68 | } 69 | } 70 | 71 | /// Serialize trait implementation that allows a STR0 to be serialized into an io::Writer. 72 | impl Serializable for $type { 73 | fn serialize(&self, writer: &mut W) -> Result { 74 | // Write the length header. 75 | let header_length = self.length.serialize(writer)?; 76 | // Then write the byte buffer. 77 | writer.write(self.data.as_bytes())?; 78 | 79 | Ok(header_length + self.length as usize) 80 | } 81 | } 82 | }; 83 | } 84 | 85 | #[cfg(test)] 86 | macro_rules! impl_sized_STR0_tests { 87 | ($type:ident, $max_length:expr) => { 88 | fn make_encoded_str(s: &str) -> Vec { 89 | let mut buffer = vec![]; 90 | buffer.push(s.len() as u8); 91 | buffer.extend_from_slice(s.as_bytes()); 92 | return buffer; 93 | } 94 | 95 | fn make_decoded_str(s: &str) -> $type { 96 | $type { 97 | length: s.len() as u8, 98 | data: s.into(), 99 | } 100 | } 101 | 102 | #[test] 103 | fn new() { 104 | let empty: String = "".into(); 105 | assert_eq!( 106 | $type::new("").unwrap(), 107 | $type { 108 | length: 0, 109 | data: empty 110 | } 111 | ); 112 | 113 | let nonempty: String = "human-readable-data".into(); 114 | assert_eq!( 115 | $type::new("human-readable-data").unwrap(), 116 | $type { 117 | length: 19, 118 | data: nonempty 119 | } 120 | ); 121 | 122 | let max_length: String = (0..$max_length).map(|_| 'a').collect(); 123 | assert_eq!( 124 | $type::new(max_length.clone()).unwrap(), 125 | $type { 126 | length: $max_length, 127 | data: max_length 128 | } 129 | ); 130 | 131 | let over_limit: String = (0..$max_length + 1).map(|_| 'a').collect(); 132 | assert!(matches!( 133 | $type::new(over_limit), 134 | Err(Error::RequirementError { .. }) 135 | )); 136 | } 137 | 138 | #[test] 139 | fn serde_ok_empty() { 140 | let encoded = make_encoded_str(""); 141 | let decoded = make_decoded_str(""); 142 | assert_eq!(deserialize::<$type>(&encoded).unwrap(), decoded); 143 | assert_eq!(serialize(&decoded).unwrap(), encoded); 144 | } 145 | 146 | #[test] 147 | fn serde_ok_nonempty() { 148 | let encoded = make_encoded_str("valid data"); 149 | let decoded = make_decoded_str("valid data"); 150 | assert_eq!(deserialize::<$type>(&encoded).unwrap(), decoded); 151 | assert_eq!(serialize(&decoded).unwrap(), encoded); 152 | } 153 | 154 | #[test] 155 | fn deserialize_err() { 156 | // No data to deserialize. 157 | assert!(matches!( 158 | deserialize::<$type>(&[]), 159 | Err(Error::ParseError { .. }) 160 | )); 161 | // No data after promised length. 162 | assert!(matches!( 163 | deserialize::<$type>(&[1u8]), 164 | Err(Error::ParseError { .. }) 165 | )); 166 | // Insufficient data after promised length. 167 | assert!(matches!( 168 | deserialize::<$type>(&[2u8, 42u8]), 169 | Err(Error::ParseError { .. }) 170 | )); 171 | // Non-Utf8 data. 172 | let data: [u8; 7] = [0x06, 0xed, 0xa0, 0x80, 0xed, 0xb0, 0x80]; 173 | assert!(matches!( 174 | deserialize::<$type>(&data), 175 | Err(Error::FromUtf8Error { .. }) 176 | )); 177 | } 178 | }; 179 | } 180 | 181 | // TODO(chpatton013): The mention of STR0_32 is probably an error in the specification. Anticipating 182 | // rename to STR0_31. 183 | impl_sized_STR0!(STR0_32, 32); 184 | impl_sized_STR0!(STR0_255, 255); 185 | 186 | #[cfg(test)] 187 | mod str0_32_tests { 188 | use super::*; 189 | use crate::codec::{deserialize, serialize}; 190 | 191 | impl_sized_STR0_tests!(STR0_32, 32); 192 | 193 | #[test] 194 | fn deserialize_over_max_length() { 195 | // The type used to encode the length of this payload has the necessary domain to describe 196 | // payloads much longer than the supposed maximum. 197 | let data = make_encoded_str((0..33).map(|_| 'a').collect::().as_str()); 198 | assert!(matches!( 199 | deserialize::(data.as_slice()), 200 | Err(Error::RequirementError { .. }) 201 | )); 202 | } 203 | } 204 | 205 | #[cfg(test)] 206 | mod str0_255_tests { 207 | use super::*; 208 | use crate::codec::{deserialize, serialize}; 209 | 210 | impl_sized_STR0_tests!(STR0_255, 255); 211 | } 212 | -------------------------------------------------------------------------------- /stratumv2/src/macro_message/message.rs: -------------------------------------------------------------------------------- 1 | pub mod macro_prelude { 2 | pub use crate::codec::{ByteParser, Deserializable, Frameable, Serializable}; 3 | pub use crate::error::Result; 4 | pub use crate::types::MessageType; 5 | pub use std::io; 6 | } 7 | 8 | /// Internal macro to build all the common requirements for a Stratum-v2 message. 9 | #[doc(hidden)] 10 | #[macro_export] 11 | macro_rules! impl_message { 12 | ( 13 | $(#[$doc_comment:meta])* 14 | $struct_name:ident, 15 | $($(#[$field_comment:meta])* 16 | $field: ident $field_type:ident),* 17 | ) => { 18 | #[allow(unused_imports)] 19 | use crate::macro_message::message::macro_prelude::*; 20 | 21 | $(#[$doc_comment])* 22 | #[derive(Debug, Clone, PartialEq)] 23 | pub struct $struct_name { 24 | $( 25 | $(#[$field_comment])* 26 | pub $field: $field_type 27 | ),* 28 | } 29 | 30 | impl Serializable for $struct_name { 31 | fn serialize(&self, writer: &mut W) -> Result { 32 | Ok([ 33 | $(self.$field.serialize(writer)?,)* 34 | ] 35 | .iter() 36 | .sum()) 37 | } 38 | } 39 | 40 | impl Deserializable for $struct_name { 41 | fn deserialize(parser: &mut ByteParser) -> Result { 42 | $struct_name::new( 43 | $($field_type::deserialize(parser)?,)* 44 | ) 45 | } 46 | } 47 | 48 | impl Frameable for $struct_name { 49 | fn message_type() -> MessageType { 50 | MessageType::$struct_name 51 | } 52 | } 53 | }; 54 | } 55 | 56 | #[cfg(test)] 57 | pub mod test_macro_prelude { 58 | pub use crate::codec::{deserialize, frame, serialize, unframe, Frameable, Message}; 59 | } 60 | 61 | #[cfg(test)] 62 | #[doc(hidden)] 63 | #[macro_export] 64 | macro_rules! impl_message_tests { 65 | ($struct_name:ident, $make_serialized:ident, $make_deserialized:ident) => { 66 | #[allow(unused_imports)] 67 | use crate::macro_message::message::test_macro_prelude::*; 68 | 69 | #[test] 70 | fn message_serde_identity() { 71 | // Verify that "good" messages and message payloads (as defined by the 72 | // $make_deserialized and $make_serialized marco expression arguments) ser/de as an 73 | // identity function. This provides us with confidence that all fields in the message 74 | // encode correctly, and in the correct order. 75 | let deserialized = $make_deserialized(); 76 | let serialized = $make_serialized(); 77 | assert_eq!(serialize(&deserialized).unwrap(), serialized); 78 | assert_eq!( 79 | deserialize::<$struct_name>(serialized.as_slice()).unwrap(), 80 | deserialized 81 | ); 82 | } 83 | 84 | #[test] 85 | fn message_type_tautology() { 86 | // Verify that this message's MessageType enum variant is the same as its struct name. 87 | assert_eq!($struct_name::message_type(), MessageType::$struct_name); 88 | } 89 | 90 | #[test] 91 | fn message_frame_identity() { 92 | // Verify that "good" messages and message payloads (un)frame as an identity function. 93 | let deserialized = $make_deserialized(); 94 | let serialized = $make_serialized(); 95 | let message = Message::new(MessageType::$struct_name, serialized); 96 | assert_eq!(frame(&deserialized).unwrap(), message); 97 | assert_eq!(unframe::<$struct_name>(&message).unwrap(), deserialized); 98 | } 99 | 100 | #[test] 101 | fn message_frame_serde() { 102 | // Verify that message frames for this type of message ser/de correctly. 103 | let serialized_frame = make_serialized_frame(); 104 | let deserialized_frame = Message::new($struct_name::message_type(), $make_serialized()); 105 | 106 | assert_eq!(serialize(&deserialized_frame).unwrap(), serialized_frame); 107 | assert_eq!( 108 | deserialize::(&serialized_frame).unwrap(), 109 | deserialized_frame 110 | ); 111 | } 112 | 113 | fn make_serialized_frame() -> Vec { 114 | let serialized_message = $make_serialized(); 115 | 116 | let mut extension_type = $struct_name::message_type().ext_type(); 117 | if $struct_name::message_type().channel_bit() { 118 | extension_type |= crate::codec::CHANNEL_BIT_MASK; 119 | } 120 | let message_type = $struct_name::message_type().msg_type(); 121 | let message_length = serialized_message.len(); 122 | 123 | let mut serialized_frame = vec![]; 124 | serialized_frame.extend(extension_type.to_le_bytes().iter()); 125 | serialized_frame.extend(message_type.to_le_bytes().iter()); 126 | serialized_frame.extend(message_length.to_le_bytes()[..3].iter()); 127 | serialized_frame.extend(serialized_message); 128 | 129 | serialized_frame 130 | } 131 | }; 132 | } 133 | 134 | #[cfg(test)] 135 | mod test_message_tests { 136 | use crate::types::{B0_16M, B0_255, B0_31, B0_32, B0_64K, STR0_255, STR0_32, U24, U256}; 137 | 138 | // A test message with all the primitives that comprise the real message types. 139 | // 140 | // This lets us make the assertion that other message implementations don't need to worry about 141 | // testing that these primitives "work" in ser/de operations. 142 | // 143 | // If a specific message type uses any fields outside of the types used here, it will be 144 | // responsibility for testing the correct ser/de behavior of that field type. 145 | // 146 | // Ser/de for each of these primitive types already has extensive test coverage in their own 147 | // unit tests, so repeating that coverage here is unnecessary. 148 | impl_message!( 149 | TestMessage1, 150 | a u8, b u16, c U24, d u32, e U256, 151 | f f32, 152 | g STR0_32, h STR0_255, 153 | i B0_31, j B0_32, k B0_255, l B0_64K, m B0_16M 154 | ); 155 | 156 | impl TestMessage1 { 157 | fn new( 158 | a: u8, 159 | b: u16, 160 | c: U24, 161 | d: u32, 162 | e: U256, 163 | f: f32, 164 | g: STR0_32, 165 | h: STR0_255, 166 | i: B0_31, 167 | j: B0_32, 168 | k: B0_255, 169 | l: B0_64K, 170 | m: B0_16M, 171 | ) -> Result { 172 | Ok(TestMessage1 { 173 | a, 174 | b, 175 | c, 176 | d, 177 | e, 178 | f, 179 | g, 180 | h, 181 | i, 182 | j, 183 | k, 184 | l, 185 | m, 186 | }) 187 | } 188 | } 189 | 190 | fn make_deserialized_test_message() -> TestMessage1 { 191 | TestMessage1::new( 192 | 1u8, 193 | 2u16, 194 | U24::new(3u32).unwrap(), 195 | 4u32, 196 | U256([5u8; 32]), 197 | 6.0f32, 198 | STR0_32::new("seven").unwrap(), 199 | STR0_255::new("eight").unwrap(), 200 | B0_31::new([9u8; 4]).unwrap(), 201 | B0_32::new([10u8; 4]).unwrap(), 202 | B0_255::new([11u8; 4]).unwrap(), 203 | B0_64K::new([12u8; 4]).unwrap(), 204 | B0_16M::new([13u8; 4]).unwrap(), 205 | ) 206 | .unwrap() 207 | } 208 | 209 | fn make_serialized_test_message() -> Vec { 210 | return vec![ 211 | 0x01, // a 212 | 0x02, 0x00, // b 213 | 0x03, 0x00, 0x00, // c 214 | 0x04, 0x00, 0x00, 0x00, // d 215 | 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 216 | 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 217 | 0x05, 0x05, 0x05, 0x05, // e 218 | 0x00, 0x00, 0xc0, 0x40, // f 219 | 0x05, 0x73, 0x65, 0x76, 0x65, 0x6e, // g 220 | 0x05, 0x65, 0x69, 0x67, 0x68, 0x74, // h 221 | 0x04, 0x09, 0x09, 0x09, 0x09, // i 222 | 0x04, 0x0a, 0x0a, 0x0a, 0x0a, // j 223 | 0x04, 0x0b, 0x0b, 0x0b, 0x0b, // k 224 | 0x04, 0x00, 0x0c, 0x0c, 0x0c, 0x0c, // l 225 | 0x04, 0x00, 0x00, 0x0d, 0x0d, 0x0d, 0x0d, // m 226 | ]; 227 | } 228 | 229 | impl_message_tests!( 230 | TestMessage1, 231 | make_serialized_test_message, 232 | make_deserialized_test_message 233 | ); 234 | } 235 | -------------------------------------------------------------------------------- /stratumv2/nx-noise/src/noisesession.rs: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------- * 2 | * PROCESSES * 3 | * ---------------------------------------------------------------- */ 4 | 5 | use crate::{ 6 | consts::{HASHLEN, MAC_LENGTH, MAX_MESSAGE}, 7 | error::NoiseError, 8 | state::{CipherState, HandshakeState}, 9 | types::{Hash, Keypair, Psk, PublicKey}, 10 | }; 11 | /// A `NoiseSession` object is used to keep track of the states of both local 12 | /// and remote parties before, during, and after a handshake. 13 | /// 14 | /// It contains: 15 | /// - `hs`: Keeps track of the local party's state while a handshake is being 16 | /// performed. 17 | /// 18 | /// - `h`: Stores the handshake hash output after a successful handshake in a 19 | /// Hash object. Is initialized as array of 0 bytes. 20 | /// 21 | /// - `cs1`: Keeps track of the local party's post-handshake state. Contains a 22 | /// cryptographic key and a nonce. 23 | /// 24 | /// - `cs2`: Keeps track of the remote party's post-handshake state. Contains a 25 | /// cryptographic key and a nonce. 26 | /// 27 | /// - `mc`: Keeps track of the total number of incoming and outgoing messages, 28 | /// including those sent during a handshake. 29 | /// 30 | /// - `i`: `bool` value that indicates whether this session corresponds to the 31 | /// local or remote party. 32 | /// 33 | /// - `is_transport`: `bool` value that indicates whether a handshake has been 34 | /// performed succesfully with a remote session and the session is in transport mode. 35 | 36 | pub struct NoiseSession { 37 | hs: HandshakeState, 38 | h: Hash, 39 | cs1: CipherState, 40 | cs2: CipherState, 41 | mc: u128, 42 | i: bool, 43 | is_transport: bool, 44 | } 45 | impl NoiseSession { 46 | /// Returns `true` if a handshake has been successfully performed and the session is in transport mode, or false otherwise. 47 | pub fn is_transport(&self) -> bool { 48 | self.is_transport 49 | } 50 | 51 | /// Clears `cs1`. 52 | pub fn clear_local_cipherstate(&mut self) { 53 | self.cs1.clear(); 54 | } 55 | 56 | /// Clears `cs2`. 57 | pub fn clear_remote_cipherstate(&mut self) { 58 | self.cs2.clear(); 59 | } 60 | 61 | /// Calls the [Rekey](https://noiseprotocol.org/noise.html#rekey) method for `cs1` 62 | pub fn rekey_local_cipherstate(&mut self) { 63 | self.cs1.rekey() 64 | } 65 | 66 | /// Calls the [Rekey](https://noiseprotocol.org/noise.html#rekey) method for `cs2` 67 | pub fn rekey_remote_cipherstate(&mut self) { 68 | self.cs1.rekey() 69 | } 70 | 71 | /// `NoiseSession` destructor function. 72 | pub fn end_session(mut self) { 73 | self.hs.clear(); 74 | self.clear_local_cipherstate(); 75 | self.clear_remote_cipherstate(); 76 | self.cs2.clear(); 77 | self.mc = 0; 78 | self.h = Hash::new(); 79 | } 80 | 81 | /// Returns `h`. 82 | pub fn get_handshake_hash(&self) -> Option<[u8; HASHLEN]> { 83 | if self.is_transport { 84 | return Some(self.h.as_bytes()); 85 | } 86 | None 87 | } 88 | 89 | /// Returns `mc`. 90 | pub fn get_message_count(&self) -> u128 { 91 | self.mc 92 | } 93 | 94 | /// Sets the value of the local ephemeral keypair as the parameter `e`. 95 | pub fn set_ephemeral_keypair(&mut self, e: Keypair) { 96 | self.hs.set_ephemeral_keypair(e); 97 | } 98 | 99 | /// Returns a `Option` object that contains the remote party's static `PublicKey`. 100 | /// Note that this function returns `None` before a handshake is successfuly performed and 101 | /// the session is in transport mode. 102 | pub fn get_remote_static_public_key(&self) -> Option { 103 | if self.is_transport { 104 | return Some(self.hs.get_remote_static_public_key()); 105 | } 106 | None 107 | } 108 | 109 | /// Instantiates a `NoiseSession` object. Takes the following as parameters: 110 | /// - `initiator`: `bool` variable. To be set as `true` when initiating a handshake with a remote party, or `false` otherwise. 111 | /// - `prologue`: `Message` object. Could optionally contain the name of the protocol to be used. 112 | /// - `s`: `Keypair` object. Contains local party's static keypair. 113 | 114 | pub fn init_session(initiator: bool, prologue: &[u8], s: Keypair) -> NoiseSession { 115 | if initiator { 116 | NoiseSession { 117 | hs: HandshakeState::initialize_initiator(prologue, s, Psk::default()), 118 | mc: 0, 119 | i: initiator, 120 | cs1: CipherState::new(), 121 | cs2: CipherState::new(), 122 | h: Hash::new(), 123 | is_transport: false, 124 | } 125 | } else { 126 | NoiseSession { 127 | hs: HandshakeState::initialize_responder(prologue, s, Psk::default()), 128 | mc: 0, 129 | i: initiator, 130 | cs1: CipherState::new(), 131 | cs2: CipherState::new(), 132 | h: Hash::new(), 133 | is_transport: false, 134 | } 135 | } 136 | } 137 | 138 | /// Takes a `&mut [u8]` containing plaintext as a parameter. 139 | /// This method returns a `Ok(()))` upon successful encryption, and `Err(NoiseError)` otherwise 140 | /// _Note that for security reasons and for better performance, `send_message` overwrites the bytes containing the plaintext with the ciphertext. For this reason and to account for the fact that ciphertext and handshake messages encapsulate important values, a pattern specific padding of zero bytes must be added to the following messages. 141 | /// For transport messages: 142 | /// All messages must be appended with 16 empty bytes that act as a placeholder for the MAC (Message Authentication Code). These 16 bytes will be overwritten by `send_message` 143 | /// For handshake messages: 144 | /// Kindly use the message lengths listed in the test file under `../tests/handshake.rs`, where examples and notes are also provided. 145 | /// _Also Note that while `is_transport` is false the ciphertext will be included as a payload for handshake messages and thus will not offer the same guarantees offered by post-handshake messages._ 146 | pub fn send_message(&mut self, in_out: &mut [u8]) -> Result<(), NoiseError> { 147 | if in_out.len() < MAC_LENGTH || in_out.len() > MAX_MESSAGE { 148 | return Err(NoiseError::UnsupportedMessageLengthError); 149 | } 150 | if self.mc == 0 { 151 | self.hs.write_message_a(in_out)?; 152 | } else if self.mc == 1 { 153 | let temp = self.hs.write_message_b(in_out)?; 154 | self.h = temp.0; 155 | self.is_transport = true; 156 | self.cs1 = temp.1; 157 | self.cs2 = temp.2; 158 | self.hs.clear(); 159 | } else if self.i { 160 | self.cs1.write_message_regular(in_out)?; 161 | } else { 162 | self.cs2.write_message_regular(in_out)?; 163 | } 164 | self.mc += 1; 165 | Ok(()) 166 | } 167 | 168 | /// Takes a `&mut [u8]` received from the remote party as a parameter. 169 | /// 170 | /// This method returns a `Ok(()))` upon successful decrytion. and `Err(NoiseError)` otherwise. 171 | /// 172 | /// _Note that for security reasons and for better performance, `recv_message` overwrites the bytes containing the ciphertext with the plaintext and clears the MAC from them last 16 bytes of the message, and other keys that might be encapsulated while performing a handshake. 173 | /// 174 | /// For transport messages: 175 | /// You should expect to find the plaintext in the same array you passed a reference of as a parameter. The last 16 bytes of this array will be zero bytes and can be safely ignored. 176 | /// For handshake messages: 177 | /// Kindly use the message lengths listed in the test file under `../tests/handshake.rs`, where examples and notes are also provided. 178 | /// 179 | /// _Note that while `is_transport` is false the ciphertext will be included as a payload for handshake messages and thus will not offer the same guarantees offered by post-handshake messages._ 180 | pub fn recv_message(&mut self, in_out: &mut [u8]) -> Result<(), NoiseError> { 181 | if in_out.len() < MAC_LENGTH || in_out.len() > MAX_MESSAGE { 182 | return Err(NoiseError::UnsupportedMessageLengthError); 183 | } 184 | 185 | if self.mc == 0 { 186 | self.hs.read_message_a(in_out)?; 187 | } else if self.mc == 1 { 188 | let temp = self.hs.read_message_b(in_out)?; 189 | self.h = temp.0; 190 | self.is_transport = true; 191 | self.cs1 = temp.1; 192 | self.cs2 = temp.2; 193 | self.hs.clear(); 194 | } else if self.i { 195 | self.cs2.read_message_regular(in_out)?; 196 | } else { 197 | self.cs1.read_message_regular(in_out)?; 198 | } 199 | 200 | self.mc += 1; 201 | Ok(()) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /stratumv2/src/types/bytes.rs: -------------------------------------------------------------------------------- 1 | use crate::codec::{ByteParser, Deserializable, Serializable}; 2 | use crate::error::{Error, Result}; 3 | use crate::types::U24; 4 | use std::io; 5 | 6 | /// An internal macro that implements a B0 type that is restricted according to a MAX_LENGTH. 7 | macro_rules! impl_sized_B0 { 8 | ($type:ident, $length_type:ident) => { 9 | impl_sized_B0!($type, $length_type, $length_type::MAX as usize); 10 | }; 11 | ($type:ident, $length_type:ident, $max_length:expr) => { 12 | #[derive(Debug, Clone, PartialEq)] 13 | pub struct $type { 14 | pub length: $length_type, 15 | pub data: Vec, 16 | } 17 | 18 | impl $type { 19 | const MAX_LENGTH: usize = $max_length; 20 | 21 | /// The constructor enforces the Vec input size as the MAX_LENGTH. A 22 | /// RequirementError will be returned if the input byte size is greater than the 23 | /// MAX_LENGTH. 24 | pub fn new>>(value: T) -> Result<$type> { 25 | let value = value.into(); 26 | if value.len() > Self::MAX_LENGTH { 27 | return Err(Error::RequirementError( 28 | "bytes size cannot be greater than MAX_LENGTH".into(), 29 | )); 30 | } 31 | 32 | use std::convert::TryInto; 33 | Ok($type { 34 | length: value.len().try_into().unwrap(), 35 | data: value, 36 | }) 37 | } 38 | } 39 | 40 | /// PartialEq implementation allowing direct comparison between the B0 type and Vec. 41 | impl PartialEq> for $type { 42 | fn eq(&self, other: &Vec) -> bool { 43 | self.data == *other 44 | } 45 | } 46 | 47 | /// PartialEq implementation allowing direct comparison between Vec and the B0 type. 48 | impl PartialEq<$type> for Vec { 49 | fn eq(&self, other: &$type) -> bool { 50 | *self == other.data 51 | } 52 | } 53 | 54 | /// From trait implementation that allows a B0 to be converted into a Vec. 55 | impl From<$type> for Vec { 56 | fn from(b: $type) -> Self { 57 | b.data 58 | } 59 | } 60 | 61 | /// Deserialize trait implementation that allows a B0 to be deserialized from a ByteParser. 62 | impl Deserializable for $type { 63 | fn deserialize(parser: &mut ByteParser) -> Result<$type> { 64 | // Parse the length header before the buffer. 65 | let header_length = $length_type::deserialize(parser)?; 66 | // Then parse the byte buffer. 67 | let bytes = parser.next_by(header_length.clone().into())?; 68 | 69 | $type::new(bytes) 70 | } 71 | } 72 | 73 | /// Serialize trait implementation that allows a B0 to be serialized into an io::Writer. 74 | impl Serializable for $type { 75 | fn serialize(&self, writer: &mut W) -> Result { 76 | // Write the length header. 77 | let header_length = self.length.serialize(writer)?; 78 | // Then write the byte buffer. 79 | writer.write(self.data.as_slice())?; 80 | 81 | Ok(header_length + self.data.len()) 82 | } 83 | } 84 | }; 85 | } 86 | 87 | #[cfg(test)] 88 | macro_rules! impl_sized_B0_tests { 89 | ($type:ident, $length_type:ident) => { 90 | impl_sized_B0_tests!($type, $length_type, $length_type::MAX as usize); 91 | }; 92 | ($type:ident, $length_type:ident, $max_length:expr) => { 93 | fn make_encoded_bytes(payload: &[u8]) -> Vec { 94 | use std::convert::TryInto; 95 | 96 | let length = payload.len().try_into().unwrap(); 97 | let header = serialize::<$length_type>(&length).unwrap(); 98 | 99 | let mut buffer = vec![]; 100 | buffer.extend_from_slice(header.as_slice()); 101 | buffer.extend_from_slice(payload); 102 | return buffer; 103 | } 104 | 105 | fn make_decoded_bytes(s: &[u8]) -> $type { 106 | use std::convert::TryInto; 107 | $type { 108 | length: s.len().try_into().unwrap(), 109 | data: s.into(), 110 | } 111 | } 112 | 113 | #[test] 114 | fn new_empty() { 115 | use std::convert::TryInto; 116 | let data = vec![]; 117 | let length: $length_type = 0usize.try_into().unwrap(); 118 | assert_eq!( 119 | $type::new(data.clone()).unwrap(), 120 | $type { 121 | length: length, 122 | data: data 123 | } 124 | ); 125 | } 126 | 127 | #[test] 128 | fn new_nonempty() { 129 | use std::convert::TryInto; 130 | let data = vec![1, 2, 3, 4, 5]; 131 | let length: $length_type = 5usize.try_into().unwrap(); 132 | assert_eq!( 133 | $type::new(data.clone()).unwrap(), 134 | $type { 135 | length: length, 136 | data: data 137 | } 138 | ); 139 | } 140 | 141 | #[test] 142 | fn new_max_length() { 143 | // There's no need to test the larger variants of this macro. If it works for the 144 | // smaller sizes it will work for the larger ones as well. 145 | if $max_length < 100000 { 146 | use std::convert::TryInto; 147 | let data: Vec = [0; $max_length].into(); 148 | let length: $length_type = $max_length.try_into().unwrap(); 149 | assert_eq!( 150 | $type::new(data.clone()).unwrap(), 151 | $type { 152 | length: length, 153 | data: data 154 | } 155 | ); 156 | } 157 | } 158 | 159 | #[test] 160 | fn new_over_limit() { 161 | // There's no need to test the larger variants of this macro. If it works for the 162 | // smaller sizes it will work for the larger ones as well. 163 | if $max_length < 100000 { 164 | let data: Vec = [0; $max_length + 1].into(); 165 | assert!(matches!( 166 | $type::new(data), 167 | Err(Error::RequirementError { .. }) 168 | )); 169 | } 170 | } 171 | 172 | #[test] 173 | fn serde_ok_empty() { 174 | let encoded = make_encoded_bytes(&[]); 175 | let decoded = make_decoded_bytes(&[]); 176 | assert_eq!(deserialize::<$type>(&encoded).unwrap(), decoded); 177 | assert_eq!(serialize(&decoded).unwrap(), encoded); 178 | } 179 | 180 | #[test] 181 | fn serde_ok_nonempty() { 182 | let encoded = make_encoded_bytes(&[1, 2, 3, 4, 5]); 183 | let decoded = make_decoded_bytes(&[1, 2, 3, 4, 5]); 184 | assert_eq!(deserialize::<$type>(&encoded).unwrap(), decoded); 185 | assert_eq!(serialize(&decoded).unwrap(), encoded); 186 | } 187 | 188 | #[test] 189 | fn deserialize_err() { 190 | // No data to deserialize. 191 | assert!(matches!( 192 | deserialize::<$type>(&[]), 193 | Err(Error::ParseError { .. }) 194 | )); 195 | // No data after promised length. 196 | assert!(matches!( 197 | deserialize::<$type>(&[1u8]), 198 | Err(Error::ParseError { .. }) 199 | )); 200 | // Insufficient data after promised length. 201 | assert!(matches!( 202 | deserialize::<$type>(&[2u8, 42u8]), 203 | Err(Error::ParseError { .. }) 204 | )); 205 | } 206 | }; 207 | } 208 | 209 | // TODO(chpatton013): The mention of both B0_31 and B0_32 is probably an error in the 210 | // specification. One of those (anticipating B0_32) will most-likely be removed. 211 | impl_sized_B0!(B0_31, u8, 31); 212 | impl_sized_B0!(B0_32, u8, 32); 213 | impl_sized_B0!(B0_255, u8); 214 | impl_sized_B0!(B0_64K, u16); 215 | impl_sized_B0!(B0_16M, U24); 216 | 217 | #[cfg(test)] 218 | mod b0_31_tests { 219 | use super::*; 220 | use crate::codec::{deserialize, serialize}; 221 | 222 | impl_sized_B0_tests!(B0_31, u8, 31); 223 | 224 | #[test] 225 | fn deserialize_over_max_length() { 226 | // The type used to encode the length of this payload has the necessary domain to describe 227 | // payloads much longer than the supposed maximum. 228 | let data = make_encoded_bytes(&[0; 32]); 229 | assert!(matches!( 230 | deserialize::(data.as_slice()), 231 | Err(Error::RequirementError { .. }) 232 | )); 233 | } 234 | } 235 | 236 | #[cfg(test)] 237 | mod b0_32_tests { 238 | use super::*; 239 | use crate::codec::{deserialize, serialize}; 240 | 241 | impl_sized_B0_tests!(B0_32, u8, 32); 242 | 243 | #[test] 244 | fn deserialize_over_max_length() { 245 | // The type used to encode the length of this payload has the necessary domain to describe 246 | // payloads much longer than the supposed maximum. 247 | let data = make_encoded_bytes(&[0; 33]); 248 | assert!(matches!( 249 | deserialize::(data.as_slice()), 250 | Err(Error::RequirementError { .. }) 251 | )); 252 | } 253 | } 254 | 255 | #[cfg(test)] 256 | mod b0_255_tests { 257 | use super::*; 258 | use crate::codec::{deserialize, serialize}; 259 | 260 | impl_sized_B0_tests!(B0_255, u8); 261 | } 262 | 263 | #[cfg(test)] 264 | mod b0_64_k_tests { 265 | use super::*; 266 | use crate::codec::{deserialize, serialize}; 267 | 268 | impl_sized_B0_tests!(B0_64K, u16); 269 | } 270 | 271 | #[cfg(test)] 272 | mod b0_16_m_tests { 273 | use super::*; 274 | use crate::codec::{deserialize, serialize}; 275 | 276 | impl_sized_B0_tests!(B0_16M, U24); 277 | } 278 | -------------------------------------------------------------------------------- /stratumv2/src/macro_message/setup_connection.rs: -------------------------------------------------------------------------------- 1 | pub mod macro_prelude { 2 | pub use crate::codec::{ByteParser, Deserializable, Serializable}; 3 | pub use crate::error::{Error, Result}; 4 | pub use crate::impl_message; 5 | pub use crate::types::{MessageType, STR0_255}; 6 | pub use std::convert::TryInto; 7 | pub use std::io; 8 | } 9 | 10 | /// Implemention of the requirements for a SetupConnection message for each 11 | /// sub protocol. 12 | #[doc(hidden)] 13 | #[macro_export] 14 | macro_rules! impl_setup_connection { 15 | ($flags_type:ident) => { 16 | use crate::macro_message::setup_connection::macro_prelude::*; 17 | 18 | impl_message!( 19 | /// It's strongly recommended to use the [SetupConnection Enum](../common/setup_connection/enum.SetupConnection.html) 20 | /// and NOT this struct to initialize, serialize, deserialize and frame each SetupConnection 21 | /// message. Use this struct to extract the inner values of the message. 22 | /// 23 | /// SetupConnection is the first message sent by a client on a new connection. 24 | /// 25 | /// The SetupConnection struct contains all the common fields for the 26 | /// SetupConnection message required for each Stratum V2 subprotocol. 27 | /// 28 | /// # Examples 29 | /// 30 | /// ```rust 31 | /// use stratumv2::mining; 32 | /// use stratumv2::job_negotiation; 33 | /// use stratumv2::common::SetupConnection; 34 | /// 35 | /// let mining_conn = SetupConnection::new_mining( 36 | /// 2, 37 | /// 2, 38 | /// mining::SetupConnectionFlags::REQUIRES_STANDARD_JOBS | mining::SetupConnectionFlags::REQUIRES_VERSION_ROLLING, 39 | /// "0.0.0.0", 40 | /// 8545, 41 | /// "Bitmain", 42 | /// "S9i 13.5", 43 | /// "braiins-os-2018-09-22-1-hash", 44 | /// "some-device-uuid", 45 | /// ); 46 | /// assert!(mining_conn.is_ok()); 47 | /// 48 | /// let job_negotiation_conn = SetupConnection::new_job_negotation( 49 | /// 2, 50 | /// 2, 51 | /// job_negotiation::SetupConnectionFlags::REQUIRES_ASYNC_JOB_MINING, 52 | /// "0.0.0.0", 53 | /// 8545, 54 | /// "Bitmain", 55 | /// "S9i 13.5", 56 | /// "braiins-os-2018-09-22-1-hash", 57 | /// "some-device-uuid", 58 | /// ); 59 | /// assert!(job_negotiation_conn.is_ok()); 60 | /// 61 | /// if let SetupConnection::Mining(v) = mining_conn.unwrap() { 62 | /// assert_eq!(v.min_version, 2); 63 | /// } 64 | /// 65 | /// if let SetupConnection::JobNegotiation(v) = job_negotiation_conn.unwrap() { 66 | /// assert_eq!(v.min_version, 2); 67 | /// } 68 | /// 69 | /// ``` 70 | SetupConnection, 71 | 72 | /// The minimum protocol version the client supports. (current default: 2) 73 | min_version u16, 74 | 75 | /// The maxmimum protocol version the client supports. (current default: 2) 76 | max_version u16, 77 | 78 | /// Flags indicating the optional protocol features the client supports. 79 | flags $flags_type, 80 | 81 | /// Used to indicate the hostname or IP address of the endpoint. 82 | endpoint_host STR0_255, 83 | 84 | /// Used to indicate the connecting port value of the endpoint. 85 | endpoint_port u16, 86 | 87 | /// The following fields relay the new_mining device information. 88 | /// 89 | /// Used to indicate the vendor/manufacturer of the device. 90 | vendor STR0_255, 91 | 92 | /// Used to indicate the hardware version of the device. 93 | hardware_version STR0_255, 94 | 95 | /// Used to indicate the firmware on the device. 96 | firmware STR0_255, 97 | 98 | /// Used to indicate the unique identifier of the device defined by the 99 | /// vendor. 100 | device_id STR0_255 101 | ); 102 | 103 | impl SetupConnection { 104 | pub fn new>( 105 | min_version: u16, 106 | max_version: u16, 107 | flags: $flags_type, 108 | endpoint_host: T, 109 | endpoint_port: u16, 110 | vendor: T, 111 | hardware_version: T, 112 | firmware: T, 113 | device_id: T, 114 | ) -> Result { 115 | let vendor = vendor.into(); 116 | if *&vendor.is_empty() { 117 | return Err(Error::RequirementError( 118 | "vendor field in SetupConnection MUST NOT be empty".into(), 119 | )); 120 | } 121 | 122 | let firmware = firmware.into(); 123 | if *&firmware.is_empty() { 124 | return Err(Error::RequirementError( 125 | "firmware field in SetupConnection MUST NOT be empty".into(), 126 | )); 127 | } 128 | 129 | if min_version < 2 { 130 | return Err(Error::VersionError("min_version must be atleast 2".into())); 131 | } 132 | 133 | if max_version < 2 { 134 | return Err(Error::VersionError("max_version must be atleast 2".into())); 135 | } 136 | 137 | Ok(SetupConnection { 138 | min_version, 139 | max_version, 140 | flags, 141 | endpoint_host: STR0_255::new(endpoint_host)?, 142 | endpoint_port, 143 | vendor: STR0_255::new(vendor)?, 144 | hardware_version: STR0_255::new(hardware_version)?, 145 | firmware: STR0_255::new(firmware)?, 146 | device_id: STR0_255::new(device_id)?, 147 | }) 148 | } 149 | } 150 | }; 151 | } 152 | 153 | #[cfg(test)] 154 | pub mod test_macro_prelude { 155 | pub use crate::impl_message_tests; 156 | } 157 | 158 | #[cfg(test)] 159 | #[doc(hidden)] 160 | #[macro_export] 161 | macro_rules! impl_setup_connection_tests { 162 | ($flags_type:ident) => { 163 | use crate::macro_message::setup_connection::test_macro_prelude::*; 164 | 165 | fn make_deserialized_setup_connection() -> SetupConnection { 166 | SetupConnection::new( 167 | 2, 168 | 2, 169 | $flags_type::all(), 170 | "0.0.0.0", 171 | 8545, 172 | "Bitmain", 173 | "S9i 13.5", 174 | "braiins-os-2018-09-22-1-hash", 175 | "some-device-uuid", 176 | ) 177 | .unwrap() 178 | } 179 | 180 | fn make_serialized_setup_connection() -> Vec { 181 | let mut serialized = vec![ 182 | 0x02, 0x00, // min_version 183 | 0x02, 0x00, // max_version 184 | ]; 185 | serialized.extend($flags_type::all().bits().to_le_bytes().iter()); // flags 186 | serialized.extend(vec![ 187 | 0x07, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x30, // endpoint_address 188 | 0x61, 0x21, // endpoint_port 189 | 0x07, 0x42, 0x69, 0x74, 0x6d, 0x61, 0x69, 0x6e, // vendor 190 | 0x08, 0x53, 0x39, 0x69, 0x20, 0x31, 0x33, 0x2e, 0x35, // hardware_version 191 | 0x1c, 0x62, 0x72, 0x61, 0x69, 0x69, 0x6e, 0x73, 0x2d, 0x6f, 0x73, 0x2d, 0x32, 0x30, 192 | 0x31, 0x38, 0x2d, 0x30, 0x39, 0x2d, 0x32, 0x32, 0x2d, 0x31, 0x2d, 0x68, 0x61, 0x73, 193 | 0x68, //firmware 194 | 0x10, 0x73, 0x6f, 0x6d, 0x65, 0x2d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x75, 195 | 0x75, 0x69, 0x64, // device_id 196 | ]); 197 | 198 | serialized 199 | } 200 | 201 | impl_message_tests!( 202 | SetupConnection, 203 | make_serialized_setup_connection, 204 | make_deserialized_setup_connection 205 | ); 206 | 207 | #[test] 208 | fn empty_vendor() { 209 | assert!(matches!( 210 | SetupConnection::new( 211 | 2, 212 | 2, 213 | $flags_type::all(), 214 | "0.0.0.0", 215 | 8545, 216 | "", 217 | "S9i 13.5", 218 | "braiins-os-2018-09-22-1-hash", 219 | "some-device-uuid", 220 | ), 221 | Err(Error::RequirementError { .. }) 222 | )); 223 | } 224 | 225 | #[test] 226 | fn empty_firmware() { 227 | assert!(matches!( 228 | SetupConnection::new( 229 | 2, 230 | 2, 231 | $flags_type::all(), 232 | "0.0.0.0", 233 | 8545, 234 | "Bitmain", 235 | "S9i 13.5", 236 | "", 237 | "some-device-uuid", 238 | ), 239 | Err(Error::RequirementError { .. }) 240 | )); 241 | } 242 | 243 | #[test] 244 | fn bad_min_version() { 245 | assert!(matches!( 246 | SetupConnection::new( 247 | 1, 248 | 2, 249 | $flags_type::all(), 250 | "0.0.0.0", 251 | 8545, 252 | "Bitmain", 253 | "S9i 13.5", 254 | "braiins-os-2018-09-22-1-hash", 255 | "some-device-uuid", 256 | ), 257 | Err(Error::VersionError { .. }) 258 | )); 259 | } 260 | 261 | #[test] 262 | fn bad_max_version() { 263 | assert!(matches!( 264 | SetupConnection::new( 265 | 2, 266 | 1, 267 | $flags_type::all(), 268 | "0.0.0.0", 269 | 8545, 270 | "Bitmain", 271 | "S9i 13.5", 272 | "braiins-os-2018-09-22-1-hash", 273 | "some-device-uuid", 274 | ), 275 | Err(Error::VersionError { .. }) 276 | )); 277 | } 278 | }; 279 | } 280 | -------------------------------------------------------------------------------- /stratumv2/src/common/setup_connection.rs: -------------------------------------------------------------------------------- 1 | use crate::codec::{ByteParser, Deserializable, Frameable, Serializable}; 2 | use crate::error::{Error, Result}; 3 | use crate::job_negotiation; 4 | use crate::mining; 5 | use crate::types::MessageType; 6 | // use crate::template_distribution; 7 | // use crate::job_distribution; 8 | use std::convert::TryFrom; 9 | use std::io; 10 | 11 | /// Protocol is an enum representing each sub protocol of Stratum V2. 12 | #[derive(Debug, PartialEq, Clone, Copy)] 13 | pub enum Protocol { 14 | /// Mining is the main and only required sub protocol in Stratum V2. 15 | Mining, 16 | 17 | /// JobNegotiation is a protocol for intermediate nodes to broker 18 | /// the terms of a connection between downstream nodes and upstream nodes. 19 | JobNegotiation, 20 | 21 | /// TemplateDistribution is a protocol for getting the next block from the 22 | /// Bitcoin RPC. This protocol is intented to replace getblocktemplate. 23 | TemplateDistribution, 24 | 25 | /// JobDistribution is a protocol for passing newly-negotiated work from the 26 | /// Job Negotiator to proxies or mining devices. If miners aren't choosing 27 | /// their transaction sets, then jobs will be distributed from pools directly 28 | /// to proxies/mining devices. 29 | JobDistribution, 30 | } 31 | 32 | impl From<&Protocol> for u8 { 33 | fn from(protocol: &Protocol) -> Self { 34 | match protocol { 35 | Protocol::Mining => 0, 36 | Protocol::JobNegotiation => 1, 37 | Protocol::TemplateDistribution => 2, 38 | Protocol::JobDistribution => 3, 39 | } 40 | } 41 | } 42 | 43 | impl From for u8 { 44 | fn from(protocol: Protocol) -> Self { 45 | u8::from(&protocol) 46 | } 47 | } 48 | 49 | impl TryFrom for Protocol { 50 | type Error = Error; 51 | 52 | fn try_from(byte: u8) -> Result { 53 | match byte { 54 | 0 => Ok(Protocol::Mining), 55 | 1 => Ok(Protocol::JobNegotiation), 56 | 2 => Ok(Protocol::TemplateDistribution), 57 | 3 => Ok(Protocol::JobDistribution), 58 | // TODO(chpatton013): Pick an error type that is more context-agnostic. 59 | _ => Err(Error::DeserializationError( 60 | "received unknown protocol byte in setup connection message".into(), 61 | )), 62 | } 63 | } 64 | } 65 | 66 | impl Serializable for Protocol { 67 | fn serialize(&self, writer: &mut W) -> Result { 68 | Ok(u8::from(self).serialize(writer)?) 69 | } 70 | } 71 | 72 | impl Deserializable for Protocol { 73 | fn deserialize(parser: &mut ByteParser) -> Result { 74 | Protocol::try_from(u8::deserialize(parser)?) 75 | } 76 | } 77 | 78 | /// Contains all the variants of each subprotocols SetupConnection message. 79 | /// When constructing a NetworkMessage this enum should be used to correctly 80 | /// serialize the SetupConnection specific to the subprotocol. 81 | pub enum SetupConnection { 82 | Mining(mining::SetupConnection), 83 | JobNegotiation(job_negotiation::SetupConnection), 84 | // TemplateDistribution(template_distribution::SetupConnection), 85 | // JobDistribution(job_distribution::SetupConnection), 86 | } 87 | 88 | impl SetupConnection { 89 | /// SetupConnection message for the mining subprotocol. 90 | /// 91 | /// # Examples 92 | /// 93 | /// ```rust 94 | /// use stratumv2::mining::SetupConnectionFlags; 95 | /// use stratumv2::common::SetupConnection; 96 | /// 97 | /// let new_connection = SetupConnection::new_mining( 98 | /// 2, 99 | /// 2, 100 | /// SetupConnectionFlags::REQUIRES_STANDARD_JOBS | SetupConnectionFlags::REQUIRES_VERSION_ROLLING, 101 | /// "0.0.0.0", 102 | /// 8545, 103 | /// "Bitmain", 104 | /// "S9i 13.5", 105 | /// "braiins-os-2018-09-22-1-hash", 106 | /// "some-device-uuid", 107 | /// ); 108 | /// assert!(new_connection.is_ok()); 109 | pub fn new_mining>( 110 | min_version: u16, 111 | max_version: u16, 112 | flags: mining::SetupConnectionFlags, 113 | endpoint_host: T, 114 | endpoint_port: u16, 115 | vendor: T, 116 | hardware_version: T, 117 | firmware: T, 118 | device_id: T, 119 | ) -> Result { 120 | Ok(SetupConnection::Mining(mining::SetupConnection::new( 121 | min_version, 122 | max_version, 123 | flags, 124 | endpoint_host, 125 | endpoint_port, 126 | vendor, 127 | hardware_version, 128 | firmware, 129 | device_id, 130 | )?)) 131 | } 132 | 133 | /// SetupConnection message for the job negotiation subprotocol. 134 | /// 135 | /// # Examples 136 | /// 137 | /// ```rust 138 | /// use stratumv2::job_negotiation::SetupConnectionFlags; 139 | /// use stratumv2::common::SetupConnection; 140 | /// 141 | /// let new_connection = SetupConnection::new_job_negotation( 142 | /// 2, 143 | /// 2, 144 | /// SetupConnectionFlags::REQUIRES_ASYNC_JOB_MINING, 145 | /// "0.0.0.0", 146 | /// 8545, 147 | /// "Bitmain", 148 | /// "S9i 13.5", 149 | /// "braiins-os-2018-09-22-1-hash", 150 | /// "some-device-uuid", 151 | /// ); 152 | /// assert!(new_connection.is_ok()); 153 | pub fn new_job_negotation>( 154 | min_version: u16, 155 | max_version: u16, 156 | flags: job_negotiation::SetupConnectionFlags, 157 | endpoint_host: T, 158 | endpoint_port: u16, 159 | vendor: T, 160 | hardware_version: T, 161 | firmware: T, 162 | device_id: T, 163 | ) -> Result { 164 | Ok(SetupConnection::JobNegotiation( 165 | job_negotiation::SetupConnection::new( 166 | min_version, 167 | max_version, 168 | flags, 169 | endpoint_host, 170 | endpoint_port, 171 | vendor, 172 | hardware_version, 173 | firmware, 174 | device_id, 175 | )?, 176 | )) 177 | } 178 | } 179 | 180 | impl Serializable for SetupConnection { 181 | fn serialize(&self, writer: &mut W) -> Result { 182 | let length = match self { 183 | SetupConnection::Mining(v) => { 184 | Protocol::Mining.serialize(writer)? + v.serialize(writer)? 185 | } 186 | SetupConnection::JobNegotiation(v) => { 187 | Protocol::JobNegotiation.serialize(writer)? + v.serialize(writer)? 188 | } // SetupConnection::TemplateDistribution(v) => { 189 | // Protocol::TemplateDistribution.serialize(writer)? + v.serialize(writer)? 190 | // } 191 | // SetupConnection::JobDistribution(v) => { 192 | // Protocol::JobDistribution.serialize(writer)? + v.serialize(writer)? 193 | // } 194 | }; 195 | 196 | Ok(length) 197 | } 198 | } 199 | 200 | impl Deserializable for SetupConnection { 201 | fn deserialize(parser: &mut ByteParser) -> Result { 202 | let protocol = Protocol::deserialize(parser)?; 203 | let variant = match protocol { 204 | Protocol::Mining => { 205 | SetupConnection::Mining(mining::SetupConnection::deserialize(parser)?) 206 | } 207 | Protocol::JobNegotiation => SetupConnection::JobNegotiation( 208 | job_negotiation::SetupConnection::deserialize(parser)?, 209 | ), 210 | _ => return Err(Error::Unimplemented()), 211 | // Protocol::TemplateDistribution => SetupConnection::TemplateDistribution( 212 | // template_distribution::SetupConnection::deserialize(parser)?, 213 | // ), 214 | // Protocol::JobDistribution => SetupConnection::JobDistribution( 215 | // job_distribution::SetupConnection::deserialize(parser)?, 216 | // ), 217 | }; 218 | 219 | Ok(variant) 220 | } 221 | } 222 | 223 | impl Frameable for SetupConnection { 224 | fn message_type() -> MessageType { 225 | MessageType::SetupConnection 226 | } 227 | } 228 | 229 | #[cfg(test)] 230 | macro_rules! impl_setup_connection_tests { 231 | ($protocol:expr, $fn:expr, $flags:ident) => { 232 | use crate::{codec, types::U24}; 233 | use std::collections::HashMap; 234 | 235 | fn default_setup_conn( 236 | empty: bool, 237 | args: HashMap, 238 | ) -> Result { 239 | let mut min_version = 2; 240 | let mut max_version = 2; 241 | let mut vendor = "Bitmain"; 242 | let mut firmware = "braiins-os-2018-09-22-1-hash"; 243 | 244 | if args.contains_key("min_version") { 245 | min_version = args.get("min_version").unwrap().parse::().unwrap(); 246 | } 247 | 248 | if args.contains_key("max_version") { 249 | max_version = args.get("max_version").unwrap().parse::().unwrap(); 250 | } 251 | 252 | if args.contains_key("vendor") { 253 | vendor = args.get("vendor").unwrap(); 254 | } 255 | 256 | if args.contains_key("firmware") { 257 | firmware = args.get("firmware").unwrap(); 258 | } 259 | 260 | let flags = if empty { 261 | $flags::empty() 262 | } else { 263 | $flags::all() 264 | }; 265 | 266 | $fn( 267 | min_version, 268 | max_version, 269 | flags, 270 | "0.0.0.0", 271 | 8545, 272 | vendor, 273 | "S9u 13.5", 274 | firmware, 275 | "some-uuid", 276 | ) 277 | } 278 | 279 | #[test] 280 | fn constructor_errors() { 281 | // Check that empty vendor string should return an error. 282 | let mut args = HashMap::new(); 283 | args.insert("vendor".into(), "".into()); 284 | assert!(default_setup_conn(false, args).is_err()); 285 | 286 | // Check that empty firmware string should return an error. 287 | let mut args = HashMap::new(); 288 | args.insert("firmware".into(), "".into()); 289 | assert!(default_setup_conn(false, args).is_err()); 290 | 291 | // Check that min and max versions must be atleast 2. 292 | let mut args = HashMap::new(); 293 | args.insert("min_version".into(), "1".into()); 294 | assert!(default_setup_conn(false, args).is_err()); 295 | 296 | let mut args = HashMap::new(); 297 | args.insert("max_version".into(), "1".into()); 298 | assert!(default_setup_conn(false, args).is_err()); 299 | } 300 | 301 | #[test] 302 | fn serialize() { 303 | let conn = default_setup_conn(false, HashMap::new()).unwrap(); 304 | let result = codec::serialize(&conn).unwrap(); 305 | 306 | // Check the serialized connection is the correct length. 307 | assert_eq!(result.len(), 75); 308 | 309 | // Check the protocol byte was serialized correctly. 310 | assert_eq!(result[0], $protocol.into()); 311 | 312 | // Check the flags were serialized correctly. 313 | assert_eq!(result[5..9], codec::serialize(&$flags::all()).unwrap()); 314 | 315 | // Sanity check - deserializing back to the struct does not cause 316 | // errors. 317 | assert!(codec::deserialize::(&result).is_ok()); 318 | } 319 | 320 | #[test] 321 | fn serialize_empty_flags() { 322 | let conn = default_setup_conn(true, HashMap::new()).unwrap(); 323 | let result = codec::serialize(&conn).unwrap(); 324 | 325 | // Check the optional flags still serialize but to empty values. 326 | assert_eq!(result[5..9], [0u8; 4]); 327 | } 328 | 329 | #[test] 330 | fn frame_message() { 331 | let conn = default_setup_conn(false, HashMap::new()).unwrap(); 332 | let network_message = codec::frame(&conn).unwrap(); 333 | 334 | let result = codec::serialize(&network_message).unwrap(); 335 | assert_eq!(result.len(), 81); 336 | 337 | // Check the extension type is empty. 338 | assert_eq!(result[0..2], [0u8; 2]); 339 | 340 | // Check that the correct byte for the message type was used. 341 | assert_eq!(result[2], network_message.message_type.msg_type()); 342 | 343 | // Check that the correct message length was used. 344 | assert_eq!( 345 | codec::deserialize::(&result[3..6]).unwrap(), 346 | network_message.payload.len() as u32 347 | ); 348 | } 349 | }; 350 | } 351 | 352 | #[cfg(test)] 353 | mod mining_setup_connection_tests { 354 | use super::*; 355 | use crate::mining::SetupConnectionFlags; 356 | 357 | impl_setup_connection_tests!( 358 | Protocol::Mining, 359 | SetupConnection::new_mining, 360 | SetupConnectionFlags 361 | ); 362 | } 363 | 364 | #[cfg(test)] 365 | mod job_negotiation_setup_connection_tests { 366 | use super::*; 367 | use crate::job_negotiation::SetupConnectionFlags; 368 | 369 | impl_setup_connection_tests!( 370 | Protocol::JobNegotiation, 371 | SetupConnection::new_job_negotation, 372 | SetupConnectionFlags 373 | ); 374 | } 375 | -------------------------------------------------------------------------------- /stratumv2/nx-noise/src/state.rs: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------- * 2 | * STATE MANAGEMENT * 3 | * ---------------------------------------------------------------- */ 4 | 5 | use crate::{ 6 | consts::{DHLEN, EMPTY_HASH, EMPTY_KEY, HASHLEN, MAC_LENGTH, NONCE_LENGTH, ZEROLEN}, 7 | error::NoiseError, 8 | prims::{decrypt, encrypt, hash, hash_with_context, hkdf}, 9 | types::{Hash, Key, Keypair, Nonce, Psk, PublicKey}, 10 | utils::from_slice_hashlen, 11 | }; 12 | use hacl_star::chacha20poly1305; 13 | use zeroize::Zeroize; 14 | 15 | pub(crate) struct CipherState { 16 | k: Key, 17 | n: Nonce, 18 | } 19 | 20 | impl CipherState { 21 | pub(crate) fn new() -> Self { 22 | Self::from_key(Key::new()) 23 | } 24 | 25 | pub(crate) fn clear_key(&mut self) { 26 | self.k.clear(); 27 | } 28 | 29 | pub(crate) fn clear(&mut self) { 30 | self.k.clear(); 31 | self.n = Nonce::new(); 32 | } 33 | 34 | pub(crate) fn from_key(key: Key) -> Self { 35 | let nonce: Nonce = Nonce::new(); 36 | Self { k: key, n: nonce } 37 | } 38 | 39 | pub(crate) fn has_key(&self) -> bool { 40 | !self.k.is_empty() 41 | } 42 | 43 | #[allow(dead_code)] 44 | pub(crate) fn set_nonce(&mut self, n: Nonce) { 45 | self.n = n; 46 | } 47 | 48 | #[allow(dead_code)] 49 | pub(crate) fn get_nonce(&self) -> Nonce { 50 | self.n 51 | } 52 | 53 | pub(crate) fn encrypt_with_ad( 54 | &mut self, 55 | ad: &[u8], 56 | in_out: &mut [u8], 57 | mac: &mut [u8; MAC_LENGTH], 58 | ) -> Result<(), NoiseError> { 59 | let nonce = self.n.get_value()?; 60 | if self.has_key() { 61 | encrypt( 62 | from_slice_hashlen(&self.k.as_bytes()[..]), 63 | nonce, 64 | ad, 65 | in_out, 66 | mac, 67 | ); 68 | self.n.increment(); 69 | return Ok(()); 70 | } 71 | Err(NoiseError::EmptyKeyError) 72 | } 73 | 74 | pub(crate) fn decrypt_with_ad( 75 | &mut self, 76 | ad: &[u8], 77 | in_out: &mut [u8], 78 | mac: &mut [u8; MAC_LENGTH], 79 | ) -> Result<(), NoiseError> { 80 | let nonce = self.n.get_value()?; 81 | if self.has_key() { 82 | if decrypt( 83 | from_slice_hashlen(&self.k.as_bytes()[..]), 84 | nonce, 85 | ad, 86 | in_out, 87 | mac, 88 | ) { 89 | self.n.increment(); 90 | return Ok(()); 91 | } 92 | return Err(NoiseError::DecryptionError); 93 | } 94 | Err(NoiseError::EmptyKeyError) 95 | } 96 | 97 | #[allow(dead_code)] 98 | pub(crate) fn rekey(&mut self) { 99 | let mut in_out = EMPTY_KEY; 100 | chacha20poly1305::key(&self.k.as_bytes()) 101 | .nonce(&[0xFFu8; NONCE_LENGTH]) 102 | .encrypt(&ZEROLEN[..], &mut in_out[..], &mut [0u8; 16]); 103 | self.k.clear(); 104 | self.k = Key::from_bytes(in_out); 105 | in_out.zeroize(); 106 | } 107 | 108 | pub(crate) fn write_message_regular(&mut self, in_out: &mut [u8]) -> Result<(), NoiseError> { 109 | let (in_out, mac) = in_out.split_at_mut(in_out.len() - MAC_LENGTH); 110 | let mut temp_mac: [u8; MAC_LENGTH] = [0u8; MAC_LENGTH]; 111 | self.encrypt_with_ad(&ZEROLEN[..], in_out, &mut temp_mac)?; 112 | mac.copy_from_slice(&temp_mac[..]); 113 | Ok(()) 114 | } 115 | 116 | pub(crate) fn read_message_regular(&mut self, in_out: &mut [u8]) -> Result<(), NoiseError> { 117 | let (in_out, mac) = in_out.split_at_mut(in_out.len() - MAC_LENGTH); 118 | let mut temp_mac: [u8; MAC_LENGTH] = [0u8; MAC_LENGTH]; 119 | temp_mac.copy_from_slice(mac); 120 | self.decrypt_with_ad(&ZEROLEN[..], in_out, &mut temp_mac)?; 121 | temp_mac.zeroize(); 122 | Ok(()) 123 | } 124 | } 125 | 126 | pub struct SymmetricState { 127 | cs: CipherState, 128 | ck: Hash, 129 | h: Hash, 130 | } 131 | 132 | impl SymmetricState { 133 | pub(crate) fn clear(&mut self) { 134 | self.cs.clear_key(); 135 | self.ck.clear(); 136 | } 137 | 138 | pub fn initialize_symmetric(protocol_name: &[u8]) -> Self { 139 | let h: Hash; 140 | match protocol_name.len() { 141 | 0..=31 => { 142 | let mut temp = [0u8; HASHLEN]; 143 | let (protocol_name_len, _) = temp.split_at_mut(protocol_name.len()); 144 | protocol_name_len.copy_from_slice(protocol_name); 145 | h = Hash::from_bytes(from_slice_hashlen(&temp[..])); 146 | } 147 | 32 => h = Hash::from_bytes(from_slice_hashlen(protocol_name)), 148 | _ => h = Hash::from_bytes(hash(protocol_name)), 149 | } 150 | let ck: Hash = Hash::from_bytes(from_slice_hashlen(&h.as_bytes()[..])); 151 | let cs: CipherState = CipherState::new(); 152 | Self { cs, ck, h } 153 | } 154 | 155 | pub(crate) fn mix_key(&mut self, input_key_material: &[u8]) { 156 | let mut out0: [u8; HASHLEN] = EMPTY_HASH; 157 | let mut out1: [u8; HASHLEN] = EMPTY_HASH; 158 | let mut out2: [u8; HASHLEN] = EMPTY_HASH; 159 | hkdf( 160 | &self.ck.as_bytes()[..], 161 | input_key_material, 162 | 2, 163 | &mut out0[..], 164 | &mut out1[..], 165 | &mut out2[..], 166 | ); 167 | self.ck = Hash::from_bytes(out0); 168 | let mut temp_k: [u8; 32] = EMPTY_KEY; 169 | temp_k.copy_from_slice(&out1[..32]); 170 | self.cs = CipherState::from_key(Key::from_bytes(temp_k)); 171 | } 172 | 173 | pub(crate) fn mix_hash(&mut self, data: &[u8]) { 174 | self.h = Hash::from_bytes(hash_with_context(&self.h.as_bytes()[..], data)); 175 | } 176 | 177 | #[allow(dead_code)] 178 | pub(crate) fn mix_key_and_hash(&mut self, input_key_material: &[u8]) { 179 | let mut out0: [u8; HASHLEN] = EMPTY_HASH; 180 | let mut out1: [u8; HASHLEN] = EMPTY_HASH; 181 | let mut out2: [u8; HASHLEN] = EMPTY_HASH; 182 | hkdf( 183 | &self.ck.as_bytes()[..], 184 | input_key_material, 185 | 3, 186 | &mut out0[..], 187 | &mut out1[..], 188 | &mut out2[..], 189 | ); 190 | self.ck = Hash::from_bytes(out0); 191 | let temp_h: [u8; HASHLEN] = out1; 192 | let mut temp_k: [u8; DHLEN] = out2; 193 | self.mix_hash(&temp_h[..]); 194 | temp_k.copy_from_slice(&out2[..32]); 195 | self.cs = CipherState::from_key(Key::from_bytes(temp_k)); 196 | out0.zeroize(); 197 | out1.zeroize(); 198 | out2.zeroize(); 199 | } 200 | 201 | #[allow(dead_code)] 202 | pub(crate) fn get_handshake_hash(&self) -> [u8; HASHLEN] { 203 | from_slice_hashlen(&self.h.as_bytes()[..]) 204 | } 205 | 206 | pub(crate) fn encrypt_and_hash(&mut self, in_out: &mut [u8]) -> Result<(), NoiseError> { 207 | let mut temp_mac: [u8; MAC_LENGTH] = [0u8; MAC_LENGTH]; 208 | 209 | // This check was added to prevent an overflow bug found in fuzz testing. 210 | // info: https://github.com/ccdle12/rust-stratum-v2/issues/190 211 | if in_out.len() < MAC_LENGTH { 212 | return Err(NoiseError::MissingneError); 213 | } 214 | 215 | let (plaintext, mac) = in_out.split_at_mut(in_out.len() - MAC_LENGTH); 216 | self.cs 217 | .encrypt_with_ad(&self.h.as_bytes()[..], plaintext, &mut temp_mac)?; 218 | mac.copy_from_slice(&temp_mac[..]); 219 | self.mix_hash(in_out); 220 | Ok(()) 221 | } 222 | 223 | pub(crate) fn decrypt_and_hash(&mut self, in_out: &mut [u8]) -> Result<(), NoiseError> { 224 | let mut temp: [u8; 2048] = [0u8; 2048]; 225 | temp[..in_out.len()].copy_from_slice(in_out); 226 | let (ciphertext, mac) = in_out.split_at_mut(in_out.len() - MAC_LENGTH); 227 | let mut temp_mac: [u8; MAC_LENGTH] = [0u8; MAC_LENGTH]; 228 | temp_mac.copy_from_slice(mac); 229 | self.cs 230 | .decrypt_with_ad(&self.h.as_bytes()[..], ciphertext, &mut temp_mac)?; 231 | self.mix_hash(&temp[..in_out.len()]); 232 | Ok(()) 233 | } 234 | 235 | pub(crate) fn split(&mut self) -> (CipherState, CipherState) { 236 | let mut temp_k1: [u8; HASHLEN] = EMPTY_HASH; 237 | let mut temp_k2: [u8; HASHLEN] = EMPTY_HASH; 238 | let mut out2: [u8; HASHLEN] = EMPTY_HASH; 239 | hkdf( 240 | &self.ck.as_bytes()[..], 241 | &ZEROLEN[..], 242 | 2, 243 | &mut temp_k1[..], 244 | &mut temp_k2[..], 245 | &mut out2[..], 246 | ); 247 | let cs1: CipherState = 248 | CipherState::from_key(Key::from_bytes(from_slice_hashlen(&temp_k1[..32]))); 249 | temp_k1.copy_from_slice(&EMPTY_HASH[..]); 250 | let cs2: CipherState = 251 | CipherState::from_key(Key::from_bytes(from_slice_hashlen(&temp_k2[..32]))); 252 | temp_k2.copy_from_slice(&EMPTY_HASH[..]); 253 | (cs1, cs2) 254 | } 255 | } 256 | 257 | pub struct HandshakeState { 258 | ss: SymmetricState, 259 | s: Keypair, 260 | e: Keypair, 261 | rs: PublicKey, 262 | re: PublicKey, 263 | psk: Psk, 264 | } 265 | 266 | impl HandshakeState { 267 | pub(crate) fn clear(&mut self) { 268 | self.s.clear(); 269 | self.e.clear(); 270 | self.re.clear(); 271 | self.psk.clear(); 272 | } 273 | 274 | pub fn get_remote_static_public_key(&self) -> PublicKey { 275 | self.rs 276 | } 277 | 278 | pub(crate) fn set_ephemeral_keypair(&mut self, e: Keypair) { 279 | self.e = e; 280 | } 281 | 282 | pub(crate) fn initialize_initiator(prologue: &[u8], s: Keypair, psk: Psk) -> HandshakeState { 283 | let protocol_name = b"Noise_NX_25519_ChaChaPoly_BLAKE2s"; 284 | let mut ss: SymmetricState = SymmetricState::initialize_symmetric(&protocol_name[..]); 285 | ss.mix_hash(prologue); 286 | let rs = PublicKey::empty(); 287 | HandshakeState { 288 | ss, 289 | s, 290 | e: Keypair::new_empty(), 291 | rs, 292 | re: PublicKey::empty(), 293 | psk, 294 | } 295 | } 296 | 297 | pub(crate) fn initialize_responder(prologue: &[u8], s: Keypair, psk: Psk) -> HandshakeState { 298 | let protocol_name = b"Noise_NX_25519_ChaChaPoly_BLAKE2s"; 299 | let mut ss: SymmetricState = SymmetricState::initialize_symmetric(&protocol_name[..]); 300 | ss.mix_hash(prologue); 301 | let rs = PublicKey::empty(); 302 | HandshakeState { 303 | ss, 304 | s, 305 | e: Keypair::new_empty(), 306 | rs, 307 | re: PublicKey::empty(), 308 | psk, 309 | } 310 | } 311 | pub(crate) fn write_message_a(&mut self, in_out: &mut [u8]) -> Result<(), NoiseError> { 312 | if in_out.len() < DHLEN { 313 | return Err(NoiseError::MissingneError); 314 | } 315 | if self.e.is_empty() { 316 | self.e = Keypair::default(); 317 | } 318 | 319 | let (ne, in_out) = in_out.split_at_mut(DHLEN); 320 | ne.copy_from_slice(&self.e.get_public_key().as_bytes()[..]); 321 | self.ss.mix_hash(ne); 322 | 323 | /* No PSK, so skipping mixKey */ 324 | self.ss.mix_hash(in_out); 325 | Ok(()) 326 | } 327 | 328 | pub(crate) fn write_message_b( 329 | &mut self, 330 | in_out: &mut [u8], 331 | ) -> Result<(Hash, CipherState, CipherState), NoiseError> { 332 | if in_out.len() < DHLEN { 333 | return Err(NoiseError::MissingneError); 334 | } 335 | if self.e.is_empty() { 336 | self.e = Keypair::default(); 337 | } 338 | let (ne, in_out) = in_out.split_at_mut(DHLEN); 339 | ne.copy_from_slice(&self.e.get_public_key().as_bytes()[..]); 340 | self.ss.mix_hash(ne); 341 | /* No PSK, so skipping mixKey */ 342 | self.ss.mix_key(&self.e.dh(&self.re.as_bytes())); 343 | 344 | // This check was added to prevent an overflow bug found in fuzz testing. 345 | // info: https://github.com/ccdle12/rust-stratum-v2/issues/190 346 | if in_out.len() < DHLEN + MAC_LENGTH { 347 | return Err(NoiseError::MissingneError); 348 | } 349 | 350 | let (ns, in_out) = in_out.split_at_mut(DHLEN + MAC_LENGTH); 351 | ns[..DHLEN].copy_from_slice(&self.s.get_public_key().as_bytes()[..]); 352 | self.ss.encrypt_and_hash(ns)?; 353 | self.ss.mix_key(&self.s.dh(&self.re.as_bytes())); 354 | self.ss.encrypt_and_hash(in_out)?; 355 | let h: Hash = Hash::from_bytes(from_slice_hashlen(&self.ss.h.as_bytes())); 356 | let (cs1, cs2) = self.ss.split(); 357 | self.ss.clear(); 358 | Ok((h, cs1, cs2)) 359 | } 360 | 361 | pub(crate) fn read_message_a(&mut self, in_out: &mut [u8]) -> Result<(), NoiseError> { 362 | if in_out.len() < MAC_LENGTH + DHLEN { 363 | return Err(NoiseError::MissingreError); 364 | } 365 | 366 | let (re, in_out) = in_out.split_at_mut(DHLEN); 367 | self.re = PublicKey::from_bytes(from_slice_hashlen(re))?; 368 | self.ss.mix_hash(&self.re.as_bytes()[..DHLEN]); 369 | 370 | /* No PSK, so skipping mixKey */ 371 | self.ss.mix_hash(in_out); 372 | Ok(()) 373 | } 374 | 375 | pub(crate) fn read_message_b( 376 | &mut self, 377 | in_out: &mut [u8], 378 | ) -> Result<(Hash, CipherState, CipherState), NoiseError> { 379 | if in_out.len() < MAC_LENGTH + DHLEN { 380 | return Err(NoiseError::MissingreError); 381 | } 382 | let (re, in_out) = in_out.split_at_mut(DHLEN); 383 | self.re = PublicKey::from_bytes(from_slice_hashlen(re))?; 384 | self.ss.mix_hash(&self.re.as_bytes()[..DHLEN]); 385 | /* No PSK, so skipping mixKey */ 386 | self.ss.mix_key(&self.e.dh(&self.re.as_bytes())); 387 | if in_out.len() < MAC_LENGTH + DHLEN { 388 | return Err(NoiseError::MissingrsError); 389 | } 390 | let (rs, in_out) = in_out.split_at_mut(MAC_LENGTH + DHLEN); 391 | self.ss.decrypt_and_hash(rs)?; 392 | self.rs = PublicKey::from_bytes(from_slice_hashlen(rs))?; 393 | self.ss.mix_key(&self.e.dh(&self.rs.as_bytes())); 394 | self.ss.decrypt_and_hash(in_out)?; 395 | let h: Hash = Hash::from_bytes(from_slice_hashlen(&self.ss.h.as_bytes())); 396 | let (cs1, cs2) = self.ss.split(); 397 | self.ss.clear(); 398 | Ok((h, cs1, cs2)) 399 | } 400 | } 401 | --------------------------------------------------------------------------------