@scure/btc-signer 2.0.1 → 2.3.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/psbt.js CHANGED
@@ -1,95 +1,250 @@
1
1
  import { hex } from '@scure/base';
2
+ import { anumber } 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
+ import { aarray, compareBytes, equalBytes, PubT, validateObject, validatePubkey, } from "./utils.js";
5
6
  // PSBT BIP174, BIP370, BIP371
6
- // Can be 33 or 64 bytes
7
- const PubKeyECDSA = P.validate(P.bytes(null), (pub) => validatePubkey(pub, PubT.ecdsa));
8
- const PubKeySchnorr = P.validate(P.bytes(32), (pub) => validatePubkey(pub, PubT.schnorr));
9
- const SignatureSchnorr = P.validate(P.bytes(null), (sig) => {
7
+ // Be friendly to bad ECMAScript parsers by not using bigint literals.
8
+ // prettier-ignore
9
+ const _0n = /* @__PURE__ */ BigInt(0), _1n = /* @__PURE__ */ BigInt(1);
10
+ // BIP174 keydata only says "public key", so legacy PSBT ECDSA fields still accept both
11
+ // compressed (33-byte) and uncompressed (65-byte) SEC1 encodings, but not x-only keys.
12
+ const PubKeyECDSA = /* @__PURE__ */ (() => P.validate(P.bytes(null), (pub) => validatePubkey(pub, PubT.ecdsa)))();
13
+ // BIP32 serialized xpub payloads specifically store `ser_P(K)`, which is always the 33-byte
14
+ // compressed SEC1 encoding of the public key rather than the looser legacy PSBT "any ECDSA pubkey".
15
+ const PubKeyECDSACompressed = /* @__PURE__ */ (() => P.validate(P.bytes(33), (pub) => validatePubkey(pub, PubT.ecdsa)))();
16
+ // BIP371 taproot PSBT key fields use 32-byte x-only pubkeys, so this coder keeps the
17
+ // fixed-length check and then reuses the shared Schnorr pubkey validator.
18
+ const PubKeySchnorr = /* @__PURE__ */ (() => P.validate(P.bytes(32), (pub) => validatePubkey(pub, PubT.schnorr)))();
19
+ // BIP371 taproot signature fields carry the 64-byte Schnorr signature, plus an optional
20
+ // trailing sighash byte when the signer used anything other than the default key-path mode.
21
+ const SignatureSchnorr = /* @__PURE__ */ (() => P.validate(P.bytes(null), (sig) => {
10
22
  if (sig.length !== 64 && sig.length !== 65)
11
23
  throw new Error('Schnorr signature should be 64 or 65 bytes long');
12
24
  return sig;
13
- });
14
- const BIP32Der = P.struct({
25
+ }))();
26
+ // PSBTInput.finalScriptWitness should keep the historical decoded witness-stack shape even though
27
+ // the exported RawWitness coder now uses TRet for declaration stability.
28
+ const RawWitnessWire = RawWitness;
29
+ // BIP174 stores the 4-byte master fingerprint as-is, then appends each child index in
30
+ // 32-bit little-endian order; cross-field checks like xpub depth matching live above this.
31
+ const BIP32Der = /* @__PURE__ */ (() => P.struct({
15
32
  fingerprint: P.U32BE,
16
33
  path: P.array(null, P.U32LE),
17
- });
18
- const TaprootBIP32Der = P.struct({
34
+ }))();
35
+ // BIP371 prepends the shared BIP32 derivation payload with the tapleaf-hash list; internal keys
36
+ // use `hashes.length === 0`, while script-path keys list the leaves that actually use that pubkey.
37
+ const TaprootBIP32Der = /* @__PURE__ */ (() => P.struct({
19
38
  hashes: P.array(CompactSizeLen, P.bytes(32)),
20
39
  der: BIP32Der,
21
- });
22
- // The 78 byte serialized extended public key as defined by BIP 32.
23
- const GlobalXPUB = P.bytes(78);
24
- const tapScriptSigKey = P.struct({ pubKey: PubKeySchnorr, leafHash: P.bytes(32) });
40
+ }))();
41
+ // BIP174 `PSBT_GLOBAL_XPUB` says the key is "The 78 byte serialized extended public key as
42
+ // defined by BIP 32", so decode it to the actual BIP32 field layout instead of preserving an
43
+ // opaque blob. We intentionally do not hardcode version-byte policy here because BIP32 version
44
+ // bytes vary by network / deployment; this layer just parses the field and enforces the BIP32
45
+ // import rules that are independent of network selection.
46
+ const GlobalXPUB = /* @__PURE__ */ (() => P.validate(P.struct({
47
+ version: P.U32BE,
48
+ depth: P.U8,
49
+ parentFingerprint: P.U32BE,
50
+ childNumber: P.U32BE,
51
+ chainCode: P.bytes(32),
52
+ // BIP32 serialization stores the public key as the final 33-byte `ser_P(K)` field and says
53
+ // importing an extended public key must verify that point data corresponds to the curve.
54
+ publicKey: PubKeyECDSACompressed,
55
+ }), (xpub) => {
56
+ // BIP32 serialization says master nodes use depth 0 with zero parent
57
+ // fingerprint and zero child number. The invalid examples explicitly
58
+ // include zero-depth xpubs with either field non-zero.
59
+ if (xpub.depth === 0 && xpub.parentFingerprint !== 0)
60
+ throw new Error('GlobalXPUB: depth=0 requires parentFingerprint=0');
61
+ if (xpub.depth === 0 && xpub.childNumber !== 0)
62
+ throw new Error('GlobalXPUB: depth=0 requires childNumber=0');
63
+ return xpub;
64
+ }))();
65
+ // BIP371 puts the x-only pubkey and leaf hash into the key side of `PSBT_IN_TAP_SCRIPT_SIG`;
66
+ // the actual 64/65-byte Schnorr signature stays in the value side under `SignatureSchnorr`.
67
+ const tapScriptSigKey = /* @__PURE__ */ (() => P.struct({ pubKey: PubKeySchnorr, leafHash: P.bytes(32) }))();
25
68
  // Complex structure for PSBT fields
26
69
  // <control byte with leaf version and parity bit> <internal key p> <C> <E> <AB>
