Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
887 changes: 723 additions & 164 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[workspace]
members = ["payjoin", "payjoin-client"]
members = ["payjoin", "payjoin-client", "payjoin-relay", "nnpsk0",]
13 changes: 13 additions & 0 deletions nnpsk0/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "noiseexplorer_nnpsk0"
version = "1.0.3"
authors = ["Symbolic Software <georgio@symbolic.software>"]
edition = "2018"

[dependencies]
blake2-rfc = "0.2.18"
constant_time_eq = "0.1.5"
hacl-star = "0.1.0"
hex = "0.4.3"
rand = "0.6.5"
zeroize = "1.3.0"
31 changes: 31 additions & 0 deletions nnpsk0/src/consts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* ---------------------------------------------------------------- *
* CONSTANTS *
* ---------------------------------------------------------------- */

#![allow(non_snake_case, non_upper_case_globals)]
use hacl_star::{chacha20poly1305, curve25519};

pub const DHLEN: usize = curve25519::SECRET_LENGTH;
pub(crate) const HASHLEN: usize = 32;
pub(crate) const BLOCKLEN: usize = 64;
pub(crate) const EMPTY_HASH: [u8; DHLEN] = [0_u8; HASHLEN];
pub(crate) const EMPTY_KEY: [u8; DHLEN] = [0_u8; DHLEN];
pub const MAC_LENGTH: usize = chacha20poly1305::MAC_LENGTH;
pub(crate) const MAX_MESSAGE: usize = 0xFFFF;
pub(crate) const MAX_NONCE: u64 = u64::max_value();
pub(crate) const NONCE_LENGTH: usize = chacha20poly1305::NONCE_LENGTH;
pub(crate) const ZEROLEN: [u8; 0] = [0_u8; 0];
pub(crate) const forbidden_curve_values: [[u8; 32]; 12] = [
[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],
[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],
[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],
[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],
[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],
[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],
[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],
[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],
[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],
[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],
[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],
[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]
];
39 changes: 39 additions & 0 deletions nnpsk0/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#[derive(Debug)]
pub enum NoiseError {
DecryptionError,
UnsupportedMessageLengthError,
ExhaustedNonceError,
InvalidKeyError,
InvalidPublicKeyError,
EmptyKeyError,
InvalidInputError,
DerivePublicKeyFromEmptyKeyError,
Hex(hex::FromHexError),
MissingnsError,
MissingneError,
MissingHsMacError,
MissingrsError,
MissingreError
}

impl std::fmt::Display for NoiseError {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
NoiseError::DecryptionError => write!(f, "Unsuccesful decryption."),
NoiseError::UnsupportedMessageLengthError => write!(f, "Unsupported Message Length."),
NoiseError::ExhaustedNonceError => write!(f, "Reached maximum number of messages that can be sent for this session."),
NoiseError::DerivePublicKeyFromEmptyKeyError => write!(f, "Unable to derive PublicKey."),
NoiseError::InvalidKeyError => write!(f, "Invalid Key."),
NoiseError::InvalidPublicKeyError => write!(f, "Invalid Public Key."),
NoiseError::EmptyKeyError => write!(f, "Empty Key."),
NoiseError::InvalidInputError => write!(f, "Invalid input length."),
NoiseError::MissingnsError => write!(f, "Invalid message length."),
NoiseError::MissingHsMacError => write!(f, "Invalid message length."),
NoiseError::MissingneError => write!(f, "Invalid message length."),
NoiseError::MissingrsError => write!(f, "Invalid message length."),
NoiseError::MissingreError => write!(f, "Invalid message length."),
NoiseError::Hex(ref e) => e.fmt(f),
}
}
}
94 changes: 94 additions & 0 deletions nnpsk0/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
NNpsk0:
-> psk, e
<- e, ee
->
<-
*/

/* ---------------------------------------------------------------- *
* PARAMETERS *
* ---------------------------------------------------------------- */

#[macro_use]
pub(crate) mod macros;

pub(crate) mod prims;
pub(crate) mod state;
pub(crate) mod utils;

pub mod consts;
pub mod error;
pub mod noisesession;
pub mod types;

