//! 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)*) => {
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 {
Self(value)
}
}
-
- }
+ };
}
macro_rules! key_impl {
($BasepointT:ty, $KeyName:expr) => {
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
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
/// 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.")
}
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.
///
///
/// [`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 = {
}
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()[..]
+ );
}
}
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;
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)]
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
(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 */ +
/// 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.
///
///
/// 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()
},
}
}
/// 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), ()> {
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()],
) -> Result<Transaction, ()> {
let (mut psbt, expected_max_weight) =
SpendableOutputDescriptor::create_spendable_outputs_psbt(
+ secp_ctx,
descriptors,
outputs,
change_destination_script,