Skip to content

Commit 02c1925

Browse files
authored
Merge pull request #559 from ariard/2020-03-move-local-commitment
Split parsing and transaction management for local transactions between Chanmon/Onchain
2 parents a44454e + 95830ed commit 02c1925

8 files changed

+635
-310
lines changed

lightning/src/chain/keysinterface.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ use util::logger::Logger;
2424
use util::ser::{Writeable, Writer, Readable};
2525

2626
use ln::chan_utils;
27-
use ln::chan_utils::{TxCreationKeys, HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys};
27+
use ln::chan_utils::{TxCreationKeys, HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys, LocalCommitmentTransaction};
28+
use ln::channelmanager::PaymentPreimage;
2829
use ln::msgs;
2930

3031
use std::sync::Arc;
@@ -215,6 +216,26 @@ pub trait ChannelKeys : Send+Clone {
215216
/// making the callee generate it via some util function we expose)!
216217
fn sign_remote_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, feerate_per_kw: u64, commitment_tx: &Transaction, keys: &TxCreationKeys, htlcs: &[&HTLCOutputInCommitment], to_self_delay: u16, secp_ctx: &Secp256k1<T>) -> Result<(Signature, Vec<Signature>), ()>;
217218

219+
/// Create a signature for a local commitment transaction
220+
///
221+
/// TODO: Document the things someone using this interface should enforce before signing.
222+
/// TODO: Add more input vars to enable better checking (preferably removing commitment_tx and
223+
/// TODO: Ensure test-only version doesn't enforce uniqueness of signature when it's enforced in this method
224+
/// making the callee generate it via some util function we expose)!
225+
fn sign_local_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, local_commitment_tx: &mut LocalCommitmentTransaction, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1<T>);
226+
227+
/// Create a signature for a local commitment transaction without enforcing one-time signing.
228+
///
229+
/// Testing revocation logic by our test framework needs to sign multiple local commitment
230+
/// transactions. This unsafe test-only version doesn't enforce one-time signing security
231+
/// requirement.
232+
#[cfg(test)]
233+
fn unsafe_sign_local_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, local_commitment_tx: &mut LocalCommitmentTransaction, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1<T>);
234+
235+
/// Signs a transaction created by build_htlc_transaction. If the transaction is an
236+
/// HTLC-Success transaction, preimage must be set!
237+
/// TODO: should be merged with sign_local_commitment as a slice of HTLC transactions to sign
238+
fn sign_htlc_transaction<T: secp256k1::Signing>(&self, local_commitment_tx: &mut LocalCommitmentTransaction, htlc_index: u32, preimage: Option<PaymentPreimage>, local_csv: u16, secp_ctx: &Secp256k1<T>);
218239
/// Create a signature for a (proposed) closing transaction.
219240
///
220241
/// Note that, due to rounding, there may be one "missing" satoshi, and either party may have
@@ -342,6 +363,19 @@ impl ChannelKeys for InMemoryChannelKeys {
342363
Ok((commitment_sig, htlc_sigs))
343364
}
344365

