From 49baf6ad7bd55bb994d8f6a4a0c274e24ea1a605 Mon Sep 17 00:00:00 2001 From: conduition Date: Thu, 15 Feb 2024 21:37:05 +0000 Subject: [PATCH] completed refactoring to different struct types --- src/lib.rs | 664 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 439 insertions(+), 225 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 362be8a..f63e295 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,8 @@ use bitcoin::{ script::ScriptBuf, sighash::{Prevouts, SighashCache, TapSighashType}, taproot::{ - LeafVersion, TaprootSpendInfo, TAPROOT_CONTROL_BASE_SIZE, TAPROOT_CONTROL_NODE_SIZE, + LeafVersion, TapLeafHash, TaprootSpendInfo, TAPROOT_CONTROL_BASE_SIZE, + TAPROOT_CONTROL_NODE_SIZE, }, transaction::InputWeightPrediction, Amount, FeeRate, OutPoint, Sequence, TapSighash, Transaction, TxIn, TxOut, Witness, @@ -15,9 +16,9 @@ use bitcoin::{ use errors::Error; use musig2::{AggNonce, KeyAggContext, PartialSignature, SecNonce}; use secp::{MaybePoint, MaybeScalar, Point, Scalar}; -use secp256k1::XOnlyPublicKey; use sha2::Digest as _; -use std::collections::BTreeMap; + +use std::collections::{BTreeMap, BTreeSet}; const P2TR_SCRIPT_PUBKEY_LEN: usize = 34; @@ -38,21 +39,21 @@ pub fn sha256(input: &[u8]) -> [u8; 32] { /// /// Using lower values is more efficient on-chain, but less secure against /// brute force attacks. -pub const TICKET_PREIMAGE_SIZE: usize = 32; +pub const PREIMAGE_SIZE: usize = 32; /// A handy type-alias for ticket preimages. We use random 32 byte preimages /// for best compatibility with lightning network clients. -pub type TicketPreimage = [u8; TICKET_PREIMAGE_SIZE]; +pub type Preimage = [u8; PREIMAGE_SIZE]; -pub fn preimage_random(rng: &mut R) -> TicketPreimage { - let mut preimage = [0u8; TICKET_PREIMAGE_SIZE]; +pub fn preimage_random(rng: &mut R) -> Preimage { + let mut preimage = [0u8; PREIMAGE_SIZE]; rng.fill_bytes(&mut preimage); preimage } /// Parse a preimage from a hex string. -pub fn preimage_from_hex(s: &str) -> Result { - let mut preimage = [0u8; TICKET_PREIMAGE_SIZE]; +pub fn preimage_from_hex(s: &str) -> Result { + let mut preimage = [0u8; PREIMAGE_SIZE]; hex::decode_to_slice(s, &mut preimage)?; Ok(preimage) } @@ -74,6 +75,12 @@ pub struct Player { /// The ticket hashes used for HTLCs. To buy into the DLC, players must /// purchase the preimages of these hashes. pub ticket_hash: [u8; 32], + + /// A hash used for unlocking the split TX output early. To allow winning + /// players to receive off-chain payouts, they must provide this `payout_hash`, + /// for which they know the preimage. By selling the preimage to the market maker, + /// they allow the market maker to reclaim the on-chain funds. + pub payout_hash: [u8; 32], } /// An oracle's announcement of a future event. @@ -134,30 +141,31 @@ impl EventAnnouncment { } } -/// Represents a mapping of player to payout weight for a given outcome. -/// -/// A player's payout is proportional to the size of their payout weight -/// in comparison to the payout weights of all other winners. -pub type PayoutWeights = BTreeMap; - #[derive(Debug, Clone)] -pub struct FundingSpendInfo<'e> { +pub struct FundingSpendInfo { key_agg_ctx: KeyAggContext, - event: &'e EventAnnouncment, funding_value: Amount, } -impl<'e> FundingSpendInfo<'e> { - fn new( - key_agg_ctx: KeyAggContext, - event: &'e EventAnnouncment, +impl FundingSpendInfo { + fn new<'p>( + market_maker: &MarketMaker, + players: impl IntoIterator, funding_value: Amount, - ) -> FundingSpendInfo<'e> { - FundingSpendInfo { + ) -> Result { + let mut pubkeys: Vec = players + .into_iter() + .map(|player| player.pubkey) + .chain([market_maker.pubkey]) + .collect(); + pubkeys.sort(); + + let key_agg_ctx = KeyAggContext::new(pubkeys)?; + + Ok(FundingSpendInfo { key_agg_ctx, - event, funding_value, - } + }) } /// Return a reference to the [`KeyAggContext`] used to spend the multisig funding output. @@ -210,7 +218,8 @@ impl<'e> FundingSpendInfo<'e> { /// player's ticket point. #[derive(Debug, Clone)] pub struct OutcomeSpendInfo { - key_agg_ctx: KeyAggContext, + untweaked_ctx: KeyAggContext, + tweaked_ctx: KeyAggContext, spend_info: TaprootSpendInfo, winner_split_scripts: BTreeMap, reclaim_script: ScriptBuf, @@ -278,7 +287,7 @@ impl OutcomeSpendInfo { weighted_script_leaves, )?; - let tweaked_ctx = untweaked_ctx.with_taproot_tweak( + let tweaked_ctx = untweaked_ctx.clone().with_taproot_tweak( tr_spend_info .merkle_root() .expect("should always have merkle root") @@ -286,7 +295,8 @@ impl OutcomeSpendInfo { )?; let outcome_spend_info = OutcomeSpendInfo { - key_agg_ctx: tweaked_ctx, + untweaked_ctx, + tweaked_ctx, spend_info: tr_spend_info, winner_split_scripts, reclaim_script, @@ -327,7 +337,7 @@ impl OutcomeSpendInfo { 0, [ SCHNORR_SIGNATURE_SIZE, // BIP340 schnorr signature - TICKET_PREIMAGE_SIZE, // Ticket preimage + PREIMAGE_SIZE, // Ticket preimage outcome_script_len, // Script TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * max_taptree_depth, // Control block ], @@ -354,22 +364,50 @@ impl OutcomeSpendInfo { ], ) } + + /// Compute the signature hash for a given split transaction. + pub fn sighash_tx_split( + &self, + split_tx: &Transaction, + winner: &Player, + outcome_value: Amount, + ) -> Result { + let outcome_prevouts = [TxOut { + script_pubkey: self.script_pubkey(), + value: outcome_value, + }]; + let split_script = self.winner_split_scripts.get(winner).ok_or(Error)?; + let leaf_hash = TapLeafHash::from_script(split_script, LeafVersion::TapScript); + + let sighash = SighashCache::new(split_tx).taproot_script_spend_signature_hash( + 0, + &Prevouts::All(&outcome_prevouts), + leaf_hash, + TapSighashType::Default, + )?; + Ok(sighash) + } } /// Represents a taproot contract for a specific player's split TX payout output. -/// This tree only has two nodes: +/// This tree has three nodes: /// /// 1. A relative-timelocked hash-lock which pays to the player if they know their ticket /// preimage after one round of block delay. /// /// 2. A relative-timelock which pays to the market maker after two rounds of block delay. +/// +/// 3. A hash-lock which pays to the market maker immediately if they learn the +// payout preimage from the player. #[derive(Debug, Clone)] pub struct SplitSpendInfo { - key_agg_ctx: KeyAggContext, + untweaked_ctx: KeyAggContext, + tweaked_ctx: KeyAggContext, spend_info: TaprootSpendInfo, winner: Player, win_script: ScriptBuf, reclaim_script: ScriptBuf, + sellback_script: ScriptBuf, } impl SplitSpendInfo { @@ -415,14 +453,33 @@ impl SplitSpendInfo { .push_opcode(OP_CHECKSIG) .into_script(); - let weighted_script_leaves = [(1, win_script.clone()), (1, reclaim_script.clone())]; + // The sellback script, used by the market maker to reclaim their capital + // if the player agrees to sell their payout output from the split TX back + // to the market maker. + // + // Inputs: + let sellback_script = bitcoin::script::Builder::new() + // Check payout preimage: OP_SHA256 OP_EQUALVERIFY + .push_opcode(OP_SHA256) + .push_slice(winner.payout_hash) + .push_opcode(OP_EQUALVERIFY) + // Check signature: OP_CHECKSIG + .push_slice(market_maker.pubkey.serialize_xonly()) + .push_opcode(OP_CHECKSIG) + .into_script(); + + let weighted_script_leaves = [ + (2, sellback_script.clone()), + (1, win_script.clone()), + (1, reclaim_script.clone()), + ]; let tr_spend_info = TaprootSpendInfo::with_huffman_tree( secp256k1::SECP256K1, joint_payout_pubkey.into(), weighted_script_leaves, )?; - let tweaked_ctx = untweaked_ctx.with_taproot_tweak( + let tweaked_ctx = untweaked_ctx.clone().with_taproot_tweak( tr_spend_info .merkle_root() .expect("should always have merkle root") @@ -430,11 +487,13 @@ impl SplitSpendInfo { )?; let split_spend_info = SplitSpendInfo { - key_agg_ctx: tweaked_ctx, + untweaked_ctx, + tweaked_ctx, spend_info: tr_spend_info, winner, win_script, reclaim_script, + sellback_script, }; Ok(split_spend_info) } @@ -459,7 +518,7 @@ impl SplitSpendInfo { 0, [ SCHNORR_SIGNATURE_SIZE, // BIP340 schnorr signature - TICKET_PREIMAGE_SIZE, // Ticket preimage + PREIMAGE_SIZE, // Ticket preimage self.win_script.len(), // Script win_control_block.size(), // Control block ], @@ -467,7 +526,7 @@ impl SplitSpendInfo { } /// Computes the input weight when spending an output of the split TX - /// as an input of the market maker's relcaim TX. This assumes the market + /// as an input of the market maker's reclaim TX. This assumes the market /// maker's reclaim script leaf is being used to unlock the taproot tree. pub fn input_weight_for_reclaim_tx(&self) -> InputWeightPrediction { let reclaim_control_block = self @@ -486,8 +545,36 @@ impl SplitSpendInfo { ], ) } + + /// Computes the input weight when spending an output of the split TX + /// as an input of the sellback TX. This assumes the market maker's sellback + /// script leaf is being used to unlock the taproot tree. + pub fn input_weight_for_sellback_tx(&self) -> InputWeightPrediction { + let sellback_control_block = self + .spend_info + .control_block(&(self.sellback_script.clone(), LeafVersion::TapScript)) + .expect("sellback script cannot be missing"); + + // The witness stack for the sellback TX which spends a split TX output is: + //