use bitcoin::{absolute::LockTime, OutPoint, Sequence, Transaction, TxIn, TxOut};
use musig2::{
AdaptorSignature, AggNonce, BatchVerificationRow, CompactSignature, PartialSignature, PubNonce,
SecNonce,
};
use secp::{Point, Scalar};
use std::collections::{BTreeMap, BTreeSet};
use crate::{
contract::{ContractParameters, Outcome, OutcomeIndex},
errors::Error,
spend_info::{FundingSpendInfo, OutcomeSpendInfo},
};
#[derive(Clone)]
pub(crate) struct OutcomeTransactionBuildOutput {
outcome_txs: BTreeMap<Outcome, Transaction>,
outcome_spend_infos: BTreeMap<Outcome, OutcomeSpendInfo>,
funding_spend_info: FundingSpendInfo,
}
impl OutcomeTransactionBuildOutput {
pub(crate) fn outcome_txs(&self) -> &BTreeMap<Outcome, Transaction> {
&self.outcome_txs
}
pub(crate) fn outcome_spend_infos(&self) -> &BTreeMap<Outcome, OutcomeSpendInfo> {
&self.outcome_spend_infos
}
pub(crate) fn funding_spend_info(&self) -> &FundingSpendInfo {
&self.funding_spend_info
}
}
pub(crate) fn build_outcome_txs(
params: &ContractParameters,
funding_outpoint: OutPoint,
) -> Result<OutcomeTransactionBuildOutput, Error> {
let all_players = params.sorted_players();
let funding_input = TxIn {
previous_output: funding_outpoint,
sequence: Sequence::MAX,
..TxIn::default()
};
let outcome_value = params.outcome_output_value()?;
let outcome_spend_infos: BTreeMap<Outcome, OutcomeSpendInfo> = params
.outcome_payouts
.iter()
.map(|(&outcome, payout_map)| {
let winners = payout_map.keys().copied();
let spend_info = OutcomeSpendInfo::new(
&all_players,
winners,
¶ms.market_maker,
outcome_value,
params.relative_locktime_block_delta,
)?;
Ok((outcome, spend_info))
})
.collect::<Result<_, Error>>()?;
let outcome_txs: BTreeMap<Outcome, Transaction> = outcome_spend_infos
.iter()
.map(|(&outcome, outcome_spend_info)| {
let outcome_output = TxOut {
value: outcome_value,
script_pubkey: outcome_spend_info.script_pubkey(),
};
let lock_time = match outcome {
Outcome::Expiry => LockTime::from_consensus(params.event.expiry),
Outcome::Attestation(_) => LockTime::ZERO, };
let outcome_tx = Transaction {
version: bitcoin::transaction::Version::TWO,
lock_time,
input: vec![funding_input.clone()],
output: vec![outcome_output],
};
(outcome, outcome_tx)
})
.collect();
let funding_spend_info =
FundingSpendInfo::new(¶ms.market_maker, ¶ms.players, params.funding_value)?;
let output = OutcomeTransactionBuildOutput {
outcome_txs,
outcome_spend_infos,
funding_spend_info,
};
Ok(output)
}
pub(crate) fn partial_sign_outcome_txs(
params: &ContractParameters,
outcome_build_out: &OutcomeTransactionBuildOutput,
seckey: Scalar,
mut secnonces: BTreeMap<Outcome, SecNonce>,
aggnonces: &BTreeMap<Outcome, AggNonce>,
) -> Result<BTreeMap<Outcome, PartialSignature>, Error> {
let outcome_txs = &outcome_build_out.outcome_txs;
let funding_spend_info = &outcome_build_out.funding_spend_info;
funding_spend_info
.key_agg_ctx()
.pubkey_index(seckey.base_point_mul())
.ok_or(Error)?;
let mut outcome_partial_sigs = BTreeMap::<Outcome, PartialSignature>::new();
for (&outcome, outcome_tx) in outcome_txs {
let aggnonce = aggnonces.get(&outcome).ok_or(Error)?; let secnonce = secnonces.remove(&outcome).ok_or(Error)?;
let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?;
let partial_sig = match outcome {
Outcome::Attestation(outcome_index) => {
let attestation_lock_point = params
.event
.attestation_lock_point(outcome_index)
.ok_or(Error)?;
musig2::adaptor::sign_partial(
funding_spend_info.key_agg_ctx(),
seckey,
secnonce,
aggnonce,
attestation_lock_point,
sighash,
)?
}
Outcome::Expiry => musig2::sign_partial(
funding_spend_info.key_agg_ctx(),
seckey,
secnonce,
aggnonce,
sighash,
)?,
};
outcome_partial_sigs.insert(outcome, partial_sig);
}
Ok(outcome_partial_sigs)
}
pub(crate) fn verify_outcome_tx_partial_signatures(
params: &ContractParameters,
outcome_build_out: &OutcomeTransactionBuildOutput,
signer_pubkey: Point,
pubnonces: &BTreeMap<Outcome, PubNonce>,
aggnonces: &BTreeMap<Outcome, AggNonce>,
partial_signatures: &BTreeMap<Outcome, PartialSignature>,
) -> Result<(), Error> {
let outcome_txs = &outcome_build_out.outcome_txs;
let funding_spend_info = &outcome_build_out.funding_spend_info;
for (&outcome, outcome_tx) in outcome_txs {
let aggnonce = aggnonces.get(&outcome).ok_or(Error)?; let pubnonce = pubnonces.get(&outcome).ok_or(Error)?; let &partial_sig = partial_signatures.get(&outcome).ok_or(Error)?;
let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?;
match outcome {
Outcome::Attestation(outcome_index) => {
let attestation_lock_point = params
.event
.attestation_lock_point(outcome_index)
.ok_or(Error)?;
musig2::adaptor::verify_partial(
funding_spend_info.key_agg_ctx(),
partial_sig,
aggnonce,
attestation_lock_point,
signer_pubkey,
pubnonce,
sighash,
)?;
}
Outcome::Expiry => {
musig2::verify_partial(
funding_spend_info.key_agg_ctx(),
partial_sig,
aggnonce,
signer_pubkey,
pubnonce,
sighash,
)?;
}
};
}
Ok(())
}
#[derive(Clone, Debug)]
pub(crate) struct OutcomeSignatures {
pub(crate) outcome_tx_signatures: BTreeMap<OutcomeIndex, AdaptorSignature>,
pub(crate) expiry_tx_signature: Option<CompactSignature>,
}
pub(crate) fn aggregate_outcome_tx_adaptor_signatures<S>(
params: &ContractParameters,
outcome_build_out: &OutcomeTransactionBuildOutput,
aggnonces: &BTreeMap<Outcome, AggNonce>,
mut partial_signature_groups: BTreeMap<Outcome, S>,
) -> Result<OutcomeSignatures, Error>
where
S: IntoIterator<Item = PartialSignature>,
{
let outcome_txs = &outcome_build_out.outcome_txs;
let funding_spend_info = &outcome_build_out.funding_spend_info;
let mut signatures = OutcomeSignatures {
outcome_tx_signatures: BTreeMap::new(),
expiry_tx_signature: None,
};
for (&outcome, outcome_tx) in outcome_txs {
let partial_sigs = partial_signature_groups.remove(&outcome).ok_or(Error)?;
let aggnonce = aggnonces.get(&outcome).ok_or(Error)?;
let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?;
match outcome {
Outcome::Attestation(outcome_index) => {
let attestation_lock_point = params
.event
.attestation_lock_point(outcome_index)
.ok_or(Error)?;
let adaptor_sig = musig2::adaptor::aggregate_partial_signatures(
funding_spend_info.key_agg_ctx(),
aggnonce,
attestation_lock_point,
partial_sigs,
sighash,
)?;
signatures
.outcome_tx_signatures
.insert(outcome_index, adaptor_sig);
}
Outcome::Expiry => {
let signature: CompactSignature = musig2::aggregate_partial_signatures(
funding_spend_info.key_agg_ctx(),
aggnonce,
partial_sigs,
sighash,
)?;
signatures.expiry_tx_signature = Some(signature);
}
};
}
Ok(signatures)
}
pub(crate) fn verify_outcome_tx_aggregated_signatures(
params: &ContractParameters,
our_pubkey: Point,
outcome_build_out: &OutcomeTransactionBuildOutput,
outcome_tx_signatures: &BTreeMap<OutcomeIndex, AdaptorSignature>,
expiry_tx_signature: Option<CompactSignature>,
) -> Result<(), Error> {
let funding_spend_info = &outcome_build_out.funding_spend_info;
let joint_pubkey: Point = funding_spend_info.key_agg_ctx().aggregated_pubkey();
let relevant_outcomes: BTreeSet<Outcome> = params
.win_conditions_claimable_by_pubkey(our_pubkey)
.ok_or(Error)?
.into_iter()
.map(|win_cond| win_cond.outcome)
.collect();
let batch: Vec<BatchVerificationRow> = relevant_outcomes
.into_iter()
.map(|outcome| {
let outcome_tx = outcome_build_out.outcome_txs.get(&outcome).ok_or(Error)?;
let sighash = outcome_build_out
.funding_spend_info
.sighash_tx_outcome(outcome_tx)?;
let batch_row = match outcome {
Outcome::Attestation(outcome_index) => {
let adaptor_point = params
.event
.attestation_lock_point(outcome_index)
.ok_or(Error)?;
let &signature = outcome_tx_signatures.get(&outcome_index).ok_or(Error)?;
BatchVerificationRow::from_adaptor_signature(
joint_pubkey,
sighash,
signature,
adaptor_point,
)
}
Outcome::Expiry => {
let signature = expiry_tx_signature.ok_or(Error)?.lift_nonce()?;
BatchVerificationRow::from_signature(joint_pubkey, sighash, signature)
}
};
Ok(batch_row)
})
.collect::<Result<_, Error>>()?;
musig2::verify_batch(&batch)?;
Ok(())
}
pub(crate) fn outcome_tx_prevout<'x>(
outcome_build_out: &'x OutcomeTransactionBuildOutput,
outcome: &Outcome,
block_delay: u16,
) -> Result<(TxIn, &'x TxOut), Error> {
let outcome_tx = outcome_build_out.outcome_txs().get(outcome).ok_or(Error)?;
let outcome_input = TxIn {
previous_output: OutPoint {
txid: outcome_tx.txid(),
vout: 0,
},
sequence: Sequence::from_height(block_delay),
..TxIn::default()
};
let prevout = outcome_tx.output.get(0).ok_or(Error)?;
Ok((outcome_input, prevout))
}