ecash-lib 4.9.0 → 4.11.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/README.md +2 -0
- package/dist/consts.d.ts +2 -0
- package/dist/consts.d.ts.map +1 -1
- package/dist/consts.js +3 -1
- package/dist/consts.js.map +1 -1
- package/dist/ffi/ecash_lib_wasm_bg_browser.js +20495 -20495
- package/dist/ffi/ecash_lib_wasm_bg_browser.wasm +0 -0
- package/dist/ffi/ecash_lib_wasm_bg_nodejs.wasm +0 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/psbt.d.ts +65 -0
- package/dist/psbt.d.ts.map +1 -0
- package/dist/psbt.js +494 -0
- package/dist/psbt.js.map +1 -0
- package/dist/script.d.ts +60 -0
- package/dist/script.d.ts.map +1 -1
- package/dist/script.js +217 -0
- package/dist/script.js.map +1 -1
- package/dist/signatories.d.ts +36 -0
- package/dist/signatories.d.ts.map +1 -0
- package/dist/signatories.js +51 -0
- package/dist/signatories.js.map +1 -0
- package/dist/tx.d.ts +20 -0
- package/dist/tx.d.ts.map +1 -1
- package/dist/tx.js +51 -0
- package/dist/tx.js.map +1 -1
- package/dist/txBuilder.d.ts +2 -30
- package/dist/txBuilder.d.ts.map +1 -1
- package/dist/txBuilder.js +1 -45
- package/dist/txBuilder.js.map +1 -1
- package/package.json +1 -1
- package/src/consts.ts +3 -0
- package/src/ffi/ecash_lib_wasm_bg_browser.js +20495 -20495
- package/src/ffi/ecash_lib_wasm_bg_browser.wasm +0 -0
- package/src/ffi/ecash_lib_wasm_bg_nodejs.wasm +0 -0
- package/src/index.ts +2 -0
- package/src/psbt.ts +590 -0
- package/src/script.ts +312 -1
- package/src/signatories.ts +85 -0
- package/src/tx.ts +51 -0
- package/src/txBuilder.ts +2 -74
package/src/script.ts
CHANGED
|
@@ -7,9 +7,21 @@ import { Writer } from './io/writer.js';
|
|
|
7
7
|
import { WriterLength } from './io/writerlength.js';
|
|
8
8
|
import { WriterBytes } from './io/writerbytes.js';
|
|
9
9
|
import { toHex, fromHex } from './io/hex.js';
|
|
10
|
-
import { Op, pushBytesOp, readOp, writeOp } from './op.js';
|
|
11
10
|
import {
|
|
11
|
+
isPushOp,
|
|
12
|
+
Op,
|
|
13
|
+
parseNumberFromOp,
|
|
14
|
+
pushBytesOp,
|
|
15
|
+
pushNumberOp,
|
|
16
|
+
readOp,
|
|
17
|
+
writeOp,
|
|
18
|
+
} from './op.js';
|
|
19
|
+
import {
|
|
20
|
+
OP_0,
|
|
21
|
+
OP_1,
|
|
22
|
+
OP_16,
|
|
12
23
|
OP_CHECKSIG,
|
|
24
|
+
OP_CHECKMULTISIG,
|
|
13
25
|
OP_CODESEPARATOR,
|
|
14
26
|
OP_DUP,
|
|
15
27
|
OP_EQUAL,
|
|
@@ -17,6 +29,7 @@ import {
|
|
|
17
29
|
OP_HASH160,
|
|
18
30
|
} from './opcode.js';
|
|
19
31
|
import { Bytes } from './io/bytes.js';
|
|
32
|
+
import { MAX_PUBKEYS_PER_MULTISIG } from './consts.js';
|
|
20
33
|
import { Address } from './address/address';
|
|
21
34
|
|
|
22
35
|
/** A Bitcoin Script locking/unlocking a UTXO */
|
|
@@ -172,6 +185,304 @@ export class Script {
|
|
|
172
185
|
public static p2pkhSpend(pk: Uint8Array, sig: Uint8Array): Script {
|
|
173
186
|
return Script.fromOps([pushBytesOp(sig), pushBytesOp(pk)]);
|
|
174
187
|
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Build m-of-n multisig script: OP_m pubkey1 pubkey2 ... pubkey_n OP_n OP_CHECKMULTISIG.
|
|
191
|
+
* Works for P2SH-wrapped or bare multisig.
|
|
192
|
+
* Pubkeys are used in the order given; callers who want canonical (sorted) multisig
|
|
193
|
+
* must sort pubkeys before calling.
|
|
194
|
+
*/
|
|
195
|
+
public static multisig(minNumPks: number, pubkeys: Uint8Array[]): Script {
|
|
196
|
+
const numPubkeys = pubkeys.length;
|
|
197
|
+
if (minNumPks < 1) {
|
|
198
|
+
throw new Error(`minNumPks must be >= 1 (got ${minNumPks})`);
|
|
199
|
+
}
|
|
200
|
+
if (minNumPks > numPubkeys) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`minNumPks must be <= numPubkeys (got ${minNumPks} of ${numPubkeys})`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
if (numPubkeys > MAX_PUBKEYS_PER_MULTISIG) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
`numPubkeys must be <= ${MAX_PUBKEYS_PER_MULTISIG} (got ${numPubkeys})`,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
const ops: Op[] = [
|
|
211
|
+
pushNumberOp(minNumPks),
|
|
212
|
+
...pubkeys.map(pk => pushBytesOp(pk)),
|
|
213
|
+
pushNumberOp(numPubkeys),
|
|
214
|
+
OP_CHECKMULTISIG,
|
|
215
|
+
];
|
|
216
|
+
return Script.fromOps(ops);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Build scriptSig for multisig: <dummy> sig1 sig2 ... sig_m [redeemScript].
|
|
221
|
+
* Omit redeemScript for bare multisig; include for P2SH-wrapped.
|
|
222
|
+
* Use undefined for missing signatures (replaced with 0x01 placeholder).
|
|
223
|
+
* For Schnorr sigs, pass pubkeyIndices (set of signer indices) to build the
|
|
224
|
+
* checkbits dummy; signatures must be ordered by sorted pubkeyIndices.
|
|
225
|
+
* The full set of signer indices must be known when building Schnorr format.
|
|
226
|
+
* For partially signed inputs where the final signers are not yet known,
|
|
227
|
+
* use ECDSA format (omit pubkeyIndices) until the full signer set is determined.
|
|
228
|
+
* Ref spec https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/2019-11-15-schnorrmultisig.md
|
|
229
|
+
*/
|
|
230
|
+
public static multisigSpend(params: {
|
|
231
|
+
signatures: (Uint8Array | undefined)[];
|
|
232
|
+
redeemScript?: Script;
|
|
233
|
+
pubkeyIndices?: Set<number>;
|
|
234
|
+
/** For bare Schnorr multisig: total pubkey count when redeemScript omitted */
|
|
235
|
+
numPubkeys?: number;
|
|
236
|
+
}): Script {
|
|
237
|
+
const { signatures, redeemScript, pubkeyIndices, numPubkeys } = params;
|
|
238
|
+
const PLACEHOLDER = new Uint8Array([0x01]);
|
|
239
|
+
let dummyOp: Op;
|
|
240
|
+
if (pubkeyIndices !== undefined) {
|
|
241
|
+
const nVal =
|
|
242
|
+
numPubkeys ??
|
|
243
|
+
(redeemScript !== undefined
|
|
244
|
+
? redeemScript.parseMultisigRedeemScript().numPubkeys
|
|
245
|
+
: undefined);
|
|
246
|
+
if (nVal === undefined) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
'pubkeyIndices requires redeemScript or numPubkeys to derive checkbits',
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
const mVal = redeemScript
|
|
252
|
+
? redeemScript.parseMultisigRedeemScript().numSignatures
|
|
253
|
+
: signatures.length;
|
|
254
|
+
if (pubkeyIndices.size !== mVal) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`pubkeyIndices must have ${mVal} elements for ${mVal}-of-${nVal}`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
for (const i of pubkeyIndices) {
|
|
260
|
+
if (i < 0 || i >= nVal) {
|
|
261
|
+
throw new Error(
|
|
262
|
+
`pubkeyIndices index ${i} out of range [0, ${nVal})`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
dummyOp = pushBytesOp(
|
|
267
|
+
checkbitsFromPubkeyIndices(pubkeyIndices, nVal),
|
|
268
|
+
);
|
|
269
|
+
} else {
|
|
270
|
+
dummyOp = OP_0;
|
|
271
|
+
}
|
|
272
|
+
const ops: Op[] = [
|
|
273
|
+
dummyOp,
|
|
274
|
+
...signatures.map(sig =>
|
|
275
|
+
pushBytesOp(sig !== undefined ? sig : PLACEHOLDER),
|
|
276
|
+
),
|
|
277
|
+
];
|
|
278
|
+
if (redeemScript !== undefined) {
|
|
279
|
+
ops.push(pushBytesOp(redeemScript.bytecode));
|
|
280
|
+
}
|
|
281
|
+
return Script.fromOps(ops);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Parse P2SH multisig spend script to extract signatures and redeem script.
|
|
286
|
+
* Returns signatures (undefined for placeholder) and parsed numSignatures, numPubkeys from redeem script.
|
|
287
|
+
* Supports both ECDSA (OP_0 + sigs) and Schnorr (checkbits + sigs) formats.
|
|
288
|
+
* Schnorr format accepts partial scriptSigs (checkbits may have fewer bits set than
|
|
289
|
+
* numSignatures when earlier signers may not yet know who else will sign).
|
|
290
|
+
* Not for bare multisig (where the locking script is in output, not input).
|
|
291
|
+
*/
|
|
292
|
+
public parseP2shMultisigSpend(): {
|
|
293
|
+
signatures: (Uint8Array | undefined)[];
|
|
294
|
+
redeemScript: Script;
|
|
295
|
+
numSignatures: number;
|
|
296
|
+
numPubkeys: number;
|
|
297
|
+
pubkeys: Uint8Array[];
|
|
298
|
+
isSchnorr: boolean;
|
|
299
|
+
/** Indices of signers (0..numPubkeys-1) for Schnorr; undefined for ECDSA */
|
|
300
|
+
pubkeyIndices?: Set<number>;
|
|
301
|
+
} {
|
|
302
|
+
const ops: Op[] = [];
|
|
303
|
+
const iter = this.ops();
|
|
304
|
+
let op: Op | undefined;
|
|
305
|
+
while ((op = iter.next()) !== undefined) {
|
|
306
|
+
ops.push(op);
|
|
307
|
+
}
|
|
308
|
+
if (ops.length < 3) {
|
|
309
|
+
throw new Error('Invalid multisig scriptSig: too few ops');
|
|
310
|
+
}
|
|
311
|
+
const redeemScriptOp = ops[ops.length - 1];
|
|
312
|
+
if (!isPushOp(redeemScriptOp)) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
'Invalid multisig scriptSig: redeem script must be final push',
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
const redeemScript = new Script(redeemScriptOp.data);
|
|
318
|
+
const parsed = Script.parseMultisigScriptSig(
|
|
319
|
+
ops.slice(0, -1),
|
|
320
|
+
redeemScript,
|
|
321
|
+
);
|
|
322
|
+
return { ...parsed, redeemScript };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Parse bare multisig spend scriptSig to extract signatures.
|
|
327
|
+
* For bare multisig the output script is the multisig script; the scriptSig
|
|
328
|
+
* is [dummy] sig1 sig2 ... sig_m with no redeem script.
|
|
329
|
+
* Assumes a fully-formed scriptSig (see parseP2shMultisigSpend for details).
|
|
330
|
+
* @param outputScript - The multisig output script (OP_m pubkey1 ... pubkey_n OP_n OP_CHECKMULTISIG)
|
|
331
|
+
*/
|
|
332
|
+
public parseBareMultisigSpend(outputScript: Script): {
|
|
333
|
+
signatures: (Uint8Array | undefined)[];
|
|
334
|
+
numSignatures: number;
|
|
335
|
+
numPubkeys: number;
|
|
336
|
+
pubkeys: Uint8Array[];
|
|
337
|
+
isSchnorr: boolean;
|
|
338
|
+
pubkeyIndices?: Set<number>;
|
|
339
|
+
} {
|
|
340
|
+
const ops: Op[] = [];
|
|
341
|
+
const iter = this.ops();
|
|
342
|
+
let op: Op | undefined;
|
|
343
|
+
while ((op = iter.next()) !== undefined) {
|
|
344
|
+
ops.push(op);
|
|
345
|
+
}
|
|
346
|
+
return Script.parseMultisigScriptSig(ops, outputScript);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private parseMultisigRedeemScript(): {
|
|
350
|
+
numSignatures: number;
|
|
351
|
+
numPubkeys: number;
|
|
352
|
+
pubkeys: Uint8Array[];
|
|
353
|
+
} {
|
|
354
|
+
const ops: Op[] = [];
|
|
355
|
+
const iter = this.ops();
|
|
356
|
+
let op: Op | undefined;
|
|
357
|
+
while ((op = iter.next()) !== undefined) {
|
|
358
|
+
ops.push(op);
|
|
359
|
+
}
|
|
360
|
+
if (ops.length < 4) {
|
|
361
|
+
throw new Error('Invalid multisig redeem script');
|
|
362
|
+
}
|
|
363
|
+
const first = ops[0];
|
|
364
|
+
const lastBeforeCheck = ops[ops.length - 2];
|
|
365
|
+
const numSignatures = Number(parseNumberFromOp(first));
|
|
366
|
+
const numPubkeys = Number(parseNumberFromOp(lastBeforeCheck));
|
|
367
|
+
const pubkeys = ops
|
|
368
|
+
.slice(1, -2)
|
|
369
|
+
.filter((op): op is { opcode: number; data: Uint8Array } =>
|
|
370
|
+
isPushOp(op),
|
|
371
|
+
)
|
|
372
|
+
.map(op => op.data);
|
|
373
|
+
if (pubkeys.length !== numPubkeys) {
|
|
374
|
+
throw new Error(
|
|
375
|
+
`Invalid multisig redeem script: expected ${numPubkeys} pubkeys, got ${pubkeys.length}`,
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
return { numSignatures, numPubkeys, pubkeys };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
private static parseMultisigScriptSig(
|
|
382
|
+
ops: Op[],
|
|
383
|
+
outputScript: Script,
|
|
384
|
+
): {
|
|
385
|
+
signatures: (Uint8Array | undefined)[];
|
|
386
|
+
numSignatures: number;
|
|
387
|
+
numPubkeys: number;
|
|
388
|
+
pubkeys: Uint8Array[];
|
|
389
|
+
isSchnorr: boolean;
|
|
390
|
+
pubkeyIndices?: Set<number>;
|
|
391
|
+
} {
|
|
392
|
+
const { numSignatures, numPubkeys, pubkeys } =
|
|
393
|
+
outputScript.parseMultisigRedeemScript();
|
|
394
|
+
if (ops.length !== numSignatures + 1) {
|
|
395
|
+
throw new Error(
|
|
396
|
+
`Invalid multisig scriptSig: expected ${numSignatures + 1} ops (dummy + ${numSignatures} sigs), got ${ops.length}`,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
const expectedCheckbitsLen = Math.floor((numPubkeys + 7) / 8);
|
|
400
|
+
let isSchnorr: boolean;
|
|
401
|
+
let sigOps: Op[];
|
|
402
|
+
let pubkeyIndices: Set<number> | undefined;
|
|
403
|
+
|
|
404
|
+
if (ops[0] === OP_0) {
|
|
405
|
+
isSchnorr = false;
|
|
406
|
+
sigOps = ops.slice(1);
|
|
407
|
+
} else {
|
|
408
|
+
let checkbitsData: Uint8Array;
|
|
409
|
+
const firstOp = ops[0];
|
|
410
|
+
if (
|
|
411
|
+
typeof firstOp === 'number' &&
|
|
412
|
+
firstOp >= OP_1 &&
|
|
413
|
+
firstOp <= OP_16
|
|
414
|
+
) {
|
|
415
|
+
checkbitsData = new Uint8Array([firstOp - 0x50]);
|
|
416
|
+
} else if (isPushOp(firstOp) && firstOp.data.length > 0) {
|
|
417
|
+
checkbitsData = firstOp.data;
|
|
418
|
+
} else {
|
|
419
|
+
throw new Error(
|
|
420
|
+
'Invalid multisig scriptSig: must start with OP_0 (ECDSA) or checkbits push (Schnorr)',
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
if (checkbitsData.length !== expectedCheckbitsLen) {
|
|
424
|
+
throw new Error(
|
|
425
|
+
`Invalid Schnorr multisig: checkbits length ${checkbitsData.length} != expected ${expectedCheckbitsLen} for numPubkeys=${numPubkeys}`,
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
isSchnorr = true;
|
|
429
|
+
pubkeyIndices = checkbitsToIndices(checkbitsData, numPubkeys);
|
|
430
|
+
sigOps = ops.slice(1);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (sigOps.length !== numSignatures) {
|
|
434
|
+
throw new Error(
|
|
435
|
+
`Invalid multisig scriptSig: expected ${numSignatures} signatures, got ${sigOps.length}`,
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
const signatures: (Uint8Array | undefined)[] = sigOps.map(op => {
|
|
439
|
+
if (!isPushOp(op)) return undefined;
|
|
440
|
+
return op.data.length === 1 && op.data[0] === 0x01
|
|
441
|
+
? undefined
|
|
442
|
+
: op.data;
|
|
443
|
+
});
|
|
444
|
+
return {
|
|
445
|
+
signatures,
|
|
446
|
+
numSignatures,
|
|
447
|
+
numPubkeys,
|
|
448
|
+
pubkeys,
|
|
449
|
+
isSchnorr,
|
|
450
|
+
...(pubkeyIndices !== undefined && { pubkeyIndices }),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Build checkbits byte array from pubkey indices for Schnorr multisig.
|
|
457
|
+
* Spec: length = floor((N+7)/8), little-endian, bit i = 1 iff index i in set.
|
|
458
|
+
*/
|
|
459
|
+
function checkbitsFromPubkeyIndices(
|
|
460
|
+
indices: Set<number>,
|
|
461
|
+
numPubkeys: number,
|
|
462
|
+
): Uint8Array {
|
|
463
|
+
const numBytes = Math.floor((numPubkeys + 7) / 8);
|
|
464
|
+
const bytes = new Uint8Array(numBytes);
|
|
465
|
+
for (const i of indices) {
|
|
466
|
+
bytes[i >>> 3] |= 1 << (i & 7);
|
|
467
|
+
}
|
|
468
|
+
return bytes;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Parse checkbits byte array to set of pubkey indices for Schnorr multisig.
|
|
473
|
+
* Inverse of checkbitsFromPubkeyIndices.
|
|
474
|
+
*/
|
|
475
|
+
function checkbitsToIndices(
|
|
476
|
+
checkbits: Uint8Array,
|
|
477
|
+
numPubkeys: number,
|
|
478
|
+
): Set<number> {
|
|
479
|
+
const indices = new Set<number>();
|
|
480
|
+
for (let i = 0; i < numPubkeys; i++) {
|
|
481
|
+
if ((checkbits[i >>> 3]! & (1 << (i & 7))) !== 0) {
|
|
482
|
+
indices.add(i);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return indices;
|
|
175
486
|
}
|
|
176
487
|
|
|
177
488
|
/** Iterator over the Ops of a Script. */
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 The Bitcoin developers
|
|
2
|
+
// Distributed under the MIT software license, see the accompanying
|
|
3
|
+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Signatories and signing helpers used with `TxBuilder` (`TxBuilderInput.signatory`).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Ecc } from './ecc.js';
|
|
10
|
+
import { sha256d } from './hash.js';
|
|
11
|
+
import { WriterBytes } from './io/writerbytes.js';
|
|
12
|
+
import { pushBytesOp } from './op.js';
|
|
13
|
+
import { Script } from './script.js';
|
|
14
|
+
import { SigHashType, SigHashTypeVariant } from './sigHashType.js';
|
|
15
|
+
import { UnsignedTxInput } from './unsignedTx.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Function that contains all the required data to sign a given `input` and
|
|
19
|
+
* return the scriptSig.
|
|
20
|
+
*
|
|
21
|
+
* Use it by attaching a `Signatory` to a TxBuilderInput, e.g. like this for a
|
|
22
|
+
* P2PKH input:
|
|
23
|
+
* ```ts
|
|
24
|
+
* new TxBuilder({
|
|
25
|
+
* inputs: [{
|
|
26
|
+
* input: { prevOut: ... },
|
|
27
|
+
* signatory: P2PKHSignatory(sk, pk, ALL_BIP143),
|
|
28
|
+
* }],
|
|
29
|
+
* ...
|
|
30
|
+
* })
|
|
31
|
+
* ```
|
|
32
|
+
**/
|
|
33
|
+
export type Signatory = (ecc: Ecc, input: UnsignedTxInput) => Script;
|
|
34
|
+
|
|
35
|
+
/** Append the sighash flags to the signature */
|
|
36
|
+
export function flagSignature(
|
|
37
|
+
sig: Uint8Array,
|
|
38
|
+
sigHashFlags: SigHashType,
|
|
39
|
+
): Uint8Array {
|
|
40
|
+
const writer = new WriterBytes(sig.length + 1);
|
|
41
|
+
writer.putBytes(sig);
|
|
42
|
+
writer.putU8(sigHashFlags.toInt() & 0xff);
|
|
43
|
+
return writer.data;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Sign the sighash using Schnorr for BIP143 signatures and ECDSA for Legacy
|
|
48
|
+
* signatures, and then flags the signature correctly
|
|
49
|
+
**/
|
|
50
|
+
export function signWithSigHash(
|
|
51
|
+
ecc: Ecc,
|
|
52
|
+
sk: Uint8Array,
|
|
53
|
+
sigHash: Uint8Array,
|
|
54
|
+
sigHashType: SigHashType,
|
|
55
|
+
): Uint8Array {
|
|
56
|
+
const sig =
|
|
57
|
+
sigHashType.variant == SigHashTypeVariant.LEGACY
|
|
58
|
+
? ecc.ecdsaSign(sk, sigHash)
|
|
59
|
+
: ecc.schnorrSign(sk, sigHash);
|
|
60
|
+
return flagSignature(sig, sigHashType);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Signatory for a P2PKH input. Always uses Schnorr signatures */
|
|
64
|
+
export const P2PKHSignatory = (
|
|
65
|
+
sk: Uint8Array,
|
|
66
|
+
pk: Uint8Array,
|
|
67
|
+
sigHashType: SigHashType,
|
|
68
|
+
) => {
|
|
69
|
+
return (ecc: Ecc, input: UnsignedTxInput): Script => {
|
|
70
|
+
const preimage = input.sigHashPreimage(sigHashType);
|
|
71
|
+
const sighash = sha256d(preimage.bytes);
|
|
72
|
+
const sigFlagged = signWithSigHash(ecc, sk, sighash, sigHashType);
|
|
73
|
+
return Script.p2pkhSpend(pk, sigFlagged);
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** Signatory for a P2PK input. Always uses Schnorr signatures */
|
|
78
|
+
export const P2PKSignatory = (sk: Uint8Array, sigHashType: SigHashType) => {
|
|
79
|
+
return (ecc: Ecc, input: UnsignedTxInput): Script => {
|
|
80
|
+
const preimage = input.sigHashPreimage(sigHashType);
|
|
81
|
+
const sighash = sha256d(preimage.bytes);
|
|
82
|
+
const sigFlagged = signWithSigHash(ecc, sk, sighash, sigHashType);
|
|
83
|
+
return Script.fromOps([pushBytesOp(sigFlagged)]);
|
|
84
|
+
};
|
|
85
|
+
};
|
package/src/tx.ts
CHANGED
|
@@ -175,6 +175,57 @@ export class Tx {
|
|
|
175
175
|
public txid(): string {
|
|
176
176
|
return toHexRev(sha256d(this.ser()));
|
|
177
177
|
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Attempt to parse a **non-SegWit** serialized transaction from `data` — the same
|
|
181
|
+
* encoding as {@link Tx.deser} / `Tx.ser()` (version, inputs, outputs, locktime;
|
|
182
|
+
* no witness marker or witness stacks). eCash does not use SegWit transactions,
|
|
183
|
+
* so this is the full-transaction wire format on-chain here. Returns a `Tx` only
|
|
184
|
+
* when `data` is **exactly** one such transaction: the parse must consume the
|
|
185
|
+
* **entire** buffer (no trailing bytes). Returns `undefined` on malformed input
|
|
186
|
+
* or if any bytes remain after `locktime`.
|
|
187
|
+
*
|
|
188
|
+
* **PSBT-only motivation:** Bitcoin ABC’s PSBT input key `0x00` (`PSBT_IN_UTXO`)
|
|
189
|
+
* stores **either** a full previous transaction (BIP 174 “non-witness UTXO”) **or**
|
|
190
|
+
* a compact `CTxOut` (amount + `scriptPubKey`). Callers must disambiguate. Plain
|
|
191
|
+
* {@link Tx.deser} reads a tx from the start of `data` but **does not** require
|
|
192
|
+
* `data.length` to match the serialized length — leftover bytes are ignored, so
|
|
193
|
+
* you cannot use it to prove “this blob is solely a full tx.” This helper is
|
|
194
|
+
* used from PSBT (`resolveWitnessFromKey00` in `psbt.ts`): if `tryDeserExact`
|
|
195
|
+
* succeeds (and the txid matches), treat as non-witness UTXO; otherwise decode as
|
|
196
|
+
* `CTxOut`-shaped bytes.
|
|
197
|
+
*/
|
|
198
|
+
public static tryDeserExact(data: Uint8Array): Tx | undefined {
|
|
199
|
+
try {
|
|
200
|
+
const bytes = new Bytes(data);
|
|
201
|
+
const version = bytes.readU32();
|
|
202
|
+
const numInputs = readVarSize(bytes);
|
|
203
|
+
const inputs: TxInput[] = [];
|
|
204
|
+
for (let i = 0; i < numInputs; ++i) {
|
|
205
|
+
const txid = bytes.readBytes(32);
|
|
206
|
+
const outIdx = bytes.readU32();
|
|
207
|
+
const script = Script.readWithSize(bytes);
|
|
208
|
+
const sequence = bytes.readU32();
|
|
209
|
+
inputs.push({
|
|
210
|
+
prevOut: { txid, outIdx },
|
|
211
|
+
script,
|
|
212
|
+
sequence,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
const numOutputs = readVarSize(bytes);
|
|
216
|
+
const outputs: TxOutput[] = [];
|
|
217
|
+
for (let i = 0; i < numOutputs; ++i) {
|
|
218
|
+
outputs.push(readTxOutput(bytes));
|
|
219
|
+
}
|
|
220
|
+
const locktime = bytes.readU32();
|
|
221
|
+
if (bytes.idx !== data.length) {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
return new Tx({ version, inputs, outputs, locktime });
|
|
225
|
+
} catch {
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
178
229
|
}
|
|
179
230
|
|
|
180
231
|
export function readTxOutput(bytes: Bytes): TxOutput {
|
package/src/txBuilder.ts
CHANGED
|
@@ -3,11 +3,8 @@
|
|
|
3
3
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
4
4
|
|
|
5
5
|
import { Ecc, EccDummy } from './ecc.js';
|
|
6
|
-
import {
|
|
7
|
-
import { WriterBytes } from './io/writerbytes.js';
|
|
8
|
-
import { pushBytesOp } from './op.js';
|
|
6
|
+
import type { Signatory } from './signatories.js';
|
|
9
7
|
import { Script } from './script.js';
|
|
10
|
-
import { SigHashType, SigHashTypeVariant } from './sigHashType.js';
|
|
11
8
|
import {
|
|
12
9
|
DEFAULT_TX_VERSION,
|
|
13
10
|
Tx,
|
|
@@ -18,27 +15,10 @@ import {
|
|
|
18
15
|
} from './tx.js';
|
|
19
16
|
import { UnsignedTx, UnsignedTxInput } from './unsignedTx.js';
|
|
20
17
|
|
|
21
|
-
/**
|
|
22
|
-
* Function that contains all the required data to sign a given `input` and
|
|
23
|
-
* return the scriptSig.
|
|
24
|
-
*
|
|
25
|
-
* Use it by attaching a `Signatory` to a TxBuilderInput, e.g. like this for a
|
|
26
|
-
* P2PKH input:
|
|
27
|
-
* ```ts
|
|
28
|
-
* new TxBuilder({
|
|
29
|
-
* inputs: [{
|
|
30
|
-
* input: { prevOut: ... },
|
|
31
|
-
* signatory: P2PKHSignatory(sk, pk, ALL_BIP143),
|
|
32
|
-
* }],
|
|
33
|
-
* ...
|
|
34
|
-
* })
|
|
35
|
-
* ```
|
|
36
|
-
**/
|
|
37
|
-
export type Signatory = (ecc: Ecc, input: UnsignedTxInput) => Script;
|
|
38
|
-
|
|
39
18
|
/** Builder input that bundles all the data required to sign a TxInput */
|
|
40
19
|
export interface TxBuilderInput {
|
|
41
20
|
input: TxInput;
|
|
21
|
+
/** Signing callback; see `Signatory` in `signatories.ts`. */
|
|
42
22
|
signatory?: Signatory;
|
|
43
23
|
}
|
|
44
24
|
|
|
@@ -226,55 +206,3 @@ export class TxBuilder {
|
|
|
226
206
|
export function calcTxFee(txSize: number, feePerKb: bigint): bigint {
|
|
227
207
|
return (BigInt(txSize) * BigInt(feePerKb) + 999n) / 1000n;
|
|
228
208
|
}
|
|
229
|
-
|
|
230
|
-
/** Append the sighash flags to the signature */
|
|
231
|
-
export function flagSignature(
|
|
232
|
-
sig: Uint8Array,
|
|
233
|
-
sigHashFlags: SigHashType,
|
|
234
|
-
): Uint8Array {
|
|
235
|
-
const writer = new WriterBytes(sig.length + 1);
|
|
236
|
-
writer.putBytes(sig);
|
|
237
|
-
writer.putU8(sigHashFlags.toInt() & 0xff);
|
|
238
|
-
return writer.data;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Sign the sighash using Schnorr for BIP143 signatures and ECDSA for Legacy
|
|
243
|
-
* signatures, and then flags the signature correctly
|
|
244
|
-
**/
|
|
245
|
-
export function signWithSigHash(
|
|
246
|
-
ecc: Ecc,
|
|
247
|
-
sk: Uint8Array,
|
|
248
|
-
sigHash: Uint8Array,
|
|
249
|
-
sigHashType: SigHashType,
|
|
250
|
-
): Uint8Array {
|
|
251
|
-
const sig =
|
|
252
|
-
sigHashType.variant == SigHashTypeVariant.LEGACY
|
|
253
|
-
? ecc.ecdsaSign(sk, sigHash)
|
|
254
|
-
: ecc.schnorrSign(sk, sigHash);
|
|
255
|
-
return flagSignature(sig, sigHashType);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/** Signatory for a P2PKH input. Always uses Schnorr signatures */
|
|
259
|
-
export const P2PKHSignatory = (
|
|
260
|
-
sk: Uint8Array,
|
|
261
|
-
pk: Uint8Array,
|
|
262
|
-
sigHashType: SigHashType,
|
|
263
|
-
) => {
|
|
264
|
-
return (ecc: Ecc, input: UnsignedTxInput): Script => {
|
|
265
|
-
const preimage = input.sigHashPreimage(sigHashType);
|
|
266
|
-
const sighash = sha256d(preimage.bytes);
|
|
267
|
-
const sigFlagged = signWithSigHash(ecc, sk, sighash, sigHashType);
|
|
268
|
-
return Script.p2pkhSpend(pk, sigFlagged);
|
|
269
|
-
};
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
/** Signatory for a P2PK input. Always uses Schnorr signatures */
|
|
273
|
-
export const P2PKSignatory = (sk: Uint8Array, sigHashType: SigHashType) => {
|
|
274
|
-
return (ecc: Ecc, input: UnsignedTxInput): Script => {
|
|
275
|
-
const preimage = input.sigHashPreimage(sigHashType);
|
|
276
|
-
const sighash = sha256d(preimage.bytes);
|
|
277
|
-
const sigFlagged = signWithSigHash(ecc, sk, sighash, sigHashType);
|
|
278
|
-
return Script.fromOps([pushBytesOp(sigFlagged)]);
|
|
279
|
-
};
|
|
280
|
-
};
|