From a21decf0db4481c106a7d8ca5b5a1fd87263c9b6 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Tue, 2 Feb 2021 22:47:09 -0500 Subject: [PATCH] Struct-ify SpendableOutputDescriptor entries relevant to channels Both SpendableOutputDescriptor::DynamicOutputP2WSH and SpendableOutputDescriptor::StaticOutputCounterpartyPayment are relevant only in the context of a given channel, making them candidates for being passed into helper functions in `InMemoryChannelKeys`. This moves them into their own structs so that they can later be used standalone. --- lightning/src/chain/channelmonitor.rs | 10 +-- lightning/src/chain/keysinterface.rs | 118 +++++++++++++++----------- lightning/src/ln/chan_utils.rs | 11 ++- lightning/src/ln/functional_tests.rs | 30 +++---- lightning/src/util/macro_logger.rs | 8 +- 5 files changed, 103 insertions(+), 74 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 90f03520..91c2a3c5 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -43,7 +43,7 @@ use ln::channelmanager::{HTLCSource, PaymentPreimage, PaymentHash}; use ln::onchaintx::{OnchainTxHandler, InputDescriptors}; use chain::chaininterface::{BroadcasterInterface, FeeEstimator}; use chain::transaction::{OutPoint, TransactionData}; -use chain::keysinterface::{SpendableOutputDescriptor, ChannelKeys, KeysInterface}; +use chain::keysinterface::{SpendableOutputDescriptor, StaticCounterpartyPaymentOutputDescriptor, DynamicP2WSHOutputDescriptor, ChannelKeys, KeysInterface}; use util::logger::Logger; use util::ser::{Readable, ReadableArgs, MaybeReadable, Writer, Writeable, U48}; use util::byte_utils; @@ -2201,7 +2201,7 @@ impl ChannelMonitor { break; } else if let Some(ref broadcasted_holder_revokable_script) = self.broadcasted_holder_revokable_script { if broadcasted_holder_revokable_script.0 == outp.script_pubkey { - spendable_output = Some(SpendableOutputDescriptor::DynamicOutputP2WSH { + spendable_output = Some(SpendableOutputDescriptor::DynamicOutputP2WSH(DynamicP2WSHOutputDescriptor { outpoint: OutPoint { txid: tx.txid(), index: i as u16 }, per_commitment_point: broadcasted_holder_revokable_script.1, to_self_delay: self.on_holder_tx_csv, @@ -2209,16 +2209,16 @@ impl ChannelMonitor { revocation_pubkey: broadcasted_holder_revokable_script.2.clone(), channel_keys_id: self.channel_keys_id, channel_value_satoshis: self.channel_value_satoshis, - }); + })); break; } } else if self.counterparty_payment_script == outp.script_pubkey { - spendable_output = Some(SpendableOutputDescriptor::StaticOutputCounterpartyPayment { + spendable_output = Some(SpendableOutputDescriptor::StaticOutputCounterpartyPayment(StaticCounterpartyPaymentOutputDescriptor { outpoint: OutPoint { txid: tx.txid(), index: i as u16 }, output: outp.clone(), channel_keys_id: self.channel_keys_id, channel_value_satoshis: self.channel_value_satoshis, - }); + })); break; } else if outp.script_pubkey == self.shutdown_script { spendable_output = Some(SpendableOutputDescriptor::StaticOutput { diff --git a/lightning/src/chain/keysinterface.rs b/lightning/src/chain/keysinterface.rs index da5a99dd..8fd4463f 100644 --- a/lightning/src/chain/keysinterface.rs +++ b/lightning/src/chain/keysinterface.rs @@ -40,6 +40,57 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::io::Error; use ln::msgs::DecodeError; +/// Information about a spendable output to a P2WSH script. See +/// SpendableOutputDescriptor::DynamicOutputP2WSH for more details on how to spend this. +#[derive(Clone, Debug, PartialEq)] +pub struct DynamicP2WSHOutputDescriptor { + /// The outpoint which is spendable + pub outpoint: OutPoint, + /// Per commitment point to derive delayed_payment_key by key holder + pub per_commitment_point: PublicKey, + /// The nSequence value which must be set in the spending input to satisfy the OP_CSV in + /// the witness_script. + pub to_self_delay: u16, + /// The output which is referenced by the given outpoint + pub output: TxOut, + /// The revocation_pubkey used to derive witnessScript + pub revocation_pubkey: PublicKey, + /// Arbitrary identification information returned by a call to + /// `ChannelKeys::channel_keys_id()`. This may be useful in re-deriving keys used in + /// the channel to spend the output. + pub channel_keys_id: [u8; 32], + /// The value of the channel which this output originated from, possibly indirectly. + pub channel_value_satoshis: u64, +} +impl DynamicP2WSHOutputDescriptor { + /// The maximum length a well-formed witness spending one of these should have. + // Calculated as 1 byte legnth + 73 byte signature, 1 byte empty vec push, 1 byte length plus + // redeemscript push length. + pub const MAX_WITNESS_LENGTH: usize = 1 + 73 + 1 + chan_utils::REVOKEABLE_REDEEMSCRIPT_MAX_LENGTH + 1; +} + +/// Information about a spendable output to our "payment key". See +/// SpendableOutputDescriptor::StaticOutputCounterpartyPayment for more details on how to spend this. +#[derive(Clone, Debug, PartialEq)] +pub struct StaticCounterpartyPaymentOutputDescriptor { + /// The outpoint which is spendable + pub outpoint: OutPoint, + /// The output which is reference by the given outpoint + pub output: TxOut, + /// Arbitrary identification information returned by a call to + /// `ChannelKeys::channel_keys_id()`. This may be useful in re-deriving keys used in + /// the channel to spend the output. + pub channel_keys_id: [u8; 32], + /// The value of the channel which this transactions spends. + pub channel_value_satoshis: u64, +} +impl StaticCounterpartyPaymentOutputDescriptor { + /// The maximum length a well-formed witness spending one of these should have. + // Calculated as 1 byte legnth + 73 byte signature, 1 byte empty vec push, 1 byte length plus + // redeemscript push length. + pub const MAX_WITNESS_LENGTH: usize = 1 + 73 + 34; +} + /// When on-chain outputs are created by rust-lightning (which our counterparty is not able to /// claim at any point in the future) an event is generated which you must track and be able to /// spend on-chain. The information needed to do this is provided in this enum, including the @@ -88,25 +139,7 @@ pub enum SpendableOutputDescriptor { /// chan_utils::get_revokeable_redeemscript. // // TODO: we need to expose utility methods in KeyManager to do all the relevant derivation. - DynamicOutputP2WSH { - /// The outpoint which is spendable - outpoint: OutPoint, - /// Per commitment point to derive delayed_payment_key by key holder - per_commitment_point: PublicKey, - /// The nSequence value which must be set in the spending input to satisfy the OP_CSV in - /// the witness_script. - to_self_delay: u16, - /// The output which is referenced by the given outpoint - output: TxOut, - /// The revocation_pubkey used to derive witnessScript - revocation_pubkey: PublicKey, - /// Arbitrary identification information returned by a call to - /// `ChannelKeys::channel_keys_id()`. This may be useful in re-deriving keys used in - /// the channel to spend the output. - channel_keys_id: [u8; 32], - /// The value of the channel which this output originated from, possibly indirectly. - channel_value_satoshis: u64, - }, + DynamicOutputP2WSH(DynamicP2WSHOutputDescriptor), /// An output to a P2WPKH, spendable exclusively by our payment key (ie the private key which /// corresponds to the public key in ChannelKeys::pubkeys().payment_point). /// The witness in the spending input, is, thus, simply: @@ -114,18 +147,7 @@ pub enum SpendableOutputDescriptor { /// /// These are generally the result of our counterparty having broadcast the current state, /// allowing us to claim the non-HTLC-encumbered outputs immediately. - StaticOutputCounterpartyPayment { - /// The outpoint which is spendable - outpoint: OutPoint, - /// The output which is reference by the given outpoint - output: TxOut, - /// Arbitrary identification information returned by a call to - /// `ChannelKeys::channel_keys_id()`. This may be useful in re-deriving keys used in - /// the channel to spend the output. - channel_keys_id: [u8; 32], - /// The value of the channel which this transactions spends. - channel_value_satoshis: u64, - } + StaticOutputCounterpartyPayment(StaticCounterpartyPaymentOutputDescriptor), } impl Writeable for SpendableOutputDescriptor { @@ -136,22 +158,22 @@ impl Writeable for SpendableOutputDescriptor { outpoint.write(writer)?; output.write(writer)?; }, - &SpendableOutputDescriptor::DynamicOutputP2WSH { ref outpoint, ref per_commitment_point, ref to_self_delay, ref output, ref revocation_pubkey, ref channel_keys_id, channel_value_satoshis } => { + &SpendableOutputDescriptor::DynamicOutputP2WSH(ref descriptor) => { 1u8.write(writer)?; - outpoint.write(writer)?; - per_commitment_point.write(writer)?; - to_self_delay.write(writer)?; - output.write(writer)?; - revocation_pubkey.write(writer)?; - channel_keys_id.write(writer)?; - channel_value_satoshis.write(writer)?; + descriptor.outpoint.write(writer)?; + descriptor.per_commitment_point.write(writer)?; + descriptor.to_self_delay.write(writer)?; + descriptor.output.write(writer)?; + descriptor.revocation_pubkey.write(writer)?; + descriptor.channel_keys_id.write(writer)?; + descriptor.channel_value_satoshis.write(writer)?; }, - &SpendableOutputDescriptor::StaticOutputCounterpartyPayment { ref outpoint, ref output, ref channel_keys_id, channel_value_satoshis } => { + &SpendableOutputDescriptor::StaticOutputCounterpartyPayment(ref descriptor) => { 2u8.write(writer)?; - outpoint.write(writer)?; - output.write(writer)?; - channel_keys_id.write(writer)?; - channel_value_satoshis.write(writer)?; + descriptor.outpoint.write(writer)?; + descriptor.output.write(writer)?; + descriptor.channel_keys_id.write(writer)?; + descriptor.channel_value_satoshis.write(writer)?; }, } Ok(()) @@ -165,7 +187,7 @@ impl Readable for SpendableOutputDescriptor { outpoint: Readable::read(reader)?, output: Readable::read(reader)?, }), - 1u8 => Ok(SpendableOutputDescriptor::DynamicOutputP2WSH { + 1u8 => Ok(SpendableOutputDescriptor::DynamicOutputP2WSH(DynamicP2WSHOutputDescriptor { outpoint: Readable::read(reader)?, per_commitment_point: Readable::read(reader)?, to_self_delay: Readable::read(reader)?, @@ -173,13 +195,13 @@ impl Readable for SpendableOutputDescriptor { revocation_pubkey: Readable::read(reader)?, channel_keys_id: Readable::read(reader)?, channel_value_satoshis: Readable::read(reader)?, - }), - 2u8 => Ok(SpendableOutputDescriptor::StaticOutputCounterpartyPayment { + })), + 2u8 => Ok(SpendableOutputDescriptor::StaticOutputCounterpartyPayment(StaticCounterpartyPaymentOutputDescriptor { outpoint: Readable::read(reader)?, output: Readable::read(reader)?, channel_keys_id: Readable::read(reader)?, channel_value_satoshis: Readable::read(reader)?, - }), + })), _ => Err(DecodeError::InvalidValue), } } diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index b1171f54..f86c53bc 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -384,11 +384,16 @@ impl TxCreationKeys { } } +/// The maximum length of a script returned by get_revokeable_redeemscript. +// Calculated as 6 bytes of opcodes, 1 byte push plus 2 bytes for contest_delay, and two public +// keys of 33 bytes (+ 1 push). +pub const REVOKEABLE_REDEEMSCRIPT_MAX_LENGTH: usize = 6 + 3 + 34*2; + /// A script either spendable by the revocation /// key or the broadcaster_delayed_payment_key and satisfying the relative-locktime OP_CSV constrain. /// Encumbering a `to_holder` output on a commitment transaction or 2nd-stage HTLC transactions. pub fn get_revokeable_redeemscript(revocation_key: &PublicKey, contest_delay: u16, broadcaster_delayed_payment_key: &PublicKey) -> Script { - Builder::new().push_opcode(opcodes::all::OP_IF) + let res = Builder::new().push_opcode(opcodes::all::OP_IF) .push_slice(&revocation_key.serialize()) .push_opcode(opcodes::all::OP_ELSE) .push_int(contest_delay as i64) @@ -397,7 +402,9 @@ pub fn get_revokeable_redeemscript(revocation_key: &PublicKey, contest_delay: u1 .push_slice(&broadcaster_delayed_payment_key.serialize()) .push_opcode(opcodes::all::OP_ENDIF) .push_opcode(opcodes::all::OP_CHECKSIG) - .into_script() + .into_script(); + debug_assert!(res.len() <= REVOKEABLE_REDEEMSCRIPT_MAX_LENGTH); + res } #[derive(Clone, PartialEq)] diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 11a202d5..b3f44044 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -4662,17 +4662,17 @@ macro_rules! check_spendable_outputs { Event::SpendableOutputs { ref outputs } => { for outp in outputs { match *outp { - SpendableOutputDescriptor::StaticOutputCounterpartyPayment { ref outpoint, ref output, ref channel_keys_id, channel_value_satoshis } => { - assert_eq!(channel_value_satoshis, $chan_value); + SpendableOutputDescriptor::StaticOutputCounterpartyPayment(ref descriptor) => { + assert_eq!(descriptor.channel_value_satoshis, $chan_value); let input = TxIn { - previous_output: outpoint.into_bitcoin_outpoint(), + previous_output: descriptor.outpoint.into_bitcoin_outpoint(), script_sig: Script::new(), sequence: 0, witness: Vec::new(), }; let outp = TxOut { script_pubkey: Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), - value: output.value, + value: descriptor.output.value, }; let mut spend_tx = Transaction { version: 2, @@ -4682,27 +4682,27 @@ macro_rules! check_spendable_outputs { }; spend_tx.output[0].value -= (spend_tx.get_weight() + 2 + 1 + 73 + 35 + 3) as u64 / 4; // (Max weight + 3 (to round up)) / 4 let secp_ctx = Secp256k1::new(); - let keys = $keysinterface.derive_channel_keys($chan_value, channel_keys_id); + let keys = $keysinterface.derive_channel_keys($chan_value, &descriptor.channel_keys_id); let remotepubkey = keys.pubkeys().payment_point; let witness_script = Address::p2pkh(&::bitcoin::PublicKey{compressed: true, key: remotepubkey}, Network::Testnet).script_pubkey(); - let sighash = Message::from_slice(&bip143::SigHashCache::new(&spend_tx).signature_hash(0, &witness_script, output.value, SigHashType::All)[..]).unwrap(); + let sighash = Message::from_slice(&bip143::SigHashCache::new(&spend_tx).signature_hash(0, &witness_script, descriptor.output.value, SigHashType::All)[..]).unwrap(); let remotesig = secp_ctx.sign(&sighash, &keys.inner.payment_key); spend_tx.input[0].witness.push(remotesig.serialize_der().to_vec()); spend_tx.input[0].witness[0].push(SigHashType::All as u8); spend_tx.input[0].witness.push(remotepubkey.serialize().to_vec()); txn.push(spend_tx); }, - SpendableOutputDescriptor::DynamicOutputP2WSH { ref outpoint, ref per_commitment_point, ref to_self_delay, ref output, ref revocation_pubkey, ref channel_keys_id, channel_value_satoshis } => { - assert_eq!(channel_value_satoshis, $chan_value); + SpendableOutputDescriptor::DynamicOutputP2WSH(ref descriptor) => { + assert_eq!(descriptor.channel_value_satoshis, $chan_value); let input = TxIn { - previous_output: outpoint.into_bitcoin_outpoint(), + previous_output: descriptor.outpoint.into_bitcoin_outpoint(), script_sig: Script::new(), - sequence: *to_self_delay as u32, + sequence: descriptor.to_self_delay as u32, witness: Vec::new(), }; let outp = TxOut { script_pubkey: Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), - value: output.value, + value: descriptor.output.value, }; let mut spend_tx = Transaction { version: 2, @@ -4711,13 +4711,13 @@ macro_rules! check_spendable_outputs { output: vec![outp], }; let secp_ctx = Secp256k1::new(); - let keys = $keysinterface.derive_channel_keys($chan_value, channel_keys_id); - if let Ok(delayed_payment_key) = chan_utils::derive_private_key(&secp_ctx, &per_commitment_point, &keys.inner.delayed_payment_base_key) { + let keys = $keysinterface.derive_channel_keys($chan_value, &descriptor.channel_keys_id); + if let Ok(delayed_payment_key) = chan_utils::derive_private_key(&secp_ctx, &descriptor.per_commitment_point, &keys.inner.delayed_payment_base_key) { let delayed_payment_pubkey = PublicKey::from_secret_key(&secp_ctx, &delayed_payment_key); - let witness_script = chan_utils::get_revokeable_redeemscript(revocation_pubkey, *to_self_delay, &delayed_payment_pubkey); + let witness_script = chan_utils::get_revokeable_redeemscript(&descriptor.revocation_pubkey, descriptor.to_self_delay, &delayed_payment_pubkey); spend_tx.output[0].value -= (spend_tx.get_weight() + 2 + 1 + 73 + 1 + witness_script.len() + 1 + 3) as u64 / 4; // (Max weight + 3 (to round up)) / 4 - let sighash = Message::from_slice(&bip143::SigHashCache::new(&spend_tx).signature_hash(0, &witness_script, output.value, SigHashType::All)[..]).unwrap(); + let sighash = Message::from_slice(&bip143::SigHashCache::new(&spend_tx).signature_hash(0, &witness_script, descriptor.output.value, SigHashType::All)[..]).unwrap(); let local_delayedsig = secp_ctx.sign(&sighash, &delayed_payment_key); spend_tx.input[0].witness.push(local_delayedsig.serialize_der().to_vec()); spend_tx.input[0].witness[0].push(SigHashType::All as u8); diff --git a/lightning/src/util/macro_logger.rs b/lightning/src/util/macro_logger.rs index e667c4d7..222964b0 100644 --- a/lightning/src/util/macro_logger.rs +++ b/lightning/src/util/macro_logger.rs @@ -138,11 +138,11 @@ impl<'a> std::fmt::Display for DebugSpendable<'a> { &SpendableOutputDescriptor::StaticOutput { ref outpoint, .. } => { write!(f, "StaticOutput {}:{} marked for spending", outpoint.txid, outpoint.index)?; } - &SpendableOutputDescriptor::DynamicOutputP2WSH { ref outpoint, .. } => { - write!(f, "DynamicOutputP2WSH {}:{} marked for spending", outpoint.txid, outpoint.index)?; + &SpendableOutputDescriptor::DynamicOutputP2WSH(ref descriptor) => { + write!(f, "DynamicOutputP2WSH {}:{} marked for spending", descriptor.outpoint.txid, descriptor.outpoint.index)?; } - &SpendableOutputDescriptor::StaticOutputCounterpartyPayment { ref outpoint, .. } => { - write!(f, "DynamicOutputP2WPKH {}:{} marked for spending", outpoint.txid, outpoint.index)?; + &SpendableOutputDescriptor::StaticOutputCounterpartyPayment(ref descriptor) => { + write!(f, "DynamicOutputP2WPKH {}:{} marked for spending", descriptor.outpoint.txid, descriptor.outpoint.index)?; } } Ok(()) -- 2.30.2