366+
fn sign_local_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, local_commitment_tx: &mut LocalCommitmentTransaction, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1<T>) {
367+
local_commitment_tx.add_local_sig(&self.funding_key, funding_redeemscript, channel_value_satoshis, secp_ctx);
368+
}
369+
370+
#[cfg(test)]
371+
fn unsafe_sign_local_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, local_commitment_tx: &mut LocalCommitmentTransaction, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1<T>) {
372+
local_commitment_tx.add_local_sig(&self.funding_key, funding_redeemscript, channel_value_satoshis, secp_ctx);
373+
}
374+
375+
fn sign_htlc_transaction<T: secp256k1::Signing>(&self, local_commitment_tx: &mut LocalCommitmentTransaction, htlc_index: u32, preimage: Option<PaymentPreimage>, local_csv: u16, secp_ctx: &Secp256k1<T>) {
376+
local_commitment_tx.add_htlc_sig(&self.htlc_base_key, htlc_index, preimage, local_csv, secp_ctx);
377+
}
378+
345379
fn sign_closing_transaction<T: secp256k1::Signing>(&self, closing_tx: &Transaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
346380
if closing_tx.input.len() != 1 { return Err(()); }
347381
if closing_tx.input[0].witness.len() != 0 { return Err(()); }

lightning/src/ln/chan_utils.rs

Lines changed: 134 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ use secp256k1::key::{SecretKey, PublicKey};
2323
use secp256k1::{Secp256k1, Signature};
2424
use secp256k1;
2525

26+
use std::{cmp, mem};
27+
28+
const MAX_ALLOC_SIZE: usize = 64*1024;
29+
2630
pub(super) const HTLC_SUCCESS_TX_WEIGHT: u64 = 703;
2731
pub(super) const HTLC_TIMEOUT_TX_WEIGHT: u64 = 663;
2832

@@ -355,7 +359,7 @@ impl_writeable!(HTLCOutputInCommitment, 1 + 8 + 4 + 32 + 5, {
355359
});
356360

357361
#[inline]
358-
pub(super) fn get_htlc_redeemscript_with_explicit_keys(htlc: &HTLCOutputInCommitment, a_htlc_key: &PublicKey, b_htlc_key: &PublicKey, revocation_key: &PublicKey) -> Script {
362+
pub(crate) fn get_htlc_redeemscript_with_explicit_keys(htlc: &HTLCOutputInCommitment, a_htlc_key: &PublicKey, b_htlc_key: &PublicKey, revocation_key: &PublicKey) -> Script {
359363
let payment_hash160 = Ripemd160::hash(&htlc.payment_hash.0[..]).into_inner();
360364
if htlc.offered {
361365
Builder::new().push_opcode(opcodes::all::OP_DUP)
@@ -475,62 +479,44 @@ pub fn build_htlc_transaction(prev_hash: &Sha256dHash, feerate_per_kw: u64, to_s
475479
}
476480
}
477481

478-
/// Signs a transaction created by build_htlc_transaction. If the transaction is an
479-
/// HTLC-Success transaction (ie htlc.offered is false), preimage must be set!
480-
pub(crate) fn sign_htlc_transaction<T: secp256k1::Signing>(tx: &mut Transaction, their_sig: &Signature, preimage: &Option<PaymentPreimage>, htlc: &HTLCOutputInCommitment, a_htlc_key: &PublicKey, b_htlc_key: &PublicKey, revocation_key: &PublicKey, per_commitment_point: &PublicKey, htlc_base_key: &SecretKey, secp_ctx: &Secp256k1<T>) -> Result<(Signature, Script), ()> {
481-
if tx.input.len() != 1 { return Err(()); }
482-
if tx.input[0].witness.len() != 0 { return Err(()); }
483-
484-
let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&htlc, a_htlc_key, b_htlc_key, revocation_key);
485-
486-
let our_htlc_key = derive_private_key(secp_ctx, per_commitment_point, htlc_base_key).map_err(|_| ())?;
487-
let sighash = hash_to_message!(&bip143::SighashComponents::new(&tx).sighash_all(&tx.input[0], &htlc_redeemscript, htlc.amount_msat / 1000)[..]);
488-
let local_tx = PublicKey::from_secret_key(&secp_ctx, &our_htlc_key) == *a_htlc_key;
489-
let our_sig = secp_ctx.sign(&sighash, &our_htlc_key);
490-
491-
tx.input[0].witness.push(Vec::new()); // First is the multisig dummy
492-
493-
if local_tx { // b, then a
494-
tx.input[0].witness.push(their_sig.serialize_der().to_vec());
495-
tx.input[0].witness.push(our_sig.serialize_der().to_vec());
496-
} else {
497-
tx.input[0].witness.push(our_sig.serialize_der().to_vec());
498-
tx.input[0].witness.push(their_sig.serialize_der().to_vec());
499-
}
500-
tx.input[0].witness[1].push(SigHashType::All as u8);
501-
tx.input[0].witness[2].push(SigHashType::All as u8);
502-
503-
if htlc.offered {
504-
tx.input[0].witness.push(Vec::new());
505-
assert!(preimage.is_none());
506-
} else {
507-
tx.input[0].witness.push(preimage.unwrap().0.to_vec());
508-
}
509-
510-
tx.input[0].witness.push(htlc_redeemscript.as_bytes().to_vec());
511-
512-
Ok((our_sig, htlc_redeemscript))
513-
}
514-
515482
#[derive(Clone)]
516483
/// We use this to track local commitment transactions and put off signing them until we are ready
517484
/// to broadcast. Eventually this will require a signer which is possibly external, but for now we
518485
/// just pass in the SecretKeys required.
519-
pub(crate) struct LocalCommitmentTransaction {
520-
tx: Transaction
486+
pub struct LocalCommitmentTransaction {
487+
tx: Transaction,
488+
//TODO: modify Channel methods to integrate HTLC material at LocalCommitmentTransaction generation to drop Option here
489+
local_keys: Option<TxCreationKeys>,
490+
feerate_per_kw: Option<u64>,
491+
per_htlc: Vec<(HTLCOutputInCommitment, Option<Signature>, Option<Transaction>)>
521492
}
522493
impl LocalCommitmentTransaction {
523494
#[cfg(test)]
524495
pub fn dummy() -> Self {
496+
let dummy_input = TxIn {
497+
previous_output: OutPoint {
498+
txid: Default::default(),
499+
vout: 0,
500+
},
501+
script_sig: Default::default(),
502+
sequence: 0,
503+
witness: vec![vec![], vec![], vec![]]
504+
};
525505
Self { tx: Transaction {
526506
version: 2,
527-
input: Vec::new(),
507+
input: vec![dummy_input],
528508
output: Vec::new(),
529509
lock_time: 0,
530-
} }
510+
},
511+
local_keys: None,
512+
feerate_per_kw: None,
513+
per_htlc: Vec::new()
514+
}
531515
}
532516

533-
pub fn new_missing_local_sig(mut tx: Transaction, their_sig: &Signature, our_funding_key: &PublicKey, their_funding_key: &PublicKey) -> LocalCommitmentTransaction {
517+
/// Generate a new LocalCommitmentTransaction based on a raw commitment transaction,
518+
/// remote signature and both parties keys
519+
pub(crate) fn new_missing_local_sig(mut tx: Transaction, their_sig: &Signature, our_funding_key: &PublicKey, their_funding_key: &PublicKey) -> LocalCommitmentTransaction {
534520
if tx.input.len() != 1 { panic!("Tried to store a commitment transaction that had input count != 1!"); }
535521
if tx.input[0].witness.len() != 0 { panic!("Tried to store a signed commitment transaction?"); }
536522

@@ -546,13 +532,20 @@ impl LocalCommitmentTransaction {
546532
tx.input[0].witness.push(Vec::new());
547533
}
548534

549-
Self { tx }
535+
Self { tx,
536+
local_keys: None,
537+
feerate_per_kw: None,
538+
per_htlc: Vec::new()
539+
}
550540
}
551541

542+
/// Get the txid of the local commitment transaction contained in this
543+
/// LocalCommitmentTransaction
552544
pub fn txid(&self) -> Sha256dHash {
553545
self.tx.txid()
554546
}
555547

548+
/// Check if LocalCommitmentTransaction has already been signed by us
556549
pub fn has_local_sig(&self) -> bool {
557550
if self.tx.input.len() != 1 { panic!("Commitment transactions must have input count == 1!"); }
558551
if self.tx.input[0].witness.len() == 4 {
@@ -567,6 +560,15 @@ impl LocalCommitmentTransaction {
567560
}
568561
}
569562

563+
/// Add local signature for LocalCommitmentTransaction, do nothing if signature is already
564+
/// present
565+
///
566+
/// Funding key is your key included in the 2-2 funding_outpoint lock. Should be provided
567+
/// by your ChannelKeys.
568+
/// Funding redeemscript is script locking funding_outpoint. This is the mutlsig script
569+
/// between your own funding key and your counterparty's. Currently, this is provided in
570+
/// ChannelKeys::sign_local_commitment() calls directly.
571+
/// Channel value is amount locked in funding_outpoint.
570572
pub fn add_local_sig<T: secp256k1::Signing>(&mut self, funding_key: &SecretKey, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1<T>) {
571573
if self.has_local_sig() { return; }
572574
let sighash = hash_to_message!(&bip143::SighashComponents::new(&self.tx)
@@ -584,11 +586,74 @@ impl LocalCommitmentTransaction {
584586
self.tx.input[0].witness.push(funding_redeemscript.as_bytes().to_vec());
585587
}
586588

587-
pub fn without_valid_witness(&self) -> &Transaction { &self.tx }
589+
/// Get raw transaction without asserting if witness is complete
590+
pub(crate) fn without_valid_witness(&self) -> &Transaction { &self.tx }
591+
/// Get raw transaction with panics if witness is incomplete
588592
pub fn with_valid_witness(&self) -> &Transaction {
589593
assert!(self.has_local_sig());
590594
&self.tx
591595
}
596+
597+
/// Set HTLC cache to generate any local HTLC transaction spending one of htlc ouput
598+
/// from this local commitment transaction
599+
pub(crate) fn set_htlc_cache(&mut self, local_keys: TxCreationKeys, feerate_per_kw: u64, htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Signature>, Option<Transaction>)>) {
600+
self.local_keys = Some(local_keys);
601+
self.feerate_per_kw = Some(feerate_per_kw);
602+
self.per_htlc = htlc_outputs;
603+
}
604+
605+
/// Add local signature for a htlc transaction, do nothing if a cached signed transaction is
606+
/// already present
607+
pub fn add_htlc_sig<T: secp256k1::Signing>(&mut self, htlc_base_key: &SecretKey, htlc_index: u32, preimage: Option<PaymentPreimage>, local_csv: u16, secp_ctx: &Secp256k1<T>) {
608+
if self.local_keys.is_none() || self.feerate_per_kw.is_none() { return; }
609+
let local_keys = self.local_keys.as_ref().unwrap();
610+
let txid = self.txid();
611+
for this_htlc in self.per_htlc.iter_mut() {
612+
if this_htlc.0.transaction_output_index.unwrap() == htlc_index {
613+
if this_htlc.2.is_some() { return; } // we already have a cached htlc transaction at provided index
614+
let mut htlc_tx = build_htlc_transaction(&txid, self.feerate_per_kw.unwrap(), local_csv, &this_htlc.0, &local_keys.a_delayed_payment_key, &local_keys.revocation_key);
615+
if !this_htlc.0.offered && preimage.is_none() { return; } // if we don't have preimage for HTLC-Success, don't try to generate
616+
let htlc_secret = if !this_htlc.0.offered { preimage } else { None }; // if we have a preimage for HTLC-Timeout, don't use it that's likely a duplicate HTLC hash
617+
if this_htlc.1.is_none() { return; } // we don't have any remote signature for this htlc
618+
if htlc_tx.input.len() != 1 { return; }
619+
if htlc_tx.input[0].witness.len() != 0 { return; }
620+
621+
let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc.0, &local_keys.a_htlc_key, &local_keys.b_htlc_key, &local_keys.revocation_key);
622+
623+
if let Ok(our_htlc_key) = derive_private_key(secp_ctx, &local_keys.per_commitment_point, htlc_base_key) {
624+
let sighash = hash_to_message!(&bip143::SighashComponents::new(&htlc_tx).sighash_all(&htlc_tx.input[0], &htlc_redeemscript, this_htlc.0.amount_msat / 1000)[..]);
625+
let our_sig = secp_ctx.sign(&sighash, &our_htlc_key);
626+
627+
htlc_tx.input[0].witness.push(Vec::new()); // First is the multisig dummy
628+
629+
htlc_tx.input[0].witness.push(this_htlc.1.unwrap().serialize_der().to_vec());
630+
htlc_tx.input[0].witness.push(our_sig.serialize_der().to_vec());
631+
htlc_tx.input[0].witness[1].push(SigHashType::All as u8);
632+
htlc_tx.input[0].witness[2].push(SigHashType::All as u8);
633+
634+
if this_htlc.0.offered {
635+
htlc_tx.input[0].witness.push(Vec::new());
636+
assert!(htlc_secret.is_none());
637+
} else {
638+
htlc_tx.input[0].witness.push(htlc_secret.unwrap().0.to_vec());
639+
}
640+
641+
htlc_tx.input[0].witness.push(htlc_redeemscript.as_bytes().to_vec());
642+
643+
this_htlc.2 = Some(htlc_tx);
644+
} else { return; }
645+
}
646+
}
647+
}
648+
/// Expose raw htlc transaction, guarante witness is complete if non-empty
649+
pub fn htlc_with_valid_witness(&self, htlc_index: u32) -> &Option<Transaction> {
650+
for this_htlc in self.per_htlc.iter() {
651+
if this_htlc.0.transaction_output_index.unwrap() == htlc_index {
652+
return &this_htlc.2;
653+
}
654+
}
655+
&None
656+
}
592657
}
593658
impl PartialEq for LocalCommitmentTransaction {
594659
// We dont care whether we are signed in equality comparison
@@ -604,6 +669,14 @@ impl Writeable for LocalCommitmentTransaction {
604669
_ => panic!("local tx must have been well-formed!"),
605670
}
606671
}
672+
self.local_keys.write(writer)?;
673+
self.feerate_per_kw.write(writer)?;
674+
writer.write_all(&byte_utils::be64_to_array(self.per_htlc.len() as u64))?;
675+
for &(ref htlc, ref sig, ref htlc_tx) in self.per_htlc.iter() {
676+
htlc.write(writer)?;
677+
sig.write(writer)?;
678+
htlc_tx.write(writer)?;
679+
}
607680
Ok(())
608681
}
609682
}
@@ -616,12 +689,27 @@ impl Readable for LocalCommitmentTransaction {
616689
_ => return Err(DecodeError::InvalidValue),
617690
},
618691
};
692+
let local_keys = Readable::read(reader)?;
693+
let feerate_per_kw = Readable::read(reader)?;
694+
let htlcs_count: u64 = Readable::read(reader)?;
695+
let mut per_htlc = Vec::with_capacity(cmp::min(htlcs_count as usize, MAX_ALLOC_SIZE / mem::size_of::<(HTLCOutputInCommitment, Option<Signature>, Option<Transaction>)>()));
696+
for _ in 0..htlcs_count {
697+
let htlc: HTLCOutputInCommitment = Readable::read(reader)?;
698+
let sigs = Readable::read(reader)?;
699+
let htlc_tx = Readable::read(reader)?;
700+
per_htlc.push((htlc, sigs, htlc_tx));
701+
}
619702

