mirror of
https://github.com/conduition/dlctix.git
synced 2026-01-30 05:05:06 +01:00
add expiry outcome branch
This commit is contained in:
@@ -45,15 +45,13 @@ pub struct ContractParameters {
|
||||
/// The event whose outcome determines the payouts.
|
||||
pub event: EventAnnouncment,
|
||||
|
||||
/// An ordered list of payout under different outcomes. Should align with
|
||||
/// `self.event.outcome_messages`.
|
||||
pub outcome_payouts: Vec<PayoutWeights>,
|
||||
|
||||
/// Who is paid out in the event of an expiry (when the oracle attestation is not
|
||||
/// received by [`event.expiry`][EventAnnouncment::expiry]). If this field is `None`,
|
||||
/// then there is no expiry condition, and the money simply remains locked in the
|
||||
/// funding outpoint until the Oracle's attestation is found.
|
||||
pub expiry_payout: Option<PayoutWeights>,
|
||||
/// A mapping of payout weights under different outcomes. Attestation indexes should
|
||||
/// align with [`self.event.outcome_messages`][EventAnnouncment::outcome_messages].
|
||||
///
|
||||
/// If this map does not contain a key of [`Outcome::Expiry`], then there is no expiry
|
||||
/// condition, and the money simply remains locked in the funding outpoint until the
|
||||
/// Oracle's attestation is found.
|
||||
pub outcome_payouts: BTreeMap<Outcome, PayoutWeights>,
|
||||
|
||||
/// A default mining fee rate to be used for pre-signed transactions.
|
||||
pub fee_rate: FeeRate,
|
||||
@@ -74,10 +72,22 @@ pub struct ContractParameters {
|
||||
pub relative_locktime_block_delta: u16,
|
||||
}
|
||||
|
||||
/// Represents one possible outcome branch of the DLC. This includes both
|
||||
/// outcomes attested-to by the Oracle, and expiry.
|
||||
#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
||||
pub enum Outcome {
|
||||
/// Indicates the oracle attested to a particular outcome of the given index.
|
||||
Attestation(usize),
|
||||
|
||||
/// Indicates the oracle failed to attest to any outcome, and the event expiry
|
||||
/// timelock was reached.
|
||||
Expiry,
|
||||
}
|
||||
|
||||
/// Points to a situation where a player wins a payout from the DLC.
|
||||
#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
||||
pub struct WinCondition {
|
||||
pub outcome_index: usize,
|
||||
pub outcome: Outcome,
|
||||
pub winner: Player,
|
||||
}
|
||||
|
||||
@@ -89,12 +99,29 @@ impl ContractParameters {
|
||||
Ok(outcome_value)
|
||||
}
|
||||
|
||||
/// Returns the set of players which this pubkey can sign for.
|
||||
///
|
||||
/// This might contain multiple players if the same key joined the DLC
|
||||
/// with different ticket/payout hashes.
|
||||
pub fn players_controlled_by_pubkey<'a>(&'a self, pubkey: Point) -> BTreeSet<&'a Player> {
|
||||
self.players
|
||||
.iter()
|
||||
.filter(|player| player.pubkey == pubkey)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Return the set of all win conditions which the given pubkey will need to sign
|
||||
/// split transactions for.
|
||||
///
|
||||
/// If `pubkey` belongs to one or more players, this returns all [`WinCondition`]s
|
||||
/// for outcomes in which the player or players are winners.
|
||||
///
|
||||
/// If `pubkey` belongs to the market maker, this returns every [`WinCondition`]
|
||||
/// across the entire contract.
|
||||
///
|
||||
/// Returns `None` if the pubkey does not belong to any player in the DLC.
|
||||
///
|
||||
/// Returns an empty `BTreeSet if the player is part of the DLC, but isn't due to
|
||||
/// Returns an empty `BTreeSet` if the player is part of the DLC, but isn't due to
|
||||
/// receive any payouts on any DLC outcome.
|
||||
pub fn win_conditions_controlled_by_pubkey(
|
||||
&self,
|
||||
@@ -103,13 +130,7 @@ impl ContractParameters {
|
||||
// To sign as the market maker, the caller need only provide the correct secret key.
|
||||
let is_market_maker = pubkey == self.market_maker.pubkey;
|
||||
|
||||
// This might contain multiple players if the same key joined the DLC
|
||||
// with different ticket/payout hashes.
|
||||
let controlling_players: BTreeSet<&Player> = self
|
||||
.players
|
||||
.iter()
|
||||
.filter(|player| player.pubkey == pubkey)
|
||||
.collect();
|
||||
let controlling_players = self.players_controlled_by_pubkey(pubkey);
|
||||
|
||||
// Short circuit if this pubkey is not known.
|
||||
if controlling_players.is_empty() && !is_market_maker {
|
||||
@@ -117,17 +138,14 @@ impl ContractParameters {
|
||||
}
|
||||
|
||||
let mut win_conditions_to_sign = BTreeSet::<WinCondition>::new();
|
||||
for (outcome_index, payout_map) in self.outcome_payouts.iter().enumerate() {
|
||||
for (&outcome, payout_map) in self.outcome_payouts.iter() {
|
||||
// We want to sign the split TX for any win-conditions whose player is controlled
|
||||
// by `pubkey`. If we're the market maker, we sign every win condition.
|
||||
win_conditions_to_sign.extend(
|
||||
payout_map
|
||||
.keys()
|
||||
.filter(|winner| is_market_maker || controlling_players.contains(winner))
|
||||
.map(|&winner| WinCondition {
|
||||
winner,
|
||||
outcome_index,
|
||||
}),
|
||||
.map(|&winner| WinCondition { winner, outcome }),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use bitcoin::{absolute::LockTime, OutPoint, Sequence, Transaction, TxIn, TxOut};
|
||||
use musig2::{AdaptorSignature, AggNonce, PartialSignature, PubNonce, SecNonce};
|
||||
use musig2::{AdaptorSignature, AggNonce, CompactSignature, PartialSignature, PubNonce, SecNonce};
|
||||
use secp::Scalar;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::{
|
||||
contract::ContractParameters,
|
||||
contract::{ContractParameters, Outcome},
|
||||
errors::Error,
|
||||
parties::Player,
|
||||
spend_info::{FundingSpendInfo, OutcomeSpendInfo},
|
||||
@@ -13,26 +15,28 @@ use crate::{
|
||||
/// This contains cached data used for constructing further transactions,
|
||||
/// or signing the outcome transactions themselves.
|
||||
pub(crate) struct OutcomeTransactionBuildOutput {
|
||||
outcome_txs: Vec<Transaction>,
|
||||
outcome_spend_infos: Vec<OutcomeSpendInfo>,
|
||||
outcome_txs: BTreeMap<Outcome, Transaction>,
|
||||
outcome_spend_infos: BTreeMap<Outcome, OutcomeSpendInfo>,
|
||||
funding_spend_info: FundingSpendInfo,
|
||||
}
|
||||
|
||||
impl OutcomeTransactionBuildOutput {
|
||||
/// Return the set of mutually exclusive outcome transactions. One of these
|
||||
/// transactions will be executed depending on the oracle's attestation.
|
||||
pub(crate) fn outcome_txs(&self) -> &[Transaction] {
|
||||
pub(crate) fn outcome_txs(&self) -> &BTreeMap<Outcome, Transaction> {
|
||||
&self.outcome_txs
|
||||
}
|
||||
|
||||
/// Return the set of mutually exclusive outcome spend info objects.
|
||||
pub(crate) fn outcome_spend_infos(&self) -> &[OutcomeSpendInfo] {
|
||||
pub(crate) fn outcome_spend_infos(&self) -> &BTreeMap<Outcome, OutcomeSpendInfo> {
|
||||
&self.outcome_spend_infos
|
||||
}
|
||||
|
||||
/// Returns the number of [`musig2`] partial signatures required by each player
|
||||
/// in the DLC (and the market maker). This is the same as the length of
|
||||
/// [`Self::outcome_txs`].
|
||||
/// in the DLC (and the market maker).
|
||||
///
|
||||
/// If the contract has an expiry payout condition, this is simply the number
|
||||
/// of outcomes plus one. Otherwise it is the number of outcomes exactly.
|
||||
pub(crate) fn signatures_required(&self) -> usize {
|
||||
self.outcome_txs.len()
|
||||
}
|
||||
@@ -50,34 +54,42 @@ pub(crate) fn build_outcome_txs(
|
||||
};
|
||||
let outcome_value = params.outcome_output_value()?;
|
||||
|
||||
let n_outcomes = params.event.outcome_messages.len();
|
||||
let outcome_spend_infos: Vec<OutcomeSpendInfo> = (0..n_outcomes)
|
||||
.map(|outcome_index| {
|
||||
let payout_map = params.outcome_payouts.get(outcome_index).ok_or(Error)?;
|
||||
let outcome_spend_infos: BTreeMap<Outcome, OutcomeSpendInfo> = params
|
||||
.outcome_payouts
|
||||
.iter()
|
||||
.map(|(&outcome, payout_map)| {
|
||||
let winners = payout_map.keys().copied();
|
||||
|
||||
OutcomeSpendInfo::new(
|
||||
let spend_info = OutcomeSpendInfo::new(
|
||||
winners,
|
||||
¶ms.market_maker,
|
||||
outcome_value,
|
||||
params.relative_locktime_block_delta,
|
||||
)
|
||||
)?;
|
||||
Ok((outcome, spend_info))
|
||||
})
|
||||
.collect::<Result<_, Error>>()?;
|
||||
|
||||
let outcome_txs: Vec<Transaction> = outcome_spend_infos
|
||||
let outcome_txs: BTreeMap<Outcome, Transaction> = outcome_spend_infos
|
||||
.iter()
|
||||
.map(|outcome_spend_info| {
|
||||
.map(|(&outcome, outcome_spend_info)| {
|
||||
let outcome_output = TxOut {
|
||||
value: outcome_value,
|
||||
script_pubkey: outcome_spend_info.script_pubkey(),
|
||||
};
|
||||
Transaction {
|
||||
|
||||
let lock_time = match outcome {
|
||||
Outcome::Expiry => LockTime::from_consensus(params.event.expiry),
|
||||
Outcome::Attestation(_) => LockTime::ZERO, // Normal outcome transaction
|
||||
};
|
||||
|
||||
let outcome_tx = Transaction {
|
||||
version: bitcoin::transaction::Version::TWO,
|
||||
lock_time: LockTime::ZERO,
|
||||
lock_time,
|
||||
input: vec![funding_input.clone()],
|
||||
output: vec![outcome_output],
|
||||
}
|
||||
};
|
||||
|
||||
(outcome, outcome_tx)
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -103,7 +115,7 @@ pub(crate) fn partial_sign_outcome_txs<'a>(
|
||||
seckey: Scalar,
|
||||
secnonces: impl IntoIterator<Item = SecNonce>,
|
||||
aggnonces: impl IntoIterator<Item = &'a AggNonce>,
|
||||
) -> Result<Vec<PartialSignature>, Error> {
|
||||
) -> Result<BTreeMap<Outcome, PartialSignature>, Error> {
|
||||
let outcome_txs = &outcome_build_out.outcome_txs;
|
||||
let funding_spend_info = &outcome_build_out.funding_spend_info;
|
||||
|
||||
@@ -113,36 +125,47 @@ pub(crate) fn partial_sign_outcome_txs<'a>(
|
||||
.pubkey_index(seckey.base_point_mul())
|
||||
.ok_or(Error)?;
|
||||
|
||||
let n_outcomes = params.event.outcome_messages.len();
|
||||
let mut outcome_partial_sigs = Vec::with_capacity(n_outcomes);
|
||||
let mut outcome_partial_sigs = BTreeMap::<Outcome, PartialSignature>::new();
|
||||
|
||||
let mut aggnonce_iter = aggnonces.into_iter();
|
||||
let mut secnonce_iter = secnonces.into_iter();
|
||||
|
||||
for (outcome_index, outcome_tx) in outcome_txs.into_iter().enumerate() {
|
||||
for (&outcome, outcome_tx) in outcome_txs {
|
||||
let aggnonce = aggnonce_iter.next().ok_or(Error)?; // must provide enough aggnonces
|
||||
let secnonce = secnonce_iter.next().ok_or(Error)?; // must provide enough secnonces
|
||||
|
||||
// All outcome TX signatures should be locked by the oracle's outcome point.
|
||||
let attestation_lock_point = params
|
||||
.event
|
||||
.attestation_lock_point(outcome_index)
|
||||
.ok_or(Error)?;
|
||||
|
||||
// Hash the outcome TX.
|
||||
let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?;
|
||||
|
||||
// partially sign the sighash.
|
||||
let partial_sig = musig2::adaptor::sign_partial(
|
||||
funding_spend_info.key_agg_ctx(),
|
||||
seckey,
|
||||
secnonce,
|
||||
aggnonce,
|
||||
attestation_lock_point,
|
||||
sighash,
|
||||
)?;
|
||||
let partial_sig = match outcome {
|
||||
Outcome::Attestation(outcome_index) => {
|
||||
// All outcome TX signatures should be locked by the oracle's outcome point.
|
||||
let attestation_lock_point = params
|
||||
.event
|
||||
.attestation_lock_point(outcome_index)
|
||||
.ok_or(Error)?;
|
||||
|
||||
outcome_partial_sigs.push(partial_sig);
|
||||
// sign under an attestation lock point
|
||||
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)
|
||||
}
|
||||
@@ -166,34 +189,63 @@ pub(crate) fn verify_outcome_tx_partial_signatures<'p, 'a>(
|
||||
let mut pubnonce_iter = pubnonces.into_iter();
|
||||
let mut partial_sig_iter = partial_signatures.into_iter();
|
||||
|
||||
for (outcome_index, outcome_tx) in outcome_txs.into_iter().enumerate() {
|
||||
for (&outcome, outcome_tx) in outcome_txs {
|
||||
let aggnonce = aggnonce_iter.next().ok_or(Error)?; // must provide enough aggnonces
|
||||
let pubnonce = pubnonce_iter.next().ok_or(Error)?; // must provide enough aggnonces
|
||||
let pubnonce = pubnonce_iter.next().ok_or(Error)?; // must provide enough pubnonces
|
||||
let partial_sig = partial_sig_iter.next().ok_or(Error)?; // must provide enough sigs
|
||||
|
||||
// Hash the outcome TX.
|
||||
let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?;
|
||||
|
||||
// All outcome TX signatures should be locked by the oracle's outcome point.
|
||||
let attestation_lock_point = params
|
||||
.event
|
||||
.attestation_lock_point(outcome_index)
|
||||
.ok_or(Error)?;
|
||||
match outcome {
|
||||
Outcome::Attestation(outcome_index) => {
|
||||
// All outcome TX signatures should be locked by the oracle's outcome point.
|
||||
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,
|
||||
player.pubkey,
|
||||
pubnonce,
|
||||
sighash,
|
||||
)?;
|
||||
musig2::adaptor::verify_partial(
|
||||
funding_spend_info.key_agg_ctx(),
|
||||
partial_sig,
|
||||
aggnonce,
|
||||
attestation_lock_point,
|
||||
player.pubkey,
|
||||
pubnonce,
|
||||
sighash,
|
||||
)?;
|
||||
}
|
||||
|
||||
Outcome::Expiry => {
|
||||
musig2::verify_partial(
|
||||
funding_spend_info.key_agg_ctx(),
|
||||
partial_sig,
|
||||
aggnonce,
|
||||
player.pubkey,
|
||||
pubnonce,
|
||||
sighash,
|
||||
)?;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The result of aggregating signatures from all signers on all outcome transactions,
|
||||
/// optionally including an expiry transaction.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct OutcomeSignatures {
|
||||
/// A set of adaptor signatures which can be unlocked by the oracle's attestation
|
||||
/// for each outcome.
|
||||
pub(crate) adaptor_signatures: Vec<AdaptorSignature>,
|
||||
|
||||
/// The complete signature on the expiry transaction. This is `None` if the
|
||||
/// [`ContractParameters::outcome_payouts`] field does not contain an
|
||||
/// [`Outcome::Expiry`] key.
|
||||
pub(crate) expiry_tx_signature: Option<CompactSignature>,
|
||||
}
|
||||
|
||||
/// Aggregate groups of partial signatures for all outcome transactions.
|
||||
///
|
||||
/// Before running this method, the partial signatures should all have been
|
||||
@@ -208,7 +260,7 @@ pub(crate) fn aggregate_outcome_tx_adaptor_signatures<'a, S>(
|
||||
outcome_build_out: &OutcomeTransactionBuildOutput,
|
||||
aggnonces: impl IntoIterator<Item = &'a AggNonce>,
|
||||
partial_signature_groups: impl IntoIterator<Item = S>,
|
||||
) -> Result<Vec<AdaptorSignature>, Error>
|
||||
) -> Result<OutcomeSignatures, Error>
|
||||
where
|
||||
S: IntoIterator<Item = PartialSignature>,
|
||||
{
|
||||
@@ -218,34 +270,52 @@ where
|
||||
let mut aggnonce_iter = aggnonces.into_iter();
|
||||
let mut partial_sig_group_iter = partial_signature_groups.into_iter();
|
||||
|
||||
outcome_txs
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(outcome_index, outcome_tx)| {
|
||||
// must provide a set of sigs for each TX
|
||||
let partial_sigs = partial_sig_group_iter.next().ok_or(Error)?;
|
||||
let mut signatures = OutcomeSignatures {
|
||||
adaptor_signatures: Vec::with_capacity(params.event.outcome_messages.len()),
|
||||
expiry_tx_signature: None,
|
||||
};
|
||||
|
||||
let aggnonce = aggnonce_iter.next().ok_or(Error)?; // must provide enough aggnonces
|
||||
for (&outcome, outcome_tx) in outcome_txs {
|
||||
// must provide a set of sigs for each TX
|
||||
let partial_sigs = partial_sig_group_iter.next().ok_or(Error)?;
|
||||
|
||||
let attestation_lock_point = params
|
||||
.event
|
||||
.attestation_lock_point(outcome_index)
|
||||
.ok_or(Error)?;
|
||||
let aggnonce = aggnonce_iter.next().ok_or(Error)?; // must provide enough aggnonces
|
||||
|
||||
// Hash the outcome TX.
|
||||
let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?;
|
||||
// Hash the outcome TX.
|
||||
let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?;
|
||||
|
||||
let adaptor_sig = musig2::adaptor::aggregate_partial_signatures(
|
||||
funding_spend_info.key_agg_ctx(),
|
||||
aggnonce,
|
||||
attestation_lock_point,
|
||||
partial_sigs,
|
||||
sighash,
|
||||
)?;
|
||||
match outcome {
|
||||
Outcome::Attestation(outcome_index) => {
|
||||
let attestation_lock_point = params
|
||||
.event
|
||||
.attestation_lock_point(outcome_index)
|
||||
.ok_or(Error)?;
|
||||
|
||||
Ok(adaptor_sig)
|
||||
})
|
||||
.collect()
|
||||
let adaptor_sig = musig2::adaptor::aggregate_partial_signatures(
|
||||
funding_spend_info.key_agg_ctx(),
|
||||
aggnonce,
|
||||
attestation_lock_point,
|
||||
partial_sigs,
|
||||
sighash,
|
||||
)?;
|
||||
|
||||
signatures.adaptor_signatures.push(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)
|
||||
}
|
||||
|
||||
/// Construct an input to spend an outcome transaction for a specific outcome.
|
||||
@@ -253,13 +323,10 @@ where
|
||||
/// to construct a set of [`bitcoin::sighash::Prevouts`].
|
||||
pub(crate) fn outcome_tx_prevout<'x>(
|
||||
outcome_build_out: &'x OutcomeTransactionBuildOutput,
|
||||
outcome_index: usize,
|
||||
outcome: &Outcome,
|
||||
block_delay: u16,
|
||||
) -> Result<(TxIn, &'x TxOut), Error> {
|
||||
let outcome_tx = outcome_build_out
|
||||
.outcome_txs()
|
||||
.get(outcome_index)
|
||||
.ok_or(Error)?;
|
||||
let outcome_tx = outcome_build_out.outcome_txs().get(outcome).ok_or(Error)?;
|
||||
|
||||
let outcome_input = TxIn {
|
||||
previous_output: OutPoint {
|
||||
@@ -270,7 +337,7 @@ pub(crate) fn outcome_tx_prevout<'x>(
|
||||
..TxIn::default()
|
||||
};
|
||||
|
||||
let prevout = outcome_tx.output.get(outcome_index).ok_or(Error)?;
|
||||
let prevout = outcome_tx.output.get(0).ok_or(Error)?;
|
||||
|
||||
Ok((outcome_input, prevout))
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use secp::Scalar;
|
||||
use crate::{
|
||||
consts::{P2TR_DUST_VALUE, P2TR_SCRIPT_PUBKEY_SIZE},
|
||||
contract::{self, fees, outcome::OutcomeTransactionBuildOutput},
|
||||
contract::{ContractParameters, WinCondition},
|
||||
contract::{ContractParameters, Outcome, WinCondition},
|
||||
errors::Error,
|
||||
parties::Player,
|
||||
spend_info::SplitSpendInfo,
|
||||
@@ -17,14 +17,14 @@ use std::{borrow::Borrow, collections::BTreeMap};
|
||||
/// This contains cached data used for constructing further transactions,
|
||||
/// or signing the split transactions themselves.
|
||||
pub(crate) struct SplitTransactionBuildOutput {
|
||||
split_txs: Vec<Transaction>,
|
||||
split_txs: BTreeMap<Outcome, Transaction>,
|
||||
split_spend_infos: BTreeMap<WinCondition, SplitSpendInfo>,
|
||||
}
|
||||
|
||||
impl SplitTransactionBuildOutput {
|
||||
/// Return the set of mutually exclusive split transactions. Each of these
|
||||
/// transactions spend from a corresponding previous outcome transaction.
|
||||
pub(crate) fn split_txs(&self) -> &[Transaction] {
|
||||
pub(crate) fn split_txs(&self) -> &BTreeMap<Outcome, Transaction> {
|
||||
&self.split_txs
|
||||
}
|
||||
|
||||
@@ -43,16 +43,13 @@ pub(crate) fn build_split_txs(
|
||||
params: &ContractParameters,
|
||||
outcome_build_output: &OutcomeTransactionBuildOutput,
|
||||
) -> Result<SplitTransactionBuildOutput, Error> {
|
||||
let n_outcomes = params.outcome_payouts.len();
|
||||
let mut split_spend_infos = BTreeMap::<WinCondition, SplitSpendInfo>::new();
|
||||
let mut split_txs = Vec::<Transaction>::with_capacity(n_outcomes);
|
||||
|
||||
for outcome_index in 0..params.outcome_payouts.len() {
|
||||
let payout_map = params.outcome_payouts.get(outcome_index).ok_or(Error)?;
|
||||
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_index)
|
||||
.get(&outcome)
|
||||
.ok_or(Error)?;
|
||||
|
||||
// Fee estimation
|
||||
@@ -69,7 +66,7 @@ pub(crate) fn build_split_txs(
|
||||
|
||||
let (outcome_input, _) = contract::outcome::outcome_tx_prevout(
|
||||
outcome_build_output,
|
||||
outcome_index,
|
||||
&outcome,
|
||||
params.relative_locktime_block_delta, // Split TXs have 1*delta block delay
|
||||
)?;
|
||||
|
||||
@@ -90,17 +87,19 @@ pub(crate) fn build_split_txs(
|
||||
|
||||
let win_cond = WinCondition {
|
||||
winner: player,
|
||||
outcome_index,
|
||||
outcome,
|
||||
};
|
||||
split_spend_infos.insert(win_cond, split_spend_info);
|
||||
}
|
||||
|
||||
split_txs.push(Transaction {
|
||||
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 {
|
||||
@@ -138,23 +137,24 @@ pub(crate) fn partial_sign_split_txs<'a>(
|
||||
return Ok(partial_signatures);
|
||||
}
|
||||
|
||||
let split_txs = &split_build_out.split_txs;
|
||||
|
||||
let mut aggnonce_iter = aggnonces.into_iter();
|
||||
let mut secnonce_iter = secnonces.into_iter();
|
||||
|
||||
for win_cond in win_conditions_to_sign {
|
||||
let split_tx = split_txs.get(win_cond.outcome_index).ok_or(Error)?;
|
||||
let split_tx = split_build_out
|
||||
.split_txs()
|
||||
.get(&win_cond.outcome)
|
||||
.ok_or(Error)?;
|
||||
|
||||
let aggnonce = aggnonce_iter.next().ok_or(Error)?; // must provide enough aggnonces
|
||||
let secnonce = secnonce_iter.next().ok_or(Error)?; // must provide enough secnonces
|
||||
|
||||
// Hash the split TX.
|
||||
let outcome_spend_info = outcome_build_out
|
||||
.outcome_spend_infos()
|
||||
.get(win_cond.outcome_index)
|
||||
.get(&win_cond.outcome)
|
||||
.ok_or(Error)?;
|
||||
|
||||
// Hash the split TX.
|
||||
let sighash = outcome_spend_info.sighash_tx_split(split_tx, &win_cond.winner)?;
|
||||
|
||||
// Partially sign the sighash.
|
||||
@@ -193,10 +193,11 @@ pub(crate) fn verify_split_tx_partial_signatures(
|
||||
.win_conditions_controlled_by_pubkey(player.pubkey)
|
||||
.ok_or(Error)?;
|
||||
|
||||
let split_txs = &split_build_out.split_txs;
|
||||
|
||||
for win_cond in win_conditions_to_sign {
|
||||
let split_tx = split_txs.get(win_cond.outcome_index).ok_or(Error)?;
|
||||
let split_tx = split_build_out
|
||||
.split_txs()
|
||||
.get(&win_cond.outcome)
|
||||
.ok_or(Error)?;
|
||||
|
||||
let aggnonce = aggnonces.get(&win_cond).ok_or(Error)?; // must provide all aggnonces
|
||||
let pubnonce = pubnonces.get(&win_cond).ok_or(Error)?; // must provide all pubnonces
|
||||
@@ -204,7 +205,7 @@ pub(crate) fn verify_split_tx_partial_signatures(
|
||||
|
||||
let outcome_spend_info = outcome_build_out
|
||||
.outcome_spend_infos()
|
||||
.get(win_cond.outcome_index)
|
||||
.get(&win_cond.outcome)
|
||||
.ok_or(Error)?;
|
||||
|
||||
// Hash the split TX.
|
||||
@@ -240,13 +241,14 @@ where
|
||||
&'s S: IntoIterator<Item = P>,
|
||||
P: Borrow<PartialSignature>,
|
||||
{
|
||||
let split_txs = &split_build_out.split_txs;
|
||||
|
||||
split_build_out
|
||||
.split_spend_infos
|
||||
.keys()
|
||||
.map(|&win_cond| {
|
||||
let split_tx = split_txs.get(win_cond.outcome_index).ok_or(Error)?;
|
||||
let split_tx = split_build_out
|
||||
.split_txs()
|
||||
.get(&win_cond.outcome)
|
||||
.ok_or(Error)?;
|
||||
|
||||
let relevant_partial_sigs = partial_signatures_by_win_cond
|
||||
.get(&win_cond)
|
||||
@@ -258,7 +260,7 @@ where
|
||||
|
||||
let outcome_spend_info = outcome_build_out
|
||||
.outcome_spend_infos()
|
||||
.get(win_cond.outcome_index)
|
||||
.get(&win_cond.outcome)
|
||||
.ok_or(Error)?;
|
||||
|
||||
// Hash the split TX.
|
||||
@@ -282,17 +284,19 @@ where
|
||||
pub(crate) fn split_tx_prevout<'x>(
|
||||
params: &ContractParameters,
|
||||
split_build_out: &'x SplitTransactionBuildOutput,
|
||||
outcome_index: usize,
|
||||
winner: &Player,
|
||||
win_cond: &WinCondition,
|
||||
block_delay: u16,
|
||||
) -> Result<(TxIn, &'x TxOut), Error> {
|
||||
let split_tx = split_build_out
|
||||
.split_txs()
|
||||
.get(outcome_index)
|
||||
.get(&win_cond.outcome)
|
||||
.ok_or(Error)?;
|
||||
|
||||
let payout_map = params.outcome_payouts.get(outcome_index).ok_or(Error)?;
|
||||
let split_tx_output_index = payout_map.keys().position(|p| p == winner).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(|p| p == &win_cond.winner)
|
||||
.ok_or(Error)?;
|
||||
|
||||
let input = TxIn {
|
||||
previous_output: OutPoint {
|
||||
|
||||
Reference in New Issue
Block a user