Skip to content

Commit f7c70cc

Browse files
committed
Don't modify LocalCommitmemntTransaction after construction
Instead of adding signatures to LocalCommitmentTransactions, we instead leave them unsigned and use them to construct signed Transactions when we want them. This cleans up the guts of LocalCommitmentTransaction enough that we can, and do, expose its state to the world, allowing external signers to have a basic awareness of what they're signing.
1 parent 2d7ce22 commit f7c70cc

File tree

4 files changed

+77
-81
lines changed

4 files changed

+77
-81
lines changed

lightning/src/ln/chan_utils.rs

Lines changed: 61 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -484,10 +484,30 @@ pub fn build_htlc_transaction(prev_hash: &Sha256dHash, feerate_per_kw: u64, to_s
484484
/// to broadcast. Eventually this will require a signer which is possibly external, but for now we
485485
/// just pass in the SecretKeys required.
486486
pub struct LocalCommitmentTransaction {
487-
tx: Transaction,
488-
pub(crate) local_keys: TxCreationKeys,
489-
pub(crate) feerate_per_kw: u64,
490-
pub(crate) per_htlc: Vec<(HTLCOutputInCommitment, Option<Signature>)>,
487+
// TODO: We should migrate away from providing the transaction, instead providing enough to
488+
// allow the ChannelKeys to construct it from scratch. Luckily we already have HTLC data here,
489+
// so we're probably most of the way there.
490+
/// The commitment transaction itself, in unsigned form.
491+
pub unsigned_tx: Transaction,
492+
/// Our counterparty's signature for the transaction, above.
493+
pub their_sig: Signature,
494+
// Which order the signatures should go in when constructing the final commitment tx witness.
495+
// The user should be able to reconstruc this themselves, so we don't bother to expose it.
496+
our_sig_first: bool,
497+
/// The key derivation parameters for this commitment transaction
498+
pub local_keys: TxCreationKeys,
499+
/// The feerate paid per 1000-weight-unit in this commitment transaction. This value is
500+
/// controlled by the channel initiator.
501+
pub feerate_per_kw: u64,
502+
/// The HTLCs and remote htlc signatures which were included in this commitment transaction.
503+
///
504+
/// Note that this includes all HTLCs, including ones which were considered dust and not
505+
/// acually included in the transaction as it appears on-chain, but who's value factor into fee
506+
/// calculations.
507+
///
508+
/// The remote HTLC signatures in the second element will always be set for non-dust HTLCs, ie
509+
/// those for which transaction_output_index.is_some().
510+
pub per_htlc: Vec<(HTLCOutputInCommitment, Option<Signature>)>,
491511
}
492512
impl LocalCommitmentTransaction {
493513
#[cfg(test)]
@@ -499,16 +519,19 @@ impl LocalCommitmentTransaction {
499519
},
500520
script_sig: Default::default(),
501521
sequence: 0,
502-
witness: vec![vec![], vec![], vec![]]
522+
witness: vec![]
503523
};
504524
let dummy_key = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&[42; 32]).unwrap());
525+
let dummy_sig = Secp256k1::new().sign(&secp256k1::Message::from_slice(&[42; 32]).unwrap(), &SecretKey::from_slice(&[42; 32]).unwrap());
505526
Self {
506-
tx: Transaction {
527+
unsigned_tx: Transaction {
507528
version: 2,
508529
input: vec![dummy_input],
509530
output: Vec::new(),
510531
lock_time: 0,
511532
},
533+
their_sig: dummy_sig,
534+
our_sig_first: false,
512535
local_keys: TxCreationKeys {
513536
per_commitment_point: dummy_key.clone(),
514537
revocation_key: dummy_key.clone(),
@@ -524,23 +547,14 @@ impl LocalCommitmentTransaction {
524547

525548
/// Generate a new LocalCommitmentTransaction based on a raw commitment transaction,
526549
/// remote signature and both parties keys
527-
pub(crate) fn new_missing_local_sig(mut tx: Transaction, their_sig: &Signature, our_funding_key: &PublicKey, their_funding_key: &PublicKey, local_keys: TxCreationKeys, feerate_per_kw: u64, htlc_data: Vec<(HTLCOutputInCommitment, Option<Signature>)>) -> LocalCommitmentTransaction {
528-
if tx.input.len() != 1 { panic!("Tried to store a commitment transaction that had input count != 1!"); }
529-
if tx.input[0].witness.len() != 0 { panic!("Tried to store a signed commitment transaction?"); }
530-
531-
tx.input[0].witness.push(Vec::new()); // First is the multisig dummy
550+
pub(crate) fn new_missing_local_sig(unsigned_tx: Transaction, their_sig: Signature, our_funding_key: &PublicKey, their_funding_key: &PublicKey, local_keys: TxCreationKeys, feerate_per_kw: u64, htlc_data: Vec<(HTLCOutputInCommitment, Option<Signature>)>) -> LocalCommitmentTransaction {
551+
if unsigned_tx.input.len() != 1 { panic!("Tried to store a commitment transaction that had input count != 1!"); }
552+
if unsigned_tx.input[0].witness.len() != 0 { panic!("Tried to store a signed commitment transaction?"); }
532553

533-
if our_funding_key.serialize()[..] < their_funding_key.serialize()[..] {
534-
tx.input[0].witness.push(Vec::new());
535-
tx.input[0].witness.push(their_sig.serialize_der().to_vec());
536-
tx.input[0].witness[2].push(SigHashType::All as u8);
537-
} else {
538-
tx.input[0].witness.push(their_sig.serialize_der().to_vec());
539-
tx.input[0].witness[1].push(SigHashType::All as u8);
540-
tx.input[0].witness.push(Vec::new());
541-
}
542-
543-
Self { tx,
554+
Self {
555+
unsigned_tx,
556+
their_sig,
557+
our_sig_first: our_funding_key.serialize()[..] < their_funding_key.serialize()[..],
544558
local_keys,
545559
feerate_per_kw,
546560
per_htlc: htlc_data,
@@ -550,22 +564,7 @@ impl LocalCommitmentTransaction {
550564
/// Get the txid of the local commitment transaction contained in this
551565
/// LocalCommitmentTransaction
552566
pub fn txid(&self) -> Sha256dHash {
553-
self.tx.txid()
554-
}
555-
556-
/// Check if LocalCommitmentTransaction has already been signed by us
557-
pub(crate) fn has_local_sig(&self) -> bool {
558-
if self.tx.input.len() != 1 { panic!("Commitment transactions must have input count == 1!"); }
559-
if self.tx.input[0].witness.len() == 4 {
560-
assert!(!self.tx.input[0].witness[1].is_empty());
561-
assert!(!self.tx.input[0].witness[2].is_empty());
562-
true
563-
} else {
564-
assert_eq!(self.tx.input[0].witness.len(), 3);
565-
assert!(self.tx.input[0].witness[0].is_empty());
566-
assert!(self.tx.input[0].witness[1].is_empty() || self.tx.input[0].witness[2].is_empty());
567-
false
568-
}
567+
self.unsigned_tx.txid()
569568
}
570569

571570
/// Gets our signature for the contained commitment transaction given our funding private key.
@@ -577,32 +576,27 @@ impl LocalCommitmentTransaction {
577576
/// ChannelKeys::sign_local_commitment() calls directly.
578577
/// Channel value is amount locked in funding_outpoint.
579578
pub fn get_local_sig<T: secp256k1::Signing>(&self, funding_key: &SecretKey, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1<T>) -> Signature {
580-
let sighash = hash_to_message!(&bip143::SighashComponents::new(&self.tx)
581-
.sighash_all(&self.tx.input[0], funding_redeemscript, channel_value_satoshis)[..]);
579+
let sighash = hash_to_message!(&bip143::SighashComponents::new(&self.unsigned_tx)
580+
.sighash_all(&self.unsigned_tx.input[0], funding_redeemscript, channel_value_satoshis)[..]);
582581
secp_ctx.sign(&sighash, funding_key)
583582
}
584583

584+
pub(crate) fn add_local_sig(&self, funding_redeemscript: &Script, our_sig: Signature) -> Transaction {
585+
let mut tx = self.unsigned_tx.clone();
586+
tx.input[0].witness.push(Vec::new()); // First is the multisig dummy
585587

586-
pub(crate) fn add_local_sig(&mut self, funding_redeemscript: &Script, our_sig: Signature) {
587-
if self.has_local_sig() { return; }
588-
589-
if self.tx.input[0].witness[1].is_empty() {
590-
self.tx.input[0].witness[1] = our_sig.serialize_der().to_vec();
591-
self.tx.input[0].witness[1].push(SigHashType::All as u8);
588+
if self.our_sig_first {
589+
tx.input[0].witness.push(our_sig.serialize_der().to_vec());
590+
tx.input[0].witness.push(self.their_sig.serialize_der().to_vec());
592591
} else {
593-
self.tx.input[0].witness[2] = our_sig.serialize_der().to_vec();
594-
self.tx.input[0].witness[2].push(SigHashType::All as u8);
592+
tx.input[0].witness.push(self.their_sig.serialize_der().to_vec());
593+
tx.input[0].witness.push(our_sig.serialize_der().to_vec());
595594
}
595+
tx.input[0].witness[1].push(SigHashType::All as u8);
596+
tx.input[0].witness[2].push(SigHashType::All as u8);
596597

597-
self.tx.input[0].witness.push(funding_redeemscript.as_bytes().to_vec());
598-
}
599-
600-
/// Get raw transaction without asserting if witness is complete
601-
pub(crate) fn without_valid_witness(&self) -> &Transaction { &self.tx }
602-
/// Get raw transaction with panics if witness is incomplete
603-
pub(crate) fn with_valid_witness(&self) -> &Transaction {
604-
assert!(self.has_local_sig());
605-
&self.tx
598+
tx.input[0].witness.push(funding_redeemscript.as_bytes().to_vec());
599+
tx
606600
}
607601

608602
/// Get a signature for each HTLC which was included in the commitment transaction (ie for
@@ -677,12 +671,14 @@ impl PartialEq for LocalCommitmentTransaction {
677671
}
678672
impl Writeable for LocalCommitmentTransaction {
679673
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
680-
if let Err(e) = self.tx.consensus_encode(&mut WriterWriteAdaptor(writer)) {
674+
if let Err(e) = self.unsigned_tx.consensus_encode(&mut WriterWriteAdaptor(writer)) {
681675
match e {
682676
encode::Error::Io(e) => return Err(e),
683677
_ => panic!("local tx must have been well-formed!"),
684678
}
685679
}
680+
self.their_sig.write(writer)?;
681+
self.our_sig_first.write(writer)?;
686682
self.local_keys.write(writer)?;
687683
self.feerate_per_kw.write(writer)?;
688684
writer.write_all(&byte_utils::be64_to_array(self.per_htlc.len() as u64))?;
@@ -695,13 +691,15 @@ impl Writeable for LocalCommitmentTransaction {
695691
}
696692
impl Readable for LocalCommitmentTransaction {
697693
fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
698-
let tx = match Transaction::consensus_decode(reader.by_ref()) {
694+
let unsigned_tx = match Transaction::consensus_decode(reader.by_ref()) {
699695
Ok(tx) => tx,
700696
Err(e) => match e {
701697
encode::Error::Io(ioe) => return Err(DecodeError::Io(ioe)),
702698
_ => return Err(DecodeError::InvalidValue),
703699
},
704700
};
701+
let their_sig = Readable::read(reader)?;
702+
let our_sig_first = Readable::read(reader)?;
705703
let local_keys = Readable::read(reader)?;
706704
let feerate_per_kw = Readable::read(reader)?;
707705
let htlcs_count: u64 = Readable::read(reader)?;
@@ -712,12 +710,14 @@ impl Readable for LocalCommitmentTransaction {
712710
per_htlc.push((htlc, sigs));
713711
}
714712

715-
if tx.input.len() != 1 {
713+
if unsigned_tx.input.len() != 1 {
716714
// Ensure tx didn't hit the 0-input ambiguity case.
717715
return Err(DecodeError::InvalidValue);
718716
}
719717
Ok(Self {
720-
tx,
718+
unsigned_tx,
719+
their_sig,
720+
our_sig_first,
721721
local_keys,
722722
feerate_per_kw,
723723
per_htlc,

lightning/src/ln/channel.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,7 +1460,7 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
14601460
// They sign the "local" commitment transaction...
14611461
secp_check!(self.secp_ctx.verify(&local_sighash, &sig, self.their_funding_pubkey()), "Invalid funding_created signature from peer");
14621462

1463-
let localtx = LocalCommitmentTransaction::new_missing_local_sig(local_initial_commitment_tx, sig, &PublicKey::from_secret_key(&self.secp_ctx, self.local_keys.funding_key()), self.their_funding_pubkey(), local_keys, self.feerate_per_kw, Vec::new());
1463+
let localtx = LocalCommitmentTransaction::new_missing_local_sig(local_initial_commitment_tx, sig.clone(), &PublicKey::from_secret_key(&self.secp_ctx, self.local_keys.funding_key()), self.their_funding_pubkey(), local_keys, self.feerate_per_kw, Vec::new());
14641464

14651465
let remote_keys = self.build_remote_transaction_keys()?;
14661466
let remote_initial_commitment_tx = self.build_commitment_transaction(self.cur_remote_commitment_transaction_number, &remote_keys, false, false, self.feerate_per_kw).0;
@@ -1574,7 +1574,7 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
15741574
let funding_txo_script = funding_redeemscript.to_v0_p2wsh();
15751575
macro_rules! create_monitor {
15761576
() => { {
1577-
let local_commitment_tx = LocalCommitmentTransaction::new_missing_local_sig(local_initial_commitment_tx.clone(), &msg.signature, &PublicKey::from_secret_key(&self.secp_ctx, self.local_keys.funding_key()), their_funding_pubkey, local_keys.clone(), self.feerate_per_kw, Vec::new());
1577+
let local_commitment_tx = LocalCommitmentTransaction::new_missing_local_sig(local_initial_commitment_tx.clone(), msg.signature.clone(), &PublicKey::from_secret_key(&self.secp_ctx, self.local_keys.funding_key()), their_funding_pubkey, local_keys.clone(), self.feerate_per_kw, Vec::new());
15781578
let mut channel_monitor = ChannelMonitor::new(self.local_keys.clone(),
15791579
&self.shutdown_pubkey, self.our_to_self_delay,
15801580
&self.destination_script, (funding_txo.clone(), funding_txo_script.clone()),
@@ -1904,7 +1904,7 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
19041904
let mut monitor_update = ChannelMonitorUpdate {
19051905
update_id: self.latest_monitor_update_id,
19061906
updates: vec![ChannelMonitorUpdateStep::LatestLocalCommitmentTXInfo {
1907-
commitment_tx: LocalCommitmentTransaction::new_missing_local_sig(local_commitment_tx.0, &msg.signature, &PublicKey::from_secret_key(&self.secp_ctx, self.local_keys.funding_key()), &their_funding_pubkey, local_keys, self.feerate_per_kw, htlcs_without_source),
1907+
commitment_tx: LocalCommitmentTransaction::new_missing_local_sig(local_commitment_tx.0, msg.signature.clone(), &PublicKey::from_secret_key(&self.secp_ctx, self.local_keys.funding_key()), &their_funding_pubkey, local_keys, self.feerate_per_kw, htlcs_without_source),
19081908
htlc_outputs: htlcs_and_sigs
19091909
}]
19101910
};
@@ -4500,11 +4500,10 @@ mod tests {
45004500
})*
45014501
assert_eq!(unsigned_tx.1.len(), per_htlc.len());
45024502

4503-
localtx = LocalCommitmentTransaction::new_missing_local_sig(unsigned_tx.0.clone(), &their_signature, &PublicKey::from_secret_key(&secp_ctx, chan.local_keys.funding_key()), chan.their_funding_pubkey(), keys.clone(), chan.feerate_per_kw, per_htlc);
4503+
localtx = LocalCommitmentTransaction::new_missing_local_sig(unsigned_tx.0.clone(), their_signature.clone(), &PublicKey::from_secret_key(&secp_ctx, chan.local_keys.funding_key()), chan.their_funding_pubkey(), keys.clone(), chan.feerate_per_kw, per_htlc);
45044504
let local_sig = chan_keys.sign_local_commitment(&localtx, &chan.secp_ctx).unwrap();
4505-
localtx.add_local_sig(&redeemscript, local_sig);
45064505

4507-
assert_eq!(serialize(localtx.with_valid_witness())[..],
4506+
assert_eq!(serialize(&localtx.add_local_sig(&redeemscript, local_sig))[..],
45084507
hex::decode($tx_hex).unwrap()[..]);
45094508

45104509
let htlc_sigs = chan_keys.sign_local_commitment_htlc_transactions(&localtx, chan.their_to_self_delay, &chan.secp_ctx).unwrap();

lightning/src/ln/channelmonitor.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,8 +1044,8 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
10441044

10451045
let mut onchain_tx_handler = OnchainTxHandler::new(destination_script.clone(), keys.clone(), their_to_self_delay, logger.clone());
10461046

1047-
let local_tx_sequence = initial_local_commitment_tx.without_valid_witness().input[0].sequence as u64;
1048-
let local_tx_locktime = initial_local_commitment_tx.without_valid_witness().lock_time as u64;
1047+
let local_tx_sequence = initial_local_commitment_tx.unsigned_tx.input[0].sequence as u64;
1048+
let local_tx_locktime = initial_local_commitment_tx.unsigned_tx.lock_time as u64;
10491049
let local_commitment_tx = LocalSignedTx {
10501050
txid: initial_local_commitment_tx.txid(),
10511051
revocation_key: initial_local_commitment_tx.local_keys.revocation_key,
@@ -1227,8 +1227,8 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
12271227
return Err(MonitorUpdateError("A local commitment tx has already been signed, no new local commitment txn can be sent to our counterparty"));
12281228
}
12291229
let txid = commitment_tx.txid();
1230-
let sequence = commitment_tx.without_valid_witness().input[0].sequence as u64;
1231-
let locktime = commitment_tx.without_valid_witness().lock_time as u64;
1230+
let sequence = commitment_tx.unsigned_tx.input[0].sequence as u64;
1231+
let locktime = commitment_tx.unsigned_tx.lock_time as u64;
12321232
let mut new_local_commitment_tx = LocalSignedTx {
12331233
txid,
12341234
revocation_key: commitment_tx.local_keys.revocation_key,

lightning/src/ln/onchaintx.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -814,9 +814,6 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
814814
// HTLC-timeout or channel force-closure), don't allow any further update of local
815815
// commitment transaction view to avoid delivery of revocation secret to counterparty
816816
// for the aformentionned signed transaction.
817-
if let Some(ref local_commitment) = self.local_commitment {
818-
if local_commitment.has_local_sig() { return Err(()) }
819-
}
820817
if self.local_htlc_sigs.is_some() || self.prev_local_htlc_sigs.is_some() {
821818
return Err(());
822819
}
@@ -865,25 +862,25 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
865862
pub(super) fn get_fully_signed_local_tx(&mut self, funding_redeemscript: &Script) -> Option<Transaction> {
866863
if let Some(ref mut local_commitment) = self.local_commitment {
867864
match self.key_storage.sign_local_commitment(local_commitment, &self.secp_ctx) {
868-
Ok(sig) => local_commitment.add_local_sig(funding_redeemscript, sig),
865+
Ok(sig) => Some(local_commitment.add_local_sig(funding_redeemscript, sig)),
869866
Err(_) => return None,
870867
}
871-
return Some(local_commitment.with_valid_witness().clone());
868+
} else {
869+
None
872870
}
873-
None
874871
}
875872

876873
#[cfg(test)]
877874
pub(super) fn get_fully_signed_copy_local_tx(&mut self, funding_redeemscript: &Script) -> Option<Transaction> {
878875
if let Some(ref mut local_commitment) = self.local_commitment {
879-
let mut local_commitment = local_commitment.clone();
876+
let local_commitment = local_commitment.clone();
880877
match self.key_storage.sign_local_commitment(&local_commitment, &self.secp_ctx) {
881-
Ok(sig) => local_commitment.add_local_sig(funding_redeemscript, sig),
878+
Ok(sig) => Some(local_commitment.add_local_sig(funding_redeemscript, sig)),
882879
Err(_) => return None,
883880
}
884-
return Some(local_commitment.with_valid_witness().clone());
881+
} else {
882+
None
885883
}
886-
None
887884
}
888885

889886
pub(super) fn get_fully_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option<PaymentPreimage>) -> Option<Transaction> {

0 commit comments

Comments
 (0)