Skip to content

Commit 288fe02

Browse files
authored
Merge pull request #2286 from benthecarman/spendable-outputs-psbt
Create and Sign PSBTs for spendable outputs
2 parents e61b128 + 8c0479a commit 288fe02

File tree

4 files changed

+171
-82
lines changed

4 files changed

+171
-82
lines changed

lightning/src/ln/functional_tests.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -4263,15 +4263,15 @@ macro_rules! check_spendable_outputs {
42634263
match event {
42644264
Event::SpendableOutputs { mut outputs } => {
42654265
for outp in outputs.drain(..) {
4266-
txn.push($keysinterface.backing.spend_spendable_outputs(&[&outp], Vec::new(), Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, &secp_ctx).unwrap());
4266+
txn.push($keysinterface.backing.spend_spendable_outputs(&[&outp], Vec::new(), Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, None, &secp_ctx).unwrap());
42674267
all_outputs.push(outp);
42684268
}
42694269
},
42704270
_ => panic!("Unexpected event"),
42714271
};
42724272
}
42734273
if all_outputs.len() > 1 {
4274-
if let Ok(tx) = $keysinterface.backing.spend_spendable_outputs(&all_outputs.iter().map(|a| a).collect::<Vec<_>>(), Vec::new(), Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, &secp_ctx) {
4274+
if let Ok(tx) = $keysinterface.backing.spend_spendable_outputs(&all_outputs.iter().map(|a| a).collect::<Vec<_>>(), Vec::new(), Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, None, &secp_ctx) {
42754275
txn.push(tx);
42764276
}
42774277
}

lightning/src/ln/monitor_tests.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ fn test_spendable_output<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, spendable_t
114114
if let Event::SpendableOutputs { outputs } = spendable.pop().unwrap() {
115115
assert_eq!(outputs.len(), 1);
116116
let spend_tx = node.keys_manager.backing.spend_spendable_outputs(&[&outputs[0]], Vec::new(),
117-
Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, &Secp256k1::new()).unwrap();
117+
Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, None, &Secp256k1::new()).unwrap();
118118
check_spends!(spend_tx, spendable_tx);
119119
} else { panic!(); }
120120
}
@@ -2348,7 +2348,7 @@ fn test_anchors_aggregated_revoked_htlc_tx() {
23482348
if let Event::SpendableOutputs { outputs } = event {
23492349
assert_eq!(outputs.len(), 1);
23502350
let spend_tx = nodes[0].keys_manager.backing.spend_spendable_outputs(
2351-
&[&outputs[0]], Vec::new(), Script::new_op_return(&[]), 253, &Secp256k1::new(),
2351+
&[&outputs[0]], Vec::new(), Script::new_op_return(&[]), 253, None, &Secp256k1::new(),
23522352
).unwrap();
23532353
check_spends!(spend_tx, revoked_claims[idx]);
23542354
} else {

lightning/src/ln/reorg_tests.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ fn do_test_to_remote_after_local_detection(style: ConnectStyle) {
587587
if let Event::SpendableOutputs { outputs } = node_a_spendable.pop().unwrap() {
588588
assert_eq!(outputs.len(), 1);
589589
let spend_tx = nodes[0].keys_manager.backing.spend_spendable_outputs(&[&outputs[0]], Vec::new(),
590-
Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, &Secp256k1::new()).unwrap();
590+
Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, None, &Secp256k1::new()).unwrap();
591591
check_spends!(spend_tx, remote_txn_b[0]);
592592
}
593593

@@ -607,7 +607,7 @@ fn do_test_to_remote_after_local_detection(style: ConnectStyle) {
607607
if let Event::SpendableOutputs { outputs } = node_b_spendable.pop().unwrap() {
608608
assert_eq!(outputs.len(), 1);
609609
let spend_tx = nodes[1].keys_manager.backing.spend_spendable_outputs(&[&outputs[0]], Vec::new(),
610-
Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, &Secp256k1::new()).unwrap();
610+
Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, None, &Secp256k1::new()).unwrap();
611611
check_spends!(spend_tx, remote_txn_a[0]);
612612
}
613613
}

lightning/src/sign/mod.rs

+165-76
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use bitcoin::blockdata::transaction::{Transaction, TxOut, TxIn, EcdsaSighashType
1616
use bitcoin::blockdata::script::{Script, Builder};
1717
use bitcoin::blockdata::opcodes;
1818
use bitcoin::network::constants::Network;
19+
use bitcoin::psbt::PartiallySignedTransaction;
1920
use bitcoin::util::bip32::{ExtendedPrivKey, ExtendedPubKey, ChildNumber};
2021
use bitcoin::util::sighash;
2122

@@ -218,6 +219,126 @@ impl_writeable_tlv_based_enum!(SpendableOutputDescriptor,
218219
(2, StaticPaymentOutput),
219220
);
220221

222+
impl SpendableOutputDescriptor {
223+
/// Turns this into a [`bitcoin::psbt::Input`] which can be used to create a
224+
/// [`PartiallySignedTransaction`] which spends the given descriptor.
225+
///
226+
/// Note that this does not include any signatures, just the information required to
227+
/// construct the transaction and sign it.
228+
pub fn to_psbt_input(&self) -> bitcoin::psbt::Input {
229+
match self {
230+
SpendableOutputDescriptor::StaticOutput { output, .. } => {
231+
// Is a standard P2WPKH, no need for witness script
232+
bitcoin::psbt::Input {
233+
witness_utxo: Some(output.clone()),
234+
..Default::default()
235+
}
236+
},
237+
SpendableOutputDescriptor::DelayedPaymentOutput(descriptor) => {
238+
// TODO we could add the witness script as well
239+
bitcoin::psbt::Input {
240+
witness_utxo: Some(descriptor.output.clone()),
241+
..Default::default()
242+
}
243+
},
244+
SpendableOutputDescriptor::StaticPaymentOutput(descriptor) => {
245+
// TODO we could add the witness script as well
246+
bitcoin::psbt::Input {
247+
witness_utxo: Some(descriptor.output.clone()),
248+
..Default::default()
249+
}
250+
},
251+
}
252+
}
253+
254+
/// Creates an unsigned [`PartiallySignedTransaction`] which spends the given descriptors to
255+
/// the given outputs, plus an output to the given change destination (if sufficient
256+
/// change value remains). The PSBT will have a feerate, at least, of the given value.
257+
///
258+
/// The `locktime` argument is used to set the transaction's locktime. If `None`, the
259+
/// transaction will have a locktime of 0. It it recommended to set this to the current block
260+
/// height to avoid fee sniping, unless you have some specific reason to use a different
261+
/// locktime.
262+
///
263+
/// Returns the PSBT and expected max transaction weight.
264+
///
265+
/// Returns `Err(())` if the output value is greater than the input value minus required fee,
266+
/// if a descriptor was duplicated, or if an output descriptor `script_pubkey`
267+
/// does not match the one we can spend.
268+
///
269+
/// We do not enforce that outputs meet the dust limit or that any output scripts are standard.
270+
pub fn create_spendable_outputs_psbt(descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>, change_destination_script: Script, feerate_sat_per_1000_weight: u32, locktime: Option<PackedLockTime>) -> Result<(PartiallySignedTransaction, usize), ()> {
271+
let mut input = Vec::with_capacity(descriptors.len());
272+
let mut input_value = 0;
273+
let mut witness_weight = 0;
274+
let mut output_set = HashSet::with_capacity(descriptors.len());
275+
for outp in descriptors {
276+
match outp {
277+
SpendableOutputDescriptor::StaticPaymentOutput(descriptor) => {
278+
if !output_set.insert(descriptor.outpoint) { return Err(()); }
279+
input.push(TxIn {
280+
previous_output: descriptor.outpoint.into_bitcoin_outpoint(),
281+
script_sig: Script::new(),
282+
sequence: Sequence::ZERO,
283+
witness: Witness::new(),
284+
});
285+
witness_weight += StaticPaymentOutputDescriptor::MAX_WITNESS_LENGTH;
286+
#[cfg(feature = "grind_signatures")]
287+
{ witness_weight -= 1; } // Guarantees a low R signature
288+
input_value += descriptor.output.value;
289+
},
290+
SpendableOutputDescriptor::DelayedPaymentOutput(descriptor) => {
291+
if !output_set.insert(descriptor.outpoint) { return Err(()); }
292+
input.push(TxIn {
293+
previous_output: descriptor.outpoint.into_bitcoin_outpoint(),
294+
script_sig: Script::new(),
295+
sequence: Sequence(descriptor.to_self_delay as u32),
296+
witness: Witness::new(),
297+
});
298+
witness_weight += DelayedPaymentOutputDescriptor::MAX_WITNESS_LENGTH;
299+
#[cfg(feature = "grind_signatures")]
300+
{ witness_weight -= 1; } // Guarantees a low R signature
301+
input_value += descriptor.output.value;
302+
},
303+
SpendableOutputDescriptor::StaticOutput { ref outpoint, ref output } => {
304+
if !output_set.insert(*outpoint) { return Err(()); }
305+
input.push(TxIn {
306+
previous_output: outpoint.into_bitcoin_outpoint(),
307+
script_sig: Script::new(),
308+
sequence: Sequence::ZERO,
309+
witness: Witness::new(),
310+
});
311+
witness_weight += 1 + 73 + 34;
312+
#[cfg(feature = "grind_signatures")]
313+
{ witness_weight -= 1; } // Guarantees a low R signature
314+
input_value += output.value;
315+
}
316+
}
317+
if input_value > MAX_VALUE_MSAT / 1000 { return Err(()); }
318+
}
319+
let mut tx = Transaction {
320+
version: 2,
321+
lock_time: locktime.unwrap_or(PackedLockTime::ZERO),
322+
input,
323+
output: outputs,
324+
};
325+
let expected_max_weight =
326+
transaction_utils::maybe_add_change_output(&mut tx, input_value, witness_weight, feerate_sat_per_1000_weight, change_destination_script)?;
327+
328+
let psbt_inputs = descriptors.iter().map(|d| d.to_psbt_input()).collect::<Vec<_>>();
329+
let psbt = PartiallySignedTransaction {
330+
inputs: psbt_inputs,
331+
outputs: vec![Default::default(); tx.output.len()],
332+
unsigned_tx: tx,
333+
xpub: Default::default(),
334+
version: 0,
335+
proprietary: Default::default(),
336+
unknown: Default::default(),
337+
};
338+
Ok((psbt, expected_max_weight))
339+
}
340+
}
341+
221342
/// A trait to handle Lightning channel key material without concretizing the channel type or
222343
/// the signature mechanism.
223344
pub trait ChannelSigner {
@@ -1171,97 +1292,40 @@ impl KeysManager {
11711292
)
11721293
}
11731294

1174-
/// Creates a [`Transaction`] which spends the given descriptors to the given outputs, plus an
1175-
/// output to the given change destination (if sufficient change value remains). The
1176-
/// transaction will have a feerate, at least, of the given value.
1295+
/// Signs the given [`PartiallySignedTransaction`] which spends the given [`SpendableOutputDescriptor`]s.
1296+
/// The resulting inputs will be finalized and the PSBT will be ready for broadcast if there
1297+
/// are no other inputs that need signing.
11771298
///
1178-
/// Returns `Err(())` if the output value is greater than the input value minus required fee,
1179-
/// if a descriptor was duplicated, or if an output descriptor `script_pubkey`
1180-
/// does not match the one we can spend.
1181-
///
1182-
/// We do not enforce that outputs meet the dust limit or that any output scripts are standard.
1299+
/// Returns `Err(())` if the PSBT is missing a descriptor or if we fail to sign.
11831300
///
11841301
/// May panic if the [`SpendableOutputDescriptor`]s were not generated by channels which used
11851302
/// this [`KeysManager`] or one of the [`InMemorySigner`] created by this [`KeysManager`].
1186-
pub fn spend_spendable_outputs<C: Signing>(&self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>, change_destination_script: Script, feerate_sat_per_1000_weight: u32, secp_ctx: &Secp256k1<C>) -> Result<Transaction, ()> {
1187-
let mut input = Vec::new();
1188-
let mut input_value = 0;
1189-
let mut witness_weight = 0;
1190-
let mut output_set = HashSet::with_capacity(descriptors.len());
1191-
for outp in descriptors {
1192-
match outp {
1193-
SpendableOutputDescriptor::StaticPaymentOutput(descriptor) => {
1194-
input.push(TxIn {
1195-
previous_output: descriptor.outpoint.into_bitcoin_outpoint(),
1196-
script_sig: Script::new(),
1197-
sequence: Sequence::ZERO,
1198-
witness: Witness::new(),
1199-
});
1200-
witness_weight += StaticPaymentOutputDescriptor::MAX_WITNESS_LENGTH;
1201-
#[cfg(feature = "grind_signatures")]
1202-
{ witness_weight -= 1; } // Guarantees a low R signature
1203-
input_value += descriptor.output.value;
1204-
if !output_set.insert(descriptor.outpoint) { return Err(()); }
1205-
},
1206-
SpendableOutputDescriptor::DelayedPaymentOutput(descriptor) => {
1207-
input.push(TxIn {
1208-
previous_output: descriptor.outpoint.into_bitcoin_outpoint(),
1209-
script_sig: Script::new(),
1210-
sequence: Sequence(descriptor.to_self_delay as u32),
1211-
witness: Witness::new(),
1212-
});
1213-
witness_weight += DelayedPaymentOutputDescriptor::MAX_WITNESS_LENGTH;
1214-
#[cfg(feature = "grind_signatures")]
1215-
{ witness_weight -= 1; } // Guarantees a low R signature
1216-
input_value += descriptor.output.value;
1217-
if !output_set.insert(descriptor.outpoint) { return Err(()); }
1218-
},
1219-
SpendableOutputDescriptor::StaticOutput { ref outpoint, ref output } => {
1220-
input.push(TxIn {
1221-
previous_output: outpoint.into_bitcoin_outpoint(),
1222-
script_sig: Script::new(),
1223-
sequence: Sequence::ZERO,
1224-
witness: Witness::new(),
1225-
});
1226-
witness_weight += 1 + 73 + 34;
1227-
#[cfg(feature = "grind_signatures")]
1228-
{ witness_weight -= 1; } // Guarantees a low R signature
1229-
input_value += output.value;
1230-
if !output_set.insert(*outpoint) { return Err(()); }
1231-
}
1232-
}
1233-
if input_value > MAX_VALUE_MSAT / 1000 { return Err(()); }
1234-
}
1235-
let mut spend_tx = Transaction {
1236-
version: 2,
1237-
lock_time: PackedLockTime(0),
1238-
input,
1239-
output: outputs,
1240-
};
1241-
let expected_max_weight =
1242-
transaction_utils::maybe_add_change_output(&mut spend_tx, input_value, witness_weight, feerate_sat_per_1000_weight, change_destination_script)?;
1243-
1303+
pub fn sign_spendable_outputs_psbt<C: Signing>(&self, descriptors: &[&SpendableOutputDescriptor], psbt: &mut PartiallySignedTransaction, secp_ctx: &Secp256k1<C>) -> Result<(), ()> {
12441304
let mut keys_cache: Option<(InMemorySigner, [u8; 32])> = None;
1245-
let mut input_idx = 0;
12461305
for outp in descriptors {
12471306
match outp {
12481307
SpendableOutputDescriptor::StaticPaymentOutput(descriptor) => {
1308+
let input_idx = psbt.unsigned_tx.input.iter().position(|i| i.previous_output == descriptor.outpoint.into_bitcoin_outpoint()).ok_or(())?;
12491309
if keys_cache.is_none() || keys_cache.as_ref().unwrap().1 != descriptor.channel_keys_id {
12501310
keys_cache = Some((
12511311
self.derive_channel_keys(descriptor.channel_value_satoshis, &descriptor.channel_keys_id),
12521312
descriptor.channel_keys_id));
12531313
}
1254-
spend_tx.input[input_idx].witness = Witness::from_vec(keys_cache.as_ref().unwrap().0.sign_counterparty_payment_input(&spend_tx, input_idx, &descriptor, &secp_ctx)?);
1314+
let witness = Witness::from_vec(keys_cache.as_ref().unwrap().0.sign_counterparty_payment_input(&psbt.unsigned_tx, input_idx, &descriptor, &secp_ctx)?);
1315+
psbt.inputs[input_idx].final_script_witness = Some(witness);
12551316
},
12561317
SpendableOutputDescriptor::DelayedPaymentOutput(descriptor) => {
1318+
let input_idx = psbt.unsigned_tx.input.iter().position(|i| i.previous_output == descriptor.outpoint.into_bitcoin_outpoint()).ok_or(())?;
12571319
if keys_cache.is_none() || keys_cache.as_ref().unwrap().1 != descriptor.channel_keys_id {
12581320
keys_cache = Some((
12591321
self.derive_channel_keys(descriptor.channel_value_satoshis, &descriptor.channel_keys_id),
12601322
descriptor.channel_keys_id));
12611323
}
1262-
spend_tx.input[input_idx].witness = Witness::from_vec(keys_cache.as_ref().unwrap().0.sign_dynamic_p2wsh_input(&spend_tx, input_idx, &descriptor, &secp_ctx)?);
1324+
let witness = Witness::from_vec(keys_cache.as_ref().unwrap().0.sign_dynamic_p2wsh_input(&psbt.unsigned_tx, input_idx, &descriptor, &secp_ctx)?);
1325+
psbt.inputs[input_idx].final_script_witness = Some(witness);
12631326
},
1264-
SpendableOutputDescriptor::StaticOutput { ref output, .. } => {
1327+
SpendableOutputDescriptor::StaticOutput { ref outpoint, ref output } => {
1328+
let input_idx = psbt.unsigned_tx.input.iter().position(|i| i.previous_output == outpoint.into_bitcoin_outpoint()).ok_or(())?;
12651329
let derivation_idx = if output.script_pubkey == self.destination_script {
12661330
1
12671331
} else {
@@ -1288,17 +1352,42 @@ impl KeysManager {
12881352

12891353
if payment_script != output.script_pubkey { return Err(()); };
12901354

1291-
let sighash = hash_to_message!(&sighash::SighashCache::new(&spend_tx).segwit_signature_hash(input_idx, &witness_script, output.value, EcdsaSighashType::All).unwrap()[..]);
1355+
let sighash = hash_to_message!(&sighash::SighashCache::new(&psbt.unsigned_tx).segwit_signature_hash(input_idx, &witness_script, output.value, EcdsaSighashType::All).unwrap()[..]);
12921356
let sig = sign_with_aux_rand(secp_ctx, &sighash, &secret.private_key, &self);
12931357
let mut sig_ser = sig.serialize_der().to_vec();
12941358
sig_ser.push(EcdsaSighashType::All as u8);
1295-
spend_tx.input[input_idx].witness.push(sig_ser);
1296-
spend_tx.input[input_idx].witness.push(pubkey.inner.serialize().to_vec());
1359+
let witness = Witness::from_vec(vec![sig_ser, pubkey.inner.serialize().to_vec()]);
1360+
psbt.inputs[input_idx].final_script_witness = Some(witness);
12971361
},
12981362
}
1299-
input_idx += 1;
13001363
}
13011364

1365+
Ok(())
1366+
}
1367+
1368+
/// Creates a [`Transaction`] which spends the given descriptors to the given outputs, plus an
1369+
/// output to the given change destination (if sufficient change value remains). The
1370+
/// transaction will have a feerate, at least, of the given value.
1371+
///
1372+
/// The `locktime` argument is used to set the transaction's locktime. If `None`, the
1373+
/// transaction will have a locktime of 0. It it recommended to set this to the current block
1374+
/// height to avoid fee sniping, unless you have some specific reason to use a different
1375+
/// locktime.
1376+
///
1377+
/// Returns `Err(())` if the output value is greater than the input value minus required fee,
1378+
/// if a descriptor was duplicated, or if an output descriptor `script_pubkey`
1379+
/// does not match the one we can spend.
1380+
///
1381+
/// We do not enforce that outputs meet the dust limit or that any output scripts are standard.
1382+
///
1383+
/// May panic if the [`SpendableOutputDescriptor`]s were not generated by channels which used
1384+
/// this [`KeysManager`] or one of the [`InMemorySigner`] created by this [`KeysManager`].
1385+
pub fn spend_spendable_outputs<C: Signing>(&self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>, change_destination_script: Script, feerate_sat_per_1000_weight: u32, locktime: Option<PackedLockTime>, secp_ctx: &Secp256k1<C>) -> Result<Transaction, ()> {
1386+
let (mut psbt, expected_max_weight) = SpendableOutputDescriptor::create_spendable_outputs_psbt(descriptors, outputs, change_destination_script, feerate_sat_per_1000_weight, locktime)?;
1387+
self.sign_spendable_outputs_psbt(descriptors, &mut psbt, secp_ctx)?;
1388+
1389+
let spend_tx = psbt.extract_tx();
1390+
13021391
debug_assert!(expected_max_weight >= spend_tx.weight());
13031392
// Note that witnesses with a signature vary somewhat in size, so allow
13041393
// `expected_max_weight` to overshoot by up to 3 bytes per input.
@@ -1512,8 +1601,8 @@ impl PhantomKeysManager {
15121601
}
15131602

15141603
/// See [`KeysManager::spend_spendable_outputs`] for documentation on this method.
1515-
pub fn spend_spendable_outputs<C: Signing>(&self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>, change_destination_script: Script, feerate_sat_per_1000_weight: u32, secp_ctx: &Secp256k1<C>) -> Result<Transaction, ()> {
1516-
self.inner.spend_spendable_outputs(descriptors, outputs, change_destination_script, feerate_sat_per_1000_weight, secp_ctx)
1604+
pub fn spend_spendable_outputs<C: Signing>(&self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>, change_destination_script: Script, feerate_sat_per_1000_weight: u32, locktime: Option<PackedLockTime>, secp_ctx: &Secp256k1<C>) -> Result<Transaction, ()> {
1605+
self.inner.spend_spendable_outputs(descriptors, outputs, change_destination_script, feerate_sat_per_1000_weight, locktime, secp_ctx)
15171606
}
15181607

15191608
/// See [`KeysManager::derive_channel_keys`] for documentation on this method.

0 commit comments

Comments
 (0)