27
- const _TaprootControlBlock = P.struct({
70
+ // Raw BIP341 control-block layout only; the exported TaprootControlBlock wrapper adds the
71
+ // `0..128` Merkle-depth bound, and later taproot logic checks version/parity semantics.
72
+ const _TaprootControlBlock = /* @__PURE__ */ (() => P.struct({
28
73
  version: P.U8, // With parity :(
29
74
  internalKey: P.bytes(32),
30
75
  merklePath: P.array(null, P.bytes(32)),
31
- });
32
- export const TaprootControlBlock = P.validate(_TaprootControlBlock, (cb) => {
76
+ }))();
77
+ /**
78
+ * Taproot control block coder.
79
+ * @example
80
+ * Encode the Taproot control block attached to a script-path witness.
81
+ * ```ts
82
+ * import { TaprootControlBlock } from '@scure/btc-signer/psbt.js';
83
+ * import { pubSchnorr, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
84
+ * TaprootControlBlock.encode({
85
+ * version: 0xc0,
86
+ * internalKey: pubSchnorr(randomPrivateKeyBytes()),
87
+ * merklePath: [],
88
+ * });
89
+ * ```
90
+ */
91
+ export const TaprootControlBlock = /* @__PURE__ */ (() => Object.freeze(P.validate(_TaprootControlBlock, (cb) => {
92
+ // BIP 341 control blocks are raw 33+32m byte records; this PSBT coder only enforces
93
+ // the length/depth shape here and leaves curve / leaf-version validation to taproot logic.
33
94
  if (cb.merklePath.length > 128)
34
95
  throw new Error('TaprootControlBlock: merklePath should be of length 0..128 (inclusive)');
35
96
  return cb;
36
- });
97
+ })))();
98
+ // BIP371 says PSBT_OUT_TAP_TREE is one or more tuples in DFS order so the Taproot tree can be
99
+ // reconstructed. Validate both the non-empty requirement and that the leaf-depth sequence really
100
+ // describes a complete left-to-right DFS walk of a binary tree, not just arbitrary tuples.
37
101
  // {<8-bit uint depth> <8-bit uint leaf version> <compact size uint scriptlen> <bytes script>}*
38
- const tapTree = P.array(null, P.struct({
102
+ const tapTree = /* @__PURE__ */ (() => P.validate(P.array(null, P.struct({
39
103
  depth: P.U8,
40
104
  version: P.U8,
41
105
  script: VarBytes,
42
- }));
43
- const BytesInf = P.bytes(null); // Bytes will conflict with Bytes type
44
- const Bytes20 = P.bytes(20);
45
- const Bytes32 = P.bytes(32);
106
+ })), (tree) => {
107
+ if (tree.length < 1)
108
+ throw new Error('tapTree: expected at least one tuple');
109
+ let path = Array(tree[0].depth).fill(0);
110
+ let maxDepth = tree[0].depth;
111
+ for (let i = 1; i < tree.length; i++) {
112
+ const { depth } = tree[i];
113
+ if (depth > maxDepth)
114
+ maxDepth = depth;
115
+ let j = path.length - 1;
116
+ while (j >= 0 && path[j] === 1)
117
+ j--;
118
+ if (j < 0)
119
+ throw new Error('tapTree: tuples must be in DFS order');
120
+ const next = path.slice(0, j);
121
+ next.push(1);
122
+ if (depth < next.length)
123
+ throw new Error('tapTree: tuples must be in DFS order');
124
+ while (next.length < depth)
125
+ next.push(0);
126
+ path = next;
127
+ }
128
+ let leaves = _0n;
129
+ for (let i = 0; i < tree.length; i++)
130
+ leaves += _1n << BigInt(maxDepth - tree[i].depth);
131
+ if (leaves !== _1n << BigInt(maxDepth))
132
+ throw new Error('tapTree: tuples must describe a complete binary tree');
133
+ return tree;
134
+ }))();
135
+ // Shared raw PSBT byte payload coder for fields whose BIP174 value format is just opaque bytes;
136
+ // field-specific structure and length checks still live at the individual field definitions.
137
+ // Keep a distinct name here so the byte coder does not collide with the Bytes type alias.
138
+ const BytesInf = /* @__PURE__ */ P.bytes(null);
139
+ // Shared 20-byte key-data helper for the BIP174 RIPEMD160 and HASH160 preimage maps.
140
+ const Bytes20 = /* @__PURE__ */ P.bytes(20);
141
+ // Shared 32-byte helper for fixed-size hash / txid / merkle-root byte fields; any stronger
142
+ // semantics such as x-only pubkey validity still need to be enforced by the field that uses it.
143
+ const Bytes32 = /* @__PURE__ */ P.bytes(32);
144
+ // jsbt mutate checks exported PSBT tables recursively, so freeze each field tuple and its
145
+ // nested version arrays here while preserving the original coder slot types for local inference.
146
+ const PSBTInfo = (type, kc, vc, reqInc, allowInc, silentIgnore) =>
147
+ /* @__PURE__ */ Object.freeze([
148
+ type,
149
+ kc && typeof kc === 'object' ? Object.freeze(kc) : kc,
150
+ vc && typeof vc === 'object' ? Object.freeze(vc) : vc,
151
+ Object.freeze([...reqInc]),
152
+ Object.freeze([...allowInc]),
153
+ silentIgnore,
154
+ ]);
46
155
  // versionsRequiringExclusing = !versionsAllowsInclusion (as set)
47
- // {name: [tag, keyCoder, valueCoder, versionsRequiringInclusion, versionsRequiringExclusing, versionsAllowsInclusion, silentIgnore]}
48
- // SilentIgnore: we use some v2 fields for v1 representation too, so we just clean them before serialize
156
+ // {name: [tag, keyCoder, valueCoder, versionsRequiringInclusion,
157
+ // versionsRequiringExclusing, versionsAllowsInclusion, silentIgnore]}
158
+ // SilentIgnore: we use some v2 fields for v1 representation too,
159
+ // so we just clean them before serialize.
49
160
  // Tables from BIP-0174 (https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki)
50
161
  // prettier-ignore
