From fe62b88bc4a7eccf1dad1a4c840cb74b8c4f8290 Mon Sep 17 00:00:00 2001 From: Matt Morehouse Date: Thu, 14 May 2026 17:37:03 -0500 Subject: [PATCH 1/2] smite: implement node_announcement codec --- smite/src/bolt.rs | 40 ++++ smite/src/bolt/node_announcement.rs | 273 ++++++++++++++++++++++++++++ 2 files changed, 313 insertions(+) create mode 100644 smite/src/bolt/node_announcement.rs diff --git a/smite/src/bolt.rs b/smite/src/bolt.rs index 5e75286..c5638d7 100644 --- a/smite/src/bolt.rs +++ b/smite/src/bolt.rs @@ -14,6 +14,7 @@ mod funding_created; mod funding_signed; mod gossip_timestamp_filter; mod init; +mod node_announcement; mod open_channel; mod open_channel2; mod ping; @@ -48,6 +49,7 @@ pub use funding_created::FundingCreated; pub use funding_signed::FundingSigned; pub use gossip_timestamp_filter::GossipTimestampFilter; pub use init::{Init, InitTlvs}; +pub use node_announcement::NodeAnnouncement; pub use open_channel::{OpenChannel, OpenChannelTlvs}; pub use open_channel2::{OpenChannel2, OpenChannel2Tlvs}; pub use ping::Ping; @@ -156,6 +158,8 @@ pub mod msg_type { pub const UPDATE_FAIL_HTLC: u16 = 131; /// `update_fail_malformed_htlc` message (BOLT 2). pub const UPDATE_FAIL_MALFORMED_HTLC: u16 = 135; + /// `node_announcement` message (BOLT 7). + pub const NODE_ANNOUNCEMENT: u16 = 257; /// Gossip timestamp filter message (BOLT 7). pub const GOSSIP_TIMESTAMP_FILTER: u16 = 265; } @@ -210,6 +214,8 @@ pub enum Message { UpdateFailHtlc(UpdateFailHtlc), /// `update_fail_malformed_htlc` message (type 135). UpdateFailMalformedHtlc(UpdateFailMalformedHtlc), + /// `node_announcement` message (type 257). + NodeAnnouncement(NodeAnnouncement), /// Gossip timestamp filter message (type 265). GossipTimestampFilter(GossipTimestampFilter), /// Unknown message type. @@ -252,6 +258,7 @@ impl Message { Self::UpdateFulfillHtlc(_) => msg_type::UPDATE_FULFILL_HTLC, Self::UpdateFailHtlc(_) => msg_type::UPDATE_FAIL_HTLC, Self::UpdateFailMalformedHtlc(_) => msg_type::UPDATE_FAIL_MALFORMED_HTLC, + Self::NodeAnnouncement(_) => msg_type::NODE_ANNOUNCEMENT, Self::GossipTimestampFilter(_) => msg_type::GOSSIP_TIMESTAMP_FILTER, Self::Unknown { msg_type, .. } => *msg_type, } @@ -286,6 +293,7 @@ impl Message { Self::UpdateFulfillHtlc(m) => out.extend(m.encode()), Self::UpdateFailHtlc(m) => out.extend(m.encode()), Self::UpdateFailMalformedHtlc(m) => out.extend(m.encode()), + Self::NodeAnnouncement(m) => out.extend(m.encode()), Self::GossipTimestampFilter(m) => out.extend(m.encode()), Self::Unknown { payload, .. } => out.extend(payload), } @@ -331,6 +339,9 @@ impl Message { msg_type::UPDATE_FAIL_MALFORMED_HTLC => Ok(Self::UpdateFailMalformedHtlc( UpdateFailMalformedHtlc::decode(cursor)?, )), + msg_type::NODE_ANNOUNCEMENT => { + Ok(Self::NodeAnnouncement(NodeAnnouncement::decode(cursor)?)) + } msg_type::GOSSIP_TIMESTAMP_FILTER => Ok(Self::GossipTimestampFilter( GossipTimestampFilter::decode(cursor)?, )), @@ -366,6 +377,7 @@ mod tests { use super::*; use bitcoin::Txid; use bitcoin::hashes::{Hash, sha256}; + use bitcoin::secp256k1::ecdsa::Signature; use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; use types::CHAIN_HASH_SIZE; @@ -777,6 +789,30 @@ mod tests { assert_eq!(decoded, Message::UpdateFailMalformedHtlc(msg)); } + /// Valid `NodeAnnouncement` message for testing. + fn sample_node_announcement() -> NodeAnnouncement { + let secp = Secp256k1::new(); + let sk = SecretKey::from_slice(&[0x11; 32]).expect("valid secret"); + + NodeAnnouncement { + signature: Signature::from_compact(&[0u8; 64]).unwrap(), + features: vec![0x01, 0x02], + timestamp: 1_700_000_000, + node_id: PublicKey::from_secret_key(&secp, &sk), + rgb_color: [0xaa, 0xbb, 0xcc], + alias: [0x42; 32], + addresses: vec![0x01, 0x7f, 0x00, 0x00, 0x01, 0x23, 0x45], + } + } + + #[test] + fn message_node_announcement_roundtrip() { + let na = sample_node_announcement(); + let encoded = Message::NodeAnnouncement(na.clone()).encode(); + let decoded = Message::decode(&encoded).unwrap(); + assert_eq!(decoded, Message::NodeAnnouncement(na)); + } + #[test] fn message_gossip_timestamp_filter_roundtrip() { let chain_hash = [0x6f; 32]; @@ -923,6 +959,10 @@ mod tests { .msg_type(), msg_type::UPDATE_FAIL_MALFORMED_HTLC ); + assert_eq!( + Message::NodeAnnouncement(sample_node_announcement()).msg_type(), + msg_type::NODE_ANNOUNCEMENT + ); assert_eq!( Message::GossipTimestampFilter(GossipTimestampFilter::no_gossip([0u8; 32])).msg_type(), msg_type::GOSSIP_TIMESTAMP_FILTER diff --git a/smite/src/bolt/node_announcement.rs b/smite/src/bolt/node_announcement.rs new file mode 100644 index 0000000..9167c66 --- /dev/null +++ b/smite/src/bolt/node_announcement.rs @@ -0,0 +1,273 @@ +//! BOLT 7 node announcement message. + +use super::BoltError; +use super::wire::WireFormat; +use bitcoin::secp256k1::PublicKey; +use bitcoin::secp256k1::ecdsa::Signature; + +/// BOLT 7 `node_announcement` message (type 257). +/// +/// Allows a node to advertise extra data associated with its public key +/// (features, alias, color, network addresses). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NodeAnnouncement { + /// Signature by `node_id` over the rest of the message body. + pub signature: Signature, + /// Feature bits, BOLT 9. + pub features: Vec, + /// Monotonic per-node timestamp; receivers reject older announcements. + pub timestamp: u32, + /// Compressed secp256k1 public key identifying the announcing node. + pub node_id: PublicKey, + /// RGB color triple for UI display. + pub rgb_color: [u8; 3], + /// UTF-8 node alias, zero-padded to 32 bytes. + pub alias: [u8; 32], + /// Network address descriptors. + pub addresses: Vec, +} + +impl NodeAnnouncement { + /// Encodes to wire format (without message type prefix). + #[must_use] + pub fn encode(&self) -> Vec { + let mut out = Vec::new(); + self.signature.write(&mut out); + self.features.write(&mut out); + self.timestamp.write(&mut out); + self.node_id.write(&mut out); + self.rgb_color.write(&mut out); + self.alias.write(&mut out); + self.addresses.write(&mut out); + out + } + + /// Decodes from wire format (without message type prefix). + /// + /// # Errors + /// + /// Returns `Truncated` if the payload is too short for any field, + /// `InvalidSignature` if the signature bytes are not a valid compact ECDSA + /// signature, or `InvalidPublicKey` if `node_id` is not a valid compressed + /// point. + pub fn decode(payload: &[u8]) -> Result { + let mut cursor = payload; + + let signature = WireFormat::read(&mut cursor)?; + let features = WireFormat::read(&mut cursor)?; + let timestamp = WireFormat::read(&mut cursor)?; + let node_id = WireFormat::read(&mut cursor)?; + let rgb_color = WireFormat::read(&mut cursor)?; + let alias = WireFormat::read(&mut cursor)?; + let addresses = WireFormat::read(&mut cursor)?; + + Ok(Self { + signature, + features, + timestamp, + node_id, + rgb_color, + alias, + addresses, + }) + } +} + +#[cfg(test)] +#[allow(clippy::range_plus_one)] +mod tests { + use super::super::{COMPACT_SIGNATURE_SIZE, PUBLIC_KEY_SIZE}; + use super::*; + use bitcoin::secp256k1::{Secp256k1, SecretKey}; + + /// Valid `NodeAnnouncement` for testing. + fn sample_node_announcement() -> NodeAnnouncement { + let secp = Secp256k1::new(); + let sk = SecretKey::from_slice(&[0x11; 32]).expect("valid secret"); + + NodeAnnouncement { + signature: Signature::from_compact(&[0u8; 64]).unwrap(), + features: vec![0x01, 0x02], + timestamp: 1_700_000_000, + node_id: PublicKey::from_secret_key(&secp, &sk), + rgb_color: [0xaa, 0xbb, 0xcc], + alias: [0x42; 32], + addresses: vec![0x01, 0x7f, 0x00, 0x00, 0x01, 0x23, 0x45], + } + } + + #[test] + fn roundtrip() { + let original = sample_node_announcement(); + let encoded = original.encode(); + let decoded = NodeAnnouncement::decode(&encoded).unwrap(); + assert_eq!(original, decoded); + } + + #[test] + fn encode_size_with_empty_variable_fields() { + let secp = Secp256k1::new(); + let sk = SecretKey::from_slice(&[0x11; 32]).unwrap(); + let na = NodeAnnouncement { + signature: Signature::from_compact(&[0u8; 64]).unwrap(), + features: vec![], + timestamp: 0, + node_id: PublicKey::from_secret_key(&secp, &sk), + rgb_color: [0; 3], + alias: [0; 32], + addresses: vec![], + }; + // 64 (sig) + 2 (flen=0) + 4 (timestamp) + 33 (node_id) + 3 (rgb) + 32 (alias) + 2 (addrlen=0) = 140 + assert_eq!(na.encode().len(), 140); + } + + #[test] + fn decode_truncated_signature() { + assert_eq!( + NodeAnnouncement::decode(&[0u8; 20]), + Err(BoltError::Truncated { + expected: COMPACT_SIGNATURE_SIZE, + actual: 20, + }) + ); + } + + #[test] + fn decode_truncated_features_len() { + // Valid sig + 1 byte (need 2 for u16 len) + let encoded = sample_node_announcement().encode(); + let data = &encoded[..COMPACT_SIGNATURE_SIZE + 1]; + assert_eq!( + NodeAnnouncement::decode(data), + Err(BoltError::Truncated { + expected: 2, + actual: 1, + }) + ); + } + + #[test] + fn decode_truncated_features_data() { + // Valid sig + features_len=5 + only 2 bytes follow + let mut data = sample_node_announcement().encode()[..COMPACT_SIGNATURE_SIZE].to_vec(); + data.extend_from_slice(&[0x00, 0x05, 0xaa, 0xbb]); + assert_eq!( + NodeAnnouncement::decode(&data), + Err(BoltError::Truncated { + expected: 5, + actual: 2, + }) + ); + } + + #[test] + fn decode_truncated_timestamp() { + // sig(64) + features(2 len + 2 data = 4) + 1 byte of timestamp + let encoded = sample_node_announcement().encode(); + let data = &encoded[..COMPACT_SIGNATURE_SIZE + 4 + 1]; + assert_eq!( + NodeAnnouncement::decode(data), + Err(BoltError::Truncated { + expected: 4, + actual: 1, + }) + ); + } + + #[test] + fn decode_truncated_node_id() { + // sig(64) + features(4) + timestamp(4) + 10 bytes of node_id (need 33) + let encoded = sample_node_announcement().encode(); + let data = &encoded[..COMPACT_SIGNATURE_SIZE + 4 + 4 + 10]; + assert_eq!( + NodeAnnouncement::decode(data), + Err(BoltError::Truncated { + expected: PUBLIC_KEY_SIZE, + actual: 10, + }) + ); + } + + #[test] + fn decode_truncated_rgb_color() { + // sig(64) + features(4) + timestamp(4) + node_id(33) + 2 bytes of rgb (need 3) + let encoded = sample_node_announcement().encode(); + let data = &encoded[..COMPACT_SIGNATURE_SIZE + 4 + 4 + PUBLIC_KEY_SIZE + 2]; + assert_eq!( + NodeAnnouncement::decode(data), + Err(BoltError::Truncated { + expected: 3, + actual: 2, + }) + ); + } + + #[test] + fn decode_truncated_alias() { + // sig(64) + features(4) + timestamp(4) + node_id(33) + rgb(3) + 10 bytes of alias (need 32) + let encoded = sample_node_announcement().encode(); + let data = &encoded[..COMPACT_SIGNATURE_SIZE + 4 + 4 + PUBLIC_KEY_SIZE + 3 + 10]; + assert_eq!( + NodeAnnouncement::decode(data), + Err(BoltError::Truncated { + expected: 32, + actual: 10, + }) + ); + } + + #[test] + fn decode_truncated_addresses_len() { + // All fields up to and including alias + 1 byte (need 2 for addrlen) + let encoded = sample_node_announcement().encode(); + let data = &encoded[..COMPACT_SIGNATURE_SIZE + 4 + 4 + PUBLIC_KEY_SIZE + 3 + 32 + 1]; + assert_eq!( + NodeAnnouncement::decode(data), + Err(BoltError::Truncated { + expected: 2, + actual: 1, + }) + ); + } + + #[test] + fn decode_truncated_addresses_data() { + // All fixed fields valid, addrlen declares 5 but only 2 bytes follow + let encoded = sample_node_announcement().encode(); + let prefix_end = COMPACT_SIGNATURE_SIZE + 4 + 4 + PUBLIC_KEY_SIZE + 3 + 32; + let mut data = encoded[..prefix_end].to_vec(); + data.extend_from_slice(&[0x00, 0x05, 0xaa, 0xbb]); + assert_eq!( + NodeAnnouncement::decode(&data), + Err(BoltError::Truncated { + expected: 5, + actual: 2, + }) + ); + } + + #[test] + fn decode_invalid_signature() { + let mut encoded = sample_node_announcement().encode(); + // r and s are both above curve order. + let bad_sig = [0xff; COMPACT_SIGNATURE_SIZE]; + encoded[..COMPACT_SIGNATURE_SIZE].copy_from_slice(&bad_sig); + assert_eq!( + NodeAnnouncement::decode(&encoded), + Err(BoltError::InvalidSignature(bad_sig)) + ); + } + + #[test] + fn decode_invalid_node_id() { + let mut encoded = sample_node_announcement().encode(); + // All-zero bytes are not a valid compressed pubkey. + let bad_pubkey = [0u8; PUBLIC_KEY_SIZE]; + let node_id_offset = COMPACT_SIGNATURE_SIZE + 4 + 4; + encoded[node_id_offset..node_id_offset + PUBLIC_KEY_SIZE].copy_from_slice(&bad_pubkey); + assert_eq!( + NodeAnnouncement::decode(&encoded), + Err(BoltError::InvalidPublicKey(bad_pubkey)) + ); + } +} From a0bd0d665aa0014f80b8f0d61bbb2209030b34b8 Mon Sep 17 00:00:00 2001 From: Matt Morehouse Date: Thu, 14 May 2026 20:13:01 -0500 Subject: [PATCH 2/2] smite: add sign and verify methods to NodeAnnouncement These methods will be needed to correctly sign node_announcement messages we create and to verify signatures provided by a target. --- smite/src/bolt.rs | 7 +- smite/src/bolt/node_announcement.rs | 127 +++++++++++++++++++++++----- 2 files changed, 111 insertions(+), 23 deletions(-) diff --git a/smite/src/bolt.rs b/smite/src/bolt.rs index c5638d7..dcb9d29 100644 --- a/smite/src/bolt.rs +++ b/smite/src/bolt.rs @@ -794,7 +794,7 @@ mod tests { let secp = Secp256k1::new(); let sk = SecretKey::from_slice(&[0x11; 32]).expect("valid secret"); - NodeAnnouncement { + let mut na = NodeAnnouncement { signature: Signature::from_compact(&[0u8; 64]).unwrap(), features: vec![0x01, 0x02], timestamp: 1_700_000_000, @@ -802,7 +802,10 @@ mod tests { rgb_color: [0xaa, 0xbb, 0xcc], alias: [0x42; 32], addresses: vec![0x01, 0x7f, 0x00, 0x00, 0x01, 0x23, 0x45], - } + extra: vec![0x01, 0x02, 0x03], + }; + na.sign(&sk); + na } #[test] diff --git a/smite/src/bolt/node_announcement.rs b/smite/src/bolt/node_announcement.rs index 9167c66..4ffb3b4 100644 --- a/smite/src/bolt/node_announcement.rs +++ b/smite/src/bolt/node_announcement.rs @@ -2,8 +2,9 @@ use super::BoltError; use super::wire::WireFormat; -use bitcoin::secp256k1::PublicKey; +use bitcoin::hashes::{Hash, sha256d}; use bitcoin::secp256k1::ecdsa::Signature; +use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; /// BOLT 7 `node_announcement` message (type 257). /// @@ -25,6 +26,9 @@ pub struct NodeAnnouncement { pub alias: [u8; 32], /// Network address descriptors. pub addresses: Vec, + /// Trailing bytes. Per BOLT 7 the signature covers future fields appended + /// to the message, so we need to preserve them even if we can't parse them. + pub extra: Vec, } impl NodeAnnouncement { @@ -33,15 +37,44 @@ impl NodeAnnouncement { pub fn encode(&self) -> Vec { let mut out = Vec::new(); self.signature.write(&mut out); - self.features.write(&mut out); - self.timestamp.write(&mut out); - self.node_id.write(&mut out); - self.rgb_color.write(&mut out); - self.alias.write(&mut out); - self.addresses.write(&mut out); + self.write_body(&mut out); out } + /// Computes the BOLT 7 signature over the post-signature body and writes it + /// into `self.signature`. + pub fn sign(&mut self, sk: &SecretKey) { + let secp = Secp256k1::new(); + let mut body = Vec::new(); + self.write_body(&mut body); + let digest = secp256k1::Message::from_digest(sha256d::Hash::hash(&body).to_byte_array()); + self.signature = secp.sign_ecdsa(&digest, sk); + } + + /// Verifies `self.signature` against the embedded `node_id` per BOLT 7. + /// Returns `true` if the signature is valid. + #[must_use] + pub fn verify(&self) -> bool { + let secp = Secp256k1::new(); + let mut body = Vec::new(); + self.write_body(&mut body); + let digest = secp256k1::Message::from_digest(sha256d::Hash::hash(&body).to_byte_array()); + secp.verify_ecdsa(&digest, &self.signature, &self.node_id) + .is_ok() + } + + /// Writes fields after the signature, in spec order. These are the fields + /// that are signed by `sign` and verified by `verify`. + fn write_body(&self, out: &mut Vec) { + self.features.write(out); + self.timestamp.write(out); + self.node_id.write(out); + self.rgb_color.write(out); + self.alias.write(out); + self.addresses.write(out); + out.extend_from_slice(&self.extra); + } + /// Decodes from wire format (without message type prefix). /// /// # Errors @@ -60,6 +93,7 @@ impl NodeAnnouncement { let rgb_color = WireFormat::read(&mut cursor)?; let alias = WireFormat::read(&mut cursor)?; let addresses = WireFormat::read(&mut cursor)?; + let extra = cursor.to_vec(); Ok(Self { signature, @@ -69,6 +103,7 @@ impl NodeAnnouncement { rgb_color, alias, addresses, + extra, }) } } @@ -81,11 +116,11 @@ mod tests { use bitcoin::secp256k1::{Secp256k1, SecretKey}; /// Valid `NodeAnnouncement` for testing. - fn sample_node_announcement() -> NodeAnnouncement { + fn sample_node_announcement(extra: &[u8]) -> NodeAnnouncement { let secp = Secp256k1::new(); let sk = SecretKey::from_slice(&[0x11; 32]).expect("valid secret"); - NodeAnnouncement { + let mut na = NodeAnnouncement { signature: Signature::from_compact(&[0u8; 64]).unwrap(), features: vec![0x01, 0x02], timestamp: 1_700_000_000, @@ -93,17 +128,58 @@ mod tests { rgb_color: [0xaa, 0xbb, 0xcc], alias: [0x42; 32], addresses: vec![0x01, 0x7f, 0x00, 0x00, 0x01, 0x23, 0x45], - } + extra: extra.to_vec(), + }; + na.sign(&sk); + na } #[test] fn roundtrip() { - let original = sample_node_announcement(); + let original = sample_node_announcement(&[]); + let encoded = original.encode(); + let decoded = NodeAnnouncement::decode(&encoded).unwrap(); + assert_eq!(original, decoded); + } + + #[test] + fn roundtrip_with_extra_bytes() { + let original = sample_node_announcement(&[0xde, 0xad, 0xbe, 0xef]); let encoded = original.encode(); let decoded = NodeAnnouncement::decode(&encoded).unwrap(); assert_eq!(original, decoded); } + #[test] + fn verify_succeeds_after_sign() { + let original = sample_node_announcement(&[]); + assert!(original.verify()); + } + + #[test] + fn verify_fails_on_tampered_body() { + let mut na = sample_node_announcement(&[]); + na.timestamp = na.timestamp.wrapping_add(1); + assert!(!na.verify()); + } + + #[test] + fn verify_fails_on_invalid_signature() { + // Overwrite a valid sig with a structurally-parseable but + // cryptographically-invalid one (r = s = 0). + let mut na = sample_node_announcement(&[]); + na.signature = Signature::from_compact(&[0u8; 64]).unwrap(); + assert!(!na.verify()); + } + + #[test] + fn verify_covers_extra() { + let mut na = sample_node_announcement(&[0xde, 0xad, 0xbe, 0xef]); + assert!(na.verify()); + na.extra[0] ^= 0xff; + assert!(!na.verify()); + } + #[test] fn encode_size_with_empty_variable_fields() { let secp = Secp256k1::new(); @@ -116,6 +192,7 @@ mod tests { rgb_color: [0; 3], alias: [0; 32], addresses: vec![], + extra: Vec::new(), }; // 64 (sig) + 2 (flen=0) + 4 (timestamp) + 33 (node_id) + 3 (rgb) + 32 (alias) + 2 (addrlen=0) = 140 assert_eq!(na.encode().len(), 140); @@ -135,7 +212,7 @@ mod tests { #[test] fn decode_truncated_features_len() { // Valid sig + 1 byte (need 2 for u16 len) - let encoded = sample_node_announcement().encode(); + let encoded = sample_node_announcement(&[]).encode(); let data = &encoded[..COMPACT_SIGNATURE_SIZE + 1]; assert_eq!( NodeAnnouncement::decode(data), @@ -149,7 +226,7 @@ mod tests { #[test] fn decode_truncated_features_data() { // Valid sig + features_len=5 + only 2 bytes follow - let mut data = sample_node_announcement().encode()[..COMPACT_SIGNATURE_SIZE].to_vec(); + let mut data = sample_node_announcement(&[]).encode()[..COMPACT_SIGNATURE_SIZE].to_vec(); data.extend_from_slice(&[0x00, 0x05, 0xaa, 0xbb]); assert_eq!( NodeAnnouncement::decode(&data), @@ -163,7 +240,7 @@ mod tests { #[test] fn decode_truncated_timestamp() { // sig(64) + features(2 len + 2 data = 4) + 1 byte of timestamp - let encoded = sample_node_announcement().encode(); + let encoded = sample_node_announcement(&[]).encode(); let data = &encoded[..COMPACT_SIGNATURE_SIZE + 4 + 1]; assert_eq!( NodeAnnouncement::decode(data), @@ -177,7 +254,7 @@ mod tests { #[test] fn decode_truncated_node_id() { // sig(64) + features(4) + timestamp(4) + 10 bytes of node_id (need 33) - let encoded = sample_node_announcement().encode(); + let encoded = sample_node_announcement(&[]).encode(); let data = &encoded[..COMPACT_SIGNATURE_SIZE + 4 + 4 + 10]; assert_eq!( NodeAnnouncement::decode(data), @@ -191,7 +268,7 @@ mod tests { #[test] fn decode_truncated_rgb_color() { // sig(64) + features(4) + timestamp(4) + node_id(33) + 2 bytes of rgb (need 3) - let encoded = sample_node_announcement().encode(); + let encoded = sample_node_announcement(&[]).encode(); let data = &encoded[..COMPACT_SIGNATURE_SIZE + 4 + 4 + PUBLIC_KEY_SIZE + 2]; assert_eq!( NodeAnnouncement::decode(data), @@ -205,7 +282,7 @@ mod tests { #[test] fn decode_truncated_alias() { // sig(64) + features(4) + timestamp(4) + node_id(33) + rgb(3) + 10 bytes of alias (need 32) - let encoded = sample_node_announcement().encode(); + let encoded = sample_node_announcement(&[]).encode(); let data = &encoded[..COMPACT_SIGNATURE_SIZE + 4 + 4 + PUBLIC_KEY_SIZE + 3 + 10]; assert_eq!( NodeAnnouncement::decode(data), @@ -219,7 +296,7 @@ mod tests { #[test] fn decode_truncated_addresses_len() { // All fields up to and including alias + 1 byte (need 2 for addrlen) - let encoded = sample_node_announcement().encode(); + let encoded = sample_node_announcement(&[]).encode(); let data = &encoded[..COMPACT_SIGNATURE_SIZE + 4 + 4 + PUBLIC_KEY_SIZE + 3 + 32 + 1]; assert_eq!( NodeAnnouncement::decode(data), @@ -233,7 +310,7 @@ mod tests { #[test] fn decode_truncated_addresses_data() { // All fixed fields valid, addrlen declares 5 but only 2 bytes follow - let encoded = sample_node_announcement().encode(); + let encoded = sample_node_announcement(&[]).encode(); let prefix_end = COMPACT_SIGNATURE_SIZE + 4 + 4 + PUBLIC_KEY_SIZE + 3 + 32; let mut data = encoded[..prefix_end].to_vec(); data.extend_from_slice(&[0x00, 0x05, 0xaa, 0xbb]); @@ -248,7 +325,7 @@ mod tests { #[test] fn decode_invalid_signature() { - let mut encoded = sample_node_announcement().encode(); + let mut encoded = sample_node_announcement(&[]).encode(); // r and s are both above curve order. let bad_sig = [0xff; COMPACT_SIGNATURE_SIZE]; encoded[..COMPACT_SIGNATURE_SIZE].copy_from_slice(&bad_sig); @@ -260,7 +337,7 @@ mod tests { #[test] fn decode_invalid_node_id() { - let mut encoded = sample_node_announcement().encode(); + let mut encoded = sample_node_announcement(&[]).encode(); // All-zero bytes are not a valid compressed pubkey. let bad_pubkey = [0u8; PUBLIC_KEY_SIZE]; let node_id_offset = COMPACT_SIGNATURE_SIZE + 4 + 4; @@ -270,4 +347,12 @@ mod tests { Err(BoltError::InvalidPublicKey(bad_pubkey)) ); } + + #[test] + fn decode_captures_trailing_bytes() { + let mut encoded = sample_node_announcement(&[]).encode(); + encoded.extend_from_slice(&[0x42, 0x42]); + let decoded = NodeAnnouncement::decode(&encoded).unwrap(); + assert_eq!(decoded.extra, vec![0x42, 0x42]); + } }