From d601e761d790984529c77fd1ca7db192659397ce Mon Sep 17 00:00:00 2001 From: olegkubrakov Date: Mon, 18 Dec 2023 16:54:59 -0800 Subject: [PATCH] Implement Script for Witness and Add Tweak in Spendable Output to Partially 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. --- lightning/src/chain/channelmonitor.rs | 1 + lightning/src/ln/channel_keys.rs | 18 ++++++- lightning/src/sign/mod.rs | 74 +++++++++++++++++++++------ 3 files changed, 76 insertions(+), 17 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index c81a48b78ac..0b8bd30838f 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -4255,6 +4255,7 @@ impl ChannelMonitorImpl { 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()), })); } } diff --git a/lightning/src/ln/channel_keys.rs b/lightning/src/ln/channel_keys.rs index b577dc60008..838dd0b6f46 100644 --- a/lightning/src/ln/channel_keys.rs +++ b/lightning/src/ln/channel_keys.rs @@ -37,6 +37,23 @@ macro_rules! basepoint_impl { 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)` + /// This calculates the hash part in the tweak derivation process, which is used to ensure + /// that each key is unique and cannot be guessed by an external party. It is equivalent + /// to the `from_basepoint` method, but without the addition operation, providing just the + /// tweak from the hash of the per_commitment_point and the 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 for $BasepointT { @@ -44,7 +61,6 @@ macro_rules! basepoint_impl { Self(value) } } - } } macro_rules! key_impl { diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index c4bb6fb8020..c420b1a401f 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -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; @@ -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)] @@ -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.121 or later. + pub channel_transaction_parameters: Option, } + 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 @@ -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 */ + @@ -149,6 +156,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, } + impl StaticPaymentOutputDescriptor { /// Returns the `witness_script` of the spendable output. /// @@ -156,14 +164,16 @@ impl StaticPaymentOutputDescriptor { /// originated from an anchor outputs channel, as they take the form of a P2WSH script. pub fn witness_script(&self) -> Option { 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. @@ -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(&self, secp_ctx: &Secp256k1) -> bitcoin::psbt::Input { match self { SpendableOutputDescriptor::StaticOutput { output, .. } => { // Is a standard P2WPKH, no need for witness script @@ -313,17 +323,48 @@ impl SpendableOutputDescriptor { ..Default::default() } }, - SpendableOutputDescriptor::DelayedPaymentOutput(descriptor) => { - // TODO we could add the witness script as well + SpendableOutputDescriptor::DelayedPaymentOutput(DelayedPaymentOutputDescriptor{channel_transaction_parameters, per_commitment_point, revocation_pubkey, to_self_delay, output, ..}) => { + let delayed_payment_basepoint = channel_transaction_parameters.as_ref().map(|params| 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, + &per_commitment_point, + ); + // Required to derive signing key: privkey = basepoint_secret + SHA256(per_commitment_point || basepoint) + let add_tweak = basepoint.derive_add_tweak(&per_commitment_point); + (Some(get_revokeable_redeemscript( + &revocation_pubkey, + *to_self_delay, + &payment_key, + )), Some(add_tweak)) + } else { + (None, None) + }; + bitcoin::psbt::Input { - witness_utxo: Some(descriptor.output.clone()), + witness_utxo: Some(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() } }, @@ -347,6 +388,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, change_destination_script: ScriptBuf, feerate_sat_per_1000_weight: u32, locktime: Option) -> 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; @@ -413,7 +455,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::>(); + let psbt_inputs = descriptors.iter().map(|d| d.to_psbt_input(&secp_ctx)).collect::>(); let psbt = PartiallySignedTransaction { inputs: psbt_inputs, outputs: vec![Default::default(); tx.output.len()],