51
- export const PSBTGlobal = {
52
- unsignedTx: [0x00, false, RawOldTx, [0], [0], false],
53
- xpub: [0x01, GlobalXPUB, BIP32Der, [], [0, 2], false],
54
- txVersion: [0x02, false, P.U32LE, [2], [2], false],
55
- fallbackLocktime: [0x03, false, P.U32LE, [], [2], false],
56
- inputCount: [0x04, false, CompactSizeLen, [2], [2], false],
57
- outputCount: [0x05, false, CompactSizeLen, [2], [2], false],
58
- txModifiable: [0x06, false, P.U8, [], [2], false], // TODO: bitfield
59
- version: [0xfb, false, P.U32LE, [], [0, 2], false],
60
- proprietary: [0xfc, BytesInf, BytesInf, [], [0, 2], false],
61
- };
162
+ /**
163
+ * PSBT global key definitions.
164
+ * @example
165
+ * Keep only the fields that are valid for the target PSBT version before serializing.
166
+ * ```ts
167
+ * import { PSBTGlobal, cleanPSBTFields } from '@scure/btc-signer/psbt.js';
168
+ * cleanPSBTFields(2, PSBTGlobal, { txVersion: 2, inputCount: 1, outputCount: 1 });
169
+ * ```
170
+ */
171
+ export const PSBTGlobal = /* @__PURE__ */ (() => Object.freeze({
172
+ unsignedTx: PSBTInfo(0x00, false, RawOldTx, [0], [0], false),
173
+ // BIP174 also requires the serialized xpub depth to match the number of path elements in the
174
+ // paired derivation value, so callers still need that cross-field check above this raw table.
175
+ xpub: PSBTInfo(0x01, GlobalXPUB, BIP32Der, [], [0, 2], false),
176
+ txVersion: PSBTInfo(0x02, false, P.U32LE, [2], [2], false),
177
+ fallbackLocktime: PSBTInfo(0x03, false, P.U32LE, [], [2], false),
178
+ inputCount: PSBTInfo(0x04, false, CompactSizeLen, [2], [2], false),
179
+ outputCount: PSBTInfo(0x05, false, CompactSizeLen, [2], [2], false),
180
+ // TODO: bitfield
181
+ txModifiable: PSBTInfo(0x06, false, P.U8, [], [2], false),
182
+ version: PSBTInfo(0xfb, false, P.U32LE, [], [0, 2], false),
183
+ proprietary: PSBTInfo(0xfc, BytesInf, BytesInf, [], [0, 2], false),
184
+ }))();
62
185
  // prettier-ignore
63
- export const PSBTInput = {
64
- nonWitnessUtxo: [0x00, false, RawTx, [], [0, 2], false],
65
- witnessUtxo: [0x01, false, RawOutput, [], [0, 2], false],
66
- partialSig: [0x02, PubKeyECDSA, BytesInf, [], [0, 2], false],
67
- sighashType: [0x03, false, P.U32LE, [], [0, 2], false],
68
- redeemScript: [0x04, false, BytesInf, [], [0, 2], false],
69
- witnessScript: [0x05, false, BytesInf, [], [0, 2], false],
70
- bip32Derivation: [0x06, PubKeyECDSA, BIP32Der, [], [0, 2], false],
71
- finalScriptSig: [0x07, false, BytesInf, [], [0, 2], false],
72
- finalScriptWitness: [0x08, false, RawWitness, [], [0, 2], false],
73
- porCommitment: [0x09, false, BytesInf, [], [0, 2], false],
74
- ripemd160: [0x0a, Bytes20, BytesInf, [], [0, 2], false],
75
- sha256: [0x0b, Bytes32, BytesInf, [], [0, 2], false],
76
- hash160: [0x0c, Bytes20, BytesInf, [], [0, 2], false],
77
- hash256: [0x0d, Bytes32, BytesInf, [], [0, 2], false],
78
- txid: [0x0e, false, Bytes32, [2], [2], true],
79
- index: [0x0f, false, P.U32LE, [2], [2], true],
80
- sequence: [0x10, false, P.U32LE, [], [2], true],
81
- requiredTimeLocktime: [0x11, false, P.U32LE, [], [2], false],
82
- requiredHeightLocktime: [0x12, false, P.U32LE, [], [2], false],
83
- tapKeySig: [0x13, false, SignatureSchnorr, [], [0, 2], false],
84
- tapScriptSig: [0x14, tapScriptSigKey, SignatureSchnorr, [], [0, 2], false],
85
- tapLeafScript: [0x15, TaprootControlBlock, BytesInf, [], [0, 2], false],
86
- tapBip32Derivation: [0x16, Bytes32, TaprootBIP32Der, [], [0, 2], false],
87
- tapInternalKey: [0x17, false, PubKeySchnorr, [], [0, 2], false],
88
- tapMerkleRoot: [0x18, false, Bytes32, [], [0, 2], false],
89
- proprietary: [0xfc, BytesInf, BytesInf, [], [0, 2], false],
90
- };
186
+ /**
187
+ * PSBT input key definitions.
188
+ * @example
189
+ * Strip input fields that do not belong in the requested PSBT version.
190
+ * ```ts
191
+ * import { hex } from '@scure/base';
192
+ * import { PSBTInput, cleanPSBTFields } from '@scure/btc-signer/psbt.js';
193
+ * cleanPSBTFields(2, PSBTInput, {
194
+ * txid: hex.decode('0000000000000000000000000000000000000000000000000000000000000001'),
195
+ * index: 0,
196
+ * witnessUtxo: { amount: 2n, script: new Uint8Array([0x51]) },
197
+ * });
198
+ * ```
199
+ */
200
+ export const PSBTInput = /* @__PURE__ */ (() => Object.freeze({
201
+ nonWitnessUtxo: PSBTInfo(0x00, false, RawTx, [], [0, 2], false),
202
+ witnessUtxo: PSBTInfo(0x01, false, RawOutput, [], [0, 2], false),
203
+ partialSig: PSBTInfo(0x02, PubKeyECDSA, BytesInf, [], [0, 2], false),
204
+ sighashType: PSBTInfo(0x03, false, P.U32LE, [], [0, 2], false),
205
+ redeemScript: PSBTInfo(0x04, false, BytesInf, [], [0, 2], false),
206
+ witnessScript: PSBTInfo(0x05, false, BytesInf, [], [0, 2], false),
207
+ bip32Derivation: PSBTInfo(0x06, PubKeyECDSA, BIP32Der, [], [0, 2], false),
208
+ finalScriptSig: PSBTInfo(0x07, false, BytesInf, [], [0, 2], false),
209
+ finalScriptWitness: PSBTInfo(0x08, false, RawWitnessWire, [], [0, 2], false),
210
+ porCommitment: PSBTInfo(0x09, false, BytesInf, [], [0, 2], false),
211
+ ripemd160: PSBTInfo(0x0a, Bytes20, BytesInf, [], [0, 2], false),
212
+ sha256: PSBTInfo(0x0b, Bytes32, BytesInf, [], [0, 2], false),
213
+ hash160: PSBTInfo(0x0c, Bytes20, BytesInf, [], [0, 2], false),
214
+ hash256: PSBTInfo(0x0d, Bytes32, BytesInf, [], [0, 2], false),
215
+ // BIP174/BIP370 serialize PREVIOUS_TXID in standard byte order, while the rest of this repo
216
+ // historically keeps TransactionInput.txid in display-order bytes matching `Transaction.id`.
217
+ // Reverse at this PSBTv2 boundary so internal txid semantics stay aligned with the raw-tx path.
218
+ txid: PSBTInfo(0x0e, false, P.bytes(32, true), [2], [2], true),
219
+ index: PSBTInfo(0x0f, false, P.U32LE, [2], [2], true),
220
+ sequence: PSBTInfo(0x10, false, P.U32LE, [], [2], true),
221
+ requiredTimeLocktime: PSBTInfo(0x11, false, P.U32LE, [], [2], false),
222
+ requiredHeightLocktime: PSBTInfo(0x12, false, P.U32LE, [], [2], false),
223
+ tapKeySig: PSBTInfo(0x13, false, SignatureSchnorr, [], [0, 2], false),
224
+ tapScriptSig: PSBTInfo(0x14, tapScriptSigKey, SignatureSchnorr, [], [0, 2], false),
225
+ tapLeafScript: PSBTInfo(0x15, TaprootControlBlock, BytesInf, [], [0, 2], false),
226
+ // BIP371 key data here is a 32-byte x-only pubkey, so reuse the shared Schnorr pubkey coder
227
+ // instead of accepting arbitrary 32-byte blobs that only fail much later in taproot flows.
228
+ tapBip32Derivation: PSBTInfo(0x16, PubKeySchnorr, TaprootBIP32Der, [], [0, 2], false),
229
+ tapInternalKey: PSBTInfo(0x17, false, PubKeySchnorr, [], [0, 2], false),
230
+ tapMerkleRoot: PSBTInfo(0x18, false, Bytes32, [], [0, 2], false),
231
+ proprietary: PSBTInfo(0xfc, BytesInf, BytesInf, [], [0, 2], false),
232
+ }))();
91
233
  // All other keys removed when finalizing
