mod chacha20poly1305;
mod fschacha20poly1305;
mod hkdf;
use core::fmt;
pub use bitcoin::Network;
use bitcoin::{
hashes::sha256,
secp256k1::{
self,
ellswift::{ElligatorSwift, ElligatorSwiftParty},
SecretKey,
},
};
use fschacha20poly1305::{FSChaCha20, FSChaCha20Poly1305};
use hkdf::Hkdf;
pub const NUM_HEADER_BYTES: usize = 1;
pub const NUM_LENGTH_BYTES: usize = 3;
const NUM_TAG_BYTES: usize = 16;
const NUM_PACKET_OVERHEAD_BYTES: usize = NUM_LENGTH_BYTES + NUM_HEADER_BYTES + NUM_TAG_BYTES;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Error {
CiphertextTooSmall,
BufferTooSmall { required_bytes: usize },
MaxGarbageLength,
HandshakeOutOfOrder,
SecretGeneration(SecretGenerationError),
Decryption(fschacha20poly1305::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::CiphertextTooSmall => {
write!(
f,
"Ciphertext does not contain enough information, should be further extended."
)
}
Error::BufferTooSmall { required_bytes } => write!(
f,
"Buffer memory allocation too small, need at least {} bytes.",
required_bytes
),
Error::MaxGarbageLength => {
write!(f, "More than 4095 bytes of garbage in the handshake.")
}
Error::HandshakeOutOfOrder => write!(f, "Handshake flow out of sequence."),
Error::SecretGeneration(e) => write!(f, "Cannot generate secrets: {:?}.", e),
Error::Decryption(e) => write!(f, "Decrytion error: {:?}.", e),
}
}
}
impl std::error::Error for Error {}
impl From<fschacha20poly1305::Error> for Error {
fn from(e: fschacha20poly1305::Error) -> Self {
Error::Decryption(e)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum SecretGenerationError {
MaterialsGeneration(secp256k1::Error),
Expansion(hkdf::MaxLengthError),
}
impl fmt::Display for SecretGenerationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SecretGenerationError::MaterialsGeneration(e) => {
write!(f, "Cannot generate materials: {}.", e)
}
SecretGenerationError::Expansion(e) => write!(f, "Cannot expand key: {}.", e),
}
}
}
impl From<secp256k1::Error> for Error {
fn from(e: secp256k1::Error) -> Self {
Error::SecretGeneration(SecretGenerationError::MaterialsGeneration(e))
}
}
impl From<hkdf::MaxLengthError> for Error {
fn from(e: hkdf::MaxLengthError) -> Self {
Error::SecretGeneration(SecretGenerationError::Expansion(e))
}
}
#[derive(Clone)]
pub struct SessionKeyMaterial {
initiator_length_key: [u8; 32],
initiator_packet_key: [u8; 32],
responder_length_key: [u8; 32],
responder_packet_key: [u8; 32],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Role {
Initiator,
Responder,
}
#[derive(Clone)]
pub struct PacketReader {
packet_decoding_aead: FSChaCha20Poly1305,
}
impl PacketReader {
pub fn decrypt_payload_no_alloc(
&mut self,
ciphertext: &[u8],
contents: &mut [u8],
aad: Option<&[u8]>,
) -> Result<(), Error> {
let auth = aad.unwrap_or_default();
if ciphertext.len() < NUM_TAG_BYTES {
return Err(Error::CiphertextTooSmall);
}
let (msg, tag) = ciphertext.split_at(ciphertext.len() - NUM_TAG_BYTES);
if contents.len() < msg.len() {
return Err(Error::BufferTooSmall {
required_bytes: msg.len(),
});
}
contents[0..msg.len()].copy_from_slice(msg);
self.packet_decoding_aead.decrypt(
auth,
&mut contents[0..msg.len()],
tag.try_into().expect("16 byte tag"),
)?;
Ok(())
}
pub fn decrypt_payload(
&mut self,
ciphertext: &[u8],
aad: Option<&[u8]>,
) -> Result<Vec<u8>, Error> {
let mut payload = vec![0u8; ciphertext.len() - NUM_TAG_BYTES];
self.decrypt_payload_no_alloc(ciphertext, &mut payload, aad)?;
Ok(payload)
}
}
#[derive(Clone)]
pub struct PacketWriter {
length_encoding_cipher: FSChaCha20,
packet_encoding_aead: FSChaCha20Poly1305,
}
impl PacketWriter {
pub fn encrypt_packet_no_alloc(
&mut self,
plaintext: &[u8],
aad: Option<&[u8]>,
packet: &mut [u8],
) -> Result<(), Error> {
if packet.len() < plaintext.len() + NUM_PACKET_OVERHEAD_BYTES {
return Err(Error::BufferTooSmall {
required_bytes: plaintext.len() + NUM_PACKET_OVERHEAD_BYTES,
});
}
let plaintext_length = plaintext.len();
let header_index = NUM_LENGTH_BYTES + NUM_HEADER_BYTES - 1;
let plaintext_start_index = header_index + 1;
let plaintext_end_index = plaintext_start_index + plaintext_length;
packet[header_index] = 0;
packet[plaintext_start_index..plaintext_end_index].copy_from_slice(plaintext);
let auth = aad.unwrap_or_default();
let tag = self
.packet_encoding_aead
.encrypt(auth, &mut packet[header_index..plaintext_end_index]);
let mut content_len = [0u8; 3];
content_len.copy_from_slice(&(plaintext_length as u32).to_le_bytes()[0..NUM_LENGTH_BYTES]);
self.length_encoding_cipher.crypt(&mut content_len);
packet[0..NUM_LENGTH_BYTES].copy_from_slice(&content_len);
packet[plaintext_end_index..(plaintext_end_index + NUM_TAG_BYTES)].copy_from_slice(&tag);
Ok(())
}
pub fn encrypt_packet(
&mut self,
plaintext: &[u8],
aad: Option<&[u8]>,
) -> Result<Vec<u8>, Error> {
let mut packet = vec![0u8; plaintext.len() + NUM_PACKET_OVERHEAD_BYTES];
self.encrypt_packet_no_alloc(plaintext, aad, &mut packet)?;
Ok(packet)
}
}
#[derive(Clone)]
pub struct PacketHandler {
packet_reader: PacketReader,
packet_writer: PacketWriter,
}
impl PacketHandler {
pub fn new(materials: SessionKeyMaterial, role: Role) -> Self {
match role {
Role::Initiator => {
let length_encoding_cipher = FSChaCha20::new(materials.initiator_length_key);
let packet_encoding_cipher =
FSChaCha20Poly1305::new(materials.initiator_packet_key);
let packet_decoding_cipher =
FSChaCha20Poly1305::new(materials.responder_packet_key);
PacketHandler {
packet_reader: PacketReader {
packet_decoding_aead: packet_decoding_cipher,
},
packet_writer: PacketWriter {
length_encoding_cipher,
packet_encoding_aead: packet_encoding_cipher,
},
}
}
Role::Responder => {
let length_encoding_cipher = FSChaCha20::new(materials.responder_length_key);
let packet_encoding_cipher =
FSChaCha20Poly1305::new(materials.responder_packet_key);
let packet_decoding_cipher =
FSChaCha20Poly1305::new(materials.initiator_packet_key);
PacketHandler {
packet_reader: PacketReader {
packet_decoding_aead: packet_decoding_cipher,
},
packet_writer: PacketWriter {
length_encoding_cipher,
packet_encoding_aead: packet_encoding_cipher,
},
}
}
}
}
pub fn reader(&mut self) -> &mut PacketReader {
&mut self.packet_reader
}
pub fn writer(&mut self) -> &mut PacketWriter {
&mut self.packet_writer
}
}
pub struct Handshake {}
impl Handshake {
pub fn get_shared_secrets(
a: ElligatorSwift,
b: ElligatorSwift,
secret: SecretKey,
party: ElligatorSwiftParty,
network: Network,
) -> Result<SessionKeyMaterial, Error> {
let data = "bip324_ellswift_xonly_ecdh".as_bytes();
let ecdh_sk = ElligatorSwift::shared_secret(a, b, secret, party, Some(data));
let ikm_salt = "bitcoin_v2_shared_secret".as_bytes();
let magic = network.magic().to_bytes();
let salt = [ikm_salt, &magic].concat();
let hk = Hkdf::<sha256::Hash>::new(salt.as_slice(), ecdh_sk.as_secret_bytes());
let mut session_id = [0u8; 32];
let session_info = "session_id".as_bytes();
hk.expand(session_info, &mut session_id)?;
let mut initiator_length_key = [0u8; 32];
let intiiator_l_info = "initiator_L".as_bytes();
hk.expand(intiiator_l_info, &mut initiator_length_key)?;
let mut initiator_packet_key = [0u8; 32];
let intiiator_p_info = "initiator_P".as_bytes();
hk.expand(intiiator_p_info, &mut initiator_packet_key)?;
let mut responder_length_key = [0u8; 32];
let responder_l_info = "responder_L".as_bytes();
hk.expand(responder_l_info, &mut responder_length_key)?;
let mut responder_packet_key = [0u8; 32];
let responder_p_info = "responder_P".as_bytes();
hk.expand(responder_p_info, &mut responder_packet_key)?;
Ok(SessionKeyMaterial {
initiator_length_key,
initiator_packet_key,
responder_length_key,
responder_packet_key,
})
}
}