diff --git a/src/lib.rs b/src/lib.rs index a2c6e8c..8e3d7fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,9 +17,9 @@ use contract::{ }; use errors::Error; -use bitcoin::{OutPoint, TxOut}; +use bitcoin::{OutPoint, Transaction, TxOut}; use musig2::{AdaptorSignature, AggNonce, CompactSignature, PartialSignature, PubNonce, SecNonce}; -use secp::{Point, Scalar}; +use secp::{MaybeScalar, Point, Scalar}; use std::collections::BTreeMap; @@ -414,4 +414,108 @@ impl SigningSession { pub fn signatures(&self) -> &ContractSignatures { &self.state.signatures } + + /// Return an unsigned outcome transaction. + pub fn unsigned_outcome_tx<'a>(&'a self, outcome_index: usize) -> Option<&'a Transaction> { + self.dlc + .outcome_tx_build + .outcome_txs() + .get(&Outcome::Attestation(outcome_index)) + } + + /// Return a signed outcome transaction given the oracle's attestation + /// to a specific outcome. + pub fn signed_outcome_tx( + &self, + outcome_index: usize, + attestation: impl Into, + ) -> Result { + let attestation = attestation.into(); + let locking_point = self + .dlc + .params + .event + .attestation_lock_point(outcome_index) + .ok_or(Error)?; + + // Invalid attestation. + if attestation.base_point_mul() != locking_point { + return Err(Error)?; + } + + let mut outcome_tx = self + .unsigned_outcome_tx(outcome_index) + .ok_or(Error)? + .clone(); + + let adaptor_signature = self + .state + .signatures + .outcome_tx_signatures + .get(outcome_index) + .ok_or(Error)?; + + let compact_sig: CompactSignature = adaptor_signature.adapt(attestation).ok_or(Error)?; + + outcome_tx.input[0].witness.push(compact_sig.serialize()); + Ok(outcome_tx) + } + + /// Return the signed expiry transaction, if one exists for this contract. + pub fn expiry_tx(&self) -> Option { + let mut expiry_tx = self + .dlc + .outcome_tx_build + .outcome_txs() + .get(&Outcome::Expiry)? + .clone(); + + let signature: CompactSignature = self.state.signatures.expiry_tx_signature?; + expiry_tx.input[0].witness.push(signature.serialize()); + Some(expiry_tx) + } + + /// Return the unsigned split transaction for the given outcome. + pub fn unsigned_split_tx<'a>(&'a self, outcome: &Outcome) -> Option<&'a Transaction> { + self.dlc.split_tx_build.split_txs().get(outcome) + } + + /// Return a signed split transaction, given the ticket preimage for a specific player. + pub fn signed_split_tx( + &self, + win_cond: &WinCondition, + ticket_preimage: hashlock::Preimage, + ) -> Result { + // Verify the preimage will unlock this specific player's split TX + // condition. + if hashlock::sha256(&ticket_preimage) != win_cond.winner.ticket_hash { + return Err(Error)?; + } + + let signature = self + .state + .signatures + .split_tx_signatures + .get(win_cond) + .ok_or(Error)?; + + let outcome_spend_info = self + .dlc + .outcome_tx_build + .outcome_spend_infos() + .get(&win_cond.outcome) + .ok_or(Error)?; + + let witness = + outcome_spend_info.witness_tx_split(signature, ticket_preimage, &win_cond.winner)?; + + let mut split_tx = self + .unsigned_split_tx(&win_cond.outcome) + .ok_or(Error)? + .clone(); + + split_tx.input[0].witness = witness; + + Ok(split_tx) + } } diff --git a/src/spend_info/outcome.rs b/src/spend_info/outcome.rs index 14efebd..fb0b97b 100644 --- a/src/spend_info/outcome.rs +++ b/src/spend_info/outcome.rs @@ -13,7 +13,7 @@ use secp::{Point, Scalar}; use crate::{ errors::Error, - hashlock::PREIMAGE_SIZE, + hashlock::{Preimage, PREIMAGE_SIZE}, parties::{MarketMaker, Player}, }; @@ -220,6 +220,28 @@ impl OutcomeSpendInfo { Ok(sighash) } + /// Compute a witness for a split transaction which spends from the outcome transaction. + pub(crate) fn witness_tx_split( + &self, + signature: &CompactSignature, + ticket_preimage: Preimage, + winner: &Player, + ) -> Result { + let split_script = self.winner_split_scripts.get(winner).ok_or(Error)?.clone(); + let control_block = self + .spend_info + .control_block(&(split_script.clone(), LeafVersion::TapScript)) + .ok_or(Error)?; + + let mut witness = Witness::new(); + witness.push(signature.serialize()); + witness.push(ticket_preimage); + witness.push(split_script); + witness.push(control_block.serialize()); + + Ok(witness) + } + /// Compute a witness for a reclaim transaction which spends from the outcome transaction. /// /// This would only be used if none of the attested DLC outcome winners actually paid for @@ -227,7 +249,7 @@ impl OutcomeSpendInfo { /// without splitting it into multiple payout contracts and recombining the outputs unnecessarily. pub(crate) fn witness_tx_reclaim>( &self, - split_tx: &Transaction, + reclaim_tx: &Transaction, input_index: usize, prevouts: &Prevouts, market_maker_secret_key: Scalar, @@ -235,7 +257,7 @@ impl OutcomeSpendInfo { ) -> Result { let leaf_hash = TapLeafHash::from_script(&self.reclaim_script, LeafVersion::TapScript); - let sighash = SighashCache::new(split_tx).taproot_script_spend_signature_hash( + let sighash = SighashCache::new(reclaim_tx).taproot_script_spend_signature_hash( input_index, prevouts, leaf_hash,