Skip to content

Commit

Permalink
Implement Script for Witness and Add Tweak in Partially Signed Bitcoi…
Browse files Browse the repository at this point in the history
…n Transaction to make it a single data source needed for a Signer to produce valid signatures.
  • Loading branch information
yellowred committed Dec 4, 2023
1 parent c2bbfff commit 1e5c85c
Showing 1 changed file with 50 additions and 8 deletions.
58 changes: 50 additions & 8 deletions lightning/src/sign/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use bitcoin::blockdata::script::{Script, ScriptBuf, Builder};
use bitcoin::blockdata::opcodes;
use bitcoin::ecdsa::Signature as EcdsaSignature;
use bitcoin::network::constants::Network;
use bitcoin::psbt::PartiallySignedTransaction;
use bitcoin::psbt::{PartiallySignedTransaction, raw};
use bitcoin::bip32::{ExtendedPrivKey, ExtendedPubKey, ChildNumber};
use bitcoin::sighash;
use bitcoin::sighash::EcdsaSighashType;
Expand All @@ -43,7 +43,7 @@ use crate::util::ser::{Writeable, Writer, Readable, ReadableArgs};
use crate::chain::transaction::OutPoint;
use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI;
use crate::ln::{chan_utils, PaymentPreimage};
use crate::ln::chan_utils::{HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys, HolderCommitmentTransaction, ChannelTransactionParameters, CommitmentTransaction, ClosingTransaction};
use crate::ln::chan_utils::{HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys, HolderCommitmentTransaction, ChannelTransactionParameters, CommitmentTransaction, ClosingTransaction, get_revokeable_redeemscript};
use crate::ln::channel_keys::{DelayedPaymentBasepoint, DelayedPaymentKey, HtlcKey, HtlcBasepoint, RevocationKey, RevocationBasepoint};
use crate::ln::msgs::{UnsignedChannelAnnouncement, UnsignedGossipMessage};
#[cfg(taproot)]
Expand Down Expand Up @@ -149,6 +149,7 @@ pub struct StaticPaymentOutputDescriptor {
/// Added as optional, but always `Some` if the descriptor was produced in v0.0.117 or later.
pub channel_transaction_parameters: Option<ChannelTransactionParameters>,
}