620703
if tx.input.len() != 1 {
621704
// Ensure tx didn't hit the 0-input ambiguity case.
622705
return Err(DecodeError::InvalidValue);
623706
}
624-
Ok(Self { tx })
707+
Ok(Self {
708+
tx,
709+
local_keys,
710+
feerate_per_kw,
711+
per_htlc,
712+
})
625713
}
626714
}
627715

lightning/src/ln/channel.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4433,7 +4433,7 @@ mod tests {
44334433

44344434
assert_eq!(PublicKey::from_secret_key(&secp_ctx, chan_keys.funding_key()).serialize()[..],
44354435
hex::decode("023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb").unwrap()[..]);
4436-
let keys_provider = Keys { chan_keys };
4436+
let keys_provider = Keys { chan_keys: chan_keys.clone() };
44374437

44384438
let their_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
44394439
let mut config = UserConfig::default();
@@ -4475,6 +4475,7 @@ mod tests {
44754475

44764476
let mut unsigned_tx: (Transaction, Vec<HTLCOutputInCommitment>);
44774477

4478+
let mut localtx;
44784479
macro_rules! test_commitment {
44794480
( $their_sig_hex: expr, $our_sig_hex: expr, $tx_hex: expr) => {
44804481
unsigned_tx = {
@@ -4489,20 +4490,20 @@ mod tests {
44894490
let sighash = Message::from_slice(&bip143::SighashComponents::new(&unsigned_tx.0).sighash_all(&unsigned_tx.0.input[0], &redeemscript, chan.channel_value_satoshis)[..]).unwrap();
44904491
secp_ctx.verify(&sighash, &their_signature, chan.their_funding_pubkey()).unwrap();
44914492

4492-
let mut 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());
4493-
localtx.add_local_sig(chan.local_keys.funding_key(), &redeemscript, chan.channel_value_satoshis, &chan.secp_ctx);
4493+
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());
4494+
chan_keys.sign_local_commitment(&mut localtx, &redeemscript, chan.channel_value_satoshis, &chan.secp_ctx);
44944495

44954496
assert_eq!(serialize(localtx.with_valid_witness())[..],
44964497
hex::decode($tx_hex).unwrap()[..]);
44974498
};
44984499
}
44994500

45004501
macro_rules! test_htlc_output {
4501-
( $htlc_idx: expr, $their_sig_hex: expr, $our_sig_hex: expr, $tx_hex: expr ) => {
4502+
( $htlc_idx: expr, $their_sig_hex: expr, $our_sig_hex: expr, $tx_hex: expr) => {
45024503
let remote_signature = Signature::from_der(&hex::decode($their_sig_hex).unwrap()[..]).unwrap();
45034504

45044505
let ref htlc = unsigned_tx.1[$htlc_idx];
4505-
let mut htlc_tx = chan.build_htlc_transaction(&unsigned_tx.0.txid(), &htlc, true, &keys, chan.feerate_per_kw);
4506+
let htlc_tx = chan.build_htlc_transaction(&unsigned_tx.0.txid(), &htlc, true, &keys, chan.feerate_per_kw);
45064507
let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, &keys);
45074508
let htlc_sighash = Message::from_slice(&bip143::SighashComponents::new(&htlc_tx).sighash_all(&htlc_tx.input[0], &htlc_redeemscript, htlc.amount_msat / 1000)[..]).unwrap();
45084509
secp_ctx.verify(&htlc_sighash, &remote_signature, &keys.b_htlc_key).unwrap();
@@ -4519,8 +4520,12 @@ mod tests {
45194520
assert!(preimage.is_some());
45204521
}
45214522

4522-
chan_utils::sign_htlc_transaction(&mut htlc_tx, &remote_signature, &preimage, &htlc, &keys.a_htlc_key, &keys.b_htlc_key, &keys.revocation_key, &keys.per_commitment_point, chan.local_keys.htlc_base_key(), &chan.secp_ctx).unwrap();
4523-
assert_eq!(serialize(&htlc_tx)[..],
4523+
let mut per_htlc = Vec::new();
4524+
per_htlc.push((htlc.clone(), Some(remote_signature), None));
4525+
localtx.set_htlc_cache(keys.clone(), chan.feerate_per_kw, per_htlc);
4526+
chan_keys.sign_htlc_transaction(&mut localtx, $htlc_idx, preimage, chan.their_to_self_delay, &chan.secp_ctx);
4527+
4528+
assert_eq!(serialize(localtx.htlc_with_valid_witness($htlc_idx).as_ref().unwrap())[..],
45244529
hex::decode($tx_hex).unwrap()[..]);
45254530
};
45264531
}

0 commit comments

Comments
 (0)