Skip to content

Commit

Permalink
Implement Script for Witness and Add Tweak in Spendable Output to Par…
Browse files Browse the repository at this point in the history
…tially Signed Bitcoin Transaction Input converter.

Adding Witness Script and key tweaks makes PSBT a single data source needed for a Signer to produce valid signatures.
A Signer is not required to be able to generate L2 keys, e.g delayed payment basepoint.
  • Loading branch information
yellowred committed Jan 18, 2024
1 parent 5592378 commit 6970176
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 29 deletions.
1 change: 1 addition & 0 deletions lightning/src/chain/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4255,6 +4255,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
revocation_pubkey: broadcasted_holder_revokable_script.2,
channel_keys_id: self.channel_keys_id,
channel_value_satoshis: self.channel_value_satoshis,
channel_transaction_parameters: Some(self.onchain_tx_handler.channel_transaction_parameters.clone()),
}));
}
}
Expand Down
43 changes: 28 additions & 15 deletions lightning/src/ln/channel_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,34 @@ macro_rules! doc_comment {
};
}
macro_rules! basepoint_impl {
($BasepointT:ty) => {
impl $BasepointT {
/// Get inner Public Key
pub fn to_public_key(&self) -> PublicKey {
self.0
}
}

impl From<PublicKey> for $BasepointT {
fn from(value: PublicKey) -> Self {
Self(value)
}
}

}
($BasepointT:ty) => {
impl $BasepointT {
/// Get inner Public Key
pub fn to_public_key(&self) -> PublicKey {
self.0
}

/// Derives a per-commitment-transaction (eg an htlc key or delayed_payment key) private key addition tweak
/// from a basepoint and a per_commitment_point:
/// `privkey = basepoint_secret + SHA256(per_commitment_point || basepoint)`
pub fn derive_add_tweak(
&self,
per_commitment_point: &PublicKey,
) -> [u8; 32] {
let mut sha = Sha256::engine();
sha.input(&per_commitment_point.serialize());
sha.input(&self.to_public_key().serialize());
Sha256::from_engine(sha).to_byte_array()
}
}

impl From<PublicKey> for $BasepointT {
fn from(value: PublicKey) -> Self {
Self(value)
}
}

}
}
macro_rules! key_impl {
($BasepointT:ty, $KeyName:expr) => {
Expand Down
73 changes: 59 additions & 14 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 @@ -37,13 +37,13 @@ use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
use bitcoin::secp256k1::schnorr;
use bitcoin::{secp256k1, Sequence, Witness, Txid};

use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI;
use crate::util::transaction_utils;
use crate::crypto::utils::{hkdf_extract_expand_twice, sign, sign_with_aux_rand};
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 @@ -103,7 +103,13 @@ pub struct DelayedPaymentOutputDescriptor {
pub channel_keys_id: [u8; 32],
/// The value of the channel which this output originated from, possibly indirectly.
pub channel_value_satoshis: u64,
/// The channel public keys and other parameters needed to generate a spending transaction or to provide to a re-derived signer through
/// [`ChannelSigner::provide_channel_parameters`].
///
/// Added as optional, but always `Some` if the descriptor was produced in v0.0.120 or later.
pub channel_transaction_parameters: Option<ChannelTransactionParameters>,
}

impl DelayedPaymentOutputDescriptor {
/// The maximum length a well-formed witness spending one of these should have.
/// Note: If you have the grind_signatures feature enabled, this will be at least 1 byte
Expand All @@ -121,6 +127,7 @@ impl_writeable_tlv_based!(DelayedPaymentOutputDescriptor, {
(8, revocation_pubkey, required),
(10, channel_keys_id, required),
(12, channel_value_satoshis, required),
(14, channel_transaction_parameters, option),
});

pub(crate) const P2WPKH_WITNESS_WEIGHT: u64 = 1 /* num stack items */ +
Expand Down Expand Up @@ -149,21 +156,24 @@ 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.
///
/// Note that this will only return `Some` for [`StaticPaymentOutputDescriptor`]s that
/// originated from an anchor outputs channel, as they take the form of a P2WSH script.
pub fn witness_script(&self) -> Option<ScriptBuf> {
self.channel_transaction_parameters.as_ref()
.and_then(|channel_params|
if channel_params.channel_type_features.supports_anchors_zero_fee_htlc_tx() {
let payment_point = channel_params.holder_pubkeys.payment_point;
.and_then(|channel_params| {
// Use simplified derivation, assuming `option_static_remotekey` is negotiated.
// `remote_payment_basepoint` is used as Payment Key.
let payment_point = channel_params.holder_pubkeys.payment_point;
if channel_params.channel_type_features.supports_anchors_zero_fee_htlc_tx() {
Some(chan_utils::get_to_countersignatory_with_anchors_redeemscript(&payment_point))
} else {
None
}
)
} else {
Some(ScriptBuf::new_p2pkh(&bitcoin::PublicKey::new(payment_point).pubkey_hash()))
}
})
}

/// The maximum length a well-formed witness spending one of these should have.
Expand Down Expand Up @@ -304,7 +314,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>) -> bitcoin::psbt::Input {
match self {
SpendableOutputDescriptor::StaticOutput { output, .. } => {
// Is a standard P2WPKH, no need for witness script
Expand All @@ -314,16 +324,49 @@ impl SpendableOutputDescriptor {
}
},
SpendableOutputDescriptor::DelayedPaymentOutput(descriptor) => {
// TODO we could add the witness script as well
let delayed_payment_basepoint = descriptor.channel_transaction_parameters.as_ref().map(|params| DelayedPaymentBasepoint::from(
params.holder_pubkeys.delayed_payment_basepoint,
));

let (witness_script, add_tweak) = if let Some(basepoint) = delayed_payment_basepoint.as_ref() {
let payment_key = DelayedPaymentKey::from_basepoint(
secp_ctx,
basepoint,
&descriptor.per_commitment_point,
);
// Required to derive signing key: privkey = basepoint_secret + SHA256(per_commitment_point || basepoint)
let add_tweak = basepoint.derive_add_tweak(&descriptor.per_commitment_point);
(Some(get_revokeable_redeemscript(
&descriptor.revocation_pubkey,
descriptor.to_self_delay,
&payment_key,
)), Some(add_tweak))
} else {
(None, None)
};

bitcoin::psbt::Input {
witness_utxo: Some(descriptor.output.clone()),
witness_script,
proprietary: add_tweak.map(|add_tweak| {vec![(
raw::ProprietaryKey {
prefix: "LDK_spendable_output".as_bytes().to_vec(),
subtype: 0,
key: "add_tweak".as_bytes().to_vec(),
},
add_tweak.to_vec(),
)].into_iter().collect()}).unwrap_or_default(),
..Default::default()
}
},
SpendableOutputDescriptor::StaticPaymentOutput(descriptor) => {
// TODO we could add the witness script as well
let witness_script = descriptor.witness_script();

// With simplified derivation, the private payment key is equal to private payment basepoint,
// so add tweak is not needed.
bitcoin::psbt::Input {
witness_utxo: Some(descriptor.output.clone()),
witness_script,
..Default::default()
}
},
Expand All @@ -347,6 +390,7 @@ impl SpendableOutputDescriptor {
///
/// 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), ()> {
let secp_ctx = Secp256k1::new();
let mut input = Vec::with_capacity(descriptors.len());
let mut input_value = 0;
let mut witness_weight = 0;
Expand Down Expand Up @@ -413,7 +457,7 @@ 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)).collect::<Vec<_>>();
let psbt = PartiallySignedTransaction {
inputs: psbt_inputs,
outputs: vec![Default::default(); tx.output.len()],
Expand Down Expand Up @@ -1615,6 +1659,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, ()> {
// TODO: provide channel keys to construct witness script
let (mut psbt, expected_max_weight) = SpendableOutputDescriptor::create_spendable_outputs_psbt(descriptors, outputs, change_destination_script, feerate_sat_per_1000_weight, locktime)?;
psbt = self.sign_spendable_outputs_psbt(descriptors, psbt, secp_ctx)?;

Expand Down

0 comments on commit 6970176

Please sign in to comment.