Skip to main content

sidestr_hitch/
lib.rs

1//! Lightning-shaped payment-channel transactions for sidestr and
2//! Bitcoin-family testnets.
3//!
4//! This is an attributed Rust port of `lib/channel.mjs` in Melvin Carvalho's
5//! [Hitch](https://github.com/bitcoin-blake/hitch), at commit `62f8e39`.
6//! Hitch is the Lightning construction without the Lightning network: two
7//! peers lock a coin in a 2-of-2 Taproot leaf, hold asymmetric revocable
8//! commitments, and update them off chain. Optional HTLC outputs add a
9//! preimage success path, an absolute timeout and a revocation path.
10//!
11//! A commitment's revocation key is a two-party key: the counterparty's
12//! basepoint plus the owner's per-state point ([`revocation_pub`]). The owner
13//! never holds its secret, so it cannot use its own revocation leaf; the
14//! counterparty learns the per-state secret when the state is revoked and
15//! then signs with [`revocation_key`]. Every announced point carries a proof
16//! of possession ([`pop_sign`]), so neither side can choose a point that
17//! cancels the other's.
18//!
19//! The transaction builders are paired with a pure peer state machine in
20//! [`protocol`] and the one-hop invoice decisions in [`route`]. Hosts provide
21//! relay transport, wallet funding, atomic snapshot storage, chain watches
22//! and broadcasting. Every builder supports both ordinary BIP 341 sighashes
23//! and Bitcoin Knots' unified sighash used by `txbt4`. This is a channel
24//! kernel, not a Lightning node, and it does not speak to Lightning peers.
25//!
26//! ```
27//! use bitcoin::hashes::Hash;
28//! use bitcoin::secp256k1::{Keypair, SecretKey};
29//! use bitcoin::{Amount, OutPoint, Txid};
30//! use sidestr_core::block::secp;
31//! use sidestr_hitch::{Balances, Channel, ChannelState, RevocationKeys, Side};
32//!
33//! let key_a = SecretKey::from_slice(&[0x11; 32]).unwrap();
34//! let key_b = SecretKey::from_slice(&[0x22; 32]).unwrap();
35//! let pub_a = Keypair::from_secret_key(secp(), &key_a).x_only_public_key().0;
36//! let pub_b = Keypair::from_secret_key(secp(), &key_b).x_only_public_key().0;
37//! let channel = Channel::new(
38//!     pub_a,
39//!     pub_b,
40//!     OutPoint { txid: Txid::all_zeros(), vout: 0 },
41//!     Amount::from_sat(100_000),
42//!     Amount::from_sat(300),
43//!     6,
44//! ).unwrap();
45//! let state = ChannelState {
46//!     balances: Balances { a: Amount::from_sat(60_000), b: Amount::from_sat(40_000) },
47//!     revocation: RevocationKeys { a: pub_a, b: pub_b },
48//!     htlcs: vec![],
49//! };
50//! let commitment = channel.commitment(1, Side::A, &state).unwrap();
51//! assert_eq!(commitment.local_value, Amount::from_sat(59_700));
52//! assert_eq!(commitment.remote_value, Amount::from_sat(40_000));
53//! ```
54
55#![deny(missing_docs)]
56
57use std::collections::BTreeMap;
58
59use bitcoin::hashes::{sha256, Hash, HashEngine};
60use bitcoin::key::{TapTweak, TweakedPublicKey};
61use bitcoin::secp256k1::{
62    schnorr::Signature, Keypair, Message, Parity, PublicKey, Scalar, SecretKey, XOnlyPublicKey,
63};
64use bitcoin::sighash::{Prevouts, SighashCache, TapSighashType};
65use bitcoin::taproot::{LeafVersion, TapLeafHash, TapNodeHash};
66use bitcoin::{
67    absolute::LockTime, transaction::Version, Amount, OutPoint, ScriptBuf, Sequence, Transaction,
68    TxIn, TxOut, Witness,
69};
70use sidestr_core::block::secp;
71use sidestr_core::federation::NUMS_X;
72use sidestr_core::sighash::{
73    key_path_hash_type, key_path_sighash, unified_taproot_sighash, SighashRules, UnifiedTaproot,
74};
75
76pub mod protocol;
77pub mod route;
78
79/// Outputs below this value are omitted from commitments, as in Hitch.
80pub const DUST: u64 = 330;
81/// Hitch's default per-transaction channel fee, in satoshis.
82pub const DEFAULT_FEE: u64 = 300;
83/// Hitch's default relative delay, in blocks.
84pub const DEFAULT_DELAY: u16 = 6;
85/// The shortest relative delay Hitch accepts.
86pub const MIN_DELAY: u16 = 3;
87/// The longest relative delay Hitch accepts.
88pub const MAX_DELAY: u16 = 144;
89/// The smallest per-transaction channel fee Hitch accepts, in satoshis.
90pub const MIN_FEE: u64 = 100;
91/// The largest per-transaction channel fee Hitch accepts, in satoshis.
92pub const MAX_FEE: u64 = 10_000;
93/// Values below this are block heights rather than timestamps in `nLockTime`.
94pub const MAX_EXPIRY: u32 = 500_000_000;
95
96const SIGHASH_ALL: u8 = 0x01;
97const INPUT_SEQUENCE: u32 = 0xffff_fffd;
98const LOCKTIME_SEQUENCE: u32 = 0xffff_fffe;
99
100/// An error while deriving scripts, building a state or signing a spend.
101#[derive(Debug, thiserror::Error)]
102pub enum Error {
103    /// The channel fee lies outside Hitch's accepted bounds.
104    #[error("fee must be between {MIN_FEE} and {MAX_FEE} sats")]
105    Fee,
106    /// The CSV delay lies outside Hitch's accepted bounds.
107    #[error("delay must be between {MIN_DELAY} and {MAX_DELAY} blocks")]
108    Delay,
109    /// An HTLC expiry is not an absolute block height.
110    #[error("HTLC expiry must be below {MAX_EXPIRY}")]
111    Expiry,
112    /// Free balances and HTLCs do not account for the funding output.
113    #[error("channel state accounts for {state} sats but funding holds {funding} sats")]
114    Unbalanced {
115        /// Value represented by free balances plus HTLCs.
116        state: u64,
117        /// Value locked in the funding output.
118        funding: u64,
119    },
120    /// A value sum overflowed a Bitcoin amount.
121    #[error("channel amount overflow")]
122    AmountOverflow,
123    /// No non-dust output remained after applying the fee.
124    #[error("nothing to pay out")]
125    NothingToPay,
126    /// A cooperative close was requested with an HTLC still in flight.
127    #[error("HTLCs in flight: settle or fail them first")]
128    HtlcsInFlight,
129    /// A claim would itself be dust after its fee.
130    #[error("the output is not worth claiming after the fee")]
131    DustClaim,
132    /// The requested commitment does not contain the output being claimed.
133    #[error("the commitment has no {0} output")]
134    MissingOutput(&'static str),
135    /// A success claim was requested without the 32-byte preimage.
136    #[error("an HTLC success claim needs its 32-byte preimage")]
137    MissingPreimage,
138    /// A script number was outside the non-negative 31-bit range Hitch uses.
139    #[error("script number out of range: {0}")]
140    ScriptNumber(u32),
141    /// Taproot's internal-key derivation failed.
142    #[error("NUMS derivation failed")]
143    Nums,
144    /// The signature hash could not be constructed.
145    #[error("signature hash failed: {0}")]
146    Sighash(&'static str),
147    /// A required funding signature was absent.
148    #[error("both funding signatures are needed")]
149    MissingSignature,
150    /// A Schnorr signature had the wrong encoding or hash type.
151    #[error("invalid leaf signature")]
152    InvalidSignature,
153    /// A two-party revocation key summed to the point at infinity or to zero.
154    #[error("bad revocation key")]
155    RevocationKey,
156    /// A secp256k1 key or tweak was invalid.
157    #[error(transparent)]
158    Secp(#[from] bitcoin::secp256k1::Error),
159}
160
161/// Result type returned by this crate.
162pub type Result<T> = std::result::Result<T, Error>;
163
164/// One of the two channel peers. `A` is the funder and pays channel fees.
165#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
166pub enum Side {
167    /// The funder.
168    A,
169    /// The other peer.
170    B,
171}
172
173impl Side {
174    /// The other peer.
175    pub const fn other(self) -> Self {
176        match self {
177            Self::A => Self::B,
178            Self::B => Self::A,
179        }
180    }
181}
182
183/// The two free balances in a channel state. HTLC values are separate.
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
185pub struct Balances {
186    /// Peer A's free balance.
187    pub a: Amount,
188    /// Peer B's free balance.
189    pub b: Amount,
190}
191
192impl Balances {
193    fn get(self, side: Side) -> Amount {
194        match side {
195            Side::A => self.a,
196            Side::B => self.b,
197        }
198    }
199}
200
201/// The revocation public key each owner put into this state.
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub struct RevocationKeys {
204    /// Revocation key for A's commitment.
205    pub a: XOnlyPublicKey,
206    /// Revocation key for B's commitment.
207    pub b: XOnlyPublicKey,
208}
209
210impl RevocationKeys {
211    fn get(self, side: Side) -> XOnlyPublicKey {
212        match side {
213            Side::A => self.a,
214            Side::B => self.b,
215        }
216    }
217}
218
219/// An HTLC carried by a channel state.
220#[derive(Debug, Clone, PartialEq, Eq)]
221pub struct Htlc {
222    /// Monotonically increasing identifier within the channel.
223    pub id: u64,
224    /// The peer offering the payment.
225    pub from: Side,
226    /// Value locked by the HTLC.
227    pub amount: Amount,
228    /// SHA-256 of the payment preimage.
229    pub payment_hash: [u8; 32],
230    /// Absolute block height after which the offerer may take the value back.
231    pub expiry: u32,
232}
233
234/// Free balances, revocation keys and outstanding HTLCs at one state number.
235#[derive(Debug, Clone, PartialEq, Eq)]
236pub struct ChannelState {
237    /// Balances not locked in HTLCs.
238    pub balances: Balances,
239    /// Per-owner revocation keys for this state.
240    pub revocation: RevocationKeys,
241    /// Outstanding HTLCs. Builders order them by `id` on the wire.
242    pub htlcs: Vec<Htlc>,
243}
244
245/// One Taproot leaf and the control block that proves it to its output.
246#[derive(Debug, Clone, PartialEq, Eq)]
247pub struct LeafSpend {
248    /// The exact tapscript bytes.
249    pub script: ScriptBuf,
250    /// `TapLeaf` hash of `script` under tapscript leaf version `0xc0`.
251    pub leaf_hash: TapLeafHash,
252    /// BIP 341 control block for this leaf.
253    pub control_block: Vec<u8>,
254    /// Whether a claim through this leaf must use the channel's CSV delay.
255    pub csv: bool,
256}
257
258/// Hitch's 2-of-2 funding output.
259#[derive(Debug, Clone, PartialEq, Eq)]
260pub struct FundingScript {
261    /// The unspendable x-only internal key.
262    pub internal_key: XOnlyPublicKey,
263    /// The tweaked Taproot output key.
264    pub output_key: TweakedPublicKey,
265    /// `OP_1 <output_key>`.
266    pub script_pubkey: ScriptBuf,
267    /// Keys in lexicographic leaf order.
268    pub signers: [XOnlyPublicKey; 2],
269    /// The `multi_a(2, a, b)` leaf and its control block.
270    pub leaf: LeafSpend,
271}
272
273/// A revocable delayed output in one commitment transaction.
274#[derive(Debug, Clone, PartialEq, Eq)]
275pub struct ToLocalScript {
276    /// The tweaked Taproot output key.
277    pub output_key: TweakedPublicKey,
278    /// `OP_1 <output_key>`.
279    pub script_pubkey: ScriptBuf,
280    /// The owner's `CHECKSEQUENCEVERIFY` path.
281    pub delayed: LeafSpend,
282    /// The counterparty's immediate revocation path.
283    pub revocation: LeafSpend,
284}
285
286/// A three-path HTLC output in one commitment transaction.
287#[derive(Debug, Clone, PartialEq, Eq)]
288pub struct HtlcScript {
289    /// The tweaked Taproot output key.
290    pub output_key: TweakedPublicKey,
291    /// `OP_1 <output_key>`.
292    pub script_pubkey: ScriptBuf,
293    /// Receiver claim with the payment preimage.
294    pub success: LeafSpend,
295    /// Offerer refund after the absolute expiry.
296    pub timeout: LeafSpend,
297    /// Counterparty claim with the revoked state's secret.
298    pub revocation: LeafSpend,
299}
300
301/// An output's role in a commitment transaction.
302#[derive(Debug, Clone, Copy, PartialEq, Eq)]
303pub enum OutputKind {
304    /// Delayed and revocable value of the commitment owner.
305    ToLocal,
306    /// Immediate key-path value of the other peer.
307    ToRemote,
308    /// Hash-and-time-locked value.
309    Htlc,
310}
311
312/// An HTLC after its exact output index and scripts have been derived for a
313/// particular commitment owner.
314#[derive(Debug, Clone, PartialEq, Eq)]
315pub struct CommitmentHtlc {
316    /// Logical HTLC shared by the two asymmetric commitments.
317    pub htlc: Htlc,
318    /// Output index in this commitment.
319    pub vout: u32,
320    /// The three script paths for this commitment owner.
321    pub scripts: HtlcScript,
322    /// Whether this commitment's owner offered the HTLC.
323    pub offered_by_owner: bool,
324}
325
326/// One peer's fully derived commitment transaction for a state.
327#[derive(Debug, Clone, PartialEq, Eq)]
328pub struct Commitment {
329    /// Unsigned transaction until both funding signatures are installed.
330    pub tx: Transaction,
331    /// Scripts for the owner's delayed output, whether or not it was dust.
332    pub to_local: ToLocalScript,
333    /// Output roles in transaction order.
334    pub kinds: Vec<OutputKind>,
335    /// HTLC metadata with final output positions.
336    pub htlcs: Vec<CommitmentHtlc>,
337    /// Owner payout after the funder's channel fee, or zero when omitted.
338    pub local_value: Amount,
339    /// Counterparty payout after the funder's channel fee, or zero when omitted.
340    pub remote_value: Amount,
341    /// Position of the `to_local` output when it is not dust.
342    pub to_local_vout: Option<u32>,
343    /// State number authorised by this transaction.
344    pub number: u64,
345    /// Peer who can broadcast this asymmetric commitment.
346    pub owner: Side,
347    /// CSV delay copied from the channel.
348    pub delay: u16,
349}
350
351/// A 65-byte tapscript signature: BIP-340 signature followed by its explicit
352/// sighash type (`0x01` or Knots' `0x21`).
353#[derive(Debug, Clone, Copy, PartialEq, Eq)]
354pub struct LeafSignature([u8; 65]);
355
356impl LeafSignature {
357    /// Parse exactly 65 bytes.
358    pub fn from_slice(bytes: &[u8]) -> Result<Self> {
359        let bytes: [u8; 65] = bytes.try_into().map_err(|_| Error::InvalidSignature)?;
360        Signature::from_slice(&bytes[..64]).map_err(|_| Error::InvalidSignature)?;
361        Ok(Self(bytes))
362    }
363
364    /// Borrow the witness encoding.
365    pub fn as_bytes(&self) -> &[u8; 65] {
366        &self.0
367    }
368
369    /// Encode the signature and explicit hash type as lower-case hex.
370    pub fn to_hex(self) -> String {
371        hex_bytes(&self.0)
372    }
373
374    /// Parse the 130-character wire form Hitch carries in messages.
375    pub fn from_hex(text: &str) -> Result<Self> {
376        if text.len() != 130 {
377            return Err(Error::InvalidSignature);
378        }
379        let mut bytes = [0u8; 65];
380        decode_hex_into(text, &mut bytes).ok_or(Error::InvalidSignature)?;
381        Self::from_slice(&bytes)
382    }
383}
384
385/// A 64-byte BIP 340 proof of possession of a revocation secret, carried as
386/// 128 lower-case hex characters on Hitch's wire.
387///
388/// See [`pop_sign`] and [`pop_verify`].
389#[derive(Debug, Clone, Copy, PartialEq, Eq)]
390pub struct PopSignature([u8; 64]);
391
392impl PopSignature {
393    /// Wrap exactly 64 signature bytes.
394    pub fn from_bytes(bytes: [u8; 64]) -> Self {
395        Self(bytes)
396    }
397
398    /// Borrow the signature bytes.
399    pub fn as_bytes(&self) -> &[u8; 64] {
400        &self.0
401    }
402
403    /// Encode as lower-case hex.
404    pub fn to_hex(self) -> String {
405        hex_bytes(&self.0)
406    }
407
408    /// Parse the 128-character lower-case wire form.
409    pub fn from_hex(text: &str) -> Result<Self> {
410        let mut bytes = [0u8; 64];
411        decode_hex_into(text, &mut bytes).ok_or(Error::InvalidSignature)?;
412        Ok(Self(bytes))
413    }
414}
415
416/// A funded channel and its invariant parameters.
417#[derive(Debug, Clone, PartialEq, Eq)]
418pub struct Channel {
419    /// A's public key followed by B's; this order defines roles, not the leaf.
420    pub keys: [XOnlyPublicKey; 2],
421    /// Confirmed funding outpoint.
422    pub funding_outpoint: OutPoint,
423    /// Value of the funding output.
424    pub funding_value: Amount,
425    /// Exact funding output construction.
426    pub funding_script: FundingScript,
427    /// Fee charged to A in every commitment or close.
428    pub fee: Amount,
429    /// Relative delay on commitment-owner claims.
430    pub delay: u16,
431}
432
433impl Channel {
434    /// Construct a channel after checking Hitch's fee and delay bounds.
435    pub fn new(
436        a: XOnlyPublicKey,
437        b: XOnlyPublicKey,
438        funding_outpoint: OutPoint,
439        funding_value: Amount,
440        fee: Amount,
441        delay: u16,
442    ) -> Result<Self> {
443        if !(MIN_FEE..=MAX_FEE).contains(&fee.to_sat()) {
444            return Err(Error::Fee);
445        }
446        if !(MIN_DELAY..=MAX_DELAY).contains(&delay) {
447            return Err(Error::Delay);
448        }
449        Ok(Self {
450            keys: [a, b],
451            funding_outpoint,
452            funding_value,
453            funding_script: funding_script(a, b)?,
454            fee,
455            delay,
456        })
457    }
458
459    /// A or B's public key by protocol role.
460    pub fn key(&self, side: Side) -> XOnlyPublicKey {
461        match side {
462            Side::A => self.keys[0],
463            Side::B => self.keys[1],
464        }
465    }
466
467    /// Funding output as a previous output for signature hashing.
468    pub fn funding_prevout(&self) -> TxOut {
469        TxOut {
470            value: self.funding_value,
471            script_pubkey: self.funding_script.script_pubkey.clone(),
472        }
473    }
474
475    /// Derive one peer's asymmetric commitment for `state`.
476    pub fn commitment(&self, number: u64, owner: Side, state: &ChannelState) -> Result<Commitment> {
477        self.check_state(state)?;
478        let remote_side = owner.other();
479        let owner_pub = self.key(owner);
480        let remote_pub = self.key(remote_side);
481        let local = payout_after_fee(state.balances.get(owner), owner, self.fee);
482        let remote = payout_after_fee(state.balances.get(remote_side), remote_side, self.fee);
483        let to_local = to_local_script(owner_pub, state.revocation.get(owner), self.delay)?;
484        let mut output = Vec::new();
485        let mut kinds = Vec::new();
486        let mut to_local_vout = None;
487        if local.to_sat() >= DUST {
488            to_local_vout = Some(output.len() as u32);
489            output.push(TxOut {
490                value: local,
491                script_pubkey: to_local.script_pubkey.clone(),
492            });
493            kinds.push(OutputKind::ToLocal);
494        }
495        if remote.to_sat() >= DUST {
496            output.push(TxOut {
497                value: remote,
498                script_pubkey: to_remote_script(remote_pub),
499            });
500            kinds.push(OutputKind::ToRemote);
501        }
502
503        let mut sorted = state.htlcs.clone();
504        sorted.sort_by_key(|h| h.id);
505        let mut htlcs = Vec::new();
506        for htlc in sorted {
507            if htlc.expiry >= MAX_EXPIRY {
508                return Err(Error::Expiry);
509            }
510            if htlc.amount.to_sat() < DUST {
511                continue;
512            }
513            let offered_by_owner = htlc.from == owner;
514            let scripts = htlc_script(HtlcScriptArgs {
515                owner_pub,
516                remote_pub,
517                revocation_pub: state.revocation.get(owner),
518                delay: self.delay,
519                payment_hash: htlc.payment_hash,
520                expiry: htlc.expiry,
521                offered_by_owner,
522            })?;
523            let vout = output.len() as u32;
524            output.push(TxOut {
525                value: htlc.amount,
526                script_pubkey: scripts.script_pubkey.clone(),
527            });
528            kinds.push(OutputKind::Htlc);
529            htlcs.push(CommitmentHtlc {
530                htlc,
531                vout,
532                scripts,
533                offered_by_owner,
534            });
535        }
536        if output.is_empty() {
537            return Err(Error::NothingToPay);
538        }
539        Ok(Commitment {
540            tx: one_input_tx(
541                self.funding_outpoint,
542                INPUT_SEQUENCE,
543                LockTime::ZERO,
544                output,
545            ),
546            to_local,
547            kinds,
548            htlcs,
549            local_value: local,
550            remote_value: remote,
551            to_local_vout,
552            number,
553            owner,
554            delay: self.delay,
555        })
556    }
557
558    /// Cooperative close paying both free balances directly to their keys.
559    pub fn cooperative_close(&self, state: &ChannelState) -> Result<Transaction> {
560        if !state.htlcs.is_empty() {
561            return Err(Error::HtlcsInFlight);
562        }
563        self.check_state(state)?;
564        let mut output = Vec::new();
565        let a = payout_after_fee(state.balances.a, Side::A, self.fee);
566        if a.to_sat() >= DUST {
567            output.push(TxOut {
568                value: a,
569                script_pubkey: to_remote_script(self.keys[0]),
570            });
571        }
572        if state.balances.b.to_sat() >= DUST {
573            output.push(TxOut {
574                value: state.balances.b,
575                script_pubkey: to_remote_script(self.keys[1]),
576            });
577        }
578        if output.is_empty() {
579            return Err(Error::NothingToPay);
580        }
581        Ok(one_input_tx(
582            self.funding_outpoint,
583            INPUT_SEQUENCE,
584            LockTime::ZERO,
585            output,
586        ))
587    }
588
589    /// Sign a commitment or close through the 2-of-2 funding leaf.
590    pub fn sign_funding(
591        &self,
592        tx: &Transaction,
593        key: &SecretKey,
594        rules: SighashRules,
595        aux: &[u8; 32],
596    ) -> Result<LeafSignature> {
597        sign_leaf(
598            tx,
599            0,
600            &[self.funding_prevout()],
601            self.funding_script.leaf.leaf_hash,
602            key,
603            rules,
604            aux,
605        )
606    }
607
608    /// Check one peer's signature on a commitment or close.
609    pub fn verify_funding(
610        &self,
611        tx: &Transaction,
612        pubkey: &XOnlyPublicKey,
613        signature: &LeafSignature,
614        rules: SighashRules,
615    ) -> bool {
616        verify_leaf_signature(
617            tx,
618            0,
619            &[self.funding_prevout()],
620            self.funding_script.leaf.leaf_hash,
621            pubkey,
622            signature,
623            rules,
624        )
625    }
626
627    /// Assemble the funding witness. Slots are reversed from leaf order so
628    /// the first key's signature is on top of the tapscript stack.
629    pub fn funding_witness(
630        &self,
631        signatures: &BTreeMap<XOnlyPublicKey, LeafSignature>,
632    ) -> Result<Witness> {
633        let mut slots = self
634            .funding_script
635            .signers
636            .iter()
637            .map(|key| {
638                signatures
639                    .get(key)
640                    .map(|signature| signature.as_bytes().to_vec())
641                    .ok_or(Error::MissingSignature)
642            })
643            .collect::<Result<Vec<_>>>()?;
644        slots.reverse();
645        slots.push(self.funding_script.leaf.script.to_bytes());
646        slots.push(self.funding_script.leaf.control_block.clone());
647        Ok(Witness::from_slice(&slots))
648    }
649
650    fn check_state(&self, state: &ChannelState) -> Result<()> {
651        let total = state.htlcs.iter().try_fold(
652            state
653                .balances
654                .a
655                .to_sat()
656                .checked_add(state.balances.b.to_sat())
657                .ok_or(Error::AmountOverflow)?,
658            |sum, htlc| {
659                sum.checked_add(htlc.amount.to_sat())
660                    .ok_or(Error::AmountOverflow)
661            },
662        )?;
663        if total != self.funding_value.to_sat() {
664            return Err(Error::Unbalanced {
665                state: total,
666                funding: self.funding_value.to_sat(),
667            });
668        }
669        Ok(())
670    }
671}
672
673/// Arguments needed to derive one commitment owner's HTLC scripts.
674#[derive(Debug, Clone, Copy, PartialEq, Eq)]
675pub struct HtlcScriptArgs {
676    /// Commitment owner's key.
677    pub owner_pub: XOnlyPublicKey,
678    /// Other peer's key.
679    pub remote_pub: XOnlyPublicKey,
680    /// Revocation key for this commitment owner and state.
681    pub revocation_pub: XOnlyPublicKey,
682    /// Channel CSV delay.
683    pub delay: u16,
684    /// SHA-256 of the payment preimage.
685    pub payment_hash: [u8; 32],
686    /// Absolute timeout height.
687    pub expiry: u32,
688    /// Whether the commitment owner offered this HTLC.
689    pub offered_by_owner: bool,
690}
691
692/// Derive Hitch's fixed unspendable internal key: BIP 341's `H` tweaked by
693/// `tagged_hash("hitch/nums", "hitch")`.
694pub fn internal_key() -> Result<XOnlyPublicKey> {
695    let tweak = tagged_sha256(b"hitch/nums", b"hitch");
696    let mut encoded = [0u8; 33];
697    encoded[0] = 0x02;
698    encoded[1..].copy_from_slice(&NUMS_X);
699    let point = PublicKey::from_slice(&encoded)?;
700    let scalar = Scalar::from_be_bytes(tweak).map_err(|_| Error::Nums)?;
701    Ok(point.add_exp_tweak(secp(), &scalar)?.x_only_public_key().0)
702}
703
704/// Negate `secret` when its public point has an odd y coordinate, so the
705/// result's point is the even lift of the same x-only key. This is Hitch's
706/// `evenSecret`.
707pub fn even_secret(secret: &SecretKey) -> SecretKey {
708    let (_, parity) = secret.x_only_public_key(secp());
709    if parity == Parity::Odd {
710        secret.negate()
711    } else {
712        *secret
713    }
714}
715
716/// The public half of a two-party revocation key: `lift_x(point) +
717/// even(scalar)·G`, as an x-only key. This is Hitch's `revocationPub`.
718///
719/// A commitment's revocation key combines the counterparty's basepoint `R`
720/// with the owner's per-state point `S_i`. The owner computes it as
721/// `revocation_pub(R, s_i)`, the counterparty as `revocation_pub(S_i, r)`,
722/// and both obtain `R + S_i`. Neither side alone knows its discrete
723/// logarithm; the counterparty learns it only when the owner reveals `s_i`
724/// to revoke the state (see [`revocation_key`]).
725///
726/// ```
727/// use bitcoin::secp256k1::{Keypair, SecretKey};
728/// use sidestr_core::block::secp;
729/// use sidestr_hitch::{revocation_key, revocation_pub};
730///
731/// let r = SecretKey::from_slice(&[0x33; 32]).unwrap();
732/// let s = SecretKey::from_slice(&[0x44; 32]).unwrap();
733/// let big_r = Keypair::from_secret_key(secp(), &r).x_only_public_key().0;
734/// let big_s = Keypair::from_secret_key(secp(), &s).x_only_public_key().0;
735/// let owner_view = revocation_pub(big_r, &s).unwrap();
736/// assert_eq!(owner_view, revocation_pub(big_s, &r).unwrap());
737/// let key = revocation_key(&r, &s).unwrap();
738/// assert_eq!(Keypair::from_secret_key(secp(), &key).x_only_public_key().0, owner_view);
739/// ```
740pub fn revocation_pub(point: XOnlyPublicKey, scalar: &SecretKey) -> Result<XOnlyPublicKey> {
741    let lifted = point.public_key(Parity::Even);
742    let tweak = Scalar::from(even_secret(scalar));
743    let combined = lifted
744        .add_exp_tweak(secp(), &tweak)
745        .map_err(|_| Error::RevocationKey)?;
746    Ok(combined.x_only_public_key().0)
747}
748
749/// The secret of a two-party revocation key: `even(basepoint_secret) +
750/// even(per_state_secret) mod n`. This is Hitch's `revocationKey`; it signs
751/// the revocation leaves of a commitment whose public key is
752/// [`revocation_pub`].
753pub fn revocation_key(basepoint_secret: &SecretKey, per_state: &SecretKey) -> Result<SecretKey> {
754    even_secret(basepoint_secret)
755        .add_tweak(&Scalar::from(even_secret(per_state)))
756        .map_err(|_| Error::RevocationKey)
757}
758
759fn pop_message(point: &XOnlyPublicKey, context: &str) -> [u8; 32] {
760    let mut message = Vec::with_capacity(context.len() + 32);
761    message.extend_from_slice(context.as_bytes());
762    message.extend_from_slice(&point.serialize());
763    tagged_sha256(b"hitch/pop", &message)
764}
765
766/// Prove possession of a revocation secret: a BIP 340 signature by `secret`
767/// over `tagged_hash("hitch/pop", context || point)`. Hitch's `context` is
768/// `"<channel id>/<owner role>/<base or state number>"`.
769///
770/// A plain sum of points is open to a rogue key: a peer could announce
771/// `k·G − R` and hold `k`. Every announced revocation point therefore
772/// carries this proof, and [`pop_verify`] runs before the point is kept.
773pub fn pop_sign(secret: &SecretKey, context: &str, aux: &[u8; 32]) -> PopSignature {
774    let keypair = Keypair::from_secret_key(secp(), secret);
775    let point = keypair.x_only_public_key().0;
776    let message = Message::from_digest(pop_message(&point, context));
777    let signature = secp().sign_schnorr_with_aux_rand(&message, &keypair, aux);
778    let mut bytes = [0u8; 64];
779    bytes.copy_from_slice(signature.as_ref());
780    PopSignature(bytes)
781}
782
783/// Check a proof of possession made by [`pop_sign`] for `point` in
784/// `context`. Malformed signatures verify as `false`.
785pub fn pop_verify(point: &XOnlyPublicKey, signature: &PopSignature, context: &str) -> bool {
786    let Ok(signature) = Signature::from_slice(&signature.0) else {
787        return false;
788    };
789    let message = Message::from_digest(pop_message(point, context));
790    secp().verify_schnorr(&signature, &message, point).is_ok()
791}
792
793/// The payment preimage revealed by a spend of an HTLC output, if any
794/// 32-byte witness item hashes to `payment_hash` under SHA-256. This is
795/// Hitch's `preimageIn`, used to carry a preimage the counterparty revealed
796/// on the chain back upstream.
797pub fn preimage_in(tx: &Transaction, payment_hash: &[u8; 32]) -> Option<[u8; 32]> {
798    tx.input
799        .iter()
800        .flat_map(|input| input.witness.iter())
801        .filter(|item| item.len() == 32)
802        .find(|item| sha256::Hash::hash(item).to_byte_array() == *payment_hash)
803        .map(|item| item.try_into().expect("a 32-byte witness item"))
804}
805
806/// Construct the 2-of-2 funding script. Keys are sorted lexicographically in
807/// the leaf, independently of their A/B protocol roles.
808pub fn funding_script(a: XOnlyPublicKey, b: XOnlyPublicKey) -> Result<FundingScript> {
809    let mut signers = [a, b];
810    signers.sort_by_key(XOnlyPublicKey::serialize);
811    let mut bytes = Vec::with_capacity(70);
812    push_key(&mut bytes, signers[0]);
813    bytes.push(0xac); // CHECKSIG
814    push_key(&mut bytes, signers[1]);
815    bytes.extend_from_slice(&[0xba, 0x52, 0x9c]); // CHECKSIGADD 2 NUMEQUAL
816    let script = ScriptBuf::from_bytes(bytes);
817    let leaf_hash = TapLeafHash::from_script(&script, LeafVersion::TapScript);
818    let internal_key = internal_key()?;
819    let (output_key, parity, script_pubkey) = taproot_output(internal_key, leaf_hash.into());
820    Ok(FundingScript {
821        internal_key,
822        output_key,
823        script_pubkey,
824        signers,
825        leaf: LeafSpend {
826            script,
827            leaf_hash,
828            control_block: control_block(internal_key, parity, &[]),
829            csv: false,
830        },
831    })
832}
833
834/// Construct the owner's delayed leaf and the counterparty's revocation
835/// leaf, rooted as `branch(delayed, revocation)`.
836pub fn to_local_script(
837    owner_pub: XOnlyPublicKey,
838    revocation_pub: XOnlyPublicKey,
839    delay: u16,
840) -> Result<ToLocalScript> {
841    let mut delayed = Vec::new();
842    push_num(&mut delayed, u32::from(delay))?;
843    delayed.extend_from_slice(&[0xb2, 0x75]); // CSV DROP
844    push_key(&mut delayed, owner_pub);
845    delayed.push(0xac);
846    let delayed = ScriptBuf::from_bytes(delayed);
847
848    let mut revocation = Vec::new();
849    push_key(&mut revocation, revocation_pub);
850    revocation.push(0xac);
851    let revocation = ScriptBuf::from_bytes(revocation);
852
853    let delayed_hash = TapLeafHash::from_script(&delayed, LeafVersion::TapScript);
854    let revocation_hash = TapLeafHash::from_script(&revocation, LeafVersion::TapScript);
855    let root = TapNodeHash::from_node_hashes(delayed_hash.into(), revocation_hash.into());
856    let internal_key = internal_key()?;
857    let (output_key, parity, script_pubkey) = taproot_output(internal_key, root);
858    Ok(ToLocalScript {
859        output_key,
860        script_pubkey,
861        delayed: LeafSpend {
862            script: delayed,
863            leaf_hash: delayed_hash,
864            control_block: control_block(internal_key, parity, &[revocation_hash.into()]),
865            csv: true,
866        },
867        revocation: LeafSpend {
868            script: revocation,
869            leaf_hash: revocation_hash,
870            control_block: control_block(internal_key, parity, &[delayed_hash.into()]),
871            csv: false,
872        },
873    })
874}
875
876/// A direct key-path output using Hitch's x-only channel key literally.
877pub fn to_remote_script(pubkey: XOnlyPublicKey) -> ScriptBuf {
878    let mut script = Vec::with_capacity(34);
879    script.extend_from_slice(&[0x51, 0x20]);
880    script.extend_from_slice(&pubkey.serialize());
881    ScriptBuf::from_bytes(script)
882}
883
884/// Construct the success, timeout and revocation leaves for an HTLC. The
885/// tree is `branch(branch(success, timeout), revocation)`.
886pub fn htlc_script(args: HtlcScriptArgs) -> Result<HtlcScript> {
887    if args.expiry >= MAX_EXPIRY {
888        return Err(Error::Expiry);
889    }
890    let mut success = Vec::new();
891    success.extend_from_slice(&[0xa8, 0x20]); // SHA256 PUSH32
892    success.extend_from_slice(&args.payment_hash);
893    success.push(0x88); // EQUALVERIFY
894    if !args.offered_by_owner {
895        push_num(&mut success, u32::from(args.delay))?;
896        success.extend_from_slice(&[0xb2, 0x75]); // CSV DROP
897    }
898    push_key(
899        &mut success,
900        if args.offered_by_owner {
901            args.remote_pub
902        } else {
903            args.owner_pub
904        },
905    );
906    success.push(0xac);
907    let success = ScriptBuf::from_bytes(success);
908
909    let mut timeout = Vec::new();
910    push_num(&mut timeout, args.expiry)?;
911    timeout.extend_from_slice(&[0xb1, 0x75]); // CLTV DROP
912    if args.offered_by_owner {
913        push_num(&mut timeout, u32::from(args.delay))?;
914        timeout.extend_from_slice(&[0xb2, 0x75]); // CSV DROP
915    }
916    push_key(
917        &mut timeout,
918        if args.offered_by_owner {
919            args.owner_pub
920        } else {
921            args.remote_pub
922        },
923    );
924    timeout.push(0xac);
925    let timeout = ScriptBuf::from_bytes(timeout);
926
927    let mut revocation = Vec::new();
928    push_key(&mut revocation, args.revocation_pub);
929    revocation.push(0xac);
930    let revocation = ScriptBuf::from_bytes(revocation);
931
932    let success_hash = TapLeafHash::from_script(&success, LeafVersion::TapScript);
933    let timeout_hash = TapLeafHash::from_script(&timeout, LeafVersion::TapScript);
934    let revocation_hash = TapLeafHash::from_script(&revocation, LeafVersion::TapScript);
935    let inner = TapNodeHash::from_node_hashes(success_hash.into(), timeout_hash.into());
936    let root = TapNodeHash::from_node_hashes(inner, revocation_hash.into());
937    let internal_key = internal_key()?;
938    let (output_key, parity, script_pubkey) = taproot_output(internal_key, root);
939    Ok(HtlcScript {
940        output_key,
941        script_pubkey,
942        success: LeafSpend {
943            script: success,
944            leaf_hash: success_hash,
945            control_block: control_block(
946                internal_key,
947                parity,
948                &[timeout_hash.into(), revocation_hash.into()],
949            ),
950            csv: !args.offered_by_owner,
951        },
952        timeout: LeafSpend {
953            script: timeout,
954            leaf_hash: timeout_hash,
955            control_block: control_block(
956                internal_key,
957                parity,
958                &[success_hash.into(), revocation_hash.into()],
959            ),
960            csv: args.offered_by_owner,
961        },
962        revocation: LeafSpend {
963            script: revocation,
964            leaf_hash: revocation_hash,
965            control_block: control_block(internal_key, parity, &[inner]),
966            csv: false,
967        },
968    })
969}
970
971/// Compute a tapscript signature hash and its explicit wire hash type.
972pub fn script_path_sighash(
973    tx: &Transaction,
974    input_index: usize,
975    prevouts: &[TxOut],
976    leaf_hash: TapLeafHash,
977    rules: SighashRules,
978) -> Result<([u8; 32], u8)> {
979    let hash_type = SIGHASH_ALL
980        | if rules == SighashRules::KnotsUnified {
981            sidestr_core::sighash::SIGHASH_UNIFIED
982        } else {
983            0
984        };
985    let leaf_hash_bytes = leaf_hash.to_byte_array();
986    let message = match rules {
987        SighashRules::KnotsUnified => unified_taproot_sighash(
988            tx,
989            input_index,
990            prevouts,
991            hash_type,
992            None,
993            UnifiedTaproot::ScriptPath {
994                leaf_hash: &leaf_hash_bytes,
995                codesep_pos: u32::MAX,
996            },
997        )
998        .map_err(Error::Sighash)?,
999        SighashRules::Bip341 => SighashCache::new(tx)
1000            .taproot_script_spend_signature_hash(
1001                input_index,
1002                &Prevouts::All(prevouts),
1003                leaf_hash,
1004                TapSighashType::All,
1005            )
1006            .map_err(|_| Error::Sighash("taproot script-path sighash"))?
1007            .to_byte_array(),
1008    };
1009    Ok((message, hash_type))
1010}
1011
1012/// Sign one tapscript leaf with an explicit hash type byte.
1013pub fn sign_leaf(
1014    tx: &Transaction,
1015    input_index: usize,
1016    prevouts: &[TxOut],
1017    leaf_hash: TapLeafHash,
1018    key: &SecretKey,
1019    rules: SighashRules,
1020    aux: &[u8; 32],
1021) -> Result<LeafSignature> {
1022    let (message, hash_type) = script_path_sighash(tx, input_index, prevouts, leaf_hash, rules)?;
1023    let keypair = Keypair::from_secret_key(secp(), key);
1024    let signature =
1025        secp().sign_schnorr_with_aux_rand(&Message::from_digest(message), &keypair, aux);
1026    let mut wire = [0u8; 65];
1027    wire[..64].copy_from_slice(signature.as_ref());
1028    wire[64] = hash_type;
1029    Ok(LeafSignature(wire))
1030}
1031
1032/// Verify one tapscript signature under the same family rules used to sign.
1033pub fn verify_leaf_signature(
1034    tx: &Transaction,
1035    input_index: usize,
1036    prevouts: &[TxOut],
1037    leaf_hash: TapLeafHash,
1038    pubkey: &XOnlyPublicKey,
1039    signature: &LeafSignature,
1040    rules: SighashRules,
1041) -> bool {
1042    let Ok((message, hash_type)) = script_path_sighash(tx, input_index, prevouts, leaf_hash, rules)
1043    else {
1044        return false;
1045    };
1046    if signature.0[64] != hash_type {
1047        return false;
1048    }
1049    let Ok(signature) = Signature::from_slice(&signature.0[..64]) else {
1050        return false;
1051    };
1052    secp()
1053        .verify_schnorr(&signature, &Message::from_digest(message), pubkey)
1054        .is_ok()
1055}
1056
1057/// Which `to_local` leaf a sweep uses.
1058#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1059pub enum SweepPath {
1060    /// Owner claim after the channel delay.
1061    Delayed,
1062    /// Counterparty penalty with the revoked state's secret.
1063    Revocation,
1064}
1065
1066/// Spend a commitment's `to_local` output through its delayed or revocation
1067/// leaf and install the complete witness.
1068pub fn sweep_to_local(
1069    commitment: &Commitment,
1070    path: SweepPath,
1071    destination: ScriptBuf,
1072    fee: Amount,
1073    key: &SecretKey,
1074    rules: SighashRules,
1075    aux: &[u8; 32],
1076) -> Result<Transaction> {
1077    let vout = commitment
1078        .to_local_vout
1079        .ok_or(Error::MissingOutput("to_local"))?;
1080    let value = commitment.local_value;
1081    let output_value = claim_value(value, fee)?;
1082    let (leaf, sequence) = match path {
1083        SweepPath::Delayed => (&commitment.to_local.delayed, u32::from(commitment.delay)),
1084        SweepPath::Revocation => (&commitment.to_local.revocation, INPUT_SEQUENCE),
1085    };
1086    let mut tx = one_input_tx(
1087        OutPoint {
1088            txid: commitment.tx.compute_txid(),
1089            vout,
1090        },
1091        sequence,
1092        LockTime::ZERO,
1093        vec![TxOut {
1094            value: output_value,
1095            script_pubkey: destination,
1096        }],
1097    );
1098    let prevouts = [TxOut {
1099        value,
1100        script_pubkey: commitment.to_local.script_pubkey.clone(),
1101    }];
1102    let signature = sign_leaf(&tx, 0, &prevouts, leaf.leaf_hash, key, rules, aux)?;
1103    tx.input[0].witness = Witness::from_slice(&[
1104        signature.as_bytes().as_slice(),
1105        leaf.script.as_bytes(),
1106        &leaf.control_block,
1107    ]);
1108    Ok(tx)
1109}
1110
1111/// Which leaf claims an HTLC output.
1112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1113pub enum HtlcClaimPath {
1114    /// Receiver claim with the preimage.
1115    Success,
1116    /// Offerer refund after expiry.
1117    Timeout,
1118    /// Counterparty penalty against a revoked commitment.
1119    Revocation,
1120}
1121
1122/// Output and witness choices for an HTLC claim transaction.
1123#[derive(Debug, Clone, PartialEq, Eq)]
1124pub struct HtlcClaim {
1125    /// Script leaf used to spend the HTLC.
1126    pub path: HtlcClaimPath,
1127    /// Script receiving the claimed value.
1128    pub destination: ScriptBuf,
1129    /// Absolute miner fee subtracted from the HTLC value.
1130    pub fee: Amount,
1131    /// Payment preimage, required only for [`HtlcClaimPath::Success`].
1132    pub preimage: Option<[u8; 32]>,
1133}
1134
1135/// Spend one HTLC output through its success, timeout or revocation leaf.
1136pub fn claim_htlc(
1137    commitment: &Commitment,
1138    htlc: &CommitmentHtlc,
1139    claim: HtlcClaim,
1140    key: &SecretKey,
1141    rules: SighashRules,
1142    aux: &[u8; 32],
1143) -> Result<Transaction> {
1144    let (leaf, lock_time) = match claim.path {
1145        HtlcClaimPath::Success => (&htlc.scripts.success, LockTime::ZERO),
1146        HtlcClaimPath::Timeout => (
1147            &htlc.scripts.timeout,
1148            LockTime::from_consensus(htlc.htlc.expiry),
1149        ),
1150        HtlcClaimPath::Revocation => (&htlc.scripts.revocation, LockTime::ZERO),
1151    };
1152    let sequence = if claim.path == HtlcClaimPath::Revocation {
1153        INPUT_SEQUENCE
1154    } else if leaf.csv {
1155        u32::from(commitment.delay)
1156    } else {
1157        LOCKTIME_SEQUENCE
1158    };
1159    let output_value = claim_value(htlc.htlc.amount, claim.fee)?;
1160    let mut tx = one_input_tx(
1161        OutPoint {
1162            txid: commitment.tx.compute_txid(),
1163            vout: htlc.vout,
1164        },
1165        sequence,
1166        lock_time,
1167        vec![TxOut {
1168            value: output_value,
1169            script_pubkey: claim.destination,
1170        }],
1171    );
1172    let prevouts = [TxOut {
1173        value: htlc.htlc.amount,
1174        script_pubkey: htlc.scripts.script_pubkey.clone(),
1175    }];
1176    let signature = sign_leaf(&tx, 0, &prevouts, leaf.leaf_hash, key, rules, aux)?;
1177    let mut witness = vec![signature.as_bytes().to_vec()];
1178    if claim.path == HtlcClaimPath::Success {
1179        witness.push(claim.preimage.ok_or(Error::MissingPreimage)?.to_vec());
1180    }
1181    witness.push(leaf.script.to_bytes());
1182    witness.push(leaf.control_block.clone());
1183    tx.input[0].witness = Witness::from_slice(&witness);
1184    Ok(tx)
1185}
1186
1187/// Spend a plain `to_remote` output by its literal x-only key path.
1188pub fn key_path_spend(
1189    outpoint: OutPoint,
1190    prevout: TxOut,
1191    destination: ScriptBuf,
1192    fee: Amount,
1193    key: &SecretKey,
1194    rules: SighashRules,
1195    aux: &[u8; 32],
1196) -> Result<Transaction> {
1197    let output_value = claim_value(prevout.value, fee)?;
1198    let mut tx = one_input_tx(
1199        outpoint,
1200        INPUT_SEQUENCE,
1201        LockTime::ZERO,
1202        vec![TxOut {
1203            value: output_value,
1204            script_pubkey: destination,
1205        }],
1206    );
1207    let hash_type = key_path_hash_type(rules);
1208    let (message, returned_type) =
1209        key_path_sighash(&tx, 0, &[prevout], rules).map_err(Error::Sighash)?;
1210    debug_assert_eq!(hash_type, returned_type);
1211    let keypair = Keypair::from_secret_key(secp(), key);
1212    let signature =
1213        secp().sign_schnorr_with_aux_rand(&Message::from_digest(message), &keypair, aux);
1214    let mut wire = signature.as_ref().to_vec();
1215    wire.push(hash_type);
1216    tx.input[0].witness = Witness::from_slice(&[wire]);
1217    Ok(tx)
1218}
1219
1220fn payout_after_fee(balance: Amount, side: Side, fee: Amount) -> Amount {
1221    if side == Side::A {
1222        Amount::from_sat(balance.to_sat().saturating_sub(fee.to_sat()))
1223    } else {
1224        balance
1225    }
1226}
1227
1228fn claim_value(value: Amount, fee: Amount) -> Result<Amount> {
1229    let value = value
1230        .to_sat()
1231        .checked_sub(fee.to_sat())
1232        .filter(|value| *value >= DUST)
1233        .ok_or(Error::DustClaim)?;
1234    Ok(Amount::from_sat(value))
1235}
1236
1237fn tagged_sha256(tag: &[u8], message: &[u8]) -> [u8; 32] {
1238    let tag_hash = sha256::Hash::hash(tag).to_byte_array();
1239    let mut engine = sha256::Hash::engine();
1240    engine.input(&tag_hash);
1241    engine.input(&tag_hash);
1242    engine.input(message);
1243    sha256::Hash::from_engine(engine).to_byte_array()
1244}
1245
1246fn hex_bytes(bytes: &[u8]) -> String {
1247    const HEX: &[u8; 16] = b"0123456789abcdef";
1248    let mut out = String::with_capacity(bytes.len() * 2);
1249    for byte in bytes {
1250        out.push(HEX[usize::from(byte >> 4)] as char);
1251        out.push(HEX[usize::from(byte & 0x0f)] as char);
1252    }
1253    out
1254}
1255
1256fn decode_hex_into(text: &str, out: &mut [u8]) -> Option<()> {
1257    if text.len() != out.len() * 2 {
1258        return None;
1259    }
1260    for (target, pair) in out.iter_mut().zip(text.as_bytes().chunks_exact(2)) {
1261        *target = (hex_nibble(pair[0])? << 4) | hex_nibble(pair[1])?;
1262    }
1263    Some(())
1264}
1265
1266fn hex_nibble(byte: u8) -> Option<u8> {
1267    match byte {
1268        b'0'..=b'9' => Some(byte - b'0'),
1269        b'a'..=b'f' => Some(byte - b'a' + 10),
1270        _ => None,
1271    }
1272}
1273
1274fn taproot_output(
1275    internal_key: XOnlyPublicKey,
1276    root: TapNodeHash,
1277) -> (TweakedPublicKey, Parity, ScriptBuf) {
1278    let (output_key, parity) = internal_key.tap_tweak(secp(), Some(root));
1279    let mut script = Vec::with_capacity(34);
1280    script.extend_from_slice(&[0x51, 0x20]);
1281    script.extend_from_slice(&output_key.serialize());
1282    (output_key, parity, ScriptBuf::from_bytes(script))
1283}
1284
1285fn control_block(
1286    internal_key: XOnlyPublicKey,
1287    parity: Parity,
1288    merkle_path: &[TapNodeHash],
1289) -> Vec<u8> {
1290    let mut control = Vec::with_capacity(33 + merkle_path.len() * 32);
1291    control.push(LeafVersion::TapScript.to_consensus() | u8::from(parity));
1292    control.extend_from_slice(&internal_key.serialize());
1293    for node in merkle_path {
1294        control.extend_from_slice(node.as_ref());
1295    }
1296    control
1297}
1298
1299fn push_key(script: &mut Vec<u8>, key: XOnlyPublicKey) {
1300    script.push(0x20);
1301    script.extend_from_slice(&key.serialize());
1302}
1303
1304fn push_num(script: &mut Vec<u8>, n: u32) -> Result<()> {
1305    if n > 0x7fff_ffff {
1306        return Err(Error::ScriptNumber(n));
1307    }
1308    if n == 0 {
1309        script.push(0x00);
1310        return Ok(());
1311    }
1312    if n <= 16 {
1313        script.push(0x50 + n as u8);
1314        return Ok(());
1315    }
1316    let mut value = n;
1317    let mut bytes = Vec::new();
1318    while value > 0 {
1319        bytes.push((value & 0xff) as u8);
1320        value >>= 8;
1321    }
1322    if bytes.last().is_some_and(|byte| byte & 0x80 != 0) {
1323        bytes.push(0);
1324    }
1325    script.push(bytes.len() as u8);
1326    script.extend_from_slice(&bytes);
1327    Ok(())
1328}
1329
1330fn one_input_tx(
1331    outpoint: OutPoint,
1332    sequence: u32,
1333    lock_time: LockTime,
1334    output: Vec<TxOut>,
1335) -> Transaction {
1336    Transaction {
1337        version: Version::TWO,
1338        lock_time,
1339        input: vec![TxIn {
1340            previous_output: outpoint,
1341            script_sig: ScriptBuf::new(),
1342            sequence: Sequence::from_consensus(sequence),
1343            witness: Witness::new(),
1344        }],
1345        output,
1346    }
1347}
1348
1349#[cfg(test)]
1350mod tests {
1351    use super::*;
1352    use bitcoin::consensus::encode::{deserialize, serialize};
1353    use bitcoin::hashes::Hash;
1354    use bitcoin::Txid;
1355
1356    fn key(byte: u8) -> SecretKey {
1357        SecretKey::from_slice(&[byte; 32]).unwrap()
1358    }
1359
1360    fn public(key: &SecretKey) -> XOnlyPublicKey {
1361        Keypair::from_secret_key(secp(), key).x_only_public_key().0
1362    }
1363
1364    fn channel() -> (Channel, SecretKey, SecretKey) {
1365        let a = key(0x11);
1366        let b = key(0x22);
1367        let channel = Channel::new(
1368            public(&a),
1369            public(&b),
1370            OutPoint {
1371                txid: Txid::from_byte_array([0xab; 32]),
1372                vout: 1,
1373            },
1374            Amount::from_sat(100_000),
1375            Amount::from_sat(DEFAULT_FEE),
1376            DEFAULT_DELAY,
1377        )
1378        .unwrap();
1379        (channel, a, b)
1380    }
1381
1382    #[test]
1383    fn scripts_match_hitch_golden_vectors() {
1384        let (channel, a, b) = channel();
1385        assert_eq!(
1386            internal_key().unwrap().to_string(),
1387            "257ea3139d352eb3705808452e5268c6b932af03a03e09bfe9017bb8be3b3ece"
1388        );
1389        assert_eq!(
1390            channel.funding_script.script_pubkey.to_hex_string(),
1391            "51201c17bc9ec453913ba2e233ea77bab2fb84e8091fd232568b3eef046717dbed72"
1392        );
1393        assert_eq!(
1394            channel.funding_script.leaf.script.to_hex_string(),
1395            "20466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27ac204f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaba529c"
1396        );
1397        let local = to_local_script(public(&a), public(&b), 6).unwrap();
1398        assert_eq!(
1399            local.script_pubkey.to_hex_string(),
1400            "5120cc7c54c2ace9c757d114cc18cb05b3865580ced8004519f72dbb104fdbc7bbc1"
1401        );
1402        assert_eq!(
1403            local.delayed.script.to_hex_string(),
1404            "56b275204f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac"
1405        );
1406        let htlc = htlc_script(HtlcScriptArgs {
1407            owner_pub: public(&a),
1408            remote_pub: public(&b),
1409            revocation_pub: public(&b),
1410            delay: 6,
1411            payment_hash: [0xab; 32],
1412            expiry: 152_200,
1413            offered_by_owner: true,
1414        })
1415        .unwrap();
1416        assert_eq!(
1417            htlc.script_pubkey.to_hex_string(),
1418            "5120cf2924765a12e11e9fed368d5251a591d40159e95c9d4da98066232542956a20"
1419        );
1420        assert_eq!(
1421            htlc.success.script.to_hex_string(),
1422            "a820abababababababababababababababababababababababababababababababab8820466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27ac"
1423        );
1424        assert_eq!(
1425            htlc.timeout.script.to_hex_string(),
1426            "03885202b17556b275204f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aaac"
1427        );
1428    }
1429
1430    #[test]
1431    fn commitments_and_close_match_hitch_shape() {
1432        let (channel, _, _) = channel();
1433        let state = ChannelState {
1434            balances: Balances {
1435                a: Amount::from_sat(60_000),
1436                b: Amount::from_sat(40_000),
1437            },
1438            revocation: RevocationKeys {
1439                a: public(&key(0x33)),
1440                b: public(&key(0x44)),
1441            },
1442            htlcs: vec![],
1443        };
1444        let a = channel.commitment(1, Side::A, &state).unwrap();
1445        let b = channel.commitment(1, Side::B, &state).unwrap();
1446        assert_eq!(a.kinds, [OutputKind::ToLocal, OutputKind::ToRemote]);
1447        assert_eq!(a.local_value, Amount::from_sat(59_700));
1448        assert_eq!(a.remote_value, Amount::from_sat(40_000));
1449        assert_eq!(b.local_value, Amount::from_sat(40_000));
1450        assert_eq!(b.remote_value, Amount::from_sat(59_700));
1451        assert_ne!(a.tx.compute_txid(), b.tx.compute_txid());
1452        let close = channel.cooperative_close(&state).unwrap();
1453        assert_eq!(close.output.len(), 2);
1454        assert_eq!(
1455            close.output.iter().map(|o| o.value.to_sat()).sum::<u64>(),
1456            99_700
1457        );
1458        let encoded = serialize(&a.tx);
1459        let decoded: Transaction = deserialize(&encoded).unwrap();
1460        assert_eq!(decoded.compute_txid(), a.tx.compute_txid());
1461    }
1462
1463    #[test]
1464    fn both_sighash_families_sign_the_funding_leaf() {
1465        let (channel, a, b) = channel();
1466        let state = ChannelState {
1467            balances: Balances {
1468                a: Amount::from_sat(60_000),
1469                b: Amount::from_sat(40_000),
1470            },
1471            revocation: RevocationKeys {
1472                a: public(&key(0x33)),
1473                b: public(&key(0x44)),
1474            },
1475            htlcs: vec![],
1476        };
1477        for rules in [SighashRules::Bip341, SighashRules::KnotsUnified] {
1478            let mut commitment = channel.commitment(1, Side::A, &state).unwrap();
1479            let sig_a = channel
1480                .sign_funding(&commitment.tx, &a, rules, &[0; 32])
1481                .unwrap();
1482            let sig_b = channel
1483                .sign_funding(&commitment.tx, &b, rules, &[0; 32])
1484                .unwrap();
1485            assert!(channel.verify_funding(&commitment.tx, &public(&a), &sig_a, rules));
1486            assert!(channel.verify_funding(&commitment.tx, &public(&b), &sig_b, rules));
1487            assert!(!channel.verify_funding(&commitment.tx, &public(&a), &sig_b, rules));
1488            let signatures = BTreeMap::from([(public(&a), sig_a), (public(&b), sig_b)]);
1489            commitment.tx.input[0].witness = channel.funding_witness(&signatures).unwrap();
1490            assert_eq!(commitment.tx.input[0].witness.len(), 4);
1491            let expected_type = if rules == SighashRules::KnotsUnified {
1492                0x21
1493            } else {
1494                0x01
1495            };
1496            assert_eq!(commitment.tx.input[0].witness[0][64], expected_type);
1497            assert_eq!(commitment.tx.input[0].witness[1][64], expected_type);
1498        }
1499    }
1500
1501    #[test]
1502    fn every_htlc_claim_path_has_the_reference_sequence_and_locktime() {
1503        let (channel, a, b) = channel();
1504        let preimage = [0x55; 32];
1505        let state = ChannelState {
1506            balances: Balances {
1507                a: Amount::from_sat(40_000),
1508                b: Amount::from_sat(40_000),
1509            },
1510            revocation: RevocationKeys {
1511                a: public(&key(0x33)),
1512                b: public(&key(0x44)),
1513            },
1514            htlcs: vec![Htlc {
1515                id: 1,
1516                from: Side::A,
1517                amount: Amount::from_sat(20_000),
1518                payment_hash: sha256::Hash::hash(&preimage).to_byte_array(),
1519                expiry: 152_200,
1520            }],
1521        };
1522        let owner_a = channel.commitment(2, Side::A, &state).unwrap();
1523        let owner_b = channel.commitment(2, Side::B, &state).unwrap();
1524        let destination_b = to_remote_script(public(&b));
1525        let success_a = claim_htlc(
1526            &owner_a,
1527            &owner_a.htlcs[0],
1528            HtlcClaim {
1529                path: HtlcClaimPath::Success,
1530                destination: destination_b.clone(),
1531                fee: Amount::from_sat(200),
1532                preimage: Some(preimage),
1533            },
1534            &b,
1535            SighashRules::KnotsUnified,
1536            &[0; 32],
1537        )
1538        .unwrap();
1539        assert_eq!(
1540            success_a.input[0].sequence.to_consensus_u32(),
1541            LOCKTIME_SEQUENCE
1542        );
1543        assert_eq!(success_a.lock_time, LockTime::ZERO);
1544
1545        let timeout_a = claim_htlc(
1546            &owner_a,
1547            &owner_a.htlcs[0],
1548            HtlcClaim {
1549                path: HtlcClaimPath::Timeout,
1550                destination: to_remote_script(public(&a)),
1551                fee: Amount::from_sat(200),
1552                preimage: None,
1553            },
1554            &a,
1555            SighashRules::KnotsUnified,
1556            &[0; 32],
1557        )
1558        .unwrap();
1559        assert_eq!(timeout_a.input[0].sequence.to_consensus_u32(), 6);
1560        assert_eq!(timeout_a.lock_time.to_consensus_u32(), 152_200);
1561
1562        let success_b = claim_htlc(
1563            &owner_b,
1564            &owner_b.htlcs[0],
1565            HtlcClaim {
1566                path: HtlcClaimPath::Success,
1567                destination: destination_b,
1568                fee: Amount::from_sat(200),
1569                preimage: Some(preimage),
1570            },
1571            &b,
1572            SighashRules::KnotsUnified,
1573            &[0; 32],
1574        )
1575        .unwrap();
1576        assert_eq!(success_b.input[0].sequence.to_consensus_u32(), 6);
1577        let revocation = claim_htlc(
1578            &owner_a,
1579            &owner_a.htlcs[0],
1580            HtlcClaim {
1581                path: HtlcClaimPath::Revocation,
1582                destination: to_remote_script(public(&b)),
1583                fee: Amount::from_sat(200),
1584                preimage: None,
1585            },
1586            &key(0x33),
1587            SighashRules::KnotsUnified,
1588            &[0; 32],
1589        )
1590        .unwrap();
1591        assert_eq!(
1592            revocation.input[0].sequence.to_consensus_u32(),
1593            INPUT_SEQUENCE
1594        );
1595    }
1596
1597    /// Hitch `test/channel-test.mjs`, the two-party revocation section.
1598    #[test]
1599    fn two_party_revocation_key_matches_hitch_golden_vectors() {
1600        let r = key(0x33);
1601        let s = key(0x44);
1602        let p1 = revocation_pub(public(&r), &s).unwrap();
1603        let p2 = revocation_pub(public(&s), &r).unwrap();
1604        assert_eq!(p1, p2, "R + s·G = S + r·G");
1605        assert_eq!(
1606            p1.to_string(),
1607            "4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa"
1608        );
1609        let combined = revocation_key(&r, &s).unwrap();
1610        assert_eq!(public(&combined), p1);
1611
1612        // Hitch draws the channel keys at random here; fixed 0x11 would equal
1613        // the combined key of these vectors (-0x33.. + 0x44.. = 0x11..).
1614        let a = key(0x12);
1615        let b = key(0x23);
1616        let channel = Channel::new(
1617            public(&a),
1618            public(&b),
1619            OutPoint {
1620                txid: Txid::from_byte_array([0xab; 32]),
1621                vout: 1,
1622            },
1623            Amount::from_sat(100_000),
1624            Amount::from_sat(DEFAULT_FEE),
1625            DEFAULT_DELAY,
1626        )
1627        .unwrap();
1628        let local = to_local_script(public(&a), p1, 6).unwrap();
1629        let state = ChannelState {
1630            balances: Balances {
1631                a: Amount::from_sat(60_000),
1632                b: Amount::from_sat(40_000),
1633            },
1634            revocation: RevocationKeys { a: p1, b: p1 },
1635            htlcs: vec![],
1636        };
1637        let commitment = channel.commitment(1, Side::A, &state).unwrap();
1638        assert_eq!(commitment.to_local, local);
1639        let prevouts = [TxOut {
1640            value: commitment.local_value,
1641            script_pubkey: local.script_pubkey.clone(),
1642        }];
1643        for rules in [SighashRules::Bip341, SighashRules::KnotsUnified] {
1644            let both = sweep_to_local(
1645                &commitment,
1646                SweepPath::Revocation,
1647                to_remote_script(public(&b)),
1648                Amount::from_sat(200),
1649                &combined,
1650                rules,
1651                &[0; 32],
1652            )
1653            .unwrap();
1654            let sig = LeafSignature::from_slice(&both.input[0].witness[0]).unwrap();
1655            assert!(verify_leaf_signature(
1656                &both,
1657                0,
1658                &prevouts,
1659                local.revocation.leaf_hash,
1660                &p1,
1661                &sig,
1662                rules
1663            ));
1664            // Neither secret alone, nor the owner's channel key, opens the leaf.
1665            for alone in [s, r, a] {
1666                let one = sweep_to_local(
1667                    &commitment,
1668                    SweepPath::Revocation,
1669                    to_remote_script(public(&b)),
1670                    Amount::from_sat(200),
1671                    &alone,
1672                    rules,
1673                    &[0; 32],
1674                )
1675                .unwrap();
1676                let sig = LeafSignature::from_slice(&one.input[0].witness[0]).unwrap();
1677                assert!(!verify_leaf_signature(
1678                    &one,
1679                    0,
1680                    &prevouts,
1681                    local.revocation.leaf_hash,
1682                    &p1,
1683                    &sig,
1684                    rules
1685                ));
1686            }
1687        }
1688    }
1689
1690    /// Hitch `test/channel-test.mjs`: fixed keys, fixed funding and fixed
1691    /// revocation points give transaction ids that must never change, or
1692    /// funded channels are stranded.
1693    #[test]
1694    fn commitment_and_close_txids_match_hitch_golden_vectors() {
1695        let (channel, _, _) = channel();
1696        let p1 = revocation_pub(public(&key(0x33)), &key(0x44)).unwrap();
1697        let revocation = RevocationKeys { a: p1, b: p1 };
1698        let state = ChannelState {
1699            balances: Balances {
1700                a: Amount::from_sat(60_000),
1701                b: Amount::from_sat(40_000),
1702            },
1703            revocation,
1704            htlcs: vec![],
1705        };
1706        assert_eq!(
1707            channel
1708                .commitment(1, Side::A, &state)
1709                .unwrap()
1710                .tx
1711                .compute_txid()
1712                .to_string(),
1713            "138fe22589d87f2f363907ad877a25f73d20d5415f233c7dcc0dbd9ac2f03c13"
1714        );
1715        assert_eq!(
1716            channel
1717                .commitment(1, Side::B, &state)
1718                .unwrap()
1719                .tx
1720                .compute_txid()
1721                .to_string(),
1722            "56ae7f0fb0b450356387a552da8a952eae772286320e2a94f36ee7c07ce05152"
1723        );
1724        let with_htlc = ChannelState {
1725            balances: Balances {
1726                a: Amount::from_sat(40_000),
1727                b: Amount::from_sat(40_000),
1728            },
1729            revocation,
1730            htlcs: vec![Htlc {
1731                id: 1,
1732                from: Side::A,
1733                amount: Amount::from_sat(20_000),
1734                payment_hash: [0xab; 32],
1735                expiry: 152_200,
1736            }],
1737        };
1738        assert_eq!(
1739            channel
1740                .commitment(2, Side::A, &with_htlc)
1741                .unwrap()
1742                .tx
1743                .compute_txid()
1744                .to_string(),
1745            "4cd9bf1f440e682fcfae5d1bb0e0dd11ff9ce71d738d9ad60e8b6f3e4fe30110"
1746        );
1747        assert_eq!(
1748            channel
1749                .cooperative_close(&state)
1750                .unwrap()
1751                .compute_txid()
1752                .to_string(),
1753            "5a31ac748e2e364b1c11f21b32b85d8e539dbf9baa9f4a8c41c596a41065d1b7"
1754        );
1755    }
1756
1757    #[test]
1758    fn htlc_revocation_leaf_needs_both_secrets_and_preimage_is_read_back() {
1759        let (channel, a, b) = channel();
1760        let r = key(0x33);
1761        let s = key(0x44);
1762        let p1 = revocation_pub(public(&r), &s).unwrap();
1763        let preimage = [0x55; 32];
1764        let payment_hash = sha256::Hash::hash(&preimage).to_byte_array();
1765        let state = ChannelState {
1766            balances: Balances {
1767                a: Amount::from_sat(40_000),
1768                b: Amount::from_sat(40_000),
1769            },
1770            revocation: RevocationKeys { a: p1, b: p1 },
1771            htlcs: vec![Htlc {
1772                id: 1,
1773                from: Side::B,
1774                amount: Amount::from_sat(20_000),
1775                payment_hash,
1776                expiry: 152_200,
1777            }],
1778        };
1779        let commitment = channel.commitment(2, Side::A, &state).unwrap();
1780        let htlc = &commitment.htlcs[0];
1781        let prevouts = [TxOut {
1782            value: htlc.htlc.amount,
1783            script_pubkey: htlc.scripts.script_pubkey.clone(),
1784        }];
1785        let claim = |key: &SecretKey, path, preimage| {
1786            claim_htlc(
1787                &commitment,
1788                htlc,
1789                HtlcClaim {
1790                    path,
1791                    destination: to_remote_script(public(&b)),
1792                    fee: Amount::from_sat(200),
1793                    preimage,
1794                },
1795                key,
1796                SighashRules::KnotsUnified,
1797                &[0; 32],
1798            )
1799            .unwrap()
1800        };
1801        let verifies = |tx: &Transaction, leaf: TapLeafHash, pubkey: XOnlyPublicKey| {
1802            let sig = LeafSignature::from_slice(&tx.input[0].witness[0]).unwrap();
1803            verify_leaf_signature(
1804                tx,
1805                0,
1806                &prevouts,
1807                leaf,
1808                &pubkey,
1809                &sig,
1810                SighashRules::KnotsUnified,
1811            )
1812        };
1813        let leaf = htlc.scripts.revocation.leaf_hash;
1814        let combined = revocation_key(&r, &s).unwrap();
1815        assert!(verifies(
1816            &claim(&combined, HtlcClaimPath::Revocation, None),
1817            leaf,
1818            p1
1819        ));
1820        assert!(!verifies(
1821            &claim(&s, HtlcClaimPath::Revocation, None),
1822            leaf,
1823            p1
1824        ));
1825
1826        // A success claim by A (the receiver on its own commitment) carries
1827        // the preimage; a timeout claim carries none.
1828        let success = claim(&a, HtlcClaimPath::Success, Some(preimage));
1829        assert_eq!(preimage_in(&success, &payment_hash), Some(preimage));
1830        let timeout = claim(&b, HtlcClaimPath::Timeout, None);
1831        assert_eq!(preimage_in(&timeout, &payment_hash), None);
1832    }
1833
1834    #[test]
1835    fn proof_of_possession_binds_the_point_and_its_place() {
1836        let secret = key(0x51);
1837        let point = public(&secret);
1838        let proof = pop_sign(&secret, "abababababababab/a/base", &[7; 32]);
1839        assert!(pop_verify(&point, &proof, "abababababababab/a/base"));
1840        assert!(!pop_verify(&point, &proof, "abababababababab/a/0"));
1841        assert!(!pop_verify(
1842            &public(&key(0x52)),
1843            &proof,
1844            "abababababababab/a/base"
1845        ));
1846        // A proof made with another secret does not cover a chosen point.
1847        let forged = pop_sign(&key(0x54), "abababababababab/a/base", &[7; 32]);
1848        assert!(!pop_verify(&point, &forged, "abababababababab/a/base"));
1849        assert_eq!(PopSignature::from_hex(&proof.to_hex()).unwrap(), proof);
1850        assert!(PopSignature::from_hex("ab").is_err());
1851    }
1852
1853    #[test]
1854    fn even_secret_lifts_to_the_even_point() {
1855        for byte in 1..=40u8 {
1856            let k = key(byte);
1857            let even = even_secret(&k);
1858            assert_eq!(public(&even), public(&k));
1859            assert_eq!(even.x_only_public_key(secp()).1, Parity::Even);
1860        }
1861    }
1862
1863    #[test]
1864    fn state_value_and_policy_bounds_are_checked() {
1865        let (channel, _, _) = channel();
1866        let bad = ChannelState {
1867            balances: Balances {
1868                a: Amount::from_sat(100_001),
1869                b: Amount::ZERO,
1870            },
1871            revocation: RevocationKeys {
1872                a: channel.keys[0],
1873                b: channel.keys[1],
1874            },
1875            htlcs: vec![],
1876        };
1877        assert!(matches!(
1878            channel.commitment(0, Side::A, &bad),
1879            Err(Error::Unbalanced { .. })
1880        ));
1881        assert!(Channel::new(
1882            channel.keys[0],
1883            channel.keys[1],
1884            channel.funding_outpoint,
1885            channel.funding_value,
1886            Amount::from_sat(99),
1887            6,
1888        )
1889        .is_err());
1890    }
1891}