// licenses.
//! Various utilities for building scripts and deriving keys related to channels. These are
-//! largely of interest for those implementing chain::keysinterface::ChannelKeys message signing
-//! by hand.
+//! largely of interest for those implementing chain::keysinterface::Sign message signing by hand.
use bitcoin::blockdata::script::{Script,Builder};
use bitcoin::blockdata::opcodes;
use bitcoin::blockdata::transaction::{TxIn,TxOut,OutPoint,Transaction, SigHashType};
-use bitcoin::consensus::encode::{Decodable, Encodable};
-use bitcoin::consensus::encode;
use bitcoin::util::bip143;
use bitcoin::hashes::{Hash, HashEngine};
use bitcoin::hashes::ripemd160::Hash as Ripemd160;
use bitcoin::hash_types::{Txid, PubkeyHash};
-use ln::channelmanager::{PaymentHash, PaymentPreimage};
+use ln::{PaymentHash, PaymentPreimage};
use ln::msgs::DecodeError;
-use util::ser::{Readable, Writeable, Writer, WriterWriteAdaptor};
-use util::byte_utils;
+use util::ser::{Readable, Writeable, Writer};
+use util::{byte_utils, transaction_utils};
+use bitcoin::hash_types::WPubkeyHash;
use bitcoin::secp256k1::key::{SecretKey, PublicKey};
-use bitcoin::secp256k1::{Secp256k1, Signature};
+use bitcoin::secp256k1::{Secp256k1, Signature, Message};
use bitcoin::secp256k1::Error as SecpError;
use bitcoin::secp256k1;
-use std::{cmp, mem};
+use io;
+use prelude::*;
+use core::cmp;
+use ln::chan_utils;
+use util::transaction_utils::sort_outputs;
+use ln::channel::{INITIAL_COMMITMENT_NUMBER, ANCHOR_OUTPUT_VALUE_SATOSHI};
+use core::ops::Deref;
+use chain;
-const MAX_ALLOC_SIZE: usize = 64*1024;
+pub(crate) const MAX_HTLCS: u16 = 483;
pub(super) const HTLC_SUCCESS_TX_WEIGHT: u64 = 703;
pub(super) const HTLC_TIMEOUT_TX_WEIGHT: u64 = 663;
+/// Gets the weight for an HTLC-Success transaction.
+#[inline]
+pub fn htlc_success_tx_weight(opt_anchors: bool) -> u64 {
+ const HTLC_SUCCESS_ANCHOR_TX_WEIGHT: u64 = 706;
+ if opt_anchors { HTLC_SUCCESS_ANCHOR_TX_WEIGHT } else { HTLC_SUCCESS_TX_WEIGHT }
+}
+
+/// Gets the weight for an HTLC-Timeout transaction.
+#[inline]
+pub fn htlc_timeout_tx_weight(opt_anchors: bool) -> u64 {
+ const HTLC_TIMEOUT_ANCHOR_TX_WEIGHT: u64 = 666;
+ if opt_anchors { HTLC_TIMEOUT_ANCHOR_TX_WEIGHT } else { HTLC_TIMEOUT_TX_WEIGHT }
+}
+
#[derive(PartialEq)]
pub(crate) enum HTLCType {
AcceptedHTLC,
res
}
+/// Build a closing transaction
+pub fn build_closing_transaction(to_holder_value_sat: u64, to_counterparty_value_sat: u64, to_holder_script: Script, to_counterparty_script: Script, funding_outpoint: OutPoint) -> Transaction {
+ let txins = {
+ let mut ins: Vec<TxIn> = Vec::new();
+ ins.push(TxIn {
+ previous_output: funding_outpoint,
+ script_sig: Script::new(),
+ sequence: 0xffffffff,
+ witness: Vec::new(),
+ });
+ ins
+ };
+
+ let mut txouts: Vec<(TxOut, ())> = Vec::new();
+
+ if to_counterparty_value_sat > 0 {
+ txouts.push((TxOut {
+ script_pubkey: to_counterparty_script,
+ value: to_counterparty_value_sat
+ }, ()));
+ }
+
+ if to_holder_value_sat > 0 {
+ txouts.push((TxOut {
+ script_pubkey: to_holder_script,
+ value: to_holder_value_sat
+ }, ()));
+ }
+
+ transaction_utils::sort_outputs(&mut txouts, |_, _| { cmp::Ordering::Equal }); // Ordering doesnt matter if they used our pubkey...
+
+ let mut outputs: Vec<TxOut> = Vec::new();
+ for out in txouts.drain(..) {
+ outputs.push(out.0);
+ }
+
+ Transaction {
+ version: 2,
+ lock_time: 0,
+ input: txins,
+ output: outputs,
+ }
+}
+
/// Implements the per-commitment secret storage scheme from
/// [BOLT 3](https://github.com/lightningnetwork/lightning-rfc/blob/dcbf8583976df087c79c3ce0b535311212e6812d/03-transactions.md#efficient-per-commitment-secret-storage).
///
}
impl Writeable for CounterpartyCommitmentSecrets {
- fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
for &(ref secret, ref idx) in self.old_secrets.iter() {
writer.write_all(secret)?;
writer.write_all(&byte_utils::be64_to_array(*idx))?;
}
+ write_tlv_fields!(writer, {});
Ok(())
}
}
impl Readable for CounterpartyCommitmentSecrets {
- fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
+ fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
let mut old_secrets = [([0; 32], 1 << 48); 49];
for &mut (ref mut secret, ref mut idx) in old_secrets.iter_mut() {
*secret = Readable::read(reader)?;
*idx = Readable::read(reader)?;
}
-
+ read_tlv_fields!(reader, {});
Ok(Self { old_secrets })
}
}
///
/// These keys are assumed to be good, either because the code derived them from
/// channel basepoints via the new function, or they were obtained via
-/// PreCalculatedTxCreationKeys.trust_key_derivation because we trusted the source of the
+/// CommitmentTransaction.trust().keys() because we trusted the source of the
/// pre-calculated keys.
#[derive(PartialEq, Clone)]
pub struct TxCreationKeys {
/// Broadcaster's Payment Key (which isn't allowed to be spent from for some delay)
pub broadcaster_delayed_payment_key: PublicKey,
}
-impl_writeable!(TxCreationKeys, 33*6,
- { per_commitment_point, revocation_key, broadcaster_htlc_key, countersignatory_htlc_key, broadcaster_delayed_payment_key });
-
-/// The per-commitment point and a set of pre-calculated public keys used for transaction creation
-/// in the signer.
-/// The pre-calculated keys are an optimization, because ChannelKeys has enough
-/// information to re-derive them.
-#[derive(PartialEq, Clone)]
-pub struct PreCalculatedTxCreationKeys(TxCreationKeys);
-
-impl PreCalculatedTxCreationKeys {
- /// Create a new PreCalculatedTxCreationKeys from TxCreationKeys
- pub fn new(keys: TxCreationKeys) -> Self {
- PreCalculatedTxCreationKeys(keys)
- }
-
- /// The pre-calculated transaction creation public keys.
- /// An external validating signer should not trust these keys.
- pub fn trust_key_derivation(&self) -> &TxCreationKeys {
- &self.0
- }
- /// The transaction per-commitment point
- pub fn per_commitment_point(&self) -> &PublicKey {
- &self.0.per_commitment_point
- }
-}
+impl_writeable_tlv_based!(TxCreationKeys, {
+ (0, per_commitment_point, required),
+ (2, revocation_key, required),
+ (4, broadcaster_htlc_key, required),
+ (6, countersignatory_htlc_key, required),
+ (8, broadcaster_delayed_payment_key, required),
+});
/// One counterparty's public keys which do not change over the life of a channel.
#[derive(Clone, PartialEq)]
pub htlc_basepoint: PublicKey,
}
-impl_writeable!(ChannelPublicKeys, 33*5, {
- funding_pubkey,
- revocation_basepoint,
- payment_point,
- delayed_payment_basepoint,
- htlc_basepoint
+impl_writeable_tlv_based!(ChannelPublicKeys, {
+ (0, funding_pubkey, required),
+ (2, revocation_basepoint, required),
+ (4, payment_point, required),
+ (6, delayed_payment_basepoint, required),
+ (8, htlc_basepoint, required),
});
-
impl TxCreationKeys {
- /// Create a new TxCreationKeys from channel base points and the per-commitment point
+ /// Create per-state keys from channel base points and the per-commitment point.
+ /// Key set is asymmetric and can't be used as part of counter-signatory set of transactions.
pub fn derive_new<T: secp256k1::Signing + secp256k1::Verification>(secp_ctx: &Secp256k1<T>, per_commitment_point: &PublicKey, broadcaster_delayed_payment_base: &PublicKey, broadcaster_htlc_base: &PublicKey, countersignatory_revocation_base: &PublicKey, countersignatory_htlc_base: &PublicKey) -> Result<TxCreationKeys, SecpError> {
Ok(TxCreationKeys {
per_commitment_point: per_commitment_point.clone(),
broadcaster_delayed_payment_key: derive_public_key(&secp_ctx, &per_commitment_point, &broadcaster_delayed_payment_base)?,
})
}
+
+ /// Generate per-state keys from channel static keys.
+ /// Key set is asymmetric and can't be used as part of counter-signatory set of transactions.
+ pub fn from_channel_static_keys<T: secp256k1::Signing + secp256k1::Verification>(per_commitment_point: &PublicKey, broadcaster_keys: &ChannelPublicKeys, countersignatory_keys: &ChannelPublicKeys, secp_ctx: &Secp256k1<T>) -> Result<TxCreationKeys, SecpError> {
+ TxCreationKeys::derive_new(
+ &secp_ctx,
+ &per_commitment_point,
+ &broadcaster_keys.delayed_payment_basepoint,
+ &broadcaster_keys.htlc_basepoint,
+ &countersignatory_keys.revocation_basepoint,
+ &countersignatory_keys.htlc_basepoint,
+ )
+ }
}
+/// The maximum length of a script returned by get_revokeable_redeemscript.
+// Calculated as 6 bytes of opcodes, 1 byte push plus 2 bytes for contest_delay, and two public
+// keys of 33 bytes (+ 1 push).
+pub const REVOKEABLE_REDEEMSCRIPT_MAX_LENGTH: usize = 6 + 3 + 34*2;
+
/// A script either spendable by the revocation
/// key or the broadcaster_delayed_payment_key and satisfying the relative-locktime OP_CSV constrain.
/// Encumbering a `to_holder` output on a commitment transaction or 2nd-stage HTLC transactions.
pub fn get_revokeable_redeemscript(revocation_key: &PublicKey, contest_delay: u16, broadcaster_delayed_payment_key: &PublicKey) -> Script {
- Builder::new().push_opcode(opcodes::all::OP_IF)
+ let res = Builder::new().push_opcode(opcodes::all::OP_IF)
.push_slice(&revocation_key.serialize())
.push_opcode(opcodes::all::OP_ELSE)
.push_int(contest_delay as i64)
.push_slice(&broadcaster_delayed_payment_key.serialize())
.push_opcode(opcodes::all::OP_ENDIF)
.push_opcode(opcodes::all::OP_CHECKSIG)
- .into_script()
+ .into_script();
+ debug_assert!(res.len() <= REVOKEABLE_REDEEMSCRIPT_MAX_LENGTH);
+ res
}
#[derive(Clone, PartialEq)]
pub transaction_output_index: Option<u32>,
}
-impl_writeable!(HTLCOutputInCommitment, 1 + 8 + 4 + 32 + 5, {
- offered,
- amount_msat,
- cltv_expiry,
- payment_hash,
- transaction_output_index
+impl_writeable_tlv_based!(HTLCOutputInCommitment, {
+ (0, offered, required),
+ (2, amount_msat, required),
+ (4, cltv_expiry, required),
+ (6, payment_hash, required),
+ (8, transaction_output_index, option),
});
#[inline]
-pub(crate) fn get_htlc_redeemscript_with_explicit_keys(htlc: &HTLCOutputInCommitment, broadcaster_htlc_key: &PublicKey, countersignatory_htlc_key: &PublicKey, revocation_key: &PublicKey) -> Script {
+pub(crate) fn get_htlc_redeemscript_with_explicit_keys(htlc: &HTLCOutputInCommitment, opt_anchors: bool, broadcaster_htlc_key: &PublicKey, countersignatory_htlc_key: &PublicKey, revocation_key: &PublicKey) -> Script {
let payment_hash160 = Ripemd160::hash(&htlc.payment_hash.0[..]).into_inner();
if htlc.offered {
- Builder::new().push_opcode(opcodes::all::OP_DUP)
+ let mut bldr = Builder::new().push_opcode(opcodes::all::OP_DUP)
.push_opcode(opcodes::all::OP_HASH160)
.push_slice(&PubkeyHash::hash(&revocation_key.serialize())[..])
.push_opcode(opcodes::all::OP_EQUAL)
.push_slice(&payment_hash160)
.push_opcode(opcodes::all::OP_EQUALVERIFY)
.push_opcode(opcodes::all::OP_CHECKSIG)
- .push_opcode(opcodes::all::OP_ENDIF)
- .push_opcode(opcodes::all::OP_ENDIF)
- .into_script()
+ .push_opcode(opcodes::all::OP_ENDIF);
+ if opt_anchors {
+ bldr = bldr.push_opcode(opcodes::all::OP_PUSHNUM_1)
+ .push_opcode(opcodes::all::OP_CSV)
+ .push_opcode(opcodes::all::OP_DROP);
+ }
+ bldr.push_opcode(opcodes::all::OP_ENDIF)
+ .into_script()
} else {
- Builder::new().push_opcode(opcodes::all::OP_DUP)
+ let mut bldr = Builder::new().push_opcode(opcodes::all::OP_DUP)
.push_opcode(opcodes::all::OP_HASH160)
.push_slice(&PubkeyHash::hash(&revocation_key.serialize())[..])
.push_opcode(opcodes::all::OP_EQUAL)
.push_opcode(opcodes::all::OP_CLTV)
.push_opcode(opcodes::all::OP_DROP)
.push_opcode(opcodes::all::OP_CHECKSIG)
- .push_opcode(opcodes::all::OP_ENDIF)
- .push_opcode(opcodes::all::OP_ENDIF)
- .into_script()
+ .push_opcode(opcodes::all::OP_ENDIF);
+ if opt_anchors {
+ bldr = bldr.push_opcode(opcodes::all::OP_PUSHNUM_1)
+ .push_opcode(opcodes::all::OP_CSV)
+ .push_opcode(opcodes::all::OP_DROP);
+ }
+ bldr.push_opcode(opcodes::all::OP_ENDIF)
+ .into_script()
}
}
/// Gets the witness redeemscript for an HTLC output in a commitment transaction. Note that htlc
/// does not need to have its previous_output_index filled.
#[inline]
-pub fn get_htlc_redeemscript(htlc: &HTLCOutputInCommitment, keys: &TxCreationKeys) -> Script {
- get_htlc_redeemscript_with_explicit_keys(htlc, &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key)
+pub fn get_htlc_redeemscript(htlc: &HTLCOutputInCommitment, opt_anchors: bool, keys: &TxCreationKeys) -> Script {
+ get_htlc_redeemscript_with_explicit_keys(htlc, opt_anchors, &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key)
}
/// Gets the redeemscript for a funding output from the two funding public keys.
}.push_opcode(opcodes::all::OP_PUSHNUM_2).push_opcode(opcodes::all::OP_CHECKMULTISIG).into_script()
}
-/// panics if htlc.transaction_output_index.is_none()!
-pub fn build_htlc_transaction(prev_hash: &Txid, feerate_per_kw: u32, contest_delay: u16, htlc: &HTLCOutputInCommitment, broadcaster_delayed_payment_key: &PublicKey, revocation_key: &PublicKey) -> Transaction {
+/// Builds an unsigned HTLC-Success or HTLC-Timeout transaction from the given channel and HTLC
+/// parameters. This is used by [`TrustedCommitmentTransaction::get_htlc_sigs`] to fetch the
+/// transaction which needs signing, and can be used to construct an HTLC transaction which is
+/// broadcastable given a counterparty HTLC signature.
+///
+/// Panics if htlc.transaction_output_index.is_none() (as such HTLCs do not appear in the
+/// commitment transaction).
+pub fn build_htlc_transaction(commitment_txid: &Txid, feerate_per_kw: u32, contest_delay: u16, htlc: &HTLCOutputInCommitment, opt_anchors: bool, broadcaster_delayed_payment_key: &PublicKey, revocation_key: &PublicKey) -> Transaction {
let mut txins: Vec<TxIn> = Vec::new();
txins.push(TxIn {
previous_output: OutPoint {
- txid: prev_hash.clone(),
+ txid: commitment_txid.clone(),
vout: htlc.transaction_output_index.expect("Can't build an HTLC transaction for a dust output"),
},
script_sig: Script::new(),
- sequence: 0,
+ sequence: if opt_anchors { 1 } else { 0 },
witness: Vec::new(),
});
- let total_fee = if htlc.offered {
- feerate_per_kw as u64 * HTLC_TIMEOUT_TX_WEIGHT / 1000
- } else {
- feerate_per_kw as u64 * HTLC_SUCCESS_TX_WEIGHT / 1000
- };
+ let weight = if htlc.offered {
+ htlc_timeout_tx_weight(opt_anchors)
+ } else {
+ htlc_success_tx_weight(opt_anchors)
+ };
+ let total_fee = feerate_per_kw as u64 * weight / 1000;
let mut txouts: Vec<TxOut> = Vec::new();
txouts.push(TxOut {
}
}
+/// Gets the witnessScript for the to_remote output when anchors are enabled.
+#[inline]
+pub(crate) fn get_to_countersignatory_with_anchors_redeemscript(payment_point: &PublicKey) -> Script {
+ Builder::new()
+ .push_slice(&payment_point.serialize()[..])
+ .push_opcode(opcodes::all::OP_CHECKSIGVERIFY)
+ .push_int(1)
+ .push_opcode(opcodes::all::OP_CSV)
+ .into_script()
+}
+
+/// Gets the witnessScript for an anchor output from the funding public key.
+/// The witness in the spending input must be:
+/// <BIP 143 funding_signature>
+/// After 16 blocks of confirmation, an alternative satisfying witness could be:
+/// <>
+/// (empty vector required to satisfy compliance with MINIMALIF-standard rule)
+#[inline]
+pub fn get_anchor_redeemscript(funding_pubkey: &PublicKey) -> Script {
+ Builder::new().push_slice(&funding_pubkey.serialize()[..])
+ .push_opcode(opcodes::all::OP_CHECKSIG)
+ .push_opcode(opcodes::all::OP_IFDUP)
+ .push_opcode(opcodes::all::OP_NOTIF)
+ .push_int(16)
+ .push_opcode(opcodes::all::OP_CSV)
+ .push_opcode(opcodes::all::OP_ENDIF)
+ .into_script()
+}
+
+/// Per-channel data used to build transactions in conjunction with the per-commitment data (CommitmentTransaction).
+/// The fields are organized by holder/counterparty.
+///
+/// Normally, this is converted to the broadcaster/countersignatory-organized DirectedChannelTransactionParameters
+/// before use, via the as_holder_broadcastable and as_counterparty_broadcastable functions.
+#[derive(Clone)]
+pub struct ChannelTransactionParameters {
+ /// Holder public keys
+ pub holder_pubkeys: ChannelPublicKeys,
+ /// The contest delay selected by the holder, which applies to counterparty-broadcast transactions
+ pub holder_selected_contest_delay: u16,
+ /// Whether the holder is the initiator of this channel.
+ /// This is an input to the commitment number obscure factor computation.
+ pub is_outbound_from_holder: bool,
+ /// The late-bound counterparty channel transaction parameters.
+ /// These parameters are populated at the point in the protocol where the counterparty provides them.
+ pub counterparty_parameters: Option<CounterpartyChannelTransactionParameters>,
+ /// The late-bound funding outpoint
+ pub funding_outpoint: Option<chain::transaction::OutPoint>,
+ /// Are anchors used for this channel. Boolean is serialization backwards-compatible
+ pub opt_anchors: Option<()>
+}
+
+/// Late-bound per-channel counterparty data used to build transactions.
+#[derive(Clone)]
+pub struct CounterpartyChannelTransactionParameters {
+ /// Counter-party public keys
+ pub pubkeys: ChannelPublicKeys,
+ /// The contest delay selected by the counterparty, which applies to holder-broadcast transactions
+ pub selected_contest_delay: u16,
+}
+
+impl ChannelTransactionParameters {
+ /// Whether the late bound parameters are populated.
+ pub fn is_populated(&self) -> bool {
+ self.counterparty_parameters.is_some() && self.funding_outpoint.is_some()
+ }
+
+ /// Convert the holder/counterparty parameters to broadcaster/countersignatory-organized parameters,
+ /// given that the holder is the broadcaster.
+ ///
+ /// self.is_populated() must be true before calling this function.
+ pub fn as_holder_broadcastable(&self) -> DirectedChannelTransactionParameters {
+ assert!(self.is_populated(), "self.late_parameters must be set before using as_holder_broadcastable");
+ DirectedChannelTransactionParameters {
+ inner: self,
+ holder_is_broadcaster: true
+ }
+ }
+
+ /// Convert the holder/counterparty parameters to broadcaster/countersignatory-organized parameters,
+ /// given that the counterparty is the broadcaster.
+ ///
+ /// self.is_populated() must be true before calling this function.
+ pub fn as_counterparty_broadcastable(&self) -> DirectedChannelTransactionParameters {
+ assert!(self.is_populated(), "self.late_parameters must be set before using as_counterparty_broadcastable");
+ DirectedChannelTransactionParameters {
+ inner: self,
+ holder_is_broadcaster: false
+ }
+ }
+}
+
+impl_writeable_tlv_based!(CounterpartyChannelTransactionParameters, {
+ (0, pubkeys, required),
+ (2, selected_contest_delay, required),
+});
+
+impl_writeable_tlv_based!(ChannelTransactionParameters, {
+ (0, holder_pubkeys, required),
+ (2, holder_selected_contest_delay, required),
+ (4, is_outbound_from_holder, required),
+ (6, counterparty_parameters, option),
+ (8, funding_outpoint, option),
+ (10, opt_anchors, option),
+});
+
+/// Static channel fields used to build transactions given per-commitment fields, organized by
+/// broadcaster/countersignatory.
+///
+/// This is derived from the holder/counterparty-organized ChannelTransactionParameters via the
+/// as_holder_broadcastable and as_counterparty_broadcastable functions.
+pub struct DirectedChannelTransactionParameters<'a> {
+ /// The holder's channel static parameters
+ inner: &'a ChannelTransactionParameters,
+ /// Whether the holder is the broadcaster
+ holder_is_broadcaster: bool,
+}
+
+impl<'a> DirectedChannelTransactionParameters<'a> {
+ /// Get the channel pubkeys for the broadcaster
+ pub fn broadcaster_pubkeys(&self) -> &ChannelPublicKeys {
+ if self.holder_is_broadcaster {
+ &self.inner.holder_pubkeys
+ } else {
+ &self.inner.counterparty_parameters.as_ref().unwrap().pubkeys
+ }
+ }
+
+ /// Get the channel pubkeys for the countersignatory
+ pub fn countersignatory_pubkeys(&self) -> &ChannelPublicKeys {
+ if self.holder_is_broadcaster {
+ &self.inner.counterparty_parameters.as_ref().unwrap().pubkeys
+ } else {
+ &self.inner.holder_pubkeys
+ }
+ }
+
+ /// Get the contest delay applicable to the transactions.
+ /// Note that the contest delay was selected by the countersignatory.
+ pub fn contest_delay(&self) -> u16 {
+ let counterparty_parameters = self.inner.counterparty_parameters.as_ref().unwrap();
+ if self.holder_is_broadcaster { counterparty_parameters.selected_contest_delay } else { self.inner.holder_selected_contest_delay }
+ }
+
+ /// Whether the channel is outbound from the broadcaster.
+ ///
+ /// The boolean representing the side that initiated the channel is
+ /// an input to the commitment number obscure factor computation.
+ pub fn is_outbound(&self) -> bool {
+ if self.holder_is_broadcaster { self.inner.is_outbound_from_holder } else { !self.inner.is_outbound_from_holder }
+ }
+
+ /// The funding outpoint
+ pub fn funding_outpoint(&self) -> OutPoint {
+ self.inner.funding_outpoint.unwrap().into_bitcoin_outpoint()
+ }
+
+ /// Whether to use anchors for this channel
+ pub fn opt_anchors(&self) -> bool {
+ self.inner.opt_anchors.is_some()
+ }
+}
+
+/// Information needed to build and sign a holder's commitment transaction.
+///
+/// The transaction is only signed once we are ready to broadcast.
#[derive(Clone)]
-/// We use this to track holder commitment transactions and put off signing them until we are ready
-/// to broadcast. This class can be used inside a signer implementation to generate a signature
-/// given the relevant secret key.
pub struct HolderCommitmentTransaction {
- // TODO: We should migrate away from providing the transaction, instead providing enough to
- // allow the ChannelKeys to construct it from scratch. Luckily we already have HTLC data here,
- // so we're probably most of the way there.
- /// The commitment transaction itself, in unsigned form.
- pub unsigned_tx: Transaction,
- /// Our counterparty's signature for the transaction, above.
+ inner: CommitmentTransaction,
+ /// Our counterparty's signature for the transaction
pub counterparty_sig: Signature,
+ /// All non-dust counterparty HTLC signatures, in the order they appear in the transaction
+ pub counterparty_htlc_sigs: Vec<Signature>,
// Which order the signatures should go in when constructing the final commitment tx witness.
- // The user should be able to reconstruc this themselves, so we don't bother to expose it.
+ // The user should be able to reconstruct this themselves, so we don't bother to expose it.
holder_sig_first: bool,
- pub(crate) keys: TxCreationKeys,
- /// The feerate paid per 1000-weight-unit in this commitment transaction. This value is
- /// controlled by the channel initiator.
- pub feerate_per_kw: u32,
- /// The HTLCs and counterparty htlc signatures which were included in this commitment transaction.
- ///
- /// Note that this includes all HTLCs, including ones which were considered dust and not
- /// actually included in the transaction as it appears on-chain, but who's value is burned as
- /// fees and not included in the to_holder or to_counterparty outputs.
- ///
- /// The counterparty HTLC signatures in the second element will always be set for non-dust HTLCs, ie
- /// those for which transaction_output_index.is_some().
- pub per_htlc: Vec<(HTLCOutputInCommitment, Option<Signature>)>,
}
+
+impl Deref for HolderCommitmentTransaction {
+ type Target = CommitmentTransaction;
+
+ fn deref(&self) -> &Self::Target { &self.inner }
+}
+
+impl PartialEq for HolderCommitmentTransaction {
+ // We dont care whether we are signed in equality comparison
+ fn eq(&self, o: &Self) -> bool {
+ self.inner == o.inner
+ }
+}
+
+impl_writeable_tlv_based!(HolderCommitmentTransaction, {
+ (0, inner, required),
+ (2, counterparty_sig, required),
+ (4, holder_sig_first, required),
+ (6, counterparty_htlc_sigs, vec_type),
+});
+
impl HolderCommitmentTransaction {
#[cfg(test)]
pub fn dummy() -> Self {
- let dummy_input = TxIn {
- previous_output: OutPoint {
- txid: Default::default(),
- vout: 0,
- },
- script_sig: Default::default(),
- sequence: 0,
- witness: vec![]
+ let secp_ctx = Secp256k1::new();
+ let dummy_key = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
+ let dummy_sig = secp_ctx.sign(&secp256k1::Message::from_slice(&[42; 32]).unwrap(), &SecretKey::from_slice(&[42; 32]).unwrap());
+
+ let keys = TxCreationKeys {
+ per_commitment_point: dummy_key.clone(),
+ revocation_key: dummy_key.clone(),
+ broadcaster_htlc_key: dummy_key.clone(),
+ countersignatory_htlc_key: dummy_key.clone(),
+ broadcaster_delayed_payment_key: dummy_key.clone(),
};
- let dummy_key = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&[42; 32]).unwrap());
- let dummy_sig = Secp256k1::new().sign(&secp256k1::Message::from_slice(&[42; 32]).unwrap(), &SecretKey::from_slice(&[42; 32]).unwrap());
- Self {
- unsigned_tx: Transaction {
- version: 2,
- input: vec![dummy_input],
- output: Vec::new(),
- lock_time: 0,
- },
+ let channel_pubkeys = ChannelPublicKeys {
+ funding_pubkey: dummy_key.clone(),
+ revocation_basepoint: dummy_key.clone(),
+ payment_point: dummy_key.clone(),
+ delayed_payment_basepoint: dummy_key.clone(),
+ htlc_basepoint: dummy_key.clone()
+ };
+ let channel_parameters = ChannelTransactionParameters {
+ holder_pubkeys: channel_pubkeys.clone(),
+ holder_selected_contest_delay: 0,
+ is_outbound_from_holder: false,
+ counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: channel_pubkeys.clone(), selected_contest_delay: 0 }),
+ funding_outpoint: Some(chain::transaction::OutPoint { txid: Default::default(), index: 0 }),
+ opt_anchors: None
+ };
+ let mut htlcs_with_aux: Vec<(_, ())> = Vec::new();
+ let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, false, dummy_key.clone(), dummy_key.clone(), keys, 0, &mut htlcs_with_aux, &channel_parameters.as_counterparty_broadcastable());
+ HolderCommitmentTransaction {
+ inner,
counterparty_sig: dummy_sig,
- holder_sig_first: false,
- keys: TxCreationKeys {
- per_commitment_point: dummy_key.clone(),
- revocation_key: dummy_key.clone(),
- broadcaster_htlc_key: dummy_key.clone(),
- countersignatory_htlc_key: dummy_key.clone(),
- broadcaster_delayed_payment_key: dummy_key.clone(),
- },
- feerate_per_kw: 0,
- per_htlc: Vec::new()
+ counterparty_htlc_sigs: Vec::new(),
+ holder_sig_first: false
}
}
- /// Generate a new HolderCommitmentTransaction based on a raw commitment transaction,
- /// counterparty signature and both parties keys.
- ///
- /// The unsigned transaction outputs must be consistent with htlc_data. This function
- /// only checks that the shape and amounts are consistent, but does not check the scriptPubkey.
- pub fn new_missing_holder_sig(unsigned_tx: Transaction, counterparty_sig: Signature, holder_funding_key: &PublicKey, counterparty_funding_key: &PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlc_data: Vec<(HTLCOutputInCommitment, Option<Signature>)>) -> HolderCommitmentTransaction {
- if unsigned_tx.input.len() != 1 { panic!("Tried to store a commitment transaction that had input count != 1!"); }
- if unsigned_tx.input[0].witness.len() != 0 { panic!("Tried to store a signed commitment transaction?"); }
-
- for htlc in &htlc_data {
- if let Some(index) = htlc.0.transaction_output_index {
- let out = &unsigned_tx.output[index as usize];
- if out.value != htlc.0.amount_msat / 1000 {
- panic!("HTLC at index {} has incorrect amount", index);
- }
- if !out.script_pubkey.is_v0_p2wsh() {
- panic!("HTLC at index {} doesn't have p2wsh scriptPubkey", index);
- }
- }
- }
-
+ /// Create a new holder transaction with the given counterparty signatures.
+ /// The funding keys are used to figure out which signature should go first when building the transaction for broadcast.
+ pub fn new(commitment_tx: CommitmentTransaction, counterparty_sig: Signature, counterparty_htlc_sigs: Vec<Signature>, holder_funding_key: &PublicKey, counterparty_funding_key: &PublicKey) -> Self {
Self {
- unsigned_tx,
+ inner: commitment_tx,
counterparty_sig,
+ counterparty_htlc_sigs,
holder_sig_first: holder_funding_key.serialize()[..] < counterparty_funding_key.serialize()[..],
- keys,
- feerate_per_kw,
- per_htlc: htlc_data,
}
}
- /// The pre-calculated transaction creation public keys.
- /// An external validating signer should not trust these keys.
- pub fn trust_key_derivation(&self) -> &TxCreationKeys {
- &self.keys
- }
-
- /// Get the txid of the holder commitment transaction contained in this
- /// HolderCommitmentTransaction
- pub fn txid(&self) -> Txid {
- self.unsigned_tx.txid()
- }
-
- /// Gets holder signature for the contained commitment transaction given holder funding private key.
- ///
- /// Funding key is your key included in the 2-2 funding_outpoint lock. Should be provided
- /// by your ChannelKeys.
- /// Funding redeemscript is script locking funding_outpoint. This is the mutlsig script
- /// between your own funding key and your counterparty's. Currently, this is provided in
- /// ChannelKeys::sign_holder_commitment() calls directly.
- /// Channel value is amount locked in funding_outpoint.
- pub fn get_holder_sig<T: secp256k1::Signing>(&self, funding_key: &SecretKey, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1<T>) -> Signature {
- let sighash = hash_to_message!(&bip143::SigHashCache::new(&self.unsigned_tx)
- .signature_hash(0, funding_redeemscript, channel_value_satoshis, SigHashType::All)[..]);
- secp_ctx.sign(&sighash, funding_key)
- }
-
pub(crate) fn add_holder_sig(&self, funding_redeemscript: &Script, holder_sig: Signature) -> Transaction {
- let mut tx = self.unsigned_tx.clone();
// First push the multisig dummy, note that due to BIP147 (NULLDUMMY) it must be a zero-length element.
+ let mut tx = self.inner.built.transaction.clone();
tx.input[0].witness.push(Vec::new());
if self.holder_sig_first {
tx.input[0].witness.push(funding_redeemscript.as_bytes().to_vec());
tx
}
+}
- /// Get a signature for each HTLC which was included in the commitment transaction (ie for
- /// which HTLCOutputInCommitment::transaction_output_index.is_some()).
+/// A pre-built Bitcoin commitment transaction and its txid.
+#[derive(Clone)]
+pub struct BuiltCommitmentTransaction {
+ /// The commitment transaction
+ pub transaction: Transaction,
+ /// The txid for the commitment transaction.
+ ///
+ /// This is provided as a performance optimization, instead of calling transaction.txid()
+ /// multiple times.
+ pub txid: Txid,
+}
+
+impl_writeable_tlv_based!(BuiltCommitmentTransaction, {
+ (0, transaction, required),
+ (2, txid, required),
+});
+
+impl BuiltCommitmentTransaction {
+ /// Get the SIGHASH_ALL sighash value of the transaction.
+ ///
+ /// This can be used to verify a signature.
+ pub fn get_sighash_all(&self, funding_redeemscript: &Script, channel_value_satoshis: u64) -> Message {
+ let sighash = &bip143::SigHashCache::new(&self.transaction).signature_hash(0, funding_redeemscript, channel_value_satoshis, SigHashType::All)[..];
+ hash_to_message!(sighash)
+ }
+
+ /// Sign a transaction, either because we are counter-signing the counterparty's transaction or
+ /// because we are about to broadcast a holder transaction.
+ pub fn sign<T: secp256k1::Signing>(&self, funding_key: &SecretKey, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1<T>) -> Signature {
+ let sighash = self.get_sighash_all(funding_redeemscript, channel_value_satoshis);
+ secp_ctx.sign(&sighash, funding_key)
+ }
+}
+
+/// This class tracks the per-transaction information needed to build a closing transaction and will
+/// actually build it and sign.
+///
+/// This class can be used inside a signer implementation to generate a signature given the relevant
+/// secret key.
+#[derive(Clone, Hash, PartialEq)]
+pub struct ClosingTransaction {
+ to_holder_value_sat: u64,
+ to_counterparty_value_sat: u64,
+ to_holder_script: Script,
+ to_counterparty_script: Script,
+ built: Transaction,
+}
+
+impl ClosingTransaction {
+ /// Construct an object of the class
+ pub fn new(
+ to_holder_value_sat: u64,
+ to_counterparty_value_sat: u64,
+ to_holder_script: Script,
+ to_counterparty_script: Script,
+ funding_outpoint: OutPoint,
+ ) -> Self {
+ let built = build_closing_transaction(
+ to_holder_value_sat, to_counterparty_value_sat,
+ to_holder_script.clone(), to_counterparty_script.clone(),
+ funding_outpoint
+ );
+ ClosingTransaction {
+ to_holder_value_sat,
+ to_counterparty_value_sat,
+ to_holder_script,
+ to_counterparty_script,
+ built
+ }
+ }
+
+ /// Trust our pre-built transaction.
+ ///
+ /// Applies a wrapper which allows access to the transaction.
+ ///
+ /// This should only be used if you fully trust the builder of this object. It should not
+ /// be used by an external signer - instead use the verify function.
+ pub fn trust(&self) -> TrustedClosingTransaction {
+ TrustedClosingTransaction { inner: self }
+ }
+
+ /// Verify our pre-built transaction.
+ ///
+ /// Applies a wrapper which allows access to the transaction.
+ ///
+ /// An external validating signer must call this method before signing
+ /// or using the built transaction.
+ pub fn verify(&self, funding_outpoint: OutPoint) -> Result<TrustedClosingTransaction, ()> {
+ let built = build_closing_transaction(
+ self.to_holder_value_sat, self.to_counterparty_value_sat,
+ self.to_holder_script.clone(), self.to_counterparty_script.clone(),
+ funding_outpoint
+ );
+ if self.built != built {
+ return Err(())
+ }
+ Ok(TrustedClosingTransaction { inner: self })
+ }
+
+ /// The value to be sent to the holder, or zero if the output will be omitted
+ pub fn to_holder_value_sat(&self) -> u64 {
+ self.to_holder_value_sat
+ }
+
+ /// The value to be sent to the counterparty, or zero if the output will be omitted
+ pub fn to_counterparty_value_sat(&self) -> u64 {
+ self.to_counterparty_value_sat
+ }
+
+ /// The destination of the holder's output
+ pub fn to_holder_script(&self) -> &Script {
+ &self.to_holder_script
+ }
+
+ /// The destination of the counterparty's output
+ pub fn to_counterparty_script(&self) -> &Script {
+ &self.to_counterparty_script
+ }
+}
+
+/// A wrapper on ClosingTransaction indicating that the built bitcoin
+/// transaction is trusted.
+///
+/// See trust() and verify() functions on CommitmentTransaction.
+///
+/// This structure implements Deref.
+pub struct TrustedClosingTransaction<'a> {
+ inner: &'a ClosingTransaction,
+}
+
+impl<'a> Deref for TrustedClosingTransaction<'a> {
+ type Target = ClosingTransaction;
+
+ fn deref(&self) -> &Self::Target { self.inner }
+}
+
+impl<'a> TrustedClosingTransaction<'a> {
+ /// The pre-built Bitcoin commitment transaction
+ pub fn built_transaction(&self) -> &Transaction {
+ &self.inner.built
+ }
+
+ /// Get the SIGHASH_ALL sighash value of the transaction.
///
- /// The returned Vec has one entry for each HTLC, and in the same order. For HTLCs which were
- /// considered dust and not included, a None entry exists, for all others a signature is
- /// included.
- pub fn get_htlc_sigs<T: secp256k1::Signing + secp256k1::Verification>(&self, htlc_base_key: &SecretKey, counterparty_selected_contest_delay: u16, secp_ctx: &Secp256k1<T>) -> Result<Vec<Option<Signature>>, ()> {
- let txid = self.txid();
- let mut ret = Vec::with_capacity(self.per_htlc.len());
- let holder_htlc_key = derive_private_key(secp_ctx, &self.keys.per_commitment_point, htlc_base_key).map_err(|_| ())?;
-
- for this_htlc in self.per_htlc.iter() {
- if this_htlc.0.transaction_output_index.is_some() {
- let htlc_tx = build_htlc_transaction(&txid, self.feerate_per_kw, counterparty_selected_contest_delay, &this_htlc.0, &self.keys.broadcaster_delayed_payment_key, &self.keys.revocation_key);
-
- let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc.0, &self.keys.broadcaster_htlc_key, &self.keys.countersignatory_htlc_key, &self.keys.revocation_key);
-
- let sighash = hash_to_message!(&bip143::SigHashCache::new(&htlc_tx).signature_hash(0, &htlc_redeemscript, this_htlc.0.amount_msat / 1000, SigHashType::All)[..]);
- ret.push(Some(secp_ctx.sign(&sighash, &holder_htlc_key)));
+ /// This can be used to verify a signature.
+ pub fn get_sighash_all(&self, funding_redeemscript: &Script, channel_value_satoshis: u64) -> Message {
+ let sighash = &bip143::SigHashCache::new(&self.inner.built).signature_hash(0, funding_redeemscript, channel_value_satoshis, SigHashType::All)[..];
+ hash_to_message!(sighash)
+ }
+
+ /// Sign a transaction, either because we are counter-signing the counterparty's transaction or
+ /// because we are about to broadcast a holder transaction.
+ pub fn sign<T: secp256k1::Signing>(&self, funding_key: &SecretKey, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1<T>) -> Signature {
+ let sighash = self.get_sighash_all(funding_redeemscript, channel_value_satoshis);
+ secp_ctx.sign(&sighash, funding_key)
+ }
+}
+
+/// This class tracks the per-transaction information needed to build a commitment transaction and will
+/// actually build it and sign. It is used for holder transactions that we sign only when needed
+/// and for transactions we sign for the counterparty.
+///
+/// This class can be used inside a signer implementation to generate a signature given the relevant
+/// secret key.
+#[derive(Clone)]
+pub struct CommitmentTransaction {
+ commitment_number: u64,
+ to_broadcaster_value_sat: u64,
+ to_countersignatory_value_sat: u64,
+ feerate_per_kw: u32,
+ htlcs: Vec<HTLCOutputInCommitment>,
+ // A boolean that is serialization backwards-compatible
+ opt_anchors: Option<()>,
+ // A cache of the parties' pubkeys required to construct the transaction, see doc for trust()
+ keys: TxCreationKeys,
+ // For access to the pre-built transaction, see doc for trust()
+ built: BuiltCommitmentTransaction,
+}
+
+impl PartialEq for CommitmentTransaction {
+ fn eq(&self, o: &Self) -> bool {
+ let eq = self.commitment_number == o.commitment_number &&
+ self.to_broadcaster_value_sat == o.to_broadcaster_value_sat &&
+ self.to_countersignatory_value_sat == o.to_countersignatory_value_sat &&
+ self.feerate_per_kw == o.feerate_per_kw &&
+ self.htlcs == o.htlcs &&
+ self.opt_anchors == o.opt_anchors &&
+ self.keys == o.keys;
+ if eq {
+ debug_assert_eq!(self.built.transaction, o.built.transaction);
+ debug_assert_eq!(self.built.txid, o.built.txid);
+ }
+ eq
+ }
+}
+
+impl_writeable_tlv_based!(CommitmentTransaction, {
+ (0, commitment_number, required),
+ (2, to_broadcaster_value_sat, required),
+ (4, to_countersignatory_value_sat, required),
+ (6, feerate_per_kw, required),
+ (8, keys, required),
+ (10, built, required),
+ (12, htlcs, vec_type),
+ (14, opt_anchors, option),
+});
+
+impl CommitmentTransaction {
+ /// Construct an object of the class while assigning transaction output indices to HTLCs.
+ ///
+ /// Populates HTLCOutputInCommitment.transaction_output_index in htlcs_with_aux.
+ ///
+ /// The generic T allows the caller to match the HTLC output index with auxiliary data.
+ /// This auxiliary data is not stored in this object.
+ ///
+ /// Only include HTLCs that are above the dust limit for the channel.
+ ///
+ /// (C-not exported) due to the generic though we likely should expose a version without
+ pub fn new_with_auxiliary_htlc_data<T>(commitment_number: u64, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, opt_anchors: bool, broadcaster_funding_key: PublicKey, countersignatory_funding_key: PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters) -> CommitmentTransaction {
+ // Sort outputs and populate output indices while keeping track of the auxiliary data
+ let (outputs, htlcs) = Self::internal_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, channel_parameters, opt_anchors, &broadcaster_funding_key, &countersignatory_funding_key).unwrap();
+
+ let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(commitment_number, channel_parameters);
+ let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
+ let txid = transaction.txid();
+ CommitmentTransaction {
+ commitment_number,
+ to_broadcaster_value_sat,
+ to_countersignatory_value_sat,
+ feerate_per_kw,
+ htlcs,
+ opt_anchors: if opt_anchors { Some(()) } else { None },
+ keys,
+ built: BuiltCommitmentTransaction {
+ transaction,
+ txid
+ },
+ }
+ }
+
+ fn internal_rebuild_transaction(&self, keys: &TxCreationKeys, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<BuiltCommitmentTransaction, ()> {
+ let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(self.commitment_number, channel_parameters);
+
+ let mut htlcs_with_aux = self.htlcs.iter().map(|h| (h.clone(), ())).collect();
+ let (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, channel_parameters, self.opt_anchors.is_some(), broadcaster_funding_key, countersignatory_funding_key)?;
+
+ let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
+ let txid = transaction.txid();
+ let built_transaction = BuiltCommitmentTransaction {
+ transaction,
+ txid
+ };
+ Ok(built_transaction)
+ }
+
+ fn make_transaction(obscured_commitment_transaction_number: u64, txins: Vec<TxIn>, outputs: Vec<TxOut>) -> Transaction {
+ Transaction {
+ version: 2,
+ lock_time: ((0x20 as u32) << 8 * 3) | ((obscured_commitment_transaction_number & 0xffffffu64) as u32),
+ input: txins,
+ output: outputs,
+ }
+ }
+
+ // This is used in two cases:
+ // - initial sorting of outputs / HTLCs in the constructor, in which case T is auxiliary data the
+ // caller needs to have sorted together with the HTLCs so it can keep track of the output index
+ // - building of a bitcoin transaction during a verify() call, in which case T is just ()
+ fn internal_build_outputs<T>(keys: &TxCreationKeys, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, opt_anchors: bool, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<(Vec<TxOut>, Vec<HTLCOutputInCommitment>), ()> {
+ let countersignatory_pubkeys = channel_parameters.countersignatory_pubkeys();
+ let contest_delay = channel_parameters.contest_delay();
+
+ let mut txouts: Vec<(TxOut, Option<&mut HTLCOutputInCommitment>)> = Vec::new();
+
+ if to_countersignatory_value_sat > 0 {
+ let script = if opt_anchors {
+ get_to_countersignatory_with_anchors_redeemscript(&countersignatory_pubkeys.payment_point).to_v0_p2wsh()
} else {
- ret.push(None);
+ get_p2wpkh_redeemscript(&countersignatory_pubkeys.payment_point)
+ };
+ txouts.push((
+ TxOut {
+ script_pubkey: script.clone(),
+ value: to_countersignatory_value_sat,
+ },
+ None,
+ ))
+ }
+
+ if to_broadcaster_value_sat > 0 {
+ let redeem_script = get_revokeable_redeemscript(
+ &keys.revocation_key,
+ contest_delay,
+ &keys.broadcaster_delayed_payment_key,
+ );
+ txouts.push((
+ TxOut {
+ script_pubkey: redeem_script.to_v0_p2wsh(),
+ value: to_broadcaster_value_sat,
+ },
+ None,
+ ));
+ }
+
+ if opt_anchors {
+ if to_broadcaster_value_sat > 0 || !htlcs_with_aux.is_empty() {
+ let anchor_script = get_anchor_redeemscript(broadcaster_funding_key);
+ txouts.push((
+ TxOut {
+ script_pubkey: anchor_script.to_v0_p2wsh(),
+ value: ANCHOR_OUTPUT_VALUE_SATOSHI,
+ },
+ None,
+ ));
+ }
+
+ if to_countersignatory_value_sat > 0 || !htlcs_with_aux.is_empty() {
+ let anchor_script = get_anchor_redeemscript(countersignatory_funding_key);
+ txouts.push((
+ TxOut {
+ script_pubkey: anchor_script.to_v0_p2wsh(),
+ value: ANCHOR_OUTPUT_VALUE_SATOSHI,
+ },
+ None,
+ ));
}
}
+
+ let mut htlcs = Vec::with_capacity(htlcs_with_aux.len());
+ for (htlc, _) in htlcs_with_aux {
+ let script = chan_utils::get_htlc_redeemscript(&htlc, opt_anchors, &keys);
+ let txout = TxOut {
+ script_pubkey: script.to_v0_p2wsh(),
+ value: htlc.amount_msat / 1000,
+ };
+ txouts.push((txout, Some(htlc)));
+ }
+
+ // Sort output in BIP-69 order (amount, scriptPubkey). Tie-breaks based on HTLC
+ // CLTV expiration height.
+ sort_outputs(&mut txouts, |a, b| {
+ if let &Some(ref a_htlcout) = a {
+ if let &Some(ref b_htlcout) = b {
+ a_htlcout.cltv_expiry.cmp(&b_htlcout.cltv_expiry)
+ // Note that due to hash collisions, we have to have a fallback comparison
+ // here for fuzztarget mode (otherwise at least chanmon_fail_consistency
+ // may fail)!
+ .then(a_htlcout.payment_hash.0.cmp(&b_htlcout.payment_hash.0))
+ // For non-HTLC outputs, if they're copying our SPK we don't really care if we
+ // close the channel due to mismatches - they're doing something dumb:
+ } else { cmp::Ordering::Equal }
+ } else { cmp::Ordering::Equal }
+ });
+
+ let mut outputs = Vec::with_capacity(txouts.len());
+ for (idx, out) in txouts.drain(..).enumerate() {
+ if let Some(htlc) = out.1 {
+ htlc.transaction_output_index = Some(idx as u32);
+ htlcs.push(htlc.clone());
+ }
+ outputs.push(out.0);
+ }
+ Ok((outputs, htlcs))
+ }
+
+ fn internal_build_inputs(commitment_number: u64, channel_parameters: &DirectedChannelTransactionParameters) -> (u64, Vec<TxIn>) {
+ let broadcaster_pubkeys = channel_parameters.broadcaster_pubkeys();
+ let countersignatory_pubkeys = channel_parameters.countersignatory_pubkeys();
+ let commitment_transaction_number_obscure_factor = get_commitment_transaction_number_obscure_factor(
+ &broadcaster_pubkeys.payment_point,
+ &countersignatory_pubkeys.payment_point,
+ channel_parameters.is_outbound(),
+ );
+
+ let obscured_commitment_transaction_number =
+ commitment_transaction_number_obscure_factor ^ (INITIAL_COMMITMENT_NUMBER - commitment_number);
+
+ let txins = {
+ let mut ins: Vec<TxIn> = Vec::new();
+ ins.push(TxIn {
+ previous_output: channel_parameters.funding_outpoint(),
+ script_sig: Script::new(),
+ sequence: ((0x80 as u32) << 8 * 3)
+ | ((obscured_commitment_transaction_number >> 3 * 8) as u32),
+ witness: Vec::new(),
+ });
+ ins
+ };
+ (obscured_commitment_transaction_number, txins)
+ }
+
+ /// The backwards-counting commitment number
+ pub fn commitment_number(&self) -> u64 {
+ self.commitment_number
+ }
+
+ /// The value to be sent to the broadcaster
+ pub fn to_broadcaster_value_sat(&self) -> u64 {
+ self.to_broadcaster_value_sat
+ }
+
+ /// The value to be sent to the counterparty
+ pub fn to_countersignatory_value_sat(&self) -> u64 {
+ self.to_countersignatory_value_sat
+ }
+
+ /// The feerate paid per 1000-weight-unit in this commitment transaction.
+ pub fn feerate_per_kw(&self) -> u32 {
+ self.feerate_per_kw
+ }
+
+ /// The non-dust HTLCs (direction, amt, height expiration, hash, transaction output index)
+ /// which were included in this commitment transaction in output order.
+ /// The transaction index is always populated.
+ ///
+ /// (C-not exported) as we cannot currently convert Vec references to/from C, though we should
+ /// expose a less effecient version which creates a Vec of references in the future.
+ pub fn htlcs(&self) -> &Vec<HTLCOutputInCommitment> {
+ &self.htlcs
+ }
+
+ /// Trust our pre-built transaction and derived transaction creation public keys.
+ ///
+ /// Applies a wrapper which allows access to these fields.
+ ///
+ /// This should only be used if you fully trust the builder of this object. It should not
+ /// be used by an external signer - instead use the verify function.
+ pub fn trust(&self) -> TrustedCommitmentTransaction {
+ TrustedCommitmentTransaction { inner: self }
+ }
+
+ /// Verify our pre-built transaction and derived transaction creation public keys.
+ ///
+ /// Applies a wrapper which allows access to these fields.
+ ///
+ /// An external validating signer must call this method before signing
+ /// or using the built transaction.
+ pub fn verify<T: secp256k1::Signing + secp256k1::Verification>(&self, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_keys: &ChannelPublicKeys, countersignatory_keys: &ChannelPublicKeys, secp_ctx: &Secp256k1<T>) -> Result<TrustedCommitmentTransaction, ()> {
+ // This is the only field of the key cache that we trust
+ let per_commitment_point = self.keys.per_commitment_point;
+ let keys = TxCreationKeys::from_channel_static_keys(&per_commitment_point, broadcaster_keys, countersignatory_keys, secp_ctx).unwrap();
+ if keys != self.keys {
+ return Err(());
+ }
+ let tx = self.internal_rebuild_transaction(&keys, channel_parameters, &broadcaster_keys.funding_pubkey, &countersignatory_keys.funding_pubkey)?;
+ if self.built.transaction != tx.transaction || self.built.txid != tx.txid {
+ return Err(());
+ }
+ Ok(TrustedCommitmentTransaction { inner: self })
+ }
+}
+
+/// A wrapper on CommitmentTransaction indicating that the derived fields (the built bitcoin
+/// transaction and the transaction creation keys) are trusted.
+///
+/// See trust() and verify() functions on CommitmentTransaction.
+///
+/// This structure implements Deref.
+pub struct TrustedCommitmentTransaction<'a> {
+ inner: &'a CommitmentTransaction,
+}
+
+impl<'a> Deref for TrustedCommitmentTransaction<'a> {
+ type Target = CommitmentTransaction;
+
+ fn deref(&self) -> &Self::Target { self.inner }
+}
+
+impl<'a> TrustedCommitmentTransaction<'a> {
+ /// The transaction ID of the built Bitcoin transaction
+ pub fn txid(&self) -> Txid {
+ self.inner.built.txid
+ }
+
+ /// The pre-built Bitcoin commitment transaction
+ pub fn built_transaction(&self) -> &BuiltCommitmentTransaction {
+ &self.inner.built
+ }
+
+ /// The pre-calculated transaction creation public keys.
+ pub fn keys(&self) -> &TxCreationKeys {
+ &self.inner.keys
+ }
+
+ /// Should anchors be used.
+ pub fn opt_anchors(&self) -> bool {
+ self.opt_anchors.is_some()
+ }
+
+ /// Get a signature for each HTLC which was included in the commitment transaction (ie for
+ /// which HTLCOutputInCommitment::transaction_output_index.is_some()).
+ ///
+ /// The returned Vec has one entry for each HTLC, and in the same order.
+ pub fn get_htlc_sigs<T: secp256k1::Signing>(&self, htlc_base_key: &SecretKey, channel_parameters: &DirectedChannelTransactionParameters, secp_ctx: &Secp256k1<T>) -> Result<Vec<Signature>, ()> {
+ let inner = self.inner;
+ let keys = &inner.keys;
+ let txid = inner.built.txid;
+ let mut ret = Vec::with_capacity(inner.htlcs.len());
+ let holder_htlc_key = derive_private_key(secp_ctx, &inner.keys.per_commitment_point, htlc_base_key).map_err(|_| ())?;
+
+ for this_htlc in inner.htlcs.iter() {
+ assert!(this_htlc.transaction_output_index.is_some());
+ let htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc, self.opt_anchors(), &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
+
+ let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, self.opt_anchors(), &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key);
+
+ let sighash = hash_to_message!(&bip143::SigHashCache::new(&htlc_tx).signature_hash(0, &htlc_redeemscript, this_htlc.amount_msat / 1000, SigHashType::All)[..]);
+ ret.push(secp_ctx.sign(&sighash, &holder_htlc_key));
+ }
Ok(ret)
}
/// Gets a signed HTLC transaction given a preimage (for !htlc.offered) and the holder HTLC transaction signature.
- pub(crate) fn get_signed_htlc_tx(&self, htlc_index: usize, signature: &Signature, preimage: &Option<PaymentPreimage>, counterparty_selected_contest_delay: u16) -> Transaction {
- let txid = self.txid();
- let this_htlc = &self.per_htlc[htlc_index];
- assert!(this_htlc.0.transaction_output_index.is_some());
+ pub(crate) fn get_signed_htlc_tx(&self, channel_parameters: &DirectedChannelTransactionParameters, htlc_index: usize, counterparty_signature: &Signature, signature: &Signature, preimage: &Option<PaymentPreimage>) -> Transaction {
+ let inner = self.inner;
+ let keys = &inner.keys;
+ let txid = inner.built.txid;
+ let this_htlc = &inner.htlcs[htlc_index];
+ assert!(this_htlc.transaction_output_index.is_some());
// if we don't have preimage for an HTLC-Success, we can't generate an HTLC transaction.
- if !this_htlc.0.offered && preimage.is_none() { unreachable!(); }
+ if !this_htlc.offered && preimage.is_none() { unreachable!(); }
// Further, we should never be provided the preimage for an HTLC-Timeout transaction.
- if this_htlc.0.offered && preimage.is_some() { unreachable!(); }
+ if this_htlc.offered && preimage.is_some() { unreachable!(); }
- let mut htlc_tx = build_htlc_transaction(&txid, self.feerate_per_kw, counterparty_selected_contest_delay, &this_htlc.0, &self.keys.broadcaster_delayed_payment_key, &self.keys.revocation_key);
- // Channel should have checked that we have a counterparty signature for this HTLC at
- // creation, and we should have a sensible htlc transaction:
- assert!(this_htlc.1.is_some());
+ let mut htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc, self.opt_anchors(), &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
- let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc.0, &self.keys.broadcaster_htlc_key, &self.keys.countersignatory_htlc_key, &self.keys.revocation_key);
+ let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, self.opt_anchors(), &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key);
// First push the multisig dummy, note that due to BIP147 (NULLDUMMY) it must be a zero-length element.
htlc_tx.input[0].witness.push(Vec::new());
- htlc_tx.input[0].witness.push(this_htlc.1.unwrap().serialize_der().to_vec());
+ htlc_tx.input[0].witness.push(counterparty_signature.serialize_der().to_vec());
htlc_tx.input[0].witness.push(signature.serialize_der().to_vec());
htlc_tx.input[0].witness[1].push(SigHashType::All as u8);
htlc_tx.input[0].witness[2].push(SigHashType::All as u8);
- if this_htlc.0.offered {
+ if this_htlc.offered {
// Due to BIP146 (MINIMALIF) this must be a zero-length element to relay.
htlc_tx.input[0].witness.push(Vec::new());
} else {
htlc_tx
}
}
-impl PartialEq for HolderCommitmentTransaction {
- // We dont care whether we are signed in equality comparison
- fn eq(&self, o: &Self) -> bool {
- self.txid() == o.txid()
- }
-}
-impl Writeable for HolderCommitmentTransaction {
- fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
- if let Err(e) = self.unsigned_tx.consensus_encode(&mut WriterWriteAdaptor(writer)) {
- match e {
- encode::Error::Io(e) => return Err(e),
- _ => panic!("holder tx must have been well-formed!"),
- }
- }
- self.counterparty_sig.write(writer)?;
- self.holder_sig_first.write(writer)?;
- self.keys.write(writer)?;
- self.feerate_per_kw.write(writer)?;
- writer.write_all(&byte_utils::be64_to_array(self.per_htlc.len() as u64))?;
- for &(ref htlc, ref sig) in self.per_htlc.iter() {
- htlc.write(writer)?;
- sig.write(writer)?;
- }
- Ok(())
+
+/// Commitment transaction numbers which appear in the transactions themselves are XOR'd with a
+/// shared secret first. This prevents on-chain observers from discovering how many commitment
+/// transactions occurred in a channel before it was closed.
+///
+/// This function gets the shared secret from relevant channel public keys and can be used to
+/// "decrypt" the commitment transaction number given a commitment transaction on-chain.
+pub fn get_commitment_transaction_number_obscure_factor(
+ broadcaster_payment_basepoint: &PublicKey,
+ countersignatory_payment_basepoint: &PublicKey,
+ outbound_from_broadcaster: bool,
+) -> u64 {
+ let mut sha = Sha256::engine();
+
+ if outbound_from_broadcaster {
+ sha.input(&broadcaster_payment_basepoint.serialize());
+ sha.input(&countersignatory_payment_basepoint.serialize());
+ } else {
+ sha.input(&countersignatory_payment_basepoint.serialize());
+ sha.input(&broadcaster_payment_basepoint.serialize());
}
+ let res = Sha256::from_engine(sha).into_inner();
+
+ ((res[26] as u64) << 5 * 8)
+ | ((res[27] as u64) << 4 * 8)
+ | ((res[28] as u64) << 3 * 8)
+ | ((res[29] as u64) << 2 * 8)
+ | ((res[30] as u64) << 1 * 8)
+ | ((res[31] as u64) << 0 * 8)
}
-impl Readable for HolderCommitmentTransaction {
- fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
- let unsigned_tx = match Transaction::consensus_decode(reader.by_ref()) {
- Ok(tx) => tx,
- Err(e) => match e {
- encode::Error::Io(ioe) => return Err(DecodeError::Io(ioe)),
- _ => return Err(DecodeError::InvalidValue),
- },
- };
- let counterparty_sig = Readable::read(reader)?;
- let holder_sig_first = Readable::read(reader)?;
- let keys = Readable::read(reader)?;
- let feerate_per_kw = Readable::read(reader)?;
- let htlcs_count: u64 = Readable::read(reader)?;
- let mut per_htlc = Vec::with_capacity(cmp::min(htlcs_count as usize, MAX_ALLOC_SIZE / mem::size_of::<(HTLCOutputInCommitment, Option<Signature>)>()));
- for _ in 0..htlcs_count {
- let htlc: HTLCOutputInCommitment = Readable::read(reader)?;
- let sigs = Readable::read(reader)?;
- per_htlc.push((htlc, sigs));
- }
- if unsigned_tx.input.len() != 1 {
- // Ensure tx didn't hit the 0-input ambiguity case.
- return Err(DecodeError::InvalidValue);
- }
- Ok(Self {
- unsigned_tx,
- counterparty_sig,
- holder_sig_first,
- keys,
- feerate_per_kw,
- per_htlc,
- })
- }
+fn get_p2wpkh_redeemscript(key: &PublicKey) -> Script {
+ Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0)
+ .push_slice(&WPubkeyHash::hash(&key.serialize())[..])
+ .into_script()
}
#[cfg(test)]
mod tests {
use super::CounterpartyCommitmentSecrets;
- use hex;
+ use ::{hex, chain};
+ use prelude::*;
+ use ln::chan_utils::{get_htlc_redeemscript, get_to_countersignatory_with_anchors_redeemscript, get_p2wpkh_redeemscript, CommitmentTransaction, TxCreationKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, HTLCOutputInCommitment};
+ use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1};
+ use util::test_utils;
+ use chain::keysinterface::{KeysInterface, BaseSign};
+ use bitcoin::Network;
+ use ln::PaymentHash;
+ use bitcoin::hashes::hex::ToHex;
+
+ #[test]
+ fn test_anchors() {
+ let secp_ctx = Secp256k1::new();
+
+ let seed = [42; 32];
+ let network = Network::Testnet;
+ let keys_provider = test_utils::TestKeysInterface::new(&seed, network);
+ let signer = keys_provider.get_channel_signer(false, 3000);
+ let counterparty_signer = keys_provider.get_channel_signer(false, 3000);
+ let delayed_payment_base = &signer.pubkeys().delayed_payment_basepoint;
+ let per_commitment_secret = SecretKey::from_slice(&hex::decode("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100").unwrap()[..]).unwrap();
+ let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret);
+ let htlc_basepoint = &signer.pubkeys().htlc_basepoint;
+ let holder_pubkeys = signer.pubkeys();
+ let counterparty_pubkeys = counterparty_signer.pubkeys();
+ let keys = TxCreationKeys::derive_new(&secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint).unwrap();
+ let mut channel_parameters = ChannelTransactionParameters {
+ holder_pubkeys: holder_pubkeys.clone(),
+ holder_selected_contest_delay: 0,
+ is_outbound_from_holder: false,
+ counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }),
+ funding_outpoint: Some(chain::transaction::OutPoint { txid: Default::default(), index: 0 }),
+ opt_anchors: None
+ };
+
+ let mut htlcs_with_aux: Vec<(_, ())> = Vec::new();
+
+ // Generate broadcaster and counterparty outputs
+ let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
+ 0, 1000, 2000,
+ false,
+ holder_pubkeys.funding_pubkey,
+ counterparty_pubkeys.funding_pubkey,
+ keys.clone(), 1,
+ &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
+ );
+ assert_eq!(tx.built.transaction.output.len(), 2);
+ assert_eq!(tx.built.transaction.output[1].script_pubkey, get_p2wpkh_redeemscript(&counterparty_pubkeys.payment_point));
+
+ // Generate broadcaster and counterparty outputs as well as two anchors
+ let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
+ 0, 1000, 2000,
+ true,
+ holder_pubkeys.funding_pubkey,
+ counterparty_pubkeys.funding_pubkey,
+ keys.clone(), 1,
+ &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
+ );
+ assert_eq!(tx.built.transaction.output.len(), 4);
+ assert_eq!(tx.built.transaction.output[3].script_pubkey, get_to_countersignatory_with_anchors_redeemscript(&counterparty_pubkeys.payment_point).to_v0_p2wsh());
+
+ // Generate broadcaster output and anchor
+ let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
+ 0, 3000, 0,
+ true,
+ holder_pubkeys.funding_pubkey,
+ counterparty_pubkeys.funding_pubkey,
+ keys.clone(), 1,
+ &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
+ );
+ assert_eq!(tx.built.transaction.output.len(), 2);
+
+ // Generate counterparty output and anchor
+ let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
+ 0, 0, 3000,
+ true,
+ holder_pubkeys.funding_pubkey,
+ counterparty_pubkeys.funding_pubkey,
+ keys.clone(), 1,
+ &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
+ );
+ assert_eq!(tx.built.transaction.output.len(), 2);
+
+ let received_htlc = HTLCOutputInCommitment {
+ offered: false,
+ amount_msat: 400000,
+ cltv_expiry: 100,
+ payment_hash: PaymentHash([42; 32]),
+ transaction_output_index: None,
+ };
+
+ let offered_htlc = HTLCOutputInCommitment {
+ offered: true,
+ amount_msat: 600000,
+ cltv_expiry: 100,
+ payment_hash: PaymentHash([43; 32]),
+ transaction_output_index: None,
+ };
+
+ // Generate broadcaster output and received and offered HTLC outputs, w/o anchors
+ let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
+ 0, 3000, 0,
+ false,
+ holder_pubkeys.funding_pubkey,
+ counterparty_pubkeys.funding_pubkey,
+ keys.clone(), 1,
+ &mut vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())],
+ &channel_parameters.as_holder_broadcastable()
+ );
+ assert_eq!(tx.built.transaction.output.len(), 3);
+ assert_eq!(tx.built.transaction.output[0].script_pubkey, get_htlc_redeemscript(&received_htlc, false, &keys).to_v0_p2wsh());
+ assert_eq!(tx.built.transaction.output[1].script_pubkey, get_htlc_redeemscript(&offered_htlc, false, &keys).to_v0_p2wsh());
+ assert_eq!(get_htlc_redeemscript(&received_htlc, false, &keys).to_v0_p2wsh().to_hex(),
+ "002085cf52e41ba7c099a39df504e7b61f6de122971ceb53b06731876eaeb85e8dc5");
+ assert_eq!(get_htlc_redeemscript(&offered_htlc, false, &keys).to_v0_p2wsh().to_hex(),
+ "002049f0736bb335c61a04d2623a24df878a7592a3c51fa7258d41b2c85318265e73");
+
+ // Generate broadcaster output and received and offered HTLC outputs, with anchors
+ channel_parameters.opt_anchors = Some(());
+ let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
+ 0, 3000, 0,
+ true,
+ holder_pubkeys.funding_pubkey,
+ counterparty_pubkeys.funding_pubkey,
+ keys.clone(), 1,
+ &mut vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())],
+ &channel_parameters.as_holder_broadcastable()
+ );
+ assert_eq!(tx.built.transaction.output.len(), 5);
+ assert_eq!(tx.built.transaction.output[2].script_pubkey, get_htlc_redeemscript(&received_htlc, true, &keys).to_v0_p2wsh());
+ assert_eq!(tx.built.transaction.output[3].script_pubkey, get_htlc_redeemscript(&offered_htlc, true, &keys).to_v0_p2wsh());
+ assert_eq!(get_htlc_redeemscript(&received_htlc, true, &keys).to_v0_p2wsh().to_hex(),
+ "002067114123af3f95405bae4fd930fc95de03e3c86baaee8b2dd29b43dd26cf613c");
+ assert_eq!(get_htlc_redeemscript(&offered_htlc, true, &keys).to_v0_p2wsh().to_hex(),
+ "0020a06e3b0d4fcf704f2b9c41e16a70099e39989466c3142b8573a1154542f28f57");
+ }
#[test]
fn test_per_commitment_storage() {