Skip to content

Commit d6a784b

Browse files
committed
Introduce CommitmentTransactionInfo
This class maintains the per-commitment transaction fields needed to construct the bitcoin transaction. It replaces passing around of Bitcoin transactions.
1 parent 9c7c3b9 commit d6a784b

File tree

2 files changed

+323
-96
lines changed

2 files changed

+323
-96
lines changed

lightning/src/ln/chan_utils.rs

Lines changed: 259 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@ use ln::msgs::DecodeError;
2828
use util::ser::{Readable, Writeable, Writer, WriterWriteAdaptor};
2929
use util::byte_utils;
3030

31+
use bitcoin::hash_types::WPubkeyHash;
3132
use bitcoin::secp256k1::key::{SecretKey, PublicKey};
32-
use bitcoin::secp256k1::{Secp256k1, Signature};
33+
use bitcoin::secp256k1::{Secp256k1, Signature, Message};
3334
use bitcoin::secp256k1::Error as SecpError;
3435
use bitcoin::secp256k1;
3536

3637
use std::{cmp, mem};
38+
use ln::chan_utils;
39+
use util::transaction_utils::sort_outputs;
40+
use ln::channel::INITIAL_COMMITMENT_NUMBER;
3741

3842
const MAX_ALLOC_SIZE: usize = 64*1024;
3943

@@ -550,6 +554,260 @@ pub fn build_htlc_transaction(prev_hash: &Txid, feerate_per_kw: u32, contest_del
550554
}
551555
}
552556