92
- export const PSBTInputFinalKeys = [
234
+ /**
235
+ * Input fields preserved after finalization.
236
+ * @example
237
+ * Use the allowlist when stripping transient signing fields after finalization.
238
+ * ```ts
239
+ * import { PSBTInputFinalKeys } from '@scure/btc-signer/psbt.js';
240
+ * const finalKeys = new Set(PSBTInputFinalKeys);
241
+ * finalKeys.has('finalScriptWitness');
242
+ * ```
243
+ */
244
+ export const PSBTInputFinalKeys = /* @__PURE__ */ Object.freeze([
245
+ // PSBTv2 extractors rebuild the final transaction from per-input fields, so
246
+ // finalized inputs still need txid/index (and any non-default sequence)
247
+ // even though BIP174's generic cleanup is stricter.
93
248
  'txid',
94
249
  'sequence',
95
250
  'index',
@@ -98,40 +253,83 @@ export const PSBTInputFinalKeys = [
98
253
  'finalScriptSig',
99
254
  'finalScriptWitness',
100
255
  'unknown',
101
- ];
256
+ ]);
102
257
  // Can be modified even on signed input
103
- export const PSBTInputUnsignedKeys = [
258
+ /**
259
+ * Input fields that may still change after signing starts.
260
+ * @example
261
+ * Signed inputs may still update these fields while new signatures are being added.
262
+ * ```ts
263
+ * import { PSBTInputUnsignedKeys } from '@scure/btc-signer/psbt.js';
264
+ * const mutableKeys = new Set(PSBTInputUnsignedKeys);
265
+ * mutableKeys.has('tapScriptSig');
266
+ * ```
267
+ */
268
+ export const PSBTInputUnsignedKeys = /* @__PURE__ */ Object.freeze([
269
+ // This is the replace/remove allowlist for signed inputs; mergeKeyMap() can still append
270
+ // previously absent metadata or new KV entries for other fields when they don't conflict.
104
271
  'partialSig',
105
272
  'finalScriptSig',
106
273
  'finalScriptWitness',
107
274
  'tapKeySig',
108
275
  'tapScriptSig',
109
- ];
276
+ ]);
110
277
  // prettier-ignore
111
- export const PSBTOutput = {
112
- redeemScript: [0x00, false, BytesInf, [], [0, 2], false],
113
- witnessScript: [0x01, false, BytesInf, [], [0, 2], false],
114
- bip32Derivation: [0x02, PubKeyECDSA, BIP32Der, [], [0, 2], false],
115
- amount: [0x03, false, P.I64LE, [2], [2], true],
116
- script: [0x04, false, BytesInf, [2], [2], true],
117
- tapInternalKey: [0x05, false, PubKeySchnorr, [], [0, 2], false],
118
- tapTree: [0x06, false, tapTree, [], [0, 2], false],
119
- tapBip32Derivation: [0x07, PubKeySchnorr, TaprootBIP32Der, [], [0, 2], false],
120
- proprietary: [0xfc, BytesInf, BytesInf, [], [0, 2], false],
121
- };
278
+ /**
279
+ * PSBT output key definitions.
280
+ * @example
281
+ * Strip output fields that are not valid for the target PSBT version.
282
+ * ```ts
283
+ * import { PSBTOutput, cleanPSBTFields } from '@scure/btc-signer/psbt.js';
284
+ * cleanPSBTFields(2, PSBTOutput, { amount: 2n, script: new Uint8Array([0x51]) });
285
+ * ```
286
+ */
287
+ export const PSBTOutput = /* @__PURE__ */ (() => Object.freeze({
288
+ redeemScript: PSBTInfo(0x00, false, BytesInf, [], [0, 2], false),
289
+ witnessScript: PSBTInfo(0x01, false, BytesInf, [], [0, 2], false),
290
+ bip32Derivation: PSBTInfo(0x02, PubKeyECDSA, BIP32Der, [], [0, 2], false),
291
+ // BIP174/BIP370 serialize PSBT_OUT_AMOUNT as a signed int64 on the wire; semantic output
292
+ // validity still rejects negative transaction amounts in `PSBTOutputCoder` below.
293
+ amount: PSBTInfo(0x03, false, P.I64LE, [2], [2], true),
294
+ script: PSBTInfo(0x04, false, BytesInf, [2], [2], true),
295
+ tapInternalKey: PSBTInfo(0x05, false, PubKeySchnorr, [], [0, 2], false),
296
+ // BIP371 expects a non-empty DFS-ordered list of tapleaf tuples here so wallets can
297
+ // reconstruct the same Taproot tree, not just an arbitrary list of serialized leaves.
298
+ tapTree: PSBTInfo(0x06, false, tapTree, [], [0, 2], false),
299
+ tapBip32Derivation: PSBTInfo(0x07, PubKeySchnorr, TaprootBIP32Der, [], [0, 2], false),
300
+ proprietary: PSBTInfo(0xfc, BytesInf, BytesInf, [], [0, 2], false),
301
+ }))();
122
302
  // Can be modified even on signed input
123
- export const PSBTOutputUnsignedKeys = [];
124
- const PSBTKeyPair = P.array(P.NULL, P.struct({
303
+ /**
304
+ * Output fields that may still change after signing starts.
305
+ * @example
306
+ * PSBTv2 outputs are fully committed once signing starts, so the set stays empty.
307
+ * ```ts
308
+ * import { PSBTOutputUnsignedKeys } from '@scure/btc-signer/psbt.js';
309
+ * const mutableKeys = new Set(PSBTOutputUnsignedKeys);
310
+ * mutableKeys.size; // 0
311
+ * ```
312
+ */
313
+ export const PSBTOutputUnsignedKeys = /* @__PURE__ */ Object.freeze([]);
314
+ // Signed outputs have no replace/remove exceptions: once a signature actually commits to a given
315
+ // output, every field on that output is frozen. SIGHASH_NONE leaves outputs fully mutable, and
316
+ // SIGHASH_SINGLE only freezes the matching output index.
317
+ // Raw BIP174 keypair framing only: `<key><value>` records terminated by `0x00`.
318
+ // Uniqueness, keyed-vs-unkeyed rules, and per-type decoding live one layer up in `PSBTKeyMap`.
319
+ const PSBTKeyPair = /* @__PURE__ */ (() => P.array(P.NULL, P.struct({
125
320
  // <key> := <keylen> <keytype> <keydata> WHERE keylen = len(keytype)+len(keydata)
126
321
  key: P.prefix(CompactSizeLen, P.struct({ type: CompactSizeLen, key: P.bytes(null) })),
127
322
  // <value> := <valuelen> <valuedata>
128
323
  value: P.bytes(CompactSizeLen),
129
- }));
324
+ })))();
130
325
  function PSBTKeyInfo(info) {
326
+ // Name the tuple slots once so version-filter helpers do not depend on raw positional indexing.
131
327
  const [type, kc, vc, reqInc, allowInc, silentIgnore] = info;
132
328
  return { type, kc, vc, reqInc, allowInc, silentIgnore };
133
329
  }
134
- const PSBTUnknownKey = P.struct({ type: CompactSizeLen, key: P.bytes(null) });
330
+ const PSBTUnknownKey = /* @__PURE__ */ (() =>
331
+ // Raw unknown/proprietary field key: compact-size keytype plus opaque keydata for pass-through.
332
+ P.struct({ type: CompactSizeLen, key: P.bytes(null) }))();
135
333
  // Key cannot be 'unknown', value coder cannot be array for elements with empty key
136
334
  function PSBTKeyMap(psbtEnum) {
137
335
  // -> Record<type, [keyName, ...coders]>
@@ -142,18 +340,32 @@ function PSBTKeyMap(psbtEnum) {
142
340
  }
143
341
  return P.wrap({
144
342
  encodeStream: (w, value) => {
343
+ const _value = value;
145
344
  let out = [];
345
+ const seen = {};
346
+ const add = (key, value) => {
347
+ const _value = value;
348
+ // BIP174 defines `<key> := <keylen> <keytype> <keydata>` and says repeated `<keytype>`
349
+ // entries are allowed within one `<map>` as long as the full `<key>` stays unique.
350
+ // `<keylen>` is derived from `<keytype><keydata>`, so `PSBTUnknownKey` is enough here.
351
+ const kStr = hex.encode(PSBTUnknownKey.encode(key));
352
+ if (seen[kStr])
353
+ throw new Error(`PSBT: duplicate key=${kStr}`);
354
+ seen[kStr] = true;
355
+ out.push({ key, value: _value });
356
+ };
146
357
  // Because we use order of psbtEnum, keymap is sorted here
147
358
  for (const name in psbtEnum) {
148
- const val = value[name];
359
+ const val = _value[name];
149
360
  if (val === undefined)
150
361
  continue;
151
362
  const [type, kc, vc] = psbtEnum[name];
152
363
  if (!kc) {
153
- out.push({ key: { type, key: P.EMPTY }, value: vc.encode(val) });
364
+ add({ type, key: P.EMPTY }, vc.encode(val));
154
365
  }
155
366
  else {
156
- // Low level interface, returns keys as is (with duplicates). Useful for debug
367
+ // BIP174 allows repeated `<keytype>` values inside one `<map>`, but the full `<key>`
368
+ // must stay unique, so keyed rows are sorted and then deduped by serialized key bytes.
157
369
  const kv = val.map(([k, v]) => [
158
370
  kc.encode(k),
159
371
  vc.encode(v),
@@ -161,13 +373,13 @@ function PSBTKeyMap(psbtEnum) {
161
373
  // sort by keys
162
374
  kv.sort((a, b) => compareBytes(a[0], b[0]));
163
375
  for (const [key, value] of kv)
164
- out.push({ key: { key, type }, value });
376
+ add({ key, type }, value);
165
377
  }
166
378
  }
167
- if (value.unknown) {
168
- value.unknown.sort((a, b) => compareBytes(a[0].key, b[0].key));
169
- for (const [k, v] of value.unknown)
170
- out.push({ key: k, value: v });
379
+ if (_value.unknown) {
380
+ _value.unknown.sort((a, b) => compareBytes(a[0].key, b[0].key));
381
+ for (const [k, v] of _value.unknown)
382
+ add(k, v);
171
383
  }
172
384
  PSBTKeyPair.encodeStream(w, out);
173
385
  },
@@ -175,7 +387,12 @@ function PSBTKeyMap(psbtEnum) {
175
387
  const raw = PSBTKeyPair.decodeStream(r);
176
388
  const out = {};
177
389
  const noKey = {};
390
+ const seen = {};
178
391
  for (const elm of raw) {
392
+ const kStr = hex.encode(PSBTUnknownKey.encode(elm.key));
393
+ if (seen[kStr])
394
+ throw new Error(`PSBT: duplicate key=${kStr}`);
395
+ seen[kStr] = true;
179
396
  let name = 'unknown';
180
397
  let key = elm.key.key;
181
398
  let value = elm.value;
@@ -199,7 +416,9 @@ function PSBTKeyMap(psbtEnum) {
199
416
  // For unknown: add key type inside key
200
417
  key = { type: elm.key.type, key: elm.key.key };
201
418
  }
202
- // Only keyed elements at this point
419
+ // Only keyed elements at this point.
420
+ // BIP174 uniqueness is over the full serialized `<key>` bytes within one map, not only keytype.
421
+ // Empty-key rows are rejected above; keyed duplicates need an explicit check before this append path.
203
422
  if (noKey[name])
204
423
  throw new Error(`PSBT: Key type with empty key and no key=${name} val=${value}`);
205
424
  if (!out[name])
@@ -210,7 +429,23 @@ function PSBTKeyMap(psbtEnum) {
210
429
  },
211
430
  });
212
431
  }
213
- export const PSBTInputCoder = P.validate(PSBTKeyMap(PSBTInput), (i) => {
432
+ /**
433
+ * Validated PSBT input coder.
434
+ * @example
435
+ * Validate a decoded PSBT input before serializing it back to bytes.
436
+ * ```ts
437
+ * import { hex } from '@scure/base';
438
+ * import { PSBTInputCoder } from '@scure/btc-signer/psbt.js';
439
+ * PSBTInputCoder.encode({
440
+ * txid: hex.decode('0000000000000000000000000000000000000000000000000000000000000001'),
441
+ * index: 0,
442
+ * witnessUtxo: { amount: 1n, script: new Uint8Array([0x51]) },
443
+ * });
444
+ * ```
445
+ */
446
+ export const PSBTInputCoder = /* @__PURE__ */ (() => Object.freeze(P.validate(PSBTKeyMap(PSBTInput), (i) => {
447
+ // This wrapper adds input-level invariants after raw PSBT key-map decoding.
448
+ // Row-level key validation and duplicate-key rejection still depend on the underlying table/map helpers.
214
449
  if (i.finalScriptWitness && !i.finalScriptWitness.length)
215
450
  throw new Error('validateInput: empty finalScriptWitness');
216
451
  //if (i.finalScriptSig && !i.finalScriptSig.length) throw new Error('validateInput: empty finalScriptSig');
@@ -239,14 +474,33 @@ export const PSBTInputCoder = P.validate(PSBTKeyMap(PSBTInput), (i) => {
239
474
  }
240
475
  }
241
476
  return i;
242
- });
243
- export const PSBTOutputCoder = P.validate(PSBTKeyMap(PSBTOutput), (o) => {
477
+ })))();
478
+ /**
479
+ * Validated PSBT output coder.
480
+ * @example
481
+ * Validate a decoded PSBT output before serializing it back to bytes.
482
+ * ```ts
483
+ * import { PSBTOutputCoder } from '@scure/btc-signer/psbt.js';
484
+ * PSBTOutputCoder.encode({ amount: 1n, script: new Uint8Array([0x51]) });
485
+ * ```
486
+ */
487
+ export const PSBTOutputCoder = /* @__PURE__ */ (() => Object.freeze(P.validate(PSBTKeyMap(PSBTOutput), (o) => {
488
+ // This wrapper only adds output-level invariants after raw key-map decoding.
489
+ // Duplicate-key rejection still depends on the key-map helper; tapTree structure is validated
490
+ // in the field coder itself because BIP371 constrains the tuple value, not just the row shape.
491
+ // BIP174/BIP370 define PSBT_OUT_AMOUNT as a signed int64 transport field, but it still
492
+ // represents the transaction output amount in satoshis, so negative output values are invalid.
493
+ if (o.amount !== undefined && o.amount < _0n)
494
+ throw new Error(`validateOutput: wrong amount=${o.amount}`);
244
495
  if (o.bip32Derivation)
245
496
  for (const [k] of o.bip32Derivation)
246
497
  validatePubkey(k, PubT.ecdsa);
247
498
  return o;
248
- });
249
- const PSBTGlobalCoder = P.validate(PSBTKeyMap(PSBTGlobal), (g) => {
499
+ })))();
500
+ const PSBTGlobalCoder = /* @__PURE__ */ (() => P.validate(PSBTKeyMap(PSBTGlobal), (g) => {
501
+ // This wrapper adds the BIP174/BIP370 cross-field invariants after raw global key-map decoding.
502
+ // `PSBT_GLOBAL_XPUB` stores the serialized xpub plus a separate derivation value, and BIP174
503
+ // says the number of 32-bit indexes in that derivation path must match the xpub depth.
250
504
  const version = g.version || 0;
251
505
  if (version === 0) {
252
506
  if (!g.unsignedTx)
@@ -255,26 +509,42 @@ const PSBTGlobalCoder = P.validate(PSBTKeyMap(PSBTGlobal), (g) => {
255
509
  if (inp.finalScriptSig && inp.finalScriptSig.length)
256
510
  throw new Error('PSBTv0: input scriptSig found in unsignedTx');
257
511
  }
512
+ for (const [xpub, der] of g.xpub || []) {
513
+ if (xpub.depth !== der.path.length)
514
+ throw new Error(`PSBT_GLOBAL_XPUB: xpub depth=${xpub.depth} must match derivation path length=${der.path.length}`);
515
+ }
258
516
  return g;
259
- });
260
- export const _RawPSBTV0 = P.struct({
517
+ }))();
518
+ export const _RawPSBTV0 = /* @__PURE__ */ (() => Object.freeze(P.struct({
261
519
  magic: P.magic(P.string(new Uint8Array([0xff])), 'psbt'),
262
520
  global: PSBTGlobalCoder,
521
+ // Raw v0 framing follows the unsigned transaction for input-map count; the stricter
522
+ // one-map-per-input/output reconciliation happens in `RawPSBTV0` / `validatePSBT`.
263
523
  inputs: P.array('global/unsignedTx/inputs/length', PSBTInputCoder),
264
524
  outputs: P.array(null, PSBTOutputCoder),
265
- });
266
- export const _RawPSBTV2 = P.struct({
525
+ })))();
526
+ export const _RawPSBTV2 = /* @__PURE__ */ (() => Object.freeze(P.struct({
267
527
  magic: P.magic(P.string(new Uint8Array([0xff])), 'psbt'),
268
528
  global: PSBTGlobalCoder,
529
+ // Raw v2 framing takes map counts from the global PSBTv2 count fields; deeper version
530
+ // and per-field validation still happens in `RawPSBTV2` / `validatePSBT`.
269
531
  inputs: P.array('global/inputCount', PSBTInputCoder),
270
532
  outputs: P.array('global/outputCount', PSBTOutputCoder),
271
- });
272
- export const _DebugPSBT = P.struct({
533
+ })))();
534
+ export const _DebugPSBT = /* @__PURE__ */ (() => Object.freeze(P.struct({
273
535
  magic: P.magic(P.string(new Uint8Array([0xff])), 'psbt'),
536
+ // Debug-only normalized view: maps become plain objects, so key order is intentionally ignored
537
+ // and duplicate keys fail while decoding instead of being preserved for byte-level diagnostics.
538
+ // Each `items[i]` is one raw PSBT map (`global`, then inputs, then outputs), keyed by the
539
+ // full serialized PSBT key bytes as hex rather than decoded field names.
274
540
  items: P.array(null, P.apply(P.array(P.NULL, P.tuple([P.hex(CompactSizeLen), P.bytes(CompactSize)])), P.coders.dict())),
275
- });
541
+ })))();
276
542
  function validatePSBTFields(version, info, lst) {
277
- for (const k in lst) {
543
+ const _lst = lst;
544
+ // Enforce the BIP174/BIP370 field-table columns directly: reject rows whose
545
+ // "Versions Allowing Inclusion" excludes this version and require rows whose
546
+ // "Versions Requiring Inclusion" includes it.
547
+ for (const k in _lst) {
278
548
  if (k === 'unknown')
279
549
  continue;
280
550
  if (!info[k])
@@ -285,14 +555,38 @@ function validatePSBTFields(version, info, lst) {
285
555
  }
286
556
  for (const k in info) {
287
557
  const { reqInc } = PSBTKeyInfo(info[k]);
288
- if (reqInc.includes(version) && lst[k] === undefined)
558
+ if (reqInc.includes(version) && _lst[k] === undefined)
289
559
  throw new Error(`PSBTv${version}: missing required field ${k}`);
290
560
  }
291
561
  }
562
+ /**
563
+ * Removes fields that are not valid for the requested PSBT version.
564
+ * @param version - target PSBT version
565
+ * @param info - PSBT field definition table
566
+ * @param lst - decoded PSBT key map
567
+ * @returns Filtered PSBT key map.
568
+ * @throws If a field cannot be serialized in the requested PSBT version. {@link Error}
569
+ * @example
570
+ * Drop fields that are not allowed in the target PSBT version before encoding.
571
+ * ```ts
572
+ * import { hex } from '@scure/base';
573
+ * import { PSBTInput, cleanPSBTFields } from '@scure/btc-signer/psbt.js';
574
+ * cleanPSBTFields(2, PSBTInput, {
575
+ * txid: hex.decode('0000000000000000000000000000000000000000000000000000000000000001'),
576
+ * index: 0,
577
+ * });
578
+ * ```
579
+ */
292
580
  export function cleanPSBTFields(version, info, lst) {
581
+ anumber(version, 'version');
582
+ validateObject(info, {}, {}, 'info');
583
+ validateObject(lst, {}, {}, 'lst');
584
+ const _lst = lst;
293
585
  const out = {};
294
- for (const _k in lst) {
586
+ for (const _k in _lst) {
295
587
  const k = _k;
588
+ // Serializer-side compatibility filter: preserve unknown pass-through fields, silently drop
589
+ // rows explicitly marked `silentIgnore`, and throw on other rows the target version forbids.
296
590
  if (k !== 'unknown') {
297
591
  if (!info[k])
298
592
  continue;
@@ -303,7 +597,7 @@ export function cleanPSBTFields(version, info, lst) {
303
597
  throw new Error(`Failed to serialize in PSBTv${version}: ${k} but versions allows inclusion=${allowInc}`);
304
598
  }
305
599
  }
306
- out[k] = lst[k];
600
+ out[k] = _lst[k];
307
601
  }
308
602
  return out;
309
603
  }
@@ -314,14 +608,19 @@ function validatePSBT(tx) {
314
608
  validatePSBTFields(version, PSBTInput, i);
315
609
  for (const o of tx.outputs)
316
610
  validatePSBTFields(version, PSBTOutput, o);
317
- // We allow only one empty element at the end of map (compat with bitcoinjs-lib bug)
611
+ // BIP174 defines `<psbt> := <magic> <global-map> <input-map>* <output-map>*`, so after decode the
612
+ // number of input/output maps should match the unsigned tx. PSBTv2 makes the same shape explicit
613
+ // through `inputCount` / `outputCount`. We intentionally violate that strict reading for one case:
614
+ // keep accepting exactly one trailing empty map because the separate bitcoinjs compatibility PSBT
615
+ // fixture corpus still contains that encoding. Anything non-empty or more than one extra map is
616
+ // still rejected here.
318
617
  const inputCount = !version ? tx.global.unsignedTx.inputs.length : tx.global.inputCount;
319
618
  if (tx.inputs.length < inputCount)
320
619
  throw new Error('Not enough inputs');
321
620
  const inputsLeft = tx.inputs.slice(inputCount);
322
621
  if (inputsLeft.length > 1 || (inputsLeft.length && Object.keys(inputsLeft[0]).length))
323
622
  throw new Error(`Unexpected inputs left in tx=${inputsLeft}`);
324
- // Same for inputs
623
+ // Same carve-out for outputs.
325
624
  const outputCount = !version ? tx.global.unsignedTx.outputs.length : tx.global.outputCount;
326
625
  if (tx.outputs.length < outputCount)
327
626
  throw new Error('Not outputs inputs');
@@ -330,21 +629,54 @@ function validatePSBT(tx) {
330
629
  throw new Error(`Unexpected outputs left in tx=${outputsLeft}`);
331
630
  return tx;
332
631
  }
632
+ /**
633
+ * Merges two PSBT key maps while preserving keyed-field uniqueness.
634
+ * @param psbtEnum - PSBT field definition table
635
+ * @param val - new values to merge in
636
+ * @param cur - existing decoded PSBT key map
637
+ * @param allowedFields - fields still allowed to change
638
+ * @param allowUnknown - whether to preserve unknown PSBT fields
639
+ * @returns Merged PSBT key map.
640
+ * @throws If keyed PSBT fields conflict or signed fields would be removed. {@link Error}
641
+ * @example
642
+ * Merge an updated `witnessUtxo` into an existing decoded input map.
643
+ * ```ts
644
+ * import { hex } from '@scure/base';
645
+ * import { PSBTInput, mergeKeyMap } from '@scure/btc-signer/psbt.js';
646
+ * mergeKeyMap(
647
+ * PSBTInput,
648
+ * { witnessUtxo: { amount: 2n, script: new Uint8Array([0x51]) } },
649
+ * {
650
+ * txid: hex.decode('0000000000000000000000000000000000000000000000000000000000000001'),
651
+ * index: 0,
652
+ * }
653
+ * );
654
+ * ```
655
+ */
333
656
  export function mergeKeyMap(psbtEnum, val, cur, allowedFields, allowUnknown) {
334
- const res = { ...cur, ...val };
657
+ validateObject(psbtEnum, {}, {}, 'psbtEnum');
658
+ validateObject(val, {}, {}, 'val');
659
+ if (cur !== undefined)
660
+ validateObject(cur, {}, {}, 'cur');
661
+ if (allowedFields !== undefined)
662
+ aarray(allowedFields, 'allowedFields');
663
+ const _val = val;
664
+ const _cur = cur;
665
+ const _allowedFields = allowedFields;
666
+ const res = { ..._cur, ..._val };
335
667
  // All arguments can be provided as hex
336
668
  for (const k in psbtEnum) {
337
669
  const key = k;
338
670
  const [_, kC, vC] = psbtEnum[key];
339
- const cannotChange = allowedFields && !allowedFields.includes(k);
340
- if (val[k] === undefined && k in val) {
671
+ const cannotChange = _allowedFields && !_allowedFields.includes(k);
672
+ if (_val[k] === undefined && k in _val) {
341
673
  if (cannotChange)
342
674
  throw new Error(`Cannot remove signed field=${k}`);
343
675
  delete res[k];
344
676
  }
345
677
  else if (kC) {
346
- const oldKV = (cur && cur[k] ? cur[k] : []);
347
- let newKV = val[key];
678
+ const oldKV = (_cur && _cur[k] ? _cur[k] : []);
679
+ let newKV = _val[key];
348
680
  if (newKV) {
349
681
  if (!Array.isArray(newKV))
350
682
  throw new Error(`keyMap(${k}): KV pairs should be [k, v][]`);
@@ -389,11 +721,30 @@ export function mergeKeyMap(psbtEnum, val, cur, allowedFields, allowUnknown) {
389
721
  else if (typeof res[k] === 'string') {
390
722
  res[k] = vC.decode(hex.decode(res[k]));
391
723
  }
392
- else if (cannotChange && k in val && cur && cur[k] !== undefined) {
393
- if (!equalBytes(vC.encode(val[k]), vC.encode(cur[k])))
724
+ else if (cannotChange && k in _val && _cur && _cur[k] !== undefined) {
725
+ if (!equalBytes(vC.encode(_val[k]), vC.encode(_cur[k])))
394
726
  throw new Error(`Cannot change signed field=${k}`);
395
727
  }
396
728
  }
729
+ if (allowUnknown && _val.unknown) {
730
+ // Unknown PSBT rows are stripped by default here, but explicit allowUnknown mode is pass-through.
731
+ // Merge them by full serialized unknown key so repeated updates do not clobber earlier opaque rows.
732
+ const map = {};
733
+ for (const [k, v] of _cur?.unknown || [])
734
+ map[hex.encode(PSBTUnknownKey.encode(k))] = [k, v];
735
+ for (const [k, v] of _val.unknown) {
736
+ const kStr = hex.encode(PSBTUnknownKey.encode(k));
737
+ if (map[kStr] === undefined) {
738
+ map[kStr] = [k, v];
739
+ continue;
740
+ }
741
+ const oldVal = hex.encode(BytesInf.encode(map[kStr][1]));
742
+ const newVal = hex.encode(BytesInf.encode(v));
743
+ if (oldVal !== newVal)
744
+ throw new Error(`keyMap(unknown): same key=${kStr} oldVal=${oldVal} newVal=${newVal}`);
745
+ }
746
+ res.unknown = Object.values(map);
747
+ }
397
748
  // Remove unknown keys except the "unknown" array if allowUnknown is true
398
749
  for (const k in res) {
399
750
  if (!psbtEnum[k]) {
@@ -404,6 +755,11 @@ export function mergeKeyMap(psbtEnum, val, cur, allowedFields, allowUnknown) {
404
755
  }
405
756
  return res;
406
757
  }
407
- export const RawPSBTV0 = P.validate(_RawPSBTV0, validatePSBT);
408
- export const RawPSBTV2 = P.validate(_RawPSBTV2, validatePSBT);
409
- //# sourceMappingURL=psbt.js.map
758
+ /** Validated PSBTv0 coder. */
759
+ // This wrapper only layers `validatePSBT`'s PSBTv0 field/count reconciliation on top of
760
+ // `_RawPSBTV0`; field-specific payload invariants still depend on the nested coders/tables.
761
+ export const RawPSBTV0 = /* @__PURE__ */ (() => Object.freeze(P.validate(_RawPSBTV0, validatePSBT)))();
762
+ /** Validated PSBTv2 coder. */
763
+ // This wrapper only layers `validatePSBT`'s PSBTv2 required-field/count reconciliation on top
764
+ // of `_RawPSBTV2`; nested input/output/global field invariants still depend on the coders below.
765
+ export const RawPSBTV2 = /* @__PURE__ */ (() => Object.freeze(P.validate(_RawPSBTV2, validatePSBT)))();