use bitcoin::{absolute::LockTime, Amount, OutPoint, Sequence, Transaction, TxIn, TxOut};
use musig2::{
AggNonce, BatchVerificationRow, CompactSignature, PartialSignature, PubNonce, SecNonce,
};
use secp::{Point, Scalar};
use crate::{
consts::{P2TR_DUST_VALUE, P2TR_SCRIPT_PUBKEY_SIZE},
contract::{self, fees, outcome::OutcomeTransactionBuildOutput, PlayerIndex},
contract::{ContractParameters, Outcome, WinCondition},
errors::Error,
spend_info::SplitSpendInfo,
};
use std::collections::{BTreeMap, BTreeSet};
#[derive(Clone, Eq, PartialEq)]
pub(crate) struct SplitTransactionBuildOutput {
split_txs: BTreeMap<Outcome, Transaction>,
split_spend_infos: BTreeMap<WinCondition, SplitSpendInfo>,
}
impl SplitTransactionBuildOutput {
pub(crate) fn split_txs(&self) -> &BTreeMap<Outcome, Transaction> {
&self.split_txs
}
pub(crate) fn split_spend_infos(&self) -> &BTreeMap<WinCondition, SplitSpendInfo> {
&self.split_spend_infos
}
}
pub(crate) fn build_split_txs(
params: &ContractParameters,
outcome_build_output: &OutcomeTransactionBuildOutput,
) -> Result<SplitTransactionBuildOutput, Error> {
let mut split_spend_infos = BTreeMap::<WinCondition, SplitSpendInfo>::new();
let mut split_txs = BTreeMap::<Outcome, Transaction>::new();
for (&outcome, payout_map) in params.outcome_payouts.iter() {
let outcome_spend_info = &outcome_build_output
.outcome_spend_infos()
.get(&outcome)
.ok_or(Error)?;
let input_weight = outcome_spend_info.input_weight_for_split_tx();
let spk_lengths = std::iter::repeat(P2TR_SCRIPT_PUBKEY_SIZE).take(payout_map.len());
let payout_values: BTreeMap<PlayerIndex, Amount> = fees::fee_calc_shared(
outcome_spend_info.outcome_value(),
params.fee_rate,
[input_weight],
spk_lengths,
P2TR_DUST_VALUE,
&payout_map,
)?;
let (outcome_input, _) = contract::outcome::outcome_tx_prevout(
outcome_build_output,
&outcome,
params.relative_locktime_block_delta, )?;
let mut split_tx_outputs = Vec::with_capacity(payout_map.len());
for (player_index, payout_value) in payout_values {
let player = params.players.get(player_index).ok_or(Error)?;
let split_spend_info = SplitSpendInfo::new(
player,
¶ms.market_maker,
params.relative_locktime_block_delta,
)?;
split_tx_outputs.push(TxOut {
value: payout_value,
script_pubkey: split_spend_info.script_pubkey(),
});
let win_cond = WinCondition {
player_index,
outcome,
};
split_spend_infos.insert(win_cond, split_spend_info);
}
let split_tx = Transaction {
version: bitcoin::transaction::Version::TWO,
lock_time: LockTime::ZERO,
input: vec![outcome_input],
output: split_tx_outputs,
};
split_txs.insert(outcome, split_tx);
}
let output = SplitTransactionBuildOutput {
split_txs,
split_spend_infos,
};
Ok(output)
}
pub(crate) fn partial_sign_split_txs(
params: &ContractParameters,
outcome_build_out: &OutcomeTransactionBuildOutput,
split_build_out: &SplitTransactionBuildOutput,
seckey: Scalar,
mut secnonces: BTreeMap<WinCondition, SecNonce>,
aggnonces: &BTreeMap<WinCondition, AggNonce>,
) -> Result<BTreeMap<WinCondition, PartialSignature>, Error> {
let pubkey = seckey.base_point_mul();
let mut partial_signatures = BTreeMap::<WinCondition, PartialSignature>::new();
let win_conditions_to_sign = params
.win_conditions_controlled_by_pubkey(pubkey)
.ok_or(Error)?;
if win_conditions_to_sign.is_empty() {
return Ok(partial_signatures);
}
for win_cond in win_conditions_to_sign {
let split_tx = split_build_out
.split_txs()
.get(&win_cond.outcome)
.ok_or(Error)?;
let aggnonce = aggnonces.get(&win_cond).ok_or(Error)?; let secnonce = secnonces.remove(&win_cond).ok_or(Error)?;
let outcome_spend_info = outcome_build_out
.outcome_spend_infos()
.get(&win_cond.outcome)
.ok_or(Error)?;
let sighash = outcome_spend_info.sighash_tx_split(split_tx, &win_cond.player_index)?;
let partial_sig = musig2::sign_partial(
outcome_spend_info.key_agg_ctx_untweaked(),
seckey,
secnonce,
aggnonce,
sighash,
)?;
partial_signatures.insert(win_cond, partial_sig);
}
Ok(partial_signatures)
}
pub(crate) fn verify_split_tx_partial_signatures(
params: &ContractParameters,
outcome_build_out: &OutcomeTransactionBuildOutput,
split_build_out: &SplitTransactionBuildOutput,
signer_pubkey: Point,
pubnonces: &BTreeMap<WinCondition, PubNonce>,
aggnonces: &BTreeMap<WinCondition, AggNonce>,
partial_signatures: &BTreeMap<WinCondition, PartialSignature>,
) -> Result<(), Error> {
let win_conditions_to_sign = params
.win_conditions_controlled_by_pubkey(signer_pubkey)
.ok_or(Error)?;
for win_cond in win_conditions_to_sign {
let split_tx = split_build_out
.split_txs()
.get(&win_cond.outcome)
.ok_or(Error)?;
let aggnonce = aggnonces.get(&win_cond).ok_or(Error)?; let pubnonce = pubnonces.get(&win_cond).ok_or(Error)?; let partial_sig = partial_signatures.get(&win_cond).copied().ok_or(Error)?;
let outcome_spend_info = outcome_build_out
.outcome_spend_infos()
.get(&win_cond.outcome)
.ok_or(Error)?;
let sighash = outcome_spend_info.sighash_tx_split(split_tx, &win_cond.player_index)?;
musig2::verify_partial(
outcome_spend_info.key_agg_ctx_untweaked(),
partial_sig,
aggnonce,
signer_pubkey,
pubnonce,
sighash,
)?;
}
Ok(())
}
pub(crate) fn aggregate_split_tx_signatures<S>(
outcome_build_out: &OutcomeTransactionBuildOutput,
split_build_out: &SplitTransactionBuildOutput,
aggnonces: &BTreeMap<WinCondition, AggNonce>,
mut partial_signatures_by_win_cond: BTreeMap<WinCondition, S>,
) -> Result<BTreeMap<WinCondition, CompactSignature>, Error>
where
S: IntoIterator<Item = PartialSignature>,
{
split_build_out
.split_spend_infos
.keys()
.map(|&win_cond| {
let split_tx = split_build_out
.split_txs()
.get(&win_cond.outcome)
.ok_or(Error)?;
let relevant_partial_sigs = partial_signatures_by_win_cond
.remove(&win_cond)
.ok_or(Error)?;
let aggnonce = aggnonces.get(&win_cond).ok_or(Error)?;
let outcome_spend_info = outcome_build_out
.outcome_spend_infos()
.get(&win_cond.outcome)
.ok_or(Error)?;
let sighash = outcome_spend_info.sighash_tx_split(split_tx, &win_cond.player_index)?;
let compact_sig = musig2::aggregate_partial_signatures(
outcome_spend_info.key_agg_ctx_untweaked(),
aggnonce,
relevant_partial_sigs,
sighash,
)?;
Ok((win_cond, compact_sig))
})
.collect()
}
pub(crate) fn verify_split_tx_aggregated_signatures(
params: &ContractParameters,
our_pubkey: Point,
outcome_build_out: &OutcomeTransactionBuildOutput,
split_build_out: &SplitTransactionBuildOutput,
split_tx_signatures: &BTreeMap<WinCondition, CompactSignature>,
) -> Result<(), Error> {
let relevant_win_conditions: BTreeSet<WinCondition> = params
.win_conditions_claimable_by_pubkey(our_pubkey)
.ok_or(Error)?;
let batch: Vec<BatchVerificationRow> = relevant_win_conditions
.into_iter()
.map(|win_cond| {
let split_tx = split_build_out
.split_txs()
.get(&win_cond.outcome)
.ok_or(Error)?;
let signature = split_tx_signatures.get(&win_cond).ok_or(Error)?;
let outcome_spend_info = outcome_build_out
.outcome_spend_infos()
.get(&win_cond.outcome)
.ok_or(Error)?;
let winners_joint_pubkey: Point = outcome_spend_info
.key_agg_ctx_untweaked()
.aggregated_pubkey();
let sighash = outcome_spend_info.sighash_tx_split(split_tx, &win_cond.player_index)?;
let batch_row = BatchVerificationRow::from_signature(
winners_joint_pubkey,
sighash,
signature.lift_nonce()?,
);
Ok(batch_row)
})
.collect::<Result<_, Error>>()?;
musig2::verify_batch(&batch)?;
Ok(())
}
pub(crate) fn split_tx_prevout<'x>(
params: &ContractParameters,
split_build_out: &'x SplitTransactionBuildOutput,
win_cond: &WinCondition,
block_delay: u16,
) -> Result<(TxIn, &'x TxOut), Error> {
let split_tx = split_build_out
.split_txs()
.get(&win_cond.outcome)
.ok_or(Error)?;
let payout_map = params.outcome_payouts.get(&win_cond.outcome).ok_or(Error)?;
let split_tx_output_index = payout_map
.keys()
.position(|&player_index| player_index == win_cond.player_index)
.ok_or(Error)?;
let input = TxIn {
previous_output: OutPoint {
txid: split_tx.compute_txid(),
vout: split_tx_output_index as u32,
},
sequence: Sequence::from_height(block_delay),
..TxIn::default()
};
let prevout = split_tx.output.get(split_tx_output_index).ok_or(Error)?;
Ok((input, prevout))
}