#[cfg(test)]
mod test {
use crate::consts::{MAC_LENGTH, DHLEN};
use crate::noisesession::NoiseSession;
use crate::types::{Keypair, Psk};

#[test]
fn e2e_noise_from_pj_sender() {
let receiver_static = Keypair::new_empty(); // security does not depend on long-term static keys in NNpsk0. The interface still requires a Keypair, but it is not used.
// let receiver_static = Keypair::default(); // s from responder aka payjoin receiver
// ...
let psk = Psk::from_bytes(new_256bit_key()); // pre-shared from responder symmetric key. // todo can use 128 bit?
// ...

// Ready Message A -> psk, e

// from the initiator (payjoin sender)
let original_psbt = b"Original PSBT";
let sender_static = Keypair::new_empty(); // security does not depend on long-term static keys in NNpsk0. The interface still requires a Keypair, but it is not used.
let mut initiator = NoiseSession::init_session(true, b"", sender_static, psk.clone());

let mut in_out: Vec<u8> = vec![0; DHLEN];
in_out.append(&mut original_psbt.to_vec());
let message_a_size = DHLEN + original_psbt.len() + MAC_LENGTH;
in_out.resize(message_a_size, 0);
initiator.send_message(&mut in_out).unwrap(); // psk, e
let mut message_a = in_out;

// from the responder (payjoin receiver)

let mut responder = NoiseSession::init_session(false, b"", receiver_static, psk);
//let mut message_a_received: Vec<u8> = Vec::with_capacity(message_a_size); // you would have to make a sized buffer in implementation
responder.recv_message(&mut message_a).unwrap(); // es derived internally
println!("message_a bytes:{:?}", message_a);
let (_initiator_e, payload) = message_a.split_at_mut(DHLEN);
let (payload, _mac) = payload.split_at_mut(payload.len() - MAC_LENGTH);
println!("message_a decrypted:{:?}", String::from_utf8(payload.to_vec()));

// Ready Message B <- e, ee
// from the responder (payjoin receiver)
let payjoin_psbt = process_original_psbt(payload);
let mut in_out: Vec<u8> = vec![0; DHLEN];
in_out.append(&mut payjoin_psbt.to_vec());
let message_b_size = DHLEN + payjoin_psbt.len() + MAC_LENGTH;
in_out.resize(message_b_size, 0);
responder.send_message(&mut in_out).unwrap(); // e, ee

// from the initiator (payjoin sender)
let mut message_b = in_out;
println!("message_b bytes:{:?}", message_b);
initiator.recv_message(&mut message_b).unwrap(); // ee derived internally
let (_responder_e, payload) = message_b.split_at_mut(DHLEN);
let (payload, _mac) = payload.split_at_mut(payload.len() - MAC_LENGTH);
println!("message_b decrypted:{:?}", String::from_utf8(payload.to_vec()));
}

fn process_original_psbt(_original_psbt: &[u8]) -> &[u8] {
b"Payjoin PSBT"
}

fn new_256bit_key() -> [u8; 32] {
use rand::RngCore;
use rand::thread_rng;

let mut rng = thread_rng();
let mut key = [0u8; 32];
rng.fill_bytes(&mut key);
key
}
}
9 changes: 9 additions & 0 deletions nnpsk0/src/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* ---------------------------------------------------------------- *
* MACROS *
* ---------------------------------------------------------------- */

macro_rules! copy_slices {
($inslice:expr, $outslice:expr) => {
$outslice[..$inslice.len()].clone_from_slice(&$inslice[..])
};
}
194 changes: 194 additions & 0 deletions nnpsk0/src/noisesession.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/* ---------------------------------------------------------------- *
* PROCESSES *
* ---------------------------------------------------------------- */

use crate::{consts::{HASHLEN, MAC_LENGTH, MAX_MESSAGE},
error::NoiseError,
state::{CipherState, HandshakeState},
types::{Hash, Keypair, Psk, PublicKey}};
/// A `NoiseSession` object is used to keep track of the states of both local
/// and remote parties before, during, and after a handshake.
///
/// It contains:
/// - `hs`: Keeps track of the local party's state while a handshake is being
/// performed.
/// - `h`: Stores the handshake hash output after a successful handshake in a
/// Hash object. Is initialized as array of 0 bytes.
/// - `cs1`: Keeps track of the local party's post-handshake state. Contains a
/// cryptographic key and a nonce.
/// - `cs2`: Keeps track of the remote party's post-handshake state. Contains a
/// cryptographic key and a nonce.
/// - `mc`: Keeps track of the total number of incoming and outgoing messages,
/// including those sent during a handshake.
/// - `i`: `bool` value that indicates whether this session corresponds to the
/// local or remote party.
/// - `is_transport`: `bool` value that indicates whether a handshake has been
/// performed succesfully with a remote session and the session is in transport mode.

