From e08f7de3faafffebd21b1606d0c7aa07f1fe7159 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 PSBT. 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 | 1 + lightning/src/ln/channel_keys.rs | 110 ++++++++++++++++------- lightning/src/sign/mod.rs | 125 ++++++++++++++++++++++---- 3 files changed, 188 insertions(+), 48 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index c2d3a43b1..bd5bd1fe9 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -4330,6 +4330,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 b577dc600..423d41074 100644 --- a/lightning/src/ln/channel_keys.rs +++ b/lightning/src/ln/channel_keys.rs @@ -10,19 +10,19 @@ //! Keys used to generate commitment transactions. //! See: -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 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(secp_ctx: &Secp256k1, per_commitment_point: &PublicKey, base_point: &PublicKey) -> PublicKey { +fn derive_public_key( + secp_ctx: &Secp256k1, 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( + secp_ctx: &Secp256k1, 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( - secp_ctx: &Secp256k1, - countersignatory_basepoint: &RevocationBasepoint, + secp_ctx: &Secp256k1, 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(&>::from_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap()[..]).unwrap(); - let per_commitment_secret = SecretKey::from_slice(&>::from_hex("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100").unwrap()[..]).unwrap(); + let base_secret = SecretKey::from_slice( + &>::from_hex( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + ) + .unwrap()[..], + ) + .unwrap(); + let per_commitment_secret = SecretKey::from_slice( + &>::from_hex( + "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100", + ) + .unwrap()[..], + ) + .unwrap(); let base_point = PublicKey::from_secret_key(&secp_ctx, &base_secret); - assert_eq!(base_point.serialize()[..], >::from_hex("036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2").unwrap()[..]); + assert_eq!( + base_point.serialize()[..], + >::from_hex( + "036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2" + ) + .unwrap()[..] + ); let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret); - assert_eq!(per_commitment_point.serialize()[..], >::from_hex("025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486").unwrap()[..]); - - assert_eq!(derive_public_key(&secp_ctx, &per_commitment_point, &base_point).serialize()[..], - >::from_hex("0235f2dbfaa89b57ec7b055afe29849ef7ddfeb1cefdb9ebdc43f5494984db29e5").unwrap()[..]); + assert_eq!( + per_commitment_point.serialize()[..], + >::from_hex( + "025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486" + ) + .unwrap()[..] + ); + + assert_eq!( + derive_public_key(&secp_ctx, &per_commitment_point, &base_point).serialize()[..], + >::from_hex( + "0235f2dbfaa89b57ec7b055afe29849ef7ddfeb1cefdb9ebdc43f5494984db29e5" + ) + .unwrap()[..] + ); } } diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 9b5efee4b..43acc67af 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -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, } + 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, } + 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( 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( + &self, secp_ctx: &Secp256k1, + ) -> 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, + pub fn create_spendable_outputs_psbt( + secp_ctx: &Secp256k1, descriptors: &[&SpendableOutputDescriptor], outputs: Vec, change_destination_script: ScriptBuf, feerate_sat_per_1000_weight: u32, locktime: Option, ) -> Result<(PartiallySignedTransaction, u64), ()> { @@ -438,7 +527,8 @@ impl SpendableOutputDescriptor { 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()], @@ -2112,6 +2202,7 @@ impl OutputSpender for KeysManager { ) -> Result { let (mut psbt, expected_max_weight) = SpendableOutputDescriptor::create_spendable_outputs_psbt( + secp_ctx, descriptors, outputs, change_destination_script, -- 2.39.5