@atomicfinance/bitcoin-ddk-provider 4.1.12 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +27 -0
- package/dist/BitcoinDdkProvider.d.ts +28 -23
- package/dist/BitcoinDdkProvider.js +219 -349
- package/dist/BitcoinDdkProvider.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/Utils.d.ts +66 -1
- package/dist/utils/Utils.js +255 -0
- package/dist/utils/Utils.js.map +1 -1
- package/lib/BitcoinDdkProvider.ts +394 -449
- package/lib/index.ts +1 -0
- package/lib/utils/Utils.ts +332 -0
- package/package.json +5 -5
|
@@ -85,7 +85,20 @@ import {
|
|
|
85
85
|
import crypto from 'crypto';
|
|
86
86
|
import { ECPairFactory, ECPairInterface } from 'ecpair';
|
|
87
87
|
|
|
88
|
-
import {
|
|
88
|
+
import {
|
|
89
|
+
checkTypes,
|
|
90
|
+
computeContractId,
|
|
91
|
+
createP2MSMultisig,
|
|
92
|
+
createP2WSHMultisig,
|
|
93
|
+
createP2WSHMultisigFromOrdered,
|
|
94
|
+
ensureBuffer,
|
|
95
|
+
ensureCompactSignature,
|
|
96
|
+
ensureDerSignature,
|
|
97
|
+
generateSerialId,
|
|
98
|
+
orderPubkeysLexicographically,
|
|
99
|
+
outputsToPayouts,
|
|
100
|
+
sortFundingInputsBySerialId,
|
|
101
|
+
} from './utils/Utils';
|
|
89
102
|
|
|
90
103
|
const ECPair = ECPairFactory(ecc);
|
|
91
104
|
|
|
@@ -105,220 +118,6 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
105
118
|
}
|
|
106
119
|
}
|
|
107
120
|
|
|
108
|
-
/**
|
|
109
|
-
* Helper function to ensure we have a Buffer object
|
|
110
|
-
* Handles cases where Buffer objects have been serialized/deserialized
|
|
111
|
-
*/
|
|
112
|
-
private ensureBuffer(
|
|
113
|
-
bufferLike: Buffer | { type: string; data: number[] } | any,
|
|
114
|
-
): Buffer {
|
|
115
|
-
if (Buffer.isBuffer(bufferLike)) {
|
|
116
|
-
return bufferLike;
|
|
117
|
-
}
|
|
118
|
-
if (bufferLike && bufferLike.type === 'Buffer' && bufferLike.data) {
|
|
119
|
-
return Buffer.from(bufferLike.data);
|
|
120
|
-
}
|
|
121
|
-
return bufferLike;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Detect if signature is in compact format (64 bytes) or DER format
|
|
126
|
-
* and convert compact to DER if needed, adding SIGHASH_ALL flag
|
|
127
|
-
*/
|
|
128
|
-
private ensureDerSignature(signature: Buffer): Buffer {
|
|
129
|
-
// If signature is 64 bytes, it's likely compact format (32-byte r + 32-byte s)
|
|
130
|
-
if (signature.length === 64) {
|
|
131
|
-
// Convert compact signature to DER format
|
|
132
|
-
const r = signature.slice(0, 32);
|
|
133
|
-
const s = signature.slice(32, 64);
|
|
134
|
-
|
|
135
|
-
// Create DER encoding manually
|
|
136
|
-
// DER format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S]
|
|
137
|
-
|
|
138
|
-
// Remove leading zeros from r and s, but keep at least one byte
|
|
139
|
-
let rBytes = r;
|
|
140
|
-
while (
|
|
141
|
-
rBytes.length > 1 &&
|
|
142
|
-
rBytes[0] === 0x00 &&
|
|
143
|
-
(rBytes[1] & 0x80) === 0
|
|
144
|
-
) {
|
|
145
|
-
rBytes = rBytes.slice(1);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
let sBytes = s;
|
|
149
|
-
while (
|
|
150
|
-
sBytes.length > 1 &&
|
|
151
|
-
sBytes[0] === 0x00 &&
|
|
152
|
-
(sBytes[1] & 0x80) === 0
|
|
153
|
-
) {
|
|
154
|
-
sBytes = sBytes.slice(1);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Add padding byte if high bit is set (to keep numbers positive)
|
|
158
|
-
if ((rBytes[0] & 0x80) !== 0) {
|
|
159
|
-
rBytes = Buffer.concat([Buffer.from([0x00]), rBytes]);
|
|
160
|
-
}
|
|
161
|
-
if ((sBytes[0] & 0x80) !== 0) {
|
|
162
|
-
sBytes = Buffer.concat([Buffer.from([0x00]), sBytes]);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const totalLength = 2 + rBytes.length + 2 + sBytes.length;
|
|
166
|
-
|
|
167
|
-
const derSignature = Buffer.concat([
|
|
168
|
-
Buffer.from([0x30, totalLength]), // SEQUENCE tag and total length
|
|
169
|
-
Buffer.from([0x02, rBytes.length]), // INTEGER tag and R length
|
|
170
|
-
rBytes,
|
|
171
|
-
Buffer.from([0x02, sBytes.length]), // INTEGER tag and S length
|
|
172
|
-
sBytes,
|
|
173
|
-
Buffer.from([0x01]), // SIGHASH_ALL flag
|
|
174
|
-
]);
|
|
175
|
-
|
|
176
|
-
return derSignature;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// If it's already DER format, check if it has SIGHASH flag
|
|
180
|
-
if (signature.length > 0 && signature[0] === 0x30) {
|
|
181
|
-
// Check if it already has a SIGHASH flag (last byte should be 0x01 for SIGHASH_ALL)
|
|
182
|
-
if (signature[signature.length - 1] !== 0x01) {
|
|
183
|
-
// Add SIGHASH_ALL flag
|
|
184
|
-
return Buffer.concat([signature, Buffer.from([0x01])]);
|
|
185
|
-
}
|
|
186
|
-
return signature;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// For other formats, return as-is
|
|
190
|
-
return signature;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Detect if signature is in DER format and convert to compact format (64 bytes)
|
|
195
|
-
* by extracting r and s values, removing SIGHASH flag if present
|
|
196
|
-
*/
|
|
197
|
-
private ensureCompactSignature(signature: Buffer): Buffer {
|
|
198
|
-
// If signature is already 64 bytes, it's likely already compact format
|
|
199
|
-
if (signature.length === 64) {
|
|
200
|
-
return signature;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Check if it's DER format (starts with 0x30)
|
|
204
|
-
if (signature.length > 6 && signature[0] === 0x30) {
|
|
205
|
-
let derSig = signature;
|
|
206
|
-
|
|
207
|
-
// Remove SIGHASH flag if present (last byte is typically 0x01 for SIGHASH_ALL)
|
|
208
|
-
if (signature[signature.length - 1] === 0x01) {
|
|
209
|
-
derSig = signature.slice(0, -1);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Parse DER format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S]
|
|
213
|
-
if (derSig[0] !== 0x30) {
|
|
214
|
-
throw new Error('Invalid DER signature: missing SEQUENCE tag');
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const totalLength = derSig[1];
|
|
218
|
-
if (derSig.length < totalLength + 2) {
|
|
219
|
-
throw new Error('Invalid DER signature: length mismatch');
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
let offset = 2;
|
|
223
|
-
|
|
224
|
-
// Parse R value
|
|
225
|
-
if (derSig[offset] !== 0x02) {
|
|
226
|
-
throw new Error('Invalid DER signature: missing INTEGER tag for R');
|
|
227
|
-
}
|
|
228
|
-
offset++;
|
|
229
|
-
|
|
230
|
-
const rLength = derSig[offset];
|
|
231
|
-
offset++;
|
|
232
|
-
|
|
233
|
-
if (offset + rLength > derSig.length) {
|
|
234
|
-
throw new Error(
|
|
235
|
-
'Invalid DER signature: R length exceeds signature length',
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
let rBytes = derSig.slice(offset, offset + rLength);
|
|
240
|
-
offset += rLength;
|
|
241
|
-
|
|
242
|
-
// Parse S value
|
|
243
|
-
if (derSig[offset] !== 0x02) {
|
|
244
|
-
throw new Error('Invalid DER signature: missing INTEGER tag for S');
|
|
245
|
-
}
|
|
246
|
-
offset++;
|
|
247
|
-
|
|
248
|
-
const sLength = derSig[offset];
|
|
249
|
-
offset++;
|
|
250
|
-
|
|
251
|
-
if (offset + sLength > derSig.length) {
|
|
252
|
-
throw new Error(
|
|
253
|
-
'Invalid DER signature: S length exceeds signature length',
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
let sBytes = derSig.slice(offset, offset + sLength);
|
|
258
|
-
|
|
259
|
-
// Remove leading zero padding from r and s (DER may pad to prevent negative interpretation)
|
|
260
|
-
while (rBytes.length > 1 && rBytes[0] === 0x00) {
|
|
261
|
-
rBytes = rBytes.slice(1);
|
|
262
|
-
}
|
|
263
|
-
while (sBytes.length > 1 && sBytes[0] === 0x00) {
|
|
264
|
-
sBytes = sBytes.slice(1);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Pad to 32 bytes each (compact format requires exactly 32 bytes for r and s)
|
|
268
|
-
while (rBytes.length < 32) {
|
|
269
|
-
rBytes = Buffer.concat([Buffer.from([0x00]), rBytes]);
|
|
270
|
-
}
|
|
271
|
-
while (sBytes.length < 32) {
|
|
272
|
-
sBytes = Buffer.concat([Buffer.from([0x00]), sBytes]);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (rBytes.length !== 32 || sBytes.length !== 32) {
|
|
276
|
-
throw new Error('Invalid signature values: r or s exceeds 32 bytes');
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Combine r and s into 64-byte compact format
|
|
280
|
-
return Buffer.concat([rBytes, sBytes]);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// For other formats, throw error as we can't convert
|
|
284
|
-
throw new Error(
|
|
285
|
-
'Unable to convert signature to compact format: unknown format',
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Compute contract ID from fund transaction ID, output index, and temporary contract ID
|
|
291
|
-
* Matches the Rust implementation in rust-dlc
|
|
292
|
-
*/
|
|
293
|
-
private computeContractId(
|
|
294
|
-
fundTxId: Buffer,
|
|
295
|
-
fundOutputIndex: number,
|
|
296
|
-
temporaryContractId: Buffer,
|
|
297
|
-
): Buffer {
|
|
298
|
-
if (fundTxId.length !== 32) {
|
|
299
|
-
throw new Error('Fund transaction ID must be 32 bytes');
|
|
300
|
-
}
|
|
301
|
-
if (temporaryContractId.length !== 32) {
|
|
302
|
-
throw new Error('Temporary contract ID must be 32 bytes');
|
|
303
|
-
}
|
|
304
|
-
if (fundOutputIndex > 0xffff) {
|
|
305
|
-
throw new Error('Fund output index must fit in 16 bits');
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const result = Buffer.alloc(32);
|
|
309
|
-
|
|
310
|
-
// XOR fund_tx_id with temporary_id, with byte order reversal for fund_tx_id
|
|
311
|
-
for (let i = 0; i < 32; i++) {
|
|
312
|
-
result[i] = fundTxId[31 - i] ^ temporaryContractId[i];
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// XOR the fund output index into the last two bytes
|
|
316
|
-
result[30] ^= (fundOutputIndex >> 8) & 0xff; // High byte
|
|
317
|
-
result[31] ^= fundOutputIndex & 0xff; // Low byte
|
|
318
|
-
|
|
319
|
-
return result;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
121
|
/**
|
|
323
122
|
* Create refund signature using PSBT method instead of DDK
|
|
324
123
|
*/
|
|
@@ -338,22 +137,11 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
338
137
|
);
|
|
339
138
|
}
|
|
340
139
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
|
|
345
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
346
|
-
|
|
347
|
-
const p2ms = payments.p2ms({
|
|
348
|
-
m: 2,
|
|
349
|
-
pubkeys: fundingPubKeys,
|
|
140
|
+
const paymentVariant = createP2WSHMultisig(
|
|
141
|
+
dlcOffer.fundingPubkey,
|
|
142
|
+
dlcAccept.fundingPubkey,
|
|
350
143
|
network,
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const paymentVariant = payments.p2wsh({
|
|
354
|
-
redeem: p2ms,
|
|
355
|
-
network,
|
|
356
|
-
});
|
|
144
|
+
);
|
|
357
145
|
|
|
358
146
|
// Add the funding input with sequence from refund transaction
|
|
359
147
|
psbt.addInput({
|
|
@@ -404,7 +192,7 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
404
192
|
const derSignature = partialSig.signature;
|
|
405
193
|
|
|
406
194
|
// Convert DER signature to compact format
|
|
407
|
-
return
|
|
195
|
+
return ensureCompactSignature(derSignature);
|
|
408
196
|
}
|
|
409
197
|
|
|
410
198
|
/**
|
|
@@ -962,12 +750,19 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
962
750
|
}),
|
|
963
751
|
);
|
|
964
752
|
|
|
965
|
-
// Calculate input amounts
|
|
966
|
-
const
|
|
753
|
+
// Calculate input amounts (including both regular inputs and DLC inputs)
|
|
754
|
+
const localRegularInputAmount = localRegularInputs.reduce<number>(
|
|
967
755
|
(prev, cur) => prev + cur.amount.GetSatoshiAmount(),
|
|
968
756
|
0,
|
|
969
757
|
);
|
|
970
758
|
|
|
759
|
+
const localDlcInputAmount = localDlcInputs.reduce<number>(
|
|
760
|
+
(prev, cur) => prev + Number(cur.fundAmount),
|
|
761
|
+
0,
|
|
762
|
+
);
|
|
763
|
+
|
|
764
|
+
const localInputAmount = localRegularInputAmount + localDlcInputAmount;
|
|
765
|
+
|
|
971
766
|
const remoteInputAmount = remoteInputs.reduce<number>(
|
|
972
767
|
(prev, cur) => prev + cur.amount.GetSatoshiAmount(),
|
|
973
768
|
0,
|
|
@@ -1046,7 +841,7 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
1046
841
|
BigInt(dlcOffer.feeRatePerVb),
|
|
1047
842
|
0,
|
|
1048
843
|
dlcOffer.cetLocktime,
|
|
1049
|
-
dlcOffer.fundOutputSerialId,
|
|
844
|
+
BigInt(dlcOffer.fundOutputSerialId),
|
|
1050
845
|
);
|
|
1051
846
|
} else {
|
|
1052
847
|
// Use regular DLC transactions when no DLC inputs
|
|
@@ -1280,19 +1075,12 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
1280
1075
|
|
|
1281
1076
|
const cetsHex = dlcTxs.cets.map((cet) => cet.serialize().toString('hex'));
|
|
1282
1077
|
|
|
1283
|
-
// Create the correct P2WSH multisig funding script
|
|
1284
|
-
const fundingPubKeys =
|
|
1285
|
-
Buffer.compare(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey) === -1
|
|
1286
|
-
? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
|
|
1287
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
1288
|
-
|
|
1289
|
-
const p2ms = payments.p2ms({
|
|
1290
|
-
m: 2,
|
|
1291
|
-
pubkeys: fundingPubKeys,
|
|
1292
|
-
network,
|
|
1293
|
-
});
|
|
1294
|
-
|
|
1295
1078
|
// We need the redeem script (multisig), not the P2WSH output
|
|
1079
|
+
const p2ms = createP2MSMultisig(
|
|
1080
|
+
dlcOffer.fundingPubkey,
|
|
1081
|
+
dlcAccept.fundingPubkey,
|
|
1082
|
+
network,
|
|
1083
|
+
);
|
|
1296
1084
|
const fundingSPK = p2ms.output!;
|
|
1297
1085
|
|
|
1298
1086
|
// For finding the private key, we still need the individual P2WPKH address
|
|
@@ -1499,16 +1287,12 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
1499
1287
|
|
|
1500
1288
|
// Create the correct P2WSH multisig funding script for verification
|
|
1501
1289
|
const network = await this.getConnectedNetwork();
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
const verifyP2ms = payments.p2ms({
|
|
1508
|
-
m: 2,
|
|
1509
|
-
pubkeys: verifyFundingPubKeys,
|
|
1290
|
+
|
|
1291
|
+
const verifyP2ms = createP2MSMultisig(
|
|
1292
|
+
dlcOffer.fundingPubkey,
|
|
1293
|
+
dlcAccept.fundingPubkey,
|
|
1510
1294
|
network,
|
|
1511
|
-
|
|
1295
|
+
);
|
|
1512
1296
|
|
|
1513
1297
|
// We need the redeem script (multisig), not the P2WSH output
|
|
1514
1298
|
const fundingSPK = verifyP2ms.output!;
|
|
@@ -1658,7 +1442,7 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
1658
1442
|
}
|
|
1659
1443
|
}
|
|
1660
1444
|
|
|
1661
|
-
private async
|
|
1445
|
+
private async CreateFundingSigs(
|
|
1662
1446
|
dlcOffer: DlcOffer,
|
|
1663
1447
|
dlcAccept: DlcAccept,
|
|
1664
1448
|
dlcTxs: DlcTransactions,
|
|
@@ -1670,14 +1454,10 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
1670
1454
|
const network = await this.getConnectedNetwork();
|
|
1671
1455
|
const psbt = new Psbt({ network });
|
|
1672
1456
|
|
|
1673
|
-
|
|
1674
|
-
const allFundingInputs = [
|
|
1457
|
+
const allFundingInputs = sortFundingInputsBySerialId([
|
|
1675
1458
|
...dlcOffer.fundingInputs,
|
|
1676
1459
|
...dlcAccept.fundingInputs,
|
|
1677
|
-
];
|
|
1678
|
-
|
|
1679
|
-
// Sort by inputSerialId to reconstruct proper transaction order
|
|
1680
|
-
allFundingInputs.sort((a, b) => Number(a.inputSerialId - b.inputSerialId));
|
|
1460
|
+
]);
|
|
1681
1461
|
|
|
1682
1462
|
// Add all inputs to PSBT with proper witnessUtxo
|
|
1683
1463
|
for (const fundingInput of allFundingInputs) {
|
|
@@ -1756,11 +1536,42 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
1756
1536
|
|
|
1757
1537
|
// Check if this is a DLC input (2-of-2 multisig from previous DLC)
|
|
1758
1538
|
if (fundingInput.dlcInput) {
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1539
|
+
const paymentVariant = createP2WSHMultisig(
|
|
1540
|
+
fundingInput.dlcInput.localFundPubkey,
|
|
1541
|
+
fundingInput.dlcInput.remoteFundPubkey,
|
|
1542
|
+
network,
|
|
1763
1543
|
);
|
|
1544
|
+
|
|
1545
|
+
const redeemScript = paymentVariant.redeem.output;
|
|
1546
|
+
|
|
1547
|
+
// Find the correct private key for this DLC input
|
|
1548
|
+
const dlcPrivKey = await this.findDlcFundingPrivateKey(
|
|
1549
|
+
fundingInput.dlcInput.localFundPubkey.toString('hex'),
|
|
1550
|
+
fundingInput.dlcInput.remoteFundPubkey.toString('hex'),
|
|
1551
|
+
);
|
|
1552
|
+
const keyPair = ECPair.fromPrivateKey(Buffer.from(dlcPrivKey, 'hex'));
|
|
1553
|
+
|
|
1554
|
+
// Update the input with the witnessScript before signing
|
|
1555
|
+
psbt.updateInput(inputIndex, {
|
|
1556
|
+
witnessScript: redeemScript,
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
psbt.signInput(inputIndex, keyPair);
|
|
1560
|
+
|
|
1561
|
+
const inputData = psbt.data.inputs[inputIndex];
|
|
1562
|
+
const partialSigs = inputData.partialSig;
|
|
1563
|
+
if (!partialSigs || partialSigs.length === 0) {
|
|
1564
|
+
throw new Error(
|
|
1565
|
+
`No signatures found for input ${inputIndex} after signing`,
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
const sigWitness = new ScriptWitnessV0();
|
|
1570
|
+
sigWitness.witness = ensureBuffer(partialSigs[0].signature);
|
|
1571
|
+
const pubKeyWitness = new ScriptWitnessV0();
|
|
1572
|
+
pubKeyWitness.witness = keyPair.publicKey;
|
|
1573
|
+
|
|
1574
|
+
witnessElements.push([sigWitness, pubKeyWitness]);
|
|
1764
1575
|
} else {
|
|
1765
1576
|
// For P2WPKH inputs, use PSBT signing
|
|
1766
1577
|
psbt.signInput(inputIndex, keyPair);
|
|
@@ -1776,7 +1587,7 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
1776
1587
|
|
|
1777
1588
|
// For P2WPKH, create witness manually: [signature, publicKey]
|
|
1778
1589
|
const sigWitness = new ScriptWitnessV0();
|
|
1779
|
-
sigWitness.witness =
|
|
1590
|
+
sigWitness.witness = ensureBuffer(partialSigs[0].signature);
|
|
1780
1591
|
const pubKeyWitness = new ScriptWitnessV0();
|
|
1781
1592
|
pubKeyWitness.witness = keyPair.publicKey;
|
|
1782
1593
|
|
|
@@ -1792,7 +1603,7 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
1792
1603
|
return fundingSignatures;
|
|
1793
1604
|
}
|
|
1794
1605
|
|
|
1795
|
-
private async
|
|
1606
|
+
private async VerifyFundingSigs(
|
|
1796
1607
|
dlcOffer: DlcOffer,
|
|
1797
1608
|
dlcAccept: DlcAccept,
|
|
1798
1609
|
dlcSign: DlcSign,
|
|
@@ -1811,12 +1622,10 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
1811
1622
|
? dlcAccept.fundingInputs
|
|
1812
1623
|
: dlcOffer.fundingInputs;
|
|
1813
1624
|
|
|
1814
|
-
|
|
1815
|
-
const allFundingInputs = [
|
|
1625
|
+
const allFundingInputs = sortFundingInputsBySerialId([
|
|
1816
1626
|
...dlcOffer.fundingInputs,
|
|
1817
1627
|
...dlcAccept.fundingInputs,
|
|
1818
|
-
];
|
|
1819
|
-
allFundingInputs.sort((a, b) => Number(a.inputSerialId - b.inputSerialId));
|
|
1628
|
+
]);
|
|
1820
1629
|
|
|
1821
1630
|
// Compare transaction IDs
|
|
1822
1631
|
const dlcFundTxId = dlcTxs.fundTx.txId.toString();
|
|
@@ -1830,10 +1639,21 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
1830
1639
|
// Add all inputs (needed for proper sighash calculation)
|
|
1831
1640
|
for (const input of allFundingInputs) {
|
|
1832
1641
|
const prevOutput = input.prevTx.outputs[input.prevTxVout];
|
|
1642
|
+
|
|
1643
|
+
// Use sequence from the original transaction to ensure consistency with CreateFundingTx
|
|
1644
|
+
const originalInput = transaction.ins.find(
|
|
1645
|
+
(txInput) =>
|
|
1646
|
+
txInput.hash.reverse().toString('hex') ===
|
|
1647
|
+
input.prevTx.txId.toString() && txInput.index === input.prevTxVout,
|
|
1648
|
+
);
|
|
1649
|
+
const sequenceValue = originalInput
|
|
1650
|
+
? originalInput.sequence
|
|
1651
|
+
: Number(input.sequence);
|
|
1652
|
+
|
|
1833
1653
|
psbt.addInput({
|
|
1834
1654
|
hash: input.prevTx.txId.toString(),
|
|
1835
1655
|
index: input.prevTxVout,
|
|
1836
|
-
sequence:
|
|
1656
|
+
sequence: sequenceValue,
|
|
1837
1657
|
witnessUtxo: {
|
|
1838
1658
|
script: Buffer.from(prevOutput.scriptPubKey.serialize().subarray(1)),
|
|
1839
1659
|
value: Number(prevOutput.value.sats),
|
|
@@ -1859,8 +1679,61 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
1859
1679
|
// Add the funding signatures to the PSBT as partial signatures
|
|
1860
1680
|
let witnessIndex = 0;
|
|
1861
1681
|
for (const fundingInput of signingPartyInputs) {
|
|
1862
|
-
//
|
|
1682
|
+
// Handle DLC inputs with multisig verification
|
|
1863
1683
|
if (fundingInput.dlcInput) {
|
|
1684
|
+
const dlcInput = fundingInput.dlcInput;
|
|
1685
|
+
|
|
1686
|
+
const paymentVariant = createP2WSHMultisig(
|
|
1687
|
+
dlcInput.localFundPubkey,
|
|
1688
|
+
dlcInput.remoteFundPubkey,
|
|
1689
|
+
network,
|
|
1690
|
+
);
|
|
1691
|
+
|
|
1692
|
+
// Find this input's index in the sorted transaction
|
|
1693
|
+
const inputIndex = allFundingInputs.findIndex(
|
|
1694
|
+
(input) =>
|
|
1695
|
+
input.prevTx.txId.toString() ===
|
|
1696
|
+
fundingInput.prevTx.txId.toString() &&
|
|
1697
|
+
input.prevTxVout === fundingInput.prevTxVout,
|
|
1698
|
+
);
|
|
1699
|
+
|
|
1700
|
+
if (inputIndex === -1) {
|
|
1701
|
+
throw new Error(
|
|
1702
|
+
`DLC input not found in transaction: ${fundingInput.prevTx.txId.toString()}:${fundingInput.prevTxVout}`,
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
// Update the input with the witnessScript for verification
|
|
1707
|
+
psbt.updateInput(inputIndex, {
|
|
1708
|
+
witnessScript: paymentVariant.redeem.output,
|
|
1709
|
+
});
|
|
1710
|
+
|
|
1711
|
+
// Add the DLC signature from witness elements if available
|
|
1712
|
+
if (witnessIndex < dlcSign.fundingSignatures.witnessElements.length) {
|
|
1713
|
+
const witnessElement =
|
|
1714
|
+
dlcSign.fundingSignatures.witnessElements[witnessIndex];
|
|
1715
|
+
const signature = witnessElement[0].witness;
|
|
1716
|
+
const publicKey = witnessElement[1].witness;
|
|
1717
|
+
|
|
1718
|
+
psbt.updateInput(inputIndex, {
|
|
1719
|
+
partialSig: [
|
|
1720
|
+
{
|
|
1721
|
+
pubkey: publicKey,
|
|
1722
|
+
signature: ensureDerSignature(signature),
|
|
1723
|
+
},
|
|
1724
|
+
],
|
|
1725
|
+
});
|
|
1726
|
+
|
|
1727
|
+
// Verify the signature for this DLC input
|
|
1728
|
+
psbt.validateSignaturesOfInput(
|
|
1729
|
+
inputIndex,
|
|
1730
|
+
(pubkey: Buffer, msghash: Buffer, signature: Buffer) => {
|
|
1731
|
+
return ecc.verify(msghash, pubkey, signature);
|
|
1732
|
+
},
|
|
1733
|
+
);
|
|
1734
|
+
|
|
1735
|
+
witnessIndex++;
|
|
1736
|
+
}
|
|
1864
1737
|
continue;
|
|
1865
1738
|
}
|
|
1866
1739
|
|
|
@@ -1893,7 +1766,7 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
1893
1766
|
partialSig: [
|
|
1894
1767
|
{
|
|
1895
1768
|
pubkey: publicKey,
|
|
1896
|
-
signature:
|
|
1769
|
+
signature: ensureDerSignature(signature),
|
|
1897
1770
|
},
|
|
1898
1771
|
],
|
|
1899
1772
|
});
|
|
@@ -1909,6 +1782,114 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
1909
1782
|
}
|
|
1910
1783
|
}
|
|
1911
1784
|
|
|
1785
|
+
/**
|
|
1786
|
+
* Get sighash for funding transaction inputs
|
|
1787
|
+
* @param dlcOffer DLC Offer
|
|
1788
|
+
* @param dlcAccept DLC Accept
|
|
1789
|
+
* @param dlcTxs DLC Transactions
|
|
1790
|
+
* @param inputIndex Index of the input to get sighash for (optional, if not provided returns all)
|
|
1791
|
+
* @returns Array of sighashes for each input or single sighash if inputIndex specified
|
|
1792
|
+
*/
|
|
1793
|
+
async getFundingTransactionSighash(
|
|
1794
|
+
dlcOffer: DlcOffer,
|
|
1795
|
+
dlcAccept: DlcAccept,
|
|
1796
|
+
dlcTxs: DlcTransactions,
|
|
1797
|
+
inputIndex?: number,
|
|
1798
|
+
): Promise<string[] | string> {
|
|
1799
|
+
// Use the detailed function to ensure consistency
|
|
1800
|
+
const details = await this.getFundingTransactionSighashDetails(
|
|
1801
|
+
dlcOffer,
|
|
1802
|
+
dlcAccept,
|
|
1803
|
+
dlcTxs,
|
|
1804
|
+
);
|
|
1805
|
+
|
|
1806
|
+
if (inputIndex !== undefined) {
|
|
1807
|
+
// Return sighash for specific input
|
|
1808
|
+
if (inputIndex >= details.length) {
|
|
1809
|
+
throw new Error(
|
|
1810
|
+
`Input index ${inputIndex} out of range. Total inputs: ${details.length}`,
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1813
|
+
return details[inputIndex].sighash;
|
|
1814
|
+
} else {
|
|
1815
|
+
// Return sighashes for all inputs
|
|
1816
|
+
return details.map((detail) => detail.sighash);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
/**
|
|
1821
|
+
* Get detailed sighash information for funding transaction (useful for debugging)
|
|
1822
|
+
* @param dlcOffer DLC Offer
|
|
1823
|
+
* @param dlcAccept DLC Accept
|
|
1824
|
+
* @param dlcTxs DLC Transactions
|
|
1825
|
+
* @returns Detailed sighash information for each input
|
|
1826
|
+
*/
|
|
1827
|
+
async getFundingTransactionSighashDetails(
|
|
1828
|
+
dlcOffer: DlcOffer,
|
|
1829
|
+
dlcAccept: DlcAccept,
|
|
1830
|
+
dlcTxs: DlcTransactions,
|
|
1831
|
+
): Promise<
|
|
1832
|
+
Array<{
|
|
1833
|
+
inputIndex: number;
|
|
1834
|
+
txid: string;
|
|
1835
|
+
vout: number;
|
|
1836
|
+
sequence: number;
|
|
1837
|
+
scriptPubKey: string;
|
|
1838
|
+
value: number;
|
|
1839
|
+
sighash: string;
|
|
1840
|
+
}>
|
|
1841
|
+
> {
|
|
1842
|
+
const transaction = btTransaction.fromBuffer(
|
|
1843
|
+
Buffer.from(dlcTxs.fundTx.serialize()),
|
|
1844
|
+
);
|
|
1845
|
+
|
|
1846
|
+
const allFundingInputs = sortFundingInputsBySerialId([
|
|
1847
|
+
...dlcOffer.fundingInputs,
|
|
1848
|
+
...dlcAccept.fundingInputs,
|
|
1849
|
+
]);
|
|
1850
|
+
|
|
1851
|
+
// Calculate detailed sighash information
|
|
1852
|
+
const details = [];
|
|
1853
|
+
|
|
1854
|
+
for (let i = 0; i < allFundingInputs.length; i++) {
|
|
1855
|
+
const input = allFundingInputs[i];
|
|
1856
|
+
const prevOutput = input.prevTx.outputs[input.prevTxVout];
|
|
1857
|
+
|
|
1858
|
+
const originalInput = transaction.ins.find(
|
|
1859
|
+
(txInput) =>
|
|
1860
|
+
txInput.hash.reverse().toString('hex') ===
|
|
1861
|
+
input.prevTx.txId.toString() && txInput.index === input.prevTxVout,
|
|
1862
|
+
);
|
|
1863
|
+
const sequenceValue = originalInput
|
|
1864
|
+
? originalInput.sequence
|
|
1865
|
+
: Number(input.sequence);
|
|
1866
|
+
|
|
1867
|
+
const script = Buffer.from(
|
|
1868
|
+
prevOutput.scriptPubKey.serialize().subarray(1),
|
|
1869
|
+
);
|
|
1870
|
+
const value = Number(prevOutput.value.sats);
|
|
1871
|
+
|
|
1872
|
+
const sighash = transaction.hashForWitnessV0(
|
|
1873
|
+
i,
|
|
1874
|
+
script,
|
|
1875
|
+
value,
|
|
1876
|
+
btTransaction.SIGHASH_ALL,
|
|
1877
|
+
);
|
|
1878
|
+
|
|
1879
|
+
details.push({
|
|
1880
|
+
inputIndex: i,
|
|
1881
|
+
txid: input.prevTx.txId.toString(),
|
|
1882
|
+
vout: input.prevTxVout,
|
|
1883
|
+
sequence: sequenceValue,
|
|
1884
|
+
scriptPubKey: script.toString('hex'),
|
|
1885
|
+
value: value,
|
|
1886
|
+
sighash: sighash.toString('hex'),
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
return details;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1912
1893
|
private async VerifyRefundSignatureAlt(
|
|
1913
1894
|
dlcOffer: DlcOffer,
|
|
1914
1895
|
dlcAccept: DlcAccept,
|
|
@@ -1926,7 +1907,7 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
1926
1907
|
: dlcSign.refundSignature;
|
|
1927
1908
|
|
|
1928
1909
|
// Ensure signature is in DER format (convert from compact if needed)
|
|
1929
|
-
const refundSignature =
|
|
1910
|
+
const refundSignature = ensureDerSignature(rawRefundSignature);
|
|
1930
1911
|
|
|
1931
1912
|
const signingPubkey = isOfferer
|
|
1932
1913
|
? dlcAccept.fundingPubkey
|
|
@@ -1942,22 +1923,11 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
1942
1923
|
// Create a PSBT for the refund transaction verification using same approach as createDlcClose
|
|
1943
1924
|
const psbt = new Psbt({ network });
|
|
1944
1925
|
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
|
|
1949
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
1950
|
-
|
|
1951
|
-
const p2ms = payments.p2ms({
|
|
1952
|
-
m: 2,
|
|
1953
|
-
pubkeys: fundingPubKeys,
|
|
1954
|
-
network,
|
|
1955
|
-
});
|
|
1956
|
-
|
|
1957
|
-
const paymentVariant = payments.p2wsh({
|
|
1958
|
-
redeem: p2ms,
|
|
1926
|
+
const paymentVariant = createP2WSHMultisig(
|
|
1927
|
+
dlcOffer.fundingPubkey,
|
|
1928
|
+
dlcAccept.fundingPubkey,
|
|
1959
1929
|
network,
|
|
1960
|
-
|
|
1930
|
+
);
|
|
1961
1931
|
|
|
1962
1932
|
// Add the funding input with sequence from refund transaction
|
|
1963
1933
|
psbt.addInput({
|
|
@@ -2011,23 +1981,11 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
2011
1981
|
): Buffer {
|
|
2012
1982
|
const network = this.getBitcoinJsNetwork();
|
|
2013
1983
|
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
|
|
2018
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
2019
|
-
|
|
2020
|
-
// Create 2-of-2 multisig script
|
|
2021
|
-
const p2ms = payments.p2ms({
|
|
2022
|
-
m: 2,
|
|
2023
|
-
pubkeys: fundingPubKeys,
|
|
1984
|
+
const paymentVariant = createP2WSHMultisig(
|
|
1985
|
+
dlcOffer.fundingPubkey,
|
|
1986
|
+
dlcAccept.fundingPubkey,
|
|
2024
1987
|
network,
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
const paymentVariant = payments.p2wsh({
|
|
2028
|
-
redeem: p2ms,
|
|
2029
|
-
network,
|
|
2030
|
-
});
|
|
1988
|
+
);
|
|
2031
1989
|
|
|
2032
1990
|
return paymentVariant.redeem!.output!;
|
|
2033
1991
|
}
|
|
@@ -2042,27 +2000,20 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
2042
2000
|
const network = await this.getConnectedNetwork();
|
|
2043
2001
|
const psbt = new Psbt({ network });
|
|
2044
2002
|
|
|
2045
|
-
|
|
2046
|
-
const allFundingInputs = [
|
|
2003
|
+
const allFundingInputs = sortFundingInputsBySerialId([
|
|
2047
2004
|
...dlcOffer.fundingInputs,
|
|
2048
2005
|
...dlcAccept.fundingInputs,
|
|
2049
|
-
];
|
|
2050
|
-
allFundingInputs.sort((a, b) => Number(a.inputSerialId - b.inputSerialId));
|
|
2006
|
+
]);
|
|
2051
2007
|
|
|
2052
2008
|
// Create a map of input txid:vout to witness elements
|
|
2053
2009
|
const witnessMap = new Map<string, ScriptWitnessV0[]>();
|
|
2054
2010
|
|
|
2055
|
-
// Map witness elements correctly -
|
|
2011
|
+
// Map witness elements correctly - CreateFundingSigs only creates witness elements
|
|
2056
2012
|
// for the party's own inputs, so we need to map them correctly
|
|
2057
2013
|
|
|
2058
|
-
// For dlcSign (offerer's signatures), map to offerer's inputs
|
|
2014
|
+
// For dlcSign (offerer's signatures), map to offerer's inputs (including DLC inputs)
|
|
2059
2015
|
let offererWitnessIndex = 0;
|
|
2060
2016
|
dlcOffer.fundingInputs.forEach((fundingInput) => {
|
|
2061
|
-
// Skip DLC inputs for now as in CreateFundingSigsAlt
|
|
2062
|
-
if (fundingInput.dlcInput) {
|
|
2063
|
-
return;
|
|
2064
|
-
}
|
|
2065
|
-
|
|
2066
2017
|
if (
|
|
2067
2018
|
offererWitnessIndex < dlcSign.fundingSignatures.witnessElements.length
|
|
2068
2019
|
) {
|
|
@@ -2075,14 +2026,9 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
2075
2026
|
}
|
|
2076
2027
|
});
|
|
2077
2028
|
|
|
2078
|
-
// For fundingSignatures (accepter's signatures), map to accepter's inputs
|
|
2029
|
+
// For fundingSignatures (accepter's signatures), map to accepter's inputs (including DLC inputs)
|
|
2079
2030
|
let accepterWitnessIndex = 0;
|
|
2080
2031
|
dlcAccept.fundingInputs.forEach((fundingInput) => {
|
|
2081
|
-
// Skip DLC inputs for now as in CreateFundingSigsAlt
|
|
2082
|
-
if (fundingInput.dlcInput) {
|
|
2083
|
-
return;
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
2032
|
if (accepterWitnessIndex < fundingSignatures.witnessElements.length) {
|
|
2087
2033
|
const key = `${fundingInput.prevTx.txId.toString()}:${fundingInput.prevTxVout}`;
|
|
2088
2034
|
witnessMap.set(
|
|
@@ -2101,13 +2047,13 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
2101
2047
|
for (const fundingInput of allFundingInputs) {
|
|
2102
2048
|
const prevOut = fundingInput.prevTx.outputs[fundingInput.prevTxVout];
|
|
2103
2049
|
|
|
2104
|
-
// Use same script handling as
|
|
2050
|
+
// Use same script handling as CreateFundingSigs
|
|
2105
2051
|
const witnessUtxo = {
|
|
2106
2052
|
script: Buffer.from(prevOut.scriptPubKey.serialize().subarray(1)),
|
|
2107
2053
|
value: Number(prevOut.value.sats),
|
|
2108
2054
|
};
|
|
2109
2055
|
|
|
2110
|
-
// Use sequence from the original transaction (same as
|
|
2056
|
+
// Use sequence from the original transaction (same as CreateFundingSigs)
|
|
2111
2057
|
const originalInput = originalTransaction.ins.find(
|
|
2112
2058
|
(input) =>
|
|
2113
2059
|
input.hash.reverse().toString('hex') ===
|
|
@@ -2145,13 +2091,76 @@ export default class BitcoinDdkProvider extends Provider {
|
|
|
2145
2091
|
|
|
2146
2092
|
const witnessElements = witnessMap.get(inputKey);
|
|
2147
2093
|
if (witnessElements && witnessElements.length === 2) {
|
|
2148
|
-
//
|
|
2094
|
+
// Handle DLC inputs with multisig finalization
|
|
2149
2095
|
if (fundingInput.dlcInput) {
|
|
2096
|
+
const dlcInput = fundingInput.dlcInput;
|
|
2097
|
+
|
|
2098
|
+
// Create the multisig script for finalization
|
|
2099
|
+
// Use lexicographic ordering to match DDK library behavior
|
|
2100
|
+
const orderedPubkeys = orderPubkeysLexicographically(
|
|
2101
|
+
dlcInput.localFundPubkey,
|
|
2102
|
+
dlcInput.remoteFundPubkey,
|
|
2103
|
+
);
|
|
2104
|
+
|
|
2105
|
+
const paymentVariant = createP2WSHMultisigFromOrdered(
|
|
2106
|
+
orderedPubkeys,
|
|
2107
|
+
network,
|
|
2108
|
+
);
|
|
2109
|
+
|
|
2110
|
+
// Update the input with the witnessScript
|
|
2111
|
+
psbt.updateInput(inputIndex, {
|
|
2112
|
+
witnessScript: paymentVariant.redeem.output,
|
|
2113
|
+
});
|
|
2114
|
+
|
|
2115
|
+
// Sign this DLC input ourselves
|
|
2116
|
+
const dlcPrivKey = await this.findDlcFundingPrivateKey(
|
|
2117
|
+
dlcInput.localFundPubkey.toString('hex'),
|
|
2118
|
+
dlcInput.remoteFundPubkey.toString('hex'),
|
|
2119
|
+
);
|
|
2120
|
+
const keyPair = ECPair.fromPrivateKey(Buffer.from(dlcPrivKey, 'hex'));
|
|
2121
|
+
|
|
2122
|
+
psbt.signInput(inputIndex, keyPair);
|
|
2123
|
+
|
|
2124
|
+
// Get our signature and determine which pubkey it corresponds to
|
|
2125
|
+
const ourPartialSig = psbt.data.inputs[inputIndex].partialSig[0];
|
|
2126
|
+
const ourSig = ensureDerSignature(ourPartialSig.signature);
|
|
2127
|
+
const ourPubkey = ourPartialSig.pubkey;
|
|
2128
|
+
|
|
2129
|
+
// Get the other party's signature from witnessElements
|
|
2130
|
+
const otherPartySig = ensureDerSignature(witnessElements[0].witness);
|
|
2131
|
+
|
|
2132
|
+
// Order signatures according to the lexicographic pubkey order used in the multisig script
|
|
2133
|
+
let sig1: Buffer, sig2: Buffer;
|
|
2134
|
+
|
|
2135
|
+
if (Buffer.compare(ourPubkey, orderedPubkeys[0]) === 0) {
|
|
2136
|
+
// Our signature corresponds to the first pubkey in lexicographic order
|
|
2137
|
+
sig1 = ourSig;
|
|
2138
|
+
sig2 = otherPartySig;
|
|
2139
|
+
} else {
|
|
2140
|
+
// Our signature corresponds to the second pubkey in lexicographic order
|
|
2141
|
+
sig1 = otherPartySig;
|
|
2142
|
+
sig2 = ourSig;
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
// Finalize with proper multisig witness: [OP_0, sig1, sig2, redeemScript]
|
|
2146
|
+
psbt.finalizeInput(inputIndex, () => ({
|
|
2147
|
+
finalScriptSig: Buffer.alloc(0),
|
|
2148
|
+
finalScriptWitness: Buffer.concat([
|
|
2149
|
+
Buffer.from([0x04]), // witness stack count
|
|
2150
|
+
Buffer.from([0x00]), // OP_0 (Bitcoin multisig bug)
|
|
2151
|
+
Buffer.from([sig1.length]),
|
|
2152
|
+
sig1,
|
|
2153
|
+
Buffer.from([sig2.length]),
|
|
2154
|
+
sig2,
|
|
2155
|
+
Buffer.from([paymentVariant.redeem.output.length]),
|
|
2156
|
+
paymentVariant.redeem.output,
|
|
2157
|
+
]),
|
|
2158
|
+
}));
|
|
2150
2159
|
continue;
|
|
2151
2160
|
}
|
|
2152
2161
|
|
|
2153
2162
|
// For P2WPKH inputs, finalize the input with witness data
|
|
2154
|
-
const signature =
|
|
2163
|
+
const signature = ensureDerSignature(witnessElements[0].witness);
|
|
2155
2164
|
const publicKey = witnessElements[1].witness;
|
|
2156
2165
|
|
|
2157
2166
|
// Try a simpler approach - let bitcoinjs-lib handle witness construction
|
|
@@ -2255,15 +2264,13 @@ Payout Group not found',
|
|
|
2255
2264
|
}
|
|
2256
2265
|
|
|
2257
2266
|
async FindOutcomeIndexFromHyperbolaPayoutCurvePiece(
|
|
2258
|
-
|
|
2267
|
+
dlcOffer: DlcOffer,
|
|
2259
2268
|
contractDescriptor: NumericalDescriptor,
|
|
2260
2269
|
contractOraclePairIndex: number,
|
|
2261
2270
|
hyperbolaPayoutCurvePiece: HyperbolaPayoutCurvePiece,
|
|
2262
2271
|
oracleAttestation: OracleAttestation,
|
|
2263
2272
|
outcome: bigint,
|
|
2264
2273
|
): Promise<FindOutcomeResponse> {
|
|
2265
|
-
const { dlcOffer } = checkTypes({ _dlcOffer });
|
|
2266
|
-
|
|
2267
2274
|
const hyperbolaCurve = HyperbolaPayoutCurve.fromPayoutCurvePiece(
|
|
2268
2275
|
hyperbolaPayoutCurvePiece,
|
|
2269
2276
|
);
|
|
@@ -2793,21 +2800,15 @@ Payout Group not found even with brute force search',
|
|
|
2793
2800
|
|
|
2794
2801
|
const network = await this.getConnectedNetwork();
|
|
2795
2802
|
|
|
2796
|
-
const fundingPubKeys =
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
const p2ms = payments.p2ms({
|
|
2802
|
-
m: 2,
|
|
2803
|
-
pubkeys: fundingPubKeys,
|
|
2804
|
-
network,
|
|
2805
|
-
});
|
|
2803
|
+
const fundingPubKeys = orderPubkeysLexicographically(
|
|
2804
|
+
dlcOffer.fundingPubkey,
|
|
2805
|
+
dlcAccept.fundingPubkey,
|
|
2806
|
+
);
|
|
2806
2807
|
|
|
2807
|
-
const paymentVariant =
|
|
2808
|
-
|
|
2808
|
+
const paymentVariant = createP2WSHMultisigFromOrdered(
|
|
2809
|
+
fundingPubKeys,
|
|
2809
2810
|
network,
|
|
2810
|
-
|
|
2811
|
+
);
|
|
2811
2812
|
|
|
2812
2813
|
const sigHashRequestPromises: Promise<string>[] = [];
|
|
2813
2814
|
const sigHashes = [];
|
|
@@ -2904,21 +2905,15 @@ Payout Group not found even with brute force search',
|
|
|
2904
2905
|
|
|
2905
2906
|
const network = await this.getConnectedNetwork();
|
|
2906
2907
|
|
|
2907
|
-
const fundingPubKeys =
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
const p2ms = payments.p2ms({
|
|
2913
|
-
m: 2,
|
|
2914
|
-
pubkeys: fundingPubKeys,
|
|
2915
|
-
network,
|
|
2916
|
-
});
|
|
2908
|
+
const fundingPubKeys = orderPubkeysLexicographically(
|
|
2909
|
+
dlcOffer.fundingPubkey,
|
|
2910
|
+
dlcAccept.fundingPubkey,
|
|
2911
|
+
);
|
|
2917
2912
|
|
|
2918
|
-
const paymentVariant =
|
|
2919
|
-
|
|
2913
|
+
const paymentVariant = createP2WSHMultisigFromOrdered(
|
|
2914
|
+
fundingPubKeys,
|
|
2920
2915
|
network,
|
|
2921
|
-
|
|
2916
|
+
);
|
|
2922
2917
|
|
|
2923
2918
|
const pubkey = isOfferer ? dlcAccept.fundingPubkey : dlcOffer.fundingPubkey;
|
|
2924
2919
|
|
|
@@ -3087,13 +3082,7 @@ Payout Group not found even with brute force search',
|
|
|
3087
3082
|
),
|
|
3088
3083
|
);
|
|
3089
3084
|
|
|
3090
|
-
const fundingInputs
|
|
3091
|
-
(input) => input as FundingInput,
|
|
3092
|
-
);
|
|
3093
|
-
|
|
3094
|
-
fundingInputs.sort(
|
|
3095
|
-
(a, b) => Number(a.inputSerialId) - Number(b.inputSerialId),
|
|
3096
|
-
);
|
|
3085
|
+
const fundingInputs = sortFundingInputsBySerialId(_fundingInputs);
|
|
3097
3086
|
|
|
3098
3087
|
const fundOutputSerialId = generateSerialId();
|
|
3099
3088
|
|
|
@@ -3283,10 +3272,8 @@ Payout Group not found even with brute force search',
|
|
|
3283
3272
|
),
|
|
3284
3273
|
);
|
|
3285
3274
|
|
|
3286
|
-
fundingInputs =
|
|
3287
|
-
|
|
3288
|
-
fundingInputs.sort(
|
|
3289
|
-
(a, b) => Number(a.inputSerialId) - Number(b.inputSerialId),
|
|
3275
|
+
fundingInputs = sortFundingInputsBySerialId(
|
|
3276
|
+
_fundingInputs.map((input) => input as FundingInput),
|
|
3290
3277
|
);
|
|
3291
3278
|
}
|
|
3292
3279
|
|
|
@@ -3371,7 +3358,7 @@ Payout Group not found even with brute force search',
|
|
|
3371
3358
|
|
|
3372
3359
|
const _dlcTransactions = dlcTransactions;
|
|
3373
3360
|
|
|
3374
|
-
const contractId =
|
|
3361
|
+
const contractId = computeContractId(
|
|
3375
3362
|
_dlcTransactions.fundTx.txId.serialize(),
|
|
3376
3363
|
_dlcTransactions.fundTxVout,
|
|
3377
3364
|
dlcOffer.temporaryContractId,
|
|
@@ -3427,7 +3414,7 @@ Payout Group not found even with brute force search',
|
|
|
3427
3414
|
true,
|
|
3428
3415
|
);
|
|
3429
3416
|
|
|
3430
|
-
const fundingSignatures = await this.
|
|
3417
|
+
const fundingSignatures = await this.CreateFundingSigs(
|
|
3431
3418
|
dlcOffer,
|
|
3432
3419
|
dlcAccept,
|
|
3433
3420
|
dlcTransactions,
|
|
@@ -3436,24 +3423,12 @@ Payout Group not found even with brute force search',
|
|
|
3436
3423
|
|
|
3437
3424
|
const dlcTxs = dlcTransactions;
|
|
3438
3425
|
|
|
3439
|
-
const contractId =
|
|
3426
|
+
const contractId = computeContractId(
|
|
3440
3427
|
dlcTxs.fundTx.txId.serialize(),
|
|
3441
3428
|
dlcTxs.fundTxVout,
|
|
3442
3429
|
dlcOffer.temporaryContractId,
|
|
3443
3430
|
);
|
|
3444
3431
|
|
|
3445
|
-
assert(
|
|
3446
|
-
Buffer.compare(
|
|
3447
|
-
contractId,
|
|
3448
|
-
this.computeContractId(
|
|
3449
|
-
dlcTxs.fundTx.txId.serialize(),
|
|
3450
|
-
dlcTxs.fundTxVout,
|
|
3451
|
-
dlcOffer.temporaryContractId,
|
|
3452
|
-
),
|
|
3453
|
-
) === 0,
|
|
3454
|
-
'contractId must be the xor of funding txid, fundingOutputIndex and the tempContractId',
|
|
3455
|
-
);
|
|
3456
|
-
|
|
3457
3432
|
dlcTxs.contractId = contractId;
|
|
3458
3433
|
|
|
3459
3434
|
dlcSign.contractId = contractId;
|
|
@@ -3480,6 +3455,17 @@ Payout Group not found even with brute force search',
|
|
|
3480
3455
|
): Promise<Tx> {
|
|
3481
3456
|
let messagesList: Messages[] = [];
|
|
3482
3457
|
|
|
3458
|
+
const contractId = computeContractId(
|
|
3459
|
+
dlcTxs.fundTx.txId.serialize(),
|
|
3460
|
+
dlcTxs.fundTxVout,
|
|
3461
|
+
dlcOffer.temporaryContractId,
|
|
3462
|
+
);
|
|
3463
|
+
|
|
3464
|
+
assert(
|
|
3465
|
+
Buffer.compare(contractId, dlcSign.contractId) === 0,
|
|
3466
|
+
`Finalize Dlc Sign Contract ID mismatch: ${contractId.toString('hex')} !== ${dlcSign.contractId.toString('hex')}`,
|
|
3467
|
+
);
|
|
3468
|
+
|
|
3483
3469
|
if (
|
|
3484
3470
|
dlcOffer.contractInfo.type === MessageType.SingleContractInfo &&
|
|
3485
3471
|
(dlcOffer.contractInfo as SingleContractInfo).contractDescriptor.type ===
|
|
@@ -3507,15 +3493,9 @@ Payout Group not found even with brute force search',
|
|
|
3507
3493
|
false,
|
|
3508
3494
|
);
|
|
3509
3495
|
|
|
3510
|
-
await this.
|
|
3511
|
-
dlcOffer,
|
|
3512
|
-
dlcAccept,
|
|
3513
|
-
dlcSign,
|
|
3514
|
-
dlcTxs,
|
|
3515
|
-
false,
|
|
3516
|
-
);
|
|
3496
|
+
await this.VerifyFundingSigs(dlcOffer, dlcAccept, dlcSign, dlcTxs, false);
|
|
3517
3497
|
|
|
3518
|
-
const fundingSignatures = await this.
|
|
3498
|
+
const fundingSignatures = await this.CreateFundingSigs(
|
|
3519
3499
|
dlcOffer,
|
|
3520
3500
|
dlcAccept,
|
|
3521
3501
|
dlcTxs,
|
|
@@ -3590,22 +3570,15 @@ Payout Group not found even with brute force search',
|
|
|
3590
3570
|
);
|
|
3591
3571
|
}
|
|
3592
3572
|
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
3598
|
-
|
|
3599
|
-
const p2ms = payments.p2ms({
|
|
3600
|
-
m: 2,
|
|
3601
|
-
pubkeys: fundingPubKeys,
|
|
3602
|
-
network,
|
|
3603
|
-
});
|
|
3573
|
+
const fundingPubKeys = orderPubkeysLexicographically(
|
|
3574
|
+
dlcOffer.fundingPubkey,
|
|
3575
|
+
dlcAccept.fundingPubkey,
|
|
3576
|
+
);
|
|
3604
3577
|
|
|
3605
|
-
const paymentVariant =
|
|
3606
|
-
|
|
3578
|
+
const paymentVariant = createP2WSHMultisigFromOrdered(
|
|
3579
|
+
fundingPubKeys,
|
|
3607
3580
|
network,
|
|
3608
|
-
|
|
3581
|
+
);
|
|
3609
3582
|
|
|
3610
3583
|
// Add the funding input with sequence from refund transaction
|
|
3611
3584
|
psbt.addInput({
|
|
@@ -3643,13 +3616,13 @@ Payout Group not found even with brute force search',
|
|
|
3643
3616
|
// This is the offerer's pubkey, use dlcSign.refundSignature
|
|
3644
3617
|
partialSigs.push({
|
|
3645
3618
|
pubkey: pubkey,
|
|
3646
|
-
signature:
|
|
3619
|
+
signature: ensureDerSignature(dlcSign.refundSignature),
|
|
3647
3620
|
});
|
|
3648
3621
|
} else if (Buffer.compare(pubkey, dlcAccept.fundingPubkey) === 0) {
|
|
3649
3622
|
// This is the accepter's pubkey, use dlcAccept.refundSignature
|
|
3650
3623
|
partialSigs.push({
|
|
3651
3624
|
pubkey: pubkey,
|
|
3652
|
-
signature:
|
|
3625
|
+
signature: ensureDerSignature(dlcAccept.refundSignature),
|
|
3653
3626
|
});
|
|
3654
3627
|
}
|
|
3655
3628
|
}
|
|
@@ -3722,21 +3695,11 @@ Payout Group not found even with brute force search',
|
|
|
3722
3695
|
const network = await this.getConnectedNetwork();
|
|
3723
3696
|
const psbt = new Psbt({ network });
|
|
3724
3697
|
|
|
3725
|
-
const
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
3729
|
-
|
|
3730
|
-
const p2ms = payments.p2ms({
|
|
3731
|
-
m: 2,
|
|
3732
|
-
pubkeys: fundingPubKeys,
|
|
3698
|
+
const paymentVariant = createP2WSHMultisig(
|
|
3699
|
+
dlcOffer.fundingPubkey,
|
|
3700
|
+
dlcAccept.fundingPubkey,
|
|
3733
3701
|
network,
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
const paymentVariant = payments.p2wsh({
|
|
3737
|
-
redeem: p2ms,
|
|
3738
|
-
network,
|
|
3739
|
-
});
|
|
3702
|
+
);
|
|
3740
3703
|
|
|
3741
3704
|
// Initiate and build PSBT
|
|
3742
3705
|
let inputs: Input[] = _inputs;
|
|
@@ -3914,9 +3877,7 @@ Payout Group not found even with brute force search',
|
|
|
3914
3877
|
|
|
3915
3878
|
// Extract close signature from psbt and decode it to only extract r and s values
|
|
3916
3879
|
const closeSignature = await script.signature.decode(
|
|
3917
|
-
|
|
3918
|
-
psbt.data.inputs[fundingInputIndex].partialSig[0].signature,
|
|
3919
|
-
),
|
|
3880
|
+
ensureBuffer(psbt.data.inputs[fundingInputIndex].partialSig[0].signature),
|
|
3920
3881
|
).signature;
|
|
3921
3882
|
|
|
3922
3883
|
// Extract funding signatures from psbt
|
|
@@ -3928,9 +3889,9 @@ Payout Group not found even with brute force search',
|
|
|
3928
3889
|
const witnessElements: ScriptWitnessV0[][] = [];
|
|
3929
3890
|
for (let i = 0; i < inputSigs.length; i++) {
|
|
3930
3891
|
const sigWitness = new ScriptWitnessV0();
|
|
3931
|
-
sigWitness.witness =
|
|
3892
|
+
sigWitness.witness = ensureBuffer(inputSigs[i].signature);
|
|
3932
3893
|
const pubKeyWitness = new ScriptWitnessV0();
|
|
3933
|
-
pubKeyWitness.witness =
|
|
3894
|
+
pubKeyWitness.witness = ensureBuffer(inputSigs[i].pubkey);
|
|
3934
3895
|
witnessElements.push([sigWitness, pubKeyWitness]);
|
|
3935
3896
|
}
|
|
3936
3897
|
const fundingSignatures = new FundingSignatures();
|
|
@@ -4176,21 +4137,11 @@ Payout Group not found even with brute force search',
|
|
|
4176
4137
|
const network = await this.getConnectedNetwork();
|
|
4177
4138
|
const psbt = new Psbt({ network });
|
|
4178
4139
|
|
|
4179
|
-
const
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
4183
|
-
|
|
4184
|
-
const p2ms = payments.p2ms({
|
|
4185
|
-
m: 2,
|
|
4186
|
-
pubkeys: fundingPubKeys,
|
|
4187
|
-
network,
|
|
4188
|
-
});
|
|
4189
|
-
|
|
4190
|
-
const paymentVariant = payments.p2wsh({
|
|
4191
|
-
redeem: p2ms,
|
|
4140
|
+
const paymentVariant = createP2WSHMultisig(
|
|
4141
|
+
dlcOffer.fundingPubkey,
|
|
4142
|
+
dlcAccept.fundingPubkey,
|
|
4192
4143
|
network,
|
|
4193
|
-
|
|
4144
|
+
);
|
|
4194
4145
|
|
|
4195
4146
|
// Make temporary array to hold all inputs and then sort them
|
|
4196
4147
|
// this method can be improved later
|
|
@@ -4524,16 +4475,10 @@ Payout Group not found even with brute force search',
|
|
|
4524
4475
|
const network = await this.getConnectedNetwork();
|
|
4525
4476
|
|
|
4526
4477
|
// Create 2-of-2 multisig payment using deterministic ordering
|
|
4527
|
-
const
|
|
4528
|
-
|
|
4529
|
-
pubkeys: orderedPubkeys,
|
|
4478
|
+
const paymentVariant = createP2WSHMultisigFromOrdered(
|
|
4479
|
+
orderedPubkeys,
|
|
4530
4480
|
network,
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
const paymentVariant = payments.p2wsh({
|
|
4534
|
-
redeem: p2ms,
|
|
4535
|
-
network,
|
|
4536
|
-
});
|
|
4481
|
+
);
|
|
4537
4482
|
|
|
4538
4483
|
const multisigAddress = paymentVariant.address!;
|
|
4539
4484
|
|