Skip to content

Commit 0f5ca0f

Browse files
committed
Add util fn for creating a Transaction from spendable outputs
This adds a utility method, `KeysManager::spend_spendable_outputs`, which constructs a Transaction from a given set of `SpendableOutputDescriptor`s, deriving relevant keys as needed. It also adds methods which can sign individual inputs where channel-specific key derivation is required to `InMemoryChannelKeys`, making it easy to sign transaction inputs when a custom `KeysInterface` is used with `InMemoryChannelKeys`.
1 parent 6755b97 commit 0f5ca0f

File tree

1 file changed

+200
-2
lines changed

1 file changed

+200
-2
lines changed

lightning/src/chain/keysinterface.rs

Lines changed: 200 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
//! spendable on-chain outputs which the user owns and is responsible for using just as any other
1212
//! on-chain output which is theirs.
1313
14-
use bitcoin::blockdata::transaction::{Transaction, TxOut, SigHashType};
14+
use bitcoin::blockdata::transaction::{Transaction, TxOut, TxIn, SigHashType};
1515
use bitcoin::blockdata::script::{Script, Builder};
1616
use bitcoin::blockdata::opcodes;
17+
use bitcoin::consensus::Encodable;
18+
use bitcoin::consensus::encode::VarInt;
1719
use bitcoin::network::constants::Network;
1820
use bitcoin::util::bip32::{ExtendedPrivKey, ExtendedPubKey, ChildNumber};
1921
use bitcoin::util::bip143;
@@ -36,9 +38,10 @@ use ln::chan_utils;
3638
use ln::chan_utils::{HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys, HolderCommitmentTransaction, ChannelTransactionParameters, CommitmentTransaction};
3739
use ln::msgs::UnsignedChannelAnnouncement;
3840

41+
use std::collections::HashSet;
3942
use std::sync::atomic::{AtomicUsize, Ordering};
4043
use std::io::Error;
41-
use ln::msgs::DecodeError;
44+
use ln::msgs::{DecodeError, MAX_VALUE_MSAT};
4245

4346
/// Information about a spendable output to a P2WSH script. See
4447
/// SpendableOutputDescriptor::DynamicOutputP2WSH for more details on how to spend this.
@@ -500,6 +503,63 @@ impl InMemoryChannelKeys {
500503
pub fn get_channel_parameters(&self) -> &ChannelTransactionParameters {
501504
self.channel_parameters.as_ref().unwrap()
502505
}
506+
507+
/// Sign the single input of spend_tx at index `input_idx` which spends the output
508+
/// described by descriptor.
509+
///
510+
/// Returns an Err if the input at input_idx does not exist, has a non-empty script_sig,
511+
/// or is not spending the outpoint described by `descriptor.outpoint`.
512+
///
513+
/// (C-not exported) as bindings don't support modifying a Transaction parameter
514+
pub fn sign_counterparty_payment_input<C: Signing>(&self, spend_tx: &mut Transaction, input_idx: usize, descriptor: &StaticCounterpartyPaymentOutputDescriptor, secp_ctx: &Secp256k1<C>) -> Result<(), ()> {
515+
// TODO: We really should be taking the SigHashCache as a parameter here instead of
516+
// spend_tx, but ideally the SigHashCache would expose the transaction's inputs read-only
517+
// so that we can check them. This requires upstream rust-bitcoin changes (as well as
518+
// bindings updates to support SigHashCache objects).
519+
if spend_tx.input.len() <= input_idx { return Err(()); }
520+
if !spend_tx.input[input_idx].script_sig.is_empty() { return Err(()); }
521+
if spend_tx.input[input_idx].previous_output != descriptor.outpoint.into_bitcoin_outpoint() { return Err(()); }
522+
523+
let remotepubkey = self.pubkeys().payment_point;
524+
let witness_script = bitcoin::Address::p2pkh(&::bitcoin::PublicKey{compressed: true, key: remotepubkey}, Network::Testnet).script_pubkey();
525+
let sighash = hash_to_message!(&bip143::SigHashCache::new(&*spend_tx).signature_hash(input_idx, &witness_script, descriptor.output.value, SigHashType::All)[..]);
526+
let remotesig = secp_ctx.sign(&sighash, &self.payment_key);
527+
spend_tx.input[input_idx].witness.push(remotesig.serialize_der().to_vec());
528+
spend_tx.input[input_idx].witness[0].push(SigHashType::All as u8);
529+
spend_tx.input[input_idx].witness.push(remotepubkey.serialize().to_vec());
530+
Ok(())
531+
}
532+
533+
/// Sign the single input of spend_tx at index `input_idx` which spends the output
534+
/// described by descriptor.
535+
///
536+
/// Returns an Err if the input at input_idx does not exist, has a non-empty script_sig,
537+
/// is not spending the outpoint described by `descriptor.outpoint`, or does not have a
538+
/// sequence set to `descriptor.to_self_delay`.
539+
///
540+
/// (C-not exported) as bindings don't support modifying a Transaction parameter
541+
pub fn sign_dynamic_p2wsh_input<C: Signing>(&self, spend_tx: &mut Transaction, input_idx: usize, descriptor: &DynamicP2WSHOutputDescriptor, secp_ctx: &Secp256k1<C>) -> Result<(), ()> {
542+
// TODO: We really should be taking the SigHashCache as a parameter here instead of
543+
// spend_tx, but ideally the SigHashCache would expose the transaction's inputs read-only
544+
// so that we can check them. This requires upstream rust-bitcoin changes (as well as
545+
// bindings updates to support SigHashCache objects).
546+
if spend_tx.input.len() <= input_idx { return Err(()); }
547+
if !spend_tx.input[input_idx].script_sig.is_empty() { return Err(()); }
548+
if spend_tx.input[input_idx].previous_output != descriptor.outpoint.into_bitcoin_outpoint() { return Err(()); }
549+
if spend_tx.input[input_idx].sequence != descriptor.to_self_delay as u32 { return Err(()); }
550+
551+
let delayed_payment_key = chan_utils::derive_private_key(&secp_ctx, &descriptor.per_commitment_point, &self.delayed_payment_base_key)
552+
.expect("We constructed the payment_base_key, so we can only fail here if the RNG is busted.");
553+
let delayed_payment_pubkey = PublicKey::from_secret_key(&secp_ctx, &delayed_payment_key);
554+
let witness_script = chan_utils::get_revokeable_redeemscript(&descriptor.revocation_pubkey, descriptor.to_self_delay, &delayed_payment_pubkey);
555+
let sighash = hash_to_message!(&bip143::SigHashCache::new(&*spend_tx).signature_hash(input_idx, &witness_script, descriptor.output.value, SigHashType::All)[..]);
556+
let local_delayedsig = secp_ctx.sign(&sighash, &delayed_payment_key);
557+
spend_tx.input[input_idx].witness.push(local_delayedsig.serialize_der().to_vec());
558+
spend_tx.input[input_idx].witness[0].push(SigHashType::All as u8);
559+
spend_tx.input[input_idx].witness.push(vec!()); //MINIMALIF
560+
spend_tx.input[input_idx].witness.push(witness_script.clone().into_bytes());
561+
Ok(())
562+
}
503563
}
504564

