@scure/btc-signer 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/script.ts CHANGED
@@ -1,10 +1,24 @@
1
1
  import * as P from 'micro-packed';
2
- import { isBytes, reverseObject, type ValueOf, type Bytes } from './utils.ts';
2
+ import { isBytes, reverseObject, type ValueOf, type Bytes, type TArg, type TRet } from './utils.ts';
3
3
 
4
+ /**
5
+ * Maximum byte size allowed for a single pushed script element.
6
+ * BIP 342 keeps this 520-byte stack-element limit even though tapscript removes
7
+ * the old 10,000-byte overall script-size cap.
8
+ */
4
9
  export const MAX_SCRIPT_BYTE_LENGTH = 520;
5
10
 
6
11
  // prettier-ignore
7
- export const OP = {
12
+ /**
13
+ * Bitcoin Script opcode table.
14
+ * @example
15
+ * Use opcode numbers when you need the raw byte form instead of Script mnemonics.
16
+ * ```ts
17
+ * import { OP } from '@scure/btc-signer/script.js';
18
+ * new Uint8Array([OP.OP_1, OP.OP_2, OP.CHECKMULTISIG]);
19
+ * ```
20
+ */
21
+ export const OP = /* @__PURE__ */ Object.freeze({
8
22
  OP_0: 0, PUSHDATA1: 76, PUSHDATA2: 77, PUSHDATA4: 78, '1NEGATE': 79,
9
23
  RESERVED: 80,
10
24
  OP_1: 81, OP_2: 82, OP_3: 83, OP_4: 84, OP_5: 85, OP_6: 86, OP_7: 87, OP_8: 88, OP_9: 89,
@@ -33,16 +47,43 @@ export const OP = {
33
47
  CHECKSIGADD: 186,
34
48
  // Invalid
35
49
  INVALID: 255,
36
- };
50
+ });
37
51
 
38
- export const OPNames = reverseObject(OP);
52
+ /**
53
+ * Reverse lookup map from opcode numbers back to names.
54
+ * @example
55
+ * Turn parsed opcode numbers back into their mnemonic names.
56
+ * ```ts
57
+ * import { OP, OPNames } from '@scure/btc-signer/script.js';
58
+ * OPNames[OP.CHECKSIG];
59
+ * ```
60
+ */
61
+ export const OPNames: Record<number, keyof typeof OP> = /* @__PURE__ */ (() =>
62
+ Object.freeze(reverseObject(OP) as Record<number, keyof typeof OP>))();
63
+ /** Numeric opcode value from {@link OP}. */
39
64
  export type OP = ValueOf<typeof OP>;
40
65
 
66
+ /** Single script element accepted by the script encoder. */
41
67
  export type ScriptOP = keyof typeof OP | Uint8Array | number;
68
+ /** Parsed Bitcoin script as a list of script elements. */
42
69
  export type ScriptType = ScriptOP[];
43
70
 
44
71
  // We can encode almost any number as ScriptNum, however, parsing will be a problem
45
72
  // since we can't know if buffer is a number or something else.
73
+ /**
74
+ * Coder for Bitcoin Script numbers.
75
+ * bytesLimit only constrains decode. encode still serializes any bigint in
76
+ * Script's signed-magnitude byte form so higher-level consumers can enforce
77
+ * opcode-specific 4-byte or 5-byte bounds separately.
78
+ * @param bytesLimit - maximum decoded length in bytes
79
+ * @param forceMinimal - whether to reject non-minimal encodings
80
+ * @returns Script number coder.
81
+ * @example
82
+ * Encode a small integer using Script number rules.
83
+ * ```ts
84
+ * ScriptNum().encode(1n);
85
+ * ```
86
+ */
46
87
  export function ScriptNum(bytesLimit = 6, forceMinimal = false): P.CoderType<bigint> {
47
88
  return P.wrap({
48
89
  encodeStream: (w: P.Writer, value: bigint) => {
@@ -84,7 +125,25 @@ export function ScriptNum(bytesLimit = 6, forceMinimal = false): P.CoderType<big
84
125
  });
85
126
  }
86
127
 
87
- export function OpToNum(op: ScriptOP, bytesLimit = 4, forceMinimal = true): number | undefined {
128
+ /**
129
+ * Attempts to decode a numeric script element into a JavaScript number.
130
+ * Accepts decoded small integers already represented as JS numbers and pushed
131
+ * `ScriptNum` byte payloads, but does not interpret opcode mnemonics like `1NEGATE`.
132
+ * @param op - script element to decode
133
+ * @param bytesLimit - maximum encoded length in bytes
134
+ * @param forceMinimal - whether to enforce minimal `ScriptNum` encoding
135
+ * @returns Decoded number, or `undefined` when the element is not a JS number or valid `ScriptNum` bytes.
136
+ * @example
137
+ * Decode a script element back into a JavaScript number when possible.
138
+ * ```ts
139
+ * OpToNum(1);
140
+ * ```
141
+ */
142
+ export function OpToNum(
143
+ op: TArg<ScriptOP>,
144
+ bytesLimit = 4,
145
+ forceMinimal = true
146
+ ): number | undefined {
88
147
  if (typeof op === 'number') return op;
89
148
  if (isBytes(op)) {
90
149
  try {
@@ -98,6 +157,28 @@ export function OpToNum(op: ScriptOP, bytesLimit = 4, forceMinimal = true): numb
98
157
  return;
99
158
  }
100
159
 
160
+ // Shared raw pushdata-length parser. Keep this aligned with Script.decodeStream
161
+ // so byte-preserving walkers can skip pushed data without semantic decode/re-encode.
162
+ /**
163
+ * Returns the pushed-data length for a push opcode.
164
+ * @param op - opcode byte already read from the script stream
165
+ * @param read - callback that reads the following 1/2/4-byte little-endian length
166
+ * @returns Push length for data-carrying opcodes, or `undefined` for non-push opcodes.
167
+ * @throws If the opcode falls through the recognized push-opcode set unexpectedly.
168
+ * {@link Error}
169
+ */
170
+ export const scriptPushLen = (
171
+ op: number,
172
+ read: (bytes: 1 | 2 | 4) => number
173
+ ): number | undefined => {
174
+ if (!(OP.OP_0 < op && op <= OP.PUSHDATA4)) return;
175
+ if (op < OP.PUSHDATA1) return op;
176
+ if (op === OP.PUSHDATA1) return read(1);
177
+ if (op === OP.PUSHDATA2) return read(2);
178
+ if (op === OP.PUSHDATA4) return read(4);
179
+ throw new Error('Should be not possible');
180
+ };
181
+
101
182
  // Converts script bytes to parsed script
102
183
  // 5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae
103
184
  // =>
@@ -107,67 +188,84 @@ export function OpToNum(op: ScriptOP, bytesLimit = 4, forceMinimal = true): numb
107
188
  // 030000000000000000000000000000000000000000000000000000000000000003
108
189
  // OP_3
109
190
  // CHECKMULTISIG
110
- export const Script: P.CoderType<ScriptType> = P.wrap({
111
- encodeStream: (w: P.Writer, value: ScriptType) => {
112
- for (let o of value) {
113
- if (typeof o === 'string') {
114
- if (OP[o] === undefined) throw new Error(`Unknown opcode=${o}`);
115
- w.byte(OP[o]);
116
- continue;
117
- } else if (typeof o === 'number') {
118
- if (o === 0x00) {
119
- w.byte(0x00);
120
- continue;
121
- } else if (1 <= o && o <= 16) {
122
- w.byte(OP.OP_1 - 1 + o);
123
- continue;
191
+ // This is a semantic AST codec, not a byte-preserving parser: encode
192
+ // canonicalizes small integers to OP_n and chooses the shortest push opcode,
193
+ // so decode(...)+encode(...) rewrites non-minimal push spellings.
194
+ /**
195
+ * Bitcoin script coder.
196
+ * @example
197
+ * Encode a short script from opcode mnemonics and small integers.
198
+ * ```ts
199
+ * Script.encode(['OP_1', 'OP_2']);
200
+ * ```
201
+ */
202
+ export const Script: TRet<P.CoderType<ScriptType>> = /* @__PURE__ */ (() =>
203
+ Object.freeze(
204
+ P.wrap({
205
+ encodeStream: (w: P.Writer, value: TArg<ScriptType>) => {
206
+ for (let o of value) {
207
+ if (typeof o === 'string') {
208
+ if (OP[o] === undefined) throw new Error(`Unknown opcode=${o}`);
209
+ w.byte(OP[o]);
210
+ continue;
211
+ } else if (typeof o === 'number') {
212
+ if (o === 0x00) {
213
+ w.byte(0x00);
214
+ continue;
215
+ } else if (o === -1) {
216
+ // BIP62 minimal push / number rules spell numeric -1 as OP_1NEGATE
217
+ // instead of a pushed ScriptNum payload 0x81.
218
+ w.byte(OP['1NEGATE']);
219
+ continue;
220
+ } else if (1 <= o && o <= 16) {
221
+ w.byte(OP.OP_1 - 1 + o);
222
+ continue;
223
+ }
224
+ }
225
+ // Encode big numbers
226
+ if (typeof o === 'number') o = ScriptNum().encode(BigInt(o));
227
+ if (!isBytes(o)) throw new Error(`Wrong Script OP=${o} (${typeof o})`);
228
+ // Bytes
229
+ const len = o.length;
230
+ if (len < OP.PUSHDATA1) w.byte(len);
231
+ else if (len <= 0xff) {
232
+ w.byte(OP.PUSHDATA1);
233
+ w.byte(len);
234
+ } else if (len <= 0xffff) {
235
+ w.byte(OP.PUSHDATA2);
236
+ w.bytes(P.U16LE.encode(len));
237
+ } else {
238
+ w.byte(OP.PUSHDATA4);
239
+ w.bytes(P.U32LE.encode(len));
240
+ }
241
+ w.bytes(o);
124
242
  }
125
- }
126
- // Encode big numbers
127
- if (typeof o === 'number') o = ScriptNum().encode(BigInt(o));
128
- if (!isBytes(o)) throw new Error(`Wrong Script OP=${o} (${typeof o})`);
129
- // Bytes
130
- const len = o.length;
131
- if (len < OP.PUSHDATA1) w.byte(len);
132
- else if (len <= 0xff) {
133
- w.byte(OP.PUSHDATA1);
134
- w.byte(len);
135
- } else if (len <= 0xffff) {
136
- w.byte(OP.PUSHDATA2);
137
- w.bytes(P.U16LE.encode(len));
138
- } else {
139
- w.byte(OP.PUSHDATA4);
140
- w.bytes(P.U32LE.encode(len));
141
- }
142
- w.bytes(o);
143
- }
144
- },
145
- decodeStream: (r: P.Reader): ScriptType => {
146
- const out: ScriptType = [];
147
- while (!r.isEnd()) {
148
- const cur = r.byte();
149
- // if 0 < cur < 78
150
- if (OP.OP_0 < cur && cur <= OP.PUSHDATA4) {
151
- let len;
152
- if (cur < OP.PUSHDATA1) len = cur;
153
- else if (cur === OP.PUSHDATA1) len = P.U8.decodeStream(r);
154
- else if (cur === OP.PUSHDATA2) len = P.U16LE.decodeStream(r);
155
- else if (cur === OP.PUSHDATA4) len = P.U32LE.decodeStream(r);
156
- else throw new Error('Should be not possible');
157
- out.push(r.bytes(len));
158
- } else if (cur === 0x00) {
159
- out.push(0);
160
- } else if (OP.OP_1 <= cur && cur <= OP.OP_16) {
161
- out.push(cur - (OP.OP_1 - 1));
162
- } else {
163
- const op = OPNames[cur] as keyof typeof OP;
164
- if (op === undefined) throw new Error(`Unknown opcode=${cur.toString(16)}`);
165
- out.push(op);
166
- }
167
- }
168
- return out;
169
- },
170
- });
243
+ },
244
+ decodeStream: (r: P.Reader): TRet<ScriptType> => {
245
+ const out: ScriptType = [];
246
+ while (!r.isEnd()) {
247
+ const cur = r.byte();
248
+ const len = scriptPushLen(cur, (bytes) => {
249
+ if (bytes === 1) return P.U8.decodeStream(r);
250
+ if (bytes === 2) return P.U16LE.decodeStream(r);
251
+ return P.U32LE.decodeStream(r);
252
+ });
253
+ if (len !== undefined) {
254
+ out.push(r.bytes(len));
255
+ } else if (cur === 0x00) {
256
+ out.push(0);
257
+ } else if (OP.OP_1 <= cur && cur <= OP.OP_16) {
258
+ out.push(cur - (OP.OP_1 - 1));
259
+ } else {
260
+ const op = OPNames[cur] as keyof typeof OP;
261
+ if (op === undefined) throw new Error(`Unknown opcode=${cur.toString(16)}`);
262
+ out.push(op);
263
+ }
264
+ }
265
+ return out as TRet<ScriptType>;
266
+ },
267
+ })
268
+ ))() as TRet<P.CoderType<ScriptType>>;
171
269
 
172
270
  // BTC specific variable length integer encoding
173
271
  // https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
@@ -176,73 +274,211 @@ const CSLimits: Record<number, [number, number, bigint, bigint]> = {
176
274
  0xfe: [0xfe, 4, 65536n, 4294967295n],
177
275
  0xff: [0xff, 8, 4294967296n, 18446744073709551615n],
178
276
  };
179
- export const CompactSize: P.CoderType<bigint> = P.wrap({
180
- encodeStream: (w: P.Writer, value: bigint) => {
181
- if (typeof value === 'number') value = BigInt(value);
182
- if (0n <= value && value <= 252n) return w.byte(Number(value));
183
- for (const [flag, bytes, start, stop] of Object.values(CSLimits)) {
184
- if (start > value || value > stop) continue;
185
- w.byte(flag);
186
- for (let i = 0; i < bytes; i++) w.byte(Number((value >> (8n * BigInt(i))) & 0xffn));
187
- return;
188
- }
189
- throw w.err(`VarInt too big: ${value}`);
190
- },
191
- decodeStream: (r: P.Reader): bigint => {
192
- const b0 = r.byte();
193
- if (b0 <= 0xfc) return BigInt(b0);
194
- const [_, bytes, start] = CSLimits[b0];
195
- let num = 0n;
196
- for (let i = 0; i < bytes; i++) num |= BigInt(r.byte()) << (8n * BigInt(i));
197
- if (num < start) throw r.err(`Wrong CompactSize(${8 * bytes})`);
198
- return num;
199
- },
200
- });
277
+ /**
278
+ * Bitcoin CompactSize integer coder.
279
+ * @example
280
+ * Encode a CompactSize integer for wire serialization.
281
+ * ```ts
282
+ * CompactSize.encode(1n);
283
+ * ```
284
+ */
285
+ export const CompactSize: P.CoderType<bigint> = /* @__PURE__ */ (() =>
286
+ Object.freeze(
287
+ P.wrap({
288
+ encodeStream: (w: P.Writer, value: bigint) => {
289
+ if (typeof value === 'number') value = BigInt(value);
290
+ if (0n <= value && value <= 252n) return w.byte(Number(value));
291
+ for (const [flag, bytes, start, stop] of Object.values(CSLimits)) {
292
+ if (start > value || value > stop) continue;
293
+ w.byte(flag);
294
+ for (let i = 0; i < bytes; i++) w.byte(Number((value >> (8n * BigInt(i))) & 0xffn));
295
+ return;
296
+ }
297
+ throw w.err(`VarInt too big: ${value}`);
298
+ },
299
+ decodeStream: (r: P.Reader): bigint => {
300
+ const b0 = r.byte();
301
+ if (b0 <= 0xfc) return BigInt(b0);
302
+ const [_, bytes, start] = CSLimits[b0];
303
+ let num = 0n;
304
+ for (let i = 0; i < bytes; i++) num |= BigInt(r.byte()) << (8n * BigInt(i));
305
+ // BIP 152 / BIP 174: CompactSize fields must use the shortest encoding,
306
+ // so wider prefixes for smaller values are rejected here.
307
+ if (num < start) throw r.err(`Wrong CompactSize(${8 * bytes})`);
308
+ return num;
309
+ },
310
+ })
311
+ ))();
201
312
 
202
313
  // Same thing, but in number instead of bigint. Checks for safe integer inside
203
- export const CompactSizeLen: P.CoderType<number> = P.apply(CompactSize, P.coders.numberBigint);
314
+ /**
315
+ * CompactSize coder that decodes into JavaScript numbers.
316
+ * @example
317
+ * Use the number-based CompactSize helper when the value fits a JS number.
318
+ * ```ts
319
+ * CompactSizeLen.encode(1);
320
+ * ```
321
+ */
322
+ export const CompactSizeLen: P.CoderType<number> = /* @__PURE__ */ (() =>
323
+ Object.freeze(P.apply(CompactSize, P.coders.numberBigint)))();
204
324
 
205
325
  // ui8a of size <CompactSize>
206
- export const VarBytes: P.CoderType<Bytes> = P.bytes(CompactSize);
326
+ // Keep an unwrapped local coder for raw transaction structs. The exported VarBytes surface uses
327
+ // TRet for declaration stability, but RawInput/RawOutput still need the old internal field shape.
328
+ const _VarBytes = /* @__PURE__ */ (() => Object.freeze(P.bytes(CompactSize)))();
329
+ /**
330
+ * Length-prefixed byte array coder.
331
+ * @example
332
+ * Prefix a byte array with its CompactSize length.
333
+ * ```ts
334
+ * VarBytes.encode(new Uint8Array([1, 2, 3]));
335
+ * ```
336
+ */
337
+ export const VarBytes: TRet<P.CoderType<Bytes>> = _VarBytes as TRet<P.CoderType<Bytes>>;
207
338
 
208
339
  // SegWit v0 stack of witness buffers
209
- export const RawWitness: P.CoderType<Bytes[]> = P.array(CompactSizeLen, VarBytes);
340
+ // Raw witness serialization is reused across witness versions and PSBT
341
+ // finalScriptWitness values; version-specific stack and element limits are
342
+ // enforced by the spending rules, not by this byte codec.
343
+ // Same split for raw witness stacks: export the wrapped surface, but keep the raw tx struct coder
344
+ // on the unwrapped local alias so witness arrays inside RawTx keep their previous internal shape.
345
+ const _RawWitness = /* @__PURE__ */ (() => Object.freeze(P.array(CompactSizeLen, _VarBytes)))();
346
+ /**
347
+ * SegWit witness stack coder.
348
+ * @example
349
+ * Encode one witness stack for a SegWit input.
350
+ * ```ts
351
+ * RawWitness.encode([new Uint8Array([1])]);
352
+ * ```
353
+ */
354
+ export const RawWitness: TRet<P.CoderType<Bytes[]>> = _RawWitness as TRet<P.CoderType<Bytes[]>>;
210
355
 
211
356
  // Array of size <CompactSize>
357
+ /**
358
+ * Coder for CompactSize-prefixed arrays.
359
+ * @param t - element coder
360
+ * @returns Array coder.
361
+ * @example
362
+ * CompactSize-prefix a small list of fixed-width integers.
363
+ * ```ts
364
+ * import * as P from 'micro-packed';
365
+ * import { BTCArray } from '@scure/btc-signer/script.js';
366
+ * BTCArray(P.U8).encode([1, 2, 3]);
367
+ * ```
368
+ */
212
369
  export const BTCArray = <T>(t: P.CoderType<T>): P.CoderType<T[]> => P.array(CompactSize, t);
213
370
 
214
- export const RawInput = P.struct({
215
- txid: P.bytes(32, true), // hash(prev_tx),
216
- index: P.U32LE, // output number of previous tx
217
- finalScriptSig: VarBytes, // btc merges input and output script, executes it. If ok = tx passes
218
- sequence: P.U32LE, // ?
219
- });
371
+ /**
372
+ * Raw Bitcoin transaction input coder.
373
+ * @example
374
+ * Encode one transaction input exactly as it appears on the wire.
375
+ * ```ts
376
+ * import { hex } from '@scure/base';
377
+ * import { RawInput } from '@scure/btc-signer/script.js';
378
+ * RawInput.encode({
379
+ * txid: hex.decode('0000000000000000000000000000000000000000000000000000000000000001'),
380
+ * index: 0,
381
+ * finalScriptSig: new Uint8Array([0x51]),
382
+ * sequence: 0xffffffff,
383
+ * });
384
+ * ```
385
+ */
386
+ export const RawInput = /* @__PURE__ */ (() =>
387
+ Object.freeze(
388
+ P.struct({
389
+ txid: P.bytes(32, true), // hash(prev_tx),
390
+ index: P.U32LE, // output number of previous tx
391
+ finalScriptSig: _VarBytes, // btc merges input and output script, executes it. If ok = tx passes
392
+ sequence: P.U32LE, // ?
393
+ })
394
+ ))();
220
395
 
221
- export const RawOutput = P.struct({ amount: P.U64LE, script: VarBytes });
396
+ /**
397
+ * Raw Bitcoin transaction output coder.
398
+ * @example
399
+ * Encode one transaction output with amount and scriptPubKey.
400
+ * ```ts
401
+ * import { RawOutput } from '@scure/btc-signer/script.js';
402
+ * RawOutput.encode({ amount: 1n, script: new Uint8Array([0x51]) });
403
+ * ```
404
+ */
405
+ export const RawOutput = /* @__PURE__ */ (() =>
406
+ Object.freeze(P.struct({ amount: P.U64LE, script: _VarBytes })))();
222
407
 
223
408
  // https://en.bitcoin.it/wiki/Protocol_documentation#tx
224
- const _RawTx = P.struct({
225
- version: P.I32LE,
226
- segwitFlag: P.flag(new Uint8Array([0x00, 0x01])),
227
- inputs: BTCArray(RawInput),
228
- outputs: BTCArray(RawOutput),
229
- witnesses: P.flagged('segwitFlag', P.array('inputs/length', RawWitness)),
230
- // < 500000000 Block number at which this transaction is unlocked
231
- // >= 500000000 UNIX timestamp at which this transaction is unlocked
232
- // Handled as part of PSBTv2
233
- lockTime: P.U32LE,
234
- });
409
+ const _RawTx = /* @__PURE__ */ (() =>
410
+ P.struct({
411
+ version: P.I32LE,
412
+ segwitFlag: P.flag(new Uint8Array([0x00, 0x01])),
413
+ inputs: BTCArray(RawInput),
414
+ outputs: BTCArray(RawOutput),
415
+ // BIP144 does not encode a witness-count field; one RawWitness entry is
416
+ // implied for each txin and follows the same order as inputs.
417
+ witnesses: P.flagged('segwitFlag', P.array('inputs/length', _RawWitness)),
418
+ // < 500000000 Block number at which this transaction is unlocked
419
+ // >= 500000000 UNIX timestamp at which this transaction is unlocked
420
+ // Handled as part of PSBTv2
421
+ lockTime: P.U32LE,
422
+ }))();
235
423
 
236
424
  function validateRawTx(tx: P.UnwrapCoder<typeof _RawTx>) {
237
- if (tx.segwitFlag && tx.witnesses && !tx.witnesses.length)
238
- throw new Error('Segwit flag with empty witnesses array');
425
+ // BIP 144: if every per-input witness field is empty, callers must use the old
426
+ // serialization format instead of the marker/flag witness form.
427
+ if (tx.segwitFlag && tx.witnesses && tx.witnesses.every((w) => !w.length))
428
+ throw new Error('Segwit flag with only empty witness fields');
239
429
  return tx;
240
430
  }
241
- export const RawTx: typeof _RawTx = P.validate(_RawTx, validateRawTx);
431
+ /**
432
+ * Raw Bitcoin transaction coder.
433
+ * @example
434
+ * Encode a SegWit transaction with one input, one output, and one witness stack.
435
+ * ```ts
436
+ * import { hex } from '@scure/base';
437
+ * import { RawTx } from '@scure/btc-signer/script.js';
438
+ * RawTx.encode({
439
+ * version: 2,
440
+ * segwitFlag: true,
441
+ * inputs: [{
442
+ * txid: hex.decode('0000000000000000000000000000000000000000000000000000000000000001'),
443
+ * index: 0,
444
+ * finalScriptSig: new Uint8Array(),
445
+ * sequence: 0xffffffff,
446
+ * }],
447
+ * outputs: [{ amount: 1n, script: new Uint8Array([0x51]) }],
448
+ * witnesses: [[new Uint8Array([1])]],
449
+ * lockTime: 0,
450
+ * });
451
+ * ```
452
+ */
453
+ export const RawTx: typeof _RawTx = /* @__PURE__ */ (() =>
454
+ Object.freeze(P.validate(_RawTx, validateRawTx)))();
242
455
  // Pre-SegWit serialization format (for PSBTv0)
243
- export const RawOldTx = P.struct({
244
- version: P.I32LE,
245
- inputs: BTCArray(RawInput),
246
- outputs: BTCArray(RawOutput),
247
- lockTime: P.U32LE,
248
- });
456
+ /**
457
+ * Pre-SegWit transaction coder used by PSBTv0.
458
+ * @example
459
+ * Encode the legacy unsigned transaction format used inside PSBTv0 globals.
460
+ * ```ts
461
+ * import { hex } from '@scure/base';
462
+ * import { RawOldTx } from '@scure/btc-signer/script.js';
463
+ * RawOldTx.encode({
464
+ * version: 2,
465
+ * inputs: [{
466
+ * txid: hex.decode('0000000000000000000000000000000000000000000000000000000000000001'),
467
+ * index: 0,
468
+ * finalScriptSig: new Uint8Array(),
469
+ * sequence: 0xffffffff,
470
+ * }],
471
+ * outputs: [{ amount: 1n, script: new Uint8Array([0x51]) }],
472
+ * lockTime: 0,
473
+ * });
474
+ * ```
475
+ */
476
+ export const RawOldTx = /* @__PURE__ */ (() =>
477
+ Object.freeze(
478
+ P.struct({
479
+ version: P.I32LE,
480
+ inputs: BTCArray(RawInput),
481
+ outputs: BTCArray(RawOutput),
482
+ lockTime: P.U32LE,
483
+ })
484
+ ))();