557+
#[derive(Clone)]
558+
/// We use this class to track the information needed to build a commitment transaction and to actually
559+
/// build and sign. It is used for holder transactions that we sign only when needed
560+
/// and for transactions we sign for the counterparty.
561+
///
562+
/// This class can be used inside a signer implementation to generate a signature given the relevant
563+
/// secret key.
564+
pub struct CommitmentTransactionInfo {
565+
/// The backwards-counting commitment number
566+
pub commitment_number: u64,
567+
/// The value to be sent to the broadcaster
568+
pub to_broadcaster_value_sat: u64,
569+
/// The value to be sent to the counterparty
570+
pub to_countersignatory_value_sat: u64,
571+
/// The feerate paid per 1000-weight-unit in this commitment transaction.
572+
pub feerate_per_kw: u32,
573+
/// The HTLCs which were included in this commitment transaction in output order.
574+
pub htlcs: Vec<HTLCOutputInCommitment>,
575+
pub(crate) keys: TxCreationKeys,
576+
}
577+
578+
impl CommitmentTransactionInfo {
579+
/// Construct an object of the class while assigning transaction output indices to HTLCs.
580+
///
581+
/// Also keeps track of associated HTLC data and returns it along with the mutated HTLCs.
582+
pub fn new_with_auxiliary_htlc_data<T: Copy>(
583+
commitment_number: u64,
584+
to_broadcaster_value_sat: u64,
585+
to_countersignatory_value_sat: u64,
586+
keys: TxCreationKeys,
587+
feerate_per_kw: u32,
588+
htlcs_with_aux: Vec<(HTLCOutputInCommitment, T)>,
589+
broadcaster_pubkeys: &ChannelPublicKeys,
590+
countersignatory_pubkeys: &ChannelPublicKeys,
591+
contest_delay: u16,
592+
secp_ctx: &Secp256k1<secp256k1::All>
593+
) -> (CommitmentTransactionInfo, Vec<(HTLCOutputInCommitment, T)>) {
594+
// Populate output indices while keeping track of the auxiliary data
595+
let mut txouts = Self::do_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, &htlcs_with_aux, broadcaster_pubkeys, countersignatory_pubkeys, contest_delay, &secp_ctx).unwrap();
596+
let mut result_htlcs_with_aux = Vec::new();
597+
let mut htlcs = Vec::new();
598+
for (idx, mut out) in txouts.drain(..).enumerate() {
599+
if let Some(mut htlc) = (out.1).1.take() {
600+
htlc.0.transaction_output_index = Some(idx as u32);
601+
result_htlcs_with_aux.push((htlc.0.clone(), htlc.1));
602+
htlcs.push(htlc.0);
603+
}
604+
}
605+
606+
let info = CommitmentTransactionInfo {
607+
commitment_number,
608+
to_broadcaster_value_sat,
609+
to_countersignatory_value_sat,
610+
feerate_per_kw,
611+
htlcs,
612+
keys,
613+
};
614+
(info, result_htlcs_with_aux)
615+
}
616+
617+
/// Build the Bitcoin transaction.
618+
///
619+
/// Required channel-static fields are provided by the caller.
620+
///
621+
/// is_outbound is true if the channel is outbound from the point of view of the broadcaster
622+
pub fn build<T: secp256k1::Signing + secp256k1::Verification>(
623+
&self,
624+
broadcaster_pubkeys: &ChannelPublicKeys,
625+
countersignatory_pubkeys: &ChannelPublicKeys,
626+
funding_outpoint: &OutPoint,
627+
contest_delay: u16,
628+
is_outbound: bool,
629+
secp_ctx: &Secp256k1<T>,
630+
) -> Result<(bitcoin::Transaction, Vec<HTLCOutputInCommitment>, Vec<Script>), ()> {
631+
let (obscured_commitment_transaction_number, txins) = self.build_inputs(broadcaster_pubkeys, countersignatory_pubkeys, funding_outpoint, is_outbound);
632+
633+
let mut txouts = self.build_outputs(broadcaster_pubkeys, countersignatory_pubkeys, contest_delay, secp_ctx)?;
634+
635+
let mut outputs = Vec::with_capacity(txouts.len());
636+
let mut scripts = Vec::with_capacity(txouts.len());
637+
let mut htlcs = Vec::new();
638+
for (idx, mut out) in txouts.drain(..).enumerate() {
639+
outputs.push(out.0);
640+
scripts.push((out.1).0.clone());
641+
if let Some(mut htlc) = (out.1).1.take() {
642+
htlc.transaction_output_index = Some(idx as u32);
643+
htlcs.push(htlc);
644+
}
645+
}
646+
647+
Ok((
648+
Transaction {
649+
version: 2,
650+
lock_time: ((0x20 as u32) << 8 * 3) | ((obscured_commitment_transaction_number & 0xffffffu64) as u32),
651+
input: txins,
652+
output: outputs,
653+
},
654+
htlcs,
655+
scripts,
656+
))
657+
}
658+
659+
fn build_outputs<T: secp256k1::Signing + secp256k1::Verification>(&self, broadcaster_pubkeys: &ChannelPublicKeys, countersignatory_pubkeys: &ChannelPublicKeys, contest_delay: u16, secp_ctx: &Secp256k1<T>) -> Result<Vec<(TxOut, (Script, Option<HTLCOutputInCommitment>))>, ()> {
660+
let htlcs = self.htlcs.iter().map(|h| (h.clone(), ())).collect();
661+
let mut txouts = Self::do_build_outputs(&self.keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &htlcs, broadcaster_pubkeys, countersignatory_pubkeys, contest_delay, secp_ctx)?;
662+
let outs = txouts.drain(..).map(|(out, (s, extra))| (out, (s, extra.map(|(p, _)| p)))).collect();
663+
Ok(outs)
664+
}
665+
666+
fn do_build_outputs<T: Copy, S: secp256k1::Signing + secp256k1::Verification>(
667+
keys: &TxCreationKeys,
668+
to_broadcaster_value_sat: u64,
669+
to_countersignatory_value_sat: u64,
670+
htlcs: &Vec<(HTLCOutputInCommitment, T)>,
671+
broadcaster_pubkeys: &ChannelPublicKeys, countersignatory_pubkeys: &ChannelPublicKeys, contest_delay: u16, secp_ctx: &Secp256k1<S>) -> Result<Vec<(TxOut, (Script, Option<(HTLCOutputInCommitment, T)>))>, ()> {
672+
let per_commitment_point = &keys.per_commitment_point;
673+
let to_broadcaster_delayed_pubkey = derive_public_key(
674+
&secp_ctx,
675+
&per_commitment_point,
676+
&broadcaster_pubkeys.delayed_payment_basepoint,
677+
).map_err(|_| ())?;
678+
679+
let revocation_pubkey = derive_public_revocation_key(
680+
&secp_ctx,
681+
per_commitment_point,
682+
&countersignatory_pubkeys.revocation_basepoint,
683+
).map_err(|_| ())?;
684+
685+
let mut txouts: Vec<(TxOut, (Script, Option<(HTLCOutputInCommitment, T)>))> = Vec::new();
686+
687+
if to_countersignatory_value_sat > 0 {
688+
let script = script_for_p2wpkh(&countersignatory_pubkeys.payment_point);
689+
txouts.push((
690+
TxOut {
691+
script_pubkey: script.clone(),
692+
value: to_countersignatory_value_sat,
693+
},
694+
(script, None),
695+
))
696+
}
697+
698+
if to_broadcaster_value_sat > 0 {
699+
let redeem_script = get_revokeable_redeemscript(
700+
&revocation_pubkey,
701+
contest_delay,
702+
&to_broadcaster_delayed_pubkey,
703+
);
704+
txouts.push((
705+
TxOut {
706+
script_pubkey: redeem_script.to_v0_p2wsh(),
707+
value: to_broadcaster_value_sat,
708+
},
709+
(redeem_script, None),
710+
));
711+
}
712+
713+
for (htlc, t) in htlcs {
714+
let script = chan_utils::get_htlc_redeemscript(&htlc, &keys);
715+
let txout = TxOut {
716+
script_pubkey: script.to_v0_p2wsh(),
717+
value: htlc.amount_msat / 1000,
718+
};
719+
txouts.push((txout, (script, Some((htlc.clone(), *t)))));
720+
}
721+
722+
sort_outputs(&mut txouts, |a, b| {
723+
if let &(_, Some(ref a_htlcout)) = a {
724+
if let &(_, Some(ref b_htlcout)) = b {
725+
a_htlcout.0.cltv_expiry.cmp(&b_htlcout.0.cltv_expiry)
726+
} else {
727+
cmp::Ordering::Equal
728+
}
729+
} else {
730+
cmp::Ordering::Equal
731+
}
732+
});
733+
Ok(txouts)
734+
}
735+
736+
fn build_inputs(&self, broadcaster_pubkeys: &ChannelPublicKeys, countersignatory_pubkeys: &ChannelPublicKeys, funding_outpoint: &OutPoint, is_outbound: bool) -> (u64, Vec<TxIn>) {
737+
let commitment_transaction_number_obscure_factor = get_commitment_transaction_number_obscure_factor(
738+
&broadcaster_pubkeys.payment_point,
739+
&countersignatory_pubkeys.payment_point,
740+
is_outbound,
741+
);
742+
743+
let obscured_commitment_transaction_number =
744+
commitment_transaction_number_obscure_factor ^ (INITIAL_COMMITMENT_NUMBER - self.commitment_number);
745+
746+
let txins = {
747+
let mut ins: Vec<TxIn> = Vec::new();
748+
ins.push(TxIn {
749+
previous_output: funding_outpoint.clone(),
750+
script_sig: Script::new(),
751+
sequence: ((0x80 as u32) << 8 * 3)
752+
| ((obscured_commitment_transaction_number >> 3 * 8) as u32),
753+
witness: Vec::new(),
754+
});
755+
ins
756+
};
757+
(obscured_commitment_transaction_number, txins)
758+
}
759+
760+
/// Sign a transaction, either because we are counter-signing the counterparty's transaction or
761+
/// because we are about to broadcast a holder transaction.
762+
///
763+
/// is_outbound is true if the channel is outbound from the point of view of the broadcaster
764+
pub fn get_signature<T: secp256k1::Signing + secp256k1::Verification>(&self, broadcaster_pubkeys: &ChannelPublicKeys, countersignatory_pubkeys: &ChannelPublicKeys, funding_outpoint: &OutPoint, contest_delay: u16, is_outbound: bool, funding_key: &SecretKey, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1<T>) -> Signature {
765+
let sighash = self.get_sighash(broadcaster_pubkeys, countersignatory_pubkeys, funding_outpoint, contest_delay, is_outbound, funding_redeemscript, channel_value_satoshis, secp_ctx).0;
766+
secp_ctx.sign(&sighash, funding_key)
767+
}
768+
769+
/// Get the sighash and the transaction.
770+
///
771+
/// Builds the transaction and computes the sighash. This can be used to verify a signature.
772+
pub fn get_sighash<T: secp256k1::Signing + secp256k1::Verification>(&self, broadcaster_pubkeys: &ChannelPublicKeys, countersignatory_pubkeys: &ChannelPublicKeys, funding_outpoint: &OutPoint, contest_delay: u16, is_outbound: bool, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1<T>) -> (Message, Transaction) {
773+
let (unsigned_tx, _, _) = self.build(broadcaster_pubkeys, countersignatory_pubkeys, funding_outpoint, contest_delay, is_outbound, secp_ctx).unwrap();
774+
let sighash = hash_to_message!(&bip143::SigHashCache::new(&unsigned_tx)
775+
.signature_hash(0, funding_redeemscript, channel_value_satoshis, SigHashType::All)[..]);
776+
(sighash, unsigned_tx)
777+
}
778+
}
779+
780+
/// Get the transaction number obscure factor
781+
pub fn get_commitment_transaction_number_obscure_factor(
782+
broadcaster_payment_basepoint: &PublicKey,
783+
countersignatory_payment_basepoint: &PublicKey,
784+
outbound: bool,
785+
) -> u64 {
786+
let mut sha = Sha256::engine();
787+
788+
if outbound {
789+
sha.input(&broadcaster_payment_basepoint.serialize());
790+
sha.input(&countersignatory_payment_basepoint.serialize());
791+
} else {
792+
sha.input(&countersignatory_payment_basepoint.serialize());
793+
sha.input(&broadcaster_payment_basepoint.serialize());
794+
}
795+
let res = Sha256::from_engine(sha).into_inner();
796+
797+
((res[26] as u64) << 5 * 8)
798+
| ((res[27] as u64) << 4 * 8)
799+
| ((res[28] as u64) << 3 * 8)
800+
| ((res[29] as u64) << 2 * 8)
801+
| ((res[30] as u64) << 1 * 8)
802+
| ((res[31] as u64) << 0 * 8)
803+
}
804+
805+
fn script_for_p2wpkh(key: &PublicKey) -> Script {
806+
Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0)
807+
.push_slice(&WPubkeyHash::hash(&key.serialize())[..])
808+
.into_script()
809+
}
810+
553811
#[derive(Clone)]
554812
/// We use this to track holder commitment transactions and put off signing them until we are ready
555813
/// to broadcast. This class can be used inside a signer implementation to generate a signature

0 commit comments

Comments
 (0)