dlctix 0.0.9

Ticketed Discreet Log Contracts
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
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},
};

/// Represents the output of building the set of outcome transactions.
/// This contains cached data used for constructing further transactions,
/// or signing the outcome transactions themselves.
#[derive(Clone, Eq, PartialEq)]
pub(crate) struct OutcomeTransactionBuildOutput {
    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) -> &BTreeMap<Outcome, Transaction> {
        &self.outcome_txs
    }

    /// Return the set of mutually exclusive outcome spend info objects.
    pub(crate) fn outcome_spend_infos(&self) -> &BTreeMap<Outcome, OutcomeSpendInfo> {
        &self.outcome_spend_infos
    }

    /// Return the funding transaction's spending info object.
    pub(crate) fn funding_spend_info(&self) -> &FundingSpendInfo {
        &self.funding_spend_info
    }
}

/// Construct a set of unsigned outcome transactions which spend from the funding TX.
pub(crate) fn build_outcome_txs(
    params: &ContractParameters,
    funding_outpoint: OutPoint,
) -> Result<OutcomeTransactionBuildOutput, Error> {
    let funding_input = TxIn {
        previous_output: funding_outpoint,
        sequence: Sequence::ENABLE_LOCKTIME_NO_RBF,
        ..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(
                &params.players,
                winners,
                &params.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.ok_or(Error::InvalidLocktime)?)
                }
                Outcome::Attestation(_) => LockTime::ZERO, // Normal outcome transaction
            };

            let outcome_tx = Transaction {
                version: bitcoin::transaction::Version::TWO,
                lock_time,
                input: vec![funding_input.clone()],
                output: vec![outcome_output],
            };

            Ok((outcome, outcome_tx))
        })
        .collect::<Result<_, Error>>()?;

    let funding_spend_info =
        FundingSpendInfo::new(&params.market_maker, &params.players, params.funding_value)?;

    let output = OutcomeTransactionBuildOutput {
        outcome_txs,
        outcome_spend_infos,
        funding_spend_info,
    };

    Ok(output)
}

/// Construct a set of partial signatures for the outcome transactions.
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;

    // Confirm the key is a part of the group.
    funding_spend_info
        .key_agg_ctx()
        .pubkey_index(seckey.base_point_mul())
        .ok_or(Error::InvalidKey)?;

    let mut outcome_partial_sigs = BTreeMap::<Outcome, PartialSignature>::new();

    for (&outcome, outcome_tx) in outcome_txs {
        let aggnonce = aggnonces
            .get(&outcome)
            .ok_or(Error::MissingNonce(String::from("aggnonce for outcome")))?; // must provide all aggnonces
        let secnonce = secnonces
            .remove(&outcome)
            .ok_or(Error::MissingNonce(String::from("secnonce for outcome")))?; // must provide all secnonces

        // Hash the outcome TX.
        let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?;

        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
                    .locking_points
                    .get(outcome_index)
                    .ok_or(Error::UnknownOutcome)?;

                // 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)
}

/// Verify a player's partial adaptor signatures on the outcome transactions.
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::MissingNonce(String::from("aggnonce for outcome")))?; // must provide all aggnonces
        let pubnonce = pubnonces
            .get(&outcome)
            .ok_or(Error::MissingNonce(String::from("pubnonce for outcome")))?; // must provide all pubnonces
        let &partial_sig = partial_signatures
            .get(&outcome)
            .ok_or(Error::MissingSignature(String::from(
                "partial_signatures for outcome",
            )))?; // must provide all sigs

        // Hash the outcome TX.
        let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?;

        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
                    .locking_points
                    .get(outcome_index)
                    .ok_or(Error::UnknownOutcome)?;

                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(())
}

/// 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) outcome_tx_signatures: BTreeMap<OutcomeIndex, 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
/// individually verified so that any blame can be assigned to signers
/// who submitted invalid signatures.
///
/// If all partial signatures are valid, then aggregation succeeds and this
/// function outputs a set of adaptor signatures which are valid once adapted
/// with the oracle's attestation.
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 {
        // must provide a set of sigs for each TX
        let partial_sigs =
            partial_signature_groups
                .remove(&outcome)
                .ok_or(Error::MissingSignature(String::from(
                    "outcome from partial signature groups",
                )))?;

        // must provide all aggnonces
        let aggnonce = aggnonces
            .get(&outcome)
            .ok_or(Error::MissingNonce(String::from("aggnonces for outcome")))?;

        // Hash the outcome TX.
        let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?;

        match outcome {
            Outcome::Attestation(outcome_index) => {
                let attestation_lock_point = params
                    .event
                    .locking_points
                    .get(outcome_index)
                    .ok_or(Error::UnknownOutcome)?;

                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)
}

/// Verify the set of complete aggregated signatures on the
/// outcome and expiry transactions.
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();

    // We only need to verify signatures on outcomes where our pubkey might
    // win something.
    let relevant_outcomes: BTreeSet<Outcome> = params
        .win_conditions_claimable_by_pubkey(our_pubkey)
        .ok_or(Error::InvalidKey)?
        .into_iter()
        .map(|win_cond| win_cond.outcome)
        .collect();

    // Construct a batch for efficient mass signature verification.
    let batch: Vec<BatchVerificationRow> = relevant_outcomes
        .into_iter()
        .map(|outcome| {
            let outcome_tx = outcome_build_out
                .outcome_txs
                .get(&outcome)
                .ok_or(Error::UnknownOutcome)?;

            let sighash = outcome_build_out
                .funding_spend_info
                .sighash_tx_outcome(outcome_tx)?;

            let batch_row = match outcome {
                // One adaptor signature for each possible attestation outcome.
                Outcome::Attestation(outcome_index) => {
                    let adaptor_point = params
                        .event
                        .locking_points
                        .get(outcome_index)
                        .ok_or(Error::UnknownOutcome)?;

                    let &signature = outcome_tx_signatures.get(&outcome_index).ok_or(
                        Error::MissingSignature(String::from(
                            "outcome index outcome tx signatures",
                        )),
                    )?;
                    BatchVerificationRow::from_adaptor_signature(
                        joint_pubkey,
                        sighash,
                        signature,
                        *adaptor_point,
                    )
                }

                // One signature for the optional expiry transaction.
                Outcome::Expiry => {
                    let signature = expiry_tx_signature
                        .ok_or(Error::MissingSignature(String::from("expiry tx signature")))?
                        .lift_nonce()?;
                    BatchVerificationRow::from_signature(joint_pubkey, sighash, signature)
                }
            };

            Ok(batch_row)
        })
        .collect::<Result<_, Error>>()?;

    // Verify all outcome signatures at once.
    musig2::verify_batch(&batch)?;

    Ok(())
}

/// Construct an input to spend an outcome transaction for a specific outcome.
/// Also returns a reference to the outcome TX's output so it can be used
/// to construct a set of [`bitcoin::sighash::Prevouts`].
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::UnknownOutcome)?;

    let outcome_input = TxIn {
        previous_output: OutPoint {
            txid: outcome_tx.compute_txid(),
            vout: 0,
        },
        sequence: Sequence::from_height(block_delay),
        ..TxIn::default()
    };

    let prevout = outcome_tx
        .output
        .get(0)
        .ok_or(Error::InvalidInput("missing outcome tx output"))?;

    Ok((outcome_input, prevout))
}