From 244faa7a70af5325d405d3453712d207a07b7c93 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Tue, 15 Sep 2020 10:01:27 -0700 Subject: [PATCH 01/45] Squash changes from 494 into a single commit This will make future changes that stabilize this work easier to review and merge. Co-authored-by: Arik Sosman Co-authored-by: Julian Knutsen --- fuzz/src/full_stack.rs | 20 +- lightning-net-tokio/src/lib.rs | 18 +- lightning/src/ln/mod.rs | 2 +- lightning/src/ln/peers/chacha.rs | 40 ++ lightning/src/ln/peers/conduit.rs | 297 ++++++++++++ .../ln/{peer_handler.rs => peers/handler.rs} | 458 ++++++++++-------- lightning/src/ln/peers/handshake/acts.rs | 42 ++ lightning/src/ln/peers/handshake/hash.rs | 25 + lightning/src/ln/peers/handshake/mod.rs | 394 +++++++++++++++ lightning/src/ln/peers/handshake/states.rs | 30 ++ lightning/src/ln/peers/handshake/tests.rs | 39 ++ lightning/src/ln/peers/hkdf.rs | 18 + lightning/src/ln/peers/mod.rs | 10 + 13 files changed, 1174 insertions(+), 219 deletions(-) create mode 100644 lightning/src/ln/peers/chacha.rs create mode 100644 lightning/src/ln/peers/conduit.rs rename lightning/src/ln/{peer_handler.rs => peers/handler.rs} (82%) create mode 100644 lightning/src/ln/peers/handshake/acts.rs create mode 100644 lightning/src/ln/peers/handshake/hash.rs create mode 100644 lightning/src/ln/peers/handshake/mod.rs create mode 100644 lightning/src/ln/peers/handshake/states.rs create mode 100644 lightning/src/ln/peers/handshake/tests.rs create mode 100644 lightning/src/ln/peers/hkdf.rs create mode 100644 lightning/src/ln/peers/mod.rs diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 1ed17b9ea3f..f6a07f351a4 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -31,7 +31,7 @@ use lightning::chain::chainmonitor; use lightning::chain::transaction::OutPoint; use lightning::chain::keysinterface::{InMemoryChannelKeys, KeysInterface}; use lightning::ln::channelmanager::{ChannelManager, PaymentHash, PaymentPreimage, PaymentSecret}; -use lightning::ln::peer_handler::{MessageHandler,PeerManager,SocketDescriptor}; +use lightning::ln::peers::handler::{MessageHandler,PeerManager,SocketDescriptor}; use lightning::routing::router::get_route; use lightning::routing::network_graph::NetGraphMsgHandler; use lightning::util::events::{EventsProvider,Event}; @@ -891,15 +891,15 @@ mod tests { super::do_test(&::hex::decode("00000000000000000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000000000300320003000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000030012000a0300000000000000000000000000000003001a00100002200000022000030000000000000000000000000000000300120141030000000000000000000000000000000300fe00207500000000000000000000000000000000000000000000000000000000000000ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679000000000000c35000000000000000000000000000000222ffffffffffffffff00000000000002220000000000000000000000fd000601e3030000000000000000000000000000000000000000000000000000000000000001030000000000000000000000000000000000000000000000000000000000000002030000000000000000000000000000000000000000000000000000000000000003030000000000000000000000000000000000000000000000000000000000000004030053030000000000000000000000000000000000000000000000000000000000000005030000000000000000000000000000000000000000000000000000000000000000010300000000000000000000000000000000fd00fd00fd0300120084030000000000000000000000000000000300940022ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb1819096793d00000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001030000000000000000000000000000000c005e020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0150c3000000000000220020ae00000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c000003001200430300000000000000000000000000000003005300243d0000000000000000000000000000000000000000000000000000000000000003010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000010301320003000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000030142000302000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030112000a0100000000000000000000000000000003011a0010000220000002200001000000000000000000000000000000050103020000000000000000000000000000000000000000000000000000000000000000c3500003e800fd00fd0301120110010000000000000000000000000000000301ff00210000000000000000000000000000000000000000000000000000000000000e02000000000000001a00000000004c4b4000000000000003e800000000000003e80000000203f00005030000000000000000000000000000000000000000000000000000000000000100030000000000000000000000000000000000000000000000000000000000000200030000000000000000000000000000000000000000000000000000000000000300030000000000000000000000000000000000000000000000000000000000000400030000000000000000000000000000000000000000000000000000000000000500030000000000000000000000000000000301210000000000000000000000000000000000010000000000000000000000000000000a03011200620100000000000000000000000000000003017200233900000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100010000000000000000000000000000000b030112004301000000000000000000000000000000030153002439000000000000000000000000000000000000000000000000000000000000000301000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80ff0000000000000000000000000000000000000000000000000000000000000000000121000300000000000000000000000000000000000000000000000000000000000005550000000e000001000000000000000003e80000007b0000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff95000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000003100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000703011200640100000000000000000000000000000003017400843900000000000000000000000000000000000000000000000000000000000000f100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112004a0100000000000000000000000000000003015a008239000000000000000000000000000000000000000000000000000000000000000000000000000000ff008888888888888888888888888888888888888888888888888888888888880100000000000000000000000000000003011200640100000000000000000000000000000003017400843900000000000000000000000000000000000000000000000000000000000000fd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000010000000000000000000000000000000301120063010000000000000000000000000000000301730085390000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000303000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000010000000000003e80ff0000000000000000000000000000000000000000000000000000000000000000000121000300000000000000000000000000000000000000000000000000000000000005550000000e000001000000000000000003e80000007b0000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff95000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200630300000000000000000000000000000003007300853d0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000303000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d00000000000000000000000000000000000000000000000000000000000000c200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030400000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000703011200640100000000000000000000000000000003017400843900000000000000000000000000000000000000000000000000000000000000fc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112002c0100000000000000000000000000000003013c00833900000000000000000000000000000000000000000000000000000000000000000000000000000100000100000000000000000000000000000003011200640100000000000000000000000000000003017400843900000000000000000000000000000000000000000000000000000000000000fb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000001000000000000000000000000000000030112006301000000000000000000000000000000030173008539000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000030500000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000703001200630300000000000000000000000000000003007300853d0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000305000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000003300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000300000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d00000000000000000000000000000000000000000000000000000000000000000000000000000200000000000b0838ff0000000000000000000000000000000000000000000000000000000000000000000121000300000000000000000000000000000000000000000000000000000000000005550000000e0000010000000000000003e8000000007b0000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff95000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200a4030000000000000000000000000000000300b400843d000000000000000000000000000000000000000000000000000000000000007b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010001c8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000003060000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000070c007d02000000013900000000000000000000000000000000000000000000000000000000000000000000000000000080020001000000000000220020bb000000000000000000000000000000000000000000000000000000000000006cc10000000000001600142b000000000000000000000000000000000000000500002000fd00fd0c005e0200000001a100000000000000000000000000000000000000000000000000000000000000000000000000000000014f00000000000000220020f600000000000000000000000000000000000000000000000000000000000000000000000c00000c000000fd0c00000c00000c000007").unwrap(), &(Arc::clone(&logger) as Arc)); let log_entries = logger.lines.lock().unwrap(); - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendAcceptChannel event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000000 for channel ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679".to_string())), Some(&1)); // 1 - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendFundingSigned event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000000 for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1)); // 2 - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendFundingLocked event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000000 for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1)); // 3 - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendFundingLocked event in peer_handler for node 030200000000000000000000000000000000000000000000000000000000000000 for channel 3900000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1)); // 4 - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendRevokeAndACK event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000000 for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&4)); // 5 - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000000 with 0 adds, 0 fulfills, 0 fails for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&3)); // 6 - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030200000000000000000000000000000000000000000000000000000000000000 with 1 adds, 0 fulfills, 0 fails for channel 3900000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&3)); // 7 - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000000 with 0 adds, 1 fulfills, 0 fails for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1)); // 8 - assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000000 with 0 adds, 0 fulfills, 1 fails for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&2)); // 9 + assert_eq!(log_entries.get(&("lightning::ln::peers::handler".to_string(), "Handling SendAcceptChannel event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000000 for channel ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679".to_string())), Some(&1)); // 1 + assert_eq!(log_entries.get(&("lightning::ln::peers::handler".to_string(), "Handling SendFundingSigned event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000000 for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1)); // 2 + assert_eq!(log_entries.get(&("lightning::ln::peers::handler".to_string(), "Handling SendFundingLocked event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000000 for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1)); // 3 + assert_eq!(log_entries.get(&("lightning::ln::peers::handler".to_string(), "Handling SendFundingLocked event in peer_handler for node 030200000000000000000000000000000000000000000000000000000000000000 for channel 3900000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1)); // 4 + assert_eq!(log_entries.get(&("lightning::ln::peers::handler".to_string(), "Handling SendRevokeAndACK event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000000 for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&4)); // 5 + assert_eq!(log_entries.get(&("lightning::ln::peers::handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000000 with 0 adds, 0 fulfills, 0 fails for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&3)); // 6 + assert_eq!(log_entries.get(&("lightning::ln::peers::handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030200000000000000000000000000000000000000000000000000000000000000 with 1 adds, 0 fulfills, 0 fails for channel 3900000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&3)); // 7 + assert_eq!(log_entries.get(&("lightning::ln::peers::handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000000 with 0 adds, 1 fulfills, 0 fails for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1)); // 8 + assert_eq!(log_entries.get(&("lightning::ln::peers::handler".to_string(), "Handling UpdateHTLCs event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000000 with 0 adds, 0 fulfills, 1 fails for channel 3d00000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&2)); // 9 assert_eq!(log_entries.get(&("lightning::chain::channelmonitor".to_string(), "Input spending counterparty commitment tx (00000000000000000000000000000000000000000000000000000000000000a1:0) in 0000000000000000000000000000000000000000000000000000000000000018 resolves outbound HTLC with payment hash ff00000000000000000000000000000000000000000000000000000000000000 with timeout".to_string())), Some(&1)); // 10 } } diff --git a/lightning-net-tokio/src/lib.rs b/lightning-net-tokio/src/lib.rs index e84ee76229f..8e02b11e719 100644 --- a/lightning-net-tokio/src/lib.rs +++ b/lightning-net-tokio/src/lib.rs @@ -38,7 +38,7 @@ //! type ChainFilter = dyn lightning::chain::Filter; //! type ChainMonitor = lightning::chain::chainmonitor::ChainMonitor, Arc, Arc, Arc>; //! type ChannelManager = lightning::ln::channelmanager::SimpleArcChannelManager; -//! type PeerManager = lightning::ln::peer_handler::SimpleArcPeerManager; +//! type PeerManager = lightning::ln::peers::handler::SimpleArcPeerManager; //! //! // Connect to node with pubkey their_node_id at addr: //! async fn connect_to_node(peer_manager: PeerManager, chain_monitor: Arc, channel_manager: ChannelManager, their_node_id: PublicKey, addr: SocketAddr) { @@ -78,8 +78,8 @@ use tokio::{io, time}; use tokio::sync::mpsc; use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}; -use lightning::ln::peer_handler; -use lightning::ln::peer_handler::SocketDescriptor as LnSocketTrait; +use lightning::ln::peers::handler; +use lightning::ln::peers::handler::SocketDescriptor as LnSocketTrait; use lightning::ln::msgs::{ChannelMessageHandler, RoutingMessageHandler}; use lightning::util::logger::Logger; @@ -134,7 +134,7 @@ impl Connection { _ => panic!() } } - async fn schedule_read(peer_manager: Arc, Arc, Arc>>, us: Arc>, mut reader: io::ReadHalf, mut read_wake_receiver: mpsc::Receiver<()>, mut write_avail_receiver: mpsc::Receiver<()>) where + async fn schedule_read(peer_manager: Arc, Arc, Arc>>, us: Arc>, mut reader: io::ReadHalf, mut read_wake_receiver: mpsc::Receiver<()>, mut write_avail_receiver: mpsc::Receiver<()>) where CMH: ChannelMessageHandler + 'static, RMH: RoutingMessageHandler + 'static, L: Logger + 'static + ?Sized { @@ -247,7 +247,7 @@ impl Connection { /// not need to poll the provided future in order to make progress. /// /// See the module-level documentation for how to handle the event_notify mpsc::Sender. -pub fn setup_inbound(peer_manager: Arc, Arc, Arc>>, event_notify: mpsc::Sender<()>, stream: TcpStream) -> impl std::future::Future where +pub fn setup_inbound(peer_manager: Arc, Arc, Arc>>, event_notify: mpsc::Sender<()>, stream: TcpStream) -> impl std::future::Future where CMH: ChannelMessageHandler + 'static, RMH: RoutingMessageHandler + 'static, L: Logger + 'static + ?Sized { @@ -289,7 +289,7 @@ pub fn setup_inbound(peer_manager: Arc(peer_manager: Arc, Arc, Arc>>, event_notify: mpsc::Sender<()>, their_node_id: PublicKey, stream: TcpStream) -> impl std::future::Future where +pub fn setup_outbound(peer_manager: Arc, Arc, Arc>>, event_notify: mpsc::Sender<()>, their_node_id: PublicKey, stream: TcpStream) -> impl std::future::Future where CMH: ChannelMessageHandler + 'static, RMH: RoutingMessageHandler + 'static, L: Logger + 'static + ?Sized { @@ -361,7 +361,7 @@ pub fn setup_outbound(peer_manager: Arc(peer_manager: Arc, Arc, Arc>>, event_notify: mpsc::Sender<()>, their_node_id: PublicKey, addr: SocketAddr) -> Option> where +pub async fn connect_outbound(peer_manager: Arc, Arc, Arc>>, event_notify: mpsc::Sender<()>, their_node_id: PublicKey, addr: SocketAddr) -> Option> where CMH: ChannelMessageHandler + 'static, RMH: RoutingMessageHandler + 'static, L: Logger + 'static + ?Sized { @@ -412,7 +412,7 @@ impl SocketDescriptor { Self { conn, id } } } -impl peer_handler::SocketDescriptor for SocketDescriptor { +impl handler::SocketDescriptor for SocketDescriptor { fn send_data(&mut self, data: &[u8], resume_read: bool) -> usize { // To send data, we take a lock on our Connection to access the WriteHalf of the TcpStream, // writing to it if there's room in the kernel buffer, or otherwise create a new Waker with @@ -504,7 +504,7 @@ impl Hash for SocketDescriptor { mod tests { use lightning::ln::features::*; use lightning::ln::msgs::*; - use lightning::ln::peer_handler::{MessageHandler, PeerManager}; + use lightning::ln::peers::handler::{MessageHandler, PeerManager}; use lightning::util::events::*; use bitcoin::secp256k1::{Secp256k1, SecretKey, PublicKey}; diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index cd959a74dc5..14e9cb86f48 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -20,7 +20,7 @@ pub mod channelmanager; pub mod msgs; -pub mod peer_handler; +pub mod peers; pub mod chan_utils; pub mod features; pub(crate) mod onchaintx; diff --git a/lightning/src/ln/peers/chacha.rs b/lightning/src/ln/peers/chacha.rs new file mode 100644 index 00000000000..4e8d948706f --- /dev/null +++ b/lightning/src/ln/peers/chacha.rs @@ -0,0 +1,40 @@ +use util::byte_utils; +use util::chacha20poly1305rfc::ChaCha20Poly1305RFC; + +pub const TAG_SIZE: usize = 16; + +pub fn encrypt(key: &[u8], nonce: u64, associated_data: &[u8], plaintext: &[u8]) -> Vec { + let mut nonce_bytes = [0; 12]; + nonce_bytes[4..].copy_from_slice(&byte_utils::le64_to_array(nonce)); + + let mut chacha = ChaCha20Poly1305RFC::new(key, &nonce_bytes, associated_data); + let mut ciphertext = vec![0u8; plaintext.len()]; + let mut authentication_tag = [0u8; 16]; + chacha.encrypt(plaintext, &mut ciphertext, &mut authentication_tag); + + let mut tagged_ciphertext = ciphertext; + tagged_ciphertext.extend_from_slice(&authentication_tag); + tagged_ciphertext +} + +pub fn decrypt(key: &[u8], nonce: u64, associated_data: &[u8], tagged_ciphertext: &[u8]) -> Result, String> { + let mut nonce_bytes = [0; 12]; + nonce_bytes[4..].copy_from_slice(&byte_utils::le64_to_array(nonce)); + + let length = tagged_ciphertext.len(); + if length < 16 { + return Err("ciphertext cannot be shorter than tag length of 16 bytes".to_string()); + } + let end_index = length - 16; + let ciphertext = &tagged_ciphertext[0..end_index]; + let authentication_tag = &tagged_ciphertext[end_index..length]; + + let mut chacha = ChaCha20Poly1305RFC::new(key, &nonce_bytes, associated_data); + let mut plaintext = vec![0u8; length - 16]; + let success = chacha.decrypt(ciphertext, &mut plaintext, authentication_tag); + if success { + Ok(plaintext.to_vec()) + } else { + Err("invalid hmac".to_string()) + } +} diff --git a/lightning/src/ln/peers/conduit.rs b/lightning/src/ln/peers/conduit.rs new file mode 100644 index 00000000000..74e420b192a --- /dev/null +++ b/lightning/src/ln/peers/conduit.rs @@ -0,0 +1,297 @@ +//! Handles all over the wire message encryption and decryption upon handshake completion. + +use ln::peers::{chacha, hkdf}; +use util::byte_utils; + +pub(super) type SymmetricKey = [u8; 32]; + +const MESSAGE_LENGTH_HEADER_SIZE: usize = 2; +const TAGGED_MESSAGE_LENGTH_HEADER_SIZE: usize = MESSAGE_LENGTH_HEADER_SIZE + chacha::TAG_SIZE; + +const KEY_ROTATION_INDEX: u32 = 1000; + +/// Returned after a successful handshake to encrypt and decrypt communication with peer nodes. +/// It should not normally be manually instantiated. +/// Automatically handles key rotation. +/// For decryption, it is recommended to call `decrypt_message_stream` for automatic buffering. +pub struct Conduit { + pub(super) encryptor: Encryptor, + pub(super) decryptor: Decryptor + +} + +pub(super) struct Encryptor { + sending_key: SymmetricKey, + sending_chaining_key: SymmetricKey, + sending_nonce: u32, +} + +pub(super) struct Decryptor { + receiving_key: SymmetricKey, + receiving_chaining_key: SymmetricKey, + receiving_nonce: u32, + + pending_message_length: Option, + read_buffer: Option>, +} + +impl Iterator for Decryptor { + type Item = Vec; + + fn next(&mut self) -> Option { + self.decrypt_single_message(None) + } +} + +impl Conduit { + /// Instantiate a new Conduit with specified sending and receiving keys + pub fn new(sending_key: SymmetricKey, receiving_key: SymmetricKey, chaining_key: SymmetricKey) -> Self { + Conduit { + encryptor: Encryptor { + sending_key, + sending_chaining_key: chaining_key, + sending_nonce: 0 + }, + decryptor: Decryptor { + receiving_key, + receiving_chaining_key: chaining_key, + receiving_nonce: 0, + read_buffer: None, + pending_message_length: None + } + } + } + + /// Encrypt data to be sent to peer + pub fn encrypt(&mut self, buffer: &[u8]) -> Vec { + self.encryptor.encrypt(buffer) + } + + pub(super) fn read(&mut self, data: &[u8]) { + self.decryptor.read(data) + } + + /// Decrypt a single message. If data containing more than one message has been received, + /// only the first message will be returned, and the rest stored in the internal buffer. + /// If a message pending in the buffer still hasn't been decrypted, that message will be + /// returned in lieu of anything new, even if new data is provided. + pub fn decrypt_single_message(&mut self, new_data: Option<&[u8]>) -> Option> { + self.decryptor.decrypt_single_message(new_data) + } + + /// Decrypt a message from the beginning of the provided buffer. Returns the consumed number of bytes. + fn decrypt(&mut self, buffer: &[u8]) -> (Option>, usize) { + self.decryptor.decrypt(buffer) + } + + fn increment_sending_nonce(&mut self) { + self.encryptor.increment_nonce() + } + + fn increment_receiving_nonce(&mut self) { + self.decryptor.increment_nonce() + } + + fn increment_nonce(nonce: &mut u32, chaining_key: &mut SymmetricKey, key: &mut SymmetricKey) { + *nonce += 1; + if *nonce == KEY_ROTATION_INDEX { + Self::rotate_key(chaining_key, key); + *nonce = 0; + } + } + + fn rotate_key(chaining_key: &mut SymmetricKey, key: &mut SymmetricKey) { + let (new_chaining_key, new_key) = hkdf::derive(chaining_key, key); + chaining_key.copy_from_slice(&new_chaining_key); + key.copy_from_slice(&new_key); + } +} + +impl Encryptor { + pub(super) fn encrypt(&mut self, buffer: &[u8]) -> Vec { + let length = buffer.len() as u16; + let length_bytes = byte_utils::be16_to_array(length); + + let mut ciphertext = vec![0u8; TAGGED_MESSAGE_LENGTH_HEADER_SIZE + length as usize + chacha::TAG_SIZE]; + + ciphertext[0..TAGGED_MESSAGE_LENGTH_HEADER_SIZE].copy_from_slice(&chacha::encrypt(&self.sending_key, self.sending_nonce as u64, &[0; 0], &length_bytes)); + self.increment_nonce(); + + ciphertext[TAGGED_MESSAGE_LENGTH_HEADER_SIZE..].copy_from_slice(&chacha::encrypt(&self.sending_key, self.sending_nonce as u64, &[0; 0], buffer)); + self.increment_nonce(); + + ciphertext + } + + fn increment_nonce(&mut self) { + Conduit::increment_nonce(&mut self.sending_nonce, &mut self.sending_chaining_key, &mut self.sending_key); + } +} + +impl Decryptor { + pub(super) fn read(&mut self, data: &[u8]) { + let read_buffer = self.read_buffer.get_or_insert(Vec::new()); + read_buffer.extend_from_slice(data); + } + + /// Decrypt a single message. If data containing more than one message has been received, + /// only the first message will be returned, and the rest stored in the internal buffer. + /// If a message pending in the buffer still hasn't been decrypted, that message will be + /// returned in lieu of anything new, even if new data is provided. + pub fn decrypt_single_message(&mut self, new_data: Option<&[u8]>) -> Option> { + let mut read_buffer = if let Some(buffer) = self.read_buffer.take() { + buffer + } else { + Vec::new() + }; + + if let Some(data) = new_data { + read_buffer.extend_from_slice(data); + } + + let (current_message, offset) = self.decrypt(&read_buffer[..]); + read_buffer.drain(..offset); // drain the read buffer + self.read_buffer = Some(read_buffer); // assign the new value to the built-in buffer + current_message + } + + fn decrypt(&mut self, buffer: &[u8]) -> (Option>, usize) { + let message_length = if let Some(length) = self.pending_message_length { + // we have already decrypted the header + length + } else { + if buffer.len() < TAGGED_MESSAGE_LENGTH_HEADER_SIZE { + // A message must be at least 18 bytes (2 for encrypted length, 16 for the tag) + return (None, 0); + } + + let encrypted_length = &buffer[0..TAGGED_MESSAGE_LENGTH_HEADER_SIZE]; + let mut length_bytes = [0u8; MESSAGE_LENGTH_HEADER_SIZE]; + length_bytes.copy_from_slice(&chacha::decrypt(&self.receiving_key, self.receiving_nonce as u64, &[0; 0], encrypted_length).unwrap()); + + self.increment_nonce(); + + // the message length + byte_utils::slice_to_be16(&length_bytes) as usize + }; + + let message_end_index = TAGGED_MESSAGE_LENGTH_HEADER_SIZE + message_length + chacha::TAG_SIZE; + + if buffer.len() < message_end_index { + self.pending_message_length = Some(message_length); + return (None, 0); + } + + self.pending_message_length = None; + + let encrypted_message = &buffer[TAGGED_MESSAGE_LENGTH_HEADER_SIZE..message_end_index]; + + let message = chacha::decrypt(&self.receiving_key, self.receiving_nonce as u64, &[0; 0], encrypted_message).unwrap(); + + self.increment_nonce(); + + (Some(message), message_end_index) + } + + fn increment_nonce(&mut self) { + Conduit::increment_nonce(&mut self.receiving_nonce, &mut self.receiving_chaining_key, &mut self.receiving_key); + } +} + +#[cfg(test)] +mod tests { + use hex; + + use ln::peers::conduit::Conduit; + + fn setup_peers() -> (Conduit, Conduit) { + let chaining_key_vec = hex::decode("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01").unwrap(); + let mut chaining_key = [0u8; 32]; + chaining_key.copy_from_slice(&chaining_key_vec); + + let sending_key_vec = hex::decode("969ab31b4d288cedf6218839b27a3e2140827047f2c0f01bf5c04435d43511a9").unwrap(); + let mut sending_key = [0u8; 32]; + sending_key.copy_from_slice(&sending_key_vec); + + let receiving_key_vec = hex::decode("bb9020b8965f4df047e07f955f3c4b88418984aadc5cdb35096b9ea8fa5c3442").unwrap(); + let mut receiving_key = [0u8; 32]; + receiving_key.copy_from_slice(&receiving_key_vec); + + let connected_peer = Conduit::new(sending_key, receiving_key, chaining_key); + let remote_peer = Conduit::new(receiving_key, sending_key, chaining_key); + + (connected_peer, remote_peer) + } + + #[test] + fn test_empty_message() { + let (mut connected_peer, mut remote_peer) = setup_peers(); + + let message: Vec = vec![]; + let encrypted_message = connected_peer.encrypt(&message); + assert_eq!(encrypted_message.len(), 2 + 16 + 16); + + let decrypted_message = remote_peer.decrypt_single_message(Some(&encrypted_message)).unwrap(); + assert_eq!(decrypted_message, vec![]); + } + + #[test] + fn test_nonce_chaining() { + let (mut connected_peer, mut remote_peer) = setup_peers(); + let message = hex::decode("68656c6c6f").unwrap(); + + let encrypted_message = connected_peer.encrypt(&message); + assert_eq!(encrypted_message, hex::decode("cf2b30ddf0cf3f80e7c35a6e6730b59fe802473180f396d88a8fb0db8cbcf25d2f214cf9ea1d95").unwrap()); + + // the second time the same message is encrypted, the ciphertext should be different + let encrypted_message = connected_peer.encrypt(&message); + assert_eq!(encrypted_message, hex::decode("72887022101f0b6753e0c7de21657d35a4cb2a1f5cde2650528bbc8f837d0f0d7ad833b1a256a1").unwrap()); + } + + #[test] + /// Based on RFC test vectors: https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#message-encryption-tests + fn test_key_rotation() { + let (mut connected_peer, mut remote_peer) = setup_peers(); + + let message = hex::decode("68656c6c6f").unwrap(); + let mut encrypted_messages: Vec> = Vec::new(); + + for _ in 0..1002 { + let encrypted_message = connected_peer.encrypt(&message); + encrypted_messages.push(encrypted_message); + } + + assert_eq!(encrypted_messages[500], hex::decode("178cb9d7387190fa34db9c2d50027d21793c9bc2d40b1e14dcf30ebeeeb220f48364f7a4c68bf8").unwrap()); + assert_eq!(encrypted_messages[501], hex::decode("1b186c57d44eb6de4c057c49940d79bb838a145cb528d6e8fd26dbe50a60ca2c104b56b60e45bd").unwrap()); + assert_eq!(encrypted_messages[1000], hex::decode("4a2f3cc3b5e78ddb83dcb426d9863d9d9a723b0337c89dd0b005d89f8d3c05c52b76b29b740f09").unwrap()); + assert_eq!(encrypted_messages[1001], hex::decode("2ecd8c8a5629d0d02ab457a0fdd0f7b90a192cd46be5ecb6ca570bfc5e268338b1a16cf4ef2d36").unwrap()); + } + + #[test] + fn test_decryption_buffering() { + let (mut connected_peer, mut remote_peer) = setup_peers(); + + let message = hex::decode("68656c6c6f").unwrap(); + let mut encrypted_messages: Vec> = Vec::new(); + + for _ in 0..1002 { + let encrypted_message = connected_peer.encrypt(&message); + encrypted_messages.push(encrypted_message); + } + + for _ in 0..501 { + // read two messages at once, filling buffer + let mut current_encrypted_message = encrypted_messages.remove(0); + let mut next_encrypted_message = encrypted_messages.remove(0); + current_encrypted_message.extend_from_slice(&next_encrypted_message); + let decrypted_message = remote_peer.decrypt_single_message(Some(¤t_encrypted_message)).unwrap(); + assert_eq!(decrypted_message, message); + } + + for _ in 0..501 { + // decrypt messages directly from buffer without adding to it + let decrypted_message = remote_peer.decrypt_single_message(None).unwrap(); + assert_eq!(decrypted_message, message); + } + } +} \ No newline at end of file diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peers/handler.rs similarity index 82% rename from lightning/src/ln/peer_handler.rs rename to lightning/src/ln/peers/handler.rs index edb2084c716..e9c2becd0fb 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peers/handler.rs @@ -39,6 +39,9 @@ use std::ops::Deref; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::sha256::HashEngine as Sha256Engine; use bitcoin::hashes::{HashEngine, Hash}; +use ln::peers::handshake::PeerHandshake; +use ln::peers::conduit::Conduit; +use ln::peers::handshake::acts::Act; /// Provides references to trait impls which handle different types of messages. pub struct MessageHandler where @@ -119,8 +122,66 @@ enum InitSyncTracker{ NodesSyncing(PublicKey), } +enum PeerState { + Authenticating(PeerHandshake), + Connected(Conduit), +} + +enum PeerDataProcessingDecision { + CompleteHandshake(bool, Option), + Continue, + Disconnect(PeerHandleError) +} + +impl PeerState { + fn is_ready_for_encryption(&self) -> bool { + match self { + &PeerState::Connected(_) => true, + _ => false + } + } + + fn process_peer_data(&mut self, data: &[u8], mutable_response_buffer: &mut LinkedList>) -> PeerDataProcessingDecision { + let mut conduit_option = None; + let mut decision_option = None; + + match self { + &mut PeerState::Authenticating(ref mut handshake) => { + let (next_act, conduit) = match handshake.process_act(data) { + Ok(act_result) => act_result, + Err(e) => { + return PeerDataProcessingDecision::Disconnect(PeerHandleError { no_connection_possible: false }); + } + }; + + let requires_response = next_act.is_some(); + if let Some(act) = next_act { + mutable_response_buffer.push_back(act.serialize()); + } + + let remote_pubkey_option = handshake.get_remote_pubkey(); + if let Some(conduit) = conduit { + conduit_option = Some(conduit); + decision_option = Some(PeerDataProcessingDecision::CompleteHandshake(requires_response, remote_pubkey_option)); + } + } + + &mut PeerState::Connected(ref mut conduit) => { + conduit.read(data); + } + }; + + if let (Some(conduit), Some(decision)) = (conduit_option, decision_option) { + *self = PeerState::Connected(conduit); + return decision; + } + + PeerDataProcessingDecision::Continue + } +} + struct Peer { - channel_encryptor: PeerChannelEncryptor, + encryptor: PeerState, outbound: bool, their_node_id: Option, their_features: Option, @@ -129,10 +190,6 @@ struct Peer { pending_outbound_buffer_first_msg_offset: usize, awaiting_write_event: bool, - pending_read_buffer: Vec, - pending_read_buffer_pos: usize, - pending_read_is_header: bool, - sync_status: InitSyncTracker, awaiting_pong: bool, @@ -279,7 +336,7 @@ impl PeerManager Vec { let peers = self.peers.lock().unwrap(); peers.peers.values().filter_map(|p| { - if !p.channel_encryptor.is_ready_for_encryption() || p.their_features.is_none() { + if !p.encryptor.is_ready_for_encryption() || p.their_features.is_none() { return None; } p.their_node_id @@ -308,25 +365,21 @@ impl PeerManager Result, PeerHandleError> { - let mut peer_encryptor = PeerChannelEncryptor::new_outbound(their_node_id.clone(), self.get_ephemeral_key()); - let res = peer_encryptor.get_act_one().to_vec(); - let pending_read_buffer = [0; 50].to_vec(); // Noise act two is 50 bytes + let mut handshake = PeerHandshake::new_outbound(&self.our_node_secret, &their_node_id, &self.get_ephemeral_key()); + let (act, ..) = handshake.process_act(&[]).unwrap(); + let res = act.unwrap().serialize(); let mut peers = self.peers.lock().unwrap(); if peers.peers.insert(descriptor, Peer { - channel_encryptor: peer_encryptor, + encryptor: PeerState::Authenticating(handshake), outbound: true, - their_node_id: None, + their_node_id: Some(their_node_id.clone()), their_features: None, pending_outbound_buffer: LinkedList::new(), pending_outbound_buffer_first_msg_offset: 0, awaiting_write_event: false, - pending_read_buffer: pending_read_buffer, - pending_read_buffer_pos: 0, - pending_read_is_header: false, - sync_status: InitSyncTracker::NoSyncRequested, awaiting_pong: false, @@ -346,12 +399,11 @@ impl PeerManager Result<(), PeerHandleError> { - let peer_encryptor = PeerChannelEncryptor::new_inbound(&self.our_node_secret); - let pending_read_buffer = [0; 50].to_vec(); // Noise act one is 50 bytes + let handshake = PeerHandshake::new_inbound(&self.our_node_secret, &self.get_ephemeral_key()); let mut peers = self.peers.lock().unwrap(); if peers.peers.insert(descriptor, Peer { - channel_encryptor: peer_encryptor, + encryptor: PeerState::Authenticating(handshake), outbound: false, their_node_id: None, their_features: None, @@ -360,10 +412,6 @@ impl PeerManager PeerManager { { log_trace!(self.logger, "Encoding and sending sync update message of type {} to {}", $msg.type_id(), log_pubkey!(peer.their_node_id.unwrap())); - peer.pending_outbound_buffer.push_back(peer.channel_encryptor.encrypt_message(&encode_msg!($msg)[..])); + match peer.encryptor { + PeerState::Connected(ref mut conduit) => peer.pending_outbound_buffer.push_back(conduit.encrypt(&encode_msg!($msg)[..])), + _ => panic!("peer must be connected!") + } } } } @@ -501,7 +552,10 @@ impl PeerManager peer.pending_outbound_buffer.push_back(conduit.encrypt(&encoded_message[..])), + _ => panic!("peer must be connected!") + } peers_needing_send.insert(descriptor); } @@ -512,145 +566,118 @@ impl PeerManager panic!("Descriptor for read_event is not already known to PeerManager"), Some(peer) => { - assert!(peer.pending_read_buffer.len() > 0); - assert!(peer.pending_read_buffer.len() > peer.pending_read_buffer_pos); - let mut read_pos = 0; - while read_pos < data.len() { - { - let data_to_copy = cmp::min(peer.pending_read_buffer.len() - peer.pending_read_buffer_pos, data.len() - read_pos); - peer.pending_read_buffer[peer.pending_read_buffer_pos..peer.pending_read_buffer_pos + data_to_copy].copy_from_slice(&data[read_pos..read_pos + data_to_copy]); - read_pos += data_to_copy; - peer.pending_read_buffer_pos += data_to_copy; + let mut send_init_message = false; + + let data_processing_decision = peer.encryptor.process_peer_data(data, &mut peer.pending_outbound_buffer); + match data_processing_decision { + PeerDataProcessingDecision::Disconnect(e) => { + log_trace!(self.logger, "Invalid act message; disconnecting: {}", e); + return Err(e); } - if peer.pending_read_buffer_pos == peer.pending_read_buffer.len() { - peer.pending_read_buffer_pos = 0; - - macro_rules! try_potential_handleerror { - ($thing: expr) => { - match $thing { - Ok(x) => x, - Err(e) => { - match e.action { - msgs::ErrorAction::DisconnectPeer { msg: _ } => { - //TODO: Try to push msg - log_trace!(self.logger, "Got Err handling message, disconnecting peer because {}", e.err); - return Err(PeerHandleError{ no_connection_possible: false }); - }, - msgs::ErrorAction::IgnoreError => { - log_trace!(self.logger, "Got Err handling message, ignoring because {}", e.err); - continue; - }, - msgs::ErrorAction::SendErrorMessage { msg } => { - log_trace!(self.logger, "Got Err handling message, sending Error message because {}", e.err); - self.enqueue_message(&mut peers.peers_needing_send, peer, peer_descriptor.clone(), &msg); - continue; - }, - } - } - }; - } + PeerDataProcessingDecision::CompleteHandshake(needs_to_send_init_message, remote_pubkey_option) => { + send_init_message = needs_to_send_init_message; + + if let Some(key) = remote_pubkey_option { + peer.their_node_id = Some(key); } - macro_rules! insert_node_id { - () => { - match peers.node_id_to_descriptor.entry(peer.their_node_id.unwrap()) { - hash_map::Entry::Occupied(_) => { - log_trace!(self.logger, "Got second connection with {}, closing", log_pubkey!(peer.their_node_id.unwrap())); - peer.their_node_id = None; // Unset so that we don't generate a peer_disconnected event - return Err(PeerHandleError{ no_connection_possible: false }) - }, - hash_map::Entry::Vacant(entry) => { - log_trace!(self.logger, "Finished noise handshake for connection with {}", log_pubkey!(peer.their_node_id.unwrap())); - entry.insert(peer_descriptor.clone()) - }, - }; + // insert node id + match peers.node_id_to_descriptor.entry(peer.their_node_id.unwrap()) { + hash_map::Entry::Occupied(_) => { + log_trace!(self.logger, "Got second connection with {}, closing", log_pubkey!(peer.their_node_id.unwrap())); + peer.their_node_id = None; // Unset so that we don't generate a peer_disconnected event + return Err(PeerHandleError { no_connection_possible: false }); } - } + hash_map::Entry::Vacant(entry) => { + log_trace!(self.logger, "Finished noise handshake for connection with {}", log_pubkey!(peer.their_node_id.unwrap())); + entry.insert(peer_descriptor.clone()) + } + }; + } + _ => {} + }; - let next_step = peer.channel_encryptor.get_noise_step(); - match next_step { - NextNoiseStep::ActOne => { - let act_two = try_potential_handleerror!(peer.channel_encryptor.process_act_one_with_keys(&peer.pending_read_buffer[..], &self.our_node_secret, self.get_ephemeral_key())).to_vec(); - peer.pending_outbound_buffer.push_back(act_two); - peer.pending_read_buffer = [0; 66].to_vec(); // act three is 66 bytes long - }, - NextNoiseStep::ActTwo => { - let (act_three, their_node_id) = try_potential_handleerror!(peer.channel_encryptor.process_act_two(&peer.pending_read_buffer[..], &self.our_node_secret)); - peer.pending_outbound_buffer.push_back(act_three.to_vec()); - peer.pending_read_buffer = [0; 18].to_vec(); // Message length header is 18 bytes - peer.pending_read_is_header = true; - - peer.their_node_id = Some(their_node_id); - insert_node_id!(); - let mut features = InitFeatures::known(); - if !self.message_handler.route_handler.should_request_full_sync(&peer.their_node_id.unwrap()) { - features.clear_initial_routing_sync(); - } + if send_init_message { + let mut features = InitFeatures::known(); + if !self.message_handler.route_handler.should_request_full_sync(&peer.their_node_id.unwrap()) { + features.clear_initial_routing_sync(); + } - let resp = msgs::Init { features }; - self.enqueue_message(&mut peers.peers_needing_send, peer, peer_descriptor.clone(), &resp); - }, - NextNoiseStep::ActThree => { - let their_node_id = try_potential_handleerror!(peer.channel_encryptor.process_act_three(&peer.pending_read_buffer[..])); - peer.pending_read_buffer = [0; 18].to_vec(); // Message length header is 18 bytes - peer.pending_read_is_header = true; - peer.their_node_id = Some(their_node_id); - insert_node_id!(); - }, - NextNoiseStep::NoiseComplete => { - if peer.pending_read_is_header { - let msg_len = try_potential_handleerror!(peer.channel_encryptor.decrypt_length_header(&peer.pending_read_buffer[..])); - peer.pending_read_buffer = Vec::with_capacity(msg_len as usize + 16); - peer.pending_read_buffer.resize(msg_len as usize + 16, 0); - if msg_len < 2 { // Need at least the message type tag - return Err(PeerHandleError{ no_connection_possible: false }); + let resp = msgs::Init { features }; + self.enqueue_message(&mut peers.peers_needing_send, peer, peer_descriptor.clone(), &resp); + send_init_message = false + } + + let mut received_messages = vec![]; + if let &mut PeerState::Connected(ref mut conduit) = &mut peer.encryptor { + let encryptor = &mut conduit.encryptor; + let decryptor = &mut conduit.decryptor; + + for msg_data in decryptor { + let mut reader = ::std::io::Cursor::new(&msg_data[..]); + let message_result = wire::read(&mut reader); + let message = match message_result { + Ok(x) => x, + Err(e) => { + match e { + msgs::DecodeError::UnknownVersion => return Err(PeerHandleError { no_connection_possible: false }), + msgs::DecodeError::UnknownRequiredFeature => { + log_debug!(self.logger, "Got a channel/node announcement with an known required feature flag, you may want to update!"); + continue; } - peer.pending_read_is_header = false; - } else { - let msg_data = try_potential_handleerror!(peer.channel_encryptor.decrypt_message(&peer.pending_read_buffer[..])); - assert!(msg_data.len() >= 2); - - // Reset read buffer - peer.pending_read_buffer = [0; 18].to_vec(); - peer.pending_read_is_header = true; - - let mut reader = ::std::io::Cursor::new(&msg_data[..]); - let message_result = wire::read(&mut reader); - let message = match message_result { - Ok(x) => x, - Err(e) => { - match e { - msgs::DecodeError::UnknownVersion => return Err(PeerHandleError { no_connection_possible: false }), - msgs::DecodeError::UnknownRequiredFeature => { - log_debug!(self.logger, "Got a channel/node announcement with an known required feature flag, you may want to update!"); - continue; - } - msgs::DecodeError::InvalidValue => { - log_debug!(self.logger, "Got an invalid value while deserializing message"); - return Err(PeerHandleError { no_connection_possible: false }); - } - msgs::DecodeError::ShortRead => { - log_debug!(self.logger, "Deserialization failed due to shortness of message"); - return Err(PeerHandleError { no_connection_possible: false }); - } - msgs::DecodeError::BadLengthDescriptor => return Err(PeerHandleError { no_connection_possible: false }), - msgs::DecodeError::Io(_) => return Err(PeerHandleError { no_connection_possible: false }), - } - } - }; - - if let Err(handling_error) = self.handle_message(&mut peers.peers_needing_send, peer, peer_descriptor.clone(), message){ - match handling_error { - MessageHandlingError::PeerHandleError(e) => { return Err(e) }, - MessageHandlingError::LightningError(e) => { - try_potential_handleerror!(Err(e)); - }, - } + msgs::DecodeError::InvalidValue => { + log_debug!(self.logger, "Got an invalid value while deserializing message"); + return Err(PeerHandleError { no_connection_possible: false }); + } + msgs::DecodeError::ShortRead => { + log_debug!(self.logger, "Deserialization failed due to shortness of message"); + return Err(PeerHandleError { no_connection_possible: false }); } + msgs::DecodeError::BadLengthDescriptor => return Err(PeerHandleError { no_connection_possible: false }), + msgs::DecodeError::Io(_) => return Err(PeerHandleError { no_connection_possible: false }), } } + }; + + received_messages.push(message); + } + } + + for message in received_messages { + macro_rules! try_potential_handleerror { + ($thing: expr) => { + match $thing { + Ok(x) => x, + Err(e) => { + match e.action { + msgs::ErrorAction::DisconnectPeer { msg: _ } => { + //TODO: Try to push msg + log_trace!(self.logger, "Got Err handling message, disconnecting peer because {}", e.err); + return Err(PeerHandleError{ no_connection_possible: false }); + }, + msgs::ErrorAction::IgnoreError => { + log_trace!(self.logger, "Got Err handling message, ignoring because {}", e.err); + continue; + }, + msgs::ErrorAction::SendErrorMessage { msg } => { + log_trace!(self.logger, "Got Err handling message, sending Error message because {}", e.err); + self.enqueue_message(&mut peers.peers_needing_send, peer, peer_descriptor.clone(), &msg); + continue; + }, + } + } + }; + } + } + + if let Err(handling_error) = self.handle_message(&mut peers.peers_needing_send, peer, peer_descriptor.clone(), message){ + match handling_error { + MessageHandlingError::PeerHandleError(e) => { return Err(e) }, + MessageHandlingError::LightningError(e) => { + try_potential_handleerror!(Err(e)); + }, } } } @@ -898,7 +925,9 @@ impl PeerManager { @@ -908,7 +937,9 @@ impl PeerManager { @@ -920,7 +951,9 @@ impl PeerManager { @@ -931,7 +964,9 @@ impl PeerManager { @@ -941,7 +976,9 @@ impl PeerManager { @@ -952,7 +989,9 @@ impl PeerManager { @@ -965,22 +1004,24 @@ impl PeerManager { @@ -990,7 +1031,9 @@ impl PeerManager { @@ -1000,7 +1043,9 @@ impl PeerManager { @@ -1010,7 +1055,9 @@ impl PeerManager { @@ -1020,7 +1067,9 @@ impl PeerManager { @@ -1030,8 +1079,8 @@ impl PeerManager PeerManager PeerManager PeerManager PeerManager PeerManager PeerManager PeerManager Vec { + match self { + &Act::One(ref act) => { + act.0.to_vec() + } + &Act::Two(ref act) => { + act.0.to_vec() + } + &Act::Three(ref act) => { + act.0.to_vec() + } + } + } +} \ No newline at end of file diff --git a/lightning/src/ln/peers/handshake/hash.rs b/lightning/src/ln/peers/handshake/hash.rs new file mode 100644 index 00000000000..e7704b37e34 --- /dev/null +++ b/lightning/src/ln/peers/handshake/hash.rs @@ -0,0 +1,25 @@ +use bitcoin::hashes::{Hash, HashEngine}; +use bitcoin::hashes::sha256::Hash as Sha256; + +pub(crate) struct HandshakeHash { + pub(super) value: [u8; 32] +} + +impl HandshakeHash { + pub(super) fn new(first_input: &[u8]) -> Self { + let mut hash = Self { + value: [0; 32] + }; + let mut sha = Sha256::engine(); + sha.input(first_input); + hash.value = Sha256::from_engine(sha).into_inner(); + hash + } + + pub(super) fn update(&mut self, input: &[u8]) { + let mut sha = Sha256::engine(); + sha.input(self.value.as_ref()); + sha.input(input); + self.value = Sha256::from_engine(sha).into_inner(); + } +} diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs new file mode 100644 index 00000000000..32efc969a22 --- /dev/null +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -0,0 +1,394 @@ +//! Execute handshakes for peer-to-peer connection establishment. +//! Handshake states can be advanced automatically, or by manually calling the appropriate step. +//! Once complete, returns an instance of Conduit. + +use bitcoin::secp256k1; + +use bitcoin::hashes::{Hash, HashEngine}; +use bitcoin::hashes::sha256::Hash as Sha256; +use bitcoin::secp256k1::{PublicKey, SecretKey}; + +use ln::peers::{chacha, hkdf}; +use ln::peers::conduit::{Conduit, SymmetricKey}; +use ln::peers::handshake::acts::{ActOne, ActThree, ActTwo, ACT_ONE_LENGTH, ACT_TWO_LENGTH, ACT_THREE_LENGTH, Act}; +use ln::peers::handshake::hash::HandshakeHash; +use ln::peers::handshake::states::{ActOneExpectation, ActThreeExpectation, ActTwoExpectation, HandshakeState}; + +pub(crate) mod acts; +mod hash; +mod states; +mod tests; + +/// Object for managing handshakes. +/// Currently requires explicit ephemeral private key specification. +pub struct PeerHandshake { + state: Option, + private_key: SecretKey, + remote_public_key: Option, + ephemeral_private_key: SecretKey, + + read_buffer: Vec, +} + +impl PeerHandshake { + /// Instantiate a new handshake with a node identity secret key and an ephemeral private key + pub fn new_outbound(private_key: &SecretKey, remote_public_key: &PublicKey, ephemeral_private_key: &SecretKey) -> Self { + Self { + state: Some(HandshakeState::Uninitiated), + private_key: (*private_key).clone(), + remote_public_key: Some(remote_public_key.clone()), + ephemeral_private_key: (*ephemeral_private_key).clone(), + read_buffer: Vec::new(), + } + } + + /// Instantiate a new handshake in anticipation of a peer's first handshake act + pub fn new_inbound(private_key: &SecretKey, ephemeral_private_key: &SecretKey) -> Self { + let mut handshake = Self { + state: Some(HandshakeState::Uninitiated), + private_key: (*private_key).clone(), + remote_public_key: None, + ephemeral_private_key: (*ephemeral_private_key).clone(), + read_buffer: Vec::new(), + }; + let public_key = Self::private_key_to_public_key(&private_key); + let (hash, chaining_key) = Self::initialize_state(&public_key); + handshake.state = Some(HandshakeState::AwaitingActOne(ActOneExpectation { + hash, + chaining_key, + })); + handshake + } + + /// Return the remote public key once it has been extracted from the third act. + /// Potentially useful for inbound connections + pub fn get_remote_pubkey(&self) -> Option { + self.remote_public_key + } + + fn initialize_state(public_key: &PublicKey) -> (HandshakeHash, [u8; 32]) { + let protocol_name = b"Noise_XK_secp256k1_ChaChaPoly_SHA256"; + let prologue = b"lightning"; + + let mut sha = Sha256::engine(); + sha.input(protocol_name); + let chaining_key = Sha256::from_engine(sha).into_inner(); + + let mut initial_hash_preimage = chaining_key.to_vec(); + initial_hash_preimage.extend_from_slice(prologue.as_ref()); + + let mut hash = HandshakeHash::new(initial_hash_preimage.as_slice()); + hash.update(&public_key.serialize()); + + (hash, chaining_key) + } + + /// Process act dynamically + /// # Arguments + /// `input`: Byte slice received from peer as part of the handshake protocol + /// + /// # Return values + /// Returns a tuple with the following components: + /// `.0`: Byte vector containing the next act to send back to the peer per the handshake protocol + /// `.1`: Conduit option if the handshake was just processed to completion and messages can now be encrypted and decrypted + pub fn process_act(&mut self, input: &[u8]) -> Result<(Option, Option), String> { + let mut response = None; + let mut connected_peer = None; + + self.read_buffer.extend_from_slice(input); + let read_buffer_length = self.read_buffer.len(); + + match &self.state { + &Some(HandshakeState::Uninitiated) => { + let remote_public_key = &self.remote_public_key.ok_or("outbound connections must be initialized with new_outbound")?; + let act_one = self.initiate(&remote_public_key)?; + response = Some(Act::One(act_one)); + } + &Some(HandshakeState::AwaitingActOne(_)) => { + if read_buffer_length < ACT_ONE_LENGTH { + return Err("need at least 50 bytes".to_string()); + } + + let mut act_one_buffer = [0u8; ACT_ONE_LENGTH]; + act_one_buffer.copy_from_slice(&self.read_buffer[..ACT_ONE_LENGTH]); + self.read_buffer.drain(..ACT_ONE_LENGTH); + + let act_two = self.process_act_one(ActOne(act_one_buffer))?; + response = Some(Act::Two(act_two)); + } + &Some(HandshakeState::AwaitingActTwo(_)) => { + if read_buffer_length < ACT_TWO_LENGTH { + return Err("need at least 50 bytes".to_string()); + } + + let mut act_two_buffer = [0u8; ACT_TWO_LENGTH]; + act_two_buffer.copy_from_slice(&self.read_buffer[..ACT_TWO_LENGTH]); + self.read_buffer.drain(..ACT_TWO_LENGTH); + + let (act_three, mut conduit) = self.process_act_two(ActTwo(act_two_buffer))?; + + if self.read_buffer.len() > 0 { // have we received more data still? + conduit.read(&self.read_buffer[..]); + self.read_buffer.drain(..); + } + + response = Some(Act::Three(act_three)); + connected_peer = Some(conduit); + } + &Some(HandshakeState::AwaitingActThree(_)) => { + if read_buffer_length < ACT_THREE_LENGTH { + return Err("need at least 66 bytes".to_string()); + } + + let mut act_three_buffer = [0u8; ACT_THREE_LENGTH]; + act_three_buffer.copy_from_slice(&self.read_buffer[..ACT_THREE_LENGTH]); + self.read_buffer.drain(..ACT_THREE_LENGTH); + + let (public_key, mut conduit) = self.process_act_three(ActThree(act_three_buffer))?; + + if self.read_buffer.len() > 0 { // have we received more data still? + conduit.read(&self.read_buffer[..]); + self.read_buffer.drain(..); + } + + connected_peer = Some(conduit); + self.remote_public_key = Some(public_key); + } + _ => { + panic!("no acts left to process"); + } + }; + Ok((response, connected_peer)) + } + + /// Initiate the handshake with a peer and return the first act + pub fn initiate(&mut self, remote_public_key: &PublicKey) -> Result { + if let &Some(HandshakeState::Uninitiated) = &self.state {} else { + return Err("Handshakes can only be initiated from the uninitiated state".to_string()); + } + + let (mut hash, chaining_key) = Self::initialize_state(&remote_public_key); + + // serialize act one + let (act_one, chaining_key, temporary_key) = Self::calculate_act_message( + &self.ephemeral_private_key, + remote_public_key, + chaining_key, + &mut hash, + ); + + self.state = Some(HandshakeState::AwaitingActTwo(ActTwoExpectation { + hash, + chaining_key, + temporary_key, + ephemeral_private_key: (*&self.ephemeral_private_key).clone(), + })); + + Ok(ActOne(act_one)) + } + + /// Process a peer's incoming first act and return the second act + pub(crate) fn process_act_one(&mut self, act: ActOne) -> Result { + let state = self.state.take(); + let act_one_expectation = match state { + Some(HandshakeState::AwaitingActOne(act_state)) => act_state, + Some(HandshakeState::Uninitiated) => { + let public_key = Self::private_key_to_public_key(&self.private_key); + let (hash, chaining_key) = Self::initialize_state(&public_key); + ActOneExpectation { + hash, + chaining_key, + } + } + _ => { + self.state = state; + panic!("unexpected state"); + } + }; + + let mut hash = act_one_expectation.hash; + let (remote_ephemeral_public_key, chaining_key, _) = Self::process_act_message( + act.0, + &self.private_key, + act_one_expectation.chaining_key, + &mut hash, + )?; + + let ephemeral_private_key = (*&self.ephemeral_private_key).clone(); + + let (act_two, chaining_key, temporary_key) = Self::calculate_act_message( + &ephemeral_private_key, + &remote_ephemeral_public_key, + chaining_key, + &mut hash, + ); + + self.state = Some(HandshakeState::AwaitingActThree(ActThreeExpectation { + hash, + chaining_key, + temporary_key, + ephemeral_private_key, + remote_ephemeral_public_key, + })); + + Ok(ActTwo(act_two)) + } + + /// Process a peer's incoming second act and return the third act alongside a Conduit instance + pub(crate) fn process_act_two(&mut self, act: ActTwo) -> Result<(ActThree, Conduit), String> { + let state = self.state.take(); + let act_two_expectation = match state { + Some(HandshakeState::AwaitingActTwo(act_state)) => act_state, + _ => { + self.state = state; + panic!("unexpected state".to_string()); + } + }; + + let mut hash = act_two_expectation.hash; + let (remote_ephemeral_public_key, chaining_key, temporary_key) = Self::process_act_message( + act.0, + &act_two_expectation.ephemeral_private_key, + act_two_expectation.chaining_key, + &mut hash, + )?; + + self.state = Some(HandshakeState::Complete); + + // start serializing act three + + let static_public_key = Self::private_key_to_public_key(&self.private_key); + let tagged_encrypted_pubkey = chacha::encrypt(&temporary_key, 1, &hash.value, &static_public_key.serialize()); + hash.update(&tagged_encrypted_pubkey); + + let ecdh = Self::ecdh(&self.private_key, &remote_ephemeral_public_key); + let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); + let authentication_tag = chacha::encrypt(&temporary_key, 0, &hash.value, &[0; 0]); + let (sending_key, receiving_key) = hkdf::derive(&chaining_key, &[0; 0]); + + let mut act_three = [0u8; ACT_THREE_LENGTH]; + act_three[1..50].copy_from_slice(&tagged_encrypted_pubkey); + act_three[50..].copy_from_slice(authentication_tag.as_slice()); + + let connected_peer = Conduit::new(sending_key, receiving_key, chaining_key); + Ok((ActThree(act_three), connected_peer)) + } + + /// Process a peer's incoming third act and return a Conduit instance + pub(crate) fn process_act_three(&mut self, act: ActThree) -> Result<(PublicKey, Conduit), String> { + let state = self.state.take(); + let act_three_expectation = match state { + Some(HandshakeState::AwaitingActThree(act_state)) => act_state, + _ => { + self.state = state; + panic!("unexpected state".to_string()); + } + }; + + let version = act.0[0]; + if version != 0 { + // this should not crash the process, hence no panic + return Err("unexpected version".to_string()); + } + + let mut tagged_encrypted_pubkey = [0u8; 49]; + tagged_encrypted_pubkey.copy_from_slice(&act.0[1..50]); + + let mut chacha_tag = [0u8; 16]; + chacha_tag.copy_from_slice(&act.0[50..66]); + + let mut hash = act_three_expectation.hash; + + let remote_pubkey_vec = chacha::decrypt(&act_three_expectation.temporary_key, 1, &hash.value, &tagged_encrypted_pubkey)?; + let mut remote_pubkey_bytes = [0u8; 33]; + remote_pubkey_bytes.copy_from_slice(remote_pubkey_vec.as_slice()); + let remote_pubkey = if let Ok(public_key) = PublicKey::from_slice(&remote_pubkey_bytes) { + public_key + } else { + return Err("invalid remote public key".to_string()); + }; + + hash.update(&tagged_encrypted_pubkey); + + let ecdh = Self::ecdh(&act_three_expectation.ephemeral_private_key, &remote_pubkey); + let (chaining_key, temporary_key) = hkdf::derive(&act_three_expectation.chaining_key, &ecdh); + let _tag_check = chacha::decrypt(&temporary_key, 0, &hash.value, &chacha_tag)?; + let (receiving_key, sending_key) = hkdf::derive(&chaining_key, &[0; 0]); + + let connected_peer = Conduit::new(sending_key, receiving_key, chaining_key); + Ok((remote_pubkey, connected_peer)) + } + + fn calculate_act_message(local_private_key: &SecretKey, remote_public_key: &PublicKey, chaining_key: [u8; 32], hash: &mut HandshakeHash) -> ([u8; 50], SymmetricKey, SymmetricKey) { + let local_public_key = Self::private_key_to_public_key(local_private_key); + + hash.update(&local_public_key.serialize()); + + let ecdh = Self::ecdh(local_private_key, &remote_public_key); + let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); + let tagged_ciphertext = chacha::encrypt(&temporary_key, 0, &hash.value, &[0; 0]); + + hash.update(&tagged_ciphertext); + + let mut act = [0u8; 50]; + act[1..34].copy_from_slice(&local_public_key.serialize()); + act[34..].copy_from_slice(tagged_ciphertext.as_slice()); + + (act, chaining_key, temporary_key) + } + + // Due to the very high similarity of acts 1 and 2, this method is used to process both + fn process_act_message(act_bytes: [u8; 50], local_private_key: &SecretKey, chaining_key: SymmetricKey, hash: &mut HandshakeHash) -> Result<(PublicKey, SymmetricKey, SymmetricKey), String> { + let version = act_bytes[0]; + if version != 0 { + // this should not crash the process, hence no panic + return Err("unexpected version".to_string()); + } + + let mut ephemeral_public_key_bytes = [0u8; 33]; + ephemeral_public_key_bytes.copy_from_slice(&act_bytes[1..34]); + let ephemeral_public_key = if let Ok(public_key) = PublicKey::from_slice(&ephemeral_public_key_bytes) { + public_key + } else { + return Err("invalid remote ephemeral public key".to_string()); + }; + + let mut chacha_tag = [0u8; 16]; + chacha_tag.copy_from_slice(&act_bytes[34..50]); + + // process the act message + + // update hash with partner's pubkey + hash.update(&ephemeral_public_key.serialize()); + + // calculate ECDH with partner's pubkey and local privkey + let ecdh = Self::ecdh(local_private_key, &ephemeral_public_key); + + // HKDF(chaining key, ECDH) -> chaining key' + next temporary key + let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); + + // Validate chacha tag (temporary key, 0, hash, chacha_tag) + let _tag_check = chacha::decrypt(&temporary_key, 0, &hash.value, &chacha_tag)?; + + hash.update(&chacha_tag); + + Ok((ephemeral_public_key, chaining_key, temporary_key)) + } + + fn private_key_to_public_key(private_key: &SecretKey) -> PublicKey { + let curve = secp256k1::Secp256k1::new(); + let pk_object = PublicKey::from_secret_key(&curve, &private_key); + pk_object + } + + fn ecdh(private_key: &SecretKey, public_key: &PublicKey) -> SymmetricKey { + let curve = secp256k1::Secp256k1::new(); + let mut pk_object = public_key.clone(); + pk_object.mul_assign(&curve, &private_key[..]).expect("invalid multiplication"); + + let preimage = pk_object.serialize(); + let mut sha = Sha256::engine(); + sha.input(preimage.as_ref()); + Sha256::from_engine(sha).into_inner() + } +} diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs new file mode 100644 index 00000000000..2a7401f5e9b --- /dev/null +++ b/lightning/src/ln/peers/handshake/states.rs @@ -0,0 +1,30 @@ +use ln::peers::handshake::hash::HandshakeHash; +use bitcoin::secp256k1::{SecretKey, PublicKey}; + +pub enum HandshakeState { + Uninitiated, + AwaitingActOne(ActOneExpectation), + AwaitingActTwo(ActTwoExpectation), + AwaitingActThree(ActThreeExpectation), + Complete, +} + +pub struct ActOneExpectation { + pub(super) hash: HandshakeHash, + pub(super) chaining_key: [u8; 32], +} + +pub struct ActTwoExpectation { + pub(super) hash: HandshakeHash, + pub(super) chaining_key: [u8; 32], + pub(super) temporary_key: [u8; 32], + pub(super) ephemeral_private_key: SecretKey, +} + +pub struct ActThreeExpectation { + pub(super) hash: HandshakeHash, + pub(super) chaining_key: [u8; 32], + pub(super) temporary_key: [u8; 32], + pub(super) ephemeral_private_key: SecretKey, + pub(super) remote_ephemeral_public_key: PublicKey, +} diff --git a/lightning/src/ln/peers/handshake/tests.rs b/lightning/src/ln/peers/handshake/tests.rs new file mode 100644 index 00000000000..e99500d1399 --- /dev/null +++ b/lightning/src/ln/peers/handshake/tests.rs @@ -0,0 +1,39 @@ +#![cfg(test)] + +use hex; +use bitcoin::secp256k1; + +use bitcoin::secp256k1::key::{PublicKey, SecretKey}; + +use ln::peers::handshake::PeerHandshake; + +#[test] +fn test_exchange() { + let curve = secp256k1::Secp256k1::new(); + + let local_private_key = SecretKey::from_slice(&[0x_11_u8; 32]).unwrap(); + let remote_private_key = SecretKey::from_slice(&[0x_21_u8; 32]).unwrap(); + + let local_ephemeral_private_key = SecretKey::from_slice(&[0x_12_u8; 32]).unwrap(); + let remote_ephemeral_private_key = SecretKey::from_slice(&[0x_22_u8; 32]).unwrap(); + + let remote_public_key = PublicKey::from_secret_key(&curve, &remote_private_key); + + let mut local_handshake = PeerHandshake::new_outbound(&local_private_key, &remote_public_key, &local_ephemeral_private_key); + let mut remote_handshake = PeerHandshake::new_inbound(&remote_private_key, &remote_ephemeral_private_key); + + let act_1 = local_handshake.initiate(&remote_public_key).unwrap(); + let act_1_hex = hex::encode(&act_1.0.to_vec()); + assert_eq!(act_1_hex, "00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a"); + + let act_2 = remote_handshake.process_act_one(act_1).unwrap(); + let act_2_hex = hex::encode(&act_2.0.to_vec()); + assert_eq!(act_2_hex, "0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae"); + + let act_2_result = local_handshake.process_act_two(act_2).unwrap(); + let act_3 = act_2_result.0; + let act_3_hex = hex::encode(&act_3.0.to_vec()); + assert_eq!(act_3_hex, "00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba"); + + remote_handshake.process_act_three(act_3).unwrap(); +} diff --git a/lightning/src/ln/peers/hkdf.rs b/lightning/src/ln/peers/hkdf.rs new file mode 100644 index 00000000000..48415594114 --- /dev/null +++ b/lightning/src/ln/peers/hkdf.rs @@ -0,0 +1,18 @@ +use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine}; +use bitcoin::hashes::sha256::Hash as Sha256; + +pub fn derive(salt: &[u8], master: &[u8]) -> ([u8; 32], [u8; 32]) { + let mut hmac = HmacEngine::::new(salt); + hmac.input(master); + let prk = Hmac::from_engine(hmac).into_inner(); // prk = sha256(master) + + let mut hmac = HmacEngine::::new(&prk[..]); + hmac.input(&[1; 1]); + let t1 = Hmac::from_engine(hmac).into_inner(); // t1 = sha256(prk | 1) + + let mut hmac = HmacEngine::::new(&prk[..]); + hmac.input(&t1); + hmac.input(&[2; 1]); + // sha256(prk | t1 | 2) = sha256(sha256(master) | sha256(sha256(sha256(master) | 1) | 2) + (t1, Hmac::from_engine(hmac).into_inner()) +} diff --git a/lightning/src/ln/peers/mod.rs b/lightning/src/ln/peers/mod.rs new file mode 100644 index 00000000000..f8082c50f10 --- /dev/null +++ b/lightning/src/ln/peers/mod.rs @@ -0,0 +1,10 @@ +//! Everything that has to do with over-the-wire peer communication. +//! The handshake module exposes mechanisms to conduct inbound and outbound handshakes. +//! When a handshake completes, it returns an instance of Conduit. +//! Conduit enables message encryption and decryption, and automatically handles key rotation. + +mod chacha; +pub mod conduit; +pub mod handshake; +pub mod handler; +mod hkdf; From ba37213a8e560bf35af0a3d583351d14365716e5 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Tue, 15 Sep 2020 11:02:58 -0700 Subject: [PATCH 02/45] Fix compilation bug in 494. Lots of build warnings still, but it builds and tests pass. --- lightning/src/ln/peers/conduit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lightning/src/ln/peers/conduit.rs b/lightning/src/ln/peers/conduit.rs index 74e420b192a..ea3b7a17afe 100644 --- a/lightning/src/ln/peers/conduit.rs +++ b/lightning/src/ln/peers/conduit.rs @@ -232,7 +232,7 @@ mod tests { assert_eq!(encrypted_message.len(), 2 + 16 + 16); let decrypted_message = remote_peer.decrypt_single_message(Some(&encrypted_message)).unwrap(); - assert_eq!(decrypted_message, vec![]); + assert_eq!(decrypted_message, Vec::::new()); } #[test] From 98bc0da44231c83e4bb0a35e17c5aa639dd76c1c Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sun, 16 Aug 2020 15:39:59 -0700 Subject: [PATCH 03/45] tests: Add unit tests to handshake/mod.rs Add unit tests against the PeerHandshake public interface prior to changing any of the implementation details. This is a solid stand-alone patch, even if the subsequent refactor and design changes are not merged. A future patch uses the test vectors from the RFC to minimize the code under test. --- lightning/src/ln/peers/conduit.rs | 10 + lightning/src/ln/peers/handshake/mod.rs | 371 ++++++++++++++++++++++++ 2 files changed, 381 insertions(+) diff --git a/lightning/src/ln/peers/conduit.rs b/lightning/src/ln/peers/conduit.rs index ea3b7a17afe..57f191a8c0c 100644 --- a/lightning/src/ln/peers/conduit.rs +++ b/lightning/src/ln/peers/conduit.rs @@ -196,6 +196,16 @@ impl Decryptor { fn increment_nonce(&mut self) { Conduit::increment_nonce(&mut self.receiving_nonce, &mut self.receiving_chaining_key, &mut self.receiving_key); } + + // Used in tests to determine whether or not excess bytes entered the conduit without needing to bring up + // infrastructure to properly encode it + #[cfg(test)] + pub fn read_buffer_length(&self) -> usize { + match &self.read_buffer { + &Some(ref vec) => { vec.len() } + &None => 0 + } + } } #[cfg(test)] diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs index 32efc969a22..2d95998aa2e 100644 --- a/lightning/src/ln/peers/handshake/mod.rs +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -392,3 +392,374 @@ impl PeerHandshake { Sha256::from_engine(sha).into_inner() } } + +#[cfg(test)] +mod test { + use hex; + + use bitcoin::secp256k1; + use bitcoin::secp256k1::key::{PublicKey, SecretKey}; + + use ln::peers::handshake::PeerHandshake; + use ln::peers::handshake::acts::Act; + use ln::peers::handshake::states::HandshakeState; + + struct TestCtx { + outbound_handshake: PeerHandshake, + outbound_public_key: PublicKey, + inbound_handshake: PeerHandshake, + inbound_public_key: PublicKey + } + + impl TestCtx { + fn new() -> TestCtx { + let curve = secp256k1::Secp256k1::new(); + + let outbound_private_key = SecretKey::from_slice(&[0x_11_u8; 32]).unwrap(); + let outbound_public_key = PublicKey::from_secret_key(&curve, &outbound_private_key); + let outbound_ephemeral_key = SecretKey::from_slice(&[0x_12_u8; 32]).unwrap(); + + let inbound_private_key = SecretKey::from_slice(&[0x_21_u8; 32]).unwrap(); + let inbound_public_key = PublicKey::from_secret_key(&curve, &inbound_private_key); + let inbound_ephemeral_key = SecretKey::from_slice(&[0x_22_u8; 32]).unwrap(); + + let outbound_handshake = PeerHandshake::new_outbound(&outbound_private_key, &inbound_public_key, &outbound_ephemeral_key); + let inbound_handshake = PeerHandshake::new_inbound(&inbound_private_key, &inbound_ephemeral_key); + + TestCtx { + outbound_handshake, + outbound_public_key, + inbound_handshake, + inbound_public_key + } + } + } + + macro_rules! assert_matches { + ($e:expr, $state_match:pat) => { + match $e { + $state_match => (), + _ => panic!() + } + } + } + + macro_rules! do_process_act_or_panic { + ($handshake:expr, $input:expr) => { + $handshake.process_act($input).unwrap().0.unwrap().serialize() + } + } + + // Default Outbound::Uninitiated + #[test] + fn peer_handshake_new_outbound() { + let test_ctx = TestCtx::new(); + + assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState::Uninitiated)); + assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); + } + + // Default Inbound::AwaitingActOne + #[test] + fn peer_handshake_new_inbound() { + let test_ctx = TestCtx::new(); + + assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState::AwaitingActOne(_))); + assert!(test_ctx.inbound_handshake.get_remote_pubkey().is_none()); + } + + /* + * PeerHandshake::process_act() tests + */ + + // Outbound::Uninitiated -> AwaitingActTwo + #[test] + fn peer_handshake_outbound_uninitiated_to_awaiting_act_two() { + let mut test_ctx = TestCtx::new(); + + assert_matches!(test_ctx.outbound_handshake.process_act(&[]).unwrap(), (Some(Act::One(_)), None)); + assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState::AwaitingActTwo(_))); + assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); + } + + // Outbound::Uninitiated -> AwaitingActTwo (extra bytes in argument) + #[test] + fn peer_handshake_outbound_uninitiated_to_awaiting_act_two_nonempty_input() { + let mut test_ctx = TestCtx::new(); + + // TODO: process_act() should error if state does not use vec, but it is non-empty + assert_matches!(test_ctx.outbound_handshake.process_act(&[1]).unwrap(), (Some(Act::One(_)), None)); + assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState::AwaitingActTwo(_))); + assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); + } + + // Inbound::AwaitingActOne -> Error (input too small) + #[test] + fn peer_handshake_new_inbound_awaiting_act_one_input_too_small() { + let mut test_ctx = TestCtx::new(); + + assert_eq!(test_ctx.inbound_handshake.process_act(&[]).err(), Some(String::from("need at least 50 bytes"))); + } + + // Inbound::AwaitingActOne -> AwaitingActThree (excess bytes) + // TODO: This should error early if we receive act3 data prior to sending act2 + #[test] + fn peer_handshake_new_inbound_awaiting_act_one_input_too_large() { + let mut test_ctx = TestCtx::new(); + let mut act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + act1.extend_from_slice(&[1]); + + assert_matches!(test_ctx.inbound_handshake.process_act(&act1).unwrap(), (Some(Act::Two(_)), None)); + assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState::AwaitingActThree(_))); + assert!(test_ctx.inbound_handshake.get_remote_pubkey().is_none()); + } + + // Inbound::AwaitingActOne -> Error (bad version byte) + #[test] + fn peer_handshake_new_inbound_awaiting_act_one_bad_version() { + let mut test_ctx = TestCtx::new(); + let mut act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + // Set bad version byte + act1[0] = 1; + + assert_eq!(test_ctx.inbound_handshake.process_act(&act1).err(), Some(String::from("unexpected version"))); + } + + // Inbound::AwaitingActOne -> Error (invalid hmac) + #[test] + fn peer_handshake_new_inbound_awaiting_act_invalid_hmac() { + let mut test_ctx = TestCtx::new(); + let mut act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + // corrupt the ciphertext + act1[34] = 0; + + assert_eq!(test_ctx.inbound_handshake.process_act(&act1).err(), Some(String::from("invalid hmac"))); + } + + // Inbound::AwaitingActOne -> Error (invalid remote ephemeral key) + #[test] + fn peer_handshake_new_inbound_awaiting_act_invalid_remote_ephemeral_key() { + let mut test_ctx = TestCtx::new(); + let mut act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + // corrupt the ephemeral public key + act1[1] = 0; + + assert_eq!(test_ctx.inbound_handshake.process_act(&act1).err(), Some(String::from("invalid remote ephemeral public key"))); + } + + // Inbound::AwaitingActOne -> AwaitingActThree + #[test] + fn peer_handshake_new_inbound_awaiting_act_one_to_awaiting_act_three() { + let mut test_ctx = TestCtx::new(); + let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + + assert_matches!(test_ctx.inbound_handshake.process_act(&act1).unwrap(), (Some(Act::Two(_)), None)); + assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState::AwaitingActThree(_))); + assert_eq!(test_ctx.inbound_handshake.get_remote_pubkey(), None); + } + + // Outbound::AwaitingActTwo -> Complete (valid conduit) + #[test] + fn peer_handshake_outbound_awaiting_act_two_process() { + let mut test_ctx = TestCtx::new(); + let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); + + assert_matches!(test_ctx.outbound_handshake.process_act(&act2).unwrap(), (Some(Act::Three(_)), Some(_))); + assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState::Complete)); + assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); + } + + + // Outbound::AwaitingActTwo -> Complete (with extra data) + // Ensures that any remaining data in the read buffer is transferred to the conduit once + // the handshake is complete + // TODO: Is this valid? Don't we expect peers to need ActThree before sending additional data? + #[test] + fn peer_handshake_new_outbound_excess_bytes_after_complete_are_in_conduit() { + let mut test_ctx = TestCtx::new(); + let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + let mut act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); + act2.extend_from_slice(&[1; 100]); + + let conduit = if let (_, Some(conduit)) = test_ctx.outbound_handshake.process_act(&act2).unwrap() { + conduit + } else { + panic!(); + }; + + assert_eq!(100, conduit.decryptor.read_buffer_length()); + assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); + } + + // Outbound::AwaitingActTwo -> Error (input too small) + #[test] + fn peer_handshake_outbound_awaiting_act_two_input_too_small() { + let mut test_ctx = TestCtx::new(); + let _act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + + assert_eq!(test_ctx.outbound_handshake.process_act(&[1]).err(), Some(String::from("need at least 50 bytes"))); + } + + // Outbound::AwaitingActTwo -> Error (bad version byte) + #[test] + fn peer_handshake_outbound_awaiting_act_two_bad_version() { + let mut test_ctx = TestCtx::new(); + let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + let mut act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); + // Set version byte to 1 + act2[0] = 1; + + assert_eq!(test_ctx.outbound_handshake.process_act(&act2).err(), Some(String::from("unexpected version"))); + } + + // Outbound::AwaitingActTwo -> Error (invalid hmac) + #[test] + fn peer_handshake_outbound_awaiting_act_two_invalid_hmac() { + let mut test_ctx = TestCtx::new(); + let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + let mut act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); + // corrupt the ciphertext + act2[34] = 1; + + assert_eq!(test_ctx.outbound_handshake.process_act(&act2).err(), Some(String::from("invalid hmac"))); + } + + // Outbound::AwaitingActTwo -> Error (invalid remote ephemeral key) + #[test] + fn peer_handshake_outbound_awaiting_act_two_invalid_remote_ephemeral_key() { + let mut test_ctx = TestCtx::new(); + let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + let mut act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); + // corrupt the ephemeral public key + act2[1] = 1; + + assert_eq!(test_ctx.outbound_handshake.process_act(&act2).err(), Some(String::from("invalid remote ephemeral public key"))); + } + + // Inbound::AwaitingActThree -> None + // TODO: should this transition to Complete instead of None? + #[test] + fn peer_handshake_new_inbound_awaiting_act_three_to_awaiting_act_three() { + let mut test_ctx = TestCtx::new(); + let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); + let act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); + + assert_matches!(test_ctx.inbound_handshake.process_act(&act3).unwrap(), (None, Some(_))); + assert_eq!(test_ctx.inbound_handshake.get_remote_pubkey(), Some(test_ctx.outbound_public_key)); + } + + // Inbound::AwaitingActThree -> None (with extra bytes) + // Ensures that any remaining data in the read buffer is transferred to the conduit once + // the handshake is complete + #[test] + fn peer_handshake_new_inbound_excess_bytes_after_complete_are_in_conduit() { + let mut test_ctx = TestCtx::new(); + let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); + let mut act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); + act3.extend_from_slice(&[2; 100]); + + let conduit = if let (None, Some(conduit)) = test_ctx.inbound_handshake.process_act(&act3).unwrap() { + conduit + } else { + panic!(); + }; + + assert_eq!(100, conduit.decryptor.read_buffer_length()); + } + + // Inbound::AwaitingActThree -> Error (input too small) + #[test] + fn peer_handshake_new_inbound_awaiting_act_three_input_too_small() { + let mut test_ctx = TestCtx::new(); + let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); + let act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); + + assert_eq!(test_ctx.inbound_handshake.process_act(&act3[..65]).err(), Some(String::from("need at least 66 bytes"))); + } + + // Inbound::AwaitingActThree -> Error (bad version byte) + #[test] + fn peer_handshake_new_inbound_awaiting_act_three_bad_version() { + let mut test_ctx = TestCtx::new(); + let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); + let mut act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); + // set version byte to 1 + act3[0] = 1; + + assert_eq!(test_ctx.inbound_handshake.process_act(&act3).err(), Some(String::from("unexpected version"))); + } + + // Inbound::AwaitingActThree -> Error (invalid hmac) + #[test] + fn peer_handshake_new_inbound_awaiting_act_three_invalid_hmac() { + let mut test_ctx = TestCtx::new(); + let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); + let mut act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); + // trigger decryption error by corrupting byte 1 + act3[1] = 0; + + assert_eq!(test_ctx.inbound_handshake.process_act(&act3).err(), Some(String::from("invalid hmac"))); + } + + // Inbound::AwaitingActThree -> Error (invalid tag hmac) + #[test] + fn peer_handshake_new_inbound_awaiting_act_three_invalid_tag_hmac() { + let mut test_ctx = TestCtx::new(); + let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); + let mut act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); + // trigger tag decryption error by corrupting byte 50 + act3[50] = 0; + + assert_eq!(test_ctx.inbound_handshake.process_act(&act3).err(), Some(String::from("invalid hmac"))); + } + + // Inbound::Complete -> Panic + #[test] + #[should_panic(expected = "no acts left to process")] + fn peer_handshake_new_inbound_complete_then_process_act() { + let mut test_ctx = TestCtx::new(); + let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); + let act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); + test_ctx.inbound_handshake.process_act(&act3).unwrap(); + + do_process_act_or_panic!(test_ctx.inbound_handshake, &[]); + } + + // Outbound::None -> Panic + #[test] + #[should_panic(expected = "no acts left to process")] + fn peer_handshake_new_outbound_complete_then_process_act() { + let mut test_ctx = TestCtx::new(); + let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); + let act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); + test_ctx.inbound_handshake.process_act(&act3).unwrap(); + + do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + } + + // Test the Act byte generation against known good hard-coded values in case the implementation + // changes in a symmetric way that makes the other tests useless + #[test] + fn peer_handshake_external_spec() { + let mut test_ctx = TestCtx::new(); + let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); + let act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); + + assert_eq!(hex::encode(&act1), + "00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a"); + assert_eq!(hex::encode(&act2), + "0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae"); + assert_eq!(hex::encode(&act3), + "00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba"); + } +} From 52da509797261e787ec37f0e5942f79ff8ea8978 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sun, 16 Aug 2020 21:13:48 -0700 Subject: [PATCH 04/45] delete: Remove duplicate tests --- lightning/src/ln/peers/handler.rs | 1 - lightning/src/ln/peers/handshake/mod.rs | 11 +++---- lightning/src/ln/peers/handshake/tests.rs | 39 ----------------------- 3 files changed, 5 insertions(+), 46 deletions(-) delete mode 100644 lightning/src/ln/peers/handshake/tests.rs diff --git a/lightning/src/ln/peers/handler.rs b/lightning/src/ln/peers/handler.rs index e9c2becd0fb..903f4ccfaa3 100644 --- a/lightning/src/ln/peers/handler.rs +++ b/lightning/src/ln/peers/handler.rs @@ -41,7 +41,6 @@ use bitcoin::hashes::sha256::HashEngine as Sha256Engine; use bitcoin::hashes::{HashEngine, Hash}; use ln::peers::handshake::PeerHandshake; use ln::peers::conduit::Conduit; -use ln::peers::handshake::acts::Act; /// Provides references to trait impls which handle different types of messages. pub struct MessageHandler where diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs index 2d95998aa2e..babdb32129b 100644 --- a/lightning/src/ln/peers/handshake/mod.rs +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -14,10 +14,9 @@ use ln::peers::handshake::acts::{ActOne, ActThree, ActTwo, ACT_ONE_LENGTH, ACT_T use ln::peers::handshake::hash::HandshakeHash; use ln::peers::handshake::states::{ActOneExpectation, ActThreeExpectation, ActTwoExpectation, HandshakeState}; -pub(crate) mod acts; +mod acts; mod hash; mod states; -mod tests; /// Object for managing handshakes. /// Currently requires explicit ephemeral private key specification. @@ -162,7 +161,7 @@ impl PeerHandshake { } /// Initiate the handshake with a peer and return the first act - pub fn initiate(&mut self, remote_public_key: &PublicKey) -> Result { + fn initiate(&mut self, remote_public_key: &PublicKey) -> Result { if let &Some(HandshakeState::Uninitiated) = &self.state {} else { return Err("Handshakes can only be initiated from the uninitiated state".to_string()); } @@ -188,7 +187,7 @@ impl PeerHandshake { } /// Process a peer's incoming first act and return the second act - pub(crate) fn process_act_one(&mut self, act: ActOne) -> Result { + fn process_act_one(&mut self, act: ActOne) -> Result { let state = self.state.take(); let act_one_expectation = match state { Some(HandshakeState::AwaitingActOne(act_state)) => act_state, @@ -235,7 +234,7 @@ impl PeerHandshake { } /// Process a peer's incoming second act and return the third act alongside a Conduit instance - pub(crate) fn process_act_two(&mut self, act: ActTwo) -> Result<(ActThree, Conduit), String> { + fn process_act_two(&mut self, act: ActTwo) -> Result<(ActThree, Conduit), String> { let state = self.state.take(); let act_two_expectation = match state { Some(HandshakeState::AwaitingActTwo(act_state)) => act_state, @@ -275,7 +274,7 @@ impl PeerHandshake { } /// Process a peer's incoming third act and return a Conduit instance - pub(crate) fn process_act_three(&mut self, act: ActThree) -> Result<(PublicKey, Conduit), String> { + fn process_act_three(&mut self, act: ActThree) -> Result<(PublicKey, Conduit), String> { let state = self.state.take(); let act_three_expectation = match state { Some(HandshakeState::AwaitingActThree(act_state)) => act_state, diff --git a/lightning/src/ln/peers/handshake/tests.rs b/lightning/src/ln/peers/handshake/tests.rs deleted file mode 100644 index e99500d1399..00000000000 --- a/lightning/src/ln/peers/handshake/tests.rs +++ /dev/null @@ -1,39 +0,0 @@ -#![cfg(test)] - -use hex; -use bitcoin::secp256k1; - -use bitcoin::secp256k1::key::{PublicKey, SecretKey}; - -use ln::peers::handshake::PeerHandshake; - -#[test] -fn test_exchange() { - let curve = secp256k1::Secp256k1::new(); - - let local_private_key = SecretKey::from_slice(&[0x_11_u8; 32]).unwrap(); - let remote_private_key = SecretKey::from_slice(&[0x_21_u8; 32]).unwrap(); - - let local_ephemeral_private_key = SecretKey::from_slice(&[0x_12_u8; 32]).unwrap(); - let remote_ephemeral_private_key = SecretKey::from_slice(&[0x_22_u8; 32]).unwrap(); - - let remote_public_key = PublicKey::from_secret_key(&curve, &remote_private_key); - - let mut local_handshake = PeerHandshake::new_outbound(&local_private_key, &remote_public_key, &local_ephemeral_private_key); - let mut remote_handshake = PeerHandshake::new_inbound(&remote_private_key, &remote_ephemeral_private_key); - - let act_1 = local_handshake.initiate(&remote_public_key).unwrap(); - let act_1_hex = hex::encode(&act_1.0.to_vec()); - assert_eq!(act_1_hex, "00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a"); - - let act_2 = remote_handshake.process_act_one(act_1).unwrap(); - let act_2_hex = hex::encode(&act_2.0.to_vec()); - assert_eq!(act_2_hex, "0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae"); - - let act_2_result = local_handshake.process_act_two(act_2).unwrap(); - let act_3 = act_2_result.0; - let act_3_hex = hex::encode(&act_3.0.to_vec()); - assert_eq!(act_3_hex, "00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba"); - - remote_handshake.process_act_three(act_3).unwrap(); -} From bfe6c4cb80b19cbf7f2026896aaa875e80e6b026 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Mon, 17 Aug 2020 15:32:56 -0700 Subject: [PATCH 05/45] refactor: Create enum-dispatch NOISE state machine The implementation logic is duplicated from handshake/mod.rs so that it can be tested and refactored independent of the current implementation. This uses enum dispatch coupled with separately 'moved' state objects that allow for simple testing, easy state inspection, and understandable transition logic between states. This pattern also removes the need for match statements inside implementation logic in favor of the instance representing the current state and available data. Every state transition is implemented in a next() function and is a straight forward translation from an object representing the existing known data and an input act message (bytes). Example usage: let (act_data, next_state) = cur_state.next(input_data); This state machine will eventually be moved into place inside the PeerHandshake which will act as a simple wrapper that just maintains the state machine and marshals the data between the peer_handler and states. --- lightning/src/ln/peers/handshake/states.rs | 661 ++++++++++++++++++++- 1 file changed, 660 insertions(+), 1 deletion(-) diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 2a7401f5e9b..3f2649bfdd8 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -1,6 +1,14 @@ -use ln::peers::handshake::hash::HandshakeHash; +use bitcoin::secp256k1; + use bitcoin::secp256k1::{SecretKey, PublicKey}; +use ln::peers::handshake::hash::HandshakeHash; +use ln::peers::handshake::acts::{Act, ActOne, ACT_ONE_LENGTH, ActTwo, ACT_TWO_LENGTH, ACT_THREE_LENGTH, ActThree}; +use ln::peers::handshake::states::HandshakeState2::{AwaitingActTwo2, AwaitingActThree2, Complete2}; +use ln::peers::handshake::PeerHandshake; +use ln::peers::{chacha, hkdf}; +use ln::peers::conduit::Conduit; + pub enum HandshakeState { Uninitiated, AwaitingActOne(ActOneExpectation), @@ -9,6 +17,298 @@ pub enum HandshakeState { Complete, } +pub enum HandshakeState2 { + Uninitiated2(UninitiatedHandshakeState), + AwaitingActOne2(AwaitingActOneHandshakeState), + AwaitingActTwo2(AwaitingActTwoHandshakeState), + AwaitingActThree2(AwaitingActThreeHandshakeState), + Complete2(Option<(Conduit, PublicKey)>), +} + +impl HandshakeState2 { + pub(crate) fn next(self, input: &[u8]) -> Result<(Option, HandshakeState2), String> { + match self { + HandshakeState2::Uninitiated2(state) => { state.next(input) }, + HandshakeState2::AwaitingActOne2(state) => { state.next(input) }, + HandshakeState2::AwaitingActTwo2(state) => { state.next(input) }, + HandshakeState2::AwaitingActThree2(state) => { state.next(input) }, + HandshakeState2::Complete2(_conduit) => { panic!("no acts left to process") } + } + } +} + +trait IHandshakeState { + fn next(self, input: &[u8]) -> Result<(Option, HandshakeState2), String>; +} + +pub struct UninitiatedHandshakeState { + initiator_private_key: SecretKey, + initiator_ephemeral_private_key: SecretKey, + responder_public_key: PublicKey, +} + +pub struct AwaitingActOneHandshakeState { + responder_private_key: SecretKey, + responder_ephemeral_private_key: SecretKey, + chaining_key: [u8; 32], + hash: HandshakeHash, + read_buffer: Vec +} + +pub struct AwaitingActTwoHandshakeState { + initiator_private_key: SecretKey, + initiator_ephemeral_private_key: SecretKey, + responder_public_key: PublicKey, + chaining_key: [u8; 32], + hash: HandshakeHash, + read_buffer: Vec +} + +pub struct AwaitingActThreeHandshakeState { + hash: HandshakeHash, + responder_ephemeral_private_key: SecretKey, + chaining_key: [u8; 32], + temporary_key: [u8; 32], + read_buffer: Vec +} + +impl UninitiatedHandshakeState { + pub(crate) fn new(initiator_private_key: SecretKey, initiator_ephemeral_private_key: SecretKey, responder_public_key: PublicKey) -> Self { + UninitiatedHandshakeState { + initiator_private_key, + initiator_ephemeral_private_key, + responder_public_key + } + } +} + +impl IHandshakeState for UninitiatedHandshakeState { + fn next(self, _input: &[u8]) -> Result<(Option, HandshakeState2), String> { + + let initiator_private_key = self.initiator_private_key; + let initiator_ephemeral_private_key = self.initiator_ephemeral_private_key; + let responder_public_key = self.responder_public_key; + + let (mut hash, chaining_key) = PeerHandshake::initialize_state(&responder_public_key); + + // serialize act one + let (act_one, chaining_key, _) = PeerHandshake::calculate_act_message( + &initiator_ephemeral_private_key, + &responder_public_key, + chaining_key, + &mut hash, + ); + + Ok(( + Some(Act::One(ActOne(act_one))), + AwaitingActTwo2(AwaitingActTwoHandshakeState::new(initiator_private_key, initiator_ephemeral_private_key, responder_public_key, chaining_key, hash)) + )) + } +} + +impl AwaitingActOneHandshakeState { + pub(crate) fn new(responder_private_key: SecretKey, responder_ephemeral_private_key: SecretKey) -> Self { + + let curve = secp256k1::Secp256k1::new(); + let responder_public_key = PublicKey::from_secret_key(&curve, &responder_private_key); + let (hash, chaining_key) = PeerHandshake::initialize_state(&responder_public_key); + + AwaitingActOneHandshakeState { + responder_private_key, + responder_ephemeral_private_key, + chaining_key, + hash, + read_buffer: Vec::new() + } + } +} + +impl IHandshakeState for AwaitingActOneHandshakeState { + fn next(self, input: &[u8]) -> Result<(Option, HandshakeState2), String> { + + let mut read_buffer = self.read_buffer; + read_buffer.extend_from_slice(input); + + if read_buffer.len() < ACT_ONE_LENGTH { + return Err("need at least 50 bytes".to_string()); + } + + let mut hash = self.hash; + let responder_private_key = self.responder_private_key; + let chaining_key = self.chaining_key; + let responder_ephemeral_private_key = self.responder_ephemeral_private_key; + + // common functions take in an array so drain here for now + let mut act_one_bytes = [0u8; ACT_ONE_LENGTH]; + act_one_bytes.copy_from_slice(&read_buffer[..ACT_ONE_LENGTH]); + read_buffer.drain(..ACT_ONE_LENGTH); + + let (initiator_ephemeral_public_key, chaining_key, _) = PeerHandshake::process_act_message( + act_one_bytes, + &responder_private_key, + chaining_key, + &mut hash, + )?; + + let (act_two, chaining_key, temporary_key) = PeerHandshake::calculate_act_message( + &responder_ephemeral_private_key, + &initiator_ephemeral_public_key, + chaining_key, + &mut hash, + ); + + Ok(( + Some(Act::Two(ActTwo(act_two))), + AwaitingActThree2( + AwaitingActThreeHandshakeState::new(hash, responder_ephemeral_private_key, chaining_key, temporary_key, read_buffer) + ) + )) + } +} + +impl IHandshakeState for AwaitingActTwoHandshakeState { + fn next(self, input: &[u8]) -> Result<(Option, HandshakeState2), String> { + + let mut read_buffer = self.read_buffer; + read_buffer.extend_from_slice(input); + + if read_buffer.len() < ACT_TWO_LENGTH { + return Err("need at least 50 bytes".to_string()); + } + + let initiator_private_key = self.initiator_private_key; + let initiator_ephemeral_private_key = self.initiator_ephemeral_private_key; + let responder_public_key = self.responder_public_key; + let mut hash = self.hash; + let chaining_key = self.chaining_key; + + // common functions take in an array so drain here for now + let mut act_two_bytes = [0u8; ACT_TWO_LENGTH]; + act_two_bytes.copy_from_slice(&read_buffer[..ACT_TWO_LENGTH]); + read_buffer.drain(..ACT_TWO_LENGTH); + + let (responder_ephemeral_public_key, chaining_key, temporary_key) = PeerHandshake::process_act_message( + act_two_bytes, + &initiator_ephemeral_private_key, + chaining_key, + &mut hash, + )?; + + // start serializing act three + + let curve = secp256k1::Secp256k1::new(); + let initiator_public_key = PublicKey::from_secret_key(&curve, &initiator_private_key); + let tagged_encrypted_pubkey = chacha::encrypt(&temporary_key, 1, &hash.value, &initiator_public_key.serialize()); + hash.update(&tagged_encrypted_pubkey); + + let ecdh = PeerHandshake::ecdh(&initiator_private_key, &responder_ephemeral_public_key); + let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); + let authentication_tag = chacha::encrypt(&temporary_key, 0, &hash.value, &[0; 0]); + let (sending_key, receiving_key) = hkdf::derive(&chaining_key, &[0; 0]); + + let mut act_three = [0u8; ACT_THREE_LENGTH]; + act_three[1..50].copy_from_slice(&tagged_encrypted_pubkey); + act_three[50..].copy_from_slice(authentication_tag.as_slice()); + + let mut conduit = Conduit::new(sending_key, receiving_key, chaining_key); + + if read_buffer.len() > 0 { // have we received more data still? + conduit.read(&read_buffer[..]); + read_buffer.drain(..); + } + + Ok(( + Some(Act::Three(ActThree(act_three))), + Complete2(Some((conduit, responder_public_key))) + )) + } +} + +impl AwaitingActTwoHandshakeState { + fn new(initiator_private_key: SecretKey, initiator_ephemeral_private_key: SecretKey, responder_public_key: PublicKey, chaining_key: [u8;32], hash: HandshakeHash) -> Self { + AwaitingActTwoHandshakeState { + initiator_private_key, + initiator_ephemeral_private_key, + responder_public_key, + chaining_key, + hash, + read_buffer: Vec::new() + } + } +} + +impl IHandshakeState for AwaitingActThreeHandshakeState { + fn next(self, input: &[u8]) -> Result<(Option, HandshakeState2), String> { + let mut read_buffer = self.read_buffer; + read_buffer.extend_from_slice(input); + + if read_buffer.len() < ACT_THREE_LENGTH { + return Err("need at least 66 bytes".to_string()); + } + + let mut hash = self.hash; + let temporary_key = self.temporary_key; + let responder_ephemeral_private_key = self.responder_ephemeral_private_key; + let chaining_key = self.chaining_key; + + let mut act_three_bytes = [0u8; ACT_THREE_LENGTH]; + act_three_bytes.copy_from_slice(&read_buffer[..ACT_THREE_LENGTH]); + read_buffer.drain(..ACT_THREE_LENGTH); + + let version = act_three_bytes[0]; + if version != 0 { + // this should not crash the process, hence no panic + return Err("unexpected version".to_string()); + } + + let mut tagged_encrypted_pubkey = [0u8; 49]; + tagged_encrypted_pubkey.copy_from_slice(&act_three_bytes[1..50]); + + let mut chacha_tag = [0u8; 16]; + chacha_tag.copy_from_slice(&act_three_bytes[50..66]); + + let remote_pubkey_vec = chacha::decrypt(&temporary_key, 1, &hash.value, &tagged_encrypted_pubkey)?; + let mut initiator_pubkey_bytes = [0u8; 33]; + initiator_pubkey_bytes.copy_from_slice(remote_pubkey_vec.as_slice()); + let initiator_pubkey = if let Ok(public_key) = PublicKey::from_slice(&initiator_pubkey_bytes) { + public_key + } else { + return Err("invalid remote public key".to_string()); + }; + + hash.update(&tagged_encrypted_pubkey); + + let ecdh = PeerHandshake::ecdh(&responder_ephemeral_private_key, &initiator_pubkey); + let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); + let _tag_check = chacha::decrypt(&temporary_key, 0, &hash.value, &chacha_tag)?; + let (receiving_key, sending_key) = hkdf::derive(&chaining_key, &[0; 0]); + + let mut conduit = Conduit::new(sending_key, receiving_key, chaining_key); + + if read_buffer.len() > 0 { // have we received more data still? + conduit.read(&read_buffer[..]); + read_buffer.drain(..); + } + + Ok(( + None, + Complete2(Some((conduit, initiator_pubkey))) + )) + } +} + +impl AwaitingActThreeHandshakeState { + fn new(hash: HandshakeHash, responder_ephemeral_private_key: SecretKey, chaining_key: [u8; 32], temporary_key: [u8; 32], read_buffer: Vec) -> Self { + AwaitingActThreeHandshakeState { + hash, + responder_ephemeral_private_key, + chaining_key, + temporary_key, + read_buffer + } + } +} + pub struct ActOneExpectation { pub(super) hash: HandshakeHash, pub(super) chaining_key: [u8; 32], @@ -28,3 +328,362 @@ pub struct ActThreeExpectation { pub(super) ephemeral_private_key: SecretKey, pub(super) remote_ephemeral_public_key: PublicKey, } + +#[cfg(test)] +mod test { + use hex; + + use bitcoin::secp256k1; + use bitcoin::secp256k1::{PublicKey, SecretKey}; + + use ln::peers::handshake::acts::Act; + use ln::peers::handshake::states::{UninitiatedHandshakeState, AwaitingActOneHandshakeState, HandshakeState2}; + use ln::peers::handshake::states::HandshakeState2::{AwaitingActThree2, AwaitingActTwo2, Complete2}; + + struct TestCtx { + initiator: HandshakeState2, + initiator_public_key: PublicKey, + responder: HandshakeState2, + responder_public_key: PublicKey + } + + impl TestCtx { + fn new() -> Self { + let curve = secp256k1::Secp256k1::new(); + let initiator_private_key = SecretKey::from_slice(&[0x_11_u8; 32]).unwrap(); + let initiator_public_key = PublicKey::from_secret_key(&curve, &initiator_private_key); + let initiator_ephemeral_private_key = SecretKey::from_slice(&[0x_12_u8; 32]).unwrap(); + + let responder_private_key = SecretKey::from_slice(&[0x_21_u8; 32]).unwrap(); + let responder_public_key = PublicKey::from_secret_key(&curve, &responder_private_key); + let responder_ephemeral_private_key = SecretKey::from_slice(&[0x_22_u8; 32]).unwrap(); + + let initiator = UninitiatedHandshakeState::new(initiator_private_key, initiator_ephemeral_private_key, responder_public_key); + let responder = AwaitingActOneHandshakeState::new(responder_private_key, responder_ephemeral_private_key); + + TestCtx { + initiator: HandshakeState2::Uninitiated2(initiator), + initiator_public_key, + responder: HandshakeState2::AwaitingActOne2(responder), + responder_public_key, + } + } + } + + macro_rules! do_next_or_panic { + ($state:expr, $input:expr) => { + if let (Some(output_act), next_state) = $state.next($input).unwrap() { + (output_act.serialize(), next_state) + } else { + panic!(); + } + } + } + + macro_rules! assert_matches { + ($e:expr, $state_match:pat) => { + match $e { + $state_match => (), + _ => panic!() + } + } + } + + // Initiator::Uninitiated -> AwaitingActTwo + #[test] + fn uninitiated_to_awaiting_act_two() { + let test_ctx = TestCtx::new(); + + assert_matches!(test_ctx.initiator.next(&[]).unwrap(), (Some(Act::One(_)), AwaitingActTwo2(_))); + } + + // Initiator::Uninitiated -> AwaitingActTwo (extra bytes in argument) + #[test] + fn uninitiated_to_awaiting_act_two_extra_bytes() { + let test_ctx = TestCtx::new(); + + assert_matches!(test_ctx.initiator.next(&[1]).unwrap(), (Some(Act::One(_)), AwaitingActTwo2(_))); + } + + // Responder::AwaitingActOne -> Error (input too small) + #[test] + fn awaiting_act_one_to_awaiting_act_three_input_too_small() { + let test_ctx = TestCtx::new(); + assert_eq!(test_ctx.responder.next(&[]).err(), Some(String::from("need at least 50 bytes"))) + } + + // Responder::AwaitingActOne -> AwaitingActThree + // TODO: Should this fail since we don't expect data > ACT_ONE_LENGTH and likely indicates + // a bad peer? + // TODO: Should the behavior be changed to handle act1 data that is striped across multiple + // next() calls? + #[test] + fn awaiting_act_one_to_awaiting_act_three_input_extra_bytes() { + let test_ctx = TestCtx::new(); + let (mut act1, _) = do_next_or_panic!(test_ctx.initiator, &[]); + act1.extend_from_slice(&[1]); + + assert_matches!(test_ctx.responder.next(&act1).unwrap(), (Some(Act::Two(_)), AwaitingActThree2(_))); + } + + // Responder::AwaitingActOne -> Error (bad version byte) + #[test] + fn awaiting_act_one_to_awaiting_act_three_input_bad_version() { + let test_ctx = TestCtx::new(); + let (mut act1, _) = do_next_or_panic!(test_ctx.initiator, &[]); + // set version byte to 1 + act1[0] = 1; + + assert_eq!(test_ctx.responder.next(&act1).err(), Some(String::from("unexpected version"))); + } + + // Responder::AwaitingActOne -> Error (invalid hmac) + #[test] + fn awaiting_act_one_to_awaiting_act_three_invalid_hmac() { + let test_ctx = TestCtx::new(); + // Modify the initiator to point to a different responder + let (mut act1, _) = do_next_or_panic!(test_ctx.initiator, &[]); + // corrupt the ciphertext + act1[34] = 0; + + assert_eq!(test_ctx.responder.next(&act1).err(), Some(String::from("invalid hmac"))); + } + + // Responder::AwaitingActOne -> Error (invalid remote ephemeral key) + #[test] + fn awaiting_act_one_to_awaiting_act_three_invalid_remote_ephemeral_key() { + let test_ctx = TestCtx::new(); + // Modify the initiator to point to a different responder + let (mut act1, _) = do_next_or_panic!(test_ctx.initiator, &[]); + // corrupt the ephemeral public key + act1[1] = 0; + + assert_eq!(test_ctx.responder.next(&act1).err(), Some(String::from("invalid remote ephemeral public key"))); + } + + // Responder::AwaitingActOne -> AwaitingActThree + #[test] + fn awaiting_act_one_to_awaiting_act_three() { + let test_ctx = TestCtx::new(); + let (act1, _) = do_next_or_panic!(test_ctx.initiator, &[]); + + assert_matches!(test_ctx.responder.next(&act1).unwrap(), (Some(Act::Two(_)), AwaitingActThree2(_))); + } + + // Initiator::AwaitingActTwo -> Complete + #[test] + fn awaiting_act_two_to_complete() { + let test_ctx = TestCtx::new(); + let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let (act2, _awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); + + let remote_pubkey = if let (Some(Act::Three(_)), Complete2(Some((_, remote_pubkey)))) = awaiting_act_two_state.next(&act2).unwrap() { + remote_pubkey + } else { + panic!(); + }; + + assert_eq!(remote_pubkey, test_ctx.responder_public_key); + } + + // Initiator::AwaitingActTwo -> Complete (with extra data) + // Ensures that any remaining data in the read buffer is transferred to the conduit once + // the handshake is complete + // TODO: Is this valid? Don't we expect peers to need ActThree before sending additional data? + #[test] + fn awaiting_act_two_to_complete_excess_bytes_are_in_conduit() { + let test_ctx = TestCtx::new(); + let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let (mut act2, _awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); + act2.extend_from_slice(&[1; 100]); + + let (_act3, complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); + + let conduit = if let Complete2(Some((conduit, _))) = complete_state { + conduit + } else { + panic!(); + }; + + assert_eq!(100, conduit.decryptor.read_buffer_length()); + } + + // Initiator::AwaitingActTwo -> Error (input too small) + #[test] + fn awaiting_act_two_input_too_small() { + let test_ctx = TestCtx::new(); + let (_act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + + assert_eq!(awaiting_act_two_state.next(&[]).err(), Some(String::from("need at least 50 bytes"))); + } + + // Initiator::AwaitingActTwo -> Error (bad version byte) + #[test] + fn awaiting_act_two_bad_version_byte() { + let test_ctx = TestCtx::new(); + let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let (mut act2, _awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); + // set invalid version byte + act2[0] = 1; + + assert_eq!(awaiting_act_two_state.next(&act2).err(), Some(String::from("unexpected version"))); + } + + // Initiator::AwaitingActTwo -> Error (invalid hmac) + #[test] + fn awaiting_act_two_invalid_hmac() { + let test_ctx = TestCtx::new(); + let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let (mut act2, _awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); + // corrupt the ciphertext + act2[34] = 0; + + assert_eq!(awaiting_act_two_state.next(&act2).err(), Some(String::from("invalid hmac"))); + } + + // Initiator::AwaitingActTwo -> Error (invalid ephemeral public key) + #[test] + fn awaiting_act_two_invalid_ephemeral_public_key() { + let test_ctx = TestCtx::new(); + let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let (mut act2, _awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); + // corrupt the ephemeral public key + act2[1] = 0; + + assert_eq!(awaiting_act_two_state.next(&act2).err(), Some(String::from("invalid remote ephemeral public key"))); + } + + // Responder::AwaitingActThree -> Complete + #[test] + fn awaiting_act_three_to_complete() { + let test_ctx = TestCtx::new(); + let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let (act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); + let (act3, _complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); + + let remote_pubkey = if let (None, Complete2(Some((_, remote_pubkey)))) = awaiting_act_three_state.next(&act3).unwrap() { + remote_pubkey + } else { + panic!(); + }; + + assert_eq!(remote_pubkey, test_ctx.initiator_public_key); + } + + // Responder::AwaitingActThree -> None (with extra bytes) + // Ensures that any remaining data in the read buffer is transferred to the conduit once + // the handshake is complete + #[test] + fn awaiting_act_three_excess_bytes_after_complete_are_in_conduit() { + let test_ctx = TestCtx::new(); + let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let (act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); + let (mut act3, _complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); + act3.extend_from_slice(&[2; 100]); + + let conduit = if let (_, Complete2(Some((conduit, _)))) = awaiting_act_three_state.next(&act3).unwrap() { + conduit + } else { + panic!(); + }; + + assert_eq!(100, conduit.decryptor.read_buffer_length()); + } + + // Responder::AwaitingActThree -> Error (input too small) + #[test] + fn awaiting_act_three_input_too_small() { + let test_ctx = TestCtx::new(); + let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let (act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); + let (act3, _complete) = do_next_or_panic!(awaiting_act_two_state, &act2); + + assert_eq!(awaiting_act_three_state.next(&act3[..65]).err(), Some(String::from("need at least 66 bytes"))); + } + + // Responder::AwaitingActThree -> Error (bad version bytes) + #[test] + fn awaiting_act_three_bad_version_bytes() { + let test_ctx = TestCtx::new(); + let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let (act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); + let (mut act3, _complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); + // set version byte to 1 + act3[0] = 1; + + assert_eq!(awaiting_act_three_state.next(&act3).err(), Some(String::from("unexpected version"))); + } + + // Responder::AwaitingActThree -> Error (invalid hmac) + #[test] + fn awaiting_act_three_invalid_hmac() { + let test_ctx = TestCtx::new(); + let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let (act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); + let (mut act3, _complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); + // corrupt encrypted pubkey + act3[1] = 1; + + assert_eq!(awaiting_act_three_state.next(&act3).err(), Some(String::from("invalid hmac"))); + } + + // Responder::AwaitingActThree -> Error (invalid tag hmac) + #[test] + fn awaiting_act_three_invalid_tag_hmac() { + let test_ctx = TestCtx::new(); + let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let (act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); + let (mut act3, _complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); + // corrupt tag + act3[50] = 1; + + assert_eq!(awaiting_act_three_state.next(&act3).err(), Some(String::from("invalid hmac"))); + } + + // Initiator::Complete -> Error + #[test] + #[should_panic(expected = "no acts left to process")] + fn initiator_complete_next_fail() { + let test_ctx = TestCtx::new(); + let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let (act2, _awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); + let (_act3, complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); + + complete_state.next(&[]).unwrap(); + } + + // Initiator::Complete -> Error + #[test] + #[should_panic(expected = "no acts left to process")] + fn responder_complete_next_fail() { + let test_ctx = TestCtx::new(); + let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let (act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); + let (act3, _complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); + + let complete_state = if let (None, complete_state) = awaiting_act_three_state.next(&act3).unwrap() { + complete_state + } else { + panic!(); + }; + + complete_state.next(&[]).unwrap(); + } + + // Test the Act byte generation against known good hard-coded values in case the implementation + // changes in a symmetric way that makes the other tests useless + #[test] + fn test_acts_against_reference_bytes() { + let test_ctx = TestCtx::new(); + let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let (act2, _awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); + let (act3, _complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); + + assert_eq!(hex::encode(&act1), + "00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a"); + assert_eq!(hex::encode(&act2), + "0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae"); + assert_eq!(hex::encode(&act3), + "00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba"); + } +} From c154f09af83b49bb650ed0a6f3357fe8bfd4b4f8 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Tue, 18 Aug 2020 20:13:39 -0700 Subject: [PATCH 06/45] refactor: Use new state machine in PeerHandshake PeerHandshake is now a simple object around the new state machine that simply marshals data between the callers and state machine states. For now, keep the APIs identical to make the patches easier to read. The only interesting piece to note here is the handling of get_remote_pubkey() to keep parity with the existing implementation. Future patches will remove this separate function in favor of callers taking ownership of it once the handshake is complete. --- lightning/src/ln/peers/handshake/mod.rs | 285 +++------------------ lightning/src/ln/peers/handshake/states.rs | 28 -- 2 files changed, 30 insertions(+), 283 deletions(-) diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs index babdb32129b..987af5339af 100644 --- a/lightning/src/ln/peers/handshake/mod.rs +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -10,9 +10,9 @@ use bitcoin::secp256k1::{PublicKey, SecretKey}; use ln::peers::{chacha, hkdf}; use ln::peers::conduit::{Conduit, SymmetricKey}; -use ln::peers::handshake::acts::{ActOne, ActThree, ActTwo, ACT_ONE_LENGTH, ACT_TWO_LENGTH, ACT_THREE_LENGTH, Act}; +use ln::peers::handshake::acts::Act; use ln::peers::handshake::hash::HandshakeHash; -use ln::peers::handshake::states::{ActOneExpectation, ActThreeExpectation, ActTwoExpectation, HandshakeState}; +use ln::peers::handshake::states::{HandshakeState2, UninitiatedHandshakeState, AwaitingActOneHandshakeState}; mod acts; mod hash; @@ -21,42 +21,25 @@ mod states; /// Object for managing handshakes. /// Currently requires explicit ephemeral private key specification. pub struct PeerHandshake { - state: Option, - private_key: SecretKey, + state: Option, remote_public_key: Option, - ephemeral_private_key: SecretKey, - - read_buffer: Vec, } impl PeerHandshake { /// Instantiate a new handshake with a node identity secret key and an ephemeral private key pub fn new_outbound(private_key: &SecretKey, remote_public_key: &PublicKey, ephemeral_private_key: &SecretKey) -> Self { Self { - state: Some(HandshakeState::Uninitiated), - private_key: (*private_key).clone(), + state: Some(HandshakeState2::Uninitiated2(UninitiatedHandshakeState::new(private_key.clone(), ephemeral_private_key.clone(), remote_public_key.clone()))), remote_public_key: Some(remote_public_key.clone()), - ephemeral_private_key: (*ephemeral_private_key).clone(), - read_buffer: Vec::new(), } } /// Instantiate a new handshake in anticipation of a peer's first handshake act pub fn new_inbound(private_key: &SecretKey, ephemeral_private_key: &SecretKey) -> Self { - let mut handshake = Self { - state: Some(HandshakeState::Uninitiated), - private_key: (*private_key).clone(), + Self { + state: Some(HandshakeState2::AwaitingActOne2(AwaitingActOneHandshakeState::new(private_key.clone(), ephemeral_private_key.clone()))), remote_public_key: None, - ephemeral_private_key: (*ephemeral_private_key).clone(), - read_buffer: Vec::new(), - }; - let public_key = Self::private_key_to_public_key(&private_key); - let (hash, chaining_key) = Self::initialize_state(&public_key); - handshake.state = Some(HandshakeState::AwaitingActOne(ActOneExpectation { - hash, - chaining_key, - })); - handshake + } } /// Return the remote public key once it has been extracted from the third act. @@ -91,231 +74,23 @@ impl PeerHandshake { /// `.0`: Byte vector containing the next act to send back to the peer per the handshake protocol /// `.1`: Conduit option if the handshake was just processed to completion and messages can now be encrypted and decrypted pub fn process_act(&mut self, input: &[u8]) -> Result<(Option, Option), String> { - let mut response = None; - let mut connected_peer = None; - - self.read_buffer.extend_from_slice(input); - let read_buffer_length = self.read_buffer.len(); - - match &self.state { - &Some(HandshakeState::Uninitiated) => { - let remote_public_key = &self.remote_public_key.ok_or("outbound connections must be initialized with new_outbound")?; - let act_one = self.initiate(&remote_public_key)?; - response = Some(Act::One(act_one)); - } - &Some(HandshakeState::AwaitingActOne(_)) => { - if read_buffer_length < ACT_ONE_LENGTH { - return Err("need at least 50 bytes".to_string()); - } - - let mut act_one_buffer = [0u8; ACT_ONE_LENGTH]; - act_one_buffer.copy_from_slice(&self.read_buffer[..ACT_ONE_LENGTH]); - self.read_buffer.drain(..ACT_ONE_LENGTH); - - let act_two = self.process_act_one(ActOne(act_one_buffer))?; - response = Some(Act::Two(act_two)); - } - &Some(HandshakeState::AwaitingActTwo(_)) => { - if read_buffer_length < ACT_TWO_LENGTH { - return Err("need at least 50 bytes".to_string()); - } - - let mut act_two_buffer = [0u8; ACT_TWO_LENGTH]; - act_two_buffer.copy_from_slice(&self.read_buffer[..ACT_TWO_LENGTH]); - self.read_buffer.drain(..ACT_TWO_LENGTH); - - let (act_three, mut conduit) = self.process_act_two(ActTwo(act_two_buffer))?; - - if self.read_buffer.len() > 0 { // have we received more data still? - conduit.read(&self.read_buffer[..]); - self.read_buffer.drain(..); - } - - response = Some(Act::Three(act_three)); - connected_peer = Some(conduit); - } - &Some(HandshakeState::AwaitingActThree(_)) => { - if read_buffer_length < ACT_THREE_LENGTH { - return Err("need at least 66 bytes".to_string()); - } - - let mut act_three_buffer = [0u8; ACT_THREE_LENGTH]; - act_three_buffer.copy_from_slice(&self.read_buffer[..ACT_THREE_LENGTH]); - self.read_buffer.drain(..ACT_THREE_LENGTH); - - let (public_key, mut conduit) = self.process_act_three(ActThree(act_three_buffer))?; - - if self.read_buffer.len() > 0 { // have we received more data still? - conduit.read(&self.read_buffer[..]); - self.read_buffer.drain(..); - } - - connected_peer = Some(conduit); - self.remote_public_key = Some(public_key); - } - _ => { - panic!("no acts left to process"); - } - }; - Ok((response, connected_peer)) - } - - /// Initiate the handshake with a peer and return the first act - fn initiate(&mut self, remote_public_key: &PublicKey) -> Result { - if let &Some(HandshakeState::Uninitiated) = &self.state {} else { - return Err("Handshakes can only be initiated from the uninitiated state".to_string()); - } - - let (mut hash, chaining_key) = Self::initialize_state(&remote_public_key); - - // serialize act one - let (act_one, chaining_key, temporary_key) = Self::calculate_act_message( - &self.ephemeral_private_key, - remote_public_key, - chaining_key, - &mut hash, - ); - - self.state = Some(HandshakeState::AwaitingActTwo(ActTwoExpectation { - hash, - chaining_key, - temporary_key, - ephemeral_private_key: (*&self.ephemeral_private_key).clone(), - })); - - Ok(ActOne(act_one)) - } - - /// Process a peer's incoming first act and return the second act - fn process_act_one(&mut self, act: ActOne) -> Result { - let state = self.state.take(); - let act_one_expectation = match state { - Some(HandshakeState::AwaitingActOne(act_state)) => act_state, - Some(HandshakeState::Uninitiated) => { - let public_key = Self::private_key_to_public_key(&self.private_key); - let (hash, chaining_key) = Self::initialize_state(&public_key); - ActOneExpectation { - hash, - chaining_key, - } - } - _ => { - self.state = state; - panic!("unexpected state"); - } - }; - - let mut hash = act_one_expectation.hash; - let (remote_ephemeral_public_key, chaining_key, _) = Self::process_act_message( - act.0, - &self.private_key, - act_one_expectation.chaining_key, - &mut hash, - )?; - - let ephemeral_private_key = (*&self.ephemeral_private_key).clone(); - - let (act_two, chaining_key, temporary_key) = Self::calculate_act_message( - &ephemeral_private_key, - &remote_ephemeral_public_key, - chaining_key, - &mut hash, - ); - - self.state = Some(HandshakeState::AwaitingActThree(ActThreeExpectation { - hash, - chaining_key, - temporary_key, - ephemeral_private_key, - remote_ephemeral_public_key, - })); - - Ok(ActTwo(act_two)) - } - - /// Process a peer's incoming second act and return the third act alongside a Conduit instance - fn process_act_two(&mut self, act: ActTwo) -> Result<(ActThree, Conduit), String> { - let state = self.state.take(); - let act_two_expectation = match state { - Some(HandshakeState::AwaitingActTwo(act_state)) => act_state, - _ => { - self.state = state; - panic!("unexpected state".to_string()); - } - }; - - let mut hash = act_two_expectation.hash; - let (remote_ephemeral_public_key, chaining_key, temporary_key) = Self::process_act_message( - act.0, - &act_two_expectation.ephemeral_private_key, - act_two_expectation.chaining_key, - &mut hash, - )?; - - self.state = Some(HandshakeState::Complete); - - // start serializing act three - - let static_public_key = Self::private_key_to_public_key(&self.private_key); - let tagged_encrypted_pubkey = chacha::encrypt(&temporary_key, 1, &hash.value, &static_public_key.serialize()); - hash.update(&tagged_encrypted_pubkey); - - let ecdh = Self::ecdh(&self.private_key, &remote_ephemeral_public_key); - let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); - let authentication_tag = chacha::encrypt(&temporary_key, 0, &hash.value, &[0; 0]); - let (sending_key, receiving_key) = hkdf::derive(&chaining_key, &[0; 0]); - - let mut act_three = [0u8; ACT_THREE_LENGTH]; - act_three[1..50].copy_from_slice(&tagged_encrypted_pubkey); - act_three[50..].copy_from_slice(authentication_tag.as_slice()); - - let connected_peer = Conduit::new(sending_key, receiving_key, chaining_key); - Ok((ActThree(act_three), connected_peer)) - } - - /// Process a peer's incoming third act and return a Conduit instance - fn process_act_three(&mut self, act: ActThree) -> Result<(PublicKey, Conduit), String> { - let state = self.state.take(); - let act_three_expectation = match state { - Some(HandshakeState::AwaitingActThree(act_state)) => act_state, - _ => { - self.state = state; - panic!("unexpected state".to_string()); - } - }; - - let version = act.0[0]; - if version != 0 { - // this should not crash the process, hence no panic - return Err("unexpected version".to_string()); - } + let cur_state = self.state.take().unwrap(); - let mut tagged_encrypted_pubkey = [0u8; 49]; - tagged_encrypted_pubkey.copy_from_slice(&act.0[1..50]); + let (act_opt, mut next_state) = cur_state.next(input)?; - let mut chacha_tag = [0u8; 16]; - chacha_tag.copy_from_slice(&act.0[50..66]); - - let mut hash = act_three_expectation.hash; + let result = match next_state { + HandshakeState2::Complete2(ref mut conduit_and_pubkey) => { + let (conduit, remote_pubkey) = conduit_and_pubkey.take().unwrap(); + self.remote_public_key = Some(remote_pubkey); - let remote_pubkey_vec = chacha::decrypt(&act_three_expectation.temporary_key, 1, &hash.value, &tagged_encrypted_pubkey)?; - let mut remote_pubkey_bytes = [0u8; 33]; - remote_pubkey_bytes.copy_from_slice(remote_pubkey_vec.as_slice()); - let remote_pubkey = if let Ok(public_key) = PublicKey::from_slice(&remote_pubkey_bytes) { - public_key - } else { - return Err("invalid remote public key".to_string()); + Ok((act_opt, Some(conduit))) + }, + _ => { Ok((act_opt, None)) } }; - hash.update(&tagged_encrypted_pubkey); + self.state = Some(next_state); - let ecdh = Self::ecdh(&act_three_expectation.ephemeral_private_key, &remote_pubkey); - let (chaining_key, temporary_key) = hkdf::derive(&act_three_expectation.chaining_key, &ecdh); - let _tag_check = chacha::decrypt(&temporary_key, 0, &hash.value, &chacha_tag)?; - let (receiving_key, sending_key) = hkdf::derive(&chaining_key, &[0; 0]); - - let connected_peer = Conduit::new(sending_key, receiving_key, chaining_key); - Ok((remote_pubkey, connected_peer)) + result } fn calculate_act_message(local_private_key: &SecretKey, remote_public_key: &PublicKey, chaining_key: [u8; 32], hash: &mut HandshakeHash) -> ([u8; 50], SymmetricKey, SymmetricKey) { @@ -401,7 +176,7 @@ mod test { use ln::peers::handshake::PeerHandshake; use ln::peers::handshake::acts::Act; - use ln::peers::handshake::states::HandshakeState; + use ln::peers::handshake::states::HandshakeState2; struct TestCtx { outbound_handshake: PeerHandshake, @@ -454,7 +229,7 @@ mod test { fn peer_handshake_new_outbound() { let test_ctx = TestCtx::new(); - assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState::Uninitiated)); + assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::Uninitiated2(_))); assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); } @@ -463,7 +238,7 @@ mod test { fn peer_handshake_new_inbound() { let test_ctx = TestCtx::new(); - assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState::AwaitingActOne(_))); + assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState2::AwaitingActOne2(_))); assert!(test_ctx.inbound_handshake.get_remote_pubkey().is_none()); } @@ -477,7 +252,7 @@ mod test { let mut test_ctx = TestCtx::new(); assert_matches!(test_ctx.outbound_handshake.process_act(&[]).unwrap(), (Some(Act::One(_)), None)); - assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState::AwaitingActTwo(_))); + assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::AwaitingActTwo2(_))); assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); } @@ -488,7 +263,7 @@ mod test { // TODO: process_act() should error if state does not use vec, but it is non-empty assert_matches!(test_ctx.outbound_handshake.process_act(&[1]).unwrap(), (Some(Act::One(_)), None)); - assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState::AwaitingActTwo(_))); + assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::AwaitingActTwo2(_))); assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); } @@ -509,7 +284,7 @@ mod test { act1.extend_from_slice(&[1]); assert_matches!(test_ctx.inbound_handshake.process_act(&act1).unwrap(), (Some(Act::Two(_)), None)); - assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState::AwaitingActThree(_))); + assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState2::AwaitingActThree2(_))); assert!(test_ctx.inbound_handshake.get_remote_pubkey().is_none()); } @@ -553,8 +328,8 @@ mod test { let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); assert_matches!(test_ctx.inbound_handshake.process_act(&act1).unwrap(), (Some(Act::Two(_)), None)); - assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState::AwaitingActThree(_))); - assert_eq!(test_ctx.inbound_handshake.get_remote_pubkey(), None); + assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState2::AwaitingActThree2(_))); + assert!(test_ctx.inbound_handshake.get_remote_pubkey().is_none()); } // Outbound::AwaitingActTwo -> Complete (valid conduit) @@ -565,7 +340,7 @@ mod test { let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); assert_matches!(test_ctx.outbound_handshake.process_act(&act2).unwrap(), (Some(Act::Three(_)), Some(_))); - assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState::Complete)); + assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::Complete2(_))); assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); } @@ -636,8 +411,7 @@ mod test { assert_eq!(test_ctx.outbound_handshake.process_act(&act2).err(), Some(String::from("invalid remote ephemeral public key"))); } - // Inbound::AwaitingActThree -> None - // TODO: should this transition to Complete instead of None? + // Inbound::AwaitingActThree -> Complete #[test] fn peer_handshake_new_inbound_awaiting_act_three_to_awaiting_act_three() { let mut test_ctx = TestCtx::new(); @@ -646,10 +420,11 @@ mod test { let act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); assert_matches!(test_ctx.inbound_handshake.process_act(&act3).unwrap(), (None, Some(_))); + assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState2::Complete2(_))); assert_eq!(test_ctx.inbound_handshake.get_remote_pubkey(), Some(test_ctx.outbound_public_key)); } - // Inbound::AwaitingActThree -> None (with extra bytes) + // Inbound::AwaitingActThree -> Complete (with extra bytes) // Ensures that any remaining data in the read buffer is transferred to the conduit once // the handshake is complete #[test] diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 3f2649bfdd8..b682d69930d 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -9,14 +9,6 @@ use ln::peers::handshake::PeerHandshake; use ln::peers::{chacha, hkdf}; use ln::peers::conduit::Conduit; -pub enum HandshakeState { - Uninitiated, - AwaitingActOne(ActOneExpectation), - AwaitingActTwo(ActTwoExpectation), - AwaitingActThree(ActThreeExpectation), - Complete, -} - pub enum HandshakeState2 { Uninitiated2(UninitiatedHandshakeState), AwaitingActOne2(AwaitingActOneHandshakeState), @@ -309,26 +301,6 @@ impl AwaitingActThreeHandshakeState { } } -pub struct ActOneExpectation { - pub(super) hash: HandshakeHash, - pub(super) chaining_key: [u8; 32], -} - -pub struct ActTwoExpectation { - pub(super) hash: HandshakeHash, - pub(super) chaining_key: [u8; 32], - pub(super) temporary_key: [u8; 32], - pub(super) ephemeral_private_key: SecretKey, -} - -pub struct ActThreeExpectation { - pub(super) hash: HandshakeHash, - pub(super) chaining_key: [u8; 32], - pub(super) temporary_key: [u8; 32], - pub(super) ephemeral_private_key: SecretKey, - pub(super) remote_ephemeral_public_key: PublicKey, -} - #[cfg(test)] mod test { use hex; From 9445feacde2b72b6116af70f896ef52f16e95253 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 19 Aug 2020 13:07:03 -0700 Subject: [PATCH 07/45] refactor: Move helper functions to the states.rs --- lightning/src/ln/peers/handshake/mod.rs | 98 +----------------- lightning/src/ln/peers/handshake/states.rs | 111 +++++++++++++++++++-- 2 files changed, 102 insertions(+), 107 deletions(-) diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs index 987af5339af..d2eb122c7c2 100644 --- a/lightning/src/ln/peers/handshake/mod.rs +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -2,16 +2,10 @@ //! Handshake states can be advanced automatically, or by manually calling the appropriate step. //! Once complete, returns an instance of Conduit. -use bitcoin::secp256k1; - -use bitcoin::hashes::{Hash, HashEngine}; -use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{PublicKey, SecretKey}; -use ln::peers::{chacha, hkdf}; -use ln::peers::conduit::{Conduit, SymmetricKey}; +use ln::peers::conduit::Conduit; use ln::peers::handshake::acts::Act; -use ln::peers::handshake::hash::HandshakeHash; use ln::peers::handshake::states::{HandshakeState2, UninitiatedHandshakeState, AwaitingActOneHandshakeState}; mod acts; @@ -48,23 +42,6 @@ impl PeerHandshake { self.remote_public_key } - fn initialize_state(public_key: &PublicKey) -> (HandshakeHash, [u8; 32]) { - let protocol_name = b"Noise_XK_secp256k1_ChaChaPoly_SHA256"; - let prologue = b"lightning"; - - let mut sha = Sha256::engine(); - sha.input(protocol_name); - let chaining_key = Sha256::from_engine(sha).into_inner(); - - let mut initial_hash_preimage = chaining_key.to_vec(); - initial_hash_preimage.extend_from_slice(prologue.as_ref()); - - let mut hash = HandshakeHash::new(initial_hash_preimage.as_slice()); - hash.update(&public_key.serialize()); - - (hash, chaining_key) - } - /// Process act dynamically /// # Arguments /// `input`: Byte slice received from peer as part of the handshake protocol @@ -92,79 +69,6 @@ impl PeerHandshake { result } - - fn calculate_act_message(local_private_key: &SecretKey, remote_public_key: &PublicKey, chaining_key: [u8; 32], hash: &mut HandshakeHash) -> ([u8; 50], SymmetricKey, SymmetricKey) { - let local_public_key = Self::private_key_to_public_key(local_private_key); - - hash.update(&local_public_key.serialize()); - - let ecdh = Self::ecdh(local_private_key, &remote_public_key); - let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); - let tagged_ciphertext = chacha::encrypt(&temporary_key, 0, &hash.value, &[0; 0]); - - hash.update(&tagged_ciphertext); - - let mut act = [0u8; 50]; - act[1..34].copy_from_slice(&local_public_key.serialize()); - act[34..].copy_from_slice(tagged_ciphertext.as_slice()); - - (act, chaining_key, temporary_key) - } - - // Due to the very high similarity of acts 1 and 2, this method is used to process both - fn process_act_message(act_bytes: [u8; 50], local_private_key: &SecretKey, chaining_key: SymmetricKey, hash: &mut HandshakeHash) -> Result<(PublicKey, SymmetricKey, SymmetricKey), String> { - let version = act_bytes[0]; - if version != 0 { - // this should not crash the process, hence no panic - return Err("unexpected version".to_string()); - } - - let mut ephemeral_public_key_bytes = [0u8; 33]; - ephemeral_public_key_bytes.copy_from_slice(&act_bytes[1..34]); - let ephemeral_public_key = if let Ok(public_key) = PublicKey::from_slice(&ephemeral_public_key_bytes) { - public_key - } else { - return Err("invalid remote ephemeral public key".to_string()); - }; - - let mut chacha_tag = [0u8; 16]; - chacha_tag.copy_from_slice(&act_bytes[34..50]); - - // process the act message - - // update hash with partner's pubkey - hash.update(&ephemeral_public_key.serialize()); - - // calculate ECDH with partner's pubkey and local privkey - let ecdh = Self::ecdh(local_private_key, &ephemeral_public_key); - - // HKDF(chaining key, ECDH) -> chaining key' + next temporary key - let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); - - // Validate chacha tag (temporary key, 0, hash, chacha_tag) - let _tag_check = chacha::decrypt(&temporary_key, 0, &hash.value, &chacha_tag)?; - - hash.update(&chacha_tag); - - Ok((ephemeral_public_key, chaining_key, temporary_key)) - } - - fn private_key_to_public_key(private_key: &SecretKey) -> PublicKey { - let curve = secp256k1::Secp256k1::new(); - let pk_object = PublicKey::from_secret_key(&curve, &private_key); - pk_object - } - - fn ecdh(private_key: &SecretKey, public_key: &PublicKey) -> SymmetricKey { - let curve = secp256k1::Secp256k1::new(); - let mut pk_object = public_key.clone(); - pk_object.mul_assign(&curve, &private_key[..]).expect("invalid multiplication"); - - let preimage = pk_object.serialize(); - let mut sha = Sha256::engine(); - sha.input(preimage.as_ref()); - Sha256::from_engine(sha).into_inner() - } } #[cfg(test)] diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index b682d69930d..de2a225cb15 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -1,13 +1,14 @@ use bitcoin::secp256k1; +use bitcoin::hashes::{Hash, HashEngine}; +use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{SecretKey, PublicKey}; use ln::peers::handshake::hash::HandshakeHash; use ln::peers::handshake::acts::{Act, ActOne, ACT_ONE_LENGTH, ActTwo, ACT_TWO_LENGTH, ACT_THREE_LENGTH, ActThree}; use ln::peers::handshake::states::HandshakeState2::{AwaitingActTwo2, AwaitingActThree2, Complete2}; -use ln::peers::handshake::PeerHandshake; use ln::peers::{chacha, hkdf}; -use ln::peers::conduit::Conduit; +use ln::peers::conduit::{Conduit, SymmetricKey}; pub enum HandshakeState2 { Uninitiated2(UninitiatedHandshakeState), @@ -81,10 +82,10 @@ impl IHandshakeState for UninitiatedHandshakeState { let initiator_ephemeral_private_key = self.initiator_ephemeral_private_key; let responder_public_key = self.responder_public_key; - let (mut hash, chaining_key) = PeerHandshake::initialize_state(&responder_public_key); + let (mut hash, chaining_key) = initialize_state(&responder_public_key); // serialize act one - let (act_one, chaining_key, _) = PeerHandshake::calculate_act_message( + let (act_one, chaining_key, _) = calculate_act_message( &initiator_ephemeral_private_key, &responder_public_key, chaining_key, @@ -103,7 +104,7 @@ impl AwaitingActOneHandshakeState { let curve = secp256k1::Secp256k1::new(); let responder_public_key = PublicKey::from_secret_key(&curve, &responder_private_key); - let (hash, chaining_key) = PeerHandshake::initialize_state(&responder_public_key); + let (hash, chaining_key) = initialize_state(&responder_public_key); AwaitingActOneHandshakeState { responder_private_key, @@ -135,14 +136,14 @@ impl IHandshakeState for AwaitingActOneHandshakeState { act_one_bytes.copy_from_slice(&read_buffer[..ACT_ONE_LENGTH]); read_buffer.drain(..ACT_ONE_LENGTH); - let (initiator_ephemeral_public_key, chaining_key, _) = PeerHandshake::process_act_message( + let (initiator_ephemeral_public_key, chaining_key, _) = process_act_message( act_one_bytes, &responder_private_key, chaining_key, &mut hash, )?; - let (act_two, chaining_key, temporary_key) = PeerHandshake::calculate_act_message( + let (act_two, chaining_key, temporary_key) = calculate_act_message( &responder_ephemeral_private_key, &initiator_ephemeral_public_key, chaining_key, @@ -179,7 +180,7 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { act_two_bytes.copy_from_slice(&read_buffer[..ACT_TWO_LENGTH]); read_buffer.drain(..ACT_TWO_LENGTH); - let (responder_ephemeral_public_key, chaining_key, temporary_key) = PeerHandshake::process_act_message( + let (responder_ephemeral_public_key, chaining_key, temporary_key) = process_act_message( act_two_bytes, &initiator_ephemeral_private_key, chaining_key, @@ -193,7 +194,7 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { let tagged_encrypted_pubkey = chacha::encrypt(&temporary_key, 1, &hash.value, &initiator_public_key.serialize()); hash.update(&tagged_encrypted_pubkey); - let ecdh = PeerHandshake::ecdh(&initiator_private_key, &responder_ephemeral_public_key); + let ecdh = ecdh(&initiator_private_key, &responder_ephemeral_public_key); let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); let authentication_tag = chacha::encrypt(&temporary_key, 0, &hash.value, &[0; 0]); let (sending_key, receiving_key) = hkdf::derive(&chaining_key, &[0; 0]); @@ -270,7 +271,7 @@ impl IHandshakeState for AwaitingActThreeHandshakeState { hash.update(&tagged_encrypted_pubkey); - let ecdh = PeerHandshake::ecdh(&responder_ephemeral_private_key, &initiator_pubkey); + let ecdh = ecdh(&responder_ephemeral_private_key, &initiator_pubkey); let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); let _tag_check = chacha::decrypt(&temporary_key, 0, &hash.value, &chacha_tag)?; let (receiving_key, sending_key) = hkdf::derive(&chaining_key, &[0; 0]); @@ -301,6 +302,96 @@ impl AwaitingActThreeHandshakeState { } } +fn initialize_state(public_key: &PublicKey) -> (HandshakeHash, [u8; 32]) { + let protocol_name = b"Noise_XK_secp256k1_ChaChaPoly_SHA256"; + let prologue = b"lightning"; + + let mut sha = Sha256::engine(); + sha.input(protocol_name); + let chaining_key = Sha256::from_engine(sha).into_inner(); + + let mut initial_hash_preimage = chaining_key.to_vec(); + initial_hash_preimage.extend_from_slice(prologue.as_ref()); + + let mut hash = HandshakeHash::new(initial_hash_preimage.as_slice()); + hash.update(&public_key.serialize()); + + (hash, chaining_key) +} + +fn calculate_act_message(local_private_key: &SecretKey, remote_public_key: &PublicKey, chaining_key: [u8; 32], hash: &mut HandshakeHash) -> ([u8; 50], SymmetricKey, SymmetricKey) { + let local_public_key = private_key_to_public_key(local_private_key); + + hash.update(&local_public_key.serialize()); + + let ecdh = ecdh(local_private_key, &remote_public_key); + let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); + let tagged_ciphertext = chacha::encrypt(&temporary_key, 0, &hash.value, &[0; 0]); + + hash.update(&tagged_ciphertext); + + let mut act = [0u8; 50]; + act[1..34].copy_from_slice(&local_public_key.serialize()); + act[34..].copy_from_slice(tagged_ciphertext.as_slice()); + + (act, chaining_key, temporary_key) +} + +// Due to the very high similarity of acts 1 and 2, this method is used to process both +fn process_act_message(act_bytes: [u8; 50], local_private_key: &SecretKey, chaining_key: SymmetricKey, hash: &mut HandshakeHash) -> Result<(PublicKey, SymmetricKey, SymmetricKey), String> { + let version = act_bytes[0]; + if version != 0 { + // this should not crash the process, hence no panic + return Err("unexpected version".to_string()); + } + + let mut ephemeral_public_key_bytes = [0u8; 33]; + ephemeral_public_key_bytes.copy_from_slice(&act_bytes[1..34]); + let ephemeral_public_key = if let Ok(public_key) = PublicKey::from_slice(&ephemeral_public_key_bytes) { + public_key + } else { + return Err("invalid remote ephemeral public key".to_string()); + }; + + let mut chacha_tag = [0u8; 16]; + chacha_tag.copy_from_slice(&act_bytes[34..50]); + + // process the act message + + // update hash with partner's pubkey + hash.update(&ephemeral_public_key.serialize()); + + // calculate ECDH with partner's pubkey and local privkey + let ecdh = ecdh(local_private_key, &ephemeral_public_key); + + // HKDF(chaining key, ECDH) -> chaining key' + next temporary key + let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); + + // Validate chacha tag (temporary key, 0, hash, chacha_tag) + let _tag_check = chacha::decrypt(&temporary_key, 0, &hash.value, &chacha_tag)?; + + hash.update(&chacha_tag); + + Ok((ephemeral_public_key, chaining_key, temporary_key)) +} + +fn private_key_to_public_key(private_key: &SecretKey) -> PublicKey { + let curve = secp256k1::Secp256k1::new(); + let pk_object = PublicKey::from_secret_key(&curve, &private_key); + pk_object +} + +fn ecdh(private_key: &SecretKey, public_key: &PublicKey) -> SymmetricKey { + let curve = secp256k1::Secp256k1::new(); + let mut pk_object = public_key.clone(); + pk_object.mul_assign(&curve, &private_key[..]).expect("invalid multiplication"); + + let preimage = pk_object.serialize(); + let mut sha = Sha256::engine(); + sha.input(preimage.as_ref()); + Sha256::from_engine(sha).into_inner() +} + #[cfg(test)] mod test { use hex; From a0d6690322ea042dabd316bc082779429b16eed3 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 19 Aug 2020 16:14:53 -0700 Subject: [PATCH 08/45] refactor: Structure initialize_state based on RFC Introduce the sha256!() and concat!() macros that are used to improve readability of the noise exchanges. --- lightning/src/ln/peers/handshake/states.rs | 45 +++++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index de2a225cb15..1a19c2f34ad 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -82,7 +82,7 @@ impl IHandshakeState for UninitiatedHandshakeState { let initiator_ephemeral_private_key = self.initiator_ephemeral_private_key; let responder_public_key = self.responder_public_key; - let (mut hash, chaining_key) = initialize_state(&responder_public_key); + let (mut hash, chaining_key) = handshake_state_initialization(&responder_public_key); // serialize act one let (act_one, chaining_key, _) = calculate_act_message( @@ -104,7 +104,7 @@ impl AwaitingActOneHandshakeState { let curve = secp256k1::Secp256k1::new(); let responder_public_key = PublicKey::from_secret_key(&curve, &responder_private_key); - let (hash, chaining_key) = initialize_state(&responder_public_key); + let (hash, chaining_key) = handshake_state_initialization(&responder_public_key); AwaitingActOneHandshakeState { responder_private_key, @@ -302,19 +302,44 @@ impl AwaitingActThreeHandshakeState { } } -fn initialize_state(public_key: &PublicKey) -> (HandshakeHash, [u8; 32]) { +// Generate a SHA-256 hash from one or more elements +macro_rules! sha256 { + ( $( $x:expr ),+ ) => {{ + let mut sha = Sha256::engine(); + $( + sha.input($x.as_ref()); + )* + Sha256::from_engine(sha).into_inner() + }} +} + +// Concatenate two slices in a Vec +macro_rules! concat { + ($arg1:expr, $arg2:expr) => {{ + let mut result = $arg1.to_vec(); + result.extend_from_slice($arg2.as_ref()); + result + }} +} + +// https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#handshake-state-initialization +fn handshake_state_initialization(responder_public_key: &PublicKey) -> (HandshakeHash, [u8; 32]) { let protocol_name = b"Noise_XK_secp256k1_ChaChaPoly_SHA256"; let prologue = b"lightning"; - let mut sha = Sha256::engine(); - sha.input(protocol_name); - let chaining_key = Sha256::from_engine(sha).into_inner(); + // 1. h = SHA-256(protocolName) + // 2. ck = h + let chaining_key = sha256!(protocol_name); + + // 3. h = SHA-256(h || prologue) + let hash = sha256!(concat!(chaining_key, prologue)); - let mut initial_hash_preimage = chaining_key.to_vec(); - initial_hash_preimage.extend_from_slice(prologue.as_ref()); + // h = SHA-256(h || responderPublicKey) + let hash = sha256!(concat!(hash, responder_public_key.serialize())); - let mut hash = HandshakeHash::new(initial_hash_preimage.as_slice()); - hash.update(&public_key.serialize()); + let hash = HandshakeHash { + value: hash + }; (hash, chaining_key) } From a68af2dc72099e074e0a4c19c4733628df1fe7e0 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 19 Aug 2020 16:49:21 -0700 Subject: [PATCH 09/45] refactor: Structure calculate_act_message based on RFC Small inconvenience with the HandshakeHash return value that will be taken care of by the end of this patch stack. --- lightning/src/ln/peers/handshake/states.rs | 33 ++++++++++++++++------ 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 1a19c2f34ad..61129d14791 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -344,19 +344,34 @@ fn handshake_state_initialization(responder_public_key: &PublicKey) -> (Handshak (hash, chaining_key) } -fn calculate_act_message(local_private_key: &SecretKey, remote_public_key: &PublicKey, chaining_key: [u8; 32], hash: &mut HandshakeHash) -> ([u8; 50], SymmetricKey, SymmetricKey) { - let local_public_key = private_key_to_public_key(local_private_key); - - hash.update(&local_public_key.serialize()); - - let ecdh = ecdh(local_private_key, &remote_public_key); +// https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (sender) +// https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (sender) +fn calculate_act_message(local_private_ephemeral_key: &SecretKey, remote_public_key: &PublicKey, chaining_key: [u8; 32], h_hash: &mut HandshakeHash) -> ([u8; 50], SymmetricKey, SymmetricKey) { + // 1. e = generateKey() (passed in) + // 2. h = SHA-256(h || e.pub.serializeCompressed()) + let serialized_local_public_key = private_key_to_public_key(local_private_ephemeral_key).serialize(); + let hash = sha256!(concat!(h_hash.value, serialized_local_public_key)); + + // 3. ACT1: es = ECDH(e.priv, rs) + // 3. ACT2: es = ECDH(e.priv, re) + let ecdh = ecdh(local_private_ephemeral_key, &remote_public_key); + + // 4. ACT1: ck, temp_k1 = HKDF(ck, es) + // 4. ACT2: ck, temp_k2 = HKDF(ck, ee) let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); - let tagged_ciphertext = chacha::encrypt(&temporary_key, 0, &hash.value, &[0; 0]); - hash.update(&tagged_ciphertext); + // 5. ACT1: c = encryptWithAD(temp_k1, 0, h, zero) + // 5. ACT2: c = encryptWithAD(temp_k2, 0, h, zero) + let tagged_ciphertext = chacha::encrypt(&temporary_key, 0, &hash, &[0; 0]); + + // 6. h = SHA-256(h || c) + *h_hash = HandshakeHash { + value: sha256!(hash, tagged_ciphertext) + }; + // Send m = 0 || e.pub.serializeCompressed() || c let mut act = [0u8; 50]; - act[1..34].copy_from_slice(&local_public_key.serialize()); + act[1..34].copy_from_slice(&serialized_local_public_key); act[34..].copy_from_slice(tagged_ciphertext.as_slice()); (act, chaining_key, temporary_key) From abf924124af29e0eb97e906a227375ddd38c37ce Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 19 Aug 2020 17:54:15 -0700 Subject: [PATCH 10/45] refactor: Structure process_act_message based on RFC Fix inconsistent use of concat vs. Sha256::engine() input. These can be deduplicated at the end. --- lightning/src/ln/peers/handshake/states.rs | 124 +++++++++++---------- 1 file changed, 66 insertions(+), 58 deletions(-) diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 61129d14791..80b2e89b19d 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -35,13 +35,13 @@ trait IHandshakeState { } pub struct UninitiatedHandshakeState { - initiator_private_key: SecretKey, + initiator_static_private_key: SecretKey, initiator_ephemeral_private_key: SecretKey, - responder_public_key: PublicKey, + responder_static_public_key: PublicKey, } pub struct AwaitingActOneHandshakeState { - responder_private_key: SecretKey, + responder_static_private_key: SecretKey, responder_ephemeral_private_key: SecretKey, chaining_key: [u8; 32], hash: HandshakeHash, @@ -49,9 +49,9 @@ pub struct AwaitingActOneHandshakeState { } pub struct AwaitingActTwoHandshakeState { - initiator_private_key: SecretKey, + initiator_static_private_key: SecretKey, initiator_ephemeral_private_key: SecretKey, - responder_public_key: PublicKey, + responder_static_public_key: PublicKey, chaining_key: [u8; 32], hash: HandshakeHash, read_buffer: Vec @@ -66,48 +66,46 @@ pub struct AwaitingActThreeHandshakeState { } impl UninitiatedHandshakeState { - pub(crate) fn new(initiator_private_key: SecretKey, initiator_ephemeral_private_key: SecretKey, responder_public_key: PublicKey) -> Self { + pub(crate) fn new(initiator_static_private_key: SecretKey, initiator_ephemeral_private_key: SecretKey, responder_static_public_key: PublicKey) -> Self { UninitiatedHandshakeState { - initiator_private_key, + initiator_static_private_key, initiator_ephemeral_private_key, - responder_public_key + responder_static_public_key } } } impl IHandshakeState for UninitiatedHandshakeState { + // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (sender) fn next(self, _input: &[u8]) -> Result<(Option, HandshakeState2), String> { - - let initiator_private_key = self.initiator_private_key; + let initiator_static_private_key = self.initiator_static_private_key; let initiator_ephemeral_private_key = self.initiator_ephemeral_private_key; - let responder_public_key = self.responder_public_key; + let responder_static_public_key = self.responder_static_public_key; - let (mut hash, chaining_key) = handshake_state_initialization(&responder_public_key); + let (mut hash, chaining_key) = handshake_state_initialization(&responder_static_public_key); // serialize act one let (act_one, chaining_key, _) = calculate_act_message( &initiator_ephemeral_private_key, - &responder_public_key, + &responder_static_public_key, chaining_key, &mut hash, ); Ok(( Some(Act::One(ActOne(act_one))), - AwaitingActTwo2(AwaitingActTwoHandshakeState::new(initiator_private_key, initiator_ephemeral_private_key, responder_public_key, chaining_key, hash)) + AwaitingActTwo2(AwaitingActTwoHandshakeState::new(initiator_static_private_key, initiator_ephemeral_private_key, responder_static_public_key, chaining_key, hash)) )) } } impl AwaitingActOneHandshakeState { - pub(crate) fn new(responder_private_key: SecretKey, responder_ephemeral_private_key: SecretKey) -> Self { - - let curve = secp256k1::Secp256k1::new(); - let responder_public_key = PublicKey::from_secret_key(&curve, &responder_private_key); - let (hash, chaining_key) = handshake_state_initialization(&responder_public_key); + pub(crate) fn new(responder_static_private_key: SecretKey, responder_ephemeral_private_key: SecretKey) -> Self { + let responder_static_public_key = private_key_to_public_key(&responder_static_private_key); + let (hash, chaining_key) = handshake_state_initialization(&responder_static_public_key); AwaitingActOneHandshakeState { - responder_private_key, + responder_static_private_key, responder_ephemeral_private_key, chaining_key, hash, @@ -117,8 +115,9 @@ impl AwaitingActOneHandshakeState { } impl IHandshakeState for AwaitingActOneHandshakeState { + // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (receiver) + // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (sender) fn next(self, input: &[u8]) -> Result<(Option, HandshakeState2), String> { - let mut read_buffer = self.read_buffer; read_buffer.extend_from_slice(input); @@ -127,18 +126,18 @@ impl IHandshakeState for AwaitingActOneHandshakeState { } let mut hash = self.hash; - let responder_private_key = self.responder_private_key; + let responder_static_private_key = self.responder_static_private_key; let chaining_key = self.chaining_key; let responder_ephemeral_private_key = self.responder_ephemeral_private_key; - // common functions take in an array so drain here for now + // 1. Read exactly 50 bytes from the network buffer let mut act_one_bytes = [0u8; ACT_ONE_LENGTH]; act_one_bytes.copy_from_slice(&read_buffer[..ACT_ONE_LENGTH]); read_buffer.drain(..ACT_ONE_LENGTH); let (initiator_ephemeral_public_key, chaining_key, _) = process_act_message( act_one_bytes, - &responder_private_key, + &responder_static_private_key, chaining_key, &mut hash, )?; @@ -160,8 +159,9 @@ impl IHandshakeState for AwaitingActOneHandshakeState { } impl IHandshakeState for AwaitingActTwoHandshakeState { + // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (receiver) + // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-three (sender) fn next(self, input: &[u8]) -> Result<(Option, HandshakeState2), String> { - let mut read_buffer = self.read_buffer; read_buffer.extend_from_slice(input); @@ -169,13 +169,13 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { return Err("need at least 50 bytes".to_string()); } - let initiator_private_key = self.initiator_private_key; + let initiator_static_private_key = self.initiator_static_private_key; let initiator_ephemeral_private_key = self.initiator_ephemeral_private_key; - let responder_public_key = self.responder_public_key; + let responder_static_public_key = self.responder_static_public_key; let mut hash = self.hash; let chaining_key = self.chaining_key; - // common functions take in an array so drain here for now + // 1. Read exactly 50 bytes from the network buffer let mut act_two_bytes = [0u8; ACT_TWO_LENGTH]; act_two_bytes.copy_from_slice(&read_buffer[..ACT_TWO_LENGTH]); read_buffer.drain(..ACT_TWO_LENGTH); @@ -190,11 +190,11 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { // start serializing act three let curve = secp256k1::Secp256k1::new(); - let initiator_public_key = PublicKey::from_secret_key(&curve, &initiator_private_key); + let initiator_public_key = PublicKey::from_secret_key(&curve, &initiator_static_private_key); let tagged_encrypted_pubkey = chacha::encrypt(&temporary_key, 1, &hash.value, &initiator_public_key.serialize()); hash.update(&tagged_encrypted_pubkey); - let ecdh = ecdh(&initiator_private_key, &responder_ephemeral_public_key); + let ecdh = ecdh(&initiator_static_private_key, &responder_ephemeral_public_key); let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); let authentication_tag = chacha::encrypt(&temporary_key, 0, &hash.value, &[0; 0]); let (sending_key, receiving_key) = hkdf::derive(&chaining_key, &[0; 0]); @@ -212,17 +212,17 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { Ok(( Some(Act::Three(ActThree(act_three))), - Complete2(Some((conduit, responder_public_key))) + Complete2(Some((conduit, responder_static_public_key))) )) } } impl AwaitingActTwoHandshakeState { - fn new(initiator_private_key: SecretKey, initiator_ephemeral_private_key: SecretKey, responder_public_key: PublicKey, chaining_key: [u8;32], hash: HandshakeHash) -> Self { + fn new(initiator_static_private_key: SecretKey, initiator_ephemeral_private_key: SecretKey, responder_static_public_key: PublicKey, chaining_key: [u8;32], hash: HandshakeHash) -> Self { AwaitingActTwoHandshakeState { - initiator_private_key, + initiator_static_private_key, initiator_ephemeral_private_key, - responder_public_key, + responder_static_public_key, chaining_key, hash, read_buffer: Vec::new() @@ -323,7 +323,7 @@ macro_rules! concat { } // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#handshake-state-initialization -fn handshake_state_initialization(responder_public_key: &PublicKey) -> (HandshakeHash, [u8; 32]) { +fn handshake_state_initialization(responder_static_public_key: &PublicKey) -> (HandshakeHash, [u8; 32]) { let protocol_name = b"Noise_XK_secp256k1_ChaChaPoly_SHA256"; let prologue = b"lightning"; @@ -335,7 +335,7 @@ fn handshake_state_initialization(responder_public_key: &PublicKey) -> (Handshak let hash = sha256!(concat!(chaining_key, prologue)); // h = SHA-256(h || responderPublicKey) - let hash = sha256!(concat!(hash, responder_public_key.serialize())); + let hash = sha256!(concat!(hash, responder_static_public_key.serialize())); let hash = HandshakeHash { value: hash @@ -378,12 +378,11 @@ fn calculate_act_message(local_private_ephemeral_key: &SecretKey, remote_public_ } // Due to the very high similarity of acts 1 and 2, this method is used to process both -fn process_act_message(act_bytes: [u8; 50], local_private_key: &SecretKey, chaining_key: SymmetricKey, hash: &mut HandshakeHash) -> Result<(PublicKey, SymmetricKey, SymmetricKey), String> { +// https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (receiver) +// https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (receiver) +fn process_act_message(act_bytes: [u8; 50], local_private_key: &SecretKey, chaining_key: SymmetricKey, h_hash: &mut HandshakeHash) -> Result<(PublicKey, SymmetricKey, SymmetricKey), String> { + // 2.Parse the read message (m) into v, re, and c let version = act_bytes[0]; - if version != 0 { - // this should not crash the process, hence no panic - return Err("unexpected version".to_string()); - } let mut ephemeral_public_key_bytes = [0u8; 33]; ephemeral_public_key_bytes.copy_from_slice(&act_bytes[1..34]); @@ -396,21 +395,30 @@ fn process_act_message(act_bytes: [u8; 50], local_private_key: &SecretKey, chain let mut chacha_tag = [0u8; 16]; chacha_tag.copy_from_slice(&act_bytes[34..50]); - // process the act message + // 3. If v is an unrecognized handshake version, then the responder MUST abort the connection attempt + if version != 0 { + // this should not crash the process, hence no panic + return Err("unexpected version".to_string()); + } - // update hash with partner's pubkey - hash.update(&ephemeral_public_key.serialize()); + // 4. h = SHA-256(h || re.serializeCompressed()) + let hash = sha256!(concat!(h_hash.value, ephemeral_public_key_bytes)); - // calculate ECDH with partner's pubkey and local privkey + // 5. Act1: es = ECDH(s.priv, re) + // 5. Act2: ee = ECDH(e.priv, ee) let ecdh = ecdh(local_private_key, &ephemeral_public_key); - // HKDF(chaining key, ECDH) -> chaining key' + next temporary key + // 6. Act1: ck, temp_k1 = HKDF(ck, es) + // 6. Act2: ck, temp_k2 = HKDF(ck, ee) let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); - // Validate chacha tag (temporary key, 0, hash, chacha_tag) - let _tag_check = chacha::decrypt(&temporary_key, 0, &hash.value, &chacha_tag)?; + // 7. p = decryptWithAD(temp_k1, 0, h, c) + let _tag_check = chacha::decrypt(&temporary_key, 0, &hash, &chacha_tag)?; - hash.update(&chacha_tag); + // 8. h = SHA-256(h || c) + *h_hash = HandshakeHash { + value: sha256!(concat!(hash, chacha_tag)) + }; Ok((ephemeral_public_key, chaining_key, temporary_key)) } @@ -447,28 +455,28 @@ mod test { initiator: HandshakeState2, initiator_public_key: PublicKey, responder: HandshakeState2, - responder_public_key: PublicKey + responder_static_public_key: PublicKey } impl TestCtx { fn new() -> Self { let curve = secp256k1::Secp256k1::new(); - let initiator_private_key = SecretKey::from_slice(&[0x_11_u8; 32]).unwrap(); - let initiator_public_key = PublicKey::from_secret_key(&curve, &initiator_private_key); + let initiator_static_private_key = SecretKey::from_slice(&[0x_11_u8; 32]).unwrap(); + let initiator_public_key = PublicKey::from_secret_key(&curve, &initiator_static_private_key); let initiator_ephemeral_private_key = SecretKey::from_slice(&[0x_12_u8; 32]).unwrap(); - let responder_private_key = SecretKey::from_slice(&[0x_21_u8; 32]).unwrap(); - let responder_public_key = PublicKey::from_secret_key(&curve, &responder_private_key); + let responder_static_private_key = SecretKey::from_slice(&[0x_21_u8; 32]).unwrap(); + let responder_static_public_key = PublicKey::from_secret_key(&curve, &responder_static_private_key); let responder_ephemeral_private_key = SecretKey::from_slice(&[0x_22_u8; 32]).unwrap(); - let initiator = UninitiatedHandshakeState::new(initiator_private_key, initiator_ephemeral_private_key, responder_public_key); - let responder = AwaitingActOneHandshakeState::new(responder_private_key, responder_ephemeral_private_key); + let initiator = UninitiatedHandshakeState::new(initiator_static_private_key, initiator_ephemeral_private_key, responder_static_public_key); + let responder = AwaitingActOneHandshakeState::new(responder_static_private_key, responder_ephemeral_private_key); TestCtx { initiator: HandshakeState2::Uninitiated2(initiator), initiator_public_key, responder: HandshakeState2::AwaitingActOne2(responder), - responder_public_key, + responder_static_public_key, } } } @@ -586,7 +594,7 @@ mod test { panic!(); }; - assert_eq!(remote_pubkey, test_ctx.responder_public_key); + assert_eq!(remote_pubkey, test_ctx.responder_static_public_key); } // Initiator::AwaitingActTwo -> Complete (with extra data) From 3eb3146a473273d54243353f60f2f5a577ff5b50 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 19 Aug 2020 18:07:19 -0700 Subject: [PATCH 11/45] refactor: Structure creation of Act3 based on RFC Identify potential issue regarding the invalid case where the read_buffer has data after completion. --- lightning/src/ln/peers/handshake/states.rs | 70 +++++++++++++--------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 80b2e89b19d..7f325b0d524 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -10,6 +10,26 @@ use ln::peers::handshake::states::HandshakeState2::{AwaitingActTwo2, AwaitingAct use ln::peers::{chacha, hkdf}; use ln::peers::conduit::{Conduit, SymmetricKey}; +// Generate a SHA-256 hash from one or more elements +macro_rules! sha256 { + ( $( $x:expr ),+ ) => {{ + let mut sha = Sha256::engine(); + $( + sha.input($x.as_ref()); + )+ + Sha256::from_engine(sha).into_inner() + }} +} + +// Concatenate two slices in a Vec +macro_rules! concat { + ($arg1:expr, $arg2:expr) => {{ + let mut result = $arg1.to_vec(); + result.extend_from_slice($arg2.as_ref()); + result + }} +} + pub enum HandshakeState2 { Uninitiated2(UninitiatedHandshakeState), AwaitingActOne2(AwaitingActOneHandshakeState), @@ -188,24 +208,38 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { )?; // start serializing act three - - let curve = secp256k1::Secp256k1::new(); - let initiator_public_key = PublicKey::from_secret_key(&curve, &initiator_static_private_key); + // 1. c = encryptWithAD(temp_k2, 1, h, s.pub.serializeCompressed()) + let initiator_public_key = private_key_to_public_key(&initiator_static_private_key); let tagged_encrypted_pubkey = chacha::encrypt(&temporary_key, 1, &hash.value, &initiator_public_key.serialize()); - hash.update(&tagged_encrypted_pubkey); + // 2. h = SHA-256(h || c) + let hash = sha256!(concat!(hash.value, tagged_encrypted_pubkey)); + + // 3. se = ECDH(s.priv, re) let ecdh = ecdh(&initiator_static_private_key, &responder_ephemeral_public_key); + + // 4. ck, temp_k3 = HKDF(ck, se) let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); - let authentication_tag = chacha::encrypt(&temporary_key, 0, &hash.value, &[0; 0]); + + // 5. t = encryptWithAD(temp_k3, 0, h, zero) + let authentication_tag = chacha::encrypt(&temporary_key, 0, &hash, &[0; 0]); + + // 6. sk, rk = HKDF(ck, zero) let (sending_key, receiving_key) = hkdf::derive(&chaining_key, &[0; 0]); + // 7. rn = 0, sn = 0 + // - done by Conduit + let mut conduit = Conduit::new(sending_key, receiving_key, chaining_key); + + // Send m = 0 || c || t over the network buffer let mut act_three = [0u8; ACT_THREE_LENGTH]; act_three[1..50].copy_from_slice(&tagged_encrypted_pubkey); act_three[50..].copy_from_slice(authentication_tag.as_slice()); - let mut conduit = Conduit::new(sending_key, receiving_key, chaining_key); - - if read_buffer.len() > 0 { // have we received more data still? + // Any remaining data in the read buffer would be encrypted, so transfer ownership + // to the Conduit for future use. In this case, it is unlikely that any valid data + // exists, since the responder doesn't have Act3 + if read_buffer.len() > 0 { conduit.read(&read_buffer[..]); read_buffer.drain(..); } @@ -302,26 +336,6 @@ impl AwaitingActThreeHandshakeState { } } -// Generate a SHA-256 hash from one or more elements -macro_rules! sha256 { - ( $( $x:expr ),+ ) => {{ - let mut sha = Sha256::engine(); - $( - sha.input($x.as_ref()); - )* - Sha256::from_engine(sha).into_inner() - }} -} - -// Concatenate two slices in a Vec -macro_rules! concat { - ($arg1:expr, $arg2:expr) => {{ - let mut result = $arg1.to_vec(); - result.extend_from_slice($arg2.as_ref()); - result - }} -} - // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#handshake-state-initialization fn handshake_state_initialization(responder_static_public_key: &PublicKey) -> (HandshakeHash, [u8; 32]) { let protocol_name = b"Noise_XK_secp256k1_ChaChaPoly_SHA256"; From dbd1a2117e339a45b4331d9dcb1cb1a5e96fd7fd Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 19 Aug 2020 18:17:49 -0700 Subject: [PATCH 12/45] refactor: Structure receiving of Act3 data based on RFC --- lightning/src/ln/peers/handshake/states.rs | 30 +++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 7f325b0d524..3c844d4a4b2 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -265,6 +265,7 @@ impl AwaitingActTwoHandshakeState { } impl IHandshakeState for AwaitingActThreeHandshakeState { + // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-three (receiver) fn next(self, input: &[u8]) -> Result<(Option, HandshakeState2), String> { let mut read_buffer = self.read_buffer; read_buffer.extend_from_slice(input); @@ -278,15 +279,13 @@ impl IHandshakeState for AwaitingActThreeHandshakeState { let responder_ephemeral_private_key = self.responder_ephemeral_private_key; let chaining_key = self.chaining_key; + // 1. Read exactly 66 bytes from the network buffer let mut act_three_bytes = [0u8; ACT_THREE_LENGTH]; act_three_bytes.copy_from_slice(&read_buffer[..ACT_THREE_LENGTH]); read_buffer.drain(..ACT_THREE_LENGTH); + // 2. Parse the read message (m) into v, c, and t let version = act_three_bytes[0]; - if version != 0 { - // this should not crash the process, hence no panic - return Err("unexpected version".to_string()); - } let mut tagged_encrypted_pubkey = [0u8; 49]; tagged_encrypted_pubkey.copy_from_slice(&act_three_bytes[1..50]); @@ -294,6 +293,13 @@ impl IHandshakeState for AwaitingActThreeHandshakeState { let mut chacha_tag = [0u8; 16]; chacha_tag.copy_from_slice(&act_three_bytes[50..66]); + // 3. If v is an unrecognized handshake version, then the responder MUST abort the connection attempt. + if version != 0 { + // this should not crash the process, hence no panic + return Err("unexpected version".to_string()); + } + + // 4. rs = decryptWithAD(temp_k2, 1, h, c) let remote_pubkey_vec = chacha::decrypt(&temporary_key, 1, &hash.value, &tagged_encrypted_pubkey)?; let mut initiator_pubkey_bytes = [0u8; 33]; initiator_pubkey_bytes.copy_from_slice(remote_pubkey_vec.as_slice()); @@ -303,15 +309,27 @@ impl IHandshakeState for AwaitingActThreeHandshakeState { return Err("invalid remote public key".to_string()); }; - hash.update(&tagged_encrypted_pubkey); + // 5. h = SHA-256(h || c) + let hash = sha256!(concat!(hash.value, tagged_encrypted_pubkey)); + // 6. se = ECDH(e.priv, rs) let ecdh = ecdh(&responder_ephemeral_private_key, &initiator_pubkey); + + // 7. ck, temp_k3 = HKDF(ck, se) let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); - let _tag_check = chacha::decrypt(&temporary_key, 0, &hash.value, &chacha_tag)?; + + // 8. p = decryptWithAD(temp_k3, 0, h, t) + let _tag_check = chacha::decrypt(&temporary_key, 0, &hash, &chacha_tag)?; + + // 9. rk, sk = HKDF(ck, zero) let (receiving_key, sending_key) = hkdf::derive(&chaining_key, &[0; 0]); + // 10. rn = 0, sn = 0 + // - done by Conduit let mut conduit = Conduit::new(sending_key, receiving_key, chaining_key); + // Any remaining data in the read buffer would be encrypted, so transfer ownership + // to the Conduit for future use. if read_buffer.len() > 0 { // have we received more data still? conduit.read(&read_buffer[..]); read_buffer.drain(..); From c88535af1fc39bf6d474a6c77a893f982170a383 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 19 Aug 2020 18:27:29 -0700 Subject: [PATCH 13/45] refactor: Inline single use constructors --- lightning/src/ln/peers/handshake/states.rs | 44 ++++++++-------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 3c844d4a4b2..4b4504956dd 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -114,7 +114,14 @@ impl IHandshakeState for UninitiatedHandshakeState { Ok(( Some(Act::One(ActOne(act_one))), - AwaitingActTwo2(AwaitingActTwoHandshakeState::new(initiator_static_private_key, initiator_ephemeral_private_key, responder_static_public_key, chaining_key, hash)) + AwaitingActTwo2(AwaitingActTwoHandshakeState { + initiator_static_private_key, + initiator_ephemeral_private_key, + responder_static_public_key, + chaining_key, + hash, + read_buffer: Vec::new() + }) )) } } @@ -171,9 +178,13 @@ impl IHandshakeState for AwaitingActOneHandshakeState { Ok(( Some(Act::Two(ActTwo(act_two))), - AwaitingActThree2( - AwaitingActThreeHandshakeState::new(hash, responder_ephemeral_private_key, chaining_key, temporary_key, read_buffer) - ) + AwaitingActThree2(AwaitingActThreeHandshakeState { + hash, + responder_ephemeral_private_key, + chaining_key, + temporary_key, + read_buffer + }) )) } } @@ -251,19 +262,6 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { } } -impl AwaitingActTwoHandshakeState { - fn new(initiator_static_private_key: SecretKey, initiator_ephemeral_private_key: SecretKey, responder_static_public_key: PublicKey, chaining_key: [u8;32], hash: HandshakeHash) -> Self { - AwaitingActTwoHandshakeState { - initiator_static_private_key, - initiator_ephemeral_private_key, - responder_static_public_key, - chaining_key, - hash, - read_buffer: Vec::new() - } - } -} - impl IHandshakeState for AwaitingActThreeHandshakeState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-three (receiver) fn next(self, input: &[u8]) -> Result<(Option, HandshakeState2), String> { @@ -342,18 +340,6 @@ impl IHandshakeState for AwaitingActThreeHandshakeState { } } -impl AwaitingActThreeHandshakeState { - fn new(hash: HandshakeHash, responder_ephemeral_private_key: SecretKey, chaining_key: [u8; 32], temporary_key: [u8; 32], read_buffer: Vec) -> Self { - AwaitingActThreeHandshakeState { - hash, - responder_ephemeral_private_key, - chaining_key, - temporary_key, - read_buffer - } - } -} - // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#handshake-state-initialization fn handshake_state_initialization(responder_static_public_key: &PublicKey) -> (HandshakeHash, [u8; 32]) { let protocol_name = b"Noise_XK_secp256k1_ChaChaPoly_SHA256"; From 41a8ac01b47c06e1786aef1259e40e632e7d176a Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 19 Aug 2020 18:46:45 -0700 Subject: [PATCH 14/45] refactor: Pull public key generation to the initial states All needed public keys are calculated during the first state and passed through as needed. Limits the call sites of private_key_to_public and makes the initial states for the initiator and responder more symmetric. --- lightning/src/ln/peers/handshake/states.rs | 35 +++++++++++++++++----- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 4b4504956dd..bfc0f093b62 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -56,13 +56,18 @@ trait IHandshakeState { pub struct UninitiatedHandshakeState { initiator_static_private_key: SecretKey, + initiator_static_public_key: PublicKey, initiator_ephemeral_private_key: SecretKey, + initiator_ephemeral_public_key: PublicKey, responder_static_public_key: PublicKey, + chaining_key: [u8; 32], + hash: HandshakeHash, } pub struct AwaitingActOneHandshakeState { responder_static_private_key: SecretKey, responder_ephemeral_private_key: SecretKey, + responder_ephemeral_public_key: PublicKey, chaining_key: [u8; 32], hash: HandshakeHash, read_buffer: Vec @@ -70,6 +75,7 @@ pub struct AwaitingActOneHandshakeState { pub struct AwaitingActTwoHandshakeState { initiator_static_private_key: SecretKey, + initiator_static_public_key: PublicKey, initiator_ephemeral_private_key: SecretKey, responder_static_public_key: PublicKey, chaining_key: [u8; 32], @@ -87,10 +93,17 @@ pub struct AwaitingActThreeHandshakeState { impl UninitiatedHandshakeState { pub(crate) fn new(initiator_static_private_key: SecretKey, initiator_ephemeral_private_key: SecretKey, responder_static_public_key: PublicKey) -> Self { + let initiator_static_public_key = private_key_to_public_key(&initiator_static_private_key); + let (hash, chaining_key) = handshake_state_initialization(&responder_static_public_key); + let initiator_ephemeral_public_key = private_key_to_public_key(&initiator_ephemeral_private_key); UninitiatedHandshakeState { initiator_static_private_key, + initiator_static_public_key, initiator_ephemeral_private_key, - responder_static_public_key + initiator_ephemeral_public_key, + responder_static_public_key, + chaining_key, + hash } } } @@ -99,14 +112,17 @@ impl IHandshakeState for UninitiatedHandshakeState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (sender) fn next(self, _input: &[u8]) -> Result<(Option, HandshakeState2), String> { let initiator_static_private_key = self.initiator_static_private_key; + let initiator_static_public_key = self.initiator_static_public_key; let initiator_ephemeral_private_key = self.initiator_ephemeral_private_key; + let initiator_ephemeral_public_key = self.initiator_ephemeral_public_key; let responder_static_public_key = self.responder_static_public_key; - - let (mut hash, chaining_key) = handshake_state_initialization(&responder_static_public_key); + let chaining_key = self.chaining_key; + let mut hash = self.hash; // serialize act one let (act_one, chaining_key, _) = calculate_act_message( &initiator_ephemeral_private_key, + &initiator_ephemeral_public_key, &responder_static_public_key, chaining_key, &mut hash, @@ -116,6 +132,7 @@ impl IHandshakeState for UninitiatedHandshakeState { Some(Act::One(ActOne(act_one))), AwaitingActTwo2(AwaitingActTwoHandshakeState { initiator_static_private_key, + initiator_static_public_key, initiator_ephemeral_private_key, responder_static_public_key, chaining_key, @@ -130,10 +147,12 @@ impl AwaitingActOneHandshakeState { pub(crate) fn new(responder_static_private_key: SecretKey, responder_ephemeral_private_key: SecretKey) -> Self { let responder_static_public_key = private_key_to_public_key(&responder_static_private_key); let (hash, chaining_key) = handshake_state_initialization(&responder_static_public_key); + let responder_ephemeral_public_key = private_key_to_public_key(&responder_ephemeral_private_key); AwaitingActOneHandshakeState { responder_static_private_key, responder_ephemeral_private_key, + responder_ephemeral_public_key, chaining_key, hash, read_buffer: Vec::new() @@ -156,6 +175,7 @@ impl IHandshakeState for AwaitingActOneHandshakeState { let responder_static_private_key = self.responder_static_private_key; let chaining_key = self.chaining_key; let responder_ephemeral_private_key = self.responder_ephemeral_private_key; + let responder_ephemeral_public_key = self.responder_ephemeral_public_key; // 1. Read exactly 50 bytes from the network buffer let mut act_one_bytes = [0u8; ACT_ONE_LENGTH]; @@ -171,6 +191,7 @@ impl IHandshakeState for AwaitingActOneHandshakeState { let (act_two, chaining_key, temporary_key) = calculate_act_message( &responder_ephemeral_private_key, + &responder_ephemeral_public_key, &initiator_ephemeral_public_key, chaining_key, &mut hash, @@ -201,6 +222,7 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { } let initiator_static_private_key = self.initiator_static_private_key; + let initiator_static_public_key = self.initiator_static_public_key; let initiator_ephemeral_private_key = self.initiator_ephemeral_private_key; let responder_static_public_key = self.responder_static_public_key; let mut hash = self.hash; @@ -220,8 +242,7 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { // start serializing act three // 1. c = encryptWithAD(temp_k2, 1, h, s.pub.serializeCompressed()) - let initiator_public_key = private_key_to_public_key(&initiator_static_private_key); - let tagged_encrypted_pubkey = chacha::encrypt(&temporary_key, 1, &hash.value, &initiator_public_key.serialize()); + let tagged_encrypted_pubkey = chacha::encrypt(&temporary_key, 1, &hash.value, &initiator_static_public_key.serialize()); // 2. h = SHA-256(h || c) let hash = sha256!(concat!(hash.value, tagged_encrypted_pubkey)); @@ -364,10 +385,10 @@ fn handshake_state_initialization(responder_static_public_key: &PublicKey) -> (H // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (sender) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (sender) -fn calculate_act_message(local_private_ephemeral_key: &SecretKey, remote_public_key: &PublicKey, chaining_key: [u8; 32], h_hash: &mut HandshakeHash) -> ([u8; 50], SymmetricKey, SymmetricKey) { +fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_ephemeral_key: &PublicKey, remote_public_key: &PublicKey, chaining_key: [u8; 32], h_hash: &mut HandshakeHash) -> ([u8; 50], SymmetricKey, SymmetricKey) { // 1. e = generateKey() (passed in) // 2. h = SHA-256(h || e.pub.serializeCompressed()) - let serialized_local_public_key = private_key_to_public_key(local_private_ephemeral_key).serialize(); + let serialized_local_public_key = local_public_ephemeral_key.serialize(); let hash = sha256!(concat!(h_hash.value, serialized_local_public_key)); // 3. ACT1: es = ECDH(e.priv, rs) From 8f17345046f2f94004743a17ddd4da75339a6799 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 19 Aug 2020 19:00:32 -0700 Subject: [PATCH 15/45] refactor: Remove HandshakeHash wrapper struct Create an alias type to make the code more readable and change all usages to moves. --- lightning/src/ln/peers/handshake/hash.rs | 25 ---------- lightning/src/ln/peers/handshake/mod.rs | 1 - lightning/src/ln/peers/handshake/states.rs | 56 ++++++++++------------ 3 files changed, 25 insertions(+), 57 deletions(-) delete mode 100644 lightning/src/ln/peers/handshake/hash.rs diff --git a/lightning/src/ln/peers/handshake/hash.rs b/lightning/src/ln/peers/handshake/hash.rs deleted file mode 100644 index e7704b37e34..00000000000 --- a/lightning/src/ln/peers/handshake/hash.rs +++ /dev/null @@ -1,25 +0,0 @@ -use bitcoin::hashes::{Hash, HashEngine}; -use bitcoin::hashes::sha256::Hash as Sha256; - -pub(crate) struct HandshakeHash { - pub(super) value: [u8; 32] -} - -impl HandshakeHash { - pub(super) fn new(first_input: &[u8]) -> Self { - let mut hash = Self { - value: [0; 32] - }; - let mut sha = Sha256::engine(); - sha.input(first_input); - hash.value = Sha256::from_engine(sha).into_inner(); - hash - } - - pub(super) fn update(&mut self, input: &[u8]) { - let mut sha = Sha256::engine(); - sha.input(self.value.as_ref()); - sha.input(input); - self.value = Sha256::from_engine(sha).into_inner(); - } -} diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs index d2eb122c7c2..13eae16a380 100644 --- a/lightning/src/ln/peers/handshake/mod.rs +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -9,7 +9,6 @@ use ln::peers::handshake::acts::Act; use ln::peers::handshake::states::{HandshakeState2, UninitiatedHandshakeState, AwaitingActOneHandshakeState}; mod acts; -mod hash; mod states; /// Object for managing handshakes. diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index bfc0f093b62..6ef7fe86dea 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -4,12 +4,14 @@ use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{SecretKey, PublicKey}; -use ln::peers::handshake::hash::HandshakeHash; use ln::peers::handshake::acts::{Act, ActOne, ACT_ONE_LENGTH, ActTwo, ACT_TWO_LENGTH, ACT_THREE_LENGTH, ActThree}; use ln::peers::handshake::states::HandshakeState2::{AwaitingActTwo2, AwaitingActThree2, Complete2}; use ln::peers::{chacha, hkdf}; use ln::peers::conduit::{Conduit, SymmetricKey}; +// Alias type to allow passing handshake hash easier +type HandshakeHash = [u8; 32]; + // Generate a SHA-256 hash from one or more elements macro_rules! sha256 { ( $( $x:expr ),+ ) => {{ @@ -120,12 +122,12 @@ impl IHandshakeState for UninitiatedHandshakeState { let mut hash = self.hash; // serialize act one - let (act_one, chaining_key, _) = calculate_act_message( + let (act_one, hash, chaining_key, _) = calculate_act_message( &initiator_ephemeral_private_key, &initiator_ephemeral_public_key, &responder_static_public_key, chaining_key, - &mut hash, + hash, ); Ok(( @@ -171,7 +173,7 @@ impl IHandshakeState for AwaitingActOneHandshakeState { return Err("need at least 50 bytes".to_string()); } - let mut hash = self.hash; + let hash = self.hash; let responder_static_private_key = self.responder_static_private_key; let chaining_key = self.chaining_key; let responder_ephemeral_private_key = self.responder_ephemeral_private_key; @@ -182,19 +184,19 @@ impl IHandshakeState for AwaitingActOneHandshakeState { act_one_bytes.copy_from_slice(&read_buffer[..ACT_ONE_LENGTH]); read_buffer.drain(..ACT_ONE_LENGTH); - let (initiator_ephemeral_public_key, chaining_key, _) = process_act_message( + let (initiator_ephemeral_public_key, hash, chaining_key, _) = process_act_message( act_one_bytes, &responder_static_private_key, chaining_key, - &mut hash, + hash, )?; - let (act_two, chaining_key, temporary_key) = calculate_act_message( + let (act_two, hash, chaining_key, temporary_key) = calculate_act_message( &responder_ephemeral_private_key, &responder_ephemeral_public_key, &initiator_ephemeral_public_key, chaining_key, - &mut hash, + hash, ); Ok(( @@ -225,7 +227,7 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { let initiator_static_public_key = self.initiator_static_public_key; let initiator_ephemeral_private_key = self.initiator_ephemeral_private_key; let responder_static_public_key = self.responder_static_public_key; - let mut hash = self.hash; + let hash = self.hash; let chaining_key = self.chaining_key; // 1. Read exactly 50 bytes from the network buffer @@ -233,19 +235,19 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { act_two_bytes.copy_from_slice(&read_buffer[..ACT_TWO_LENGTH]); read_buffer.drain(..ACT_TWO_LENGTH); - let (responder_ephemeral_public_key, chaining_key, temporary_key) = process_act_message( + let (responder_ephemeral_public_key, hash, chaining_key, temporary_key) = process_act_message( act_two_bytes, &initiator_ephemeral_private_key, chaining_key, - &mut hash, + hash, )?; // start serializing act three // 1. c = encryptWithAD(temp_k2, 1, h, s.pub.serializeCompressed()) - let tagged_encrypted_pubkey = chacha::encrypt(&temporary_key, 1, &hash.value, &initiator_static_public_key.serialize()); + let tagged_encrypted_pubkey = chacha::encrypt(&temporary_key, 1, &hash, &initiator_static_public_key.serialize()); // 2. h = SHA-256(h || c) - let hash = sha256!(concat!(hash.value, tagged_encrypted_pubkey)); + let hash = sha256!(concat!(hash, tagged_encrypted_pubkey)); // 3. se = ECDH(s.priv, re) let ecdh = ecdh(&initiator_static_private_key, &responder_ephemeral_public_key); @@ -319,7 +321,7 @@ impl IHandshakeState for AwaitingActThreeHandshakeState { } // 4. rs = decryptWithAD(temp_k2, 1, h, c) - let remote_pubkey_vec = chacha::decrypt(&temporary_key, 1, &hash.value, &tagged_encrypted_pubkey)?; + let remote_pubkey_vec = chacha::decrypt(&temporary_key, 1, &hash, &tagged_encrypted_pubkey)?; let mut initiator_pubkey_bytes = [0u8; 33]; initiator_pubkey_bytes.copy_from_slice(remote_pubkey_vec.as_slice()); let initiator_pubkey = if let Ok(public_key) = PublicKey::from_slice(&initiator_pubkey_bytes) { @@ -329,7 +331,7 @@ impl IHandshakeState for AwaitingActThreeHandshakeState { }; // 5. h = SHA-256(h || c) - let hash = sha256!(concat!(hash.value, tagged_encrypted_pubkey)); + let hash = sha256!(concat!(hash, tagged_encrypted_pubkey)); // 6. se = ECDH(e.priv, rs) let ecdh = ecdh(&responder_ephemeral_private_key, &initiator_pubkey); @@ -376,20 +378,16 @@ fn handshake_state_initialization(responder_static_public_key: &PublicKey) -> (H // h = SHA-256(h || responderPublicKey) let hash = sha256!(concat!(hash, responder_static_public_key.serialize())); - let hash = HandshakeHash { - value: hash - }; - (hash, chaining_key) } // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (sender) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (sender) -fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_ephemeral_key: &PublicKey, remote_public_key: &PublicKey, chaining_key: [u8; 32], h_hash: &mut HandshakeHash) -> ([u8; 50], SymmetricKey, SymmetricKey) { +fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_ephemeral_key: &PublicKey, remote_public_key: &PublicKey, chaining_key: [u8; 32], hash: HandshakeHash) -> ([u8; 50], HandshakeHash, SymmetricKey, SymmetricKey) { // 1. e = generateKey() (passed in) // 2. h = SHA-256(h || e.pub.serializeCompressed()) let serialized_local_public_key = local_public_ephemeral_key.serialize(); - let hash = sha256!(concat!(h_hash.value, serialized_local_public_key)); + let hash = sha256!(concat!(hash, serialized_local_public_key)); // 3. ACT1: es = ECDH(e.priv, rs) // 3. ACT2: es = ECDH(e.priv, re) @@ -404,22 +402,20 @@ fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_e let tagged_ciphertext = chacha::encrypt(&temporary_key, 0, &hash, &[0; 0]); // 6. h = SHA-256(h || c) - *h_hash = HandshakeHash { - value: sha256!(hash, tagged_ciphertext) - }; + let hash = sha256!(hash, tagged_ciphertext); // Send m = 0 || e.pub.serializeCompressed() || c let mut act = [0u8; 50]; act[1..34].copy_from_slice(&serialized_local_public_key); act[34..].copy_from_slice(tagged_ciphertext.as_slice()); - (act, chaining_key, temporary_key) + (act, hash, chaining_key, temporary_key) } // Due to the very high similarity of acts 1 and 2, this method is used to process both // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (receiver) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (receiver) -fn process_act_message(act_bytes: [u8; 50], local_private_key: &SecretKey, chaining_key: SymmetricKey, h_hash: &mut HandshakeHash) -> Result<(PublicKey, SymmetricKey, SymmetricKey), String> { +fn process_act_message(act_bytes: [u8; 50], local_private_key: &SecretKey, chaining_key: SymmetricKey, hash: HandshakeHash) -> Result<(PublicKey, HandshakeHash, SymmetricKey, SymmetricKey), String> { // 2.Parse the read message (m) into v, re, and c let version = act_bytes[0]; @@ -441,7 +437,7 @@ fn process_act_message(act_bytes: [u8; 50], local_private_key: &SecretKey, chain } // 4. h = SHA-256(h || re.serializeCompressed()) - let hash = sha256!(concat!(h_hash.value, ephemeral_public_key_bytes)); + let hash = sha256!(concat!(hash, ephemeral_public_key_bytes)); // 5. Act1: es = ECDH(s.priv, re) // 5. Act2: ee = ECDH(e.priv, ee) @@ -455,11 +451,9 @@ fn process_act_message(act_bytes: [u8; 50], local_private_key: &SecretKey, chain let _tag_check = chacha::decrypt(&temporary_key, 0, &hash, &chacha_tag)?; // 8. h = SHA-256(h || c) - *h_hash = HandshakeHash { - value: sha256!(concat!(hash, chacha_tag)) - }; + let hash = sha256!(concat!(hash, chacha_tag)); - Ok((ephemeral_public_key, chaining_key, temporary_key)) + Ok((ephemeral_public_key, hash, chaining_key, temporary_key)) } fn private_key_to_public_key(private_key: &SecretKey) -> PublicKey { From 988e767f4f851323aad4c1c06dd073a54d771f64 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 19 Aug 2020 19:17:01 -0700 Subject: [PATCH 16/45] refactor: Combine duplicate code path for act1 and act2 Push buffer read into helper function Combines static length values of ActOne and ActTwo structs --- lightning/src/ln/peers/handshake/acts.rs | 7 ++-- lightning/src/ln/peers/handshake/states.rs | 37 ++++++++-------------- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/lightning/src/ln/peers/handshake/acts.rs b/lightning/src/ln/peers/handshake/acts.rs index 4f29d0e4f8d..cddc2a1ac4b 100644 --- a/lightning/src/ln/peers/handshake/acts.rs +++ b/lightning/src/ln/peers/handshake/acts.rs @@ -1,15 +1,14 @@ -pub const ACT_ONE_LENGTH: usize = 50; -pub const ACT_TWO_LENGTH: usize = 50; +pub const ACT_ONE_TWO_LENGTH: usize = 50; pub const ACT_THREE_LENGTH: usize = 66; /// Wrapper for the first act message pub struct ActOne( - pub(super) [u8; ACT_ONE_LENGTH] + pub(super) [u8; ACT_ONE_TWO_LENGTH] ); /// Wrapper for the second act message pub struct ActTwo( - pub(super) [u8; ACT_TWO_LENGTH] + pub(super) [u8; ACT_ONE_TWO_LENGTH] ); /// Wrapper for the third act message diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 6ef7fe86dea..d6c190250f7 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -4,7 +4,7 @@ use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{SecretKey, PublicKey}; -use ln::peers::handshake::acts::{Act, ActOne, ACT_ONE_LENGTH, ActTwo, ACT_TWO_LENGTH, ACT_THREE_LENGTH, ActThree}; +use ln::peers::handshake::acts::{Act, ActOne, ACT_ONE_TWO_LENGTH, ActTwo, ACT_THREE_LENGTH, ActThree}; use ln::peers::handshake::states::HandshakeState2::{AwaitingActTwo2, AwaitingActThree2, Complete2}; use ln::peers::{chacha, hkdf}; use ln::peers::conduit::{Conduit, SymmetricKey}; @@ -169,23 +169,14 @@ impl IHandshakeState for AwaitingActOneHandshakeState { let mut read_buffer = self.read_buffer; read_buffer.extend_from_slice(input); - if read_buffer.len() < ACT_ONE_LENGTH { - return Err("need at least 50 bytes".to_string()); - } - let hash = self.hash; let responder_static_private_key = self.responder_static_private_key; let chaining_key = self.chaining_key; let responder_ephemeral_private_key = self.responder_ephemeral_private_key; let responder_ephemeral_public_key = self.responder_ephemeral_public_key; - // 1. Read exactly 50 bytes from the network buffer - let mut act_one_bytes = [0u8; ACT_ONE_LENGTH]; - act_one_bytes.copy_from_slice(&read_buffer[..ACT_ONE_LENGTH]); - read_buffer.drain(..ACT_ONE_LENGTH); - let (initiator_ephemeral_public_key, hash, chaining_key, _) = process_act_message( - act_one_bytes, + &mut read_buffer, &responder_static_private_key, chaining_key, hash, @@ -219,10 +210,6 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { let mut read_buffer = self.read_buffer; read_buffer.extend_from_slice(input); - if read_buffer.len() < ACT_TWO_LENGTH { - return Err("need at least 50 bytes".to_string()); - } - let initiator_static_private_key = self.initiator_static_private_key; let initiator_static_public_key = self.initiator_static_public_key; let initiator_ephemeral_private_key = self.initiator_ephemeral_private_key; @@ -230,13 +217,8 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { let hash = self.hash; let chaining_key = self.chaining_key; - // 1. Read exactly 50 bytes from the network buffer - let mut act_two_bytes = [0u8; ACT_TWO_LENGTH]; - act_two_bytes.copy_from_slice(&read_buffer[..ACT_TWO_LENGTH]); - read_buffer.drain(..ACT_TWO_LENGTH); - let (responder_ephemeral_public_key, hash, chaining_key, temporary_key) = process_act_message( - act_two_bytes, + &mut read_buffer, &initiator_ephemeral_private_key, chaining_key, hash, @@ -415,7 +397,16 @@ fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_e // Due to the very high similarity of acts 1 and 2, this method is used to process both // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (receiver) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (receiver) -fn process_act_message(act_bytes: [u8; 50], local_private_key: &SecretKey, chaining_key: SymmetricKey, hash: HandshakeHash) -> Result<(PublicKey, HandshakeHash, SymmetricKey, SymmetricKey), String> { +fn process_act_message(read_buffer: &mut Vec, local_private_key: &SecretKey, chaining_key: SymmetricKey, hash: HandshakeHash) -> Result<(PublicKey, HandshakeHash, SymmetricKey, SymmetricKey), String> { + // 1. Read exactly 50 bytes from the network buffer + if read_buffer.len() < ACT_ONE_TWO_LENGTH { + return Err("need at least 50 bytes".to_string()); + } + + let mut act_bytes = [0u8; ACT_ONE_TWO_LENGTH]; + act_bytes.copy_from_slice(&read_buffer[..ACT_ONE_TWO_LENGTH]); + read_buffer.drain(..ACT_ONE_TWO_LENGTH); + // 2.Parse the read message (m) into v, re, and c let version = act_bytes[0]; @@ -557,7 +548,7 @@ mod test { } // Responder::AwaitingActOne -> AwaitingActThree - // TODO: Should this fail since we don't expect data > ACT_ONE_LENGTH and likely indicates + // TODO: Should this fail since we don't expect data > ACT_ONE_TWO_LENGTH and likely indicates // a bad peer? // TODO: Should the behavior be changed to handle act1 data that is striped across multiple // next() calls? From 1e816425e5ac51da893beec49a2ccd7853bb983a Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 19 Aug 2020 19:18:22 -0700 Subject: [PATCH 17/45] refactor: Introduce ChainingKey type alias --- lightning/src/ln/peers/handshake/states.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index d6c190250f7..b9d2047fc10 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -11,6 +11,7 @@ use ln::peers::conduit::{Conduit, SymmetricKey}; // Alias type to allow passing handshake hash easier type HandshakeHash = [u8; 32]; +type ChainingKey = [u8; 32]; // Generate a SHA-256 hash from one or more elements macro_rules! sha256 { @@ -62,7 +63,7 @@ pub struct UninitiatedHandshakeState { initiator_ephemeral_private_key: SecretKey, initiator_ephemeral_public_key: PublicKey, responder_static_public_key: PublicKey, - chaining_key: [u8; 32], + chaining_key: ChainingKey, hash: HandshakeHash, } @@ -70,7 +71,7 @@ pub struct AwaitingActOneHandshakeState { responder_static_private_key: SecretKey, responder_ephemeral_private_key: SecretKey, responder_ephemeral_public_key: PublicKey, - chaining_key: [u8; 32], + chaining_key: ChainingKey, hash: HandshakeHash, read_buffer: Vec } @@ -80,7 +81,7 @@ pub struct AwaitingActTwoHandshakeState { initiator_static_public_key: PublicKey, initiator_ephemeral_private_key: SecretKey, responder_static_public_key: PublicKey, - chaining_key: [u8; 32], + chaining_key: ChainingKey, hash: HandshakeHash, read_buffer: Vec } @@ -88,7 +89,7 @@ pub struct AwaitingActTwoHandshakeState { pub struct AwaitingActThreeHandshakeState { hash: HandshakeHash, responder_ephemeral_private_key: SecretKey, - chaining_key: [u8; 32], + chaining_key: ChainingKey, temporary_key: [u8; 32], read_buffer: Vec } @@ -346,7 +347,7 @@ impl IHandshakeState for AwaitingActThreeHandshakeState { } // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#handshake-state-initialization -fn handshake_state_initialization(responder_static_public_key: &PublicKey) -> (HandshakeHash, [u8; 32]) { +fn handshake_state_initialization(responder_static_public_key: &PublicKey) -> (HandshakeHash, ChainingKey) { let protocol_name = b"Noise_XK_secp256k1_ChaChaPoly_SHA256"; let prologue = b"lightning"; @@ -365,7 +366,7 @@ fn handshake_state_initialization(responder_static_public_key: &PublicKey) -> (H // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (sender) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (sender) -fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_ephemeral_key: &PublicKey, remote_public_key: &PublicKey, chaining_key: [u8; 32], hash: HandshakeHash) -> ([u8; 50], HandshakeHash, SymmetricKey, SymmetricKey) { +fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_ephemeral_key: &PublicKey, remote_public_key: &PublicKey, chaining_key: ChainingKey, hash: HandshakeHash) -> ([u8; 50], HandshakeHash, SymmetricKey, SymmetricKey) { // 1. e = generateKey() (passed in) // 2. h = SHA-256(h || e.pub.serializeCompressed()) let serialized_local_public_key = local_public_ephemeral_key.serialize(); From de5a2e86acd6f5eb1af4e3c7046e3cc85742f73c Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 19 Aug 2020 20:16:22 -0700 Subject: [PATCH 18/45] refactor: Remove Act object All consumers immediately serialize the struct so just do it for them and hide the implementation details of the handshake. --- lightning/src/ln/peers/handler.rs | 4 +- lightning/src/ln/peers/handshake/acts.rs | 41 ----------------- lightning/src/ln/peers/handshake/mod.rs | 17 +++---- lightning/src/ln/peers/handshake/states.rs | 53 ++++++++++++---------- 4 files changed, 38 insertions(+), 77 deletions(-) delete mode 100644 lightning/src/ln/peers/handshake/acts.rs diff --git a/lightning/src/ln/peers/handler.rs b/lightning/src/ln/peers/handler.rs index 903f4ccfaa3..05431529644 100644 --- a/lightning/src/ln/peers/handler.rs +++ b/lightning/src/ln/peers/handler.rs @@ -155,7 +155,7 @@ impl PeerState { let requires_response = next_act.is_some(); if let Some(act) = next_act { - mutable_response_buffer.push_back(act.serialize()); + mutable_response_buffer.push_back(act); } let remote_pubkey_option = handshake.get_remote_pubkey(); @@ -366,7 +366,7 @@ impl PeerManager Result, PeerHandleError> { let mut handshake = PeerHandshake::new_outbound(&self.our_node_secret, &their_node_id, &self.get_ephemeral_key()); let (act, ..) = handshake.process_act(&[]).unwrap(); - let res = act.unwrap().serialize(); + let res = act.unwrap(); let mut peers = self.peers.lock().unwrap(); if peers.peers.insert(descriptor, Peer { diff --git a/lightning/src/ln/peers/handshake/acts.rs b/lightning/src/ln/peers/handshake/acts.rs deleted file mode 100644 index cddc2a1ac4b..00000000000 --- a/lightning/src/ln/peers/handshake/acts.rs +++ /dev/null @@ -1,41 +0,0 @@ -pub const ACT_ONE_TWO_LENGTH: usize = 50; -pub const ACT_THREE_LENGTH: usize = 66; - -/// Wrapper for the first act message -pub struct ActOne( - pub(super) [u8; ACT_ONE_TWO_LENGTH] -); - -/// Wrapper for the second act message -pub struct ActTwo( - pub(super) [u8; ACT_ONE_TWO_LENGTH] -); - -/// Wrapper for the third act message -pub struct ActThree( - pub(super) [u8; ACT_THREE_LENGTH] -); - -/// Wrapper for any act message -pub enum Act { - One(ActOne), - Two(ActTwo), - Three(ActThree), -} - -impl Act { - /// Convert any act into a byte vector - pub fn serialize(&self) -> Vec { - match self { - &Act::One(ref act) => { - act.0.to_vec() - } - &Act::Two(ref act) => { - act.0.to_vec() - } - &Act::Three(ref act) => { - act.0.to_vec() - } - } - } -} \ No newline at end of file diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs index 13eae16a380..07728d81fa9 100644 --- a/lightning/src/ln/peers/handshake/mod.rs +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -5,10 +5,8 @@ use bitcoin::secp256k1::{PublicKey, SecretKey}; use ln::peers::conduit::Conduit; -use ln::peers::handshake::acts::Act; use ln::peers::handshake::states::{HandshakeState2, UninitiatedHandshakeState, AwaitingActOneHandshakeState}; -mod acts; mod states; /// Object for managing handshakes. @@ -49,7 +47,7 @@ impl PeerHandshake { /// Returns a tuple with the following components: /// `.0`: Byte vector containing the next act to send back to the peer per the handshake protocol /// `.1`: Conduit option if the handshake was just processed to completion and messages can now be encrypted and decrypted - pub fn process_act(&mut self, input: &[u8]) -> Result<(Option, Option), String> { + pub fn process_act(&mut self, input: &[u8]) -> Result<(Option>, Option), String> { let cur_state = self.state.take().unwrap(); let (act_opt, mut next_state) = cur_state.next(input)?; @@ -78,7 +76,6 @@ mod test { use bitcoin::secp256k1::key::{PublicKey, SecretKey}; use ln::peers::handshake::PeerHandshake; - use ln::peers::handshake::acts::Act; use ln::peers::handshake::states::HandshakeState2; struct TestCtx { @@ -123,7 +120,7 @@ mod test { macro_rules! do_process_act_or_panic { ($handshake:expr, $input:expr) => { - $handshake.process_act($input).unwrap().0.unwrap().serialize() + $handshake.process_act($input).unwrap().0.unwrap() } } @@ -154,7 +151,7 @@ mod test { fn peer_handshake_outbound_uninitiated_to_awaiting_act_two() { let mut test_ctx = TestCtx::new(); - assert_matches!(test_ctx.outbound_handshake.process_act(&[]).unwrap(), (Some(Act::One(_)), None)); + assert_matches!(test_ctx.outbound_handshake.process_act(&[]).unwrap(), (Some(_), None)); assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::AwaitingActTwo2(_))); assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); } @@ -165,7 +162,7 @@ mod test { let mut test_ctx = TestCtx::new(); // TODO: process_act() should error if state does not use vec, but it is non-empty - assert_matches!(test_ctx.outbound_handshake.process_act(&[1]).unwrap(), (Some(Act::One(_)), None)); + assert_matches!(test_ctx.outbound_handshake.process_act(&[1]).unwrap(), (Some(_), None)); assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::AwaitingActTwo2(_))); assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); } @@ -186,7 +183,7 @@ mod test { let mut act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); act1.extend_from_slice(&[1]); - assert_matches!(test_ctx.inbound_handshake.process_act(&act1).unwrap(), (Some(Act::Two(_)), None)); + assert_matches!(test_ctx.inbound_handshake.process_act(&act1).unwrap(), (Some(_), None)); assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState2::AwaitingActThree2(_))); assert!(test_ctx.inbound_handshake.get_remote_pubkey().is_none()); } @@ -230,7 +227,7 @@ mod test { let mut test_ctx = TestCtx::new(); let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - assert_matches!(test_ctx.inbound_handshake.process_act(&act1).unwrap(), (Some(Act::Two(_)), None)); + assert_matches!(test_ctx.inbound_handshake.process_act(&act1).unwrap(), (Some(_), None)); assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState2::AwaitingActThree2(_))); assert!(test_ctx.inbound_handshake.get_remote_pubkey().is_none()); } @@ -242,7 +239,7 @@ mod test { let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); - assert_matches!(test_ctx.outbound_handshake.process_act(&act2).unwrap(), (Some(Act::Three(_)), Some(_))); + assert_matches!(test_ctx.outbound_handshake.process_act(&act2).unwrap(), (Some(_), Some(_))); assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::Complete2(_))); assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); } diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index b9d2047fc10..949ec3f5817 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -4,11 +4,13 @@ use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{SecretKey, PublicKey}; -use ln::peers::handshake::acts::{Act, ActOne, ACT_ONE_TWO_LENGTH, ActTwo, ACT_THREE_LENGTH, ActThree}; use ln::peers::handshake::states::HandshakeState2::{AwaitingActTwo2, AwaitingActThree2, Complete2}; use ln::peers::{chacha, hkdf}; use ln::peers::conduit::{Conduit, SymmetricKey}; +const ACT_ONE_TWO_LENGTH: usize = 50; +const ACT_THREE_LENGTH: usize = 66; + // Alias type to allow passing handshake hash easier type HandshakeHash = [u8; 32]; type ChainingKey = [u8; 32]; @@ -42,7 +44,7 @@ pub enum HandshakeState2 { } impl HandshakeState2 { - pub(crate) fn next(self, input: &[u8]) -> Result<(Option, HandshakeState2), String> { + pub(crate) fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState2), String> { match self { HandshakeState2::Uninitiated2(state) => { state.next(input) }, HandshakeState2::AwaitingActOne2(state) => { state.next(input) }, @@ -54,7 +56,7 @@ impl HandshakeState2 { } trait IHandshakeState { - fn next(self, input: &[u8]) -> Result<(Option, HandshakeState2), String>; + fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState2), String>; } pub struct UninitiatedHandshakeState { @@ -113,7 +115,7 @@ impl UninitiatedHandshakeState { impl IHandshakeState for UninitiatedHandshakeState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (sender) - fn next(self, _input: &[u8]) -> Result<(Option, HandshakeState2), String> { + fn next(self, _input: &[u8]) -> Result<(Option>, HandshakeState2), String> { let initiator_static_private_key = self.initiator_static_private_key; let initiator_static_public_key = self.initiator_static_public_key; let initiator_ephemeral_private_key = self.initiator_ephemeral_private_key; @@ -132,7 +134,7 @@ impl IHandshakeState for UninitiatedHandshakeState { ); Ok(( - Some(Act::One(ActOne(act_one))), + Some(act_one.to_vec()), AwaitingActTwo2(AwaitingActTwoHandshakeState { initiator_static_private_key, initiator_static_public_key, @@ -166,7 +168,7 @@ impl AwaitingActOneHandshakeState { impl IHandshakeState for AwaitingActOneHandshakeState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (receiver) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (sender) - fn next(self, input: &[u8]) -> Result<(Option, HandshakeState2), String> { + fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState2), String> { let mut read_buffer = self.read_buffer; read_buffer.extend_from_slice(input); @@ -192,7 +194,7 @@ impl IHandshakeState for AwaitingActOneHandshakeState { ); Ok(( - Some(Act::Two(ActTwo(act_two))), + Some(act_two), AwaitingActThree2(AwaitingActThreeHandshakeState { hash, responder_ephemeral_private_key, @@ -207,7 +209,7 @@ impl IHandshakeState for AwaitingActOneHandshakeState { impl IHandshakeState for AwaitingActTwoHandshakeState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (receiver) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-three (sender) - fn next(self, input: &[u8]) -> Result<(Option, HandshakeState2), String> { + fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState2), String> { let mut read_buffer = self.read_buffer; read_buffer.extend_from_slice(input); @@ -249,9 +251,11 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { let mut conduit = Conduit::new(sending_key, receiving_key, chaining_key); // Send m = 0 || c || t over the network buffer - let mut act_three = [0u8; ACT_THREE_LENGTH]; - act_three[1..50].copy_from_slice(&tagged_encrypted_pubkey); - act_three[50..].copy_from_slice(authentication_tag.as_slice()); + let mut act_three = Vec::with_capacity(ACT_THREE_LENGTH); + act_three.extend(&[0]); + act_three.extend(&tagged_encrypted_pubkey); + act_three.extend(&authentication_tag); + assert_eq!(act_three.len(), ACT_THREE_LENGTH); // Any remaining data in the read buffer would be encrypted, so transfer ownership // to the Conduit for future use. In this case, it is unlikely that any valid data @@ -262,7 +266,7 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { } Ok(( - Some(Act::Three(ActThree(act_three))), + Some(act_three), Complete2(Some((conduit, responder_static_public_key))) )) } @@ -270,7 +274,7 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { impl IHandshakeState for AwaitingActThreeHandshakeState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-three (receiver) - fn next(self, input: &[u8]) -> Result<(Option, HandshakeState2), String> { + fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState2), String> { let mut read_buffer = self.read_buffer; read_buffer.extend_from_slice(input); @@ -366,7 +370,7 @@ fn handshake_state_initialization(responder_static_public_key: &PublicKey) -> (H // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (sender) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (sender) -fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_ephemeral_key: &PublicKey, remote_public_key: &PublicKey, chaining_key: ChainingKey, hash: HandshakeHash) -> ([u8; 50], HandshakeHash, SymmetricKey, SymmetricKey) { +fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_ephemeral_key: &PublicKey, remote_public_key: &PublicKey, chaining_key: ChainingKey, hash: HandshakeHash) -> (Vec, HandshakeHash, SymmetricKey, SymmetricKey) { // 1. e = generateKey() (passed in) // 2. h = SHA-256(h || e.pub.serializeCompressed()) let serialized_local_public_key = local_public_ephemeral_key.serialize(); @@ -388,9 +392,11 @@ fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_e let hash = sha256!(hash, tagged_ciphertext); // Send m = 0 || e.pub.serializeCompressed() || c - let mut act = [0u8; 50]; - act[1..34].copy_from_slice(&serialized_local_public_key); - act[34..].copy_from_slice(tagged_ciphertext.as_slice()); + let mut act = Vec::with_capacity(ACT_ONE_TWO_LENGTH); + act.extend(&[0]); + act.extend_from_slice(&serialized_local_public_key); + act.extend(&tagged_ciphertext); + assert_eq!(act.len(), ACT_ONE_TWO_LENGTH); (act, hash, chaining_key, temporary_key) } @@ -472,7 +478,6 @@ mod test { use bitcoin::secp256k1; use bitcoin::secp256k1::{PublicKey, SecretKey}; - use ln::peers::handshake::acts::Act; use ln::peers::handshake::states::{UninitiatedHandshakeState, AwaitingActOneHandshakeState, HandshakeState2}; use ln::peers::handshake::states::HandshakeState2::{AwaitingActThree2, AwaitingActTwo2, Complete2}; @@ -509,7 +514,7 @@ mod test { macro_rules! do_next_or_panic { ($state:expr, $input:expr) => { if let (Some(output_act), next_state) = $state.next($input).unwrap() { - (output_act.serialize(), next_state) + (output_act, next_state) } else { panic!(); } @@ -530,7 +535,7 @@ mod test { fn uninitiated_to_awaiting_act_two() { let test_ctx = TestCtx::new(); - assert_matches!(test_ctx.initiator.next(&[]).unwrap(), (Some(Act::One(_)), AwaitingActTwo2(_))); + assert_matches!(test_ctx.initiator.next(&[]).unwrap(), (Some(_), AwaitingActTwo2(_))); } // Initiator::Uninitiated -> AwaitingActTwo (extra bytes in argument) @@ -538,7 +543,7 @@ mod test { fn uninitiated_to_awaiting_act_two_extra_bytes() { let test_ctx = TestCtx::new(); - assert_matches!(test_ctx.initiator.next(&[1]).unwrap(), (Some(Act::One(_)), AwaitingActTwo2(_))); + assert_matches!(test_ctx.initiator.next(&[1]).unwrap(), (Some(_), AwaitingActTwo2(_))); } // Responder::AwaitingActOne -> Error (input too small) @@ -559,7 +564,7 @@ mod test { let (mut act1, _) = do_next_or_panic!(test_ctx.initiator, &[]); act1.extend_from_slice(&[1]); - assert_matches!(test_ctx.responder.next(&act1).unwrap(), (Some(Act::Two(_)), AwaitingActThree2(_))); + assert_matches!(test_ctx.responder.next(&act1).unwrap(), (Some(_), AwaitingActThree2(_))); } // Responder::AwaitingActOne -> Error (bad version byte) @@ -603,7 +608,7 @@ mod test { let test_ctx = TestCtx::new(); let (act1, _) = do_next_or_panic!(test_ctx.initiator, &[]); - assert_matches!(test_ctx.responder.next(&act1).unwrap(), (Some(Act::Two(_)), AwaitingActThree2(_))); + assert_matches!(test_ctx.responder.next(&act1).unwrap(), (Some(_), AwaitingActThree2(_))); } // Initiator::AwaitingActTwo -> Complete @@ -613,7 +618,7 @@ mod test { let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); let (act2, _awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); - let remote_pubkey = if let (Some(Act::Three(_)), Complete2(Some((_, remote_pubkey)))) = awaiting_act_two_state.next(&act2).unwrap() { + let remote_pubkey = if let (Some(_), Complete2(Some((_, remote_pubkey)))) = awaiting_act_two_state.next(&act2).unwrap() { remote_pubkey } else { panic!(); From e0a1e71e8981a72975c219027caa31bbdd1813d6 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 19 Aug 2020 21:21:18 -0700 Subject: [PATCH 19/45] refactor: Remove get_remote_pubkey interface Now that final process_act() returns (conduit, remote_pubkey), the callers can just receive it when the handshake is complete. --- lightning/src/ln/peers/handler.rs | 18 +++----------- lightning/src/ln/peers/handshake/mod.rs | 33 +++---------------------- 2 files changed, 8 insertions(+), 43 deletions(-) diff --git a/lightning/src/ln/peers/handler.rs b/lightning/src/ln/peers/handler.rs index 05431529644..eb92785187e 100644 --- a/lightning/src/ln/peers/handler.rs +++ b/lightning/src/ln/peers/handler.rs @@ -141,12 +141,9 @@ impl PeerState { } fn process_peer_data(&mut self, data: &[u8], mutable_response_buffer: &mut LinkedList>) -> PeerDataProcessingDecision { - let mut conduit_option = None; - let mut decision_option = None; - match self { &mut PeerState::Authenticating(ref mut handshake) => { - let (next_act, conduit) = match handshake.process_act(data) { + let (next_act, conduit_and_remote_pubkey) = match handshake.process_act(data) { Ok(act_result) => act_result, Err(e) => { return PeerDataProcessingDecision::Disconnect(PeerHandleError { no_connection_possible: false }); @@ -158,23 +155,16 @@ impl PeerState { mutable_response_buffer.push_back(act); } - let remote_pubkey_option = handshake.get_remote_pubkey(); - if let Some(conduit) = conduit { - conduit_option = Some(conduit); - decision_option = Some(PeerDataProcessingDecision::CompleteHandshake(requires_response, remote_pubkey_option)); + if let Some((conduit, remote_pubkey)) = conduit_and_remote_pubkey { + *self = PeerState::Connected(conduit); + return PeerDataProcessingDecision::CompleteHandshake(requires_response, Some(remote_pubkey)); } } &mut PeerState::Connected(ref mut conduit) => { conduit.read(data); } - }; - - if let (Some(conduit), Some(decision)) = (conduit_option, decision_option) { - *self = PeerState::Connected(conduit); - return decision; } - PeerDataProcessingDecision::Continue } } diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs index 07728d81fa9..472aabcd439 100644 --- a/lightning/src/ln/peers/handshake/mod.rs +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -13,7 +13,6 @@ mod states; /// Currently requires explicit ephemeral private key specification. pub struct PeerHandshake { state: Option, - remote_public_key: Option, } impl PeerHandshake { @@ -21,7 +20,6 @@ impl PeerHandshake { pub fn new_outbound(private_key: &SecretKey, remote_public_key: &PublicKey, ephemeral_private_key: &SecretKey) -> Self { Self { state: Some(HandshakeState2::Uninitiated2(UninitiatedHandshakeState::new(private_key.clone(), ephemeral_private_key.clone(), remote_public_key.clone()))), - remote_public_key: Some(remote_public_key.clone()), } } @@ -29,16 +27,9 @@ impl PeerHandshake { pub fn new_inbound(private_key: &SecretKey, ephemeral_private_key: &SecretKey) -> Self { Self { state: Some(HandshakeState2::AwaitingActOne2(AwaitingActOneHandshakeState::new(private_key.clone(), ephemeral_private_key.clone()))), - remote_public_key: None, } } - /// Return the remote public key once it has been extracted from the third act. - /// Potentially useful for inbound connections - pub fn get_remote_pubkey(&self) -> Option { - self.remote_public_key - } - /// Process act dynamically /// # Arguments /// `input`: Byte slice received from peer as part of the handshake protocol @@ -47,17 +38,14 @@ impl PeerHandshake { /// Returns a tuple with the following components: /// `.0`: Byte vector containing the next act to send back to the peer per the handshake protocol /// `.1`: Conduit option if the handshake was just processed to completion and messages can now be encrypted and decrypted - pub fn process_act(&mut self, input: &[u8]) -> Result<(Option>, Option), String> { + pub fn process_act(&mut self, input: &[u8]) -> Result<(Option>, Option<(Conduit, PublicKey)>), String> { let cur_state = self.state.take().unwrap(); let (act_opt, mut next_state) = cur_state.next(input)?; let result = match next_state { HandshakeState2::Complete2(ref mut conduit_and_pubkey) => { - let (conduit, remote_pubkey) = conduit_and_pubkey.take().unwrap(); - self.remote_public_key = Some(remote_pubkey); - - Ok((act_opt, Some(conduit))) + Ok((act_opt, conduit_and_pubkey.take())) }, _ => { Ok((act_opt, None)) } }; @@ -80,9 +68,7 @@ mod test { struct TestCtx { outbound_handshake: PeerHandshake, - outbound_public_key: PublicKey, inbound_handshake: PeerHandshake, - inbound_public_key: PublicKey } impl TestCtx { @@ -102,9 +88,7 @@ mod test { TestCtx { outbound_handshake, - outbound_public_key, inbound_handshake, - inbound_public_key } } } @@ -130,7 +114,6 @@ mod test { let test_ctx = TestCtx::new(); assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::Uninitiated2(_))); - assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); } // Default Inbound::AwaitingActOne @@ -139,7 +122,6 @@ mod test { let test_ctx = TestCtx::new(); assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState2::AwaitingActOne2(_))); - assert!(test_ctx.inbound_handshake.get_remote_pubkey().is_none()); } /* @@ -153,7 +135,6 @@ mod test { assert_matches!(test_ctx.outbound_handshake.process_act(&[]).unwrap(), (Some(_), None)); assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::AwaitingActTwo2(_))); - assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); } // Outbound::Uninitiated -> AwaitingActTwo (extra bytes in argument) @@ -164,7 +145,6 @@ mod test { // TODO: process_act() should error if state does not use vec, but it is non-empty assert_matches!(test_ctx.outbound_handshake.process_act(&[1]).unwrap(), (Some(_), None)); assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::AwaitingActTwo2(_))); - assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); } // Inbound::AwaitingActOne -> Error (input too small) @@ -185,7 +165,6 @@ mod test { assert_matches!(test_ctx.inbound_handshake.process_act(&act1).unwrap(), (Some(_), None)); assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState2::AwaitingActThree2(_))); - assert!(test_ctx.inbound_handshake.get_remote_pubkey().is_none()); } // Inbound::AwaitingActOne -> Error (bad version byte) @@ -229,7 +208,6 @@ mod test { assert_matches!(test_ctx.inbound_handshake.process_act(&act1).unwrap(), (Some(_), None)); assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState2::AwaitingActThree2(_))); - assert!(test_ctx.inbound_handshake.get_remote_pubkey().is_none()); } // Outbound::AwaitingActTwo -> Complete (valid conduit) @@ -241,7 +219,6 @@ mod test { assert_matches!(test_ctx.outbound_handshake.process_act(&act2).unwrap(), (Some(_), Some(_))); assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::Complete2(_))); - assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); } @@ -256,14 +233,13 @@ mod test { let mut act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); act2.extend_from_slice(&[1; 100]); - let conduit = if let (_, Some(conduit)) = test_ctx.outbound_handshake.process_act(&act2).unwrap() { + let (conduit, _) = if let (_, Some(conduit)) = test_ctx.outbound_handshake.process_act(&act2).unwrap() { conduit } else { panic!(); }; assert_eq!(100, conduit.decryptor.read_buffer_length()); - assert_eq!(test_ctx.outbound_handshake.get_remote_pubkey(), Some(test_ctx.inbound_public_key)); } // Outbound::AwaitingActTwo -> Error (input too small) @@ -321,7 +297,6 @@ mod test { assert_matches!(test_ctx.inbound_handshake.process_act(&act3).unwrap(), (None, Some(_))); assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState2::Complete2(_))); - assert_eq!(test_ctx.inbound_handshake.get_remote_pubkey(), Some(test_ctx.outbound_public_key)); } // Inbound::AwaitingActThree -> Complete (with extra bytes) @@ -335,7 +310,7 @@ mod test { let mut act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); act3.extend_from_slice(&[2; 100]); - let conduit = if let (None, Some(conduit)) = test_ctx.inbound_handshake.process_act(&act3).unwrap() { + let (conduit, _) = if let (None, Some(conduit)) = test_ctx.inbound_handshake.process_act(&act3).unwrap() { conduit } else { panic!(); From 2f67eed1c6c8639a92a49652bd40cdeaeffeaec4 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Thu, 20 Aug 2020 10:15:01 -0700 Subject: [PATCH 20/45] refactor: Clean up process_peer_data Now that the remote_pubkey is returned from process_act(), the logic can be more straightforward. Each branch is responsible for returning the next PeerState and PeerDataProcessingDecision. Remove Option from CompleteHandshake now that the PublicKey is guaranteed to exist --- lightning/src/ln/peers/handler.rs | 56 ++++++++++++++++++------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/lightning/src/ln/peers/handler.rs b/lightning/src/ln/peers/handler.rs index eb92785187e..c8ace0e4439 100644 --- a/lightning/src/ln/peers/handler.rs +++ b/lightning/src/ln/peers/handler.rs @@ -127,7 +127,7 @@ enum PeerState { } enum PeerDataProcessingDecision { - CompleteHandshake(bool, Option), + CompleteHandshake(bool, PublicKey), Continue, Disconnect(PeerHandleError) } @@ -141,31 +141,44 @@ impl PeerState { } fn process_peer_data(&mut self, data: &[u8], mutable_response_buffer: &mut LinkedList>) -> PeerDataProcessingDecision { - match self { + let (new_state_opt, decision) = match self { &mut PeerState::Authenticating(ref mut handshake) => { - let (next_act, conduit_and_remote_pubkey) = match handshake.process_act(data) { - Ok(act_result) => act_result, + match handshake.process_act(data) { + // Any errors originating from the handshake sequence result in Disconnect Err(e) => { - return PeerDataProcessingDecision::Disconnect(PeerHandleError { no_connection_possible: false }); - } - }; - - let requires_response = next_act.is_some(); - if let Some(act) = next_act { - mutable_response_buffer.push_back(act); - } + (None, PeerDataProcessingDecision::Disconnect(PeerHandleError { no_connection_possible: false })) + }, + // Otherwise, handshake may or may not be complete depending on whether or not + // we receive a (conduit, pubkey) + Ok((response_vec_opt, conduit_and_remote_pubkey_opt)) => { + + // Any response generated by the handshake sequence is put into the response buffer + let requires_response = response_vec_opt.is_some(); + if let Some(response_vec) = response_vec_opt { + mutable_response_buffer.push_back(response_vec); + } - if let Some((conduit, remote_pubkey)) = conduit_and_remote_pubkey { - *self = PeerState::Connected(conduit); - return PeerDataProcessingDecision::CompleteHandshake(requires_response, Some(remote_pubkey)); + // if process_act() returns the conduit and remote_pubkey the handshake + // is complete + if let Some((conduit, remote_pubkey)) = conduit_and_remote_pubkey_opt { + (Some(PeerState::Connected(conduit)), PeerDataProcessingDecision::CompleteHandshake(requires_response, remote_pubkey)) + } else { + (None, PeerDataProcessingDecision::Continue) + } + } } - } - + }, &mut PeerState::Connected(ref mut conduit) => { conduit.read(data); + (None, PeerDataProcessingDecision::Continue) } + }; + + if let Some(new_state) = new_state_opt { + *self = new_state; } - PeerDataProcessingDecision::Continue + + decision } } @@ -565,12 +578,9 @@ impl PeerManager { + PeerDataProcessingDecision::CompleteHandshake(needs_to_send_init_message, remote_pubkey) => { send_init_message = needs_to_send_init_message; - - if let Some(key) = remote_pubkey_option { - peer.their_node_id = Some(key); - } + peer.their_node_id = Some(remote_pubkey); // insert node id match peers.node_id_to_descriptor.entry(peer.their_node_id.unwrap()) { From ab4ff644ec6a560108c8b7279cd229f7730417d0 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Thu, 20 Aug 2020 10:28:22 -0700 Subject: [PATCH 21/45] refactor: Doc and variable name cleanup in handshake/mod.rs Clean up loose threads (wrong docs, variable names) now that this code is in a more stable state. --- lightning/src/ln/peers/handshake/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs index 472aabcd439..ec5a91f6ad5 100644 --- a/lightning/src/ln/peers/handshake/mod.rs +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -17,16 +17,16 @@ pub struct PeerHandshake { impl PeerHandshake { /// Instantiate a new handshake with a node identity secret key and an ephemeral private key - pub fn new_outbound(private_key: &SecretKey, remote_public_key: &PublicKey, ephemeral_private_key: &SecretKey) -> Self { + pub fn new_outbound(initiator_static_private_key: &SecretKey, responder_static_public_key: &PublicKey, initiator_ephemeral_private_key: &SecretKey) -> Self { Self { - state: Some(HandshakeState2::Uninitiated2(UninitiatedHandshakeState::new(private_key.clone(), ephemeral_private_key.clone(), remote_public_key.clone()))), + state: Some(HandshakeState2::Uninitiated2(UninitiatedHandshakeState::new(initiator_static_private_key.clone(), initiator_ephemeral_private_key.clone(), responder_static_public_key.clone()))), } } /// Instantiate a new handshake in anticipation of a peer's first handshake act - pub fn new_inbound(private_key: &SecretKey, ephemeral_private_key: &SecretKey) -> Self { + pub fn new_inbound(responder_static_private_key: &SecretKey, responder_ephemeral_private_key: &SecretKey) -> Self { Self { - state: Some(HandshakeState2::AwaitingActOne2(AwaitingActOneHandshakeState::new(private_key.clone(), ephemeral_private_key.clone()))), + state: Some(HandshakeState2::AwaitingActOne2(AwaitingActOneHandshakeState::new(responder_static_private_key.clone(), responder_ephemeral_private_key.clone()))), } } @@ -37,7 +37,7 @@ impl PeerHandshake { /// # Return values /// Returns a tuple with the following components: /// `.0`: Byte vector containing the next act to send back to the peer per the handshake protocol - /// `.1`: Conduit option if the handshake was just processed to completion and messages can now be encrypted and decrypted + /// `.1`: Some(Conduit, PublicKey) if the handshake was just processed to completion and messages can now be encrypted and decrypted pub fn process_act(&mut self, input: &[u8]) -> Result<(Option>, Option<(Conduit, PublicKey)>), String> { let cur_state = self.state.take().unwrap(); From f29936b9ac80aa72fcd85fc8de4205dfb43f6305 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Thu, 20 Aug 2020 13:02:14 -0700 Subject: [PATCH 22/45] tests: Finish testing handshake/mod.rs Now that the implementation is complete and swapped over, remove the duplicated tests for process_act(). At this layer, all that needs to be tested is: * The new state in PeerHandshake is set to the state returned from state_machine.next() * Any error generated from state_machine.next() is returned * Any calls to process_next() after an error was returned panic These tests are a good use case for a mocking library, but for now just leverage implementation details to trigger the proper errors and states. --- lightning/src/ln/peers/handshake/mod.rs | 305 +++--------------------- 1 file changed, 38 insertions(+), 267 deletions(-) diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs index ec5a91f6ad5..87f568e0d78 100644 --- a/lightning/src/ln/peers/handshake/mod.rs +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -58,8 +58,6 @@ impl PeerHandshake { #[cfg(test)] mod test { - use hex; - use bitcoin::secp256k1; use bitcoin::secp256k1::key::{PublicKey, SecretKey}; @@ -68,27 +66,31 @@ mod test { struct TestCtx { outbound_handshake: PeerHandshake, + outbound_static_public_key: PublicKey, inbound_handshake: PeerHandshake, + inbound_static_public_key: PublicKey } impl TestCtx { fn new() -> TestCtx { let curve = secp256k1::Secp256k1::new(); - let outbound_private_key = SecretKey::from_slice(&[0x_11_u8; 32]).unwrap(); - let outbound_public_key = PublicKey::from_secret_key(&curve, &outbound_private_key); - let outbound_ephemeral_key = SecretKey::from_slice(&[0x_12_u8; 32]).unwrap(); + let outbound_static_private_key = SecretKey::from_slice(&[0x_11_u8; 32]).unwrap(); + let outbound_static_public_key = PublicKey::from_secret_key(&curve, &outbound_static_private_key); + let outbound_ephemeral_private_key = SecretKey::from_slice(&[0x_12_u8; 32]).unwrap(); - let inbound_private_key = SecretKey::from_slice(&[0x_21_u8; 32]).unwrap(); - let inbound_public_key = PublicKey::from_secret_key(&curve, &inbound_private_key); - let inbound_ephemeral_key = SecretKey::from_slice(&[0x_22_u8; 32]).unwrap(); + let inbound_static_private_key = SecretKey::from_slice(&[0x_21_u8; 32]).unwrap(); + let inbound_static_public_key = PublicKey::from_secret_key(&curve, &inbound_static_private_key); + let inbound_ephemeral_private_key = SecretKey::from_slice(&[0x_22_u8; 32]).unwrap(); - let outbound_handshake = PeerHandshake::new_outbound(&outbound_private_key, &inbound_public_key, &outbound_ephemeral_key); - let inbound_handshake = PeerHandshake::new_inbound(&inbound_private_key, &inbound_ephemeral_key); + let outbound_handshake = PeerHandshake::new_outbound(&outbound_static_private_key, &inbound_static_public_key, &outbound_ephemeral_private_key); + let inbound_handshake = PeerHandshake::new_inbound(&inbound_static_private_key, &inbound_ephemeral_private_key); TestCtx { outbound_handshake, + outbound_static_public_key, inbound_handshake, + inbound_static_public_key, } } } @@ -128,287 +130,56 @@ mod test { * PeerHandshake::process_act() tests */ - // Outbound::Uninitiated -> AwaitingActTwo - #[test] - fn peer_handshake_outbound_uninitiated_to_awaiting_act_two() { - let mut test_ctx = TestCtx::new(); - - assert_matches!(test_ctx.outbound_handshake.process_act(&[]).unwrap(), (Some(_), None)); - assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::AwaitingActTwo2(_))); - } - - // Outbound::Uninitiated -> AwaitingActTwo (extra bytes in argument) - #[test] - fn peer_handshake_outbound_uninitiated_to_awaiting_act_two_nonempty_input() { - let mut test_ctx = TestCtx::new(); - - // TODO: process_act() should error if state does not use vec, but it is non-empty - assert_matches!(test_ctx.outbound_handshake.process_act(&[1]).unwrap(), (Some(_), None)); - assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::AwaitingActTwo2(_))); - } - - // Inbound::AwaitingActOne -> Error (input too small) - #[test] - fn peer_handshake_new_inbound_awaiting_act_one_input_too_small() { - let mut test_ctx = TestCtx::new(); - - assert_eq!(test_ctx.inbound_handshake.process_act(&[]).err(), Some(String::from("need at least 50 bytes"))); - } - - // Inbound::AwaitingActOne -> AwaitingActThree (excess bytes) - // TODO: This should error early if we receive act3 data prior to sending act2 - #[test] - fn peer_handshake_new_inbound_awaiting_act_one_input_too_large() { - let mut test_ctx = TestCtx::new(); - let mut act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - act1.extend_from_slice(&[1]); - - assert_matches!(test_ctx.inbound_handshake.process_act(&act1).unwrap(), (Some(_), None)); - assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState2::AwaitingActThree2(_))); - } - - // Inbound::AwaitingActOne -> Error (bad version byte) - #[test] - fn peer_handshake_new_inbound_awaiting_act_one_bad_version() { - let mut test_ctx = TestCtx::new(); - let mut act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - // Set bad version byte - act1[0] = 1; - - assert_eq!(test_ctx.inbound_handshake.process_act(&act1).err(), Some(String::from("unexpected version"))); - } - - // Inbound::AwaitingActOne -> Error (invalid hmac) - #[test] - fn peer_handshake_new_inbound_awaiting_act_invalid_hmac() { - let mut test_ctx = TestCtx::new(); - let mut act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - // corrupt the ciphertext - act1[34] = 0; - - assert_eq!(test_ctx.inbound_handshake.process_act(&act1).err(), Some(String::from("invalid hmac"))); - } - - // Inbound::AwaitingActOne -> Error (invalid remote ephemeral key) - #[test] - fn peer_handshake_new_inbound_awaiting_act_invalid_remote_ephemeral_key() { - let mut test_ctx = TestCtx::new(); - let mut act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - // corrupt the ephemeral public key - act1[1] = 0; - - assert_eq!(test_ctx.inbound_handshake.process_act(&act1).err(), Some(String::from("invalid remote ephemeral public key"))); - } - - // Inbound::AwaitingActOne -> AwaitingActThree - #[test] - fn peer_handshake_new_inbound_awaiting_act_one_to_awaiting_act_three() { - let mut test_ctx = TestCtx::new(); - let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - - assert_matches!(test_ctx.inbound_handshake.process_act(&act1).unwrap(), (Some(_), None)); - assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState2::AwaitingActThree2(_))); - } - - // Outbound::AwaitingActTwo -> Complete (valid conduit) + // Full sequence from initiator and responder as a sanity test. State machine is tested in states.rs #[test] - fn peer_handshake_outbound_awaiting_act_two_process() { + fn full_sequence_sanity_test() { let mut test_ctx = TestCtx::new(); let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); - assert_matches!(test_ctx.outbound_handshake.process_act(&act2).unwrap(), (Some(_), Some(_))); - assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::Complete2(_))); - } - - - // Outbound::AwaitingActTwo -> Complete (with extra data) - // Ensures that any remaining data in the read buffer is transferred to the conduit once - // the handshake is complete - // TODO: Is this valid? Don't we expect peers to need ActThree before sending additional data? - #[test] - fn peer_handshake_new_outbound_excess_bytes_after_complete_are_in_conduit() { - let mut test_ctx = TestCtx::new(); - let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - let mut act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); - act2.extend_from_slice(&[1; 100]); - - let (conduit, _) = if let (_, Some(conduit)) = test_ctx.outbound_handshake.process_act(&act2).unwrap() { - conduit + let (act3, inbound_remote_pubkey) = if let (Some(act3), Some((_, remote_pubkey))) = test_ctx.outbound_handshake.process_act(&act2).unwrap() { + (act3, remote_pubkey) } else { panic!(); }; - assert_eq!(100, conduit.decryptor.read_buffer_length()); - } - - // Outbound::AwaitingActTwo -> Error (input too small) - #[test] - fn peer_handshake_outbound_awaiting_act_two_input_too_small() { - let mut test_ctx = TestCtx::new(); - let _act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - - assert_eq!(test_ctx.outbound_handshake.process_act(&[1]).err(), Some(String::from("need at least 50 bytes"))); - } - - // Outbound::AwaitingActTwo -> Error (bad version byte) - #[test] - fn peer_handshake_outbound_awaiting_act_two_bad_version() { - let mut test_ctx = TestCtx::new(); - let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - let mut act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); - // Set version byte to 1 - act2[0] = 1; - - assert_eq!(test_ctx.outbound_handshake.process_act(&act2).err(), Some(String::from("unexpected version"))); - } - - // Outbound::AwaitingActTwo -> Error (invalid hmac) - #[test] - fn peer_handshake_outbound_awaiting_act_two_invalid_hmac() { - let mut test_ctx = TestCtx::new(); - let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - let mut act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); - // corrupt the ciphertext - act2[34] = 1; - - assert_eq!(test_ctx.outbound_handshake.process_act(&act2).err(), Some(String::from("invalid hmac"))); - } - - // Outbound::AwaitingActTwo -> Error (invalid remote ephemeral key) - #[test] - fn peer_handshake_outbound_awaiting_act_two_invalid_remote_ephemeral_key() { - let mut test_ctx = TestCtx::new(); - let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - let mut act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); - // corrupt the ephemeral public key - act2[1] = 1; - - assert_eq!(test_ctx.outbound_handshake.process_act(&act2).err(), Some(String::from("invalid remote ephemeral public key"))); - } - - // Inbound::AwaitingActThree -> Complete - #[test] - fn peer_handshake_new_inbound_awaiting_act_three_to_awaiting_act_three() { - let mut test_ctx = TestCtx::new(); - let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); - let act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); - - assert_matches!(test_ctx.inbound_handshake.process_act(&act3).unwrap(), (None, Some(_))); - assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState2::Complete2(_))); - } - - // Inbound::AwaitingActThree -> Complete (with extra bytes) - // Ensures that any remaining data in the read buffer is transferred to the conduit once - // the handshake is complete - #[test] - fn peer_handshake_new_inbound_excess_bytes_after_complete_are_in_conduit() { - let mut test_ctx = TestCtx::new(); - let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); - let mut act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); - act3.extend_from_slice(&[2; 100]); - - let (conduit, _) = if let (None, Some(conduit)) = test_ctx.inbound_handshake.process_act(&act3).unwrap() { - conduit + let outbound_remote_pubkey = if let (None, Some((_, remote_pubkey))) = test_ctx.inbound_handshake.process_act(&act3).unwrap() { + remote_pubkey } else { panic!(); }; - assert_eq!(100, conduit.decryptor.read_buffer_length()); + assert_eq!(inbound_remote_pubkey, test_ctx.inbound_static_public_key); + assert_eq!(outbound_remote_pubkey, test_ctx.outbound_static_public_key); } - // Inbound::AwaitingActThree -> Error (input too small) + // Test that the internal state object matches the return from state_machine.next() + // This could make use of a mocking library to remove the dependency on the state machine. All + // that needs to be tested is that the expected state (returned) from state_machine.next() matchse + // the internal set state. #[test] - fn peer_handshake_new_inbound_awaiting_act_three_input_too_small() { + fn process_act_properly_updates_state() { let mut test_ctx = TestCtx::new(); - let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); - let act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); - - assert_eq!(test_ctx.inbound_handshake.process_act(&act3[..65]).err(), Some(String::from("need at least 66 bytes"))); - } - - // Inbound::AwaitingActThree -> Error (bad version byte) - #[test] - fn peer_handshake_new_inbound_awaiting_act_three_bad_version() { - let mut test_ctx = TestCtx::new(); - let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); - let mut act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); - // set version byte to 1 - act3[0] = 1; - - assert_eq!(test_ctx.inbound_handshake.process_act(&act3).err(), Some(String::from("unexpected version"))); - } - - // Inbound::AwaitingActThree -> Error (invalid hmac) - #[test] - fn peer_handshake_new_inbound_awaiting_act_three_invalid_hmac() { - let mut test_ctx = TestCtx::new(); - let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); - let mut act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); - // trigger decryption error by corrupting byte 1 - act3[1] = 0; - - assert_eq!(test_ctx.inbound_handshake.process_act(&act3).err(), Some(String::from("invalid hmac"))); - } - - // Inbound::AwaitingActThree -> Error (invalid tag hmac) - #[test] - fn peer_handshake_new_inbound_awaiting_act_three_invalid_tag_hmac() { - let mut test_ctx = TestCtx::new(); - let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); - let mut act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); - // trigger tag decryption error by corrupting byte 50 - act3[50] = 0; - - assert_eq!(test_ctx.inbound_handshake.process_act(&act3).err(), Some(String::from("invalid hmac"))); - } - - // Inbound::Complete -> Panic - #[test] - #[should_panic(expected = "no acts left to process")] - fn peer_handshake_new_inbound_complete_then_process_act() { - let mut test_ctx = TestCtx::new(); - let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); - let act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); - test_ctx.inbound_handshake.process_act(&act3).unwrap(); - - do_process_act_or_panic!(test_ctx.inbound_handshake, &[]); + do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::AwaitingActTwo2(_))); } - // Outbound::None -> Panic + // Test that any errors from the state machine are passed back to the caller + // This could make use of a mocking library to remove the dependency on the state machine + // logic. All that needs to be tested is that an error from state_machine.next() + // results in an error in process_act() #[test] - #[should_panic(expected = "no acts left to process")] - fn peer_handshake_new_outbound_complete_then_process_act() { + fn errors_properly_returned() { let mut test_ctx = TestCtx::new(); - let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); - let act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); - test_ctx.inbound_handshake.process_act(&act3).unwrap(); - - do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); + assert_matches!(test_ctx.inbound_handshake.process_act(&[]).err(), Some(_)); } - // Test the Act byte generation against known good hard-coded values in case the implementation - // changes in a symmetric way that makes the other tests useless + // Test that any use of the PeerHandshake after returning an error panics #[test] - fn peer_handshake_external_spec() { + #[should_panic(expected = "called `Option::unwrap()` on a `None` value")] + fn use_after_error_panics() { let mut test_ctx = TestCtx::new(); - let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); - let act3 = do_process_act_or_panic!(test_ctx.outbound_handshake, &act2); - - assert_eq!(hex::encode(&act1), - "00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a"); - assert_eq!(hex::encode(&act2), - "0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae"); - assert_eq!(hex::encode(&act3), - "00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba"); + assert_matches!(test_ctx.inbound_handshake.process_act(&[]).err(), Some(_)); + test_ctx.inbound_handshake.process_act(&[]).unwrap(); } } From ab277b4a6be481c4ee9dfecbd1c5767a84aea16b Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Thu, 20 Aug 2020 13:46:26 -0700 Subject: [PATCH 23/45] refactor: Clean up use of Sha256 in states.rs The previous concat!() macro and the variadic sha256!() had the same functionality due to the way sha.input() works. Reduce to a common code path that now uses concat_then_sha256() in all places and reduces the number of potential copies. Where appropriate, use the actual Sha256 type. The full type usage is hard due to the usages of hkdf::derive() in other parts of the code, but this is a good start on the path to cleaner type usage. --- lightning/src/ln/peers/handshake/states.rs | 65 +++++++++------------- 1 file changed, 26 insertions(+), 39 deletions(-) diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 949ec3f5817..a67ee07b37e 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -11,27 +11,16 @@ use ln::peers::conduit::{Conduit, SymmetricKey}; const ACT_ONE_TWO_LENGTH: usize = 50; const ACT_THREE_LENGTH: usize = 66; -// Alias type to allow passing handshake hash easier -type HandshakeHash = [u8; 32]; type ChainingKey = [u8; 32]; -// Generate a SHA-256 hash from one or more elements -macro_rules! sha256 { +// Generate a SHA-256 hash from one or more elements concatenated together +macro_rules! concat_then_sha256 { ( $( $x:expr ),+ ) => {{ let mut sha = Sha256::engine(); $( sha.input($x.as_ref()); )+ - Sha256::from_engine(sha).into_inner() - }} -} - -// Concatenate two slices in a Vec -macro_rules! concat { - ($arg1:expr, $arg2:expr) => {{ - let mut result = $arg1.to_vec(); - result.extend_from_slice($arg2.as_ref()); - result + Sha256::from_engine(sha) }} } @@ -65,16 +54,16 @@ pub struct UninitiatedHandshakeState { initiator_ephemeral_private_key: SecretKey, initiator_ephemeral_public_key: PublicKey, responder_static_public_key: PublicKey, - chaining_key: ChainingKey, - hash: HandshakeHash, + chaining_key: Sha256, + hash: Sha256 } pub struct AwaitingActOneHandshakeState { responder_static_private_key: SecretKey, responder_ephemeral_private_key: SecretKey, responder_ephemeral_public_key: PublicKey, - chaining_key: ChainingKey, - hash: HandshakeHash, + chaining_key: Sha256, + hash: Sha256, read_buffer: Vec } @@ -84,12 +73,12 @@ pub struct AwaitingActTwoHandshakeState { initiator_ephemeral_private_key: SecretKey, responder_static_public_key: PublicKey, chaining_key: ChainingKey, - hash: HandshakeHash, + hash: Sha256, read_buffer: Vec } pub struct AwaitingActThreeHandshakeState { - hash: HandshakeHash, + hash: Sha256, responder_ephemeral_private_key: SecretKey, chaining_key: ChainingKey, temporary_key: [u8; 32], @@ -122,14 +111,14 @@ impl IHandshakeState for UninitiatedHandshakeState { let initiator_ephemeral_public_key = self.initiator_ephemeral_public_key; let responder_static_public_key = self.responder_static_public_key; let chaining_key = self.chaining_key; - let mut hash = self.hash; + let hash = self.hash; // serialize act one let (act_one, hash, chaining_key, _) = calculate_act_message( &initiator_ephemeral_private_key, &initiator_ephemeral_public_key, &responder_static_public_key, - chaining_key, + chaining_key.into_inner(), hash, ); @@ -181,7 +170,7 @@ impl IHandshakeState for AwaitingActOneHandshakeState { let (initiator_ephemeral_public_key, hash, chaining_key, _) = process_act_message( &mut read_buffer, &responder_static_private_key, - chaining_key, + chaining_key.into_inner(), hash, )?; @@ -232,7 +221,7 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { let tagged_encrypted_pubkey = chacha::encrypt(&temporary_key, 1, &hash, &initiator_static_public_key.serialize()); // 2. h = SHA-256(h || c) - let hash = sha256!(concat!(hash, tagged_encrypted_pubkey)); + let hash = concat_then_sha256!(hash, tagged_encrypted_pubkey); // 3. se = ECDH(s.priv, re) let ecdh = ecdh(&initiator_static_private_key, &responder_ephemeral_public_key); @@ -282,7 +271,7 @@ impl IHandshakeState for AwaitingActThreeHandshakeState { return Err("need at least 66 bytes".to_string()); } - let mut hash = self.hash; + let hash = self.hash; let temporary_key = self.temporary_key; let responder_ephemeral_private_key = self.responder_ephemeral_private_key; let chaining_key = self.chaining_key; @@ -318,7 +307,7 @@ impl IHandshakeState for AwaitingActThreeHandshakeState { }; // 5. h = SHA-256(h || c) - let hash = sha256!(concat!(hash, tagged_encrypted_pubkey)); + let hash = concat_then_sha256!(hash, tagged_encrypted_pubkey); // 6. se = ECDH(e.priv, rs) let ecdh = ecdh(&responder_ephemeral_private_key, &initiator_pubkey); @@ -351,30 +340,30 @@ impl IHandshakeState for AwaitingActThreeHandshakeState { } // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#handshake-state-initialization -fn handshake_state_initialization(responder_static_public_key: &PublicKey) -> (HandshakeHash, ChainingKey) { +fn handshake_state_initialization(responder_static_public_key: &PublicKey) -> (Sha256, Sha256) { let protocol_name = b"Noise_XK_secp256k1_ChaChaPoly_SHA256"; let prologue = b"lightning"; // 1. h = SHA-256(protocolName) // 2. ck = h - let chaining_key = sha256!(protocol_name); + let chaining_key = concat_then_sha256!(protocol_name); // 3. h = SHA-256(h || prologue) - let hash = sha256!(concat!(chaining_key, prologue)); + let hash = concat_then_sha256!(chaining_key, prologue); // h = SHA-256(h || responderPublicKey) - let hash = sha256!(concat!(hash, responder_static_public_key.serialize())); + let hash = concat_then_sha256!(hash, responder_static_public_key.serialize()); (hash, chaining_key) } // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (sender) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (sender) -fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_ephemeral_key: &PublicKey, remote_public_key: &PublicKey, chaining_key: ChainingKey, hash: HandshakeHash) -> (Vec, HandshakeHash, SymmetricKey, SymmetricKey) { +fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_ephemeral_key: &PublicKey, remote_public_key: &PublicKey, chaining_key: ChainingKey, hash: Sha256) -> (Vec, Sha256, SymmetricKey, SymmetricKey) { // 1. e = generateKey() (passed in) // 2. h = SHA-256(h || e.pub.serializeCompressed()) let serialized_local_public_key = local_public_ephemeral_key.serialize(); - let hash = sha256!(concat!(hash, serialized_local_public_key)); + let hash = concat_then_sha256!(hash, serialized_local_public_key); // 3. ACT1: es = ECDH(e.priv, rs) // 3. ACT2: es = ECDH(e.priv, re) @@ -389,7 +378,7 @@ fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_e let tagged_ciphertext = chacha::encrypt(&temporary_key, 0, &hash, &[0; 0]); // 6. h = SHA-256(h || c) - let hash = sha256!(hash, tagged_ciphertext); + let hash = concat_then_sha256!(hash, tagged_ciphertext); // Send m = 0 || e.pub.serializeCompressed() || c let mut act = Vec::with_capacity(ACT_ONE_TWO_LENGTH); @@ -404,7 +393,7 @@ fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_e // Due to the very high similarity of acts 1 and 2, this method is used to process both // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (receiver) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (receiver) -fn process_act_message(read_buffer: &mut Vec, local_private_key: &SecretKey, chaining_key: SymmetricKey, hash: HandshakeHash) -> Result<(PublicKey, HandshakeHash, SymmetricKey, SymmetricKey), String> { +fn process_act_message(read_buffer: &mut Vec, local_private_key: &SecretKey, chaining_key: ChainingKey, hash: Sha256) -> Result<(PublicKey, Sha256, SymmetricKey, SymmetricKey), String> { // 1. Read exactly 50 bytes from the network buffer if read_buffer.len() < ACT_ONE_TWO_LENGTH { return Err("need at least 50 bytes".to_string()); @@ -435,7 +424,7 @@ fn process_act_message(read_buffer: &mut Vec, local_private_key: &SecretKey, } // 4. h = SHA-256(h || re.serializeCompressed()) - let hash = sha256!(concat!(hash, ephemeral_public_key_bytes)); + let hash = concat_then_sha256!(hash, ephemeral_public_key_bytes); // 5. Act1: es = ECDH(s.priv, re) // 5. Act2: ee = ECDH(e.priv, ee) @@ -449,7 +438,7 @@ fn process_act_message(read_buffer: &mut Vec, local_private_key: &SecretKey, let _tag_check = chacha::decrypt(&temporary_key, 0, &hash, &chacha_tag)?; // 8. h = SHA-256(h || c) - let hash = sha256!(concat!(hash, chacha_tag)); + let hash = concat_then_sha256!(hash, chacha_tag); Ok((ephemeral_public_key, hash, chaining_key, temporary_key)) } @@ -466,9 +455,7 @@ fn ecdh(private_key: &SecretKey, public_key: &PublicKey) -> SymmetricKey { pk_object.mul_assign(&curve, &private_key[..]).expect("invalid multiplication"); let preimage = pk_object.serialize(); - let mut sha = Sha256::engine(); - sha.input(preimage.as_ref()); - Sha256::from_engine(sha).into_inner() + concat_then_sha256!(preimage).into_inner() } #[cfg(test)] From 57f95fe3fecaf2263cad83912eece67e21e56fff Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sat, 22 Aug 2020 14:14:47 -0700 Subject: [PATCH 24/45] refactor: Clean up hkdf::derive and add test Add documentation and a unit test against the relevant test vector from the RFC. --- lightning/src/ln/peers/hkdf.rs | 81 ++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 14 deletions(-) diff --git a/lightning/src/ln/peers/hkdf.rs b/lightning/src/ln/peers/hkdf.rs index 48415594114..6ac1391666d 100644 --- a/lightning/src/ln/peers/hkdf.rs +++ b/lightning/src/ln/peers/hkdf.rs @@ -1,18 +1,71 @@ use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine}; use bitcoin::hashes::sha256::Hash as Sha256; -pub fn derive(salt: &[u8], master: &[u8]) -> ([u8; 32], [u8; 32]) { - let mut hmac = HmacEngine::::new(salt); - hmac.input(master); - let prk = Hmac::from_engine(hmac).into_inner(); // prk = sha256(master) - - let mut hmac = HmacEngine::::new(&prk[..]); - hmac.input(&[1; 1]); - let t1 = Hmac::from_engine(hmac).into_inner(); // t1 = sha256(prk | 1) - - let mut hmac = HmacEngine::::new(&prk[..]); - hmac.input(&t1); - hmac.input(&[2; 1]); - // sha256(prk | t1 | 2) = sha256(sha256(master) | sha256(sha256(sha256(master) | 1) | 2) - (t1, Hmac::from_engine(hmac).into_inner()) +// Allows 1 or more inputs and "concatenates" them together using the input() function +// of HmacEngine:: +macro_rules! hmac_hash { + ( $salt:expr, ($( $input:expr ),+ )) => {{ + let mut engine = HmacEngine::::new($salt); + $( + engine.input($input); + )+ + Hmac::from_engine(engine).into_inner() + }} } + +/// Implements HKDF defined in [BOLT #8](https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#handshake-state) +/// [RFC 5869](https://tools.ietf.org/html/rfc5869pub) +/// Returns the first 64 octets as two 32 byte arrays +pub(super) fn derive(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32]) { + // 2.1. Notation + // + // HMAC-Hash denotes the HMAC function [HMAC] instantiated with hash + // function 'Hash'. HMAC always has two arguments: the first is a key + // and the second an input (or message). (Note that in the extract + // step, 'IKM' is used as the HMAC input, not as the HMAC key.) + // + // When the message is composed of several elements we use concatenation + // (denoted |) in the second argument; for example, HMAC(K, elem1 | + // elem2 | elem3). + + // 2.2. Step 1: Extract + // HKDF-Extract(salt, IKM) -> PRK + // PRK = HMAC-Hash(salt, IKM) + let prk = hmac_hash!(salt, (ikm)); + + // 2.3. Step 2: Expand + // HKDF-Expand(PRK, info, L) -> OKM + // N = ceil(L/HashLen) + // T = T(1) | T(2) | T(3) | ... | T(N) + // OKM = first L octets of T + // + // where: + // T(0) = empty string (zero length) + // T(1) = HMAC-Hash(PRK, T(0) | info | 0x01) + let t1 = hmac_hash!(&prk, (&[1])); + // T(2) = HMAC-Hash(PRK, T(1) | info | 0x02) + let t2 = hmac_hash!(&prk, (&t1, &[2])); + + return (t1, t2) +} + +// Appendix A. Test Vectors +#[cfg(test)] +mod test { + use hex; + use ln::peers::hkdf::derive; + + // Test with SHA-256 and zero-length salt/info + // Our implementation uses a zero-length info field and returns the first 64 octets. As a result, + // this test will be a prefix match on the vector provided by the RFC which is 42 bytes. + #[test] + fn rfc_5869_test_vector_3() { + let ikm = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(); + let (t1, t2) = derive(&[], &ikm); + + let mut calculated_okm = t1.to_vec(); + calculated_okm.extend_from_slice(&t2); + calculated_okm.truncate(42); + assert_eq!(calculated_okm, hex::decode("8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8").unwrap()); + } +} \ No newline at end of file From ea0ff518c7adf02d0f478d985917b0408a405725 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Thu, 20 Aug 2020 14:09:57 -0700 Subject: [PATCH 25/45] refactor: Rename Handshake2 and related structs/enums --- lightning/src/ln/peers/handshake/mod.rs | 18 +-- lightning/src/ln/peers/handshake/states.rs | 123 +++++++++++---------- 2 files changed, 74 insertions(+), 67 deletions(-) diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs index 87f568e0d78..6970e2a6c9e 100644 --- a/lightning/src/ln/peers/handshake/mod.rs +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -5,28 +5,28 @@ use bitcoin::secp256k1::{PublicKey, SecretKey}; use ln::peers::conduit::Conduit; -use ln::peers::handshake::states::{HandshakeState2, UninitiatedHandshakeState, AwaitingActOneHandshakeState}; +use ln::peers::handshake::states::{HandshakeState, InitiatorStartingState, ResponderAwaitingActOneState}; mod states; /// Object for managing handshakes. /// Currently requires explicit ephemeral private key specification. pub struct PeerHandshake { - state: Option, + state: Option, } impl PeerHandshake { /// Instantiate a new handshake with a node identity secret key and an ephemeral private key pub fn new_outbound(initiator_static_private_key: &SecretKey, responder_static_public_key: &PublicKey, initiator_ephemeral_private_key: &SecretKey) -> Self { Self { - state: Some(HandshakeState2::Uninitiated2(UninitiatedHandshakeState::new(initiator_static_private_key.clone(), initiator_ephemeral_private_key.clone(), responder_static_public_key.clone()))), + state: Some(HandshakeState::InitiatorStarting(InitiatorStartingState::new(initiator_static_private_key.clone(), initiator_ephemeral_private_key.clone(), responder_static_public_key.clone()))), } } /// Instantiate a new handshake in anticipation of a peer's first handshake act pub fn new_inbound(responder_static_private_key: &SecretKey, responder_ephemeral_private_key: &SecretKey) -> Self { Self { - state: Some(HandshakeState2::AwaitingActOne2(AwaitingActOneHandshakeState::new(responder_static_private_key.clone(), responder_ephemeral_private_key.clone()))), + state: Some(HandshakeState::ResponderAwaitingActOne(ResponderAwaitingActOneState::new(responder_static_private_key.clone(), responder_ephemeral_private_key.clone()))), } } @@ -44,7 +44,7 @@ impl PeerHandshake { let (act_opt, mut next_state) = cur_state.next(input)?; let result = match next_state { - HandshakeState2::Complete2(ref mut conduit_and_pubkey) => { + HandshakeState::Complete(ref mut conduit_and_pubkey) => { Ok((act_opt, conduit_and_pubkey.take())) }, _ => { Ok((act_opt, None)) } @@ -62,7 +62,7 @@ mod test { use bitcoin::secp256k1::key::{PublicKey, SecretKey}; use ln::peers::handshake::PeerHandshake; - use ln::peers::handshake::states::HandshakeState2; + use ln::peers::handshake::states::HandshakeState; struct TestCtx { outbound_handshake: PeerHandshake, @@ -115,7 +115,7 @@ mod test { fn peer_handshake_new_outbound() { let test_ctx = TestCtx::new(); - assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::Uninitiated2(_))); + assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState::InitiatorStarting(_))); } // Default Inbound::AwaitingActOne @@ -123,7 +123,7 @@ mod test { fn peer_handshake_new_inbound() { let test_ctx = TestCtx::new(); - assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState2::AwaitingActOne2(_))); + assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState::ResponderAwaitingActOne(_))); } /* @@ -161,7 +161,7 @@ mod test { fn process_act_properly_updates_state() { let mut test_ctx = TestCtx::new(); do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState2::AwaitingActTwo2(_))); + assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState::InitiatorAwaitingActTwo(_))); } // Test that any errors from the state machine are passed back to the caller diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index a67ee07b37e..1434cf28d82 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -4,7 +4,7 @@ use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{SecretKey, PublicKey}; -use ln::peers::handshake::states::HandshakeState2::{AwaitingActTwo2, AwaitingActThree2, Complete2}; +use ln::peers::handshake::states::HandshakeState::{InitiatorAwaitingActTwo, ResponderAwaitingActThree, Complete}; use ln::peers::{chacha, hkdf}; use ln::peers::conduit::{Conduit, SymmetricKey}; @@ -24,31 +24,35 @@ macro_rules! concat_then_sha256 { }} } -pub enum HandshakeState2 { - Uninitiated2(UninitiatedHandshakeState), - AwaitingActOne2(AwaitingActOneHandshakeState), - AwaitingActTwo2(AwaitingActTwoHandshakeState), - AwaitingActThree2(AwaitingActThreeHandshakeState), - Complete2(Option<(Conduit, PublicKey)>), +pub enum HandshakeState { + InitiatorStarting(InitiatorStartingState), + ResponderAwaitingActOne(ResponderAwaitingActOneState), + InitiatorAwaitingActTwo(InitiatorAwaitingActTwoState), + ResponderAwaitingActThree(ResponderAwaitingActThreeState), + Complete(Option<(Conduit, PublicKey)>), } -impl HandshakeState2 { - pub(crate) fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState2), String> { +// Trait for all individual states to implement that ensure HandshakeState::next() can +// delegate to a common function signature. +trait IHandshakeState { + fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState), String>; +} + +// Enum dispatch for state machine. Single public interface can statically dispatch to all states +impl HandshakeState { + pub fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState), String> { match self { - HandshakeState2::Uninitiated2(state) => { state.next(input) }, - HandshakeState2::AwaitingActOne2(state) => { state.next(input) }, - HandshakeState2::AwaitingActTwo2(state) => { state.next(input) }, - HandshakeState2::AwaitingActThree2(state) => { state.next(input) }, - HandshakeState2::Complete2(_conduit) => { panic!("no acts left to process") } + HandshakeState::InitiatorStarting(state) => { state.next(input) }, + HandshakeState::ResponderAwaitingActOne(state) => { state.next(input) }, + HandshakeState::InitiatorAwaitingActTwo(state) => { state.next(input) }, + HandshakeState::ResponderAwaitingActThree(state) => { state.next(input) }, + HandshakeState::Complete(_conduit) => { panic!("no acts left to process") } } } } -trait IHandshakeState { - fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState2), String>; -} - -pub struct UninitiatedHandshakeState { +// Handshake state of the Initiator prior to generating Act 1 +pub struct InitiatorStartingState { initiator_static_private_key: SecretKey, initiator_static_public_key: PublicKey, initiator_ephemeral_private_key: SecretKey, @@ -58,7 +62,8 @@ pub struct UninitiatedHandshakeState { hash: Sha256 } -pub struct AwaitingActOneHandshakeState { +// Handshake state of the Responder prior to receiving Act 1 +pub struct ResponderAwaitingActOneState { responder_static_private_key: SecretKey, responder_ephemeral_private_key: SecretKey, responder_ephemeral_public_key: PublicKey, @@ -67,7 +72,8 @@ pub struct AwaitingActOneHandshakeState { read_buffer: Vec } -pub struct AwaitingActTwoHandshakeState { +// Handshake state of the Initiator prior to receiving Act 2 +pub struct InitiatorAwaitingActTwoState { initiator_static_private_key: SecretKey, initiator_static_public_key: PublicKey, initiator_ephemeral_private_key: SecretKey, @@ -77,7 +83,8 @@ pub struct AwaitingActTwoHandshakeState { read_buffer: Vec } -pub struct AwaitingActThreeHandshakeState { +// Handshake state of the Responder prior to receiving Act 3 +pub struct ResponderAwaitingActThreeState { hash: Sha256, responder_ephemeral_private_key: SecretKey, chaining_key: ChainingKey, @@ -85,12 +92,12 @@ pub struct AwaitingActThreeHandshakeState { read_buffer: Vec } -impl UninitiatedHandshakeState { +impl InitiatorStartingState { pub(crate) fn new(initiator_static_private_key: SecretKey, initiator_ephemeral_private_key: SecretKey, responder_static_public_key: PublicKey) -> Self { let initiator_static_public_key = private_key_to_public_key(&initiator_static_private_key); let (hash, chaining_key) = handshake_state_initialization(&responder_static_public_key); let initiator_ephemeral_public_key = private_key_to_public_key(&initiator_ephemeral_private_key); - UninitiatedHandshakeState { + InitiatorStartingState { initiator_static_private_key, initiator_static_public_key, initiator_ephemeral_private_key, @@ -102,9 +109,9 @@ impl UninitiatedHandshakeState { } } -impl IHandshakeState for UninitiatedHandshakeState { +impl IHandshakeState for InitiatorStartingState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (sender) - fn next(self, _input: &[u8]) -> Result<(Option>, HandshakeState2), String> { + fn next(self, _input: &[u8]) -> Result<(Option>, HandshakeState), String> { let initiator_static_private_key = self.initiator_static_private_key; let initiator_static_public_key = self.initiator_static_public_key; let initiator_ephemeral_private_key = self.initiator_ephemeral_private_key; @@ -124,7 +131,7 @@ impl IHandshakeState for UninitiatedHandshakeState { Ok(( Some(act_one.to_vec()), - AwaitingActTwo2(AwaitingActTwoHandshakeState { + InitiatorAwaitingActTwo(InitiatorAwaitingActTwoState { initiator_static_private_key, initiator_static_public_key, initiator_ephemeral_private_key, @@ -137,13 +144,13 @@ impl IHandshakeState for UninitiatedHandshakeState { } } -impl AwaitingActOneHandshakeState { +impl ResponderAwaitingActOneState { pub(crate) fn new(responder_static_private_key: SecretKey, responder_ephemeral_private_key: SecretKey) -> Self { let responder_static_public_key = private_key_to_public_key(&responder_static_private_key); let (hash, chaining_key) = handshake_state_initialization(&responder_static_public_key); let responder_ephemeral_public_key = private_key_to_public_key(&responder_ephemeral_private_key); - AwaitingActOneHandshakeState { + ResponderAwaitingActOneState { responder_static_private_key, responder_ephemeral_private_key, responder_ephemeral_public_key, @@ -154,10 +161,10 @@ impl AwaitingActOneHandshakeState { } } -impl IHandshakeState for AwaitingActOneHandshakeState { +impl IHandshakeState for ResponderAwaitingActOneState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (receiver) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (sender) - fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState2), String> { + fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState), String> { let mut read_buffer = self.read_buffer; read_buffer.extend_from_slice(input); @@ -184,7 +191,7 @@ impl IHandshakeState for AwaitingActOneHandshakeState { Ok(( Some(act_two), - AwaitingActThree2(AwaitingActThreeHandshakeState { + ResponderAwaitingActThree(ResponderAwaitingActThreeState { hash, responder_ephemeral_private_key, chaining_key, @@ -195,10 +202,10 @@ impl IHandshakeState for AwaitingActOneHandshakeState { } } -impl IHandshakeState for AwaitingActTwoHandshakeState { +impl IHandshakeState for InitiatorAwaitingActTwoState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (receiver) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-three (sender) - fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState2), String> { + fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState), String> { let mut read_buffer = self.read_buffer; read_buffer.extend_from_slice(input); @@ -256,14 +263,14 @@ impl IHandshakeState for AwaitingActTwoHandshakeState { Ok(( Some(act_three), - Complete2(Some((conduit, responder_static_public_key))) + Complete(Some((conduit, responder_static_public_key))) )) } } -impl IHandshakeState for AwaitingActThreeHandshakeState { +impl IHandshakeState for ResponderAwaitingActThreeState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-three (receiver) - fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState2), String> { + fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState), String> { let mut read_buffer = self.read_buffer; read_buffer.extend_from_slice(input); @@ -334,7 +341,7 @@ impl IHandshakeState for AwaitingActThreeHandshakeState { Ok(( None, - Complete2(Some((conduit, initiator_pubkey))) + Complete(Some((conduit, initiator_pubkey))) )) } } @@ -465,13 +472,13 @@ mod test { use bitcoin::secp256k1; use bitcoin::secp256k1::{PublicKey, SecretKey}; - use ln::peers::handshake::states::{UninitiatedHandshakeState, AwaitingActOneHandshakeState, HandshakeState2}; - use ln::peers::handshake::states::HandshakeState2::{AwaitingActThree2, AwaitingActTwo2, Complete2}; + use ln::peers::handshake::states::{InitiatorStartingState, ResponderAwaitingActOneState, HandshakeState}; + use ln::peers::handshake::states::HandshakeState::{ResponderAwaitingActThree, InitiatorAwaitingActTwo, Complete}; struct TestCtx { - initiator: HandshakeState2, + initiator: HandshakeState, initiator_public_key: PublicKey, - responder: HandshakeState2, + responder: HandshakeState, responder_static_public_key: PublicKey } @@ -486,13 +493,13 @@ mod test { let responder_static_public_key = PublicKey::from_secret_key(&curve, &responder_static_private_key); let responder_ephemeral_private_key = SecretKey::from_slice(&[0x_22_u8; 32]).unwrap(); - let initiator = UninitiatedHandshakeState::new(initiator_static_private_key, initiator_ephemeral_private_key, responder_static_public_key); - let responder = AwaitingActOneHandshakeState::new(responder_static_private_key, responder_ephemeral_private_key); + let initiator = InitiatorStartingState::new(initiator_static_private_key, initiator_ephemeral_private_key, responder_static_public_key); + let responder = ResponderAwaitingActOneState::new(responder_static_private_key, responder_ephemeral_private_key); TestCtx { - initiator: HandshakeState2::Uninitiated2(initiator), + initiator: HandshakeState::InitiatorStarting(initiator), initiator_public_key, - responder: HandshakeState2::AwaitingActOne2(responder), + responder: HandshakeState::ResponderAwaitingActOne(responder), responder_static_public_key, } } @@ -517,20 +524,20 @@ mod test { } } - // Initiator::Uninitiated -> AwaitingActTwo + // Initiator::Starting -> AwaitingActTwo #[test] - fn uninitiated_to_awaiting_act_two() { + fn starting_to_awaiting_act_two() { let test_ctx = TestCtx::new(); - assert_matches!(test_ctx.initiator.next(&[]).unwrap(), (Some(_), AwaitingActTwo2(_))); + assert_matches!(test_ctx.initiator.next(&[]).unwrap(), (Some(_), InitiatorAwaitingActTwo(_))); } - // Initiator::Uninitiated -> AwaitingActTwo (extra bytes in argument) + // Initiator::Starting -> AwaitingActTwo (extra bytes in argument) #[test] - fn uninitiated_to_awaiting_act_two_extra_bytes() { + fn starting_to_awaiting_act_two_extra_bytes() { let test_ctx = TestCtx::new(); - assert_matches!(test_ctx.initiator.next(&[1]).unwrap(), (Some(_), AwaitingActTwo2(_))); + assert_matches!(test_ctx.initiator.next(&[1]).unwrap(), (Some(_), InitiatorAwaitingActTwo(_))); } // Responder::AwaitingActOne -> Error (input too small) @@ -551,7 +558,7 @@ mod test { let (mut act1, _) = do_next_or_panic!(test_ctx.initiator, &[]); act1.extend_from_slice(&[1]); - assert_matches!(test_ctx.responder.next(&act1).unwrap(), (Some(_), AwaitingActThree2(_))); + assert_matches!(test_ctx.responder.next(&act1).unwrap(), (Some(_), ResponderAwaitingActThree(_))); } // Responder::AwaitingActOne -> Error (bad version byte) @@ -595,7 +602,7 @@ mod test { let test_ctx = TestCtx::new(); let (act1, _) = do_next_or_panic!(test_ctx.initiator, &[]); - assert_matches!(test_ctx.responder.next(&act1).unwrap(), (Some(_), AwaitingActThree2(_))); + assert_matches!(test_ctx.responder.next(&act1).unwrap(), (Some(_), ResponderAwaitingActThree(_))); } // Initiator::AwaitingActTwo -> Complete @@ -605,7 +612,7 @@ mod test { let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); let (act2, _awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); - let remote_pubkey = if let (Some(_), Complete2(Some((_, remote_pubkey)))) = awaiting_act_two_state.next(&act2).unwrap() { + let remote_pubkey = if let (Some(_), Complete(Some((_, remote_pubkey)))) = awaiting_act_two_state.next(&act2).unwrap() { remote_pubkey } else { panic!(); @@ -627,7 +634,7 @@ mod test { let (_act3, complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); - let conduit = if let Complete2(Some((conduit, _))) = complete_state { + let conduit = if let Complete(Some((conduit, _))) = complete_state { conduit } else { panic!(); @@ -689,7 +696,7 @@ mod test { let (act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); let (act3, _complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); - let remote_pubkey = if let (None, Complete2(Some((_, remote_pubkey)))) = awaiting_act_three_state.next(&act3).unwrap() { + let remote_pubkey = if let (None, Complete(Some((_, remote_pubkey)))) = awaiting_act_three_state.next(&act3).unwrap() { remote_pubkey } else { panic!(); @@ -709,7 +716,7 @@ mod test { let (mut act3, _complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); act3.extend_from_slice(&[2; 100]); - let conduit = if let (_, Complete2(Some((conduit, _)))) = awaiting_act_three_state.next(&act3).unwrap() { + let conduit = if let (_, Complete(Some((conduit, _)))) = awaiting_act_three_state.next(&act3).unwrap() { conduit } else { panic!(); From 816a83c38086d286c3cb08a0690cc4d9a0dae14a Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Thu, 20 Aug 2020 16:11:10 -0700 Subject: [PATCH 26/45] refactor: Clean up visibility/imports in handshake/ Not using IHandshakeState slipped through the cracks until this point, but using it makes the enum dispatch follow a more standardized pattern. --- lightning/src/ln/peers/handshake/mod.rs | 11 +++---- lightning/src/ln/peers/handshake/states.rs | 38 +++++++++++++--------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs index 6970e2a6c9e..52efde4ad97 100644 --- a/lightning/src/ln/peers/handshake/mod.rs +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -5,7 +5,7 @@ use bitcoin::secp256k1::{PublicKey, SecretKey}; use ln::peers::conduit::Conduit; -use ln::peers::handshake::states::{HandshakeState, InitiatorStartingState, ResponderAwaitingActOneState}; +use ln::peers::handshake::states::{HandshakeState, IHandshakeState}; mod states; @@ -19,14 +19,14 @@ impl PeerHandshake { /// Instantiate a new handshake with a node identity secret key and an ephemeral private key pub fn new_outbound(initiator_static_private_key: &SecretKey, responder_static_public_key: &PublicKey, initiator_ephemeral_private_key: &SecretKey) -> Self { Self { - state: Some(HandshakeState::InitiatorStarting(InitiatorStartingState::new(initiator_static_private_key.clone(), initiator_ephemeral_private_key.clone(), responder_static_public_key.clone()))), + state: Some(HandshakeState::new_initiator(initiator_static_private_key, responder_static_public_key, initiator_ephemeral_private_key)) } } /// Instantiate a new handshake in anticipation of a peer's first handshake act pub fn new_inbound(responder_static_private_key: &SecretKey, responder_ephemeral_private_key: &SecretKey) -> Self { Self { - state: Some(HandshakeState::ResponderAwaitingActOne(ResponderAwaitingActOneState::new(responder_static_private_key.clone(), responder_ephemeral_private_key.clone()))), + state: Some(HandshakeState::new_responder(responder_static_private_key, responder_ephemeral_private_key)) } } @@ -58,12 +58,11 @@ impl PeerHandshake { #[cfg(test)] mod test { + use super::*; + use bitcoin::secp256k1; use bitcoin::secp256k1::key::{PublicKey, SecretKey}; - use ln::peers::handshake::PeerHandshake; - use ln::peers::handshake::states::HandshakeState; - struct TestCtx { outbound_handshake: PeerHandshake, outbound_static_public_key: PublicKey, diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 1434cf28d82..a95ee0158fc 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -4,7 +4,6 @@ use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{SecretKey, PublicKey}; -use ln::peers::handshake::states::HandshakeState::{InitiatorAwaitingActTwo, ResponderAwaitingActThree, Complete}; use ln::peers::{chacha, hkdf}; use ln::peers::conduit::{Conduit, SymmetricKey}; @@ -24,7 +23,7 @@ macro_rules! concat_then_sha256 { }} } -pub enum HandshakeState { +pub(super) enum HandshakeState { InitiatorStarting(InitiatorStartingState), ResponderAwaitingActOne(ResponderAwaitingActOneState), InitiatorAwaitingActTwo(InitiatorAwaitingActTwoState), @@ -34,13 +33,22 @@ pub enum HandshakeState { // Trait for all individual states to implement that ensure HandshakeState::next() can // delegate to a common function signature. -trait IHandshakeState { +pub(super) trait IHandshakeState { fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState), String>; } // Enum dispatch for state machine. Single public interface can statically dispatch to all states impl HandshakeState { - pub fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState), String> { + pub(super) fn new_initiator(initiator_static_private_key: &SecretKey, responder_static_public_key: &PublicKey, initiator_ephemeral_private_key: &SecretKey) -> Self { + HandshakeState::InitiatorStarting(InitiatorStartingState::new(initiator_static_private_key.clone(), initiator_ephemeral_private_key.clone(), responder_static_public_key.clone())) + } + pub(super) fn new_responder(responder_static_private_key: &SecretKey, responder_ephemeral_private_key: &SecretKey) -> Self { + HandshakeState::ResponderAwaitingActOne(ResponderAwaitingActOneState::new(responder_static_private_key.clone(), responder_ephemeral_private_key.clone())) + } +} + +impl IHandshakeState for HandshakeState { + fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState), String> { match self { HandshakeState::InitiatorStarting(state) => { state.next(input) }, HandshakeState::ResponderAwaitingActOne(state) => { state.next(input) }, @@ -52,7 +60,7 @@ impl HandshakeState { } // Handshake state of the Initiator prior to generating Act 1 -pub struct InitiatorStartingState { +pub(super) struct InitiatorStartingState { initiator_static_private_key: SecretKey, initiator_static_public_key: PublicKey, initiator_ephemeral_private_key: SecretKey, @@ -63,7 +71,7 @@ pub struct InitiatorStartingState { } // Handshake state of the Responder prior to receiving Act 1 -pub struct ResponderAwaitingActOneState { +pub(super) struct ResponderAwaitingActOneState { responder_static_private_key: SecretKey, responder_ephemeral_private_key: SecretKey, responder_ephemeral_public_key: PublicKey, @@ -73,7 +81,7 @@ pub struct ResponderAwaitingActOneState { } // Handshake state of the Initiator prior to receiving Act 2 -pub struct InitiatorAwaitingActTwoState { +pub(super) struct InitiatorAwaitingActTwoState { initiator_static_private_key: SecretKey, initiator_static_public_key: PublicKey, initiator_ephemeral_private_key: SecretKey, @@ -84,7 +92,7 @@ pub struct InitiatorAwaitingActTwoState { } // Handshake state of the Responder prior to receiving Act 3 -pub struct ResponderAwaitingActThreeState { +pub(super) struct ResponderAwaitingActThreeState { hash: Sha256, responder_ephemeral_private_key: SecretKey, chaining_key: ChainingKey, @@ -131,7 +139,7 @@ impl IHandshakeState for InitiatorStartingState { Ok(( Some(act_one.to_vec()), - InitiatorAwaitingActTwo(InitiatorAwaitingActTwoState { + HandshakeState::InitiatorAwaitingActTwo(InitiatorAwaitingActTwoState { initiator_static_private_key, initiator_static_public_key, initiator_ephemeral_private_key, @@ -191,7 +199,7 @@ impl IHandshakeState for ResponderAwaitingActOneState { Ok(( Some(act_two), - ResponderAwaitingActThree(ResponderAwaitingActThreeState { + HandshakeState::ResponderAwaitingActThree(ResponderAwaitingActThreeState { hash, responder_ephemeral_private_key, chaining_key, @@ -263,7 +271,7 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { Ok(( Some(act_three), - Complete(Some((conduit, responder_static_public_key))) + HandshakeState::Complete(Some((conduit, responder_static_public_key))) )) } } @@ -341,7 +349,7 @@ impl IHandshakeState for ResponderAwaitingActThreeState { Ok(( None, - Complete(Some((conduit, initiator_pubkey))) + HandshakeState::Complete(Some((conduit, initiator_pubkey))) )) } } @@ -467,14 +475,14 @@ fn ecdh(private_key: &SecretKey, public_key: &PublicKey) -> SymmetricKey { #[cfg(test)] mod test { + use super::*; + use super::HandshakeState::*; + use hex; use bitcoin::secp256k1; use bitcoin::secp256k1::{PublicKey, SecretKey}; - use ln::peers::handshake::states::{InitiatorStartingState, ResponderAwaitingActOneState, HandshakeState}; - use ln::peers::handshake::states::HandshakeState::{ResponderAwaitingActThree, InitiatorAwaitingActTwo, Complete}; - struct TestCtx { initiator: HandshakeState, initiator_public_key: PublicKey, From 9c5a4b7f28345f528e0eea66297f3308529e6eb8 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 21 Aug 2020 10:41:55 -0700 Subject: [PATCH 27/45] tests: Leverage RFC test vectors in unit tests This uses hard-coded test vectors to minimize the code under test as well as ensures full coverage. One additional test was added to account for an Act3 bad rs value. --- lightning/src/ln/peers/handshake/states.rs | 216 ++++++++++++--------- 1 file changed, 122 insertions(+), 94 deletions(-) diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index a95ee0158fc..d7de6845b71 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -119,7 +119,12 @@ impl InitiatorStartingState { impl IHandshakeState for InitiatorStartingState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (sender) - fn next(self, _input: &[u8]) -> Result<(Option>, HandshakeState), String> { + fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState), String> { + + if input.len() > 0 { + return Err("first call for initiator must be empty".to_string()); + } + let initiator_static_private_key = self.initiator_static_private_key; let initiator_static_public_key = self.initiator_static_public_key; let initiator_ephemeral_private_key = self.initiator_ephemeral_private_key; @@ -474,6 +479,8 @@ fn ecdh(private_key: &SecretKey, public_key: &PublicKey) -> SymmetricKey { } #[cfg(test)] +// Reference RFC test vectors for hard-coded values +// https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#appendix-a-transport-test-vectors mod test { use super::*; use super::HandshakeState::*; @@ -487,7 +494,11 @@ mod test { initiator: HandshakeState, initiator_public_key: PublicKey, responder: HandshakeState, - responder_static_public_key: PublicKey + responder_static_public_key: PublicKey, + valid_act1: Vec, + valid_act2: Vec, + valid_act3: Vec, + } impl TestCtx { @@ -509,6 +520,9 @@ mod test { initiator_public_key, responder: HandshakeState::ResponderAwaitingActOne(responder), responder_static_public_key, + valid_act1: hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap(), + valid_act2: hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap(), + valid_act3: hex::decode("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba").unwrap() } } } @@ -536,23 +550,31 @@ mod test { #[test] fn starting_to_awaiting_act_two() { let test_ctx = TestCtx::new(); + let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - assert_matches!(test_ctx.initiator.next(&[]).unwrap(), (Some(_), InitiatorAwaitingActTwo(_))); + assert_eq!(act1, test_ctx.valid_act1); + assert_matches!(awaiting_act_two_state, InitiatorAwaitingActTwo(_)); } // Initiator::Starting -> AwaitingActTwo (extra bytes in argument) #[test] fn starting_to_awaiting_act_two_extra_bytes() { let test_ctx = TestCtx::new(); + let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - assert_matches!(test_ctx.initiator.next(&[1]).unwrap(), (Some(_), InitiatorAwaitingActTwo(_))); + assert_eq!(act1, test_ctx.valid_act1); + assert_matches!(awaiting_act_two_state, InitiatorAwaitingActTwo(_)); } - // Responder::AwaitingActOne -> Error (input too small) + // Responder::AwaitingActOne -> AwaitingActThree + // RFC test vector: transport-responder successful handshake #[test] - fn awaiting_act_one_to_awaiting_act_three_input_too_small() { + fn awaiting_act_one_to_awaiting_act_three() { let test_ctx = TestCtx::new(); - assert_eq!(test_ctx.responder.next(&[]).err(), Some(String::from("need at least 50 bytes"))) + let (act2, awaiting_act_three_state) = test_ctx.responder.next(&test_ctx.valid_act1).unwrap(); + + assert_eq!(act2.unwrap(), test_ctx.valid_act2); + assert_matches!(awaiting_act_three_state, ResponderAwaitingActThree(_)); } // Responder::AwaitingActOne -> AwaitingActThree @@ -563,70 +585,70 @@ mod test { #[test] fn awaiting_act_one_to_awaiting_act_three_input_extra_bytes() { let test_ctx = TestCtx::new(); - let (mut act1, _) = do_next_or_panic!(test_ctx.initiator, &[]); + let mut act1 = test_ctx.valid_act1; act1.extend_from_slice(&[1]); + let (act2, awaiting_act_three_state) = test_ctx.responder.next(&act1).unwrap(); - assert_matches!(test_ctx.responder.next(&act1).unwrap(), (Some(_), ResponderAwaitingActThree(_))); + assert_eq!(act2.unwrap(), test_ctx.valid_act2); + assert_matches!(awaiting_act_three_state, ResponderAwaitingActThree(_)); } - // Responder::AwaitingActOne -> Error (bad version byte) + // Responder::AwaitingActOne -> Error (input too small) + // RFC test vector: transport-responder act1 short read test #[test] - fn awaiting_act_one_to_awaiting_act_three_input_bad_version() { + fn awaiting_act_one_to_awaiting_act_three_input_too_small() { let test_ctx = TestCtx::new(); - let (mut act1, _) = do_next_or_panic!(test_ctx.initiator, &[]); - // set version byte to 1 - act1[0] = 1; - - assert_eq!(test_ctx.responder.next(&act1).err(), Some(String::from("unexpected version"))); + let act1 = hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c").unwrap(); + assert_eq!(test_ctx.responder.next(&act1).err(), Some(String::from("need at least 50 bytes"))) } - // Responder::AwaitingActOne -> Error (invalid hmac) + // Responder::AwaitingActOne -> Error (bad version byte) + // RFC test vector: transport-responder act1 bad version test #[test] - fn awaiting_act_one_to_awaiting_act_three_invalid_hmac() { + fn awaiting_act_one_to_awaiting_act_three_input_bad_version() { let test_ctx = TestCtx::new(); - // Modify the initiator to point to a different responder - let (mut act1, _) = do_next_or_panic!(test_ctx.initiator, &[]); - // corrupt the ciphertext - act1[34] = 0; + let act1 = hex::decode("01036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap(); - assert_eq!(test_ctx.responder.next(&act1).err(), Some(String::from("invalid hmac"))); + assert_eq!(test_ctx.responder.next(&act1).err(), Some(String::from("unexpected version"))); } // Responder::AwaitingActOne -> Error (invalid remote ephemeral key) + // RFC test vector: transport-responder act1 bad key serialization test #[test] fn awaiting_act_one_to_awaiting_act_three_invalid_remote_ephemeral_key() { let test_ctx = TestCtx::new(); - // Modify the initiator to point to a different responder - let (mut act1, _) = do_next_or_panic!(test_ctx.initiator, &[]); - // corrupt the ephemeral public key - act1[1] = 0; + let act1 = hex::decode("00046360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap(); assert_eq!(test_ctx.responder.next(&act1).err(), Some(String::from("invalid remote ephemeral public key"))); } - // Responder::AwaitingActOne -> AwaitingActThree + // Responder::AwaitingActOne -> Error (invalid hmac) + // RFC test vector: transport-responder act1 bad MAC test #[test] - fn awaiting_act_one_to_awaiting_act_three() { + fn awaiting_act_one_to_awaiting_act_three_invalid_hmac() { let test_ctx = TestCtx::new(); - let (act1, _) = do_next_or_panic!(test_ctx.initiator, &[]); + let act1 = hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6b").unwrap(); - assert_matches!(test_ctx.responder.next(&act1).unwrap(), (Some(_), ResponderAwaitingActThree(_))); + assert_eq!(test_ctx.responder.next(&act1).err(), Some(String::from("invalid hmac"))); } // Initiator::AwaitingActTwo -> Complete + // RFC test vector: transport-initiator successful handshake #[test] fn awaiting_act_two_to_complete() { let test_ctx = TestCtx::new(); - let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - let (act2, _awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); + let (_act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let (act3, complete_state) = do_next_or_panic!(awaiting_act_two_state, &test_ctx.valid_act2); - let remote_pubkey = if let (Some(_), Complete(Some((_, remote_pubkey)))) = awaiting_act_two_state.next(&act2).unwrap() { - remote_pubkey + let (conduit, remote_pubkey) = if let Complete(Some((conduit, remote_pubkey))) = complete_state { + (conduit, remote_pubkey) } else { panic!(); }; + assert_eq!(act3, test_ctx.valid_act3); assert_eq!(remote_pubkey, test_ctx.responder_static_public_key); + assert_eq!(0, conduit.decryptor.read_buffer_length()); } // Initiator::AwaitingActTwo -> Complete (with extra data) @@ -636,81 +658,81 @@ mod test { #[test] fn awaiting_act_two_to_complete_excess_bytes_are_in_conduit() { let test_ctx = TestCtx::new(); - let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - let (mut act2, _awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); + let (_act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let mut act2 = test_ctx.valid_act2; act2.extend_from_slice(&[1; 100]); + let (act3, complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); - let (_act3, complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); - - let conduit = if let Complete(Some((conduit, _))) = complete_state { - conduit + let (conduit, remote_pubkey) = if let Complete(Some((conduit, remote_pubkey))) = complete_state { + (conduit, remote_pubkey) } else { panic!(); }; + assert_eq!(act3, test_ctx.valid_act3); + assert_eq!(remote_pubkey, test_ctx.responder_static_public_key); assert_eq!(100, conduit.decryptor.read_buffer_length()); } // Initiator::AwaitingActTwo -> Error (input too small) + // RFC test vector: transport-initiator act2 short read test #[test] fn awaiting_act_two_input_too_small() { let test_ctx = TestCtx::new(); let (_act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let act2 = hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730").unwrap(); - assert_eq!(awaiting_act_two_state.next(&[]).err(), Some(String::from("need at least 50 bytes"))); + assert_eq!(awaiting_act_two_state.next(&act2).err(), Some(String::from("need at least 50 bytes"))); } // Initiator::AwaitingActTwo -> Error (bad version byte) + // RFC test vector: transport-initiator act2 bad version test #[test] fn awaiting_act_two_bad_version_byte() { let test_ctx = TestCtx::new(); - let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - let (mut act2, _awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); - // set invalid version byte - act2[0] = 1; + let (_act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let act2 = hex::decode("0102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap(); assert_eq!(awaiting_act_two_state.next(&act2).err(), Some(String::from("unexpected version"))); } - // Initiator::AwaitingActTwo -> Error (invalid hmac) + // Initiator::AwaitingActTwo -> Error (invalid ephemeral public key) + // RFC test vector: transport-initiator act2 bad key serialization test #[test] - fn awaiting_act_two_invalid_hmac() { + fn awaiting_act_two_invalid_ephemeral_public_key() { let test_ctx = TestCtx::new(); - let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - let (mut act2, _awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); - // corrupt the ciphertext - act2[34] = 0; + let (_act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let act2 = hex::decode("0004466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap(); - assert_eq!(awaiting_act_two_state.next(&act2).err(), Some(String::from("invalid hmac"))); + assert_eq!(awaiting_act_two_state.next(&act2).err(), Some(String::from("invalid remote ephemeral public key"))); } - // Initiator::AwaitingActTwo -> Error (invalid ephemeral public key) + // Initiator::AwaitingActTwo -> Error (invalid hmac) + // RFC test vector: transport-initiator act2 bad MAC test #[test] - fn awaiting_act_two_invalid_ephemeral_public_key() { + fn awaiting_act_two_invalid_hmac() { let test_ctx = TestCtx::new(); - let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - let (mut act2, _awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); - // corrupt the ephemeral public key - act2[1] = 0; + let (_act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); + let act2 = hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730af").unwrap(); - assert_eq!(awaiting_act_two_state.next(&act2).err(), Some(String::from("invalid remote ephemeral public key"))); + assert_eq!(awaiting_act_two_state.next(&act2).err(), Some(String::from("invalid hmac"))); } // Responder::AwaitingActThree -> Complete + // RFC test vector: transport-responder successful handshake #[test] fn awaiting_act_three_to_complete() { let test_ctx = TestCtx::new(); - let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - let (act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); - let (act3, _complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); + let (_act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &test_ctx.valid_act1); - let remote_pubkey = if let (None, Complete(Some((_, remote_pubkey)))) = awaiting_act_three_state.next(&act3).unwrap() { - remote_pubkey + let (conduit, remote_pubkey) = if let (None, Complete(Some((conduit, remote_pubkey)))) = awaiting_act_three_state.next(&test_ctx.valid_act3).unwrap() { + (conduit, remote_pubkey) } else { panic!(); }; assert_eq!(remote_pubkey, test_ctx.initiator_public_key); + assert_eq!(0, conduit.decryptor.read_buffer_length()); } // Responder::AwaitingActThree -> None (with extra bytes) @@ -719,66 +741,71 @@ mod test { #[test] fn awaiting_act_three_excess_bytes_after_complete_are_in_conduit() { let test_ctx = TestCtx::new(); - let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - let (act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); - let (mut act3, _complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); + let (_act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &test_ctx.valid_act1); + let mut act3 = test_ctx.valid_act3; act3.extend_from_slice(&[2; 100]); - let conduit = if let (_, Complete(Some((conduit, _)))) = awaiting_act_three_state.next(&act3).unwrap() { - conduit + let (conduit, remote_pubkey) = if let (None, Complete(Some((conduit, remote_pubkey)))) = awaiting_act_three_state.next(&act3).unwrap() { + (conduit, remote_pubkey) } else { panic!(); }; + assert_eq!(remote_pubkey, test_ctx.initiator_public_key); assert_eq!(100, conduit.decryptor.read_buffer_length()); } - // Responder::AwaitingActThree -> Error (input too small) + // Responder::AwaitingActThree -> Error (bad version bytes) + // RFC test vector: transport-responder act3 bad version test #[test] - fn awaiting_act_three_input_too_small() { + fn awaiting_act_three_bad_version_bytes() { let test_ctx = TestCtx::new(); - let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - let (act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); - let (act3, _complete) = do_next_or_panic!(awaiting_act_two_state, &act2); + let (_act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &test_ctx.valid_act1); + let act3 = hex::decode("01b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba").unwrap(); - assert_eq!(awaiting_act_three_state.next(&act3[..65]).err(), Some(String::from("need at least 66 bytes"))); + assert_eq!(awaiting_act_three_state.next(&act3).err(), Some(String::from("unexpected version"))); } - // Responder::AwaitingActThree -> Error (bad version bytes) + // Responder::AwaitingActThree -> Error (input too small) + // RFC test vector: transport-responder act3 short read test #[test] - fn awaiting_act_three_bad_version_bytes() { + fn awaiting_act_three_input_too_small() { let test_ctx = TestCtx::new(); - let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - let (act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); - let (mut act3, _complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); - // set version byte to 1 - act3[0] = 1; + let (_act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &test_ctx.valid_act1); + let act3 = hex::decode("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139").unwrap(); - assert_eq!(awaiting_act_three_state.next(&act3).err(), Some(String::from("unexpected version"))); + assert_eq!(awaiting_act_three_state.next(&act3).err(), Some(String::from("need at least 66 bytes"))); } // Responder::AwaitingActThree -> Error (invalid hmac) + // RFC test vector: transport-responder act3 bad MAC for ciphertext test #[test] fn awaiting_act_three_invalid_hmac() { let test_ctx = TestCtx::new(); - let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - let (act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); - let (mut act3, _complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); - // corrupt encrypted pubkey - act3[1] = 1; + let (_act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &test_ctx.valid_act1); + let act3 = hex::decode("00c9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba").unwrap(); assert_eq!(awaiting_act_three_state.next(&act3).err(), Some(String::from("invalid hmac"))); } + // Responder::AwaitingActThree -> Error (invalid remote_static_key) + // RFC test vector: transport-responder act3 bad rs test + #[test] + fn awaiting_act_three_invalid_rs() { + let test_ctx = TestCtx::new(); + let (_act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &test_ctx.valid_act1); + let act3 = hex::decode("00bfe3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa2235536ad09a8ee351870c2bb7f78b754a26c6cef79a98d25139c856d7efd252c2ae73c").unwrap(); + + assert_eq!(awaiting_act_three_state.next(&act3).err(), Some(String::from("invalid remote public key"))); + } + // Responder::AwaitingActThree -> Error (invalid tag hmac) + // RFC test vector: transport-responder act3 bad MAC test #[test] fn awaiting_act_three_invalid_tag_hmac() { let test_ctx = TestCtx::new(); - let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - let (act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &act1); - let (mut act3, _complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); - // corrupt tag - act3[50] = 1; + let (_act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &test_ctx.valid_act1); + let act3 = hex::decode("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139bb").unwrap(); assert_eq!(awaiting_act_three_state.next(&act3).err(), Some(String::from("invalid hmac"))); } @@ -830,3 +857,4 @@ mod test { "00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba"); } } + From 3ae760452596dbb6eceb17b4e20b6035004187f3 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 21 Aug 2020 11:36:53 -0700 Subject: [PATCH 28/45] refactor: Simplify logic for init message sending The previous implementation used implementation details of the act byte returns from process_next_act() to determine whether or not to send an init message, but the Peer already has that information in it's outbound field. Use it instead to clean up the layering. --- lightning/src/ln/peers/handler.rs | 33 +++++++++++++------------------ 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/lightning/src/ln/peers/handler.rs b/lightning/src/ln/peers/handler.rs index c8ace0e4439..373b821e0bf 100644 --- a/lightning/src/ln/peers/handler.rs +++ b/lightning/src/ln/peers/handler.rs @@ -127,7 +127,7 @@ enum PeerState { } enum PeerDataProcessingDecision { - CompleteHandshake(bool, PublicKey), + CompleteHandshake(PublicKey), Continue, Disconnect(PeerHandleError) } @@ -153,7 +153,6 @@ impl PeerState { Ok((response_vec_opt, conduit_and_remote_pubkey_opt)) => { // Any response generated by the handshake sequence is put into the response buffer - let requires_response = response_vec_opt.is_some(); if let Some(response_vec) = response_vec_opt { mutable_response_buffer.push_back(response_vec); } @@ -161,7 +160,7 @@ impl PeerState { // if process_act() returns the conduit and remote_pubkey the handshake // is complete if let Some((conduit, remote_pubkey)) = conduit_and_remote_pubkey_opt { - (Some(PeerState::Connected(conduit)), PeerDataProcessingDecision::CompleteHandshake(requires_response, remote_pubkey)) + (Some(PeerState::Connected(conduit)), PeerDataProcessingDecision::CompleteHandshake(remote_pubkey)) } else { (None, PeerDataProcessingDecision::Continue) } @@ -569,8 +568,6 @@ impl PeerManager panic!("Descriptor for read_event is not already known to PeerManager"), Some(peer) => { - let mut send_init_message = false; - let data_processing_decision = peer.encryptor.process_peer_data(data, &mut peer.pending_outbound_buffer); match data_processing_decision { PeerDataProcessingDecision::Disconnect(e) => { @@ -578,10 +575,19 @@ impl PeerManager { - send_init_message = needs_to_send_init_message; + PeerDataProcessingDecision::CompleteHandshake(remote_pubkey) => { peer.their_node_id = Some(remote_pubkey); + if peer.outbound { + let mut features = InitFeatures::known(); + if !self.message_handler.route_handler.should_request_full_sync(&peer.their_node_id.unwrap()) { + features.clear_initial_routing_sync(); + } + + let resp = msgs::Init { features }; + self.enqueue_message(&mut peers.peers_needing_send, peer, peer_descriptor.clone(), &resp); + } + // insert node id match peers.node_id_to_descriptor.entry(peer.their_node_id.unwrap()) { hash_map::Entry::Occupied(_) => { @@ -595,20 +601,9 @@ impl PeerManager {} + _ => { } }; - if send_init_message { - let mut features = InitFeatures::known(); - if !self.message_handler.route_handler.should_request_full_sync(&peer.their_node_id.unwrap()) { - features.clear_initial_routing_sync(); - } - - let resp = msgs::Init { features }; - self.enqueue_message(&mut peers.peers_needing_send, peer, peer_descriptor.clone(), &resp); - send_init_message = false - } - let mut received_messages = vec![]; if let &mut PeerState::Connected(ref mut conduit) = &mut peer.encryptor { let encryptor = &mut conduit.encryptor; From afd0334c17523641745688441549a5ae5663afb8 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 21 Aug 2020 11:42:49 -0700 Subject: [PATCH 29/45] refactor: Clean up unused variable warnings --- lightning/src/ln/peers/conduit.rs | 19 +++---------------- lightning/src/ln/peers/handler.rs | 8 ++------ 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/lightning/src/ln/peers/conduit.rs b/lightning/src/ln/peers/conduit.rs index 57f191a8c0c..c753f0b8ff2 100644 --- a/lightning/src/ln/peers/conduit.rs +++ b/lightning/src/ln/peers/conduit.rs @@ -79,19 +79,6 @@ impl Conduit { self.decryptor.decrypt_single_message(new_data) } - /// Decrypt a message from the beginning of the provided buffer. Returns the consumed number of bytes. - fn decrypt(&mut self, buffer: &[u8]) -> (Option>, usize) { - self.decryptor.decrypt(buffer) - } - - fn increment_sending_nonce(&mut self) { - self.encryptor.increment_nonce() - } - - fn increment_receiving_nonce(&mut self) { - self.decryptor.increment_nonce() - } - fn increment_nonce(nonce: &mut u32, chaining_key: &mut SymmetricKey, key: &mut SymmetricKey) { *nonce += 1; if *nonce == KEY_ROTATION_INDEX { @@ -247,7 +234,7 @@ mod tests { #[test] fn test_nonce_chaining() { - let (mut connected_peer, mut remote_peer) = setup_peers(); + let (mut connected_peer, _remote_peer) = setup_peers(); let message = hex::decode("68656c6c6f").unwrap(); let encrypted_message = connected_peer.encrypt(&message); @@ -261,7 +248,7 @@ mod tests { #[test] /// Based on RFC test vectors: https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#message-encryption-tests fn test_key_rotation() { - let (mut connected_peer, mut remote_peer) = setup_peers(); + let (mut connected_peer, _remote_peer) = setup_peers(); let message = hex::decode("68656c6c6f").unwrap(); let mut encrypted_messages: Vec> = Vec::new(); @@ -292,7 +279,7 @@ mod tests { for _ in 0..501 { // read two messages at once, filling buffer let mut current_encrypted_message = encrypted_messages.remove(0); - let mut next_encrypted_message = encrypted_messages.remove(0); + let next_encrypted_message = encrypted_messages.remove(0); current_encrypted_message.extend_from_slice(&next_encrypted_message); let decrypted_message = remote_peer.decrypt_single_message(Some(¤t_encrypted_message)).unwrap(); assert_eq!(decrypted_message, message); diff --git a/lightning/src/ln/peers/handler.rs b/lightning/src/ln/peers/handler.rs index 373b821e0bf..9f50bd06340 100644 --- a/lightning/src/ln/peers/handler.rs +++ b/lightning/src/ln/peers/handler.rs @@ -22,7 +22,6 @@ use ln::msgs; use ln::msgs::{ChannelMessageHandler, LightningError, RoutingMessageHandler}; use ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager}; use util::ser::{VecWriter, Writeable}; -use ln::peer_channel_encryptor::{PeerChannelEncryptor,NextNoiseStep}; use ln::wire; use ln::wire::Encode; use util::byte_utils; @@ -145,7 +144,7 @@ impl PeerState { &mut PeerState::Authenticating(ref mut handshake) => { match handshake.process_act(data) { // Any errors originating from the handshake sequence result in Disconnect - Err(e) => { + Err(_e) => { (None, PeerDataProcessingDecision::Disconnect(PeerHandleError { no_connection_possible: false })) }, // Otherwise, handshake may or may not be complete depending on whether or not @@ -606,10 +605,7 @@ impl PeerManager Date: Thu, 20 Aug 2020 19:53:12 -0700 Subject: [PATCH 30/45] refactor: Hide empty next() requirement inside PeerHandshake The fact that we have to call next() to generate act1 is an implementation detail of the handshake. Rename the old constructor to indicate that it creates and initializes the state and fix up the uses. --- lightning/src/ln/peers/handler.rs | 6 ++---- lightning/src/ln/peers/handshake/mod.rs | 23 ++++++++++++---------- lightning/src/ln/peers/handshake/states.rs | 4 +--- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/lightning/src/ln/peers/handler.rs b/lightning/src/ln/peers/handler.rs index 9f50bd06340..05c72c84a16 100644 --- a/lightning/src/ln/peers/handler.rs +++ b/lightning/src/ln/peers/handler.rs @@ -365,9 +365,7 @@ impl PeerManager Result, PeerHandleError> { - let mut handshake = PeerHandshake::new_outbound(&self.our_node_secret, &their_node_id, &self.get_ephemeral_key()); - let (act, ..) = handshake.process_act(&[]).unwrap(); - let res = act.unwrap(); + let (initial_bytes, handshake) = PeerHandshake::create_and_initialize_outbound(&self.our_node_secret, &their_node_id, &self.get_ephemeral_key()); let mut peers = self.peers.lock().unwrap(); if peers.peers.insert(descriptor, Peer { @@ -386,7 +384,7 @@ impl PeerManager Self { - Self { - state: Some(HandshakeState::new_initiator(initiator_static_private_key, responder_static_public_key, initiator_ephemeral_private_key)) - } + pub fn create_and_initialize_outbound(initiator_static_private_key: &SecretKey, responder_static_public_key: &PublicKey, initiator_ephemeral_private_key: &SecretKey) -> (Vec, Self) { + let state = HandshakeState::new_initiator(initiator_static_private_key, responder_static_public_key, initiator_ephemeral_private_key); + + // This transition does not have a failure path + let (response_vec_opt, next_state) = state.next(&[]).unwrap(); + (response_vec_opt.unwrap(), Self { state: Some(next_state) }) } /// Instantiate a new handshake in anticipation of a peer's first handshake act @@ -64,6 +66,7 @@ mod test { use bitcoin::secp256k1::key::{PublicKey, SecretKey}; struct TestCtx { + act1: Vec, outbound_handshake: PeerHandshake, outbound_static_public_key: PublicKey, inbound_handshake: PeerHandshake, @@ -82,10 +85,11 @@ mod test { let inbound_static_public_key = PublicKey::from_secret_key(&curve, &inbound_static_private_key); let inbound_ephemeral_private_key = SecretKey::from_slice(&[0x_22_u8; 32]).unwrap(); - let outbound_handshake = PeerHandshake::new_outbound(&outbound_static_private_key, &inbound_static_public_key, &outbound_ephemeral_private_key); + let (act1, outbound_handshake) = PeerHandshake::create_and_initialize_outbound(&outbound_static_private_key, &inbound_static_public_key, &outbound_ephemeral_private_key); let inbound_handshake = PeerHandshake::new_inbound(&inbound_static_private_key, &inbound_ephemeral_private_key); TestCtx { + act1, outbound_handshake, outbound_static_public_key, inbound_handshake, @@ -114,7 +118,7 @@ mod test { fn peer_handshake_new_outbound() { let test_ctx = TestCtx::new(); - assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState::InitiatorStarting(_))); + assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState::InitiatorAwaitingActTwo(_))); } // Default Inbound::AwaitingActOne @@ -133,8 +137,7 @@ mod test { #[test] fn full_sequence_sanity_test() { let mut test_ctx = TestCtx::new(); - let act1 = do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &act1); + let act2 = do_process_act_or_panic!(test_ctx.inbound_handshake, &test_ctx.act1); let (act3, inbound_remote_pubkey) = if let (Some(act3), Some((_, remote_pubkey))) = test_ctx.outbound_handshake.process_act(&act2).unwrap() { (act3, remote_pubkey) @@ -159,8 +162,8 @@ mod test { #[test] fn process_act_properly_updates_state() { let mut test_ctx = TestCtx::new(); - do_process_act_or_panic!(test_ctx.outbound_handshake, &[]); - assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState::InitiatorAwaitingActTwo(_))); + do_process_act_or_panic!(test_ctx.inbound_handshake, &test_ctx.act1); + assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState::ResponderAwaitingActThree(_))); } // Test that any errors from the state machine are passed back to the caller diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index d7de6845b71..8392dda3fff 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -560,10 +560,8 @@ mod test { #[test] fn starting_to_awaiting_act_two_extra_bytes() { let test_ctx = TestCtx::new(); - let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - assert_eq!(act1, test_ctx.valid_act1); - assert_matches!(awaiting_act_two_state, InitiatorAwaitingActTwo(_)); + assert_eq!(test_ctx.initiator.next(&[1]).err(), Some(String::from("first call for initiator must be empty"))); } // Responder::AwaitingActOne -> AwaitingActThree From cc5cc9abd267c943f571fe19dcec5fad3e228b25 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Mon, 24 Aug 2020 09:41:05 -0700 Subject: [PATCH 31/45] refactor: Remove unnecessary copy_from_slice calls in states.rs Use the slice refs from the act array instead. --- lightning/src/ln/peers/handshake/states.rs | 27 ++++++---------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 8392dda3fff..e94554b0636 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -297,18 +297,12 @@ impl IHandshakeState for ResponderAwaitingActThreeState { let chaining_key = self.chaining_key; // 1. Read exactly 66 bytes from the network buffer - let mut act_three_bytes = [0u8; ACT_THREE_LENGTH]; - act_three_bytes.copy_from_slice(&read_buffer[..ACT_THREE_LENGTH]); - read_buffer.drain(..ACT_THREE_LENGTH); + let act_three_bytes: Vec = read_buffer.drain(..ACT_THREE_LENGTH).collect(); // 2. Parse the read message (m) into v, c, and t let version = act_three_bytes[0]; - - let mut tagged_encrypted_pubkey = [0u8; 49]; - tagged_encrypted_pubkey.copy_from_slice(&act_three_bytes[1..50]); - - let mut chacha_tag = [0u8; 16]; - chacha_tag.copy_from_slice(&act_three_bytes[50..66]); + let tagged_encrypted_pubkey = &act_three_bytes[1..50]; + let chacha_tag = &act_three_bytes[50..66]; // 3. If v is an unrecognized handshake version, then the responder MUST abort the connection attempt. if version != 0 { @@ -318,9 +312,7 @@ impl IHandshakeState for ResponderAwaitingActThreeState { // 4. rs = decryptWithAD(temp_k2, 1, h, c) let remote_pubkey_vec = chacha::decrypt(&temporary_key, 1, &hash, &tagged_encrypted_pubkey)?; - let mut initiator_pubkey_bytes = [0u8; 33]; - initiator_pubkey_bytes.copy_from_slice(remote_pubkey_vec.as_slice()); - let initiator_pubkey = if let Ok(public_key) = PublicKey::from_slice(&initiator_pubkey_bytes) { + let initiator_pubkey = if let Ok(public_key) = PublicKey::from_slice(remote_pubkey_vec.as_slice()) { public_key } else { return Err("invalid remote public key".to_string()); @@ -419,24 +411,19 @@ fn process_act_message(read_buffer: &mut Vec, local_private_key: &SecretKey, return Err("need at least 50 bytes".to_string()); } - let mut act_bytes = [0u8; ACT_ONE_TWO_LENGTH]; - act_bytes.copy_from_slice(&read_buffer[..ACT_ONE_TWO_LENGTH]); - read_buffer.drain(..ACT_ONE_TWO_LENGTH); + let act_bytes: Vec = read_buffer.drain(..ACT_ONE_TWO_LENGTH).collect(); // 2.Parse the read message (m) into v, re, and c let version = act_bytes[0]; + let ephemeral_public_key_bytes = &act_bytes[1..34]; + let chacha_tag = &act_bytes[34..50]; - let mut ephemeral_public_key_bytes = [0u8; 33]; - ephemeral_public_key_bytes.copy_from_slice(&act_bytes[1..34]); let ephemeral_public_key = if let Ok(public_key) = PublicKey::from_slice(&ephemeral_public_key_bytes) { public_key } else { return Err("invalid remote ephemeral public key".to_string()); }; - let mut chacha_tag = [0u8; 16]; - chacha_tag.copy_from_slice(&act_bytes[34..50]); - // 3. If v is an unrecognized handshake version, then the responder MUST abort the connection attempt if version != 0 { // this should not crash the process, hence no panic From b617dfa38b2d3532c72980033eb58728a8a86135 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sat, 22 Aug 2020 18:49:10 -0700 Subject: [PATCH 32/45] tests: Add new fuzz testing for PeerHandshake Write a new fuzz test for the PeerHandshake module that is able to generate failure paths that occur after a partial handshake sequence has been completed. To enable this, a new testing object FuzzGen has been introduced that can deterministically generate bytes and bools based on the random fuzz input. These building blocks are enough for the test code to generates different execution paths and complete partial phases of the handshake protocol before generating an error. When going through a test cycle where the handshake completes, it will also verify that the initiator and responder can communicate successfully through the returned conduits sending variable length data and validating the contents. --- fuzz/src/peer_crypt.rs | 331 +++++++++++++++++++++++++++++++++-------- 1 file changed, 273 insertions(+), 58 deletions(-) diff --git a/fuzz/src/peer_crypt.rs b/fuzz/src/peer_crypt.rs index f41137fc828..4679601a8c1 100644 --- a/fuzz/src/peer_crypt.rs +++ b/fuzz/src/peer_crypt.rs @@ -7,81 +7,241 @@ // You may not use this file except in accordance with one or both of these // licenses. -use lightning::ln::peer_channel_encryptor::PeerChannelEncryptor; +use bitcoin::secp256k1; use bitcoin::secp256k1::key::{PublicKey,SecretKey}; - +use lightning::ln::peers::conduit::Conduit; +use lightning::ln::peers::handshake::PeerHandshake; use utils::test_logger; -#[inline] -fn slice_to_be16(v: &[u8]) -> u16 { - ((v[0] as u16) << 8*1) | - ((v[1] as u16) << 8*0) +// Test structure used to generate "random" values based on input data. It is used throughout +// the various test cases to send random messages between nodes and to ensure the code does not fail +// unexpectedly. +pub struct FuzzGen<'a> { + read_pos: usize, + data: &'a [u8], } -#[inline] -pub fn do_test(data: &[u8]) { - let mut read_pos = 0; - macro_rules! get_slice { - ($len: expr) => { - { - let slice_len = $len as usize; - if data.len() < read_pos + slice_len { - return; - } - read_pos += slice_len; - &data[read_pos - slice_len..read_pos] +impl<'a> FuzzGen<'a> { + pub fn new(data: &'a [u8]) -> Self { + Self { + read_pos: 0, + data + } + } + + pub fn generate_bytes(&mut self, num: usize) -> Result<&'a [u8], String> { + if self.data.len() < self.read_pos + num { + return Err("out of bytes".to_string()); + } + + self.read_pos += num; + Ok(&self.data[self.read_pos - num..self.read_pos]) + } + + pub fn generate_secret_key(&mut self) -> Result { + // Loop through the input 32 bytes at a time until a valid + // secret key can be created or we run out of bytes + loop { + match SecretKey::from_slice(self.generate_bytes(32)?) { + Ok(key) => { return Ok(key) }, + _ => {} } } } - let our_network_key = match SecretKey::from_slice(get_slice!(32)) { - Ok(key) => key, - Err(_) => return, + pub fn generate_bool(&mut self) -> Result { + let next_byte = self.generate_bytes(1)?; + Ok(next_byte[0] & 1 == 1) + } +} + +struct TestCtx { + initiator_static_public_key: PublicKey, + initiator_handshake: PeerHandshake, + responder_static_public_key: PublicKey, + responder_handshake: PeerHandshake, + act1: Vec +} + +impl TestCtx { + fn make(generator: &mut FuzzGen) -> Result { + let curve = secp256k1::Secp256k1::new(); + + // Generate needed keys for successful handshake + let initiator_static_private_key = generator.generate_secret_key()?; + let initiator_static_public_key = PublicKey::from_secret_key(&curve, &initiator_static_private_key); + let initiator_ephemeral_private_key = generator.generate_secret_key()?; + let responder_static_private_key = generator.generate_secret_key()?; + let responder_static_public_key = PublicKey::from_secret_key(&curve, &responder_static_private_key); + let responder_ephemeral_private_key = generator.generate_secret_key()?; + + let (act1, initiator_handshake) = PeerHandshake::create_and_initialize_outbound(&initiator_static_private_key, &responder_static_public_key, &initiator_ephemeral_private_key); + let responder_handshake = PeerHandshake::new_inbound(&responder_static_private_key, &responder_ephemeral_private_key); + + Ok(TestCtx { + initiator_static_public_key, + initiator_handshake, + responder_static_public_key, + responder_handshake, + act1 + }) + } +} + +// Common test function that sends encrypted messages between two conduits until the source data +// runs out. +#[inline] +fn do_conduit_tests(generator: &mut FuzzGen, initiator_conduit: &mut Conduit, responder_conduit: &mut Conduit) -> Result<(), String> { + // Keep sending messages back and forth until the input data is consumed + loop { + // Randomly generate message length + let msg_len_raw = generator.generate_bytes(1)?; + let msg_len = msg_len_raw[0] as usize; + + // Randomly generate message + let sender_unencrypted_msg = generator.generate_bytes(msg_len)?; + + // randomly choose sender of message + let receiver_unencrypted_msg = if generator.generate_bool()? { + let encrypted_msg = initiator_conduit.encrypt(sender_unencrypted_msg); + responder_conduit.decrypt_single_message(Some(&encrypted_msg)) + } else { + let encrypted_msg = responder_conduit.encrypt(sender_unencrypted_msg); + initiator_conduit.decrypt_single_message(Some(&encrypted_msg)) + }; + + assert_eq!(sender_unencrypted_msg, receiver_unencrypted_msg.unwrap().as_slice()); + } +} + +// This test completes a valid handshake based on "random" private keys and then sends variable +// length encrypted messages between two conduits to validate that they can communicate. +#[inline] +fn do_completed_handshake_test(generator: &mut FuzzGen) -> Result<(), String> { + let mut test_ctx = TestCtx::make(generator)?; + + // The handshake should complete with any valid private keys + let act2 = test_ctx.responder_handshake.process_act(&test_ctx.act1).unwrap().0.unwrap(); + let (act3, (mut initiator_conduit, responder_pubkey)) = match test_ctx.initiator_handshake.process_act(&act2) { + Ok((Some(act3), Some((conduit, remote_pubkey)))) => { + (act3, (conduit, remote_pubkey)) + } + _ => panic!("handshake failed") }; - let ephemeral_key = match SecretKey::from_slice(get_slice!(32)) { - Ok(key) => key, - Err(_) => return, + + let (mut responder_conduit, initiator_pubkey) = match test_ctx.responder_handshake.process_act(&act3) { + Ok((None, Some((conduit, remote_pubkey)))) => { + (conduit, remote_pubkey) + } + _ => panic!("handshake failed") }; - let mut crypter = if get_slice!(1)[0] != 0 { - let their_pubkey = match PublicKey::from_slice(get_slice!(33)) { - Ok(key) => key, - Err(_) => return, - }; - let mut crypter = PeerChannelEncryptor::new_outbound(their_pubkey, ephemeral_key); - crypter.get_act_one(); - match crypter.process_act_two(get_slice!(50), &our_network_key) { - Ok(_) => {}, - Err(_) => return, + // The handshake should complete with each peer knowing the static_public_key of the remote peer + assert_eq!(initiator_pubkey, test_ctx.initiator_static_public_key); + assert_eq!(responder_pubkey, test_ctx.responder_static_public_key); + + // The nodes should be able to communicate over the conduit + do_conduit_tests(generator, &mut initiator_conduit, &mut responder_conduit)?; + + unreachable!(); +} + +// This test variant goes through the peer handshake between the initiator and responder with +// "random" failures to verify that nothing panics. +// In the event of an error from process_act() we validate that the input data was generated +// randomly to ensure real act generation still produces valid transitions. +#[inline] +fn do_handshake_test(generator: &mut FuzzGen) -> Result<(), String> { + let mut test_ctx = TestCtx::make(generator)?; + let mut used_generated_data = false; + + // Possibly generate bad data for act1 and ensure that the responder does not panic + let mut act1 = test_ctx.act1; + if generator.generate_bool()? { + act1 = (generator.generate_bytes(50)?).to_vec(); + used_generated_data = true; + } + + let mut act2 = match test_ctx.responder_handshake.process_act(&act1) { + Ok((Some(act2), None)) => { + act2 } - assert!(crypter.is_ready_for_encryption()); - crypter - } else { - let mut crypter = PeerChannelEncryptor::new_inbound(&our_network_key); - match crypter.process_act_one_with_keys(get_slice!(50), &our_network_key, ephemeral_key) { - Ok(_) => {}, - Err(_) => return, + Err(_) => { + assert!(used_generated_data); + return Err("generated expected failure with bad data".to_string()); } - match crypter.process_act_three(get_slice!(66)) { - Ok(_) => {}, - Err(_) => return, + _ => panic!("responder required to output act bytes and no conduit/pubkey") + }; + + // Possibly generate bad data for act2 and ensure that the initiator does not panic + if generator.generate_bool()? { + act2 = (generator.generate_bytes(50)?).to_vec(); + used_generated_data = true; + } + + let (mut act3, (mut initiator_conduit, responder_pubkey)) = match test_ctx.initiator_handshake.process_act(&act2) { + Ok((Some(act3), Some((conduit, remote_pubkey)))) => { + (act3, (conduit, remote_pubkey)) } - assert!(crypter.is_ready_for_encryption()); - crypter + Err(_) => { + assert!(used_generated_data); + return Err("generated expected failure with bad data".to_string()); + } + _ => panic!("initiator required to output act bytes and no conduit/pubkey") }; - loop { - if get_slice!(1)[0] == 0 { - crypter.encrypt_message(get_slice!(slice_to_be16(get_slice!(2)))); - } else { - let len = match crypter.decrypt_length_header(get_slice!(16+2)) { - Ok(len) => len, - Err(_) => return, - }; - match crypter.decrypt_message(get_slice!(len as usize + 16)) { - Ok(_) => {}, - Err(_) => return, - } + + // Possibly generate bad data for act3 and ensure that the responder does not panic + if generator.generate_bool()? { + act3 = (generator.generate_bytes(66)?).to_vec(); + used_generated_data = true; + } + + let (mut responder_conduit, initiator_pubkey) = match test_ctx.responder_handshake.process_act(&act3) { + Ok((None, Some((conduit, remote_pubkey)))) => { + (conduit, remote_pubkey) + } + Err(_) => { + // extremely unlikely we randomly generate a correct act3, but if so.. reset this + assert!(used_generated_data); + return Err("generated expected failure with bad data".to_string()); + }, + _ => panic!("responder required to output conduit/remote pubkey and no act bytes") + }; + + // The handshake should complete with each peer knowing the static_public_key of the remote peer + if initiator_pubkey != test_ctx.initiator_static_public_key { + assert!(used_generated_data); + return Ok(()); + } + if responder_pubkey != test_ctx.responder_static_public_key { + assert!(used_generated_data); + return Ok(()); + } + + // The nodes should be able to communicate over the conduit + do_conduit_tests(generator, &mut initiator_conduit, &mut responder_conduit)?; + + unreachable!(); +} + +#[inline] +fn do_test(data: &[u8]) { + let mut generator = FuzzGen::new(data); + + // Based on a "random" bool, decide which test variant to run + let do_valid_handshake = match generator.generate_bool() { + Ok(value) => { value }, + _ => { return } + }; + + if do_valid_handshake { + match do_completed_handshake_test(&mut generator) { + _ => {} + } + } else { + match do_handshake_test(&mut generator) { + _ => {} } } } @@ -94,3 +254,58 @@ pub fn peer_crypt_test(data: &[u8], _out: Out) { pub extern "C" fn peer_crypt_run(data: *const u8, datalen: usize) { do_test(unsafe { std::slice::from_raw_parts(data, datalen) }); } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn data_generator_empty() { + let mut generator = FuzzGen::new(&[]); + assert_eq!(generator.generate_bool().err(), Some("out of bytes".to_string())); + } + + #[test] + fn data_generator_bool_true() { + let mut generator = FuzzGen::new(&[1]); + assert!(generator.generate_bool().unwrap()); + } + + #[test] + fn data_generator_bool_false() { + let mut generator = FuzzGen::new(&[0]); + assert!(!generator.generate_bool().unwrap()); + } + + #[test] + fn data_generator_bool_then_error() { + let mut generator = FuzzGen::new(&[1]); + assert!(generator.generate_bool().unwrap()); + assert_eq!(generator.generate_bool().err(), Some("out of bytes".to_string())); + } + + #[test] + fn data_generator_bytes_too_many() { + let mut generator = FuzzGen::new(&[1, 2, 3, 4]); + assert_eq!(generator.generate_bytes(5).err(), Some("out of bytes".to_string())); + } + + #[test] + fn data_generator_bytes() { + let input = [1, 2, 3, 4]; + let mut generator = FuzzGen::new(&input); + let result = generator.generate_bytes(4).unwrap(); + assert_eq!(result, input); + } + + #[test] + fn data_generator_bytes_sequential() { + let input = [1, 2, 3, 4]; + let mut generator = FuzzGen::new(&input); + let result = generator.generate_bytes(2).unwrap(); + assert_eq!(result, &input[..2]); + let result = generator.generate_bytes(2).unwrap(); + assert_eq!(result, &input[2..]); + assert_eq!(generator.generate_bytes(1).err(), Some("out of bytes".to_string())); + } +} \ No newline at end of file From 4435b126ac58fcb765fd48ed0f35df684ef778b3 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sat, 22 Aug 2020 19:46:45 -0700 Subject: [PATCH 33/45] delete: Remove peer_channel_encryptor.rs Functionality moved to: - peers/handshake/mod.rs - peers/handshake/states.rs - peers/conduit.rs Fuzz testing rewritten here: - fuzz/src/peer_crypt.rs --- lightning/src/ln/mod.rs | 6 - lightning/src/ln/peer_channel_encryptor.rs | 738 --------------------- lightning/src/ln/peers/conduit.rs | 39 ++ lightning/src/ln/peers/mod.rs | 12 +- 4 files changed, 49 insertions(+), 746 deletions(-) delete mode 100644 lightning/src/ln/peer_channel_encryptor.rs diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index 14e9cb86f48..5f0f1f59f6a 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -25,11 +25,6 @@ pub mod chan_utils; pub mod features; pub(crate) mod onchaintx; -#[cfg(feature = "fuzztarget")] -pub mod peer_channel_encryptor; -#[cfg(not(feature = "fuzztarget"))] -pub(crate) mod peer_channel_encryptor; - mod channel; mod onion_utils; mod wire; @@ -53,4 +48,3 @@ mod reorg_tests; #[allow(unused_mut)] mod onion_route_tests; -pub use self::peer_channel_encryptor::LN_MAX_MSG_LEN; diff --git a/lightning/src/ln/peer_channel_encryptor.rs b/lightning/src/ln/peer_channel_encryptor.rs deleted file mode 100644 index 8310edb64e2..00000000000 --- a/lightning/src/ln/peer_channel_encryptor.rs +++ /dev/null @@ -1,738 +0,0 @@ -// This file is Copyright its original authors, visible in version control -// history. -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. - -use ln::msgs::LightningError; -use ln::msgs; - -use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine}; -use bitcoin::hashes::sha256::Hash as Sha256; - -use bitcoin::secp256k1::Secp256k1; -use bitcoin::secp256k1::key::{PublicKey,SecretKey}; -use bitcoin::secp256k1::ecdh::SharedSecret; -use bitcoin::secp256k1; - -use util::chacha20poly1305rfc::ChaCha20Poly1305RFC; -use util::byte_utils; -use bitcoin::hashes::hex::ToHex; - -/// Maximum Lightning message data length according to -/// [BOLT-8](https://github.com/lightningnetwork/lightning-rfc/blob/v1.0/08-transport.md#lightning-message-specification) -/// and [BOLT-1](https://github.com/lightningnetwork/lightning-rfc/blob/master/01-messaging.md#lightning-message-format): -pub const LN_MAX_MSG_LEN: usize = ::std::u16::MAX as usize; // Must be equal to 65535 - -// Sha256("Noise_XK_secp256k1_ChaChaPoly_SHA256") -const NOISE_CK: [u8; 32] = [0x26, 0x40, 0xf5, 0x2e, 0xeb, 0xcd, 0x9e, 0x88, 0x29, 0x58, 0x95, 0x1c, 0x79, 0x42, 0x50, 0xee, 0xdb, 0x28, 0x00, 0x2c, 0x05, 0xd7, 0xdc, 0x2e, 0xa0, 0xf1, 0x95, 0x40, 0x60, 0x42, 0xca, 0xf1]; -// Sha256(NOISE_CK || "lightning") -const NOISE_H: [u8; 32] = [0xd1, 0xfb, 0xf6, 0xde, 0xe4, 0xf6, 0x86, 0xf1, 0x32, 0xfd, 0x70, 0x2c, 0x4a, 0xbf, 0x8f, 0xba, 0x4b, 0xb4, 0x20, 0xd8, 0x9d, 0x2a, 0x04, 0x8a, 0x3c, 0x4f, 0x4c, 0x09, 0x2e, 0x37, 0xb6, 0x76]; - -pub enum NextNoiseStep { - ActOne, - ActTwo, - ActThree, - NoiseComplete, -} - -#[derive(PartialEq)] -enum NoiseStep { - PreActOne, - PostActOne, - PostActTwo, - // When done swap noise_state for NoiseState::Finished -} - -struct BidirectionalNoiseState { - h: [u8; 32], - ck: [u8; 32], -} -enum DirectionalNoiseState { - Outbound { - ie: SecretKey, - }, - Inbound { - ie: Option, // filled in if state >= PostActOne - re: Option, // filled in if state >= PostActTwo - temp_k2: Option<[u8; 32]>, // filled in if state >= PostActTwo - } -} -enum NoiseState { - InProgress { - state: NoiseStep, - directional_state: DirectionalNoiseState, - bidirectional_state: BidirectionalNoiseState, - }, - Finished { - sk: [u8; 32], - sn: u64, - sck: [u8; 32], - rk: [u8; 32], - rn: u64, - rck: [u8; 32], - } -} - -pub struct PeerChannelEncryptor { - secp_ctx: Secp256k1, - their_node_id: Option, // filled in for outbound, or inbound after noise_state is Finished - - noise_state: NoiseState, -} - -impl PeerChannelEncryptor { - pub fn new_outbound(their_node_id: PublicKey, ephemeral_key: SecretKey) -> PeerChannelEncryptor { - let secp_ctx = Secp256k1::signing_only(); - - let mut sha = Sha256::engine(); - sha.input(&NOISE_H); - sha.input(&their_node_id.serialize()[..]); - let h = Sha256::from_engine(sha).into_inner(); - - PeerChannelEncryptor { - their_node_id: Some(their_node_id), - secp_ctx: secp_ctx, - noise_state: NoiseState::InProgress { - state: NoiseStep::PreActOne, - directional_state: DirectionalNoiseState::Outbound { - ie: ephemeral_key, - }, - bidirectional_state: BidirectionalNoiseState { - h: h, - ck: NOISE_CK, - }, - } - } - } - - pub fn new_inbound(our_node_secret: &SecretKey) -> PeerChannelEncryptor { - let secp_ctx = Secp256k1::signing_only(); - - let mut sha = Sha256::engine(); - sha.input(&NOISE_H); - let our_node_id = PublicKey::from_secret_key(&secp_ctx, our_node_secret); - sha.input(&our_node_id.serialize()[..]); - let h = Sha256::from_engine(sha).into_inner(); - - PeerChannelEncryptor { - their_node_id: None, - secp_ctx: secp_ctx, - noise_state: NoiseState::InProgress { - state: NoiseStep::PreActOne, - directional_state: DirectionalNoiseState::Inbound { - ie: None, - re: None, - temp_k2: None, - }, - bidirectional_state: BidirectionalNoiseState { - h: h, - ck: NOISE_CK, - }, - } - } - } - - #[inline] - fn encrypt_with_ad(res: &mut[u8], n: u64, key: &[u8; 32], h: &[u8], plaintext: &[u8]) { - let mut nonce = [0; 12]; - nonce[4..].copy_from_slice(&byte_utils::le64_to_array(n)); - - let mut chacha = ChaCha20Poly1305RFC::new(key, &nonce, h); - let mut tag = [0; 16]; - chacha.encrypt(plaintext, &mut res[0..plaintext.len()], &mut tag); - res[plaintext.len()..].copy_from_slice(&tag); - } - - #[inline] - fn decrypt_with_ad(res: &mut[u8], n: u64, key: &[u8; 32], h: &[u8], cyphertext: &[u8]) -> Result<(), LightningError> { - let mut nonce = [0; 12]; - nonce[4..].copy_from_slice(&byte_utils::le64_to_array(n)); - - let mut chacha = ChaCha20Poly1305RFC::new(key, &nonce, h); - if !chacha.decrypt(&cyphertext[0..cyphertext.len() - 16], res, &cyphertext[cyphertext.len() - 16..]) { - return Err(LightningError{err: "Bad MAC".to_owned(), action: msgs::ErrorAction::DisconnectPeer{ msg: None }}); - } - Ok(()) - } - - fn hkdf_extract_expand(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32]) { - let mut hmac = HmacEngine::::new(salt); - hmac.input(ikm); - let prk = Hmac::from_engine(hmac).into_inner(); - let mut hmac = HmacEngine::::new(&prk[..]); - hmac.input(&[1; 1]); - let t1 = Hmac::from_engine(hmac).into_inner(); - let mut hmac = HmacEngine::::new(&prk[..]); - hmac.input(&t1); - hmac.input(&[2; 1]); - (t1, Hmac::from_engine(hmac).into_inner()) - } - - #[inline] - fn hkdf(state: &mut BidirectionalNoiseState, ss: SharedSecret) -> [u8; 32] { - let (t1, t2) = Self::hkdf_extract_expand(&state.ck, &ss[..]); - state.ck = t1; - t2 - } - - #[inline] - fn outbound_noise_act(secp_ctx: &Secp256k1, state: &mut BidirectionalNoiseState, our_key: &SecretKey, their_key: &PublicKey) -> ([u8; 50], [u8; 32]) { - let our_pub = PublicKey::from_secret_key(secp_ctx, &our_key); - - let mut sha = Sha256::engine(); - sha.input(&state.h); - sha.input(&our_pub.serialize()[..]); - state.h = Sha256::from_engine(sha).into_inner(); - - let ss = SharedSecret::new(&their_key, &our_key); - let temp_k = PeerChannelEncryptor::hkdf(state, ss); - - let mut res = [0; 50]; - res[1..34].copy_from_slice(&our_pub.serialize()[..]); - PeerChannelEncryptor::encrypt_with_ad(&mut res[34..], 0, &temp_k, &state.h, &[0; 0]); - - let mut sha = Sha256::engine(); - sha.input(&state.h); - sha.input(&res[34..]); - state.h = Sha256::from_engine(sha).into_inner(); - - (res, temp_k) - } - - #[inline] - fn inbound_noise_act(state: &mut BidirectionalNoiseState, act: &[u8], our_key: &SecretKey) -> Result<(PublicKey, [u8; 32]), LightningError> { - assert_eq!(act.len(), 50); - - if act[0] != 0 { - return Err(LightningError{err: format!("Unknown handshake version number {}", act[0]), action: msgs::ErrorAction::DisconnectPeer{ msg: None }}); - } - - let their_pub = match PublicKey::from_slice(&act[1..34]) { - Err(_) => return Err(LightningError{err: format!("Invalid public key {}", &act[1..34].to_hex()), action: msgs::ErrorAction::DisconnectPeer{ msg: None }}), - Ok(key) => key, - }; - - let mut sha = Sha256::engine(); - sha.input(&state.h); - sha.input(&their_pub.serialize()[..]); - state.h = Sha256::from_engine(sha).into_inner(); - - let ss = SharedSecret::new(&their_pub, &our_key); - let temp_k = PeerChannelEncryptor::hkdf(state, ss); - - let mut dec = [0; 0]; - PeerChannelEncryptor::decrypt_with_ad(&mut dec, 0, &temp_k, &state.h, &act[34..])?; - - let mut sha = Sha256::engine(); - sha.input(&state.h); - sha.input(&act[34..]); - state.h = Sha256::from_engine(sha).into_inner(); - - Ok((their_pub, temp_k)) - } - - pub fn get_act_one(&mut self) -> [u8; 50] { - match self.noise_state { - NoiseState::InProgress { ref mut state, ref directional_state, ref mut bidirectional_state } => - match directional_state { - &DirectionalNoiseState::Outbound { ref ie } => { - if *state != NoiseStep::PreActOne { - panic!("Requested act at wrong step"); - } - - let (res, _) = PeerChannelEncryptor::outbound_noise_act(&self.secp_ctx, bidirectional_state, &ie, &self.their_node_id.unwrap()); - *state = NoiseStep::PostActOne; - res - }, - _ => panic!("Wrong direction for act"), - }, - _ => panic!("Cannot get act one after noise handshake completes"), - } - } - - pub fn process_act_one_with_keys(&mut self, act_one: &[u8], our_node_secret: &SecretKey, our_ephemeral: SecretKey) -> Result<[u8; 50], LightningError> { - assert_eq!(act_one.len(), 50); - - match self.noise_state { - NoiseState::InProgress { ref mut state, ref mut directional_state, ref mut bidirectional_state } => - match directional_state { - &mut DirectionalNoiseState::Inbound { ref mut ie, ref mut re, ref mut temp_k2 } => { - if *state != NoiseStep::PreActOne { - panic!("Requested act at wrong step"); - } - - let (their_pub, _) = PeerChannelEncryptor::inbound_noise_act(bidirectional_state, act_one, &our_node_secret)?; - ie.get_or_insert(their_pub); - - re.get_or_insert(our_ephemeral); - - let (res, temp_k) = PeerChannelEncryptor::outbound_noise_act(&self.secp_ctx, bidirectional_state, &re.unwrap(), &ie.unwrap()); - *temp_k2 = Some(temp_k); - *state = NoiseStep::PostActTwo; - Ok(res) - }, - _ => panic!("Wrong direction for act"), - }, - _ => panic!("Cannot get act one after noise handshake completes"), - } - } - - pub fn process_act_two(&mut self, act_two: &[u8], our_node_secret: &SecretKey) -> Result<([u8; 66], PublicKey), LightningError> { - assert_eq!(act_two.len(), 50); - - let final_hkdf; - let ck; - let res: [u8; 66] = match self.noise_state { - NoiseState::InProgress { ref state, ref directional_state, ref mut bidirectional_state } => - match directional_state { - &DirectionalNoiseState::Outbound { ref ie } => { - if *state != NoiseStep::PostActOne { - panic!("Requested act at wrong step"); - } - - let (re, temp_k2) = PeerChannelEncryptor::inbound_noise_act(bidirectional_state, act_two, &ie)?; - - let mut res = [0; 66]; - let our_node_id = PublicKey::from_secret_key(&self.secp_ctx, &our_node_secret); - - PeerChannelEncryptor::encrypt_with_ad(&mut res[1..50], 1, &temp_k2, &bidirectional_state.h, &our_node_id.serialize()[..]); - - let mut sha = Sha256::engine(); - sha.input(&bidirectional_state.h); - sha.input(&res[1..50]); - bidirectional_state.h = Sha256::from_engine(sha).into_inner(); - - let ss = SharedSecret::new(&re, our_node_secret); - let temp_k = PeerChannelEncryptor::hkdf(bidirectional_state, ss); - - PeerChannelEncryptor::encrypt_with_ad(&mut res[50..], 0, &temp_k, &bidirectional_state.h, &[0; 0]); - final_hkdf = Self::hkdf_extract_expand(&bidirectional_state.ck, &[0; 0]); - ck = bidirectional_state.ck.clone(); - res - }, - _ => panic!("Wrong direction for act"), - }, - _ => panic!("Cannot get act one after noise handshake completes"), - }; - - let (sk, rk) = final_hkdf; - self.noise_state = NoiseState::Finished { - sk: sk, - sn: 0, - sck: ck.clone(), - rk: rk, - rn: 0, - rck: ck, - }; - - Ok((res, self.their_node_id.unwrap().clone())) - } - - pub fn process_act_three(&mut self, act_three: &[u8]) -> Result { - assert_eq!(act_three.len(), 66); - - let final_hkdf; - let ck; - match self.noise_state { - NoiseState::InProgress { ref state, ref directional_state, ref mut bidirectional_state } => - match directional_state { - &DirectionalNoiseState::Inbound { ie: _, ref re, ref temp_k2 } => { - if *state != NoiseStep::PostActTwo { - panic!("Requested act at wrong step"); - } - if act_three[0] != 0 { - return Err(LightningError{err: format!("Unknown handshake version number {}", act_three[0]), action: msgs::ErrorAction::DisconnectPeer{ msg: None }}); - } - - let mut their_node_id = [0; 33]; - PeerChannelEncryptor::decrypt_with_ad(&mut their_node_id, 1, &temp_k2.unwrap(), &bidirectional_state.h, &act_three[1..50])?; - self.their_node_id = Some(match PublicKey::from_slice(&their_node_id) { - Ok(key) => key, - Err(_) => return Err(LightningError{err: format!("Bad node_id from peer, {}", &their_node_id.to_hex()), action: msgs::ErrorAction::DisconnectPeer{ msg: None }}), - }); - - let mut sha = Sha256::engine(); - sha.input(&bidirectional_state.h); - sha.input(&act_three[1..50]); - bidirectional_state.h = Sha256::from_engine(sha).into_inner(); - - let ss = SharedSecret::new(&self.their_node_id.unwrap(), &re.unwrap()); - let temp_k = PeerChannelEncryptor::hkdf(bidirectional_state, ss); - - PeerChannelEncryptor::decrypt_with_ad(&mut [0; 0], 0, &temp_k, &bidirectional_state.h, &act_three[50..])?; - final_hkdf = Self::hkdf_extract_expand(&bidirectional_state.ck, &[0; 0]); - ck = bidirectional_state.ck.clone(); - }, - _ => panic!("Wrong direction for act"), - }, - _ => panic!("Cannot get act one after noise handshake completes"), - } - - let (rk, sk) = final_hkdf; - self.noise_state = NoiseState::Finished { - sk: sk, - sn: 0, - sck: ck.clone(), - rk: rk, - rn: 0, - rck: ck, - }; - - Ok(self.their_node_id.unwrap().clone()) - } - - /// Encrypts the given message, returning the encrypted version - /// panics if msg.len() > 65535 or Noise handshake has not finished. - pub fn encrypt_message(&mut self, msg: &[u8]) -> Vec { - if msg.len() > LN_MAX_MSG_LEN { - panic!("Attempted to encrypt message longer than 65535 bytes!"); - } - - let mut res = Vec::with_capacity(msg.len() + 16*2 + 2); - res.resize(msg.len() + 16*2 + 2, 0); - - match self.noise_state { - NoiseState::Finished { ref mut sk, ref mut sn, ref mut sck, rk: _, rn: _, rck: _ } => { - if *sn >= 1000 { - let (new_sck, new_sk) = Self::hkdf_extract_expand(sck, sk); - *sck = new_sck; - *sk = new_sk; - *sn = 0; - } - - Self::encrypt_with_ad(&mut res[0..16+2], *sn, sk, &[0; 0], &byte_utils::be16_to_array(msg.len() as u16)); - *sn += 1; - - Self::encrypt_with_ad(&mut res[16+2..], *sn, sk, &[0; 0], msg); - *sn += 1; - }, - _ => panic!("Tried to encrypt a message prior to noise handshake completion"), - } - - res - } - - /// Decrypts a message length header from the remote peer. - /// panics if noise handshake has not yet finished or msg.len() != 18 - pub fn decrypt_length_header(&mut self, msg: &[u8]) -> Result { - assert_eq!(msg.len(), 16+2); - - match self.noise_state { - NoiseState::Finished { sk: _, sn: _, sck: _, ref mut rk, ref mut rn, ref mut rck } => { - if *rn >= 1000 { - let (new_rck, new_rk) = Self::hkdf_extract_expand(rck, rk); - *rck = new_rck; - *rk = new_rk; - *rn = 0; - } - - let mut res = [0; 2]; - Self::decrypt_with_ad(&mut res, *rn, rk, &[0; 0], msg)?; - *rn += 1; - Ok(byte_utils::slice_to_be16(&res)) - }, - _ => panic!("Tried to decrypt a message prior to noise handshake completion"), - } - } - - /// Decrypts the given message. - /// panics if msg.len() > 65535 + 16 - pub fn decrypt_message(&mut self, msg: &[u8]) -> Result, LightningError> { - if msg.len() > LN_MAX_MSG_LEN + 16 { - panic!("Attempted to decrypt message longer than 65535 + 16 bytes!"); - } - - match self.noise_state { - NoiseState::Finished { sk: _, sn: _, sck: _, ref rk, ref mut rn, rck: _ } => { - let mut res = Vec::with_capacity(msg.len() - 16); - res.resize(msg.len() - 16, 0); - Self::decrypt_with_ad(&mut res[..], *rn, rk, &[0; 0], msg)?; - *rn += 1; - - Ok(res) - }, - _ => panic!("Tried to decrypt a message prior to noise handshake completion"), - } - } - - pub fn get_noise_step(&self) -> NextNoiseStep { - match self.noise_state { - NoiseState::InProgress {ref state, ..} => { - match state { - &NoiseStep::PreActOne => NextNoiseStep::ActOne, - &NoiseStep::PostActOne => NextNoiseStep::ActTwo, - &NoiseStep::PostActTwo => NextNoiseStep::ActThree, - } - }, - NoiseState::Finished {..} => NextNoiseStep::NoiseComplete, - } - } - - pub fn is_ready_for_encryption(&self) -> bool { - match self.noise_state { - NoiseState::InProgress {..} => { false }, - NoiseState::Finished {..} => { true } - } - } -} - -#[cfg(test)] -mod tests { - use super::LN_MAX_MSG_LEN; - - use bitcoin::secp256k1::key::{PublicKey,SecretKey}; - - use hex; - - use ln::peer_channel_encryptor::{PeerChannelEncryptor,NoiseState}; - - fn get_outbound_peer_for_initiator_test_vectors() -> PeerChannelEncryptor { - let their_node_id = PublicKey::from_slice(&hex::decode("028d7500dd4c12685d1f568b4c2b5048e8534b873319f3a8daa612b469132ec7f7").unwrap()[..]).unwrap(); - - let mut outbound_peer = PeerChannelEncryptor::new_outbound(their_node_id, SecretKey::from_slice(&hex::decode("1212121212121212121212121212121212121212121212121212121212121212").unwrap()[..]).unwrap()); - assert_eq!(outbound_peer.get_act_one()[..], hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap()[..]); - outbound_peer - } - - fn get_inbound_peer_for_test_vectors() -> PeerChannelEncryptor { - // transport-responder successful handshake - let our_node_id = SecretKey::from_slice(&hex::decode("2121212121212121212121212121212121212121212121212121212121212121").unwrap()[..]).unwrap(); - let our_ephemeral = SecretKey::from_slice(&hex::decode("2222222222222222222222222222222222222222222222222222222222222222").unwrap()[..]).unwrap(); - - let mut inbound_peer = PeerChannelEncryptor::new_inbound(&our_node_id); - - let act_one = hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap().to_vec(); - assert_eq!(inbound_peer.process_act_one_with_keys(&act_one[..], &our_node_id, our_ephemeral.clone()).unwrap()[..], hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap()[..]); - - let act_three = hex::decode("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba").unwrap().to_vec(); - // test vector doesn't specify the initiator static key, but it's the same as the one - // from transport-initiator successful handshake - assert_eq!(inbound_peer.process_act_three(&act_three[..]).unwrap().serialize()[..], hex::decode("034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa").unwrap()[..]); - - match inbound_peer.noise_state { - NoiseState::Finished { sk, sn, sck, rk, rn, rck } => { - assert_eq!(sk, hex::decode("bb9020b8965f4df047e07f955f3c4b88418984aadc5cdb35096b9ea8fa5c3442").unwrap()[..]); - assert_eq!(sn, 0); - assert_eq!(sck, hex::decode("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01").unwrap()[..]); - assert_eq!(rk, hex::decode("969ab31b4d288cedf6218839b27a3e2140827047f2c0f01bf5c04435d43511a9").unwrap()[..]); - assert_eq!(rn, 0); - assert_eq!(rck, hex::decode("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01").unwrap()[..]); - }, - _ => panic!() - } - - inbound_peer - } - - #[test] - fn noise_initiator_test_vectors() { - let our_node_id = SecretKey::from_slice(&hex::decode("1111111111111111111111111111111111111111111111111111111111111111").unwrap()[..]).unwrap(); - - { - // transport-initiator successful handshake - let mut outbound_peer = get_outbound_peer_for_initiator_test_vectors(); - - let act_two = hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap().to_vec(); - assert_eq!(outbound_peer.process_act_two(&act_two[..], &our_node_id).unwrap().0[..], hex::decode("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba").unwrap()[..]); - - match outbound_peer.noise_state { - NoiseState::Finished { sk, sn, sck, rk, rn, rck } => { - assert_eq!(sk, hex::decode("969ab31b4d288cedf6218839b27a3e2140827047f2c0f01bf5c04435d43511a9").unwrap()[..]); - assert_eq!(sn, 0); - assert_eq!(sck, hex::decode("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01").unwrap()[..]); - assert_eq!(rk, hex::decode("bb9020b8965f4df047e07f955f3c4b88418984aadc5cdb35096b9ea8fa5c3442").unwrap()[..]); - assert_eq!(rn, 0); - assert_eq!(rck, hex::decode("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01").unwrap()[..]); - }, - _ => panic!() - } - } - { - // transport-initiator act2 short read test - // Can't actually test this cause process_act_two requires you pass the right length! - } - { - // transport-initiator act2 bad version test - let mut outbound_peer = get_outbound_peer_for_initiator_test_vectors(); - - let act_two = hex::decode("0102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap().to_vec(); - assert!(outbound_peer.process_act_two(&act_two[..], &our_node_id).is_err()); - } - - { - // transport-initiator act2 bad key serialization test - let mut outbound_peer = get_outbound_peer_for_initiator_test_vectors(); - - let act_two = hex::decode("0004466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap().to_vec(); - assert!(outbound_peer.process_act_two(&act_two[..], &our_node_id).is_err()); - } - - { - // transport-initiator act2 bad MAC test - let mut outbound_peer = get_outbound_peer_for_initiator_test_vectors(); - - let act_two = hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730af").unwrap().to_vec(); - assert!(outbound_peer.process_act_two(&act_two[..], &our_node_id).is_err()); - } - } - - #[test] - fn noise_responder_test_vectors() { - let our_node_id = SecretKey::from_slice(&hex::decode("2121212121212121212121212121212121212121212121212121212121212121").unwrap()[..]).unwrap(); - let our_ephemeral = SecretKey::from_slice(&hex::decode("2222222222222222222222222222222222222222222222222222222222222222").unwrap()[..]).unwrap(); - - { - let _ = get_inbound_peer_for_test_vectors(); - } - { - // transport-responder act1 short read test - // Can't actually test this cause process_act_one requires you pass the right length! - } - { - // transport-responder act1 bad version test - let mut inbound_peer = PeerChannelEncryptor::new_inbound(&our_node_id); - - let act_one = hex::decode("01036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap().to_vec(); - assert!(inbound_peer.process_act_one_with_keys(&act_one[..], &our_node_id, our_ephemeral.clone()).is_err()); - } - { - // transport-responder act1 bad key serialization test - let mut inbound_peer = PeerChannelEncryptor::new_inbound(&our_node_id); - - let act_one =hex::decode("00046360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap().to_vec(); - assert!(inbound_peer.process_act_one_with_keys(&act_one[..], &our_node_id, our_ephemeral.clone()).is_err()); - } - { - // transport-responder act1 bad MAC test - let mut inbound_peer = PeerChannelEncryptor::new_inbound(&our_node_id); - - let act_one = hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6b").unwrap().to_vec(); - assert!(inbound_peer.process_act_one_with_keys(&act_one[..], &our_node_id, our_ephemeral.clone()).is_err()); - } - { - // transport-responder act3 bad version test - let mut inbound_peer = PeerChannelEncryptor::new_inbound(&our_node_id); - - let act_one = hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap().to_vec(); - assert_eq!(inbound_peer.process_act_one_with_keys(&act_one[..], &our_node_id, our_ephemeral.clone()).unwrap()[..], hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap()[..]); - - let act_three = hex::decode("01b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba").unwrap().to_vec(); - assert!(inbound_peer.process_act_three(&act_three[..]).is_err()); - } - { - // transport-responder act3 short read test - // Can't actually test this cause process_act_three requires you pass the right length! - } - { - // transport-responder act3 bad MAC for ciphertext test - let mut inbound_peer = PeerChannelEncryptor::new_inbound(&our_node_id); - - let act_one = hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap().to_vec(); - assert_eq!(inbound_peer.process_act_one_with_keys(&act_one[..], &our_node_id, our_ephemeral.clone()).unwrap()[..], hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap()[..]); - - let act_three = hex::decode("00c9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba").unwrap().to_vec(); - assert!(inbound_peer.process_act_three(&act_three[..]).is_err()); - } - { - // transport-responder act3 bad rs test - let mut inbound_peer = PeerChannelEncryptor::new_inbound(&our_node_id); - - let act_one = hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap().to_vec(); - assert_eq!(inbound_peer.process_act_one_with_keys(&act_one[..], &our_node_id, our_ephemeral.clone()).unwrap()[..], hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap()[..]); - - let act_three = hex::decode("00bfe3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa2235536ad09a8ee351870c2bb7f78b754a26c6cef79a98d25139c856d7efd252c2ae73c").unwrap().to_vec(); - assert!(inbound_peer.process_act_three(&act_three[..]).is_err()); - } - { - // transport-responder act3 bad MAC test - let mut inbound_peer = PeerChannelEncryptor::new_inbound(&our_node_id); - - let act_one = hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap().to_vec(); - assert_eq!(inbound_peer.process_act_one_with_keys(&act_one[..], &our_node_id, our_ephemeral.clone()).unwrap()[..], hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap()[..]); - - let act_three = hex::decode("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139bb").unwrap().to_vec(); - assert!(inbound_peer.process_act_three(&act_three[..]).is_err()); - } - } - - - #[test] - fn message_encryption_decryption_test_vectors() { - // We use the same keys as the initiator and responder test vectors, so we copy those tests - // here and use them to encrypt. - let mut outbound_peer = get_outbound_peer_for_initiator_test_vectors(); - - { - let our_node_id = SecretKey::from_slice(&hex::decode("1111111111111111111111111111111111111111111111111111111111111111").unwrap()[..]).unwrap(); - - let act_two = hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap().to_vec(); - assert_eq!(outbound_peer.process_act_two(&act_two[..], &our_node_id).unwrap().0[..], hex::decode("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba").unwrap()[..]); - - match outbound_peer.noise_state { - NoiseState::Finished { sk, sn, sck, rk, rn, rck } => { - assert_eq!(sk, hex::decode("969ab31b4d288cedf6218839b27a3e2140827047f2c0f01bf5c04435d43511a9").unwrap()[..]); - assert_eq!(sn, 0); - assert_eq!(sck, hex::decode("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01").unwrap()[..]); - assert_eq!(rk, hex::decode("bb9020b8965f4df047e07f955f3c4b88418984aadc5cdb35096b9ea8fa5c3442").unwrap()[..]); - assert_eq!(rn, 0); - assert_eq!(rck, hex::decode("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01").unwrap()[..]); - }, - _ => panic!() - } - } - - let mut inbound_peer = get_inbound_peer_for_test_vectors(); - - for i in 0..1005 { - let msg = [0x68, 0x65, 0x6c, 0x6c, 0x6f]; - let res = outbound_peer.encrypt_message(&msg); - assert_eq!(res.len(), 5 + 2*16 + 2); - - let len_header = res[0..2+16].to_vec(); - assert_eq!(inbound_peer.decrypt_length_header(&len_header[..]).unwrap() as usize, msg.len()); - assert_eq!(inbound_peer.decrypt_message(&res[2+16..]).unwrap()[..], msg[..]); - - if i == 0 { - assert_eq!(res, hex::decode("cf2b30ddf0cf3f80e7c35a6e6730b59fe802473180f396d88a8fb0db8cbcf25d2f214cf9ea1d95").unwrap()); - } else if i == 1 { - assert_eq!(res, hex::decode("72887022101f0b6753e0c7de21657d35a4cb2a1f5cde2650528bbc8f837d0f0d7ad833b1a256a1").unwrap()); - } else if i == 500 { - assert_eq!(res, hex::decode("178cb9d7387190fa34db9c2d50027d21793c9bc2d40b1e14dcf30ebeeeb220f48364f7a4c68bf8").unwrap()); - } else if i == 501 { - assert_eq!(res, hex::decode("1b186c57d44eb6de4c057c49940d79bb838a145cb528d6e8fd26dbe50a60ca2c104b56b60e45bd").unwrap()); - } else if i == 1000 { - assert_eq!(res, hex::decode("4a2f3cc3b5e78ddb83dcb426d9863d9d9a723b0337c89dd0b005d89f8d3c05c52b76b29b740f09").unwrap()); - } else if i == 1001 { - assert_eq!(res, hex::decode("2ecd8c8a5629d0d02ab457a0fdd0f7b90a192cd46be5ecb6ca570bfc5e268338b1a16cf4ef2d36").unwrap()); - } - } - } - - #[test] - fn max_msg_len_limit_value() { - assert_eq!(LN_MAX_MSG_LEN, 65535); - assert_eq!(LN_MAX_MSG_LEN, ::std::u16::MAX as usize); - } - - #[test] - #[should_panic(expected = "Attempted to encrypt message longer than 65535 bytes!")] - fn max_message_len_encryption() { - let mut outbound_peer = get_outbound_peer_for_initiator_test_vectors(); - let msg = [4u8; LN_MAX_MSG_LEN + 1]; - outbound_peer.encrypt_message(&msg); - } - - #[test] - #[should_panic(expected = "Attempted to decrypt message longer than 65535 + 16 bytes!")] - fn max_message_len_decryption() { - let mut inbound_peer = get_inbound_peer_for_test_vectors(); - - // MSG should not exceed LN_MAX_MSG_LEN + 16 - let msg = [4u8; LN_MAX_MSG_LEN + 17]; - inbound_peer.decrypt_message(&msg).unwrap(); - } -} diff --git a/lightning/src/ln/peers/conduit.rs b/lightning/src/ln/peers/conduit.rs index c753f0b8ff2..11ebfbca2f6 100644 --- a/lightning/src/ln/peers/conduit.rs +++ b/lightning/src/ln/peers/conduit.rs @@ -5,6 +5,11 @@ use util::byte_utils; pub(super) type SymmetricKey = [u8; 32]; +/// Maximum Lightning message data length according to +/// [BOLT-8](https://github.com/lightningnetwork/lightning-rfc/blob/v1.0/08-transport.md#lightning-message-specification) +/// and [BOLT-1](https://github.com/lightningnetwork/lightning-rfc/blob/master/01-messaging.md#lightning-message-format): +const LN_MAX_MSG_LEN: usize = ::std::u16::MAX as usize; // Must be equal to 65535 + const MESSAGE_LENGTH_HEADER_SIZE: usize = 2; const TAGGED_MESSAGE_LENGTH_HEADER_SIZE: usize = MESSAGE_LENGTH_HEADER_SIZE + chacha::TAG_SIZE; @@ -75,6 +80,7 @@ impl Conduit { /// only the first message will be returned, and the rest stored in the internal buffer. /// If a message pending in the buffer still hasn't been decrypted, that message will be /// returned in lieu of anything new, even if new data is provided. + #[cfg(any(test, feature = "fuzztarget"))] pub fn decrypt_single_message(&mut self, new_data: Option<&[u8]>) -> Option> { self.decryptor.decrypt_single_message(new_data) } @@ -96,6 +102,10 @@ impl Conduit { impl Encryptor { pub(super) fn encrypt(&mut self, buffer: &[u8]) -> Vec { + if buffer.len() > LN_MAX_MSG_LEN { + panic!("Attempted to encrypt message longer than 65535 bytes!"); + } + let length = buffer.len() as u16; let length_bytes = byte_utils::be16_to_array(length); @@ -136,6 +146,10 @@ impl Decryptor { read_buffer.extend_from_slice(data); } + if read_buffer.len() > LN_MAX_MSG_LEN + 16 { + panic!("Attempted to decrypt message longer than 65535 + 16 bytes!"); + } + let (current_message, offset) = self.decrypt(&read_buffer[..]); read_buffer.drain(..offset); // drain the read buffer self.read_buffer = Some(read_buffer); // assign the new value to the built-in buffer @@ -197,6 +211,7 @@ impl Decryptor { #[cfg(test)] mod tests { + use super::*; use hex; use ln::peers::conduit::Conduit; @@ -291,4 +306,28 @@ mod tests { assert_eq!(decrypted_message, message); } } + + #[test] + fn max_msg_len_limit_value() { + assert_eq!(LN_MAX_MSG_LEN, 65535); + assert_eq!(LN_MAX_MSG_LEN, ::std::u16::MAX as usize); + } + + #[test] + #[should_panic(expected = "Attempted to encrypt message longer than 65535 bytes!")] + fn max_message_len_encryption() { + let (mut connected_peer, _) = setup_peers(); + let msg = [4u8; LN_MAX_MSG_LEN + 1]; + connected_peer.encrypt(&msg); + } + + #[test] + #[should_panic(expected = "Attempted to decrypt message longer than 65535 + 16 bytes!")] + fn max_message_len_decryption() { + let (mut connected_peer, _) = setup_peers(); + + // MSG should not exceed LN_MAX_MSG_LEN + 16 + let msg = [4u8; LN_MAX_MSG_LEN + 17]; + connected_peer.decrypt_single_message(Some(&msg)); + } } \ No newline at end of file diff --git a/lightning/src/ln/peers/mod.rs b/lightning/src/ln/peers/mod.rs index f8082c50f10..072950c0dba 100644 --- a/lightning/src/ln/peers/mod.rs +++ b/lightning/src/ln/peers/mod.rs @@ -4,7 +4,15 @@ //! Conduit enables message encryption and decryption, and automatically handles key rotation. mod chacha; -pub mod conduit; -pub mod handshake; pub mod handler; mod hkdf; + +#[cfg(feature = "fuzztarget")] +pub mod conduit; +#[cfg(not(feature = "fuzztarget"))] +mod conduit; + +#[cfg(feature = "fuzztarget")] +pub mod handshake; +#[cfg(not(feature = "fuzztarget"))] +mod handshake; From a123f5ee761829c175117f256c0adba1268e39fc Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Sun, 23 Aug 2020 21:09:41 -0700 Subject: [PATCH 34/45] fix: Allow partial act messages to be received Fix regression introduced in eb6a371 that would error and subsequently cause a disconnect if a partial act was sent to the state machine, even if future calls could be added to the read buffer to create a valid act. Add appropriate unit tests and update fuzz testing to send partial acts some of the time. --- lightning/src/ln/peers/handshake/mod.rs | 6 +- lightning/src/ln/peers/handshake/states.rs | 93 +++++++++++++++++----- 2 files changed, 76 insertions(+), 23 deletions(-) diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs index 35d5b9627d8..3bf47ebb60a 100644 --- a/lightning/src/ln/peers/handshake/mod.rs +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -173,7 +173,8 @@ mod test { #[test] fn errors_properly_returned() { let mut test_ctx = TestCtx::new(); - assert_matches!(test_ctx.inbound_handshake.process_act(&[]).err(), Some(_)); + let invalid_act1 = [0; 50]; + assert_matches!(test_ctx.inbound_handshake.process_act(&invalid_act1).err(), Some(_)); } // Test that any use of the PeerHandshake after returning an error panics @@ -181,7 +182,8 @@ mod test { #[should_panic(expected = "called `Option::unwrap()` on a `None` value")] fn use_after_error_panics() { let mut test_ctx = TestCtx::new(); - assert_matches!(test_ctx.inbound_handshake.process_act(&[]).err(), Some(_)); + let invalid_act1 = [0; 50]; + assert_matches!(test_ctx.inbound_handshake.process_act(&invalid_act1).err(), Some(_)); test_ctx.inbound_handshake.process_act(&[]).unwrap(); } } diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index e94554b0636..8aba9fe20c1 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -181,6 +181,22 @@ impl IHandshakeState for ResponderAwaitingActOneState { let mut read_buffer = self.read_buffer; read_buffer.extend_from_slice(input); + // In the event of a partial fill, stay in the same state and wait for more data + if read_buffer.len() < ACT_ONE_TWO_LENGTH { + return Ok(( + None, + HandshakeState::ResponderAwaitingActOne(Self { + responder_static_private_key: self.responder_static_private_key, + responder_ephemeral_private_key: self.responder_ephemeral_private_key, + responder_ephemeral_public_key: self.responder_ephemeral_public_key, + chaining_key: self.chaining_key, + hash: self.hash, + read_buffer + }) + )); + } + + let hash = self.hash; let responder_static_private_key = self.responder_static_private_key; let chaining_key = self.chaining_key; @@ -222,6 +238,22 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { let mut read_buffer = self.read_buffer; read_buffer.extend_from_slice(input); + // In the event of a partial fill, stay in the same state and wait for more data + if read_buffer.len() < ACT_ONE_TWO_LENGTH { + return Ok(( + None, + HandshakeState::InitiatorAwaitingActTwo(Self { + initiator_static_private_key: self.initiator_static_private_key, + initiator_static_public_key: self.initiator_static_public_key, + initiator_ephemeral_private_key: self.initiator_ephemeral_private_key, + responder_static_public_key: self.responder_static_public_key, + chaining_key: self.chaining_key, + hash: self.hash, + read_buffer + }) + )); + } + let initiator_static_private_key = self.initiator_static_private_key; let initiator_static_public_key = self.initiator_static_public_key; let initiator_ephemeral_private_key = self.initiator_ephemeral_private_key; @@ -287,8 +319,18 @@ impl IHandshakeState for ResponderAwaitingActThreeState { let mut read_buffer = self.read_buffer; read_buffer.extend_from_slice(input); + // In the event of a partial fill, stay in the same state and wait for more data if read_buffer.len() < ACT_THREE_LENGTH { - return Err("need at least 66 bytes".to_string()); + return Ok(( + None, + HandshakeState::ResponderAwaitingActThree(Self { + hash: self.hash, + responder_ephemeral_private_key: self.responder_ephemeral_private_key, + chaining_key: self.chaining_key, + temporary_key: self.temporary_key, + read_buffer + }) + )); } let hash = self.hash; @@ -407,10 +449,6 @@ fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_e // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (receiver) fn process_act_message(read_buffer: &mut Vec, local_private_key: &SecretKey, chaining_key: ChainingKey, hash: Sha256) -> Result<(PublicKey, Sha256, SymmetricKey, SymmetricKey), String> { // 1. Read exactly 50 bytes from the network buffer - if read_buffer.len() < ACT_ONE_TWO_LENGTH { - return Err("need at least 50 bytes".to_string()); - } - let act_bytes: Vec = read_buffer.drain(..ACT_ONE_TWO_LENGTH).collect(); // 2.Parse the read message (m) into v, re, and c @@ -503,9 +541,9 @@ mod test { let responder = ResponderAwaitingActOneState::new(responder_static_private_key, responder_ephemeral_private_key); TestCtx { - initiator: HandshakeState::InitiatorStarting(initiator), + initiator: InitiatorStarting(initiator), initiator_public_key, - responder: HandshakeState::ResponderAwaitingActOne(responder), + responder: ResponderAwaitingActOne(responder), responder_static_public_key, valid_act1: hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap(), valid_act2: hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap(), @@ -565,8 +603,6 @@ mod test { // Responder::AwaitingActOne -> AwaitingActThree // TODO: Should this fail since we don't expect data > ACT_ONE_TWO_LENGTH and likely indicates // a bad peer? - // TODO: Should the behavior be changed to handle act1 data that is striped across multiple - // next() calls? #[test] fn awaiting_act_one_to_awaiting_act_three_input_extra_bytes() { let test_ctx = TestCtx::new(); @@ -578,13 +614,18 @@ mod test { assert_matches!(awaiting_act_three_state, ResponderAwaitingActThree(_)); } - // Responder::AwaitingActOne -> Error (input too small) + // Responder::AwaitingActOne -> AwaitingActThree (segmented calls) // RFC test vector: transport-responder act1 short read test + // Divergence from RFC tests due to not reading directly from the socket (partial message OK) #[test] - fn awaiting_act_one_to_awaiting_act_three_input_too_small() { + fn awaiting_act_one_to_awaiting_act_three_segmented() { let test_ctx = TestCtx::new(); - let act1 = hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c").unwrap(); - assert_eq!(test_ctx.responder.next(&act1).err(), Some(String::from("need at least 50 bytes"))) + let act1_partial1 = &test_ctx.valid_act1[..25]; + let act1_partial2 = &test_ctx.valid_act1[25..]; + + let next_state = test_ctx.responder.next(&act1_partial1).unwrap(); + assert_matches!(next_state, (None, ResponderAwaitingActOne(_))); + assert_matches!(next_state.1.next(&act1_partial2).unwrap(), (Some(_), ResponderAwaitingActThree(_))); } // Responder::AwaitingActOne -> Error (bad version byte) @@ -659,15 +700,20 @@ mod test { assert_eq!(100, conduit.decryptor.read_buffer_length()); } - // Initiator::AwaitingActTwo -> Error (input too small) + // Initiator::AwaitingActTwo -> Complete (segmented calls) // RFC test vector: transport-initiator act2 short read test + // Divergence from RFC tests due to not reading directly from the socket (partial message OK) #[test] - fn awaiting_act_two_input_too_small() { + fn awaiting_act_two_to_complete_segmented() { let test_ctx = TestCtx::new(); let (_act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - let act2 = hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730").unwrap(); - assert_eq!(awaiting_act_two_state.next(&act2).err(), Some(String::from("need at least 50 bytes"))); + let act2_partial1 = &test_ctx.valid_act2[..25]; + let act2_partial2 = &test_ctx.valid_act2[25..]; + + let next_state = awaiting_act_two_state.next(&act2_partial1).unwrap(); + assert_matches!(next_state, (None, InitiatorAwaitingActTwo(_))); + assert_matches!(next_state.1.next(&act2_partial2).unwrap(), (Some(_), Complete(_))); } // Initiator::AwaitingActTwo -> Error (bad version byte) @@ -751,15 +797,20 @@ mod test { assert_eq!(awaiting_act_three_state.next(&act3).err(), Some(String::from("unexpected version"))); } - // Responder::AwaitingActThree -> Error (input too small) + // Responder::AwaitingActThree -> Complete (segmented calls) // RFC test vector: transport-responder act3 short read test + // Divergence from RFC tests due to not reading directly from the socket (partial message OK) #[test] - fn awaiting_act_three_input_too_small() { + fn awaiting_act_three_to_complete_segmented() { let test_ctx = TestCtx::new(); let (_act2, awaiting_act_three_state) = do_next_or_panic!(test_ctx.responder, &test_ctx.valid_act1); - let act3 = hex::decode("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139").unwrap(); - assert_eq!(awaiting_act_three_state.next(&act3).err(), Some(String::from("need at least 66 bytes"))); + let act3_partial1 = &test_ctx.valid_act3[..35]; + let act3_partial2 = &test_ctx.valid_act3[35..]; + + let next_state = awaiting_act_three_state.next(&act3_partial1).unwrap(); + assert_matches!(next_state, (None, ResponderAwaitingActThree(_))); + assert_matches!(next_state.1.next(&act3_partial2), Ok((None, Complete(_)))); } // Responder::AwaitingActThree -> Error (invalid hmac) From 070d19bcb7b4068ce0c15fc35eb7600669f79707 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Mon, 24 Aug 2020 16:52:15 -0700 Subject: [PATCH 35/45] fix: Conduit::Decryptor should not panic on decryption failure introduced in eb6a371 On decryption failure, the Decryptor should return an error to the caller so it can disconnect instead of panicking. Implement this behavior by plumbing Result through the iterator code path and adding the appropriate tests and comments. --- fuzz/src/peer_crypt.rs | 2 +- lightning/src/ln/peers/conduit.rs | 159 ++++++++++++++++++++++++++---- lightning/src/ln/peers/handler.rs | 70 ++++++++----- 3 files changed, 187 insertions(+), 44 deletions(-) diff --git a/fuzz/src/peer_crypt.rs b/fuzz/src/peer_crypt.rs index 4679601a8c1..6cbfc274919 100644 --- a/fuzz/src/peer_crypt.rs +++ b/fuzz/src/peer_crypt.rs @@ -111,7 +111,7 @@ fn do_conduit_tests(generator: &mut FuzzGen, initiator_conduit: &mut Conduit, re initiator_conduit.decrypt_single_message(Some(&encrypted_msg)) }; - assert_eq!(sender_unencrypted_msg, receiver_unencrypted_msg.unwrap().as_slice()); + assert_eq!(sender_unencrypted_msg, receiver_unencrypted_msg.unwrap().unwrap().as_slice()); } } diff --git a/lightning/src/ln/peers/conduit.rs b/lightning/src/ln/peers/conduit.rs index 11ebfbca2f6..807a06ddd12 100644 --- a/lightning/src/ln/peers/conduit.rs +++ b/lightning/src/ln/peers/conduit.rs @@ -38,13 +38,29 @@ pub(super) struct Decryptor { pending_message_length: Option, read_buffer: Option>, + poisoned: bool, // signal an error has occurred so None is returned on iteration after failure } impl Iterator for Decryptor { - type Item = Vec; + type Item = Result>, String>; fn next(&mut self) -> Option { - self.decrypt_single_message(None) + if self.poisoned { + return None; + } + + match self.decrypt_single_message(None) { + Ok(Some(result)) => { + Some(Ok(Some(result))) + }, + Ok(None) => { + None + } + Err(e) => { + self.poisoned = true; + Some(Err(e)) + } + } } } @@ -62,7 +78,8 @@ impl Conduit { receiving_chaining_key: chaining_key, receiving_nonce: 0, read_buffer: None, - pending_message_length: None + pending_message_length: None, + poisoned: false } } } @@ -81,8 +98,8 @@ impl Conduit { /// If a message pending in the buffer still hasn't been decrypted, that message will be /// returned in lieu of anything new, even if new data is provided. #[cfg(any(test, feature = "fuzztarget"))] - pub fn decrypt_single_message(&mut self, new_data: Option<&[u8]>) -> Option> { - self.decryptor.decrypt_single_message(new_data) + pub fn decrypt_single_message(&mut self, new_data: Option<&[u8]>) -> Result>, String> { + Ok(self.decryptor.decrypt_single_message(new_data)?) } fn increment_nonce(nonce: &mut u32, chaining_key: &mut SymmetricKey, key: &mut SymmetricKey) { @@ -135,7 +152,7 @@ impl Decryptor { /// only the first message will be returned, and the rest stored in the internal buffer. /// If a message pending in the buffer still hasn't been decrypted, that message will be /// returned in lieu of anything new, even if new data is provided. - pub fn decrypt_single_message(&mut self, new_data: Option<&[u8]>) -> Option> { + pub fn decrypt_single_message(&mut self, new_data: Option<&[u8]>) -> Result>, String> { let mut read_buffer = if let Some(buffer) = self.read_buffer.take() { buffer } else { @@ -150,25 +167,25 @@ impl Decryptor { panic!("Attempted to decrypt message longer than 65535 + 16 bytes!"); } - let (current_message, offset) = self.decrypt(&read_buffer[..]); + let (current_message, offset) = self.decrypt(&read_buffer[..])?; read_buffer.drain(..offset); // drain the read buffer self.read_buffer = Some(read_buffer); // assign the new value to the built-in buffer - current_message + Ok(current_message) } - fn decrypt(&mut self, buffer: &[u8]) -> (Option>, usize) { + fn decrypt(&mut self, buffer: &[u8]) -> Result<(Option>, usize), String> { let message_length = if let Some(length) = self.pending_message_length { // we have already decrypted the header length } else { if buffer.len() < TAGGED_MESSAGE_LENGTH_HEADER_SIZE { // A message must be at least 18 bytes (2 for encrypted length, 16 for the tag) - return (None, 0); + return Ok((None, 0)); } let encrypted_length = &buffer[0..TAGGED_MESSAGE_LENGTH_HEADER_SIZE]; let mut length_bytes = [0u8; MESSAGE_LENGTH_HEADER_SIZE]; - length_bytes.copy_from_slice(&chacha::decrypt(&self.receiving_key, self.receiving_nonce as u64, &[0; 0], encrypted_length).unwrap()); + length_bytes.copy_from_slice(&chacha::decrypt(&self.receiving_key, self.receiving_nonce as u64, &[0; 0], encrypted_length)?); self.increment_nonce(); @@ -180,18 +197,18 @@ impl Decryptor { if buffer.len() < message_end_index { self.pending_message_length = Some(message_length); - return (None, 0); + return Ok((None, 0)); } self.pending_message_length = None; let encrypted_message = &buffer[TAGGED_MESSAGE_LENGTH_HEADER_SIZE..message_end_index]; - let message = chacha::decrypt(&self.receiving_key, self.receiving_nonce as u64, &[0; 0], encrypted_message).unwrap(); + let message = chacha::decrypt(&self.receiving_key, self.receiving_nonce as u64, &[0; 0], encrypted_message)?; self.increment_nonce(); - (Some(message), message_end_index) + Ok((Some(message), message_end_index)) } fn increment_nonce(&mut self) { @@ -243,7 +260,7 @@ mod tests { let encrypted_message = connected_peer.encrypt(&message); assert_eq!(encrypted_message.len(), 2 + 16 + 16); - let decrypted_message = remote_peer.decrypt_single_message(Some(&encrypted_message)).unwrap(); + let decrypted_message = remote_peer.decrypt_single_message(Some(&encrypted_message)).unwrap().unwrap(); assert_eq!(decrypted_message, Vec::::new()); } @@ -296,17 +313,123 @@ mod tests { let mut current_encrypted_message = encrypted_messages.remove(0); let next_encrypted_message = encrypted_messages.remove(0); current_encrypted_message.extend_from_slice(&next_encrypted_message); - let decrypted_message = remote_peer.decrypt_single_message(Some(¤t_encrypted_message)).unwrap(); + let decrypted_message = remote_peer.decrypt_single_message(Some(¤t_encrypted_message)).unwrap().unwrap(); assert_eq!(decrypted_message, message); } for _ in 0..501 { // decrypt messages directly from buffer without adding to it - let decrypted_message = remote_peer.decrypt_single_message(None).unwrap(); + let decrypted_message = remote_peer.decrypt_single_message(None).unwrap().unwrap(); assert_eq!(decrypted_message, message); } } + // Decryption errors should result in Err + #[test] + fn decryption_failure_errors() { + let (mut connected_peer, mut remote_peer) = setup_peers(); + let encrypted = remote_peer.encrypt(&[1]); + + connected_peer.decryptor.receiving_key = [0; 32]; + assert_eq!(connected_peer.decrypt_single_message(Some(&encrypted)), Err("invalid hmac".to_string())); + } + + // Test next()::None + #[test] + fn decryptor_iterator_empty() { + let (mut connected_peer, _) = setup_peers(); + + assert_eq!(connected_peer.decryptor.next(), None); + } + + // Test next() -> next()::None + #[test] + fn decryptor_iterator_one_item_valid() { + let (mut connected_peer, mut remote_peer) = setup_peers(); + let encrypted = remote_peer.encrypt(&[1]); + connected_peer.read(&encrypted); + + assert_eq!(connected_peer.decryptor.next(), Some(Ok(Some(vec![1])))); + assert_eq!(connected_peer.decryptor.next(), None); + } + + // Test next()::err -> next()::None + #[test] + fn decryptor_iterator_error() { + let (mut connected_peer, mut remote_peer) = setup_peers(); + let encrypted = remote_peer.encrypt(&[1]); + connected_peer.read(&encrypted); + + connected_peer.decryptor.receiving_key = [0; 32]; + assert_eq!(connected_peer.decryptor.next(), Some(Err("invalid hmac".to_string()))); + assert_eq!(connected_peer.decryptor.next(), None); + } + + // Test next()::Some -> next()::err -> next()::None + #[test] + fn decryptor_iterator_error_after_success() { + let (mut connected_peer, mut remote_peer) = setup_peers(); + let encrypted = remote_peer.encrypt(&[1]); + connected_peer.read(&encrypted); + let encrypted = remote_peer.encrypt(&[2]); + connected_peer.read(&encrypted); + + assert_eq!(connected_peer.decryptor.next(), Some(Ok(Some(vec![1])))); + connected_peer.decryptor.receiving_key = [0; 32]; + assert_eq!(connected_peer.decryptor.next(), Some(Err("invalid hmac".to_string()))); + assert_eq!(connected_peer.decryptor.next(), None); + } + + // Test that next()::Some -> next()::err -> next()::None + // Error should poison decryptor + #[test] + fn decryptor_iterator_next_after_error_returns_none() { + let (mut connected_peer, mut remote_peer) = setup_peers(); + let encrypted = remote_peer.encrypt(&[1]); + connected_peer.read(&encrypted); + let encrypted = remote_peer.encrypt(&[2]); + connected_peer.read(&encrypted); + let encrypted = remote_peer.encrypt(&[3]); + connected_peer.read(&encrypted); + + // Get one valid value + assert_eq!(connected_peer.decryptor.next(), Some(Ok(Some(vec![1])))); + let valid_receiving_key = connected_peer.decryptor.receiving_key; + + // Corrupt the receiving key and ensure we get a failure + connected_peer.decryptor.receiving_key = [0; 32]; + assert_eq!(connected_peer.decryptor.next(), Some(Err("invalid hmac".to_string()))); + + // Restore the receiving key, do a read and ensure None is returned (poisoned) + connected_peer.decryptor.receiving_key = valid_receiving_key; + assert_eq!(connected_peer.decryptor.next(), None); + } + + // Test next()::Some -> next()::err -> read() -> next()::None + // Error should poison decryptor even after future reads + #[test] + fn decryptor_iterator_read_next_after_error_returns_none() { + let (mut connected_peer, mut remote_peer) = setup_peers(); + let encrypted = remote_peer.encrypt(&[1]); + connected_peer.read(&encrypted); + let encrypted = remote_peer.encrypt(&[2]); + connected_peer.read(&encrypted); + + // Get one valid value + assert_eq!(connected_peer.decryptor.next(), Some(Ok(Some(vec![1])))); + let valid_receiving_key = connected_peer.decryptor.receiving_key; + + // Corrupt the receiving key and ensure we get a failure + connected_peer.decryptor.receiving_key = [0; 32]; + assert_eq!(connected_peer.decryptor.next(), Some(Err("invalid hmac".to_string()))); + + // Restore the receiving key, do a read and ensure None is returned (poisoned) + let encrypted = remote_peer.encrypt(&[3]); + connected_peer.read(&encrypted); + connected_peer.decryptor.receiving_key = valid_receiving_key; + assert_eq!(connected_peer.decryptor.next(), None); + } + #[test] fn max_msg_len_limit_value() { assert_eq!(LN_MAX_MSG_LEN, 65535); @@ -328,6 +451,6 @@ mod tests { // MSG should not exceed LN_MAX_MSG_LEN + 16 let msg = [4u8; LN_MAX_MSG_LEN + 17]; - connected_peer.decrypt_single_message(Some(&msg)); + connected_peer.decrypt_single_message(Some(&msg)).unwrap(); } } \ No newline at end of file diff --git a/lightning/src/ln/peers/handler.rs b/lightning/src/ln/peers/handler.rs index 05c72c84a16..91de3d6920d 100644 --- a/lightning/src/ln/peers/handler.rs +++ b/lightning/src/ln/peers/handler.rs @@ -603,33 +603,53 @@ impl PeerManager x, - Err(e) => { - match e { - msgs::DecodeError::UnknownVersion => return Err(PeerHandleError { no_connection_possible: false }), - msgs::DecodeError::UnknownRequiredFeature => { - log_debug!(self.logger, "Got a channel/node announcement with an known required feature flag, you may want to update!"); - continue; - } - msgs::DecodeError::InvalidValue => { - log_debug!(self.logger, "Got an invalid value while deserializing message"); - return Err(PeerHandleError { no_connection_possible: false }); - } - msgs::DecodeError::ShortRead => { - log_debug!(self.logger, "Deserialization failed due to shortness of message"); - return Err(PeerHandleError { no_connection_possible: false }); + // Using Iterators that can error requires special handling + // The item returned from next() has type Option, String>> + // The Some wrapper is stripped for each item inside the loop + // There are 3 valid match cases: + // 1) Some(Ok(Some(msg_data))) => Indicates a valid decrypted msg accessed via msg_data + // 2) Some(Err(_)) => Indicates an error during decryption that should be handled + // 3) None -> Indicates there were no messages available to decrypt + // Invalid Cases + // 1) Some(Ok(None)) => Translated to None case above so users of iterators can stop correctly + for msg_data_result in &mut conduit.decryptor { + match msg_data_result { + Ok(Some(msg_data)) => { + let mut reader = ::std::io::Cursor::new(&msg_data[..]); + let message_result = wire::read(&mut reader); + let message = match message_result { + Ok(x) => x, + Err(e) => { + match e { + msgs::DecodeError::UnknownVersion => return Err(PeerHandleError { no_connection_possible: false }), + msgs::DecodeError::UnknownRequiredFeature => { + log_debug!(self.logger, "Got a channel/node announcement with an known required feature flag, you may want to update!"); + continue; + } + msgs::DecodeError::InvalidValue => { + log_debug!(self.logger, "Got an invalid value while deserializing message"); + return Err(PeerHandleError { no_connection_possible: false }); + } + msgs::DecodeError::ShortRead => { + log_debug!(self.logger, "Deserialization failed due to shortness of message"); + return Err(PeerHandleError { no_connection_possible: false }); + } + msgs::DecodeError::BadLengthDescriptor => return Err(PeerHandleError { no_connection_possible: false }), + msgs::DecodeError::Io(_) => return Err(PeerHandleError { no_connection_possible: false }), + } } - msgs::DecodeError::BadLengthDescriptor => return Err(PeerHandleError { no_connection_possible: false }), - msgs::DecodeError::Io(_) => return Err(PeerHandleError { no_connection_possible: false }), - } - } - }; + }; - received_messages.push(message); + received_messages.push(message); + }, + Err(e) => { + log_trace!(self.logger, "Message decryption failed due to: {}", e); + return Err(PeerHandleError { no_connection_possible: false }); + } + Ok(None) => { + panic!("Invalid behavior. Conduit iterator should never return this match.") + } + } } } From 3a43c218597fb80ceb78bfb7477bc6ae8bb4bfc6 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Mon, 24 Aug 2020 14:11:52 -0700 Subject: [PATCH 36/45] test: Update handshake fuzz test to cover more cases Enhanced the fuzz testing to simulate more test cases: * Valid or random act data * Optionally sending garbage data along with act * Optionally sending act data in multiple segments Found regression introduced in eb6a371 relating to the Conduit panicking during an hmac failure. --- fuzz/src/peer_crypt.rs | 336 ++++++++++++++++++++++++++++++++--------- 1 file changed, 265 insertions(+), 71 deletions(-) diff --git a/fuzz/src/peer_crypt.rs b/fuzz/src/peer_crypt.rs index 6cbfc274919..572856a1d83 100644 --- a/fuzz/src/peer_crypt.rs +++ b/fuzz/src/peer_crypt.rs @@ -7,6 +7,9 @@ // You may not use this file except in accordance with one or both of these // licenses. +use std::cmp; +use std::{error, fmt}; + use bitcoin::secp256k1; use bitcoin::secp256k1::key::{PublicKey,SecretKey}; @@ -23,39 +26,56 @@ pub struct FuzzGen<'a> { } impl<'a> FuzzGen<'a> { - pub fn new(data: &'a [u8]) -> Self { + fn new(data: &'a [u8]) -> Self { Self { read_pos: 0, data } } - pub fn generate_bytes(&mut self, num: usize) -> Result<&'a [u8], String> { + fn generate_bytes(&mut self, num: usize) -> Result<&'a [u8], GeneratorFinishedError> { if self.data.len() < self.read_pos + num { - return Err("out of bytes".to_string()); + return Err(GeneratorFinishedError { }); } self.read_pos += num; Ok(&self.data[self.read_pos - num..self.read_pos]) } - pub fn generate_secret_key(&mut self) -> Result { + fn generate_secret_key(&mut self) -> Result { // Loop through the input 32 bytes at a time until a valid // secret key can be created or we run out of bytes loop { match SecretKey::from_slice(self.generate_bytes(32)?) { Ok(key) => { return Ok(key) }, - _ => {} + _ => { } } } } - pub fn generate_bool(&mut self) -> Result { + fn generate_bool(&mut self) -> Result { let next_byte = self.generate_bytes(1)?; Ok(next_byte[0] & 1 == 1) } } +#[derive(PartialEq)] +struct GeneratorFinishedError { } + +impl fmt::Debug for GeneratorFinishedError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + formatter.write_str("Generator out of bytes") + } +} +impl fmt::Display for GeneratorFinishedError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + formatter.write_str("Generator out of bytes") + } +} +impl error::Error for GeneratorFinishedError { + fn description(&self) -> &str { "Generator out of bytes" } +} + struct TestCtx { initiator_static_public_key: PublicKey, initiator_handshake: PeerHandshake, @@ -65,7 +85,7 @@ struct TestCtx { } impl TestCtx { - fn make(generator: &mut FuzzGen) -> Result { + fn make(generator: &mut FuzzGen) -> Result { let curve = secp256k1::Secp256k1::new(); // Generate needed keys for successful handshake @@ -92,7 +112,7 @@ impl TestCtx { // Common test function that sends encrypted messages between two conduits until the source data // runs out. #[inline] -fn do_conduit_tests(generator: &mut FuzzGen, initiator_conduit: &mut Conduit, responder_conduit: &mut Conduit) -> Result<(), String> { +fn do_conduit_tests(generator: &mut FuzzGen, initiator_conduit: &mut Conduit, responder_conduit: &mut Conduit, failures_expected: bool) -> Result<(), GeneratorFinishedError> { // Keep sending messages back and forth until the input data is consumed loop { // Randomly generate message length @@ -105,20 +125,30 @@ fn do_conduit_tests(generator: &mut FuzzGen, initiator_conduit: &mut Conduit, re // randomly choose sender of message let receiver_unencrypted_msg = if generator.generate_bool()? { let encrypted_msg = initiator_conduit.encrypt(sender_unencrypted_msg); - responder_conduit.decrypt_single_message(Some(&encrypted_msg)) + if let Ok(msg) = responder_conduit.decrypt_single_message(Some(&encrypted_msg)) { + msg + } else { + assert!(failures_expected); + return Ok(()); + } } else { let encrypted_msg = responder_conduit.encrypt(sender_unencrypted_msg); - initiator_conduit.decrypt_single_message(Some(&encrypted_msg)) + if let Ok(msg) = initiator_conduit.decrypt_single_message(Some(&encrypted_msg)) { + msg + } else { + assert!(failures_expected); + return Ok(()); + } }; - assert_eq!(sender_unencrypted_msg, receiver_unencrypted_msg.unwrap().unwrap().as_slice()); + assert_eq!(sender_unencrypted_msg, receiver_unencrypted_msg.unwrap().as_slice()); } } // This test completes a valid handshake based on "random" private keys and then sends variable // length encrypted messages between two conduits to validate that they can communicate. #[inline] -fn do_completed_handshake_test(generator: &mut FuzzGen) -> Result<(), String> { +fn do_completed_handshake_test(generator: &mut FuzzGen) -> Result<(), GeneratorFinishedError> { let mut test_ctx = TestCtx::make(generator)?; // The handshake should complete with any valid private keys @@ -141,10 +171,37 @@ fn do_completed_handshake_test(generator: &mut FuzzGen) -> Result<(), String> { assert_eq!(initiator_pubkey, test_ctx.initiator_static_public_key); assert_eq!(responder_pubkey, test_ctx.responder_static_public_key); - // The nodes should be able to communicate over the conduit - do_conduit_tests(generator, &mut initiator_conduit, &mut responder_conduit)?; + // The nodes should be able to communicate over the conduit. + do_conduit_tests(generator, &mut initiator_conduit, &mut responder_conduit, false) +} - unreachable!(); +// Either returns (act, false) or (random_bytes, true) where random_bytes is the same len as act +fn maybe_generate_bad_act(generator: &mut FuzzGen, act: Vec) -> Result<(Vec, bool), GeneratorFinishedError> { + if generator.generate_bool()? { + Ok(((generator.generate_bytes(act.len())?).to_vec(), true)) + } else { + Ok((act, false)) + } +} + +// Add between 0..15 bytes of garbage to a vec and returns whether or not it added bytes +fn maybe_add_garbage(generator: &mut FuzzGen, input: &mut Vec) -> Result { + let garbage_amount = (generator.generate_bytes(1)?)[0] >> 4; + + if garbage_amount != 0 { + input.extend(generator.generate_bytes(garbage_amount as usize)?); + Ok(true) + } else { + Ok(false) + } +} + +// Splits a Vec into between 1..7 chunks returning a Vec of slices into the original data +fn split_vec<'a>(generator: &mut FuzzGen, input: &'a Vec) -> Result, GeneratorFinishedError> { + let num_chunks = cmp::max(1, ((generator.generate_bytes(1)?)[0] as u8) >> 5) as usize; + let chunk_size = input.len() / num_chunks; + + Ok(input.chunks(chunk_size).collect()) } // This test variant goes through the peer handshake between the initiator and responder with @@ -152,62 +209,108 @@ fn do_completed_handshake_test(generator: &mut FuzzGen) -> Result<(), String> { // In the event of an error from process_act() we validate that the input data was generated // randomly to ensure real act generation still produces valid transitions. #[inline] -fn do_handshake_test(generator: &mut FuzzGen) -> Result<(), String> { +fn do_handshake_test(generator: &mut FuzzGen) -> Result<(), GeneratorFinishedError> { let mut test_ctx = TestCtx::make(generator)?; - let mut used_generated_data = false; - // Possibly generate bad data for act1 and ensure that the responder does not panic - let mut act1 = test_ctx.act1; - if generator.generate_bool()? { - act1 = (generator.generate_bytes(50)?).to_vec(); - used_generated_data = true; + // Possibly generate bad data for act1 + let (mut act1, mut used_generated_data) = maybe_generate_bad_act(generator, test_ctx.act1)?; + + // Optionally, add garbage data to the end + used_generated_data |= maybe_add_garbage(generator, &mut act1)?; + + // Split act1 into between 1..7 chunks + let act1_chunks = split_vec(generator, &act1)?; + + let mut act2_option = None; + for partial_act1 in act1_chunks { + match test_ctx.responder_handshake.process_act(&partial_act1) { + // Save valid act2 for initiator + Ok((Some(act2_option_inner), None)) => { + act2_option = Some(act2_option_inner); + }, + // Partial act + Ok((None, None)) => { } + // errors are fine as long as they happened due to using bad data + Err(_) => { + assert!(used_generated_data); + return Ok(()); + } + _ => panic!("responder required to output act bytes and no conduit/pubkey") + }; } + let act2 = act2_option.unwrap(); - let mut act2 = match test_ctx.responder_handshake.process_act(&act1) { - Ok((Some(act2), None)) => { - act2 - } - Err(_) => { - assert!(used_generated_data); - return Err("generated expected failure with bad data".to_string()); - } - _ => panic!("responder required to output act bytes and no conduit/pubkey") - }; + // Possibly generate bad data for act2 + let (mut act2, is_bad_data) = maybe_generate_bad_act(generator, act2)?; - // Possibly generate bad data for act2 and ensure that the initiator does not panic - if generator.generate_bool()? { - act2 = (generator.generate_bytes(50)?).to_vec(); - used_generated_data = true; - } + // Optionally, add garbage data to the end + let did_add_garbage = maybe_add_garbage(generator, &mut act2)?; - let (mut act3, (mut initiator_conduit, responder_pubkey)) = match test_ctx.initiator_handshake.process_act(&act2) { - Ok((Some(act3), Some((conduit, remote_pubkey)))) => { - (act3, (conduit, remote_pubkey)) - } - Err(_) => { - assert!(used_generated_data); - return Err("generated expected failure with bad data".to_string()); - } - _ => panic!("initiator required to output act bytes and no conduit/pubkey") - }; + // Keep track of whether or not we have generated bad data up to this point + used_generated_data |= is_bad_data | did_add_garbage; - // Possibly generate bad data for act3 and ensure that the responder does not panic - if generator.generate_bool()? { - act3 = (generator.generate_bytes(66)?).to_vec(); - used_generated_data = true; + // Split act2 into between 1..7 chunks + let act2_chunks = split_vec(generator, &act2)?; + + let mut act3_option = None; + let mut conduit_and_remote_pubkey_option = None; + for partial_act2 in act2_chunks { + match test_ctx.initiator_handshake.process_act(&partial_act2) { + Ok((Some(act3), Some(conduit_and_remote_pubkey_option_inner))) => { + act3_option = Some(act3); + conduit_and_remote_pubkey_option = Some(conduit_and_remote_pubkey_option_inner); + + // Valid conduit and pubkey indicates handshake is over + break; + } + // Partial act + Ok((None, None)) => { }, + // errors are fine as long as they happened due to using bad data + Err(_) => { + assert!(used_generated_data); + return Ok(()); + } + _ => panic!("initiator required to output act bytes, conduit, and pubkey") + }; } - let (mut responder_conduit, initiator_pubkey) = match test_ctx.responder_handshake.process_act(&act3) { - Ok((None, Some((conduit, remote_pubkey)))) => { - (conduit, remote_pubkey) - } - Err(_) => { - // extremely unlikely we randomly generate a correct act3, but if so.. reset this - assert!(used_generated_data); - return Err("generated expected failure with bad data".to_string()); - }, - _ => panic!("responder required to output conduit/remote pubkey and no act bytes") - }; + // Ensure we actually received act3 bytes, conduit, and remote pubkey from process_act() + let act3 = act3_option.unwrap(); + let (mut initiator_conduit, responder_pubkey) = conduit_and_remote_pubkey_option.unwrap(); + + // Possibly generate bad data for act3 + let (mut act3, is_bad_data) = maybe_generate_bad_act(generator, act3)?; + + // Optionally, add garbage data to the end + let did_add_garbage = maybe_add_garbage(generator, &mut act3)?; + + // Keep track of whether or not we have generated bad data up to this point + used_generated_data |= is_bad_data | did_add_garbage; + + // Split act3 into between 1..7 chunks + let act3_chunks = split_vec(generator, &act3)?; + + let mut conduit_and_remote_pubkey_option = None; + for partial_act3 in act3_chunks { + match test_ctx.responder_handshake.process_act(&partial_act3) { + Ok((None, Some(conduit_and_remote_pubkey_option_inner))) => { + conduit_and_remote_pubkey_option = Some(conduit_and_remote_pubkey_option_inner); + + // Valid conduit and pubkey indicates handshake is over + break; + }, + // partial act + Ok((None, None)) => { }, + // errors are fine as long as they happened due to using bad data + Err(_) => { + assert!(used_generated_data); + return Ok(()); + }, + _ => panic!("responder required to output conduit, and pubkey") + }; + } + // Ensure we actually received conduit and remote pubkey from process_act() + let (mut responder_conduit, initiator_pubkey) = conduit_and_remote_pubkey_option.unwrap(); // The handshake should complete with each peer knowing the static_public_key of the remote peer if initiator_pubkey != test_ctx.initiator_static_public_key { @@ -220,9 +323,7 @@ fn do_handshake_test(generator: &mut FuzzGen) -> Result<(), String> { } // The nodes should be able to communicate over the conduit - do_conduit_tests(generator, &mut initiator_conduit, &mut responder_conduit)?; - - unreachable!(); + do_conduit_tests(generator, &mut initiator_conduit, &mut responder_conduit, used_generated_data) } #[inline] @@ -235,13 +336,19 @@ fn do_test(data: &[u8]) { _ => { return } }; + // The only valid error that can leak here is the FuzzGen error to indicate + // the input bytes have been exhausted and the test can't proceed. Everything + // else should be caught and handled by the individual tests to validate any + // errors. if do_valid_handshake { match do_completed_handshake_test(&mut generator) { - _ => {} + Err(_) => { } + _ => { } } } else { match do_handshake_test(&mut generator) { - _ => {} + Err(_) => { } + _ => { } } } } @@ -262,7 +369,7 @@ mod test { #[test] fn data_generator_empty() { let mut generator = FuzzGen::new(&[]); - assert_eq!(generator.generate_bool().err(), Some("out of bytes".to_string())); + assert_eq!(generator.generate_bool(), Err(GeneratorFinishedError { })); } #[test] @@ -281,13 +388,13 @@ mod test { fn data_generator_bool_then_error() { let mut generator = FuzzGen::new(&[1]); assert!(generator.generate_bool().unwrap()); - assert_eq!(generator.generate_bool().err(), Some("out of bytes".to_string())); + assert_eq!(generator.generate_bool(), Err(GeneratorFinishedError { })); } #[test] fn data_generator_bytes_too_many() { let mut generator = FuzzGen::new(&[1, 2, 3, 4]); - assert_eq!(generator.generate_bytes(5).err(), Some("out of bytes".to_string())); + assert_eq!(generator.generate_bytes(5), Err(GeneratorFinishedError { })); } #[test] @@ -306,6 +413,93 @@ mod test { assert_eq!(result, &input[..2]); let result = generator.generate_bytes(2).unwrap(); assert_eq!(result, &input[2..]); - assert_eq!(generator.generate_bytes(1).err(), Some("out of bytes".to_string())); + assert_eq!(generator.generate_bytes(1), Err(GeneratorFinishedError { })); + } + + #[test] + fn maybe_generate_bad_act_gen_bad() { + // 1 is used to take bad branch and 2 is used to generate bad act + let input = [1, 2]; + let mut generator = FuzzGen::new(&input); + + let original_act = &[5]; + + let (act, is_bad) = maybe_generate_bad_act(&mut generator, original_act.to_vec()).unwrap(); + assert!(is_bad); + assert_eq!(act, &[2]); + } + + #[test] + fn maybe_generate_bad_act_gen_good() { + // 0 is used to take good branch + let input = [0]; + let mut generator = FuzzGen::new(&input); + let original_act = &[5]; + + let (act, is_bad) = maybe_generate_bad_act(&mut generator, original_act.to_vec()).unwrap(); + assert!(!is_bad); + assert_eq!(act, &[5]); + } + + #[test] + fn maybe_add_garbage_did_add() { + // 0x10 consumed to specify amount of garbage (1 byte) and 2 is consumed to add garbage + let input = [0x10, 2]; + let mut generator = FuzzGen::new(&input); + let mut act = vec![5]; + + let did_add_garbage = maybe_add_garbage(&mut generator, &mut act).unwrap(); + assert!(did_add_garbage); + assert_eq!(act, &[5, 2]); + } + + #[test] + fn maybe_add_garbage_no_add() { + // 0x10 consumed to specify amount of garbage (1 byte) and 2 is consumed to add garbage + let input = [0]; + let mut generator = FuzzGen::new(&input); + let mut act = vec![5]; + + let did_add_garbage = maybe_add_garbage(&mut generator, &mut act).unwrap(); + assert!(!did_add_garbage); + assert_eq!(act, &[5]); + } + + #[test] + fn split_vec_1_chunk() { + // 0 consumed for number of chunks (1 is min) + let input = [0]; + let mut generator = FuzzGen::new(&input); + let act = vec![5, 6]; + + let act_parts = split_vec(&mut generator, &act).unwrap(); + assert_eq!(act_parts.len(), 1); + assert_eq!(act_parts[0], &[5, 6]); + } + + #[test] + fn split_vec_2_chunks() { + // 40 consumed for number of chunks. Chunk size is equal to the high three bits (2) + let input = [0x40]; + let mut generator = FuzzGen::new(&input); + let act = vec![5, 6]; + + let act_parts = split_vec(&mut generator, &act).unwrap(); + assert_eq!(act_parts.len(), 2); + assert_eq!(act_parts[0], &[5]); + assert_eq!(act_parts[1], &[6]); + } + #[test] + fn split_vec_2_chunks_odd() { + // 40 consumed for number of chunks. Chunk size is equal to the high three bits (2) + let input = [0x40]; + let mut generator = FuzzGen::new(&input); + let act = vec![5, 6, 7, 8, 9]; + + let act_parts = split_vec(&mut generator, &act).unwrap(); + assert_eq!(act_parts.len(), 3); + assert_eq!(act_parts[0], &[5, 6]); + assert_eq!(act_parts[1], &[7, 8]); + assert_eq!(act_parts[2], &[9]); } } \ No newline at end of file From ec5803c9d27816e7a7b05b2d1425fcba0d590f54 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Mon, 24 Aug 2020 17:35:00 -0700 Subject: [PATCH 37/45] review: Split outbound initialization into new()/set_up() To avoid tuple returns from a single initialization function, create a setup_outbound() function that is only callable by the outbound PeerHandshake, but doesn't leak implementation details. --- fuzz/src/peer_crypt.rs | 3 +- lightning/src/ln/peers/handler.rs | 3 +- lightning/src/ln/peers/handshake/mod.rs | 61 +++++++++++++++++++++---- 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/fuzz/src/peer_crypt.rs b/fuzz/src/peer_crypt.rs index 572856a1d83..d21c8009898 100644 --- a/fuzz/src/peer_crypt.rs +++ b/fuzz/src/peer_crypt.rs @@ -96,7 +96,8 @@ impl TestCtx { let responder_static_public_key = PublicKey::from_secret_key(&curve, &responder_static_private_key); let responder_ephemeral_private_key = generator.generate_secret_key()?; - let (act1, initiator_handshake) = PeerHandshake::create_and_initialize_outbound(&initiator_static_private_key, &responder_static_public_key, &initiator_ephemeral_private_key); + let mut initiator_handshake = PeerHandshake::new_outbound(&initiator_static_private_key, &responder_static_public_key, &initiator_ephemeral_private_key); + let act1 = initiator_handshake.set_up_outbound(); let responder_handshake = PeerHandshake::new_inbound(&responder_static_private_key, &responder_ephemeral_private_key); Ok(TestCtx { diff --git a/lightning/src/ln/peers/handler.rs b/lightning/src/ln/peers/handler.rs index 91de3d6920d..64c433e0624 100644 --- a/lightning/src/ln/peers/handler.rs +++ b/lightning/src/ln/peers/handler.rs @@ -365,7 +365,8 @@ impl PeerManager Result, PeerHandleError> { - let (initial_bytes, handshake) = PeerHandshake::create_and_initialize_outbound(&self.our_node_secret, &their_node_id, &self.get_ephemeral_key()); + let mut handshake = PeerHandshake::new_outbound(&self.our_node_secret, &their_node_id, &self.get_ephemeral_key()); + let initial_bytes = handshake.set_up_outbound(); let mut peers = self.peers.lock().unwrap(); if peers.peers.insert(descriptor, Peer { diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs index 3bf47ebb60a..571a912674c 100644 --- a/lightning/src/ln/peers/handshake/mod.rs +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -13,25 +13,42 @@ mod states; /// Currently requires explicit ephemeral private key specification. pub struct PeerHandshake { state: Option, + ready_for_process: bool } impl PeerHandshake { /// Instantiate a new handshake with a node identity secret key and an ephemeral private key - pub fn create_and_initialize_outbound(initiator_static_private_key: &SecretKey, responder_static_public_key: &PublicKey, initiator_ephemeral_private_key: &SecretKey) -> (Vec, Self) { + pub fn new_outbound(initiator_static_private_key: &SecretKey, responder_static_public_key: &PublicKey, initiator_ephemeral_private_key: &SecretKey) -> Self { let state = HandshakeState::new_initiator(initiator_static_private_key, responder_static_public_key, initiator_ephemeral_private_key); - // This transition does not have a failure path - let (response_vec_opt, next_state) = state.next(&[]).unwrap(); - (response_vec_opt.unwrap(), Self { state: Some(next_state) }) + Self { + state: Some(state), + ready_for_process: false + } } /// Instantiate a new handshake in anticipation of a peer's first handshake act pub fn new_inbound(responder_static_private_key: &SecretKey, responder_ephemeral_private_key: &SecretKey) -> Self { Self { - state: Some(HandshakeState::new_responder(responder_static_private_key, responder_ephemeral_private_key)) + state: Some(HandshakeState::new_responder(responder_static_private_key, responder_ephemeral_private_key)), + ready_for_process: true } } + /// Initializes the outbound handshake and provides the initial bytes to send to the responder + pub fn set_up_outbound(&mut self) -> Vec { + assert!(!self.ready_for_process); + let cur_state = self.state.take().unwrap(); + + // This transition does not have a failure path + let (response_vec_opt, next_state) = cur_state.next(&[]).unwrap(); + + self.state = Some(next_state); + + self.ready_for_process = true; + response_vec_opt.unwrap() + } + /// Process act dynamically /// # Arguments /// `input`: Byte slice received from peer as part of the handshake protocol @@ -41,6 +58,7 @@ impl PeerHandshake { /// `.0`: Byte vector containing the next act to send back to the peer per the handshake protocol /// `.1`: Some(Conduit, PublicKey) if the handshake was just processed to completion and messages can now be encrypted and decrypted pub fn process_act(&mut self, input: &[u8]) -> Result<(Option>, Option<(Conduit, PublicKey)>), String> { + assert!(self.ready_for_process); let cur_state = self.state.take().unwrap(); let (act_opt, mut next_state) = cur_state.next(input)?; @@ -85,7 +103,8 @@ mod test { let inbound_static_public_key = PublicKey::from_secret_key(&curve, &inbound_static_private_key); let inbound_ephemeral_private_key = SecretKey::from_slice(&[0x_22_u8; 32]).unwrap(); - let (act1, outbound_handshake) = PeerHandshake::create_and_initialize_outbound(&outbound_static_private_key, &inbound_static_public_key, &outbound_ephemeral_private_key); + let mut outbound_handshake= PeerHandshake::new_outbound(&outbound_static_private_key, &inbound_static_public_key, &outbound_ephemeral_private_key); + let act1 = outbound_handshake.set_up_outbound(); let inbound_handshake = PeerHandshake::new_inbound(&inbound_static_private_key, &inbound_ephemeral_private_key); TestCtx { @@ -113,9 +132,35 @@ mod test { } } + // Test that the outbound needs to call set_up_outbound() before process_act() + #[test] + #[should_panic(expected = "assertion failed: self.ready_for_process")] + fn new_outbound_no_set_up_panics() { + let curve = secp256k1::Secp256k1::new(); + + let outbound_static_private_key = SecretKey::from_slice(&[0x_11_u8; 32]).unwrap(); + let outbound_ephemeral_private_key = SecretKey::from_slice(&[0x_12_u8; 32]).unwrap(); + let inbound_static_private_key = SecretKey::from_slice(&[0x_21_u8; 32]).unwrap(); + let inbound_static_public_key = PublicKey::from_secret_key(&curve, &inbound_static_private_key); + + let mut outbound_handshake= PeerHandshake::new_outbound(&outbound_static_private_key, &inbound_static_public_key, &outbound_ephemeral_private_key); + outbound_handshake.process_act(&[]).unwrap(); + } + + // Test that calling set_up_outbound() on the inbound panics + #[test] + #[should_panic(expected = "assertion failed: !self.ready_for_process")] + fn new_inbound_calling_set_up_panics() { + let inbound_static_private_key = SecretKey::from_slice(&[0x_21_u8; 32]).unwrap(); + let inbound_ephemeral_private_key = SecretKey::from_slice(&[0x_22_u8; 32]).unwrap(); + + let mut inbound_handshake = PeerHandshake::new_inbound(&inbound_static_private_key, &inbound_ephemeral_private_key); + inbound_handshake.set_up_outbound(); + } + // Default Outbound::Uninitiated #[test] - fn peer_handshake_new_outbound() { + fn new_outbound() { let test_ctx = TestCtx::new(); assert_matches!(test_ctx.outbound_handshake.state, Some(HandshakeState::InitiatorAwaitingActTwo(_))); @@ -123,7 +168,7 @@ mod test { // Default Inbound::AwaitingActOne #[test] - fn peer_handshake_new_inbound() { + fn new_inbound() { let test_ctx = TestCtx::new(); assert_matches!(test_ctx.inbound_handshake.state, Some(HandshakeState::ResponderAwaitingActOne(_))); From 1189e845d8be5ba765e4086bd4a2539525aacda0 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 26 Aug 2020 11:32:42 -0700 Subject: [PATCH 38/45] feature: Tighten the constraints for act message sizes Proper handshake peers won't send more bytes than required for act1 and act2 and it is impossible to generate future acts without first receiving an interleaved response. In those cases, the peer is misbehaving and we can fail early. This unlocks a Vec-less design in the handshake code now that there is an upper limit on read buffer sizes. --- lightning/src/ln/peers/handshake/states.rs | 66 ++++++++++------------ 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 8aba9fe20c1..7eca40f68c8 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -181,6 +181,12 @@ impl IHandshakeState for ResponderAwaitingActOneState { let mut read_buffer = self.read_buffer; read_buffer.extend_from_slice(input); + // Any payload larger than ACT_ONE_TWO_LENGTH indicates a bad peer since initiator data + // is required to generate act3 (so it can't come before we transition) + if read_buffer.len() > ACT_ONE_TWO_LENGTH { + return Err("Act One too large".to_string()); + } + // In the event of a partial fill, stay in the same state and wait for more data if read_buffer.len() < ACT_ONE_TWO_LENGTH { return Ok(( @@ -238,6 +244,12 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { let mut read_buffer = self.read_buffer; read_buffer.extend_from_slice(input); + // Any payload larger than ACT_ONE_TWO_LENGTH indicates a bad peer since responder data + // is required to generate post-authentication messages (so it can't come before we transition) + if read_buffer.len() > ACT_ONE_TWO_LENGTH { + return Err("Act Two too large".to_string()); + } + // In the event of a partial fill, stay in the same state and wait for more data if read_buffer.len() < ACT_ONE_TWO_LENGTH { return Ok(( @@ -289,7 +301,7 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { // 7. rn = 0, sn = 0 // - done by Conduit - let mut conduit = Conduit::new(sending_key, receiving_key, chaining_key); + let conduit = Conduit::new(sending_key, receiving_key, chaining_key); // Send m = 0 || c || t over the network buffer let mut act_three = Vec::with_capacity(ACT_THREE_LENGTH); @@ -298,14 +310,6 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { act_three.extend(&authentication_tag); assert_eq!(act_three.len(), ACT_THREE_LENGTH); - // Any remaining data in the read buffer would be encrypted, so transfer ownership - // to the Conduit for future use. In this case, it is unlikely that any valid data - // exists, since the responder doesn't have Act3 - if read_buffer.len() > 0 { - conduit.read(&read_buffer[..]); - read_buffer.drain(..); - } - Ok(( Some(act_three), HandshakeState::Complete(Some((conduit, responder_static_public_key))) @@ -600,18 +604,16 @@ mod test { assert_matches!(awaiting_act_three_state, ResponderAwaitingActThree(_)); } - // Responder::AwaitingActOne -> AwaitingActThree - // TODO: Should this fail since we don't expect data > ACT_ONE_TWO_LENGTH and likely indicates - // a bad peer? + // Responder::AwaitingActOne -> AwaitingActThree (bad peer) + // Act2 requires data from the initiator. If we receive a payload for act1 that is larger than + // expected it indicates a bad peer #[test] fn awaiting_act_one_to_awaiting_act_three_input_extra_bytes() { let test_ctx = TestCtx::new(); let mut act1 = test_ctx.valid_act1; act1.extend_from_slice(&[1]); - let (act2, awaiting_act_three_state) = test_ctx.responder.next(&act1).unwrap(); - assert_eq!(act2.unwrap(), test_ctx.valid_act2); - assert_matches!(awaiting_act_three_state, ResponderAwaitingActThree(_)); + assert_eq!(test_ctx.responder.next(&act1).err(), Some(String::from("Act One too large"))); } // Responder::AwaitingActOne -> AwaitingActThree (segmented calls) @@ -658,36 +660,26 @@ mod test { assert_eq!(test_ctx.responder.next(&act1).err(), Some(String::from("invalid hmac"))); } - // Initiator::AwaitingActTwo -> Complete - // RFC test vector: transport-initiator successful handshake + // Initiator::AwaitingActTwo -> Complete (bad peer) + // Initiator data is required to generate post-authentication messages. This means any extra + // data indicates a bad peer. #[test] - fn awaiting_act_two_to_complete() { + fn awaiting_act_two_to_complete_extra_bytes() { let test_ctx = TestCtx::new(); let (_act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - let (act3, complete_state) = do_next_or_panic!(awaiting_act_two_state, &test_ctx.valid_act2); - - let (conduit, remote_pubkey) = if let Complete(Some((conduit, remote_pubkey))) = complete_state { - (conduit, remote_pubkey) - } else { - panic!(); - }; + let mut act2 = test_ctx.valid_act2; + act2.extend_from_slice(&[1]); - assert_eq!(act3, test_ctx.valid_act3); - assert_eq!(remote_pubkey, test_ctx.responder_static_public_key); - assert_eq!(0, conduit.decryptor.read_buffer_length()); + assert_eq!(awaiting_act_two_state.next(&act2).err(), Some(String::from("Act Two too large"))); } - // Initiator::AwaitingActTwo -> Complete (with extra data) - // Ensures that any remaining data in the read buffer is transferred to the conduit once - // the handshake is complete - // TODO: Is this valid? Don't we expect peers to need ActThree before sending additional data? + // Initiator::AwaitingActTwo -> Complete + // RFC test vector: transport-initiator successful handshake #[test] - fn awaiting_act_two_to_complete_excess_bytes_are_in_conduit() { + fn awaiting_act_two_to_complete() { let test_ctx = TestCtx::new(); let (_act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - let mut act2 = test_ctx.valid_act2; - act2.extend_from_slice(&[1; 100]); - let (act3, complete_state) = do_next_or_panic!(awaiting_act_two_state, &act2); + let (act3, complete_state) = do_next_or_panic!(awaiting_act_two_state, &test_ctx.valid_act2); let (conduit, remote_pubkey) = if let Complete(Some((conduit, remote_pubkey))) = complete_state { (conduit, remote_pubkey) @@ -697,7 +689,7 @@ mod test { assert_eq!(act3, test_ctx.valid_act3); assert_eq!(remote_pubkey, test_ctx.responder_static_public_key); - assert_eq!(100, conduit.decryptor.read_buffer_length()); + assert_eq!(0, conduit.decryptor.read_buffer_length()); } // Initiator::AwaitingActTwo -> Complete (segmented calls) From 229c35fb117e2fb971fbd4960558758ce0408054 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Tue, 25 Aug 2020 17:02:53 -0700 Subject: [PATCH 39/45] perf: Replace Vec w/ static arrays for act buffers After reviewing #494 there was design feedback on the use of Vec vs. arrays in this layer of the code for fragmentation reasons. To get ahead of the same issues in this new state machine and to limit the behavior changes from master, remove Vec from the state machine in favor of thin wrappers around fixed-size arrays. This patch reintroduces the Act object (a thin wrapper around fixed size arrays) with some convenience features to make them a bit easier to pass around and build. The Handshake code is still note Vec-clean as the encryption code uses Vecs during encryption, but it is one step closer to passing back slices to the peer_handler. --- lightning/src/ln/peers/handshake/acts.rs | 171 +++++++++++++++++++++ lightning/src/ln/peers/handshake/mod.rs | 10 +- lightning/src/ln/peers/handshake/states.rs | 117 +++++++------- 3 files changed, 236 insertions(+), 62 deletions(-) create mode 100644 lightning/src/ln/peers/handshake/acts.rs diff --git a/lightning/src/ln/peers/handshake/acts.rs b/lightning/src/ln/peers/handshake/acts.rs new file mode 100644 index 00000000000..81591be6c50 --- /dev/null +++ b/lightning/src/ln/peers/handshake/acts.rs @@ -0,0 +1,171 @@ +//! Helper library for working with NOISE handshake Act data. Contains utilities for passing Act +//! objects around and building them from received data. +//! Act objects are thin wrappers about raw arrays for stack-based processing as well as convenient +//! coercion to slices for flexible use by the higher-level modules. + +use std::{cmp, ops}; + +pub const ACT_ONE_TWO_LENGTH: usize = 50; +pub const ACT_THREE_LENGTH: usize = 66; +pub const EMPTY_ACT_ONE: ActOne = [0; ACT_ONE_TWO_LENGTH]; +pub const EMPTY_ACT_TWO: ActTwo = EMPTY_ACT_ONE; +pub const EMPTY_ACT_THREE: ActThree = [0; ACT_THREE_LENGTH]; +type ActOne = [u8; ACT_ONE_TWO_LENGTH]; +type ActTwo = ActOne; +type ActThree = [u8; ACT_THREE_LENGTH]; + +/// Wrapper for any act message +pub(super) enum Act { + One(ActOne), + Two(ActTwo), + Three(ActThree) +} + +impl Act { + /// Returns the size of the underlying array + fn len(&self) -> usize { + self.as_ref().len() + } +} + +impl From for Act { + /// Convert a finished ActBuilder into an Act + fn from(act_builder: ActBuilder) -> Self { + assert!(act_builder.is_finished()); + act_builder.partial_act + } +} + +impl ops::Deref for Act { + type Target = [u8]; + + /// Allows automatic coercion to slices in function calls + /// &Act -> &[u8] + fn deref(&self) -> &Self::Target { + match self { + &Act::One(ref act) => { + act + } + &Act::Two(ref act) => { + act + } + &Act::Three(ref act) => { + act + } + } + } +} + +impl AsRef<[u8]> for Act { + /// Allow convenient exposure of the underlying array through as_ref() + /// Act.as_ref() -> &[u8] + fn as_ref(&self) -> &[u8] { + &self + } +} + +// Simple fill implementation for both almost-identical structs to deduplicate code +// $act: Act[One|Two|Three], $input: &[u8]; returns &[u8] of remaining input that was not processed +macro_rules! fill_impl { + ($act:expr, $write_pos:expr, $input:expr) => {{ + let fill_amount = cmp::min($act.len() - $write_pos, $input.len()); + + $act[$write_pos..$write_pos + fill_amount].copy_from_slice(&$input[..fill_amount]); + + $write_pos += fill_amount; + &$input[fill_amount..] + }} +} + +/// Light wrapper around an Act that allows multiple fill() calls before finally +/// converting to an Act via Act::from(act_builder). Handles all of the bookkeeping +/// and edge cases of the array fill +pub(super) struct ActBuilder { + partial_act: Act, + write_pos: usize +} + +impl ActBuilder { + /// Returns a new ActBuilder for Act::One + pub(super) fn new(empty_act: Act) -> Self { + Self { + partial_act: empty_act, + write_pos: 0 + } + } + + /// Fills the Act with bytes from input and returns the unprocessed bytes + pub(super) fn fill<'a>(&mut self, input: &'a [u8]) -> &'a [u8] { + match &mut self.partial_act { + &mut Act::One(ref mut act) => { + fill_impl!(act, self.write_pos, input) + } + &mut Act::Two(ref mut act) => { + fill_impl!(act, self.write_pos, input) + } + &mut Act::Three(ref mut act) => { + fill_impl!(act, self.write_pos, input) + } + } + } + + /// Returns true if the Act is finished building (enough bytes via fill()) + pub(super) fn is_finished(&self) -> bool { + self.write_pos == self.partial_act.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Test bookkeeping of partial fill + #[test] + fn partial_fill() { + let mut builder = ActBuilder::new(Act::One(EMPTY_ACT_ONE)); + + let remaining = builder.fill(&[1, 2, 3]); + assert_eq!(builder.partial_act.len(), ACT_ONE_TWO_LENGTH); + assert_eq!(builder.write_pos, 3); + assert!(!builder.is_finished()); + assert_eq!(remaining, &[]); + } + + // Test bookkeeping of exact fill + #[test] + fn exact_fill() { + let mut builder = ActBuilder::new(Act::One(EMPTY_ACT_ONE)); + + let input = [0; 50]; + let remaining = builder.fill(&input); + assert_eq!(builder.partial_act.len(), ACT_ONE_TWO_LENGTH); + assert_eq!(builder.write_pos, ACT_ONE_TWO_LENGTH); + assert!(builder.is_finished()); + assert_eq!(Act::from(builder).as_ref(), &input[..]); + assert_eq!(remaining, &[]); + } + + // Test bookkeeping of overfill + #[test] + fn over_fill() { + let mut builder = ActBuilder::new(Act::One(EMPTY_ACT_ONE)); + + let input = [0; 51]; + let remaining = builder.fill(&input); + + assert_eq!(builder.partial_act.len(), ACT_ONE_TWO_LENGTH); + assert_eq!(builder.write_pos, ACT_ONE_TWO_LENGTH); + assert!(builder.is_finished()); + assert_eq!(Act::from(builder).as_ref(), &input[..50]); + assert_eq!(remaining, &[0]); + } + + // Converting an unfinished ActBuilder panics + #[test] + #[should_panic(expected="as")] + fn convert_not_finished_panics() { + let builder = ActBuilder::new(Act::One(EMPTY_ACT_ONE)); + let _should_panic = Act::from(builder); + } +} + diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs index 571a912674c..7443207c137 100644 --- a/lightning/src/ln/peers/handshake/mod.rs +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -7,6 +7,7 @@ use bitcoin::secp256k1::{PublicKey, SecretKey}; use ln::peers::conduit::Conduit; use ln::peers::handshake::states::{HandshakeState, IHandshakeState}; +mod acts; mod states; /// Object for managing handshakes. @@ -46,7 +47,7 @@ impl PeerHandshake { self.state = Some(next_state); self.ready_for_process = true; - response_vec_opt.unwrap() + response_vec_opt.unwrap().to_vec() } /// Process act dynamically @@ -61,7 +62,12 @@ impl PeerHandshake { assert!(self.ready_for_process); let cur_state = self.state.take().unwrap(); - let (act_opt, mut next_state) = cur_state.next(input)?; + // Convert the Act to a Vec before passing it back. Next patch removes this in favor + // of passing back a slice. + let (act_opt, mut next_state) = match cur_state.next(input)? { + (Some(act), next_state) => (Some(act.to_vec()), next_state), + (None, next_state) => (None, next_state) + }; let result = match next_state { HandshakeState::Complete(ref mut conduit_and_pubkey) => { diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 7eca40f68c8..a2b64928bfc 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -6,9 +6,7 @@ use bitcoin::secp256k1::{SecretKey, PublicKey}; use ln::peers::{chacha, hkdf}; use ln::peers::conduit::{Conduit, SymmetricKey}; - -const ACT_ONE_TWO_LENGTH: usize = 50; -const ACT_THREE_LENGTH: usize = 66; +use ln::peers::handshake::acts::{Act, ActBuilder, EMPTY_ACT_ONE, EMPTY_ACT_TWO, EMPTY_ACT_THREE}; type ChainingKey = [u8; 32]; @@ -34,7 +32,7 @@ pub(super) enum HandshakeState { // Trait for all individual states to implement that ensure HandshakeState::next() can // delegate to a common function signature. pub(super) trait IHandshakeState { - fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState), String>; + fn next(self, input: &[u8]) -> Result<(Option, HandshakeState), String>; } // Enum dispatch for state machine. Single public interface can statically dispatch to all states @@ -48,7 +46,7 @@ impl HandshakeState { } impl IHandshakeState for HandshakeState { - fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState), String> { + fn next(self, input: &[u8]) -> Result<(Option, HandshakeState), String> { match self { HandshakeState::InitiatorStarting(state) => { state.next(input) }, HandshakeState::ResponderAwaitingActOne(state) => { state.next(input) }, @@ -77,7 +75,7 @@ pub(super) struct ResponderAwaitingActOneState { responder_ephemeral_public_key: PublicKey, chaining_key: Sha256, hash: Sha256, - read_buffer: Vec + act_one_builder: ActBuilder } // Handshake state of the Initiator prior to receiving Act 2 @@ -88,7 +86,7 @@ pub(super) struct InitiatorAwaitingActTwoState { responder_static_public_key: PublicKey, chaining_key: ChainingKey, hash: Sha256, - read_buffer: Vec + act_two_builder: ActBuilder } // Handshake state of the Responder prior to receiving Act 3 @@ -97,7 +95,7 @@ pub(super) struct ResponderAwaitingActThreeState { responder_ephemeral_private_key: SecretKey, chaining_key: ChainingKey, temporary_key: [u8; 32], - read_buffer: Vec + act_three_builder: ActBuilder } impl InitiatorStartingState { @@ -119,7 +117,7 @@ impl InitiatorStartingState { impl IHandshakeState for InitiatorStartingState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (sender) - fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState), String> { + fn next(self, input: &[u8]) -> Result<(Option, HandshakeState), String> { if input.len() > 0 { return Err("first call for initiator must be empty".to_string()); @@ -134,16 +132,18 @@ impl IHandshakeState for InitiatorStartingState { let hash = self.hash; // serialize act one - let (act_one, hash, chaining_key, _) = calculate_act_message( + let mut act_one = EMPTY_ACT_ONE; + let (hash, chaining_key, _) = calculate_act_message( &initiator_ephemeral_private_key, &initiator_ephemeral_public_key, &responder_static_public_key, chaining_key.into_inner(), hash, + &mut act_one ); Ok(( - Some(act_one.to_vec()), + Some(Act::One(act_one)), HandshakeState::InitiatorAwaitingActTwo(InitiatorAwaitingActTwoState { initiator_static_private_key, initiator_static_public_key, @@ -151,7 +151,7 @@ impl IHandshakeState for InitiatorStartingState { responder_static_public_key, chaining_key, hash, - read_buffer: Vec::new() + act_two_builder: ActBuilder::new(Act::Two(EMPTY_ACT_TWO)) }) )) } @@ -169,7 +169,7 @@ impl ResponderAwaitingActOneState { responder_ephemeral_public_key, chaining_key, hash, - read_buffer: Vec::new() + act_one_builder: ActBuilder::new(Act::One(EMPTY_ACT_ONE)) } } } @@ -177,18 +177,19 @@ impl ResponderAwaitingActOneState { impl IHandshakeState for ResponderAwaitingActOneState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (receiver) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (sender) - fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState), String> { - let mut read_buffer = self.read_buffer; - read_buffer.extend_from_slice(input); + fn next(self, input: &[u8]) -> Result<(Option, HandshakeState), String> { + let mut act_one_builder = self.act_one_builder; + let remaining = act_one_builder.fill(input); // Any payload larger than ACT_ONE_TWO_LENGTH indicates a bad peer since initiator data // is required to generate act3 (so it can't come before we transition) - if read_buffer.len() > ACT_ONE_TWO_LENGTH { + if remaining.len() != 0 { return Err("Act One too large".to_string()); } // In the event of a partial fill, stay in the same state and wait for more data - if read_buffer.len() < ACT_ONE_TWO_LENGTH { + if !act_one_builder.is_finished() { + assert_eq!(remaining.len(), 0); return Ok(( None, HandshakeState::ResponderAwaitingActOne(Self { @@ -197,41 +198,43 @@ impl IHandshakeState for ResponderAwaitingActOneState { responder_ephemeral_public_key: self.responder_ephemeral_public_key, chaining_key: self.chaining_key, hash: self.hash, - read_buffer + act_one_builder }) )); } - let hash = self.hash; let responder_static_private_key = self.responder_static_private_key; let chaining_key = self.chaining_key; let responder_ephemeral_private_key = self.responder_ephemeral_private_key; let responder_ephemeral_public_key = self.responder_ephemeral_public_key; + let act_one = Act::from(act_one_builder); let (initiator_ephemeral_public_key, hash, chaining_key, _) = process_act_message( - &mut read_buffer, + &act_one, &responder_static_private_key, chaining_key.into_inner(), hash, )?; - let (act_two, hash, chaining_key, temporary_key) = calculate_act_message( + let mut act_two = EMPTY_ACT_TWO; + let (hash, chaining_key, temporary_key) = calculate_act_message( &responder_ephemeral_private_key, &responder_ephemeral_public_key, &initiator_ephemeral_public_key, chaining_key, hash, + &mut act_two ); Ok(( - Some(act_two), + Some(Act::Two(act_two)), HandshakeState::ResponderAwaitingActThree(ResponderAwaitingActThreeState { hash, responder_ephemeral_private_key, chaining_key, temporary_key, - read_buffer + act_three_builder: ActBuilder::new(Act::Three(EMPTY_ACT_THREE)) }) )) } @@ -240,18 +243,19 @@ impl IHandshakeState for ResponderAwaitingActOneState { impl IHandshakeState for InitiatorAwaitingActTwoState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (receiver) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-three (sender) - fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState), String> { - let mut read_buffer = self.read_buffer; - read_buffer.extend_from_slice(input); + fn next(self, input: &[u8]) -> Result<(Option, HandshakeState), String> { + let mut act_two_builder = self.act_two_builder; + let remaining = act_two_builder.fill(input); // Any payload larger than ACT_ONE_TWO_LENGTH indicates a bad peer since responder data // is required to generate post-authentication messages (so it can't come before we transition) - if read_buffer.len() > ACT_ONE_TWO_LENGTH { + if remaining.len() != 0 { return Err("Act Two too large".to_string()); } // In the event of a partial fill, stay in the same state and wait for more data - if read_buffer.len() < ACT_ONE_TWO_LENGTH { + if !act_two_builder.is_finished() { + assert_eq!(remaining.len(), 0); return Ok(( None, HandshakeState::InitiatorAwaitingActTwo(Self { @@ -261,7 +265,7 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { responder_static_public_key: self.responder_static_public_key, chaining_key: self.chaining_key, hash: self.hash, - read_buffer + act_two_builder }) )); } @@ -272,9 +276,10 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { let responder_static_public_key = self.responder_static_public_key; let hash = self.hash; let chaining_key = self.chaining_key; + let act_two = Act::from(act_two_builder); let (responder_ephemeral_public_key, hash, chaining_key, temporary_key) = process_act_message( - &mut read_buffer, + &act_two, &initiator_ephemeral_private_key, chaining_key, hash, @@ -304,14 +309,12 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { let conduit = Conduit::new(sending_key, receiving_key, chaining_key); // Send m = 0 || c || t over the network buffer - let mut act_three = Vec::with_capacity(ACT_THREE_LENGTH); - act_three.extend(&[0]); - act_three.extend(&tagged_encrypted_pubkey); - act_three.extend(&authentication_tag); - assert_eq!(act_three.len(), ACT_THREE_LENGTH); + let mut act_three = EMPTY_ACT_THREE; + act_three[1..50].copy_from_slice(&tagged_encrypted_pubkey); + act_three[50..].copy_from_slice(&authentication_tag); Ok(( - Some(act_three), + Some(Act::Three(act_three)), HandshakeState::Complete(Some((conduit, responder_static_public_key))) )) } @@ -319,12 +322,13 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { impl IHandshakeState for ResponderAwaitingActThreeState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-three (receiver) - fn next(self, input: &[u8]) -> Result<(Option>, HandshakeState), String> { - let mut read_buffer = self.read_buffer; - read_buffer.extend_from_slice(input); + fn next(self, input: &[u8]) -> Result<(Option, HandshakeState), String> { + let mut act_three_builder = self.act_three_builder; + let remaining = act_three_builder.fill(input); // In the event of a partial fill, stay in the same state and wait for more data - if read_buffer.len() < ACT_THREE_LENGTH { + if !act_three_builder.is_finished() { + assert_eq!(remaining.len(), 0); return Ok(( None, HandshakeState::ResponderAwaitingActThree(Self { @@ -332,7 +336,7 @@ impl IHandshakeState for ResponderAwaitingActThreeState { responder_ephemeral_private_key: self.responder_ephemeral_private_key, chaining_key: self.chaining_key, temporary_key: self.temporary_key, - read_buffer + act_three_builder }) )); } @@ -343,7 +347,7 @@ impl IHandshakeState for ResponderAwaitingActThreeState { let chaining_key = self.chaining_key; // 1. Read exactly 66 bytes from the network buffer - let act_three_bytes: Vec = read_buffer.drain(..ACT_THREE_LENGTH).collect(); + let act_three_bytes = Act::from(act_three_builder); // 2. Parse the read message (m) into v, c, and t let version = act_three_bytes[0]; @@ -385,10 +389,7 @@ impl IHandshakeState for ResponderAwaitingActThreeState { // Any remaining data in the read buffer would be encrypted, so transfer ownership // to the Conduit for future use. - if read_buffer.len() > 0 { // have we received more data still? - conduit.read(&read_buffer[..]); - read_buffer.drain(..); - } + conduit.read(remaining); Ok(( None, @@ -417,7 +418,7 @@ fn handshake_state_initialization(responder_static_public_key: &PublicKey) -> (S // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (sender) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (sender) -fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_ephemeral_key: &PublicKey, remote_public_key: &PublicKey, chaining_key: ChainingKey, hash: Sha256) -> (Vec, Sha256, SymmetricKey, SymmetricKey) { +fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_ephemeral_key: &PublicKey, remote_public_key: &PublicKey, chaining_key: ChainingKey, hash: Sha256, act_out: &mut [u8]) -> (Sha256, SymmetricKey, SymmetricKey) { // 1. e = generateKey() (passed in) // 2. h = SHA-256(h || e.pub.serializeCompressed()) let serialized_local_public_key = local_public_ephemeral_key.serialize(); @@ -439,22 +440,18 @@ fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_e let hash = concat_then_sha256!(hash, tagged_ciphertext); // Send m = 0 || e.pub.serializeCompressed() || c - let mut act = Vec::with_capacity(ACT_ONE_TWO_LENGTH); - act.extend(&[0]); - act.extend_from_slice(&serialized_local_public_key); - act.extend(&tagged_ciphertext); - assert_eq!(act.len(), ACT_ONE_TWO_LENGTH); - (act, hash, chaining_key, temporary_key) + act_out[1..34].copy_from_slice(&serialized_local_public_key); + act_out[34..50].copy_from_slice(&tagged_ciphertext); + + (hash, chaining_key, temporary_key) } // Due to the very high similarity of acts 1 and 2, this method is used to process both // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (receiver) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (receiver) -fn process_act_message(read_buffer: &mut Vec, local_private_key: &SecretKey, chaining_key: ChainingKey, hash: Sha256) -> Result<(PublicKey, Sha256, SymmetricKey, SymmetricKey), String> { +fn process_act_message(act_bytes: &[u8], local_private_key: &SecretKey, chaining_key: ChainingKey, hash: Sha256) -> Result<(PublicKey, Sha256, SymmetricKey, SymmetricKey), String> { // 1. Read exactly 50 bytes from the network buffer - let act_bytes: Vec = read_buffer.drain(..ACT_ONE_TWO_LENGTH).collect(); - // 2.Parse the read message (m) into v, re, and c let version = act_bytes[0]; let ephemeral_public_key_bytes = &act_bytes[1..34]; @@ -581,7 +578,7 @@ mod test { let test_ctx = TestCtx::new(); let (act1, awaiting_act_two_state) = do_next_or_panic!(test_ctx.initiator, &[]); - assert_eq!(act1, test_ctx.valid_act1); + assert_eq!(act1.as_ref(), test_ctx.valid_act1.as_slice()); assert_matches!(awaiting_act_two_state, InitiatorAwaitingActTwo(_)); } @@ -600,7 +597,7 @@ mod test { let test_ctx = TestCtx::new(); let (act2, awaiting_act_three_state) = test_ctx.responder.next(&test_ctx.valid_act1).unwrap(); - assert_eq!(act2.unwrap(), test_ctx.valid_act2); + assert_eq!(act2.unwrap().as_ref(), test_ctx.valid_act2.as_slice()); assert_matches!(awaiting_act_three_state, ResponderAwaitingActThree(_)); } @@ -687,7 +684,7 @@ mod test { panic!(); }; - assert_eq!(act3, test_ctx.valid_act3); + assert_eq!(act3.as_ref(), test_ctx.valid_act3.as_slice()); assert_eq!(remote_pubkey, test_ctx.responder_static_public_key); assert_eq!(0, conduit.decryptor.read_buffer_length()); } From 753b51784d5c397db11d7102eb0a32585fe91c12 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 26 Aug 2020 11:12:16 -0700 Subject: [PATCH 40/45] perf: Remove Vec from chacha::encrypt/decrypt In order to limit perf/allocation changes from master, remove the last use of Vec from the handshake code. The decrypt/encrypt code now takes an out parameter to place the decrypted bytes. All handshake users know the fixed-size of the output decryption so they can use arrays. The Conduit code will still allocate a Vec based on the input message and pass it through. After this patch, the handshake code is now Vec-less and copies are minimized. --- lightning/src/ln/peers/chacha.rs | 23 +++++++--------- lightning/src/ln/peers/conduit.rs | 9 +++--- lightning/src/ln/peers/handshake/states.rs | 32 ++++++++++------------ 3 files changed, 29 insertions(+), 35 deletions(-) diff --git a/lightning/src/ln/peers/chacha.rs b/lightning/src/ln/peers/chacha.rs index 4e8d948706f..76742bad452 100644 --- a/lightning/src/ln/peers/chacha.rs +++ b/lightning/src/ln/peers/chacha.rs @@ -3,21 +3,18 @@ use util::chacha20poly1305rfc::ChaCha20Poly1305RFC; pub const TAG_SIZE: usize = 16; -pub fn encrypt(key: &[u8], nonce: u64, associated_data: &[u8], plaintext: &[u8]) -> Vec { +pub fn encrypt(key: &[u8], nonce: u64, associated_data: &[u8], plaintext: &[u8], ciphertext_out: &mut [u8]) { let mut nonce_bytes = [0; 12]; nonce_bytes[4..].copy_from_slice(&byte_utils::le64_to_array(nonce)); let mut chacha = ChaCha20Poly1305RFC::new(key, &nonce_bytes, associated_data); - let mut ciphertext = vec![0u8; plaintext.len()]; let mut authentication_tag = [0u8; 16]; - chacha.encrypt(plaintext, &mut ciphertext, &mut authentication_tag); + chacha.encrypt(plaintext, &mut ciphertext_out[..plaintext.len()], &mut authentication_tag); - let mut tagged_ciphertext = ciphertext; - tagged_ciphertext.extend_from_slice(&authentication_tag); - tagged_ciphertext + ciphertext_out[plaintext.len()..].copy_from_slice(&authentication_tag); } -pub fn decrypt(key: &[u8], nonce: u64, associated_data: &[u8], tagged_ciphertext: &[u8]) -> Result, String> { +pub fn decrypt(key: &[u8], nonce: u64, associated_data: &[u8], tagged_ciphertext: &[u8], plaintext_out: &mut [u8]) -> Result<(), String> { let mut nonce_bytes = [0; 12]; nonce_bytes[4..].copy_from_slice(&byte_utils::le64_to_array(nonce)); @@ -27,14 +24,14 @@ pub fn decrypt(key: &[u8], nonce: u64, associated_data: &[u8], tagged_ciphertext } let end_index = length - 16; let ciphertext = &tagged_ciphertext[0..end_index]; - let authentication_tag = &tagged_ciphertext[end_index..length]; + let authentication_tag = &tagged_ciphertext[end_index..]; let mut chacha = ChaCha20Poly1305RFC::new(key, &nonce_bytes, associated_data); - let mut plaintext = vec![0u8; length - 16]; - let success = chacha.decrypt(ciphertext, &mut plaintext, authentication_tag); - if success { - Ok(plaintext.to_vec()) - } else { + let success = chacha.decrypt(ciphertext, plaintext_out, authentication_tag); + + if !success { Err("invalid hmac".to_string()) + } else { + Ok(()) } } diff --git a/lightning/src/ln/peers/conduit.rs b/lightning/src/ln/peers/conduit.rs index 807a06ddd12..d820d0497b8 100644 --- a/lightning/src/ln/peers/conduit.rs +++ b/lightning/src/ln/peers/conduit.rs @@ -128,10 +128,10 @@ impl Encryptor { let mut ciphertext = vec![0u8; TAGGED_MESSAGE_LENGTH_HEADER_SIZE + length as usize + chacha::TAG_SIZE]; - ciphertext[0..TAGGED_MESSAGE_LENGTH_HEADER_SIZE].copy_from_slice(&chacha::encrypt(&self.sending_key, self.sending_nonce as u64, &[0; 0], &length_bytes)); + chacha::encrypt(&self.sending_key, self.sending_nonce as u64, &[0; 0], &length_bytes, &mut ciphertext[..TAGGED_MESSAGE_LENGTH_HEADER_SIZE]); self.increment_nonce(); - ciphertext[TAGGED_MESSAGE_LENGTH_HEADER_SIZE..].copy_from_slice(&chacha::encrypt(&self.sending_key, self.sending_nonce as u64, &[0; 0], buffer)); + &chacha::encrypt(&self.sending_key, self.sending_nonce as u64, &[0; 0], buffer, &mut ciphertext[TAGGED_MESSAGE_LENGTH_HEADER_SIZE..]); self.increment_nonce(); ciphertext @@ -185,7 +185,7 @@ impl Decryptor { let encrypted_length = &buffer[0..TAGGED_MESSAGE_LENGTH_HEADER_SIZE]; let mut length_bytes = [0u8; MESSAGE_LENGTH_HEADER_SIZE]; - length_bytes.copy_from_slice(&chacha::decrypt(&self.receiving_key, self.receiving_nonce as u64, &[0; 0], encrypted_length)?); + chacha::decrypt(&self.receiving_key, self.receiving_nonce as u64, &[0; 0], encrypted_length, &mut length_bytes)?; self.increment_nonce(); @@ -203,8 +203,9 @@ impl Decryptor { self.pending_message_length = None; let encrypted_message = &buffer[TAGGED_MESSAGE_LENGTH_HEADER_SIZE..message_end_index]; + let mut message = vec![0u8; message_length]; - let message = chacha::decrypt(&self.receiving_key, self.receiving_nonce as u64, &[0; 0], encrypted_message)?; + chacha::decrypt(&self.receiving_key, self.receiving_nonce as u64, &[0; 0], encrypted_message, &mut message)?; self.increment_nonce(); diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index a2b64928bfc..6b0a7be63b9 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -285,12 +285,14 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { hash, )?; + let mut act_three = EMPTY_ACT_THREE; + // start serializing act three // 1. c = encryptWithAD(temp_k2, 1, h, s.pub.serializeCompressed()) - let tagged_encrypted_pubkey = chacha::encrypt(&temporary_key, 1, &hash, &initiator_static_public_key.serialize()); + chacha::encrypt(&temporary_key, 1, &hash, &initiator_static_public_key.serialize(), &mut act_three[1..50]); // 2. h = SHA-256(h || c) - let hash = concat_then_sha256!(hash, tagged_encrypted_pubkey); + let hash = concat_then_sha256!(hash, act_three[1..50]); // 3. se = ECDH(s.priv, re) let ecdh = ecdh(&initiator_static_private_key, &responder_ephemeral_public_key); @@ -299,7 +301,7 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); // 5. t = encryptWithAD(temp_k3, 0, h, zero) - let authentication_tag = chacha::encrypt(&temporary_key, 0, &hash, &[0; 0]); + chacha::encrypt(&temporary_key, 0, &hash, &[0; 0], &mut act_three[50..]); // 6. sk, rk = HKDF(ck, zero) let (sending_key, receiving_key) = hkdf::derive(&chaining_key, &[0; 0]); @@ -308,11 +310,6 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { // - done by Conduit let conduit = Conduit::new(sending_key, receiving_key, chaining_key); - // Send m = 0 || c || t over the network buffer - let mut act_three = EMPTY_ACT_THREE; - act_three[1..50].copy_from_slice(&tagged_encrypted_pubkey); - act_three[50..].copy_from_slice(&authentication_tag); - Ok(( Some(Act::Three(act_three)), HandshakeState::Complete(Some((conduit, responder_static_public_key))) @@ -352,7 +349,7 @@ impl IHandshakeState for ResponderAwaitingActThreeState { // 2. Parse the read message (m) into v, c, and t let version = act_three_bytes[0]; let tagged_encrypted_pubkey = &act_three_bytes[1..50]; - let chacha_tag = &act_three_bytes[50..66]; + let chacha_tag = &act_three_bytes[50..]; // 3. If v is an unrecognized handshake version, then the responder MUST abort the connection attempt. if version != 0 { @@ -361,8 +358,9 @@ impl IHandshakeState for ResponderAwaitingActThreeState { } // 4. rs = decryptWithAD(temp_k2, 1, h, c) - let remote_pubkey_vec = chacha::decrypt(&temporary_key, 1, &hash, &tagged_encrypted_pubkey)?; - let initiator_pubkey = if let Ok(public_key) = PublicKey::from_slice(remote_pubkey_vec.as_slice()) { + let mut remote_pubkey = [0; 33]; + chacha::decrypt(&temporary_key, 1, &hash, &tagged_encrypted_pubkey, &mut remote_pubkey)?; + let initiator_pubkey = if let Ok(public_key) = PublicKey::from_slice(&remote_pubkey) { public_key } else { return Err("invalid remote public key".to_string()); @@ -378,7 +376,7 @@ impl IHandshakeState for ResponderAwaitingActThreeState { let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); // 8. p = decryptWithAD(temp_k3, 0, h, t) - let _tag_check = chacha::decrypt(&temporary_key, 0, &hash, &chacha_tag)?; + chacha::decrypt(&temporary_key, 0, &hash, &chacha_tag, &mut [0; 0])?; // 9. rk, sk = HKDF(ck, zero) let (receiving_key, sending_key) = hkdf::derive(&chaining_key, &[0; 0]); @@ -434,15 +432,13 @@ fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_e // 5. ACT1: c = encryptWithAD(temp_k1, 0, h, zero) // 5. ACT2: c = encryptWithAD(temp_k2, 0, h, zero) - let tagged_ciphertext = chacha::encrypt(&temporary_key, 0, &hash, &[0; 0]); + chacha::encrypt(&temporary_key, 0, &hash, &[0; 0], &mut act_out[34..]); // 6. h = SHA-256(h || c) - let hash = concat_then_sha256!(hash, tagged_ciphertext); + let hash = concat_then_sha256!(hash, &act_out[34..]); // Send m = 0 || e.pub.serializeCompressed() || c - act_out[1..34].copy_from_slice(&serialized_local_public_key); - act_out[34..50].copy_from_slice(&tagged_ciphertext); (hash, chaining_key, temporary_key) } @@ -455,7 +451,7 @@ fn process_act_message(act_bytes: &[u8], local_private_key: &SecretKey, chaining // 2.Parse the read message (m) into v, re, and c let version = act_bytes[0]; let ephemeral_public_key_bytes = &act_bytes[1..34]; - let chacha_tag = &act_bytes[34..50]; + let chacha_tag = &act_bytes[34..]; let ephemeral_public_key = if let Ok(public_key) = PublicKey::from_slice(&ephemeral_public_key_bytes) { public_key @@ -481,7 +477,7 @@ fn process_act_message(act_bytes: &[u8], local_private_key: &SecretKey, chaining let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); // 7. p = decryptWithAD(temp_k1, 0, h, c) - let _tag_check = chacha::decrypt(&temporary_key, 0, &hash, &chacha_tag)?; + chacha::decrypt(&temporary_key, 0, &hash, &chacha_tag, &mut [0; 0])?; // 8. h = SHA-256(h || c) let hash = concat_then_sha256!(hash, chacha_tag); From a7a25ca431f7ac05796d16c378a675a64c92d2c3 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 2 Sep 2020 09:41:15 -0700 Subject: [PATCH 41/45] review: Non-controversial renames, nits, small fixups Clean up small items based on review comments that are expected to be non-controversial so they can all go in one commit. --- fuzz/src/peer_crypt.rs | 15 +++++- lightning/src/ln/peers/handler.rs | 12 ++--- lightning/src/ln/peers/handshake/acts.rs | 55 +++++++++++----------- lightning/src/ln/peers/handshake/mod.rs | 37 +++++++-------- lightning/src/ln/peers/handshake/states.rs | 30 ++++++++---- lightning/src/ln/peers/hkdf.rs | 8 ++-- 6 files changed, 90 insertions(+), 67 deletions(-) diff --git a/fuzz/src/peer_crypt.rs b/fuzz/src/peer_crypt.rs index d21c8009898..7d832122584 100644 --- a/fuzz/src/peer_crypt.rs +++ b/fuzz/src/peer_crypt.rs @@ -85,6 +85,9 @@ struct TestCtx { } impl TestCtx { + // At the completion of this call each handshake has the following state: + // initiator_handshake: HandshakeState::InitiatorStarting + // responder_handshake: HandshakeState::ResponderAwaitingActOne fn make(generator: &mut FuzzGen) -> Result { let curve = secp256k1::Secp256k1::new(); @@ -98,6 +101,14 @@ impl TestCtx { let mut initiator_handshake = PeerHandshake::new_outbound(&initiator_static_private_key, &responder_static_public_key, &initiator_ephemeral_private_key); let act1 = initiator_handshake.set_up_outbound(); + + // Sanity checks around initialization to catch errors before we get to the fuzzing + + // act 1 is the correct size + assert_eq!(act1.len(), 50); + + // act 1 has the correct version + assert_eq!(act1[0], 0); let responder_handshake = PeerHandshake::new_inbound(&responder_static_private_key, &responder_ephemeral_private_key); Ok(TestCtx { @@ -146,8 +157,8 @@ fn do_conduit_tests(generator: &mut FuzzGen, initiator_conduit: &mut Conduit, re } } -// This test completes a valid handshake based on "random" private keys and then sends variable -// length encrypted messages between two conduits to validate that they can communicate. +// This test completes a valid handshake based on fuzzer-provided private keys and then sends +// variable length encrypted messages between two conduits to validate that they can communicate. #[inline] fn do_completed_handshake_test(generator: &mut FuzzGen) -> Result<(), GeneratorFinishedError> { let mut test_ctx = TestCtx::make(generator)?; diff --git a/lightning/src/ln/peers/handler.rs b/lightning/src/ln/peers/handler.rs index 64c433e0624..25ecfcaaadd 100644 --- a/lightning/src/ln/peers/handler.rs +++ b/lightning/src/ln/peers/handler.rs @@ -149,17 +149,17 @@ impl PeerState { }, // Otherwise, handshake may or may not be complete depending on whether or not // we receive a (conduit, pubkey) - Ok((response_vec_opt, conduit_and_remote_pubkey_opt)) => { + Ok((response_vec_option, conduit_and_remote_static_public_key_option)) => { // Any response generated by the handshake sequence is put into the response buffer - if let Some(response_vec) = response_vec_opt { + if let Some(response_vec) = response_vec_option { mutable_response_buffer.push_back(response_vec); } - // if process_act() returns the conduit and remote_pubkey the handshake - // is complete - if let Some((conduit, remote_pubkey)) = conduit_and_remote_pubkey_opt { - (Some(PeerState::Connected(conduit)), PeerDataProcessingDecision::CompleteHandshake(remote_pubkey)) + // if process_act() returns the conduit and remote static public key (node id) + // the handshake is complete + if let Some((conduit, remote_static_public_key)) = conduit_and_remote_static_public_key_option { + (Some(PeerState::Connected(conduit)), PeerDataProcessingDecision::CompleteHandshake(remote_static_public_key)) } else { (None, PeerDataProcessingDecision::Continue) } diff --git a/lightning/src/ln/peers/handshake/acts.rs b/lightning/src/ln/peers/handshake/acts.rs index 81591be6c50..fa7a2ece5a6 100644 --- a/lightning/src/ln/peers/handshake/acts.rs +++ b/lightning/src/ln/peers/handshake/acts.rs @@ -5,13 +5,14 @@ use std::{cmp, ops}; -pub const ACT_ONE_TWO_LENGTH: usize = 50; +pub const ACT_ONE_LENGTH: usize = 50; +pub const ACT_TWO_LENGTH: usize = 50; pub const ACT_THREE_LENGTH: usize = 66; -pub const EMPTY_ACT_ONE: ActOne = [0; ACT_ONE_TWO_LENGTH]; -pub const EMPTY_ACT_TWO: ActTwo = EMPTY_ACT_ONE; +pub const EMPTY_ACT_ONE: ActOne = [0; ACT_ONE_LENGTH]; +pub const EMPTY_ACT_TWO: ActTwo = [0; ACT_TWO_LENGTH]; pub const EMPTY_ACT_THREE: ActThree = [0; ACT_THREE_LENGTH]; -type ActOne = [u8; ACT_ONE_TWO_LENGTH]; -type ActTwo = ActOne; +type ActOne = [u8; ACT_ONE_LENGTH]; +type ActTwo = [u8; ACT_TWO_LENGTH]; type ActThree = [u8; ACT_THREE_LENGTH]; /// Wrapper for any act message @@ -64,19 +65,6 @@ impl AsRef<[u8]> for Act { } } -// Simple fill implementation for both almost-identical structs to deduplicate code -// $act: Act[One|Two|Three], $input: &[u8]; returns &[u8] of remaining input that was not processed -macro_rules! fill_impl { - ($act:expr, $write_pos:expr, $input:expr) => {{ - let fill_amount = cmp::min($act.len() - $write_pos, $input.len()); - - $act[$write_pos..$write_pos + fill_amount].copy_from_slice(&$input[..fill_amount]); - - $write_pos += fill_amount; - &$input[fill_amount..] - }} -} - /// Light wrapper around an Act that allows multiple fill() calls before finally /// converting to an Act via Act::from(act_builder). Handles all of the bookkeeping /// and edge cases of the array fill @@ -96,15 +84,28 @@ impl ActBuilder { /// Fills the Act with bytes from input and returns the unprocessed bytes pub(super) fn fill<'a>(&mut self, input: &'a [u8]) -> &'a [u8] { + // Simple fill implementation for both almost-identical structs to deduplicate code + // $act: Act[One|Two|Three], $input: &[u8]; returns &[u8] of remaining input that was not processed + macro_rules! fill_act_content { + ($act:expr, $write_pos:expr, $input:expr) => {{ + let fill_amount = cmp::min($act.len() - $write_pos, $input.len()); + + $act[$write_pos..$write_pos + fill_amount].copy_from_slice(&$input[..fill_amount]); + + $write_pos += fill_amount; + &$input[fill_amount..] + }} + } + match &mut self.partial_act { &mut Act::One(ref mut act) => { - fill_impl!(act, self.write_pos, input) + fill_act_content!(act, self.write_pos, input) } &mut Act::Two(ref mut act) => { - fill_impl!(act, self.write_pos, input) + fill_act_content!(act, self.write_pos, input) } &mut Act::Three(ref mut act) => { - fill_impl!(act, self.write_pos, input) + fill_act_content!(act, self.write_pos, input) } } } @@ -125,7 +126,7 @@ mod tests { let mut builder = ActBuilder::new(Act::One(EMPTY_ACT_ONE)); let remaining = builder.fill(&[1, 2, 3]); - assert_eq!(builder.partial_act.len(), ACT_ONE_TWO_LENGTH); + assert_eq!(builder.partial_act.len(), ACT_ONE_LENGTH); assert_eq!(builder.write_pos, 3); assert!(!builder.is_finished()); assert_eq!(remaining, &[]); @@ -138,8 +139,8 @@ mod tests { let input = [0; 50]; let remaining = builder.fill(&input); - assert_eq!(builder.partial_act.len(), ACT_ONE_TWO_LENGTH); - assert_eq!(builder.write_pos, ACT_ONE_TWO_LENGTH); + assert_eq!(builder.partial_act.len(), ACT_ONE_LENGTH); + assert_eq!(builder.write_pos, ACT_ONE_LENGTH); assert!(builder.is_finished()); assert_eq!(Act::from(builder).as_ref(), &input[..]); assert_eq!(remaining, &[]); @@ -153,8 +154,8 @@ mod tests { let input = [0; 51]; let remaining = builder.fill(&input); - assert_eq!(builder.partial_act.len(), ACT_ONE_TWO_LENGTH); - assert_eq!(builder.write_pos, ACT_ONE_TWO_LENGTH); + assert_eq!(builder.partial_act.len(), ACT_ONE_LENGTH); + assert_eq!(builder.write_pos, ACT_ONE_LENGTH); assert!(builder.is_finished()); assert_eq!(Act::from(builder).as_ref(), &input[..50]); assert_eq!(remaining, &[0]); @@ -162,7 +163,7 @@ mod tests { // Converting an unfinished ActBuilder panics #[test] - #[should_panic(expected="as")] + #[should_panic(expected="assertion failed: act_builder.is_finished()")] fn convert_not_finished_panics() { let builder = ActBuilder::new(Act::One(EMPTY_ACT_ONE)); let _should_panic = Act::from(builder); diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs index 7443207c137..d914b299518 100644 --- a/lightning/src/ln/peers/handshake/mod.rs +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -14,40 +14,39 @@ mod states; /// Currently requires explicit ephemeral private key specification. pub struct PeerHandshake { state: Option, - ready_for_process: bool + ready_to_process: bool, } impl PeerHandshake { - /// Instantiate a new handshake with a node identity secret key and an ephemeral private key + /// Instantiate a handshake given the peer's static public key. The ephemeral private key MUST + /// generate a new session with strong cryptographic randomness. pub fn new_outbound(initiator_static_private_key: &SecretKey, responder_static_public_key: &PublicKey, initiator_ephemeral_private_key: &SecretKey) -> Self { let state = HandshakeState::new_initiator(initiator_static_private_key, responder_static_public_key, initiator_ephemeral_private_key); Self { state: Some(state), - ready_for_process: false + ready_to_process: false, } } - /// Instantiate a new handshake in anticipation of a peer's first handshake act + /// Instantiate a handshake in anticipation of a peer's first handshake act pub fn new_inbound(responder_static_private_key: &SecretKey, responder_ephemeral_private_key: &SecretKey) -> Self { Self { state: Some(HandshakeState::new_responder(responder_static_private_key, responder_ephemeral_private_key)), - ready_for_process: true + ready_to_process: true, } } /// Initializes the outbound handshake and provides the initial bytes to send to the responder pub fn set_up_outbound(&mut self) -> Vec { - assert!(!self.ready_for_process); - let cur_state = self.state.take().unwrap(); + assert!(!self.ready_to_process); + self.ready_to_process = true; // This transition does not have a failure path - let (response_vec_opt, next_state) = cur_state.next(&[]).unwrap(); - - self.state = Some(next_state); + let (response_vec_option, conduit_and_remote_static_public_key_option) = self.process_act(&[]).unwrap(); + assert!(conduit_and_remote_static_public_key_option.is_none()); - self.ready_for_process = true; - response_vec_opt.unwrap().to_vec() + response_vec_option.unwrap() } /// Process act dynamically @@ -59,21 +58,19 @@ impl PeerHandshake { /// `.0`: Byte vector containing the next act to send back to the peer per the handshake protocol /// `.1`: Some(Conduit, PublicKey) if the handshake was just processed to completion and messages can now be encrypted and decrypted pub fn process_act(&mut self, input: &[u8]) -> Result<(Option>, Option<(Conduit, PublicKey)>), String> { - assert!(self.ready_for_process); + assert!(self.ready_to_process); let cur_state = self.state.take().unwrap(); - // Convert the Act to a Vec before passing it back. Next patch removes this in favor - // of passing back a slice. - let (act_opt, mut next_state) = match cur_state.next(input)? { + let (act_option, mut next_state) = match cur_state.next(input)? { (Some(act), next_state) => (Some(act.to_vec()), next_state), (None, next_state) => (None, next_state) }; let result = match next_state { HandshakeState::Complete(ref mut conduit_and_pubkey) => { - Ok((act_opt, conduit_and_pubkey.take())) + Ok((act_option, conduit_and_pubkey.take())) }, - _ => { Ok((act_opt, None)) } + _ => { Ok((act_option, None)) } }; self.state = Some(next_state); @@ -140,7 +137,7 @@ mod test { // Test that the outbound needs to call set_up_outbound() before process_act() #[test] - #[should_panic(expected = "assertion failed: self.ready_for_process")] + #[should_panic(expected = "assertion failed: self.ready_to_process")] fn new_outbound_no_set_up_panics() { let curve = secp256k1::Secp256k1::new(); @@ -155,7 +152,7 @@ mod test { // Test that calling set_up_outbound() on the inbound panics #[test] - #[should_panic(expected = "assertion failed: !self.ready_for_process")] + #[should_panic(expected = "assertion failed: !self.ready_to_process")] fn new_inbound_calling_set_up_panics() { let inbound_static_private_key = SecretKey::from_slice(&[0x_21_u8; 32]).unwrap(); let inbound_ephemeral_private_key = SecretKey::from_slice(&[0x_22_u8; 32]).unwrap(); diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 6b0a7be63b9..c3195976fc3 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -6,8 +6,9 @@ use bitcoin::secp256k1::{SecretKey, PublicKey}; use ln::peers::{chacha, hkdf}; use ln::peers::conduit::{Conduit, SymmetricKey}; -use ln::peers::handshake::acts::{Act, ActBuilder, EMPTY_ACT_ONE, EMPTY_ACT_TWO, EMPTY_ACT_THREE}; +use ln::peers::handshake::acts::{Act, ActBuilder, ACT_ONE_LENGTH, ACT_TWO_LENGTH, ACT_THREE_LENGTH, EMPTY_ACT_ONE, EMPTY_ACT_TWO, EMPTY_ACT_THREE}; +// Alias type to help differentiate between temporary key and chaining key when passing bytes around type ChainingKey = [u8; 32]; // Generate a SHA-256 hash from one or more elements concatenated together @@ -30,7 +31,8 @@ pub(super) enum HandshakeState { } // Trait for all individual states to implement that ensure HandshakeState::next() can -// delegate to a common function signature. +// delegate to a common function signature. May transition to the same state in the event there are +// not yet enough bytes to move forward with the handshake. pub(super) trait IHandshakeState { fn next(self, input: &[u8]) -> Result<(Option, HandshakeState), String>; } @@ -101,7 +103,7 @@ pub(super) struct ResponderAwaitingActThreeState { impl InitiatorStartingState { pub(crate) fn new(initiator_static_private_key: SecretKey, initiator_ephemeral_private_key: SecretKey, responder_static_public_key: PublicKey) -> Self { let initiator_static_public_key = private_key_to_public_key(&initiator_static_private_key); - let (hash, chaining_key) = handshake_state_initialization(&responder_static_public_key); + let (hash, chaining_key) = initialize_handshake_state(&responder_static_public_key); let initiator_ephemeral_public_key = private_key_to_public_key(&initiator_ephemeral_private_key); InitiatorStartingState { initiator_static_private_key, @@ -160,7 +162,7 @@ impl IHandshakeState for InitiatorStartingState { impl ResponderAwaitingActOneState { pub(crate) fn new(responder_static_private_key: SecretKey, responder_ephemeral_private_key: SecretKey) -> Self { let responder_static_public_key = private_key_to_public_key(&responder_static_private_key); - let (hash, chaining_key) = handshake_state_initialization(&responder_static_public_key); + let (hash, chaining_key) = initialize_handshake_state(&responder_static_public_key); let responder_ephemeral_public_key = private_key_to_public_key(&responder_ephemeral_private_key); ResponderAwaitingActOneState { @@ -181,7 +183,7 @@ impl IHandshakeState for ResponderAwaitingActOneState { let mut act_one_builder = self.act_one_builder; let remaining = act_one_builder.fill(input); - // Any payload larger than ACT_ONE_TWO_LENGTH indicates a bad peer since initiator data + // Any payload larger than ACT_ONE_LENGTH indicates a bad peer since initiator data // is required to generate act3 (so it can't come before we transition) if remaining.len() != 0 { return Err("Act One too large".to_string()); @@ -247,7 +249,7 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { let mut act_two_builder = self.act_two_builder; let remaining = act_two_builder.fill(input); - // Any payload larger than ACT_ONE_TWO_LENGTH indicates a bad peer since responder data + // Any payload larger than ACT_TWO_LENGTH indicates a bad peer since responder data // is required to generate post-authentication messages (so it can't come before we transition) if remaining.len() != 0 { return Err("Act Two too large".to_string()); @@ -310,6 +312,7 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { // - done by Conduit let conduit = Conduit::new(sending_key, receiving_key, chaining_key); + // 8. Send m = 0 || c || t Ok(( Some(Act::Three(act_three)), HandshakeState::Complete(Some((conduit, responder_static_public_key))) @@ -345,6 +348,7 @@ impl IHandshakeState for ResponderAwaitingActThreeState { // 1. Read exactly 66 bytes from the network buffer let act_three_bytes = Act::from(act_three_builder); + assert_eq!(act_three_bytes.len(), ACT_THREE_LENGTH); // 2. Parse the read message (m) into v, c, and t let version = act_three_bytes[0]; @@ -396,8 +400,11 @@ impl IHandshakeState for ResponderAwaitingActThreeState { } } +// The handshake state always uses the responder's static public key. When running on the initiator, +// the initiator provides the remote's static public key and running on the responder they provide +// their own. // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#handshake-state-initialization -fn handshake_state_initialization(responder_static_public_key: &PublicKey) -> (Sha256, Sha256) { +fn initialize_handshake_state(responder_static_public_key: &PublicKey) -> (Sha256, Sha256) { let protocol_name = b"Noise_XK_secp256k1_ChaChaPoly_SHA256"; let prologue = b"lightning"; @@ -414,6 +421,7 @@ fn handshake_state_initialization(responder_static_public_key: &PublicKey) -> (S (hash, chaining_key) } +// Due to the very high similarity of acts 1 and 2, this method is used to process both // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-one (sender) // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (sender) fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_ephemeral_key: &PublicKey, remote_public_key: &PublicKey, chaining_key: ChainingKey, hash: Sha256, act_out: &mut [u8]) -> (Sha256, SymmetricKey, SymmetricKey) { @@ -448,6 +456,11 @@ fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_e // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (receiver) fn process_act_message(act_bytes: &[u8], local_private_key: &SecretKey, chaining_key: ChainingKey, hash: Sha256) -> Result<(PublicKey, Sha256, SymmetricKey, SymmetricKey), String> { // 1. Read exactly 50 bytes from the network buffer + // Partial act messages are handled by the callers. By the time it gets here, it + // must be the correct size. + assert_eq!(act_bytes.len(), ACT_ONE_LENGTH); + assert_eq!(act_bytes.len(), ACT_TWO_LENGTH); + // 2.Parse the read message (m) into v, re, and c let version = act_bytes[0]; let ephemeral_public_key_bytes = &act_bytes[1..34]; @@ -476,7 +489,8 @@ fn process_act_message(act_bytes: &[u8], local_private_key: &SecretKey, chaining // 6. Act2: ck, temp_k2 = HKDF(ck, ee) let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); - // 7. p = decryptWithAD(temp_k1, 0, h, c) + // 7. Act1: p = decryptWithAD(temp_k1, 0, h, c) + // 7. Act2: p = decryptWithAD(temp_k2, 0, h, c) chacha::decrypt(&temporary_key, 0, &hash, &chacha_tag, &mut [0; 0])?; // 8. h = SHA-256(h || c) diff --git a/lightning/src/ln/peers/hkdf.rs b/lightning/src/ln/peers/hkdf.rs index 6ac1391666d..61f6c49f92a 100644 --- a/lightning/src/ln/peers/hkdf.rs +++ b/lightning/src/ln/peers/hkdf.rs @@ -3,7 +3,7 @@ use bitcoin::hashes::sha256::Hash as Sha256; // Allows 1 or more inputs and "concatenates" them together using the input() function // of HmacEngine:: -macro_rules! hmac_hash { +macro_rules! hmac_sha256 { ( $salt:expr, ($( $input:expr ),+ )) => {{ let mut engine = HmacEngine::::new($salt); $( @@ -31,7 +31,7 @@ pub(super) fn derive(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32]) { // 2.2. Step 1: Extract // HKDF-Extract(salt, IKM) -> PRK // PRK = HMAC-Hash(salt, IKM) - let prk = hmac_hash!(salt, (ikm)); + let prk = hmac_sha256!(salt, (ikm)); // 2.3. Step 2: Expand // HKDF-Expand(PRK, info, L) -> OKM @@ -42,9 +42,9 @@ pub(super) fn derive(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32]) { // where: // T(0) = empty string (zero length) // T(1) = HMAC-Hash(PRK, T(0) | info | 0x01) - let t1 = hmac_hash!(&prk, (&[1])); + let t1 = hmac_sha256!(&prk, (&[1])); // T(2) = HMAC-Hash(PRK, T(1) | info | 0x02) - let t2 = hmac_hash!(&prk, (&t1, &[2])); + let t2 = hmac_sha256!(&prk, (&t1, &[2])); return (t1, t2) } From b8136beaae913d6583f4fe55f1a1ca315050c546 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 2 Sep 2020 09:51:54 -0700 Subject: [PATCH 42/45] review: rename hkdf -> hkdf5869rfc.rs Follow existing pattern used in util/ for consistency. --- lightning/src/ln/peers/conduit.rs | 4 ++-- lightning/src/ln/peers/handshake/states.rs | 14 +++++++------- lightning/src/ln/peers/{hkdf.rs => hkdf5869rfc.rs} | 2 +- lightning/src/ln/peers/mod.rs | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) rename lightning/src/ln/peers/{hkdf.rs => hkdf5869rfc.rs} (98%) diff --git a/lightning/src/ln/peers/conduit.rs b/lightning/src/ln/peers/conduit.rs index d820d0497b8..5ffc1aa1e89 100644 --- a/lightning/src/ln/peers/conduit.rs +++ b/lightning/src/ln/peers/conduit.rs @@ -1,6 +1,6 @@ //! Handles all over the wire message encryption and decryption upon handshake completion. -use ln::peers::{chacha, hkdf}; +use ln::peers::{chacha, hkdf5869rfc}; use util::byte_utils; pub(super) type SymmetricKey = [u8; 32]; @@ -111,7 +111,7 @@ impl Conduit { } fn rotate_key(chaining_key: &mut SymmetricKey, key: &mut SymmetricKey) { - let (new_chaining_key, new_key) = hkdf::derive(chaining_key, key); + let (new_chaining_key, new_key) = hkdf5869rfc::derive(chaining_key, key); chaining_key.copy_from_slice(&new_chaining_key); key.copy_from_slice(&new_key); } diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index c3195976fc3..13b392ba980 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -4,7 +4,7 @@ use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{SecretKey, PublicKey}; -use ln::peers::{chacha, hkdf}; +use ln::peers::{chacha, hkdf5869rfc}; use ln::peers::conduit::{Conduit, SymmetricKey}; use ln::peers::handshake::acts::{Act, ActBuilder, ACT_ONE_LENGTH, ACT_TWO_LENGTH, ACT_THREE_LENGTH, EMPTY_ACT_ONE, EMPTY_ACT_TWO, EMPTY_ACT_THREE}; @@ -300,13 +300,13 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { let ecdh = ecdh(&initiator_static_private_key, &responder_ephemeral_public_key); // 4. ck, temp_k3 = HKDF(ck, se) - let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); + let (chaining_key, temporary_key) = hkdf5869rfc::derive(&chaining_key, &ecdh); // 5. t = encryptWithAD(temp_k3, 0, h, zero) chacha::encrypt(&temporary_key, 0, &hash, &[0; 0], &mut act_three[50..]); // 6. sk, rk = HKDF(ck, zero) - let (sending_key, receiving_key) = hkdf::derive(&chaining_key, &[0; 0]); + let (sending_key, receiving_key) = hkdf5869rfc::derive(&chaining_key, &[0; 0]); // 7. rn = 0, sn = 0 // - done by Conduit @@ -377,13 +377,13 @@ impl IHandshakeState for ResponderAwaitingActThreeState { let ecdh = ecdh(&responder_ephemeral_private_key, &initiator_pubkey); // 7. ck, temp_k3 = HKDF(ck, se) - let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); + let (chaining_key, temporary_key) = hkdf5869rfc::derive(&chaining_key, &ecdh); // 8. p = decryptWithAD(temp_k3, 0, h, t) chacha::decrypt(&temporary_key, 0, &hash, &chacha_tag, &mut [0; 0])?; // 9. rk, sk = HKDF(ck, zero) - let (receiving_key, sending_key) = hkdf::derive(&chaining_key, &[0; 0]); + let (receiving_key, sending_key) = hkdf5869rfc::derive(&chaining_key, &[0; 0]); // 10. rn = 0, sn = 0 // - done by Conduit @@ -436,7 +436,7 @@ fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_e // 4. ACT1: ck, temp_k1 = HKDF(ck, es) // 4. ACT2: ck, temp_k2 = HKDF(ck, ee) - let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); + let (chaining_key, temporary_key) = hkdf5869rfc::derive(&chaining_key, &ecdh); // 5. ACT1: c = encryptWithAD(temp_k1, 0, h, zero) // 5. ACT2: c = encryptWithAD(temp_k2, 0, h, zero) @@ -487,7 +487,7 @@ fn process_act_message(act_bytes: &[u8], local_private_key: &SecretKey, chaining // 6. Act1: ck, temp_k1 = HKDF(ck, es) // 6. Act2: ck, temp_k2 = HKDF(ck, ee) - let (chaining_key, temporary_key) = hkdf::derive(&chaining_key, &ecdh); + let (chaining_key, temporary_key) = hkdf5869rfc::derive(&chaining_key, &ecdh); // 7. Act1: p = decryptWithAD(temp_k1, 0, h, c) // 7. Act2: p = decryptWithAD(temp_k2, 0, h, c) diff --git a/lightning/src/ln/peers/hkdf.rs b/lightning/src/ln/peers/hkdf5869rfc.rs similarity index 98% rename from lightning/src/ln/peers/hkdf.rs rename to lightning/src/ln/peers/hkdf5869rfc.rs index 61f6c49f92a..546e64e6fc7 100644 --- a/lightning/src/ln/peers/hkdf.rs +++ b/lightning/src/ln/peers/hkdf5869rfc.rs @@ -53,7 +53,7 @@ pub(super) fn derive(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32]) { #[cfg(test)] mod test { use hex; - use ln::peers::hkdf::derive; + use ln::peers::hkdf5869rfc::derive; // Test with SHA-256 and zero-length salt/info // Our implementation uses a zero-length info field and returns the first 64 octets. As a result, diff --git a/lightning/src/ln/peers/mod.rs b/lightning/src/ln/peers/mod.rs index 072950c0dba..aadef42d67d 100644 --- a/lightning/src/ln/peers/mod.rs +++ b/lightning/src/ln/peers/mod.rs @@ -5,7 +5,7 @@ mod chacha; pub mod handler; -mod hkdf; +mod hkdf5869rfc; #[cfg(feature = "fuzztarget")] pub mod conduit; From 8c6da109ef2284799e5d5322ced4d55c55d1f56e Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 2 Sep 2020 09:55:35 -0700 Subject: [PATCH 43/45] review: Explicitly set version in Act creation Don't rely on the initialized buffer filled with zeros. --- lightning/src/ln/peers/handshake/states.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 13b392ba980..d40ebb5619a 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -313,6 +313,7 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { let conduit = Conduit::new(sending_key, receiving_key, chaining_key); // 8. Send m = 0 || c || t + act_three[0] = 0; Ok(( Some(Act::Three(act_three)), HandshakeState::Complete(Some((conduit, responder_static_public_key))) @@ -446,6 +447,7 @@ fn calculate_act_message(local_private_ephemeral_key: &SecretKey, local_public_e let hash = concat_then_sha256!(hash, &act_out[34..]); // Send m = 0 || e.pub.serializeCompressed() || c + act_out[0] = 0; act_out[1..34].copy_from_slice(&serialized_local_public_key); (hash, chaining_key, temporary_key) From 19a0f42c87b490c3b81ffb9a02694fdf4ec9cd2b Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Wed, 2 Sep 2020 10:08:46 -0700 Subject: [PATCH 44/45] review: ActBuilder::fill() returns the number of bytes consumed Instead of returning a subslice of input for the caller to process, just return the number of bytes consume and let them do the accounting. --- lightning/src/ln/peers/handshake/acts.rs | 25 +++++++++++----------- lightning/src/ln/peers/handshake/states.rs | 24 ++++++++++----------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/lightning/src/ln/peers/handshake/acts.rs b/lightning/src/ln/peers/handshake/acts.rs index fa7a2ece5a6..2795ae621df 100644 --- a/lightning/src/ln/peers/handshake/acts.rs +++ b/lightning/src/ln/peers/handshake/acts.rs @@ -82,8 +82,8 @@ impl ActBuilder { } } - /// Fills the Act with bytes from input and returns the unprocessed bytes - pub(super) fn fill<'a>(&mut self, input: &'a [u8]) -> &'a [u8] { + /// Fills the Act with bytes from input and returns the number of bytes consumed from input. + pub(super) fn fill(&mut self, input: &[u8]) -> usize { // Simple fill implementation for both almost-identical structs to deduplicate code // $act: Act[One|Two|Three], $input: &[u8]; returns &[u8] of remaining input that was not processed macro_rules! fill_act_content { @@ -93,7 +93,7 @@ impl ActBuilder { $act[$write_pos..$write_pos + fill_amount].copy_from_slice(&$input[..fill_amount]); $write_pos += fill_amount; - &$input[fill_amount..] + fill_amount }} } @@ -125,11 +125,12 @@ mod tests { fn partial_fill() { let mut builder = ActBuilder::new(Act::One(EMPTY_ACT_ONE)); - let remaining = builder.fill(&[1, 2, 3]); + let input = [1, 2, 3]; + let bytes_read = builder.fill(&input); assert_eq!(builder.partial_act.len(), ACT_ONE_LENGTH); assert_eq!(builder.write_pos, 3); assert!(!builder.is_finished()); - assert_eq!(remaining, &[]); + assert_eq!(bytes_read, input.len()); } // Test bookkeeping of exact fill @@ -137,13 +138,13 @@ mod tests { fn exact_fill() { let mut builder = ActBuilder::new(Act::One(EMPTY_ACT_ONE)); - let input = [0; 50]; - let remaining = builder.fill(&input); + let input = [0; ACT_ONE_LENGTH]; + let bytes_read = builder.fill(&input); assert_eq!(builder.partial_act.len(), ACT_ONE_LENGTH); assert_eq!(builder.write_pos, ACT_ONE_LENGTH); assert!(builder.is_finished()); assert_eq!(Act::from(builder).as_ref(), &input[..]); - assert_eq!(remaining, &[]); + assert_eq!(bytes_read, input.len()); } // Test bookkeeping of overfill @@ -151,14 +152,14 @@ mod tests { fn over_fill() { let mut builder = ActBuilder::new(Act::One(EMPTY_ACT_ONE)); - let input = [0; 51]; - let remaining = builder.fill(&input); + let input = [0; ACT_ONE_LENGTH + 1]; + let bytes_read = builder.fill(&input); assert_eq!(builder.partial_act.len(), ACT_ONE_LENGTH); assert_eq!(builder.write_pos, ACT_ONE_LENGTH); assert!(builder.is_finished()); - assert_eq!(Act::from(builder).as_ref(), &input[..50]); - assert_eq!(remaining, &[0]); + assert_eq!(Act::from(builder).as_ref(), &input[..ACT_ONE_LENGTH]); + assert_eq!(bytes_read, ACT_ONE_LENGTH); } // Converting an unfinished ActBuilder panics diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index d40ebb5619a..6dbfe3cd764 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -181,17 +181,17 @@ impl IHandshakeState for ResponderAwaitingActOneState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-two (sender) fn next(self, input: &[u8]) -> Result<(Option, HandshakeState), String> { let mut act_one_builder = self.act_one_builder; - let remaining = act_one_builder.fill(input); + let bytes_read = act_one_builder.fill(input); - // Any payload larger than ACT_ONE_LENGTH indicates a bad peer since initiator data - // is required to generate act3 (so it can't come before we transition) - if remaining.len() != 0 { + // Any payload that is not fully consumed by the builder indicates a bad peer since responder + // data is required to generate act3 (so it can't come before we transition) + if bytes_read < input.len() { return Err("Act One too large".to_string()); } // In the event of a partial fill, stay in the same state and wait for more data if !act_one_builder.is_finished() { - assert_eq!(remaining.len(), 0); + assert_eq!(bytes_read, input.len()); return Ok(( None, HandshakeState::ResponderAwaitingActOne(Self { @@ -247,17 +247,17 @@ impl IHandshakeState for InitiatorAwaitingActTwoState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-three (sender) fn next(self, input: &[u8]) -> Result<(Option, HandshakeState), String> { let mut act_two_builder = self.act_two_builder; - let remaining = act_two_builder.fill(input); + let bytes_read = act_two_builder.fill(input); - // Any payload larger than ACT_TWO_LENGTH indicates a bad peer since responder data + // Any payload that is not fully consumed by the builder indicates a bad peer since responder data // is required to generate post-authentication messages (so it can't come before we transition) - if remaining.len() != 0 { + if bytes_read < input.len() { return Err("Act Two too large".to_string()); } // In the event of a partial fill, stay in the same state and wait for more data if !act_two_builder.is_finished() { - assert_eq!(remaining.len(), 0); + assert_eq!(bytes_read, input.len()); return Ok(( None, HandshakeState::InitiatorAwaitingActTwo(Self { @@ -325,11 +325,11 @@ impl IHandshakeState for ResponderAwaitingActThreeState { // https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#act-three (receiver) fn next(self, input: &[u8]) -> Result<(Option, HandshakeState), String> { let mut act_three_builder = self.act_three_builder; - let remaining = act_three_builder.fill(input); + let bytes_read = act_three_builder.fill(input); // In the event of a partial fill, stay in the same state and wait for more data if !act_three_builder.is_finished() { - assert_eq!(remaining.len(), 0); + assert_eq!(bytes_read, input.len()); return Ok(( None, HandshakeState::ResponderAwaitingActThree(Self { @@ -392,7 +392,7 @@ impl IHandshakeState for ResponderAwaitingActThreeState { // Any remaining data in the read buffer would be encrypted, so transfer ownership // to the Conduit for future use. - conduit.read(remaining); + conduit.read(&input[bytes_read..]); Ok(( None, From 30286c0328f71ba0c47472d6320f3977eb584707 Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 18 Sep 2020 10:29:41 -0700 Subject: [PATCH 45/45] docs: Add license to new peers/ files --- lightning/src/ln/peers/chacha.rs | 9 +++++++++ lightning/src/ln/peers/conduit.rs | 9 +++++++++ lightning/src/ln/peers/handshake/acts.rs | 9 +++++++++ lightning/src/ln/peers/handshake/mod.rs | 9 +++++++++ lightning/src/ln/peers/handshake/states.rs | 9 +++++++++ lightning/src/ln/peers/hkdf5869rfc.rs | 9 +++++++++ lightning/src/ln/peers/mod.rs | 9 +++++++++ 7 files changed, 63 insertions(+) diff --git a/lightning/src/ln/peers/chacha.rs b/lightning/src/ln/peers/chacha.rs index 76742bad452..7d5ab45c50c 100644 --- a/lightning/src/ln/peers/chacha.rs +++ b/lightning/src/ln/peers/chacha.rs @@ -1,3 +1,12 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + use util::byte_utils; use util::chacha20poly1305rfc::ChaCha20Poly1305RFC; diff --git a/lightning/src/ln/peers/conduit.rs b/lightning/src/ln/peers/conduit.rs index 5ffc1aa1e89..aa9ad87bc58 100644 --- a/lightning/src/ln/peers/conduit.rs +++ b/lightning/src/ln/peers/conduit.rs @@ -1,3 +1,12 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + //! Handles all over the wire message encryption and decryption upon handshake completion. use ln::peers::{chacha, hkdf5869rfc}; diff --git a/lightning/src/ln/peers/handshake/acts.rs b/lightning/src/ln/peers/handshake/acts.rs index 2795ae621df..e95fe8d9cfb 100644 --- a/lightning/src/ln/peers/handshake/acts.rs +++ b/lightning/src/ln/peers/handshake/acts.rs @@ -1,3 +1,12 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + //! Helper library for working with NOISE handshake Act data. Contains utilities for passing Act //! objects around and building them from received data. //! Act objects are thin wrappers about raw arrays for stack-based processing as well as convenient diff --git a/lightning/src/ln/peers/handshake/mod.rs b/lightning/src/ln/peers/handshake/mod.rs index d914b299518..f964c245a94 100644 --- a/lightning/src/ln/peers/handshake/mod.rs +++ b/lightning/src/ln/peers/handshake/mod.rs @@ -1,3 +1,12 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + //! Execute handshakes for peer-to-peer connection establishment. //! Handshake states can be advanced automatically, or by manually calling the appropriate step. //! Once complete, returns an instance of Conduit. diff --git a/lightning/src/ln/peers/handshake/states.rs b/lightning/src/ln/peers/handshake/states.rs index 6dbfe3cd764..347620cbb8f 100644 --- a/lightning/src/ln/peers/handshake/states.rs +++ b/lightning/src/ln/peers/handshake/states.rs @@ -1,3 +1,12 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + use bitcoin::secp256k1; use bitcoin::hashes::{Hash, HashEngine}; diff --git a/lightning/src/ln/peers/hkdf5869rfc.rs b/lightning/src/ln/peers/hkdf5869rfc.rs index 546e64e6fc7..1d9f66ab1cb 100644 --- a/lightning/src/ln/peers/hkdf5869rfc.rs +++ b/lightning/src/ln/peers/hkdf5869rfc.rs @@ -1,3 +1,12 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine}; use bitcoin::hashes::sha256::Hash as Sha256; diff --git a/lightning/src/ln/peers/mod.rs b/lightning/src/ln/peers/mod.rs index aadef42d67d..5c10e56657c 100644 --- a/lightning/src/ln/peers/mod.rs +++ b/lightning/src/ln/peers/mod.rs @@ -1,3 +1,12 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + //! Everything that has to do with over-the-wire peer communication. //! The handshake module exposes mechanisms to conduct inbound and outbound handshakes. //! When a handshake completes, it returns an instance of Conduit.