Implement Script for Witness and Add Tweak in PSBT.
authorolegkubrakov <oleg@lightspark.com>
Tue, 19 Dec 2023 00:54:59 +0000 (16:54 -0800)
committerolegkubrakov <oleg@lightspark.com>
Thu, 18 Apr 2024 23:57:54 +0000 (16:57 -0700)
Adding Witness Script and key tweaks makes
a Partially Signed Bitcoin Transaction the 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
lightning/src/ln/channel_keys.rs
lightning/src/sign/mod.rs

index c2d3a43b1f4bd40d65ea041478313a878d49d7ca..bd5bd1fe9c6ea574ec18e2ce6e2e5c89619709bc 100644 (file)
@@ -4330,6 +4330,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()),
                                        }));
                                }
                        }
index b577dc60008583537c4d9c1cebc8b2f6e5f48e77..423d410740720dc2bb5b4e12aa39cf5d1bf4c2ca 100644 (file)
 //! Keys used to generate commitment transactions.
 //! See: <https://github.com/lightning/bolts/blob/master/03-transactions.md#keys>
 
-use bitcoin::hashes::Hash;
-use bitcoin::hashes::HashEngine;
-use bitcoin::secp256k1::Scalar;
-use bitcoin::secp256k1::SecretKey;
-use bitcoin::secp256k1::Secp256k1;
-use bitcoin::secp256k1;
+use crate::io;
 use crate::ln::msgs::DecodeError;
 use crate::util::ser::Readable;
-use crate::io;
-use crate::util::ser::Writer;
 use crate::util::ser::Writeable;
-use bitcoin::secp256k1::PublicKey;
+use crate::util::ser::Writer;
 use bitcoin::hashes::sha256::Hash as Sha256;
+use bitcoin::hashes::Hash;
+use bitcoin::hashes::HashEngine;
+use bitcoin::secp256k1;
+use bitcoin::secp256k1::PublicKey;
+use bitcoin::secp256k1::Scalar;
+use bitcoin::secp256k1::Secp256k1;
+use bitcoin::secp256k1::SecretKey;
 
 macro_rules! doc_comment {
        ($x:expr, $($tt:tt)*) => {
@@ -37,6 +37,20 @@ 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<PublicKey> for $BasepointT {
@@ -44,8 +58,7 @@ macro_rules! basepoint_impl {
                                Self(value)
                        }
                }
-
-       }
+       };
 }
 macro_rules! key_impl {
        ($BasepointT:ty, $KeyName:expr) => {
@@ -87,11 +100,9 @@ macro_rules! key_read_write {
                                Ok(Self(key))
                        }
                }
-       }
+       };
 }
 
-
-
 /// Base key used in conjunction with a `per_commitment_point` to generate a [`DelayedPaymentKey`].
 ///
 /// The delayed payment key is used to pay the commitment state broadcaster their
@@ -102,7 +113,6 @@ pub struct DelayedPaymentBasepoint(pub PublicKey);
 basepoint_impl!(DelayedPaymentBasepoint);
 key_read_write!(DelayedPaymentBasepoint);
 
-
 /// A derived key built from a [`DelayedPaymentBasepoint`] and `per_commitment_point`.
 ///
 /// The delayed payment key is used to pay the commitment state broadcaster their
@@ -150,14 +160,26 @@ key_read_write!(HtlcKey);
 /// Derives a per-commitment-transaction public key (eg an htlc key or a delayed_payment key)
 /// from the base point and the per_commitment_key. This is the public equivalent of
 /// derive_private_key - using only public keys to derive a public key instead of private keys.
