diff --git a/lightning/src/chain/keysinterface.rs b/lightning/src/chain/keysinterface.rs index 73c22684c54..584b8428a18 100644 --- a/lightning/src/chain/keysinterface.rs +++ b/lightning/src/chain/keysinterface.rs @@ -2,7 +2,7 @@ //! spendable on-chain outputs which the user owns and is responsible for using just as any other //! on-chain output which is theirs. -use bitcoin::blockdata::transaction::{Transaction, OutPoint, TxOut}; +use bitcoin::blockdata::transaction::{Transaction, OutPoint, TxOut, SigHashType}; use bitcoin::blockdata::script::{Script, Builder}; use bitcoin::blockdata::opcodes; use bitcoin::network::constants::Network; @@ -26,6 +26,7 @@ use util::ser::{Writeable, Writer, Readable}; use ln::chan_utils; use ln::chan_utils::{TxCreationKeys, HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys, LocalCommitmentTransaction}; use ln::msgs; +use ln::channelmanager::PaymentPreimage; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -245,6 +246,12 @@ pub trait ChannelKeys : Send+Clone { /// return value must contain a signature. fn sign_local_commitment_htlc_transactions(&self, local_commitment_tx: &LocalCommitmentTransaction, local_csv: u16, secp_ctx: &Secp256k1) -> Result>, ()>; + /// Signs a justice transaction. + fn sign_justice_transaction(&self, bumped_tx: &mut Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_key: &SecretKey, revocation_pubkey: &PublicKey, is_htlc: bool, secp_ctx: &Secp256k1); + + /// Signs a remote htlc transaction. + fn sign_remote_htlc_transaction(&self, bumped_tx: &mut Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_point: &PublicKey, preimage: &Option, secp_ctx: &Secp256k1); + /// Create a signature for a (proposed) closing transaction. /// /// Note that, due to rounding, there may be one "missing" satoshi, and either party may have @@ -393,6 +400,38 @@ impl ChannelKeys for InMemoryChannelKeys { local_commitment_tx.get_htlc_sigs(&self.htlc_base_key, local_csv, secp_ctx) } + fn sign_justice_transaction(&self, bumped_tx: &mut Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_key: &SecretKey, revocation_pubkey: &PublicKey, is_htlc: bool, secp_ctx: &Secp256k1) { + if let Ok(revocation_key) = chan_utils::derive_private_revocation_key(&secp_ctx, &per_commitment_key, &self.revocation_base_key) { + let sighash_parts = bip143::SighashComponents::new(&bumped_tx); + let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[input], &witness_script, amount)[..]); + let sig = secp_ctx.sign(&sighash, &revocation_key); + bumped_tx.input[input].witness.push(sig.serialize_der().to_vec()); + bumped_tx.input[input].witness[0].push(SigHashType::All as u8); + if is_htlc { + bumped_tx.input[input].witness.push(revocation_pubkey.clone().serialize().to_vec()); + } else { + bumped_tx.input[input].witness.push(vec!(1)); + } + bumped_tx.input[input].witness.push(witness_script.clone().into_bytes()); + } + } + + fn sign_remote_htlc_transaction(&self, bumped_tx: &mut Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_point: &PublicKey, preimage: &Option, secp_ctx: &Secp256k1) { + if let Ok(htlc_key) = chan_utils::derive_private_key(&secp_ctx, &per_commitment_point, &self.htlc_base_key) { + let sighash_parts = bip143::SighashComponents::new(&bumped_tx); + let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[input], &witness_script, amount)[..]); + let sig = secp_ctx.sign(&sighash, &htlc_key); + bumped_tx.input[input].witness.push(sig.serialize_der().to_vec()); + bumped_tx.input[input].witness[0].push(SigHashType::All as u8); + if let &Some(preimage) = preimage { + bumped_tx.input[input].witness.push(preimage.0.to_vec()); + } else { + bumped_tx.input[input].witness.push(vec![0]); + } + bumped_tx.input[input].witness.push(witness_script.clone().into_bytes()); + } + } + fn sign_closing_transaction(&self, closing_tx: &Transaction, secp_ctx: &Secp256k1) -> Result { if closing_tx.input.len() != 1 { return Err(()); } if closing_tx.input[0].witness.len() != 0 { return Err(()); } diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 62d3103337d..3794e8a9402 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -198,7 +198,7 @@ pub(super) fn derive_public_key(secp_ctx: &Secp256k1, /// Derives a revocation key from its constituent parts. /// Note that this is infallible iff we trust that at least one of the two input keys are randomly /// generated (ie our own). -pub(super) fn derive_private_revocation_key(secp_ctx: &Secp256k1, per_commitment_secret: &SecretKey, revocation_base_secret: &SecretKey) -> Result { +pub fn derive_private_revocation_key(secp_ctx: &Secp256k1, per_commitment_secret: &SecretKey, revocation_base_secret: &SecretKey) -> Result { let revocation_base_point = PublicKey::from_secret_key(&secp_ctx, &revocation_base_secret); let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret); diff --git a/lightning/src/ln/channelmonitor.rs b/lightning/src/ln/channelmonitor.rs index c417671eca0..1096aef78df 100644 --- a/lightning/src/ln/channelmonitor.rs +++ b/lightning/src/ln/channelmonitor.rs @@ -32,7 +32,7 @@ use ln::msgs::DecodeError; use ln::chan_utils; use ln::chan_utils::{CounterpartyCommitmentSecrets, HTLCOutputInCommitment, LocalCommitmentTransaction, HTLCType}; use ln::channelmanager::{HTLCSource, PaymentPreimage, PaymentHash}; -use ln::onchaintx::OnchainTxHandler; +use ln::onchaintx::{OnchainTxHandler, InputDescriptors}; use chain::chaininterface::{ChainListener, ChainWatchInterface, BroadcasterInterface, FeeEstimator}; use chain::transaction::OutPoint; use chain::keysinterface::{SpendableOutputDescriptor, ChannelKeys}; @@ -425,15 +425,13 @@ struct LocalSignedTx { #[derive(Clone, PartialEq)] pub(crate) enum InputMaterial { Revoked { - witness_script: Script, - pubkey: Option, - key: SecretKey, - is_htlc: bool, + per_commitment_point: PublicKey, + per_commitment_key: SecretKey, + input_descriptor: InputDescriptors, amount: u64, }, RemoteHTLC { - witness_script: Script, - key: SecretKey, + per_commitment_point: PublicKey, preimage: Option, amount: u64, locktime: u32, @@ -450,18 +448,16 @@ pub(crate) enum InputMaterial { impl Writeable for InputMaterial { fn write(&self, writer: &mut W) -> Result<(), ::std::io::Error> { match self { - &InputMaterial::Revoked { ref witness_script, ref pubkey, ref key, ref is_htlc, ref amount} => { + &InputMaterial::Revoked { ref per_commitment_point, ref per_commitment_key, ref input_descriptor, ref amount} => { writer.write_all(&[0; 1])?; - witness_script.write(writer)?; - pubkey.write(writer)?; - writer.write_all(&key[..])?; - is_htlc.write(writer)?; + per_commitment_point.write(writer)?; + writer.write_all(&per_commitment_key[..])?; + input_descriptor.write(writer)?; writer.write_all(&byte_utils::be64_to_array(*amount))?; }, - &InputMaterial::RemoteHTLC { ref witness_script, ref key, ref preimage, ref amount, ref locktime } => { + &InputMaterial::RemoteHTLC { ref per_commitment_point, ref preimage, ref amount, ref locktime } => { writer.write_all(&[1; 1])?; - witness_script.write(writer)?; - key.write(writer)?; + per_commitment_point.write(writer)?; preimage.write(writer)?; writer.write_all(&byte_utils::be64_to_array(*amount))?; writer.write_all(&byte_utils::be32_to_array(*locktime))?; @@ -484,28 +480,24 @@ impl Readable for InputMaterial { fn read(reader: &mut R) -> Result { let input_material = match ::read(reader)? { 0 => { - let witness_script = Readable::read(reader)?; - let pubkey = Readable::read(reader)?; - let key = Readable::read(reader)?; - let is_htlc = Readable::read(reader)?; + let per_commitment_point = Readable::read(reader)?; + let per_commitment_key = Readable::read(reader)?; + let input_descriptor = Readable::read(reader)?; let amount = Readable::read(reader)?; InputMaterial::Revoked { - witness_script, - pubkey, - key, - is_htlc, + per_commitment_point, + per_commitment_key, + input_descriptor, amount } }, 1 => { - let witness_script = Readable::read(reader)?; - let key = Readable::read(reader)?; + let per_commitment_point = Readable::read(reader)?; let preimage = Readable::read(reader)?; let amount = Readable::read(reader)?; let locktime = Readable::read(reader)?; InputMaterial::RemoteHTLC { - witness_script, - key, + per_commitment_point, preimage, amount, locktime @@ -1064,7 +1056,7 @@ impl ChannelMonitor { let our_channel_close_key_hash = Hash160::hash(&shutdown_pubkey.serialize()); let shutdown_script = Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0).push_slice(&our_channel_close_key_hash[..]).into_script(); - let mut onchain_tx_handler = OnchainTxHandler::new(destination_script.clone(), keys.clone(), their_to_self_delay, logger.clone()); + let mut onchain_tx_handler = OnchainTxHandler::new(destination_script.clone(), keys.clone(), their_to_self_delay, their_delayed_payment_base_key.clone(), their_htlc_base_key.clone(), our_to_self_delay, logger.clone()); let local_tx_sequence = initial_local_commitment_tx.unsigned_tx.input[0].sequence as u64; let local_tx_locktime = initial_local_commitment_tx.unsigned_tx.lock_time as u64; @@ -1099,8 +1091,8 @@ impl ChannelMonitor { current_remote_commitment_txid: None, prev_remote_commitment_txid: None, - their_htlc_base_key: their_htlc_base_key.clone(), - their_delayed_payment_base_key: their_delayed_payment_base_key.clone(), + their_htlc_base_key: *their_htlc_base_key, + their_delayed_payment_base_key: *their_delayed_payment_base_key, funding_redeemscript, channel_value_satoshis: channel_value_satoshis, their_cur_revocation_points: None, @@ -1205,7 +1197,7 @@ impl ChannelMonitor { log_trace!(self, "New potential remote commitment transaction: {}", encode::serialize_hex(unsigned_commitment_tx)); self.prev_remote_commitment_txid = self.current_remote_commitment_txid.take(); self.current_remote_commitment_txid = Some(new_txid); - self.remote_claimable_outpoints.insert(new_txid, htlc_outputs); + self.remote_claimable_outpoints.insert(new_txid, htlc_outputs.clone()); self.current_remote_commitment_number = commitment_number; //TODO: Merge this into the other per-remote-transaction output storage stuff match self.their_cur_revocation_points { @@ -1226,6 +1218,13 @@ impl ChannelMonitor { self.their_cur_revocation_points = Some((commitment_number, their_revocation_point, None)); } } + let mut htlcs = Vec::with_capacity(htlc_outputs.len()); + for htlc in htlc_outputs { + if htlc.0.transaction_output_index.is_some() { + htlcs.push(htlc.0); + } + } + self.onchain_tx_handler.provide_latest_remote_tx(new_txid, htlcs); } pub(super) fn provide_rescue_remote_commitment_tx_info(&mut self, their_revocation_point: PublicKey) { @@ -1448,11 +1447,8 @@ impl ChannelMonitor { let per_commitment_key = ignore_error!(SecretKey::from_slice(&secret)); let per_commitment_point = PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key); let revocation_pubkey = ignore_error!(chan_utils::derive_public_revocation_key(&self.secp_ctx, &per_commitment_point, &self.keys.pubkeys().revocation_basepoint)); - let revocation_key = ignore_error!(chan_utils::derive_private_revocation_key(&self.secp_ctx, &per_commitment_key, &self.keys.revocation_base_key())); - let b_htlc_key = ignore_error!(chan_utils::derive_public_key(&self.secp_ctx, &per_commitment_point, &self.keys.pubkeys().htlc_basepoint)); let local_payment_key = ignore_error!(chan_utils::derive_private_key(&self.secp_ctx, &per_commitment_point, &self.keys.payment_base_key())); let delayed_key = ignore_error!(chan_utils::derive_public_key(&self.secp_ctx, &PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key), &self.their_delayed_payment_base_key)); - let a_htlc_key = ignore_error!(chan_utils::derive_public_key(&self.secp_ctx, &PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key), &self.their_htlc_base_key)); let revokeable_redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey, self.our_to_self_delay, &delayed_key); let revokeable_p2wsh = revokeable_redeemscript.to_v0_p2wsh(); @@ -1467,7 +1463,7 @@ impl ChannelMonitor { // First, process non-htlc outputs (to_local & to_remote) for (idx, outp) in tx.output.iter().enumerate() { if outp.script_pubkey == revokeable_p2wsh { - let witness_data = InputMaterial::Revoked { witness_script: revokeable_redeemscript.clone(), pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: false, amount: outp.value }; + let witness_data = InputMaterial::Revoked { per_commitment_point, per_commitment_key, input_descriptor: InputDescriptors::RevokedOutput, amount: outp.value }; claimable_outpoints.push(ClaimRequest { absolute_timelock: height + self.our_to_self_delay as u32, aggregable: true, outpoint: BitcoinOutPoint { txid: commitment_txid, vout: idx as u32 }, witness_data}); } } @@ -1476,13 +1472,11 @@ impl ChannelMonitor { if let Some(ref per_commitment_data) = per_commitment_option { for (_, &(ref htlc, _)) in per_commitment_data.iter().enumerate() { if let Some(transaction_output_index) = htlc.transaction_output_index { - let expected_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&htlc, &a_htlc_key, &b_htlc_key, &revocation_pubkey); if transaction_output_index as usize >= tx.output.len() || - tx.output[transaction_output_index as usize].value != htlc.amount_msat / 1000 || - tx.output[transaction_output_index as usize].script_pubkey != expected_script.to_v0_p2wsh() { + tx.output[transaction_output_index as usize].value != htlc.amount_msat / 1000 { return (claimable_outpoints, (commitment_txid, watch_outputs)); // Corrupted per_commitment_data, fuck this user } - let witness_data = InputMaterial::Revoked { witness_script: expected_script, pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: true, amount: tx.output[transaction_output_index as usize].value }; + let witness_data = InputMaterial::Revoked { per_commitment_point, per_commitment_key, input_descriptor: if htlc.offered { InputDescriptors::RevokedOfferedHTLC } else { InputDescriptors::RevokedReceivedHTLC }, amount: tx.output[transaction_output_index as usize].value }; claimable_outpoints.push(ClaimRequest { absolute_timelock: htlc.cltv_expiry, aggregable: true, outpoint: BitcoinOutPoint { txid: commitment_txid, vout: transaction_output_index }, witness_data }); } } @@ -1600,10 +1594,6 @@ impl ChannelMonitor { if revocation_points.0 == commitment_number + 1 { Some(point) } else { None } } else { None }; if let Some(revocation_point) = revocation_point_option { - let revocation_pubkey = ignore_error!(chan_utils::derive_public_revocation_key(&self.secp_ctx, revocation_point, &self.keys.pubkeys().revocation_basepoint)); - let b_htlc_key = ignore_error!(chan_utils::derive_public_key(&self.secp_ctx, revocation_point, &self.keys.pubkeys().htlc_basepoint)); - let htlc_privkey = ignore_error!(chan_utils::derive_private_key(&self.secp_ctx, revocation_point, &self.keys.htlc_base_key())); - let a_htlc_key = ignore_error!(chan_utils::derive_public_key(&self.secp_ctx, revocation_point, &self.their_htlc_base_key)); let local_payment_key = ignore_error!(chan_utils::derive_private_key(&self.secp_ctx, revocation_point, &self.keys.payment_base_key())); self.broadcasted_remote_payment_script = { @@ -1616,16 +1606,14 @@ impl ChannelMonitor { // Then, try to find htlc outputs for (_, &(ref htlc, _)) in per_commitment_data.iter().enumerate() { if let Some(transaction_output_index) = htlc.transaction_output_index { - let expected_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&htlc, &a_htlc_key, &b_htlc_key, &revocation_pubkey); if transaction_output_index as usize >= tx.output.len() || - tx.output[transaction_output_index as usize].value != htlc.amount_msat / 1000 || - tx.output[transaction_output_index as usize].script_pubkey != expected_script.to_v0_p2wsh() { + tx.output[transaction_output_index as usize].value != htlc.amount_msat / 1000 { return (claimable_outpoints, (commitment_txid, watch_outputs)); // Corrupted per_commitment_data, fuck this user } let preimage = if htlc.offered { if let Some(p) = self.payment_preimages.get(&htlc.payment_hash) { Some(*p) } else { None } } else { None }; let aggregable = if !htlc.offered { false } else { true }; if preimage.is_some() || !htlc.offered { - let witness_data = InputMaterial::RemoteHTLC { witness_script: expected_script, key: htlc_privkey, preimage, amount: htlc.amount_msat / 1000, locktime: htlc.cltv_expiry }; + let witness_data = InputMaterial::RemoteHTLC { per_commitment_point: *revocation_point, preimage, amount: htlc.amount_msat / 1000, locktime: htlc.cltv_expiry }; claimable_outpoints.push(ClaimRequest { absolute_timelock: htlc.cltv_expiry, aggregable, outpoint: BitcoinOutPoint { txid: commitment_txid, vout: transaction_output_index }, witness_data }); } } @@ -1655,13 +1643,9 @@ impl ChannelMonitor { let secret = if let Some(secret) = self.get_secret(commitment_number) { secret } else { return (Vec::new(), None); }; let per_commitment_key = ignore_error!(SecretKey::from_slice(&secret)); let per_commitment_point = PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key); - let revocation_pubkey = ignore_error!(chan_utils::derive_public_revocation_key(&self.secp_ctx, &per_commitment_point, &self.keys.pubkeys().revocation_basepoint)); - let revocation_key = ignore_error!(chan_utils::derive_private_revocation_key(&self.secp_ctx, &per_commitment_key, &self.keys.revocation_base_key())); - let delayed_key = ignore_error!(chan_utils::derive_public_key(&self.secp_ctx, &per_commitment_point, &self.their_delayed_payment_base_key)); - let redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey, self.our_to_self_delay, &delayed_key); log_trace!(self, "Remote HTLC broadcast {}:{}", htlc_txid, 0); - let witness_data = InputMaterial::Revoked { witness_script: redeemscript, pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: false, amount: tx.output[0].value }; + let witness_data = InputMaterial::Revoked { per_commitment_point, per_commitment_key, input_descriptor: InputDescriptors::RevokedOutput, amount: tx.output[0].value }; let claimable_outpoints = vec!(ClaimRequest { absolute_timelock: height + self.our_to_self_delay as u32, aggregable: true, outpoint: BitcoinOutPoint { txid: htlc_txid, vout: 0}, witness_data }); (claimable_outpoints, Some((htlc_txid, tx.output.clone()))) } diff --git a/lightning/src/ln/onchaintx.rs b/lightning/src/ln/onchaintx.rs index 49572f2cb8b..829449ded7a 100644 --- a/lightning/src/ln/onchaintx.rs +++ b/lightning/src/ln/onchaintx.rs @@ -3,20 +3,21 @@ //! OnchainTxHandler objetcs are fully-part of ChannelMonitor and encapsulates all //! building, tracking, bumping and notifications functions. -use bitcoin::blockdata::transaction::{Transaction, TxIn, TxOut, SigHashType}; +use bitcoin::blockdata::transaction::{Transaction, TxIn, TxOut}; use bitcoin::blockdata::transaction::OutPoint as BitcoinOutPoint; use bitcoin::blockdata::script::Script; -use bitcoin::util::bip143; use bitcoin_hashes::sha256d::Hash as Sha256dHash; use secp256k1::{Secp256k1, Signature}; +use secp256k1::key::PublicKey; use secp256k1; use ln::msgs::DecodeError; use ln::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER, InputMaterial, ClaimRequest}; use ln::channelmanager::PaymentPreimage; -use ln::chan_utils::{HTLCType, LocalCommitmentTransaction}; +use ln::chan_utils; +use ln::chan_utils::{TxCreationKeys, LocalCommitmentTransaction, HTLCOutputInCommitment}; use chain::chaininterface::{FeeEstimator, BroadcasterInterface, ConfirmationTarget, MIN_RELAY_FEE_SAT_PER_1000_WEIGHT}; use chain::keysinterface::ChannelKeys; use util::logger::Logger; @@ -48,6 +49,14 @@ enum OnchainEvent { } } +/// Cache remote basepoint to compute any transaction on +/// remote outputs, either justice or preimage/timeout transactions. +struct RemoteTxCache { + remote_delayed_payment_base_key: PublicKey, + remote_htlc_base_key: PublicKey, + per_htlc: HashMap> +} + /// Higher-level cache structure needed to re-generate bumped claim txn if needed #[derive(Clone, PartialEq)] pub struct ClaimTxBumpMaterial { @@ -93,8 +102,8 @@ impl Readable for ClaimTxBumpMaterial { } } -#[derive(PartialEq)] -pub(super) enum InputDescriptors { +#[derive(PartialEq, Clone, Copy)] +pub(crate) enum InputDescriptors { RevokedOfferedHTLC, RevokedReceivedHTLC, OfferedHTLC, @@ -102,6 +111,53 @@ pub(super) enum InputDescriptors { RevokedOutput, // either a revoked to_local output on commitment tx, a revoked HTLC-Timeout output or a revoked HTLC-Success output } +impl Writeable for InputDescriptors { + fn write(&self, writer: &mut W) -> Result<(), ::std::io::Error> { + match self { + &InputDescriptors::RevokedOfferedHTLC => { + writer.write_all(&[0; 1])?; + }, + &InputDescriptors::RevokedReceivedHTLC => { + writer.write_all(&[1; 1])?; + }, + &InputDescriptors::OfferedHTLC => { + writer.write_all(&[2; 1])?; + }, + &InputDescriptors::ReceivedHTLC => { + writer.write_all(&[3; 1])?; + } + &InputDescriptors::RevokedOutput => { + writer.write_all(&[4; 1])?; + } + } + Ok(()) + } +} + +impl Readable for InputDescriptors { + fn read(reader: &mut R) -> Result { + let input_descriptor = match ::read(reader)? { + 0 => { + InputDescriptors::RevokedOfferedHTLC + }, + 1 => { + InputDescriptors::RevokedReceivedHTLC + }, + 2 => { + InputDescriptors::OfferedHTLC + }, + 3 => { + InputDescriptors::ReceivedHTLC + }, + 4 => { + InputDescriptors::RevokedOutput + } + _ => return Err(DecodeError::InvalidValue), + }; + Ok(input_descriptor) + } +} + macro_rules! subtract_high_prio_fee { ($self: ident, $fee_estimator: expr, $value: expr, $predicted_weight: expr, $used_feerate: expr) => { { @@ -195,6 +251,8 @@ pub struct OnchainTxHandler { prev_local_commitment: Option, prev_local_htlc_sigs: Option>>, local_csv: u16, + remote_tx_cache: RemoteTxCache, + remote_csv: u16, key_storage: ChanSigner, @@ -241,6 +299,18 @@ impl OnchainTxHandler { self.local_csv.write(writer)?; + self.remote_tx_cache.remote_delayed_payment_base_key.write(writer)?; + self.remote_tx_cache.remote_htlc_base_key.write(writer)?; + writer.write_all(&byte_utils::be64_to_array(self.remote_tx_cache.per_htlc.len() as u64))?; + for (ref txid, ref htlcs) in self.remote_tx_cache.per_htlc.iter() { + writer.write_all(&txid[..])?; + writer.write_all(&byte_utils::be64_to_array(htlcs.len() as u64))?; + for &ref htlc in htlcs.iter() { + htlc.write(writer)?; + } + } + self.remote_csv.write(writer)?; + self.key_storage.write(writer)?; writer.write_all(&byte_utils::be64_to_array(self.pending_claim_requests.len() as u64))?; @@ -289,6 +359,31 @@ impl ReadableArgs> for OnchainTx let local_csv = Readable::read(reader)?; + let remote_tx_cache = { + let remote_delayed_payment_base_key = Readable::read(reader)?; + let remote_htlc_base_key = Readable::read(reader)?; + let per_htlc_len: u64 = Readable::read(reader)?; + let mut per_htlc = HashMap::with_capacity(cmp::min(per_htlc_len as usize, MAX_ALLOC_SIZE / 64)); + for _ in 0..per_htlc_len { + let txid: Sha256dHash = Readable::read(reader)?; + let htlcs_count: u64 = Readable::read(reader)?; + let mut htlcs = Vec::with_capacity(cmp::min(htlcs_count as usize, MAX_ALLOC_SIZE / 32)); + for _ in 0..htlcs_count { + let htlc = Readable::read(reader)?; + htlcs.push(htlc); + } + if let Some(_) = per_htlc.insert(txid, htlcs) { + return Err(DecodeError::InvalidValue); + } + } + RemoteTxCache { + remote_delayed_payment_base_key, + remote_htlc_base_key, + per_htlc, + } + }; + let remote_csv = Readable::read(reader)?; + let key_storage = Readable::read(reader)?; let pending_claim_requests_len: u64 = Readable::read(reader)?; @@ -341,6 +436,8 @@ impl ReadableArgs> for OnchainTx prev_local_commitment, prev_local_htlc_sigs, local_csv, + remote_tx_cache, + remote_csv, key_storage, claimable_outpoints, pending_claim_requests, @@ -352,10 +449,16 @@ impl ReadableArgs> for OnchainTx } impl OnchainTxHandler { - pub(super) fn new(destination_script: Script, keys: ChanSigner, local_csv: u16, logger: Arc) -> Self { + pub(super) fn new(destination_script: Script, keys: ChanSigner, local_csv: u16, remote_delayed_payment_base_key: PublicKey, remote_htlc_base_key: PublicKey, remote_csv: u16, logger: Arc) -> Self { let key_storage = keys; + let remote_tx_cache = RemoteTxCache { + remote_delayed_payment_base_key, + remote_htlc_base_key, + per_htlc: HashMap::new(), + }; + OnchainTxHandler { destination_script, local_commitment: None, @@ -363,6 +466,8 @@ impl OnchainTxHandler { prev_local_commitment: None, prev_local_htlc_sigs: None, local_csv, + remote_tx_cache, + remote_csv, key_storage, pending_claim_requests: HashMap::new(), claimable_outpoints: HashMap::new(), @@ -490,8 +595,8 @@ impl OnchainTxHandler { let mut dynamic_fee = true; for per_outp_material in cached_claim_datas.per_input_material.values() { match per_outp_material { - &InputMaterial::Revoked { ref witness_script, ref is_htlc, ref amount, .. } => { - inputs_witnesses_weight += Self::get_witnesses_weight(if !is_htlc { &[InputDescriptors::RevokedOutput] } else if HTLCType::scriptlen_to_htlctype(witness_script.len()) == Some(HTLCType::OfferedHTLC) { &[InputDescriptors::RevokedOfferedHTLC] } else if HTLCType::scriptlen_to_htlctype(witness_script.len()) == Some(HTLCType::AcceptedHTLC) { &[InputDescriptors::RevokedReceivedHTLC] } else { unreachable!() }); + &InputMaterial::Revoked { ref input_descriptor, ref amount, .. } => { + inputs_witnesses_weight += Self::get_witnesses_weight(&[*input_descriptor]); amt += *amount; }, &InputMaterial::RemoteHTLC { ref preimage, ref amount, .. } => { @@ -529,34 +634,50 @@ impl OnchainTxHandler { for (i, (outp, per_outp_material)) in cached_claim_datas.per_input_material.iter().enumerate() { match per_outp_material { - &InputMaterial::Revoked { ref witness_script, ref pubkey, ref key, ref is_htlc, ref amount } => { - let sighash_parts = bip143::SighashComponents::new(&bumped_tx); - let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[i], &witness_script, *amount)[..]); - let sig = self.secp_ctx.sign(&sighash, &key); - bumped_tx.input[i].witness.push(sig.serialize_der().to_vec()); - bumped_tx.input[i].witness[0].push(SigHashType::All as u8); - if *is_htlc { - bumped_tx.input[i].witness.push(pubkey.unwrap().clone().serialize().to_vec()); - } else { - bumped_tx.input[i].witness.push(vec!(1)); + &InputMaterial::Revoked { ref per_commitment_point, ref per_commitment_key, ref input_descriptor, ref amount } => { + if let Ok(chan_keys) = TxCreationKeys::new(&self.secp_ctx, &per_commitment_point, &self.remote_tx_cache.remote_delayed_payment_base_key, &self.remote_tx_cache.remote_htlc_base_key, &self.key_storage.pubkeys().revocation_basepoint, &self.key_storage.pubkeys().payment_basepoint, &self.key_storage.pubkeys().htlc_basepoint) { + + let mut this_htlc = None; + if *input_descriptor != InputDescriptors::RevokedOutput { + if let Some(htlcs) = self.remote_tx_cache.per_htlc.get(&outp.txid) { + for htlc in htlcs { + if htlc.transaction_output_index.unwrap() == outp.vout { + this_htlc = Some(htlc); + } + } + } + } + + let witness_script = if *input_descriptor != InputDescriptors::RevokedOutput && this_htlc.is_some() { + chan_utils::get_htlc_redeemscript_with_explicit_keys(&this_htlc.unwrap(), &chan_keys.a_htlc_key, &chan_keys.b_htlc_key, &chan_keys.revocation_key) + } else if *input_descriptor != InputDescriptors::RevokedOutput { + return None; + } else { + chan_utils::get_revokeable_redeemscript(&chan_keys.revocation_key, self.remote_csv, &chan_keys.a_delayed_payment_key) + }; + + self.key_storage.sign_justice_transaction(&mut bumped_tx, i, &witness_script, *amount, &per_commitment_key, &chan_keys.revocation_key, *input_descriptor != InputDescriptors::RevokedOutput, &self.secp_ctx); + + log_trace!(self, "Going to broadcast Penalty Transaction {} claiming revoked {} output {} from {} with new feerate {}...", bumped_tx.txid(), if *input_descriptor == InputDescriptors::RevokedOutput { "to_local" } else if *input_descriptor == InputDescriptors::RevokedOfferedHTLC { "offered" } else if *input_descriptor == InputDescriptors::RevokedReceivedHTLC { "received" } else { "" }, outp.vout, outp.txid, new_feerate); } - bumped_tx.input[i].witness.push(witness_script.clone().into_bytes()); - log_trace!(self, "Going to broadcast Penalty Transaction {} claiming revoked {} output {} from {} with new feerate {}...", bumped_tx.txid(), if !is_htlc { "to_local" } else if HTLCType::scriptlen_to_htlctype(witness_script.len()) == Some(HTLCType::OfferedHTLC) { "offered" } else if HTLCType::scriptlen_to_htlctype(witness_script.len()) == Some(HTLCType::AcceptedHTLC) { "received" } else { "" }, outp.vout, outp.txid, new_feerate); }, - &InputMaterial::RemoteHTLC { ref witness_script, ref key, ref preimage, ref amount, ref locktime } => { - if !preimage.is_some() { bumped_tx.lock_time = *locktime }; // Right now we don't aggregate time-locked transaction, if we do we should set lock_time before to avoid breaking hash computation - let sighash_parts = bip143::SighashComponents::new(&bumped_tx); - let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[i], &witness_script, *amount)[..]); - let sig = self.secp_ctx.sign(&sighash, &key); - bumped_tx.input[i].witness.push(sig.serialize_der().to_vec()); - bumped_tx.input[i].witness[0].push(SigHashType::All as u8); - if let &Some(preimage) = preimage { - bumped_tx.input[i].witness.push(preimage.clone().0.to_vec()); - } else { - bumped_tx.input[i].witness.push(vec![]); + &InputMaterial::RemoteHTLC { ref per_commitment_point, ref preimage, ref amount, ref locktime } => { + if let Ok(chan_keys) = TxCreationKeys::new(&self.secp_ctx, &per_commitment_point, &self.remote_tx_cache.remote_delayed_payment_base_key, &self.remote_tx_cache.remote_htlc_base_key, &self.key_storage.pubkeys().revocation_basepoint, &self.key_storage.pubkeys().payment_basepoint, &self.key_storage.pubkeys().htlc_basepoint) { + let mut this_htlc = None; + if let Some(htlcs) = self.remote_tx_cache.per_htlc.get(&outp.txid) { + for htlc in htlcs { + if htlc.transaction_output_index.unwrap() == outp.vout { + this_htlc = Some(htlc); + } + } + } + if this_htlc.is_none() { return None; } + let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&this_htlc.unwrap(), &chan_keys.a_htlc_key, &chan_keys.b_htlc_key, &chan_keys.revocation_key); + + if !preimage.is_some() { bumped_tx.lock_time = *locktime }; // Right now we don't aggregate time-locked transaction, if we do we should set lock_time before to avoid breaking hash computation + self.key_storage.sign_remote_htlc_transaction(&mut bumped_tx, i, &witness_script, *amount, &per_commitment_point, preimage, &self.secp_ctx); + log_trace!(self, "Going to broadcast Claim Transaction {} claiming remote {} htlc output {} from {} with new feerate {}...", bumped_tx.txid(), if preimage.is_some() { "offered" } else { "received" }, outp.vout, outp.txid, new_feerate); } - bumped_tx.input[i].witness.push(witness_script.clone().into_bytes()); - log_trace!(self, "Going to broadcast Claim Transaction {} claiming remote {} htlc output {} from {} with new feerate {}...", bumped_tx.txid(), if preimage.is_some() { "offered" } else { "received" }, outp.vout, outp.txid, new_feerate); }, _ => unreachable!() } @@ -870,6 +991,10 @@ impl OnchainTxHandler { } } + pub(super) fn provide_latest_remote_tx(&mut self, commitment_txid: Sha256dHash, htlcs: Vec) { + self.remote_tx_cache.per_htlc.insert(commitment_txid, htlcs); + } + #[cfg(test)] pub(super) fn get_fully_signed_copy_local_tx(&mut self, funding_redeemscript: &Script) -> Option { if let Some(ref mut local_commitment) = self.local_commitment { diff --git a/lightning/src/util/enforcing_trait_impls.rs b/lightning/src/util/enforcing_trait_impls.rs index 3e1ffd04446..90108d009a7 100644 --- a/lightning/src/util/enforcing_trait_impls.rs +++ b/lightning/src/util/enforcing_trait_impls.rs @@ -1,11 +1,13 @@ use ln::chan_utils::{HTLCOutputInCommitment, TxCreationKeys, ChannelPublicKeys, LocalCommitmentTransaction}; use ln::{chan_utils, msgs}; +use ln::channelmanager::PaymentPreimage; use chain::keysinterface::{ChannelKeys, InMemoryChannelKeys}; use std::cmp; use std::sync::{Mutex, Arc}; use bitcoin::blockdata::transaction::Transaction; +use bitcoin::blockdata::script::Script; use bitcoin::util::bip143; use secp256k1; @@ -105,6 +107,14 @@ impl ChannelKeys for EnforcingChannelKeys { Ok(self.inner.sign_local_commitment_htlc_transactions(local_commitment_tx, local_csv, secp_ctx).unwrap()) } + fn sign_justice_transaction(&self, bumped_tx: &mut Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_key: &SecretKey, revocation_pubkey: &PublicKey, is_htlc: bool, secp_ctx: &Secp256k1) { + self.inner.sign_justice_transaction(bumped_tx, input, witness_script, amount, per_commitment_key, revocation_pubkey, is_htlc, secp_ctx); + } + + fn sign_remote_htlc_transaction(&self, bumped_tx: &mut Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_point: &PublicKey, preimage: &Option, secp_ctx: &Secp256k1) { + self.inner.sign_remote_htlc_transaction(bumped_tx, input, witness_script, amount, per_commitment_point, preimage, secp_ctx); + } + fn sign_closing_transaction(&self, closing_tx: &Transaction, secp_ctx: &Secp256k1) -> Result { Ok(self.inner.sign_closing_transaction(closing_tx, secp_ctx).unwrap()) }