Skip to content

Commit 293677c

Browse files
committed
Add support for opt_shutdown_anysegwit feature #780
* Implemented protocol. * Made feature optional. * Verify that the default value is true. * Verify that on shutdown, if Channel.supports_shutdown_anysegwit is enabled, the script can be a witness program. * Added a test that verifies that a scriptpubkey for an unreleased segwit version is handled successfully. * Added a test that verifies that if node has op_shutdown_anysegwit disabled, a scriptpubkey with an unreleased segwit version on shutdown throws an error. * Added peer InitFeatures to handle_shutdown * Check if shutdown script is valid when given upfront. * Added a test to verify that an invalid test results in error. * Added a test to check that if a segwit script with version 0 is provided, the updated anysegwit check detects it and returns unsupported.
1 parent d6f41d3 commit 293677c

File tree

9 files changed

+244
-52
lines changed

9 files changed

+244
-52
lines changed

lightning-net-tokio/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ mod tests {
551551
fn handle_funding_created(&self, _their_node_id: &PublicKey, _msg: &FundingCreated) {}
552552
fn handle_funding_signed(&self, _their_node_id: &PublicKey, _msg: &FundingSigned) {}
553553
fn handle_funding_locked(&self, _their_node_id: &PublicKey, _msg: &FundingLocked) {}
554-
fn handle_shutdown(&self, _their_node_id: &PublicKey, _msg: &Shutdown) {}
554+
fn handle_shutdown(&self, _their_node_id: &PublicKey, _their_features: &InitFeatures, _msg: &Shutdown) {}
555555
fn handle_closing_signed(&self, _their_node_id: &PublicKey, _msg: &ClosingSigned) {}
556556
fn handle_update_add_htlc(&self, _their_node_id: &PublicKey, _msg: &UpdateAddHTLC) {}
557557
fn handle_update_fulfill_htlc(&self, _their_node_id: &PublicKey, _msg: &UpdateFulfillHTLC) {}

lightning/src/ln/channel.rs

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -737,15 +737,14 @@ impl<Signer: Sign> Channel<Signer> {
737737
let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() {
738738
match &msg.shutdown_scriptpubkey {
739739
&OptionalField::Present(ref script) => {
740-
// Peer is signaling upfront_shutdown and has provided a non-accepted scriptpubkey format. We enforce it while receiving shutdown msg
741-
if script.is_p2pkh() || script.is_p2sh() || script.is_v0_p2wsh() || script.is_v0_p2wpkh() {
742-
Some(script.clone())
743-
// Peer is signaling upfront_shutdown and has opt-out with a 0-length script. We don't enforce anything
744-
} else if script.len() == 0 {
740+
if is_unsupported_shutdown_script(&their_features, script) {
741+
return Err(ChannelError::Close(format!("Peer is signaling upfront_shutdown but has provided a non-accepted scriptpubkey format. script: ({})", script.to_bytes().to_hex())));
742+
}
743+
744+
if script.len() == 0 {
745745
None
746-
// Peer is signaling upfront_shutdown and has provided a non-accepted scriptpubkey format. Fail the channel
747746
} else {
748-
return Err(ChannelError::Close(format!("Peer is signaling upfront_shutdown but has provided a non-accepted scriptpubkey format. script: ({})", script.to_bytes().to_hex())));
747+
Some(script.clone())
749748
}
750749
},
751750
// Peer is signaling upfront shutdown but don't opt-out with correct mechanism (a.k.a 0-length script). Peer looks buggy, we fail the channel
@@ -1439,15 +1438,14 @@ impl<Signer: Sign> Channel<Signer> {
14391438
let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() {
14401439
match &msg.shutdown_scriptpubkey {
14411440
&OptionalField::Present(ref script) => {
1442-
// Peer is signaling upfront_shutdown and has provided a non-accepted scriptpubkey format. We enforce it while receiving shutdown msg
1443-
if script.is_p2pkh() || script.is_p2sh() || script.is_v0_p2wsh() || script.is_v0_p2wpkh() {
1444-
Some(script.clone())
1445-
// Peer is signaling upfront_shutdown and has opt-out with a 0-length script. We don't enforce anything
1446-
} else if script.len() == 0 {
1441+
if is_unsupported_shutdown_script(&their_features, script) {
1442+
return Err(ChannelError::Close(format!("Peer is signaling upfront_shutdown but has provided a non-accepted scriptpubkey format. script: ({})", script.to_bytes().to_hex())));
1443+
}
1444+
1445+
if script.len() == 0 {
14471446
None
1448-
// Peer is signaling upfront_shutdown and has provided a non-accepted scriptpubkey format. Fail the channel
14491447
} else {
1450-
return Err(ChannelError::Close(format!("Peer is signaling upfront_shutdown but has provided a non-accepted scriptpubkey format. scriptpubkey: ({})", script.to_bytes().to_hex())));
1448+
Some(script.clone())
14511449
}
14521450
},
14531451
// Peer is signaling upfront shutdown but don't opt-out with correct mechanism (a.k.a 0-length script). Peer looks buggy, we fail the channel
@@ -3066,7 +3064,7 @@ impl<Signer: Sign> Channel<Signer> {
30663064
})
30673065
}
30683066

3069-
pub fn shutdown<F: Deref>(&mut self, fee_estimator: &F, msg: &msgs::Shutdown) -> Result<(Option<msgs::Shutdown>, Option<msgs::ClosingSigned>, Vec<(HTLCSource, PaymentHash)>), ChannelError>
3067+
pub fn shutdown<F: Deref>(&mut self, fee_estimator: &F, their_features: &InitFeatures, msg: &msgs::Shutdown) -> Result<(Option<msgs::Shutdown>, Option<msgs::ClosingSigned>, Vec<(HTLCSource, PaymentHash)>), ChannelError>
30703068
where F::Target: FeeEstimator
30713069
{
30723070
if self.channel_state & (ChannelState::PeerDisconnected as u32) == ChannelState::PeerDisconnected as u32 {
@@ -3085,14 +3083,7 @@ impl<Signer: Sign> Channel<Signer> {
30853083
}
30863084
assert_eq!(self.channel_state & ChannelState::ShutdownComplete as u32, 0);
30873085

3088-
// BOLT 2 says we must only send a scriptpubkey of certain standard forms, which are up to
3089-
// 34 bytes in length, so don't let the remote peer feed us some super fee-heavy script.
3090-
if self.is_outbound() && msg.scriptpubkey.len() > 34 {
3091-
return Err(ChannelError::Close(format!("Got counterparty shutdown_scriptpubkey ({}) of absurd length from remote peer", msg.scriptpubkey.to_bytes().to_hex())));
3092-
}
3093-
3094-
//Check counterparty_shutdown_scriptpubkey form as BOLT says we must
3095-
if !msg.scriptpubkey.is_p2pkh() && !msg.scriptpubkey.is_p2sh() && !msg.scriptpubkey.is_v0_p2wpkh() && !msg.scriptpubkey.is_v0_p2wsh() {
3086+
if is_unsupported_shutdown_script(&their_features, &msg.scriptpubkey) {
30963087
return Err(ChannelError::Close(format!("Got a nonstandard scriptpubkey ({}) from remote peer", msg.scriptpubkey.to_bytes().to_hex())));
30973088
}
30983089

@@ -4217,6 +4208,48 @@ impl<Signer: Sign> Channel<Signer> {
42174208
}
42184209
}
42194210

4211+
fn is_unsupported_shutdown_script(their_features: &InitFeatures, script: &Script) -> bool {
4212+
// We restrain shutdown scripts to standards forms to avoid transactions not propagating on the p2p tx-relay network
4213+
4214+
// Providing an empty is supported
4215+
let script_lenght = script.len();
4216+
if script_lenght == 0 {
4217+
return false;
4218+
}
4219+
4220+
// BOLT 2 says we must only send a scriptpubkey of certain standard forms,
4221+
// which for a a BIP-141-compliant witness program is at max 42 bytes in length.
4222+
// So don't let the remote peer feed us some super fee-heavy script.
4223+
let is_script_too_long = script_lenght > 42;
4224+
if is_script_too_long {
4225+
return true;
4226+
}
4227+
4228+
if their_features.supports_shutdown_anysegwit() {
4229+
// A scriptPubKey (or redeemScript as defined in BIP16/P2SH) that consists of a 1-byte
4230+
// push opcode (for 1 to 16) followed by a data push between 2 and 40 bytes gets a new
4231+
// special meaning.
4232+
let min_vernum: u8 = opcodes::all::OP_PUSHNUM_1.into_u8();
4233+
let max_vernum: u8 = opcodes::all::OP_PUSHNUM_16.into_u8();
4234+
let script_bytes = script.as_bytes();
4235+
let is_anysegwit = script.len() >= 3
4236+
&& script.len() <= 42
4237+
// PUSHNUM_1-PUSHNUM_16
4238+
&& (script_bytes[0] >= min_vernum && script_bytes[0] <= max_vernum)
4239+
// Second byte push opcode 2-40 bytes
4240+
&& script_bytes[1] >= opcodes::all::OP_PUSHBYTES_2.into_u8()
4241+
&& script_bytes[1] <= opcodes::all::OP_PUSHBYTES_40.into_u8()
4242+
// Check that the rest of the script has the correct size
4243+
&& script.len() - 2 == script_bytes[1] as usize;
4244+
4245+
if is_anysegwit {
4246+
return false;
4247+
}
4248+
}
4249+
4250+
return !script.is_p2pkh() && !script.is_p2sh() && !script.is_v0_p2wpkh() && !script.is_v0_p2wsh()
4251+
}
4252+
42204253
const SERIALIZATION_VERSION: u8 = 1;
42214254
const MIN_SERIALIZATION_VERSION: u8 = 1;
42224255

@@ -4436,6 +4469,7 @@ impl<Signer: Sign> Writeable for Channel<Signer> {
44364469
self.counterparty_shutdown_scriptpubkey.write(writer)?;
44374470

44384471
self.commitment_secrets.write(writer)?;
4472+
44394473
Ok(())
44404474
}
44414475
}
@@ -5553,4 +5587,5 @@ mod tests {
55535587
assert_eq!(chan_utils::derive_private_revocation_key(&secp_ctx, &per_commitment_secret, &base_secret).unwrap(),
55545588
SecretKey::from_slice(&hex::decode("d09ffff62ddb2297ab000cc85bcb4283fdeb6aa052affbc9dddcf33b61078110").unwrap()[..]).unwrap());
55555589
}
5590+
55565591
}

lightning/src/ln/channelmanager.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2507,7 +2507,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
25072507
}
25082508
}
25092509

2510-
fn internal_shutdown(&self, counterparty_node_id: &PublicKey, msg: &msgs::Shutdown) -> Result<(), MsgHandleErrInternal> {
2510+
fn internal_shutdown(&self, counterparty_node_id: &PublicKey, their_features: &InitFeatures, msg: &msgs::Shutdown) -> Result<(), MsgHandleErrInternal> {
25112511
let (mut dropped_htlcs, chan_option) = {
25122512
let mut channel_state_lock = self.channel_state.lock().unwrap();
25132513
let channel_state = &mut *channel_state_lock;
@@ -2517,7 +2517,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
25172517
if chan_entry.get().get_counterparty_node_id() != *counterparty_node_id {
25182518
return Err(MsgHandleErrInternal::send_err_msg_no_close("Got a message for a channel from the wrong node!".to_owned(), msg.channel_id));
25192519
}
2520-
let (shutdown, closing_signed, dropped_htlcs) = try_chan_entry!(self, chan_entry.get_mut().shutdown(&self.fee_estimator, &msg), channel_state, chan_entry);
2520+
let (shutdown, closing_signed, dropped_htlcs) = try_chan_entry!(self, chan_entry.get_mut().shutdown(&self.fee_estimator, &their_features, &msg), channel_state, chan_entry);
25212521
if let Some(msg) = shutdown {
25222522
channel_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
25232523
node_id: counterparty_node_id.clone(),
@@ -3351,9 +3351,9 @@ impl<Signer: Sign, M: Deref + Sync + Send, T: Deref + Sync + Send, K: Deref + Sy
33513351
let _ = handle_error!(self, self.internal_funding_locked(counterparty_node_id, msg), *counterparty_node_id);
33523352
}
33533353

3354-
fn handle_shutdown(&self, counterparty_node_id: &PublicKey, msg: &msgs::Shutdown) {
3354+
fn handle_shutdown(&self, counterparty_node_id: &PublicKey, their_features: &InitFeatures, msg: &msgs::Shutdown) {
33553355
let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
3356-
let _ = handle_error!(self, self.internal_shutdown(counterparty_node_id, msg), *counterparty_node_id);
3356+
let _ = handle_error!(self, self.internal_shutdown(counterparty_node_id, their_features, msg), *counterparty_node_id);
33573357
}
33583358

33593359
fn handle_closing_signed(&self, counterparty_node_id: &PublicKey, msg: &msgs::ClosingSigned) {

lightning/src/ln/features.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ mod sealed {
100100
StaticRemoteKey,
101101
// Byte 2
102102
,
103+
// Byte 3
104+
,
103105
],
104106
optional_features: [
105107
// Byte 0
@@ -108,6 +110,8 @@ mod sealed {
108110
VariableLengthOnion | PaymentSecret,
109111
// Byte 2
110112
BasicMPP,
113+
// Byte 3
114+
ShutdownAnySegwit,
111115
],
112116
});
113117
define_context!(NodeContext {
@@ -118,6 +122,8 @@ mod sealed {
118122
StaticRemoteKey,
119123
// Byte 2
120124
,
125+
// Byte 3
126+
,
121127
],
122128
optional_features: [
123129
// Byte 0
@@ -126,6 +132,8 @@ mod sealed {
126132
VariableLengthOnion | PaymentSecret,
127133
// Byte 2
128134
BasicMPP,
135+
// Byte 3
136+
ShutdownAnySegwit,
129137
],
130138
});
131139
define_context!(ChannelContext {
@@ -252,6 +260,8 @@ mod sealed {
252260
"Feature flags for `payment_secret`.");
253261
define_feature!(17, BasicMPP, [InitContext, NodeContext],
254262
"Feature flags for `basic_mpp`.");
263+
define_feature!(25, ShutdownAnySegwit, [InitContext, NodeContext],
264+
"Feature flags for `opt_shutdown_anysegwit`.");
255265

256266
#[cfg(test)]
257267
define_context!(TestingContext {
@@ -549,6 +559,17 @@ impl<T: sealed::BasicMPP> Features<T> {
549559
}
550560
}
551561

562+
impl<T: sealed::ShutdownAnySegwit> Features<T> {
563+
pub(crate) fn supports_shutdown_anysegwit(&self) -> bool {
564+
<T as sealed::ShutdownAnySegwit>::supports_feature(&self.flags)
565+
}
566+
#[cfg(test)]
567+
pub(crate) fn clear_shutdown_anysegwit(mut self) -> Self {
568+
<T as sealed::ShutdownAnySegwit>::clear_bits(&mut self.flags);
569+
self
570+
}
571+
}
572+
552573
impl<T: sealed::Context> Writeable for Features<T> {
553574
fn write<W: Writer>(&self, w: &mut W) -> Result<(), ::std::io::Error> {
554575
w.size_hint(self.flags.len() + 2);
@@ -619,6 +640,9 @@ mod tests {
619640
assert!(!InitFeatures::known().requires_basic_mpp());
620641
assert!(!NodeFeatures::known().requires_basic_mpp());
621642

643+
assert!(InitFeatures::known().supports_shutdown_anysegwit());
644+
assert!(NodeFeatures::known().supports_shutdown_anysegwit());
645+
622646
let mut init_features = InitFeatures::known();
623647
assert!(init_features.initial_routing_sync());
624648
init_features.clear_initial_routing_sync();
@@ -657,10 +681,12 @@ mod tests {
657681
// - option_data_loss_protect
658682
// - var_onion_optin | static_remote_key (req) | payment_secret
659683
// - basic_mpp
660-
assert_eq!(node_features.flags.len(), 3);
684+
// - opt_shutdown_anysegwit
685+
assert_eq!(node_features.flags.len(), 4);
661686
assert_eq!(node_features.flags[0], 0b00000010);
662687
assert_eq!(node_features.flags[1], 0b10010010);
663688
assert_eq!(node_features.flags[2], 0b00000010);
689+
assert_eq!(node_features.flags[3], 0b00000010);
664690
}
665691

666692
// Check that cleared flags are kept blank when converting back:

lightning/src/ln/functional_test_utils.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ pub fn close_channel<'a, 'b, 'c>(outbound_node: &Node<'a, 'b, 'c>, inbound_node:
604604
let (tx_a, tx_b);
605605

606606
node_a.close_channel(channel_id).unwrap();
607-
node_b.handle_shutdown(&node_a.get_our_node_id(), &get_event_msg!(struct_a, MessageSendEvent::SendShutdown, node_b.get_our_node_id()));
607+
node_b.handle_shutdown(&node_a.get_our_node_id(), &InitFeatures::known(), &get_event_msg!(struct_a, MessageSendEvent::SendShutdown, node_b.get_our_node_id()));
608608

609609
let events_1 = node_b.get_and_clear_pending_msg_events();
610610
assert!(events_1.len() >= 1);
@@ -629,7 +629,7 @@ pub fn close_channel<'a, 'b, 'c>(outbound_node: &Node<'a, 'b, 'c>, inbound_node:
629629
})
630630
};
631631

632-
node_a.handle_shutdown(&node_b.get_our_node_id(), &shutdown_b);
632+
node_a.handle_shutdown(&node_b.get_our_node_id(), &InitFeatures::known(), &shutdown_b);
633633
let (as_update, bs_update) = if close_inbound_first {
634634
assert!(node_a.get_and_clear_pending_msg_events().is_empty());
635635
node_a.handle_closing_signed(&node_b.get_our_node_id(), &closing_signed_b.unwrap());

0 commit comments

Comments
 (0)