-fn derive_public_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, per_commitment_point: &PublicKey, base_point: &PublicKey) -> PublicKey {
+fn derive_public_key<T: secp256k1::Signing>(
+       secp_ctx: &Secp256k1<T>, per_commitment_point: &PublicKey, base_point: &PublicKey,
+) -> PublicKey {
        let mut sha = Sha256::engine();
        sha.input(&per_commitment_point.serialize());
        sha.input(&base_point.serialize());
        let res = Sha256::from_engine(sha).to_byte_array();
 
-       let hashkey = PublicKey::from_secret_key(&secp_ctx,
-               &SecretKey::from_slice(&res).expect("Hashes should always be valid keys unless SHA-256 is broken"));
+       add_public_key_tweak(secp_ctx, base_point, &res)
+}
+
+/// Adds a tweak to a public key to derive a new public key.
+pub fn add_public_key_tweak<T: secp256k1::Signing>(
+       secp_ctx: &Secp256k1<T>, base_point: &PublicKey, tweak: &[u8; 32],
+) -> PublicKey {
+       let hashkey = PublicKey::from_secret_key(
+               &secp_ctx,
+               &SecretKey::from_slice(tweak)
+                       .expect("Hashes should always be valid keys unless SHA-256 is broken"),
+       );
        base_point.combine(&hashkey)
                .expect("Addition only fails if the tweak is the inverse of the key. This is not possible when the tweak contains the hash of the key.")
 }
@@ -169,7 +191,6 @@ pub struct RevocationBasepoint(pub PublicKey);
 basepoint_impl!(RevocationBasepoint);
 key_read_write!(RevocationBasepoint);
 
-
 /// The revocation key is used to allow a channel party to revoke their state - giving their
 /// counterparty the required material to claim all of their funds if they broadcast that state.
 ///
@@ -192,8 +213,7 @@ impl RevocationKey {
        ///
        /// [`chan_utils::derive_private_revocation_key`]: crate::ln::chan_utils::derive_private_revocation_key
        pub fn from_basepoint<T: secp256k1::Verification>(
-               secp_ctx: &Secp256k1<T>,
-               countersignatory_basepoint: &RevocationBasepoint,
+               secp_ctx: &Secp256k1<T>, countersignatory_basepoint: &RevocationBasepoint,
                per_commitment_point: &PublicKey,
        ) -> Self {
                let rev_append_commit_hash_key = {
@@ -227,28 +247,56 @@ impl RevocationKey {
 }
 key_read_write!(RevocationKey);
 
-
 #[cfg(test)]
 mod test {
-       use bitcoin::secp256k1::{Secp256k1, SecretKey, PublicKey};
-       use bitcoin::hashes::hex::FromHex;
        use super::derive_public_key;
+       use bitcoin::hashes::hex::FromHex;
+       use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
 
        #[test]
        fn test_key_derivation() {
                // Test vectors from BOLT 3 Appendix E:
                let secp_ctx = Secp256k1::new();
 
-               let base_secret = SecretKey::from_slice(&<Vec<u8>>::from_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap()[..]).unwrap();
-               let per_commitment_secret = SecretKey::from_slice(&<Vec<u8>>::from_hex("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100").unwrap()[..]).unwrap();
+               let base_secret = SecretKey::from_slice(
+                       &<Vec<u8>>::from_hex(
+                               "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+                       )
+                       .unwrap()[..],
+               )
+               .unwrap();
+               let per_commitment_secret = SecretKey::from_slice(
+                       &<Vec<u8>>::from_hex(
+                               "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100",
+                       )
+                       .unwrap()[..],
+               )
+               .unwrap();
 
                let base_point = PublicKey::from_secret_key(&secp_ctx, &base_secret);
-               assert_eq!(base_point.serialize()[..], <Vec<u8>>::from_hex("036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2").unwrap()[..]);
+               assert_eq!(
+                       base_point.serialize()[..],
+                       <Vec<u8>>::from_hex(
+                               "036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2"
+                       )
+                       .unwrap()[..]
+               );
 
                let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret);
-               assert_eq!(per_commitment_point.serialize()[..], <Vec<u8>>::from_hex("025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486").unwrap()[..]);
-
-               assert_eq!(derive_public_key(&secp_ctx, &per_commitment_point, &base_point).serialize()[..],
-                       <Vec<u8>>::from_hex("0235f2dbfaa89b57ec7b055afe29849ef7ddfeb1cefdb9ebdc43f5494984db29e5").unwrap()[..]);
+               assert_eq!(
+                       per_commitment_point.serialize()[..],
+                       <Vec<u8>>::from_hex(
+                               "025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486"
+                       )
+                       .unwrap()[..]
+               );
+
+               assert_eq!(
+                       derive_public_key(&secp_ctx, &per_commitment_point, &base_point).serialize()[..],
+                       <Vec<u8>>::from_hex(
+                               "0235f2dbfaa89b57ec7b055afe29849ef7ddfeb1cefdb9ebdc43f5494984db29e5"
+                       )
+                       .unwrap()[..]
+               );
        }
 }
index 9b5efee4b9695cc10a3df1cef99e4d4328dc05b1..43acc67af04b3699ef406a8dc64113908463b357 100644 (file)
@@ -40,13 +40,14 @@ use bitcoin::{secp256k1, Sequence, Txid, Witness};
 use crate::chain::transaction::OutPoint;
 use crate::crypto::utils::{hkdf_extract_expand_twice, sign, sign_with_aux_rand};
 use crate::ln::chan_utils::{
-       make_funding_redeemscript, ChannelPublicKeys, ChannelTransactionParameters, ClosingTransaction,
-       CommitmentTransaction, HTLCOutputInCommitment, HolderCommitmentTransaction,
+       get_revokeable_redeemscript, make_funding_redeemscript, ChannelPublicKeys,
+       ChannelTransactionParameters, ClosingTransaction, CommitmentTransaction,
+       HTLCOutputInCommitment, HolderCommitmentTransaction,
 };
 use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI;
 use crate::ln::channel_keys::{
-       DelayedPaymentBasepoint, DelayedPaymentKey, HtlcBasepoint, HtlcKey, RevocationBasepoint,
-       RevocationKey,
+       add_public_key_tweak, DelayedPaymentBasepoint, DelayedPaymentKey, HtlcBasepoint, HtlcKey,
+       RevocationBasepoint, RevocationKey,
 };
 #[cfg(taproot)]
 use crate::ln::msgs::PartialSignatureWithNonce;
@@ -68,6 +69,7 @@ use crate::sign::ecdsa::{EcdsaChannelSigner, WriteableEcdsaChannelSigner};
 use crate::sign::taproot::TaprootChannelSigner;
 use crate::util::atomic_counter::AtomicCounter;
 use crate::util::invoice::construct_invoice_preimage;
+use core::convert::TryInto;
 use core::ops::Deref;
 use core::sync::atomic::{AtomicUsize, Ordering};
 #[cfg(taproot)]
@@ -108,7 +110,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.123 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
@@ -127,6 +135,7 @@ impl_writeable_tlv_based!(DelayedPaymentOutputDescriptor, {
        (8, revocation_pubkey, required),
        (10, channel_keys_id, required),
        (12, channel_value_satoshis, required),
+       (13, channel_transaction_parameters, option),
 });
 
 pub(crate) const P2WPKH_WITNESS_WEIGHT: u64 = 1 /* num stack items */ +
@@ -155,6 +164,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.
        ///
@@ -306,25 +316,104 @@ 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 {
+       ///
+       /// The proprietary field is used to store add tweak for the signing key of this transaction.
+       /// See the [`DelayedPaymentBasepoint::derive_add_tweak`] docs for more info on add tweak and how to use it.
+       ///
+       /// To get the proprietary field use:
+       /// ```
+       /// use bitcoin::psbt::{PartiallySignedTransaction};
+       /// use bitcoin::hashes::hex::FromHex;
+       ///
+       /// # let s = "70736274ff0100520200000001dee978529ab3e61a2987bea5183713d0e6d5ceb5ac81100fdb54a1a2\
+       ///     #                69cef505000000000090000000011f26000000000000160014abb3ab63280d4ccc5c11d6b50fd427a8\
+       ///     #                e19d6470000000000001012b10270000000000002200200afe4736760d814a2651bae63b572d935d9a\
+       /// #            b74a1a16c01774e341a32afa763601054d63210394a27a700617f5b7aee72bd4f8076b5770a582b7fb\
+       ///     #                d1d4ee2ea3802cd3cfbe2067029000b27521034629b1c8fdebfaeb58a74cd181f485e2c462e594cb30\
+       ///     #                34dee655875f69f6c7c968ac20fc144c444b5f7370656e6461626c655f6f7574707574006164645f74\
+       ///     #                7765616b20a86534f38ad61dc580ef41c3886204adf0911b81619c1ad7a2f5b5de39a2ba600000";
+       /// # let psbt = PartiallySignedTransaction::deserialize(<Vec<u8> as FromHex>::from_hex(s).unwrap().as_slice()).unwrap();
+       /// let key = bitcoin::psbt::raw::ProprietaryKey {
+       ///     prefix: "LDK_spendable_output".as_bytes().to_vec(),
+       ///     subtype: 0,
+       ///     key: "add_tweak".as_bytes().to_vec(),
+       /// };
+       /// let value = psbt
+       ///     .inputs
+       ///     .first()
+       ///     .expect("Unable to get add tweak as there are no inputs")
+       ///     .proprietary
+       ///     .get(&key)
+       ///     .map(|x| x.to_owned());
+       /// ```
+       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
                                bitcoin::psbt::Input { witness_utxo: Some(output.clone()), ..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() {
+                                               // Required to derive signing key: privkey = basepoint_secret + SHA256(per_commitment_point || basepoint)
+                                               let add_tweak = basepoint.derive_add_tweak(&per_commitment_point);
+                                               let payment_key = DelayedPaymentKey(add_public_key_tweak(
+                                                       secp_ctx,
+                                                       &basepoint.to_public_key(),
+                                                       &add_tweak,
+                                               ));
+
+                                               (
+                                                       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| {
+                                                       [(
+                                                               bitcoin::psbt::raw::ProprietaryKey {
+                                                                       // A non standard namespace for spendable outputs, used to store the tweak needed
+                                                                       // to derive the private key
+                                                                       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
-                               bitcoin::psbt::Input {
-                                       witness_utxo: Some(descriptor.output.clone()),
-                                       ..Default::default()
-                               }
+                       SpendableOutputDescriptor::StaticPaymentOutput(descriptor) => bitcoin::psbt::Input {
+                               witness_utxo: Some(descriptor.output.clone()),
+                               witness_script: descriptor.witness_script(),
+                               ..Default::default()
                        },
                }
        }
@@ -345,8 +434,8 @@ 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>,
+       pub fn create_spendable_outputs_psbt<T: secp256k1::Signing>(
+               secp_ctx: &Secp256k1<T>, descriptors: &[&SpendableOutputDescriptor], outputs: Vec<TxOut>,
                change_destination_script: ScriptBuf, feerate_sat_per_1000_weight: u32,
                locktime: Option<LockTime>,
        ) -> Result<(PartiallySignedTransaction, u64), ()> {
@@ -438,7 +527,8 @@ impl SpendableOutputDescriptor {
                        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()],
@@ -2112,6 +2202,7 @@ impl OutputSpender for KeysManager {
        ) -> Result<Transaction, ()> {
                let (mut psbt, expected_max_weight) =
                        SpendableOutputDescriptor::create_spendable_outputs_psbt(
+                               secp_ctx,
                                descriptors,
                                outputs,
                                change_destination_script,