impl StaticPaymentOutputDescriptor {
/// Returns the `witness_script` of the spendable output.
///
Expand Down Expand Up @@ -304,7 +305,7 @@ impl SpendableOutputDescriptor {
///
/// This is not exported to bindings users as there is no standard serialization for an input.
/// See [`Self::create_spendable_outputs_psbt`] instead.
pub fn to_psbt_input(&self) -> bitcoin::psbt::Input {
pub fn to_psbt_input<T: secp256k1::Signing>(&self, secp_ctx: &Secp256k1<T>, delayed_payment_basepoint: Option<&DelayedPaymentBasepoint>) -> bitcoin::psbt::Input {
match self {
SpendableOutputDescriptor::StaticOutput { output, .. } => {
// Is a standard P2WPKH, no need for witness script
Expand All @@ -314,9 +315,21 @@ impl SpendableOutputDescriptor {
}
},
SpendableOutputDescriptor::DelayedPaymentOutput(descriptor) => {
// TODO we could add the witness script as well
let witness_script = delayed_payment_basepoint.map(|basepoint| {
let payment_key = DelayedPaymentKey::from_basepoint(
secp_ctx,
basepoint,
&descriptor.per_commitment_point,
);
get_revokeable_redeemscript(
&descriptor.revocation_pubkey,
descriptor.to_self_delay,
&payment_key,
)
});
bitcoin::psbt::Input {
witness_utxo: Some(descriptor.output.clone()),
witness_script: witness_script,
..Default::default()
}
},
Expand All @@ -330,6 +343,7 @@ impl SpendableOutputDescriptor {
}
}


/// Creates an unsigned [`PartiallySignedTransaction`] which spends the given descriptors to
/// the given outputs, plus an output to the given change destination (if sufficient
/// change value remains). The PSBT will have a feerate, at least, of the given value.
Expand All @@ -346,11 +360,14 @@ impl SpendableOutputDescriptor {
/// does not match the one we can spend.
///
/// We do not enforce that outputs meet the dust limit or that any output scripts are standard.
pub fn create_spendable_outputs_psbt(descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>, change_destination_script: ScriptBuf, feerate_sat_per_1000_weight: u32, locktime: Option<LockTime>) -> Result<(PartiallySignedTransaction, u64), ()> {
pub fn create_spendable_outputs_psbt(descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>, change_destination_script: ScriptBuf, feerate_sat_per_1000_weight: u32, locktime: Option<LockTime>, delayed_payment_basepoint: Option<&DelayedPaymentBasepoint>) -> Result<(PartiallySignedTransaction, u64), ()> {
let secp_ctx = Secp256k1::new();
let mut input = Vec::with_capacity(descriptors.len());
let mut input_value = 0;
let mut witness_weight = 0;
let mut output_set = HashSet::with_capacity(descriptors.len());
// Required to derive signing key: privkey = basepoint_secret + SHA256(per_commitment_point || basepoint)
let mut add_tweak: Option<Vec<u8>> = None;
for outp in descriptors {
match outp {
SpendableOutputDescriptor::StaticPaymentOutput(descriptor) => {
Expand Down Expand Up @@ -387,6 +404,8 @@ impl SpendableOutputDescriptor {
#[cfg(feature = "grind_signatures")]
{ witness_weight -= 1; } // Guarantees a low R signature
input_value += descriptor.output.value;

add_tweak = delayed_payment_basepoint.and_then(|basepoint| Some(derive_add_tweak(&descriptor.per_commitment_point, &basepoint)));
},
SpendableOutputDescriptor::StaticOutput { ref outpoint, ref output, .. } => {
if !output_set.insert(*outpoint) { return Err(()); }
Expand All @@ -413,20 +432,43 @@ impl SpendableOutputDescriptor {
let expected_max_weight =
transaction_utils::maybe_add_change_output(&mut tx, input_value, witness_weight, feerate_sat_per_1000_weight, change_destination_script)?;

let psbt_inputs = descriptors.iter().map(|d| d.to_psbt_input()).collect::<Vec<_>>();
let psbt_inputs = descriptors.iter().map(|d| d.to_psbt_input(&secp_ctx, delayed_payment_basepoint)).collect::<Vec<_>>();
let psbt = PartiallySignedTransaction {
inputs: psbt_inputs,
outputs: vec![Default::default(); tx.output.len()],
unsigned_tx: tx,
xpub: Default::default(),
version: 0,
proprietary: Default::default(),
proprietary: add_tweak.map(|add_tweak| {vec![(
raw::ProprietaryKey {
prefix: "spendable_output".as_bytes().to_vec(),
subtype: 0,
key: "add_tweak".as_bytes().to_vec(),
},
add_tweak,
)].into_iter().collect()}).unwrap_or_default(),
unknown: Default::default(),
};
Ok((psbt, expected_max_weight))
}
}

/// Derives a per-commitment-transaction (eg an htlc key or delayed_payment key) private key addition tweak
/// from a delayed payment basepoint and a per_commitment_point:
/// `privkey = basepoint_secret + SHA256(per_commitment_point || basepoint)`
/// TODO(oleg): refactor after migration to LDK v119
pub fn derive_add_tweak(
per_commitment_point: &PublicKey,
basepoint: &DelayedPaymentBasepoint,
) -> Vec<u8> {
let mut sha = Sha256::engine();
sha.input(&per_commitment_point.serialize());
sha.input(&basepoint.to_public_key().serialize());
let res = Sha256::from_engine(sha).to_byte_array();
res.to_vec()
}


/// The parameters required to derive a channel signer via [`SignerProvider`].
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ChannelDerivationParameters {
Expand Down Expand Up @@ -1615,7 +1657,7 @@ impl KeysManager {
/// May panic if the [`SpendableOutputDescriptor`]s were not generated by channels which used
/// this [`KeysManager`] or one of the [`InMemorySigner`] created by this [`KeysManager`].
pub fn spend_spendable_outputs<C: Signing>(&self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>, change_destination_script: ScriptBuf, feerate_sat_per_1000_weight: u32, locktime: Option<LockTime>, secp_ctx: &Secp256k1<C>) -> Result<Transaction, ()> {
let (mut psbt, expected_max_weight) = SpendableOutputDescriptor::create_spendable_outputs_psbt(descriptors, outputs, change_destination_script, feerate_sat_per_1000_weight, locktime)?;
let (mut psbt, expected_max_weight) = SpendableOutputDescriptor::create_spendable_outputs_psbt(descriptors, outputs, change_destination_script, feerate_sat_per_1000_weight, locktime, None)?;
psbt = self.sign_spendable_outputs_psbt(descriptors, psbt, secp_ctx)?;

let spend_tx = psbt.extract_tx();
Expand Down

0 comments on commit 1e5c85c

Please sign in to comment.