diff --git a/Cargo.lock b/Cargo.lock index bffa4558..1523ed43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -374,6 +374,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hex-conservative" diff --git a/smite-ir/src/generators/funding_created.rs b/smite-ir/src/generators/funding_created.rs index eb05b2ae..7b9edd2d 100644 --- a/smite-ir/src/generators/funding_created.rs +++ b/smite-ir/src/generators/funding_created.rs @@ -20,6 +20,7 @@ impl Generator for FundingCreatedGenerator { fn generate(&self, builder: &mut ProgramBuilder, rng: &mut impl Rng) { // Funding and commitment transaction keys and parameters. let opener_funding_privkey = builder.pick_variable(VariableType::PrivateKey, rng); + let opener_htlc_basepoint_privkey = builder.pick_variable(VariableType::PrivateKey, rng); let opener_funding_pubkey = builder.append(Operation::DerivePoint, &[opener_funding_privkey]); let acceptor_funding_pubkey = builder.pick_variable(VariableType::Point, rng); @@ -44,6 +45,7 @@ impl Generator for FundingCreatedGenerator { &[ funding_transaction, opener_funding_privkey, + opener_htlc_basepoint_privkey, temporary_channel_id, ], ); diff --git a/smite-ir/src/generators/funding_flow.rs b/smite-ir/src/generators/funding_flow.rs index b07f6791..c9c382d9 100644 --- a/smite-ir/src/generators/funding_flow.rs +++ b/smite-ir/src/generators/funding_flow.rs @@ -25,8 +25,12 @@ impl Generator for FundingFlowGenerator { let funding_privkey = builder.generate_fresh(VariableType::PrivateKey, rng); let funding_pubkey = builder.append(Operation::DerivePoint, &[funding_privkey]); + // Generate a fresh HTLC basepoint key pair for the commitment transaction. + let htlc_basepoint_privkey = builder.generate_fresh(VariableType::PrivateKey, rng); + let htlc_basepoint = builder.append(Operation::DerivePoint, &[htlc_basepoint_privkey]); + // Build and send open_channel. - let open_channel = append_open_channel(builder, rng, funding_pubkey); + let open_channel = append_open_channel(builder, rng, funding_pubkey, htlc_basepoint); // Receive accept_channel. let accept_channel = builder.append( @@ -55,6 +59,7 @@ impl Generator for FundingFlowGenerator { &[ funding_transaction, funding_privkey, + htlc_basepoint_privkey, open_channel.temporary_channel_id, ], ); diff --git a/smite-ir/src/generators/open_channel.rs b/smite-ir/src/generators/open_channel.rs index 581071fe..efde1b13 100644 --- a/smite-ir/src/generators/open_channel.rs +++ b/smite-ir/src/generators/open_channel.rs @@ -88,6 +88,7 @@ pub fn append_open_channel( builder: &mut ProgramBuilder, rng: &mut impl Rng, funding_pubkey: usize, + htlc_basepoint: usize, ) -> OpenChannelVars { type Bounds = OpenChannelGenerator; @@ -95,7 +96,6 @@ pub fn append_open_channel( let revocation_basepoint = builder.generate_fresh(VariableType::Point, rng); let payment_basepoint = builder.generate_fresh(VariableType::Point, rng); let delayed_payment_basepoint = builder.generate_fresh(VariableType::Point, rng); - let htlc_basepoint = builder.generate_fresh(VariableType::Point, rng); let first_per_commitment_point = builder.generate_fresh(VariableType::Point, rng); // Bounds for the channel parameters, so generators are more likely to @@ -193,12 +193,13 @@ pub fn append_open_channel( impl Generator for OpenChannelGenerator { fn generate(&self, builder: &mut ProgramBuilder, rng: &mut impl Rng) { - // The funding public key is generated fresh to ensure it's distinct - // from the basepoints. + // The funding public key and HTLC basepoint are generated fresh to + // ensure they are distinct from the other basepoints. let funding_pubkey = builder.generate_fresh(VariableType::Point, rng); + let htlc_basepoint = builder.generate_fresh(VariableType::Point, rng); // Build and send open_channel. - let open_channel = append_open_channel(builder, rng, funding_pubkey); + let open_channel = append_open_channel(builder, rng, funding_pubkey, htlc_basepoint); // Receive accept_channel. builder.append( diff --git a/smite-ir/src/operation.rs b/smite-ir/src/operation.rs index 22a17899..2b369251 100644 --- a/smite-ir/src/operation.rs +++ b/smite-ir/src/operation.rs @@ -184,10 +184,11 @@ pub enum Operation { /// Build and send a `funding_created` message (BOLT 2, type 34). /// Produces a `SentFundingCreated` variable. /// - /// Inputs (3): + /// Inputs (4): /// 0: `funding_transaction` (`FundingTransaction`) /// 1: `opener_funding_privkey` (`PrivateKey`) - /// 2: `temporary_channel_id` (`ChannelId`) + /// 2: `opener_htlc_basepoint_privkey` (`PrivateKey`) + /// 3: `temporary_channel_id` (`ChannelId`) SendFundingCreated, /// Build and send a `channel_ready` message (BOLT 2, type 36). /// @@ -705,6 +706,7 @@ impl Operation { Self::SendFundingCreated => vec![ VariableType::FundingTransaction, // funding_transaction VariableType::PrivateKey, // opener_funding_privkey + VariableType::PrivateKey, // opener_htlc_basepoint_privkey VariableType::ChannelId, // temporary_channel_id ], Self::SendChannelReady { .. } => vec![ diff --git a/smite-ir/src/tests.rs b/smite-ir/src/tests.rs index 53fdd3cf..f52b6bb6 100644 --- a/smite-ir/src/tests.rs +++ b/smite-ir/src/tests.rs @@ -667,7 +667,7 @@ fn postcard_roundtrip() { }, Instruction { operation: Operation::SendFundingCreated, - inputs: vec![11, 0, 3], + inputs: vec![11, 0, 8, 3], }, ], }; @@ -829,6 +829,10 @@ fn displays_send_funding_created_recv_funding_signed_program() { inputs: vec![1, 1, 2, 3], }, // funding_created parameters. + Instruction { + operation: Operation::LoadPrivateKey(key(2)), + inputs: vec![], + }, Instruction { operation: Operation::LoadChannelId([0xbb; 32]), inputs: vec![], @@ -836,12 +840,12 @@ fn displays_send_funding_created_recv_funding_signed_program() { // Build and send funding_created. Instruction { operation: Operation::SendFundingCreated, - inputs: vec![4, 0, 5], + inputs: vec![4, 0, 5, 6], }, // receive funding_signed. Instruction { operation: Operation::RecvFundingSigned, - inputs: vec![6], + inputs: vec![7], }, ]; @@ -858,9 +862,10 @@ fn displays_send_funding_created_recv_funding_signed_program() { "v2 = LoadAmount(10000000)".into(), "v3 = LoadFeeratePerKw(15000)".into(), "v4 = CreateFundingTransaction(v1, v1, v2, v3)".into(), - format!("v5 = LoadChannelId(0x{b32})"), - "v6 = SendFundingCreated(v4, v0, v5)".into(), - "v7 = RecvFundingSigned(v6)".into(), + format!("v5 = LoadPrivateKey(0x{z31}02)"), + format!("v6 = LoadChannelId(0x{b32})"), + "v7 = SendFundingCreated(v4, v0, v5, v6)".into(), + "v8 = RecvFundingSigned(v7)".into(), ]; assert_eq!(lines.len(), expected.len(), "line count mismatch"); diff --git a/smite-scenarios/src/executor.rs b/smite-scenarios/src/executor.rs index 3ca0b58f..a3804e7f 100644 --- a/smite-scenarios/src/executor.rs +++ b/smite-scenarios/src/executor.rs @@ -797,7 +797,7 @@ fn build_open_channel(variables: &[Option], inputs: &[usize]) -> OpenC } } -/// Builds a `funding_created` message from 3 input variables. +/// Builds a `funding_created` message from 4 input variables. /// /// Channel parameters are read from the negotiated `open_channel` and /// `accept_channel` messages recorded in `negotiations`, ensuring the @@ -806,6 +806,7 @@ fn build_open_channel(variables: &[Option], inputs: &[usize]) -> OpenC /// /// If the negotiation for `temporary_channel_id` is incomplete, emits a /// `funding_created` with the derived outpoint and an all-zero signature. +#[allow(clippy::too_many_lines)] fn build_funding_created( variables: &[Option], inputs: &[usize], @@ -815,7 +816,8 @@ fn build_funding_created( ) -> Result { let funding_tx = resolve_funding_transaction(variables, inputs[0]); let opener_funding_privkey_bytes = resolve_private_key(variables, inputs[1]); - let temporary_channel_id = resolve_channel_id(variables, inputs[2]); + let opener_htlc_basepoint_privkey_bytes = resolve_private_key(variables, inputs[2]); + let temporary_channel_id = resolve_channel_id(variables, inputs[3]); let funding_outpoint = OutPoint { txid: funding_tx.tx.compute_txid(), @@ -849,12 +851,15 @@ fn build_funding_created( let opener_funding_privkey = SecretKey::from_slice(&opener_funding_privkey_bytes).expect("valid private key"); + let opener_htlc_basepoint_privkey = + SecretKey::from_slice(&opener_htlc_basepoint_privkey_bytes).expect("valid private key"); let opener = ChannelPartyConfig { funding_pubkey: open_channel.funding_pubkey, payment_basepoint: open_channel.payment_basepoint, revocation_basepoint: open_channel.revocation_basepoint, delayed_payment_basepoint: open_channel.delayed_payment_basepoint, + htlc_basepoint: open_channel.htlc_basepoint, dust_limit_satoshis: open_channel.dust_limit_satoshis, to_self_delay: open_channel.to_self_delay, }; @@ -863,6 +868,7 @@ fn build_funding_created( payment_basepoint: accept_channel.payment_basepoint, revocation_basepoint: accept_channel.revocation_basepoint, delayed_payment_basepoint: accept_channel.delayed_payment_basepoint, + htlc_basepoint: accept_channel.htlc_basepoint, dust_limit_satoshis: accept_channel.dust_limit_satoshis, to_self_delay: accept_channel.to_self_delay, }; @@ -884,8 +890,10 @@ fn build_funding_created( let holder = HolderIdentity { side: Side::Opener, funding_privkey: opener_funding_privkey, + htlc_basepoint_privkey: opener_htlc_basepoint_privkey, }; - let signature = config.sign_counterparty_commitment(&commitments, &holder); + let (signature, htlc_signatures) = config.sign_counterparty_commitment(&commitments, &holder); + assert_eq!(htlc_signatures, Vec::::new()); // There are no HTLCs in the initial commitment transaction. // Only track a new channel when this negotiation has not built a // `funding_created` yet. If it has, we are likely resending one for the diff --git a/smite-scenarios/src/executor/tests.rs b/smite-scenarios/src/executor/tests.rs index 0f666957..0ab6dd27 100644 --- a/smite-scenarios/src/executor/tests.rs +++ b/smite-scenarios/src/executor/tests.rs @@ -984,13 +984,15 @@ fn execute_send_funding_created_and_recv_funding_signed() { let holder = HolderIdentity { side: Side::Acceptor, funding_privkey: acceptor_funding_sk(), + htlc_basepoint_privkey: acceptor_htlc_basepoint_sk(), }; - assert!( - state - .config - .verify_counterparty_signature(&state.commitments, &holder, &fc.signature) - ); + assert!(state.config.verify_counterparty_signature( + &state.commitments, + &holder, + &fc.signature, + &[] + )); let pending = fx.negotiation(&TemporaryChannelId::new([0xbb; 32])); assert!(pending.funding_built); @@ -1058,6 +1060,7 @@ fn execute_send_funding_created_after_funding_built_does_not_track_channel() { &[ second_tx, funding.opener_privkey, + first.htlc_basepoint_privkey, first.temporary_channel_id, ], ); @@ -1503,7 +1506,12 @@ fn execute_recv_channel_ready_resend_with_new_alias() { let second_temp_chan_id = b.append(Operation::LoadChannelId(second_id.0), &[]); b.append( Operation::SendFundingCreated, - &[tx.tx, tx.opener_privkey, second_temp_chan_id], + &[ + tx.tx, + tx.opener_privkey, + tx.opener_privkey, + second_temp_chan_id, + ], ); b.append(Operation::MineBlocks(6), &[]); b.append(Operation::RecvChannelReady, &[]); diff --git a/smite-scenarios/src/executor/tests/harness.rs b/smite-scenarios/src/executor/tests/harness.rs index 6ef8fe6e..7795a86a 100644 --- a/smite-scenarios/src/executor/tests/harness.rs +++ b/smite-scenarios/src/executor/tests/harness.rs @@ -404,6 +404,18 @@ pub fn acceptor_funding_sk() -> SecretKey { .expect("valid secret key") } +/// The opener's htlc basepoint key for the funding flow. +pub fn opener_htlc_basepoint_sk() -> SecretKey { + SecretKey::from_str("1111111111111111111111111111111111111111111111111111111111111111") + .expect("valid secret key") +} + +/// The acceptor's htlc basepoint key for the funding flow. +pub fn acceptor_htlc_basepoint_sk() -> SecretKey { + SecretKey::from_str("4444444444444444444444444444444444444444444444444444444444444444") + .expect("valid secret key") +} + /// The outpoint of the funding transaction the funding-flow programs build. pub fn funding_outpoint() -> OutPoint { OutPoint { diff --git a/smite-scenarios/src/executor/tests/programs.rs b/smite-scenarios/src/executor/tests/programs.rs index 362ab313..c6838438 100644 --- a/smite-scenarios/src/executor/tests/programs.rs +++ b/smite-scenarios/src/executor/tests/programs.rs @@ -7,7 +7,10 @@ //! `raw_program` is an exception and doesn't use [`ProgramBuilder`] since its //! purpose is to create malformed program. -use super::harness::{PointSource, SampleOpenChannel, acceptor_funding_sk, opener_funding_sk}; +use super::harness::{ + PointSource, SampleOpenChannel, acceptor_funding_sk, opener_funding_sk, + opener_htlc_basepoint_sk, +}; use crate::executor::*; use smite_ir::Instruction; use smite_ir::builder::ProgramBuilder; @@ -248,6 +251,7 @@ pub fn create_funding_tx_with( #[derive(Clone, Copy)] pub struct SentFundingCreated { pub tx: FundingTxVars, + pub htlc_basepoint_privkey: usize, pub temporary_channel_id: usize, /// The `SendFundingCreated` result, an affine variable a single /// `RecvFundingSigned` may consume. @@ -274,14 +278,24 @@ pub fn send_funding_created_with( tx: FundingTxVars, signing_privkey: usize, ) -> SentFundingCreated { + let htlc_basepoint_privkey = b.append( + Operation::LoadPrivateKey(opener_htlc_basepoint_sk().secret_bytes()), + &[], + ); let temporary_channel_id = b.append(Operation::LoadChannelId([0xbb; 32]), &[]); let sent = b.append( Operation::SendFundingCreated, - &[tx.tx, signing_privkey, temporary_channel_id], + &[ + tx.tx, + signing_privkey, + htlc_basepoint_privkey, + temporary_channel_id, + ], ); SentFundingCreated { tx, + htlc_basepoint_privkey, temporary_channel_id, sent, } diff --git a/smite/Cargo.toml b/smite/Cargo.toml index b4361563..de7e42b4 100644 --- a/smite/Cargo.toml +++ b/smite/Cargo.toml @@ -35,3 +35,4 @@ hex = "0.4" [dev-dependencies] tempfile = "3" bitcoin = { workspace = true, features = ["serde"] } +hex = { version = "0.4", features = ["serde"] } diff --git a/smite/src/channel_tx.rs b/smite/src/channel_tx.rs index fc8b89fe..a24eaccd 100644 --- a/smite/src/channel_tx.rs +++ b/smite/src/channel_tx.rs @@ -8,6 +8,6 @@ mod funding; pub use commitment::{ ChannelCommitments, ChannelConfig, ChannelPartyConfig, ChannelState, CommitmentCost, - CommitmentError, CommitmentState, HolderIdentity, Side, + CommitmentError, CommitmentState, HolderIdentity, Htlc, Side, }; pub use funding::{FundingTransaction, InsufficientFunds, build_funding_transaction}; diff --git a/smite/src/channel_tx/commitment.rs b/smite/src/channel_tx/commitment.rs index abbdce20..8081fe17 100644 --- a/smite/src/channel_tx/commitment.rs +++ b/smite/src/channel_tx/commitment.rs @@ -4,6 +4,7 @@ use super::funding::build_funding_witness_script; use crate::bolt::Features; use bitcoin::absolute::LockTime; +use bitcoin::hashes::ripemd160::Hash as Ripemd160; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::opcodes::all as opcodes; @@ -13,7 +14,8 @@ use bitcoin::secp256k1::{Message, PublicKey, Scalar, Secp256k1, SecretKey}; use bitcoin::sighash::{EcdsaSighashType, SighashCache}; use bitcoin::transaction::Version; use bitcoin::{ - Amount, CompressedPublicKey, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness, + Amount, CompressedPublicKey, OutPoint, PubkeyHash, ScriptBuf, Sequence, Transaction, TxIn, + TxOut, Witness, }; /// Anchor output value in satoshis. @@ -25,6 +27,15 @@ const COMMITMENT_TX_BASE_WEIGHT_NON_ANCHOR: u64 = 724; /// Weight of an anchor commitment transaction without HTLCs. const COMMITMENT_TX_BASE_WEIGHT_ANCHOR: u64 = 1124; +/// Additional commitment weight per non-trimmed HTLC output. +const COMMITMENT_TX_WEIGHT_PER_HTLC: u64 = 172; + +/// Weight of an HTLC-timeout transaction on a non-anchor channel. +const HTLC_TIMEOUT_TX_WEIGHT_NON_ANCHOR: u64 = 663; + +/// Weight of an HTLC-success transaction on a non-anchor channel. +const HTLC_SUCCESS_TX_WEIGHT_NON_ANCHOR: u64 = 703; + /// Errors that can occur when constructing or validating commitment transactions. #[derive(Debug, thiserror::Error)] pub enum CommitmentError { @@ -35,10 +46,18 @@ pub enum CommitmentError { /// Push amount exceeds the total funding amount. #[error("push_msat exceeds funding_msat")] PushExceedsFunding, + + /// Adding an HTLC would underflow the offerer's balance. + #[error("htlc amount exceeds offerer's balance")] + HtlcExceedsBalance, + + /// No in-flight HTLC matched the given id and offerer. + #[error("htlc with the given id and offerer was not found")] + HtlcNotFound, } /// Identifies the channel participant relative to the funding flow. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq)] pub enum Side { Opener, Acceptor, @@ -50,6 +69,8 @@ pub struct HolderIdentity { pub side: Side, /// Holder's funding private key. pub funding_privkey: SecretKey, + /// Holder's HTLC basepoint private key. + pub htlc_basepoint_privkey: SecretKey, } /// Static public keys and channel parameters for one side of a channel (opener or acceptor). @@ -62,6 +83,8 @@ pub struct ChannelPartyConfig { pub revocation_basepoint: PublicKey, /// Delayed payment basepoint used to derive the time-locked `to_local` output key. pub delayed_payment_basepoint: PublicKey, + /// HTLC basepoint used to derive HTLC keys. + pub htlc_basepoint: PublicKey, /// Minimum output value below which outputs are trimmed as dust. pub dust_limit_satoshis: u64, /// CSV delay this party imposes on the other's `to_local` output. @@ -86,6 +109,25 @@ pub struct ChannelConfig { pub minimum_depth: u32, } +/// An in-flight HTLC that appears (subject to dust trimming) as an output in +/// the commitment transaction. +#[derive(Clone, Copy)] +pub struct Htlc { + /// HTLC ID, unique per channel and offering direction. + pub id: u64, + /// The party that offered this HTLC. + /// + /// Combined with the commitment owner, this determines whether the HTLC + /// is treated as an "offered" or "received" output. + pub offerer: Side, + /// HTLC amount in millisatoshis. + pub amount_msat: u64, + /// The expiry height of the HTLC. + pub cltv_expiry: u32, + /// `SHA256` of the payment preimage. + pub payment_hash: [u8; 32], +} + /// State of `local_side`'s commitment transaction. /// /// Each side has its own commitment state, so the commitment number, fee rate, @@ -109,8 +151,18 @@ pub struct CommitmentState { /// represented as separate outputs in the commitment transaction, so those /// values are already deducted from these balance values. pub acceptor_balance_msat: u64, - // TODO: When adding HTLC support, store pending HTLCs (offered/received) for both sides - // to correctly compute balances and construct HTLC outputs in the commitment transaction. + /// In-flight HTLCs offered in either direction. + pub htlcs: Vec, +} + +impl CommitmentState { + /// Returns a mutable reference to the given side's balance. + fn balance_msat_mut(&mut self, side: Side) -> &mut u64 { + match side { + Side::Opener => &mut self.opener_balance_msat, + Side::Acceptor => &mut self.acceptor_balance_msat, + } + } } /// State of both sides' commitments. @@ -130,12 +182,58 @@ pub struct CommitmentCost { pub anchor_cost_sat: u64, } -/// Per-commitment keys used when constructing a commitment transaction. +/// An HTLC output included in a commitment transaction after dust trimming and +/// output ordering have been finalized. +#[derive(Clone, Copy)] +struct HtlcOutputInCommitment { + /// Whether this is an offered or received HTLC output from the commitment + /// owner's perspective. + offered: bool, + /// The HTLC that this output corresponds to. + htlc: Htlc, + /// The output index of the HTLC in the commitment transaction. + vout: u32, +} + +/// Per-commitment keys used when constructing and signing a commitment +/// transaction. struct TxCreationKeys { + /// The per-commitment point used to derive the commitment-specific keys. + per_commitment_point: PublicKey, /// Local delayed payment pubkey. local_delayedpubkey: PublicKey, /// Revocation pubkey for this commitment. revocationpubkey: PublicKey, + /// Local per-commitment HTLC pubkey. + local_htlcpubkey: PublicKey, + /// Remote per-commitment HTLC pubkey. + remote_htlcpubkey: PublicKey, +} + +/// A fully built commitment transaction with the metadata needed to construct +/// and sign its second-stage HTLC transactions. +struct BuiltCommitmentTx { + /// Side whose commitment transaction this is. + local_side: Side, + /// Fee rate of the commitment transaction, also used to compute the fees + /// of its second-stage HTLC transactions. + feerate_per_kw: u32, + /// Per-commitment keys for the local side. + keys: TxCreationKeys, + /// The set of non-dust HTLCs included in the commitment. They must be sorted + /// in increasing output index order. + nondust_htlcs: Vec, + /// The assembled commitment transaction. + tx: Transaction, +} + +/// A second-stage HTLC transaction spending an HTLC output from a commitment +/// transaction. +struct BuiltHtlcTx { + /// The commitment transaction HTLC output being spent. + nondust_htlc: HtlcOutputInCommitment, + /// The HTLC-success or HTLC-timeout transaction. + tx: Transaction, } /// State of a single channel, including its static configuration, holder @@ -311,6 +409,7 @@ impl ChannelConfig { per_commitment_point, opener_balance_msat: to_opener_balance_msat, acceptor_balance_msat: to_acceptor_balance_msat, + htlcs: Vec::new(), }; Ok(ChannelCommitments { @@ -319,52 +418,85 @@ impl ChannelConfig { }) } - /// Builds the signature for the counterparty's commitment transaction. + /// Counts the non-dust HTLCs for the given commitment side. + #[must_use] + pub fn count_nondust_htlcs(&self, state: &CommitmentState, local_side: Side) -> usize { + state + .htlcs + .iter() + .filter(|htlc| { + !htlc.is_dust( + self.party(local_side).dust_limit_satoshis, + state.feerate_per_kw, + &self.channel_type, + local_side, + ) + }) + .count() + } + + /// Builds the signatures for the counterparty's commitment transaction: + /// the funding-input signature plus one signature per non-dust HTLC + /// output, in commitment-output order. #[must_use] pub fn sign_counterparty_commitment( &self, commitments: &ChannelCommitments, holder: &HolderIdentity, - ) -> Signature { + ) -> (Signature, Vec) { let state = commitments.state(holder.counterparty_side()); let commitment = self.build_commitment_tx(state); - self.sign_commitment_tx(&commitment, &holder.funding_privkey) + let htlc_txs = self.build_htlc_txs(&commitment); + let commitment_sig = self.sign_commitment_tx(&commitment, &holder.funding_privkey); + let htlc_sigs = self.sign_htlc_txs(&commitment, &htlc_txs, holder); + + (commitment_sig, htlc_sigs) } - /// Verifies the counterparty's signature on the holder's commitment - /// transaction. Returns `true` if the signature is valid. + /// Verifies the counterparty's signatures on the holder's commitment + /// transaction and all of its non-dust HTLC outputs. #[must_use] pub fn verify_counterparty_signature( &self, commitments: &ChannelCommitments, holder: &HolderIdentity, commitment_sig: &Signature, + htlc_sigs: &[Signature], ) -> bool { let state = commitments.state(holder.side); let commitment = self.build_commitment_tx(state); - self.verify_commitment_sig( + if !self.verify_commitment_sig( &commitment, &self.party(holder.counterparty_side()).funding_pubkey, commitment_sig, - ) + ) { + return false; + } + + let htlc_txs = self.build_htlc_txs(&commitment); + self.verify_htlc_sigs(&commitment, &htlc_txs, htlc_sigs) } - /// Builds the signature for the holder's commitment transaction. - /// Only used to exercise BOLT 3 test vectors. + /// Builds the signatures for the holder's own commitment transaction and + /// its HTLC outputs. Only used to exercise BOLT 3 test vectors. #[cfg(test)] fn sign_holder_commitment( &self, commitments: &ChannelCommitments, holder: &HolderIdentity, - ) -> Signature { + ) -> (Signature, Vec) { let state = commitments.state(holder.side); let commitment = self.build_commitment_tx(state); - self.sign_commitment_tx(&commitment, &holder.funding_privkey) + let htlc_txs = self.build_htlc_txs(&commitment); + let commitment_sig = self.sign_commitment_tx(&commitment, &holder.funding_privkey); + let htlc_sigs = self.sign_htlc_txs(&commitment, &htlc_txs, holder); + + (commitment_sig, htlc_sigs) } /// Builds the commitment transaction. The commitment format (legacy or /// anchor) is determined by the `channel_type`. - fn build_commitment_tx(&self, state: &CommitmentState) -> Transaction { + fn build_commitment_tx(&self, state: &CommitmentState) -> BuiltCommitmentTx { // Obscured commitment number. let obscuring_factor = compute_obscuring_factor( &self.opener.payment_basepoint, @@ -386,7 +518,7 @@ impl ChannelConfig { // Build the commitment transaction let keys = TxCreationKeys::derive(self, state); - let outputs = self.build_commitment_outputs(state, &keys); + let (outputs, nondust_htlcs) = self.build_commitment_outputs(state, &keys); // Witness is not included in the BIP 143 sighash, so we leave it empty. let input = TxIn { @@ -396,11 +528,19 @@ impl ChannelConfig { witness: Witness::new(), }; - Transaction { + let tx = Transaction { version: Version::TWO, lock_time: LockTime::from_consensus(locktime), input: vec![input], output: outputs, + }; + + BuiltCommitmentTx { + local_side: state.local_side, + feerate_per_kw: state.feerate_per_kw, + keys, + nondust_htlcs, + tx, } } @@ -425,7 +565,8 @@ impl ChannelConfig { sighash.to_byte_array() } - /// Builds the lexicographically sorted commitment outputs. + /// Builds the lexicographically sorted commitment outputs together with + /// the mapping from each non-dust HTLC to its output index. /// /// Outputs are built for the commitment side specified by `state`: /// the opener or the acceptor. @@ -433,13 +574,54 @@ impl ChannelConfig { &self, state: &CommitmentState, keys: &TxCreationKeys, - ) -> Vec { + ) -> (Vec, Vec) { let local_side = state.local_side; let anchor = self.channel_type.supports_feature(Features::OPTION_ANCHORS); let mut outputs: Vec = Vec::new(); + // Insert non-dust HTLC outputs, recording each one's output index. + let nondust_htlc_outputs = self.build_sorted_nondust_htlc_outputs(state, keys); + let nondust_htlc_count = nondust_htlc_outputs.len(); + let mut nondust_htlcs: Vec = Vec::with_capacity(nondust_htlc_count); + for (vout, (txout, htlc)) in nondust_htlc_outputs.into_iter().enumerate() { + nondust_htlcs.push(HtlcOutputInCommitment { + offered: htlc.is_offered(local_side), + htlc, + vout: u32::try_from(vout) + .expect("commitment cannot have more than u32::MAX outputs"), + }); + outputs.push(txout); + } + + // Insert the non-HTLC outputs, ordered by value, then by script pubkey. + let mut insert_non_htlc_output = |non_htlc_output: TxOut| { + let idx = outputs + .binary_search_by(|output| { + output + .value + .cmp(&non_htlc_output.value) + .then(output.script_pubkey.cmp(&non_htlc_output.script_pubkey)) + }) + .unwrap_or_else(|i| i); + + outputs.insert(idx, non_htlc_output); + + // Increment the transaction output indices of all the HTLCs that + // come after the output we just inserted. + nondust_htlcs + .iter_mut() + .rev() + .take_while(|nondust_htlc| { + nondust_htlc.vout + >= u32::try_from(idx) + .expect("commitment cannot have more than u32::MAX outputs") + }) + .for_each(|nondust_htlc| nondust_htlc.vout += 1); + }; + // Fee and balances. - let commitment_cost = CommitmentCost::new(state.feerate_per_kw, &self.channel_type); + let commitment_cost = + CommitmentCost::new(state.feerate_per_kw, &self.channel_type, nondust_htlc_count); let opener_balance = (state.opener_balance_msat / 1000).saturating_sub(commitment_cost.total_sat()); let acceptor_balance = state.acceptor_balance_msat / 1000; @@ -461,7 +643,7 @@ impl ChannelConfig { remote.to_self_delay, ); - outputs.push(TxOut { + insert_non_htlc_output(TxOut { value: Amount::from_sat(to_local_value), script_pubkey: to_local_spk, }); @@ -469,46 +651,90 @@ impl ChannelConfig { if has_to_remote { let to_remote_spk = build_to_remote_scriptpubkey(&remote.payment_basepoint, anchor); - outputs.push(TxOut { + insert_non_htlc_output(TxOut { value: Amount::from_sat(to_remote_value), script_pubkey: to_remote_spk, }); } if anchor { - if has_to_local { - outputs.push(TxOut { + if has_to_local || nondust_htlc_count > 0 { + insert_non_htlc_output(TxOut { value: Amount::from_sat(ANCHOR_OUTPUT_VALUE), script_pubkey: build_anchor_scriptpubkey(&local.funding_pubkey), }); } - if has_to_remote { - outputs.push(TxOut { + if has_to_remote || nondust_htlc_count > 0 { + insert_non_htlc_output(TxOut { value: Amount::from_sat(ANCHOR_OUTPUT_VALUE), script_pubkey: build_anchor_scriptpubkey(&remote.funding_pubkey), }); } } - // BOLT 3 output ordering: sort by (value, script_pubkey). - outputs.sort_by(|a, b| { - a.value - .cmp(&b.value) - .then_with(|| a.script_pubkey.as_bytes().cmp(b.script_pubkey.as_bytes())) + (outputs, nondust_htlcs) + } + + /// Builds the non-dust HTLC outputs for the commitment transaction, tagging + /// each output with its corresponding [`Htlc`] and returning them in BOLT 3 + /// commitment output order. + fn build_sorted_nondust_htlc_outputs( + &self, + state: &CommitmentState, + keys: &TxCreationKeys, + ) -> Vec<(TxOut, Htlc)> { + let local_side = state.local_side; + let anchor = self.channel_type.supports_feature(Features::OPTION_ANCHORS); + let dust_limit = self.party(local_side).dust_limit_satoshis; + + // Add non-dust HTLCs as commitment transaction outputs. + let mut outputs = Vec::new(); + for htlc in &state.htlcs { + if htlc.is_dust( + dust_limit, + state.feerate_per_kw, + &self.channel_type, + local_side, + ) { + continue; + } + + let htlc_witness_script = + build_htlc_witness_script(htlc, keys, htlc.is_offered(local_side), anchor); + let output = TxOut { + script_pubkey: htlc_witness_script.to_p2wsh(), + value: htlc.amount(), + }; + + outputs.push((output, *htlc)); + } + + // Sort into BOLT 3 transaction output order: by value, then by + // `scriptPubKey` bytes, then by `cltv_expiry` for HTLC outputs. + outputs.sort_by(|(a_txout, a_htlc), (b_txout, b_htlc)| { + a_txout + .value + .cmp(&b_txout.value) + .then_with(|| { + a_txout + .script_pubkey + .as_bytes() + .cmp(b_txout.script_pubkey.as_bytes()) + }) + .then_with(|| a_htlc.cltv_expiry.cmp(&b_htlc.cltv_expiry)) }); outputs } - /// Signs the commitment transaction using the local party's funding private - /// key. + /// Signs the commitment transaction with the holder's funding private key. fn sign_commitment_tx( &self, - commitment: &Transaction, + commitment: &BuiltCommitmentTx, funding_privkey: &SecretKey, ) -> Signature { - let sighash = self.build_commitment_sighash(commitment); + let sighash = self.build_commitment_sighash(&commitment.tx); sign(&sighash, funding_privkey) } @@ -516,13 +742,144 @@ impl ChannelConfig { /// public key. fn verify_commitment_sig( &self, - commitment: &Transaction, + commitment: &BuiltCommitmentTx, funding_pubkey: &PublicKey, commitment_sig: &Signature, ) -> bool { - let sighash = self.build_commitment_sighash(commitment); + let sighash = self.build_commitment_sighash(&commitment.tx); verify(&sighash, commitment_sig, funding_pubkey) } + + /// Builds the second-stage HTLC transactions that spend the non-dust HTLC + /// outputs of `commitment`, in commitment-output order. + fn build_htlc_txs(&self, commitment: &BuiltCommitmentTx) -> Vec { + let local_side = commitment.local_side; + let anchor = self.channel_type.supports_feature(Features::OPTION_ANCHORS); + let mut htlc_txs = Vec::new(); + + for &nondust_htlc in &commitment.nondust_htlcs { + // Spend the HTLC output of the commitment transaction. + let input = TxIn { + previous_output: OutPoint { + txid: commitment.tx.compute_txid(), + vout: nondust_htlc.vout, + }, + script_sig: ScriptBuf::new(), + sequence: Sequence(u32::from(anchor)), + // Witness is not included in the BIP 143 sighash, so we leave + // it empty. + witness: Witness::new(), + }; + + let second_stage_fee_sat = htlc_tx_fee_sat( + &self.channel_type, + commitment.feerate_per_kw, + nondust_htlc.offered, + ); + + // Spend the HTLC output to a revocable output identical to `to_local`. + let output = TxOut { + script_pubkey: build_revocable_scriptpubkey( + &commitment.keys.local_delayedpubkey, + &commitment.keys.revocationpubkey, + self.party(local_side.other()).to_self_delay, + ), + value: nondust_htlc.htlc.amount() - Amount::from_sat(second_stage_fee_sat), + }; + + let tx = Transaction { + version: Version::TWO, + lock_time: LockTime::from_consensus(if nondust_htlc.offered { + nondust_htlc.htlc.cltv_expiry + } else { + 0 + }), + input: vec![input], + output: vec![output], + }; + + htlc_txs.push(BuiltHtlcTx { nondust_htlc, tx }); + } + + htlc_txs + } + + /// Builds the sighash for a second-stage HTLC transaction of `commitment` + /// signed by `signer`. + fn build_htlc_sighash( + &self, + htlc_tx: &BuiltHtlcTx, + commitment: &BuiltCommitmentTx, + signer: Side, + ) -> [u8; 32] { + // HTLC output witness script. + let anchor = self.channel_type.supports_feature(Features::OPTION_ANCHORS); + let htlc_witness_script = build_htlc_witness_script( + &htlc_tx.nondust_htlc.htlc, + &commitment.keys, + htlc_tx.nondust_htlc.offered, + anchor, + ); + + let sighash_type = if anchor && signer != commitment.local_side { + EcdsaSighashType::SinglePlusAnyoneCanPay + } else { + EcdsaSighashType::All + }; + + // Compute the BIP143 sighash + let sighash = SighashCache::new(&htlc_tx.tx) + .p2wsh_signature_hash( + 0, + &htlc_witness_script, + htlc_tx.nondust_htlc.htlc.amount(), + sighash_type, + ) + .expect("input index 0 is always in bounds for a single input transaction"); + + sighash.to_byte_array() + } + + /// Signs each second-stage HTLC transaction of `commitment` with the + /// holder's per-commitment HTLC private key. + fn sign_htlc_txs( + &self, + commitment: &BuiltCommitmentTx, + htlc_txs: &[BuiltHtlcTx], + holder: &HolderIdentity, + ) -> Vec { + let htlc_privkey = derive_privkey( + &holder.htlc_basepoint_privkey, + &commitment.keys.per_commitment_point, + ); + + htlc_txs + .iter() + .map(|htlc_tx| { + let sighash = self.build_htlc_sighash(htlc_tx, commitment, holder.side); + sign(&sighash, &htlc_privkey) + }) + .collect() + } + + /// Verifies the counterparty's signatures on each second-stage HTLC + /// transaction of `commitment` against its per-commitment HTLC public key. + fn verify_htlc_sigs( + &self, + commitment: &BuiltCommitmentTx, + htlc_txs: &[BuiltHtlcTx], + htlc_sigs: &[Signature], + ) -> bool { + if htlc_sigs.len() != htlc_txs.len() { + return false; + } + + let signer = commitment.local_side.other(); + htlc_txs.iter().zip(htlc_sigs.iter()).all(|(htlc_tx, sig)| { + let sighash = self.build_htlc_sighash(htlc_tx, commitment, signer); + verify(&sighash, sig, &commitment.keys.remote_htlcpubkey) + }) + } } impl ChannelCommitments { @@ -533,14 +890,150 @@ impl ChannelCommitments { Side::Acceptor => &self.acceptor, } } + + /// Returns a mutable reference to the state of the given side's commitment. + fn state_mut(&mut self, side: Side) -> &mut CommitmentState { + match side { + Side::Opener => &mut self.opener, + Side::Acceptor => &mut self.acceptor, + } + } + + /// Adds `htlc` to the in-flight set of `side`'s commitment, debiting + /// its amount from the offerer's balance. + /// + /// # Errors + /// + /// Returns [`CommitmentError::HtlcExceedsBalance`] if the HTLC amount + /// would underflow the offerer's balance. + pub fn add_htlc(&mut self, side: Side, htlc: Htlc) -> Result<(), CommitmentError> { + let state = self.state_mut(side); + let offerer_balance = state.balance_msat_mut(htlc.offerer); + // According to the spec, the target should reject an `update_add_htlc` + // if it exceeds the offerer's balance, leaves the offerer below its + // channel reserve, or leaves the funder unable to cover the commitment + // fee. We only guard the balance here: if the HTLC exceeds it, the + // balance would underflow below zero, so we error out rather than build + // a commitment from a broken balance that would otherwise be masked as + // an invalid signature. + *offerer_balance = offerer_balance + .checked_sub(htlc.amount_msat) + .ok_or(CommitmentError::HtlcExceedsBalance)?; + state.htlcs.push(htlc); + Ok(()) + } + + /// Settles the in-flight HTLC that `offerer` added with the given `id` on + /// `side`'s commitment, removing it from the in-flight set and crediting + /// its amount to the receiver's balance. + /// + /// # Errors + /// + /// Returns [`CommitmentError::HtlcNotFound`] if no in-flight HTLC matches + /// `id` and `offerer`. + pub fn fulfill_htlc( + &mut self, + side: Side, + id: u64, + offerer: Side, + ) -> Result<(), CommitmentError> { + let state = self.state_mut(side); + // The target is currently the only side that sends HTLC resolutions, so + // a resolution for an unknown HTLC means the target is misbehaving; we + // surface it as `HtlcNotFound`. Once we send our own resolutions, we + // should skip this error for those, since we cannot shift the balance + // and the resulting broken commitment would otherwise be masked as an + // invalid signature. + let pos = state + .htlcs + .iter() + .position(|h| h.id == id && h.offerer == offerer) + .ok_or(CommitmentError::HtlcNotFound)?; + let htlc = state.htlcs.remove(pos); + *state.balance_msat_mut(htlc.offerer.other()) += htlc.amount_msat; + Ok(()) + } + + /// Fails the in-flight HTLC that `offerer` added with the given `id` on + /// `side`'s commitment, removing it from the in-flight set and refunding + /// its amount to the offerer's balance. + /// + /// # Errors + /// + /// Returns [`CommitmentError::HtlcNotFound`] if no in-flight HTLC matches + /// `id` and `offerer`. + pub fn fail_htlc(&mut self, side: Side, id: u64, offerer: Side) -> Result<(), CommitmentError> { + let state = self.state_mut(side); + // The target is currently the only side that sends HTLC resolutions, so + // a resolution for an unknown HTLC means the target is misbehaving; we + // surface it as `HtlcNotFound`. Once we send our own resolutions, we + // should skip this error for those, since we cannot shift the balance + // and the resulting broken commitment would otherwise be masked as an + // invalid signature. + let pos = state + .htlcs + .iter() + .position(|h| h.id == id && h.offerer == offerer) + .ok_or(CommitmentError::HtlcNotFound)?; + let htlc = state.htlcs.remove(pos); + *state.balance_msat_mut(htlc.offerer) += htlc.amount_msat; + Ok(()) + } + + /// Updates the fee rate for `side`'s commitment transaction. + pub fn update_fee(&mut self, side: Side, feerate_per_kw: u32) { + self.state_mut(side).feerate_per_kw = feerate_per_kw; + } + + /// Updates the per-commitment point for `side`'s commitment. + pub fn update_per_commitment_point(&mut self, side: Side, per_commitment_point: PublicKey) { + self.state_mut(side).per_commitment_point = per_commitment_point; + } + + /// Advances `side`'s commitment transaction number by one. + pub fn advance_commitment_number(&mut self, side: Side) { + self.state_mut(side).commitment_number += 1; + } +} + +impl Htlc { + /// Returns whether this HTLC is offered on the commitment owned by + /// `local_side` (otherwise it is received). + fn is_offered(&self, local_side: Side) -> bool { + self.offerer == local_side + } + + /// Returns whether this HTLC would be trimmed from the commitment + /// transaction due to dust limits. + fn is_dust( + &self, + dust_limit_satoshis: u64, + feerate_per_kw: u32, + channel_type: &Features, + local_side: Side, + ) -> bool { + let stage_fee = htlc_tx_fee_sat(channel_type, feerate_per_kw, self.is_offered(local_side)); + let amount_sat = self.amount_msat / 1000; + amount_sat < dust_limit_satoshis.saturating_add(stage_fee) + } + + /// Converts the HTLC amount from millisatoshis to satoshis. + #[must_use] + pub const fn amount(&self) -> Amount { + Amount::from_sat(self.amount_msat / 1000) + } } impl CommitmentCost { - /// Calculates the total cost of a commitment transaction. + /// Calculates the total cost of a commitment transaction with non-dust HTLCs. #[must_use] - pub fn new(feerate_per_kw: u32, channel_type: &Features) -> CommitmentCost { + pub fn new( + feerate_per_kw: u32, + channel_type: &Features, + nondust_htlc_count: usize, + ) -> CommitmentCost { CommitmentCost { - fee_sat: commit_tx_fee_sat(feerate_per_kw, channel_type), + fee_sat: commit_tx_fee_sat(feerate_per_kw, nondust_htlc_count, channel_type), anchor_cost_sat: total_anchors_sat(channel_type), } } @@ -561,6 +1054,7 @@ impl TxCreationKeys { let remote = config.party(local_side.other()); Self { + per_commitment_point, local_delayedpubkey: derive_pubkey( &local.delayed_payment_basepoint, &per_commitment_point, @@ -569,18 +1063,24 @@ impl TxCreationKeys { &remote.revocation_basepoint, &per_commitment_point, ), + local_htlcpubkey: derive_pubkey(&local.htlc_basepoint, &per_commitment_point), + remote_htlcpubkey: derive_pubkey(&remote.htlc_basepoint, &per_commitment_point), } } } -/// Get the fee cost of a commitment tx in satoshis. -fn commit_tx_fee_sat(feerate_per_kw: u32, channel_type: &Features) -> u64 { - let commitment_weight = if channel_type.supports_feature(Features::OPTION_ANCHORS) { +/// Get the fee cost of a commitment tx with a given number of HTLC outputs in +/// satoshis. +/// Note that `num_htlcs` should not include dust HTLCs. +fn commit_tx_fee_sat(feerate_per_kw: u32, num_htlcs: usize, channel_type: &Features) -> u64 { + let commitment_base_weight = if channel_type.supports_feature(Features::OPTION_ANCHORS) { COMMITMENT_TX_BASE_WEIGHT_ANCHOR } else { COMMITMENT_TX_BASE_WEIGHT_NON_ANCHOR }; + let commitment_weight = + commitment_base_weight + (num_htlcs as u64) * COMMITMENT_TX_WEIGHT_PER_HTLC; u64::from(feerate_per_kw) * commitment_weight / 1000 } @@ -593,6 +1093,20 @@ fn total_anchors_sat(channel_type: &Features) -> u64 { } } +/// Get the fee cost of a second-stage HTLC transaction in satoshis. +/// `is_offered` selects between the HTLC-timeout and HTLC-success weights. +fn htlc_tx_fee_sat(channel_type: &Features, feerate_per_kw: u32, is_offered: bool) -> u64 { + if channel_type.supports_feature(Features::OPTION_ANCHORS) { + return 0; + } + + if is_offered { + u64::from(feerate_per_kw) * HTLC_TIMEOUT_TX_WEIGHT_NON_ANCHOR / 1000 + } else { + u64::from(feerate_per_kw) * HTLC_SUCCESS_TX_WEIGHT_NON_ANCHOR / 1000 + } +} + /// Returns `SHA256(pubkey1 || pubkey2)`. /// /// Both public keys are serialized in compressed form before hashing. @@ -630,6 +1144,18 @@ fn derive_pubkey(basepoint: &PublicKey, per_commitment_point: &PublicKey) -> Pub .expect("point addition of two valid pubkeys cannot produce infinity") } +/// Derives a private key from a basepoint secret and a per-commitment point per +/// BOLT 3. +fn derive_privkey(basepoint_secret: &SecretKey, per_commitment_point: &PublicKey) -> SecretKey { + let secp = Secp256k1::new(); + let basepoint = basepoint_secret.public_key(&secp); + let tweak = hash_pubkeys(per_commitment_point, &basepoint); + let scalar = Scalar::from_be_bytes(tweak).expect("SHA256 output is a valid scalar"); + basepoint_secret + .add_tweak(&scalar) + .expect("derived HTLC privkey tweak must be valid") +} + /// Derives the `revocationpubkey` per BOLT 3. fn derive_revocation_pubkey( revocation_basepoint: &PublicKey, @@ -662,7 +1188,7 @@ fn derive_revocation_pubkey( } /// Builds the revocable P2WSH `script_pubkey` per BOLT 3. -/// Used by the `to_local` commitment output. +/// Used by the `to_local` commitment output and by 2nd-stage HTLC outputs. fn build_revocable_scriptpubkey( local_delayedpubkey: &PublicKey, revocationpubkey: &PublicKey, @@ -714,6 +1240,73 @@ fn build_anchor_scriptpubkey(funding_pubkey: &PublicKey) -> ScriptBuf { .to_p2wsh() } +/// Builds the HTLC output witness script per BOLT 3. +/// +/// `is_offered` selects between the offered and received HTLC scripts. +fn build_htlc_witness_script( + htlc: &Htlc, + keys: &TxCreationKeys, + is_offered: bool, + anchor: bool, +) -> ScriptBuf { + let payment_hash160 = Ripemd160::hash(&htlc.payment_hash[..]).to_byte_array(); + + let mut bldr = Builder::new() + .push_opcode(opcodes::OP_DUP) + .push_opcode(opcodes::OP_HASH160) + .push_slice(PubkeyHash::hash(&keys.revocationpubkey.serialize())) + .push_opcode(opcodes::OP_EQUAL) + .push_opcode(opcodes::OP_IF) + .push_opcode(opcodes::OP_CHECKSIG) + .push_opcode(opcodes::OP_ELSE) + .push_slice(keys.remote_htlcpubkey.serialize()) + .push_opcode(opcodes::OP_SWAP) + .push_opcode(opcodes::OP_SIZE) + .push_int(32) + .push_opcode(opcodes::OP_EQUAL); + + bldr = if is_offered { + bldr.push_opcode(opcodes::OP_NOTIF) + .push_opcode(opcodes::OP_DROP) + .push_int(2) + .push_opcode(opcodes::OP_SWAP) + .push_slice(keys.local_htlcpubkey.serialize()) + .push_int(2) + .push_opcode(opcodes::OP_CHECKMULTISIG) + .push_opcode(opcodes::OP_ELSE) + .push_opcode(opcodes::OP_HASH160) + .push_slice(payment_hash160) + .push_opcode(opcodes::OP_EQUALVERIFY) + .push_opcode(opcodes::OP_CHECKSIG) + .push_opcode(opcodes::OP_ENDIF) + } else { + bldr.push_opcode(opcodes::OP_IF) + .push_opcode(opcodes::OP_HASH160) + .push_slice(payment_hash160) + .push_opcode(opcodes::OP_EQUALVERIFY) + .push_int(2) + .push_opcode(opcodes::OP_SWAP) + .push_slice(keys.local_htlcpubkey.serialize()) + .push_int(2) + .push_opcode(opcodes::OP_CHECKMULTISIG) + .push_opcode(opcodes::OP_ELSE) + .push_opcode(opcodes::OP_DROP) + .push_int(i64::from(htlc.cltv_expiry)) + .push_opcode(opcodes::OP_CLTV) + .push_opcode(opcodes::OP_DROP) + .push_opcode(opcodes::OP_CHECKSIG) + .push_opcode(opcodes::OP_ENDIF) + }; + + if anchor { + bldr = bldr + .push_opcode(opcodes::OP_PUSHNUM_1) + .push_opcode(opcodes::OP_CSV) + .push_opcode(opcodes::OP_DROP); + } + bldr.push_opcode(opcodes::OP_ENDIF).into_script() +} + /// Signs a sighash with the given private key. fn sign(sighash: &[u8; 32], privkey: &SecretKey) -> Signature { let secp = Secp256k1::new(); diff --git a/smite/src/channel_tx/commitment/test_vectors/bolt3_appendix_c_legacy.json b/smite/src/channel_tx/commitment/test_vectors/bolt3_appendix_c_legacy.json index e709ca94..0e73eb06 100644 --- a/smite/src/channel_tx/commitment/test_vectors/bolt3_appendix_c_legacy.json +++ b/smite/src/channel_tx/commitment/test_vectors/bolt3_appendix_c_legacy.json @@ -8,18 +8,22 @@ "to_self_delay": 144, "opener": { "funding_privkey": "30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749", + "htlc_basepoint_privkey": "1111111111111111111111111111111111111111111111111111111111111111", "funding_pubkey": "023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb", "payment_basepoint": "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa", "revocation_basepoint": "02c6047f9441ed7d6d3045406e95c07cd85a0f5f0f3b9b3f3d5f9b1e5e4a7c4f09", "delayed_payment_basepoint": "023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1", + "htlc_basepoint": "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa", "per_commitment_point": "025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486" }, "acceptor": { "funding_privkey": "1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e13", + "htlc_basepoint_privkey": "4444444444444444444444444444444444444444444444444444444444444444", "funding_pubkey": "030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c1", "payment_basepoint": "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", "revocation_basepoint": "02466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27", "delayed_payment_basepoint": "02a1633caf7bf0b7d9e5c4b8a1d6f2e3c4b5a6978877665544332211ffeeddccbb", + "htlc_basepoint": "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", "per_commitment_point": "03b28f7c5a9d1e4f8c6a7b2d3e9f1048576a1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e" }, "tests": [ @@ -30,43 +34,766 @@ "to_opener_msat": 7000000000, "to_acceptor_msat": 3000000000, "local_signature": "30440220616210b2cc4d3afb601013c373bbd8aac54febd9f15400379a8cb65ce7deca60022034236c010991beb7ff770510561ae8dc885b8d38d1947248c38f2ae055647142", - "remote_signature": "3045022100c3127b33dcc741dd6b05b1e63cbd1a9a7d816f37af9b6756fa2376b056f032370220408b96279808fe57eb7e463710804cdf4f108388bc5cf722d8c848d2c7f9f3b0" + "remote_signature": "3045022100c3127b33dcc741dd6b05b1e63cbd1a9a7d816f37af9b6756fa2376b056f032370220408b96279808fe57eb7e463710804cdf4f108388bc5cf722d8c848d2c7f9f3b0", + "acceptor_num_htlcs": 0 + }, + { + "name": "commitment tx with all five HTLCs untrimmed (minimum feerate)", + "feerate_per_kw": 0, + "dust_limit_satoshis": 546, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], + "local_signature": "304402206fc2d1f10ea59951eefac0b4b7c396a3c3d87b71ff0b019796ef4535beaf36f902201765b0181e514d04f4c8ad75659d7037be26cdb3f8bb6f78fe61decef484c3ea", + "local_htlc_signatures": [ + "30440220636de5682ef0c5b61f124ec74e8aa2461a69777521d6998295dcea36bc3338110220165285594b23c50b28b82df200234566628a27bcd17f7f14404bd865354eb3ce", + "3045022100803159dee7935dba4a1d36a61055ce8fd62caa528573cc221ae288515405a252022029c59e7cffce374fe860100a4a63787e105c3cf5156d40b12dd53ff55ac8cf3f", + "3045022100a437cc2ce77400ecde441b3398fea3c3ad8bdad8132be818227fe3c5b8345989022069d45e7fa0ae551ec37240845e2c561ceb2567eacf3076a6a43a502d05865faa", + "304402203121d9b9c055f354304b016a36662ee99e1110d9501cb271b087ddb6f382c2c80220549882f3f3b78d9c492de47543cb9a697cecc493174726146536c5954dac7487", + "3045022100d9080f103cc92bac15ec42464a95f070c7fb6925014e673ee2ea1374d36a7f7502200c65294d22eb20d48564954d5afe04a385551919d8b2ddb4ae2459daaeee1d95" + ], + "remote_signature": "3044022009b048187705a8cbc9ad73adbe5af148c3d012e1f067961486c822c7af08158c022006d66f3704cfab3eb2dc49dae24e4aa22a6910fc9b424007583204e3621af2e5", + "remote_htlc_signatures": [ + "3045022100d9e29616b8f3959f1d3d7f7ce893ffedcdc407717d0de8e37d808c91d3a7c50d022078c3033f6d00095c8720a4bc943c1b45727818c082e4e3ddbc6d3116435b624b", + "30440220649fe8b20e67e46cbb0d09b4acea87dbec001b39b08dee7bdd0b1f03922a8640022037c462dff79df501cecfdb12ea7f4de91f99230bb544726f6e04527b1f896004", + "30440220770fc321e97a19f38985f2e7732dd9fe08d16a2efa4bcbc0429400a447faf49102204d40b417f3113e1b0944ae0986f517564ab4acd3d190503faf97a6e420d43352", + "304402207bcbf4f60a9829b05d2dbab84ed593e0291836be715dc7db6b72a64caf646af802201e489a5a84f7c5cc130398b841d138d031a5137ac8f4c49c770a4959dc3c1363", + "3044022076dca5cb81ba7e466e349b7128cdba216d4d01659e29b96025b9524aaf0d1899022060de85697b88b21c749702b7d2cfa7dfeaa1f472c8f1d7d9c23f2bf968464b87" + ], + "acceptor_num_htlcs": 5 + }, + { + "name": "commitment tx with seven outputs untrimmed (maximum feerate)", + "feerate_per_kw": 647, + "dust_limit_satoshis": 546, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], + "local_signature": "30450221009ec15c687898bb4da8b3a833e5ab8bfc51ec6e9202aaa8e66611edfd4a85ed1102203d7183e45078b9735c93450bc3415d3e5a8c576141a711ec6ddcb4a893926bb7", + "local_htlc_signatures": [ + "30440220344b0deb055230d01703e6c7acd45853c4af2328b49b5d8af4f88a060733406602202ea64f2a43d5751edfe75503cbc35a62e3141b5ed032fa03360faf4ca66f670b", + "30450221009e5e3822b0185c6799a95288c597b671d6cc69ab80f43740f00c6c3d0752bdda02206da947a74bd98f3175324dc56fdba86cc783703a120a6f0297537e60632f4c7f", + "3045022100fcfc47e36b712624677626cef3dc1d67f6583bd46926a6398fe6b00b0c9a37760220525788257b187fc775c6370d04eadf34d06f3650a63f8df851cee0ecb47a1673", + "3045022100e78211b8409afb7255ffe37337da87f38646f1faebbdd61bc1920d69e3ead67a02201a626305adfcd16bfb7e9340928d9b6305464eab4aa4c4a3af6646e9b9f69dee", + "3044022048762cf546bbfe474f1536365ea7c416e3c0389d60558bc9412cb148fb6ab68202207215d7083b75c96ff9d2b08c59c34e287b66820f530b486a9aa4cdd9c347d5b9" + ], + "remote_signature": "3045022100a135f9e8a5ed25f7277446c67956b00ce6f610ead2bdec2c2f686155b7814772022059f1f6e1a8b336a68efcc1af3fe4d422d4827332b5b067501b099c47b7b5b5ee", + "remote_htlc_signatures": [ + "30450221008437627f9ad84ac67052e2a414a4367b8556fd1f94d8b02590f89f50525cd33502205b9c21ff6e7fc864f2352746ad8ba59182510819acb644e25b8a12fc37bbf24f", + "304402205a67f92bf6845cf2892b48d874ac1daf88a36495cf8a06f93d83180d930a6f75022031da1621d95c3f335cc06a3056cf960199dae600b7cf89088f65fc53cdbef28c", + "30440220437e21766054a3eef7f65690c5bcfa9920babbc5af92b819f772f6ea96df6c7402207173622024bd97328cfb26c6665e25c2f5d67c319443ccdc60c903217005d8c8", + "304402207436e10737e4df499fc051686d3e11a5bb2310e4d1f1e691d287cef66514791202207cb58e71a6b7a42dd001b7e3ae672ea4f71ea3e1cd412b742e9124abb0739c64", + "30450221009acd6a827a76bfee50806178dfe0495cd4e1d9c58279c194c7b01520fe68cb8d022024d439047c368883e570997a7d40f0b430cb5a742f507965e7d3063ae3feccca" + ], + "acceptor_num_htlcs": 5 + }, + { + "name": "commitment tx with six outputs untrimmed (minimum feerate)", + "feerate_per_kw": 648, + "dust_limit_satoshis": 546, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], + "local_signature": "3045022100b15f72908ba3382a34ca5b32519240a22300cc6015b6f9418635fb41f3d01d8802207adb331b9ed1575383dca0f2355e86c173802feecf8298fbea53b9d4610583e9", + "local_htlc_signatures": [ + "304502210097e1873b57267730154595187a34949d3744f52933070c74757005e61ce2112e02204ecfba2aa42d4f14bdf8bad4206bb97217b702e6c433e0e1b0ce6587e6d46ec6", + "3044022019de73b00f1d818fb388e83b2c8c31f6bce35ac624e215bc12f88f9dc33edf48022006ff814bb9f700ee6abc3294e146fac3efd4f13f0005236b41c0a946ee00c9ae", + "3045022100bd0be6100c4fd8f102ec220e1b053e4c4e2ecca25615490150007b40d314dc3902201a1e0ea266965b43164d9e6576f58fa6726d42883dd1c3996d2925c2e2260796", + "3045022100bbfb9d0a946d420807c86e985d636cceb16e71c3694ed186316251a00cbd807202207773223f9a337e145f64673825be9b30d07ef1542c82188b264bedcf7cda78c6" + ], + "remote_signature": "304402203948f900a5506b8de36a4d8502f94f21dd84fd9c2314ab427d52feaa7a0a19f2022059b6a37a4adaa2c5419dc8aea63c6e2a2ec4c4bde46207f6dc1fcd22152fc6e5", + "remote_htlc_signatures": [ + "3045022100a031202f3be94678f0e998622ee95ebb6ada8da1e9a5110228b5e04a747351e4022010ca6a21e18314ed53cfaae3b1f51998552a61a468e596368829a50ce40110e0", + "304402202361012a634aee7835c5ecdd6413dcffa8f404b7e77364c792cff984e4ee71e90220715c5e90baa08daa45a7439b1ee4fa4843ed77b19c058240b69406606d384124", + "304402207e8e82cd71ed4febeb593732c260456836e97d81896153ecd2b3cf320ca6861702202dd4a30f68f98ced7cc56a36369ac1fdd978248c5ff4ed204fc00cc625532989", + "3044022024cd52e4198c8ae0e414a86d86b5a65ea7450f2eb4e783096736d93395eca5ce022078f0094745b45be4d4b2b04dd5978c9e66ba49109e5704403e84aaf5f387d6be" + ], + "acceptor_num_htlcs": 5 + }, + { + "name": "commitment tx with six outputs untrimmed (maximum feerate)", + "feerate_per_kw": 2069, + "dust_limit_satoshis": 546, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], + "local_signature": "3045022100ad9a9bbbb75d506ca3b716b336ee3cf975dd7834fcf129d7dd188146eb58a8b4022061a759ee417339f7fe2ea1e8deb83abb6a74db31a09b7648a932a639cda23e33", + "local_htlc_signatures": [ + "3045022100a637902a5d4c9ba9e7c472a225337d5aac9e2e3f6744f76e237132e7619ba0400220035c60d784a031c0d9f6df66b7eab8726a5c25397399ee4aa960842059eb3f9d", + "3045022100e57e46234f8782d3ff7aa593b4f7446fb5316c842e693dc63ee324fd49f6a1c302204a2f7b44c48bd26e1554422afae13153eb94b29d3687b733d18930615fb2db61", + "3044022068613fb1b98eb3aec7f44c5b115b12343c2f066c4277c82b5f873dfe68f37f50022028109b4650f3f528ca4bfe9a467aff2e3e43893b61b5159157119d5d95cf1c18", + "3045022100d315522e09e7d53d2a659a79cb67fef56d6c4bddf3f46df6772d0d20a7beb7c8022070bcc17e288607b6a72be0bd83368bb6d53488db266c1cdb4d72214e4f02ac33" + ], + "remote_signature": "304502210090b96a2498ce0c0f2fadbec2aab278fed54c1a7838df793ec4d2c78d96ec096202204fdd439c50f90d483baa7b68feeef4bd33bc277695405447bcd0bfb2ca34d7bc", + "remote_htlc_signatures": [ + "3045022100f33513ee38abf1c582876f921f8fddc06acff48e04515532a32d3938de938ffd02203aa308a2c1863b7d6fdf53159a1465bf2e115c13152546cc5d74483ceaa7f699", + "3045022100ce07682cf4b90093c22dc2d9ab2a77ad6803526b655ef857221cc96af5c9e0bf02200f501cee22e7a268af40b555d15a8237c9f36ad67ef1841daf9f6a0267b1e6df", + "3045022100e3e35492e55f82ec0bc2f317ffd7a486d1f7024330fe9743c3559fc39f32ef0c02203d1d4db651fc388a91d5ad8ecdd8e83673063bc8eefe27cfd8c189090e3a23e0", + "304402207475aeb0212ef9bf5130b60937817ad88c9a87976988ef1f323f026148cc4a850220739fea17ad3257dcad72e509c73eebe86bee30b178467b9fdab213d631b109df" + ], + "acceptor_num_htlcs": 4 + }, + { + "name": "commitment tx with five outputs untrimmed (minimum feerate)", + "feerate_per_kw": 2070, + "dust_limit_satoshis": 546, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], + "local_signature": "3044022001014419b5ba00e083ac4e0a85f19afc848aacac2d483b4b525d15e2ae5adbfe022015ebddad6ee1e72b47cb09f3e78459da5be01ccccd95dceca0e056a00cc773c1", + "local_htlc_signatures": [ + "30440220150b11069454da70caf2492ded9e0065c9a57f25ac2a4c52657b1d15b6c6ed85022068a38833b603c8892717206383611bad210f1cbb4b1f87ea29c6c65b9e1cb3e5", + "30450221009a6ed18e6873bc3644332a6ee21c152a5b102821865350df7a8c74451a51f9f2022050d801fb4895d7d7fbf452824c0168347f5c0cbe821cf6a97a63af5b8b2563c6", + "30440220408ad3009827a8fccf774cb285587686bfb2ed041f89a89453c311ce9c8ee0f902203c7392d9f8306d3a46522a66bd2723a7eb2628cb2d9b34d4c104f1766bf37502" + ], + "remote_signature": "304402204ca1ba260dee913d318271d86e10ca0f5883026fb5653155cff600fb40895223022037b145204b7054a40e08bb1fefbd826f827b40838d3e501423bcc57924bcb50c", + "remote_htlc_signatures": [ + "304402205f6b6d12d8d2529fb24f4445630566cf4abbd0f9330ab6c2bdb94222d6a2a0c502202f556258ae6f05b193749e4c541dfcc13b525a5422f6291f073f15617ba8579b", + "3045022100f960dfb1c9aee7ce1437efa65b523e399383e8149790e05d8fed27ff6e42fe0002202fe8613e062ffe0b0c518cc4101fba1c6de70f64a5bcc7ae663f2efae43b8546", + "3045022100ae5fc7717ae684bc1fcf9020854e5dbe9842c9e7472879ac06ff95ac2bb10e4e022057728ada4c00083a3e65493fb5d50a232165948a1a0f530ef63185c2c8c56504" + ], + "acceptor_num_htlcs": 3 + }, + { + "name": "commitment tx with five outputs untrimmed (maximum feerate)", + "feerate_per_kw": 2194, + "dust_limit_satoshis": 546, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], + "local_signature": "3044022072c2e2b1c899b2242656a537dde2892fa3801be0d6df0a87836c550137acde8302201654aa1974d37a829083c3ba15088689f30b56d6a4f6cb14c7bad0ee3116d398", + "local_htlc_signatures": [ + "304502210099c98c2edeeee6ec0fb5f3bea8b79bb016a2717afa9b5072370f34382de281d302206f5e2980a995e045cf90a547f0752a7ee99d48547bc135258fe7bc07e0154301", + "3045022100fd85bd7697b89c08ec12acc8ba89b23090637d83abd26ca37e01ae93e67c367302202b551fe69386116c47f984aab9c8dfd25d864dcde5d3389cfbef2447a85c4b77", + "30450221008a9f2ea24cd455c2b64c1472a5fa83865b0a5f49a62b661801e884cf2849af8302204d44180e50bf6adfcf1c1e581d75af91aba4e28681ce4a5ee5f3cbf65eca10f3" + ], + "remote_signature": "304402204bb3d6e279d71d9da414c82de42f1f954267c762b2e2eb8b76bc3be4ea07d4b0022014febc009c5edc8c3fc5d94015de163200f780046f1c293bfed8568f08b70fb3", + "remote_htlc_signatures": [ + "3045022100939726680351a7856c1bc386d4a1f422c7d29bd7b56afc139570f508474e6c40022023175a799ccf44c017fbaadb924c40b2a12115a5b7d0dfd3228df803a2de8450", + "3044022021bb883bf324553d085ba2e821cad80c28ef8b303dbead8f98e548783c02d1600220638f9ef2a9bba25869afc923f4b5dc38be3bb459f9efa5d869392d5f7779a4a0", + "3045022100c9e6f0454aa598b905a35e641a70cc9f67b5f38cc4b00843a041238c4a9f1c4a0220260a2822a62da97e44583e837245995ca2e36781769c52f19e498efbdcca262b" + ], + "acceptor_num_htlcs": 3 + }, + { + "name": "commitment tx with four outputs untrimmed (minimum feerate)", + "feerate_per_kw": 2195, + "dust_limit_satoshis": 546, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], + "local_signature": "3044022044d592025b610c0d678f65032e87035cdfe89d1598c522cc32524ae8172417c30220749fef9d5b2ae8cdd91ece442ba8809bc891efedae2291e578475f97715d1767", + "local_htlc_signatures": [ + "3045022100ecc8c6529d0b2316d046f0f0757c1e1c25a636db168ec4f3aa1b9278df685dc0022067ae6b65e936f1337091f7b18a15935b608c5f2cdddb2f892ed0babfdd376d76", + "3044022014d66f11f9cacf923807eba49542076c5fe5cccf252fb08fe98c78ef3ca6ab5402201b290dbe043cc512d9d78de074a5a129b8759bc6a6c546b190d120b690bd6e82" + ], + "remote_signature": "304402201a8c1b1f9671cd9e46c7323a104d7047cc48d3ee80d40d4512e0c72b8dc65666022066d7f9a2ce18c9eb22d2739ffcce05721c767f9b607622a31b6ea5793ddce403", + "remote_htlc_signatures": [ + "3045022100e57b845066a06ee7c2cbfc29eabffe52daa9bf6f6de760066d04df9f9b250e0002202ffb197f0e6e0a77a75a9aff27014bd3de83b7f748d7efef986abe655e1dd50e", + "3045022100d193b7ecccad8057571620a0b1ffa6c48e9483311723b59cf536043b20bc51550220546d4bd37b3b101ecda14f6c907af46ec391abce1cd9c7ce22b1a62b534f2f2a" + ], + "acceptor_num_htlcs": 2 + }, + { + "name": "commitment tx with four outputs untrimmed (maximum feerate)", + "feerate_per_kw": 3702, + "dust_limit_satoshis": 546, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], + "local_signature": "3045022100e5efb73c32d32da2d79702299b6317de6fb24a60476e3855926d78484dd1b3c802203557cb66a42c944ef06e00bcc4da35a5bcb2f185aab0f8e403e519e1d66aaf75", + "local_htlc_signatures": [ + "304402206e36c683ebf2cb16bcef3d5439cf8b53cd97280a365ed8acd7abb85a8ba5f21c02206e8621edfc2a5766cbc96eb67fd501127ff163eb6b85518a39f7d4974aef126f", + "304402207faad26678c8850e01b4a0696d60841f7305e1832b786110ee9075cb92ed14a30220516ef8ee5dfa80824ea28cbcec0dd95f8b847146257c16960db98507db15ffdc" + ], + "remote_signature": "304502210092a587aeb777f869e7ff0d7898ea619ee26a3dacd1f3672b945eea600be431100220077ee9eae3528d15251f2a52b607b189820e57a6ccfac8d1af502b132ee40169", + "remote_htlc_signatures": [ + "304402206fa54c11f98c3bae1e93df43fc7affeb05b476bf8060c03e29c377c69bc08e8b0220672701cce50d5c379ff45a5d2cfe48ac44973adb066ac32608e21221d869bb89", + "3044022057649739b0eb74d541ead0dfdb3d4b2c15aa192720031044c3434c67812e5ca902201e5ede42d960ae551707f4a6b34b09393cf4dee2418507daa022e3550dbb5817" + ], + "acceptor_num_htlcs": 1 + }, + { + "name": "commitment tx with three outputs untrimmed (minimum feerate)", + "feerate_per_kw": 3703, + "dust_limit_satoshis": 546, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], + "local_signature": "304402201b736d1773a124c745586217a75bed5f66c05716fbe8c7db4fdb3c3069741cdd02205083f39c321c1bcadfc8d97e3c791a66273d936abac0c6a2fde2ed46019508e1", + "local_htlc_signatures": [ + "3045022100988c143e2110067117d2321bdd4bd16ca1734c98b29290d129384af0962b634e02206c1b02478878c5f547018b833986578f90c3e9be669fe5788ad0072a55acbb05" + ], + "remote_signature": "3045022100b495d239772a237ff2cf354b1b11be152fd852704cb184e7356d13f2fb1e5e430220723db5cdb9cbd6ead7bfd3deb419cf41053a932418cbb22a67b581f40bc1f13e", + "remote_htlc_signatures": [ + "3045022100c34c61735f93f2e324cc873c3b248111ccf8f6db15d5969583757010d4ad2b4602207867bb919b2ddd6387873e425345c9b7fd18d1d66aba41f3607bc2896ef3c30a" + ], + "acceptor_num_htlcs": 1 + }, + { + "name": "commitment tx with three outputs untrimmed (maximum feerate)", + "feerate_per_kw": 4914, + "dust_limit_satoshis": 546, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], + "local_signature": "3045022100d72638bc6308b88bb6d45861aae83e5b9ff6e10986546e13bce769c70036e2620220320be7c6d66d22f30b9fcd52af66531505b1310ca3b848c19285b38d8a1a8c19", + "local_htlc_signatures": [ + "30440220585dee80fafa264beac535c3c0bb5838ac348b156fdc982f86adc08dfc9bfd250220130abb82f9f295cc9ef423dcfef772fde2acd85d9df48cc538981d26a10a9c10" + ], + "remote_signature": "3045022100b4b16d5f8cc9fc4c1aff48831e832a0d8990e133978a66e302c133550954a44d022073573ce127e2200d316f6b612803a5c0c97b8d20e1e44dbe2ac0dd2fb8c95244", + "remote_htlc_signatures": [ + "3045022100f43591c156038ba217756006bb3c55f7d113a325cdd7d9303c82115372858d68022016355b5aadf222bc8d12e426c75f4a03423917b2443a103eb2a498a3a2234374" + ], + "acceptor_num_htlcs": 1 }, { "name": "commitment tx with two outputs untrimmed (minimum feerate)", "feerate_per_kw": 4915, "dust_limit_satoshis": 546, - "to_opener_msat": 6988000000, - "to_acceptor_msat": 3000000000, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], "local_signature": "30450221008a953551f4d67cb4df3037207fc082ddaf6be84d417b0bd14c80aab66f1b01a402207508796dc75034b2dee876fe01dc05a08b019f3e5d689ac8842ade2f1befccf5", - "remote_signature": "304402203a286936e74870ca1459c700c71202af0381910a6bfab687ef494ef1bc3e02c902202506c362d0e3bee15e802aa729bf378e051644648253513f1c085b264cc2a720" + "remote_signature": "304402203a286936e74870ca1459c700c71202af0381910a6bfab687ef494ef1bc3e02c902202506c362d0e3bee15e802aa729bf378e051644648253513f1c085b264cc2a720", + "acceptor_num_htlcs": 1 }, { "name": "commitment tx with two outputs untrimmed (maximum feerate)", "feerate_per_kw": 9651180, "dust_limit_satoshis": 546, - "to_opener_msat": 6988000000, - "to_acceptor_msat": 3000000000, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], "local_signature": "3045022100e11b638c05c650c2f63a421d36ef8756c5ce82f2184278643520311cdf50aa200220259565fb9c8e4a87ccaf17f27a3b9ca4f20625754a0920d9c6c239d8156a11de", - "remote_signature": "304402200a8544eba1d216f5c5e530597665fa9bec56943c0f66d98fc3d028df52d84f7002201e45fa5c6bc3a506cc2553e7d1c0043a9811313fc39c954692c0d47cfce2bbd3" + "remote_signature": "304402200a8544eba1d216f5c5e530597665fa9bec56943c0f66d98fc3d028df52d84f7002201e45fa5c6bc3a506cc2553e7d1c0043a9811313fc39c954692c0d47cfce2bbd3", + "acceptor_num_htlcs": 0 }, { "name": "commitment tx with one output untrimmed (minimum feerate)", "feerate_per_kw": 9651181, "dust_limit_satoshis": 546, - "to_opener_msat": 6988000000, - "to_acceptor_msat": 3000000000, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], "local_signature": "304402207e8d51e0c570a5868a78414f4e0cbfaed1106b171b9581542c30718ee4eb95ba02203af84194c97adf98898c9afe2f2ed4a7f8dba05a2dfab28ac9d9c604aa49a379", - "remote_signature": "304402202ade0142008309eb376736575ad58d03e5b115499709c6db0b46e36ff394b492022037b63d78d66404d6504d4c4ac13be346f3d1802928a6d3ad95a6a944227161a2" + "remote_signature": "304402202ade0142008309eb376736575ad58d03e5b115499709c6db0b46e36ff394b492022037b63d78d66404d6504d4c4ac13be346f3d1802928a6d3ad95a6a944227161a2", + "acceptor_num_htlcs": 0 }, { "name": "commitment tx with fee greater than funder amount", "feerate_per_kw": 9651936, "dust_limit_satoshis": 546, - "to_opener_msat": 6988000000, - "to_acceptor_msat": 3000000000, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], "local_signature": "304402207e8d51e0c570a5868a78414f4e0cbfaed1106b171b9581542c30718ee4eb95ba02203af84194c97adf98898c9afe2f2ed4a7f8dba05a2dfab28ac9d9c604aa49a379", - "remote_signature": "304402202ade0142008309eb376736575ad58d03e5b115499709c6db0b46e36ff394b492022037b63d78d66404d6504d4c4ac13be346f3d1802928a6d3ad95a6a944227161a2" + "remote_signature": "304402202ade0142008309eb376736575ad58d03e5b115499709c6db0b46e36ff394b492022037b63d78d66404d6504d4c4ac13be346f3d1802928a6d3ad95a6a944227161a2", + "acceptor_num_htlcs": 0 + }, + { + "name": "commitment tx with 3 htlc outputs, 2 offered having the same amount and preimage", + "feerate_per_kw": 253, + "dust_limit_satoshis": 546, + "to_opener_msat": 6998000000, + "to_acceptor_msat": 3002000000, + "incoming_htlcs": [ + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + } + ], + "outgoing_htlcs": [ + { + "id": 5, + "amount_msat": 5000000, + "cltv_expiry": 506, + "payment_preimage": "0505050505050505050505050505050505050505050505050505050505050505" + }, + { + "id": 6, + "amount_msat": 5000001, + "cltv_expiry": 505, + "payment_preimage": "0505050505050505050505050505050505050505050505050505050505050505" + } + ], + "local_signature": "304402200d10bf5bc5397fc59d7188ae438d80c77575595a2d488e41bd6363a810cc8d72022012b57e714fbbfdf7a28c47d5b370cb8ac37c8545f596216e5b21e9b236ef457c", + "local_htlc_signatures": [ + "3044022017b90c65207522a907fb6a137f9dd528b3389465a8ae72308d9e1d564f512cf402204fc917b4f0e88604a3e994f85bfae7c7c1f9d9e9f78e8cd112e0889720d9405b", + "3045022100ee2e16b90930a479b13f8823a7f14b600198c838161160b9436ed086d3fc57e002202a66fa2324f342a17129949c640bfe934cbc73a869ba7c06aa25c5a3d0bfb53d", + "304402207157f452f2506d73c315192311893800cfb3cc235cc1185b1cfcc136b55230db022014be242dbc6c5da141fec4034e7f387f74d6ff1899453d72ba957467540e1ecb" + ], + "remote_signature": "304402207d0870964530f97b62497b11153c551dca0a1e226815ef0a336651158da0f82402200f5378beee0e77759147b8a0a284decd11bfd2bc55c8fafa41c134fe996d43c8", + "remote_htlc_signatures": [ + "3045022100b470fe12e5b7fea9eccb8cbff1972cea4f96758041898982a02bcc7f9d56d50b0220338a75b2afaab4ec00cdd2d9273c68c7581ff5a28bcbb40c4d138b81f1d45ce5", + "3045022100b575379f6d8743cb0087648f81cfd82d17a97fbf8f67e058c65ce8b9d25df9500220554a210d65b02d9f36c6adf0f639430ca8293196ba5089bf67cc3a9813b7b00a", + "30440220471c9f3ad92e49b13b7b8059f43ecf8f7887b0dccbb9fdb54bfe23d62a8ae332022024bd22fae0740e86a44228c35330da9526fd7306dffb2b9dc362d5e78abef7cc" + ], + "acceptor_num_htlcs": 3 } ] } \ No newline at end of file diff --git a/smite/src/channel_tx/commitment/test_vectors/bolt3_appendix_f_anchor.json b/smite/src/channel_tx/commitment/test_vectors/bolt3_appendix_f_anchor.json index 892c8cd5..d12e0737 100644 --- a/smite/src/channel_tx/commitment/test_vectors/bolt3_appendix_f_anchor.json +++ b/smite/src/channel_tx/commitment/test_vectors/bolt3_appendix_f_anchor.json @@ -8,18 +8,22 @@ "to_self_delay": 144, "opener": { "funding_privkey": "30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749", + "htlc_basepoint_privkey": "1111111111111111111111111111111111111111111111111111111111111111", "funding_pubkey": "023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb", "payment_basepoint": "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa", "revocation_basepoint": "02c6047f9441ed7d6d3045406e95c07cd85a0f5f0f3b9b3f3d5f9b1e5e4a7c4f09", "delayed_payment_basepoint": "023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1", + "htlc_basepoint": "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa", "per_commitment_point": "025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486" }, "acceptor": { "funding_privkey": "1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e13", + "htlc_basepoint_privkey": "4444444444444444444444444444444444444444444444444444444444444444", "funding_pubkey": "030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c1", "payment_basepoint": "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", "revocation_basepoint": "02466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27", "delayed_payment_basepoint": "02a1633caf7bf0b7d9e5c4b8a1d6f2e3c4b5a6978877665544332211ffeeddccbb", + "htlc_basepoint": "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", "per_commitment_point": "03b28f7c5a9d1e4f8c6a7b2d3e9f1048576a1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e" }, "tests": [ @@ -30,7 +34,8 @@ "to_opener_msat": 7000000000, "to_acceptor_msat": 3000000000, "local_signature": "30450221008266ac6db5ea71aac3c95d97b0e172ff596844851a3216eb88382a8dddfd33d2022050e240974cfd5d708708b4365574517c18e7ae535ef732a3484d43d0d82be9f7", - "remote_signature": "3045022100f89034eba16b2be0e5581f750a0a6309192b75cce0f202f0ee2b4ec0cc394850022076c65dc507fe42276152b7a3d90e961e678adbe966e916ecfe85e64d430e75f3" + "remote_signature": "3045022100f89034eba16b2be0e5581f750a0a6309192b75cce0f202f0ee2b4ec0cc394850022076c65dc507fe42276152b7a3d90e961e678adbe966e916ecfe85e64d430e75f3", + "acceptor_num_htlcs": 0 }, { "name": "simple commitment tx with no HTLCs and single anchor", @@ -39,25 +44,354 @@ "to_opener_msat": 10000000000, "to_acceptor_msat": 0, "local_signature": "3044022007cf6b405e9c9b4f527b0ecad9d8bb661fabb8b12abf7d1c0b3ad1855db3ed490220616d5c1eeadccc63bd775a131149455d62d95a42c2a1b01cc7821fc42dce7778", - "remote_signature": "30440220655bf909fb6fa81d086f1336ac72c97906dce29d1b166e305c99152d810e26e1022051f577faa46412c46707aaac46b65d50053550a66334e00a44af2706f27a8658" + "remote_signature": "30440220655bf909fb6fa81d086f1336ac72c97906dce29d1b166e305c99152d810e26e1022051f577faa46412c46707aaac46b65d50053550a66334e00a44af2706f27a8658", + "acceptor_num_htlcs": 0 + }, + { + "name": "commitment tx with seven outputs untrimmed", + "feerate_per_kw": 644, + "dust_limit_satoshis": 546, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], + "local_signature": "3045022100ef82a405364bfc4007e63a7cc82925a513d79065bdbc216d60b6a4223a323f8a02200716730b8561f3c6d362eaf47f202e99fb30d0557b61b92b5f9134f8e2de3681", + "local_htlc_signatures": [ + "3044022036f77f88b49bd03dc16d3a015efcc7acffc2a93b213324035d77a716f79013ff02203c218eb882a40402a8bb05dbb751a442345a4e56307be76b214b6d4db9ed3c92", + "3044022026739b1adbfa34c485bf0e5a19e0cf7532f64545bcc5c95b95643ccc2d351e1902201743bb1b34f00e031cc15f6eff0f8ab764508d2681ff965ba62e76e540bca1e8", + "30440220191ba44e57b1601a59d99f85971d4801b286d428de275487c87ceeb8df1a4811022002215875d92833df0c9615c9096cf97152f87139ed9f82718bbb5b8b3d312524", + "3045022100f03047e38bc0aae2d80d53424b8c1d1b8139120e2bf09ad31a2803978745e6e102205b74c0eef0b472710b98c77e619ee9d0cc47a9dd786f4f214a564f44d79f9b9a", + "3044022022604660234aef9bd21284598ec50f070ac82a3a0152e0af5e98a02cd6e8976f022042b0b9112ee00806b856dff6de52a82c98b036a4fe14bb5fd2926725e2fc8191" + ], + "remote_signature": "3045022100e0106830467a558c07544a3de7715610c1147062e7d091deeebe8b5c661cda9402202ad049c1a6d04834317a78483f723c205c9f638d17222aafc620800cc1b6ae35", + "remote_htlc_signatures": [ + "30440220746dc89a593e1b50915db63359b50c3c404f8324f78075c87708c866ccefda2502202a11062012dc8607b17e8c46ea4eefdfcc6b89a35440de99432ba12788d7bb17", + "3045022100a847bc3b8cf2441013725ae32dc589c419835d069afd6d7f3d9834d8be7cea6e02204ce9d35ec7f0788da80e4d62e810c4ccd13617e58fa10832e38fb7ae87bdf338", + "3044022035977f2aa6d6ae12f1dae3366440faa17558e9c88c3188bfc8df7e276c6c65410220659f1f9070c725d9d46e43b99a36fc6d711d36069704add2d57fc1fa2818cf12", + "3044022002f3ff9a31270092c214d3d5b8b4f826599404bba64b87f7536ef6324d41551b022079dc4cb25f7ecd84b49f5cce03eae7e2655b59f39d543765daef0d4f11c93fa2", + "30440220547937564288f64fb3e3c945a1348b912471f0c1c4cc7dc8ceca15a4cbd299b6022053c4f8e30832b13dfbe31e4091e313428625e0b5ac61eecba93f8f11c1e26225" + ], + "acceptor_num_htlcs": 5 + }, + { + "name": "commitment tx with six outputs untrimmed (minimum dust limit)", + "feerate_per_kw": 645, + "dust_limit_satoshis": 1001, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], + "local_signature": "3045022100d57697c707b6f6d053febf24b98e8989f186eea42e37e9e91663ec2c70bb8f70022079b0715a472118f262f43016a674f59c015d9cafccec885968e76d9d9c5d0051", + "local_htlc_signatures": [ + "3045022100b7c49846466b13b190ff739bbe3005c105482fc55539e55b1c561f76b6982b6c02200e5c35808619cf543c8405cff9fedd25f333a4a2f6f6d5e8af8150090c40ef09", + "3045022100d29330f24db213b262068706099b39c15fa7e070c3fcdf8836c09723fc4d365602203ce57d01e9f28601e461a0b5c4a50119b270bde8b70148d133a6849c70b115ac", + "304402202d4ce515cd9000ec37575972d70b8d24f73909fb7012e8ebd8c2066ef6fe187902202830b53e64ea565fecd0f398100691da6bb2a5cf9bb0d1926f1d71d05828a11e", + "3045022100b20cd63e0587d1711beaebda4730775c4ac8b8b2ec78fe18a0c44c3f168c25230220079abb7fc4924e2fca5950842e5b9e416735585026914570078c4ef62f286226" + ], + "remote_signature": "3044022025d97466c8049e955a5afce28e322f4b34d2561118e52332fb400f9b908cc0a402205dc6fba3a0d67ee142c428c535580cd1f2ff42e2f89b47e0c8a01847caffc312", + "remote_htlc_signatures": [ + "3045022100e04d160a326432659fe9fb127304c1d348dfeaba840081bdc57d8efd902a48d8022008a824e7cf5492b97e4d9e03c06a09f822775a44f6b5b2533a2088904abfc282", + "3045022100fbdc3c367ce3bf30796025cc590ee1f2ce0e72ae1ac19f5986d6d0a4fc76211f02207e45ae9267e8e820d188569604f71d1abd11bd385d58853dd7dc034cdb3e9a6e", + "3044022066c5ef625cee3ddd2bc7b6bfb354b5834cf1cc6d52dd972fb41b7b225437ae4a022066cb85647df65c6b87a54e416dcdcca778a776c36a9643d2b5dc793c9b29f4c1", + "3044022022c7e11595c53ee89a57ca76baf0aed730da035952d6ab3fe6459f5eff3b337a022075e10cc5f5fd724a35ce4087a5d03cd616698626c69814032132b50bb97dc615" + ], + "acceptor_num_htlcs": 4 + }, + { + "name": "commitment tx with four outputs untrimmed (minimum dust limit)", + "feerate_per_kw": 2185, + "dust_limit_satoshis": 2001, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], + "local_signature": "3045022100cd8479cfe1edb1e5a1d487391e0451a469c7171e51e680183f19eb4321f20e9b02204eab7d5a6384b1b08e03baa6e4d9748dfd2b5ab2bae7e39604a0d0055bbffdd5", + "local_htlc_signatures": [ + "30440220669de9ca7910eff65a7773ebd14a9fc371fe88cde5b8e2a81609d85c87ac939b02201ac29472fa4067322e92d75b624942d60be5050139b20bb363db75be79eb946f", + "3045022100e3104ed8b239f8019e5f0a1a73d7782a94a8c36e7984f476c3a0b3cb0e62e27902207e3d52884600985f8a2098e53a5c30dd6a5e857733acfaa07ab2162421ed2688" + ], + "remote_signature": "3044022040f63a16148cf35c8d3d41827f5ae7f7c3746885bb64d4d1b895892a83812b3e02202fcf95c2bf02c466163b3fa3ced6a24926fbb4035095a96842ef516e86ba54c0", + "remote_htlc_signatures": [ + "304402206870514a72ad6e723ff7f1e0370d7a33c1cd2a0b9272674143ebaf6a1d02dee102205bd953c34faf5e7322e9a1c0103581cb090280fda4f1039ee8552668afa90ebb", + "3045022100949e8dd938da56445b1cdfdebe1b7efea086edd05d89910d205a1e2e033ce47102202cbd68b5262ab144d9ec12653f87dfb0bb6bd05d1f58ae1e523f028eaefd7271" + ], + "acceptor_num_htlcs": 2 + }, + { + "name": "commitment tx with three outputs untrimmed (minimum dust limit)", + "feerate_per_kw": 3687, + "dust_limit_satoshis": 3001, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], + "local_signature": "3045022100c970799bcb33f43179eb43b3378a0a61991cf2923f69b36ef12548c3df0e6d500220413dc27d2e39ee583093adfcb7799be680141738babb31cc7b0669a777a31f5d", + "local_htlc_signatures": [ + "3045022100af7a8b7c7ff2080c68995254cb66d64d9954edcc5baac3bb4f27ed2d29aaa6120220421c27da7a60574a9263f271e0f3bd34594ec6011095190022b3b54596ea03de" + ], + "remote_signature": "3045022100ad6c71569856b2d7ff42e838b4abe74a713426b37f22fa667a195a4c88908c6902202b37272b02a42dc6d9f4f82cab3eaf84ac882d9ed762859e1e75455c2c228377", + "remote_htlc_signatures": [ + "3044022017b558a3cf5f0cb94269e2e927b29ed22bd2416abb8a7ce6de4d1256f359b93602202e9ca2b1a23ea3e69f433c704e327739e219804b8c188b1d52f74fd5a9de954c" + ], + "acceptor_num_htlcs": 1 }, { "name": "commitment tx with two outputs untrimmed (minimum dust limit)", "feerate_per_kw": 4894, "dust_limit_satoshis": 4001, - "to_opener_msat": 6988000000, - "to_acceptor_msat": 3000000000, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], "local_signature": "30450221009f16ac85d232e4eddb3fcd750a68ebf0b58e3356eaada45d3513ede7e817bf4c02207c2b043b4e5f971261975406cb955219fa56bffe5d834a833694b5abc1ce4cfd", - "remote_signature": "3045022100e784a66b1588575801e237d35e510fd92a81ae3a4a2a1b90c031ad803d07b3f3022021bc5f16501f167607d63b681442da193eb0a76b4b7fd25c2ed4f8b28fd35b95" + "remote_signature": "3045022100e784a66b1588575801e237d35e510fd92a81ae3a4a2a1b90c031ad803d07b3f3022021bc5f16501f167607d63b681442da193eb0a76b4b7fd25c2ed4f8b28fd35b95", + "acceptor_num_htlcs": 0 }, { "name": "commitment tx with one output untrimmed (minimum dust limit)", "feerate_per_kw": 6216010, "dust_limit_satoshis": 4001, - "to_opener_msat": 6988000000, - "to_acceptor_msat": 3000000000, + "to_opener_msat": 6993000000, + "to_acceptor_msat": 3007000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 1000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "id": 4, + "amount_msat": 4000000, + "cltv_expiry": 504, + "payment_preimage": "0404040404040404040404040404040404040404040404040404040404040404" + } + ], + "outgoing_htlcs": [ + { + "id": 2, + "amount_msat": 2000000, + "cltv_expiry": 502, + "payment_preimage": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "id": 3, + "amount_msat": 3000000, + "cltv_expiry": 503, + "payment_preimage": "0303030303030303030303030303030303030303030303030303030303030303" + } + ], "local_signature": "30450221009ad80792e3038fe6968d12ff23e6888a565c3ddd065037f357445f01675d63f3022018384915e5f1f4ae157e15debf4f49b61c8d9d2b073c7d6f97c4a68caa3ed4c1", - "remote_signature": "30450221008fd5dbff02e4b59020d4cd23a3c30d3e287065fda75a0a09b402980adf68ccda022001e0b8b620cd915ddff11f1de32addf23d81d51b90e6841b2cb8dcaf3faa5ecf" + "remote_signature": "30450221008fd5dbff02e4b59020d4cd23a3c30d3e287065fda75a0a09b402980adf68ccda022001e0b8b620cd915ddff11f1de32addf23d81d51b90e6841b2cb8dcaf3faa5ecf", + "acceptor_num_htlcs": 0 + }, + { + "name": "commitment tx with 3 htlc outputs, 2 offered having the same amount and preimage", + "feerate_per_kw": 253, + "dust_limit_satoshis": 546, + "to_opener_msat": 6998000000, + "to_acceptor_msat": 3002000000, + "incoming_htlcs": [ + { + "id": 1, + "amount_msat": 2000000, + "cltv_expiry": 501, + "payment_preimage": "0101010101010101010101010101010101010101010101010101010101010101" + } + ], + "outgoing_htlcs": [ + { + "id": 5, + "amount_msat": 5000000, + "cltv_expiry": 506, + "payment_preimage": "0505050505050505050505050505050505050505050505050505050505050505" + }, + { + "id": 6, + "amount_msat": 5000001, + "cltv_expiry": 505, + "payment_preimage": "0505050505050505050505050505050505050505050505050505050505050505" + } + ], + "local_signature": "3045022100b4014970d9d7962853f3f85196144671d7d5d87426250f0a5fdaf9a55292e92502205360910c9abb397467e19dbd63d081deb4a3240903114c98cec0a23591b79b76", + "local_htlc_signatures": [ + "304402205df665e2908c7690d2d33eb70e6e119958c28febe141a94ed0dd9a55ce7c8cfc0220364d02663a5d019af35c5cd5fda9465d985d85bbd12db207738d61163449a424", + "304402203f99ec05cdd89558a23683b471c1dcce8f6a92295f1fff3b0b5d21be4d4f97ea022019d29070690fc2c126fe27cc4ab2f503f289d362721b2efa7418e7fddb939a5b", + "3045022100f2cd35e385b9b7e15b92a5d78d120b6b2c5af4e974bc01e884c5facb3bb5966c0220706e0506477ce809a40022d6de8e041e9ef13136c45abee9c36f58a01fdb188b" + ], + "remote_signature": "3044022027b38dfb654c34032ffb70bb43022981652fce923cbbe3cbe7394e2ade8b34230220584195b78da6e25c2e8da6b4308d9db25b65b64975db9266163ef592abb7c725", + "remote_htlc_signatures": [ + "30440220078fe5343dab88c348a3a8a9c1a9293259dbf35507ae971702cc39dd623ea9af022011ed0c0f35243cd0bb4d9ca3c772379b2b5f4af93140e9fdc5600dfec1cdb0c2", + "304402202df6bf0f98a42cfd0172a16bded7d1b16c14f5f42ba23f5c54648c14b647531302200fe1508626817f23925bb56951d5e4b2654c751743ab6db48a6cce7dda17c01c", + "3045022100bd206b420c495f3aa714d3ea4766cbe95441deacb5d2f737f1913349aee7c2ae02200249d2c950dd3b15326bf378ae5d2b871d33d6737f5d70735f3de8383140f2a1" + ], + "acceptor_num_htlcs": 3 } ] } \ No newline at end of file diff --git a/smite/src/channel_tx/commitment/test_vectors/custom_anchor.json b/smite/src/channel_tx/commitment/test_vectors/custom_anchor.json index e1ed5b12..778ea8c1 100644 --- a/smite/src/channel_tx/commitment/test_vectors/custom_anchor.json +++ b/smite/src/channel_tx/commitment/test_vectors/custom_anchor.json @@ -8,18 +8,22 @@ "to_self_delay": 144, "opener": { "funding_privkey": "30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749", + "htlc_basepoint_privkey": "1111111111111111111111111111111111111111111111111111111111111111", "funding_pubkey": "023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb", "payment_basepoint": "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa", "revocation_basepoint": "02c6047f9441ed7d6d3045406e95c07cd85a0f5f0f3b9b3f3d5f9b1e5e4a7c4f09", "delayed_payment_basepoint": "023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1", + "htlc_basepoint": "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa", "per_commitment_point": "025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486" }, "acceptor": { "funding_privkey": "1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e13", + "htlc_basepoint_privkey": "4444444444444444444444444444444444444444444444444444444444444444", "funding_pubkey": "030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c1", "payment_basepoint": "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", "revocation_basepoint": "02466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27", "delayed_payment_basepoint": "02a1633caf7bf0b7d9e5c4b8a1d6f2e3c4b5a6978877665544332211ffeeddccbb", + "htlc_basepoint": "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", "per_commitment_point": "03b28f7c5a9d1e4f8c6a7b2d3e9f1048576a1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e" }, "tests": [ @@ -31,7 +35,8 @@ "to_opener_msat": 6999999000, "to_acceptor_msat": 3000000123, "local_signature": "304402202573a6da7fffc40fffb98d106dc4c83a5c94266118b3b0b44ea03100e20dab1e022038d9e65b3b84096ccebc91f9b56117d30c1cc249e21426d2d3dbf3e4617935fd", - "remote_signature": "30450221009a6289d5082bca47cfbd0496a14c86f17a2ebdfd4888e779786626e018159e810220026bec6a9f3257359740132d28db54faebacdcb176e08eee32106daab97eacf0" + "remote_signature": "30450221009a6289d5082bca47cfbd0496a14c86f17a2ebdfd4888e779786626e018159e810220026bec6a9f3257359740132d28db54faebacdcb176e08eee32106daab97eacf0", + "acceptor_num_htlcs": 0 }, { "name": "commitment tx with equal output values orders by script_pubkey", @@ -41,7 +46,142 @@ "to_opener_msat": 5008760000, "to_acceptor_msat": 4991240000, "local_signature": "30440220156f857fc1cfaa0e13dadc5a07553244971a91d99a3f53bf87305189864043a402200bd512ace372ac10c54a3745ae123e69d99305c564bd0420ade72ebcac994bd8", - "remote_signature": "304402210086f1b5cfaa104c969998d606472e24a971d97d56a17bc6efa5ce0ae1ba7c96c3021f18fa1d6adf78af0bd10b1183304044af68ba8021d487bb5b8be0584f515453" + "remote_signature": "304402210086f1b5cfaa104c969998d606472e24a971d97d56a17bc6efa5ce0ae1ba7c96c3021f18fa1d6adf78af0bd10b1183304044af68ba8021d487bb5b8be0584f515453", + "acceptor_num_htlcs": 0 + }, + { + "name": "commitment tx with an htlc amount_msat not a multiple of 1000", + "comment": "Covers the case where an HTLC's amount_msat % 1000 != 0, to ensure there is no off-by-one error in the HTLC amount calculation.", + "feerate_per_kw": 647, + "dust_limit_satoshis": 546, + "to_opener_msat": 7000000000, + "to_acceptor_msat": 3000000000, + "outgoing_htlcs": [ + { + "id": 0, + "amount_msat": 2000999, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "local_signature": "304402203afba6c5bbe9e455ae9447de02b36567f34bc4d159b4b9163c136d364963122b022020bcdc1591ec8065e08922c3f3eb4271d62ebdc373440744d37c0cede40b902d", + "local_htlc_signatures": [ + "30440220244eeb877ec58ef8976ae81270feeca3303a0bb989d207f2ed6029d2fb36fd4a02207833cd008f5d9a4b20b7c02818593e27c7cea9e859f8dd8cdd97ba78e0920b9b" + ], + "remote_signature": "3044022047ce706776b0b1e4bc156ec274e327d700fc2d3f7955ace0e35c4bf3bc1da97702205923718d410327631327508c2e365919c54e42dc6f9872fec63b30f5fd5bdcf6", + "remote_htlc_signatures": [ + "30450221008c38edfba583642b4e70d34ca4f9cf1bd828eb03e86e87ab44810811e4573f27022051a5ad0932d11708d9c37da95e3d80f9867d0c44ba1ead4bd087bdb87ab50670" + ], + "acceptor_num_htlcs": 1 + }, + { + "name": "commitment tx with an htlc output value equal to a non-htlc output value", + "comment": "Covers the case where an HTLC output has the same value as a non-HTLC output, ensuring outputs with equal values are ordered by script_pubkey and the HTLC output index accounts for outputs inserted before it.", + "feerate_per_kw": 15000, + "dust_limit_satoshis": 546, + "to_opener_msat": 8000000000, + "to_acceptor_msat": 2000000000, + "outgoing_htlcs": [ + { + "id": 0, + "amount_msat": 2000000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "local_signature": "3045022100bbac5fc61362cf1f1e6f88efd30ff181bf7d280a584647b6df0357607407e01a0220098af2c102759991c558e5cf5a2f2fc13853eca03f942590795a6926b622ee67", + "local_htlc_signatures": [ + "3045022100e49627506b85bdbb3a761edfe853c31afba97f2f3b2b0402bb58ba137cf645810220266f579ee3afa21f732f0da14b5035d46175a818608bb7d7cbd902ae7368735c" + ], + "remote_signature": "3045022100f48d15d944308832b3acffd3b188967433e67f7a2abb060cf2387eff8b369eb702202c50731090700a58f8b1ebe94d17118b9280c545827dfc364bf1135847763a4e", + "remote_htlc_signatures": [ + "30440220096d96dff49a16161bc070be6640327142ec19e579ce0c65884e89e6672d09d5022066553f5beffbd466d60953d596eb6ec04864c4a312a5b55b20a7c0c4b06af202" + ], + "acceptor_num_htlcs": 1 + }, + { + "name": "commitment tx with non-htlc outputs between htlc outputs", + "comment": "Covers the case where the non-HTLC outputs sort between two HTLC outputs, so only the HTLC outputs after each insertion point are shifted to later output indices.", + "feerate_per_kw": 15000, + "dust_limit_satoshis": 546, + "to_opener_msat": 7000000000, + "to_acceptor_msat": 3000000000, + "outgoing_htlcs": [ + { + "id": 0, + "amount_msat": 2000000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 4000000000, + "cltv_expiry": 501, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "local_signature": "3045022100b1f70a52198e0c43f47264150733ecded0765e9f8a1e3c536a7b99f3ed0a7cd602202232cfeea815851474b9254fb3f74df72cc92bec42e9e4c69ef0d7c4215d3c14", + "local_htlc_signatures": [ + "30450221009f1e176c5cf306c0406853af751457a142bc41525bf8a8dc534ad09797cd9292022037d6642e7737c0ce5d6f593a0a9e7ed805dea1a1ce829676aceb2c6e0bff7b13", + "3045022100eff7fa286ca3cf7cec8be7c6272ae3c2357b76ebe769f97c4274b5465231ad7602203bbab2a1de53e4f4de0d5b088abe662ad27b7d25906cc5d08e538b537553c64a" + ], + "remote_signature": "30440220741a31cf021cd3205b0c8d838423f4ead9501e9d8c2aab5452b6bf3f8575ac0c022055fb395e886f41d55cc2141e1d94e3a0c67cf39524627a4dd15f47d2caa6c213", + "remote_htlc_signatures": [ + "3044022001d340b46ebcc3c01a518d2f88fb18af1c75f538b693e98aadc455249f54c516022029b16d28f46f844d589821c915755bd48c9679beb3ea9debbada479bfefbd322", + "304402202496a11a0047d22ab54ce1d886ea291af0d34607db5e60fadeadab5d20d32bec022044b0bf1a26339355907985f5e2d2b08ad5c0523b58f2f42eba928faa8297a683" + ], + "acceptor_num_htlcs": 2 + }, + { + "name": "commitment tx with asymmetric dust limits", + "comment": "Covers the case where the two sides have asymmetric dust limits, so the same HTLC is an output on the opener's commitment and trimmed on the acceptor's.", + "feerate_per_kw": 15000, + "opener_dust_limit_satoshis": 546, + "acceptor_dust_limit_satoshis": 20000, + "to_opener_msat": 7000000000, + "to_acceptor_msat": 3000000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 15000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "local_signature": "3045022100cede1655fb5576ee3b585c4522fbb53cb15aa78798829a47887e1c791712a60402200b69f60087bf0fbc3f8695ca5b6fbf823f732e64e8766decbbc67e7c55b7ac6b", + "local_htlc_signatures": [ + "3044022002b6e43948a41df041bf82407c41c5db953f008cd113876301b775c7465ec24602204ea3cf73b71fffdbaa89795ffc7f9af0ff63c5db87422d28d29067190103bb4f" + ], + "remote_signature": "304402202c96c14a9e1abc300ae2fb2f0fd7de60504ef86d0d31751c8754cfff2135f4a00220434f82d576ec6f772aa9c1d2ffc8f2573c54905f99e69d57ce1342788dfb0552", + "remote_htlc_signatures": [ + "304402202cdc2895ea584558a32d095bd142dae70a16ad81027878a0ef705c7fa2e6039a022032c425b74391a06e410eab1cc60137f9a28af6131d7fcb3367d9d681f115729c" + ], + "acceptor_num_htlcs": 0 + }, + { + "name": "commitment tx with main output trimmed but htlcs present", + "comment": "Covers the case where a party's main output is trimmed but the commitment still carries a non-dust HTLC, so both anchor outputs must be present.", + "feerate_per_kw": 15000, + "dust_limit_satoshis": 546, + "to_opener_msat": 10000000000, + "to_acceptor_msat": 0, + "outgoing_htlcs": [ + { + "id": 0, + "amount_msat": 5000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "local_signature": "30440220656278bd97a2cd248ccc37c3884ac1dd53ae36dc9fd1b0fc11165e4d31ac893402203d1ce41ba056e6f6cea33cb4d68bee5763d8b50afae352a413143349471a9ba8", + "local_htlc_signatures": [ + "30450221008f19c964869f8610d8d255022b3f91df4cf2311f54d9d40fd9761d42aee15da302203913abd3cbd8feb0084bba75f170a90a57416727cb0f2d638cfd6ba6fdc7b8cc" + ], + "remote_signature": "3045022100c2bee5cf14dba147a7d3aa92c6d6290d04408007806e7eb629a96f6abaa1ad6902206202a6d872946d6d9207bf87eea134c01ad7ace44ce5da585ce395825ca8d9b8", + "remote_htlc_signatures": [ + "30440220692cac8484475fd83a9481a999a24321c2aa64b2dc5cf3090b5d2b9314de46560220528f0e850c51648e680509ec5a246aa6c877addd0c425bbe92d127dc26633674" + ], + "acceptor_num_htlcs": 1 } ] } \ No newline at end of file diff --git a/smite/src/channel_tx/commitment/test_vectors/custom_legacy.json b/smite/src/channel_tx/commitment/test_vectors/custom_legacy.json index f3f54d1f..bf29d864 100644 --- a/smite/src/channel_tx/commitment/test_vectors/custom_legacy.json +++ b/smite/src/channel_tx/commitment/test_vectors/custom_legacy.json @@ -8,18 +8,22 @@ "to_self_delay": 144, "opener": { "funding_privkey": "30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749", + "htlc_basepoint_privkey": "1111111111111111111111111111111111111111111111111111111111111111", "funding_pubkey": "023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb", "payment_basepoint": "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa", "revocation_basepoint": "02c6047f9441ed7d6d3045406e95c07cd85a0f5f0f3b9b3f3d5f9b1e5e4a7c4f09", "delayed_payment_basepoint": "023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1", + "htlc_basepoint": "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa", "per_commitment_point": "025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486" }, "acceptor": { "funding_privkey": "1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e13", + "htlc_basepoint_privkey": "4444444444444444444444444444444444444444444444444444444444444444", "funding_pubkey": "030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c1", "payment_basepoint": "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", "revocation_basepoint": "02466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27", "delayed_payment_basepoint": "02a1633caf7bf0b7d9e5c4b8a1d6f2e3c4b5a6978877665544332211ffeeddccbb", + "htlc_basepoint": "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", "per_commitment_point": "03b28f7c5a9d1e4f8c6a7b2d3e9f1048576a1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e" }, "tests": [ @@ -31,7 +35,8 @@ "to_opener_msat": 6999999000, "to_acceptor_msat": 3000000123, "local_signature": "3045022100a41609df3e71b939046d6dfface892aa6161ef8fb61898e142aeffc0ce1462df02201d1ca13eb145436593b0cb1a201c48bf2fdd6fc0c754784240d5f407c06ab4cf", - "remote_signature": "3045022100aae069d93bc6bfb0b9ba79cfa992c23ad6986d7e7e49c8c271754ccce876b76402207b71bf09695c561d5f569ecdb23e529bdcfb542a105a827c1138284fe51c3814" + "remote_signature": "3045022100aae069d93bc6bfb0b9ba79cfa992c23ad6986d7e7e49c8c271754ccce876b76402207b71bf09695c561d5f569ecdb23e529bdcfb542a105a827c1138284fe51c3814", + "acceptor_num_htlcs": 0 }, { "name": "commitment tx with equal output values orders by script_pubkey", @@ -41,7 +46,117 @@ "to_opener_msat": 5005430000, "to_acceptor_msat": 4994570000, "local_signature": "3045022100a51021a83202743cb336edad88ee08bd14f434779bff21351c8f39d78d035f9602200d889a4a98332aff37f02938157cd3d7cf336313e5663848ac18bcd09ad5ff13", - "remote_signature": "304502210093f2074fdc4b2f3d9369eb841522907d1d5279f956348d48e2d3a7ad58db4ee7022012173ea8609bedd4e03e16b41b38b3abf8a87f4a55372c06294578733d3fd785" + "remote_signature": "304502210093f2074fdc4b2f3d9369eb841522907d1d5279f956348d48e2d3a7ad58db4ee7022012173ea8609bedd4e03e16b41b38b3abf8a87f4a55372c06294578733d3fd785", + "acceptor_num_htlcs": 0 + }, + { + "name": "commitment tx with an htlc amount_msat not a multiple of 1000", + "comment": "Covers the case where an HTLC's amount_msat % 1000 != 0, to ensure there is no off-by-one error in the HTLC amount calculation.", + "feerate_per_kw": 647, + "dust_limit_satoshis": 546, + "to_opener_msat": 7000000000, + "to_acceptor_msat": 3000000000, + "outgoing_htlcs": [ + { + "id": 0, + "amount_msat": 2000999, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "local_signature": "3045022100a40923481abb54013b0b85ae1fc6ec2a98bcfad532a902312acabbe1c0b5606302201c2b46e4beb40295c74dd85d3ac7637181c1c83d5a2b57fd1f33248ad78989e7", + "local_htlc_signatures": [ + "3045022100db1854f0e554418c054f5537df9bcc99ee28af2a93e862cbb892612ee1f1041c02204b1ecb1845539f6055cc4de6a30d47cc36510b38f6a48d4ee0e0910707b34f60" + ], + "remote_signature": "30440220038e3d6736e250d487612065b7ff9e2298d8671c7238b33347e061da141f056b022033761ec4566de4e162214fed71ffb5e06f31a98b9de992d325d13b909f44bbf6", + "remote_htlc_signatures": [ + "3045022100980b1185dcf7e8006686ce6f317b9dc6bc23eeaab95a6d0ba0be592c5179f5cc02203a21c044c542aaf3b6c24c4eb412850502cc6ce508044d2d05930417bff51a37" + ], + "acceptor_num_htlcs": 1 + }, + { + "name": "commitment tx with an htlc output value equal to a non-htlc output value", + "comment": "Covers the case where an HTLC output has the same value as a non-HTLC output, ensuring outputs with equal values are ordered by script_pubkey and the HTLC output index accounts for outputs inserted before it.", + "feerate_per_kw": 15000, + "dust_limit_satoshis": 546, + "to_opener_msat": 8000000000, + "to_acceptor_msat": 2000000000, + "outgoing_htlcs": [ + { + "id": 0, + "amount_msat": 2000000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "local_signature": "3045022100cfe88de4c1e3fd036d73fe64bbdc70ba127f30721fbc912ba7bfde04522c948a02206b83377c3a43961f771596b501c5f704dd989c5a85a329a03a38582947174080", + "local_htlc_signatures": [ + "304502210083c359c48414d8a86b7e5663bf59cd5024f8232a31653472e8ed46794b83cb7b022024b151cf06559c469f055ba3f18a892f092a8a86b42e64933719de88f7c3b68d" + ], + "remote_signature": "304402210090fc7f86ae691bf50820d719241ba8ea694ca66f46cb204428b5abdedd224f96021f3dee0cc5e214ce7cb4eb0ce50501732cc6421d7e411052a6561c1775020bd6", + "remote_htlc_signatures": [ + "30450221009b9456712702f6dea040a67dbfd145a77ae8493a5a1a9bc3fdda463a1aebcd9702203f465742ca4c0cc3e13e74d5ff28cd26dd5b04d6fea57abfbe47e90b49a8078f" + ], + "acceptor_num_htlcs": 1 + }, + { + "name": "commitment tx with non-htlc outputs between htlc outputs", + "comment": "Covers the case where the non-HTLC outputs sort between two HTLC outputs, so only the HTLC outputs after each insertion point are shifted to later output indices.", + "feerate_per_kw": 15000, + "dust_limit_satoshis": 546, + "to_opener_msat": 7000000000, + "to_acceptor_msat": 3000000000, + "outgoing_htlcs": [ + { + "id": 0, + "amount_msat": 2000000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "id": 1, + "amount_msat": 4000000000, + "cltv_expiry": 501, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "local_signature": "3045022100d984f27ad3542ab2fdf12738ab1e0cff57a605ddc997aa3f8dcd8cc9c2fb71ff022025f76697020b8d45d73e8d98d32f70d45683936cfb28a0f5865a7681a9b9b183", + "local_htlc_signatures": [ + "3045022100beaecfb05ea32a060af1787cedd71aa1617e0af643758772f222a8bc625ee76a0220391ede3ae420f58bc1647cbfddb1c6442a16e055c260ba4b5e031bf8fab29e92", + "3045022100e80f47393ea39ca3498bbfb13d8880779c29fdf193fc05ffcac7e19c6e94269c02206f97146dbf1194aa5a04f1793af9d9f664a47102ff8a3eaeab649c9b25f57239" + ], + "remote_signature": "304402202f3bb56245790a8ff39ec9b66a0c70f0e25e74cb5f2b6ddc5cabc315dbcc739e02203d59d538906080e3282ab1bce79d4c2725739c5a466dcc6206bd5f120013eff1", + "remote_htlc_signatures": [ + "304402205677d1c9af31718ae4546acf3ebfc51d7e7c61f3aa9aa9097da8a1e6a2892c9602201bbeb9e658dee513701deb8c66afd3739498d744bd10ee9f933989bafdd9bd3a", + "30440220448bac8d929a870d75b3aa254ec7c75eaeb48af9fd3b6a6f7f9a61fa1f37fd10022052dbff5e4ce8c0b85f0900533f4c87bb30c796f68bdeabf36069f4c8528f497e" + ], + "acceptor_num_htlcs": 2 + }, + { + "name": "commitment tx with asymmetric dust limits", + "comment": "Covers the case where the two sides have asymmetric dust limits, so the same HTLC is an output on the opener's commitment and trimmed on the acceptor's.", + "feerate_per_kw": 15000, + "opener_dust_limit_satoshis": 546, + "acceptor_dust_limit_satoshis": 20000, + "to_opener_msat": 7000000000, + "to_acceptor_msat": 3000000000, + "incoming_htlcs": [ + { + "id": 0, + "amount_msat": 20000000, + "cltv_expiry": 500, + "payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "local_signature": "304402203b294ce9542c604e7432e2bd149a9a58de5696385e17997c54a6cc3e32383e5f02207be0ac31c9d66315baea8431318b9b6f68928c2db607cdd0b571e2eb25573dad", + "local_htlc_signatures": [ + "304402200f8a51289fd5635d0f759aa41d24ef9bfcb290b835eca25c112fee27510d13ba022005a518038ca6b2fe9fa7b395020fcd83f860e0e3275ecda4d0d016167b4075e0" + ], + "remote_signature": "30440220585c2c1de009b1d012dddd35f59fbd5090307b53e1b8771dfe7159ce746ab47c02204eea2ee75ab8c8d9adcfbf874e8a079a8488f9f14c9b418dca6c687b5372692f", + "remote_htlc_signatures": [ + "304402206a3d24183ca3ee4dfba7b66452452895330caa285b34e5f2dca9e150ea8f1fcc0220796dd753225740974920313ea00ad111977475c3ba2a6cb6dac663f371cf4698" + ], + "acceptor_num_htlcs": 0 } ] } \ No newline at end of file diff --git a/smite/src/channel_tx/commitment/tests.rs b/smite/src/channel_tx/commitment/tests.rs index d714a4c7..96745a70 100644 --- a/smite/src/channel_tx/commitment/tests.rs +++ b/smite/src/channel_tx/commitment/tests.rs @@ -2,6 +2,8 @@ mod harness; +use std::str::FromStr; + use super::*; use harness::run_commitment_vectors; @@ -75,12 +77,17 @@ fn derive_revocation_pubkey_from_basepoint() { } fn sample_chan_config(funding_satoshis: u64, channel_type: Features) -> ChannelConfig { - let sample_key = pubkey("03b28f7c5a9d1e4f8c6a7b2d3e9f1048576a1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e"); - let sample_party = || ChannelPartyConfig { + let opener_pubkey = + pubkey("023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb"); + let acceptor_pubkey = + pubkey("030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c1"); + + let sample_party = |sample_key: PublicKey| ChannelPartyConfig { funding_pubkey: sample_key, payment_basepoint: sample_key, revocation_basepoint: sample_key, delayed_payment_basepoint: sample_key, + htlc_basepoint: sample_key, dust_limit_satoshis: 546, to_self_delay: 144, }; @@ -94,8 +101,8 @@ fn sample_chan_config(funding_satoshis: u64, channel_type: Features) -> ChannelC }, funding_satoshis, channel_type, - opener: sample_party(), - acceptor: sample_party(), + opener: sample_party(opener_pubkey), + acceptor: sample_party(acceptor_pubkey), minimum_depth: 8, } } @@ -133,28 +140,301 @@ fn opener_balance_after_commitment_cost_total_sat_checks() { // Comfortably affordable let opener_balance_sat: u64 = 20_000; assert_eq!( - opener_balance_sat.checked_sub(CommitmentCost::new(feerate_per_kw, &legacy).total_sat()), + opener_balance_sat.checked_sub(CommitmentCost::new(feerate_per_kw, &legacy, 0).total_sat()), Some(9_140), ); // Exact zero opener balance let opener_balance_sat: u64 = 10_860; assert_eq!( - opener_balance_sat.checked_sub(CommitmentCost::new(feerate_per_kw, &legacy).total_sat()), + opener_balance_sat.checked_sub(CommitmentCost::new(feerate_per_kw, &legacy, 0).total_sat()), Some(0), ); // Balance does not cover the fee let opener_balance_sat: u64 = 10_000; assert_eq!( - opener_balance_sat.checked_sub(CommitmentCost::new(feerate_per_kw, &legacy).total_sat()), + opener_balance_sat.checked_sub(CommitmentCost::new(feerate_per_kw, &legacy, 0).total_sat()), None ); // Balance covers the fee but not the anchor outputs let opener_balance_sat: u64 = 17_500; assert_eq!( - opener_balance_sat.checked_sub(CommitmentCost::new(feerate_per_kw, &anchor).total_sat()), + opener_balance_sat.checked_sub(CommitmentCost::new(feerate_per_kw, &anchor, 0).total_sat()), None, ); } + +#[test] +fn opener_balance_after_commitment_cost_total_sat_with_htlc_checks() { + let feerate_per_kw: u32 = 15_000; + let legacy = Features::from_bits(&[Features::OPTION_STATIC_REMOTEKEY]); + let anchor = Features::from_bits(&[Features::OPTION_ANCHORS]); + let sample_key = pubkey("03b28f7c5a9d1e4f8c6a7b2d3e9f1048576a1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e"); + let htlc = |id: u64, offerer: Side, amount_msat: u64| Htlc { + id, + offerer, + amount_msat, + cltv_expiry: 500, + payment_hash: [0; 32], + }; + let state_with = |config: &ChannelConfig, push_msat: u64, htlcs: &[Htlc]| { + let mut commitments = config + .new_initial_commitments(push_msat, feerate_per_kw, sample_key, sample_key) + .expect("valid commitment"); + for h in htlcs { + commitments.add_htlc(Side::Opener, *h).unwrap(); + commitments.add_htlc(Side::Acceptor, *h).unwrap(); + } + + // Since both sides are built identically, we can use either side's state + // to verify the opener's balance after deducting the commitment cost. + commitments.opener + }; + // Returns opener balance after deducting the commitment cost. + let opener_balance = |config: &ChannelConfig, state: &CommitmentState, local_side: Side| { + let opener_balance_sat = state.opener_balance_msat / 1000; + let cost = CommitmentCost::new( + state.feerate_per_kw, + &config.channel_type, + config.count_nondust_htlcs(state, local_side), + ); + opener_balance_sat.checked_sub(cost.total_sat()) + }; + // Legacy fee: 15000 * (724 + 172 per non-dust HTLC) / 1000 + // = 10_860 / 13_440 / 16_020 sat for 0 / 1 / 2 HTLCs + // Legacy dust thresholds: 546 + 15000 * 663 / 1000 = 10_491 sat (offered); + // 546 + 15000 * 703 / 1000 = 11_091 sat (received) + // Anchor fee: 15000 * (1124 + 172) / 1000 = 19_440 sat for 1 HTLC; anchor_cost = 660 sat + + // Opener balance exactly covers the one-HTLC fee + let config = sample_chan_config(50_000, legacy.clone()); + let offered = htlc(0, Side::Opener, 12_000_000); + let state = state_with(&config, 24_560_000, &[offered]); + assert_eq!(opener_balance(&config, &state, Side::Opener), Some(0)); + assert_eq!(opener_balance(&config, &state, Side::Acceptor), Some(0)); + + // One msat short of the one-HTLC fee + let state = state_with(&config, 24_560_001, &[offered]); + assert_eq!(opener_balance(&config, &state, Side::Opener), None); + assert_eq!(opener_balance(&config, &state, Side::Acceptor), None); + + // Dust HTLC is trimmed and adds no fee + let state = state_with( + &config, + 24_560_000, + &[offered, htlc(1, Side::Acceptor, 1_000_000)], + ); + assert_eq!(opener_balance(&config, &state, Side::Opener), Some(0)); + assert_eq!(opener_balance(&config, &state, Side::Acceptor), Some(0)); + + // Second non-dust HTLC pushes the fee out of reach + let state = state_with( + &config, + 24_560_000, + &[offered, htlc(1, Side::Acceptor, 12_000_000)], + ); + assert_eq!(opener_balance(&config, &state, Side::Opener), None); + assert_eq!(opener_balance(&config, &state, Side::Acceptor), None); + + // Exactly at the offered dust threshold: kept on the opener's + // commitment, trimmed as received on the acceptor's + let state = state_with(&config, 27_509_000, &[htlc(0, Side::Opener, 10_491_000)]); + assert_eq!(opener_balance(&config, &state, Side::Opener), None); + assert_eq!(opener_balance(&config, &state, Side::Acceptor), Some(1_140)); + + // Dust limit of the evaluated side decides trimming + let mut config = sample_chan_config(50_000, legacy); + config.acceptor.dust_limit_satoshis = 20_000; + let state = state_with(&config, 26_000_000, &[offered]); + assert_eq!(opener_balance(&config, &state, Side::Opener), None); + assert_eq!(opener_balance(&config, &state, Side::Acceptor), Some(1_140)); + + // Anchor dust threshold is the dust limit alone + let config = sample_chan_config(50_000, anchor); + let htlcs = [ + htlc(0, Side::Opener, 546_000), + htlc(1, Side::Opener, 545_000), + ]; + let state = state_with(&config, 28_809_000, &htlcs); + assert_eq!(opener_balance(&config, &state, Side::Opener), Some(0)); + assert_eq!(opener_balance(&config, &state, Side::Acceptor), Some(0)); + + // One msat short of the anchor one-HTLC fee plus anchor cost + let state = state_with(&config, 28_809_001, &htlcs); + assert_eq!(opener_balance(&config, &state, Side::Opener), None); + assert_eq!(opener_balance(&config, &state, Side::Acceptor), None); +} + +#[test] +#[allow(clippy::too_many_lines)] +fn commitment_dance_adds_and_resolves_htlc() { + let secp = Secp256k1::new(); + let privkey1 = + SecretKey::from_str("30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749") + .expect("valid secret key"); + let privkey2 = + SecretKey::from_str("1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e13") + .expect("valid secret key"); + let pubkey1 = PublicKey::from_secret_key(&secp, &privkey1); + let pubkey2 = PublicKey::from_secret_key(&secp, &privkey2); + + // Set up the channel configuration, initial commitments, and holder + // identities for the HTLC commitment dance. + let config = sample_chan_config( + 10_000_000, + Features::from_bits(&[Features::OPTION_STATIC_REMOTEKEY]), + ); + let mut commitments = config + .new_initial_commitments(3_000_000_000, 253, pubkey1, pubkey2) + .expect("valid commitment"); + let opener_holder = HolderIdentity { + side: Side::Opener, + funding_privkey: privkey1, + htlc_basepoint_privkey: privkey1, + }; + let acceptor_holder = HolderIdentity { + side: Side::Acceptor, + funding_privkey: privkey2, + htlc_basepoint_privkey: privkey2, + }; + + let htlc = |id: u64, offerer: Side, amount_msat: u64| Htlc { + id, + offerer, + amount_msat, + cltv_expiry: 500, + payment_hash: [0; 32], + }; + // A `commitment_signed` creates the counterparty's next commitment: `signer` + // advances it (updates number and per-commitment point) and signs it, then + // `verifier` (that counterparty) verifies. + let commitment_signed = |commitments: &mut ChannelCommitments, + signer: &HolderIdentity, + verifier: &HolderIdentity, + next_per_commitment_point: PublicKey| { + commitments.update_per_commitment_point(verifier.side, next_per_commitment_point); + commitments.advance_commitment_number(verifier.side); + assert_eq!( + commitments.state(verifier.side).per_commitment_point, + next_per_commitment_point, + ); + let (commitment_sig, htlc_sigs) = config.sign_counterparty_commitment(commitments, signer); + assert!(config.verify_counterparty_signature( + commitments, + verifier, + &commitment_sig, + &htlc_sigs, + )); + }; + + // Asserts a side's commitment balances, feerate, and in-flight HTLC count. + let assert_state = |commitments: &ChannelCommitments, + side: Side, + opener_balance_msat: u64, + acceptor_balance_msat: u64, + feerate_per_kw: u32, + htlc_count: usize| { + let state = commitments.state(side); + assert_eq!(state.opener_balance_msat, opener_balance_msat); + assert_eq!(state.acceptor_balance_msat, acceptor_balance_msat); + assert_eq!(state.feerate_per_kw, feerate_per_kw); + assert_eq!(state.htlcs.len(), htlc_count); + }; + + // Opener sends two `update_add_htlc`, an `update_fee`, and + // `commitment_signed`, committing the HTLCs and the new feerate to the + // acceptor's commitment #1. + commitments + .add_htlc(Side::Acceptor, htlc(0, Side::Opener, 2_000_000_000)) + .expect("balance covers HTLC"); + commitments + .add_htlc(Side::Acceptor, htlc(1, Side::Opener, 1_000_000_000)) + .expect("balance covers HTLC"); + commitments.update_fee(Side::Acceptor, 500); + assert_state( + &commitments, + Side::Acceptor, + 4_000_000_000, + 3_000_000_000, + 500, + 2, + ); + commitment_signed( + &mut commitments, + &opener_holder, + &acceptor_holder, + pubkey1, // Rotate the pubkeys to verify that PCP is updated. + ); + + // Acceptor replies with `commitment_signed`, committing the same to the + // opener's commitment #1. + commitments + .add_htlc(Side::Opener, htlc(0, Side::Opener, 2_000_000_000)) + .expect("balance covers HTLC"); + commitments + .add_htlc(Side::Opener, htlc(1, Side::Opener, 1_000_000_000)) + .expect("balance covers HTLC"); + commitments.update_fee(Side::Opener, 500); + assert_state( + &commitments, + Side::Opener, + 4_000_000_000, + 3_000_000_000, + 500, + 2, + ); + commitment_signed( + &mut commitments, + &acceptor_holder, + &opener_holder, + pubkey2, // Rotate the pubkeys to verify that PCP is updated. + ); + + // The acceptor received both of the opener's HTLCs: it fulfills HTLC 0 + // (crediting itself) and fails HTLC 1 (refunding the opener). Its + // `commitment_signed` resolves both on the opener's commitment #2. + commitments + .fulfill_htlc(Side::Opener, 0, Side::Opener) + .expect("HTLC 0 is in-flight"); + commitments + .fail_htlc(Side::Opener, 1, Side::Opener) + .expect("HTLC 1 is in-flight"); + assert_state( + &commitments, + Side::Opener, + 5_000_000_000, + 5_000_000_000, + 500, + 0, + ); + commitment_signed( + &mut commitments, + &acceptor_holder, + &opener_holder, + pubkey1, // Rotate the pubkeys to verify that PCP is updated. + ); + + // Opener's reply resolves both on the acceptor's commitment #2. + commitments + .fulfill_htlc(Side::Acceptor, 0, Side::Opener) + .expect("HTLC 0 is in-flight"); + commitments + .fail_htlc(Side::Acceptor, 1, Side::Opener) + .expect("HTLC 1 is in-flight"); + assert_state( + &commitments, + Side::Acceptor, + 5_000_000_000, + 5_000_000_000, + 500, + 0, + ); + commitment_signed( + &mut commitments, + &opener_holder, + &acceptor_holder, + pubkey2, // Rotate the pubkeys to verify that PCP is updated. + ); +} diff --git a/smite/src/channel_tx/commitment/tests/harness.rs b/smite/src/channel_tx/commitment/tests/harness.rs index 5d6fd64b..efb47dab 100644 --- a/smite/src/channel_tx/commitment/tests/harness.rs +++ b/smite/src/channel_tx/commitment/tests/harness.rs @@ -34,6 +34,8 @@ struct TestVectorFile { struct PartyKeys { /// Funding private key, used to sign commitment transactions. funding_privkey: SecretKey, + /// HTLC basepoint private key, used to sign HTLC transactions. + htlc_basepoint_privkey: SecretKey, /// Funding public key used in the funding output. funding_pubkey: PublicKey, /// Payment basepoint used to derive the `to_remote` output key. @@ -42,6 +44,8 @@ struct PartyKeys { revocation_basepoint: PublicKey, /// Delayed payment basepoint used to derive the `to_local` output key. delayed_payment_basepoint: PublicKey, + /// HTLC basepoint used to derive HTLC keys. + htlc_basepoint: PublicKey, /// Per-commitment point used to derive all commitment-specific keys. per_commitment_point: PublicKey, } @@ -53,16 +57,70 @@ struct CommitmentVector { name: String, /// Fee rate for the commitment transaction. feerate_per_kw: u32, - /// Dust limit both parties use for this vector. - dust_limit_satoshis: u64, + /// Default dust limit used by both parties unless overridden below. + #[serde(default)] + dust_limit_satoshis: Option, + /// Dust limit used for the opener's commitment, overriding the default. + #[serde(default)] + opener_dust_limit_satoshis: Option, + /// Dust limit used for the acceptor's commitment, overriding the default. + #[serde(default)] + acceptor_dust_limit_satoshis: Option, /// Opener's balance in millisatoshis, before fees and anchor outputs. to_opener_msat: u64, /// Acceptor's balance in millisatoshis. to_acceptor_msat: u64, + /// HTLCs offered by the acceptor, deducted from its balance above. + #[serde(default)] + incoming_htlcs: Vec, + /// HTLCs offered by the opener, deducted from its balance above. + #[serde(default)] + outgoing_htlcs: Vec, /// Opener's expected signature over its own commitment, DER encoded. local_signature: Signature, + /// Opener's expected signatures over its own HTLC transactions, in + /// commitment output order. + #[serde(default)] + local_htlc_signatures: Vec, /// Acceptor's signature over the opener's commitment, DER encoded. remote_signature: Signature, + /// Acceptor's signatures over the opener's HTLC transactions, in commitment + /// output order. + #[serde(default)] + remote_htlc_signatures: Vec, + /// Number of untrimmed HTLC outputs on the acceptor's commitment, signed by + /// the opener. + acceptor_num_htlcs: usize, +} + +/// A single in-flight HTLC used by a commitment vector. +/// +/// The offering side comes from the key holding it: `incoming_htlcs` is offered +/// by the acceptor, `outgoing_htlcs` by the opener. +#[derive(serde::Deserialize)] +struct HtlcVector { + /// HTLC ID. + id: u64, + /// HTLC amount in millisatoshis. + amount_msat: u64, + /// The expiry height of the HTLC. + cltv_expiry: u32, + /// Payment preimage, HTLC's payment hash is `SHA256` of this. + #[serde(with = "hex")] + payment_preimage: [u8; 32], +} + +impl HtlcVector { + /// Builds the HTLC as offered by `offerer`. + fn to_htlc(&self, offerer: Side) -> Htlc { + Htlc { + id: self.id, + offerer, + amount_msat: self.amount_msat, + cltv_expiry: self.cltv_expiry, + payment_hash: Sha256::hash(&self.payment_preimage).to_byte_array(), + } + } } impl PartyKeys { @@ -74,6 +132,7 @@ impl PartyKeys { payment_basepoint: self.payment_basepoint, revocation_basepoint: self.revocation_basepoint, delayed_payment_basepoint: self.delayed_payment_basepoint, + htlc_basepoint: self.htlc_basepoint, dust_limit_satoshis, to_self_delay, } @@ -83,21 +142,31 @@ impl PartyKeys { impl TestVectorFile { /// Builds the channel config for a vector. fn build_channel_config(&self, vector: &CommitmentVector) -> ChannelConfig { + let opener_dust_limit_satoshis = vector + .opener_dust_limit_satoshis + .or(vector.dust_limit_satoshis) + .expect("opener dust limit must be set"); + let acceptor_dust_limit_satoshis = vector + .acceptor_dust_limit_satoshis + .or(vector.dust_limit_satoshis) + .expect("acceptor dust limit must be set"); + ChannelConfig { funding_outpoint: self.funding_outpoint, funding_satoshis: self.funding_amount_satoshis, channel_type: self.channel_type.to_features(), opener: self .opener - .to_party_config(vector.dust_limit_satoshis, self.to_self_delay), + .to_party_config(opener_dust_limit_satoshis, self.to_self_delay), acceptor: self .acceptor - .to_party_config(vector.dust_limit_satoshis, self.to_self_delay), + .to_party_config(acceptor_dust_limit_satoshis, self.to_self_delay), minimum_depth: 8, } } - /// Builds the channel commitment states for both sides from a commitment vector. + /// Builds the channel commitment states for both sides from a commitment + /// vector, adding its HTLCs to both. fn build_channel_commitments(&self, vector: &CommitmentVector) -> ChannelCommitments { let new_state = |local_side, per_commitment_point| CommitmentState { local_side, @@ -106,24 +175,47 @@ impl TestVectorFile { per_commitment_point, opener_balance_msat: vector.to_opener_msat, acceptor_balance_msat: vector.to_acceptor_msat, + htlcs: Vec::new(), }; - ChannelCommitments { + let mut commitments = ChannelCommitments { opener: new_state(Side::Opener, self.opener.per_commitment_point), acceptor: new_state(Side::Acceptor, self.acceptor.per_commitment_point), + }; + + // Build both commitment states identically, as in the BOLT test + // vectors. They could differ in practice, but identical states still + // cover the same case. + for side in [Side::Opener, Side::Acceptor] { + // Add incoming HTLCs. + for htlc in &vector.incoming_htlcs { + commitments + .add_htlc(side, htlc.to_htlc(Side::Acceptor)) + .expect("balance covers HTLCs"); + } + + // Add outgoing HTLCs. + for htlc in &vector.outgoing_htlcs { + commitments + .add_htlc(side, htlc.to_htlc(Side::Opener)) + .expect("balance covers HTLCs"); + } } + + commitments } /// Builds the holder identity for the given side. fn build_holder_identity(&self, side: Side) -> HolderIdentity { - let funding_privkey = match side { - Side::Opener => self.opener.funding_privkey, - Side::Acceptor => self.acceptor.funding_privkey, + let keys = match side { + Side::Opener => &self.opener, + Side::Acceptor => &self.acceptor, }; HolderIdentity { side, - funding_privkey, + funding_privkey: keys.funding_privkey, + htlc_basepoint_privkey: keys.htlc_basepoint_privkey, } } @@ -135,17 +227,25 @@ impl TestVectorFile { let acceptor_holder = self.build_holder_identity(Side::Acceptor); let mut failures = Vec::new(); - // Opener signs own commitment. - let local_signature = channel_config.sign_holder_commitment(&commitments, &opener_holder); + // Opener signs own commitment and its HTLC transactions. + let (local_signature, local_htlc_signatures) = + channel_config.sign_holder_commitment(&commitments, &opener_holder); if local_signature != vector.local_signature { failures.push(format!( "{}: local signature mismatch\n expected: {}\n actual: {}", vector.name, vector.local_signature, local_signature, )); } + if local_htlc_signatures != vector.local_htlc_signatures { + failures.push(format!( + "{}: local HTLC signatures mismatch\n expected: {:?}\n actual: {:?}", + vector.name, vector.local_htlc_signatures, local_htlc_signatures, + )); + } - // Acceptor signs opener's commitment, then the opener verifies it. - let remote_signature = + // Acceptor signs opener's commitment and HTLC transactions, then the + // opener verifies it. + let (remote_signature, remote_htlc_signatures) = channel_config.sign_counterparty_commitment(&commitments, &acceptor_holder); if remote_signature != vector.remote_signature { failures.push(format!( @@ -153,24 +253,41 @@ impl TestVectorFile { vector.name, vector.remote_signature, remote_signature, )); } + if remote_htlc_signatures != vector.remote_htlc_signatures { + failures.push(format!( + "{}: remote HTLC signatures mismatch\n expected: {:?}\n actual: {:?}", + vector.name, vector.remote_htlc_signatures, remote_htlc_signatures, + )); + } if !channel_config.verify_counterparty_signature( &commitments, &opener_holder, &vector.remote_signature, + &vector.remote_htlc_signatures, ) { failures.push(format!("{}: remote signature does not verify", vector.name)); } - // Opener signs the acceptor's commitment, then the acceptor verifies it. - let acceptor_commit_sig = + // Opener signs the acceptor's commitment and HTLC transactions, then + // the acceptor verifies it. + let (acceptor_commit_sig, acceptor_htlc_sigs) = channel_config.sign_counterparty_commitment(&commitments, &opener_holder); + if acceptor_htlc_sigs.len() != vector.acceptor_num_htlcs { + failures.push(format!( + "{}: acceptor HTLC signature count mismatch\n expected: {}\n actual: {}", + vector.name, + vector.acceptor_num_htlcs, + acceptor_htlc_sigs.len(), + )); + } if !channel_config.verify_counterparty_signature( &commitments, &acceptor_holder, &acceptor_commit_sig, + &acceptor_htlc_sigs, ) { failures.push(format!( - "{}: acceptor commitment signature does not verify", + "{}: acceptor signature does not verify", vector.name, )); } diff --git a/smite/src/oracles/accept_channel.rs b/smite/src/oracles/accept_channel.rs index 50bc9e45..d59a8c02 100644 --- a/smite/src/oracles/accept_channel.rs +++ b/smite/src/oracles/accept_channel.rs @@ -412,7 +412,7 @@ fn verify_initial_commitment( // Check that the opener can afford the proposed feerate. let opener_balance_sat = (open_channel.funding_satoshis * 1000 - open_channel.push_msat) / 1000; - let commitment_cost = CommitmentCost::new(open_channel.feerate_per_kw, channel_type); + let commitment_cost = CommitmentCost::new(open_channel.feerate_per_kw, channel_type, 0); let Some(balance_after_fee) = opener_balance_sat.checked_sub(commitment_cost.fee_sat) else { return Err(format!( "opener balance {opener_balance_sat} sat cannot cover the commitment fee of {} sat", diff --git a/smite/src/oracles/funding_signed.rs b/smite/src/oracles/funding_signed.rs index c1d3fa4f..0d87af95 100644 --- a/smite/src/oracles/funding_signed.rs +++ b/smite/src/oracles/funding_signed.rs @@ -65,6 +65,7 @@ impl Oracle> for FundingSignedOracle { &channel.commitments, &channel.holder, &context.funding_signed.signature, + &[], ) { return Err(Violation::InvalidFundingSigned( context.funding_signed.channel_id, @@ -95,6 +96,7 @@ mod tests { /// Valid channel state for testing. fn channel_state() -> ChannelState { + let skey1 = secret_key(1); let pkey1 = pubkey(1); let pkey2 = pubkey(2); @@ -112,6 +114,7 @@ mod tests { payment_basepoint: pkey1, revocation_basepoint: pkey1, delayed_payment_basepoint: pkey1, + htlc_basepoint: pkey1, dust_limit_satoshis: 546, to_self_delay: 144, }, @@ -120,6 +123,7 @@ mod tests { payment_basepoint: pkey2, revocation_basepoint: pkey2, delayed_payment_basepoint: pkey2, + htlc_basepoint: pkey2, dust_limit_satoshis: 546, to_self_delay: 144, }, @@ -130,7 +134,8 @@ mod tests { .expect("valid initial commitments"); let holder = HolderIdentity { side: Side::Opener, - funding_privkey: secret_key(1), + funding_privkey: skey1, + htlc_basepoint_privkey: skey1, }; ChannelState::new(config, holder, commitments, true, false, false) @@ -138,15 +143,18 @@ mod tests { /// Valid `funding_signed` message for testing. fn funding_signed(channel: &ChannelState) -> FundingSigned { + let skey2 = secret_key(2); let acceptor = HolderIdentity { side: Side::Acceptor, - funding_privkey: secret_key(2), + funding_privkey: skey2, + htlc_basepoint_privkey: skey2, }; FundingSigned { channel_id: ChannelId::v1_from_funding_outpoint(channel.config.funding_outpoint), signature: channel .config - .sign_counterparty_commitment(&channel.commitments, &acceptor), + .sign_counterparty_commitment(&channel.commitments, &acceptor) + .0, } }