pub struct NoiseSession {
hs: HandshakeState,
h: Hash,
cs1: CipherState,
cs2: CipherState,
mc: u128,
i: bool,
is_transport: bool,
}
impl NoiseSession {
/// Returns `true` if a handshake has been successfully performed and the session is in transport mode, or false otherwise.
pub fn is_transport(&self) -> bool {
self.is_transport
}

/// Clears `cs1`.
pub fn clear_local_cipherstate(&mut self) {
self.cs1.clear();
}

/// Clears `cs2`.
pub fn clear_remote_cipherstate(&mut self) {
self.cs2.clear();
}

/// Calls the [Rekey](https://noiseprotocol.org/noise.html#rekey) method for `cs1`
pub fn rekey_local_cipherstate(&mut self) {
self.cs1.rekey()
}

/// Calls the [Rekey](https://noiseprotocol.org/noise.html#rekey) method for `cs2`
pub fn rekey_remote_cipherstate(&mut self) {
self.cs1.rekey()
}

/// `NoiseSession` destructor function.
pub fn end_session(mut self) {
self.hs.clear();
self.clear_local_cipherstate();
self.clear_remote_cipherstate();
self.cs2.clear();
self.mc = 0;
self.h = Hash::new();
}

/// Returns `h`.
pub fn get_handshake_hash(&self) -> Option<[u8; HASHLEN]> {
if self.is_transport {
return Some(self.h.as_bytes());
}
None
}

/// Returns `mc`.
pub fn get_message_count(&self) -> u128 {
self.mc
}

/// Sets the value of the local ephemeral keypair as the parameter `e`.
pub fn set_ephemeral_keypair(&mut self, e: Keypair) {
self.hs.set_ephemeral_keypair(e);
}

/// Returns a `Option<PublicKey>` object that contains the remote party's static `PublicKey`.
/// Note that this function returns `None` before a handshake is successfuly performed and
/// the session is in transport mode.
pub fn get_remote_static_public_key(&self) -> Option<PublicKey> {
if self.is_transport {
return Some(self.hs.get_remote_static_public_key());
}
None
}


/// Instantiates a `NoiseSession` object. Takes the following as parameters:
/// - `initiator`: `bool` variable. To be set as `true` when initiating a handshake with a remote party, or `false` otherwise.
/// - `prologue`: `Message` object. Could optionally contain the name of the protocol to be used.
/// - `s`: `Keypair` object. Contains local party's static keypair.

/// - `psk`: `Psk` object. Contains the pre-shared key.
pub fn init_session(initiator: bool, prologue: &[u8], s: Keypair, psk: Psk) -> NoiseSession {
if initiator {
NoiseSession{
hs: HandshakeState::initialize_initiator(prologue, s, psk),
mc: 0,
i: initiator,
cs1: CipherState::new(),
cs2: CipherState::new(),
h: Hash::new(),
is_transport: false,
}
} else {
NoiseSession {
hs: HandshakeState::initialize_responder(prologue, s, psk),
mc: 0,
i: initiator,
cs1: CipherState::new(),
cs2: CipherState::new(),
h: Hash::new(),
is_transport: false,
}
}
}

/// Takes a `&mut [u8]` containing plaintext as a parameter.
/// This method returns a `Ok(()))` upon successful encryption, and `Err(NoiseError)` otherwise
/// _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.
/// For transport messages:
/// 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`
/// For handshake messages:
/// Kindly use the message lengths listed in the test file under `../tests/handshake.rs`, where examples and notes are also provided.
/// _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._
pub fn send_message(&mut self, in_out: &mut [u8]) -> Result<(), NoiseError> {
if in_out.len() < MAC_LENGTH || in_out.len() > MAX_MESSAGE {
return Err(NoiseError::UnsupportedMessageLengthError);
}
if self.mc == 0 {
self.hs.write_message_a(in_out)?;
}
else if self.mc == 1 {
let temp = self.hs.write_message_b(in_out)?;
self.h = temp.0;
self.is_transport = true;
self.cs1 = temp.1;
self.cs2 = temp.2;
self.hs.clear();
} else if self.i {
self.cs1.write_message_regular(in_out)?;
} else {
self.cs2.write_message_regular(in_out)?;
}
self.mc += 1;
Ok(())
}

/// Takes a `&mut [u8]` received from the remote party as a parameter.
/// This method returns a `Ok(()))` upon successful decrytion. and `Err(NoiseError)` otherwise.
/// _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.
/// For transport messages:
/// 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.
/// For handshake messages:
/// Kindly use the message lengths listed in the test file under `../tests/handshake.rs`, where examples and notes are also provided.
///
/// _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._
pub fn recv_message(&mut self, in_out: &mut [u8]) -> Result<(), NoiseError> {
if in_out.len() < MAC_LENGTH || in_out.len() > MAX_MESSAGE {
return Err(NoiseError::UnsupportedMessageLengthError);
}
if self.mc == 0 {
self.hs.read_message_a(in_out)?;
}
else if self.mc == 1 {
let temp = self.hs.read_message_b(in_out)?;
self.h = temp.0;
self.is_transport = true;
self.cs1 = temp.1;
self.cs2 = temp.2;
self.hs.clear();
} else if self.i {
self.cs2.read_message_regular(in_out)?;
} else {
self.cs1.read_message_regular(in_out)?;
}
self.mc += 1;
Ok(())
}
}
Loading