@scure/btc-signer 1.4.0 → 1.6.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 +5 -2
- package/_type_test.js +2 -2
- package/_type_test.js.map +1 -1
- package/esm/_type_test.js +2 -2
- package/esm/_type_test.js.map +1 -1
- package/esm/index.d.ts +8 -6
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +7 -10
- package/esm/index.js.map +1 -1
- package/esm/payment.d.ts +43 -76
- package/esm/payment.d.ts.map +1 -1
- package/esm/payment.js +15 -2
- package/esm/payment.js.map +1 -1
- package/esm/psbt.d.ts +1 -1
- package/esm/psbt.d.ts.map +1 -1
- package/esm/psbt.js +2 -3
- package/esm/psbt.js.map +1 -1
- package/esm/script.d.ts +3 -1
- package/esm/script.d.ts.map +1 -1
- package/esm/script.js.map +1 -1
- package/esm/transaction.d.ts +10 -121
- package/esm/transaction.d.ts.map +1 -1
- package/esm/transaction.js +8 -8
- package/esm/transaction.js.map +1 -1
- package/esm/utils.d.ts +17 -12
- package/esm/utils.d.ts.map +1 -1
- package/esm/utils.js +6 -4
- package/esm/utils.js.map +1 -1
- package/esm/utxo.d.ts +23 -21
- package/esm/utxo.d.ts.map +1 -1
- package/esm/utxo.js +6 -6
- package/esm/utxo.js.map +1 -1
- package/index.d.ts +8 -6
- package/index.d.ts.map +1 -1
- package/index.js +20 -22
- package/index.js.map +1 -1
- package/package.json +16 -12
- package/payment.d.ts +43 -76
- package/payment.d.ts.map +1 -1
- package/payment.js +14 -1
- package/payment.js.map +1 -1
- package/psbt.d.ts +1 -1
- package/psbt.d.ts.map +1 -1
- package/psbt.js +5 -6
- package/psbt.js.map +1 -1
- package/script.d.ts +3 -1
- package/script.d.ts.map +1 -1
- package/script.js.map +1 -1
- package/src/_type_test.ts +2 -2
- package/src/index.ts +25 -14
- package/src/payment.ts +74 -27
- package/src/psbt.ts +27 -11
- package/src/script.ts +5 -5
- package/src/transaction.ts +50 -36
- package/src/utils.ts +26 -16
- package/src/utxo.ts +30 -18
- package/transaction.d.ts +10 -121
- package/transaction.d.ts.map +1 -1
- package/transaction.js +17 -17
- package/transaction.js.map +1 -1
- package/utils.d.ts +17 -12
- package/utils.d.ts.map +1 -1
- package/utils.js +6 -4
- package/utils.js.map +1 -1
- package/utxo.d.ts +23 -21
- package/utxo.d.ts.map +1 -1
- package/utxo.js +4 -4
- package/utxo.js.map +1 -1
package/src/transaction.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import * as P from 'micro-packed';
|
|
2
1
|
import { hex } from '@scure/base';
|
|
3
|
-
import
|
|
2
|
+
import * as P from 'micro-packed';
|
|
3
|
+
import { Address, type CustomScript, OutScript, checkScript, tapLeafHash } from './payment.js';
|
|
4
4
|
import * as psbt from './psbt.js'; // circular
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import {
|
|
6
|
+
CompactSizeLen,
|
|
7
|
+
RawOldTx,
|
|
8
|
+
RawOutput,
|
|
9
|
+
RawTx,
|
|
10
|
+
RawWitness,
|
|
11
|
+
Script,
|
|
12
|
+
VarBytes,
|
|
13
|
+
} from './script.js';
|
|
8
14
|
import * as u from './utils.js';
|
|
9
|
-
import {
|
|
15
|
+
import { type Bytes, NETWORK, concatBytes, equalBytes, isBytes } from './utils.js';
|
|
16
|
+
import { getInputType, getPrevOut, normalizeInput, toVsize } from './utxo.js'; // circular
|
|
10
17
|
|
|
11
18
|
const EMPTY32 = new Uint8Array(32);
|
|
12
19
|
const EMPTY_OUTPUT: P.UnwrapCoder<typeof RawOutput> = {
|
|
@@ -30,15 +37,15 @@ export const PRECISION = 8;
|
|
|
30
37
|
export const DEFAULT_VERSION = 2;
|
|
31
38
|
export const DEFAULT_LOCKTIME = 0;
|
|
32
39
|
export const DEFAULT_SEQUENCE = 4294967295;
|
|
33
|
-
export const Decimal = P.coders.decimal(PRECISION);
|
|
40
|
+
export const Decimal: P.Coder<bigint, string> = P.coders.decimal(PRECISION);
|
|
34
41
|
|
|
35
42
|
// Same as value || def, but doesn't overwrites zero ('0', 0, 0n, etc)
|
|
36
|
-
export const def = <T>(value: T | undefined, def: T) => (value === undefined ? def : value);
|
|
43
|
+
export const def = <T>(value: T | undefined, def: T): T => (value === undefined ? def : value);
|
|
37
44
|
|
|
38
45
|
export function cloneDeep<T>(obj: T): T {
|
|
39
46
|
if (Array.isArray(obj)) return obj.map((i) => cloneDeep(i)) as unknown as T;
|
|
40
47
|
// slice of nodejs Buffer doesn't copy
|
|
41
|
-
else if (obj
|
|
48
|
+
else if (isBytes(obj)) return Uint8Array.from(obj) as unknown as T;
|
|
42
49
|
// immutable
|
|
43
50
|
else if (['number', 'bigint', 'boolean', 'string', 'undefined'].includes(typeof obj)) return obj;
|
|
44
51
|
// null is object
|
|
@@ -166,7 +173,7 @@ function unpackSighash(hashType: number) {
|
|
|
166
173
|
};
|
|
167
174
|
}
|
|
168
175
|
|
|
169
|
-
function validateOpts(opts: TxOpts) {
|
|
176
|
+
function validateOpts(opts: TxOpts): Readonly<TxOpts> {
|
|
170
177
|
if (opts !== undefined && {}.toString.call(opts) !== '[object Object]')
|
|
171
178
|
throw new Error(`Wrong object type for transaction options: ${opts}`);
|
|
172
179
|
|
|
@@ -182,7 +189,8 @@ function validateOpts(opts: TxOpts) {
|
|
|
182
189
|
if (typeof _opts.allowUnknowOutput !== 'undefined')
|
|
183
190
|
opts.allowUnknownOutputs = _opts.allowUnknowOutput;
|
|
184
191
|
// 0 and -1 happens in tests
|
|
185
|
-
if (![-1, 0, 1, 2].includes(_opts.version))
|
|
192
|
+
if (![-1, 0, 1, 2, 3].includes(_opts.version))
|
|
193
|
+
throw new Error(`Unknown version: ${_opts.version}`);
|
|
186
194
|
if (typeof _opts.lockTime !== 'number') throw new Error('Transaction lock time should be number');
|
|
187
195
|
P.U32LE.encode(_opts.lockTime); // Additional range checks that lockTime
|
|
188
196
|
// There is no PSBT v1, and any new version will probably have fields which we don't know how to parse, which
|
|
@@ -233,7 +241,7 @@ export class Transaction {
|
|
|
233
241
|
}
|
|
234
242
|
|
|
235
243
|
// Import
|
|
236
|
-
static fromRaw(raw: Bytes, opts: TxOpts = {}) {
|
|
244
|
+
static fromRaw(raw: Bytes, opts: TxOpts = {}): Transaction {
|
|
237
245
|
const parsed = RawTx.decode(raw);
|
|
238
246
|
const tx = new Transaction({ ...opts, version: parsed.version, lockTime: parsed.lockTime });
|
|
239
247
|
for (const o of parsed.outputs) tx.addOutput(o);
|
|
@@ -246,7 +254,7 @@ export class Transaction {
|
|
|
246
254
|
return tx;
|
|
247
255
|
}
|
|
248
256
|
// PSBT
|
|
249
|
-
static fromPSBT(psbt_: Bytes, opts: TxOpts = {}) {
|
|
257
|
+
static fromPSBT(psbt_: Bytes, opts: TxOpts = {}): Transaction {
|
|
250
258
|
let parsed: P.UnwrapCoder<typeof psbt.RawPSBTV0>;
|
|
251
259
|
try {
|
|
252
260
|
parsed = psbt.RawPSBTV0.decode(psbt_);
|
|
@@ -281,7 +289,7 @@ export class Transaction {
|
|
|
281
289
|
if (lockTime !== DEFAULT_LOCKTIME) tx.global.fallbackLocktime = lockTime;
|
|
282
290
|
return tx;
|
|
283
291
|
}
|
|
284
|
-
toPSBT(PSBTVersion = this.opts.PSBTVersion) {
|
|
292
|
+
toPSBT(PSBTVersion: number | undefined = this.opts.PSBTVersion): Uint8Array {
|
|
285
293
|
if (PSBTVersion !== 0 && PSBTVersion !== 2)
|
|
286
294
|
throw new Error(`Wrong PSBT version=${PSBTVersion}`);
|
|
287
295
|
// if (PSBTVersion === 0 && this.inputs.length === 0) {
|
|
@@ -338,7 +346,7 @@ export class Transaction {
|
|
|
338
346
|
}
|
|
339
347
|
|
|
340
348
|
// BIP370 lockTime (https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki#determining-lock-time)
|
|
341
|
-
get lockTime() {
|
|
349
|
+
get lockTime(): number {
|
|
342
350
|
let height = DEFAULT_LOCKTIME;
|
|
343
351
|
let heightCnt = 0;
|
|
344
352
|
let time = DEFAULT_LOCKTIME;
|
|
@@ -358,7 +366,7 @@ export class Transaction {
|
|
|
358
366
|
return this.global.fallbackLocktime || DEFAULT_LOCKTIME;
|
|
359
367
|
}
|
|
360
368
|
|
|
361
|
-
get version() {
|
|
369
|
+
get version(): number {
|
|
362
370
|
// Should be not possible
|
|
363
371
|
if (this.global.txVersion === undefined) throw new Error('No global.txVersion');
|
|
364
372
|
return this.global.txVersion;
|
|
@@ -381,7 +389,8 @@ export class Transaction {
|
|
|
381
389
|
// We will lose some vectors -> smaller test coverage of preimages (very important!)
|
|
382
390
|
private inputSighash(idx: number) {
|
|
383
391
|
this.checkInputIdx(idx);
|
|
384
|
-
const
|
|
392
|
+
const inputSighash = this.inputs[idx].sighashType;
|
|
393
|
+
const sighash = inputSighash === undefined ? SignatureHash.DEFAULT : inputSighash;
|
|
385
394
|
// ALL or DEFAULT -- everything signed
|
|
386
395
|
// NONE -- all inputs + no outputs
|
|
387
396
|
// SINGLE -- all inputs + output with same index
|
|
@@ -418,7 +427,7 @@ export class Transaction {
|
|
|
418
427
|
return { addInput, addOutput, inputs, outputs };
|
|
419
428
|
}
|
|
420
429
|
|
|
421
|
-
get isFinal() {
|
|
430
|
+
get isFinal(): boolean {
|
|
422
431
|
for (let idx = 0; idx < this.inputs.length; idx++)
|
|
423
432
|
if (this.inputStatus(idx) !== 'finalized') return false;
|
|
424
433
|
return true;
|
|
@@ -452,7 +461,7 @@ export class Transaction {
|
|
|
452
461
|
get vsize(): number {
|
|
453
462
|
return toVsize(this.weight);
|
|
454
463
|
}
|
|
455
|
-
toBytes(withScriptSig = false, withWitness = false) {
|
|
464
|
+
toBytes(withScriptSig = false, withWitness = false): Uint8Array {
|
|
456
465
|
return RawTx.encode({
|
|
457
466
|
version: this.version,
|
|
458
467
|
lockTime: this.lockTime,
|
|
@@ -468,15 +477,15 @@ export class Transaction {
|
|
|
468
477
|
get unsignedTx(): Bytes {
|
|
469
478
|
return this.toBytes(false, false);
|
|
470
479
|
}
|
|
471
|
-
get hex() {
|
|
480
|
+
get hex(): string {
|
|
472
481
|
return hex.encode(this.toBytes(true, this.hasWitnesses));
|
|
473
482
|
}
|
|
474
483
|
|
|
475
|
-
get hash() {
|
|
484
|
+
get hash(): string {
|
|
476
485
|
if (!this.isFinal) throw new Error('Transaction is not finalized');
|
|
477
486
|
return hex.encode(u.sha256x2(this.toBytes(true)));
|
|
478
487
|
}
|
|
479
|
-
get id() {
|
|
488
|
+
get id(): string {
|
|
480
489
|
if (!this.isFinal) throw new Error('Transaction is not finalized');
|
|
481
490
|
return hex.encode(u.sha256x2(this.toBytes(true)).reverse());
|
|
482
491
|
}
|
|
@@ -485,11 +494,11 @@ export class Transaction {
|
|
|
485
494
|
if (!Number.isSafeInteger(idx) || 0 > idx || idx >= this.inputs.length)
|
|
486
495
|
throw new Error(`Wrong input index=${idx}`);
|
|
487
496
|
}
|
|
488
|
-
getInput(idx: number) {
|
|
497
|
+
getInput(idx: number): psbt.TransactionInput {
|
|
489
498
|
this.checkInputIdx(idx);
|
|
490
499
|
return cloneDeep(this.inputs[idx]);
|
|
491
500
|
}
|
|
492
|
-
get inputsLength() {
|
|
501
|
+
get inputsLength(): number {
|
|
493
502
|
return this.inputs.length;
|
|
494
503
|
}
|
|
495
504
|
// Modification
|
|
@@ -499,7 +508,7 @@ export class Transaction {
|
|
|
499
508
|
this.inputs.push(normalizeInput(input, undefined, undefined, this.opts.disableScriptCheck));
|
|
500
509
|
return this.inputs.length - 1;
|
|
501
510
|
}
|
|
502
|
-
updateInput(idx: number, input: psbt.TransactionInputUpdate, _ignoreSignStatus = false) {
|
|
511
|
+
updateInput(idx: number, input: psbt.TransactionInputUpdate, _ignoreSignStatus = false): void {
|
|
503
512
|
this.checkInputIdx(idx);
|
|
504
513
|
let allowedFields = undefined;
|
|
505
514
|
if (!_ignoreSignStatus) {
|
|
@@ -519,17 +528,17 @@ export class Transaction {
|
|
|
519
528
|
if (!Number.isSafeInteger(idx) || 0 > idx || idx >= this.outputs.length)
|
|
520
529
|
throw new Error(`Wrong output index=${idx}`);
|
|
521
530
|
}
|
|
522
|
-
getOutput(idx: number) {
|
|
531
|
+
getOutput(idx: number): psbt.TransactionOutput {
|
|
523
532
|
this.checkOutputIdx(idx);
|
|
524
533
|
return cloneDeep(this.outputs[idx]);
|
|
525
534
|
}
|
|
526
|
-
getOutputAddress(idx: number, network = NETWORK): string | undefined {
|
|
535
|
+
getOutputAddress(idx: number, network: u.BTC_NETWORK = NETWORK): string | undefined {
|
|
527
536
|
const out = this.getOutput(idx);
|
|
528
537
|
if (!out.script) return;
|
|
529
538
|
return Address(network).encode(OutScript.decode(out.script));
|
|
530
539
|
}
|
|
531
540
|
|
|
532
|
-
get outputsLength() {
|
|
541
|
+
get outputsLength(): number {
|
|
533
542
|
return this.outputs.length;
|
|
534
543
|
}
|
|
535
544
|
private normalizeOutput(
|
|
@@ -567,7 +576,7 @@ export class Transaction {
|
|
|
567
576
|
this.outputs.push(this.normalizeOutput(o));
|
|
568
577
|
return this.outputs.length - 1;
|
|
569
578
|
}
|
|
570
|
-
updateOutput(idx: number, output: psbt.TransactionOutputUpdate, _ignoreSignStatus = false) {
|
|
579
|
+
updateOutput(idx: number, output: psbt.TransactionOutputUpdate, _ignoreSignStatus = false): void {
|
|
571
580
|
this.checkOutputIdx(idx);
|
|
572
581
|
let allowedFields = undefined;
|
|
573
582
|
if (!_ignoreSignStatus) {
|
|
@@ -577,7 +586,7 @@ export class Transaction {
|
|
|
577
586
|
}
|
|
578
587
|
this.outputs[idx] = this.normalizeOutput(output, this.outputs[idx], allowedFields);
|
|
579
588
|
}
|
|
580
|
-
addOutputAddress(address: string, amount: bigint, network = NETWORK): number {
|
|
589
|
+
addOutputAddress(address: string, amount: bigint, network: u.BTC_NETWORK = NETWORK): number {
|
|
581
590
|
return this.addOutput({ script: OutScript.encode(Address(network).decode(address)), amount });
|
|
582
591
|
}
|
|
583
592
|
// Utils
|
|
@@ -632,7 +641,12 @@ export class Transaction {
|
|
|
632
641
|
});
|
|
633
642
|
return u.sha256x2(tmpTx, P.I32LE.encode(hashType));
|
|
634
643
|
}
|
|
635
|
-
preimageWitnessV0(
|
|
644
|
+
preimageWitnessV0(
|
|
645
|
+
idx: number,
|
|
646
|
+
prevOutScript: Bytes,
|
|
647
|
+
hashType: number,
|
|
648
|
+
amount: bigint
|
|
649
|
+
): Uint8Array {
|
|
636
650
|
const { isAny, isNone, isSingle } = unpackSighash(hashType);
|
|
637
651
|
let inputHash = EMPTY32;
|
|
638
652
|
let sequenceHash = EMPTY32;
|
|
@@ -670,7 +684,7 @@ export class Transaction {
|
|
|
670
684
|
leafScript?: Bytes,
|
|
671
685
|
leafVer = 0xc0,
|
|
672
686
|
annex?: Bytes
|
|
673
|
-
) {
|
|
687
|
+
): Uint8Array {
|
|
674
688
|
if (!Array.isArray(amount) || this.inputs.length !== amount.length)
|
|
675
689
|
throw new Error(`Invalid amounts array=${amount}`);
|
|
676
690
|
if (!Array.isArray(prevOutScript) || this.inputs.length !== prevOutScript.length)
|
|
@@ -877,7 +891,7 @@ export class Transaction {
|
|
|
877
891
|
return num;
|
|
878
892
|
}
|
|
879
893
|
|
|
880
|
-
finalizeIdx(idx: number) {
|
|
894
|
+
finalizeIdx(idx: number): void {
|
|
881
895
|
this.checkInputIdx(idx);
|
|
882
896
|
if (this.fee < 0n) throw new Error('Outputs spends more than inputs amount');
|
|
883
897
|
const input = this.inputs[idx];
|
|
@@ -1026,10 +1040,10 @@ export class Transaction {
|
|
|
1026
1040
|
if (finalScriptWitness) input.finalScriptWitness = finalScriptWitness;
|
|
1027
1041
|
cleanFinalInput(input);
|
|
1028
1042
|
}
|
|
1029
|
-
finalize() {
|
|
1043
|
+
finalize(): void {
|
|
1030
1044
|
for (let i = 0; i < this.inputs.length; i++) this.finalizeIdx(i);
|
|
1031
1045
|
}
|
|
1032
|
-
extract() {
|
|
1046
|
+
extract(): Uint8Array {
|
|
1033
1047
|
if (!this.isFinal) throw new Error('Transaction has unfinalized inputs');
|
|
1034
1048
|
if (!this.outputs.length) throw new Error('Transaction has no outputs');
|
|
1035
1049
|
if (this.fee < 0n) throw new Error('Outputs spends more than inputs amount');
|
|
@@ -1061,7 +1075,7 @@ export class Transaction {
|
|
|
1061
1075
|
for (let i = 0; i < this.outputs.length; i++) this.updateOutput(i, other.outputs[i], true);
|
|
1062
1076
|
return this;
|
|
1063
1077
|
}
|
|
1064
|
-
clone() {
|
|
1078
|
+
clone(): Transaction {
|
|
1065
1079
|
// deepClone probably faster, but this enforces that encoding is valid
|
|
1066
1080
|
return Transaction.fromPSBT(this.toPSBT(this.opts.PSBTVersion), this.opts);
|
|
1067
1081
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { schnorr, secp256k1 as secp } from '@noble/curves/secp256k1';
|
|
2
2
|
import { ripemd160 } from '@noble/hashes/ripemd160';
|
|
3
3
|
import { sha256 } from '@noble/hashes/sha256';
|
|
4
|
-
import {
|
|
4
|
+
import { utils as packedUtils, U32LE } from 'micro-packed';
|
|
5
5
|
|
|
6
|
+
export type Hex = string | Uint8Array;
|
|
6
7
|
export type Bytes = Uint8Array;
|
|
7
8
|
const Point = secp.ProjectivePoint;
|
|
8
9
|
const CURVE_ORDER = secp.CURVE.n;
|
|
9
10
|
|
|
10
|
-
const
|
|
11
|
-
|
|
11
|
+
const isBytes: (a: unknown) => a is Uint8Array = packedUtils.isBytes;
|
|
12
|
+
const concatBytes: (...arrays: Uint8Array[]) => Uint8Array = packedUtils.concatBytes;
|
|
13
|
+
const equalBytes: (a: Uint8Array, b: Uint8Array) => boolean = packedUtils.equalBytes;
|
|
14
|
+
export { concatBytes, equalBytes, isBytes, sha256 };
|
|
12
15
|
|
|
13
|
-
export const hash160 = (msg:
|
|
14
|
-
export const sha256x2 = (...msgs:
|
|
15
|
-
export const randomPrivateKeyBytes = schnorr.utils.randomPrivateKey;
|
|
16
|
+
export const hash160 = (msg: Uint8Array): Uint8Array => ripemd160(sha256(msg));
|
|
17
|
+
export const sha256x2 = (...msgs: Uint8Array[]): Uint8Array => sha256(sha256(concatBytes(...msgs)));
|
|
18
|
+
export const randomPrivateKeyBytes: () => Uint8Array = schnorr.utils.randomPrivateKey;
|
|
16
19
|
export const pubSchnorr = schnorr.getPublicKey as (priv: string | Uint8Array) => Uint8Array;
|
|
17
|
-
export const pubECDSA =
|
|
20
|
+
export const pubECDSA: (privateKey: string | Uint8Array, isCompressed?: boolean) => Uint8Array =
|
|
21
|
+
secp.getPublicKey;
|
|
18
22
|
|
|
19
23
|
// low-r signature grinding. Used to reduce tx size by 1 byte.
|
|
20
24
|
// noble/secp256k1 does not support the feature: it is not used outside of BTC.
|
|
@@ -35,8 +39,8 @@ export function signECDSA(hash: Bytes, privateKey: Bytes, lowR = false): Bytes {
|
|
|
35
39
|
return sig.toDERRawBytes();
|
|
36
40
|
}
|
|
37
41
|
|
|
38
|
-
export const signSchnorr = schnorr.sign;
|
|
39
|
-
export const tagSchnorr = schnorr.utils.taggedHash;
|
|
42
|
+
export const signSchnorr: typeof schnorr.sign = schnorr.sign;
|
|
43
|
+
export const tagSchnorr: typeof schnorr.utils.taggedHash = schnorr.utils.taggedHash;
|
|
40
44
|
|
|
41
45
|
export enum PubT {
|
|
42
46
|
ecdsa,
|
|
@@ -65,7 +69,7 @@ export function tapTweak(a: Bytes, b: Bytes): bigint {
|
|
|
65
69
|
return tn;
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
export function taprootTweakPrivKey(privKey:
|
|
72
|
+
export function taprootTweakPrivKey(privKey: Bytes, merkleRoot: Bytes = new Uint8Array()): Bytes {
|
|
69
73
|
const u = schnorr.utils;
|
|
70
74
|
const seckey0 = u.bytesToNumberBE(privKey); // seckey0 = int_from_bytes(seckey0)
|
|
71
75
|
const P = Point.fromPrivateKey(seckey0); // P = point_mul(G, seckey0)
|
|
@@ -78,7 +82,7 @@ export function taprootTweakPrivKey(privKey: Uint8Array, merkleRoot = new Uint8A
|
|
|
78
82
|
return u.numberToBytesBE(u.mod(seckey + t, CURVE_ORDER), 32);
|
|
79
83
|
}
|
|
80
84
|
|
|
81
|
-
export function taprootTweakPubkey(pubKey:
|
|
85
|
+
export function taprootTweakPubkey(pubKey: Bytes, h: Bytes): [Bytes, number] {
|
|
82
86
|
const u = schnorr.utils;
|
|
83
87
|
const t = tapTweak(pubKey, h); // t = int_from_bytes(tagged_hash("TapTweak", pubkey + h))
|
|
84
88
|
const P = u.lift_x(u.bytesToNumberBE(pubKey)); // P = lift_x(int_from_bytes(pubkey))
|
|
@@ -93,16 +97,22 @@ export function taprootTweakPubkey(pubKey: Uint8Array, h: Uint8Array): [Uint8Arr
|
|
|
93
97
|
// It is possible to switch SECP256K1_GENERATOR_POINT with some random point;
|
|
94
98
|
// but it's too complex to prove.
|
|
95
99
|
// Also used by bitcoin-core and bitcoinjs-lib
|
|
96
|
-
export const TAPROOT_UNSPENDABLE_KEY = sha256(Point.BASE.toRawBytes(false));
|
|
100
|
+
export const TAPROOT_UNSPENDABLE_KEY: Bytes = sha256(Point.BASE.toRawBytes(false));
|
|
97
101
|
|
|
98
|
-
export
|
|
102
|
+
export type BTC_NETWORK = {
|
|
103
|
+
bech32: string;
|
|
104
|
+
pubKeyHash: number;
|
|
105
|
+
scriptHash: number;
|
|
106
|
+
wif: number;
|
|
107
|
+
};
|
|
108
|
+
export const NETWORK: BTC_NETWORK = {
|
|
99
109
|
bech32: 'bc',
|
|
100
110
|
pubKeyHash: 0x00,
|
|
101
111
|
scriptHash: 0x05,
|
|
102
112
|
wif: 0x80,
|
|
103
113
|
};
|
|
104
114
|
|
|
105
|
-
export const TEST_NETWORK:
|
|
115
|
+
export const TEST_NETWORK: BTC_NETWORK = {
|
|
106
116
|
bech32: 'tb',
|
|
107
117
|
pubKeyHash: 0x6f,
|
|
108
118
|
scriptHash: 0xc4,
|
|
@@ -110,7 +120,7 @@ export const TEST_NETWORK: typeof NETWORK = {
|
|
|
110
120
|
};
|
|
111
121
|
|
|
112
122
|
// Exported for tests, internal method
|
|
113
|
-
export function compareBytes(a: Bytes, b: Bytes) {
|
|
123
|
+
export function compareBytes(a: Bytes, b: Bytes): number {
|
|
114
124
|
if (!isBytes(a) || !isBytes(b)) throw new Error(`cmp: wrong type a=${typeof a} b=${typeof b}`);
|
|
115
125
|
// -1 -> a<b, 0 -> a==b, 1 -> a>b
|
|
116
126
|
const len = Math.min(a.length, b.length);
|
package/src/utxo.ts
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
import { hex } from '@scure/base';
|
|
2
2
|
import * as P from 'micro-packed';
|
|
3
|
-
import { Address, CustomScript, OutScript, checkScript, tapLeafHash } from './payment.js';
|
|
3
|
+
import { Address, type CustomScript, OutScript, checkScript, tapLeafHash } from './payment.js';
|
|
4
4
|
import * as psbt from './psbt.js';
|
|
5
5
|
import { CompactSizeLen, RawOutput, RawTx, RawWitness, Script, VarBytes } from './script.js';
|
|
6
6
|
import {
|
|
7
7
|
DEFAULT_SEQUENCE,
|
|
8
|
-
TxOpts,
|
|
9
|
-
inputBeforeSign,
|
|
10
8
|
SignatureHash,
|
|
11
9
|
Transaction,
|
|
10
|
+
type TxOpts,
|
|
11
|
+
inputBeforeSign,
|
|
12
12
|
} from './transaction.js'; // circular
|
|
13
13
|
import {
|
|
14
|
+
type Bytes,
|
|
14
15
|
NETWORK,
|
|
15
|
-
|
|
16
|
+
PubT,
|
|
17
|
+
TAPROOT_UNSPENDABLE_KEY,
|
|
16
18
|
compareBytes,
|
|
17
19
|
equalBytes,
|
|
18
20
|
isBytes,
|
|
19
|
-
TAPROOT_UNSPENDABLE_KEY,
|
|
20
21
|
sha256,
|
|
22
|
+
validatePubkey,
|
|
21
23
|
} from './utils.js';
|
|
22
|
-
|
|
24
|
+
|
|
25
|
+
export type PSBTInputs = psbt.PSBTKeyMapKeys<typeof psbt.PSBTInput>;
|
|
23
26
|
|
|
24
27
|
// Normalizes input
|
|
25
28
|
export function getPrevOut(input: psbt.TransactionInput): P.UnwrapCoder<typeof RawOutput> {
|
|
@@ -48,7 +51,7 @@ export function normalizeInput(
|
|
|
48
51
|
if (typeof txid === 'string') txid = hex.decode(txid);
|
|
49
52
|
// TODO: if we have nonWitnessUtxo, we can extract txId from here
|
|
50
53
|
if (txid === undefined) txid = cur?.txid;
|
|
51
|
-
let res:
|
|
54
|
+
let res: PSBTInputs = { ...cur, ...i, nonWitnessUtxo, txid };
|
|
52
55
|
if (!('nonWitnessUtxo' in i) && res.nonWitnessUtxo === undefined) delete res.nonWitnessUtxo;
|
|
53
56
|
if (res.sequence === undefined) res.sequence = DEFAULT_SEQUENCE;
|
|
54
57
|
if (res.tapMerkleRoot === null) delete res.tapMerkleRoot;
|
|
@@ -122,9 +125,17 @@ export function getInputType(input: psbt.TransactionInput, allowLegacyWitnessUtx
|
|
|
122
125
|
}
|
|
123
126
|
}
|
|
124
127
|
|
|
125
|
-
export const toVsize = (weight: number) => Math.ceil(weight / 4);
|
|
128
|
+
export const toVsize = (weight: number): number => Math.ceil(weight / 4);
|
|
126
129
|
// UTXO Select
|
|
127
|
-
type Output = { address: string; amount: bigint } | { script: Uint8Array; amount: bigint };
|
|
130
|
+
export type Output = { address: string; amount: bigint } | { script: Uint8Array; amount: bigint };
|
|
131
|
+
export type Accumulated =
|
|
132
|
+
| {
|
|
133
|
+
indices: number[];
|
|
134
|
+
fee: bigint | undefined;
|
|
135
|
+
weight: number;
|
|
136
|
+
total: bigint;
|
|
137
|
+
}
|
|
138
|
+
| undefined;
|
|
128
139
|
type TapLeafScript = psbt.TransactionInput['tapLeafScript'];
|
|
129
140
|
type TB = Parameters<typeof psbt.TaprootControlBlock.encode>[0];
|
|
130
141
|
const encodeTapBlock = (item: TB) => psbt.TaprootControlBlock.encode(item);
|
|
@@ -252,7 +263,7 @@ function estimateInput(
|
|
|
252
263
|
}
|
|
253
264
|
|
|
254
265
|
// Exported for tests, internal method
|
|
255
|
-
export const _cmpBig = (a: bigint, b: bigint) => {
|
|
266
|
+
export const _cmpBig = (a: bigint, b: bigint): 0 | 1 | -1 => {
|
|
256
267
|
const n = a - b;
|
|
257
268
|
if (n < 0n) return -1;
|
|
258
269
|
else if (n > 0n) return 1;
|
|
@@ -276,7 +287,7 @@ export type EstimatorOpts = TxOpts & {
|
|
|
276
287
|
|
|
277
288
|
function getScript(o: Output, opts: TxOpts = {}, network = NETWORK) {
|
|
278
289
|
let script;
|
|
279
|
-
if ('script' in o && o.script
|
|
290
|
+
if ('script' in o && isBytes(o.script)) {
|
|
280
291
|
script = o.script;
|
|
281
292
|
}
|
|
282
293
|
if ('address' in o) {
|
|
@@ -442,27 +453,27 @@ export class _Estimator {
|
|
|
442
453
|
}
|
|
443
454
|
|
|
444
455
|
// Sort by value instead of amount
|
|
445
|
-
get biggest() {
|
|
456
|
+
get biggest(): number[] {
|
|
446
457
|
return this.normalizedInputs
|
|
447
458
|
.map((_i, j) => j)
|
|
448
459
|
.sort((a, b) => _cmpBig(this.normalizedInputs[b].value, this.normalizedInputs[a].value));
|
|
449
460
|
}
|
|
450
|
-
get smallest() {
|
|
461
|
+
get smallest(): number[] {
|
|
451
462
|
return this.biggest.reverse();
|
|
452
463
|
}
|
|
453
464
|
// These assume that UTXO array has historical order.
|
|
454
465
|
// Otherwise, we have no way to know which tx is oldest
|
|
455
466
|
// Explorers usually give UTXO in this order.
|
|
456
|
-
get oldest() {
|
|
467
|
+
get oldest(): number[] {
|
|
457
468
|
return this.normalizedInputs.map((_i, j) => j);
|
|
458
469
|
}
|
|
459
|
-
get newest() {
|
|
470
|
+
get newest(): number[] {
|
|
460
471
|
return this.oldest.reverse();
|
|
461
472
|
}
|
|
462
473
|
// exact - like blackjack from coinselect.
|
|
463
474
|
// exact(biggest) will select one big utxo which is closer to targetValue+dust, if possible.
|
|
464
475
|
// If not, it will accumulate largest utxo until value is close to targetValue+dust.
|
|
465
|
-
accumulate(indices: number[], exact = false, skipNegative = true, all = false) {
|
|
476
|
+
accumulate(indices: number[], exact = false, skipNegative = true, all = false): Accumulated {
|
|
466
477
|
// TODO: how to handle change addresses?
|
|
467
478
|
// - cost of input
|
|
468
479
|
// - cost of change output (if input requires change)
|
|
@@ -524,7 +535,7 @@ export class _Estimator {
|
|
|
524
535
|
}
|
|
525
536
|
|
|
526
537
|
// Works like coinselect default method
|
|
527
|
-
default() {
|
|
538
|
+
default(): Accumulated {
|
|
528
539
|
const { biggest } = this;
|
|
529
540
|
const exact = this.accumulate(biggest, true, false);
|
|
530
541
|
if (exact) return exact;
|
|
@@ -602,7 +613,8 @@ export class _Estimator {
|
|
|
602
613
|
for (const o of outputs)
|
|
603
614
|
tx.addOutput({ ...o, script: getScript(o, this.opts, this.opts.network) });
|
|
604
615
|
}
|
|
605
|
-
return
|
|
616
|
+
return Object.assign(res, { tx });
|
|
617
|
+
// return { ...res, tx: tx };
|
|
606
618
|
}
|
|
607
619
|
}
|
|
608
620
|
|
package/transaction.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as P from 'micro-packed';
|
|
2
|
-
import { CustomScript } from './payment.js';
|
|
2
|
+
import { type CustomScript } from './payment.js';
|
|
3
3
|
import * as psbt from './psbt.js';
|
|
4
|
-
import
|
|
4
|
+
import * as u from './utils.js';
|
|
5
|
+
import { type Bytes } from './utils.js';
|
|
5
6
|
interface HDKey {
|
|
6
7
|
publicKey: Bytes;
|
|
7
8
|
privateKey: Bytes;
|
|
@@ -15,10 +16,7 @@ export declare const PRECISION = 8;
|
|
|
15
16
|
export declare const DEFAULT_VERSION = 2;
|
|
16
17
|
export declare const DEFAULT_LOCKTIME = 0;
|
|
17
18
|
export declare const DEFAULT_SEQUENCE = 4294967295;
|
|
18
|
-
export declare const Decimal:
|
|
19
|
-
encode: (from: bigint) => string;
|
|
20
|
-
decode: (to: string) => bigint;
|
|
21
|
-
};
|
|
19
|
+
export declare const Decimal: P.Coder<bigint, string>;
|
|
22
20
|
export declare const def: <T>(value: T | undefined, def: T) => T;
|
|
23
21
|
export declare function cloneDeep<T>(obj: T): T;
|
|
24
22
|
export type TxOpts = {
|
|
@@ -65,22 +63,7 @@ export type TransactionInputRequired = {
|
|
|
65
63
|
finalScriptSig: Bytes;
|
|
66
64
|
};
|
|
67
65
|
export declare function inputBeforeSign(i: psbt.TransactionInput): TransactionInputRequired;
|
|
68
|
-
declare function validateOpts(opts: TxOpts): Readonly<
|
|
69
|
-
version: number;
|
|
70
|
-
lockTime: number;
|
|
71
|
-
PSBTVersion: number;
|
|
72
|
-
/** @deprecated Use `allowUnknownOutputs` */
|
|
73
|
-
allowUnknowOutput?: boolean;
|
|
74
|
-
allowUnknownOutputs?: boolean;
|
|
75
|
-
/** @deprecated Use `allowUnknownInputs` */
|
|
76
|
-
allowUnknowInput?: boolean;
|
|
77
|
-
allowUnknownInputs?: boolean;
|
|
78
|
-
disableScriptCheck?: boolean;
|
|
79
|
-
bip174jsCompat?: boolean;
|
|
80
|
-
allowLegacyWitnessUtxo?: boolean;
|
|
81
|
-
lowR?: boolean;
|
|
82
|
-
customScripts?: CustomScript[];
|
|
83
|
-
}>;
|
|
66
|
+
declare function validateOpts(opts: TxOpts): Readonly<TxOpts>;
|
|
84
67
|
export declare class Transaction {
|
|
85
68
|
private global;
|
|
86
69
|
private inputs;
|
|
@@ -89,7 +72,7 @@ export declare class Transaction {
|
|
|
89
72
|
constructor(opts?: TxOpts);
|
|
90
73
|
static fromRaw(raw: Bytes, opts?: TxOpts): Transaction;
|
|
91
74
|
static fromPSBT(psbt_: Bytes, opts?: TxOpts): Transaction;
|
|
92
|
-
toPSBT(PSBTVersion?: number): Uint8Array;
|
|
75
|
+
toPSBT(PSBTVersion?: number | undefined): Uint8Array;
|
|
93
76
|
get lockTime(): number;
|
|
94
77
|
get version(): number;
|
|
95
78
|
private inputStatus;
|
|
@@ -105,112 +88,18 @@ export declare class Transaction {
|
|
|
105
88
|
get hash(): string;
|
|
106
89
|
get id(): string;
|
|
107
90
|
private checkInputIdx;
|
|
108
|
-
getInput(idx: number): psbt.
|
|
109
|
-
readonly nonWitnessUtxo: readonly [0, false, P.CoderType<P.StructInput<{
|
|
110
|
-
version: number;
|
|
111
|
-
segwitFlag: boolean | undefined;
|
|
112
|
-
inputs: P.StructInput<{
|
|
113
|
-
txid: any;
|
|
114
|
-
index: any;
|
|
115
|
-
finalScriptSig: any;
|
|
116
|
-
sequence: any;
|
|
117
|
-
}>[];
|
|
118
|
-
outputs: P.StructInput<{
|
|
119
|
-
amount: any;
|
|
120
|
-
script: any;
|
|
121
|
-
}>[];
|
|
122
|
-
witnesses: P.Option<Uint8Array[][]>;
|
|
123
|
-
lockTime: number;
|
|
124
|
-
}>>, readonly [], readonly [0, 2], false];
|
|
125
|
-
readonly witnessUtxo: readonly [1, false, P.CoderType<P.StructInput<{
|
|
126
|
-
amount: bigint;
|
|
127
|
-
script: Uint8Array;
|
|
128
|
-
}>>, readonly [], readonly [0, 2], false];
|
|
129
|
-
readonly partialSig: readonly [2, P.CoderType<Uint8Array>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
130
|
-
readonly sighashType: readonly [3, false, P.CoderType<number>, readonly [], readonly [0, 2], false];
|
|
131
|
-
readonly redeemScript: readonly [4, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
132
|
-
readonly witnessScript: readonly [5, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
133
|
-
readonly bip32Derivation: readonly [6, P.CoderType<Uint8Array>, P.CoderType<P.StructInput<{
|
|
134
|
-
fingerprint: number;
|
|
135
|
-
path: number[];
|
|
136
|
-
}>>, readonly [], readonly [0, 2], false];
|
|
137
|
-
readonly finalScriptSig: readonly [7, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
138
|
-
readonly finalScriptWitness: readonly [8, false, P.CoderType<Uint8Array[]>, readonly [], readonly [0, 2], false];
|
|
139
|
-
readonly porCommitment: readonly [9, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
140
|
-
readonly ripemd160: readonly [10, P.CoderType<Uint8Array>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
141
|
-
readonly sha256: readonly [11, P.CoderType<Uint8Array>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
142
|
-
readonly hash160: readonly [12, P.CoderType<Uint8Array>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
143
|
-
readonly hash256: readonly [13, P.CoderType<Uint8Array>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
144
|
-
readonly txid: readonly [14, false, P.CoderType<Uint8Array>, readonly [2], readonly [2], true];
|
|
145
|
-
readonly index: readonly [15, false, P.CoderType<number>, readonly [2], readonly [2], true];
|
|
146
|
-
readonly sequence: readonly [16, false, P.CoderType<number>, readonly [], readonly [2], true];
|
|
147
|
-
readonly requiredTimeLocktime: readonly [17, false, P.CoderType<number>, readonly [], readonly [2], false];
|
|
148
|
-
readonly requiredHeightLocktime: readonly [18, false, P.CoderType<number>, readonly [], readonly [2], false];
|
|
149
|
-
readonly tapKeySig: readonly [19, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
150
|
-
readonly tapScriptSig: readonly [20, P.CoderType<P.StructInput<{
|
|
151
|
-
pubKey: Uint8Array;
|
|
152
|
-
leafHash: Uint8Array;
|
|
153
|
-
}>>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
154
|
-
readonly tapLeafScript: readonly [21, P.CoderType<P.StructInput<{
|
|
155
|
-
version: number;
|
|
156
|
-
internalKey: Uint8Array;
|
|
157
|
-
merklePath: Uint8Array[];
|
|
158
|
-
}>>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
159
|
-
readonly tapBip32Derivation: readonly [22, P.CoderType<Uint8Array>, P.CoderType<P.StructInput<{
|
|
160
|
-
hashes: Uint8Array[];
|
|
161
|
-
der: P.StructInput<{
|
|
162
|
-
fingerprint: any;
|
|
163
|
-
path: any;
|
|
164
|
-
}>;
|
|
165
|
-
}>>, readonly [], readonly [0, 2], false];
|
|
166
|
-
readonly tapInternalKey: readonly [23, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
167
|
-
readonly tapMerkleRoot: readonly [24, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
168
|
-
readonly proprietary: readonly [252, P.CoderType<Uint8Array>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
169
|
-
}>;
|
|
91
|
+
getInput(idx: number): psbt.TransactionInput;
|
|
170
92
|
get inputsLength(): number;
|
|
171
93
|
addInput(input: psbt.TransactionInputUpdate, _ignoreSignStatus?: boolean): number;
|
|
172
94
|
updateInput(idx: number, input: psbt.TransactionInputUpdate, _ignoreSignStatus?: boolean): void;
|
|
173
95
|
private checkOutputIdx;
|
|
174
|
-
getOutput(idx: number): psbt.
|
|
175
|
-
|
|
176
|
-
readonly witnessScript: readonly [1, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
177
|
-
readonly bip32Derivation: readonly [2, P.CoderType<Uint8Array>, P.CoderType<P.StructInput<{
|
|
178
|
-
fingerprint: number;
|
|
179
|
-
path: number[];
|
|
180
|
-
}>>, readonly [], readonly [0, 2], false];
|
|
181
|
-
readonly amount: readonly [3, false, P.CoderType<bigint>, readonly [2], readonly [2], true];
|
|
182
|
-
readonly script: readonly [4, false, P.CoderType<Uint8Array>, readonly [2], readonly [2], true];
|
|
183
|
-
readonly tapInternalKey: readonly [5, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
184
|
-
readonly tapTree: readonly [6, false, P.CoderType<P.StructInput<{
|
|
185
|
-
depth: number;
|
|
186
|
-
version: number;
|
|
187
|
-
script: Uint8Array;
|
|
188
|
-
}>[]>, readonly [], readonly [0, 2], false];
|
|
189
|
-
readonly tapBip32Derivation: readonly [7, P.CoderType<Uint8Array>, P.CoderType<P.StructInput<{
|
|
190
|
-
hashes: Uint8Array[];
|
|
191
|
-
der: P.StructInput<{
|
|
192
|
-
fingerprint: any;
|
|
193
|
-
path: any;
|
|
194
|
-
}>;
|
|
195
|
-
}>>, readonly [], readonly [0, 2], false];
|
|
196
|
-
readonly proprietary: readonly [252, P.CoderType<Uint8Array>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
|
|
197
|
-
}>;
|
|
198
|
-
getOutputAddress(idx: number, network?: {
|
|
199
|
-
bech32: string;
|
|
200
|
-
pubKeyHash: number;
|
|
201
|
-
scriptHash: number;
|
|
202
|
-
wif: number;
|
|
203
|
-
}): string | undefined;
|
|
96
|
+
getOutput(idx: number): psbt.TransactionOutput;
|
|
97
|
+
getOutputAddress(idx: number, network?: u.BTC_NETWORK): string | undefined;
|
|
204
98
|
get outputsLength(): number;
|
|
205
99
|
private normalizeOutput;
|
|
206
100
|
addOutput(o: psbt.TransactionOutputUpdate, _ignoreSignStatus?: boolean): number;
|
|
207
101
|
updateOutput(idx: number, output: psbt.TransactionOutputUpdate, _ignoreSignStatus?: boolean): void;
|
|
208
|
-
addOutputAddress(address: string, amount: bigint, network?:
|
|
209
|
-
bech32: string;
|
|
210
|
-
pubKeyHash: number;
|
|
211
|
-
scriptHash: number;
|
|
212
|
-
wif: number;
|
|
213
|
-
}): number;
|
|
102
|
+
addOutputAddress(address: string, amount: bigint, network?: u.BTC_NETWORK): number;
|
|
214
103
|
get fee(): bigint;
|
|
215
104
|
private preimageLegacy;
|
|
216
105
|
preimageWitnessV0(idx: number, prevOutScript: Bytes, hashType: number, amount: bigint): Uint8Array;
|