From 91caefb9899ec0b405862254f032d8192a11f0be Mon Sep 17 00:00:00 2001 From: conduition Date: Sun, 10 Mar 2024 16:47:49 +0000 Subject: [PATCH] add cooperative split closing transaction signing This allows the market maker to cooperatively close individual payout contracts with specific winners, if some of an outcome's winners cooperate but others do not. --- src/lib.rs | 61 ++++++++++++++++++++++++++++++++++++++++- src/spend_info/split.rs | 33 ++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 563d13f..38ca569 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -618,6 +618,13 @@ impl SignedContract { ) } + pub fn outcome_close_tx_input_and_prevout<'a>( + &'a self, + outcome: &Outcome, + ) -> Result<(TxIn, &'a TxOut), Error> { + contract::outcome::outcome_tx_prevout(&self.dlc.outcome_tx_build, outcome, 0) + } + pub fn split_win_tx_input_and_prevout<'a>( &'a self, win_cond: &WinCondition, @@ -712,7 +719,7 @@ impl SignedContract { // Confirm we're signing the correct input let (mut expected_input, expected_prevout) = - self.outcome_reclaim_tx_input_and_prevout(outcome)?; + self.outcome_close_tx_input_and_prevout(outcome)?; // The caller can use whatever sequence they want. expected_input.sequence = close_tx.input.get(input_index).ok_or(Error)?.sequence; @@ -886,6 +893,58 @@ impl SignedContract { sellback_tx.input[input_index].witness = witness; Ok(()) } + + /// Sign a cooperative closing transaction which spends a player's split transaction output. + /// The market maker can use this method once they have issued off-chain payouts to this + /// winning player. Once the player has her off-chain payout, they can send their secret + /// key to the market maker to let him reclaim all the on-chain capital efficiently. + pub fn sign_split_close_tx_input>( + &self, + win_cond: &WinCondition, + close_tx: &mut Transaction, + input_index: usize, + prevouts: &Prevouts, + market_maker_secret_key: impl Into, + player_secret_key: impl Into, + ) -> Result<(), Error> { + let market_maker_secret_key = market_maker_secret_key.into(); + if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey { + return Err(Error); + } + + // Confirm we're signing the correct input + let (mut expected_input, expected_prevout) = + self.split_sellback_tx_input_and_prevout(win_cond)?; + + // The caller can use whatever sequence they want. + expected_input.sequence = close_tx.input.get(input_index).ok_or(Error)?.sequence; + + check_input_matches_expected( + close_tx, + prevouts, + input_index, + &expected_input, + expected_prevout, + )?; + + let split_spend_info = self + .dlc + .split_tx_build + .split_spend_infos() + .get(win_cond) + .ok_or(Error)?; + + let witness = split_spend_info.witness_tx_close( + close_tx, + input_index, + prevouts, + market_maker_secret_key, + player_secret_key.into(), + )?; + + close_tx.input[input_index].witness = witness; + Ok(()) + } } /// Validate that a given `Transaction` and `Prevouts` set match the expected diff --git a/src/spend_info/split.rs b/src/spend_info/split.rs index 848a85d..bab3ca8 100644 --- a/src/spend_info/split.rs +++ b/src/spend_info/split.rs @@ -318,4 +318,37 @@ impl SplitSpendInfo { Ok(witness) } + + /// Derive the witness for a cooperative closing transaction which spends from + /// a single player's split TX output. The market maker must provide the secret + /// key given by the player after a complete off-chain payout. + pub(crate) fn witness_tx_close>( + &self, + close_tx: &Transaction, + input_index: usize, + prevouts: &Prevouts, + market_maker_secret_key: Scalar, + player_secret_key: Scalar, + ) -> Result { + let mm_pubkey = market_maker_secret_key.base_point_mul(); + let sighash = SighashCache::new(close_tx).taproot_key_spend_signature_hash( + input_index, + prevouts, + TapSighashType::Default, + )?; + + let ordered_seckeys = self.tweaked_ctx.pubkeys().into_iter().map(|pubkey| { + if pubkey == &mm_pubkey { + market_maker_secret_key + } else { + player_secret_key + } + }); + + let group_seckey: Scalar = self.tweaked_ctx.aggregated_seckey(ordered_seckeys)?; + + let signature: CompactSignature = musig2::deterministic::sign_solo(group_seckey, sighash); + let witness = Witness::from_slice(&[signature.serialize()]); + Ok(witness) + } }