Skip to content

Commit

Permalink
Merge pull request #1040 from rrybarczyk/2024-07-rust-docs-protocols-…
Browse files Browse the repository at this point in the history
…codec-sv2

Rust docs for `protocols::v2::codec-sv2`
  • Loading branch information
GitGab19 authored Oct 14, 2024
2 parents 14f3e8a + 7356d57 commit b08c163
Show file tree
Hide file tree
Showing 9 changed files with 990 additions and 103 deletions.
5 changes: 4 additions & 1 deletion protocols/v2/codec-sv2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ const_sv2 = { version = "2.0.0", path = "../../../protocols/v2/const-sv2"}
buffer_sv2 = { version = "1.0.0", path = "../../../utils/buffer"}
tracing = { version = "0.1"}

[dev-dependencies]
key-utils = { version = "^1.0.0", path = "../../../utils/key-utils" }

[features]
with_serde = ["binary_sv2/with_serde", "serde", "framing_sv2/with_serde", "buffer_sv2/with_serde"]
with_buffer_pool = ["framing_sv2/with_buffer_pool"]
no_std = []

[package.metadata.docs.rs]
all-features = true
all-features = true
49 changes: 49 additions & 0 deletions protocols/v2/codec-sv2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# `codec_sv2`

[![crates.io](https://img.shields.io/crates/v/codec_sv2.svg)](https://crates.io/crates/codec_sv2)
[![docs.rs](https://docs.rs/codec_sv2/badge.svg)](https://docs.rs/codec_sv2)
[![rustc+](https://img.shields.io/badge/rustc-1.75.0%2B-lightgrey.svg)](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html)
[![license](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](https://github.com/stratum-mining/stratum/blob/main/LICENSE.md)
[![codecov](https://codecov.io/gh/stratum-mining/stratum/branch/main/graph/badge.svg?flag=codec_sv2-coverage)](https://codecov.io/gh/stratum-mining/stratum)

`codec_sv2` provides the message encoding and decoding functionality for the Stratum V2 (Sv2)
protocol, handling secure communication between Sv2 roles. This crate abstracts the complexity of
message encoding/decoding with optional Noise protocol support, ensuring both regular and encrypted
messages can be serialized, transmitted, and decoded consistently and reliably.

## Main Components

- **Encoder**: Encodes Sv2 messages with or without Noise protocol support.
- **Decoder**: Decodes Sv2 messages with or without Noise protocol support.
- **Handshake State**: Manages the current Noise protocol handshake state of the codec.


## Usage

To include this crate in your project, run:

```bash
cargo add codec_sv2
```

This crate can be built with the following feature flags:

- `noise_sv2`: Enables support for Noise protocol encryption and decryption.
- `with_buffer_pool`: Enables buffer pooling for more efficient memory management.
- `with_serde`: builds [`binary_sv2`](https://crates.io/crates/binary_sv2) and
[`buffer_sv2`](https://crates.io/crates/buffer_sv2) crates with `serde`-based encoding and
decoding. Note that this feature flag is only used for the Message Generator, and deprecated
for any other kind of usage. It will likely be fully deprecated in the future.

### Examples

This crate provides two examples demonstrating how to encode and decode Sv2 frames:

1. **[Unencrypted Example](https://github.com/stratum-mining/stratum/blob/main/protocols/v2/codec-sv2/examples/unencrypted.rs)**:
Encode and decode standard Sv2 frames, detailing the message serialization and transmission
process for unencrypted communications.

2. **[Encrypted Example](https://github.com/stratum-mining/stratum/blob/main/protocols/v2/codec-sv2/examples/encrypted.rs)**:
Encode and decode Sv2 frames with Noise protocol encryption, detailing the entire encryption
handshake and transport phase and serialization and transmission process for encrypted
communications.
208 changes: 208 additions & 0 deletions protocols/v2/codec-sv2/examples/encrypted.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// # Using Sv2 Codec with Noise Encryption
//
// This example demonstrates how to use the `codec-sv2` crate to encode and decode Sv2 frames
// with Noise protocol encryption over a TCP connection. It showcases how to:
//
// * Perform a Noise handshake between the sender and receiver.
// * Create an arbitrary custom message type (`CustomMessage`) for encryption/encoding and
// decryption/decoding.
// * Encode the message into an encrypted Sv2 frame using Noise.
// * Send the encrypted frame over a TCP connection.
// * Decode the encrypted Sv2 frame on the receiving side after completing the Noise handshake.
//
// ## Run
//
// ```
// cargo run --example encrypted --features noise_sv2
// ```

use binary_sv2::{binary_codec_sv2, Deserialize, Serialize};
#[cfg(feature = "noise_sv2")]
use codec_sv2::{
Error, HandshakeRole, Initiator, NoiseEncoder, Responder, StandardEitherFrame,
StandardNoiseDecoder, StandardSv2Frame, State, Sv2Frame,
};
#[cfg(feature = "noise_sv2")]
use const_sv2::{
INITIATOR_EXPECTED_HANDSHAKE_MESSAGE_SIZE, RESPONDER_EXPECTED_HANDSHAKE_MESSAGE_SIZE,
};
#[cfg(feature = "noise_sv2")]
use key_utils::{Secp256k1PublicKey, Secp256k1SecretKey};
use std::convert::TryInto;
#[cfg(feature = "noise_sv2")]
use std::{
io::{Read, Write},
net::{TcpListener, TcpStream},
};

// Arbitrary message type.
// Supported Sv2 message types are listed in the [Sv2 Spec Message
// Types](https://github.com/stratum-mining/sv2-spec/blob/main/08-Message-Types.md).
#[cfg(feature = "noise_sv2")]
const CUSTOM_MSG_TYPE: u8 = 0xff;

// Emulate a TCP connection
#[cfg(feature = "noise_sv2")]
const TCP_ADDR: &str = "127.0.0.1:3333";

#[cfg(feature = "noise_sv2")]
const AUTHORITY_PUBLIC_K: &str = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72";
#[cfg(feature = "noise_sv2")]
const AUTHORITY_PRIVATE_K: &str = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n";
#[cfg(feature = "noise_sv2")]
const CERT_VALIDITY: std::time::Duration = std::time::Duration::from_secs(3600);

// Example message type.
// In practice, all Sv2 messages are defined in the following crates:
// * `common_messages_sv2`
// * `mining_sv2`
// * `job_declaration_sv2`
// * `template_distribution_sv2`
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CustomMessage {
nonce: u16,
}

#[cfg(feature = "noise_sv2")]
fn main() {
// Start a receiving listener
let listener_receiver = TcpListener::bind(TCP_ADDR).expect("Failed to bind TCP listener");

// Start a sender stream
let mut stream_sender: TcpStream =
TcpStream::connect(TCP_ADDR).expect("Failed to connect to TCP stream");

// Start a receiver stream
let mut stream_receiver: TcpStream = listener_receiver
.incoming()
.next()
.expect("Failed to accept incoming TCP stream")
.expect("Failed to connect to incoming TCP stream");

// Handshake
let authority_public_k: Secp256k1PublicKey = AUTHORITY_PUBLIC_K
.to_string()
.try_into()
.expect("Failed to convert receiver public key to Secp256k1PublicKey");

let authority_private_k: Secp256k1SecretKey = AUTHORITY_PRIVATE_K
.to_string()
.try_into()
.expect("Failed to convert receiver private key to Secp256k1PublicKey");

let initiator = Initiator::from_raw_k(authority_public_k.into_bytes())
.expect("Failed to create initiator role from raw pub key");

let responder = Responder::from_authority_kp(
&authority_public_k.into_bytes(),
&authority_private_k.into_bytes(),
CERT_VALIDITY,
)
.expect("Failed to initialize responder from pub/key pair and/or cert");

let mut sender_state = State::initialized(HandshakeRole::Initiator(initiator));
let mut receiver_state = State::initialized(HandshakeRole::Responder(responder));

let first_message = sender_state
.step_0()
.expect("Initiator failed first step of handshake");
let first_message: [u8; RESPONDER_EXPECTED_HANDSHAKE_MESSAGE_SIZE] = first_message
.get_payload_when_handshaking()
.try_into()
.expect("Handshake remote invlaid message");

let (second_message, receiver_state) = receiver_state
.step_1(first_message)
.expect("Responder failed second step of handshake");
let second_message: [u8; INITIATOR_EXPECTED_HANDSHAKE_MESSAGE_SIZE] = second_message
.get_payload_when_handshaking()
.try_into()
.expect("Handshake remote invlaid message");

let sender_state = sender_state
.step_2(second_message)
.expect("Initiator failed third step of handshake");

let mut sender_state = match sender_state {
State::Transport(c) => State::with_transport_mode(c),
_ => panic!("todo"),
};

let mut receiver_state = match receiver_state {
State::Transport(c) => State::with_transport_mode(c),
_ => panic!("todo"),
};

// Create a message
let nonce = 1337;
let msg = CustomMessage { nonce };
let msg_type = CUSTOM_MSG_TYPE;
// Unique identifier of the extension describing the protocol message, as defined by Sv2 Framing
let extension_type = 0;
// This message is intended for the receiver, so set to false
let channel_msg = false;

let frame = StandardEitherFrame::<CustomMessage>::Sv2(
Sv2Frame::from_message(msg, msg_type, extension_type, channel_msg)
.expect("Failed to create the frame"),
);

let mut encoder = NoiseEncoder::<CustomMessage>::new();
let encoded_frame = encoder
.encode(frame, &mut sender_state)
.expect("Failed to encode the frame");

// Send the encoded frame
stream_sender
.write_all(encoded_frame.as_slice())
.expect("Failed to send the encoded frame");

// Initialize the decoder
let mut decoder = StandardNoiseDecoder::<CustomMessage>::new();

let mut decoded_frame;

// Continuously read the frame from the TCP stream into the decoder buffer until the full
// message is received.
//
// Note: The length of the payload is defined in a header field. Every call to `next_frame`
// will return a `MissingBytes` error, until the full payload is received.
loop {
let decoder_buf = decoder.writable();

// Read the frame header into the decoder buffer
stream_receiver
.read_exact(decoder_buf)
.expect("Failed to read the encoded frame header");

let result = decoder.next_frame(&mut receiver_state);
match result {
Ok(frame) => {
let frame: StandardSv2Frame<CustomMessage> = frame
.try_into()
.expect("Failed to decode frame into Sv2Frame");
decoded_frame = frame;
break;
}
Err(Error::MissingBytes(_)) => {}
Err(_) => panic!("Failed to decode the frame"),
}
}

// Parse the decoded frame header and payload
let decoded_frame_header = decoded_frame
.get_header()
.expect("Failed to get the frame header");

let decoded_msg: CustomMessage = binary_sv2::from_bytes(decoded_frame.payload())
.expect("Failed to extract the message from the payload");

// Assert that the decoded message is as expected
assert_eq!(decoded_frame_header.msg_type(), CUSTOM_MSG_TYPE);
assert_eq!(decoded_msg.nonce, nonce);
}

#[cfg(not(feature = "noise_sv2"))]
fn main() {
eprintln!("Noise feature not enabled. Skipping example.");
}
Loading

0 comments on commit b08c163

Please sign in to comment.