@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
|
@@ -65,169 +65,6 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
65
65
|
await (0, utils_1.sleep)(10);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Helper function to ensure we have a Buffer object
|
|
70
|
-
* Handles cases where Buffer objects have been serialized/deserialized
|
|
71
|
-
*/
|
|
72
|
-
ensureBuffer(bufferLike) {
|
|
73
|
-
if (Buffer.isBuffer(bufferLike)) {
|
|
74
|
-
return bufferLike;
|
|
75
|
-
}
|
|
76
|
-
if (bufferLike && bufferLike.type === 'Buffer' && bufferLike.data) {
|
|
77
|
-
return Buffer.from(bufferLike.data);
|
|
78
|
-
}
|
|
79
|
-
return bufferLike;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Detect if signature is in compact format (64 bytes) or DER format
|
|
83
|
-
* and convert compact to DER if needed, adding SIGHASH_ALL flag
|
|
84
|
-
*/
|
|
85
|
-
ensureDerSignature(signature) {
|
|
86
|
-
// If signature is 64 bytes, it's likely compact format (32-byte r + 32-byte s)
|
|
87
|
-
if (signature.length === 64) {
|
|
88
|
-
// Convert compact signature to DER format
|
|
89
|
-
const r = signature.slice(0, 32);
|
|
90
|
-
const s = signature.slice(32, 64);
|
|
91
|
-
// Create DER encoding manually
|
|
92
|
-
// DER format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S]
|
|
93
|
-
// Remove leading zeros from r and s, but keep at least one byte
|
|
94
|
-
let rBytes = r;
|
|
95
|
-
while (rBytes.length > 1 &&
|
|
96
|
-
rBytes[0] === 0x00 &&
|
|
97
|
-
(rBytes[1] & 0x80) === 0) {
|
|
98
|
-
rBytes = rBytes.slice(1);
|
|
99
|
-
}
|
|
100
|
-
let sBytes = s;
|
|
101
|
-
while (sBytes.length > 1 &&
|
|
102
|
-
sBytes[0] === 0x00 &&
|
|
103
|
-
(sBytes[1] & 0x80) === 0) {
|
|
104
|
-
sBytes = sBytes.slice(1);
|
|
105
|
-
}
|
|
106
|
-
// Add padding byte if high bit is set (to keep numbers positive)
|
|
107
|
-
if ((rBytes[0] & 0x80) !== 0) {
|
|
108
|
-
rBytes = Buffer.concat([Buffer.from([0x00]), rBytes]);
|
|
109
|
-
}
|
|
110
|
-
if ((sBytes[0] & 0x80) !== 0) {
|
|
111
|
-
sBytes = Buffer.concat([Buffer.from([0x00]), sBytes]);
|
|
112
|
-
}
|
|
113
|
-
const totalLength = 2 + rBytes.length + 2 + sBytes.length;
|
|
114
|
-
const derSignature = Buffer.concat([
|
|
115
|
-
Buffer.from([0x30, totalLength]), // SEQUENCE tag and total length
|
|
116
|
-
Buffer.from([0x02, rBytes.length]), // INTEGER tag and R length
|
|
117
|
-
rBytes,
|
|
118
|
-
Buffer.from([0x02, sBytes.length]), // INTEGER tag and S length
|
|
119
|
-
sBytes,
|
|
120
|
-
Buffer.from([0x01]), // SIGHASH_ALL flag
|
|
121
|
-
]);
|
|
122
|
-
return derSignature;
|
|
123
|
-
}
|
|
124
|
-
// If it's already DER format, check if it has SIGHASH flag
|
|
125
|
-
if (signature.length > 0 && signature[0] === 0x30) {
|
|
126
|
-
// Check if it already has a SIGHASH flag (last byte should be 0x01 for SIGHASH_ALL)
|
|
127
|
-
if (signature[signature.length - 1] !== 0x01) {
|
|
128
|
-
// Add SIGHASH_ALL flag
|
|
129
|
-
return Buffer.concat([signature, Buffer.from([0x01])]);
|
|
130
|
-
}
|
|
131
|
-
return signature;
|
|
132
|
-
}
|
|
133
|
-
// For other formats, return as-is
|
|
134
|
-
return signature;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Detect if signature is in DER format and convert to compact format (64 bytes)
|
|
138
|
-
* by extracting r and s values, removing SIGHASH flag if present
|
|
139
|
-
*/
|
|
140
|
-
ensureCompactSignature(signature) {
|
|
141
|
-
// If signature is already 64 bytes, it's likely already compact format
|
|
142
|
-
if (signature.length === 64) {
|
|
143
|
-
return signature;
|
|
144
|
-
}
|
|
145
|
-
// Check if it's DER format (starts with 0x30)
|
|
146
|
-
if (signature.length > 6 && signature[0] === 0x30) {
|
|
147
|
-
let derSig = signature;
|
|
148
|
-
// Remove SIGHASH flag if present (last byte is typically 0x01 for SIGHASH_ALL)
|
|
149
|
-
if (signature[signature.length - 1] === 0x01) {
|
|
150
|
-
derSig = signature.slice(0, -1);
|
|
151
|
-
}
|
|
152
|
-
// Parse DER format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S]
|
|
153
|
-
if (derSig[0] !== 0x30) {
|
|
154
|
-
throw new Error('Invalid DER signature: missing SEQUENCE tag');
|
|
155
|
-
}
|
|
156
|
-
const totalLength = derSig[1];
|
|
157
|
-
if (derSig.length < totalLength + 2) {
|
|
158
|
-
throw new Error('Invalid DER signature: length mismatch');
|
|
159
|
-
}
|
|
160
|
-
let offset = 2;
|
|
161
|
-
// Parse R value
|
|
162
|
-
if (derSig[offset] !== 0x02) {
|
|
163
|
-
throw new Error('Invalid DER signature: missing INTEGER tag for R');
|
|
164
|
-
}
|
|
165
|
-
offset++;
|
|
166
|
-
const rLength = derSig[offset];
|
|
167
|
-
offset++;
|
|
168
|
-
if (offset + rLength > derSig.length) {
|
|
169
|
-
throw new Error('Invalid DER signature: R length exceeds signature length');
|
|
170
|
-
}
|
|
171
|
-
let rBytes = derSig.slice(offset, offset + rLength);
|
|
172
|
-
offset += rLength;
|
|
173
|
-
// Parse S value
|
|
174
|
-
if (derSig[offset] !== 0x02) {
|
|
175
|
-
throw new Error('Invalid DER signature: missing INTEGER tag for S');
|
|
176
|
-
}
|
|
177
|
-
offset++;
|
|
178
|
-
const sLength = derSig[offset];
|
|
179
|
-
offset++;
|
|
180
|
-
if (offset + sLength > derSig.length) {
|
|
181
|
-
throw new Error('Invalid DER signature: S length exceeds signature length');
|
|
182
|
-
}
|
|
183
|
-
let sBytes = derSig.slice(offset, offset + sLength);
|
|
184
|
-
// Remove leading zero padding from r and s (DER may pad to prevent negative interpretation)
|
|
185
|
-
while (rBytes.length > 1 && rBytes[0] === 0x00) {
|
|
186
|
-
rBytes = rBytes.slice(1);
|
|
187
|
-
}
|
|
188
|
-
while (sBytes.length > 1 && sBytes[0] === 0x00) {
|
|
189
|
-
sBytes = sBytes.slice(1);
|
|
190
|
-
}
|
|
191
|
-
// Pad to 32 bytes each (compact format requires exactly 32 bytes for r and s)
|
|
192
|
-
while (rBytes.length < 32) {
|
|
193
|
-
rBytes = Buffer.concat([Buffer.from([0x00]), rBytes]);
|
|
194
|
-
}
|
|
195
|
-
while (sBytes.length < 32) {
|
|
196
|
-
sBytes = Buffer.concat([Buffer.from([0x00]), sBytes]);
|
|
197
|
-
}
|
|
198
|
-
if (rBytes.length !== 32 || sBytes.length !== 32) {
|
|
199
|
-
throw new Error('Invalid signature values: r or s exceeds 32 bytes');
|
|
200
|
-
}
|
|
201
|
-
// Combine r and s into 64-byte compact format
|
|
202
|
-
return Buffer.concat([rBytes, sBytes]);
|
|
203
|
-
}
|
|
204
|
-
// For other formats, throw error as we can't convert
|
|
205
|
-
throw new Error('Unable to convert signature to compact format: unknown format');
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Compute contract ID from fund transaction ID, output index, and temporary contract ID
|
|
209
|
-
* Matches the Rust implementation in rust-dlc
|
|
210
|
-
*/
|
|
211
|
-
computeContractId(fundTxId, fundOutputIndex, temporaryContractId) {
|
|
212
|
-
if (fundTxId.length !== 32) {
|
|
213
|
-
throw new Error('Fund transaction ID must be 32 bytes');
|
|
214
|
-
}
|
|
215
|
-
if (temporaryContractId.length !== 32) {
|
|
216
|
-
throw new Error('Temporary contract ID must be 32 bytes');
|
|
217
|
-
}
|
|
218
|
-
if (fundOutputIndex > 0xffff) {
|
|
219
|
-
throw new Error('Fund output index must fit in 16 bits');
|
|
220
|
-
}
|
|
221
|
-
const result = Buffer.alloc(32);
|
|
222
|
-
// XOR fund_tx_id with temporary_id, with byte order reversal for fund_tx_id
|
|
223
|
-
for (let i = 0; i < 32; i++) {
|
|
224
|
-
result[i] = fundTxId[31 - i] ^ temporaryContractId[i];
|
|
225
|
-
}
|
|
226
|
-
// XOR the fund output index into the last two bytes
|
|
227
|
-
result[30] ^= (fundOutputIndex >> 8) & 0xff; // High byte
|
|
228
|
-
result[31] ^= fundOutputIndex & 0xff; // Low byte
|
|
229
|
-
return result;
|
|
230
|
-
}
|
|
231
68
|
/**
|
|
232
69
|
* Create refund signature using PSBT method instead of DDK
|
|
233
70
|
*/
|
|
@@ -238,19 +75,7 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
238
75
|
if (Number(dlcTxs.refundTx.locktime) !== dlcOffer.refundLocktime) {
|
|
239
76
|
throw new Error(`Refund transaction locktime ${dlcTxs.refundTx.locktime} does not match expected ${dlcOffer.refundLocktime}`);
|
|
240
77
|
}
|
|
241
|
-
|
|
242
|
-
const fundingPubKeys = Buffer.compare(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey) === -1
|
|
243
|
-
? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
|
|
244
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
245
|
-
const p2ms = bitcoinjs_lib_1.payments.p2ms({
|
|
246
|
-
m: 2,
|
|
247
|
-
pubkeys: fundingPubKeys,
|
|
248
|
-
network,
|
|
249
|
-
});
|
|
250
|
-
const paymentVariant = bitcoinjs_lib_1.payments.p2wsh({
|
|
251
|
-
redeem: p2ms,
|
|
252
|
-
network,
|
|
253
|
-
});
|
|
78
|
+
const paymentVariant = (0, Utils_1.createP2WSHMultisig)(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey, network);
|
|
254
79
|
// Add the funding input with sequence from refund transaction
|
|
255
80
|
psbt.addInput({
|
|
256
81
|
hash: dlcTxs.fundTx.txId.serialize(),
|
|
@@ -283,7 +108,7 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
283
108
|
const partialSig = psbt.data.inputs[0].partialSig[0];
|
|
284
109
|
const derSignature = partialSig.signature;
|
|
285
110
|
// Convert DER signature to compact format
|
|
286
|
-
return
|
|
111
|
+
return (0, Utils_1.ensureCompactSignature)(derSignature);
|
|
287
112
|
}
|
|
288
113
|
/**
|
|
289
114
|
* Find private key for DLC funding pubkey by deriving wallet addresses
|
|
@@ -632,8 +457,10 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
632
457
|
const input = await this.fundingInputToInput(fundingInput, false);
|
|
633
458
|
return input.toUtxo();
|
|
634
459
|
}));
|
|
635
|
-
// Calculate input amounts
|
|
636
|
-
const
|
|
460
|
+
// Calculate input amounts (including both regular inputs and DLC inputs)
|
|
461
|
+
const localRegularInputAmount = localRegularInputs.reduce((prev, cur) => prev + cur.amount.GetSatoshiAmount(), 0);
|
|
462
|
+
const localDlcInputAmount = localDlcInputs.reduce((prev, cur) => prev + Number(cur.fundAmount), 0);
|
|
463
|
+
const localInputAmount = localRegularInputAmount + localDlcInputAmount;
|
|
637
464
|
const remoteInputAmount = remoteInputs.reduce((prev, cur) => prev + cur.amount.GetSatoshiAmount(), 0);
|
|
638
465
|
let payouts = [];
|
|
639
466
|
let messagesList = [];
|
|
@@ -688,7 +515,7 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
688
515
|
let dlcTxs;
|
|
689
516
|
if (hasDlcInputs) {
|
|
690
517
|
// Use spliced DLC transactions when DLC inputs are present
|
|
691
|
-
dlcTxs = await this._ddk.createSplicedDlcTransactions(outcomes, localParams, remoteParams, dlcOffer.refundLocktime, BigInt(dlcOffer.feeRatePerVb), 0, dlcOffer.cetLocktime, dlcOffer.fundOutputSerialId);
|
|
518
|
+
dlcTxs = await this._ddk.createSplicedDlcTransactions(outcomes, localParams, remoteParams, dlcOffer.refundLocktime, BigInt(dlcOffer.feeRatePerVb), 0, dlcOffer.cetLocktime, BigInt(dlcOffer.fundOutputSerialId));
|
|
692
519
|
}
|
|
693
520
|
else {
|
|
694
521
|
// Use regular DLC transactions when no DLC inputs
|
|
@@ -860,16 +687,8 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
860
687
|
async CreateCetAdaptorAndRefundSigs(dlcOffer, dlcAccept, dlcTxs, messagesList, isOfferer) {
|
|
861
688
|
const network = await this.getConnectedNetwork();
|
|
862
689
|
const cetsHex = dlcTxs.cets.map((cet) => cet.serialize().toString('hex'));
|
|
863
|
-
// Create the correct P2WSH multisig funding script
|
|
864
|
-
const fundingPubKeys = Buffer.compare(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey) === -1
|
|
865
|
-
? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
|
|
866
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
867
|
-
const p2ms = bitcoinjs_lib_1.payments.p2ms({
|
|
868
|
-
m: 2,
|
|
869
|
-
pubkeys: fundingPubKeys,
|
|
870
|
-
network,
|
|
871
|
-
});
|
|
872
690
|
// We need the redeem script (multisig), not the P2WSH output
|
|
691
|
+
const p2ms = (0, Utils_1.createP2MSMultisig)(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey, network);
|
|
873
692
|
const fundingSPK = p2ms.output;
|
|
874
693
|
// For finding the private key, we still need the individual P2WPKH address
|
|
875
694
|
const individualFundingSPK = bitcoin_1.Script.p2wpkhLock((0, crypto_1.hash160)(isOfferer ? dlcOffer.fundingPubkey : dlcAccept.fundingPubkey))
|
|
@@ -980,14 +799,7 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
980
799
|
});
|
|
981
800
|
// Create the correct P2WSH multisig funding script for verification
|
|
982
801
|
const network = await this.getConnectedNetwork();
|
|
983
|
-
const
|
|
984
|
-
? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
|
|
985
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
986
|
-
const verifyP2ms = bitcoinjs_lib_1.payments.p2ms({
|
|
987
|
-
m: 2,
|
|
988
|
-
pubkeys: verifyFundingPubKeys,
|
|
989
|
-
network,
|
|
990
|
-
});
|
|
802
|
+
const verifyP2ms = (0, Utils_1.createP2MSMultisig)(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey, network);
|
|
991
803
|
// We need the redeem script (multisig), not the P2WSH output
|
|
992
804
|
const fundingSPK = verifyP2ms.output;
|
|
993
805
|
const ddkOracleInfo = {
|
|
@@ -1070,17 +882,14 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
1070
882
|
}
|
|
1071
883
|
}
|
|
1072
884
|
}
|
|
1073
|
-
async
|
|
885
|
+
async CreateFundingSigs(dlcOffer, dlcAccept, dlcTxs, isOfferer) {
|
|
1074
886
|
const transaction = bitcoinjs_lib_1.Transaction.fromBuffer(Buffer.from(dlcTxs.fundTx.serialize()));
|
|
1075
887
|
const network = await this.getConnectedNetwork();
|
|
1076
888
|
const psbt = new bitcoinjs_lib_1.Psbt({ network });
|
|
1077
|
-
|
|
1078
|
-
const allFundingInputs = [
|
|
889
|
+
const allFundingInputs = (0, Utils_1.sortFundingInputsBySerialId)([
|
|
1079
890
|
...dlcOffer.fundingInputs,
|
|
1080
891
|
...dlcAccept.fundingInputs,
|
|
1081
|
-
];
|
|
1082
|
-
// Sort by inputSerialId to reconstruct proper transaction order
|
|
1083
|
-
allFundingInputs.sort((a, b) => Number(a.inputSerialId - b.inputSerialId));
|
|
892
|
+
]);
|
|
1084
893
|
// Add all inputs to PSBT with proper witnessUtxo
|
|
1085
894
|
for (const fundingInput of allFundingInputs) {
|
|
1086
895
|
const prevOut = fundingInput.prevTx.outputs[fundingInput.prevTxVout];
|
|
@@ -1137,9 +946,26 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
1137
946
|
const keyPair = ECPair.fromPrivateKey(Buffer.from(privKey, 'hex'));
|
|
1138
947
|
// Check if this is a DLC input (2-of-2 multisig from previous DLC)
|
|
1139
948
|
if (fundingInput.dlcInput) {
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
949
|
+
const paymentVariant = (0, Utils_1.createP2WSHMultisig)(fundingInput.dlcInput.localFundPubkey, fundingInput.dlcInput.remoteFundPubkey, network);
|
|
950
|
+
const redeemScript = paymentVariant.redeem.output;
|
|
951
|
+
// Find the correct private key for this DLC input
|
|
952
|
+
const dlcPrivKey = await this.findDlcFundingPrivateKey(fundingInput.dlcInput.localFundPubkey.toString('hex'), fundingInput.dlcInput.remoteFundPubkey.toString('hex'));
|
|
953
|
+
const keyPair = ECPair.fromPrivateKey(Buffer.from(dlcPrivKey, 'hex'));
|
|
954
|
+
// Update the input with the witnessScript before signing
|
|
955
|
+
psbt.updateInput(inputIndex, {
|
|
956
|
+
witnessScript: redeemScript,
|
|
957
|
+
});
|
|
958
|
+
psbt.signInput(inputIndex, keyPair);
|
|
959
|
+
const inputData = psbt.data.inputs[inputIndex];
|
|
960
|
+
const partialSigs = inputData.partialSig;
|
|
961
|
+
if (!partialSigs || partialSigs.length === 0) {
|
|
962
|
+
throw new Error(`No signatures found for input ${inputIndex} after signing`);
|
|
963
|
+
}
|
|
964
|
+
const sigWitness = new messaging_1.ScriptWitnessV0();
|
|
965
|
+
sigWitness.witness = (0, Utils_1.ensureBuffer)(partialSigs[0].signature);
|
|
966
|
+
const pubKeyWitness = new messaging_1.ScriptWitnessV0();
|
|
967
|
+
pubKeyWitness.witness = keyPair.publicKey;
|
|
968
|
+
witnessElements.push([sigWitness, pubKeyWitness]);
|
|
1143
969
|
}
|
|
1144
970
|
else {
|
|
1145
971
|
// For P2WPKH inputs, use PSBT signing
|
|
@@ -1152,7 +978,7 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
1152
978
|
}
|
|
1153
979
|
// For P2WPKH, create witness manually: [signature, publicKey]
|
|
1154
980
|
const sigWitness = new messaging_1.ScriptWitnessV0();
|
|
1155
|
-
sigWitness.witness =
|
|
981
|
+
sigWitness.witness = (0, Utils_1.ensureBuffer)(partialSigs[0].signature);
|
|
1156
982
|
const pubKeyWitness = new messaging_1.ScriptWitnessV0();
|
|
1157
983
|
pubKeyWitness.witness = keyPair.publicKey;
|
|
1158
984
|
witnessElements.push([sigWitness, pubKeyWitness]);
|
|
@@ -1164,7 +990,7 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
1164
990
|
fundingSignatures.witnessElements = witnessElements;
|
|
1165
991
|
return fundingSignatures;
|
|
1166
992
|
}
|
|
1167
|
-
async
|
|
993
|
+
async VerifyFundingSigs(dlcOffer, dlcAccept, dlcSign, dlcTxs, isOfferer) {
|
|
1168
994
|
const network = await this.getConnectedNetwork();
|
|
1169
995
|
const transaction = bitcoinjs_lib_1.Transaction.fromBuffer(Buffer.from(dlcTxs.fundTx.serialize()));
|
|
1170
996
|
// Get the party whose signatures we're verifying
|
|
@@ -1173,12 +999,10 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
1173
999
|
const signingPartyInputs = isOfferer
|
|
1174
1000
|
? dlcAccept.fundingInputs
|
|
1175
1001
|
: dlcOffer.fundingInputs;
|
|
1176
|
-
|
|
1177
|
-
const allFundingInputs = [
|
|
1002
|
+
const allFundingInputs = (0, Utils_1.sortFundingInputsBySerialId)([
|
|
1178
1003
|
...dlcOffer.fundingInputs,
|
|
1179
1004
|
...dlcAccept.fundingInputs,
|
|
1180
|
-
];
|
|
1181
|
-
allFundingInputs.sort((a, b) => Number(a.inputSerialId - b.inputSerialId));
|
|
1005
|
+
]);
|
|
1182
1006
|
// Compare transaction IDs
|
|
1183
1007
|
const dlcFundTxId = dlcTxs.fundTx.txId.toString();
|
|
1184
1008
|
const psbtBuilderTxId = transaction.getId();
|
|
@@ -1188,10 +1012,16 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
1188
1012
|
// Add all inputs (needed for proper sighash calculation)
|
|
1189
1013
|
for (const input of allFundingInputs) {
|
|
1190
1014
|
const prevOutput = input.prevTx.outputs[input.prevTxVout];
|
|
1015
|
+
// Use sequence from the original transaction to ensure consistency with CreateFundingTx
|
|
1016
|
+
const originalInput = transaction.ins.find((txInput) => txInput.hash.reverse().toString('hex') ===
|
|
1017
|
+
input.prevTx.txId.toString() && txInput.index === input.prevTxVout);
|
|
1018
|
+
const sequenceValue = originalInput
|
|
1019
|
+
? originalInput.sequence
|
|
1020
|
+
: Number(input.sequence);
|
|
1191
1021
|
psbt.addInput({
|
|
1192
1022
|
hash: input.prevTx.txId.toString(),
|
|
1193
1023
|
index: input.prevTxVout,
|
|
1194
|
-
sequence:
|
|
1024
|
+
sequence: sequenceValue,
|
|
1195
1025
|
witnessUtxo: {
|
|
1196
1026
|
script: Buffer.from(prevOutput.scriptPubKey.serialize().subarray(1)),
|
|
1197
1027
|
value: Number(prevOutput.value.sats),
|
|
@@ -1212,8 +1042,40 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
1212
1042
|
// Add the funding signatures to the PSBT as partial signatures
|
|
1213
1043
|
let witnessIndex = 0;
|
|
1214
1044
|
for (const fundingInput of signingPartyInputs) {
|
|
1215
|
-
//
|
|
1045
|
+
// Handle DLC inputs with multisig verification
|
|
1216
1046
|
if (fundingInput.dlcInput) {
|
|
1047
|
+
const dlcInput = fundingInput.dlcInput;
|
|
1048
|
+
const paymentVariant = (0, Utils_1.createP2WSHMultisig)(dlcInput.localFundPubkey, dlcInput.remoteFundPubkey, network);
|
|
1049
|
+
// Find this input's index in the sorted transaction
|
|
1050
|
+
const inputIndex = allFundingInputs.findIndex((input) => input.prevTx.txId.toString() ===
|
|
1051
|
+
fundingInput.prevTx.txId.toString() &&
|
|
1052
|
+
input.prevTxVout === fundingInput.prevTxVout);
|
|
1053
|
+
if (inputIndex === -1) {
|
|
1054
|
+
throw new Error(`DLC input not found in transaction: ${fundingInput.prevTx.txId.toString()}:${fundingInput.prevTxVout}`);
|
|
1055
|
+
}
|
|
1056
|
+
// Update the input with the witnessScript for verification
|
|
1057
|
+
psbt.updateInput(inputIndex, {
|
|
1058
|
+
witnessScript: paymentVariant.redeem.output,
|
|
1059
|
+
});
|
|
1060
|
+
// Add the DLC signature from witness elements if available
|
|
1061
|
+
if (witnessIndex < dlcSign.fundingSignatures.witnessElements.length) {
|
|
1062
|
+
const witnessElement = dlcSign.fundingSignatures.witnessElements[witnessIndex];
|
|
1063
|
+
const signature = witnessElement[0].witness;
|
|
1064
|
+
const publicKey = witnessElement[1].witness;
|
|
1065
|
+
psbt.updateInput(inputIndex, {
|
|
1066
|
+
partialSig: [
|
|
1067
|
+
{
|
|
1068
|
+
pubkey: publicKey,
|
|
1069
|
+
signature: (0, Utils_1.ensureDerSignature)(signature),
|
|
1070
|
+
},
|
|
1071
|
+
],
|
|
1072
|
+
});
|
|
1073
|
+
// Verify the signature for this DLC input
|
|
1074
|
+
psbt.validateSignaturesOfInput(inputIndex, (pubkey, msghash, signature) => {
|
|
1075
|
+
return ecc.verify(msghash, pubkey, signature);
|
|
1076
|
+
});
|
|
1077
|
+
witnessIndex++;
|
|
1078
|
+
}
|
|
1217
1079
|
continue;
|
|
1218
1080
|
}
|
|
1219
1081
|
if (witnessIndex >= dlcSign.fundingSignatures.witnessElements.length) {
|
|
@@ -1233,7 +1095,7 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
1233
1095
|
partialSig: [
|
|
1234
1096
|
{
|
|
1235
1097
|
pubkey: publicKey,
|
|
1236
|
-
signature:
|
|
1098
|
+
signature: (0, Utils_1.ensureDerSignature)(signature),
|
|
1237
1099
|
},
|
|
1238
1100
|
],
|
|
1239
1101
|
});
|
|
@@ -1243,6 +1105,67 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
1243
1105
|
witnessIndex++;
|
|
1244
1106
|
}
|
|
1245
1107
|
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Get sighash for funding transaction inputs
|
|
1110
|
+
* @param dlcOffer DLC Offer
|
|
1111
|
+
* @param dlcAccept DLC Accept
|
|
1112
|
+
* @param dlcTxs DLC Transactions
|
|
1113
|
+
* @param inputIndex Index of the input to get sighash for (optional, if not provided returns all)
|
|
1114
|
+
* @returns Array of sighashes for each input or single sighash if inputIndex specified
|
|
1115
|
+
*/
|
|
1116
|
+
async getFundingTransactionSighash(dlcOffer, dlcAccept, dlcTxs, inputIndex) {
|
|
1117
|
+
// Use the detailed function to ensure consistency
|
|
1118
|
+
const details = await this.getFundingTransactionSighashDetails(dlcOffer, dlcAccept, dlcTxs);
|
|
1119
|
+
if (inputIndex !== undefined) {
|
|
1120
|
+
// Return sighash for specific input
|
|
1121
|
+
if (inputIndex >= details.length) {
|
|
1122
|
+
throw new Error(`Input index ${inputIndex} out of range. Total inputs: ${details.length}`);
|
|
1123
|
+
}
|
|
1124
|
+
return details[inputIndex].sighash;
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
// Return sighashes for all inputs
|
|
1128
|
+
return details.map((detail) => detail.sighash);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Get detailed sighash information for funding transaction (useful for debugging)
|
|
1133
|
+
* @param dlcOffer DLC Offer
|
|
1134
|
+
* @param dlcAccept DLC Accept
|
|
1135
|
+
* @param dlcTxs DLC Transactions
|
|
1136
|
+
* @returns Detailed sighash information for each input
|
|
1137
|
+
*/
|
|
1138
|
+
async getFundingTransactionSighashDetails(dlcOffer, dlcAccept, dlcTxs) {
|
|
1139
|
+
const transaction = bitcoinjs_lib_1.Transaction.fromBuffer(Buffer.from(dlcTxs.fundTx.serialize()));
|
|
1140
|
+
const allFundingInputs = (0, Utils_1.sortFundingInputsBySerialId)([
|
|
1141
|
+
...dlcOffer.fundingInputs,
|
|
1142
|
+
...dlcAccept.fundingInputs,
|
|
1143
|
+
]);
|
|
1144
|
+
// Calculate detailed sighash information
|
|
1145
|
+
const details = [];
|
|
1146
|
+
for (let i = 0; i < allFundingInputs.length; i++) {
|
|
1147
|
+
const input = allFundingInputs[i];
|
|
1148
|
+
const prevOutput = input.prevTx.outputs[input.prevTxVout];
|
|
1149
|
+
const originalInput = transaction.ins.find((txInput) => txInput.hash.reverse().toString('hex') ===
|
|
1150
|
+
input.prevTx.txId.toString() && txInput.index === input.prevTxVout);
|
|
1151
|
+
const sequenceValue = originalInput
|
|
1152
|
+
? originalInput.sequence
|
|
1153
|
+
: Number(input.sequence);
|
|
1154
|
+
const script = Buffer.from(prevOutput.scriptPubKey.serialize().subarray(1));
|
|
1155
|
+
const value = Number(prevOutput.value.sats);
|
|
1156
|
+
const sighash = transaction.hashForWitnessV0(i, script, value, bitcoinjs_lib_1.Transaction.SIGHASH_ALL);
|
|
1157
|
+
details.push({
|
|
1158
|
+
inputIndex: i,
|
|
1159
|
+
txid: input.prevTx.txId.toString(),
|
|
1160
|
+
vout: input.prevTxVout,
|
|
1161
|
+
sequence: sequenceValue,
|
|
1162
|
+
scriptPubKey: script.toString('hex'),
|
|
1163
|
+
value: value,
|
|
1164
|
+
sighash: sighash.toString('hex'),
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
return details;
|
|
1168
|
+
}
|
|
1246
1169
|
async VerifyRefundSignatureAlt(dlcOffer, dlcAccept, dlcSign, dlcTxs, isOfferer) {
|
|
1247
1170
|
const network = await this.getConnectedNetwork();
|
|
1248
1171
|
// Get the refund signature we need to verify
|
|
@@ -1252,7 +1175,7 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
1252
1175
|
? dlcAccept.refundSignature
|
|
1253
1176
|
: dlcSign.refundSignature;
|
|
1254
1177
|
// Ensure signature is in DER format (convert from compact if needed)
|
|
1255
|
-
const refundSignature =
|
|
1178
|
+
const refundSignature = (0, Utils_1.ensureDerSignature)(rawRefundSignature);
|
|
1256
1179
|
const signingPubkey = isOfferer
|
|
1257
1180
|
? dlcAccept.fundingPubkey
|
|
1258
1181
|
: dlcOffer.fundingPubkey;
|
|
@@ -1262,19 +1185,7 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
1262
1185
|
}
|
|
1263
1186
|
// Create a PSBT for the refund transaction verification using same approach as createDlcClose
|
|
1264
1187
|
const psbt = new bitcoinjs_lib_1.Psbt({ network });
|
|
1265
|
-
|
|
1266
|
-
const fundingPubKeys = Buffer.compare(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey) === -1
|
|
1267
|
-
? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
|
|
1268
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
1269
|
-
const p2ms = bitcoinjs_lib_1.payments.p2ms({
|
|
1270
|
-
m: 2,
|
|
1271
|
-
pubkeys: fundingPubKeys,
|
|
1272
|
-
network,
|
|
1273
|
-
});
|
|
1274
|
-
const paymentVariant = bitcoinjs_lib_1.payments.p2wsh({
|
|
1275
|
-
redeem: p2ms,
|
|
1276
|
-
network,
|
|
1277
|
-
});
|
|
1188
|
+
const paymentVariant = (0, Utils_1.createP2WSHMultisig)(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey, network);
|
|
1278
1189
|
// Add the funding input with sequence from refund transaction
|
|
1279
1190
|
psbt.addInput({
|
|
1280
1191
|
hash: dlcTxs.fundTx.txId.serialize(),
|
|
@@ -1311,55 +1222,32 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
1311
1222
|
}
|
|
1312
1223
|
CreateFundingScript(dlcOffer, dlcAccept) {
|
|
1313
1224
|
const network = this.getBitcoinJsNetwork();
|
|
1314
|
-
|
|
1315
|
-
const fundingPubKeys = Buffer.compare(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey) === -1
|
|
1316
|
-
? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
|
|
1317
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
1318
|
-
// Create 2-of-2 multisig script
|
|
1319
|
-
const p2ms = bitcoinjs_lib_1.payments.p2ms({
|
|
1320
|
-
m: 2,
|
|
1321
|
-
pubkeys: fundingPubKeys,
|
|
1322
|
-
network,
|
|
1323
|
-
});
|
|
1324
|
-
const paymentVariant = bitcoinjs_lib_1.payments.p2wsh({
|
|
1325
|
-
redeem: p2ms,
|
|
1326
|
-
network,
|
|
1327
|
-
});
|
|
1225
|
+
const paymentVariant = (0, Utils_1.createP2WSHMultisig)(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey, network);
|
|
1328
1226
|
return paymentVariant.redeem.output;
|
|
1329
1227
|
}
|
|
1330
1228
|
async CreateFundingTx(dlcOffer, dlcAccept, dlcSign, dlcTxs, fundingSignatures) {
|
|
1331
1229
|
const network = await this.getConnectedNetwork();
|
|
1332
1230
|
const psbt = new bitcoinjs_lib_1.Psbt({ network });
|
|
1333
|
-
|
|
1334
|
-
const allFundingInputs = [
|
|
1231
|
+
const allFundingInputs = (0, Utils_1.sortFundingInputsBySerialId)([
|
|
1335
1232
|
...dlcOffer.fundingInputs,
|
|
1336
1233
|
...dlcAccept.fundingInputs,
|
|
1337
|
-
];
|
|
1338
|
-
allFundingInputs.sort((a, b) => Number(a.inputSerialId - b.inputSerialId));
|
|
1234
|
+
]);
|
|
1339
1235
|
// Create a map of input txid:vout to witness elements
|
|
1340
1236
|
const witnessMap = new Map();
|
|
1341
|
-
// Map witness elements correctly -
|
|
1237
|
+
// Map witness elements correctly - CreateFundingSigs only creates witness elements
|
|
1342
1238
|
// for the party's own inputs, so we need to map them correctly
|
|
1343
|
-
// For dlcSign (offerer's signatures), map to offerer's inputs
|
|
1239
|
+
// For dlcSign (offerer's signatures), map to offerer's inputs (including DLC inputs)
|
|
1344
1240
|
let offererWitnessIndex = 0;
|
|
1345
1241
|
dlcOffer.fundingInputs.forEach((fundingInput) => {
|
|
1346
|
-
// Skip DLC inputs for now as in CreateFundingSigsAlt
|
|
1347
|
-
if (fundingInput.dlcInput) {
|
|
1348
|
-
return;
|
|
1349
|
-
}
|
|
1350
1242
|
if (offererWitnessIndex < dlcSign.fundingSignatures.witnessElements.length) {
|
|
1351
1243
|
const key = `${fundingInput.prevTx.txId.toString()}:${fundingInput.prevTxVout}`;
|
|
1352
1244
|
witnessMap.set(key, dlcSign.fundingSignatures.witnessElements[offererWitnessIndex]);
|
|
1353
1245
|
offererWitnessIndex++;
|
|
1354
1246
|
}
|
|
1355
1247
|
});
|
|
1356
|
-
// For fundingSignatures (accepter's signatures), map to accepter's inputs
|
|
1248
|
+
// For fundingSignatures (accepter's signatures), map to accepter's inputs (including DLC inputs)
|
|
1357
1249
|
let accepterWitnessIndex = 0;
|
|
1358
1250
|
dlcAccept.fundingInputs.forEach((fundingInput) => {
|
|
1359
|
-
// Skip DLC inputs for now as in CreateFundingSigsAlt
|
|
1360
|
-
if (fundingInput.dlcInput) {
|
|
1361
|
-
return;
|
|
1362
|
-
}
|
|
1363
1251
|
if (accepterWitnessIndex < fundingSignatures.witnessElements.length) {
|
|
1364
1252
|
const key = `${fundingInput.prevTx.txId.toString()}:${fundingInput.prevTxVout}`;
|
|
1365
1253
|
witnessMap.set(key, fundingSignatures.witnessElements[accepterWitnessIndex]);
|
|
@@ -1370,12 +1258,12 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
1370
1258
|
const originalTransaction = bitcoinjs_lib_1.Transaction.fromBuffer(Buffer.from(dlcTxs.fundTx.serialize()));
|
|
1371
1259
|
for (const fundingInput of allFundingInputs) {
|
|
1372
1260
|
const prevOut = fundingInput.prevTx.outputs[fundingInput.prevTxVout];
|
|
1373
|
-
// Use same script handling as
|
|
1261
|
+
// Use same script handling as CreateFundingSigs
|
|
1374
1262
|
const witnessUtxo = {
|
|
1375
1263
|
script: Buffer.from(prevOut.scriptPubKey.serialize().subarray(1)),
|
|
1376
1264
|
value: Number(prevOut.value.sats),
|
|
1377
1265
|
};
|
|
1378
|
-
// Use sequence from the original transaction (same as
|
|
1266
|
+
// Use sequence from the original transaction (same as CreateFundingSigs)
|
|
1379
1267
|
const originalInput = originalTransaction.ins.find((input) => input.hash.reverse().toString('hex') ===
|
|
1380
1268
|
fundingInput.prevTx.txId.toString() &&
|
|
1381
1269
|
input.index === fundingInput.prevTxVout);
|
|
@@ -1402,12 +1290,57 @@ class BitcoinDdkProvider extends provider_1.default {
|
|
|
1402
1290
|
const inputKey = `${fundingInput.prevTx.txId.toString()}:${fundingInput.prevTxVout}`;
|
|
1403
1291
|
const witnessElements = witnessMap.get(inputKey);
|
|
1404
1292
|
if (witnessElements && witnessElements.length === 2) {
|
|
1405
|
-
//
|
|
1293
|
+
// Handle DLC inputs with multisig finalization
|
|
1406
1294
|
if (fundingInput.dlcInput) {
|
|
1295
|
+
const dlcInput = fundingInput.dlcInput;
|
|
1296
|
+
// Create the multisig script for finalization
|
|
1297
|
+
// Use lexicographic ordering to match DDK library behavior
|
|
1298
|
+
const orderedPubkeys = (0, Utils_1.orderPubkeysLexicographically)(dlcInput.localFundPubkey, dlcInput.remoteFundPubkey);
|
|
1299
|
+
const paymentVariant = (0, Utils_1.createP2WSHMultisigFromOrdered)(orderedPubkeys, network);
|
|
1300
|
+
// Update the input with the witnessScript
|
|
1301
|
+
psbt.updateInput(inputIndex, {
|
|
1302
|
+
witnessScript: paymentVariant.redeem.output,
|
|
1303
|
+
});
|
|
1304
|
+
// Sign this DLC input ourselves
|
|
1305
|
+
const dlcPrivKey = await this.findDlcFundingPrivateKey(dlcInput.localFundPubkey.toString('hex'), dlcInput.remoteFundPubkey.toString('hex'));
|
|
1306
|
+
const keyPair = ECPair.fromPrivateKey(Buffer.from(dlcPrivKey, 'hex'));
|
|
1307
|
+
psbt.signInput(inputIndex, keyPair);
|
|
1308
|
+
// Get our signature and determine which pubkey it corresponds to
|
|
1309
|
+
const ourPartialSig = psbt.data.inputs[inputIndex].partialSig[0];
|
|
1310
|
+
const ourSig = (0, Utils_1.ensureDerSignature)(ourPartialSig.signature);
|
|
1311
|
+
const ourPubkey = ourPartialSig.pubkey;
|
|
1312
|
+
// Get the other party's signature from witnessElements
|
|
1313
|
+
const otherPartySig = (0, Utils_1.ensureDerSignature)(witnessElements[0].witness);
|
|
1314
|
+
// Order signatures according to the lexicographic pubkey order used in the multisig script
|
|
1315
|
+
let sig1, sig2;
|
|
1316
|
+
if (Buffer.compare(ourPubkey, orderedPubkeys[0]) === 0) {
|
|
1317
|
+
// Our signature corresponds to the first pubkey in lexicographic order
|
|
1318
|
+
sig1 = ourSig;
|
|
1319
|
+
sig2 = otherPartySig;
|
|
1320
|
+
}
|
|
1321
|
+
else {
|
|
1322
|
+
// Our signature corresponds to the second pubkey in lexicographic order
|
|
1323
|
+
sig1 = otherPartySig;
|
|
1324
|
+
sig2 = ourSig;
|
|
1325
|
+
}
|
|
1326
|
+
// Finalize with proper multisig witness: [OP_0, sig1, sig2, redeemScript]
|
|
1327
|
+
psbt.finalizeInput(inputIndex, () => ({
|
|
1328
|
+
finalScriptSig: Buffer.alloc(0),
|
|
1329
|
+
finalScriptWitness: Buffer.concat([
|
|
1330
|
+
Buffer.from([0x04]), // witness stack count
|
|
1331
|
+
Buffer.from([0x00]), // OP_0 (Bitcoin multisig bug)
|
|
1332
|
+
Buffer.from([sig1.length]),
|
|
1333
|
+
sig1,
|
|
1334
|
+
Buffer.from([sig2.length]),
|
|
1335
|
+
sig2,
|
|
1336
|
+
Buffer.from([paymentVariant.redeem.output.length]),
|
|
1337
|
+
paymentVariant.redeem.output,
|
|
1338
|
+
]),
|
|
1339
|
+
}));
|
|
1407
1340
|
continue;
|
|
1408
1341
|
}
|
|
1409
1342
|
// For P2WPKH inputs, finalize the input with witness data
|
|
1410
|
-
const signature =
|
|
1343
|
+
const signature = (0, Utils_1.ensureDerSignature)(witnessElements[0].witness);
|
|
1411
1344
|
const publicKey = witnessElements[1].witness;
|
|
1412
1345
|
// Try a simpler approach - let bitcoinjs-lib handle witness construction
|
|
1413
1346
|
psbt.finalizeInput(inputIndex, () => ({
|
|
@@ -1468,8 +1401,7 @@ Payout Group found but incorrect group index');
|
|
|
1468
1401
|
Payout Group not found');
|
|
1469
1402
|
return { index: payoutIndexOffset + index, groupLength };
|
|
1470
1403
|
}
|
|
1471
|
-
async FindOutcomeIndexFromHyperbolaPayoutCurvePiece(
|
|
1472
|
-
const { dlcOffer } = (0, Utils_1.checkTypes)({ _dlcOffer });
|
|
1404
|
+
async FindOutcomeIndexFromHyperbolaPayoutCurvePiece(dlcOffer, contractDescriptor, contractOraclePairIndex, hyperbolaPayoutCurvePiece, oracleAttestation, outcome) {
|
|
1473
1405
|
const hyperbolaCurve = core_2.HyperbolaPayoutCurve.fromPayoutCurvePiece(hyperbolaPayoutCurvePiece);
|
|
1474
1406
|
const clampBN = (val) => bignumber_js_1.default.max(0, bignumber_js_1.default.min(val, dlcOffer.contractInfo.totalCollateral.toString()));
|
|
1475
1407
|
const payout = clampBN(hyperbolaCurve.getPayout(outcome));
|
|
@@ -1767,18 +1699,8 @@ Payout Group not found even with brute force search');
|
|
|
1767
1699
|
_dlcTxs,
|
|
1768
1700
|
});
|
|
1769
1701
|
const network = await this.getConnectedNetwork();
|
|
1770
|
-
const fundingPubKeys =
|
|
1771
|
-
|
|
1772
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
1773
|
-
const p2ms = bitcoinjs_lib_1.payments.p2ms({
|
|
1774
|
-
m: 2,
|
|
1775
|
-
pubkeys: fundingPubKeys,
|
|
1776
|
-
network,
|
|
1777
|
-
});
|
|
1778
|
-
const paymentVariant = bitcoinjs_lib_1.payments.p2wsh({
|
|
1779
|
-
redeem: p2ms,
|
|
1780
|
-
network,
|
|
1781
|
-
});
|
|
1702
|
+
const fundingPubKeys = (0, Utils_1.orderPubkeysLexicographically)(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey);
|
|
1703
|
+
const paymentVariant = (0, Utils_1.createP2WSHMultisigFromOrdered)(fundingPubKeys, network);
|
|
1782
1704
|
const sigHashRequestPromises = [];
|
|
1783
1705
|
const sigHashes = [];
|
|
1784
1706
|
for (let i = 0; i < rawCloseTxs.length; i++) {
|
|
@@ -1837,18 +1759,8 @@ Payout Group not found even with brute force search');
|
|
|
1837
1759
|
});
|
|
1838
1760
|
const dlcCloses = _dlcCloses.map((_dlcClose) => (0, Utils_1.checkTypes)({ _dlcClose }).dlcClose);
|
|
1839
1761
|
const network = await this.getConnectedNetwork();
|
|
1840
|
-
const fundingPubKeys =
|
|
1841
|
-
|
|
1842
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
1843
|
-
const p2ms = bitcoinjs_lib_1.payments.p2ms({
|
|
1844
|
-
m: 2,
|
|
1845
|
-
pubkeys: fundingPubKeys,
|
|
1846
|
-
network,
|
|
1847
|
-
});
|
|
1848
|
-
const paymentVariant = bitcoinjs_lib_1.payments.p2wsh({
|
|
1849
|
-
redeem: p2ms,
|
|
1850
|
-
network,
|
|
1851
|
-
});
|
|
1762
|
+
const fundingPubKeys = (0, Utils_1.orderPubkeysLexicographically)(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey);
|
|
1763
|
+
const paymentVariant = (0, Utils_1.createP2WSHMultisigFromOrdered)(fundingPubKeys, network);
|
|
1852
1764
|
const pubkey = isOfferer ? dlcAccept.fundingPubkey : dlcOffer.fundingPubkey;
|
|
1853
1765
|
const sigsValidity = [];
|
|
1854
1766
|
for (let i = 0; i < rawCloseTxs.length; i++) {
|
|
@@ -1959,8 +1871,7 @@ Payout Group not found even with brute force search');
|
|
|
1959
1871
|
changeSerialId = initResult.changeSerialId;
|
|
1960
1872
|
}
|
|
1961
1873
|
_fundingInputs.forEach((input) => (0, assert_1.default)(input.type === messaging_1.MessageType.FundingInput, 'FundingInput must be V0'));
|
|
1962
|
-
const fundingInputs =
|
|
1963
|
-
fundingInputs.sort((a, b) => Number(a.inputSerialId) - Number(b.inputSerialId));
|
|
1874
|
+
const fundingInputs = (0, Utils_1.sortFundingInputsBySerialId)(_fundingInputs);
|
|
1964
1875
|
const fundOutputSerialId = (0, Utils_1.generateSerialId)();
|
|
1965
1876
|
(0, assert_1.default)(changeSerialId !== fundOutputSerialId, 'changeSerialId cannot equal the fundOutputSerialId');
|
|
1966
1877
|
dlcOffer.contractFlags = Buffer.from('00', 'hex');
|
|
@@ -2074,8 +1985,7 @@ Payout Group not found even with brute force search');
|
|
|
2074
1985
|
changeSerialId = initResult.changeSerialId;
|
|
2075
1986
|
const _fundingInputs = initResult.fundingInputs;
|
|
2076
1987
|
_fundingInputs.forEach((input) => (0, assert_1.default)(input.type === messaging_1.MessageType.FundingInput, 'FundingInput must be V0'));
|
|
2077
|
-
fundingInputs = _fundingInputs.map((input) => input);
|
|
2078
|
-
fundingInputs.sort((a, b) => Number(a.inputSerialId) - Number(b.inputSerialId));
|
|
1988
|
+
fundingInputs = (0, Utils_1.sortFundingInputsBySerialId)(_fundingInputs.map((input) => input));
|
|
2079
1989
|
}
|
|
2080
1990
|
(0, assert_1.default)(Buffer.compare(dlcOffer.fundingPubkey, fundingPubKey) !== 0, 'DlcOffer and DlcAccept FundingPubKey cannot be the same');
|
|
2081
1991
|
const dlcAccept = new messaging_1.DlcAccept();
|
|
@@ -2113,7 +2023,7 @@ Payout Group not found even with brute force search');
|
|
|
2113
2023
|
const { dlcTransactions, messagesList } = await this.createDlcTxs(dlcOffer, dlcAccept);
|
|
2114
2024
|
const { cetSignatures, refundSignature } = await this.CreateCetAdaptorAndRefundSigs(dlcOffer, dlcAccept, dlcTransactions, messagesList, false);
|
|
2115
2025
|
const _dlcTransactions = dlcTransactions;
|
|
2116
|
-
const contractId =
|
|
2026
|
+
const contractId = (0, Utils_1.computeContractId)(_dlcTransactions.fundTx.txId.serialize(), _dlcTransactions.fundTxVout, dlcOffer.temporaryContractId);
|
|
2117
2027
|
_dlcTransactions.contractId = contractId;
|
|
2118
2028
|
dlcAccept.cetAdaptorSignatures = cetSignatures;
|
|
2119
2029
|
dlcAccept.refundSignature = refundSignature;
|
|
@@ -2133,10 +2043,9 @@ Payout Group not found even with brute force search');
|
|
|
2133
2043
|
const { dlcTransactions, messagesList } = await this.createDlcTxs(dlcOffer, dlcAccept);
|
|
2134
2044
|
await this.VerifyCetAdaptorAndRefundSigs(dlcOffer, dlcAccept, dlcSign, dlcTransactions, messagesList, true);
|
|
2135
2045
|
const { cetSignatures, refundSignature } = await this.CreateCetAdaptorAndRefundSigs(dlcOffer, dlcAccept, dlcTransactions, messagesList, true);
|
|
2136
|
-
const fundingSignatures = await this.
|
|
2046
|
+
const fundingSignatures = await this.CreateFundingSigs(dlcOffer, dlcAccept, dlcTransactions, true);
|
|
2137
2047
|
const dlcTxs = dlcTransactions;
|
|
2138
|
-
const contractId =
|
|
2139
|
-
(0, assert_1.default)(Buffer.compare(contractId, this.computeContractId(dlcTxs.fundTx.txId.serialize(), dlcTxs.fundTxVout, dlcOffer.temporaryContractId)) === 0, 'contractId must be the xor of funding txid, fundingOutputIndex and the tempContractId');
|
|
2048
|
+
const contractId = (0, Utils_1.computeContractId)(dlcTxs.fundTx.txId.serialize(), dlcTxs.fundTxVout, dlcOffer.temporaryContractId);
|
|
2140
2049
|
dlcTxs.contractId = contractId;
|
|
2141
2050
|
dlcSign.contractId = contractId;
|
|
2142
2051
|
dlcSign.cetAdaptorSignatures = cetSignatures;
|
|
@@ -2154,6 +2063,8 @@ Payout Group not found even with brute force search');
|
|
|
2154
2063
|
*/
|
|
2155
2064
|
async finalizeDlcSign(dlcOffer, dlcAccept, dlcSign, dlcTxs) {
|
|
2156
2065
|
let messagesList = [];
|
|
2066
|
+
const contractId = (0, Utils_1.computeContractId)(dlcTxs.fundTx.txId.serialize(), dlcTxs.fundTxVout, dlcOffer.temporaryContractId);
|
|
2067
|
+
(0, assert_1.default)(Buffer.compare(contractId, dlcSign.contractId) === 0, `Finalize Dlc Sign Contract ID mismatch: ${contractId.toString('hex')} !== ${dlcSign.contractId.toString('hex')}`);
|
|
2157
2068
|
if (dlcOffer.contractInfo.type === messaging_1.MessageType.SingleContractInfo &&
|
|
2158
2069
|
dlcOffer.contractInfo.contractDescriptor.type ===
|
|
2159
2070
|
messaging_1.MessageType.SingleContractInfo) {
|
|
@@ -2168,8 +2079,8 @@ Payout Group not found even with brute force search');
|
|
|
2168
2079
|
messagesList = oracleEventMessagesList;
|
|
2169
2080
|
}
|
|
2170
2081
|
await this.VerifyCetAdaptorAndRefundSigs(dlcOffer, dlcAccept, dlcSign, dlcTxs, messagesList, false);
|
|
2171
|
-
await this.
|
|
2172
|
-
const fundingSignatures = await this.
|
|
2082
|
+
await this.VerifyFundingSigs(dlcOffer, dlcAccept, dlcSign, dlcTxs, false);
|
|
2083
|
+
const fundingSignatures = await this.CreateFundingSigs(dlcOffer, dlcAccept, dlcTxs, false);
|
|
2173
2084
|
const fundTx = await this.CreateFundingTx(dlcOffer, dlcAccept, dlcSign, dlcTxs, fundingSignatures);
|
|
2174
2085
|
return fundTx;
|
|
2175
2086
|
}
|
|
@@ -2204,19 +2115,8 @@ Payout Group not found even with brute force search');
|
|
|
2204
2115
|
if (Number(dlcTxs.refundTx.locktime) !== dlcOffer.refundLocktime) {
|
|
2205
2116
|
throw new Error(`Refund transaction locktime ${dlcTxs.refundTx.locktime} does not match expected ${dlcOffer.refundLocktime}`);
|
|
2206
2117
|
}
|
|
2207
|
-
|
|
2208
|
-
const
|
|
2209
|
-
? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
|
|
2210
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
2211
|
-
const p2ms = bitcoinjs_lib_1.payments.p2ms({
|
|
2212
|
-
m: 2,
|
|
2213
|
-
pubkeys: fundingPubKeys,
|
|
2214
|
-
network,
|
|
2215
|
-
});
|
|
2216
|
-
const paymentVariant = bitcoinjs_lib_1.payments.p2wsh({
|
|
2217
|
-
redeem: p2ms,
|
|
2218
|
-
network,
|
|
2219
|
-
});
|
|
2118
|
+
const fundingPubKeys = (0, Utils_1.orderPubkeysLexicographically)(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey);
|
|
2119
|
+
const paymentVariant = (0, Utils_1.createP2WSHMultisigFromOrdered)(fundingPubKeys, network);
|
|
2220
2120
|
// Add the funding input with sequence from refund transaction
|
|
2221
2121
|
psbt.addInput({
|
|
2222
2122
|
hash: dlcTxs.fundTx.txId.serialize(),
|
|
@@ -2246,14 +2146,14 @@ Payout Group not found even with brute force search');
|
|
|
2246
2146
|
// This is the offerer's pubkey, use dlcSign.refundSignature
|
|
2247
2147
|
partialSigs.push({
|
|
2248
2148
|
pubkey: pubkey,
|
|
2249
|
-
signature:
|
|
2149
|
+
signature: (0, Utils_1.ensureDerSignature)(dlcSign.refundSignature),
|
|
2250
2150
|
});
|
|
2251
2151
|
}
|
|
2252
2152
|
else if (Buffer.compare(pubkey, dlcAccept.fundingPubkey) === 0) {
|
|
2253
2153
|
// This is the accepter's pubkey, use dlcAccept.refundSignature
|
|
2254
2154
|
partialSigs.push({
|
|
2255
2155
|
pubkey: pubkey,
|
|
2256
|
-
signature:
|
|
2156
|
+
signature: (0, Utils_1.ensureDerSignature)(dlcAccept.refundSignature),
|
|
2257
2157
|
});
|
|
2258
2158
|
}
|
|
2259
2159
|
}
|
|
@@ -2303,18 +2203,7 @@ Payout Group not found even with brute force search');
|
|
|
2303
2203
|
isOfferer = await this.isOfferer(dlcOffer, dlcAccept);
|
|
2304
2204
|
const network = await this.getConnectedNetwork();
|
|
2305
2205
|
const psbt = new bitcoinjs_lib_1.Psbt({ network });
|
|
2306
|
-
const
|
|
2307
|
-
? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
|
|
2308
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
2309
|
-
const p2ms = bitcoinjs_lib_1.payments.p2ms({
|
|
2310
|
-
m: 2,
|
|
2311
|
-
pubkeys: fundingPubKeys,
|
|
2312
|
-
network,
|
|
2313
|
-
});
|
|
2314
|
-
const paymentVariant = bitcoinjs_lib_1.payments.p2wsh({
|
|
2315
|
-
redeem: p2ms,
|
|
2316
|
-
network,
|
|
2317
|
-
});
|
|
2206
|
+
const paymentVariant = (0, Utils_1.createP2WSHMultisig)(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey, network);
|
|
2318
2207
|
// Initiate and build PSBT
|
|
2319
2208
|
let inputs = _inputs;
|
|
2320
2209
|
if (!_inputs) {
|
|
@@ -2414,7 +2303,7 @@ Payout Group not found even with brute force search');
|
|
|
2414
2303
|
return ecc.verify(msghash, pubkey, signature);
|
|
2415
2304
|
});
|
|
2416
2305
|
// Extract close signature from psbt and decode it to only extract r and s values
|
|
2417
|
-
const closeSignature = await bitcoinjs_lib_1.script.signature.decode(
|
|
2306
|
+
const closeSignature = await bitcoinjs_lib_1.script.signature.decode((0, Utils_1.ensureBuffer)(psbt.data.inputs[fundingInputIndex].partialSig[0].signature)).signature;
|
|
2418
2307
|
// Extract funding signatures from psbt
|
|
2419
2308
|
const inputSigs = psbt.data.inputs
|
|
2420
2309
|
.filter((input) => input !== fundingInputIndex)
|
|
@@ -2423,9 +2312,9 @@ Payout Group not found even with brute force search');
|
|
|
2423
2312
|
const witnessElements = [];
|
|
2424
2313
|
for (let i = 0; i < inputSigs.length; i++) {
|
|
2425
2314
|
const sigWitness = new messaging_1.ScriptWitnessV0();
|
|
2426
|
-
sigWitness.witness =
|
|
2315
|
+
sigWitness.witness = (0, Utils_1.ensureBuffer)(inputSigs[i].signature);
|
|
2427
2316
|
const pubKeyWitness = new messaging_1.ScriptWitnessV0();
|
|
2428
|
-
pubKeyWitness.witness =
|
|
2317
|
+
pubKeyWitness.witness = (0, Utils_1.ensureBuffer)(inputSigs[i].pubkey);
|
|
2429
2318
|
witnessElements.push([sigWitness, pubKeyWitness]);
|
|
2430
2319
|
}
|
|
2431
2320
|
const fundingSignatures = new messaging_1.FundingSignatures();
|
|
@@ -2555,18 +2444,7 @@ Payout Group not found even with brute force search');
|
|
|
2555
2444
|
dlcClose.validate();
|
|
2556
2445
|
const network = await this.getConnectedNetwork();
|
|
2557
2446
|
const psbt = new bitcoinjs_lib_1.Psbt({ network });
|
|
2558
|
-
const
|
|
2559
|
-
? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
|
|
2560
|
-
: [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
|
|
2561
|
-
const p2ms = bitcoinjs_lib_1.payments.p2ms({
|
|
2562
|
-
m: 2,
|
|
2563
|
-
pubkeys: fundingPubKeys,
|
|
2564
|
-
network,
|
|
2565
|
-
});
|
|
2566
|
-
const paymentVariant = bitcoinjs_lib_1.payments.p2wsh({
|
|
2567
|
-
redeem: p2ms,
|
|
2568
|
-
network,
|
|
2569
|
-
});
|
|
2447
|
+
const paymentVariant = (0, Utils_1.createP2WSHMultisig)(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey, network);
|
|
2570
2448
|
// Make temporary array to hold all inputs and then sort them
|
|
2571
2449
|
// this method can be improved later
|
|
2572
2450
|
const psbtInputs = [];
|
|
@@ -2793,15 +2671,7 @@ Payout Group not found even with brute force search');
|
|
|
2793
2671
|
: [remotePubkey, localPubkey];
|
|
2794
2672
|
const network = await this.getConnectedNetwork();
|
|
2795
2673
|
// Create 2-of-2 multisig payment using deterministic ordering
|
|
2796
|
-
const
|
|
2797
|
-
m: 2,
|
|
2798
|
-
pubkeys: orderedPubkeys,
|
|
2799
|
-
network,
|
|
2800
|
-
});
|
|
2801
|
-
const paymentVariant = bitcoinjs_lib_1.payments.p2wsh({
|
|
2802
|
-
redeem: p2ms,
|
|
2803
|
-
network,
|
|
2804
|
-
});
|
|
2674
|
+
const paymentVariant = (0, Utils_1.createP2WSHMultisigFromOrdered)(orderedPubkeys, network);
|
|
2805
2675
|
const multisigAddress = paymentVariant.address;
|
|
2806
2676
|
// Verify this matches the actual funding output address
|
|
2807
2677
|
const actualFundingOutput = tx.outputs[dlcInputInfo.fundVout];
|