@scure/btc-signer 2.2.0 → 2.4.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 +196 -37
- package/index.d.ts +4 -4
- package/index.js +3 -4
- package/musig2.d.ts +28 -9
- package/musig2.js +46 -18
- package/net.d.ts +355 -0
- package/net.js +880 -0
- package/p2p.d.ts +0 -1
- package/p2p.js +23 -7
- package/package.json +18 -21
- package/payment.d.ts +18 -11
- package/payment.js +203 -58
- package/psbt.d.ts +680 -10
- package/psbt.js +204 -43
- package/script.d.ts +1 -2
- package/script.js +71 -59
- package/src/_type_test.ts +83 -0
- package/src/index.ts +4 -2
- package/src/musig2.ts +71 -19
- package/src/net.ts +1111 -0
- package/src/p2p.ts +22 -6
- package/src/payment.ts +233 -70
- package/src/psbt.ts +228 -39
- package/src/script.ts +57 -35
- package/src/transaction.ts +869 -168
- package/src/utils.ts +92 -4
- package/src/utxo.ts +223 -113
- package/transaction.d.ts +40 -7
- package/transaction.js +745 -151
- package/utils.d.ts +45 -2
- package/utils.js +80 -5
- package/utxo.d.ts +192 -2
- package/utxo.js +200 -99
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/musig2.d.ts.map +0 -1
- package/musig2.js.map +0 -1
- package/p2p.d.ts.map +0 -1
- package/p2p.js.map +0 -1
- package/payment.d.ts.map +0 -1
- package/payment.js.map +0 -1
- package/psbt.d.ts.map +0 -1
- package/psbt.js.map +0 -1
- package/script.d.ts.map +0 -1
- package/script.js.map +0 -1
- package/transaction.d.ts.map +0 -1
- package/transaction.js.map +0 -1
- package/utils.d.ts.map +0 -1
- package/utils.js.map +0 -1
- package/utxo.d.ts.map +0 -1
- package/utxo.js.map +0 -1
package/psbt.js
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import { hex } from '@scure/base';
|
|
2
|
+
import { anumber, concatBytes } from '@noble/hashes/utils.js';
|
|
2
3
|
import * as P from 'micro-packed';
|
|
3
4
|
import { CompactSize, CompactSizeLen, RawOldTx, RawOutput, RawTx, RawWitness, VarBytes, } from "./script.js";
|
|
4
|
-
import { compareBytes, equalBytes, PubT, validatePubkey, } from "./utils.js";
|
|
5
|
-
|
|
5
|
+
import { aarray, compareBytes, equalBytes, PubT, validateObject, validatePubkey, } from "./utils.js";
|
|
6
|
+
const unknowns = (mode, name) => {
|
|
7
|
+
if (mode === true)
|
|
8
|
+
return 'ignore';
|
|
9
|
+
if (mode === false)
|
|
10
|
+
return 'strip';
|
|
11
|
+
if (mode === 'ignore' || mode === 'strip' || mode === 'strict')
|
|
12
|
+
return mode;
|
|
13
|
+
throw new Error(`PSBT: invalid ${name} policy=${mode}`);
|
|
14
|
+
};
|
|
15
|
+
// Be friendly to bad ECMAScript parsers by not using bigint literals.
|
|
16
|
+
// prettier-ignore
|
|
17
|
+
const _0n = /* @__PURE__ */ BigInt(0), _1n = /* @__PURE__ */ BigInt(1);
|
|
6
18
|
// BIP174 keydata only says "public key", so legacy PSBT ECDSA fields still accept both
|
|
7
19
|
// compressed (33-byte) and uncompressed (65-byte) SEC1 encodings, but not x-only keys.
|
|
8
20
|
const PubKeyECDSA = /* @__PURE__ */ (() => P.validate(P.bytes(null), (pub) => validatePubkey(pub, PubT.ecdsa)))();
|
|
@@ -121,10 +133,10 @@ const tapTree = /* @__PURE__ */ (() => P.validate(P.array(null, P.struct({
|
|
|
121
133
|
next.push(0);
|
|
122
134
|
path = next;
|
|
123
135
|
}
|
|
124
|
-
let leaves =
|
|
136
|
+
let leaves = _0n;
|
|
125
137
|
for (let i = 0; i < tree.length; i++)
|
|
126
|
-
leaves +=
|
|
127
|
-
if (leaves !==
|
|
138
|
+
leaves += _1n << BigInt(maxDepth - tree[i].depth);
|
|
139
|
+
if (leaves !== _1n << BigInt(maxDepth))
|
|
128
140
|
throw new Error('tapTree: tuples must describe a complete binary tree');
|
|
129
141
|
return tree;
|
|
130
142
|
}))();
|
|
@@ -132,11 +144,58 @@ const tapTree = /* @__PURE__ */ (() => P.validate(P.array(null, P.struct({
|
|
|
132
144
|
// field-specific structure and length checks still live at the individual field definitions.
|
|
133
145
|
// Keep a distinct name here so the byte coder does not collide with the Bytes type alias.
|
|
134
146
|
const BytesInf = /* @__PURE__ */ P.bytes(null);
|
|
147
|
+
// PSBTKeyMap emits the 0xfc type byte itself, so the public value remains only the bytes after it.
|
|
148
|
+
// Validate that raw suffix here: otherwise a caller-supplied leading 0xfc becomes a 252-byte
|
|
149
|
+
// identifier declaration which scure can relay but Bitcoin Core cannot parse.
|
|
150
|
+
const ProprietaryKey = /* @__PURE__ */ (() => {
|
|
151
|
+
const suffix = P.struct({
|
|
152
|
+
identifier: P.bytes(CompactSizeLen),
|
|
153
|
+
subtype: CompactSizeLen,
|
|
154
|
+
data: BytesInf,
|
|
155
|
+
});
|
|
156
|
+
return P.validate(BytesInf, (key) => {
|
|
157
|
+
try {
|
|
158
|
+
suffix.decode(key);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
162
|
+
throw new Error(`Proprietary key: expected BIP174 identifier and subtype, got ${message}`);
|
|
163
|
+
}
|
|
164
|
+
return key;
|
|
165
|
+
});
|
|
166
|
+
})();
|
|
135
167
|
// Shared 20-byte key-data helper for the BIP174 RIPEMD160 and HASH160 preimage maps.
|
|
136
168
|
const Bytes20 = /* @__PURE__ */ P.bytes(20);
|
|
137
169
|
// Shared 32-byte helper for fixed-size hash / txid / merkle-root byte fields; any stronger
|
|
138
170
|
// semantics such as x-only pubkey validity still need to be enforced by the field that uses it.
|
|
139
171
|
const Bytes32 = /* @__PURE__ */ P.bytes(32);
|
|
172
|
+
const Bytes64 = /* @__PURE__ */ P.bytes(64);
|
|
173
|
+
const Bytes66 = /* @__PURE__ */ P.bytes(66);
|
|
174
|
+
// BIP373 uses the same aggregate-key participant list in input and output maps.
|
|
175
|
+
const MuSig2Participants = /* @__PURE__ */ (() => P.array(null, PubKeyECDSACompressed))();
|
|
176
|
+
// BIP373 keys append an optional tapleaf hash to participant || aggregate. A structured key keeps
|
|
177
|
+
// the optional hash distinct while preserving the exact 66/98-byte wire encoding.
|
|
178
|
+
const MuSig2Key = /* @__PURE__ */ (() => P.apply(BytesInf, {
|
|
179
|
+
decode: (key) => {
|
|
180
|
+
const _key = key;
|
|
181
|
+
return concatBytes(PubKeyECDSACompressed.encode(_key.participantPubkey), PubKeyECDSACompressed.encode(_key.aggregatePubkey), _key.leafHash === undefined ? P.EMPTY : Bytes32.encode(_key.leafHash));
|
|
182
|
+
},
|
|
183
|
+
encode: (raw) => {
|
|
184
|
+
const _raw = raw;
|
|
185
|
+
if (_raw.length !== 66 && _raw.length !== 98)
|
|
186
|
+
throw new Error(`MuSig2 key: expected 66 or 98 bytes, got ${_raw.length}`);
|
|
187
|
+
const key = {
|
|
188
|
+
participantPubkey: PubKeyECDSACompressed.decode(_raw.subarray(0, 33)),
|
|
189
|
+
aggregatePubkey: PubKeyECDSACompressed.decode(_raw.subarray(33, 66)),
|
|
190
|
+
};
|
|
191
|
+
if (_raw.length === 98)
|
|
192
|
+
key.leafHash = Bytes32.decode(_raw.subarray(66));
|
|
193
|
+
return key;
|
|
194
|
+
},
|
|
195
|
+
}))();
|
|
196
|
+
const SilentPaymentInfo = /* @__PURE__ */ (() => P.struct({ scanKey: PubKeyECDSACompressed, spendKey: PubKeyECDSACompressed }))();
|
|
197
|
+
// BIP353 prefixes the human-readable name with one byte; the RFC9102 proof is opaque here.
|
|
198
|
+
const DNSSECProof = /* @__PURE__ */ (() => P.struct({ name: P.bytes(P.U8), proof: BytesInf }))();
|
|
140
199
|
// jsbt mutate checks exported PSBT tables recursively, so freeze each field tuple and its
|
|
141
200
|
// nested version arrays here while preserving the original coder slot types for local inference.
|
|
142
201
|
const PSBTInfo = (type, kc, vc, reqInc, allowInc, silentIgnore) =>
|
|
@@ -175,8 +234,12 @@ export const PSBTGlobal = /* @__PURE__ */ (() => Object.freeze({
|
|
|
175
234
|
outputCount: PSBTInfo(0x05, false, CompactSizeLen, [2], [2], false),
|
|
176
235
|
// TODO: bitfield
|
|
177
236
|
txModifiable: PSBTInfo(0x06, false, P.U8, [], [2], false),
|
|
237
|
+
// These assigned extension fields must not fall through the unknown-field policy.
|
|
238
|
+
spEcdhShare: PSBTInfo(0x07, PubKeyECDSACompressed, PubKeyECDSACompressed, [], [2], false),
|
|
239
|
+
spDleq: PSBTInfo(0x08, PubKeyECDSACompressed, Bytes64, [], [2], false),
|
|
240
|
+
genericSignedMessage: PSBTInfo(0x09, false, BytesInf, [], [0, 2], false),
|
|
178
241
|
version: PSBTInfo(0xfb, false, P.U32LE, [], [0, 2], false),
|
|
179
|
-
proprietary: PSBTInfo(0xfc,
|
|
242
|
+
proprietary: PSBTInfo(0xfc, ProprietaryKey, BytesInf, [], [0, 2], false),
|
|
180
243
|
}))();
|
|
181
244
|
// prettier-ignore
|
|
182
245
|
/**
|
|
@@ -224,7 +287,15 @@ export const PSBTInput = /* @__PURE__ */ (() => Object.freeze({
|
|
|
224
287
|
tapBip32Derivation: PSBTInfo(0x16, PubKeySchnorr, TaprootBIP32Der, [], [0, 2], false),
|
|
225
288
|
tapInternalKey: PSBTInfo(0x17, false, PubKeySchnorr, [], [0, 2], false),
|
|
226
289
|
tapMerkleRoot: PSBTInfo(0x18, false, Bytes32, [], [0, 2], false),
|
|
227
|
-
|
|
290
|
+
p2cKeyTweak: PSBTInfo(0x19, PubKeyECDSACompressed, Bytes32, [], [0, 2], false),
|
|
291
|
+
musig2ParticipantPubkeys: PSBTInfo(0x1a, PubKeyECDSACompressed, MuSig2Participants, [], [0, 2], false),
|
|
292
|
+
musig2PubNonce: PSBTInfo(0x1b, MuSig2Key, Bytes66, [], [0, 2], false),
|
|
293
|
+
musig2PartialSig: PSBTInfo(0x1c, MuSig2Key, Bytes32, [], [0, 2], false),
|
|
294
|
+
spEcdhShare: PSBTInfo(0x1d, PubKeyECDSACompressed, PubKeyECDSACompressed, [], [2], false),
|
|
295
|
+
spDleq: PSBTInfo(0x1e, PubKeyECDSACompressed, Bytes64, [], [2], false),
|
|
296
|
+
spSpendBip32Derivation: PSBTInfo(0x1f, PubKeyECDSACompressed, BIP32Der, [], [2], false),
|
|
297
|
+
spTweak: PSBTInfo(0x20, false, Bytes32, [], [2], false),
|
|
298
|
+
proprietary: PSBTInfo(0xfc, ProprietaryKey, BytesInf, [], [0, 2], false),
|
|
228
299
|
}))();
|
|
229
300
|
// All other keys removed when finalizing
|
|
230
301
|
/**
|
|
@@ -239,37 +310,43 @@ export const PSBTInput = /* @__PURE__ */ (() => Object.freeze({
|
|
|
239
310
|
*/
|
|
240
311
|
export const PSBTInputFinalKeys = /* @__PURE__ */ Object.freeze([
|
|
241
312
|
// PSBTv2 extractors rebuild the final transaction from per-input fields, so
|
|
242
|
-
// finalized inputs still need txid/index
|
|
313
|
+
// finalized inputs still need txid/index, any non-default sequence, and locktime requirements
|
|
243
314
|
// even though BIP174's generic cleanup is stricter.
|
|
244
315
|
'txid',
|
|
245
316
|
'sequence',
|
|
246
317
|
'index',
|
|
247
318
|
'witnessUtxo',
|
|
248
319
|
'nonWitnessUtxo',
|
|
320
|
+
'requiredTimeLocktime',
|
|
321
|
+
'requiredHeightLocktime',
|
|
249
322
|
'finalScriptSig',
|
|
250
323
|
'finalScriptWitness',
|
|
251
324
|
'unknown',
|
|
252
325
|
]);
|
|
253
|
-
// Can be modified even on signed input
|
|
254
326
|
/**
|
|
255
|
-
*
|
|
327
|
+
* Signature and final-satisfaction fields used while reopening a finalized input.
|
|
256
328
|
* @example
|
|
257
|
-
*
|
|
329
|
+
* Finalized inputs may remove their existing satisfaction before further mutation.
|
|
258
330
|
* ```ts
|
|
259
|
-
* import {
|
|
260
|
-
* const mutableKeys = new Set(
|
|
331
|
+
* import { PSBTInputSignatureKeys } from '@scure/btc-signer/psbt.js';
|
|
332
|
+
* const mutableKeys = new Set(PSBTInputSignatureKeys);
|
|
261
333
|
* mutableKeys.has('tapScriptSig');
|
|
262
334
|
* ```
|
|
263
335
|
*/
|
|
264
|
-
export const
|
|
265
|
-
// This is the replace/remove allowlist for signed inputs; mergeKeyMap() can still append
|
|
266
|
-
// previously absent metadata or new KV entries for other fields when they don't conflict.
|
|
336
|
+
export const PSBTInputSignatureKeys = /* @__PURE__ */ Object.freeze([
|
|
267
337
|
'partialSig',
|
|
268
338
|
'finalScriptSig',
|
|
269
339
|
'finalScriptWitness',
|
|
270
340
|
'tapKeySig',
|
|
271
341
|
'tapScriptSig',
|
|
272
342
|
]);
|
|
343
|
+
/**
|
|
344
|
+
* A static list cannot describe mutable input fields because that depends on each signature's
|
|
345
|
+
* algorithm, sighash, and target index.
|
|
346
|
+
* @deprecated Use {@link PSBTInputSignatureKeys} only for signature/final-satisfaction records;
|
|
347
|
+
* transaction mutation is enforced by {@link Transaction.updateInput}.
|
|
348
|
+
*/
|
|
349
|
+
export const PSBTInputUnsignedKeys = PSBTInputSignatureKeys;
|
|
273
350
|
// prettier-ignore
|
|
274
351
|
/**
|
|
275
352
|
* PSBT output key definitions.
|
|
@@ -293,7 +370,11 @@ export const PSBTOutput = /* @__PURE__ */ (() => Object.freeze({
|
|
|
293
370
|
// reconstruct the same Taproot tree, not just an arbitrary list of serialized leaves.
|
|
294
371
|
tapTree: PSBTInfo(0x06, false, tapTree, [], [0, 2], false),
|
|
295
372
|
tapBip32Derivation: PSBTInfo(0x07, PubKeySchnorr, TaprootBIP32Der, [], [0, 2], false),
|
|
296
|
-
|
|
373
|
+
musig2ParticipantPubkeys: PSBTInfo(0x08, PubKeyECDSACompressed, MuSig2Participants, [], [0, 2], false),
|
|
374
|
+
spV0Info: PSBTInfo(0x09, false, SilentPaymentInfo, [], [2], false),
|
|
375
|
+
spV0Label: PSBTInfo(0x0a, false, P.U32LE, [], [2], false),
|
|
376
|
+
dnssecProof: PSBTInfo(0x35, false, DNSSECProof, [], [0, 2], false),
|
|
377
|
+
proprietary: PSBTInfo(0xfc, ProprietaryKey, BytesInf, [], [0, 2], false),
|
|
297
378
|
}))();
|
|
298
379
|
// Can be modified even on signed input
|
|
299
380
|
/**
|
|
@@ -486,7 +567,7 @@ export const PSBTOutputCoder = /* @__PURE__ */ (() => Object.freeze(P.validate(P
|
|
|
486
567
|
// in the field coder itself because BIP371 constrains the tuple value, not just the row shape.
|
|
487
568
|
// BIP174/BIP370 define PSBT_OUT_AMOUNT as a signed int64 transport field, but it still
|
|
488
569
|
// represents the transaction output amount in satoshis, so negative output values are invalid.
|
|
489
|
-
if (o.amount !== undefined && o.amount <
|
|
570
|
+
if (o.amount !== undefined && o.amount < _0n)
|
|
490
571
|
throw new Error(`validateOutput: wrong amount=${o.amount}`);
|
|
491
572
|
if (o.bip32Derivation)
|
|
492
573
|
for (const [k] of o.bip32Derivation)
|
|
@@ -574,6 +655,9 @@ function validatePSBTFields(version, info, lst) {
|
|
|
574
655
|
* ```
|
|
575
656
|
*/
|
|
576
657
|
export function cleanPSBTFields(version, info, lst) {
|
|
658
|
+
anumber(version, 'version');
|
|
659
|
+
validateObject(info, {}, {}, 'info');
|
|
660
|
+
validateObject(lst, {}, {}, 'lst');
|
|
577
661
|
const _lst = lst;
|
|
578
662
|
const out = {};
|
|
579
663
|
for (const _k in _lst) {
|
|
@@ -603,22 +687,27 @@ function validatePSBT(tx) {
|
|
|
603
687
|
validatePSBTFields(version, PSBTOutput, o);
|
|
604
688
|
// BIP174 defines `<psbt> := <magic> <global-map> <input-map>* <output-map>*`, so after decode the
|
|
605
689
|
// number of input/output maps should match the unsigned tx. PSBTv2 makes the same shape explicit
|
|
606
|
-
// through `inputCount` / `outputCount`.
|
|
607
|
-
// keep accepting exactly one trailing empty map because the separate bitcoinjs compatibility PSBT
|
|
608
|
-
// fixture corpus still contains that encoding. Anything non-empty or more than one extra map is
|
|
609
|
-
// still rejected here.
|
|
690
|
+
// through `inputCount` / `outputCount`. Input maps are count-framed and must match exactly.
|
|
610
691
|
const inputCount = !version ? tx.global.unsignedTx.inputs.length : tx.global.inputCount;
|
|
611
|
-
if (tx.inputs.length
|
|
612
|
-
throw new Error(
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
//
|
|
692
|
+
if (tx.inputs.length !== inputCount)
|
|
693
|
+
throw new Error(`Wrong number of input maps=${tx.inputs.length}, expected=${inputCount}`);
|
|
694
|
+
// PSBTv0 compatibility mode may append exactly one empty output map when the unsigned
|
|
695
|
+
// transaction has no outputs. bip174js additionally inserts an empty input map when there are
|
|
696
|
+
// no inputs; count-framing makes that map appear before the real output maps in this array.
|
|
697
|
+
// PSBTv2 map counts remain strict.
|
|
617
698
|
const outputCount = !version ? tx.global.unsignedTx.outputs.length : tx.global.outputCount;
|
|
618
699
|
if (tx.outputs.length < outputCount)
|
|
619
700
|
throw new Error('Not outputs inputs');
|
|
701
|
+
const hasBip174InputMap = version === 0 &&
|
|
702
|
+
inputCount === 0 &&
|
|
703
|
+
Object.keys(tx.outputs[0] || {}).length === 0 &&
|
|
704
|
+
((outputCount > 0 && tx.outputs.length === outputCount + 1) ||
|
|
705
|
+
(outputCount === 0 && tx.outputs.length === 2 && Object.keys(tx.outputs[1]).length === 0));
|
|
706
|
+
if (hasBip174InputMap)
|
|
707
|
+
return tx;
|
|
620
708
|
const outputsLeft = tx.outputs.slice(outputCount);
|
|
621
|
-
if (outputsLeft.length > 1 ||
|
|
709
|
+
if (outputsLeft.length > 1 ||
|
|
710
|
+
(outputsLeft.length && (version !== 0 || Object.keys(outputsLeft[0]).length)))
|
|
622
711
|
throw new Error(`Unexpected outputs left in tx=${outputsLeft}`);
|
|
623
712
|
return tx;
|
|
624
713
|
}
|
|
@@ -628,7 +717,8 @@ function validatePSBT(tx) {
|
|
|
628
717
|
* @param val - new values to merge in
|
|
629
718
|
* @param cur - existing decoded PSBT key map
|
|
630
719
|
* @param allowedFields - fields still allowed to change
|
|
631
|
-
* @param
|
|
720
|
+
* @param unknown - handling policy for unknown PSBT fields
|
|
721
|
+
* @param proprietary - handling policy for proprietary PSBT fields
|
|
632
722
|
* @returns Merged PSBT key map.
|
|
633
723
|
* @throws If keyed PSBT fields conflict or signed fields would be removed. {@link Error}
|
|
634
724
|
* @example
|
|
@@ -646,14 +736,34 @@ function validatePSBT(tx) {
|
|
|
646
736
|
* );
|
|
647
737
|
* ```
|
|
648
738
|
*/
|
|
649
|
-
export function mergeKeyMap(psbtEnum, val, cur, allowedFields,
|
|
739
|
+
export function mergeKeyMap(psbtEnum, val, cur, allowedFields, unknown = 'strip', proprietary = 'strip') {
|
|
740
|
+
validateObject(psbtEnum, {}, {}, 'psbtEnum');
|
|
741
|
+
validateObject(val, {}, {}, 'val');
|
|
742
|
+
if (cur !== undefined)
|
|
743
|
+
validateObject(cur, {}, {}, 'cur');
|
|
744
|
+
if (allowedFields !== undefined)
|
|
745
|
+
aarray(allowedFields, 'allowedFields');
|
|
650
746
|
const _val = val;
|
|
651
747
|
const _cur = cur;
|
|
652
748
|
const _allowedFields = allowedFields;
|
|
749
|
+
const unknownMode = unknowns(unknown, 'unknown');
|
|
750
|
+
const proprietaryMode = unknowns(proprietary, 'proprietary');
|
|
751
|
+
for (const [name, mode] of [
|
|
752
|
+
['unknown', unknownMode],
|
|
753
|
+
['proprietary', proprietaryMode],
|
|
754
|
+
]) {
|
|
755
|
+
if (mode !== 'strict')
|
|
756
|
+
continue;
|
|
757
|
+
if (_val[name]?.length ||
|
|
758
|
+
_cur?.[name]?.length)
|
|
759
|
+
throw new Error(`PSBT: ${name} PSBT field is not allowed in strict mode`);
|
|
760
|
+
}
|
|
653
761
|
const res = { ..._cur, ..._val };
|
|
654
762
|
// All arguments can be provided as hex
|
|
655
763
|
for (const k in psbtEnum) {
|
|
656
764
|
const key = k;
|
|
765
|
+
if (k === 'proprietary' && proprietaryMode !== 'ignore')
|
|
766
|
+
continue;
|
|
657
767
|
const [_, kC, vC] = psbtEnum[key];
|
|
658
768
|
const cannotChange = _allowedFields && !_allowedFields.includes(k);
|
|
659
769
|
if (_val[k] === undefined && k in _val) {
|
|
@@ -699,22 +809,31 @@ export function mergeKeyMap(psbtEnum, val, cur, allowedFields, allowUnknown) {
|
|
|
699
809
|
throw new Error(`Cannot remove signed field=${key}/${k}`);
|
|
700
810
|
delete map[kStr];
|
|
701
811
|
}
|
|
702
|
-
else
|
|
812
|
+
else {
|
|
813
|
+
if (cannotChange && map[kStr] === undefined)
|
|
814
|
+
throw new Error(`Cannot add signed field=${key}/${kStr}`);
|
|
703
815
|
add(kStr, k, v);
|
|
816
|
+
}
|
|
704
817
|
}
|
|
705
818
|
res[key] = Object.values(map);
|
|
706
819
|
}
|
|
707
820
|
}
|
|
708
|
-
else
|
|
709
|
-
res[k]
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
821
|
+
else {
|
|
822
|
+
if (typeof res[k] === 'string')
|
|
823
|
+
res[k] = vC.decode(hex.decode(res[k]));
|
|
824
|
+
if (cannotChange && k in _val) {
|
|
825
|
+
if (!_cur || _cur[k] === undefined)
|
|
826
|
+
throw new Error(`Cannot add signed field=${k}`);
|
|
827
|
+
let current = _cur[k];
|
|
828
|
+
if (typeof current === 'string')
|
|
829
|
+
current = vC.decode(hex.decode(current));
|
|
830
|
+
if (!equalBytes(vC.encode(res[k]), vC.encode(current)))
|
|
831
|
+
throw new Error(`Cannot change signed field=${k}`);
|
|
832
|
+
}
|
|
714
833
|
}
|
|
715
834
|
}
|
|
716
|
-
if (
|
|
717
|
-
// Unknown PSBT rows are stripped by default here, but explicit
|
|
835
|
+
if (unknownMode === 'ignore' && _val.unknown) {
|
|
836
|
+
// Unknown PSBT rows are stripped by default here, but explicit ignore mode is pass-through.
|
|
718
837
|
// Merge them by full serialized unknown key so repeated updates do not clobber earlier opaque rows.
|
|
719
838
|
const map = {};
|
|
720
839
|
for (const [k, v] of _cur?.unknown || [])
|
|
@@ -732,16 +851,59 @@ export function mergeKeyMap(psbtEnum, val, cur, allowedFields, allowUnknown) {
|
|
|
732
851
|
}
|
|
733
852
|
res.unknown = Object.values(map);
|
|
734
853
|
}
|
|
735
|
-
// Remove
|
|
854
|
+
// Remove properties outside the table, except opaque rows in explicit ignore mode.
|
|
736
855
|
for (const k in res) {
|
|
737
856
|
if (!psbtEnum[k]) {
|
|
738
|
-
if (
|
|
857
|
+
if (unknownMode === 'ignore' && k === 'unknown')
|
|
739
858
|
continue;
|
|
740
859
|
delete res[k];
|
|
741
860
|
}
|
|
742
861
|
}
|
|
862
|
+
if (unknownMode !== 'ignore')
|
|
863
|
+
delete res.unknown;
|
|
864
|
+
if (proprietaryMode !== 'ignore')
|
|
865
|
+
delete res.proprietary;
|
|
743
866
|
return res;
|
|
744
867
|
}
|
|
868
|
+
/**
|
|
869
|
+
* Combines two independently produced PSBT maps without choosing between conflicting scalar
|
|
870
|
+
* values. Keyed fields retain {@link mergeKeyMap}'s union semantics.
|
|
871
|
+
* @param psbtEnum - PSBT field definition table
|
|
872
|
+
* @param current - first map
|
|
873
|
+
* @param other - second map
|
|
874
|
+
* @param unknown - handling policy for unknown PSBT fields
|
|
875
|
+
* @param proprietary - handling policy for proprietary PSBT fields
|
|
876
|
+
* @returns The symmetric map union.
|
|
877
|
+
* @throws If both maps provide different values for the same scalar field. {@link Error}
|
|
878
|
+
* @example
|
|
879
|
+
* Combine disjoint global metadata without choosing an operand as authoritative.
|
|
880
|
+
* ```ts
|
|
881
|
+
* import { combineKeyMap, PSBTGlobal } from '@scure/btc-signer/psbt.js';
|
|
882
|
+
* combineKeyMap(PSBTGlobal, { txVersion: 2 }, { fallbackLocktime: 0 });
|
|
883
|
+
* ```
|
|
884
|
+
*/
|
|
885
|
+
export function combineKeyMap(psbtEnum, current, other, unknown = 'strip', proprietary = 'strip') {
|
|
886
|
+
validateObject(psbtEnum, {}, {}, 'psbtEnum');
|
|
887
|
+
validateObject(current, {}, {}, 'current');
|
|
888
|
+
validateObject(other, {}, {}, 'other');
|
|
889
|
+
const _current = current;
|
|
890
|
+
const _other = other;
|
|
891
|
+
for (const k in psbtEnum) {
|
|
892
|
+
const key = k;
|
|
893
|
+
const [_, keyCoder, valueCoder] = psbtEnum[key];
|
|
894
|
+
if (keyCoder || _current[key] === undefined || _other[key] === undefined)
|
|
895
|
+
continue;
|
|
896
|
+
let a = _current[key];
|
|
897
|
+
let b = _other[key];
|
|
898
|
+
if (typeof a === 'string')
|
|
899
|
+
a = valueCoder.decode(hex.decode(a));
|
|
900
|
+
if (typeof b === 'string')
|
|
901
|
+
b = valueCoder.decode(hex.decode(b));
|
|
902
|
+
if (!equalBytes(valueCoder.encode(a), valueCoder.encode(b)))
|
|
903
|
+
throw new Error(`Cannot combine conflicting field=${k}`);
|
|
904
|
+
}
|
|
905
|
+
return mergeKeyMap(psbtEnum, _other, _current, undefined, unknown, proprietary);
|
|
906
|
+
}
|
|
745
907
|
/** Validated PSBTv0 coder. */
|
|
746
908
|
// This wrapper only layers `validatePSBT`'s PSBTv0 field/count reconciliation on top of
|
|
747
909
|
// `_RawPSBTV0`; field-specific payload invariants still depend on the nested coders/tables.
|
|
@@ -750,4 +912,3 @@ export const RawPSBTV0 = /* @__PURE__ */ (() => Object.freeze(P.validate(_RawPSB
|
|
|
750
912
|
// This wrapper only layers `validatePSBT`'s PSBTv2 required-field/count reconciliation on top
|
|
751
913
|
// of `_RawPSBTV2`; nested input/output/global field invariants still depend on the coders below.
|
|
752
914
|
export const RawPSBTV2 = /* @__PURE__ */ (() => Object.freeze(P.validate(_RawPSBTV2, validatePSBT)))();
|
|
753
|
-
//# sourceMappingURL=psbt.js.map
|
package/script.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as P from 'micro-packed';
|
|
2
|
-
import { type
|
|
2
|
+
import { type Bytes, type TArg, type TRet, type ValueOf } from './utils.ts';
|
|
3
3
|
/**
|
|
4
4
|
* Maximum byte size allowed for a single pushed script element.
|
|
5
5
|
* BIP 342 keeps this 520-byte stack-element limit even though tapscript removes
|
|
@@ -351,4 +351,3 @@ export declare const RawOldTx: Readonly<P.CoderType<P.StructInput<{
|
|
|
351
351
|
lockTime: number;
|
|
352
352
|
}>>>;
|
|
353
353
|
export {};
|
|
354
|
-
//# sourceMappingURL=script.d.ts.map
|
package/script.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import * as P from 'micro-packed';
|
|
2
|
-
import { isBytes, reverseObject } from "./utils.js";
|
|
2
|
+
import { aarray, abytes, isBytes, reverseObject, } from "./utils.js";
|
|
3
3
|
/**
|
|
4
4
|
* Maximum byte size allowed for a single pushed script element.
|
|
5
5
|
* BIP 342 keeps this 520-byte stack-element limit even though tapscript removes
|
|
6
6
|
* the old 10,000-byte overall script-size cap.
|
|
7
7
|
*/
|
|
8
8
|
export const MAX_SCRIPT_BYTE_LENGTH = 520;
|
|
9
|
+
// Be friendly to bad ECMAScript parsers by not using bigint literals.
|
|
10
|
+
// prettier-ignore
|
|
11
|
+
const _0n = /* @__PURE__ */ BigInt(0), _1n = /* @__PURE__ */ BigInt(1), _2n = /* @__PURE__ */ BigInt(2), _8n = /* @__PURE__ */ BigInt(8);
|
|
12
|
+
const U8_MAX = /* @__PURE__ */ BigInt(0xff);
|
|
13
|
+
const COMPACT_DIRECT_MAX = /* @__PURE__ */ BigInt(0xfc);
|
|
9
14
|
// prettier-ignore
|
|
10
15
|
/**
|
|
11
16
|
* Bitcoin Script opcode table.
|
|
@@ -75,13 +80,13 @@ export const OPNames = /* @__PURE__ */ (() => Object.freeze(reverseObject(OP)))(
|
|
|
75
80
|
export function ScriptNum(bytesLimit = 6, forceMinimal = false) {
|
|
76
81
|
return P.wrap({
|
|
77
82
|
encodeStream: (w, value) => {
|
|
78
|
-
if (value ===
|
|
83
|
+
if (value === _0n)
|
|
79
84
|
return;
|
|
80
85
|
const neg = value < 0;
|
|
81
86
|
const val = BigInt(value);
|
|
82
87
|
const nums = [];
|
|
83
|
-
for (let abs = neg ? -val : val; abs; abs >>=
|
|
84
|
-
nums.push(Number(abs &
|
|
88
|
+
for (let abs = neg ? -val : val; abs; abs >>= _8n)
|
|
89
|
+
nums.push(Number(abs & U8_MAX));
|
|
85
90
|
if (nums[nums.length - 1] >= 0x80)
|
|
86
91
|
nums.push(neg ? 0x80 : 0);
|
|
87
92
|
else if (neg)
|
|
@@ -93,24 +98,23 @@ export function ScriptNum(bytesLimit = 6, forceMinimal = false) {
|
|
|
93
98
|
if (len > bytesLimit)
|
|
94
99
|
throw new Error(`ScriptNum: number (${len}) bigger than limit=${bytesLimit}`);
|
|
95
100
|
if (len === 0)
|
|
96
|
-
return
|
|
101
|
+
return _0n;
|
|
102
|
+
// Read the payload once instead of peeking for the minimality check and
|
|
103
|
+
// then re-reading it byte-by-byte through the Reader.
|
|
104
|
+
const data = r.bytes(len);
|
|
97
105
|
if (forceMinimal) {
|
|
98
|
-
const data = r.bytes(len, true);
|
|
99
106
|
// MSB is zero (without sign bit) -> not minimally encoded
|
|
100
|
-
if ((data[
|
|
107
|
+
if ((data[len - 1] & 0x7f) === 0) {
|
|
101
108
|
// exception
|
|
102
|
-
if (len <= 1 || (data[
|
|
109
|
+
if (len <= 1 || (data[len - 2] & 0x80) === 0)
|
|
103
110
|
throw new Error('Non-minimally encoded ScriptNum');
|
|
104
111
|
}
|
|
105
112
|
}
|
|
106
|
-
let
|
|
107
|
-
let
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
res
|
|
111
|
-
}
|
|
112
|
-
if (last >= 0x80) {
|
|
113
|
-
res &= (2n ** BigInt(len * 8) - 1n) >> 1n;
|
|
113
|
+
let res = _0n;
|
|
114
|
+
for (let i = 0; i < len; ++i)
|
|
115
|
+
res |= BigInt(data[i]) << (_8n * BigInt(i));
|
|
116
|
+
if (data[len - 1] >= 0x80) {
|
|
117
|
+
res &= (_2n ** BigInt(len * 8) - _1n) >> _1n;
|
|
114
118
|
res = -res;
|
|
115
119
|
}
|
|
116
120
|
return res;
|
|
@@ -137,7 +141,9 @@ export function OpToNum(op, bytesLimit = 4, forceMinimal = true) {
|
|
|
137
141
|
if (isBytes(op)) {
|
|
138
142
|
try {
|
|
139
143
|
const val = ScriptNum(bytesLimit, forceMinimal).decode(op);
|
|
140
|
-
|
|
144
|
+
// Symmetric safe-integer bound: large negative values would otherwise
|
|
145
|
+
// coerce through Number() with silent precision loss.
|
|
146
|
+
if (val > Number.MAX_SAFE_INTEGER || val < -Number.MAX_SAFE_INTEGER)
|
|
141
147
|
return;
|
|
142
148
|
return Number(val);
|
|
143
149
|
}
|
|
@@ -192,11 +198,15 @@ export const scriptPushLen = (op, read) => {
|
|
|
192
198
|
*/
|
|
193
199
|
export const Script = /* @__PURE__ */ (() => Object.freeze(P.wrap({
|
|
194
200
|
encodeStream: (w, value) => {
|
|
201
|
+
aarray(value, 'value');
|
|
195
202
|
for (let o of value) {
|
|
196
203
|
if (typeof o === 'string') {
|
|
197
|
-
|
|
204
|
+
const op = OP[o];
|
|
205
|
+
// OP is a plain object, so inherited Object.prototype keys ('toString',
|
|
206
|
+
// 'constructor', ...) are not opcodes and must be rejected here too.
|
|
207
|
+
if (typeof op !== 'number')
|
|
198
208
|
throw new Error(`Unknown opcode=${o}`);
|
|
199
|
-
w.byte(
|
|
209
|
+
w.byte(op);
|
|
200
210
|
continue;
|
|
201
211
|
}
|
|
202
212
|
else if (typeof o === 'number') {
|
|
@@ -218,8 +228,7 @@ export const Script = /* @__PURE__ */ (() => Object.freeze(P.wrap({
|
|
|
218
228
|
// Encode big numbers
|
|
219
229
|
if (typeof o === 'number')
|
|
220
230
|
o = ScriptNum().encode(BigInt(o));
|
|
221
|
-
|
|
222
|
-
throw new Error(`Wrong Script OP=${o} (${typeof o})`);
|
|
231
|
+
abytes(o, undefined, 'value');
|
|
223
232
|
// Bytes
|
|
224
233
|
const len = o.length;
|
|
225
234
|
if (len < OP.PUSHDATA1)
|
|
@@ -269,13 +278,6 @@ export const Script = /* @__PURE__ */ (() => Object.freeze(P.wrap({
|
|
|
269
278
|
return out;
|
|
270
279
|
},
|
|
271
280
|
})))();
|
|
272
|
-
// BTC specific variable length integer encoding
|
|
273
|
-
// https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
|
|
274
|
-
const CSLimits = {
|
|
275
|
-
0xfd: [0xfd, 2, 253n, 65535n],
|
|
276
|
-
0xfe: [0xfe, 4, 65536n, 4294967295n],
|
|
277
|
-
0xff: [0xff, 8, 4294967296n, 18446744073709551615n],
|
|
278
|
-
};
|
|
279
281
|
/**
|
|
280
282
|
* Bitcoin CompactSize integer coder.
|
|
281
283
|
* @example
|
|
@@ -284,37 +286,48 @@ const CSLimits = {
|
|
|
284
286
|
* CompactSize.encode(1n);
|
|
285
287
|
* ```
|
|
286
288
|
*/
|
|
287
|
-
export const CompactSize = /* @__PURE__ */ (() =>
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
289
|
+
export const CompactSize = /* @__PURE__ */ (() => {
|
|
290
|
+
// BTC specific variable length integer encoding
|
|
291
|
+
// https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
|
|
292
|
+
const limits = {
|
|
293
|
+
0xfd: [0xfd, 2, BigInt(0xfd), BigInt(0xffff)],
|
|
294
|
+
0xfe: [0xfe, 4, BigInt(0x10000), BigInt(0xffffffff)],
|
|
295
|
+
0xff: [0xff, 8, BigInt(0x100000000), BigInt('0xffffffffffffffff')],
|
|
296
|
+
};
|
|
297
|
+
// Hoisted: Object.values() would otherwise allocate a fresh array on every encode.
|
|
298
|
+
const limitsList = Object.values(limits);
|
|
299
|
+
return Object.freeze(P.wrap({
|
|
300
|
+
encodeStream: (w, value) => {
|
|
301
|
+
if (typeof value === 'number')
|
|
302
|
+
value = BigInt(value);
|
|
303
|
+
if (_0n <= value && value <= COMPACT_DIRECT_MAX)
|
|
304
|
+
return w.byte(Number(value));
|
|
305
|
+
for (const [flag, bytes, start, stop] of limitsList) {
|
|
306
|
+
if (start > value || value > stop)
|
|
307
|
+
continue;
|
|
308
|
+
w.byte(flag);
|
|
309
|
+
for (let i = 0; i < bytes; i++)
|
|
310
|
+
w.byte(Number((value >> (_8n * BigInt(i))) & U8_MAX));
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
throw w.err(`VarInt too big: ${value}`);
|
|
314
|
+
},
|
|
315
|
+
decodeStream: (r) => {
|
|
316
|
+
const b0 = r.byte();
|
|
317
|
+
if (b0 <= 0xfc)
|
|
318
|
+
return BigInt(b0);
|
|
319
|
+
const [_, bytes, start] = limits[b0];
|
|
320
|
+
let num = _0n;
|
|
297
321
|
for (let i = 0; i < bytes; i++)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const [_, bytes, start] = CSLimits[b0];
|
|
308
|
-
let num = 0n;
|
|
309
|
-
for (let i = 0; i < bytes; i++)
|
|
310
|
-
num |= BigInt(r.byte()) << (8n * BigInt(i));
|
|
311
|
-
// BIP 152 / BIP 174: CompactSize fields must use the shortest encoding,
|
|
312
|
-
// so wider prefixes for smaller values are rejected here.
|
|
313
|
-
if (num < start)
|
|
314
|
-
throw r.err(`Wrong CompactSize(${8 * bytes})`);
|
|
315
|
-
return num;
|
|
316
|
-
},
|
|
317
|
-
})))();
|
|
322
|
+
num |= BigInt(r.byte()) << (_8n * BigInt(i));
|
|
323
|
+
// BIP 152 / BIP 174: CompactSize fields must use the shortest encoding,
|
|
324
|
+
// so wider prefixes for smaller values are rejected here.
|
|
325
|
+
if (num < start)
|
|
326
|
+
throw r.err(`Wrong CompactSize(${8 * bytes})`);
|
|
327
|
+
return num;
|
|
328
|
+
},
|
|
329
|
+
}));
|
|
330
|
+
})();
|
|
318
331
|
// Same thing, but in number instead of bigint. Checks for safe integer inside
|
|
319
332
|
/**
|
|
320
333
|
* CompactSize coder that decodes into JavaScript numbers.
|
|
@@ -470,4 +483,3 @@ export const RawOldTx = /* @__PURE__ */ (() => Object.freeze(P.struct({
|
|
|
470
483
|
outputs: BTCArray(RawOutput),
|
|
471
484
|
lockTime: P.U32LE,
|
|
472
485
|
})))();
|
|
473
|
-
//# sourceMappingURL=script.js.map
|