Skip to content

Commit

Permalink
ping-pong-encrypted example
Browse files Browse the repository at this point in the history
Co-authored-by: bit-aloo <[email protected]>
  • Loading branch information
plebhash and Shourya742 committed Jun 29, 2024
1 parent 29c9acc commit 5f67449
Show file tree
Hide file tree
Showing 13 changed files with 388 additions and 413 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ jobs:
run: |
cargo test --manifest-path=protocols/Cargo.toml --features prop_test
- name: Run ping-pong-with-noise example
- name: Run ping-pong-encrypted example
run: |
cargo run --manifest-path=examples/ping-pong-with-noise/Cargo.toml --bin ping_pong_with_noise -- 10
cargo run --manifest-path=examples/ping-pong-encrypted/Cargo.toml
- name: Run ping-pong-without-noise example
run: |
Expand Down
19 changes: 19 additions & 0 deletions examples/ping-pong-encrypted/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "ping-pong-encrypted"
version = "0.1.0"
edition = "2021"
authors = [ "SRI Community" ]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
binary_sv2 = { path = "../../protocols/v2/binary-sv2/binary-sv2" }
codec_sv2 = { path = "../../protocols/v2/codec-sv2", features = [ "noise_sv2" ] }
noise_sv2 = { path = "../../protocols/v2/noise-sv2" }
key-utils = { version = "^1.0.0", path = "../../utils/key-utils" }
network_helpers_sv2 = { version = "2.0.0", path = "../../roles/roles-utils/network-helpers", features = [
"with_tokio",
] }
rand = "0.8"
tokio = { version = "1", features = [ "full" ] }
async-channel = "1.5.1"
20 changes: 20 additions & 0 deletions examples/ping-pong-encrypted/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
`ping-pong-encrypted` is an example of how to encode and decode SV2 binary frames (without any encryption layer) while leveraging the following crates:
- [`binary_sv2`](http://docs.rs/binary_sv2)
- [`codec_sv2`](http://docs.rs/codec_sv2)
- [`framing_sv2`](http://docs.rs/framing_sv2) (which is actually just re-exported by `codec_sv2`)
- [`noise_sv2`](http://docs.rs/noise_sv2)

We establish a simple `Ping`-`Pong` protocol with a server and a client communicating over a TCP socket.

The server expects to receive a `Ping` message encoded as a SV2 binary frame.
The `Ping` message contains a `nonce`, which is a `u8` generated randomly by the client.

The client expects to get a `Pong` message in response, also encoded as a SV2 binary frame, with the same `nonce`.

The messages are assigned arbitrary values for binary encoding:
```rust
pub const PING_MSG_TYPE: u8 = 0xfe;
pub const PONG_MSG_TYPE: u8 = 0xff;
```

All communication is encrypted with [SV2 Noise Protocol](https://stratumprotocol.org/specification/04-Protocol-Security/).
74 changes: 74 additions & 0 deletions examples/ping-pong-encrypted/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use crate::messages::{Message, Ping, Pong, PING_MSG_TYPE, PONG_MSG_TYPE};
use codec_sv2::{Frame, HandshakeRole, Initiator, StandardSv2Frame};
use key_utils::Secp256k1PublicKey;
use network_helpers_sv2::noise_connection_tokio::Connection;
use tokio::net::TcpStream;

use crate::error::Error;

pub async fn start_client(address: &str, k_pub: String) -> Result<(), Error> {
let stream = TcpStream::connect(address).await?;

println!("CLIENT: Connected to server on {}", address);

// parse server pubkey
let k_pub: Secp256k1PublicKey = k_pub.try_into()?;

// noise handshake initiator
let initiator = Initiator::from_raw_k(k_pub.into_bytes())?;

// channels for encrypted connection
let (receiver, sender, _, _) =
Connection::new(stream, HandshakeRole::Initiator(initiator)).await?;

// create Ping message
let ping = Ping::new()?;
let ping_nonce = ping.get_nonce();
let message = Message::Ping(ping);

// create Ping frame
let ping_frame =
StandardSv2Frame::<Message>::from_message(message.clone(), PING_MSG_TYPE, 0, false)
.ok_or(Error::FrameFromMessage)?;

// send Ping frame (sender takes care of encryption)
println!(
"CLIENT: Sending encrypted Ping to server with nonce: {}",
ping_nonce
);
sender.send(ping_frame.into()).await.map_err(|_| Error::Sender)?;

// ok, we have successfully sent the ping message
// now it's time to receive and verify the pong response
// receiver already took care of decryption
let mut frame: StandardSv2Frame<Message> = match receiver.recv().await {
Ok(f) => f.try_into()?,
Err(_) => return Err(Error::Receiver),
};

let frame_header = frame.get_header().ok_or(Error::FrameHeader)?;

// check message type on header
if frame_header.msg_type() != PONG_MSG_TYPE {
return Err(Error::FrameHeader);
}

// decode frame payload
let decoded_payload: Pong = match binary_sv2::from_bytes(frame.payload()) {
Ok(pong) => pong,
Err(e) => return Err(Error::BinarySv2(e)),
};

// check if nonce is the same as ping
let pong_nonce = decoded_payload.get_nonce();
if ping_nonce == pong_nonce {
println!(
"CLIENT: Received encrypted Pong with identical nonce as Ping: {}",
pong_nonce
);
} else {
return Err(Error::Nonce);
}

Ok(())
}
59 changes: 59 additions & 0 deletions examples/ping-pong-encrypted/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#[derive(std::fmt::Debug)]
pub enum Error {
Io(std::io::Error),
CodecSv2(codec_sv2::Error),
FramingSv2(codec_sv2::framing_sv2::Error),
BinarySv2(binary_sv2::Error),
NoiseSv2(noise_sv2::Error),
NetworkHelpersSv2(network_helpers_sv2::Error),
KeyUtils(key_utils::Error),
Receiver,
Sender,
FrameHeader,
FrameFromMessage,
Nonce,
WrongMessage,
Tcp(std::io::Error),
}

impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Error {
Error::Io(e)
}
}

impl From<codec_sv2::Error> for Error {
fn from(e: codec_sv2::Error) -> Error {
Error::CodecSv2(e)
}
}

impl From<network_helpers_sv2::Error> for Error {
fn from(e: network_helpers_sv2::Error) -> Error {
Error::NetworkHelpersSv2(e)
}
}

impl From<binary_sv2::Error> for Error {
fn from(e: binary_sv2::Error) -> Error {
Error::BinarySv2(e)
}
}

impl From<noise_sv2::Error> for Error {
fn from(e: noise_sv2::Error) -> Error {
Error::NoiseSv2(e)
}
}

impl From<key_utils::Error> for Error {
fn from(e: key_utils::Error) -> Error {
Error::KeyUtils(e)
}
}

impl From<codec_sv2::framing_sv2::Error> for Error {
fn from(e: codec_sv2::framing_sv2::Error) -> Error {
Error::FramingSv2(e)
}
}
33 changes: 33 additions & 0 deletions examples/ping-pong-encrypted/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
mod client;
mod error;
mod messages;
mod server;

const ADDR: &str = "127.0.0.1:3333";
const SERVER_PUBLIC_K: &str = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72";
const SERVER_PRIVATE_K: &str = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n";
const SERVER_CERT_VALIDITY: std::time::Duration = std::time::Duration::from_secs(3600);

#[tokio::main]
async fn main() {
// start the server in a separate thread
tokio::spawn(async {
server::start_server(
ADDR,
SERVER_PUBLIC_K.to_string(),
SERVER_PRIVATE_K.to_string(),
SERVER_CERT_VALIDITY,
)
.await
.expect("Server failed");
});

// give the server a moment to start up
std::thread::sleep(std::time::Duration::from_secs(1));

// start the client
// note: it only knows the server's pubkey!
client::start_client(ADDR, SERVER_PUBLIC_K.to_string())
.await
.expect("Client failed");
}
83 changes: 83 additions & 0 deletions examples/ping-pong-encrypted/src/messages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use crate::error::Error;
use binary_sv2::{
binary_codec_sv2,
decodable::{DecodableField, FieldMarker},
Deserialize, Serialize,
};

use rand::Rng;

pub const PING_MSG_TYPE: u8 = 0xfe;
pub const PONG_MSG_TYPE: u8 = 0xff;

// we derive binary_sv2::{Serialize, Deserialize}
// to allow for binary encoding
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Ping {
nonce: u8,
}

impl Ping {
pub fn new() -> Result<Self, Error> {
let mut rng = rand::thread_rng();
let random: u8 = rng.gen();
Ok(Self { nonce: random })
}

pub fn get_nonce(&self) -> u8 {
self.nonce
}
}

// we derive binary_sv2::{Serialize, Deserialize}
// to allow for binary encoding
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Pong {
nonce: u8,
}

impl<'decoder> Pong {
pub fn new(nonce: u8) -> Result<Self, Error> {
Ok(Self { nonce })
}

pub fn get_nonce(&self) -> u8 {
self.nonce
}
}

// unifies message types for noise_connection_tokio::Connection
#[derive(Clone)]
pub enum Message {
Ping(Ping),
Pong(Pong),
}

impl binary_sv2::GetSize for Message {
fn get_size(&self) -> usize {
match self {
Self::Ping(ping) => ping.get_size(),
Self::Pong(pong) => pong.get_size(),
}
}
}

impl From<Message> for binary_sv2::encodable::EncodableField<'_> {
fn from(m: Message) -> Self {
match m {
Message::Ping(p) => p.into(),
Message::Pong(p) => p.into(),
}
}
}

impl Deserialize<'_> for Message {
fn get_structure(_v: &[u8]) -> std::result::Result<Vec<FieldMarker>, binary_sv2::Error> {
unimplemented!()
}
fn from_decoded_fields(
_v: Vec<DecodableField>,
) -> std::result::Result<Self, binary_sv2::Error> {
unimplemented!()
}
}
Loading

0 comments on commit 5f67449

Please sign in to comment.