Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 37 additions & 2 deletions smite/src/bolt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mod tx_init_rbf;
mod tx_remove_input;
mod tx_remove_output;
mod types;
mod update_fail_malformed_htlc;
mod warning;
mod wire;

Expand All @@ -47,8 +48,9 @@ pub use tx_remove_input::TxRemoveInput;
pub use tx_remove_output::TxRemoveOutput;
pub use types::{
BigSize, CHANNEL_ID_SIZE, COMPACT_SIGNATURE_SIZE, ChannelId, MAX_MESSAGE_SIZE, PUBLIC_KEY_SIZE,
TXID_SIZE, Txid,
SHA256_HASH_SIZE, TXID_SIZE, Txid,
Comment thread
morehouse marked this conversation as resolved.
};
pub use update_fail_malformed_htlc::UpdateFailMalformedHtlc;
pub use warning::Warning;
pub use wire::WireFormat;

Expand Down Expand Up @@ -127,6 +129,8 @@ pub mod msg_type {
pub const TX_ACK_RBF: u16 = 73;
/// `tx_abort` message (BOLT 2).
pub const TX_ABORT: u16 = 74;
/// `update_fail_malformed_htlc` message (BOLT 2).
pub const UPDATE_FAIL_MALFORMED_HTLC: u16 = 135;
/// Gossip timestamp filter message (BOLT 7).
pub const GOSSIP_TIMESTAMP_FILTER: u16 = 265;
}
Expand Down Expand Up @@ -171,6 +175,8 @@ pub enum Message {
TxAckRbf(TxAckRbf),
/// `tx_abort` message (type 74).
TxAbort(TxAbort),
/// `update_fail_malformed_htlc` message (type 135).
UpdateFailMalformedHtlc(UpdateFailMalformedHtlc),
/// Gossip timestamp filter message (type 265).
GossipTimestampFilter(GossipTimestampFilter),
/// Unknown message type.
Expand Down Expand Up @@ -208,6 +214,7 @@ impl Message {
Self::TxInitRbf(_) => msg_type::TX_INIT_RBF,
Self::TxAckRbf(_) => msg_type::TX_ACK_RBF,
Self::TxAbort(_) => msg_type::TX_ABORT,
Self::UpdateFailMalformedHtlc(_) => msg_type::UPDATE_FAIL_MALFORMED_HTLC,
Self::GossipTimestampFilter(_) => msg_type::GOSSIP_TIMESTAMP_FILTER,
Self::Unknown { msg_type, .. } => *msg_type,
}
Expand Down Expand Up @@ -237,6 +244,7 @@ impl Message {
Self::TxInitRbf(m) => out.extend(m.encode()),
Self::TxAckRbf(m) => out.extend(m.encode()),
Self::TxAbort(m) => out.extend(m.encode()),
Self::UpdateFailMalformedHtlc(m) => out.extend(m.encode()),
Self::GossipTimestampFilter(m) => out.extend(m.encode()),
Self::Unknown { payload, .. } => out.extend(payload),
}
Expand Down Expand Up @@ -273,6 +281,9 @@ impl Message {
msg_type::TX_INIT_RBF => Ok(Self::TxInitRbf(TxInitRbf::decode(cursor)?)),
msg_type::TX_ACK_RBF => Ok(Self::TxAckRbf(TxAckRbf::decode(cursor)?)),
msg_type::TX_ABORT => Ok(Self::TxAbort(TxAbort::decode(cursor)?)),
msg_type::UPDATE_FAIL_MALFORMED_HTLC => Ok(Self::UpdateFailMalformedHtlc(
UpdateFailMalformedHtlc::decode(cursor)?,
)),
msg_type::GOSSIP_TIMESTAMP_FILTER => Ok(Self::GossipTimestampFilter(
GossipTimestampFilter::decode(cursor)?,
)),
Expand Down Expand Up @@ -306,7 +317,7 @@ pub fn message_with_type(msg_type: u16, payload: &[u8]) -> Vec<u8> {
#[cfg(test)]
mod tests {
use super::*;
use secp256k1::hashes::Hash;
use secp256k1::hashes::{Hash, sha256};
use secp256k1::{PublicKey, Secp256k1, SecretKey};
use types::CHAIN_HASH_SIZE;

Expand Down Expand Up @@ -615,6 +626,19 @@ mod tests {
assert_eq!(decoded, Message::TxAbort(tx_abort));
}

#[test]
fn message_update_fail_malformed_htlc_roundtrip() {
let msg = UpdateFailMalformedHtlc {
channel_id: ChannelId::new([0x42; CHANNEL_ID_SIZE]),
id: 12345,
sha256_of_onion: sha256::Hash::from_byte_array([0xaa; SHA256_HASH_SIZE]),
failure_code: 0x8001,
};
let encoded = Message::UpdateFailMalformedHtlc(msg.clone()).encode();
let decoded = Message::decode(&encoded).unwrap();
assert_eq!(decoded, Message::UpdateFailMalformedHtlc(msg));
}

#[test]
fn message_gossip_timestamp_filter_roundtrip() {
let chain_hash = [0x6f; 32];
Expand All @@ -637,6 +661,7 @@ mod tests {
}

#[test]
#[allow(clippy::too_many_lines)]
fn message_type_values() {
assert_eq!(
Message::Warning(Warning::all_channels("")).msg_type(),
Expand Down Expand Up @@ -722,6 +747,16 @@ mod tests {
Message::TxAbort(TxAbort::new(ChannelId::new([0; CHANNEL_ID_SIZE]), "")).msg_type(),
msg_type::TX_ABORT
);
assert_eq!(
Message::UpdateFailMalformedHtlc(UpdateFailMalformedHtlc {
channel_id: ChannelId::new([0x42; CHANNEL_ID_SIZE]),
id: 12345,
sha256_of_onion: sha256::Hash::from_byte_array([0xaa; SHA256_HASH_SIZE]),
failure_code: 0x8001,
})
.msg_type(),
msg_type::UPDATE_FAIL_MALFORMED_HTLC
);
assert_eq!(
Message::GossipTimestampFilter(GossipTimestampFilter::no_gossip([0u8; 32])).msg_type(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh looks like I didn't use CHAIN_HASH_SIZE myself here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's worth it to replace usage of CHAIN_HASH_SIZE with SHA256_HASH_SIZE, since they're essentially the same thing?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about that too. But wouldn't this also apply to TXID_SIZE and CHANNEL_ID_SIZE? A txid is also just a sha256 hash, and a channel id is a txid XOR output index.

msg_type::GOSSIP_TIMESTAMP_FILTER
Expand Down
3 changes: 3 additions & 0 deletions smite/src/bolt/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ pub const CHANNEL_ID_SIZE: usize = 32;
/// Size of a chain hash (SHA256).
pub const CHAIN_HASH_SIZE: usize = 32;

/// Size of a SHA256 Hash.
pub const SHA256_HASH_SIZE: usize = 32;

/// Size of a transaction ID in bytes.
pub const TXID_SIZE: usize = 32;

Expand Down
155 changes: 155 additions & 0 deletions smite/src/bolt/update_fail_malformed_htlc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
//! BOLT 2 `update_fail_malformed_htlc` message.

use super::BoltError;
use super::types::ChannelId;
use super::wire::WireFormat;
use secp256k1::hashes::sha256;

/// BOLT 2 `update_fail_malformed_htlc` message (type 135). Sent
/// when a node cannot parse an incoming HTLC's onion packet.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpdateFailMalformedHtlc {
/// The channel ID.
pub channel_id: ChannelId,
/// The HTLC ID being failed.
pub id: u64,
/// Hash of the received unparsable onion.
pub sha256_of_onion: sha256::Hash,
/// The specific error code.
pub failure_code: u16,
}

impl UpdateFailMalformedHtlc {
/// Encodes to wire format (without message type prefix).
#[must_use]
pub fn encode(&self) -> Vec<u8> {
let mut out = Vec::new();
Comment thread
morehouse marked this conversation as resolved.
self.channel_id.write(&mut out);
self.id.write(&mut out);
self.sha256_of_onion.write(&mut out);
self.failure_code.write(&mut out);
out
}

/// Decodes from wire format (without message type prefix).
///
/// # Errors
///
/// Returns `Truncated` if the payload is too short.
pub fn decode(payload: &[u8]) -> Result<Self, BoltError> {
let mut cursor = payload;

let channel_id = WireFormat::read(&mut cursor)?;
let id = WireFormat::read(&mut cursor)?;
let sha256_of_onion = WireFormat::read(&mut cursor)?;
let failure_code = WireFormat::read(&mut cursor)?;
Ok(Self {
channel_id,
id,
sha256_of_onion,
failure_code,
})
}
}

#[cfg(test)]
mod tests {
use super::super::CHANNEL_ID_SIZE;
use super::*;
use crate::bolt::SHA256_HASH_SIZE;
use secp256k1::hashes::Hash;

/// Valid `UpdateFailMalformedHtlc` message for testing.
fn sample_msg() -> UpdateFailMalformedHtlc {
UpdateFailMalformedHtlc {
channel_id: ChannelId::new([0x42; CHANNEL_ID_SIZE]),
id: 12345,
sha256_of_onion: sha256::Hash::from_byte_array([0xaa; SHA256_HASH_SIZE]),
failure_code: 0x8001, // BADONION bit + 1
}
}

#[test]
fn encode_fixed_field_size() {
let msg = sample_msg();
let encoded = msg.encode();
// channel_id(32) + id(8) + sha256_of_onion(32) + failure_code(2) = 74
assert_eq!(encoded.len(), 74);
}

#[test]
fn roundtrip() {
let original = sample_msg();
let encoded = original.encode();
let decoded = UpdateFailMalformedHtlc::decode(&encoded).unwrap();
assert_eq!(original, decoded);
}

#[test]
fn decode_truncated_channel_id() {
assert_eq!(
UpdateFailMalformedHtlc::decode(&[0x00; 20]),
Err(BoltError::Truncated {
expected: CHANNEL_ID_SIZE,
actual: 20
})
);
}

#[test]
fn decode_truncated_id() {
// Full channel_id (32 bytes) + only 4 bytes of id
let mut data = vec![0xaa; CHANNEL_ID_SIZE];
data.extend_from_slice(&[0x00; 4]);
assert_eq!(
UpdateFailMalformedHtlc::decode(&data),
Err(BoltError::Truncated {
expected: 8,
actual: 4
})
);
}

#[test]
fn decode_truncated_sha256_of_onion() {
// Full channel_id (32 bytes) + full id (8 bytes) + only 16 bytes of sha256_of_onion
let mut data = vec![0x00u8; CHANNEL_ID_SIZE];
data.extend_from_slice(&[0x00; 8]);
data.extend_from_slice(&[0x00; 16]);
assert_eq!(
UpdateFailMalformedHtlc::decode(&data),
Err(BoltError::Truncated {
expected: SHA256_HASH_SIZE,
actual: 16
})
);
}

#[test]
fn decode_truncated_failure_code() {
// Full channel_id (32) + full id (8) + full sha256_of_onion (32) = 72
// failure_code needs 2, only give 1
let mut data = vec![0x00u8; CHANNEL_ID_SIZE];
data.extend_from_slice(&[0x00; 8]);
data.extend_from_slice(&[0x00; SHA256_HASH_SIZE]);
data.push(0x00);
assert_eq!(
UpdateFailMalformedHtlc::decode(&data),
Err(BoltError::Truncated {
expected: 2,
actual: 1
})
);
}

#[test]
fn decode_empty() {
assert_eq!(
UpdateFailMalformedHtlc::decode(&[]),
Err(BoltError::Truncated {
expected: CHANNEL_ID_SIZE,
actual: 0
})
);
}
}
62 changes: 60 additions & 2 deletions smite/src/bolt/wire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

use crate::bolt::BoltError;
use crate::bolt::types::{
BigSize, CHANNEL_ID_SIZE, COMPACT_SIGNATURE_SIZE, ChannelId, PUBLIC_KEY_SIZE, TXID_SIZE, Txid,
BigSize, CHANNEL_ID_SIZE, COMPACT_SIGNATURE_SIZE, ChannelId, PUBLIC_KEY_SIZE, SHA256_HASH_SIZE,
TXID_SIZE, Txid,
};
use secp256k1::PublicKey;
use secp256k1::ecdsa::Signature;
use secp256k1::hashes::Hash;
use secp256k1::hashes::{Hash, sha256};

/// A type that can be read from and written to the Lightning wire format.
pub trait WireFormat: Sized {
Expand Down Expand Up @@ -195,6 +196,17 @@ impl WireFormat for Signature {
}
}

impl WireFormat for sha256::Hash {
fn read(data: &mut &[u8]) -> Result<Self, BoltError> {
let buf: [u8; SHA256_HASH_SIZE] = WireFormat::read(data)?;
Ok(sha256::Hash::from_byte_array(buf))
}

fn write(&self, out: &mut Vec<u8>) {
self.to_byte_array().write(out);
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -863,4 +875,50 @@ mod tests {
assert_eq!(decoded, sig);
assert!(cursor.is_empty());
}

#[test]
fn sha256_hash_read_truncated() {
let mut empty: &[u8] = &[];
assert_eq!(
sha256::Hash::read(&mut empty),
Err(BoltError::Truncated {
expected: SHA256_HASH_SIZE,
actual: 0
})
);

let mut short: &[u8] = &[0xaa; 31]; // One byte short
assert_eq!(
sha256::Hash::read(&mut short),
Err(BoltError::Truncated {
expected: SHA256_HASH_SIZE,
actual: 31
})
);
}

#[test]
fn sha256_hash_write_roundtrip() {
let hash = sha256::Hash::from_byte_array([0xee; SHA256_HASH_SIZE]);

let mut buf = Vec::new();
hash.write(&mut buf);
assert_eq!(buf.len(), SHA256_HASH_SIZE);

let mut cursor: &[u8] = &buf;
let decoded = sha256::Hash::read(&mut cursor).unwrap();
assert_eq!(decoded, hash);
assert!(cursor.is_empty());
}

#[test]
fn sha256_hash_read_advances_cursor() {
let mut data: &[u8] = &[0x55; SHA256_HASH_SIZE + 5]; // 5 extra bytes
let hash = sha256::Hash::read(&mut data).unwrap();
assert_eq!(
hash,
sha256::Hash::from_byte_array([0x55; SHA256_HASH_SIZE])
);
assert_eq!(data.len(), 5); // 5 bytes remaining
}
}
Loading