@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
package/lib/index.ts
CHANGED
package/lib/utils/Utils.ts
CHANGED
|
@@ -5,8 +5,12 @@ import {
|
|
|
5
5
|
DlcOffer,
|
|
6
6
|
DlcSign,
|
|
7
7
|
DlcTransactions,
|
|
8
|
+
FundingInput,
|
|
8
9
|
MessageType,
|
|
9
10
|
} from '@node-dlc/messaging';
|
|
11
|
+
import { BitcoinNetwork } from 'bitcoin-network';
|
|
12
|
+
import { payments } from 'bitcoinjs-lib';
|
|
13
|
+
import { Payment } from 'bitcoinjs-lib';
|
|
10
14
|
import randomBytes from 'randombytes';
|
|
11
15
|
|
|
12
16
|
export const asyncForEach = async (
|
|
@@ -116,3 +120,331 @@ interface OutputsToPayoutsResponse {
|
|
|
116
120
|
payouts: PayoutRequest[];
|
|
117
121
|
messagesList: Messages[];
|
|
118
122
|
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Orders public keys lexicographically for consistent multisig script creation.
|
|
126
|
+
* This ensures deterministic ordering regardless of which party creates the script.
|
|
127
|
+
*
|
|
128
|
+
* @param pubkey1 First public key buffer
|
|
129
|
+
* @param pubkey2 Second public key buffer
|
|
130
|
+
* @returns Array of public keys in lexicographic order [smaller, larger]
|
|
131
|
+
*/
|
|
132
|
+
export function orderPubkeysLexicographically(
|
|
133
|
+
pubkey1: Buffer,
|
|
134
|
+
pubkey2: Buffer,
|
|
135
|
+
): [Buffer, Buffer] {
|
|
136
|
+
return Buffer.compare(pubkey1, pubkey2) === -1
|
|
137
|
+
? [pubkey1, pubkey2]
|
|
138
|
+
: [pubkey2, pubkey1];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Creates a 2-of-2 P2WSH multisig payment variant with lexicographically ordered pubkeys.
|
|
143
|
+
* This is the standard pattern used for DLC funding scripts.
|
|
144
|
+
*
|
|
145
|
+
* @param pubkey1 First public key buffer
|
|
146
|
+
* @param pubkey2 Second public key buffer
|
|
147
|
+
* @param network Bitcoin network
|
|
148
|
+
* @returns Payment variant with P2WSH multisig script
|
|
149
|
+
*/
|
|
150
|
+
export function createP2WSHMultisig(
|
|
151
|
+
pubkey1: Buffer,
|
|
152
|
+
pubkey2: Buffer,
|
|
153
|
+
network: BitcoinNetwork,
|
|
154
|
+
): Payment {
|
|
155
|
+
const orderedPubkeys = orderPubkeysLexicographically(pubkey1, pubkey2);
|
|
156
|
+
|
|
157
|
+
const p2ms = payments.p2ms({
|
|
158
|
+
m: 2,
|
|
159
|
+
pubkeys: orderedPubkeys,
|
|
160
|
+
network,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return payments.p2wsh({
|
|
164
|
+
redeem: p2ms,
|
|
165
|
+
network,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function createP2MSMultisig(
|
|
170
|
+
pubkey1: Buffer,
|
|
171
|
+
pubkey2: Buffer,
|
|
172
|
+
network: BitcoinNetwork,
|
|
173
|
+
): Payment {
|
|
174
|
+
const orderedPubkeys = orderPubkeysLexicographically(pubkey1, pubkey2);
|
|
175
|
+
|
|
176
|
+
return payments.p2ms({
|
|
177
|
+
m: 2,
|
|
178
|
+
pubkeys: orderedPubkeys,
|
|
179
|
+
network,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Creates a 2-of-2 P2WSH multisig payment variant from pre-ordered pubkeys.
|
|
185
|
+
* Use this when you already have the pubkeys in the correct order.
|
|
186
|
+
*
|
|
187
|
+
* @param orderedPubkeys Array of public key buffers in the desired order
|
|
188
|
+
* @param network Bitcoin network
|
|
189
|
+
* @returns Payment variant with P2WSH multisig script
|
|
190
|
+
*/
|
|
191
|
+
export function createP2WSHMultisigFromOrdered(
|
|
192
|
+
orderedPubkeys: Buffer[],
|
|
193
|
+
network: BitcoinNetwork,
|
|
194
|
+
): Payment {
|
|
195
|
+
const p2ms = payments.p2ms({
|
|
196
|
+
m: 2,
|
|
197
|
+
pubkeys: orderedPubkeys,
|
|
198
|
+
network,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return payments.p2wsh({
|
|
202
|
+
redeem: p2ms,
|
|
203
|
+
network,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Creates a 2-of-2 multisig script (P2MS) from lexicographically ordered pubkeys.
|
|
209
|
+
* Returns just the multisig script, not wrapped in P2WSH.
|
|
210
|
+
*
|
|
211
|
+
* @param pubkey1 First public key buffer
|
|
212
|
+
* @param pubkey2 Second public key buffer
|
|
213
|
+
* @param network Bitcoin network
|
|
214
|
+
* @returns Multisig payment script
|
|
215
|
+
*/
|
|
216
|
+
export function createMultisigScript(
|
|
217
|
+
pubkey1: Buffer,
|
|
218
|
+
pubkey2: Buffer,
|
|
219
|
+
network: BitcoinNetwork,
|
|
220
|
+
): Payment {
|
|
221
|
+
const orderedPubkeys = orderPubkeysLexicographically(pubkey1, pubkey2);
|
|
222
|
+
|
|
223
|
+
return payments.p2ms({
|
|
224
|
+
m: 2,
|
|
225
|
+
pubkeys: orderedPubkeys,
|
|
226
|
+
network,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Helper function to ensure we have a Buffer object
|
|
232
|
+
* Handles cases where Buffer objects have been serialized/deserialized
|
|
233
|
+
*/
|
|
234
|
+
export function ensureBuffer(
|
|
235
|
+
bufferLike: Buffer | { type: string; data: number[] } | any,
|
|
236
|
+
): Buffer {
|
|
237
|
+
if (Buffer.isBuffer(bufferLike)) {
|
|
238
|
+
return bufferLike;
|
|
239
|
+
}
|
|
240
|
+
if (bufferLike && bufferLike.type === 'Buffer' && bufferLike.data) {
|
|
241
|
+
return Buffer.from(bufferLike.data);
|
|
242
|
+
}
|
|
243
|
+
return bufferLike;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Detect if signature is in compact format (64 bytes) or DER format
|
|
248
|
+
* and convert compact to DER if needed, adding SIGHASH_ALL flag
|
|
249
|
+
*/
|
|
250
|
+
export function ensureDerSignature(signature: Buffer): Buffer {
|
|
251
|
+
// If signature is 64 bytes, it's likely compact format (32-byte r + 32-byte s)
|
|
252
|
+
if (signature.length === 64) {
|
|
253
|
+
// Convert compact signature to DER format
|
|
254
|
+
const r = signature.subarray(0, 32);
|
|
255
|
+
const s = signature.subarray(32, 64);
|
|
256
|
+
|
|
257
|
+
// Create DER encoding manually
|
|
258
|
+
// DER format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S]
|
|
259
|
+
|
|
260
|
+
// Remove leading zeros from r and s, but keep at least one byte
|
|
261
|
+
let rBytes = r;
|
|
262
|
+
while (
|
|
263
|
+
rBytes.length > 1 &&
|
|
264
|
+
rBytes[0] === 0x00 &&
|
|
265
|
+
(rBytes[1] & 0x80) === 0
|
|
266
|
+
) {
|
|
267
|
+
rBytes = rBytes.subarray(1);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let sBytes = s;
|
|
271
|
+
while (
|
|
272
|
+
sBytes.length > 1 &&
|
|
273
|
+
sBytes[0] === 0x00 &&
|
|
274
|
+
(sBytes[1] & 0x80) === 0
|
|
275
|
+
) {
|
|
276
|
+
sBytes = sBytes.subarray(1);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Add padding byte if high bit is set (to keep numbers positive)
|
|
280
|
+
if ((rBytes[0] & 0x80) !== 0) {
|
|
281
|
+
rBytes = Buffer.concat([Buffer.from([0x00]), rBytes]);
|
|
282
|
+
}
|
|
283
|
+
if ((sBytes[0] & 0x80) !== 0) {
|
|
284
|
+
sBytes = Buffer.concat([Buffer.from([0x00]), sBytes]);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const totalLength = 2 + rBytes.length + 2 + sBytes.length;
|
|
288
|
+
|
|
289
|
+
const derSignature = Buffer.concat([
|
|
290
|
+
Buffer.from([0x30, totalLength]), // SEQUENCE tag and total length
|
|
291
|
+
Buffer.from([0x02, rBytes.length]), // INTEGER tag and R length
|
|
292
|
+
rBytes,
|
|
293
|
+
Buffer.from([0x02, sBytes.length]), // INTEGER tag and S length
|
|
294
|
+
sBytes,
|
|
295
|
+
Buffer.from([0x01]), // SIGHASH_ALL flag
|
|
296
|
+
]);
|
|
297
|
+
|
|
298
|
+
return derSignature;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// If it's already DER format, check if it has SIGHASH flag
|
|
302
|
+
if (signature.length > 0 && signature[0] === 0x30) {
|
|
303
|
+
// Check if it already has a SIGHASH flag (last byte should be 0x01 for SIGHASH_ALL)
|
|
304
|
+
if (signature[signature.length - 1] !== 0x01) {
|
|
305
|
+
// Add SIGHASH_ALL flag
|
|
306
|
+
return Buffer.concat([signature, Buffer.from([0x01])]);
|
|
307
|
+
}
|
|
308
|
+
return signature;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// For other formats, return as-is
|
|
312
|
+
return signature;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Detect if signature is in DER format and convert to compact format (64 bytes)
|
|
317
|
+
* by extracting r and s values, removing SIGHASH flag if present
|
|
318
|
+
*/
|
|
319
|
+
export function ensureCompactSignature(signature: Buffer): Buffer {
|
|
320
|
+
// If signature is already 64 bytes, it's likely already compact format
|
|
321
|
+
if (signature.length === 64) {
|
|
322
|
+
return signature;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Check if it's DER format (starts with 0x30)
|
|
326
|
+
if (signature.length > 6 && signature[0] === 0x30) {
|
|
327
|
+
let derSig = signature;
|
|
328
|
+
|
|
329
|
+
// Remove SIGHASH flag if present (last byte is typically 0x01 for SIGHASH_ALL)
|
|
330
|
+
if (signature[signature.length - 1] === 0x01) {
|
|
331
|
+
derSig = signature.subarray(0, -1);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Parse DER format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S]
|
|
335
|
+
if (derSig[0] !== 0x30) {
|
|
336
|
+
throw new Error('Invalid DER signature: missing SEQUENCE tag');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const totalLength = derSig[1];
|
|
340
|
+
if (derSig.length < totalLength + 2) {
|
|
341
|
+
throw new Error('Invalid DER signature: length mismatch');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
let offset = 2;
|
|
345
|
+
|
|
346
|
+
// Parse R value
|
|
347
|
+
if (derSig[offset] !== 0x02) {
|
|
348
|
+
throw new Error('Invalid DER signature: missing INTEGER tag for R');
|
|
349
|
+
}
|
|
350
|
+
offset++;
|
|
351
|
+
|
|
352
|
+
const rLength = derSig[offset];
|
|
353
|
+
offset++;
|
|
354
|
+
|
|
355
|
+
if (offset + rLength > derSig.length) {
|
|
356
|
+
throw new Error(
|
|
357
|
+
'Invalid DER signature: R length exceeds signature length',
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
let rBytes = derSig.subarray(offset, offset + rLength);
|
|
362
|
+
offset += rLength;
|
|
363
|
+
|
|
364
|
+
// Parse S value
|
|
365
|
+
if (derSig[offset] !== 0x02) {
|
|
366
|
+
throw new Error('Invalid DER signature: missing INTEGER tag for S');
|
|
367
|
+
}
|
|
368
|
+
offset++;
|
|
369
|
+
|
|
370
|
+
const sLength = derSig[offset];
|
|
371
|
+
offset++;
|
|
372
|
+
|
|
373
|
+
if (offset + sLength > derSig.length) {
|
|
374
|
+
throw new Error(
|
|
375
|
+
'Invalid DER signature: S length exceeds signature length',
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
let sBytes = derSig.subarray(offset, offset + sLength);
|
|
380
|
+
|
|
381
|
+
// Remove leading zero padding from r and s (DER may pad to prevent negative interpretation)
|
|
382
|
+
while (rBytes.length > 1 && rBytes[0] === 0x00) {
|
|
383
|
+
rBytes = rBytes.subarray(1);
|
|
384
|
+
}
|
|
385
|
+
while (sBytes.length > 1 && sBytes[0] === 0x00) {
|
|
386
|
+
sBytes = sBytes.subarray(1);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Pad to 32 bytes each (compact format requires exactly 32 bytes for r and s)
|
|
390
|
+
while (rBytes.length < 32) {
|
|
391
|
+
rBytes = Buffer.concat([Buffer.from([0x00]), rBytes]);
|
|
392
|
+
}
|
|
393
|
+
while (sBytes.length < 32) {
|
|
394
|
+
sBytes = Buffer.concat([Buffer.from([0x00]), sBytes]);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (rBytes.length !== 32 || sBytes.length !== 32) {
|
|
398
|
+
throw new Error('Invalid signature values: r or s exceeds 32 bytes');
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Combine r and s into 64-byte compact format
|
|
402
|
+
return Buffer.concat([rBytes, sBytes]);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// For other formats, throw error as we can't convert
|
|
406
|
+
throw new Error(
|
|
407
|
+
'Unable to convert signature to compact format: unknown format',
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Compute contract ID from fund transaction ID, output index, and temporary contract ID
|
|
413
|
+
* Matches the Rust implementation in rust-dlc
|
|
414
|
+
*/
|
|
415
|
+
export function computeContractId(
|
|
416
|
+
fundTxId: Buffer,
|
|
417
|
+
fundOutputIndex: number,
|
|
418
|
+
temporaryContractId: Buffer,
|
|
419
|
+
): Buffer {
|
|
420
|
+
if (fundTxId.length !== 32) {
|
|
421
|
+
throw new Error('Fund transaction ID must be 32 bytes');
|
|
422
|
+
}
|
|
423
|
+
if (temporaryContractId.length !== 32) {
|
|
424
|
+
throw new Error('Temporary contract ID must be 32 bytes');
|
|
425
|
+
}
|
|
426
|
+
if (fundOutputIndex > 0xffff) {
|
|
427
|
+
throw new Error('Fund output index must fit in 16 bits');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const result = Buffer.alloc(32);
|
|
431
|
+
|
|
432
|
+
// XOR fund_tx_id with temporary_id, with byte order reversal for fund_tx_id
|
|
433
|
+
for (let i = 0; i < 32; i++) {
|
|
434
|
+
result[i] = fundTxId[31 - i] ^ temporaryContractId[i];
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// XOR the fund output index into the last two bytes
|
|
438
|
+
result[30] ^= (fundOutputIndex >> 8) & 0xff; // High byte
|
|
439
|
+
result[31] ^= fundOutputIndex & 0xff; // Low byte
|
|
440
|
+
|
|
441
|
+
return result;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function sortFundingInputsBySerialId(
|
|
445
|
+
inputs: FundingInput[],
|
|
446
|
+
): FundingInput[] {
|
|
447
|
+
return inputs.sort(
|
|
448
|
+
(a, b) => Number(a.inputSerialId) - Number(b.inputSerialId),
|
|
449
|
+
);
|
|
450
|
+
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atomicfinance/bitcoin-ddk-provider",
|
|
3
3
|
"umdName": "BitcoinDdkProvider",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.2.0",
|
|
5
5
|
"description": "Bitcoin Abstraction Layer Ddk Provider",
|
|
6
6
|
"author": "Atomic Finance <info@atomic.finance>",
|
|
7
7
|
"homepage": "",
|
|
8
8
|
"license": "ISC",
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@atomicfinance/bitcoin-utils": "4.
|
|
12
|
-
"@atomicfinance/provider": "^4.
|
|
13
|
-
"@atomicfinance/types": "^4.
|
|
14
|
-
"@atomicfinance/utils": "^4.
|
|
11
|
+
"@atomicfinance/bitcoin-utils": "4.2.0",
|
|
12
|
+
"@atomicfinance/provider": "^4.2.0",
|
|
13
|
+
"@atomicfinance/types": "^4.2.0",
|
|
14
|
+
"@atomicfinance/utils": "^4.2.0",
|
|
15
15
|
"bignumber.js": "^9.0.1",
|
|
16
16
|
"bip66": "^1.1.5",
|
|
17
17
|
"bitcoin-network": "0.1.0",
|