Skip to content

Move remote htlc transactions signature behind signer interface #561

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion lightning/src/chain/keysinterface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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};
Expand Down Expand Up @@ -245,6 +246,12 @@ pub trait ChannelKeys : Send+Clone {
/// return value must contain a signature.
fn sign_local_commitment_htlc_transactions<T: secp256k1::Signing + secp256k1::Verification>(&self, local_commitment_tx: &LocalCommitmentTransaction, local_csv: u16, secp_ctx: &Secp256k1<T>) -> Result<Vec<Option<Signature>>, ()>;

/// Signs a justice transaction.
fn sign_justice_transaction<T: secp256k1::Signing>(&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<T>);

/// Signs a remote htlc transaction.
fn sign_remote_htlc_transaction<T: secp256k1::Signing>(&self, bumped_tx: &mut Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_point: &PublicKey, preimage: &Option<PaymentPreimage>, secp_ctx: &Secp256k1<T>);

/// Create a signature for a (proposed) closing transaction.
///
/// Note that, due to rounding, there may be one "missing" satoshi, and either party may have
Expand Down Expand Up @@ -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<T: secp256k1::Signing>(&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<T>) {
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<T: secp256k1::Signing>(&self, bumped_tx: &mut Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_point: &PublicKey, preimage: &Option<PaymentPreimage>, secp_ctx: &Secp256k1<T>) {
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<T: secp256k1::Signing>(&self, closing_tx: &Transaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
if closing_tx.input.len() != 1 { return Err(()); }
if closing_tx.input[0].witness.len() != 0 { return Err(()); }
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/ln/chan_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ pub(super) fn derive_public_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>,
/// 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<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, per_commitment_secret: &SecretKey, revocation_base_secret: &SecretKey) -> Result<SecretKey, secp256k1::Error> {
pub fn derive_private_revocation_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, per_commitment_secret: &SecretKey, revocation_base_secret: &SecretKey) -> Result<SecretKey, secp256k1::Error> {
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);

Expand Down
88 changes: 36 additions & 52 deletions lightning/src/ln/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -425,15 +425,13 @@ struct LocalSignedTx {
#[derive(Clone, PartialEq)]
pub(crate) enum InputMaterial {
Revoked {
witness_script: Script,
pubkey: Option<PublicKey>,
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<PaymentPreimage>,
amount: u64,
locktime: u32,
Expand All @@ -450,18 +448,16 @@ pub(crate) enum InputMaterial {
impl Writeable for InputMaterial {
fn write<W: Writer>(&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))?;
Expand All @@ -484,28 +480,24 @@ impl Readable for InputMaterial {
fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
let input_material = match <u8 as Readable>::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
Expand Down Expand Up @@ -1064,7 +1056,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
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;
Expand Down Expand Up @@ -1099,8 +1091,8 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
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,
Expand Down Expand Up @@ -1205,7 +1197,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
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 {
Expand All @@ -1226,6 +1218,13 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
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) {
Expand Down Expand Up @@ -1448,11 +1447,8 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
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();
Expand All @@ -1467,7 +1463,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
// 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});
}
}
Expand All @@ -1476,13 +1472,11 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
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 });
}
}
Expand Down Expand Up @@ -1600,10 +1594,6 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
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 = {
Expand All @@ -1616,16 +1606,14 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
// 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 });
}
}
Expand Down Expand Up @@ -1655,13 +1643,9 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
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())))
}
Expand Down
Loading