11import 'dart:typed_data' ;
2+ import 'package:coinlib/src/common/bigints.dart' ;
23import 'package:coinlib/src/common/bytes.dart' ;
34import 'package:coinlib/src/common/hex.dart' ;
45import 'package:coinlib/src/common/serial.dart' ;
56import 'package:coinlib/src/crypto/ec_public_key.dart' ;
67import 'package:coinlib/src/crypto/hash.dart' ;
78import 'package:coinlib/src/musig/library.dart' ;
89import 'package:coinlib/src/network.dart' ;
10+ import 'package:coinlib/src/taproot/leaves.dart' ;
11+ import 'package:coinlib/src/taproot/taproot.dart' ;
912import 'package:coinlib/src/tx/locktime.dart' ;
10- import 'package:coinlib/src/tx/output.dart' ;
1113import 'package:coinlib/src/tx/transaction.dart' ;
1214import 'package:collection/collection.dart' ;
13-
14- class InvalidDLCTerms implements Exception {
15-
16- final String message;
17-
18- InvalidDLCTerms (this .message);
19- InvalidDLCTerms .badOutcomeMatch ()
20- : this ("Contains outcome output amounts not matching the funded amount" );
21- InvalidDLCTerms .badVersion (int v)
22- : this ("Version $v isn't allowed. Only v1 is supported." );
23- InvalidDLCTerms .noOutputs () : this ("CETOutputs have no outputs" );
24- InvalidDLCTerms .smallOutput (BigInt min)
25- : this ("Contains output value less than min of $min " );
26- InvalidDLCTerms .smallFunding (BigInt min)
27- : this ("Contains funding value less than min of $min " );
28- InvalidDLCTerms .notOrdered ()
29- : this ("The input bytes contain out-of-order keys" );
30-
31- }
32-
33- BigInt _addBigInts (Iterable <BigInt > ints) => ints.fold (
34- BigInt .zero, (a, b) => a+ b,
35- );
15+ import 'outcome.dart' ;
16+ import 'errors.dart' ;
3617
3718Map <ECPublicKey , T > _xOnlyUnmodifiableMap <T >(Map <ECPublicKey , T > map)
3819 => Map .unmodifiable (map.map ((key, v) => MapEntry (key.xonly, v)));
@@ -69,35 +50,6 @@ void _writeOrderedPubkeyMap<T>(
6950
7051}
7152
72- /// A CET will pay to the [outputs] with the value of each output evenly reduced
73- /// to cover the transaction fee.
74- class CETOutputs {
75-
76- /// The outputs to be included in the CET of this outcome. The values must
77- /// add up to the amounts being funded by the participants in
78- /// [DLCTerms.fundAmounts] .
79- ///
80- /// The [Output.value] for each output will have an equal share of the
81- /// transaction fee removed when the transaction is constructed. When doing
82- /// this, if any of the outputs fall below the dust amount, they will be
83- /// removed first.
84- final List <Output > outputs;
85-
86- /// Requires that the output values are at least [Network.minOutput] or
87- /// [InvalidDLCTerms] may be thrown.
88- CETOutputs (this .outputs, Network network) {
89- if (outputs.isEmpty) {
90- throw InvalidDLCTerms .noOutputs ();
91- }
92- if (outputs.any ((out) => out.value.compareTo (network.minOutput) < 0 )) {
93- throw InvalidDLCTerms .smallOutput (network.minOutput);
94- }
95- }
96-
97- BigInt get totalValue => _addBigInts (outputs.map ((out) => out.value));
98-
99- }
100-
10153/// Specifies the terms of a DLC contract to be agreed upon by all
10254/// [participants] .
10355///
@@ -124,7 +76,7 @@ class DLCTerms with Writable {
12476 /// contribution to the Funding Transaction fee in excess of these amounts.
12577 final Map <ECPublicKey , BigInt > fundAmounts;
12678
127- /// Maps oracle adaptor points to [CETOutputs ] that contain the output
79+ /// Maps oracle adaptor points to [CETOutcome ] that contain the output
12880 /// information to include in Contract Execution Transactions.
12981 ///
13082 /// The points can be arbitrarily announced by the oracle in association with
@@ -137,7 +89,7 @@ class DLCTerms with Writable {
13789 /// computation of multiple adaptor points for multiple outcome messages given
13890 /// the R and P points. coinlib doesn't provide an abstraction for
13991 /// constructing adaptor points via signatures this way.
140- final Map <ECPublicKey , CETOutputs > outcomes;
92+ final Map <ECPublicKey , CETOutcome > outcomes;
14193
14294 /// The [Transaction.locktime] to be used in the Refund Transaction where
14395 /// participants may regain access to funds.
@@ -151,7 +103,7 @@ class DLCTerms with Writable {
151103 DLCTerms ({
152104 required Set <ECPublicKey > participants,
153105 required Map <ECPublicKey , BigInt > fundAmounts,
154- required Map <ECPublicKey , CETOutputs > outcomes,
106+ required Map <ECPublicKey , CETOutcome > outcomes,
155107 required this .refundLocktime,
156108 required Network network,
157109 }) :
@@ -166,7 +118,7 @@ class DLCTerms with Writable {
166118 }
167119
168120 // The outcome output amounts must add up to the total funded amount
169- final totalToFund = _addBigInts (fundAmounts.values);
121+ final totalToFund = addBigInts (fundAmounts.values);
170122 if (
171123 outcomes.values.any (
172124 (outcome) => outcome.totalValue.compareTo (totalToFund) != 0 ,
@@ -175,6 +127,14 @@ class DLCTerms with Writable {
175127 throw InvalidDLCTerms .badOutcomeMatch ();
176128 }
177129
130+ if (
131+ outcomes.values.any (
132+ (outcome) => ! outcome.locktime.isDefinitelyBefore (refundLocktime),
133+ )
134+ ) {
135+ throw InvalidDLCTerms .cetLocktimeAfterRf ();
136+ }
137+
178138 }
179139
180140 /// There are no size limits, so the caller may wish to enforce a reasonable
@@ -196,10 +156,7 @@ class DLCTerms with Writable {
196156 fundAmounts: _readPubKeyMap (reader, () => reader.readVarInt ()),
197157 outcomes: _readPubKeyMap (
198158 reader,
199- () => CETOutputs (
200- reader.readListWithFunc (() => Output .fromReader (reader)),
201- network,
202- ),
159+ () => CETOutcome .fromReader (reader, network),
203160 ),
204161 refundLocktime: reader.readLocktime (),
205162 network: network,
@@ -242,7 +199,7 @@ class DLCTerms with Writable {
242199 _writeOrderedPubkeyMap (
243200 writer,
244201 outcomes,
245- (outputs ) => writer. writeWritableVector (outputs.outputs ),
202+ (outcome ) => outcome. write (writer ),
246203 );
247204
248205 writer.writeLocktime (refundLocktime);
@@ -252,14 +209,23 @@ class DLCTerms with Writable {
252209 // Use a tagged hasher to avoid potential conflicts that could lead to key
253210 // reuse
254211 static final _dlcKeyTweakHash = getTaggedHasher ("CoinlibDLCKeyTweak" );
255- Uint8List ? _tweakHashCache;
212+
213+ MuSigPublicKeys ? _muSigCache;
256214
257215 /// Obtains the tweaked MuSig2 aggregate key for this DLC. The key is
258216 /// aggregated from the [participants] and then tweaked from the [DLCTerms]
259217 /// data to prevent key-reuse across multiple DLCs in the event that
260218 /// participants re-use their individual keys.
261- MuSigPublicKeys get musig => MuSigPublicKeys (participants).tweak (
262- _tweakHashCache ?? = _dlcKeyTweakHash (toBytes ()),
219+ MuSigPublicKeys get musig
220+ => _muSigCache ?? = MuSigPublicKeys (participants).tweak (
221+ _dlcKeyTweakHash (toBytes ()),
222+ );
223+
224+ /// Obtains [Taproot] allowing key-path spend using the [musig] key or an APO
225+ /// CHECKSIG script-path using the same key used by the CETs and RF.
226+ Taproot get taproot => Taproot (
227+ internalKey: musig.aggregate,
228+ mast: TapLeafChecksig .apoInternal,
263229 );
264230
265231}
0 commit comments