505565
impl ChannelKeys for InMemoryChannelKeys {
@@ -827,6 +887,144 @@ impl KeysManager {
827887
(params_1, params_2),
828888
)
829889
}
890+
891+
/// Creates a Transaction which spends the given descriptors to the given outputs, plus an
892+
/// output to the given change destination (if sufficient change value remains). The
893+
/// transaction will have a feerate, at least, of the given value.
894+
///
895+
/// Returns `Err(())` if the output value is greater than the input value minus required fee or
896+
/// if a descriptor was duplicated.
897+
///
898+
/// May panic if the `SpendableOutputDescriptor`s were not generated by Channels which used
899+
/// this KeysManager or one of the `InMemoryChannelKeys` created by this KeysManager.
900+
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, ()> {
901+
let mut input = Vec::new();
902+
let mut input_value = 0;
903+
let mut witness_weight = 0;
904+
let mut output_set = HashSet::with_capacity(descriptors.len());
905+
for outp in descriptors {
906+
match outp {
907+
SpendableOutputDescriptor::StaticOutputCounterpartyPayment(descriptor) => {
908+
input.push(TxIn {
909+
previous_output: descriptor.outpoint.into_bitcoin_outpoint(),
910+
script_sig: Script::new(),
911+
sequence: 0,
912+
witness: Vec::new(),
913+
});
914+
witness_weight += StaticCounterpartyPaymentOutputDescriptor::MAX_WITNESS_LENGTH;
915+
input_value += descriptor.output.value;
916+
if !output_set.insert(descriptor.outpoint) { return Err(()); }
917+
},
918+
SpendableOutputDescriptor::DynamicOutputP2WSH(descriptor) => {
919+
input.push(TxIn {
920+
previous_output: descriptor.outpoint.into_bitcoin_outpoint(),
921+
script_sig: Script::new(),
922+
sequence: descriptor.to_self_delay as u32,
923+
witness: Vec::new(),
924+
});
925+
witness_weight += DynamicP2WSHOutputDescriptor::MAX_WITNESS_LENGTH;
926+
input_value += descriptor.output.value;
927+
if !output_set.insert(descriptor.outpoint) { return Err(()); }
928+
},
929+
SpendableOutputDescriptor::StaticOutput { ref outpoint, ref output } => {
930+
input.push(TxIn {
931+
previous_output: outpoint.into_bitcoin_outpoint(),
932+
script_sig: Script::new(),
933+
sequence: 0,
934+
witness: Vec::new(),
935+
});
936+
witness_weight += 1 + 73 + 34;
937+
input_value += output.value;
938+
if !output_set.insert(*outpoint) { return Err(()); }
939+
}
940+
}
941+
if input_value > MAX_VALUE_MSAT / 1000 { return Err(()); }
942+
}
943+
let mut output_value = 0;
944+
for output in outputs.iter() {
945+
output_value += output.value;
946+
if output_value >= input_value { return Err(()); }
947+
}
948+
let mut spend_tx = Transaction {
949+
version: 2,
950+
lock_time: 0,
951+
input,
952+
output: outputs,
953+
};
954+
955+
let mut change_output = TxOut {
956+
script_pubkey: change_destination_script,
957+
value: 0,
958+
};
959+
let change_len = change_output.consensus_encode(&mut std::io::sink()).unwrap();
960+
let mut weight_with_change: i64 = spend_tx.get_weight() as i64 + 2 + witness_weight as i64 + change_len as i64 * 4;
961+
// Include any extra bytes required to push an extra output.
962+
weight_with_change += (VarInt(spend_tx.output.len() as u64 + 1).len() - VarInt(spend_tx.output.len() as u64).len()) as i64 * 4;
963+
// When calculating weight, add two for the flag bytes
964+
let change_value: i64 = (input_value - output_value) as i64 - weight_with_change * feerate_sat_per_1000_weight as i64 / 1000;
965+
if change_value > 0 {
966+
change_output.value = change_value as u64;
967+
spend_tx.output.push(change_output);
968+
} else if (input_value - output_value) as i64 - (spend_tx.get_weight() as i64 + 2 + witness_weight as i64) * feerate_sat_per_1000_weight as i64 / 1000 < 0 {
969+
return Err(());
970+
}
971+
972+
spend_tx.output[0].value -= (spend_tx.get_weight() + 2 + witness_weight + 3) as u64 * feerate_sat_per_1000_weight as u64 / 1000;
973+
974+
let mut keys_cache: Option<(InMemoryChannelKeys, (u64, u64))> = None;
975+
let mut input_idx = 0;
976+
for outp in descriptors {
977+
match outp {
978+
SpendableOutputDescriptor::StaticOutputCounterpartyPayment(descriptor) => {
979+
if keys_cache.is_none() || keys_cache.as_ref().unwrap().1 != descriptor.key_derivation_params {
980+
keys_cache = Some((
981+
self.derive_channel_keys(descriptor.channel_value_satoshis, descriptor.key_derivation_params.0, descriptor.key_derivation_params.1),
982+
descriptor.key_derivation_params));
983+
}
984+
keys_cache.as_ref().unwrap().0.sign_counterparty_payment_input(&mut spend_tx, input_idx, &descriptor, &secp_ctx).unwrap();
985+
},
986+
SpendableOutputDescriptor::DynamicOutputP2WSH(descriptor) => {
987+
if keys_cache.is_none() || keys_cache.as_ref().unwrap().1 != descriptor.key_derivation_params {
988+
keys_cache = Some((
989+
self.derive_channel_keys(descriptor.channel_value_satoshis, descriptor.key_derivation_params.0, descriptor.key_derivation_params.1),
990+
descriptor.key_derivation_params));
991+
}
992+
keys_cache.as_ref().unwrap().0.sign_dynamic_p2wsh_input(&mut spend_tx, input_idx, &descriptor, &secp_ctx).unwrap();
993+
},
994+
SpendableOutputDescriptor::StaticOutput { ref output, .. } => {
995+
let derivation_idx = if output.script_pubkey == self.destination_script {
996+
1
997+
} else {
998+
2
999+
};
1000+
let secret = {
1001+
// Note that when we aren't serializing the key, network doesn't matter
1002+
match ExtendedPrivKey::new_master(Network::Testnet, &self.seed) {
1003+
Ok(master_key) => {
1004+
match master_key.ckd_priv(&secp_ctx, ChildNumber::from_hardened_idx(derivation_idx).expect("key space exhausted")) {
1005+
Ok(key) => key,
1006+
Err(_) => panic!("Your RNG is busted"),
1007+
}
1008+
}
1009+
Err(_) => panic!("Your rng is busted"),
1010+
}
1011+
};
1012+
let pubkey = ExtendedPubKey::from_private(&secp_ctx, &secret).public_key;
1013+
if derivation_idx == 2 {
1014+
assert_eq!(pubkey.key, self.shutdown_pubkey);
1015+
}
1016+
let witness_script = bitcoin::Address::p2pkh(&pubkey, Network::Testnet).script_pubkey();
1017+
let sighash = hash_to_message!(&bip143::SigHashCache::new(&spend_tx).signature_hash(input_idx, &witness_script, output.value, SigHashType::All)[..]);
1018+
let sig = secp_ctx.sign(&sighash, &secret.private_key.key);
1019+
spend_tx.input[input_idx].witness.push(sig.serialize_der().to_vec());
1020+
spend_tx.input[input_idx].witness[0].push(SigHashType::All as u8);
1021+
spend_tx.input[input_idx].witness.push(pubkey.key.serialize().to_vec());
1022+
},
1023+
}
1024+
input_idx += 1;
1025+
}
1026+
Ok(spend_tx)
1027+
}
8301028
}
8311029

8321030
impl KeysInterface for KeysManager {

0 commit comments

Comments
 (0)