From db0d6ecdbbde48e77ae458e904099a4a973d1511 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Wed, 18 Jan 2023 13:03:06 -0800 Subject: [PATCH] Add NodeSigner::sign_gossip_message Adds signing capability to NodeSigner for all gossip messages that require a node signature. --- fuzz/src/chanmon_consistency.rs | 39 ++++++++++++++++------------ fuzz/src/full_stack.rs | 14 +++++++--- fuzz/src/onion_message.rs | 4 +++ lightning/src/chain/keysinterface.rs | 19 +++++++++++++- lightning/src/ln/msgs.rs | 19 ++++++++++++++ lightning/src/util/test_utils.rs | 4 +++ 6 files changed, 78 insertions(+), 21 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 3c174f309..def53d034 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -29,6 +29,7 @@ use bitcoin::network::constants::Network; use bitcoin::hashes::Hash as TraitImport; use bitcoin::hashes::sha256::Hash as Sha256; +use bitcoin::hashes::sha256d::Hash as Sha256dHash; use bitcoin::hash_types::{BlockHash, WPubkeyHash}; use lightning::chain; @@ -54,10 +55,9 @@ use lightning::routing::router::{InFlightHtlcs, Route, RouteHop, RouteParameters use crate::utils::test_logger::{self, Output}; use crate::utils::test_persister::TestPersister; -use bitcoin::secp256k1::{PublicKey, SecretKey, Scalar}; +use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1}; use bitcoin::secp256k1::ecdh::SharedSecret; -use bitcoin::secp256k1::ecdsa::RecoverableSignature; -use bitcoin::secp256k1::Secp256k1; +use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; use std::mem; use std::cmp::{self, Ordering}; @@ -174,7 +174,7 @@ impl chain::Watch for TestChainMonitor { } struct KeyProvider { - node_id: u8, + node_secret: SecretKey, rand_bytes_id: atomic::AtomicU32, enforcement_states: Mutex>>>, } @@ -182,7 +182,7 @@ struct KeyProvider { impl EntropySource for KeyProvider { fn get_secure_random_bytes(&self) -> [u8; 32] { let id = self.rand_bytes_id.fetch_add(1, atomic::Ordering::Relaxed); - let mut res = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, self.node_id]; + let mut res = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, self.node_secret[31]]; res[30-4..30].copy_from_slice(&id.to_le_bytes()); res } @@ -190,7 +190,7 @@ impl EntropySource for KeyProvider { impl NodeSigner for KeyProvider { fn get_node_secret(&self, _recipient: Recipient) -> Result { - Ok(SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, self.node_id]).unwrap()) + Ok(self.node_secret.clone()) } fn get_node_id(&self, recipient: Recipient) -> Result { @@ -207,12 +207,18 @@ impl NodeSigner for KeyProvider { } fn get_inbound_payment_key_material(&self) -> KeyMaterial { - KeyMaterial([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, self.node_id]) + KeyMaterial([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, self.node_secret[31]]) } fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result { unreachable!() } + + fn sign_gossip_message(&self, msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result { + let msg_hash = Message::from_slice(&Sha256dHash::hash(&msg.encode()[..])[..]).map_err(|_| ())?; + let secp_ctx = Secp256k1::signing_only(); + Ok(secp_ctx.sign_ecdsa(&msg_hash, &self.node_secret)) + } } impl SignerProvider for KeyProvider { @@ -229,12 +235,12 @@ impl SignerProvider for KeyProvider { let keys = InMemorySigner::new( &secp_ctx, self.get_node_secret(Recipient::Node).unwrap(), - SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, self.node_id]).unwrap(), - SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, self.node_id]).unwrap(), - SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, self.node_id]).unwrap(), - SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, self.node_id]).unwrap(), - SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, self.node_id]).unwrap(), - [id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, self.node_id], + SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, self.node_secret[31]]).unwrap(), + SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, self.node_secret[31]]).unwrap(), + SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, self.node_secret[31]]).unwrap(), + SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, self.node_secret[31]]).unwrap(), + SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, self.node_secret[31]]).unwrap(), + [id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, self.node_secret[31]], channel_value_satoshis, channel_keys_id, ); @@ -257,14 +263,14 @@ impl SignerProvider for KeyProvider { fn get_destination_script(&self) -> Script { let secp_ctx = Secp256k1::signing_only(); - let channel_monitor_claim_key = SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, self.node_id]).unwrap(); + let channel_monitor_claim_key = SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, self.node_secret[31]]).unwrap(); let our_channel_monitor_claim_key_hash = WPubkeyHash::hash(&PublicKey::from_secret_key(&secp_ctx, &channel_monitor_claim_key).serialize()); Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0).push_slice(&our_channel_monitor_claim_key_hash[..]).into_script() } fn get_shutdown_scriptpubkey(&self) -> ShutdownScript { let secp_ctx = Secp256k1::signing_only(); - let secret_key = SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, self.node_id]).unwrap(); + let secret_key = SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, self.node_secret[31]]).unwrap(); let pubkey_hash = WPubkeyHash::hash(&PublicKey::from_secret_key(&secp_ctx, &secret_key).serialize()); ShutdownScript::new_p2wpkh(&pubkey_hash) } @@ -402,7 +408,8 @@ pub fn do_test(data: &[u8], underlying_out: Out) { macro_rules! make_node { ($node_id: expr, $fee_estimator: expr) => { { let logger: Arc = Arc::new(test_logger::TestLogger::new($node_id.to_string(), out.clone())); - let keys_manager = Arc::new(KeyProvider { node_id: $node_id, rand_bytes_id: atomic::AtomicU32::new(0), enforcement_states: Mutex::new(HashMap::new()) }); + let node_secret = SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, $node_id]).unwrap(); + let keys_manager = Arc::new(KeyProvider { node_secret, rand_bytes_id: atomic::AtomicU32::new(0), enforcement_states: Mutex::new(HashMap::new()) }); let monitor = Arc::new(TestChainMonitor::new(broadcast.clone(), logger.clone(), $fee_estimator.clone(), Arc::new(TestPersister { update_ret: Mutex::new(ChannelMonitorUpdateStatus::Completed) diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 6d952b42d..20a6e55cc 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -26,6 +26,7 @@ use bitcoin::network::constants::Network; use bitcoin::hashes::Hash as TraitImport; use bitcoin::hashes::HashEngine as TraitImportEngine; use bitcoin::hashes::sha256::Hash as Sha256; +use bitcoin::hashes::sha256d::Hash as Sha256dHash; use bitcoin::hash_types::{Txid, BlockHash, WPubkeyHash}; use lightning::chain; @@ -47,15 +48,14 @@ use lightning::util::errors::APIError; use lightning::util::events::Event; use lightning::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState}; use lightning::util::logger::Logger; -use lightning::util::ser::ReadableArgs; +use lightning::util::ser::{ReadableArgs, Writeable}; use crate::utils::test_logger; use crate::utils::test_persister::TestPersister; -use bitcoin::secp256k1::{PublicKey, SecretKey, Scalar}; +use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1}; use bitcoin::secp256k1::ecdh::SharedSecret; -use bitcoin::secp256k1::ecdsa::RecoverableSignature; -use bitcoin::secp256k1::Secp256k1; +use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; use std::cell::RefCell; use hashbrown::{HashMap, hash_map}; @@ -317,6 +317,12 @@ impl NodeSigner for KeyProvider { fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result { unreachable!() } + + fn sign_gossip_message(&self, msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result { + let msg_hash = Message::from_slice(&Sha256dHash::hash(&msg.encode()[..])[..]).map_err(|_| ())?; + let secp_ctx = Secp256k1::signing_only(); + Ok(secp_ctx.sign_ecdsa(&msg_hash, &self.node_secret)) + } } impl SignerProvider for KeyProvider { diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index a86734d45..b17623412 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -122,6 +122,10 @@ impl NodeSigner for KeyProvider { fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result { unreachable!() } + + fn sign_gossip_message(&self, _msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result { + unreachable!() + } } impl SignerProvider for KeyProvider { diff --git a/lightning/src/chain/keysinterface.rs b/lightning/src/chain/keysinterface.rs index 9fc00e252..83243ee88 100644 --- a/lightning/src/chain/keysinterface.rs +++ b/lightning/src/chain/keysinterface.rs @@ -41,7 +41,7 @@ use crate::chain::transaction::OutPoint; use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI; use crate::ln::{chan_utils, PaymentPreimage}; use crate::ln::chan_utils::{HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys, HolderCommitmentTransaction, ChannelTransactionParameters, CommitmentTransaction, ClosingTransaction}; -use crate::ln::msgs::UnsignedChannelAnnouncement; +use crate::ln::msgs::{UnsignedChannelAnnouncement, UnsignedGossipMessage}; use crate::ln::script::ShutdownScript; use crate::prelude::*; @@ -494,6 +494,14 @@ pub trait NodeSigner { /// /// Errors if the [`Recipient`] variant is not supported by the implementation. fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result; + + /// Sign a gossip message. + /// + /// Note that if this fails, LDK may panic and the message will not be broadcast to the network + /// or a possible channel counterparty. If LDK panics, the error should be resolved to allow the + /// message to be broadcast, as otherwise it may prevent one from receiving funds over the + /// corresponding channel. + fn sign_gossip_message(&self, msg: UnsignedGossipMessage) -> Result; } /// A trait that can return signer instances for individual channels. @@ -1290,6 +1298,11 @@ impl NodeSigner for KeysManager { }; Ok(self.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&Sha256::hash(&preimage)), &secret)) } + + fn sign_gossip_message(&self, msg: UnsignedGossipMessage) -> Result { + let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.encode()[..])[..]); + Ok(sign(&self.secp_ctx, &msg_hash, &self.node_secret)) + } } impl SignerProvider for KeysManager { @@ -1394,6 +1407,10 @@ impl NodeSigner for PhantomKeysManager { let secret = self.get_node_secret(recipient)?; Ok(self.inner.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&Sha256::hash(&preimage)), &secret)) } + + fn sign_gossip_message(&self, msg: UnsignedGossipMessage) -> Result { + self.inner.sign_gossip_message(msg) + } } impl SignerProvider for PhantomKeysManager { diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 6cbcd9787..40817aa3c 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -632,6 +632,25 @@ impl Readable for NetAddress { } } +/// Represents the set of gossip messages that require a signature from a node's identity key. +pub enum UnsignedGossipMessage<'a> { + /// An unsigned channel announcement. + ChannelAnnouncement(&'a UnsignedChannelAnnouncement), + /// An unsigned channel update. + ChannelUpdate(&'a UnsignedChannelUpdate), + /// An unsigned node announcement. + NodeAnnouncement(&'a UnsignedNodeAnnouncement) +} + +impl<'a> Writeable for UnsignedGossipMessage<'a> { + fn write(&self, writer: &mut W) -> Result<(), io::Error> { + match self { + UnsignedGossipMessage::ChannelAnnouncement(ref msg) => msg.write(writer), + UnsignedGossipMessage::ChannelUpdate(ref msg) => msg.write(writer), + UnsignedGossipMessage::NodeAnnouncement(ref msg) => msg.write(writer), + } + } +} /// The unsigned part of a [`node_announcement`] message. /// diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 627ee54eb..9fb5178ad 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -659,6 +659,10 @@ impl NodeSigner for TestKeysInterface { fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result { self.backing.sign_invoice(hrp_bytes, invoice_data, recipient) } + + fn sign_gossip_message(&self, msg: msgs::UnsignedGossipMessage) -> Result { + self.backing.sign_gossip_message(msg) + } } impl SignerProvider for TestKeysInterface { -- 2.39.5